Writing a Goboy: starting off

July 6, 2019

This is the first in a series of posts documenting my progress writing a Gameboy emulator in Go (imaginatively called Goboy). Sometimes it’s nice to write something that’s not a web app. My plan is more to track my progress and highlight interesting bits rather than create an in-depth guide to writing an emulator so these may be a bit scrappy and intermittent. I’m trying to take a fairly agile, one-instruction-at-a-time approach so there will probably be plenty of missteps and backtracking.

The first instruction

I began by going through the Gameboy programmer’s manual and making notes on what seemed to be the salient parts. The Gameboy has an 8-bit processor and about 8KB of memory. The first instruction in the manual happens to need nothing more than a few registers:

LD R1, R2

This copies the value in R2 into R1. To emulate this we just need some registers and to execute a single instruction.

Registers

The Gameboy has eight byte-length registers. A is the accumulator and seems to do most of the work. B-E are general registers. F is the flags register. H and L seem to be used together to handle 16-bit addresses. The stack pointer (SP) and program counter (PC) are both 16-bit as well.

To begin with I implemented the registers as members of a struct:

type CPU struct {
       A, B, C, D, E, F, H, L byte
       SP, PC                 uint16
}

The problem is that in Go the member being accessed needs to be known at compile time so that the compiler can compute the correct offset. Since I’ll only know which register to use at runtime after decoding the instruction I think I’d need to do something like:

func (cpu *CPU) Get(r Register) byte {
  switch r {
    case A:
      return cpu.A
    case B:
      return cpu.B
    ...
  }
}

That looked like a bit of a faff so I decided to just store the registers in a map and access them directly:

type Register byte

const (
	A Register = 0x7
	B          = 0x0
    // ... and so on
)

type Registers map[Register]byte

type CPU struct {
	r      Registers
	SP, PC uint16
}

func (cpu *CPU) Get(reg Register) byte {
  return cpu.r[reg]
}

Register is defined as a constant with a value matching the bit pattern identifying the register in the instruction opcode. This means I can just pull the register out of the opcode and use it directly as the key to the map. This works nicely although two registers share a bit pattern. I think this is because they can never be used in the same instruction but this may come back to bite me.

Ideally I’d use a sum type for the registers so that it would be a compile error to pass in an invalid register value, perhaps from an incorrectly-decoded instruction. At present any byte value is accepted even if there isn’t a defined constant for it. Sum types can be implemented in Go but they seem a bit clunky and, well, if I want super-strong type safety then I shouldn’t be using Go.

Decoding the instruction

The Gameboy manual tells me that the LD D, S instruction is of the format 01dddsss, where s and d are three-bit patterns identifying the source and destination registers respectively. All opcodes are 8-bit but some instructions are two bytes long because they have an opcode and then an 8-bit value. I decode the instruction into a struct implementing the Instruction interface:

type Opcode byte

type Instruction interface{} // TODO: specify interface
type LoadRegisterInstr struct {
	source, dest Register
}

const LoadRegisterMask = 0xC0
const LoadRegisterPattern = 0x40
const DestRegisterMask = 0x38
const DestRegisterShift = 3
const SourceRegisterMask = 0x7

func decode(op Opcode) Instruction {
	switch {
	case op&LoadRegisterMask == LoadRegisterPattern:
		source := Register(op & SourceRegisterMask)
		dest := Register(op & DestRegisterMask >> DestRegisterShift)

		return LoadRegisterInstr{source, dest}
	default:
		return nil
	}
}

Decoding is a fairly standard process of masking the relevant bits of the opcode and checking whether they match the pattern for the instruction. Note that Go allows you to write switch {}. The idea of having an intermediate Instruction struct is to make it easier to separate decoding from execution. I don’t really want to have to be performing the decoding and execution in one giant function so it’s nice to be able to abstract away from the actual hex values of the instructions and pass the structs around.

The actual execution is currently a single step function that gets the current instruction, decodes it and matches on the type to determine how to execute it:

func (cpu *CPU) Run(opcode byte) {
       // TODO: get opcode from memory

       i := decode(opcode)

       switch instr := i.(type) {
       case LoadRegisterInstr:
               cpu.set(instr.dest, cpu.get(instr.source))
       }
}

For now I’m just passing the opcode in to make testing easy but obviously I’ll need a loop where it pulls the instructions from wherever the program counter dictates. This can be tested like so:

func TestHardcodedRun(t *testing.T) {
        state := Init() // Create the CPU struct etc
        state.set(B, 9)

        state.Run(0x78) // LD A, B
        if state.get(A) != 9 {
                t.Error("A register does not match B")
        }

So this works! Next up I’ll implement a few more load instructions. They’ll require getting a basic memory implementation in place, loading programs into memory and doing a proper run loop. I am enjoying this approach of designing as I go - it makes it feel more like a fun puzzle than a complex task that I have to get right first time.