Building 8-bit Emulator in Golang

by Justin Harris

Learning Go via Emulation

The Go programming language has been receiving a lot of attention over the past year, and we have started to incorporate it more and more into our tech stack at WP Engine. While I have played around the language a bit when it was very young, I wanted to work on a side project with enough interesting aspects to evaluate the language and tooling in 2017. I have started to become interested in writing game emulators lately, and using Go seemed like a promising fit. This post will highlight how the final product turned out, as well as some thoughts on using Go to pull it off.

CHIP-8

For anyone taking their first step into emulation, CHIP-8 is often recommended as an easy platform to get going. Initially used by a few 8-bit microcomputers in the mid to late 70s, the game format went through a revival in the early 90s in order to play games on graphing calculators. With 35 opcode, a 64×32 pixel display, and a simple sound system (beep or don’t beep), the architecture can support only very basic games — but they can still be quite fun.

Emulator Design

I found this reference to have a nice specification of the CHIP-8 architecture, as well as a detailed description of the instructions. The first step is to read the game data and initialize the program. Once the initialization is complete, there are three main loops that are executed concurrently:

Program Loop

The program loop executes the opcodes defined by the input program, one after the next. The function is defined below (slightly simplified from the actual implementation), with a detailed explanation of each line:

6. Execute the Operation on the VirtualMachine. Each operation acts as a visitor, manipulating the the struct as needed.

7. Increment the program counter, so that the next iteration of the loop will pick up the subsequent opcode.

Timer Loop

The timer loop is structurally very similar to the program loop. The difference is that it operates at a different frequency, 60Hz, as defined in the spec. It simply decrements the two timer registers defined in the architecture, if they are positive. The delay timer is used to provide countdown functionality for the games, while the sound timer indicates to the system that a sound should be played when the value is positive.

Render Loop

The render loop handles both user input and rendering of the pixel data to a display. While my first inclination was to make these two distinct subsystems, most graphics libraries handle both, so combining seemed like the most pragmatic approach. One advantageous side-effect of decoupling the graphics from the program execution is that new implementations are easy to add. There are currently two displays: one using OpenGL and a second using the terminal (with the awesome termbox library).

Thoughts on using Go

Type System

As I have been using quite a bit of Python lately, getting back into the using a statically typed compiled language was a bit of a transition, but honestly not much of one. The Go compiler is blazingly fast, to the point where these days are a thing of the past. Getting back into using types has been a joy – as the fast compilation speed enables a very tight feedback loop. Here is an examples of using types in the CHIP-8 emulator:

type OpCode uint16

This is a very simple case of how types can help make sense of a codebase. Rather than just throwing around integers and relying on either variable naming or code comments to indicate that a parameter needs to be an integer representing an opcode, the type can indicate that explicitly. The function signature for the CreateOperation function that we saw earlier makes that clear:

func CreateOperation(opcode system.OpCode) Operation { … }

This function doesn’t create an operation for a given integer, but more specifically for an OpCode. Creating a custom type also allows methods to be written for those types:

This method generates a string representation of the OpCode, as you would expect. As an added bonus, the fmt package will call this method automagically when printing variables, which behaves like toString() in Java or __str__() in Python.

One of the biggest complaints around the syntax of Java, and perhaps one of the motivations to move to dynamic languages a few years ago, was the verbosity that the type system dictated. For example, here is a hypothetical variable declaration if this were to be written in Java:

private VirtualMachine vm = new VirtualMachine();

In Go:

vm := VirtualMachine{}

This simple example demonstrates the language design quite well. The type of the declared variable can be inferred from the assignment, and the := operator makes this inference explicit. There is a nice balance between brevity and clarity, with very little boilerplate.

Interfaces and Program Composition

Go takes a pretty different approach to OO features, most notably in a lack of inheritance and the use of interfaces to support dynamic method dispatching. On the composition vs. inheritancetopic, the Go authors have taken a definitive stance. While this initially seemed quite limiting, I found that I didn’t miss using inheritance at all. This sort of minimalism is a core aspect of the language, and one of its biggest strengths.

Go is unapologetically opinionated, both in the core language and tooling. Declaring an unused variable or importing and unused package is a compilation error, pure and simple. No warning or suggestion, your build fails. This rigidity, coupled with tools such as gofmt and golint, makes for little ambiguity on how to structure the more mundane aspects of writing code, such as spacing or the comment format. This really helps to focus on the problem at hand, rather than being distracted by trivial decisions.

Channels

Concurrency is a first class language feature in Go, and channels are a core construct that enables data synchronization between goroutines. We’ve already seen an example of channel usage in the standard library in the timer.Ticker struct. This shows quite well how useful channels can be used to express and reason about concurrency without dealing with mutexes or function callbacks. Overall, I think that channels and goroutines are a killer feature of the language, as the programming model is very different from e.g. Java threading.

I did violate one of the primary tenets of Go, namely share data by communicating, in the CHIP-8 design, as there are multiple concurrent goroutines operating on the VirtualMachine struct – these are the three loops mentioned the design discussion above. However, I feel that this is a justified decision for the following reason: The data is really only flowing one way in each case where data is shared.

For pixel display, the “draw” operation sets pixel values, and the render loop simply draws the data in the pixel array to the screen.

For keyboard input, the render loop updates the input data, and the variations of “if key” operations manipulate the system state based on those values.

For timer data, the timer loop updates the timer registers, and various operations can check or manipulate those values. The nature of the timer loop, specifically that it is constantly counting down at a steady rate, means that practically there is no need to synchronize access to those registers.

Development Process

While some of the more interesting parts of the implementation are the display implementation, the bulk of the code is in the operations package. This package contains both parsers, for translating an OpCode (16 bit word) into an Operation, and the Operations themselves. Once these interfaces were established, the development process was very straightforward with TDD. Two tools were extremely helpful in this process:

Testify for the test assertion library – I did find it a bit odd that this sort of testing functionality was not part of the standard library, especially since there are so many batteries included as it is. This library fills the void nicely.

The Gogland IDE from JetBrains – I have been a fan of JetBrains for quite a while, and even though their Go IDE is only available as an “early build”, I found it quite capable and a real productivity booster to settle into a red/green/refactor rhythm.

Final Assessment

Now almost 10 years from initial inception, Go has clearly had a meteoric rise, and even from this small project, the appeal is clear. Testing, linting, documentation generation, etc. all work quite well, and the amount of focus and single responsibility for each area is a refreshing contrast to some alternatives. One counterpoint is that Go 1.9 was released as I was writing the emulator, and a newly introduced function motivated me to upgrade. This ended up increasing the compilation times significantly, which became very painful as my TDD process continued. This prompted two realizations:

Go is still very much evolving, and adopting the latest and greatest is not always so great. I’m sure that this is something that will soon be addressed by the team.

The blazing speed of compilation and testing is addicting, and it is surprising how quickly I started to depend on it.

The final product is available on GitHub for viewing and hacking. Overall it was a lot of fun to learn the basics of the language and to learn more about emulators – I’m thinking that NES or Sega could be next. Also, WP Engine is hiring – so if you want to work on Go code with awesome people, be sure to reach out to us.