CIC抽取器的计算机辅助设计:从行为模型到Verilog代码
高效且资源高效的滤波器架构在现代数字信号处理中占据了特殊的位置,其中Cic(级联积分器-梳状)滤波器因其独特的能力而脱颖而出,无需使用乘法器 本文介绍了一种全面的CIC抽取器设计方法,该方法具有滤波器结构的自动生成和有效信号的智能控制,最终生成了面向硬件的Verilog代码。
在我们的示例中,cic抽取器设计方法分为三个连续的阶段,形成了从概念到硬件实现的完整循环。:
1. 过滤器结构的自动生成
该算法根据指定的参数(顺序、抽取、位深度)自动创建cic滤波器的分层架构。 自动化消除了手动设计错误并缩短了开发时间。
2. 有效信号的智能控制
子系统 Gen_valid 生成与抽取过程同步的时钟验证信号。 该架构包括具有抽取系数的计数器、脉冲产生逻辑和信号滤波器控制机制。 Enable.
3. 从模型描述生成Verilog代码
将行为模型转化为Verilog语言中的硬件描述的过程包括将数学运算转化为硬件块,寄存器的生成和在速度和资源强度方面具有优化的I/O接口。 我们生成代码的模型的上层如下所示。
现在让我们继续实现,下面的代码设置CIC滤波器的基本参数:生成唯一的模型名称,确定保存文件的路径,设置抽取系数R=5和16位14小数位的定点格式。
# CIC滤波器参数
name_model = "cic_$(round(Int, rand() * 10000))"
Path = (@__DIR__) * "/" * name_model * ".engee"
println("Path: $Path")
R = 5 # 抽取系数
FIXED_POINT_TYPE = "fixdt(1, 16, 14)"
接下来,我们将创建CIC过滤器的基本结构。
首先,使用输入和输出端口创建新模型,并设置定点格式。 然后加入一个积分器部分,由一个加法器和一个延时单元连接在一个反馈回路中实现积分。 此外,在周期中创建单个延迟的级联,其数量对应于抽取系数R=5。 组合部分通过添加加法器形成,该加法器将积分器的输出连接到最后一个延迟的输出以实施微分。 在形成完整的过滤器结构后,模型被保存到文件中并上传回以供进一步工作。
engee.create(name_model)
engee.add_block("/Basic/Ports & Subsystems/In1", name_model*"/")
engee.add_block("/Basic/Ports & Subsystems/Out1", name_model*"/")
engee.set_param!(name_model*"/In1",
"OutDataTypeStr" => "Fixed-point",
"OutDataTypeStrFixed" => FIXED_POINT_TYPE)
engee.add_block("/Basic/Math Operations/Add", name_model*"/Integrator_Add")
engee.add_block("/Basic/Discrete/Unit Delay", name_model*"/Integrator_Delay")
engee.set_param!(name_model*"/Integrator_Add",
"Inputs" => "+-",
"OutDataTypeStr" => "Fixed-point",
"OutDataTypeStrFixed" => FIXED_POINT_TYPE)
engee.add_line("In1/1", "Integrator_Add/1")
engee.add_line("Integrator_Add/1", "Integrator_Delay/1")
engee.add_line("Integrator_Delay/1", "Integrator_Add/2")
prev_delay = "Integrator_Delay"
for i in 1:R
delay_name = "Delay_$i"
engee.add_block("/Basic/Discrete/Unit Delay", name_model*"/"*delay_name)
if i == 1
engee.add_line("Integrator_Delay/1", delay_name*"/1")
else
prev_delay_name = "Delay_$(i-1)"
engee.add_line(prev_delay_name*"/1", delay_name*"/1")
end
prev_delay = delay_name
end
engee.add_block("/Basic/Math Operations/Add", name_model*"/Comb_Add")
engee.set_param!(name_model*"/Comb_Add",
"OutDataTypeStr" => "Fixed-point",
"OutDataTypeStrFixed" => FIXED_POINT_TYPE)
engee.add_line("Integrator_Delay/1", "Comb_Add/1")
engee.add_line("Delay_$R/1", "Comb_Add/2")
engee.add_line("Comb_Add/1", "Out1/1")
engee.save(Path)
model = engee.load(Path, force=true)
下面的函数启动CIC过滤器模型,它检查模型是加载、打开还是加载,执行仿真并返回结果,然后提取仿真结果:
Valid_out-验证信号(每5个时钟周期有效)Data_out-输出数据(仅限有效样本)Data_CIC-所有过滤数据
我们建立了两个图表:
- 比较
Data_out和Data_CIC-显示抽取效果 - 信号
Valid_out-演示验证的频率
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("cic_5") # 启动模型。
Valid_out = collect(simout["cic_5/CIC_decim/Gen_valid/Logical Operator.1"]).value
Data_out = collect(simout["cic_5/CIC_decim/开关。1"]).value
Data_CIC = collect(simout["cic_5/CIC_decim/CIC.Out1"]).value
p1 = plot(Data_out, label="Data_out", linewidth=2, ylabel="意义", title="CIC滤波器的输出", legend=:topright)
plot!(p1, Data_CIC, label="Data_CIC", linewidth=2, linestyle=:dash)
p2 = plot(Valid_out, label="Valid_out", linewidth=2, ylabel="有效信号", title="有效信号(有效)", color=:red, legend=:topright)
plot(p1, p2, layout=(@layout [a; b]), size=(800, 600))
第一个图表显示有效计数之间的零值,这证实了抽取器正常工作。
现在让我们生成代码。
.png)
根据结果,我们看到5阶抽取器CIC的正确硬件代码已经生成。 该体系结构是分层实现的,主模块集成了CIC滤波器和有效信号发生器。 该系统具有高能效-过滤器仅使用有效数据激活,使用16位14小数位的定点格式。 每5个时钟周期用一个有效脉冲实现5:1抽取. 结构进行了优化-没有乘法,只有加法和减法运算。 该代码已准备好在FPGA中进行合成,并且完全符合原始模型。
为了确保过滤器正常工作,我们编写了一个简单的测试。:
module tb_cic;
reg clock;
reg reset;
wire valid_out;
wire [15:0] data_out;
reg [15:0] data_in;
reg valid_in;
real real_output_value;
integer results_file;
integer sim_log;
integer clock_count = 0;
integer sample_count = 0;
cic_5_CIC_decim dut (
.clock(clock),
.reset(reset),
.io_Data_in(data_in),
.io_Valid_in(valid_in),
.io_Valid_out(valid_out),
.io_Data_out(data_out)
);
always @(*) begin
real_output_value = $itor($signed(data_out)) / 16384.0;
end
always #5 clock = ~clock;
always @(posedge clock) begin
if (!reset) clock_count <= clock_count + 1;
end
initial begin
clock = 0;
reset = 1;
data_in = 16'h2000;
valid_in = 1;
results_file = $fopen("results.txt", "w");
sim_log = $fopen("simulation.log", "w");
$fdisplay(results_file, "Clock\tSample\tValid_out\tData_hex\tData_float");
$fdisplay(sim_log, "Начало симуляции CIC дециматора");
#20 reset = 0;
$fdisplay(sim_log, "\text{Сброс} \text{снят} \text{в} time = %0d", $time);
#1000;
$fdisplay(sim_log, "Завершение симуляции. Всего тактов: %0d, Валидных выходов: %0d",
clock_count, sample_count);
$fclose(results_file);
$fclose(sim_log);
$finish;
end
always @(posedge clock) begin
if (!reset) begin
if (clock_count == 10) data_in = 16'h4000;
else if (clock_count == 20) data_in = 16'h6000;
else if (clock_count == 30) data_in = 16'h2000;
else if (clock_count == 40) data_in = 16'h0000;
end
end
always @(posedge clock) begin
if (!reset) begin
if (valid_out) begin
sample_count = sample_count + 1;
$fdisplay(results_file, "%0d\t%0d\t%d\t\t%h\t\t%0.4f",
clock_count, sample_count, valid_out, data_out, real_output_value);
$fdisplay(sim_log, "Valid выход @ такт %0d: data=%h (float=%0.4f)",
clock_count, data_out, real_output_value);
end
if (clock_count > 10 && valid_out) begin
if ((clock_count % 5) != 0) begin
$fdisplay(sim_log, "ОШИБКА: valid_out=1 в такте %0d (должен быть каждый 5-й такт)", clock_count);
end
end
if (sample_count >= 50) begin
$fdisplay(sim_log, "Достигнуто 50 валидных выходов. Завершение.");
$fclose(results_file);
$fclose(sim_log);
$finish;
end
end
end
initial begin
#10;
forever begin
@(posedge clock);
if (!reset) begin
if ($time > 100 && $time < 200) begin
$fdisplay(sim_log, "Такт %0d: valid_in=%d, valid_out=%d, data_in=%h",
clock_count, valid_in, valid_out, data_in);
end
end
end
end
endmodule
# 使用代码定义目录的路径
prj_path = joinpath(@__DIR__, "cic_5_CIC_decim_code")
# 检查目录的存在
if !isdir(prj_path)
error("未找到目录:pr prj_path")
end
# 暂时移动到项目目录
cd(prj_path) do
# 汇编
run(`iverilog -o sim tb_cic.v cic_5_CIC_decim.v CIC.v Gen_valid.v`)
# 运行模拟
run(`vvp sim`)
# 从文件读取结果
if isfile("results.txt")
lines = readlines("results.txt")
out_model = Float64[]
for line in lines
if occursin(r"^\d+\t", line) # 我们正在寻找带有数据的行(以数字开始)
parts = split(line, "\t")
if length(parts) >= 2
push!(out_model, parse(Float64, parts[2]))
end
end
end
# 表头的输出
println("非也。.\T值")
println("--\t-------")
# 仅输出前8个值
for i in 1:min(8, length(out_model))
println("$i\t$(out_model[i])")
end
# 显示总共有多少条记录
println("\n文件中的条目数:$(length(out_model))")
else
println("文件结果。txt未找到!")
end
end
测试已经证实了5阶cic抽取器的正确操作。 该系统运行正常,所获得的结果证明了这一点。 抽取按照5:1的预设比例工作-在100个仿真周期中,获得了20个有效输出样本,其对应于期望值。 输出数据不为零,这证实了滤波器的可操作性和通过整个处理链的信号传输。
有效信号的正确性由它们的激活频率来确认--有效的_out信号每5个时钟周期被激活一次,就像抽取器结构中所期望的那样。 输出值的图表显示了从1.0到8.0的预期进展,这对应于积分器累积输入数据的工作。
结论
得到的结果证实,5阶的CIC滤波器功能正确,系数为5:1的抽取正确实现,积分器和组合器的结构按照理论预期处理数据,有效信号发生器正常工作。
该系统可用于实际数字信号处理应用,您可以使用不同类型的输入信号进行更复杂的测试,以全面验证性能。