Making nestest pass

Discuss emulation of the Nintendo Entertainment System and Famicom.

Moderator: Moderators

Post Reply
rodri042
Posts: 9
Joined: Wed Mar 06, 2019 11:57 pm

Making nestest pass

Post by rodri042 »

Hey everyone!
I'm new here, and as many of you, I'm trying to create a NES emulator to learn about its internals. I have implemented all the official instructions and I'll start reading to start the PPU, but first I wanted to test my CPU with `nestest`.

I've added logs in my `.step(...)` method to print something similar to what's expected (http://www.qmtpro.com/~nes/misc/nestest.log). Comparing with meld the output of the first 100 steps, I find some differences:

1)
Image
In nestest, NOP is taking 3 cycles, but http://www.obelisk.me.uk/6502/reference.html#NOP says that NOP uses 2 cycles.

2)
Image
The other problem is here... after a PLA instruction, nestest seems to be setting the bit 4 of the flags register. I thought that bit 4 and 5 were not used by emulators.

Also, there are some things I don't understand yet:
- Why is the cycle counter starting at 7? (instead of 0)
- Why is SP starting in $FD (instead of $FF)?
- How does the "extra cycle if page crossed" behavior works? If I have a `LDA $2040,X`, what would it mean that $2040+X and $2040 are in different "pages"?
- Where can I read about interrupt vectors? I cannot find clear documentation. I think that I should initialize my program counter with the RESET interrupt handler (at 0xFFCC), but after I read the two bytes of 0xFFCC/D and set that as PC, I don't find any valid instruction there. Should I treat this address in my dummy-mapper (mapper 0) as a sentinel value to point to the start of the PRG-ROM (0x8000)?

I'd appreciate some help on this. Thanks for reading!!
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Making nestest pass

Post by tepples »

I think it starts at 7 because that's how long the hardware takes to handle a /RESET, /NMI, /IRQ, or BRK. Games don't depend on the duration of /RESET.

I think it starts at $FD because /RESET shares internal circuitry with /NMI, /IRQ, and BRK, all of which push the old program counter (PC) and processor status (P) before jumping to the vector. /RESET just differs by treating them as reads instead of writes. Take $00, subtract 3, and wrap mod 256, and you have $FD. Games probably don't depend on this, however, as they initialize the stack pointer at the start of the program.

Different "pages" on 6502 means bits 15-8 of the address differ. Addresses $0640 and $06FF are in the same page, whereas $0640 and $0700 are in different pages.

Reset is at $FFFC and $FFFD, not $FFCC and $FFCD. For example, if the bytes are [$FFFC] = $A0 and [$FFFD] = $FF, then jump to $FFA0.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Making nestest pass

Post by tokumaru »

rodri042 wrote:In nestest, NOP is taking 3 cycles, but http://www.obelisk.me.uk/6502/reference.html#NOP says that NOP uses 2 cycles.
NOP is indeed 2 cycles. Since the cycle count listed is the value BEFORE the instruction runs, couldn't it be that you're getting the timing of the BCS above wrong? Branch instructions use 2 cycles when not taken, 3 cycles when taken without crossing a page boundary, and 4 cycles when taken and crossing a page boundary.
The other problem is here... after a PLA instruction, nestest seems to be setting the bit 4 of the flags register. I thought that bit 4 and 5 were not used by emulators.
See here. No actual flag exists for bit 4, but depending on how the status byte is pushed onto the stack, either by an instruction, which is the case here, or by an actual interrupt, bit 4 will be set or clear, respectively.
- Why is the cycle counter starting at 7? (instead of 0)
RESET is an interrupt, and when an interrupt fires, it takes 7 cycles to call the respective interrupt handler. This is the time it takes to push the return address and status flags to the stack, and copy the value of the interrupt vector to the PC. It appears that the data isn't actually written to the stack on resets, but the logic is still there.
- Why is SP starting in $FD (instead of $FF)?
Again, probably because RESET is an interrupt, and after "pretending" to push 3 bytes to the stack (return address + status flags) when the stack pointer is at $00, it ends up as $FD.
If I have a `LDA $2040,X`, what would it mean that $2040+X and $2040 are in different "pages"?
Yes.
I think that I should initialize my program counter with the RESET interrupt handler (at 0xFFCC)
You're supposed to initialize the PC using the values at $FFFC/$FFFD.
but after I read the two bytes of 0xFFCC/D and set that as PC, I don't find any valid instruction there.
If you're getting garbage when using the address at $FFFC/D, remember that ROM starts at $8000 (i.e. PRG-ROM offset $0000 is at address $8000), so be sure to map the PRG-ROM correctly.
Should I treat this address in my dummy-mapper (mapper 0) as a sentinel value to point to the start of the PRG-ROM (0x8000)?
Definitely not. You should honor the contents of $FFFC/D as the starting value for the PC.
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: Making nestest pass

Post by koitsu »

People helping out need to actually load the log and find the quoted/referenced code/lines. Context (outside of NMI vs. NMI) matters, and that's omitted from the description.

1. Log statements like STX $10 = 00 -- the = 00 is an emulator feature that is showing you what the value is at the effective address (in this case, the value $00) as a convenience. For whatever reason it says = 00 instead of = $00, but I assure you the value in hexadecimal. This is important/useful because, at least when making an emulator, it ensures you're reading/writing the right data from/to the right locations (effective addresses).

2. nop takes 2 cycles. tokumaru explained this better, but you're misreading the log file. The problem is actually with your cycle counts for branching (ex. bcs); branches taken take an extra cycle. Also, be aware that if a branch taken span a page boundary, an additional cycle penalty also applies on top of that (keep reading to understand that).

3. The problem is not with your pla, it's with your php (sort of). tokumaru covers this better.

4. CPU cycle count correctly is 7 at start -- the "why" is already answered by others.

5. S=$FD because that's what the CPU defaults to on power-on: https://wiki.nesdev.com/w/index.php/CPU_power_up_state -- the others answers here are correct but are convoluted and might confuse you.

6. The cycle penalty for page crossing happens on certain instructions where page wrapping can occur. Pages on the 6502 are 256 bytes (e.g. zero page = $0000-00FF, page 1 = $0100-01FF, etc.). For example, lda $2040,x will only take an additional cycle if the X register is $C0 or larger, because the effective address calculated "wraps" into the next page (ex. ldx #$c0 / lda $2040,x would take an additional cycle (effective address $2100), ldx #$bf / lda $2040,x would not (effective address $20ff)).

7. Interrupt vectors: answered by others. But more specifically, these vectors/addresses are specific to the 6502 CPU, so you'll find info about them in 6502 docs; they are not unique to the NES. You need to honour these.

For internal 6502 operation, I suggest reading this document which gives you insights into each "phase" (called a T-state) of an opcode and what the CPU is doing each cycle. The details start around line 820 -- it's for the 6510 (a different processor with some additional opcodes), but the timings are the same. Edit: you can also use this one or this one from _Bnu if you wish. There's code for emulation/methodology at the bottom.

I'll also mention this in advance beacuse it's something which comes up nearly every single time someone tries doing an emulator: proper ADC/SBC emulation. Take the time and read the thread/details I reference there, do not skim it; overflow and twos-complement confuse most people. If you still don't understand it, come back and ask, but the code examples given are correct.
rodri042
Posts: 9
Joined: Wed Mar 06, 2019 11:57 pm

Re: Making nestest pass

Post by rodri042 »

Thank you so much @tepples, @tokumaru and @koitsu for the detailed responses!

With your feedback, today I've implemented the initial state and the extra cycle penalities, as well as some interrupts code. The first 2029 cycles are equal now :)

I think the next step would be fixing bugs until the logs show weird instructions (I don't want to implement unofficial opcodes yet). Making this little "diff tool" to debug helped a lot!
Last edited by rodri042 on Fri Mar 08, 2019 1:33 am, edited 1 time in total.
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: Making nestest pass

Post by koitsu »

Yeah, save implementing illegal opcodes for last -- like, the *very* last. There's only a handful of titles that use them (two (2) off the top of my head, 2 but there's probably more, but really they're like 0.0001% of all the games). For now, just make your emulator throw an exception (pause, break, stop, dialog box, whatever) if you encounter one, since odds are it means something has gone completely haywire. You'll certainly run into that when trying to implement PRG-ROM switching mappers down the road.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Making nestest pass

Post by tokumaru »

I think there's a sound engine commonly used in homebrews that relies on illegal opcodes... Don't remember which.
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Making nestest pass

Post by tepples »

Here's the list: among licensed games I see Aladdin (port from Genesis), Beauty and the Beast, Dynowarz, Infiltrator, Puzznic, and Super Cars.

The homebrew audio driver you're thinking of is MUSE, used in Driar and STREEMERZ.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Making nestest pass

Post by tokumaru »

Ah, so I guess it's not that common after all.
spacee88
Posts: 1
Joined: Mon Mar 11, 2019 5:37 am

Re: Making nestest pass

Post by spacee88 »

thank you
Post Reply