此页面是有关时钟域的系列中的三个中的第一个。
介绍
除了非常琐碎的 FPGA 设计之外,不止一个时钟与它的同步逻辑单元(synchronous elements)一起使用(触发器(flip-flops)、 block RAMs、 shift 寄存器等)。逻辑设计内部的大部分功能单元都是基于一个时钟的,所以大多数时候,多个时钟的话题并不需要特别注意。当基于一个时钟的逻辑与依赖于另一个时钟的逻辑连接时,事情变得更加困难。本系列页面讨论如何在设计中使用多个时钟。
在连接依赖于不同时钟的逻辑时,首先要回答的也是最重要的问题是是否需要 resynchronization 逻辑。这相当于询问这两个时钟是不是 related 时钟。此页面解释了这个问题以及如何回答它,以及其他相关主题。下面的很多讨论都与时序约束(timing constraints)以及在逻辑的某些元素上强制执行它们的可能性有关。出于这个原因,我将首先简要回顾一下时序(timing)。
本系列页面的讨论仅限于上升沿触发触发器(positive edge triggered flip-flops),即触发器对其时钟输入的上升沿(rising edges)做出反应。当然也存在其他同步逻辑单元,即基于时钟采样和产生信号的逻辑单元(logic elements): Shift 寄存器、 block RAMs 和一大堆其他功能元素。其中一些元素可能会对时钟的下降沿(falling edge)产生反应,甚至对两者都有反应。在这里的讨论中,为了简单起见,我忽略了所有这些。
另外,我将使用“X 与 Y同步”这个表述来表示同步逻辑单元 X(synchronous element X)的时钟输入连接到时钟 Y(clock Y)。因此,这个同步逻辑单元(synchronous element)会用这个时钟对其输入(或输入)进行采样,还会用它更新其输出(outputs)。
简述时序的基础知识
这是时序理论的简短总结。可以在单独的页面上找到更详细的解释。
考虑下图,它显示了信号路径(signal path)从一个触发器(flip-flop)到另一 LUT:
此图中的 LUT (Look-Up Table) 代表任意一组组合逻辑(combinatorial logic): I1、 I2、 I3 或 I4 的任何变化都会导致输出 O(output O)立即发生变化,数量为传播延迟(propagation delay)。
因此信号路径是这样的: 左边触发器的 Q 输出,标有 @foo,在其输入时钟、 @clk1上一个上升沿(rising edge)后变化。由于此输出连接到 I1,因此 LUT的输出 O 可能会在短暂延迟后发生变化。这进入右边触发器的数据输入(data input),也就是 D。在 @clk2上的上升沿之后,触发器将 D 复制到 Q (标记为 @bar)。
但事情没那么简单。所有触发器都有时序要求(timing requirements): 数据输入(D)在 @clk2的上升沿之前必须是稳定的 tsu ( setup time),在边沿(edge)之后保持稳定的 thold ( hold time)。
一 FPGA 中的绝大多数信号路径都与单个时钟相关,因此 @clk1 和 @clk2 是完全相同的时钟信号(我们将忽略时钟偏移(clock skew))。对于这样的路径(paths),可以计算出这两个时序要求(timing requirements)是否可以保证。
例如,让我们看一下上面的绘图以及时序要求和 tsu: 思路是找出从 @clk1 的上升沿到右边触发器的 D 输入更新稳定需要多少时间。这是路径上所有延迟的总和: 它从 @clk1 的上升沿到 @foo 更新(“时钟 to output(clock to output)”)的延迟开始,并继续所有延迟,直到右侧的触发器的 D 输入。这包括 LUT 的延迟以及逻辑单元(布线时延(routing delays)) 之间的布线延迟。
因为 @clk1 和 @clk2 是同一个时钟,所以我们知道两个触发器上的下一个上升沿什么时候是: 稍后是时钟周期(clock period)(例如 10 ns 用于 100 MHz 时钟)。所以路径的总延迟一定要低于时钟周期,有 tsu的余量。如果可以保证这一点,它可以确保 tsu 需要什么: 右侧的触发器的 D 输入在 @clk2的上升沿之前具有一定的时间余量 (tsu)。有关数字示例,请参阅有关时序理论的页面。
可以对 thold进行类似的计算。与 tsu不同,要求是右侧触发器的 D 输入在 @clk2的上升沿之后保持稳定一段时间(thold)。因此,时钟周期是无关紧要的,不予考虑(当 @clk1 和 @clk2 相同时)。重要的是在同一个时钟周期(clock cycle)上发生了什么,而不是下一个。
请注意,这种计算是可能的,因为我们知道 @clk1 的上升沿和 @clk2的上升沿之间的时间差。特别是如果 @clk1 和 @clk2 是同一个时钟,这个用来计算 tsu 的时间差就是那个时钟的时钟周期。但是如果这两个时钟的时间差不知道的话,就不能保证既不是 tsu 也不是 thold。
每个触发器都需要 tsu 和 thold 的要求。当然,这包括 FPGA上的所有触发器,但也包括外部 devices 上的触发器(即从 FPGA 的输出引脚(output pin)到外部 device ,它使用时钟对信号进行采样)。所以对于系统中的每一个触发器,我们都要问问自己,是否保证满足这两个要求。对于我们无法保证这一点的触发器,我们必须确保尽管如此(即 resynchronization 逻辑)仍能确保可靠运行的机制。简而言之,这就是 crossing 时钟域的主题。
再说一遍: 时序什么时候有保证?
以防您在上一节中迷失了方向,以下是要点:
回想一下, FPGA 设计总是包含时序约束,它通知设计工具有关时钟的频率。基于此信息,工具可确保两个时序要求(tsu) 和 thold)均满足。或者,如果工具未能满足这些要求,则会报告此失败。
如上所述,可以保证 tsu 而 thold只有在知道路径开头的时钟的上升沿和路径结尾的时钟的时间差的情况下。大多数情况下都是这种情况,因为逻辑设计的大部分由寄存器(registers)组成,寄存器依赖于与同一时钟同步的其他寄存器。
但是如果使用两个不同的时钟会怎么样?如果一个时钟在路径的开头连接到触发器,而另一个时钟在路径的结尾连接到触发器?换句话说,如果 @clk1 与 @clk2不一样会怎么样?是否仍然可以进行时序计算并确保时序要求?嗯,这取决于情况。这就是本页其余部分的内容。
时钟域和跨时钟域(clock domain crossing)
clock domain 由与某个时钟信号同步的所有同步逻辑单元(即触发器等)组成。
考虑这个简单的 Verilog 代码片段:
reg foo, bar;
always @(posedge clk1)
foo <= !foo;
always @(posedge clk2)
bar <= foo;
在本例中, @foo 与 @clk1同步, @bar 与 @clk2同步。很明显, @foo 和 @bar 属于不同的时钟域(分别属于 @clk1 和 @clk2)。
@foo无需担心: 它只取决于它自己。但是 @bar 与 @clk2 同步,并且依赖于 @foo, @foo与 @clk1同步。那么 @bar 可以和任何寄存器一样使用吗?我们是否可以假设 @bar 总是以合法的时间获得 @foo 的值,所以它的行为是已知的和可重复的?
在尝试回答这个问题之前,让我们为刚刚发生的事情命名。这是 clock domain crossing: @foo 和 @bar 实现为两个触发器,它们与不同的时钟同步。所以从 @foo 到 @bar 的路径从一个时钟域(clock domain)到另一个。
更一般地, clock domain crossing 是指当属于一个时钟域的同步逻辑单元的输出结束于属于另一个时钟域的同步逻辑单元的输入时。这两个同步逻辑单元之间经常有组合逻辑。
所以即使 @bar 被定义为
always @(posedge clk2)
bar <= !foo || !bar;
这里还有一个跨时钟域(clock domain crossing)。在这种情况下,它会从 @foo开始,经过实现逻辑函数的 LUT ,并在 @bar结束,如上图所示。
Related 时钟与 unrelated 时钟
术语 related clocks 指的是从相同的参考时钟(reference clock)派生的时钟,以确保它们的上升沿和下降沿之间可预测的时间差(除了已知的不准确和抖动(jitter))。
通常使用术语“同步时钟(synchronous clocks)”代替“related 时钟”。同样,通常使用“异步时钟”代替“unrelated 时钟”。
related 时钟的一个常见示例是使用单个 FPGA 锁相环生成多个时钟,它们的频率之间具有已知的关系。在这种情况下, FPGA 工具通常会排列时钟缓冲器(clock buffers),使时钟边沿(clock edges)尽可能对齐。
例如,当参考时钟乘以 2 和 3 时:
在此示例中, x1 clk、 x2 clk 和 x3 clk 是 related 时钟,因为每对时钟之间的时序是可预测的。
在一般情况下,参考时钟是与其他任何时钟相关的 unrelated 时钟: 参考时钟与边沿之间的时序相对于其他时钟可能会随温度和其他因素而变化。但是,如果锁相环(PLL)配置为确保参考时钟和锁相环的输出之间的可预测对齐,那么即使参考时钟也是 related 时钟。
时钟之间的时间关系知识允许在时钟域之间进行路径的时间计算。例如, x1 clk 的 domains 和 x2 clk 之间的路径的时序计算与 x2 clk 和自身之间的路径的要求相同。这是因为这两个时钟之间的最短时间差与 x2 clk的两个上升沿之间的时间相同。
同样,可以计算出 x2 clk 和 x3 clk 之间的路径,但最短的时间差仅为 x1 clk和 period的六分之一。因此,最坏情况的时序要求对应于想象中的 x6 clk。原因是 x2 clk 的第二个上升沿和 x3 clk的第三个上升沿之间的时间差。
有关如何计算时序的更详细讨论,请参阅此页面。
所以这个例子的重点是说一些即使在一般情况下也是正确的: 使用 related 时钟,可以通过在这些时钟之间对路径强制执行时序约束来保证时序 ,就像在同一个时钟域中强制执行路径一样。当 FPGA 工具有意确保时钟边沿对齐时,这是可能的。如示例中所示,时序要求通常比每个时钟单独执行更严格,有时甚至严格得多。
但是请注意,两个时钟派生自同一个参考时钟(reference clock)的事实并不一定使它们成为 related 时钟。特别是,如果这些时钟之间的偏移(skew)不受控制或未知(除非设计工具明确使用具有相同延迟的时钟 distribution (clock distribution)资源,否则会出现这种情况),则应将它们视为 unrelated 时钟。
甚至 clk x1 也不一定与其参考时钟相关,即使它们具有完全相同的频率。除非这些时钟故意相互对齐,否则它们的相位 relation (phase relation)是未知的。
由于这个与相位(phase)相关的主题,我在上面时钟域的定义中写了“某个时钟 signal(clock signal)”,而不仅仅是“某个时钟”。例如,“某个时钟”可能表示电路板上的同一个时钟,它连接到 FPGA的两个不同输入引脚。另一方面,“某个时钟信号”是指 Verilog 中的线(wire),或设计的网表(netlist)中的线,它代表时钟信号,如上例中的 @clk1 和 @clk2 。它通过端口在例化(instantiations)中的连接或使用简单的“assign”语句分布在 Verilog 设计中。
时钟与 Verilog 中的信号完全相同这一事实意味着这些工具将确保使用确保硬件上时钟偏移较低的资源。它还对这些工具是否将时钟视为 related 时钟有影响(更多内容见下文)。
Related 时钟、时序约束和 resynchronization 逻辑
让我们回到最初的问题: 上例中的 @bar 可以像任何寄存器一样使用吗?或者更一般地说,如果触发器是从一个时钟域到另一个时钟域的路径的目的地,我们能否像任何触发器的输出一样可靠地使用它的输出?当至少一个到达其数据输入的路径与另一个时钟同步时,这与询问是否可以确保此触发器的 setup time 和 hold time 相同。
答案很简单: 如果两边的时钟(本例中的@clk1 和 @clk2 )都是 related clocks,那么目的地的触发器的输出就完全没问题了,可以像任何寄存器一样使用,前提是这条路径(path)的时序约束合适。否则,无法确保目的地的时序,必须添加 resynchronization 逻辑来解决这个问题。
此页面显示了两个 related 时钟之间的一条路径的完整时序分析(timing analysis)。
在上面冗长的讨论之后,是时候将其总结为一些简单的规则了:
- 在属于 unrelated 时钟的时钟域之间对路径强制执行时序约束是不可能的。
- 如果两个时钟域之间的所有路径上都有 resynchronization 逻辑,则无需对这些路径强制执行时序约束。
- 如果两个时钟域之间的路径上没有 resynchronization 逻辑,则必须在此路径上强制执行时序约束。
第一条规则是最简单的: 如果您不能确定两个时钟是 related 时钟,请确保在时钟域之间的所有路径上都有 resynchronization 逻辑(下页说明如何)。还要确保在这些路径上没有强制执行时序约束,因为根据第二条规则这是可以的。不必要的时序约束只会让 FPGA 工具变得更难。
这些规则的另一个结果是,即使时钟是 related 时钟,也可以将它们视为 unrelated 时钟。如前所述,通过确保在所有路径上安装 resynchronization 逻辑并关闭时序约束的执行来做到这一点(更多内容见下文)。
最后,如果您确定时钟是 related 时钟,则不需要 resynchronization 逻辑,但必须在时钟域之间的所有路径上正确执行时序约束。
此表总结了本节。表格的行是关于路径的末尾是否有 resynchronization 逻辑,它的列是关于时钟是否是 related 时钟。表格中间表示路径是否需要时序约束。
时钟是 | |||
related clocks | unrelated clocks | ||
Resynch- |
没有申请 | 路径需要时序约束 | 这是个错误 |
应用 | 路径不需要时序约束 |
常见错误
在复杂的设计中,信号通过不同的模块连接,很容易忽略哪个信号与哪个时钟同步,从而在不知不觉中从一个时钟域移动到另一个时钟域。
有害错误有两种选择: 第一个是在时钟域和 unrelated 时钟之间创建一条路径,而没有任何 resynchronization 逻辑。这意味着在这些路径的目标处,时序可能偶尔会被违反。与所有时序错误一样,可见的问题可能会非常具有误导性。如果工具从时序约束推断出两个时钟是 related 时钟,从而不必要地将时序约束应用于路径,则可能会增加混乱。这些时序约束在 unrelated 时钟之间的路径上没有任何意义,但这些路径会出现在报告(reports)中,就好像它们已被正确处理一样。这可能会使时序报告(timing reports)的人类读者感到困惑,认为一切都很好,甚至可能认为时钟实际上是 related 时钟。
第二个有害错误是当两个时钟是 related 时钟并被逻辑视为 related 时钟时,但工具却不认为它们是 related 时钟。因此,在时钟域之间的路径上不会强制执行任何时序约束。因此,无法保证在目的地会遇到时序要求。这再次导致不可靠的行为。不过,在设计的时序验证检查中可以发现此类错误。
没有注意到跨时钟域真正无害的唯一情况是在 related 时钟之间,并且 FPGA 工具也认为它们是这样的(因此将时序约束强制执行到相关的路径)。如果时钟有不同的频率,仍然可能存在功能错误,而逻辑没有考虑到这一点,但这就像逻辑中的任何错误一样。
避免不必要的 constraining
很多时候,单个锁相环(PLL)用于创建具有不同频率的时钟,供不同功能单元使用。从设计者的角度来看,它们是 unrelated 时钟,但实际上它们是 related 时钟,工具通常认为它们是 related 时钟。
例如,假设一个锁相环从一个参考时钟和一 10 MHz生成两个时钟。一个时钟是 90 MHz ,第二个时钟是 100 MHz。目的是将这些时钟用于项目的不同部分,因此在概念上它们是 unrelated 时钟。但是如果两台触发器之间有连接,一台触发器与一台时钟同步,另一台触发器与另一台时钟同步呢?
因为第一个时钟的 period 是 11.11 ns ,第二个时钟的 period 是 10 ns,所以上升沿之间的最坏情况时间差是 1.11 ns (考虑到所有可能的相位组合)。那相当于 900 MHz的时钟周期。因此,这两个触发器之间的路径的时序是紧的,即使能达到这个要求,也给工具带来了困难,特别是如果有很多这样的路径。
这不是理论上的情况: 一个常见的陷阱是使用 dual-clock FIFO 与时钟域接口,而不注意时钟是不是 related 时钟。这个错误没有功能问题,但由于 dual-clock FIFOs 总是有 resynchronization 逻辑,所以强制工具在两个时钟域之间的路径上强制执行时序约束是没有意义的。这些工具可能无法毫无目的地实现这些时序约束。
因此,重要的是要注意不是这样使用的 related 时钟,特别是来自同一个锁相环的时钟: 从一个时钟域到另一条路径上,时序约束很可能被强制执行。这种强制执行没有任何好处,因为 resynchronization 逻辑可以防止时序违反(timing violations)。另一方面,该工具在这些路径上实现时序约束的努力可能会导致在整个设计上实现时序约束的困难。
要解决此问题,请添加将时钟声明为 unrelated 时钟的时序约束 。或者,为这些路径定义false 路径或 maximal 时延 。但是,在某些情况下,这可能不是必需的,因此在对这些路径进行任何操作之前,有必要检查时序报告是否适合这些路径。例如,当使用 FPGA 工具提供的 dual-clock FIFO 时,通常会自动为时钟域之间连接的路径添加足够的时序约束。
误导时序约束
需要强调的是, FPGA 工具通常会接受并执行 unrelated 时钟之间的时序约束。这样的约束(constraints)毫无意义且容易引起混淆,特别是因为相关的路径出现在时序报告中,就好像它们的时序要求是有保证的一样。如前所述,不可能保证时钟域之间属于 unrelated 时钟的路径的时序。
请务必记住,时序约束只是向 FPGA 工具提供有关设计信息的一种方式。如果一个设计达到了时序约束,那只有在时序约束是正确的情况下才有意义。因此,如果您不确定时钟是否为 related 时钟,请不要尝试仅通过添加时序约束来解决跨时钟域。
再次强调,如果相关,最好添加时序约束,将时钟定义为 unrelated 时钟,或者定义 false 路径。这不仅使 FPGA 工具更容易使用,而且可以避免混淆。
确保正确通知工具
出于上述所有原因, FPGA 设计工具必须具有正确的信息,即哪些时钟是 related 时钟,哪些不是。或者更准确地说, FPGA 工具应将时序约束应用于所有不受 resynchronization 逻辑保护的路径(包括时钟域内的路径,但这与此处无关)。
然而,确保这些工具与逻辑设计具有相同的感知可能很困难: 每个 FPGA design 软件都有自己的方式来自动假设时钟之间的关系。所有工具的唯一共同点是,如果使用该工具的专用时钟 IP (clocking IP)生成两个或更多具有相同锁相环的时钟,该工具会将这些时钟视为 related 时钟。因此,相关时钟域之间的路径将被计时,并且时序约束将在这些路径上强制执行。此实施通常基于为参考时钟提供的时序约束。但不要认为这是理所当然的。
除此之外,每个工具都有自己的方式来推断时钟之间的关系,甚至来自同一 FPGA 供应商的不同工具在某些情况下可能会做出不同的决定。最值得注意的是,与 Xilinx之前的旗舰工具 ISE相比, Xilinx的 Vivado 更倾向于假设时钟是 related 时钟。
因此,没有适用于所有 FPGA 设计工具的通用原则,甚至特定工具也可能做出令人惊讶的决定。真正解决这个问题的唯一方法是检查时序报告,并为特定的路径 groups (path groups)生成时序报告,以确保时序要求在需要时正确,在不需要时不存在。这是一项艰巨的任务,但值得付出努力。