RTL设计(5)- 异步FIFO

FIFO简介

FIFO(First In First Out )是一种先进先出的数据缓存器,它与普通存储器的区别是没有外部读写地址线,这样使用起来非常简单,但缺点就是只能顺序写入数据,顺序的读出数据,其数据地址由内部读写指针自动加1完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。

同步FIFO是指读时钟和写时钟为同一个时钟;
异步FIFO是指读写时钟不一致,读写时钟是互相独立的。异步FIFO常用于多bit数据的跨时钟域。

异步FIFO空满检测

空满状态是使用格雷码跨时钟域来判断:
(由于格雷码每次只有1位变化,所以可以使用double clock的方式来跨时钟域,但是会因此产生状态判断的延迟)

满标志位在写时钟域判断,因为要反馈给写数据端
空标志位在读时钟域判断,因为要反馈给读数据端
在这里插入图片描述
在这里插入图片描述
其中格雷码的位宽是ADDR_WIDTH+1的:(是为了判断满状态)
在这里插入图片描述
以下面的格雷码为例,假如ADDR_WIDTH为3,读指针为0000当写指针为1100时所有地址对应的数据都被写入了(写入了一圈),则FIFO满,当读指针为其他值时同理。
在这里插入图片描述

异步FIFO最小深度

在这里插入图片描述

异步FIFO实例

模块中使用到了 RTL设计(2)- 双口RAM

asyncfifo.v

`timescale 1ns / 1ps

// Company: 
// Engineer: 
// 
// Create Date: 2020/12/12
// Author Name: Sniper
// Module Name: asyncfifo
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 


module asyncfifo
#(
    parameter DATA_WIDTH = 8,
    parameter DATA_DEPTH = 256
)
(
    input rst_n,

    //write
    input wr_clk,
    input wr_en,
    input [DATA_WIDTH-1:0] wr_data,
    output full,

    //read
    input rd_clk,
    input rd_en,
    output reg rd_valid,
    output [DATA_WIDTH-1:0] rd_data,
    output empty
);

localparam ADDR_WIDTH = $clog2(DATA_DEPTH);

//reg
reg [ADDR_WIDTH:0] wr_addr_ptr;//add one extra MSB for check full or empty
reg [ADDR_WIDTH:0] rd_addr_ptr;
reg [ADDR_WIDTH:0] wr_addr_gray_d1;//use for CDC
reg [ADDR_WIDTH:0] wr_addr_gray_d2;
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];


//wire
wire [ADDR_WIDTH-1:0] wr_addr;//RAM present address
wire [ADDR_WIDTH-1:0] rd_addr;
wire [ADDR_WIDTH:0] wr_addr_gray;//gray code corresponding to the address pointer
wire [ADDR_WIDTH:0] rd_addr_gray;


//assign
assign wr_addr = wr_addr_ptr[ADDR_WIDTH-1-:ADDR_WIDTH];//bits from ADDR_WIDTH-1 counting ADDR_WIDTH number bits
assign rd_addr = rd_addr_ptr[ADDR_WIDTH-1-:ADDR_WIDTH];

assign wr_addr_gray = (wr_addr_ptr >> 1) ^ wr_addr_ptr;//translate gray code  in order to  reduce Metastable state
assign rd_addr_gray = (rd_addr_ptr >> 1) ^ rd_addr_ptr;

/*full : highest two bits different : wr over number */
/*empty: addr equal **********************************/
//full  flag in write clock domain
assign full = (wr_addr_gray == {
    
    ~(rd_addr_gray_d2[ADDR_WIDTH-:2]),rd_addr_gray_d2[ADDR_WIDTH-2:0]}) ;
//empty flag in read  clock domain
assign empty = ( rd_addr_gray == wr_addr_gray_d2 );


//rd_valid delay 1 cycle
always@(posedge rd_clk or negedge rst_n)
begin
    if(!rst_n)
        rd_valid <= 1'b0;
    else if(rd_en && (~empty))
        rd_valid <= 1'b1;
    else
        rd_valid <= 1'b0;
end


//write addr ptr control
always@(posedge wr_clk or negedge rst_n)
begin
  if(!rst_n)
	 wr_addr_ptr <= 'h0;
  else if(wr_en && (~full))
	 wr_addr_ptr <= wr_addr_ptr + 1;
  else 
	 wr_addr_ptr <= wr_addr_ptr;
end

//read addr ptr control
always@(posedge rd_clk or negedge rst_n)
begin
  if(!rst_n)
	 rd_addr_ptr <= 'h0;
  else if(rd_en && (~empty))
	 rd_addr_ptr <= rd_addr_ptr + 1;
  else 
	 rd_addr_ptr <= rd_addr_ptr;
end


/*****CLock Domain Crossing*****/
//gray code addr buff
always@(posedge rd_clk )
begin
 wr_addr_gray_d1 <= wr_addr_gray;
 wr_addr_gray_d2 <= wr_addr_gray_d1;
end

//gray code addr buff
always@(posedge wr_clk )
begin
  rd_addr_gray_d1 <= rd_addr_gray;
  rd_addr_gray_d2 <= rd_addr_gray_d1;
end


dualram 
#(
    .WIDTH(DATA_WIDTH),
    .DEPTH(DATA_DEPTH)
)
u_dualram
(
    .wr_clk(wr_clk),
    .wr_addr(wr_addr),
    .wr_data(wr_data),
    .wr_en(wr_en),
    .rd_clk(rd_clk),
    .rd_addr(rd_addr),
    .rd_data(rd_data),
    .rd_en(rd_en)
);


endmodule

tb_asyncfifo.v

`timescale 1ns / 1ps

// Company:
// Engineer:
//
// Create Date: 2020/12/12
// Author Name: Sniper
// Module Name: tb_asyncfifo
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//


module tb_asyncfifo;

//parameter
parameter DATA_WIDTH = 8;
parameter DATA_DEPTH = 256;


reg rst_n;

reg wr_clk;
reg wr_en;
reg [DATA_WIDTH-1:0] wr_data;
wire full;

reg rd_clk;
reg rd_en;
wire rd_valid;
wire [DATA_WIDTH-1:0] rd_data;
wire empty;



initial
begin
    rst_n = 0;
    wr_clk = 0;
    wr_en = 0;
    wr_data = 0;
    rd_clk = 0;
    rd_en = 0;

	#100;
    rst_n = 1;

    fork
        forever @(posedge wr_clk)
        begin
            if(!full)
                wr_en <= 1;
            else
                wr_en <= 0;

            if(wr_en == 1)
                wr_data <= wr_data + 1;
        end

        forever @(posedge rd_clk)
        begin
            if(!empty)
                rd_en <= 1;
            else
                rd_en <= 0;
        end
    join

end

//clock
always #8 wr_clk = ~wr_clk;
always #5 rd_clk = ~rd_clk;



//DUT
asyncfifo 
#(
    .DATA_WIDTH(DATA_WIDTH),
    .DATA_DEPTH(DATA_DEPTH)
)
DUT
(
    .rst_n(rst_n),
    .wr_clk(wr_clk),
    .wr_en(wr_en),
    .wr_data(wr_data),
    .full(full),
    .rd_clk(rd_clk),
    .rd_en(rd_en),
    .rd_valid(rd_valid),
    .rd_data(rd_data),
    .empty(empty)
);

initial
begin
    $dumpfile("tb_asyncfifo.vcd");
    $dumpvars(0,tb_asyncfifo);
end

initial #100_000 $finish;

endmodule

运行结果

vcs -R dualram.v asyncfifo.v tb_asyncfifo.v

时钟周期:
always #8 wr_clk = ~wr_clk;
always #5 rd_clk = ~rd_clk;
在这里插入图片描述

时钟周期:
always #5 wr_clk = ~wr_clk;
always #12 rd_clk = ~rd_clk;
在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/meng1506789/article/details/111075932