Two design methods of synchronous FIFO (counter method and high-order expansion method)

1. What is FIFO

         FIFO is a first in first out data buffer, which is widely used in logic design. FIFO design can be said to be a common sense design that logic designers must master. FIFO is generally used to isolate places where the read-write bandwidth on both sides is inconsistent or the bit width is different. In FPGA design, there are generally two methods to use FIFO. The first method is to directly call the official FIFO IP, and the other method is to design FIFO control logic by yourself. Of course, when we learn to design FIFO, we don't really need to make our own wheels. It just means that as practitioners, we need to understand the relevant design methods. After all, our own wheels don't necessarily run.

         FIFO includes synchronous FIFO and asynchronous FIFO. Synchronous FIFO has a clock signal, which is used by read and write logic. Asynchronous FIFO has two clock signals, which are used by read and write logic. The difference between FIFO and ordinary memory RAM is that there is no external read-write address line, which is very simple to use, but the disadvantage is that it can only write data in sequence. The data address is automatically added by 1 by the internal read-write pointer. It can not be read or written to a specified address by the address line like ordinary memory. FIFO is essentially a FIFO data buffer composed of ram and read-write control logic.

         Common parameters of FIFO:

  • FIFO width: that is, the data bits of FIFO for one read / write operation
  • Depth of FIFO: refers to how many N-bit data can be stored in FIFO (if the width is N)
  • Full flag: a signal sent by the FIFO state when the FIFO is full or about to be full to prevent the FIFO write operation from continuing to write data to the FIFO, resulting in overflow
  • Empty flag: a signal sent by the FIFO state when the FIFO is empty or about to be empty, so as to prevent the reading operation of FIFO from continuing to read data from FIFO, resulting in the reading of invalid data (underflow)
  • Read clock: the clock followed by the read operation. Data is read at each clock edge (consistent with the write clock in the synchronization FIFO)
  • Write clock: the clock followed by the write operation. Data is written at each clock edge (consistent with the read clock in the synchronization FIFO)

2. Design method of synchronous FIFO

Working principle of FIFO read-write pointer (read-write pointer is read-write address):

  • Write pointer: always point to the next unit to be written. When reset, point to the first unit (No. 0)  
  • Read pointer: it always points to the data to be read out. During reset, it points to the "empty" / "full" detection of the first unit (No. 0) FIFO  

         The key of FIFO design is to generate reliable FIFO read-write pointer and FIFO "empty" / "full" status flag. When the read-write pointers are equal, it indicates that the FIFO is empty. This occurs during the reset operation, or when the read pointer catches up with the write pointer after reading the last word in the FIFO, as shown in the following figure:

         When the read-write pointers are equal again, it indicates that the FIFO is full. This happens when the write pointer rotates one circle and the wrapped around catches up with the read pointer, as shown in the following figure:

        It can be seen that the read-write pointer can be + 1 per clock cycle when the read-write enable is effective, and how to generate a reliable "empty" / "full" signal has become the focus of synchronous FIFO design. There are two solutions:  

2.1 counter method

        Build a counter (fifo_cnt) to indicate the number of data in the current FIFO:

  • When reset, the counter is 0 and the number of data in FIFO is 0
  • When the read / write enable signals are valid, it means that the counter is read and write again, the counter remains unchanged, and the number of data in FIFO does not change
  • When write enable is valid and full=0, fifo_cnt +1; Indicates that when a write operation occurs and the FIFO is not full, the number of data in the FIFO increases by 1  
  • When the read enable is valid and empty=0, fifo_cnt -1;; Indicates that the number of data in the FIFO is reduced by 1 when the FIFO is not full  
  • fifo_ When CNT = 0, it means that FIFO is empty and empty=1 needs to be set; fifo_ CNT = depth of FIFO   When, it means that the FIFO is now full and full=1 needs to be set

2.1.1 Verilog code

         After solving the above problems, you can easily post Verilg Code:

//Realization of synchronous FIFO by counter method
module	sync_fifo_cnt
#(
	parameter   DATA_WIDTH = 'd8  ,							//FIFO bit width
    parameter   DATA_DEPTH = 'd16 							//FIFO depth
)
(
	input									clk		,		//System clock
	input									rst_n	,       //Low level active reset signal
	input	[DATA_WIDTH-1:0]				data_in	,       //Data written
	input									rd_en	,       //Read enable signal, high level active
	input									wr_en	,       //Write enable signal, high level active
															
	output	reg	[DATA_WIDTH-1:0]			data_out,	    //Output data
	output									empty	,	    //Empty flag, high level indicates that the current FIFO is full
	output									full	,       //Full flag, high level indicates that the current FIFO has been read empty
	output	reg	[$clog2(DATA_DEPTH) : 0]	fifo_cnt		//$clog2 is the base 2 logarithm	
);

//reg define
reg [DATA_WIDTH - 1 : 0] fifo_buffer[DATA_DEPTH - 1 : 0];	//Implementation of RAM with two-dimensional array	
reg [$clog2(DATA_DEPTH) - 1 : 0]	wr_addr;				//Write address
reg [$clog2(DATA_DEPTH) - 1 : 0]	rd_addr;				//Read address

//Read operation, update read address
always @ (posedge clk or negedge rst_n) begin
	if (!rst_n)
		rd_addr <= 0;
	else if (!empty && rd_en)begin							//The read enable is valid and not empty
		rd_addr <= rd_addr + 1'd1;
		data_out <= fifo_buffer[rd_addr];
	end
end
//Write operation, update write address
always @ (posedge clk or negedge rst_n) begin
	if (!rst_n)
		wr_addr <= 0;
	else if (!full && wr_en)begin							//Write enable is valid and not full
		wr_addr <= wr_addr + 1'd1;
		fifo_buffer[wr_addr]<=data_in;
	end;
end
//Update counter
always @ (posedge clk or negedge rst_n) begin
	if (!rst_n)
		fifo_cnt <= 0;
	else begin
		case({wr_en,rd_en})									//Splicing read-write enable signal for judgment
			2'b00:fifo_cnt <= fifo_cnt;						//No reading, no writing
			2'b01:	                               			//Just read
				if(fifo_cnt != 0)				   			//fifo is not read empty
					fifo_cnt <= fifo_cnt - 1'b1;   			//Number of fifo - 1
			2'b10:                                 			//Just write
				if(fifo_cnt != DATA_DEPTH)         			//fifo is not full
					fifo_cnt <= fifo_cnt + 1'b1;   			//Number of fifo + 1
			2'b11:fifo_cnt <= fifo_cnt;	           			//Simultaneous reading and writing
			default:;                              	
		endcase
	end
end
//Update the indication signal according to the counter status
//According to different thresholds, you can also design half empty, half full, almost empty and almost full
assign full  = (fifo_cnt == DATA_DEPTH) ? 1'b1 : 1'b10;		//Null signal
assign empty = (fifo_cnt == 0)? 1'b1 : 1'b0;				//Full signal

endmodule

2.1.2 Testbench and simulation results

         Next, write a script to test the source code:

  • Instantiate a synchronous FIFO with a depth of 8 and a bit width of 8
  • Write FIFO first until it is full, and the written data is random data
  • Then read the FIFO until it is empty
  • Then write four random data to FIFO and read them at the same time
`timescale 1ns/1ns	//Time unit / precision

//------------< module and Port declaration >----------------------------------------
module tb_sync_fifo_cnt();

parameter   DATA_WIDTH = 8  ;			//FIFO bit width
parameter   DATA_DEPTH = 8 ;			//FIFO depth

reg									clk		;
reg									rst_n	;
reg		[DATA_WIDTH-1:0]			data_in	;
reg									rd_en	;
reg									wr_en	;
						
wire	[DATA_WIDTH-1:0]			data_out;	
wire								empty	;	
wire								full	;
wire	[$clog2(DATA_DEPTH) : 0]	fifo_cnt;


//------------< instantiate the module under test >----------------------------------------
sync_fifo_cnt
#(
	.DATA_WIDTH	(DATA_WIDTH),			//FIFO bit width
    .DATA_DEPTH	(DATA_DEPTH)			//FIFO depth
)
sync_fifo_cnt_inst(
	.clk		(clk		),
	.rst_n		(rst_n		),
	.data_in	(data_in	),
	.rd_en		(rd_en		),
	.wr_en		(wr_en		),
                 
	.data_out	(data_out	),	
	.empty		(empty		),	
	.full		(full		),
	.fifo_cnt	(fifo_cnt	)			
);

//------------< set initial test conditions >----------------------------------------
initial begin
	clk = 1'b0;							//The initial clock is 0
	rst_n <= 1'b0;						//Initial reset
	data_in <= 'd0;		
	wr_en <= 1'b0;		
	rd_en <= 1'b0;
//Repeat the write operation 8 times to fill the FIFO 	
	repeat(8) begin		
		@(negedge clk)begin		
			rst_n <= 1'b1;				
			wr_en <= 1'b1;		
			data_in <= $random;			//Generate 8-bit random number
		end
	end
//Repeat the read operation for 8 times to make the FIFO read empty	
	repeat(8) begin
		@(negedge clk)begin		
			wr_en <= 1'b0;
			rd_en <= 1'd1;
		end
	end
//Repeat the write operation for 4 times and write 4 random data	
	repeat(4) begin
		@(negedge clk)begin		
			wr_en <= 1'b1;
			data_in <= $random;	//Generate 8-bit random number
			rd_en <= 1'b0;
		end
	end
//Continuously read and write FIFO at the same time, and the written data is random data	
	forever begin
		@(negedge clk)begin		
			wr_en <= 1'b1;
			data_in <= $random;	//Generate 8-bit random number
			rd_en <= 1'b1;
		end
	end
end

//------------< set clock >----------------------------------------------
always #10 clk = ~clk; 			// System clock cycle 20ns

endmodule

        modelsim is used for simulation, and the simulation results are as follows:

          It can be seen that the simulation result is consistent with the expected effect (see the note in the figure for details).

2.2 high level expansion method

        For example, in a FIFO with a depth of 8, a 3-bit read-write pointer is required to indicate the 8 addresses of the read-write addresses 3'b000-3'b111 respectively. If the address pointer is extended by 1 bit, it becomes a 4-bit address, and the address representation interval becomes 4'b0000-4'b1111. If you do not look at the highest bit, the representation interval of the last three bits is still 3'b000-3'b111, which means that the highest bit can be used as an indicator bit.

  • When the highest bits are different and the other bits are the same, it means that the read pointer or write pointer has run one more circle, but obviously it will not let the read pointer run one more circle (what do you read by running one more circle?), so the possible situation can only be that the write pointer runs one more circle, and means that the FIFO is full
  • When the highest bits are the same and the other bits are the same, it means that the read pointer has caught up with the write pointer or the write pointer has caught up with the read pointer, and obviously the write pointer will not catch up with the read pointer (in this case, the write pointer can only exceed the read pointer by one circle), so the possible situation can only be that the read pointer has caught up with the write pointer, which means that the FIFO is read empty

2.2.1 Verilog code

         After solving the above problems, you can easily post Verilg Code:

module	sync_fifo_ptr
#(
	parameter   DATA_WIDTH = 'd8  ,								//FIFO bit width
    parameter   DATA_DEPTH = 'd16 								//FIFO depth
)
(
	input										clk		,		//System clock
	input										rst_n	,       //Low level active reset signal
	input	[DATA_WIDTH-1:0]					data_in	,       //Data written
	input										rd_en	,       //Read enable signal, high level active
	input										wr_en	,       //Write enable signal, high level active
						                                        
	output	reg	[DATA_WIDTH-1:0]				data_out,	    //Output data
	output										empty	,	    //Empty flag, high level indicates that the current FIFO is full
	output										full		    //Full flag, high level indicates that the current FIFO has been read empty
);                                                              

//reg define
//Implementation of RAM with two-dimensional array
reg [DATA_WIDTH - 1 : 0]			fifo_buffer[DATA_DEPTH - 1 : 0];	
reg [$clog2(DATA_DEPTH) : 0]		wr_ptr;						//Write address pointer, one more bit wide	
reg [$clog2(DATA_DEPTH) : 0]		rd_ptr;						//Read address pointer, one bit wider	

//wire define
wire [$clog2(DATA_DEPTH) - 1 : 0]	wr_ptr_true;				//Real write address pointer
wire [$clog2(DATA_DEPTH) - 1 : 0]	rd_ptr_true;				//Real read address pointer
wire								wr_ptr_msb;					//Write address pointer address highest bit
wire								rd_ptr_msb;					//Read address pointer address highest bit

assign {wr_ptr_msb,wr_ptr_true} = wr_ptr;						//Splice the highest bit with other bits
assign {rd_ptr_msb,rd_ptr_true} = rd_ptr;						//Splice the highest bit with other bits

//Read operation, update read address
always @ (posedge clk or negedge rst_n) begin
	if (rst_n == 1'b0)
		rd_ptr <= 'd0;
	else if (rd_en && !empty)begin								//The read enable is valid and not empty
		data_out <= fifo_buffer[rd_ptr_true];
		rd_ptr <= rd_ptr + 1'd1;
	end
end
//Write operation, update write address
always @ (posedge clk or negedge rst_n) begin
	if (!rst_n)
		wr_ptr <= 0;
	else if (!full && wr_en)begin								//Write enable is valid and not full
		wr_ptr <= wr_ptr + 1'd1;
		fifo_buffer[wr_ptr_true] <= data_in;
	end	
end

//Update indication signal
//When all bits are equal, the read pointer catches the write pointer, and the FIFO is read null
assign	empty = ( wr_ptr == rd_ptr ) ? 1'b1 : 1'b10;
//When the highest bits are different but the other bits are equal, the write pointer exceeds the read pointer by one circle, and the FIFO is written empty
assign	full  = ( (wr_ptr_msb != rd_ptr_msb ) && ( wr_ptr_true == rd_ptr_true ) )? 1'b1 : 1'b0;

endmodule

2.2.2 Testbench and simulation results

         Next, write a script to test the source code (consistent with 2.1.2):

  • Instantiate a synchronous FIFO with a depth of 8 and a bit width of 8
  • Write FIFO first until it is full, and the written data is random data
  • Then read the FIFO until it is empty
  • Then write four random data to FIFO and read them at the same time
`timescale 1ns/1ns	//Time unit / precision

//------------< module and Port declaration >----------------------------------------
module tb_sync_fifo_ptr();

parameter   DATA_WIDTH = 8  ;		//FIFO bit width
parameter   DATA_DEPTH = 8 ;		//FIFO depth

reg									clk		;
reg									rst_n	;
reg		[DATA_WIDTH-1:0]			data_in	;
reg									rd_en	;
reg									wr_en	;
						
wire	[DATA_WIDTH-1:0]			data_out;	
wire								empty	;	
wire								full	;
wire	[$clog2(DATA_DEPTH) : 0]	fifo_cnt;


//------------< instantiate the module under test >----------------------------------------
sync_fifo_ptr
#(
	.DATA_WIDTH	(DATA_WIDTH),			//FIFO bit width
    .DATA_DEPTH	(DATA_DEPTH)			//FIFO depth
)
sync_fifo_ptr_inst(
	.clk		(clk		),
	.rst_n		(rst_n		),
	.data_in	(data_in	),
	.rd_en		(rd_en		),
	.wr_en		(wr_en		),
                 
	.data_out	(data_out	),	
	.empty		(empty		),	
	.full		(full		)
);

//------------< set initial test conditions >----------------------------------------
initial begin
	clk = 1'b0;					//The initial clock is 0
	rst_n <= 1'b0;				//Initial reset
	data_in <= 'd0;
	wr_en <= 1'b0;
	rd_en <= 1'b0;
//Repeat the write operation 8 times to fill the FIFO 	
	repeat(8) begin
		@(negedge clk)begin
			rst_n <= 1'b1;		
			wr_en <= 1'b1;
			data_in <= $random;	//Generate 8-bit random number
		end
	end
//Repeat the read operation for 8 times to make the FIFO read empty 	
	repeat(8) begin
		@(negedge clk)begin		
			wr_en <= 1'b0;
			rd_en <= 1'd1;
		end
	end
//Repeat the write operation for 4 times and write 4 random data	
	repeat(4) begin
		@(negedge clk)begin		
			wr_en <= 1'b1;
			data_in <= $random;	//Generate 8-bit random number
			rd_en <= 1'b0;
		end
	end
//Continuously read and write FIFO at the same time, and the written data is random data	
	forever begin
		@(negedge clk)begin		
			wr_en <= 1'b1;
			data_in <= $random;	//Generate 8-bit random number
			rd_en <= 1'b1;
		end
	end
end

//------------< set clock >----------------------------------------------
always #10 clk = ~clk; 			// System clock cycle 20ns

endmodule

         modelsim is used for simulation, and the simulation results are as follows:

         It can be seen that the simulation result is consistent with the expected effect (see the note in the figure for details).

3. Other

  • After understanding the design method of synchronous FIFO, it is relatively simple to design asynchronous FIFO, which mainly solves the problem of cross clock domain comparison of read and write pointers in asynchronous FIFO. The next article will analyze and verify the design of asynchronous FIFO
  • Friends who need the whole project can leave a private letter or comment in the email
  • It's not easy to create. I hope you guys can support it for three times! If there is any mistake, please correct it!

 

 

        

        

Keywords: Verilog FPGA FIFO CDC

Added by Plagel on Thu, 04 Nov 2021 05:10:01 +0200