跨时钟域笔记(二)
原文链接,在此稍加整合了一下。
上一篇中我们回顾了一些基础知识,其中最重要的概念就是亚稳态。我们接下来所要看到的各种CDC的设计方法,本质上都是围绕在如何解决亚稳态带来的问题。
我们首先来看最基本的问题,single bit level 信号的跨时钟域。single bit 直接被destination domain的flop去sample产生的问题我们在上一篇已经讨论过,那么解决的办法呢?看起来很简单 – 之后再加一个flop,也就是说用两级的flop来同步source domain的signal。我们通常把这种synchronizer 叫做2flop synchronizer或者double flop synchronizer,俗称“打两拍”。
是否有疑问这么简单就可以了吗?凭什么第二级的输出就没有亚稳态了?相信有很多初学者也和我当初有同样的困惑。在这里我们要再次回顾一下metastable产生的原因。第一级flop产生metastable的原因是flop里面没有及时锁住该锁的值,所以我们无法直接使用第一级flop的Q来直接用于bclk时钟域。但是要注意,我们之前说过,第一级flop的Q会最终稳定下来的,而且在绝大多数时候,可以在一个bclk周期内稳定下来,这样第二级flop的D输入就是一个稳定的值,进而第二级flop的Q是满足clk-to-q的,没有亚稳态的产生。如上图所示,尽管bdata0产生了metastable,但是bdata是stable的。
那你可能会问,上一讲你不是说第一级的flop的输出可能需要很长时间才能稳定吗?你凭什么说在第二个时钟沿到来的时候bdata0就能稳定呢?如果还是不稳定,让第二级flop产生了setup/hold time violation,那第二级flop不还是有可能产生metastable么?
这是一个非常正确的问题,的确,double flop synchronizer不能完全消除亚稳态!但是很多有经验的工程师会告诉你,用个double flop synchronizer就够了,那是因为double flop会使得metastable产生的概率显著降低,这就又回到了我们上一讲的MTBF的概念。在使用double flop的时候,由于给了第一级flop一个周期的时间去稳定,使得两级发生metastable的概率大大降低。我知道大家都不喜欢看公式,下面给了一个100MHz时钟的例子,大家只需要关心一下最终的结果,是957亿年,而地球的年龄是46亿年,太阳的寿命是100亿年,也就是说,直到太阳毁灭,你也碰不到下一次metastable的产生。
但是注意,随着sample clock frequency的提升,以及工艺节点越来越小,有些时候打两拍已经不太够了,那么就简单粗暴来一个打三拍,就能够保证了。
意外出现:
不是说好了单比特信号用double flop么,肯定是哪里不对?老李仔细一看,这adata怎么在bclk的沿到来之前又变了呢,这样bdata0这个flop压根看不到adata的变化啊,看来double flop来做synchronizer是有条件的,不是随便什么单bit信号都可以直接无脑用!
其实使用double flop来同步,有个最基本的“3个沿”要求,就是source data必须保证稳定不变至少碰见destination clock 3个连续的沿,这个沿可以是上升沿也可以是下降沿,持续3个沿之后才能变,否则就有可能在destination clock domain根本看不到这个data的变化。例如下图所示:adata第一次变高,只碰到了bclk的2个沿,就可能导致bdata根本没有看到这个pulse,而第二次adata变高,持续了3个沿,这样bdata就能够确保也可以变高了。
所以如果bclk的频率是1.5倍的aclk频率以上,即使adata是aclk域的一个短pulse,也可以保证3edge要求。如果没有这样频率的关系,那就得对adata有要求了,adata的变化不能很迅速,要稳定足够长的时间,这样才不会让bclk域错过值,具体怎么做呢?在回答这个问题之前,我们应该先停一下,问自己一个问题:我是不是要将adata的每一次翻转都同步到bclk呢?
其实这个问题应该是在考虑要将一个信号跨到另一个时钟域的时候首先要问自己的问题。大多数情况下,回答都是肯定的,也就是说adata变化了,bclk这边的信号也要变化。但是也有些时候,即使漏了adata的变化,可能也没关系,这就是和设计的要求相关了.
如果必须要求adata的每一次翻转都同步到bclk,一个办法就是利用反馈。也就是说信号从aclk域同步到bclk域,再同步回aclk域。aclk的data只有看到同步回来的值之后才能再翻转。如下图所示
但是这样做的缺点也很明显,就是将aclk的data进行了扩展,两次的同步也增加了延时,这是为了达到每次变化都同步而付出的代价。大多数时候,设计者知道adata变化的频率很低,比如是一个软件配置位,配置好之后可能不轻易更改;比如是一个中断信号,中断发生之后可能需要很长时间才会被软件清除,这些时候就没有必要设计反馈电路了。
最后再讲两个知识点,也是非常关键的知识点。
第一,利用double flop,bdata发生变化可能是在adata翻转之后1个周期,也可能是2个周期,这是由于第一级flop的metastable可能会resolve在不同的值。如果第一级flop 稳定在和adata相同的值,那么就只需要1个周期就能看到bdata翻转。而如果第一级flop 稳定在和adata相反的值,那么则需要再多一个周期。所以在设计和仿真验证中,不能假定bdata一定会在2个周期之后发生变化,而是将这个因素随机在仿真中,有的时候真的会暴露出设计中的问题。
第二,我们说的单bit信号,有人可能会说,组合逻辑的输出可不可以用double flop呢?比如一个AND门的输出,不也是单bit吗?答案很简单,不可以。原因就是组合逻辑的输出可能会有毛刺,这些毛刺会增大第一级flop产生metastable的概率,进而影响整个synchronizer的MTBF。所以对于任何单bit信号,在跨时钟域之前一定要先寄存(flop),只有flop的输出才能经过synchronizer. 下面的图就是不flop的情形。
其实在下面这个例子中,adata在aclk域一个周期就翻转了,其实是aclk域的一个pulse信号。对于pulse信号,我们其实不应该用double flop来同步,原因很简单,就是上面所示的可能丢失pulse
pulse信号在设计当中很常见,通常在某个时钟沿变高,在下一个时钟沿变低。前面一个例子可见当aclk频率比bclk频率高的时候,adata变高一个周期,那么有可能bclk的时钟沿根本看不到这个变化,或者有时候即使能被bclk采到一次,也可能无法满足3个沿的要求,导致无法用常见的2flop synchronizer来去sync。
那你肯定就会问了,那如果bclk的频率比aclk高,比1.5还倍高(3个沿的基本要求),比如10倍,一个aclk周期内可以见到bclk的10个上升沿,那不是肯定满足3个沿要求吗?难道这种情况也不能用2flop synchronizer吗?在回答这个问题之前,我们首先要回答一个问题:
aclk时钟域的一个pulse,到了bclk时钟域内,应该是什么样的信号呢?
如果这个问题不好回答,那么换一个问法:
为什么要把aclk的pulse同步到bclk时钟域呢?
这才是触及灵魂的问题,对于普通的面试者,能够正确回答上pulse synchronizer当然不错,但是能够从根本上理解并且讲清楚pulse synchronizer存在的理由,那才是真正优秀的候选者。其实这个问题在工程师设计电路的时候也要自己问自己,当你无法给出确信的理由说你一定要把aclk的pulse同步到bclk时钟域,那你应该重新思考,也许你就会发现可能你真的不需要一个pulse synchronizer。
在电路设计中,经常我们需要设计一些pulse信号,比如说,有一个counter在不停地计数,每个周期加一或者减一,当counter的值等于一个特定的值的时候,我们就输出一个周期的pulse,可以用这个pulse来作为使能信号(enable)来做其他的事情,例如去set或者clear某个寄存器。反过来,也有可能用这个信号去作为某个counter增加1或者减去1的条件。再比如说,需要对memory进行读操作或者写操作,现在memory通常都有两个使能端口CE(Chip Enable)和WE(Write Enable),CE和WE同时为1的周期表示要对memory进行写操作,CE为1但WE为0的表示要对memory进行读操作。再比如对于一个FIFO,push为高一个周期就表示给FIFO加入一个数,pop为高一个周期就是表示给FIFO减去一个数。以上这些例子,都说明了当这些信号为高时,就要有相应的操作发生,为高一个周期,就操作一次,再次为高时,就需要再操作一次,这是和另外一些状态信号(status signal)的差别。对于那些状态信号,它们为高或低只表示一种状态,而与它们为高为低经过了多少个时钟周期没有关系。我们在上一讲说到的用2flop来同步的单bit信号,几乎都是针对的那些状态信号。而对于active时需要进行相应操作的信号来说,很显然由于2flop synchronizer的限制,adata同步到bclk时钟域就无法保证持续相应的周期数,这里可能是最开始的例子bclk连一个cycle的pulse都没有,也可能是持续了多个cycle,自然不能用2flop synchronizer了。
我们现在可以回答之前提出的问题了,当我们要同步aclk时钟域的一个单周期的pulse到bclk时钟域时,我们期望bdata是什么样呢?答案就是,bclk时钟域也是单周期的一个pulse。
那么如何克服2flop synchronizer的问题呢?咱们来看,由于pulse只持续一个周期,2flop synchronizer可能会miss掉pulse,那我们想个办法让产生过pulse这个之前发生过的事件记录在那里不就好了吗?这就是破解这个问题的思路:
将aclk时钟域的pulse信号转为一个level信号
用2-flop synchronizer来同步这个level信号
在bclk时钟域将同步过来的level信号转化为pulse
好了,废话不多说,直接上图
其中的关键就是图中所示的Toggle Flop。可以看到当输入是一个单周期的pulse时,里面Toggle flop的输出只会翻转一下。这样我们就把一个pulse转化成为了level,这个level信号直到下一个pulse来之前都是稳定的,于是我们就可以利用2flop synchronizer来将这个level信号同步到bclk时钟域,然后我们再借助一个XOR和一个flop来重新创造出一个pulse。
如果你面试的时候已经能够正确回答出上面的电路图,在面试官心里你已经及格了,但是距离完胜其他求职者还要再深入思考一些。
我们继续看,既然这个pulse synchronizer中间利用了2flop,那么2flop的3edge要求就必须要满足,换句话说,我们转化成为的level的信号Tq要足够长。如果Tq不满足bclk的3edge要求,那么这个level信号我们就无法同步过去,也就无法产生bclk的pulse了。而Tq每次变化是由于aclk来了一个新的pulse,这也就是要求aclk的连续两个pulse之间的间隔要足够大,要满足bclk的3edge要求。
如果你回答出来了这个pulse synchronizer的局限性,那么面试官很可能会接着问,如果我不知道下一个pulse是什么时间来怎么办呢?老李当年还就真被这么问到过,把老李差点整懵逼了。我们先来看,aclk时钟域最接近的两个pulse能靠多近呢,显然就是两个pulse中间只有一个aclk周期,这其实就是将aclk进行了2分频。那么相应的,Tq就是对aclk进行了4分频,每个Tq的level持续时间是2个aclk cycle,这2个cycle需要满足bclk的3edge要求,如果满足,那么就可以保证bclk域也可以产生每个cycle的pulse的要求。
如果无法满足,那么我们就要另想办法,如果还是要使用pulse synchronizer这个电路,我们继续祭出反馈大法
本质思路就是我们不能让aclk域的pulse产生得很快,而是要等到pulse同步到bclk之后才能继续产生下一个pulse,于是我们要将b_p重新synchronize回到aclk域,作为放行下一个pulse的条件。
可是这个反馈的办法要求每个pulse产生都要同步回来,这样做的效率不高,有没有更快的办法呢?我们想象有一个细长的管子,管子的一头我们往里面塞玻璃球,每塞进去个玻璃球对应一个aclk时钟域的pulse,管子的另一头我们往外弹玻璃球,每弹出一个玻璃球对应一个bclk时钟域的pulse。(哎,是不是嗅到了一点FIFO的味道了?)那么除非假定管子无限长,那么我们往管子里塞入球的平均速率不能永远大于从管子里弹出球的速率,我们可以允许某一段时间塞入球的速率高,管子作为一个缓冲区,来容纳之前塞入但是还没有弹出的球,但是当管子塞满的时候,我们必须得停下塞入球的动作,等待弹出球的那边弹出球之后才能腾出空间来继续塞。也就是说,当管子不是无限长时,尽管两边的时钟速率不同,能够保持一一对应的条件是两边塞球弹球的平均速率是一样的。这就是利用FIFO来解决的思路。如果FIFO告诉aclk说FIFO满了,那么aclk域就得停止产生pulse,如果不满,就可以继续产生pulse,最后我们把FIFO里面的元素完全弹完,就可以做到一一对应了。