Engee 文档
Notebook

QPSK+冷杉Verilog

此示例显示了与FIR滤波器组合的QPSK调制器模型。 该示例的目的是演示代码生成器的功能,以及使用嵌入在Engee环境中的Verilog模拟器验证此代码。

该模型具有模块化结构,由三个基本块组成:

image.png
  1. Gen_data-简单地生成从[0,0]到[1,1]的位组合

  2. QPSK_modulator-将位序列转换为复数符号(在此实现中不考虑载波相移)。

image.png
  1. FIR是一个具有有限脉冲响应的数字滤波器,它将每个输出值计算为最后11个输入样本的加权和。
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
run_model("QPSK+FIR_verilog") # Запуск модели.
Building...
Progress 0%
Progress 80%
Progress 100%
Progress 100%
Out[0]:
SimulationResult(
    "FIR.im" => WorkspaceArray{Fixed{1, 8, 7, Int8}}("QPSK+FIR_verilog/FIR.im")
,
    "FIR.re" => WorkspaceArray{Fixed{1, 8, 7, Int8}}("QPSK+FIR_verilog/FIR.re")
,
    "FIR.valid" => WorkspaceArray{Bool}("QPSK+FIR_verilog/FIR.valid")

)
In [ ]:
Re = collect(simout["QPSK+FIR_verilog/FIR.re"]).value
Im = collect(simout["QPSK+FIR_verilog/FIR.im"]).value
plot(Re, label="Re")
plot!(Im, label="Im")
Out[0]:
In [ ]:
collect(simout["QPSK+FIR_verilog/FIR.re"]).value
Out[0]:
301-element Vector{Fixed{1, 8, 7, Int8}}:
 fi(-0.0078125, 1, 8, 7)
 fi(-0.0078125, 1, 8, 7)
 fi(-0.0234375, 1, 8, 7)
 fi(-0.015625, 1, 8, 7)
 fi(-0.03125, 1, 8, 7)
 fi(0.25, 1, 8, 7)
 fi(0.234375, 1, 8, 7)
 fi(0.2421875, 1, 8, 7)
 fi(0.2265625, 1, 8, 7)
 fi(0.2265625, 1, 8, 7)
 fi(0.21875, 1, 8, 7)
 fi(0.21875, 1, 8, 7)
 fi(0.21875, 1, 8, 7)
 ⋮
 fi(0.2265625, 1, 8, 7)
 fi(0.21875, 1, 8, 7)
 fi(0.21875, 1, 8, 7)
 fi(0.21875, 1, 8, 7)
 fi(0.21875, 1, 8, 7)
 fi(0.21875, 1, 8, 7)
 fi(0.21875, 1, 8, 7)
 fi(0.21875, 1, 8, 7)
 fi(0.21875, 1, 8, 7)
 fi(0.21875, 1, 8, 7)
 fi(0.21875, 1, 8, 7)
 fi(0.2265625, 1, 8, 7)

这些图表将有助于我们与Verilog代码的工作进行比较。
现在,我们将从模型块生成代码,并以与模型输入相同的方式描述测试模块。

In [ ]:
engee.generate_code(
"$(@__DIR__)/test_codgen_ic.engee",
"$(@__DIR__)/prj",
subsystem_name="QPSK_modulator"
)
engee.generate_code(
"$(@__DIR__)/test_codgen_ic.engee",
"$(@__DIR__)/prj",
subsystem_name="fir-1"
)

Tb模块是一个测试平台,通过馈送测试数据并记录结果来验证QPSK调制器和FIR滤波器模块的操作。

测试台提供位的循环序列到输入[11, 00, 10, 01], 将调制器(QPSK_Re,QPSK_Im)和滤波器(FIR_Re,FIR_Im)的输出值写入文件output_data。txt和它在控制台中可视化它们,并生成波形测试文件_codgen_ic。用于时间图分析的vcd。

In [ ]:
filename = "$(@__DIR__)/prj/tb.v"
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
Содержимое файла /user/my_projects/Demo/QPSK+FIR_verilog/prj/tb.v:
==================================================
module tb;
  reg clock;
  reg reset;
  reg io_bit_1;
  reg io_bit_2;
  reg io_Valid;
  wire io_Out_Valid;
  wire [3:0] io_Out_Re;
  wire [3:0] io_Out_Im;
  wire fir_valid;
  wire [7:0] fir_re;
  wire [7:0] fir_im;
  
  reg [1:0] test_pattern [0:3];
  integer pattern_index;
  integer cycle_count;
  
  QPSKFIR_verilog_QPSK_modulator qpsk_mod (
    .clock(clock),
    .reset(reset),
    .io_bit_1(io_bit_1),
    .io_bit_2(io_bit_2),
    .io_Valid(io_Valid),
    .io_Out_Valid(io_Out_Valid),
    .io_Out_Re(io_Out_Re),
    .io_Out_Im(io_Out_Im)
  );
  
  QPSKFIR_verilog_FIR fir_filter (
    .clock(clock),
    .reset(reset),
    .io_Re(io_Out_Re),
    .io_Im(io_Out_Im),
    .io_Valid(io_Out_Valid),
    .io_valid(fir_valid),
    .io_re(fir_re),
    .io_im(fir_im)
  );
  
  always #5 clock = ~clock;
  
  initial begin
    $dumpfile("test_codgen_ic.vcd");
    $dumpvars(0, tb);
  end
  
  // Функции для преобразования в дробный формат
  function real qpsk_to_real;
    input [3:0] value;
    begin
      qpsk_to_real = $signed(value) / 8.0; // Q3.4: 3 бита целая часть, 4 бита дробная
    end
  endfunction
  
  function real fir_to_real;
    input [7:0] value;
    begin
      fir_to_real = $signed(value) / 128.0; // Q1.7: 1 бит знак, 7 бит дробная часть
    end
  endfunction
  
  integer data_file;
  initial begin
    data_file = $fopen("output_data.txt", "w");
    $fwrite(data_file, "Cycle\tTime\tQPSK_Valid\tQPSK_Re\tQPSK_Im\tFIR_Valid\tFIR_Re\tFIR_Im\tQPSK_Re_Real\tQPSK_Im_Real\tFIR_Re_Real\tFIR_Im_Real\n");
  end
  
  initial begin
    test_pattern[0] = 2'b11;
    test_pattern[1] = 2'b00;
    test_pattern[2] = 2'b10;
    test_pattern[3] = 2'b01;
    pattern_index = 0;
    cycle_count = 0;
  end
  
  initial begin
    clock = 0;
    reset = 1;
    io_bit_1 = 0;
    io_bit_2 = 0;
    io_Valid = 0;
    #20 reset = 0;
    #10;
    for (integer i = 0; i < 50; i = i + 1) begin
      io_bit_1 = test_pattern[pattern_index][1];
      io_bit_2 = test_pattern[pattern_index][0];
      io_Valid = 1;
      pattern_index = (pattern_index + 1) % 4;
      #10;
    end
    #100;
    $fclose(data_file);
    $finish;
  end
  
  always @(posedge clock) begin
    if (!reset) begin
      real qpsk_re_real, qpsk_im_real, fir_re_real, fir_im_real;
      
      // Преобразуем в дробные числа
      qpsk_re_real = qpsk_to_real(io_Out_Re);
      qpsk_im_real = qpsk_to_real(io_Out_Im);
      fir_re_real = fir_to_real(fir_re);
      fir_im_real = fir_to_real(fir_im);
      
      $fwrite(data_file, "%d\t%0t\t%b\t%d\t%d\t%b\t%d\t%d\t%f\t%f\t%f\t%f\n", 
              cycle_count, $time, 
              io_Out_Valid, $signed(io_Out_Re), $signed(io_Out_Im),
              fir_valid, $signed(fir_re), $signed(fir_im),
              qpsk_re_real, qpsk_im_real, fir_re_real, fir_im_real);
              
      $display("Cycle: %d | QPSK: Valid=%b, Re=%d(%f), Im=%d(%f) | FIR: Valid=%b, Re=%d(%f), Im=%d(%f)",
               cycle_count,
               io_Out_Valid, $signed(io_Out_Re), qpsk_re_real, $signed(io_Out_Im), qpsk_im_real,
               fir_valid, $signed(fir_re), fir_re_real, $signed(fir_im), fir_im_real);
               
      cycle_count = cycle_count + 1;
    end
  end
endmodule
==================================================
Конец файла

现在让我们运行模拟。

In [ ]:
# Компиляция
run(`iverilog -o sim tb.v QPSKFIR_verilog_FIR.v QPSKFIR_verilog_QPSK_modulator.v`)
# Запуск симуляции
run(`vvp sim`)

可以看到,根据仿真结果,生成了2个txt和vcd文件。

TXT是一个带有数据表(时间戳,信号值)的文本文件。

VCD(值变化转储)是时间图的二进制文件,用于GTKWave和其他分析仪中信号的可视化调试。

让我们尝试执行VCD解析。

In [ ]:
function vcd_to_txt(vcd_filename; output_txt="simple_output.txt")
    println("Упрощенная конвертация VCD в TXT: ", vcd_filename)
    lines = readlines(vcd_filename)
    signals = Dict{String, Vector{Tuple{Float64, Any}}}()
    current_time = 0.0
    for line in lines
        line = strip(line)
        isempty(line) && continue
        if startswith(line, "\$var") 
            parts = split(line)
            if length(parts) >= 4
                signal_id = parts[3]
                signal_name = parts[4]
                signals[signal_name] = []
            end
        elseif startswith(line, "#")
            current_time = parse(Float64, line[2:end])
        elseif length(line) >= 2
            value_char = line[1:1]
            signal_id = line[2:end]
            value = if value_char == "0"
                0
            elseif value_char == "1"
                1
            else
                0
            end
            for (name, values) in signals
                if occursin(signal_id, name) || signal_id == string(hash(name))[1:min(3, end)]
                    push!(values, (current_time, value))
                    break
                end
            end
        end
    end
    open(output_txt, "w") do io
        header = "Time\t" * join(keys(signals), "\t")
        println(io, header)
        all_times = Set{Float64}()
        for values in values(signals)
            for (time, _) in values
                push!(all_times, time)
            end
        end
        sorted_times = sort(collect(all_times))
        for time in sorted_times
            row = string(time)
            for signal_name in keys(signals)
                value = 0
                for (t, v) in signals[signal_name]
                    if t == time
                        value = v
                        break
                    elseif t < time
                        value = v
                    end
                end
                row *= "\t" * string(value)
            end
            println(io, row)
        end
    end
    println("Упрощенная конвертация завершена: ", output_txt)
end
vcd_to_txt("test_codgen_ic.vcd", output_txt="simple_result.txt")
Упрощенная конвертация VCD в TXT: test_codgen_ic.vcd
Упрощенная конвертация завершена: simple_result.txt

正如我们所看到的,这是可能的,但不是很方便和快速,再加上我们没有拉出字段的名称。

所以让我们使用第二个选项并分析TXT。

In [ ]:
using DataFrames
using DelimitedFiles
using Plots

data = readdlm("output_data.txt", '\t', skipstart=1)  # Пропускаем заголовок
cycle = data[:, 1]
qpsk_re = data[:, 4]  # QPSK_Re в 4-й колонке
qpsk_im = data[:, 5]  # QPSK_Im в 5-й колонке
fir_re = data[:, 7]   # FIR_Re в 7-й колонке
fir_im = data[:, 8]   # FIR_Im в 8-й колонке

p = plot(layout=(2, 1), size=(800, 600))
plot!(p[1], cycle, qpsk_re, label="QPSK Real", linewidth=2, color=:blue)
plot!(p[1], cycle, qpsk_im, label="QPSK Imag", linewidth=2, color=:red)
title!(p[1], "QPSK Signal")
plot!(p[2], cycle, fir_re, label="FIR Real", linewidth=2, color=:green)
plot!(p[2], cycle, fir_im, label="FIR Imag", linewidth=2, color=:orange)
title!(p[2], "FIR Filter Output")
Out[0]:

验证结果确认测试用例的行为与预期模型匹配。 微小的差异只能通过不同类型的数据来解释。 模块正常工作:QPSK生成字符,FIR滤波器处理它们。

结论

在这个例子中,我们已经弄清楚了如何可视化Verilog代码模拟测试,并且还生成了通信系统传输路径的简单模型。