240. Alternancia de segunda escritura de ppuaddr

Implementa el comportamiento de dirección de doble escritura de PPUADDR ($2006).

Lección 240 de 356 · tests/chapter_03_ppu_memory_and_graphics_data/test_240_ppuaddr_second_write_toggle.py

Referencia

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

Archivo a actualizar

emulator/ppu/ppu.py

Estado a añadir

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

Por qué existe este paso

PPUADDR es el registro visible para la CPU en $2006. La CPU lo usa para establecer la dirección VRAM interna de la PPU. Esa dirección interna se usa más adelante mediante PPUDATA ($2007) para leer/escribir en la memoria de la PPU.

Idea importante

$2006 no es simplemente un registro de almacenamiento de un byte. Es un puerto de doble escritura.

La CPU escribe la dirección en dos partes

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

Ejemplo

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

Resultado tras ambas escrituras

vram_addr == $2000

Modelo importante del registro interno

La primera escritura a $2006 actualiza temp_vram_addr, no vram_addr. La segunda escritura a $2006 completa temp_vram_addr y lo copia en vram_addr.

Por qué second_write_toggle: la PPU debe recordar si la próxima escritura a $2006 es la primera o la segunda. En este tutorial, second_write_toggle significa:

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

Por qué el byte alto se enmascara con 0x3F

El espacio de direcciones de la PPU es de 14 bits

$0000-$3FFF

Solo los 6 bits inferiores del byte alto son útiles para este rango de direcciones. Por lo tanto, la primera escritura debería usar:

(value & 0x3F) << 8

Pseudocódigo de implementación sugerido

@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

            ...

Fuera de alcance

  • escritura de PPUDATA ($2007) a través de PpuBus
  • reinicio del toggle de escritura por parte de PPUSTATUS ($2002)
  • PPUSCROLL ($2005) usando este mismo toggle
  • comportamiento de incremento en 32 de PPUCTRL

Ejecutar esta lección

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