The whole CPU core is deterministic, so every lesson is one concrete spec with exact register, memory, and framebuffer values: 7XNN adding past 0xFF wraps to 0x00 without touching VF, 8XY4 setting the carry flag after the store even when X is VF, 8XY5 setting VF to 1 when there is no borrow, 8XY6 shifting a bit out into VF, DXYN drawing a sprite by XOR and flagging a collision, and FX33 splitting 254 into the digits 2, 5, 4. Each opcode is a small checkable target you can assert without ever running a graphical program.
Over 34 lessons you build a working CHIP-8 emulator from scratch: the interpreter for a tiny 1970s virtual machine whose programs are streams of two-byte opcodes. Because the whole machine is deterministic - 4KB of memory, sixteen 8-bit registers, a 64x32 monochrome screen - every step is exactly testable, so you assert real register and framebuffer values at each lesson rather than eyeballing a running game.
You start by modelling the machine: its memory, the V0-VF registers, a framebuffer, and the built-in hex font. Then you build the fetch-decode-execute cycle that reads a two-byte opcode, advances the program counter, and decodes it by nibbles. On top of that you implement the full opcode set one instruction per lesson - jumps, subroutine calls through the stack, conditional skips, the 8XY_ ALU with its exact carry, borrow, and shift semantics, the DXYN XOR sprite draw with its collision flag, the hex keypad, the delay and sound timers, and the FX memory operations - pinning every quirk (which flag is written, whether the index register moves) to a concrete value. The capstone loads a small embedded ROM and runs it to completion, asserting the exact pixels it draws.
This is a teaching-grade emulator built around the standard CHIP-8 instruction set: it runs classic asset-free ROMs (the IBM-logo program and similar display-driven ROMs) and renders the 64x32 display to your terminal. It is honest about its limits - it implements original CHIP-8, not the SUPER-CHIP or XO-CHIP extensions, models sound as a counting-down timer rather than audio output, and does not aim for cycle-exact hardware timing, and the bundled terminal front end is display-only (no real keyboard is wired in, so input-driven games are watched rather than played) - which is exactly the honest core that full emulators extend with higher resolutions, real input, audio, and precise timing.
A CHIP-8 machine is mostly one thing - a small block of memory that holds both the program and its data. Today you build that memory and give it a way to read and write a single byte, the foundation every later lesson carves into.
Create a virtual machine with 4096 bytes of memory and read and write one byte of it.
Every CHIP-8 program, and everything it works on, lives inside a single 4096-byte memory (0x000 to 0xFFF). There is no separate disk, no heap, no registers-versus-RAM distinction to worry about yet: just one flat array of bytes that the machine reads instructions from and scribbles data into. Building it first means every later piece - the program counter, sprites, the stack - has somewhere to live.
Keep the interface tiny: write a byte at an address, read a byte back. Addresses are 12-bit values (0 through 4095), so a 16-bit unsigned integer holds one comfortably. Starting fully zeroed matters - a fresh machine is blank, and later lessons will rely on unwritten memory reading as 0x00.
type VM struct {mem [4096]byte // all of CHIP-8's memory, addresses 0x000..0xFFF}func NewVM() *VM { return &VM{} }// named SetByte/GetByte (not Write/ReadByte) to avoid clashing with// the io.ByteWriter/io.ByteReader signatures go vet checks forfunc (v *VM) SetByte(addr uint16, b byte) { v.mem[addr] = b }func (v *VM) GetByte(addr uint16) byte { return v.mem[addr] }
The interpreter implements the full standard CHIP-8 instruction set correctly with every quirk pinned, but the bundled terminal front end is display-only: no real keyboard input, no audio, and no cycle-accurate timing, so many real games can be watched but not played as shipped.
The canonical opcode-by-opcode reference: memory map, register set, and the precise behaviour of every instruction. Keep it open the whole project - it is where the exact values in these specs come from.
A modern, beginner-friendly walkthrough that explains the fetch-decode-execute loop and, crucially, the ambiguous quirks (shift, jump-with-offset, the FX55/FX65 index increment) and how to pick one - the same quirks this project pins.
A concise opcode reference and instruction-set overview - a second, independent description of each instruction to cross-check against Cowgod when a behaviour looks ambiguous.
A collection of test ROMs that exercise the opcodes, flags, and quirks one by one - the correctness harness to run once your emulator boots, especially the flags and quirks tests.
A curated index of CHIP-8 references, ROM archives, and other emulator writeups - where to find asset-free public-domain ROMs (IBM logo, Pong, Tetris) to feed your finished interpreter.