218. Mapper000 nrom
create
Lesson 218 of 356 · tests/chapter_02_rom_loading/test_218_mapper000_nrom.py
create emulator/cartridge/mapper000.py::Mapper000.read_prg for NROM CPU mapping.
Why this step exists
NROM-128 mirrors one 16 KiB bank across $8000-$FFFF; NROM-256 maps 32 KiB directly. Keeping this translation in the mapper prevents Cartridge from becoming hardware behavior. It uses the PRG and CHR payloads exposed by lesson 217.
Suggested implementation at this lesson boundary
from dataclasses import dataclass
PRG_ROM_START = 0x8000
PRG_ROM_END = 0xFFFF
NROM_128_SIZE = 16 * 1024
NROM_256_SIZE = 32 * 1024
CHR_ROM_SIZE = 8 * 1024
@dataclass(frozen=True)
class Mapper000:
prg_rom: bytes
chr_rom: bytes
def read_prg(self, addr: int) -> int:
if not (PRG_ROM_START <= addr <= PRG_ROM_END):
raise ValueError(
f"Address out of PRG ROM range: {addr:04X}"
)
if len(self.prg_rom) == NROM_128_SIZE:
offset = (addr - PRG_ROM_START) % NROM_128_SIZE
elif len(self.prg_rom) == NROM_256_SIZE:
offset = addr - PRG_ROM_START
else:
raise ValueError(
"Mapper000 supports only 16KB or 32KB PRG ROM"
)
return self.prg_rom[offset]Invariants: accept only CPU $8000-$FFFF and exactly 16 or 32 KiB PRG; mirror only the 16 KiB case; retain PRG/CHR constructor order. A common mistake is indexing prg_rom with the CPU address directly or implementing the translation on Cartridge. read_chr, PPU routing, CHR RAM, writes, mirroring, and bank switching are out of scope for this step.
Out of scope for this step
1. Lesson 219 adds `read_chr` for the PPU-side CHR range.
2. Lesson 220 adds mapper selection.
3. PPU routing, CHR RAM, writes, mirroring, and bank switching come later.Run this lesson
uv run pytest tests/chapter_02_rom_loading/test_218_mapper000_nrom.py -v