267. CPU.step() возвращает такты

Заставить CPU.step() возвращать базовые такты инструкции.

Урок 267 из 356 · tests/chapter_04_ppu_timing_and_vblank/test_267_cpu_step_returns_cycles.py

Справочник

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

Файлы для обновления

emulator/cpu/cpu.py

Зачем нужен этот шаг

В эмуляторе теперь есть отдельная таблица OPCODE_CYCLES. Следующий элемент синхронизации — возвращение CPU.step() базового числа тактов выполненного им опкода.

Это готовит будущий вид Console.step():

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

Что такое базовое число тактов инструкции? Базовое число тактов инструкции — это обычное число тактов CPU, которое занимает опкод до добавления динамических штрафов.

Минимальные примеры

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

Распространённое заблуждение

Такты — не то же самое, что считанные байты. JSR занимает 3 байта, но требует 6 тактов. Не считайте такты внутри fetch_byte().

Пример предлагаемой реализации

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]

Почему это позволяет избежать большого рефакторинга

OPCODE_TABLE остаётся существующим отображением опкод -> обработчик. OPCODE_CYCLES — параллельная таблица метаданных. Старые тесты поведения инструкций должны продолжать проходить, потому что CPU.step() по-прежнему выполняет тот же обработчик.

Важное ограничение

Здесь возвращаются только базовые такты. Динамическая синхронизация намеренно выходит за рамки:

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

Они должны быть смоделированы позже отдельным шагом.

Выходит за рамки

  • переделка OPCODE_TABLE в записи dataclasses
  • добавление тактов в fetch_byte()
  • Console.step()
  • продвижение PPU на такты CPU * 3
  • динамические дополнительные такты

Запустить этот урок

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