零基础看懂 FPGA 实现 IIR 滤波器:大白话 + 手算实例 + 代码全拆解
做数字信号处理的同学,刚接触 FPGA 实现 IIR 滤波器时,总会被一堆专业名词砸晕 —— 差分方程、零点极点、定点化、符号扩展、算术右移…… 看着代码好像每个关键字都认识,凑一起就不知道在干啥。
这篇文章彻底抛开晦涩术语,用生活类比 + 逐行代码拆解 + 手算实例,带你从零搞懂 IIR 滤波器的 FPGA 实现逻辑,看完就能对应到自己的工程代码里。
一、先搞懂:IIR 滤波器到底是啥?
1. 滤波器:一个 “信号筛子”
你可以把滤波器想象成一个信号筛子:
- 输入是一段混杂了杂音的信号(比如人声 + 高频电流杂音)
- 输出是筛掉杂音后的干净信号
- 我们常说的「低通滤波器」,本质就是留下低频的有效信号,筛掉高频的噪声干扰。
2. FIR 与 IIR 的核心区别:有没有 “回头路”
两种最常见的数字滤波器,核心差异就在有没有反馈机制:
- FIR 滤波器:只有一条 “向前走” 的通路,信号从左边进,一层一层过筛子,直接从右边输出,没有回头路。优点是绝对稳定,不会 “炸机”;缺点是要想滤波效果好,得堆很多层 “筛子”,非常占用 FPGA 资源。
- IIR 滤波器:多了一条 “回头路”—— 把输出的一部分,再送回输入端重新参与运算(这个机制就叫反馈)。优点是只用很少几层 “筛子”,就能实现很强的滤波效果,省资源;缺点是反馈比例没调好的话,信号会越放越大,最终发散失控。
一句话总结:IIR = 带循环反馈的信号筛子,用更少资源实现更强滤波。
3. 专业名词大白话对照表
把所有劝退人的术语全部翻译成日常表达,对照着看再也不懵:
表格
| 专业名词 | 大白话解释 |
|---|---|
| 差分方程 | IIR 的 “计算公式”:当前输出 = 一部分当前 + 历史输入 - 一部分历史输出 |
| 零点系数 b | 前向通路(输入侧)每层筛子的 “松紧比例”,决定输入信号的权重占比 |
| 极点系数 a | 反馈通路(输出回传侧)每层筛子的 “松紧比例”,决定反馈信号的权重占比 |
| 延迟单元 z⁻¹ | 就是一个 “寄存格子”,把上一个时刻的数值存起来,下一拍再拿出来用,在 FPGA 里就是寄存器 |
| 直接 I 型 | 先走完所有前向筛子,再走反馈回路,两条通路完全分开,结构最直白易懂 |
| 定点化 | FPGA 算小数很麻烦,于是把所有小数系数先放大 N 倍变成整数,算完再缩小 N 倍还原,这个过程就叫定点化 |
| 缩放因子 | 定点化里放大 / 缩小的那个倍数,常见的是 2 的整数次幂,比如典型代码里常用 512(2 的 9 次方) |
| 符号扩展 | 负数在 FPGA 里用补码表示,两个位数不一样的负数相加减,要把短位数的符号位复制补齐,否则会算错正负 |
| 右移实现除法 | 除以 2 的 n 次方,直接把二进制数往右挪 n 位就能实现,比专门做除法器省超多资源,是 FPGA 设计的必用技巧 |
二、代码逐行拆解:直接 I 型 IIR 的硬件逻辑
我们以一段典型的直接 I 型 IIR 顶层代码为例,拆成模块逐个讲,完全对应上面的 “筛子” 类比。
1. 两个子模块:两套独立的筛子
代码里例化了zero和pole两个模块,分别对应前向通路和反馈通路:
verilog
zero U0(.Xin(Din), .Xout(Xout)); // 前向筛子:处理输入信号 pole U1(.Yin(Yin), .Yout(Yout)); // 反馈筛子:处理反馈回来的历史输出(1)zero 模块(前向通路 / 零点)
- 核心功能:把「当前输入、上一拍输入、上上拍输入……」分别乘以对应的 b 系数,然后全部累加,得到前向通路的加权总和
Xout。 - 以二阶为例,运算逻辑就是:
Xout = b0*当前输入 + b1*上一拍输入 + b2*上上拍输入 - 为什么输出位宽比输入宽?因为 12 位的输入乘以系数、再累加,数值会变大,位数不够就会溢出出错,所以中间运算必须用更宽的位宽预留余量。
(2)pole 模块(反馈通路 / 极点)
- 核心功能:把「上一拍输出、上上拍输出……」分别乘以对应的 a 系数,然后全部累加,得到反馈通路的加权总和
Yout。 - 以二阶为例,运算逻辑就是:
Yout = a1*上一拍输出 + a2*上上拍输出 - 为什么位宽比 zero 模块更宽?因为反馈是循环累加的,数值会持续堆叠,动态范围更大,需要更多的位数防止溢出。
2. 核心反馈环路:混合 + 缩放还原
这三行是 IIR 滤波器的灵魂,对应反馈、定点化还原的完整逻辑:
verilog
wire signed [25:0] Ysum = {{5{Xout[20]}},Xout} - Yout; // 减法:前向总和 - 反馈总和 wire signed [25:0] Ydiv = {{9{Ysum[25]}},Ysum[25:9]}; // 除法:除以512,还原定点缩放 assign Yin = rst ? 'd0 : Ydiv[11:0]; // 结果回传+输出第一步:符号扩展 + 减法求和
verilog
{{5{Xout[20]}},Xout}这行就是符号扩展操作:
Xout是 21 位,Yout是 26 位,位宽不同的有符号数不能直接相加减;- 把
Xout的最高位(符号位,0 代表正数、1 代表负数)复制 5 次,拼接在高位,把 21 位补成 26 位; - 补位后正负属性不变,负数运算才不会出错。
完成位宽对齐后,执行减法:前向加权和 - 反馈加权和,对应 IIR 差分方程的核心逻辑:
当前运算结果 = 输入加权和 - 历史输出加权和
第二步:算术右移 = 定点化还原
verilog
{{9{Ysum[25]}},Ysum[25:9]}这行是算术右移 9 位,等价于有符号数除以 2⁹ = 512。
为什么要除以 512?这就是定点化的 “还原操作”:
- 前面 zero 和 pole 模块里的 b、a 系数,原本是小数(比如 0.1、0.2);
- FPGA 做整数运算效率最高,所以设计时把所有系数都乘以 512,转成整数参与运算;
- 全部运算完成后,再把结果除以 512,还原成真实的物理数值。
这里同样做了符号扩展,保证负数右移时高位补 1,不会出现正负错乱。
第三步:截位输出 + 反馈回传
verilog
assign Yin = rst ? 'd0 : Ydiv[11:0]; assign Dout = Yin;- 复位时强制输出 0,让所有寄存器初始状态为 0,避免上电状态混乱;
- 正常工作时,从 26 位的运算结果里截取低 12 位,作为最终结果;
- 这个结果兵分两路:一路送回
pole模块输入端,参与下一拍的反馈运算(这就是 IIR 的 “循环” 本质);另一路直接作为整个滤波器的输出。
三、手算一遍:一阶 IIR 实例,彻底搞懂运算逻辑
光看代码抽象,我们用一个最简单的一阶 IIR 举例子,数字全部凑成整数,跟着算一遍就全通了。
例子设定
- 滤波公式:
y[n] = 0.5*x[n] + 0.5*y[n-1](一阶低通,实现信号平滑效果) - 定点化规则:缩放因子选 2(2 的 1 次方),所有系数乘以 2 转成整数:
- 零点系数 b0 = 0.5 × 2 = 1
- 极点系数 a1 = -0.5 × 2 = -1
- 输入序列:x = [4, 0, 0, 0, 0] (第一个时刻输入 4,之后全为 0,也就是冲激信号)
- 初始状态:第 0 拍之前的输出 y [-1] = 0
逐拍手算过程(定点整数版)
第 0 拍(n=0),输入 x [0]=4
- 前向加权和:1 × 4 = 4
- 反馈加权和:(-1) × y [-1] = (-1) × 0 = 0
- 运算总和:4 - 0 = 4
- 除以 2 还原缩放:4 ÷ 2 = 2
- 本拍输出 y [0] = 2
第 1 拍(n=1),输入 x [1]=0
- 前向加权和:1 × 0 = 0
- 反馈加权和:(-1) × y [0] = (-1) × 2 = -2
- 运算总和:0 - (-2) = 2
- 除以 2 还原缩放:2 ÷ 2 = 1
- 本拍输出 y [1] = 1
第 2 拍(n=2),输入 x [2]=0
- 前向加权和:1 × 0 = 0
- 反馈加权和:(-1) × y [1] = (-1) × 1 = -1
- 运算总和:0 - (-1) = 1
- 除以 2 还原缩放:0.5 → 整数截位后为 0
- 本拍输出 y [2] = 0
你看,只输入了一个 4,输出是 2、1、0…… 慢慢衰减下去,这就是 IIR “无限冲激响应” 的含义 —— 输入消失了,输出还在慢慢变化,理论上永远不会绝对归零。
工程里的高阶 IIR 代码,逻辑和这个手算例子完全一致,只是阶数更高、位宽更大、缩放因子更大而已。
四、FPGA 设计 IIR 的完整步骤(通俗版)
搞懂原理后,完整的工程设计流程也很好理解:
- 定需求:明确滤波目标、采样率、输入输出位宽、通带阻带衰减等指标
- 算系数:用 MATLAB 的 Filter Designer 等工具,输入需求算出浮点格式的 b、a 系数
- 定点化:选择合适的缩放因子,把小数系数量化为整数,同时规划好每一级运算的位宽
- 搭结构:划分前向、反馈通路,用寄存器做延迟、乘法器乘系数、加法器做累加
- 防溢出:中间运算位宽留足余量,输出端可增加饱和逻辑防止溢出反转
- 写代码 + 仿真:编写 Verilog 代码,输入测试信号验证滤波效果与精度
五、初学者常见疑问解答
1. 为什么用减法不用加法?
标准 IIR 传递函数的分母形式是1 + a1 z⁻¹ + ...,移项整理成差分方程后,反馈项就是减法形式,因此硬件里用减法实现。
2. 为什么一定要定点化?不能直接算小数吗?
FPGA 也可以实现浮点运算,但浮点运算器资源占用极大、运行速度慢。滤波器这类需要高速、重复运算的模块,全行业都采用定点整数方案,性价比最高。
3. 定点截位会不会算错?精度够吗?
截位确实会引入微小的量化误差,但只要缩放因子选取得当、中间位宽预留充足,误差完全在可接受范围内,音频处理、工业控制、传感器信号采集等绝大多数场景都能满足要求。
写在最后
IIR 滤波器看似复杂,本质就是 “前向加权 + 反馈加权 + 缩放还原” 的循环逻辑。只要理解了反馈的意义、定点化的原因、每一行代码对应的运算,就能从 “能跑就行” 进阶到 “知道为什么这么写”。
如果是刚入门 FPGA 数字信号处理,建议先从一阶、二阶 IIR 入手,手写代码 + 仿真对比 MATLAB 结果,上手会非常快。