Programming UNROM: Difference between revisions

From NESdev Wiki
Jump to navigationJump to search
(Undo good faith edit by Lidnariq (talk): NES 2.0 not really needed unless iNES is ambiguous. Will take it to talk.)
(on the other hand, after discussion, there's a reason for NES 2.0 here)
Line 6: Line 6:


UNROM, 64 KB:
UNROM, 64 KB:
  .segment "HEADER"
<pre>
    .byte "NES", $1A
.segment "HEADER"
    .byte $04      ;UNROM has 4 16k banks
  .byte "NES", $1A
    .byte $00      ;UNROM uses CHR RAM, so no CHR ROM
  .byte 4        ;UNROM has 4 16k banks
    .byte $20, $00  ;UNROM is Mapper 2
  .byte 0        ;UNROM uses CHR RAM, so no CHR ROM
    .byte $00      ;UNROM has no PRG RAM, though [[PRG RAM circuit|clone boards might]]
  .byte $20, $00  ;UNROM is Mapper 2; configured for horizontal mirroring
  .byte $00      ;UNROM has no PRG RAM
</pre>


UNROM, 128 KB (most common):
UNROM, 128 KB (most common):
  .segment "HEADER"
<pre>
    .byte "NES", $1A
.segment "HEADER"
    .byte $08      ;UNROM has 8 16k banks
  .byte "NES", $1A
    .byte $00      ;UNROM uses CHR RAM, so no CHR ROM
  .byte 8        ;UNROM has 8 16k banks
    .byte $20, $00  ;UNROM is Mapper 2
  .byte 0        ;UNROM uses CHR RAM, so no CHR ROM
    .byte $00      ;UNROM has no PRG RAM, though clone boards might
  .byte $20, $00  ;UNROM is Mapper 2; configured for horizontal mirroring
  .byte $00      ;UNROM has no PRG RAM, though clone boards might
</pre>


UOROM, 256 KB:
UOROM, 256 KB:
  .segment "HEADER"
<pre>
    .byte "NES", $1A
.segment "HEADER"
    .byte $10      ;UOROM has 16 16k banks
  .byte "NES", $1A
    .byte $00      ;UOROM uses CHR RAM, so no CHR ROM
  .byte 16        ;UOROM has 16 16k banks
    .byte $20, $00  ;UOROM is Mapper 2
  .byte 0        ;UOROM uses CHR RAM, so no CHR ROM
    .byte $00      ;UOROM has no PRG RAM, though clone boards might
  .byte $20, $00  ;UOROM is Mapper 2; configured for horizontal mirroring
  .byte $00      ;UOROM has no PRG RAM, though clone boards might
</pre>
 
To use vertical [[mirroring]] instead of horizontal mirroring, change the <code>.byte $20, $00</code> to <code>.byte $21, $00</code>
 
Emulators using the iNES formats emulate [[PRG RAM circuit|RAM at $6000-$7FFF]] that official mapper 2 boards lack.
To keep yourself from making your program rely on this extra memory, you'll want to use an [[NES 2.0]] header so that compatible emulators correctly emulate open bus.
Here's the NES 2.0 header for 128 KB:
<pre>
.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 PRG RAM, no battery
  .byte $00      ;NTSC
  .byte $00      ;No special PPU
</pre>


== Bankswitching ==
== Bankswitching ==

Revision as of 18:58, 27 February 2013

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.

iNES header

Here is an iNES header for the UNROM mapper.

UNROM, 64 KB:

.segment "HEADER"
  .byte "NES", $1A
  .byte 4         ;UNROM has 4 16k banks
  .byte 0         ;UNROM uses CHR RAM, so no CHR ROM
  .byte $20, $00  ;UNROM is Mapper 2; configured for horizontal mirroring
  .byte $00       ;UNROM has no PRG RAM

UNROM, 128 KB (most common):

.segment "HEADER"
  .byte "NES", $1A
  .byte 8         ;UNROM has 8 16k banks
  .byte 0         ;UNROM uses CHR RAM, so no CHR ROM
  .byte $20, $00  ;UNROM is Mapper 2; configured for horizontal mirroring
  .byte $00       ;UNROM has no PRG RAM, though clone boards might

UOROM, 256 KB:

.segment "HEADER"
  .byte "NES", $1A
  .byte 16        ;UOROM has 16 16k banks
  .byte 0         ;UOROM uses CHR RAM, so no CHR ROM
  .byte $20, $00  ;UOROM is Mapper 2; configured for horizontal mirroring
  .byte $00       ;UOROM has no PRG RAM, though clone boards might

To use vertical mirroring instead of horizontal mirroring, change the .byte $20, $00 to .byte $21, $00

Emulators using the iNES formats emulate RAM at $6000-$7FFF that official mapper 2 boards lack. To keep yourself from making your program rely on this extra memory, you'll want to use an NES 2.0 header so that compatible emulators correctly emulate open bus. Here's the NES 2.0 header for 128 KB:

.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 PRG RAM, no battery
  .byte $00       ;NTSC
  .byte $00       ;No special PPU

Bankswitching

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 MUST be located in the fixed bank ($C000-$FFFF) so that they are always available. 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

See also