riple

Stay Hungry, Stay Foolish.

维护遗留代码(8)——当riple遇到ripple(行波时钟)

0
阅读(3243)

在补充完整了时序例外约束,排除了虚假时序违规报告之后,设计中的绝大部分时钟都得到了收敛。但是,仍然有部分关键时钟不能收敛,时序余量总的负值很大。 二进制文件下载后,时序不收敛路径对应的功能不能在设计要求的频率下正常运行,选用较低的频率后运行正常。这表明时序分析报告反映出的问题与实测结果是一 致的。启用高级编译选项后,不收敛的路径有局部变化,但是整体上的时序收敛问题依然存在,优化效果并不明显。
        在以往的工作中,我遇到的时序收敛问题可以归纳为两类:一类是缺少引脚时序约束导致的编译不稳定问题;另一类是组合逻辑过于复杂导致的时 序收敛困难问题。在这一设计中,通过添加引脚时序约束,第一类问题已经不存在了;这一设计存在第二类问题的可能性较大。通过对不收敛路径起 止节点和所在时钟域的分析,可以看出,在这些路径上固然存在比较复杂的组合逻辑,但是还有一个共同点:这些路径的起点和终点的寄存器并不是由同一个时钟驱 动的,这两个不同的时钟处在同一个时钟组内,相互之间存在确定的(或可推断的)时钟相位关系,属于同源时钟。(这里需要说明的是,在前面添加时序例外约束 的工作中,我已经把不同时钟域之间可能存在的路径都设置为了false_path,在时序分析报告中不会存在虚假的跨时钟域路径报告。)通过进一步分析这 些同源时钟,还可以发现另一个规律:出现时序违规的路径大都发生在“生成时钟(Generated Clock)”之间,或者是“基时钟(Base Clock)”与“生成时钟”之间;这些“生成时钟”都是通过计数器分频或状态机输出得到的,通过PLL分频得到的“生成时钟”并不存在时序收敛问题。现在,我遇到了时序收敛的第三类问题:由于使用“生成时钟”造成的时序收敛困难。
       至此,该设计在时序收敛上存在的问题变得清晰了——该设计的时序收敛问题,并不仅仅是由于缺少完整的时序约束导致的虚假报告或端口时序问题,也不单纯是组合 逻辑过于复杂导致的,该设计在时钟方案(Clock Scheme)上存在的问题是时序收敛困难的根本原因。在着手对该设计进行时序调整之 初,我就对该设计中大量使用了“生成时钟”的做法提出过质疑,但是当时缺乏充分的证据来支持这一论断。只有完成了前面一系列的时序约束工作,除却“浮云遮 望眼”,这一问题才能最终“水落石出”。

        在我和我的同事们接触过的设计中,都不存在这么复杂的时钟生成关系。我们在设计中偶尔也会使用“生成时钟”来完成简单的时钟分频,由于芯片资源丰富或“生 成时钟”作用范围小等原因,并没有遇到过什么严重的问题。我们对于“生成时钟”的普遍看法是,使用计数器分频产生“生成时钟”的做法不好,可能的话最好用 PLL来实现;计数器分频产生的“生成时钟”属于同步设计(对于TimeQuest来说,“基时钟”与“生成时钟”之间的相位关系是可以确定的,并不是随 机的,因此也是可以采用同步时序分析方法加以分析的),不会引入跨时钟域问题;只要综合工具能够把“生成时钟”分配到全局时钟网络上,“生成时钟”的偏斜 (skew)就会很小,也就不会引入时序收敛问题。上述看法的前两点都是对的,但是最后一点是经不住推敲的。对于最后这一点,可以参考AlteraForum 上的一篇文章。我也会在后面的博客中给出详尽的实例分析。这里不做展开论述。
        由计数器分频或状态机输出产生的时钟又叫“行波时钟(ripple clock)”。当前一级时序逻辑的数据输出被用作下一级时序逻辑的时钟输入时,这个时钟就是“行波时钟”,在TimeQuest里被归为“生成时钟”。在 大学里最初接触计数器电路时,我们都学过“行波计数器(ripple counter)”,其原理就是采用“行波时钟”来驱动下一级触发器。这是一类并不被推荐的计数器设计风格,被归为异步设计,但它们在数字电路的发展史上 占有一席之地。由于我的名字中有一个“波”字,从那时起,出于对数字电路的兴趣,我把自己的网名起成了riple。没想到,近十年后的今天,riple在维护遗留代码的过程中与ripple相遇了下面,我就 讲讲我是如何把“行波时钟”转换为正常时钟的。


        针对“行波”时钟方案向同步时钟方案转换的问题,所有的相关文献上都建议采用“时钟使能”来解决。通过修改"行波时钟"的生成逻辑,在“行波时钟”的上升 沿(或下降沿)产生宽度为一个“基时钟”周期的正脉冲,把这个脉冲信号作为后级触发器的时钟使能信号,用“基时钟”代替“行波时钟”作为后级触发器的时钟 输入信号,从而把原本由“行波时钟”驱动的后级逻辑转换为由“基时钟”驱动,并采用新生成的时钟使能信号来产生与“行波时钟”边沿对应的时序控制。这样一 来,原本由两个时钟驱动的复杂同步电路(“行波时钟”方案并不能看作异步时钟方案)就转化为了 由同一个“基时钟”驱动的简单同步电路
        简单同步电路带来的好处是,较小的时钟偏斜给P&R工具调整源寄存器和目的寄存器的布局位置和其间的走线延时提供了便利。
在 TimeQuest对简单同步电路时序路径的分析中,我们可以看到源寄存器和目的寄存器的时钟到达时间大致相同,时钟延迟虽然占了总延迟中很大的比例,但 是在计算公式中相互抵消了。在由“行波时钟”驱动的复杂同步电路中,较大的时钟延迟差异就会产生副 作用。当“行波时钟”驱动目的寄存器时,由“行波时钟”生成逻辑引入的延时很大,导致目的寄存器的时钟到达时间增大,等价于源寄存器输出数 据到达目的寄存器过快,容易产生保持时间违规;当“行波时钟”驱动源寄存器时,源寄存器的时钟到达时间增大,等价于源寄存器输出数据到达目的寄存器过慢, 容易产生建立时间违规;当源和目的寄存器都是由“行波时钟”驱动时,由于“行波时钟”生成逻辑分别到达源和目的寄存器的时间不同(当“基时钟”与“行波时 钟”的数量总和超出FPGA芯片能够提供的全局时钟布线资源时,部分“行波时钟”就要采用普通的逻辑布线资源到达寄存器,这种情况就会发生),较大的时钟 偏斜也会引入保持或建立时间违规。P&R工具需要作出额外的努力来保证上述三种路径的时序 收敛,增加了困难不说,最终的结果往往是时序不能收敛。

        明白了对“行波时钟”方案修改的基本原理和带来的益处,下面我们来看看我在具体的实施过程中采用的方法。上述时钟修改方法需要对“行波时钟”的生成逻辑进 行改动,以产生时钟使能脉冲。这种方法适用于“行波时钟”数量较少,并且都是采用计数器分频方式生成的情况;当一个设计中需要在多处进行修改,并且“行波 时钟”的生成逻辑是复杂的状态机输出时,这种修改方法很容易引入逻辑错误。
        在riple遇到的这个设计中,需要修改的“行波时钟”的个数达到了20条。如果采用直接修改生成逻辑的方法,我需要逐个分析这20条时钟的生成逻辑,并 作出修改,这样的工作量和可能会引入的逻辑错误是不可接受的。此外,修改后,一个“行波时钟”信号至少要变为两个信号(一个“基时钟”信号,一个时钟使能 信号),由“行波时钟”的生成逻辑所在的位置到达使用“行波时钟”的模块需要穿越多个例化层次,每个层次都需要修改和增加端口信号,这样的工作量也是不小 的。我面临的问题是:找到一种通用的、独立于具体编码的、产生影响最小的“行波时钟”修改方法

        针对上述问题,我写了一个小模块。采用这个 小模块,一个“行波时钟”信号变成了四个信号:“行波时钟”信号本身,“基时钟”信号,“行波时钟”上升沿对应的正脉冲,“行波时钟”下降沿对应的正脉 冲。这四个信号覆盖了对“行波时钟”信号进行修改后所有可能的使用情况:
        1. 使用“行波时钟”信号作为数据输入的情况。
        2. 使用“行波时钟”上升沿对应正脉冲信号和“基时钟”信号的情况。
        3. 使用“行波时钟”下降沿对应正脉冲信号和“基时钟”信号的情况。
这四个信号以一个4位矢量的形式输出:
assign sig_out[3] = neg_pulse;
assign sig_out[2] = pos_pulse;
assign sig_out[1] = clk_in;           // base clock
assign sig_out[0] = sig_in;           // target ripple clock
当在各个模块之间传递时,这个4位矢量可以使用“行波时钟”的原信号名称,在各个模块端口只需要修改原信 号宽度,不需要增加信号个数。在使用“行波时钟”的模块中,可以根据“行波时钟”的使用情况,在原信号的名称后增加一个位选择符号来选择相应的信号进行使 用

`ifdef Ripple_Clk_Terminator_For_ripple_clk
always @ (posedge ripple_clk[1]) begin    // new clock-enable usage, with base clock
if (ripple_clk[2]) begin
`else
always @ (posedge ripple_clk[0]) begin    // original posedge usage, with ripple clock
`endif

(original logic here)

`ifdef Ripple_Clk_Terminator_For_ripple_clk
end
end

`else
end
`endif

        这个小模块的内部逻辑很简单,使用了一个寄存器对“行波时钟”信号进行一拍延时,延时后的信号与原信号一起产生“行波时钟”上升沿和下降沿对应的正脉冲。 在模块的输入端口,除了“基时钟”和“行波时钟”外,还引入了一个使能输入信号。这个使能信号可以用来级联多个这样的模块,解决第二级、第三级“行波时 钟”的同步化问题。

采用这样的方法,原有的“行波时钟”都能得到风格一致的修改。修改工作的复杂度下降了,工作量减 小了,可能引入的问题也少了。
        在“行波时钟”逐步减少的过程中,能看到时序收敛问题的逐步改善。当这些关键的“行波时钟”最 终都被转化为同步时钟使能后,这个FPGA设计的时序终于收敛了。
        需要注意的是,这样的修改会改变“基时钟”与“行波时钟”之间原有的时序关系。如果原始设计在“基时钟”与“行波时钟”之间的 信号传递是发生在相邻两个“基”时钟周期之间的,那么这样修改后不会对原有的时序关系产生影响;如果原始设计利用了“基时钟”与“行波时钟”之间的异步关 系,试图在同一个“基”时钟周期内完成信号的传递,那么进行同步修改后的时序关系就不同于原设计了,需要重新设计基于同步逻辑的时序关系。这样的问题,我 在修改后并没有遇到很多,凡是遇到的都会产生逻辑错误。所以,建议在修改前建立一个基本的仿真环境,对修改前后的逻辑功能进行比较。
        需要注意的是,无论是否采用了这种方法,对“行波时钟”的修改,都会产生新的多周期时钟关系,需要补充多周期时序例外约束;原有的针对“行波 时钟”的多周期时序例外约束也需要进行相应的放大调整。比如“行波时钟”是“基时钟”的二分频,那么原有的针对“行波时钟”的单个周期的缺 省时序要求就要变成针对“基时钟”的2个周期的多周期约束;原有的针对“行波时钟”的2个周期的多周期约束就要变成针对“基时钟”的4个周期的多周期约 束。补充和修改多周期时序例外约束是必要的,否则就会引入新的虚假时序违规报告,甚至产生“过约束”的副作用。

        后记:该设计由同一个模块两次例化组合而成,占了FPGA芯片可用资源的95%以上。最初的设计只例化了一次,相应地,最初的设计资源使用情况近似是当前 的一半,其中包含的“行波时钟”个数也是当前设计的一半。在当前设计时序收敛后,我们针对最初的设计也进行了编译。我们发现,最初的设计在采用“行波时 钟”方案时,只要添加正确的时序约束,不经过同步化修改也能实现时序收敛。最初的“行波时钟”方案也能实现时序收敛,是因为FPGA芯片资源紧张的使用情 况得到了缓解,全局时钟布线资源与“行波时钟”的个数相比是相对丰富的。这也是为什么第三方工程师在最初的设计中使用了“行波时钟”方案而没有遇到大问题 的原因。这一发现,从另一个角度证明了“行波时钟”时钟方案潜在的风险。谁也保不准今天 资源充分的设计,在以后的维护过程中不会变成资源紧张的设计。以不变应万变的策略是:遵循好的 编码风格和时钟方案,给后来的维护者预留充分的时序余量。

 

相关链接:FPGA clock schemes