HomeCDC GuideDay 3
DAY 3 · CDC FUNDAMENTALS

Gray Code — Crossing Multi-Bit Buses

By EcrioniX · Updated Jun 13, 2026

The 2-FF synchronizer solves metastability for single bits. But what about crossing a multi-bit value, like a counter or FIFO pointer? Gray code is the answer: a binary code where only 1 bit changes between consecutive values. Even if that single bit is delayed by metastability, the result is always a valid code, never an invalid state.

1. The problem: multi-bit binary crossing

Consider crossing a 4-bit counter from clock domain A to domain B. In regular binary, consecutive numbers differ in multiple bits:

If you cross these asynchronously, metastability can delay some bits but not others. You might read an intermediate invalid value:

⚠️ Binary is unsafe for CDC

Never cross a multi-bit binary value asynchronously without Gray code conversion. The synchronizer can't protect you — multiple bits can be in inconsistent states during the transition.

2. What is Gray code?

Gray code (also called reflected binary code) is a binary code where consecutive values differ by exactly 1 bit:

DecimalBinaryGrayBits changed
000000000
1000100011 bit
2001000111 bit
3001100101 bit
4010001101 bit
5010101111 bit
6011001011 bit
7011101001 bit
8100011001 bit
9100111011 bit
10101011111 bit
11101111101 bit
12110010101 bit
13110110111 bit
14111010011 bit
15111110001 bit

Notice: every transition changes only 1 bit. This is the magic of Gray code.

3. Why Gray code is safe for CDC

If only 1 bit changes between consecutive Gray values, then even if metastability delays that bit:

✅ Gray code + 2-FF synchronizer = safe multi-bit crossing

Convert binary to Gray, sync the Gray value across clock domains with a 2-FF synchronizer per bit, then convert back to binary. The receiver always reads a valid Gray code, even if metastability delays individual bits. This is the standard pattern for synchronizing FIFO pointers, event counters, and state information.

4. Binary ↔ Gray conversion

Binary to Gray:

Gray = Binary XOR (Binary >> 1)

Example: Binary 0101 (5)

Gray to Binary: Reverse the process by XOR-ing all more-significant bits:

5. Verilog implementation

gray_converter.sv
// Binary to Gray and Gray to Binary converters
module gray_converter #(parameter WIDTH = 8) (
  input  [WIDTH-1:0] binary_in,
  output [WIDTH-1:0] gray_out
);
  assign gray_out = binary_in ^ (binary_in >> 1);
endmodule

module gray_to_binary #(parameter WIDTH = 8) (
  input  [WIDTH-1:0] gray_in,
  output [WIDTH-1:0] binary_out
);
  genvar i;
  generate
    for (i = 0; i < WIDTH; i = i + 1) begin : g2b
      if (i == WIDTH - 1)
        assign binary_out[i] = gray_in[i];
      else
        assign binary_out[i] = gray_in[i] ^ binary_out[i + 1];
    end
  endgenerate
endmodule

// Example: 4-bit Gray counter
module gray_counter #(parameter WIDTH = 4) (
  input clk, rst_n,
  output [WIDTH-1:0] gray_out,
  output [WIDTH-1:0] binary_out
);
  reg [WIDTH-1:0] counter;

  always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
      counter <= 0;
    else
      counter <= counter + 1;
  end

  gray_converter #(.WIDTH(WIDTH)) gc (.binary_in(counter), .gray_out(gray_out));
  assign binary_out = counter;
endmodule

6. Using Gray code in FIFO pointer synchronization

A dual-clock FIFO (covered in depth on Day 5) uses Gray code like this:

  • Write domain: Maintains write_ptr (binary), converts to write_ptr_gray
  • Crosses to read domain: write_ptr_gray synced with 2-FF synchronizer
  • Read domain: Synced gray value converted back to binary for comparisons
  • Same in reverse: read_ptr crosses back to write domain as Gray

This pattern ensures pointers never produce invalid intermediate states during crossing.

gray_fifo_pointer.sv (snippet)
// Simplified: FIFO pointer synchronization using Gray code
module fifo_wr_ptr_gray #(parameter AWIDTH = 4) (
  input clk_w, rst_n,
  input wr_en,
  output [AWIDTH-1:0] wr_ptr_gray,
  output [AWIDTH-1:0] wr_ptr_bin
);
  reg [AWIDTH-1:0] wr_ptr;

  always @(posedge clk_w or negedge rst_n) begin
    if (!rst_n)
      wr_ptr <= 0;
    else if (wr_en)
      wr_ptr <= wr_ptr + 1;
  end

  assign wr_ptr_bin = wr_ptr;
  assign wr_ptr_gray = wr_ptr ^ (wr_ptr >> 1);
endmodule

// In read domain, synchronize wr_ptr_gray
wire [AWIDTH-1:0] wr_ptr_gray_sync;
sync_2ff #(.WIDTH(AWIDTH)) wr_sync (
  .clk(clk_r),
  .rst_n(rst_n),
  .async_in(wr_ptr_gray),
  .sync_out(wr_ptr_gray_sync)
);

// Convert back to binary for comparison
wire [AWIDTH-1:0] wr_ptr_sync;
gray_to_binary #(.WIDTH(AWIDTH)) g2b (
  .gray_in(wr_ptr_gray_sync),
  .binary_out(wr_ptr_sync)
);

// Now wr_ptr_sync is safe to use in read_ptr comparisons

7. Best practices and limitations

Use Gray code for:Don't use Gray code for:
FIFO read/write pointersRandom multi-bit data values
Event counters crossing domainsCommands or control words (use handshake)
Address busesData that doesn't increment sequentially
State machine pointersSituations where you need exact binary value (convert back from Gray)

⚠️ Gray code gotcha: conversion overhead

Gray code adds a small logic delay for binary↔gray conversion. For very high-frequency designs, pre-compute Gray conversion stages or pipeline them. Also, Gray code only helps with sequential values; for random data, you need a handshake (Day 4).

🎯 Day 3 takeaways

  • Gray code property: only 1 bit changes between consecutive values
  • Safe for CDC: even if 1 bit is delayed, result is always a valid adjacent code
  • Binary to Gray: Gray = Binary XOR (Binary >> 1)
  • Gray to binary: iterative XOR of more-significant bits
  • FIFO pointer pattern: Gray encode before sync, decode after sync
  • Typical use: FIFO pointers, event counters, address buses
  • Not for random data: use handshake protocols instead (covered Day 4)

FAQ

Why is Gray code safe for CDC?

Only 1 bit changes between consecutive Gray values. If that bit is delayed by metastability, you always read a valid code (or adjacent code), never an invalid intermediate state. Binary does not have this property.

What is the Gray code formula?

Binary to Gray: G = B XOR (B >> 1). Gray to Binary: iteratively XOR all more-significant bits. Most HDL libraries have parameterized Gray converters available.

Can I use Gray code for all multi-bit CDC?

Gray code is best for counters and pointers (strictly incrementing values). For random multi-bit data, Gray code doesn't help — you need a handshake protocol with valid/ready signaling (Day 4).

How many FF stages do I need after Gray sync?

Use a 2-FF synchronizer for each Gray code bit, just like single-bit signals. The Gray code property (1-bit change) prevents invalid states, but metastability resolution still requires 2 FFs.

Is Gray code the only way to sync multi-bit values?

No. For random data, use a valid/ready handshake (Day 4). For commands, use encoded handshakes or CDC-safe protocols. Gray code is specific to strictly-incrementing sequences like FIFO pointers.