Chips

Симуляторы чипов на уровне вентилей: M6502Core (6502), APUSim (2A03/2A07 CPU+звук), PPUSim (2C02/2C07 PPU) и MMC1

Здесь расположены функциональные симуляторы больших микросхем и ядер.

M6502Core

Эмулятор процессорного ядра MOS 6502 на уровне вентилей.

Да, всё так просто. Мы берём логические схемы 6502 из Wiki и просто повторяем их на C++.

Чтобы понять, что здесь происходит, достаточно понимать, как работают схемы. Код просто повторяет их работу.

Подходы к симуляции

Некоторые модули симулируются сразу с выдачей значений на выходах. А для некоторых симуляция и выдача значений разделены для удобства.

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

Если у модуля мало входов/выходов, они передаются как параметры. Если входов/выходов много, в качестве параметра используется массив, индексируемый определениями enum.

Комбинационные и последовательные схемы

Как известно, схемы бывают двух типов: простые однонаправленные каскады вентилей (NOR, NAND) и циклические (последовательные) схемы на триггерах (Flip/flops).

Комбинационные схемы симулируются просто повторением работы вентилей. Без всяких хитростей.

К последовательным схемам (триггерам, FF) применяется следующий подход:

Следует отметить, что в 6502 довольно много скрытых и довольно запутанных циклов, например:

Скрытые запутанные циклы в 6502 (t1_ff)

Шины

Модули, подключённые к внутренним шинам, требуют особого подхода и определённого порядка исполнения, как написано здесь: https://github.com/emu-russia/breaks/blob/master/BreakingNESWiki_DeepL/6502/context_control.md

Следует также учитывать случай, когда несколько источников (например, регистры) одновременно выставляют свои значения на одну и ту же шину. Для решения таких ситуаций («конфликтов шины») необходимо использовать правило «Ground wins».

Здесь учитывается особенность 6502, при которой шины «предзаряжаются» во время PHI2. Это требуется для формирования констант (например, адреса стека, адреса прерывания). Зарядка выполняется в самом начале симуляции.

Оптимизация

M6502Core с некоторой натяжкой можно назвать пригодным для приложений реального времени.

Для оптимизации используются следующие подходы:

Узким местом является random logic, на которую уходит 50–60 % времени вычислений.

Кроме того, сейчас обе части симулируются по 2 раза за каждый полутакт, чтобы стабилизировать защёлки (latches).

APUSim

Симулятор APU на уровне вентилей.

Примечание: в проекте Breaks термин APU относится ко всей микросхеме CPU (2A03/2A07), которая включает ядро 6502 и сам APU. Поэтому здесь APU и CPU — синонимы.

Подходы к симуляции

В целом все подходы обкатываются на M6502Core, в плане APU ничего особенно нового нет. Мы берём схему и повторяем её работу на C++. Сначала как-нибудь, а потом стараемся оптимизировать.

Все схемы APU можно найти здесь: https://github.com/emu-russia/breaks/tree/master/BreakingNESWiki_DeepL/APU

Чтобы упростить понимание, на следующем изображении показаны «слои», в которых симулируются отдельные части APU:

Слои симуляции APU (apu_layers)

PPUSim

Симулятор PPU на уровне вентилей.

Подходы к симуляции

В целом все подходы обкатываются на M6502Core, в плане PPU ничего особенно нового нет. Мы берём схему и повторяем её работу на C++. Сначала как-нибудь, а потом стараемся оптимизировать.

Все схемы PPU можно найти здесь: https://github.com/emu-russia/breaks/tree/master/BreakingNESWiki_DeepL/PPU

Чтобы упростить понимание, на следующем изображении показаны «слои», в которых симулируются отдельные части PPU:

Слои симуляции PPU (ppu_layers)