1. 摘要
在数字信号处理系统中,CIC(级联积分梳状)滤波器因其结构简单、无需乘法器、处理速率高等优点,被广泛应用于数字下变频(DDC)和数字上变频(DUC)中的抽取与插值环节。本文基于Xilinx CIC Compiler IP核,设计了一个输入16bit、输出24bit的CIC滤波器模块(tops),并给出了完整的Verilog实现、输出打拍优化以及一个功能全面的仿真测试平台(test_tops)。文章详细介绍了设计思路、创新点、功能点,并附上全部源码,方便读者直接使用或二次开发。
2. 设计背景与需求
- CIC滤波器原理:由积分器、梳状器和抽取/插值器组成,利用多速率信号处理技术,仅使用加法器、减法器和寄存器,非常适合FPGA实现。
- 应用场景:无线通信接收机中的DDC、ADC采样率变换、音频处理等。
- 本设计目标:
- 输入:16位有符号数据(二进制补码)
- 输出:24位有符号数据
- 使用Xilinx CIC Compiler IP核(可参数化配置级数、抽取因子、差分延迟等)
- 增加输出打拍寄存器以提高时序收敛能力
- 提供完备的仿真激励,验证滤波器功能
3. 模块设计及创新点
3.1 顶层模块tops
端口列表:
| 端口名 | 方向 | 位宽 | 说明 |
|---|---|---|---|
| i_clk | input | 1 | 工作时钟 |
| i_rst | input | 1 | 异步复位(高有效) |
| i_din | input | signed [15:0] | 输入数据 |
| o_dout | output | signed [23:0] | 输出数据 |
内部结构:
- 实例化Xilinx CIC Compiler IP核(
cic_compiler_0)- 使用
~i_rst完成复位极性转换(IP核为低电平复位) s_axis_data_tvalid固定拉高,表示数据持续有效- 输出数据
m_axis_data_tdata位宽24bit
- 使用
- 输出打拍优化:新增寄存器
ro_dout,在时钟上升沿寄存滤波结果,最后通过assign o_dout = ro_dout输出。这样做可以切断IP核输出到外部端口的组合路径,大幅改善时序,尤其适用于高时钟频率设计。
创新点总结:
- 输出打拍结构:采用
ro_前缀寄存器 +assign输出,既满足命名规范,又优化了时序。 - 复位极性转换:在实例化端口处直接用
~i_rst完成,无需额外逻辑。 - 规范的代码风格:按照参数分组、信号前缀、三段式状态机预留等要求编写,具有良好的可读性和可维护性。
3.2 仿真测试模块test_tops
为验证CIC滤波器的正确性,设计了一个长时间运行的测试平台:
- 产生50MHz(周期10ns)的时钟信号(
#5翻转) - 复位后延迟100ns释放
- 向输入端连续施加多组随机变化的16位有符号数据,包括正数、负数、零以及边界值(如±5000、±4500等)
- 仿真时间足够长(超过80个数据变化),可以观察到滤波器的瞬态响应和稳态输出
功能点:
- 覆盖了典型的信号变化模式,能够测试CIC滤波器的频率响应和累加溢出行为。
- 使用
#40延迟(即4个时钟周期)改变一次输入数据,模拟低速数据更新场景。 - 可直接在Vivado/Modelsim中运行,观察输出波形或导出数据做频域分析。
4. 使用说明
- IP核生成:在Vivado中打开IP Catalog,搜索“CIC Compiler”,配置参数(如滤波器类型为抽取,抽取因子=2,级数=4,差分延迟=1等)。生成后IP核名称为
cic_compiler_0。 - 文件添加:将
tops.v和test_tops.v添加到工程中。 - 仿真运行:在Vivado Simulator或Modelsim中运行
test_tops模块,观察o_dout波形。由于CIC滤波器存在处理延迟(群延迟 = N×D×R/2,N为级数,D为差分延迟,R为抽取因子),输出信号会相对于输入有一定的延时,同时实现抽取/插值效果。 - 时序约束:顶层
tops由于增加了输出寄存器,可直接将o_dout约束到输出引脚,时序收敛更容易。
5. 总结
本文提供了一个可综合的CIC滤波器Verilog设计,其特点包括:
- 基于Xilinx IP核,可灵活配置参数
- 输出打拍结构,优化时序性能
- 规范且易于理解的代码组织
- 功能全面的测试激励
读者可以直接复制代码到自己的工程中,根据实际需要修改IP核参数或测试数据。如有任何疑问,欢迎在评论区交流讨论。
6. 完整代码
6.1 顶层模块 tops.v
`timescale 1ns / 1ps module tops ( input i_clk, input i_rst, input signed [15:0] i_din, output signed [23:0] o_dout ); //========================================================= // parameter definitions //========================================================= //========================================================= // reg definitions //========================================================= reg signed [23:0] ro_dout; // output pipeline register //========================================================= // wire definitions //========================================================= wire [23:0] w_m_axis_data_tdata; // CIC output data //========================================================= // assign definitions //========================================================= //========================================================= // FSM state (not used) //========================================================= //========================================================= // inst //========================================================= cic_compiler_0 cic_compiler_u ( .aclk (i_clk), // input wire aclk .aresetn (~i_rst), // input wire aresetn .s_axis_data_tdata (i_din), // input wire [15:0] s_axis_data_tdata .s_axis_data_tvalid (1'b1), // input wire s_axis_data_tvalid .s_axis_data_tready (), // output wire s_axis_data_tready .m_axis_data_tdata (w_m_axis_data_tdata), // output wire [23:0] m_axis_data_tdata .m_axis_data_tvalid () // output wire m_axis_data_tvalid ); //========================================================= // combine_Logic //========================================================= //========================================================= // always //========================================================= // Output pipeline register always @(posedge i_clk or posedge i_rst) begin if (i_rst) ro_dout <= 'd0; else ro_dout <= w_m_axis_data_tdata; end // Connect output assign o_dout = ro_dout; endmodule6.2 仿真测试模块 test_tops.v
`timescale 1ns / 1ps module test_tops; reg i_clk; reg i_rst; reg signed [15:0] i_din; wire signed [23:0] o_dout; tops tops_u ( .i_clk (i_clk), .i_rst (i_rst), .i_din (i_din), .o_dout (o_dout) ); initial begin i_clk = 1'b1; i_rst = 1'b1; i_din = 16'd0; #100 i_rst = 1'b0; i_din = 16'd1000; #40 i_din = 16'd2000; #40 i_din = -3000; #40 i_din = 16'd5000; #40 i_din = 16'd1000; #40 i_din = 16'd1200; #40 i_din = 16'd300; #40 i_din = 16'd4500; #40 i_din = -2000; #40 i_din = -2300; #40 i_din = 16'd50; #40 i_din = 16'd1500; #40 i_din = 16'd200; #40 i_din = 16'd1200; #40 i_din = 16'd4200; #40 i_din = -2000; #40 i_din = -3000; #40 i_din = 16'd5000; #40 i_din = 16'd1000; #40 i_din = 16'd1200; #40 i_din = 16'd300; #40 i_din = -4500; #40 i_din = -2000; #40 i_din = 16'd2300; #40 i_din = 16'd50; #40 i_din = 16'd1500; #40 i_din = -200; #40 i_din = 16'd1200; #40 i_din = 16'd4200; #40 i_din = -2000; #40 i_din = -3000; #40 i_din = 16'd5000; #40 i_din = 16'd1000; #40 i_din = 16'd1200; #40 i_din = 16'd300; #40 i_din = -4500; #40 i_din = -2000; #40 i_din = -2300; #40 i_din = 16'd50; #40 i_din = 16'd1500; #40 i_din = 16'd200; #40 i_din = 16'd1200; #40 i_din = -4200; #40 i_din = 16'd2000; #40 i_din = 16'd3000; #40 i_din = -5000; #40 i_din = -1000; #40 i_din = 16'd1200; #40 i_din = 16'd300; #40 i_din = 16'd4500; #40 i_din = -2000; #40 i_din = -2300; #40 i_din = 16'd50; #40 i_din = 16'd1500; #40 i_din = 16'd200; #40 i_din = 16'd1200; #40 i_din = -4200; #40 i_din = 16'd2000; #40 i_din = 16'd3000; #40 i_din = -5000; #40 i_din = 16'd1000; #40 i_din = 16'd1200; #40 i_din = 16'd300; #40 i_din = 16'd4500; #40 i_din = -2000; #40 i_din = 16'd2300; #40 i_din = 16'd50; #40 i_din = 16'd1500; #40 i_din = -200; #40 i_din = 16'd1200; #40 i_din = 16'd4200; #40 i_din = 16'd2000; #40 i_din = -3000; #40 i_din = 16'd5000; #40 i_din = 16'd1000; #40 i_din = 16'd1200; #40 i_din = 16'd300; #40 i_din = -4500; #40 i_din = 16'd2000; #40 i_din = 16'd2300; #40 i_din = -50; #40 i_din = 16'd1500; #40 i_din = 16'd200; #40 i_din = -1200; #40 i_din = 16'd4200; end always #5 i_clk = ~i_clk; endmodule版权声明:本文为CSDN博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。