Bank Switching
Expanding memory through hardware tricks
Bank switching allows systems with limited address space to access more memory by swapping different memory regions into the same address range.
Overview
Bank switching solves a fundamental problem: CPUs with 16-bit address buses can only address 64KB of memory, but games and programs often need more. The solution involves mapping different physical memory regions into the same address range, swapping "banks" as needed. This technique enabled NES cartridges to contain megabytes of data despite the console's limited addressing.
Fast Facts
| Aspect | Detail |
|---|---|
| Problem solved | 16-bit address limit (64KB) |
| Method | Swap memory regions via hardware |
| Common platforms | NES, Game Boy, C64, ZX Spectrum 128K |
| Modern equivalent | Virtual memory, paging |
The Address Space Problem
An 8-bit CPU with 16-bit addressing:
| Addressing | Maximum |
|---|---|
| 16-bit address bus | 2^16 = 65,536 bytes |
| 64KB ceiling | Hard limit for direct access |
| Game requirements | Often exceeded 64KB |
How Bank Switching Works
The concept:
| Step | Action |
|---|---|
| 1 | CPU sees fixed address range (e.g., $8000-$BFFF) |
| 2 | Hardware maps this to physical ROM/RAM |
| 3 | Writing to special register selects which bank |
| 4 | New bank appears at same addresses |
| 5 | Program code switches banks as needed |
Platform Examples
NES Mappers
The NES used cartridge hardware called "mappers":
| Mapper | Features | Typical games |
|---|---|---|
| NROM (0) | No switching, 16KB or 32KB PRG | Donkey Kong, Mario Bros |
| UxROM (2) | 256KB PRG bank-switching, fixed last bank | Mega Man, Castlevania |
| CNROM (3) | 32KB PRG fixed, 32KB CHR bank-switching | Paperboy |
| MMC1 (1) | Up to 512KB PRG + 128KB CHR, mirroring control, 5-bit serial register | Zelda, Metroid |
| MMC3 (4) | 512KB PRG + 256KB CHR, scanline IRQ via PPU A12 edge, paired even/odd writes | Super Mario Bros 3 |
| MMC5 (5) | 1MB PRG + 1MB CHR, extended attributes, expansion audio | Castlevania III |
Different games used different mappers depending on complexity. The NES 2.0 spec catalogues several hundred distinct mappers across licensed and unlicensed cartridges.
Commodore 64
The C64 banks system ROMs and I/O in and out via the 6510's on-chip processor port at $01:
| Region | Banks (controlled by $01 bits 0-2) |
|---|---|
| $8000-$9FFF | RAM, or cartridge LO ROM (when /EXROM is asserted) |
| $A000-$BFFF | RAM, BASIC ROM, or cartridge HI ROM (LORAM bit) |
| $D000-$DFFF | RAM, character ROM, or I/O area (CHAREN bit) |
| $E000-$FFFF | RAM, KERNAL ROM, or cartridge HI ROM (HIRAM bit) |
Bit 0 = LORAM, bit 1 = HIRAM, bit 2 = CHAREN. The Ultimax cartridge mode (/EXROM=0, /GAME=0) replaces large parts of the map with cartridge ROM regardless of $01.
ZX Spectrum 128K
The 128K Spectrum banked RAM at $C000-$FFFF:
| Feature | Detail |
|---|---|
| Fixed Bank 5 | Always at $4000-$7FFF (the regular screen) |
| Fixed Bank 2 | Always at $8000-$BFFF |
| Switchable | Eight 16KB banks at $C000-$FFFF (banks 0-7, selected by $7FFD bits 0-2) |
| Control | Port $7FFD (write-only); bit 5 locks paging until reset |
The +2A/+3 add a second paging port at $1FFD for "all-RAM" configurations and ROM disk routing.
Technical Challenges
| Challenge | Consequence |
|---|---|
| Code can't span banks | Jump targets must be in same bank |
| Data access planning | Know which bank contains what |
| Interrupt handling | Bank state during interrupts |
| Performance overhead | Bank switching takes cycles |
Programming Patterns
| Pattern | Purpose |
|---|---|
| Fixed bank | Code that must always be accessible |
| Data banks | Level data, graphics, audio |
| Trampoline code | Jump between banks via fixed code |
| Bank tables | Track what's where |
Trampoline pattern
The trampoline lives in a fixed bank (e.g. MMC3's last 8 KB at $E000-$FFFF, which never switches). It saves the current bank, switches to the target bank, calls into it, and restores the original bank on return. Code in switchable banks calls foreign functions only via the trampoline:
; Lives at $E000 in the fixed bank
far_call:
PHA ; save bank arg & target addr (pushed by caller)
; ... swap to target bank using mapper-specific writes ...
JSR (target_addr) ; run the routine in the now-paged bank
; ... swap back to caller's bank ...
RTS
Without a trampoline, an RTS from a foreign bank would land back in the wrong bank's code.
NES bank switching by mapper
The bank-select interface varies enormously between mappers. Three patterns cover most of them.
UxROM, CNROM, AxROM — direct write
A single write to any address in the cartridge range latches the bank number:
LDA #$03
STA $8000 ; UxROM: select PRG bank 3 at $8000-$BFFF
; (last bank stays fixed at $C000-$FFFF)
MMC1 — 5-bit serial shift register
MMC1 collects five sequential writes into an internal shift register. Each write latches one bit (bit 0 of the value); on the fifth write the assembled 5-bit value is committed to one of four registers, picked by the address of the fifth write:
LDA #%10000000 ; bit 7 set = reset shift register
STA $8000 ; (use this to abort a partial write sequence)
; Now write the 5 bits of the bank number, LSB first
LDA #$1 ; bit 0
STA $E000
LDA #$1
STA $E000
LDA #$0
STA $E000
LDA #$0
STA $E000
LDA #$0
STA $E000 ; fifth write to $E000-$FFFF latches PRG bank %00011 = 3
MMC3 — paired bank-select / bank-data
MMC3 splits the operation across even and odd addresses. Write the register index to an even address in $8000-$9FFF, then the bank value to the odd address one byte later:
LDA #$06 ; index 6 = PRG bank at $8000 (in default mode)
STA $8000 ; bank select
LDA #$0A
STA $8001 ; bank data — PRG bank $0A now at $8000
MMC3 also holds an 8-bit IRQ counter that decrements on each rising edge of PPU A12, used for split-screen and status-bar effects.
Historical Context
| Era | Approach |
|---|---|
| Arcade boards | Custom banking hardware |
| Console cartridges | Mapper chips in cart |
| Home computers | System-level banking |
| CD-ROM era | Streaming replaced banking |
Legacy
Bank switching was the bridge between limited addressing and modern virtual memory. Developers learned to structure code and data around bank boundaries—skills that translated into understanding memory hierarchies, caching, and modern paging systems. Emulator developers must accurately emulate mapper behaviour to run games correctly.