Coding on Gameboy - TypeDefinition/NautiBuoy GitHub Wiki
Registers and instructions
Registers are a set of locations in the CPU to store temporary data. It prevents frequent access of memory since accessing memory is slower compared to accessing data on the CPU. We use the registers to help perform arithmetic or logical operations. If we want to 'edit' data on memory, for example, Player's health, we need to get the data from memory, store it on a register, perform the operations, and then write the data from the register back to memory.
There are 10 registers. A, B, C, D, E, F, H, L, SP (stack pointer) and PC (program counter).
General-purpose registers
- A, B, C, D, E, F, H, L, are general-purpose registers.
- Register A and F are a little special. We'll go into that later.
- They are 8 bits each. Thus, can only store value from values 0 - 255.
- We are allowed to combine the registers to form a 16-bit value instead. The pairs are AF, BC, DE, HL. This is great for storing addresses since addresses in Gameboy are 16-bits.
ld b, $AB ; b = 0xAB
ld c, $01 ; c = 0x01
; therefore, bc = 0xAB01
Register a
- The Gameboy has an accumulator-type architecture.
- Accumulator-type architecture is where there is a special register, the accumulator register, in this case, it's register a, that store results of operations.
- Thus, most arithmetic and logic operations such as add, subtract, AND, OR, compare, even some load instructions require register a.
sub a, b ; subtract the value in a by the value in b, this is allowed
sub b ; this is also allowed, automatically assumed we are doing a - b
sub b, e ; this is not allowed
Register f and conditions
- Do not write to this register.
- Register f stores flags that are updated after most instruction (some instructions like ld do not set the flags).
- The flags stored are the zero flag, subtract flag, half carry flag and carry flag.
- The zero flag (bit 7 of register f), is set when the previous operation gives a 0. For example, subtracting two values and it gives 0, will make the zero flag true.
- The subtract flag (bit 6 of register f), is set when the last instruction performed was a subtraction
- Half carry flag (bit 5 of register f), is set if a carry happened from the lower nibble (lower half) of the last math operation
- Carry flag (bit 4 of register f), is set if a carry occurred from the last instruction.
- The flags are really useful for the conditional jump instructions
- More info on the flags: https://eldred.fr/gb-asm-tutorial/flags.html
ld a, 5 ; a = 5
ld c, 5 ; c = 5
sub a, c ; a - c = 0, zero flag is set
jr z, .itIsZero ; if the operation before this instruction gives 0, jump to the instructions at label .itIsZero.
.itIsZero
....
Stack pointer (SP) register and the Stack
- The Gameboy also has a stack, to help store temporary 16-bit values.
- Stack grows downward in RAM memory as more info is put in.
- The stack is LIFO (last-in-first-out), whatever you push into the stack last, will be the first to be popped out.
- Instructions: pop, ret, reti, takes info out of the stack
- Instructions: call for functions, and interrupts automatically push a return address onto the stack
- Instruction push and pop must be used with register pairs as arguments.
- The stack pointer keeps track of where the top of the stack is by storing an address to it. Every time you add something to the stack, the stack pointer will automatically decrement.
- The stack pointer is default initialised to $FFFE, but we should initialise it to the top of RAM since it grows downward in RAM memory. Thus, best to initialised it at $E000.
push bc ; put the values in bc onto the stack, let say original bc = 0xABCD
ld b, $56 ; So now bc = 0x56CD
pop bc ; bc is back to the value you pushed it, bc = 0xABCD
Program counter register (PC)
- Holds the address of the next instruction to be executed.
- We can influence the value in PC register through jump instructions.
- In the Gameboy, when powered up, the PC is initialized to $0100 and the instruction found here will be executed. Might want to put a jump instruction at $0100 to your actual game codes.
Instructions
- Different instructions will have different sizes
- The instruction list and info such as sizes, flags set eg.
- Instruction set table
Jump instructions
- There are two types of jump instructions, jp and jr.
- jp allows you to jump to any instruction in memory
- jr is relative to PC, smaller and faster in the Gameboy as compared to Z80, but it is limited. It can only jump 128b backwards and 127b forwards.
Great references for registers:
- https://eldred.fr/gb-asm-tutorial/registers.html
- http://marc.rawer.de/Gameboy/Docs/GBCPUman.pdf
- https://www.youtube.com/watch?v=RZUDEaLa5Nw&t=407s&ab_channel=media.ccc.demedia.ccc.de
Structuring your codes
There's not really a fixed way to structure your codes. You can separate your codes into files or put whichever codes into functions as you see fit.
Structuring codes between Banks
- ROM0 ($0000 - $3FFF) is always accessible.
- ROMX is where the banks are ($4000 - $7FFF). The number of banks you have depends on how much you need.
- Each ROM is 16kb
- Just code as per normal, and if you run out of space can try moving to certain banks.
- Be careful, let say you want to run a code in ROMX bank 2 at location $4111, if you do not specify which bank, it'll just run the code at $4111 in whichever bank was previously set (like bank 1).
- Do not switch between banks when executing codes in ROMX, do it in ROM0.
- To switch banks, we init the bank value to [$2000]
LD [$2000], 3 ; switch to bank 3
Labels
- Labels are just memory addresses so the instruction at the label can be easily located. For example, if you want to call a function and have to 'jump' to the instruction. Or another example would be for if statements, where sometimes you want to 'skip' certain instructions when a condition is met.
- Labels are great in helping to structure your codes.
- More info on labels: https://eldred.fr/gb-asm-tutorial/syntax.html
Using data from another file and linking them
- Let say you have two different files, file A that has your gameplay update loop, and file B which has your logic and codes for the player. You have a UpdatePlayer function in file B, but how do you call UpdatePlayer in file A?
- One way is to do an INCLUDE "fileB.asm" in file A, so you are able to call UpdatePlayer
- Another way, is to add
::
to the label UpdatePlayer in fileB. This allows you to export the label so it can be used in other files. - Normal label:
Start:
- Exported label: 'Start::', this can be used in other files.
- When you are linking the files, just do an additional
-n symbols.map
, this will generate a symbol file so the other files will be able to use the exported labels.rgblink -o hello.gb -n symbols.map fileA.o fileB.o