18

I'm writing a Game Boy emulator, but I don't completely understand how its memory mapping works. Here is what I (think) I know (and don't know).

The CPU can address up to 0x10000 memory locations with the 16-bit address bus. The memory is mapped as follows:

$0000-$3fff: a 16KB ROM bank. Initially, reading from this accesses the bootloader, but afterwards ot accesses a fixed bank in the cartridge.

$4000-$7fff: accesses ROM from cartridge, but can access more memory by switching banks (what does this mean?)

$8000-$9fff: VRAM, the PPU reads from this to draw the screen

$a000-$bfff: external ram found in the cartridge, this is the switchable bank?

$c000-$cfff: actual work RAM stored in the Gameboy (not in the cartridge)

$d000-$dfff: also the switchable bank that is accessed when reading from $4000-$7fff?

$e000-$fdff: echo of $c000-$dfff, accesses the same memory

$fe00-$fe9f: sprite attribute table, used to hold sprites

$ff00-$ff7f: IO registers

$ff80-$fffe: also accessible ram that is supposedly fast to access?

$ffff: Interrupt Enable Register flag, used to allow or disable interrupts to the CPU.

  • 1
    Bank switching means that there's more ROM in the cartridge than will fit into the space available for it, so only part of the ROM shows up in that space. You can switch which a part of the ROM, or bank, appears there in order to access the entire ROM. – Ross Ridge Jul 20 at 22:18
  • So what are the memory locations d000-dfff and a000-bfff? – David Tran Jul 20 at 22:28
  • I assume the later is what you said it is, RAM located on the cartridge. Presumably this is battery-backed RAM so that it can be used to save game state. I don''t know what the former is but it can't be the same thing as what's located at $4000-$7FFF because it's much smaller. – Ross Ridge Jul 20 at 22:43
  • The answers to How does memory addressing/mapping work in 8-bit systems? may also be useful. – Curt J. Sampson Jul 21 at 7:09
20

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 (0x0XX00000). 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

  • How would external hardware be addressed? For example, how did the Gameboy access memory provided by a RTC (0f type cartridges?) – David Tran Jul 21 at 17:49
  • The cartridge bus is directly mapped to the CPU so a controller on the cartridge can see where read/write accesses are going so it can choose to map rom pages or some other IO at those addresses. – PaulHK Jul 22 at 5:30
9

Address Decoding

Any computer system (including video game consoles) with more than one memory device needs hardware (usually) external to the CPU to determine which device to access for any particular address. This process is called address decoding. In the simplest case this could be a single device that looks at the address and makes the decision this way:

  • Address < $c000: access the RAM chip by setting the chip select signal for the RAM chip "on" and the chip select signal for the ROM chip "off."
  • Address >= $e000: access the ROM chip by setting the chip select signal for the RAM chip "off" and the chip select signal for the ROM chip "on."

This can also cause the mirroring that you sometimes see. Say that the RAM chip is only 4 KiB, and thus uses only 12-bit addresses, with the CPU's address bus lines A0-A11 connected to the RAM chip's address pins. In the case above accessing $0003 will access address 3 on the RAM chip, but also so will accessing $1003 or $2003, because in all cases:

  1. The address is less than $c000, so the address decoding logic enables the RAM chip and disables all other devices.
  2. The lower 12 bits of the address, are $003 (sometimes expressed as $x003, where the x is the four "don't care" bits the RAM chip doesn't see), accessing the third location on the RAM chip.
  3. The higher address bits A12-A15 are $0, $1 or $2 (sometimes expressed as $0xxx, $1xxx or $2xxx), but these address bits are not connected to the RAM chip and are thus ignored by it.

Bank Switching

Bank switching extends this by changing which device is accessed in a programmable way. Say you have a latch (basically, a one-bit memory location) accessed at $c000. (This would usually be on a separate device, also enabled/accessed via appropriate address decoding.) You might configure the hardware so that when this latch contains a 0 any access to $e000-$ffff will access ROM chip #0, but when a 1 is in this latch any access to $e000-$ffff will instead access ROM chip #1.

Using this technique you can access more than 0x10000 memory locations even though you have only 0x10000 addresses available by having multiple devices at the same memory address and choosing them based on further information such as the bit stored in the latch example above. The system would start out at boot with a 0 in that latch, but at any time the program could write a 1 to $c000, access the alternate memory, and when done, write a 0 to $c000 so that further access would read the first memory again. (The program would usually not be running from those $e000-$ffff memory locations during this process. But with careful design of what's in each memory bank, it could also be stored in both ROMs and switch itself as it's running.)

Read vs. Write Decoding

There's one more bit of complexity that can be added to address decoding: different decoding for reads and writes. In the example above I had $c000 dedicated to the latch switching the ROM banks at $e000-$ffff. However, since ROM is read-only and thus never written, I can make the ROM-addresses dual-purpose by making writes access a different device from reads. So I could, for example, make my ROM two banks of 16 KiB, both accessed by reading $c000-$ffff, and still use $c000 to switch as well. That address would thus be accessing three separate devices:

  • On write to $c000, with the current value of the latch 0 or 1, the data are always written to the latch.
  • On read from $c000 with the current value of the latch = 0 the data are read from ROM chip #0.
  • On read from $c000 with the current value of the latch = 1 the data are read from ROM chip #1.

Additional Hardware

Though every non-trivial video game system will have some address decoding hardware built-in, it's common to add extra address decoding hardware on the cartridge if the game is bigger than will fit into the cartridge's address space. If the built-in address decoding always accesses the cartridge for addresses $4000-$7fff, that could be just a single 16 KiB ROM chip on the cartridge, or the cartridge could have additional address decoding to select one of multiple ROM chips based on other information, such as a latch also added to the cartridge.

Cartridges may also have extra non-ROM hardware, such as RAM, battery-backed RAM, timers, clocks, other sensors, and so on. The address decoding on the cartridge also needs to manage how all these devices are accessed, usually by assigning them specific memory locations as well.

Game Boy Cartridge Memory Bank Controllers

On the Game Boy the additional address decoding logic on a cartridge is called a Memory Bank Controller (MBC). There are several different standard versions of this available, and you can find out which one is used and the configuration of the cartridge by checking certain memory locations in the cartridge header:

  • 0x147: Cartridge type.

    • $00 means it's a ROM-only cartridge, having no extra address decoding. Addresses $0000-$7ffff each access one ROM location.
    • $01 is an MBC1 controller, allowing bank switching for a maximum of 2 MiB of ROM, as described below.
    • $02 is MBC1+RAM, adding access to RAM on the cartridge (maximum 32 KiB) as described below.
    • $03 is MBC1+RAM+battery, with battery-backed RAM so that it's preserved when the cartridge is removed. You need to remember to disable the RAM when you're not using it so that it's not corrupted when the cartridge is removed.
    • $04 is MBC2, which is similar to MBC1 but allows access to more ROM and RAM. I don't describe this here; see the GB Dev Wiki for details.
    • Higher values are yet more memory bank controller systems and arrangements; again, see the GB Dev Wiki for details.
  • 0x148: ROM size.

    • $00 means one 32 KiB bank of ROM; this is the only valid value for a cartridge type of $00.
    • $01 means 64 KiB of ROM. The first 16 KiB is bank 0 and is always accessed at $0000-$3fff. The additional three 16 KiB banks are accessed at $4000-$7fff via bank switching. To select the current bank using this range you write the ROM bank number $01, $02 or $03 to any address in the $2000-$3fff range. (This is ROM address space when read, but control latches when written.)
  • 0x149: RAM size.

    • $00 means no RAM.
    • $01 and $02 are 2 KiB and 8 KiB of RAM respectively. The RAM is accessed at addresses $a000-$bfff, after enabling it by writing $0a any address in the range $0000-$1fff. (This is ROM address space when read, but control latches when written.)
    • $03 means 32 KiB of RAM in four 8 KiB banks. This still accessed only in the 8 KiB range $a000-$bfff, but now you must bank switch this to choose which of the four 8 KiB banks is read and written when accessing that range.
    • $04, $05 mean 128 KiB and 32 KiB respectively, bank switched (in 16 and 8 banks, respectively) as for 32 KiB above.

The above information is far from complete; it's just intended to give the general idea of how this works. The Memory Bank Controller and Cartridge Header pages on the Game Boy Development Wiki (where this information came from) contain much more extensive information about the devices and address decoding for the Game Boy and its various cartridges. If you have specific questions about how to access certain things in the Game Boy, it would probably be best to ask them as separate questions here.

1

b13rg's answer is excellent, and covers the cartridge bank switching well.

I wanted to cover the gameboy side of things; the addresses from $8000-$9FFF, and $C000 onwards.

Gameboy internal banking

Your description of VRAM is correct. The gameboy color makes it a little weirder. The area from $8000-$9FFF is banked on a GBC. Both banks hold tile data between $8000-$97FF, while the first bank holds tilemaps in $9800-$9FFF, and the second bank holds attribute data for the first bank's tilemaps.

To read/write to the first bank, you'd write a 0 to address $FF4F, and for the second bank, you'd write a 1. There's also a bit in tilemap attributes for specifying which bank to pull tiledata from, as well as in sprites' attributes.

$D000-$DFFF is RAM inside the gameboy, just like $C000-$CFFF. On the gameboy color (not the original), this, too, is banked. All internal to the GBC, you can pick which 4 kilobytes to see in the $D000-$DFFF window.

There are 7 banks of 4 kilobytes to choose from, which you pick by writing a number from 1 - 7 to address $FF70. Writing a 0 behaves like writing a 1.

The workings of both these is just like a cartridge, with the exception of the addresses involved.

HRAM

The high addresses (from $FF00 onwards) are special for two reasons:
- They are internal to the CPU
- They can be accessed with special instructions

Point 2 above refers to the LDH instructions, where LDH (nn),A only takes up two bytes instead of 3 for LD (nnnn),A and is also faster because of it, and `LDH (c),A

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.