Binary to Gray Code Verilog Code

Gray code is an ordering of binary numbers where any two adjacent values differ in exactly one bit. That single-bit property is why it shows up in rotary encoders, Karnaugh maps, FSM state encoding, and clock-domain-crossing FIFOs. The good news for RTL designers is that converting plain binary to Gray code needs no adders and no state — a single XOR expression does it. This page gives you production-ready binary to gray code Verilog you can paste into a simulator today, a width-parameterized module, and a testbench that prints the full binary→gray mapping so you can eyeball the result.

The one-line conversion rule

For an n-bit binary value bin, the Gray code is:

gray = bin ^ (bin >> 1);

The most-significant bit passes through unchanged (it is XORed with a shifted-in zero), and every other Gray bit is the XOR of two neighbouring binary bits: gray[i] = bin[i] ^ bin[i+1]. The right-shift lines bin[i+1] up under bin[i], so the whole vector converts in one combinational operation. No clock, no reset, purely wires.

Parameterized binary-to-gray converter + testbench

Here is the complete design. The module is parameterized on WIDTH so the same file works for a 4-bit encoder or a 32-bit FIFO pointer. The testbench sweeps every value and prints the mapping.

// bin2gray.v - parameterized binary-to-gray converter (combinational XOR)
module bin2gray #(
    parameter WIDTH = 4
)(
    input  wire [WIDTH-1:0] bin,
    output wire [WIDTH-1:0] gray
);
    // Gray = Binary XOR (Binary >> 1)
    assign gray = bin ^ (bin >> 1);
endmodule

// tb_bin2gray.v - sweep every 4-bit code and print the mapping
module tb_bin2gray;
    localparam WIDTH = 4;
    reg  [WIDTH-1:0] bin;
    wire [WIDTH-1:0] gray;
    integer i;

    bin2gray #(.WIDTH(WIDTH)) dut (.bin(bin), .gray(gray));

    initial begin
        $display("BIN  -> GRAY  (WIDTH=%0d)", WIDTH);
        $display("---------------------------");
        for (i = 0; i < (1 << WIDTH); i = i + 1) begin
            bin = i[WIDTH-1:0];
            #1; // let the combinational logic settle
            $display("%b -> %b   (%0d -> %0d)", bin, gray, bin, gray);
        end
        $finish;
    end
endmodule
BIN  -> GRAY  (WIDTH=4)
---------------------------
0000 -> 0000   (0 -> 0)
0001 -> 0001   (1 -> 1)
0010 -> 0011   (2 -> 3)
0011 -> 0010   (3 -> 2)
0100 -> 0110   (4 -> 6)
0101 -> 0111   (5 -> 7)
0110 -> 0101   (6 -> 5)
0111 -> 0100   (7 -> 4)
1000 -> 1100   (8 -> 12)
1001 -> 1101   (9 -> 13)
1010 -> 1111   (10 -> 15)
1011 -> 1110   (11 -> 14)
1100 -> 1010   (12 -> 10)
1101 -> 1011   (13 -> 11)
1110 -> 1001   (14 -> 9)
1111 -> 1000   (15 -> 8)
/var/folders/l0/gs5hnkm56g54bmxg32r6nnwr0000gn/T/tmpou89u4_m/bin2gray.v:29: $finish called at 16 (1s)

Line-by-line walkthrough

  • module bin2gray #( parameter WIDTH = 4 ) — the #(...) block declares a parameter with a default. A caller can override it per instance, so one source file scales to any bus width.
  • input wire [WIDTH-1:0] bin / output wire [WIDTH-1:0] gray — both ports are WIDTH bits wide. The output is a wire because it is driven by a continuous assignment, not a procedural block.
  • assign gray = bin ^ (bin >> 1); — the entire converter. bin >> 1 is a logical right shift that pads the vacated MSB with zero, and ^ is the bitwise XOR. This synthesizes to one XOR gate per bit.
  • In the testbench, bin2gray #(.WIDTH(WIDTH)) dut (...) instantiates the design with named port connections — the clearest, least error-prone style.
  • The for loop counts from 0 to 2^WIDTH - 1. (1 << WIDTH) computes 16 for a 4-bit sweep; i[WIDTH-1:0] slices the integer down to the bus width before driving it.
  • #1; advances simulation time by one unit so the combinational output settles before $display samples it. Without a delay the loop would race the assignment.
  • $display with %b prints binary and %0d prints decimal with no padding. $finish ends the simulation cleanly.

Build and run it with: iverilog -g2005 -o sim bin2gray.v && vvp sim. Notice in the log how codes 2 and 3 map to 3 and 2 — and that every row differs from its neighbour by a single bit.

Reusing the module at a different width

Because the width lives in a parameter, you reuse the exact same converter for an 8-bit value by overriding WIDTH at instantiation. This testbench checks a few landmark codes and confirms the single-bit-change property holds at the byte boundary.

// bin2gray.v - same converter, instantiated at WIDTH=8
module bin2gray #(
    parameter WIDTH = 4
)(
    input  wire [WIDTH-1:0] bin,
    output wire [WIDTH-1:0] gray
);
    assign gray = bin ^ (bin >> 1);
endmodule

// tb_bin2gray_wide.v - override WIDTH to 8, check a few landmark values
module tb_bin2gray_wide;
    localparam WIDTH = 8;
    reg  [WIDTH-1:0] bin;
    wire [WIDTH-1:0] gray;

    bin2gray #(.WIDTH(WIDTH)) dut (.bin(bin), .gray(gray));

    task show;
        input [WIDTH-1:0] value;
        begin
            bin = value;
            #1;
            $display("%3d : %b -> %b", bin, bin, gray);
        end
    endtask

    initial begin
        $display("8-bit binary -> gray (adjacent values differ by 1 bit)");
        $display("-----------------------------------------------------");
        show(8'd0);
        show(8'd1);
        show(8'd2);
        show(8'd3);
        show(8'd127);
        show(8'd128);
        show(8'd255);
        $finish;
    end
endmodule
8-bit binary -> gray (adjacent values differ by 1 bit)
-----------------------------------------------------
  0 : 00000000 -> 00000000
  1 : 00000001 -> 00000001
  2 : 00000010 -> 00000011
  3 : 00000011 -> 00000010
127 : 01111111 -> 01000000
128 : 10000000 -> 11000000
255 : 11111111 -> 10000000
/var/folders/l0/gs5hnkm56g54bmxg32r6nnwr0000gn/T/tmpl6za7g0z/bin2gray_wide.v:38: $finish called at 7 (1s)

Look at the jump from 127 to 128 in the log: binary flips eight bits, but the Gray code changes only one. That is the whole reason Gray code is safe for asynchronous sampling.

Proving it with a round-trip check

A conversion is only trustworthy if it is reversible. Gray-to-binary is a running XOR: bin[i] = bin[i+1] ^ gray[i], starting from the MSB. The testbench below feeds binary through the encoder and back through the decoder, then flags any mismatch — a self-checking test you can drop into regression.

// bin2gray.v + gray2bin.v - converter and its inverse, used to self-check
module bin2gray #(
    parameter WIDTH = 4
)(
    input  wire [WIDTH-1:0] bin,
    output wire [WIDTH-1:0] gray
);
    assign gray = bin ^ (bin >> 1);
endmodule

module gray2bin #(
    parameter WIDTH = 4
)(
    input  wire [WIDTH-1:0] gray,
    output reg  [WIDTH-1:0] bin
);
    integer k;
    always @(*) begin
        bin[WIDTH-1] = gray[WIDTH-1];
        for (k = WIDTH-2; k >= 0; k = k - 1)
            bin[k] = bin[k+1] ^ gray[k];
    end
endmodule

// tb_roundtrip.v - bin -> gray -> bin must return the original value
module tb_roundtrip;
    localparam WIDTH = 4;
    reg  [WIDTH-1:0] bin;
    wire [WIDTH-1:0] gray;
    wire [WIDTH-1:0] back;
    integer i, errors;

    bin2gray #(.WIDTH(WIDTH)) enc (.bin(bin),  .gray(gray));
    gray2bin #(.WIDTH(WIDTH)) dec (.gray(gray), .bin(back));

    initial begin
        errors = 0;
        $display("BIN  -> GRAY -> BIN   status");
        $display("----------------------------");
        for (i = 0; i < (1 << WIDTH); i = i + 1) begin
            bin = i[WIDTH-1:0];
            #1;
            if (back === bin)
                $display("%b -> %b -> %b   OK", bin, gray, back);
            else begin
                $display("%b -> %b -> %b   MISMATCH", bin, gray, back);
                errors = errors + 1;
            end
        end
        $display("----------------------------");
        $display("Round-trip errors: %0d", errors);
        $finish;
    end
endmodule
BIN  -> GRAY -> BIN   status
----------------------------
0000 -> 0000 -> 0000   OK
0001 -> 0001 -> 0001   OK
0010 -> 0011 -> 0010   OK
0011 -> 0010 -> 0011   OK
0100 -> 0110 -> 0100   OK
0101 -> 0111 -> 0101   OK
0110 -> 0101 -> 0110   OK
0111 -> 0100 -> 0111   OK
1000 -> 1100 -> 1000   OK
1001 -> 1101 -> 1001   OK
1010 -> 1111 -> 1010   OK
1011 -> 1110 -> 1011   OK
1100 -> 1010 -> 1100   OK
1101 -> 1011 -> 1101   OK
1110 -> 1001 -> 1110   OK
1111 -> 1000 -> 1111   OK
----------------------------
Round-trip errors: 0
/var/folders/l0/gs5hnkm56g54bmxg32r6nnwr0000gn/T/tmpdpuowzr5/roundtrip.v:52: $finish called at 16 (1s)

Common mistakes

  • Using an arithmetic or signed shift. With unsigned wire/reg vectors, >> is a logical shift and pads with zero — exactly what you want. If you make the operand signed, >>> would replicate the sign bit and corrupt the MSB. Keep the ports unsigned.
  • Driving a wire output from an always block (or an assign output declared reg). The combinational version uses assign + wire; the gray2bin loop uses always @(*) + reg. Mixing them is a compile error.
  • Forgetting a settle delay in the testbench. Sampling the output in the same time step you drive the input can read the stale value. A #1 (or a clocked sample) fixes it.
  • Hard-coding the width. Writing bin[3:0] ^ {1'b0, bin[3:1]} works but locks you to 4 bits. The parameterized bin ^ (bin >> 1) is width-agnostic and far easier to maintain.
  • Confusing the two directions. Binary→Gray is a single XOR; Gray→Binary is a cumulative XOR chain. They are not the same expression.

Quick check

What single Verilog expression converts binary to Gray code?

gray = bin ^ (bin >> 1); — each Gray bit is the XOR of a binary bit and its left neighbour, with the MSB passing through unchanged.

Why does the MSB of the Gray code equal the MSB of the binary input?

The right shift pads the top position with 0, so gray[MSB] = bin[MSB] ^ 0 = bin[MSB]. The most-significant bit is never altered.

For 4-bit binary 0100 (decimal 4), what is the Gray code?

0110. Compute 0100 ^ 0010 = 0110 (decimal 6), which matches the simulation log.

Is the converter combinational or sequential, and what does that imply for synthesis?

Combinational — it is pure XOR logic with no clock or reset, so it synthesizes to one XOR gate per bit and introduces only gate-delay latency, no register stages.

Have questions about this topic?
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
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