В этой статье мы прикрутим к проекту из предыдущей статьи самую настоящую проверку работоспособности без заливки на железо, а также заменим скучный Quartus на Visual Studio Code, при этом научим её выполнять следующией действия:
- Подсвечивать синтаксис Verilog
- Подсвечивать допущенные в коде ошибки (любимое)
- Использовать автодополнение
- Запускать тесты и отображать результаты симуляции
- Компилировать проект и заливать на плату без UI Quartus-а.
В прошлой статье был создан проект на Quartus, а также было разобрано проектирование такого простого устройства, как 4-х кнопочное пианино (убьют меня музыканты за то что я свою пищалку так назвал 😀).
Помимо самого проекта, его декомпозиции и реализации, мы даже создали файл с временными ограничениями, которые помогают Quartus-у синтезировать корректную с точки зрения тайминга схему.
Но если вы прошли этот путь вместе со мной, то должны были уже отметить некоторую несовременность Quartus-а: и визуальный стиль из начала двухтысячных, и возможности редактора лишь чуточку превосходящие блокнот, да и всякие плюшки типа автодополнения в нём не работают.
И ведь на самом деле - всё, с чем мы работали до того - это же просто файлы, какая разница, где именно их редактировать.
Значит качаем и устанавливаем Visual Studio Code, если вы его до сих пор не установили.
Для работы с Verilog потребуется вот это дополнение. Оно не единственное возможное, но пожалуй самое лучшее из того что есть на сегодняшний день.
Так что прямо с web-страницы приложение жмём Install, далее Continue в появившимся окне, и Открыть приложение, на запрос браузера. Страничка расширения откроется в VSCode, где снова надо будет нажать Intall. Всё, приложение установлено, но ему требуется кучка посторонних программ для работы.
Первое - и основное для нас - это Icarus Verilog. Вот только качать его приходится с сайта билдов под Windows. Качаем, устанавливаем, не понимаем что это такое и как это запускать. Сразу скажу - icarus является консольной утилитой, но ручками из консоли запускать его мы не будем - всё за нас будет делать VSCode.
Второе - это ctags. Он даст минимальный autocomplete. Качать нужно отсюда. Тут уже не будет даже установщика. Просто создайте себе где-нибудь папку, которую вы не удалите случайно (можно в Program Files, например) и свалите туда содержимое архива.
Последнее - языковой сервер svls. Добавит предупреждений на кривом коде. Качается отсюда. Он ещё смешнее, чем ctags, так как представляет из себя просто единственный exe-файл. Я рекомендую запихнуть его в ту-же папку, что и ctags.
Эти программы были необходимы для работы расширения. От себя добавлю такую пару:
Поскольку я использую makefile в проектах, а VSCode здорово умеет их запускать, то рекомендую поставить Make for Windows. Непосредственная ссылка на скачивание тут.
Python 3. Качать и ставить с официального сайта
Одной лишь установки недостаточно, расширение для VSCode требует, чтобы iverilog и svls были доступны из командной строки с любого места - то есть находились в папках, указанных в PATH. Ну и python с make тоже удобно, когда есть из командной строки. Да и сам Quartus. Сделаем это.
В Windows 10 для этого надо набрать в окне поиска:
Изменение системных переменных среды
В Windows 8 делается как и в 10, только там нет окна поиска, нужно было нажать на пуск и начать вбивать нужное. В Windows 7 и ранее это можно было сделать из панели управления, вкладки "система", примерно вот так:
Нужно было на этой вкладке нажать на Дополнительные параметры системы, а в открывшемся окне - Переменные среды...
Так или иначе, у вас откроется подобное окно: в нём будет 2 списка - переменные среды пользователя, и системные.
Если внести изменение во второй список, то ваши изменения коснуться всех пользователей компьютера (если их несколько), но будут доступны лишь после перезагрузки. А для применения изменений в первом списке достаточно закрыть и открыть нужную программу. Поэтому я рекомендую первый, в нём ищем параметр Path. Щёлкаем по нему, и видим окно-редактор.
Я не помню точно, с какой именно версии Windows появилась такая красота, в Win XP уж точно такого не было. Там была строка ввода на 1 строчку, в которой всё подобное содержимое было вписано через точку с запятой. И да - это была чертовски длинная строка, которая в поле не вмещалась, приходилось копировать содержимое в какой-нибудь блокнот/sublime/vscode/... редактировать и вставлять обратно. Но в Windows 10 полноценный редактор списков, возможно в Windows 7 тоже, не помню. И в нём нужно добавить пути к установке всех 3-х программ: Icarus Verilog, ctags, svls. На моём скриншоте вы можете видеть:
c:\User\alexx\.local\bin - а сюда я сгрузил ctags и svls
c:\iverilog\bin - это путь к icarus verilog
c:\iverilog\gtkwave\bin - это путь к gtkwave, которая идёт в комплекте
F:\intelFPGA\18.0\quartus\bin64 - путь к bin папке quartus
c:\Program Files (x86)\GnuWin32\bin - тут make для Windows
c:\Users\alexx\AppData... - пути к питону и его скриптам
Вот эти вещи нужно добавить и вам тоже. Надеюсь на вашу сообразительность, вы не должны копировать мои пути, а ставить свои к соответствующим папкам.
Вот честно - на macOS или linux всё то-же самое, что мы уже абзацев 10 как делаем - делается за 30 секунд в пару команд из консоли...
С установками программ всё, далее серия, ОК, ОК, ОК, закрываем и заново запускаем Visual Studio Code, доставим ещё пару расширений попроще и перейдём к настройкам.
По желанию можете настроить русский язык интерфейса VSCode. После установки надо будет нажать Ctrl+Shift+P, вбить config, выбрать Configure Display Language, и выбрать ru.
Для запуска всяких команд из Makefile подойдёт Task Explorer.
Кроме того, я рекомендую поставить WaveTrace - проприетарную (это может многих не устроить) и даже платную для версии "без ограничений" просматривалку результатов тестбенчей. В качестве альтернативы выступает уже поставленный вами вместе с Icarus-ом бесплатный GTKWave. Да и по возможностям GTKWave будет более весомым. Но WaveTrace интегрируется прямо в VSCode, меня это подкупает. А вам решать самостоятельно.
Ну и последняя рекомендация - это Verilog Testbench Runner. Удобная штуковина для запуска testbench-ей прямо из под VSCode. Он и запустит симуляцию на Icarus Verilog (я выше говорил, что напрямую из командной строки его дёргать не придётся), и результат симуляции предложит открыть в GTKWave.
Ну и наконец - настройки. А настраивать много не надо - только самое первое установленное расширение и Task Explorer. Для этого жмём Ctrl+, (ага, запятая на английской раскладке). Откроется панель настройки. Там открываем пункт Extensions (или Расширения, если русифицировали), в нём Verilog Configuration.
А нужно заполнить такие настройки:
Verilog: Language Server - svls
Verilog › Linting › Iverilog: Run At File Location - YES
Verilog › Linting: Linter - iverilog
Verilog › Logging: Enabled - YES
Для настройки Task Explorer-а в том-же окне щёлкаем на него, и меняем
Path to make - make
до того там стояло nmake.
Собственно и всё, закрываем, и можем открывать папку с созданным до того на Quartus проектом, видим примерно такое:
Дерево файлов на месте, подсветка синтаксиса тоже присутствует. Сверху справа маячит зелёненькая кнопочка-треугольник play. А снизу красуется свёрнутая панель task explorer. Значит всё замечательно.
А раз всё работает, то надо сделать самое важное. То, что не было сделано в предыдущей статье - проверка написанного кода. И да, конечно - заливка на железо и запуск - это уже проверка. Вот только реальные проекты с первого раза практически наверняка работать не будут! В программировании так-же. Ни один программист не надеется, что написанная им на пару тысяч строк программа вот так возьмёт и заработает с первого запуска. Да кому вообще в голову взбредёт в первый раз запустить программу после написания 1000+ строк. В целом - ошибки неизбежны и их надо ловить. Для FPGA принято использовать тестбенчи. И давайте такой и напишем!
Я предпочитаю начинать с заглушки. Ctrl+N, Ctrl+S, beeper_tb.v, Enter. Файл тестбенча создали, пишем в него:
Первая строка определяет временную шкалу. Чему равен "такт" теста и на сколько промежуточных тактов он подразделяется. Ничего не поняли - просто вбивайте это в начале каждого тестбенча.
Вторая строка - это обычный include. Мы подхватываем файл с пищалкой, которую будем тут тестировать.
Дальше мы объявляем модуль тестбенча. В Verilog всё должно быть разбито по модулям. Вот и тут он нужен. У тестбенчей нет ни входов, ни выходов, поэтому в скобках - пустота.
Осталось заполнить чем то вменяемым. Вспоминаем, какие входы-выходы есть у проверяемого модуля:
beeper(input clk, input enable, input [WIDTH-1:0] tone, output reg q = 1'b1)
Нам его надо будет подключать, а значит нужно создать необходимые провода и регистры в тестбенче. На каждый входной параметр создаём по регистру. Первый это clk.
Тут мы не только создали регистр clk и записали в него значение 0, мы ещё ему сказали, чтобы каждые 10 (число после решётки) тактов значение clk менялось на противоположное.
Дальше enable и tone: их просто создаём
Тип reg сам содержит значения. Его можно рассматривать как переменную в языках программирования. Именно он нам был нужен для того, чтобы задать входные значения для модуля. Но в случае с q всё иначе, так как он уже и без нас рулит своим значением. Чтобы подключиться к такому, нужен простой провод
Грубо говоря, провода можно рассматривать как ссылки на переменные. Или на некоторые промежуточные значения.
Как только мы объявили все нужные переменные - поставим в testbench наш beeper и подключим его
Так - сначала идёт название модуля. Потом после решётки (это решётка, а не звёздочка) - значения статических параметров модуля (опционально). В случае с beeper - это WIDTH, который мы поставили равным 2, так как нам не зачем для теста большие значения. За первой скобкой идёт название инстанса. Называй хоть горшком. Совпадающее с названием модуля имя тоже годиться. Его и использую за недостатком фантазии. Ну а потом - через запятую все провода в том-же порядке, что они объявлены в модуле beeper.
Тут я хотел-бы сделать уточнение. Никто меня не заставлял в модуле beeper_tb называть clk именно clk, а не какой-нибудь clk1. Вместо tone я бы мог создать регистр cmp или с каким-угодно другим именем. clk модуля beeper_tb связывается с clk модуля beeper исключительно потому, что он первый в скобках.
Так. Это был подготовительный этап, теперь сам тест.
Запускаем при помощи той самой кнопки play над окном редактора справа: снизу появится всплывашка, предлагающая открыть результат в GTKWave. Можете открыть из этой всплывашки, а я открою в WaveTrace
Сигналы в область вывода ещё надо добавить из списка.
Теперь давайте разберём, что мы такое написали. Блок initial - это блок, выполняемый в момент загрузки тестбенча. Их может быть много, и все будут выполнены в момент загрузки. Сама попытка определения очерёдности их выполнения будет некорректной. В схемотехнике одновременность - вполне естественное понятие, в то время как последовательность - результат сознательных действий.
Начинаем с $dumpfile и $dumpvars - эти инструкции сообщают icarus-у, что он должен сформировать файл с результатами тестирования и имя этого файла.
Конструкции решётка-число - это задержки. Если dumpfile и dumpvars будут исполнены прямо в момент запуска, то всё что правее и ниже #30, будет выполнено через 30 тактов после загрузки. Страшная конструкция @(posedge clk) обозначает, что 30 тактов нам мало, мы ещё хотим дождаться и переднего фронта сигнала clk. А из-за упомянутого ранее блока always, он обязательно произойдёт. Получается что мы ждём прямой сигнал clk, но не раньше 30-го такта. И как дождались - устанавливаем регистр enable в 1. На приведённой мной диаграмме это видно: сигнал enable устанавливается через 2 такта. 30 шагов - это полтора такта и ещё 10 тактов до переднего фронта.
Потом мы снова ждём (уже от момента установки enable = 1). Сначала 200 тактов, а потом ещё и передний фронт сигнала q чтобы дождаться полного выходного цикла. Потом меняем значение tone и снова ждём один цикл q. Видим, что длительность импульсов и вправду понизилась. При tone = 3, на цикл требовалось 8 тактов. А при tone=2, всего 6. Ну - ровно то что и предсказывала формула, разобранная в прошлой статье.
Можно придраться, что исходя из предложенного тестбенча непонятно, будет ли правильно отрабатываться сброс enable, но это можете уже добавить самостоятельно. Ну а мой файл beeper_tb.v:
Теперь сделаем тестбенч для второго модуля. Им у нас был блок управления. Мы жали на разные кнопки, он должен был определиться - какую ноту надо играть и посчитать для неё подходящий делитель частоты.
В обязанности модуля управления входит отработка в том числе и вариантов, когда зажаты несколько кнопок одновременно: он должен выбирать лишь одну ноту, отдавая приоритет более высоким). А раз кнопки всего 4, то можно перебрать вообще все варианты. Кроме того, у модуля напрочь отсутствует внутреннее состояние, в нём ни одного регистра. И пусть не смутит вас кейворд reg в объявлении
Хотя это и требует некоторого понимания языка Verilog для того, чтобы понять - почему при указании кейворда reg не синтезируется регистр.
Поскольку мы можем перебрать все, то сделаем регистр на 4 бита, и будем каждые 20 тактов прибавлять к нему единичку. через 320 тактов этот регистр переберёт все возможные варианты:
Это наш регистр для кнопочек. Далее нужен вывод из проверяемого модуля. Его объявим сначала как:
Ну и всё, инстанцируем модуль.
Вместо 48мгц, я указал что он будет считать константы для кварца в 10кгц. Просто числа меньше, в них сложнее потеряться. Уменьшение разрядности, небольшие числа - всегда хороший способ делать компактные и быстро проходящие тестбенчи.
Как только мы сохраним файл, строка с инстанцированием подкрасится жёлтым:
Port 5 (tone) of selector expects 5 bits, got 32
Мол криво мы передаём tone, он ведь в модуле всего-лишь 5-битный, а мы передаём ему 32-бит. Ну - раз теперь нам подсказали, сколько именно бит потребуется, то меняем определение tone на
Я уже говорил раньше, что никто не заставляет называть сигналы в тестбенче также, как и в модуле. Вот и тут в модуле входы названы:
selector(input a, input g, input f, input c, output reg [WIDTH-1:0] tone);
А в тестбенче в них передаются keys[0], keys[1], keys[2] и keys[3]. В целом - это работает ровно также, как и вызов какой-нибудь вызов функции в python.
Сама последовательность теста будет такой
Привычные dumpfile/dumpvars, и новая конструкция wait. Но мне кажется она интуитивна. Просто повисает до тех пор, пока значение keys не станет равным числу 16 (ну или битам 1111). Дальше ждём ещё 20 шагов и завершаем тестбенч.
А на выходе будет такое:
Минимальное число 10 обязано соответствовать ноте ЛЯ, ведь она делит тактовую частоту на максимальный делитель 440. И именно этой ноте отдаётся максимальный приоритет. И вправду, в каждой чётной ячейке диаграммы записано 10 - всякий раз, когда младший бит keys равен 1. Именно то, что мы ожидали. А нота ДО с минимальным приоритетом и числом 18 встречается только 2 раза: для нажатой одинокой кнопке (1000) и для отпущенных всех кнопках (0000). В целом - тестбенч показывает, что управляющий модуль тоже работает без нареканий. Файл selector_tb.v:
Теперь о заливке проекта на плату. Сделать это можно, открыв проект в Quartus-е. А можно воспользоваться моим Makefile, который я дополнил для кроссплатформенности. Он успешно отрабаывает на macOS с докером постольку, поскольку именно для macOS я его изначально и создавал. Также все его команды отлично отрабатывают на Windows без докера. По идее, он должен успешно работать и с докером под Windows, а также под Linux как с докером так и без, но я не проверял.
Запускать задачи из Makefile можно через консоль, но не все её любят - тут и помогает Task Explorer. Как то так:
Снизу Task Explorer. Там отображён make и куча целей для него. К сожалению, сам Task Explorer натаскал из Makefile всё, что было похоже на цель, так что из всего этого нужны лишь следующие цели:
- waves - пройтись по всем testbench-ам (по всем файлам *_tb.v), выполнить их и сложить результаты в папку output_files. Разом прогнать все тестбенчи проекта может быть весьма удобным решением
- clean - удаляет папку output_files со всеми результатами тестбенчей и сборки прошивки
- firmware - запускает Quartus без интерфейса и компилирует на нём всю прошивку
- flash - записывает прошивку на устройство. Если прошивки ещё нет, то сначала выполняется цель firmware
- quartus - открывает текущий проект в Quartus. Команда будет висеть до выхода из Quartus-а. Подразумевается, что вы его открываете для каких-то быстрых манипуляций.
В таком режиме можно работать. Quartus теперь нужен только для непосредственного создания проекта и добавления в него сторонних модулей. А разрабатывать схему можно прямо из VSCode.
Так. Осталась одна беда. При открытии top.v все инстанцирования подсвечиваются красным. Всё потому, что icarus-у нужно либо передавать все файлы в командной строке или же расставить include. Командной строкой заведует плагин для VSCode. Остаются только include-ы. Сделаем так. Добавим в верх top.v строчки:
И теперь все подчёркивания исчезают. А вот задача firmware работать перестаёт, Quartus сыплет ошибками. Ему не нравится, что одни и те-же файлы добавлены и в проект, и через include.
Ну да - удалим их из проекта, они и через include сгодятся. Убираем строки:
из top.qsf
И теперь всё работает. И никаких ошибок не возникает.
Как вывод - мы избавились от Quartus в ежедневной работе, зато научились делать testbench-и и заливать проекты на плату. А ведь это и есть 99% работы схемотехника. Quartus нужен лишь для старта проекта и для редких добавлений IP Cores. Так что можно сказать что второй шаг готов.
Я не являюсь профессионалом в написании руководств и учебников, поэтому крайне нуждаюсь в обратной связи по качеству материала. Если что-то написано непонятно, что то не до конца мной разъяснено или избыточно разжёванно, прошу написать об этом в комментариях. Если всё хорошо и полезно - тоже пишите или просто поставьте лайк.
Ещё я мог бы написать руководство по самому Verilog. Если есть желающие такое почитать - пишите в комменты.