企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
[TOC] > # 简介 **FIFO**(First input First output)简单说就是指先进先出。 **FIFO的作用:** * 增加数据传输率 * 处理大量数据流 * 匹配不同传输率的系统 * 对连续的[数据流](https://baike.baidu.com/item/%E6%95%B0%E6%8D%AE%E6%B5%81)进行缓存,防止在进机和存储操作时丢失数据 * 数据集中起来进行进机和存储,可避免频繁的总线操作,减轻CPU的负担 * 允许系统进行DMA操作,提高数据的传输速度 ## 一些关键点 * FIFO的本质是RAM,先进先出 * FIFO深度:简单来说就是需要存多少个数据 * FIFO位宽:每个数据的宽度 * FIFO有同步和异步两种,**同步即读写时钟相同,异步即读写时钟不相同** * 同步FIFO用的少,可以作为数据缓存 * 异步FIFO可以解决跨时钟域的问题,在应用时需根据实际情况考虑好fifo深度即可 ># 同步FIFO的实现 fifo中的ram一般是双端口ram,所以有独立的读写地址。因此可以一种是设置**读,写指针**,写指针指向下一个要写入数据的地址,读指针指向下一个要读的地址,最后通过比较读指针和写指针的大小来确定空满状态。</br> 设置一个计数器,当写使能有效的时候计数器加一;当读使能有效的时候,计数器减一,将计数器与ram的size进行比较来判断fifo的空满状态。这种方法设计比较简单,但是需要的额外的计数器,就会产生额外的资源,而且当fifo比较大时,会降低fifo最终可以达到的速度。 ># 异步FIFO的设计 ## 读空信号如何产生?写满信号如何产生? **读空信号**:复位的时候,读指针和写指针相等,读空信号有效(这里所说的指针其实就是读地址、写地址)当读指针赶上写指针的时候,写指针等于读指针意味着最后一个数据被读完,此时读空信号有效</br> **写满信号**:当写指针比读指针多一圈时,写指针等于读指针意味着写满了,此时写满信号有效。我们会发现读空的条件是写指针等于读指针,写满的条件也是写指针等于读指针,到底如何区分呢?</br> **解决方法**:将指针的位宽多定义一位</br> 举个例子说明:假设要设计深度为8的异步FIFO,此时定义读写指针只需要3位(2^3=8)就够用了,但是我们在设计时将指针的位宽设计成4位,最高位的作用就是区分是读空还是写满,具体**理论1**如下 >**当最高位相同,其余位相同认为是读空** >**当最高位不同,其余位相同认为是写满** 注意:理论1试用的是二进制数之间的空满比较判断。 使用格雷码时: 用格雷码判断是否为读空或写满时应使用**理论2**,看最高位和次高位是否相等,具体如下: >**当最高位和次高位相同,其余位相同认为是读空** >**当最高位和次高位不同,其余位相同认为是写满** 补:理论2这个判断方法适用于用格雷码判断比较空满</br> 在实际设计中如果不想用格雷码比较,就可以利用格雷码将读写地址同步到一个时钟域后再将格雷码再次转化成二进制数再用理论1进行比较就好了。 ## 由于是异步FIFO的设计,读写时钟不一样,在产生读空信号和写满信号时,会涉及到跨时钟域的问题,如何解决? **跨时钟域的问题:** 上面我们已经提到要通过比较读写指针来判断产生读空和写满信号,但是读指针是属于读时钟域的,写指针是属于写时钟域的,而异步FIFO的读写时钟域不同,是异步的,要是将读时钟域的读指针与写时钟域的写指针不做任何处理直接比较肯定是错误的,因此我们需要进行同步处理以后仔进行比较 **解决方法**:**两级寄存器同步****+格雷码**</br> 同步的过程有两个: **(1)将写时钟域的写指针同步到读时钟域,将同步后的写指针与读时钟域的读指针进行比较产生读空信号** **(2)将读时钟域的读指针同步到写时钟域,将同步后的读指针与写时钟域的写指针进行比较产生写满信号**</br> 同步的思想就是用**两级寄存器同步**,简单说就是打两拍,相信有点基础的早都烂熟于心,就不再多做解释,不懂的可以看看代码结合理解。</br> 只是这样简单的同步就可以了吗?no no no,可怕的亚稳态还在等着你。</br> 我们如果直接用二进制编码的读写指针去完成上述的两种同步是不行的,使用格雷码更合适,为什么呢?</br> 因为二进制编码的指针在跳变的时候有可能是多位数据一起变化,如二进制的7-->8即0111--> 1000 ,在跳变的过程中4位全部发生了改变,这样很容易产生毛刺,例如异步FIFO的写指针和读指针分属不同时钟域,这样指针在进行同步过程中很容易出错,比如写指针在从0111到1000跳变时4位同时改变,这样读时钟在进行写指针同步后得到的写指针可能是0000-1111的某个值,一共有2^4个可能的情况,而这些都是不可控制的,你并不能确定会出现哪个值,那出错的概率非常大,怎么办呢?到了格雷码发挥作用的时候了,而格雷码的编码特点是相邻位每次只有1位发生变化,这样在进行指针同步的时候,只有两种可能出现的情况:1.指针同步正确,正是我们所要的;2.指针同步出错,举例假设格雷码写指针从000->001,将写指针同步到读时钟域同步出错,出错的结果只可能是000->000,因为相邻位的格雷码每次只有一位变化,这个出错结果实际上也就是写指针没有跳变保持不变,我们所关心的就是这个错误会不会导致读空判断出错?答案是不会,最多是让空标志在FIFO不是真正空的时候产生,而不会出现空读的情形。所以gray码保证的是同步后的读写指针即使在出错的情形下依然能够保证FIFO功能的正确性。在同步过程中的亚稳态不可能消除,但是我们只要保证它不会影响我们的正常工作即可。 ## 由于设计的时候读写指针用了至少两级寄存器同步,同步会消耗至少两个时钟周期,势必会使得判断空或满有所延迟,这会不会导致设计出错呢? 异步FIFO通过比较读写指针进行满空判断,但是读写指针属于不同的时钟域,所以在比较之前需要先将读写指针进行同步处理,将写指针同步到读时钟域再和读指针比较进行FIFO空状态判断,因为在同步写指针时需要时间,而在这个同步的时间内有可能还会写入新的数据,因此同步后的写指针一定是小于或者等于当前实际的写指针,所以此时判断FIFO为空不一定是真空,这样更加保守,一共不会出现空读的情况,虽然会影响FIFO的性能,但是并不会出错,同理将读指针同步到写时钟域再和写指针比较进行FIFO满状态判断,同步后的读指针一定是小于或者等于当前的读指针,所以此时判断FIFO为满不一定是真满,这样更保守,这样可以保证FIFO的特性:FIFO空之后不能继续读取,FIFO满之后不能继续写入。**总结来说异步逻辑转到同步逻辑不可避免需要额外的时钟开销,这会导致满空趋于保守,但是保守并不等于错误,这么写会稍微有性能损失,但是不会出错。**</br> **举个例子:**大多数情形下,异步FIFO两端的时钟不是同频的,或者读快写慢,或者读慢写快,慢的时钟域同步到快的时钟域不会出现漏掉指针的情况,但是将指针从快的时钟域同步到慢的时钟域时可能会有指针遗漏,**举个例子**以读慢写快为例,进行满标志判断的时候需要将读指针同步到写时钟域,因为读慢写快,所以不会有读指针遗漏,同步消耗时钟周期,所以同步后的读指针滞后(小于等于)当前读地址,所以可能满标志会提前产生,满并非真满。进行空标志判断的时候需要将写指针同步到读指针,因为读慢写快,所以当读时钟同步写指针的时候,必然会漏掉一部分写指针,我们不用关心那到底会漏掉哪些写指针,我们在乎的是漏掉的指针会对FIFO的空标志产生影响吗?比如写指针从0写到10,期间读时钟域只同步捕捉到了3、5、8这三个写指针而漏掉了其他指针。当同步到8这个写指针时,真实的写指针可能已经写到10,相当于在读时钟域还没来得及觉察的情况下,写时钟域可能偷偷写了数据到FIFO去,这样在判断它是不是空的时候会出现不是真正空的情况,漏掉的指针也没有对FIFO的逻辑操作产生影响。 > # 异步FIFO深度计算 异步FIFO可以解决跨时终域的问题,但是需要注意深度。 ## 异步FIFO最小深度计算 计算FIFO深度是FIFO设计中常遇到的问题。当异步FIFO读写端口的throught-put(吞吐量)不同时,会遇到数据丢失的问题,这时就需要考虑FIFO的Deepth问题了,即**为满足读写流畅不卡顿(数据不丢失)时,FIFO的Deepth的最小值**。</br> 计算异步FIFO的最小深度,首先必定是要了解清楚应用场景的,这关乎到FIFO的最小深度的计算。**FIFO主要是用于数据的缓存,用在读慢写快的场景下**。异步FIFO读写不同频,我们选用的FIFO要能够在极端的情况下仍然能够保证数据的不溢出。因此,**考虑的前提一般都是读慢写快的情景(写时钟大于读时钟)**,但需要注意的是,这里的写操作是**猝发传输**,而不能使连续操作。倘若写快读慢的场景下,写数据流是连续的,那再大的FIFO都会有写满的时候,因此无法避免数据的溢出(下面有一个蓄水的例子)。</br> 当**写快读慢**时,FIFO便可被用作系统中元件或队列。因此**FIFO的大小**其实也就暗示了所需**缓存数据的容量**,该**容量**取决于**读写数据的速率**。据统计,**系统的数据速率**取决于**系统的负载能力**。因此为了保证FIFO的大小,需要考虑FIFO传输的最坏情况。 > **所谓最坏情况,就是使得写速率最大,读速率最小;通常是考虑猝发传输。** 宏观看,整个时间域上,"写数据=读数据",这个是异步FIFO正常工作最基本的要求,是大前提。由于**写快读慢**,在发送方"**突发传输**"的发送数据的T内,是很有可能发送方写数据量>接收方读取的数据量,那么剩下未读取的数据必定需要存储共接收方继续读取并不能丢弃,因此FIFO的深度要能够保证,在这段时间T内,如果接收方未能将发送方发送的数据接收完毕的话,剩下的数据都是可以存储在FIFO内部而且不会溢出的,那么在发送方停止发送数据的"空闲时隙"内,接收方可以从容地接收剩下来的数据。 ### 异步FIFO最小深度计算原理 **速率的概念** >想象一个场景,有一个水龙头在不断向下流水,水龙头下放着一口缸在接水,而缸上有一个出水口也在源源不断的出水。假如,水龙头的单位时间进水量大于出水口的出水量,那么缸内就会开始不断的积水。如果水龙头按照这样持续不断的进水,那积水必将越来越多,结果就是无论缸多大,早晚都会盛满溢出的。因此这种供水需要   间歇性   的地往缸里注水(数据的**猝发传输**)。 **全速读写,引起的溢出问题:** ![](https://img.kancloud.cn/31/7a/317abd187d846ea2f4393abd2272f8ad_502x315.png) 同样的道理,如果数据流是在**连续不断写**,则FIFO无论多大,只要是读写时钟**不同源同频**就都会**丢数(****类比可能不太恰当,水缸空了,读空;水缸满了,写数据会覆盖****)**。这涉及到一个数据的最大连续写长度(一个cycle写一个数据)以保证数据的正确传输即FIFO能够完整传输数据。</br>  那到底如何利用异步FIFO呢?一般数据的传输会以一定格式的数据包,且以一定频率进行传输,而不是永久的连续传输下去。这样的话,就算是写快,读慢,只要保证在写满FIFO之前能把一个数据包发送完毕.</br> 很多场景比较简单,没有考虑性能和资源的问题,只要Deepth合理就行,比如有时候常取Deepth_value=   WR_Burst_len*(Wr_clk/Rd_clk),会选取大于Deepth_vlaue最接近的2^N数值,或是直接对Wr_clk/Rclk向上取整。</br> FIFO常用于缓冲块数据,一般用在写快读慢的情况下,遵循如下规则: ![](https://img.kancloud.cn/2c/62/2c62cbed69d7c610012c1265e267d64e_488x45.png) 本质上就是: ![](https://img.kancloud.cn/3c/83/3c833f28a5eb59ddfb5c8b2ba1eb2100_468x45.png) **例**: A/D采样速率50Mhz,dsp读A/D的速率40Mhz,要不丢失地将将10万个采样数据送入DSP ,在A/D和DSP之间至少加多大容量的(深度)FIFO才行??(来源网络) ![](https://img.kancloud.cn/d2/38/d238e05a079c1578e9925c60e7886762_822x539.png) **答**: Deepth/(50000000-40000000)>(100000/50000000) 即Deepth>10\_0000/5=20000。那么最小深度为20k。