← ブログに戻る2進符号化

グレイコード入門:ロータリーエンコーダが使う理由

グレイコードでは隣接値のハミング距離が 1 になります。変換式、エンコーダでの利点、そして非同期 FIFO でなお必要となる CDC の制約を学びます。

DennyDenny
読了まで約10分
Chalkboard feature illustration for “グレイコード入門:ロータリーエンコーダが使う理由”

**要点:**反射二進グレイコードでは、隣接する値は 1 ビットだけ異なります。この性質は、正しく構成された位置境界での曖昧さを抑え、非同期 FIFO のポインタに役立ちます。ただし、メタステーブル、センサー故障、組み合わせ回路のグリッチ、CDC タイミング制約の必要性をなくすものではありません。

標準的な 4 ビット二進カウンタが 01110111 から 10001000 へ進むと、4 ビットすべてが同時に反転します。理想化されたタイミング図では、それらは同時に変化します。しかし現実のシリコンでは、ゲート遅延の不一致、エッジの鈍り、電源レールのノイズによって、変化の順序は定まりません。そのためバス上には、11111111 や 00000000 のような中間値が一瞬ちらつきます。CPU レジスタなら問題ありません。すべてが安定した後、クロックエッジで値をサンプリングするからです。非同期な観測者が読むセンサーでは、そのグリッチも実際の読み値になります。

反射二進符号とも呼ばれるグレイコードは、この境界条件を変えます。隣接するコード語が異なるのは 1 ビットだけなので、適切に整列した観測者は、複数の意図されたビット遷移を一度に判定する必要がありません。それでも、遷移中の 1 ビットは曖昧になり、ノイズの影響を受け、メタステーブルになる可能性があります。グレイコードはある種類の誤りを減らすのであって、インターフェースを絶対に壊れないものにするわけではありません。

なぜ 1 ビット隣接性が重要なのか

ロータリーエンコーダを考えてみましょう。これは、モーターコントローラに軸の角度を伝える光学または磁気式のディスクです。ディスクには同心円状のトラックがあり、各トラックが位置語の 1 ビットを担います。固定角度に置かれたセンサーがそれを読み取ります。ディスクが回るにつれて、センサーは二進値の列を見ます。

トラックを通常の二進数で符号化すると、位置 33(011011)から位置 44(100100)への境界では、同じ公称角度で 3 本のトラックが状態を変えなければなりません。センサーの配置、トラック形状、しきい値の違いにより、それらの遷移が同時に観測されるとは限りません。コントローラは、隣接するどちらの位置からも遠い中間語を一瞬デコードしてしまうことがあります。

同じディスクをグレイコードで符号化すれば、各角度境界で遷移するのは 1 ビットだけです。

位置二進数グレイ
0000000
1001001
2010011
3011010
4100110
5101111
6110101
7111100

グレイの列を見てください。どの隣接する行の間でも、反転するのはちょうど 1 ビットです。行 7 と行 0 の間でも 1 ビットだけが反転するため、この列は循環的であり、単位距離です。意図した「一度に一つの境界だけ」というモデルでは、しきい値の不確かさは古いコードか新しい隣接コードに限られます。複数境界にまたがるずれ、汚れ、トラックの損傷、電気ノイズ、またはメタステーブルなサンプルのデコードは、依然として誤りを生みます。

4 ビットのグレイコード表

このパターンは自然に拡張できます。

十進数二進数グレイ
000000000
100010001
200100011
300110010
401000110
501010111
601100101
701110100
810001100
910011101
1010101111
1110111110
1211001010
1311011011
1411101001
1511111000

「反射」という名前は構成規則に由来します。(n+1)(n+1) ビットのコードを作るには、nn ビットのコードを取り、上下に鏡映し、元の半分の先頭に 00 を、鏡映した半分の先頭に 11 を付けます。鏡映によって、二つの半分の境界は 1 ビット遷移、すなわち新しい MSB の遷移になります。

二進数からグレイコードへの変換

二進数 bb からグレイ gg への変換は、デジタル論理でもっとも簡潔な式の一つです。

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

MSB については bn=0b_n = 0 とします。一つの式にすると次のとおりです。

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

ハードウェアでは、各グレイ出力は隣接する二進ビットの XOR 一つです。キャリー連鎖はなく、理想的な論理深さは XOR 一段です。ただし、二進カウンタの複数ビットが物理的に変化している間も組み合わせ出力がグリッチフリーであることは、これでは保証されません。変換済みのグレイ値を CDC 信号として扱う前にレジスタへ格納してください。XOR が適切な演算子である理由は、差分検出器としての XOR ゲートを見直すと分かります。

**計算例。**二進数 10111011 をグレイへ変換します。

  • 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

結果はグレイ 11101110 です。上の表でも、十進数 11 の符号化であることを確認できます。

グレイコードから二進数への変換

逆変換は、前方 XOR です。

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

別の言い方をすると、MSB から下へ進みます。bn=gnb_n = g_n、次に bi=bi+1⊕gib_i = b_{i+1} \oplus g_i です。直接的な直列実装では XOR 深さは線形になりますが、並列プレフィックス XOR ツリーなら、配線とゲートを増やす代わりに深さを減らせます。素朴な連鎖だけが実装方法だと思わないでください。

先ほどの例で、グレイ 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

復元された二進数は 10111011、十進数 11 です。往復変換はきれいに一致します。

エンコーダの先へ:クロックドメインクロッシング

ロータリーエンコーダは教科書的な動機ですが、グレイコードの現代における最も重要な用途は FPGA や SoC の内部、すなわちクロックドメインクロッシング(CDC)用の非同期 FIFO ポインタにあります。

デュアルクロック FIFO では、フルまたは空の状態を求めるため、各側が相手側のポインタを推定する必要があります。通常の多ビット二進カウンタをそのまま境界越しに送るのは危険です。1 回のインクリメントで複数ビットが変わることがあるためです。

堅牢なグレイポインタ設計では、通常、次のようにします。

  1. ローカル演算のため、動作中のポインタを二進数で保持する。
  2. 次のポインタをグレイコードへ変換する。
  3. グレイ値を送信元ドメインでレジスタに格納する。
  4. 各グレイビットを宛先ドメインへ同期させる。
  5. 同期済みポインタを、FIFO で文書化された full/empty 方程式で比較する。

グレイ符号化により、1 回のインクリメントで論理的に変わるポインタビットは一つだけになります。しかし、それだけで CDC が正しくなるわけではありません。変化するビットは宛先フリップフロップのアパーチャ条件に違反してメタステーブルになる可能性があります。また、実装制約で遅延またはバススキューを制限しなければ、配線スキューによって異なる送信元遷移のグレイビットが宛先で重なって見えることもあります。AMD と Intel はどちらも、グレイコード FIFO ポインタに対するこうした制約を文書化しています。

同期済みポインタは、宛先クロック数周期分だけ遅れてもいます。full/empty 論理はこの遅延に対して保守的になるよう設計されており、クロックドメインをまたぐ連続的に正確なカウントではありません。正しさが重要な場合は、実証済みの非同期 FIFO 設計またはベンダー IP を使用してください。

ハードウェアで作るグレイコードカウンタ

実用的な手法は二つあります。

  1. 二進カウンタ + レジスタ変換。 gray_next = binary_next XOR (binary_next >> 1) を計算し、同じ送信元クロックで両方の次値を取り込みます。レジスタ済みグレイ出力はコード列に従って変化します。リップルやスキューのある二進カウントを入力とする未レジスタの変換器を、グリッチフリーであるかのように公開してはいけません。
  2. **グレイ状態機械を直接作る。**選んだグレイ列を直接たどる次状態論理を導きます。二進状態レジスタは不要になりますが、インクリメント/デクリメントと FIFO アドレス演算は扱いにくくなります。

最初の手法を試すには、4 ビット二進カウンタのテンプレートを開き、XOR ネットワークを追加して、その 4 出力をレジスタに格納します。各クロック後の安定状態を比べてください。シミュレータがゲート遅延をモデル化するなら、遷移中の未レジスタ変換器の出力も調べましょう。送信元レジスタが必要な理由が見えてきます。

欠点:演算には不向き

グレイコードの強み――1 ビット遷移――には代償があります。グレイコードでの算術は厄介です。グレイ符号化値に直接使えるキャリー伝搬の公式はありません。二つのグレイ数を足すには、両方を二進数へ戻し、加算してから、再びグレイへ変換しなければなりません。乗算やシフトも同様に使えません。

そのため通常の算術データパスでは、グレイ符号化された状態を、直接演算するのではなく二進数へ変換するのが一般的です。この符号化は特定の境界で役立つのであり、二進数をあらゆる場面で置き換えるものではありません。カルノー図がグレイ順序を使う理由は別です。隣接セルが 1 入力変数だけ異なれば、幾何学的なグループがインプリカントに対応するためです。

歴史メモ

この符号化は、ベル研究所の研究者フランク・グレイにちなんで名付けられました。彼の「Pulse Code Communication」特許出願は 1947 年に行われ、1953 年に米国特許 2,632,058 として発行されました。そこではこの方式を反射二進符号と呼び、隣接する表現振幅が 1 個のパルス位置だけ異なることを強調しています。関連する単位距離列は特許より前から存在したため、あらゆるその種の列を発明したとするより、この影響力の大きい応用と命名の系譜においてグレイを評価する方が正確です。

よくある落とし穴

  • **算術ができると思い込むこと。**グレイ符号化値をビット単位で加算しても意味のない結果になります。必ず先に二進数へ変換してください。
  • **MSB のゼロという約束を無視すること。**二進数からグレイコードへの式は、MSB の上に仮想的なゼロがある前提です。これを忘れると最上位ビットの符号が反転します。
  • グレイコードとシフトレジスタのループを混同すること。SISO/SIPO の設計のような標準的なシフトレジスタは、ビットを横方向へずらすため、端から端まで単位距離の性質を保ちません。境界で変換してください。
  • **グレイコードとワンホットを混同すること。**ワンホット符号化(常に 1 ビットだけがセットされる)も、別の意味でグリッチに強い方式ですが、幅は nn ではなく O(2n)O(2^n) です。コンパクトさではグレイコードが優れています。

次に読むもの

公開済みの SISO、SIPO、PISO、PIPO ガイドでは、代表的な 4 種のシフトレジスタのデータパスを比較しています。通常のシフトレジスタを非同期 FIFO の代用品にしてはいけません。関連する多ビットデータには、クロック境界をまたいで一貫性を保つプロトコルが必要です。

グレイコードの動作を見るには、4 ビット二進カウンタのテンプレートを読み込み、変換器を作り、出力レジスタを加えて、連続するレジスタ済み値のハミング距離を確認します。次に遅延を意識したモデルで意図的にレジスタを外し、遷移中の挙動を観察してください。

主な参考資料

アイデアを実際に動く回路にしましょう。