Wednesday, July 21, 2021

Verilog HDL Examples - FIFO Design - Asynchronous FIFOs



Asynchronous FIFO Design :

- A FIFO Design where data values are written to a FIFO buffer from one clock domain and the data values are read from the same FIFO buffer from another clock domain

- The clock domain are asynchronous to each other

- Asynchronous FIFOs are used to safely pass the data from one clock domain to another clock domain

- The main difficulty in designing the asynchronous FIFO design is related to generating the FIFO

pointers and finding a reliable way to determine FULL and EMPTY status on the FIFO

- Unlike synchronous FIFO Design (here) , the increment -decrement FIFO fill counter can not be used because two different and asynchronous clocks would be required to control the counter

- We need to compare write and read pointers to determine full and empty status for an asynchronous FIFO design

- The FIFO is empty when both read and write pointers are equal which happens when both pointers are reset to zero during a reset operation or when the read pointer catches up to the write pointer , having read the last word from the FIFO

- The FIFO is full when both write and read pointers are again equal that is when the write pointer has wrapped around and caught up to the read pointer

- In both Empty and Full FIFO condition both read and write pointers are equal. Problem ? Yes definitely ... Lets see how this can be avoided -

- Add an extra bit to each pointer

- When the write pointer increments past the final FIFO address, the write pointer will increment the unused MSB bit while setting rest of the bits to zero. The same is done for the read pointer.

- If the MSB of the two pointers are different, that means the write pointer has wrapped one more time than the read pointer

- If the MSB of both pointers are same that means both pointers have wrapped the same number of times





- EMPTY : When both read and synchronized write pointers are equal including MSB bit

- FULL : When the MSB is different and rest of the bits of both pointers are same

- Trying to synchronize a binary count value from one clock domain to another is problematic because every bit of an N-bit counter can change simultaneously ( Ex. 7 [0111] -> 8[1000])

- A good approach for FIFO counter pointers is to use Gray code counter. They allow only one bit to change for every clock transaction , eliminating the problem associated with trying to synchronize multiple changing signals on the same clock edge

- Block diagram of an Asynchronous FIFO -





- The write and read pointers are synchronized in the other domain before they are used for the comparison

- In this FIFO architecture, the read and write pointer must be passed to the opposite clock domain for pointer comparison

- There is a problem using Gary Code for determining the FULL condition as the Gray code is a symmetric code except the MSB bit 







- Problem #1 : As depicted in the above picture when both pointers points to the location Gray-7(The FIFO is EMPTY ) , On the next write operation, writer pointer will increment the Gray Counter and will point to (1_100) where the MSB of read and write pointer are not same but rest of the bits are same and the FIFO FULL Flag will get asserted which is WRONG ( Read Pointer = 0_100 , Write Pointer = 1_100)

- Problem #2 : When the write counter increments from Gray 7 to Gray 8, the 3 bit MSB ( which really points the memory address) are unchanged and results in the same memory location over- write

- Solution : Synchronized the read pointer in write clock domain and then meet the below 3 necessary conditions to calculate the FIFO FULL condition -

1) Write pointer and synchronized read pointers MSB's must be unequal ( Ensures one more time write pointer wrapping )

2) Write pointer and synchronized read pointer's MSB's are not equal (Use (n-1) Gray Code as discussed previously , the inverted 2nd MSB of write pointer must be tested against the un-inverted 2nd MSB's of read pointer )

3) All other bits of write and read pointer must be equal


Verilog HDL Code:

// Code your design here

module async_fifo #(parameter FIFO_DEPTH = 8,
parameter DATA_WIDTH = 8)(
input w_clk,
input r_clk,
input rst,
input [7:0] w_data,
input w_en,
output [7:0] r_data,
input r_en,
output full,
output empty
);

reg [3:0] wr_ptr, rd_ptr, wr_ptr_synced, rd_ptr_synced; // Extra 1 bit MSB for 'full' condition check
reg [3:0] wr_ptr_g, rd_ptr_g;
reg [DATA_WIDTH-1:0] mem[0: FIFO_DEPTH -1];

assign full = ((wr_ptr_synced[2:0] == rd_ptr_synced[2:0]) && (wr_ptr_synced[3] != rd_ptr_synced[3]));
assign empty = (wr_ptr_synced == rd_ptr_synced);

always @(posedge w_clk)
begin
if(w_en && !full)
begin
mem[wr_ptr] <= w_data;
end
end

always @(posedge r_clk)
begin
if(r_en && !empty)
begin
r_data <= mem[rd_ptr];
end
end

always @(posedge w_clk or negedge rst)
begin
if(!rst)
begin
wr_ptr <= 4'b0;
end
else if(w_en)
begin
wr_ptr <= wr_ptr + 1;
end
end

always @(posedge r_clk or negedge rst)
begin
if(!rst)
begin
rd_ptr <= 4'b0;
end
else if(r_en)
begin
rd_ptr <= rd_ptr + 1;
end
end

binary2gray b2g_wr_ptr(.i_binary(wr_ptr), .o_gray(wr_ptr_g)); //Not used here , Any Challenge if using gray code?
binary2gray b2g_rd_ptr(.i_binary(rd_ptr), .o_gray(rd_ptr_g)); // Not used here ,Any Challenge if using gray code?

dff_sync2 dff_sync2_wr_ptr_0(.clk(r_clk), .rst(rst), .d(wr_ptr[0]), .q_synced(wr_ptr_synced[0]));
dff_sync2 dff_sync2_wr_ptr_1(.clk(r_clk), .rst(rst), .d(wr_ptr[1]), .q_synced(wr_ptr_synced[1]));
dff_sync2 dff_sync2_wr_ptr_2(.clk(r_clk), .rst(rst), .d(wr_ptr[2]), .q_synced(wr_ptr_synced[2]));
dff_sync2 dff_sync2_wr_ptr_3(.clk(r_clk), .rst(rst), .d(wr_ptr[3]), .q_synced(wr_ptr_synced[3]));
dff_sync2 dff_sync2_rd_ptr_0(.clk(w_clk), .rst(rst), .d(rd_ptr[0]), .q_synced(rd_ptr_synced[0]));
dff_sync2 dff_sync2_rd_ptr_1(.clk(w_clk), .rst(rst), .d(rd_ptr[1]), .q_synced(rd_ptr_synced[1]));
dff_sync2 dff_sync2_rd_ptr_2(.clk(w_clk), .rst(rst), .d(rd_ptr[2]), .q_synced(rd_ptr_synced[2]));
dff_sync2 dff_sync2_rd_ptr_3(.clk(w_clk), .rst(rst), .d(rd_ptr[3]), .q_synced(rd_ptr_synced[3]));

endmodule

module binary2gray #(NUM_BITS = 4)(
input[3:0] i_binary,
output[3:0] o_gray
)

assign o_gray[3] = i_binary[3];
assign o_gray[2] = i_binary[3] ^ i_binary[2];
assign o_gray[1] = i_binary[2] ^ i_binary[1];
assign o_gray[0] = i_binary[1] ^ i_binary[0];

endmodule


module dff_sync2(
input clk,
input rst,
input d,
output q_synced
);

reg sync_flop1;
reg sync_flop2;

always @(posedge clk or negedge rst)
begin
if(!rst)
begin
sync_flop1 <= 1'b0;
sync_flop2 <= 1'b0;
end
else begin
sync_flop1 <= d;
sync_flop2 <= sync_flop1;
end
end
assign q_synced = sync_flop2;
endmodule


-------------------------------------------------Happy Learning -----------------------------------------

0 Comments:

Post a Comment