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:
PC through the bus, which decides whether the address is cartridge ROM, video RAM, work RAM or a hardware register.?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 need | Why | Where to get it |
|---|---|---|
| A language that's comfortable with bits | Every instruction is masks, shifts and flags | C, C++, Rust or Go |
| A pixel buffer, audio output and keyboard input | To see, hear and play | SDL2 or SDL3, raylib, or minifb plus an audio crate |
| A CHIP-8 reference | Every opcode and its quirks | Cowgod's Chip-8 Technical Reference; Tobias Langhoff's guide |
| A Game Boy reference | Memory map, registers, PPU and APU behaviour | Pan Docs at gbdev.io, plus an opcode table |
| Test ROMs | They tell you what's wrong before a game does | Timendus' CHIP-8 test suite, Blargg's tests, mooneye-gb, dmg-acid2 |
| Something to play | It's the real goal | Homebrew 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.
CHIP-8: fetch, decode, execute
1 eveningModel 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.
CHIP-8: the whole machine
1 weekendImplement 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.
Game Boy: the CPU
2 weekendsInside 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.
The memory map and cartridges
1 weekendFill 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.
Timers and interrupts
1 weekendDIV 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.
The PPU: tiles, backgrounds and sprites
2 weekendsIts 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.
Input, pacing and a real game
1 weekendAt 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.
Sound
1–2 weekendsSound 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.
05Traps that catch everyone
| Symptom | Cause | Fix |
|---|---|---|
| A CHIP-8 game glitches that works elsewhere | It expects different quirks for shifts, FX55 or jumps | Make quirks configurable per game |
| CHIP-8 timers run far too fast | Timers tied to instruction count | Tick timers at 60 Hz and run a configurable number of instructions per frame |
Blargg fails on DAA or ADD SP | Half-carry and carry flags computed wrong | H is the carry out of bit 3; for ADD SP,e8 the flags come from the low byte |
| A game hangs on a black screen | It's polling LY or waiting for VBlank, and your PPU isn't advancing | Step the PPU by every instruction's cycles and raise the VBlank interrupt |
| Tiles are garbage, but some look right | LCDC bit 4 selects signed or unsigned tile addressing | Implement both addressing modes |
| Games crash after the title screen | Writes to ROM treated as writes to memory | Route writes below 0x8000 to the MBC |
| Audio crackles or slowly drifts | Audio and video pacing disagree | Pace 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
The short, classic description of every CHIP-8 instruction, register and the font. Enough for milestones 1 and 2.
Explains the machine without giving you code, and is careful about the quirks that differ between interpreters.
The community's Game Boy reference, hosted by gbdev.io. Memory map, every register, the PPU, the APU and the cartridge controllers.
A one-hour 33C3 talk covering the whole machine, from the CPU to the pixel FIFO. Watch it before milestone 3.
The standard tests for CPU instructions, timing and hardware edge cases. mooneye-gb's are by Gekkio and are especially strict about timing.
A precise, cycle-level document on the CPU and its instruction timings. Use it when Pan Docs isn't detailed enough.
Matt Currie's single test ROM for the PPU. One image that's right or wrong in specific, documented ways.