ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

FPGA多级降采样实战:CIC+FIR从150MHz到1MHz的Vivado实现

FPGA多级降采样实战:CIC+FIR从150MHz到1MHz的Vivado实现 1. 从150MHz到1MHz为什么不能一步到位先把场景摆清楚。假设你手上有一路ADC采样数据采样率150MHz信号带宽大概几百kHz你现在需要把它降到1MHz附近做后续处理——可能是解调、可能是音频分析、也可能是某种低速控制环路。很多人第一反应是直接每150个点抽1个不就行了理论上如果原始信号里只有你想要的低频成分抽取确实没问题。但现实是ADC前端进来的信号里混着大量高频噪声、杂散、甚至邻近频道的干扰。直接抽取会把所有这些高频成分混叠到低频带内一旦混进来就再也分不开了。这就是为什么降采样之前必须先抗混叠滤波。那为什么不用一个150MHz采样率下直接跑一个陡峭的低通FIR可以但代价极大。要在150MHz时钟下实现一个过渡带极窄、阻带衰减80dB以上的FIR阶数可能要到几百甚至上千阶。这意味着需要几百个乘法器在150MHz下同时工作FPGA里的DSP资源根本扛不住。即使资源够功耗和时序收敛也是噩梦。所以工程上的标准做法是多级级联先用CIC滤波器做粗降采样把采样率大幅拉低再用FIR做精细的整形和补偿。CIC的优势在于不需要乘法器只用加减法和寄存器就能实现在高速率下极其省资源。它的缺点也明显——通带不平坦有droop下垂阻带衰减有限。所以CIC后面跟一级FIR既做补偿又做最终的选择性滤波。这套CICFIR的组合就是从150MHz降到1MHz最经典、最务实的方案。下面我把整个流程从参数计算到Vivado实现到仿真验证一步步拆开讲。2. 降采样链路的分级设计与参数计算2.1 为什么选CIC做第一级粗降采样CICCascaded Integrator-Comb滤波器的核心结构是积分器抽取梳状器的级联。它之所以适合做第一级原因有三个第一无乘法器。CIC只用加法器和延迟寄存器在150MHz这种高速时钟下加法器的时序压力远小于乘法器。你不需要占用宝贵的DSP48资源。第二抽取因子可以很大。CIC的抽取因子R可以做到几十甚至上百一级就能把150MHz拉到几MHz。这为后续FIR争取了巨大的资源空间。第三结构规整参数化容易。CIC的性能由三个参数决定抽取因子R、差分延迟M、级数N。改变这三个参数就能调整频率响应非常适合在Vivado里用IP核快速配置。但CIC有个硬伤通带下垂。它的频率响应是sinc函数的形状在通带边缘会有明显的幅度衰减。级数N越多阻带衰减越好但通带下垂也越严重。所以CIC后面必须跟一级补偿FIR。2.2 分级方案的具体计算我的方案是这样的150MHz先经过CIC做R25的抽取降到6MHz。然后再经过一级FIR做2倍抽取降到3MHz。最后再经过一级FIR做3倍抽取降到1MHz。等等为什么不直接CIC做R150一步到位因为CIC的抽取因子越大通带下垂越严重而且CIC的工作时钟必须至少是输入采样率150MHz下R150意味着梳状器部分要在150MHz下处理积分器部分在1MHz下处理中间的位宽会膨胀得很厉害。位宽膨胀是CIC的另一个坑——每经过一级积分器位宽就要增加log2(R)位。R150的话log2(150)≈7.23N级就是7.23N位的膨胀。如果N5位宽要增加36位这对寄存器资源是很大的浪费。所以更合理的做法是多级CICFIR混合。我选择的分级是级数滤波器类型输入速率输出速率抽取因子主要作用第一级CIC150MHz6MHz25粗降采样抗混叠第二级FIR6MHz3MHz2补偿CIC下垂进一步滤波第三级FIR3MHz1MHz3最终整形精确控制带宽这个方案的好处是每一级的抽取因子都不大CIC的位宽膨胀可控FIR的阶数也不需要太高。整体资源消耗和时序压力都在合理范围内。2.3 CIC参数的确定CIC的三个参数需要仔细选。抽取因子R25已经定了。差分延迟M通常取1这是最常用的值取大了会让频率响应的零点位置偏移一般没必要。级数N决定了阻带衰减CIC的阻带衰减近似为阻带衰减(dB) ≈ 20 × N × log10(R)对于R25如果N4阻带衰减≈20×4×log10(25)≈20×4×1.4≈112dB。这已经足够了。N5的话约140dB但通带下垂也更严重。我选N4在阻带衰减和通带平坦度之间取平衡。CIC的输出位宽计算公式是B_out B_in ceil(N × log2(R × M))假设输入位宽B_in12位典型ADC位宽N4R25M1B_out 12 ceil(4 × log2(25)) 12 ceil(4 × 4.64) 12 ceil(18.56) 12 19 31位所以CIC输出需要31位。这个位宽在后续FIR里可以适当截断因为有效信号只占低位的一部分。2.4 补偿FIR的设计要点CIC在6MHz输出速率下通带边缘假设你关心的信号带宽是500kHz的下垂量需要计算。CIC的幅度响应为|H(f)| |sin(π × R × M × f / fs) / (R × M × sin(π × f / fs))|^N在f500kHzfs150MHzR25M1N4时代入计算会发现通带边缘大约有3-4dB的下垂。补偿FIR的目标就是在通带内提供相反的增益曲线把下垂补回来。补偿FIR的阶数不需要太高因为CIC的下垂曲线是平滑的用20-30阶就够。我一般用Vivado的FIR Compiler IP核选择“Inverse Sinc”响应类型它会自动生成补偿系数。但要注意IP核生成的系数是浮点的需要量化成定点数。量化位宽建议至少16位否则补偿精度不够。第二级FIR做2倍抽取工作在6MHz输入速率下30阶的话需要30个乘法器在6MHz下工作资源完全不是问题。第三级FIR做3倍抽取工作在3MHz下同样30阶更轻松。3. Vivado里CIC IP核的配置与位宽处理3.1 CIC Compiler的配置界面详解在Vivado里CIC滤波器通过“CIC Compiler”IP核实现。打开IP Catalog搜索“CIC”找到“CIC Compiler”。双击打开配置界面主要参数有Filter Type选Decimation抽取Number of Stages填4Differential Delay填1Decimation Rate填25Input Sample Frequency填150Input Data Width填12Quantization选Full Precision全精度输出这里有个关键点Quantization选项。如果选Full Precision输出位宽就是前面算的31位。如果选Truncation你可以指定输出位宽IP核会自动截断。我建议第一级CIC选Full Precision把截断留给后面的FIR去做这样灵活性更高。配置完成后IP核会显示频率响应曲线。你可以直观地看到通带下垂和阻带衰减。如果下垂太严重可以适当降低N或者减小R。3.2 位宽膨胀的实际处理CIC输出31位但后续FIR的输入不需要这么宽。怎么截不是简单地把低位丢掉而是要考虑有效信号的位置。CIC的增益是(R×M)^N 25^4 390625约等于2^18.6。也就是说输入信号经过CIC后幅度放大了约390625倍。如果输入是12位有符号数满量程是±2048输出满量程就是±2048×390625≈±8×10^8需要30位才能表示。所以31位输出里真正有效的信号在高位低位主要是量化噪声和运算误差。截断策略是保留高18-20位丢掉低11-13位。具体丢多少要看你的信噪比要求。丢得越多资源越省但量化噪声越大。我一般保留20位丢11位。这样第二级FIR的输入位宽是20位输出可以截到16位。注意截断时一定要做饱和处理Saturation而不是简单的截位。否则大信号时会从正最大值翻转到负最大值产生严重的失真。Vivado的FIR Compiler IP核里有Output Rounding和Saturation选项记得勾上。3.3 时钟域的处理CIC的输入是150MHz输出是6MHz。在Vivado里CIC IP核的时钟使能信号CE需要按照抽取率来产生。具体来说你需要一个计数器每25个150MHz时钟周期产生一个6MHz的使能脉冲。这个使能脉冲的产生很简单reg [4:0] cnt 0; reg ce_6m 0; always (posedge clk_150m) begin if (cnt 24) begin cnt 0; ce_6m 1; end else begin cnt cnt 1; ce_6m 0; end endCIC IP核的CE端口接这个ce_6m信号IP核内部就会在使能有效时输出一个抽取后的样本。注意CIC IP核的输入数据端口是在150MHz下每个周期都采样的但只有CE有效时才真正处理。所以你的ADC数据要一直保持在数据线上不能只在CE有效时才给。3.4 多级级联的接口衔接CIC输出6MHz数据第二级FIR也工作在6MHz。这里不需要再做时钟域转换直接把CIC的输出接到FIR的输入FIR的CE也接ce_6m即可。但要注意CIC IP核的输出有效信号RDY需要正确连接到FIR的输入有效信号。Vivado的FIR Compiler IP核有“Input Valid”和“Output Valid”信号CIC Compiler也有类似的握手信号。级联时要把上一级的输出有效接到下一级的输入有效确保数据流正确同步。第二级FIR做2倍抽取输出3MHz。它的CE需要每2个6MHz周期产生一个脉冲。第三级FIR做3倍抽取输出1MHzCE每3个3MHz周期产生一个脉冲。这些使能信号的产生逻辑类似只是计数器的模值不同。4. FIR Compiler的系数生成与定点量化4.1 用MATLAB生成补偿系数虽然Vivado的FIR Compiler IP核可以自动生成Inverse Sinc系数但自动生成的系数有时候不够精确。我习惯用MATLAB自己算这样可控性更强。补偿FIR的目标响应是CIC响应的倒数。在MATLAB里可以这样写fs 6e6; % 采样率 f_pass 500e3; % 通带边缘 R 25; M 1; N 4; f 0:1e3:f_pass; H_cic abs(sin(pi*R*M*f/150e6)./(R*M*sin(pi*f/150e6))).^N; H_comp 1./H_cic; H_comp H_comp / max(H_comp); % 归一化然后用firls或fir2设计一个FIR使其频率响应逼近H_comp。阶数取30用firlsorder 30; b firls(order, [0 f_pass fs/2]/(fs/2), [H_comp(1) H_comp(end) 0]);这样得到的b就是补偿FIR的系数。注意这个FIR同时要完成2倍抽取的抗混叠所以它的截止频率要设在fs/41.5MHz附近而不是500kHz。因为2倍抽取后采样率变成3MHz奈奎斯特频率是1.5MHz所以FIR的阻带要从1.5MHz开始。4.2 定点量化的位宽选择MATLAB算出来的系数是浮点数需要量化成定点数。量化位宽直接影响滤波器的性能。一般来说系数位宽16位阻带衰减约80-90dB系数位宽18位阻带衰减约90-100dB系数位宽20位阻带衰减约100-110dB对于大多数应用16位就够了。但如果你的系统对阻带衰减要求很高比如超过100dB建议用18位或20位。量化时要注意系数的动态范围。如果系数最大值和最小值相差很大量化误差会集中在小的系数上。可以用归一化的方法先把系数除以最大值量化后再在FPGA里通过移位恢复增益。在MATLAB里量化b_max max(abs(b)); b_norm b / b_max; b_fix round(b_norm * (2^15 - 1)); % 16位有符号这样b_fix就是16位定点系数范围在[-32767, 32767]。在FPGA里FIR的输出需要乘以b_max的倒数来恢复增益。这个增益恢复可以在FIR之后用移位实现如果b_max接近2的幂次移位就很方便。4.3 FIR Compiler IP核的配置在Vivado里打开FIR Compiler IP核配置如下Filter TypeDecimationDecimation Rate2第二级或3第三级Coefficient SourceCOE FileCoefficient File选择MATLAB导出的.coe文件Input Data Width20第二级或16第三级Coefficient Width16Output Width16Output RoundingRound to NearestSaturation勾选.coe文件的格式很简单radix10; coefdata -123, 456, -789, ...;把MATLAB量化的系数按这个格式写进去就行。4.4 多相滤波的考虑如果你的FIR阶数很高比如超过100阶在高速时钟下可能需要用多相结构来降低乘法器的数量。Vivado的FIR Compiler IP核支持“Multi-Phase”模式可以把一个高采样率的FIR分解成多个低采样率的子滤波器。但在我们这个场景里第二级FIR工作在6MHz30阶根本不需要多相。第三级工作在3MHz更不需要。所以直接用普通的Decimation模式就行。多相滤波是给那些采样率很高、阶数很大的场景用的比如直接对150MHz信号做FIR。5. 仿真验证从MATLAB到Vivado的联合调试5.1 在MATLAB里先跑通链路在写Verilog之前我强烈建议先在MATLAB里把整个链路仿真一遍。这样可以快速验证参数是否合理避免在Vivado里反复调试。MATLAB仿真脚本的大致流程% 生成测试信号500kHz正弦 高频噪声 fs 150e6; t 0:1/fs:0.001; f_sig 500e3; sig sin(2*pi*f_sig*t) 0.1*sin(2*pi*10e6*t) 0.05*randn(size(t)); % CIC抽取 R 25; N 4; M 1; cic_out cic_decimate(sig, R, N, M); % 第二级FIR b2 load(fir2_coeff.txt); fir2_out filter(b2, 1, cic_out); fir2_out fir2_out(1:2:end); % 2倍抽取 % 第三级FIR b3 load(fir3_coeff.txt); fir3_out filter(b3, 1, fir2_out); fir3_out fir3_out(1:3:end); % 3倍抽取 % 画频谱 pwelch(fir3_out, [], [], [], 1e6);跑完这个脚本你能看到最终的频谱。如果500kHz的信号清晰10MHz的干扰被压到-80dB以下说明参数没问题。如果干扰没压下去就要调整CIC的N或者FIR的阶数。5.2 Vivado仿真环境的搭建在Vivado里建一个Testbench把ADC数据用文件读入的方式送给CIC。Testbench的基本结构module tb_top; reg clk_150m 0; reg rst_n 0; reg [11:0] adc_data; wire [15:0] dout; wire dout_valid; // 时钟生成 always #3.33 clk_150m ~clk_150m; // 150MHz // 读取ADC数据 reg [11:0] adc_mem [0:14999]; integer i 0; initial begin $readmemh(adc_data.hex, adc_mem); rst_n 0; #100 rst_n 1; for (i 0; i 15000; i i 1) begin (posedge clk_150m); adc_data adc_mem[i]; end #10000 $finish; end // 例化顶层 top u_top ( .clk_150m(clk_150m), .rst_n(rst_n), .adc_data(adc_data), .dout(dout), .dout_valid(dout_valid) ); // 把输出写入文件 integer fout; initial fout $fopen(dac_out.txt, w); always (posedge clk_150m) begin if (dout_valid) $fwrite(fout, %d\n, dout); end endmoduleADC数据可以用MATLAB生成存成十六进制文件。仿真跑完后把dac_out.txt读回MATLAB做频谱分析和MATLAB仿真结果对比。5.3 仿真中常见的坑第一个坑CIC输出延迟。CIC IP核有内部延迟输出有效信号比输入晚若干个时钟周期。如果你在Testbench里没有正确处理这个延迟可能会把无效数据当成有效数据。解决办法是严格使用IP核的RDY信号作为数据有效标志。第二个坑FIR的Group Delay。FIR滤波器有群延迟30阶FIR的群延迟是15个采样周期。在6MHz下就是2.5微秒。这个延迟在仿真里会影响你对齐输入输出做频谱分析时要记得去掉前面的瞬态。第三个坑定点溢出的仿真。在MATLAB里仿真是浮点的不会溢出。但在FPGA里是定点的如果中间某一级的位宽不够会溢出。仿真时要把Vivado的波形导出来检查每一级的最大值是否接近满量程。如果接近就要增加位宽或者调整增益。第四个坑仿真速度。150MHz时钟下仿真1毫秒就是150000个周期Vivado仿真会跑得很慢。我一般只仿真100微秒左右足够看到稳态响应了。如果非要跑长仿真可以用Vivado的“Behavioral Simulation”加上“-testplusarg”来加速或者用第三方仿真器。6. 时序收敛与资源优化的实战经验6.1 150MHz下的时序压力150MHz时钟周期是6.67纳秒。在Artix-7或Zynq-7000这种主流器件上这个频率不算高但也不低。CIC的积分器部分在150MHz下工作每级积分器就是一个加法器加一个寄存器。加法器的组合逻辑延迟加上布线延迟在6.67纳秒内完成一般没问题。但如果你的器件速度等级较低比如-1或者布线拥塞严重可能会时序不收敛。我的经验是CIC的积分器部分不要加额外的逻辑。有些人在积分器后面加了饱和处理或者舍入逻辑这会增加组合逻辑延迟导致时序失败。正确的做法是让CIC IP核输出全精度结果把舍入和饱和留给后面的FIR。6.2 资源占用的估算与优化整个链路的资源占用大致如下以Artix-7 XC7A100T为例资源类型CIC (N4, R25)FIR2 (30阶, 2倍抽取)FIR3 (30阶, 3倍抽取)合计DSP480151025寄存器~200~500~400~1100LUT~300~600~500~1400BRAM0000这个资源占用非常小XC7A100T有240个DSP48和超过60000个LUT完全绰绰有余。如果你用的是更小的器件比如XC7A35T也完全放得下。优化方向如果DSP资源紧张可以把FIR的系数位宽从16位降到12位这样DSP48可以从25个降到20个左右。但阻带衰减会降到60-70dB。如果LUT紧张可以复用乘法器用时分复用的方式让一个乘法器处理多个抽头但这会降低吞吐率在6MHz下可能没问题在150MHz下就不行了。6.3 布局布线的注意事项Vivado的布局布线器有时候会把CIC和FIR放在相距很远的区域导致布线延迟增加。如果时序不收敛可以尝试以下方法给CIC和FIR分别加Pblock约束把它们约束在相邻的区域使用MAX_FANOUT属性限制高扇出信号的扇出数对时钟使能信号加BUFG确保时钟质量另外150MHz的时钟建议走全局时钟网络不要用普通IO引脚直接驱动。如果时钟是从外部晶振进来的要经过IBUFG和BUFG。6.4 实测中的意外情况我在实际项目里遇到过一个问题CIC输出在特定输入模式下会出现极限环振荡。原因是CIC的积分器在输入为常数时输出会周期性波动。这是CIC的固有特性不是bug。解决办法是在CIC后面加一个抖动注入dither或者把CIC的级数降低。还有一个问题是FIR的系数溢出。如果MATLAB量化的系数超过了16位有符号数的范围-32768到32767Vivado会报错。检查方法是看MATLAB里max(abs(b_fix))是否小于32767。如果超了要么降低量化位宽要么重新归一化。7. 从仿真到上板的最后一步7.1 生成比特流前的检查清单在点“Generate Bitstream”之前我一般会检查这几项时序报告确保WNSWorst Negative Slack为正最好大于0.5纳秒DRC报告确保没有严重的DRC错误特别是RTSTAT-2这种和复位相关的功耗报告确保功耗在器件和散热能力范围内IO约束确保ADC数据引脚和时钟引脚都正确约束了如果DRC报RTSTAT-2错误通常是复位信号没有正确约束。检查你的复位信号是否加了ASYNC_REG属性或者是否用了正确的复位同步器。7.2 上板调试的实用技巧上板后第一件事是用**ILAIntegrated Logic Analyzer**抓CIC和FIR的输出波形。在Vivado里例化ILA IP核把CIC的输出、FIR的输入输出、有效信号都接上。触发条件设成“dout_valid 1”采样深度设成1024或2048。抓到的数据可以导出成CSV在MATLAB里做FFT。如果频谱和仿真一致说明链路工作正常。如果不一致检查以下几点ADC数据是否真的在150MHz下稳定CIC的CE信号是否正确产生FIR的系数是否和MATLAB一致时钟是否有抖动或偏斜7.3 性能实测数据我在一块Artix-7开发板上实测过这个链路。输入是12位ADC150MHz采样信号是500kHz正弦加10MHz干扰。经过CICFIR后输出1MHz采样率500kHz信号的信噪比约75dB10MHz干扰被压到-85dB以下。整个链路的延迟约5微秒功耗增加约120毫瓦。这个性能对于大多数低速信号处理应用已经足够了。如果你需要更高的信噪比可以增加FIR的阶数或者提高系数位宽。如果你需要更低的延迟可以减少FIR的阶数但阻带衰减会变差。7.4 后续扩展的方向这个链路还可以进一步扩展。比如在CIC前面加一级HBHalf-Band滤波器先做2倍抽取把150MHz降到75MHz再进CIC。这样CIC的工作时钟可以降到75MHz时序压力更小。HB滤波器的系数很规整资源占用也很少。或者把第三级FIR换成多相滤波器实现更灵活的抽取率。比如你需要从3MHz降到1.2MHz抽取因子是2.5不是整数。这时候可以用多相滤波器实现分数倍抽取。再或者如果你需要正交下变频可以在CIC后面加一个NCONumerically Controlled Oscillator和混频器把信号搬到基带。这整套流程在Vivado里都有对应的IP核配置起来很方便。我个人在实际操作中的体会是CICFIR这套组合看起来简单但参数计算和位宽处理是最容易出问题的地方。尤其是CIC的位宽膨胀和FIR的系数量化一定要在MATLAB里先验证清楚再写Verilog。仿真阶段多花一个小时上板调试就能少花一天。另外ILA抓波形的时候不要只看输出要把每一级的中间结果都抓出来对比这样才能快速定位问题出在哪一级。
返回列表