Interrupt is a hardware feature where external signals may cause the CPU to temporarily "pause" whatever it is doing in order to perform specific tasks. Once the interruption process is done the CPU returns to the interrupted process and continues working as if "nothing had happened". Countless programs for the 64 make use of the interrupt system for a host of different purposes. Even the 64's KERNAL system uses one type of interrupt to make RUN/STOP+RESTORE work, another to blink the cursor.
In the C-64
The 64 with its 6510 CPU supports two different types of interrupt; Interrupt ReQuest (IRQ) and Non-Maskable Interrupt (NMI). Each type has a host of hardware devices in the C64 that may supply the prompting signal to the CPU. The "official" difference is that the CPU has the option of ignoring the IRQ type, whereas it is forced to respond to an NMI. Unofficially, there are ways to disable the NMI systems as well!
The first thing the CPU does in response to both types of interrupts is to save the program counter and the status register onto the stack for retrieval after servicing the interrupt. For both types of interrupt, along with the cold start, the CPU is hardwired to perform what amounts to an indirect JMP via one of three vectors stored in the very last six bytes of KERNAL ROM:
- 65530–65531/$FFFA–$FFFB is the vector for NMI handling; it points to 65091/$FE43
- 65532–65533/$FFFC–$FFFD is the vector for handling cold start, pointing to 64738/$FCE2
- 65534–65535/$FFFE–$FFFF is the vector for handling both IRQ and BRK-instructions; it points to 65352/$FF48
All three vectors point to routines in KERNAL ROM, but the interrupt service routines in ROM end up taking a jump through a vector in RAM, which can be re-directed towards a user-supplied interrupt service routines.
Interrupt Request (IRQ)
The 65xx CPUs have an internal interrupt flag which can be manipulated through the use of the SEI and CLI machine code commands: This interrupt flag determines whether to respond to IRQs or not; when the bit is set, interrupts are ignored; when the bit is clear, the CPU responds to IRQs.
When responding to an IRQ the CPU ends up in the interrupt service routine at 65352/$FF48, which pushes the accumulator, the X and Y index registers onto the stack (see asm below) – notice that this happens after the CPU has already pushed the contents of its program counter and status register onto the stack on its own "initiative".
The interrupt service routine is not only responsible for IRQ handling but also for the use of BRK commands (used as part of debugging machine language programs). Therefore the routine proceeds to check the status register byte now stored on the stack to see if the BREAK flag was set, i.e. if the call to the routine was caused by a BRK command (BREAK flag set) or a "real" IRQ (BREAK flag clear). Then it performs an indirect JMP via either the IRQ vector at 788–789/$0314–0315 or the BREAK vector at 790–791/$0316–$0317.
By default the IRQ vector points to 59953/$EA31 where the "actual" IRQ service routine resides in ROM: It maintains the jiffy clock, scans the keyboard (particularly the RUN/STOP key), blinks the cursor and performs a few other functions. Finally (at 60033/$EA81) it pulls back the items stored from the stack (all those the CPU won't restore itself) and "hands" the system back to the interrupted process.
.C:ff48 48 PHA ;push A onto stack .C:ff49 8A TXA ;load X into A .C:ff4a 48 PHA ;push A onto stack (X register) .C:ff4b 98 TYA ;load Y into A .C:ff4c 48 PHA ;push A onto stack (Y register) ;*****NOTE******* ;now the stack contains: ;... ;--return address high byte ;--return address low byte ;--processor status at time of IRQ ;--A register at time of IRQ ;--X register at time of IRQ ;--Y register at time of IRQ .C:ff4d BA TSX ;transfer stack pointer to X .C:ff4e BD 04 01 LDA $0104,X ;load processor status into A ? .C:ff51 29 10 AND #$10 ;check for break condition .C:ff53 F0 03 BEQ $FF58 ;vector to ISR if break flag is set .C:ff55 6C 16 03 JMP ($0316) ;vector to break ISR .C:ff58 6C 14 03 JMP ($0314) ;vector to ISR
Non-Maskable Interrupt (NMI)
When responding to NMI-type interrupts the corresponding ROM routine first locks out IRQs by setting the interrupt flag and then takes an indirect JMP via the NMI vector at 792–793/$0318–$0319. This means that both the default ROM routine it points to (at 65095/$FE47) and any user-supplied NMI service routine must first save the accumulator and X and Y index registers (the ROM routine uses the stack just like the IRQ routine does).
The NMI routine checks if a cartridge is present. If there is, it hands over the system to cartridge code via a vector at 32770–32771/$8002–$8003. If the RUN-STOP key is pressed, the system assumes the NMI was caused as the user pressed the RUN-STOP and RESTORE key combination and handles it as a warm start, i.e. restoring default screen settings, quieting the SID, and returning command to the BASIC system.
Remark: The keyboard is shut down during this time. The C64 can't be used normally. This method should be used only in BASIC-Programs which are working without any errors. When the BASIC-Program is done, it should restart the interrupt.
- Activate Interrupt in BASIC:
POKE 56334, PEEK(56334) OR 1
- Activate Interrupt with key-combination via keyboard: <RUN/STOP>+<RESTORE>