A 4-bit binary counter going from 7 to 8 changes all four bits at once: 0111 becomes 1000. If another clock domain samples the counter during that change, each bit can be caught old or new, so the reader can see any of 16 values. A Gray counter visits the same 16 values in a different order, chosen so that every step, including the wrap, changes exactly one bit. A reader that samples mid-change then sees either the old value or the new one, never a third.
The same property saves switching. Over one full lap the 4-bit binary count flips 30 bits; the Gray count flips 16, one per step. The bench at the end of this page measures both.
Read each slot as one clock cycle, and look for which of the four bottom lanes moved since the slot before: there is always exactly one.
Slots 2 to 3: binary goes 7 to 8 and all four binary bits change; of the Gray bits only g3 moves (it rises).
Slot 7: binary goes b to c, three binary bits change; only g2 falls.
Slot 11: the wrap from f to 0 clears four binary bits; only g3 falls.
g0 changes on every second step and g3 only twice in the trace: the higher the bit, the less often it moves.
Binary to Gray is one row of XOR gates: each Gray bit is the binary bit XOR the binary bit above it, and the top bit is copied.
Gray to binary runs the other way, from the top bit down. Each binary bit is the Gray bit XOR the binary bit just computed above it: a prefix XOR, so bit 0 depends on every Gray bit.
Worked example: decode Gray 1101. b3 = g3 = 1. b2 = b3 XOR g2 = 1 XOR 1 = 0. b1 = b2 XOR g1 = 0 XOR 0 = 0. b0 = b1 XOR g0 = 0 XOR 1 = 1. The binary value is 1001, which is 9. Check by encoding: 1001 XOR 0100 = 1101.
The 3-bit table shows the whole cycle. Bold marks the one Gray bit that changed from the row above (row 0 is reached from row 7 by the wrap).
Count | Binary | Binary bits changed from the row above | Gray |
|---|---|---|---|
0 | 000 | 3 | 000 |
1 | 001 | 1 | 001 |
2 | 010 | 2 | 011 |
3 | 011 | 1 | 010 |
4 | 100 | 3 | 110 |
5 | 101 | 1 | 111 |
6 | 110 | 2 | 101 |
7 | 111 | 1 | 100 |
The binary column changes 14 bits per lap and the Gray column 8. In general an N-bit binary counter flips 2 x (2ᴺ − 1) bits per lap and a Gray counter 2ᴺ, so Gray roughly halves the toggles.
Both styles below keep the Gray value in flops. They differ in what else they store.
// snippet: style A keeps a binary register and registers the Gray code of the NEXT count
assign bin_next = bin + en;
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) begin bin <= '0; gray <= '0; end
else begin
bin <= bin_next;
gray <= bin_next ^ (bin_next >> 1); // convert before the flop, not after
end// snippet: style B keeps only the Gray register: decode, add one, encode
assign bin_now = gray2bin(gray); // prefix XOR, W - 1 gates deep
assign bin_next = bin_now + en;
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) gray <= '0;
else gray <= bin_next ^ (bin_next >> 1);Style A: binary plus Gray | Style B: Gray only | |
|---|---|---|
Flops | 2W | W |
Path into the flops | Adder, then one XOR | XOR chain, adder, one XOR: longer |
Local binary value | Free, from the bin register | Must be decoded again |
Toggles per lap | Binary and Gray both switch | Gray only: the full saving |
Typical use | FIFO pointers, where the binary value addresses memory | Small counters where area or switching matter |
Style A converts bin_next, not bin. Converting bin would make the Gray register lag the binary one by a cycle, a classic source of off-by-one flags in FIFOs.
The one-bit property holds for the stored values, not for the gates that compute them. The tempting shortcut is to keep only a binary register and decode it with a continuous assignment:
// bad-example: combinational Gray decode in front of a crossing
always_ff @(posedge clk) bin <= bin + 1'b1;
assign gray_out = bin ^ (bin >> 1); // feeds a synchroniser in another domainOn the 7 to 8 step all four binary bits change, each arriving at the XOR gates at a slightly different time. While they settle, gray_out can briefly pass through values that differ from both the old and new code in several bits, and a receiving flop can capture one of them. Put the XOR before the flop, as both styles do, and send nothing but flop outputs across. The bus synchroniser lesson shows this failure in simulation.
A Gray crossing still needs each bit synchronised and the bits kept within a bounded skew of each other. How an asynchronous FIFO uses two Gray pointers to compute full and empty is the subject of Pointers and Gray conversion.
The bench below runs both mod-10 counts through the same encoder. Its output:
naive mod-10: 9 -> 0 gray 1101 -> 0000 bits changed: 3
offset mod-10: 12 -> 3 gray 1010 -> 0010 bits changed: 1
PASS: naive mod-10 wrap changes 3 bits; offset 3..12 changes 1 bit on every stepmodule tb_gray_mod10;
function automatic logic [3:0] to_gray(input logic [3:0] b);
return b ^ (b >> 1);
endfunction
function automatic int ones(input logic [3:0] x); // bits set in x
int k = 0;
for (int i = 0; i < 4; i++) k += x[i];
return k;
endfunction
logic [3:0] now_g, next_g;
int next_n, flips, worst_naive = 0, worst_offset = 0;
initial begin
// naive mod 10: count 0..9, then wrap to 0
for (int n = 0; n <= 9; n++) begin
next_n = (n == 9) ? 0 : n + 1;
now_g = to_gray(n[3:0]); next_g = to_gray(next_n[3:0]);
flips = ones(now_g ^ next_g);
if (flips > worst_naive) worst_naive = flips;
if (flips != 1) $display("naive mod-10: %0d -> %0d gray %b -> %b bits changed: %0d", n, next_n, now_g, next_g, flips);
end
// offset mod 10: count 3..12, the ten codes centred in the 16-code cycle
for (int n = 3; n <= 12; n++) begin
next_n = (n == 12) ? 3 : n + 1;
now_g = to_gray(n[3:0]); next_g = to_gray(next_n[3:0]);
flips = ones(now_g ^ next_g);
if (flips > worst_offset) worst_offset = flips;
if (n == 12) $display("offset mod-10: %0d -> %0d gray %b -> %b bits changed: %0d", n, next_n, now_g, next_g, flips);
end
if (worst_naive != 3 || worst_offset != 1) $fatal(1, "FAIL: worst naive=%0d offset=%0d", worst_naive, worst_offset);
$display("PASS: naive mod-10 wrap changes %0d bits; offset 3..12 changes 1 bit on every step", worst_naive);
$finish;
end
endmoduleThe same rule covers any counter that jumps: a reload, a skip or a count by two changes several Gray bits in one step. If the value can jump, cross it with a handshake instead.
b3 = 0. b2 = 0 XOR 1 = 1. b1 = 1 XOR 1 = 0. b0 = 0 XOR 0 = 0. Binary 0100, which is 4. Check: 0100 XOR 0010 = 0110.
Centre six codes in the 8-code cycle: from 4 − 3 = 1 to 4 + 3 − 1 = 6. The wrap goes from 6 (Gray 101) back to 1 (Gray 001), which changes only the top bit. Counting 0 to 5 instead would wrap from Gray 111 to 000, three bits at once.
Both styles side by side for two full laps of a 4-bit count. Every step checks that exactly one Gray bit changed, that decoding the Gray value in the bench gives the expected count, and that the two styles agree; holding en low must freeze the value. The bench also counts toggles.
// Style A: count in binary, register the Gray code of the NEXT binary value.
module gray_ctr_a #(parameter int W = 4) (
input logic clk, rst_n, en,
output logic [W-1:0] bin, // binary count, for local use
output logic [W-1:0] gray // a flop output: safe to send across a clock domain
);
logic [W-1:0] bin_next;
assign bin_next = bin + {{(W-1){1'b0}}, en};
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) begin
bin <= '0;
gray <= '0;
end else begin
bin <= bin_next;
gray <= bin_next ^ (bin_next >> 1); // convert before the flop, not after
end
endmodule
// Style B: keep only the Gray register; decode, add one, encode again.
module gray_ctr_b #(parameter int W = 4) (
input logic clk, rst_n, en,
output logic [W-1:0] gray
);
function automatic logic [W-1:0] gray2bin(input logic [W-1:0] g);
logic [W-1:0] b; // prefix XOR from the top bit down
b[W-1] = g[W-1];
for (int i = W - 2; i >= 0; i--) b[i] = b[i+1] ^ g[i];
return b;
endfunction
logic [W-1:0] bin_now, bin_next;
assign bin_now = gray2bin(gray);
assign bin_next = bin_now + {{(W-1){1'b0}}, en};
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) gray <= '0;
else gray <= bin_next ^ (bin_next >> 1);
endmodule
toggles over 32 steps: binary 60, Gray 32
PASS: both styles agree, one Gray bit per step across 32 steps and 2 wraps, round trip exactSixty binary toggles over two laps is 30 per lap, against 16 for Gray. In style A both registers switch, so that saving only appears on the wires that carry the Gray bits; style B keeps it everywhere.
Tool: Icarus Verilog 13.0, run on 2026-10-17. Commands: iverilog -g2012 -Wall -o sim.out gray.sv tb_gray.sv && vvp sim.out (the two counter styles and tb_gray_ctr shown on the page); iverilog -g2012 -Wall -o m.out tb_mod10.sv && vvp m.out (tb_gray_mod10).
Result: tb_gray_ctr prints toggles over 32 steps: binary 60, Gray 32 and its PASS line; tb_gray_mod10 prints the naive 9 to 0 wrap changing 3 bits (1101 to 0000), the offset 12 to 3 wrap changing 1 bit (1010 to 0010), and PASS. Both are also run by run-rtl-testbenches.mjs.
The waveform is generated by node scripts/tools/gen-waves.mjs sequential:counters-timers from wave-src/counters-timers/gray-count.sv (style A, W = 4, skip 5, 12 cycles) with expect strings checked; a second run left the generated file unchanged.
The benches count toggles with a small ones() function: in this Icarus build $countones returned wrong values inside a loop for a one-bit difference, while Verilator 5.048 gave the right one.
Scope and limits: zero-delay functional simulation. It checks values at clock edges, not the glitches of a combinational decode, synchroniser behaviour or bit-to-bit skew; toggle counts are register bit flips, not measured power.