FPGA级联型IIR滤波器Verilog实现:从结构选型到上板验收

📅 发布时间:2026/10/20 19:14:24
FPGA级联型IIR滤波器Verilog实现:从结构选型到上板验收
简介这份资源面向FPGA数字信号处理的学习者与工程实践者聚焦级联型IIR滤波器的Verilog硬件实现帮助读者理解如何用Vivado将递归滤波算法落地为可综合的硬件电路。内容围绕多个二阶节biquad级联结构展开涉及系数寄存器、乘法器与加法器的并行运算、testbench仿真验证以及环路稳定性等关键问题适合具备Verilog基础、希望深入DSP硬件设计的中高级读者。资源包共82个文件约223KB以v源码、log与sdb工程日志、wdf波形、txt与xml配置、tcl脚本及xpr工程文件为主另含m脚本、rpt报告与dcp综合结果覆盖从源码、仿真到综合实现的完整工程目录。目前已有2550人学习下载。读者可据此获得可综合的级联IIR滤波器参考设计、仿真激励与验证思路并借助工程文件快速复现综合流程、排查时序与稳定性问题。1. 从一次“玄学”波形说起为什么级联型 IIR 值得单独做一版做 FPGA 数字信号处理时很多人第一次写 IIR 滤波器都会踩同一个坑直接型结构仿真波形挺漂亮一上板就出现高频自激输出要么饱和要么像被“污染”过。更玄学的是换一块板子、换一个时钟频率现象还不一样。问题往往不在算法本身而在结构——高阶 IIR 的极点对系数量化极其敏感直接型把分子分母系数一股脑塞进一条差分方程有限字长效应一放大滤波器就“翻车”了。级联型 IIR 的思路是把一个高阶传递函数拆成若干个一阶、二阶子系统的乘积每个二阶节单独实现、单独量化、单独调增益。这样极点位置可控量化误差不会跨节累积定点实现时稳定性好很多。这套方案面向的是正在做 FPGA 数字信号处理课程设计、毕设或者需要把滤波器真正跑到板子上的同学和工程师。关键词 FPGA、Vivado、IIR、Verilog 会贯穿全文但重点不是罗列工具而是把“为什么这么设计、怎么落地、怎么验收”讲透。2. 需求与结构选型为什么是级联型而不是直接型2.1 从传递函数到二阶节乘积一个 N 阶 IIR 滤波器的系统函数可以写成有理分式。直接型实现时差分方程里同时出现前馈系数 b 和反馈系数 a高阶情况下分母多项式的根极点对系数舍入非常敏感。级联型把它因式分解成多个二阶节SOSSecond-Order Sections的乘积H(z) G · ∏ (b0k b1k·z⁻¹ b2k·z⁻²) / (1 a1k·z⁻¹ a2k·z⁻²)每个二阶节只负责一对共轭极点或两个实极点系数动态范围小量化后极点偏移有限。通常的做法是先用 MATLAB 或 Python 的滤波器设计工具算出 SOS 矩阵和总增益 G再把每个二阶节映射成独立的 Verilog 模块。这样做的代价是乘法器和加法器数量比直接型多但换来了定点实现的稳定性和可调试性——对课程设计和毕设来说这个交换是划算的。2.2 定点位宽的分配策略级联型虽然稳但位宽分配不合理照样出问题。我一般按这个顺序定输入数据假设 1216 位有符号数取决于 ADC 或前级模块。系数每个二阶节的 b、a 系数单独量化通常 16 位有符号Q15 格式。系数位宽太小会导致频响偏移太大浪费 DSP 资源。中间节点每个二阶节输出后要留保护位。因为级联时前一级的输出可能接近满量程后一级再放大就会溢出。通常的做法是每个二阶节内部用全精度累加输出时截位或舍入回 16 位并在节与节之间插入可选的增益缩放。累加器乘法结果相加时至少扩展 log2(节数) 位防止累加溢出。下面是一个二阶节的系数定义示例用 parameter 写死方便综合时优化// 单个二阶节系数Q15 定点格式 // 传递函数 Hk(z) (b0 b1*z^-1 b2*z^-2) / (1 a1*z^-1 a2*z^-2) parameter signed [15:0] B0 16sd8192; // 0.25 in Q15 parameter signed [15:0] B1 16sd16384; // 0.5 parameter signed [15:0] B2 16sd8192; // 0.25 parameter signed [15:0] A1 -16sd24576; // -0.75 parameter signed [15:0] A2 16sd8192; // 0.25这里 B0、B1、B2 是前馈系数A1、A2 是反馈系数全部用 Q15 表示。注意 A1 取了负值因为差分方程里反馈项移项后符号会变。实际项目中这些值由 MATLAB 的tf2sos或sosfilt生成不要手算。2.3 级联顺序对输出的影响二阶节的排列顺序不是随便定的。极点离单位圆越近的节输出动态范围越大应该放在靠前的位置并配合缩放否则后级容易饱和。通常的做法是按极点半径从大到小排列或者用 MATLAB 的sos排序选项自动处理。如果顺序排错仿真时可能看不出问题上板后小信号正常、大信号削顶这就是典型的级联顺序踩坑。3. 总体架构设计四个模块与一条数据流水线3.1 模块划分与职责整套设计我一般拆成四个模块职责清晰方便单独仿真模块职责关键接口顶层滤波器例化多个二阶节串联数据流clk, rst_n, din, dout二阶节实现单个 SOS 差分方程clk, rst_n, x_in, y_out系数存储存放各节系数可参数化addr, coef_out时序控制产生 valid 握手信号clk, rst_n, valid_in, valid_out顶层只做连线和增益缩放不写运算逻辑。二阶节是核心每个节内部包含乘法、累加和寄存器。系数存储可以用 parameter 直接写死也可以做成 ROM 方便换滤波器类型。时序控制负责 valid 信号逐级传递保证流水线对齐。3.2 二阶节的 Verilog 实现下面是一个可综合的二阶节核心代码采用直接Ⅱ型转置结构节省寄存器module biquad #( parameter signed [15:0] B0 16sd8192, parameter signed [15:0] B1 16sd16384, parameter signed [15:0] B2 16sd8192, parameter signed [15:0] A1 -16sd24576, parameter signed [15:0] A2 16sd8192 )( input wire clk, input wire rst_n, input wire signed [15:0] x_in, input wire valid_in, output reg signed [15:0] y_out, output reg valid_out ); // 内部状态寄存器扩展位宽防溢出 reg signed [31:0] acc; reg signed [15:0] x1, x2, y1, y2; always (posedge clk or negedge rst_n) begin if (!rst_n) begin x1 16sd0; x2 16sd0; y1 16sd0; y2 16sd0; y_out 16sd0; valid_out 1b0; end else if (valid_in) begin // 全精度累加前馈 反馈 acc B0 * x_in B1 * x1 B2 * x2 - A1 * y1 - A2 * y2; // 舍入后截位回 16 位 y_out acc[30:15] acc[14]; // 更新状态 x2 x1; x1 x_in; y2 y1; y1 y_out; valid_out 1b1; end else begin valid_out 1b0; end end endmodule这段代码的关键点acc用 32 位全精度累加避免中间结果被截断输出时取acc[30:15]并加上acc[14]做舍入相当于右移 15 位并四舍五入反馈项- A1 * y1 - A2 * y2对应差分方程移项后的符号。valid_in和valid_out构成握手保证多级级联时数据对齐。3.3 顶层级联与增益缩放顶层把多个 biquad 串起来并在节间插入缩放。缩放系数通常取 2 的幂用移位实现不消耗乘法器module iir_cascade #( parameter NUM_SECTIONS 3 )( input wire clk, input wire rst_n, input wire signed [15:0] din, input wire valid_in, output wire signed [15:0] dout, output wire valid_out ); // 三个二阶节串联节间用移位缩放 wire signed [15:0] y0, y1, y2; wire v0, v1, v2; biquad u_sos0 ( .clk(clk), .rst_n(rst_n), .x_in(din), .valid_in(valid_in), .y_out(y0), .valid_out(v0) ); // 节间缩放右移 1 位相当于乘 0.5防后级饱和 wire signed [15:0] y0_scaled {y0[15], y0[15:1]}; biquad u_sos1 ( .clk(clk), .rst_n(rst_n), .x_in(y0_scaled), .valid_in(v0), .y_out(y1), .valid_out(v1) ); biquad u_sos2 ( .clk(clk), .rst_n(rst_n), .x_in(y1), .valid_in(v1), .y_out(y2), .valid_out(v2) ); assign dout y2; assign valid_out v2; endmodule这里y0_scaled用算术右移实现乘 0.5保留了符号位。缩放系数要根据各节的实际增益算不能拍脑袋。通常的做法是在 MATLAB 里对每个二阶节单独算幅频响应找出最大增益然后分配缩放使每级输出不超过满量程的 70%80%。4. 实现细节与仿真验证从 MATLAB 系数到 Vivado 波形4.1 系数生成与定点化脚本系数不能手算用脚本生成最稳。下面是一段 Python 示例用 scipy 设计低通滤波器并导出 Q15 系数import numpy as np from scipy import signal # 设计 8 阶低通截止频率 0.2*Nyquist sos signal.butter(8, 0.2, btypelow, outputsos) # 计算总增益 G np.prod(sos[:, 0]) sos[:, 0] / sos[:, 0] # 归一化 b0 # 定点化到 Q15 def to_q15(x): return np.clip(np.round(x * 32768), -32768, 32767).astype(int) for i, s in enumerate(sos): b0, b1, b2, a0, a1, a2 s print(f// Section {i}) print(fB0 16sd{to_q15(b0)};) print(fB1 16sd{to_q15(b1)};) print(fB2 16sd{to_q15(b2)};) print(fA1 16sd{to_q15(a1)};) print(fA2 16sd{to_q15(a2)};)这段脚本输出每个二阶节的 Q15 系数直接复制到 Verilog 的 parameter 里。注意a0通常归一化为 1所以反馈系数只取 a1、a2。总增益 G 可以分配到某一节或单独用移位实现。4.2 Testbench 与仿真对比Testbench 要做的核心一件事把 MATLAB 的浮点输出和 Verilog 的定点输出放在一起比。通常的做法是生成一段扫频信号或白噪声分别喂给 MATLAB 模型和 Verilog 仿真然后把两组输出写到文件里用 Python 画图对比。initial begin $readmemh(input_stimulus.hex, stim_mem); for (i 0; i STIM_LEN; i i 1) begin din stim_mem[i]; valid_in 1b1; #10; end valid_in 1b0; $writememh(output_fixed.hex, out_mem); $finish; end仿真时重点看三个指标通带纹波是否和浮点一致、阻带衰减是否达标、阶跃响应有没有持续振荡。如果定点输出和浮点输出在低频段吻合、高频段有偏差通常是系数位宽不够如果出现持续振荡检查反馈系数符号和累加器位宽。4.3 Vivado 综合与资源评估综合后看三个报告时序报告确认建立时间满足、资源报告看 DSP48 和寄存器用量、功耗报告估算动态功耗。级联型 IIR 的 DSP 用量大致是每个二阶节 5 个乘法器b0、b1、b2、a1、a2如果系数是 2 的幂可以用移位替代能省 DSP。我一般会在综合前把能移位的系数改成移位实现DSP 紧张时这招很管用。5. 避坑与常见问题排查那些仿真过但上板挂的瞬间5.1 输出持续振荡或自激现象上板后输出一直是高频振荡复位也不管用。原因反馈系数符号写反或者累加器位宽不够导致溢出后符号翻转。解决先检查差分方程移项后的符号A1、A2 在代码里应该是减号再把累加器位宽扩展到 32 位以上确认中间结果不溢出。5.2 小信号正常、大信号削顶现象输入幅度小的时候波形正常幅度一大输出就平顶。原因级联顺序不对高增益节放在后面导致后级饱和或者节间缩放系数没算对。解决按极点半径重排二阶节把高增益节放前面并加缩放用 MATLAB 算每节最大增益分配缩放使每级输出不超过满量程 80%。5.3 频响和 MATLAB 对不上现象仿真出来的幅频响应和 MATLAB 差很多通带纹波变大。原因系数量化位宽不够或者 Q15 格式转换时舍入方式不对。解决把系数位宽从 16 位加到 18 或 20 位试试检查定点化脚本里np.round和np.clip的顺序先舍入再截断。5.4 时序不收敛现象Vivado 时序报告显示建立时间为负综合能过但上板不稳定。原因级联节数多组合逻辑路径太长或者乘法器没打拍。解决在每个二阶节内部插入流水线寄存器把乘法和累加分到两个时钟周期或者用 DSP48 的寄存器资源做流水。5.5 valid 信号不对齐现象多级级联后输出数据错位波形整体偏移几个周期。原因每级 biquad 的延迟不一致valid 信号没有逐级传递。解决确保每个 biquad 的valid_out在valid_in有效后的下一个周期拉高顶层严格按级联顺序连接 valid 链不要跳级。6. 验收与答辩怎么证明这套滤波器真的能用验收不是看仿真波形好看就行要拿数据说话。我一般按下面四步走每一步都有可复现的判据。第一步频响对比。用 MATLAB 生成浮点滤波器的幅频响应曲线再用 Verilog 仿真输出做 FFT两条曲线叠在一张图上。通带内偏差小于 0.5 dB、阻带衰减偏差小于 2 dB就算定点化合格。这一步能直接回答“你的定点实现和理论差多少”。第二步阶跃响应。给一个阶跃信号看输出有没有过冲、振荡、稳态误差。级联型 IIR 的阶跃响应应该单调收敛或有限过冲如果持续振荡说明极点位置偏了。把 MATLAB 和 Verilog 的阶跃响应放一起收敛时间差不超过 2 个采样周期。第三步资源与时序。Vivado 综合报告里DSP48 用量、寄存器数量、最大时钟频率三项要写进答辩 PPT。通常的做法是给一个资源上限比如“DSP 不超过 20 个、时钟跑到 100 MHz”然后展示报告截图证明达标。第四步上板实测。如果有条件用信号发生器输入正弦扫频示波器看输出幅度。没有硬件就用 ILA 抓内部信号重点看每个二阶节的输出有没有饱和。答辩时把 ILA 波形截图和 MATLAB 对比图并排展示比讲一堆公式有说服力。验收项判据工具频响偏差通带 0.5 dB阻带 2 dBMATLAB FFT阶跃响应收敛时间差 2 周期仿真波形资源用量DSP ≤ 20时序收敛Vivado 报告上板输出无饱和、无自激ILA / 示波器答辩时最容易被问的是“为什么不用直接型”。这时候把极点敏感度分析拿出来给直接型系数加 1% 扰动看极点偏移再给级联型加同样扰动看极点偏移。两张图一对比级联型的优势一目了然。另一个高频问题是“位宽怎么定的”回答思路是从输入位宽出发按每级增益分配保护位累加器扩展 log2(节数) 位最后用仿真验证。从那以后我每次做 IIR 都强制走一遍“浮点对比 → 定点仿真 → 上板验证”三步不再跳过任何一步。级联型结构虽然多花几个乘法器但省下的调试时间远不止这点资源。希望帮到你。本文还有配套的精品资源点击获取