codeslinger.co.uk

Gameboy - Hardware.

General Info:

All of the info below was taken straight from nocashs pan-docs.

CPU: 8-bit (similar to the z80 processor)
Clock-Speed: 4.194304MHz
Screen Resolution: 160x144
Vertical Sync: 59.73Hz
Internal Memory size: 0x10000

The CPU is very similar to the z80 processor (there are a couple of added instructions and not all flags are used). All the instructions can be found on the Gameboy pdf file im hosting aswell as an explanation for each and the amount of clock cycles the instruction takes to execute.
I have explained how to emulate the clock speed for accurate timing aswell as the vertical refresh rate in the Getting Started section of this site. The screen resolution is quite interesting to emulate. The way I did it was to use a 3D array. The first part is the screen x axis. The second is the y axis and the third is the colour (three elements for red, green and blue). This gives the following declaration:

BYTE m_ScreenData[160][144][3] ;

So if I wanted to set the very middle pixel of the screen to red I would do the following:

m_ScreenData[160/2][144/2][0] = 0xFF ;
m_ScreenData[160/2][144/2][1] = 0 ;
m_ScreenData[160/2][144/2][2] = 0 ;

If you dont understand why the values 0xFF,0,0 gives red then youu should google about RGB hex colours. The cpu is 8-bit and the memory is 0x10000 bytes in size. So this will give the declaration of the main memory:

BYTE m_Rom[0x10000] ;

I will discuss the internal memory in much more detail in the section called Memory Control and Map

The Registers:

The gameboy has 8 registers named A,B,C,D,E,F,H,L each are 8-Bits in size. However these registers are often paired to form 4 16-Bit registers. The pairings are AF, BC, DE, HL. AF is less frequently used then the others because A is the accumulator and F is the flag register so working with them as a pair is less frequent than the others. HL is used very frequently mainly for referring to game memory.

There are many ways to emulate the registers in code. One of the ways is to represent each of the register pairs with a word variable and supply a function for retrieving the hi and lo bytes for the individual registers. Another way is the opposite and have 8 byte variables for each register and then supply a function to combine these to form a word to represent the pairings. However I prefer to use unions to emulate the registers like so:

union Register
{
  WORD reg ;
  struct
  {
   BYTE lo ;
   BYTE hi ;
  };
};

Register m_RegisterAF ;
Register m_RegisterBC ;
Register m_RegisterDE ;
Register m_RegisterHL ;

Each field in a union occupies the same region in memory. So if you did m_RegisterAF.reg = 0xAABB then m_RegisterAF.hi would equal 0xAA and m_RegisterAF.lo would equal 0xBB. You could then do m_RegisterAF.lo = 0xCC and m_RegisterAF.hi would still be 0xAA but m_RegisterAF.reg would be 0xAACC. This is perfect for representing the registers because it gives easy access to the individual registers (with hi and lo fields) and also as a pair (with reg field). You may be wondering why BYTE lo is declared before BYTE hi. This is due to the endianess.

The Flags:

As previously stated the F register doubles as the flag register. There are 4 flags the cpu uses (opposed to the real z80 which I believe has 8) called "the carry flag", "the half carry flag", "the subtract flag" and "the zero flag". The carry flag gets set after a mathematical instruction which resulted in a value that cannot fit within the range of the data type. For example if the instruction was to set register A to the value of 250 + 7 then A would be set to 257. However as register A is an unsigned byte it can only store values between 0 and 255. Which means A would wrap around on itself and become 1 and the carry flag would be set to show the result overflowed.
The half carry flag is similar to the carry flag except it is set when a mathematical instruction overflow from the lower nibble to the upper nibble (or with 16 bit operations the lower byte to the upper byte). This flag is set by many instructions but is only used with the DAA instruction
The zero flag is set when a mathematical instruction results to 0. I.E. 4-4.
The Subtract flag is set when a mathematical operation is a subtraction. This is also only used with the DAA instrction.

I use the following definitions when referring to the flags:

#define FLAG_Z 7
#define FLAG_N 6
#define FLAG_H 5
#define FLAG_C 4

Each value is the bit in the F register for that flag.

Program Counter and Stack Pointer:

The program counter is the address of the next opcode in memory to execute. Some opcodes require the next one or two bytes in memory to use as arguments. When this is the case the program counter needs to skip these to point to the next actual opcode. The gameboy memory is 0x10000 bytes in size which means it has a range of 0 to 0xFFFF. This is also the same range as an unsigned word data type which is perfect for representing the program counter. The program counter will change its value from jump instructions along with call instructions and the execution of interupts. The program counter is initialized to 0x100

The stack is a place in memory where data gets loaded in to. The last piece of data put onto the stack will be the first retrieved from the stack. The stack pointer points to the very top of the stack in memory, in other words the area of memory where the next piece of data to be added to the stack would occupy. Like the program counter the stack pointer points to address is memory so a WORD data type would also represent the stack pointer well. However some of the opcodes use the hi and lo bytes of the stack pointer so it would be easier to emulate the stack pointer the same way we do the registers. The stack pointer is initialized to 0xFFFE.

The above information gives us the following declarations:

WORD m_ProgramCouner ;
Regiser m_StackPointer

Initializing:

The following information is also found in nocashs-pan-docs When the emulator starts it must set the state of the registers, stackpointer, program counter and the special rom registers to the following:

m_ProgramCounter=0x100 ;
m_RegisterAF=0x01B0;
m_RegisterBC=0x0013;
m_RegisterDE=0x00D8;
m_RegisterHL=0x014D;
m_StackPointer=0xFFFE;
m_Rom[0xFF05] = 0x00 ;
m_Rom[0xFF06] = 0x00 ;
m_Rom[0xFF07] = 0x00 ;
m_Rom[0xFF10] = 0x80 ;
m_Rom[0xFF11] = 0xBF ;
m_Rom[0xFF12] = 0xF3 ;
m_Rom[0xFF14] = 0xBF ;
m_Rom[0xFF16] = 0x3F ;
m_Rom[0xFF17] = 0x00 ;
m_Rom[0xFF19] = 0xBF ;
m_Rom[0xFF1A] = 0x7F ;
m_Rom[0xFF1B] = 0xFF ;
m_Rom[0xFF1C] = 0x9F ;
m_Rom[0xFF1E] = 0xBF ;
m_Rom[0xFF20] = 0xFF ;
m_Rom[0xFF21] = 0x00 ;
m_Rom[0xFF22] = 0x00 ;
m_Rom[0xFF23] = 0xBF ;
m_Rom[0xFF24] = 0x77 ;
m_Rom[0xFF25] = 0xF3 ;
m_Rom[0xFF26] = 0xF1 ;
m_Rom[0xFF40] = 0x91 ;
m_Rom[0xFF42] = 0x00 ;
m_Rom[0xFF43] = 0x00 ;
m_Rom[0xFF45] = 0x00 ;
m_Rom[0xFF47] = 0xFC ;
m_Rom[0xFF48] = 0xFF ;
m_Rom[0xFF49] = 0xFF ;
m_Rom[0xFF4A] = 0x00 ;
m_Rom[0xFF4B] = 0x00 ;
m_Rom[0xFFFF] = 0x00 ;

The other m_Rom elements are set to random values inside the original gameboy but I personally set them all to 0.