本页属于有关时序(timing)的一系列页面。前几页解释了时序计算背后的理论,讨论了时钟周期约束(clock period constraint),展示了时序收敛(timing closure)的原理,并开始研究 Tcl 环境。本页解释了允许时序约束(timing constraints)具体的命令。
概述
回想一下,时序约束的目的是确保满足设计中所有路径(paths)的时序要求。不同的路径可能有不同的要求,所以每个时序约束(timing constraint)都必须指向路径的相关组,不能包含任何其他内容。
定义路径的唯一方法是参考与这些路径相关的逻辑单元。因此,能够编写准确的表达式来选择正确的逻辑单元组非常重要。换句话说,必须正确使用命令,例如 get_clocks、 get_ports 和 get_cells,以便它们的结果正是所需的。
本页介绍了描述逻辑单元组的基本技术。几乎所有内容都特定于 SDC 语法。我还假设 Tcl command-line 接口可用。这对于 Vivado 和 Quartus以及最近的大多数 FPGA 工具都是如此。
一些 FPGA 供应商公开宣称 Synopsys的软件已集成到他们的工具中,因此这些工具自然使用相同的 Tcl 命令。另一方面, Vivado不被认为是从 Synopsys派生而来的。然而,两者之间有一些惊人的相似之处,特别是在 Tcl 接口方面。 Quartus 似乎是独立开发的,因此其 Tcl 接口略有不同。
乍一看,似乎此页面对 Tcl 脚本的详细信息过多。事实恰恰相反: 准确性极其重要,而这只有通过准确理解命令的解释方式才能实现。事实上,本页仅解释了基本原理。阅读文档无可替代。
获取 FPGA 工具的指导
编写时序约束往往很难上手,因为要处理的细节太多了。 FPGA 工具可以帮助解决这个问题。
"help" 命令可以在 Tcl console 中使用,用于查看 Tcl 命令上的文档。这通常与官方文档中的信息完全相同(例如 pdf 格式)。
大多数 FPGA 工具都有一个图形用户界面(GUI)接口,用于自动创建时序约束。工作方法通常用于选择所需的时序约束类型。下一步是从列表中选择相关的逻辑单元。结果是添加到 SDC 文件中的一个或多个时序约束。
使用此类 wizard 有助于获得有关 Tcl 语法的提示。有时,自动创建的约束(constraints)也可以按原样使用。然而,在大多数情况下,抵制使用 wizard输出的诱惑是值得的,而是仔细思考如何最好地实现时序约束的目的。同样重要的是要考虑项目的预期发展方式,并确保时序约束随着时间的推移保持正确。使用图形用户界面 wizard (GUI wizard)进行快速会话不太可能实现这一目标。
一些 FPGA 工具还具有用于在设计中查找逻辑单元的图形用户界面(GUI interface)。使用此界面时,通常会显示与请求的搜索相对应的 Tcl 命令。这是一种获取用于查找特定逻辑单元的 Tcl 表达式的便捷方法。再次强调,这些表达式应被视为进一步工作的起点。
大多数 FPGA 工具的第三个功能是 Tcl command-line console ,它允许输入命令(或使用复制-粘贴(Copy-Paste))并在屏幕上查看结果。这允许测试命令及其 search patterns,并查看找到哪个对象(objects)。这有助于验证 search pattern 是否正确。
总之, FPGA 工具可以帮助创建 Tcl 命令。但正如前面所说,由工具创建的 Tcl 命令应该只是编写准确的 search patterns 的基础,保证随着时间的推移能够正常工作。
现在,当我们知道创建时序约束的惰性方法时,是时候学习如何正确地执行此操作了。
网表(netlist): 一个简短的提醒
在更具体地了解 Tcl 环境之前,我想简要提醒一下网表。
网表最常见的文件格式是 EDIF,但许多 FPGA 工具也有自己的特定格式。通常,综合工具(synthesizer)会创建网表,尽管工具也可能在后期对其进行更改。此文件描述了逻辑设计的基本组件及其之间的连接。它就像接线图,但采用的是文本表示而不是图形图像。
网表中的组件称为“cells”。绝大多数 cells 都类似于 LUT,组合逻辑(combinatorial logic)或触发器(flip-flop)的另一个小元素。此外, Verilog 代码(例如 IP cores)中的 black boxes 的例化(instantiations)在网表中表示为 cell 。其他 cells 有锁相环(PLLs)、 block RAMs、大逻辑单元(“hard IPs”): PCIe blocks、 MGT transceivers、处理器等
每 cell 都有一些引脚 : 这些引脚就像一个物理电子元件的外部连接点。但不要将它与 FPGA的外部 I/O混淆: cells 和引脚都存在于 FPGA内部。
网表中的互连由网络(nets)组成。这些就像物理电线。例如,当在 Verilog中使用“线(wire)”定义信号时,这将导致网络。一个网络(net)连接在两个或多个引脚之间,这样可以保证这些引脚始终拥有相同的逻辑级(logic level)。
逻辑单元表示为对象(objects)
当 FPGA 工具运行到项目的实现(implementation)时,实际上会执行 Tcl 脚本。这对于 Vivado 和 Quartus 以及其他几个 FPGA 工具都是如此。即使软件的工作方式不同,做出以下假设仍然是正确的: 一切都是一个大 Tcl 脚本的错觉是由应用程序接口(API)为约束和其他脚本(script)文件创建的。
在这个 Tcl 脚本(无论是否虚构)的环境中,所有逻辑单元都表示为由不同类(classes)创建的对象。这些对象可由 SDC 文件中的时序约束(或 Xilinx的 XDC 文件)访问。同样, Tcl command-line console 和 Tcl 脚本中的 Tcl 命令可访问这些对象。
这些是五个 Tcl 命令,所有与 SDC 语法配合使用的 FPGA 工具都支持它们。这些命令用于查找不同类型的对象(即不同的类)。我在前面的时序约束示例中已经使用过它们。事实上,如果没有这些命令,就不可能编写有意义的时序约束。
没有任何 arguments,这些命令会找到所有相关类型的对象。稍后我们将了解如何细化搜索。
- get_cells: 这个命令找到代表 cells (网表中的组件)的对象。
- get_pins: 这个命令找到代表引脚( cells的连接点)的对象。
- get_nets: 这个命令找到代表网络(网表中的“电线”)的对象。
- get_ports: 这个命令找到了代表 FPGA 设计的外部 I/O 端口的对象。这些是 toplevel 模块的端口。
- get_clocks: 此命令找到时钟对象(clock objects)。请注意,在此处提到的对象中,时钟对象是唯一不与设计中的逻辑单元对应的对象。相反,时钟对象用于包含有关时钟的信息,如前所述。
除了这五个命令外,每个 FPGA 工具都有自己的附加命令和附加对象。例如, Vivado 有附加的命令,如 all_ffs、 all_registers、 all_inputs、 all_outputs、 all_rams 和其他几个此类。 Quartus 支持其中一些,还有 get_registers、 get_keepers、 get_nodes、 get_fanins 和 get_fanouts 等。
这些对象的重要性在于它们在时序约束中用于引用 FPGA 设计中的逻辑单元。它们还可以在 Tcl 脚本中用于获取信息,例如时钟的频率。每个 FPGA 工具都有自己的应用程序接口,用于访问 Tcl 脚本中的这些对象。例如,这个 Tcl 命令可以在 Vivado 中使用以获取时钟周期(clock period):
> get_property PERIOD [get_clocks clk] 4.000
在 Quartus中也一样:
> get_clock_info -period [get_clocks clk] 4.000
尽管存在这些差异,但大多数 FPGA 工具都同意 SDC 格式中时序约束的语法和含义。有关每个工具支持的应用程序接口的信息通常可以在标题与 Tcl 脚本或时序收敛相关的 user guides 中找到。
除非另有说明,否则此页面上的示例基于 Vivado。
Tcl的一些注意事项
Tcl 是一种古老的语言,但是因为它在逻辑设计领域已经很成熟,所以预计这种语言不会很快消失。幸运的是,即使不太了解,也可以使用 Tcl 做一些有用的事情。
首先,我已经简要提到过,方括号(“[”和“]”)。在 Tcl中,这意味着执行方括号中的命令,并将结果放在它们的位置。对于熟悉 shell 脚本或 Perl的人来说,这与 backticks相同。例如,在这个命令中, get_port 被名为“clk”的端口对象(port object)替换:
create_clock -period 4.000 -name clk [get_ports clk]
同样,这可以这样写:
set the_clk_port [get_ports clk] create_clock -period 4.000 -name clk $the_clk_port
如第二个示例所示,使用“set”命令定义并分配变量(variables)的值。要访问变量的值,则使用 dollar sign ($)。同样,与 shell 脚本和 Perl类似。
关于 curly braces (“{”和“}”),情况不同: 像其他几种语言一样,它们的含义在很大程度上取决于它们的上下文。在 Tcl中, curly braces 的意想不到的含义之一是封闭的字符串(string)应保持不变。换句话说,应该没有替代品, whitespaces 应该被视为任何字符(character)。例如,同一个时序约束可以这样写:
create_clock -period {4.000} -name {clk} [get_ports {clk}]
在这个例子中, curly braces 完全没有必要,而这个命令的含义与之前完全相同。不幸的是,不必要的 curly braces 很常见,而且通常它们没有任何意义,就像这个例子中的情况一样。
使用 Tcl console的提示
经常发生的情况是,找到的对象数量很大,这使得阅读搜索命令的输出变得困难。这可以通过简单的 Tcl 命令来解决。具体怎么做取决于使用哪种工具。使用 Vivado,这个命令会打印出设计中的所有 cells 。每 cell 都打印在单独的行中,因此即使有很多 cells,输出仍然可读:
> join [get_cells -hierarchical] \n
GND
VCC
bar__0_i_1
bar_reg_OBUF_inst
bar_reg__0
[ ... ]
“join”命令将 newline 放在 get_cell 产生的 array 中的每个元素之间。
使用 Quartus,可以通过以下方式实现相同的结果:
join [query_collection -all [get_cells -hierarchical] ] \n
或者更明智地使用 query_collection :
query_collection -all -report_format [get_cells -hierarchical]
查找特定元素
介绍了很久,终于到了说说真正有趣的地方的时候了。为了下面的例子,参考下面的 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
正如本页顶部所述,时序约束的准确性取决于选择正确的逻辑单元组。因此,有可能并且有必要从上面提到的五个 get_* 命令中缩小搜索结果。有几种方法可以做到这一点,但最常见的方法是基于对象的名称。最简单的 pattern 是找到一个具有我们正在寻找的确切名称的对象。例如,在 Vivado的 Tcl console中:
> get_ports clk clk
同样,可以找到名称与特定 pattern匹配的所有对象:
> get_pins pll_i/* pll_i/clk_in1 pll_i/clk_out1 pll_i/clk_out2
请注意,这两个示例中的输出都是对象。在 Vivado的 Tcl console中,为了方便,把这些对象的名字都打印出来了。
更重要的是,请注意每个 FPGA 工具相对于这些 search patterns的行为略有不同。此处的示例使用了 Vivado 。同样的原则也适用于其他工具。
星号(“*”)是通配符字符(wildcard character),可替代任意数量的字符。问号(“?”)代替一个字符(character)。这就像带有文件名的通配符(wildcards)一样工作。
请注意,就像文件名一样,通配符与 hierarchy separator 不匹配(例如“/”与 Vivado 和“|”与 Quartus)。
hierarchical 路径和文件目录之间也有相似之处: 搜索是针对 top-level hierarchy进行的,它类似于 file 系统的 root 目录。所以,例如:
> get_pins */clk_* pll_i/clk_in1 pll_i/clk_out1 pll_i/clk_out2 > get_pins pll_i/clk_out? pll_i/clk_out1 pll_i/clk_out2
需要指定每个逻辑单元(logic element)在 hierarchy 中的确切位置通常是一个重大缺点: 我们要找的逻辑单元往往在不同的位置。这是用“-hierarchical”解决的: 当此 flag 存在时,将在 hierarchy中的所有位置搜索 pattern 。
一般来说,不推荐使用通配符找逻辑单元。唯一的例外是当 search pattern 非常简单或别无选择时。有一个单独的页面解释了如何使用通配符和“-hierarchical”,并且还显示了此方法的局限性。
使用 -filter
使用基于通配符的 search patterns 有几个缺点。最显着的缺点是 hierarchy 必须精确定义或根本不定义。一个问题是通常希望将搜索限制为属于 sub-hierarchy的对象。然而,这对于通配符是不可能的,“-hierarchical”选项也不能解决这个问题。
由于这个原因和其他原因,定义 search patterns 的首选方法是使用 -filter 选项。此选件随 boolean 表达式一起提供。使用此选项时,仅保留此表达式(expression)为 true 的搜索结果。
例如,
> get_pins */clk_* pll_i/clk_in1 pll_i/clk_out1 pll_i/clk_out2 > get_pins */clk_* -filter {name =~ *2*} pll_i/clk_out2
在此示例中,在通过通配符找到的每个对象上检查了名为“name”的属性(property)。仅当此属性与“*2*”匹配时,对象才会保留在搜索结果中。换句话说,只有当对象的名称中包含“2”时。
从实用的角度来看,这个例子并不有趣。使用“-hierarchical”时更有趣: 没有任何 search pattern,所有对象都找到了。换句话说,这个命令在所有 hierarchies中找到所有引脚:
get_pins -hierarchical
从这一点来看,可以缩小 -filter的搜索结果范围:
> get_pins -hierarchical -filter {name =~ pll_i/*/*out1 } pll_i/inst/clk_out1 > get_pins -hierarchical -filter {name =~ pll_i/*out1 } pll_i/clk_out1 pll_i/inst/clk_out1 > get_pins -hierarchical -filter {name =~ *out1 } pll_i/clk_out1 pll_i/inst/clk_out1
请注意, curly braces (“{”和“}”)的含义只是它们内部的部分不应被 Tcl interpreter修改。
另请注意, search pattern 属于 -filter 选项,并且表现不同: 它将“name”视为对象的属性。因此,所有字符都被平等对待: “/”没有特殊意义。 “/”是 hierarchy separator并不重要。所有字符,包括“/”,都可以与通配符(“*”)匹配。同样,所有字符,包括“/”,都可以在 search pattern中使用。如果没有 -filter,这当然不是真的。
任何字符都可以与“*”匹配的事实使 -filter 成为更强大的工具,但这种优势也可能导致错误: 很容易忘记,无辜的“*”可能会意外地与“pll_i/clk_”和“pll_i/inst/clk_”匹配,如上例所示。
正确使用此功能是为了在 hierarchy的某处找到具有已知名称的逻辑单元:
> get_pins -hierarchical -filter {name =~ */clkout2_buf/O } pll_i/inst/clkout2_buf/O
如果我们确定设计中只有一 cell 名为“clkout2_buf”,则此方法正确。通过使用此命令,即使包含此逻辑单元的模块在 project的 hierarchy中移动,也始终可以找到此 cell 的输出引脚(output pin)。在实际场景中,最好选择一个比“clkout2_buf”更独特的名称。
同样的方法适用于 get_cells 和 get_nets,例如:
> get_cells -hierarchical -filter {name =~ */clk*_buf} pll_i/inst/clkf_buf pll_i/inst/clkout1_buf pll_i/inst/clkout2_buf
但是 -filter 适用于所有属性,而不仅仅是名称。因此,这个命令会找到名称中包含“bar”的所有寄存器(registers):
get_cells -hier -filter {primitive_type =~ register.*.* && name =~ *bar*}
请注意“&&”运算符,它表示逻辑与(logical AND)(就像在 Verilog 和 C中一样)。
这个命令中的“primitive_type”和“name”只是属性的名字。“=~”运算符进行比较,并允许通配符。
回想一下,对象的属性可以与 "report_property" 命令一起列出(在 Vivado中)。
所以 -filter 的使用非常灵活。遗憾的是,并非所有 FPGA 工具都具备此功能。
注意, Vivado 有一个名为 filter的命令,它执行的操作与 -filter相同。因此,以下两个命令是等效的:
set result [get_cells -hierarchical -filter {name =~ *_reg}] set result [filter [get_cells -hierarchical] {name =~ *_reg}]
第二种格式在对象列表存储在变量中时很有用。因此上面的两个命令也等同于此:
set all_cells [get_cells -hierarchical]
set result [filter $all_cells {name =~ *_reg}]
使用 -regex
熟悉 regular expressions 的人可能想要使用此选项。这样做通常不是一个好主意,主要是因为它会让其他人更难理解时序约束。支持 regular expressions 的 FPGA 工具通常也支持 -filter 选项,因此使用 -filter几乎总是更好的选择。
回想一下,通常的 search pattern 的主要问题是 hierarchy separator 与通配符不匹配。
所以 -regexp 可以解决这个问题,如以下几个示例所示:
> get_pins -hierarchical -regexp {.+/clk_out[123]} pll_i/clk_out1 pll_i/clk_out2 pll_i/inst/clk_out1 pll_i/inst/clk_out2 > get_pins {pll_i/[^/]+/clk[^/]+} -hierarchical -regexp pll_i/inst/clk_in1 pll_i/inst/clk_out1 pll_i/inst/clk_out2
第一个命令表示“.”与任意字符匹配。这包括“/”( hierarchy separator)。
第二个命令演示了如何使用“[^/]+”来匹配除 hierarchy separator之外的任何内容。因此,这允许控制搜索结果中 hierarchy 的准确深度。这个命令还演示了 search pattern 不是 -regexp的 argument ,而是 -regexp 改变了 search pattern的语法。
请注意, regular 表达式必须与对象的全名匹配。换句话说,这些工具隐含地在 regular 表达式的开头添加了一个“^”,并在末尾添加了一个“$”。
但是,如果有另一种方法可以达到相同的结果,请不要使用 -regex 。大多数其他 FPGA 设计师将无法理解 search pattern。
使用 -of_object
-of_object 允许根据它们与其他对象的关系查找对象,而不是根据它们的名称查找对象。在大多数情况下,“-of_objects”大致表示“连接到”。
例如,为了找到连接到网络的所有引脚:
> get_pins -of_objects [get_nets bar] bar_reg_reg/D baz_metaguard_reg/D bar_reg__0/Q
或者 cell的所有引脚:
> get_pins -of_objects [get_cells bar_reg_reg] bar_reg_reg/Q bar_reg_reg/C bar_reg_reg/CE bar_reg_reg/D bar_reg_reg/R
或作为时钟起源的引脚:
> get_pins -of_objects [get_clocks clk_out1_clk_wiz_1] pll_i/inst/mmcme3_adv_inst/CLKOUT0
同样的方法也适用于查找网络。例如,哪些网络连接到特定的引脚?
> get_nets -of_objects [get_pins bar_reg_reg/C] pll_clk_6
或者,哪些网络连接到相关的 cell?
> get_nets -of_objects [get_cells bar_reg_reg] bar_reg_OBUF pll_clk_6 <const1> bar <const0>
同样,可以通过这种方式搜索 cells 。例如,哪些 cells 连接到 @pll_clk_6?
> get_cells -of_objects [get_nets pll_clk_6]
bar_reg__0 bar_reg_reg pll_i
请注意,“pll_i”是时钟 Wizard(Clock Wizard)的 IP中的 instantiation name 。这可能不是想要的搜索结果。所以也许可以将搜索结果缩小到触发器?
> get_cells -of_objects [get_nets pll_clk_6] -filter {primitive_type =~ register.*.*} bar_reg__0 bar_reg_reg
到目前为止,上面的 -of_objects 示例演示了引脚、网络和 cells 如何相互引用。但也可以使用此选项找到时钟对象:
> get_clocks -of_objects [get_nets pll_clk_6] clk_out2_clk_wiz_1 > get_clocks -of_objects [get_cells bar_reg_reg] clk_out2_clk_wiz_1 > get_clocks -of_objects [get_pins bar_reg_reg/C] clk_out2_clk_wiz_1
这三个命令显示了如何根据与其相连的逻辑单元找到时钟。请注意,当此逻辑单元是 cell时,可能会有多个结果。例如:
> get_clocks -of_objects [get_cells pll_i] clk clk_out1_clk_wiz_1 clk_out2_clk_wiz_1
回想一下“pll_i”是一 IP,所以它的引脚就是这个模块的端口:
> get_pins -of_objects [get_cells pll_i] pll_i/clk_in1 pll_i/clk_out1 pll_i/clk_out2
所以为了找到具体的时钟, get_pins 比较安全:
> get_clocks -of_objects [get_pins pll_i/clk_out2] clk_out2_clk_wiz_1
但当然,最好在外部 I/O 端口上找到时钟对象:
> get_clocks -of_objects [get_ports clk] clk
或者,使用更短的命令,其等效性如下:
> get_clocks [get_ports clk] clk
说到 get_ports,它也可以与 -of_objects配合使用。请参阅有关特定于 get_ports的可能性的文档。与其他命令类似的可能性毫无意义,例如:
> get_ports -of_objects [get_nets clk] clk
总之, -of_objects 是选择特定逻辑单元的绝佳方法。尤其是因为很难根据对象的名称进行搜索,这是下面的下一个主题。
不幸的是,许多 FPGA 工具不支持 -of_objects,这使得我们只能依赖不太可靠的方法。
按名称搜索的问题
如上所述,时序约束的准确性取决于找到逻辑单元的准确性。至少其中一些搜索结果取决于对象的名称。让我们看看这会如何引起麻烦。
例如,“get_ports clk”用于在时钟对象和特定 I/O 引脚上存在的信号之间建立连接。此 I/O 引脚由名为“clk”的端口对象表示:
create_clock -period 4.000 -name clk [get_ports clk]
但是为什么这款端口对象会得到“clk”这个名字呢?答案与综合工具如何创建网表有关: Verilog 代码中 top-level 端口的名称也被选为网表的 top-level 端口的名称。这是一个显而易见的选择,因此任何合适的综合工具都会做同样的事情。
但是 cells的名称呢?我们以上面 Verilog 代码中名称为“foo_reg”的寄存器为例。代表这个寄存器(register)的触发器的 cell 对象叫什么名字? Vivado的综合工具为这个对象选择了“foo_reg_reg”这个名字。所以很明显,这款综合工具在 Verilog 的代号中倾向于在名字上加一个“_reg”后缀。这听起来像是一个可以依赖的规则。但另一个综合工具(synthesizer)可能会做一些不同的事情。
但是名为“bar”的寄存器呢?相关的 cell 对象的名称应该是“bar_reg”,但综合工具却做出了不同的选择: “bar_reg__0”。这是因为在 Verilog 代码中有一个名为“bar_reg”的寄存器。因此,为避免名称冲突,综合工具选择了一个略有不同的名称: 它添加了“_reg__0”而不仅仅是“_reg”。这个简单的例子演示了依赖对象名称的问题。
更糟糕的是,假设时序约束是在名称为“bar_reg”的寄存器被添加到 Verilog 代码之前编写的。在这种情况下,与 @bar 相关的 cell 对象照常获得名称“bar_reg”。所以时序约束会为对象使用这个名称。在后期,名称为“bar_reg”的寄存器被添加到设计中。结果,请求的 cell 对象的名称从“bar_reg”更改为“bar_reg__0”。靠着“bar_reg”这个名字的时序约束突然不对了。运气好的话,这些工具会发布关于此的 warning 。
使用对象的名称可能出错还有其他可能的原因。例如,如果扇出(fan-out)超出限制,这些工具可能会自动复制寄存器。发生这种情况时,额外的寄存器可能不会包含在时序约束中,因为新的寄存器的名称与 search pattern不匹配。
更严重的问题是逻辑单元意外包含在时序约束中。例如,属于 IP blocks的逻辑单元可能会发生这种情况。因为我们无法控制这些逻辑单元的名称,所以这些名称可能会意外地与时序约束中的 pattern 匹配。
时序约束中意外包含逻辑单元也可能是懒惰的结果: 时序约束通常是在为逻辑设计添加新功能的同时编写的。如果 search pattern 是通过反复试验编写的,则可能不会考虑未来逻辑单元的名称。因此,当添加新的逻辑时,其某些逻辑单元的名称可能会无意中与现有的时序约束匹配。
如何避免时序约束出错
第一个也是最重要的规则是,仅仅测试哪个逻辑单元与时序约束的 search pattern 匹配是不够的。即使您列出了这些逻辑单元的完整列表并仔细查看了此列表,也不能保证以后会添加有关逻辑的任何内容。如果对象的名称被综合工具更改或在实现的另一个阶段,这样的审查也不能保证时序约束将继续按预期工作。
因此,将 search patterns 视为数学表达式非常重要: 这些 search patterns 现在按预期工作是不够的。如果它们没有按预期工作,仅仅进行小的修复是不够的。相反,必须有一个逻辑解释来解释为什么 search pattern 是正确的,以及为什么它很可能随着时间的推移保持正确。
search patterns 依赖不太可能改变的东西也很重要。例如,工具不会更改 Verilog 代码中的例化名称。这对于模块、 IPs 和 primitives的例化也是如此。因此,当 hierarchical 路径仅由 Verilog 代码中写入的例化名称组成时,依赖它是安全的。使用在 IP block内部创建的名称并不那么安全。为了解释这一点,让我们看看这个命令:
get_clocks -of_object [get_pins pll_i/inst/clkout1_buf/O]
这是参考分配时钟的全局时钟缓冲器(global clock buffer)的输出引脚得到时钟对象的方法。问题是我们是否可以确定这会长期有效。
这种方法的问题是“inst”和“clkout1_buf”是时钟 Wizard IP(Clocking Wizard IP)内部制作的例化的名称。尽管这些名称不太可能更改,但不能保证它们不会更改。
一种可能的解决方案是找到实现 IP的 Verilog 代码,并将此 Verilog 直接包含在项目中。这确保将来不会发生任何变化。
另一种方法是查看 Verilog中的 IP 的例化。回想一下,它是:
clk_wiz_1 pll_i
(.clk_in1(clk),
.clk_out1(pll_clk_8),
.clk_out2(pll_clk_6));
因为这是一 black box的例化,所以每个端口在网表中都有一个引脚。名称不能更改,因为这是识别 IP 的端口的方式。因此保证名称为“pll_i/clk_out1”的引脚与 @pll_clk_8建立连接。所以这是获取时钟对象的安全方法:
get_clocks -of_object [get_pins pll_i/clk_out1]
请注意,如果 clk_wiz_1 只是项目中的另一个 Verilog 模块,这可能不起作用: 在这种情况下,不会代表此模块的例化创建引脚(因为综合工具通常通过合并网络来实现端口的连接)。一种可能的解决方案是使用出现在 hierarchy较低级别的名称。
因此,有几种名称可以依赖:
- 当 IP 作为 black box包含在项目中时,例化的名称和此 IP 的引脚的名称是安全的。
- primitive的例化也是如此: 例化和引脚的名称是安全的。这在引用全局时钟缓冲器时特别有用。另一种情况是触发器的显式例化凭借 FPGA的相关 primitive。这可以防止工具进行任何操作。
- FPGA的 I/O 端口的名称也可以依赖。这些是 top-level 模块的端口的名称。
- 为寄存器使用长且不寻常的名称。例如,如果所有 metastability guards 的名称都以“_metastability_do_not_protect”结尾,那么编写这样的 false path 约束是相当安全的:
set_false_path -to [get_cells -hier *_metastability_do_not_protect*]
请注意 pattern末尾的通配符。
这个约束(constraint)不太可能适用于它不应该适用的东西。名称也不太可能发生太大变化,以至于这款约束会忽略寄存器。然而,这是一种不太安全的策略。
大失败更好
最糟糕的情况是时序约束几乎正确: 当只有少数路径不包括在内时。或者当只有少数路径被错误地包含在时序约束中时。这种错误是最难发现的。
这也是短小精悍、数学风格的时序约束更胜一筹的主要原因。如果每一小组逻辑单元都有一个时序约束,那么很容易在这长长的规则列表中犯错。
为了演示这个想法,让我们回到上面显示的用于查找时钟对象的示例:
get_clocks -of_object [get_pins pll_i/inst/clkout1_buf/O]
如上所述,这个表达式(expression)的问题在于,如果 pll_i 的例化移动到 hierarchy中的另一个位置,则将找不到时钟对象。这会有多糟糕?
假设这个时钟对象(clock object)存储在 Tcl 变量中,如下所示:
set my_clock [get_clocks -of_object [get_pins pll_i/inst/clkout1_buf/O]]
然后 $my_clock 用在很多时序约束里面,所以涉及到很多路径。在这种情况下,如果 $my_clock 由于 bug 突然不包含任何时钟对象其实问题不大: 这个错误很有可能很快就会被发现,因为很多事情都会出错。
但是如果 $my_clock 只用在一个时序约束上,而这个约束是为了解决一个很少有什么效果的小问题,这是很糟糕的情况。很可能这个错误会被忽视。
综上所述: 一个写得很好的时序约束要么完美地工作,要么根本不工作。
本页展示了如何准确选择逻辑单元。在下一页中,这些知识用于定义选择性时序约束。