CPU interrupts: Difference between revisions

From NESdev Wiki
Jump to navigationJump to search
(Interrupts are actually polled at the end of the second-to-last cycle (verified in Visual 6502, and doing it like that makes blargg's cpu_interrupts_v2 pass) (more changes coming))
(Interrupts are polled right before the PCH fixup cycle for branches. Looks like the interrupt hijacking timing was already correct.)
Line 1: Line 1:
== Detailed interrupt behavior ==
== Detailed interrupt behavior ==


The NMI input is edge-sensitive (reacts to high-to-low transitions in the signal) while IRQ is level-sensitive (reacts to the signal level). Both are active low. NMI is polled every CPU cycle (to be able to detect transitions in the middle of an instruction), while IRQ is usually polled during the second-to-last cycle of an instruction (two cycles before the opcode fetch). The polling happens while [[CPU_pin_out_and_signal_description|φ2 is high]], so the status of the interrupt lines on the falling edge of φ2 (at the end of the second-to-last cycle, just before the last cycle) matters. If the polling operation detects that the interrupt line has been asserted, the next "instruction" executed is the interrupt sequence. The point where IRQ is polled is also the point where the CPU will register a pending NMI, making NMIs behave similarly to IRQs w.r.t. the behavior discussed below.
The NMI input is edge-sensitive (reacts to high-to-low transitions in the signal) while IRQ is level-sensitive (reacts to the signal level). Both are active low. NMI is polled every CPU cycle (to be able to detect transitions in the middle of an instruction), while IRQ is usually polled during the second-to-last cycle of an instruction (two cycles before the opcode fetch). The polling happens while [[CPU_pin_out_and_signal_description|φ2 is high]], so the status of the interrupt lines on the falling edge of φ2 (at the end of the second-to-last cycle, just before the last cycle) matters. If the polling operation detects that the interrupt line has been asserted, the next "instruction" executed is the interrupt sequence. The point where IRQ is polled is also the point where the CPU will register a pending NMI, making NMIs behave similarly to IRQs w.r.t. the behaviors discussed below.
 
The interrupt sequences themselves do not perform interrupt polling, meaning at least one instruction from the interrupt handler will execute before another interrupt is serviced.


== Delayed IRQ response after CLI, SEI, and PLP ==
== Delayed IRQ response after CLI, SEI, and PLP ==
Line 13: Line 15:
== Branch instructions and interrupts ==
== Branch instructions and interrupts ==


The branch instructions have more subtle interrupt polling behavior. Interrupts are always polled during the second CPU cycle (during the operand fetch). Additionally, for taken branches that cross a page boundary, interrupts are polled during the PCH fixup cycle (see [http://nesdev.org/6502_cpu.txt] for a tick-by-tick breakdown of the branch instructions). An interrupt being detected at either of these polling points (including only being detected at the first one) will trigger a CPU interrupt.
The branch instructions have more subtle interrupt polling behavior. Interrupts are always polled during the second CPU cycle (during the operand fetch). Additionally, for taken branches that cross a page boundary, interrupts are polled before the PCH fixup cycle (see [http://nesdev.org/6502_cpu.txt] for a tick-by-tick breakdown of the branch instructions). An interrupt being detected at either of these polling points (including only being detected at the first one) will trigger a CPU interrupt.


== Interrupt hijacking ==
== Interrupt hijacking ==
Line 24: Line 26:
Each [] is a CPU tick. [...] is whatever tick precedes the BRK opcode fetch.
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.
Asserting NMI during the interval marked with * causes a branch to the NMI routine instead of the IRQ/BRK routine:


     ********************
     ********************
Line 50: Line 52:
=== IRQ and NMI tick-by-tick execution ===
=== 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 [http://visual6502.org/ Visual 6502]). IRQ and NMI trigger after the instruction during which they're asserted (but see the next section for caveats).
For exposition and to emphasize similarity with BRK, here's the tick-by-tick breakdown of IRQ and NMI (derived from [http://visual6502.org/ Visual 6502]).


<pre>
<pre>

Revision as of 05:49, 26 August 2013

Detailed interrupt behavior

The NMI input is edge-sensitive (reacts to high-to-low transitions in the signal) while IRQ is level-sensitive (reacts to the signal level). Both are active low. NMI is polled every CPU cycle (to be able to detect transitions in the middle of an instruction), while IRQ is usually polled during the second-to-last cycle of an instruction (two cycles before the opcode fetch). The polling happens while φ2 is high, so the status of the interrupt lines on the falling edge of φ2 (at the end of the second-to-last cycle, just before the last cycle) matters. If the polling operation detects that the interrupt line has been asserted, the next "instruction" executed is the interrupt sequence. The point where IRQ is polled is also the point where the CPU will register a pending NMI, making NMIs behave similarly to IRQs w.r.t. the behaviors discussed below.

The interrupt sequences themselves do not perform interrupt polling, meaning at least one instruction from the interrupt handler will execute before another interrupt is serviced.

Delayed IRQ response after CLI, SEI, and PLP

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. This is due to RTI changing the I flag before the interrupt polling point.

The CLI, SEI, and PLP instructions on the other hand change the I flag after polling for interrupts, meaning they can effectively delay an interrupt 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.

Verification and testing of this behavior in emulators can be accomplished through test ROM images.

Branch instructions and interrupts

The branch instructions have more subtle interrupt polling behavior. Interrupts are always polled during the second CPU cycle (during the operand fetch). Additionally, for taken branches that cross a page boundary, interrupts are polled before the PCH fixup cycle (see [1] for a tick-by-tick breakdown of the branch instructions). An interrupt being detected at either of these polling points (including only being detected at the first one) will trigger a CPU interrupt.

Interrupt hijacking

The MOS 6502 and by extension the 2A03/2A07 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 (PC increments will occur 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, set I flag
 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 PC increments occur.

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).

 #  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), set I flag
 7   A       R  fetch PCH (A = FFFF for IRQ, A = FFFB for NMI)

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.
  • 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.