01signal.com

关于时钟周期约束的更多内容

本页属于关于时序的系列文章之一。在简要介绍了时序约束(timing constraints)背后的理论以及关于时钟周期约束的第一页之后,我们来看几个使用该约束的真实场景。

下一步:使用 PLL

上一页的例子里,外部时钟引脚直接连接到了逻辑上。在实际设计中,通常都会使用某种 PLL 来产生逻辑所用的时钟。这样做最明显的原因是逻辑需要的频率与外部时钟不同。但使用 PLL 还有助于消除外部时钟的不完美,尤其是抖动(jitter)。

可以用下面的 Verilog 代码把一个 PLL 加入到设计中:

module top(
    input clk,
    input foo,
    output reg bar_reg
);
    reg foo_reg;
    reg bar;
    wire pll_clk;

   clk_wiz_0 pll_i
   (.clk_in1(clk),
    .clk_out1(pll_clk));

always @(posedge pll_clk)
  begin
    foo_reg <= foo;
    bar <= !foo_reg;
    bar_reg <= bar;
  end
endmodule

这与前面的例子相似,但此时触发器的时钟是 @pll_clk,而不是 @clk。该 PLL 由 Vivado 的 Clocking Wizard IP 生成,在 Verilog 代码中以名为 clk_wiz_0 的模块形式出现。

本例中,Clocking Wizard 被配置为接受一个 250 MHz 的参考时钟,并在输出端口上产生一个 125 MHz 的时钟(即 @clk_out1)。为了简单起见,clk_wiz_0 没有复位(reset)输入,也没有 locked 输出。在大多数实际设计中,建议启用这些端口并使用它们。

关于 clk_wiz_0 还有一点:它启用了相位对齐(phase alignment)选项,因此它会把 @pll_clk 的时钟边沿与 @clk 的时钟边沿对齐。当设计中存在与外部时钟同步的 I/O 端口时,这个选项很有用:由于启用了相位对齐,外部时钟与内部时钟之间的时序关系变得可以预测。这对于那些必须满足相对外部时钟的时序要求的 I/O 端口是很有用的。

为了准确使用 Xilinx 的术语,Xilinx 的 FPGA 有两种类型的 PLL:一种叫 PLL,另一种叫 MMCM。对这个例子来说,二者之间的区别无关紧要。clk_wiz_0 是一个 MMCM,但为了叙述清晰,我一律用 PLL 来称呼它。

有必要再次说明,这里关于 PLL 所说的内容适用于市场上所有 FPGA。这个例子用 Vivado 来展示,但一个功能与 clk_wiz_0 完全相同的 PLL,也可以为任何 FPGA 生成。

带 PLL 的时序约束

关于带 PLL 的时序约束,最重要的一点是:你不需要做什么特殊处理。时序约束是相对于外部引脚(本例中是 @clk)来写的,如果 PLL 产生另一个频率的时钟,那就是工具要考虑的事了。

值得再说一次:绝不应该因为使用了 PLL,就需要额外写一条时序约束。如果你觉得有必要这样做,那很可能是设计本身有问题,而你用额外的时序约束去“修复”问题,并不能真正解决问题。

所以和之前一样,时序约束就只是:

create_clock -period 4.000 -name clk [get_ports clk]

使用 Quartus 的用户要注意本页所描述的那个陷阱。

带 PLL 的时序报告

现在我们来看与上一页示例中相同路径的时序报告。唯一的区别是加入了 PLL,如上所示。此处按时序报告中的实际顺序给出相关路径的分析。首先,这是分析的摘要:

Slack (MET) :             7.288ns  (required time - arrival time)
  Source:                 foo_reg_reg/C
                            (rising edge-triggered cell FDRE clocked by clk_out1_clk_wiz_0  {rise@0.000ns fall@4.000ns period=8.000ns})
  Destination:            bar_reg__0/D
                            (rising edge-triggered cell FDRE clocked by clk_out1_clk_wiz_0  {rise@0.000ns fall@4.000ns period=8.000ns})
  Path Group:             clk_out1_clk_wiz_0
  Path Type:              Setup (Max at Slow Process Corner)
  Requirement:            8.000ns  (clk_out1_clk_wiz_0 rise@8.000ns - clk_out1_clk_wiz_0 rise@0.000ns)
  Data Path Delay:        0.669ns  (logic 0.382ns (57.100%)  route 0.287ns (42.900%))
  Logic Levels:           1  (LUT1=1)
  Clock Path Skew:        -0.048ns (DCD - SCD + CPR)
    Destination Clock Delay (DCD):    -0.715ns = ( 7.285 - 8.000 )
    Source Clock Delay      (SCD):    -0.616ns
    Clock Pessimism Removal (CPR):    0.051ns
  Clock Uncertainty:      0.062ns  ((TSJ^2 + DJ^2)^1/2) / 2 + PE
    Total System Jitter     (TSJ):    0.071ns
    Discrete Jitter          (DJ):    0.103ns
    Phase Error              (PE):    0.000ns
  Clock Net Delay (Source):      1.389ns (routing 0.002ns, distribution 1.387ns)
  Clock Net Delay (Destination): 1.218ns (routing 0.002ns, distribution 1.216ns)

有几处明显的差异。首先,Requirement 是 8 ns,而之前是 4 ns。这符合预期,因为 PLL 的输出是 125 MHz。工具自动为这个输出额外创建了一条时序约束,其中的时钟周期等于 8 ns。由于时钟周期变长了 4 ns,而数据路径没有变,时序余量(slack)也随之增加了约 4 ns。

自动时序约束的另一个体现是,这份报告中 Path Group 写的是 clk_out1_clk_wiz_0,而之前写的是 clk。事实上,之前报告中凡是出现 clk 的地方,这份报告里现在都写成了 clk_out1_clk_wiz_0。自动时序约束在时序报告中的表示方式,会在后面与“多个时钟”相关的内容中讨论。

PLL 带来的一个较小影响是,时钟不确定性(clock uncertainty)上升到了 0.062 ns:前一个例子中是 0.035 ns。这是因为 Discrete Jitter 现在是 0.103 ns,而不是零。

下面来看时序分析本身。这一次,源时钟路径、数据路径和目的时钟路径放在一起展示,完全按真实报告中的样子给出:

    Location             Delay type                Incr(ns)  Path(ns)    Netlist Resource(s)
  -------------------------------------------------------------------    -------------------
                         (clock clk_out1_clk_wiz_0 rise edge)
                                                      0.000     0.000 r
    AG12                                              0.000     0.000 r  clk (IN)
                         net (fo=0)                   0.000     0.000    pll_i/inst/clkin1_ibuf/I
    AG12                 INBUF (Prop_INBUF_HRIO_PAD_O)
                                                      0.738     0.738 r  pll_i/inst/clkin1_ibuf/INBUF_INST/O
                         net (fo=1, routed)           0.105     0.843    pll_i/inst/clkin1_ibuf/OUT
    AG12                 IBUFCTRL (Prop_IBUFCTRL_HRIO_I_O)
                                                      0.049     0.892 r  pll_i/inst/clkin1_ibuf/IBUFCTRL_INST/O
                         net (fo=1, routed)           0.975     1.867    pll_i/inst/clk_in1_clk_wiz_0
    MMCME3_ADV_X1Y0      MMCME3_ADV (Prop_MMCME3_ADV_CLKIN1_CLKOUT0)
                                                     -4.474    -2.607 r  pll_i/inst/mmcme3_adv_inst/CLKOUT0
                         net (fo=1, routed)           0.501    -2.106    pll_i/inst/clk_out1_clk_wiz_0
    BUFGCE_X1Y0          BUFGCE (Prop_BUFCE_BUFGCE_I_O)
                                                      0.101    -2.005 r  pll_i/inst/clkout1_buf/O
    X2Y0 (CLOCK_ROOT)    net (fo=3, routed)           1.389    -0.616    pll_clk
    SLICE_X49Y58         FDRE                                         r  foo_reg_reg/C
  -------------------------------------------------------------------    -------------------
    SLICE_X49Y58         FDRE (Prop_EFF2_SLICEL_C_Q)
                                                      0.138    -0.478 f  foo_reg_reg/Q
                         net (fo=1, routed)           0.241    -0.237    foo_reg
    SLICE_X49Y58         LUT1 (Prop_D5LUT_SLICEL_I0_O)
                                                      0.244     0.007 r  bar__0_i_1/O
                         net (fo=1, routed)           0.046     0.053    p_0_in
    SLICE_X49Y58         FDRE                                         r  bar_reg__0/D
  -------------------------------------------------------------------    -------------------

                         (clock clk_out1_clk_wiz_0 rise edge)
                                                      8.000     8.000 r
    AG12                                              0.000     8.000 r  clk (IN)
                         net (fo=0)                   0.000     8.000    pll_i/inst/clkin1_ibuf/I
    AG12                 INBUF (Prop_INBUF_HRIO_PAD_O)
                                                      0.515     8.515 r  pll_i/inst/clkin1_ibuf/INBUF_INST/O
                         net (fo=1, routed)           0.066     8.581    pll_i/inst/clkin1_ibuf/OUT
    AG12                 IBUFCTRL (Prop_IBUFCTRL_HRIO_I_O)
                                                      0.034     8.615 r  pll_i/inst/clkin1_ibuf/IBUFCTRL_INST/O
                         net (fo=1, routed)           0.873     9.488    pll_i/inst/clk_in1_clk_wiz_0
    MMCME3_ADV_X1Y0      MMCME3_ADV (Prop_MMCME3_ADV_CLKIN1_CLKOUT0)
                                                     -3.934     5.554 r  pll_i/inst/mmcme3_adv_inst/CLKOUT0
                         net (fo=1, routed)           0.422     5.976    pll_i/inst/clk_out1_clk_wiz_0
    BUFGCE_X1Y0          BUFGCE (Prop_BUFCE_BUFGCE_I_O)
                                                      0.091     6.067 r  pll_i/inst/clkout1_buf/O
    X2Y0 (CLOCK_ROOT)    net (fo=3, routed)           1.218     7.285    pll_clk
    SLICE_X49Y58         FDRE                                         r  bar_reg__0/C
                         clock pessimism              0.051     7.336
                         clock uncertainty           -0.062     7.274
    SLICE_X49Y58         FDRE (Setup_DFF2_SLICEL_C_D)
                                                      0.067     7.341    bar_reg__0
  -------------------------------------------------------------------
                         required time                          7.341
                         arrival time                          -0.053
  -------------------------------------------------------------------
                         slack                                  7.288

与前面的例子相比,路径上只有一处不同:在外部时钟引脚和全局时钟缓冲器之间插入了 PLL(在报告中显示为 MMCME3_ADV_X1Y0)。

这个 PLL 产生的影响很大:在源时钟路径中,PLL 的延迟是 -4.474 ns,而在目的时钟路径中,同一个延迟是 -3.934 ns。这个负延迟表示 PLL 对时钟边沿做了调整,使全局时钟比输入引脚上的时钟稍微早一点。注意,在源时钟路径中,全局时钟缓冲器输出端的总延迟是 -0.616 ns;在目的时钟路径中,同样的总延迟是 7.285 ns,这比第二个时钟边沿(在 8 ns 处)早 0.715 ns。

换句话说,外部时钟引脚与(送到逻辑的)全局时钟之间的时间差,在两条时钟路径上几乎相同。尽管一条时钟路径按最大延迟计算、另一条按最小延迟计算,最终结果却几乎一样。

这并非巧合:PLL 以全局时钟输出为参考,因此会调整全局时钟缓冲器输入的相位,以确保它相对于时钟输入引脚的关系。PLL 补偿了最小延迟与最大延迟之间的差异。时序计算也反映了这一点:最快与最慢情况之间的差只有 0.1 ns。在前一个例子中没有 PLL,这个差值要大得多(0.575 ns,参见上一页的“时钟悲观消除”一节)。

为什么全局时钟被调整到比外部输入早约 0.6 ns,而不是别的值,这是另一个故事。在许多情况下,这样做更容易满足与 I/O 引脚相关的时序约束,所以工具会自动作出这个选择。不过,所有 FPGA 也都允许你手动操控这个延迟。

两个相关时钟

逻辑设计常常需要不止一个时钟。FPGA 设计中存在多个时钟本身就是一个话题,在时钟域入门中有讨论。时钟域和时序这两个话题密不可分,所以建议在继续往下读之前先(哪怕是粗略地)看看那篇入门文章。正因为这两个话题关系紧密,那篇入门文章与本系列页面会有一些重叠。

在下面的讨论中,我会经常使用“信号 X 与 @clk 同步”之类的说法。它的意思是,X 是一个触发器的输出,该触发器只在名为 clk 的时钟上升沿到来时才改变输出值(异步复位的情况除外,但这里不讨论)。自然,如果两个信号都是响应同一个时钟的触发器的输出,那么这两个信号就是“与同一个时钟同步”的。

现在我们来看由同一个 PLL 产生的两个时钟。这很有意思,因为在大多数情况下,这两个时钟被认为是相关时钟(related clocks)。为了理解为什么这很有意思,假设有一个触发器与其中一个时钟同步,另一个触发器与另外一个时钟同步。在这种情况下,这两个触发器之间传输信号是没问题的,就像它们与同一个时钟同步一样。FPGA 工具会保证此时时序要求得到满足。

为了更好地理解如何处理多个时钟,有一篇关于相关时钟和无关时钟(unrelated clocks)的单独页面。那篇页面是时钟域交叉(clock domain crossing)的入门。这里我们侧重于相关时钟的时序问题,特别是这类时钟在时序报告中的体现。

为了生成下面的时序报告,示例中使用了另一个 PLL(即 Clocking Wizard IP)。这个新 PLL 的名字是 clk_wiz_1。使用的 Verilog 代码如下:

module top(
    input clk,
    input foo,
    output reg bar_reg
);
    reg foo_reg;
    reg bar;
    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

和前面一样,这个 PLL 的输入(即 @clk)是 250 MHz,但它有两个输出:一个连接到 @pll_clk_8,频率为 125 MHz(也就是时钟周期为 8 ns,信号因此得名)。这与上一个例子中的 @pll_clk 完全一样。第二个输出连接到 @pll_clk_6,它的时钟周期是 6 ns,约等于 166.67 MHz。

由于 @pll_clk_8 和 @pll_clk_6 由同一个 PLL 产生,它们是相关时钟(related clocks)。

clk_wiz_1 也启用了相位对齐选项,主要是为了与前面的例子保持一致。顺便说一句,如果对此有任何疑问:现在仍然只有一条时序约束,和之前一样:

create_clock -period 4.000 -name clk [get_ports clk]

理解时钟摘要

关于所有时钟的信息会汇总在时序报告开头的时钟摘要(clock summary)中。我到现在才提到它,是因为当时钟数量多时这个摘要更有看头。所有 FPGA 工具都会在报告中生成这类摘要,经常看一下这部分是个好习惯:

-------------------------------------------------------------------------
| Clock Summary
| -------------
-------------------------------------------------------------------------

Clock                 Waveform(ns)         Period(ns)      Frequency(MHz)
-----                 ------------         ----------      --------------
clk                   {0.000 2.000}        4.000           250.000
  clk_out1_clk_wiz_1  {0.000 4.000}        8.000           125.000
  clk_out2_clk_wiz_1  {0.000 3.000}        6.000           166.667
  clkfbout_clk_wiz_1  {0.000 2.000}        4.000           250.000

这个部分最大的优点是容易看懂,也很容易检查出与时序约束有关的最常见错误:某个时钟的频率是否正确。

这份时钟摘要说明时序约束被正确解释了:定义了一个名为 clk 的外部时钟,它的周期是 4 ns。此外还有三个派生时钟(derived clocks):clk_out1_clk_wiz_1、clk_out2_clk_wiz_1 和 clkfbout_clk_wiz_1。正如名字所示,它们是因为名为 clk_wiz_1 的 PLL 而自动生成的。

前两个时钟(clk_out1_clk_wiz_1 和 clk_out2_clk_wiz_1)是 PLL 的两个输出。第三个时钟(clkfbout_clk_wiz_1)是 PLL 用来把全局时钟的相位调整到与外部时钟一致所用的反馈时钟。clkfbout_clk_wiz_1 与 @clk 的频率相同。

当相位对齐选项启用时,PLL 的反馈时钟(clkfbout_clk_wiz_1)被连接到全局时钟缓冲器。由于 PLL 总是让自己的输入时钟与反馈时钟同步(通过让它们对齐,或在它们之间保持固定延迟),clkfbout_clk_wiz_1 是全局时钟这一点,也保证了输入时钟与其他输出时钟之间的延迟是已知的。

需要特别注意的是,clk_out1_clk_wiz_1、clk_out2_clk_wiz_1 和 clkfbout_clk_wiz_1 并不定义真实存在的时钟,它们只是软件在时序计算中使用的时钟符号。一个能说明这些时钟只是理论时钟的证据,是它们显示在摘要中的波形:这些波形反映的是每个时钟的占空比。四个时钟(clk 和这三个理论时钟)都在 0 ns 处有上升沿这件事没有任何实际含义。它尤其表示这四个时钟是完美对齐的。即使它们是对齐的,也只能通过时序计算间接看出来,而实际对齐并不完美。

这些理论时钟是如何使用的,将在下文结合涉及其中两个时钟的时序计算来讨论。

关于这些理论时钟,还有一点很重要:它们的产生并没有显式的时序约束。clk_out1_clk_wiz_* 这类时钟的创建,不存在于设计的源文件或工具生成的文件中的任何地方。在大多数情况下,试图写一条这样的时序约束是错误的,因为这样时钟路径的延迟就不会被正确计算。如果写了这种约束,工具对时钟之间相对时序的计算就会出错,相关时钟域之间的路径也就无法被正确计算。

所以再说一次:只写一条时序约束,就应当能自动为 PLL 的所有输出时钟生成相应的时序约束。

两个相关时钟的时序报告

从上面的 Verilog 可以看出,@foo_reg 与 @pll_clk_8 同步,@bar 与 @pll_clk_6 同步。因此,更新 @bar 的那条语句涉及一次时钟域交叉(clock domain crossing):

bar <= !foo_reg;

这两个时钟是相关时钟(related clocks),所以工具会通过下面这项计算保证 @bar 的时序要求得到满足(针对 tsu):

Slack (MET) :             0.475ns  (required time - arrival time)
  Source:                 foo_reg_reg/C
                            (rising edge-triggered cell FDRE clocked by clk_out1_clk_wiz_1  {rise@0.000ns fall@4.000ns period=8.000ns})
  Destination:            bar_reg__0/D
                            (rising edge-triggered cell FDRE clocked by clk_out2_clk_wiz_1  {rise@0.000ns fall@3.000ns period=6.000ns})
  Path Group:             clk_out2_clk_wiz_1
  Path Type:              Setup (Max at Slow Process Corner)
  Requirement:            2.000ns  (clk_out2_clk_wiz_1 rise@18.000ns - clk_out1_clk_wiz_1 rise@16.000ns)
  Data Path Delay:        1.160ns  (logic 0.307ns (26.466%)  route 0.853ns (73.534%))
  Logic Levels:           1  (LUT1=1)
  Clock Path Skew:        -0.250ns (DCD - SCD + CPR)
    Destination Clock Delay (DCD):    -0.681ns = ( 17.319 - 18.000 )
    Source Clock Delay      (SCD):    -0.600ns = ( 15.400 - 16.000 )
    Clock Pessimism Removal (CPR):    -0.169ns
  Clock Uncertainty:      0.182ns  ((TSJ^2 + DJ^2)^1/2) / 2 + PE
    Total System Jitter     (TSJ):    0.071ns
    Discrete Jitter          (DJ):    0.103ns
    Phase Error              (PE):    0.120ns
  Clock Net Delay (Source):      1.369ns (routing 0.002ns, distribution 1.367ns)
  Clock Net Delay (Destination): 1.208ns (routing 0.002ns, distribution 1.206ns)

    Location             Delay type                Incr(ns)  Path(ns)    Netlist Resource(s)
  -------------------------------------------------------------------    -------------------
                         (clock clk_out1_clk_wiz_1 rise edge)
                                                     16.000    16.000 r
    AG12                                              0.000    16.000 r  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 r  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 r  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 r  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 r  pll_i/inst/clkout1_buf/O
    X2Y0 (CLOCK_ROOT)    net (fo=1, routed)           1.369    15.400    pll_clk_8
    SLICE_X49Y58         FDRE                                         r  foo_reg_reg/C
  -------------------------------------------------------------------    -------------------
    SLICE_X49Y58         FDRE (Prop_EFF_SLICEL_C_Q)
                                                      0.139    15.539 f  foo_reg_reg/Q
                         net (fo=1, routed)           0.807    16.346    foo_reg
    SLICE_X49Y58         LUT1 (Prop_D5LUT_SLICEL_I0_O)
                                                      0.168    16.514 r  bar__0_i_1/O
                         net (fo=1, routed)           0.046    16.560    p_0_in
    SLICE_X49Y58         FDRE                                         r  bar_reg__0/D
  -------------------------------------------------------------------    -------------------

                         (clock clk_out2_clk_wiz_1 rise edge)
                                                     18.000    18.000 r
    AG12                                              0.000    18.000 r  clk (IN)
                         net (fo=0)                   0.000    18.000    pll_i/inst/clkin1_ibuf/I
    AG12                 INBUF (Prop_INBUF_HRIO_PAD_O)
                                                      0.515    18.515 r  pll_i/inst/clkin1_ibuf/INBUF_INST/O
                         net (fo=1, routed)           0.066    18.581    pll_i/inst/clkin1_ibuf/OUT
    AG12                 IBUFCTRL (Prop_IBUFCTRL_HRIO_I_O)
                                                      0.034    18.615 r  pll_i/inst/clkin1_ibuf/IBUFCTRL_INST/O
                         net (fo=1, routed)           0.873    19.488    pll_i/inst/clk_in1_clk_wiz_1
    MMCME3_ADV_X1Y0      MMCME3_ADV (Prop_MMCME3_ADV_CLKIN1_CLKOUT1)
                                                     -3.890    15.598 r  pll_i/inst/mmcme3_adv_inst/CLKOUT1
                         net (fo=1, routed)           0.422    16.020    pll_i/inst/clk_out2_clk_wiz_1
    BUFGCE_X1Y0          BUFGCE (Prop_BUFCE_BUFGCE_I_O)
                                                      0.091    16.111 r  pll_i/inst/clkout2_buf/O
    X2Y0 (CLOCK_ROOT)    net (fo=2, routed)           1.208    17.319    pll_clk_6
    SLICE_X49Y58         FDRE                                         r  bar_reg__0/C
                         clock pessimism             -0.169    17.150
                         clock uncertainty           -0.182    16.967
    SLICE_X49Y58         FDRE (Setup_DFF2_SLICEL_C_D)
                                                      0.067    17.034    bar_reg__0
  -------------------------------------------------------------------
                         required time                         17.034
                         arrival time                         -16.560
  -------------------------------------------------------------------
                         slack                                  0.475

如前所述,时序计算是一个假想实验:一个假想秒表随某个时钟边沿一起启动。在前面的例子中,秒表从 0 ns 启动。但在本次计算中,秒表随 16 ns 处的时钟边沿启动。这是因为两个时钟的周期不相等。计算针对第一个时钟和第二个时钟之间最坏情况的组合来进行。

第一个时钟的上升沿在 0 ns、8 ns、16 ns、24 ns……第二个时钟的上升沿在 0 ns、6 ns、12 ns、18 ns、24 ns……因此,第一个时钟与第二个时钟之间最小的时间间隔,是第一个时钟的 16 ns 与第二个时钟的 18 ns 之间的那个间隔。这次时序计算考察的正是这种情况。

这意味着数据路径被限制在约 2 ns 内,相当于 500 MHz。这是一个非常严格的要求,但由于数据路径上只有一个 LUT,而且两个触发器位于同一个切片(slice)上,所以很容易达到目标。不过,这个例子也说明了,为什么为相关时钟选择能良好配合的频率很重要。通常的做法是让一个时钟的频率是另一个时钟频率的整数倍,这样可以避免本例中出现的 2 ns 间隔这类情况。

顺便说一句,如果实在无法选择能良好配合的频率,解决办法就是把它们当作无关时钟(unrelated clocks),如这一页所述。

派生时钟

前面我提到 clk_out1_clk_wiz_1 和 clk_out2_clk_wiz_1 是理论时钟。时序报告中这样体现这一点:注意两条路径的计算都以外部引脚(AG12)为起点。这怎么会合理呢?实际上,这个引脚上的信号是参考时钟,不是这两个时钟中的任何一个。

以 clk_out1_clk_wiz_1 为例:这次计算的思路是假装外部引脚上有一个 125 MHz 的时钟。实际上,125 MHz 的时钟只存在于 PLL 的输出端,也就是 MMCME3_ADV_X1Y0 的输出端。不过,时序计算并没有把 PLL 的频率变换纳入其中,而是用一个理论时钟来代替。这个理论时钟具有从 0 ns 开始的理想波形。PLL 被当作一个不改变任何时钟频率、只增加延迟的器件。

因此,理论时钟(如 clk_out1_clk_wiz_1)定义的是时钟的波形(频率、占空比、抖动等)。但它并不定义这个时钟与 FPGA 上作为真实信号存在的其他时钟之间的时序关系。

那么,如何看出 @pll_clk_6 和 @pll_clk_8 实际上是对齐的呢?答案在于比较这些时钟的实际时序与理想时序。例如,在上面的计算中,clk_out1_clk_wiz_1 的理论时钟边沿在 16 ns 处,而 clk_out2_clk_wiz_1 的时钟边沿在 18 ns 处,晚 2 ns。现在比较全局时钟树输出端的两个时钟边沿:根据时序报告,第一个时钟的边沿到达于 15.400 ns;第二个时钟的边沿到达于 17.319 ns。因此按计算,时间差是 1.919 ns,而不是 2 ns。这比理想差值只少了 0.081 ns。所以这两个时钟确实是对齐的。

再与前面只有一个时钟的例子比较:两个时钟边沿之间的理想时间差就是时钟周期,即 8 ns。但根据相应的时序报告(见上文),第一个时钟边沿到达于 -0.616 ns,第二个时钟边沿到达于 7.285 ns。理想时间差是 8 ns,实际却是 7.901 ns。所以第二个时钟边沿比预期早了 0.099 ns。

所以,对于两个相关时钟的情况,与理想时间差的偏差是 0.081 ns。对于只有一个时钟的情况,这个偏差也差不多,为 0.099 ns。在两种情况下,偏差的原因都是:第一个时钟边沿的计算使用最大延迟,而第二个时钟边沿使用最小延迟。

结论是:@pll_clk_6 和 @pll_clk_8 的对齐精度,与它们其实是同一个时钟时的精度大致相同。

注意,即使没有启用 PLL 的相位对齐选项,这两个时钟也会彼此对齐:PLL 的各输出通常本来就是对齐的。这种对齐是通过使用延迟几乎相同的全局时钟缓冲器来保证的,无论其扇出(fan-out)多大。换句话说,每个时钟缓冲器接了多少逻辑元件并不重要,从 PLL 输出到目的地的延迟大致相同。

关于相关时钟我们学到了什么

对这份时序报告的分析表明,两个相关时钟域之间的路径,大致等价于同一个时钟域内的路径。不过,二者并不完全相同。尤其是时钟不确定性(clock uncertainty)从 0.062 ns 上升到 0.182 ns,因为每个时钟都有自己的抖动,而且对齐也不是完美的。

这份时序报告也展示了两个时钟的对齐是如何反映在时序计算中的。

当 FPGA 设计中存在时钟域交叉(clock domain crossing)时,最好在时序报告中检查这些时钟之间的相互作用。目的是确保工具对时钟的处理符合我们的预期。以下是两个最需要检查的要点:

Vivado 可以生成 Clock Interaction Report,它以图形方式显示设计中的时钟域交叉,以及工具如何处理每一个交叉。其他 FPGA 工具也有类似功能,例如 Quartus Pro 的 CDC Viewer。只要条件允许,建议生成并查看这类报告。

另一个值得检查的是时钟是否对齐。如果设计错误地使用了不对齐的时钟,可能会给满足时序约束带来不必要的困难。例如,如果 @clk 与 @pll_clk_8 之间存在一条路径,工具会强制满足其时序约束,从而保证这条路径可靠工作。但要注意,@clk 是送入 PLL 的参考时钟,而 @pll_clk_8 是 PLL 的输出时钟,这两个时钟并没有对齐。结果是,工具可能会为了满足这条路径的时序约束而付出不必要的努力,其他路径则可能因为这种不必要的努力而无法满足自己的时序约束。

再说一次,关于时钟域的更多内容由这个系列介绍。

thold 同样重要

目前展示的所有时序计算都与 tsu 要求有关。人们自然会把注意力放在这个要求上,因为当工具无法满足时序约束时,几乎总是因为至少有一条路径没满足 tsu 的要求。

不过,保持时间 thold 仍然很重要:为了满足这个要求,工具有时会人为地在数据路径上增加布线延迟,从而让数据路径变慢。因此,虽然 thold 很少被说成是时序约束失败的原因,但它有可能是失败的隐藏原因。

事实上,连接两个触发器的那根导线就发生了类似的情况:在所有只涉及一个时钟的时序报告中,这根导线都在同一个切片(SLICE_X49Y58)内部,因此它的延迟一直是 0.241 ns。但当路径涉及两个时钟时,这个延迟升到了 0.807 ns。

解释如下:0.241 ns 是放置在同一个切片内、两个触发器之间所能达到的最小延迟。更长的延迟(0.807 ns)则是这根导线采用了另一种布线方式的结果。这不是偶然发生的:工具为了使 thold 要求得到满足,有意把布线绕得更长。报告中的 Data Path Delay 一行也反映了这一点:逻辑延迟只占 26.5%,其余都是布线延迟。仅这一点就表明有情况发生。下面还会详细讨论。

thold 要求的分析

回想本系列中理论部分的内容:thold 是指第二个触发器上的数据输入必须在时钟边沿之后继续保持稳定的时间。下面描述 thold 违例的情形:一个时钟边沿到达第一个触发器,同时第二个触发器也差不多在同一时刻收到一个时钟边沿(注意,这两个时钟边沿可以来自同一个时钟,也可以来自不同的时钟)。在时钟边沿的驱动下,第一个触发器经过一段延迟(clock-to-output)后更新其输出。但这个更新后的值到达第二个触发器太早了,于是第二个触发器无法可靠地采到它自己的时钟边沿到来之前的值:新值到达时,第二个触发器还没来得及完成对时钟边沿的响应。换句话说,thold 要求被违反了。

当两个触发器使用同一个时钟时,这种情况主要可能由时钟偏斜(clock skew)引起:如果时钟边沿先到达第一个触发器,那么第一个触发器的输出可能会过早改变。这样一来,更新后的信号到达第二个触发器时,就可能快到足以违反 thold 要求。注意,到达两个触发器的是同一个时钟边沿,所以时钟的频率和抖动都不起作用,只有两边的延迟差(也就是时钟偏斜)才起作用。

不过,我们下面要做的时序分析仍沿用最后一个例子,也就是涉及两个时钟 @pll_clk_8 和 @pll_clk_6 的例子。只有一个时钟的时序分析也类似,但没有这么有意思,因为只有一个时钟时,thold 要求太容易满足了。

所以,我们来看的这份时序报告是针对两个相关时钟生成的。这两个时钟频率不同,而且和前面一样,第一个时钟边沿与第二个时钟边沿之间存在各种组合。但与 tsu 计算不同,thold 的最坏情况是两个时钟边沿都在 0 ns 处:thold 违例发生在两个时钟边沿几乎同时出现的时候,所以没有其他组合比这更坏。

带着这些认识,来看这份时序报告:

Slack (MET) :             0.093ns  (arrival time - required time)
  Source:                 foo_reg_reg/C
                            (rising edge-triggered cell FDRE clocked by clk_out1_clk_wiz_1  {rise@0.000ns fall@4.000ns period=8.000ns})
  Destination:            bar_reg__0/D
                            (rising edge-triggered cell FDRE clocked by clk_out2_clk_wiz_1  {rise@0.000ns fall@3.000ns period=6.000ns})
  Path Group:             clk_out2_clk_wiz_1
  Path Type:              Hold (Min at Fast Process Corner)
  Requirement:            0.000ns  (clk_out2_clk_wiz_1 rise@0.000ns - clk_out1_clk_wiz_1 rise@0.000ns)
  Data Path Delay:        0.458ns  (logic 0.104ns (22.707%)  route 0.354ns (77.293%))
  Logic Levels:           1  (LUT1=1)
  Clock Path Skew:        0.127ns (DCD - SCD - CPR)
    Destination Clock Delay (DCD):    -0.542ns
    Source Clock Delay      (SCD):    -0.248ns
    Clock Pessimism Removal (CPR):    -0.421ns
  Clock Uncertainty:      0.182ns  ((TSJ^2 + DJ^2)^1/2) / 2 + PE
    Total System Jitter     (TSJ):    0.071ns
    Discrete Jitter          (DJ):    0.103ns
    Phase Error              (PE):    0.120ns
  Clock Net Delay (Source):      0.495ns (routing 0.002ns, distribution 0.493ns)
  Clock Net Delay (Destination): 0.576ns (routing 0.002ns, distribution 0.574ns)

    Location             Delay type                Incr(ns)  Path(ns)    Netlist Resource(s)
  -------------------------------------------------------------------    -------------------
                         (clock clk_out1_clk_wiz_1 rise edge)
                                                      0.000     0.000 r
    AG12                                              0.000     0.000 r  clk (IN)
                         net (fo=0)                   0.000     0.000    pll_i/inst/clkin1_ibuf/I
    AG12                 INBUF (Prop_INBUF_HRIO_PAD_O)
                                                      0.339     0.339 r  pll_i/inst/clkin1_ibuf/INBUF_INST/O
                         net (fo=1, routed)           0.025     0.364    pll_i/inst/clkin1_ibuf/OUT
    AG12                 IBUFCTRL (Prop_IBUFCTRL_HRIO_I_O)
                                                      0.015     0.379 r  pll_i/inst/clkin1_ibuf/IBUFCTRL_INST/O
                         net (fo=1, routed)           0.405     0.784    pll_i/inst/clk_in1_clk_wiz_1
    MMCME3_ADV_X1Y0      MMCME3_ADV (Prop_MMCME3_ADV_CLKIN1_CLKOUT0)
                                                     -1.721    -0.937 r  pll_i/inst/mmcme3_adv_inst/CLKOUT0
                         net (fo=1, routed)           0.167    -0.770    pll_i/inst/clk_out1_clk_wiz_1
    BUFGCE_X1Y1          BUFGCE (Prop_BUFCE_BUFGCE_I_O)
                                                      0.027    -0.743 r  pll_i/inst/clkout1_buf/O
    X2Y0 (CLOCK_ROOT)    net (fo=1, routed)           0.495    -0.248    pll_clk_8
    SLICE_X49Y58         FDRE                                         r  foo_reg_reg/C
  -------------------------------------------------------------------    -------------------
    SLICE_X49Y58         FDRE (Prop_EFF_SLICEL_C_Q)
                                                      0.049    -0.199 f  foo_reg_reg/Q
                         net (fo=1, routed)           0.343     0.144    foo_reg
    SLICE_X49Y58         LUT1 (Prop_D5LUT_SLICEL_I0_O)
                                                      0.055     0.199 r  bar__0_i_1/O
                         net (fo=1, routed)           0.011     0.210    p_0_in
    SLICE_X49Y58         FDRE                                         r  bar_reg__0/D
  -------------------------------------------------------------------    -------------------

                         (clock clk_out2_clk_wiz_1 rise edge)
                                                      0.000     0.000 r
    AG12                                              0.000     0.000 r  clk (IN)
                         net (fo=0)                   0.000     0.000    pll_i/inst/clkin1_ibuf/I
    AG12                 INBUF (Prop_INBUF_HRIO_PAD_O)
                                                      0.595     0.595 r  pll_i/inst/clkin1_ibuf/INBUF_INST/O
                         net (fo=1, routed)           0.042     0.637    pll_i/inst/clkin1_ibuf/OUT
    AG12                 IBUFCTRL (Prop_IBUFCTRL_HRIO_I_O)
                                                      0.022     0.659 r  pll_i/inst/clkin1_ibuf/IBUFCTRL_INST/O
                         net (fo=1, routed)           0.457     1.116    pll_i/inst/clk_in1_clk_wiz_1
    MMCME3_ADV_X1Y0      MMCME3_ADV (Prop_MMCME3_ADV_CLKIN1_CLKOUT1)
                                                     -2.474    -1.358 r  pll_i/inst/mmcme3_adv_inst/CLKOUT1
                         net (fo=1, routed)           0.209    -1.149    pll_i/inst/clk_out2_clk_wiz_1
    BUFGCE_X1Y0          BUFGCE (Prop_BUFCE_BUFGCE_I_O)
                                                      0.031    -1.118 r  pll_i/inst/clkout2_buf/O
    X2Y0 (CLOCK_ROOT)    net (fo=2, routed)           0.576    -0.542    pll_clk_6
    SLICE_X49Y58         FDRE                                         r  bar_reg__0/C
                         clock pessimism              0.421    -0.121
                         clock uncertainty            0.182     0.061
    SLICE_X49Y58         FDRE (Hold_DFF2_SLICEL_C_D)
                                                      0.056     0.117    bar_reg__0
  -------------------------------------------------------------------
                         required time                         -0.117
                         arrival time                           0.210
  -------------------------------------------------------------------
                         slack                                  0.093

首先,有几处明显的不同:Path Type 是 Hold,而不是之前的 Setup。这符合预期。同时它写的是 Min at Fast Process Corner,正好与 setup 路径相反:本次计算对数据路径使用最小延迟,而不是最大延迟。这适合对“数据输入变化太早”这种情况做最坏情况计算。

Requirement 是 0 ns,这对 hold 路径来说很典型。时钟的频率在这里不起作用:被考察的场景是两个时钟同时有上升沿。

至于时钟路径,注意源时钟路径中每个元件的延迟都一致地比目的时钟路径中对应元件的延迟短(而不是更长)。这同样与本次计算的目的相符,因为最坏情况是数据相对于第二个触发器收到时钟边沿来说变化得太早。多工艺角时序分析已经在上一页解释过。

但这份时序报告最重要的地方是:时序余量(slack)很小,只有 0.093 ns。这通常说明工具不得不花力气才能满足要求。不过要注意,时序余量小并不一定表示存在需要解决的问题。

在做 thold 时序分析时,时序余量小是相当常见的。那么,为什么这条路径看起来可疑呢?主要就是前面提到的、位于同一个切片(SLICE_X49Y58)内的两个触发器之间出现了较大的布线延迟。很可能在布局布线(place and route)的早期阶段,软件发现这两个触发器之间的 thold 要求没有被满足。那它是怎么修正的呢?

thold 违例意味着数据信号相对于第二个触发器的时钟边沿到得太早。修正的方法,是人为地在数据路径上增加延迟。这样一来,第二个触发器数据输入端的电平会晚一点才改变。工具很可能把两个触发器之间的导线绕得更长了,从而增加布线延迟,解决 thold 的问题。这种延迟增加本来可能给满足 tsu 要求造成麻烦,但在这个例子里没有出现这种问题:这条路径的时序约束被满足了(也就是 tsu 和 thold 的要求都达到了)。

不过,这个例子也说明,为了解决 thold 问题而做的修整,可能会造成一个看似无关的 tsu 问题。当一条路径的逻辑延迟与布线延迟之比非常低时,值得留意这一点。当然,布线延迟很大也可能有其他原因,尤其是扇出(fan-out)很大。但当扇出很低时(比如本例中扇出为 1),值得问问:这个很大的布线延迟是不是工具为了满足 thold 而有意插入的?

但为什么只有在两个时钟的情况下才会出现 thold 问题呢?答案是,有两个时钟时,时钟边沿到达两个触发器的时刻存在更大的不确定性。造成这种不确定性的因素有好几个,尤其是时钟偏斜(clock skew)和抖动。因为 thold 的计算发生在 0 ns 到 0 ns 之间,所以它对时钟边沿到达时刻的微小不确定性更加敏感。

因此,与两个相关时钟相关的不确定性,常常需要采取修正措施才能满足 thold 要求。这些修正当然都是工具自动完成的。不过,当出现难以满足时序约束的问题时,必须意识到这类修正的存在。

小结

本系列最后这两页展示了一些时序报告示例,它们都来自同一条具体的时序约束,也都是针对同一个简单逻辑示例的。希望这些例子能帮助你建立对时序基础知识的基本理解。

到了这一步,建议你去看一看自己设计中的时序报告,了解同样的原理如何应用到包含不止一个 LUT 的路径上。

下一页将开始讨论时序问题以及如何解决它们。

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