FPGA自学笔记--VIVADO FIFO IP核控制和使用
本文主要学习在VIVADO软件中如何生成所需要的FIFO IP核,以及相关的配置定义,并搭建tb对生成的IP读写控制时序进行仿真和测试。主要学习小梅哥的笔记整理而成。
FIFO(First In First Out,先进先出)是一种具有先进先出特性的缓冲存储结构,在 FPGA 或 ASIC 设计中常用于数据缓存和高速异步数据交互。与普通存储器相比,FIFO 没有外部读写地址线,读写操作依赖内部读写指针自动递增,简化了使用,但只能顺序写入和顺序读出,无法像普通存储器那样通过地址线随机访问指定位置。
本文重点介绍 FIFO 的基本原理,并以双时钟 FIFO 为例,说明 FIFO IP 的生成流程及其基本使用方法。
一、FIFO结构与应用场景
从读写时钟角度来看,FIFO 可分为两种结构:单时钟 FIFO(同步 FIFO)和双时钟 FIFO(异步 FIFO)。
-
单时钟 FIFO:读写共用同一个时钟,所有输入信号的采样与输出信号的更新都在该时钟的上升沿完成,因此所有输入输出操作都与这一时钟保持同步。
-
双时钟 FIFO:读端和写端各自使用独立的时钟,写相关信号仅与写时钟
wr_clk
同步,读相关信号则仅与读时钟rd_clk
同步,实现跨时钟域的数据传输。
单时钟 FIFO 常用于片内同步数据的传输与缓存。
例如,FPGA 在控制外部传感器时,可以先以 2 MHz 的 SPI 速率快速读取 20 个数据,再通过 UART 以 9600 bps 的波特率依次发送。由于传感器读取速度远高于串口发送速度,需先将数据写入 FIFO 进行缓存,然后按串口速率慢速输出。读取与发送均可依赖同一时钟,因此适合采用单时钟结构的 FIFO。
双时钟 FIFO 则用于不同步时钟域之间的数据交换。
典型场景是高速采集系统:高速 ADC 以外部锁相环产生的 CLK1 采样,而 FPGA 内部运行在独立的 CLK2(如 125 MHz)。两者频率和相位互不关联,无法直接用 125 MHz 时钟采集 65 MHz 的数据,否则会产生速率不匹配及亚稳态等问题。借助双时钟 FIFO,可在写端同步于 CLK1、读端同步于 CLK2,实现跨时钟域的安全数据传输。
二、FIFO的常见参数和实现方式
FIFO 的常见关键参数包括:
-
宽度:一次读写操作的数据位数。
-
深度:可存储的 N 位数据的数量(若宽度为 N)。
-
满标志:当 FIFO 已满或接近满时输出,用于禁止继续写入以避免溢出。
-
空标志:当 FIFO 已空或接近空时输出,用于禁止继续读取以防读到无效数据。
-
读时钟:控制读操作的时钟信号,每个有效沿触发一次读取。
-
写时钟:控制写操作的时钟信号,每个有效沿触发一次写入。
实现 FIFO 的主要方式有三种:
-
自行编写逻辑:用户根据实际需求手动设计 FIFO 电路,适用于功能要求较特殊的场合。
-
采用第三方开源 IP 核:直接使用源码形式的开源 FIFO,实现速度快,并可在源码基础上做定制修改,以满足个性化需求。
-
使用 Xilinx Vivado 自带的免费 FIFO IP:Vivado 提供可视化配置界面,可轻松设置参数和结构,并针对不同系列器件做优化,功能齐全且稳定,系统设计中通常优先推荐这种方式。
三、FIFO IP配置流程
在IP库中找到FIFO
在 IP Location 设置中,可以更改生成的 IP 存放路径。此处保持默认路径,仅将 IP 名称修改为 fifo。
在 IP 配置界面中,将接口类型设为 Native。根据所用资源和读写时钟是否一致,FIFO 类型有多种可选。此示例中创建的是读写时钟独立且使用嵌入式 Block RAM 资源的 FIFO,因此选择 Independent Clocks Block RAM。
将读写数据位宽都设置为 8 bit(实际应用中读写位宽可不同),数据深度配置为 256 words。需要注意的是,虽然界面上设置为 256,但生成的 FIFO 实际有效深度只有 255。最终以工具显示的实际深度为准,这与 FIFO IP 的内部实现有关,无需深入探究,使用时留意即可。
在 Read Mode 设置中有两种可选模式:
-
Standard FIFO:只有在读使能信号有效后,数据才会输出。
-
First Word Fall Through (FWFT):当前数据会提前出现在数据线上,读使能到来时,下一个数据立即送出。本例选择 Standard FIFO 模式。
此处可保持默认选项,勾选 复位管脚、复位同步 以及 Enable Safety Circuit:
-
复位:重置数据输出和内部读写指针计数,复位后读写指针清零。
-
复位同步:将异步复位信号分别在读时钟域和写时钟域内先做同步,再执行各自的复位操作。
-
Safety Circuit:通过额外逻辑提高复位可靠性。启用后会多出
wr_rst_busy
和rd_rst_busy
两个信号,分别表示写/读时钟域处于复位忙状态。
使用时需注意:
-
只有当
wr_rst_busy
由 1 变为 0 后,且 FIFO 未满,才能开始写操作;wr_rst_busy
为 1 时禁止写入。 -
只有当
rd_rst_busy
由 1 变为 0 后才能开始读操作;rd_rst_busy
为 1 时禁止读取。
状态输出信号可按实际需求选择。为了便于实验观察波形,此处将空、将满以及读写端握手相关信号全部勾选。勾选后,FIFO 的输出端口会增加以下常见信号:
-
wr_ack:写入响应,当数据成功写入 FIFO 时输出高电平,表示写操作已完成。
-
overflow:上溢出标志,当 FIFO 已满仍继续写入时,该信号拉高提示溢出。
-
valid:读数据有效指示,在读操作时伴随数据输出拉高,高电平表示输出数据有效,可据此触发后续处理。
-
underflow:下溢出标志,当 FIFO 已空仍继续读出时,该信号拉高提示下溢。
空满信号的触发阈值可通过 Programmable Full Type 和 Programmable Empty Type 设置。默认配置为 No Programmable Full Threshold 和 No Programmable Empty Threshold,对应默认阈值依次为 253、252、2、3。
其逻辑可简单理解为:
-
Full 信号:当 FIFO 内数据量 ≥ 253 时置 1;在 full 为 1 的情况下,如果数据量降至 ≤ 252,full 信号恢复为 0。
-
Empty 信号:当 FIFO 内数据量从 0 增加到 ≥ 3 时,empty 信号变为 0;在 empty 为 0 的情况下,如果数据量降至 ≤ 2,empty 信号恢复为 1。
由此可见,full 和 empty 信号并非在 FIFO 完全写满或完全读空时才改变状态,而是预留了一定余量以提高系统稳定性。
以上默认设置是可修改的,通过 Programmable Full Type 和 Programmable Empty Type 可以选择不同模式进行配置。以 Programmable Full Type 为例:
-
Single Programmable Full Threshold Constant:仅可设置 Assert Value,Negate Value 保持默认不可改。
-
Multiple Programmable Full Threshold Constant:Assert Value 和 Negate Value 均可自定义设置。
-
Single Programmable Full Threshold Input Port:Assert Value 可通过输入端口动态设置,Negate Value 保持默认不可改,此时 FIFO 会多出一个输入端口 prog_full_thresh 用于输入阈值。
FIFO 提供数据量计数信号输出:
-
Write Data Count:同步于写时钟,表示 FIFO 内当前已写入的数据数量。
-
Read Data Count:同步于读时钟,表示 FIFO 内当前可读的数据数量。
计数信号的位宽可根据实际需求设置,此处保持默认 8 位。
完成上述设置后,可在 Summary 栏查看配置总结,内容包括存储器类型、数据位宽 8 bit、实际深度 255 以及已选择的状态信号等信息。
点击OK,再点击generate,生成IP ing。
四、FIFO IP 例化
在生成的IP source中,有IP的例化模板。
fifo your_instance_name (.rst(rst), // input wire rst.wr_clk(wr_clk), // input wire wr_clk.rd_clk(rd_clk), // input wire rd_clk.din(din), // input wire [7 : 0] din.wr_en(wr_en), // input wire wr_en.rd_en(rd_en), // input wire rd_en.dout(dout), // output wire [7 : 0] dout.full(full), // output wire full.almost_full(almost_full), // output wire almost_full.wr_ack(wr_ack), // output wire wr_ack.overflow(overflow), // output wire overflow.empty(empty), // output wire empty.valid(valid), // output wire valid.underflow(underflow), // output wire underflow.rd_data_count(rd_data_count), // output wire [7 : 0] rd_data_count.wr_data_count(wr_data_count), // output wire [7 : 0] wr_data_count.wr_rst_busy(wr_rst_busy), // output wire wr_rst_busy.rd_rst_busy(rd_rst_busy) // output wire rd_rst_busy
);