做FPGA信号处理的工程师,迟早会碰到一类需求:系统里不同模块跑在不同的采样率上。比如ADC进来是245.76M的采样流,DDC之后数据率降到几十M,再接均衡、AGC、解调,每一级的速率都不一样。这种**多速率系统(Multi-Rate Systems)**用传统RTL硬写不是不行,但改参数、调结构、做仿真验证,来回折腾的功夫足够让人怀疑人生。Xilinx System Generator(SysGen)把DSP设计搬进了Simulink的图形化环境,抽取、插值、多相滤波这些多速率处理都能拖拽成模型,仿真波形直接看,最后还能打包成IP核扔进Vivado做时序收敛和板级验证。这篇文章我就从原理到实操,把System Generator里做多速率系统这套流程完整拆一遍。
1. 多速率信号处理到底在解决什么问题
1.1 为什么系统会存在多速率
通信和雷达系统里,不同处理环节对采样率的需求差别很大。ADC的采样率往往由中频频率和信号带宽决定,比如LTE基站里常见的122.88M采样率,是为了覆盖20M带宽的载波信号。但基带端的符号速率通常只有几十M甚至几M,如果整条链路都跑在122.88M上,后面的均衡器、定时同步、译码器全是白算的:没用的样本点也在消耗DSP资源、寄存器和动态功耗。
所以处理思路就一句话:能用低速率处理的部分,就不要让数据在高速率里空转。在ADC之后立刻做数字下变频,把数据率一步一步降下来,直到基带处理能够承受的程度。反过来,发射链路需要把基带的低速率信号逐步内插到DAC的采样率,这就是数字上变频。这一降一升的过程,就是多速率信号处理的核心场景。
SysGen做这类系统有天然优势。它本身就是Simulink的一个工具箱,Simulink模型天生有采样时间的概念,每个信号都可以带不同的采样率。SysGen在此基础上把Xilinx的DSP硬核——DSP48、Block RAM、CIC硬核——封装成了一个一个的模块,你在模型里拖出来的滤波器,生成RTL以后对应的就是FPGA里真实的逻辑资源,不是黑箱仿真。
1.2 多速率建模的核心难点
很多人第一次用SysGen做多速率就卡在同一个地方:Simulink模型跑起来了,波形也出来了,但结果跟理论对不上,或者干脆仿真挂起。问题多半出在你没搞明白SysGen的速率机制和Simulink的速率机制是两套东西。
Simulink本身是多速率求解器,它允许不同的子系统跑不同的采样时间,系统会自动调度。但SysGen生成的硬件逻辑是单一时钟域的(除非你显式用多时钟模块),FPGA里所有逻辑跑在同一个时钟上,多速率是靠每N个周期有效一次的方式实现的,对应的就是SysGen里的Sample Rate、采样周期倍数这些参数。
换句话说,你在SysGen里看到的多速率,本质上是单一时钟下的门控时钟使能(clock enable)机制。这个理解很关键,因为你在Simulink里可以随便连不同速率的信号,但到了硬件里,每一条链路都得有明确的速率转换关系。SysGen在生成RTL之前会做一次全模型的速率检查,发现速率不匹配会直接报错。
另外,SysGen的仿真时间是基于FPGA时钟周期的,模型里设一个20ns的时钟周期,Simulink的仿真步长就跟这个绑定。你在SysGen token里设的FPGA采样时间,和Simulink里各个模块的采样时间,必须构成一个严格的倍数关系,否则仿真结果就是错的。
2. 动手前先搞懂速率转换的数学本质
2.1 抽取、插值与频谱的关系
多速率系统绕不开两个基本操作:抽取(decimation,也叫下采样)和插值(interpolation,也叫上采样)。
抽取就是在连续M个样本里取一个,数据率降为原来的M分之一。这件事在时域看是"丢点",在频域看是频谱被展宽了M倍。原来在-midfs到fs/2范围内的频谱,抽取后被压缩到新的奈奎斯特区间时,会把带外的信号折叠到带内——这就是混叠(aliasing)。所以抽取之前必须先经过一个低通滤波器,把带外信号滤干净,这个滤波器叫抗混叠滤波器。
插值则相反,在两个真实样本之间插入M-1个零,数据率变成原来的M倍。时域上插零并不改变原始信号形状,但频域上会产生一串镜像频谱,以新的采样率为周期重复。这些镜像必须用低通滤波器滤掉,叫镜像抑制滤波器。
2.2 多相分解是效率的关键
抽取和插值其实都能用滤波器实现,但如果你直接在FPGA里把滤波器放在抽取/插值之前,那滤波器仍然跑在高速率上,等于多速率省白省。真正的效率提升来自多相分解(polyphase decomposition)。
多相分解的基本思路是把一个低通滤波器的系数按相位分成M组,每组系数构成一个子滤波器。抽取的时候,你只需要在M个相位分支上各放一个子滤波器,每个子滤波器都跑在低速率上,运算量直接降到原来的M分之一。CIC、半带、FIR抽取/插值器,硬件实现上都离不开多相分解。
SysGen里这些你不用自己写,模块都封装好了。但你要懂背后的逻辑,才能理解为什么FIR编译器里Decimation Rate=4的时候,内部实际计算量比单速率模式小得多,也才能理解为什么CIC Compiler级数设置不同,输出位宽会差那么多。
2.3 SysGen多速率模块对照
SysGen提供的多速率模块我整理了一个对照表,方便按场景选型:
| 模块 | 速率转换类型 | 适用场景 | 资源消耗 |
|---|---|---|---|
Down Sample | 直接抽取 | 快速验证、整数倍抽取 | 极低 |
Up Sample | 直接插零 | 快速验证、整数倍插值 | 极低 |
FIR Compiler | 单速率/抽取/插值可配 | 中低倍数抽取插值、通道滤波 | 使用DSP48,系数可配 |
CIC Compiler | 高倍数抽取/插值 | DDC/DUC前端、带宽较宽的信号 | 无乘法器,加法器为主 |
CIC补偿滤波器 | 与CIC搭配 | CIC通带下垂补偿 | DSP48 |
FIR Decimation | 抽取滤波器 | 低延迟、简单用途 | 中等 |
FIR Interpolation | 插值滤波器 | 低延迟、简单用途 | 中等 |
Polyphase Filter Bank | 信道化 | 多通道DFT滤波器组 | 高 |
选型建议就一条经验:CIC适合高倍数转换,因为不需要乘法器,但通带会有下垂,后面必须补补偿滤波器;FIR Compiler适合中低倍数的精细滤波,频率响应好控制。实际DDC链路里,高倍数抽取用CIC,低倍数精调再用FIR,这是最常见的组合。
3. 完整实操:DDC多速率链路设计
3.1 设计指标与链路规划
我拿一个典型的中频数字化接收机DDC链路来演示。假设ADC采样率是122.88M,中频频率30.72M,信号带宽10M,基带处理需要的符号速率是15.36M。设计目标:把122.88M的ADC数据,经过混频、抽取、滤波,最终输出15.36M的IQ基带信号。
链路规划如下:
- 第一级:NCO混频(DDS Compiler),把30.72M中频搬移到基带;
- 第二级:CIC抽取滤波器,4倍抽取,把122.88M降到30.72M;
- 第三级:CIC补偿FIR + 半带FIR,2倍抽取,把30.72M降到15.36M;
- 第四级:Convert位宽转换,输出定点IQ数据。
总抽取倍数是4×2=8,输出速率122.88/8=15.36M,满足基带处理需求。
3.2 参数计算的完整推导
CIC抽取器参数:CIC的增益峰值可以用公式估算,级数N、微分延迟M、抽取倍数R的情况下,最大增益是(R×M)^N。本设计R=4,M=1,N=4,最大增益4^4=256,换算成功率增益约48dB。CIC输出位宽B_out = B_in + ceil(N×log2(R×M)),如果输入16位,输出就是16 + ceil(4×2)=24位。SysGen的CIC Compiler模块会自动算好位宽,但你要明白这个数是怎么来的,才能判断自动计算结果是否合理。
这里有一个我在实际项目里踩过的坑:CIC的高增益不止体现在通带,处理宽带信号时带外噪声会被一起放大。CIC后端如果不加AGC或者增益控制,很容易把后级FIR打满。所以CIC的输出不要直接进FIR,建议先给一个Scale或者用Convert截位,保证进入FIR的数据在合理动态范围内。
半带FIR参数:半带滤波器有个特性,通带和阻带关于fs/4对称,所以只能做2倍抽取/插值。它的运算量比普通FIR省一半,因为一半系数是0。本设计用半带完成最后一级2倍抽取,系数阶数我选了32阶,过渡带从4M到5.5M。SysGen里可以直接调用fdatool生成系数,然后再通过FDATool模块导入,也可以在MATLAB里用firhalfband函数直接生成,然后填到FIR Compiler的系数向量里。
NCO参数:DDS Compiler里设置Phase Width=32位,输出位宽16位,频率字计算方式是2^32×30.72/122.88 = 2^31,正好是半个量程,对应fs/4的频率。这里从数学上看是干净的信号,但实际中频如果不在fs/4,相位累加器会引入杂散,需要根据SFDR要求选择抖动(dithering)配置。
3.3 Simulink模型搭建逐步拆解
打开Simulink新建模型,先拖一个System Generatortoken进来,双击配置:目标器件选你板卡上的型号,比如Kintex-7 325T;FPGA时钟周期设成8.138ns(122.88M的倒数);硬件描述语言选Verilog;采样周期保持默认。注意这个FPGA时钟周期,后面所有模块的采样时间都跟它挂钩。
模型整体分三段。第一段用DDS Compiler产生正交本振信号,输出接两个乘法器,把输入的实中频信号和cos/sin相乘,得到正交混频后的I、Q两路。混频输出Q路会有2倍频分量,按道理要先滤一下,但因为我们紧接着就接CIC抽取,CIC的前置抗混叠滤波器会一并处理高频分量,链路可以简化。
第二段接CIC Compiler,双击进去设置:滤波类型选Interpolation还是Decimation,这里选Decimation;抽取因子填4;差分延迟填1;级数填4(CIC的级数决定阻带衰减,级数越高阻带衰减越大,但通带下垂也越严重)。输入端类型选有符号定点,输入位宽16位。输出端会自动算出25位(16+ceil(log2(4^4))=16+8=24? 我前面推的是24,SysGen实际计算可能多1位的无符号进位,以实际模型显示为准)。
第三段接FIR Compiler。双击进入之后,在Filter Type里选Half-band Decimation,抽取因子设2,系数来自firhalfband(32,0.28)生成的向量。采样时间方面的配置要特别注意:CIC输出端的采样时间是122.88M/4=30.72M,在SysGen里表现为采样周期倍数是4;FIR输入端采样时间必须与CIC输出端一致,而在FIR内部配置的抽取因子会让输出端自动变成采样周期倍数8,对应15.36M。
最后接Convert模块,把FIR输出位宽截位到16位。后续如果要对接外部接口,可以再接一个Gateway Out,它会生成一个FPGA引脚对应的输出信号。整个模型里各个模块的采样时间倍数分别是:混频前1(122.88M),CIC后4(30.72M),FIR后8(15.36M)。你可以双击任意信号线,在Simulink的格式设置里查看采样时间,确认链路速率关系正确。
3.4 仿真验证与频谱分析
仿真前先检查一下求解器设置,这点我每次都要提醒:Simulink配置参数里,求解器必须选Fixed-step、discrete,步长跟System Generator token里的FPGA时钟周期一致。如果选了可变步长求解器,SysGen会警告,而且仿真结果大概率不对。
激励源的生成有个小技巧。用SysGen自带的Signal Generator不行,因为它输出的不是定点数。正确做法是用MATLAB工作区生成一个包含中频信号的激励数组,比如:
fs = 122.88e6; t = (0:4096-1).'/fs; % 构造中频30.72M信号,用double表示,幅度控制在±0.5 sig = 0.5 * cos(2*pi*30.72e6*t + pi/4);然后用From Workspace模块导入,接一个Gateway In转换成定点。这样可以直接复用MATLAB里做好的仿真数据,后面做硬件在环(HIL)或者上板实测的时候,激励也可以保持一致。
跑完仿真以后,在FIR输出端接一个Scope,同时把数据导回MATLAB做FFT分析。验证点有三个:
- 输出信号频率应该是0附近(基带),峰值幅度在输入信号幅度折算后基本合理;
- 30.72M中频信号混频到基带后,如果CIC和FIR链路设计正确,输出频谱的主峰干净,没有明显镜像分量。镜像分量出现在输出频谱的非零频处,如果太大说明滤波器的阻带衰减不够;
- IQ两路幅度一致性要好,差太多说明混频或滤波器链路的位宽截断有问题。
数值上可以用max(abs(fft( ... )))看主峰,用20*log10(max(abs(fft(...))) / max(abs(fft(...,'tail'))))估算信噪比。实测链路信噪比如果低于60dB,先排查位宽截断是否过于激进,再看滤波器系数是否需要提高阶数。
4. SysGen开发的深水区:时钟域、流水线与硬件部署
4.1 采样率标注机制与硬件行为
SysGen的采样率标注机制,我花了好长时间才完全搞明白。Simulink对信号线的采样时间是有自动推断的,你连一条线,如果上游是1(对应122.88M),下游模块期望的输入采样时间是4(对应30.72M),Simulink会报错。然而SysGen的模块特殊性在于,它们都支持Sample Rate参数,模块本身往往会声明"我接受采样时间倍数为N的输入",并输出"采样时间倍数为M"的结果。
这个机制是好事也是坑。好处是你不必手动在每个模块里指定采样时间,Simulink会自动传播;坏处是,如果你连错了模块,比如把CIC的输入接到了FIR Compiler采样时间倍数为4的输出端口,SysGen会报"sample time mismatch"错误。排查方法是双击报错的信号线,看它的采样时间标注,再反向追踪上游模块的输出设置。
有一个更隐蔽的问题:Down Sample模块和CIC Compiler都把速率降下来了,但它们的硬件行为不同。Down Sample只是简单的数据选择器,它不包含抗混叠滤波,你要自己保证输入信号带宽已经满足抽取后的奈奎斯特定理;而CIC Compiler内部自带滤波级联,它输出的数据已经过滤掉了抽取产生的镜像。所以在模型设计时,不要把这两类模块混用而不加滤波器。
4.2 插入流水线:从仿真正确到时序收敛
SysGen模型仿真正确,不代表生成的RTL在FPGA上一定能跑到目标时钟频率。原因是Simulink模型里模块之间的组合逻辑延迟是"零",但FPGA上的路径延迟是真实的。特别是多速率系统中,数据率高的部分路径较长,组合逻辑层级多,很容易出现时序不收敛。
解决办法是在关键路径上插入Delay模块,这个操作也叫流水线化。SysGen里直接拖Delay模块(Xilinx Block集里有),双击设置Latency,往上加1、2个周期。插入Delay后,模型的功能不会变(因为DSP链路是线性时不变的),但会破坏模块间的时序依赖,帮助综合工具做时序收敛。
我的习惯是这样:先在Simulink里用Resource Estimator看一下每条链路用了多少LUT、DSP48和BRAM,如果DSP48用得很多,或者LUT逻辑层级很深,就在乘法器前后各插一个Delay。插完后重新跑一遍仿真,确认没有引入位宽变化或者数据对齐问题——Delay如果插在了IQ两路中的一路,另一路也要对应插相同的延迟,否则IQ相位会错开。SysGen有一个FDATool模块不能插Delay,但FIR Compiler内部有Output Latency参数,可以直接调,不用额外插外部模块。
4.3 生成IP核并集成到Vivado
SysGen设计完成的最终产物是IP核。在System Generator token里点Generate,它会生成对应的Vivado IP工程文件(.xci或.xpr),包含RTL代码、约束文件和仿真模型。生成之后,在Vivado的IP Catalog里能找到这个自定义IP,可以像使用官方IP一样把它例化到工程里。
这里有个要点:SysGen生成的IP核默认没有AXI接口,只是一个纯组合/时序逻辑模块,输入输出对应模型里的Gateway In和Gateway Out。如果你想挂到Zynq的AXI总线上,要么自己在模型里加AXI4-Stream接口模块,要么在Vivado里包一层AXI外设封装。SysGen新版提供了AXI4-Stream Interface相关的模块,可以直接拖到模型里作为输入输出端口。
集成进Vivado以后,记得把SysGen生成的约束文件里关于时钟的约束检查一遍。SysGen默认假定只有一个时钟,也就是你mmodel里设置的FPGA时钟。如果你的设计里有多时钟域,SysGen不会自动处理跨时钟域逻辑,你需要自己在Vivado里添加异步FIFO或者XPM_CDC原语。不要图省事直接用两个时钟驱动SysGen IP的端口,跑上板测试的时候数据会偶发错乱,排查起来极其折磨。
板级调试还有一个容易忽略的问题:下载器和驱动。Xilinx的Platform Cable USB在Windows上偶尔会出现"无法加载设备的驱动"这个经典报错,表现为设备管理器里一个带黄色感叹号的未知设备。这通常不是下载器坏了,而是驱动版本没配对,或者之前装过旧版ISE/Vivado留下了残留驱动。解决方法是卸载旧驱动后,安装当前Vivado自带的Cable Drivers,安装完再重插USB,注意插拔顺序:先装驱动,再插cable,然后打开Vivado Hardware Manager。这个顺序反了,照样可能识别不到。
5. 多速率建模的典型坑与调试心得
5.1 仿真挂起、速度慢
Simulink模型点运行以后,仿真卡在一个时间点不动,或者慢到像死机。这是多速率SysGen最常见的问题,原因大概率是求解器配置错误。SysGen模型必须用固定步长离散求解器,步长等于FPGA时钟周期。如果你用auto求解器,Simulink会在连续/离散之间反复切换,采样时间推断也会混乱,仿真直接没法收敛。
另外一个原因是模型里有无穷循环或代数环,Simulink会为了求解代数环反复迭代。SysGen生成的DSP链路理论上不会有代数环,但如果你接了From Workspace再通过Gateway Out回环到输入,就会造成组合逻辑自环,仿真就会挂起。解决办法是给回环路径加一个Delay(哪怕延迟1拍),破坏组合环。
仿真耗时太长的另一个大坑是仿真步长太细。SysGen模型里FPGA时钟周期设成1ns的话,仿真1ms需要100万个步长。实际调试时,我习惯在System Generator token里临时把时钟周期改大一点,比如从8.138ns改成100ns,这样仿真速度能快接近10倍。注意,这么做之后模型里的速率关系不变(因为所有模块的采样时间都基于同一个时钟周期),但仿真时数据有效节奏会变慢,适合功能验证,不适合时序性能评估。
5.2 数据结果与理论不一致
如果你确认滤波器系数和结构都没错,但仿真输出频谱跟理论对不上,优先检查位宽截断。多速率链路里每一级数据位宽都在变化,CIC输出宽,FIR输出宽,到了Convert截位,截断方式直接决定信噪比。SysGen的Convert模块有Quantization设置,Truncate是直接截断,Round是四舍五入。FPGA上Round比Truncate多消耗一点点逻辑,但信噪比好很多,一般建议用Round加Saturate,防止溢出产生大的毛刺。
如果你导出的定点数和MATLAB浮点仿真对不上,先确认定点格式是不是选对了。整数位宽不够,信号一满量程就溢出了;小数位宽太宽,动态范围就窄了。SysGen里有Hardware Assistant,会自动检测数据范围,提示你哪些信号有溢出风险,这个功能一定要开。开的位置在System Generator token的Advanced选项卡里。
5.3 FIR编译器多速率配置的几个雷
FIR Compiler是SysGen里用得多也最容易出问题的模块。配置抽取/插值模式时,有两个地方特别容易犯错。
第一,Filter Type选Decimation后,下面还有一个Decimation Rate,如果你写的是4,模块输出端的数据率是输入端的1/4,但滤波器计算频率是不变的。如果滤波器的阶数很高,输出端会出现数据来不及算的情况,SysGen会插入pipeline stall,输出端会多出一个rdy信号,指示数据什么时候有效。硬件上,这个rdy信号很重要,你后续模块必须等待rdy有效才采样数据。好多人在验证时只接数据线,不接rdy,出来的数全是乱的。
第二,滤波器系数导入的格式。SysGen的FIR Compiler要求系数是向量形式,而且必须是行向量,不能用列向量。有时候你在MATLAB里用fir1生成的是行向量,没问题;但如果你从Excel导入系数,或者用字符串拼接,很容易变成列向量,SysGen不会报错,但滤波器会全部变成0。排查方法是在工作区里用isa(coeff,'double')检查类型,用size(coeff)确认维度。
5.4 硬件验证与仿真结果不一致
上板以后,实测数据跟仿真对不上是另一个高发问题。先别急着怀疑SysGen的模型,大概率是下面几种情况之一:
- 没有等时钟稳定就释放复位。SysGen的IP复位是高电平有效还是低电平有效,在生成的时候可以选。如果复位极性配错了,整个链路可能会一直处于复位状态,输出自然是0。
- 输入数据的对齐问题。SysGen生成的IP如果输入是并行多路(比如4路122.88M合并成1路491.52M),那数据是按什么顺序排列的,必须严格参照生成的RTL代码里的注释。在Vivado仿真里先跑一遍生成的网表,确认输入输出时序关系,再上板接真实数据。
- 时钟使能信号没有正确连接。多速率IP内部是靠时钟使能来控制数据有效率的,如果你在Vivado里把时钟使能引脚悬空或者接成了恒定高电平,所有数据都会变成每个时钟都有效,速率关系直接被破坏,输出频谱自然错乱。查这个问题的时候,直接在Vivado里找到IP实例,看
ce信号的来源,多半能找到根源。
调试强烈建议用Vivado的ILA插入几个内部信号,同时抓混频前和CIC后的数据。抓下来以后,把ILA导出的数据在MATLAB里做个FFT,和Simulink仿真波形放在一起对比,如果频谱特征一致,说明链路基本对了,只是后面某个环节处理不当;如果完全不一样,优先排查时钟和复位配置,再回头检查采样时间设置。
5.5 系统级选型的一些参考
最后延伸一个话题。多速率系统的模块选型和FPGA选型是有强关联的。同样是做4倍抽取,CIC方案几乎不吃DSP48,但速率很高的时候对逻辑资源(加法器)的需求会涨;多相FIR方案消耗DSP48,但对逻辑资源友好。选型的时候建议查一下手头芯片的资源表,再掂量你的设计里DSP48数量够不够。Xilinx的选型手册里,每个型号的DSP48、BRAM和LUT数量都有明确标注,DDC这种链路建议先粗算DSP48用量再选型。还有一个容易忽略的点:Zynq平台做多速率系统时,片内PS侧和PL侧的接口速率也是瓶颈,PL侧的AXI-Stream接口位宽和时钟决定了数据吞吐上限,这块要记得留充裕余量,不要卡着理论极限选型。
我个人在实际操作里的体会是,SysGen的多速率建模能力很强,但它要求你对两件事有清晰的认识:一是信号处理的基本原理,抽取插值背后的频谱变化,二是FPGA底层的实现机制,时钟使能、流水线、位宽截断。这两块一旦通了,做多速率系统基本就是张飞吃豆芽。如果只把SysGen当作一个自动生成代码的黑盒子,遇到问题就只能瞎试,效率反而不如直接用Verilog写。最后再分享一个小技巧:每次跑仿真前,把System Generator token里的Simulation Mode设置成Hardware Co-Simulation,先在Simulink里做纯仿真,通过之后再切到板级硬件协同仿真。这样每一轮的调试周期会缩短很多,板上抓回来的数据可以直接和模型里的波形做逐点对比,定位问题的时间至少省一半。