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
endmodule
bin=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
PASS

All 16 codes round-trip exactly, so the log ends with matches=16/16 and PASS.

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
endmodule
gray=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
PASS

Same result as the chain version: matches=16/16 then PASS.

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 (assign only, 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 reference bin[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: ^gray reduces 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 form B[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 Lab

Open this design in the free in-browser simulator — edit, compile and see the waveform/log instantly.

Run it in the Lab

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
Next batch starts 18th November 2026
Start your VLSI career with ChipXpert

Live online and classroom batches in Hyderabad & Bengaluru. Fill in your details and a counsellor will call you back.

  • Real EDA tools in your browser: industry-standard EDAReal EDA tools in your browser
  • Recorded sessions on the elearn portalRecorded sessions
  • Placement assistance: resume, mock interviewsPlacement assistance
  • Merit scholarship up to 60% · EMI optionsScholarship up to 60%
Hi! Ask me about courses, fees, batches or discounts. ×
BENGALURU
HYDERABAD