TimeQuest之multicycle paths
王敏志
概述
Multicycle paths即多周期路径,指的是两个寄存器之间数据要经过多个时钟才能稳定的路径,一般出现于组合逻辑较大的那些路径。在实际工程中,除了乘除法器等少数比较特殊的电路,一般应该尽量避免采用多周期路径电路。即使有所使用,也应该通过约束在综合工具中指出该路径,使得综合工具在计算Fmax的时候忽略这条路径,避免用大量的时间对该路径进行优化。 对多周期路径可加一下约束:set_multicycle_path -from D_reg -to S_reg。
本文成文之前参考了网上《DC概论之多周期路径》,并使用其中一些例子,这里对作者的辛勤劳作表示感谢。《DC概论之多周期路径》网上链接众多,本人不负责辨认何处为原创,这里只是给出其中一个链接:
http://bbs.ednchina.com/BLOG_ARTICLE_214699.HTM
另外,这里有一个疑似原创出处:http://www.cnblogs.com/liuokay/category/291986.html
关于何时何处使用Multicycle Path
上述《概论》论述的是IC设计的时候的多周期路径,那么对于FPGA设计特别是ALTERA的FPGA设计的时候何时何处会用到多周期约束呢?其实简单点说就是设计者如何知道自己的设计中哪里需要进行多周期设计的问题。
在设计中很多地方都有涉及多周期路径,当个两个触发器之间的逻辑如果一个周期执行不完的话一般有两个解决方案:
1、插入流水线使得组合逻辑打散 ;
2、使用使能信号控制,几个周期读取一次数据。
这里面2所使用的方法就需要设定multi-cycle path,方法2和1之间是有区别的。方法1数据的吞吐量更大,方法2牺牲了数据吞吐量,但是设计中有的地方对吞吐量没有要求是可以使用这种方法的。
举个简单的例子,某个设计大部分逻辑可以跑1G,但有个乘法器只能跑100M, 所以你就把multi-cycle设成10T, 但是design也要修改, 就是每10T才采集一次乘法器的结果。
在《概论》里详细介绍了所谓的“快采慢”和“慢采快”的两种情况,可以很好理解两个不同寄存器的采样时钟频率不一样时的情形。
多周期的分类
这里有必要交待一下多周期的分类,说到这个分类,有两种。一种就是上述《概论》里提到的快采慢和慢采快,另外一种是Setup多周期和Hold多周期。
ALTERA其实对于第一种分类是这样描述的,即分为Destination和Source。Destination是基于目标时钟沿,向后移动Latch时钟沿来放松建立保持时间。而Source约束是基于源时钟沿,通过向前移动Launch时钟沿来放松建立保持时间。Start和end说明多周期路径依赖于start clock还是依赖于end clock。
ALTERA关于第二种分法的描述是这样的,即Setup多周期是增加建立时间分析的周期数,默认情况下是1个周期。Hold多周期是增加保持时间分析周期数,默认情况下是0。Setup和Hold说明多周期路径是用在建立时间检查还是用在保持时间检查。
具体什么是多周期路径约束呢?
时序分析器会分析除了定义为false path的所有路径。所有的路径都是在最近的沿分析setup,在最近的沿的前一个沿分析hold。 设置为multicycle 的path会改变分析方法, 比如muticycle 2就是说会在最近的沿的下一个沿分析setup,分析setup的沿的前一个沿分析hold。
具体的多周期路径约束语法如下所示:
set_multicycle_path [-h | -help] [-long_help] [-end] [-fall_from <names>] [-fall_to <names>] [-from <names>] [-hold] [-rise_from <names>] [-rise_to <names>] [-setup] [-start] [-through <names>] [-to <names>] <value>
-start:选择源(source)多周期
-end:选择目标(destination)多周期(默认)
-setup|hold:指定建立或者保持多周期
<value>:扩展分析的周期数目
其他基本和false path命令基本相同。下面通过几个例子来具体说明如何理解多周期。
1、 标准单周期寄存器传输
图1
2、 双周期setup,单周期hold传输
图2
那么图2所示的多周期路径约束如下所示:
Set_multicycle_path –from [get_pins reg1|clk] –to [get_pins reg2|datain] –end –setup 2
3、 实际应用中经常遇到的乘法器多周期例子
图3
前面有介绍,在多周期设置中,Setup的周期是X,那么设置的Hold周期为X-1。所谓的快采慢如图4所示,请注意《概论》中的默认情况和ALTERA的TQ工具里默认情况的异同。概论里提到的工具如果命令没有指明Start和end,那么默认情况下Setup是参照end,而Hold参照start。而ALTERA似乎均默认为end。
图4
假设已经知道3*clk1>delay>2*clk1,这时候多周期约束为
set_multicycle_path -setup 3 -end -from clk1 -to clk2
set_multicycle_path -hold 2 -end -from clk1 -to clk2
再来看看“慢采快”,如图5所示
图5
假设已经知道3*clk1>delay>2*clk1,这时候多周期约束为
set_multicycle_path -setup 3 -start -from clk1 -to clk2
set_multicycle_path -hold 2 -start -from clk1 -to clk2
最后再来一个“慢采快”的例子,100MHz时钟分频得到一个50MHz的时钟,那么为从100MHz到50MHz时钟域所有路径加一个源多周期为2,保持多周期为1的约束如下所示:
create_clock -period 10.000 -name CLK [get_ports clk]
create_generated_clock -divide_by 2 -source [get_ports clk] -name CLKDIV2 [get_registers clkdiv]
set_multicycle_path -start -setup -from [get_clocks CLK] -to [get_clocks CLKDIV2] 2
set_multicycle_path -start -hold -from [get_clocks CLK] -to [get_clocks CLKDIV2] 1
实际应用实例
图6是本人实际工作中设计的一个工程部分示意框图,在全编译之后主要是图中的三个PLL的输出时钟有报告时序问题,在不进行时序约束的时候上板调试是没有遇到什么问题的,只是这么多时序违约报告不知道长时间运行是否稳定,所以有必要仔细分析每一个违约时序报告。
图6
这个工程只是对于CLKIN(外部100MHz时钟)和PLL的输出进行约束,如下所示
create_clock -period 10.000 -name refclk -waveform {0 5} [get_ports {refclk}]
create_clock -period 10.000 -name clkin -waveform {0 5} [get_ports {clkin}]
derive_pll_clocks
全编译之后查看TimeQuest的报告图7所示,*pll*clk[0]、*pll*clk[2]和*pll*clk[3]分
图7
别是图6中的100MHz、250MHz和62.5MHz三个时钟我们发现这三个时钟都有报告时序违约,而且250MHz的Fmax报告才不到94MHz。打开TimeQuest分别对这三个时钟的红色报告执行report timing命令并进行分析,图8显示了对于100MHz时钟执行Report Timing后的结果,产生违约的路径的Launch clock是62.5MHz,Latch clock是100MHz,也就是说这些违约路径都是跨时钟域的路径,再分析源代码发现这些路径都是跨时钟传递数据,逻辑设计保证在传递数据的时候能安全传递,即通过握手控制信号保证数据稳定传输,所以这些路径可以认为是false paths。针对这些路径加入false paths约束如下
set_false_path -from [get_clocks {inst|altpll_component|pll|clk[3]}]\ -to [get_clocks {inst|altpll_component|pll|clk[0]}]
再重新编译工程,发现100MHz的时序违约没有了,如图9所示。
图8、对100MHz时序违约路径执行Report Timing命令结果
图9、解决*pll|clk[0]即100MHz时序违约
重新打开TimeQuest,依照从易到难原则,这次解决*pll|clk[3]即62.5MHz时钟时序违约问题。同样执行Report Timing命令我们得到类似图8的报告界面,如图10所示。
图10、对62.5MHz时序违约路径执行Report Timing命令结果
产生违约的路径的Launch clock是100MHz,Latch clock是62.5MHz,也就是说这些违约路径同样都是跨时钟域的路径,再分析源代码发现这些路径都是跨时钟传递的控制信号(图6框图所示),这些100MHz时钟域的控制信号和62.5MHz时钟域被控制的模块是完全异步的关系,所以这些路径可以认为是false paths。针对这些路径加入false paths约束如下
set_false_path -from [get_clocks {inst|altpll_component|pll|clk[0]}]\ -to [get_clocks {inst|altpll_component|pll|clk[3]}]
上述false paths命令其实就是前面false paths命令的反向操作,即这两条false paths命令分别“cut”了100MHz和62.5MHz这两个时钟域之间路径的时序分析。再重新编译工程,发现62.5MHz的时序违约没有了,如图11所示。
图11、解决*pll|clk[3]即62.5MHz时序违约
还剩最后一个,总的slack到-10.586ns,打开TimeQuest并对此违约路径执行Report Timing命令,报告结果如图12所示。
图12、对250MHz时序违约路径执行Report Timing命令结果
由于负slack量很大,所以时序违约也许不止一种情况(实际情况也确实如此),图12所示的报告默认只报告最差的10条。仔细分析图12已经报告出来的这些违约路径,发现跟图8和图10报告的违约路径类似,也都是一些跨时钟域的路径,这里的Launch clock是100MHz,Latch clock是250MHz。也就是说可以把这些路径当作false paths处理,由于本文是论述多周期,所以我想是不是可以通过加入多周期约束来解决这个问题呢?尽管100MHz与250MHz不是整数倍的关系,但是图12时序图看出可以加入一个4周期的end建立多周期约束,同时加入一个3周期的保持多周期来解决这个问题。命令如下所示:
set_multicycle_path -from [get_clocks {inst|altpll_component|pll|clk[0]}] -to [get_clocks {inst|altpll_component|pll|clk[2]}] -end -setup 4
set_multicycle_path -from [get_clocks {inst|altpll_component|pll|clk[0]}] -to [get_clocks {inst|altpll_component|pll|clk[2]}] -end -hold 3
(注:本人不确定多周期是否必须两个时钟必须要是整数倍关系,但根据这个例子似乎不是也可行啊!)
重新编译工程,发现250MHz还是有时序违约,但是负的slack减少了,如图13所示
图13
打开TimeQuest针对违约路径执行Report Timing命令,得到的报告结果如图14所示
图14
仔细分析图14报告的违约路径,以及时序图,这些违约路径的Launch clock和Latch clock为同一时钟,即250MHz时钟。研究这些违约路径的原始代码发现from node其实是一个计数器的输出,而此计数器的计数频率虽然是250MHz,但是有条件的计数,所以这些路径更适合加多周期约束,这里先使用false paths约束,最后再把图12和图14所示的违约路径分别改成false paths和多周期路径约束。这里先加入的false paths如下所示
set_false_path -from [get_registers {SEP_TDC_7ch:inst13|tdc1ch_won:inst?|TDC1ch2a:TCH0|lpm_counter30:RGZ1|lpm_counter:lpm_counter_component|cntr_rsh:auto_generated|safe_q[*]}] -to [get_clocks {inst|altpll_component|pll|clk[2]}]
重新编译工程,发现250MHz的时序违约没有了,而它的Fmax也提高到250MHz以上了,如图15所示(注意和图7比较)。
图15
附1
前面有提到图12所示的违约路径应该false paths约束,而图14所示的违约路径应该加多周期路径约束。那么首先将图12所示的违约路径修改成false paths约束,约束命令如下,也即Launch clock是100MHz,而Latch clock是250MHz时钟。
set_false_path -from [get_clocks {inst|altpll_component|pll|clk[0]}] -to [get_clocks {inst|altpll_component|pll|clk[2]}]
重新编译后的结果如图16所示。
图16
上图显示修改成false paths约束后似乎也能解决时序违约问题,对比图15,Fmax似乎跑的更高一些。而图14所示的时序违约路径的约束修改为多周期路径约束并没有成功,不管我如何加多周期约束在FIT的时候都是通不过,提示“Critical Warning (332008): Read_sdc failed due to errors in the SDC file”,也许是多周期语法有错?下面列出我尝试过的命令格式:
第一条:
set_multicycle_path -end -setup –to [get_pins {SEP_TDC_7ch:inst*|tdc1ch_won:inst1|TDC1ch2a:TCH0|lpm_add_sub21:RGT1|lpm_add_sub:lpm_add_sub_component|add_sub_30k:auto_generated|pipeline_dffe[*]}] 3
第二条:
set_multicycle_path -end -setup –to [get_pins {SEP_TDC_7ch:inst13|tdc1ch_won:inst?|TDC1ch2a:TCH0|lpm_counter30:RGZ1|lpm_counter:lpm_counter_component|cntr_rsh:auto_generated|safe_q[*]}] 3
第三条:加上 –from即从时钟到上述PINs也不行。
第四条:将“get_pins”改成“get_cells”以及“get_registers”也不行。
所以对于图14显示的违约路径还是加false paths来处理。最终工程的时序报告如图16所示。
附2
Multicycles can be specified relative to the source clock (-start) or
destination clock (-end). This is useful when the source clock and
destination clock are operating at different frequencies. For
example, if the source clock is twice as fast (half period) as the
destination clock, a -start multicycle of 2 is usually required.
Hold multicycles (-hold) are computed relative to setup multicycles
(-setup). The value of the hold multicycle represents the number
clock edges away from the default hold multicycle. The default hold
multicycle value is 0.
The -from and -to values are collections of clocks, registers, ports,
pins, or cells in the design. If the -from or -to values are not
specified, the collection is converted automatically into [get_keepers
*]. It is worth noting that if the counterpart of the unspecified
collection is a clock collection, it is more efficient to explicitly
specify this collection as a clock collection but only if the clock
collection also generates the desired assignment.
Applying exceptions between clocks applies the exception from all
register or ports driven by the -from clock to all registers or ports
driven by the -to clock. Also, applying exceptions between a pair of
clocks is more efficient than for specific node to node or node to
clock paths.
If pin names or collections are used, the -from value must be a clock
pin and the -to value must be any non-clock input pin to a register.
Assignments from clock pins or to and from cells applies to all
registers in the cell or driven by the clock pin.