Preface: HARDWARE FOLKS, PLEASE CORRECT ME IF I'M WRONG OR DESCRIBE THIS INACCURATELY. lidnariq, I'm lookin' at you ;-)
beneficii wrote:
... The only thing I had neglected, was the $4210 read at the beginning of the NMI (though why this is required is beyond me). ...
The explanation is semi-complicated and involves understanding a little bit about how the actual hardware works. I understand "part" of how it works, but myself have questions. The NES suffers from the same phenomenon (sort of), however see the very last paragraph of this post for a "hmm, maybe the SNES does something different/unique". I'll provide references for my statements as well.
NMI on the 6502/65c02/65816 is a
"edge-sensitive" or "edge-triggered" signal connected via a physical trace between the NMI pin on the CPU and the PPU (I don't remember on the SNES if it's PPU1 or PPU2, but it's irrelevant in understanding the behaviour). The signal is digital, so it only has two states: high (1) and low (0).
NMI is what's known as a "falling edge", which means the signal must transition from high to low (1 --> 0) for the CPU to run code at your NMI vector. Low to low (0 --> 0), high to high (1 --> 1), and low to high (0 --> 1) all do nothing. (Educational: in contrast, "rising edge" would mean the signal must transition from low to high (0 --> 1) for something to happen).
As such, it's presumed the PPU keeps the signal high (1) as a default -- keep reading.
When $4200 bit 7 is set to 1 (the trigger-NMI-on-VBlank bit), when VBlank happens, the PPU sets the signal to low (0). Thus, the high --> low transition causes NMI to happen within the CPU, and it runs code starting at the NMI vector.
Following so far? Maybe a bit complex and into the hardware level of things, but it should make sense.
For the PPU to essentially "reset" back to its default state -- make the PPU keep the signal high (1) again -- one must read register $4210 to essentially "reset the latch".
Failing to read $4210 in your NMI routine would therefore mean the signal would remain in low (0) state perpetually -- which, to me, means no more NMIs would fire (i.e. it happens once and that's it). However, what I've seen stated repeatedly is that failure to do this can cause "NMI to fire multiple times" (I don't understand how that would happen because I'm not a hardware guy).
There are at least two documents that outline NMI behaviour with VBlank on the SNES (I haven't looked at nocash's, but there's probably useful stuff in there too). Here are those:
Charles MacDonald's documentation says the following, emphasis mine:
Quote:
Non-maskable interrupts
According to the 65816 manual, the NMI signal is an edge-sensitive input,
so that only a high to low transition on the NMI pin will cause an interrupt
to occur. Leaving it high, low, or having a low to high transition has no
effect. I'm going to assume the PPU normally holds NMI high and brings it
low to trigger an interrupt.
The PPU will pull NMI low at line $00E8 in 224-line mode or line $00F7 in
239-line mode. It will remain low until either $4210 is read, or a new
frame starts, at which point the PPU will bring NMI high again.
The inverted state of the NMI signal from the PPU can be read through bit 7
of $4210, where 0= no NMI has been requested, 1= an NMI is pending. After
reading this register, the PPU resets the NMI signal by pulling it high.
Bit 7 of $4200 doesn't affect how the PPU manages NMIs at all, it is just
a gate between the NMI output of the PPU and the NMI input of the CPU.
When set, NMIs can be generated. When cleared, the CPU ignores the state of
the NMI pin.
Here are some details of specific situations:
When there is a pending NMI, toggling bit 7 of $4200 does not create any
additional interrupts.
Bit 7 of $4210 will be set in the NMI routine when it executes.
After the NMI routine finishes, bit 7 will remain set (assuming $4210 wasn't
read) until the start of the next frame. This will not cause more NMIs to
occur as they are only triggered by high-to-low transitions, having the NMI
pin remain low does not do anything.
Bit 7 of $4210 reflects the NMI status independantly of bit 7 of $4200.
Anomie's SNES timing doc says this, emphasis mine:
Quote:
The internal timer will set its NMI output low at H=0.5 at the beginning of
V-Blank. The CPU's /NMI input is forced high by clearing bit 7 of register
$4200, so the CPU may not actually see the NMI transition. The CPU will jump to
the NMI routine at the end of the instruction during which /NMI transitions.
The internal timer sets its NMI output high at H=0 V=0, or when register
$4210 is read. Possibly also when $4200 is written?
So if the PPU itself is resetting NMI to high (1) when it begins to draw the pixel at 0,0 then why do Nintendo's docs say one must read $4210 else risk duplicate NMIs -- and how would you risk getting more than one?
Both can potentially be explained by details about the NES.
The nesdev Wiki, and this is for the NES specifically,
has a write-up about this behaviour, specifically how NMI can end up occurring multiple times. PPUCTRL ($2000) in that Wiki page would be essentially $4200 on the SNES, and PPUSTATUS ($2002) would essentially be $4210 on the SNES. On the NES, you need to read $2002 and write to $2000 to "truly" get the PPU to re-set the NMI signal back to high (1). On the NES, I don't think when H=0 and V=0 does the NES PPU set NMI to high (1) automatically.
So I'm left to believe that the comments in the official SNES documentation are a kind of "learned behaviour" from the NES days, where you had to "do extra stuff" to essentially get the PPU to reset the NMI signal to high (1). Nintendo may have alleviated all that nonsense by simply having the PPU set NMI to high (1) itself when drawing pixel 0,0.
Long-winded answer, but it's the best I got. If someone else has more concrete details or explanations, I'd love to hear 'em.
Edit: it looks like nocash's documentation sort of confirms my suspicions, but actually explains how "multiple NMIs" could happen if you don't read $4210. From
http://problemkaputt.de/fullsnes.htm#snesppuinterrupts talking about $4210, emphasis mine:
Quote:
The SNES has only one NMI source (vblank), and the NMI flag is automatically reset (on vblank end), so there's normally no need to read/acknowledge the flag, except one special case: If one does disable and re-enable NMIs, then an old NMI may be executed again; acknowledging avoids that effect.
The CPU includes another internal NMI flag, which gets set when "[4200h].7 AND [4210h].7" changes from 0-to-1, and gets cleared when the NMI gets executed (which should happen around after the next opcode) (if a DMA transfer is in progress, then it is somewhere after the DMA, in that case the NMI can get executed outside of the Vblank period, ie. at a time when [4210h].7 is no longer set).
So yeah, it looks like Nintendo alleviated the annoyance that stemmed from the NES days by doing things differently, but also added that you should read from $4210 for a particular unique case (if you were to set bit 7 of $4200 to 0, then later set bit 7 of $4200 to 1, without reading $4210 in your NMI routine, you could risk re-executing your NMI routine).
Hardware is a weird beast.