Module 49 min

Buses and Gray Code

Why you cannot just synchronize a bus bit by bit

The two-flop synchronizer is perfect for one bit. The most common CDC mistake is assuming you can use one on every bit of a bus. You cannot, and here is exactly why.

The bus skew problem

Put a separate two-flop synchronizer on each bit of a multi-bit value and the bits resolve independently. When several bits change at once, some may settle on the new value and some on the old value in the same cycle. The destination then reads a combination that never actually existed.

For a binary counter going from 0111 to 1000, all four bits change at once. If three resolve to the new value and one lags, the destination could read 1111 or 0000 for a cycle. That garbage value can be catastrophic.

Gray code - one bit at a time

Gray code is a counting sequence where only one bit changes between consecutive values. If only one bit ever changes, then even if that bit is caught mid-transition, the synchronized result is either the old value or the new value, never a corrupt mix.

verilog
// Binary to gray: one bit changes per increment
assign gray = bin ^ (bin >> 1);

// Gray back to binary (when you need to use the value)
integer i;
always @(*) begin
  bin_back[WIDTH-1] = gray[WIDTH-1];
  for (i = WIDTH-2; i >= 0; i = i - 1)
    bin_back[i] = bin_back[i+1] ^ gray[i];
end
DecimalBinaryGray
0000000
1001001
2010011
3011010
4100110

Notice each gray value differs from the one above it in exactly one bit. That single-bit-change property is what makes it safe to synchronize.

Watch out

Gray code only helps for values that increment or decrement by one, like FIFO pointers. It does not make an arbitrary data bus safe to cross. For general data you hold the bus steady and synchronize a control signal instead, which is the handshake approach.

Pro tip

This is exactly why asynchronous FIFOs use gray-coded read and write pointers. The next lesson builds one.