This proposal for an NES mapper combines the runtime flexibility of CHR RAM with some of the tile animation capability of CHR ROM, while still using fewer state bits than (say) an MMC3. Certain parts are inspired by CPROM and the Famicom Disk System.
The pattern table is divided into 32 rows, each 256 bytes in size: $0000-$00FF, $0100-$01FF, ..., $1F00-$1FFF. The 32 KiB CHR RAM (a 62256 SRAM) is divided into 128 such rows. Most of the time, PPU $0000-$1FFF is mapped directly into CHR RAM $0000-$1FFF. There are two "windows" that override this direct mapping: a variable-size window of 1 to 32 rows and a fixed-size window of 1 row. PPU addresses within either window are mapped to another row elsewhere in the CHR RAM.
The fixed-size window maps one row at a time. It is useful for switching the player character's sprite cel. The variable-size window maps several rows starting to some place elsewhere in the CHR RAM. It is useful for animating parts of the background in a loop, like Super Mario Bros. 2, Super Mario Bros. 3, or Kirby's Adventure.
- $4800: Reserved for Famicom expansion sound chips such as the Namco 163
- $5000: Port address ($00-$0F)
- $5800: Port data ($00-$FF)
Ports 0-4 control the CHR bank windows. Port 5 controls nametable mirroring. Ports 6, 8, 10, 12, and 14 control PRG bankswitching. Port 9 controls control scanline counter IRQs. Ports 7, 13, and 15 are reserved.
Port 0, 1, 2: Variable-size window
The pattern table is divided into 32 rows, each 256 bytes in size: $0000-$00FF, $0100-$01FF, ..., $1F00-$1FFF. Most of this space is directly mapped into $0000-$1FFF of the CHR RAM.
- $00: First row that will be replaced ($00-$1F)
- $01: Number of rows that will be replaced, minus one (0-31)
- $02: Starting row in CHR RAM of the replacement ($00-$7F)
To disable this window, set the value of port 2 equal to the value of port 0.
Port 3, 4: Single-row window
A second window, 16 tiles in size, is useful for bankswitching a player's sprite cels.
- $03: PPU row that will be replaced ($00-$1F)
- $04: CHR RAM row to replace it with ($00-$7F)
This window takes priority over the variable-size window if they overlap. To disable it, set the value of ports 3 and 4 equal to the value of port 0.
Port 5: Mirroring
The four MMC1 nametable mirroring modes and a four-screen mode are available.
- 0: one-screen, lower bank of CIRAM
- 1: one-screen, upper bank of CIRAM
- 2: vertical mirroring (horizontal arrangement) from CIRAM
- 3: horizontal mirroring (vertical arrangement) from CIRAM
- 4-7: four-screen from CHR RAM $7000-$7FFF
When combined with sprite 0 or the optional IRQ feature, this allows four-screen mirroring and a status bar at the same time, as the playfield is in CHR RAM and the status bar is in CIRAM.
Port 6, 8, 10, 12, 14: PRG bank
The PRG side of the mapper is mostly independent of the rest, and several sub-mappers are possible with different capabilities to fit different CPLDs.
The most full-featured version of this mapper, similar to MMC5 or FME-7, has five switchable banks:
- $06: PRG bank at $6000
- $08: PRG bank at $8000
- $0A: PRG bank at $A000
- $0C: PRG bank at $C000
- $0E: PRG bank at $E000
Each of these registers has the format RBBB BBBB, where B controls PRG A19-A13 (8192 byte bank number) for this region and R chooses between the two PRG chip selects: 0 for PRG ROM or 1 for PRG RAM. Loss of M2 oscillation, detected with a circuit like this one, causes register $0E to revert to a value of $7F, mapping the last ROM bank in the cart into $E000-$FFFF.
Several cost-reduced versions of the PRG logic are possible.
One simpler mapper is comparable to UNROM:
- $06: PRG bank at $6000
- $08: Pair of PRG banks at $8000 and $A000
In this case, $08 would have the format RBBB BBBx, where x is assumed 0 for $08. $C000-$FFFF would be fixed to PRG ROM in the last two banks of the cart.
Another is comparable to MMC3:
- $06: Only bit 7 is implemented
- $08: PRG bank at $8000 or $C000
- $0A: PRG bank at $A000
- $0C: If bit 1 is set, swap $C000 and $8000
$C000-$FFFF would be fixed to $7E and $7F, putting PRG ROM in the last bank of the cart.
Port 9: IRQ
- $09: Set IRQ count
At the start of each scanline, the PPU freezes for a few cycles, and PPU A13 stays high for at least three consecutive cycles of PPU /RD. The mapper detects this and subtracts 1 from the value in $09 unless the value is $F0-$FF. While the value is 0, /IRQ is pulled low.
Programming tip: Reading from the nametables or palette during vertical or forced blanking will cause counts unless you write $FF to port $09.
Some implementations may count M2 cycles (1.8 MHz) instead of PPU /RD cycles (2.7 MHz) to save a pin. Cost-reduced versions may lack IRQ logic entirely.
Internally, the CHR RAM address is computed using two 5-bit adders and a multiplexer:
ppu_hi = PPU A12..A8 rel_rowno = ppu_hi - reg_0 if PPU A13 is true: CHR A14, A13, A12 = 111 CHR A11, A9, A8 = ppu_hi 3, 1, 0 CHR A10 = from mirroring logic elif ppu_hi == reg_3: CHR A14..A8 = reg_4 elif rel_rowno <= reg_1: CHR A14..A8 = reg_2 + rel_rowno else: CHR A14, A13 = 0 CHR A12..A8 = ppu_hi
CPLDs typically require one macrocell for each bit of state, plus a few more for intermediate results.
- Port address: 4 bits
- Variable-size window: 17 bits
- Fixed-size window: 12 bits
- Mirroring mode: 3 bits
- PRG banks: 40 bits (15-17 for reduced version)
- IRQ: 10 bits (2 for /RD counter, 8 for scanline counter)
Total: 86 bits, or 61-63 bits for versions with reduced PRG bank capability
If this were produced on an ASIC, the package would be roughly the same size as an MMC3.
- Inputs (21)
- CPU A14-A11, D7-D0, M2, R/W, PPU A13-A8, /RD
- Outputs (19)
- PRG A19-A13, PRG RAM /CE, PRG ROM /CE, CHR A14-A8, CHR RAM /CE, CIRAM /CE, /IRQ