UNROM is the common name for a discrete mapper found on the UNROM board as well as the less common UOROM board. UNROM has 64 KB or 128 KB PRG-ROM (divided into 8 16k banks) and CHR-RAM. The UOROM board works the same way and can take PRG-ROM up to 256 KB (16 banks). It is very easy to use once you know how to load CHR RAM.
Here is an NES 2.0 header for mapper 2 on the UNROM and UOROM boards. It should be backward-compatible with emulators supporting only the older iNES header format, but they may emulate extra RAM at $6000-$7FFF, where official boards have open bus.
.segment "HEADER" .byte "NES", $1A .byte 8 ;UNROM has 8 16k banks; change this to 4 or 16 as needed .byte 0 ;No CHR ROM .byte $20, $08 ;Mapper 2, horizontal mirroring, NES 2.0 .byte $00 ;No submapper .byte $00 ;PRG ROM not 4 MiB or larger .byte $00 ;No PRG RAM .byte $07 ;8192 (64 * 2^7) bytes CHR RAM, no battery .byte $00 ;NTSC; use $01 for PAL .byte $00 ;No special PPU
To use vertical mirroring instead of horizontal mirroring, change the
.byte $20, $08 to
.byte $21, $08
UNROM has four or eight banks 16 KB in size; UOROM has 16 banks. The last of these banks is fixed at $C000-$FFFF. The rest (numbered 0-2, 0-6, or 0-14) are switchable at $8000-$BFFF.
Switching banks requires a write to $8000-$FFFF. In UNROM, bits 0-2 of the byte written to $8000-$FFFF will select the bank; UOROM uses bits 0-3. When writing to $8000-$FFFF, the value you are writing must match the value located at the destination address in ROM (see Bus conflict). One way to ensure this is to have a bankswitch lookup table. You can read from this table and then immediately write that value back to the table.
.segment "RODATA" banktable: ; Write to this table to switch banks. .byte $00, $01, $02, $03, $04, $05, $06 .byte $07, $08, $09, $0A, $0B, $0C, $0D, $0E ; UNROM needs only the first line of this table (0-6) ; but UOROM needs both lines (0-14). .segment "ZEROPAGE": ; The mapper is read-only; need to track its state separately current_bank: .res 1 .segment "CODE" bankswitch_y: sty current_bank ; save the current bank in RAM so the NMI handler can restore it bankswitch_nosave: lda banktable, y ; read a byte from the banktable sta banktable, y ; and write it back, switching banks rts
The lookup table and the bankswitching subroutine are normally located in the fixed bank ($C000-$FFFF).
To save 12 cycles per bankswitch at a cost of 5 bytes of ROM, the
bankswitch_y subroutine can be made into a macro.
With the lookup table and bankswitching subroutine in place, switching banks is as easy as this:
ldy #$02 jsr bankswitch_y ;switch to bank 2
If you switch banks in the NMI handler, such as to run a sound engine, do not write to
current_bank. Instead, do this at the end of the NMI handler just before pulling registers:
ldy current_bank jsr bankswitch_nosave