A synchronous FIFO (first in, first out) is a buffer that stores data words in the order they are written and returns them in the same order, with both the write side and the read side running on the same clock. In Verilog, it is built from a small memory array, a write pointer, a read pointer, and full and empty flags derived from the two pointers.
15 min read | Intermediate Level
What You'll Learn
- What a synchronous FIFO does, and how to choose its depth and width
- Write a parameterized FIFO with read and write pointers and status flags
- How full and empty are detected, and why the basic design holds one entry less than its depth
- Test the FIFO with independent writer and reader processes
What is a synchronous FIFO ?
A synchronous FIFO is a data buffer whose read and write operations both use the same clock. Data leaves the FIFO in exactly the order it arrived, so the reader sees the same sequence the writer produced. It is called synchronous because its pointers and flags update on that one clock, and all data transfers between the FIFO and the surrounding logic happen on clock edges.
A FIFO decouples a producer from a consumer. When the producer sometimes delivers data faster than the consumer can accept it, for example in bursts, the FIFO holds the extra words until the consumer catches up. Without it, data would be lost or the producer would have to stall on every cycle the consumer is busy.
What does depth and width indicate ?
Width is the number of bits in each entry. It equals the width of the data bus, so it sets how much data moves in one write or read.
Depth is the number of entries the FIFO can hold at once. It must be large enough to absorb the largest difference between the amount of data written and the amount read during a burst. If a burst of B words is written at rate W while the reader drains at a slower rate R, the reader removes B x (R / W) words during the burst, so the FIFO must hold the rest:
Depth >= B - B x (R / W) = B x (1 - R / W)
For example, a burst of 100 words written at 1 word per clock, read at 1 word every 2 clocks (R / W = 1/2), needs a depth of at least 100 x (1 - 1/2) = 50 entries. In practice, depth is rounded up to a power of two and a few entries of margin are added for the latency of the flags.
What are the main IO ports ?
- Data ports:
dincarries the word being written anddoutthe word being read. - Enables:
wr_enrequests a write andrd_enrequests a read on the next clock edge. - Status flags:
fullmeans no more data can be written until some is read.emptymeans there is no data to read.
Internally, a write pointer holds the address where the next word will be written, and a read pointer holds the address of the next word to be read. Both update on the clock.
How does it work ?
- Write: on a clock edge where
wr_enis 1 and the FIFO is not full,dinis stored at the write pointer and the write pointer advances by one. - Read: on a clock edge where
rd_enis 1 and the FIFO is not empty, the word at the read pointer is copied todoutand the read pointer advances by one.
Both pointers wrap from the last address back to 0, so the memory is used as a circular buffer. Reads and writes can happen on the same clock edge, and the number of stored words is the distance from the read pointer to the write pointer.
Synchronous FIFO Verilog Code
module sync_fifo #(parameter DEPTH=8, DWIDTH=16)
(
input rstn, // Active low reset
clk, // Clock
wr_en, // Write enable
rd_en, // Read enable
input [DWIDTH-1:0] din, // Data written into FIFO
output reg [DWIDTH-1:0] dout, // Data read from FIFO
output empty, // FIFO is empty when high
full // FIFO is full when high
);
reg [$clog2(DEPTH)-1:0] wptr; // Write address, wraps at DEPTH
reg [$clog2(DEPTH)-1:0] rptr; // Read address, wraps at DEPTH
reg [DWIDTH-1 : 0] fifo[0:DEPTH-1]; // Storage
// Write side: store din and advance the write pointer
always @ (posedge clk) begin
if (!rstn) begin
wptr <= 0;
end else begin
if (wr_en & !full) begin
fifo[wptr] <= din;
wptr <= wptr + 1;
end
end
end
// Print every change, for following the simulation
initial begin
$monitor("[%0t] [FIFO] wr_en=%0b din=0x%0h rd_en=%0b dout=0x%0h empty=%0b full=%0b",
$time, wr_en, din, rd_en, dout, empty, full);
end
// Read side: output the oldest word and advance the read pointer
always @ (posedge clk) begin
if (!rstn) begin
rptr <= 0;
end else begin
if (rd_en & !empty) begin
dout <= fifo[rptr];
rptr <= rptr + 1;
end
end
end
// Flags: full when one more write would make the pointers equal
assign full = (wptr + 1'b1) == rptr;
assign empty = wptr == rptr;
endmodule
The $monitor is placed in the design only to make this example's simulation easy to follow. In real projects, monitoring code belongs in the testbench.
Synthesis result: The array fifo is written in a clocked block and read into the register dout, which is the pattern synthesis tools map to block RAM or a memory macro. Around it, the tool builds two $clog2(DEPTH)-bit counters for the pointers, an incrementer and comparator for full, a comparator for empty, and the gating that blocks writes when full and reads when empty. See Verilog Single Port RAM for how memory arrays are inferred.
Quick Check
Q: The FIFO is empty, and wr_en and rd_en are both 1 for one clock edge. What happens?
Show Answer
The write happens and the read does not. At that edge empty is still 1, so the read side is blocked, while the write side stores din and advances wptr. After the edge, the FIFO holds one word and empty falls, so a read on the next edge succeeds.
Full and Empty Detection
Both flags compare the pointers. empty is simple: when the read pointer has caught up with the write pointer, nothing is left to read. Full is harder, because when the writer catches up with the reader after filling every entry, the pointers are also equal, and the FIFO could not tell full from empty.
The design above avoids that by declaring the FIFO full one entry early, when wptr + 1 equals rptr. The pointers then become equal only when the FIFO is empty. The cost is that one entry is never used: with DEPTH = 8, the FIFO holds at most 7 words.
The common alternative adds one extra bit to each pointer. The lower bits address the memory, and the extra bit toggles each time a pointer wraps around. When the lower bits are equal, the extra bits tell the two cases apart: equal extra bits mean empty, different extra bits mean the writer is exactly one lap ahead, which means full.
module sync_fifo_full #(parameter DEPTH = 8, DWIDTH = 16) (
input clk,
input rstn,
input wr_en,
input rd_en,
input [DWIDTH-1:0] din,
output reg [DWIDTH-1:0] dout,
output empty,
output full,
output [$clog2(DEPTH):0] count // Number of words stored, 0 to DEPTH
);
localparam AW = $clog2(DEPTH); // Address width; DEPTH must be a power of two
reg [DWIDTH-1:0] fifo [0:DEPTH-1];
reg [AW:0] wptr, rptr; // One extra bit to count laps
always @(posedge clk) begin
if (!rstn) wptr <= 0;
else if (wr_en & !full) begin
fifo[wptr[AW-1:0]] <= din; // Lower bits address the memory
wptr <= wptr + 1'b1;
end
end
always @(posedge clk) begin
if (!rstn) rptr <= 0;
else if (rd_en & !empty) begin
dout <= fifo[rptr[AW-1:0]];
rptr <= rptr + 1'b1;
end
end
assign empty = (wptr == rptr); // Same lap, same address
assign full = (wptr[AW] != rptr[AW]) &&
(wptr[AW-1:0] == rptr[AW-1:0]); // One lap ahead, same address
assign count = wptr - rptr; // Wraps correctly in AW+1 bits
endmodule
This version uses all DEPTH entries and provides a count output for free, since the difference between the two pointers is the number of stored words.
Where are Synchronous FIFOs Used?
A DMA engine that copies data from memory to a peripheral fetches data in bursts from the memory bus, while the peripheral consumes it at its own steady rate. A synchronous FIFO between them absorbs each burst, so the DMA engine can release the bus quickly and the peripheral is never left waiting for data between bursts.
Testbench
The testbench runs a writer and a reader as two independent initial blocks, so reads and writes overlap as they would in a real system. The writer drives random write enables and data for 20 iterations and waits whenever the FIFO is full. The reader drives random read enables and waits whenever the FIFO is empty. A stop flag set by the writer ends the reader's loop.
module tb;
reg clk;
reg [15:0] din;
wire [15:0] dout;
reg [15:0] rdata;
wire empty;
reg rd_en;
reg wr_en;
wire full;
reg rstn;
reg stop;
integer i;
sync_fifo u_sync_fifo ( .rstn(rstn),
.wr_en(wr_en),
.rd_en(rd_en),
.clk(clk),
.din(din),
.dout(dout),
.empty(empty),
.full(full)
);
always #10 clk = ~clk;
initial begin
clk <= 0;
rstn <= 0;
wr_en <= 0;
rd_en <= 0;
stop <= 0;
#50 rstn <= 1;
end
// Writer
initial begin
@(posedge clk);
for (i = 0; i < 20; i = i+1) begin
// Wait until there is space in fifo
while (full) begin
@(posedge clk);
$display("[%0t] FIFO is full, wait for reads to happen", $time);
end;
// Drive new values into FIFO
wr_en <= $random;
din <= $random;
$display("[%0t] clk i=%0d wr_en=%0d din=0x%0h ", $time, i, wr_en, din);
// Wait for next clock edge
@(posedge clk);
end
stop = 1;
end
// Reader
initial begin
@(posedge clk);
while (!stop) begin
// Wait until there is data in fifo
while (empty) begin
rd_en <= 0;
$display("[%0t] FIFO is empty, wait for writes to happen", $time);
@(posedge clk);
end;
// Sample new values from FIFO at random pace
rd_en <= $random;
@(posedge clk);
rdata <= dout;
$display("[%0t] clk rd_en=%0d rdata=0x%0h ", $time, rd_en, rdata);
end
#500 $finish;
end
endmodule
The $display calls run right after the non-blocking assignments in the same time step, before those assignments take effect, so each printed wr_en, din and rdata is the value from the previous clock. The [FIFO] lines from $monitor show the settled values.
Sample Buffers Between Converters and DSP
An analog-to-digital converter delivers samples at a fixed rate, while the signal processing block that consumes them may pause briefly, for example while it loads new filter coefficients. A FIFO between them stores the samples that arrive during the pause, so none are lost.
Simulation Results
The log shows the FIFO going empty and full depending on how fast the random writer and reader run relative to each other.

xcelium> run [0] [FIFO] wr_en=0 din=0xx rd_en=0 dout=0xx empty=x full=x [10] clk i=0 wr_en=0 din=0xx [10] [FIFO] wr_en=0 din=0x5e81 rd_en=1 dout=0xx empty=1 full=0 [30] clk i=1 wr_en=0 din=0x5e81 [30] clk rd_en=1 rdata=0xx [30] FIFO is empty, wait for writes to happen [30] [FIFO] wr_en=1 din=0x7b0d rd_en=0 dout=0xx empty=1 full=0 [50] clk i=2 wr_en=1 din=0x7b0d [50] FIFO is empty, wait for writes to happen [50] [FIFO] wr_en=1 din=0x8465 rd_en=0 dout=0xx empty=0 full=0 [70] clk i=3 wr_en=1 din=0x8465 [70] [FIFO] wr_en=0 din=0xe301 rd_en=1 dout=0xx empty=0 full=0 [90] clk i=4 wr_en=0 din=0xe301 [90] clk rd_en=1 rdata=0xx [90] [FIFO] wr_en=0 din=0xcd3d rd_en=1 dout=0x7b0d empty=0 full=0 [110] clk i=5 wr_en=0 din=0xcd3d [110] clk rd_en=1 rdata=0xx [110] [FIFO] wr_en=0 din=0xe9f9 rd_en=0 dout=0x8465 empty=1 full=0 [130] clk i=6 wr_en=0 din=0xe9f9 [130] clk rd_en=0 rdata=0x7b0d [130] FIFO is empty, wait for writes to happen [130] [FIFO] wr_en=1 din=0xd2aa rd_en=0 dout=0x8465 empty=1 full=0 [150] clk i=7 wr_en=1 din=0xd2aa [150] FIFO is empty, wait for writes to happen [150] [FIFO] wr_en=1 din=0x7277 rd_en=0 dout=0x8465 empty=0 full=0 [170] clk i=8 wr_en=1 din=0x7277 [170] [FIFO] wr_en=0 din=0xdb8f rd_en=0 dout=0x8465 empty=0 full=0 [190] clk i=9 wr_en=0 din=0xdb8f [190] clk rd_en=0 rdata=0x8465 [190] [FIFO] wr_en=0 din=0x7ae8 rd_en=1 dout=0x8465 empty=0 full=0 [210] clk i=10 wr_en=0 din=0x7ae8 [210] clk rd_en=1 rdata=0x8465 [210] [FIFO] wr_en=0 din=0x28bd rd_en=1 dout=0xd2aa empty=0 full=0 [230] clk i=11 wr_en=0 din=0x28bd [230] clk rd_en=1 rdata=0x8465 [230] [FIFO] wr_en=1 din=0x6263 rd_en=0 dout=0x7277 empty=1 full=0 [250] clk i=12 wr_en=1 din=0x6263 [250] clk rd_en=0 rdata=0xd2aa [250] FIFO is empty, wait for writes to happen [250] [FIFO] wr_en=0 din=0x2120 rd_en=0 dout=0x7277 empty=0 full=0 [270] clk i=13 wr_en=0 din=0x2120 [270] [FIFO] wr_en=0 din=0xcc9d rd_en=0 dout=0x7277 empty=0 full=0 [290] clk i=14 wr_en=0 din=0xcc9d [290] clk rd_en=0 rdata=0x7277 [290] [FIFO] wr_en=1 din=0x380d rd_en=1 dout=0x7277 empty=0 full=0 [310] clk i=15 wr_en=1 din=0x380d [310] clk rd_en=1 rdata=0x7277 [310] [FIFO] wr_en=1 din=0x2ad5 rd_en=0 dout=0x6263 empty=0 full=0 [330] clk i=16 wr_en=1 din=0x2ad5 [330] clk rd_en=0 rdata=0x7277 [330] [FIFO] wr_en=0 din=0xe91d rd_en=1 dout=0x6263 empty=0 full=0 [350] clk i=17 wr_en=0 din=0xe91d [350] clk rd_en=1 rdata=0x6263 [350] [FIFO] wr_en=1 din=0x650a rd_en=0 dout=0x380d empty=0 full=0 [370] clk i=18 wr_en=1 din=0x650a [370] clk rd_en=0 rdata=0x6263 [370] [FIFO] wr_en=0 din=0xbdf2 rd_en=0 dout=0x380d empty=0 full=0 [390] clk i=19 wr_en=0 din=0xbdf2 [390] clk rd_en=0 rdata=0x380d [390] [FIFO] wr_en=1 din=0x34d8 rd_en=0 dout=0x380d empty=0 full=0 [410] clk i=20 wr_en=1 din=0x34d8 [410] clk rd_en=0 rdata=0x380d [410] [FIFO] wr_en=1 din=0xdeb rd_en=0 dout=0x380d empty=0 full=0 [430] clk rd_en=0 rdata=0x380d [490] [FIFO] wr_en=1 din=0xdeb rd_en=0 dout=0x380d empty=0 full=1 Simulation complete via $finish(1) at time 930 NS + 0
Points to look for in the log:
- Reading an empty FIFO: at 30 ns and 130 ns the reader finds
empty=1, dropsrd_enand waits for the writer. - Read latency: the word
0x7b0d, written whilewr_enwas 1 around 50 ns, appears ondoutat 90 ns, after it has been written and then read out on a later edge. - Filling up: after the writer's loop ends,
wr_enis left at 1, so the FIFO keeps accepting the lastdinon every clock while the reader has stopped.fullrises at 490 ns, and from then on further writes are blocked.
Quick Check
Q: With DEPTH = 8, how many words can sync_fifo hold, and how many can sync_fifo_full hold?
Show Answer
sync_fifo holds 7, because it declares full when wptr + 1 == rptr, one entry early. sync_fifo_full holds all 8, because the extra pointer bit distinguishes a full FIFO from an empty one when the address bits are equal.
Common Beginner Mistakes
Mistake #1: Writing without checking full
Wrong: A producer that asserts wr_en on every valid word, regardless of full
Right: Only write when full is 0, and stall the producer otherwise
Why? The FIFO protects its contents by ignoring writes when full, so a word written while full is high is silently dropped. The writer must use full as backpressure, as the testbench does with its while (full) loop. The same applies to reading with empty.
Mistake #2: Expecting DEPTH entries from the simple design
Wrong: Sizing a buffer for 8 words and using sync_fifo #(.DEPTH(8))
Right: Use the extra-bit pointer version, or set DEPTH one larger than needed
Why? The wptr + 1 == rptr full condition leaves one entry unused. A system that relies on 8 entries of buffering stalls one word earlier than planned, which can cause throughput loss or, if the producer cannot stall, data loss.
Mistake #3: Using a DEPTH that is not a power of two
Wrong: sync_fifo #(.DEPTH(6))
Right: Use a power of two, or add explicit wrap logic: wptr <= (wptr == DEPTH-1) ? 0 : wptr + 1;
Why? $clog2(6) is 3, so the pointers count 0 to 7 before wrapping. Addresses 6 and 7 do not exist in fifo[0:5]: writes there are lost and reads return x. The flags are also wrong, because they assume the pointers wrap at DEPTH.
Mistake #4: Using dout in the same cycle as rd_en
Wrong: Treating dout as valid on the same clock edge where rd_en is asserted
Right: Use dout after the clock edge that performed the read, for example by registering a valid flag along with it
Why? dout is a register loaded on the edge where rd_en and !empty are true. Logic that samples dout on that same edge gets the previous word, so every word is shifted by one, which the testbench's rdata lagging behind dout in the log illustrates.
Practice Exercise
Challenge: Programmable Almost-Full Flag
Add an output almost_full to sync_fifo_full, with a parameter AF_LEVEL. almost_full should be 1 whenever the FIFO holds AF_LEVEL or more words. Producers with a few cycles of reaction time use such a flag to stop before the FIFO is completely full.
Hint
sync_fifo_full already computes count. The new flag is a single comparison on it.
Solution
// Add to the parameter list:
// parameter AF_LEVEL = DEPTH - 2
// Add to the port list:
// output almost_full
// High when AF_LEVEL or more words are stored
assign almost_full = (count >= AF_LEVEL);
With DEPTH = 8 and AF_LEVEL = 6, almost_full rises when the sixth word is written, leaving two free entries. A producer that needs up to two cycles to react to the flag can therefore stop writing without ever overflowing. An almost_empty flag for the reader works the same way, with count <= AE_LEVEL.
Summary
In this tutorial, you learned:
- A synchronous FIFO buffers data in order between a producer and a consumer on the same clock
- Width is the bits per entry; depth must cover the difference between writes and reads during the longest burst
- The FIFO is a circular buffer addressed by a write pointer and a read pointer
- Empty is detected when the pointers are equal; full needs either one unused entry or an extra pointer bit
- The memory array maps to block RAM when it is written and read in clocked blocks
- Writers must respect
fulland readers must respectempty - DEPTH should be a power of two for pointer wrap-around to work without extra logic