I've been scratching my head over how my emulator can completely pass all of blargg's nes_instr_test ROMs
except for the stack test ROM. The message at $6004 says
Code:
08 PHP
68 PLA
28 PLP
9A TXS
BA TSX
10-stack
Failed
My emulator also produces output on nestest.nes identical to the "golden" Nintendulator log (ignoring the cycle count and scanline columns, which my emulator doesn't output yet.) In other words, the stack pointer and processor status registers are both verified correct, at least with nestest.
Adding to my confusion - these five instructions are REALLY simple. If these fundamental instructions were broken, how could my emulator do so well on the other CPU tests?
Wild guess, but are you handling wrap-around properly so that you always stay within the stack page?
PHA wasn't flagged, so I'm guessing that your processor has 8 status bits in the P register, rather than only
6 as is correct.
PLA was, so maybe wrapping is wrong, or you aren't setting status flags correctly.
Failing TSX is probably not setting the Z and N flags based on what's put into X. I can only assume the same for TXS, which doesn't change any flags.
Thank you for helping me investigate this!
With regard to wrapping, I use a uint8_t to store the stack pointer, which I post-decrement on Push and pre-increment on Pop:
Code:
inline void CPU::PushStack(uint8_t value)
{
Write(value, STACK + Registers.sp);
--Registers.sp;
}
inline uint8_t CPU::PopStack()
{
++Registers.sp;
return Read(STACK + Registers.sp);
}
I use an 8-bit byte to store the processor status, but I try to be careful to ensure that there are only six real flags.
My implementation of PLP looks like this:
Code:
// dummy read of top of stack
Read(STACK + Registers.sp);
Registers.p.raw = (PopStack() | 0x20) & 0xEF; // bit 5 is always 1, b is set back to 0
TSX:
Code:
Registers.x = Registers.sp;
Registers.p.n = (Registers.x >= 0x80);
Registers.p.z = (Registers.x == 0);
TXS:
Code:
Registers.sp = Registers.x;
PHP:
Code:
PushStack(Registers.p.raw | 0x10); // PHP pushes the flags with b set to 1
One more thing - I downloaded instr_test-v3 today, and despite passing everything in nes_instr_test except test 10, I get failures all OVER the place with instr_test-v3. Not every instruction, but many of them. For example, the failures for tests 1 and 2:
instr_test-v3/rom_singles/01-implied.nes:
0A ASL A, 8A TXA, AA TAX, C8 INY, 88 DEY, 38 SEC, 18 CLC, F8 SED, 78 SEI, 58 CLI, 5A NOP
instr_test-v3/rom_singles/02-immediate.nes:
A2 LDX #n, E0 CPX #n, 82 DOP #n, 89 DOP #n, C2 DOP #n, E2 DOP #n, CB AXS #n
I failed NOP (opcode 0x5A)? How did I fail that NOP and not the other six versions of NOP? And in 02-immediate, I fail 4 DOPs, but not the fifth DOP (opcode 0x80)?
Even though my PPU is not done yet, is there anything I need do for reads from $2002, $2004, or $2007 to make the tests happy?
Probably something systemic. Is your emulator generating any interrupts during the tests? That would modify the stack and cause failure. NMI and /IRQ should not be occurring during the tests.
The tests shouldn't rely on anything from the PPU. At the most, they might need $2002 to wait for VBL, but the symptom of that would be a hang, not a failure. Later versions don't require a PPU at all (I'm pretty sure I got that released).
That was it - I was generating NMIs when I should not have been. My half-baked PPU logic was worse than no PPU at all.
Now my emulator passes all of your instr_tests!
Thank you so much for your help!
Glad you finally passed! I swear I had a version that checked for unwanted interrupts, since I think this happened to someone a few years back. Now that I've been working on NES stuff again, I'll have to do an update.
OK, updated instruction test that reports unexpected interrupts instead of random failing tests (added to Wiki too):
instr_test-v4.zipTurns out I had written this improvement, but never been able to build and release it a few years ago.