Skip to content
Shane DeSeranno edited this page Nov 4, 2015 · 1 revision

Go Home

Part 04 - Booting

NOTE: This section will be long!

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)

XOR A (0xAF)

Details of this OpCode are in the CPU Spec on page 174 of the PDF. It may look weird because it is written as "XOR s". This is because "s" represents one of the available 8 bit registers (B, C, D, E, H, L, and A). XOR is an exclusive OR. And all actions done with this OpCode work against the accumulator register and the results are stored in the accumulator. When you XOR anything with itself, it will result in a zero value. This is the easiest way to set the A register to zero!

Don't forget to increment the PC and add 4 cycles!

And now the next OpCode in the boot ROM is: LD HL,nn (0x21 0xFF 0x9F) or LD HL, 0x9FFF

LD HL, nn (0x21)

This one is exactly like LD SP, nn except it loads the value into HL! Easy!

LDD (HL), A (0x32)

The next OpCode is 0x32... but what is that? Well, this is where we must be careful. There are differences between the Gameboy CPU and the real Z80. This is one of them. You can refer to CPU Comparison With Z80. And you can see that the 0x32 OpCode is "moved".

0x32      LD   (nn),A     LDD  (HL),A

This means that 0x32 was LD (nn),A and is now LDD (HL),A. The difference is subtle and there is also those confusing ()'s around the HL! What do those mean?! Help! Look at the Z80 PDF on page 149. This even provides an example. In English, load (LD) the byte in the accumulator (A) register into the address pointed to by the HL register, then the second D indicates it should decrement the HL register. For example:

A  = 0x00
HL = 0x9FFF
Run: LDD (HL), A
HL = 0x9FFE
m_memory[0x9FFF] = 0x00 (the value from A)

This is setting up to fill the memory from 0x9FFF to 0x8000 with 0x00. This just happens to be the VRAM (video RAM). Don't forget that the PC will be incremented by 1 and the cycles go up by 8 cycles.

So, what is next? 0xCB, what is that?! It's our first two byte OpCode! 0xCB maps to an entire table of different OpCodes. If you look at this table you will see that the 0xCB has ****. This indicates to look below at the 0xCB table, and look up the next OpCode byte, which is: 0x7C, or BIT 7, h.

BIT 7, h (0xCB 0x7C)

This OpCode is documented in the PDF on page 242 and is called: BIT b,r

As documented, this instruction tests bit "b" in register "r" and sets the Z (zero) flag accordingly. For example, if the bit 2 in register B contains a 0, at execution of BIT 2, B the Z flag in the F register contains 1 and bit 2 in register B remains 0.

In this case, we are checking bit 7 of the H register. If it is zero (unset), then we need to set the Zero (Z) flag. If it is one (set), then we need to clear the Zero (Z) flag.

Let's pause for a moment and ask what the booting process is doing. Why is it checking the 7th bit of the H register right now? Well, so far, it has loaded 0x9FFF into the HL register. Then it moved 0x00 to that address. Then it decremented HL. So HL is now 0x9FFE. Now it looks at H register (0x9F) which is 1001 1111 in binary.

Remember the bits are (in order): 76543210

So, clearly the 7th bit is set on: 1001 1111. The next OpCode (which we haven't got to) will look at the Zero (Z) flag and if it is set, loop back to the previous OpCode. This is a loop! And it will loop until this 7th bit of the H register is not set. Which will happen as soon as the HL register gets down to 0x7FFF or 0111 1111 1111 1111.

I know this may be a little overwhelming, and it really isn't necessary to understand everything being implemented, or why, but I just thought this was cool! This loop is clearing the memory from 0x9FFF to 0x8000! Awesome!

That brings us to our next OpCode: JR NZ, e (0x20 0xFB)

JR NZ, e

This OpCode is documented on page 266 of the PDF. If the Zero (Z) flag is set, then the PC is just incremented by 2 and execution continues. In this case, the cycle cost is actually cheaper and only takes 12 cycles.

However, if we have to read the argument and jump, then it costs 16 cycles. If the Zero (Z) flag is not set, then we read the next byte (0xFB). Now we must treat this a SIGNED byte. And if you cast 0xFB to a signed byte, it becomes -5. Finally we can add this value to our PC (after incrementing by 2), and it'll make the PC jump back to the LDD (HL), A (0x32) OpCode! Loop achieved!

At this point, our boot ROM should actually run for quite a while as it should loop and clear all the memory in the VRAM! But once HL gets down to 0x7FFF, it will continue on. If you are using a debugger, I recommend setting a conditional breakpoint that breaks when HL = 0x7FFF.

Now we can move on to our next OpCode: LD HL, nn (0x21).. OH WAIT! We've already done this one! This will start to happen more and more! So nice. The boot process is loading the address 0xFF26 into HL. If you look at the specs you can see that 0xFF26 maps to: 0xFF26 - NR52 - Sound on/off. This boot system is getting ready to setup sound. Don't worry about this for now, we'll make sound happen, but much later.

Okay, so the next OpCode is: LD C, e (0x0E 0x11)

LD C, e

As you might have guessed, this loads the next byte into the C register. In this case 0x11. After this OpCode runs, then the C register will equal 0x11.

Next: LD A, e (0x3E 0x80)

LD A, e

And now it loads the next byte into the A register. In this case 0x80. After this OpCode runs, the A register will equal 0x80.

Next: LD (HL-),A (0x32) - Another one that we have already done! So this is going to load 0x80 into the memory that HL points to (0xFF26) and then decrement HL down to 0xFF25.

Next: 0xE2... another remapped one:

0xE2      JP   PO,nn      LD   (FF00+C),A

So, LD (0xFF00+C), A (0xE2)

LD (0xFF00+C), A

This OpCode is special for the Gameboy because 0xFF00 is the offset where a lot of flags are accessible. So they made a special OpCode to allow for easy loading of values into them. In this case it is loading A (0x80) into the address location at 0xFF00 + C, which is current 0x11. So 0xFF11 will be set to 0x80. And 0xFF11 is more sound stuff:

0xFF11 - NR11 - Channel 1 Sound length/Wave pattern duty (R/W)

Next: INC C (0x0C)

INC C

This does exactly what you think it does... It increments the C register.

Now would be a good time to remind you ensure you are adding the required # of cycles. Determining the total # of cycles can be a bit of guess work. The PDF is a little too accurate and allows for odd cycle values. We want to keep all of our cycles to multiples of 4. In find the CPU Instruction Set is the best place to look. In the case of INC:

Operation      OpCode    Cycles
inc  r           xx         4   z0h- r=r+1

Next: LD A, n (0x3E 0xF3)

This one loads the next byte (0xF3) into the A register.

Next: LD (0xFF00+C), A (0xE2)

We already did this one! Awesome, so this loads the A register (0xF3) into 0xFF00+C or 0xFF12!

Next: LD (HL-),A (0x32)

Another one we have already. The boot ROM is now loading 0xF3 into HL (0xFF25) and then decrements HL again. So HL should be 0xFF24 now. It is still fiddling with sound bits.

Next: LD A, e (0x3E 0x77)

Another one we have done, and it loads 0x77 into the A register.

Next: LD (HL),A (0x77)

This is a new one, but behaves very similar to another. It loads the A register, which is currently (0x77) into the address that HL is pointing at, currently 0xFF24. But this OpCode does not decrement HL.

Next: LD A, e (0x3E 0xFC)

Done already! Woot! This is getting ready to setup the video's background palette.

Next: LD (0xFF00+C), A (0xE0 0x47)

This is another remapped OpCode:

0xE0      RET  PO         LD   (0xFF00+n),A

0xE0 is now LD (0xFF00+n), A. This means we need to read the next byte (0x47), add it to 0xFF00, then write the A register to that memory address (0xFF47). It just so happens this address maps to:

0xFF47 - BGP - BG Palette Data (R/W) - Non CGB Mode Only

Next: LD DE, nn (0x11 0x04 0x01)

This is pretty identical to LD SP, nn. Except it loads the value into the DE register. After this line runs it the DE register should be 0x0104.

Next: LD HL, nn (0x21 0x10 0x80)

Already done! After this runs, HL now equals 0x8010.

Next: LD A,(DE) (0x1A)

This one loads the value stored at the address pointed to by DE (currently 0x0104) and stores in the A register. This is the first time we have read data from somewhere in memory, but right now, our memory is pretty empty. The boot sequence is getting ready to read data from the cartridge ROM. The cartridge owns the memory from 0x0000 to 0x3FFF. And 0x0104 lives in there. In fact, if you refer to the cartridge header specifications, you will see that 0x0104 is the start of the Nintendo Logo. During boot, the Gameboy checks to make sure the cartridge has this logo exactly it expects. This is some form of protection.

So, we must now pause the boot process to load the cartridge!