Memory Layout

CHIP-8 has 4 kilobytes of memory — 4096 bytes, addressable from 0x000 to 0xFFF. That is the entire address space.

The map

0x000 ┌─────────────────────┐
      │  Reserved           │  interpreter, font sprites (0x000–0x04F)
0x1FF ├─────────────────────┤
0x200 │  Program (ROM)      │  ← all programs load here
      │                     │
0xFFF └─────────────────────┘

The first 512 bytes (0x000–0x1FF) were reserved for the original CHIP-8 interpreter that ran on the host machine. ROMs always start at 0x200 — this is a hard convention that every CHIP-8 program follows without exception. PC is initialized to 0x200 for this reason.

The range 0x000–0x04F is used by us for the built-in font sprites (16 digits × 5 bytes). See Font Sprites.

The Memory struct

#![allow(unused)]
fn main() {
pub const MEMORY_SIZE: usize = 4096;
pub const PROGRAM_START: u16 = 0x200;

pub struct Memory {
    ram: [u8; MEMORY_SIZE],  // private — access only through read/write
}
}

ram is private. Nothing outside memory.rs can touch it directly. All reads and writes go through explicit methods:

#![allow(unused)]
fn main() {
impl Memory {
    pub fn read(&self, address: u16) -> u8 {
        self.ram[address as usize]
    }

    pub fn write(&mut self, address: u16, value: u8) {
        self.ram[address as usize] = value;
    }
}
}

This encapsulation means we can add bounds checking or memory-mapped I/O later without changing any other code.

Loading a ROM

#![allow(unused)]
fn main() {
pub fn load_rom(&mut self, data: &[u8]) {
    let start = PROGRAM_START as usize;
    self.ram[start..start + data.len()].copy_from_slice(data);
}
}

data is a byte slice — a reference to a sequence of bytes of any length. The ROM is placed starting at 0x200. copy_from_slice copies every byte from data into the correct position in RAM.

Memory is owned by the CPU

Memory is a field of Cpu, not a global. This mirrors how the hardware works — the CPU has direct access to its memory. The CPU accesses memory through self.memory.read(addr) and self.memory.write(addr, value).


Rust concepts used here: The &[u8] parameter type for load_rom is explained in Slices and Ranges. The private ram field and pub methods are covered in the same chapter. File and module organization is in Modules.