Exploring Emulation in Go: CHIP-8
Emulation has always seemed a bit magical to me. Creating hardware on top of hardware, accurately re-creating popular computers and consoles seemingly through genius-level reverse engineering. Recently, I discovered a shallower learning curve into the world of emulation: CHIP-8.
CHIP-8 is an early interpreted language, designed in the 1970s to make it easier to build games for multiple platforms. With a very small list of opcodes, simple graphics and input, building a CHIP-8 emulator is a great introductory project. So I’ve built one in Go, and learned a heck of a lot in the process.
Finding a guide
A quick Google search led me to Laurence Muller’s excellent tutorial, which gives an overview of the general concepts and provides a few sample implementations of opcodes (including the more difficult graphics related ones). The tutorial stops short of explaining all the opcodes, since they’re all listed on Wikipedia and elsewhere, and this leaves just enough challenge for a first time implementer. Not only that, but I was able to work through the tutorial itself in a few hours, and have a great starting point to finish everything off the next day.
Implementing Opcodes (the repetitive bit)
I got hold of a couple of roms from other projects (there are tons of examples out there), and loaded them against the skeleton I had. I found that implementing the opcodes was much less tedious if I loaded up a rom, and ran it until it hit an unimplemented opcode, then I could fix that opcode and move on to the next.
In my enthusiasm, I hadn’t actually seen a CHIP-8 emulator running before I started the project, so was going in blind. I had a rough idea of what pong was supposed to look like, but I was very much guessing as to whether or not I had it right.
I considered firing up the debugger, but having no real understanding of the language I was implementing, or the programs I was running, it would be very slow going, and I’d only really be guessing at whether or not the state was correct.
But patterns quickly emerged when running a lot of cycles in succession. To get something of a birds-eye view of what was going on, I printed a trace of the opcodes being executed in each cycle, based on the pseudo C code listed on Wikipedia.
This came in handy when a bug in my handling of the return opcode created an infinite loop.
Some issues might have required more robust debugging abilities, so had I run into bigger problems I might have started outputting registers, or implementing some kind of breakpoints. But with the relatively simple opcodes, I didn’t need to resort to that.
It turned out that the example roms I was using didn’t use all of the opcodes anyway, so it wasn’t long before I could start adding graphics and keypad input.
Graphics and Input
I was already familiar with Pixel, a 2D game library for Go, so it was an easy choice for this project. The structure recommended by the tutorial lent itself really well to separating the actual rendering of graphics from the CHIP-8 emulation itself. All I needed to do was expose the appropriate inputs and outputs and add calls to handlers inside the main loop.
The structure of Pixel programs in general also gelled nicely with emulation. A main loop is more or less expected, and keyboard input is handled in the same manner as in the emulated programs themselves: checking whether or not a particular key had been pressed or released since the last time it was checked. There was one caveat that caught me out for a while though. Since the screen is not drawn every cycle, you need to call UpdateInput to trigger an update of the key states.
Once these elements were in place, it was easier to see where things needed improvement.
It was pretty clear that the timers were off when I saw Tetris running way too fast. Re-reading, I discovered that they needed to be counting down at 60Hz, which the skeleton provided by the tutorial wasn’t doing. Go’s concurrency support made it easy to limit the counters to this frequency, using a time.Ticker and a select statement to only decrement if there was a tick waiting in the channel.
From Switch Statements to Functions
My initial implementation of opcodes involved a very large switch statement, which was difficult to follow and moreso to debug. The tutorial recommended moving away from this to a function based approach as a further goal for the project, so I created a separate function for each major opcode, and mapped them to the mask values previously checked by the switch statement.
With this in place, handling the opcode was just a case of retrieving the function from the map.
To make the migration a little easier, I structured the file so that a missing opcode in the map would not cause an error at first, and fell back on the remaining parts of the switch statement, this let me test each opcode one at a time.
Speaking of testing, this structure also allows each opcode to be more easily unit tested individually. Were I to do the project over, I’d start with this structure so I could apply some TDD, rather than having to launch a ROM and play Tetris until a line disappeared to use a particular opcode.
You may have also noticed the Result type being returned from the opcode function. This moves the responsibility for logging to the caller for greater flexibility, and provides detail of the opcode being handled, as well as register state before and after execution.
This allows not only console logging that can be enabled and disabled at will, but in future could permit the state to be displayed in a GUI.
This has been a fascinating weekend project that I’d recommend to anyone who feels like dipping their toe into the world of emulation programming. Especially if you feel like you don’t have time for it, you can have something up and running in a few hours!
Now I’ve got a taste for it, I’m eager to implement a machine that has actually existed at some point. Forums such as EmuTalk recommend a Space Invaders cabinet (based on the 8080 processor), or the Game Boy (based on Z80). I gather they’re quite a leap, but I think it could be good fun!