A FIFO buffer helps store data in the order it arrives and gives it back in the same order. It works like a line where the first person in is the first person out.
FIFO buffer design concept in Verilog
module fifo_buffer #(parameter DATA_WIDTH = 8, parameter DEPTH = 16) ( input wire clk, input wire reset, input wire write_enable, input wire read_enable, input wire [DATA_WIDTH-1:0] data_in, output reg [DATA_WIDTH-1:0] data_out, output wire full, output wire empty ); reg [DATA_WIDTH-1:0] memory [0:DEPTH-1]; reg [$clog2(DEPTH)-1:0] write_pointer = 0; reg [$clog2(DEPTH)-1:0] read_pointer = 0; reg [$clog2(DEPTH):0] count = 0; assign full = (count == DEPTH); assign empty = (count == 0); always @(posedge clk or posedge reset) begin if (reset) begin write_pointer <= 0; read_pointer <= 0; count <= 0; data_out <= 0; end else begin if (write_enable && !full) begin memory[write_pointer] <= data_in; write_pointer <= (write_pointer + 1) % DEPTH; end if (read_enable && !empty) begin data_out <= memory[read_pointer]; read_pointer <= (read_pointer + 1) % DEPTH; end if (write_enable && !full && !(read_enable && !empty)) begin count <= count + 1; end else if (read_enable && !empty && !(write_enable && !full)) begin count <= count - 1; end end end endmodule
This FIFO uses a circular buffer with read and write pointers.
The 'full' and 'empty' signals help control when to write or read data safely.
module fifo_buffer #(parameter DATA_WIDTH = 8, parameter DEPTH = 4) ( input wire clk, input wire reset, input wire write_enable, input wire read_enable, input wire [DATA_WIDTH-1:0] data_in, output reg [DATA_WIDTH-1:0] data_out, output wire full, output wire empty ); reg [DATA_WIDTH-1:0] memory [0:DEPTH-1]; reg [$clog2(DEPTH)-1:0] write_pointer = 0; reg [$clog2(DEPTH)-1:0] read_pointer = 0; reg [$clog2(DEPTH):0] count = 0; assign full = (count == DEPTH); assign empty = (count == 0); always @(posedge clk or posedge reset) begin if (reset) begin write_pointer <= 0; read_pointer <= 0; count <= 0; data_out <= 0; end else begin if (write_enable && !full) begin memory[write_pointer] <= data_in; write_pointer <= (write_pointer + 1) % DEPTH; end if (read_enable && !empty) begin data_out <= memory[read_pointer]; read_pointer <= (read_pointer + 1) % DEPTH; end if (write_enable && !full && !(read_enable && !empty)) begin count <= count + 1; end else if (read_enable && !empty && !(write_enable && !full)) begin count <= count - 1; end end end endmodule
// Edge case: FIFO empty - no read allowed // read_enable is high but empty is true, so data_out stays the same. // Edge case: FIFO full - no write allowed // write_enable is high but full is true, so new data is ignored.
// Edge case: Only one element in FIFO // write_pointer and read_pointer point to the same location. // Reading removes the element, making FIFO empty. // Writing adds an element, making FIFO full if depth is 1.
This testbench creates a FIFO with depth 4 and 8-bit data. It writes three values, reads two, writes two more, then reads all remaining values. The monitor prints the FIFO status at each step.
`timescale 1ns / 1ps
module test_fifo;
reg clk = 0;
reg reset = 1;
reg write_enable = 0;
reg read_enable = 0;
reg [7:0] data_in = 0;
wire [7:0] data_out;
wire full;
wire empty;
fifo_buffer #(8, 4) my_fifo (
.clk(clk),
.reset(reset),
.write_enable(write_enable),
.read_enable(read_enable),
.data_in(data_in),
.data_out(data_out),
.full(full),
.empty(empty)
);
always #5 clk = ~clk; // 10ns clock period
initial begin
$display("Time | Reset | Write | Read | Data In | Data Out | Full | Empty");
$monitor("%4t | %b | %b | %b | %d | %d | %b | %b", $time, reset, write_enable, read_enable, data_in, data_out, full, empty);
#10 reset = 0;
// Write 3 values
data_in = 10; write_enable = 1; read_enable = 0; #10;
data_in = 20; #10;
data_in = 30; #10;
write_enable = 0;
// Read 2 values
read_enable = 1; #20;
read_enable = 0;
// Write 2 more values
data_in = 40; write_enable = 1; #10;
data_in = 50; #10;
write_enable = 0;
// Read all values until empty
read_enable = 1; #40;
$finish;
end
endmodule
module fifo_buffer #(parameter DATA_WIDTH = 8, parameter DEPTH = 4) (
input wire clk,
input wire reset,
input wire write_enable,
input wire read_enable,
input wire [DATA_WIDTH-1:0] data_in,
output reg [DATA_WIDTH-1:0] data_out,
output wire full,
output wire empty
);
reg [DATA_WIDTH-1:0] memory [0:DEPTH-1];
reg [$clog2(DEPTH)-1:0] write_pointer = 0;
reg [$clog2(DEPTH)-1:0] read_pointer = 0;
reg [$clog2(DEPTH):0] count = 0;
assign full = (count == DEPTH);
assign empty = (count == 0);
always @(posedge clk or posedge reset) begin
if (reset) begin
write_pointer <= 0;
read_pointer <= 0;
count <= 0;
data_out <= 0;
end else begin
if (write_enable && !full) begin
memory[write_pointer] <= data_in;
write_pointer <= (write_pointer + 1) % DEPTH;
end
if (read_enable && !empty) begin
data_out <= memory[read_pointer];
read_pointer <= (read_pointer + 1) % DEPTH;
end
if (write_enable && !full && !(read_enable && !empty)) begin
count <= count + 1;
end else if (read_enable && !empty && !(write_enable && !full)) begin
count <= count - 1;
end
end
end
endmoduleTime complexity for read and write is O(1) because pointers move directly.
Space complexity is O(depth) for the memory array.
Common mistake: not checking full or empty before writing or reading can cause data loss or errors.
Use FIFO when you need ordered data buffering; use other buffers if order is not important.
FIFO stores data in the order it arrives and outputs in the same order.
It uses read and write pointers to track where to read and write next.
Signals 'full' and 'empty' help control safe reading and writing.