Registers and the CPU

A register is a tiny storage location inside the CPU — faster than memory, but very limited in number. Before writing a single line of code, we need to know exactly what hardware we are modeling.

General-purpose registers: V0–VF

#![allow(unused)]
fn main() {
pub v: [u8; 16]
}

Sixteen registers, each holding one byte (0–255). Programs use them to store temporary values: the current score, a position, a loop counter. In code, v[0] is V0, v[15] is VF.

VF is special. Several opcodes write to it automatically as a side effect:

OpcodeWhat VF receives
ADD Vx, Vy1 if result overflowed (carry), 0 otherwise
SUB Vx, Vy1 if Vx ≥ Vy (no borrow), 0 otherwise
SHR Vxthe bit shifted out
DRW Vx, Vy, n1 if any pixel was erased (collision), 0 otherwise

Programs should not store data in VF — any arithmetic or draw instruction will overwrite it.

Index register: I

#![allow(unused)]
fn main() {
pub i: u16
}

A 16-bit pointer to a memory address. Before drawing a sprite, you point I at the sprite data. Before reading or writing memory, you point I at the location. It is the CPU's one shared memory pointer.

It is 16-bit because addresses go up to 0xFFF (4095), which does not fit in 8 bits.

Program counter: PC

#![allow(unused)]
fn main() {
pub pc: u16
}

Holds the address of the next instruction to execute. Each cycle:

  1. Read the 2-byte opcode at memory[pc] and memory[pc + 1]
  2. Advance pc by 2
  3. Decode and execute

PC starts at 0x200. The first 512 bytes of memory were reserved for the original firmware, so all CHIP-8 programs begin there.

Stack and stack pointer

#![allow(unused)]
fn main() {
pub stack: [u16; 16]
pub sp: u8
}

The stack stores return addresses for subroutine calls. When a CALL instruction runs, the current PC is pushed onto the stack so RET can come back to it. CHIP-8 supports up to 16 levels of nesting.

CALL addr → stack[sp] = pc;  sp++;  pc = addr
RET       → sp--;  pc = stack[sp]

Summary

RegisterSizeRole
V0–VE8-bitGeneral-purpose data
VF8-bitStatus flag (carry, borrow, collision)
I16-bitMemory address pointer
PC16-bitNext instruction address
SP8-bitStack depth counter
stack16 × 16-bitReturn address storage

Rust concepts used here: The CPU is modeled as a struct — see Structs and impl Blocks. The choice of u8 and u16 for each field is explained in Primitive Types and Arrays. The new() function that initializes PC to 0x200 is covered in Constructors and Initialization.