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 <OSSYoshiRulz+GitHub@gmail.com>

* Update src/BizHawk.Emulation.Cores/CPUs/MOS 6502X/Execute.cs

Co-authored-by: YoshiRulz <OSSYoshiRulz+GitHub@gmail.com>

* Update src/BizHawk.Emulation.Cores/CPUs/MOS 6502X/Execute.cs

Co-authored-by: YoshiRulz <OSSYoshiRulz+GitHub@gmail.com>

* Update src/BizHawk.Emulation.Cores/CPUs/MOS 6502X/Execute.cs

Co-authored-by: YoshiRulz <OSSYoshiRulz+GitHub@gmail.com>

* Update src/BizHawk.Emulation.Cores/CPUs/MOS 6502X/Execute.cs

Co-authored-by: YoshiRulz <OSSYoshiRulz+GitHub@gmail.com>

* Update src/BizHawk.Emulation.Cores/CPUs/MOS 6502X/Execute.cs

Co-authored-by: YoshiRulz <OSSYoshiRulz+GitHub@gmail.com>

* Update src/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Chip6510.cs

Co-authored-by: YoshiRulz <OSSYoshiRulz+GitHub@gmail.com>

* Update src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/APU.cs

Co-authored-by: YoshiRulz <OSSYoshiRulz+GitHub@gmail.com>

* Update src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs

Co-authored-by: YoshiRulz <OSSYoshiRulz+GitHub@gmail.com>

* Update src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs

Co-authored-by: YoshiRulz <OSSYoshiRulz+GitHub@gmail.com>

* 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 <OSSYoshiRulz+GitHub@gmail.com>

* 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 <OSSYoshiRulz+GitHub@gmail.com>
This commit is contained in:
Chris Siebert 2025-06-14 03:34:42 -04:00 committed by GitHub
parent 4636cd8cd7
commit 85f90604bd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 190 additions and 188 deletions

View File

@ -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()

View File

@ -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();
}

View File

@ -54,8 +54,8 @@ namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS
_cpu = new MOS6502X<CpuLink>(new CpuLink(this))
{
// Required to pass the Lorenz test suite.
AneConstant = 0xEF,
LxaConstant = 0xFE
AneConstant = 0xEE,
LxaConstant = 0xEE,
};
// perform hard reset

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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;
}
}
}

View File

@ -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();

View File

@ -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

View File

@ -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;
}