简介
这是 一个系列的四篇文章中的第二篇,讨论 Xilinx Vivado 中的部分重配置(Partial Reconfiguration,即动态功能交换 DFX)。本文的目标是逐步讲解如何在 FPGA 设计中启用部分重配置,并解释其中的每个步骤。如果你还没读过第一篇,建议先读一下,因为它解释了这些步骤背后的概念。
Xilinx 在 2020 年把部分重配置重新命名为 Dynamic Function eXchange(DFX)。DFX 是 Vivado 的菜单和提示信息中使用的表述。不过,本文仍然使用技术术语“部分重配置”。
为简单起见,本文假设工程中只有一个可重构分区。将其扩展到多个分区是相当直接的事。
本文描述的操作步骤从工程尚未启用部分重配置时开始。粗略划分,步骤如下:
- 为布局规划准备工程
- 为部分重配置做初步工程设置
- 布局规划(floorplanning)
- 通过对设计进行实现来检查并修正布局规划
- 添加第二个(或多个)可重构模块
- 执行实现以获得比特流文件
- 检查工程结果
布局规划的准备
在所有这些工作中,布局规划是最需要动脑的一步。它需要在“不浪费 FPGA 逻辑区域”和“确保静态分区与可重构分区在布局布线过程中不会遇到重大障碍”之间取得微妙的平衡。
因此,本文的大部分篇幅都会讨论这个话题。
在很久以前,布局规划是一种用于实现时序收敛(timing closure)的技术,它能帮助工具以合理的方式摆放逻辑。随着 FPGA 设计工具不断改进,我已经很多年没见过布局规划能帮助满足时序约束(timing constraints)了。今天,实现时序收敛的最佳策略几乎总是让工具自己做决定。
在使用部分重配置时,布局规划是必须的,因此目标是不把事情弄得更糟。这通常要靠试错。不过,获得良好结果的最简单方法是:先不加任何布局规划约束,对设计做一次实现,然后以逻辑的自然布局为起点,再尝试以适合部分重配置的方式来组织各个区域。
在插件式使用(plugin usage)场景下,布局规划可以随着工程的发展而更新。但在远程更新(Remote Update)场景下却不能这样做。所谓远程更新,是指把部分重配置用作对一个已发布设计进行版本升级的手段:在远程更新中,所有部分比特流都必须与初始比特流匹配。因此,一旦初始比特流发布,设计中的静态逻辑部分就被冻结了。这还意味着,布局规划也必须保持不变。
所以,即使在开始做部分重配置之前,首要任务也是在 FPGA 中为静态逻辑找到一个合适的区域。在这上面花太多时间没有意义,只需要获得一个合适的起点,以便之后把工程拆成两半时继续使用即可。
别搞混淆:这一步的目的不是布局规划本身,而是想看看 Vivado 在没有限制时会把逻辑放在哪里,并据此决定应把哪个区域留给静态逻辑。步骤如下:
- 照常对设计进行一次实现。打开实现后的设计(implemented design),查看器件视图。尝试了解静态设计大约需要多少逻辑资源,以及 Vivado 倾向于怎样摆放这些资源。
- 如果把打算作为可重构逻辑的部分临时从工程中移除(但要注意别因为逻辑优化而把静态逻辑也一并删掉),这一步做起来可能会更容易。
- 确保 Device 视图中没有选中任何逻辑单元,然后在视图某个位置右键,选择 Draw Pblock。画出一个看起来适合容纳静态逻辑的区域。不必刻意模仿 Vivado 原本的布局,而是尽量找到一个既能占用最小面积、又不会给逻辑布局和满足时序约束造成障碍的形状。
- 这时 Vivado 会弹出一个对话框,显示 Create a new Pblock。它可能会建议按时钟区域(clock region)来定义 Pblock;如果是这样,别这么做。请选择基于 slice、DSP 以及可能的其它逻辑单元的 Pblock 类型。
- 对于 UltraScale FPGA,Pblock 对话框可能还会建议把 IOB 包含进来。如果是这样,请取消该选项,否则 Vivado 之后在保存或调整 Pblock 时可能会卡住(这是 Vivado 的一个 bug)。
- 注意 Pblock 的形状,尤其是 slice 的范围。这些信息可以从 Vivado GUI 的 Pblock Properties 面板(General 标签页)中看到,也可以从 Tcl Console 获得;控制台里会显示类似这样的内容:
startgroup create_pblock pblock_1 resize_pblock pblock_1 -add {SLICE_X108Y148:SLICE_X149Y249 DSP48_X4Y60:DSP48_X5Y99 RAMB18_X4Y60:RAMB18_X6Y99 RAMB36_X4Y30:RAMB36_X6Y49} endgroup - 如果 Tcl Console 里出现警告,忽略它们即可。
- 关闭实现后的设计时,Vivado 会询问是否保存。选择 No,因为刚刚创建的 Pblock 没有用处。
把工程设置为支持部分重配置
Xilinx 的 UG909 给出了两种部分重配置的工作流程:
- 非工程流程(Non-Project Flow,见其第 3 章),即通过显式编写并运行 Tcl 脚本(script)来完成实现。
- 工程流程(Project Flow,见第 4 章),即使用 Vivado 的 GUI 以及它自动生成的脚本。
本文选用工程流程,虽然它有一些限制,其中一部分与可重构模块中的源文件类型有关(尤其是使用 block design 时)。无论如何,从工程流程入手是比较好的,因为工程流程为实现所生成的脚本,在需要时可以作为非工程流程的良好基础。
下面是在一个已有工程上启用部分重配置支持的步骤:
- 选择 Tools > Enable Dynamic Function eXchange…,然后点击 Convert。GUI 会提醒你:把工程转换为部分可重构流程是不可逆的;同意即可。实际执行的 Tcl 命令是
set_property PR_FLOW 1 [current_project]
- 决定可重构分区的顶层模块是哪一个,然后在 Project Manager 的 Sources 面板中右键点击该模块的源文件,选择 Create Partition Definition…。这个选项只有在启用 DFX 之后才可用,而你刚刚已经启用了。
- 这时会出现 Create Partition Definition 对话框,询问两件事:第一,Partition Definition 的名称,这个名称将用来指代逻辑层次中的某个位置,也就是可以插入不同可重构模块的那个层次位置。一个合适的名称可以是 “pr”。第二,Reconfigurable Module Name,它说明把哪个逻辑放进该分区。例如,如果要用部分重配置来替换音频滤波器,那么 Reconfigurable Module Name 可以合理地命名为 “lpf”、“bpf”、“hpf” 等,每个名称表示所用的滤波器类型。也可以直接使用顶层模块的名称,只要这有助于理解其功能就行。
- 所选模块在 Sources 列表中的那一行现在会显示一个黄色菱形标志,同时显示模块名和实例名(例如 “pr_block” 和 “pr_block_ins”),这些名字是在 Verilog / VHDL 文件中定义的。这些名字并不表示分区中插入的是哪个逻辑,而只是反映它们在 HDL 中的名称。可以在同一个 Sources 面板的 Partition Definitions 标签页中找到该分区和可重构模块。
- 如果可重构模块中含有 IP 核的实例化(例如 FIFO),可以右键点击该 IP 在工程主源文件列表(Hierarchy 面板)中的行,选择 Move to configurable module… 来把它移入可重构模块。对应的 Tcl 命令大致是:
move_files -of_objects [get_reconfig_modules lpf] [get_files /path/to/blkmem.xci]这会把这个 IP 核移到 Partition Definitions 标签页中。 - 不仅如此,Partition Definitions 标签页实际上就像一组源文件层次的集合,每个可重构模块各有一个。例如,要为某个可重构模块添加它需要的 HDL 文件,点击该标签页下的“+”即可。
- 可重构模块设置好之后,定义父实现(Parent Implementation)。关于父实现、子实现(Child Implementation)和向导的介绍,请参见上一篇:
- 选择 Tools > Dynamic Function eXchange Wizard。
- 在欢迎页面和编辑可重构模块的页面上点击 Next。
- 在 Edit Configurations 页面,点击“+”添加一个配置。默认的 config_1 名称就可以了,因为它不太重要。默认情况下,Vivado 会正确地给 config_1 选择那个可重构模块。这并不奇怪,因为目前它是唯一的一个。
- 下一个页面是用来添加配置运行(configuration run)的:现在不要添加任何运行。
- 完成向导。
在这个阶段尝试对工程做一次实现,很可能会失败,并出现类似这样的错误:[DRC HDPR-30] Missing PBLOCK On Reconfigurable Cell: HD.RECONFIGURABLE cell 'pr_block_ins' must have PBLOCK assigned to itself or its descendant cells。简单说,就是必须做布局规划。
布局规划(floorplanning)
到了这个阶段,工程的状态已经刚好足够用来做布局规划了。
在把这项任务拆成小步骤之前,有几点值得先记住:
- FPGA 上给静态逻辑的区域应尽可能小,但又不能给布局布线造成困难。前面(见上面的“布局规划的准备”)应该已经对它的大致形状有了粗略估计。
- 静态逻辑和可重构逻辑的形状都应尽量简单,最好是简单的矩形或其它不会给布线制造困难的形状。
- 静态逻辑的布线可以穿过可重构逻辑的区域,但在大多数情况下反过来是不可以的。
- 本次布局规划会话中,要画的是可重构逻辑的形状。由于上面最后两点,这个形状尤其要保持简单。
- 要清楚你所使用的具体 FPGA 在布局规划上的可能性和限制,详见 UG909 的第 6 至 8 章。例如,如果使用 7 系列 FPGA,最好把区域边界与时钟区域的边界对齐。
下面把它分解成步骤:
- 启动对工程的综合(即启动 synth_1 运行)。可重构模块的综合会自动以 Out-of-Context (OOC) 方式运行,例如 lpf_synth_1。关于 OOC 的进一步说明,请参见最后一篇。
- 综合运行完成后,打开综合后的设计(synthesized design)。此时不能进行实现,因为还没有给可重构模块关联 Pblock。
- 为可重构逻辑画一个 Pblock。与准备阶段不同,这个 Pblock 必须与可重构逻辑关联,因此:确保左上方面板打开在 Netlist 标签页,然后右键点击要放进可重构分区的顶层单元(例如 “pr_block_ins”),选择 Floorplanning > Draw Pblock,并在 FPGA 上画一个区域。GUI 操作如前面“布局规划的准备”所述;也就是说,要基于 slice 以及其它逻辑单元进行选择。
- 再次提醒,如果 Vivado 建议把 IOB 也包含进 Pblock,不要接受这个建议,否则 Vivado 之后处理它时可能会卡住。
- 这一步不必太较劲,因为很可能会因为 Vivado 报错而需要修改。再次记住:Pblock 是为可重构逻辑画的,静态逻辑使用剩下的区域。
- 接下来看 Pblock Properties 面板。如果它没有出现,可能需要在器件视图中右键点击 Pblock,然后选择 Pblock Properties…。
- 在 Pblock Properties 面板中选择 Properties 标签页。
- 对于 7 系列 FPGA(即非 UltraScale 及以后的器件):如果你希望在加载部分比特流之后让逻辑接收 FPGA 的内部复位,建议在 Pblock Properties 面板中把 RESET_AFTER_RECONFIG 设置为开启。关于可重构模块复位的更多内容,见下一篇。这会产生类似下面的 XDC 约束:
set_property RESET_AFTER_RECONFIG true [get_pblocks pblock_pr_block_ins]
除其它作用外,这个约束会把触发器(flip-flop)恢复到它们的默认值。但要注意,这与在 HDL 中或其它地方定义的复位没有任何关系。还要注意,在 7 系列 FPGA 上,这个功能要求 Pblock 的垂直边界与时钟区域对齐。
对于 UltraScale FPGA 及之后的器件,该复位始终是启用的。 - 还有一个 SNAPPING_MODE 属性。在 7 系列 FPGA 上它默认未定义(相当于 OFF)。对某些 FPGA,很可能需要把它设为 ROUTING 或 ON(UltraScale 的默认值是 ON)。这一点我后面会讲到。
- 接下来,按 CTRL-S 保存约束(或点击顶栏的磁盘图标)。这会向 XDC 文件添加几行内容,大致如下:
create_pblock pblock_pr_block_ins add_cells_to_pblock [get_pblocks pblock_pr_block_ins] [get_cells -quiet [list pr_block_ins]] resize_pblock [get_pblocks pblock_pr_block_ins] -add {SLICE_X40Y100:SLICE_X79Y149} resize_pblock [get_pblocks pblock_pr_block_ins] -add {DSP48_X2Y40:DSP48_X2Y59} resize_pblock [get_pblocks pblock_pr_block_ins] -add {RAMB18_X2Y40:RAMB18_X2Y59} resize_pblock [get_pblocks pblock_pr_block_ins] -add {RAMB36_X2Y20:RAMB36_X2Y29} - 关闭综合后的设计。
- 重置 synth_1 运行。
- 尝试生成比特流(点击 Generate Bitstream)。这次实现的目的是检查布局规划是否有缺陷。换句话说,就是看 Vivado 是否会对这些缺陷发出严重警告(Critical Warning)。这听起来也许不像一种专业的验证方法,但它既简单又可靠。
修正布局规划
这也许是部分重配置中最不令人愉快的部分:要把布局规划做到恰到好处。如果你是针对远程更新场景做这件事,这一阶段就格外重要,因为这个布局规划会在整个工程生命周期内保持不变。
需要修改布局规划的主要原因有两个:一是为了回应 Vivado 发出的严重警告,二是在后续阶段为了优化 FPGA 的使用率——目标是减少资源浪费,同时避免给布局布线制造障碍。
做修改并不困难,因为拖动 Pblock 的边界很容易;用鼠标右键点击 Pblock 并选择 Add Pblock Rectangle,也很容易给 Pblock 增加一个矩形。
严重警告往往会说明需要做什么修正。尽管如此,一定要阅读 Xilinx 用户指南 UG909 中与你所用 FPGA 布局规划限制相关的章节(第 6、7 或 8 章)。
本节其余部分讨论 7 系列 FPGA 可能遇到的问题。UltraScale FPGA 用起来要容易得多。
7 系列 FPGA 上一个常见的错误是互联瓦片列(interconnect tile column)被切开。例如:
[Constraints 18-993] The Pblock pblock_pr_block_ins has defined an area that causes the splitting of interconnect tile columns. Dynamic Function eXchange requires that the left and right paired interconnect tile columns cannot be split by a reconfigurable boundary. This is caused by either the left or right edge of a Pblock boundary, or by the Pblock spanning over logic types not included in the Pblock ranges. To avoid an unroutable situation, placement will be prohibited from both of these columns. To avoid placement restrictions, modify the Pblock to avoid splitting the two columns. The column of the split contains interconnect tile INT_L_X48Y299 (SLICE_X79Y299 SLICE_X78Y299). Please refer to the Xilinx document on Dynamic Function eXchange. Resolution: Set the Pblock property SNAPPING_MODE to value of ON, or modify the column/X specification of the pblock to avoid this edge.
以及
[Constraints 18-996] The split between the left and right columns occurs between a reconfigurable Pblock and Static logic. The static sites are not reconfigurable. The Pblock should be adjusted to remove the column from the Pblock, unless the excluded reconfigurable and static sites are not needed for the design. Note that adjusting the Pblock will prevent prohibits and improve placement of the design, but may reduce the routability if the removed sites were needed to span across the static logic. Failure to modify the Pblock may lead to an unplaceable design if these prohibited sites are required by the design. Resolution: Set the Pblock property SNAPPING_MODE to value of ON, or modify the column/X specification of the pblock to avoid this edge. and
要解决这个问题,可以按照第一条警告的建议,把 Pblock 的 SNAPPING_MODE 属性设为 ROUTING 或 ON(很有可能 ROUTING 还不够,所以请选 ON)。这样做很可能会在 XDC 文件中添加大量类似下面的 PROHIBIT 约束:
set_property PROHIBIT true [get_sites SLICE_X79Y349] set_property PROHIBIT true [get_sites SLICE_X78Y349] [ ... ] set_property PROHIBIT true [get_sites SLICE_X79Y191] set_property PROHIBIT true [get_sites SLICE_X78Y191] set_property PROHIBIT true [get_sites PMV_X0Y2] set_property PROHIBIT true [get_sites SLICE_X36Y190] set_property PROHIBIT true [get_sites SLICE_X37Y190] [ ... ] set_property PROHIBIT true [get_sites SLICE_X79Y176] set_property PROHIBIT true [get_sites SLICE_X78Y176] set_property PROHIBIT true [get_sites T14] set_property PROHIBIT true [get_sites R15] set_property PROHIBIT true [get_sites XADC_X0Y0] set_property PROHIBIT true [get_sites SLICE_X36Y175] set_property PROHIBIT true [get_sites SLICE_X37Y175] [ ... ]
后面还有一大串。
其中的 slice 位置上的 PROHIBIT 正是用来消除前述严重警告的。其它 PROHIBIT 赋值则是为那些位于几何区域内但不允许用于该 FPGA 部分重配置的逻辑位置而添加的。UltraScale FPGA 及之后的器件所产生的 PROHIBIT 行要少得多,如果有的话。
如果只是为了消除严重警告,那么把那些逐行的 PROHIBIT 删除,只留下一条针对 slice 范围的约束,通常就够了。做法是:把 Vivado 由于改变 SNAPPING_MODE 而添加的那些 slice 范围,合并成类似下面这样的一行:
set_property PROHIBIT true [get_sites -range {SLICE_X79Y0 SLICE_X79Y349}]
这样的 XDC 行既可能解决互联列被切开的问题,又不会让文件变得庞大。
无论如何,XDC 文件中还可能会出现下面这样一行。删除它似乎也没问题:
set_property HD.PLATFORM_WRAPPER true [get_cells pr_block_ins]
把 XDC 文件缩减到刚好能消除严重警告的最小程度,看起来也许有些敷衍,但否则就会得到一个巨大的约束文件,这会给之后的维护带来混乱。以我的经验,只要没有这类警告,就可以认为设计的布局规划没有问题。
请注意:即使你手动修改了 XDC 文件,只要把 SNAPPING_MODE 改回 OFF,十有八九问题又会重新出现。
添加可重构模块
到目前为止,这种实现与层次化设计相比,实际上效果差不多,只是多了一些额外限制。虽然已经生成了一个部分比特流,但它没什么用,因为加载它之后,设计不会发生任何变化。
所以接下来的目标是创建另一个部分比特流,它基于另一个可重构模块。这需要添加一个子实现(Child Implementation)。
在继续往下读之前,请确保你对上一篇的内容还有清晰的印象,尤其是关于父实现、子实现以及 Dynamic Function eXchange 向导的部分。也请记住本文前面提到的一点:Partition Definitions 标签页中保存着当前定义好的可重构模块及其源文件。
从 Tools 菜单打开 Dynamic Function eXchange Wizard,在欢迎窗口点击 Next。
在 Edit Reconfigurable Modules 窗口,点击“+”。这会打开一个添加可重构模块的对话框。这个对话框里唯一值得关注的字段是 Reconfigurable Module Name:正如前面解释过的,这个名称用来标识该可重构逻辑。
对话框还要求把这个模块与某个分区定义的名称关联起来;不过反正这里只有一个分区(因为本文假设只定义一个分区)。
要继续,至少需要添加一个 Verilog / VHDL 源文件。之后还可以从 Partition Definitions 标签页添加更多文件。如果无法从源文件本身看出用途,那么填写这个可重构模块的顶层模块名称也比较好。
回到向导后,再次点击 Next,进入 Edit Configurations 窗口。点击“+”,输入配置名称。这个名称的唯一重要性在于它会出现在 Design Runs 窗口中。诸如 config_2 之类的名字就可以了。
配置列表中出现了一行新记录。修改该行中对应分区那一列的可重构模块,使每个配置对应一个不同的可重构模块。
最后一个窗口是 Edit Configuration Runs,用于把运行与配置关联起来。最简单的做法是删除该窗口中列出的所有运行(如果有的话),然后点击 automatically create configuration runs(自动创建配置运行)。这恰好会完成你本来要手动做的事情:创建一个父运行,把它命名为 “impl_1”,然后创建若干子运行,想叫什么名字都可以,并把它们设置为 “impl_1” 的子运行。
向导会为每个运行选择一个配置,不过这很容易修改。唯一重要的是父运行与哪个配置关联。
顺便说一句,如果你在向导中删除了所有运行,所有子运行都会消失,但 impl_1 仍然保留。
终于到了:对设计进行实现
要生成比特流,照常点击 Vivado 中的 Generate Bitstreams 即可。正如上一篇已经提到的,在部分重配置工程中,每个配置都会生成两个或三个比特流。
例如,在 UltraScale FPGA 上,比特流文件可能是:
- theproject.bit:初始比特流文件,包含静态逻辑以及与该配置相关的可重构逻辑。
- pr_block_ins_lpf_partial.bit:用于加载与该配置相关的可重构逻辑的部分比特流。
- 仅在 UltraScale 上,还会有 pr_block_ins_lpf_partial_clear.bit:如果 FPGA 中已经存在某个配置,那么在加载任何新的部分比特流之前,需要先加载这个清除比特流。
注意,所有实现都会生成相同数量的比特流文件。也就是说,子实现也会生成初始比特流文件,因此完全可以先用某个子实现的初始比特流加载 FPGA,然后从那个状态继续操作。
如果想找一种通过 PCIe 或 USB 3.x 加载部分比特流的简单方法,参见这个页面。
所有实现都不应再出现严重警告,也不应因为关于 Pblock 和布局规划之类的抱怨而失败,因为这类问题应该已经解决了。如果仍然出现这种问题,就必须像上面所解释的那样修正布局规划。
有时候,当你点击 Generate Bitstream,而改动只发生在子实现中时,Vivado 可能会提示 “Bitstream generation has already completed and is up-to-date. Re-run anyway?”。这有点让人困惑,但点击 Yes 就会正确地运行子实现。子实现这种机制本身在 Vivado 中有点像附加功能,这也是为什么实现过程中状态行会出现类似 “write_bitstream complete. Child running” 的文字。
检查结果
部分重配置在很大程度上是关于布局的,所以检查实现后的设计是个好主意。你可以通过右键点击 Open Implemented Design,然后在弹出的子菜单中再次指向 Open Implemented Design,再从列表中选择要打开的实现。如果某个实现不在列表中,它可能已经打开了。
试试在 Implemented Design 视图的 Netlist 面板中右键点击可重构逻辑的顶层单元,选择 Highlight Leaf cells;然后再用另一种颜色对静态逻辑做同样的操作。
在同一个右键菜单中还有 Show Connectivity,它会在相互连接的逻辑单元之间画出白色直线。FPGA 上的实际布线路径当然与此不同,所以这些线是否穿过布局规划中的某个区域并不重要。不过,查看连接关系仍然有助于发现布局规划的整体组织是否让工具变得吃力。
某些看起来显然属于静态逻辑的单元被放进了可重构区域,反过来也一样——这是完全正常、也没问题的。真正需要警惕的是某个地方似乎出现拥塞——比如逻辑总体上被挤得太紧,或者在某个特定区域过于密集。如果可以,修改布局规划会有助于缓解这个问题。
另外还值得一看的是分区引脚(partition pin)的位置。它们在器件视图中显示为白色的水平条,如下图所示(点击图片可放大):
正如上一篇所提到的,分区引脚可以出现在可重构分区内的任何位置。不过,如果分区引脚远离分区的边界,这可能表明布线器在父实现过程中为了满足时序费了很大劲。
也可以用下面的 Tcl 命令获得分区引脚坐标的文本列表(把 pr_block_ins 改成你的可重构逻辑单元名称):
foreach s [get_pins -of [get_cells pr_block_ins]] { set partpin [get_pplocs -quiet -pins [get_pins $s]] ; puts "$s => $partpin"; }
分区引脚的坐标对应 CLB 的网格(而不是 slice 的网格)。在显示的图纸中,这些引脚被称为 Cell pins。
有些单元引脚可能没有得到分区引脚。这种情况发生在可重构模块的端口列表(和/或向量位宽)与其在静态逻辑模块中的实例化不一致时。这种不一致在 Verilog 中是合法的,但结果可能不是你想要的。运行上述 Tcl 命令可以发现这些未连接的端口,尤其是当这种不一致并非有意为之的时候。
关于 Vivado 工程设置的技术部分就到这里。不过,下一篇将讨论 FPGA 设计中一个重要的方面:如何确保逻辑替换能够可靠而顺利地进行。
