CPU interrupts: Difference between revisions

From NESdev Wiki
Jump to navigationJump to search
(Update explanation for CLI/SEI/PLP IRQ delay and link to a more relevant post)
No edit summary
Line 71: Line 71:
* The above interrupt hijacking and IRQ response behavior is tested by the [[Emulator tests|cpu_interrupts_v2]] test ROM.
* The above interrupt hijacking and IRQ response behavior is tested by the [[Emulator tests|cpu_interrupts_v2]] test ROM.
* For more quirky behavior related to VBlank NMI's from the PPU, see [[PPU frame timing]].
* For more quirky behavior related to VBlank NMI's from the PPU, see [[PPU frame timing]].
* NMI is edge-sensitive while IRQ is level-sensitive. Both are active low.
* NMI is edge-sensitive while IRQ is level-sensitive. Both are active low. NMI is polled every cycle (to be able to detect transitions in the middle of an instruction), while IRQ is polled during the last cycle of an instruction (before the opcode fetch for the next instruction).
* The B status flag doesn't physically exist inside the CPU, and only appears as different values being pushed for bit 4 of the saved status bits by BRK and NMI/IRQ.
* The B status flag doesn't physically exist inside the CPU, and only appears as different values being pushed for bit 4 of the saved status bits by BRK and NMI/IRQ.
* For a more technical description of what causes the hijacking behavior, see [http://visual6502.org/wiki/index.php?title=6502_BRK_and_B_bit Visual6502's writeup].
* For a more technical description of what causes the hijacking behavior, see [http://visual6502.org/wiki/index.php?title=6502_BRK_and_B_bit Visual6502's writeup].

Revision as of 03:25, 12 April 2013

Interrupt hijacking

The MOS 6502 and by extension the 2A03/07 has a quirk that can cause one type of interrupt to partially hijack another type if they occur very close to one another.

For example, if NMI is asserted during the first four ticks of a BRK instruction, the BRK instruction will execute normally at first (an additional PC increment will occur compared to other interrupt types, and the status word will be pushed with the B flag set), but execution will branch to the NMI vector instead of the IRQ/BRK vector:

Each [] is a CPU tick. [...] is whatever tick precedes the BRK opcode fetch.

Asserting NMI during the interval marked with * causes a branch to the NMI routine instead of the IRQ/BRK routine.

     ********************
[...][BRK][BRK][BRK][BRK][BRK][BRK][BRK]

In a tick-by-tick breakdown of BRK, this looks like

 #  address R/W description
--- ------- --- -----------------------------------------------
 1    PC     R  fetch opcode, increment PC
 2    PC     R  read next instruction byte (and throw it away),
                increment PC
 3  $0100,S  W  push PCH on stack, decrement S
 4  $0100,S  W  push PCL on stack, decrement S
*** At this point, the signal status determines which interrupt vector is used ***
 5  $0100,S  W  push P on stack (with B flag set), decrement S
 6   $FFFE   R  fetch PCL
 7   $FFFF   R  fetch PCH

Similarly, an NMI can hijack an IRQ, and an IRQ can hijack a BRK (though it won't be as visible since they use the same interrupt vector). The tick-by-tick breakdown of all types of interrupts is essentially like that of BRK, save for whether the B bit is pushed as set and whether an additional PC increment occurs.

IRQ and NMI tick-by-tick execution

For exposition and to emphasize similarity with BRK, here's the tick-by-tick breakdown of IRQ and NMI (derived from Visual 6502). IRQ and NMI trigger after the instruction during which they're asserted (but see the next section for caveats).

 #  address R/W description
--- ------- --- -----------------------------------------------
 1    PC     R  fetch opcode (and discard it - $00 (BRK) is forced into the opcode register instead)
 2    PC     R  read next instruction byte (actually the same as above, since PC increment is suppressed. Also discarded.)
 3  $0100,S  W  push PCH on stack, decrement S
 4  $0100,S  W  push PCL on stack, decrement S
*** At this point, the signal status determines which interrupt vector is used ***
 5  $0100,S  W  push P on stack (with B flag *clear*), decrement S
 6   A       R  fetch PCL (A = FFFE for IRQ, A = FFFA for NMI)
 7   A       R  fetch PCH (A = FFFF for IRQ, A = FFFB for NMI)

Delayed IRQ response after CLI, SEI, and PLP

(The below description was pulled from blargg's cpu_interrupts_v2.)

The RTI instruction affects IRQ inhibition immediately. If an IRQ is pending and an RTI is executed that clears the I flag, the CPU will invoke the IRQ handler immediately after RTI finishes executing.

The CLI, SEI, and PLP instructions effectively delay changes to the I flag until after the next instruction. For example, if an interrupt is pending and the I flag is currently set, executing CLI will execute the next instruction before the CPU invokes the IRQ handler. This delay only affects inhibition, not the value of the I flag itself; CLI followed by PHP will leave the I flag cleared in the saved status byte on the stack (bit 2), as expected.

The above behavior is likely due to the I flag not changing until after the last tick of CLI, SEI, and PLP, meaning the next instruction will already have begun executing when the new value becomes effective; see this post.

Notes

  • The above interrupt hijacking and IRQ response behavior is tested by the cpu_interrupts_v2 test ROM.
  • For more quirky behavior related to VBlank NMI's from the PPU, see PPU frame timing.
  • NMI is edge-sensitive while IRQ is level-sensitive. Both are active low. NMI is polled every cycle (to be able to detect transitions in the middle of an instruction), while IRQ is polled during the last cycle of an instruction (before the opcode fetch for the next instruction).
  • The B status flag doesn't physically exist inside the CPU, and only appears as different values being pushed for bit 4 of the saved status bits by BRK and NMI/IRQ.
  • For a more technical description of what causes the hijacking behavior, see Visual6502's writeup.