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