1.概述
网上有很多有关FIFO原理及其概述,这里给出三个比较好文章的提供参考:
https://blog.youkuaiyun.com/huigeyu/article/details/107331221
https://blog.youkuaiyun.com/alangaixiaoxiao/article/details/81432144
https://blog.youkuaiyun.com/weixin_46022434/article/details/105348433
以上的文章都是讲述通用的异步FIFO,但本文主要讲的是根据需求改写的异步FIFO
2.端口信号说明
名字 | 类型 | 说明 |
---|---|---|
data_width | parameter | FIFO的宽度 |
data_depth | parameter | FIFO的深度 |
addr_width | parameter | 地址位宽 |
read_width | parameter | 数据包位宽 |
rst_n | input | 复位 |
wr_clk | input | 写时钟,256Mhz |
wr_en | input | 写使能,信号是随机的,持续时间也是随机的,但至少5个写时钟周期 |
data_in | input | 数据输入,32bit |
rd_clk | input | 读时钟,128Mhz,是wr_clk二分频得到的 |
rd_en | input | 读使能,信号是随机的,持续时间也是随机的,但至少4个读时钟周期 |
out_empty | output | 空标志 |
out_full | output | 满标志 |
fifo_output | output | 数据输出,8bit,把32bit数据拆成4x8bit输出,每一个读时钟周期输出一个8bit的数据包 |
clk_32 | output | 将wr_clk八分频的时钟,用于其他模块使用 |
3.文件说明
名字 | 说明 |
---|---|
TOP | 顶层连接文件 |
div_2 | 二分频module,将wr_clk二分频给rd_clk |
div_8 | 八分频module,将wr_clk八分频给外面模块使用 |
a_FIFO | 异步FIFO模块 |
4.代码
a_FIFO代码:
//-----------------------------------------------------------------------------------------------
// Copyright :
// Author : Luk.wj
// File Name : a_FIFO
// Module Name : a_FIFO
// Create : 2021.07.15
// Revise : 2021.07.30
// Fuction :
//-----------------------------------------------------------------------------------------------
module a_FIFO #(
parameter data_width = 32,
parameter data_depth = 4,
parameter addr_width = 2,
parameter read_width = 8
) //FIFO数据宽度,深度;地址位宽;数据包位宽
(
input rst_n,
input wr_clk,
input wr_en,
input [data_width-1:0] data_in,
input rd_clk,
input rd_en,
output reg out_empty,
output reg out_full,
output reg [read_width-1:0] fifo_output
);
reg [1:0] count; //计数
reg rst_nw1; //异步复位,同步释放(write)
reg rst_nw2;
reg rst_nr1; //异步复位,同步释放(read)
reg rst_nr2;
reg [data_width-1:0] data_out; //32位输出
reg [addr_width:0] wr_addr_ptr; //地址指针,比地址多一位,MSB检测
reg [addr_width:0] rd_addr_ptr;
wire [addr_width-1:0] wr_addr; //地址
wire [addr_width-1:0] rd_addr;
wire [addr_width:0] wr_addr_gray; //地址指针对应的格雷码
reg [addr_width:0] wr_addr_gray_d1; //第一级寄存器锁存
reg [addr_width:0] wr_addr_gray_d2; //第二级寄存器锁存
wire [addr_width:0] rd_addr_gray;
reg [addr_width:0] rd_addr_gray_d1;
reg [addr_width:0] rd_addr_gray_d2;
reg [data_width-1:0] fifo_ram [data_depth-1:0]; //定义FIFO_RAM
reg flag_wr; //写标志
reg flag_rd; //读标志
reg flag_wr_ptr; //写指针标志
reg flag_rd_ptr; //读指针标志
reg rd_en_delay1; //读书使能打一拍,用于32bit转4x8bit输出
wire full;
wire empty;
//========================================================= 打一拍,用于32bit转4x8bit输出
always @(posedge rd_clk or negedge rst_nr2) begin
if (!rst_nr2) rd_en_delay1 <= 1'b0;
else rd_en_delay1 <= rd_en;
end
//========================================================= write Synchronized Asynchronous Reset
always @(posedge wr_clk or negedge rst_n) begin
if (!rst_n) begin
rst_nw1 <= 1'b0;
rst_nw2 <= 1'b0;
end
else begin
rst_nw1 <= 1'b1;
rst_nw2 <= rst_nw1;
end
end
//========================================================= read Synchronized Asynchronous Reset
always @(posedge rd_clk or negedge rst_n) begin
if (!rst_n) begin
rst_nr1 <= 1'b0;
rst_nr2 <= 1'b0;
end
else begin
rst_nr1 <= 1'b1;
rst_nr2 <= rst_nr1;
end
end
//========================================================= write fifo
always @(posedge wr_clk or negedge rst_nw2) begin
if (!rst_nw2)
flag_wr <= 1'b0;
else if(wr_en && (!full) && (!flag_wr)) begin
fifo_ram[wr_addr] <= data_in;
flag_wr <= 1'b1;
end
else if(!wr_en)
flag_wr <= 1'b0;
end
//======================================================== read_fifo
always@(posedge rd_clk or negedge rst_nr2) begin
if(!rst_nr2) begin
data_out <= 32'b0;
flag_rd <= 1'b0;
end
else if(rd_en && (!empty) && (!flag_rd)) begin
data_out <= fifo_ram[rd_addr];
flag_rd <= 1'b1;
end
else if(!rd_en)
flag_rd <= 1'b0;
end
always @(posedge rd_clk or negedge rst_nr2) begin
if (!rst_nr2) begin
count <= 2'b00;
fifo_output <= 8'b0;
end
else if(rd_en_delay1) begin
case (count)
2'b00 : begin
fifo_output <= data_out[7:0];
count <= count + 1;
end
2'b01 : begin
fifo_output <= data_out[15:8];
count <= count + 1;
end
2'b10 : begin
fifo_output <= data_out[23:16];
count <= count + 1;
end
2'b11 : begin
fifo_output <= data_out[31:24];
count <= 2'b00;
end
default: begin
count <= 2'b00;
fifo_output <= 8'b0;
end
endcase
end
else count <= 2'b00;
end
//地址为地址指针去掉第一位
assign wr_addr = wr_addr_ptr[addr_width-1:0];
assign rd_addr = rd_addr_ptr[addr_width-1:0];
//============================================================= wr_clk
always@(posedge wr_clk ) begin //格雷码同步化,两级寄存器锁存
rd_addr_gray_d1 <= rd_addr_gray;
rd_addr_gray_d2 <= rd_addr_gray_d1;
end
always@(posedge wr_clk or negedge rst_nw2) begin //写指针+1
if(!rst_nw2) begin
wr_addr_ptr <= 'b0;
flag_wr_ptr <= 1'b0;
end
else if(wr_en && (!full) && (!flag_wr_ptr)) begin
wr_addr_ptr <= wr_addr_ptr + 1;
flag_wr_ptr <= 1'b1;
end
else if(!wr_en)
flag_wr_ptr <= 1'b0;
end
//========================================================= rd_clk
always@(posedge rd_clk ) begin //格雷码同步化,两级寄存器锁存
wr_addr_gray_d1 <= wr_addr_gray;
wr_addr_gray_d2 <= wr_addr_gray_d1;
end
always@(posedge rd_clk or negedge rst_nr2) begin //读指针+1
if(!rst_nr2) begin
rd_addr_ptr <= 'b0;
flag_rd_ptr <= 1'b0;
end
else if(rd_en && (!empty) && (!flag_rd_ptr)) begin
rd_addr_ptr <= rd_addr_ptr + 1;
flag_rd_ptr <= 1'b1;
end
else if(!rd_en)
flag_rd_ptr <= 1'b0;
end
//========================================================== translation gary code
assign wr_addr_gray = (wr_addr_ptr >> 1) ^ wr_addr_ptr;
assign rd_addr_gray = (rd_addr_ptr >> 1) ^ rd_addr_ptr;
//========================================================== judge full or empty
assign full = (wr_addr_gray == {~(rd_addr_gray_d2[addr_width:1]),rd_addr_gray_d2[addr_width-2:0]}) ; //高两位不同,其余位相同
assign empty = (rd_addr_gray == wr_addr_gray_d2);
always @(posedge rd_clk or negedge rst_nr2) begin
if (!rst_nr2) out_empty <= 1'b0;
else out_empty <= empty;
end
always @(posedge wr_clk or negedge rst_nw2) begin
if (!rst_nw2) out_full <= 1'b0;
else out_full <= full;
end
endmodule
div_2代码:
//-----------------------------------------------------------------------------------------------
// Copyright :
// Author : Luk.wj
// File Name : div_2
// Module Name : div_2
// Create : 2021.08.17
// Revise : 2021.08.17
// Fuction : 时钟二分频
//-----------------------------------------------------------------------------------------------
module div_2 (
input wr_clk,
input rst_n,
output reg rd_clk
);
always @(posedge wr_clk or negedge rst_n) begin
if (!rst_n) rd_clk <= 1'b0;
else rd_clk <= ~rd_clk;
end
endmodule
div_8代码:
//-----------------------------------------------------------------------------------------------
// Copyright :
// Author : Luk.wj
// File Name : div_8
// Module Name : div_8
// Create : 2021.08.17
// Revise : 2021.08.17
// Fuction : 时钟八分频
//-----------------------------------------------------------------------------------------------
module div_8 (
input wr_clk,
input rst_n,
output reg clk_32
);
reg [1:0] cnt;
always @(posedge wr_clk or negedge rst_n) begin
if (!rst_n) begin
cnt <= 2'b0;
clk_32 <= 1'b0;
end
else if (cnt == 3) begin
cnt <= 2'b0;
clk_32 <= ~clk_32;
end
else cnt <= cnt + 1;
end
endmodule
TOP代码:
//-----------------------------------------------------------------------------------------------
// Copyright :
// Author : Luk.wj
// File Name : TOP
// Module Name : TOP
// Create : 2021.08.17
// Revise : 2021.08.17
// Fuction : 顶层连接文件
//-----------------------------------------------------------------------------------------------
module TOP (
input [31:0] data_in,
input rst_n,
input wr_en,
input rd_en,
input wr_clk,
output out_empty,
output out_full,
output [7:0] fifo_output,
output clk_32
);
wire rd_clk;
/* Define Relationship */
div_2 div2(
.wr_clk ( wr_clk ),
.rst_n ( rst_n ),
.rd_clk ( rd_clk )
);
div_8 div8(
.wr_clk ( wr_clk ),
.rst_n ( rst_n ),
.clk_32 ( clk_32 )
);
a_FIFO#(
.data_width ( 32 ),
.data_depth ( 4 ),
.addr_width ( 2 ),
.read_width ( 8 )
)FIFO(
.rst_n ( rst_n ),
.wr_clk ( wr_clk ),
.wr_en ( wr_en ),
.data_in ( data_in ),
.rd_clk ( rd_clk ),
.rd_en ( rd_en ),
.out_empty ( out_empty ),
.out_full ( out_full ),
.fifo_output ( fifo_output )
);
endmodule
5.测试&波形
测试代码:
//-----------------------------------------------------------------------------------------------
// Copyright :
// Author : Luk.wj
// File Name : a_FIFO_tb
// Module Name : a_FIFO_tb
// Create : 2021.07.20
// Revise :
// Fuction :
//-----------------------------------------------------------------------------------------------
`timescale 1 ns/ 1 ps
module TOP_tb (
);
// inputs
reg [31:0] data_in;
reg rd_en;
reg rst_n;
reg wr_clk;
reg wr_en;
// outputs
wire [7:0] fifo_output;
wire out_empty;
wire out_full;
wire clk_32;
localparam w_CYCLE = 4; //256Mhz
integer l_delay, h_delay, r_l_delay, i, j, k; // i,j,k分别是写使能个数,数据个数,读使能个数
integer flag_tb_f; // 用于标志判断的
TOP i1(
.data_in ( data_in ),
.rst_n ( rst_n ),
.wr_en ( wr_en ),
.rd_en ( rd_en ),
.wr_clk ( wr_clk ),
.out_empty ( out_empty ),
.out_full ( out_full ),
.fifo_output ( fifo_output ),
.clk_32 ( clk_32 )
);
// generate write clock
initial begin
wr_clk = 1'b0;
forever
#(w_CYCLE/2) wr_clk = ~wr_clk;
end
// reset
initial begin
rst_n = 1'b1;
#2
rst_n = 1'b0;
#10
rst_n = 1'b1;
end
// generate random data
initial begin
#1
data_in = 32'h0;
for (j = 0; j < 800; j = j + 1) begin
#(w_CYCLE) data_in = {$random} % 16 + 16 * ({$random} % 16) + 16**2 * ({$random} % 16) + 16**3 * ({$random} % 16)
+ 16**4 * ({$random} % 16) + 16**5 * ({$random} % 16) + 16**6 * ({$random} % 16) + 16**7 * ({$random} % 16);
end
end
// 写满标志显示
initial begin // 写满标志flag初始化
flag_tb_f = 0;
// flag_tb_r = 0;
end
always @(posedge wr_clk) begin
if (wr_en && (!flag_tb_f) && out_full) begin
$display("full happen time is %t", $time);
$display("full is %h, Full!!!", out_full);
flag_tb_f <= 1;
end
else if (!wr_en) begin
flag_tb_f <= 0;
end
end
// 写空标志显示
// always @(posedge rd_clk) begin
// if (rd_en && (!flag_tb_r) && out_empty) begin
// $display("empty happen time is %t", $time);
// $display("empty is %h, Empty!!!", out_empty);
// flag_tb_r <= 1;
// end
// else if (!rd_en) begin
// flag_tb_r <= 0;
// end
// end
// generate random wr_en
initial begin
wr_en = 1'b0;
#40
for (i = 0; i < 6; i = i + 1) begin
l_delay = 20 * (1 + {$random} % 4) + ({$random} % 10); // 低电平持续时间
h_delay = 20 * (2 + {$random} % 5) + ({$random} % 10); // 高电平持续时间
wr_en = 1'b1;
#h_delay
wr_en = 1'b0;
#l_delay;
end
#200
wr_en = 1'b1;
#103
wr_en = 1'b0;
#200
wr_en = 1'b1;
#89
wr_en = 1'b0;
end
// read data
initial begin
rd_en = 1'b0;
#900
for (k = 0; k < 7; k = k + 1) begin
r_l_delay = 30 * (3 + {$random} % 4) + ({$random} % 10); // 读使能低电平持续20 * (3 + 0-3) + 0-9
rd_en = 1'b1;
#(8*w_CYCLE)
rd_en = 1'b0;
#r_l_delay;
end
#400 $stop;
end
endmodule
波形: