此页面属于关于时序(timing)的一系列页面。前几页解释了时序计算背后的理论,讨论了时钟周期约束,并展示了时序收敛(timing closure)的原理。现在是时候开始了解时序约束(timing constraints)的技术细节了。
create_clock 作为 Tcl 命令的含义
前面几页都是围绕着这个时序约束(timing constraint)展开的:
create_clock -period 4 -name clk [get_ports clk]
直到现在我才故意不讨论这一行的语法。所以是时候解释一下这到底意味着什么了。
这个时序约束是用 SDC (Synopsys Design 约束) 格式编写的,这是时序约束最常见的格式。 Vivado 和 Quartus 使用此格式,以及其他几种 FPGA 工具。
SDC 文件本质上是用 Tcl编写的脚本(script)。因此, SDC 文件的内容是一个简短的计算机程序,而不仅仅是信息的集合。但是, SDC 文件作为脚本的功能仅限于命令的一小部分,这些子集旨在用于编写约束(constraints): 并非 Tcl 脚本中允许的所有内容都可以在 SDC 文件中完成。
create_clock 命令用于定义时序约束。但实际上,这个命令告诉 FPGA 工具创建一个新的时钟对象。而“对象(object)”这个词的意思与软件工程中通常的意思相同。因此,新的时钟对象是作为对象存储在 Tcl interpreter内存中的,而对象有自己的属性(properties)。
例如, create_clock 命令中标有“-name clk”的部分将值“clk”提供给名为“name”的属性。回想一下前面的页面,时序报告(timing reports)中使用了这个名称: 名称“clk”与时序路径(timing paths)一起出现是因为这个约束(constraint)(或者更准确地说,因为这个时钟对象(clock object))。
后来,我们看到时钟还有其他名字,比如 clk_out1_clk_wiz_1 和 clk_out2_clk_wiz_1。这些实际上是其他时钟对象的名称,由工具自动创建。
有一个 Tcl 命令用于列出所有时钟: get_clocks。因此,对于上一页中带有两个时钟的示例,这是 Vivado的 Tcl console上的会话:
> get_clocks clk clkfbout_clk_wiz_1 clk_out1_clk_wiz_1 clk_out2_clk_wiz_1
下一页将更详细地解释get_clocks 和类似的命令。
也可以查看这些对象的属性。这些属性无需全部了解: 我展示这个只是为了说明时钟是对象。就个人而言,我从来不需要直接操作任何对象的属性。
> report_property [get_clocks clk] Property Type Read-only Value CLASS string true clock INPUT_JITTER double true 0.040 IS_GENERATED bool true 0 IS_PROPAGATED bool true 1 IS_USER_GENERATED bool true 0 IS_VIRTUAL bool true 0 NAME string true clk PERIOD double true 4.000 SOURCE_PINS string* true clk SYSTEM_JITTER double true 0.050 WAVEFORM double* true 0.000 2.000 > report_property [get_clocks clk_out1_clk_wiz_1] Property Type Read-only Value CLASS string true clock EDGES int* true 1 2 3 EDGE_SHIFT double* true 0.000 2.000 4.000 INPUT_JITTER double true 0.000 IS_GENERATED bool true 1 IS_INVERTED bool true 0 IS_PROPAGATED bool true 1 IS_RENAMED bool true 0 IS_USER_GENERATED bool true 0 IS_VIRTUAL bool true 0 MASTER_CLOCK clock true clk NAME string true clk_out1_clk_wiz_1 PERIOD double true 8.000 SOURCE pin true pll_i/inst/mmcme3_adv_inst/CLKIN1 SOURCE_PINS string* true pll_i/inst/mmcme3_adv_inst/CLKOUT0 SYSTEM_JITTER double true 0.050 WAVEFORM double* true 0.000 4.000
需要注意的是, create_clock 只是创建了一个对象(object)。这个命令的参数(parameters)仅决定如何设置这个对象的属性。例如,写着“-period 4”的部分(在我反复展示的时序约束中)只是意味着某个属性(property)(称为“PERIOD”)应该具有值 4。
如果你想亲自尝试这些 Tcl 命令,请注意 FPGA 工具之间存在差异。
在 Vivado中,这些命令需要打开 Implemented 设计才可以使用。
在 Quartus中,先打开 TimeQuest Timing Analyzer,然后点击 Create Timing Netlist、 Read SDC File 和 Update Timing Netlist。然后在 Tcl console上尝试一些命令,例如:
> join [ query_collection -all [ get_clocks ] ] "\n" > get_clock_info -waveform [get_clocks clk]
“时钟”这个词的含义
当 FPGA 工具使用“clock”一词时,通常是指时钟对象,而不是 FPGA内部的物理信号。在时序报告中尤其如此。
回想一下上一页,我曾多次使用术语“理论上的时钟”。这些实际上是时钟对象。它们在时序报告中被称为“时钟”,但在时序分析(timing analysis)中的使用表明它们只是信息的容器。
那么这些时钟对象和真正的信号之间有什么联系呢?我们已经在时序分析中看到了时钟对象的名称。这一切是如何协同工作的?
当工具执行设计的静态时序分析(static timing analysis)时,将检查所有路径(paths)。如果路径以触发器(flip-flop)开头,则工具会检查连接到触发器的时钟输入的信号(即网络(net)): 是否有任何时钟对象与此信号相关?例如,当信号是 @clk时,相关的时钟对象是与 create_clock 命令一起被命名为“clk”的时钟对象。找到相关的时钟对象后,工具可以从此对象的属性中提取必要的信息。
路径末端的触发器也会发生同样的事情。所以现在工具有两个时钟对象对应于路径。利用这些对象的信息,工具可以执行时序分析。
当然,同样的程序适用于任何时序逻辑元件(sequential element),而不仅仅是触发器。
为什么了解这一点很重要?其中,因为有时候时序报告里面有 error message 说有寄存器(registers)没有时钟。通常,这并不意味着触发器与其时钟输入没有任何连接。相反,这意味着工具没有找到与此时钟输入相关的时钟对象。也就是说,工具并没有查到这个触发器(flip-flop)的时钟输入的任何信息。所以问题通常不在逻辑设计,而是少了一个时序约束(或者写错了)。
值得再说一遍: 当它在时序报告中显示“时钟”时,这并不意味着逻辑设计中有一个具有该名称的信号,而是已经创建了一个具有该名称的时钟对象。我们怎么知道哪个信号?那是下一个话题。
这是谁的时钟?
使时序报告难以阅读的原因之一是时钟的名称。逻辑设计中的大多数时钟信号都是由锁相环(PLL)创建的,我们已经看到时序报告中出现的名称可能毫无用处。大多数 FPGA 工具允许通过向 SDC 文件添加命令来重命名时钟对象,但在大多数项目中并没有这样做。黄金法则 4是避免使用对您的项目来说很特别的东西。
当时钟的来源是 IP block (例如 Gigabit transceiver、 PCIe block 或 on-chip processor core)时,名称问题变得更加困难。在这种情况下,时钟的名称通常很少说明它的来源和相关内容。
那么这个问题该如何解决呢?让我们从最简单的情况开始,当时钟的名称来自我们自己的 SDC 文件中的 create_clock 命令时。这又是相同的时序约束:
create_clock -period 4 -name clk [get_ports clk]
这个命令中的最后一部分是“[get_ports clk]”。在 Tcl 语言中,方括号表示将括号中的内容作为 Tcl 命令执行,然后用这个命令的结果代替这些括号。
get_ports 命令找到名为“clk”的 I/O 端口。这个命令的结果就是代表这个端口的对象。所以在上面的 create_clock 命令中,这个对象是这个命令的 argument 。这就是 create_clock 在时钟对象和真正的信号之间建立联系的方式。
注意端口的名称和对象的名称都是“clk”。不要求它们相同,但建议这样做: 对象的名称出现在时序报告中。因此,端口这个名字通常是最好的选择。
也可以使用网络对象(net objects)和引脚对象(pin objects)作为信号的标识符。这在代表 IP blocks自动生成的时序约束中很常见。但是,如果您觉得有必要在您自己的约束中执行此操作,则很可能是您做错了什么。
因此,如果在 SDC 文件中使用了 create_clock 命令,则很容易判断时钟对象与哪个信号相关。但是工具自动创建的时钟对象又如何呢?
在这种情况下,识别时钟的最佳方法是查看时序报告。例如,在上一页的示例中,哪个时钟对象与 @pll_clk_8 相关?找出答案的一个简单方法是在时序报告中执行 text search 。于是搜索“pll_clk_8”,找到以下部分:
Location Delay type Incr(ns) Path(ns) Netlist Resource(s) -------------------------------------------------------------- ------------------- (clock clk_out1_clk_wiz_1 rise edge) 16.000 16.000 AG12 0.000 16.000 clk (IN) net (fo=0) 0.000 16.000 pll_i/inst/clkin1_ibuf/I AG12 INBUF (Prop_INBUF_HRIO_PAD_O) 0.738 16.738 pll_i/inst/clkin1_ibuf/INBUF_INST/O net (fo=1, routed) 0.105 16.843 pll_i/inst/clkin1_ibuf/OUT AG12 IBUFCTRL (Prop_IBUFCTRL_HRIO_I_O) 0.049 16.892 pll_i/inst/clkin1_ibuf/IBUFCTRL_INST/O net (fo=1, routed) 0.975 17.867 pll_i/inst/clk_in1_clk_wiz_1 MMCME3_ADV_X1Y0 MMCME3_ADV (Prop_MMCME3_ADV_CLKIN1_CLKOUT0) -4.438 13.429 pll_i/inst/mmcme3_adv_inst/CLKOUT0 net (fo=1, routed) 0.501 13.930 pll_i/inst/clk_out1_clk_wiz_1 BUFGCE_X1Y1 BUFGCE (Prop_BUFCE_BUFGCE_I_O) 0.101 14.031 pll_i/inst/clkout1_buf/O X2Y0 (CLOCK_ROOT) net (fo=1, routed) 1.369 15.400 pll_clk_8 SLICE_X49Y58 FDRE foo_reg_reg/C
这是 clk_out1_clk_wiz_1的源时钟路径(Source Clock Path),这就是问题的答案。
另一种方法是使用 Tcl 命令获取信息。具体如何操作因 FPGA 工具而异。在 Vivado中,打开 Implemented Design后可以使用如下所示的命令:
> get_clocks -of_objects [ get_nets pll_clk_8 ] clk_out1_clk_wiz_1
此方法需要知道网络的名称。有时它像本例一样简单,有时它需要找到这个网络(net)的名称。 FPGA 工具通常提供一种使用图形用户界面(GUI)执行此操作的方法。也可以使用 Tcl 命令来实现此目的。
事实上,我希望 Tcl 的几个示例让您相信了解如何正确使用 Tcl 的重要性。这就是下一页的内容。
使用 get_port的意义
在上面的例子中, create_clock 命令依靠 get_port 在时钟对象和物理输入引脚之间建立连接。如上所述,此连接对于了解哪些逻辑单元(logic elements)连接到此时钟(或从其生成的时钟)是必要的。
但是使用 get_port 并不是唯一的可能性。例如,也可以引用全局时钟缓冲器(global clock buffer)的输出引脚(output pin)。是这样的:
create_clock -name clk -period 4 [get_pins my_BUFG_inst/O]
不同之处在于工具将全局时钟缓冲器的输出引脚视为时钟的起源。也就是说,时钟路径(clock paths)的计算是从这个位置开始的。此原点的第一个时钟边沿(clock edge)出现在 0 ns,因此此输出引脚成为时间参考。
这是一个合法的时序约束,但它有两个重要的缺点:
- 必须要求工具将此时钟视为与所有其他时钟相关的unrelated 时钟 。即使对于使用相同锁相环生成的时钟也是如此。原因是这些工具将输出引脚视为时间参考。因此,对于时钟之间关于它们的时钟路径时延(clock path delays)的差异没有补偿(即不考虑时钟偏移(clock skews))。
- 不可能(或很难)定义与在 FPGA外部可见的时钟相关的I/O 时序约束。这是因为这样的约束依赖一个时钟对象作为时间参考。但再一次,时间参考是全局时钟缓冲器的输出引脚。从外部时钟到全局时钟缓冲器的时钟偏移是未知的(例如它随温度变化)。
因此,应尽可能使用 get_port 。否则,除了它本身,时钟的时序应该被认为是未知的。
本页介绍了许多 Tcl 命令,但没有充分解释它们。下一页填补了这一空白。