Saturday, May 2, 2020

Synchronous FIFO Verilog Code

The First In First Out (FIFO) is a data arrangement structure in which the data that enters first is the one that is removed first. Let us see how to implement Synchronous FIFO in Verilog in this post.


Procedure to implement FIFO:
  1. Create a normal memory in Verilog.
  2. When the data and push signal is given, write to the memory starting from first address.
  3. When pop signal is given, read from the memory from the first address.
  4. When FIFO becomes empty, assert empty and if it becomes full, assert full signal.
We require a write pointer as well as a read pointer to control a FIFO because we keep incrementing the address while writing, whereas for read, we have to start from the first address.

Empty and Full:
Empty and full condition assertion in synchronous FIFO are not as simple as in stack. Here we have rdptr and wrptr. When both the pointers are equal, the FIFO could either be empty or full.

If the last operation was write and the pointers become equal, then the FIFO is full.
If the last operation was read and the pointers become equal, then the FIFO is empty.

So we use another variable 'fullchk' to check if the last operation was write and the FIFO became full. Then this can be used to assert full signal.
Similarly if 'emptychk' is there, then we can say that the FIFO is empty.

Verilog Code Logic:
  1. Use variables rdptr to traverse read location and wrptr to traverse write locations in memory.
  2. During push signal, write to the memory and increment wrptr.
  3. During pop, read from the memory and decrement rdptr.
  4. Empty is asserted when pointers are equal and there is emptychk. Deasserted when a push occurs.
  5. Full occurs when there is fullchk. Deasserted when a pop occurs.
Verilog Code:

module sync_fifo(
 clk,
 rstn,
 pop,
 push,
 empty,
 full,
 din,
 dout
    );

parameter PTR_WIDTH = 3;
parameter DATA_WIDTH = 8;
parameter DEPTH = 8;

input clk;
input rstn;
input pop;
input push;
input [DATA_WIDTH-1:0]din;

output [DATA_WIDTH-1:0]dout;
output empty;
output full;

reg [DATA_WIDTH-1:0]fifo[DEPTH-1:0];
reg [PTR_WIDTH-1:0]rdptr, next_rdptr;
reg [PTR_WIDTH-1:0]wrptr, next_wrptr;
reg [DATA_WIDTH-1:0]dout, next_dout;
reg empty, next_empty;
reg full, next_full;

assign fullchk = push && !(|(wrptr^(rdptr-1'b1)));
assign emptychk = pop && !(|(rdptr^(wrptr-1'b1)));

always @ (posedge clk) //Sequential block
begin
  if(!rstn)
  begin
    dout    <= 8'd0;
    empty   <= 1'b1;
    full    <= 1'b0;
    rdptr   <= 1'b0;
    wrptr   <= 1'b0;
  end
  else
  begin
    dout    <= next_dout;
    empty   <= next_empty;
    full    <= next_full; 
    rdptr   <= next_rdptr;
    wrptr   <= next_wrptr;
  end
end

always @ (*) //Combinational Block
begin
  next_dout    = dout;
  next_empty   = emptychk ? 1'b1 : push ? 1'b0 : empty;
  next_full    = fullchk ? 1'b1 : pop ? 1'b0 : full;
  next_rdptr   = rdptr;
  next_wrptr   = wrptr;
 
  if(push)  //write
  begin
  fifo[wrptr] = din;
  next_wrptr  = wrptr+1;
  end
  else if(pop)  //read
  begin
  next_dout    = fifo[rdptr];
  next_rdptr   = rdptr+1;
  end
  else
  begin
  next_dout  = dout;
  next_rdptr = rdptr;
  next_wrptr = wrptr;
  end 
end
endmodule

Testbench:

module Fifo_tb;
 // Inputs
 reg clk;
 reg rstn;
 reg pop;
 reg push;
 reg [7:0] din;

 // Outputs
 wire empty;
 wire full;
 wire [7:0] dout;

 // Instantiate the Unit Under Test (UUT)
 Fifo uut (
  .clk(clk), 
  .rstn(rstn), 
  .pop(pop), 
  .push(push), 
  .empty(empty), 
  .full(full), 
  .din(din), 
  .dout(dout)
 );

 always #5 clk = ~clk;
 
 task reset();
 begin
 clk = 1'b1;
 rstn = 1'b0;
 pop = 1'b0;
 push = 1'b0;
 din = 8'd0;
 #30; rstn = 1'b1;
 end
 endtask

 task read_fifo();
 begin
 pop = 1'b1;
 #10;
 pop = 1'b0;
 end
 endtask
   
 task write_fifo([7:0]din_tb);
 begin
 push = 1'b1;
 din = din_tb;
 #10 push = 1'b0;
 end
 endtask
 
 // Main code
 initial
 begin
 reset();
 #10;
 repeat(2)
 begin
 write_fifo(8'h11);
 #10;
 write_fifo(8'h22);
 #10;
 write_fifo(8'h33);
 #10;
 write_fifo(8'h44);
 #10;
 end
 #10;
 repeat(2)
 begin
 read_fifo();
 #10;
 end
 #10;
 $finish;
 end
      
endmodule

Simulation Result:


From the simulation result,
  1. The FIFO is initially empty as there is no data.
  2. Data is pushed into it until it becomes full.
  3. After that all the data have been read, indicating empty. Since it is a FIFO, the first data which entered is the one that is removed.
Thus we have verified the basic operation of a Synchronous FIFO.
Note: Depth of the FIFO must be in powers of 2 and the ptr_width parameter must be set to log to the base 2 of FIFO depth.

Further Steps:
Next you can look into an Asynchronous FIFO. This kind of FIFO works on two different clocks and so we require synchronization. This makes its implementation a little more complex. 

However here is a good article on the design of an Asynchronous FIFO on verilogpro:

1 comment: