Engee 文档
Notebook

从具有嵌套子系统的模型生成代码。

这个例子在逻辑上延续了前面的【演示】(https://engee.com/community/ru/catalogs/projects/qpsk-fir-verilog )。 它的目的是展示Verilog代码生成器的功能,即通过使用原子子系统构建最终项目的各种方法。

下图显示了实现的模型。 它包含配置 "Treat as atomic unit" 仅应用于两个块中的一个,这允许您直观地比较生成结果。

image.png

现在让我们继续代码生成并将结果与原始模型进行比较。 首先,让我们声明一个运行模型的辅助函数。

In [ ]:
function run_model( name_model)
    Path = (@__DIR__) * "/" * name_model * ".engee"
    if name_model in [m.name for m in engee.get_all_models()] # Проверка условия загрузки модели в ядро
        model = engee.open( name_model ) # Открыть модель
        model_output = engee.run( model, verbose=true ); # Запустить модель
    else
        model = engee.load( Path, force=true ) # Загрузить модель
        model_output = engee.run( model, verbose=true ); # Запустить модель
        engee.close( name_model, force=true ); # Закрыть модель
    end
    sleep(0.1)
    return model_output
end
Out[0]:
run_model (generic function with 1 method)

现在让我们在Verilog上配置代码生成的模型。

image.png

我们将直接从模型界面(单击按钮)执行代码生成,以免重载已经庞大的脚本。

image.png

接下来,我们声明一个辅助函数,用于读取生成的Verilog文件。 函数如何工作 read_v 它由以下内容组成。 首先,它检查指定文件在文件系统中的存在。 如果未找到该文件,则显示相应的消息。 成功检测后,该函数将文件的全部内容作为字符串读取,并将其直观地输出到控制台,并使用标头和分隔符对其进行框定,以便于感知。

In [ ]:
function read_v(filename)
    try
        if !isfile(filename)
            println("Файл $filename не найден!")
            return
        end
        println("Содержимое файла $filename:")
        println("="^50)
        content = read(filename, String)
        println(content)
        println("="^50)
        println("Конец файла")
    catch e
        println("Ошибка при чтении файла: ", e)
    end
end
Out[0]:
read_v (generic function with 1 method)

现在让我们继续比较子系统的实现。 关键区别在于输出代码的组织。

Без имени.png

在不使用原子子系统的情况下实现,头项目文件包括系统的所有逻辑,呈现为单个单片模块。 该模块包含多个寄存器和复杂的组合链,组合在一个命名空间中。

In [ ]:
read_v("$(@__DIR__)/model_RX.v")
Содержимое файла /user/start/examples/codegen/qpsk_and_fir_verilog_v2/model_RX.v:
==================================================
/* Code generated by Engee
 * Model name: model.engee
 * Code generator: release-1.1.22
 * Date: Wed Sep 10 06:27:06 2025 GMT
 */

module model_RX(
  input        clock,
               reset,
  output       io_Out3,
  output [7:0] io_Out1,
               io_Out2
);

  reg         UnitDelay_1_state;
  reg         UnitDelay_13_state;
  reg         UnitDelay_state;
  reg         UnitDelay_1_1_state;
  reg         UnitDelay_2_state;
  reg         UnitDelay_3_state;
  reg         UnitDelay_4_state;
  reg         UnitDelay_13_1_state;
  reg         UnitDelay_14_state;
  reg         UnitDelay_5_state;
  reg         UnitDelay_6_state;
  reg         UnitDelay_14_1_state;
  reg         UnitDelay_7_state;
  reg         UnitDelay_8_state;
  reg         UnitDelay_9_state;
  reg         UnitDelay_10_state;
  reg         UnitDelay_11_state;
  reg  [3:0]  UnitDelay_12_state;
  reg  [3:0]  UnitDelay_13_2_state;
  reg  [3:0]  UnitDelay_14_2_state;
  reg  [3:0]  UnitDelay_15_state;
  reg  [3:0]  UnitDelay_16_state;
  reg  [3:0]  UnitDelay_17_state;
  reg  [3:0]  UnitDelay_18_state;
  reg  [3:0]  UnitDelay_19_state;
  reg  [3:0]  UnitDelay_20_state;
  reg  [3:0]  UnitDelay_21_state;
  reg  [3:0]  UnitDelay_22_state;
  reg  [3:0]  UnitDelay_23_state;
  reg  [3:0]  UnitDelay_24_state;
  reg  [3:0]  UnitDelay_25_state;
  reg  [3:0]  UnitDelay_26_state;
  reg  [3:0]  UnitDelay_27_state;
  reg  [3:0]  UnitDelay_28_state;
  reg  [3:0]  UnitDelay_29_state;
  reg  [3:0]  UnitDelay_30_state;
  reg  [3:0]  UnitDelay_31_state;
  wire        LogicalOperator = UnitDelay_13_state ^ UnitDelay_14_state;
  wire        LogicalOperator_1 = UnitDelay_1_state ^ UnitDelay_14_1_state;
  wire [2:0]  _IdxAccum_T_2 =
    {1'h0, LogicalOperator, 1'h0} + {2'h0, LogicalOperator_1} + 3'h1;
  wire        _tmp6_T = _IdxAccum_T_2 == 3'h2;
  wire        _constellation_selector_im_T = _IdxAccum_T_2 == 3'h1;
  wire [3:0]  _constellation_selector_re_new_T_1 =
    _constellation_selector_im_T | ~(_tmp6_T | _IdxAccum_T_2 == 3'h3) ? 4'h6 : 4'hA;
  wire [3:0]  _constellation_selector_im_new_T_1 =
    _constellation_selector_im_T | _tmp6_T ? 4'h6 : 4'hA;
  wire [10:0] _Gain_1_new_T_1 =
    {{7{_constellation_selector_re_new_T_1[3]}}, _constellation_selector_re_new_T_1}
    * 11'h7FF;
  wire [10:0] _Gain_1_1_new_T_1 =
    {{7{_constellation_selector_im_new_T_1[3]}}, _constellation_selector_im_new_T_1}
    * 11'h7FF;
  wire [10:0] _Gain_3_new_T_1 =
    {{7{UnitDelay_14_2_state[3]}}, UnitDelay_14_2_state} * 11'h7FE;
  wire [10:0] _Gain_3_1_new_T_1 =
    {{7{UnitDelay_15_state[3]}}, UnitDelay_15_state} * 11'h7FE;
  wire [10:0] _Gain_5_new_T_1 =
    {{7{UnitDelay_19_state[3]}}, UnitDelay_19_state} * 11'h7FE;
  wire [10:0] _Gain_5_1_new_T_1 =
    {{7{UnitDelay_18_state[3]}}, UnitDelay_18_state} * 11'h7FE;
  wire [10:0] _Gain_6_new_T_1 = {{7{UnitDelay_21_state[3]}}, UnitDelay_21_state} * 11'h30;
  wire [10:0] _Gain_6_1_new_T_1 =
    {{7{UnitDelay_20_state[3]}}, UnitDelay_20_state} * 11'h30;
  wire [10:0] _Gain_7_new_T_1 =
    {{7{UnitDelay_22_state[3]}}, UnitDelay_22_state} * 11'h7FE;
  wire [10:0] _Gain_7_1_new_T_1 =
    {{7{UnitDelay_23_state[3]}}, UnitDelay_23_state} * 11'h7FE;
  wire [10:0] _Gain_9_new_T_1 =
    {{7{UnitDelay_27_state[3]}}, UnitDelay_27_state} * 11'h7FE;
  wire [10:0] _Gain_9_1_new_T_1 =
    {{7{UnitDelay_26_state[3]}}, UnitDelay_26_state} * 11'h7FE;
  wire [10:0] _Gain_11_new_T_1 =
    {{7{UnitDelay_30_state[3]}}, UnitDelay_30_state} * 11'h7FF;
  wire [10:0] _Gain_11_1_new_T_1 =
    {{7{UnitDelay_31_state[3]}}, UnitDelay_31_state} * 11'h7FF;
  always @(posedge clock) begin
    if (reset) begin
      UnitDelay_1_state <= 1'h0;
      UnitDelay_13_state <= 1'h0;
      UnitDelay_state <= 1'h0;
      UnitDelay_1_1_state <= 1'h0;
      UnitDelay_2_state <= 1'h0;
      UnitDelay_3_state <= 1'h0;
      UnitDelay_4_state <= 1'h0;
      UnitDelay_13_1_state <= 1'h1;
      UnitDelay_14_state <= 1'h0;
      UnitDelay_5_state <= 1'h0;
      UnitDelay_6_state <= 1'h0;
      UnitDelay_14_1_state <= 1'h1;
      UnitDelay_7_state <= 1'h0;
      UnitDelay_8_state <= 1'h0;
      UnitDelay_9_state <= 1'h0;
      UnitDelay_10_state <= 1'h1;
      UnitDelay_11_state <= 1'h0;
      UnitDelay_12_state <= 4'h0;
      UnitDelay_13_2_state <= 4'h0;
      UnitDelay_14_2_state <= 4'h0;
      UnitDelay_15_state <= 4'h0;
      UnitDelay_16_state <= 4'h0;
      UnitDelay_17_state <= 4'h0;
      UnitDelay_18_state <= 4'h0;
      UnitDelay_19_state <= 4'h0;
      UnitDelay_20_state <= 4'h0;
      UnitDelay_21_state <= 4'h0;
      UnitDelay_22_state <= 4'h0;
      UnitDelay_23_state <= 4'h0;
      UnitDelay_24_state <= 4'h0;
      UnitDelay_25_state <= 4'h0;
      UnitDelay_26_state <= 4'h0;
      UnitDelay_27_state <= 4'h0;
      UnitDelay_28_state <= 4'h0;
      UnitDelay_29_state <= 4'h0;
      UnitDelay_30_state <= 4'h0;
      UnitDelay_31_state <= 4'h0;
    end
    else begin
      UnitDelay_1_state <= UnitDelay_13_1_state;
      UnitDelay_13_state <= UnitDelay_10_state;
      UnitDelay_state <= UnitDelay_1_1_state;
      UnitDelay_1_1_state <= UnitDelay_9_state;
      UnitDelay_2_state <= UnitDelay_11_state;
      UnitDelay_3_state <= UnitDelay_5_state;
      UnitDelay_4_state <= UnitDelay_2_state;
      UnitDelay_13_1_state <= UnitDelay_8_state;
      UnitDelay_14_state <= UnitDelay_13_state;
      UnitDelay_5_state <= UnitDelay_4_state;
      UnitDelay_6_state <= UnitDelay_3_state;
      UnitDelay_14_1_state <= UnitDelay_1_state;
      UnitDelay_7_state <= UnitDelay_6_state;
      UnitDelay_8_state <= LogicalOperator_1;
      UnitDelay_9_state <= UnitDelay_7_state;
      UnitDelay_10_state <= LogicalOperator;
      UnitDelay_11_state <= 1'h1;
      UnitDelay_12_state <= _constellation_selector_re_new_T_1;
      UnitDelay_13_2_state <= _constellation_selector_im_new_T_1;
      UnitDelay_14_2_state <= UnitDelay_12_state;
      UnitDelay_15_state <= UnitDelay_13_2_state;
      UnitDelay_16_state <= UnitDelay_15_state;
      UnitDelay_17_state <= UnitDelay_14_2_state;
      UnitDelay_18_state <= UnitDelay_16_state;
      UnitDelay_19_state <= UnitDelay_17_state;
      UnitDelay_20_state <= UnitDelay_18_state;
      UnitDelay_21_state <= UnitDelay_19_state;
      UnitDelay_22_state <= UnitDelay_21_state;
      UnitDelay_23_state <= UnitDelay_20_state;
      UnitDelay_24_state <= UnitDelay_23_state;
      UnitDelay_25_state <= UnitDelay_22_state;
      UnitDelay_26_state <= UnitDelay_24_state;
      UnitDelay_27_state <= UnitDelay_25_state;
      UnitDelay_28_state <= UnitDelay_26_state;
      UnitDelay_29_state <= UnitDelay_27_state;
      UnitDelay_30_state <= UnitDelay_29_state;
      UnitDelay_31_state <= UnitDelay_28_state;
    end
  end // always @(posedge)
  assign io_Out3 = UnitDelay_state;
  assign io_Out1 =
    _Gain_1_new_T_1[10:3] + {8{UnitDelay_12_state[3]}} + _Gain_3_new_T_1[10:3]
    + {{6{UnitDelay_17_state[3]}}, UnitDelay_17_state[3:2]} + _Gain_5_new_T_1[10:3]
    + _Gain_6_new_T_1[10:3] + _Gain_7_new_T_1[10:3]
    + {{6{UnitDelay_25_state[3]}}, UnitDelay_25_state[3:2]} + _Gain_9_new_T_1[10:3]
    + {8{UnitDelay_29_state[3]}} + _Gain_11_new_T_1[10:3];
  assign io_Out2 =
    _Gain_1_1_new_T_1[10:3] + {8{UnitDelay_13_2_state[3]}} + _Gain_3_1_new_T_1[10:3]
    + {{6{UnitDelay_16_state[3]}}, UnitDelay_16_state[3:2]} + _Gain_5_1_new_T_1[10:3]
    + _Gain_6_1_new_T_1[10:3] + _Gain_7_1_new_T_1[10:3]
    + {{6{UnitDelay_24_state[3]}}, UnitDelay_24_state[3:2]} + _Gain_9_1_new_T_1[10:3]
    + {8{UnitDelay_28_state[3]}} + _Gain_11_1_new_T_1[10:3];
endmodule


==================================================
Конец файла
image.png

原子子系统方法演示了项目构建的模块化原理。 Head文件充当顶级包装器,用于实例化和连接各个功能模块:数据生成器、调制器和过滤器。 这些模块中的每一个都是一个独立的模块(Gen_data,QPSK_modulator,fir),具有自己明确的I/O接口。 这种结构不仅体现了将系统逻辑划分为组件,而且显着提高了代码的可读性、可维护性和可重用性。

In [ ]:
read_v("$(@__DIR__)/model_RX_atomic_code/model_RX_atomic.v")
Содержимое файла /user/start/examples/codegen/qpsk_and_fir_verilog_v2/model_RX_atomic_code/model_RX_atomic.v:
==================================================
/* Code generated by Engee
 * Model name: model.engee
 * Code generator: release-1.1.22
 * Date: Wed Sep 10 06:28:25 2025 GMT
 */

module model_RX_atomic(
  input        clock,
               reset,
  output [7:0] io_Out1,
               io_Out2,
  output       io_Out3
);

  wire [3:0] _QPSK_modulator_io_Out_Re;
  wire [3:0] _QPSK_modulator_io_Out_Im;
  wire       _Gen_data_io_Out1;
  wire       _Gen_data_io_Out2;
  Gen_data Gen_data (
    .clock   (clock),
    .reset   (reset),
    .io_Out1 (_Gen_data_io_Out1),
    .io_Out2 (_Gen_data_io_Out2)
  );
  QPSK_modulator QPSK_modulator (
    .io_bit_1  (_Gen_data_io_Out1),
    .io_bit_2  (_Gen_data_io_Out2),
    .io_Out_Re (_QPSK_modulator_io_Out_Re),
    .io_Out_Im (_QPSK_modulator_io_Out_Im)
  );
  fir fir (
    .clock    (clock),
    .reset    (reset),
    .io_Re    (_QPSK_modulator_io_Out_Re),
    .io_Im    (_QPSK_modulator_io_Out_Im),
    .io_valid (io_Out3),
    .io_re    (io_Out1),
    .io_im    (io_Out2)
  );
endmodule


==================================================
Конец файла

从比较中也可以看出,编写测试环境(testbench)是一项更简单的任务,专门针对没有原子子系统的项目版本。 由于在这种情况下生成的代码是单个模块,因此其连接和监视输出不需要复杂层次结构的描述。 在这个例子中,系统的所有逻辑都包含在一个块中,允许您直接监视输出信号。

我们将利用这一优势进行验证。 由于项目的两个版本的功能行为是相同的,我们将检查单片实现工作的正确性。

此测试台执行两个主要功能:

  1. 时钟和复位产生:产生周期性时钟信号(clock)和复位控制信号(reset)以初始化器件。
  2. 输出信号的日志记录:所有设备输出(io_Out3,io_Out1,io_Out2)都写入文本文件"输出。txt"在复位信号被去除之后的时钟信号的每个正沿上。
In [ ]:
read_v("$(@__DIR__)/tb.v")
Содержимое файла /user/start/examples/codegen/qpsk_and_fir_verilog_v2/tb.v:
==================================================
`timescale 1ns/1ps

module model_RX_tb;
  reg clock;
  reg reset;
  wire io_Out3;
  wire [7:0] io_Out1;
  wire [7:0] io_Out2;

  model_RX dut (
    .clock(clock),
    .reset(reset),
    .io_Out3(io_Out3),
    .io_Out1(io_Out1),
    .io_Out2(io_Out2)
  );
  integer file;
  
  always #5 clock = ~clock;
  
  initial begin
    clock = 0;
    reset = 1;
    file = $fopen("output.txt", "w");
    #10 reset = 0;
    #1000;
    $fclose(file);
    $finish;
  end
  
  always @(posedge clock) begin
    if (!reset) begin
      $fdisplay(file, "%0t %b %h %h", $time, io_Out3, io_Out1, io_Out2);
    end
  end
endmodule
==================================================
Конец файла

现在让我们运行初始模型并测试生成的代码。 这将允许我们通过比较Engee仿真环境中源系统的行为与在模拟器中执行生成的Verilog代码时获得的结果来验证生成器的正确性。

In [ ]:
run_model("model") # Запуск модели.
run(`iverilog -o sim tb.v model_RX.v`)# Компиляция
run(`vvp sim`)# Запуск симуляции
Building...
Progress 0%
Progress 100%
Progress 100%
Out[0]:
Process(`vvp sim`, ProcessExited(0))

为了后续的分析,需要测试平台生成的文本文件的解析功能。 实现的功能 parse_simulation_data 将原始记录的数据转换为适合分析的格式,函数本身返回三个准备好分析和绘图的数组。

运算算法:

  1. **读取数据:**函数读取文件,用空格分隔行(时间戳、有效信号、正交分量的十六进制值)
  2. **格式转换:**十六进制字符串通过从十六进制表示解析转换为UInt8数字
  3. **归一化:**关键步骤是通过附加代码(twos补码)将数字转换,然后通过除以128将其归一化为范围[-1.0,~0.992]
In [ ]:
using DelimitedFiles
function parse_simulation_data(filename)
    data = readdlm(filename, ' ', skipstart=0)
    valid = Int.(data[:, 2])
    re_hex = string.(data[:, 3])
    im_hex = string.(data[:, 4])
    Re_u8 = [parse(UInt8, h, base=16) for h in re_hex]
    Im_u8 = [parse(UInt8, h, base=16) for h in im_hex]
    
    function twos_complement_to_float(x::UInt8)
        x_signed = reinterpret(Int8, x)
        return Float64(x_signed) / 128.0
    end
    Re = twos_complement_to_float.(Re_u8)
    Im = twos_complement_to_float.(Im_u8)
    
    return valid, Im, Re
end

Val, Im, Rm = parse_simulation_data("output.txt")
Re_sim = collect(simout["model/RX.Out1"]).value
Im_sim = collect(simout["model/RX.Out2"]).value
Val_sim = collect(simout["model/RX.Out3"]).value;

现在让我们继续进行比较分析。 下面的图表显示:星座图,信号的实部和虚部的图表,以及有效性信号的图表。

In [ ]:
function plot_iq_constellation(Re, Im; title="", color=:blue)
    scatter(Re, Im, 
            aspect_ratio=:equal,
            markersize=2,
            markerstrokewidth=0,
            alpha=0.6,
            title=title,
            xlabel="In-phase Component (I)",
            ylabel="Quadrature Component (Q)",
            legend=false,
            grid=true,
            color=color)
end

valid_indices_sim = findall(Val_sim .== 1)  # Индексы где Val_sim == 1
valid_indices = findall(Val .== 1)          # Индексы где Val == 1

p1 = plot_iq_constellation(Re_sim[valid_indices_sim], Im_sim[valid_indices_sim], 
                            title="Созвездие модели", color=:blue)
p2 = plot_iq_constellation(Rm[valid_indices], Im[valid_indices],
                            title="Созвездие Verilog", color=:red)
plot(p1, p2, layout=(1,2), size=(800,400))
Out[0]:
In [ ]:
plot(Re_sim, label="Re_sim", seriestype=:steppost)
plot!(Rm, label="Rm", seriestype=:steppost)
Out[0]:
In [ ]:
plot(Im_sim, label="Im_sim", seriestype=:steppost)
plot!(Im, label="Im", seriestype=:steppost)
Out[0]:
In [ ]:
plot(Val_sim, label="Valid_sim", seriestype=:steppost)
plot!(Val, label="Valid", seriestype=:steppost)
Out[0]:

通过对这些图形的可视化分析,您可以验证生成的代码是否与原始模型的行为完全匹配。 信号的形状,星座的性质和时间参数完全相同,这证实了自动生成的Verilog代码的正确功能及其与原始数学模型的精确等价。

结论

这项工作清楚地展示了使用Engee模型自动Verilog代码生成的有效性。 通过代码生成获得的系统初始数学模型与其硬件实现之间的完整功能等价性已经通过实验证实。

对两种项目结构化方法的比较分析-使用原子子系统的单片和模块化-显示了它们在代码组织方面的根本差异,同时保持相同的行为。 模块化方法提供了更好的代码可读性、可维护性和可重用性,而整体实现简化了测试环境的创建。

通过时间图和星座图的比较验证结果,确认了所有输出信号的确切对应关系,这表明代码生成工具的正确操作及其实际应用于数字系统设计的可能性。