设计一个异步fifo?

发布时间 2023-08-03 14:39:02作者: 余你余生

请设计一个异步fifo?宽度为8bit,深度为4bit。

异步fifo:从硬件的观点来看,就是一块数据内存。它有两个端口,一个用来写数据,就是将数据存入FIFO;另一个用来读数据,也就是将数据从FIFO当中取出。与FIFO操作相关的有两个指针,写指针指向要写的内存部分,读指针指向要读的内存部分。FIFO控制器通过外部的读写信号控制这两个指针移动,并由此产生FIFO空信号或满信号。来自异步FIFO_百度百科 (baidu.com)

异步fifo作用:用于将数据从一个时钟域安全的传送到另一个时钟域。

异步fifo设计的关键:生成fifo读写指针,fifo空/满状态。

异步fifo框图如下:整体分为五个小模块:1、写指针同步至读时钟域。2、读指针同步至写时钟域。3、双端口ram。4、写指针、写地址、满信号的产生。5、读指针、读地址、空信号的产生。

二进制编码在传递中容易产生亚稳态,故采用格雷码传递。格雷码具有良好的对称性。

使用格雷码判断空满状态:

 

异步fifo的设计代码&激励&仿真波形:

module  asyn_fifo
#(
parameter           DATA_WIDTH  = 8     ,
parameter           DATA_DEPTH  = 16    ,
parameter           PTR         = 4         //2^4=16

)
(
//写时钟域
input                           w_clk               ,
input                           w_rst_n             ,
input                           w_en                ,
input       [DATA_WIDTH-1:0]    data_in             ,
output         reg              full                ,

//读时钟域
input                            r_clk              ,
input                            r_rst_n            ,
input                            r_en               ,
output     [DATA_WIDTH-1:0]     data_out           ,
output          reg              empty               
);

reg [DATA_WIDTH-1:0] DPRAM [DATA_DEPTH-1:0];          //双端口RAM

//写时钟域
reg   [PTR:0]        w_bin            ;
wire  [PTR:0]        w_bin_next       ;
reg   [PTR:0]        w_gray           ;
wire  [PTR:0]        w_gray_next      ;    
reg   [PTR:0]        w_gray_ff0       ;
reg   [PTR:0]        w_gray_ff1       ;    //wq2_rptr 
wire  [PTR-1:0]      w_addr           ;
wire                 full_flag        ;
//读时钟域
reg   [PTR:0]        r_bin            ;
wire  [PTR:0]        r_bin_next       ;
reg   [PTR:0]        r_gray           ;
wire  [PTR:0]        r_gray_next      ;
reg   [PTR:0]        r_gray_ff0       ;
reg   [PTR:0]        r_gray_ff1       ;    //rq2_wptr
wire  [PTR-1:0]      r_addr           ;
wire                 empty_flag       ;


//读时钟域下 
// 产生读地址计数(r_bin) 读指针(r_gray)  需要打一拍
always@(posedge r_clk or negedge r_rst_n)  begin 
    if(!r_rst_n)  begin   
       r_bin<='d0; 
       r_gray<='d0;
    end  
    else  begin 
       r_bin<=r_bin_next; 
       r_gray<=r_gray_next;
    end    
end 

assign  r_bin_next =r_bin+(r_en&&(!empty));       //在读使能有效 且未空 读地址+1
assign  r_gray_next = (r_bin_next>>1)^r_bin_next; //二进制码转格雷码
assign  r_addr=r_bin[PTR-1:0] ;                   //读地址

always@(posedge r_clk or negedge r_rst_n)  begin  //打两拍 将写指针同步至读时钟域下
    if(!r_rst_n) begin  
       r_gray_ff0<='d0;
       r_gray_ff1<='d0;
    end 
    else  begin 
       r_gray_ff0<=w_gray;
       r_gray_ff1<=r_gray_ff0;    //rq2_wptr
    end 
end 

//空信号标志产生      这里比较的是同步至读时钟域下写指针  和下一读指针比较  重点
assign   empty_flag=(r_gray_ff1==r_gray_next)?1'b1:1'b0;

//上面空信号标志产生  提前一个读时钟周期 产生真的空信号 需要打一拍
always@(posedge r_clk or negedge r_rst_n) begin
    if(!r_rst_n)
        empty<=1'b0;
    else 
        empty<=empty_flag;
end 

//开始读取双端口RAM中的数据 
assign   data_out = DPRAM[r_addr];     //需要注意的地方 与写入数据不同



//写时钟域下 
// 产生写地址计数(w_bin) 写指针(w_gray) 需要打一拍
always@(posedge w_clk or negedge w_rst_n)  begin  
    if(!w_rst_n) begin 
       w_bin<='d0;
       w_gray<='d0;
    end 
    else begin 
       w_bin<=w_bin_next;
       w_gray<=w_gray_next;
    end    
end                                                  
assign  w_gray_next =(w_bin_next>>1)^w_bin_next  ;  //在写使能有效 且未满 写地址+1
assign  w_bin_next  = w_bin +(w_en&&!full)       ;  //二进制码转格雷码
assign  w_addr      = w_bin[PTR-1:0]             ;  //读地址


//打两拍 将读指针同步至写时钟域下
always@(posedge w_clk or negedge w_rst_n)  begin 
    if(!w_rst_n) begin  
       w_gray_ff0<='d0;
       w_gray_ff1<='d0;
    end 
    else  begin 
       w_gray_ff0<=r_gray;
       w_gray_ff1<=w_gray_ff0;     //wq2_rptr
    end 
end 

//满信号标志产生      这里比较的是同步至写时钟域下读指针  和下一写指针比较  重点
assign   full_flag=(w_gray_next=={~w_gray_ff1[PTR:PTR-1],w_gray_ff1[PTR-2:0]})?1'b1:1'b0;
//上面满信号标志产生  提前一个写时钟周期 产生真的空信号 需要打一拍
always@(posedge w_clk or negedge w_rst_n) begin
    if(!w_rst_n)
        full<=1'b0;
    else 
        full<=full_flag;
end 

//开始写入双端口RAM中的数据 在写使能有效 且非满下写入
always@(posedge w_clk or negedge w_rst_n) begin
    if(!w_rst_n)
        DPRAM[w_addr]<='d0;
    else if (w_en&&!full)
        DPRAM[w_addr] <= data_in;
    else 
        DPRAM[w_addr]<='d0;
end

endmodule 
`timescale  1ns/1ns 
module tb_asyn_fifo();
reg        w_clk                ;                 
reg        w_rst_n              ;  
reg        w_en                 ;  
reg  [7:0] data_in              ;  
wire       full                 ; 
reg        r_clk                ;                 
reg        r_rst_n              ;  
reg        r_en                 ;  
wire [7:0] data_out             ;  
wire       empty                ; 

initial begin 
    w_clk<=1'b0             ;   //模拟激励产生
    r_clk<=1'b0             ;
    w_rst_n<=1'b0           ;
    r_rst_n<=1'b0           ;
    w_en<=1'b0              ;
    r_en<=1'b0              ;
    data_in<='d0            ;
    #20
    w_rst_n<=1'b1           ;
    r_rst_n<=1'b1           ;
    #10
    w_en<=1'b1              ;
    #200
    r_en<=1'b1              ;
    #10
    w_en<=1'b0              ;
    #200
    r_en<=1'b0              ;
    #10
    w_en<=1'b1              ;
    #300
    w_en<=1'b0              ;
    #10
    r_en<=1'b1              ;
    #400
    r_en<=1'b0              ;
    #10
    w_en<=1'b1              ;
    #500
    w_en<=1'b0              ;
    #10
    r_en<=1'b1              ;
    #100
    w_en<=1'b1              ;
    #30
    r_en<=1'b0              ;
    #10
    w_en<=1'b0              ;
    #20
    r_en<=1'b1              ;
    #20
    w_en<=1'b1              ;
    #50
    r_en<=1'b0              ;
    #10
    w_en<=1'b0              ;
    #20
    r_en<=1'b1              ;
    
end          


always@(posedge w_clk or negedge w_rst_n) begin
    if(!w_rst_n)
        data_in<='d0;
    else if(w_en&&(!full))
        data_in<=data_in+1'b1;
    else 
        data_in<=data_in;
end 
always  #10  w_clk<= ~w_clk       ;
always  #20  r_clk<= ~r_clk       ;

asyn_fifo
#(
.DATA_WIDTH(8 )    ,
.DATA_DEPTH(16)    ,
.PTR       (4 )        //2^4=16

)
asyn_fifo_inst
(
//写时钟域
.w_clk     (w_clk   )          ,
.w_rst_n   (w_rst_n )          ,
.w_en      (w_en    )          ,
.data_in   (data_in )          ,
.full      (full    )          ,
//读时钟域
.r_clk     (r_clk   )          ,
.r_rst_n   (r_rst_n )          ,
.r_en      (r_en    )          ,
.data_out  (data_out)          ,
.empty     (empty   )           
);


endmodule 

若有不对的地方,敬请指正,万分感谢。

参考资料:

1、Simulation and Synthesis Techniques for Asynchronous FIFO Design