CHR ROM vs. CHR RAM
An NES cartridge has at least two memory chips on it: PRG (connected to the CPU) and CHR (connected to the PPU). There is always at least one PRG ROM, and there may be an additional PRG RAM to hold data. Some cartridges have a CHR ROM, which holds a fixed set of graphics tile data available to the PPU from the moment it turns on. Other cartridges have a CHR RAM that holds data that the CPU has copied from PRG ROM through a port on the PPU. (A few have both CHR ROM and CHR RAM.)
The PPU can see only 8 KiB of tile data at once. So once your game has more than that much tile data, you'll need to use a mapper to load more data into the PPU. Some mappers bankswitch the CHR ROM so that the PPU can see different "pages". Other mappers are designed for CHR RAM; they let the CPU switch to a PRG ROM bank containing CHR data and copy it to CHR RAM.
CHR ROM
Advantages
- Takes less time and code to get at least something displayed. The "hello world" program for a CHR ROM mapper is shorter.
- Switching tiles is fast, needs no vblank time, and can be done mid-frame or even mid-scanline.
- Can be used together with MMC3 and PRG RAM on a cartridge. Only two games have a board with MMC3 + PRG RAM + CHR RAM, and both are Japan-exclusive RPGs. Only three NES games use TGROM (MMC3 + CHR RAM) and two NES games use TQROM (MMC3 + CHR RAM + CHR ROM) even without PRG RAM.
Applications
Smash TV's title screen alone uses more than 8 KB of tile data.
A horizontal status bar might use a separate set of tiles from the playfield. This needs either a mapper with a raster interrupt or a sprite 0 overlap trigger. (e.g. Super Mario Bros. 3)
A game that scrolls in all four directions will often have artifacts on one side of the screen because the NES doesn't have enough VRAM to keep the "seam" where new map data is loaded clean. To hide this, a game such as Jurassic Park might display tiles from a blank pattern table for the first or last 8 to 16 scanlines.[1]
In some pseudo-3D games, each row of the floor texture can be stored in a separate bank. Both CHR ROM and CHR RAM let the program switch the background between CHR banks in $0000 and $1000 using PPUCTRL ($2000),[2] but CHR ROM allows far more than two banks to be used, as seen in a forward-scrolling shooter called Cosmic Epsilon.
A drawback of using CHR ROM is that the split between PRG ROM and CHR ROM fragments your data, but it can be worked around. If your PRG ROM is slightly bigger than a power of two, but you have a bit of spare CHR ROM left, you can stash the data in CHR ROM and read it out through $2006/7. For instance, Super Mario Bros. keeps its title screen map data at the end of CHR ROM and copies it into PRG RAM to draw it. However, you can't read this data while rendering is turned on, and due to the DMA glitch, reading $2007 while playing sampled sound is unreliable.
CHR RAM
Advantages
- Can switch tiles in small increments, and the granularity of switching does not depend on the mapper's complexity.
- Tile data can be compressed in ROM.
- Tile data can be otherwise generated in real time.
- Only one chip to rewire and put on the board when replicating your work on cartridge.
- All data is stored in one address space, as opposed to a small amount being inaccessible when rendering is on and unreliable when DPCM is on.
Applications
A few games allow the user to edit tiles. These include paint programs such as Videomation and Color a Dinosaur, or moddable titles such as a shooter maker released in Japan.
CHR RAM allows drawing text in a proportional font. Not a lot of NES games used this, but something like Word Munchers or Fraction Munchers might benefit.
Contra's graphics are compressed using a run-length encoding scheme.
Hatris and Shanghai II have a large playfield where large stacks of objects are not aligned to an 8x8 tile grid.
Qix and Elite have vector graphics. Qix has horizontal lines, vertical lines, and filled areas that aren't aligned to a tile grid, and Elite's graphics are wireframe 3D.
Some CHR ROM games restrict which objects can be seen together because of what bank their CHR data is stored in. CHR RAM has no such problem because any object's can be loaded at any position. The extreme of this is Battletoads, which keeps only one frame of each player's animation loaded. To switch frames of animation, it copies them into CHR RAM. But then it has to turn off rendering at the top of the screen, creating a blank strip in the status bar, in order to fit all the time. If you are using 8x16 sprites, there is enough space in $0000-$0FFF to hold the current and next cel for all 64 sprites. This effect is used even more intensely in platforms with dual-ported VRAM (TurboGrafx-16, Game Boy Advance) and in platforms which have hardware-assisted memory copying to video ports other than OAM (Genesis, Super NES, Game Boy Color).
Effects possible with both mappers
Tile animation. Think of the animated ? blocks in Super Mario Bros. 3 or the animated grass in Super Mario Bros. 2, or the independent parallax scrolling of distant repeating tile patterns in Batman: Return of the Joker, Crisis Force, and Metal Storm.
With CHR ROM, you'd make a separate bank for each frame of animation that you want to display, or for each offset between the distant pattern's scroll position and the foreground pattern's scroll position. It works best on a mapper with CHR banks smaller than 4 KB, such as MMC3.
With CHR RAM, you'd copy the tiles into VRAM as needed. Assuming moderately unrolled VRAM loading code, the NTSC vblank is long enough to copy about 160 bytes per frame plus the OAM display list without turning rendering on late. This is enough for 10 tiles alone, or 8 tiles plus a nametable row, nametable column, or entire palette.
Switching to CHR RAM
It's straightforward to change an existing project using NROM to use CHR RAM.
- Make sure at least 8300 bytes are free in the PRG ROM. We want to make sure CHR RAM works before dealing with mapper bankswitch.
- Remove the CHR ROM data from your build process, whether it be
.incbin "mytiles.chr"
after the IRQ vector orcopy /b game.prg+mytiles.chr game.nes
after assembly and linking. - In your program's iNES header, set the number of CHR banks to 0 (which signifies CHR RAM).
- In your program's init code, after the PPU has stabilized but before you turn on rendering,
jsr copy_mytiles_chr
which is listed below. - Rebuild your project. The size should end up as 32,784 bytes (16 bytes of header and 32,768 bytes of PRG ROM).
; for ca65 PPUADDR = $2006 PPUDATA = $2007 .segment "CODE" .proc copy_mytiles_chr src = 0 lda #<mytiles_chr ; load the source address into a pointer in zero page sta src lda #>mytiles_chr sta src+1 ldy #0 ; starting index into the first page sty PPUADDR ; load the destination address into the PPU sty PPUADDR ldx #32 ; number of 256-byte pages to copy loop: lda (src),y ; copy one byte sta PPUDATA iny bne loop ; repeat until we finish the page inc src+1 ; go to the next page dex bne loop ; repeat until we've copied enough pages rts .endproc .segment "RODATA" mytiles_chr: .incbin "mytiles.chr"
A few emulators do not emulate iNES Mapper 000 (NROM) with CHR RAM, as the original NROM board needs to be rewired slightly to take a 6264 SRAM chip in the CHR holes. For these, you'll need to use iNES Mapper 034 (BNROM), which has CHR RAM and 32 KiB bank switching.
The next step after this is to switch to a mapper that allows switching PRG banks. See Programming UNROM and Programming MMC1.