
1. 先搞清楚CIC滤波器到底是什么CIC滤波器这名字听起来挺唬人全称是Cascaded Integrator-Comb翻译过来就是“级联积分器梳状滤波器”。我第一次接触这东西的时候教材上又是Z变换又是零极点图搞得一头雾水。后来实际调过几次FPGA里的数字下变频之后才真正搞明白它到底好在哪、坑在哪。先别管那一堆数学公式CIC滤波器干的活本质上就两件事一个是累加一个是做差。积分器Integrator在时域上就是 y[n] y[n-1] x[n]每次把上一次的输出和当前输入加起来。这个结构在数字信号处理里叫作“累加器”它其实是一个极点在单位圆 z1 上的滤波器。梳状器Comb在时域上就是 y[n] x[n] - x[n-D]D 是延迟拍数。这个结构会把信号和D拍之前的信号做差形成若干均匀分布的零点频率响应长得像一把梳子所以叫“梳状器”。单个“积分器梳状器”组合起来就构成了最基本的单级CIC滤波器。它的传递函数是H(z) (1 - z^(-RM))^N / (1 - z^(-1))^N其中R 是抽取率或内插率也就是你要对信号做多少倍的降采样或升采样。M 是差分延迟数工程上通常取1或2。N 是级数也就是串联了几个“积分-梳状”模块。为什么把积分器和梳状器放在一起就能当滤波器用因为积分器的极点刚好和梳状器的零点在单位圆的 z1 处抵消了。梳状器在 0、2π/RM、4π/RM 这些频率点上提供了零点用来抑制那些会被混叠到基带的频率分量。积分器则对信号做平均等效于一个低通滤波器。这个结构最迷人的地方是整个滤波器里面没有乘法器只有加法器和延迟单元。在FPGA或者ASIC里面乘法器可是稀缺资源一个18x18的硬件乘法器要占不少逻辑资源而CIC滤波器可以做到纯加法器实现这就让它在大降采样率、高速数据流的场景下几乎成了唯一解。我举个例子你就理解了。假设你用ADC以100MHz采样率采信号想降到1MHz的基带再做解调这就是100倍的降采样。如果用FIR滤波器100倍抽取意味着你需要一个阶数可能高达几千阶的FIR来压制混叠噪底每个输出点要做几千次乘累加这个计算量多可怕。而CIC滤波器只需要几个累加器就能跑完代价是幅频响应没那么陡峭带外抑制能力也一般但用在后级再接一个抽取FIR的方式就能把性能补回来。1.1 从名字拆解核心原理我当年学CIC被那一堆“差分延迟”“抽取/内插”“多相结构”绕得头晕。现在回过头来看其实只要抓住这条主线抽取前必须抗混叠滤波内插后必须镜像抑制滤波CIC就是一种专门为极低复杂度而生的多速率抗混叠滤波器。先看积分器。积分器在时域里做的事就是累加它的频响是|H_int(f)| 1 / |2 sin(πf/f_s)|当频率接近0的时候sin(πf/f_s)趋近于0增益趋近于无穷大所以积分器本身是一个高增益低通结构。但单独一个积分器是不稳定的因为你没有任何东西来约束它不断的累加。梳状器的频响是|H_comb(f)| |2 sin(πf·RM/f_s)|它在 f k·f_s/(RM)k为整数处增益为0。把两者级联起来梳状器的零点把积分器的极点抵消掉整个系统的频率响应就变成了稳定、可控的低通特性。这个“零极点抵消”的思想跟我后面要讲到的CIC补偿滤波器设计其实是一脉相承的——你用某一种曲线去补偿另一种曲线最终达到系统指标。多级CIC就是把这个“积分-梳状”基础单元串起来。级数N越接近旁瓣衰减越快通带内纹波也会更明显。这和我们做FIR滤波器时提高阶数有点类似但CIC的代价是通带边缘的下垂更加厉害。1.2 频率响应当中的三个关键参数CIC滤波器的频率响应由三个参数共同决定抽取率R、差分延迟M、级数N。R决定主瓣宽度。主瓣和第一个零点的宽度是 f_s/RM。你说你要做64倍抽取那第一个零点就落在 f_s/(64M) 的位置。如果你把M从1改成2零点频率就减半主瓣就压得更窄但通带也会收窄所以M不是越大越好。N决定阻带衰减斜率。N每增加一级旁瓣衰减大约多20dB/dec。很多工程实现里N取4到5是比较常见的。N取5在64倍抽取下第一旁瓣的衰减大约是40到50dB够你用N取6以上通带内的下垂就会变得很夸张后级FIR补偿压力非常大。RM三者的乘积直接决定系统增益。CIC滤波器在DC处的增益等于(RM)^N。这个增益非常可观例如R64、M1、N5DC增益就是64^51,073,741,824。你在MATLAB里跑仿真的时候如果忘记归一化或者没有做合适的截位处理看到输出数据爆表是必然的那不是算法错了是增益没处理到位。还有一点需要提醒的是抽取率R和差分延迟M的位置可以交换有人写成RM有人写成R·M意思都一样。很多初学者在阅读不同资料时会被符号绕晕其实只需要记清楚H(z) (1 - z^(-RM))^N / (1 - z^(-1))^N 这个标准式子就行。1.3 什么场景会用到CIC滤波器CIC滤波器的经典应用场景包括数字下变频DDC在软件无线电接收机里ADC采样的宽带信号需要经过数字混频搬移到基带然后大幅降采样CIC滤波器作为第一级抽取抗混叠滤波器几乎跑不掉。数字上变频DUC发射链路里基带信号需要插值到高采样率CIC插值滤波器可以在最后一级大幅度提高采样率后面再接一个FIR滤波器做镜像抑制和补偿。过采样ADC/DAC的降采样/上采样Σ-Δ ADC后面的数字抽取滤波器首选CIC因为数据率很高FIR扛不住。音频采样率转换例如44.1kHz和48kHz之间互转不少人会用CIC加补偿FIR的方式做虽然性能一般但胜在简单。一句话概括只要你遇到“高采样率×大降采样倍数”优先考虑CIC。如果你需要的是精细的带通或者窄带低通那CIC就帮不上忙了它只擅长做宽带低通和粗略的抽取抗混叠。2. 为什么选CIC优势和代价做工程其实永远是在资源、性能和成本之间做权衡。CIC滤波器的存在本身就是这种权衡的结果。它把“零乘法器”这个特点发挥到了极致但同时也把“频响不理想”这个问题甩给了后级电路。2.1 最大的优点我先说结论CIC滤波器最大的优点不是性能而是极低的硬件复杂度。在FPGA上实现一个N级CIC抽取滤波器所需的资源大约是N个累加器和N个延迟寄存器。以5级、64倍抽取为例你在Xilinx Artix-7这类中低端FPGA上跑几百MHz的数据流毫无压力但你要是用同样的FPGA去做一个512阶FIR抽取滤波器光DSP48乘法器可能就得吃几十个更别提布线时钟频率能不能跑起来。另一个优点是CIC滤波器天然适合极高的数据率。因为积分器和梳状器之间隔着抽取器你可以把速度较高的积分器部分放在前端速度较低的梳状器部分放在后端这样内部大部分逻辑都工作在低速时钟域下功耗大幅下降。这个“多速率分解”的思想在很多高速信号处理链里都会用到。2.2 最大的代价有优点就有代价CIC滤波器的最大代价集中在两点通带下垂和增益爆炸。通带下垂是指CIC滤波器的幅频响应在通带边缘处衰减得很厉害。单级CIC在通带边缘的衰减大约是Attenuation ≈ N × 20log10( sin(πf0/f_s) / (πf0/f_s) )其中f0是通带边界频率。如果你的通带边界是f_s/(4R)也就是抽取后奈奎斯特频率的一半那么单级CIC在通带边缘的衰减大约是0.22dB左右5级就是1.1dB。这个数值你看着不大但在某些对幅度一致性要求高的应用比如星座解调里就会影响误差矢量幅度EVM必须做补偿。增益爆炸就更直接了。前面说的DC增益等于(RM)^N这个数值非常大。用定点实现时每级积分器的进位位宽需要按增长位宽来设计否则就溢出。设计公式是B_out N × log2(RM) B_in这个位宽不是随便加的加少了溢出加多了浪费资源。我见过不少初学者在MATLAB仿真里用浮点爽了一把转到FPGA定点之后直接把位宽截短结果输出全成噪声完全没意识到字长增长这个问题。2.3 与其他低通滤波器的对比我常用一个表来对比CIC和FIR在抽取场景下的差异对比项CIC滤波器FIR抽取滤波器乘法器0个纯加法实现每个抽头一个乘法器阶数越高越多资源占用极少适合ASIC/FPGA较多但性能上限高通带平坦度较差边缘下垂明显可精确设计等纹波或线性相位阻带抑制依赖级数约每级20dB/dec可达任意阶数带外可做极陡灵活性参数少R、M、N调整有限抽头系数任意设计自由度高适用场景大抽取率、高速数据流中低抽取率、指标严格的场合业界最常见的做法是用CIC做第一级粗抽取把数据率降下来再用FIR做第二级精滤波和补偿。这样既利用了CIC的低复杂度优点又弥补了它性能不足的短板。你在Xilinx的DDS Compiler、Signal Processing Blockset里看到的很多DDC参考设计基本都是这个套路。3. MATLAB实现指南说完了原理进入实操。MATLAB里做CIC滤波器设计有好几种路子我平时用下来比较顺手的是两条一条是用fdesign.cic快速设计适合验证指标另一条是手写积分器梳状器结构适合做定点仿真和理解内部细节。两条路我分别讲。3.1 方案一用fdesign.cic快速出滤波器fdesign.cic是MATLAB内置的CIC滤波器设计工具用法非常简洁几行代码就能得到filter对象% 抽取率 R64差分延迟 M1级数 N5 R 64; M 1; N 5; % 创建CIC抽取滤波器设计对象 d fdesign.decimator(R, CIC, M, N); Hcic design(d); % 查看幅频响应 fvtool(Hcic); % 查看增益信息 gain Hcic.Gain; fprintf(CIC DC增益: %.2f dB\n, 20*log10(gain));这里design函数会自动算出CIC滤波器在不同阶段的增益方便你后续做定点化参考。fvtool打开的可视化窗口可以直接看到幅频、相频、零极点图非常直观。如果要做CIC插值滤波器把fdesign.decimator换成fdesign.interpolator就行参数完全一样d fdesign.interpolator(R, CIC, M, N); Hcic_interp design(d);注意这里的CIC滤波器对象是一个多速率滤波器调用filter函数的时候它内部会自动完成抽取或插值。例如% 输入40MHz采样率的信号 fs 40e6; t (0:4095)/fs; x cos(2*pi*1e6*t) 0.1*randn(size(t)); % 1MHz信号加噪声 % 过CIC抽取64倍 y filter(Hcic, x); disp(length(x)); % 4096 disp(length(y)); % 64输出长度就是 ceil(4096/64)64滤波器内部自动做了抽取。3.2 方案二手写CIC结构方便看内部信号用fdesign.cic虽然方便但它封装得太严实了你很难看到每一级积分器、梳状器中间的数据形态。我后来做定点仿真的时候喜欢手写一个基础CIC结构既能验证理论又能逐级检查位宽。以下是抽取模式的CIC滤波器MATLAB实现浮点版本便于理解function y cic_fir_decimate(x, R, M, N) % 输入: x为输入序列, R为抽取率, M为差分延迟, N为级数 % 输出: y为抽取后的输出序列 % 1. 级联N级积分器 integrator zeros(1, N); integ_out zeros(size(x)); for n 1:length(x) tmp x(n); for k 1:N integrator(k) integrator(k) tmp; tmp integrator(k); end integ_out(n) integrator(N); end % 2. 抽取简单起见每R点取一个输出 decimated integ_out(R:R:end); % 3. 级联N级梳状器 comb_delay zeros(N, M); y zeros(size(decimated)); for n 1:length(decimated) tmp decimated(n); for k 1:N delayed comb_delay(k, end); % 取M拍前的值 % 更新延迟线 comb_delay(k, 2:end) comb_delay(k, 1:end-1); comb_delay(k, 1) tmp; tmp tmp - delayed; end y(n) tmp; end end这段代码虽然效率不高但逻辑非常清晰你可以逐行打印中间变量观察数据流。实际工程中CIC滤波器的实现可以采用高效的多相分解结构但在MATLAB里完全没必要弄那么复杂理解原理才是重点。如果你用的是新版本的MATLAB还可以用dsp.CICDecimation这个系统对象它封装性更好也支持定点% 用dsp.CICDecimation实现64倍抽取 cicDec dsp.CICDecimation(DecimationFactor, R, ... NumSections, N, ... DifferentialDelay, M, ... FixedPointDataType, Custom);dsp.CICDecimation的定点模式支持位宽设置做FPGA定点预演的时候非常有用建议多翻一翻帮助文档里的案例。3.3 频响分析和幅频图绘制设计完滤波器我们总得验证一下频响是否符合预期。fvtool是交互式工具适合快速看但如果要出图放进报告里我一般用freqz手动绘% 用freqz绘制CIC滤波器的等效低通频响 R 64; M 1; N 5; % 系数如何算CIC等效FIR可以用不同方式近似这里用fdesign得到对象后转换 d fdesign.decimator(R, CIC, M, N); Hcic design(d); % 获取等效FIR系数抽取后的等效响应 b Hcic.Numerator; % 对于CIC抽取这是个长向量 a 1; % 画出归一化频响 [H, F] freqz(b, a, 4096, whole); % 实际只用到前半段 F_limited F(1:2048); H_limited H(1:2048); plot(F_limited/pi, 20*log10(abs(H_limited))); xlabel(归一化频率 (x π rad/sample)); ylabel(幅度 (dB)); title([CIC抽取滤波器频响 R num2str(R) , M num2str(M) , N num2str(N)]); grid on;如果你不想用fdesign转换可以自己直接根据理论公式算频率响应N 5; M 1; R 64; f linspace(0, 0.5, 4096); fp f / (R*M); % 归一化到抽取前的频率CIC频响公式中分母的归一化频率 H_abs abs( sin(pi*fp) ./ sin(pi*fp/R) ).^N; H_abs H_abs / (R^N); % 归一化让DC处增益为1 plot(f, 20*log10(H_abs)); grid on;注意这里用了归一化让DC处增益为1。如果不归一化你会看到一条增益高达120dB以上的曲线那是真实增益但看图太丑了。4. 参数选择与补偿设计参数怎么选这是每次做CIC都要重新想一遍的问题。我给出一些工程经验值你可以以此作为起点再微调。4.1 三个参数的工程经验抽取率R这个通常由系统架构决定不是滤波设计能定的。ADC采样率和后端处理的符号率/码率决定了R必须取多少。比如你做蓝牙接收机2.4GHz频段中频采样率80MHz基带信号带宽1MHz那大概需要R32到R64的抽取组合。在多级抽取链路中CIC一般负责最大的那级降采样。差分延迟M工程上M取1就够了。M2会带来两个变化第一个零点频率减半带外抑制更好但通带也会更窄同时系统增益翻倍位宽多一位。我一般在窄带信号、大抽取率的场景下会试M2其余情况一律M1。M再大的话通带损失太严重而且CIC本身不是高精度滤波器没必要为了这点带外性能牺牲通带。级数NN决定阻带衰减。需要多少阻带衰减取决于你对混叠的要求。如果要求混叠抑制60dB以上N通常需要4到5。公式近似是Attenuation ≈ 20N log10(RM/π) 在第一个旁瓣峰值附近更准确的做法是直接跑一遍仿真看结果。N5在多数工程场景下够用N6以上慎用因为通带下垂会成倍增加。N超过6的CIC后续补偿FIR会做得非常痛苦补偿滤波器自身的阻带特性也可能被补偿过程破坏。这三个参数串在一起最终效果就是通带纹波和阻带抑制之间的权衡。我建议你用MATLAB写一个小循环扫N和M画出不同参数组合下的频响叠加图一眼就能看出什么配置适合你的指标。4.2 通带补偿的实测思路CIC的通带下垂是它的“原罪”。补偿方案很多最简单的是在CIC后面接一个补偿FIR。MATLAB里用fdesign.ciccomp可以快速生成补偿滤波器系数% 设计CIC补偿FIR假设CIC参数 R64, M1, N5 Fpass 0.2; % 通带边界归一化到输出采样率 Fstop 0.5; % 阻带起始频率 Ap 0.1; % 通带纹波要求dB Ast 60; % 阻带衰减要求dB d fdesign.ciccomp(R, M, N, ... Fp,Fst,Ap,Ast, Fpass, Fstop, Ap, Ast); Hcomp design(d, equiripple); % 查看补偿滤波器系数 coeffs Hcomp.Numerator; fvtool(Hcomp);这里fdesign.ciccomp的输入参数顺序是(R, M, N)注意和fdesign.cic在设计CIC本身时参数顺序保持一致。设计完成后你可以把CIC和补偿FIR级联起来看整体频响% 级联响应 Htotal Hcic * Hcomp; % 注意这只是频率响应相乘实际系统要实现为物理级联 fvtool(Hcic, Hcomp, Htotal);如果整体通带纹波还是不满足要求可以微调Fpass或者补偿滤波器的阶数。补偿FIR的阶数一般控制在几十到一两百阶之间即可毕竟它的任务只是“找平”不需要做抗混叠主滤波。有一个更省资源的做法在抽取后的低速率端用IIR滤波器实现补偿。比如一个一阶或二阶的高频提升滤波就能很大程度上改善CIC通带下垂。IIR的线性相位特性虽然不好但在补偿CIC这种相位本身就非线性场景下IIR引入的相位失真可以接受前提是你的系统对群延迟不敏感。4.3 定点化和字长设计重点这一点我反复强调因为它决定CIC能不能在硬件上真正跑通。CIC滤波器在FPGA中实现时的位宽选择遵循公式B_out B_in ceil(N × log2(RM))其中B_in输入位宽B_out是积分器内部最大位宽。这个公式是CIC实现的理论基石来自Hogenauer的经典论文。他证明了只要内部位宽不小于这个值滤波器就不会发生溢出错。举个例子输入是16位ADCR64M1N5B_out 16 ceil(5 × log2(64)) 16 30 46位也就是说积分器内部至少要46位宽的累加器。你看这个位宽一下就涨到46位了很多人一开始会觉得不可思议。但CIC的位宽增长是必须的否则累加器溢出后梳状器做差分就会产生严重失真输出数据根本不能用。位宽定下来之后还有一步是截位设计。你要从46位的数据中截出16位或者24位给后级FIR截多少位合适通常做法是保留足够多的有效位同时考虑噪声。CIC滤波器的输出噪声包含量化噪声整形和累加器截位噪声截位位的选择需要结合后级FIR的动态范围来定。MATLAB里做定点仿真我推荐用fixed-point designer工具箱配合dsp.CICDecimation的Custom定点模式来跑。你可以设置内部字长 46位小数位长按增益分配然后对比浮点和定点的输出误差。误差在-100dB以下基本就算正常。5. 常见问题与排查技巧实录CIC滤波器调试中遇到的坑大多集中在参数配置、位宽、以及频响理解上。我把这几年遇到过的问题整理成了一张表方便你对照排查。5.1 问题速查表现象可能原因解决方法输出全是峰值或有规律的大数累加器位宽不足发生了溢出按 B_out B_in N×log2(RM) 重新计算位宽幅频响应DC增益不等于(RM)^N忘记考虑多级级联的增益累积查看Hcic.Gain设计时统一归一化口径通带边缘衰减太大级数N过高或M取值偏大降低N或M改回1或在后级加补偿FIR抽取后信号出现混叠噪声抽取率R太大或者CIC级数不足提高N或采用两级抽取CICFIR输出没有信号只有噪声梳状器和积分器的顺序搞反了抽取系统必须“积分器→抽取→梳状器”定点仿真输出与浮点差异大截位位置不对或截位噪声过高用fixed-point designer扫描小数位长频响曲线在特定频点有陷波M大于1导致的零点偏移如果不需要则设M1级联补偿FIR后整体频响反而变差补偿滤波器的阶数太低或者Fpass设置太宽增大补偿FIR阶数适当收紧Fpass这些问题的根源大多不是MATLAB代码写错了而是对CIC结构的理解有偏差。尤其是“积分器在前、梳状器在后”这个顺序抽取模式下不能颠倒。原因是抽取器会改变信号的时间索引梳状器如果放在积分器前面抽取后延迟线的构建会变得复杂且容易出错。5.2 我在实际项目中的几个教训第一个教训是关于增益归一化的。有一阵子我做DDC链路CIC后接了一个NCO混频器结果发现混频器输入信号幅度过大导致NCO查表输出的正弦波被截位削顶。排查了很久才发现CIC输出端的信号幅度比输入大了上百倍我没有做自动增益控制AGC就直接往后送了。从那以后我只要用CIC就一定会先跑幅值统计y filter(Hcic, x); fprintf(输出峰值: %.4f\n, max(abs(y))); fprintf(输出均值: %.4f\n, mean(abs(y)));第二个教训是别迷信M越大越好。早期我看有些资料说M2可以改善带外抑制就一股脑把差分延迟都设成2结果通带边缘下垂严重补偿FIR的阶数从32阶飙到200多阶还压不平。后来对比了M1和M2在不同R下的频谱图才意识到M2只对特定的通带占比有用不能一概而论。第三个教训是仿真时记得过采样。有人直接用正弦波丢进CIC滤波器里看输出结果发现输出波形“很奇怪”其实是输入频率接近抽取后的奈奎斯特边界产生了混叠。CIC滤波器在抽取前相当于一个宽通带低通它不会帮你去掉通带内的信号所以输入信号本身要保证位于抽取后的有用带宽内否则混叠是正常的不是滤波器坏了。5.3 仿真验证的小技巧最后分享几个仿真验证的实操技巧。验证CIC滤波器是否正确的简单方法给输入一个单频正弦频率低于抽取后奈奎斯特频率的一半观察输出的幅度和相位是否合理。如果输入频率超出这个范围输出会出现混叠那是正常的物理现象别慌。更严谨的做法是使用扫频信号或线性调频信号chirp作为输入用spectrogram观察输出频谱。这样能直观看到滤波器在整个频带内的工作状态尤其是通带边缘的下垂和阻带混叠的情况。% 产生chirp信号测试 fs 40e6; t (0:40959)/fs; x chirp(t, 0, 40e-3, 5e6); % 0到5MHz扫频 y filter(Hcic, x); % 画输入输出频谱 figure; spectrogram(x, 256, 128, 256, fs, yaxis); title(输入信号频谱); figure; spectrogram(y, 256, 128, 256, fs/R, yaxis); title(CIC输出信号频谱);用chirp信号还有个好处就是能直观确认CIC的工作频率范围和混叠情况。如果你的CIC设计有问题在chirp时频谱图中很容易看到异常峰值或“折叠”现象。最后一点建议做定点仿真的时候把浮点CIC输出作为“黄金参考”逐点对比定点的误差。误差的均方根值要控制在信号有效位以下通常比信号峰值低60dB以上才算合格。如果误差偏大优先检查内部位宽是否足够其次再看截位策略。CIC滤波器虽老但它凭借零乘法器的天然优势在高速数据链路里活得非常好。搞懂它的原理、会用MATLAB做参数设计和定点验证你在做数字接收机、发射链路或者音频处理的时候会顺手很多。我个人的体会是凡是碰到“高采样率、大抽取比、资源紧张”这三个关键词同时出现别绕远路直接上CIC准没错。