NMI Timing

Discuss emulation of the Nintendo Entertainment System and Famicom.

Moderator: Moderators

Post Reply
UpDog
Posts: 2
Joined: Tue Feb 07, 2023 4:24 pm

NMI Timing

Post by UpDog »

I'm trying to track down an issue where various test ROMs show that I'm firing an NMI a cycle too early or late (test outputs below.)

Before I start poring over megabytes of logs or spending a lot of time in a debugger comparing my per-cycle actions with another emulator, I'm wondering if there is something obvious I am just missing that the smart folks here could point out.

I'm passing various cpu and timing tests (blargg_nes_cpu_test5, branch_timing_tests, instr_test_v5, instr_timing), and vbl_nmi_timing's frame_basics, vbl_timing, even_odd_frames, vbl_clear_timing.

If I'm interpreting it correctly, cpu_interrupts_v2 test 3 (NMI & IRQ) and ppu_vbl_nmi test 5 (nmi_timing) seems to be show me firing NMI a cycle early.

My CPU and PPU strive to be cycle accurate, and currently execute sequentially every clock cycle (i.e., I run CPU clock, PPU clock, PPU clock, PPU clock, CPU clock, PPU clock, ...) - there is no smart run ahead, or running the PPU to catch up, etc. I'm focused on correctness before further optimization. CPU is clocked first, then PPU. I don't emulate individual up or down phases of the CPU clock. The only "smart" thing I do is not worry about being cycle accurate for CPU effects that can't be observed by PPU, APU, or a mapper - writes to internal RAM just happen on last cycle, storing results in a register happen on last cycle and not overlapped with next instruction fetch, etc.

I set the PPUStatus.Vblank flag on line 241, cycle 1. My frame starts on scanline 0, prerender scanline is 261. On any PPU cycle where PPUStatus.Vblank=1 and PPUCTRL.NMIEnable=1, my PPU asserts the NMI signal.

In my CPU, I check for the NMI rising edge (PPU NMI signal is 1 and state of the signal in previous cycle is 0) on the next to last cycle of the instruction. If NMI rising edge is detected, I set up the state so that we begin executing the NMI on cycle 0 of the next instruction sequence. My NMI interrupt handler runs for 7 clock cycles. On clocks 0-3 of interrupt dispatch or the BRK instruction execution, I check for NMI rising edge again to allow for redirecting the interrupt to the NMI.

I've tried the following, which either had no effect or caused other related failures - I can provide details if any of these routes are interesting.
Clocking PPU before the CPU.
Checking NMI rising edge on every CPU clock cycle.
Pushing NMI checking ahead or behind 1 CPU cycle.
Tweaking which cycle I detect vblank on (these cause expected failures in vblank timing tests)

cpu_interrupts fails on test 3 with the below. I believe this shows NMI too early, if NMI happened a cycle, the data would shift over to be correct.
NMI BRK
00 21
00 21
00 20
00 20
00 20
00 20
00 20
00 20
20 00
25 20
25 20
25 20

ppu_vbl_nmi fails on test 5 with the below. Again, it looks to me like I am firing NMI a cycle too early, if the sequence starts a cycle later, it'd be correct.

00 4
01 4
02 4
03 4
04 3
04 3
06 3
07 3
08 3
09 3

However, vbl_nmi_timing's fails with #7 - NMI occurred 1 PPU clock too late.
posit
Posts: 15
Joined: Tue Oct 11, 2022 11:40 pm

Re: NMI Timing

Post by posit »

It's been a while, but I recall some of these tests depend on certain alignments between CPU and PPU. That means phi1 and phi2 do also matter because when a cpu read/write syncs with ppu write/read could be in one of the 3 or 4 ppu clocks you described running in sequence per cpu tick.

Also if you're looking for super-quick visual confirmation, the nmi_demo_ntsc and nmi_demo_pal tests are great as extra confirmation of correct timing... though now I'm remembering that may mean considering some ppu register write effective delays.
Post Reply