Документация Engee

Генерация Verilog (HDL) кода

Страница в процессе разработки.

Помимо генерации Си-кода, из ограниченного набора блоков в Engee можно генерировать и Verilog-код.

Verilog — это популярный язык описания аппаратуры (HDL), применяемый для проектирования и тестирования ASIC и ПЛИС. Сгенерированный код может быть синтезирован в netlist, который используется для фотолитографии ASIC или создания прошивок ПЛИС.

Процесс генерации Verilog-кода внешне аналогичен генерации Си-кода.

  1. В окне настроек debug article icon 1 перейдите на вкладку «Генерация кода» и для опции Целевая платформа выберите Verilog;

  2. Нажмите кнопку «Сгенерировать код» codegen icon 1 в левом верхнем углу рабочего пространства;

  3. В файловом браузере file browser 7 в папке {model_name}_code появится файл с расширением .v — сгенерированный Verilog-код.

Возможности генератора Verilog кода

  • Поддерживаемые типы данных:

    • Целочисленные типы любой ширины до 128 бит, включая нестандартные размеры (не только степени двойки);

    • Знаковые типы с фиксированной точкой и положительной длиной дробной части;

  • Генерация кода из виртуальных подсистем;

  • Поддерживается Генерация кода на основе пользовательских шаблонов.

  • Верификация сгенерированного кода (см. ниже).

Пример

Рассмотрим модель из примера, которая представляет собой PID контроллер. В качестве типов данных используются числа с фиксированной точкой:

verilog model example

Основной алгоритм реализован в подсистеме (блок Subsystem):

verilog model example subsystem

Поскольку алгоритм реализован в подсистеме, то из нее и будет генерироваться Verilog-код. Для этого нужно выставить Verilog как целевую платформу в окне настроек debug article icon 1 и выполнить команду в терминале:

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 — указание языка для генератора кода.

Вместо явного указания имени файла также можно передать текущую открытую модель с помощью engee.gcm():

engee.generate_code(engee.gcm(), "pid_fixed_code", subsystem_name="SubSystem", target="verilog")

После выполнения команды, в файловом браузере file browser 7 появится файл 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, содержащая следующие вспомогательные файлы:

    verilog files example

Файл pid_fixed_Subsystem_verification.jl содержит скрипт на языке командного управления. Для получения проверочной модели необходимо выполнить этот файл. Сделать это можно двумя способами:

В результате выполнения скрипта будет создана модель {model_name}_verification.engee. Она включает:

  • Входные и выходные блоки исходной модели (или подсистемы);

  • Блок C Function;

  • Вспомогательные блоки для преобразования типов сигналов (если модель использует fixed-point типы).

verilog command line example 1

Упрощенно говоря, блок C Function содержит сгенерированный из исходной модели Verilog-код. Благодаря этому можно:

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

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

Обычные процессоры общего назначения не могут напрямую исполнять Verilog RTL-код, предназначенный для синтеза. Однако это возможно с помощью эмуляторов, таких как Verilator. Этот инструмент преобразует Verilog-код в эквивалентный по поведению C++-код, который можно запустить для сравнения результатов.

Сгенерированный C++-код упаковывается в библиотеку, содержащую интерфейс для управления симуляцией, а вспомогательные файлы размещаются в папке obj_dir (упомянута ранее). Затем блок C Function в верификационной модели использует эту библиотеку для работы.

Как устроена работа с Verilog изнутри

advanced users ru

Для продвинутых пользователей, таких как разработчики шаблонов для генерации HDL-кода, важно понимать этапы генерации Verilog. Упрощенно, процесс выглядит так:

  1. Трансляция в Chisel — генератор кода переводит входную модель в код на языке Chisel. Chisel — это язык для высокоуровневого описания аппаратуры, встроенный в Scala. Он предоставляет абстракции, которые упрощают проектирование аппаратуры и позволяют использовать возможности Scala для работы с дизайнами;

  2. Преобразование в FIRRTL — Chisel раскрывает высокоуровневые конструкции и преобразуется в FIRRTL (Flexible Intermediate Representation for RTL). На этом этапе:

    1. 1. Высокоуровневые методы, такие как reduce, заменяются их низкоуровневыми эквивалентами;

    1. 2. Операции с FixedPoint преобразуются в битовые операции.

  3. Трансляция в 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-код будет сохранен в указанной папке, и его можно использовать для дальнейшей работы:

verilog command line example 2

В файле с расширением .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.

Подробнее о работе с пользовательскими шаблонами генерации кода можно прочитать в статье.

Поддерживаемые блоки

Генератором Verilog кода Engee поддерживаются следующие библиотечные блоки: