使用软件: Vivado
参考文档:FIFO Generator v13.2
基础知识
FIFO(Fist In Fist Out),即为先进先出,常被用于数据的缓存或高速异步数据的交互,与普通存储器区别是没有外部读写地址线,使用简单,缺点是只能顺序写入数据,顺序的读出数据,其数据地址由内部读写指针自动加1完成,不能像普通存储器那样由地址线决定读取或写入某个指定的地址。
1. FIFO 结构
FIFO从读写时钟上看,分为两类:
(1)单时钟FIFO(同步FIFO)
读写共用一个时钟,即所有输入输出信号的变化都是由一个时钟信号控制。
(2)双时钟FIFO(异步FIFO)
读写各由一个时钟信号控制,写时钟:wr_clk,读时钟:rd_clk。
双时钟FIFO的整体框图和内部结构如下所示:
2. 应用场景
场景一
需求:
FPGA内部有个16位计数器,以50MHz频率计数,此时,希望随机截取计数器连续256个计数周期的值发到电脑上进行分析处理。
用串口发送到电脑上。(数据产生速率大于数据消费速率)
解决:
需要用存储器先将256个数据存储起来,再由串口慢慢发送到电脑。
对于FIFO来说,写入的数据是16位,读出的数据可能是16位或者8位
场景二
需求:
任意脉冲信号发生器:由电脑通过串口发送256个14位(16)的数据到FPGA,FPGA再把这256个数据,以50MHz的输出速率送给DAC,产生脉冲信号
问题:数据的产生速率小于数据消费速率
解决:
电脑发送速率最大为115200,小于FPGA发送的速率,需要FPGA将256个数据完全接收到并存储后,再一次性送给DAC输出
对于FIFO来说,写入的数据可能是16位(UART收到2个8位数据后,拼接起来一起写入)或者8位,读出的数据是16位
3. FIFO 常见参数
FIFO 的宽度:即 FIFO一次读写操作的数据位;
FIFO 的深度:指的是 FIFO可以存储多少个 N 位的数据(如果宽度为 N)。
满标志:FIFO已满或将要满时由 FIFO的状态电路送出的一个信号,以阻止FIFO的写操作继续向FIFO 中写数据而造成溢出。
空标志:FIFO 已空或将要空时由 FIFO 的状态电路送出的一个信号,以阻止FIFO的读操作继续从 FIFO 中读出数据而造成无效数据的读出。
读时钟:读操作所遵循的时钟,在每个时钟沿来临时读数据。
FIFO IP核的使用及仿真
双时钟 FIFO IP的配置
在Vivado软件的左侧项目管理栏下找到[IP Catalog],鼠标单击,在右边搜索框输入fifo搜索,选择[Memories&Storage Elements]下的[FIFO Generator],双击进入FIFO设置界面。
在Basic栏下进行配置
修改IP名字,为fifo,如下图所示
- 接口类型分为3种,一般选择Native类型
(1)Native:能够配置时钟域,用于写入和读取操作。
(2)AXI Memory Mapped:AXI指定写入通道和读取通道。
(3)AXI Stream:适合非基于地址的点对点应用,与使用此接口的其他IP进行连接。(如AXI4版本的DSP功能、FFT、DDS和FIR编译器等)
具体介绍看官方文档:FIFO Generator v13.2 - 创建一个独立的读写时钟,使用嵌入式Block RAM资源的FIFO,选择Independent Clocks Block RAM
对引脚进行配置
1. Read Mode选择Standard FIFO
对于Standard FIFO,在给了读数据使能后,数据才出来
对于First Word Fall Through,当前数据提前到了数据读数据线还是那个,在读使能到来后,下一个数据汇到数据线上,可以结合下面时序图来看:
Standard FIFO时序图:
First Word Fall Through时序图:
2. 读写数据位宽均设置为 8bits(实际根据需要可以设置读写数据位宽不一样),数据深度为 256words。
这里修改数据位宽及深度。需要注意的是设置数据后面有实际深度,这里虽然我们配置的是 256,但实际 FIFO 只有 255 的深度,这个是需要注意的,最终配置深度以后面实际深度为准,这个可能和 FIFO IP 内部设计有关,在仿真的时候可以看出来。
从下图中可以看到,在蓝线的位置,rd_clk为上升沿,检测到rd_en为1,隔了一个时钟周期,在黄线的位置,dout有了第一个有效输出值:01
3. 勾选[Output Register]
后面选择[Embedded Register]会使输出多一个寄存器,输出就会多延迟一个时钟周期出来,如果选择[Embedded Reg AND Fabric Reg]的话,输出会延迟2个时钟周期,从后面仿真图中可以看出。
4. 初始化设置
(1)这里可以保持默认的勾选复位管脚、复位同步和使能 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 过程需要注意的地方,有关具体时序波形可以到看后面仿真。
(2)Full Flag Reset Value:设置在复位时,满信号处于什么值,这里保持默认的1,也就是复位时认为是满,禁止写入。
Dout Reset Value:设置复位时,FIFO 输出处于什么值,保持默认。
Read Latency:这里会根据前面设置自动更新,前面我们设置输出加上一个寄存器,这里就变为了 2,不加寄存器的情况下是 1,加两个寄存器就是 3。
Status Flags 状态标志设置
状态输出信号的设置,包括将空和将满信号,读写端口握手信号,这里根据实际情况进行选择,这里作为实验演示就都勾选上,便于后面看波形。这里读写端口握手信号都勾选上后,会看到 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时,prog_full 信号置 1,在 prog_full 为 1 情况下,fifo 内数据量减小到 252 以下(包括 252)时,prog_full变为 0。
同理对与空信号,当 FIFO 内数据量从0增加到 3(或大于 3)时,prog_empty 信号变为0,在 prog_empty 为 0 的情况下,当 FIFO 数据量减少到 2(包括 2)时,prog_empty 变为高。
从这里可以看出,prog_full 信号并不是在 FIFO 完全写满才置 1,留有一定的余量,同样 prog_empty 也是一样,并非在 FIFO 完全读空才置 1。
Data Counts设置
FIFO 数据量计数信号输出,Write Data Count 和 Read Data Count 分别同步与写时钟和读时钟。位宽可以根据实际进行设置,这里保持默认位宽 8。
以上设置完后,可以在 Summary 一栏看到前面一些设置的总结,里面信息包括存储器类型,数据位宽 8bit,深度 255 以及一些状态信号的选择等信息。
点击界面左下角 OK,弹出界面 Generate,生成IP即可。
仿真验证
仿真代码:
`timescale 1ns / 1ps
//
// Create Date: 2022/10/26 20:42:41
// Design Name:
// Module Name: sim_fifo_ip
// Revision 0.01 - File Created
// Additional Comments:
//
//
/*
The computer sends data to FPGA at 20MHz and FPGA sends data to DAC at 50MHz
*/
`define WR_CYCLE 40 //write cycle is 40 ns = 25MHz
`define RD_CYCLE 20 //write cycle is 20 ns =50MHz
module sim_fifo_ip();
reg wr_clk=0,rd_clk=0;
reg[9:0] cnt1=0;
reg[7:0] din=8'b00000000;
reg wr_en=0,rd_en=0,rst=1;
wire[7:0] dout,rd_data_count,wr_data_count;
wire full,almost_full,wr_ack,overflow,empty,almost_empty,valid,underflow,prog_full,prog_empty,wr_rst_busy,rd_rst_busy;
always #(`WR_CYCLE/2) wr_clk=~wr_clk;
always #(`RD_CYCLE/2) rd_clk=~rd_clk;
initial begin
#(`WR_CYCLE*8+1);
rst=0;
@(negedge wr_rst_busy);
//write data
while(full==0)
begin
@(posedge wr_clk);
#1;
wr_en=1;
din=din+1'b1;
end
//write a data again
//多写2个数据:
@(posedge wr_clk);
din=2;
@(posedge wr_clk);
@(posedge wr_clk);
din=3;
@(posedge wr_clk);
@(posedge wr_clk);
wr_en=0;
@(posedge rd_clk);
//enable read
wait(wr_rst_busy==1'b0);
#2000;
while(empty==0)
begin
@(posedge rd_clk)
begin
#1;
rd_en=1;
end
end
//多读一个数据看看变化
@(posedge rd_clk);
rd_en=0;//读使能置零
//reset,观察复位时间
#200;
rst = 1'b1;
#(`WR_CYCLE*3+1);
rst = 1'b0;
@(negedge wr_rst_busy);
wait(rd_rst_busy == 1'b0);
#2000;
$stop;
end
fifo fifo_testbench(
.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
.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
.prog_empty(prog_empty), // output wire prog_empty
.wr_rst_busy(wr_rst_busy), // output wire wr_rst_busy
.rd_rst_busy(rd_rst_busy) // output wire rd_rst_busy
);
endmodule
为了测试仿真编写测试激励文件,添加并新建仿真文件命名为 sim_fifo_ip.v。
上面创建的FIFO 是独立时钟,首先分别产生写时钟信号 wr_clk 和读时钟信号 rd_clk,周期时间值通过宏定义 define 分别设置为 40ns 和 20ns。
仿真中首先是产生一段时间的复位信号(高电平复位),等到写复位忙信号 wr_rst_busy 从 1 变为 0(等待 wr_rst_busy 下降沿),说明写复位已经做完,
然后通过一个 while 循环在 FIFO 没有满的情况下,一直往 FIFO 里写入数据,数据值从 254 递减,为了让仿真波形更加贴合实际,写使能信号在时钟上升沿之后延时 1ns 给高电平,这样能更加清晰看到 FIFO 各信号之间的关系。在 FIFO 写满之后将写使能置 0,停止写入数据。
然后多写入两个数据,看看仿真结果。
读操作类似,前面进行过一次复位操作,所以先确保读复位忙信号rd_rst_busy 已经为 0,然后在 fifo 为非空的情况下进行读数据操作,直到 FIFO读空就停止进行读操作。然后多读一个数据,看看仿真情况。
之后又对复位进行一次测试,等复位完后延时一段时间就停止仿真。
仿真结果及分析
-
仿真一开始进行了一段时间的复位(rst 拉高大概 8 个写时钟周期),这个时候 FIFO 就进入到 FIFO 操作,之后 wr_rst_busy 和 rd_rst_busy 也先后被拉高,说明读写都进入到复位过程,复位期间 full 和 almost_full 信号会变为高电平。这里复位期间 full 为高为低是之前在IP 配置过程中进行设置的,默认是复位时 full 为高,因为复位情况下是不允许进行写操作的,full 为高可以避免写入数据,如果有特殊要求也可以将这里设置为 0.
-
在蓝线处开始写入数据,但是在其下一个上升沿中wr_ack才变为1,表示写入成功,再下一个上升沿显示wr_data_count=1,表示写入数据的数量;
-
写复位完成后,接下来是在非满情况下进行写数据操作,直到 FIFO 的 full 信号被拉高就停止写操作。多写一个‘2’,则overflow置1,表示溢出,此时数字‘2’不会被写入FIFO中。almost_full是提前一个计数值(253-1),出现高电平.
-
从波形上可以看到,读出数据和对应的数据 valid 信号延迟读使能一个时钟周期出来。之所以延迟一个时钟周期出来,与前面 IP 配置选择增加一个输出寄存器是一致的。
-
rd_data_count 信号的变化延迟读使能一个周期出现,该信号同步于读端口时钟(wr_data_count 信号是同步于写端口时钟)。
-
FIFO 空信号标识信号 empty 是在rd_data_count计数值为 2 的时候变为高电平,与前面 IP 配置是一致的。从波形上可以看到 almost_empty提前一个 wr_data_count 计数值变为高电平(这里 2+1=3)
underflow 信号是 FIFO 读溢出标志信号,从读数据的波形看到 FIFO 读空后,未再进行读数据操作,没出现读溢出,所以 underflow 信号一直为低电平,如果此时再对 FIFO 进行一次读数据操作,underflow 信号就会变为高电平。
文章来源:https://www.toymoban.com/news/detail-595870.html -
在仿真最后又对 FIFO 进行了一次复位,等复位完成后过了 2000ns 就停止仿真。
文章来源地址https://www.toymoban.com/news/detail-595870.html
到了这里,关于FPGA — FIFO学习笔记的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!