这些天我调试的一个系统就因为毛刺的问题出现了不稳定现象。对于毛刺我是很小心的,专门对这个18位的计数器q值进行了两级同步。但是今天早晨用 signal tap抓到了由于两级同步造成的“毛刺放大”现象。图形如下:
图中dma_req_end_d2信号与counter|q[7:0]信号(只对18位计数器的低8位信号进行了采样,在下面的代码中对应 dma_req_counter)的关系如下: riple
wire dma_req_end = (dma_req_counter==18'd0)?1'b1:1'b0;
always @(posedge clk)
begin
dma_req_end_d1<=dma_req_end;
dma_req_end_d2<=dma_req_end_d1;
end
wire dma_req_end_filtered = dma_req_end && dma_req_end_d2;
由上面的代码可知,只有当dma_req_counter计数到全0时,组合逻辑dma_req_end才应该输出一个高电平,dma_req_end_d2信号是对dma_req_end信号进行了两级同步得到的。
初看起来,两级同步是满可以把毛刺滤除的,也是跨时钟域的典型处理方法。但是明眼人会看出,如果同步时钟的采样沿恰好命中一个毛刺,而且毛刺的宽度和位置恰好满足建立保持时间的话,那么在第一级同步时就会采样到一个毛刺。毛刺毕竟是毛刺,这个毛刺在第一级同步的下一个采样时钟就消失了。这样,第一级同步的输出就会是一个完整的脉冲。这个脉冲可以很好的满足第二级同步的建立保持时间,于是这个被放大的毛刺就传递到了第二级同步的输出端。
在上面的波形中,q值由10跳转到0f过程中不可避免的会产生00毛刺(10->00->0f),这个毛刺就被上面代码中的两级同步放大成了 dma_req_end_d2的输出脉冲。这个脉冲引发状态机的异常跳转,进而引发bug。由于我采用的采样时钟相对较慢,真正的毛刺在上面的波形中是看不到的。但是通过代码是可以分析出毛刺和异常脉冲之间的关系的。
这样看来,逐级同步非但不能滤除毛刺,在绝大多数情况下还会放大毛刺,起到了推波助澜的作用。
那么,对于这种毛刺就一定不能滤除吗?(这样的毛刺一定要滤除,否则被状态机的跳转逻辑采样到也会产生同样的问题。)同步方法真的不可取吗?答案是否定的。大家看一下代码的最后一行,这一句是我找到bug后添加进去的。这句话可以有效地起到滤除毛刺的作用,最大可以滤除长度为一个同步时钟周期的毛刺。
滤除的原理是把原始不干净的信号和此信号延时后的信号相与,如果二者都是1的话,才能产生结果为1的输出。对毛刺的滤除效果取决于延时的长度。当然,也可以把第一级同步和后几级同步的信号相与,效果是相同的。
这种滤除毛刺的方法利用了毛刺和稳定信号在持续时间长度上的差别。换句话说,毛刺的频率通常会高于稳定信号的频率,如果构建一个合适的数字滤波器,就可以把毛刺对应的频率滤除,得到干净的稳定信号。当然,代价是引入了一定的延时。
今天发现上面的文字有一些不甚严密的地方:
1. 被第一级同步采样到的毛刺不一定要满足第一级同步寄存器的建立保持时间才会被放大。如果这个毛刺不满足建立保持时间的话,第一级同步寄存器的输出会进入亚稳态(metastable),第二级寄存器对这个亚稳态采样会得到一个不确定的稳定状态(也有极小的可能性进入一个新的亚稳态,概率比第一级进入亚稳态小得多了)。这就是消除亚稳态的措施遵循的原理:第二级进入亚稳态的可能性要比第一级小得多,所以第二级的输出可以认为是稳定的。但是需要注意的是,第二级的输出是稳定的,但取值是不确定的;只有在第一级的输出退出亚稳态后,在第二级才能得到稳定且确定的输出。
这个输出可能是我希望的0状态,也可能是我不希望的1状态。如果是1状态,那么就会产生一个稳定的脉冲输出。
实际上,完全消除亚稳态是不可能的,只能采取上面这样的措施使亚稳态发生的概率减小至可以忽略的程度。亚稳态是个杀不死的幽灵。
2. 把原始信号与两级同步的输出信号相与不能滤除所有形式的毛刺。假设第一个毛刺被两级同步放大成脉冲,而在第三个时钟上升沿处原始信号又出现一个毛刺,那么两者相与的结果就是一个新的毛刺,这个毛刺被后面的逻辑采样到的话,就会引发新的问题(毛刺放大或者是亚稳态传递)。
所以,上面用于滤除毛刺的代码是建立在某种假设上的,我的假设是在第三个时钟上升沿处不会出现新的毛刺或者跳变沿。这个假设在本例中是成立的:计数器时钟周期是同步时钟的10倍。第一级同步采样到毛刺后,9个时钟周期内都不会采样到新的毛刺或跳变沿;第一级同步采样到毛刺时,之前的9个时钟周期内都没有毛刺或跳变沿。也就是说,毛刺的持续时间不会超过1个本地时钟周期,出现的周期最短是10个本地时钟周期,符合假设。
用这种方法可以起到跨时钟域同步和滤除毛刺的双重功能。
3. natived朋友说得很对,优化逻辑消除不稳定因素是根本。如果把上面的计数器改为格雷码计数器可以有效地避免毛刺的产生。 riple
natived朋友担心采用上面的方法会造成降频,其实不必。
两级同步是跨时钟域信号处理的常用方法,不会造成系统降频。
当组合逻辑过于复杂以至于在一个时钟周期内不能稳定下来时,常可以采用“pipelining”和设计“multicycle路径”两种方法应对。这两种方法都通过引入局部延时避免了系统降频。甚至是提高系统频率的好方法。
所以,设计合理的局部延时不会造成系统降频。
我这个设计确实存在跨时钟域的问题:计数器的时钟与同步的时钟不同,周期是同步时钟的10倍。跨时钟域通常采用两级同步的方式避免产生亚稳态,这也是上面代码的初衷。我认为,跨时钟域处理可以在计数器之前进行(把计数器的时钟同步到后级时钟),也可以在计数器之后进行(就像上面代码中那样)。至于哪种更好,还请大家批评。