Генерация кода в Engee
Генерация Verilog-кода
О языке Verilog
Помимо генерации Си-кода, из ограниченного набора блоков в Engee можно генерировать код на языке Verilog.
Verilog — это язык описания аппаратуры (HDL), используемый для проектирования и моделирования цифровых систем, таких, как схемы ПЛИС и ASIC. Он позволяет описывать структуру и поведение цифровых схем на уровне регистров и логики, что делает его основным инструментом для инженеров в сфере аппаратного обеспечения. Verilog поддерживает моделирование параллельных процессов, что критично для схем с большим количеством одновременно работающих компонентов.
Основные сферы применения Verilog:
- Проектирование ПЛИС и ASIC: Verilog позволяет описывать сложные цифровые системы, которые затем можно синтезировать в физические схемы на ПЛИС или интегральных схемах.
- Симуляция и верификация: Verilog предоставляет возможность симуляции, позволяя тестировать и отлаживать схемы перед их реализацией на аппаратуре.
- Автоматизация разработки: синтезаторы переводят Verilog-код в оптимизированные логические схемы, что упрощает процесс создания и тестирования аппаратных компонентов.
Verilog применяется в проектах, требующих высокоскоростной обработки данных и надежного параллельного выполнения.
Генерация Verilog-кода из моделей Engee
Процесс генерации Verilog-кода внешне аналогичен генерации Си-кода.
- В окне настроек модели
перейдите на вкладку Генерация кода и для опции Целевая платформа выберите Verilog.
-
Нажмите правой кнопкой мыши на подсистему и в появившемся контекстном меню выберите пункт Сгенерировать код.
-
В файловом браузере
в папке {model_name}_codeпоявится файл с расширением.v— сгенерированный Verilog-код.
Также можно сгенерировать код в командной строке или в интерактивном скрипте Engee с помощью функции engee.generate_code, указав verilog в качестве целевого языка (target="verilog").
Возможности генератора Verilog-кода
Поддерживаемые типы данных:
- целочисленные типы размером до 128 бит, включая нестандартные размеры (не только степени двойки);
- знаковые типы с фиксированной точкой и положительной длиной дробной части;
- логический тип.
Также доступны:
- Генерация кода из виртуальных и атомарных подсистем;
- Пользовательские шаблоны;
- Верификация сгенерированного кода.
Пример
Рассмотрим модель pid.engee, которая представляет собой ПИД-регулятор:
Основной алгоритм реализован в подсистеме SubSystem:
В качестве типов данных в подсистеме используются числа с фиксированной точкой.
Поскольку алгоритм реализован в подсистеме, то из нее и будет генерироваться Verilog-код. Для этого нужно выставить verilog как целевую платформу в окне настроек модели и выполнить команду в терминале или в интерактивном скрипте:
engee.generate_code("$(@__DIR__)/pid.engee", "$(@__DIR__)/pid_code"; subsystem_name="SubSystem", target="verilog")
Здесь:
pid.engee— имя модели;/pid_code— папка, в которую будет генерироваться Verilog-код;subsystem_name="SubSystem"— указывает на подсистему, из которой будет генерироваться код;target="verilog"— указание языка для генератора кода.
После выполнения команды в файловом браузере появится файл pid_SubSystem.v со следующим кодом на языке Verilog:
/* Code generated by Engee
* Model name: pid.engee
* Code generator: release-1.1.22
* Date: Thu Sep 18 09:51:17 2025 GMT
*/
module pid_SubSystem(
input clock,
reset,
input [15:0] io_setpoint,
io_feedback,
output [15:0] io_command
);
wire [15:0] _AddAccum_T = io_setpoint - io_feedback;
wire [16:0] _Gain_new_T_1 = {_AddAccum_T[15], _AddAccum_T} * 17'h3;
assign io_command = _Gain_new_T_1[16:1];
endmodule
✏️Задание 1
Постройте модель, складывающую два числа, и сгенерируйте для нее Verilog-код:
-
Создайте новую модель. Добавьте в модель блоки Константа (2 шт.), Сложение и Заглушка. Соедините выходные порты блоков Константа со входами блока Сложение, а выходной порт блока Сложение – со входом блока Заглушка. Задайте значение одной константы равным
2, а второй –3. -
Зажав клавишу Shift, выделите оба блока Константа и Сложение и поместите их в подсистему. Переименуйте подсистему в SubSystem.
-
В окне настроек модели во вкладке Генерация кода для опции Целевая платформа выберите
Verilog. -
Сохраните модель в папке данного курса под именем
add.engee. -
Чтобы сгенерировать Verilog-код для подсистемы
SubSystem, нажмите правой кнопкой мыши на подсистему и в появившемся контекстном меню выберите пункт Сгенерировать код. В результате в текущей папке будет создана папкаadd_SubSystem_code, в которой будет находиться файлadd_SubSystem.vсо сгенерированным кодом.
Решение
Модель add.engee:
Подсистема SubSystem:
Файл add_SubSystem.v:
/* Code generated by Engee
* Model name: add.engee
* Code generator: release-1.1.24
* Date: Mon Sep 22 15:21:49 2025 GMT
*/
module add_SubSystem(
input clock,
reset,
output [15:0] io_Out1
);
assign io_Out1 = 16'h5;
endmodule
Верификация
Верификация подразумевает создание проверочной модели с блоком C Function, результаты симуляции которой должны совпадать с результатами исходной модели при одинаковых входных данных.
Как и при генерации Си-кода, в окне настроек на вкладке Генерация кода можно включить опцию Генерировать блок C Function. В этом случае, помимо файла Verilog (.v), в папке со сгенерированным Verilog-кодом появятся:
.jl-скрипт;- папка
obj_dir, содержащая следующие вспомогательные файлы:add_cfn_api.h;libadd.so.
Файл modelname_verification.jl содержит скрипт на языке командного управления. Для получения проверочной модели необходимо выполнить этот скрипт. Сделать это можно двумя способами:
- Ввести команду
include("/path/to/file")в командной строке. - Нажать кнопку «Запустить скрипт»
в правом верхнем углу редактора скриптов.
В результате выполнения скрипта будет создана модель modelname_verification.engee. Она включает:
- входные и выходные блоки исходной модели (или подсистемы);
- блок C Function;
- вспомогательные блоки для преобразования типов сигналов (если модель использует типы с фиксированной точкой).
Упрощенно говоря, блок C Function содержит сгенерированный из исходной модели Verilog-код. Благодаря этому можно:
- Включить запись выходных сигналов или сохранить результаты симуляции в рабочем пространстве. Затем сравнить результаты симуляции исходной модели и верификационной модели на одних и тех же входных данных (они должны совпадать);
- Встроить верификационную модель как подсистему исходной модели и сравнить результаты.
Важно
-
Верификационная модель, созданная из
.jlскрипта, будет работать только при выборе Verilog в качестве целевой платформы. -
Если папка
obj_dirне была сгенерирована, то верификационная модель не запустится.
✏️Задание 2
Выполните верификацию кода, сгенерированного для подсистемы SubSystem модели add.engee:
-
Откройте модель
add.engee, залогируйте выходной сигнал подсистемы, запустите модель и постройте график выходного сигнала. -
В окне настроек модели во вкладке Генерация кода поставьте галочку Генерировать блок C Function.
-
Нажмите правой кнопкой мыши на подсистему и в появившемся контекстном меню выберите пункт Сгенерировать код. В результате в текущей папке будет создана папка
add_SubSystem_code, в которой будут находиться файлыadd_SubSystem.vиadd_SubSystem_verification.jl, а также папкаobj_dir. -
Откройте скрипт
add_SubSystem_verification.jlв редакторе скриптов и запустите его. В результате в той же папке появится файл со сгенерированной модельюadd_SubSystem_verification.engee. -
Откройте эту модель, залогируйте выходной сигнал блока C Function, запустите модель и постройте график выходного сигнала. Убедитесь в том, что результаты симуляции обеих моделей идентичны.
Решение
Скрипт add_SubSystem_verification.jl:
# This is a verification script for the add_SubSystem generated code.
# Run this code in order to create a verification model with a CFunction block inside that behaves like the original model.
# CFunction variables
start_code = """
add_SubSystem_init();"""
output_code = """
add_SubSystem_eval(&Out1);
"""
terminate_code = """
add_SubSystem_final();"""
# Close
try engee.close("add_SubSystem_verification";force=true) catch end
# Create
engee.create("add_SubSystem_verification")
# Add Blocks
engee.add_block("/Basic/User-Defined Functions/C Function", "add_SubSystem_verification/")
engee.add_block("/Basic/Ports & Subsystems/Out1", "add_SubSystem_verification/Out1")
# Edit block parameters
engee.set_param!("add_SubSystem_verification/C Function", "OutputCode" => output_code, "StartCode" => start_code, "TerminateCode" => terminate_code)
engee.set_param!("add_SubSystem_verification/C Function", "Inputs" => 0)
engee.set_param!("add_SubSystem_verification/C Function", "Outputs" => 1)
engee.set_param!("add_SubSystem_verification/C Function", "SampleTime" => "0.01")
engee.set_param!("add_SubSystem_verification/C Function", "Parameters" => 0)
engee.set_param!("add_SubSystem_verification/C Function", "OutputPort1Label" => "Out1")
engee.set_param!("add_SubSystem_verification/C Function", "OutputPort1VarName" => "Out1")
engee.set_param!("add_SubSystem_verification/C Function", "OutputPort1Size" => "()")
engee.set_param!("add_SubSystem_verification/C Function", "OutputPort1Type" => "int16_t")
engee.set_param!("add_SubSystem_verification/C Function", "IncludeDirectories" => "\"/user/start/Курсы/Генерация кода в Engee/add_SubSystem_code/obj_dir\"")
engee.set_param!("add_SubSystem_verification/C Function", "LibraryDirectories" => "\"/user/start/Курсы/Генерация кода в Engee/add_SubSystem_code/obj_dir\"")
engee.set_param!("add_SubSystem_verification/C Function", "Headers" => "\"add_SubSystem_cfn_api.h\"")
engee.set_param!("add_SubSystem_verification/C Function", "Libraries" => "\"libadd_SubSystem.so\"")
# Edit model parameters
engee.set_param!("add_SubSystem_verification", "FixedStep" => 0.01)
engee.set_param!("add_SubSystem_verification", "StartTime" => 0.0)
engee.set_param!("add_SubSystem_verification", "StopTime" => 10)
engee.set_param!("add_SubSystem_verification", "EnableMultiTasking" => false)
# Add lines
engee.add_line("add_SubSystem_verification", "C Function/1", "Out1/1")
# Save
engee.save("add_SubSystem_verification.engee"; force=true)
Сгенерированная модель add_SubSystem_verification.engee:
График выходного сигнала:
Как устроена работа с Verilog изнутри
Упрощенно, процесс генерации Verilog-кода выглядит так:
-
Трансляция в Chisel — генератор кода переводит входную модель в код на языке Chisel. Chisel — это язык для высокоуровневого описания аппаратуры, встроенный в Scala. Он предоставляет абстракции, которые упрощают проектирование аппаратуры и позволяют использовать возможности Scala для работы с дизайнами.
-
Преобразование в FIRRTL — Chisel раскрывает высокоуровневые конструкции и преобразуется в FIRRTL (Flexible Intermediate Representation for RTL). На этом этапе:
- высокоуровневые методы, такие как
reduce, заменяются их низкоуровневыми эквивалентами; - операции с фиксированной точкой преобразуются в битовые операции.
- высокоуровневые методы, такие как
-
Трансляция в Verilog — FIRRTL с помощью CIRCT (инструмент firtool) преобразуется в итоговый Verilog-код.
По умолчанию Chisel-код, сгенерированный на первом этапе, не сохраняется в папке с кодом. Однако он может быть полезен для отладки или разработки. Чтобы получить этот файл, используйте программное управление, передав в команду engee.generate_code аргумент target="chisel". Например:
engee.generate_code("$(@__DIR__)/pid.engee", "$(@__DIR__)/pid_code"; subsystem_name="SubSystem", target="chisel")
После выполнения этой команды Chisel-код будет сохранен в указанной папке в файле с расширением .scala, и его можно будет использовать для дальнейшей работы.
Пример Chisel-кода
// Code generated by Engee
// Model name: pid.engee
// Code generator: release-1.1.24
// Date: Wed Sep 24 08:22:54 2025 GMT
//> using scala "2.13.14"
//> using dep "org.chipsalliance::chisel:6.7.0"
//> using plugin "org.chipsalliance:::chisel-plugin:6.7.0"
//> using options "-unchecked" "-deprecation" "-feature" "-language:reflectiveCalls" "-Xcheckinit" "-Xfatal-warnings" "-Wdead-code"
import chisel3._
import fixedpoint._
import fixedpoint.shadow.{Mux}
class pid_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_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.S * Add
/* Gain: /Gain incorporates:
* Sum: /Add
*/
Gain := 3.S * Add
/* Sum: /Add-1 incorporates:
* Gain: /Gain-2
* UnitDelay: /Unit Delay
*/
Add_1Cast0Gain_2 := Gain_2
Add_1Accum := Add_1Cast0Gain_2 + UnitDelay
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_SubSystemDriver extends App {
_root_.circt.stage.ChiselStage.emitSystemVerilogFile(
new pid_SubSystem,
Array("--split-verilog"),
Array("--disable-all-randomization", "--strip-debug-info")
)
}
Поддерживаемые блоки
Генератором кода Engee поддерживаются генерация Verilog-кода для следующих библиотечных блоков:
- Запаздывание на шаг
- Логический оператор
- Сложение
- Коэффициент усиления
- Вход1
- Выход1
- Подсистема
- Переключатель
- Константа
С выходом новых релизов Engee этот список постепенно расширяется.