引言
在逻辑设计中,只要用 PLL 从一个时钟生成另一个时钟,通常都会指望时序工具自己算出频率,并弄清楚不同时钟之间的时序关系。
然而在 Intel 的 Quartus 中,默认并不是这样。要让工具做这件事,必须在 SDC 约束文件中加入 derive_pll_clocks 命令。事实上,任何由工具自动生成的 SDC 文件里,几乎都会有这条命令。
但可怕的地方在这里:如果漏掉了 derive_pll_clocks,你可能会期望工具为 PLL 生成的时钟正确地施加时序约束(timing constraints);另一个可能的期望,是根本不给这些时钟施加任何约束。不幸的是,实际情况完全不同:正如下面所见,工具确实会对这些时钟做时序计算,但使用的约束是错误的。这样一来,你可能会以为时序约束已经满足、一切正常,但实际上没有一件事做对。
一个例子
假设 FPGA 的输入接的是一个 48 MHz 的振荡器(因此周期是 20.833 ns),PLL 会生成一个 240 MHz 的时钟(即周期 4.166 ns)。
先看一个简单、写得很规矩的 SDC 文件,内容如下:
create_clock -name root_clk -period 20.833 [get_ports {osc_clock}]
derive_pll_clocks
derive_clock_uncertainty
注意,这个文件里有 derive_pll_clocks 命令。
现在来看一条路径(path)上的时序报告,这条路径位于两个寄存器之间,并由 PLL 生成的时钟驱动。报告中最值得关注的部分用红色标出:
+-------------------------------------------------------------------------------------------------------------------------------------------------+
; Data Arrival Path ;
+---------+----------+----+------+--------+------------------------+------------------------------------------------------------------------------+
; Total ; Incr ; RF ; Type ; Fanout ; Location ; Element ;
+---------+----------+----+------+--------+------------------------+------------------------------------------------------------------------------+
; 0.000 ; 0.000 ; ; ; ; ; launch edge time ;
; 4.937 ; 4.937 ; ; ; ; ; clock path ;
; 0.000 ; 0.000 ; ; ; ; ; source latency ;
; 0.000 ; 0.000 ; ; ; 1 ; PIN_B12 ; osc_clock ;
; 0.000 ; 0.000 ; RR ; IC ; 1 ; IOIBUF_X19_Y29_N8 ; osc_clock~input|i ;
; 0.667 ; 0.667 ; RR ; CELL ; 2 ; IOIBUF_X19_Y29_N8 ; osc_clock~input|o ;
; 2.833 ; 2.166 ; RR ; IC ; 1 ; PLL_3 ; clkrst_ins|altpll_component|auto_generated|pll1|inclk[0] ;
; 1.119 ; -1.714 ; RR ; COMP ; 1 ; PLL_3 ; clkrst_ins|altpll_component|auto_generated|pll1|observablevcoout ;
; 1.119 ; 0.000 ; RR ; CELL ; 1 ; PLL_3 ; clkrst_ins|altpll_component|auto_generated|pll1|clk[0] ;
; 3.274 ; 2.155 ; RR ; IC ; 1 ; CLKCTRL_G13 ; clkrst_ins|altpll_component|auto_generated|wire_pll1_clk[0]~clkctrl|inclk[0] ;
; 3.274 ; 0.000 ; RR ; CELL ; 8 ; CLKCTRL_G13 ; clkrst_ins|altpll_component|auto_generated|wire_pll1_clk[0]~clkctrl|outclk ;
; 4.336 ; 1.062 ; RR ; IC ; 1 ; FF_X40_Y24_N27 ; clkrst_ins|main_state[0]|clk ;
; 4.937 ; 0.601 ; RR ; CELL ; 1 ; FF_X40_Y24_N27 ; clkrst:clkrst_ins|main_state[0] ;
; 6.774 ; 1.837 ; ; ; ; ; data path ;
; 5.169 ; 0.232 ; ; uTco ; 1 ; FF_X40_Y24_N27 ; clkrst:clkrst_ins|main_state[0] ;
; 5.169 ; 0.000 ; FF ; CELL ; 5 ; FF_X40_Y24_N27 ; clkrst_ins|main_state[0]|q ;
; 5.591 ; 0.422 ; FF ; IC ; 1 ; LCCOMB_X40_Y24_N24 ; clkrst_ins|Equal1~0|dataa ;
; 6.002 ; 0.411 ; FR ; CELL ; 1 ; LCCOMB_X40_Y24_N24 ; clkrst_ins|Equal1~0|combout ;
; 6.370 ; 0.368 ; RR ; IC ; 1 ; DDIOOUTCELL_X41_Y24_N4 ; clkrst_ins|the_register|d ;
; 6.774 ; 0.404 ; RR ; CELL ; 1 ; DDIOOUTCELL_X41_Y24_N4 ; clkrst:clkrst_ins|the_register ;
+---------+----------+----+------+--------+------------------------+------------------------------------------------------------------------------+
+-------------------------------------------------------------------------------------------------------------------------------------------------+
; Data Required Path ;
+---------+----------+----+------+--------+------------------------+------------------------------------------------------------------------------+
; Total ; Incr ; RF ; Type ; Fanout ; Location ; Element ;
+---------+----------+----+------+--------+------------------------+------------------------------------------------------------------------------+
; 4.166 ; 4.166 ; ; ; ; ; latch edge time ;
; 9.005 ; 4.839 ; ; ; ; ; clock path ;
; 4.166 ; 0.000 ; ; ; ; ; source latency ;
; 4.166 ; 0.000 ; ; ; 1 ; PIN_B12 ; osc_clock ;
; 4.166 ; 0.000 ; RR ; IC ; 1 ; IOIBUF_X19_Y29_N8 ; osc_clock~input|i ;
; 4.833 ; 0.667 ; RR ; CELL ; 2 ; IOIBUF_X19_Y29_N8 ; osc_clock~input|o ;
; 6.912 ; 2.079 ; RR ; IC ; 1 ; PLL_3 ; clkrst_ins|altpll_component|auto_generated|pll1|inclk[0] ;
; 5.119 ; -1.793 ; RR ; COMP ; 1 ; PLL_3 ; clkrst_ins|altpll_component|auto_generated|pll1|observablevcoout ;
; 5.119 ; 0.000 ; RR ; CELL ; 1 ; PLL_3 ; clkrst_ins|altpll_component|auto_generated|pll1|clk[0] ;
; 7.187 ; 2.068 ; RR ; IC ; 1 ; CLKCTRL_G13 ; clkrst_ins|altpll_component|auto_generated|wire_pll1_clk[0]~clkctrl|inclk[0] ;
; 7.187 ; 0.000 ; RR ; CELL ; 8 ; CLKCTRL_G13 ; clkrst_ins|altpll_component|auto_generated|wire_pll1_clk[0]~clkctrl|outclk ;
; 8.199 ; 1.012 ; RR ; IC ; 1 ; DDIOOUTCELL_X41_Y24_N4 ; clkrst_ins|the_register|clk ;
; 8.736 ; 0.537 ; RR ; CELL ; 1 ; DDIOOUTCELL_X41_Y24_N4 ; clkrst:clkrst_ins|the_register ;
; 9.005 ; 0.269 ; ; ; ; ; clock pessimism removed ;
; 8.985 ; -0.020 ; ; ; ; ; clock uncertainty ;
; 8.890 ; -0.095 ; ; uTsu ; 1 ; DDIOOUTCELL_X41_Y24_N4 ; clkrst:clkrst_ins|the_register ;
+---------+----------+----+------+--------+------------------------+------------------------------------------------------------------------------+
注意报告中带引号的 latch edge time(锁存沿时间)那一行。这个数值表示的是某个时钟沿在穿过时钟网络(并累加时钟缓冲器和布线的延时)之后,成为驱动接收寄存器的那个时钟的时刻。由于这条路径的两个寄存器使用的是同一个时钟的同一个上升沿,所以 latch edge time 就该等于时钟周期。报告里也的确写着 4.166 ns。到目前为止,一切都很正常。
然而,灾难来了
现在再看看,如果漏掉 derive_pll_clocks 命令会发生什么。也就是说,SDC 文件变成这样:
create_clock -name root_clk -period 20.833 [get_ports {osc_clock}]
derive_clock_uncertainty
对同一条路径(path),时序报告却变成:
+-------------------------------------------------------------------------------------------------------------------------------------------------+
; Data Arrival Path ;
+---------+----------+----+------+--------+------------------------+------------------------------------------------------------------------------+
; Total ; Incr ; RF ; Type ; Fanout ; Location ; Element ;
+---------+----------+----+------+--------+------------------------+------------------------------------------------------------------------------+
; 0.000 ; 0.000 ; ; ; ; ; launch edge time ;
; 4.937 ; 4.937 ; ; ; ; ; clock path ;
; 0.000 ; 0.000 ; ; ; ; ; source latency ;
; 0.000 ; 0.000 ; ; ; 1 ; PIN_B12 ; osc_clock ;
; 0.000 ; 0.000 ; RR ; IC ; 1 ; IOIBUF_X19_Y29_N8 ; osc_clock~input|i ;
; 0.667 ; 0.667 ; RR ; CELL ; 2 ; IOIBUF_X19_Y29_N8 ; osc_clock~input|o ;
; 2.833 ; 2.166 ; RR ; IC ; 1 ; PLL_3 ; clkrst_ins|altpll_component|auto_generated|pll1|inclk[0] ;
; 1.119 ; -1.714 ; RR ; COMP ; 1 ; PLL_3 ; clkrst_ins|altpll_component|auto_generated|pll1|observablevcoout ;
; 1.119 ; 0.000 ; RR ; CELL ; 1 ; PLL_3 ; clkrst_ins|altpll_component|auto_generated|pll1|clk[0] ;
; 3.274 ; 2.155 ; RR ; IC ; 1 ; CLKCTRL_G13 ; clkrst_ins|altpll_component|auto_generated|wire_pll1_clk[0]~clkctrl|inclk[0] ;
; 3.274 ; 0.000 ; RR ; CELL ; 8 ; CLKCTRL_G13 ; clkrst_ins|altpll_component|auto_generated|wire_pll1_clk[0]~clkctrl|outclk ;
; 4.336 ; 1.062 ; RR ; IC ; 1 ; FF_X40_Y24_N19 ; clkrst_ins|main_state[0]|clk ;
; 4.937 ; 0.601 ; RR ; CELL ; 1 ; FF_X40_Y24_N19 ; clkrst:clkrst_ins|main_state[0] ;
; 6.765 ; 1.828 ; ; ; ; ; data path ;
; 5.169 ; 0.232 ; ; uTco ; 1 ; FF_X40_Y24_N19 ; clkrst:clkrst_ins|main_state[0] ;
; 5.169 ; 0.000 ; FF ; CELL ; 5 ; FF_X40_Y24_N19 ; clkrst_ins|main_state[0]|q ;
; 5.583 ; 0.414 ; FF ; IC ; 1 ; LCCOMB_X40_Y24_N24 ; clkrst_ins|Equal1~0|datab ;
; 5.994 ; 0.411 ; FR ; CELL ; 1 ; LCCOMB_X40_Y24_N24 ; clkrst_ins|Equal1~0|combout ;
; 6.361 ; 0.367 ; RR ; IC ; 1 ; DDIOOUTCELL_X41_Y24_N4 ; clkrst_ins|the_register|d ;
; 6.765 ; 0.404 ; RR ; CELL ; 1 ; DDIOOUTCELL_X41_Y24_N4 ; clkrst:clkrst_ins|the_register ;
+---------+----------+----+------+--------+------------------------+------------------------------------------------------------------------------+
+--------------------------------------------------------------------------------------------------------------------------------------------------+
; Data Required Path ;
+----------+----------+----+------+--------+------------------------+------------------------------------------------------------------------------+
; Total ; Incr ; RF ; Type ; Fanout ; Location ; Element ;
+----------+----------+----+------+--------+------------------------+------------------------------------------------------------------------------+
; 20.833 ; 20.833 ; ; ; ; ; latch edge time ;
; 25.672 ; 4.839 ; ; ; ; ; clock path ;
; 20.833 ; 0.000 ; ; ; ; ; source latency ;
; 20.833 ; 0.000 ; ; ; 1 ; PIN_B12 ; osc_clock ;
; 20.833 ; 0.000 ; RR ; IC ; 1 ; IOIBUF_X19_Y29_N8 ; osc_clock~input|i ;
; 21.500 ; 0.667 ; RR ; CELL ; 2 ; IOIBUF_X19_Y29_N8 ; osc_clock~input|o ;
; 23.579 ; 2.079 ; RR ; IC ; 1 ; PLL_3 ; clkrst_ins|altpll_component|auto_generated|pll1|inclk[0] ;
; 21.786 ; -1.793 ; RR ; COMP ; 1 ; PLL_3 ; clkrst_ins|altpll_component|auto_generated|pll1|observablevcoout ;
; 21.786 ; 0.000 ; RR ; CELL ; 1 ; PLL_3 ; clkrst_ins|altpll_component|auto_generated|pll1|clk[0] ;
; 23.855 ; 2.069 ; RR ; IC ; 1 ; CLKCTRL_G13 ; clkrst_ins|altpll_component|auto_generated|wire_pll1_clk[0]~clkctrl|inclk[0] ;
; 23.855 ; 0.000 ; RR ; CELL ; 8 ; CLKCTRL_G13 ; clkrst_ins|altpll_component|auto_generated|wire_pll1_clk[0]~clkctrl|outclk ;
; 24.867 ; 1.012 ; RR ; IC ; 1 ; DDIOOUTCELL_X41_Y24_N4 ; clkrst_ins|the_register|clk ;
; 25.404 ; 0.537 ; RR ; CELL ; 1 ; DDIOOUTCELL_X41_Y24_N4 ; clkrst:clkrst_ins|the_register ;
; 25.672 ; 0.268 ; ; ; ; ; clock pessimism removed ;
; 25.572 ; -0.100 ; ; ; ; ; clock uncertainty ;
; 25.477 ; -0.095 ; ; uTsu ; 1 ; DDIOOUTCELL_X41_Y24_N4 ; clkrst:clkrst_ins|the_register ;
+----------+----------+----+------+--------+------------------------+------------------------------------------------------------------------------+
整个分析过程仍然一模一样,只是它假定时钟周期是 20.833 ns(请再次注意报告中的 latch edge time)。可以发现,时序分析确实走过了 PLL,但完全忽略了 PLL 的输出是另一个频率这一事实。它就好像在说:你忘了 derive_pll_clocks 命令?没关系。我们就假装 PLL 的输入时钟直接穿过去了。
坦白说,我想不出任何场景下这种行为是合理的。要么干脆不要用 PLL 的时钟来计算这些路径的时序,要么就正确地算。但只是把原始时钟的周期硬套上去,算是怎么回事?而且,这些被算错的路径既不会出现在未约束路径汇总(unconstrained path summary)里,也没有任何其他迹象会提醒你,时序已经错得离谱。
为工具说句公道话:时序分析其实会就这个问题产生警告,但其中没有一条达到严重警告(critical warning)级别,所以很容易在 FPGA 工具总是产生的那一大堆警告中漏掉它们。
结论
- 如果设计中用到了 PLL,请务必让 SDC 文件包含 derive_pll_clocks 命令(除非你已经为 PLL 的时钟写好了显式约束)。
- 务必生成时序报告,并且务必阅读、看懂它。
- 务必设置一些不可能达到的数值来考验你的约束,然后验证那些失败的路径确实被计算正确。