Synchronous FIFO Design and Implementation in Verilog
A Synchronous FIFO (First-In, First-Out) is a digital storage structure where data is written and read using a single clock source. This component is essential for buffering data streams between logic blocks that operate within the same clock domain but may process data at different rates.
Core Design Components
Clock and Synchronous Logic
In a synchronous FIFO, all internal registers, including the memory array, write pointers, and read pointers, are driven by a single clock signal. This simplifies the design as there are no metastability issues typical of cross-clock domain transfers, allowing status flags like 'full' and 'empty' to be generated through direct comparison of pointers or occupancy counters.
Pointer Management
Two primary registers track the state of the buffer:
- Write Pointer: Indicates the memory address for the next incoming data word. It increments after every successful write operation.
- Read Pointer: Indicates the memory address of the next data word to be retrieved. It increments after every successful read operation. Both pointers typically wrap around to zero upon reaching the maximum depth of the FIFO.
Memory Array
The data is stored in a dual-port RAM or a register bank. This structure allows simultaneous read and write access, provided the FIFO is neither full nor empty.
Status Flags and Flow Control
- Full Flag: Logic that prevents further writing when the buffer caapcity is reached. This is usually determined when the write pointer catches up to the read pointer (or when an occupancy counter equals the depth).
- Empty Flag: Logic that prevents reading when there is no valid data. This occurs when the read pointer equals the write pointer.
- Occupancy Counter: A counter that tracks the number of words current stored, facilitating the generation of 'almost full' or 'almost empty' signals.
Verilog Implementation
The following implementation utilizes a counter-based approach to manage the FIFO status flags and pointer increments.
module SynchronousFIFO #(
parameter WIDTH = 8,
parameter DEPTH = 16
)(
input wire clk,
input wire rst_n,
input wire w_en,
input wire [WIDTH-1:0] w_data,
input wire r_en,
output reg [WIDTH-1:0] r_data,
output wire full,
output wire empty,
output reg [$clog2(DEPTH):0] count
);
reg [WIDTH-1:0] fifo_mem [0:DEPTH-1];
reg [$clog2(DEPTH)-1:0] w_ptr;
reg [$clog2(DEPTH)-1:0] r_ptr;
// Status Flags
assign full = (count == DEPTH);
assign empty = (count == 0);
// Write Logic
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
w_ptr <= 0;
end else if (w_en && !full) begin
fifo_mem[w_ptr] <= w_data;
w_ptr <= w_ptr + 1'b1;
end
end
// Read Logic
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
r_ptr <= 0;
r_data <= 0;
end else if (r_en && !empty) begin
r_data <= fifo_mem[r_ptr];
r_ptr <= r_ptr + 1'b1;
end
end
// Occupancy Counter
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
count <= 0;
end else begin
case ({w_en && !full, r_en && !empty})
2'b10: count <= count + 1'b1;
2'b01: count <= count - 1'b1;
default: count <= count;
endcase
end
end
endmodule
Verification Testbench
This testbench simulates basic write and read cycles to verify the integrity of the FIFO logic.
`timescale 1ns/1ps
module tb_SyncFIFO();
parameter W = 8;
parameter D = 8;
reg clk;
reg rst_n;
reg w_en;
reg [W-1:0] w_data;
reg r_en;
wire [W-1:0] r_data;
wire full, empty;
wire [$clog2(D):0] count;
SynchronousFIFO #(.WIDTH(W), .DEPTH(D)) uut (
.clk(clk), .rst_n(rst_n),
.w_en(w_en), .w_data(w_data),
.r_en(r_en), .r_data(r_data),
.full(full), .empty(empty),
.count(count)
);
always #5 clk = ~clk;
initial begin
clk = 0; rst_n = 0; w_en = 0; r_en = 0; w_data = 0;
#20 rst_n = 1;
// Write until full
repeat (D) begin
@(posedge clk);
if (!full) begin
w_en = 1;
w_data = w_data + 1;
end
end
@(posedge clk) w_en = 0;
// Read until empty
repeat (D) begin
@(posedge clk);
if (!empty) r_en = 1;
end
@(posedge clk) r_en = 0;
#50 $finish;
end
endmodule