ROM and RAM bank switching is controlled by a memory bank controller present on the cartridge.
By writing values to areas of read-only memory, a game or program is able to specify which ROM banks to access when read operations are performed.
The simplest cartridges simply contained ROM and had only had 32 KBytes of space.
It is mapped directly to $0000-$7FFF
.
There was no MBC on these types of cartridges because the entire game would be able to fit on the cartridge.
This types of cartridge could also contain up to 8KB of RAM, mapped to $A000-$BFFF
, though it would require an MBC-like circuit to work.
An example of a game that didn't use a MBC was Tetris, whose ROM is only 19KB.
Larger games required larger amounts of storage, which as you pointed out, the Gameboy was not natively able to address.
Types of Memory Bank Controllers
There were 3 (and a half) types of MBCs that cartridges used.
They each possessed different amounts of ROM and RAM.
Each ROM bank was 16KB in size
- MBC1: max 2MByte ROM (125 banks) and/or up to 32KByte RAM
- MBC2: max 256KByte ROM (16 banks) and 512x4 bits RAM
- MBC3: max 2MByte ROM (128 banks) and/or 32KByte RAM (4 banks) and Timer
- HuC1: Similar to MBC1 with an Infrared Controller
One thing to note is that MBC1 and MBC3 both support 2MB, but have a different number of banks.
I will explain that once I explain the memory map and bank switching.
Before describing how the bank switching takes place, I'm going to explain how the MBC1 itself operates.
MBC1 Memory Map
This was the first MBC chip for the Gameboy.
It behaves much the same as the other, with a few exceptions.
A cartridge with an MBC1 uses to following memory ranges:
$0000-$3FFF
: ROM Bank $00
(Read Only)
This always contains the first 16KB of the cartridge, the first memory bank.
It is unable to be switched or modified.
$4000-$7FFF
: Additional ROM Banks (Read Only)
This area of memory will contain whatever memory bank is currently selected.
$A000-$BFFF
: Ram Bank, if present (Read/Write)
This is where RAM banks will be placed in memory.
They are able to be both written to and read from, allowing the game to store data.
If the cartridge contains a battery, like in Pokemon cartridges, the data written is able to be preserved across shutdowns.
This type of MBC had 3 different RAM options:
- 2KB: $A000-A7FF
- 8KB: $A000-BFFF
- 32KB: $A000-BFFF
(four switchable 8KB banks)
$0000-$1FFF
: RAM Enable (Write Only)
Before a game is able to use the RAM, it must be specifically enabled.
This is done by writing a value with the lower 4 bits being $0A
somewhere in this address space.
To disable RAM, any number except $0A
can be written.
It does not matter where it is written, just as long as it within the address range.
You will notice that this address range is part of the first ROM bank, which is read only.
Because it is ROM, there is obviously no way to actually write data to those memory locations.
Instead, the write call is "intercepted" and interpreted differently by the MBC.
This method of writing to ROM is also used for the remaining memory areas I'll explain now.
$2000-$3FFF
: ROM Bank Number (Write Only)
Writing a value to this address range will select the lower 5 bits of the bank number.
There are a few special cases though.
If the value $00
is written, it will converted to bank $01
.
This is not an issue because bank $00
is always present at $0000-$3FFF
.
The issue lies in writing the values $20
, $40
, and $60
.
When these values are written, instead of addressing the correct ROM banks they will address banks $21``$41
and $61
respectively.
I couldn't find an explanation of why this takes place, but I assume it has something to do with how the lower 5 bits are used when choosing the bank.
Each of these numbers have all zeros as the lower 5 bits (0x0XX
00000
).
This issue is not present in MBC2 and MBC3.
$6000-$7FFF
: ROM/RAM Mode Select (Write Only)
Writing either $00
or $01
to this area will select which mode the MBC is in.
On an MBC1, there are two modes: 16Mb ROM/8KB RAM and 4Mb ROM/32KB RAM.
The game is able to switch between the modes on the fly, allowing a game to access extended ROM banks during normal operation and switch to RAM mode temporarily when data needs to be read.
Valid values are $00
for ROM mode and $01
for RAM mode.
$4000-$5FFF
: RAM Bank Number or Upper Bits of ROM Bank Number (Write Only)
Writing to this area of memory will effect the ROM or RAM modes, depending on what is written in $6000-$7FFF
.
Only the first two bits of the value matter.
If in ROM mode (no RAM bank switching), it will specify the upper two bits of the ROM bank number.
In this mode, only RAM bank $00
may be used.
If in RAM mode, it will specify which RAM bank to load into $A000-$BFFF
.
In this mode, only ROM banks $00-$1f
may be used.
The MBC1 is only able to switch between 125 different ROM banks, instead of the expected 128.
The ROM banks are addressed using a 6 bit number created from writing to $2000-$3FFF
and $4000-$5FFF
.
The reason is because of the number conversion when writing $20
, $40
, and $60
to $2000-$3FFF
.
This results in it being able to address 128-3 or 125 banks for a actual total of 1.95MB, not 2MB.
This issue is not present in MBC2 and MBC3.
MBC2 has a maximum of 16 banks, so the issue is never encountered.
The MBC3 chip correctly addresses $20
, $40
, and $60
and does not perform the conversion.
Examples
That was a lot of numbers and memory ranges, so now I'll give a few examples of bank switching and the process a game would take.
When the Gameboy is first turned on, the cartridge header is read and gives information on the ROM and RAM sizes present on the cartridge.
Byte $0147
specifies the type of MBC and what hardware is present on the cart.
Byte $0148
specifies the size of the ROM, from which the number of banks can be derived.
These examples assume the cart has a MBC1 type chip.
Switching to a ROM Bank < $20
Switching to banks $01-$1F
is very simple.
We only need to write our intended bank to $2000-$3FFF
.
Here we are switching to bank $05
:
ld $2000, $05
; Now able to read data from bank $05
Switching to a ROM Bank > $1F
To switch to a ROM bank greater than $1F
, there is some extra legwork to be done.
First, we need to switch to the ROM banking mode.
Then we write the lower 5 bits to $2000-$3FFF
and the upper 2 bits to $4000-$5FFF
.
For this example, I will be loading bank $46
.
This value is 0x0100 0x0110
in binary.
ld $6000, $00 ; Set ROM mode
ld $2000, $06 ; Set lower 5 bits, could also use $46
ld $4000, $02 ; Set upper 2 bits
; Now able to read data from bank $46
Reading a Value from RAM Bank $00
When reading a value from RAM Bank $00
, there is no need to change the banking mode.
This example assumes that there is RAM present on the cart.
Before reading and writing the RAM, we need to enable the RAM.
It is prudent to disable RAM after it is accessed in case the Gameboy is suddenly turned off.
If it it isn't turned off, an unexpected shutdown could leave the RAM in an inconsistent state.
Once we are done moving data in and out of RAM, we should disable once again.
ld $0000, $0A ; Enable RAM
; Perform operations on RAM data
ld $0000, $00 ; Disable RAM
Reading a Value from RAM Bank $02
To read a value from a RAM bank other than $00
, we need to switch to RAM mode.
Once this is done, we can select the RAM bank, enable RAM, and begin moving data.
Note we are able to select the RAM bank before enabling RAM.
ld $6000, $01 ; Switch to RAM mode
ld $4000, $02 ; Select our RAM bank
ld $0000, $0A ; Enable RAM
; Perform operations on RAM data
ld $0000, $00 ; Disable RAM
Reference / Further Reading
Pan Docs
GB Dev Wiki: Memory Bank Controllers
Gameboy CPU Manual