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.
📐 Conversion Formula
The Binary-to-Gray conversion formula is beautifully simple — each Gray code bit is an XOR of two adjacent binary bits:
gray[i] = bin[i+1] ^ bin[i]
Each other Gray bit = XOR of two adjacent binary bits
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 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | — |
| 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | gray[0] |
| 2 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 1 | gray[1] |
| 3 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 0 | gray[0] |
| 4 | 0 | 1 | 0 | 0 | 0 | 1 | 1 | 0 | gray[2] |
| 5 | 0 | 1 | 0 | 1 | 0 | 1 | 1 | 1 | gray[0] |
| 6 | 0 | 1 | 1 | 0 | 0 | 1 | 0 | 1 | gray[1] |
| 7 | 0 | 1 | 1 | 1 | 0 | 1 | 0 | 0 | gray[0] |
| 8 | 1 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | gray[3] |
| 9 | 1 | 0 | 0 | 1 | 1 | 1 | 0 | 1 | gray[0] |
| 10 | 1 | 0 | 1 | 0 | 1 | 1 | 1 | 1 | gray[1] |
| 11 | 1 | 0 | 1 | 1 | 1 | 1 | 1 | 0 | gray[0] |
| 12 | 1 | 1 | 0 | 0 | 1 | 0 | 1 | 0 | gray[2] |
| 13 | 1 | 1 | 0 | 1 | 1 | 0 | 1 | 1 | gray[0] |
| 14 | 1 | 1 | 1 | 0 | 1 | 0 | 0 | 1 | gray[1] |
| 15 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | gray[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
XOR Chain Visualisation
Example: binary 1011 (decimal 11) → gray 1110:
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.
// ============================================================ // 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
(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.
// ============================================================ // 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.
// ============================================================ // 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).
// 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)
// ============================================================ // 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).
// ============================================================ // 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
| Check | Method | Validates |
|---|---|---|
| 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.
💻 Simulation Console Output
How to Run
# ── 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
| Implementation | Code lines | Critical path | Gates | Best for |
|---|---|---|---|---|
| Data Flow | ~14 | 1 XOR (O(1)) | N-1 XOR | Production RTL — minimal, synthesizes to optimal hardware |
| Behavioral | ~18 | 1 XOR (O(1)) | N-1 XOR | Teaching — formula most clearly expressed bit-by-bit |
| Generate | ~20 | 1 XOR (O(1)) | N-1 XOR | Library cells — named instances visible in hierarchy |
| Gray→Binary | ~16 | N-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.
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.
