尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

FPGA动态功能交换DFX实战:从Vivado操作到部分重配加载

FPGA动态功能交换DFX实战:从Vivado操作到部分重配加载 FPGA做了一段时间你迟早会遇到这类需求同一个板卡要在不同时间跑好几套硬件逻辑但板子面积、功耗、成本都不允许你把所有逻辑一次性铺满。以前的做法要么换更大的FPGA要么上外置配置芯片轮着加载整片bitstream代价都不小。后来我接触到Vivado DFX也就是动态功能交换Dynamic Function eXchange也就是大家常说的FPGA部分动态重配才觉得这个问题真正有了工程化的解法。先说明白这不是什么偏门技巧。Xilinx从Vivado 2017.3开始把原来的Partial ReconfigurationPR统一改名为DFX后续每个版本都在完善这套流程。现在你在Vivado里搜索PR相关菜单基本找不到了所有入口都叫DFX。很多老教程看不了就是因为名字换得比较多。这篇文章写给两类人一类是已经被部分重配这个词吸引、想在项目里验证可行性的人另一类是已经开始用DFX但被各种诡异报错折磨的工程师。我不会只讲概念而是把从工程架构、Vivado操作、bitstream生成到实际加载验证的完整链路铺开再把我调试过程中踩过的坑也一并交代清楚。1. DFX到底解决了什么问题先搞清楚它和整体重配的本质区别1.1 一个典型场景同一片FPGA如何做到一板多役拿我最近参与的一个软件无线电项目举例。板卡上有AD9361射频前端后端需要支持MIMO检测、OFDM解调、Turbo译码三套算法。三套算法每套单独上板都需要不少逻辑资源尤其Turbo译码器那一套光RAM就吃掉大半个中端器件。如果全量加载一块板放三套完整逻辑资源不够如果通过外部配置芯片反复整片重配每次切换要几百毫秒还要重跑PCIe链路枚举和DDR训练整个上位机对板卡的连接状态全乱了。DFX的解法是把所有公共部分比如PCIe DMA、DDR控制器、时钟管理、射频配置接口全部留在静态区不参与切换把三套算法各自封装成独立模块每次只加载其中一个到可重配区域。切换时间从几百毫秒压到几十毫秒因为每次只更新一小块配置帧静态区的PCIe链路始终在线上位机根本感觉不到底层已经换了一套算法。1.2 从PR到DFX为什么现在的Vivado搜索不到老教程我经常在群里看到有人问为什么照着2015年的PR教程操作Vivado 2021.2里找不到对应菜单原因就是改名。Xilinx在ISE时代就有早期版本的Partial Reconfiguration进入Vivado后一直叫PR直到2017.3版本开始全面转向DFX这个名称同时把整个流程UI化不再完全是Tcl脚本驱动。版本之间的能力差异也要注意。7系列和UltraScale系列都有DFX支持但支持的细节不一样。比如Zynq-7000的PS侧配置接口和UltraScale的PCAP配置流程就有区别Versal上又引入了DFX over NoC等概念。如果网上教程没写清楚器件型号照抄很有可能会在实现阶段报错。我个人的习惯是先查UG909Vivado Partial Reconfiguration User Guide对照自己用的器件型号和Vivado版本再动手。1.3 DFX的边界条件不是所有设计都适合做部分重配说句实在话DFX不是万金油。我见过不少团队把简单需求做成DFX最后自讨苦吃。有几个硬性条件必须评估清楚第一可重配分区之间必须接口固定。不是说引脚相同就行是信号方向、位宽、协议时序都要完全一致否则各个RMReconfigurable Module可重配模块根本没法放入同一个RPReconfigurable Partition可重配分区。第二逻辑必须能干净地切分成模块层级。静态区和RM不能在同一个module内部交织你要是刚开始设计时没做模块化半路想拆DFX痛苦程度远高于重新写RTL。第三时序收敛压力会增加。每个RM都是一个独立实现要做多次布局布线时序分析也要分别通过。如果RM里恰好用了大量DSP48和BRAMfloorplan阶段就要提前规划容量否则后面会出现放不下的尴尬。第四加载接口和加载时间需要系统级设计。FPGA本身支持部分重配但谁来触发、加载失败怎么回滚、RM切换期间静态区接口怎么处理这些都是应用层必须回答的问题不在Vivado默认流程里。2. DFX工程的架构设计静态区与RM的边界处理是我最重视的部分2.1 接口拆分RM与静态区之间该用什么样的信号交互设计DFX工程的第一步不是打开Vivado而是先把整份RTL按静态区和可重配区重新画一遍架构图。这个前期工作做好了后面所有流程都会顺畅反之则会在实现阶段反复返工。RM与静态区的接口越少越好。我见过有人为了省事把几十个控制信号直接跨分区直连结果每次重配前后都要做大量时序约束性能还不稳定。更推荐的做法是把RM对外交互的接口收敛成AXI4-Lite从接口或者简单的同步FIFO接口所有跨分区信号在边界打两拍再做逻辑。为什么强调打拍因为部分重配过程中可重配区域的配置帧被刷新RM内部逻辑瞬间变成不确定状态跨分区的组合路径会出现短暂毛刺或亚稳态。如果不做同步处理静态区的状态机可能收到误触发。这个细节在Xilinx官方文档里讲得比较含蓄实际工程里非常关键。另外每个RM必须保持相同的端口列表和位宽。Vivado在创建分区时会检查这一点但不会检查信号含义是否一致比如一个RM里把端口当控制信号用另一个RM里把同一端口当地址用工具不报错运行时不工作。这类问题只能靠设计评审和代码规范来卡。2.2 时钟与复位跨分区的潜规则时钟和复位往往是DFX设计里最容易出问题的环节。QRM内部能不能放MMCM或者PLLA官方允许但我不建议这样做。原因是如果把MMCM放在RM里部分重配会连带重配时钟相关资源重配过程中静态区如果还要用这个时钟整个系统就会在切换窗口内失去时钟很多无刷电机控制、以太网链路这类应用根本承受不了。稳妥的架构是所有时钟从静态区的全局时钟引脚进入通过BUFG驱动后直接入RMRM内部不再例化MMCM。RM内部需要分频、相移的尽量用逻辑做同步分频或者干脆把MMCM放在静态区只给RM送时钟。复位策略也不是很多人想的那样重配前把RM复位一下就行。因为RM本身已经被卸载复位信号打在已经失效的逻辑上没有任何意义。更合理的方式是静态区与RM之间定义一个软复位寄存器由静态区在重配前拉高活动信号让RM内部自己执行关断时序加载完成后静态区再释放活动信号RM内部状态机完成自复位再进入正常工作。2.3 Pblock物理划分为什么不能随便画个框就完事Pblock物理块约束是DFX实现阶段的另一个决定性因素。很多人第一次操作时在Floorplanning里拖一个矩形把RM圈起来然后跑实现结果发现要么布线拥塞严重要么时序收敛不了。原因在于Pblock不是越大越好也不是越小越好它要和RM里实际用的资源类型匹配。在Vivado里创建Pblock后要检查它覆盖的SLICE、BRAM、DSP数量是否大于RM实际所需资源的1.2到1.3倍。留一点余量是给布线器腾空间的不然Pblock区域内部走线密集得像早晚高峰地铁布局布线时间会成倍拉长还容易报资源不足。另一个变量是Pblock的位置。RM如果涉及高速收发器GTPblock必须靠近对应的GT引脚区域如果只是普通逻辑处理尽量放在器件中部避免离I/O过远导致引脚延迟过大。Pblock的形状也值得考究窄长条往往比正方形更难布线因为逻辑单元之间的连接会被拉得很远。我做DFX项目时一般先让Vivado自动划分一个Pblock区域再根据资源报告手动微调边界而不是从零开始凭空画。3. Vivado中的DFX流程实操一步步把动态重配工程跑起来3.1 创建DFX工程从RTL模块标记到分区建立Vivado的DFX流程经历了多次改版我这里以2020.1以后的版本为准因为它把流程做得最成熟。第一步还是正常创建工程、添加RTL文件。这里有一个关键操作在Sources窗口里找到你想要作为可重配分区的模块右键选择Set Reconfigurable Partition。执行后Vivado会把这个模块标记为RP同时自动为该分区创建两个RM占位模块一般默认叫rm_design_1和rm_design_2。如果你已经有多个RTL实现文件则可以在Sources窗口里右键RP选择Add Reconfigurable Module把所有变体文件挂到同一个分区下。每个RM的端口必须完全一致RTL顶层模块名可以用不同的但接口定义必须同构。Vivado会在你添加RM时做语法检查不一致会直接弹error这个环节几乎骗不过去。做完标记后建议在RTL顶层里把静态区和RM的实例关系整理清楚保持模块名、实例名唯一。我有一个习惯给RM实例名加上明确后缀比如rm_inst_demod、rm_inst_decoder这样在多RM工程里看时序报告、波形文件时能一眼定位到具体模块避免在十几个名字相似的分区里翻箱倒柜。3.2 每个RM的独立综合设置为什么必须用OOCDFX流程里每个RM建议设置成OOCOut-of-Context综合模式也就是脱离顶层单独综合。这样做的原因有两个。一个是编译效率如果整个顶层一起综合每次改一个RM所有静态逻辑和其他RM都必须重新综合一遍工期完全耗不起。OOC模式下每个RM单独出网表改其中一个RM只重跑这一个模块的综合静态区和其余RM的网表直接复用增量构建速度能提升好几倍。另一个是约束隔离OOC综合时RM内部可以独立定义XDC约束不会和静态区约束互相污染。比如某个RM内部有假路径约束另一个RM没有它们在综合阶段就能各自保持正确约束而不是在顶层被统一起伏。设置方式很简单在Sources窗口选中RM文件右键点击Set Synthesis Options把Mode改成Out-of-Context (OOC)。也可以在图中的Synthesis Settings里一次性把所有RM统一设置。如果你忘了做这一步直接用全局综合通常也能跑通但实现阶段可能会报各种莫名其妙的跨分区连接错误所以别省这一步。3.3 Implementation多Run管理与bitstream生成做普通工程的实现时一个工程通常只有一个Implementation Run。DFX工程则相反每个RM组合或者说每个配置都必须对应一个独立的Implementation Run因为布局布线必须分别对静态区RM1、静态区RM2单独做一遍。在Vivado的Flow Navigator里选择Implementation Settings进入后创建新Run并设置该Run对应的RM配置。比如Run名为impl_rm1的就把RP的分区实现指定为rm_design_1Run名为impl_rm2的指定为rm_design_2。这两个Run可以并行跑多核机器上能明显省时间。所有Run跑完布局布线后执行Generate Bitstream。Vivado会自动为每个Run生成两类产物第一类是完整的全量bitstream用于上电初始化第二类是部分bitstream文件名通常带有_partial后缀只包含可重配区域的配置帧。注意全量bitstream是在RM1或RM2的基础上生成的但它初始化整个FPGA所以相当于出厂状态包含了某个RM的初始实现。后续通过部分bitstream在做切换时硬件就从RM1切到了RM2。这里还有一个容易忽略的点要专门为部分bitstream生成.bin格式因为很多加载路径比如后面要说的devcfg直接读.bin文件。Vivado生成bitstream后默认会有.bit和.rbt你可以用bootgen工具把.bit转成.binbootgen -image BOOT.bif -o design_rm1_partial.bin -wBIF文件里指向对应的_partial.bit文件即可。如果不想弄bootgen也可以在Vivado的Generate Bitstream设置里勾选输出bin格式不同版本的具体位置略有差异但方向是一致的。4. 把部分比特流真正用起来加载接口与运行时切换实操4.1 主流的DFX配置通路选型Vivado把bitstream生成出来只是第一步系统运行时怎么加载才是项目落地的核心。FPGA上部分重配的配置通路有好几条按使用场景可以分成几类配置通路控制端位置典型器件特点ICAPFPGA内部可编程逻辑7系列、UltraScale不依赖外部设备PL自行加载需挂PRC控制器PCAPZynq PS侧Zynq-7000、Zynq UltraScale由ARM核或Linux驱动控制简单可靠SelectMAP外部设备CPU/CPLD7系列等适合板级设计由外部主控控制MCAPPCIe接口UltraScale通过PCIe链路加载适合数据中心场景我在这里重点推荐两种实际项目里最常见的方案。第一种是Zynq配合PCAP第二种是在纯FPGA里使用ICAP原语加Xilinx官方PRCPartial Reconfiguration ControllerIP。其他通路要么需要外部主控参与要么受限于器件通用性不强。这里要特别强调一下PRC IP的价值。如果不例化PRC想要加载部分bitstream你就得自己用状态机去操作ICAPE2/ICAPE3原语解析bitstream帧格式处理CRC校验难度很大。而Xilinx的PRC IP专门干这个它提供AXI4-Lite从接口你只要把数据通过AXI总线写给它它就帮你完成内部时序控制和状态反馈。用这种方式你可以在纯FPGA设计里实现一芯双模的动态切换不需要搭载任何处理器。4.2 ZynqLinux下的实测加载路径写文件不比写寄存器麻烦如果你的项目用的是Zynq系列部分重配的实现路径会舒服很多。Zynq的PS侧自带PCAPLinux内核里也有对应的驱动。在PetaLinux或标准内核里打开CONFIG_FPGA_MGR和Xilinx FPGA Manager驱动后用户态就会多出一个设备节点通常路径是/dev/xdevcfg老内核或者通过FPGA Manager骨架层暴露的字符设备。实际加载部分bitstream的操作简单到你可能不相信——把.bin文件丢给设备节点echo 0 /sys/class/fpga_manager/fpga0/flags cat design_rm1_partial.bin /dev/xdevcfg或者使用新版FPGA Manager接口cat design_rm1_partial.bin /sys/class/fpga_manager/fpga0/load写入完成后通过检查设备节点的状态位来确认加载是否成功cat /sys/class/fpga_manager/fpga0/state正常情况下会显示operating或success。我建议在实际项目中写一个小工具把加载过程封装成API接口。因为切换RM往往涉及上层业务逻辑联动比如DMA描述符需要重新配置、算法参数需要重新下发如果每次切换都靠手动cat文件迟早会出问题。一个简单的C程序或者Python脚本打开设备节点write整个bin文件再等待状态寄存器变位整个链路就受控了。还需注意的一点是部分比特流的.bin文件必须和当前FPGA全量bitstream使用的RM版本匹配。比如全量加载的是RM1你现在要切到RM2那加载的应该是rm2_partial.bin你如果连续往同一个分区写入两个RM1的partial.bin第二次加载其实没有意义还白白占用切换时间窗。4.3 切换过程中的时序与信号毛刺防护做过一次DFX切换实测之后你会明白一个道理加载本身不难难的是切换过程不会把静态区的一块逻辑冲垮。加载期间RM内部的LUT、FF、BRAM、DSP内容会被逐帧刷新输出端口电平会出现一段不确定状态。举例说RM输出直接接到静态区的DMA写使能加载过程中DMA如果恰好采样到高电平就可能产生一次虚假写操作数据直接乱掉。这类问题很难复现、很难调试因为它和时间窗相关。我总结了一套防护套路基本能防住这类问题第一RM内所有输出到静态区的信号在RM内部先经过一个输出使能门控使能信号由静态区控制。加载前静态区拉低使能RM内部输出被钳位到固定电平0或1具体取决于安全电平加载完成后延时几个周期再拉高使能。第二静态区所有接收RM信号的模块增加同步器和有效信号判定。简单来说就是不要直接用电平信号而是用握手信号触发一次事务开始避免毛刺被当作有效动作。第三切换前通过中央控制寄存器把RM的功能上下文保存到静态区BRAM或外部DDR中切换完成后RM再从静态区恢复上下文。这个做法的应用场景是RM内部有滤波器系数、协议状态机之类的参数不保存的话每次重配都要重新初始化。5. 排错与验证我在DFX项目里踩过的坑5.1 Floorplanning阶段最典型的资源不足报错还记得第一次跑DFX的Implementation时Vivado直接报了一大段Place错误ERROR: [Place 30-638] Reconfigurable partition rm_inst_demod cannot fit within pblock pblock_rm_inst_demod. Please check the available resources inside the pblock...这个报错的核心原因是Pblock圈出来的资源不足以容纳RM内部实际使用的SLICE、BRAM、DSP或者Pblock形状不连续导致资源碎片化。排错思路是先打开综合后的资源报告看RM具体消耗了多少LUT、FF、BRAM、DSP然后打开Device视图检查Pblock内对应类型的资源容量再手动扩大Pblock边界或调整位置。这类问题往往不是一次能解决的。因为RM内部使用的BRAM和DSP在物理上有列分布限制Pblock里就算SLICE容量够BRAM可能不够。我的经验是先按资源类型分别创建资源报告比如RM用了32个BRAM那就专门看Pblock范围内的BRAM_RAMB36数量如果数量接近但拐角多还要检查是否为离散分布尽量调整Pblock让资源密度更均匀。5.2 分区边界时序违例多Run分别出报告才是关键DFX工程里最迷惑的现象是同一个FPGA设计RM1跑时序收敛RM2跑时序也收敛但把RM1和RM2放在一起看时序报告发现一堆跨分区路径违例。这个问题把不少人绕进去了原因其实在于Vivado对每个Implementation Run独立做时序分析RM的实现不同分区边界的负载和延迟也不同所以不能直接拿RM1的报告去套RM2。正确的排错姿势是为每个RM实现单独打开Route Design后的时序报告只关注当前Run里与跨分区路径相关的违例。尤其是对从静态区到RM、从RM到静态区的路径要给它们加上适当的输出延迟和输入延迟约束而不是把整个设计一把梭约束成理想时钟。还有一类边界时序问题和时钟有关。如果RM用了静态区送进来的时钟但静态区时钟约束不够严格Vivado在分析跨分区路径时会用不确定的时钟关系来算结果往往是全红。这时要检查静态区对RM送出的时钟是否设置了明确的create_clock和set_clock_uncertainty而不是依赖默认推导。5.3 bitstream下载失败一个字节序和格式引发的排查链条DFX调试还有一个高频问题bitstream下载失败。常见的现象是在Vivado Hardware Manager里加载partial.bit能成功但到了系统里用PCAP加载同一份文件就是加载后功能异常或者设备节点提示校验失败。第一步排查确认加载的是不是完整bitstream文件。很多人生成部分bitstream时选错文件直接加载了全量.bit结果把整个FPGA都重新配置了静态区逻辑必然丢失系统异常是当然的。第二步排查确认文件格式是否匹配加载接口。Zynq通过PCAP加载时通常需要.bin格式且字节序要求是高字节优先而Vivado默认生成的.bit是BITSTREAM.GENERAL.CRC使能、包含头文件的格式。你拿.bit直接喂给PCAP大概率失败需要使用bootgen将.bit转换后再加载。转换方法我在前面给过命令。第三步排查检查硬件连接和电源。部分重配加载瞬间可重配区域会有较大电流变化如果FPGA核心电源纹波过大可能导致配置帧写入失败。我遇到过一块老开发板DFX切换10次里会有1次加载失败最后发现是退耦电容不足换了一颗大容量MLCC后问题消失。另外建议在系统正式投用前加入加载失败回滚机制。方法是在外部非易失存储里保存一份当前已加载成功的全量bitstream以及在切换前先把待切换的partial.bin备份到DDR里做校验。PCAP本身不提供自动回滚实际工程里要么依赖上层软件在状态寄存器里超时判断要么设计上保证RM加载失败后还能重新加载原版本否则系统会卡在一个黑盒状态。5.4 调试观察点怎么选ILA在DFX里的限制很多人习惯在普通FPGA设计里靠ILA抓内部信号到DFX工程里也顺手把ILA核插到RM内部结果发现每个RM变体都要单独例化ILA而且ILA占用的BRAM和发生逻辑在Pblock里非常占地方很容易把本来就紧张的布线资源挤爆。我的经验是在DFX工程里尽量把ILA放在静态区观察跨分区接口的信号如果必须观察RM内部信号就为每个RM单独插入ILA但只在调试阶段插入调试完成后从RM里删掉再重新走一遍实现流程。这样才能保证最终交付的物理区域不被调试逻辑浪费。RM运行时的变量观测优先考虑VIOVirtual I/OIP。VIO可以挂在静态区通过AXI接口访问RM内部状态寄存器不需要在RM里加额外ILA调试灵活性高很多。RM内部只需要预留一组调试只读寄存器把关键状态汇总到这些寄存器里VIO读写这些寄存器就能掌握RM运行情况。这样做虽然不能看到每个时钟周期的波形细节但对功能验证和状态定位来说足够用。最后再分享一个个人习惯。凡是涉及DFX的项目无论工期多紧我建议在第一个版本就把整个链路跑通哪怕RM只用最小功能占位全量加载、切换到RM2、再切回RM1这三次加载能稳定通过再做具体算法功能迭代。因为DFX的坑多集中在工程流程和硬件时序层面这些问题拖到功能做全了再排查会混入大量功能逻辑干扰变量排查成本会高出一个数量级。先把流水线打通后面每个RM版本迭代都只是常规流程踩坑率会低很多。
返回列表