Stable Raster
Cycle-exact timing
The double-IRQ technique that achieves cycle-exact synchronisation with the VIC-II raster beam, enabling perfect mid-screen effects on the Commodore 64.
Stable raster is the Commodore 64 technique for achieving cycle-exact synchronisation with the VIC-II’s raster beam. The jitter is not a bus-contention effect: it is that the CPU finishes whatever instruction it is executing before it takes the interrupt, and instructions are different lengths. That costs 0-7 cycles, and everything after it is fixed overhead — 7 cycles to enter the handler, 13 to push the registers, so the effect code starts somewhere in a 20-to-27-cycle window. The stable raster technique eliminates this jitter, enabling perfectly timed mid-screen effects.
Fast facts
| Aspect | Detail |
|---|---|
| Problem | IRQ timing varies by 0-7 cycles |
| Solution | Double-IRQ with self-modifying code |
| Precision | Cycle-exact (0 jitter) |
| Used for | Border removal, split screens, colour bars |
The Jitter Problem
When a raster interrupt fires, the CPU might be mid-instruction:
| Instruction Length | Jitter |
|---|---|
| 2 cycles | 0-2 cycle variation |
| 3 cycles | 0-3 cycle variation |
| 7 cycles | 0-7 cycle variation |
This jitter causes visible glitches in timing-sensitive effects.
The Double-IRQ Solution
The technique uses two interrupts:
| IRQ | Purpose |
|---|---|
| First IRQ | Sets up second IRQ one line later |
| Second IRQ | Triggers inside a long NOP slide |
Step by Step
- First IRQ fires with unknown jitter
- Set second IRQ for next raster line
- Execute NOP slide (many 2-cycle NOPs)
- Second IRQ fires inside NOP slide
- Jitter is now only 0-1 cycles
- Measure exact cycle position
- Compensate with calculated delay
Code Structure
; First IRQ - unstable, sets up second one line later
first_irq:
lda #<second_irq
sta $0314 ; Point CINV at second handler
lda #>second_irq
sta $0315
inc $d012 ; Next IRQ one line down
lda #$01
sta $d019 ; Acknowledge
tsx
cli ; Re-enable IRQs (so second can fire)
nop : nop : nop : nop : nop : nop : nop : nop
; ... long NOP slide ...
; The second IRQ will fire somewhere inside this slide,
; at one of two cycle positions (jitter is now 0 or 1).
; Second IRQ - stabilised
second_irq:
; Read $d012 to compensate for the remaining 0-1 cycle jitter
lda $d012
cmp $d012 ; If line just changed, branch to align
beq @aligned
@aligned:
; Now cycle-exact — perform the effect
sta $d020 ; Example: change border colour
; ... acknowledge and exit ...
The standard idiom is “first IRQ enables nested IRQ, NOP slide gives the second IRQ a known entry point, then a CMP+BEQ trick eliminates the last cycle of jitter.” This is the backbone of every cycle-exact C64 effect.
Why it works
| Mechanism | Effect |
|---|---|
| NOP slide | All instructions same length (2 cycles) |
| Maximum jitter = 1 | Only 0 or 1 cycle variation |
| Cycle counting | Determine exact position |
| Branch compensation | Conditional delay to align |
Applications
| Effect | Requirement |
|---|---|
| Border removal | Exact $D011 / $D016 bit-3 writes at cycle-precise raster positions |
| Raster bars | Colour changes per scanline |
| Split screens | Mode changes mid-frame (text/bitmap split, multicolour switch) |
| FLD (Flexible Line Distance) | Manipulate $D011 YSCROLL to space rendered lines vertically — produces the “stretchy screen” effect |
| FLI (Flexible Line Interpretation) | Per-line $D018 change combined with forced badlines — yields 8 colours per 8×8 cell |
| Tech-tech | Per-line $D016 XSCROLL change so text undulates in a sine wave |
What breaks it
The reference routine states its own preconditions without softening them:
Other Interrupts,
$d012=#$ff, Sprites, Badline and Badline-1 = fuckup.
Sprites and badlines both steal cycles from the CPU, so a stable raster established outside them stops being stable inside them. The KERNAL is a further complication: “if you have the KERNAL banked in, you need to modify the IRQ-Vectors.”
It is region-specific
The NOP slide is tuned to the line length, so it does not survive the PAL/NTSC
boundary. Codebase64’s routine carries three variants — PAL-63, NTSC-64,
NTSC-65 — differing by a single loop count, and a note to “add one extra nop for
65 cycle NTSC machines”. The shortest known form, “the polling method, half variance
technique”, makes the same switch in one byte.
This is the reason so much C64 demo code is PAL-only. It is not a preference; the timing does not carry.
Performance Cost
| Factor | Impact |
|---|---|
| CPU time | Two IRQs per stable point |
| Code space | NOP slides, handler code |
| Complexity | Requires deep understanding |
Modern relevance
Understanding stable raster teaches:
- Interrupt handling fundamentals
- Cycle-exact timing
- Hardware synchronisation
- Why modern GPUs use VSync