© 1999 Dermot Mac Flannchaidh. Game Boy is TM Nintendo Co., Ltd. This document is not licensed or endorsed by Nintendo. This document may be freely distributed as long as its text remain unchanged. Conversion of this document to varying document formats (i.e. plain text, HTML, PDF, WordPerfect, WinHelp, etc.) is permissible as long as these rules are followed. This document may not be sold, held for incentive, or otherwise solicited (with access restricted) in any way. No authors or distributors of this document will be held liable for any possible negative effects from the information given by this document, including but not limited to paper cut. All rights reserved. If there are any errors or anything critical missing from this document, please email Dermot with the details.
From this moment on, it is assumed that you understand the basic internal architecture of Game Boy, including machine instructions, memory management, bank switching, etc. Those subjects would require another document to fully explain. This document also recognizes Pascal Felber's Game Boy Development Kit as a de facto standard in Game Boy C development, because of its wide use.Programming large programs for Game Boy is not easy or trivial. Because of GBZ80's limited 16-bit memory addressing, Game Boy ROMs are split up into individual 16KByte banks, and it can prove frustrating, in either C or assembly, to manage all the required bank switches to keep a program executing and accessing memory smoothly. This specification describes a way, when properly implemented into a C compiler for Game Boy, that all available Game Boy memory in every bank can be seamlessly accessed using 24-bit far pointers. This system is not intuitive to Game Boy's internal architecture, so it must be managed on the software level using maintenance code all residing in ROM bank zero of each Game Boy program that uses it. Various subroutines provide services for reading or storing memory in a far memory address, calling and returning from a far function, and the conversion of pointer variables between near and far types. Using this mechanism, the amount of accessible memory in a Game Boy program expands from 64KBytes to 16MBytes, and virtually eliminates a programmer's needs to constantly handle bank switching manually. This specification is not perfect because it will only support Game Boy ROMs up to 8MBytes in size (though this is plenty for the majority of Game Boy programs), and far memory maintenance functions add overhead to a programmer's routine. However, it is the challenge of a good implementor to make them as small and fast as possible without sacrificing the benefits.
A future specification could specify a different mechanism for 32-bit far pointers, but the author of this document decided at the time that using yet an additional byte is overkill because its only benefit would be allowing the access of all available ROM data, while sacrificing more speed and memory performance (i.e. more overhead).
While conventional memory addresses are two bytes (16 bits), far memory addresses are three bytes (24 bits). That extra most significant byte is used to indicate where to access the data. All implementations following this specification must use this.Return to Table of Contents
Far Address Service Handling $0xxxxx .. $7xxxxx ROM Banks When this address range is encountered, the data handled is located in the ROM bank whose number is equal to (address >> 14)
. The byte position in the bank handled is equal to(address & 0x3FFFU)
. However, if the address range is between $000000 and $003FFF, ROM bank zero is handled, even though it is not a switchable bank. This implementation assumes that there are 512 ROM banks available, though this may not always be true.$8xxxxx .. $9xxxxx SRAM Banks When this address range is encountered, the data handled is located in the SRAM bank whose number is equal to (address >> 13)
. The byte position in the bank handled is equal to(address & 0x1FFFU)
. This implementation assumes that there are 256 ROM banks available, though this may not always be true.$Cxxxxx GBC RAM Banks When this address range is encountered, the data handled is located in the GBC internal RAM bank whose number is equal to (address >> 12)
. The byte position in the bank handled is equal to(address & 0xFFFU)
. However, if the address range is between $C00000 and $C00FFF, internal RAM bank zero is handled, even though it is not a switchable bank. This implementation assumes that there are 256 ROM banks available, though as it is, Game Boy Color only has 8. This region of far memory is only useful to Game Boy Color programs, since previous Game Boy models didn't have switchable internal RAM.$FFxxxx Conventional Memory When this address range is encountered, the far address is truncated to a conventional address (discarding the top FFh byte), and conventional memory is handled. $Axxxxx .. $Bxxxxx
$D0xxxx .. $FExxxxReserved (Unused) All write attempts should do nothing, and all read attempts should return FFh.
Do keep track of banks, especially when a far function calls another far function, both the stack and a few addresses in HIRAM are used to remember this information. $FFFD and $FFFE are used to store the current ROM bank as a 16-bit unsigned integer, $FFFC is used to store the current SRAM bank as an 8-bit unsigned integer, and $FFFB is used to store the current internal RAM bank as an 8-bit unsigned integer. But since Game Boy startup defines that theReturn to Table of ContentsSP
register begins at $FFFE, it must be moved at setup to a location where it can't corrupt this data. The three global variables are made accessible to C program code through the following preprocessor definitions:
#define CURRENT_ROM_BANK (*((UWORD*)0xFFFDU)) #define CURRENT_SRAM_BANK (*((UBYTE*)0xFFFCU)) #define CURRENT_IRAM_BANK (*((UBYTE*)0xFFFBU))In addition, the HIRAM addresses, $FFF7, $FFF8, $FFF9 and $FFFA are reserved for temporary data storage in maintenance functions.
In a C program, when the program attempts to type-cast a conventional pointer to a far pointer, it goes through a process to ensure that it will always point to the correct memory or bank address from the moment of its conversion. All implementations following this specification must use this.Return to Table of Contents
Conventional Address Conversion Algorithim $4xxx .. $7xxx ((CURRENT_ROM_BANK & 0x1FF) << 14) | (address & 0x3FFF)
$Axxx .. $Bxxx 0x800000 | (CURRENT_SRAM_BANK << 13) | (address - & 0x1FFF)
$Dxxx
$F0xx .. $FDxx0xC00000 | (CURRENT_IRAM_BANK << 12) | (address & 0xFFF)
$0xxx .. $3xxx
$8xxx .. $9xxx
$Cxxx
$Exxx
$FExx .. $FFxx0xFF0000 | address
Conversion from far address to conventional address is almost never accurate, but implementation must nonetheless exist. All implementations following this specification must use this.
Far Address Conversion Algorithim $000xxx .. $003xxx address
$004xxx .. $7xxxxx 0x4000 | (address & 0x3FFF)
$8xxxxx .. $9xxxxx 0xA000 | (address & 0x1FFF)
$C00xxx 0xC000 | (address & 0xFFF)
$C01xxx .. $CFFxxx 0xD000 | (address & 0xFFF)
$Axxxxx .. $Bxxxxx
$Dxxxxx .. $Fxxxxxaddress & 0xFFFF
This is fairly straightforward, and applies for both reading and writing memory. Whatever banks need to be switched are switched, and the memory is accessed. The banks are then switched back to their previous states (using the data stored the appropriate CURRENT_<whatever>_BANK
), and the operation is concluded.
Return to Table of Contents
This is much more complex than simple memory access, because under the hood, calling a far function actually calls a maintenance function, which manipulates the stack and banks, and calls the actual function. When that function finally returns, the stack and banks are restored to their previous state, and the maintenance function returns.Return to Table of ContentsFirst, it must be noted that the
BC
register should not be used through this procedure, to conform to GBDK function calling convention. Also, the far address of the function being called is pushed invisibly in the compiled code, after any conventional arguments pushed onto the stack. Anyway, to begin with, which of the banks being switched (if any) is determined, and a two-byte word is pushed onto the stack, encoded with the pre-switch value of which bank of which type is about to be switched. That two-byte word follows this format:
Value Condition 00xxh .. 01xxh Indicates the appropriate ROM bank number. 10xxh This value (truncated to one byte) indicates the appropriate SRAM bank number. 11xxh This value (truncated to one byte) indicates the appropriate internal RAM bank number. Misc. No banks are being switched. It is recommended that this maintenance function use the value FFFFh if no banks are to be switched. After that, any necessary banks are switched, and any necessary changes are made to the appropriate
CURRENT_<whatever>_BANK
variable. When this procedure is prepared to call the actual function, it must ensure that theSP
is aligned directly on top of that last two-byte word that was just pushed; this is to ensure that when the actual function executes, it can know exactly where to access the stacked argument variables, which start 9 bytes down the stack, rather than the 2 bytes down that conventional functions use in compilers like GBDK. When the actual function finally returns, the values of registersHL
andDE
are saved in the reserved temporary data addresses (see 4. Current Bank Data), because they may contain function return values. One two-byte word is then popped (that one with the switched bank information), the proper bank is switched back if necessary, and the properCURRENT_<whatever>_BANK
value is restored if necessary, and finally, the saved values forHL
andDE
are restored. This maintenance function can now return. *whew!* :)
Each far addressing maintenance function needs to conform to certain conventions, so that inline assembly programmers can access them from any compiler that implements this specification.Return to Table of Contents
Function ID Arguments Return Description _far_read
3 bytes - far address stored in E
Reads a byte from far memory. _far_write
3 bytes - far address
1 byte - byte valuevoid Stores a byte to far memory. _cast_near_far
2 bytes - conventional address stored in LDE
Converts a conventional address to a far address. _cast_far_near
3 bytes - far address stored in DE
Converts a far address to a conventional address. _far_call
first arguments vary
3 bytes - far function addressstored in HLDE
Calls a far function, and returns its value.
These additions aren't too complicated.Return to Table of Contents
- For a pointer variable to be far, the keyword
far
must be placed between the variable type and the asterisk. Therefore, as an example,void*
changes tovoid far*
. This should look familiar to 16-bit DOS programmers and the like.
- When typecasting between conventional pointer types and far pointer types, a simple C typecast operator is used.
- Far functions can only properly be accessed or called as far addresses. Therefore, the modifier
FARCALL
is placed with each far function, to indicate its status as a far function. Example:void FARCALL MyFarFunction(MyArgumentType* whatever); /* Now that my function is a FARCALL, its benefits should be mostly seamless! */
- Because far addressing opens a whole new realm of memory management, APIs should be provided for far memory and string management. These must be implemented:
Prototype Header Description BYTE FARCALL _fatoi(char far* fs);
gbfarlib.h Parses a signed 8-bit integer from a far string. WORD FARCALL _fatol(char far* fs);
gbfarlib.h Parses a signed 16-bit integer from a far string. char far* FARCALL _fitoa(BYTE i);
gbfarlib.h Allocates a new far string based on the decimal value of the provided signed 8-bit integer. char far* FARCALL _fltoa(WORD i);
gbfarlib.h Allocates a new far string based on the decimal value of the provided signed 16-bit integer. void far* FARCALL farmalloc(size_t s);
gbfarlib.h Allocates a new far pointer with the specified size. If not enough memory is available, this function returns NULL. If Game Boy Color is detected, this function can take advantage of extended Game Boy Color internal RAM banks. void far* FARCALL farsmalloc(size_t s);
gbfarlib.h Allocates a new far pointer with the specified size, inside SRAM. If there is not enough free SRAM, or SRAM is disabled, this function returns NULL. Allocation inside SRAM begins at SRAM offset $100. void far* FARCALL farrealloc(void far* fp, size_t s);
gbfarlib.h Adjusts the size of a far pointer. If there is not enough free memory in the same region to resize this pointer, a new far pointer in internal RAM is returned. If there is not enough memory for even this, NULL is returned. If Game Boy Color is detected, this function can take advantage of extended Game Boy Color internal RAM banks. void far* FARCALL farsrealloc(void far* fp, size_t s);
gbfarlib.h Adjusts the size of a far pointer. If there is not enough free memory in the same region to resize this pointer, a new far pointer in SRAM is returned. If there is not enough memory for even this, NULL is returned. If SRAM is disabled, NULL is returned no matter what. BYTE FARCALL farfree(void far* fp);
gbfarlib.h Attempts to free an allocated pointer. Returns 0 on success, -1 if already freed, and -2 if the region was never allocated. void far* FARCALL _fmemset(void far* fp, UBYTE b, size_t n);
gbfarstr.h Sets n
number of consecutive far memory bytes, starting atfp
, to the value ofb
. Returns the same pointer passed to this function. This function is not expected to be nearly as fast asmemset
.void far* FARCALL _fmemcpy(void far* dest, void far* src, size_t n);
gbfarstr.h Copies n
number of consecutive far memory bytes, from their source positions starting at far pointersrc
to their destination positions starting at far pointerdest
. Returns the destination pointer passed to this function. This function is not expected to bear nearly as fast asmemcpy
.char far* FARCALL _fstrcat(char far* fs, char far* toAppend);
gbfarstr.h Appends the string value of toAppend
to the end offs
, returningfs
.BYTE FARCALL _fstrcmp(char far* fs1, char far* fs2);
gbfarstr.h Compares two far strings, returning their lexicographical rank relative to each other. Returns 0 if strings are equal. char far* FARCALL _fstrcpy(char far* dest, char far* src);
gbfarstr.h Copies the string value of src
todest
, returningdest
.UBYTE FARCALL _fstrlen(char far* fs);
gbfarstr.h Returns the string length of fs
. This function produces an inaccurate (overflowed) result if the string is longer than 255 characters.char far* FARCALL _fstrncat(char far* fs, char far* toAppend, UBYTE n);
gbfarstr.h Appends the string value of toAppend
to the end offs
until eithertoAppend
is finished orn
chars are reached, returningfs
.BYTE FARCALL _fstrncmp(char far* fs1, char far* fs2, UBYTE n);
gbfarstr.h Compares two far strings until either the end of the shorter string or n
chars are reached, returning their lexicographical rank relative to each other. Returns 0 if strings are equal under the provided circumstances.UBYTE FARCALL _fstrncpy(char far* dest, char far* src, UBYTE n);
gbfarstr.h Copies the string value of src
todest
until either the length of the shorter string orn
chars are reached, returningdest
.
Return to Table of Contents
- 29 November 1999
- Wrote version 1.0 of this document.
I'd like to thank all these people for their assistance in my studies. :)Return to Table of Contents
- Adam Rippon, for always discussing Game Boy babble with me.
- Stefan Knorr, whose Graal ports to Java, Windows and Game Boy Color really made me think and ask questions.
- Pascal Felber, for preparing the Game Boy Development Kit that most of us use!
- Martin Korth, for programming NO$GMB and its full debugging environment.
- Game Boy Development Mailing List, for the endless stream of information and insight. :)
- You, for taking the interest to read this document. :P