Skip to content

Interrupts

Ola Horg Jacobsen edited this page Feb 2, 2021 · 1 revision

Interrupts


Disclaimer: there is a lot of literature out there regarding this subject. If you want to know the full details, I recommend that you read Wilson Mines Co.'s (excellent) article on the subject, which can be found here. The MOS 6502 hardware manual is also a great resource. The information on this page is from a wide variety of sources, so take this wiki-page with a grain of salt, as it's only meant as an internal resource for the Macroprocessor team.

What are interrupts?

The NMOS 6502 processor has support for responding to outside events. This is very helpful when programming a computer. User input, scheduled events (like drawing to a monitor) and many more things become possible. These events are called interrupts, as they interrupt the processor's currently executing code. The 6502 has a couple interrupts (in decreasing priority, if any more than one happened at the same time, the higher priority interrupt will be serviced):

  • RES - the "reset", cannot be ignored
  • NMI - the "non-maskable interrupt", cannot be ignored
  • BRK - Not an outside interrupt! If the processor reads the instruction $00 (BRK) from memory, this will also trigger the same routine as an:
  • IQR - the "interrupt request", can be ignored (masked) These are all active-low, meaning they need to stay high (5.0v) until an interrupt should be triggered.

The interrupt service routine

The Interrupt Sequence - Source: http://wilsonminesco.com/6502interrupts/

When an interrupt is triggered, the interrupt service routine (ISR) will handle the interrupt. This first goes through a 7-cycle long setup phase, which leaves the processor at the first instruction in the programmed ISR. Let's go through the 7-cycles:

  1. Previous instruction is allowed to finish, registers or memory are updated as required.
  2. Next instruction is fetched
  3. Return address high byte (current ADH) is pushed on the stack
  4. Return address low byte (current ADL) is pushed on the stack
  5. Status register (P) is pushed on the stack, interrupt-disable flag (I) is set right after
  6. Interrupt vector low byte is fetched from memory (RES: $FFFC, NMI: $FFFA, IRQ: FFFE)
  7. Interrupt vector high byte is fetched from memory (RES: $FFFD, NMI: $FFFB, IRQ: FFFF)

Important points:

  • If the ISR borrows any registers for use, it must, when done, return it to the way it was before any changes were made. This is not just good practice, it's mandatory. The processor will crash coming out of the ISR if this is not upheld. However, we're in luck, since the 6502 already handles this for us.

  • The interrupt forces a $00 (BRK) into the instruction register (IR). This means that the processor essentially looks at a $00 (BRK) read from memory, the same as any other interrupt. However, interrupts are latched and fed into the random control logic, which makes the processor respond differently to each interrupt (which vector etc.)

  • When a byte is pulled off the stack, the stack pointer is incremented before the byte is read. As a sidenote, it should be mentioned that the interrupt sequence does not push the return address the same way that $20 (JSR) does. The $20 (JSR) does not push the address exactly where the execution should be resumed, but rather the address of the last byte of the three-byte $20 (JSR) instruction. The $20 (RTS) instruction at the end of a subroutine must take the address off the stack and increment it before picking up the next instruction. However, the $40 (RTI) at the end of an ISR does not need to do this incrementing. In other words, PLP RTS is not just a longer way to do RTI. It would actually land you at a different address, the one after RTI would have taken you to.

  • If a 3-cycle branch instruction (i.e. a taken branch not crossing a page boundary) ends in the cycle before the 6502 would normally start the 7-cycle start of the ISR, the 6502 executes another full instruction and starts with interrupt handling after that. A few different resources have tested and verified this behaviour.

Overview of the process

  • On the first half cycle of the next instruction, this means half-cycle 5, or start of clock cycle 3, the processor checks for the NMI and IRQ interrupts. If any of these lines are low (NMI pri over IRQ), we force 0 into IR, and start the ISR.

Let's go through the summary of what happens.

Step 1

During an instruction, an interrupt is triggered.

Step 2

Because an interrupt was triggered, the processor forces $00 (BRK) into the IR.

Step 3

This value is latched on the next instruction fetch. (Timing state T1)

Step 4

The processor starts exectuing instruction $00 (BRK).

The interrupts are passed to random control

The processor starts exectuing instruction $00 (BRK).

This means that what causes the 7-cycle interrupt, is the IR being $00. Now, this means that there really isn't any way to distinguish a BRK and IRQ/NMI/RES.

  • Check for all interrupt lines (latched - minimize unintentional behaviour) (needs more research?)
  • Given that the IR is 0, if the case is that none of the IRQ/NMI/RES lines are low, we can modify the B flag accordingly. ($00 (BRK) read from memory caused the interrupt, not an outside source)
  • Everything else happens accordingly, where the line which is pulled low dictates where the prosessor will look for the according reset vectors.

However, this is not all. The reset interrupt (RES) is special.

  • If this line is low, (none of the actual resetting is taken into account here), we will have to force the R/W status of the prosessor to be in read mode, even though this wouldn't normally happen on stack pushes. This is so we don't modify any memory, but the processor will be in the correct state to operate after the reset is done.

tl;dr

The interrupt routine is actually all the same "instruction", $00 (BRK). The prosessor is "hacked" to lookup the different interrupt service vectors according to which of the external control lines are pulled low.