263. Enrutamiento de escritura PRG en el bus de la CPU

Enrutar escrituras en el espacio PRG de la CPU a través del mapper antes de las pruebas de NMI de la CPU.

Lección 263 de 356 · tests/chapter_04_ppu_timing_and_vblank/test_263_cpu_bus_prg_write_routing.py

Archivos a actualizar

emulator/cartridge/mapper_interface.py
emulator/cartridge/mapper000.py
emulator/bus/cpu_bus.py

Por qué existe este paso

La siguiente prueba de interrupción de la CPU necesitará colocar los bytes del vector NMI en

$FFFA = NMI vector low byte
$FFFB = NMI vector high byte

Esas direcciones se encuentran dentro del área PRG de la CPU

$8000-$FFFF

Antes de probar CPU.interrupt_nmi(), necesitamos que el comportamiento de escritura del bus de la CPU en ese rango sea explícito y seguro.

¿Qué es el enrutamiento de escritura PRG? El enrutamiento de escritura PRG significa que CpuBus no modifica directamente los bytes de la ROM del cartucho. En su lugar, las escrituras en $8000-$FFFF se reenvían al mapper del cartucho:

CpuBus.write($8000-$FFFF, value)
    -> mapper.write_prg(addr, value)

Ejemplo mínimo

CPU writes $8000 = $12
CpuBus calls mapper.write_prg($8000, $12)
Mapper000/NROM ignores it because it has no PRG write registers
Later mappers may treat the same write as a bank-switch command

Concepto erróneo común

Escribir en $8000 no significa que la CPU cambie bytes de la ROM. En cartuchos reales, las escrituras en este rango normalmente son ignoradas por placas simples o interpretadas como señales de control del mapper por placas más avanzadas.

Separación importante

Real cartridge path:
    CpuBus.write($8000-$FFFF) -> mapper.write_prg(...)

Tutorial FakeROM path:
    CpuBus.write($8000-$FFFF) -> FakeROM.write(addr - $8000, value)

La ruta FakeROM es solo una conveniencia para la configuración de pruebas. Permite que las próximas pruebas de interrupciones instalen bytes de vector en $FFFA/$FFFB sin hacer que la PRG ROM real de Mapper000 sea escribible.

Ejemplos de implementación sugeridos

class MapperInterface(Protocol):
    def read_prg(self, addr: int) -> int:
        ...

    def write_prg(self, addr: int, value: int) -> None:
        ...

    def read_chr(self, addr: int) -> int:
        ...

    def write_chr(self, addr: int, value: int) -> None:
        ...

class Mapper000:
    def write_prg(self, addr: int, value: int) -> None:
        if not (PRG_ROM_START <= addr <= PRG_ROM_END):
            raise ValueError(f"Address out of PRG ROM range: {addr:04X}")

        # Mapper000/NROM has no writable PRG registers.
        # Real hardware ignores writes to PRG ROM space, so we ignore them too.
        return

class CpuBus:
    def write(self, addr: int, value: int) -> None:
        ...

        if 0x8000 <= addr <= 0xFFFF:
            if self.mapper is not None:
                self.mapper.write_prg(addr, value)
                return

            if self.program_rom is not None:
                self.program_rom.write(addr - 0x8000, value)
                return

        raise ValueError(f"Unsupported CPU bus write: {addr:04X}")

Fuera de alcance

  • Comportamiento de CPU.interrupt_nmi()
  • Consumo de la solicitud NMI de la PPU por la CPU
  • Conmutación de bancos Mapper001/MMC
  • Hacer que la PRG ROM real sea escribible

Ejecutar esta lección

uv run pytest tests/chapter_04_ppu_timing_and_vblank/test_263_cpu_bus_prg_write_routing.py -v