Лисья Нора

Оглавление


§ Установка ПО

Всем привет! Сегодня я решил начать серию рассказов о том, каким образом можно создать простую схемку, а потом чуть более сложную схему и запустить ее выполняться на verilog.
Первое и самое важное, что я хочу сказать – работать я буду только в linux ubuntu, потому что там есть необходимое мне для работы программное обеспечение. Для винды не знаю, не пробовал, потому без понятия, как там настроить нормально. Например, если еще в винде есть iverilog и прочие ништяки, то verilator что-то на горизонте не наблюдался, либо я искал плохо.
Итак, что требуется для установки (sudo apt install):
После того, как все установлено, можно начинать кодить.

§ Самый простой проект

Пользоваться будем компилятором 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
Разберу построчно.

§ Создание makefile

Обычно я люблю создавать makefile для того, чтобы выполнять разные рутинные действия:
all:
iverilog -g2005-sv -DICARUS=1 -o tb.qqq tb.v
vvp tb.qqq >> /dev/null
Компиляция
Симулятор
vvp tb.qqq >> /dev/null
Эта программа приблизительно симулирует поведение верилог-файла, который был передан. Результаты выгружаются отдельно, команда, которая выгружает результаты, задается в tb.v файле:
module tb;
...
initial begin $dumpfile("tb.vcd"); $dumpvars(0, tb); end
...
endmodule;
Что означает:
Помимо значений регистров и проводов в модуле, будут выгружены также результаты из всех субмодулей, если они есть.

§ Отладка

После компиляции появится 2 новых файла:
Чтобы посмотреть, что получилось в симуляции, я пользуюсь GtkWave, для этого надо запустить команду gtkwave tb.vcd.
gtkwave1.png
Перед тем, как сигнал посмотреть, надо выбрать модуль, потом выбрать один из проводов или регистров и добавить через 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
Теперь внимательно разберусь с каждым пунктом:
Есть момент такой, что когда синтезируется модуль 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;
 
// Обработка 10 тактов
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-файл:
#include "obj_dir/Vtb.h"
А потом создается объект класса:
Vtb* top = new Vtb;
При каждом запуске обязательно надо инициализировать код:
Verilated::commandArgs(argc, argv);
Инициализируется top->a = 0; стартовое состояние входа a. Выполняется 10 тактов. На каждом такте происходит исполнение модуля, симуляция:
top->eval();
И потом значение входа меняется на противоположное:
top->a = !top->a;
И так 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
Что, собственно, и правильно.