本文主体翻译自C. E. Cummings and S. Design, “Simulation and Synthesis Techniques for Asynchronous FIFO Design 一文,添加了笔者的个人理解与注释,文中蓝色部分为笔者注或意译。前文链接:
FIFO样式#1的框图如图5所示:
为了便于对风格#1的FIFO设计进行静态时序分析,该设计被划分为以下6个Verilog模块,具有以下功能和时钟域(这部分我们只是进行一个简单的介绍,具体的实现方式将在文章的后面部分带来):
(参见第6.1节中的示例2)-这是包含所有时钟域的顶层封装模块。顶层模块仅用作一个封装来例化在设计中使用的所有其他FIFO模块。如果将这个FIFO用作更大的ASIC或FPGA设计的一部分,那么这个顶层封装模块可能会被丢弃,以允许将其他FIFO模块分组到它们各自的时钟域中,以改进综合和静态时序分析。
(参见第6.2节中的示例3)-这是由写和读时钟域同时访问的FIFO内存缓冲区。这个缓冲区很可能是一个实例化的同步双端口RAM。其他内存样式可以适应作为FIFO缓冲区。
(参见第6.3节中的示例4)-这是一个同步器模块,用于将读指针同步到写时钟域。同步读指针将被wptr_full模块用来生成FIFO满条件。此模块只包含与写时钟同步的触发器。此模块中不包含任何其他逻辑。
(参见第6.4节中的示例5)-这是一个同步器模块,用于将写指针同步到读时钟域。rptr_empty模块将使用同步的写指针来生成FIFO为空的条件。此模块只包含与读时钟同步的触发器。此模块中不包含任何其他逻辑。
(参见第6.5节中的示例6)-此模块与读时钟域完全同步,并包含FIFO读指针和空标志逻辑。
(参见第6.6节中的示例7)-此模块与写时钟域完全同步,并包含FIFO写指针和满标志逻辑。
为了使用这种FIFO设计方式能够执行FIFO full和FIFO empty测试,必须将读写指针传递到相反的时钟域以进行指针比较。与其他FIFO设计一样,由于这两个指针是由两个不同的时钟域生成的,因此这些指针需要“安全地”传递到相反的时钟域。本文所展示的技术是同步格雷码指针,以确保一次只有一个位可以改变。
以下的所有部分非原文内容,为笔者自行补充。
在本文中使用到了格雷码指针,实际上在如今大量的异步FIFO的设计中,都会使用格雷码来替代二进制码来实现FIFO的指针,但是具体的原因在原论文中没有详细展开,只是简单提到了确保只有一个指针位被改变,这里我们进行一个简单的说明,为什么异步FIFO的设计一般会采用格雷码的形式来实现指针。
我们知道了判断FIFO的空、满需要将读写指针分别同步,而跨时钟域传输的一旦没处理好就会引起亚稳态问题,造成指针的值异常,从而引发FIFO的功能错误。那么应该如何将读写指针同步到对方的时钟域呢?
由于两个指针所处的时钟域不同,且彼此异步,在使用二进制计数器实现指针的时候,就会导致用于比较的指针值取样错误,例如:二进制指针FFF->000。这时所有的位都会同时发生改变,虽然能够通过同步计数器避免亚稳态,但是仍然可能得到极其不相关的取样值,所以同步计数器不是最终的解决方案。
从FFF->000的可能情况有:
如果同步时钟沿在FFF->000的中间到来,就可能将3位二进制数的任何值取样并同步到对侧时钟域,这就会使得FIFO同步后的读/写指针错误,从而导致FIFO的空/满信号错误,最终导致FIFO运行异常。
实现FIFO指针的一种方式是使用格雷码,如下图所示:
格雷码的一大优势是在从一个数变化到相邻的另一个数的时候,只有一个位发生变化,也就是一种单位间距码,因此,在发生转换时,最多出现一位的错误,例如,计数器从“1010”变化到 "1011" ,取样值只可能是“1010”或者“1011”。即便没有采集到变化后的1011,也会采集到变化之前的1010,这就避免了由于采样指针错误导致的空/满信号错误的可能性。