Записки о NES / APU. Cчетчик кадров

Внезапно я осознал, что держать все в голове невозможно. Поэтому буду делать небольшие заметки об устройстве NES, которые я смог выяснить, изучая различные версии документаций и играясь с Visual 2A03. Некоторая информация, которую я здесь привожу, нигде не упоминается, но учитывается в современных эмуляторах.
Здесь я постарался описать работу счетчика кадров, который отвечает за генерацию сигналов для остальных частей APU в определенное время на кадре.

Работа счетчика и такты APU

Как и CPU, APU обрабатывает такты в две фазы, однако это происходит только по окончанию фазы \phi_2 у CPU. Это иллюстрирует диаграмма:

По началу \phi_1 обновляется счетчик. Он представляет из себя 15-разрядный сдвиговый регистр с обратной связью (странноватый подход). Биты 13 и 14 складываются и кладутся в начало при каждом сдвиге

Начальное значение счетчика устанавливается в 0x7fff.
Текущая фаза определяется по значению счетчика: 0x1061 для первой фазы, 0x3603 для второй, 0x2cd3 для третьей, 0x0a1f для четвертой (флаг устанавливается только в 4-фазном режиме), 0x7165 для пятой фазы. Установленный флаг определяет, какие сигналы будут генерироваться по \phi_2. Можно составить следующую таблицу:

Значение счетчика Номер такта Номер фазы Генерируемые сигналы
0x1061 3728 A QuarterFrame
0x3603 7456 B QuarterFrame, HalfFrame
0x2cd3 11185 C QuarterFrame
0x0a1f 14914 D QuarterFrame, HalfFrame
0x7185 18640 E QuarterFrame, HalfFrame

Флаг фазы D напрямую связан с флагом FrameIRQ: и по \phi_1, и по \phi_2 будет генерироваться прерывание, пока флаг установлен (при условии, что прерывания не запрещены). Также флаги фаз D и E устанавливают флаг сброса счетчика.
По сбросу счетчика также генерируется прерывание (если установлен 4-фазный режим и прерывания не запрещены).
В итоге диаграмма выглядит следующим образом:

Импульсы HalfFrame и QuarterFrame длятся только половину такта, поскольку все остальные части APU работают с той же частотой, что и CPU, в отличие от счетчика кадров.

Регистры

Режимы счетчика определяются регистром 0x4017:

Флаг запрета прерываний нельзя путать с флагом прерывания. Установка флага запрета прерывания приведет к немедленному сбросу флага прерывания. Чтение из 0x4015 также приводит к сбросу флага прерывания, но не затрагивает флаг запрета прерывания.
Помимо всего прочего, запись в 0x4017 приводит к генерации QuarterFrame и HalfFrame в 5-фазном режиме. В 4-фазном этого не происходит.
После записи в 0x4017 по следующему \phi_1 будет установлен флаг сброса счетчика, что повлечет за собой сброс регистра и вызов прерывания (если оно не запрещено) по следующему далее \phi_1.
Диаграмма выглядит следующим образом:

Однако может произойти следующее:

Из-за того, что запись пришлась на середину такта APU произошла задержка при сбросе счетчика. Такие же задержки происходят с генерацией сигналов четверти и половины кадра.
Люди, изучавшие документацию по 2A03/2A07, сразу же заметят ошибку: какое еще прерывание по записи в 0x4017?! И действительно, обычно при записи в этот регистр никакого прерывания не вызывается. Дело в том, что по сбросу счетчика FrameIRQ устанавливается в 1, только если установлен внутренний флаг. Этот флаг устанавливается по фазе D и сбрасывается при сбросе счетчика. Однако если при каких-то условиях он не будет сброшен (например по RESET), то мы увидим прерывание по 0x4017. Но как это протестировать — пока не понятно.

Двойная запись

Было бы очень интересно посмотреть, что произойдет, если записать в 0x4017 два такта подряд, либо через один такт. Второй вариант реализовать практически невозможно, но первый — легко. Для этого достаточно написать:

Что произойдет? Вот как будет это обрабатывать наш CPU:

В итоге произойдут две последовательных записи по адресу 0x4017. Что мы будем туда записывать? При чтении $4017 возвращает статус кнопок второго джойстика, либо открытую шину (0x40). В любом случае биты M и I будут сброшены.
Получаем диаграмму:

Либо, если добавить сдвиг:

Результат вполне логичный и подтверждает предположения, изложенные выше: по началу \phi_1 устанавливается флаг для сброса.

Эмуляторы

Насколько же хорошо эмуляторы учитывают все описанное выше? Большинство разработчиков стараются сделать их программы как можно более точными, поскольку многие игры NES очень зависят от этого и полагаются на всякие аппаратные гличи. Я написал простой тест, проверяющий генерацию QuarterFrame и HalfFrame по записи в 0x4017 значения 0x80. Все эмуляторы легко прошли тест.
Но что, если запись происходит именно тогда, когда сам счетчик кадров должен генерировать эти сигналы? Сигнал должен генерироваться лишь один раз. И тут все эмуляторы посыпались. И сложно их винить — в документации про это не сказано абсолютно ничего.
Диаграмма для теста 1:

Диаграмма для теста 2:

Диаграмма для теста 3:

Диаграмма для теста 4:

Тесты 5-8 повторяют 1-4 в 5-фазном режиме.
Таблица с результатами:

Эмулятор test_1 test_2 test_3 test_4 test_5 test_6 test_7 test_8
puNES 0.82 03.04.2014 TEST PASSED TEST PASSED TEST PASSED TEST PASSED TEST PASSED TEST PASSED TEST PASSED TEST PASSED
Nestopia — Undead Edition 1.45 30.07.2013 TEST PASSED TEST PASSED TEST PASSED TEST PASSED TEST PASSED TEST PASSED TEST PASSED TEST PASSED
vpnes 0.4 Build 117 (r242_p1) 09.04.2013 TEST PASSED TEST PASSED Программа уходит в бесконечный цикл 0_o Программа уходит в бесконечный цикл 0_o TEST FAILED TEST FAILED TEST PASSED TEST PASSED
FCEUX 2.2.2 24.09.2013 TEST PASSED TEST FAILED TEST PASSED TEST PASSED TEST PASSED TEST PASSED TEST FAILED TEST FAILED

test_apu.tar.gz

Заключение

Все особенности учесть невероятно сложно. Сейчас я понятия не имею, как вписать все сказанное выше в мой эмулятор. И ведь это лишь небольшой кусочек всего того, что необходимо правильно обрабатывать. Позднее я копну еще глубже и во всех направлениях, а результаты напишу в этом блоге.

VP