时序漫谈之十二·跨时钟

Robin Latticesemi 4天前

时钟域的处理比较复杂,我们要分几次来讲。


如果在我们的设计中存在从clock1时钟域到clock2时钟域的信号传递,但我们只分别约束了clock1和clock2的频率,对跨时钟域的部分没有特别指定,软件会怎么确定这之间的路径的延时约束呢?


情况1:如果两个时钟的频率相同,或者这两个时钟之间有整数倍的关系。那软件会以相对快速的时钟周期做为跨时钟域的数据路径的延时约束。

CLK1和CLK2频率相同,他们之间的跨时钟域延时约束就是一个时钟周期。

CLK1的频率是CLK2的2倍,以更快的一个CLK1的时钟周期作为跨时钟域路径的延时约束。


情况2:如果两个时钟频率没有整数倍的关系,以两个时钟之间时钟沿的最小间隔作为跨时钟域路径的延时约束。

CLK1的约束是101MHz,CLK2的约束是50MHz。假定两个时钟的第一个上升沿同步,在下一个50MHz的上升沿,它与101MHz的上升沿的间隔是0.2ns。所以软件会以0.2ns作为跨时钟域路径的延时约束。


为了避免这样的误解,我们可以修改频率约束,让两个时钟的频率约束是整数倍的关系。

还有一种情况

CLK1和CLK2虽然做STA的频率约束有整数倍的关系,但布局布线的约束分别是106MHz和54MHz。所以他们之间跨时钟域路径的延时约束是0.2ns,不是10ns。我们可以采用类似的方法修改频率约束。

这里我们简单介绍一下par_adj。我们在做频率约束的时候,为了获得更好的性能,会做过约束。但在做时序分析的时候,还是要以系统实际运行的频率来观察。这就造成我们必须为布局布线和时序分析准备不同的时序约束。这给约束的设计带来了复杂性。Par_adj的作用在于EDA软件在做布局布线的时候,以约束的频率+par_adj频率做为目标进行布局布线,在做时序分析的时候,忽略par_adj的参数,以实际系统需要的频率分析。这样我们做布局布线和时序分析就可以统一在一个约束下了。

前面我们讲了在有跨时钟域的操作的时候,EDA软件的处理方式。可以看到,如果不对跨时钟域的数据路径做约束,会给我们的时序分析带来很多不确定性。软件缺省以最快的时钟做为跨时钟域路径的约束,相当于是过约束。比如从100MHz到50MHz的跨时钟域的操作,目的逻辑只能以50MHz的频率处理从100MHz时钟域过来的数据,在跨时钟域上要求延时是一个100MHz的周期是没有意义的。从设计来说,这种跨时钟域的操作并不是需要每一拍都要处理。


时钟域的约束由此而生,在sdc格式中,这个约束是 set false path。set false path覆盖的含义很多,跨时钟域约束只是其中的一部分。简化起见,这里我们以Lattice的Multicycle约束为例来专门讲解跨时钟的约束设置。原理上是相通的,只是语法上略有差别。


完整的Multicycle语法也很复杂,我们是面向应用的,所以这里不做详细的语法解释,我们从最常用的multicycle用法入手了解这个约束。

 

MULTICYCLE FROM CLKNET "clk1" TO CLKNET "clk2" [DELAY] ;

 

这个约束定义了从clka到clkb的延时。[DELAY]可以是个绝对的时间。比如:

 

MULTICYCLE FROM CLKNET "clk1" TO CLKNET "clk2" 10 ns ;

 

如果clk1是200MHz的时钟,clk2是100MHz的时钟,在没有上面这个约束的情况下,软件缺省认为从clk1到clk2的数据延时约束是5ns。

上面这条约束会覆盖软件的缺省设置,这样从clk1到clk2的跨时钟域延时约束就变成了10ns。要注意,这条约束定义的是clk1到clk2,不是双向的。在没有单独指定的情况下,从clk2到clk1的延时约束还是5ns。如果你的datapath设计这两个时钟域有双向的数据传递,要分别设置multicycle约束。


这样描述跨时钟域的延时约束好处是可读性好,设计人员可以把需要的绝对时间作为约束。缺点是当时钟频率变化的时候,你需要重新设置这个绝对时间。而且我们在做逻辑设计的时候,更多的是考虑从一个时钟域到另一个时钟域我们是多少个周期座一次操作。所以multicycle还有另外一个用法。

 

MULTICYCLE FROM CLKNET "clk1" TO CLKNET "clk2" n X ;

 

n在这里代表一个数字,可以是整数,也可以是小数。这样一个语法定义的从clk1到clk2的数据延时是


default+(n-1)*clk_period)


Default就是我们之前介绍的缺省设置。Clk_period如果不做特别指定,是目标时钟的周期。我们举例来说吧。还是假定clk1是200MHz,clk2是100MHz,如果不加multicycle约束,从clk1到clk2软件的缺省的数据延时是5ns。那么下面这条约束:


MULTICYCLE FROM CLKNET “clk1” TO CLKNET “clk2” 1.0 X

N = 5 ns + (1.0 – 1.0) * 10 ns = 5 ns  (destination clock 10ns default)

 

所以讲n设置为1就是软件的缺省设置。

 

MULTICYCLE FROM CLKNET “clk1” TO CLKNET “clk2” 2.0 X

N = 5 ns + (2.0 – 1.0) * 10 ns = 15 ns  (destination clock 10ns default)

 

这样一个设置就将延时从上面的5ns扩大到15ns。


第二种设置方法适合时钟频率相关的,会根据时钟频率约束的变化而变化,灵活性会更高一些。但不要犯错误,比如:

 

MULTICYCLE FROM CLKNET “clk1” TO CLKNET “clk2” 0.5 X

N = 5 ns + (0.5 – 1.0) * 10 ns = 0 ns     

MULTICYCLE FROM CLKNET “clk1” TO CLKNET “clk2” 0.25 X_DEST

N = 5 ns + (0.25 – 1.0) * 10 ns = -2.5 ns  

 

在应用中你可以根据自己的喜好来设置。

前面我们介绍了跨时钟的操作和Multicycle约束的用法。在实际设计中我们该如何应用呢?原理讲一讲大家都清楚,不用我在这里废话,这些内容在网上也可以搜的到。关键是如何使用。


通过我们前两讲的介绍,有心的读者可能已经想到,multicycle的约束实际上是把约束放的更宽松,而不是更严格了。也就是如果我们不设置这个约束,软件默认可能是10ns的延时约束,我们添加这个约束后,变成20ns了。这样做到底好不好呢?


有些人可能会有一点不舒服。长期的固定思维是让我们认为频率越高越好,你这放宽时序约束不是暗指我的设计不够优秀,需要降频才能满足要求嘛!是你的器件不行吧,责任为什么要我工程师来背?

这里我们要理清两个问题:


1. 时钟频率是不是跑的越高越好?

摩尔定律还在野蛮成长的年代,我们被以Intel为主的CPU厂商的长期洗脑给误导了。在有些场景,是的,频率越高代表着处理能力的提高。但在有些场景,频率的提高还代表着功耗、成本的增加。这里成本的增加,不仅仅是单纯的物料成本的增加,还包括为了达到更高的性能所付出的更多人工成本。所以一个理智成熟的设计者,做出的设计性能一定是满足相关应用的最低频率,而不是最高频率。这样我们才能在性能和价格之间找到一个最好的平衡点。追求更快,更高,更强是我们的共同目标。我们需要有DF21这样的战略武器去占领制高点。但我们大量需要的还是HQ这样常规战术武器保护我们的日常生活。方方面面都最好其结果一定是完美的吗?我想起了一个笑话,一个女孩希望的男朋友是个大眼睛,高鼻梁,耳朵要大,这样有福,嘴巴要大,大吃四方。最后系统匹配的最佳男友是“猪八戒”。用最小的代价,包括功耗,成本,满足设计指标,而不是一定要远远超过设计指标,这才能证明你的设计实力。用山珍海味做一顿大餐不代表你就是料理大师,用简单的食材做出可口的饭菜才是传说中的厨神。


2. Multicycle是不是相当于降频的约束?

Multicycle是更准确的定义了跨时钟域的data path需要的时间。有了这个准确的定义,可以帮助EDA工具更合理的分配布局布线资源,将更短的路径分给频率要求更高的信号。从这个角度来看,multicycle不是降频的约束,恰恰相反,正式因为multicycle约束,会让我们的系统频率跑的更高


设计中有跨时钟域的操作的时候,设计者就要考虑是不是要添加multicycle的约束。特别是当时序不满足,而且关键路径就在跨时钟域的data path上的时候。在使用multicycle约束的时候,其主导者一定是设计者自己,软件仅仅是提供一个工具。


例如我们有一个从200MHz的时钟域到100MHz的时钟域的data path。在实际应用中,不可能每个200MHz时钟周期都会有更新的数据需要目的时钟来处理,100MHz的时钟也处理不过来。更多的时候,我们是通过时钟使能信号,在两个或者多个200MHz的时钟周期间隔给100MHz发一次数据。这种情况下,只要保证在这些周期的时间内,数据能从200MHz时钟域到达100MHz的时钟域,对功能就不会有任何影响。


always @(posedge clk200 or negedge rst_n)

begin

  if (!rst_n)

    cnt200 <= 2'b0;

  else

cnt200 <= cnt200+1;

end

 

always @(posedge clk200 or negedge rst_n)

begin

  if (!rst_n)

    data2_100 <= 1'b0;

  else if (cnt200 == 2’b11)

data2_100 <= datain200;

end

 

从上面这段代码,我们看到data2_100信号每4个失踪周期才更新一次。针对这个情况我们可以将multicycle这样设置:


MULTICYCLE FROM CLKNET “clk200” TO CLKNET “clk100” 4 X_SOURCE

N = 5 ns + (4 – 1.0) * 5 ns = 20 ns

MULTICYCLE FROM CLKNET “clk200” TO CLKNET “clk100” 20 ns