It is currently Thu Sep 19, 2019 8:04 am

All times are UTC - 7 hours





Post new topic Reply to topic  [ 10 posts ] 
Author Message
 Post subject: Making nestest pass
PostPosted: Thu Mar 07, 2019 12:51 am 
Offline

Joined: Wed Mar 06, 2019 11:57 pm
Posts: 3
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!!


Top
 Profile  
 
 Post subject: Re: Making nestest pass
PostPosted: Thu Mar 07, 2019 5:59 am 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 21591
Location: NE Indiana, USA (NTSC)
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.

_________________
Pin Eight | Twitter | GitHub | Patreon


Top
 Profile  
 
 Post subject: Re: Making nestest pass
PostPosted: Thu Mar 07, 2019 9:10 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 11412
Location: Rio de Janeiro - Brazil
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.

Quote:
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.

Quote:
- 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.

Quote:
- 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.

Quote:
If I have a `LDA $2040,X`, what would it mean that $2040+X and $2040 are in different "pages"?

Yes.

Quote:
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.

Quote:
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.

Quote:
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.


Top
 Profile  
 
 Post subject: Re: Making nestest pass
PostPosted: Thu Mar 07, 2019 2:54 pm 
Offline
User avatar

Joined: Sun Sep 19, 2004 9:28 pm
Posts: 4208
Location: A world gone mad
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.


Top
 Profile  
 
 Post subject: Re: Making nestest pass
PostPosted: Fri Mar 08, 2019 1:02 am 
Offline

Joined: Wed Mar 06, 2019 11:57 pm
Posts: 3
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.

Top
 Profile  
 
 Post subject: Re: Making nestest pass
PostPosted: Fri Mar 08, 2019 1:13 am 
Offline
User avatar

Joined: Sun Sep 19, 2004 9:28 pm
Posts: 4208
Location: A world gone mad
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.


Top
 Profile  
 
 Post subject: Re: Making nestest pass
PostPosted: Fri Mar 08, 2019 1:19 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 11412
Location: Rio de Janeiro - Brazil
I think there's a sound engine commonly used in homebrews that relies on illegal opcodes... Don't remember which.


Top
 Profile  
 
 Post subject: Re: Making nestest pass
PostPosted: Fri Mar 08, 2019 7:38 am 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 21591
Location: NE Indiana, USA (NTSC)
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.

_________________
Pin Eight | Twitter | GitHub | Patreon


Top
 Profile  
 
 Post subject: Re: Making nestest pass
PostPosted: Fri Mar 08, 2019 11:14 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 11412
Location: Rio de Janeiro - Brazil
Ah, so I guess it's not that common after all.


Top
 Profile  
 
 Post subject: Re: Making nestest pass
PostPosted: Mon Mar 11, 2019 5:41 am 
Offline

Joined: Mon Mar 11, 2019 5:37 am
Posts: 1
thank you


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 10 posts ] 

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 3 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group