266. Tabla de ciclos de opcodes
Validar los metadatos de ciclos de opcodes antes de modificar CPU.step().
Lección 266 de 356 · tests/chapter_04_ppu_timing_and_vblank/test_266_opcode_cycles_table.py
Referencia
https://www.nesdev.org/wiki/Visual6502wiki/6502_all_256_OpcodesArchivo a actualizar
emulator/cpu/opcodes.pyPor qué existe este paso intermedio
El emulador ya tiene despacho de opcodes funcional a través de
OPCODE_TABLE[opcode] -> handler(cpu)No queremos refactorizar esa tabla todavía, porque las pruebas antiguas del capítulo de la CPU ya dependen de la ruta de ejecución de instrucciones actual.
En su lugar, este paso introduce la temporización como metadatos separados
OPCODE_CYCLES[opcode] -> base CPU cycle countEsto permite que un futuro CPU.step() devuelva ciclos sin cambiar cada manejador de instrucción ni reemplazar la tabla de despacho de opcodes existente.
Ejemplo de implementación sugerido
Copia esta tabla en emulator/cpu/opcodes.py. Fue creada a partir de la referencia de temporización de opcodes enlazada arriba:
OPCODE_CYCLES = [
7,6,0,8,3,3,5,5,3,2,2,2,4,4,6,6,
3,5,0,8,4,4,6,6,2,4,2,7,4,4,7,7,
6,6,0,8,3,3,5,5,4,2,2,2,4,4,6,6,
2,5,0,8,4,4,6,6,2,4,2,7,4,4,7,7,
6,6,0,8,3,3,5,5,3,2,2,2,3,4,6,6,
3,5,0,8,4,4,6,6,2,4,2,7,4,4,7,7,
6,6,0,8,3,3,5,5,4,2,2,2,5,4,6,6,
2,5,0,8,4,4,6,6,2,4,2,7,4,4,7,7,
2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,
3,6,0,6,4,4,4,4,2,5,2,5,5,5,5,5,
2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,
2,5,0,5,4,4,4,4,2,4,2,4,4,4,4,4,
2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,
3,5,0,8,4,4,6,6,2,4,2,7,4,4,7,7,
2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,
2,5,0,8,4,4,6,6,2,4,2,7,4,4,7,7]
Corrección importante
El primer valor debe ser 7 porque el opcode $00 es BRK, y BRK toma 7 ciclos de CPU. Si tu tabla copiada tiene 0 en el índice $00, corrígelo a 7 porque este emulador ya implementa BRK.
¿Qué es una tabla de ciclos de opcodes? Una tabla de ciclos de opcodes es una tabla de búsqueda de 256 entradas donde cada índice es un byte de opcode y cada valor es el número base de ciclos de CPU para ese opcode.
Ejemplo mínimo
OPCODE_CYCLES[0xEA] == 2 # NOP implied
OPCODE_CYCLES[0xA9] == 2 # LDA immediate
OPCODE_CYCLES[0x20] == 6 # JSR absoluteConcepto erróneo común
El conteo de ciclos no es lo mismo que la longitud de instrucción o el número de lecturas. Por ejemplo, JSR ocupa 3 bytes pero toma 6 ciclos de CPU.
Cómo aparece esto en el emulador
Current step shape:
opcode = cpu.fetch_byte()
handler = OPCODE_TABLE[opcode]
handler(cpu)
Future step shape, without refactoring OPCODE_TABLE:
opcode = cpu.fetch_byte()
handler = OPCODE_TABLE[opcode]
handler(cpu)
return OPCODE_CYCLES[opcode]Limitación importante
Estos son conteos de ciclos base. Algunas instrucciones después necesitan ciclos extra dinámicos, por ejemplo:
branch taken
branch crosses page
indexed load crosses pageNo resuelvas esas penalizaciones dinámicas en este paso. Primero haz que los metadatos de temporización base sean explícitos y comprobables.
Fuera de alcance
- modificar CPU.step() para que devuelva ciclos
- refactorizar las entradas de OPCODE_TABLE en dataclasses
- modificar las pruebas antiguas de instrucciones de la CPU
- penalizaciones dinámicas de ciclo por cruce de página o salto
- Console.step() avanzando la PPU por ciclos de CPU * 3
Ejecutar esta lección
uv run pytest tests/chapter_04_ppu_timing_and_vblank/test_266_opcode_cycles_table.py -v