本页是关于时钟域(clock domains)的系列文章中的第三页。
范围
如上一页所述,如果一条路径(path)跨越 related 时钟的时钟域,逻辑不需要特殊处理。然而,需要确保 FPGA 工具在此路径(path)上强制执行时序约束(timing constraints),以保证同步逻辑单元(synchronous element)在目的地(setup 和 hold)的时序要求。所以这种情况的处理就好像根本没有跨时钟域,在路径是定时的意义上。
但是,如果时钟是 unrelated 时钟,则需要 resynchronization 逻辑: 必须有专门用于解决此问题的 Verilog (或其他语言)代码。本页介绍了如何执行此操作的基础知识。
如果可以,请使用 FIFO
我认为从如何完全避免这种头痛开始,有利于那些有可能逃脱的人,这将是公平的。这是:
由 FPGA 工具生成的Dual-clock FIFOs绝对是最安全的穿越时钟域的方式。这是最常见的解决方案,而且这种方法被广泛使用的事实本身就是信任它的理由。
特别是,如果您是 FPGAs的新手,那么有充分的理由更喜欢 FIFO,即使您需要为此目的稍微更改自己的设计。使用 FIFO 可能会消耗比为您的目的量身定制的逻辑稍多的资源。但请注意,如果 FIFO的深度较浅,通常不需要 block RAM 。因此,差异不一定值得冒险搞砸。
FIFO 通常是跨越时钟域的自然解决方案,特别是当数据数据流(data stream)从一个功能单元到另一个功能单元时。但即使不是那么自然,也通常可以重新组织逻辑以使其适合。例如,如果一个时钟域(clock domain)中的逻辑需要通知另一个时钟域中的逻辑某些事件,这可以通过对消息字进行编码并将其写入浅层 FIFO来完成。这种解决方案不仅不易出现错误,而且还可能导致设计更有条理和可读性。
如何连接和使用 FIFOs 在本系列页面中进行了广泛讨论。
Resynchronization 逻辑用于单个比特(bit)
如果 FIFO 不能解决您的问题,是时候卷起袖子,实施 resynchronization 逻辑以获得安全的跨时钟域。本页的其余部分仅限于使用具有单个比特宽度的信号穿过时钟域。这是任何 resynchronization 逻辑的基石,对于这个看似简单的案例有很多话要说。
本系列的下一页讨论如何使用更宽的信号通过时钟域,基于下面介绍的 metastability guard 技术。
Metastability
在继续讨论解决方案之前,重要的是要了解当触发器(flip-flop)的时序要求(timing requirements)被违反时会发生什么。换句话说,当数据输入(data input)处的信号在 tsu 开始于时钟边沿(clock edge)之前、 thold 结束于时钟边沿之后的时间段内不稳定时。相关的时钟边沿当然是触发器的时钟输入处的时钟边沿。正是这个时钟边沿(clock edge)导致它对数据输入进行采样。
显然,如果在这个时间段内数据发生变化,那么这个时钟边沿之后的触发器的输出(output)是不可预知的。但它甚至比这样更糟糕: 触发器可能需要比平常更长的时间才能在触发器的输出上呈现合法的 '0' 或 '1' 。尽管触发器旨在使输出稳定在这两个可能值之一(即 '0' 或 '1')上,但违反时序要求可能会导致触发器短暂处于不稳定状态。或者,用官方术语来说,触发器是 metastable。
我不会跳过这个陈词滥调的形象,即球站在山顶,这通常用于描绘触发器可以达到的不稳定情况:
Metastability 是一个更大的问题,而不仅仅是触发器最终登陆的不确定性。如果触发器的输出连接到多个目的地,则尤其如此: 由于在 '0' 或 '1'上着陆需要较长时间,因此对于到达目的地的触发器而言,信号可能变得稳定太晚。因此,目的地的一些触发器可能会像 '0' 一样接收到摆动信号,而其他触发器可能会像 '1'一样接收到它。这种不匹配会使逻辑进入非法状态,从这种情况下,逻辑可以表现出 black magic 的风格。因此,对于 metastability 有风险的触发器永远不应连接到多个逻辑单元(logic element)。
为了解决 metastability,很高兴知道触发器需要多长时间才能进入其稳定状态之一。不幸的是,对此没有明确的答案。这就像问球会在山顶停留多长时间: 这取决于很多因素,随机振动最终会使它以这种或另一种方式倒下。与触发器的 metastability相同: 由于电子电路中的随机噪声或其他任何原因,它将脱离这种状态。
所以理论上,一台触发器可以永远保持 metastability 的状态,但实际上它会在一段时间后进入它的一种稳定状态。它停留在这种状态的时间是 random 变量。为了估计这款 random 变量的行为,已经进行了大量的实验和模拟。然而,这些尝试都不是真正相关的,因为行为取决于 silicon的制造技术、温度、串扰(crosstalk)的噪音水平等等。
所以再一次,即使在 metastability的状态下有一个触发器(flip-flop)永远不会超过的指定最大时间会很方便,但没有这样的限制。甚至不可能得到一个大概的数字,因为更新的制造工艺使得触发器倾向于更快地离开 metastability 的状态。
相反,这是习惯的想法: 当您将时钟域与 unrelated 时钟一起使用时,总是有可能一些触发器将保持 metastability 状态的时间超过设计可以容忍的时间,因此会出现问题。作为设计师(designers),我们唯一能做的就是降低这种风险。我们充其量只能实现一个可以忍受的 MTBF (Mean Time Between Failure)。
幸运的是,有一种成熟的技术可以实现这一点,这将我们带到下一个主题。
metastability guard
长话短说,让我们重温上一页的第一个代码示例。如果 @clk1 和 @clk2 是 unrelated 时钟,那么从 @foo 获得稳定 @bar 的常用 resynchronization 逻辑是
reg foo, bar, bar_metaguard;
always @(posedge clk1)
foo <= !foo;
always @(posedge clk2)
begin
bar_metaguard <= foo;
bar <= bar_metaguard;
end
顾名思义, @bar_metaguard 是 metastability guard。在对 @foo进行采样时,偶尔会违反实现 @bar_metaguard 的触发器的时序要求。因此, @bar_metaguard 可能会出现短暂的 metastability时刻。由于预计此触发器会从此状态快速恢复,因此它将足够快地稳定下来以满足 @bar的 setup 时序要求。因此, @bar 可以在 @clk2的时钟域内可靠地使用。
这种解释听起来可能不准确,但确实如此。它实际上与我在上面写的关于 metastability 的内容相矛盾,因为 metastability没有故障安全解决方案。我将在下面进一步讨论。但是现在,我们还是按照惯例,如上图添加 metastability guard ,不用再操心了。说实话,我从来没有听说过有人对此有问题。
想要更加安全的人,请添加更多寄存器(registers)。所以双 metastability guard 是这样完成的:
reg foo, bar, bar_metaguard_a, bar_metaguard_b;
always @(posedge clk1)
foo <= !foo;
always @(posedge clk2)
begin
bar_metaguard_a <= foo;
bar_metaguard_b <= bar_metaguard_a;
bar <= bar_metaguard_b;
end
@bar_metaguard_a 是第一 metastability guard。如果我们运气不好,它会停留在 metastability 状态太久,并且 @bar_metaguard_b的时序要求被违反。因此, @bar_metaguard_b 在下一个时钟周期(clock cycle)上有一 metastability 周期。但这次 metastability 的周期很短,我们可以希望: 他们说,闪电从来不会两次击中一个地方。但当然 @bar_metaguard_b 也可以在 metastability 状态下保持足够长的时间来违反 @bar的时序(timing)。但这种情况的概率是多少?请记住,目标是获得合理的 MTBF。
总结一下: 如果您正在寻找有关如何通过单个比特处理时钟域的食谱解决方案,就是这样。一 metastability guard 或两 metastability guards 可以很好地完成这项工作。如果您正在寻找一个永不失败的解决方案,不幸的是这是不可能的。但是,如果您想尽可能减少发生事故的可能性,请继续阅读。
时序分析(Timing analysis)的 metastability
因此,现在让我们回到上面使用单 metastability guard的示例,并问为什么我如此确定 @bar_metaguard 从 metastability 中恢复得足够快。正如已经提到的,答案是没有理由确定。
不过,让我们来做一个时序分析(timing analysis)。 @bar_metaguard 和 @bar 都与同一个时钟同步。假设没有专门为这些寄存器分配特殊的时序约束,那么它们之间的路径将受到 @clk2的时序约束(时钟周期(clock period)) 的限制。也就是说,工具保证在没有 metastability的情况下, @bar 的输入信号是用合法的时序采样的。
但 @bar_metaguard 的重点是偶尔允许使用 metastability 。因此时序分析不够用: 实际上,触发器在 metastability 状态下所花的时间是其 clock-to-output 时间的附加值。换句话说, metastability 意味着触发器的输出比其通常的时延(delay)更晚稳定。因此,如果 @bar_metaguard 和 @bar 之间的路径的 slack 接近于零(即其传播延迟(propagation delay)足以达到时序,但没有余量), metastability 可能会使路径的总时延超过允许的限制。此类事件会导致 @bar的输入上出现时序违反(timing violation)。当然,这适用于温度、电压和制造工艺方面的最坏情况,但是: 事实上,这些工具将常规的时序约束应用于 metastability guard的输出,这意味着它不能确保必要的时序要求。
幸运的是,这很容易解决。或者改进,我应该说。方法是在需要可靠的 metastability guards 到寄存器的所有路径上加一个更紧的时序约束。换句话说,这个想法是在这些路径上允许更少的传播延迟。通过减少传播延迟允许的时间,这个时间是为了从 metastability恢复。
例如,如果所有 metastability guard 寄存器都具有 *_metaguard 后缀,则可以编写单个时序约束(timing constraint)以要求来自它们的所有路径获得一些额外的稳定时间。对于 Vivado,这样的约束(constraint)会说:
set_max_delay -from [ get_cells -hier -filter {name=~*_metaguard*} ] 0.75
(set_max_delay 在时序异常页面有说明)
这个时序约束意味着任何以 metastability guard 开始的路径都有 0.75 ns 到达其目的地(这些路径由寄存器名称的后缀选择)。这是我在我尝试过的特定 FPGA 上不使时序约束失败而设法获得的最小延迟(因此在其他 FPGAs上可能有所不同)。达到这个数字的方法是选择一个较低的数字,直到工具无法达到这个约束(constraint)。出现此故障时,根据需要增加此数字以获得非常小的 slack (例如 0.2 ns)。这迫使工具在这些路径上发挥最大的作用。
此 set_max_delay 命令相当于请求 0.75ns (1333 MHz) 的时钟周期。因此,例如,如果实际的时钟周期是 250 MHz (4 ns),则实现此时序约束至少会多出 4 – 0.75 = 3.25 ns。因此,如果 metastability guards 处于 metastability 状态长达 3.25 ns,则由于有此 set_max_delay 约束,不会发生时序违反。
这当然比完全没有保证要好,但是 3.25 ns 就够了吗?很多吗?是否足以确保时钟域的故障安全过渡?
如前所述,这个问题的答案取决于几个因素。从已发表的实验来看,我个人的印象是,在今天实际使用的任何 FPGA 上,极不可能看到触发器保持 metastability 状态的时间与 1 ns一样长。但是在这件事上很难说任何肯定的话。
但是通过添加这个时序约束,从而推动工具发挥最大作用,您可以使您的 FPGA 设计比其他人的情况更符合或更好。因此,本着黄金法则#4 (don't push your luck) 的精神,让自己处于一个位置,如果它对你不利,那么很多其他人也会有理由抱怨。
关于时序的讨论的另一个见解是,如果时钟的频率相对于使用的 FPGA 而言较高,那么双 metastability guard 可能是个好主意。如果不使用上面建议的额外时序约束,则尤其如此: 如上所述, metastability 消耗了路径的 slack。随着时钟周期变小(频率变高),通常 slack会越来越少,因此 metastability的额外时间也会越来越少。
最后,让我们问一下,为什么几乎每个人都忽略了有关时序的整个问题,但却没有人抱怨问题。我将提供一些可能的解释,但这些只是猜测。
所以第一个解释是,这些工具很有可能将 metastability guard 在物理上非常靠近 FPGA的逻辑阵列(logic fabric)与另一个触发器。因此,这两个触发器之间的传播延迟(例如 @bar_metaguard 到 @bar)就 FPGA 而言非常短。但是,如上所述,如果没有明确的时序约束,则无法确保这种紧密的布局。
在 FPGA的供应商提供的官方 FIFOs 中,这种类型的触发器对经常出现在同一 slice上。无论如何,可以相信官方 FIFOs 已经解决了这个问题。
另一件事是,随着 FPGAs 变得更快,更高的时钟频率成为可能,事实证明触发器也更快地摆脱了 metastability 。因此,从 metastability 和传播延迟恢复所需的时间,仍然比时钟的时钟周期短得多。
因此,大多数人只是添加 metastability guard 而不再担心它也就不足为奇了。然而,这并不是懒惰添加约束的借口。
时钟的频率限制
到目前为止,我还没有对任何时钟域中时钟的频率说太多。例如,如果源的时钟域(@clk1) 中的时钟比目标的时钟(@clk2) 快怎么办?
因为时钟之间不需要同步,所以源的频率本身是更高还是更低并不重要。但是,如果源的信号 (@foo) 变化太快,它可能会在目的地的触发器有机会对这种变化进行采样之前来回变化。因此,如果所有转换必须在目标处可见,则源的时钟周期必须比目标的时钟稍长(即具有较低的频率)。
还要多久?不是很多。或者,如果您坚持要准确的答案: 这主要取决于目的地的时序要求和触发器。下面是一个简单的计算(除非您真的感兴趣,否则请跳过此步骤):
如果此触发器的输入在触发器的时钟边沿之前恰好改变 tsu ,则它处于正确采样的边缘,因此必须在下一个时钟周期上对其进行采样。否则,也许会错过此变化。为了在下一个时钟周期上可靠地采样此变化,输入信号必须保持稳定,直到下一个时钟边沿之后的 thold 。因此,源的时钟周期必须比目标的时钟周期长 tsu + thold + 2tj。 tj 是时钟周期(抖动(jitter))中的不确定性。抖动被计算两次,一次针对源的时钟,第二次针对目的地(destination)的时钟。
所以 tsu + thold + 2tj 就是我对源时钟的“稍长”时钟周期的意思。
讲述 metastability guards的工具
一些 FPGA 工具的文档建议在触发器上加一个属性(attribute)作为 metastability guards使用。这可以防止工具对这些触发器进行操作。它还鼓励工具将 metastability guard 放在与下一个触发器相同的 slice 上,因此布线(routing)尽可能短。
例如, Vivado 有一个名为 ASYNC_REG的属性(attribute)。所以和以前一样,如果所有 metastability guard 寄存器都有 *_metaguard 后缀,则 XDC 文件中的这一行会根据需要设置属性:
set_property ASYNC_REG true [get_cells -hier -filter {name=~*_metaguard*}]
也可以在 Verilog 代码中设置此属性。
但是,这个属性不太可能产生任何影响,因为这些工具通常无论如何都能正确处理 metastability guards 。