About a year ago a friend told me about a great book called The Elements of Computing Systems: Building a Modern Computer from First Principles. As a professional developer from a math instead of CS background, this book filled in the missing links in my mental model of the bridge between hardware and software and I highly recommend it.
After finishing the assembler project I thought it would be interesting to build a Hack (the name of the CPU architecture made for the book) emulator that could run in a browser. I’ve always been curious about how to actually implement an emulator and thought that this would be a simple one to start with.
TL;DR
Below is a demo of the emulator and the source code can be found here. For those that haven’t gone through the book, you can download Fill.hack or Pong.hack and then load it below. For Pong, you may need to tweak the settings a little to make it playable.
Components
The CPU
The Hack CPU instruction set (described in Chapter 4 of the book) consists of an A-instruction that loads a 15 bit address into register A and 28 different C-instructions, each with destination and jump bits that control where the output is stored and where to load the next instruction from. There are two registers, A and D, one pseudo-register M that represents the value at the address in register A, and the program counter. We initialize these values in the constructor of the CPU and add some setters/getters for ones that need to be exposed externally:
|
|
Instead of hardcoding all 253 possible opcodes, we can encode the 28 different compute instructions and process the destination and jump bits separately. This gives a total of 30 opcodes (28 + A-instruction + NOOP).
Now that the instruction parsing is all set, we can simulate a single CPU tick quite easily.
|
|
The only thing left is to implement each of the opcodes which are very straightforward.
|
|
MMU
The MMU controls writing and reading memory. The Hack computer has a ROM and RAM each consisting of 32K 16-bit addressable slots. The ROM is readonly except for loading a new program. The RAM consists of normal memory as well as memory maps for the screen (0x4000 - 0x5FFF
) and the keyboard (0x6000
). Addresses 0x6001 - 0x7FFF
are normal RAM again.
|
|
GPU
There is no real GPU in the Hack computer, but this module handles writing the data from the memory map to the screen. The Hack screen is 512 pixels wide by 256 pixels high where each pixel is represented by a single bit. We represent the screen with a canvas
and use an ImageData
object to address and update each pixel.
|
|
Keyboard
The keyboard is a very simple memory map of a single byte at address 0x6000. There are a few non-standard key mappings defined that we can easily translate to their corresponding values.
|
|
The Emulator
Putting it all together
Lastly, we need to hook the components together so that the Hack computer runs and renders to the screen. After trying a few different rendering methods, I decided to do a full screen render at a user defined refresh rate using requestAnimationFrame
because it gave better results than rendering each pixel on write.
|
|
Using a setTimeout
per CPU tick is WAY too long even with a timeout of 0. Instead in each setTimeout
call we run a configurable number of ticks so that we can control the CPU speed a little better since Hack computer doesn’t have a real timing mechanism.
|
|