Writing a Game Boy Emulator - Part 1

Introduction

Although I wasn't born to experience the era were the Game Boy was first released, I have fond memories of playing games on a Game Boy Color. Therefore it didn't take me too long to decide that the Game Boy would be my new emulation target after programming my Chip-8 emulator (we all did that one).

Planning

The CPU of the Gameboy is the 8-bit SHARP LR35902. That's the first part to emulate. It's a hybrid of the Intel 8080 and Zilog Z80, but compatible to neither of the two.

A nice overview of the instruction set can be seen here: instruction set

Of the 4 instruction extensions (CB, DD, ED and FD) of the Z80 only CB is supported on the LR35902.

Short-term Goal

The first thing the Gameboy executes is 256 Byte long builtin boot ROM, the so called DMG ROM.

After doing the usual things (setting up the stack, clearing up the video ram) it draws the Nintendo logo on the screen, scrolls it, plays the "po-ling" sound and then starts the game (after some anti-piracy measurements).

gameboy logo

My (first) ultimate goal is to get this image on my screen (let's ignore the sound system for now).

Implementing Opcodes

Writing each opcode by hand would be time consuming, but there is a better way:

Thankfully there already exists a github repo with all Game Boy opcodes in json format. Github repo

{
  "unprefixed": {
    "0xc0": {                 <-- Address
      "mnemonic": "SET",      <-- Instruction mnemonic
      "length": 2,            <-- Length in bytes
      "cycles": [             <-- Duration in cycles
        8
      ],
      "flags": [              <-- Flags affected
        "-",                  <-- Z - Zero Flag
        "-",                  <-- N - Subtract Flag
        "-",                  <-- H - Half Carry Flag
        "-"                   <-- C - Carry Flag
      ],
      "addr": "0xc0",         <-- Address
      "group": "control/misc" <-- Opcode group
      "operand1": "0",        <-- Operand 1
      "operand2": "B"         <-- Operand 2
    },
    ...
  },
  ...
}

As you can see, a lot of information can be used to speed up the process of implementing every single opcode.

So I wrote a python script which would convert the json of every opcode to its C function prototype. I decided on this format:

// 0x00 NOP - control/misc
// length: 1, cycles: 4
// flags: -, -, -, -
// operands: None
void opcode_NOP()
{
    if (debug) std::cout << "NOP" << std::endl;
    cycles += 4;
    std::cout << "UNIMPLEMENTED" << std::endl;
}

With that I could see which operations have not yet been implemented. Also could see with at a glance, which flags are changed.

The second thing the script does is creating a switch - case table for all opcodes to jump to. Also don't forget to think of the 0xCB prefix, the byte after that is the actuall opcode in the CB table.

Boot ROM

Now what I could have done would be to program all opcodes at once. But a single error would then be hard to track down, so I decided against that.

Instead I decided to implement the boot ROM opcode after opcode and verify the correct behaviour after every newly implemented opcode.

The boot ROM is available disassembled with comments which helps a lot during this process.

Boot ROM (disassembled)

Boot ROM (binary)

The first opcode is to load a value in the stack pointer.

So I basically did this ...

  1. Implement the opcode
  2. Start the emulator
  3. Verify correct behaviour

... over and over.

The following has helped me to debug my errors:

  • step command, to execute only the next opcode (duh!)
  • break command, to run until a certain address is loaded into the PC (you don't want to step through RAM clearing)
  • hex dump of RAM

Fixing bugs

I implemented the neccessary opcodes until I came to the point where two routines were called to setup the video RAM with the tiles needed to display the logo.

This piece of code was called in a loop and after a lot of iterations I always returned to the wrong address. Why? My push/pop functions seemed to be correct and the boot ROM should also call them in correct order.

So this took me a while. In order for push/pop to work I added High RAM (127 Bytes for the stack) to the memory map, but I actually didn't. I got something wrong and my stack location was therefore in video RAM ... where the boot ROM is writing to ... So that was quickly fixed after finding the problem.

Getting to something

Now that the tiles are loaded into video RAM we can actually display them! I already did some projects with SDL so I decided to use SDL2 for drawing the tiles.

Video RAM starts at $8000 and one tile is 16 bytes large.

From gbdev:

For each line, the first byte defines the least significant bits of the color numbers for each pixel, and the second byte defines the upper bits of the color numbers. In either case, Bit 7 is the leftmost pixel, and Bit 0 the rightmost.

So that's everything we need to know to draw our first tile.

Actually make sure to draw more than one tile, or start with the second one. That's because the first tile is just blank, for the background.

This was one of my first results (ignore the diagonal line):

first test

Yes, we can actually see something! Don't believe me?

Now if we draw even more we can roughly see the Nintendo logo:

second test

Why does it look shifted? Because that's only the tile data, it could be in either order. The tilemap specifies where each tile ends up on the screen, potentially reusing tiles to save memory.


That's all for now. The next goal is to implement the tilemap properly.