目录
一、FIFO简介
FIFO
的英文全称是
First In First Out
,即先进先出。
FPGA
使用的
FIFO
一般指的是对数据的存储具有先进先出特性的一个缓存器,常被用于数据的缓存或者高速异步数据的交互,也即所谓的跨时钟域信号传递。它与 FPGA
内部的
RAM
和
ROM
的区别是没有外部读写地址线,采取顺序写入数据,顺序读出数据的方式,使用起来简单方便,由此带来的缺点就是不能像 RAM 和
ROM
那样可以由地址线决定读取或写入某个指定的地址。
FIFO
从输入时钟的角度来分,有两种类型:
单时钟 FIFO和双时钟 FIFO;单时钟 FIFO 具有一个独立的时钟端口 clock,因此,所有的输入输出信号都同步于 clock 信号。双时钟 FIFO 结构中,写端口 和读端口分别有独立的时钟,所有与写相关的信号都是同步于写时钟 wr_clk,所有与读相关 的信号都是同步于读时钟 rd_clk。
二、FIFO的应用
1. 单时钟 FIFO 常用于同步时钟的数据缓存;
2. 双时钟 FIFO
常用于跨时钟域的数据信号的传递,例如时钟域
A
下的数据
data1
传递 给异步时钟域 B
,当
data1
为连续变化信号时,如果直接传递给时钟域
B
则可能会 导致收到的数据不是发送的数据的情况,即在采集过程中会出现包括亚稳态(数据采样失真)问题在内的一系列问题,使用双时钟 FIFO
能够将不同时钟域中的数据同步到所需的时钟域中
。
三、Vivado FIFO创建
1、新建工程后进入以下界面,点击IP Catalog,在右侧界面搜索fifo,找到FIFO Cenerator打开。
2、在FIFO IP核配置界面,Component Name中可以更改命名,Fifo Impiementation中可以选择单时钟(Common Clock Block RAM)和双时钟(Independent Clocks Block RAM),这里我选择双时钟。
3、模式选择有标准模式和FWFT模式,标准模式中读取的数据滞后读信号一个时钟周期;FWFT模式中读信号有效时,读取的数据也立即有效。这里我选择标准模式,设置读写数据位宽为16,数据深度为512,根据需要自行设置。
4、读写计数界面,Write Data Count表示FIFO已经写入多少数据,Read Data Count表示FIFO中有多少数据可以读。点击“OK”,完成异步FIFO 配置。
四、FIFO IP核实例化
1、Sources里的fifo_ip.veo文件中是IP的例化模板。我们只需要将文件中内容复制粘贴到我们verilog程序中,对IP进行实例化。。
2、我们在创建一个顶层设计文件来实例化这个FIFO IP, 编写pll_test.v代码如下。
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2023/03/31 14:59:11
// Design Name:
// Module Name: fifo_test
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module fifo_test(
input wr_clk ,//写 FIFO 时钟
input rd_clk ,//读 FIFO 时钟
input rst_n ,//复位
input [7:0] wr_din ,//写入 FIFO 的数据
input wr_en ,//写使能
input rd_en ,//读使能
output reg [7:0] rd_dout,//从 FIFO 读出的数据
output reg rd_out_vld //从 FIFO 读出的数据有效指示信号
);
//信号定义
wire [7:0] wr_data ;
wire [7:0] q ;
wire wr_req ;
wire rd_req ;
wire rd_empty ;
wire wr_full ;
wire [7:0] wrusedw ;
wire [7:0] rdusedw ;
//FIFO 例化
fifo_ip your_instance_name (
.wr_clk (wr_clk ), // input wire wr_clk
.rd_clk (rd_clk ), // input wire rd_clk
.din (wr_data ), // input wire [15 : 0] din
.wr_en (wr_req ), // input wire wr_en
.rd_en (rd_req ), // input wire rd_en
.dout (q ), // output wire [15 : 0] dout
.full (wr_full ), // output wire full
.empty (rd_empty ), // output wire empty
.rd_data_count(rdusedw ), // output wire [8 : 0] rd_data_count
.wr_data_count(wrusedw ) // output wire [8 : 0] wr_data_count
);
assign wr_data = wr_din;//输入的数据
assign wr_req = (wr_full == 1'b0)?wr_en:1'b0;//非满才写
assign rd_req = (rd_empty == 1'b0)?rd_en:1'b0;//非空才读
always @(posedge rd_clk or negedge rst_n)begin
if(!rst_n)begin
rd_dout <= 0;
end
else begin
rd_dout <= q;
end
end
always @(posedge rd_clk or negedge rst_n)begin
if(!rst_n)begin
rd_out_vld <= 1'b0;
end
else begin
rd_out_vld <= rd_req;
end
end
endmodule
五、对实例化顶层文件仿真
1、我们在创建一个仿真激励文件来仿真这个fifo_test顶层文件, 编写fifo_test_tb.v代码如下。
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2023/03/31 15:11:44
// Design Name:
// Module Name: fifo_test_tb
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module fifo_test_tb();
//时钟和复位
reg wr_clk ;
reg rd_clk ;
reg rst_n ;
//输入信号
reg [7:0] wr_din;
reg wr_en ;
reg rd_en ;
//输出信号
wire rd_out_vld ;
wire [7:0] rd_dout ;
//参数定义
parameter WR_CYCLE = 20;//写时钟周期,单位为 ns,
parameter RD_CYCLE = 30;//读时钟周期,单位为 ns,
parameter RST_TIME = 3 ;//复位时间,此时表示复位 3 个时钟周期的时间。
//待测试的模块例化
fifo_test u_fifo_test(
.wr_clk (wr_clk ),//时钟
.rd_clk (rd_clk ),
.rst_n (rst_n ),//复位
.wr_din (wr_din ),//写入 FIFO 的数据
.wr_en (wr_en ),//写使能
.rd_en (rd_en ),//读使能
//输出信号定义
.rd_dout (rd_dout ),//从 FIFO 读出的数据
.rd_out_vld (rd_out_vld )//从 FIFO 读出的数据有效指示信号
);
integer i = 0;
//生成本地时钟 50M
initial wr_clk = 0;
always #(WR_CYCLE/2) wr_clk=~wr_clk;
initial rd_clk = 0;
always #(RD_CYCLE/2) rd_clk=~rd_clk;
//产生复位信号
initial begin
rst_n = 1;
#2;
rst_n = 0;
#(WR_CYCLE*RST_TIME);
rst_n = 1;
end
//输入信号赋值
initial begin
#1;
//赋初值
wr_din = 0;
wr_en = 0;
#(100*WR_CYCLE);
//开始赋值
for(i=0;i<500;i=i+1)begin
wr_din = {$random};
wr_en = {$random};
#(1*WR_CYCLE);
end
#(100*WR_CYCLE);
end
initial begin
#1;
//赋初值
rd_en = 0;
#(120*RD_CYCLE);
//开始赋值
for(i=0;i<500;i=i+1)begin
rd_en = {$random};
#(1*RD_CYCLE);
end
#(100*RD_CYCLE);
$stop;
end
endmodule
2、仿真结果如下,输出rd_dout[7:0]的结果和输入wr_din[7:0]一致。