KnowSys
◧Build it yourself

Build a Game Boy emulator

A CHIP-8 interpreter in a weekend, then a Game Boy emulator with a real CPU, memory map, cartridge banking, timers, graphics, input and sound that plays the cartridges you own.

Intermediate⏱ 6–10 weekendsC · C++ · Rust · Go

An emulator is a program that pretends to be a whole computer. It reads the same bytes the original CPU read, does what that CPU would have done with them, and draws the same pixels on the same frame. When it works, a game written in 1989 has no idea it's running on your laptop.

You'll get there in two steps. First CHIP-8, a tiny virtual machine from the 1970s with 35 instructions and a 64 by 32 screen, which you can finish in a weekend and which teaches the fetch, decode and execute loop with nothing in the way. Then the Game Boy: a real CPU with a few hundred opcodes, a memory map full of hardware registers, a graphics chip that draws line by line, timers, interrupts, four sound channels and cartridges that switch memory banks. By the end it plays real games at the right speed.

01Why build this

Most engineers never work this close to a CPU. An emulator puts you inside one, and some of that sticks:

  • You'll understand what an instruction actually is. Opcodes, flags, addressing modes and the stack stop being diagrams. Chapter 01 reads differently afterwards.
  • Memory-mapped I/O becomes obvious. On the Game Boy, writing to an address can change the screen, start a sound or swap which part of the cartridge is visible. That's how most hardware still works under the drivers.
  • Timing becomes a correctness problem. Games depend on exactly how many cycles things take. Getting that right is a small version of the problems in hypervisors and simulators.
  • You learn to debug against a reference. Test ROMs and known-good logs turn "the game looks wrong" into "instruction 1,204,337 set the wrong flag". That skill carries over to any system with a spec.

It's also a project with a very clear finish line: either the game runs or it doesn't.

02What you're building

Your emulator's Game Boy half runs this loop, over and over, millions of times a second:

One step of your Game Boy
●
⬇
Fetch
PC → bus
⌥
Decode
opcode table
▶
Execute
ALU + flags
◷
Timers
DIV / TIMA
▦
PPU
dots + lines
↯
Interrupts
IE & IF
Step 1. Read the byte at PC through the bus, which decides whether the address is cartridge ROM, video RAM, work RAM or a hardware register.
1 / 6

?Why advance everything after every instruction?

Because games are written against the hardware's timing, not just its behaviour. A game might wait for the screen to reach a particular line, change the scroll register in the gap between lines, then keep going. If your CPU runs a whole frame's worth of instructions and then your PPU catches up, that trick breaks. Stepping each component by the cycles the last instruction took is simple and accurate enough for almost every game.

03Before you start

You needWhyWhere to get it
A language that's comfortable with bitsEvery instruction is masks, shifts and flagsC, C++, Rust or Go
A pixel buffer, audio output and keyboard inputTo see, hear and playSDL2 or SDL3, raylib, or minifb plus an audio crate
A CHIP-8 referenceEvery opcode and its quirksCowgod's Chip-8 Technical Reference; Tobias Langhoff's guide
A Game Boy referenceMemory map, registers, PPU and APU behaviourPan Docs at gbdev.io, plus an opcode table
Test ROMsThey tell you what's wrong before a game doesTimendus' CHIP-8 test suite, Blargg's tests, mooneye-gb, dmg-acid2
Something to playIt's the real goalHomebrew from the gbdev community, or dumps of cartridges you own

04The roadmap

Eight milestones: two for CHIP-8, six for the Game Boy. Each one ends with a test you can run.

1

CHIP-8: fetch, decode, execute

1 evening

Model the machine: 4 KB of memory with programs loaded at 0x200, sixteen 8-bit registers V0 to VF, an index register I, a program counter and a small stack. Each instruction is two bytes, big-endian, and you decode it by splitting it into four-bit nibbles.

IBM's logo ROM uses only six instructions: clear screen, jump, set register, add, set I, and draw. Implement those first and draw to a 64 by 32 pixel buffer. Seeing the logo appear is a good feeling this early.

You’ll learnfetch-decode-executeopcode nibblesprogram counterframebuffers
Done when: The classic IBM logo ROM draws the IBM logo in your window.
2

CHIP-8: the whole machine

1 weekend

Implement the rest. Sprites are drawn by XOR, and VF is set if any pixel was turned off, which is how games detect collisions. Two timers count down at 60 Hz, regardless of how fast instructions run. A 16-key hex keypad maps onto four rows of your keyboard.

Then meet quirks. Different CHIP-8 interpreters disagreed about a few instructions, such as whether shifts use VX or VY. Games written for one break on another. Make those behaviours switchable, and use the quirks test to check them.

You’ll learnXOR spritescollision flag60 Hz timerskeypadquirks
Done when: Timendus' opcode, flags and quirks tests all pass, and you can play Pong with the keyboard.
3

Game Boy: the CPU

2 weekends

Inside the Game Boy is a Sharp SM83 CPU, close to an Intel 8080 with some Z80 features. It has eight 8-bit registers that pair into 16-bit ones, SP, PC and four flags. There are about 500 opcodes counting the 0xCB table, but they fall into regular patterns, so decode by bit fields, not one case per opcode.

Run Blargg's CPU tests. They print results through the serial port at 0xFF01 and 0xFF02, so you can read them before you have graphics. When one fails, log every instruction's registers and diff them against a reference log, such as the ones Gameboy Doctor provides.

You’ll learnSM83 registersflags and half-carryCB-prefixed opcodescycle counts
Done when: Each of Blargg's individual cpu_instrs ROMs prints 'Passed' to your serial output, and your trace matches a known-good log line for line.
4

The memory map and cartridges

1 weekend

Fill in the bus. 0x0000 to 0x7FFF is cartridge ROM, 0x8000 to 0x9FFF video RAM, 0xC000 to 0xDFFF work RAM, and 0xFF00 upwards the hardware registers and a small fast RAM. Pan Docs has the full table.

Cartridges bigger than 32 KB use a memory bank controller. Writes to the ROM range don't write anything; they tell the MBC which bank to map into 0x4000. Start with MBC1. Skip the boot ROM by setting the registers to their documented post-boot values and starting at 0x0100.

You’ll learnaddress decodingMBC1 bank switchingecho RAMpost-boot state
Done when: The combined cpu_instrs ROM, which needs MBC1 banking, runs all eleven tests and ends with 'Passed all tests'.
5

Timers and interrupts

1 weekend

DIV counts up at a fixed rate, and TIMA counts at one of four rates chosen by TAC, requesting an interrupt when it overflows. Drive both from the cycle counts your CPU already returns.

Interrupts need care. A request sets a bit in IF, IE says which are allowed, and the master switch IME gates them all. EI takes effect one instruction late, and HALT wakes up when an allowed interrupt is pending even if IME is off. Get either wrong and games freeze at their first loading screen.

You’ll learnDIV and TIMAIE and IFIME and EI delayHALT
Done when: Blargg's instr_timing test passes, and a test ROM that waits on a timer interrupt wakes from HALT at the right time.
6

The PPU: tiles, backgrounds and sprites

2 weekends

Its screen is 160 by 144 pixels, and the PPU draws it one scanline at a time: each line takes 456 dots through OAM scan, drawing and horizontal blank, and after 144 lines come 10 lines of vertical blank. Implement those modes, the LY register games poll, and the VBlank and STAT interrupts.

Then draw. Backgrounds come from a 32 by 32 map of 8 by 8 tiles, scrolled by SCX and SCY; the window is a second layer on top; and up to 40 sprites come from OAM, at most ten per line. A scanline renderer is enough for nearly every game. dmg-acid2 checks all of it in one image.

You’ll learnscanlines and modestile data and tile mapsscrolling and the windowOAM spritespalettes
Done when: dmg-acid2 renders identically to its reference image.
7

Input, pacing and a real game

1 weekend

At 0xFF00, the joypad register is a small matrix: the game selects either the direction keys or the buttons, then reads four bits back. Map them to your keyboard, and raise the joypad interrupt on a press.

Pace the emulator to the real machine: run 70,224 cycles, present the frame, then wait, which works out to roughly 59.7 frames a second. Write cartridge RAM to disk so saves persist. If you're below full speed, profile before you optimise; it's usually the bus or the PPU. Chapter 41 covers how.

You’ll learnjoypad matrixframe pacingsave RAMprofiling the hot loop
Done when: A game you own plays start to finish at the original speed, and its save survives quitting and relaunching the emulator.
8

Sound

1–2 weekends

Sound comes from four APU channels: two square waves (the first with a frequency sweep), a wave channel that plays 32 samples from RAM, and a noise channel. A frame sequencer clocked from DIV steps their length counters, volume envelopes and sweep.

Generate samples at the Game Boy's rate, mix the channels, and resample to your sound card's 44.1 or 48 kHz. Then sync: if you pace by video alone, audio will slowly underrun or pile up. Many emulators pace by how full the audio buffer is instead.

You’ll learnAPU channelsframe sequencerenvelopes and sweepresamplingaudio-video sync
Done when: A game's music plays at the right pitch for several minutes with no crackles and no drift away from the picture.

05Traps that catch everyone

SymptomCauseFix
A CHIP-8 game glitches that works elsewhereIt expects different quirks for shifts, FX55 or jumpsMake quirks configurable per game
CHIP-8 timers run far too fastTimers tied to instruction countTick timers at 60 Hz and run a configurable number of instructions per frame
Blargg fails on DAA or ADD SPHalf-carry and carry flags computed wrongH is the carry out of bit 3; for ADD SP,e8 the flags come from the low byte
A game hangs on a black screenIt's polling LY or waiting for VBlank, and your PPU isn't advancingStep the PPU by every instruction's cycles and raise the VBlank interrupt
Tiles are garbage, but some look rightLCDC bit 4 selects signed or unsigned tile addressingImplement both addressing modes
Games crash after the title screenWrites to ROM treated as writes to memoryRoute writes below 0x8000 to the MBC
Audio crackles or slowly driftsAudio and video pacing disagreePace by audio buffer fill, or resample dynamically

06Stretch goals

  • Game Boy Color. Colour palettes, banked video RAM and a double-speed CPU mode, on top of what you already have.
  • A pixel FIFO PPU. Replace the scanline renderer with the real fetcher and FIFO, and pass the stricter mooneye-gb timing tests.
  • A debugger. A disassembler, breakpoints, memory viewer and tile viewer. You'll probably wish you'd built it at milestone 3.
  • Save states and rewind. Serialise the whole machine every frame and step backwards.
  • Run it in a browser. Compile to WebAssembly and draw to a canvas.
  • The NES next. A 6502, a stranger PPU and many more mappers.

07References worth your time

Cowgod's Chip-8 Technical Reference

The short, classic description of every CHIP-8 instruction, register and the font. Enough for milestones 1 and 2.

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

Explains the machine without giving you code, and is careful about the quirks that differ between interpreters.

Pan Docs

The community's Game Boy reference, hosted by gbdev.io. Memory map, every register, the PPU, the APU and the cartridge controllers.

Michael Steil, The Ultimate Game Boy Talk

A one-hour 33C3 talk covering the whole machine, from the CPU to the pixel FIFO. Watch it before milestone 3.

Blargg's test ROMs and the mooneye-gb test suite

The standard tests for CPU instructions, timing and hardware edge cases. mooneye-gb's are by Gekkio and are especially strict about timing.

Gekkio, Game Boy: Complete Technical Reference

A precise, cycle-level document on the CPU and its instruction timings. Use it when Pan Docs isn't detailed enough.

dmg-acid2

Matt Currie's single test ROM for the PPU. One image that's right or wrong in specific, documented ways.

Chapters that back this project

Next project⎇ Git, from scratch→