266. Таблица тактов опкодов

Проверить метаданные тактов опкодов перед изменением CPU.step().

Урок 266 из 356 · tests/chapter_04_ppu_timing_and_vblank/test_266_opcode_cycles_table.py

Справочник

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

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

emulator/cpu/opcodes.py

Зачем нужен этот промежуточный шаг

В эмуляторе уже есть рабочая диспетчеризация опкодов через

OPCODE_TABLE[opcode] -> handler(cpu)

Мы пока не хотим рефакторить эту таблицу, потому что старые тесты главы о CPU уже зависят от текущего пути выполнения инструкций.

Вместо этого этот шаг вводит синхронизацию в виде отдельных метаданных

OPCODE_CYCLES[opcode] -> base CPU cycle count

Это позволит будущему CPU.step() возвращать такты, не изменяя каждый обработчик инструкций и не заменяя существующую таблицу диспетчеризации опкодов.

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

Скопируйте эту таблицу в emulator/cpu/opcodes.py. Она создана по справочнику тактов опкодов, ссылка на который приведена выше:

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

]

Важное исправление

Первое значение должно быть 7, потому что опкод $00 — это BRK, а BRK занимает 7 тактов CPU. Если в скопированной таблице на индексе $00 стоит 0, исправьте его на 7, потому что этот эмулятор уже реализует BRK.

Что такое таблица тактов опкодов? Таблица тактов опкодов — это таблица поиска на 256 записей, где каждый индекс — это байт опкода, а каждое значение — базовое число тактов CPU для этого опкода.

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

OPCODE_CYCLES[0xEA] == 2   # NOP implied
OPCODE_CYCLES[0xA9] == 2   # LDA immediate
OPCODE_CYCLES[0x20] == 6   # JSR absolute

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

Число тактов — не то же самое, что длина инструкции или число выборок. Например, JSR занимает 3 байта, но требует 6 тактов CPU.

Как это выглядит в эмуляторе

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]

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

Это базовые числа тактов. Некоторым инструкциям позже потребуются динамические дополнительные такты, например:

branch taken
branch crosses page
indexed load crosses page

Не пытайтесь решить эти динамические штрафы в этом шаге. Сначала сделайте метаданные базовой синхронизации явными и проверяемыми.

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

  • изменение CPU.step() так, чтобы он возвращал такты
  • переделка записей OPCODE_TABLE в dataclasses
  • изменение старых тестов инструкций CPU
  • динамические штрафы тактов за пересечение страницы или ветвление
  • продвижение PPU в Console.step() на такты CPU * 3

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

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