249. Búfer de lectura de PPUDATA

Implementa el comportamiento de lectura con búfer de PPUDATA ($2007).

Lección 249 de 356 · tests/chapter_03_ppu_memory_and_graphics_data/test_249_ppudata_read_buffer.py

Referencia

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

Archivo a actualizar

emulator/ppu/ppu.py

Estado a añadir

ppu_data_buffer: int = 0

Por qué existe este paso

PPUDATA no es un registro simple normal. Es un puerto visible para la CPU hacia la memoria de la PPU. En la mayoría de las lecturas de memoria de la PPU, la NES devuelve el búfer de lectura interno antiguo y después recarga ese búfer desde la dirección actual de memoria de la PPU.

Comportamiento normal de lectura con búfer

read $2007:
    value = ppu_data_buffer
    ppu_data_buffer = ppu_bus.read(vram_addr)
    vram_addr += increment
    return value

Esto significa que la primera lectura suele devolver datos antiguos/obsoletos del búfer, y la segunda lectura devuelve el byte que se cargó con la primera lectura.

Ejemplo

ppu_bus[$2000] = $AA
ppu_bus[$2001] = $BB
ppu_data_buffer = $00
vram_addr = $2000

read $2007 -> returns $00, buffer becomes $AA, vram_addr becomes $2001
read $2007 -> returns $AA, buffer becomes $BB, vram_addr becomes $2002

Campo de compatibilidad/depuración

El modelo anterior del tutorial tenía data como un valor simple de PPUDATA. Conserva self.data como campo de compatibilidad/depuración que contiene el valor devuelto por la última

lectura de PPUDATA, pero el comportamiento real debería usar

ppu_data_buffer
ppu_bus.read(vram_addr)

Pseudocódigo de implementación sugerido

@dataclass
class PPU:
    ...
    data: int = 0  # Compatibility/debug: last PPUDATA value
    ppu_data_buffer: int = 0

    def read_register(self, addr: int) -> int:
        match addr:
            ...
            case 0x2007:
                value = self.ppu_data_buffer
                self.ppu_data_buffer = self.ppu_bus.read(self.vram_addr)

                increment = 32 if self.ctrl & CTRL_VRAM_INCREMENT_BY_32 else 1
                self.vram_addr = (self.vram_addr + increment) & 0x3FFF

                # Preserve old compatibility/debug field.
                self.data = value
                return self.data

Nota importante para el futuro

Las lecturas de paleta de $3F00-$3FFF son una excepción en el hardware real. Devuelven el byte de paleta inmediatamente en lugar de devolver el valor retardado del búfer. Esa excepción no se implementa de forma intencionada en este test.

Fuera del alcance

  • excepción de lectura de paleta
  • precisión de la RAM de paleta
  • escrituras de PPUDATA, ya probadas anteriormente

Ejecutar esta lección

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