IP核的使用之FIFO(Vivado)
一、引言
在开始介绍FIFO IP核之前,我们先设想这么一个实际场景:FPGA内部有个16位计数器,以50MHz的频率计数,此时,我们希望随机截取计数器连续256个计数周期的值发到电脑上进行分析处理。用串口发送到电脑上。(数据产生速率大于数据使用速率),此时需要使用存储器先将这256个数据存储起来,再由串口慢慢发送到电脑。这时就引出了FIFO的使用场景即数据产生和使用速率不匹配。
二、FIFO IP核及相关内容扫盲
1.FIFO 简介
FIFO(First In First Out),即先进先出。FPGA 或者 ASIC 中使用到的 FIFO 一般指的是对数据的存储具有先进先出特性的一个缓存器,常被用于数据的缓存或者高速异步数据的交互。它与普通存储器的区别是没有外部读写地址线,这样使用起来相对简单,但缺点就是只能顺序写入数据,顺序的读出数据,其数据地址由内部读写指针自动加 1 完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。
2.FIFO 结构
FIFO 从读写时钟上来分有两类结构:单时钟 FIFO(同步 FIFO - SCFIFO)和双时钟 FIFO(异步FIFO - DCFIFO)。单时钟 FIFO 具有一个时钟(读写共用一个时钟)输入,因此所有输入信号的读取都是在这个时钟的上升沿进行的,所有输出信号的变化也是在这个时钟信号的上升沿的控制下进行的,即单时钟 FIFO 的所有输入输出信号都是同步这个时钟信号的。而在双时钟 FIFO结构中,写端口和读端口分别有独立的时钟,所有与写相关的信号都是同步于写时钟 wr_clk的,所有与读相关的信号都是同步于读时钟 rd_clk 的。下面图是双时钟 FIFO 的整体框图和内部实现的框图,有单独的模块对读写时钟域进行同步处理。
关于 FIFO 各个端口的详细功能解释可参考 Xilinx FIFO IP 手册。
3.FIFO 应用场景
(1) 单时钟 FIFO(同步 FIFO - SCFIFO)
**单时钟 FIFO 常用于片内数据交互。**例如,在 FPGA 的控制下从外部传感器读取到的一连串传感器数据,首先被写入 FIFO 中,然后再以 UART 串口波特率将数据依次发送出去。由于传感器的单次读取数据可能很快,但并不是时刻都需要采集数据,例如某传感器使用SPI 接口的协议,FPGA 以 2M 的 SPI 数据速率从该传感器中读取 20 个数据,然后以 9600的波特率通过串口发送出去。因为 2M 的数据速率远高于串口 9600 的波特率,因此需要将从传感器中采集到的数据首先用 FIFO 缓存起来,然后再以串口的数据速率缓慢发送出去。这里,由于传感器数据的读取和串口数据的发送都是可以同步于同一个时钟的,因此可以使用单时钟结构的 FIFO 来实现此功能。
(2) 双时钟 FIFO(异步FIFO - DCFIFO)
双时钟 FIFO 的一个典型应用就是异步数据的收发,所谓异步数据是指数据的发送端和接收端分别使用不同的时钟域。使用双时钟 FIFO 能够将不同时钟域中的数据同步到所需的时钟域系统中。例如,在一个高速数据采集系统中,实现将高速 ADC 采集的数据通过千兆以太网发送到 PC 机。ADC 的采样时钟(CLK1)由外部专用锁相环芯片产生,则高速 ADC 采样得到的数据就是同步于该 CLK1 时钟信号,在 FPGA 内部,如果 FPGA 工作时钟(CLK2)是由独立的时钟芯片加片上锁相环产生的,则 CLK1 和 CLK2就是两个不同域的时钟,他们的频率和相位没有必然的联系,假如 CLK1 为 65M,CLK2 为 125M,那么就不能使用 125M的数据来直接采集 65M 速率的数据,因为两者数据速率不匹配,在采集过程中会出现包括亚稳态问题在内的一系列问题,所以这里就可以使用一个具备双时钟结构的 FIFO 来进行异步数据的收发。如下图为使用 FIFO 进行异步数据收发的简易系统框图。
在此系统中,由于 ADC 的数据位宽为 8 位,基于 UDP 协议的以太网发送模块所需的数据也是 8 位,因此使用的是读写数据宽度相同的双时钟 FIFO 结构。假如 CLK1 的频率为20M,ADC 的数据位宽为 16 位,则可以使用读写数据位宽不同的双时钟 FIFO,在实现异步时钟域数据收发的同时,实现数据位宽的转换。通过设置双时钟 FIFO 的写入位宽为 16位,读取位宽为 8 位,则可以实现将 16 位的 ADC 数据转换为以太网支持的 8 位发送数据,然后通过以太网发送到 PC 机。
4.FIFO 常见参数
①FIFO 的宽度:即 FIFO 一次读写操作的数据位;
②FIFO 的深度:指的是 FIFO 可以存储多少个 N 位的数据(如果宽度为 N)。
③满标志:FIFO已满或将要满时由FIFO的状态电路送出的一个信号,以阻止FIFO的写操作继续向FIFO中写数据而造成溢出。
④空标志:FIFO已空或将要空时由FIFO的状态电路送出的一个信号,以阻止FIFO的读操作继续从FIFO中读出数据而造成无效数据的读出。
⑤读时钟:读操作所遵循的时钟,在每个时钟沿来临时读数据。
⑥写时钟:写操作所遵循的时钟,在每个时钟沿来临时写数据。
5.实现 FIFO 的方法
使用 FIFO 实现用户功能设计主要有三种实现方式。
第一种为用户根据需求自己编写 FIFO 逻辑,当对于 FIFO 的功能有特殊需求时,可以使用此种方式实现。
第二种方式为使用第三方提供的开源 IP 核,此种 IP 核以源码的形式提供,能够快速的应用到用户系统中,当用户对 FIFO 功能有特殊需求时,可以在此源码的基础上进行修改,以适应自己的系统需求。
第三种方式为使用 Xilinx Vivado 软件提供的免费 FIFO IP 核,此种方式下,Xilinx Vivado软件为用户提供了友好的图形化界面方便用户对 FIFO 的各种参数和结构进行配置,生成的 FIFO IP 核针对 Xilinx 不同系列的器件,还可以实现结构上的优化。由于该FIFO IP 核已经提供了大部分设计所需的所有功能,因此在系统设计中,推荐使用该 FIFO IP 核进行系统设计。
三、双时钟 FIFO(异步FIFO - DCFIFO)配置
打开 Vivado 软件,新建一个以名为 fifo_ip 的工程,然后在 Vivado 软件窗口左侧找到 IP Catalog 并 点 击, 在 右边 的 IP Catalog 窗 口 搜索 fifo , 在 下 面 搜 索 的结 果 中 找 到 Memories&Storage Elements 下 FIFO 中的 FIFO Generator 并双击。
进入到 FIFO 的设置界面
在 IP Location 可以修改 IP 生成的路径,这里保持默认路径,修改 IP 名称为 dcfifo。在 IP 设置界面对接口类型设置为 Native(常规接口),FIFO 类型上根据使用的资源以及读写时钟是否相同分为多种,这里创建一个独立读写时钟,使用嵌入式 Block RAM 资源的 FIFO,选择 Independent Clocks Block RAM(表示为异步FIFO,用的块RAM资源)。
写数据位宽均设置为 8bits(实际根据需要可以设置读写数据位宽不一样),数据深度为 256words。这里修改数据位宽及深度。需要注意的是设置数据后面有实际深度,这里虽然我们配置的是 256,但实际 FIFO 只有 255 的深度,这个是需要注意的,最终配置深度以后面实际深度为准,这个可能和 FIFO IP 内部设计有关,不过多讲解,实际使用时注意就行。
上面 Read Mode 有两种类型可选,Standard FIFO 是在给了读数据使能后,数据才出来,而 First Word Fall Through 模式是,当前数据提前已经到数据读数据线上,在读使能到来后,下一个数据会到数据线上,可以结合下面的时序图进行理解。这里选择 Standard FIFO 类型。
关于 ECC 这里不做讲解,需要了解的可以查看手册,在 Output Register 勾选并选择 Embedded Register,也可以选择 Fabric Register,或者两个寄存器都选加上,多加一个输出寄存器,输出就会多延迟一个时钟周期出来。
这里可以保持默认的勾选复位管脚、复位同步和使能 Safety Circuit,这里的复位是对数据输出以及内部读写指针计数等进行复位,复位后,读写指针清零。这里的复位同步功能是对异步输入的复位信号分别在读写时钟域内先进行同步后再进行读写的各自复位。使用 Safety Circuit 可以认为是一种更加可靠模式,内部通过额外的逻辑电路让 FIFO 复位的更加可靠,勾选 Enable Safety Circuit 后 fifo 模块的管脚会多出 wr_rst_busy 和 rd_rst_busy 两个信号输出,这两个信号分别表示的是写/读时钟域复位忙信号(为 1 表示忙,处于复位中,为 0表示复位完),所以每次给一个异步复位信号对 FIFO 进行复位时,需要等到 wr_rst_busy 从 1 变为 0 后才能对 FIFO 进行写数据操作(在 FIFO 非满情况下,这个是任何写操作时候都需要满足的),在 wr_rst_busy 为 1 时进行读是不允许的;同样的要等到 rd_rst_busy 从 1 变为 0 后才能对 FIFO 进行读操作,在 rd_rst_busy 为 1 时进行读是不允许的,这些是我们在使用 IP 过程需要注意的地方,有关具体时序波形可以到看后面仿真。
手册中还有提到一点,就是异步复位信号持续时间需要多长的问题,理论上 1 个较小的时钟周期(读写时钟周期中较小的那个,这里说的是周期较小的,也就是频率高的那个的)就行,实际使用推荐至少保持有 3 个或着设置同步级数个周期。这里提到的同步级数就是在 IP 设置的第一个界面中的 Synchronization Stages,这个只有在选择独立时钟 FIFO 类型时才会有这个设置,在相同时钟 FIFO 类型下是没有这个设置的。
①Full Flag Reset Value:设置在复位时,满信号处于什么值,这里保持默认的 1,也就是复位时认为是满,禁止写入。
②Dout Reset Value:设置复位时,FIFO 输出处于什么值,保持默认。
③Read Latency:这里会根据前面设置自动更新,前面我们设置输出加上一个寄存器,这里就变为了 2,不加寄存器的情况下是 1,加两个寄存器就是 3
状态输出信号的设置,包括将空和将满信号,读写端口握手信号,这里根据实际情况进行选择,这里作为实验演示就都勾选上,便于后面看波形。这里读写端口握手信号都勾选上后,会看到 FIFO 输出管脚会多出一些信号。
①wr_ack:写操作响应信号,当向 FIFO 写入数据时,如果正确写入到 FIFO 了,会输出
高电平表示数据已成功写入。
②overflow:上溢出标识信号,表示 FIFO 数据满情况下继续往 FIFO 里写数据,该信号会
拉高标志向上溢出。
③valid:读数据输出数据有效信号,在进行读操作时,valid 信号伴随数据输出而拉高,高
电平表示输出数据有效,用户逻辑可以根据该信号对读出的数据做后续进一步的处理使
用。
④underflow:下溢出标识信号,表示 FIFO 数据空情况下继续对 FIFO 进行读操作,该信
号会拉高标志向下溢出。
空满信号产生的数据个数的阈值设置,Programmable Full Type 和 Programmable Empty Type有多种类型可以选择。默认是No Programmable Full Threshold和No Programmable Empty Threshold,默认情况下设置值依次为 253,252,2,3。简单描述下就是当数据大于等于 253时,full 信号置 1,在 full 为 1 情况下,fifo 内数据量减小到 252 以下(包括 252)时,full 变为 0。同理对与空信号,当 FIFO 内数据量从 0 增加到 3(或大于 3)时,empty 信号变为 0,在 empty 为 0 的情况下,当 FIFO 数据量减少到 2(包括 2)时,empty 变为高。从这里可以看出,full 信号并不是在 FIFO 完全写满才置 1,留有一定的余量,同样 empty 也是一样,并非在 FIFO 完全读空才置 1。
当然这里只是默认设置,里面的值我们是可以更改设置的,通过 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。
④Multiple Programmable Full Threshold Input Port:Assert Value 和 Negate Value 均可由输入端口进行设置。这种情况下FIFO会多出两个输入端口分别为 prog_full_thresh_assert[7:0]和 prog_full_thresh_negate[7:0],用于设置 Assert Value 和 Negate Value。
FIFO 数据量计数信号输出,Write Data Count 和 Read Data Count 分别同步与写时钟和读时钟。位宽可以根据实际进行设置,这里保持默认位宽 8。
以上设置完后,可以在 Summary 一栏看到前面一些设置的总结,里面信息包括存储器类型,数据位宽 8bit,深度 255 以及一些状态信号的选择等信息。
点击界面左下角 OK,弹出Generate,生成FIFO IP核。
四、双时钟 FIFO(异步FIFO - DCFIFO)仿真验证
1.测试代码:
`timescale 1ns / 1ns
`define WR_CLK_PERIOD 10
`define RD_CLK_PERIOD 30
module dcfifo_tb();
reg rst;
reg wr_clk;
reg rd_clk;
reg [7:0]din;
reg wr_en;
reg rd_en;
reg [7:0] prog_full_thresh_assert;
reg [7:0] prog_full_thresh_negate;
wire [7:0]dout;
wire full;
wire almost_full;
wire wr_ack;
wire overflow;
wire empty;
wire almost_empty;
wire valid;
wire underflow;
wire [7:0]rd_data_count;
wire [7:0]wr_data_count;
wire prog_full;
wire wr_rst_busy;
wire rd_rst_busy;
dcfifo dcfifo_inst (
.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
.prog_full_thresh_assert(prog_full_thresh_assert), // input wire [7 : 0] prog_full_thresh_assert
.prog_full_thresh_negate(prog_full_thresh_negate), // input wire [7 : 0] prog_full_thresh_negate
.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
.almost_empty(almost_empty), // output wire almost_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
.prog_full(prog_full), // output wire prog_full
.wr_rst_busy(wr_rst_busy), // output wire wr_rst_busy
.rd_rst_busy(rd_rst_busy) // output wire rd_rst_busy
);
initial wr_clk = 1;
always #(`WR_CLK_PERIOD/2) wr_clk = ~wr_clk;
initial rd_clk = 1;
always #(`RD_CLK_PERIOD/2) rd_clk = ~rd_clk;
initial begin
rst = 1'b1;
wr_en = 1'b0;
rd_en = 1'b0;
din = 8'hff;
prog_full_thresh_assert = 8'd0;
prog_full_thresh_negate = 8'd0;
#(`WR_CLK_PERIOD*3+1);
rst = 1'b0;
prog_full_thresh_assert = 8'd253;
prog_full_thresh_negate = 8'd252;
@(negedge wr_rst_busy);
//write data
while(full == 1'b0)
begin
@(posedge wr_clk);
#1;
wr_en = 1'b1;
din = din - 1'b1;
end
wr_en = 1'b0;
wait(rd_rst_busy == 1'b0);
while(empty == 1'b0)
begin
@(posedge rd_clk);
#1;
rd_en = 1'b1;
end
rd_en = 1'b0;
//reset
#200;
rst = 1'b1;
#(`WR_CLK_PERIOD*3+1);
rst = 1'b0;
@(negedge wr_rst_busy);
wait(rd_rst_busy == 1'b0);
#2000;
$stop;
end
endmodule
2、仿真结果
五、单时钟 FIFO(同步FIFO - SCFIFO)仿真验证
选 FIFO 的类型,以及使用什么资源来实现。选择“Common Clock Block RAM”使用块 RAM 来实现同步 FIFO;其中 Common Clock 表示是同步 FIFO,Block RAM 表示的是块 RAM 资源。
其余设置可参考上文。文章来源:https://www.toymoban.com/news/detail-802045.html
参考视频:
小梅哥:FIFO模型与应用场景详解文章来源地址https://www.toymoban.com/news/detail-802045.html
到了这里,关于IP核的使用之FIFO(Vivado)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!