Как создать verilog файл

Обновлено: 03.07.2024

Написание кода на Verilog позволяет нам сфокусироваться на поведении аппаратуры с точки зрения высокого уровня вместо того, чтобы описывать схему на низком уровне с помощью низкоуровневых элементов логики. Существует множество качественной литературы по программированию на HDL-языках Verilog и VHDL. Однако, к сожалению, часто такие книжки велики по объему и довольно трудны для освоения и понимания. Это руководство (перевод [1]) делает попытку ускоренного знакомства с Verilog для тех, кто хочет быстро начать программировать на практике.

Разработка цифровой схемы на Verilog обычно включает в себя два основных процесса - тест на основе симуляции и реализация рабочего кода. На рисунке ниже показаны эти процессы. Сначала нужно передать исходные файлы Verilog в инструментарий симуляции, что показано на рисунке слева. Инструмент симуляции программно эмулирует реальное поведение аппаратуры схемы для определенных входных воздействий, что описывается в специально написанном тесте (testbench). Поскольку компиляция нашего кода Verilog для инструмента симуляции осуществляется относительно быстро, то этот шаг используется главным образом для оценки общей работоспособности дизайна.

Примечание: на рисунке слева показана симуляция с помощью пакета ModelSim, однако симуляцию можно реализовать и с помощью бесплатного симулятора iSIM, входящего в пакет разработки Xilinx ISE Webpack. Процесс установки этого пакета и описание симуляции в iSIM показаны в статье [3].

Simulation flow left and Synthesis flow right

На рисунке справа упрощенно показан процесс реализации проекта после успешного прогона теста в симуляторе. После того, как мы удостоверились в корректности высокоуровневого исходного кода Verilog, мы используем инструментарий синтеза для превращения этого кода в низкоуровневый список логических вентилей (gate netlist). Затем утилита отображения (mapping tool) привязывает netlist к соответствующим ресурсам используемого кристалла логики (микросхема CPLD или FPGA). На заключительном шаге мы загружаем конфигурационный поток данных (bitstream) в выбранную микросхему логики (это делается с помощью кабеля JTAG). В результате получается готовая схема, выполняющая нужные логические функции.

[Философия Verilog]

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

• Операторы Verilog по своей природе изначально конкурентны (они выполняются в реальном времени параллельно). Это означает, что кроме кода, который появляется между ограничителями блока begin и end, нет заранее определенного порядка, в каком выполняются операторы. Такое поведение отличается от большинства языков программирования наподобие C, в которых подразумевается, что операторы выполняются последовательно, друг за другом. Первая строка в функции main() будет выполнена первой, затем вторая строка, и так далее.
• Синтезируемый код Verilog в конечном счете отображается (map) на реальные аппаратные вентили логики. С другой стороны, компилируемый код C отображается на некие биты в памяти, которые CPU будет или не будет интерпретировать как команды и/или данные при выполнении программы.

[Синтаксис синтезируемой комбинаторной логики Verilog]

Модули. Базовый блок программы Verilog это оператор module. Он в чем-то является аналогом определения функции на языке C:

Ниже в качестве примера приведен модуль, у которого 3 входа: два 5-разрядных операнда a и b, вход разрешения работы en, и также есть выход a_gt_b. Модуль называется comparator (для простоты код логики модуля не приведен).

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

Инстанциация модулей. Для создания экземпляра модуля в коде другого модуля используется следующий синтаксис:

Портами называются сигналы, с которыми работает модуль. Например, для инстанциации модуля компаратора с именем экземпляра comparator1, входными сигналами in1, in2 и en, и выходом gt (они будут составлять списокпортов), мы должны написать следующий код:

Эта инстанциация зависит от порядка следования портов в списке аргументов модуля comparator. Также есть альтернативный синтаксис инстанциации модулей, не зависящий от порядка следования портов, часто использующийся по этой причине:

Для предыдущего примера получится следующий код:

Обратите внимание, что несмотря на то, что порядок следования портов в аргументах модуля изменен (b теперь идет перед a), эта инстанциация теперь все еще будет работать корректно, потому что дополнительно указаны конкретные сигналы, к которым подключаются порты.

Комментарии. Комментарии Verilog добавляются по таким же правилам, что и на языке C.

Числовые литералы. Многие модули будут содержать в коде числовые литералы. По умолчанию на Verilog числовые литералы обрабатываются как 32-битные числа без знака, однако следует выработать в себе привычку декларировать ширину (разрядность) каждого такого числового литерала. Это приведет к меньшему количеству догадок, каким образом нужно связывать сигнал (wire) и числовой литерал (что показано ниже).

Примеры числовых литералов:

Мы пока не определили, что что такое сигнал (wire), сделаем это позже.

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

Обратите внимание, что assign выполняет НЕПРЕРЫВНОЕ (continuous) присваивание. Это означает в предыдущем примере, что всегда, когда меняется входной сигнал two_bit_input, то также поменяется значение two_bit_wire, a_wire и five_bit_wire. Нет никакого порядка очередности для этих изменений сигналов - все они происходят одновременно. Именно по этой причине нельзя несколько раз применять assign к одному и тому же wire - сигнал wire не может управляться разными выходами одновременно. Одновременное действие операторов assign это именно то, что мы подразумеваем, говоря о Verilog, что он "изначально параллелен".

В заключение рассмотрим краткое выражение, которое иногда используется для декларирования и назначения за один раз:

Регистры. Еще один тип данных, который мы будем использовать, это регистр. Несмотря на имя типа, регистры не подразумевают, что это память. Это просто конструкция языка, обозначающая переменные, которые могут появляться на левой стороне блока always (и в коде симуляции в блоках initial и forever). Вы декларируете регистры наподобие сигналов wire, на верхнем уровне модуля, и Вы не можете делать назначение (assign) для сигналов (wire) внутри блока always.

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

В простейшем случае регистр в операторе always работает наподобие типа данных wire, как в этом простом модуле:

В этом модуле описывается, что всегда, когда меняется входной сигнал a_in, будет обрабатываться блок always, в результате чего a_out получает значение, вычисленное из значения a_in. Это как бы если мы декларировали a_out как wire, и назначили ему значение побитной инверсии a_in.

Операторы if и case. Еще более интересно, что мы можем поместить операторы case и if внутрь блоков always. Об операторах case и if мы можем думать, что они синтезируются в некий вид мультиплексора. В сущности if и case работают одинаково, однако они существуют отдельно в основном только для того, чтобы сделать код более удобочитаемым.

Ниже показана простая схема, использующая оператор case внутри оператора if:

В частности, имейте в виду:

• Операторы case и if должны размещаться в блоке always.
• По поводу использования always @*: это новая конструкция Verilog-2001, которая автоматически заполняет список чувствительности всеми переменными, перечисленными в правой части выражений блока always. Если специально не указано нечто другое, Вы должны всегда использовать always @* для списков чувствительности блоков always, что может экономить многие часы отладки.
• Операторы begin end используются для обозначения блоков из нескольких строк (то же самое, для чего используют фигурные скобки <> на языке C). Можно опустить конструкцию begin/end, если присваивания в операторах case или if состоят только из одной строки.
• Фактически каждый оператор case имеет оператор default, и каждый оператор if имеет соответствующий else. Не забывайте об этом, или Вы сгенерируете защелки (latch)!

Параметры. Часто мы хотим создать стандартный модуль, который можно настроить несколькими параметрами, когда для этого модуля создается экземпляр (инстанциация модуля). Это то место, где появляется оператор parameter. Ниже показан пример параметризованного ALU, который по умолчанию 32-разрядный, если при инстанциации модуля не был предоставлен параметр:

[Симуляция, тестирование кода]

Код для поддержки тестирования (testbench). До настоящего момента мы писали только синтезируемый код Verilog, т. е. тот код, который синтезируется, транслируется и отображается в реальную аппаратуру (например в вентили FPGA). Однако перед тем, как пробовать нашу логику на реальном устройстве, нам следует удостовериться, что она функционирует корректно. Проверка делается с помощью написания на Verilog кода теста (testbench) и применения программного обеспечения симуляции, чтобы запустить тест.

Verilog testbench это специальный файл, который инстанциирует модуль (или модули), которые нам нужны для прогона теста. Этот testbench не синтезируется в аппаратуру. Вместо этого он предоставляет входные сигналы (стимул) в инстанциированных модулях, благодаря чему мы можем запустить testbench в программном симуляторе нашего проектируемого дизайна логики. Из-за того, что этот код testbench не синтезируется, он может быть написан в другом стиле, отличающемся от обычного синтезируемого модуля Verilog.

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

Единица времени задается в начале файла Verilog строкой вида:

1ns означает шкалу времени для симулятора, а 1ps означает точность учета времени для него. Единицы времени, указанные здесь, применяются при создании тестовых сигналов, подаваемых на вход тестируемой в симуляторе схемы.

Блоки initial. Блоки initial и forever подобны блокам always, которые выполняются в определенном порядке, когда задано их срабатывание. Точно так же в левой части этих блоков можно использовать только регистры. Однако в отличие от блоков always, которые срабатывают всякий раз, когда истинно условие списка чувствительности, блок initial выполняется только один раз в начале программы. Блок forever, судя по своему названию, предназначен для создания бесконечного зацикливания куска кода.

Следующий код устанавливает регистры opcode, op_a и op_b в значения 0, 10 и 20 соответственно в момент времени симуляции t=0, и затем через задержку 5 их назначения меняются на 2, 10 и 20 соответственно (момент времени симуляции t=5):

Оператор display. Используется для отображения значения переменной, для форматирования результата используется printf-подобный синтаксис. При выводе оператор автоматически добавляет в конец строки переход на новую строку.

[Пример testbench]

Используя эти операторы, мы можем создать очень сырой тест для модуля ALU.

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

Самыми популярными языками описания цифровой аппаратуры являются VHDL и Verilog. В этой статье я постараюсь сравнить синтаксис двух этих языков на примере "бегущего огонька", архивы проектов будут прикреплены в конце статьи. Для полного понимания описываемых процессов настоятельно рекомендую ознакомиться с предыдущими статьями цикла:

Рассмотрим структуру создаваемого модуля "бегущего огонька", который назовем leds_case. В модуле будет один вход для тактового сигнала clk, и четыре выхода (четырехбитная шина) для светодиодов.


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

Чтобы не быть голословным, реализуем данный модуль на отладочной плате от ALTERA с CPLD семейства MAX II.


Плата прошивается по JTAG программатором USB BLASTER.


Т.к. на самой отладочной плате нет светодиодов, то я нашел у себя старую плату, на которой было 4 светодиода включенных по схеме на рисунке ниже.


Итак, теперь создадим новый проект в среде Quartus II, которую можно скачать на официальном сайте Altera. Запускаем среду, для создания нового проекта кликаем на New Project Wizard (на картинке обведено красным).


В этом окне среда нам сообщает какие операции нужно совершить для создания проекта. Пропускаем.


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


Далее выбираем Empty project, тем самым подтверждаем создание абсолютно чистого проекта.


В следующем диалоговом окне нам предлагают подключить файлы проекта, но так-как нам нечего подключать, то пропустим этот шаг.


В этом диалоговом окне выбираем микросхему, которую мы будем программировать. Выбираем параметры соответствующие микросхеме на нашей плате: семейство MAX II корпус TQFP и количество ножек 100. Немного остановимся на параметре Core Speed grade - это параметр характеризующий время задержки прохождения сигнала между внутренними соединениями в ПЛИС. Проект созданный для ПЛИС со speed grade 5 без проблем заработает на ПЛИС со speed grade 10, но проект созданный для ПЛИС со speed grade 10 не будет адекватно работать, если вообще разведется, на ПЛИС со speed grade 5. Как определить speed grade? Очень просто: в названии микросхемы EPM240T100C5 последняя цифра 5 и есть значение speed grade.


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


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


Для начала напишем модуль на VHDL. Cоздадим VHDL файл. Выберем File -> New


В появившемся окне выбираем VHDL file.


Теперь напишем код и разберем его.

Теперь напишем код выполняющий ту же самую функцию но на Verilog, для этого создадим новый проект, но новый модуль создадим как Verilog file


// Как видите, никаких библиотек на математику и типы данных мы не подключаем

Как Вы можете убедится оба языка очень похожи и мнение о том, что синтаксис Verilog подобен высокоуровневому языку С сильно преувеличено. Как я и говорил ранее, выбор на чем писать сугубо личный и основан исключительно на популярности языка, так что о том что для читателя является достоинством, а что недостатком я доверю решать самому читателю. А вообще, никто не мешает Вам выучить и Verilog и VHDL. В одном проекте допускается то, что один модуль может быть написан на Verilog, а другой на VHDL. Хочется еще сказать пару слов о переносимости проектов на разные ПЛИС: если Вы не используете специфические ресурсы ПЛИС, такие как аппаратные умножители, множители частоты и т.д., то перенести проект на другую ПЛИС не составит труда. Например наш проект не использует ничего кроме таблиц соответствия и триггеров, и следовательно его можно перенести не только на ПЛИС из другого семейства, но и на ПЛИС другой фирмы!

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


После того как синтез проекта выполнится успешно, настало время присвоить портам из модуля реальные пины ПЛИС. Для этого кликнем на значок Pin Planning вверху экрана.


Перед нами откроется изображение нашей микросхемы. Мы видим на какие ножки мы можем "повесить" наши сигналы, но перед тем как это сделать расскажу Вам, что есть три типа пинов: обычные пины входа/выхода, на них можно заводить переферию (у ПЛИС есть несколько банков, куда входят группы пинов), пины тактирования, на них заводится тактовая частота, а еще есть пины сброса (reset) на которые заводится сигнал сброса. У меня на плате нет кнопки reset'а, поэтому эти пины трогать не будем. На PIN_14 нужно завести сигнал clk, чтобы затактировать банк с пинами номер 1. PIN_2, PIN_3, PIN_4, PIN_5 входят в банк пинов номер 1, и следовательно на них "повесим" выходную шину управления светодиодами. После того, как Вы назначили все требуемые пины, просто закройте окно Pin Planner'а, все результаты сохранятся.


После того как мы все правильно присвоили, сделаем синтез и имплементацию проекта нажав на соответствующую иконку вверху экрана.


После успешной сборки проекта откроем утилиту Programmer в верней части экрана, в которой можно прошить нашу ПЛИС.


В появившемся окне Programmera нажмите Hardware Setup, если Ваш программатор не определился автоматически. Драйвера на программатор должны установиться автоматически, но если по каким-то причинам этого не произошло, то их можно найти в папке с установленной средой. Нажмите Add File, чтобы загрузить сгенерированный средой файл прошивки.


Файл прошивки можно найти в папке output_files вашего проекта. Выбираем его.


Далее появится вот такое окошко. Внизу мы можем увидеть, что ПЛИС выбрана правильно. Чтобы запрограммировать нашу ПЛИС ставим галочки Program/Configure и Verify и жмем Start. Если все сделано правильно, то статус-бар Progress станет зеленым и будет отображать 100%.


Добавлю видео работы прошивки, чтобы Вы убедились что все написанное правда, а так же два варианта прошивки: на Verilog и на VHDL. Спасибо за внимание.

В этой статье попробую вкратце показать, как можно настроить Visual Studio Code для написания HDL кода на языках Verilog, SystemVerilog, настроить задачи для компиляции и симуляции работы, а также отображения результатов симуляции в GTKWave.

Написание HDL кода в Visual Studio Code по сравнению с Intel Quartus Prime порядком удобней и можно просто использовать Visual Studio Code как продвинутый редактор кода (если вам не нужна компиляция и симуляция из Icarus Verilog).

B67-0

Установка Visual Studio Code

Скачиваем Visual Studio Code для вашей ОС, в моём случае я скачал версию для Windows x64, ZIP архив (версия 1.27.1). Распаковываем\устанавливаем и запускаем, нас встречает главное окно редактора (да, да, это не полноценная IDE, как Visual Studio, а именно легкий редактор кода с возможностью кастомизации расширениями практически до уровня IDE) со стартовой страницей:

B67-1

Произведём минимальные настройки для удобства. Открываем настройки Visual Studio Code:

B67-2

B67-3

B67-4

Этот параметр изменит вид меню и интегрирует само меню в заголовок окна, что экономит место и визуально редактор становится приятнее, пример этого решения в конце абзаца.

B67-5

B67-6

Итак, перезапускаем редактор и видим следующие визуальные изменения:

B67-7

Установка расширений для Visual Studio Code

Далее, чтобы удобно (подсветка, автодополнение) работать с Verilog, SystemVerilog, TCL файлами, нам необходимо установить расширения, поддерживающие эти типы файлов.

Открываем панель расширений (Ctrl+Shift+X):

B67-8

B67-9

B67-10

B67-11

Пример работы расширения:

B67-12

Установка Icarus Verilog

B67-13

И не убирайте галочки добавления путей к GTKWave и в PATH:

B67-14

Примечание: у меня установщик никаких правок в PATH не внёс и я не сразу понял, почему не работает. Пришлось вручную внести в пользовательский PATH следующие две строки (Icarus Verilog установлен в C:\Data\iverilog\):

B67-19

Настройка проекта под Visual Studio Code

Приведу их ещё раз. counter.v :

counter_tb.v :

Итак, открываем корневую папку проекта, в которой и находятся файлы counter.v и counter_tb.v:

B67-15

Редактор теперь показывает открытую папку TEST и файлы в ней:

B67-16

Далее необходимо создать задачи компиляции и симуляции проекта (точнее Test Bench). Переходим в меню по пункту Terminal -> Configure Tasks, откроется следующий диалог:

B67-17

B67-18

Закрываем редактор, чтобы он подхватил изменения файла .vscode\Tasks.json при следующем запуске.

Примечание: Можно еще хоткеи настроить для задач, но это уже оставлю на вашу любознательность. ;)

B67-20

Вначале выбираем Compile Verilog File и потом выбираем запуск задачи без сканирования лога выполнения:

B67-21

Успешно откомпилировано и создан файл для отображения симуляции работы (на идеальных компонентах):

B67-22

После успешной компиляции аналогичным образом вызываем Run GTKWave для просмотра результатов. Откроется основное окно GTKWave:

B67-23

Раскрываем слева экземпляр тестбенча и выделяем экземпляр DUT тестируемого модуля counter, внизу появятся его сигналы, просто перетаскиваем их в колонку Signals:

В прошлой статье мы сделали достаточно сложный модуль. Разумеется, я вставил в тело статьи уже отлаженный результат. Но мне показалось, что достаточно странно, когда автор говорит «делай, как я», но при этом не показывает очень важного процесса. Давайте я покажу, как вообще проводится отладка системы путём моделирования. Причём в следующей статье будут содержаться сведения, которые ещё неделю назад не знал даже я. Но, чтобы перейти к ним, надо разобраться с базовыми принципами. Итак. Давайте рассмотрим, как быстро подготовить и не менее быстро запустить процесс моделирования в среде ModelSim.




Как работает обычная программа для ЭВМ? Имеется некая внешняя среда (монитор и клавиатура с «мышкой» — самые типичные представители этой самой среды). Программа с ними взаимодействует. При отладке можно производить настоящие воздействия от внешней среды, а можно эмулировать их. У нас тестеры часто пишут всякие скрипты, которые как раз эмулируют внешние воздействия. После чего запускаются анализаторы логов, которые проверяют, чтобы ответы в среду уходили верные.

Что делать, если в этой программе для ЭВМ всё глючит? Можно поставить точки останова и изучать срез системы в момент, когда они сработали. Срез системы — это значения переменных. Может, состояния различных мьютексов и прочих объектов синхронизации. В общем, срез внутренних параметров отлаживаемой системы.

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

И здесь возникает вопрос о внешней среде. Как сымитировать её? Нам на помощь приходят модели. На языке Verilog (как и VHDL, и других похожих) вполне можно описать поведение чего угодно. Делаем мы систему, которая работает с микросхемой ULPI… Значит, чтобы проверить её работу, на том конце должно быть что-то, что ведёт себя именно, как ULPI. То есть, модель ULPI. Но этого мало. Наш блок реагирует на команды от шины ALAVON_MM. Именно эта шина заставляет блок жить. Поэтому надо ещё добавить модель шины AVALON_MM, причём эта модель должна быть активной. Именно она будет подавать тестовые воздействия.


В конечном итоге мы должны сделать именно такую систему. И тогда мы сможем снимать временные диаграммы сигналов на всех её шинах и даже внутри любых её модулей. Если будет возникать ошибка, мы сможем устанавливать точки останова и изучать срезы системы, чтобы найти врага. Хотя, лично я эти точки останова обычно не ставлю, чаще всего хватает анализа временных диаграмм. Дело в том, что сигналы можно смотреть не только интерфейсные, а любые внутренние. Вытянув десяток-другой внутренних сигналов на график, обычно можно догадаться, что в логике реализовано не так.

Цель сегодняшней статьи — не рассказать о том, что такое моделирование вообще (это — долгая история), а показать, как это моделирование провести быстрее всего. И рассмотрим мы это не на боевой задаче, а на простом примере. Сделаем совсем простенькую тестовую систему, чтобы в следующей статье уже понимать, откуда растут ноги у более сложного её варианта, ведь при чтении удобнее не сидеть и недоумевать: «Зачем он это делает?», а знать все базовые принципы, из которых уже вытекают усложнения. Кстати, недавно выяснилось, что один мой знакомый хоть и владеет мастерством моделирования, но не знал, что в среду Quartus встроены механизмы, которые позволяют делать это легко и непринуждённо. Он тратил на это намного больше усилий, чем требуется. Так что может, кто-то тоже сейчас узнает для себя что-то новое о возможностях, заложенных в Quartus. Итак, приступаем.

Создание простейшей модели на языке Verilog

Люди делятся на две категории. Те, кто любит создавать всё с нуля руками и те, кто любит делать это, повозив мышкой. Руками создавать всё — правильнее. Можно контролировать каждое действие и делать всё заведомо идеально. Но память — штука ненадёжная. Если всё время заниматься одним и тем же делом, она держит детали в уме, а если приходится всё время переключаться между языками, через месяц-другой приходится вспоминать, что же там надо сделать. Поэтому работа через вариант «повозить мышкой» имеет право на существование хотя бы из-за этого. Опять же, если у отлаживаемого модуля десяток-другой интерфейсных сигналов, мне всегда скучно делать рутинную работу по их переобъявлению и пробросу. Поэтому сейчас мы рассмотрим, как сделать модель при помощи «мышки». А дальше — каждый для себя решит, достаточно ему этого, или стоит переходить на ручную работу.

Итак, мы хотим промоделировать модуль. Что такое «промоделировать» выходит за рамки нашего цикла, можно на эту тему написать отдельный большой цикл. То есть, в рамках этого раздела, считаем, что вы знакомы с методикой разработки модели. Но дальше надо всё включить в проект… Или нет? Как ни странно, для моделирования модуля совершенно не нужно даже создавать собственный проект. Мы можем прицепиться в качестве паразита к любому проекту, не включая в него ничего нового, а только создав тестовый набор, который никак не будет участвовать в основной сборке.

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

Видно, что данные в него поступают по шине, очень отдалённо напоминающей AVALON_MM, а выходят просто в параллельном коде.

Положим получившийся файл в каталог с нашим проектом, но не станем включать его в проект в Quartus. Вместо этого — создадим тестовый набор специально под него. Для этого выбираем пункт меню Assignments—>Settings:


и в появившемся дереве ищем пункт EDA Tools Settings—>Simulation:


Кстати, о типе моделирования, выделенном зелёной рамкой. Возможно, кто-то помнит, в первых статьях я говорил, что при создании проекта чисто по привычке выбираю ModelSim Altera? Это было то самое ружьё на сцене, которое рано или поздно должно было выстрелить. Но если при создании проекта тип моделирования не был выбран, его можно выбрать или изменить здесь.

Продолжаем создавать тестовый набор. Переключаем радиокнопку на Compile test bench (кстати, а как этот термин красиво переводится на русский? Я не могу заставить себя писать «тестовый стенд», так как не вижу никакого стенда) и нажимаем кнопку Test Benches:


В открывшемся диалоге нажимаем New:


Если делать тестовый набор вручную, то можно заполнить поля за один проход. Но так как мы делаем всё при помощи «мышки», то сейчас заполняем только часть полей, а остальные – дозаполним позже. В поле Test bench name я вбил слово Parazit (а как ещё назвать тест, который просто паразитирует на проекте?). Слово Parazit под ним заполнилось автоматически. Сейчас мы не будем его менять, но в будущем нам ещё предстоит это сделать. Также при помощи кнопки «. » я выбрал файл sum.sv с кодом отлаживаемого сумматора, после чего, при помощи кнопки Add, затолкнул его в список файлов теста. Пока — всё. Закрываем диалог…


Дальше мы продолжим формирование теста в среде ModelSim. Для этого выбираем пункт меню Tools—>Run Simulation Tools—>RTL Simulation:



Не найдено модуля верхнего уровня. Это нормально. Мы его ещё не создали просто. Поэтому идём в перечне библиотек к work и раскрываем её. Вот он, наш сумматор.


Наводимся на него, нажимаем правую кнопку «Мыши» и выбираем пункт меню Create Wave. Это в тексте всё так занудно, если бы я снимал видео, весь процесс занимал бы десятки секунд, так что не пугайтесь, а следите за руками дальше. Итак, Create Wave…


Интерфейсные сигналы модуля автоматически переехали на график:


Надо назначить значение какого-нибудь из них. Не важно какого, важно назначить. Очень старая среда моделирования Квартуса умела красиво генерить тактовые сигналы. Увы, её давно изъяли из поставки, так как стали прилагать ModelSim, а тут с подобным всё не так красиво. Проку в формировании генератора здесь, я не увидел, поэтому даже показывать не буду. Так что… Ну, давайте линию we нулю присвоим. Наводимся на сигнал, нажимаем правую кнопку, выбираем пункт меню Edit—>Wave Editor—>Create/Modify WaveForm.


В появившемся диалоге выбираем Constant. И время заодно поменяем, скажем, на 100 микросекунд:


Далее — указываем значение 0:


Всё, минимально необходимый набор данных мы создали, а остальное проще будет ручками сделать. Экспортируем файл. Для этого выбираем пункт меню File—>Export—>Waveform:



Всё, ModelSim можно закрывать, времянку при этом сохранять не нужно.

Что делать с моделью дальше

Вот такой кривоватый, но всё-таки готовый Верилоговский файл нам создала система:

Автоматика избавила нас от написания стандартных блоков. Причём если бы интерфейсных сигналов было больше, автоматика бы послушно прописала бы и соединила все цепи. Лично меня при ручном создании тестовых наборов удручает именно процесс описания сигналов и их проброса. Теперь в этом файле мы сейчас создадим модель среды, которая будет воздействовать на отлаживаемый модуль sum.

Как видим, толку от задания констант, сделанного автогенератором никакого. Но всё-таки, созданы все цепи, подключён модуль, подлежащий тестированию, даже секция initial создана. Давайте облагородим код. Первое — выкинем точку останова, удалив строки:

Дальше — добавим модель тактового генератора (как же мне не хватает замечательного генератора, который делали старинные Квартусы! Там можно было задать частоту в мегагерцах и не думать о пересчёте её в период, а тем более — полупериод).

Теперь нам надо послать несколько байт данных. Проще всего это сделать прямо в секции initial, но если я буду прописывать там каждую фазу доступа к шине, код в этой секции станет запутанным. Поэтому я сделаю такую задачку (именно она выступает в роли модели шины):

Завершение подготовки тестового набора

Пришла пора добавить этот текст к тестовому набору. Для этого идём в уже известный нам диалог


  1. Добавил файл parazit_tb.v в список.
  2. Так как файле parazit_tb.v модуль верхнего уровня имеет имя parazit_tb (можете убедиться, глянув исходник из предыдущего раздела), я вписал это имя в строку Top level module in test bench.
  3. Я сказал вести моделирование в течение 10 микросекунд, после чего приостановиться. Если что — я домоделирую через нажатие кнопок ручного управления.


Итого

Закрываем всё. Снова запускаем ModelSim. Видим, что всё работает верно. Данные приходят и учитываются в сумме. Если же на такте нет данных (we в нуле) – сумма не увеличивается.


Как пользоваться самой средой моделирования — это тема на несколько статей. Причём скорее в видеоформате. Но в целом — мы познакомились с методикой быстрой подготовки и запуска тестов на языке Verilog из среды Quartus.

Теперь, зная, как быстро запустить моделирование, мы можем набросать модели среды для нашей головы USB-анализатора и проверять её работу. При этом мы не запоминали ни одного заклинания ModelSim, так как Квартус позволяет всё настроить при помощи «мышки». Все необходимые скрипты он генерит сам и среду ModelSim вызывает тоже сам. Базу для модели нам также создали в автоматическом режиме, хоть её и пришлось затем доработать вручную.

Увы и ах. Один из элементов внешней среды — модуль ULPI. Чтобы разработать его модель самостоятельно, надо, во-первых, тщательно разобраться в логике работы той микросхемы. А в предыдущей статье я говорил, что она очень заковыристая. Ну и, во-вторых, надо затратить уйму времени на разработку кода модели. И устранение ошибок в нём… Понятно, что проще найти что-то готовое. Но готовую модельку удалось найти только на языке SystemC. Поэтому в следующей статье мы будем учиться моделировать систему с использованием этого языка.

Читайте также: