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.
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 to 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 or 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 () and position () 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:
| Position | Binary | Gray |
|---|---|---|
| 0 | 000 | 000 |
| 1 | 001 | 001 |
| 2 | 010 | 011 |
| 3 | 011 | 010 |
| 4 | 100 | 110 |
| 5 | 101 | 111 |
| 6 | 110 | 101 |
| 7 | 111 | 100 |
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:
| 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 “reflected” name comes from the construction rule: to build the -bit code, take the -bit code, mirror it vertically, prepend a to the original half and a 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 to Gray is one of the cleanest formulas in digital logic:
with for the MSB. As a single expression:
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 to Gray.
Result: Gray , which the table above confirms is the encoding of decimal 11.
Gray-to-Binary Conversion
The inverse direction is a prefix-XOR:
Equivalently, walking from the MSB down: , then . 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 :
Recovered binary: , 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:
- keeps the working pointer in binary for local arithmetic;
- converts the next pointer to Gray;
- registers the Gray value in the source domain;
- synchronizes each Gray bit into the destination domain; and
- 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:
- 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. - 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 wide instead of . 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.
