NES TDD

240. Переключатель второй записи PPUADDR

Реализовать поведение адреса с двумя записями в PPUADDR ($2006).

Урок 240 из 356 · tests/chapter_03_ppu_memory_and_graphics_data/test_240_ppuaddr_second_write_toggle.py

Справочник

https://www.nesdev.org/wiki/PPU_registers#PPUADDR

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

emulator/ppu/ppu.py

Состояние для добавления

vram_addr: int = 0
temp_vram_addr: int = 0
second_write_toggle: bool = False

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

PPUADDR — видимый для CPU регистр по адресу $2006. CPU использует его, чтобы задать внутренний адрес VRAM PPU. Этот внутренний адрес позже используется PPUDATA ($2007) для чтения/записи памяти PPU.

Важная идея

$2006 — это не простой однобайтовый регистр хранения. Это порт с двумя записями.

CPU записывает адрес двумя частями

first write  -> high byte
second write -> low byte

Пример

write $20 to $2006
write $00 to $2006

Результат после обеих записей

vram_addr == $2000

Важная модель внутренних регистров

Первая запись в $2006 обновляет temp_vram_addr, а не vram_addr. Вторая запись в $2006 завершает temp_vram_addr и копирует его в vram_addr.

Зачем second_write_toggle: PPU должен помнить, будет ли следующая запись в $2006 первой или второй записью. В этом руководстве second_write_toggle означает:

False -> next $2006 write is the first write / high byte
True  -> next $2006 write is the second write / low byte

Зачем старший байт маскируется значением 0x3F

Адресное пространство PPU 14-битное

$0000-$3FFF

Для этого диапазона адресов полезны только младшие 6 бит старшего байта. Поэтому первая запись должна использовать:

(value & 0x3F) << 8

Предлагаемый псевдокод реализации

@dataclass
class PPU:
    ...
    addr: int = 0
    vram_addr: int = 0
    temp_vram_addr: int = 0
    second_write_toggle: bool = False

    def write_register(self, addr: int, value: int) -> None:
        value = value & 0xFF

        match addr:
            ...
            case 0x2006:
                # Keep old simple register-field behavior for test compatibility.
                self.addr = value

                if not self.second_write_toggle:
                    self.temp_vram_addr = (
                        (self.temp_vram_addr & 0x00FF)
                        | ((value & 0x3F) << 8)
                    )
                    self.second_write_toggle = True
                else:
                    self.temp_vram_addr = (
                        (self.temp_vram_addr & 0x3F00)
                        | value
                    )
                    self.vram_addr = self.temp_vram_addr
                    self.second_write_toggle = False

            ...

Вне области действия

  • запись PPUDATA ($2007) через PpuBus
  • сброс переключателя записи через PPUSTATUS ($2002)
  • использование этого же переключателя в PPUSCROLL ($2005)
  • поведение инкремента на 32 из PPUCTRL

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

uv run pytest tests/chapter_03_ppu_memory_and_graphics_data/test_240_ppuaddr_second_write_toggle.py -v