Skip to content

Memory & the system bus

Explore the memory map, register side effects, access widths, wait states and open-bus behaviour.

6 min read Updated 28 Oct 2026

On this page

On a Game Boy Advance, an address means more than a location in a large array. It can refer to working RAM, cartridge ROM, sprite attributes, a palette entry or a control register that changes hardware behaviour. GBAx’s memory implementation must identify that destination and reproduce its access rules before returning a value or completing a write.

This is the connection between CPU instructions and the rest of the console. The game uses ordinary loads and stores, but their effects depend on where they go, how wide they are and when they happen.

A map of the important regions

The CPU sees a single address space containing several distinct regions. The table below shows their usual base addresses and physical roles; it is an orientation map, not a complete list of mirrors, unused ranges and access restrictions.

Base address Region Role
0x00000000 BIOS, 16 KiB Boot and system-service code, with special read behaviour.
0x02000000 External work RAM, 256 KiB General game data and executable code.
0x03000000 Internal work RAM, 32 KiB Small, fast working area often used for time-sensitive code and data.
0x04000000 Memory-mapped I/O Display, sound, timer, DMA, input and system registers.
0x05000000 Palette RAM, 1 KiB Background and sprite colour entries.
0x06000000 Video RAM, 96 KiB Tiles, maps and bitmap data, depending on display mode.
0x07000000 OAM, 1 KiB Sprite attributes and affine parameters.
0x08000000 Game Pak ROM window Cartridge program/data; further windows select different wait-state settings.
0x0E000000 Cartridge save region SRAM or Flash-style access. EEPROM uses a different serial protocol.

The core allocates storage for writable regions and maps cartridge data for reads. Region constants and memory-block descriptions appear in the reviewed GBA source. The access functions in memory.c decide which storage or hardware handler receives a request.

A write can be an action

Imagine a store to a timer’s control register. If an emulator merely copied that value into an I/O array, the game could read the value back, but the timer would never advance. The write must also update the timer’s enabled state, prescaler and scheduled overflow. Register contents and component behaviour have to change together.

GBAx routes I/O writes into register-specific handling in io.c. Timer registers call timer logic; sound registers call audio logic; DMA registers configure transfers; display registers notify the video implementation. Some registers are read-only or have bits that do not behave like ordinary writable memory.

  1. CPU storeAn instruction supplies an address, value and access width.
  2. Address decodeThe memory system selects RAM, cartridge or I/O handling.
  3. Device updateA register write changes the component and its scheduled work.
  4. Later observationThe game reads new state or receives an interrupt.
Why copying bytes is only part of memory emulation: some addresses control ongoing behaviour.

Access width, alignment and byte order

The GBA supports byte, halfword and word accesses, but not every region handles every width identically. The core has separate paths for 8-, 16- and 32-bit operations. Video and cartridge regions need their own rules; treating every write as a generic host-memory assignment would bypass those rules.

Values use little-endian byte order. A 32-bit value such as 0x12345678, written to ordinary aligned RAM, appears in increasing byte addresses as 78 56 34 12. This ordering must remain the same regardless of the host platform used to compile the emulator.

Alignment is another visible property. Some misaligned ARM accesses behave differently from aligned ones, including rotation or special signed-load handling. An emulator must implement the target CPU’s behaviour rather than inheriting whatever its host language or processor happens to do. When debugging such an access, the address, instruction type and width are all necessary evidence.

Wait states make memory part of CPU timing

A read from cartridge ROM does not have the same timing as a read from internal RAM. Bus width also matters: a wider CPU operation can require multiple transfers over a narrower bus. The memory implementation therefore supplies timing information as well as data.

The reviewed code keeps timing tables for sequential and non-sequential accesses at different widths. Sequential accesses represent continuation along an established access stream; a new address sequence can incur a different cost. The WAITCNT register controls relevant Game Pak timing settings, including prefetch-related behaviour.

This is why moving code into internal RAM can affect how much work a GBA program completes before a display event. It is not enough for the emulator to execute the same number of instructions. It must also account for the instruction fetches and data accesses that those instructions require.

ROM windows and mirrors are not extra cartridges

The GBA exposes Game Pak ROM through multiple address windows with different wait-state selections. Those windows do not mean the machine contains three independent copies of the game. Address masks and region selection determine which underlying cartridge bytes are observed and what timing applies.

Other regions also contain mirrors or addresses that do not correspond to distinct physical storage. Correct address decoding makes two mirrored addresses observe the intended shared state. Allocating independent memory for every apparent address would produce a machine that has more storage, and different behaviour, than the original hardware.

Unmapped reads do not always return zero

Some GBA reads reach places where no device supplies a fresh value. In those cases, the value observed can depend on previously driven bus data or the processor’s instruction-fetch state. This family of behaviour is often called open bus. Returning a fixed zero for all such reads can break software that relies on what real hardware returns.

The reviewed memory code includes paths that consider CPU prefetch state and DMA-related bus state. This is a useful example of why CPU, memory and transfers cannot be emulated independently. Their previous activity can affect the result of a later read, even when that read does not target ordinary memory.

mGBA’s development article about a game stuck in an apparently impossible loop documents this kind of investigation: a value left by DMA helped explain behaviour that simpler models missed. The lesson is about method—reproduce the observation, inspect the interacting components and compare with hardware—rather than assuming every invalid address should behave like a desktop operating-system fault.

From memory access to a complete system

A useful trace records the instruction address, target address, read/write width, value and emulated time. For an I/O write, it also follows the device-specific handler and any event that handler schedules. That sequence shows both the immediate effect and what the rest of the console will observe next.

Continue with timers and DMA for timed transfers, video for the use of VRAM, palette RAM and OAM, or cartridge save hardware for writes that must persist beyond the current session.

Sources & implementation

Implementation references follow GBAx 2.0.0 Preview 1, commit c1a826ae. The hardware implementation derives from mGBA; these links point to the matching GBAx source, rather than assuming a newer upstream revision behaves identically.

Further reading: mGBA development: an open-bus debugging case.

Need help with a specific problem? Start with troubleshooting →