Оглавление
§ Установка ПО
Всем привет! Сегодня я решил начать серию рассказов о том, каким образом можно создать простую схемку, а потом чуть более сложную схему и запустить ее выполняться на verilog.
Первое и самое важное, что я хочу сказать – работать я буду только в linux ubuntu, потому что там есть необходимое мне для работы программное обеспечение. Для винды не знаю, не пробовал, потому без понятия, как там настроить нормально. Например, если еще в винде есть iverilog и прочие ништяки, то verilator что-то на горизонте не наблюдался, либо я искал плохо.
Итак, что требуется для установки (sudo apt install):
iverilog gtkwave – для синтеза
verilator – для тестирования
libsdl2-2.0-0 libsdl2-dev – для запуска теста
После того, как все установлено, можно начинать кодить.
§ Самый простой проект
Пользоваться будем компилятором icarus verilog и потому я создам самый простой проект в истории человечества, а именно hello world. Файл назову tb.v:
`timescale 10ns / 1ns
module tb;
reg clock;
always #0.5 clock = ~clock;
initial begin clock = 0; #2000 $finish; end
endmodule
Разберу построчно.
`timescale 10ns / 1ns – это директива компилятору и симулятору, что минимальной единицей измерения является 1 наносекунда, а единицей отсчета – 10 наносекунд. Это примерно как 1 миллиметр и 10 миллиметров (1 сантиметр). Отсчеты ведутся именно на 10ns.
module tb; ... endmodule – начало и завершение модуля (обертка модуля)
reg clock; – объявление регистра clock. Регистр – это 1 бит памяти, который может хранить либо 0, либо 1
initial begin clock = 0; #2000 $finish; end – инициализация при старте кода, вначале регистру clock присваивается значение 0, и спустя 2000*10ns (указано в timescale) = 20 000 нс, или 20 микросекунд, симуляция будет завершена
always #0.5 clock = ~clock; – эта конструкция означает, что каждые 0.5*10 нс или каждые 5 нс будет перебрасываться из 0 в 1, и из 1 в 0, тем самым симулируется тактовый генератор.
§ Создание makefile
Обычно я люблю создавать makefile для того, чтобы выполнять разные рутинные действия:
all:
iverilog -g2005-sv -DICARUS=1 -o tb.qqq tb.v
vvp tb.qqq >> /dev/null
Компиляция
iverilog -g2005-sv -DICARUS=1 -o tb.qqq tb.v – это компилятор, который компилирует файл tb.v в tb.qqq
-g2005-sv – опция означает, что используется system verilog 2005
-DICARUS=1 – передача параметр ICARUS=1, то есть, делается #define ICARUS 1, параметров может быть много
-o tb.qqq – куда выгрузить откомпилированный файл
Симулятор
vvp tb.qqq >> /dev/null
Эта программа приблизительно симулирует поведение верилог-файла, который был передан. Результаты выгружаются отдельно, команда, которая выгружает результаты, задается в tb.v файле:
module tb;
...
initial begin $dumpfile("tb.vcd"); $dumpvars(0, tb); end
...
endmodule;
Что означает:
$dumpfile("tb.vcd"); – команда говорит, что результаты симуляции будут выгружены в tb.vcd файл
$dumpvars(0, tb); – то, из какого модуля будут выгружены результаты
Помимо значений регистров и проводов в модуле, будут выгружены также результаты из всех субмодулей, если они есть.
§ Отладка
После компиляции появится 2 новых файла:
Чтобы посмотреть, что получилось в симуляции, я пользуюсь GtkWave, для этого надо запустить команду gtkwave tb.vcd.
Перед тем, как сигнал посмотреть, надо выбрать модуль, потом выбрать один из проводов или регистров и добавить через Append. Обновлять сигналы можно через сочетание клавиш ctrl+shift+r, чтобы не перезагружать снова.
После того, как сигналы были добавлены, можно сохранить их через ctrl+s, выбрав имя, например, tb.gtkw. Теперь же, после каждой загрузки можно не добавлять сигналы снова, а просто вызвать команду gtkwave tb.gtkw.
§ Verilator
Признаюсь, верилятор я освоил совсем недавно и был крайне удивлен тому, как он работает и вообще, считаю его самым интересным инструментом для работы. К нему я еще могу вернуться в будущем, когда буду делать видеоадаптер. А сейчас пока что расскажу как первично его настроить.
Для чего нужен верилятор вообще? Собственно, только для того, чтобы преобразовать модуль из верилога в c++ код, представить его полный эквивалент, чтобы потом использовать в тестировании кода вне ПЛИС-а.
Как обычно, сначала перейдем к рассмотрению makefile
VINC=/usr/share/verilator/include
all:
verilator -Wall -Wno-unused -cc tb.v
cd obj_dir && make -f Vtb.mk
g++ -o tb -I$(VINC) tb.cc $(VINC)/verilated.cpp obj_dir/Vtb__ALL.a
Теперь внимательно разберусь с каждым пунктом:
VINC=/usr/share/verilator/include – это путь к библиотекам верилятора, очень важно
verilator -Wall -Wno-unused -cc tb.v – собственно, компиляция tb.v
cd obj_dir && make -f Vtb.mk – переход в рабочий каталог и сборка
g++ -o tb -I$(VINC) tb.cc $(VINC)/verilated.cpp obj_dir/Vtb__ALL.a – строка компиляции С++ файла
Есть момент такой, что когда синтезируется модуль tb, то имя его класса в С++ становится Vtb – то есть, приписывается V вначале (от слова Verilated, я так понимаю). Здесь obj_dir/Vtb__ALL.a – это архивированные коды для дальнейшего их использования в линковщике g++.
Итак, теперь рассмотрим то, что находится в tb.cc:
#include "obj_dir/Vtb.h"
int main(int argc, char **argv) {
Verilated::commandArgs(argc, argv);
Vtb* top = new Vtb;
top->a = 0;
for (int i = 0; i < 10; i++) {
top->eval();
printf("i=%d | a=%d, b=%d\n", i, top->a, top->b);
top->a = !top->a;
if (Verilated::gotFinish()) break;
}
delete top;
return 0;
}
Этот код можно считать неким таким шаблонов для тестбенчей на вериляторе.В самом начале подключается необходимый h-файл:
А потом создается объект класса:
При каждом запуске обязательно надо инициализировать код:
Verilated::commandArgs(argc, argv);
Инициализируется top->a = 0; стартовое состояние входа a. Выполняется 10 тактов. На каждом такте происходит исполнение модуля, симуляция:
И потом значение входа меняется на противоположное:
И так 10 раз.
В результате выполнения программы получается следующее:
i=0 | a=0, b=1
i=1 | a=1, b=0
i=2 | a=0, b=1
i=3 | a=1, b=0
i=4 | a=0, b=1
i=5 | a=1, b=0
i=6 | a=0, b=1
i=7 | a=1, b=0
i=8 | a=0, b=1
i=9 | a=1, b=0
Что, собственно, и правильно.