01signal.com

オーバーフロー防止のための EOF 付き FIFO

このページは、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 点が異なります。

最初の機能により、変更した 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 つあることがわかります。

最初の 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 になります。

これらの条件を 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 に変換する方法とその逆、さらにタイミングを改善する方法を示します。

このページは英語から機械翻訳されたものです。不明な点があれば、原文を参照してください。
Copyright © 2021-2026. All rights reserved. (dcc38493)