Gray-to-Binary Converter in Verilog: Prefix-XOR and Reduction-XOR Designs
A Gray code changes only one bit between consecutive values, which is exactly why it is used for position encoders, asynchronous FIFO pointers, and anywhere a multi-bit value is sampled across a clock boundary. But once the value is inside synchronous logic you usually need plain weighted binary back. This page gives you clean, synthesizable gray to binary verilog code you can drop straight into a design.
We build a parameterized gray2bin converter two ways — a prefix-XOR chain and a reduction-XOR one-liner — and prove both with a self-checking testbench that sweeps all 16 codes for N = 4. Note that gray-to-binary is the exact inverse of binary-to-gray (gray = bin ^ (bin >> 1)), so a round-trip is the cleanest way to test it.
Prefix-XOR converter (genvar chain)
The most significant bit of the binary output equals the MSB of the Gray input. Every lower binary bit is the XOR of the next-higher binary bit with the corresponding Gray bit: bin[i] = bin[i+1] ^ gray[i]. This is a running prefix XOR from the top down. The testbench drives the converter through a known forward map gray = bin ^ (bin >> 1) and asserts the recovered binary matches for every one of the 16 codes.
// gray2bin: parameterized Gray-code to binary converter
module gray2bin #(parameter N=4)(input [N-1:0] gray, output [N-1:0] bin);
// MSB passes through; each lower bit = XOR of higher binary bit with gray bit
assign bin[N-1] = gray[N-1];
genvar i;
generate
for (i = N-2; i >= 0; i = i-1) begin : g
assign bin[i] = bin[i+1] ^ gray[i];
end
endgenerate
endmodule
module tb;
reg [3:0] bin_in;
wire [3:0] gray;
wire [3:0] bin_out;
integer k, matches;
// Forward map binary->gray to generate a known Gray code
assign gray = bin_in ^ (bin_in >> 1);
// Device under test: gray->binary
gray2bin #(.N(4)) dut (.gray(gray), .bin(bin_out));
initial begin
matches = 0;
for (k = 0; k < 16; k = k+1) begin
bin_in = k[3:0];
#1;
if (bin_out === bin_in) begin
matches = matches + 1;
$display("bin=%b gray=%b -> recovered=%b OK", bin_in, gray, bin_out);
end else begin
$display("bin=%b gray=%b -> recovered=%b MISMATCH", bin_in, gray, bin_out);
end
end
$display("matches=%0d/16", matches);
if (matches == 16) $display("PASS");
else $display("FAIL");
$finish;
end
endmodulebin=0000 gray=0000 -> recovered=0000 OK bin=0001 gray=0001 -> recovered=0001 OK bin=0010 gray=0011 -> recovered=0010 OK bin=0011 gray=0010 -> recovered=0011 OK bin=0100 gray=0110 -> recovered=0100 OK bin=0101 gray=0111 -> recovered=0101 OK bin=0110 gray=0101 -> recovered=0110 OK bin=0111 gray=0100 -> recovered=0111 OK bin=1000 gray=1100 -> recovered=1000 OK bin=1001 gray=1101 -> recovered=1001 OK bin=1010 gray=1111 -> recovered=1010 OK bin=1011 gray=1110 -> recovered=1011 OK bin=1100 gray=1010 -> recovered=1100 OK bin=1101 gray=1011 -> recovered=1101 OK bin=1110 gray=1001 -> recovered=1110 OK bin=1111 gray=1000 -> recovered=1111 OK matches=16/16 PASSAll 16 codes round-trip exactly, so the log ends with
matches=16/16andPASS.Reduction-XOR one-liner form
The prefix chain can be unrolled into a closed form: each binary bit is the XOR of all Gray bits at or above its position,
bin[i] = ^(gray >> i). The unary^reduction operator XORs every bit of its operand together. This is functionally identical to the chain and often what a synthesizer infers anyway, but it reads as a single expression per bit.// gray2bin_xor: each binary bit is reduction-XOR of the gray bits from i up module gray2bin_xor #(parameter N=4)(input [N-1:0] gray, output [N-1:0] bin); genvar i; generate for (i = 0; i < N; i = i+1) begin : g // bin[i] = XOR of all gray bits at positions >= i assign bin[i] = ^(gray >> i); end endgenerate endmodule module tb; reg [3:0] bin_in; wire [3:0] gray; wire [3:0] bin_out; integer k, matches; assign gray = bin_in ^ (bin_in >> 1); gray2bin_xor #(.N(4)) dut (.gray(gray), .bin(bin_out)); initial begin matches = 0; for (k = 0; k < 16; k = k+1) begin bin_in = k[3:0]; #1; if (bin_out === bin_in) begin matches = matches + 1; $display("gray=%b -> bin=%b (expected %b) OK", gray, bin_out, bin_in); end else begin $display("gray=%b -> bin=%b (expected %b) MISMATCH", gray, bin_out, bin_in); end end $display("matches=%0d/16", matches); if (matches == 16) $display("PASS"); else $display("FAIL"); $finish; end endmodulegray=0000 -> bin=0000 (expected 0000) OK
gray=0001 -> bin=0001 (expected 0001) OK
gray=0011 -> bin=0010 (expected 0010) OK
gray=0010 -> bin=0011 (expected 0011) OK
gray=0110 -> bin=0100 (expected 0100) OK
gray=0111 -> bin=0101 (expected 0101) OK
gray=0101 -> bin=0110 (expected 0110) OK
gray=0100 -> bin=0111 (expected 0111) OK
gray=1100 -> bin=1000 (expected 1000) OK
gray=1101 -> bin=1001 (expected 1001) OK
gray=1111 -> bin=1010 (expected 1010) OK
gray=1110 -> bin=1011 (expected 1011) OK
gray=1010 -> bin=1100 (expected 1100) OK
gray=1011 -> bin=1101 (expected 1101) OK
gray=1001 -> bin=1110 (expected 1110) OK
gray=1000 -> bin=1111 (expected 1111) OK
matches=16/16
PASSSame result as the chain version:
matches=16/16thenPASS.How the conversion works
Gray and binary are related by a simple XOR rule. Going forward, binary to Gray is
G = B ^ (B >> 1): each Gray bit is the XOR of two adjacent binary bits. Going back, you cannot just undo one XOR locally because the information propagates. The MSB is unchanged (B[N-1] = G[N-1]), and each lower bit depends on the binary bit already recovered above it:B[i] = B[i+1] ^ G[i]. Expanding that recurrence shows every binary bit is the XOR of all Gray bits from that position up — which is the reduction-XOR form in the second example. The two designs are therefore the same logic written at different levels of abstraction.Synthesis notes
Both versions are pure combinational logic (
assignonly, no clock) and synthesize to a tree of XOR gates. The prefix-chain form literally describes a ripple of 2-input XORs, giving a logic depth of roughly N−1 gates on the LSB path. A good synthesis tool will balance this into an XOR tree of depth about log2(N), so for small N the critical path is tiny. The reduction-XOR form hands the whole tree to the tool up front. For any realistic width this converter is not a timing concern; it is the binary-to-Gray direction plus the synchronizer flops that matter in a real FIFO, not the decode.Common pitfalls
The biggest mistake is treating Gray-to-binary as a bitwise operation and writing
bin = gray ^ (gray >> 1)— that is the forward (binary-to-Gray) map and it does not invert itself. Another is getting the loop direction wrong in the chain:bin[i]must referencebin[i+1], which has to already be resolved, so think of the dependency as top-down even though continuous assignments settle regardless of textual order. Also mind the reduction operator:^grayreduces the whole vector, while^(gray >> i)reduces only the upper slice — the shift is what selects the right subset of bits for each output.Interview angle
Expect to be asked to write this on a whiteboard and to explain why the decode is not symmetric with the encode. Strong answers state both the recurrence
B[i] = B[i+1] ^ G[i]and the closed formB[i] = XOR of G[N-1..i], and mention the real-world reason Gray code exists: only one bit toggles per step, so sampling it across clock domains cannot produce a wildly wrong transient value. That single-bit-change property is the whole point, and it is why asynchronous FIFO read/write pointers are carried in Gray code and converted back to binary only after synchronization.Have questions about this topic?
Share your question in comments or talk to our mentor team for batch guidance.▶ Try it in the ChipXpert LabRun it in the LabOpen this design in the free in-browser simulator — edit, compile and see the waveform/log instantly.
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 Hi! Ask me about courses, fees, batches or discounts. ×ChipXpert AssistantAsk about courses, fees, batches and centres