267. CPU.step devuelve ciclos

Hacer que CPU.step() devuelva los ciclos base de instrucción.

Lección 267 de 356 · tests/chapter_04_ppu_timing_and_vblank/test_267_cpu_step_returns_cycles.py

Referencia

https://www.nesdev.org/wiki/Visual6502wiki/6502_all_256_Opcodes

Archivos a actualizar

emulator/cpu/cpu.py

Por qué existe este paso

El emulador ahora tiene una tabla OPCODE_CYCLES independiente. El siguiente puente de temporización es que CPU.step() devuelva el conteo de ciclos base del opcode que ejecutó.

Esto prepara la forma futura de Console.step():

cpu_cycles = cpu.step()
ppu.step(cpu_cycles * 3)
console.consume_nmi_if_requested()

¿Qué es un conteo de ciclos base de instrucción? Un conteo de ciclos base de instrucción es el número normal de ciclos de CPU que un opcode toma antes de que se añadan penalizaciones dinámicas.

Ejemplos mínimos

NOP implied      opcode $EA -> 2 cycles
LDA immediate   opcode $A9 -> 2 cycles
JSR absolute    opcode $20 -> 6 cycles

Concepto erróneo común

Los ciclos no son lo mismo que los bytes leídos. JSR ocupa 3 bytes pero toma 6 ciclos. No cuentes ciclos dentro de fetch_byte().

Ejemplo de implementación sugerido

from emulator.cpu.opcodes import OPCODE_CYCLES, OPCODE_TABLE


class CPU:
    ...

    def step(self) -> int:
        opcode = self.fetch_byte()
        handler = OPCODE_TABLE.get(opcode)
        if handler is None:
            raise NotImplementedError(f"Opcode {opcode:02X} not implemented")

        handler(self)
        return OPCODE_CYCLES[opcode]

Por qué esto evita una gran refactorización

OPCODE_TABLE sigue siendo el mapeo existente de opcode a manejador. OPCODE_CYCLES es una tabla de metadatos paralela. Las pruebas antiguas de comportamiento de instrucciones deberían seguir pasando porque CPU.step() sigue ejecutando el mismo manejador.

Limitación importante

Esto devuelve solo ciclos base. La temporización dinámica está intencionalmente fuera de alcance aquí:

branch taken penalties
branch page-cross penalties
indexed load page-cross penalties

Esos deberían modelarse después como un paso separado.

Fuera de alcance

  • refactorizar OPCODE_TABLE en entradas de dataclass
  • añadir ciclos a fetch_byte()
  • Console.step()
  • avance de la PPU por ciclos de CPU * 3
  • ciclos extra dinámicos

Ejecutar esta lección

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