ARTICLE DETAIL

资讯详情

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

FPGA多速率系统设计:System Generator时钟使能与CIC抽取实践

FPGA多速率系统设计:System Generator时钟使能与CIC抽取实践 做FPGA-DSP的同学应该都有过这种体验在Simulink里做算法仿真采样率想怎么设就怎么设但一搬到Xilinx System Generator下面我直接叫Sysgen里多速率系统Multi-Rate Systems突然变成了一堆摸不着头脑的报错和莫名其妙的仿真时长。我在项目里用Sysgen做过CIC抽取、多相滤波器组、DDC/DUC链路深切体会到一件事多速率在Sysgen里不是能跑就行而是要理解它背后的时钟生成机制否则连sample time为什么是inf这种问题都能卡你一下午。这篇文章就把我实际用下来的理解、配置方法、踩过的坑一次讲清楚适合正在用Sysgen做信号处理、又对多速率系统没什么把握的工程师也适合刚接触FPGA数字信号处理的同学当入门参考。1. 多速率系统的本质没那么玄采样率变换的三种理由1.1 为什么信号处理链路里要用多速率通信和音频系统里不同模块对数据率的需求天然不一样。举个最常见的例子ADC采样率是100 MHz但后级的DSP解调只需要20 MHz的处理速度总不可能让所有模块都白白跑在100 MHz上消耗动态功耗。这时候在ADC后面做抽取decimation把100 MHz的数据流降成20 MHz后面的滤波、解调、同步逻辑全部跑在低速率域资源和功耗都能省一大截。反过来如果把一个低速率的基带信号要搬移到中频载波上需要内插interpolation把采样率抬升才能满足上变频对奈奎斯特频率的要求。更典型的是音频设备常见的44.1 kHz转48 kHz采样率转换这个转换比例本身就是非整数不可能是单一速率时钟能搞定的。所以多速率系统的存在理由总结起来就三个降低后级运算量、匹配不同接口的数据率、实现非整数倍采样率变换。抽取和内插是两种最基本的操作多相分解则是在FIR滤波器里用更高效的结构实现同样的功能。理解了这三个动机你就知道Sysgen里那些Upsample、Downsample、CIC Compiler这些模块是拿来干嘛的了。1.2 从Simulink的虚拟采样率到FPGA的时钟使能很多人在Simulink纯算法仿真里用多速率用习惯了觉得采样率只是个数字换到Sysgen就踩坑。Simulink里的sample time确实是模拟性质的虚拟时间轴求解器靠它决定步长和信号更新顺序。但FPGA里不能有时间轴只有一个真实的物理时钟周期在节拍。Sysgen对多速率的解决方案本质上是一个物理时钟 多个时钟使能Clock Enable。它不给你真的生成多个独立时钟域而是所有模块都跑在同一个System Clock下不同的采样率通过给模块分发不同频率的CE脉冲实现。比如系统时钟是100 MHz某个模块的采样时间是1/20 MHz那Sysgen生成的硬件逻辑中这个模块每5个时钟周期被使能一次、工作一次其他4个周期它保持输出不变。这是整个多速率设计的核心认知也是和Simulink差异最大的地方。Simulink里你能看到的不同采样时间在Sysgen里换算成的是不同的CE分频比。后面的所有配置都以这个为前提展开。2. 同步多速率的正确玩法System Clock、System Period和Clock Enable2.1 速率到底是怎么在模块间传播的在Sysgen里每个模块的采样时间不是独立设置的而是从数据源的起点向后传播。我在实际项目中用到的源模块主要有三个Gateway In、Constant、以及离散时钟源比如从MCode里输出的定时脉冲。设置的逻辑跟串冰糖葫芦差不多第一个模块定了采样时间后面连着的模块自动继承同一个采样时间直到你插入一个会改变速率的模块比如Downsample这个模块之后的数据流再按新速率继续传播。所以在Sysgen里搭多速率链路你是从源头开始一路往下配sample time的不是每个模块单独乱设。具体操作上双击任意模块在Basic选项卡里能看到Sample Time的框。填-1表示继承输入端的采样时间填0表示常数信号不随时间变填正数则是绝对周期。Sysgen推荐你在Configuring sample times时尽量用继承-1而不是每个模块都手填一个具体值因为一旦源头的速率改了手填的模块会和你系统级配置不一致报错能报到你怀疑人生。2.2 System Generator Token里的全局配置Sysgen里有一个System Generator Token就是自动生成的紫色图标它是整个设计的大脑。双击打开后有两个关键参数跟多速率直接相关第一个是Simulink System Period。这个参数单位是System Clock的周期数简单说就是把System Clock周期设为基准1所有其他采样时间必须是这个System Period的整数倍。比如你的底层物理时钟是100 MHz采样时间想要20 MHz那System Period就是5。通常的做法是把Simulink System Period设为1然后把实际时钟约束在FPGA工程里做Sysgen这边只处理采样率的比例关系。第二个是Clock Enables标签页。这里可以定义多达16个独立的CE信号每个CE用系统时钟周期 / N来指定自己的频率。刚才说的20 MHz数据流在System Period1的前提下CE的频率就是1/5。如果你设计了多个采样率域比如一个20 MHz的前级、一个4 MHz的后级就要在这里配两个CE然后每个模块关联到对应的CE上。模块关联CE的操作是在模块参数里设置在System Generator Token中定义好CE后各模块的sample time只要与对应CE匹配仿真时Sysgen就会自动把模块映射到正确的使能域。这里有个实际经验不要把CE设得太多。我最初做DDC链路时为了好看设了四五个采样率域结果综合后的布局布线毛刺很多时序收敛困难。能合并成同一个CE的速率尽量合并CE的数量从2个加到4个会让控制逻辑复杂度上升一个量级。2.3 上采样、下采样和看起来没变但其实变了的模块Sysgen里处理速率变换的模块和Simulink类似但行为有细微差别这坑过不少人。Downsample模块是每N个输入采样取1个输出。它的输出采样时间是输入采样时间的N倍。使用时注意相位偏移参数如果不设置偏移抽取出来的序列可能和预期相位差了几个周期在后面的匹配滤波器之前你需要先做符号定时同步这个相位偏移就得根据定时误差估计算出来填进去。Upsample模块则是每输入一个采样输出端连续输出N个同样的值零阶保持或者在中间补零。注意这里有个细节Sysgen的Upsample默认补零还是保持取决于你选的是Upsample还是Zero-Order Hold两者工作方式完全不同。补零的好处是可以配合后续FIR做插值滤波保持型插值则信号频谱会有较多镜像需要更强的镜像抑制滤波器。另外还有个常见的坑Rate Change模块输出的数据率变了但Sample Time显示上因为继承机制会自动改变很多人以为没生效。如果你做完速率变换后发现后面模块的仿真时间和预想的不对先从Display模块的仿真输出帧数上判断是不是真的变了再看Sample Time Color的颜色变化。Sysgen里判断多速率是否生效最直接的办法是打开Simulation菜单下的Debug开启Sample Time Color显示。不同采样率的信号会用不同颜色标注出来一眼就能看到哪些信号在一个速率域、哪些已经跨到另一个速率域了。这个功能我每次做多速率调试都会开着比看数字省心多了。3. 异步多速率真正跨时钟的时候握手信号是规矩3.1 什么时候一定要走异步路径前面说的CE分频仍然属于同步多速率范围——所有模块共享同一个物理时钟只是使能频率不同。但真实项目里多速率系统往往还涉及真正的异步跨时钟域比如ADC的采样时钟是100 MHz但DSP处理时钟是125 MHz这两个时钟之间没有整数倍关系甚至来源是两个振荡器相位完全随机。这种情况下直接把数据从一个时钟域送到另一个时钟域是绝对危险的亚稳态问题轻则数据偶尔错一下重则系统整体挂掉。Sysgen里解决这种问题的方式是异步FIFO。你可能会问能不能自己写个Black Box来做个双口RAM可以但没必要Sysgen里提供现成的Async FIFO模块在Basic Elements下的Memory目录里它的接口直接就是AXI4-Stream风格和IP核协作非常方便。3.2 用AXI4-Stream握手把数据安全递过去AXI4-Stream在Sysgen里的核心信号是tvalid、tready和tlast。tvalid是发送端告诉接收端我这拍数据是有效的tready是接收端告诉发送端我准备好了可以接收tlast表示这是包的最后一拍。三个信号配合起来实现的就是一个有礼貌的推送过程两边都同意数据才算真正传过去。在多速率转换场景下比如从写端100 MHz的数据率转到读端125 MHz的数据率你要做的是把写端数据、写端有效信号接到FIFO的写侧把读端时钟和读侧控制接到FIFO的读侧。Sysgen中的Async FIFO模块天然处理了跨时钟域的同步问题你只需要关注输出侧tvalid/tready的逻辑。这里有一个我在实际项目中反复遇到的问题握手信号的处理不完整。很多同学只看tvalid不管tready认为FIFO有足够深度就一定不会满。这在连续数据流稳定传输时确实没问题但如果数据是突发式的——比如DDC里每N个周期来一个矢量输出——FIFO就可能在突发期内写满这时如果不看tready数据就直接丢了。所以真正的可复位设计方案输入侧逻辑必须做tvalid tready才推进的处理也就是把握手成功的那个时钟周期作为真正有效的数据拍。3.3 FIFO深度和空满标志的选择FIFO深度的计算思路我在Sysgen里一般这样估算先确定写侧最大突发长度Burst_Length比如连续写入N拍数据然后带宽差是(写入速率-读取速率)。FIFO深度至少要大于Burst_Length减去在突发期间能读走的数据量公式大概是 Depth ≥ Burst_Length × (1 - 读速率/写速率) × 安全系数。实际项目中安全系数我会取1.5到2因为跨时钟域的最小粒度和FIFO内部同步打拍还会多占用几个周期。空满标志的选择上empty和full是最基本的prog_full可编程满和prog_empty更适合做反压。Sysgen里的Async FIFO模块默认提供empty、full、almost_empty等端口你把prog_full阈值设成接近FIFO容量的一半或三分之二就能提前拉高反压信号避免数据打到边沿上。另外一个易错点FIFO的复位时序。跨时钟域FIFO的复位信号要跟写时钟域同步打两拍再释放不然复位后可能出现偶发的空/满标志误判。我在Vivado里做PR时遇到过这种情况仿真怎么都对上板就偶尔丢一拍后来查来查去就是复位释放的异步问题。4. 自定义模块在多速率链路里的Sample Time该怎么交代清楚4.1 Black Box的Sample Time表格别乱填做实际设计时经常需要把已有的VHDL或Verilog模块用Black Box包进Sysgen里。Black Box的配置界面里会有一个表格让你给每个端口指定Sample Time。很多人的做法是全部默认成-1继承觉得这样省事。在单速率设计里这样没问题但在多速率系统里就要小心了如果你的自定义模块内部做了速率变换——比如里面自己写了一个抽取器或插值器——那么它的输入端口和输出端口实际上是处于不同的采样率域的全部用-1会导致Sysgen无法识别这个模块改变了速率后续模块的采样时间传播就全乱了。正确做法是给这个Black Box的输入端口配上输入域的采样时间给输出端口配上输出域的采样时间。Sysgen会自动根据你的设置把两个速率域正确地连接起来。如果你的自定义模块是完全组合逻辑或单一速率的那全部填-1没问题但只要是内部改变了速率的模块一定要在端口级别明确区分采样时间。4.2 MCode和状态机模块的多速率约束Sysgen的MCode模块用MATLAB语言写控制逻辑的那种在多速率链路里的行为也值得注意。MCode本质是一种有限状态机生成的硬件逻辑它的采样时间传播规则是继承输入端的采样时间。换句话说MCode无法自己改变速率它的输出采样率永远跟输入采样率一致。在项目里我做过一个比较典型的例子用MCode控制一组多相滤波器支路的切换输入是20 MHz的基带数据但是MCode每5个周期切换一次支路导致它实际上需要4 MHz的逻辑速率。如果直接把MCode插在20 MHz的数据流里而不做处理要么就得在MCode里把工作使能逻辑写清楚要么就在它的前面先降速率再进MCode。这里有个比较实用的技巧如果MCode的输入端有多路不同采样率的信号MCode的输出采样时间会以最快的那路输入为准这一点经常是bug来源。你以为是慢速控制信号结果输出直接被Sysgen提升到了快速域后面的模块全都跟着跑快资源暴涨。遇到这种场景我给的建议是调度信号和控制信号分开处理——数据走数据通路控制信号单独控制使能不在MCode里做跨速率混合。4.3 采样时间设为0的隐含含义Sysgen里采样时间0表示常数这个很多人知道但有个相关细节容易忽略常数信号在多速率系统中会被自动复用到所有速率域所以用一个常数做某个低速模块的输入时这个低速模块的速率不会被常数抬高。也正是因为这个多速率链路里想给某个模块喂一个永远不变的值直接连一个Constant模块是最省事的做法不用担心速度匹配。相比之下如果你把一个低速运行模块和常数信号加在一起却把常数信号的采样时间也继承成了高速那混合模块就会运行在高速白白浪费资源。所以给常数模块手动设置采样时间为0常数或者明确指定低速域是降资源占用的一招。这个细节光看文档容易忽略实际做工程才知道差别这么大。5. 完整案例一个CIC抽取滤波器从仿真到上板的搭建过程5.1 为什么CIC是练习多速率必备的案例CIC级联积分梳状滤波器是做抽取时最常用的一种结构它最大的特点是完全不需要乘法器只用加法器和延迟器特别适合FPGA。它的传递函数公式是H(z) (1 - z^(-D·M))^N / (1 - z^(-1))^N这里M是抽取因子D是微分延迟通常为1或2N是级数。整个结构分成两半积分器部分工作在输入高采样率域梳状器部分工作在抽取后的低采样率域。所以CIC滤波器天生就是一个多速率系统——你在Sysgen里搭CIC的过程就是多速率设计最典型的训练场。5.2 在Sysgen里的实际搭建步骤我以一个输入采样率100 MHz、抽取因子M5、微分延迟D1、级数N2的CIC抽取滤波器为目标参数来说明。第一步是数据源。用一个Gateway In接一个Sine Wave发生器把Gateway In的Sample Time设为1/100e6也就是10 ns一个采样。这个时间不是绝对物理时钟但Sysgen会用它来计算CE比例。第二步放积分器。CIC前级需要两个级联的积分器级数N2用Sysgen里最普通的加法器和Unit Delay就能搭。积分器的采样时间继承输入也就是100 MHz域。每一级积分器的输出位宽都会增长这里先不详细算后面专门说位宽问题。第三步放抽取器。在积分器之后放一个Downsample模块抽取因子设为5采样时间自动变成1/20e6。此时信号已经进入低速域。第四步放梳状器。梳状器是当前值减去D·M个周期前的值D1、M5所以差分延迟是5个低速时钟周期。用单位延迟拼接出5级延迟线然后做减法。也可以直接用Sysgen里的FIR Compiler中的CIC模式或者用Vivado的CIC Compiler IP核但自己手工搭一遍才能理解CIC的速率切换是怎么实现的。第五步是输出。在梳状器后面接一个Gateway Out采样时间设为1/20e6这样整个链路的速率变换就完成了。5.3 位宽和资源账要提前算好CIC最大的坑在位宽。CIC的增益是(M×D)^N以M5、D1、N2为例增益就是(5×1)^225。也就是说即使输入只有12 bit积分器内部做几次加法之后位宽会迅速膨胀。Sysgen里模块默认会帮你做full precision处理但如果你不想要那么大的位宽就需要自己在关键节点插入quantization模块做截位或舍入。Sysgen中对多速率模块的位宽处理有个很好的机制双击加法器可以选择full precision或者手动指定输出位宽。CIC的实际设计里常见做法是每级积分器增加ceil(log2(R×M))位总共增加N×ceil(log2(R×M))位。具体的位宽设计公式我项目里用的是B_out B_in ceil(N × log2(R × M))其中B_in是输入位宽R是差分延迟M是抽取因子N是级数。以12 bit输入、M5、N2、D1为例输出位宽至少是12 ceil(2 × log2(5)) 12 5 17 bit。实际设计中我会再多留2 bit余量防止中间数据溢出。资源方面CIC本身是加法器为主的结构在7系列或Ultrascale上跑几百MHz完全没压力。Sysgen里编译后可以在Vivado里看资源报告一个两级5倍抽取的CIC大概只需要几十个LUT加几十个FF比同规格的FIR滤波器小一个数量级。这也是CIC在多速率链路里长盛不衰的根本原因。6. 多速率调试排错手记那些资料里不写但实际要命的时刻6.1 Sample Time显示为inf怎么排查这是多速率相关的最常见报错我见过好几个同事卡在这里。inf的sample time表示数据流断链了——某个模块上游没有有效的速率来源。常见场景是你把一个常数模块默认sample time为0或-1直接当数据源接到了一个需要非零速率的模块上然后Sysgen没办法往上追溯速率就报inf。排查步骤我从项目经验里总结成三条用Sample Time Color功能检查哪个信号线是红色的红色通常代表inf或错误速率。从inf出现的模块往回沿信号线找看哪个源头模块的Sample Time没设置对——特别要注意是不是整个Chain里混入了一个Simulink原生模块不是Sysgen库的模块它会把Simulink的连续时间域带进来。给所有Gateway In和Constant模块都显式设置采样时间不要让它们依赖自动继承因为自动传播在跨速率边界时经常产生歧义。6.2 仿真时间长得离谱概率出在多速率步长上Sysgen的多速率仿真有一点和Simulink不一样Sysgen默认生成的仿真模型会让你的Simulink配置固定步长求解器。如果你在Simulink的求解器设置里不小心选了变步长Variable-step而Sysgen模型里又同时存在100 MHz和20 MHz两个速率域变步长求解器为了追踪最快信号会把仿真步长压得极小仿真时间瞬间爆炸。我的解决办法是在Simulink模型配置里强制用固定步长Fixed-step discrete solver并把步长设成所有采样时间的最大公约数。Sysgen甚至会报错提醒你不能变步长跑但很多人为了能跑起来强行忽略结果就是能跑是真能跑就是跑不完。另外信号源的零点处有个小细节多速率系统里正弦波等Simulink源如果直接用可能被Sysgen当作连续时间模块导致仿真模型变得非常慢。把这个源接到一个有离散采样时间的Gateway In后面通常能规避这个问题。6.3 常见报错信息的翻译和应对Sysgen的多速率报错信息写得很工程化但对不看文档的人来说就像加密语言。我把最常见的几个翻译成正常人能理解的逻辑第一条Input sample period is not an integer multiple of the output sample period。意思是两个信号速率不成整数倍关系。Sysgen的同步多速率要求所有速率之间有整数倍关系。你如果硬要接一个1/100M的信号和1/30M的信号做运算它当然不干。应对方案改用异步FIFO或者把采样率设计改成整数倍关系比如30 MHz改成25 MHz。第二条A multirate block cannot be directly connected to another multirate block。两个速率变换模块不能直接首尾相接。原因是Sysgen要求速率变换之间必须有某种处理逻辑防止两个变换级联导致相位混乱。实际上这里你需要插一个具有状态保持能力的模块比如Unit Delay或者把两步速率变换合成一步。第三条Sample time in the model does not match the generated clock enables。你在模块参数里填的采样时间和System Generator Token里配置的CE不一致。这里没有别的办法老老实实去System Generator Token里面加CE或者改采样时间二选一让两边对齐。这三条我都在实践中一一踩过而且真实项目的坑往往比文档案例更隐蔽——因为它混合了多个问题。遇到改一步报一个错的时候不要急着改先把整条链路里的采样时间标注画出来理清每个速率域的边界再逐个解决。画完你就发现80%的报错其实都是自己在某个边界上接错了。到最后回头看多速率系统在System Generator里真正要掌握的就三样东西一是理解CE分频的工作机制明白多速率在FPGA里是多使能而不是多时钟二是用好Sample Time的继承和显式配置让速率传播不失控三是分清楚什么时候用同步CE、什么时候必须上异步FIFO。把这三样理清了CIC、多相滤波器组、DDC/DUC这类常见多速率设计就都顺手了。如果让我给刚开始用Sysgen做多速率项目的朋友一句实在建议任何一次仿真跑通之后都花点时间把Sample Time Color打开把你整个模型的采样时间分布截个图存下来。下次遇到速率相关的诡异问题翻出这张图能帮你省一半的排查时间。
返回列表