# Game Boy ASM style guide
This style guide aims to formalize a style that most Game Boy ASM programmers agree on, and provide a good baseline for new programmers just starting in this field. (If that's you, welcome! 😄)
To quote the Linux kernel style guide (opens new window):
Coding style is very personal, and I won't force my views on anybody, but this is what goes for anything that I have to be able to maintain, and I'd prefer it for most other things too. Please at least consider the points made here.
Many people follow alternate style guides, and that's fine; but if you're starting to code in ASM, a clean style goes a long way to keep your code organized. Again: you don't have to do everything listed here, but please at least consider the reasons behind each bullet point.
Symbol names can contain letters, numbers, underscores ‘_’, hashes ‘#’ and at signs ‘@’. However, they must begin with either a letter, or an underscore.
However, naming conventions make code easier to read, since they help convey the different semantics between each symbol's name.
Labels use PascalCase:
Labels in RAM (VRAM, SRAM, WRAM, HRAM; you shouldn't be using Echo RAM or OAM) use the same convention but are prefixed with the initial of the RAM they're in, in lowercase:
sSaveFileChecksum. Rationale: to know in which memory type the label is; this is important because VRAM and SRAM have special access precautions and HRAM can (should (must)) be accessed using the
Local labels use camelCase, regardless of memory type:
Macro names use snake_case:
Constants use CAPS_SNAKE:
Exception: constants that are used like labels should follow the label naming conventions. For example, see hardware.inc (opens new window)'s
# Best practices
Avoid hardcoding things. This means:
No magic numbers.
ld a, CURSOR_SPEEDis much more obvious than
ld a, 5. In addition, if you ever change your mind and decide to change the cursor speed, you will only need to do so in one location (
CURSOR_SPEED equ 5→
CURSOR_SPEED equ 4) instead of at every location you're using it, potentially missing some.
Unless absolutely necessary, don't force a
SECTION's bank (opens new window) or address. This puts the burden of managing ROM space on you, instead of offloading the job to RGBLINK, which performs very well in typical cases. Exceptions:
- Your ROM's entry point must be at $0100 (opens new window), however the jump does not have to be to $0150 (example (opens new window)).
rstvectors and interrupt handlers (opens new window) obviously need to be at the corresponding locations.
- RGBDS presently does not allow forcing different sections to be in the same bank (opens new window). If you need to do so, the ideal fix is to merge the two sections together (either by moving the code, or using
SECTION FRAGMENT(opens new window)), but if that option is unavailable, the only alternative is to explicitly declare them with the same
BANKattribute. (In which case it's advisable to add an
assert BANK("Section A") == BANK("Section B")line.)
If you need some alignment, prefer
ALIGN(opens new window) to forcing the address. A typical example is OAM DMA (opens new window); for that, prefer
SECTION "Shadow OAM", WRAM0,ALIGNover e.g.
SECTION "Shadow OAM", WRAM0[$C000].
- Removing, adding, or changing the size of a variable that isn't the last one doesn't require updating every variable after it.
- The size of each variable is obvious (
ds 4) instead of having to be calculated from the addresses.
equallocation is equivalent to hardcoding section addresses (see above), whereas labels are placed automatically by RGBLINK.
- Labels support
BANK()(opens new window) and many cool other features!
- Labels are output in
sym(opens new window) files.
If a file gets too big, you should split it. Files too large are harder to read and navigate. However, the splitting should stay coherent and consistent; having to jump around files constantly is equally as hard to read and navigate.
Unless you're making a 32k ROM (opens new window), put things in
ROMX(opens new window) by default.
ROM0space is precious, and can deplete quickly; and when you run out, it's difficult to move things to ROMX.
However, if you have code in ROM bank A refer to code or data in ROM bank B, then either should probably be moved to ROM0, or both be placed in the same bank (options for that are mentioned further above).
farcall(opens new window) is a good way to make your code really spaghetti (opens new window).
Don't clear RAM at init! Good debugging emulators will warn you when you're reading uninitialized RAM (BGB (opens new window) has one in the option's Exceptions tab, for example), which will let you know that you forgot to initialize a variable. Clearing RAM does not fix most of these bugs, but silences the helpful warnings.
Also, a lot of the time, variables need to get initialized to values other than 0, so clearing RAM is actually counter-productive in these cases.
The difference between these and the "best practices" above is that these are more subjective, but they're still worth talking about here.
Historically, RGBDS has required label definitions to begin at "column 1" (i.e. no whitespace before them on their line). However, later versions (with full support added in 0.5.0) allow indenting labels (opens new window), for example to make loops stand out like in higher-level languages. However, a lot of people don't do this (opens new window), so it's up to you.
Please use the
.s) file extensions, not
.z80. The GB CPU isn't a Z80, so syntax highlighters get it mostly right, but not quite. And it helps spreading the false idea that the GB CPU is a Z80. 😢
Compressing data is useful for several reasons; however, it's not necessary in a lot of cases nowadays, so you may want to only look at it after more high-priority aspects.
Avoid abusing macros. Macros tend to make code opaque and hard to read for people trying to help you, in addition to having side effects and sometimes leading to very inefficient code.
Never let the hardware draw a corrupted frame even if it's just one frame. If it's noticeable by squinting a bit, it must go.
Makefiles are bae (opens new window); they speed up build time by not re-processing what hasn't changed, and they can automate a lot of tedium. Writing a good Makefile can be quite daunting, but gb-boilerplate (opens new window) and gb-starter-kit (opens new window) can help you get started faster.
© 2015-2021 gbdev contributors.