异步FIFO

一、原理介绍

这里我就不多写原理了(Doreen大佬写的很好),主要记录一下自己学习异步FIFO过程中的理解。

1.1 ram读写

看“声明”中的帖子时,发现同步FIFO和异步FIFO在读写ram时有一个明显的区别。即同步FIFO读写ram需要外部输入的读写信号以及空满信号联合判断能否读写,而异步FIFO是通过外部输入的读写信号以及空满信号联合对读写指针的自增进行限制,从而达到控制是否读写的目的。

由于空满标志的判断需要将读写指针同步到对方的时钟域下,要打两拍,所以说空满标志的判断是不及时的。例如FIFO深度1024,写入1024个数据后,开始读出数据。当读出一个数据后,就应该可以继续写入了,但由于读地址有两拍的滞后性,此时出现“假满”,还不能写数据,不过这种情况不是不可接受的,只是FIFO的效率降低一点罢了。“假空”也是类似的。

二、异步FIFO代码

2.1 代码

`timescale 1ns/1ps
module AsyncFIFO #(
    parameter DATA_WIDTH = 8,
    parameter FIFO_DEPTH = 16
)(
    input  wire                     wr_clk_i    ,
    input  wire                     rd_clk_i    ,
    input  wire                     wr_rstn_i   ,
    input  wire                     rd_rstn_i   ,
    
    input  wire                     rd_en_i     ,
    input  wire                     wr_en_i     ,
    input  wire [DATA_WIDTH-1:0]    wr_data_i   ,

    output wire [DATA_WIDTH-1:0]    rd_data_o   ,
    output wire                     fifo_full_o ,
    output wire                     fifo_empty_o
);
localparam DEPTH = $clog2(FIFO_DEPTH);

reg  [DEPTH:0] wr_ptr_d1;
reg  [DEPTH:0] rd_ptr_d1;
reg  [DEPTH:0] wr_ptr_gray_sync1;
reg  [DEPTH:0] wr_ptr_gray_sync2;
reg  [DEPTH:0] rd_ptr_gray_sync1;
reg  [DEPTH:0] rd_ptr_gray_sync2;
reg            fifo_empty_d1;

wire [DEPTH:0] wr_ptr_gray = (wr_ptr_d1 >> 1) ^ wr_ptr_d1;
wire [DEPTH:0] rd_ptr_gray = (rd_ptr_d1 >> 1) ^ rd_ptr_d1;
wire           fifo_empty  = (rd_ptr_gray == wr_ptr_gray_sync2) ? 1'b1 : 1'b0; 
wire           wr_able     =  wr_en_i && !fifo_full_o;
wire           rd_able     =  rd_en_i && !fifo_empty;
wire [DEPTH:0] wr_ptr      = (wr_able) ? wr_ptr_d1 + 1'b1 : wr_ptr_d1;
wire [DEPTH:0] rd_ptr      = (rd_able) ? rd_ptr_d1 + 1'b1 : rd_ptr_d1;

reg  [DATA_WIDTH-1:0] ram [0:FIFO_DEPTH-1];
reg  [DATA_WIDTH-1:0] rd_data;

wire [DEPTH-1:0] wr_addr = wr_ptr_d1[DEPTH-1:0]; 
wire [DEPTH-1:0] rd_addr = rd_ptr_d1[DEPTH-1:0]; 

always @(posedge wr_clk_i)                      if(wr_able)    ram[wr_addr]      <= wr_data_i;
always @(posedge rd_clk_i or negedge rd_rstn_i) if(!rd_rstn_i) rd_data           <= 'd0; else if(rd_able) rd_data <= ram[rd_addr];

always @(posedge wr_clk_i or negedge wr_rstn_i) if(!wr_rstn_i) wr_ptr_d1         <= 'd0; else wr_ptr_d1           <= wr_ptr;
always @(posedge rd_clk_i or negedge rd_rstn_i) if(!rd_rstn_i) rd_ptr_d1         <= 'd0; else rd_ptr_d1           <= rd_ptr;
always @(posedge wr_clk_i or negedge wr_rstn_i) if(!wr_rstn_i) rd_ptr_gray_sync1 <= 'd0; else rd_ptr_gray_sync1   <= rd_ptr_gray;
always @(posedge rd_clk_i or negedge rd_rstn_i) if(!rd_rstn_i) wr_ptr_gray_sync1 <= 'd0; else wr_ptr_gray_sync1   <= wr_ptr_gray;
always @(posedge rd_clk_i or negedge rd_rstn_i) if(!rd_rstn_i) fifo_empty_d1     <= 'd0; else fifo_empty_d1       <= fifo_empty;

always @(posedge wr_clk_i or negedge wr_rstn_i) if(!wr_rstn_i) rd_ptr_gray_sync2 <= 'd0; else rd_ptr_gray_sync2   <= rd_ptr_gray_sync1;
always @(posedge rd_clk_i or negedge rd_rstn_i) if(!rd_rstn_i) wr_ptr_gray_sync2 <= 'd0; else wr_ptr_gray_sync2   <= wr_ptr_gray_sync1;
 
assign rd_data_o    = rd_data;
assign fifo_full_o  = (wr_ptr_gray == {~rd_ptr_gray_sync2[DEPTH:DEPTH-1], rd_ptr_gray_sync2[DEPTH-2:0]}) ? 1'b1 : 1'b0;
assign fifo_empty_o = fifo_empty_d1;

endmodule

设计的整体思路都是模仿Doreen的方法。

最初写代码的时候,fifo_empty_o没有像上面的代码一样延一个时钟周期,仿真时一直拉高rd_en_i时能正常读出ram的最后一个值,但是在读出最后一个值的同时fifo_empty_o就拉高了,我是认为这里有点奇怪,但是觉得既然能正常读出,应该没有多大的问题。

但是后来我想到,如果fifo输出了fifo_empty_o给读取电路,读取电路就应该立即拉低rd_en_i,而不是像上面说的恒拉高rd_en_i,此时就会出现漏读,像这样,

image

左边的蓝色框正常写入8个数据,wr_en_i有1ps的延时,所以最左侧的上升沿没有跟写时钟上升沿对齐。红框中是读出数据,rd_en_i同样有1ps的延时,只读出了前7个数据,所以fifo_empty_o应该要延一个时钟周期

延一个时钟周期的图如下,

image

可见红框中正常读出了8个数,只是数字8没有截出来。

然后考虑到万一读电路保持rd_en_i恒为高,能不能正常工作,

image

这里rd_en_i就是恒拉高,在读出8的下一个时钟上升沿,fifo_empty_o才拉高,我认为这样才是正确的,而且rd_en_i恒拉高没有产生任何影响。

下面是从还没有写入数据就开始读,

image

在还没有写入数据的时候是读不出数据的,并且由于指针同步造成的假空,导致fifo_empty_o频繁翻转,但是读数据是正常读出了8个数。

上面是慢到快,经过验证,快到慢、非整数倍时钟都一样能正常工作。

针对fifo_empty_o是否需要延迟这一个时钟周期,或者本来就应该是这样(我最初的写法有问题),欢迎指正。

2.2 仿真代码

`timescale 1ns/1ps
module AsyncFIFO_tb();
localparam              DATA_WIDTH = 8;
localparam              FIFO_DEPTH = 8;
reg                     wr_clk_i;
reg                     rd_clk_i;
reg                     wr_rstn_i;
reg                     rd_rstn_i;
reg                     rd_en_i;
reg                     wr_en_i;
reg  [DATA_WIDTH-1:0]   wr_data_i;
wire [DATA_WIDTH-1:0]   rd_data_o;
wire                    fifo_full_o;
wire                    fifo_empty_o;

AsyncFIFO #(
    .DATA_WIDTH(DATA_WIDTH),
    .FIFO_DEPTH(FIFO_DEPTH)
)myFIFO(
    .wr_clk_i       (wr_clk_i),
    .rd_clk_i       (rd_clk_i),
    .wr_rstn_i      (wr_rstn_i),
    .rd_rstn_i      (rd_rstn_i),
    .rd_en_i        (rd_en_i),
    .wr_en_i        (wr_en_i),
    .wr_data_i      (wr_data_i),
    .rd_data_o      (rd_data_o),
    .fifo_full_o    (fifo_full_o),
    .fifo_empty_o   (fifo_empty_o)
);
initial begin
    $fsdbDumpfile("test.fsdb");
    $fsdbDumpvars(0, AsyncFIFO_tb);
end

always #20 wr_clk_i = ~wr_clk_i;
always #10 rd_clk_i = ~rd_clk_i;

initial begin
    wr_clk_i = 0;
    rd_clk_i = 0;
    wr_rstn_i = 1;
    rd_rstn_i = 1;
    wr_en_i = 0;
    rd_en_i = 0;
    wr_data_i = 0;
    #10;
    wr_rstn_i = 0;
    rd_rstn_i = 0;
    #10;
    wr_rstn_i = 1;
    rd_rstn_i = 1;

    repeat(10) @(posedge wr_clk_i);

    @(posedge rd_clk_i) #1 rd_en_i = 1;
    repeat(10) @(posedge wr_clk_i);
    @(posedge wr_clk_i) #1 wr_en_i = 1; wr_data_i = 1;
    @(posedge wr_clk_i) #1 wr_data_i = 2;
    @(posedge wr_clk_i) #1 wr_data_i = 3;
    @(posedge wr_clk_i) #1 wr_data_i = 4;
    @(posedge wr_clk_i) #1 wr_data_i = 5;
    @(posedge wr_clk_i) #1 wr_data_i = 6;
    @(posedge wr_clk_i) #1 wr_data_i = 7;
    @(posedge wr_clk_i) #1 wr_data_i = 8;
    @(posedge wr_clk_i) #1 wr_en_i = 0;

    //@(posedge rd_clk_i) #1 rd_en_i = 1;
    //repeat(9) @(posedge rd_clk_i);
    //rd_en_i = 0;
    //repeat(10) @(posedge rd_clk_i);

    

    repeat(100) @(posedge wr_clk_i);
    $finish;
end

endmodule

声明

本文的第一节和第二节参考自<掰开揉碎讲 FIFO(同步FIFO和异步FIFO) - Doreen的FPGA自留地 - 博客园>
https://www.cnblogs.com/DoreenLiu/p/17348480.html

posted @ 2025-11-05 17:45  Holybanban  阅读(2)  评论(0)    收藏  举报