Hand tear code – asynchronous FIFO
- 1. Asynchronous FIFO principle and design
-
- Read and write address pointer control
- Reading and writing address pointer cross-clock processing and full and empty signal judgment
- Read and write addresses and read and write operations
- 2. Complete code and simulation files
- 3. Simulation results
1. Asynchronous FIFO principle and design
In the design of FIFO, whether it is a synchronous FIFO or an asynchronous FIFO, the most important thing is how to judge the empty signal empty and the full signal full. In the above “Hand Tear Code – Synchronous FIFO”, the synchronous FIFO we designed uses a data counter cnt to calculate the number of data stored in the current FIFO, and judges the empty and full signal of the FIFO according to the value of the counter cnt Signal full. If the counter cnt=0, it means that the number of data currently stored in the FIFO is 0, then pull up the empty signal empty; if the counter cnt=FIFO_DEPTH (FIFO depth), it means pull up the full signal full. Then, in the design of asynchronous FIFO, can the counter be used to judge the empty and full signal? In the design of the asynchronous FIFO, we do not use the counter to judge whether it is full or empty, but increase the bit width of the read-write pointer address by one redundant bit, which is used to judge whether it is full or empty.
Read and write address pointer control
First of all, it is the control of reading and writing address pointers. In the write clock domain, when the write enable wr_en is valid and the full signal full is low, it means that the data in the FIFO is not full, and the write operation is performed, and the write address pointer wr_ptr is incremented by 1; in the read clock domain, when the read enable rd_en is valid And when the empty signal empty is low level, it means that the data in the FIFO is not read empty, and the read operation is performed, and the read address pointer rd_ptr is automatically incremented by 1.
//write pointer always @(posedge wr_clk or negedge wr_rst_n) begin if(!wr_rst_n) wr_ptr <= 'd0; else if(wr_en & amp; & amp; !full) wr_ptr <= wr_ptr + 1'b1; end //read pointer always @(posedge rd_clk or negedge rd_rst_n) begin if(!rd_rst_n) rd_ptr <= 'd0; else if(rd_en & amp; & amp; !empty) rd_ptr <= rd_ptr + 1'b1; end
Read and write address pointer cross-clock processing and empty-full signal judgment
Secondly, use the read and write address pointer to judge the empty and full signal. The read address pointer rd_ptr is in the read clock domain wr_clk, and the empty signal empty is also generated in the read clock domain; while the write address pointer wr_ptr is in the write clock domain, and the full signal is also generated in the write clock domain.
Then, to use the read address pointer rd_ptr to compare with the write address pointer wr_ptr to generate an empty signal empty, can it be directly compared? The answer is no. Because these two signals are in different clock domains, cross-clock domain CDC processing is required, and multi-bit signals are processed across clock domains. The common method is to use asynchronous FIFO for synchronization, but aren’t we designing asynchronous FIFO? Therefore, the asynchronous FIFO is designed here. The problem of multi-bit cross-clock domain processing can be transformed into single-bit cross-clock domain processing. After converting the read and write address pointers into Gray codes, cross-clock domain processing is performed, because no matter how many bits of Gray Code, every time adding 1, only change 1 bit. Convert the read address pointer rd_ptr to Gray code, and then synchronize to the write clock domain wr_clk, compare and judge with the Gray code representation of the write pointer address wr_ptr, and obtain the full signal full; similarly, convert the write address pointer wr_ptr to Gray code, and then Synchronize to the read clock domain rd_clk, compare and judge with the Gray code representation of the read address pointer rd_ptr, and obtain the empty signal empty.
Therefore, it is necessary to first convert the read address pointer rd_ptr and write address pointer wr_ptr represented by binary code into Gray code (for the conversion principle and design of binary code and Gray code, refer to “The principle of mutual conversion between binary code and Gray code and Verilog implementation” ).
//write pointer(binary to gray) assign wr_ptr_gray = wr_ptr ^ (wr_ptr>>1); //read pointer(binary to gray) assign rd_ptr_gray = rd_ptr ^ (rd_ptr>>1);
Then do single-bit cross-clock domain processing on the read-write address pointer represented by Gray code, and make two beats. It should be noted that: the gray code wr_ptr_gray of the write address pointer indicates that it needs to be synchronized to the read clock domain, so the clock signal rd_clk of the read clock domain should be used; the gray code of the read address pointer indicates that rd_ptr_gray indicates that it must be synchronized to the write clock domain, so it is necessary to Use the clock signal wr_clk of the write clock domain.
//gray write pointer syncrous always @(posedge rd_clk or negedge rd_rst_n) begin if(!rd_rst_n) begin wr_ptr_gray_w2r_1 <= 'd0; wr_ptr_gray_w2r_2 <= 'd0; end else begin wr_ptr_gray_w2r_1 <= wr_ptr_gray; wr_ptr_gray_w2r_2 <= wr_ptr_gray_w2r_1; end end //gray read pointer syncrous always @(posedge wr_clk or negedge wr_rst_n) begin if(!wr_rst_n) begin rd_ptr_gray_r2w_1 <= 'd0; rd_ptr_gray_r2w_2 <= 'd0; end else begin rd_ptr_gray_r2w_1 <= rd_ptr_gray; rd_ptr_gray_r2w_2 <= rd_ptr_gray_r2w_1; end end
Then use the gray code representation of the read and write address pointer after synchronous processing for comparison, and get the empty and full signal. If the Gray code of the write address pointer indicates that it is synchronized to the read clock domain, it is consistent with the Gray code of the write address pointer, indicating that it is empty; if the Gray code of the read address pointer indicates that it is synchronized to the write clock domain, the upper two bits are the same as the write The gray code of the address pointer is opposite, and the remaining bits are consistent with the gray code of the write address pointer, which means that it is full.
//full and empty assign full = (wr_ptr_gray == {<!-- -->~rd_ptr_gray_r2w_2[ADDR_WIDTH:ADDR_WIDTH-1],rd_ptr_gray_r2w_2[ADDR_WIDTH-2:0]}) ? 1'b1 : 1'b0; assign empty = (rd_ptr_gray == wr_ptr_gray_w2r_2) ? 1'b1 : 1'b0;
Read and write addresses and read and write operations
In the read-write address pointer, we set a redundant bit width of one bit to judge whether it is empty or full. Then, in fact, the read-write address should be the second highest bit to the 0th bit of the read-write address pointer. Take the second highest bit of the read-write address pointer to the 0th bit to get the read-write address, and perform read-write operations according to the read-write address.
//write address and read address assign wr_addr = wr_ptr[ADDR_WIDTH-1:0]; assign rd_addr = rd_ptr[ADDR_WIDTH-1:0];
//write always @(posedge wr_clk or negedge wr_rst_n) begin if(!wr_rst_n) for(i=0;i<FIFO_DEPTH;i=i + 1) begin mem[i] <= 'd0; end else if(wr_en & amp; & amp; !full) mem[wr_addr] <= din; end //read always @(posedge rd_clk or negedge rd_rst_n) begin if(!rd_rst_n) dout_r <= 'd0; else if(rd_en & amp; & amp; !empty) dout_r <= mem[rd_addr]; end assign dout = dout_r;
2. Complete code and simulation file
The complete code of asynchronous FIFO is as follows:
`timescale 1ns / 1ps // // Company: //Engineer: // // Create Date: 2023/05/17 20:24:34 // Design Name: // Module Name: async_fifo // Project Name: //Target Devices: // Tool Versions: // Description: Asynchronous FIFO // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // // module async_fifo #( parameter FIFO_WIDTH = 8, parameter FIFO_DEPTH = 8, parameter ADDR_WIDTH = $clog2(FIFO_DEPTH) ) ( //write clock domain input wr_clk, input wr_rst_n , input wr_en , input [FIFO_WIDTH-1:0] din , output full , //read clock domain input rd_clk, input rd_rst_n, input rd_en , output [FIFO_WIDTH-1:0] dout , output empty ); //------------------------------------------------ // integer i; //memory reg [FIFO_WIDTH-1:0] mem [FIFO_DEPTH-1:0]; //write address and read address wire [ADDR_WIDTH-1:0] wr_addr; wire [ADDR_WIDTH-1:0] rd_addr; //write pointer reg [ADDR_WIDTH:0] wr_ptr; wire [ADDR_WIDTH:0] wr_ptr_gray; reg [ADDR_WIDTH:0] wr_ptr_gray_w2r_1; reg [ADDR_WIDTH:0] wr_ptr_gray_w2r_2; //read pointer reg [ADDR_WIDTH:0] rd_ptr; wire [ADDR_WIDTH:0] rd_ptr_gray; reg [ADDR_WIDTH:0] rd_ptr_gray_r2w_1; reg [ADDR_WIDTH:0] rd_ptr_gray_r2w_2; // reg [FIFO_WIDTH-1:0] dout_r; //------------------------------------------------ // //----------------------// //write pointer always @(posedge wr_clk or negedge wr_rst_n) begin if(!wr_rst_n) wr_ptr <= 'd0; else if(wr_en & amp; & amp; !full) wr_ptr <= wr_ptr + 1'b1; end //write pointer(binary to gray) assign wr_ptr_gray = wr_ptr ^ (wr_ptr>>1); //gray write pointer syncrous always @(posedge rd_clk or negedge rd_rst_n) begin if(!rd_rst_n) begin wr_ptr_gray_w2r_1 <= 'd0; wr_ptr_gray_w2r_2 <= 'd0; end else begin wr_ptr_gray_w2r_1 <= wr_ptr_gray; wr_ptr_gray_w2r_2 <= wr_ptr_gray_w2r_1; end end //----------------------// //read pointer always @(posedge rd_clk or negedge rd_rst_n) begin if(!rd_rst_n) rd_ptr <= 'd0; else if(rd_en & amp; & amp; !empty) rd_ptr <= rd_ptr + 1'b1; end //read pointer(binary to gray) assign rd_ptr_gray = rd_ptr ^ (rd_ptr>>1); //gray read pointer syncrous always @(posedge wr_clk or negedge wr_rst_n) begin if(!wr_rst_n) begin rd_ptr_gray_r2w_1 <= 'd0; rd_ptr_gray_r2w_2 <= 'd0; end else begin rd_ptr_gray_r2w_1 <= rd_ptr_gray; rd_ptr_gray_r2w_2 <= rd_ptr_gray_r2w_1; end end //----------------------// //write address and read address assign wr_addr = wr_ptr[ADDR_WIDTH-1:0]; assign rd_addr = rd_ptr[ADDR_WIDTH-1:0]; //full assign full = (wr_ptr_gray == {~rd_ptr_gray_r2w_2[ADDR_WIDTH:ADDR_WIDTH-1],rd_ptr_gray_r2w_2[ADDR_WIDTH-2:0]}) ? 1'b1 : 1'b0; //empty assign empty = (rd_ptr_gray == wr_ptr_gray_w2r_2) ? 1'b1 : 1'b0; //----------------------// //write always @(posedge wr_clk or negedge wr_rst_n) begin if(!wr_rst_n) for(i=0;i<FIFO_DEPTH;i=i + 1) begin mem[i] <= 'd0; end else if(wr_en & amp; & amp; !full) mem[wr_addr] <= din; end //read always @(posedge rd_clk or negedge rd_rst_n) begin if(!rd_rst_n) dout_r <= 'd0; else if(rd_en & amp; & amp; !empty) dout_r <= mem[rd_addr]; end assign dout = dout_r; endmodule
The simulation file is as follows:
`timescale 1ns / 1ps // // Company: //Engineer: // // Create Date: 2023/05/17 21:18:22 // Design Name: // Module Name: tb_async_fifo // Project Name: //Target Devices: // Tool Versions: //Description: // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // // module tb_async_fifo(); parameter FIFO_WIDTH = 8; parameter FIFO_DEPTH = 8; parameter ADDR_WIDTH = $clog2(FIFO_DEPTH); reg rst_n; reg wr_clk; reg wr_en; reg [FIFO_WIDTH-1:0] din; wire full; reg rd_clk; reg rd_en; wire [FIFO_WIDTH-1:0] dout; wire empty; initial begin rst_n <= 1'b0; wr_clk = 1'b1; rd_clk = 1'b1; wr_en <= 1'b0; rd_en <= 1'b0; din <= 8'b0; # 5 rst_n <= 1'b1; end initial begin #20 wr_en <= 1'b1; rd_en <= 1'b0; #40 wr_en <= 1'b0; rd_en <= 1'b1; #30 wr_en <= 1'b1; rd_en <= 1'b0; #13 rd_en <= 1'b1; #10 repeat(100) begin #5 wr_en <= {$random}%2 ; rd_en <= {<!-- -->$random}%2 ; end #100 $finish; end always #1.5 wr_clk = ~wr_clk; always #1 rd_clk = ~rd_clk; always #3 din <= {$random}%8'hFF; async_fifo #( .FIFO_WIDTH(FIFO_WIDTH), .FIFO_DEPTH(FIFO_DEPTH), .ADDR_WIDTH(ADDR_WIDTH) ) async_fifo ( .wr_clk (wr_clk ), .wr_rst_n (rst_n ), .wr_en (wr_en ), .din (din ), .full (full ), .rd_clk (rd_clk ), .rd_rst_n (rst_n ), .rd_en (rd_en ), .dout (dout ), .empty (empty ) ); endmodule
3. Simulation results
According to the TestBench file, simulate the asynchronous FIFO. It can be seen that in the write clock domain wr_clk, the write enable wr_en is valid, and the data write operation is successfully performed. After 8 data are successfully written, the write full signal full is pulled high. During the full signal full is high, Even if the write enable wr_en is valid, no data write operation is performed.
At the same time, it can be seen that the write address pointer wr_ptr is under the write clock domain wr_clk. When the write enable wr_en is valid, it will be incremented by 1. At the same time, the Gray code representation of the write pointer address wr_ptr_gray is synchronized to the read clock domain by two beats.
You can see that the FIFO read data is correct. After reading all the data in the FIFO, pull up the empty read signal empty.
In summary, the asynchronous FIFO design has passed the verification.