ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

数字滤波器结构怎么选?IIR/FIR实现对比与定点工程实战

数字滤波器结构怎么选?IIR/FIR实现对比与定点工程实战

写这种主题的稿子,最容易犯的错就是对着教材抄一遍公式,最后读者看完还是不知道该选哪种结构。我尽量换个讲法:把《数字信号处理》第5章里的这些结构当成“同一个函数的不同物理实现”,讲清楚它们各自的脾气、成本、坑点,以及你在真实项目里到底该怎么挑。文章面向正在学DSP基础的学生,也更适合已经写过滤波器、对性能和资源敏感、需要把算法落到DSP或FPGA上的工程师。看完至少能回答三个问题:为什么同一个滤波器有这么多结构?它们差别到底在哪?我的实际场景该用哪一个。

1. 数字滤波器的“结构”到底指什么

1.1 同一个传递函数,不等于同一种实现

我们说的数字滤波器,本质上就是一个线性时不变系统,用传递函数H(z)描述。比如一个二阶IIR滤波器可以写成:

H(z) = (b0 + b1*z^-1 + b2*z^-2) / (1 + a1*z^-1 + a2*z^-2)

看到这个式子,第一反应是写差分方程:

y[n] = b0*x[n] + b1*x[n-1] + b2*x[n-2] - a1*y[n-1] - a2*y[n-2]

这个差分方程对应的“直接画出来”的结构,就是直接I型。但如果你把分子部分和分母部分换个顺序,把中间的延迟链合并,同样还是这个H(z),画出来的结构就变成了延迟单元更少的直接II型。再把H(z)拆成几个一阶/二阶因子的乘积,逐级串联,那就是级联型。再对它做部分分式展开,每一支并联起来相加,就是并联型。

结构不同,但输入输出关系完全一样。这一点是理解整章的关键:结构讲的不是“换了个滤波器”,而是“同一个滤波器换了一套内部接线的方案”。真正决定一个结构好坏的,是四件事——用了多少乘法器/加法器/延迟单元、系数变化时频率响应稳不稳(系数灵敏度)、有限字长下噪声大不大、以及能不能用流水线或并行方式来提速。

1.2 信号流图:这个章节的秘密语言

第5章所有结构都是用信号流图画的。你不需要把它学得多深,只要盯住三个基本单元:延迟z^-1(记住上一个输入)、乘法(乘一个系数)、加法(多路相加)。理解了这三个单元的组合规则,后面所有结构图都看得懂。

信号流图还有一个特别有用的操作叫“流图转置”:把每条支路方向反转,输入输出互换,新的流图实现的传递函数和原来完全一样。这个定理在工程上价值很大,因为有些结构虽然数学上等价,但转置之后数据流动方向变了,更适合在FPGA上做流水线,或者在某些DSP上能减少数据冒险。我后面讲直接II型时会再提到它。

1.3 评估一个结构好不好的三个维度

以我落地的经验,评估结构时一般只看三个维度。

第一是资源成本。乘法器在DSP和FPGA里都是贵资源,延迟单元对应寄存器或RAM,加法器稍微便宜点,但也不是白给。直接I型为什么被嫌弃?它用了M+N个延迟单元,明明可以更省。第二是有限字长下的表现。系数存在有限位宽寄存器里会量化,运算结果每次也要舍入,这些误差在不同结构里被放大的程度完全不同。第三是实现友好度。有些结构天然能流水线,有些结构则存在依赖链,吞吐率上不去。

这三个维度贯穿整章,也是你后面看各种结构时该持续追问的:它在哪个维度上有优势,又在哪个维度上付出了代价。

2. IIR滤波器的四种经典结构

IIR滤波器因为有反馈环路,极点问题让结构选择变得敏感。四种结构中,我最想强调的是“为什么直接型只配做教材入门”,以及“为什么工程上绝大多数时候在用级联型”。

2.1 直接I型:最直观,也最浪费

直接I型就是把差分方程“照抄”成电路:先用b_k那串系数对输入x[n]做加权延时,得到一个中间结果;再用a_k那串系数对输出y[n]做反馈加权延时。分子部分是前馈结构,分母部分是反馈结构,两者串联。

它的优点是理解成本为零,跟差分方程一一对应,初学调试时方便看数据流。缺点也很明显:需要M+N个延迟单元。一个10阶滤波器,直接I型光延迟单元就要20个,乘法器要21个,在资源紧的嵌入式环境里相当浪费。更麻烦的是高阶情况下系数灵敏度很高,定点实现时极点位置稍有偏差,频率响应就面目全非。

我实际项目中基本不直接拿它做高阶滤波器。它最多作为教学推导的起点,或者在一阶二阶这种低阶场景下临时用用。

2.2 直接II型:延迟单元减半的“典范”

直接II型的高明之处在于改变运算顺序。原本先算前馈部分、再算反馈部分,两个部分各有自己的延迟链。但因为整个系统是线性时不变的,可以把反馈部分放到前边先算,让两部分共享同一条延迟链。数学上可以引入中间变量w[n]:

w[n] = x[n] - a1*w[n-1] - a2*w[n-2] - ... y[n] = b0*w[n] + b1*w[n-1] + b2*w[n-2] + ...

看到没有,w[n]只存一份,分子分母计算都用同一批延迟单元。延迟单元从M+N直接降到max(M,N)。这也是它被称为“典范型”的原因——延迟单元数达到了理论最小值。

但直接II型并不是免费的午餐。它把反馈环节放在前面,中间变量w[n]的动态范围可能比输入输出大不少。定点实现时如果中间变量溢出,后面无论如何都救不回来。实践中用直接II型时,我通常会额外加增益调整,把w[n]的幅度压到安全范围。很多教材不提这个细节,实际调DSP时踩到坑才明白。

这里还要提一句转置直接II型。流图转置后,输出y[n]的计算只依赖当前的输入和一组状态变量,数据依赖链更短。在FPGA里做流水线时,转置结构能把关键路径打断,提升时钟频率。所以别看它跟直接II型“数学上等价”,工程实现上差别可以很大。

2.3 级联型:工程上的默认答案

直接把一个高阶H(z)拆成一堆二阶因子的乘积,每个二阶因子用一个直接II型小节实现,然后一级一级串联,这就是级联型:

H(z) = A * H1(z) * H2(z) * ... * Hk(z)

其中每个Hi(z)是实系数的二阶(或一阶)节。这样做的最大好处是:每个二阶节的极点只由它自己的系数决定,高阶系统的极点被“物理隔离”。某个二阶节的系数量化误差只会扰动它自己的那对极点,不会像直接型那样所有极点互相牵扯、一点小量化就全盘漂移。零点同理,可以逐节精确控制。

级联型还有一个实际操作上的好特性:调试友好。滤波器指标出问题时,可以单独测每一级,快速定位是哪一对零极点偏离了预期。我在某个带通滤波器项目里就是这么排障的——直接看每一级的幅频响应,发现是其中一对共轭极点因为系数舍入向单位圆外挪了,单独修那一级的系数就解决了。

需要注意两个细节:其一,零极点配对和级联顺序不是唯一的,不同的配对/排序会影响中间各级的信号动态范围和总噪声,理论上要做优化,工程上一般先把离单位圆近的极点跟最近的零点配对,再把增益最大的级放在中间偏后的位置。其二,每一级内部通常用直接II型,因为它延迟单元少、数值表现也尚可。

2.4 并联型:极点独立,但零点不好控

并联型的出发点完全不同。它不是把传递函数拆成乘积,而是做部分分式展开,变成一堆一阶/二阶节的和:

H(z) = C + H1(z) + H2(z) + ... + Hk(z)

每个Hi(z)对应一对极点,所有支路输入共享同一个x[n],输出相加。

并联型的优势是极点控制极其独立。你想调整某一个谐振峰的频率或锐度,只需要动对应那条支路的系数,其他支路完全不受影响。对于需要在线调整某些频点响应的应用(比如自适应陷波),并联型比级联型更合适。

但它的短板是把零点搞没了。整体传递函数的零点是由各支路并联相加“相互抵消/合成”出来的,你没法单独指定一个零点放哪。如果滤波器设计对零点位置要求很严(比如陷波滤波器就非常依赖精确零点),并联型用起来就很别扭。另外,部分分式展开的计算量也比较大,系数数值有时不太好看,定点化时也要小心。

2.5 IIR结构怎么选

直接I型基本不用于高阶;直接II型适合低阶、对延迟敏感、资源紧张的场景,但要注意中间变量的动态范围;级联型是通用首选,稳定、可控、调试友好;并联型适合强调极点独立调整的场景。如果做的是定点实现,级联型几乎是默认答案。

3. FIR滤波器的几种基本结构

FIR滤波器没有反馈,所有极点都在原点,天生稳定。结构选择的逻辑从“担心极点漂移”转向“怎么省乘法器”“怎么利用线性相位”“怎么在频域直接做”。

3.1 直接型(横截型):最直接的卷积机

FIR直接型就是卷积的直接展开:

y[n] = h[0]*x[n] + h[1]*x[n-1] + ... + h[N-1]*x[n-(N-1)]

一个N阶(N个抽头)的FIR直接型需要N个乘法器和N-1个加法器,N-1个延迟单元。结构图就是一条抽头延迟线,每个抽头乘上对应系数再累加。教科书里也叫横截型。

直接型最值得讲的优化是线性相位。线性相位FIR的系数满足偶对称或奇对称:h[k] = ± h[N-1-k]。这意味着可以把对称的两个抽头先相加,再乘同一个系数。一个N抽头滤波器,乘法器从N个降到N/2(N为偶数)或(N+1)/2(N为奇数)。

我在一个需要做256抽头线性相位低通滤波的项目里,用这个对称优化把乘法器从256个砍到128个,FPGA里直接省出一大块DSP单元。注意,在对称点相加减法要先对齐,这属于实现层面的细节,但非常值钱。

3.2 级联型:把长FIR拆成短因子

跟IIR一样,FIR的传递函数H(z)也可以分解成二阶因子的乘积,然后串联起来。因为系统没有反馈极点,每个二阶因子都是纯前馈的,实现上就是直接型小节的串联。

级联型对FIR的价值集中在零点控制上。如果你需要精确放置某几个零点(比如要压掉某个特定频率),直接型里所有零点混在一起,系数一动全局都动;级联型则可以一组一组地调,互不干扰。代价是乘法器数量通常会变多,因为分解后会有公共系数被重复计算,总体资源反而增加。所以FIR级联型通常只在需要精细调整零点或者硬件规模不大时使用。

3.3 频率采样型:从频域样本直接搭滤波器

频率采样型是一个比较“妙”的结构,它是从频率采样重建的角度来的。对N点FIR,在单位圆上等间隔取N个频响样本H(k),然后可以构造:

H(z) = (1/N) * (1 - z^-N) * Σ_{k=0}^{N-1} H(k) / (1 - W_N^-k * z^-1)

其中W_N是旋转因子。结构上分成两部分:前面(1 - z^-N)是一个梳状滤波器,它产生了N个均匀分布在单位圆上的零点;后面每个1/(1 - W_N^-k * z^-1)是一个谐振器,在对应频率点产生极点。零极点一一配对后,整个系统又变回FIR。

这个结构的好处是直接从频域样本值H(k)出发,想改某个频点的响应,直接改对应H(k)就行,非常适合做频域自适应或动态频谱整形。坏处也很明显:谐振器系数是复数,硬件上要实现复数乘法;而且零极点都在单位圆上,有限字长下零极点一旦不精确配对,系统就可能不稳定。实际实现一般会把采样圆半径从1往内收一点,比如取r = 0.99,牺牲一点点精度换稳定性。

3.4 快速卷积型:FFT换速度

快速卷积型的思路完全不在“结构图”上发力,而是利用圆周卷积定理,用FFT把时域卷积变成频域乘法:

Y(k) = X(k) * H(k)

然后IFFT回去。FIR抽头很长、数据流又是一段一段来的时候,用分段卷积(重叠相加法或重叠保留法)可以大幅降计算量。一个1024点的FIR,直接卷积每输出一个点要做1024次乘加,用FFT之后整个块的计算量降到O(N log N)量级,差距是几十倍。

不过它不适合所有场景:系统延迟变大,因为要攒够一块数据才能做FFT;存储开销也大;对实时性要求极高的单样本流水场景,反而不如直接型。所以快速卷积型一般出现在长滤波器、非实时或准实时的音频处理、软件无线电这类场景。

3.5 FIR结构怎么选

资源不紧张、抽头短时,直接用直接型加线性相位优化,最省事;需要精细控制零点选级联型;需要在线改频域响应,频率采样型有独特价值;抽头长、吞吐要求高,优先评估快速卷积型。

4. 结构选型与有限字长效应:最容易被忽略的决定性因素

这一节是工程现实和教材理论的交汇点。前面说的资源差异、分解方便等,在浮点实现里可能只是“省不省事”的问题;但一旦进入定点世界,结构选择直接决定系统是稳定运行还是全面崩溃。

4.1 一张表格看清四种IIR结构的差异

结构延迟单元数乘法器参考数极点控制零点控制典型风险常用场景
直接I型M+NM+N+1差差系数灵敏度高,资源浪费教学入门,低阶
直接II型max(M,N)M+N+1差差中间变量动态范围大资源敏感的低阶
级联型每节约2个每节约5个独立独立节间顺序需优化工程首选,通用
并联型每支约2个每支约5个独立无部分分式展开繁琐极点单独调节

FIR那边,直接型最适合常规实现;级联型乘法器略多但零点控制好;频率采样型系数为复数、需做稳定修正;快速卷积型延迟和内存开销高,但长滤波器吞吐优势大。

4.2 有限字长效应对结构选择的决定性影响

教科书用一大节讲有限字长效应,实际含义说白了就是:系数存进有限位宽的寄存器时会被“截断”成近似值,每个乘加运算的中间结果也要舍入。在直接型结构里,高阶系统的所有极点都是由同一个多项式系数组共同决定的,某个系数哪怕是0.1%级别的偏移,多个极点就可能一起大幅度移位。我见过一个8阶带通滤波器,设计时有一对极点在单位圆内0.97的位置,定点化之后直接跑到1.003,系统当场自激振荡,输出顶到满幅。换成级联型之后,同样的8阶滤波器拆成4个二阶节,每一对极点由本节的2~3个系数单独决定,同样的量化精度下,极点最大偏移只有直接型的几十分之一。这不是玄学,而是高阶多项式求根对系数扰动极度敏感这一数学事实的必然结果。

所以如果项目确定要用定点实现,IIR滤波器别犹豫,上级联型。并联型在极点稳定性上也不错,但零点不可控,看需求取舍。FIR则由于没有极点反馈,结构选择对稳定性的压力远小于IIR,更多的考虑是乘法器数量和延迟约束。

4.3 一个实战案例:8阶带通滤波器的定点化过程

我在一个音频前端项目里做过一个8阶IIR带通,中心频率和带宽要求都挺苛刻。第一版图省事,直接用直接II型在32位定点DSP上实现,系数按Q15格式量化。跑仿真时一切正常,一上板就发现输出总有低频自激成分,怎么调增益都没用。后来把系数打印出来,把实际量化的分母多项式求根,发现有一对共轭极点落在单位圆外。

接下来换级联型,把8阶拆成4个二阶节。重新做零极点配对:先把离单位圆近的极点优先与最近的零点配对,并按“增益从小到大的级放后面”的顺序排列。每个二阶节单独做系数归一化,确保每一级的w[n]中间变量不溢出。这一版上板后自激消失,通带纹波和设计值基本一致。整个过程最有价值的教训是:在设计阶段预留一个“定点化模拟”环节,用你目标平台的量化位宽去模拟滤波器行为,而不是等上板再排雷。

4.4 转置结构在流水线中的额外优势

前面提过流图转置定理。它在实际硬件实现里有个非常实用的作用:转置后的直接II型结构,每个输出样本的计算可以拆成时间上并行的多个乘加操作,关键路径缩短,也就是所谓“转置结构更吃流水线”。在FPGA上做高速滤波器时,把直接II型二阶节转置一下,时钟频率往往能提升一大截。有人不理解为什么数学等价的结构性能差这么多,其实是因为原结构里输出y[n]的计算依赖一串串行累加,转置后这些累加拆给了不同时刻,每个周期只需要做一个乘加。这个细节在实时信号处理系统里很值钱。

5. 常见问题与排查技巧实录

下面这些坑,教材里通常会讲,但读教材时感受不深,真正在项目里撞见才刻骨铭心。

5.1 系数量化后频率响应偏移,怎么救

量化导致频响偏移,先判断偏移方向。如果通带边界偏移均匀、幅度误差也不大,可能是系数位数不足,直接增加系数位宽即可。如果频率响应出现局部剧烈变化,尤其是IIR出现额外谐振峰,多半是极点位置受到量化扰动,这时候换级联型重新分解是最快的解法。如果是FIR线性相位特性变差,检查是否因为对称系数量化后不再严格对称,这会直接破坏线性相位。

5.2 IIR滤波器的极限环振荡

极限环是IIR定点实现里一个特别让人迷惑的现象:输入为零时,输出却在某个小幅度上来回震荡,不衰减到零。原因是递归运算中乘法结果被舍入后,某些瞬时状态落进了一个“死带”,状态更新永远在这个死带里循环。破解方法有几个:一是使用幅度截断方式而不是四舍五入,让能量持续损耗;二是加法采用饱和处理,防止溢出带来的非线性跳变;三是在必要时给信号叠加一个极小幅度的抖动信号,破坏周期性。我遇到过一次陷波器在静音时输出端有微弱周期性噪声,排查半天才确认是极限环,加上饱和处理就干净了。

5.3 中间变量动态范围太大,怎么处理

直接II型结构里w[n]的动态范围往往比输入大很多,这是结构本身决定的。处理办法有两个方向:一是做增益归一化,在每级前分配合理的增益,保证中间信号不溢出也不太小;二是考虑改用转置结构或是级联型,让每一步运算的数值范围更可控。我在定点开发中习惯先把整个滤波器放到浮点环境里,把所有中间节点最大值打出来,据此给每一级分配Q格式,这样量化噪声每个节点都能控制在合理水平。

5.4 浮点平台上结构选择就没那么重要吗

浮点平台上系数灵敏度和舍入噪声压力会小很多,结构选择的侧重点就变成资源、延迟、并行度和代码可维护性。即便如此,级联型依然是IIR实现里更稳的选择,因为它容错性强,调试也方便。浮点不是放任用直接型的理由,只是容错空间大了一些。定点项目里如果偷懒,结构会以极其残酷的方式“回报”你,这是我在多个项目里反复验证过的事。

实操心得小结

滤波器结构这块,我的总体体会是:公式能推导是一回事,真正决定项目成败的是你选择的结构在目标平台上的数值表现。做定点实现时,IIR直接上级联型,别在直接型上浪费时间;FIR优先看线性相位对称性优化,乘法器直接减半;如果平台是FPGA且追求高吞吐,转置结构值得认真考虑。每次换结构之前,都把系数量化后的实际零极点算一遍,这会帮你提前发现绝大多数问题。滤波器结构不是一个需要背的考点,而是一个你在工程中反复使用的工具箱——挑对工具,比努力打磨一个错误的工具重要得多。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进