Verilog Code for Asynchronous FIFO: Syntax and Example
An
asynchronous FIFO in Verilog uses separate clocks for write and read sides, with dual-port memory and gray-coded pointers for safe crossing. The design includes write and read pointers, synchronization registers, and full/empty flags to manage data flow between clock domains.Syntax
An asynchronous FIFO module typically has separate clock inputs for writing and reading, data inputs and outputs, write and read enable signals, and status flags like full and empty. It uses dual-port RAM internally and gray-coded pointers to safely cross clock domains.
- clk_wr: Write clock domain
- clk_rd: Read clock domain
- rst: Reset signal
- wr_en: Write enable
- rd_en: Read enable
- data_in: Data input bus
- data_out: Data output bus
- full: FIFO full flag
- empty: FIFO empty flag
verilog
module async_fifo #(parameter DATA_WIDTH=8, parameter ADDR_WIDTH=4) ( input wire clk_wr, input wire clk_rd, input wire rst, input wire wr_en, input wire rd_en, input wire [DATA_WIDTH-1:0] data_in, output reg [DATA_WIDTH-1:0] data_out, output wire full, output wire empty ); // Internal signals and memory declaration here endmodule
Example
This example shows a complete asynchronous FIFO with 8-bit data width and 16-depth memory. It uses gray-coded pointers and synchronizers to safely transfer write and read pointers across clock domains. The FIFO sets full and empty flags accordingly.
verilog
module async_fifo #(parameter DATA_WIDTH=8, parameter ADDR_WIDTH=4) ( input wire clk_wr, input wire clk_rd, input wire rst, input wire wr_en, input wire rd_en, input wire [DATA_WIDTH-1:0] data_in, output reg [DATA_WIDTH-1:0] data_out, output wire full, output wire empty ); localparam DEPTH = 1 << ADDR_WIDTH; reg [DATA_WIDTH-1:0] mem [0:DEPTH-1]; // Write pointer and gray code reg [ADDR_WIDTH:0] wr_ptr_bin = 0; reg [ADDR_WIDTH:0] wr_ptr_gray = 0; // Read pointer and gray code reg [ADDR_WIDTH:0] rd_ptr_bin = 0; reg [ADDR_WIDTH:0] rd_ptr_gray = 0; // Synchronizers for pointers crossing clock domains reg [ADDR_WIDTH:0] wr_ptr_gray_sync1 = 0, wr_ptr_gray_sync2 = 0; reg [ADDR_WIDTH:0] rd_ptr_gray_sync1 = 0, rd_ptr_gray_sync2 = 0; // Convert gray to binary function function [ADDR_WIDTH:0] gray2bin; input [ADDR_WIDTH:0] gray; integer i; begin gray2bin[ADDR_WIDTH] = gray[ADDR_WIDTH]; for (i=ADDR_WIDTH-1; i>=0; i=i-1) begin gray2bin[i] = gray2bin[i+1] ^ gray[i]; end end endfunction // Write pointer logic always @(posedge clk_wr or posedge rst) begin if (rst) begin wr_ptr_bin <= 0; wr_ptr_gray <= 0; end else if (wr_en && !full) begin wr_ptr_bin <= wr_ptr_bin + 1; wr_ptr_gray <= (wr_ptr_bin + 1) ^ ((wr_ptr_bin + 1) >> 1); mem[wr_ptr_bin[ADDR_WIDTH-1:0]] <= data_in; end end // Read pointer logic always @(posedge clk_rd or posedge rst) begin if (rst) begin rd_ptr_bin <= 0; rd_ptr_gray <= 0; data_out <= 0; end else if (rd_en && !empty) begin data_out <= mem[rd_ptr_bin[ADDR_WIDTH-1:0]]; rd_ptr_bin <= rd_ptr_bin + 1; rd_ptr_gray <= (rd_ptr_bin + 1) ^ ((rd_ptr_bin + 1) >> 1); end end // Synchronize read pointer into write clock domain always @(posedge clk_wr or posedge rst) begin if (rst) begin rd_ptr_gray_sync1 <= 0; rd_ptr_gray_sync2 <= 0; end else begin rd_ptr_gray_sync1 <= rd_ptr_gray; rd_ptr_gray_sync2 <= rd_ptr_gray_sync1; end end // Synchronize write pointer into read clock domain always @(posedge clk_rd or posedge rst) begin if (rst) begin wr_ptr_gray_sync1 <= 0; wr_ptr_gray_sync2 <= 0; end else begin wr_ptr_gray_sync1 <= wr_ptr_gray; wr_ptr_gray_sync2 <= wr_ptr_gray_sync1; end end // Convert synchronized pointers to binary wire [ADDR_WIDTH:0] rd_ptr_bin_sync = gray2bin(rd_ptr_gray_sync2); wire [ADDR_WIDTH:0] wr_ptr_bin_sync = gray2bin(wr_ptr_gray_sync2); // Full when next write pointer equals read pointer synchronized to write clock domain assign full = ((wr_ptr_bin + 1) & ((1 << (ADDR_WIDTH+1)) - 1)) == rd_ptr_bin_sync; // Empty when read pointer equals write pointer synchronized to read clock domain assign empty = rd_ptr_bin == wr_ptr_bin_sync; endmodule
Common Pitfalls
Common mistakes when designing asynchronous FIFOs include:
- Not using gray code for pointers crossing clock domains, causing metastability and data corruption.
- Failing to synchronize pointers properly between write and read clock domains.
- Incorrect full and empty flag logic leading to data overwrite or underflow.
- Not handling reset properly in both clock domains.
Always use double flip-flop synchronizers and gray-coded pointers to avoid these issues.
verilog
/* Wrong: Using binary pointers directly across clock domains */ // This can cause metastability and incorrect full/empty flags /* Right: Use gray-coded pointers and synchronizers as shown in the example section */
Quick Reference
- Use separate clocks for write and read sides.
- Use gray code for pointers crossing clock domains.
- Synchronize pointers with double flip-flops.
- Check full and empty flags carefully to avoid data loss.
- Reset both clock domains properly.
Key Takeaways
Use gray-coded pointers to safely cross clock domains in asynchronous FIFOs.
Synchronize pointers with double flip-flops to prevent metastability.
Implement full and empty flags based on synchronized pointers to avoid data corruption.
Reset logic must be handled in both write and read clock domains.
Separate write and read clocks enable safe asynchronous data transfer.