Engee 文档
Notebook

OPSK接收机和FPGA发射机

此示例演示了为代码生成而设计的通信系统的测试模型。 为了清楚地显示从简单概念到Fpga现成代码的过渡过程,我们特意在模型中引入了一些简化。 本文介绍了该项目的完整描述和所有工作阶段的详细分析。https://habr.com/ru/companies/etmc_exponenta/articles/969564
/>)

我们将考虑的第一个模型将为代码热电联产开发的块与Engee标准库中的块进行比较。 这使我们能够验证我们创建的算法。

1.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("test_sim") # 启动模型。
Building...
Progress 0%
Progress 41%
Progress 100%
Progress 100%
Out[0]:
SimulationResult(
    run_id => 3,
    "QPSK_demodulator-1.Out_Valid" => WorkspaceArray{Bool}("test_sim/QPSK_demodulator-1.Out_Valid")
,
    "out_v" => WorkspaceArray{UInt32}("test_sim/out_v")
,
    "Complex to Real-Imag-1.r" => WorkspaceArray{Float64}("test_sim/Complex to Real-Imag-1.r")
,
    "Nyquist_filter_TX.re" => WorkspaceArray{Float64}("test_sim/Nyquist_filter_TX.re")
,
    "out_ref" => WorkspaceArray{Int64}("test_sim/out_ref")
,
    "Complex to Real-Imag-2.r" => WorkspaceArray{Vector{Float64}}("test_sim/Complex to Real-Imag-2.r")
,
    "Complex to Real-Imag.r" => WorkspaceArray{Vector{Float64}}("test_sim/Complex to Real-Imag.r")
,
    "Nyquist_filter_RX.re" => WorkspaceArray{Float64}("test_sim/Nyquist_filter_RX.re")
,
    "Gain.1" => WorkspaceArray{Fixed{1, 16, 10, Int16}}("test_sim/Gain.1")

)
In [ ]:
out_v_data = collect(simout["test_sim/out_v"]).value
out_ref_data = collect(simout["test_sim/out_ref"]).value

p_ref = plot(out_ref_data, 
           seriestype = :steppost,  # 图形类型-步骤
           label="的参考信号", 
           linewidth=2, 
           color=:orange,
           ylabel="意义", 
           title="参考信号(out_ref)",
           xlims=(0, 16),  # 范围从0到16
           legend=:topright)

p_v = plot(out_v_data, 
           seriestype = :steppost,  # 图形类型-步骤
           label="用于代码生成的模型信号", 
           linewidth=2, 
           color=:blue,
           ylabel="意义", 
           title="代码生成的模型信号(out_v)",
           xlims=(0, 150),  # 范围从0到150
           legend=:topright)
plot(p_ref, p_v, 
     layout=(@layout [a; b]),  # 两个图形垂直
     size=(800, 600))
Out[0]:

在绘制了两种型号的QPSK解调器输出后,我们可以看到它们相同的操作。 行为的唯一区别在于,由于代码生成模型中存在有效性信号,所有样本都从其输出中排除,直到累加全帧。 基于
在测试中,可以得出结论,我们开发的滤波器和调制器工作正常。

我们将考虑的第二个模型是代码生成的最终模型。 它包括一个测试数据发生器和输入和输出之间的误差输出,以及实现频率偏移,而不实现低通滤波器,该算法的部分实现主要是为了演示这一特性,而不是完全显示功能。.

image.png
In [ ]:
run_model("model") # 启动模型。
Building...
Progress 0%
Progress 10%
Progress 51%
Progress 99%
Progress 100%
Progress 100%
Out[0]:
SimulationResult(
    run_id => 4,
    "Test.err_val" => WorkspaceArray{Bool}("model/System/Test.err_val")
,
    "RX.valid" => WorkspaceArray{Bool}("model/System/RX.valid")
,
    "Gen_data.Valid" => WorkspaceArray{Bool}("model/System/Gen_data.Valid")
,
    "Data Type Conversion-1.1" => WorkspaceArray{Fixed{0, 1, 0, UInt8}}("model/System/Gen_data/Data_rend/Data Type Conversion-1.1")
,
    "QPSK_modulator.Re" => WorkspaceArray{Fixed{1, 8, 7, Int8}}("model/System/TX/QPSK_modulator.Re")
,
    "Nyquist_filter_RX.FIR_out" => WorkspaceArray{Fixed{1, 16, 14, Int16}}("model/System/RX/Nyquist_filter_RX.FIR_out")
,
    "err_bit_1" => WorkspaceArray{Bool}("model/System/err_bit_1")
,
    "Nyquist_filter_TX.re" => WorkspaceArray{Fixed{1, 16, 14, Int16}}("model/System/TX/Nyquist_filter_TX.re")
,
    "Delay_103.Out1" => WorkspaceArray{Bool}("model/System/Test/Delay_103.Out1")
,
    "Nyquist_filter_RX.im" => WorkspaceArray{Fixed{1, 16, 14, Int16}}("model/System/RX/Nyquist_filter_RX.im")
,
    "Nyquist_filter_RX.re" => WorkspaceArray{Fixed{1, 16, 14, Int16}}("model/System/RX/Nyquist_filter_RX.re")
,
    "Switch.1" => WorkspaceArray{Fixed{1, 16, 14, Int16}}("model/System/TX/Switch.1")
,
    "System.Re_filt_RX" => WorkspaceArray{Fixed{1, 16, 14, Int16}}("model/System.Re_filt_RX")
,
    "QPSK_demodulator.valid" => WorkspaceArray{Bool}("model/System/RX/QPSK_demodulator.valid")
,
    "Data Type Conversion.1" => WorkspaceArray{Fixed{0, 1, 0, UInt8}}("model/System/Gen_data/Data_rend/Data Type Conversion.1")
,
    "Valid_out.1" => WorkspaceArray{Bool}("model/System/Test/Valid_out.1")
,
    "err_bit_2" => WorkspaceArray{Bool}("model/System/err_bit_2")

)
In [ ]:
err_bit_1_data = collect(simout["model/System/err_bit_1"]).value
err_bit_2_data = collect(simout["model/System/err_bit_2"]).value
err_val_data = collect(simout["model/System/Test.err_val"]).value
sum_err_bit_1 = sum(err_bit_1_data)    
sum_err_bit_2 = sum(err_bit_2_data)   
sum_err_val = sum(err_val_data)       
println("第一位上的误差总和: ", sum_err_bit_1)
println("第二个比特上的误差总和: ", sum_err_bit_2)
println("验证错误的总和:    ", sum_err_val)
Сумма ошибок по первому биту: 0
Сумма ошибок по второму биту: 0
Сумма ошибок по валидам:    0

正如我们所看到的,模型不会注册错误。 这意味着,至少在不考虑通信信道的情况下,系统正常工作。

下一步是在Verilog上生成代码并验证其功能。 除了代码本身,我们还使用验证器在C中创建了一个模型,用于功能验证。![未命名。png](附件:没有名字。png)

In [ ]:
run_model("verification") # 启动模型。
Building...
Progress 0%
Progress 100%
Progress 100%
Out[0]:
SimulationResult(
    run_id => 7,
    "C Function.3" => WorkspaceArray{Bool}("verification/C Function.3")
,
    "C Function.1" => WorkspaceArray{Bool}("verification/C Function.1")
,
    "C Function.2" => WorkspaceArray{Bool}("verification/C Function.2")

)

为了检查生成代码的可操作性,通过与前面的模型类比,我们将计算位流和有效性信号的错误。

In [ ]:
err_bit_1_data = collect(simout["verification/C Function.1"]).value
err_bit_2_data = collect(simout["verification/C Function.2"]).value
err_val_data = collect(simout["verification/C Function.3"]).value
sum_err_bit_1 = sum(err_bit_1_data)    
sum_err_bit_2 = sum(err_bit_2_data)   
sum_err_val = sum(err_val_data)       
println("第一位上的误差总和: ", sum_err_bit_1)
println("第二个比特上的误差总和: ", sum_err_bit_2)
println("验证错误的总和:    ", sum_err_val)
Сумма ошибок по первому биту: 0
Сумма ошибок по второму биту: 0
Сумма ошибок по валидам:    0

正如我们所看到的,生成的代码与原始模型的工作原理相同。 这使您确信,在没有资源或时间特性(时序)问题的情况下,它将在FPGA上正常工作,生成项目本身的文件结构如下所示。

In [ ]:
current_dir = @__DIR__
println("目录下的文件结构:$current_dir")
println()
for (root, dirs, files) in walkdir(current_dir)
    for dir in dirs
        if startswith(dir, "model_System_code")
            println("$dir/")
            dir_path = joinpath(root, dir)
            for file in readdir(dir_path)
                println("  └── $file")
            end
            println()
        end
    end
end
Структура файлов в директории: /user/my_projects/Demo/OPSK_verilog

model_System_code/
  └── Delay_103.v
  └── Delay_20.v
  └── Gen_data.v
  └── Nyquist_filter_RX.v
  └── Nyquist_filter_TX.v
  └── QPSK_demodulator.v
  └── QPSK_modulator.v
  └── RX.v
  └── TX.v
  └── Test.v
  └── bit_and_val_2.v
  └── delay_8.v
  └── delay_8_1_1.v
  └── fir_41.v
  └── gen_sin_cos_demod.v
  └── gen_sin_cos_mod.v
  └── model_System.v
  └── obj_dir
  └── quadrature_demodulator.v
  └── quadrature_modulator.v

结论

所有测试阶段的结果(图形的可视化比较,位错误的计数,用C模型验证)清楚地表明该项目的可操作性。 对所开发的算法进行了验证,并对模型自动生成代码的过程进行了研究,并给出了正确的结果。 因此,所提出的例子证实了用于创建数字通信系统的基于模型的设计方法的可行性。

显示了从Engee环境中的功能模型到硬件描述语言中随时可合成的代码的完整路径,这大大加快了FPGA开发过程。