Safely transfer data between different clock domains
When data crosses from one clock domain to another, timing relationships are unknown.
When setup/hold is violated, flip-flop output becomes unpredictable.
For single-bit signals (control signals, flags).
// 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
For multi-bit values that change slowly (like FIFO pointers).
// 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
// 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
For multi-bit data transfers that happen occasionally.
// 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
Best solution for streaming data between clock domains.
| Mistake | Problem | Solution |
|---|---|---|
| No synchronizer | Metastability | Use 2-FF sync |
| Multi-bit sync with binary | Wrong values | Use Gray code |
| Combo logic between sync FFs | Defeats purpose | Direct FF-to-FF |
| Synchronizing same signal twice | Inconsistency | Sync once, distribute |
| Fast pulse crossing slow domain | Missed pulse | Use handshake |
Tools like Synopsys SpyGlass or Cadence Conformal check for CDC issues:
| Signal Type | Solution |
|---|---|
| Single-bit control | 2-FF synchronizer |
| Counter/pointer | Gray code + sync |
| Occasional multi-bit | Handshake protocol |
| Streaming data | Async FIFO |