Dermot's
Game Boy C
24-bit Far Memory Addressing
Specification

Author: Dermot Mac Flannchaidh (sproatne@utah-inter.net)
Version: 1.0
Date: 29 November 1999

© 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.


1. Table of Contents

  1. Table of Contents
  2. Introduction
  3. Far Memory Map
  4. Current Bank Data
  5. Converting Between Conventional and Far
  6. Dereferencing Far Pointers
  7. Far Function Calling Conventions
  8. Maintenance Function Specifications
  9. Far Addressing Additions to Game Boy C
  10. Changes
  11. Thanks


2. Introduction

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).

Return to Table of Contents


3. Far Memory Map

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.

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 .. $FExxxx
Reserved (Unused) All write attempts should do nothing, and all read attempts should return FFh.

Return to Table of Contents


4. Current Bank Data

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 the SP 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.

Return to Table of Contents


5. Converting Between Conventional and Far

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.

Conventional Address Conversion Algorithim
$4xxx .. $7xxx ((CURRENT_ROM_BANK & 0x1FF) << 14) | (address & 0x3FFF)
$Axxx .. $Bxxx 0x800000 | (CURRENT_SRAM_BANK << 13) | (address - & 0x1FFF)
$Dxxx
$F0xx .. $FDxx
0xC00000 | (CURRENT_IRAM_BANK << 12) | (address & 0xFFF)
$0xxx .. $3xxx
$8xxx .. $9xxx
$Cxxx
$Exxx
$FExx .. $FFxx
0xFF0000 | 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 .. $Fxxxxx
address & 0xFFFF

Return to Table of Contents


6. Dereferencing Far Pointers

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


7. Far Function Calling Conventions

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.

First, 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 the SP 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 registers HL and DE 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 proper CURRENT_<whatever>_BANK value is restored if necessary, and finally, the saved values for HL and DE are restored. This maintenance function can now return. *whew!* :)

Return to Table of Contents


8. Maintenance Function Specifications

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.

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 value
void 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 address
stored in HLDE Calls a far function, and returns its value.

Return to Table of Contents


9. Far Addressing Additions to Game Boy C

These additions aren't too complicated.

Return to Table of Contents


10. Changes

29 November 1999
Wrote version 1.0 of this document.
Return to Table of Contents


11. Thanks

I'd like to thank all these people for their assistance in my studies. :)

Return to Table of Contents