异步FIFO的设计&VIVADO FIFO IP CORE的使用

Scroll Down

前言

文章标题其实是两件事,FIFO可以自己写也可以用自带的IP核。但是都是关于FIFO的设计和实现,所以就放在这一篇文章里了。

FIFO(First Input First Output)先入先出队列

Snipaste_20200525_230553.jpg
FIFO的原理很好理解,先进来的数据先出去,只按照顺序读写,也就是所谓的队列。宽度(Width)和深度(Deepth)分别代表FIFO的数据位宽和能够寄存的数据队列长度。FIFO的读写需要由时钟控制,当读取或者写入速度快过比另一方快时,就会出现FIFO的写满或者读空的情况,这个时候就需要引入满(full)和空(empty)信号来控制外部对FIFO的继续读写。

同步和异步FIFO

FIFO的同步和异步指的是读写时钟是否相同,如果读写时钟一致则是同步FIFO,如果读写时钟相互独立则为异步FIFO。在实际使用中,用到异步FIFO的情况会更多一些。

FIFO的主要用途

其实通过前面的说明也不难看出,FIFO其实就是起到一个数据的缓冲作用,让数据排个队。

  • 跨时钟域的数据通信

比如说高速模块的多Bit数据要传递给低速的模块,如果直连的话显然会有数据丢失,但是经过FIFO缓冲后就能实现跨时钟域的通信,显然这里会用到异步FIFO:高速模块写入接入高速时钟,低速模块读取接入低速时钟。我们知道单Bit信号的跨时钟域处理可以通过将信号通过两级DFF来实现,显然这里和异步FIFO是一个道理。具体的例子比如说AD采样数据和内部逻辑的通信;DDR3内部数据通过千兆网络发送时。

  • 同频反向的数据

当收发模块的时钟是同频,但是相位关系不确定,为保险起见也可以引入FIFO,那么这里用到的还是异步FIFO。

异步FIFO的Verilog逻辑实现

设计思路

  • 读写时钟跨时钟域的同步

  • 读写地址的比较

  • 写满和读空信号的产生

Code

timescale 1ns/1ns

module async_fifo #(
    parameter U_DLY = 1,
    parameter DATESIZE = 8,
    parameter ADDRSIZE = 4,
    parameter ALMOST_GAP = 3
)(
/*==========================Write&Read=========================*/
    input            [DATASIZE-1:0] wr_dat                      , 
    input                           wr_en                       , 
    input                           wr_clk                      , 
    input                           wr_rst_n                    ,
    input                           rd_en                       , 
    input                           rd_clk                      , 
    input                           rd_rst_n                    , 
    output           [DATASIZE-1:0] rd_dat                      , 
/*===========================Full&Empty Flag===================*/
    output reg                      full                        , 
    output reg                      empty                       , 
    output reg                      almost_full                 , 
    output reg                      almost_empty                 

);
//Local Parameter
//Pre Set;16 Depth With 4 Bit Address  
localparam DEPTH = 1 << ADDRSIZE; 

//Register Define

reg [ADDRSIZE:0] wr_ptr;
reg [ADDRSIZE:0] rd_ptr;

reg [DATASIZE-1:0] memo [0:DEPTH-1];

//Read Clock Sync
reg [ADDRSIZE:0] rd_ptr_dly1;
reg [ADDRSIZE:0] rd_ptr_dly2;
//Write Clock Sync
reg [ADDRSIZE:0] wr_ptr_dly1;
reg [ADDRSIZE:0] wr_ptr_dly2; 

//Wire Define
wire [ADDRSIZE-1:0] wr_addr;
wire [ADDRSIZE-1:0] rd_addr;
//Full&Empty Flag Value
wire empty_val;
wire full_val;


/*===========Read & Write Data From/To Memory==============*/
assign rd_dat = memo[rd_addr];
always @ (posedge wr_clk)
begin
    if((wr_en == 1'b1) && (full == 1''b0))
        memo[wr_addr] <= #U_DLY wr_dat;
end

/*===============Read Clock Domain Sync===================*/
always @ (posedge wr_clk or negedge wr_rst_n)
begin
    if(wr_rst_n == 1''b0)
    begin
        rd_ptr_dly1 <= #U_DLY 1'b0;
        rd_ptr_dly2 <= #U_DLY 1'b0;
    end
    else
    begin
        rd_ptr_dly1 <= #U_DLY rd_ptr;
        rd_ptr_dly2 <= #U_DLY rd_ptr_dly1;
    end
end
/*================Write Clock Domain Sync================*/
always @ (posedge rd_clk or negedge rd_rst_n)
begin
    if(rd_rst_n == 1''b0)
    begin
        wr_ptr_dly1 <= #U_DLY 1'b0;
        wr_ptr_dly2 <= #U_DLY 1'b0;
    end
    else
    begin
        wr_ptr_dly1 <= #U_DLY rd_ptr;
        wr_ptr_dly2 <= #U_DLY rd_ptr_dly1;
    end
end


```