Skip to content
Hardware

Inside the VIC-II

The graphics chip that defined the Commodore 64

Sprites, smooth scrolling, and 16 colours on a budget — the VIC-II (6567/6569) generated everything you saw on a Commodore 64. This entry covers both the historical context and the full programmer's reference.

commodore-64 vic-iiraster-interruptsspritesbadlinesregistersdisplay-modes 1982–1994

Overview

The MOS Technology VIC-II (6567 NTSC / 6569 PAL) is the Commodore 64's video chip. It paints the 40×25 character screen, drives eight hardware sprites, scrolls backgrounds smoothly in hardware, and exposes 47 registers that let programmers control every aspect of the display. Its quirks — especially badlines — defined the timing tricks that demo coders exploited for decades.

Fast facts

  • Display modes: 320×200 hi-res (two colours per cell) or 160×200 multicolour (four colours per cell), plus bitmap and extended-colour variants.
  • Palette: 16 fixed colours with famously rich blues and browns.
  • Sprites: eight 24×21 pixel sprites, independently positioned, with 2× horizontal stretch ($D01D) and 2× vertical stretch ($D017).
  • Registers: 47 at $D000-$D02E (53248-53294 decimal).
  • Timing: steals CPU cycles on certain scanlines, so code must dance around the raster beam.

Chip variants

  • 6567 (NTSC) — ~60Hz, 263 raster lines per frame, 65 CPU cycles per line
  • 6569 (PAL) — ~50Hz, 312 raster lines per frame, 63 CPU cycles per line

Most programming is identical between variants. Timing-critical code (raster interrupts, sprite multiplexing, music players) needs region-specific values.

Register map

The VIC-II's registers live at $D000-$D02E (53248-53294 decimal). Writing to these addresses controls display hardware directly.

AddressRegisterPurpose
$D000-$D001MOB0X, MOB0YSprite 0 position
$D002-$D003MOB1X, MOB1YSprite 1 position
$D004-$D005MOB2X, MOB2YSprite 2 position
$D006-$D007MOB3X, MOB3YSprite 3 position
$D008-$D009MOB4X, MOB4YSprite 4 position
$D00A-$D00BMOB5X, MOB5YSprite 5 position
$D00C-$D00DMOB6X, MOB6YSprite 6 position
$D00E-$D00FMOB7X, MOB7YSprite 7 position
$D010MSIGXHigh X bits (for X > 255)
$D011SCROLYVertical scroll, screen enable, bitmap mode, raster high bit
$D012RASTERCurrent raster line counter
$D013-$D014LPENX, LPENYLight pen position
$D015SPENASprite enable bits
$D016SCROLXHorizontal scroll, multicolour mode
$D017YXPANDSprite Y expansion (double height)
$D018VMCSBMemory pointers for screen and character set
$D019VICIRQInterrupt status
$D01AIRQMASKInterrupt enable
$D01BSPBGPRSprite-background priority
$D01CSPMCSprite multicolour enable
$D01DXXPANDSprite X expansion (double width)
$D01ESPSPCLSprite-sprite collision
$D01FSPBGCLSprite-background collision
$D020EXTCOLBorder colour
$D021BGCOL0Background colour 0
$D022BGCOL1Background colour 1
$D023BGCOL2Background colour 2
$D024BGCOL3Background colour 3
$D025SPMC0Sprite multicolour 0
$D026SPMC1Sprite multicolour 1
$D027-$D02ESP0COL-SP7COLIndividual sprite colours

Border and background colours

The simplest VIC-II registers to use:

POKE 53280,0  : REM Border colour (black)
POKE 53281,1  : REM Background colour (white)

Colour values (0-15):

0=Black   1=White    2=Red      3=Cyan
4=Purple  5=Green    6=Blue     7=Yellow
8=Orange  9=Brown   10=Lt Red  11=Dk Grey
12=Grey  13=Lt Grn  14=Lt Blue 15=Lt Grey

Games use border colour for visual effects — flash red on damage, pulse with music, change per level.

Screen RAM configuration ($D018)

Register $D018 (53272) tells the VIC-II where to find screen RAM and the character set.

Default value: $14 (20 decimal)

  • Screen RAM at $0400 (1024)
  • Character ROM at $1000

Bit layout:

Bits 7-4: Screen RAM location (×$0400)
Bits 3-1: Character set location (×$0800)
Bit 0:    Unused

Example — move screen to $0C00:

POKE 53272,(PEEK(53272) AND 15) OR 48

Calculation: $0C00 / $0400 = 3, so bits 7-4 = 3 (0011), shifted left 4 bits = 48.

Games use this for double-buffering (draw to hidden screen, flip display).

Display control ($D011)

Register $D011 (53265) controls vertical scrolling, screen height, and display mode.

Bit layout:

Bit 7:    Raster compare bit 8 (for $D012)
Bit 6:    Extended colour mode
Bit 5:    Bitmap mode
Bit 4:    Screen enable (0=blank screen)
Bit 3:    25/24 row select (1=25 rows)
Bits 2-0: Vertical scroll (0-7 pixels)

Default value: $1B (27 decimal) — 25 rows, screen on, text mode, scroll=3.

Example — enable bitmap mode:

POKE 53265,PEEK(53265) OR 32

Bit 5 set = bitmap mode. Screen now displays 320×200 bitmap instead of 40×25 text.

Horizontal control ($D016)

Register $D016 (53270) controls horizontal scrolling and multicolour mode.

Bit layout:

Bit 5:    Reset bit (always 0)
Bit 4:    Multicolour mode
Bit 3:    40/38 column select (1=40 columns)
Bits 2-0: Horizontal scroll (0-7 pixels)

Default value: $08 (8 decimal) — 40 columns, single colour, scroll=0.

Example — enable multicolour text:

POKE 53270,PEEK(53270) OR 16

Multicolour mode gives 4 colours per character cell but halves horizontal resolution (160×200 effectively).

Raster register ($D012)

Register $D012 (53266) shows the current raster line (0-311 PAL, 0-261 NTSC).

Read this register to:

  • Time code to specific screen positions
  • Create raster interrupts (change colours mid-screen)
  • Synchronise sprite movement
  • Split screen effects (status bars, parallax)

Example — wait for raster line 100:

10 IF PEEK(53266)<>100 THEN 10
20 POKE 53280,2  : REM Change border when line 100 reached

Sprite enable ($D015)

Register $D015 (53269) enables/disables sprites using bit flags — bit 0 = sprite 0, bit 7 = sprite 7.

Example — enable sprites 0, 1, and 2:

POKE 53269,7  : REM Binary 00000111 = sprites 0-2 on

Turn off sprite 1:

POKE 53269,PEEK(53269) AND 253  : REM Clear bit 1

Sprite positions ($D000-$D010)

Each sprite has X and Y registers. Y is 8-bit (0-255), X is 9-bit (0-511) using $D010 for high bits.

Example — position sprite 0 at (150, 100):

POKE 53248,150  : REM X low byte
POKE 53249,100  : REM Y position
POKE 53264,PEEK(53264) AND 254  : REM Clear X high bit

For X > 255:

POKE 53248,260 AND 255  : REM Low byte (260-256=4)
POKE 53264,PEEK(53264) OR 1  : REM Set bit 0 in $D010

Register $D010 stores high X bits for all 8 sprites (bit 0 = sprite 0, bit 1 = sprite 1, etc.).

Sprite data

Sprite shapes are stored in 64-byte blocks (63 bytes used, last byte unused). Each sprite is 24×21 pixels, stored as 3 bytes per row × 21 rows.

Sprite pointers: Screen RAM + $03F8-$03FF (1016-1023)

  • Pointer for sprite 0 at 2040
  • Pointer for sprite 1 at 2041
  • ...
  • Pointer for sprite 7 at 2047

Example — set sprite 0 to block 13:

POKE 2040,13  : REM Sprite data at 13*64 = 832

Create simple sprite (vertical line):

10 FOR I=832 TO 894
20 POKE I,255  : REM First byte of each row = 11111111
30 NEXT I
40 POKE 2040,13  : REM Point sprite 0 to block 13
50 POKE 53269,1  : REM Enable sprite 0
60 POKE 53248,150  : REM X position
70 POKE 53249,100  : REM Y position

Collision detection

  • Sprite-sprite: $D01E (53278)
  • Sprite-background: $D01F (53279)

Both registers use bit flags (bit 0 = sprite 0, etc.).

Example — check if sprite 0 hit anything:

10 C=PEEK(53278)  : REM Sprite-sprite
20 IF C AND 1 THEN PRINT "SPRITE 0 HIT ANOTHER SPRITE"
30 B=PEEK(53279)  : REM Sprite-background
40 IF B AND 1 THEN PRINT "SPRITE 0 HIT BACKGROUND"

Important: Reading these registers clears them. Check once per frame.

Display modes

The VIC-II supports multiple display modes via $D011 and $D016:

Mode$D011 bit 5-6$D016 bit 4ResolutionColours
Text00040×25 chars2 per char
Multicolour text00140×25 chars4 per char
Bitmap010320×200 pixels2 per 8×8
Multicolour bitmap011160×200 pixels4 per 4×8
Extended colour10040×25 chars4 backgrounds

Most games use bitmap modes for graphics, text mode for UI.

Badlines

Every 8th raster line in the visible area, the VIC-II "steals" 40 CPU cycles to fetch screen data. These are badlines — they run from raster line 48 to 247, occurring whenever the bottom three bits of $D012 match the bottom three bits of $D011 (the YSCROLL register). With the default YSCROLL value of 3, badlines fall on lines 51, 59, 67…251.

Effect: CPU halts for 40 cycles. Code timing becomes unpredictable.

Solutions:

  • Execute time-critical code outside badlines
  • Disable screen during tight loops (POKE 53265,PEEK(53265) AND 239)
  • Use raster interrupts to run code in overscan area

Demo coders exploit badlines for FLI (Flexible Line Interpretation): by writing YSCROLL into $D011 to match the current raster line's low three bits, every line becomes a badline. Then changing $D018 each line points the VIC-II at a fresh character set per row, yielding 8 colours per 8×8 cell instead of 2.

Famous quirks

  • Sprite priority: sprites can pass in front of or behind the background with per-sprite control bits.
  • Open borders: the top/bottom border opens via $D011 bit 3 (switch to 24-row mode at the right cycle); the side borders open via $D016 bit 3 (switch to 38-column mode). Both rely on cycle-exact timing relative to the raster beam.
  • Sprite crunching: repositioning sprites mid-frame creates the illusion of more than eight on screen — critical for shooters.

Practical example: colour cycling border

10 FOR C=0 TO 15
20 POKE 53280,C
30 FOR D=1 TO 50:NEXT D
40 NEXT C
50 GOTO 10

Cycles border through all 16 colours with delay.

Practical example: raster bars

10 POKE 53265,PEEK(53265) AND 239  : REM Blank screen
20 FOR R=0 TO 255
30 IF PEEK(53266)<>R THEN 30
40 POKE 53280,(R AND 15)
50 NEXT R
60 GOTO 20

Creates animated colour bars by changing border colour every raster line.

Historical notes

The VIC-II was designed by Al Charpentier at MOS Technology in 1981, evolving from the VIC chip in the VIC-20. Charpentier added hardware sprites (building on the player-missile graphics ideas from earlier Atari hardware), smooth scrolling registers, and collision detection — features that made arcade-quality games possible on a $595 home computer, undercutting competitors dramatically.

Early games used the VIC-II's built-in features straightforwardly — sprites for player/enemies, text mode for screens. By 1985, programmers discovered register tricks: open borders (full-screen graphics), sprite multiplexing (reusing sprites mid-frame), and FLI modes (changing graphics mode per scanline). Demo groups like Crest and Censor Design pushed furthest, creating effects Commodore's engineers never imagined — and still find new raster tricks four decades later.

See also