I’m a professional computer programmer, but I don’t often do all that much computer programming. Instead I spend much of my time futzing around with config files, other people’s frameworks, and other people. This makes it easy to forget how much fun it is to write a long program that truly interests you and watch it do something a little bit astonishing.
I write a lot of words in my spare time, but rarely any code. However, a year or so ago my wife and toddler went to America to visit her family while I stayed at home for a staycation. I’d been enjoying work and was feeling professionally inspired, so I wanted to spend some of my holiday building something.
But choosing a personal project is hard. This was going to be the freest time I’d had since my son had been born two years earlier. If I was going to spend it programming instead of being outside or playing Skyrim then I wanted to be sure that I was programming something very, very compelling. I wanted a project that protruded the right distance outside of my comfort zone. I wanted to go a bit closer to the machine than I normally get. I wanted concrete outputs that would impress both me and my friends. I wanted to know what I’d learn, how it was relevant to my other interests, and where it could lead in the future.
I decided to write a Game Boy emulator: a program that would run old Game Boy games on my computer. This seemed hard but achievable, and I thought it would dazzle my friends for at least a few seconds. The internals of the Game Boy seemed well-documented enough to be accessible, but poorly-documented enough to feel like I’d be the one doing the work. I’d never made anything like it before, and I loved the idea of running Pokemon Blue and catch a Pidgey using a program I’d written myself. I called the project Gamebert because it was a Game Boy and my name is Robert.
I waved goodbye to my family and went home to get started.
I chose to write Gamebert in Go. I guessed that an emulator would be a resource intensive program and would benefit from a fast language, especially if, as seemed likely for a first attempt, it wasn’t written very well. I anticipated making lots of mistakes, and wanted a typechecker that would catch some of them quickly. Since I’d be simulating registers (small slots in a CPU for storing temporary data) and bytes of RAM that held a specific number of bits, I wanted to be able to specify the size of my integers and have the typechecker hold me to my choice. Representing an 8-bit register using an unsigned 8-bit integer (
uint8) would handle overflows and underflows for me (i.e. making sure that
255+1 looped back round to
0), and prevent me from accidentally assigning the register an oversized number that a real 8-bit register couldn’t actually hold.
I watched some talks on Gameboy architecture. The talks were clear and entertaining, but I didn’t absorb enough information to actually start anything. I continued wandering the internet in search of the right type of handrail.
Lots of Reddit threads mentioned the CHIP-8, a machine similar in structure to a Game Boy but much simpler. The CHIP-8 isn’t a piece of actual physical hardware; it’s a specification for a theoretical virtual machine. Writing a CHIP-8 emulator is like writing a Game Boy emulator if Nintendo had never actually manufactured any consoles. Reddit suggested that novices cut their teeth on a CHIP-8 emulator before grinding them on a Game Boy, so I did. Writing my CHIP-8 didn’t take too long (thanks in large part to this guide), and it got me used to emulator concepts like flags (slots in a CPU that store boolean values about the last operation, such as whether the result was zero) and opcodes (numerical codes representing the different operations that a CPU can perform). I was ready to apply what I’d learned from my CHIP-8 to my Game Boy.
A Game Boy has a few major components, including a CPU, an LCD, a few memory banks, a game cartridge, and a motherboard. The CPU performs the machine’s calculations, using tiny instructions like “load the byte from RAM location 1234 into register D” and “add 1 to register B”. It executes about a million of these instructions every second, which miraculously add up to a game. The LCD works out which pixels to display on the screen, which in a Game Boy is surprisingly complicated.
A game cartridge contains a game’s code, written by its programmers using the Game Boy’s assembly language. Some types of cartridge offer other functionality, such as allowing the Game Boy to write data back to the cartridge to save the player’s progress. Memory banks store data about the game’s intermediate state as it executes, such as “how much life does this Charmander have left?” and “which room is the player in?” All of these components plug into a motherboard, which they use to communicate with each other. For example, the CPU can write data to memory banks, and the LCD can read this data in order to work out what to display to the user.
After a few days of reading and partially-understanding the documentation, I got Gamebert to load a game and scroll the Nintendo logo down the opening screen for the first time. This was a magical moment. I’d never built anything that could produce emergent output so far removed from the code I had written. Nowhere in my code did it say anything like “print an N, then an I, then…” I’d produced this animation by passing a bizarre series of bytes (a ROM file ripped from a Pokemon Blue game cartridge) through a program that I’s written entirely myself.
This motivating milestone came relatively early in the project, after only a few days (although your own definition of early will depend on how much patience and free time you have). The day after I produced my first Nintendo logo I woke up at 5:30am and worked straight through until 8pm, which I’ve only ever done once before.
But after the logo dropped, the screen went blank. No Pokemon appeared, and I didn’t understand my system well enough to be able to work out why. I’ve never written assembly code, and so I had no intuition for which behaviours looked right or wrong. I didn’t know which errors were there because I hadn’t finished, and which were because I’d made a mistake. The only way I could think of to test my emulator was to write the whole thing and hope it produced a Squirtle. I wasn’t sure if I had bugs in my CPU or my LCD, although with hindsight the answer was of course “both”.
At the end of my week alone I was still stuck and nowhere near finished. My emulator continued to loop forever doing nothing and I had no idea why. I’d found test ROMs (like Blargg’s ROMs) that helped emulator developers like me validate our in-progress creations, but my program couldn’t execute them accurately enough to even produce an error message. My family and job came back. I still had an hour or two of discretionary brain time most days, but I couldn’t make much progress without stretches of at least half a day. Any understanding I had of how a Game Boy works fell out of my head.
Fortunately, a year later I had another child. Before he was born I took a month of vacation that I hadn’t used during COVID. I spent this time playing with the toddler I already had, working on a new project, and picking up Gamebert again. I used my fresh eyes to fix some now-obvious bugs, although none of them made my emulator stop looping and start working. My new son was born, and after a few weeks we were settled enough that I could work on Gamebert while he slept, strapped to my chest. I was on parental leave and he slept a lot, so I had a lot of time. I looked for a new strategy.
The clearest way to describe something as fiddly a Game Boy is often through code. I read other people’s emulators on Github, which sometimes felt like cheating, but then I remembered that this is my free time and there are no rules. I chose two reference projects written in different languages (Python and Rust) to the one I was using (Go). This meant that I had to translate between languages and architectures, which I think helped my comprehension, although sometimes I did still have to copy without understanding.
There’s also a lot of community-written documentation, which is both amazing and kind of crummy. It’s extensive and detailed, but also near-impossible for a newcomer to parse. For example, here’s part of a description of an LCD screen from the PanDocs, the most comprehensive public Game Boy manual:
After checking for sprites at X coordinate 0 the fetcher is advanced two steps. The first advancement lengthens mode 3 by 1 dot and the second advancement lengthens mode 3 by 3 dots. After each fetcher advancement there is a chance for a sprite fetch abortion to occur.
I’m not criticising the people who wrote these docs. They are still invaluable and I doubt that it’s possible to make them accessible to novices without spending a year writing a book that would be lucky to sell a hundred copies. I found them especially handy when I was able to match a paragraph of prose to a line of someone else’s code and use each to understand the other. But as a warning for other newcomers having finding the docs hard going - it’s not just you. At the very least, it’s me as well.
My big methodological breakthrough came when I joined the Emulator Developer Discord. The helpful denizens of the #gameboy channel pointed me to a GitHub repo of logfiles, in which a kind person with a working emulator had run Blargg’s ROMs and logged the state of their emulator after each instruction.
I used these golden logs to validate my own emulator’s behaviour, even though it still wasn’t correct enough to get a sensible result from the ROM’s actual tests. I logged the output from Gamebert in the same format as the logfiles in the repo, and wrote a tool that compared my logs line-by-line against the repo’s until I found a discrepancy. This allowed me to identify the exact CPU cycle when my emulator started misbehaving. I called my tool Game Boy Doctor.
I stopped aimlessly scrolling through my code and started using Game Boy Doctor to reliably pinpoint bugs. The Doctor found several errors in my CPU; faulty bit twiddles and and buggy half-carries. It couldn’t say how to fix the problems it uncovered, but the solutions were often obvious once I knew where to look.
The biggest, most boneheaded mistake it revealed was that I had accidentally left a debug flag turned on. This flag told Gamebert to silently continue if it was asked to execute an opcode that I hadn’t implemented yet, instead of crashing. Since I had implemented fewer than half of its 501 opcodes, Gamebert was quickly getting into a nonsensical state. I lost days of my life to this stupid flag, and I don’t know why I even added it in the first place - you can’t expect a program to do anything useful if you skip half its instructions. Nonetheless, with Game Boy Doctor’s help I finished implementing every instruction in my CPU, and successfully ran all of Blargg’s CPU test ROMs. I published Game Boy Doctor on GitHub, and now it’s helping other people debug and fix their own emulators.
Now I had momentum and understanding, and I swept through the remaining components of the project. I’d written enough of an LCD display to draw the Nintendo logo, and now I added the code to display the parts that I’d skipped over. The LCD was the most complex part of the project, and I think I just got lucky that it mostly worked first time. I tidied up my cartridge, joypad, and motherboard, but I stopped at implementing sound. You can tell yourself and your friends that your Game Boy works even if it doesn’t have sound, and by all accounts sound is quite difficult.
I ran Tetris, and it sort of worked. The Tetris pieces were invisible until they landed, but I correctly guessed that this was due to a bug in how my LCD combined pixels from its different layers. I fixed the problem and ran Pokemon Blue. It worked. I caught a Pidgey, walked around for a few minutes, and stopped. Gamebert seemed stable, but I hadn’t added any functionality to save the game and it would probably crash before too long, so I didn’t want to spend much time training up a squad. I’d reached my goal.
Gamebert was the best personal project I’ve ever worked on, and it’s one of the few I’ve actually finished. Despite this, it still sometimes felt absurd and pointless. Was I really spending 100 or so hours of my spare time on a shoddy replica of other people’s work instead of, say, playing with my family? This is of course an ungenerous way to talk to yourself, and is easy to attack from multiple angles. Who knows what zigzag professional paths my new knowledge might open up, and more to the point, who cares? Everyone needs time to themselves, and not everything has to have meaning or benefit beyond having some fun.