异步FIFO(一)-FPGA常见问题社区-FPGA CPLD-ChipDebug

异步FIFO(一)

在进入主题前,先介绍两个概念,亚稳态和格雷码

亚稳态的介绍:

对于采样电路中,一个信号在过渡到另一个时钟域时,如果仅仅用一个触发器将其锁存,那么采样的结果将可能是亚稳态。因为触发器存在建立时间和保持时间(数字电路中0和1之间的相互转化不是理想的一瞬间),一旦违反这个建立时间和保持时间,那么触发器就可能会处于亚稳态,所谓的亚稳态就是既不是0也不是1的状态,那么因为亚稳态的原因很可能产生设计错误。亚稳态是不可避免的,这是器件的固有属性,并没有理想器件。但是我们可以减少亚稳态的发生和传播,避免亚稳态带来的消极影响,例如使用以下的方法:

1.  使用同步器:也就是我们常用的2级或者多级FF打拍的方法;

2.  降低频率:如果能满足功能要求,那么降低频率能够减少亚稳态的产生;

3.  避免变化过快或者过于频繁的信号进行跨时钟采样;

4.  采用更快的触发器:更快的触发器,也可以减少亚稳态的产生。

很多时候器件基本是固定的属性无法改变,而2和3往往是设计性能的要求,那么避免亚稳态常用的就是方法1即使用两级触发器来减少亚稳态的发生,当然请注意打两拍(两级触发器的方法)仅适用于单比特的数据传输,多比特的数据传输会在后续讲解。

图片[1]-异步FIFO(一)-FPGA常见问题社区-FPGA CPLD-ChipDebug

 

如上图

触发器FF1对异步信号a进行采样,产生输出信号aw。由于FF1进入非法状态的概率非常大,信号aw是不安全的,为了保护系统的其余部分免受不安全信号的影响,等待一个时钟周期来让FF1的非法状态衰减,然后再用FF2对aw进行采样,产生输出as。这种进入非法状态的概率是存在计算公式的,在打两拍之后进入亚稳态的概率明显降低很多,大家只需要知道这个概念,不需要知道计算公式,感兴趣的可以查找相关书籍。上面讲到这里的打两拍仅仅适用于单比特数据,但实际中,我们可能在跨时钟域传输N比特,这又应该怎么办?(FIFO同步器)

先介绍一下格雷码:在数字系统中,常要求代码按一定顺序变化。例如,按自然数递增计数,若采用8421码,则数0111变到1000时四位均要变化,而在实际电路中,4位的变化不可能绝对同时发生,则计数中可能出现短暂的其它代码(1100、1111等)。在特定情况下可能导致电路状态错误或输入错误。使用格雷码可以避免这种错误(格雷码的数据变化仅有一位,下面的介绍会解释清楚)。

二进制转格雷码:从最右边一位起,依次将每一位与左边一位异或(XOR),作为对应格雷码该位的值,最左边一位不变。(异或运算:相同为0相异为1)

图片[2]-异步FIFO(一)-FPGA常见问题社区-FPGA CPLD-ChipDebug

图片[3]-异步FIFO(一)-FPGA常见问题社区-FPGA CPLD-ChipDebug

从图中可以看出,五位的二进制数转换为格雷码可以避免在数据变化时同时存在多位改变,即信号一次只能改变一次,那么就减少了亚稳态的概率。

下面进入正题:

在多时钟域系统的设计中,跨时钟域的设计在所难免,尤其是在设计模块和外围芯片通 信中出现。当数据从一个时钟域向另一个异步时钟域传递的过程中,由于不同速率器件间的速率匹配问题,一般不能够通过直接传递保证数据传输的正确、完整性,故需要有一个中间 数据缓冲来保证数据能够准确无误的传递。FIFO(FirstIn First Out)是一种队列,数据从一端进队列,另一端出队列,先进入队列的数据先被读取出,这种机制可以保证数据的时序上的正确性。除此之外对 FIFO 的应用不仅仅是跨时钟域接口设计,在同步系统中可以起到防止时钟抖动等因素造成的数据传输错误提高系统稳定性能。因为传输的数据不是单比特,所以打两拍的设计就不可以了,这里的FIFO就恰好可以解决

FIFO的一些重要参数

FIFO的宽度:FIFO一次读写操作的数据位,就像MCU有8位和16位,ARM 32位等等。FIFO的宽度在单片成品IC中是固定的,也有可选择的,如果用FPGA自己实现一个FIFO,其数据位,也就是宽度是可以自己定义的。

FIFO的深度:FIFO可以存储多少个N位的数据(如果宽度为N)。如一个8位的FIFO,若深度为8,它可以存储8个8位的数据,深度为12 ,就可以存储12个8位的数据。

看完基本的概念开始更深入理解,同步FIFO相对简单不考虑,这篇文章主要讲异步FIFO

FIFO的数据路径如图所示,FIFO同步器通过使用一组寄存器R0到RN来工作。数据在输入时钟的控制下存储到寄存器中,并在输出是时钟的控制下读取。尾指针选择下一个要写入的寄存器,头指针选择下一个要读取的寄存器。头和尾指针是格雷码计数器,即每次读数据时,读指针都会加1,每次写操作时,写指针加1.(后面会详细介绍怎么用格雷码计数)

图片[4]-异步FIFO(一)-FPGA常见问题社区-FPGA CPLD-ChipDebug

 

我们考虑这种情况,如果输入时钟运行速度比输出时钟快,那么FIFO将会很快溢出,如果FIFO的所有寄存器都满时,那么就需要停止将输入的数据继续往FIFO中插入。另一种情况,如果clkout比clkin快,那么就会出现FIFO空的情况,这就需要停止从FIFO中继续移出数据。这些都需要在FIFO中添加控制流,如下图所示显示控制路径的FIFO。

图片[5]-异步FIFO(一)-FPGA常见问题社区-FPGA CPLD-ChipDebug

控制的路径中,在输入接口和输出端口都添加了控制流,分别是输入端:iready/ivalid输出端oready/ovalid。如果发送器在数据线上有有效数据,那么有效信号(valid)为真,如果接收器准备好接收新数据那么就绪信号(ready)为真。数据传输只有在有效信号和就绪信号都为真的情况下才会发生。

在输入端,ivalid是一个输入信号,如果FIFO未满,则声明iready信号有效。在输出端,oready是一个输入信号,如果FIFO未空,则声明ovalid有效。iready信号(未满)和ovalid信号(未空)是通过比较头和尾指针来生成的。由于头尾指针在不同的时钟域,在使用多比特强力同步器(打多拍),在输入时钟域生成head(头指针)的另一个headi,在输出时钟同样如此生成tailo。这样可以解决不同时钟域的问题。一旦有了处于同一时钟域的头指针和尾指针,那么就可以很容易确定FIFO空和满的情况,但出现一个问题,不管是FIFO为空还是满,头指针和尾指针都是相同的。而确定头尾指针相同哪个是空,哪个是满,就具有高度的复杂度。我们设计时为了减少复杂度,当FIFO只有一个位为空时(几乎满),声明FIFO为满。这种方法总是会让一个寄存器为空,比如存在8个寄存器,那么当7个寄存器存在有效数据(几乎满),那么我们直接声明为满,这样确实会浪费一个寄存器,但却简化区分空满的情况。

图片[6]-异步FIFO(一)-FPGA常见问题社区-FPGA CPLD-ChipDebug

上图中就是格雷编码的示意图,之前已经有关于格雷码的介绍,这里不再赘述,即二进制(000-111)用格雷编码就是(0、1、3、2、6、7、5、4)。图中(a)是FIFO为空的情况,即首尾指针相同。图中(b)是插入第一个数据的情况,图中(c)是插入两个数据的情况。图中(d)是插入7个数据的情况,即几乎满的情况,这里我们把插入7个数据定义为“满”。实际的满应该是图中(e)的情况,但从图中可以看出,不管是满还是空,首尾指针都是一样的,为了方便区别这种状况,就把“几乎满”定义为“满”。这样就浪费了一个寄存器。当然还有一种,例如现在我们使用的是三位的指针,但可以增加一位“虚位”即四个位的指针,最高位仅作用来区分空和满的情况,这里仅简单讲一下,具体读者可以自己查阅相关资料。

总结:到这里异步FIFO基本的概念都讲清楚了,控制流的信号和格雷编码的空满判断都讲解了,下面就是写代码和仿真了,我使用的是一本书中的代码,自己作一点修改,目前仿真已经做完,将会在下一期讲解代码(VHDL)。

请登录后发表评论

    没有回复内容