Skip to content
RetrogamingDeep Dive Published Updated 9 min readViews unavailable

MAME Address Maps: Decoding Arcade Buses, Banks, Mirrors, and Handlers

Trace MAME arcade address maps across bus widths, memory shares, ROM regions, banks, mirrors, and debugger tools to diagnose incorrect reads precisely.

An arcade CPU address is not automatically an offset into a ROM file. It is a transaction on one emulated address space, interpreted by the machine configuration’s decode map. That map routes a range to ROM, RAM, a bank, an I/O port, or a device handler. When a game reads the wrong byte, crashes after a bank switch, or writes a register that appears inert, the useful question is not merely “which ROM contains this address?” It is “which address space, mask, range, bank entry, and handler receive this access at this point in emulated time?”

MAME describes these connections declaratively during machine configuration. The description is a model of board wiring and CPU-visible behavior. It can be simpler than the physical circuit, but it must preserve the observable decode semantics that the software depends on. A successful ROM audit does not validate that decode: files may match expected hashes while the driver maps them at a wrong offset or into the wrong bank.

Start with the address space, not the hex value

An address space belongs to a device such as a CPU and has properties including data width, address width, endianness, and address shift. An address printed by a debugger is meaningful only in that context. A 16-bit CPU may expose program and I/O spaces; another CPU may separate data and opcode fetches. MAME’s special address-space indices and the CPU’s address-space configuration determine which map is used for an access. If an architecture has encrypted opcodes or distinct instruction and data views, the program map alone may not explain what the CPU fetches.

There can also be a distinction between a CPU’s address units and byte offsets. A map range is expressed in the address space’s native units. On a bus where one address unit represents more than one byte, treating a displayed address as a byte offset can produce a systematic factor-of-two error. Check the CPU device’s address-space width and shift before converting an address into a ROM file offset.

The address map itself is a static description installed during machine configuration. A compact illustrative 8-bit program map looks like this:

void board_state::main_map(address_map &map)
{
    map(0x0000, 0x7fff).rom();
    map(0x8000, 0x87ff).ram();
    map(0x8800, 0x8fff).ram().share("workram");
    map(0x9000, 0x9000).w(FUNC(board_state::bank_select_w));
    map(0xa000, 0xa0ff).rw("video", FUNC(video_device::read),
                                      FUNC(video_device::write));
}

This is a map fragment, not a complete driver: the CPU configuration, device declarations, handlers, and function signatures must match the actual machine. The example separates direct ROM reads, ordinary RAM, shared RAM, a write-only bank-selection register, and a device-owned read/write range. An address map does not infer that a write at 0x9000 changes a bank simply because the programmer intended it to; the handler must implement the board’s latch behavior and select a valid entry.

Decode ranges, mirrors, and overlaps deliberately

Real boards often decode fewer address lines than the CPU presents. Unconnected or ignored address lines create aliases. MAME’s .mirror(mask) qualifier models addresses that differ only in the selected mask bits as equivalent for the mapped range. It is not a request to duplicate the memory contents, and it should be used only when board evidence supports those address-line aliases. Mapping an entire upper address region as a mirror because a game happens to work there can hide a real decode error.

Overlapping handlers require similar care. The MAME memory documentation specifies how overlapping map entries are resolved; order and qualifiers therefore have observable meaning. Do not add a broad catch-all RAM range after a narrow I/O mapping without checking which handler ultimately wins. When a range is intentionally partially decoded, express and comment the exact mask rather than relying on a future reader to infer the circuit.

A map may route to a memory share when multiple devices need to see the same RAM object. A share is not merely two separately allocated arrays with matching contents. It provides named shared storage in the emulated machine, and MAME can include such state in save states. A ROM region is different again: it is populated by ROM declarations and generally represents loaded image bytes. Keep three layers distinct while tracing a read:

  1. The ROM declaration determines which preserved file bytes are loaded into a named region.
  2. The address map determines which region, bank, RAM block, or handler is visible at a CPU address.
  3. Runtime control logic can change a bank or device state after reset.

A discrepancy in layer one is an audit or load-layout problem. A discrepancy in layer two is a decode problem. A discrepancy in layer three is a latch, bank-selection, or timing problem. Editing a ROM offset to compensate for a wrong map may make one screen look plausible while breaking another access path.

Banks model changing windows

Many boards expose a fixed address window whose backing ROM or RAM changes in response to a register write. A bank is appropriate when the CPU sees one window at a time and the selected entry changes at runtime. Configure the bank’s entries using the board’s known regions, entry count, stride, and reset selection. Then have the latch handler select only a valid entry and preserve any unrelated bits that share the latch.

Avoid describing a bank as if it remaps the CPU address space itself. The map continues to route the window to the bank; the bank changes the backing pointer or entry. This distinction is valuable in debugger output: the handler may be stable while the resolved bytes change. Document bank number, source region offset, window size, reset entry, and register bit interpretation together. If a physical board uses separate read and write windows, or has a banked opcode view, model those semantics explicitly rather than assuming one read bank covers all access types.

MAME also supports views for a small number of alternate map configurations, such as hardware modes that select different submaps. Use an explicit view when the address decode itself switches between alternative handlers. Use a bank when the decode window remains constant but its backing storage changes. Conflating these models makes both configuration and debugging harder.

Make peripheral handlers match bus behavior

A device handler is called because an address range routes to it; the handler then must honor the relevant access details. On wider buses, a read or write can include a lane mask or access mask. A byte-wide peripheral connected to one lane may not respond to every byte position. Ignoring masks can make writes appear to affect multiple registers or cause reads to return repeated data. Check the address map API and the handler signature used by the device, especially when porting a driver from an 8-bit map to a wider CPU map.

Read side effects deserve equal attention. A status register may clear an interrupt flag on read, acknowledge a device, or advance a FIFO. A debugger memory inspection can therefore have different consequences depending on whether it performs side-effect-disabled access. Use the debugger’s documented inspection modes, and do not treat a destructive status read as a passive observation. For reproducible diagnosis, record the exact command, address space, width, and side-effect setting.

A reproducible decode investigation

Start from a known machine and software configuration. Record the MAME version, system name, selected BIOS, ROM audit result, and any software-list selection. Use memdump in the debugger to write the current memory maps to a file; the command accepts an optional filename and device selector. Inspect the map for the CPU or peripheral that owns the address instead of assuming the first CPU is the one involved. The debugger’s map command can help map a logical address to the corresponding physical address and handler where that distinction applies.

Then use a small evidence table for each failing address:

Observation Record
Access origin CPU/device tag, address space, read/write/fetch
Address interpretation Native address units, bus width, lane mask
Route Map range, qualifiers, bank or view, final handler
Backing data Region/share name, offset, selected entry
Time/state Reset phase, latch value, interrupt or device state
Expected basis Board schematic, chip pinout, verified dump, or trace

Use breakpoints on the relevant address, step through the instruction or I/O sequence, and compare actual handler results with the board-level expectation. If an address is mirrored, probe both aliases. If a bank changes, capture the selector write and verify the entry before examining the next read. If a map entry appears absent, confirm that the device actually owns that address space; MAME’s device memory interface can ignore mappings for non-existent spaces rather than reporting a useful runtime failure.

Keep fixes narrow. Change a map range, bank source, or decode mask only when you can state which hardware observation it represents. Add a regression note with the reproducible access sequence and expected result. A clean startup, successful ROM audit, or attractive attract screen does not establish that every address is decoded correctly. The strongest evidence is a map that matches the documented bus behavior plus tests of the key read, write, bank, and alias paths.

Common failure patterns

An off-by-one endpoint can leave a register outside the intended range. An overly broad RAM entry can shadow a device. A mirror mask can create aliases that real hardware would not decode. A bank with the wrong stride can load correct ROM bytes into the wrong window. A native address unit mistaken for a byte offset can shift every mapped location. A missing opcode or data space can produce a split symptom in which disassembly looks plausible but execution fails. Finally, a handler that ignores lane masks can work for one CPU and break another bus width.

Treat each symptom as a hypothesis about the route and access semantics, not as proof that the ROM is corrupt. MAME’s address-space metadata, static maps, runtime banks, shared memory, and debugger diagnostics together provide a precise way to test that hypothesis without confusing a CPU-visible address with a file offset.

Related:

Sources:

Comments