news 2026/10/20 19:14:24

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

作者头像

张小明

前端开发工程师

1.2k 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,高阶情况下分母多项式的根(极点)对系数舍入非常敏感。级联型把它因式分解成多个二阶节(SOS,Second-Order Sections)的乘积:

H(z) = G · ∏ (b0k + b1k·z⁻¹ + b2k·z⁻²) / (1 + a1k·z⁻¹ + a2k·z⁻²)

每个二阶节只负责一对共轭极点或两个实极点,系数动态范围小,量化后极点偏移有限。通常的做法是:先用 MATLAB 或 Python 的滤波器设计工具算出 SOS 矩阵和总增益 G,再把每个二阶节映射成独立的 Verilog 模块。这样做的代价是乘法器和加法器数量比直接型多,但换来了定点实现的稳定性和可调试性——对课程设计和毕设来说,这个交换是划算的。

2.2 定点位宽的分配策略

级联型虽然稳,但位宽分配不合理照样出问题。我一般按这个顺序定:

  • 输入数据:假设 12~16 位有符号数,取决于 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 = 16'sd8192; // 0.25 in Q15 parameter signed [15:0] B1 = 16'sd16384; // 0.5 parameter signed [15:0] B2 = 16'sd8192; // 0.25 parameter signed [15:0] A1 = -16'sd24576; // -0.75 parameter signed [15:0] A2 = 16'sd8192; // 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 = 16'sd8192, parameter signed [15:0] B1 = 16'sd16384, parameter signed [15:0] B2 = 16'sd8192, parameter signed [15:0] A1 = -16'sd24576, parameter signed [15:0] A2 = 16'sd8192 )( 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 <= 16'sd0; x2 <= 16'sd0; y1 <= 16'sd0; y2 <= 16'sd0; y_out <= 16'sd0; valid_out <= 1'b0; 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 <= 1'b1; end else begin valid_out <= 1'b0; 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, btype='low', output='sos') # 计算总增益 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(f"B0 = 16'sd{to_q15(b0)};") print(f"B1 = 16'sd{to_q15(b1)};") print(f"B2 = 16'sd{to_q15(b2)};") print(f"A1 = 16'sd{to_q15(a1)};") print(f"A2 = 16'sd{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 = 1'b1; #10; end valid_in = 1'b0; $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 都强制走一遍“浮点对比 → 定点仿真 → 上板验证”三步,不再跳过任何一步。级联型结构虽然多花几个乘法器,但省下的调试时间远不止这点资源。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 19:13:28

ROS Melodic + MoveIt + UR5 实时控制方案:从零搭建到真机优化全解析

去年在这个方向上折腾了大半年&#xff0c;把一套基于 ROS Melodic MoveIt 的 UR5 实时控制方案从零搭了起来。中间踩过的坑不少&#xff0c;从环境配置到驱动选型&#xff0c;再到 MoveIt 规划链路和真实机械臂底层的实时指令衔接&#xff0c;每一步都有值得记录的细节。这个…

作者头像 李华
网站建设 2026/10/20 19:12:03

AI辅助课程论文写作全流程:选题、大纲到文献的实战指南

1. 课程论文为什么总卡在起跑线上1.1 选题没有抓手&#xff0c;是绝大多数“卡文”的真正源头我先说个常见的场景。每到课程论文截止前三周&#xff0c;宿舍群里就会出现一个永恒的循环&#xff1a;一开始是“随便写写就完事”&#xff0c;中间变成“题目是不是选太大了”&…

作者头像 李华
网站建设 2026/10/20 19:11:41

2026主流接口测试平台并发能力评测与选型指南

接口测试平台这几年越来越像“测试团队的基础设施”&#xff0c;早就不再是发个请求、断言个返回值那么简单。尤其是并发量检测这块&#xff0c;很多团队从最开始的“能压就行”&#xff0c;逐渐变成“压得准、压得稳、看得懂”。标题里写的是2026年&#xff0c;但老实说&#…

作者头像 李华
网站建设 2026/10/20 19:11:27

智算平台与普通云服务器有何不同?移动云智算实战解析

前一阵有同行在讨论&#xff0c;说现在云服务市场最大的变化不是又多了几家厂商&#xff0c;而是智算平台这个物种开始变得绕不过去了。搜索“移动云智算平台是什么”的人&#xff0c;大概率不是想做名词解释&#xff0c;而是手里已经有一个模型要训练&#xff0c;或者公司正在…

作者头像 李华
网站建设 2026/10/20 19:11:27

LangGraph 1.0智能邮件处理实战:条件边、记忆与人在回路设计

这阵子我在做企业邮箱智能助理的项目X&#xff0c;核心是把积压的邮件自动分类、自动回复、高风险邮件人工审批。整体搬到了LangGraph 1.0上重写&#xff0c;过程中把条件边&#xff08;conditional edges&#xff09;、记忆&#xff08;memory&#xff09;、人在回路&#xff…

作者头像 李华
网站建设 2026/10/20 19:11:11

考虑光伏逆变器无功响应的分布式电源优化配置及Matlab实现

分布式电源优化配置的文章和代码&#xff0c;网上已经有一大堆了&#xff0c;绝大多数都在围绕两件事做文章&#xff1a;给分布式电源挑个合适的位置&#xff0c;算一个合适的容量&#xff0c;目标是网损最小、电压最稳&#xff0c;或者年综合费用最优。这套思路本身没问题&…

作者头像 李华