← Back to The Vault

Screen Memory

How the C64 displays text and graphics

Screen RAM, colour RAM, and the VIC-II—understanding how 1000 bytes create 40×25 characters of colourful text.

C64 Screen RAMVIC-IIMemory mapDirect screen access 1982–1994

Overview

The C64’s text screen is 40 characters wide by 25 lines tall—exactly 1000 characters. Each character is stored in screen RAM (which character to display) and colour RAM (what colour to draw it). Change a byte in screen RAM and the display updates instantly—no PRINT statements, no scrolling, just direct hardware control. This is how games draw fast.

Memory Layout

Screen RAM

Address: $0400-$07E7 (1024-2023 decimal)
Size: 1000 bytes (40 × 25)
Purpose: Stores screen codes for each character position

The screen is laid out sequentially in memory:

  • Bytes 1024-1063: Row 0 (top line, 40 characters)
  • Bytes 1064-1103: Row 1
  • Bytes 1104-1143: Row 2
  • Bytes 1984-2023: Row 24 (bottom line)

Colour RAM

Address: $D800-$DBE7 (55296-56295 decimal)
Size: 1000 bytes (40 × 25)
Purpose: Stores colour value (0-15) for each character

Colour RAM is not in the normal memory map—it’s special VIC-II chip RAM. Only the low 4 bits of each byte are used (values 0-15). The high 4 bits are ignored.

Position Formula

To find the memory address for any screen position:

REM Calculate screen position
ROW = 10
COL = 5
SCREEN = 1024 + (ROW * 40) + COL
COLOUR = 55296 + (ROW * 40) + COL

Example: Position (0,0) = 1024. Position (0,39) = 1063. Position (1,0) = 1064. Position (24,39) = 2023.

How It Works

The VIC-II chip scans screen RAM 50 times per second (PAL) or 60 times per second (NTSC), reading each byte and converting it to pixels on screen. Each screen code points to a character definition in the character ROM (or custom character set). The corresponding colour RAM byte tells the VIC-II what colour to draw that character.

The process:

  1. VIC-II reads byte from screen RAM (e.g., value 1 = letter ‘A’)
  2. VIC-II reads corresponding byte from colour RAM (e.g., value 7 = yellow)
  3. VIC-II looks up character 1 in character ROM (8 bytes define the ‘A’ shape)
  4. VIC-II draws yellow ‘A’ on screen

This happens continuously, automatically, every frame.

Screen Codes vs PETSCII

Critical difference: Screen RAM uses screen codes, not PETSCII codes.

CharacterPETSCIIScreen Code
@640
A651
B662
Space3232
04848

Rule of thumb:

  • PRINT uses PETSCII: PRINT "A" uses code 65
  • POKE uses screen codes: POKE 1024,1 displays ‘A’

See PETSCII Chart for complete conversion tables.

Practical Examples

Example 1: Write Character to Screen

10 REM Put 'H' at top-left corner
20 POKE 1024,8
30 POKE 55296,7

Screen code 8 = ‘H’. Colour 7 = yellow. Result: yellow ‘H’ at position (0,0).

Example 2: Clear Screen to Spaces

10 FOR I=1024 TO 2023
20 POKE I,32
30 NEXT I

Screen code 32 = space. Fills entire screen with spaces (much faster than PRINT).

Example 3: Write Text at Position

10 T$="HELLO"
20 R=10:C=15
30 P=1024+(R*40)+C
40 FOR I=1 TO LEN(T$)
50 A=ASC(MID$(T$,I,1))
60 IF A>=65 AND A<=90 THEN S=A-64 ELSE S=A
70 POKE P+I-1,S
80 POKE 55296+(R*40)+C+I-1,1
90 NEXT I

Displays “HELLO” in white at row 10, column 15. Converts PETSCII to screen codes on the fly.

Example 4: Fill Screen with Pattern

10 FOR I=1024 TO 2023
20 POKE I,160
30 POKE 55296+(I-1024),6
40 NEXT I

Screen code 160 = solid block. Colour 6 = blue. Result: solid blue screen.

Example 5: Inverse Video

10 REM Normal 'A' at position 0
20 POKE 1024,1
30 REM Inverse 'A' at position 1
40 POKE 1025,129

Screen codes 128-255 are the inverse (reverse video) versions of 0-127. Add 128 to any screen code for inverse display.

Character ROM Location

By default, the VIC-II reads character definitions from ROM at $D000-$DFFF. Each character is defined by 8 bytes (8×8 pixel matrix).

Character 1 (‘A’) definition in ROM:

$D008: 00011000  (top row - 2 pixels on)
$D009: 00111100  (3 pixels either side)
$D00A: 01100110  (wider)
$D00B: 01100110  (same)
$D00C: 01111110  (crossbar)
$D00D: 01100110  (legs)
$D00E: 01100110  (legs)
$D00F: 00000000  (bottom - blank)

You can point the VIC-II to custom character sets in RAM—that’s how games create custom graphics. See VIC-II register $D018 for details.

VIC-II Memory Pointers

The VIC-II’s view of memory is controlled by register $D018 (53272 decimal).

Default setup:

  • Screen RAM: $0400 (1024)
  • Character ROM: $D000

To change screen RAM location:

POKE 53272,PEEK(53272) AND 15 OR (bank*16)

Where bank is 0-15. Bank 1 = $0400 (default). Bank 2 = $0800. Bank 15 = $3C00.

Games often move screen RAM to different locations for double-buffering (draw to hidden screen, then flip).

Performance Notes

PRINT is slow because it:

  1. Converts PETSCII to screen codes
  2. Updates cursor position
  3. Handles scrolling
  4. Processes control codes

POKE is fast because it:

  1. Writes directly to memory
  2. No conversion overhead
  3. No cursor tracking
  4. Instant display update

For games, always POKE when drawing static screens, UI elements, or fast-updating content.

Limitations

  • Screen RAM is 1000 bytes, not 1024. Poking beyond 2023 wraps into other memory.
  • Colour RAM only uses low 4 bits (0-15). High bits ignored.
  • Screen RAM can be moved, but must align to 1KB boundaries ($0400, $0800, $0C00, etc.).
  • Character ROM can’t be written to (it’s ROM!). Copy to RAM for custom characters.

Advanced Techniques

Double Buffering

Draw to hidden screen ($0800), then switch display:

10 REM Draw to hidden screen at $0800
20 FOR I=2048 TO 3047:POKE I,160:NEXT
30 REM Flip display to $0800
40 POKE 53272,(PEEK(53272) AND 15) OR 32

Smooth Scrolling

Combine screen RAM updates with VIC-II hardware scrolling registers ($D011, $D016) for smooth pixel-perfect scrolling. Games like Scramble and Defender used this constantly.

Screen Splits

Use raster interrupts to change $D018 mid-frame, displaying different screen RAM on different parts of the screen. Status bars, split-screen games, and parallax effects all use this.

Historical Notes

The C64’s screen layout inherited from the PET (1977) and VIC-20 (1981), which both used 1KB screen RAM. The VIC-II’s colour RAM being separate from main memory was a cost-saving measure—1KB of fast static RAM was expensive in 1982, so Commodore used cheap 4-bit RAM chips for colour only.

Games like Impossible Mission and Beach Head pioneered double-buffering techniques, updating hidden screens then flipping the display for flicker-free animation. Demo coders pushed screen RAM manipulation to extremes, creating effects like raster bars, multiplexed sprites, and FLI (Flexible Line Interpretation) that displayed different character sets on every scanline.

See Also