Engee documentation

Verilog (HDL) code generation

Page in progress.

In addition to C-code generation, Verilog code can also be generated from a limited set of blocks in Engee.

Verilog is a popular hardware description language (HDL) used to design and test ASICs and FPGAs. The generated code can be synthesised into a netlist that is used for ASIC photolithography or FPGA firmware creation.

The process of generating Verilog code is similar to generating C code.

  1. In settings window debug article icon 1, click on the "Code Generation" tab and for the Target Platform option, select Verilog;

  2. Click the "Generate Code" button codegen icon 1 in the upper left corner of the workspace;

  3. In file browser file browser 7, a file with extension .v - the generated Verilog code - will appear in the {model_name}_code folder.

Features of the Verilog code generator

  • Supported data types:

    • Integer types of any width up to 128 bits, including non-standard sizes (not just powers of two);

    • Sign types with fixed point and positive fractional length;

  • Code generation from virtual subsystems;

  • Supported by Code generation based on custom templates.

  • Verification of generated code (see below).

Example

Consider the model from the example, which is a PID controller. Fixed point numbers are used as data types:

verilog model example

The basic algorithm is implemented in the subsystem (block Subsystem):

verilog model example subsystem

Since the algorithm is implemented in the subsystem, Verilog code will be generated from it. To do this, set Verilog as the target platform in settings window debug article icon 1 and execute the command in the terminal:

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

Here:

  • pid_fixed.engee - model name;

  • pid_fixed_code - folder where Verilog code will be generated;

  • subsystem_name="SubSystem - indicates the subsystem from which the code will be generated;

  • target="verilog - specifies the language for the code generator.

Instead of specifying the file name explicitly, you can also pass the current open model with engee.gcm():

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

After executing the command, a pid_fixed.v file with the following Verilog code will appear in file browser file browser 7:

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

The generated code has the following features:

  • clock and reset signals are always generated;

  • Both sequential and combinatorial logic is used, but combinatorial loops are not supported;

  • `reset' is always synchronous and active-high.

Verification

Verification in this paper implies the creation of a verification model with the block C Function, whose simulation results should match the results of the original model with the same input data.

As with C code generation, you can enable the option "Generate C Function block" in the settings window on the "Code generation" tab. In this case, in addition to the Verilog (.v) file, the folder with the generated Verilog code will contain:

  • .jl script;

  • An obj_dir folder containing the following auxiliary files:

    verilog files example

The file pid_fixed_Subsystem_verification.jl contains a script in the command control language. To get the verification model, you need to execute this file. You can do this in two ways:

  • Enter the include("/path/to/file") command at command line img 41 1 2:

    verilog command line example

  • Click the "Run Script" button content button 3 in the upper right corner of script editor interactive script icon:

    verilog script start

Running the script will create a model {model_name}_verification.engee. It includes:

  • Input and output blocks of the original model (or subsystem);

  • The *C Function block;

  • Auxiliary blocks for conversion of signal types (if the model uses fixed-point types).

verilog command line example 1

Simplistically speaking, the C Function block contains the Verilog code generated from the source model. Thanks to this it is possible to:

  • Enable output recording or save simulation results in the workspace. Then compare the simulation results of the source model and the verification model on the same input data (they must match);

    The verification is supported by the generation of proven models with еру block C Function, and the results of the simulations can be found in results of simulations of unusual models данных.
  • Build the verification model as a subsystem of the source model and compare the results.

Conventional general purpose processors cannot directly execute Verilog RTL code intended for synthesis. However, this is possible using emulators such as Verilator. This tool converts Verilog code into equivalent behavioural C++ code, which can be run to compare the results.

The generated C++ code is packaged in a library containing an interface to control the simulation, and the auxiliary files are placed in the obj_dir folder (mentioned earlier). The C Function block in the verification model then uses this library to operate.

How Verilog works internally

advanced users en

For advanced users, such as template developers for HDL code generation, it is important to understand the steps of Verilog generation. Simplified, the process looks like this:

  1. Translation to Chisel - The code generator translates the input model into code in the Chisel language. Chisel is a language for high-level hardware description built into Scala. It provides abstractions that simplify hardware design and allow you to leverage Scala’s design capabilities;

  2. Conversion to FIRRTL - Chisel exposes high-level designs and converts to FIRRTL (Flexible Intermediate Representation for RTL). In this step:

    1. 1. High-level methods such as reduce are replaced by their low-level equivalents;

    1. 2. FixedPoint operations are converted to bitwise operations.

  3. Translation to Verilog - FIRRTL is converted to the final Verilog code using CIRCT (firtool tool).

How to get Chisel code?

By default, the Chisel code generated in the first step is not saved in the code folder. However, it can be useful for debugging or development. To retrieve this file, use program control by passing the target="chisel" argument to the generate_code command. For example:

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

After executing the Chisel command, the Chisel code will be saved in the specified folder and can be used for further work:

verilog command line example 2

The file with the extension .scala contains the Chisel code:

Chisel code example
//> 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"))
}
The verification model generated from the .jl script will only run if Verilog is selected as the target platform. If the obj_dir folder was not generated, the verification model will not run.

Code templates are exposed in the first generation step, so HDL template creation should be done mostly in Chisel, using Julia’s built-in control constructs if necessary.

You can read more about working with custom code generation templates in article.

Supported units

The following library blocks are supported by the Verilog code generator Engee: