⚡ Digital Design Blog

Part 7 of 10

Clock Domain Crossing

Safely transfer data between different clock domains

By Praveen Kumar Vagala

1,127 views

The Problem

When data crosses from one clock domain to another, timing relationships are unknown.

Domain A (100 MHz) Domain B (133 MHz) ┌────────┐ ┌────────┐ │ FF_A │────── DATA ───────►│ FF_B │ └────┬───┘ └────┬───┘ │ │ CLK_A CLK_B Problem: CLK_A and CLK_B are asynchronous! Setup/hold times at FF_B may be violated.

Metastability

When setup/hold is violated, flip-flop output becomes unpredictable.

Normal: Metastable: ┌──── ────┐ │ │ ← Stuck in middle! ────┘ └──── Clean 0→1 May resolve to 0 or 1 transition after random delay
Metastability can cause:

Solution 1: Two-FF Synchronizer

For single-bit signals (control signals, flags).

CLK_A CLK_B │ │ ▼ ▼ ┌──────┐ ┌──────┐ ┌──────┐ │ FF_A │────── signal_a ──────►│ FF_1 │───►│ FF_2 │───► signal_b └──────┘ └──────┘ └──────┘ │ │ May be Very likely metastable stable
// Two-FF Synchronizer
module sync_2ff (
    input      clk_b,       // Destination clock
    input      rst_n,
    input      signal_a,    // From clock domain A
    output     signal_b     // Synchronized to clock domain B
);
    reg sync_ff1, sync_ff2;
    
    always @(posedge clk_b or negedge rst_n) begin
        if (!rst_n) begin
            sync_ff1 <= 1'b0;
            sync_ff2 <= 1'b0;
        end else begin
            sync_ff1 <= signal_a;   // May go metastable
            sync_ff2 <= sync_ff1;   // Very likely stable
        end
    end
    
    assign signal_b = sync_ff2;
endmodule
Use 3-FF synchronizers for high-speed or safety-critical designs!

Solution 2: Gray Code for Counters

For multi-bit values that change slowly (like FIFO pointers).

Binary counting: Gray code counting: 000 → 001 → 010 000 → 001 → 011 ↑ ↑ 2 bits change! 1 bit changes! Problem with binary: If 2 bits change and one synchronizes before the other, you get a wrong intermediate value! Gray code: Only 1 bit changes at a time - safe to synchronize!

Gray Code Conversion

// Binary to Gray
function [N-1:0] bin2gray;
    input [N-1:0] bin;
    bin2gray = bin ^ (bin >> 1);
endfunction

// Gray to Binary
function [N-1:0] gray2bin;
    input [N-1:0] gray;
    integer i;
    begin
        gray2bin[N-1] = gray[N-1];
        for (i = N-2; i >= 0; i = i-1)
            gray2bin[i] = gray2bin[i+1] ^ gray[i];
    end
endfunction

Async FIFO Pointer Crossing

// Write pointer synchronized to read domain
module ptr_sync (
    input            rd_clk, rst_n,
    input  [PTR_W:0] wr_ptr,      // Binary pointer
    output [PTR_W:0] wr_ptr_sync  // Synchronized (gray)
);
    wire [PTR_W:0] wr_ptr_gray;
    reg  [PTR_W:0] sync1, sync2;
    
    // Convert to Gray in write domain (not shown: use wr_clk)
    assign wr_ptr_gray = wr_ptr ^ (wr_ptr >> 1);
    
    // Synchronize Gray pointer to read domain
    always @(posedge rd_clk or negedge rst_n) begin
        if (!rst_n) begin
            sync1 <= 0;
            sync2 <= 0;
        end else begin
            sync1 <= wr_ptr_gray;
            sync2 <= sync1;
        end
    end
    
    assign wr_ptr_sync = sync2;
endmodule

Solution 3: Handshake Protocol

For multi-bit data transfers that happen occasionally.

Domain A Domain B 1. A puts data on bus, raises REQ ────── data ──────► ────── req ───────► 2. B synchronizes REQ, latches data, raises ACK ◄────── ack ────── 3. A sees ACK (synchronized), lowers REQ ────── req=0 ─────► 4. B sees REQ low, lowers ACK ◄───── ack=0 ────── 5. Transfer complete, ready for next
// Handshake - Sender side (Domain A)
module hs_sender (
    input            clk_a, rst_n,
    input            send,
    input  [7:0]     data_in,
    output reg       busy,
    output reg [7:0] data_out,
    output reg       req,
    input            ack_sync    // ACK synchronized to clk_a
);
    localparam IDLE = 2'd0;
    localparam WAIT_ACK = 2'd1;
    localparam WAIT_ACK_LOW = 2'd2;
    
    reg [1:0] state;
    
    always @(posedge clk_a or negedge rst_n) begin
        if (!rst_n) begin
            state <= IDLE;
            req <= 0;
            busy <= 0;
        end else begin
            case (state)
                IDLE: begin
                    if (send) begin
                        data_out <= data_in;
                        req <= 1;
                        busy <= 1;
                        state <= WAIT_ACK;
                    end
                end
                WAIT_ACK: begin
                    if (ack_sync) begin
                        req <= 0;
                        state <= WAIT_ACK_LOW;
                    end
                end
                WAIT_ACK_LOW: begin
                    if (!ack_sync) begin
                        busy <= 0;
                        state <= IDLE;
                    end
                end
            endcase
        end
    end
endmodule

Solution 4: Async FIFO

Best solution for streaming data between clock domains.

CLK_WR CLK_RD │ │ ▼ ▼ ┌───────┐ ┌─────────────┐ ┌───────┐ │ Write │────►│ Memory │─────►│ Read │ │ Logic │ │ (Dual-Port)│ │ Logic │ └───┬───┘ └─────────────┘ └───┬───┘ │ │ │ ┌─────────────┐ │ └────────►│ wr_ptr_gray │──►sync───►│ (empty calc) └─────────────┘ │ ┌─────────────┐ │ │◄─sync◄──│ rd_ptr_gray │◄──────────┘ │ └─────────────┘ (full calc)

Common CDC Mistakes

MistakeProblemSolution
No synchronizerMetastabilityUse 2-FF sync
Multi-bit sync with binaryWrong valuesUse Gray code
Combo logic between sync FFsDefeats purposeDirect FF-to-FF
Synchronizing same signal twiceInconsistencySync once, distribute
Fast pulse crossing slow domainMissed pulseUse handshake

CDC Verification

Tools like Synopsys SpyGlass or Cadence Conformal check for CDC issues:

Summary

Signal TypeSolution
Single-bit control2-FF synchronizer
Counter/pointerGray code + sync
Occasional multi-bitHandshake protocol
Streaming dataAsync FIFO