Asynchronous FIFO :

Asynchronous FIFO is needed whenever we want to transfer data between design blocks that are in different clock domains. The difference in clock domains makes writing and reading the FIFO tricky.

If appropriate precautions are not taken then we could end up in a scenario where write into FIFO has not yet finished and we are attempting to Read it or Vice-versa. This scenario often causes data loss and Metastability issues.

In order to avoid such scenarios, the reading and writing is done via a synchronizer. The synchronizer ensures that read and write pointers calculations are consistent and data in FIFO is not accidentally overwritten or read twice.

However, with the clock crossing we need to ensure that FIFO full and empty conditions are taking into account the clock crossing cycles. In other words, pessimistic full and empty conditions need to be added.

Here’s an example to 8-deep FIFO with Write in aclk domain and read in bclk domain:

logic [3:0] wraddr, wraddr_gray, rdaddr, rdaddr_gray;
logic [3:0] wraddr_b1ff,wraddr_bff, wraddr_aff;
logic [3:0] rdaddr_a1ff, rdaddr_aff, rdaddr_bff;
logic [7:0] wren, wren_qual, rden, rden_qual;
logic [31:0] data_ff[7:0];
logic [31:0] data_in, data_out;
logic wr_en, rd_en;
logic fifo_empty, fifo_full;

always_comb begin
if(!fifo_full & wr_en)
  wraddr = wraddr_ff + 1;
else
  wraddr = wraddr_aff;
end

always_ff @(posedge aclk or negedge reset) begin
 if(!reset)
   wraddr_aff <= 0;
 else if (wr_en)
   wraddr_aff <=  wraddr;
 else
   wraddr_aff <= wraddr_aff;
end

//---- Convert wraddr to Gray code ----//
assign wraddr_gray = (wraddr >> 1) ^ wraddr;

//----- Clock sync to bclk --------//
always_ff @(posedge bclk or negedge reset) begin
 if(!reset) begin
   wraddr_bff  <= 0;
   wraddr_b1ff <= 0;
 end
 else begin
   wraddr_bff  <= wraddr_gray;
   wraddr_b1ff <= wraddr_bff;
 end
end

always_comb begin
 if(!fifo_empty & rd_en)
   rdaddr = rdaddr_bff + 1;
 else
   rdaddr = rdaddr_bff;
end

always_ff @(posedge bclk or negedge reset) begin
 if(!reset)
   rdaddr_bff <= 0;
 else if (rd_en)
   rdaddr_bff <=  rdaddr;
 else
   rdaddr_bff <= rdaddr_bff;
end

//------ Convert rdaddr to Gray code -------//
assign rdaddr_gray = (rdaddr >> 1) ^ rdaddr;

//------- Clock Sync to aclk ----------//
always_ff @(posedge aclk or negedge reset) begin
 if(!reset) begin
   rdaddr_aff  <= 0;
   rdaddr_a1ff <= 0;
 end
 else begin
   rdaddr_aff  <= rdaddr_gray;
   rdaddr_a1ff <= rdaddr_aff;
 end
end

//------- Data Read and Write ---------//
assign wren_qual = (wr_en & !fifo_full) ? 
                   (1 << wraddr) : 8'b0;
assign rden_qual = (rd_en & !fifo_empty) ? 
                   (1 << rdaddr) : 8'b0;

genvar i;
generate
for (i = 0; i < 8; i++) begin  
always_ff @(posedge aclk or negedge reset) begin
  if(wren_qual[i]) 
     data_ff[i]  <= data_in;
   else
     data_ff[i] <= data_ff[i];
  end
end
endgenerate

always_comb begin
 for (int j = 0; j < 8; j++) begin
  if(rden_qual[j]) 
    data_out = data_ff[j];
  else
    data_out = 8'b0;  
end

//------ Full and Empty Conditions -----------------//
assign fifo_full = 
(wraddr_gray[2:0] == rdaddr_a1ff[2:0]) &&
(wraddr_gray[3:3] != rdaddr_a1ff[3:3]);

assign fifo_empty = 
(wraddr_b1ff[3:0] == rdaddr_gray[3:0]);

Alternately, similar to Synchronous FIFO, we can also use synchronized write and read rollover signals in calculation of full and empty conditions.

Synchronous FIFO :

Fifo (first-in-first-out) are used to for serial transfer of information whenever there is a difference of Transfer rate. The Transfer rate may differ due to difference in number of ports, frequency or data-width between source and destination.

The FIFO width is chosen to compensate for the Transfer rate and is calculated as follows:

Fifo size = Source Freq. * ports * Data-with / Dest. Freq. * ports * Data-with

Ex: Source : Port = 1, Freq. = 100KHz, Data-Width = 20
Destination : Port = 2, Freq. = 50KHz, Data-Width = 10
FIFO Size : 1*100*20/ 1*50*10 = 4 Entries

If the FIFO size is a fractional number then we round-up the FIFO size to nearest largest whole number. For Ex 4.33 -> 5.

Here’s a SV reference for 8 Entry deep FIFO with Data-Width of 32 bits :

8-Entry FIFO
logic [3:0] wraddr, rdaddr;
logic [7:0] wren, rden;
logic [31:0] data_ff[7:0];
logic [31:0] data_in, data_out;
logic wr_en, rd_en;
logic fifo_empty, fifo_full;

always_comb begin
if(!fifo_full & wr_en)
 wraddr = wraddr + 1;
else
 wraddr = wraddr_ff;
end

always_comb begin
 if(!fifo_empty & rd_en)
   rdaddr = rdaddr + 1;
 else
   rdaddr = rdaddr_ff;
end

always_ff @(posedge clk or negedge reset) begin
 if(!reset)
   rdaddr_ff <= 0;
 else if (rd_en)
  rdaddr_ff <=  rdaddr;
 else
  rdaddr_ff <= rdaddr_ff;
end

always_ff @(posedge clk or negedge reset) begin
 if(!reset)
   wraddr_ff <= 0;
 else if (wr_en)
  wraddr_ff <=  wraddr;
 else
  wraddr_ff <= wraddr_ff;
end

assign fifo_full = (wraddr[2:0] == rdaddr[2:0]) & 
                   (wraddr[3] != rdaddr[3]);

assign fifo_empty = (wraddr[2:0] == rdaddr[2:0]) & 
                    (wraddr[3] == rdaddr[3]);

assign wren = (wr_en == 1'b1) ? (1 << wraddr) : 8'b0;
assign rden = (rd_en == 1'b1) ? (1 << rdaddr) : 8'b0;

genvar i;
generate 
for (i = 0; i < 8; i++) begin  
always_ff @(posedge clk or negedge reset) begin
  if(wren[i]) 
     data_ff[i]  <= data_in;
   else
      data_ff[i] <= data_ff[i];
  end
end
endgenerate

always_comb begin
 for (int j = 0; j < 8; j++) begin
  if(rden[j]) 
    data_out = data_ff[j];
  else
    data_out = 8'b0;  
end

If the Fifo Width is not binary multiples of 2 , detecting full and empty conditions is difficult using above method. Alternately, we can use below two implementations that are more scalable with Fifo Widths:

1st implementation can be followed with a use of Single Counter to track Full and Empty conditions :

logic [2:0] fifo_count, fifo_count_ff;

always_comb begin
unique casez ({rd_en, wr_en})
2'b00 : fifo_count = fifo_count_ff;
2'b01 : fifo_count = (fifo_count_ff == 3'b111) ? 3'b111
: (fifo_count_ff + 3'b001);
2'b10 : fifo_count = (fifo_count_ff == 3'b0) ? 3'b0
: (fifo_count_ff - 3'b001);
2'b11 : fifo_count = fifo_count_ff;
endcase
end

always_ff @(posedge clk or negedge reset) begin
if(!reset)
fifo_count_ff <= 3'b0;
else
fifo_count_ff <= fifo_count;
end

assign fifo_full = (fifo_count == 3'b111);
assign fifo_empty = (fifo_count == 3'b0);

2nd implementation is using read and write roll-over pointers on wraddr and rdaddr.

logic wr_rollover, wr_rollover_ff;
logic rd_rollover, rd_rollover_ff;

always_comb begin
if((wraddr == 3'b111) & (wr_en & !rd_en))
wr_rollover = ~wr_rollover_ff;
else
wr_rollover = wr_rollover_ff;
end

always_ff @(posedge clk or negedge reset) begin
if (!reset)
wr_rollover_ff <= 1'b0;
else
wr_rollover_ff <= wr_rollover;
end

always_comb begin
if((rdaddr == 3'b0) & (!wr_en & rd_en))
rd_rollover = ~rd_rollover_ff;
else
rd_rollover = rd_rollover_ff;
end

always_ff @(posedge clk or negedge reset) begin
if (!reset)
rd_rollover_ff <= 1'b0;
else
rd_rollover_ff <= rd_rollover;
end

assign fifo_full = (wraddr[2:0] == rdaddr[2:0]) &
(wr_rollover != rd_rollover);

assign fifo_empty = (wraddr[2:0] == rdaddr[2:0]) &
(wr_rollover == rd_rollover);