The emulation adventure continues!

This is part two of my series on writing an emulator for CHIP-8 in Golang. If you need to catch up part one is available. Since then I’ve made good progress!

Opcodes, Opcodes Everywhere

The headline for this post is that the emulator now has enough opcodes implemented to load and run a ROM! An opcode is an instruction to a machine that specifies an operation to be performed. These instructions can do anything from allocating memory, calling sub-routines, updating graphics and many more.

In the case of the CHIP-8 we have 35 opcodes to deal with. This is a relatively small number compared to other 8 bit platforms; which makes it a great choice for a first emulator. Similarly to other systems the CHIP-8 opcodes indicate both the operation to carry out and the data they will process.

A CHIP-8 opcode is two bytes long which allows us in Go to easily treat them as an uint16. Fetching the current opcode from the memory (once a ROM is loaded) is done like so:

func (v *VM) Cycle() error {
    ...
    v.opc = uint16(v.mem[v.pc])<<8 | uint16(v.mem[v.pc+1])
    ...
}
There are a few things going on here of note:

  1. We get two instructions using the program counter v.pc and the next program counter v.pc+1
  2. We then shift the first instruction left by 8 bits (10100010 becomes 1010001000000000)
  3. Finally we apply a bitwise OR operation. This merges the two instructions as we already know the last 8 bits are all zeroes. (10100010 -> 1010001000000000 -> 1010001011110000)

To actually act upon the opcodes; the emulator has a func map. For each opcode there is a registered handler function that’ll appropriately alter memory, stack pointers etc

func (v *VM) registerHandlers() {
    ...
    v.handlers = map[uint16]opcodeHandler{
        0x0000: {
            opcode:  "handle0x0000",
            handler: v.handle0x0000,
        },
    }
    ...
}

Sound and Vision

The other big steps since last time are the implenting of graphics and sound. Unsuprisingly the CHIP-8 has a very simple display and sound system. The display is a 64x32 pixel array which can be simply represented in Go as a byte array:

type VM struct {
    ...
    disp [64 * 32]byte
    ...
}
The DXYN opcode is responsible for updating the display. The documentation for which reads as:

Draws a sprite at coordinate (VX, VY) that has a width of 8 pixels and a height of N
pixels. Each row of 8 pixels is read as bit-coded starting from memory location I; I
value doesn’t change after the execution of this instruction. As described above, VF
is set to 1 if any screen pixels are flipped from set to unset when the sprite is
drawn, and to 0 if that doesn’t happen 

This basically means that a pixel at co-ordindates X,Y with a height of N is updated. The update itself is a bitwise XOR operation: 0’s become 1’s and 1’s become 0’s. There is also an ingenious little trick here for collision detection:

func (v *VM) draw() (uint16, error) {
    x := uint16(v.v[(v.opc&0x0F00)>>8])
    y := uint16(v.v[(v.opc&0x00F0)>>4])
    
    for cY := uint16(0); cY < height; cY++ {
		for cX := uint16(0); cX < 8; cX++ {
			index := x + cX + ((y + cY) * 64)
			...
			if v.disp[index] == 1 {
				v.v[0xF] = 1
			}
            ...
		}
	}
}
Notice that if the pixel at the derived index was already set to 1 we update the VF register to 1. This indicates that a collision has occurred because a pixel has been ‘switched off’. To actually render the game I am using the Pixel library, specificaly the OpenGL bindings. I won’t go into it too much as it’s not the topic I want to focus on, but suffice to say it’s a great package and very easy to work with.

Sound is also very simple to implement. The CHIP-8 has a sound “timer” which in reality is a byte which counts down on each cycle. The FX18 opcode sets the timer to a given value, each subsequent cycle will then count down the timer. When it hits one this indicates that a sound should be played. Again I am using libraries for playing sounds, in this case I am using Beep to play the sound and Packr for embedding a wav file into the binary.

Pong

And here you can see pong in action! It doesn’t look like much, but to have a ROM loaded and running without crashing is a nice milestone and something to be pleased with :)

Bad Time

Next Steps

While pong does run on the emulator it isn’t actually playable yet! So the key next step is to implement controls, this should not be too complex in typical CHIP-8 fashion but does require several new opcodes.

Otherwise the goal is to get all 35 opcodes in place which will allow other games to run on the emulator.