本文主要讲解了格雷码在异步FIFO中的使用,与其他文章不同,本文不仅分析了使用格雷码的原因,也解释了格雷码在读写时钟频率差异较大时的传输情况,即一些文章中提到的多bit跳变问题。
本文要求读者已经了解格雷码和二进制码的关系,以及互相转换的方法。
1. 异步FIFO跨时钟域产生的问题
当我们创建异步FIFO的时候,跨时钟域是一个无法避免的问题,为了正确的在不同时钟域传递地址指针,用于空满判断以及实时水线计算,通常我们会采用寄存器打拍和格雷码编码两种手段降低亚稳态的影响。为什么要使用格雷码而不采用常规的二进制编码?简单的讲,就是格雷码每个时钟周期只会变化一位。而二进制码会在一个周期内变化多位。但是深究一下,为什么要求只变化一位呢?或者说,普通二进制变化多位会产生什么影响呢?
如上图所示,FIFO的满信号由写入控制模块产生,空信号由读取控制模块产生。读控制模块把当前时刻的读地址传递给写模块,但是由于中间传递需要时间,写模块接收到的读地址永远都是小于或者等于当前真实的读地址的。同时,由于FIFO中的数据个数永远是大于等于0,也就是说满足如下关系:
当前真实的写地址 ≥ 当前的读地址 ≥ 接收到的读地址
按照正常的思维,无论你使用什么进制传递数据,上面的不等式在任意时刻都应该成立。但是在真实的传输过程中,地址每个bit位由于和上级寄存器之间的走线距离不一样,翻转的速度不一样,在跨时钟采样时可能发生错误。
假设现在需要将 a时钟域的地址 dat 同步到 b时钟域上,dat是多bit位数据。设dat上一时刻为1111,下一时刻将变为0000。由于每个bit位翻转的速度有差别,而dat的四个数据位并不一定是同时翻转的,有可能是存在1111->0111->0001->0000这个过程。dat通常只能保证第一个a时钟的上升沿是1111,在下一个a时钟的上升沿到来时,已经稳定在0000。
由于a_clk和b_clk之间相位、频率不一定相同,b时钟的上升沿可能在两个a时钟上升沿之间到来,此时dat不一定已经稳定,就会发生采样错误,最左侧的寄存器采样到了0111,并传递给下一个寄存器。虽然可能在下一个b时钟上升沿到来时能够正确采样,但是这个错误值已经被传递到另一端了。
如上图所示,虽然addr满足在下个clk_r上升沿完成从1111跳变到0000,但是clk_w对addr的采样可能像addr'所示那样,中间出现一个异常值,这个异常值不满足 当前真实的写地址 ≥ 当前的读地址 ≥ 接收到的读地址这个不等式。进而影响另一端的空满状态判断,以及实时水线等,例如空满同时拉高,空满未告警,读写溢出等等。
具体来讲,如果中间的寄存器采样到了0001,并将地址传递给了读模块,会导致传递值比期望值大,这就会造成空满异常。比如明明读地址还很小,传了个大数,让写模块以为还可以继续写入,这就造成了溢出。
2. 为什么需要使用格雷码
格雷码一次只变化一位,即使采样错误,也只是没有更新当前的数值,格雷码在初始值和最终值之间没有中间状态,能够保证变化前后传输地址之间的大小关系不会突变。原本传输0001,结果传输了0000,这并不会影响FIFO的功能,只会让FIFO提前判断空满。
如上图所示,使用格雷码的话前后数据只有一位的差别,从0001->0000这个过程没有中间状态,不存在某个采样点出现不满足不等式的异常值。(大不了接收到的地址比当前的地址更小)
3. 读写时钟差异过大时,格雷码还有用吗?
这个话题来源于CSDN的几篇博客中的讨论:
异步fifo 读写时钟差别太大问题 (首先这个设计是不合理的)_异步fifo读写时钟相差太大-CSDN博客
关于FIFO的一些问题_异步fifo读写时钟相差太大-CSDN博客
他们提到的一个观点是,快时钟域的地址被慢时钟采样时,两个采样点之间可能有多bit变化,体现不出来格雷码的优点。具体意思是,设0时刻写地址为0000(0),读时钟比写时钟慢很多倍,可能等到采样时,写地址已经变化到了0011(2)。在这个过程中,即使使用的格雷码传递地址,也会发生多bit的跳变,和二进制的情形一样了,事实是这样吗?
我认为不是。
与上述二进制地址不同,格雷码每次的变化路径都是已知,中间的每个状态都是可以确定的。例如,从中间的寄存器来看,似乎数据从000直接变化到了011,在变化的过程中可以有很多情况。但事实上,可以选择变化的地址只有000-001-011这其中的一个,在任何时刻都不可能出现采样到010的这种情况,因为在任何一个时钟周期,第1个寄存器的输出只有两种可能,没有任何时刻会出现010的状态。格雷码每一次变化都是当前状态已经稳定了的情况。因此,无论何时采样,1中的不等式恒成立。
但是读写时钟差异过大,会对FIFO的深度有要求。如果深度太浅,会出现空满同时拉高的情况。例如写时钟很快,当FIFO是空的时,空信号拉高。如果此时开始写入数据,写地址还没来得及同步到读时钟域上作比较,把空信号拉低,而在写侧实时的写地址已经到了FIFO深度,满信号也会拉高,FIFO出现同时空满的异常状态。
4. 只要使用格雷码就万无一失吗?
是不是只要使用了格雷码传递地址,FIFO就能正常工作呢?
2中提到了一个容易忽略的关键点,就是格雷码要求要更新状态时,上一次的状态已经稳定。如果说格雷码的某一bit位路径时延特别大,其他位都要进行下一次变化了,上一时刻的状态值还没更新过来,这就会出现问题。
因此在设计异步FIFO的时候需要对格雷码的路径进行时序约束,传播的最大时延应该小于一个本时钟域的时钟周期。
set_max_delay [-datapath_only] -from [ node_list] -to [node_list] delay_value
一般而言,设计可以留一些裕量,有的公司会设置为周期的一半。
实际上也可以不这样约束,我们约束格雷码的初衷其实是为了对齐格雷码的各个bit位,并不要求整体的传播延迟大小,而是最大延迟和最小延迟之间的差别,因此在上述约束条件不过的情况,可以同时约束max_delay和min_delay,约束条件可以放宽,只需要保证差值小于源时钟域的一个时钟周期。