FIFO 是 First In First Out 的简称。
是指在FPGA内部用逻辑资源实现的能对数据的存储具有先进先出特性的一种缓存器。
FIFO 与 FPGA 内部的 RAM 和 ROM 的区别是 FIFO 没有外部读写地址线,采取顺序写入数据,顺序读出数据的方式,其数据地址由内部读写指针自动加1完成。
FIFO 使用起来简单方便,由此带来的缺点是不能像 RAM 和 ROM 那样可以由地址线决定读取或写入某个指定的地址。
FPGA内的程序实现的电路实际上是由一个个独立的功能模块组成的,各个模块又通过相关信号关联在一起,当存在模块间处理数据速度不同(有快有慢)时,处理得快的模块就需要等一等处理得慢的模块,这个等待其实就是缓存的实现。
我们可以采用FIFO来解决数据的缓存。
打个比方,就像水龙头放水慢(输入慢),但我们人提水的时候是一次处理一桶水(输出快),所以需要一个水桶作为缓存,等存满一桶水,再一次被人提走。
又或者像我们人喝水,一次接一杯水(输入快), 渴的时候喝两口(输出慢)。这里,杯子作为缓存。
另外,在现代集成电路芯片中,随着设计规模的不断扩大,一个系统中往往含有数个时钟,此时,异步时钟之间的接口电路的设计将成为关键。
而使用异步FIFO可以在两个不同时钟系统之间快速而方便地传输实时数据。
- 数据缓存、
- 协议处理、
- 串并转换、
- 跨时钟域数据处理。
FIFO根据读写时钟是否为同一时钟分为同步FIFO和异步FIFO。
- 同步FIFO是指读时钟和写时钟为同一个时钟,在时钟沿来临时可同时发生读写操作。
- 异步FIFO是指读写时钟不一致,读写时钟是互相独立的2个时钟。
- 同步FIFO在实际应用中比较少见,常用的是异步FIFO,但基于学习的目的,下文对两种FIFO都进行讲解。
简单来说,同步FIFO其实就是一个双口RAM加上两个读写控制模块。FIFO的常见参数和信号如下:
- FIFO的宽度:即FIFO一次读写操作的数据位;
- FIFO的深度:指的是FIFO可以存储多少个N位的数据(如果宽度为N)。
- 满标志:FIFO已满或将要满时由FIFO的状态电路送出的一个信号,以阻止FIFO的写操作继续向FIFO中写数据而造成溢出(overflow)。
- 空标志:FIFO已空或将要空时由FIFO的状态电路送出的一个信号,以阻止FIFO的读操作继续从FIFO中读出数据而造成无效数据的读出(underflow)。
- 读时钟:读操作所遵循的时钟,在每个时钟沿来临时读数据。(同步FIFO 读写只有一个时钟)
- 写时钟:写操作所遵循的时钟,在每个时钟沿来临时写数据。(异步FIFO读写时钟分开)
- 读指针:总是指向下一个将要被写入的单元,写完后自动加1,复位时,指向第1个单元(编号为0)。
- 写指针:总是指向下一个将要被读出的单元,读完后自动加1,复位时,指向第1个单元(编号为0)
其实可以把FIFO比作一个单向行驶的隧道,隧道两端都有一个门进行控制,FIFO宽度就是这个隧道单向有几个车道,FIFO的深度就是一个车道能容纳多少辆车,当隧道内停满车辆时,这就是FIFO的写满状态,当隧道内没有一辆车时,这便是FIFO的读空状态。
FIFO 的设计原则是任何时候都不能向满FIFO中写入数据(写溢出),任何时候都不能从空FIFO中读取数据(读溢出)。
FIFO 设计的核心是空满判断。FIFO设置读,写地址指针,FIFO初始化的时候 读指针和写指针都指向地址为0的位置, 当往FIFO里面每写一个数据,写地址指针自动加1指向下一个要写入的地址。
当从FIFO里面每读一个数据,读地址指针自动加1指向下一个要读出的地址,最后通过比较读地址指针和写地址指针的大小来确定空满状态。
当读地址指针追上写地址指针,写地址指针跟读地址指针相等,此时FIFO是读空状态。
当写地址指针再次追上读地址指针,写指针跟读地址指针再次相等的时候,此时FIFO是写满状态。
可以设置一个计数器,当写使能有效的时候计数器加一;
当读使能有效的时候,计数器减一,将计数器与FIFO的size进行比较来判断FIFO的空满状态。
这种方法设计比较简单,但是需要的额外的计数器,就会产生额外的资源,而且当FIFO比较大时,会降低FIFO最终可以达到的速度。
同步FIFO基本接口:
同步FIFO实现代码如下:
同步FIFO仿真测试文件
采用Modelsim仿真得到如下波形:
可以在Modelsim的View——Transcript窗口看到有如下打印信息:
虽然各大厂商都有自己的现成 FIFO IP 可供调用, 而且自己设计异步FIFO是比较复杂的。
但是我们仍然需要学习FIFO的设计原理,这样我们在设计或移植的过程中查找问题起来将有据可循。
所以掌握异步FIFO设计原理是一名合格FPGA工程师的基本功。
异步FIFO有两个时钟信号,读和写接口分别采用不同时钟,这两个时钟可能时钟频率不同,也可能时钟相位不同,可能是同源时钟,也可能是不同源时钟。
下面是异步FIFO的系统框图:
可以看到异步FIFO实质上也是基于中间的双口RAM,外加一些读写控制电路组成的。
因为这里读写用的是两个不同的时钟。这将涉及到跨时钟域问题。跨时钟域的电路会带来亚稳态。
外部电路对异步FIFO进行读写操作时,需要根据异步FIFO输出的空满信号来判断是否能继续对异步FIFO进行读或者写的操作。
那么FIFO是如何输出空满信号的呢?
异步FIFO跟同步FIFO一样设置读写指针, 同样也是当读地址指针追上写地址指针(写地址指针跟读地址指针相等),此时FIFO是读空状态。
当写地址指针再次追上读地址指针(写指针跟读地址指针再次相等的时候),此时FIFO是写满状态。
同步FIFO里面指针的比较直接采用了一个额外的计数器统计FIFO里面还剩多少数据。
但异步FIFO是读写时钟不同步的,只能将读时钟域的读地址指针传输到写时钟域然后与写地址指针进行比较判断FIFO是否为满,将写时钟域的写地址指针传输到读时钟域然后与读地址指针进行比较判断FIFO是否为空。
这种跨时钟域的处理就会产生亚稳态。下面举个例子:
同步时钟:
假设数据从0跳变到1,一般数据的跳变不是立马跳变,而是有一个上升时间,有个斜坡。
如果是同步时钟采集数据则不会有什么影响。
第一个时钟周期采集到的是0, 第二个周期电平已经稳定到1。
异步时钟:
如果是异步时钟,比如数据跟clk1是同步的,第2个时钟比第1个时钟滞后一点点,那么第2个时钟在采集数据的时候有可能时钟上升沿正好对应在数据跳变的阶段,那此时读到的数据可能是0, 可能是1, 也可能输出中间级电平,或者是处于振荡状态。
这就是出现了亚稳态。这种不确定的电平输出会沿着信号通道上的电路继续传递下去,对电路造成很大危害,极有可能让整个系统挂死。
亚稳态不可完全避免, 只能通过一些手段如 引入同步机制(打2拍) 以及 格雷码等来降低亚稳态出现的机率。
异步FIFO的跨时钟域处理所带来的亚稳态可以通过同步机制(打两拍)来降低亚稳态发生的概率。
如下图,A时钟域的数据Q1传递给B时钟域, 当B时钟上升沿来时,可能恰好数据Q1从0跳变到1,这样Q2极有可能出现亚稳态。
如果我们将Q2的值直接拿来用,将会导致亚稳态传播下去。
所以后面再设置一个D触发器继续对Q2进行采样得到Q3。
可能Q2会产生亚稳态,但等到Q3时候电平就会稳定到0或者1(也有可能继续是亚稳态,但一个电路出现亚稳态概率非常低, 然后连续两次出现亚稳态的概率更低, 低到我们可以忽略, 因此我们可以假设打两拍以后Q3 不存在亚稳态了,因此打两拍可以解决亚稳态传播的问题)。
Q1经过B时钟打两拍同步以后的数据Q3才能在B时钟域被使用。
当然,可能有人会问,如果Q1当时跳变为1时却被识别为0 ,对电路就没有影响吗?
答案是,如果只是一个地方判断错误不会有太大影响。怕就怕亚稳态一直被传播下去。
格雷码是一种相邻数据只有1bit变化的码制。
如果地址采用二进制码,地址从3(0011)跳变到4(0100),有3个bit发生了变化, 每个bit 都有可能发生亚稳态,那么此时亚稳态出现的几率是1bit 跳变的3倍。
因为格雷码每次跳变只有一个bit,所以采用格雷码将大大降低了亚稳态发生的概率。
格雷码是二进制码右移1位再与原码相异或的结果。
二进制码转格雷码的Verilog代码实现如下:
graycode = (bincode>>1) ^ bincode;
如果二进制变化没有任何规律,那么采用格雷码也可能发生多 bit 的跳变,而 FIFO 设计中的读写地址都是连续变化的,因此格雷码适用于 FIFO 的地址处理。
(1)空判断
当读地址指针追上写地址指针,写地址指针跟读地址指针相等,此时FIFO是读空状态。
(2)满判断
当写地址指针再次追上读地址指针,写指针跟读地址指针再次相等的时候,此时FIFO是写满状态。
(3)虚空、虚满
当发现计数器不准。
当写地址同步到读时钟域时,这个地址需要在读时钟域打两拍,而这两拍的过程中写控制端还可以继续向FIFO里面写数据,如果此时判断FIFO为空的话,这个空属于虚空。
当读地址同步到写时钟域时 这个地址需要在写时钟域打两拍,而这两拍的过程中读控制端还可以继续从FIFO里面读取 数据,如果此时判断FIFO为满的话,这个满属于虚满。
虚空虚满不会产生错误, 只是影响FIFO 效率。 理解这些原理后,分析问题就知道去哪里分析。
写比读快
第一种情况,已知连续写数据的长度(Burst Length),那么只需要考虑这段时间内最多会写进多少个数,以及会读走多少个数,二者只差就是FIFO的深度。
读比写快
FIFO的深度为1就可以了。
读写一样
FIFO的深度为1就可以了。
(1)顶层模块
(2)双端口RAM模块
双端口RAM模块用于存储数据。
(3)同步模块1
sync_r2w 模块用于读地址同步到写控制端。
(4)同步模块2
sync_w2r模块用于写地址同步到读控制端。
(5)空判断模块
空判断模块用于判断是否可以读取数据。
读操作时,读使能rinc有效且FIFO未空。
(6)满判断模块
满判断模块用于判断是否可以写入数据。
写操作时,写使能winc有效且FIFO未满。
异步FIFO设计的整体RTL Viewer如下图所示:
(1)异步FIFO仿真文件
这里选择的是读写频率相同,但读是在时钟下降沿, 写在时钟的上升沿。
(2)异步FIFO仿真结果
打开Quartus 的 菜单栏的 Tools——Run Simulation Tool——RTL Simulation看到波形如下:
当复位撤销(复位信号低有效)之后,在写使能 i_w_en 拉高有效之后,写数据也开始变化:
empty 空标记也开始在几拍之后变为非空(有一个写到读侧的异步转换,打了两拍):
当读使能i_ r_en 拉高有效之后,读数据在下一拍也开始变化:
可以在Modelsim的View——Transcript窗口看到有如下打印信息:
内容参考:LTC2308 ADC器件解读以及LTC2308控制器代码解读
自己设计FIFO的目的一般是为了学习一下FIFO的结构,设计思路等,如果是一般的项目设计 ,建议可以直接调用厂商提供的FIFO IP 进行简单配置会不容易出错一点。
使用Quartus II软件提供的免费FIFO IP核,Quartus II软件为用户提供了友好的图形化界面方便用户对FIFO的各种参数和结构进行配置,生成的FIFO IP核针对Altera不同系列的器件,还可以实现结构上的优化。
Quartus 里面提供的FIFO可分为两种结构:单时钟FIFO(SCFIFO)和双时钟FIFO(DCFIFO), 本实验我们调用DCFIFO。
- 掰开揉碎讲 FIFO(同步FIFO和异步FIFO)
版权声明:
本文来源网络,所有图片文章版权属于原作者,如有侵权,联系删除。
本文网址:https://www.mushiming.com/mjsbk/6889.html