nmi timing?

Discuss emulation of the Nintendo Entertainment System and Famicom.

Moderator: Moderators

User avatar
Zepper
Formerly Fx3
Posts: 3262
Joined: Fri Nov 12, 2004 4:59 pm
Location: Brazil
Contact:

nmi timing?

Post by Zepper »

Test rom "05-nmi_timing.nes" should print "4 4 4 3 3..." as a "pass". My emulator prints "4 4 4 4 3..." and fails. After debugging the test rom, I noticed that a NMI is being acknowledged too late in a special case of being set at PPU cycle 0 (VBlank start) and with 3 PPU cycles left to run. My hack is to acknowledge the NMI if there are 3 PPU cycles left, but is this correct?

Code: Select all

CUSTOM_NMI=1
.include "shell.inc"
.include "sync_vbl.s"

zp_byte nmi_data

nmi:    stx nmi_data
	rti

main:   jsr console_hide
	loop_n_times test,10
	check_crc $A6CCB10A
	jmp tests_passed

test:   jsr print_a
	jsr disable_rendering
	jsr sync_vbl_delay
	delay 29749+29781
	lda #$FF
	sta nmi_data
	ldx #0
	lda #$80
	sta $2000
landing:
	; NMI occurs after one of these
	; instructions and prints X
	ldx #1
	ldx #2
	ldx #3
	ldx #4
	ldx #5
	
	lda #0
	sta $2000
	lda nmi_data
	jsr print_dec
	jsr print_newline
	rts
User avatar
Disch
Posts: 1848
Joined: Wed Nov 10, 2004 6:47 pm

Re: nmi timing?

Post by Disch »

I don't recall there being any special cases here, but it's been a while. What do you mean by "3 PPU cycles left to run"?


NMIs aren't 'acknowledged' like IRQs are. Once an NMI triggers it will happen as soon as the current instruction finishes no matter what. There's no way to stop it after it's been triggered (except for a Reset).

But an NMI is a one-time thing... and as soon as it happens it's over. Unlike IRQs which happen persistently until you tell them to stop (ie: "acknowledge" them).
User avatar
Zepper
Formerly Fx3
Posts: 3262
Joined: Fri Nov 12, 2004 4:59 pm
Location: Brazil
Contact:

Re: nmi timing?

Post by Zepper »

Disch wrote: What do you mean by "3 PPU cycles left to run"?
Really!? Okay, so... "for each CPU cycle, the NTSC PPU runs for 3 cycles". 8-)

There's a special (?) case about CPU/PPU alignment, where there was a write to $2000 (enabling NMI) and the PPU is at VBlank start cycle (0). In this case, the NMI should have been "activated" in the previous CPU clock. With 3 PPU cycles left (a full PPU clock), the NMI will be triggered later. So, to make it immediate, I have to "acknowledge" the pending request - this way, it'll occur in the next instruction. Otherwise, 1 instruction is skipped, making the test rom to fail.
NMIs aren't 'acknowledged' like IRQs are.
True, but you even explained what you just said:
Once an NMI triggers, it will happen as soon as the current instruction finishes, no matter what.
The pending/requested NMI is "acknowledged" at the end of the current instruction.
User avatar
Disch
Posts: 1848
Joined: Wed Nov 10, 2004 6:47 pm

Re: nmi timing?

Post by Disch »

Zepper wrote:
Disch wrote: What do you mean by "3 PPU cycles left to run"?
Really!? Okay, so... "for each CPU cycle, the NTSC PPU runs for 3 cycles". 8-)
Language barrier? =x

"left to run" to me means there are 3 more cycles that need to execute until something interesting happens.

"Y happens with 3 cycles left to run"
==
"Y happens 3 cycles before X"

and I didn't know what 'X' was.

There's a special (?) case about CPU/PPU alignment, where there was a write to $2000 (enabling NMI) and the PPU is at VBlank start cycle (0).
This shouldn't matter.

Given the below code:

Code: Select all

; NMIs disabled here
LDA #$80
STA $2000   ;enable NMI
NOP

As you know, the actual write to $2000 occurs on the very last cycle of the STA instruction.
This means it is already too late for the NMI to happen because the CPU has already started the process of fetching the next byte.

So if the $2000 write AND the NMI happen at the same time... your emu can chose which one to do first... but regardless of which one you choose, the NMI will not happen until after the NOP

Example:

Option 1 (write before NMI):
- $2000.7 written with bit 7 high enables NMIs
- $2002.7 goes high at start of VBlank
- $2002.7 transitions from 0->1 while $2000.7 is high -- this triggers an NMI
- NOP completes because of CPU pipelining
- NMI executes

Option 2 (NMI before write):
- $2002.7 goes high as VBlank starts.
- NMIs are disabled, so no NMI
- $2000 is written with bit 7 high
- $2000.7 makes low->high transition while $2002.7 is high -- this triggers an NMI
- NOP completes because of CPU pipelining
- NMI executes


As you can see, no matter which you do first, the result is the same. The following instruction executes, then NMI triggers. It doesn't matter if it happens on the same cycle as a $2000 write.
User avatar
koitsu
Posts: 4201
Joined: Sun Sep 19, 2004 9:28 pm
Location: A world gone mad

Re: nmi timing?

Post by koitsu »

Disch, are you sure about that? Reference: http://wiki.nesdev.com/w/index.php/CPU_interrupts
The output from the edge detector and level detector are polled at certain points to detect pending interrupts. For most instructions, this polling happens during the final cycle of the instruction, before the opcode fetch for the next instruction. If the polling operation detects that an interrupt has been asserted, the next "instruction" executed is the interrupt sequence.
I read this to mean that in your example, the NMI routine **could** (not would) get executed after the STA finishes completely but *before* the NOP. I say "could" because I'm under the impression VBlank is happening regardless of whether or not $2000.7 is set; meaning, I would think there is a chance when you enable NMI-on-VBlank (set $2000.7 to 1) that VBlank could happen and trigger NMI before the NOP.

There's a large amount of research on this subject ("what happens if an NMI happens during X/Y/Z scenario") which seems to imply that's the case. WDC's own documentation for their 65xx CPUs states it tersely ("... an interrupt occurring during execution of an instruction is not acted upon until execution of the instruction is complete, ..."), and there's also this (review the actual diagram in Figure 8): http://www.6502.org/tutorials/interrupts.html#a

If I'm wrong then I'm wrong -- and I'm happy to be wrong, especially in this case! -- so Zepper, don't take my post as authoritative. :-)

Footnote: I found http://visual6502.org/wiki/index.php?ti ... t_Handling to be an interesting read/study as well, particularly because there's repeated mentions of branches (BNE/BEQ/etc.) possibly being handled differently in some weird way (note one of the reference threads "A taken branch delays interrupt handling by one instruction").
User avatar
Disch
Posts: 1848
Joined: Wed Nov 10, 2004 6:47 pm

Re: nmi timing?

Post by Disch »

koitsu wrote:Disch, are you sure about that?
Unless I'm missing a really, really bizarre edge case, yes.

The underlined text you quoted does not specify whether or not the polling occurs before the write.. only that it occurs at some point during that cycle.

But we know that it MUST be polled before the write because games (specifically, Adventures of Lolo 3) rely on this behavior. Lolo 3 will enable NMIs when the VBlank flag set, and will expect the instruction immediately after it to complete before the NMI actually happens. Failing to trigger an NMI and failing to put in that 1 instruction delay will cause the game to break. (EDIT: or at least... I think it was lolo 3. It was one of the lolo games....)

Unless there's some functional difference between $2000.7 rising when $2002.7 is high vs. $2002.7 rising when $2000.7 is high -- all the same logic applies. But AFAIK those two scenarios are functionaly identical.

Also from the wiki page you linked (paragraph following the one you quoted):
As can be deduced from above, it's really the status of the interrupt lines at the end of the second-to-last cycle that matters.
This all goes hand-in-hand with the reason why CLI/SEI/PLP are "delayed". The CPU has to decide whether or not the next instruction is an interrupt before the final cycle of the instruction is actually executed -- so things that happen on the last cycle can't change whether or not you're going to have an interrupt.


(EDIT: PLP is delayed... RTI isn't. whoops)
User avatar
Zepper
Formerly Fx3
Posts: 3262
Joined: Fri Nov 12, 2004 4:59 pm
Location: Brazil
Contact:

Re: nmi timing?

Post by Zepper »

Here's the tracelog. In fact, I cropped 2,5 millions of generated lines into blocks where the NMI is tested.
The words "hack me" should be my hack of asserting the NMI to be triggered at the end of the current instruction, but I didn't. The test fails.
Uh... the first number is the instruction counter. Next is the opcode, then the PC address, then the rest. ^_^;;
The (1) or (0) around means the VBlank flag.

The 4th NMI should occur before the LDX #$04, and not after it. Notice the CPU/PPU alignment - there are 3 PPU cycles left.
The 5th NMI is correct and THIS should be the correct way.
Attachments
rocknes_nmi.txt
(7.1 KiB) Downloaded 172 times
User avatar
Disch
Posts: 1848
Joined: Wed Nov 10, 2004 6:47 pm

Re: nmi timing?

Post by Disch »

On your 4th NMI, it looks like "frame edge" is where the NMI should be occuring, but you're waiting a cycle ("frame start") before actually doing it. Why? What is the difference between "frame edge" and "frame start"?

You correctly have VBlank set on "frame edge". When VBlank goes 0->1 and NMIs are enabled... that is what triggers the NMI. There is no delay.

The only "delay" is because the CPU polls for interrupts earlier than you'd expect (see previous posts)







( $2000 also seems completely unrelated, since that is written to several cycles before the NMI happens in all cases, so NMI is not happening at the same time )
User avatar
Zepper
Formerly Fx3
Posts: 3262
Joined: Fri Nov 12, 2004 4:59 pm
Location: Brazil
Contact:

Re: nmi timing?

Post by Zepper »

I'm sorry for the language barrier. :oops: But feel free to ask again, just in case. :beer: :beer:

"Frame edge" is the transition scanline 261->0 (VBlank start, ppu cycle 0).
"Frame start" is the beginning of the scanline 0 (VBlank start, ppu cycle 0).

Notice that "frame edge" isn't always followed by a "frame start". It occurs because there are no more PPU cycles left (always 3 per CPU cycle), so a new CPU cycle starts. Then, the following "frame start" has 3 PPU cycles (a full cycle), and the NMI fails - it should be asserted at the "frame edge" moment, and not later... as you pointed out.

Yup, $2000 just activates the NMI enable flag a few cycles before it. So, yeah, unrelated. The correct thing is $2002 reads.
User avatar
Disch
Posts: 1848
Joined: Wed Nov 10, 2004 6:47 pm

Re: nmi timing?

Post by Disch »

So... on Frame Edge, you have a (1), so VBlank flag must be set in your emu.

But if Frame Edge is before the first cycle of scanline 0 ... then it shouldn't be set yet.

VBlank flag is set... and NMI is triggered... both at the exact same time. In your logs, they appear to be happening at different times.



(edited for clarity)

Also: No need to apologize for the language barrier. Your English is excellent and I'm a lazy American who doesn't know any other languages. :mrgreen:
User avatar
Zepper
Formerly Fx3
Posts: 3262
Joined: Fri Nov 12, 2004 4:59 pm
Location: Brazil
Contact:

Re: nmi timing?

Post by Zepper »

Actually, the (1) flag isn't vblank. :? It's set 1 PPU cycle before VBlank and cleared on $2002 reads. If this flag is clear, the VBlank flag isn't set.
User avatar
Disch
Posts: 1848
Joined: Wed Nov 10, 2004 6:47 pm

Re: nmi timing?

Post by Disch »

=x

I'm confused.

So if (0), VBlank isn't set.... but if (1), VBlank might be set? =x

So (0)/(1) kind of doesn't tell us anything then? =P


Anyway looking back at your log:

Code: Select all

732428 | A2, E350 LDX #$02
-- frame edge -- (1) PPU cycles left: 0
732429 | A2, E352 LDX -- frame start -- (1) PPU cycles left: 2
.NMI trigger - PPU cycles left 3
..hack me
#$03
732430 | A2, E354 LDX #$04
*NMI TRIGGER 04*
732431 | 86, E308 STX $20
Here is something suspicious:

LDX #$03 has 2 cycles: 'read A2' and 'read 03'

- Frame Edge happens before the 'read A2' cycle
- From my understanding, I would expect Frame Start to happen on the next PPU cycle.
- So Frame Start (and NMI trigger) happens during the 'read A2' cycle.
- If NMI happens during 'read A2', it must be before 'read 03'
- If it's before 'read 03'... NMI should be happening IMMEDIATELY after the LDX #$03 instruction -- but you are waiting until after LDX #$04


Even if I'm off by 1 and Frame Edge is happening one PPU cycle later, Frame Start would still be happening before 'read 03' so the CPU will do the NMI after this instruction.



Also....
".NMI trigger" has 3 PPU cycles left
but "frame start" has 2 PPU cycles left

So are those happening at different times? They shouldn't be! NMI triggers on frame start -- the exact same time $2002.7 goes high.
User avatar
Zepper
Formerly Fx3
Posts: 3262
Joined: Fri Nov 12, 2004 4:59 pm
Location: Brazil
Contact:

Re: nmi timing?

Post by Zepper »

I have no other conclusion than "if the next PPU clock (3 cycles) begins EXACTLY at VBlank start (line 0, cycle 0) and a NMI is requested, it must be asserted immediately in order to avoid triggering it only in the next instruction, but at the end of the current one".

Those test ROMs are fantastic. They're able to synchronize the CPU/PPU with 0, 1, 2 or 3 cycles left!:shock:
Let me say that YES, my statement is a special case for NMI, an "edge scenario". Unless someone prints here what happens in another emulator, I'm stuck. :cry: :? :?
User avatar
Zepper
Formerly Fx3
Posts: 3262
Joined: Fri Nov 12, 2004 4:59 pm
Location: Brazil
Contact:

Re: nmi timing?

Post by Zepper »

I modified the log a bit. Look below... :lol: Remember that the PPU runs before the CPU.

Code: Select all

732428 | A2, E350 LDX #$02
============================ 
-- frame edge -- (1) PPU cycles left: 0
732429 | A2, E352 LDX 
============================
-- frame start -- (1) PPU cycles left: 2
.NMI requested - PPU cycles left 3
//NMI must be asserted here; otherwise, it won't trigger at the end of this instruction...
#$03
============================
NMI asserted   //...too late, as you can see.
732430 | A2, E354 LDX #$04
*NMI TRIGGER 04*
732431 | 86, E308 STX $20
User avatar
Disch
Posts: 1848
Joined: Wed Nov 10, 2004 6:47 pm

Re: nmi timing?

Post by Disch »

I'm not convinced this is a special case. Maybe I'm having difficulty reading the logs =x

Here's an illustration of what should be happening -- maybe you can match this up closer to what your emu is doing:

Code: Select all

given 3 instructions:
LDX #$02
LDX #$03
LDX #$04

This will span 6 CPU cycles, and 6*3 = 18 PPU cycles.

3 PPU cycles on the 'LDX' opcode fetch
3 PPU cycles on the #$02 fetch
etc


Cycle     NMI
=======|=======
LDX.1  |   A
LDX.2  |   A
LDX.3  |   A
#$02.1 |   B
#$02.1 |   B
#$02.1 |   B
       |      <- do NMI here if it happened on an 'A' cycle
LDX.1  |   B  <- as far as I can tell your 4th NMI is triggering here, so it should happen at 'B',
LDX.2  |   B        but you are doing it at 'C'
LDX.3  |   B
#$03.1 |   C
#$03.1 |   C
#$03.1 |   C
       |      <- do NMI here if it happened on a 'B' cycle
LDX.1  |   C
LDX.2  |   C
LDX.3  |   C
#$04.1 |
#$04.1 |
#$04.1 |
       |      <- do NMI here if it happened on a 'C' cycle





EDIT:



Okay so I looked more closely at your log. If your PPU is running first, that means your emu might be running into this:

Code: Select all

LDX.1  |   B
LDX.2  |   B
LDX.3  |   B   <- "Frame Edge" (last cycle of previous frame?) here
#$03.1 |   C   <- "Frame Start" here -- VBlank flag raised & NMI triggered here
If this is what is happening, then your problem may not be with your NMI code... it may be with how the test ROM syncs up with your PPU (specifically, $2002 reads).
Post Reply