267. Cpu step returns cycles

Make CPU.step() return base instruction cycles.

Lesson 267 of 356 · tests/chapter_04_ppu_timing_and_vblank/test_267_cpu_step_returns_cycles.py

Reference

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

Files to update

emulator/cpu/cpu.py

Why this step exists

The emulator now has a standalone OPCODE_CYCLES table. The next timing bridge is for CPU.step() to return the base cycle count for the opcode it executed.

This prepares the future Console.step() shape:

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

What is a base instruction cycle count? A base instruction cycle count is the normal number of CPU cycles an opcode takes before dynamic penalties are added.

Minimal examples

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

Common misconception

Cycles are not the same as bytes fetched. JSR is 3 bytes but takes 6 cycles. Do not count cycles inside fetch_byte().

Suggested implementation example

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]

Why this avoids a large refactor

OPCODE_TABLE remains the existing opcode -> handler mapping. OPCODE_CYCLES is a parallel metadata table. Old instruction behavior tests should continue to pass because CPU.step() still executes the same handler.

Important limitation

This returns base cycles only. Dynamic timing is intentionally out of scope here:

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

Those should be modeled later as a separate step.

Out of scope

  • refactoring OPCODE_TABLE into dataclass entries
  • adding cycles to fetch_byte()
  • Console.step()
  • PPU advancement by CPU cycles * 3
  • dynamic extra cycles

Run this lesson

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