Gray code is a binary code where each successive value differs from the previous value by only one bit. A Verilog binary to Gray converter turns an N-bit binary number into its Gray code with one XOR gate per bit: the most significant bit is copied, and every other Gray bit is the XOR of a binary bit and the bit above it.

8 min read | Beginner Level

What You'll Learn

  • How Gray code differs from binary, and the rule for converting between them
  • Write a parameterized binary to Gray converter in two ways
  • Why both versions synthesize into the same N-1 XOR gates
  • Verify the conversion exhaustively and avoid common conversion bugs

Binary and Gray Code

In binary counting, several bits can change between consecutive values, such as all four bits from 7 (0111) to 8 (1000). In Gray code, exactly one bit changes between any two consecutive values, including the wrap-around from the last value back to 0.

DecimalBinaryGray
000000000
100010001
200100011
300110010
401000110
501010111
601100101
701110100
810001100
910011101
1010101111
1110111110
1211001010
1311011011
1411101001
1511111000

The conversion rule for an N-bit value is:

gray[N-1] = bin[N-1]                  // MSB is copied
gray[i]   = bin[i+1] ^ bin[i]         // for i = N-2 down to 0

Each Gray bit is 1 when the binary bit and its upper neighbor differ. For example, binary 1011 gives Gray 1, 1^0 = 1, 0^1 = 1, 1^1 = 0, which is 1110.

Implementation #1

The first version applies the rule bit by bit, using a generate loop to create one assign statement for each bit except the MSB.


module bin2gray #(parameter N=4) ( input  [N-1:0] bin,
                                   output [N-1:0] gray);

  genvar i;
  generate
    for(i = 0; i < N-1; i = i + 1) begin : gen_xor   // One XOR per bit below the MSB
      assign gray[i] = bin[i] ^ bin[i+1];
    end
  endgenerate

  assign gray[N-1] = bin[N-1];                         // MSB copied unchanged
endmodule

The loop runs only to N-2, because bin[i+1] for the MSB would refer to a bit that does not exist. The MSB is handled separately.

Hardware Schematic

Schematic of the generate-loop binary to Gray converter: three XOR gates for a 4-bit input, with the MSB passed straight through

For N = 4, the schematic shows three 2-input XOR gates and a direct connection from bin[3] to gray[3]. All the XOR gates work in parallel, so the delay is one gate regardless of N.

Quick Check

Q: What is the Gray code of binary 8'b1100_0101, and how many XOR gates does an 8-bit converter use?

Show Answer

8'b1010_0111. Shifting right gives 0110_0010, and 1100_0101 ^ 0110_0010 = 1010_0111. An 8-bit converter uses 7 XOR gates, one for every bit except the MSB.

Where is Binary to Gray Conversion Used?

Asynchronous FIFOs convert their binary read and write pointers to Gray code before sending them across to the other clock domain. Since only one bit changes per increment, a pointer sampled at the wrong moment is always either the old or the new value, never a random one. See Verilog Gray Counter.

Implementation #2

The whole rule can also be written as one vector expression. Shifting bin right by one places each bit's upper neighbor in its position, with a 0 shifted into the MSB, so XORing the two produces every Gray bit at once.


module bin2gray #(parameter N=4) ( input  [N-1:0] bin,
                                   output [N-1:0] gray);

  // bin >> 1 lines up bin[i+1] with bin[i]; the shifted-in 0 leaves the MSB unchanged
  assign gray = bin ^ (bin >> 1);

endmodule

Hardware Schematic

Elaborated schematic of the expression-based binary to Gray converter, showing a shift operator block feeding an XOR

This schematic is the elaborated, pre-optimization view, so it shows a shift operator feeding a vector XOR. That shift block is not a shift register and does not cost any gates. Shifting by a constant amount only renames wires: (bin >> 1)[i] is simply bin[i+1], and its MSB is the constant 0. After optimization, gray[N-1] = bin[N-1] ^ 0 reduces to a wire, and the result is the same N-1 XOR gates as Implementation #1.

PPA Impact: Both implementations produce identical hardware after synthesis, with the same area, power and delay. Choose between them for readability: the one-line expression is shorter, while the generate loop makes the per-bit rule visible.

Absolute Position Encoders

Rotary and linear position encoders in motors and robot joints mark their positions in Gray code, so a sensor reading taken exactly on the boundary between two positions is off by at most one step. Controllers that generate expected encoder patterns, or test equipment that simulates an encoder, use a binary to Gray converter like this one to produce the code for a given position.

Testbench

The testbench applies all 16 values of a 4-bit input and prints each binary value with its Gray code. The output should match the table at the start of this article.


module tb;
  reg  [3:0] bin;
  wire [3:0] gray;
  integer    i;

  bin2gray #(.N(4)) u0 (.bin(bin), .gray(gray));

  initial begin
    for (i = 0; i < 16; i = i + 1) begin
      bin = i;
      #1;                                   // Let the combinational output settle
      $display("bin=%b gray=%b", bin, gray);
    end
  end
endmodule

Quick Check

Q: If you apply the same formula to a Gray code, g ^ (g >> 1), do you get the binary value back?

Show Answer

No. For Gray 1110 (binary 11), 1110 ^ 0111 = 1001, which is 9. Converting back needs each binary bit to be the XOR of all Gray bits from the MSB down to that bit: b[i] = b[i+1] ^ g[i]. That is a chain of XOR gates, and is covered in Verilog Gray Counter.

Low-Power Bus Encoding

Every bit that toggles on a long, heavily loaded bus costs dynamic power. For address buses that mostly step through consecutive addresses, such as instruction fetches or buffer reads, sending the address in Gray code toggles one wire per step instead of an average of two. The receiver converts it back to binary.

Common Beginner Mistakes

Mistake #1: Shifting in the wrong direction

Wrong: assign gray = bin ^ (bin << 1);
Right: assign gray = bin ^ (bin >> 1);

Why? Each Gray bit uses the binary bit above it. A left shift pairs each bit with the bit below it, which produces a code where several bits can change between consecutive values. Checking binary 1 and 2 catches it: the correct Gray codes are 0001 and 0011.

Mistake #2: Running the generate loop to N-1

Wrong: for (i = 0; i < N; i = i + 1) assign gray[i] = bin[i] ^ bin[i+1];
Right: Loop to i < N-1 and assign gray[N-1] = bin[N-1] separately

Why? In the last iteration, bin[i+1] is bin[N], which is outside the vector. Simulation reads it as x, so gray[N-1] becomes x, and the separate MSB assignment then gives gray[N-1] two drivers.

Mistake #3: Using an arithmetic shift on a signed value

Wrong: assign gray = bin ^ (bin >>> 1); with bin declared signed
Right: Use the logical shift >>, or keep bin unsigned

Why? >>> on a signed value copies the MSB into the vacated bit instead of shifting in 0. The Gray MSB then becomes bin[N-1] ^ bin[N-1] = 0 for every input, so half of the codes are wrong.

Mistake #4: Converting Gray back to binary with the same formula

Wrong: assign bin = gray ^ (gray >> 1);
Right: Build the XOR chain bin[i] = bin[i+1] ^ gray[i], starting from bin[N-1] = gray[N-1]

Why? The binary to Gray conversion is not its own inverse. Each binary bit depends on all the Gray bits above it, not just its neighbor, as the Quick Check above shows.

Practice Exercise

Challenge: Exhaustive Self-Checking Testbench

Write a testbench that checks bin2gray #(.N(4)) for all 16 inputs. For each pair of consecutive inputs, including 15 followed by 0, it must verify that the two Gray codes differ in exactly one bit. It should print an error for every failure and a pass or fail message at the end.

Hint

XOR the two Gray codes: the result has a 1 in every bit position that differs. Count the 1s with a small loop, since $countones is a SystemVerilog function, and check that the count is exactly 1.

Solution

module tb_check;
  reg  [3:0] bin;
  wire [3:0] gray;
  reg  [3:0] prev_gray, diff;
  integer    i, b, ones, errors;

  bin2gray #(.N(4)) u0 (.bin(bin), .gray(gray));

  initial begin
    errors = 0;
    bin = 4'd15;  #1  prev_gray = gray;      // Start from 15 to check the wrap to 0

    for (i = 0; i < 16; i = i + 1) begin
      bin = i;  #1;
      diff = gray ^ prev_gray;               // 1 in every bit that changed
      ones = 0;
      for (b = 0; b < 4; b = b + 1)
        ones = ones + diff[b];
      if (ones != 1) begin
        $display("ERROR: %0d -> %0d changes %0d bits", (i + 15) % 16, i, ones);
        errors = errors + 1;
      end
      prev_gray = gray;
    end

    if (errors == 0) $display("PASS: every step changes exactly one bit");
    else             $display("FAIL: %0d errors", errors);
  end
endmodule

With either implementation, the testbench prints PASS. Changing the design to use << instead of >> makes it report several errors immediately. Because the checks test the defining property of Gray code rather than a fixed table, the same testbench works for any N if the loop limits and the wrap-around start value are adjusted.

Summary

In this tutorial, you learned:

  • Gray code changes exactly one bit between consecutive values, including the wrap-around
  • The Gray MSB equals the binary MSB, and every other bit is bin[i+1] ^ bin[i]
  • The conversion can be written as a generate loop or as bin ^ (bin >> 1)
  • A shift by a constant is only wiring, so both versions synthesize into the same N-1 XOR gates
  • The conversion has a delay of one XOR gate for any width
  • Gray to binary conversion needs a chain of XORs and is not the same formula
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5