Генерация Verilog (HDL) кода
Страница в процессе разработки. |
Помимо генерации Си-кода, из ограниченного набора блоков в Engee можно генерировать и Verilog-код.
Verilog — это популярный язык описания аппаратуры (HDL), применяемый для проектирования и тестирования ASIC и ПЛИС. Сгенерированный код может быть синтезирован в netlist, который используется для фотолитографии ASIC или создания прошивок ПЛИС.
Процесс генерации Verilog-кода внешне аналогичен генерации Си-кода.
-
В окне настроек
перейдите на вкладку «Генерация кода» и для опции Целевая платформа выберите Verilog;
-
Нажмите кнопку «Сгенерировать код»
в левом верхнем углу рабочего пространства;
-
В файловом браузере
в папке
{model_name}_code
появится файл с расширением .v — сгенерированный Verilog-код.
Возможности генератора Verilog кода
-
Поддерживаемые типы данных:
-
Целочисленные типы любой ширины до 128 бит, включая нестандартные размеры (не только степени двойки);
-
Знаковые типы с фиксированной точкой и положительной длиной дробной части;
-
-
Генерация кода из виртуальных подсистем;
-
Поддерживается Генерация кода на основе пользовательских шаблонов.
-
Верификация сгенерированного кода (см. ниже).
Пример
Рассмотрим модель из примера, которая представляет собой PID контроллер. В качестве типов данных используются числа с фиксированной точкой:
Основной алгоритм реализован в подсистеме (блок Subsystem):
Поскольку алгоритм реализован в подсистеме, то из нее и будет генерироваться Verilog-код. Для этого нужно выставить Verilog как целевую платформу в окне настроек и выполнить команду в терминале:
engee.generate_code("pid_fixed.engee", "pid_fixed_code", subsystem_name="SubSystem", target="verilog")
Здесь:
-
pid_fixed.engee
— имя модели; -
pid_fixed_code
— папка, куда будет генерироваться Verilog-код; -
subsystem_name="SubSystem
— указывает на подсистему, из которой будет генерироваться код; -
target="verilog
— указание языка для генератора кода.
Вместо явного указания имени файла также можно передать текущую открытую модель с помощью
|
После выполнения команды, в файловом браузере появится файл pid_fixed.v со следующим кодом на Verilog:
module pid_fixed_SubSystem(
input clock,
reset,
input [15:0] io_setpoint,
io_feedback,
output [15:0] io_command
);
reg [15:0] UnitDelay_state;
wire [15:0] _AddAccum_T = io_setpoint - io_feedback;
wire [41:0] _Gain_2_new_T_3 = {{26{_AddAccum_T[15]}}, _AddAccum_T} * 42'h148000;
wire [29:0] _Gain_new_T_1 = {{14{_AddAccum_T[15]}}, _AddAccum_T} * 30'h6000;
wire [15:0] _Add_1Accum_T = {_Gain_2_new_T_3[41:27], 1'h0} + UnitDelay_state;
always @(posedge clock) begin
if (reset)
UnitDelay_state <= 16'h0;
else
UnitDelay_state <= _Add_1Accum_T;
end // always @(posedge)
assign io_command = _Gain_new_T_1[29:14] + {_Add_1Accum_T[15], _Add_1Accum_T[15:1]};
endmodule
Сгенерированный код имеет следующие особенности:
-
Сигналы
clock
иreset
генерируются всегда; -
Используется как последовательная, так и комбинаторная логика, однако комбинаторные циклы (петли) не поддерживаются;
-
reset
всегда синхронный и активен при высоком уровне сигнала (active-high).
Верификация
Верификация в этой статье подразумевает создание проверочной модели с блоком C Function, результаты симуляции которой должны совпадать с результатами исходной модели при одинаковых входных данных.
Как и при генерации Си-кода, в окне настроек на вкладке «Генерация кода» можно включить опцию «Генерировать блок C Function». В этом случае, помимо файла Verilog (.v), в папке со сгенерированным Verilog-кодом появятся:
-
.jl скрипт;
-
Папка obj_dir, содержащая следующие вспомогательные файлы:
Файл pid_fixed_Subsystem_verification.jl содержит скрипт на языке командного управления. Для получения проверочной модели необходимо выполнить этот файл. Сделать это можно двумя способами:
-
Ввести команду
include("/path/to/file")
в командной строке:
-
Нажать кнопку «Запустить скрипт»
в правом верхнем углу редактора скриптов
:
В результате выполнения скрипта будет создана модель {model_name}_verification.engee
. Она включает:
-
Входные и выходные блоки исходной модели (или подсистемы);
-
Блок C Function;
-
Вспомогательные блоки для преобразования типов сигналов (если модель использует fixed-point типы).
Упрощенно говоря, блок C Function содержит сгенерированный из исходной модели Verilog-код. Благодаря этому можно:
-
Включить запись выходных сигналов или сохранить результаты симуляции в рабочем пространстве. Затем сравнить результаты симуляции исходной модели и верификационной модели на одних и тех же входных данных (они должны совпадать);
Под верификацией понимается генерация проверочной модели с блоком C Function, чьи результаты симуляции должны совпадать с результатами симуляции исходной модели при одинаковых входных данных. -
Встроить верификационную модель как подсистему исходной модели и сравнить результаты.
Обычные процессоры общего назначения не могут напрямую исполнять Verilog RTL-код, предназначенный для синтеза. Однако это возможно с помощью эмуляторов, таких как Verilator. Этот инструмент преобразует Verilog-код в эквивалентный по поведению C++-код, который можно запустить для сравнения результатов. Сгенерированный C++-код упаковывается в библиотеку, содержащую интерфейс для управления симуляцией, а вспомогательные файлы размещаются в папке obj_dir (упомянута ранее). Затем блок C Function в верификационной модели использует эту библиотеку для работы. |
Как устроена работа с Verilog изнутри
Для продвинутых пользователей, таких как разработчики шаблонов для генерации HDL-кода, важно понимать этапы генерации Verilog. Упрощенно, процесс выглядит так:
-
Трансляция в Chisel — генератор кода переводит входную модель в код на языке Chisel. Chisel — это язык для высокоуровневого описания аппаратуры, встроенный в Scala. Он предоставляет абстракции, которые упрощают проектирование аппаратуры и позволяют использовать возможности Scala для работы с дизайнами;
-
Преобразование в FIRRTL — Chisel раскрывает высокоуровневые конструкции и преобразуется в FIRRTL (Flexible Intermediate Representation for RTL). На этом этапе:
-
1. Высокоуровневые методы, такие как reduce, заменяются их низкоуровневыми эквивалентами;
-
2. Операции с FixedPoint преобразуются в битовые операции.
-
-
Трансляция в Verilog — FIRRTL с помощью CIRCT (инструмент firtool) преобразуется в итоговый Verilog-код.
Как получить Chisel-код?
По умолчанию Chisel-код, сгенерированный на первом этапе, не сохраняется в папке с кодом. Однако он может быть полезен для отладки или разработки. Чтобы получить этот файл, используйте программное управление, передав в команду generate_code
аргумент target="chisel"
. Например:
engee.generate_code(engee.gcm(), "pid_fixed_code", target="chisel", subsystem_name="SubSystem")
После выполнения команды Chisel-код будет сохранен в указанной папке, и его можно использовать для дальнейшей работы:
В файле с расширением .scala содержится Chisel код:
Пример Chisel кода
//> using scala "2.13.14"
//> using dep "org.chipsalliance::chisel:6.5.0"
//> using plugin "org.chipsalliance:::chisel-plugin:6.5.0"
//> using options "-unchecked", "-deprecation", "-feature", "-language:reflectiveCalls", "-Xcheckinit", "-Xfatal-warnings", "-Wdead-code"
import chisel3._
import circt.stage.ChiselStage
import fixedpoint._
class pid_fixed_SubSystem extends Module {
val io = IO(new Bundle{
val setpoint = Input(FixedPoint(16.W,14.BP)) /* /setpoint */
val feedback = Input(FixedPoint(16.W,14.BP)) /* /feedback */
val command = Output(FixedPoint(16.W,13.BP)) /* /command */
})
val Add = Wire(FixedPoint(16.W,14.BP))
val AddAccum = Wire(FixedPoint(16.W,14.BP))
val AddCast0iosetpoint = Wire(FixedPoint(16.W,14.BP))
val AddCast1iofeedback = Wire(FixedPoint(16.W,14.BP))
val UnitDelay = Wire(FixedPoint(16.W,14.BP))
val Gain_2 = Wire(FixedPoint(16.W,13.BP))
val Gain = Wire(FixedPoint(16.W,13.BP))
val Add_1 = Wire(FixedPoint(16.W,14.BP))
val Add_1Accum = Wire(FixedPoint(16.W,14.BP))
val Add_1Cast0Gain_2 = Wire(FixedPoint(16.W,14.BP))
val Add_1Cast1UnitDelay = Wire(FixedPoint(16.W,14.BP))
val Add_2 = Wire(FixedPoint(16.W,13.BP))
val Add_2Accum = Wire(FixedPoint(16.W,13.BP))
val Add_2Cast0Gain = Wire(FixedPoint(16.W,13.BP))
val Add_2Cast1Add_1 = Wire(FixedPoint(16.W,13.BP))
val UnitDelay_state = RegInit({ val _init = Wire(FixedPoint(16.W,14.BP)); _init := 0.0.F(16.W,14.BP); _init })
/* Output for UnitDelay: /Unit Delay */
UnitDelay := UnitDelay_state
/* Sum: /Add incorporates:
* Inport: /setpoint
* Inport: /feedback
*/
AddCast0iosetpoint := io.setpoint
AddCast1iofeedback := io.feedback
AddAccum := AddCast0iosetpoint - AddCast1iofeedback
Add := AddAccum
/* Gain: /Gain-2 incorporates:
* Sum: /Add
*/
Gain_2 := 0.02.F(16.W,13.BP) * Add
/* Gain: /Gain incorporates:
* Sum: /Add
*/
Gain := 3.0.F(16.W,13.BP) * Add
/* Sum: /Add-1 incorporates:
* Gain: /Gain-2
* UnitDelay: /Unit Delay
*/
Add_1Cast0Gain_2 := Gain_2
Add_1Cast1UnitDelay := UnitDelay
Add_1Accum := Add_1Cast0Gain_2 + Add_1Cast1UnitDelay
Add_1 := Add_1Accum
/* Sum: /Add-2 incorporates:
* Gain: /Gain
* Sum: /Add-1
*/
Add_2Cast0Gain := Gain
Add_2Cast1Add_1 := Add_1
Add_2Accum := Add_2Cast0Gain + Add_2Cast1Add_1
Add_2 := Add_2Accum
/* Outport: /command incorporates:
* Sum: /Add-2
*/
io.command := Add_2
/* Update for UnitDelay: /Unit Delay */
UnitDelay_state := Add_1
}
object pid_fixed_SubSystemDriver extends App {
ChiselStage.emitSystemVerilogFile(
new pid_fixed_SubSystem,
firtoolOpts = Array("--disable-all-randomization", "--strip-debug-info",
"--lowering-options=disallowLocalVariables"))
}
Верификационная модель, созданная из .jl скрипта, будет работать только при выборе Verilog в качестве целевой платформы. Если папка obj_dir не была сгенерирована, то верификационная модель не запустится. |
Шаблоны кода раскрываются на первом этапе генерации, поэтому создание HDL-шаблонов нужно выполнять преимущественно на языке Chisel, используя при необходимости встроенные управляющие конструкции Julia.
Подробнее о работе с пользовательскими шаблонами генерации кода можно прочитать в статье.