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.
| Decimal | Binary | Gray |
|---|---|---|
| 0 | 0000 | 0000 |
| 1 | 0001 | 0001 |
| 2 | 0010 | 0011 |
| 3 | 0011 | 0010 |
| 4 | 0100 | 0110 |
| 5 | 0101 | 0111 |
| 6 | 0110 | 0101 |
| 7 | 0111 | 0100 |
| 8 | 1000 | 1100 |
| 9 | 1001 | 1101 |
| 10 | 1010 | 1111 |
| 11 | 1011 | 1110 |
| 12 | 1100 | 1010 |
| 13 | 1101 | 1011 |
| 14 | 1110 | 1001 |
| 15 | 1111 | 1000 |
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

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

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