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.
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:
- VIC-II reads byte from screen RAM (e.g., value 1 = letter ‘A’)
- VIC-II reads corresponding byte from colour RAM (e.g., value 7 = yellow)
- VIC-II looks up character 1 in character ROM (8 bytes define the ‘A’ shape)
- 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.
| Character | PETSCII | Screen Code |
|---|---|---|
| @ | 64 | 0 |
| A | 65 | 1 |
| B | 66 | 2 |
| Space | 32 | 32 |
| 0 | 48 | 48 |
Rule of thumb:
- PRINT uses PETSCII:
PRINT "A"uses code 65 - POKE uses screen codes:
POKE 1024,1displays ‘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:
- Converts PETSCII to screen codes
- Updates cursor position
- Handles scrolling
- Processes control codes
POKE is fast because it:
- Writes directly to memory
- No conversion overhead
- No cursor tracking
- 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
- PETSCII Chart — character code reference
- PRINT vs POKE — performance comparison
- VIC-II Chip — display hardware details
- Commodore 64 — system overview