1. The Problem: Why Binary Fails Across Clock Domains
From Day 2, we learned the dual-FF synchronizer is safe for single-bit signals. But what about multi-bit data? Consider an 8-bit counter transitioning from `10000000` (binary 128) to `01111111` (binary 127):
The fundamental problem: multiple bits changing creates a "race condition" during synchronization. Different bits resolve at different times, creating invalid intermediate values.
2. The Gray Code Solution
Core Property: Single Bit Changes
Gray code (also called Reflected Binary Code) has one special property:
When incrementing, exactly ONE bit changes at a time.
This eliminates glitches. If only one bit can change, the metastable window is limited to that one bit transitioning, not all bits.
Why This Helps CDC
When Gray code synchronizer (Gray counter in Clock A, dual-FF in Clock B) crosses domains:
- Only one bit transitions at a time → Only one FF1 stage can be metastable
- Other 7 bits are stable → Other FF2 stages capture clean data
- Worst case: One bit is metastable in FF1, but dual-FF ensures it resolves before FF2 samples
- Result: FF2 output is either the old value or the new value—never an intermediate glitch
3. Binary to Gray Encoding
Formula
Convert binary number B to Gray code G:
Hardware Implementation
module binary_to_gray #(parameter WIDTH = 8) ( input [WIDTH-1:0] binary, output [WIDTH-1:0] gray ); // G[i] = B[i] XOR B[i+1] assign gray = binary ^ (binary >> 1); endmodule // Example usage: // binary_to_gray #(.WIDTH(8)) b2g ( // .binary(counter_bin), // 8-bit binary counter // .gray(gray_code) // Convert to Gray // );
One line of logic: The XOR of the binary value with itself right-shifted by 1. This compiles to N XOR gates in parallel (zero delay).
4. Gray to Binary Decoding
Formula
Convert Gray code G back to binary B (used in Clock B domain after synchronization):
Hardware Implementation
module gray_to_binary #(parameter WIDTH = 8) (
input [WIDTH-1:0] gray,
output [WIDTH-1:0] binary
);
// B[i] = Gray[i] XOR B[i+1]
// Cascaded XORs: each bit depends on all higher bits
assign binary[WIDTH-1] = gray[WIDTH-1];
genvar i;
generate
for (i = WIDTH-2; i >= 0; i = i-1) begin
assign binary[i] = binary[i+1] ^ gray[i];
end
endgenerate
endmodule
// Complete Gray Code CDC Module:
module gray_cdc #(parameter WIDTH = 8) (
input clk_a,
input [WIDTH-1:0] binary_a, // Binary counter in Clock A
input clk_b,
output [WIDTH-1:0] binary_b // Synchronized binary in Clock B
);
wire [WIDTH-1:0] gray_a, gray_b_sync, gray_b_ff2;
reg [WIDTH-1:0] gray_b_ff1;
// Clock A: Convert binary to Gray
binary_to_gray #(.WIDTH(WIDTH)) b2g_a (
.binary(binary_a),
.gray(gray_a)
);
// Clock B: Dual-FF synchronizer on Gray code
always @(posedge clk_b) begin
gray_b_ff1 <= gray_a; // May be metastable
gray_b_ff2 <= gray_b_ff1; // Definitely resolved
end
// Clock B: Convert Gray back to binary
gray_to_binary #(.WIDTH(WIDTH)) g2b_b (
.gray(gray_b_ff2),
.binary(binary_b)
);
endmodule
5. MTBF Analysis for Gray CDC
Gray code CDC has the same MTBF characteristics as binary dual-FF, but with a critical advantage:
6. Real-World Applications
Cross-Domain Counters
Synchronizing a counter from Clock A to Clock B:
- Not Gray: Binary counter → 8 separate dual-FF synchronizers on each bit → glitches during multi-bit transitions
- Gray: Binary counter → convert to Gray → 1 dual-FF synchronizer on Gray word → convert back to binary → guaranteed no glitches
Dual-Clock FIFO Pointers
FIFO write pointer in Clock A, read pointer in Clock B. Both wrap around (saturating counters). Gray code ensures pointer can cross domains safely without misinterpreting pointer equality or wrap-around conditions.
Multi-Bit Command Buses
If sending multi-bit encoded commands (not just incremented counters), Gray code might not work directly. Use Gray + encoding constraint (e.g., one-hot encoding) or pulse synchronization (Day 4).
7. Limitations and When NOT to Use Gray Code
Only For Monotonic Counters
Gray code is designed for counters that increment (or decrement) by 1. If your data is arbitrary binary (not monotonic), Gray code won't help.
Gray Code Limitation Example
Problem: Sending arbitrary 8-bit config value across clock domains Config can be: 0x00, 0x15, 0xA3, 0xFF, etc. (any value) Gray code does NOT help: - Gray code property (single-bit change) only applies to incremented counters - Arbitrary value A to arbitrary value B may need to change all bits - Solution: Use dual-FF on all 8 bits + handshake protocol, or pulse sync Gray code helps: - Counter incrementing 0 → 1 → 2 → ... → 255 → 0 - Only 1 bit changes per increment - Gray code ensures safe crossing
8. Implementation Checklist
- ✅ Identify what's crossing: Is it a counter or arbitrary data?
- ✅ Monotonic counter: Use Gray code synchronizer
- ✅ Arbitrary data: Use dual-FF on each bit or pulse synchronization
- ✅ Convert binary → Gray in source domain using X OR (X >> 1)
- ✅ Apply dual-FF synchronizer to the Gray word (not binary)
- ✅ Convert Gray → binary in destination domain using cascaded XOR
- ✅ Test wrap-around: 255 → 0 should work smoothly
- ✅ Verify MTBF: Should be billions of years for typical counters
Next (Day 4): Pulse synchronizers for single-bit control signals and handshakes.