Exercise: Design an Asynchronous FIFO
An asynchronous FIFO is one of the most important CDC structures in FPGA design. This exercise focuses on the architecture and verification mindset. In production, prefer a proven vendor FIFO or a heavily reviewed reusable implementation.
Learning Objectives
You will:
- understand write and read clock domains;
- use pointers, gray code, full, and empty flags;
- verify FIFO ordering under different clock rates;
- identify common CDC failure symptoms.
Prerequisites
- Clock-domain crossing basics.
- Block RAM/FIFO concepts.
- Verilog testbenches.
- Ability to read timing and CDC warnings.
Task
Design a small asynchronous FIFO with:
wr_clk,wr_en,wr_data,full;rd_clk,rd_en,rd_data,empty;- depth of
16entries; - data width of
8bits.
Architecture
The actual RAM is shared. The control logic must respect which clock domain owns each pointer.
Buildable Simplified Skeleton
This skeleton shows structure, not a drop-in production FIFO.
module async_fifo #(
parameter DW = 8,
parameter AW = 4
) (
input wire wr_clk,
input wire wr_rst,
input wire wr_en,
input wire [DW-1:0] wr_data,
output wire full,
input wire rd_clk,
input wire rd_rst,
input wire rd_en,
output reg [DW-1:0] rd_data,
output wire empty
);
localparam DEPTH = (1 << AW);
reg [DW-1:0] mem [0:DEPTH-1];
reg [AW:0] wr_bin, wr_gray;
reg [AW:0] rd_bin, rd_gray;
reg [AW:0] rd_gray_w1, rd_gray_w2;
reg [AW:0] wr_gray_r1, wr_gray_r2;
function [AW:0] bin2gray(input [AW:0] b);
bin2gray = (b >> 1) ^ b;
endfunction
wire [AW:0] wr_bin_next = wr_bin + (wr_en && !full);
wire [AW:0] wr_gray_next = bin2gray(wr_bin_next);
wire [AW:0] rd_bin_next = rd_bin + (rd_en && !empty);
wire [AW:0] rd_gray_next = bin2gray(rd_bin_next);
assign empty = (rd_gray == wr_gray_r2);
assign full = (wr_gray_next == {~rd_gray_w2[AW:AW-1], rd_gray_w2[AW-2:0]});
always @(posedge wr_clk) begin
if (wr_rst) begin
wr_bin <= 0;
wr_gray <= 0;
end else begin
if (wr_en && !full)
mem[wr_bin[AW-1:0]] <= wr_data;
wr_bin <= wr_bin_next;
wr_gray <= wr_gray_next;
end
end
always @(posedge rd_clk) begin
if (rd_rst) begin
rd_bin <= 0;
rd_gray <= 0;
rd_data <= 0;
end else begin
if (rd_en && !empty)
rd_data <= mem[rd_bin[AW-1:0]];
rd_bin <= rd_bin_next;
rd_gray <= rd_gray_next;
end
end
always @(posedge wr_clk) begin
rd_gray_w1 <= rd_gray;
rd_gray_w2 <= rd_gray_w1;
end
always @(posedge rd_clk) begin
wr_gray_r1 <= wr_gray;
wr_gray_r2 <= wr_gray_r1;
end
endmodule
Expected Behavior
- Data read order must match write order.
emptymust be high after reset.- Writes must stop when
fullis high. - Reads must stop when
emptyis high. - The FIFO must tolerate unrelated write and read clocks.
Verification Steps
- Use different clock periods, for example
wr_clk = 10 ns,rd_clk = 14 ns. - Reset both domains.
- Write a known sequence such as
00, 01, 02, .... - Read whenever not empty.
- Compare every read byte with the expected sequence.
- Repeat with faster read than write and faster write than read.
- Add pauses to both sides.
Common Failure Symptoms
| Symptom | Likely cause |
|---|---|
| duplicated data | read pointer update bug |
| missing data | write pointer or full logic bug |
| random order | unsafe pointer crossing |
| false full/empty | gray-code comparison error |
| works only when clocks match | not actually asynchronous-safe |
Debugging Guidance
- Display binary and gray pointers.
- Check full/empty calculations in their own domains.
- Confirm only gray-coded pointers cross domains.
- Add assertions: never read when empty, never write when full.
- Test with clocks whose edges drift relative to each other.
Extension Challenge
Add almost_full and almost_empty flags. Then write a testbench that proves the flags assert at the intended occupancy thresholds.
Explained Solution
The FIFO separates data storage from clock-domain control. The write domain owns the write pointer; the read domain owns the read pointer. Pointers cross domains in gray code so only one bit changes per increment, reducing ambiguity during synchronization. Full and empty are calculated using synchronized opposite-domain pointers. The design trades flag latency for safe CDC behavior.
Summary
Asynchronous FIFOs are powerful because they turn unsafe clock crossings into a reviewed structure. They are also easy to get subtly wrong. Use proven implementations in real products, but understand the architecture well enough to review and verify them.
Next: Verification and Design Review.
Further Reading
- Clifford Cummings asynchronous FIFO papers
- Vendor FIFO IP documentation
- CDC verification guides