Z80 bankswitching

From NeoGeo Development Wiki
Jump to navigation Jump to search

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