266. Opcode cycles table
Validate opcode cycle metadata before changing CPU.step().
Lesson 266 of 356 · tests/chapter_04_ppu_timing_and_vblank/test_266_opcode_cycles_table.py
Reference
https://www.nesdev.org/wiki/Visual6502wiki/6502_all_256_OpcodesFile to update
emulator/cpu/opcodes.pyWhy this intermediate step exists
The emulator already has working opcode dispatch through
OPCODE_TABLE[opcode] -> handler(cpu)We do not want to refactor that table yet, because old CPU chapter tests already depend on the current instruction execution path.
Instead, this step introduces timing as separate metadata
OPCODE_CYCLES[opcode] -> base CPU cycle countThis lets a future CPU.step() return cycles without changing every instruction handler or replacing the existing opcode dispatch table.
Suggested implementation example
Copy this table into emulator/cpu/opcodes.py. It was created from the opcode timing reference linked above:
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]
Important correction
The first value must be 7 because opcode $00 is BRK, and BRK takes 7 CPU cycles. If your copied table has 0 at index $00, fix it to 7 because this emulator already implements BRK.
What is an opcode cycle table? An opcode cycle table is a 256-entry lookup table where each index is an opcode byte and each value is the base number of CPU cycles for that opcode.
Minimal example
OPCODE_CYCLES[0xEA] == 2 # NOP implied
OPCODE_CYCLES[0xA9] == 2 # LDA immediate
OPCODE_CYCLES[0x20] == 6 # JSR absoluteCommon misconception
Cycle count is not the same thing as instruction length or number of fetches. For example, JSR is 3 bytes but takes 6 CPU cycles.
How this appears in the emulator
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]Important limitation
These are base cycle counts. Some instructions later need dynamic extra cycles, for example:
branch taken
branch crosses page
indexed load crosses pageDo not solve those dynamic penalties in this step. First make the base timing metadata explicit and testable.
Out of scope
- changing CPU.step() to return cycles
- refactoring OPCODE_TABLE entries into dataclasses
- modifying old CPU instruction tests
- dynamic page-crossing or branch cycle penalties
- Console.step() advancing PPU by CPU cycles * 3
Run this lesson
uv run pytest tests/chapter_04_ppu_timing_and_vblank/test_266_opcode_cycles_table.py -v