(码字不易、三连支持)
FIFO(first in first out)是一种先进先出的存储器,与栈不同,栈对应的是一种先进后出的数据存储理念。
FIFO无论是在IC设计中、IP核设计中、SOC设计中都存在广泛的应用。特别是随着设计复杂度的提高,在一个系统中往往会引入多个时钟,这也就使得数据的跨时钟域处理显得尤为重要,而FIFO正是解决这一问题的有效方法。
FIFO的设计应用:
1.德州仪器tm4c123g单片机的UART模块使用FIFO做缓冲。
2.NXP单片机正交解码器缓冲。
3.摄像头的数据缓冲。
4.使用ADC配合FIFO与DMA实现高速采集。
5.提高状态机模块的数据吞吐率(软件不需要再读状态机的busy标志位,只需关心fifo是否空满)。
FIFO根据读写时钟域的不同可分为同步FIFO与异步FIFO。
同步FIFO是指读写通道均在相同的时钟下进行信号采样的FIFO,主要用于设备之间数据传输速率的匹配。
异步FIFO是指读写通道在不同的时钟下进行信号采样的FIFO,主要处理跨时钟域之间的数据传输问题。
系统2如果直接去采样处于时钟域1的系统数据,很有可能会采样到处于亚稳态的数据。使用异步FIFO对数据进行缓冲一定程度上减少了亚稳态发生的概率。
(无论是同步还是异步的FIFO,都是面向数据流的一种数据存储器,都具有数据缓冲作用)。
在FIFO实际的应用中,一般将empty与almost_full配合起来使用。为了减少FIFO上溢的风险,full信号很少使用。
data_depth是否越大越好?
否,data_depth应该根据读写数据方的速率进行合理确定。
过大的data_depth会消耗过多的资源(若RAM是LUT实现,则消耗大量的LUT,若是Block RAM 则消耗Block RAM资源)
3.2.1 data_depth的确定
深度的确定既要满足数据不丢失,且不能过多的浪费资源;
这里首先考虑极端情况:
如果写数据方的写入速率大于读数据方的读出速率,则FIFO的数据深度只有无穷大时,才能确保数据不溢出;显然无穷大深度的FIFO是不存在的;这种情况是无解的;
相较于上述的极端情况,更多的是考虑写入Burst数据的情况,虽然写入数据流是连续的,但写Burst之间数据往往存在时间间隔;
fw>fr,且读写之间没有空闲周期:
假设fw为100Mhz,fr为80Mhz,一个Burst传输长度为2400。
根据fw,可以知道写入一个数据需要10ns
同理根据fr,可知读出一个数据需要12.5ns
将2400个数据写入FIFO需要2400*10ns = 24000ns
而在这2400个数据被写入期间,FIFO实际被读出的数据为24000/12.5 = 1920个
则该情况下FIFO深度需要2400-1920 = 480
其他情况下的FIFO深度计算也可以参考上述的情况,无论情形如何,最终需要计算的都是写入数据与读出数据之差;
如写时钟大于读时钟,且连写入之间会有1时钟空闲,读出之间会有3时钟空闲;
这种情况下其实写入的频率变为了100Mhz/(1+1) = 50Mhz,读出频率变为了80Mhz/(3+1) = 20Mhz,后面的解法就和上面一样了;
还有些情况会给出读写使能信号的占空比,这其实也变相的给出了读写频率,如wr_en占空比50%,rd_en占空比25%则fw = 100Mhz*(50%) = 50Mhz,fr = 80Mhz*(25%) = 20Mhz;总之万变不离其宗;
FIFO的存储地址是不需要设备给出的,每次读使能有效则地址自增1,每次写使能有效则地址自增1;
如果说RAM对应的是存储地址首尾不相接的数据空间,则FIFO的地址是首位相连的;
如下图在初始化后,写指针与读指针指向地址0,此时FIFO为空状态;此时若强行读出数据,r_ptr则增1,到地址1的位置,很显然此处并无数据写入这个数据是错误的,这种情况为为数据下溢;
当FIFO写入了7个数据后,w_ptr指向地址7,此时若再写入一个数据,写指针w_ptr将回到地址0,w_ptr与r_ptr相同此时FIFO写满,若继续写入数据,则会覆盖之前的数据,这就是数据的上溢。
在FIFO实际使用中,写和读操作往往同时进行,此时w_ptr与r_ptr就会在这个环式地址空间上赛跑;假设写入了5个数据,则此时w_ptr指向了地址5,此时读出了两个数据,则r_ptr指向了地址2,如下图(红色为写入数据,蓝色为已读出的数据)。
被读出的数据可以看作是被释放了出来,可以再次被写入数据;读写指针就这不断进行绕圈;
在绕圈情况中,如果r_ptr超过了w_ptr(蓝色部分完全覆盖了红色部分),则会读出错误数据;
若w_ptr超过r_ptr(红色部分覆盖了蓝色部分),则会出现覆盖数据的情况;
上述两种情况需要避免,可以通过空满信号来避免;
当r_ptr 赶上w_ptr时,则判断为空,此时FIFO阻止数据继续读出;
当w_ptr赶上r_ptr时,则判断为满,此时FIFO阻止数据继续写入;
同比FIFO设计中,空满信号的判断比较简单,我们只需要想办法描述上述的两种“赶上”情况就可以了;
描述方法有很多,这里给出一个最简单的方法:
假设深度为8,则地址宽度为3,我们在设计中对实际的地址进行1位扩位来存储“圈数”。
此时地址枚举出来如下:
我们可以看到在自增8之后,高位从0变为了1,此时就代表该指针已经开始跑第二圈了;若此时写指针追上了还在跑上一圈(高位不同)的读指针,则说明此时FIFO满;
空则是相同圈内(高位相同),读指针赶上了写指针;
上述逻辑则可以写为:
异步FIFO设计则需要考虑同步问题;
此时同步FIFO中指针计数器会出现亚稳态问题,因为地址从上一个变化到下一个会产生多个比特位的变化,如0111到1000,变化的比特位为4位,由于布局布线的问题,每一比特位的变化速度不同,这将导致采集到不可预测的错误数据;
因此我们需要另一种比特位变化更少的编码,即格雷码;
*二进制码与格雷码对照表
如上表,相邻的两位变化的比特位仅为1;除此之外,格雷码还具有很重要的对称性,我们可以在上图的7到8之间画一条分界线,除了高位,其余为按照这条分界线对称;
另一方面,需要将转换后的格雷码进行时钟域同步,这里采用典型的二级同步器;
空逻辑判断:
将w_ptr对应的格雷码w2r_ptr_gray同步到读时钟域,再将同步后的w2r_ptr_gray与r_ptr对应的格雷码r_ptr_gray进行对比,若两者相等则判定为空;
满逻辑判断:
将r_ptr对应的格雷码r2w_ptr_gray同步到写时钟域,再将同步后的r2w_ptr_gray与w_ptr对应的格雷码w_ptr_gray进行对比,若两者高两位为取反关系,剩余位相同,则判断为满;
综上,异步FIFO的架构可以总结为下图所示:
6.2.1 二进制-格雷码转换器
6.2.2 信号同步器(dff)
6.2.3 异步FIFO顶层
6.2.4 测试源码
6.2.5 功能仿真结果
我们观察上面的仿真图,fifo_empty在写入两个数据后才拉低,fifo_full在读出两个数据后才拉低,这就是假空假满现象;
本质上这个现象是由同步器造成的,空信号是在读时钟与将读指针与同步过来的写指针进行比较,由于同步器采用了两级,因此会有两个clk的滞后输出,即实际的写地址已经增至2了,但同步过来的地址仍为0;假满状态同理;
但这种现象并不会影响FIFO的使用,因为这样产生的空满信号实际上是悲观的,虽然FIFO中有数据,但空信号仍然会阻止数据的读出,因此只会在FIFO的效率上产生影响。
非2次幂FIFO实际上与2次幂异步FIFO差不多,只不过在编码格式上我们需要有所改变,对应的空满信号的判断也需要稍加改动;
假设是6深度的FIFO,使用顺序的格雷码,变化的比特位就不再是1位了;
如从6变化到0,共两个码位发生了变化;
针对这个问题,可以利用格雷码的对称性解决;如下图
由13到2,(1011)到(0011)只变化了1位;
将原来的顺序格雷码映射到对称格雷码也比较简单;
只需在指针与格雷码的转换逻辑上,每次转换的指针加上一个偏置项FIFO_DEPTH_COMPLE;
进一步在第七章中的FIFO上进行将空将满信号的设计;
将空将满已经在之前介绍过了,可以视作一个阈值,当FIFO中的数据数大于等于这个阈值时将满信号拉高,反之将空信号拉高;
上一章已经介绍了r_fifo_data_count与w_fifo_data_count的计算方法,我们使用这两个计数值来增加将空将满逻辑,如下:
FIFO_DATA_DEPTH_MARGIN为余量值;
这里观察almost_empty和almost_empty信号,其假将空与假将满产生的原因与empty和full的假空假满相同;
版权声明:
本文来源网络,所有图片文章版权属于原作者,如有侵权,联系删除。
本文网址:https://www.mushiming.com/mjsbk/10173.html