Part of Curriculum: Memory and FIFO Design (Module 10: Complete Dual-Clock Asynchronous FIFO)
View Full Roadmap →
Level 7 Expert ★ Advanced #55 50 pts ✓ Solved

Dual-Clock Asynchronous FIFO with Gray Pointer CDC Synchronization

Advanced RTL & Protocol Design · advanced-rtl, cdc-and-rdc, systemverilog, asic, fpga

Design a dual-clock asynchronous FIFO with Gray-coded pointers, 2-flop CDC synchronizers, and safe full/empty flag generation.

Write a synthesizable Verilog module named top_module matching the ports below exactly.
📐 Circuit Schematic Diagram
wr_enrd_enfullempty>clkrst_nDUAL-CLOCK ASYNCHRONDIGITAL HARDWARE BLOCK
⚡ Timing & Synthesizability Constraints

Timing Contract: Completely independent wr_clk and rd_clk. Note: RTL simulation checks functional equivalence; physical CDC safety requires ASYNC_REG synthesis attributes and set_max_delay timing constraints.

RTL Constraints: No multi-bit CDC crossing in binary. Use Gray code. Synthesizable Verilog-2001.

Objective: Implement a production-grade dual-clock FIFO (wr_clk and rd_clk). Pointers are maintained in binary for memory indexing and converted to Gray code for domain crossing. Synchronize Gray pointers through 2-flop synchronizers. Detect full in the write domain and empty in the read domain safely.

Prerequisites: Asynchronous FIFO design principles (Cummings paper), Gray coding, 2-flop synchronizer, MTBF considerations.
PortDirWidthDescription
wr_clk, wr_rst_ninput1Write domain clock and reset
wr_en, wr_data[7:0]input1, 8Write domain push handshake
rd_clk, rd_rst_ninput1Read domain clock and reset
rd_en, rd_data[7:0]in/out1, 8Read domain pop handshake
fulloutput1Full flag synchronous to wr_clk
emptyoutput1Empty flag synchronous to rd_clk
Expected Waveform (Golden Timing Contract)
💡 Need Guidance? Progressive Hints (3 Levels)
Hint 1: Conceptual Direction & Hardware Primitives
Gray code conversion is: gray = bin ^ (bin >> 1).
Hint 2: Implementation Architecture & Approach
To detect full, the write pointer has wrapped around while the read pointer has not. In Gray code, full condition occurs when the two most significant bits are inverted and all lower bits match.
Hint 3: Edge Cases, Timing & Verilog Syntax
Empty detection occurs in the read domain when rd_gray matches the synchronized wr_gray_s2 exactly.
🔒 Reveal Verified Reference RTL Solution (Click to expand)
⚠️ Self-Discovery Warning: Try solving and simulating first! Viewing the verified reference implementation will bypass the real debugging experience.
Verified Synthesizable RTL
module top_module #(
  parameter DATA_WIDTH = 8,
  parameter ADDR_WIDTH = 3 // Depth = 8
)(
  input  wire                  wr_clk,
  input  wire                  wr_rst_n,
  input  wire                  wr_en,
  input  wire [DATA_WIDTH-1:0] wr_data,
  input  wire                  rd_clk,
  input  wire                  rd_rst_n,
  input  wire                  rd_en,
  output wire [DATA_WIDTH-1:0] rd_data,
  output wire                  full,
  output wire                  empty
);

  localparam DEPTH = 1 << ADDR_WIDTH;

  reg [DATA_WIDTH-1:0] mem [0:DEPTH-1];

  // Binary and Gray pointers (ADDR_WIDTH+1 bits)
  reg [ADDR_WIDTH:0] wr_bin, wr_gray;
  reg [ADDR_WIDTH:0] rd_bin, rd_gray;

  // 2-Flop CDC synchronizers
  reg [ADDR_WIDTH:0] wr_gray_s1, wr_gray_s2; // into rd_clk domain
  reg [ADDR_WIDTH:0] rd_gray_s1, rd_gray_s2; // into wr_clk domain

  // Write operation
  wire write_valid = wr_en && !full;
  always @(posedge wr_clk) begin
    if (write_valid) begin
      mem[wr_bin[ADDR_WIDTH-1:0]] <= wr_data;
    end
  end

  always @(posedge wr_clk or negedge wr_rst_n) begin
    if (!wr_rst_n) begin
      wr_bin  <= 0;
      wr_gray <= 0;
    end else if (write_valid) begin
      wr_bin  <= wr_bin + 1'b1;
      wr_gray <= (wr_bin + 1'b1) ^ ((wr_bin + 1'b1) >> 1);
    end
  end

  // Synchronize rd_gray into wr_clk domain
  always @(posedge wr_clk or negedge wr_rst_n) begin
    if (!wr_rst_n) begin
      rd_gray_s1 <= 0;
      rd_gray_s2 <= 0;
    end else begin
      rd_gray_s1 <= rd_gray;
      rd_gray_s2 <= rd_gray_s1;
    end
  end

  // Full detection in wr_clk domain: MSB and 2nd MSB inverted, remaining bits equal
  assign full = (wr_gray == {~rd_gray_s2[ADDR_WIDTH:ADDR_WIDTH-1], rd_gray_s2[ADDR_WIDTH-2:0]});

  // Read operation
  wire read_valid = rd_en && !empty;
  assign rd_data = mem[rd_bin[ADDR_WIDTH-1:0]];

  always @(posedge rd_clk or negedge rd_rst_n) begin
    if (!rd_rst_n) begin
      rd_bin  <= 0;
      rd_gray <= 0;
    end else if (read_valid) begin
      rd_bin  <= rd_bin + 1'b1;
      rd_gray <= (rd_bin + 1'b1) ^ ((rd_bin + 1'b1) >> 1);
    end
  end

  // Synchronize wr_gray into rd_clk domain
  always @(posedge rd_clk or negedge rd_rst_n) begin
    if (!rd_rst_n) begin
      wr_gray_s1 <= 0;
      wr_gray_s2 <= 0;
    end else begin
      wr_gray_s1 <= wr_gray;
      wr_gray_s2 <= wr_gray_s1;
    end
  end

  // Empty detection in rd_clk domain
  assign empty = (rd_gray == wr_gray_s2);

endmodule
Verilog-2001 / SV Saved
Font: |
// Click Run Tests (or press Ctrl+Enter) to compile your RTL and simulate against hidden testbenches.
Simulation Timing Diagram (VCD Waveform)
100%
No simulation waveform yet. Run tests to simulate and generate VCD traces.
No compilation run yet. Syntax warnings and elaboration messages will appear here.
No simulation output yet. Raw stdout from vvp will appear here.

RTL Problems / Level 7 · Advanced RTL & Protocol Design

What You Will Learn

By solving Dual-Clock Asynchronous FIFO with Gray Pointer CDC Synchronization, you will acquire core industry skills:

  • Synthesizable hardware modeling in standard Verilog-2001 / SystemVerilog.
  • Correct handling of interface port contracts, edge-triggered clocks, and active-low/high resets.
  • Simulation waveform interpretation and debugging using timing diagrams (VCD traces).
  • Architectural avoidance of timing violations, glitching hazards, and synthesis warnings.

Problem Statement & Requirements

Design a dual-clock asynchronous FIFO with Gray-coded pointers, 2-flop CDC synchronizers, and safe full/empty flag generation.

Hardware design teams at leading semiconductor firms evaluate these exact port contracts, race-condition assertions, and corner cases during technical design reviews and ASIC engineer interviews.

Objective: Implement a production-grade dual-clock FIFO (wr_clk and rd_clk). Pointers are maintained in binary for memory indexing and converted to Gray code for domain crossing. Synchronize Gray pointers through 2-flop synchronizers. Detect full in the write domain and empty in the read domain safely.

Prerequisites: Asynchronous FIFO design principles (Cummings paper), Gray coding, 2-flop synchronizer, MTBF considerations.

Port List & Interface Timing

The hidden testbench instantiates top_module. Module port names and bit-widths must match the interface contract exactly:

PortDirWidthDescription
wr_clk, wr_rst_ninput1Write domain clock and reset
wr_en, wr_data[7:0]input1, 8Write domain push handshake
rd_clk, rd_rst_ninput1Read domain clock and reset
rd_en, rd_data[7:0]in/out1, 8Read domain pop handshake
fulloutput1Full flag synchronous to wr_clk
emptyoutput1Empty flag synchronous to rd_clk

Timing Constraints & Synthesizable RTL Rules

⚡ Synthesizable Implementation Guidelines

Timing Contract: Completely independent wr_clk and rd_clk. Note: RTL simulation checks functional equivalence; physical CDC safety requires ASYNC_REG synthesis attributes and set_max_delay timing constraints.

Synthesis Constraints: No multi-bit CDC crossing in binary. Use Gray code. Synthesizable Verilog-2001.

Architectural Deep Dive & Implementation Walkthrough

Classic Dual-Clock FIFO Architecture

Asynchronous FIFOs are ubiquitous in SoCs whenever data passes between independent oscillator domains (e.g. PCIe PHY, DDR controller, CPU core).

Why Gray Code is Mandatory

If binary pointers were synchronized across clock domains, a multi-bit change (e.g. 3'b011 -> 3'b100 where all 3 bits change simultaneously) would suffer from physical routing delay skew. The receiving synchronizer could sample intermediate corrupt codes (e.g. 3'b000 or 3'b111), causing false full or empty flags.

Gray code guarantees that exactly ONE bit changes between any two consecutive values ($000 o 001 o 011 o 010$). Even if the synchronizer samples during a metastable transition, it can only ever resolve to either the old value or the new value, never an erroneous random code.

Synthesis Note: Real silicon implementations require the (* ASYNC_REG = "TRUE" *) attribute on synchronizer registers and a set_max_delay -datapath_only constraint in the SDC timing script.

Common RTL Designer Pitfalls

⚠️ Common Mistakes to Avoid

Checking full in the read clock domain or empty in the write clock domain. Full must be checked in wr_clk, empty in rd_clk.

Silicon & Hardware Engineering Notes

🛠️ ASIC vs FPGA Hardware Considerations

Async FIFO depth is restricted to powers of 2 ($2^N$) so that the Gray code sequence wraps around with a single-bit transition.

Starter RTL Code

module top_module #(
  parameter DATA_WIDTH = 8,
  parameter ADDR_WIDTH = 3 // Depth = 8
)(
  input  wire                  wr_clk,
  input  wire                  wr_rst_n,
  input  wire                  wr_en,
  input  wire [DATA_WIDTH-1:0] wr_data,
  input  wire                  rd_clk,
  input  wire                  rd_rst_n,
  input  wire                  rd_en,
  output wire [DATA_WIDTH-1:0] rd_data,
  output wire                  full,
  output wire                  empty
);

  // Implement Dual-Clock Async FIFO:
  // 1. Dual-port RAM addressed by binary wr_ptr[2:0] and rd_ptr[2:0].

Verified Reference Solution

🔒 Reveal Verified Reference RTL Solution
module top_module #(
  parameter DATA_WIDTH = 8,
  parameter ADDR_WIDTH = 3 // Depth = 8
)(
  input  wire                  wr_clk,
  input  wire                  wr_rst_n,
  input  wire                  wr_en,
  input  wire [DATA_WIDTH-1:0] wr_data,
  input  wire                  rd_clk,
  input  wire                  rd_rst_n,
  input  wire                  rd_en,
  output wire [DATA_WIDTH-1:0] rd_data,
  output wire                  full,
  output wire                  empty
);

  localparam DEPTH = 1 << ADDR_WIDTH;

  reg [DATA_WIDTH-1:0] mem [0:DEPTH-1];

  // Binary and Gray pointers (ADDR_WIDTH+1 bits)
  reg [ADDR_WIDTH:0] wr_bin, wr_gray;
  reg [ADDR_WIDTH:0] rd_bin, rd_gray;

  // 2-Flop CDC synchronizers
  reg [ADDR_WIDTH:0] wr_gray_s1, wr_gray_s2; // into rd_clk domain
  reg [ADDR_WIDTH:0] rd_gray_s1, rd_gray_s2; // into wr_clk domain

  // Write operation
  wire write_valid = wr_en && !full;
  always @(posedge wr_clk) begin
    if (write_valid) begin
      mem[wr_bin[ADDR_WIDTH-1:0]] <= wr_data;
    end
  end

  always @(posedge wr_clk or negedge wr_rst_n) begin
    if (!wr_rst_n) begin
      wr_bin  <= 0;
      wr_gray <= 0;
    end else if (write_valid) begin
      wr_bin  <= wr_bin + 1'b1;
      wr_gray <= (wr_bin + 1'b1) ^ ((wr_bin + 1'b1) >> 1);
    end
  end

  // Synchronize rd_gray into wr_clk domain
  always @(posedge wr_clk or negedge wr_rst_n) begin
    if (!wr_rst_n) begin
      rd_gray_s1 <= 0;
      rd_gray_s2 <= 0;
    end else begin
      rd_gray_s1 <= rd_gray;
      rd_gray_s2 <= rd_gray_s1;
    end
  end

  // Full detection in wr_clk domain: MSB and 2nd MSB inverted, remaining bits equal
  assign full = (wr_gray == {~rd_gray_s2[ADDR_WIDTH:ADDR_WIDTH-1], rd_gray_s2[ADDR_WIDTH-2:0]});

  // Read operation
  wire read_valid = rd_en && !empty;
  assign rd_data = mem[rd_bin[ADDR_WIDTH-1:0]];

  always @(posedge rd_clk or negedge rd_rst_n) begin
    if (!rd_rst_n) begin
      rd_bin  <= 0;
      rd_gray <= 0;
    end else if (read_valid) begin
      rd_bin  <= rd_bin + 1'b1;
      rd_gray <= (rd_bin + 1'b1) ^ ((rd_bin + 1'b1) >> 1);
    end
  end

  // Synchronize wr_gray into rd_clk domain
  always @(posedge rd_clk or negedge rd_rst_n) begin
    if (!rd_rst_n) begin
      wr_gray_s1 <= 0;
      wr_gray_s2 <= 0;
    end else begin
      wr_gray_s1 <= wr_gray;
      wr_gray_s2 <= wr_gray_s1;
    end
  end

  // Empty detection in rd_clk domain
  assign empty = (rd_gray == wr_gray_s2);

endmodule

Related RTL Problems in Level 7

  • Async-Assert / Sync-Deassert Reset — Assert reset the instant it goes active, but only release it once it has been clean for 2 clock edges — the standard reset architecture for any real design.
  • 3-Flop CDC Synchronizer — A deeper 3-stage synchronizer for signals that need extra MTBF margin beyond the standard 2-flop chain.
  • Synchronized Rising-Edge Detector — Safely synchronize an asynchronous level signal, then produce a clean single-cycle pulse the moment it rises in the local clock domain.

Frequently Asked Questions

What does Dual-Clock Asynchronous FIFO with Gray Pointer CDC Synchronization require?

Design a dual-clock asynchronous FIFO with Gray-coded pointers, 2-flop CDC synchronizers, and safe full/empty flag generation. Implement it as Verilog module top_module matching the listed ports.

Is this design combinational or sequential?

Tags: advanced-rtl, cdc-and-rdc, systemverilog, asic, fpga. If a clock signal is present in the port table, implement it as sequential logic; otherwise, use continuous assignments or combinational blocks.

How is my solution graded?

A hidden SystemVerilog testbench simulates your module in the cloud judge. You pass when all directed corner cases match the golden model and the VCD timing waveform matches the contract.

How does this challenge help with semiconductor interviews?

Technical interviewers at NVIDIA, Qualcomm, Apple, and Intel routinely ask candidates to code, debug, and explain the timing waveforms of Dual-Clock Asynchronous FIFO with Gray Pointer CDC Synchronization to assess real-world synthesizability and race-condition awareness.