← Back to blogBinary encoding

Gray Code Explained: Why Rotary Encoders Use It

Gray code gives adjacent values a one-bit Hamming distance. Learn its conversion formulas, encoder benefits, and the CDC constraints an asynchronous FIFO still needs.

DennyDenny
7 min read
Chalkboard feature illustration for “Gray Code Explained: Why Rotary Encoders Use It”

TL;DR: In reflected binary Gray code, adjacent values differ in one bit. That property limits ambiguity at a correctly constructed position boundary and is useful for asynchronous-FIFO pointers. It does not eliminate metastability, sensor faults, combinational glitches, or the need for CDC timing constraints.

A standard 4-bit binary counter advancing from 01110111 to 10001000 flips all four bits at once. In an idealized timing diagram those bits change simultaneously. In real silicon — with mismatched gate delays, slewing edges, and noisy supply rails — they change in some unspecified order, producing a brief flicker of intermediate values like 11111111 or 00000000 on the bus. For a CPU register that’s harmless: the value is sampled by a clock edge after everything settles. For a sensor read by an asynchronous observer, those glitches are real readings.

Gray code, also called reflected binary code, changes the boundary condition: adjacent code words differ in one bit, so a properly aligned observer does not have to resolve several intended bit transitions at once. The one transitioning bit can still be ambiguous, noisy, or metastable, so Gray code reduces a class of error rather than making the interface infallible.

Why Does Single-Bit Adjacency Matter?

Consider a rotary encoder — the optical or magnetic disc that tells a motor controller what angle a shaft is at. The disc has concentric tracks, each track contributing one bit of the position word, read by a sensor at a fixed angle. As the disc rotates, the sensor sees a sequence of binary values.

If the tracks are encoded in straight binary, the boundary between position 33 (011011) and position 44 (100100) requires three tracks to change state at the same nominal angle. Sensor placement, track geometry, and threshold differences mean those transitions need not be observed simultaneously. The controller can briefly decode an intermediate word far from either adjacent position.

Encoding the same disc in Gray code makes every angular boundary a single-bit transition:

PositionBinaryGray
0000000
1001001
2010011
3011010
4100110
5101111
6110101
7111100

Look at the Gray column. Between any two adjacent rows exactly one bit flips. Between row 7 and row 0, exactly one bit flips, so this sequence is cyclic and unit-distance. Under the intended one-boundary-at-a-time model, threshold uncertainty is confined to the old or new adjacent code. Misalignment across multiple boundaries, contamination, damaged tracks, electrical noise, or decoding a metastable sample can still produce an error.

The 4-Bit Gray Code Table

The pattern extends naturally:

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

The “reflected” name comes from the construction rule: to build the (n+1)(n+1)-bit code, take the nn-bit code, mirror it vertically, prepend a 00 to the original half and a 11 to the mirrored half. Mirroring guarantees the boundary between halves is a single-bit transition (the new MSB).

Binary-to-Gray Conversion

The conversion from binary bb to Gray gg is one of the cleanest formulas in digital logic:

gi=bi⊕bi+1g_i = b_i \oplus b_{i+1}

with bn=0b_n = 0 for the MSB. As a single expression:

g=b⊕(b≫1)g = b \oplus (b \gg 1)

In hardware, each Gray output is one XOR of adjacent binary bits; there is no carry chain, and the ideal logic depth is one XOR stage. That does not guarantee a glitch-free combinational output while several bits of a binary counter are physically changing. Register the converted Gray value before treating it as a CDC signal. To see why XOR is the right operator, revisit the XOR gate’s role as a difference detector.

Worked example. Convert binary 10111011 to Gray.

  • g3=b3⊕0=1g_3 = b_3 \oplus 0 = 1
  • g2=b2⊕b3=0⊕1=1g_2 = b_2 \oplus b_3 = 0 \oplus 1 = 1
  • g1=b1⊕b2=1⊕0=1g_1 = b_1 \oplus b_2 = 1 \oplus 0 = 1
  • g0=b0⊕b1=1⊕1=0g_0 = b_0 \oplus b_1 = 1 \oplus 1 = 0

Result: Gray 11101110, which the table above confirms is the encoding of decimal 11.

Gray-to-Binary Conversion

The inverse direction is a prefix-XOR:

bi=gi⊕gi+1⊕gi+2⊕⋯⊕gnb_i = g_i \oplus g_{i+1} \oplus g_{i+2} \oplus \dots \oplus g_n

Equivalently, walking from the MSB down: bn=gnb_n = g_n, then bi=bi+1⊕gib_i = b_{i+1} \oplus g_i. A direct serial implementation has linear XOR depth, but a parallel-prefix XOR tree can reduce depth at the cost of more wiring and gates. Do not assume the naïve chain is the only implementation.

For the example above, decoding Gray 11101110:

  • b3=g3=1b_3 = g_3 = 1
  • b2=b3⊕g2=1⊕1=0b_2 = b_3 \oplus g_2 = 1 \oplus 1 = 0
  • b1=b2⊕g1=0⊕1=1b_1 = b_2 \oplus g_1 = 0 \oplus 1 = 1
  • b0=b1⊕g0=1⊕0=1b_0 = b_1 \oplus g_0 = 1 \oplus 0 = 1

Recovered binary: 10111011, decimal 11. Round-trip clean.

Beyond Encoders: Clock Domain Crossing

Rotary encoders are the textbook motivation, but the most consequential modern use of Gray code lives inside FPGAs and SoCs: asynchronous FIFO pointers for clock-domain crossing (CDC).

A dual-clock FIFO needs each side to estimate the other side’s pointer so it can derive full or empty state. Sending an ordinary multi-bit binary counter directly across the boundary is unsafe because one increment can change several bits.

A robust Gray-pointer design usually:

  1. keeps the working pointer in binary for local arithmetic;
  2. converts the next pointer to Gray;
  3. registers the Gray value in the source domain;
  4. synchronizes each Gray bit into the destination domain; and
  5. compares the synchronized pointer using the FIFO’s documented full/empty equations.

Gray coding means only one logical pointer bit changes per increment, but it does not guarantee correct CDC by itself. The changing bit can violate a destination flip-flop’s aperture and become metastable. Routing skew can also make different Gray bits from different source transitions overlap at the destination unless implementation constraints limit delay or bus skew. AMD and Intel both document such constraints for Gray-coded FIFO pointers.

The synchronized pointer is also delayed by several destination clocks. Full/empty logic is designed to be conservative around that latency; it is not a continuously exact cross-domain count. Use a proven asynchronous-FIFO design or vendor IP when correctness matters.

A Gray-Code Counter in Hardware

Two practical approaches are:

  1. Binary counter + registered conversion. Compute gray_next = binary_next XOR (binary_next >> 1) and capture both next values on the same source clock. The registered Gray output changes according to the code sequence; do not expose an unregistered converter fed by a rippling or skewed binary count as though it were glitch-free.
  2. Native Gray-state machine. Derive next-state logic that follows the selected Gray sequence directly. This avoids a binary state register but makes increment/decrement and FIFO-address arithmetic less convenient.

To experiment with the first approach, open the 4-bit binary counter template, add the XOR network, and register its four outputs. Compare stable states after each clock. If the simulator models gate delay, also inspect the unregistered converter output during transitions; it can reveal why the source register matters.

Drawbacks: The Math Is Awkward

Gray code’s superpower — single-bit transitions — comes with a price: arithmetic in Gray code is painful. There is no carry-propagation formula that works directly on Gray-coded values. Adding two Gray numbers requires converting both to binary, adding, and converting back. Multiplication and shifting are similarly broken.

That is why ordinary arithmetic datapaths generally convert Gray-coded state to binary rather than perform full arithmetic directly in Gray code. The encoding is useful at particular boundaries, not a universal replacement for binary. Karnaugh maps use Gray ordering for a different reason: adjacent cells must differ in one input variable so geometric groups correspond to implicants.

Historical Note

The encoding is named after Bell Labs researcher Frank Gray. His “Pulse Code Communication” patent application was filed in 1947 and issued as US 2,632,058 in 1953; it calls the scheme reflected binary code and emphasizes that neighboring represented amplitudes differ in one pulse position. Related unit-distance sequences predate the patent, so it is more precise to credit Gray with this influential application and naming lineage than with inventing every such sequence.

Common Pitfalls

  • Assuming arithmetic works. Adding Gray-coded values bit-wise produces nonsense. Always convert to binary first.
  • Ignoring the MSB-zero convention. The binary-to-Gray formula assumes a virtual zero above the MSB. Forgetting this flips the sign of the high bit.
  • Mixing Gray with shift-register loops. A standard shift register, like the SISO/SIPO designs, shifts bits laterally and doesn’t preserve unit-distance properties end-to-end. Convert at the boundaries.
  • Confusing Gray with one-hot. One-hot encoding (only one bit ever set) is also glitch-resistant in a different sense, but it’s O(2n)O(2^n) wide instead of nn. Gray code is the compact choice.

What’s Next

The published SISO, SIPO, PISO, and PIPO guide compares the four common shift-register data paths. Do not use an ordinary shift register as a substitute for an asynchronous FIFO: related multi-bit data requires a protocol that preserves coherence across the clock boundary.

To see Gray code in action, load the 4-bit binary counter template, build the converter, add an output register, and verify the Hamming distance between consecutive registered values. Then intentionally remove the register in a delay-aware model and inspect the transitional behavior.

Primary References

Apply the concept in a simulator model.