NMI: Difference between revisions
mNo edit summary |
(Removed GBA-style register names (this article was SO obviously made by tepples)) |
||
Line 9: | Line 9: | ||
== Operation == | == Operation == | ||
Two 1-bit registers inside the PPU control the generation of NMI signals. | Two 1-bit registers inside the PPU control the generation of NMI signals. | ||
[[PPU frame timing|Frame timing]] and accesses to the PPU's [[PPU_registers#Controller ($2000) > write| | [[PPU frame timing|Frame timing]] and accesses to the PPU's [[PPU_registers#Controller ($2000) > write|$2000]] and [[PPU_registers#Status ($2002) < read|$2002]] registers change these registers as follows: | ||
#Start of vertical blanking: Set NMI_occurred in PPU to true. | #Start of vertical blanking: Set NMI_occurred in PPU to true. | ||
#End of vertical blanking, sometime in pre-render scanline: Set NMI_occurred to false. | #End of vertical blanking, sometime in pre-render scanline: Set NMI_occurred to false. | ||
#Read | #Read $2002: Return old status of NMI_occurred in bit 7, then set NMI_occurred to false. | ||
#Write to PPUCTRL: Set NMI_output to bit 7. | #Write to PPUCTRL: Set NMI_output to bit 7. | ||
The PPU pulls /NMI low if and only if both NMI_occurred and NMI_output are true. | The PPU pulls /NMI low if and only if both NMI_occurred and NMI_output are true. | ||
By toggling NMI_output ( | By toggling NMI_output ($2000 bit 7) during vertical blank without reading $2002, a program can cause /NMI to be pulled low multiple times, causing multiple NMIs to be generated. | ||
== Caveats == | == Caveats == | ||
=== Old emulators === | === Old emulators === | ||
The earliest emulators, such as NESticle, do ''not'' turn off NMI_occurred in line 3. | The earliest emulators, such as NESticle, do ''not'' turn off NMI_occurred in line 3. | ||
Thus, some [[Program Compatibility|defective homebrew programs]] developed in this era will wait for | Thus, some [[Program Compatibility|defective homebrew programs]] developed in this era will wait for $2002.7 to become false and expect this to happen at the end of vblank. | ||
(The right way to wait for the end of vblank involves triggering a sprite 0 hit and waiting for ''that'' flag to become 0.) | (The right way to wait for the end of vblank involves triggering a sprite 0 hit and waiting for ''that'' flag to become 0.) | ||
Some newer homebrew programs have been known to display a diagnostic message if an emulator incorrectly returns true on two consecutive reads of | Some newer homebrew programs have been known to display a diagnostic message if an emulator incorrectly returns true on two consecutive reads of $2002.7 . | ||
=== Race condition === | === Race condition === | ||
If 1 and 3 happen simultaneously, | If 1 and 3 happen simultaneously, $2002.7 is read as false, and NMI_occurred is set to false anyway. | ||
This means that the following code that waits for vertical blank by spinning on | This means that the following code that waits for vertical blank by spinning on $2002.7 is likely to miss an occasional frame: | ||
<pre> | <pre> | ||
wait_status7: | wait_status7: | ||
bit | bit $2002 | ||
bpl wait_status7 | bpl wait_status7 | ||
rts | rts |
Revision as of 15:16, 25 February 2010
The 2A03 and most other 6502 family CPUs are capable of processing a non-maskable interrupt (NMI). This input is edge-sensitive, meaning that if other circuitry on the board pulls the /NMI pin from high to low voltage, this sets a flip-flop in the CPU. When the CPU checks for interrupts and find that the flip-flop is set, it pushes the processor status register and return address on the stack, reads the NMI handler's address from $FFFA-$FFFB, clears the flip-flop, and jumps to this address.
"Non-maskable" means that no state inside the CPU can prevent NMI from happening. However, most boards that use a 6502 CPU's /NMI line allow the CPU to disable the generation of /NMI signals by writing to a memory-mapped I/O device. In the case of the NES, the /NMI line is connected to the NES PPU and used to detect vertical blanking.
Operation
Two 1-bit registers inside the PPU control the generation of NMI signals. Frame timing and accesses to the PPU's $2000 and $2002 registers change these registers as follows:
- Start of vertical blanking: Set NMI_occurred in PPU to true.
- End of vertical blanking, sometime in pre-render scanline: Set NMI_occurred to false.
- Read $2002: Return old status of NMI_occurred in bit 7, then set NMI_occurred to false.
- Write to PPUCTRL: Set NMI_output to bit 7.
The PPU pulls /NMI low if and only if both NMI_occurred and NMI_output are true. By toggling NMI_output ($2000 bit 7) during vertical blank without reading $2002, a program can cause /NMI to be pulled low multiple times, causing multiple NMIs to be generated.
Caveats
Old emulators
The earliest emulators, such as NESticle, do not turn off NMI_occurred in line 3. Thus, some defective homebrew programs developed in this era will wait for $2002.7 to become false and expect this to happen at the end of vblank. (The right way to wait for the end of vblank involves triggering a sprite 0 hit and waiting for that flag to become 0.) Some newer homebrew programs have been known to display a diagnostic message if an emulator incorrectly returns true on two consecutive reads of $2002.7 .
Race condition
If 1 and 3 happen simultaneously, $2002.7 is read as false, and NMI_occurred is set to false anyway. This means that the following code that waits for vertical blank by spinning on $2002.7 is likely to miss an occasional frame:
wait_status7: bit $2002 bpl wait_status7 rts
Once the PPU is warmed up and the game is running, the most reliable way to wait for a vertical blank is to turn on NMI_output and then wait for the NMI handler to set a variable:
wait_nmi: lda retraces @notYet: cmp retraces beq @notYet rts nmi_handler: inc retraces rti
However, code like wait_status7
should still be used before the PPU has warmed up.