目录
- 一、FIFO简介
- 1.定义
- 2.特点
- 3.分类
- 4.FIFO在FPGA中的应用
- 二、实验任务
- 三、FIFO IP核
- 1.接口
- 2.写时序
- 3.读时序
- 1.Standara
- 2 .FWFT
- 四、vivado设置
- 五、程序设计
- 1.模块
- 2.时序
- 3.异步信号传输
- 4.RTL代码
- 五、仿真
- 1.Testbench代码
- 2.波形
一、FIFO简介
1.定义
FIFO是一种先进先出的数据缓存队列,主要特点是数据顺序写入,再按照同样的顺序输出数据,即先进去的数据先被取出来。
2.特点
- 无需外部读写地址线:FIFO的使用非常简单,因为它不需要外部读写地址线,数据会自动按照先进先出的顺序进行存取。
- 数据顺序性:FIFO保证了数据的顺序性,即先写入的数据会先被读取出来。
- 缓存作用:FIFO可以作为数据缓存,当数据源和数据接收端的速度不一致时,FIFO可以起到缓冲数据的作用,避免数据丢失或溢出。
3.分类
根据FIFO输入时钟的区别,可以分为同步FIFO和异步FIFO:
同步FIFO:只有一个独立的时钟端口clock,所有的输入输出信号都同步于clock信号。
异步FIFO:有两个时钟,写端口和读端口分别有独立的时钟,所有写相关的信号都属于写时钟,所有与读相关的信号都属于读时钟。
4.FIFO在FPGA中的应用
FIFO在FPGA开发中的应用非常广泛,主要包括以下几个方面:
- 数据传输同步:在数据传输过程中,由于不同的设备或处理器的时钟频率可能不同,因此需要进行同步处理。FIFO作为一种缓冲元件,可以将异步数据进行存储,并在同步时按照先进先出的顺序输出,确保数据的正确传输。
- 网络协议转换:在计算机网络中,不同的协议可能具有不同的数据包格式和传输速率。使用FIFO可以将一种协议的数据包转换为另一种协议的数据包,实现协议转换。
- 数据压缩与信号处理:在处理大量数据时,FIFO可以用于存储待压缩的数据,并控制数据的压缩顺序。在信号处理中,FIFO可以用于缓存信号数据,并对数据进行预处理和后处理。
- 并行数据处理:在并行数据处理中,多个处理器或线程同时对数据进行处理。FIFO可以用于协调不同处理器之间的数据传输,并确保数据的顺序和完整性。
二、实验任务
生成一个异步FIFO,当FIFO为空时,向FIFO写入数据,直到将FIFO写满后停止写操作,当FIFO为满时,从FIFO中读数据,直到FIFO被读空后停止读操作。将FIFO的深度和宽度分别设置为256和8进行读写测试
三、FIFO IP核
笔者参考了xlinx的pg_057参考文档,针对于本博客需要的知识来向读者进行分享,详细说明请参考xlinx提供的官方文档。
1.接口
xlinx提供的IP核有如下接口,笔者标注了每个接口信号的基本功能
2.写时序
3.读时序
需要注意的是,在FIFO中读有两种模式,Standard模式和**First-Word Fall-Through(FWFT)**模式,关于这两种模式的区分,xlinx文档已经给出了很详尽的解释
笔者将通过时序图来向大家简单介绍一下这两种模式的区分
1.Standara
2 .FWFT
学习完上诉知识后,我们来正式开始今天的实验任务,设计一个异步FIFO
四、vivado设置
其余保持默认即可
五、程序设计
1.模块
顶层模块
写模块
读模块
关于各个模块的接口,建议读者首先根据自身的任务要求,其次可参考vivado ip核中的例化模板,可知道ip核的输入输出端口,即可做好模块的划分
2.时序
写模块时序
读模块时序
3.异步信号传输
- 对于单bit信号,我们一般采用打拍的方法,通过打两拍来将异步的信号变为同步,例如在本任务中,full信号是写模块输出的,其时钟和读模块时钟不一致,因此需要对full进行打两拍,来使得full信号与读模块时序一致,同理empty信号也是一样
- 对于多bit信号,我们则用FIFO来进行信号的传输,哈哈哈哈哈,突然感觉知识产生了闭环对吧,挺有意思
4.RTL代码
写模块
module fifo_wr (
input wr_clk, //写时钟
input rst_n, //复位信号
input wr_rst_busy, //写忙信号
input almost_full, //满前一个
input empty, //空信号
input wr_data_count, //计数信号
output reg wr_en, //使能信号
output reg [7:0] wr_data //写数据
);
//打拍
reg empty_d0;
reg empty_d1;
//打两拍,使得异步单比特信号变成同步
always @(posedge wr_clk or negedge rst_n) begin
if(!rst_n) begin
empty_d0 <= 1'b0;
empty_d1 <= 1'b0;
end
else begin
empty_d0 <= empty;
empty_d1 <= empty_d0;
end
end
//使能信号赋值
always @(posedge wr_clk or negedge rst_n) begin
if(!rst_n)
wr_en <= 1'b0;
else if(!wr_rst_busy) begin
if(empty_d1)
wr_en <= 1'b1;
else if(almost_full)
wr_en <= 1'b0;
end
else
wr_en <= 1'b0;
end
//写数据
always @(posedge wr_clk or negedge rst_n) begin
if(!rst_n)
wr_data <= 8'd0;
else if(wr_en == 1 && wr_data < 8'd254)
wr_data <= wr_data + 8'd1;
else
wr_data <= 8'd0;
end
endmodule
读模块
module fifo_rd(
input rd_clk,
input rst_n,
input full,
input rd_rst_busy,
input almost_empty,
input [7:0] rd_data,
output reg rd_en
);
reg full_d0;
reg full_d1;
//打拍
always @(posedge rd_clk or negedge rst_n) begin
if(!rst_n) begin
full_d0 <= 1'b0;
full_d1 <= 1'b0;
end
else begin
full_d0 <= full;
full_d1 <= full_d0;
end
end
//对rd_en进行赋值,FIFO写满之后开始读,读空之后停止读
always @(posedge rd_clk or negedge rst_n) begin
if(!rst_n)
rd_en <= 1'b0;
else if(!rd_rst_busy) begin
if(full_d1)
rd_en <= 1'b1;
else if(almost_empty)
rd_en <= 1'b0;
end
else
rd_en <= 1'b0;
end
endmodule
顶层模块
module ip_fifo(
input sys_clk,
input sys_rst_n
);
wire clk_50m ; // 50M时钟
wire clk_100m ; // 100M时钟
wire locked ; // 时钟锁定信号
wire rst_n ; // 复位,低有效
wire wr_rst_busy ; // 写复位忙信号
wire rd_rst_busy ; // 读复位忙信号
wire wr_en ; // FIFO写使能信号
wire rd_en ; // FIFO读使能信号
wire [7:0] wr_data ; // 写入到FIFO的数据
wire [7:0] rd_data ; // 从FIFO读出的数据
wire almost_full ; // FIFO将满信号
wire almost_empty ; // FIFO将空信号
wire full ; // FIFO满信号
wire empty ; // FIFO空信号
wire [7:0] wr_data_count ; // FIFO写时钟域的数据计数
wire [7:0] rd_data_count ; // FIFO读时钟域的数据计数
assign rst_n = sys_rst_n & locked;
clk_wiz_0 u_clk_wiz_0
(
.clk_out1 (clk_50m),
.clk_out2 (clk_100m),
.locked (locked),
.clk_in1 (sys_clk)
);
//例化写FIFO模块
fifo_wr u_fifo_wr(
.wr_clk (clk_50m),// 写时钟
.rst_n (rst_n), // 复位信号
.wr_rst_busy (wr_rst_busy),// 写复位忙信号
.almost_full (almost_full ),
.empty (empty),
.wr_data_count (wr_data_count ),
.wr_en (wr_en),
.wr_data (wr_data)
);
//例化读FIFO模块
fifo_rd u_fifo_rd(
.rd_clk (clk_100m),
.rst_n (rst_n),
.full (full),
.rd_rst_busy (rd_rst_busy),
.almost_empty (almost_empty),
.rd_data (rd_data),
.rd_en (rd_en)
);
//例化FIFO IP核
fifo_generator_0 u_fifo_generator_0(
.rst (~rst_n), //比较奇怪
.wr_clk (clk_50m), //写时钟
.rd_clk (clk_100m), //读时钟
.din (wr_data),
.wr_en (wr_en),
.rd_en (rd_en),
.dout (rd_data),
.full (full),
.almost_full (almost_full),
.empty (empty),
.almost_empty (almost_empty),
.rd_data_count (rd_data_count),
.wr_data_count (wr_data_count),
.wr_rst_busy (wr_rst_busy),
.rd_rst_busy (rd_rst_busy)
);
endmodule
FIFO ip核和 clocking wizard ip核在顶层模块中调用即可
需要注意的是:我们会发现,这个顶层模块也太难写了,第一眼看完全无从下手,这里笔者认为,根据顶层模块框图来对各个模块进行例化即可不那么模糊,至于前面变量的调用,缺啥补啥就可以了,都是wire线网类型,对各个变量进行提前说明即可,这是笔者的一些小技巧。
五、仿真
1.Testbench代码
`timescale 1ns/1ns //仿真的单位/仿真的精度s
module tb_ip_fifo();
parameter CLK_PERIOD = 20;
reg sys_clk; //周期20ns
reg sys_rst_n;
initial begin
sys_clk <= 1'b0;
sys_rst_n <=1'b0;
#200
sys_rst_n <= 1'b1;
end
always #(CLK_PERIOD/2) sys_clk = ~sys_clk;
ip_fifo u_ip_fifo (
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n)
);
endmodule
因为顶层模块只有两个激励,所以仿真测试代码还是很好写的
2.波形
笔者是利用vivado和modelsim来进行联合仿真
写模块
读模块
仿真验证正确