Gray Code Counter in Verilog: Code, Testbench, Explained
A Gray code counter steps through a sequence where exactly one bit changes between consecutive values. That single-bit property makes it indispensable in digital design: it eliminates the decoding glitches that a plain binary counter produces when several bits flip at once, and it is the backbone of safe clock-domain-crossing (CDC) in asynchronous FIFOs. If you have ever seen a FIFO pointer synchronized across two clocks, you have almost certainly seen a Gray counter underneath.
This tutorial gives you working, runnable gray code counter Verilog code. We build a binary counter, convert its output to Gray with a one-line XOR, parameterize the width, and finish with a self-checking testbench that decodes Gray back to binary to prove the conversion is correct. Every example below is complete Verilog-2005 and compiles with iverilog -g2005 -o sim file.v && vvp sim.
What is a Gray code counter?
Instead of counting 000, 001, 010, 011 like binary, a 3-bit Gray sequence runs 000, 001, 011, 010, 110, 111, 101, 100 and then wraps back to 000. Notice that every transition, including the wrap-around from the last value to the first, flips a single bit. The cleanest way to build one in hardware is not to generate the Gray pattern directly. Instead you keep an ordinary binary counter as the state register and derive the Gray output combinationally. This keeps the next-state logic trivial (a simple increment) while still giving you a glitch-free coded output.
Binary-to-Gray conversion
The conversion from binary to Gray is a famous identity: each Gray bit is the XOR of the binary bit with the binary bit immediately above it, and the most significant bit passes through unchanged. In Verilog this collapses into a single right-shift and XOR, with zero-extension handling the top bit automatically.
assign gray = bin ^ (bin >> 1);
Because the shift pads with a zero on the left, the top Gray bit equals the top binary bit, which is exactly the rule we want. This expression is width-agnostic, so the same line works whether bin is 4 bits or 32.
A 4-bit Gray code counter with testbench
Here is the first complete design. The register bin increments on every rising clock edge, an active-low synchronous reset clears it, and the Gray output is produced by the assign statement. The testbench drives a clock, releases reset mid-cycle to avoid a sampling-edge race, and uses $display to print the binary count beside its Gray code for one full 16-count period.
// gray_counter_4bit.v -- 4-bit binary counter with binary->gray conversion
module gray_counter_4bit (
input wire clk,
input wire rst_n, // active-low synchronous reset
output reg [3:0] bin, // underlying binary count
output wire [3:0] gray // Gray-coded output
);
// Count up in binary on every rising clock edge
always @(posedge clk) begin
if (!rst_n)
bin <= 4'b0000;
else
bin <= bin + 4'b0001;
end
// Binary-to-Gray: gray = bin XOR (bin >> 1)
assign gray = bin ^ (bin >> 1);
endmodule
// --- Testbench (last module) ---
module tb_gray_counter_4bit;
reg clk;
reg rst_n;
wire [3:0] bin;
wire [3:0] gray;
integer i;
gray_counter_4bit dut (
.clk(clk), .rst_n(rst_n), .bin(bin), .gray(gray)
);
// 10-time-unit clock period: rising edges at 5, 15, 25, ...
initial clk = 1'b0;
always #5 clk = ~clk;
initial begin
rst_n = 1'b0; // hold reset over the first edge
#12 rst_n = 1'b1; // release mid-cycle, away from any edge
$display("time bin gray");
for (i = 0; i < 16; i = i + 1) begin
@(posedge clk);
#1 $display("%4t %b %b", $time, bin, gray);
end
$finish;
end
endmoduletime bin gray 16 0001 0001 26 0010 0011 36 0011 0010 46 0100 0110 56 0101 0111 66 0110 0101 76 0111 0100 86 1000 1100 96 1001 1101 106 1010 1111 116 1011 1110 126 1100 1010 136 1101 1011 146 1110 1001 156 1111 1000 166 0000 0000 /var/folders/l0/gs5hnkm56g54bmxg32r6nnwr0000gn/T/tmpkvjk_0qs/gray_counter_4bit.v:44: $finish called at 166 (1s)
Read the log column by column and you will see the Gray output change by exactly one bit on each line, right through the wrap from 1111/1000 back to 0000.
Parameterized-width Gray code counter
Hardcoding a width is fine for a demo, but reusable IP should be parameterized. This version adds a WIDTH parameter and a count-enable input en. The reset and increment constants are built with Verilog replication operators so they scale to any width. The testbench instantiates a 3-bit counter and runs two full cycles to show the wrap-around.
// gray_counter.v -- parameterized-width binary counter + Gray conversion
module gray_counter #(
parameter WIDTH = 4
) (
input wire clk,
input wire rst_n,
input wire en, // count enable
output reg [WIDTH-1:0] bin,
output wire [WIDTH-1:0] gray
);
always @(posedge clk) begin
if (!rst_n)
bin <= {WIDTH{1'b0}};
else if (en)
bin <= bin + {{(WIDTH-1){1'b0}}, 1'b1};
end
// Reflected binary Gray code
assign gray = bin ^ (bin >> 1);
endmodule
// --- Testbench (last module) ---
module tb_gray_counter;
localparam W = 3; // instantiate a 3-bit counter
reg clk;
reg rst_n;
reg en;
wire [W-1:0] bin;
wire [W-1:0] gray;
integer i;
gray_counter #(.WIDTH(W)) dut (
.clk(clk), .rst_n(rst_n), .en(en), .bin(bin), .gray(gray)
);
initial clk = 1'b0;
always #5 clk = ~clk;
initial begin
rst_n = 1'b0;
en = 1'b1;
#12 rst_n = 1'b1;
$display("WIDTH=%0d", W);
$display("step bin gray");
for (i = 0; i < 2*(1<<W); i = i + 1) begin
@(posedge clk);
#1 $display("%4d %b %b", i, bin, gray);
end
$finish;
end
endmoduleWIDTH=3 step bin gray 0 001 001 1 010 011 2 011 010 3 100 110 4 101 111 5 110 101 6 111 100 7 000 000 8 001 001 9 010 011 10 011 010 11 100 110 12 101 111 13 110 101 14 111 100 15 000 000 /var/folders/l0/gs5hnkm56g54bmxg32r6nnwr0000gn/T/tmpuzqplaxy/gray_counter.v:49: $finish called at 166 (1s)
To build an 8-bit counter you change nothing but the override: gray_counter #(.WIDTH(8)) u0 (...). The single-bit increment constant {{(WIDTH-1){1'b0}}, 1'b1} is the idiomatic way to write a width-correct “1” and avoids the width-mismatch warnings you get from a bare literal.
Decoding Gray back to binary
A Gray counter is only half the story; a consumer usually needs the binary value back. Decoding reverses the rule: the MSB passes through, and each lower bit is the XOR of its Gray bit with the already-decoded bit above it. The module below instantiates the counter, decodes its Gray output, and the testbench is self-checking: it compares the decoded value against the reference binary count and prints OK or MISMATCH, then reports a total error count.
// gray_roundtrip.v -- Gray counter + Gray->binary decoder, self-checking
module gray_roundtrip #(
parameter WIDTH = 4
) (
input wire clk,
input wire rst_n,
output reg [WIDTH-1:0] bin, // reference binary count
output wire [WIDTH-1:0] gray, // Gray output
output reg [WIDTH-1:0] decoded // Gray decoded back to binary
);
integer k;
always @(posedge clk) begin
if (!rst_n)
bin <= {WIDTH{1'b0}};
else
bin <= bin + {{(WIDTH-1){1'b0}}, 1'b1};
end
// Encode: binary -> Gray
assign gray = bin ^ (bin >> 1);
// Decode: Gray -> binary. MSB passes through; each lower bit
// is the XOR of the Gray bit with the decoded bit above it.
always @(*) begin
decoded[WIDTH-1] = gray[WIDTH-1];
for (k = WIDTH-2; k >= 0; k = k - 1)
decoded[k] = gray[k] ^ decoded[k+1];
end
endmodule
// --- Testbench (last module) ---
module tb_gray_roundtrip;
localparam W = 4;
reg clk;
reg rst_n;
wire [W-1:0] bin;
wire [W-1:0] gray;
wire [W-1:0] decoded;
integer i;
integer errors;
gray_roundtrip #(.WIDTH(W)) dut (
.clk(clk), .rst_n(rst_n), .bin(bin), .gray(gray), .decoded(decoded)
);
initial clk = 1'b0;
always #5 clk = ~clk;
initial begin
errors = 0;
rst_n = 1'b0;
#12 rst_n = 1'b1;
$display("step bin gray decoded check");
for (i = 0; i < 16; i = i + 1) begin
@(posedge clk);
#1;
if (decoded !== bin) errors = errors + 1;
$display("%4d %b %b %b %s",
i, bin, gray, decoded,
(decoded === bin) ? "OK" : "MISMATCH");
end
$display("decode errors = %0d", errors);
$finish;
end
endmodulestep bin gray decoded check 0 0001 0001 0001 OK 1 0010 0011 0010 OK 2 0011 0010 0011 OK 3 0100 0110 0100 OK 4 0101 0111 0101 OK 5 0110 0101 0110 OK 6 0111 0100 0111 OK 7 1000 1100 1000 OK 8 1001 1101 1001 OK 9 1010 1111 1010 OK 10 1011 1110 1011 OK 11 1100 1010 1100 OK 12 1101 1011 1101 OK 13 1110 1001 1110 OK 14 1111 1000 1111 OK 15 0000 0000 0000 OK decode errors = 0 /var/folders/l0/gs5hnkm56g54bmxg32r6nnwr0000gn/T/tmpdin_cmta/gray_roundtrip.v:64: $finish called at 166 (1s)
A zero error count is your proof that encode and decode are exact inverses across the whole range.
How the code works
Three ideas carry all three designs. First, the state is always plain binary; Gray is a view of that state, never the state itself. Second, the encoder is combinational (assign), so the Gray output tracks the register with no extra latency. Third, the decoder ripples from MSB down because each bit depends on the one above it. Keeping the register binary is what makes the counter easy to reason about and easy to extend, for example to an up/down counter or a FIFO pointer.
Common mistakes
- Trying to increment the Gray value directly. There is no simple “+1” in Gray space. Always increment binary and re-encode. Attempting Gray-domain arithmetic leads to tangled, buggy logic.
- Width mismatches on the increment. Writing
bin + 1against a sized vector can trigger lint warnings and surprising sign extension. Use a width-correct constant such as{{(WIDTH-1){1'b0}}, 1'b1}. - Releasing reset exactly on the clock edge. In simulation, changing
rst_nat the same timestep as the sampling edge creates a race and can leave the register at X. Deassert reset mid-cycle, as the testbenches above do with#12. - Reading the counter across clock domains without synchronizing. The single-bit-change property makes Gray safe to synchronize, but you still need a multi-flop synchronizer in the receiving domain; the Gray code alone does not remove the need for it.
- Using a blocking assignment for the register. The sequential
binmust use non-blocking (<=) to model a flip-flop correctly; the combinational decode correctly uses blocking (=).
Quick check
Why does a Gray code counter change only one bit per step, and why does that matter?
Gray code is defined so consecutive values differ in a single bit position. This removes the multi-bit transition hazards of binary, which is critical when the value is sampled by another clock domain or decoded asynchronously, because only one bit can be mid-transition at any sampling instant.
What does the expression bin ^ (bin >> 1) compute?
It converts an unsigned binary value to its reflected Gray code. Each Gray bit becomes the XOR of the binary bit with the next higher binary bit; the right shift pads a zero on the left, so the MSB passes through unchanged.
In the parameterized design, why write the increment as {{(WIDTH-1){1'b0}}, 1'b1} instead of just 1?
It builds a constant of exactly WIDTH bits representing the value one, which keeps the addition width-correct for any WIDTH and avoids width-mismatch or sign-extension warnings from a bare literal.
How do you decode Gray back to binary, and why does the loop run from MSB to LSB?
The MSB of the binary equals the MSB of the Gray value; every lower binary bit is the XOR of its Gray bit with the binary bit immediately above it. Because each bit depends on the already-computed bit above it, the loop must proceed from the most significant bit downward.
Share your question in comments or talk to our mentor team for batch guidance.
Learn Verilog with mentors, labs and placement support
This free series is maintained by the ChipXpert training team in Hyderabad & Bengaluru. Go deeper with instructor-led courses on real EDA tools.
Explore VLSI Courses