01signal.com

时序收敛的策略

本页属于关于时序的系列文章之一。前几页讲解了时序计算背后的理论,讨论了时钟周期约束,并介绍了时序收敛(timing closure)。但如果你已经把设计写得很小心,却仍然遇到时序问题,那该怎么办?本页试图回答这个问题。

引言

上一页中,我曾试图让你相信:解决时序收敛问题没有一种万能的方法。有时应该把注意力放在关键路径(critical path)上,有时则不应该。有时只需对工具的设置做一点小改动就能解决问题,有时却要困难得多。要找到问题的根源,你必须调动全部经验和智慧。时序收敛没有办法概括成一张检查清单。

话虽如此,手里有一份可能的策略清单常常还是有帮助的。所以,我把几个值得思考的话题汇集在本页,供你在面对时序问题时参考。如果你是因为有一个具体的时序问题要解决而读到这一页,那么这里面的某个想法也许能把你引向答案。但不要指望你的解决方案会直接写在这里。

还要记住,本系列并没有到此结束。出于激发兴趣的考虑,我选择在讨论许多其他主题之前先聊时序收敛。不过,后续页面里的信息也同样重要。

出于同样的原因,关于 I/O 时序约束的讨论被推迟到后面。目前,我专注于那些起点和终点都在 FPGA 内部的路径(path)。

下面就是几个与时序收敛有关的想法,供你参考。

想法 #1:修复逻辑设计

这永远是最不吸引人的解决方案。尤其当设计已经被证明能正常工作时更是如此:你不想去改一个能工作的东西。然而,问题的根本原因往往是,Verilog 代码没有按所要求的性能写好。修改逻辑设计可以一劳永逸地解决问题,而不是长期面对各种困难。

关于如何写出快速的逻辑,上一页给出了一些建议。这里值得再强调一次:在开发过程中要始终把时序放在心上。事后来修时序问题,远比从一开始就把 Verilog 代码写好要困难得多。

想法 #2:降低扇出

当一个信号网络具有很高的扇出(fan-out)时,传播延迟(propagation delay)会变大,原因主要有两个:

如果设计中使用了同步复位(synchronous reset),这个信号很可能具有很高的扇出。这个话题在单独的一页中有讨论。

不过,凡是需要到达大量逻辑元件的信号,都有可能因为高扇出而带来时序问题。有时这种高扇出是显而易见的(例如时钟使能(clock enable)信号),有时则不那么容易预见到。FPGA 工具通常可以帮助你列出扇出最高的那些网络。

有两种方法可以控制扇出:

显然,两种方法得到的结果相同:把高扇出寄存器复制成多个。那么,如果工具可以用第一种方法自动完成,为什么还要像第二种方法那样手动去做?

第二种方法需要多花些力气,但它有一个重要优势:可以以一种有意义的方式复制寄存器。要记住,目标不只是降低扇出。同样重要的是,每个寄存器的输出只分布到位于逻辑阵列上一个小区域内的逻辑元件;否则物理距离会带来很大的布线延迟。所以,如果在写 Verilog 代码时就把扇出考虑进去,就可以确保逻辑元件之间的连线比较短。另一页用同步复位信号演示了这一点。

相比之下,如果让 FPGA 工具负责复制寄存器,结果可能没那么高效。布线延迟能改善多少,取决于工具用什么算法来决定每个被复制出来的寄存器如何使用。结果质量也取决于具体使用哪款 FPGA 工具。

注意,默认情况下,当综合器发现两个寄存器行为完全相同时,会自动把它们合并成一个寄存器。所以,即使你在 Verilog 代码里复制了一个寄存器,综合器也常常会把所有等效的副本合并回一个寄存器。即使这些等效寄存器定义在不同的模块里,也常常如此。要避免这种合并,需要显式地禁用这个功能。常见办法是使用综合属性,例如 "dont_touch"、"dont_merge" 或 "keep"。

想法 #3:检查布局规划

默认情况下,逻辑元件在 FPGA 逻辑阵列上的位置由 FPGA 工具自动决定(更准确地说,由布局器(placer)决定)。但你可以要求某些逻辑元件放在 FPGA 上的特定区域,也可以要求某个逻辑元件放在某个具体的位置。这类要求统称为布局规划(floorplanning)。这些要求通过布局约束(placement constraints)来表达,其语法通常与 Tcl 命令中的时序约束相似。

在大多数情况下,布局约束会让满足时序约束变得更难。第一个原因很明显:布局器的选择空间受到限制后,结果只会比不加限制时更差。不过,还有一些更具体的原因:

在大多数设计中,最好避免布局规划,让布局器自由地优化逻辑元件的位置。不过也有一些情况经常使用布局约束,例如:

就时序收敛而言,要意识到布局约束可能是问题的一个潜在原因。特别是当布线延迟大于预期时,根源可能是布局器无法优化逻辑元件的位置。请记住,布局规划可能对与受约束逻辑元件完全无关的路径产生负面影响。

想法 #4:检查时序约束

时序约束(timing constraints)对 FPGA 的可靠运行至关重要,所以应在项目实现之前就进行验证。但即使如此,时序约束有时仍然会是错的。这些错误常常会在时序收敛过程中暴露出来。这种事本不该发生,但一旦发生了,当然最好还是把问题找出来改掉。

关于如何检查时序约束,有专门的一页。这里我只谈两个会导致时序收敛出问题的常见错误:

先谈无关时钟。时钟域交叉(clock domain crossing)这个话题前面已经讨论过。时序约束必须准确反映哪些时钟是相关时钟(related clocks)、哪些不是,这有几个重要原因。最重要的原因是保证逻辑正确工作,但时序收敛同样会受影响:如果一对时钟被工具不必要地当作相关时钟处理,就会在这两个时钟之间的所有路径上强制施加时序要求。结果,工具把力气花在这些路径上,真正需要优化的路径反而得不到足够的努力。

能够解决这个问题的时序约束会在本系列稍后解释。

至于异步复位:大多数情况下,需要对终点为异步复位的路径施加时序约束。但有时候并没有这个必要。例如,如果可以保证复位信号变为无效时时钟不会工作;又或者,接收异步复位的触发器带有防止时序违例的保护机制,就像时钟域交叉那样。在这些情况下,工具为满足时序要求而做的努力毫无意义。

这类问题会造成时序收敛困难,这一点有时很难意识到:有时关键路径与那对被不必要当作相关时钟的时钟完全无关。如果异步复位转移了工具的精力,那就更难察觉。这种情况下,试图聚焦关键路径来改善其时序,往往是徒劳的。

当然,时序约束还可能以其他各种方式出错。这里描述的只是一些可能。因此,遇到时序问题时,正好可以借此机会全面检查一下时序约束。

想法 #5:直接再试一次

回想一下,布局布线(place and route)过程的起点,是把逻辑元件相当随意地散布在 FPGA 的逻辑阵列上。随后,工具通过反复尝试来改善时序。因此,这个过程能否成功,确实带有一定的运气成分。即使完全找不出理由,布局布线算法的行为只要有一点点不同,也可能得到更好的结果。

所以,如果时序约束没有满足,而负时序余量(slack)又比较小(约占总延迟的 10% 到 20%),那么也许再试一次就够了。但仅仅重新运行一遍实现大概不会有帮助:大多数 FPGA 软件在输入相同的情况下,会精确重复之前的结果。因此,重新运行前必须改变一点什么。这个改动不一定要与关键路径有关,目的只是避免上一次的实现被一模一样地重复一遍。

例如,在 Vivado 中,每次运行都有一个名为 strategy 的属性。顾名思义,它控制工具在实现过程中采用的策略。修改这个属性可以保证下一次实现不会与上一次完全相同。当然,也有可能某种不同的策略对这个具体的逻辑设计更合适。

所有 FPGA 工具都提供类似的途径来修改实现过程的参数。你常常可以要求工具用更高的努力程度来实现设计目标。有时候确实需要提高努力程度,但也常常只是因为工具做了点不同的事,结果就好了。

另一种避免重复实现的方法是修改 Verilog 代码。同样,这个改动不需要与关键路径有关。有时只需给某个寄存器换个名字,就能让新的实现与上一次实现有足够的差别。

这个思路甚至可以用到极致:你可以让几台计算机并行地运行实现,每台使用略微不同的参数。当 FPGA 的价格比较重要时,这样做是合理的:让计算机多花些力气,以便能在更便宜的 FPGA 上满足时序约束。

总而言之,这个方法主要靠运气。期望也应相应调整:只有当工具只是偶尔无法满足时序约束时,再试一次才会有效。不过,如果可能,最好还是用其他方式改善时序。

想法 #6:FPGA 是不是已经满了?

非常常见的情况是,当 FPGA 的资源占用率达到约 70% 时,时序问题就开始出现。导致这种情况的原因有三个:

在上面三个原因中,只有第三个有某种解决办法:例如,可以手动决定哪些逻辑使用 FPGA 的块 RAM 以及其他类似资源。除此之外,FPGA 满了,唯一的解决办法就是换一个更大的 FPGA。但这也并不总是可行。因此,要预见到:随着设计中逻辑越来越多,时序收敛会越来越困难。

想法 #7:也许你需要的是另一款 FPGA?

有时候,你别无选择,只能承认当前这款 FPGA 胜任不了这项工作。如果同一款 FPGA 还有更高的速度等级可选,那么升级用更快的 FPGA 也许就是解决办法。这个决定当然会增加采购成本,但它还可能带来另一个不那么容易想到的后果:更高速度等级的 FPGA 可能会缺货。即使某段时间里高速型号货源充足,一旦需求超过供给,最先从市场上消失的通常也是这些高速型号。

这很自然:更快的 FPGA 是那些测试表现更好的芯片,而且它们总能用来替代较慢的 FPGA。有时是厂商造不出足够多的高速型号,有时则是有某个大客户会把它所有能用到的都买走。

所以,如果你的产品打算长期生产,务必优先选择你的设计能稳定工作的最低速度等级。即使不差钱,也应如此。

另一种完全不同的升级方式,是选一款更新系列的 FPGA,或者干脆换一家 FPGA 厂商。这种变化更彻底,但如果项目还在早期阶段,就值得考虑。我们总是容易喜欢上自己熟悉的工具和器件。但当一切都显得太舒适时,也许正是到外面看看替代方案的好时机。

话虽如此,有经验的 FPGA 工程师都知道,选择最新、最时髦的 FPGA 及其工具是一场有风险的赌博。不过,通常情况下,总存在一个相当成熟、而且比当前选择好得多的替代方案。这种情况下,最好跳出舒适区,尝试点新东西。

想法 #8:降低温度范围

我之所以把这条几乎放在最后,是因为它是最不好看的解决方案。但有时确实别无选择。

默认情况下,工具对时序约束的执行会保证 FPGA 在整个数据手册声明的温度范围内可靠工作。一些 FPGA 工具允许为某个项目选择另一个温度范围(例如 Quartus 为此提供了一个名为 MAX_CORE_JUNCTION_TEMP 的属性)。这可以用来告诉工具:不需要支持全部温度范围。

一般来说,FPGA 中逻辑元件的延迟会随着温度升高而增加。如果把最高温度降低,时序计算中的延迟值也会变小,因此更容易满足 tsetup 要求。有时候,这是让工具满足时序约束的唯一办法。

必须充分理解使用这种方法的风险。尤其是要注意,我们说的是结温(junction temperature),也就是 FPGA 硅片上的温度,而不是环境温度。

所以,当最高温度为 85°C 时,并不是说 FPGA 能在 85°C 的烘箱里工作。这个温度在室温(25°C)下也可能达到,尤其是当 FPGA 没有加散热器时。结温和环境温度之间总是有差异,差异的大小取决于 FPGA 的功耗和散热方案。

如果你做的是商业产品,要明白 FPGA 附近的环境温度可能远高于室温。尤其是当 FPGA 放在一个通风不好的机箱里时,箱内温度会比箱外高很多。更麻烦的是,大多数电子产品都预期能在约 0°C 到 40°C 之间工作(具体数值因产品而异)。因此,当最终产品在最高环境温度下测试、而 FPGA 位于产品外壳内部时,结温是多少?这才是该问的问题。时序计算必须基于这个温度(或更高温度)。

换句话说,如果你为了满足时序约束而降低最高温度,而且在实验室里一切工作正常,这不能说明任何问题。请记住,在时序约束这件事上,FPGA 设计在实验室里能工作从来都不能说明问题。但降低最高温度在这一方面更加危险。不假思索地缩小温度范围,无异于请麻烦上门:产品量产前的最终测试是在最高温度下进行的,到那时可能会失败;如果再把温度范围改回来,时序约束又无法满足。到那时唯一能做的就是从头改写 FPGA 设计。

所以,在为时序收敛而修改温度范围之前,先确认这样做是安全的:对结温在所有可能工作条件下的范围做一次严格的评估。

注意,通过修改实现参数,不可能把温度范围扩大到默认设置之外。工具的默认温度范围始终与数据手册一致。因此,无法保证 FPGA 在这个默认温度范围之外还能可靠工作。

想法 #9:相关时钟未对齐

这是一个比较神秘的情况,理解起来也有点难。所以我把它放在最后。

假设两个相关时钟之间存在一个时钟域交叉,而这两个时钟没有对齐。换句话说,两个时钟都由同一个参考时钟派生而来,但没有任何机制来控制这两个时钟之间的时钟偏斜(clock skew)。

这样一来,工具要满足这两个时钟之间路径的时序要求就会更困难。可能的困难有两种(回想一下,tsetup 和 thold 在前面已经解释过):

需要特别指出的是,当工具需要比平时更努力地克服这些困难时,代价可能是占用优化其他路径的资源。

但相关时钟未对齐并不是错误,有时也无法避免。这种情况只意味着工具需要更加努力。如果时序约束得到满足,设计没有问题。但当可以合理避免时,还是应该尽量避免。

注意,前面“想法 #4”中提到的错误与这里不同。虽然两种错误都会让工具不必要地辛苦,而且两者都与时钟域交叉有关。

解决相关时钟未对齐局面的最好办法是让这些时钟对齐。这通常通过增加一个 PLL,或者给现有 PLL 增加一个时钟输出来实现。目标是让所涉及的两个时钟成为同一个 PLL 的输出。

另一种可能的办法是把这些时钟当作无关时钟(unrelated clocks)。这既需要修改逻辑设计本身,也需要修改时序约束。如果做起来不太难,这样做也许是值得的。后面会有更多介绍

下面来看一个相关时钟未对齐时发生时钟域交叉的示例。

   wire       pll_clk;

   reg [24:0] result;
   reg [11:0] x, y, x1, y1;

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

   always @(posedge clk)
     begin
	x1 <= x;
	y1 <= y;
     end

   always @(posedge pll_clk)
     result <= x1 * y1;

注意,这里有一个 PLL(clk_wiz_0)。它以频率为 250 MHz 的 @clk 作为参考时钟。这与上一页顶部示例中的 PLL 相同。@pll_clk 的频率是 125 MHz。

这个示例的重点是 @clk 与 @pll_clk 这两个相关时钟之间的时钟域交叉。因为只有 @pll_clk 由 PLL 产生,这两个时钟并不对齐。因此,通向 @result(从 @x1 和 @y1 出发)的路径上存在时钟偏斜。虽然有时钟偏斜,但这两个时钟依然是相关时钟,工具会努力满足时序要求。

如果使用 @clk 的唯一原因是你需要一个 250 MHz 的时钟,那么正确的解决方案是让 PLL 再生成一个 250 MHz 的时钟。产生一个与参考时钟同频的时钟并不浪费资源。恰恰相反,这样做能让工具省下很多力气。只有一种情况下上面的示例中直接使用 @clk 才是合理的:使用 @clk 的逻辑必须能在 PLL 产生出可用时钟之前就开始工作。

Vivado 生成的时序报告如下。在这个具体例子中,只有 tsetup 要求出了问题。

Slack (VIOLATED) :        -1.456ns  (required time - arrival time)
  Source:                 x1_reg[7]/C
                            (rising edge-triggered cell FDRE clocked by clk  {rise@0.000ns fall@2.000ns period=4.000ns})
  Destination:            result_reg/DSP_OUTPUT_INST/ALU_OUT[10]
                            (rising edge-triggered cell DSP_OUTPUT 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:            4.000ns  (clk_out1_clk_wiz_0 rise@8.000ns - clk rise@4.000ns)
  Data Path Delay:        3.012ns  (logic 2.677ns (88.878%)  route 0.335ns (11.122%))
  Logic Levels:           5  (DSP_A_B_DATA=1 DSP_ALU=1 DSP_M_DATA=1 DSP_MULTIPLIER=1 DSP_PREADD_DATA=1)
  Clock Path Skew:        -2.192ns (DCD - SCD + CPR)
    Destination Clock Delay (DCD):    0.998ns = ( 8.998 - 8.000 )
    Source Clock Delay      (SCD):    3.202ns = ( 7.202 - 4.000 )
    Clock Pessimism Removal (CPR):    0.012ns
  Clock Uncertainty:      0.148ns  ((TSJ^2 + DJ^2)^1/2) / 2 + PE
    Total System Jitter     (TSJ):    0.071ns
    Discrete Jitter          (DJ):    0.103ns
    Phase Error              (PE):    0.086ns
  Clock Net Delay (Source):      1.414ns (routing 0.002ns, distribution 1.412ns)
  Clock Net Delay (Destination): 1.184ns (routing 0.002ns, distribution 1.182ns)
  Clock Domain Crossing:  Inter clock paths are considered valid unless explicitly excluded by timing constraints such as set_clock_groups or set_false_path.

    Location             Delay type                Incr(ns)  Path(ns)    Netlist Resource(s)
  -------------------------------------------------------------------    -------------------
                         (clock clk rise edge)        4.000     4.000 r
    AG12                                              0.000     4.000 r  clk (IN)
                         net (fo=0)                   0.000     4.000    clk_IBUF_inst/I
    AG12                 INBUF (Prop_INBUF_HRIO_PAD_O)
                                                      0.738     4.738 r  clk_IBUF_inst/INBUF_INST/O
                         net (fo=1, routed)           0.105     4.843    clk_IBUF_inst/OUT
    AG12                 IBUFCTRL (Prop_IBUFCTRL_HRIO_I_O)
                                                      0.049     4.892 r  clk_IBUF_inst/IBUFCTRL_INST/O
                         net (fo=1, routed)           0.795     5.687    clk_IBUF
    BUFGCE_X1Y2          BUFGCE (Prop_BUFCE_BUFGCE_I_O)
                                                      0.101     5.788 r  clk_IBUF_BUFG_inst/O
    X2Y0 (CLOCK_ROOT)    net (fo=62, routed)          1.414     7.202    clk_IBUF_BUFGCE
    SLICE_X52Y45         FDRE                                         r  x1_reg[7]/C
  -------------------------------------------------------------------    -------------------
    SLICE_X52Y45         FDRE (Prop_HFF_SLICEM_C_Q)
                                                      0.138     7.340 f  x1_reg[7]/Q
                         net (fo=1, routed)           0.335     7.675    result_reg/A[7]
    DSP48E2_X8Y18        DSP_A_B_DATA (Prop_DSP_A_B_DATA_DSP48E2_A[7]_A2_DATA[7])
                                                      0.396     8.071 r  result_reg/DSP_A_B_DATA_INST/A2_DATA[7]
                         net (fo=1, routed)           0.000     8.071    result_reg/DSP_A_B_DATA.A2_DATA<7>
    DSP48E2_X8Y18        DSP_PREADD_DATA (Prop_DSP_PREADD_DATA_DSP48E2_A2_DATA[7]_A2A1[7])
                                                      0.182     8.253 r  result_reg/DSP_PREADD_DATA_INST/A2A1[7]
                         net (fo=1, routed)           0.000     8.253    result_reg/DSP_PREADD_DATA.A2A1<7>
    DSP48E2_X8Y18        DSP_MULTIPLIER (Prop_DSP_MULTIPLIER_DSP48E2_A2A1[7]_U[10])
                                                      0.994     9.247 f  result_reg/DSP_MULTIPLIER_INST/U[10]
                         net (fo=1, routed)           0.000     9.247    result_reg/DSP_MULTIPLIER.U<10>
    DSP48E2_X8Y18        DSP_M_DATA (Prop_DSP_M_DATA_DSP48E2_U[10]_U_DATA[10])
                                                      0.164     9.411 r  result_reg/DSP_M_DATA_INST/U_DATA[10]
                         net (fo=1, routed)           0.000     9.411    result_reg/DSP_M_DATA.U_DATA<10>
    DSP48E2_X8Y18        DSP_ALU (Prop_DSP_ALU_DSP48E2_U_DATA[10]_ALU_OUT[10])
                                                      0.803    10.214 r  result_reg/DSP_ALU_INST/ALU_OUT[10]
                         net (fo=1, routed)           0.000    10.214    result_reg/DSP_ALU.ALU_OUT<10>
    DSP48E2_X8Y18        DSP_OUTPUT                                   r  result_reg/DSP_OUTPUT_INST/ALU_OUT[10]
  -------------------------------------------------------------------    -------------------

                         (clock clk_out1_clk_wiz_0 rise edge)
                                                      8.000     8.000 r
    BUFGCE_X1Y2          BUFGCE                       0.000     8.000 r  clk_IBUF_BUFG_inst/O
                         net (fo=62, routed)          1.078     9.078    pll_i/inst/clk_in1
    MMCME3_ADV_X1Y0      MMCME3_ADV (Prop_MMCME3_ADV_CLKIN1_CLKOUT0)
                                                     -1.777     7.301 r  pll_i/inst/mmcme3_adv_inst/CLKOUT0
                         net (fo=1, routed)           0.422     7.723    pll_i/inst/clk_out1_clk_wiz_0
    BUFGCE_X1Y0          BUFGCE (Prop_BUFCE_BUFGCE_I_O)
                                                      0.091     7.814 r  pll_i/inst/clkout1_buf/O
    X2Y0 (CLOCK_ROOT)    net (fo=6, routed)           1.184     8.998    result_reg/CLK
    DSP48E2_X8Y18        DSP_OUTPUT                                   r  result_reg/DSP_OUTPUT_INST/CLK
                         clock pessimism              0.012     9.010
                         clock uncertainty           -0.148     8.862
    DSP48E2_X8Y18        DSP_OUTPUT (Setup_DSP_OUTPUT_DSP48E2_CLK_ALU_OUT[10])
                                                     -0.104     8.758    result_reg/DSP_OUTPUT_INST
  -------------------------------------------------------------------
                         required time                          8.758
                         arrival time                         -10.214
  -------------------------------------------------------------------
                         slack                                 -1.456

这份报告显示,工具未能满足时序约束。所显示的路径从 @clk 在 4 ns 处的上升沿开始,到 @pll_clk 在 8 ns 处的上升沿结束。问题出在时钟从输入引脚到达第一个触发器时钟引脚所需的时间上:3.2 ns。因此这个时钟边沿到达的时间是 7.2 ns。

但 @pll_clk 是由 PLL 产生的,所以这个时钟与 @clk 的输入引脚对齐,其延迟只有 1.0 ns。因此 @pll_clk 到达第二个触发器的时间是 9.0 ns。于是留给数据路径的时间只有 9.0 – 7.2 = 1.8 ns(这还只是一个约数,还要考虑时钟不确定性等)。这个时间不足以完成一次乘法运算,即使使用专用算术单元也不够。因此,时序要求无法得到满足。

所以在这个示例中,时钟因为偏斜而较晚到达第一个触发器,结果导致 tsetup 要求未能满足。

注意,这个问题可以通过调整 @pll_clk 的对齐来解决。例如,可以把分配 @clk 的全局时钟缓冲器输出作为 PLL 的参考时钟;也可以给 PLL 设置相移(phase shift),以获得更好的对齐。不过,这些都是万不得已才使用的办法。

小结

再说一次,上面只是一些可能帮助你解决时序收敛问题的思路。遗憾的是,这类问题往往需要比这些多得多。事实上,FPGA 领域里没有一个话题是时序收敛完全无关的。

如前所述,最好的策略是从一开始就仔细地编写逻辑设计。应对时序收敛最好的办法,就是避免出现时序收敛问题。


到此为止,本系列页面一直在讨论时序,但对时序约束本身谈得不多。这种情况即将改变:下一页开始,讨论将变得更加技术化。

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