文章目录
👉注:代码链接在文末,有需要直接移至文末观看。
👉注:更多精彩请看:面试常问的verilog代码汇总 一文
简介
异步FIFO 的读写是用不同的时钟来控制的,所以不能采用共享同一个计数器的方法来产生空满标志。那么我将通过引入问题的方式,逐步异步FIFO中重要的概念和机制,再来进行代码环节。
注:同步FIFO也可以看同步FIFO
指针该怎么设计?
将读写指针向高位扩展一位,也就是说,深度为2n 的异步FIFO,读写指针的宽度应该为(n+1)位。当读写指针指向最FIFO中的最后一位时,指针最高为从0变为1,其他位变成0,指针再次折回到FIFO的首位。
空满标志产生的条件?
因为读写指针向高位扩展一位,所以当使用二进制进行编码读写指针时,空满标志为:
- 读写指针相同时,FIFO为空。只有在复位操作或者当读指针读出FIFO中的最后一个数据时,读指针追赶上了写指针。
- 读写指针最高位(MSB)不同,其他位相同时,FIFO为满。这说明写指针比读指针已经多折回了一次,此时FIFO已满。
为什么是格雷码?
用上面二进制编码读写指针,这样问题就真的解决了吗?还是那个问题,读写指针是在不同的时钟域,要进行跨时钟域同步(不太了解的可以看跨时钟域传输),这势必要考虑传输时的亚稳态问题。采用二进制码进行读写指针的编码时,我们知道,其相邻点的递增可能有好几个二进制位同时发生变化。而在异步FIFO中,我们一般采用两级触发器同步打两拍的方式进行读写指针的同步。也就是说,如果使用二进制进行编码,就不能采用这种简单的同步两拍的方式,因为这种方式只能进行单bit信号跨时钟域传输。那为何解决这个问题?
采用格雷码。格雷码是一种在0 ~ 2n -1之间循环变化的编码,其相邻两个数值之间只有一位二进制位发生变化,有效的降低了在指针同步的过程中产生亚稳态的概率。
所以采用格雷码后,产生空满标志的条件又要变一变了。
- 空标志:读写指针所有位均相同。
- 满标志:读写指针最高位(MSB)不同,次高位不同,其他位相同。
PS:这里我补充解释几个点:
1.为什么要改变满标志判断条件?
答:假如是如下图所示的4位格雷码,FIFO深度为8,读写指针从0000 ,…,0100(7)再到1100(8)。格雷码具有镜像对称的特点,这里的1100(8)的意义就相当于指针折回到FIFO的首位(0000)。所以假如此时要比较满时,要比较1100和0000,也就是要满足最高位和次高位不同,其他位相同。
2.是用格雷码需要注意什么?
答:格雷码只有满足2n 个数,才可以形成循环,否则会导致相邻位不止一位发生变化。这也就是使用格雷码作为读写指针时,FIFO的深度必须为2n 。有的时候宁愿浪费一点空间,也要将深度设为2n 。
例如:如果设置FIFO的深度为6,首位跳变时有多位发送变化。
3.格雷码同步时出错了,怎么办?
答:格雷码每次只要1位二进制位发生变化,这样在进行地址同步时,如果地址同步出错,如下图:写地址跳变由000到001,同步时出错,将写指针由001同步成000。
尽管写地址跳变出错,在读时钟域判断空是否产生空标志时也不会出现严重错误,最坏的情况就是让空标志在FIFO不是为空的时钟产生空标志,也就是假空的情况。但是不会产生更加严重的空度情况。
4.格雷码与二进制之间的转换?
答:这个还是看我之前的博客:IC验证面试题(题2),描述的更详细。
空满标志产生需要读写指针如何同步?
首先我们要明白,读写指针受不同的时钟控制,假如你把写指针同步到读指针时钟域中去判断满,行不行?不行,假如加入此时你判断了为满(最高位和次高位不同,其他位相同),控制FIFO不要再写了。但你注意了,此时写指针在经过两级触发器同步到读时钟域的过程中,写指针它在写时钟域中还进行了至少两次的写操作(一般写时钟更快),这就造成了FIFO的溢出。
那么该怎么办?
- 满标志: 将读指针同步到写时钟域,告诉写信号“你TM别写了!,再写就溢了”
- 空标志:将写指针同步到读时钟域,告诉写信号“你TM别读了!,再读就空了”
那么这就有个问题了?上面说同步的过程中,FIFO实际还在写,所以会造成写满。那你现在把读指针同步到写时钟域来判断满,实际FIFO应该还未满,这不造成羞耻的浪费了嘛。其实这种设计称为保守设计,就是为了增加FIFO的安全性,防止写指针孬头吧唧地写满了。同理,也防止读指针孬头吧唧地产生空读,读出无效数据。
FIFO的深度如何计算?
Question1:写时钟频率为100MHz,读时钟频率为80MHz。每100个时钟周期写入80个数据,每一个周期读出一个数据。那么FIFO的深度应该是多少?
首先我们要确定最极端的情况,也就是FIFO的Burst 会写了多少个数据。这里要考虑数据是“背靠背”传输的,如下:
这种情况是,前100个时钟周期,数据在后80个周期内发生了连续的写操作,在后100个时钟周期内,数据在前80个周期写入。这就造成了:
- 数据突发长度为 :160
- burst长度(160个数据)写入需要的时间:burst长度/写时钟频率 = 160/100
- burst长度(160个数据)写入期间可以读出的数据量:160/100 ÷ 1/80 =128
- FIFO的深度:160 - 128 = 32
Question2:设计一个同步FIFO,每100个时钟周期可以写80个数据,每10个周期可以读8个数据,FIFO的深度为多少?
和上面的情况一样,burst长度为160。所以:
- burst长度(B)为:B =160
- 写入B长度所需的时间:T = (1/fwr)*160
- 从FIFO中读出一个数据所需的时间:t = (1/frd)*(8/10)
- 在T时间读走的数据量:T/T= 160* (frd/fwr)(8/10)
- FIFO的深度:160 - 160* (frd/fwr)(8/10)
因为是同步FIFO,所以frd/fwr = 1,代入得 深度为32。
由此我们可以推出FIFO的计算公式:
想了解FIFO深度的计算场景,还可以看一个大佬的FIFO深度计算
FIFO的测试点?
question1:(22届乐鑫提前批)一个异步FIFO,rdata和wdata均为8位,FIFO深度为16,当rst_n为低时,FIFO复位。当wclk的上升沿采样且wr为高时,数据被写入FIFO,当rclk的上升沿采样且rd为高时,FIFO输出数据。此外,当FIFO为空时,EMPTY为高,当FIFO为满时,FULL信号为高。尽可能列出你能想到的FIFO测试点。
有两个一级测试点:1.FIFO的基本功能;2.异步处理
1.FIFO的基本功能测试包括:
-
检查端口时序。
1.检查数据数据是否在wclk的上升沿并且wr为高时被写入。如,在wr为高或为低时写入的数据能不能被有效写入。
2.检查数据数据是否在rclk的上升沿采样且rd为高时读出。
-
数据一致性。先写后读,检查是否能按先入先出的顺序读出;
-
空满信号能不能正确生成。先连续地写,再连续的读,检查空满信号是否准确生成;
-
读写指针有没有做空满限制。当FIFO满时,外部有没有做保证写指针不会增加;当FIFO空时,读指针是否还在增加;
-
检查复位信号,各个寄存器和信号是否能被正常复位,复位释放后信号的初始值是否正常;
-
读写指针转换为格雷码的逻辑检查。
2.异步处理:
- 读写指针跨时钟域传输时,有没有做同步处理。
- FIFO的读写时钟频率差速耐受能力。检查读快写慢、写慢读快一定周期后,FIFO是否还能保持稳定。
异步FIFO的verilog代码
异步FIFO的结构如图所示。由读时钟域、写时钟域、Mermory模块和跨时钟域同步组成。
异步FIFO代码
本博客内容过长,不方便阅读。详细代码请看我的另一篇博客:异步FIFO代码(Verilog)