1、FIFO IP 核简介
FIFO(First In First Out,即先入先出),是一种数据缓冲器,用来实现数据先入先出的读写方式。与 ROM 或 RAM 的按地址读写方式不同, FIFO 的读写遵循“先进先出”的原则,即数据按顺序写入 FIFO,先被写入的数据同样在读取的时候先被读出,所以 FIFO存储器没有地址线。 FIFO 有一个写端口和一个读端口外部无需使用者控制地址,使用方便。
FIFO 存储器主要是作为缓存,应用在同步时钟系统和异步时钟系统中,在很多的设计中都会使用,后面实例中如:多比特数据做跨时钟域的转换、前后带宽不同步等都用到了FIFO。 FIFO 根据读写时钟是否相同,分为 SCFIFO(同步 FIFO)和 DCFIFO(异步FIFO), SCFIFO 的读写为同一时钟,应用在同步时钟系统中; DCFIFO 的读写时钟不同,应用在异步时钟系统中。
2、如何调用FIFO
直接在vivado中,点击左上角IP catalog选项,输入FIFO进行搜索,选择FIFO Generator进入FIFO的配置。
SCFIFO(同步 FIFO)和 DCFIFO(异步FIFO)两者的调用方法基本一致,只是在于是使用“common Clock Block RAM”还是使用“Independent Clock Block RAM”,这里我们以DCFIFO的调用为例。
使用块 RAM 来实现异步 FIFO;其中 Independent Clock 表示是异步FIFO, Block RAM 表示的是块 RAM 资源。
依次进行DCFIFO的各个参数配置,之后再生成产品即可。
3、顶层调用代码编写
在.v文件中调用FIFO模块(FIFO模块可在其.voe文件中找到,直接复制过来)。
`timescale 1ns / 1ps
module IP_DCFIFO1(
input wire wr_clk,
input wire rd_clk,
input wire rst_n,
input wire [7:0] din,
input wire wr_en,
input wire rd_en,
output wire [15:0] dout,
output wire full,
output wire empty,
output [6:0] rd_da_cnt,
output [7:0] wr_da_cnt
);
IP_dcfifo_8x256 ip_dcfifo1 (
.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 [15 : 0] dout
.full(full), // output wire full
.empty(empty), // output wire empty
.rd_data_count(rd_da_cnt), // output wire [6 : 0] rd_data_count
.wr_data_count(wr_da_cnt) // output wire [7 : 0] wr_data_count
);
endmodule
4、编写仿真代码
在仿真代码中给各个输入激励。
我们给予了两个异步的时钟信号,写时钟频率为50MHz,读时钟频率为25MHz。
在调用DCFIFO时,我们这里设置的是写入数据为8位宽,读出数据为16位宽,仿真时也这样设计,以便完整地显示出读出数。
下面将演示如何在跨时钟域工程中使用DCFIFO完成数据的写入读出。
`timescale 1ns / 1ps
module IP_DCFIFO_tb( );
reg wr_clk;
reg rd_clk;
reg rst_n;
reg [7:0] din;
reg wr_en;
reg rd_en;
//reg [1:0] cnt_baud;
reg full_reg0;
reg full_reg1;
wire [15:0] dout;
wire full;
wire empty;
wire [6:0] rd_da_cnt;
wire [7:0] wr_da_cnt;
initial
begin
wr_clk <= 1'b1;
rd_clk <= 1'b1;
rst_n <= 1'b0;
#20
rst_n <= 1'b1;
#100000
rst_n <= 1'b0;
end
always #10 wr_clk = ~wr_clk;
always #20 rd_clk = ~rd_clk;
always @(posedge wr_clk or rst_n)
if (rst_n == 1'b0)
din <= 8'd0;
else if ((din == 8'd255) && (wr_en == 1'd1))
din <= 8'd0;
else if (wr_en == 1'd1)
din <= din + 8'd1;
else
;
always @(posedge wr_clk or rst_n)
if (rst_n == 1'b0)
wr_en <= 1'd0;
else if ((full == 1'd1) && (rd_en <= 1'd1))
wr_en <= 1'd0;
else if((rd_en <= 1'd0)&& (empty <= 1'd1))
wr_en <= 1'd1;
else
;
//读取数据激励
always @(posedge rd_clk or rst_n)
if (rst_n == 1'b0)
begin
full_reg0 <= 1'd0;
full_reg1 <= 1'd0;
end
else //rd_clk打两拍,full仍然为高电平再进行读取
begin
full_reg0 <= full;
full_reg1 <= full_reg0;
end
always @(posedge rd_clk or rst_n)
if (rst_n == 1'b0)
rd_en <= 1'd0;
//如果 full 信号有效就立刻读,则不会看到 full 信号拉高,
//所以此处使用 full 在 rd_clk 时钟下打两拍后的信号
else if (full_reg1 == 1'd1)
rd_en <= 1'd1;
else if (empty == 1'b1)
rd_en <= 1'd0;
else
;
IP_DCFIFO1 ip_dcfifo_tb(
.wr_clk (wr_clk ),
.rd_clk (rd_clk ),
.rst_n (rst_n ),
.din (din ),
.wr_en (wr_en ),
.rd_en (rd_en ),
.dout (dout ),
.full (full ),
.empty (empty ),
.rd_da_cnt (rd_da_cnt),
.wr_da_cnt (wr_da_cnt)
);
endmodule
5、仿真结果