Skip to content

6502 SEI/CLI Debate: Interrupt Control

I keep seeing people on forums criticising the use of SEI and CLI in 6502 code as “cargo cult programming.” The argument goes something like: “you don’t need SEI because if you’ve set up your interrupts properly, masking them is unnecessary.” This sounds clever until you think about it for more than five seconds.

Counter-Example 1: The Loader Problem

You’ve got a game with an IRQ-driven loader — the kind that uses CIA timer interrupts at $DC0D to handle serial bus communication while the main program continues running. The load completes. Now you need to set up a completely new IRQ schema for the next game level — different raster splits, different music player calls, different timing. You must disable interrupts while you reconfigure the IRQ vector at $FFFE/$FFFF (or $0314/$0315 via the Kernal), set up new raster compare values at $D012, and acknowledge any pending interrupts. If an IRQ fires mid-reconfiguration, the vector could be half-written — low byte pointing to your new handler, high byte still pointing to the old one. That’s a crash. SEI before reconfiguration, CLI after. Simple. Correct. Not cargo cult.

Counter-Example 2: The Experimenter

Someone’s been experimenting with raster IRQs all evening. They’ve got a chain running, split bars all over the screen, timers configured. Then they load a game from disk. If that game doesn’t issue an SEI at startup, the old raster IRQ chain is still live, firing away, trying to execute code at addresses that are now occupied by game data instead of interrupt handlers. Instant crash, or worse — subtle corruption that manifests as glitches minutes into gameplay.

This is the exact same principle as cleaning up expanded sprites. If a previous program sets $D017 (Y-expand) or $D01D (X-expand) and the next program doesn’t clear those registers, you get sprites displaying at double size when they shouldn’t be. Nobody calls clearing $D017 “cargo cult programming.” So why is SEI any different? It’s defensive initialisation. You don’t know what state the machine was in before your code started running.

Bonus: CLD at Startup

While we’re at it: put a CLD at the start of your code too. The 6502’s decimal mode flag persists across program boundaries. If the previous program (or the BASIC ROM, or some cartridge initialiser) left decimal mode on, your first ADC or SBC instruction will produce BCD results instead of binary. That’s a spectacularly confusing bug to track down. One byte, two cycles, and you’ll never have to wonder why your score counter is adding $06 + $01 and getting $07 instead of $07. Wait — bad example. Try $08 + $01: in decimal mode you get $09, in binary you get $09. Fine. $09 + $01: decimal gives $10, binary gives $0A. There it is.

SEI and CLD at program start. Not cargo cult. Just good practice from someone who’s been bitten by both.

See also: interrupt handler patterns · NMI configuration · more programming opinions