跨时钟域处理--最终详尽版
为了彻底理解跨时钟域问题,多方搜集资料,做个简单整理备忘。主要参考了如下几个资源:
- https://zhuanlan.zhihu.com/p/45186793 跟IC君一起学习集成电路
- https://www.cnblogs.com/PG13/p/10329678.html 新芯时代
- 格雷码 https://www.cnblogs.com/zhuruibi/p/8988044.html
- 主要参考来源:“ASIC 中的异步时序设计” 王夏泉 华中科技大学
- 主要参考来源: "Clock Domain Crossing (CDC) Design & Verification Techniques Using SystemVerilog"
- https://blog.csdn.net/lovingduo/article/details/77723219
1. 异步时序定义
异步时序设计指的是在设计中有两个或以上的时钟, 且时钟之间是同频不同相或不同频率的关系。而异步时序设计的关键就是把数据或控制信号正确地进行跨时钟域传输。
2. 亚稳态
每一个触发器都有其规定的建立(setup)和保持(hold)时间参数, 在这个时间参数内, 输入信号在时钟的上升沿是不允许发生变的。 如果在信号的建立时间中对其进行采样, 得到的结果将是不可预知的,即亚稳态。
触发器进入亚稳态的时间可以用参数 MTBF(mean time between failures)来描述, MTBF即触发器采样失败的时间间隔,其公式描述如下:
其中:
- \(t_r\) = 分辨时间(时钟沿开始)
- \(\tau,T_0\) = 触发器参数
- \(f\) = 采样时钟频率
- a = 异步事件触发频率
通常,MTBF越大说明系统采样失败的可能越小。可见,对于高速的设计,MTBF是更容易发生的。对于一个典型的 0.25µm 工艺的 ASIC 库中的一个触发器,我们取如下的参数:
tr = 2.3ns, τ = 0.31ns, T0 = 9.6as, f=100MHZ, a = 10MHZ, MTBF = 2.01 days
也就是说触发器以100MHZ工作,假设异步事件触发的频率,也就是数据变化的频率跟采样时钟频率相同。我们用10MHZ的频率去不停地采它的数据,每个上升沿数据都会发生变化,则每两天就可能采集到一次亚稳态(个人理解,如有误请指正)。如果使用单锁存器同步,b的时钟上升沿采集a的数据时很可能采到亚稳态数据。
3. 单比特同步策略
方法一:双锁存器
为了避免亚稳态,应该使得MTBF尽量大。采用双锁存器可以改善这一问题:
当使用了双锁存器以后, b_dat2 的MTBF由以下公式可以得出:
如果我们仍然使用上一节所提供的参数,则b_dat2 的MTBF为 9.57*109(years)。由上述结果可以看出,双锁存器法可以消除亚稳态问题。
注意问题1
时钟域B两级同步的寄存器跟时钟域A的输出寄存器之间不能有组合逻辑。组合逻辑电路各个输入信号的不一致性以及组合逻辑内部路径的延时时间不一样,运算后的信号存在毛刺如图(2),我们无法预先知道CLKB 的上升沿何时会到来,CLKB 采样到的信号就无法预知。
因此,要想CLKB 能采到稳定的信号,时钟域A的信号必须是经过CLKA 敲过,在一个时钟周期内是稳定的信号,如图(3)所示:
注意问题2
Clock-gating enable 信号没有经过异步处理:
在下图中a_in 信号经过CLKA的DFF敲过,再送到两级DFF 同步器处理,完全没毛病。但是F2的使能信号EN是从时钟域A来的,当EN信号变化的时候,由于时钟域不一样,无法保证使能之后的CLKB信号采样数据时满足setup/hold time 要求,这时F2输出信号也就变得无法预测了。因此对clk gating的信号也要做处理。
注意问题3
如果较快的时钟域是较慢时钟域频率的1.5倍(或更多),那么将较慢的控制信号同步到较快的时钟域通常不是问题,因为较快的时钟信号将采样较慢的CDC信号一次或多次。因此将快采慢比慢采快引起的潜在问题更少,所以对于快采慢的情况,使用简单的两个触发器同步器在时钟域之间传递单个Clock Domain Crossing (CDC)信号即可。
而对于慢采快的情况则最好更加稳妥些,一般要求在接收时钟域中采样信号要保持三个时钟上升沿的时间("three edge" requirement),也就是1.5倍的采样时钟周期。一般来说1.25倍也够。
如果采样信号维持时间过短,则慢时钟域很可能会漏采:
解决方案:
1.开环地控制信号的长度
2.闭环控制
采用反馈信号自动延长快时钟域的信号:
扩展*
同样一个快时钟域的脉冲信号,其宽度只有快时钟域的一个周期的长度,现在需要将该信号同步到一个相对较慢的时钟域,如果要求不能在快时钟域将脉冲信号展宽,并且同样采用双寄存器同步,该如何实现?
下面的这个电路可以解决这一问题:
4.多比特同步策略
经常会遇到多个比特的控制信号或者数据信号跨时钟域传输的问题,解决这类问题的出发点主要有:
- 尽可能将这些信号合并成单bit
- Multi-cycle path (MCP). 使用同步信号。
- 使用Gray码
控制信号多比特同步
同步变化的控制信号
例如下面的例子,load和en作为两个控制信号需要跨时钟同步。两个信号可能需要同时拉高才能正确操作,但是实际同步时可能两者存在一定skew,导致再同步域两级reg同步后两个信号错开,导致控制失败:
这种解决办法也很简单,只要将他们合并成一个即可:
控制信号多比特之间有一定时钟相位差
这种情况下,如果两个信号隔开固定的相位,在传输过程中可能会有一定偏移,则同步时也会导致下面的问题:
数据多比特同步
在数据的多比特传输时更要注意skew的问题,如果bit之间有skew,则传输值很可能出错:
方法一:脉冲同步法(开环的结绳法)
首先用到的一个电路是脉冲产生电路:
一个典型的脉冲同步器用于信号同步的例子:
或者还有另一个版本更加清楚一些:
脉冲同步器的工作方式如下:
- 在A时钟域产生一组单周期脉冲,其间隔至少需要比B时钟域的两个时钟周期大,否则B中无法进行边沿检测。同时这组脉冲也表示了A中数据传送的开始和结束。
- 在A时钟域中,两个脉冲被电平翻转器(可以由异或门或者mux+inv构成)将脉冲之间的区域变为一段高电平(结绳toggle)
- A中的结绳信号在B中通过边沿产生器翻译为两个单周期脉冲
- B中需要由逻辑去监测脉冲并进行信号的采样
仿真案例:
方法二:闭环结绳法
上面的脉冲同步信号实际上是开环产生的,需要考虑B时钟域的周期宽度。而下面这种闭环方式解决了这个问题:
其具体原理是:
- 首先,asend生成一个脉冲,经过结绳、解绳到B中变为另一个脉冲
- 该脉冲反馈回A中通过边沿采样变回A中的单周期脉冲(因为这边讨论的是慢采快,所以A总能采到B的单周期脉冲)
- 一个简单的FSM接受反馈的脉冲并给出ready信号,表示单次采样结束,A模块可以改变adata的数据了,同时控制asend信号给出一个新的脉冲,代表单次传输结束,结绳解绳到B中后B也接受到了结束脉冲。
上面的这种方法还是不够完备,因为反馈信号也是单周期脉冲,在上面第二步中可以发现,如果A的频率比B慢,采不到脉冲岂不是僵硬,所以为了通用,慢采快还是快采慢都能适配,干脆在B中也加入A的的结绳以及FSM来控制整个流程,原理图如下:
可见,B中的状态机接收到脉冲后输出valid信号告诉外面B可以采数据了,bload置高,数据开始采样,之后b_ack置高,送入A中产生结束脉冲,结束脉冲返回B中后FSM跳回not valid状态,一次传输结束。
下面是上面的简陋版本:
其中标明_clk1 的信号表示该信号属于 clk1 时钟域, 同理标明_clk2 的信号表示该信号属于 clk2 时钟域。在两次src_req_clk1 之间被 src_vld_clk1“ 结绳” (pluse2toggle),在将src_vld_clk1 用双锁存器同步以后, 将该信号转换为 dst_req_clk2(toggle2pluse)。同理,用dst_vld_clk2 将 dst_req_clk2“结绳”, dst_vld_clk2 表明在 clk2 时钟域中, src_dat_clk1 已经可以进行正确采样了。 最后将 dst_vld_clk2 转换为 dst_ack_clk1(synchronizer and toggle2pluse), dst_ack_clk1 表明 src_dat_clk1 已经被 clk2 正确采样了, 此后 clk1 时钟域就可以安全地传输下一个数据了。 可以看出,“结绳法” 关键是将信号结绳以后, 使其保持了足够长的时间,以便另一个时钟可以正确地采样。
电路图如下:
上图描述了握手协议的完整流程,其中三角带横线的符号是异或门。同时给出了两个脉冲之间结绳信号(vld信号)的产生方法。
总之:
- 优点:“结绳法” 可以解决快时钟域向慢时钟域过渡的问题, 且其适用的范围很广。
- 缺点:实现较为复杂,特别是其效率不高,在对设计性能要求较高的场合应该慎用。
方法三:异步双口RAM+格雷码(异步FIFO)
处理多bit数据的跨时钟域,一般采用异步双口RAM。假设我们现在有一个信号采集平台,ADC芯片提供源同步时钟60MHz,ADC芯片输出的数据在60MHz的时钟上升沿变化,而FPGA内部需要使用100MHz的时钟来处理ADC采集到的数据(多bit)。
在这种类似的场景中,我们便可以使用异步双口RAM来做跨时钟域处理。先利用ADC芯片提供的60MHz时钟将ADC输出的数据写入异步双口RAM,然后使用100MHz的时钟从RAM中读出。
但我们读出RAM中的数据时,肯定不是一上电就直接读取,而是要等RAM中有ADC的数据之后才去读RAM。这就需要100MHz的时钟对RAM的写地址进行判断,当写地址大于某个值之后再去读取RAM。
在这个场景中,其实很多人都是使用直接用100MHz的时钟对RAM的写地址进行打两拍的方式,但RAM的写地址属于多bit,如果单纯只是打两拍,那不一定能确保写地址数据的每一个bit在100MHz的时钟域变化都是同步的,肯定有一个先后顺序。如果在低速的环境中不一定会出错,在高速的环境下就不一定能保证了。所以更为妥当的一种处理方法就是使用格雷码转换。
格雷码简介
在一组数的编码中,若任意两个相邻的代码只有一位二进制数不同,则称这种编码为格雷码(Gray Code),另外由于最大数与最小数之间也仅一位数不同,即“首尾相连”,因此又称循环码或反射码。格雷码(Gray Code)又称Grey Code、葛莱码、格莱码、戈莱码、循环码、反射二进制码、最小差错码等。
格雷码有多种编码形式
十进制数 | 4位自然二进制码 | 4位典型格雷码 | 十进制余三格雷码 | 十进制空六格雷码 | 十进制跳六格雷码 | 步进码 |
---|---|---|---|---|---|---|
0 | 0000 | 0000 | 0010 | 0000 | 0000 | 00000 |
1 | 0001 | 0001 | 0110 | 0001 | 0001 | 00001 |
2 | 0010 | 0011 | 0111 | 0011 | 0011 | 00011 |
表中典型格雷码具有代表性。若不作特别说明,格雷码就是指典型格雷码,它可从自然二进制码转换而来。
二进制格雷码的生成方法有很多,具体可自行搜索或见:
https://www.cnblogs.com/zhuruibi/p/8988044.html
回到刚才的问题,多比特利用双寄存器打两拍在高速场合不再适用,而使用格雷码可以将这种多比特变为单比特传输(格雷码每次变化只有一位会变)如果先将RAM的写地址转为格雷码,然后再将写地址的格雷码进行打两拍,之后再在RAM的读时钟域将格雷码恢复成10进制。这种处理就相当于对单bit数据的跨时钟域处理了。
异步FIFO
使用异步双口ram的场合其实用异步fifo也是一样的。
使用场景:在有大量的数据需要进行跨时钟域传输, 并且对数据传输速度要求比较高的场合 。
一个异步 FIFO 一般由如下部分组成:
1. Memory, 作为数据的存储器;
2. 写逻辑部分,主要负责产生写信号和地址;
3. 读逻辑部分,主要负责产生读信号和地址;
4. 地址比较部分,主要负责产生 FIFO 空、满的标志。
异步FIFO代码可以参考我的另一篇文章:
https://www.cnblogs.com/lyc-seu/p/12439203.html#示例代码
方法四:二深度FIFO同步器
二深度FIFO的地址实际上只有单bit,所以不需要进行gary编码,所以异步FIFO中的编码模块可以简化:
其中的gray2bin,bin2gray都可以省略,并且空满信号的产生之间通过同或门和异或门即可产生。
限制: 由于深度只有2,所以只适合某些特定场景(突发传输要求FIFO最小深度小于2的场景)
方法五:DMUX同步器
对于多bit的data信号,还可以使用使能技术,也就是通过一个使能信号来判断data信号是否已经稳定,当使能信号有效的时候说明data处于稳定状态,在这种情况下终点寄存器才对信号进行采样,可以保证没有setup/hold违例。而使能信号一般使用double FF的方法来进行同步。下面是DMUX的同步示意图:
来源:oschina
链接:https://my.oschina.net/u/4271269/blog/4316117