Skip to content
Shane DeSeranno edited this page Oct 23, 2015 · 34 revisions

Overview

How do emulators work? That is the goal of this project. Just learn the rough process to emulate a Gameboy. The first step is to setup a basic game loop as you might for any other game. This should initialize your render library (SDL2 in this case). In the game loop you will then want to execute one video frame of CPU cycles, then render the screen, and then wait any extra time to ensure your processor and video are running at the correct speed.

Key Links: http://problemkaputt.de/pandocs.htm

How long is one frame on the Gameboy? If you look in the link above and find the "Game Boy Technical Data" section, you will see that vertical sync is 59.73 Hz. This means, we need to render about 60 frames per second.

How may cycles does the Gameboy CPU process in one frame? If you look in the link above and find the "LCD Status Register" section, you will see that a complete screen refresh occurs every 70,244 clock cycles. The CPU should continue processing OpCodes until it meet or exceeds 70,244 clock cycles and then return to allow the emulator to refresh the screen.

Part 2 - The CPU

A CPU is the brain of the emulator. It is responsible for tracking the state of memory, registers and executing code. CPU Register and Flags are fundamental. They are accessed often and change regularly. The first part of CPU implementation is getting the registers and flags setup.

CPU Specifications PDF - This contains more details than we need to implement, but contains VERY detailed description of everything related to the CPU. If you go to page 22 of the PDF you will see details on these registers. All page numbers in PDF are the GOTO page, not the page printed on the sheet.

Registers

There are 6 registers on the Gameboy CPU. Each is 16 bits and most can be accessed by the HIGH and LOW bits.

  • AF: Accumulator and Flags
  • BC: General purpose
  • DE: General purpose
  • HL: General purpose
  • SP: Stack pointer
  • PC: Program counter

All 8 bit arithmetic output is stored in the accumulator (A). This is often used for small values because it takes less CPU cycles.

Flags store state of the last operation such as CARRY, ZERO, ADD, SUBTRACT. These are detailed in the PDF on page 94. For now, we'll start by setting the initial PC as per the info at Powerup Sequence. It says it will start by running the first instruction at 0x0000. Eventually this will be a hidden chuck of BIOS memory that is 256 bytes long. For now, just create a 64k (0xFFFF) byte array that will act as the memory. Be sure to initialize this memory to all zeros.

OpCodes

Now, let's implement our first opcode, NOP (0x00). In the PDF referenced above, you can find the instruction for the NOP. It basically does nothing and uses 4 clock cycles. So, read from the PC (0x0000), to get the OpCode (0x00/NOP), then read for an array of function pointers. This should map to one for NOP. All that function does is increase the total cycles by 4 and increment the PC by 1.

Since the memory is all zeros, it'll run through the entire stack of memory (64k) running NOP, advancing the clock cycles by 4 every step. So, now would be a good time to make the CPU track the cycles and return once it gets to 70,244, or one frame. It is important to make sure you don't drop cycles, and you don't want to have to worry about the cycles overflowing, so every time you get to 70244 or higher, then subtract 70244 from the total cycles.

If my math is correct, then after running StepFrame() once, the Program Counter (PC) register should be around 17561. It might be a few higher or lower.

Booting

The Gameboy has a ROM built in that is 256 bytes long. This ROM is contains the basic initialization and Gameboy cartridge checksum. This is a good article that covers what the boot ROM does. It goes through line by line an breaks down what each byte does.

This ROM is loaded in to memory from 0x0000 to 0x00FF and execution starts at 0x0000. Per the Powerup Sequence, after the ROM has ran and reaches the end successfully, the internal ROM is disabled and execution of the cartridge starts at 0x0100. From this point forward 0x0000-0x00FF will no longer point to the internal boot ROM, but instead must map to the cartridge memory as shown in the Memory Map.

What this means is that the emulator memory needs to get smarter than just an array of bytes. You'll need a way to route memory requests to different pieces. For example:

  • 0x0000=>0x00FF [During Boot] goes to internal ROM, but is hidden and maps to the cartridge after boot.
  • 0x0000=>0x3FFF goes to the cartridge.
  • 0x4000=>0x7FFF goes to a switchable (which means it can point to different offsets on the cartrige) bank of memory on the cartridge.
  • 0x8000=>0x9FFF maps to the video RAM (used to load sprites, etc).
  • 0xA000=>0xBFFF maps to external RAM that may exist on the cartridge (switchable).
  • 0xC000=>0xCFFF goes to working RAM provided by the CPU/system. This is used by the game for calulations, etc.
  • 0xD000=>0xDFFF goes to a switchable bank of working RAM also provided by the CPU.
  • 0xE000=>0xFDFF is an echo of 0xC000->0xDDFF, but is really not ever used.
  • 0xFE00=>0xFE9F is the Sprite Attribute Table (OAM), more on this later.
  • 0xFEA0=>0xFEFF is not usable an can fail catastrophically if desired.
  • 0xFF00=>0xFF7F is used for I/O Ports (gamepad buttons, etc).
  • 0xFF80=>0xFFFE is High RAM (HRAM) provided by the CPU. It is very small, but can be accessed at times when other memory cannot. Some games may move OpCodes into here to run during this dead time.
  • 0xFFFF is the Interrupt Enable Register, more on this later.

With all of this covered, we need to create a memory map unit (MMU) that maps the address to different classes that represent the various devices. And we need to load the BIOS ram into a 256 byte array chunk of memory that is used in "boot mode" to start execution. Finally, we need to create a Cartridge class that loads the game ROM into memory and is addressable via the MMU to read data from the game ROM.

Once all of this is done, we can start working on the first OpCode of the boot ROM:

LD SP,nn (0x31)

OpCode LD SP,nn (0x31)

This is the first Opcode of the boot ROM. Details of this OpCode are in the CPU Spec on page 120 of the PDF. It may look weird because it is written as "LD dd, nn". This is because "dd" represents one of the available 16 bit registers. And the bits at 4 and 5 determine which register to to access. In the case of SP, then the 4th and 5th bits are both one giving the binary value of 0011 0001 or 0x31 in hex. So this is written as LD SP,nn. The description explains: "The 2-byte integer nn is loaded to the dd register pair..." This means, the next two bytes in memory are read (don't forget to advance the PC by 2 when doing this!) and the SP (Stack Pointer) register is then set to this value. In other words, load (LD) into the Stack Pointer (SP) the 2 byte value (nn). If you look at the first 3 bytes of the BIOS.bin, you will see:

0x31 0xFE 0xFF
or
LD SP, 0xFFFE

This effectively sets the stack pointer to the correct space near the end of the memory map. You may have noticed that the bytes were read backwards. The least significant byte is read first, then the most significant byte. Again, the documentation states: "The first n operand after the OpCode is the low order byte." This is referred to as Little Endian.

There will be a lot of OpCodes that do identical actions, but to different registers. LD dd,nn is one of them. This means, you can probably write one function that handles all of these cases. And all you need to do is determine which register needs to be updated and then update the correct value. The alternative, is to implement a separate function for each OpCode like LD SP,nn and LD BC, nn and LD DE, nn and LD HL, nn. Either way will work fine, but think about this as you move forward as possible code optimization.

Finally, you need to remember to add the appropriate number of cycles. This data can be found at CPU Instruction Set. In this case they refer to the instruction as "LD r,n" and it costs 8 cycles.

So, if you have loaded the boot ROM, and implemented this OpCode correctly. Then, after executing this one OpCode, the registers should be:

(Remember, when you read the 2 bytes for the LD SP,nn argument, you need to advance PC past these arguments)
PC = 0x0003
SP = 0xFFFE

And now the next OpCode in the boot ROM is: XOR A (0xAF)

Clone this wiki locally