01signal.com

时钟域、相关时钟与无关时钟

本页是时钟域系列文章三篇中的第一篇。

引言

除了非常简单的 FPGA 设计以外,FPGA 中的同步元件(触发器、块 RAM(block RAM)、移位寄存器等)都会使用不止一个时钟。不过,逻辑设计中的大多数功能单元通常只基于某个单一的时钟,所以大多数时候并不需要特别关注多时钟问题。当基于一个时钟的逻辑与依赖另一个时钟的逻辑相连时,事情就变得复杂了。本系列页面讨论的正是如何在设计中处理多个时钟。

当由不同时钟驱动的逻辑相互连接时,第一个、也是最重要的问题是:是否需要再同步逻辑(resynchronization logic)?这个问题等价于问:两个时钟是不是相关时钟?本页将解释这个问题、如何回答它,以及相关的其他话题。下面很多内容都与时序约束(timing constraints)有关,也与能否把这种约束施加到某些逻辑元件上有关。因此,我打算先简要回顾一下时序。

本系列文章的讨论仅限于上升沿触发的触发器(positive edge triggered flip-flops),也就是对时钟输入上升沿作出响应的触发器。当然,也还存在其他同步元件,也就是根据时钟对信号进行采样并产生输出的逻辑元件:移位寄存器、块 RAM,以及其他很多功能元件。其中一些可能在时钟下降沿、甚至两个边沿动作。为了简单起见,这里的讨论不考虑这些情况。

另外,我会用“X 与 Y 同步”表示同步元件 X 的时钟输入与时钟 Y 相连。因此,该同步元件会以这个时钟来采样其输入(一个或多个),并用这个时钟更新其输出。

时序基础简述

这是时序理论的简要总结。更详细的解释可以参见专门页面

请看下面的示意图,它显示了一条从触发器经过 LUT 到另一个触发器的信号路径:

Crossing clock domains: Two flip-flops and a LUT between them

图中的 LUT(查找表,Look-Up Table)代表任意一组组合逻辑(combinatorial logic):I1、I2、I3 或 I4 中任何一个发生变化,都会引起输出 O 的变化,只是这种变化会有一定的传播延迟(propagation delay)。

所以这条信号路径的动作是这样的:左侧触发器的 Q 输出(记为 @foo)在输入时钟 @clk1 的上升沿之后改变。由于这个输出连接到 I1,LUT 的输出 O 会经过一小段延迟后改变。O 送到右侧触发器的数据输入 D。当 @clk2 的上升沿到来时,触发器将 D 复制到 Q(记为 @bar)。

但事情并没有这么简单。所有触发器都有时序要求:数据输入 D 必须在 @clk2 上升沿之前稳定 tsu(建立时间,setup time),并在该上升沿之后继续稳定 thold(保持时间,hold time)。

FPGA 中绝大多数信号路径都只与单个时钟有关,所以 @clk1 和 @clk2 其实是同一个时钟信号(我们先不考虑时钟偏斜(clock skew))。对这类路径,可以计算出它的两个时序要求是否都能得到保证。

例如,以上面的示意图和 tsu 的要求来看:思路是计算从 @clk1 的上升沿开始,到右侧触发器的 D 输入完成更新并稳定下来,一共要花多长时间。这等于这条路径(path)上所有延迟的总和:先从 @clk1 的上升沿到 @foo 完成更新(即“时钟到输出”延迟),再加上其后直到右侧触发器 D 输入为止的所有延迟,包括 LUT 的延迟,以及逻辑元件之间连线的延迟(布线延迟,routing delays)。

因为 @clk1 和 @clk2 是同一个时钟,我们知道两个触发器上的下一个上升沿何时到来:一个时钟周期之后(例如 100 MHz 时钟对应 10 ns)。因此路径的总延迟必须小于时钟周期,并留出 tsu 的裕量。如果能保证这一点,也就满足了 tsu 的要求:右侧触发器的 D 输入在 @clk2 上升沿到来之前已经稳定,而且至少提前 tsu。带数字的例子参见时序理论页面

thold 也有类似计算。与 tsu 不同的是,它要求右侧触发器的 D 输入在 @clk2 的上升沿之后继续稳定一段时间(thold)。因此时钟周期在这里无关紧要,也不会参与计算(当 @clk1 和 @clk2 是同一个时钟时)。重要的是同一个时钟周期内发生什么,而不是下一个周期。

注意,这类计算之所以能够进行,仅仅是因为我们知道 @clk1 与 @clk2 上升沿之间的时间差。特别是,如果 @clk1 和 @clk2 是同一个时钟,那么用于计算 tsu 的时间差就是该时钟的周期。但如果两个时钟之间的时间差未知,那么 tsu 和 thold 都无法得到保证。

tsu 和 thold 适用于每个触发器。当然,这包括 FPGA 内部的所有触发器,也包括外部器件中的触发器(例如,信号从 FPGA 的输出引脚送到外部器件,由外部器件用某个时钟对它进行采样(sampling))。所以,对系统中的每个触发器,我们都必须问自己:这两个要求是否已经得到保证?对于无法保证的触发器,必须准备一种机制,确保即使如此也能可靠工作,也就是再同步逻辑。概括来说,这就是跨时钟域问题。

那么再说一次:时序什么时候能得到保证?

如果你在上面的内容里绕晕了,这里再列出要点:

回想一下,FPGA 设计中总是会包含时序约束,用于把时钟频率告知设计工具。工具依据这些信息确保 tsu 和 thold 这两项时序要求得到满足;如果无法满足,工具会报告。

如上所述,只有在知道路径起点时钟与终点时钟上升沿之间的时间差时,才可能保证 tsu 和 thold。大多数情况下这个前提成立,因为逻辑设计的大部分由寄存器组成,而这些寄存器所依赖的其他寄存器往往与同一个时钟同步。

但如果用了两个不同的时钟呢?如果路径起点的触发器接一个时钟,路径终点的触发器接另一个时钟呢?换句话说,如果 @clk1 不是 @clk2?这种情况下还能进行时序计算并保证时序要求吗?答案是:看情况。这正是本页余下内容要讨论的。

时钟域与跨时钟域

时钟域(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 同步,却依赖与 @clk1 同步的 @foo。那么,能把 @bar 当作普通寄存器来用吗?我们能假定 @bar 总是在时序合法的前提下取得 @foo 的值,从而让它的行为是确定且可重复的吗?

在尝试回答之前,先给刚才发生的事情一个名字:这就是一次跨时钟域(clock domain crossing,CDC)。@foo 和 @bar 被实现为两个触发器,分别与不同的时钟同步。因此,从 @foo 到 @bar 的路径从一个时钟域进入了另一个时钟域。

更一般地说,跨时钟域是指:属于一个时钟域的同步元件的输出,最终到达属于另一个时钟域的同步元件的输入。在这两个同步元件之间,通常还会有组合逻辑。

因此,即使 @bar 的定义是下面这样:

always @(posedge clk2)
  bar <= !foo || !bar;

这里仍然存在跨时钟域。这条路径从 @foo 出发,经过实现该逻辑函数的 LUT,最后到达 @bar,正如上面的示意图所示。

相关时钟与无关时钟

术语“相关时钟(related clocks)”指的是由同一个参考时钟以某种方式派生出来的时钟;这种派生方式使它们上升沿和下降沿之间的时间差是可预测的(已知误差和抖动(jitter)除外)。

术语“同步时钟(synchronous clocks)”经常被用来代替“相关时钟”;同样,“异步时钟(asynchronous clocks)”也常被用作“无关时钟(unrelated clocks)”的同义词。

相关时钟的一个常见例子是:用同一个 FPGA PLL 产生几个时钟,它们之间的频率关系已知。这种情况下,FPGA 工具通常会安排时钟缓冲器,使时钟边沿尽可能对齐。

例如,把一个参考时钟分别放大 2 倍和 3 倍时:

Simple clock diagram with clock multiplication

在这个例子中,x1 clk、x2 clk 和 x3 clk 都是相关时钟,因为每一对时钟之间的时序都可预测。

一般情况下,参考时钟与这些派生时钟中的任何一个都是无关时钟:参考时钟边沿和这些时钟之间的时序会随温度等因素变化。但如果 PLL 的配置能保证参考时钟与 PLL 输出之间存在可预测的对齐关系,那么参考时钟也算相关时钟。

知道了时钟之间的时间关系,就可以对跨越相应时钟域的路径做时序计算。例如,x1 clk 与 x2 clk 时钟域之间路径的时序,按 x2 clk 自身路径的同样要求计算。这是因为这两个时钟之间最短的时间差,正好等于 x2 clk 连续两个上升沿之间的时间。

同样,x2 clk 与 x3 clk 之间的路径也可以计算,但二者之间最短的时间差只有 x1 clk 周期的六分之一。因此,最坏情况下的时序要求相当于一个假想的 x6 clk 的要求。原因是 x2 clk 的第二个上升沿与 x3 clk 的第三个上升沿之间的时间差。

更详细的时序计算方法,请参见这个页面

这个例子要说明的是一个在一般情况下也成立的道理:对于相关时钟,只要在这些时钟之间的路径上施加时序约束,时序就能得到保证,和同一个时钟域内部的路径一样。这是可能的,因为 FPGA 工具会有意识地确保时钟边沿对齐。正如这个例子所示,此时的时序要求往往比单独看其中每个时钟时更严格,有时甚至严格得多。

不过要注意,两个时钟同源于一个参考时钟,并不一定意味着它们就是相关时钟。特别是,如果这两个时钟之间的偏斜(clock skew)不受控或未知(除非设计工具明确使用了延迟相等的时钟分配资源,否则就会出现这种情况),就应该把它们当成无关时钟。

即使 x1 clk 也未必与其参考时钟相关,就算两者频率完全相同。除非刻意让这些时钟彼此对齐,否则它们之间的相位关系是未知的。

正是由于这种相位上的原因,我在上面给时钟域下定义时用的是“某个时钟信号”,而不仅仅是“某个时钟”。“某个时钟”可能指电路板上的同一个时钟,例如它连接到 FPGA 的两个不同输入引脚;而“某个时钟信号”指的是 Verilog 代码或设计网表中的一根代表时钟信号的线网,就像上面例子中的 @clk1 和 @clk2。在 Verilog 设计中,它是通过实例化(instantiation)中的端口连接或普通的 assign 语句来传递的。

当时钟在 Verilog 中确实是同一个信号时,工具会确保使用能让硬件上的时钟偏斜(clock skew)保持较低的资源。这也会影响工具是否把这些时钟视为相关时钟(下面还会讨论)。

相关时钟、时序约束与再同步逻辑

回到最初的问题:上面例子里的 @bar 能像普通寄存器一样使用吗?更一般地说,如果一个触发器是一条从某个时钟域通往另一个时钟域的路径的终点,我们能像使用其他任何触发器输出一样,放心地使用它的输出吗?这个问题等价于:当到达该触发器数据输入的路径中至少有一条与另一个时钟同步时,该触发器的 tsu(建立时间)和 thold(保持时间)是否仍能得到保证。

答案简短而明确:如果两侧的时钟(例子中的 @clk1 和 @clk2)是相关时钟,那么只要这条路径满足了合适的时序约束,终点触发器的输出就完全正常,可以像普通寄存器一样使用。否则,终点处的时序无法得到保证,必须加入再同步逻辑来应对这一点。

这个页面给出了两个相关时钟之间一条路径的完整时序分析。

在经历了上面这么长的讨论之后,现在可以把它总结成几条简单规则:

  1. 对属于无关时钟的两个时钟域之间的路径,无法施加有效的时序约束。
  2. 如果两个时钟域之间的所有路径都带有再同步逻辑,就不需要给这些路径施加时序约束。
  3. 如果两个时钟域之间的某条路径没有再同步逻辑,就必须给这条路径施加时序约束。

第一条规则最简单:如果你不能确定两个时钟是相关时钟,就要保证两个时钟域之间的所有路径上都有再同步逻辑(下一页会介绍具体做法)。同时,还要确保没有在这些路径上施加时序约束,因为按照第二条规则这样做是没有问题的。多余的时序约束只会让 FPGA 工具更难做。

这些规则还带来另一个结果:即使两个时钟确实是相关时钟,把它们当无关时钟处理也是可以的。正如前面所说,做法是在所有路径上都放再同步逻辑,并关闭这些路径上的时序约束(后面会进一步说明)。

最后,如果你确定两个时钟是相关时钟,就不需要再同步逻辑,但必须在这两个时钟域之间的所有路径上正确地施加时序约束。

下表总结了这一节。表格的行表示路径终点处是否使用了再同步逻辑,列表示两个时钟是否为相关时钟。表格中间的内容说明路径是否需要时序约束。

两个时钟
相关时钟 无关时钟

再同步
逻辑:

未使用 路径上需要施加时序约束 这是错误做法
已使用 路径上不需要施加时序约束

常见错误

在复杂设计中,信号会在不同模块之间互相连接,因此很容易忽视某个信号到底与哪个时钟同步,从而在不知情中跨过了时钟域。

有害的错误有两种。第一种是在无关时钟的两个时钟域之间建立路径,却没有加任何再同步逻辑。这表示在这些路径的终点,时序可能不时地被违反。与所有时序问题一样,表面看到的现象可能非常有误导性。如果工具根据时序约束判断这两个时钟是相关时钟,从而不必要地给这些路径施加时序约束,问题会更混乱。这些时序约束对无关时钟之间的路径没有意义,但是它们会出现在报告中,看起来就像已经被妥善处理过一样。这会让阅读时序报告的人误以为一切正常,甚至误以为这两个时钟真是相关时钟。

第二种有害的错误是:两个时钟实际上是相关时钟,逻辑设计也按相关时钟来处理它们,但工具却不认为它们相关。结果,工具不会对两个时钟域之间的路径施加时序约束,于是终点处的时序要求是否满足就没有保证。这同样会导致不可靠的行为。不过,这类错误可以通过设计的时序验证检查发现。

唯一一种“没有及时发现跨时钟域”也真正无害的情况是:两个时钟是相关时钟,FPGA 工具也认为它们是相关时钟(因而会对这些相关路径施加时序约束)。如果时钟频率不同,而逻辑设计没有考虑到这一点,仍然可能出现功能缺陷,但这与其他普通逻辑缺陷没有两样。

避免不必要的约束

相当常见的情况是,用同一个 PLL 产生不同频率的时钟,供不同的功能单元使用。从设计者的角度看,这些时钟是无关的;但事实上它们是相关时钟,工具通常也会把它们视为相关时钟。

例如,假设某个 PLL 以 10 MHz 的参考时钟生成了两个时钟,一个 90 MHz,另一个 100 MHz。设计意图是把它们用于项目的不同部分,所以从概念上讲它们是无关时钟。但如果两个触发器之间存在一条连接,其中一个触发器与一个时钟同步,另一个与另一个时钟同步,会怎么样?

第一个时钟的周期是 11.11 ns,第二个时钟的周期是 10 ns,把所有可能的相位组合都考虑进去后,两个时钟上升沿之间最坏情况下的时间差是 1.11 ns。这相当于 900 MHz 的时钟周期。因此,这两个触发器之间路径的时序会非常紧;即使最终能够满足,也会给工具造成困难,尤其当这样的路径很多时。

这不是纯理论上的情况:一个常见的坑是使用双时钟 FIFO 在两个时钟域之间做接口,却不关心这两个时钟是不是相关时钟。这个错不会造成功能问题,但既然双时钟 FIFO 本身总是带有再同步逻辑,再强迫工具在两个时钟域之间的路径上施加时序约束就没有意义了。工具可能白费力气去满足这些时序约束。

因此,要特别注意没有被当作相关时钟来使用的相关时钟,尤其是来自同一个 PLL 的时钟:很可能存在从一个时钟域指向另一个时钟域的路径,并且工具正在这些路径上施加时序约束。这样做没有好处,因为有再同步逻辑在防止时序违例。另一方面,工具为了满足这些路径的时序约束而花的力气,可能反而使整个设计难以满足时序约束。

要解决这个问题,可以添加将时钟声明为无关时钟的时序约束;也可以为这些路径定义伪路径(false paths)或最大延迟。不过,有些情况下这一步也许不是必须的,所以在动手之前最好先看看这些路径的时序报告。例如,如果使用的是 FPGA 工具自带的双时钟 FIFO,通常会自动为连接两个时钟域的路径添加合适的时序约束。

容易误导的时序约束

必须强调,FPGA 工具通常会接受并施加无关时钟之间的时序约束。这样的约束毫无意义,而且会制造困惑,尤其是这些路径会出现在时序报告中,看起来好像其时序要求已经得到了保证。正如已经讨论过的,属于无关时钟的两个时钟域之间的路径,其时序是无法保证的。

要知道,时序约束只是向 FPGA 工具传递设计信息的一种手段。如果设计满足了时序约束,那只有在约束本身正确的情况下才有意义。所以,如果你不确定两个时钟是相关时钟,就不要单靠添加时序约束来解决跨时钟域问题。

另外再次提醒,在适当的情况下,最好添加把时钟定义为无关时钟的时序约束,或者定义伪路径。这不仅让 FPGA 工具更好做,也能避免误解。

确保工具获得正确信息

出于上述所有原因,FPGA 设计工具必须对哪些时钟是相关时钟、哪些不是获得正确的信息,这一点至关重要。更准确地说,FPGA 工具必须对所有未被再同步逻辑保护的路径施加时序约束(这包括同一个时钟域内部的路径,但在这里不是讨论重点)。

不过,要让工具与逻辑设计保持一致可能很困难:每种 FPGA 设计软件都有自己的一套自动推断时钟之间关系的规则。所有工具似乎只有一个共同点:如果你使用该工具自带的专用时钟 IP,通过同一个 PLL 产生两个或更多时钟,工具就会认为它们是相关时钟。因此,这些相关时钟域之间的路径会被纳入时序计算,并被施加相应约束。这种约束通常基于为参考时钟给出的约束。但即使是这一点,也不能想当然。

除此之外,每种工具都有自己推断时钟关系的方法,甚至同一家 FPGA 厂商的不同工具,在某些情况下也可能得出不同结论。最值得注意的是,与 Xilinx 上一代旗舰工具 ISE 相比,Xilinx 的 Vivado 更倾向于默认时钟为相关时钟。

因此,对不同的 FPGA 设计工具而言没有统一原则,即使是某个具体工具也可能做出出人意料的选择。真正处理这一问题的唯一途径是检查时序报告,并为特定路径组生成时序报告,以确保该有时序要求的地方有正确要求,不该有时序要求的地方没有要求。这是一项艰巨的任务,但值得去做。

本页到此结束,本页是本系列中的第一篇。下一页将介绍跨时钟域的基础知识。

本页由机器从英文翻译而来。如有疑问,请参阅原文
Copyright © 2021-2026. All rights reserved. (dcc38493)