← Третья часть

Сколько себя помню, я любил играть в LEGO. Больше всего меня восхищало то, что из одного и того же набора типов блоков можно построить что угодно: главными были не блоки, а воображение или план, показывающий, в каком порядке их соединять. Мой проект работает аналогичным образом: один набор исходников, хороший план и множество различных способов собирать и тестировать его.

LEGO

В этой части я расскажу об окружениях и их взаимосвязи.

Один и тот же код везде

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

  • ModelSim для потактово точной симуляции HDL с волновым выводом

  • Verilator для быстрой симуляции на C++, с тестовой обвязкой (calctest) и другими инструментами

  • Десктопное приложение на Qt для интерактивной работы с интегрированным отладчиком на уровне исходников

  • Qt WebAssembly для запуска калькулятора в браузере с попиксельной точностью

  • Quartus для синтеза и записи на физическую плату FPGA

Каждое из окружений позволило выявлять свой класс багов. ModelSim отлавливает проблемы таймингов на уровне сигналов. Verilator обладает достаточной скоростью для проверки тысяч тестовых векторов. Симулятор на Qt позволяет удобно пользоваться интерактивной отладкой. WebAssembly доказывает работоспособность кода в совершенно иной среде исполнения, в которой видением приложения можно поделиться со всем миром. Целевая платформа FPGA, реальная плата — это то, что можно потрогать, окончательная реализация идеи.

Типичная отладочная сессия может выглядеть так: пишем или изменяем микрокод функции; собираем его; прогоняем тестовые векторы через Verilator при помощи calctest, чтобы найти проблемы и регрессии; переходим в приложение на Qt, если что-то поломалось и требует отладки на уровне исходников; проверяем тайминги сигналов в ModelSim, если проблема похожа на аппаратную, и выполняем синтез в Quartus, когда код готов для FPGA.

ModelSim

ModelSim — это симулятор HDL, созданный компанией Mentor Graphics (основана в 1981 году в штате Орегон, в 2017 году куплена Siemens). Он обеспечивает потактово точную симуляцию дизайнов на Verilog и VHDL, показывающую все сигналы на каждом фронте тактового сигнала. Это первый инструмент, который я запускаю, когда что-то идёт не так на аппаратном уровне: ничто не сравнимо с наблюдением за тем, как работают настоящие wire-сигналы.

Бесплатная версия ModelSim поставляется с Quartus. В папке modelsim/ содержится готовая к запуску конфигурация: пакетный файл запускает ModelSim с Tcl-скриптом, компилирующим все исходники SystemVerilog; затем он запускает набор тестов и симуляцию. Для изучения результатов в ModelSim есть собственное приложение для просмотра волновых форм.

Другой тип прогона симуляции возможен через Makefile. В нём используется Verilator и генерируется файл трассировок, который можно просмотреть в GtkWave. Размеры файлов трассировок быстро растут. Один прогон нескольких тысяч тактов может привести к генерации сотен мегабайт трассировок, поэтому трассировки ограничиваются областью, в которой они действительно нужны.

Verilator

Verilator — это бесплатный опенсорсный компилятор Verilog, транслирующий SystemVerilog в потактово точный C++. Первую его версию разработала в 1994 году Digital Equipment Corporation для параллельной симуляции Verilog процессора Alpha и модели CPU на C. Автор этой версии Уилсон Снайдер опубликовал исходники и продолжил разработку, добавив вывод SystemC. После того, как примерно в 20024-2005 году код был переписан на C++ (версия 3.0), этот инструмент превратился из транслятора в полнофункциональный компилятор. Сегодня он выполняет однопоточную симуляцию примерно в сто раз быстрее, чем интерпретирующие симуляторы Verilog. Разработчик пишет на C++ обёртку, управляющую тактовым генератором и входными контактами, а сгенерированная модель выполняется как нативный двоичный файл.

Обёртка sim_main.cpp из папки verilog/ управляет верхним модулем. Базовый цикл симуляции прост:

while (!contextp->gotFinish()) {
    contextp->timeInc(1);
    top->CLOCK_50 = !top->CLOCK_50;
    top->eval();
    // read outputs, check assertions
}

Verilator можно использовать и в качестве линтера. Если запустить его с -lint-only -Wall для указанного файла Verilog, то он сможет находить ошибки быстрее, чем любой инструмент для синтеза: отсутствующие ветвления default в конструкциях case, неуправляемые сигналы, косвенные объявления wire-сигналов. Прежде, чем браться за Quartus, я прогнал через него каждый файл.

В проекте используются две версии Verilator: v5.x для десктопной сборки и v4.228 для сборки WebAssembly (последний релиз 4.x, не имевший поддержки потоков). Команда make qt в папке verilog/ генерирует код C++ для целевой платформы по умолчанию.

Qt

Qt — это фреймворк C++ для сборки кроссплатформенных приложений. Одни и те же исходники без внесения изменений компилируются для Windows, Linux, macOS и Android. Я использовал его в своей работе почти два десятка лет (поэтому и инстинктивно обращаюсь к нему каждый раз, когда мне нужен GUI) и он остаётся наиболее практичным из известных мне выбором для подобных проектов.

Qt разрабатывается Qt Company (изначально Trolltech, основана в Норвегии), предлагающей коммерческое лицензирование и бесплатную опенсорсную лицензию для некоммерческого использования. У проекта отличная документация и исчерпывающая библиотека классов (согласно официальному списку, более двух тысяч классов, однако на практике я регулярно использую примерно пятьдесят).

В моём проекте Qt обёртывает сгенерированный Verilator код на C++ в GUI-приложение, имеющее два основных компонента: виджет калькулятора, который рендерит ЖК-дисплей и обрабатывает ввод с клавиатуры, а также панель отладчика.

Qt-приложение включает в себя и отладчик микрокода уровня исходников с контрольными точками по именам символов, изучением регистров и исполнением тестовых векторов. В части 7, где мы начнём писать микрокод, отладчик будет рассмотрен более подробно. Для запуска внутри симулятора FPGA отладчика уровня исходников для микрокода собственного CPU потребовалось много труда, зато это сильно упростило разработку движка.

WebAssembly

WebAssembly — это формат двоичных команд, запускаемый во всех современных браузерах. Это не JavaScript: на этапе загрузки он компилируется в нативный машинный код и выполняется почти с нативной скоростью. Эта технология выросла из asm.js — ограниченного подмножества JavaScript, которое инженер Mozilla Алон Закаи разработал в 2013 году в качестве целевой платформы компиляции кода на C++. Технология работала, но движкам браузеров приходилось распознавать и специально оптимизировать её. В 2015 году Google, Mozilla, Microsoft и Apple решили спроектировать вместо этой технологии настоящий двоичный формат, который браузеры могли бы компилировать, ничего не гадая. В декабре 2019 года W3C стандартизировал результат их работы. Исходный компилятор Закаи Emscripten по-прежнему остаётся основным тулчейном для компиляции C++ в Wasm.

Qt 6 поддерживает Wasm в качестве целевой платформы сборки. Достаточно сменить целевую платформу сборки Qt Creator с Desktop на WebAssembly, пересобрать тот же проект Calculator.pro, и калькулятор запустится в браузере. Вся симуляция Verilator работает на стороне клиента: никакого сервера и бэкенда, только браузер, выполняющий загрузку и JIT-компиляцию двоичного файла Wasm.

Но здесь есть один тонкий момент: Wasm выполняется в песочнице и не может читать файловую систему хоста. Содержимое ROM добавлено в виде ресурсов Qt и при запуске записывается в виртуальную систему. После этого сборки для браузера и десктопа ведут себя идентично.

Интерактивное демо можно запустить на baltazarstudios.com/calculator.

Quartus

Quartus II 13.0 SP1 синтезирует дизайн для устройства Cyclone II и программирует плату FPGA через USB Blaster. Версия Web бесплатна и обладает всеми нужными нам функциями. GUI как будто пришёл из прошлого века, но вполне справляется со своей задачей.

У проекта есть два режима программирования. Режим JTAG (правый разъём, файл .sof) загружается в ОЗУ FPGA (быстрая и неразрушающая, сбрасывается при отключении питания). Режим Active Serial (левый разъём, файл .pof) программирует флэш-память конфигурации (сохраняющуюся при отключении питания). При разработке по умолчанию используется режим JTAG. Режим .pof пригождается, когда прошивка уже готова к постоянному хранению.

Целевая платформа FPGA

Наша аппаратная целевая платформа — это Altera Cyclone II EP2C5T144C8 на макетной плате с eBay за 5 долларов. Чип содержит 4608 логических элементов, 26 блоков памяти M4K и 89 контакта ввода-вывода, чего с избытком хватает для нашего калькулятора. В части 8 подробно рассказывается о модификациях платы и физической сборке.

Altera Cyclone II
Altera Cyclone II

calctest: замыкаем цикл тестирования

Обвязка calctest пропускает сгенерированные Proto тестовые векторы через симуляцию Verilated и сравнивает результаты:

# Генерация тестовых векторов
cd Pathfinding/Proto && ./proto -t -a > all.txt

# Запуск для симуляции
cd calctest && ./calctest ../Pathfinding/Proto/all.txt

Те же самые тестовые векторы можно также загрузить в Qt-симулятор и интерактивно выполнить пошагово, что полезно при ошибках в определённых вычислениях и если вы хотите посмотреть, что делает CPU при каждой команде.

Это замкнутый цикл: Proto генерирует ожидаемые результаты, calctest верифицирует возвращающее их оборудование, а Qt-отладчик позволяет находить команду, в которой результаты разошлись.

Что обеспечивает этот фреймворк

Все перечисленные выше инструменты бесплатны. Verilator и GtkWave выложены в опенсорс. У Qt есть бесплатная лицензия для некоммерческого использования. Quartus lite и ModelSim можно бесплатно скачать с сайта Intel/Altera. Единственная стоимость этого программного стека заключается во времени, потраченном на его установку и настройку.

Система разработки выполняется в Windows 10/11 с WSL2 для Verilator, GtkWave и ассемблерных инструментов. Qt Creator работает в Windows нативно. ModelSim работает в Windows. Файлы исходников FPGA расположены в разделе Windows, доступ к ним из WSL2 возможен через /mnt/c/.

В части 5 мы рассмотрим первый аппаратный прототип (печатную плату, клавиатуру, дисплей).

Часть 5: Оборудование

Всё, что мы делали ранее, находилось в ПО. Код FPGA выполнялся в ModelSim и Verilator. Раскладка клавиш существовала в виде макета на C#. Арифметический движок был написан на C++. Всё это было полезно, но у программ нет физических кнопок, которые можно нажимать.

Всё изменилось после первого заказа печатной платы.

От схем к плате

Первая аппаратная цель была простой: плата с матрицей клавиатуры и OLED-дисплеем, подключенная к макетной плате EP2C5 40-контактным шлейфом. На этой плате нет ни FPGA, ни источника питания, ни аккумулятора, только переключатели и разъём дисплея, а ещё клавиатура, на которую можно наконец-то нажимать.

Для проектирования я воспользовался EasyEDA — веб-редактором печатных плат, который работает в браузере без необходимости установки. По возможностям он далёк от Eagle и KiCad, но для простой платы более чем адекватен. Рекомендую пользоваться им, если вы заказываете с JLCPCB: они тесно интегрированы и имеют общий каталог деталей.

Клавиатура состоит из 35 тактильных переключателей, выстроенных в семь строк и пять столбцов. При нажатии клавиши происходит соединение линии строки и линии столбца. FPGA сканирует по очереди каждый столбец, подаёт на него низкий сигнал и считывает, какие линии строк реагируют. Подтягивающие вверх резисторы на линиях строк обеспечивают на них высокий сигнал, когда клавиша не нажата.

В качестве OLED-дисплея я выбрал Newhaven NHD-0216AW: модуль размером 2×16 символов с параллельным интерфейсом, совместимым с HD44780. Восемь линий данных, RS и E. Это тот же интерфейс, который разработала Hitachi в 1987 году для ЖК-панелей; он был спроектирован для беспроблемного соединения с шиной Intel MCS-51; с тех пор его клонируют все производители символьных дисплеев. То, что современный OLED-модуль, разработанный в 2021 год, понимает команды, придуманные для чипа 1987 года — это либо дань уважения качественному проектированию интерфейса, либо неспособность создать что-то новое. Как бы то ни было, благодаря этому код драйвера было очень просто написать.

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

FPGA Calculator: RevA Schematics
Схема версии RevA

Структура печатной платы RevA. Основную часть занимают переключатели. OLED-дисплей находится сверху.

Заказ у JLCPCB

JLCPCB — это производитель печатных плат, находящийся в Шэньчжэне. Он специализируется на небольших прототипах: его стандартный минимум — пять плат по ценам, которые бы ещё десять лет назад были невероятными. Пять двухслойных плат этого размера стоят примерно 2 доллара плюс доставка, и их доставляют примерно за две недели. Тем, кому доводилось ждать по шесть недель и платить западному производителю по 50 долларов за плату, современная ситуация с прототипированием печатных плат покажется ошеломляющей. (Меня не спонсировала ни эта компания, ни какая-либо другая, это лишь мой личный опыт).

Процесс заказа из EasyEDA прост: нажимаем на «Generate Gerber», чтобы экспортировать стандартные файлы для производства, затем на «Order at JLCPCB» для их отправки. Перед тем, как подтвердить заказ, на сайте JLCPCB можно посмотреть плату в 3D. Основные параметры — это размеры платы, количество слоёв (почти всегда два), обработка поверхности (для прототипов достаточно HASL) и нужно ли выполнять сборку деталей.

Для версии Rev A я заказал только платы, без сборки. Компонентов мало, поэтому припаять всё вручную было быстрее, чем указывать порядок сборки; к тому же так я мог непосредственно контролировать размещение компонентов. Переключатели и OLED я смонтировал вручную.

При большом количестве компонентов JLCPCB также предлагает сборку SMD. Процесс выглядит так: выбираем «PCB+Assembly» при оплате, загружаем файлы BOM и CPL (расположения компонентов), компания заказывает детали у LCSC (её аффилированного дистрибьютора деталей) и монтирует их. Детали, которые нельзя собрать, помечаются; их вы находите и припаиваете самостоятельно. Минимальное количество каждой позиции для заказов со сборкой меньше, чем при прямых заказах у LCSC, что полезно, если вам нужен только один конкретный компонент.

Общая стоимость за пять плат плюс детали (переключатели, OLED, резисторы, разъёмы): с доставкой вышла приблизительно в 30 долларов. (Примечание: это было в 2021 году. Сегодня с учётом тарифов на импорт в США цены намного выше.)

JLCPCB orders
Некоторые из моих предыдущих заказов на JLCPCB

Соединение двух плат

Клавиатурная плата в сборе соединяется с макетной платой EP2C5 стандартным 40-контактным плоским шлейфом IDC; тем же самым типом шлейфа подключаются жёсткие диски IDE. Разъём IDC (Insulation-Displacement Contact) врезается в проводник без его зачистки, образуя при помощи холодной сварки тесное соединение с медным проводником, дешёвое и вполне надёжное. На одной плате расположены FPGA, тактовый генератор и программный интерфейс JTAG. На другой находятся клавиатура и дисплей. Шлейф обеспечивает питание, заземление и передачу сигналов GPIO между платами.

FPGA Calculator: Rev A Board with keyplate

Такое решение из двух плат — классическая стратегия разработки прототипов: точно работоспособный коммерческий модуль остаётся неприкосновенным, к нему подсоединяется сзади новая работа, и они связываются хорошо известным интерфейсом. Если что-то пойдёт не так, то проблема почти наверняка возникла на новой плате.

Запускаем устройство

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

В зависимости от модели и силы нажатия механический тактильный переключатель может создавать дребезг в диапазоне 1-20 мс. Если окно устранения дребезга будет слишком маленьким, то станут регистрироваться фантомные нажатия; если слишком большим, то калькулятор будет казаться тормозным. Я подключил осциллограф к контакту строки и замерил реальное время дребезга для выбранных мной переключателей (6-миллиметровые тактильные аналоги Cherry). Результат был чётким: сильно меньше 5 мс, поэтому я задал соответствующий порог устранения дребезга.

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

Для последовательности инициализации OLED требуются конкретные задержки между командами: внутренний сброс контроллера выполняется медленно, поэтому хост должен ждать. Если подобрать тайминг неверно, то дисплей инициализируется частично или вообще не инициализируется. Прежде, чем переходить к оборудованию, я пошагово выполнил последовательность в ModelSim, поэтому она заработала с первой попытки.

Что удалось понять из версии Rev A

Пока я проектировал версию Rev B, версия Rev A проработала у меня много месяцев. За это время я получил характеристики всей периферии:

  • Время устранения дребезга сканера клавиатуры подтвердилось на реальном оборудовании.

  • Была подтверждена последовательность инициализации OLED.

  • Точно определились модель переключателей и значения подтягивающих вверх резисторов.

  • Соотношения контактов между шлейфом и GPIO FPGA были задокументированы в файле .qsf Quartus.

Симуляция подтверждает правильность логики. Оборудование показывает, что тайминги работают, а детали ведут себя так, как написано в спецификации. Источником нестабильности оказался плоский шлейф: система из двух плат была громоздкой. У неё не было ни аккумулятора, ни корпуса. Следующим шагом будет размещение всего калькулятора на одной плате, чтобы получить по-настоящему переносное устройство.

В части 6 мы поговорим о проектировании CPU — о наборе команд и микроархитектуре. Часть 7 будет посвящена работающему в нём микрокоду. В части 8 мы закончим путь от двух плат до одного готового устройства.