222. Cpu bus cartridge integration part2

Чтение PRG ROM картриджа через CpuBus, часть 2.

Урок 222 из 356 · tests/chapter_02_rom_loading/test_222_cpu_bus_cartridge_integration_part2.py

Предварительное условие

Поле cartridge, __post_init__ и подключение фабрики mapper'ов из урока 221 должны быть завершены до изменения пути чтения здесь.

Файл для обновления

emulator/bus/cpu_bus.py

Символ для обновления

emulator.bus.cpu_bus.CpuBus.read

Что реализует эта часть

- CpuBus routes CPU reads in $8000-$FFFF to mapper.read_prg(addr) when a
  cartridge-backed mapper exists
- CpuBus keeps the old program_rom=FakeROM path working
- CpuBus fails loudly if no PRG source is attached

Почему этот шаг нужен

CPU видит программную ROM картриджа по адресам CPU

$8000-$FFFF

Но байты PRG ROM картриджа хранятся начиная со смещения 0. Эту трансляцию выполняет mapper. CpuBus должен только определять, что адреса в диапазоне $8000-$FFFF принадлежат области PRG картриджа, а затем делегировать вызов mapper'у.

Правильный путь для картриджа

CpuBus.read($8000)
    -> mapper.read_prg($8000)
    -> Mapper000 translates to PRG offset $0000

Важное отличие от старого пути FakeROM

program_rom.read(addr - $8000)

использует смещение, потому что FakeROM — это простое тестовое устройство, а не mapper.

mapper.read_prg(addr)

использует полный адрес CPU, потому что mapper'ы реализуют трансляцию адресов CPU.

Распространённая ошибка

Не вызывайте mapper.read_prg(addr - 0x8000). Это передаст смещение коду, который ожидает адрес CPU в диапазоне $8000-$FFFF.

Предлагаемая реализация

def read(self, addr: int) -> int:
    # Read from CPU Bus.
    if 0x0 <= addr <= 0x1FFF:
        return self.ram.read(addr & 0x07FF)
    if 0x8000 <= addr <= 0xFFFF:
        if self.mapper is not None:
            return self.mapper.read_prg(addr)
        if self.program_rom is not None:
            return self.program_rom.read(addr - 0x8000)
        raise ValueError("No program ROM or cartridge attached")

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

Обоснование и инварианты

CpuBus отвечает за маршрутизацию по диапазонам адресов, а Mapper000.read_prg отвечает за трансляцию адресов NROM. Зеркалирование внутренней RAM остаётся неизменным. Чтения картриджа получают полный адрес CPU; устаревший MemoryDevice получает смещение от нуля; и настроенный источник обязателен на всём включительном диапазоне $8000-$FFFF. Путь mapper'а имеет приоритет только потому, что часть 1 запрещает одновременную настройку обоих источников, поэтому неоднозначной допустимой конструкции никогда не возникает.

Ещё одно распространённое заблуждение — скопировать правило деления по модулю на 16КБ из Mapper000 в CpuBus. Утверждение о зеркале $C000 — это доказательство того, что делегирование работает, а не новая обязанность шины.

Вне рамок

1. PPU register reads at $2000-$3FFF belong to Chapter 3.
2. PRG and mapper writes are not added here.
3. PPU/CHR routing, timing, and later behavior must not be anticipated.

Запустить этот урок

uv run pytest tests/chapter_02_rom_loading/test_222_cpu_bus_cartridge_integration_part2.py -v