はじめに
FPGA や ASIC 等で異なるクロックで動作する回路同士でデータをやりとりする場合には、非同期 FIFO (Asynchronous First-In First-Out) を使って安全にデータを受け渡すことがあります。
非同期 FIFO を設計する際にはグレイコードを使う方法がよく使われますが、この記事ではグレイコードを使わない非同期 FIFO の実装方法について、何回かにわけて説明します。
-
概要編 (この記事)
1.1 グレイコードを使った非同期 FIFO の概要
1.2 グレイコードを使わない非同期 FIFO の概要 - Syncronizer 編
- Pending Register 編
グレイコードを使った非同期 FIFO の概要
非同期 FIFO を設計する際には、グレイコードを使う方法がよく使われます。そこで、まずはグレイコードを使った非同期 FIFO の説明をします。
構成例
次図にグレイコードを使った非同期 FIFO の構成例を示します。
Fig.1 グレイコードを使った非同期 FIFO の構成例
グレイコードとは
グレイコード(Gray code) は、隣り合う値の間で ビットが1ビットだけ変化する 特殊な2進数の表現方法です。
非同期 FIFO では、異なるクロックドメイン間で Dual Port RAM のアドレス情報(書き込み・読み出しポインタ)をやりとりします。そのとき、通常の2進数だと複数ビットが同時に変化することがあり、配線遅延のバラツキやメタステーブルなどによって、転送途中で不正な値になる可能性があります。
グレイコードを使えば、1ビットずつしか変わらないため、誤解釈のリスクが低くなり、安全に値を転送できます。
仕組み
書き込み側は Dual Port RAM への書き込みアドレスをグレイコードに変換して読み出し側に伝えます。また読み出し側は Dual Port RAM への読み出しアドレスをグレイコードに変換して書き込み側に伝えます。
書き込み側は、自分が保持している書き込みアドレスと読み出し側から送られてきた読み出しアドレスとを比較して、Full 信号を生成します。
読み出し側は、自分が保持している読み出しアドレスと書き込み側から送られてきた書き込みアドレスとを比較して、Empty 信号を生成します。
グレイコードを使った非同期 FIFO の問題点
グレイコードが使えるのは、Dual Port RAMの書き込みアドレスや読み出しアドレスのように、単純にカウントアップしていくような情報に限られます。処理の開始、終了、中断、停止などのイベントを書き込み側と読み出し側との間で通知する際にグレイコードは使えません。この場合は、非同期 FIFO とは別途、同期化回路(Syncronizer) が必要になります。
グレイコードを使わない非同期 FIFO の概要
構成図
グレイコードを使わない非同期 FIFO の構成は次の図のようになっています。
Fig.2 グレイコードを使わない非同期 FIFO の構成
書き込みと読み出しの制御の仕組み
グレイコードを使わない非同期 FIFO では、書き込み側と読み出し側でアドレスをやりとりするのではありません。代りに書き込み側は「書き込んだデータ量」を読み出し側に通知し、読み出し側は「読み出したデータ量」を書き込み側に通知します。それぞれの側で独立したカウンタを持ち、やりとりされた情報を使って、FIFO の状態を把握しています。
書き込み側では、データを FIFO に書き込むたびに、対応するカウンタの値がすぐに増えます。一方、読み出し側で読み出されたデータの量は、同期化回路(Synchronizer) を通じて書き込み側に伝えられ、その分だけカウンタから引かれます。このため、書き込み側のカウンタには「書き込んだデータ量」は即座に反映されますが、「読み出したデータ量」は伝わるまでに少し時間がかかります。つまり、カウンタの示す値は、実際に FIFO に残っているデータ量より一時的に多くなることがありますが、逆に少なくなることはありません。この仕組みのおかげで、FIFO に「最低限どれくらい空きがあるか」を正確に判断できるため、オーバーフローを起こさずに安全にデータの書き込みを制御できます。
同様に、読み出し側では、データを FIFO から読み出すたびに、対応するカウンタの値がすぐに減ります。一方、書き込み側で書き込まれたデータの量は、同期化回路(Syncronizer) を通じて読み出し側に伝えられ、その分だけカウンタに足されます。このため読み出し側のカウンタには「読み出したデータ量」はすぐに反映されますが、「書き込んだデータ量」は伝わるまでに少し時間がかかります。つまり、カウンタの示す値は、実際に FIFO に残っているデータ量よりも一時的に少なくなることがありますが、逆に多くなることはありません。この仕組みのおかげで、FIFO に「最低限どれくらいのデータ量が残っているか」を正確に判断できるため、アンダーフローを起こさずに安全にデータの読み出しを制御できます。
ハンドシェイクによるクロックドメイン間の情報のやりとり
グレイコードを使わない非同期 FIFO では、同期化回路(Syncronizer) によって、書き込み側から「書き込んだデータ量」を読み出し側に伝え、読み出し側から「読み出したデータ量」を書き込み側に伝えます。
この同期化回路(Syncronizer) では、異なるクロックで動作する回路同士で情報を安全にやりとりするために、request/acknowlege によるハンドシェイクをしています。
Fig.3 同期化回路(Syncronizer)の構成
送信側は、情報(I_DATA と I_VALID)を送信する際に request 信号(SYNC_REQ)を受信側に送ると共に情報をレジスタ(SYNC_DATA と SYNC_VALID)に保持します。そして受信側からの acknowledge 信号(SYNC_ACK)が来るまでレジスタ(SYNC_DATA と SYNC_VALID)の値を変更しません。
受信側は、送信側から届いた request 信号(SYNC_REQ)を受け取り、情報(SYNC_DATA と SYNC_VALID)を自分のレジスタ(O_DATA と O_VALID)に保存してから、送信側に acknowledge 信号(SYNC_ACK)を返します。
この仕組みによって、受信側が自分のレジスタ(O_DATA と O_VALID) への入力が完了するまで情報(SYNC_DATA と SYNC_VALID) の値は変化することがないので、メタステーブルを心配することなく安全に情報(I_DATA の値と I_VALID の値)を自分のレジスタ(O_DATA と O_VALID) にロードすることが出来ます。
Fig.4 Syncronizer のタイミングチャート
詳細は次の記事を参照してください。
ペンディングレジスタ
前節で説明した同期化回路(Syncronizer) では、異なるクロックで動作する回路同士で情報を安全にやりとりするために、request/acknowlege によるハンドシェイクをしています。この同期化回路(Syncronizer) の問題点は、ハンドシェイク中は新たなデータを転送できないことです。
そこで同期化回路(Syncronizer) の前に、ハンドシェイク中で転送出来ない期間の情報を保持しておくための仕組み(Syncronizer_Input_Pending_Register) を用意しています。
例えば、非同期 FIFO の書き込み側は次のように、ハンドシェイク中はライトした数をペンディングレジスタに保持しておき、ハンドシェイク可能な状態になってからまとめて送信していします。
下の図の例では、書き込み回数(WRITE 信号がアサートされた回数)を読み出し側に伝えています。ペンディングレジスタは Syncronizer がハンドシェイク中(I_READY=0)の時にWRITE 信号がアサートされた時はその回数を保持しておいて I_DATA に出力しています。Syncronizer はハンドシェイク終了後に再度ハンドシェイクをしてペンディングされていた書き込み回数を読み出し側に伝えています。
Fig.5 書き込み側のペンディングレジスタのタイミングチャート
詳細は次の記事を参照してください。