Gray code only helps if the CDC net is a registered Gray bus. If Gray is combinational from a binary counter that is also transitioning, multiple Gray bits can glitch in one window — you are back to the binary problem.
wire [PTR_W-1:0] bin_next = bin + wr_fire;
wire [PTR_W-1:0] gray_next = bin_next ^ (bin_next >> 1);
always @(posedge wr_clk or negedge wr_rst_n) begin
if (!wr_rst_n) begin
bin <= {PTR_W{1'b0}};
gray <= {PTR_W{1'b0}};
end else begin
bin <= bin_next;
gray <= gray_next; // flop the Gray word, then 2FF-sync gray
end
end
The net that leaves the write domain is gray, a flop output. Between wr_clk edges it is stable (ignoring clock-to-q). At most one Gray bit differs from the previous registered value.
always @(posedge wr_clk) bin <= bin + wr_fire; wire [PTR_W-1:0] gray_combo = bin ^ (bin >> 1); // then 2FF-sync gray_combo ← CDC on combinational XOR
When bin bits have different clock-to-q, the XOR cloud produces intermediate Gray values with two or more bits flipping during the settling window. Sampling that cloud is the same class of bug as sampling binary.
A related silicon bug: registering binary, then registering Gray from the old binary in a way that two Gray bits update on one cycle at a specific count (for example 7→8) because the XOR was applied to a mis-aligned copy of the counter. Check with $onehot0(gray ^ $past(gray)) on the exact net you synchronize.
Mark the 2FF pair (* ASYNC_REG = "TRUE" *) (Xilinx) / equivalent dont_touch. Do not let retiming insert logic between the Gray flop and the first synchronizer flop.
Set a false path or a max-delay path from the Gray source flop to the first synchronizer flop. A false path says timing does not have to close at the destination period — correct because you accept metastability on FF1. Some teams prefer a set_max_delay of one destination period minus setup to keep the bus from being absurdly routed. Do not false-path the whole FIFO; RAM and local pointer math must still meet their clocks.
Even registered Gray can have bit-to-bit routing skew on a wide pointer. Because only one bit is meant to toggle, skew just moves when that one bit arrives. If two bits toggled, skew would recreate reconvergence. Unit-distance is what makes moderate skew safe. Still keep the Gray bus compact in the floorplan: source flops next to the synchronizer bank.
If you CDC a combinational Gray XOR of a binary counter, bits glitch while the counter’s clock-to-q skews. Sample that cloud and you can capture multi-bit changes. Convert bin_next to gray_next, then register Gray, then 2FF the flop output.
The first synchronizer flop in the destination domain, and usually the second. Do not retime logic between the source Gray flop and FF1.
$onehot0(gray ^ $past(gray)) on the exact source register that feeds the CDC, every source clock, excluding reset.
Yes if both use the same next-state. Prefer one block so they cannot diverge. Never Gray-encode the *old* binary while incrementing a separate copy.
gray_next from bin_next, register both.