This tutorial will guide you through the steps of programming a ROM which, when run using a GameBoy emulator, will write the words “Hello world” on the screen. The code in this tutorial will all be the assembly language used by the Game Boy processor, which is a slightly modified version of the Zilog Z80. If you don’t know any assembly language, don’t panic! I’ll be going through this almost line by line and explaining everything along the way. I will, however, assume you know what a ROM is, and what an emulator is. Some stuff here might also be a bit difficult to follow if you’ve never done any programming before – and, to be honest, if you haven’t programmed before, then jumping right into assembler like this is probably not the best way to learn.
Before you go on, however, you need a few things first: an editor so you can actually write some code; an assembler and linker which will allow you to make your assembly language program into an actual, working ROM; and finally, an emulator so you can run your program.
To view and edit assembly language, I use Vim, because a) it’s free, and b) it has a few features, like syntax highlighting and line numbering, which make it fairly useful for coding. I also found a Vim syntax highlighting file for Game Boy assembler, which you can download from here. Of course, you don’t need Vim for this – you can use any text editor that can open .asm files, including Notepad.
The compiler and linker I used were the RGBDS tools, which you can download for free from their creator’s website. I’ll be going through how to use these tools to make your ROM later on.
Now that all that’s out the way, we can start coding. The first bit of code we’re going to look at is these two lines:
These two lines import two include files. The first one, gbhw.inc, attaches names to a load of locations in the GameBoy’s memory – meaning that we don’t have to remember exact addresses for all of this stuff. Where we run across one of these names further on, I’ll point it out along with which location that name represents. The second include file, ibmpc1.inc, defines the ASCII font set as a set of 8×8 pixel images, also known as “tiles”. This is really important, as it’s how we can display our text in the first place.
The reason we need our letters as tiles is because of how the Game Boy stores and displays graphics. The GameBoy can store and display two different kinds of images: “tiles”, which are 8×8 images; and “sprites”, which are either 8×8 pixel images, or 8×16 pixel images. The GameBoy can’t display anything other than images – there’s no text output – so any writing we’re storing has to be displayed as images. Because we aren’t using sprites in this program, I’ll ignore them for now and focus on tiles – of course, if you’d rather get right back to coding, then ignore the next bit.
The GameBoy draws its graphics to a “buffer”, which is then displayed on the screen. There are multiple buffers used by the GameBoy, but the one we’ll be worrying about is the background tile buffer. This buffer consists of 256×256 pixels, or 32×32 tiles – remember that tiles are 8 pixels by 8 pixels – arranged in a grid. Because the GameBoy only has a 160×144 pixel screen, this means that only part of the background can be displayed at a time. We won’t be using them much in this tutorial, but the SCROLLX and SCROLLY registers store the co-ordinates of the background buffer which will be displayed in the top left corner of the screen. For example, setting these to 0 will mean that we’ll see the first 160 columns and 144 rows of pixels, while setting SCROLLX to 160 will mean that we see the last 96 columns of pixels. The background buffer is also kind of interesting in that it wraps around. This means that if we set SCROLLX to 150, we won’t get gaps – instead, we’ll see the last 96 columns, and then the first 64 columns on the right-hand side of the screen.
Now, the next few bits of code we’re dealing with are these ten lines, here:
SECTION "Vblank", HOME[$0040]
SECTION "LCDC", HOME[$0048]
SECTION "Timer_Overflow", HOME[$0050]
SECTION "Serial", HOME[$0058]
SECTION "p1thru4", HOME[$0060]
Now, these lines don’t actually do anything in this program. They’re there because of a particularly important concept in GameBoy programming, called interrupts. An interrupt is a signal, sent by another part of the hardware, that stops the program executing normally until it’s performed another task – hence the name “interrupt”. Different hardware generates different interrupts, and the programmer can use these sections to decide how the program handles each kind of interrupt. When an interrupt occurs, the program will jump to the relevant section and execute what’s in there before picking up where it left off in the main program. In this case, we won’t actually be using interrupts at all, so none of these routines do anything – the only instruction in any of them is “ret”, which is used to exit the interrupt handler and go back to where the program was interrupted.
The numbers in square brackets are locations in memory, which we’ve attached labels to. The $ sign before the number is used to mark the number as hexadecimal i.e. instead of going from 0 to 9 before adding another digit, like decimal (base-10) numbers, the numbers go from 0 through F before rolling over to another digit.
Setting up the ROM header
The next bit of code after that is this:
SECTION "start", HOME[$0100]
ROM_HEADER ROM_NOMBC, ROM_SIZE_32KBYTE, RAM_SIZE_0KBYTE
On startup, the first thing an actual GameBoy does is execute a small (256 byte) program, which reads the memory locations from $100 to $14D – an area also known as the ‘ROM header’ – and uses the information there to perform various bits of setup, which I won’t go into too much detail about here because it’s not particularly important for this program. The line beginning with ROM_HEADER up there executes a macro which handles most of the stuff we need in there. The three arguments we’ve put in to that macro, ROM_NOMBC, ROM_SIZE_32KBYTE, and RAM_SIZE_0KBYTE, tell the GameBoy that our ROM is just a simple, 32 kilobyte ROM – 32 kilobytes is the minimum size for a GameBoy ROM.
The next thing it does is jump to memory location $100, and executes the code there. In this case, it’s the code you see up there – which, incidentally, are also the first assembler instructions we’ve seen so far. If you’ve ever seen any assembler language before, the syntax should be pretty familiar. There is only one instruction on each line, and each instruction consists of one or more parts. The first part, the ‘mnemonic’, tells the GameBoy what kind of instruction it’s executing, while if there are more parts then those are the ‘arguments’ or ‘parameters, which are bits of data the instruction uses. In other words, the mnemonic tells the GameBoy what to do, and the parameters tell the GameBoy what to do stuff to.
The first instruction, nop, is unfortunately not particularly interesting. It’s a ‘no-op’ instruction: all the GameBoy does is wait for 4 CPU cycles, before moving on to the next thing. Th next one, jp begin, is a bit more exciting. jp is a ‘jump’ instruction – when the program runs across it, it skips straight to the address in memory given as its argument. In some cases, this will be a number – here, however, we’re using a label, ‘begin’, to tell it where to go.
‘begin’ is the address in memory where our code actually starts:
‘di’ stands for Disable Interrupts – because they’re not being used in this program, I’m turning them off and keeping them off.
Stacks ‘n stuff
ld sp, $ffff
This instruction is one of the more important bits of setup code we’re running. What it does is set the stack pointer to $ffff, which is one more than the highest memory address the GameBoy can access. The ‘stack’ is the bit of memory the GameBoy uses to store things when it’s running, including arguments, variables, and return addresses – this is something it has in common with most processors. There are two things the GameBoy can do with the stack – it can ‘push’ a piece of data onto it, and it can ‘pop’ the last bit of data added to the stack and use it. The stack pointer is used to keep track of where the ‘top’ of the stack is – this is the bit of memory that stores the last item to be pushed onto the stack and not subsequently popped.
Setting the stack pointer higher than where the GameBoy can access might seem counter-intuitive, but it’s because of how the GameBoy pushes values onto the stack. When a GameBoy pushes a piece of data onto the stack, it performs two operations:
- It subtracts 1 from the stack pointer.
- It writes the data being pushed to the memory location that the stack pointer is pointing to.
For example, if we were to push the value 5 onto the stack, we would first move the stack pointer so that it’s pointing at memory location $FFFE (1 less than $FFFF), and then write 5 to that memory location.
The stuff above describes what that line of code is doing, but not how it’s doing it. To explain that, let’s take a look at the GameBoy’s CPU. Like any CPU, a GameBoy CPU has several ‘registers’, which are parts of the processor that can store a value so it can be accessed quickly later on. The Gameboy has eight 8-bit registers, which can store values up to 255, called A,B,C,D,E,F,H and L. There are also two 16-bit registers, called SP and PC. SP stands for ‘stack pointer’, and is where the address that the stack pointer is pointing to is actually stored. The instruction ‘ld’, used up there, stores its second argument in its first one – in this case, storing $FFFF in the SP register.
Setting up the screen
But enough about stacks – the next bits of code are a bit more interesting, and useful for us at the moment.
ld a, %11100100
ld [rBGP], a
This stores the value 11100100, expressed in binary numbers, in register A, and then stores the contents of A in memory location $ff47 (which [rBGP] is standing in for here). $ff47 is the memory location that stores the background tile palette, which are the shades of grey used by the GameBoy when it draws its graphics. The value 11100100 can be split into 4 parts (11, 10, 01, and 00), representing the colours black, dark grey, light grey, and white. Essentially, taken together, these 2 lines set up the palette so that any tiles drawn will use these 4 colours.
The next set of instructions sets the scroll registers (mentioned above) to 0, meaning that the screen will show everything starting at the top-left corner of the buffer.
ld a, 0
ld [rSCX], a
ld [rSCY], a
The next thing to do after that is to turn the screen off, and then copy our data into the video RAM. Turning the screen off is a bit of an awkward procedure – what we actually need to do is check if the screen’s already on, tell it to turn off, and then wait for a bit until it actually finishes turning itself off. That sounds like a lot (or a few lines, at least) of work to do something we can sum up pretty easily in a few words, so this seems like the perfect time to introduce something new: subroutines. If you’ve programmed before, you’ll have seen these, although you might have called them “methods” or “modules”. They’re just sets of related statements, which have all been grouped together and given a name:
jr nz, TurnLCDOff
ldh [$41], a
That goes down at the bottom, after all your other code. Then, in the main bit of the program, you can ‘call’ your newly minted subroutine:
The ‘call’ command basically means ‘find this subroutine, and do everything in it’. The ‘ret’ instruction in our subroutine up there is how we get back out the routine once we’ve finished doing everything in it. This works a lot like a method call in a language like C# or C++ – it’s a bit more complicated in those languages (for reasons I won’t go into here), but if you look at the assembler code your compiler generates when you call a method in C++, there will be a ‘call’ statement there.
On to the next thing: loading our letters into tile memory. To do this, we’re using a subroutine from that ‘memory.asm’ file we included earlier, called mem_CopyMono. This uses three of our processor registers: HL, which is where the routine pulls data from; DE, which is where the routine copies data to; and BC, which stores how many bytes we’re copying. So, first, we make sure that wherever we’re pulling tile data from actually has the tiles in it, like so:
ld hl, TileData
Next, we set up DE so that it’s storing the location in RAM where tile memory starts on a GameBoy.
ld de, _VRAM
Finally, we store the value (8 * 256) in bc, and call mem_CopyMono. This is because the ASCII character set we’re using has 256 characters, with 8 bytes each – and we want to make sure we’re copying all of them in.
ld bc, 8*256
Now that we’re done messing around with tile memory, it’s safe to turn the screen on again. So, let’s do that:
ld a, LCDCF_ON|LCDCF_BG8000|LCDCF_BG9800|LCDCF_BGON|LCDCF_OBJ16|LCDCF_OBJOFF
ld [rLCDC], a
Now, all we need to do is clear our screen. We can do that by setting every bit of the screen to a blank space, which is why we’re loading 32 into the A register here (32 is the ASCII space character in decimal).
ld a, 32
ld hl, _SCRN0
ld bc, SCRN_VX_B * SCRN_VY_B
And, now, finally, the bit you’ve all been waiting for: writing to the screen. To set up our message, put this at the end of your code:
DB "Hello World!"
And then put this in the main bit:
ld hl, Title
ld de, _SCRN0+3+(SCRN_VY_B*7)
ld bc, TitleEnd-Title
mem_CopyVRAM works a lot like mem_CopyMono above, and it’s from the same header file. What we’ve just done is told it to load the characters in “Hello World!” into video RAM, and set them up at position _SCRN0+3+(SCRN_VY_B*7) – aka “3 columns across, 7 rows down”.
Now, there’s just one final thing to do:
This just makes us stay in an infinite loop until the user closes the window. It does this with the combination of a label, ‘wait:’, and the instruction ‘jr wait’, which just means “Jump back to ‘wait:'”. And, with that in there, you should be able to see the output.
How to make a ROM
That’s the code written – now, we can make it into a ROM. We do this using RGBDS tools from the beginning. To get a working ROM from your assembler source file, simply store the RGBDS tools in the same folder as your assembly code. Then, open up a command prompt, and type in the following:
rgbasm -o hello.o hello.asm
rgblink -o hello.gb hello.o
rgbfix -v -p 0 hello.gb
(just replace “hello.” with whatever you called your file.)
It should then compile and link, and your shiny new ROM will appear in the same folder, ready to be run in whatever emulator you like (provided that it runs GameBoy games, anyway).
And, we’re done here! I hoped you enjoyed reading this. It’s a cliché, but I had a lot of fun writing it. I’ve also put the full source code up on GitHub, so by all means take a look if you get stuck. A lot of it was also borrowed from the sample code John Harrison wrote for a course in GameBoy programming at Wichita State University – you can see more about that here.
Everything You Always Wanted To Know About GameBoy:
A complete GameBoy specification, including info about important memory locations, the CPU’s design, and definitions for the complete Z80 instruction set. Also available in slightly modified form as a PDF.