A ground-up emulator for the Nintendo Game Boy (DMG-01) written in Rust. The project focuses on accuracy, readability, and clean architecture rather than raw performance.
🎯 Core Goals
- Accurate hardware emulation: Detailed representation of the Game Boy CPU (LR35902)
- Educational: Each component is documented and easy to follow
- Transparent: No “magic” — code is testable and explicit
- Modular: Clear separation of CPU, memory, PPU and timer
🏗️ Architecture
The emulator design follows the real hardware structure:
┌──────────────────────────────────────┐
│ Main Emulator Loop │
│ • Input Processing (Joypad) │
│ • CPU Step │
│ • PPU Step │
│ • Frame Rendering │
└──────────────────────────────────────┘
↓ ↓ ↓
┌─────────┬──────────┬──────────┐
│ CPU │ MMU │ PPU │
│ LR35902 │ Memory │ Graphics │
└─────────┴──────────┴──────────┘
↓ ↓ ↓
[Register] [RAM] [Display]
CPU (LR35902)
pub struct Cpu {
pub program_counter: u16,
pub stack_pointer: u16,
pub registers: Registers, // A, F, B, C, D, E, H, L
pub ime: bool, // Interrupt Master Enable
pub halted: bool, // HALT-Status
}
The CPU implements the complete Game Boy (LR35902) instruction set:
- 256 base opcodes
- 256 CB-prefixed opcodes (bit operations)
- ALU operations: ADD, ADC, SUB, SBC, AND, OR, XOR, CP
- Flag handling (Z, N, H, C)
- Interrupts & HALT states
Note: Opcode decoding is implemented with explicit match arms for clarity and better compiler optimization.
Memory Management Unit (MMU)
pub struct Mmu {
rom: Vec<u8>, // 0x0000–0x7FFF (ROM)
wram: [u8; 0x2000], // 0xC000–0xDFFF (Working RAM)
hram: [u8; 0x80], // 0xFF80–0xFFFE (High RAM)
io_registers: [u8; 0x80], // Hardware-Register
}
The MMU is the single access point for CPU memory access. It provides:
- Correct memory mapping
- Lazy loading of ROM data
- Hardware register emulation
- Interrupt flag management
PPU (Graphics Processor)
pub enum PpuMode {
OamScan, // 80 Zyklen – OAM-Scan
Drawing, // 172 Zyklen – draw Scanline
HBlank, // 204 Zyklen – Horizontal Blanking
VBlank, // Vertical Blanking (10 Scanlines)
}
The PPU follows the exact timing specification of the Game Boy:
- Mode transitions driven by CPU cycles
- Scanline rendering with background and sprite layers
- Framebuffer output (160×144 pixels)
- VBlank interrupt for frame updates
💻 Technical highlights
1. Complete opcode decoding
// Example: 8-Bit Addition with Carry Flag
0x89 => { // ADD A, C
let val = self.registers.read8(&Reg8::C);
self.alu_add_u8(val);
4 // Cycles
}
Over 500 opcodes are explicitly defined with correct cycle timings.
2. Precise flag handling
All CPU flags are updated correctly after every operation:
- Z flag: set when the result is zero
- N flag: set for subtraction operations
- H flag: set on half-carry (bit 3→4)
- C flag: set on carry/borrow
fn alu_add_u8(&mut self, value: u8) {
let result = self.registers.a.wrapping_add(value);
self.registers.flags.z = result == 0;
self.registers.flags.n = false;
self.registers.flags.h = (self.registers.a & 0xF) + (value & 0xF) > 0xF;
self.registers.flags.c = (self.registers.a as u16) + (value as u16) > 0xFF;
self.registers.a = result;
}
3. Hardware-accurate timing
Each opcode has its correct cycle timing:
- NOP: 4 cycles
- Memory accesses: 8 or 16 cycles
- Conditional jumps: 12/8 cycles (depending on condition)
This enables correct synchronization with the PPU and timer.
4. Modular input handling
pub enum Key {
Right, Left, Up, Down,
Start, Select, B, A,
}
// Real-time Input Processing
match key {
Key::A => self.mmu.key_down(key),
Key::B => self.mmu.key_down(key),
_ => {}
}
The joypad is integrated into the MMU and triggers interrupts on key presses.
🎮 Supported ROMs
✅ Pokemon Red
✅ Tetris
✅ Mooneye Test Suite (used for correctness validation)
📊 Code structure
src/
├── main.rs # event loop & window management
├── gameboy.rs # main emulator struct
├── rom.rs # ROM loading & parsing
└── gameboy/
├── cpu.rs # CPU logic
├── mmu.rs # Memory Management Unit
├── ppu.rs # Graphics Processor
├── timer.rs # Timer emulation
├── joypad.rs # Input handling
└── screen/
├── window.rs # GUI (minifb)
└── framebuffer.rs # pixel buffer
🚀 Performance & optimisations
- Rust Memory Safety: No segmentation faults thanks to borrow checker
- Zero-Cost Abstractions: High performance despite clear structure
- Inlining: Frequently called functions are optimised inline
- Direct Memory Access: Fast RAM access without indirection
Measurements:
- ✅ Real-time execution for most ROMs
- ✅ CPU runs stably at 4.194 MHz
📚 References & Data Sources
This project is based on the most authoritative Game Boy documentation:
- Pandocs – Game Boy Technical Reference
- Game Boy Opcode Tables
- RGBDS CPU Manual
🔧 Build & Run
# execute ROM file
cargo run --release -- --rom_path roms/games/Pokemon_Red.gb
# with debugging
cargo run -- --rom_path roms/games/Tetris.gb
Dependencies:
minifb: Cross-platform window handling & rendering
📈 Concepts learned
✅ Low-level emulation: hardware timing, instruction decoding
✅ Rust systems programming: memory safety in critical systems
✅ CPU architecture: registers, flags, interrupts, stack
✅ Graphics pipelines: scanline rendering, mode timing
✅ Timing-critical systems: cycle-accurate emulation
🔮 Future enhancements
- Sound (APU) – Audio chip emulation
- MBC1/MBC3/MBC5 – Cartridge support
- Memory persistence (save states)
- Debugger with breakpoints
- Performance profiling
💡 Why this project?
Gameboy emulation is a classic in emulator development – it offers the perfect balance between:
- Complexity: Real CPU, memory management, timing
- Learning potential: Hardware details are accessible and well documented
- Sense of achievement: Functional games run
- Rust application: Perfect use case for systems programming
This project demonstrates both technical understanding and clean software architecture in a language that combines security and performance.