Tsd1588

10 Insights from Building a Game Boy Emulator in F#

Published: 2026-04-30 19:51:41 | Category: Cybersecurity

When software engineer Nick Kossolapov set out to understand how computers truly work, he didn't just read a textbook. He built a Game Boy emulator from scratch using F#. The result, dubbed Fame Boy, runs on both desktop and web, complete with sound and a personal connection to his childhood. In this list, we break down the ten most insightful lessons from his journey—from hardware fundamentals to functional programming patterns.

1. Why the Game Boy Was the Perfect Learning Tool

The Game Boy isn't just a nostalgic artifact; it's an ideal candidate for emulation. Its hardware is real yet relatively simple, making it accessible for a first-time emulator builder. Nick spent hundreds of hours as a kid catching Pokémon, so the emotional connection kept him motivated through late nights. The device's limited scope—a sharp LR35902 CPU, 8KB of RAM, a simple PPU, and no operating system—means you can model the entire system without drowning in complexity. This makes it the perfect stepping stone for anyone who wants to move beyond theoretical computer science and into practical low-level understanding.

insights from building
Image via Flickr

2. Start with the Fundamentals: From NAND to Tetris

Before writing a single line of emulator code, Nick took the From NAND to Tetris course. This decision paid off immensely. The course walks you through building a general-purpose computer from basic logic gates, covering registers, memory, ALUs, and machine language. By the time he started the Game Boy project, he had a solid grasp of how each component interacts. Without this foundation, the CPU and memory map of the Game Boy would have been overwhelming. The lesson is clear: don't skip the fundamentals even if you're eager to code.

3. A Warm-Up Project: Building a CHIP-8 Emulator

Jumping straight into Game Boy emulation would be like trying to run before walking. Nick first built a CHIP-8 emulator in F# called Fip-8. CHIP-8 is an interpreted programming language from the 1970s, with a tiny instruction set and minimal hardware. This warm-up project taught him how to structure an emulator—fetch-decode-execute loops, handling timers, and displaying pixels. It also let him get comfortable with F#’s functional style in a constrained environment. After that, the Game Boy’s complexity felt manageable.

4. Separation of Concerns: Core vs. Frontend

One of Nick's key design decisions was to keep the emulator core completely decoupled from any frontend. The core exposes only two arrays and two functions. This allowed him to develop a desktop version using one UI framework and later add a web version without changing the emulation logic. The interface is essentially a memory buffer for video output and an input buffer for button presses. Any frontend—whether WPF, Avalonia, or Blazor—can plug in and just call the step function. This modularity is a best practice for any emulator project that targets multiple platforms.

5. Modelling the CPU with Functional Domain Design

The CPU in Fame Boy was written in the most “F#-ish” manner. Nick leaned heavily on functional domain modelling, using discriminated unions for opcodes and immutable state for registers. The Sharp LR35902 in the Game Boy knows nothing about the outside hardware except a memory map. By mirroring that, the CPU core remains pure and testable. Functions like stepCpu take the current state and the memory bus and return a new state plus the number of machine cycles consumed. This side-effect-free approach simplifies debugging and makes the codebase easier to reason about.

6. The Memory Map as the Central Bus

The Memory.fs module holds most of the Game Boy's RAM and acts as the bus between the CPU, IO Controller, and cartridge. It also shares references to the same VRAM and OAM arrays with the PPU for performance. In the real hardware, these components run in parallel; in software, sharing memory avoids copying and keeps synchronization simple. However, Nick found that as he added more hardware registers, the memory module became bloated. That led to the creation of a separate IO controller.

7. The IO Controller: A Synthetic But Useful Abstraction

The Game Boy hardware doesn't have a dedicated IO controller; all hardware registers live in a single address space. But Nick discovered that putting everything in Memory.fs made the code messy and unsafe. He created an IoController.fs that encapsulates all hardware registers and provides clean interfaces for timers, serial, audio, and the CPU. While not historically accurate, this abstraction improved safety and readability. It's a great example of when a practical design decision outweighs strict faithfulness to the original hardware.

insights from building
Image via Flickr

8. The Stepper Function: Gluing Everything Together

The heart of the emulator is the stepper function in Emulator.fs. It composes all individual component steps into a single cycle. The CPU executes one instruction, consuming a certain number of machine cycles. For each machine cycle, it steps the timers, serial, and APU. Then for each of the four times that number (t-cycles), it steps the PPU. The function returns the cycles taken so the frontend can throttle to real-time speed. This sequential composition mimics the parallel hardware by interleaving operations, and it's where the F#-style function composition shines.

9. Performance Considerations: Unrolling the APU

The APU (audio processing unit) technically runs at four times the CPU clock speed. In the emulator, Nick batches the APU steps by calling stepApu once per machine cycle instead of four times per t-cycle. This optimization reduces function call overhead while still producing correct audio output. He also notes that the PPU should ideally be stepped alongside the APU in the t-cycle loop, but for simplicity he kept them separate. Such pragmatic performance tweaks are common in emulation when exact timing isn't critical.

10. The Personal Journey: Late Nights and Lasting Rewards

Building Fame Boy took months of late nights, often going to bed at 2 AM after telling himself he'd only work an hour. But the reward was immense: a fully functional emulator that plays real Game Boy games, complete with sound, that runs in a web browser—all written in F#. The project gave Nick a visceral understanding of how computers work from the silicon up. For anyone considering a similar project, he recommends starting small, embracing functional programming, and picking something you love. The motivation from that personal connection makes the late nights worthwhile.

Conclusion

Building a Game Boy emulator isn't just a technical challenge—it's a journey into the heart of computing. Nick's story shows that with the right preparation (NAND to Tetris, a warm-up project) and a thoughtful design (separate core, functional CPU, synthetic IO controller), even a complex emulator can become manageable. Whether you're a software engineer looking to deepen your understanding or a hobbyist seeking a fun project, these ten insights provide a roadmap. And the best part? You get to play Pokémon when you're done.