Vivado里边有个功能从ISE时代的Partial Reconfiguration演化过来,叫DFX(Dynamic Function eXchange)。每次跟同行聊到这个,发现好多人要么只听过名字不知道具体怎么落地,要么在Vivado里折腾半天卡在实现步骤上。这篇文章就把这个技术从原理到实操给你捋一遍,尽量做到看完能自己动手。
1. DFX要解决的根本问题:整片重配的代价太高了
1.1 传统重配方式的痛点在哪
FPGA传统重配置流程很简单:在bitstream中存放完整比特流,需要切换功能时通过SPI、JTAG或者SelectMAP把整个比特流重新灌进去。这个过程有几个麻烦:
第一是重配期间FPGA的全部逻辑都停止工作,哪怕你只是想改其中一个数据通路模块,整颗芯片都得停下来等配置完成。对于7系列器件,中等规模芯片完整配置时间通常在几十毫秒到上百毫秒量级,UltraScale+器件更大、bitstream更大,时间更长。这个时间在通信设备、图像采集系统里往往不可接受。
第二是资源浪费。很多场景下,FPGA的逻辑资源分为几个功能模块,但这些模块不会同时被使用。比如一个软件无线电平台,DDS模块和信号解调模块可能复用同一块运算资源,但传统比特流必须把所有这些模块同时放进FPGA,硬件成本直线上升。
第三是灵活性受限。你想OTA升级某段算法,传统方案必须整片更新,升级风险大、回滚困难。一旦新比特流有问题,设备可能直接变成砖。
DFX的思路就是把这个重配粒度从”整个器件”降到”器件内部某个区域”。动态功能交换允许在FPGA运行期间,只重新配置某一个指定分区的逻辑,其他分区照常工作,I/O也不闪断。这就是它区别于传统重配的核心价值。
1.2 DFX的核心概念:静态区、动态区、可重构分区和可重构模块
DFX设计在逻辑上把FPGA分成两种区域。静态区(Static Region)保持一直运行,不受重配影响;动态区(也叫可重构分区,Reconfigurable Partition,简称RP)则专门承载会被切换的逻辑模块。每个RP下可以挂载多个不同的可重构模块(Reconfigurable Module,简称RM),同一时间只能有一个RM被激活并加载进RP,切换时由配置引擎将当前RM替换为目标RM。
我一般给新接触的人打这个比方:把FPGA想象成一套房子,静态区是承重墙和管线,绝对不会动;动态区是房间里的家具,你可以随时把沙发换成餐桌,房子结构不受影响。DFX的重配过程就相当于把家具从一个房间搬进另一个房间,承重墙完全不知道发生了什么。
还有一个关键概念叫顶层边界(Top-Level boundary,通常写作TL边界)。在DFX工程里,“顶层”依然是静态区所在的那个层级,动态逻辑以黑盒形式挂在静态逻辑下面。所有跨区域的信号都要通过这个边界交换,Vivado会在这个边界上自动插入分区引脚(Partition Pin)和接口逻辑。这些引脚在布线时会被固定下来,形成静态逻辑到动态逻辑的信号通路。
版本上还需要注意,Vivado从2015.1开始用DFX流程取代了传统的Partial Reconfiguration流程。你搜网上资料经常看到PR这个老提法,在2015以后的版本里对应的是DFX。我建议直接用Vivado 2020.1之后的版本来搭DFX工程,老版本在约束和综合流程上的细节差异比较多,容易踩坑。
2. 动手前先把家底摸清:哪些器件支持、工程结构怎么搭
2.1 器件支持清单与许可证问题
做任何DFX项目之前,第一步必须是确认器件和许可证条件。DFX不是所有Xilinx器件都免费开放的功能:
- 7系列(Artix-7、Kintex-7、Virtex-7):支持DFX,但需要单独的DFX license,这个license通常在购买开发板或者联系FAE时可以获得。
- UltraScale、UltraScale+(Kintex-UltraScale、Virtex-UltraScale+等):支持DFX,同样需要license。Vivado IDE里如果没加载有效license,DFX相关菜单会直接灰掉,没法操作。
- Versal ACAP:支持DFX,但流程上跟传统FPGA有较大差异,涉及AI Engine和可配置逻辑的协同重配,复杂度更高。
- Zynq-7000、Zynq UltraScale+ MPSoC:支持DFX,同时支持通过PCAP接口在PS侧触发动态重配,比较适合跑Linux的场景。
另外要注意,license不是”能用”就行。DFX还分标准版和增强版。增强版支持一些高级特性(比如部分重配的调试逻辑、动态区支持更多资源类型),你的license需要匹配你用的器件和功能集合。我遇到过一次比较坑的情况:license在Vivado里显示有效,但跑到write_bitstream阶段直接报错,检查半天发现是license类型与器件型号不匹配,大家在开跑之前最好先确认一下。
开发环境版本上,Vivado 2020.1以上的DFX流程已经比较成熟,GUI对话框的布局也稳定了,下面的步骤都基于这个版本线来写。
2.2 DFX工程结构:怎么组织RTL代码
DFX工程和普通工程的目录组织差异主要在于:RM的代码隔离。
我在实际项目里的做法是这样组织的:
project_root/ ├── rtl/ │ ├── static/ │ │ └── top.v │ ├── rp/ │ │ ├── rm_module_a/ │ │ │ ├── rm_a.v │ │ │ └── rm_a_alu.v │ │ ├── rm_module_b/ │ │ │ ├── rm_b.v │ │ │ └── rm_b_fft.v │ └── common/ │ ├── reset_sync.v │ └── clock_manager.v ├── xdc/ │ ├── top.xdc │ └── pblock.xdc └── scripts/ └── dfx.tcl顶层模块要把RM对应的实例化声明为黑盒,不能在顶层直接引用RM的端口内部逻辑。更规范的做法是,在顶层RTL里用类似于wrapper的方式把RP实例和RM选择分开:
module top ( input clk, input rst_n, input mode, input [7:0] din, output [15:0] dout ); // RM实例,具体实现由RM提供 rm_wrapper u_rm_wrapper ( .clk(clk), .rst_n(rst_n), .mode(mode), .din(din), .dout(dout) ); // static logic ... endmodule这里rm_wrapper的某个实例会被设置为RP,后续综合时Vivado通过Implementation的配置把它关联到具体的RM实现。
代码组织上还有一个容易被忽略的点:静态区里不能引用任何RM内部信号。一旦引了,DFX实现阶段会报出类似“reference to non-static signal in static context”的错误。这一点必须在写代码阶段就强制自己遵守,否则后期改动成本很高。
2.3 综合方式选择:OOC还是Flat
DFX要求RP对应的RM必须独立综合(Out-of-Context,OOC),也就是每个RM单独做综合网表,然后作为黑盒插入到顶层网表中。
具体到Vivado操作:在综合设置里,把RM模块设置为OOC方式。这样每个RM的综合结果是一个独立的dcp文件,顶层综合时这些RM实例以OOC module的身份被引用。
关于flat综合:在一些老版本的PR流程里,有人尝试把所有模块一起综合,然后靠布局阶段去区分区域,这在DFX流程里是行不通的。DFX对每个RM的边界和接口有严格要求,必须在综合阶段就保证RM是一个封闭的、可独立重配的逻辑单元。强行flat综合大概率在后端阶段报“unsupported configuration”错误。
我建议在工程早期就把所有RM设为OOC,并且为每个RM单独建立一个仿真testbench,先把功能验证做扎实,因为后端的DFX流程调试起来远比RTL仿真麻烦。
3. 在Vivado里设置DFX工程的完整流程
3.1 创建可重构分区(RP)
打开Vivado工程后,在Flow Navigator里选择”Create and Package New IP”或者直接在Sources窗口右键,找到”Create Partition Definition”。
这时Vivado会弹出一个对话框,让你选择:
- Partition Definition名称(给这个RP起个名字)
- 关联的模块(选你在RTL里准备好的RM wrapper实例)
- 选择该RP对应的RM模块
关键点来了:这里RM的个数没有硬性限制,但至少需要两个RM才能体现DFX的意义(否则切什么?)。不过你完全可以在早期只用两个RM做验证,逻辑越简单越好,先把流程跑通。
RP定义完成后,Sources面板里会看到这个模块带上了特殊标记,比如在Sources的Hierarchy视图里,RP模块图标可能带一个小齿轮或者“pblock”标记。
3.2 分配Pblock物理区域
Pblock(物理块约束)可以说是DFX流程中最核心的约束,它决定了:动态区占用哪些CLB、BRAM、DSP、UltraScale的SLR等资源。
具体操作路径是:在Vivado主界面打开Floorplanning(布局规划)视图,选中RP模块对应的cell,右键选择”Draw Pblock”或者”Assign Pblock”。手动画一个矩形区域,把RP的逻辑限制在这个区域内。
这里有几个实践要点:
Pblock不能和静态区的逻辑位置重叠。Vivado会自动检查冲突,但如果你有很多Pblock,最好在布局视图里把静态区域的资源占用情况也打开看一下,选择一块静态逻辑放置比较稀疏的区域给动态区。
Pblock要包含足够的资源种类。如果你的RM里既有LUT/FF,又有BRAM和DSP,那么Pblock需要覆盖这些资源的列。Xilinx FPGA的CLB、BRAM、DSP是交替分列的,画矩形时注意把需要的列都圈进来。
对于多SLR的器件(比如VU9P),Pblock的分配还需要考虑超逻辑区域(Super Logic Region,SLR)。跨SLR的信号走线延迟明显变大,所以尽量让一个RP的Pblock落在一个SLR内,不要横跨多个SLR,否则时序收敛会很痛苦。
如果你不想手动画,也可以用XDC约束来定义Pblock,例如:
create_pblock pblock_rm1 add_cells_to_pblock pblock_rm1 [get_cells [list u_rm_wrapper]] resize_pblock pblock_rm1 -add {CLOCKREGION_X1Y2:CLOCKREGION_X2Y3}不过手动画更直观,而且Vivado会实时显示资源和位置,新手推荐先用GUI。
3.3 处理分区间的信号:需要手动约束吗
DFX里分区间信号的处理,是很多人忽略但影响很大的细节。
跨分区信号在综合之后会被Vivado自动加上特殊的属性(例如PR_DECOUPL、DONT_TOUCH相关属性)。但在RTL层面,你需要对跨分区信号做一次同步处理(如果涉及跨时钟域),并且建议对关键接口使用寄存器打拍再跨边界。这是因为分区引脚的位置在布局后会固定在Pblock边界上,静态逻辑到动态逻辑的路径上会有一段不短的布线延迟,不能在路径上出现组合逻辑的亚稳态累积。
另外,跨分区的时钟和复位信号要单独考虑。最稳妥的做法是,在静态区里把全局时钟缓冲器(BUFG)放在RP外部,让所有RP内的触发器使用同一个BUFG驱动的时钟网络。复位则建议在静态区先做异步复位同步释放,再把同步后的复位信号送进RP。
实际上Vivado对跨分区信号的管理还涉及“配置引脚”(reconfigurable partition pin)。如果你的RP接口信号比较多,手动一个个管不现实,建议在Pblock属性里开启“Include partition pins”选项,让Vivado自动在Pblock边界创建partition pin。
3.4 添加约束文件并运行综合与实现
约束文件里除了Pblock,还需要保证I/O约束、时钟约束都在top.xdc里声明。DFX对时序约束的要求和普通FPGA设计一样,但更严格的地方在于:你最好给每个RM都做一遍时序分析,确保不同RM都能在同一个时钟频率下收敛。
实际操作命令示例:
read_verilog [list top.v rm_a.v rm_b.v] synth_design -top top -part xcvu9p-flga2104-2L-i create_pblock ... add_cells_to_pblock ... set_property HD.RECONFIGURABLE 1 [get_cells u_rm_wrapper] # 如果需要对RM单独综合,这里需要为RM设置OOC # 然后进入实现 opt_design place_design route_design上面的Tcl命令只是示意,项目里一般会用工程脚本封装。在GUI模式下,这些操作大多会被界面按钮替代,但理解底层命令仍然有助于排查问题。
在综合前需要为每个RM设置OOC综合策略。在Vivado的Hierarchy右键RM,选择“Set OOC…”即可。然后在Run Synthesis时,Vivado会先生成各RM的dcp,再综合顶层。
实现阶段注意:Vivado会为每个RM单独做一遍布局布线,但在其中一次实现中,只有一个RM会被选为活动RM。这就是为什么DFX实现阶段比普通设计耗时更长——同一份设计,你需要在每个RM配置上都跑一遍实现并生成对应比特流。多RM的工程,总实现时间约等于所有RM各自的实现时间之和,这一点在项目排期时要提前预留时间。
4. 比特流生成与加载:把动态重配变成现实
4.1 生成全比特流和部分比特流
当实现完成、时序收敛后,就进入比特流生成阶段。
在DFX流程中,Vivado会生成两类比特流:
- 一个完整比特流(full bitstream):包含静态区和当前活动RM的全部配置数据,也就是设备上电后第一次加载用的文件。
- 若干部分比特流(partial bitstream):每个RM一个,只包含该RM所在Pblock区域的配置数据,用于运行时动态切换。
在Vivado GUI中,配置完Run Implementation后,选择”Generate Bitstream”,Vivado会询问你要生成哪些配置(一般会按当前活动RM生成全比特流和对应部分比特流)。如果想一次性生成所有RM的部分比特流,需要在实现设置里把“RM”全部勾选上。
通过Tcl脚本方式生成比特流,基本命令是:
write_bitstream -force -bin_file ./output/top_full.bit write_bitstream -force -bin_file ./output/rm_a_partial.bit -cell u_rm_wrapper write_bitstream -force -bin_file ./output/rm_b_partial.bit -cell u_rm_wrapper其中-cell参数指定要生成哪个RM的部分比特流。需要说明的是,write_bitstream会为指定的cell生成对应区域的部分比特流,前提是这个cell已经在DFX流程中成功实现过。
特别提醒:在生成部分比特流之前,必须在综合时或者综合后设置set_property BITSTREAM.CONFIG.CONFIGRATE 50000000之类的配置属性,保证配置时钟合适。同时,对于使用ICAP/PCAP加载的场景,建议在生成比特流时开启set_property BITSTREAM.GENERAL.COMPRESS TRUE,可以减小部分比特流尺寸,加载更快。
4.2 在硬件里触发重配:ICAP、PCAP和DFX Controller
比特流文件生成只是第一步,真正体现DFX价值的是运行时的动态切换。
FPGA内部实现动态重配,最常用的路径是内部配置访问端口:
- 7系列使用ICAPE2原语
- UltraScale/UltraScale+使用ICAPE3原语
- Zynq/Zynq UltraScale+可以使用PCAP(通过PS侧访问)
- 另外,通过SelectMAP接口(外部控制器加载)也可以实现部分重配
我在项目里最常用的方案是用MicroBlaze或Zynq PS通过AXI接口连接Xilinx官方提供的DFX Controller IP核,由它来管理ICAP/PCAP,负责将部分比特流加载到指定区域。
DFX Controller这个IP的核心理念是:把比特流文件存放在外部存储(比如SD卡、QSPI Flash、DDR),处理器通过AXI向DFX Controller下发重配指令和比特流数据,DFX Controller内部完成ICAP时序和地址管理。
电路连接大致如下:
+-------------+ AXI +----------------+ ICAP/PCAP +---------+ | Zynq PS/MB | <-----------> | DFX Controller | <-----------------> | FPGA CFG| +-------------+ +----------------+ +---------+DFX Controller IP在Vivado IP Catalog里直接搜“DFX Controller”就能找到。配置时主要需要设置:
- 支持的RM数量和地址映射
- ICAP/PCAP接口时钟频率
- 是否开启回读校验(readback verification)
在驱动软件层面,核心操作其实就三步:
- 把部分比特流数据拷贝到DDR中的指定缓冲区。
- 向DFX Controller的寄存器写入目标RM地址和长度,触发加载。
- 等待加载完成中断或状态寄存器翻转。
如果你用的是Zynq+Linux方案,也可以考虑使用现成的设备树驱动和用户态工具,加载部分比特流更简单。
4.3 硬件调试时的注意事项
DFX硬件调试比普通FPGA调试多一层复杂度,原因在于:部分重配之后,ILA(集成逻辑分析仪)如果部署在动态区,它的调试核也变成可重配的了,断点、触发条件都要重新拉取。
我的经验是:调试阶段先把ILA放进静态区,把关键信号复制一份出来监测,尽量别放在动态区。否则每次切换RM之后ILA都要重新配置,调试效率非常低。
另外强烈建议在硬件验证前,先在Vivado里用PR Verify功能对设计做一遍校验。PR Verify会检查静态区和动态区的接口连接是否一致、Pblock是否合法、是否存在非法跨越。跑一遍比在板子上查快得多,能省掉大量时间。
5. DFX设计里的进阶问题:时序、资源、风险规避
5.1 时序收敛:为什么DFX更容易出现时序问题
DFX工程比普通FPGA工程更容易遇到时序不收敛的情况,原因主要有三点。
分区引脚固定导致绕线路径变长。跨分区信号必须经过Pblock边界上的固定引脚,这个引脚位置在布局后就被钉死。如果你的Pblock位置选择不当,边界上某个扇出很大的信号绕线距离可能非常远,路径延迟直接超标。解决思路是在Pblock内部尽量把接口FF放在靠边界的逻辑上,同时减少跨分区组合逻辑级别。
RM多样性导致的最差路径不同。不同RM布局布线后,各自内部的时序关键路径往往在不同位置。你可能发现RM_A在某个频率下收敛了,但换到RM_B又出现建立时间违例。这类问题只能逐个RM查看时序报告,找到跟Pblock边界相关的公共瓶颈,单独优化。
多SLR跨域。前面提到,Pblock尽量不要跨SLR。如果跨了,SLR之间的超长线延迟通常在几百皮秒量级,对300MHz以上的设计影响很大。这种问题最有效的办法是重新划分Pblock,或者将跨SLR信号在RP内部打拍。
5.2 部分重配前后的行为一致性
动态重配不只是“换个bitstream”,还涉及到重配前后系统状态的平滑交接。
比如你的RM内部有大量状态寄存器,切换前如果不清空,加载完新RM后FPGA内部残留状态可能让新RM工作异常。这时候就需要在RM内部设计一个“状态初始化”逻辑——比如上电后自动拉一段复位信号,或者由静态区在切换完成后向RM发送一个软复位。
另外关于RM的握手协议:如果你的设计里静态区需要等RM切换完成才能继续工作,一定要用DFX Controller的完成信号来触发时序控制,而不是简单地延时等待。加载时间受bitstream大小、配置时钟频率、总线宽度等多因素影响,固定延时方案很容易在边界条件下出问题。
我实际项目中踩过最大的坑就是:切换RM时,静态区还在向RM发数据,结果新RM刚加载完收到一堆垃圾数据直接跑飞。后来改成切换前由静态区主动拉高ready信号并暂停数据发送,等DFX Controller上报完成后再恢复发送,问题立刻解决。
5.3 RP内资源利用的约束对比
Pblock内部资源利用率并不是越高越好。因为部分重配时,新RM的布局布线要在不改变分区引脚的前提下完成,如果Pblock内部资源已经用到95%以上,新的RM几乎必然会出现拥塞、布线失败或者时序违例。
一般建议把Pblock资源利用率控制在70%-80%左右。同时,不同RM的资源占比最好相近,不要一个RM只用了LUT,另一个RM大量使用DSP,这样Pblock区域内的资源很难平衡利用。
举一个具体例子:我之前在一个Kintex-7项目里,RM_A只占Pblock的40%资源,RM_B却占到了95%,结果RM_B每次重配都要做很长的拥塞处理,而且时序很差。后来把Pblock重新调大,给两个RM都留出足够的空间,问题才缓解。
5.4 常见DFX报错速查与应对
下面列出几个我实际项目中最常见的报错和对应思路:
| 报错关键字 | 可能原因 | 处理办法 |
|---|---|---|
| invalid Pblock or overlaps with static | Pblock范围与其他逻辑重叠 | 打开Floorplan视图,调整Pblock位置和尺寸 |
| cannot merge OOC module | RM的OOC综合未完成或dcp路径错误 | 检查综合运行状态,确认RM的OOC工程正确生成 |
| No legal sites found within Pblock | Pblock资源不足或包含不支持重配的资源列 | 检查Pblock内LUT/FF/BRAM/DSP数量是否满足RM需求 |
| partition pins mismatched | RM之间的顶层接口不一致 | 确保所有RM对外的端口列表、位宽、方向完全一致 |
| bitstream generation failed | 综合实现阶段隐藏错误 | 先运行PR Verify,确认PR过程无误后再查具体错误 |
需要说明的是,部分报错信息在Vivado的不同版本中会有措辞差异,但本质原因大多在上述范围内。排查时最好把PR Verify结果、时序报告、资源报告三者一起看,能快速定位问题。
5.5 与普通多比特流方案的选择
这里也想强调一下:并不是所有需要多功能的FPGA都必须上DFX。如果你的应用场景切换频率极低(比如设备维护时才切换),或者FPGA资源非常充裕、多份逻辑同时实现并无压力,那么传统多比特流方案(整片重配)反而更简单,成本也低。
DFX适用的典型场景是:
- 业务不能中断,必须无感切换(比如通信基带处理)
- 硬件资源紧张,多套功能必须复用同一块逻辑(比如软件无线电)
- 需要远程按需加载不同算法(比如AI加速器中不同模型)
我见过一个项目,最初规划时把两套视频编码算法同时放在FPGA里,资源利用率达到了95%,时序几乎无法收敛。后来引入DFX,把两套编码算法分别做成RM,同一时刻只加载一个,资源利用率降到70%,时序问题也随之消失。这类场景下DFX的优势非常明显。
6. 一些关于DFX设计的日常习惯和收尾经验
聊了这么多原理和流程,最后说几个我在实际项目中形成的习惯,也算是经验汇总。
第一,DFX工程一定要从一开始就保持RTL层面的接口契约清晰。所有RM必须拥有完全相同的接口列表,包括信号名、位宽、方向和时序约束。我曾经因为某个RM多了一个调试端口,导致整个DFX流程重跑了一遍,浪费了整整一天。
第二,自动化脚本必不可少。DFX工程的综合和实现时间远长于普通工程,全部GUI操作效率太低。我会把DFX流程做成Tcl脚本,放到持续集成环境里跑,每次改完RTL自动化跑一遍综合实现和PR Verify,有问题会收到报告。Vivado的non-project批处理模式在这类场景里非常好用。
第三,license问题别拖。DFX功能受license管控,尽早申请、尽早配置。特别是在一些严格的研发环境里,license可能只在特定服务器上有效,一旦换机器会导致DFX功能消失。
第四,从简单案例开始验证。如果你第一次接触DFX,别直接拿一个复杂的工程做实验。先建一个只有几十个LUT的小设计,用两个简单的RM跑通全流程,包括bitstream生成、ICAP加载、状态切换、时序验证,把整个流程的细节都摸透,再迁移到真实项目里去。这样一来,你手里那套成熟的DFX流程就是可复用的资产了。
最后,实际运行DFX时,建议对部分比特流加载过程做一次完整的回读校验。DFX Controller的回读功能可以确认重配区域的寄存器状态是否符合预期,这对长时间运行、环境恶劣的设备非常重要。这个功能要消耗一些配置带宽,但带来的可靠性提升很值得。