资讯动态

CIC滤波器设计指南:原理、MATLAB仿真与FPGA实现避坑

发布时间:2026/10/6 9:24:17 来源:尧图企业网站定制
搞信号处理的人应该都有过这种经历——ADC采样率高得吓人后端DSP根本算不过来第一反应就是“做抽取”。可抽取倍数一大直接用FIR做抗混叠滤波阶数动辄上千乘法器瞬间吃满。我第一次在FPGA里遇到这个问题折腾了好久才意识到CIC filter级联积分梳状滤波器才是这种场景的标配。这篇文章是我的个人学习笔记把CIC filter的原理、MATLAB实现、补偿滤波器设计以及我从仿真环境一路踩到FPGA实现里的各种坑都整理一遍给正在做DDC数字下变频、DUC数字上变频、过采样转换或者单纯被CIC通带下降和增益问题困扰的兄弟们一个参考。关于CIC filter网上的资料不少但多数要么只讲数学推导要么直接甩一段MATLAB代码。我把这两块拼起来再加一点工程视角的选型和排坑经验希望能帮你少走几步弯路。1. 为什么非用CIC不可多速率转换的成本账1.1 高采样率下FIR的“算力灾难”先看一个具体场景中频采样率100MHz基带信号带宽1MHz你想把采样率降到2MHz也就是抽取率R50。为了保证抽取后不混叠抽取前必须放一个抗混叠低通滤波器把通带外的能量滤掉。用等纹波FIR设计一个过渡带从0.8MHz到1MHz、阻带衰减到-60dB的低通滤波器阶数大概在400到600之间这还算是要求不极端的。如果过渡带更窄、衰减更狠上千阶也是常有的事。再算算算力账一个500阶FIR滤波器在100MHz采样率下每秒需要执行500×100e65e10次乘累加。放到FPGA里就算你有300个DSP48乘法器也得跑到300MHz以上的主频才能兜住成本和功耗直接起飞。更麻烦的是这个滤波器只是整条链路里的“第一道粗滤”后面通常还要级联补偿滤波器、成形滤波器如果第一道就这么重后面的资源预算根本没法分配。这就是CIC filter能派上大用场的核心原因它在做同样甚至更大倍数的抽取/插值运算时几乎不消耗乘法器资源只剩下加法器、减法器和延迟寄存器。第一级抽取交给它后面再用少量阶数的FIR去做细致加工整体资源开销会低一个数量级。1.2 CIC是怎么把乘法器“优化掉”的很多人第一次看CIC结构都会觉得神奇不就是一个累加器加上一个差分器吗凭什么能当滤波器我从数学变形来解释。假如我们想要一个最简单的抗混叠FIR滤波器系数全是1长度为RD。对应的传递函数是B(z) 1 z^(-1) z^(-2) ... z^(-(RD-1)) (1 - z^(-RD)) / (1 - z^(-1))妙就妙在这个等号右边。分子(1 - z^(-RD))是两个延迟项相减对应梳状滤波器分母(1 - z^(-1))是一个累加器的倒数对应积分器。也就是说这个原本需要RD次乘累加操作的FIR滤波器可以拆成“一个积分器”和“一个梳状滤波器”的串联中间没有任何乘法器。如果我们想要更强的阻带衰减就做N级级联等效于把长度为RD的全1系数卷积N次。旁瓣抑制大致每级提升13.46dB代价是通带下降越来越明显。这个推导也解释了为什么CIC的硬件实现那么轻巧——它本质上是用“矩形窗卷积”这种最朴素的FIR响应通过代数恒等变换避开大规模乘法换取极低的计算复杂度。频响质量相对一般所以更适合做链路中的第一级粗滤。1.3 CIC适合和不适用的场景清单用了几次CIC之后我自己总结了一套判断依据不一定多严谨但工程上很实用。适合的场景抽取率或插值率特别大比如几十到几千倍FIR直接做资源扛不住作为整条链路的第一级粗滤后面还有FIR精修Σ-Δ ADC后面的降采样、DDC/DUC链路、窄带接收机FPGA资源紧张DSP乘法器数量有限不适合的场景对通带平坦度要求极高的系统单用CIC肯定不行阻带衰减要求特别猛比如-100dB以下CIC旁瓣抑制不够需要精确控制滤波器截止频率边界的场景CIC的过渡带没有独立频率参数可调一句话CIC是“粗活”专家把最重的抽取任务先吃掉把“精细活”留给后续FIR。理解了这个分工后面设计链路时就不会拿CIC硬扛所有需求。2. 拆开CIC看看积分器、梳状滤波器与传递函数2.1 积分器只做加法的“记忆体”积分器的时域公式是y[n]y[n-1]x[n]输出等于当前输入加上上一个输出。用生活化的比喻它像一个永远不会清空的桶你倒入多少水它就攒多少随时告诉你当前的总量。从z变换看积分器的传递函数是H(z)1/(1-z^(-1))在z1处有一个极点。这意味着它的频率响应在直流处趋于无穷单独拿出来看其实是一个不稳定结构。如果直接在MATLAB里把积分器中间节点打印出来数值会随着输入序列不断累计直流分量大的话甚至能涨到10的6次方量级看起来像“炸了”。但这不是真正的发散或振荡因为CIC整体是FIR系统后面的梳状滤波器会把这些累计量精确抵消掉。这个现象新手特别容易误判第5节我还会专门讲。在FPGA里积分器就是一个固定位宽的累加器输出溢出后二进制补码回绕是设计好的只要位宽足够正确性不会受影响。2.2 梳状滤波器做一个“差分”就是滤波梳状滤波器的时域公式是y[n]x[n]-x[n-D]其中D是微分延迟通常取1或2。z变换为H(z)1-z^(-D)单位圆上有D个等间隔零点频率响应是一排“齿”所以叫梳状滤波器。在CIC结构中梳状滤波器放在积分器后面的意义就是消掉积分器在z1上的极点。梳状滤波器在z1处恰好也有一个零点两者级联零极点对消整体就变成一个FIR系统。这就是“积分梳状”组合的数学本质——用一个看似不稳定的累加器配一个精准的差分器把对方的问题互相抵消。D的选取会影响系统应用。D1时第一个零点在z1适合绝大多数抽取场景D2时系统在z-1也会出现零点即输出Nyquist频率附近多了一个凹陷对某些频谱规划有利但也会让通带衰减更明显。具体选1还是2要看输出频带的安排第4节会给出建议。2.3 传递函数推导和幅频特性对于抽取率R、微分延迟D、级数N的CIC滤波器传递函数为H(z)[(1-z^(-RD))/(1-z^(-1))]^N代入ze^(j2πf)这里f是相对于输入采样率Fs_in的归一化频率可以得到幅度响应|H(f)||sin(πRDf)/sin(πf)|^N如果想让频率轴更直观以输出采样率Fs_outFs_in/R为参考设归一化频率为f_out则|H(f_out)||sin(πD·f_out)/sin(π·f_out/R)|^N直流增益为(RD)^N。例如R16、D1、N3时直流增益是16^34096。这个数值看着吓人却是定点实现时必须知道的关键参数——它决定了内部寄存器要预留多少位宽。从幅频形状看CIC在通带内单调下降越接近Nyquist掉得越多阻带第一旁瓣相对主瓣的衰减大约为13.46N dB。这些特征后面用MATLAB画出来会更直观。2.4 CIC的等效FIR系数由于零极点对消CIC的脉冲响应其实是有限长的等效FIR系数就是把长度为RD的全1序列与自身卷积N次b ones(1, R*D); b_cic 1; for k 1:N b_cic conv(b_cic, b); end比如R16、D1、N3b_cic长度为3×(16-1)146。这就是“矩形窗卷积三次”的结果。用这个等效系数和dsp.CICDecimator的输出做对比可以验证System Object的仿真是否正确第3节的验证环节会用到这个思路。理解这一点也有助于后续在FPGA里做定点仿真——等效FIR系数可以作为浮点参考模型和定点CIC实现的输出对比快速定位量化误差和位宽问题。3. MATLAB仿真手把手跑通一个DDC抽取例程3.1 仿真参数设计进入MATLAB实现环节。我特意搭了一个比较贴近实际数字下变频的参数组合R 16; % 抽取率 D 1; % 微分延迟 N 3; % 级数 Fs_in 4.8e6; % 输入采样率 4.8MHz Fs_out Fs_in / R; % 输出采样率 300kHz测试信号包括一个90kHz的带内信号和一个1.7MHz的带外干扰。1.7MHz的干扰在抽取后会折叠到200kHz附近正好落在输出频带内用来观察CIC的抗混叠能力。n (0:4096*R-1); t n / Fs_in; x_sig sin(2*pi*90e3*t) 0.5*sin(2*pi*1.7e6*t);顺手说一句选90kHz是因为它落在输出采样率300kHz的0.3位置附近CIC在这附近的通带下降大概有4dB适合后面做补偿验证。如果只关心抗混叠效果信号频率可以再低一点比如50kHz。3.2 用System Object快速搭建CIC抽取器使用dsp.CICDecimator可以一行代码创建CIC抽取器第一个参数是抽取率第二个是微分延迟第三个是级数cicDecim dsp.CICDecimator(R, D, N); y cicDecim(x_sig);如果用的是老版本MATLAB也可以写成step(cicDecim, x_sig)效果一样。输出y已经是抽取后的序列长度大约是输入长度除以R不整除时向下取整。System Object的好处是内部帮你管理状态和类型比直接用filter函数写传递函数更接近FPGA实际行为这也是我在仿真中优先用它的原因。要看CIC频响freqz(cicDecim)可以直接画不过它归一化频率的基准在不同版本里可能不同。为了稳妥我习惯用理论公式自己画心里更有底f_out linspace(0, 0.5, 4097); f_out f_out(2:end); H_cic abs(sin(pi*D*f_out) ./ sin(pi*f_out/R)).^N; H_cic H_cic / (R*D)^N; % 归一化到直流增益 plot(f_out, 20*log10(H_cic)); xlabel(归一化频率 (相对Fs_{out})); ylabel(幅度 (dB)); grid on;这条曲线会告诉你CIC的通带并不是平的而是越靠近0.5衰减越大。以R16、D1、N3为例在f_out0.3处衰减约4dB在f_out0.4处约7dB。所以后面补一个补偿滤波器几乎是必然的。3.3 用等效FIR系数做参考验证我建议每个做CIC仿真的朋友都养成一个习惯用等效FIR系数做一个独立的“参考模型”再去和System Object对比。这样可以快速发现参数设错、归一化基准搞反等问题。b ones(1, R*D); b_cic 1; for k 1:N b_cic conv(b_cic, b); end % 参考模型先滤波再抽取 x_ref filter(b_cic, 1, x_sig); y_ref x_ref(1:R:end); % 丢弃开头若干点避免初始状态差异 offset 20; y_cic y(offset1:end); y_ref_trim y_ref(1:length(y_cic)); err y_cic - y_ref_trim; rms_err sqrt(mean(err.^2)); fprintf(RMS误差: %e\n, rms_err);理论上这个RMS误差应该接近浮点精度。如果你看到误差很大十有八九是dsp.CICDecimator的参数顺序或者抽取率设置出了问题。3.4 频谱验证看CIC如何“清理”带外杂散先看输入信号和抽取后信号的频谱对比。这里注意FFT长度输入信号长度是4096×R输出是4096左右两者频率轴基准也不一样直接叠在一起画意义不大建议分开画。Nfft_in length(x_sig); Nfft_out length(y); X fft(x_sig, Nfft_in)/Nfft_in; Y fft(y, Nfft_out)/Nfft_out; f_in (0:Nfft_in-1)/Nfft_in * Fs_in; f_out_axis (0:Nfft_out-1)/Nfft_out * Fs_out; figure; subplot(2,1,1); plot(f_in/1e3, 20*log10(abs(X)1e-12)); title(输入频谱); subplot(2,1,2); plot(f_out_axis/1e3, 20*log10(abs(Y)1e-12)); title(CIC抽取后频谱);观察结果时90kHz的带内信号还在但1.7MHz的干扰被大幅削弱了。虽然CIC在这里的阻带抑制不算特别狠但已经完成了粗滤任务后面接一个简单的等纹波FIR就能把残留杂散压到目标水平。需要提醒一下CIC的输出幅度会被放大(RD)^N倍。拿R16、D1、N3来说就是4096倍画频谱前如果不做归一化纵轴数字会大得离谱。上面代码里Y已经除以了FFT长度但没除直流增益所以纵轴会整体抬高大约72dB20×log10(4096)。如果你觉得这个高度影响阅读可以把Y再除以(RD)^N再画看相对值更舒服。4. CIC性能评估与通带补偿4.1 三个关键性能指标评估一个CIC滤波器在链路里合不合格我通常看三个东西通带下降passband droopCIC在通带内的衰减是否在可接受范围旁瓣抑制对带外干扰的抑制能力直流增益决定定点实现的位宽预算这三个指标都可以从H_cic公式一次性算出来。用表格总结一下不同级数在R16、D1条件下的行为级数N第一旁瓣抑制(dB)通带下降趋势硬件资源1约13.5轻极少2约26.9中等少3约40.4明显中等4约53.8很严重较多这里的旁瓣抑制是相对主瓣峰值的经验值实际以精确计算为准。表格的意思是让你心里先有个量级别等画完图才发现选错级数。4.2 通带下降的量化分析用公式来算第3节例子的通带衰减。R16、D1、N3时f_out0.3处|H||sin(0.3π)/sin(0.3π/16)|^3≈(0.809/0.0588)^3≈2604直流增益是4096所以相对衰减为20log10(2604/4096)≈-3.9dB。这意味着90kHz的信号经过CIC后幅度已经掉了约4dB。如果系统对通带平坦度有要求这个损耗必须补回来。补的方法很直接——设计一个幅度响应近似为CIC通带响应倒数的FIR滤波器这就是CIC补偿滤波器。4.3 用fdesign.ciccomp设计补偿滤波器MATLAB的Filter Design Toolbox里有一个专门做这件事的函数fdesign.ciccomp它直接接收CIC的级数N、微分延迟D和抽取率R自动把CIC的通带形状折算进设计规格里fs_out Fs_in / R; d fdesign.ciccomp(N, D, R, ... Fp,Fst,Ap,Ast, 0.3*fs_out, 0.49*fs_out, 0.1, 60, fs_out); Hcomp design(d, equiripple); % 查看补偿滤波器频响 freqz(Hcomp, 1024, fs_out); % 级联验证先过CIC再过补偿滤波器 y_comp filter(Hcomp, y);注意这里的Fp0.3×fs_out、Fst0.49×fs_out是绝对频率Hz因为调用时最后一个参数传了采样率fs_out。如果你的MATLAB版本不支持带Fs的写法就改用归一化频率此时0到1映射到0到Fs_out/2通带边缘想设在0.3×Fs_out就要写成Fp0.6。设计完之后最好用第2.3节的解析公式画一条CIC幅频曲线再把补偿滤波器和CIC的频响乘起来看总响应。我实测下来在通带边缘部分级联后的总响应能控制在±0.1dB以内前提是补偿滤波器阶数给够、设计规格别太极端。注意fdesign.ciccomp在不同MATLAB版本里参数顺序和归一化方式偶尔不一样。建议每换一次环境先help fdesign.ciccomp确认参数定义再用理论公式画总响应对比一下确保补偿方向是对的。4.4 级数和微分延迟的选型策略级数N不是越大越好。N增大确实让旁瓣抑制变强但通带下降也成比例增大补偿压力变大FPGA里积分器和梳状滤波器的级联也更多资源消耗和位宽要求全部上去了。我个人的习惯是抽样率需求一般、后续还有成形滤波器时N取3足够希望第一级就把带外干扰压制得更狠N取4但一定要配补偿滤波器链路非常简单、只用CIC没有后续精修N取2甚至1配合强一点的补偿链微分延迟D方面D1最常用D2会让系统在输出Nyquist附近多一个零点对抑制混叠有帮助但代价是通带衰减更明显。如果输出频带已经用到Fs_out/2附近D1会更舒服。我一般只在输出带宽占比不高、且频谱规划允许的情况下用D2。4.5 CIC插值器与抽取器的不对称之处前面讲的都是抽取实际上CIC在插值侧同样常用。MATLAB里对应的是dsp.CICInterpolatorcicInterp dsp.CICInterpolator(R, D, N); y_up cicInterp(x_up); % x_up是低速信号输出是R倍高速信号结构上和抽取器正好反过来梳状滤波器放在低速侧插值器零填充在中间积分器放在高速侧。这样做的目的是把加法运算尽量留到低速侧进一步提升效率。插值过程会在原始信号频谱上复制多个镜像分量必须靠后级滤波器滤除这个角色通常也由FIR补偿滤波器承担。所以实际DDC/DUC链路里CIC后面接FIR补偿是常规操作不管抽取还是插值补偿滤波器的核心思路是一样的。提示插值器的补偿滤波器和抽取器的补偿滤波器在原理上类似主要差异是插值侧还要考虑镜像频谱的抑制通带规格通常略保守一些。5. 从MATLAB到FPGA我踩过的那些坑5.1 积分器输出巨大是“正常现象”第一次在MATLAB里把CIC拆开看内部节点我差点以为程序写错了——积分器的输出数值一路狂涨甚至到了10的6次方量级。后来才想明白CIC的直流增益(RD)^N就是这么巨大积分器中间节点表示的是信号的“累积量”数值大是数学本质决定的不是发散。真正要担心的是定点实现。如果FPGA里寄存器位宽不够累加器回绕就会破坏结果的正确性。位宽估算经验公式是寄存器位宽≈输入数据位宽N×ceil(log2(RD))。例如输入16bit、R16、D1、N3RD16log2(RD)4N×412所以内部位宽至少要到28bit。如果不够仿真时可能看不出来一上板子就出现诡异噪声。注意CIC的积分器中间节点数值巨大不代表系统不稳定。CIC整体是FIR系统零极点已经完全对消输出是有限且正确的。5.2 频率轴归一化基准搞错会怀疑人生在MATLAB里画CIC频响最大的坑就是归一化频率的基准。dsp.CICDecimator的freqz在不同版本里显示的范围可能不一样有时候横轴0到1表示0到Fs_in/2有时候又按输出采样率Fs_out来。如果不搞清楚拿理论公式一对比零点位置完全对不上还以为自己公式推错了。我的解决办法是不依赖freqz的默认横轴而是用理论公式自己算H_cic再指定频率向量画图。这样无论在哪个版本、哪台机器上结果都是一样的出了问题也容易排查。这个习惯后来帮我避免了很多跨版本仿真的麻烦。5.3 System Object状态残留问题在仿真长数据流时如果分帧调用dsp.CICDecimator记得在每帧开始前调用reset(cicDecim)。否则上一帧的状态会残留到下一帧导致输出出现莫名其妙的瞬态偏差。这个问题在突发式DDC仿真里特别常见我当时排查了很久才发现是忘了在帧间隙重置状态。如果你用等效FIR系数做参考模型也要注意filter函数默认状态是全零每次调用前要想清楚是继续上一次状态还是重新开始。System Object设计初衷就是帮你管理状态但跨帧重置还是得自己调用。5.4 位宽设计要留够余量承接5.1的位宽公式再补充一个工程经验估算出的位宽要留一点余量因为公式基于最坏情况增益实际信号可能有比较大的峰值因子。我在一次设计中按理论值算了28bit结果上板后发现信号在峰值阶段偶尔饱和最终在积分器和梳状之间多加了两位才彻底解决。这个余量不需要太大多2到4bit足够但一定不要省略。如果位宽实在紧张可以考虑在CIC后面做饱和处理而不是简单截断效果会好很多。这些都是实际调试才能积累出来的细节仿真阶段很少暴露。5.5 补偿滤波器的增益与级联注意事项CIC的直流增益是(RD)^N补偿FIR如果按照“归一化后倒过来”的方式设计它的增益通常会远小于1因为要抵消CIC的巨大增益。在实际级联中注意不要让中间数据超过固定点表示范围。一个常规做法是CIC抽取后先做右移归一化把幅度压回合理范围再进补偿滤波器。右移位数就取N×ceil(log2(RD))这个量级正好把CIC的直流增益削掉。这样补偿滤波器就不用处理超大动态范围的信号了。另外补偿滤波器的频率范围只需覆盖信号实际占用的带宽不需要覆盖到Fs_out/2。如果通带Fp设得太宽补偿滤波器会在高频处额外放大噪声反而不利于信噪比。我在一次链路设计里就吃过这个亏把Fp设成了0.49Fs_out结果带外噪声被补偿滤波器抬高了差不多10dB后来把Fp收到信号带宽附近整个链路的噪声性能才正常。6. 写在最后的一些体会最后分享一点个人感受。CIC filter在我眼里是“最朴素也最聪明”的滤波器之一整个实现只有加法、减法和延迟却能撑起DDC/DUC和过采样转换的半壁江山。它的数学内核其实很简单把一串全1的FIR系数用代数恒等变换拆成积分和差分的级联从而避开了大规模乘法运算。如果你看完这篇文章只记一句话那就是“CIC是大抽取率链路的第一道粗滤器通带不平和旁瓣有限的短板都可以靠后续FIR补偿来弥补”。另外MATLAB仿真只是第一步真正落地还是在FPGA里验证定点行为。我个人的经验是先用浮点模型跑通算法再用System Object的定点仿真看量化影响最后才能放心进RTL。如果你手头也有CIC相关的项目不妨先从第3节的例子开始跑一遍再把参数换成你自己的采样率、抽取率和带宽看看通带下降和抗混叠余量是不是符合预期。微分延迟D取2还是取1、补偿滤波器阶数怎么定这些都是在实际链路里才真正有感受的话题。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑