此页面属于关于时序(timing)的一系列页面。前几页解释了时序计算背后的理论,展示了如何编写几个时序约束(timing constraints)并讨论了时序收敛(timing closure)的原理。本页说明如何定义与时钟域(clock domains)相关的时序约束。
介绍
了解了如何找到特定的逻辑单元(logic elements)以及如何定义路径(paths),我们现在将开始研究利用这些知识的时序约束。
我们首先要看的是时序约束和时钟域之间的关系。这两个话题密切相关,讨论其中一个话题不可能不涉及另一个话题。因此,如果您不熟悉这个主题,我建议您通读有关时钟域的这一系列页面。其中,这对于理解我将在下面使用的术语是必要的。
逻辑定义了时钟之间的关系
让我们看一下 Verilog 代码的这个示例:
module top(
input clk,
input foo,
output reg bar_reg,
output reg baz
);
reg foo_reg;
reg bar;
reg baz_metaguard;
wire pll_clk_8, pll_clk_6;
clk_wiz_1 pll_i
(.clk_in1(clk),
.clk_out1(pll_clk_8),
.clk_out2(pll_clk_6));
always @(posedge pll_clk_8)
foo_reg <= foo;
always @(posedge pll_clk_6)
begin
bar <= !foo_reg;
bar_reg <= bar;
end
always @(posedge clk)
begin
baz_metaguard <= bar;
baz <= baz_metaguard;
end
这与我们之前看到的锁相环(PLL)示例几乎相同。不同的是有新的跨时钟域: @bar 的值通过 metastability guard复制到 @baz 。
所以本例中有两种类型的跨时钟域:
- 从 @foo_reg 到 @bar,在两个 related 时钟之间(没有 metastability guard)。
- 从 @bar 到 @baz,在两个 unrelated 时钟之间(@baz_metaguard 是一 metastability guard)。
我只看了一个标准来确定跨时钟域的类型: metastability guard的存在或不存在。如果逻辑期望 unrelated 时钟,它会使用必要的安全机制。因此,其他都不重要: 即使可以确保这些时钟之间的路径上的时序要求(timing requirements),也没有理由这样做。
反之亦然: 如果逻辑期望 related 时钟,则没有针对时序违反(timing violations)的保护。因此时序要求必须在这些时钟之间的所有路径上得到满足。
因此,总而言之,逻辑总是假设哪些时钟是 related 时钟,哪些不是。这些假设反映在保护机制的存在与否上。但正是 FPGA 工具确保这些假设成为现实。特别是,这些工具确保在 related 时钟之间的路径上满足时序要求。
因此,我们需要确保工具了解逻辑对时钟的假设。
请注意,在上面的示例中,工具无法了解逻辑对 @pll_clk_6 和 @clk的期望: 逻辑可能假设这些是 related 时钟,也可能假设相反。事实上,我之前使用了一个类似的示例(参见“想法 #9”)来演示 related 时钟之间的路径。在上面的例子中,这两个时钟被当作 unrelated 时钟处理。
也许这些工具可以从 @baz_metaguard的名称中做出智能猜测。也许 metastability guard 的典型结构可以提供一些提示。但这还不足以就如此重要的问题做出决定。
讲述时钟的工具
在到目前为止的示例中,只有一个时序约束:
create_clock -period 4.000 -name clk [get_ports clk]
想到的第一个问题是默认情况下,工具如何将跨时钟域从 @pll_clk_6 处理为 @clk 。如果这是唯一的时序约束,这些工具是否会在 @bar 和 @baz_metaguard之间的路径上执行任何操作?
答案是,这取决于使用哪个 FPGA 工具。尽管几乎所有 FPGA 工具都会将 @pll_clk_6 和 @pll_clk_8 视为 related 时钟,但其他时钟对会发生什么情况并不明显。工具通常默认将所有时钟视为 related 时钟,但依赖这一点并不安全。
许多 FPGA 工具支持 set_clock_groups 命令。这是定义时钟之间关系的最佳选择。由于此命令的重要性,在设计中使用它之前,最好先参考工具的文档。
对于上面的例子,这个命令可以在 Vivado 上像这样使用(或与其他工具类似):
set_clock_groups -asynchronous \ -group [list \ [get_clocks -of_objects [get_pins pll_i/clk_out1] ] \ [get_clocks -of_objects [get_pins pll_i/clk_out2] ] ] \ -group [get_clocks -of_objects [ get_pins pll_i/clk_in1] ]
get_clocks 和 get_pins 的这种用法之前已经解释过: 每个 get_clock 命令都会根据 clk_wiz_1 对应的端口的名字找到一个时钟对象(clock object)(注意 clk_wiz_1对应的例化(instantiation)的名字是 pll_i),比如最后的 get_clock 命令就会得到 @clk对应的时钟对象(clock object)。
此示例显示 set_clock_groups 如何定义时钟的组。在这种情况下,一组由 @pll_clk_6 和 @pll_clk_8组成,第二组仅由 @clk 组成。这个约束(constraint)告诉工具属于同一组的所有时钟都是 related 时钟。同样,如果两个时钟属于不同的组,工具会将它们视为 unrelated 时钟。
换句话说,当且仅当路径两侧的时钟属于同一组时,这些工具才会在路径上强制执行时序约束。
设计的时序约束中允许有多个 set_clock_groups 命令。但最好使用一个 set_clock_groups 命令将所有时钟分成几组。这样可以避免矛盾,也有助于防止混淆: 这个命令的主要优点是它简洁地描述了时钟之间的关系。简短、简洁、数学化。这就是我们想要的。
请注意, set_clock_groups 应该在所有 create_clock 命令之后使用,因为提到的时钟对象应该已经存在。
时钟之间的不一致关系
如果时钟在设计中有的地方被认为是 related 时钟,有的地方被认为是 unrelated 时钟,那么就不能用 set_clock_groups了。
例如, @clk 和 @pll_clk_6 在上面的 Verilog 代码中被视为 unrelated 时钟。但理论上,可能会有额外的逻辑将它们视为 related 时钟。尽管这不是一个好主意,但可以为这个额外的逻辑在 @pll_clk_6 和 @clk 之间强制执行时序约束。
如果因为这个原因 set_clock_groups 不能用,先问问自己是不是不想换逻辑,这样 set_clock_groups 就可以用了。不只是因为这个命令这么好用: 如果没有一个简单的规则来说明哪些时钟是 related 时钟,哪些不是,则很容易在跨时钟域上出错。如果您仍然不想在逻辑中进行此类更改,解决方案是定义 false 路径。
简要介绍 set_false_path 命令
有两种主要的命令用于声明 false paths: set_clock_groups 和set_false_paths 。
set_clock_groups 命令如上所述: 任何位于属于不同组的两个时钟之间的路径都被视为 false path。但 set_clock_groups 并不总是适用于所有应声明为 false path的路径。在某些情况下,路径的选择必须比时钟的定义组更具体。 set_false_path 命令通过允许特定选择路径解决了这个问题。
例如,让我们将上面的 set_clock_groups 命令替换为 set_false_path 命令。唯一需要修复的是路径从 @bar 到 @baz_metaguard。所有其他路径的时序要求都按原样正确执行。因此,这是为这条路径(path)编写 set_false_path 命令的一种方法。但不要从这个例子中吸取教训:
set_false_path -from [get_cells bar_reg__0] -to [get_cells baz_metaguard_reg]
这个命令使用 get_cells 来选择路径的开头和路径的结尾。这需要知道两侧 cell 对象的名称。在本例中,我们得到的名称是“bar_reg__0”,这说明了依赖名称的问题: 这个名字应该是“bar_reg”,但正如已经解释的那样,由于巧合,它以“bar_reg__0”结尾。
所以这个例子展示了 set_false_path的问题之一: 设计中逻辑单元的详细选择通常需要依赖对象(object)的名称。这个问题和可能的解决方案之前已经讨论过了。
这个命令可以简化为: 由于 @baz_metaguard 是 metastability guard,因此路径到寄存器(register)来自哪里并不重要。那么为什么不忽略所有路径到这个寄存器(register)?
set_false_path -to [get_cells baz_metaguard_reg]
这个命令的效果也是一模一样的。
set_false_path 而不是 set_clock_groups
还有一种可能就是按照时钟来定义路径,其实上面的 set_clock_groups 命令可以用这些约束(constraints)来代替:
set_false_path -from [get_clocks -of_objects [get_pins pll_i/clk_out1]] \ -to [get_clocks -of_objects [ get_pins pll_i/clk_in1] ] set_false_path -from [get_clocks -of_objects [get_pins pll_i/clk_out2] ] \ -to [get_clocks -of_objects [ get_pins pll_i/clk_in1] ] set_false_path -from [get_clocks -of_objects [ get_pins pll_i/clk_in1] ] \ -to [get_clocks -of_objects [get_pins pll_i/clk_out1] ] set_false_path -from [get_clocks -of_objects [ get_pins pll_i/clk_in1] ] \ -to [get_clocks -of_objects [get_pins pll_i/clk_out2] ]
这是创建相同 false paths的详细方法。 不是将时钟分成几组,而是每对 unrelated 时钟有两个 set_false_path 命令: 每个方向一个命令。很明显,这么多约束很容易出错。这是一个有三个时钟的简单示例。
尽管如此, set_false_path 经常被这样使用,而不是 set_clock_groups。这很可能是因为有人从某处复制了时序约束。
错误如何发生的示例
让我们看一个可能的错误的简单示例: 从上面的讨论中,很明显只有一条路径需要声明为 false 路径: 从 @bar 到 @baz_metaguard。因此,上例中的四个 set_false_path 命令中,只有这一个有意义:
set_false_path -from [get_clocks -of_objects [get_pins pll_i/clk_out2] ] \ -to [get_clocks -of_objects [ get_pins pll_i/clk_in1] ]
其他三个 set_false_path 命令不覆盖任何路径。但是等一下,如何进一步简化约束?也许所有以 @clk 结尾的路径都应该是 false 路径?这样如何?
set_false_path -to [get_clocks -of_objects [ get_pins pll_i/clk_in1] ]
实际上,由于定义“clk”的 create_clock 命令正好位于 set_false_path 命令上方,因此可以改写为:
set_false_path -to [get_clocks clk]
这款约束短而优雅。不幸的是,这是非常错误的: 我建议所有以 @clk 结尾的路径都应该是 false path。但是从 @baz_metaguard 到 @baz的路径呢?那是从 @clk 到 @clk的路径。当然,这条路径肯定不应该是 false 路径。但是最后两个 set_false_path 命令包括所有以 @clk结尾的路径,即使路径从 @clk开始。
犯这种错误很容易,特别是如果 false path 的约束是在没有精确的数学思维方式的情况下编写的。创建特殊的时序报告(timing reports)可能会有所帮助,它可以揭示哪些路径是 false paths ,哪些不是。
关于 FPGA内部的路径的时序约束的实际讨论到此结束。下一页讲解 multi-cycle 路径,也属于这个话题。但是,通常不推荐 multi-cycle path 约束。因此,可以直接跳到 I/O 约束的介绍。