QPSK+冷杉Verilog
此示例显示了与FIR滤波器组合的QPSK调制器模型。 该示例的目的是演示代码生成器的功能,以及使用嵌入在Engee环境中的Verilog模拟器验证此代码。
该模型具有模块化结构,由三个基本块组成:
 
- 
Gen_data-简单地生成从[0,0]到[1,1]的位组合 
- 
QPSK_modulator-将位序列转换为复数符号(在此实现中不考虑载波相移)。 
 
- FIR是一个具有有限脉冲响应的数字滤波器,它将每个输出值计算为最后11个输入样本的加权和。
 
现在让我们继续启动模型和代码生成。
    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") # Запуск модели.
Out[0]:
    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]:
这些图表将有助于我们与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
现在让我们运行模拟。
    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")
正如我们所看到的,这是可能的,但不是很方便和快速,再加上我们没有拉出字段的名称。
所以让我们使用第二个选项并分析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代码模拟测试,并且还生成了通信系统传输路径的简单模型。