CIA: The C64's Swiss Army Chip
Two chips for I/O, timing, and everything else
The MOS 6526 Complex Interface Adapter handled keyboard scanning, joystick input, serial communication, and timing—the unsung heroes of the Commodore 64.
The Commodore 64 contains two CIA chips: CIA1 ($DC00-$DC0F) handles the keyboard and joysticks, while CIA2 ($DD00-$DD0F) manages serial bus communication, VIC-II bank selection, and the user port. Together they provide the glue logic that connects the C64 to the outside world.
Fast facts
- Model: MOS 6526, or 8521 (HMOS-II shrink in late C64C boards). Commodore’s ordering table defines the
Asuffix as a speed grade — no suffix is the 1 MHz part,Athe 2 MHz one — not as a revision. - Chips per C64: two, at $DC00 and $DD00.
- Features: 16 programmable I/O lines across two ports, two linkable 16-bit interval timers, a 24-hour time-of-day clock with alarm, and an 8-bit serial shift register.
- Interrupts: can generate IRQ (CIA1) or NMI (CIA2).
CIA1 ($DC00-$DC0F) - User input
| Register | Address | Purpose |
|---|---|---|
| Port A | $DC00 | Keyboard column / joystick 2 |
| Port B | $DC01 | Keyboard row / joystick 1 |
| DDRA | $DC02 | Data direction for Port A |
| DDRB | $DC03 | Data direction for Port B |
| Timer A | $DC04-$DC05 | 16-bit countdown timer |
| Timer B | $DC06-$DC07 | 16-bit countdown timer |
| TOD | $DC08-$DC0B | Time of day clock |
| Serial | $DC0C | Serial shift register |
| ICR | $DC0D | Interrupt control register |
| CRA | $DC0E | Control register A |
| CRB | $DC0F | Control register B |
CIA2 ($DD00-$DD0F) - System control
| Function | Register | Bits |
|---|---|---|
| VIC bank | $DD00 | bits 0-1 (inverted) |
| RS-232 TX | $DD00 | bit 2 |
| Serial bus out | $DD00 | bits 3-5 (ATN, CLK, DATA) |
| Serial bus in | $DD00 | bits 6-7 (CLK, DATA) |
| User port | $DD01 | 8 lines |
| NMI source | $DD0D | flags and mask |
$DD0D is CIA2’s Interrupt Control Register. Reading returns the active flags and clears them; writing updates the mask using the set/clear protocol. Five events can trigger an NMI through CIA2: Timer A underflow (bit 0), Timer B underflow (bit 1), TOD alarm match (bit 2), serial shift register byte completion (bit 3), and the /FLAG input pulse (bit 4) — typically wired to the user port.
Keyboard scanning
The keyboard matrix uses CIA1’s ports:
- Write to $DC00 to select a column (pull one bit low).
- Read $DC01 to see which rows have keys pressed.
- Scan all 8 columns to read the full keyboard.
$DC00 bits: column select (directly affects keyboard matrix lines)
$DC01 bits: row read (directly affected by pressed keys)
Joystick reading
Joysticks share lines with the keyboard:
| Direction | CIA1 Port | Bit |
|---|---|---|
| Joy 1 Up | $DC01 | 0 |
| Joy 1 Down | $DC01 | 1 |
| Joy 1 Left | $DC01 | 2 |
| Joy 1 Right | $DC01 | 3 |
| Joy 1 Fire | $DC01 | 4 |
| Joy 2 Up | $DC00 | 0 |
| Joy 2 Down | $DC00 | 1 |
| Joy 2 Left | $DC00 | 2 |
| Joy 2 Right | $DC00 | 3 |
| Joy 2 Fire | $DC00 | 4 |
Important: bits are active low (0 = pressed, 1 = released).
Timer operations
Each CIA has two 16-bit timers useful for:
- Music playback: trigger IRQs at precise intervals.
- Timing loops: measure elapsed time.
- Raster sync: supplement VIC-II raster interrupts.
- Serial timing: generate baud rates for communication. In output mode Timer A is the baud rate generator, shifting at half its underflow rate.
A timer can also drive a pin directly. A control bit routes Timer A’s output to PB6 and Timer B’s to PB7, either toggling or emitting a single one-cycle pulse on each underflow. This overrides DDRB and forces that line to an output, whatever the direction register says.
Basic timer setup
- Set timer low byte ($DC04) and high byte ($DC05).
- Configure control register ($DC0E) for one-shot or continuous.
- Enable timer interrupt in ICR ($DC0D).
- Timer counts down from loaded value to zero.
Time of day clock
The TOD clock is the one part of the CIA that does not run off the system clock. Commodore’s data sheet requires “an external 60Hz or 50Hz (programmable) TTL level input on the TOD pin for accurate time-keeping” — the C64 feeds it mains frequency. It counts in BCD across four registers (tenths, seconds, minutes, hours) with the AM/PM flag in the top bit of the hours register, so a single bit test tells you which half of the day it is.
The alarm shares those same four addresses; CRB bit 7 selects whether a write sets the clock or the alarm. A match raises one of the CIA’s five interrupt sources.
Because it is tied to the mains rather than to a crystal, a C64’s TOD keeps time only as well as the supply frequency does — and stops keeping it at all on a machine running from an inverter or a bench supply at the wrong frequency.
Handshaking
Ports A and B are not just pin banks. /PC goes low for one cycle after any read or write of Port B, signalling “data ready” or “data accepted”, and /FLAG is a negative-edge-sensitive input designed to receive another CIA’s /PC. On the C64, /FLAG on CIA2 is wired to the user port, which is why it appears in the NMI list above.
VIC-II bank selection
CIA2’s Port A bits 0-1 select which 16KB bank the VIC-II sees:
| $DD00 bits 0-1 | Bank | Address range |
|---|---|---|
| 11 | 0 | $0000-$3FFF |
| 10 | 1 | $4000-$7FFF |
| 01 | 2 | $8000-$BFFF |
| 00 | 3 | $C000-$FFFF |
Note: bits are inverted—lower values select higher addresses.