From 85f90604bd46dd8eaddf044e2acfcda66d49a78c Mon Sep 17 00:00:00 2001 From: Chris Siebert Date: Sat, 14 Jun 2025 03:34:42 -0400 Subject: [PATCH] Fixed behavior of SHA, SHX, SHY, and SHS in NesHawk (squshed PR #4309) * Fixed incorrect behavior of SHA, SHX, SHY, and SHS Opcodes $93, $9B, $9C, $9E, and $9F, also referred to as the "Unstable High Byte Group", all rely on the High Byte of the target address being incremented internally when the processor adds the X or Y register as an offset, and this new value of the high byte is ANDed with the value being stored. * Fixed a typo In the comment made for private byte H, changed the word "varaible" to "variable" * Added H to the 6502 SyncState Added H to the 6502 SyncState, which is used during the final cycles of a few unofficial instructions. * Updated LxaConstant for Chip6510.cs This now passes Disk2.d64's "LXAB" test. Does this need to change in Drive1541.cs as well? * Updated AneConstant for Chip6510.cs This now passes Disk2.d64's "TRAP1" test. Does this need to change in Drive1541.cs as well? * Updated the 6502's unstable high byte group for RDY beahvior. SHA, for example, typically writes a value of A & X & H, but if the RDY line is low 2 cycles before the write occurs, (tested with a DMC DMA) the value written is just A & X. To recreate that behavior, H is set to $FF if the RDY line is low during that particular stage of the instruction. * Removed trailing whitespace added in previous commit * The 6502 ext_ppu_cycle variable is now updated in NesHawk The ext_ppu_cycle was only used in SubNesHawk, though it would still appear in the tracelogger for NesHawk, just always with a value of 0. This value is now incremented at the end of runppu() in PPU.cs, and reset at the beginning of a frame inside DoFrame() of SubNesHawk, and FrameAdvance() for NesHawk. * Fixed formatting of a line added in the previous commit. Added spaces around the equal sign. * Added the 6502 address bus, and DMC DMA Halt cycles occur on the correct cycles Added the 16 bit address bus as a public variable to Execute.cs The 6502 Address Bus is not updated by the DMC DMA or OAM DMA. The DMC DMA's "halt cycles" and "put cycles" read from wherever the 6502 address bus currently is. This can result in extra reads from read-sensitive PPU and APU registers. Likewise, APU Register Activation is tied to the 6502 address bus. (Previously using the value of the PC, as the address bus will be the value of the PC during OAM DMAs) * Removed unnecessary includes I have no idea what added these, but it was a mistake. * Added the 6502 address bus to the SyncState Added the 6502 address bus to the SyncState * Updated controller strobing and clocking Controllers only get strobed when the CPU transitions from a get cycle to a put cycle. Controllers only get clocked if the previous CPU cycle was not reading from the controller port. * Updating timing of clearing the Frame Counter Interrupt Flag The Frame Counter Interrupt Flag is only cleared on "put" cycles. This can be detected by running an instruction that double-reads address $4015, such as the unofficial SLO $4015, X instruction. The Value in the A register after that instruction will have different results depending on if the cycle this ran on was a get or a put cycle, since the flag is only cleared on the put cycle after the read occured. * Update src/BizHawk.Emulation.Cores/CPUs/MOS 6502X/Execute.cs Co-authored-by: YoshiRulz * Update src/BizHawk.Emulation.Cores/CPUs/MOS 6502X/Execute.cs Co-authored-by: YoshiRulz * Update src/BizHawk.Emulation.Cores/CPUs/MOS 6502X/Execute.cs Co-authored-by: YoshiRulz * Update src/BizHawk.Emulation.Cores/CPUs/MOS 6502X/Execute.cs Co-authored-by: YoshiRulz * Update src/BizHawk.Emulation.Cores/CPUs/MOS 6502X/Execute.cs Co-authored-by: YoshiRulz * Update src/BizHawk.Emulation.Cores/CPUs/MOS 6502X/Execute.cs Co-authored-by: YoshiRulz * Update src/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Chip6510.cs Co-authored-by: YoshiRulz * Update src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/APU.cs Co-authored-by: YoshiRulz * Update src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs Co-authored-by: YoshiRulz * Update src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs Co-authored-by: YoshiRulz * Fixed a typo * Updated formatting, and fixed a potential bug. Two read cycles in a row, one from either controller port, will still clock both controllers once. * Update src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs Co-authored-by: YoshiRulz * Updated sprite evaluation for misaligned OAM If a write to $2003 occurs on a visible scanline before sprite evaluation, the initial index into OAM for the evaluation will not begin at OAM index 0. If the OAM index was not a multiple of 4 when this occurs, an interesting phenomenon becomes visible, where the lower two bits of the OAM index are cleared if the Y position is out of range for the scanline. Surprisingly, the X position makes this same "in range" check, resulting in the lower two bits being cleared if not in range. * Updated glitchy increment of OAM index when writing to $2004 When this occurs, reg_2003 needs to increment, and this also results in a bitwise AND with $FC. --------- Co-authored-by: YoshiRulz --- .../CPUs/MOS 6502X/Execute.cs | 109 +++++++----- .../CPUs/MOS 6502X/MOS6502X.cs | 4 +- .../Computers/Commodore64/MOS/Chip6510.cs | 4 +- .../Consoles/Nintendo/NES/APU.cs | 26 ++- .../Consoles/Nintendo/NES/NES.Core.cs | 156 ++++++++---------- .../Consoles/Nintendo/NES/NES.IStatable.cs | 7 - .../Consoles/Nintendo/NES/PPU.cs | 2 +- .../Consoles/Nintendo/NES/PPU.regs.cs | 25 +-- .../Consoles/Nintendo/NES/PPU.run.cs | 43 ++--- .../SubNESHawk/SubNESHawk.IEmulator.cs | 2 +- 10 files changed, 190 insertions(+), 188 deletions(-) diff --git a/src/BizHawk.Emulation.Cores/CPUs/MOS 6502X/Execute.cs b/src/BizHawk.Emulation.Cores/CPUs/MOS 6502X/Execute.cs index b964d8080f..ea59cdf22b 100644 --- a/src/BizHawk.Emulation.Cores/CPUs/MOS 6502X/Execute.cs +++ b/src/BizHawk.Emulation.Cores/CPUs/MOS 6502X/Execute.cs @@ -196,7 +196,7 @@ namespace BizHawk.Emulation.Cores.Components.M6502 /*TYA [implied]*/ new Uop[] { Uop.Imp_TYA, Uop.End }, /*STA addr,Y [absolute indexed WRITE]*/ new Uop[] { Uop.Fetch2, Uop.AbsIdx_Stage3_Y, Uop.AbsIdx_Stage4, Uop.AbsIdx_WRITE_Stage5_STA, Uop.End }, /*TXS [implied]*/ new Uop[] { Uop.Imp_TXS, Uop.End }, - /*SHS* addr,Y [absolute indexed WRITE Y] [unofficial] */ new Uop[] { Uop.Fetch2, Uop.AbsIdx_Stage3_Y, Uop.AbsIdx_Stage4, Uop.AbsIdx_WRITE_Stage5_ERROR, Uop.End }, + /*SHS* addr,Y [absolute indexed WRITE Y] [unofficial] */ new Uop[] { Uop.Fetch2, Uop.AbsIdx_Stage3_Y, Uop.AbsIdx_Stage4_SHS, Uop.AbsIdx_WRITE_Stage5_SHS, Uop.End }, /*SHY** [absolute indexed WRITE] [unofficial]*/ new Uop[] { Uop.Fetch2, Uop.AbsIdx_Stage3_X, Uop.AbsIdx_Stage4_SHY, Uop.AbsIdx_WRITE_Stage5_SHY, Uop.End }, /*STA addr,X [absolute indexed WRITE]*/ new Uop[] { Uop.Fetch2, Uop.AbsIdx_Stage3_X, Uop.AbsIdx_Stage4, Uop.AbsIdx_WRITE_Stage5_STA, Uop.End }, /*SHX* addr,Y [absolute indexed WRITE Y] [unofficial]*/ new Uop[] { Uop.Fetch2, Uop.AbsIdx_Stage3_Y, Uop.AbsIdx_Stage4_SHX, Uop.AbsIdx_WRITE_Stage5_SHX, Uop.End }, @@ -416,7 +416,7 @@ namespace BizHawk.Emulation.Cores.Components.M6502 //[absolute indexed WRITE] AbsIdx_WRITE_Stage5_STA, AbsIdx_WRITE_Stage5_SHY, AbsIdx_WRITE_Stage5_SHX, //unofficials - AbsIdx_WRITE_Stage5_ERROR, + AbsIdx_WRITE_Stage5_SHS, //[absolute indexed READ] AbsIdx_READ_Stage4, AbsIdx_READ_Stage5_LDA, AbsIdx_READ_Stage5_CMP, AbsIdx_READ_Stage5_SBC, AbsIdx_READ_Stage5_ADC, AbsIdx_READ_Stage5_EOR, AbsIdx_READ_Stage5_LDX, AbsIdx_READ_Stage5_AND, AbsIdx_READ_Stage5_ORA, AbsIdx_READ_Stage5_LDY, AbsIdx_READ_Stage5_NOP, @@ -474,7 +474,8 @@ namespace BizHawk.Emulation.Cores.Components.M6502 IndIdx_WRITE_Stage5_SHA, AbsIdx_Stage4_SHX, AbsIdx_Stage4_SHY, - AbsIdx_Stage4_SHA + AbsIdx_Stage4_SHA, + AbsIdx_Stage4_SHS, } private void InitOpcodeHandlers() @@ -524,6 +525,8 @@ namespace BizHawk.Emulation.Cores.Components.M6502 public int mi; //microcode index private bool iflag_pending; //iflag must be stored after it is checked in some cases (CLI and SEI). public bool rdy_freeze; //true if the CPU must be frozen + private byte H; //internal temp variable used in the "unstable high byte group" of unofficial instructions + public ushort address_bus; // The 16 bit address bus. //tracks whether an interrupt condition has popped up recently. //not sure if this is real or not but it helps with the branch_irq_hack @@ -605,6 +608,7 @@ namespace BizHawk.Emulation.Cores.Components.M6502 opcode = _link.ReadMemory(PC++); mi = -1; } + address_bus = PC; } private void Fetch2() @@ -622,6 +626,7 @@ namespace BizHawk.Emulation.Cores.Components.M6502 if (RDY) { opcode3 = _link.ReadMemory(PC++); + address_bus = (ushort) ((opcode3 << 8) | opcode2); } } @@ -939,8 +944,10 @@ namespace BizHawk.Emulation.Cores.Components.M6502 if (RDY) { alu_temp = ea + Y; - ea = (_link.ReadMemory((byte)(opcode2 + 1)) << 8) + ea = (_link.ReadMemory((byte) (opcode2 + 1)) << 8) | ((alu_temp & 0xFF)); + address_bus = (ushort) ea; + H = 0; // In preparation for SHA (indirect, X), set H to 0. } } @@ -957,16 +964,20 @@ namespace BizHawk.Emulation.Cores.Components.M6502 private void IndIdx_WRITE_Stage5_SHA() { rdy_freeze = !RDY; + if (RDY) { + H |= (byte) ((ea >> 8) + 1); _link.ReadMemory((ushort) ea); if (alu_temp.Bit(8)) { ea = (ushort) (ea & 0xFF | ((ea + 0x100) & 0xFF00 & ((A & X) << 8))); } - - ea += unchecked((ushort) (alu_temp & 0xFF00)); + } + else + { + H = 0xFF; //If the RDY line is low here, the SHA instruction omits the bitwise AND with H } } @@ -1008,13 +1019,7 @@ namespace BizHawk.Emulation.Cores.Components.M6502 private void IndIdx_WRITE_Stage6_SHA() { - alu_temp = A & X; - - if (RDY) - { - alu_temp &= unchecked((byte) ((ea >> 8) + 1)); - } - + alu_temp = A & X & H; _link.WriteMemory((ushort) ea, unchecked((byte) alu_temp)); } @@ -2045,6 +2050,7 @@ namespace BizHawk.Emulation.Cores.Components.M6502 if (RDY) { ea = _link.ReadMemory((ushort)alu_temp); + address_bus = (ushort) ea; } } @@ -2453,7 +2459,8 @@ namespace BizHawk.Emulation.Cores.Components.M6502 opcode3 = _link.ReadMemory(PC++); alu_temp = opcode2 + Y; ea = (opcode3 << 8) + (alu_temp & 0xFF); - + address_bus = (ushort) ea; + H = 0; // In preparation for SHA, SHS, and SHX, set H to 0. //new Uop[] { Uop.Fetch2, Uop.AbsIdx_Stage3_Y, Uop.AbsIdx_Stage4, Uop.AbsIdx_WRITE_Stage5_STA, Uop.End }, } } @@ -2466,6 +2473,8 @@ namespace BizHawk.Emulation.Cores.Components.M6502 opcode3 = _link.ReadMemory(PC++); alu_temp = opcode2 + X; ea = (opcode3 << 8) + (alu_temp & 0xFF); + address_bus = (ushort) ea; + H = 0; // In preparation for SHY, set H to 0. } } @@ -2509,6 +2518,7 @@ namespace BizHawk.Emulation.Cores.Components.M6502 if (RDY) { + H |= (byte) ((ea >> 8) + 1); var adjust = alu_temp.Bit(8); alu_temp = _link.ReadMemory((ushort) ea); @@ -2517,6 +2527,10 @@ namespace BizHawk.Emulation.Cores.Components.M6502 ea = (ushort) (ea & 0xFF | ((ea + 0x100) & 0xFF00 & (X << 8))); } } + else + { + H = 0xFF; //If the RDY line is low here, the SHX instruction omits the bitwise AND with H + } } private void AbsIdx_Stage4_SHY() @@ -2525,6 +2539,7 @@ namespace BizHawk.Emulation.Cores.Components.M6502 if (RDY) { + H |= (byte) ((ea >> 8) + 1); var adjust = alu_temp.Bit(8); alu_temp = _link.ReadMemory((ushort) ea); @@ -2533,6 +2548,10 @@ namespace BizHawk.Emulation.Cores.Components.M6502 ea = (ushort) (ea & 0xFF | ((ea + 0x100) & 0xFF00 & (Y << 8))); } } + else + { + H = 0xFF; //If the RDY line is low here, the SHY instruction omits the bitwise AND with H + } } private void AbsIdx_Stage4_SHA() @@ -2541,6 +2560,28 @@ namespace BizHawk.Emulation.Cores.Components.M6502 if (RDY) { + H |= (byte) ((ea >> 8) + 1); + var adjust = alu_temp.Bit(8); + alu_temp = _link.ReadMemory((ushort) ea); + + if (adjust) + { + ea = (ushort) ((ea & 0xFF) | ((ea + 0x100) & 0xFF00 & ((A & X) << 8))); + } + } + else + { + H = 0xFF; //If the RDY line is low here, the SHA instruction omits the bitwise AND with H + } + } + + private void AbsIdx_Stage4_SHS() + { + rdy_freeze = !RDY; + + if (RDY) + { + H |= (byte) ((ea >> 8) + 1); var adjust = alu_temp.Bit(8); alu_temp = _link.ReadMemory((ushort) ea); @@ -2549,6 +2590,10 @@ namespace BizHawk.Emulation.Cores.Components.M6502 ea = (ushort) (ea & 0xFF | ((ea + 0x100) & 0xFF00 & ((A & X) << 8))); } } + else + { + H = 0xFF; //If the RDY line is low here, the SHS instruction omits the bitwise AND with H + } } private void AbsIdx_WRITE_Stage5_STA() @@ -2558,44 +2603,26 @@ namespace BizHawk.Emulation.Cores.Components.M6502 private void AbsIdx_WRITE_Stage5_SHY() { - alu_temp = Y; - - if (RDY) - { - alu_temp &= unchecked((byte)((ea >> 8) + 1)); - } - + alu_temp = Y & H; _link.WriteMemory((ushort) ea, (byte) alu_temp); } private void AbsIdx_WRITE_Stage5_SHX() { - alu_temp = X; - - if (RDY) - { - alu_temp &= unchecked((byte)((ea >> 8) + 1)); - } - + alu_temp = X & H; _link.WriteMemory((ushort) ea, (byte) alu_temp); } private void AbsIdx_WRITE_Stage5_SHA() { - alu_temp = A & X; - - if (RDY) - { - alu_temp &= unchecked((byte)((ea >> 8) + 1)); - } - + alu_temp = A & X & H; _link.WriteMemory((ushort) ea, (byte) alu_temp); } - private void AbsIdx_WRITE_Stage5_ERROR() + private void AbsIdx_WRITE_Stage5_SHS() { S = (byte)(X & A); - _link.WriteMemory((ushort)ea, (byte)(S & (opcode3+1))); + _link.WriteMemory((ushort) ea, (byte) (S & H)); } private void AbsIdx_RMW_Stage5() @@ -3225,11 +3252,12 @@ namespace BizHawk.Emulation.Cores.Components.M6502 case Uop.AbsIdx_Stage4_SHX: AbsIdx_Stage4_SHX(); break; case Uop.AbsIdx_Stage4_SHY: AbsIdx_Stage4_SHY(); break; case Uop.AbsIdx_Stage4_SHA: AbsIdx_Stage4_SHA(); break; + case Uop.AbsIdx_Stage4_SHS: AbsIdx_Stage4_SHS(); break; case Uop.AbsIdx_WRITE_Stage5_STA: AbsIdx_WRITE_Stage5_STA(); break; case Uop.AbsIdx_WRITE_Stage5_SHY: AbsIdx_WRITE_Stage5_SHY(); break; case Uop.AbsIdx_WRITE_Stage5_SHX: AbsIdx_WRITE_Stage5_SHX(); break; case Uop.AbsIdx_WRITE_Stage5_SHA: AbsIdx_WRITE_Stage5_SHA(); break; - case Uop.AbsIdx_WRITE_Stage5_ERROR: AbsIdx_WRITE_Stage5_ERROR(); break; + case Uop.AbsIdx_WRITE_Stage5_SHS: AbsIdx_WRITE_Stage5_SHS(); break; case Uop.AbsIdx_RMW_Stage5: AbsIdx_RMW_Stage5(); break; case Uop.AbsIdx_RMW_Stage7: AbsIdx_RMW_Stage7(); break; case Uop.AbsIdx_RMW_Stage6_DEC: AbsIdx_RMW_Stage6_DEC(); break; @@ -3292,6 +3320,11 @@ namespace BizHawk.Emulation.Cores.Components.M6502 if (!rdy_freeze) mi++; + + if (Microcode[opcode][mi] is Uop.End) + { + address_bus = PC; // If the next cycle is the start of a new instruction, the address bus needs to be set to the PC now, so a DMC DMA's halt cycles don't use the wrong address bus value. + } } public bool AtInstructionStart() diff --git a/src/BizHawk.Emulation.Cores/CPUs/MOS 6502X/MOS6502X.cs b/src/BizHawk.Emulation.Cores/CPUs/MOS 6502X/MOS6502X.cs index 71bf48b041..13ab34b24b 100644 --- a/src/BizHawk.Emulation.Cores/CPUs/MOS 6502X/MOS6502X.cs +++ b/src/BizHawk.Emulation.Cores/CPUs/MOS 6502X/MOS6502X.cs @@ -157,7 +157,7 @@ namespace BizHawk.Emulation.Cores.Components.M6502 public bool NMI; public bool RDY; - // ppu cycle (used with SubNESHawk) + // ppu cycle public int ext_ppu_cycle = 0; public void SyncState(Serializer ser) @@ -184,6 +184,8 @@ namespace BizHawk.Emulation.Cores.Components.M6502 ser.Sync(nameof(branch_irq_hack), ref branch_irq_hack); ser.Sync(nameof(rdy_freeze), ref rdy_freeze); ser.Sync(nameof(ext_ppu_cycle), ref ext_ppu_cycle); + ser.Sync(nameof(H), ref H); + ser.Sync(nameof(address_bus), ref address_bus); ser.EndSection(); } diff --git a/src/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Chip6510.cs b/src/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Chip6510.cs index efb83c245e..7a0bee9559 100644 --- a/src/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Chip6510.cs +++ b/src/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Chip6510.cs @@ -54,8 +54,8 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS _cpu = new MOS6502X(new CpuLink(this)) { // Required to pass the Lorenz test suite. - AneConstant = 0xEF, - LxaConstant = 0xFE + AneConstant = 0xEE, + LxaConstant = 0xEE, }; // perform hard reset diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/APU.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/APU.cs index fdeab530e8..49a37dc5f6 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/APU.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/APU.cs @@ -1032,6 +1032,11 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES dmc.Fetch(); } + public void RunDMCHaltFetch() + { + nes.ReadMemory(nes.cpu.address_bus); + } + private readonly int[][] sequencer_lut = new int[2][]; private int sequencer_check_1, sequencer_check_2; @@ -1298,7 +1303,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES { byte ret = PeekReg(0x4015); // Console.WriteLine("{0} {1,5} $4015 clear irq, was at {2}", nes.Frame, sequencer_counter, sequencer_irq); - sequencer_irq_flag = false; + sequencer_irq_clear_pending = true; SyncIRQ(); return ret; } @@ -1329,6 +1334,25 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES public void RunOneLast() { + if (dmc.timer % 2 is 1) + { + // The controllers only get strobed when transitioning from a get cycle to a put cycle. + if (nes.joypadStrobed) + { + nes.joypadStrobed = false; + nes.strobe_joyport(); + } + } + else + { + // The frame counter interrupt flag is only cleared when transitioning from a put cycle to a get cycle. + if (sequencer_irq_clear_pending) + { + sequencer_irq_clear_pending = false; + sequencer_irq_flag = false; + } + } + // we need to predict if there will be a length clock here, because the sequencer ticks last, but the // timer reload shouldn't happen if length clock and write happen simultaneously // I'm not sure if we can avoid this by simply processing the sequencer first diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs index dde58186f6..d87a402d34 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs @@ -83,6 +83,13 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES public int old_s = 0; + public long double_controller_read = 0; + public ushort double_controller_read_address = 0; + public byte previous_controller1_read = 0; + public byte previous_controller2_read = 0; + public bool joypadStrobed; + public byte joypadStrobeValue; + public bool CanProvideAsync => false; internal void ResetControllerDefinition(bool subframe) @@ -343,6 +350,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES VS_coin_inserted &= 1; } + cpu.ext_ppu_cycle = 0; // Reset this value at the beginning of each frame + if (ppu.ppudead > 0) { while (ppu.ppudead > 0) @@ -443,9 +452,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES public byte oam_dma_byte; public bool dmc_dma_exec = false; public bool dmc_realign; - public bool reread_trigger; - public int do_the_reread_2002, do_the_reread_2007, do_the_reread_cont_1, do_the_reread_cont_2; - public int reread_opp_4016, reread_opp_4017; public byte DB; //old data bus values from previous reads internal void RunCpuOne() @@ -509,45 +515,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES apu.dmc_dma_countdown--; if (apu.dmc_dma_countdown == 0) { - reread_trigger = true; - - do_the_reread_2002++; - - do_the_reread_2007++; - - // if the DMA address has the same bits set as the re-read address, they don't occur - // TODO: need to check if also true for ppu regs - /* - if ((apu.dmc.sample_address & 0x2007) != 0x2002) - { - do_the_reread_2002++; - } - - if ((apu.dmc.sample_address & 0x2007) != 0x2007) - { - do_the_reread_2007++; - } - */ - if ((apu.dmc.sample_address & 0x1F) != 0x16) - { - do_the_reread_cont_1++; - } - - if ((apu.dmc.sample_address & 0x1F) == 0x16) - { - reread_opp_4016++; - } - - if ((apu.dmc.sample_address & 0x1F) != 0x17) - { - do_the_reread_cont_2++; - } - - if ((apu.dmc.sample_address & 0x1F) == 0x17) - { - reread_opp_4017++; - } - apu.RunDMCFetch(); dmc_dma_exec = false; @@ -568,6 +535,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES apu.dmc.fill_glitch_2 = true; } } + else + { + // the DMC DMA Halt, Put cycles + apu.RunDMCHaltFetch(); + + } } ///////////////////////////// @@ -601,17 +574,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES apu.RunOneLast(); - if (reread_trigger && cpu.RDY) - { - do_the_reread_2002 = 0; - do_the_reread_2007 = 0; - do_the_reread_cont_1 = 0; - do_the_reread_cont_2 = 0; - reread_opp_4016 = 0; - reread_opp_4017 = 0; - reread_trigger = false; - } - if (!cpu.RDY && !dmc_dma_exec && !oam_dma_exec) { cpu.RDY = true; @@ -660,24 +622,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES else { // special hardware glitch case + if (dmc_dma_exec && ppu.region is not PPU.Region.NTSC) + { + return DB; + } ret_spec = read_joyport(addr); - //if (reread_trigger && (do_the_reread_cont_1 == 0)) { Console.WriteLine("same 1 " + (apu.dmc.sample_address - 1)); } - - if ((reread_opp_4017 > 0) && ppu.region == PPU.Region.NTSC) - { - read_joyport(0x4017); - //Console.WriteLine("DMC glitch player 2 opposite " + cpu.TotalExecutedCycles + " addr " + (apu.dmc.sample_address - 1)); - } - - if ((do_the_reread_cont_1 > 0) && ppu.region==PPU.Region.NTSC) - { - ret_spec = read_joyport(addr); - do_the_reread_cont_1--; - if (do_the_reread_cont_1 > 0) { ret_spec = read_joyport(addr); } - //Console.WriteLine("DMC glitch player 1 " + cpu.TotalExecutedCycles + " addr " + (apu.dmc.sample_address - 1)); - } - return ret_spec; } case 0x4017: @@ -696,22 +646,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES // special hardware glitch case ret_spec = read_joyport(addr); - //if (reread_trigger && (do_the_reread_cont_2 == 0)) { Console.WriteLine("same 2 " + (apu.dmc.sample_address - 1)); } - - if ((reread_opp_4016 > 0) && ppu.region == PPU.Region.NTSC) - { - read_joyport(0x4016); - //Console.WriteLine("DMC glitch player 1 opposite " + cpu.TotalExecutedCycles + " addr " + (apu.dmc.sample_address - 1)); - } - - if ((do_the_reread_cont_2 > 0) && ppu.region == PPU.Region.NTSC) - { - ret_spec = read_joyport(addr); - do_the_reread_cont_2--; - if (do_the_reread_cont_2 > 0) { ret_spec = read_joyport(addr); } - //Console.WriteLine("DMC glitch player 2 " + cpu.TotalExecutedCycles + " addr " + (apu.dmc.sample_address - 1)); - } - return ret_spec; } default: @@ -838,12 +772,19 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES private void write_joyport(byte value) { - //Console.WriteLine("cont " + value + " frame " + Frame); + joypadStrobeValue = value; + joypadStrobed = true; + } - var si = new StrobeInfo(latched4016, value); + public void strobe_joyport() + { + // The controllers only get strobed when transitioning from a get cycle to a put cycle. + + //Console.WriteLine("cont " + value + " frame " + Frame); + var si = new StrobeInfo(latched4016, joypadStrobeValue); ControllerDeck.Strobe(si, _controller); - latched4016 = value; - new_strobe = (value & 1) > 0; + latched4016 = joypadStrobeValue; + new_strobe = (joypadStrobeValue & 1) is not 0; if (current_strobe && !new_strobe) { controller_was_latched = true; @@ -863,7 +804,32 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES } else { - ret = addr == 0x4016 ? ControllerDeck.ReadA(_controller) : ControllerDeck.ReadB(_controller); + if (TotalExecutedCycles == double_controller_read && addr == double_controller_read_address) + { + if (addr == 0x4016) + { + ret = previous_controller1_read; + } + else + { + ret = previous_controller2_read; + } + } + else + { + if (addr == 0x4016) + { + ret = ControllerDeck.ReadA(_controller); + previous_controller1_read = ret; // If the following CPU cycle is also reading from this controller port, read the same value without clocking the controller. + } + else + { + ret = ControllerDeck.ReadB(_controller); + previous_controller2_read = ret; + } + } + double_controller_read_address = (ushort) addr; + double_controller_read = TotalExecutedCycles + 1; // The shift register in the controller is only updated if the previous CPU cycle did not read from the controller port. } ret &= 0x1f; @@ -997,6 +963,10 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES public byte ReadMemory(ushort addr) { + if (!oam_dma_exec && !dmc_dma_exec) + { + cpu.address_bus = addr; + } byte ret; if (addr >= 0x8000) @@ -1041,7 +1011,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES // this means that OAM DMA can actually access memory that the cpu cannot if (oam_dma_exec) { - if ((cpu.PC >= 0x4000) && (cpu.PC < 0x4020)) + if (cpu.address_bus is >= 0x4000 and < 0x4020) { ret = ReadReg(addr); } @@ -1119,6 +1089,10 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES public void WriteMemory(ushort addr, byte value) { + if (!oam_dma_exec) + { + cpu.address_bus = addr; + } if (addr < 0x0800) { ram[addr] = value; diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.IStatable.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.IStatable.cs index a448bd8a9d..4238fa99d7 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.IStatable.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.IStatable.cs @@ -28,13 +28,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES ser.Sync(nameof(oam_dma_byte), ref oam_dma_byte); ser.Sync(nameof(dmc_dma_exec), ref dmc_dma_exec); ser.Sync(nameof(dmc_realign), ref dmc_realign); - ser.Sync(nameof(reread_trigger), ref reread_trigger); - ser.Sync(nameof(do_the_reread_2002), ref do_the_reread_2002); - ser.Sync(nameof(do_the_reread_2007), ref do_the_reread_2007); - ser.Sync(nameof(do_the_reread_cont_1), ref do_the_reread_cont_1); - ser.Sync(nameof(do_the_reread_cont_2), ref do_the_reread_cont_2); - ser.Sync(nameof(reread_opp_4016), ref reread_opp_4016); - ser.Sync(nameof(reread_opp_4017), ref reread_opp_4017); // VS related ser.Sync("VS", ref _isVS); diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.cs index ea679148df..8b62eba3b3 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.cs @@ -258,7 +258,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES ser.Sync("Spr_zero_in_Range", ref sprite_zero_in_range); ser.Sync(nameof(is_even_cycle), ref is_even_cycle); ser.Sync(nameof(soam_index), ref soam_index); - ser.Sync(nameof(soam_m_index), ref soam_m_index); ser.Sync(nameof(oam_index), ref oam_index); ser.Sync(nameof(oam_index_aux), ref oam_index_aux); ser.Sync(nameof(soam_index_aux), ref soam_index_aux); @@ -438,6 +437,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES nes.Board.ClockPpu(); } _totalCycles += 1; + nes.cpu.ext_ppu_cycle += 1; } } } diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.regs.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.regs.cs index 9b695877a3..1018af6566 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.regs.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.regs.cs @@ -344,25 +344,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES private byte read_2002() { byte ret = peek_2002(); - /* - if (nes.do_the_reread_2002 > 0) - { - if (Reg2002_vblank_active || Reg2002_vblank_active_pending) - Console.WriteLine("reread 2002"); - } - */ // reading from $2002 resets the destination for $2005 and $2006 writes vtoggle = false; Reg2002_vblank_active = 0; Reg2002_vblank_active_pending = false; - if (nes.do_the_reread_2002 > 0) - { - ret = peek_2002(); - // could be another reread, but no other side effects, so don't bother - } - // update the open bus here ppu_open_bus = ret; PpuOpenBusDecay(DecayType.High); @@ -443,6 +430,9 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES { // glitchy increment of OAM index oam_index += 4; + oam_index &= 0x1FC; + reg_2003 += 4; + reg_2003 &= 0xFC; } else { @@ -681,7 +671,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES case 6: return read_2006(); case 7: { - if (nes.cpu.TotalExecutedCycles == double_2007_read) + if (nes.cpu.TotalExecutedCycles == double_2007_read && !nes.dmc_dma_exec) { return ppu_open_bus; } @@ -690,13 +680,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES ret_spec = read_2007(); double_2007_read = nes.cpu.TotalExecutedCycles + 1; } - - if (nes.do_the_reread_2007 > 0) - { - ret_spec = read_2007(); - ret_spec = read_2007(); - // always 2? - } return ret_spec; } default: throw new InvalidOperationException(); diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.run.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.run.cs index 9f93dbd297..c2cef0f937 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.run.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/PPU.run.cs @@ -24,7 +24,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES public byte read_value; public int soam_index; public int soam_index_prev; - public int soam_m_index; public int oam_index; public int oam_index_aux; public int soam_index_aux; @@ -219,7 +218,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES spr_true_count = 0; soam_index = 0; - soam_m_index = 0; oam_index_aux = 0; oam_index = 0; is_even_cycle = true; @@ -297,10 +295,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES if (is_even_cycle) { - if ((oam_index + soam_m_index) < 256) - read_value = OAM[oam_index + soam_m_index]; - else - read_value = OAM[oam_index + soam_m_index - 256]; + read_value = OAM[oam_index & 0xFF]; } else if (sprite_eval_write) { @@ -316,21 +311,24 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES { //a flag gets set if sprite zero is in range if (oam_index == reg_2003) { sprite_zero_in_range = true; } - + oam_index++; spr_true_count++; - soam_m_index++; } else if (spr_true_count > 0 && spr_true_count < 4) { - soam[soam_index * 4 + soam_m_index] = read_value; - - soam_m_index++; + soam[soam_index * 4 + spr_true_count] = read_value; + oam_index++; spr_true_count++; if (spr_true_count == 4) { - oam_index += 4; soam_index++; + // The X coordinate makes the same checks as the Y position to see if "read_value" is in range of the scanline. + if (!(yp >= read_value && yp < read_value + spriteHeight) && spr_true_count == 4) + { + // and if it isn't, clear the lower 2 bits of the OAM index. + oam_index &= 0x1FC; // This is an integer instead of a byte. Bit 8 is used to check if the OAM address overflows. + } if (soam_index == 8) { // oam_index could be pathologically misaligned at this point, so we have to find the next @@ -338,13 +336,14 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES oam_index_aux = (oam_index % 4) * 4; } - soam_m_index = 0; spr_true_count = 0; } } else { + // This object is out of the range of the scanline oam_index += 4; + oam_index &= 0x1FC; } } else @@ -357,30 +356,24 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES if (yp >= read_value && yp < read_value + spriteHeight && spr_true_count == 0) { spr_true_count++; - soam_m_index++; } else if (spr_true_count > 0 && spr_true_count < 4) { - soam_m_index++; - + oam_index++; spr_true_count++; if (spr_true_count == 4) { - oam_index += 4; + if (!(yp >= read_value && yp < read_value + spriteHeight) && spr_true_count == 4) + { + oam_index &= 0x1FC; + } soam_index++; - soam_m_index = 0; spr_true_count = 0; } } else { - oam_index += 4; - if (soam_index == 8) - { - soam_m_index++; // glitchy increment - soam_m_index &= 3; - } - + oam_index += 5; // glitchy increment } read_value = soam[0]; //writes change to reads diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SubNESHawk/SubNESHawk.IEmulator.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SubNESHawk/SubNESHawk.IEmulator.cs index a190ac9e8a..2f9242c8b3 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SubNESHawk/SubNESHawk.IEmulator.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SubNESHawk/SubNESHawk.IEmulator.cs @@ -111,6 +111,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SubNESHawk private void DoFrame(IController controller) { + _nesCore.cpu.ext_ppu_cycle = 0; // Reset this value at the beginning of each frame stop_cur_frame = false; while (!stop_cur_frame) { @@ -121,7 +122,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.SubNESHawk } _nesCore.do_single_step(controller, out pass_new_input, out pass_a_frame); current_cycle++; - _nesCore.cpu.ext_ppu_cycle = current_cycle; stop_cur_frame |= pass_a_frame; stop_cur_frame |= pass_new_input; }