このページは、FIFO に関する全 5 ページのシリーズの 4 ページ目です。このページでは、オーバーフロー(overflow)が発生する可能性への対処方法を提案します。
はじめに
FIFO を使う多くのアプリケーションでは、到着するデータのフローを制御する手段がありません。たとえば、データ取得(data acquisition)アプリケーションでは、ロジックが取り込んだデータを FIFO に直接書き込みます。FIFO が full(満杯)の場合、このデータは失われます。この種のアプリケーションでは、FIFO からの読み出し側が FIFO 内のデータを十分な速さで消費して、FIFO が full(満杯)にならないようにする責任を負います。
しかし、FIFO が full(満杯)になると、データが失われるため、データの連続性が失われます。FIFO は書き込みの試行を無視するからです。その結果、FIFO の内容は不正確になります。この状況をオーバーフロー(overflow)と呼びます。
オーバーフロー(overflow)は何らかの誤動作の結果であり、避けるべきものです。しかし、オーバーフロー(overflow)が発生した場合にも、その事象を検出し、被害を抑えることが重要です。以下に、そのための戦略を提案します。
変更を加えた FIFO
常に、オーバーフロー(overflow)を発生させないことを最優先にすべきです。それでも発生してしまった場合、後は FIFO から誤ったデータが読み出されないようにすることだけが残ります。言い換えれば、FIFO から読み出されるすべてのデータが、連続かつ正確であることを保証します。
提案する解決策は、変更を加えた FIFO であり、次の 2 点が異なります。
- オーバーフロー(overflow)が発生すると(つまり FIFO が full(満杯)になると)、FIFO がリセットされるまで、それ以上データを書き込むことができなくなります。
- オーバーフロー(overflow)の後、FIFO が empty(空)になると、FIFO はリセットが行われるまで empty(空)のままであることを報告します。この状態を EOF と呼ぶことにします。
最初の機能により、変更した FIFO から読み出されるデータが常に連続かつ正確であることが保証されます。データの連続性が失われた後は、FIFO が書き込みを拒否するからです。2 つ目の機能(EOF)は、FIFO を使うロジックに何かがおかしいというメッセージを送ります。これにより、ロジックはデータフローを再開する機会を得ます。
EOF という名前は End of File に由来します。この変更した FIFO は、Xillybus IP コア(IP core)に基づくデータ取得(data acquisition)アプリケーションで役立ちます。この種のアプリケーションでは、Xillybus IP コアがデータをコンピュータへ転送します。コンピュータ上の単純なプログラムは、標準のファイル I/O API を使って FIFO からデータを読み出します。つまり、そのプログラムは通常の方法でファイルを開き、そこからデータを読み出します。FIFO の EOF により、このファイルは通常のファイルの末尾に達したときと同じように振る舞います。読み出すデータがもうないという事実に対する自然な応答です。
Verilog による実装
以下は、上で提案した変更を加えた FIFO を実装する Verilog モジュールの例です。
module eof_fifo
(
input rst,
input wr_clk,
input rd_clk,
input [31:0] din,
input wr_en,
input rd_en,
output [31:0] dout,
output full,
output empty,
output eof
);
reg rst_sync;
reg rst_cross;
reg fifo_has_been_full;
reg fifo_has_been_nonfull;
reg has_been_full_cross;
reg has_been_full;
assign ok_to_write = !rst_sync && !full && !fifo_has_been_full;
assign eof = empty && has_been_full;
always @(posedge wr_clk)
begin
if (!full)
fifo_has_been_nonfull <= 1;
else if (rst_sync)
fifo_has_been_nonfull <= 0;
if (full && fifo_has_been_nonfull)
fifo_has_been_full <= 1;
else if (rst_sync)
fifo_has_been_full <= 0;
end
// Clock domain crossing logic: asynchronous -> wr_clk
always @(posedge wr_clk)
begin
rst_cross <= rst;
rst_sync <= rst_cross;
end
// Clock domain crossing logic: wr_clk -> rd_clk
always @(posedge rd_clk)
begin
has_been_full_cross <= fifo_has_been_full;
has_been_full <= has_been_full_cross;
end
fifo fifo_ins
(
.rst(rst),
.wr_clk(wr_clk),
.rd_clk(rd_clk),
.din(din),
.wr_en(wr_en && ok_to_write),
.rd_en(rd_en),
.dout(dout),
.full(full),
.empty(empty)
);
endmodule
このモジュールが、スタンダード FIFO のインスタンシエーション(instantiation)に追加のロジックを加えたものであることは明らかです。このモジュールのポートは、スタンダード FIFO のポートとほぼ完全に同じであることに注意してください。唯一の違いは、変更した FIFO に @eof というポートがあることです。
また、@din と @dout の幅は 32 ビットです。異なる幅の FIFO が必要な場合、変更が必要なのはモジュール先頭のこれらのポートの宣言だけです。
変更した FIFO には @full(満杯)ポートがありますが、これは必ずしも役に立ちません。FIFO に書き込むロジックは、このポートを無視してもよいでしょう。FIFO が full(満杯)になっても、どうしようもないからです。いずれにせよ、FIFO はそれ以降の書き込みの試行を無視します。ほとんどのアプリケーションでは、FIFO が full(満杯)になったことを知ってもあまり役に立ちません。@eof が High になるのを待って、メカニズム全体を再起動するのが最善です。
オーバーフロー後の書き込み操作の防止
既に述べたとおり、このモジュールはスタンダード FIFO に基づいています。この FIFO のポートはすべて eof_fifo のポートに直接接続されていますが、@wr_en だけは例外です。このポートは "wr_en && ok_to_write" に接続されています。つまり、@ok_to_write は、FIFO が full(満杯)になった後に書き込み操作を停止するために使われます。このワイヤの定義は次のとおりです。
assign ok_to_write = !rst_sync && !full && !fifo_has_been_full;
この式から、書き込みを妨げる状況が 3 つあることがわかります。
- FIFO がリセットされているとき
- FIFO が full(満杯)のとき
- FIFO が過去に full(満杯)になったことがあるとき
最初の 2 つの条件は当然のことです。3 つ目の条件に注目しましょう。この条件は @fifo_has_been_full によって表されます。
always @(posedge wr_clk)
begin
if (!full)
fifo_has_been_nonfull <= 1;
else if (rst_sync)
fifo_has_been_nonfull <= 0;
if (full && fifo_has_been_nonfull)
fifo_has_been_full <= 1;
else if (rst_sync)
fifo_has_been_full <= 0;
end
@fifo_has_been_full は、FIFO が full(満杯)になったことがあるときに High になります。このレジスタが High に変わるのは、@full(満杯)と @fifo_has_been_nonfull の両方が High のときです。
前半の条件は当然のことです。@full(満杯)は FIFO の full 出力に接続されています。しかし、なぜ @fifo_has_been_nonfull が必要なのでしょうか。その理由は、FIFO はリセット状態にある間、full 出力を High に維持することが多いからです。この機能の目的は、FIFO がまだデータを受け取る準備ができていないことをアプリケーションロジックに知らせることです。@fifo_has_been_nonfull の目的は、このシナリオで @fifo_has_been_full が誤って High になるのを防ぐことです。
@fifo_has_been_full は、FIFO がリセットされると Low に変わります。言い換えれば、オーバーフロー(overflow)発生後に変更した FIFO の通常動作を再開する唯一の方法は、FIFO をリセットすることです。@rst は非同期リセット(asynchronous reset)であることに注意してください。そのため、正しいクロックドメイン(clock domain)に属するリセット信号を生成するロジックを追加する必要があります。この信号が、@rst のコピーである @rst_sync です。
EOF の生成
@eof ポートは、次の 2 つの条件が両方とも満たされると High になります。
- FIFO 内のすべてのデータが消費され、FIFO が empty(空)であること。
- FIFO へのデータの書き込みが今後行われないこと。@fifo_has_been_full が High である、つまり FIFO が過去に full(満杯)になったことがあるためです。したがって、@ok_to_write は FIFO がリセットされるまで Low のままです。
これらの条件を Verilog コードで表すと、次のようになります。
assign eof = empty && has_been_full;
この式が @fifo_has_been_full ではなく @has_been_full に基づいていることに注意してください。@eof と @empty(空)はどちらも @rd_clk のクロックドメイン(clock domain)に属します。一方、@fifo_has_been_full は @wr_clk のクロックドメインに属します。そこで、@fifo_has_been_full をクロックドメイン間遷移(clock domain crossing)によって @rd_clk のクロックドメインにコピーしたものが @has_been_full です。
つまり @eof は、「FIFO が empty(空)であるだけでなく、リセットするまでデータが満たされることもない」という意味になります。
まとめ
多くのアプリケーションでは、オーバーフロー(overflow)が発生しないことを保証することは不可能です。この事象が許容できない場合、発生時の被害を減らすことは可能です。その戦略は、オーバーフロー(overflow)が発生するまでに書き込まれたデータを通し、その後はそれ以上のデータの書き込みを許可しないというものです。やがて FIFO は empty(空)になります。そのとき、FIFO は @eof ポートによって、今後データが到着しないことを報告します。
この方法は、FIFO から読み出されるデータの連続性を保証します。FIFO が full(満杯)になったことによるデータの欠落が途中で発生しないからです。このため、この方法はデータ取得(data acquisition)アプリケーションに役立ちます。
このページでは、この戦略に基づく変更を加えた FIFO の実装方法を示しました。この変更した FIFO は、スタンダード FIFO の置き換えとしてそのまま使えます。唯一重要な違いは、FIFO を使うロジックが @eof ポートに注意を払うことです。このポートが High になったら、ロジックは FIFO をリセットし、データフローを再開しなければなりません。
これで、FIFO に関するこのシリーズの 4 ページ目は終わりです。次のページでは、「スタンダード FIFO」を FWFT FIFO に変換する方法とその逆、さらにタイミングを改善する方法を示します。