Font Sprites
CHIP-8 defines 16 built-in sprites for the hexadecimal digits 0–F. Every interpreter must provide them — programs rely on these sprites to draw numbers on screen without embedding their own.
Sprite format
Each digit is 5 bytes tall and 4 pixels wide. The pixels occupy the upper 4 bits of each byte (the lower 4 are always zero):
digit 0: digit A:
0xF0 ████░░░░ 0xF0 ████░░░░
0x90 █░░█░░░░ 0x90 █░░█░░░░
0x90 █░░█░░░░ 0xF0 ████░░░░
0x90 █░░█░░░░ 0x90 █░░█░░░░
0xF0 ████░░░░ 0x90 █░░█░░░░
Memory layout
Fonts are loaded into 0x000 at startup — before any ROM. The address of digit N is simply N × 5:
0x000 digit 0 (5 bytes)
0x005 digit 1
0x00A digit 2
...
0x04B digit F
Loading in Memory::new()
#![allow(unused)] fn main() { const FONTS: [u8; 80] = [ 0xF0, 0x90, 0x90, 0x90, 0xF0, // 0 0x20, 0x60, 0x20, 0x20, 0x70, // 1 0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2 // ... 13 more digits ]; mem.ram[FONT_START..FONT_START + FONTS.len()].copy_from_slice(&FONTS); }
Every Memory instance has fonts loaded from the very first instruction — they are part of initialization, not an optional setup step.
Opcode FX29 — LD F, Vx
Sets I to the address of the font sprite for digit Vx:
#![allow(unused)] fn main() { 0x29 => { self.i = (self.v[x] as usize * FONT_SPRITE_SIZE) as u16; } }
After this, DRW with N=5 draws that digit on screen.
Typical usage in a program
LD V0, 0xA ; digit to draw = A (10)
LD F, V0 ; I = address of 'A' sprite = 0x032
LD V1, 10 ; x position
LD V2, 5 ; y position
DRW V1, V2, 5 ; draw 5-row sprite at (10, 5)
The test program in main.rs uses exactly this pattern to draw all 16 digits across the screen.
Rust concepts used here:
copy_from_slicewith a range is covered in Slices and Ranges. The constant arrayFONTSuses the array syntax from Primitive Types and Arrays. See DRW — Drawing Sprites for how sprites are read from memory and drawn usingI.