Projects/Build a CHIP-8 Emulator

Build a CHIP-8 Emulator

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.

34 lessonsSmall~20 min / lessonBytecode interpretersFetch-decode-executeSprite rendering
The project

What you'll build over the next 34 lessons

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.

build-a-chip-8-emulator / lesson-01.md
Lesson 01 · The CHIP-8 machine

The 4KB memory

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.

The goal

Create a virtual machine with 4096 bytes of memory and read and write one byte of it.

Start here - the target
TO DO
Scenario: Memory stores and returns a byte
Givena new VM created with NewVM()
Whenthe byte 0xAB is written to address 0x300 with SetByte and then read back with GetByte
Thenthe read returns 0xAB
Andan untouched address such as 0x301 still reads 0x00, and the total memory size is 4096 bytes
Background

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.

Make it work
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 for
func (v *VM) SetByte(addr uint16, b byte) { v.mem[addr] = b }
func (v *VM) GetByte(addr uint16) byte { return v.mem[addr] }
CheckpointDONE
You have a VM with 4KB of zeroed memory that stores and returns bytes. Commit and stop here.
Scope & extensions

Where this project stops - and where to go next

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.

Extend it next
  • Wire up real keyboard input (a terminal raw-mode reader mapped to the 16-key hex keypad) so input-driven games become playable
  • Add real 60Hz frame pacing with live terminal redraw instead of printing one final static frame per run
  • Add audio output (even a simple beep) driven by the sound timer counting down
  • Support loading ROMs from stdin or a URL and embed a few more public-domain test ROMs to run out of the box
  • Run the Timendus CHIP-8 test-suite ROMs and add a quirks-configuration flag so the shift, jump-offset, and FX55/FX65 behaviours can be toggled per ROM
  • Extend to SUPER-CHIP (128x64 hi-res, scrolling, the extra opcodes) once standard CHIP-8 is solid
Recommended reading

Books & references that go deeper

  • 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.

  • Guide to making a CHIP-8 emulator · Tobias V. Langhoff

    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.

  • Mastering CHIP-8 · Matthew Mikolay

    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.

  • CHIP-8 test suite · Timendus

    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.