VERILOG DESIGNS · MODULE 28

Verilog Designs — Binary to Gray Code Conversion — VLSI Trainers
Verilog Designs · Module 28

Binary to Gray Code Conversion

Complete implementation of the Binary-to-Gray conversion circuit — data flow, behavioral, parameterised generate, and reverse Gray-to-Binary — with full truth table, XOR circuit diagram, and exhaustive testbench covering all 256 input values.

🔀 Introduction & Theory

The Gray code (also called the reflected binary code) is a binary numeral system where two successive values differ in only one bit. This property — called the unit distance property — makes it invaluable in digital systems where transitions between adjacent states must not cause transient errors from multiple bits changing simultaneously.

1️⃣
Unit Distance Property
Any two consecutive Gray code values differ in exactly one bit. Standard binary can differ in multiple bits between adjacent values.
🔄
Encoder Wheels
Gray code eliminates transient multi-bit glitches when reading mechanical position encoders — consecutive sectors differ by only one bit.
🔁
CDC FIFOs
Gray code is used for read/write pointers crossing clock domain boundaries — only one bit changes per count so CDC metastability is safe.
📐
XOR-only Circuit
Conversion uses only XOR gates — one per output bit. Extremely fast (single XOR delay) and area-efficient.
Why Gray code in Clock Domain Crossing FIFOs? In an asynchronous FIFO, the write pointer (in the write clock domain) must be read by the read clock domain. If the pointer is in binary, incrementing from 0111 to 1000 changes all 4 bits simultaneously — the sampling flip-flops in the read domain may read any of the 16 possible intermediate values. Gray code ensures only one bit changes per increment, so the sampled value is always either the old or new pointer — never a phantom intermediate value.

📐 Conversion Formula

The Binary-to-Gray conversion formula is beautifully simple — each Gray code bit is an XOR of two adjacent binary bits:

Binary → Gray Conversion Formula (N-bit)
gray[N-1] = bin[N-1]
gray[i] = bin[i+1] ^ bin[i]
MSB of Gray = MSB of Binary (unchanged)
Each other Gray bit = XOR of two adjacent binary bits
gray[3] = bin[3]
gray[2] = bin[3] ^ bin[2]
gray[1] = bin[2] ^ bin[1]
gray[0] = bin[1] ^ bin[0]
For 4-bit (N=4) example
Verilog one-liner: gray = (bin >> 1) ^ bin;
The one-liner explained: bin >> 1 shifts binary right by one position, aligning each bit with its upper neighbor. XORing with bin then computes: bit N = bin[N] ^ 0 (shifted-in zero) = bin[N] for MSB, and bit i = bin[i+1] ^ bin[i] for all others. This matches the formula exactly — all N bits computed in one expression, synthesizing to N-1 XOR gates.

📊 Truth Table (4-bit)

The complete 4-bit Binary-to-Gray conversion table. Note the highlighted rows at binary transitions where multiple binary bits change but only one Gray bit changes:

Decimal bin[3]bin[2]bin[1]bin[0] gray[3]gray[2]gray[1]gray[0] Changed bit
0 0000 0000
1 0001 0001gray[0]
2 0010 0011gray[1]
3 0011 0010gray[0]
4 0100 0110gray[2]
5 0101 0111gray[0]
6 0110 0101gray[1]
7 0111 0100gray[0]
8 1000 1100gray[3]
9 1001 1101gray[0]
101010 1111gray[1]
111011 1110gray[0]
121100 1010gray[2]
131101 1011gray[0]
141110 1001gray[1]
151111 1000gray[0]

Every row transition changes exactly one Gray code bit (highlighted in the “Changed bit” column) — this is the unit distance property. Binary transitions like 7→8 change four binary bits but only one Gray bit (gray[3]).

🔌 Circuit Diagram

Fig 1 — 4-bit Binary to Gray: MSB passes through, others use XOR of adjacent binary bits
bin[3] bin[2] bin[1] bin[0] pass-through (no XOR) XOR XOR XOR gray[3] = bin[3] gray[2] = bin[3]⊕bin[2] gray[1] = bin[2]⊕bin[1] gray[0] = bin[1]⊕bin[0] 3 XOR gates · Single-gate delay · O(1) critical path

XOR Chain Visualisation

Example: binary 1011 (decimal 11) → gray 1110:

bin[3]=1
MSB
=
gray[3]=1
pass-through
bin[3]=1
bin[2]=0
=
gray[2]=1
bin[2]=0
bin[1]=1
=
gray[1]=1
bin[1]=1
bin[0]=1
LSB
=
gray[0]=0
LSB

Binary 1011 → Gray 1110. Verify: 1⊕0=1, 0⊕1=1, 1⊕1=0. ✅

Data Flow Implementation

The most concise implementation — a single assign statement using the right-shift XOR idiom. Synthesis produces exactly N-1 XOR gates.

1
bin2gray_dataflow
Data flow — single assign · (bin >> 1) ^ bin · N-1 XOR gates
⚡ Data Flow
// ============================================================
// Module   : bin2gray_dataflow
// Method   : Data Flow — one-liner XOR formula
// Formula  : gray = (bin >> 1) ^ bin
// Synthesis: N-1 XOR gates, single-gate critical path
// ============================================================
`timescale 1ns/1ps
`default_nettype none

module bin2gray_dataflow #(
  parameter N = 8   // configurable width
) (
  input  [N-1:0] bin,
  output [N-1:0] gray
);
  // The right-shift by 1 introduces a 0 at position N-1,
  // making gray[N-1] = bin[N-1] ^ 0 = bin[N-1] (MSB pass-through)
  // and gray[i] = bin[i+1] ^ bin[i] for all other bits
  assign gray = (bin >> 1) ^ bin;

endmodule

`default_nettype wire
Why this works for all N without modification: The right-shift fills the vacated MSB position with 0. So for position N-1: (bin >> 1)[N-1] = 0, giving gray[N-1] = 0 ^ bin[N-1] = bin[N-1]. For all lower positions: (bin >> 1)[i] = bin[i+1], giving gray[i] = bin[i+1] ^ bin[i]. This exactly matches the formula for all N — no special-casing needed.

🧠 Behavioral Implementation

An explicit behavioral description using a for loop in an always @(*) block. Each iteration assigns one Gray code bit, making the formula visible bit by bit.

2
bin2gray_behavioral
Behavioral — for-loop over bits · Explicit XOR per bit · Most readable formula
🧠 Behavioral
// ============================================================
// Module   : bin2gray_behavioral
// Method   : Behavioral — explicit for-loop XOR per bit
// Clarity  : Directly expresses the formula bit by bit
// ============================================================
`timescale 1ns/1ps
`default_nettype none

module bin2gray_behavioral #(
  parameter N = 8
) (
  input      [N-1:0] bin,
  output reg [N-1:0] gray
);
  integer i;

  always @(*) begin
    // MSB: Gray code MSB = Binary MSB (no XOR needed)
    gray[N-1] = bin[N-1];

    // All other bits: XOR adjacent binary bits
    for (i = 0; i < N-1; i = i + 1)
      gray[i] = bin[i+1] ^ bin[i];
  end

endmodule

`default_nettype wire

🔧 Parameterised Generate Implementation

The generate block creates explicit named XOR instances for each bit position. This makes the hardware structure visible in the source, creates individually named nets for simulation probing, and is the most explicit structural description.

3
bin2gray_generate
Generate block — named XOR instances · Structural clarity · Simulation-visible nets
🔧 Generate
// ============================================================
// Module   : bin2gray_generate
// Method   : Generate loop — explicit XOR primitives per bit
// Benefit  : Named instances visible in simulator hierarchy
//            xor_bit[0], xor_bit[1], ... xor_bit[N-2]
// ============================================================
`timescale 1ns/1ps
`default_nettype none

module bin2gray_generate #(
  parameter N = 8
) (
  input  [N-1:0] bin,
  output [N-1:0] gray
);
  // MSB: direct assignment (no gate)
  assign gray[N-1] = bin[N-1];

  // Generate N-1 XOR gates, one per remaining output bit
  genvar i;
  generate
    for (i = 0; i < N-1; i = i + 1) begin : xor_bit
      // Creates instances: xor_bit[0].g, xor_bit[1].g ... xor_bit[N-2].g
      xor g (gray[i], bin[i+1], bin[i]);
    end
  endgenerate

endmodule

`default_nettype wire

🔁 Reverse — Gray to Binary Conversion

The inverse operation recovers binary from Gray code. Unlike the forward conversion which is XOR-of-adjacent, the reverse requires a cascaded XOR — each binary bit depends on all higher-order binary bits, making it inherently sequential in logic (though still purely combinational).

Fig 2 — Gray to Binary: cascaded (not adjacent-pair) XOR
// Gray-to-Binary formula:
// bin[N-1] = gray[N-1]
// bin[i]   = bin[i+1] ^ gray[i]   ← depends on BINARY bit above, not gray bit above

// Example: gray = 1110 → binary = ?
//   bin[3] = gray[3] = 1
//   bin[2] = bin[3] ^ gray[2] = 1 ^ 1 = 0
//   bin[1] = bin[2] ^ gray[1] = 0 ^ 1 = 1
//   bin[0] = bin[1] ^ gray[0] = 1 ^ 0 = 1
//   → binary = 1011 = 11 ✓ (matches the forward example)
4
gray2bin
Gray to Binary — cascaded XOR · Each bit depends on binary bits above
🔁 Gray→Binary
// ============================================================
// Module   : gray2bin
// Function : Reverse — Gray code back to binary
// Key diff : Uses cascaded XOR (binary[i] depends on binary[i+1])
//            NOT the simple adjacent-pair formula of forward conversion
// Critical : O(N) logic depth — each bit depends on higher bits
//            Can be pipelined for high-speed applications
// ============================================================
`timescale 1ns/1ps
`default_nettype none

module gray2bin #(
  parameter N = 8
) (
  input      [N-1:0] gray,
  output reg [N-1:0] bin
);
  integer i;

  always @(*) begin
    // MSB: binary MSB = gray MSB (same starting point)
    bin[N-1] = gray[N-1];

    // Each bit: XOR with the BINARY bit above (not gray bit above!)
    // This creates a dependency chain — must go MSB to LSB
    for (i = N-2; i >= 0; i = i - 1)
      bin[i] = bin[i+1] ^ gray[i];
  end

endmodule

`default_nettype wire

🔵 Forward: Binary → Gray

// Adjacent pair XOR — parallel
gray[N-1] = bin[N-1];
gray[i]   = bin[i+1] ^ bin[i];
// Depth: O(1) — single XOR

🟠 Reverse: Gray → Binary

// Cascaded XOR — serial dependency
bin[N-1] = gray[N-1];
bin[i]   = bin[i+1] ^ gray[i];
// Depth: O(N) — XOR chain

🧪 Comprehensive Testbench

The testbench exhaustively tests all three forward converters and the reverse converter across all 256 possible 8-bit input values. It verifies both the forward conversion result and round-trip correctness (binary → gray → binary = original).

TB
bin2gray_tb
All 256 values · Three forward implementations · Gray→Binary round-trip · Unit-distance check
🧪 Testbench
// ============================================================
// Testbench  : bin2gray_tb
// Coverage   : All 256 8-bit input values (exhaustive)
// Checks     :
//   1. All three forward converters match reference
//   2. Round-trip: gray2bin(bin2gray(x)) == x
//   3. Unit-distance: consecutive gray codes differ by 1 bit
// ============================================================
`timescale 1ns/1ps
`default_nettype none

module bin2gray_tb;

  // ── Shared stimulus ────────────────────────────────────────
  reg  [7:0] bin_in;

  // ── Outputs from three forward implementations ─────────────
  wire [7:0] gray_df, gray_beh, gray_gen;

  // ── Gray-to-Binary reverse ─────────────────────────────────
  wire [7:0] roundtrip;   // gray2bin(gray_df) — should equal bin_in

  // ── Instantiate all modules ────────────────────────────────
  bin2gray_dataflow  dut_df  (.bin(bin_in),.gray(gray_df));
  bin2gray_behavioral dut_beh(.bin(bin_in),.gray(gray_beh));
  bin2gray_generate  dut_gen (.bin(bin_in),.gray(gray_gen));
  gray2bin           dut_rev (.gray(gray_df),.bin(roundtrip));

  initial begin
    $dumpfile("bin2gray.vcd");
    $dumpvars(0, bin2gray_tb);
  end

  integer pass_cnt=0, fail_cnt=0, test_num=0;
  integer v;
  reg [7:0] exp_gray, prev_gray;
  reg [7:0] diff;
  integer    popcount;

  initial begin
    $display("\n=============================================================");
    $display("  Binary-to-Gray Testbench — Exhaustive 256-Value Coverage");
    $display("=============================================================");
    $display("  bin      | gray(df)  | gray(beh) | gray(gen) | round-trip");
    $display("  -----------------------------------------------------------");

    prev_gray = 8'h00;

    for (v=0; v<=255; v=v+1) begin
      bin_in   = v[7:0];
      #2;

      // Reference: compute expected gray with formula
      exp_gray = (bin_in >> 1) ^ bin_in;

      // For v>0, check unit-distance with previous gray value
      if (v > 0) begin
        diff     = gray_df ^ prev_gray;   // bits that differ
        popcount = diff[0]+diff[1]+diff[2]+diff[3]+
                   diff[4]+diff[5]+diff[6]+diff[7];
        if (popcount !== 1) begin
          $display("  UNIT-DIST FAIL: v=%0d gray=%08b prev=%08b diff=%0d bits",
                   v, gray_df, prev_gray, popcount);
          fail_cnt++;
        end
      end
      prev_gray = gray_df;

      test_num++;
      if (gray_df===exp_gray  &&
          gray_beh===exp_gray &&
          gray_gen===exp_gray &&
          roundtrip===bin_in) begin
        if (v % 32 == 0)
          $display("  PASS 0x%02h=%08b → gray=%08b round-trip=%02h",
                   bin_in,bin_in,gray_df,roundtrip);
        pass_cnt++;
      end else begin
        $display("  FAIL 0x%02h | df=%08b beh=%08b gen=%08b exp=%08b rt=%08b",
                 bin_in,gray_df,gray_beh,gray_gen,exp_gray,roundtrip);
        fail_cnt++;
      end
    end

    // ── Key corner cases printed explicitly ─────────────────
    $display("\n  --- Key corner cases ---");
    for (v=0; v<=15; v=v+1) begin
      bin_in = v; #2;
      $display("  bin=%0d (%04b) → gray=%04b",
               v, bin_in[3:0], gray_df[3:0]);
    end

    $display("\n=============================================================");
    $display("  RESULTS: %0d / %0d PASS  |  %0d FAIL",
             pass_cnt,test_num,fail_cnt);
    $display("=============================================================");
    if(fail_cnt==0)
      $display("  ✅ ALL TESTS PASSED — unit-distance and round-trip verified\n");
    else
      $fatal(1,"  ❌ %0d FAILURE(S)\n",fail_cnt);
    #10; $finish;
  end

endmodule
`default_nettype wire

Three Verification Checks

CheckMethodValidates
Forward correctness All three implementations vs reference (bin>>1)^bin All three converters produce the same correct Gray code
Round-trip gray2bin(bin2gray(x)) === x for all 256 values The reverse converter correctly inverts the forward converter
Unit-distance popcount(gray[v] XOR gray[v-1]) == 1 for all v=1..255 Confirms the fundamental Gray code property holds for all transitions

📈 Simulation Waveform

The waveform shows the binary input sweeping from 0 to 15, with the Gray code output changing exactly one bit per step. The unit-distance property is visually apparent — Gray transitions are simpler than binary.

Fig 3 — Binary vs Gray: consecutive values, single-bit Gray transitions
bin[3:0] bin[3] bin[2] bin[1] bin[0] gray[3:0] 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111 0000 0001 0011 0010 0110 0111 0101 0100 1100 1101 1111 1110 1010 1011 1001 1000

💻 Simulation Console Output

============================================================= Binary-to-Gray Testbench — Exhaustive 256-Value Coverage ============================================================= bin | gray(df) | gray(beh) | gray(gen) | round-trip ———————————————————– PASS 0x00=00000000 → gray=00000000 round-trip=00 PASS 0x20=00100000 → gray=00110000 round-trip=20 PASS 0x40=01000000 → gray=01100000 round-trip=40 PASS 0x60=01100000 → gray=01010000 round-trip=60 PASS 0x80=10000000 → gray=11000000 round-trip=80 PASS 0xA0=10100000 → gray=11110000 round-trip=A0 PASS 0xC0=11000000 → gray=10100000 round-trip=C0 PASS 0xE0=11100000 → gray=10010000 round-trip=E0 … (256 total, all pass) … PASS 0xFF=11111111 → gray=10000000 round-trip=FF — Key corner cases (first 16 values) — bin=0 (0000) → gray=0000 bin=1 (0001) → gray=0001 bin=2 (0010) → gray=0011 bin=3 (0011) → gray=0010 bin=4 (0100) → gray=0110 bin=5 (0101) → gray=0111 bin=6 (0110) → gray=0101 bin=7 (0111) → gray=0100 bin=8 (1000) → gray=1100 ← 7→8: binary changes 4 bits, gray changes 1! bin=9 (1001) → gray=1101 bin=10 (1010) → gray=1111 bin=11 (1011) → gray=1110 bin=12 (1100) → gray=1010 bin=13 (1101) → gray=1011 bin=14 (1110) → gray=1001 bin=15 (1111) → gray=1000 ============================================================= RESULTS: 256 / 256 PASS | 0 FAIL ============================================================= ✅ ALL TESTS PASSED — unit-distance and round-trip verified

How to Run

Compile all four modules and the testbench
# ── Icarus Verilog ────────────────────────────────────────────
iverilog -o gray_sim \
    bin2gray_dataflow.v   \
    bin2gray_behavioral.v \
    bin2gray_generate.v   \
    gray2bin.v            \
    bin2gray_tb.v
vvp gray_sim
gtkwave bin2gray.vcd

# ── ModelSim ──────────────────────────────────────────────────
vlog bin2gray_dataflow.v bin2gray_behavioral.v \
     bin2gray_generate.v gray2bin.v bin2gray_tb.v
vsim -c bin2gray_tb -do "run -all; quit -f"

🔬 Design Analysis

Implementation Comparison

ImplementationCode linesCritical pathGatesBest for
Data Flow ~141 XOR (O(1))N-1 XOR Production RTL — minimal, synthesizes to optimal hardware
Behavioral ~181 XOR (O(1))N-1 XOR Teaching — formula most clearly expressed bit-by-bit
Generate ~201 XOR (O(1))N-1 XOR Library cells — named instances visible in hierarchy
Gray→Binary ~16N-1 XOR (O(N))N-1 XOR Reverse decode — note the longer critical path

🟢 Binary→Gray: O(1) depth

All N-1 XOR gates operate in parallel — each depends only on two binary input bits, not on other output bits. The entire conversion completes in one XOR gate delay regardless of N. This is why Binary→Gray is so efficient.

🟠 Gray→Binary: O(N) depth

Each binary output bit depends on the binary bit above it — creating a serial dependency chain. For N=8, the LSB depends on 7 XOR delays. For high-speed Gray→Binary, pipeline or use a dedicated look-up table to break the chain.

Gray code in asynchronous FIFOs: The standard asynchronous FIFO design (Clifford Cummings, 2002) uses Gray-coded read and write pointers for clock domain crossing. The Binary→Gray converter (1 XOR delay) is used on the producing side before the pointer is registered into the other domain’s CDC flip-flops. The Gray→Binary converter is used after synchronization on the consuming side. Since Gray code changes only one bit per count, the CDC flip-flops can only sample either the old or new pointer value — never an invalid intermediate — eliminating multi-bit metastability hazards.
Parameterisation: All four modules accept parameter N. To build a 16-bit Gray converter: bin2gray_dataflow #(.N(16)) u1 (.bin(bin16), .gray(gray16));. The data flow implementation using (bin >> 1) ^ bin automatically scales to any width — no other changes needed. The generate implementation creates exactly N-1 named XOR instances for the specified width.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top