首页 / 资讯中心 / 文章详情

FPGA级联型IIR滤波器Verilog实现:从MATLAB定点化到Vivado上板验证

FPGA级联型IIR滤波器Verilog实现:从MATLAB定点化到Vivado上板验证 ★ FEATURED ARTICLE
简介这份资源面向FPGA数字信号处理的学习者与工程实践者聚焦级联型IIR滤波器的Verilog硬件实现帮助读者理解如何用Vivado将递归滤波算法落地为可综合的硬件电路。内容围绕多个二阶节biquad级联结构展开涉及乘法器、加法器、系数寄存器、状态变量以及环路稳定性等关键设计点并配有测试平台用于仿真验证频率响应与输出正确性。资源包共82个文件约223KB以Verilog源码、Vivado工程文件、仿真脚本、日志与报告为主另含MATLAB脚本、测试数据与说明文档便于对照代码与仿真结果逐步复现设计流程。目前已有2550人学习下载适合具备一定Verilog与数字信号处理基础、希望掌握FPGA滤波器实现方法的读者参考可从中获取完整的工程结构、模块划分思路与验证方法。1. 从一次“玄学”调试说起为什么级联型IIR滤波器值得单独做一版Verilog带过FPGA数字信号处理项目的人多半有过这种经历直接型IIR滤波器在MATLAB里仿真完美定点化之后上板输出波形却开始周期性抖动换一组系数又正常再换一组又翻车。问题往往不在算法本身而在高阶直接型结构的系数敏感度和量化误差累积。级联型IIR滤波器把高阶传递函数拆成多个二阶节串联每一节单独做定点缩放系数动态范围可控这种结构在FPGA上做Vivado综合时资源占用和时序收敛都更友好。这套方案面向正在做FPGA数字信号处理课程设计、毕设或需要交付滤波器IP的开发者用Verilog把级联型IIR从系数生成、定点量化、二阶节实现到Vivado上板验证整条链路走通交付一份可以直接改参数复用的工程。2. 需求分析与技术选型为什么是级联型而不是直接型2.1 从滤波器指标反推结构选型假设一个典型需求采样率48 kHz通带截止4 kHz阻带衰减不低于40 dB过渡带1 kHz。用MATLAB的ellipord或butter算一下椭圆滤波器大概6到8阶就能满足。8阶直接型IIR意味着差分方程里有8个前馈系数和8个反馈系数定点化之后分母系数的微小扰动会直接改变极点位置极点一旦跑到单位圆外输出就发散。级联型把8阶拆成4个二阶节每个二阶节的极点只由自己那两个系数决定量化误差被局部化不会跨节放大。选型时我一般按这个顺序判断阶数低于4阶直接型够用4到10阶且对稳定性敏感优先级联型超过10阶再考虑并联型。这套方案锁定级联型是因为它在FPGA上还有一个隐性优势——二阶节结构规整可以做成参数化模块例化多次代码复用率高Vivado综合时也容易推断出DSP48资源。2.2 定点位宽与系数缩放策略浮点系数直接截断成定点会引入两类误差系数量化误差和运算舍入误差。工程上通常用Q格式定点数比如Q15表示范围[-1, 1)分辨率约3e-5。但二阶节的系数不一定都在[-1,1)内尤其是增益系数。我的做法是每个二阶节单独做增益缩放把该节的最大系数归一化到Q15范围内节与节之间用移位补偿。具体操作MATLAB算出级联型的二阶节系数sos矩阵后对每一节计算max(abs(b))和max(abs(a))取较大者作为缩放因子把系数除以该因子同时记录缩放指数。Verilog里每节输出右移对应位数。这样做的代价是引入额外的移位逻辑但换来的是系数不溢出、精度可控。注意缩放因子不要用2的幂以外的数否则移位补偿会变成乘法白白消耗DSP资源。2.3 Verilog实现与Vivado工具链的配合代码风格上二阶节用always (posedge clk)写时序逻辑系数用parameter或localparam定义方便综合前改参数。Vivado综合时把乘法器推断成DSP48E1需要保证乘法操作数位宽不超过25×18。如果系数位宽是16 bit、数据位宽是16 bit乘积32 bit累加器要留够位宽防止溢出。我一般把累加器设成40 bit留足余量。仿真阶段用Vivado自带的仿真器跑行为级测试激励用MATLAB生成正弦扫频信号导出成.txtVerilog testbench用$readmemh读入。上板验证时用ILA抓二阶节中间节点对比MATLAB定点仿真结果逐节排查。3. 总体架构设计四个二阶节加一个控制模块的职责划分3.1 顶层模块的端口与数据流顶层模块iir_cascade_top的端口包括clk、rst_n、data_in16 bit有符号、data_valid、data_out16 bit有符号、out_valid。数据流是输入数据依次经过4个biquad二阶节每个节输出打一拍有效信号4级流水后out_valid拉高。module iir_cascade_top #( parameter DATA_WIDTH 16, parameter COEFF_WIDTH 16, parameter ACC_WIDTH 40 )( input wire clk, input wire rst_n, input wire signed [DATA_WIDTH-1:0] data_in, input wire data_valid, output wire signed [DATA_WIDTH-1:0] data_out, output wire out_valid ); // 4级二阶节级联每级输出位宽保持DATA_WIDTH wire signed [DATA_WIDTH-1:0] stage_out [0:3]; wire stage_valid [0:3]; // 第一节 biquad #(.DATA_WIDTH(DATA_WIDTH), .COEFF_WIDTH(COEFF_WIDTH), .ACC_WIDTH(ACC_WIDTH)) u_biquad0 ( .clk(clk), .rst_n(rst_n), .data_in(data_in), .data_valid(data_valid), .data_out(stage_out[0]), .out_valid(stage_valid[0]) ); // 后续节依次连接此处省略例化细节 endmodule这段代码的关键点是级联顺序不能乱MATLAB生成的sos矩阵顺序就是Verilog例化顺序。每个二阶节的系数通过parameter传入改滤波器只需重新生成系数不动结构代码。3.2 二阶节模块的内部结构单个biquad模块实现传递函数H(z) (b0 b1z^-1 b2z^-2) / (1 a1z^-1 a2z^-2)直接II型转置结构最省寄存器也最容易做流水线。内部维护x1, x2, y1, y2四个延迟寄存器每个时钟周期算一次输出。module biquad #( parameter DATA_WIDTH 16, parameter COEFF_WIDTH 16, parameter ACC_WIDTH 40, parameter signed [COEFF_WIDTH-1:0] B0 16sd32767, parameter signed [COEFF_WIDTH-1:0] B1 16sd0, parameter signed [COEFF_WIDTH-1:0] B2 16sd0, parameter signed [COEFF_WIDTH-1:0] A1 16sd0, parameter signed [COEFF_WIDTH-1:0] A2 16sd0 )( input wire clk, input wire rst_n, input wire signed [DATA_WIDTH-1:0] data_in, input wire data_valid, output reg signed [DATA_WIDTH-1:0] data_out, output reg out_valid ); reg signed [DATA_WIDTH-1:0] x1, x2, y1, y2; reg signed [ACC_WIDTH-1:0] acc; always (posedge clk or negedge rst_n) begin if (!rst_n) begin x1 0; x2 0; y1 0; y2 0; data_out 0; out_valid 0; end else if (data_valid) begin // 前馈部分 反馈部分注意反馈用负号 acc B0 * data_in B1 * x1 B2 * x2 - A1 * y1 - A2 * y2; // 截位回DATA_WIDTH实际工程中需做饱和处理 data_out acc[ACC_WIDTH-1 - (ACC_WIDTH-DATA_WIDTH) : 0]; x2 x1; x1 data_in; y2 y1; y1 data_out; out_valid 1b1; end else begin out_valid 1b0; end end endmodule参数说明B0/B1/B2/A1/A2是Q15格式系数ACC_WIDTH要保证B0*data_in不溢出16 bit乘16 bit得32 bit加上4个乘积项累加40 bit足够。截位时直接取高位会引入直流偏置更稳妥的做法是加舍入再截位或者做饱和判断。3.3 系数生成与定点化脚本MATLAB脚本负责从滤波器指标生成sos矩阵再做定点缩放最后输出Verilog可用的参数文件。% 设计8阶椭圆低通采样48kHz截止4kHz [order, Wn] ellipord(4000/(48000/2), 5000/(48000/2), 0.5, 40); [z, p, k] ellip(order, 0.5, 40, Wn); [sos, g] zp2sos(z, p, k); % 逐节定点缩放 scale zeros(size(sos,1),1); sos_fixed sos; for i 1:size(sos,1) max_coeff max(abs([sos(i,1:3), sos(i,5:6)])); scale(i) 2^floor(log2(1/max_coeff)); sos_fixed(i,1:3) sos(i,1:3) * scale(i); sos_fixed(i,5:6) sos(i,5:6) * scale(i); end % 转Q15 sos_q15 round(sos_fixed * 32768); % 输出到文件格式按Verilog参数顺序 fid fopen(coeff.txt, w); for i 1:size(sos_q15,1) fprintf(fid, %d %d %d %d %d\n, sos_q15(i,1), sos_q15(i,2), ... sos_q15(i,3), sos_q15(i,5), sos_q15(i,6)); end fclose(fid);这段脚本的核心是scale的计算用2^floor(log2(1/max_coeff))保证缩放后最大系数不超过1。实际工程中还要检查缩放后系数是否超出Q15范围超出就再降一档。4. 关键模块实现二阶节流水线与系数加载4.1 流水线设计对时序收敛的影响二阶节内部如果在一个时钟周期内完成5次乘加组合逻辑路径会很长Vivado综合后时序可能跑不到100 MHz。我的做法是在乘加链中间插一级流水寄存器把5次乘加拆成两级第一级算前馈三项第二级算反馈两项并求和。代价是输出延迟增加一个时钟周期但时序余量明显改善。// 第一级流水前馈乘加 always (posedge clk or negedge rst_n) begin if (!rst_n) begin ff_sum 0; end else if (data_valid) begin ff_sum B0 * data_in B1 * x1 B2 * x2; end end // 第二级流水反馈乘加并求和 always (posedge clk or negedge rst_n) begin if (!rst_n) begin acc 0; end else if (data_valid) begin acc ff_sum - A1 * y1 - A2 * y2; end end这样改完之后Vivado时序报告里WNS从负值变成正值代价是out_valid要多打一拍级联时注意对齐。4.2 系数在线加载的取舍有些场景需要运行时切换滤波器参数比如自适应滤波。如果系数用parameter写死综合后无法改。要支持在线加载得把系数存到寄存器里通过AXI-Lite或简单寄存器接口写入。但这样会消耗额外寄存器资源而且系数路径变成寄存器输出时序可能变差。我的建议课程设计和毕设阶段系数固定用parameter简单可靠如果确实需要动态切换用case语句做多组系数切换综合工具会推断成多路选择器比寄存器堆省资源。4.3 测试激励的生成与对比方法Testbench里用$readmemh读MATLAB生成的扫频信号每个时钟周期送一个样点。输出数据写到文件再用MATLAB读回来和定点仿真结果对比。initial begin $readmemh(stimulus.txt, stim_mem); // 复位、逐点送入 for (i 0; i STIM_LEN; i i 1) begin (posedge clk); data_in stim_mem[i]; data_valid 1b1; end data_valid 1b0; $writememh(output.txt, out_mem); end对比时看两个指标最大绝对误差和信噪比。如果误差超过1个LSB检查截位方式如果信噪比低于理论值检查系数缩放是否正确。5. 避坑与常见问题排查从仿真到上板的五个翻车点5.1 输出周期性抖动现象上板后输出波形有规律的小幅抖动周期和某个二阶节的极点频率一致。原因通常是截位时直接取高位低位被丢弃导致极限环振荡。解决在截位前加舍入或者把累加器位宽再放宽输出时做饱和处理而不是简单截断。5.2 综合后时序不收敛现象Vivado综合报告WNS为负频率跑不到目标值。原因二阶节组合逻辑太长5次乘加串在一起。解决按4.1节插流水线或者把乘法器输出打一拍再累加。如果DSP48不够用检查是否被推断成了逻辑乘法而不是DSP。5.3 系数溢出导致输出饱和现象输入信号幅度稍大输出就削顶。原因某个二阶节的增益系数缩放后仍然大于1或者级联后总增益超过预期。解决重新检查MATLAB脚本里的scale计算确保每节最大系数不超过Q15范围级联时在节间加移位补偿。5.4 仿真结果和MATLAB对不上现象Verilog仿真输出和MATLAB定点仿真逐点对比误差越来越大。原因MATLAB用的是浮点Verilog用的是定点两者本身就有差异。解决MATLAB侧也做定点仿真用fi对象或者手动量化确保对比基准一致。另外检查Verilog的截位方式和MATLAB是否一致。5.5 上板后ILA抓不到有效数据现象ILA触发后看不到out_valid拉高。原因data_valid的时序不对或者级联时有效信号没有逐级传递。解决检查每个二阶节的out_valid是否在data_valid后一拍拉高级联时把上一级的out_valid接到下一级的data_valid。6. 验收与答辩怎么证明这套滤波器真的能用验收阶段我一般分三步走。第一步MATLAB浮点仿真和定点仿真对比确认定点化引入的误差在可接受范围内通常信噪比不低于60 dB。第二步Verilog行为级仿真和MATLAB定点仿真逐点对比最大绝对误差不超过2个LSB。第三步上板实测用信号发生器送正弦扫频ILA抓输出看幅频响应是否和理论一致。答辩时评委常问的几个问题为什么选级联型不选直接型系数怎么定点化的资源占用多少时序余量多少这些问题在方案里都有答案。我习惯准备一张对比表把直接型和级联型的资源、时序、稳定性列在一起一目了然。对比项直接型级联型DSP48用量8个乘法器每节5个共20个时序余量紧张宽松系数敏感度高低代码复用性低高资源占用方面8阶级联型在主流FPGA上大约用20个DSP48、500个LUT、600个寄存器具体数字看器件和综合选项。时序余量在100 MHz时钟下WNS不低于0.5 ns。从那以后我每次做IIR滤波器都强制先跑一遍MATLAB定点仿真再写Verilog最后上板对比。这个顺序看起来慢实际上省掉了大量来回调试的时间。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站