Anonymous IRQ Handlers

Written by Trident .:•:.

Anonymous IRQ (Interrupt Request Handlers) is a technique for making Commodore 64 demo programming easier by making interrupt handlers sequential: your code will be neatly kept in one place and its structure makes it easy to understand exactly what it is doing.

I developed this technique after struggling with messy interrupt handlers for some 30+ years. And it made C64 programming so much more fun, because I no longer have to think about low-level details.

The anonymous IRQ handlers technique was also covered in my talk at Fjälldata 2025, which can be seen here:

Background: Commodore 64 Raster Interrupts

When writing C64 demos, everything is driven by the action on the screen. And on the C64 we have a neat way to drive action on the screen in code: raster interrupts.

A raster interrupt happens on specific raster line on the screen. The raster line is provided by writing a value into the $d012 register (you may also need to write one bit to the $d011 register, but ignore that for now). And when the graphics chip is about to paint that line on the screen, it sends an interrupt signal to the CPU. This causes the CPU to interrupt whatever is was doing and jump to an interrupt routine. The interrupt routine’s memory address is taken from the $fffe/$ffff pair of addresses.

Once our interrupt handler has been called, we can update things on the screen, such as the border color.

That interrupt handler can then set up a new raster interrupt, further down on the screen, to update something more.

This chaining of interrupt handlers is key: this lets the underlying code do something else (such as loading the next part from disk) while we are updating the screen.

The raw assembler code to setup a raster interrupt handler looks something like this:

{
    lda #$7f
    sta $dc0d // Turn off timer interrupts 
    lda $dc0d // Acknowledge any lingering timer interrupt

    sei       // Disable interrupts while we work on the interrupt settings

    lda #$35
    sta $01   // Turn off ROM

    lda #$01
    sta $d01a // Enable raster interrupts

    lda #$40 
    sta $d012 // Set up a raster interrupt on raster line $40

    lda #<irq1
    sta $fffe
    lda #>irq1
    sta $ffff // Make the raster interrupt call the routine in irq1
    
    cli       // Enable interrupts

              // Here we could load the next part, but for now, just do an
    jmp *     // infinite loop - the interrupt handlers drive everything now
}

And the interrupt handler might look like this:

irq1:
{
    pha
    txa
    pha
    tya
    pha // save the registers on the stack

    lda #$2 
    sta $d020 // Set the border color to dark red

    lda #$50
    sta $d012 // Set up a new raster interrupt on raster line $50

    lda #<irq2
    sta $fffe
    lda #>irq2
    sta $ffff  // Set up a new interrupt handler for the next interrupt

    inc $d019 // Acknowledge the raster interrupt

    pla 
    tay
    pla
    tax
    pla  // Restore processor registers from the stack
    rti  // Return from interrupt
}

The code above set up a second raster interrupt, with an interrupt handler that may look like this:

irq2:
{
    pha
    txa
    pha
    tya
    pha // save the registers on the stack

    lda #$e 
    sta $d020 // Set the border color to light blue again

    lda #$40
    sta $d012 // Set up the raster interrupt on raster line $40 again

    lda #<irq1
    sta $fffe
    lda #>irq1
    sta $ffff  // Set up the first interrupt handler again

    inc $d019 // Acknowledge the raster interrupt

    pla 
    tay
    pla
    tax
    pla  // Restore processor registers from the stack
    rti  // Return from interrupt
}

If we run this code, the result will look something like this:

Border colors

The border color is switched to dark red at rasterline $40 and then back to light blue at rasterline $50.

From looking at this code, there are some obvious repetition that we can encapsulate using assembler macros. This will make the code a little easier to follow, so let’s do that.

.macro irq_setup(irqhandler, rasterline) {
    lda #rasterline
    sta $d012 // Set the low 8 bits of the rasterline in $d012
    lda $d011 // Set the 9th bit of the rasterline in the 8th bit of $d011
    .if (rasterline < $100) {
        and #$7f
    } else {
        ora #$80
    }
    sta $d011

    lda #<irqhandler
    sta $fffe
    lda #>irqhandler
    sta $ffff // Set the interrupt handler address
}
.macro irq_enter() {
    pha
    txa
    pha
    tya
    pha  // Store registers on the stack
}
.macro irq_leave() {
    inc $d019 // Acknowledge the raster interrupt

    pla 
    tay
    pla
    tax
    pla  // Restore processor registers from the stack
    rti  // Return from interrupt
}

Now our interrupt handlers are briefer:

irq1:
{
    irq_enter()

    lda #$2 
    sta $d020 // Set the border color to dark red

    irq_setup(irq2, $50) // Next interrupt in the chain at rasterline $50

    irq_leave()
}

irq2:
{
    irq_enter()

    lda #$e 
    sta $d020 // Set the border color to light blue again

    irq_setup(irq1, $40) // Next interrupt at rasterline $40

    irq_leave()
}

Although the assembler macros help us make the code easier to read, they do not help with the real problem: that we need to spread our code over multiple interrupt handlers, which we need to give individual names.

The Problem: Interrupt Handlers Get Messy, Quickly

The problem with interrupt handlers is that they need to be in a separate subroutine, which we will have to give a unique name.

We may start with irq1 and irq2, which is reasonably neat, but eventually we may want to insert another interrupt handler between them. If we call this irq3, the order will be difficult to maintain, so we might call it irq1b. And we update the code in irq1 to set up irq1b instead of irq2. But what if we now want to change the order of irq1b and irq2? We will need to update the code inside both irq1 and irq1b so that the chain of interrupts is correct. But should we now change the names too? Sometimes we do, and sometimes we don’t, so we end up with a chain of interrupts that may or may not have names that follow their order.

And regardless of the names of our interrupt handlers, their code will be spread out all over the file, which makes it very difficult to see what the code actually is supposed to do.

I was always writing code like this in the past and it made the code trickier and tricker to get right. We can also see this pattern in the source code of many recent demos, such as Next Level by Performers, Halloweed 4 by Xenon, and No Bounds by Genesis Project.

The Solution: Anonymous IRQ Handlers

Now what if I told you that there is a way to write interrupt handlers like a sequence where everything is encapsulated in the same subroutine and were we could just “wait” for those raster interrupts to occur:

irq:
{
    irq_wait_rasterline($40)
    lda #$2
    sta $d020

    irq_wait_rasterline($50)
    lda #$e
    sta $d020

    jmp irq
}

This code will provide the exact same result as the code above: the border color will be changed to dark red at rasterline $40 and to light blue at rasterline $50. But instead of spreading this code out into multiple IRQ handlers, everything is contained in the same subroutine.

I call this technique anonymous IRQ handlers: inside those irq_wait_rasterline() they are IRQ handlers, just like in the old code, but they are anonymous: we do not need to give them names.

This is what the irq_wait_rasterline() macro looks like:

.macro irq_wait_rasterline(rasterline) {
    irq_setup(anonymous, rasterline)
    irq_leave()
anonymous:
    irq_enter()
}

Extremely simple!

The next interrupt will be handled by that anonymous label, which is completely contained inside the macro.

Anonynmous IRQ handlers mean that we don’t need to think about how to name our IRQ handlers. We don’t even need to think of them as IRQ handlers, all we need to think about is what we want to do on the screen, and where we want to do it.

It is now easy to change the order of things, simply by moving the code around inside that irq subroutine. And updating the place on screen at which that interrupt will occur is easy too: just change the number in the code. The updated code will be exactly as easy to read as the original code was, before the change.

Conditionals

Anonymous IRQ handlers make it easy to selectively chose which IRQ handlers that should be run. And the intent of the code becomes immediately evident from reading it. Like this:

irq:
{
    irq_wait_rasterline($40)
    jsr do_something

    lda should_show_rasterbar
    beq dont_show_rasterbar
    {
        irq_wait_rasterline($50)
        jsr show_rasterbar
    }
dont_show_rasterbar:

    jmp irq
}
should_show_rasterbar:
    .byte 0

Loops

Anonymous IRQ handlers even make it possible to loop around IRQ handlers. For this example we need to define a shorter version of our waiting macro:

.macro irq_wait() {    
    lda #<anonymous
    sta $fffe
    lda #>anonymous
    sta $ffff 
    // Do not set $d012 here - let the caller do that
    irq_leave()
anonymous:
    irq_enter()
}

And now we can update the border color in a loop:

irq:
{
    lda #0
    sta color_counter
    lda #$40
    sta rasterline_counter

loop:
    lda rasterline_counter
    sta $d012
    irq_wait()
    lda color_counter
    sta $d020

    inc color_counter
    lda rasterline_counter
    clc
    adc #8
    cmp #$f0
    bne loop
    
    lda #$e
    sta $d020

    jmp irq

color_counter:
    .byte 0
rasterline_counter:
    .byte 0    
}

This will result in the following screen output:

Border colors

The First IRQ Handler

There is one more thing we need to do here before using or anonymous IRQ handlers: we need to make sure that our irq subroutine is called with the correct context. Because the first irq_wait_rasterline() will restore the stack with irq_restore(), we need to set up the stack correctly before calling it.

The easiest way to do this is to have a trampoline interrupt, which we set up during the initialization code, that will jump into our anonymous IRQ handler subroutine. This code will look something like this:

{
    lda #$7f
    sta $dc0d // Turn off timer interrupts 
    lda $dc0d // Acknowledge any lingering timer interrupt

    sei       // Disable interrupts while we work on the interrupt settings

    lda #$35
    sta $01   // Turn off ROM

    lda #$01
    sta $d01a // Enable raster interrupts

    irq_setup(trampoline, 0) // Set up our trampoline interrupt at the top of the screen
    
    cli       // Enable interrupts

              // Here we could load the next part, but for now, just do an
    jmp *     // infinite loop - the interrupt handlers drive everything now

trampoline:
{
    irq_enter()
    jmp irq
}
}

Conclusions

Raster interupts are a cornerstone of C64 demo programming, but the code quickly gets messy after adding a few of them. This makes the code difficult to write and to read, which makes it difficult to change and will make the resulting code error-prone.

Anonymous IRQ handlers is a set of macros that remove the need to give each IRQ handler a name, resulting in code that is sequential and can be contained in a single subroutine. The resulting code is easier to write, easier to read, easier to modify and usually is less error-prone as a result.

Full Source Code

For reference, here is the full source code for this (in Kickasm format):

.macro irq_setup(irqhandler, rasterline) {
    lda #rasterline
    sta $d012 // Set the low 8 bits of the rasterline in $d012
    lda $d011 // Set the 9th bit of the rasterline in the 8th bit of $d011
    .if (rasterline < $100) {
        and #$7f
    } else {
        ora #$80
    }
    sta $d011

    lda #<irqhandler
    sta $fffe
    lda #>irqhandler
    sta $ffff // Set the interrupt handler address
}
.macro irq_enter() {
    pha
    txa
    pha
    tya
    pha  // Store registers on the stack
}
.macro irq_leave() {
    inc $d019 // Acknowledge the raster interrupt

    pla
    tay
    pla
    tax
    pla  // Restore processor registers from the stack
    rti  // Return from interrupt
}

.macro irq_wait_rasterline(rasterline) {
    irq_setup(anonymous, rasterline)
    irq_leave()
anonymous:
    irq_enter()
}

    * = $0801
    BasicUpstart(start)

    * = $0810
start:
{
    lda #$7f
    sta $dc0d // Turn off timer interrupts
    lda $dc0d // Acknowledge any lingering timer interrupt

    sei       // Disable interrupts while we work on the interrupt settings

    lda #$35
    sta $01   // Turn off ROM

    lda #$01
    sta $d01a // Enable raster interrupts

    irq_setup(trampoline, 0) // Set up our trampoline interrupt at the top of the screen

    cli       // Enable interrupts

              // Here we could load the next part, but for now, just do an
    jmp *     // infinite loop - the interrupt handlers drive everything now

trampoline:
{
    irq_enter()
    jmp irq
}
}

irq:
{
    irq_wait_rasterline($40)
    lda #$2
    sta $d020

    irq_wait_rasterline($50)
    lda #$e
    sta $d020

    jmp irq
}