Design and verification of asynchronous fifo

Keywords: C Verilog Single-Chip Microcomputer

The book continues from the above. The previous article introduced the slightly simple synchronous fifo, and then began the more complex asynchronous fifo.

1. Difference between synchronous fifo and asynchronous fifo

  When there is only one clock in the design, whether all registers use the same one, there will be no transmission speed mismatch; However, when there are multiple clock signals in the design and data is transmitted in these clock domains, data loss may occur due to clock signal mismatch. At this time, asynchronous fifo needs to cache data to ensure normal data transmission. Therefore, generally asynchronous fifo will include a dual port ram

2. Asynchronous fifo composition

Figure 1. Asynchronous fifo

Module composition (6): top-level module, dual port RAM, 2 cross clock domain synchronous pointer modules, null judgment logic and full judgment logic.

  3. Precautions

        1) Avoid using binary counters to implement pointers, because if the current value is carried by one, all bits may change, and the error rate may increase. eg:0111 plus 1 will become 1000, and three of them will change, so the error rate will be increased.

        2) To solve the above problems, it is recommended to directly use gray code counting.

        3) Define more than one bit of the pointer's bit width. For example, suppose you want to design an asynchronous FIFO with a depth of 8. At this time, only 3 bits (2 ^ 3 = 8) are needed to define the read-write pointer. However, we design the pointer's bit width to 4 bits in the design. The function of the highest bit is to distinguish whether to read null or write full. Specific theory 1   As follows, when the highest bit is the same, the other bits are the same, it is considered to be read empty; When the highest bit is different and the other bits are the same, it is considered to be full

                 Design concept of gray code counter: Gray code to binary converter, adder, binary code to gray code converter, register for saving gray value

3. Corresponding module code segment

1) Top level module

//************Top level module*************************
`timescale 1 ns/ 1 ps
module asyn_fifo_top #(
	parameter DATA_WIDTH = 8,
	parameter ADDR_WIDTH = 4,
	parameter RAM_DEPTH = 16
)
(
	input 		              WCLK,
	input                         WRSTn,
	input                         RCLK,
	input                         RRSTn,
	input                         write,
	input                         read,
	input  [ DATA_WIDTH - 1 : 0 ] wdata,
	output [ DATA_WIDTH - 1 : 0 ] rdata,
	output                        full,
	output                        empty
);

wire [ ADDR_WIDTH : 0 ]    wpt;
wire [ ADDR_WIDTH : 0 ]    rpt;
wire [ ADDR_WIDTH : 0 ]    rp2_wpt;
wire [ ADDR_WIDTH : 0 ]    wp2_rpt;
wire [ ADDR_WIDTH - 1: 0 ] waddr;
wire [ ADDR_WIDTH - 1: 0 ] raddr;

double_ram#(.DATA_WIDTH(DATA_WIDTH),
			.ADDR_WIDTH(ADDR_WIDTH),
			.RAM_DEPTH(RAM_DEPTH)) 
	ram_module (.WCLK(WCLK),
	            .write(write),
				.waddr(waddr),
				.wdata(wdata),
				.raddr(raddr),
				.rdata(rdata));
r2w_sync #(.ADDR_WIDTH(ADDR_WIDTH))
	r2w_module (.WCLK(WCLK),
				.WRSTn(WRSTn),
				.rpt(rpt),
				.rp2_wpt(rp2_wpt));
w2r_sync #(.ADDR_WIDTH(ADDR_WIDTH))
	w2r_module (.RCLK(RCLK),
				.RRSTn(RRSTn),
				.wpt(wpt),
				.wp2_rpt(wp2_rpt));
full#(.ADDR_WIDTH(ADDR_WIDTH)) 
	full_module(.WCLK(WCLK),
				.WRSTn(WRSTn),
				.write(write),
				.rp2_wpt(rp2_wpt),
				.wpt(wpt),
				.waddr(waddr),
				.full(full));
empty #(.ADDR_WIDTH(ADDR_WIDTH))
	empty_module(.RCLK(RCLK),
				 .RRSTn(RRSTn),
				 .read(read),
				 .wp2_rpt(wp2_rpt),
				 .rpt(rpt),
				 .raddr(raddr),
				 .empty(empty));


endmodule

3.2 RAM module

//******************Dual interface RAM******************
module double_ram#(
	parameter DATA_WIDTH = 8,
	parameter ADDR_WIDTH = 4,
	parameter RAM_DEPTH = 16
)
(	
	input                       WCLK,   //write clk
	input 					    write,  //write en
	input  [ ADDR_WIDTH - 1:0 ] waddr,  //write address from full.v
	input  [ DATA_WIDTH - 1:0 ] wdata,  //write data
	input  [ ADDR_WIDTH - 1:0 ] raddr,  //read address from empty.v
	output  [ DATA_WIDTH - 1:0 ] rdata   //read data
);

reg [ DATA_WIDTH - 1:0 ] RAM [ RAM_DEPTH - 1:0 ];  //double Port ram


always @ ( posedge WCLK )
begin
	if ( write == 1'b1 )
	begin
		RAM [ waddr ] <= wdata; 
	end
	else
	begin
		RAM [ waddr ] <= RAM [ waddr ];
	end
end

assign rdata = RAM [ raddr ];

endmodule

  3) Judge full logic module

//*****************Judge full logic****************
module full#(
	parameter ADDR_WIDTH = 4
)
(
	input                           WRSTn,
	input                           WCLK,
	input                           write,
	input      [ ADDR_WIDTH : 0 ]   rp2_wpt,
	output reg [ ADDR_WIDTH : 0 ]   wpt,
	output     [ ADDR_WIDTH - 1:0 ] waddr,
	output reg                      full
);

reg  [ ADDR_WIDTH :0 ] wbin;
wire [ ADDR_WIDTH :0 ] wbin_next;
wire [ ADDR_WIDTH :0 ] wgray_next;
wire                   full_reg;

always @ ( posedge WCLK or negedge WRSTn )
begin
	if (!WRSTn)
	begin
		wpt <= 0;
		wbin <= 0;
	end
	else 
	begin
		wpt <= wgray_next;
		wbin <= wbin_next;
	end
end

assign wbin_next = ( !full ) ? ( wbin + write ) : wbin;
assign wgray_next = ( wbin_next >> 1 ) ^ wbin_next;
assign waddr = wbin [ ADDR_WIDTH - 1:0 ];


always @ ( posedge WCLK or negedge WRSTn )
begin
	if (!WRSTn)
	begin
		full <= 0;
	end
	else
	begin
		full <= full_reg;
	end
end

assign full_reg = ( wgray_next == { -rp2_wpt [ ADDR_WIDTH : ADDR_WIDTH - 1],rp2_wpt [ ADDR_WIDTH - 2:0]});

endmodule 

4) Determine whether it is a null pointer

//********************Judge whether it is empty logic************
module empty#(
	parameter ADDR_WIDTH = 4
)
(
	input                           RRSTn,
	input                           RCLK,
	input                           read,
	input      [ ADDR_WIDTH : 0 ]   wp2_rpt,
	output reg [ ADDR_WIDTH : 0 ]   rpt,
	output reg [ ADDR_WIDTH - 1:0 ] raddr,
	output reg                      empty
);

reg  [ ADDR_WIDTH :0 ] rbin;
wire [ ADDR_WIDTH :0 ] rbin_next;
wire [ ADDR_WIDTH :0 ] rgray_next;
wire                   empty_reg;

always @ ( posedge RCLK or negedge RRSTn )
begin
	if (!RRSTn)
	begin
		rpt <= 0;
		rbin <= 0;
	end
	else 
	begin
		rpt <= rgray_next;
		rbin <= rbin_next;
	end
end

assign rbin_next = ( !empty ) ? ( rbin + read ) : rbin;
assign rgray_next = ( rbin_next >> 1 ) ^ rbin_next;
//assign raddr = rbin [ ADDR_WIDTH - 1:0 ];

always @ ( posedge RCLK or negedge RRSTn )
begin
	if (read)
	begin
	   raddr <= rbin [ ADDR_WIDTH - 1:0 ];
	end
	
end

always @ ( posedge RCLK or negedge RRSTn )
begin
	if (!RRSTn)
	begin
		empty <= 0;
	end
	else
	begin
		empty <= empty_reg;
	end
end

assign empty_reg = ( rgray_next == wp2_rpt );

endmodule 

5) Read write cross clock domain

//****************Read write cross clock domain****************
module r2w_sync#(
	parameter ADDR_WIDTH = 4
)
(
	input 						  WRSTn,    //write RSTn
	input 					      WCLK,     //write CLK
	input      [ ADDR_WIDTH : 0 ] rpt,      //output to write port gray
	output reg [ ADDR_WIDTH : 0 ] rp2_wpt   //D trigger sync with two levels,second level
);


reg [ ADDR_WIDTH : 0 ] rp1_wpt;         //frist level
//reg [ ADDR_WIDTH : 0 ] rp2_wpt; 

always @ ( posedge WCLK or negedge WRSTn )
begin
	if ( !WRSTn )
	begin
		{ rp2_wpt,rp1_wpt } <= 0;
	end
	else 
	begin
		{ rp2_wpt,rp1_wpt } <= { rp1_wpt,rpt };
	end
end

endmodule

6) Write read cross clock domain

/**Write to Read Sync module**/
module w2r_sync#(
	parameter ADDR_WIDTH = 4
)
(
	input 						  RRSTn,    //read RSTn
	input 						  RCLK,     //reaf CLK
	input      [ ADDR_WIDTH : 0 ] wpt,      //output to read port gray
	output reg [ ADDR_WIDTH : 0 ] wp2_rpt   //D trigger sync with two levels,second level
);

reg [ ADDR_WIDTH : 0 ] wp1_rpt;         //frist level
//reg [ ADDR_WIDTH : 0 ] wp2_rpt;   

always @ ( posedge RCLK or negedge RRSTn )
begin
	if ( !RRSTn )
	begin
		{ wp2_rpt,wp1_rpt } <= 0;
	end
	else 
	begin
		{ wp2_rpt,wp1_rpt } <= { wp1_rpt,wpt};
	end
end

endmodule 

4.tb

`timescale 1 ns/ 1 ps
module asyn_fifo_tb();

parameter DATA_WIDTH = 8;

	reg                         WCLK;
	reg                         WRSTn;
	reg                         RCLK;
	reg                         RRSTn;
	reg                         write;
	reg                         read;
	reg  [ DATA_WIDTH - 1 : 0 ] wdata;
	wire [ DATA_WIDTH - 1 : 0 ] rdata;
	wire                        full;
	wire                        empty;
	//integer                     i = 0;
	
asyn_fifo_top asyn_fifo(.WCLK(WCLK),
						.WRSTn(WRSTn),
						.RCLK(RCLK),
						.RRSTn(RRSTn),
						.write(write),
						.read(read),
						.wdata(wdata),
						.rdata(rdata),
						.full(full),
						.empty(empty));
initial 
begin
	WCLK <= 0;
	forever #100 WCLK = ~WCLK;
end

initial 
begin
	RCLK <= 0;
	forever #200 RCLK = ~RCLK;
end

initial 
begin
	WRSTn = 0;
	wdata = 0;
	#100 WRSTn = 1;
end

initial
begin
	RRSTn = 0;
	#100 RRSTn = 1;
	#10000;
	$finish();
end


always @ ( posedge WCLK or negedge WRSTn )
begin
	wdata <= wdata + 1'b1;
end

//always  @(posedge WCLK or negedge WRSTn)
//begin
//      if(WRSTn==1'b0)
//      begin
//         i <= 0;
//      end
//      else if(!full)
//      begin
//         i = i+1;
//      end
//      else begin
//         i <= i;
//      end
//end

//always @ (*)
//begin
//    if (!full)
//        wdata = i;
//    else 
//        wdata = 0;
//end


always @ ( full or WRSTn )
begin
	if (!WRSTn)
	begin
		write <= 0;
	end
	else if (!full)
	begin
		write <= 1;
	end
	else 
	begin
		write <= 0;
	end
end

always @ ( empty or RRSTn )
begin
	if (!RRSTn)
	begin
		read <= 0;
		
	end
	
	else if (!empty)
	begin
		read <= 1;
	end 
	else 
	begin
		read <= 0;
	end
end

        initial begin
            $fsdbDumpfile("jacky");
            $fsdbDumpvars;
            $vcdpluson;
        end


endmodule

2, Another design idea (from csdn:FPGA)

//asyn_fifo code
module asyn_fifo #(
    parameter data_width = 16;
    parameter data_depth = 8;
    parameter ram_depth = 256;
    )
    (
        input       rst_n,

        input                       wr_clk,         //Write clock domain signal
        input                        wr_en,
        input   [data_width-1:0]    data_in,
        output                      full, 

        input                       rd_clk,         //Clock reading domain signal
        input                       rd_en,
        input   [data_width-1:0]    data_out,
        output                      empty,
    )
    
    reg [data_depth-1:0]            wr_adr;         //Defines the address of the read-write pointer
    reg [data_depth-1:0]            rd_adr;

    reg [data_depth:0]              wr_adr_ptr ;    //Defines the pointer used for comparison
    reg [data_depth:0]              rd_adr_ptr ;

    wire [data_depth:0]             wr_adr_gray;    //Convert the pointer used for comparison to gray code
    reg [data_depth:0]              wr_adr_gray1 ;  //Used in two shots
    reg [data_depth:0]              wr_adr_gray2 ;
    wire [data_depth:0]             rd_adr_gray;    //Convert the pointer used for comparison to gray code
    reg [data_depth:0]              rd_adr_gray1 ;  //Used in two shots
    reg [data_depth:0]              rd_adr_gray2 ;


    //*********************Data read / write module defined in dual port ram******************
    assign wr_adr  =  wr_adr_ptr[data_depth-1:0]    //Defines the relationship between the comparison pointer and the read / write pointer
    assign rd_adr  =  rd_adr_ptr[data_depth-1:0]

    integer i;                                      //Instantiate a RAM
    reg [data_width-1:0]  ram_fifo [data_depth-1:0] ;
        //Write in ram
    always @ (posedge wr_clk or negedge rst_n) 
        begin     
        if(!rst_n)
            begin                                   //Set all ram to zero
                for(i;i<ram_depth;i=i+1)
                    ram_fifo[i] <= 'd0;
            end
        else if (wr_en && ~full)
            ram_fifo[wr_adr] <= data_in;
        else
            ram_fifo[wr_adr] <= ram_fifo[wr_adr];
        end

        //Reading data in ram
    always @ (posedge rd_clk or rst_n)
        begin
        if (!rst_n)
            data_out <= 'd0;
        else if (rd_en && ~empty)
            data_out <= ram_fifo [rd_adr];
        else
            data_out <= 'd0;
         end   
//**********After reading and writing, add 1 to the comparison pointer************
    always @ (posedge wr_clk or !rst_n)
        begin
            if (!rst_n)
                wr_adr_ptr <='b0;
            else if (wr_en && ~full)
                wr_adr_ptr <= wr_adr_ptr + 1'b1;
            else
                wr_adr_ptr <= wr_adr_ptr
        end
    always @ (posedge rd_clk or !rst_n)
        begin 
            if (!rst_n)
                rd_adr_ptr <= 'b0;
            else if (rd_en && ~empty)
                rd_adr_ptr <= rd_adr_ptr + 1'b0;
            else 
                rd_adr_ptr <= rd_adr_ptr
        end


 //**********************Convert comparison pointer to gray code*******************
    assign wr_adr_gray = (wr_adr_ptr >> 1)^ wr_adr_ptr;
    assign rd_adr_gray = (rd_adr_ptr >> 1) ^ rd_adr_ptr;

//*************************Compare the size of gray code************
   //When judging whether it is empty, judge whether the converted gray codes are equal in the time reading clock field
   //When judging whether it is full, compare the highest bits of gray code and the remaining bits in the write clock field

   //Make two beats of the comparison gray code under the corresponding clock to synchronize the clock domain
   always @ (posedge wr_clk or !rst_n)
        begin
        if (!rst_n)
            begin
                rd_adr_gray1 <= 'b0;
                rd_adr_gray2 <= 'b0;
            end
        else begin
            rd_adr_gray1 <= rd_adr_gray;
            rd_adr_gray2 <= rd_adr_gray1;
            end 
        end
   always @ (posedge rd_clk or !rst_n)
        begin
        if (!rst_n)
            begin
                wr_adr_gray1 <= 'b0;
                wr_adr_gray2 <= 'b0;
            end
        else begin
            wr_adr_gray1 <= wr_adr_gray;
            wr_adr_gray2 <= wr_adr_gray1;
            end 
        end        

        //Judge empty and full
    assign empty = (rd_adr_gray2 == wr_adr_gray2) ? 1'b:1'b0;
    assign full = (rd_adr_gray2[data_depth : data_depth-1] != wr_adr_gray2[data_depth:data_depth-1]) && (wr_adr_gray2[data_depth-2:0] == rd_adr_gray2[data_depth-2:0]) 


endmodule

tb

//testbench for asynchronous fifo
`timescale 1ns/1ps

module asyn_fifo_tb;

    reg                 rst_n;

    reg                 wr_clk;
    reg                 wr_en;
    reg     [15:0]      data_in;
    wire                full;

    reg                 rd_clk;
    reg                 rd_en;
    reg     [15:0]      data_out;
    wire                empty;


    asyn_fifo asyn_fifo_inst
    (
        .rst_n      (rst_n),

        .wr_clk     (wr_clk),
        .wr_en      (wr_en),
        .data_in    (data_in),
        .full       (full),

        .rd_clk     (rd_clk),
        .rd_en       (rd_en),
        .data_out       (data_out),
        .empty      (empty)
    )

    //Define clock
    initial begin
        wr_clk = 0;
        forever #10 wr_clk = ~wr_clk;
    end

    initial begin
        rd_clk = 0 ;
        forever  #30 rd_clk = ~rd_clk;
    end

    // Define input data_in
    always @ (posedge wr_clk or negedge  rst_n)
        begin
            if (!rst_n)
                data_in <= 'd0;
            else if (wr_en)
                data_in <= data_in + 1'b1;
            else
                data_in <= data_in;
        end
    //Define other input signals

initial begin
    rst_n = 0;
    wr_en =0;
    rd_en = 0;
    #200
    rst_n = 1;
    wr_en = 1;
    #20000
    wr_en = 0;
    rd_en = 1;
    #20000
    rd_en = 0;

    $stop
end
endmodule

Posted by Sooz719 on Tue, 30 Nov 2021 12:26:46 -0800