ExHiROM 64 Mbit Cartridge Test
Cartridge & Mapping TestsAn 8 MiB ExHiROM cartridge (map mode $25) — the ceiling of the
extended map, and the size where the map runs out of banks. The second region wants
$40–$7D, but $7E/$7F are Work RAM, so the last
128 KiB is reachable only through the $3E/$3F upper-half mirrors —
leaving 64 KiB physically present and addressable by nothing.
Self-running — no controls. The screen turns green when every canary, mirror and span read the byte the model predicted, red if any did not. (It spends a few seconds folding spans before the verdict appears.)
Click the screen, then play. (Tab away and it pauses.)
The mapping
map mode $25 (ExHiROM, slow ROM)
header file $40FFB0
image 8,388,608 bytes = 64 Mbit, one mask ROM
region A $C0-$FF:0000-FFFF <- file $000000-$3FFFFF
region B $40-$7D:0000-FFFF <- file $400000-$7DFFFF
tail $3E:8000-FFFF <- file $7E8000-$7EFFFF
$3F:8000-FFFF <- file $7F8000-$7FFFFF
HOLES file $7E0000-$7E7FFF and $7F0000-$7F7FFF (64 KiB, unreachable)
Banks $7E/$7F are the console's 128 KiB of Work RAM, mapped over the
cartridge, so region B's full-bank window stops at $7D. The last 128 KiB of the image is
reachable only through the $3E/$3F upper-half mirrors — which are 32 KiB
each. The low halves of those two banks are therefore physically present in the file and
addressable by nothing. The model reports them (holes()) and its checked inverse
refuses to hand back an address for them, rather than silently returning a mirror. This
cartridge places canaries at the first and last byte of both tail windows to prove they really are
reachable.
ROM map
Every half-bank cell the decoder maps, generated from tools/snes_cartmap.py: the tail's $3E/$3F canonical windows, the two 32 KiB holes they leave physically present but unreachable, and both file→CPU seams.
What it checks
| Check | What it proves |
|---|---|
| 14 canary bytes | The first and last byte of every decoded CPU window, every physical mask ROM, and each 1 MiB divider — each read through a 24-bit far pointer (lda [dp]) and compared against the byte the host model says lives there |
| 3 mirror probes | Each accepted mirror returns the same byte as its canonical window. The padding pattern is a non-linear hash of the full 24-bit file offset, so a decoder that folded a mirror to a different offset is caught immediately |
| 4 folded spans | Byte runs crossing one 64 KiB CPU bank, several banks, and one crossing into the WRAM-capped tail windows. Each is walked as an ordered list of bank-bounded segments, re-establishing the bank byte at every segment start — never by incrementing a pointer across a boundary |
| Rotate-add fold | The oracle is order-sensitive and non-linear over GF(2). An earlier XOR fold over an XOR-linear pattern collapsed to $0000 — and to $0000 for the wrong bank too, proving nothing. A bank off-by-one, a flipped byte, a dropped byte, a duplicated byte and a reversed order each change it |
How it is built
Every address this ROM tests comes from one authoritative host model,
tools/snes_cartmap.py — a direct port of the bsnes-jg cartridge bus
(Database/boards.bml plus Bus::map/mirror/reduce),
not a paraphrase of a wiki page. A host test greps the vendored boards.bml so a
transcription drift fails on the desktop instead of in an emulator. The linker script, the canary
addresses, the segment lists, the expected bytes and the final oracle are all generated
from that model; the C source contains no addresses of its own.
The model exposes the two directions deliberately asymmetrically. decode(bank, addr) is
mirror-aware emulator truth. file_to_cpu(offset) returns only the one canonical
CPU address and raises on an addressing hole. A descriptor that named a mirror would still read the
right byte — which is exactly the bug class this cartridge exists to catch — so the emitting side is
restricted and the reading side is permissive.
Written in C with the llvm-mos 65816 toolchain under +mos-a16
(a runtime far pointer is a 32-bit value, so the cross-bank cursor needs native 16-bit mode). Hit
Verify fidelity to reproduce the build gate's WRAM assert live in this tab — the same oracle
the host model and the cycle-accurate bsnes-jg core agree on.
ROM SHA-256 b57dd018b4f8dfa15e3b44c83f4ef47f94717e9ebb6399b5e24fb32972817db3. Verified on bsnes-jg (native and this in-browser core). MAME was not available on the build machine — it needs the SPC700 IPL, which is not distributable — so the emulator evidence here is bsnes-jg only.