作者:碎碎思 来源:OpenFPGA
有一个有趣的现象,众多数字设计特别是与FPGA设计相关的教科书都特别强调整个设计最好采用唯一的时钟域。换句话说,只有一个独立的网络可以驱动一个设计中所有触发器的时钟端口。虽然这样可以简化时序分析以及减少很多与多时钟域有关的问题,但是由于FPGA外各种系统限制,只使用一个时钟常常又不现实。FPGA时常需要在两个不同时钟频率系统之间交换数据,在系统之间通过多I/O接口接收和发送数据,处理异步信号,以及为带门控时钟的低功耗ASIC进行原型验证。本章讨论一下在FPGA设计中多时钟域和异步信号处理有关的问题和解决方案,并提供实践指导。
这里以及后面章节提到的时钟域,是指一组逻辑,这组逻辑中的所有同步单元(触发器、同步RAM块以及流水乘法器等)都使用同一个网络作为时钟。假如设计中所有的触发器都使用一个全局网络,比如FPGA的主时钟输入,那么我们说这个设计只有一个时钟域。假如设计有两个输入时钟,如图1所示,一个时钟给接口1使用,另一给接口2使用,那么我们说这个设计中有两个时钟域。
图1:双时钟域设计
平时我们在设计中遇到的门控时钟、衍生时钟以及事件驱动的触发器都可归为时钟域类别。如图2所示,通过一个简单门控时钟创建了一个新的时钟域。我们知道,这类时钟控制在FPGA设计中并不被推崇(可以使用时钟使能替代时钟门控),然而它却非常有利于我们理解时钟域这一概念。
本章我们将着重详细讨论以下主题:
图2:通过门控时钟创建的时钟域
一、跨时钟域
设计中包含多时钟域,首先要解决的是在不同时钟域之间传输信号的问题。信号跨时钟域传输将会是一个大问题,原因如下:
1、 信号跨时钟域传输产生的故障总是不太容易复现。设计中如果存在两个异步时钟域,故障往往与这两个时钟沿的相对时序有关。来自片外时钟源的时钟通常与器件实际功能并无任何关联。
2、 根据技术的不同,问题也不一样。(尽管由于其他因素的影响,这种情况并不总是成立)我们常常会发现,如果约束较小的建立和保持时间,从统计上来说高速设计技术比低速设计技术更不容易产生故障。同时,其它因素,比如同步器件中设计实现对输出的缓冲,也会对一个可能的故障产生显著影响。
3、 EDA工具一般不会探测和标注这类问题,静态时序分析工具是基于独立的时钟区域来进行时序分析,而且只有在特定的方式下根据指定的要求才能进行跨时钟域的时序分析。
4、 通常来说,如果没有很好地理解,跨时钟域故障难以探测且难以调试。所以所有跨时钟域接口都必须要在任何功能实现之前被很好地定义和处理。
让我们首先来看看在不同时钟域之间传输信号到底会产生什么错误。考虑图3所示的情况,一个信号在两个时钟域之间传播。
如图4所示,低速时钟的周期是高速时钟周期的两倍。低速时钟上升沿与高速时钟上升沿之间的间隔为常量,而且总是等于dC。由于这两个时钟的这种相位匹配关系,dC总是保持不变(假定频率没有漂移),而且在这个例子中,dC总是大于逻辑延时与高速时钟驱动的触发器建立时间之和。
图3:时钟域之间的简单信号传输
图4:两个时钟域之间的时序关系
当这些时钟一启动,它们之间存在一个固定的相位关系,如此可以避免任何建立时间和保持时间违规。只要时钟没有漂移,就没有任何时序违规出现,并且器件会如预想那样工作。现在我们再考虑另外一种情况,同样的时钟上电后的相位关系如图5所示。
图5:会造成时序违规的时钟相位关系
图5中两个时钟之间的这种相位关系就会造成时序问题。这种情况会在任意频率的两个时钟域之间。然而,如果时钟的频率匹配不对,这种时序问题在这种情况下也不会发生。
总结来说,时钟同步问题在FPGA设计中通常是一种不可复现的问题,而且会对设计的可靠性带来严重后果。后面我们会讨论解决这类问题的方案,在此之前,我们必须要讨论当建立和保持时间违规时到底会发生什么。下一小节就是关于这个主题。
1.1 亚稳态
触发器的建立时间和保持时间在时钟上升沿左右定义了一个时间窗口,如果触发器的数据输入端口上数据在这个时间窗口内发生变化(或者数据更新),那么就会产生时序违规。存在这个时序违规是因为建立时间要求和保持时间要求被违反了,此时触发器内部的一个节点(一个内部节点或者要输出到外部节点)可能会在一个电压范围内浮动,无法稳定在逻辑0或者逻辑1状态。换句话说,如果数据在上述窗口中被采集,触发器中的晶体管不能可靠地设置为逻辑0或者逻辑1对应的电平上。所以此时的晶体管并未处于饱和区对应的高或者低电平,而是在稳定到一个确定电平之前,徘徊在一个中间电平状态(这个中间电平或许是一个正确值,也许不是)。如图6所示,这就是所谓的亚稳态。
图6:时序违规导致亚稳态
如图6的波形所示,信号的跳变发生在建立和保持边界组成的时间窗口内,这意味着输出不会是逻辑0或逻辑1对应的确定电平,而是它们之间的一个中间电平。如果触发器包含有一个输出缓冲,那么亚稳态本身就可以称为随着内部信号的逐渐稳定而在输出上表现的杂散过渡。输出保持亚稳态的时间是随机的,甚至可能在整个时钟周期内都保持亚稳态。那么,如果这个亚稳态值输入到组合逻辑,根据逻辑门电路的切换门槛,错误的操作就可以发生。从时序收敛的角度来说,两个触发器之间的组合逻辑延时都要求要小于最小的时钟周期,但是这种亚稳态信号保持亚稳态的时间,本身就是变相地增加了逻辑延时。很显然,一个亚稳态信号会给设计带来致命的功能故障,而且该信号也将无法在各个时钟沿上采集到一致的结果。
事实上需要注意的是,在FPGA设计流程中想通过仿真来确定亚稳态对设计的危害是非常困难的。纯数字的仿真器并不能检查到建立和保持违规,从而在违规发生时,仿真出一个逻辑“X”(未知)值。而普通的RTL仿真,并不会出现建立和保持违规,所以也就不会有信号出现亚稳态状态。尽管门级仿真的时候会检查建立和保持是否违规,但是仿真由两个异步信号对齐而导致一个同步故障依然是一件十分困难的事情。尤其困难的是,设计或者验证工程师并不是在设计伊始即查找问题。那么,理解如何保持设计的可靠性以及如何避免需要通过仿真来揭露设计的同步问题,就显得十分重要了。解决亚稳态的方法有很多,后面我们将逐一进行讨论。
1.2 解决亚稳态方案1:相位控制
考虑这样一个设计,两个时钟域的周期不同,而且相位关系任意。如果至少有一个时钟由FPGA内部的PLL或者DLL控制,而且在PLL或者DLL的精度范围内,其中一个时钟的周期是另外一个时钟周期的数倍。那么如图7所示,通过相位对齐可以避免实现违规。
考虑这样一个例子,一个信号从低速时钟域传递进入另一个时钟域,而此时钟域的周期是低速时钟域的一半。根据前面的分析,如果没有任何相位关系的保证,那么时序违规就有可能发生。然后,通过使用DLL由低速时钟派生这个高速时钟,那么相位对齐就可以达成。
图7中,DLL调整高速时钟(采集)的相位来对齐低速时钟(发送)。数据在两个时钟域之间传递的时间是dC,该传递时间总是处于其最大可能值。本例中,只要从低速触发器到高速触发器的传播延时小于高速时钟周期,那么就不会有建立时间违规发生。如果因为时钟歪斜不够小而导致保持时间要求无法满足,那么可以通过配置实用高速时钟的下降沿来采集信号,当然前提是有足够的时序余量能确保建立时间要求得到满足。
图7:使用DLL对齐相位
总结来说,相位控制技术可以在一个时钟频率是另外一个时钟的数倍且其中一个时钟可以由FPGA内部PLL或者DLL控制时使用。
在很多例子中,设计控制时钟域之间的相位关系是很奢侈的。尤其是时序要求由FPG**外的芯片施加,或者时钟域之间没有任何确定相位关系的时候。举例来说,如果FPGA在两个系统之间提供了一个接口,而这两个系统施加在芯片输入输出延时上的时序要求非常紧张,调整任何这两个系统的时钟相位是不可能的。类似这种例子在实践中会经常遇到,所以需要使用新的方法来解决,下一节将讨论这种新的方法。
1.3 解决亚稳态方案2:打两拍处理,即寄存两拍
跨越两个异步时钟域传输单比特信号时,可以使用打两拍技术。根据上一节的讨论,建立或保持时间违规会导致一个触发器内节点上电平徘徊在一个中间状态,从而产生亚稳态问题,而且信号从这种中间状态到一个稳定状态需要时间,此时间的长度未知。这个未知的时间会被加入到时钟到输出的时间(Tco)里(影响随后路径上的延时),且会在下一级导致一个时序违规。如果该信号输入到一个控制分支或者一个判决树,那将是非常危险的。不幸的是,没有很好的办法来预测这种亚稳态将会持续多长时间,也没有很好的办法将这些信息反标注到时序分析工具以及优化工具。假定两个时钟域之间完全异步(即无法实现相位控制),那么尽可能避免亚稳态的一个最简单办法就是使用双触发。在其它也许教科书中也称这种方法为同步位、两级触发器或两级同步器。
图8所示的配置中,同步器电路(其输入为Din)中的第一拍后也许会产生亚稳态,但是信号有机会在其被第二级锁存以及被其它逻辑看到之前稳定下来,如图9所示。
内容
图9:打两拍重同步器
图9中,Dsync是同步器中第一个触发器的输出,而Dout是第二个触发器的输出。Dout本质上是等到同步后的信号一旦稳定下来后将其往下传,并且确保其它电路不会收到亚稳态信号。同步器两级触发器之间不要添加任何逻辑,这样可以使得信号获得尽可能长的时间来回到稳定状态。所以总结来说,打两拍同步器在单比特信号跨异步时钟传输时,用来将该单比特信号重新同步到异步时钟域。
理论上来说,第一个触发器的输出应该一直保持不确定的亚稳态,但是在现实中它会受到实际系统一系列因素影响后稳定下来。打个比方,想象一下一个皮球稳定地停住在一个山尖上,从任何方向上轻推一下球,它都会由相反的方向从山上滚落。同样,处于亚稳态的一个逻辑门,由发热、辐射等产生的随机波动都会促使该亚稳态回到逻辑0或者逻辑1对应的稳态。
使用打两拍技术采样一个异步信号时,无法完全预知我们想要的信号跳变,将在当前时钟发生还是下一个时钟发生。当信号属于一个数据总线中的一部分(有些数据位比其它比特晚一个时钟周期跳变)时,或者关键数据必须要精确到单个时钟周期内到达时,这种打两拍技术是没有帮助的。不过,对于控制信号来说,如果它们可以忍受正负一个或更多个时钟周期的变化,这种技术还是非常有用的。
举例来说,一个外部事件控制一个比特来触发FPGA内部动作,这个触发动作发生的频率可以非常的低,比如两个事件之间的间隔可以达到微秒甚至毫秒级。在这个例子中,一些额外的数纳秒的延时并不会影响该事件的行为。如果由外部事件驱动的改比特输入到一个状态机的控制结构中,通过同步器打两拍处理,那么想要的信号变化只是被延迟了一个时钟周期。然而,如果没有进行打两拍处理,那么判决逻辑也许会从该异步信号的亚稳态状态解码出不同状态跳转信息,并使得状态机同时跳转到不同的分支。
除了纯数字系统外,还有一种混合信号系统,这种系统会通常会产生异步反馈信号到FPGA,如图10所示。
图10:重新同步模拟反馈
上述对异步信号打两拍的同步器的Verilog代码如下所示:
module analog_interface(
...
output regfbr2,
input feedback);
reg fbr1;
always @ (posedge clk) begin
fbr1<=feedback;
fbr2<=fbr1;//;doubleflop
end
...
反馈信号会产生时序违规,而且fbr1在时钟沿后一个不确定的时间内处于亚稳态。那么,其它逻辑只可以使用的信号fbr2。
使用打两拍同步处理技术时指定时序约束是非常重要的,需要施加的约束是将位于第一个和第二个寄存器时钟域之间的信号路径指定为假路径,即让时序分析器部分此路径。因为打两拍同步器结构用于重新同步信号,在这两个时钟域之间并没有需要分析的同步路径。此外,如前所述这两个触发器之间的时序要尽可能的小,这样可以减小亚稳态被传播到第二级触发器的可能性。
1.4 解决亚稳态方案3:使用FIFO结构
跨时钟域传输数据用得最多的方法就是使用先入先出(即FIFO)结构。FIFO可以用于在两个异步时钟域之间传输多个比特信号。我们通常看到的FIFO应用包括在两个标准总线之间传输数据,以及从可突发访问的存储器中读出数据或者对其写入数据。例如,如图11所示,显示的是一个可突发访问存储器与一个PCI总线之间的接口。
图11:FIFO在PCI应用中
在很多不同的应用中,FIFO都是一种非常有用的数据结构,不过这里我们仅仅关注其处理跨时钟域突发数据的能力。
FIFO非常类似于在超市里的结账通道,每个客户到达结账台的时间多少有点随机性,结账速度在一定意义上说是匀速的。有时候结账客户可能会很少,而其他某些时候又会突发很多客户需要结账,收款员不可能立刻为每个客户服务,所以需要排队。抽象地来说,我们称这种排成一队的数据为一个序列。随后,收款员会以或多或少平均的速度为每一个顾客服务,并不会理会队列的长度。假如需要结账的顾客涌入收银台的速度超过了收款员的服务速度,那么这种收款结构就无法支撑了。那么此时,就需要采取措施,要么加快收款员的服务速率,要么减少新增顾客数。
同样的道理也存在于数据传输中,数据可能到达某个时钟域的间隔是完全随机的,有时候或许会面临一个很大突发数据块。这种情况下,处在另一个时钟域的接收设备只能以指定的速率来处理数据。如图12所示,一个FIFO被用于缓存数据,这样在设备中就形成了一个数据序列。
图12:异步FIFO
通过使用异步FIFO,数据发送端可以以随意的间隔发送数据,而接收端也可以以其固有的带宽从数据序列里取出数据并进行处理。由于任何由FIFO实现的数据序列的长度都不能无限制,所以需要一些控制来防止FIFO溢出。这时候,有两种选项可以采用:
注意,发送设备的时钟频率没有必要高于接收端设备,否则容易造成溢出。以较慢的频率将数据送入FIFO,那么数据写入FIFO的时钟周期数要少于接收端将要处理数据的时钟周期数。那么,如果不采取握手控制,就必须要理解以上描述会产生溢出的最坏的情况。
在任何一段时间内,假设数据发送写FIFO的速率大于接收处理数据的速率,那么很轻易地使系统无法维持。因为没有任何存储设备可以存得下无限的数据,这种问题需要在系统结构层级才能解决。通常来说,突发发送一般是以小周期性或非周期性发生。所以FIFO的最大尺寸要大于等于(具体还要根据数据接收器的属性)突发的尺寸。
在很多例子中,不管是突发尺寸还是数据到达的分配都无法很好地定义。这种时候,就有必要使用握手控制来防止FIFO产生数据溢出。如图13所示,这种握手控制通常由一些标志信号来实现。这些标志信号,一个是发送侧的满标志,用于提示FIFO没有多余空间存储数据了,另一个是而空标志,用于提示接收侧,FIFO中没有数据需要处理了。管理这些握手信号可能还需要一个状态机,正如图13所示。
图13:FIFO的握手控制
FIFO在FPGA内一般是通过封装一个双口RAM来实现。表面上看微不足道的标志信号如空和满指示等,实际上是实现起来反而比较困难。原因就在于输入控制常常需要依据输出来产生,同样的输出控制也常常需要依据输入来产生。例如,驱动输入的逻辑必须要知道FIFO是否已满,而这只能通过获取从输出端读出的数据数量才能得知。同样的道理,在输出侧从FIFO读数据的逻辑必须要了解FIFO中是否还有数据(即FIFO是否已空),而这只能通过输入端口的写指针才能判决。
这里我们探讨使用FIFO在两个异步时钟域之间传输数据,不过同样会面临实现FIFO本身时遇到的握手标志问题。为了在两个时钟域之间传递必要的信号,我们必须重回上一节讨论到的打两拍技术。下面我们以图14所示的简单异步FIFO框图为例进行阐述。
图14:异步FIFO简单框图
图14中,在产生空和满信号时,写地址和读地址都必须是异步传递到对方时钟域中。这样在重新同步多比特地址总线时,问题就来了,即根据各个比特不同的走线,总线中某些比特可能会比其它比特晚一个时钟周期。换句话说,由于两个时钟域异步的自然属性,使得地址总线有些比特在一个时钟沿上被采集,而另一些比特却在下一个时钟沿上被采集,当然这取决于数据是否在第一个触发器的时钟沿到达之前提前足够长时间有效。如果上述情况发生,那么会给系统带来严重后果,因为二进制地址中有些位变化有些位却没有,因此接收逻辑将会得到一个完全无效的地址,这个地址既不是当前地址也不是上一个地址。
这个问题可以通过将二进制地址转换为格雷码来解决。格雷码是一种非常特殊的计数器,两个相邻地址中只有一个比特是不同的。所以当地址改变时,只需要改变地址中的一个比特即可,这样就可以避免上面提到的问题。如果发生变化的那个比特并没有被下一个时钟正确采集,地址线上会“同步地”保留旧的地址值。那么,任何不正确的地址(即既不是当前地址也不是旧地址)操作都被消除了。所以总结来说,格雷码常用来在异步时钟域之间传递多比特计数值,且多用于FIFO内。
需要额外注意的一点是,由于只有读写地址是需要在异步时钟域之间传递,所以地址就有可能比预想的晚一个时钟周期,同时意味着空或者满标志置位晚一个时钟周期,但是这并不表示错误导致了数据溢出状况。如果这种情况在传递地址到读时钟域时,读逻辑将简单地认为数据没有写入,且将认为FIFO已空尽管此时FIFO已经被写入一个数据。这只会对总的吞吐率有一些小影响,但是不会导致下溢(即读已空的FIFO)状况发生。同样地,当地址被传递到写时钟域时,如果读地址被延时了,那么写逻辑会认为FIFO里没有多余空间,尽管此时FIFO还未满。这同样只会对总的数据吞吐率有些微小影响,却不会造成上溢(写已满的FIFO)发生。
FIFO是一种足够通用的模块,大部分FPGA供应商都提供了工具,可以让客户根据自己的要求来自动产生软核。这些用户FIFO可以像其它IP模块那样由用户手动地在设计中例化。那么,在一个FPGA设计中使用自己的FIFO时,上述讨论的问题很可能将不必由设计自己来解决。当然,同样的问题也经常在异步时钟域之间传递数据的时候发生,所以理解这类设计实践对于一个高级FPGA设计者来说非常重要。
1.5 设计分区同步器模块
在顶层为设计划分好设计分区是一个好的设计实践行为,这样任何功能模块外面都包含一个独立的同步器模块。这样有利于在划分模块的基础上实现所谓的理想时钟域情况(即整个设计模块只有一个时钟),如图15所示。
图15:设计分区同步器模块
对设计进行分区有很多理由。首先,对每个独立的功能模块进行时序分析变得简易,因为模块都是完全的同步设计。其次,整个同步模块中的时序例外也很容易得到定义。再次,底层模块的同步器加时序例外在代入到设计顶层时,大大降低了由于人为失误造成的疏漏。所以,同步寄存器应该在功能模块外单独分区。还有很多类似的设计实践在使用FPGA作为ASIC的设计原型时得到应用,下一节我们将再进行详细地讨论。
二、ASIC原型设计中的门控时钟
ASIC设计一般对功耗非常敏感,同时ASIC的时钟树设计又非常灵活,所以会在整个设计中经常使用门控时钟在逻辑不需要活动的时候来去使能这些逻辑。虽然使用FPGA作为ASIC的原型可以模拟整个逻辑功能,但是二者之间的有些物理属性,如功耗方面,还是不太一样。那么,要求FPGA来模拟ASIC的整个低功耗优化是没有必要的。实际上,正是由于FPGA的粗放式的时钟资源,让其模拟这方面功能也是不太可能的。这一节我们将讨论一些解决这个问题方法,并且再讨论一些可以应用于ASIC设计的技术来使FPGA原型设计更加容易。
2.1 时钟模块
如果一个ASIC设计中使用了大量的门控时钟,建议将所有这些门控操作统一放在一个专门的时钟生成模块中,并与功能模块隔离,如图16所示。
图16:统一的时钟模块
通过将时钟门控置于一个单一的模块,不但可以是约束处理更简单,而且当要对FPGA原型进行任何修改时也更容易。例如,如果设计者选择某次编译时删除所有门控单元,那么一个单一的模块里很容易实现。下一节我们将对此进行详细讨论。
2.2 时钟门控移除
有很多办法可以从FPGA原型里删除时钟门控,下面的例子就显示了一个很明显,但却也是很麻烦的一个方法。这个例子的代码如下所示,该代码是删除FPGA原型里所有的门控功能。
‘define FPGA
//‘define ASIC
module clocks_block(...)
‘ifdef ASIC
assign clock_domain_1=system_clock_1&clock_enable_1;
‘else
assign clock_domain_1=system_clock_1;
‘endif
如果上述代码需要开放时钟门控,那么在FPGA原型设计中只需要修改宏定义即可。不足之处是,任何时候要将FPGA原型转化为ASIC设计时总是需要做出一些修改(其实就是修改宏定义)。很多设计者对此会感觉不是太舒服,因为他们认为二者使用的不是一样的RTL。一个更好的办法是使用一个自动门控删除工具来消除任何认为造成失误的可能。许多现代的综合工具通过正确的约束,现在都提供这项功能。例如,Synplify就有一个称为“Fix gated clocks”选项,就是用于自动地从时钟线上将门控操作删除,并将其移动到数据路径上。我们来看下面这个代码示例:
module clockstest(
output reg oDat,
input iClk,iEnable,
input iDat);
wire gated_clock=iClk&iEnable;
always @ (posedge gated_clock)
oDat<=iDat;
endmodule
在上面的代码中,系统时钟被一个使能信号门控产生一个门控时钟。这个门控时钟被用于驱动触发器oDat,而oDat用于寄存器输入iDat。如果没有启用“fixing the clock gating”选项,那么综合工具将会直接实现逻辑功能,如图17所示。
图17:直接时钟门控
图17的逻辑实现中,在时钟线上放置了门控操作。那么设计中现在有了两个时钟域,必须分别对它们进行约束,而且必须分别将它们布局到时钟资源。但是,如果启动了时钟门控删除,这个逻辑门就会比较容易地被移动到数据路径上,如图18所示。
图18:时钟门控删除
现在大部分逻辑器件里逻辑单元都提供了一个时钟使能输入,有了该使能输入就可以不使用本方案。然而,如果一个特定的技术并未提供触发器时钟使能,那么只能使用本技术来删除时钟门控,只是这样就将会在数据路径上增加延时。
三、要点总结