# Async FIFO (CDC)

## What You Will Learn

When writer and reader use different clocks, pointer comparison must cross safely. `async_fifo_cdc` uses gray-coded pointers and two-flop synchronizers—watch `wr_clk` and `rd_clk` diverge in the waveform viewer.

## Learning Objectives

- Contrast dual-clock FIFO with the synchronous `sync_fifo` example.
- Understand why binary pointers are synchronized as gray codes.
- Locate 2FF synchronizer chains on each clock domain.
- Read `full` on write side and `empty` on read side definitions.

## Theory

Binary counters can change multiple bits per increment—metastability could corrupt a pointer snapshot. Gray encoding changes one bit per step, so a synchronized value is either the old or new pointer. Functions `bin2gray` and `gray2bin` convert for comparisons.

Write logic runs on `wr_clk`, read on `rd_clk`. Each side synchronizes the other's gray pointer through two registers before occupancy decisions.

## When to use this pattern

Any time data crosses clock domains—PCIe to core, ADC clock to system bus, processor to pixel clock—async FIFOs buffer safely.

## Files

| File | Role |
|------|------|
| `tb/tb_cdc.v` | Testbench — **`tb_cdc`** |
| `rtl/async_fifo_cdc.v` | RTL / DUT — **`async_fifo_cdc`** |

## Practice Architecture

```mermaid
flowchart TB
  tb[tb_cdc] -->|wr_clk wr_en din| dut[async_fifo_cdc]
  tb -->|rd_clk rd_en| dut
  dut -->|dout full empty| tb
```

## What to explore

- Overlay `wr_clk` and `rd_clk` at different frequencies in waves.
- Trace `wr_gray` crossing into `wr_gray_rdclk_ff2`.
- Fill then drain the FIFO and watch `full`/`empty` timing.
- Explain why `empty` compares `rd_gray` to synchronized write pointer.
- Compare flop count vs `sync_fifo`.

## Summary

CDC FIFOs add gray pointers and synchronizers to the FIFO story. This minimal implementation is the conceptual backbone of vendor FIFO generators.

# Appendix (Code Review)

This section is an optional deep dive into the example source files. Line numbers refer to the copies in your workspace under `rtl/` and `tb/`.

## rtl/async_fifo_cdc.v

**Purpose:** Dual-clock gray-pointer FIFO (`async_fifo_cdc`).

Lines 8–81 — Module `async_fifo_cdc`

```verilog
`default_nettype none
// Tiny async FIFO (DEPTH=4) with 2FF synchronizers — watch wr_clk vs rd_clk in waves.
module async_fifo_cdc #(
  parameter W = 8,
  parameter DEPTH = 4
) (
  input wire             wr_clk,
  input wire             rd_clk,
  input wire             rst_n,
  input wire             wr_en,
  input wire [W-1:0]     din,
  input wire             rd_en,
  output reg [W-1:0]     dout,
  output wire full,
  output wire empty
);
  localparam integer AW = $clog2(DEPTH);

  function [AW:0] bin2gray(input [AW:0] b);
    bin2gray = (b >> 1) ^ b;
  endfunction
  function [AW:0] gray2bin(input [AW:0] g);
    integer i;
    begin
      gray2bin[AW] = g[AW];
      for (i = AW - 1; i >= 0; i = i - 1)
        gray2bin[i] = gray2bin[i+1] ^ g[i];
    end
  endfunction

  reg [W-1:0] mem [0:DEPTH-1];
  reg [AW:0] wr_bin, rd_bin;
  wire [AW:0] wr_gray, rd_gray;
  reg [AW:0] wr_gray_rdclk_ff1, wr_gray_rdclk_ff2;
  reg [AW:0] rd_gray_wrclk_ff1, rd_gray_wrclk_ff2;
  wire [AW:0] wr_bin_rd, rd_bin_wr;

  assign wr_gray = bin2gray(wr_bin);
  assign rd_gray = bin2gray(rd_bin);

  always @(posedge wr_clk or negedge rst_n) begin
    if (!rst_n) begin
      wr_bin <= 0;
      rd_gray_wrclk_ff1 <= 0;
      rd_gray_wrclk_ff2 <= 0;
    end else begin
      rd_gray_wrclk_ff1 <= rd_gray;
      rd_gray_wrclk_ff2 <= rd_gray_wrclk_ff1;
      if (wr_en && !full) begin
        mem[wr_bin[AW-1:0]] <= din;
        wr_bin <= wr_bin + 1'b1;
      end
    end
  end

  always @(posedge rd_clk or negedge rst_n) begin
    if (!rst_n) begin
      rd_bin <= 0;
      dout <= 0;
      wr_gray_rdclk_ff1 <= 0;
      wr_gray_rdclk_ff2 <= 0;
    end else begin
      wr_gray_rdclk_ff1 <= wr_gray;
      wr_gray_rdclk_ff2 <= wr_gray_rdclk_ff1;
      if (rd_en && !empty) begin
        dout <= mem[rd_bin[AW-1:0]];
        rd_bin <= rd_bin + 1'b1;
      end
    end
  end

  assign wr_bin_rd = gray2bin(wr_gray_rdclk_ff2);
  assign rd_bin_wr = gray2bin(rd_gray_wrclk_ff2);
  assign full  = (bin2gray(wr_bin + 1'b1) == rd_gray_wrclk_ff2);
  assign empty = (rd_gray == wr_gray_rdclk_ff2);
endmodule
```

**Code review:** Clocks: `wr_clk` write side; `rd_clk` read side; shared `rst_n`. Pointers: `wr_bin`, `rd_bin` with gray converts for cross-domain compare. Sync: `wr_gray_rdclk_ff1/ff2` and `rd_gray_wrclk_ff1/ff2`—classic 2FF. full: `bin2gray(wr_bin+1)` vs synced read gray on write clock. empty: `rd_gray ==` synced write gray on read clock. Read data: Registered `dout` on read clock.

## tb/tb_cdc.v

**Purpose:** Testbench `tb_cdc` — elaborates `DUT` and captures waves under `sim/`.

Lines 7–47 — Module `tb_cdc`

```verilog
`timescale 1ns/1ps
module tb_cdc;
  reg wr_clk, rd_clk, rst_n, wr_en, rd_en, full, empty;
  reg [7:0] din, dout;

  async_fifo_cdc #(.W(8), .DEPTH(4)) u_fifo (
    .wr_clk(wr_clk),
    .rd_clk(rd_clk),
    .rst_n(rst_n),
    .wr_en(wr_en),
    .din(din),
    .rd_en(rd_en),
    .dout(dout),
    .full(full),
    .empty(empty)
  );

  initial wr_clk = 0;
  always #4 wr_clk = ~wr_clk;  // ~125 MHz-ish period 8ns
  initial rd_clk = 0;
  always #7 rd_clk = ~rd_clk;  // unrelated period 14ns

  initial begin
    $dumpfile("sim/dump.vcd");
    $dumpvars(0, tb_cdc);
    rst_n = 0; wr_en = 0; rd_en = 0; din = 8'h00;
    #40 rst_n = 1;
    // write a few on wr_clk
    repeat (3) begin
      @(posedge wr_clk);
      wr_en = 1; din = din + 8'h11;
    end
    @(posedge wr_clk); wr_en = 0;
    // read a few on rd_clk
    repeat (3) begin
      @(posedge rd_clk);
      rd_en = 1;
    end
    @(posedge rd_clk); rd_en = 0;
    #200 $finish;
  end
endmodule
```

**Code review:** `tb_cdc` instantiates `DUT`. Look for `$dumpfile` / `$dumpvars` in an `initial` block and any loops or `$display` checks that report PASS/FAIL.

---

© 2026 RtlStudio. Licensed under [CC BY-NC-ND 4.0](https://creativecommons.org/licenses/by-nc-nd/4.0/). Commercial use and modifications by third parties are not permitted.

© 2026 RtlStudio. All rights reserved. RTL Studio is proprietary software.