Gray coding is a contract with three clauses: one step at a time, registered Gray, nothing between the register and the synchroniser. Each clause below shows how the contract breaks and what the destination observes instead of old-or-new.
A counter that can be loaded, or reset to a nonzero value, changes several Gray bits in one cycle whenever the jump lands on a distant value. The destination then samples a mixture again, exactly the failure Gray was meant to remove.
// bad-example: a loadable "Gray" counter. Loading 12 (Gray 1010)
// while the register holds 3 (Gray 0010) flips two bits at once.
logic [3:0] bin, gray;
always_ff @(posedge clk_s or negedge rst_n)
if (!rst_n) begin bin <= '0; gray <= '0; end
else if (load) begin bin <= load_val; gray <= (load_val >> 1) ^ load_val; end
else begin bin <= bin + 4'd1; gray <= ((bin + 4'd1) >> 1) ^ (bin + 4'd1); endFix: if the value can jump, do not Gray-cross it. Use a handshake with held data or a FIFO (see multi-bit strategies).
If the source advances two or more steps per destination sample, each individual sample is still old-or-new of its step, but values are skipped: the destination never observes the intermediate counts. Logic that must see every value, such as an event counter that subtracts successive samples, silently undercounts.
// bad-example: differencing Gray samples to count events. With two
// source steps per destination sample, every second event is never observed.
always_ff @(posedge clk_d)
if (gray_d != gray_d_q) events <= events + 1'b1; // counts samples seen, not steps takenFix: count in the source domain and cross the count, or move the data through a FIFO so nothing is skipped.
Decoding, comparing, or re-encoding the Gray value between the register and the synchroniser breaks the argument twice: the logic output can glitch during the step, and in the destination domain separately synchronised Gray bits recombined through logic is reconvergence (see reconvergence analysis). Cross the registered Gray value unchanged; decode after the synchroniser.
// bad-example: comparing before the crossing. The comparator output
// glitches while the Gray bits settle, and the glitch crosses as a pulse.
assign almost_full_s = (gray == 4'b0110); // do not cross this bit instead of the Gray value
sync2 u_af (.clk(clk_d), .d(almost_full_s), .q(almost_full_d)); // snippet: synchroniser wrapperOnly if the load is never used while the destination is sampling, which must be proven by the reset and startup sequence, not assumed. The safer review position is that any reachable load disqualifies Gray crossing unless the load value is constrained to a one-bit neighbour of the current value.