01signal.com

时钟周期约束与时钟对象

本页属于关于时序的系列文章之一。前几页解释了时序计算背后的理论,讨论了时钟周期约束,并展示了时序收敛的原则。现在开始进入时序约束的技术细节。

create_clock 作为 Tcl 命令的含义

前面几页都是围绕下面这条时序约束(timing constraints)展开的:

create_clock -period 4 -name clk [get_ports clk]

到目前为止,我刻意没有讨论这行命令的语法。现在是时候解释一下它的真正含义了。

这条时序约束是以 SDC(Synopsys Design Constraints)格式编写的,这是目前最常用的时序约束格式。Vivado 和 Quartus 以及其他多种 FPGA 工具都使用这种格式。

SDC 文件本质上是一个用 Tcl 编写的脚本(script)。因此,SDC 文件的内容是一小段计算机程序,而不仅仅是一组信息。不过,作为脚本,SDC 文件的能力仅限于一小部分用于编写约束的命令:并不是 Tcl 脚本中允许的所有内容都能在 SDC 文件中使用。

create_clock 命令用于定义一条时序约束。但实际上,这条命令是让 FPGA 工具创建一个新的时钟对象(clock object)。这里的“对象”一词,就是软件工程中通常所说的对象。因此,这个新时钟对象是存储在 Tcl 解释器内存中的一个实体,它拥有自己的属性。

例如,create_clock 命令中的 -name clk 部分就是把值 clk 赋给名为 name 的属性。回想一下前几页中某页的内容:这个名字出现在时序报告中,名为 clk 的时钟随着因这条约束(更准确地说,因这个时钟对象)而计算的时序路径一起出现。

后面的页面里,我们看到另外一些时钟名字,例如 clk_out1_clk_wiz_1 和 clk_out2_clk_wiz_1。这些其实就是其他时钟对象的名字,是工具自动创建的。

有一个 Tcl 命令可以列出所有时钟:get_clocks。以上一页中两个时钟的例子来说,在 Vivado 的 Tcl 控制台上会看到下面这样的会话:

> 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 只是创建一个对象。这条命令的参数只是决定如何设置这个对象的各个属性。例如,命令中的 -period 4(在我反复展示的那条时序约束里)只是表示名为 PERIOD 的属性取值应为 4。

如果你想亲自试试这些 Tcl 命令,请注意不同 FPGA 工具之间是有差异的。

在 Vivado 中,只有打开实现后的设计(Implemented Design)之后才能使用这些命令。

在 Quartus 中,先打开 TimeQuest Timing Analyzer,然后依次点击 Create Timing Netlist、Read SDC File 和 Update Timing Netlist。然后在 Tcl 控制台上试试一些命令,例如:

> join [ query_collection -all [ get_clocks ] ] "\n"
> get_clock_info -waveform [get_clocks clk]

单词“clock”的含义

当 FPGA 工具使用“clock”这个词时,通常指的是时钟对象,而不是 FPGA 内部的物理信号。在时序报告中尤其如此。

回想一下,在上一页我几次使用了“理论时钟(theoretical clocks)”这个说法。这些其实就是时钟对象。它们在时序报告里被称为 clocks,但从它们在时序分析中的用法可以看出,它们只是一些承载信息的容器而已。

那么,这些时钟对象和真实信号之间是什么关系?我们已经在时序分析中看到过时钟对象的名字。这一切是如何协同工作的呢?

当工具对设计进行静态时序分析(static timing analysis)时,会检查所有路径。如果一条路径从触发器开始,工具会检查连接到该触发器时钟输入端的信号(即网络 net):是否存在与这个信号相关的时钟对象?例如,当信号是 @clk 时,相关的时钟对象就是用 create_clock 命令命名为 clk 的那个。找到对应的时钟对象后,工具就可以从这个对象的属性中取得所需信息。

对于路径终点的触发器,也会进行同样的处理。于是,工具就得到与这条路径对应的两个时钟对象。利用这些对象中的信息,工具执行时序分析。

当然,同样的处理适用于任何时序元件(sequential element),而不仅仅是触发器。

为什么理解这一点很重要?原因之一是,时序报告中有时会看到一条错误消息,说有些寄存器没有时钟(registers with no clock)。通常,这并不意味着某个触发器的时钟输入端什么东西都没接,而是说工具没有找到与该时钟输入端相关的时钟对象。换句话说,工具没有找到关于这个触发器时钟输入端的任何信息。因此,问题通常不出在逻辑设计上,而是缺少了一条时序约束(或者时序约束写得不对)。

这一点值得再强调一遍:当时序报告里出现“clock”时,并不是说逻辑设计中存在一个同名信号,而是说创建了一个同名时钟对象。那么,怎么知道它对应哪个信号?这就是下一个话题。

这个时钟是谁的?

让时序报告难以阅读的一个原因,就是时钟的名字。逻辑设计中的大部分时钟信号都是由 PLL 产生的,而我们之前已经看到,时序报告中出现的名字可能并没有多大帮助。大多数 FPGA 工具都允许通过在 SDC 文件中添加命令来重命名时钟对象,但在大多数项目中人们并不会这样做。此外,黄金法则第 4 条(Golden Rule #4)也提醒我们,不要去搞那些只对你自己项目有意义的特殊做法。

当时钟来源是一个 IP 模块(IP block,例如千兆收发器、PCIe 模块或片上处理器核)时,命名问题就更加棘手了。这种情况下,时钟对象的名字往往很能难让你看出它来自何处、与什么相关。

那这个问题怎么解决?我们先从最简单的情况说起:时钟对象的名字来自我们自己 SDC 文件里的 create_clock 命令。这又是同一条时序约束:

create_clock -period 4 -name clk [get_ports clk]

这条命令的最后一部分是 [get_ports clk]。在 Tcl 语言中,方括号表示把括号里的内容当作一条 Tcl 命令来执行,然后用该命令返回的结果替换整个方括号。

get_ports 命令查找名为 clk 的 I/O 端口,其返回值是代表这个端口的对象。因此,在上面这条 create_clock 命令中,这个对象被作为参数传给了命令。就这样,create_clock 在时钟对象与真实信号之间建立起了联系。

注意,这里端口名和对象名都叫 clk。两者并不要求一致,但我建议保持一致,因为对象的名称会出现在时序报告中。因此,端口名通常是最好的时钟对象名。

也可以使用网络对象(net object)和引脚对象(pin object)来标识信号。在由 IP 模块自动生成的时序约束中,这种做法很常见。但如果你在自己写的约束中觉得需要这样做,那很可能说明你哪里做错了。

因此,只要某个 SDC 文件里有 create_clock 命令,就很容易判断这个时钟对象对应哪个信号。但由工具自动创建的时钟对象又该怎么办呢?

这种情况下,识别时钟的最好办法是查看时序报告。例如,在上一页的例子中,哪个时钟对象与 @pll_clk_8 相关?一个简单的办法是在时序报告里做文本搜索。搜索 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 中,打开实现后的设计之后,可以使用下面这样的命令:

> get_clocks -of_objects [ get_nets pll_clk_8 ]
clk_out1_clk_wiz_1

这个方法要求你知道该网络的名称。有时像本例一样简单,有时则需要先把这个网络的名字找出来。FPGA 工具通常都会提供通过图形界面(GUI)来完成这件事的途径,也可以使用 Tcl 命令来达到目的。

事实上,我希望这几个 Tcl 示例能让你明白,学会正确使用 Tcl 是多么重要。这正是下一页要讲的内容。

使用 get_port 的意义

在上面的例子中,create_clock 命令依靠 get_port 在时钟对象与物理输入引脚之间建立联系。正如上面所说,这种联系是必要的,否则就无法知道哪些逻辑元件连接在这个时钟(或由它派生的其他时钟)上。

但使用 get_port 并不是唯一的选择。例如,也可以指向某个全局时钟缓冲器(global clock buffer)的输出引脚。像这样:

create_clock -name clk -period 4 [get_pins my_BUFG_inst/O]

区别在于,工具把全局时钟缓冲器的输出引脚视为时钟的源点。换句话说,时钟路径的计算从该位置开始。这个源点上的第一个时钟边沿发生在 0 ns,因此这个输出引脚成了时间基准。

这是一条合法的时序约束,但它有两个重要缺陷:

因此,只要可能,就应该使用 get_port。否则,这个时钟与除它自身以外任何事物的时序关系都应当被视为未知。


本页介绍了不少 Tcl 命令,但都没有充分解释。下一页会补上这一课。

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