这是关于 Xilinx Vivado 部分重配置(Partial Reconfiguration,即动态功能交换 DFX)的一个系列四篇文章中的第三篇。前两篇展示了如何设置 FPGA 设计,本篇则讨论静态逻辑如何应对可重构逻辑消失又重现的过程。
概述
加载部分比特流(partial bitstream)的过程与物理硬件的热插拔(hot swapping)很相似:某一部分被突然取下,换上另一部分,然后重新上电。本文讨论如何确保这种切换能够平稳而可靠地进行。
本文假设部分重配置是由 FPGA 上的静态逻辑自己来执行的。为了确保操作平滑,建议按以下几个阶段进行(详细解释见下文):
- 使可重构逻辑进入安全状态,这样对它进行复位时,不会因为它的输出而影响任何相关电路。
- 激活可重构逻辑的复位信号。
- 启动解耦(decoupling)。
- 将部分比特流送入 ICAP。
- 等待配置的 STARTUP 序列完成。
- 关闭解耦。
- 撤销可重构逻辑的复位信号。
为什么需要解耦
在加载部分比特流的过程中,静态逻辑与可重构逻辑之间的连接处于不可预测的状态。因此,可重构模块的输出端口可能会产生随机模式或非法电平。这不一定意味着每个输出端口在所有时刻都会如此,但十有八九会出现某种异常行为。
由于静态逻辑在加载部分比特流期间不会停止工作,因此有必要忽略可重构模块可能送来的不可预测信号,以避免任何不良影响。Xilinx 的用户指南 UG909 把这种处理称为解耦(decoupling)。
如何实现解耦,取决于可重构模块输出端口的性质:应该分析每个输出端口对静态逻辑的影响,并在必要时采取预防措施。例如,可以在这段重配置期间把输出端口多路复用到中性值,为不希望其响应的逻辑添加时钟使能(clock enable),或者让静态逻辑的某些部分保持在复位状态。
如果可重构逻辑直接连接到 I/O 焊盘,可能有必要把这些焊盘置于高阻态(high-Z),或者关闭 I/O 逻辑的时钟使能(如果该 I/O 逻辑包含输出寄存器的话)。另外,如果可重构逻辑连接了外部器件,可能还需要在重配置之前先把这些外部器件带到安全状态。
Vivado 的 IP Catalog 中提供了一个用于 AXI 连接解耦的部分重配置解耦器 IP 核(Partial Reconfiguration Decoupler IP)。
可重构模块的输入端口不需要这样的处理——静态逻辑并不在乎它所驱动的信号有没有被接收。
对于 UltraScale 器件,由于清除比特流(clearing bitstream)会把可重构逻辑关闭,因此在加载清除比特流之前必须先进行解耦。
关于 STARTUP 序列的几点说明
完整比特流和部分比特流中都有一个重要部分,即 START 配置命令,它会触发 STARTUP 序列。该序列包含几个机制,目的都是让逻辑以一致的方式完成启动(bring-up)。
第一个机制是,在配置结束时,所有同步元件都会被赋予默认值。对于 UltraScale 及之后的 FPGA,这一点始终成立。对于 7 系列 FPGA,在全配置(full configuration,即使用初始比特流)以及启用了 RESET_AFTER_RECONFIG 的部分重配置中,这一点也成立。
第二个机制是 GWE(Global Write Enable,全局写使能;不要与同步元件的使能输入或写使能输入混淆)。这个信号允许触发器和 RAM 改变数值。在全配置期间,整个 FPGA 的 GWE 保持为低电平,然后在配置启动序列的某个阶段变为高电平。加载部分比特流时,只有被重配置的逻辑会受到影响。
自然地,相对于应用逻辑提供的任何时钟而言,GWE 的变化都是异步的,因此这个变化与第一个有效时钟之间的时序,对任何同步元件来说都是不可预测的。
在部分重配置场景中,与全配置一样,这意味着在流程结束之后(对于 UltraScale 及之后的器件,或者设置了 RESET_AFTER_RECONFIG 时),所有同步元件都会立即呈现出它们的初始值。但存在一种随机可能性:某些同步元件会对应用逻辑的第一个时钟周期作出响应,而另外一些则不会。这种随机行为取决于第一个时钟相对于 GWE 变为高电平的时刻何时到来。因此,对所有对这种不确定性敏感的逻辑,正确地施加复位非常重要。
加载部分比特流之后是否需要复位可重构逻辑,这与全配置之后的情况是一样的。不过,在全配置情况下需要复位这一点更直观,尤其是因为复位通常要保持有效,直到某些逻辑单元稳定下来(例如 MMCM 或 PLL 已经锁定、外部硬件已经就绪等等)。
总而言之,对于可重构逻辑是否需要复位、以及复位其中哪些部分,并没有一个唯一的答案。与全配置一样,同步元件会被赋予默认值,并开始响应时钟。在某些情况下这已经足够了,但在另一些场景下则需要复位。
检测 STARTUP 结束
在上面列出的阶段中,唯一不受应用逻辑控制的部分是 STARTUP 序列。然而,知道它什么时候结束非常重要。
每种 FPGA 系列的 Configuration Guide 中都有对 STARTUP 序列的描述,但长话短说,这个序列所需的时间在很大程度上取决于比特流的选项。例如,可以把该序列配置为等待 MMCM 锁定,或者等待 DCI 完成阻抗匹配。
FPGA 会提供一个名为 End Of Startup(EOS)的信号,它在 STARTUP 序列的最后一个阶段(也就是序列结束时)变为高电平。依靠 EOS 来判断何时让可重构逻辑恢复,是规范上正确的做法:发起复位,并开始恢复输出端口的耦合(recoupling)。
EOS 信号只能从逻辑阵列内部获得,方法是实例化(instantiation)一个 STARTUPE2 原语(primitive),例如下面这样:
wire eos;
STARTUPE2 #(.PROG_USR("FALSE")) startup_ins
(
.CLK(1'b0),
.GSR(1'b0),
.GTS(1'b0),
.KEYCLEARB(1'b1),
.PACK(1'b0),
.USRCCLKO(1'b0),
.USRCCLKTS(1'b0),
.USRDONEO(1'b1),
.USRDONETS(1'b1),
.CFGCLK(),
.CFGMCLK(),
.EOS(eos),
.PREQ());
因此,当比特流已经全部送入 ICAP 之后,等待 EOS 变为高电平,然后开始复位和重新耦合。
UltraScale FPGA 用的是 STARTUPE3 原语,不过 Vivado 也接受在这些 FPGA 上使用 STARTUPE2 原语,并会正确地把它转换为 STARTUPE3。所以上面的代码示例适用于所有 FPGA 系列。
我做了一些零星的非正式测试,用来测量从比特流的 START 命令到达 ICAP,到 EOS 变为高电平需要多长时间。
对于 Kintex-7,在比特流默认设置下,这个过程用了 26 个时钟周期(在 100 MHz 下,即大约 260 ns)。由于比特流中还有其它附加数据,其中包括一些 NOP,所以 EOS 很可能是在比特流最后一个数据字被送入 ICAP 后不久就变高了。
但对 Kintex UltraScale FPGA 做同样的测试,结果完全不同:在 START 命令之后,EOS 随机地在大约 0.8 ms 到 4.5 ms 之后才变为高电平。
虽然按上面那样使用 STARTUPE2 原语相当容易,但也可以选择在比特流加载完成后等待一段固定时间,再开始重新耦合。例如,很难想象 STARTUP 序列会花掉 100 ms 之久,而且对人类来说,这么短的延迟几乎感觉不到。不过,考虑到上面两个测试结果,使用 STARTUPE2 原语似乎才是稳妥的做法。
至于 UltraScale FPGA 在加载清除比特流后 EOS 的行为,显然没有见诸文档。但在一次非正式测试中,EOS 在加载清除比特流后保持低电平,直到随后加载的部分比特流完成之后才变为高电平。
第三篇到此结束。最后一篇将深入幕后,探讨 Vivado 如何借助 OOC 与 DCP 处理静态逻辑和可重构逻辑之间的关系,以及理解这一点如何为实现远程更新场景下的部分比特流提供一条可靠之路。