Z80 bankswitching
The Z80 can only natively address 64kiB of memory at most because of its 16-bit address bus. To overcome this limit, bankswitching is provided in cartridges to access up to 4MiB of M1 ROM. CD systems provide no Z80 bankswitching.
Operation
A standard of four bankswitching windows are available. The $0000~$7FFF range is fixed to the first 32kiB of ROM, and $F800~$FFFF to Z80 RAM.
All windows allow an 8-bit bank selection.
| Z80 port | Window address | Size | Bank range |
|---|---|---|---|
| $08 | $F000~$F7FF | 2KiB | $000000~$07FFFF |
| $09 | $E000~$EFFF | 4KiB | $000000~$0FFFFF |
| $0A | $C000~$DFFF | 8KiB | $000000~$1FFFFF |
| $0B | $8000~$BFFF | 16KiB | $000000~$3FFFFF |
Bank numbers are undefined on startup, the Z80 code must initialize them.
To save pins on bankswitching chips in cartridges such as the NEO-ZMC, SNK used a quirk of the Z80 that allows placing data on its address bus. Therefore, to set a bank number, the Z80 port must be read with the bank number in the upper address bits.
For example, this will set the $F000~$F7FF window to bank 25, ie. ROM 25 * 2kiB = $C800~$CFFF
ld a,25 in a,(8) ; Places 25 on address[15:8] (bank number), 8 on address[7:0] (port number)
For example, this will set the $8000~$BFFF window to bank 243, ie. ROM 243 * 16kiB = $3CC000~$3CFFFF
ld bc,$F30B in a,(c) ; Places $F3 = 243 on address[15:8] (bank number), $0B on address[7:0] (port number)
To set up the banks to map the whole $0000~$F7FF range linearly to the beginning of the M1 ROM:
ld a,30 in a,($08) ; Set window $F000~$F7FF at 30*$800 = $F000 ld a,14 in a,($09) ; Set window $E000~$EFFF at 14*$1000 = $E000 ld a,6 in a,($0A) ; Set window $C000~$DFFF at 6*$2000 = $C000 ld a,2 in a,($0B) ; Set window $8000~$BFFF at 2*$4000 = $8000
Another example from The King of Fighters '97:
; Function takes 32KiB bank number in A rlca rlca ld e,a ld d,$00 ; DE = A * 4 (table entry size) ld hl,$289C ; Table address, see below add hl,de ld a,(hl) in a,($08) ; Set window $F000~$F7FF inc hl ld a,(hl) in a,($09) ; Set window $E000~$EFFF inc hl ld a,(hl) in a,($0A) ; Set window $C000~$DFFF inc hl ld a,(hl) in a,($0B) ; Set window $8000~$BFFF
The table at $289C is used to set the 4 windows like an unique $7800-bytes long bank in 32KiB increments, meaning that last $800 bytes are wasted at the end of each range (mapped to Z80 RAM):
1E 0E 06 02 = F000, E000, C000, 8000 "Big bank 0": $08000~$0F7FF ($0F800~$0FFFF is wasted) 2E 16 0A 04 = 17000, 16000, 14000, 10000 "Big bank 1": $10000~$177FF ($177FF~$17FFF is wasted) 3E 1E 0E 06 = 1F000, 1E000, 1C000, 18000 "Big bank 2": $18000~$1F7FF ($1F7FF~$1FFFF is wasted) 4E 26 12 08 = 27000, 26000, 24000, 20000 "Big bank 3": $20000~$277FF ($277FF~$27FFF is wasted) 5E 2E 16 0A = 2F000, 2E000, 2C000, 28000 "Big bank 4": $28000~$2F7FF ($2F7FF~$2FFFF is wasted) 6E 36 1A 0C = 37000, 36000, 34000, 30000 "Big bank 5": $30000~$377FF ($377FF~$37FFF is wasted) 7E 3E 1E 0E = 3F000, 3E000, 3C000, 38000 "Big bank 6": $38000~$3F7FF ($3F7FF~$3FFFF is wasted)
Note that the game has a 128KiB M1 ROM, but the table is long enough to map up to 256KiB.
Details
SDA8~SDA15 are used for the bank number (data), SDA0 and SDA1 for selecting the window.
The write (Z80 read) strobe SDRD0 is port address decoded inside the system when SDA[3:2] = 2 (ports $8~$B).
Internal logic: zmc.v (verilog)
SDRD0 must be high before configuring banks.
To configure a bank with NEO-ZMC:
- Set SDRD0 low (prepare for new bank configuration, outputs are tri-stated ?)
- Set SDA[15:0] = $050B, bank 5 in $8000~$BFFF window
- Set SDRD0 high (latch bank)
- Now when the Z80 reads the $8000~BFFF range, it will be mapped to M1 ROM $14000~$17FFF