Skip to content

Raster Interrupts

Precision timing with the video beam

A raster interrupt stops the main program when the television picture reaches a chosen line, so a handler can change colours, scroll registers or sprites partway down the screen. On the Commodore 64 the VIC-II chip provides it directly; the Amiga gets the same effect from its copper, and the NES from interrupts built into some cartridges.

A television draws its picture one line at a time, top to bottom, fifty or sixty times a second. A home computer’s video chip reads the same registers for every line, so a colour or scroll value set once applies to the whole screen. Change a register while the picture is being drawn, though, and everything below that point uses the new value. A raster interrupt makes the change happen at the right moment: the video chip interrupts the processor when the beam reaches a chosen line, and a short handler rewrites whatever it needs before the beam moves on.

Commodore said what this was for in the Commodore 64 Programmer’s Reference Guide in 1982: “This powerful interrupt structure lets you use split screen modes. For instance you can have half of the screen bit mapped, half text, more than 8 sprites at a time, etc. The secret is to use interrupts properly.” Five years later ZZAP!64‘s machine-code column, written by Gary Liddon, put it in players’ terms: “all those games that have the top half of the screen scrolling and the bottom half of the screen stationary are using raster interrupts.” The playfield scrolls, and a raster interrupt partway down puts the scroll register back to zero for the score panel.

On the Commodore 64

The VIC-II counts the lines it draws, and compares the count with a value the program has written. When they are equal it sets a flag, and if that source is enabled it pulls the processor’s IRQ line. Four registers do the work:

Register What it does here
$D012 (53266) Write: bits 0–7 of the line to interrupt on. Read: bits 0–7 of the current line.
$D011 (53265), bit 7 Bit 8 of the same line number, for lines 256 and above.
$D019 (53273), bit 0 Set when the line matches. Writing a 1 to it clears it.
$D01A (53274), bit 0 Set it to let the raster match cause an interrupt.

A PAL machine draws 312 lines and an NTSC one 262 or 263, depending on the chip revision, so eight bits cannot name them all; hence the ninth bit in $D011. Lines above 262 exist only on PAL, so an interrupt set for line 280 never happens on an NTSC machine.

The flag in $D019 stays set until the program clears it. The Programmer’s Reference Guide explains that each flag “must be cleared by writing a 1 to that bit in the interrupt register when you’re ready to handle it”. A handler that forgets returns to find the IRQ line still held low, and is called again at once, for ever. Liddon’s readers found the rule odd too: the flag “has to be cleared by bunging an $FF in there, yes I know bunging a zero in would be much more logical, but that’s the way it is.”

The comparison is for equality. If a program sets $D012 to a line the beam has already passed, nothing happens until that line comes round again in the next frame.

Sharing the IRQ line with the KERNAL

The VIC-II is not the only chip that can pull the IRQ line. CIA 1 does it too, about sixty times a second, to drive the KERNAL’s own interrupt routine, which keeps the clock, flashes the cursor and reads the keyboard; see CIA 6526. Mapping the Commodore 64 warns: “There is only one IRQ vector, but there are many sources for IRQ interrupts”. A program that adds a raster interrupt either works out which chip called, or switches the CIA’s interrupt off and calls the KERNAL routine itself once a frame.

The KERNAL gives two ways in. With the ROM switched in, the processor’s hardware vector at $FFFE points to a KERNAL routine at $FF48. It saves A, X and Y on the stack, checks that the interrupt was not a BRK instruction, and jumps through the RAM vector at $0314–$0315, which normally points to $EA31. A program changes $0314–$0315 to point to its own handler. That route costs 29 cycles after the processor’s own 7-cycle interrupt sequence, before the handler’s first instruction runs.

The handler then leaves in one of two ways:

  • JMP $EA31 runs the whole KERNAL routine: clock, cursor, keyboard scan. Then it restores the registers and returns.
  • JMP $EA81 skips all that. The five instructions there, PLA, TAY, PLA, TAX, PLA, followed by RTI, restore the registers and return, in 22 cycles.

All three C64 KERNAL revisions have both routines at the same addresses. They are two different exits, not PAL and NTSC versions of one, and the chain below runs unchanged on both kinds of machine.

A chain of interrupts

A split needs at least two interrupts a frame: one to change a register partway down, and one to change it back. Each handler sets up the next, by writing the next line number and pointing the vector at the next handler. The last one points back to the first.

This program colours three bands of the border, with interrupts on lines 100, 180 and 260. The third line is above 255, so the handler before it sets bit 7 of $D011, and the next one clears it again. It switches off CIA 1’s interrupts and lets the last handler of the frame call the KERNAL routine, so the keyboard and cursor keep working at one scan per frame. It returns to BASIC afterwards.

Assembly
; raster chain through the KERNAL vector at $0314 (ACME syntax)
; start with SYS 2304
        * = $0900
start   sei                 ; no interrupts while we change things
        lda #$7f
        sta $dc0d           ; switch off CIA 1's timer interrupts
        lda $dc0d           ; and clear any that are waiting
        lda #<irq1
        sta $0314           ; point the KERNAL's IRQ vector at irq1
        lda #>irq1
        sta $0315
        lda #100
        sta $d012           ; first interrupt on line 100...
        lda $d011
        and #$7f
        sta $d011           ; ...with bit 8 of the line number clear
        lda #$01
        sta $d01a           ; enable the raster interrupt
        sta $d019           ; clear any raster interrupt already flagged
        cli
        rts

irq1    lda #$01
        sta $d019           ; acknowledge
        lda #2
        sta $d020           ; border red from line 100
        lda #180
        sta $d012           ; next interrupt on line 180
        lda #<irq2
        sta $0314
        lda #>irq2
        sta $0315
        jmp $ea81           ; restore registers and return

irq2    lda #$01
        sta $d019
        lda #7
        sta $d020           ; border yellow from line 180
        lda #<260
        sta $d012           ; next interrupt on line 260 ($104):
        lda $d011           ; low byte in $d012,
        ora #$80            ; bit 8 in bit 7 of $d011
        sta $d011
        lda #<irq3
        sta $0314
        lda #>irq3
        sta $0315
        jmp $ea81

irq3    lda #$01
        sta $d019
        lda #14
        sta $d020           ; border back to light blue from line 260
        lda #100
        sta $d012
        lda $d011
        and #$7f
        sta $d011
        lda #<irq1
        sta $0314
        lda #>irq1
        sta $0315
        jmp $ea31           ; once a frame: the KERNAL's clock, cursor and keyboard

Line 260 exists on both PAL and NTSC machines, so the chain runs on either. The border is light blue at the top, red from line 100, yellow from line 180, and light blue again in the lower border. Because the KERNAL routine now runs once per frame, not from its own timer, BASIC’s TI clock counts frames: fifty a second on PAL, sixty on NTSC.

Without the KERNAL

Games that needed the memory, or the cycles, switched the KERNAL ROM out altogether. Liddon’s 1987 example did this “by altering location 1”, the processor port that selects which ROMs are visible. With the ROM gone, the processor fetches its vector from RAM at $FFFE–$FFFF, so the handler starts 7 cycles after the interrupt is taken instead of 36. It has to do the rest itself. The processor saves only the program counter and status register, so the handler saves any register it uses, and returns with RTI.

Assembly
; raster interrupt through the hardware vector, KERNAL switched out
; (ACME syntax; start it from a BASIC SYS line)
start   sei
        lda #$35
        sta $01             ; RAM at $a000 and $e000; I/O stays in at $d000
        lda #$7f
        sta $dc0d           ; no CIA 1 interrupts (IRQ)
        sta $dd0d           ; no CIA 2 interrupts (NMI)
        lda $dc0d           ; clear any that are waiting
        lda $dd0d
        lda #<irq1
        sta $fffe           ; the processor's own IRQ vector, now in RAM
        lda #>irq1
        sta $ffff
        lda #<nmi
        sta $fffa           ; RESTORE would jump through here
        lda #>nmi
        sta $fffb
        lda #100
        sta $d012
        lda $d011
        and #$7f
        sta $d011
        lda #$01
        sta $d01a
        sta $d019
        cli
main    jmp main            ; the main program would run here

irq1    pha                 ; this handler uses only A, so saves only A
        lda #$01
        sta $d019           ; acknowledge
        lda #2
        sta $d020           ; border red from line 100
        lda #200
        sta $d012           ; next interrupt on line 200
        lda #<irq2
        sta $fffe
        lda #>irq2
        sta $ffff
        pla
nmi     rti

irq2    pha
        lda #$01
        sta $d019
        lda #14
        sta $d020           ; border light blue from line 200
        lda #100
        sta $d012
        lda #<irq1
        sta $fffe
        lda #>irq1
        sta $ffff
        pla
        rti

The RESTORE key is wired to the processor’s NMI line, which SEI cannot block. With the KERNAL gone, its vector at $FFFA would point into whatever RAM lies underneath, so the program points it at an RTI.

What the handler cannot control

The interrupt is on time; the handler is not quite. The processor finishes the instruction it is running before it takes the interrupt, and 6510 instructions take 2 to 7 cycles, so the handler starts a few cycles later in some frames than in others. Each cycle is 8 pixels of screen, so a colour change made this way has a ragged edge. On a badline the VIC-II also stops the processor for 40 to 43 cycles; see the VIC-II entry. For most game splits, made in the border or between rows of graphics, none of this shows. For effects that must land on one exact cycle, programmers built stable raster routines.

The processor sets its interrupt-disable flag when it takes an interrupt, so a second raster interrupt waits until the first handler’s RTI restores the flag. A handler that runs longer than the gap to the next line in the chain misses it, and the next interrupt comes a frame late: the split jumps or flickers. Sprite multiplexers, which may need an interrupt every few lines, check for this before they return; see sprite multiplexing.

On the Amiga

The Amiga’s custom chips have no register that interrupts the processor at a chosen line. They have something better suited to most of the jobs: the copper, a coprocessor that waits for beam positions and writes to the custom chip registers itself, so colour splits and screen splits need no processor time at all.

When the processor does need to act at a particular line, the copper can interrupt it. Commodore’s Hardware Reference Manual says: “To interrupt the 680x0, use the Copper MOVE instruction to store a 1” into bit 4 of INTREQ, the bit called COPER, which “is used by the Copper to issue a level 3 interrupt”. Two instructions in a copper list do it:

68000 assembly
    dc.w    $8007,$fffe        ; WAIT for line 128 ($80)
    dc.w    $009c,$8010        ; MOVE to INTREQ: set bit 4, COPER

The level 3 handler reads INTREQR to see which source called, since vertical blank and the blitter share the level, and clears COPER by writing $0010 to INTREQ. The interrupt reaches the processor only if COPER and the master enable bit are set in INTENA.

On the NES

The NES’s picture chip, the PPU, has no line counter a program can set. It can raise a non-maskable interrupt when vertical blank begins, which is where most games do their screen updates. For a split partway down, a program can wait for the sprite 0 hit flag, as Super Mario Bros. does to hold its status bar still, but that means the processor waits in a loop.

A true mid-screen interrupt came from the cartridge. The MMC3 mapper counts scanlines by watching one of the PPU’s address lines and raises an IRQ when its counter reaches zero. NESdev’s documentation of the chip lists four registers: $C000 holds the number of lines to count, a write to $C001 reloads the counter, a write to $E001 enables the interrupt, and a write to $E000 “will disable MMC3 interrupts AND acknowledge any pending interrupts”. A handler that wants another interrupt later in the frame therefore writes $E000 to acknowledge, then sets the counter and writes $E001 again.

Sources

  • Commodore, Commodore 64 Programmer’s Reference Guide (1982): “More sprites can be displayed using RASTER INTERRUPT techniques” (p. 131); the raster register, the interrupt status and enable registers, clearing a flag by writing a 1, split screens and “more than 8 sprites at a time” (pp. 150–152); Appendix N, the interrupt register (p. 447).
  • Gary Liddon, machine-code column, “Raster interrupts drive Mr Liddon off the rails”, ZZAP!64 issue 29, September 1987, pp. 68–69: split-screen scrolling, banking out the KERNAL through location 1, enabling with $D01A, clearing $D019 with $FF.
  • Sheldon Leemon, Mapping the Commodore 64 (COMPUTE! Publications, 1984): the IRQ vector at 788 ($314), the ROM’s register saving, “only one IRQ vector, but there are many sources” (pp. 72–73); the IRQ entry at 59953 ($EA31) (pp. 219–220).
  • Commodore 64 KERNAL ROM, revisions 901227-01, -02 and -03, read from the ROM images: the IRQ entry at $FF48 (register saves, BRK test, JMP ($0314)) and the exit at $EA81 (PLA, TAY, PLA, TAX, PLA, RTI), identical in all three. Cycle counts are from the standard 6502 instruction timings.
  • Christian Bauer, The MOS 6567/6569 video controller (VIC-II) and its application in the Commodore 64 (1996), section 3.4: lines per frame for the 6569, 6567R8 and 6567R56A; section 3.5: badlines stop the processor for 40–43 cycles.
  • Synertek, SY6500/MCS6500 Microcomputer Family Programming Manual (August 1976), p. 25: the interrupt disable flag “is set by the microprocessor during reset and interrupt commands”.
  • Amiga Hardware Reference Manual, 3rd edition (Commodore-Amiga, Addison-Wesley, 1991): interrupting the 680x0 from the copper (p. 35); COPER and the level 3 interrupt (p. 219); INTENA, INTREQ and INTREQR, the level 3 sources and the INTEN master bit (Appendix A).
  • NESdev Wiki, “MMC3” (community reference, web, secondary): the IRQ latch, reload, disable and enable registers. https://www.nesdev.org/wiki/MMC3

The two C64 listings were assembled with ACME and run in the VICE emulator (x64sc) as PAL and NTSC machines.

See also

Checked. Verified against primary sources and signed off by a person.