ここで扱うFIFO
- FIFOはタイミング吸収のため、多くの場面で使用されます。このため、求められる機能を把握して共通のモジュールを作っておくと大変便利です。
- 異なる仕様のたいていは、データ幅とFIFOの深さです。なお、データ幅は任意のビット幅、FIFOの深さは通常2の累乗で示す[1]ことがほとんです。ここでは、parameter文で上位モジュールから指定できることを条件にします。
// Here is upper module
// WIDTH indicates width of data word
// DEPTHR indicates radix of depth of fifo
fifo #(WIDTH, DEPTHR) fifo_0 (
.iVld (),
........
// Here is lower module
module fifo (
iVld,
iStall,
iData,
........
parameter W = 32;
parameter DR = 2;
........
input [W-1:0] iData;
- 基本的なFIFOの構造は述べましたが、ここでは以下の3つのFIFOを例として順次示したいと思います。ほとんどは他の文献等で述べらていますが、別のページで参照するためもあり記述を含めて掲載します。
- Threshold対応FIFO
- SRAMを使用したFIFO
- 非同期FIFO
FIFOの基本的な原理
- First-in First-outの名から分かるように、同じ順序の入出力を行い、常にレイテンシを1を保ちます(通常のパイプラインだとバッファ容量分のレイテンシが必要)。
- レイテンシを1を保つには、バッファの状態によりバッファの入出力の管理を行う必要があります。最もポピュラーな方法は、入出力ポインタを管理し、そのポインタによりバッファへの書き込みと読み出しを行うことです。簡単に言うと、入力ポインタが示すバッファに入力データを書き込み、出力ポインタが示すバッファから出力データを得るということになります。
- ポインタは入出力ごとにインクリメントして、バッファの指し示す位置を更新します。これに対して、ありもしないデータを出力したり、バッファが一杯なのにデータを入力したりすることがないよう制御することがポイントです。
- ここでは、冗長な入出力ポインタを用いた制御を説明します。直感的に、ポインタはバッファ容量分をカバーすればいいように思われますが、バッファは巡回して使用しているのでポインタが同値の場合FullかEmptyか2つの状態を判別できません。例えば、図の左側は緑にデータが入っていることはわかりますが、図の右側はデータが全くないか全て埋まっているか分かりません。
- つまりもう1つ情報が必要です。FullかEmptyを示すFFが用意されているとすればこれを状態として利用できます。詳細は述べませんが、ポインタが同値になる手前の状態と組み合わせることで解決することができます。しかしこの方法はポインタの値を見て柔軟に操作しにくく、後述するThreshold対応FIFOや非同期FIFOには向きません。
- ここでは、入出力ポインタに巡回を示す1ビットを加えた方式を述べます。1ビット加えると言っても、インクリメントで発生するCarryビットを残すだけです。明らかに、上図の右側の状態はポインタだけで判定できます(Carryビットの比較で判定)。下図のような組み合わせができます。入力ポインタから出力ポインタを引いたものは必ずバッファの数以下になります
基本的なFIFOの設計
- FIFOはFullとEmptyだけで制御することが大半です。FullとEmptyの判定は、ポインタが必ず1しか進まないことを考えれば大小比較ではなく、次のような比較だけで十分です。
- Empty: 入出力ポインタにおいて、Carryを含めて同一であること
- Full:  入出力ポインタにおいて、Carryが不一致でその他が同一であること(一巡違い)
- タイミングアークを切断するためFIFOを使用する場合もあるので、FullとEmptyはFF出力にします。単なるポインタ比較は組み合わせ回路になるため、先取りしたポインタを用いて比較を行います。遅延の移動を参考にして下さい。
- 同様にデータに関してもFF出力にします。FullとEmptyに比べ、データに関してはタイミング制約が緩い場合が多いので、回路節約のため省くことも選択肢の一つです。
- サンプルコードはFullをStallに、not EmptyをValidにしているので、必要に応じて読み替えて下さい。
コード(RTL)
/* **************************** MODULE PREAMBLE ********************************
Copyright (c) 2011, ArchiTek
This document constitutes confidential and proprietary information
of ArchiTek. All rights reserved.
*/
// ***************************** MODULE HEADER *********************************
module fifo (
iVld,
iStall,
iData,
oVld,
oStall,
oData,
reset,
clk
);
// ************************ PARAMETER DECLARATIONS *****************************
// 初期のパラメータ、上位モジュールで再定義のこと
parameter W = 32; // Data Length
parameter DR = 2; // Depth Radix
// FIFOの深さ(2の累乗)を求める
parameter D = 1<<DR;
// *************************** I/O DECLARATIONS ********************************
// パイプライン入力、言及してきた信号名を合わせるためFullはStallに変更
input iVld;
output iStall;
input [W-1:0] iData;
// パイプライン出力、言及してきた信号名を合わせるためBusy(not Empty)はoVldに変更
output oVld;
input oStall;
output [W-1:0] oData;
// 一般的に必要なリセットとクロック
// ユーザーによるソフトリセットを考慮して同期型のリセットを採用
input reset;
input clk;
// ************************** LOCAL DECLARATIONS *******************************
reg iStall;
wire iStallD;
reg oVld;
wire oVldD;
// 入力ポインター、MSBは周期、その他はデータ位置を示す
reg [DR:0] iPtr;
wire [DR:0] iPtrD;
// 出力ポインター、MSBは周期、その他はデータ位置を示す
// 入力側の周期情報だけで足りるが、記述簡単のためMSBに周期情報を残す
reg [DR:0] oPtr;
wire [DR:0] oPtrD;
// データ出力はラッチ、なお下記の配列memと同一データを管理する冗長性が存在します
reg [W-1:0] oData;
// 入出力パルス、EmptyおよびFullの場合は論理的にアサートしない
wire iAlloc = iVld & !iStall;
wire oAlloc = oVld & !oStall;
// FIFOの中身はレジスタで構成、FPGAツールではたいていSRAMへ自動マッピング
reg [W-1:0] mem[0:D-1];
// ****************************** MODULE BODY **********************************
// -----------------------------------------------------------------------------
// Data FIFO
// FIFOが空の場合は入力データが直接、そうでない場合は次のポインタが示すFIFOを出力FFにラッチ
// 事前にFFのD端子信号を用意する方法でデータをラッチ、遅延の移動を参照
always @(posedge clk)
if (iAlloc & (iPtr[DR-1:0] == oPtrD[DR-1:0]))
oData <= #1 iData;
else if (!oStall)
oData <= #1 mem[oPtrD[DR-1:0]];
// FIFOにデータを入力、ここはiPtrによる選択のため組み合わせ回路がどうしても挿入される
always @(posedge clk)
if (iAlloc)
mem[iPtr[DR-1:0]]
<= #1 iData;
// -----------------------------------------------------------------------------
// Data FIFO Pointer
// iStallはFull、oVldはBusy(not Empty)に相当
// 事前にFFのD端子信号を用意する方法でデータをラッチ、遅延の移動を参照
// ポインターはインクリメント型でいきなり+2になることはなく、=か!=を用いたピンポイント比較でよい
// ただし、FIFOポインタ内をランダムにアクセスする型は大小比較をする必要がある
always @(posedge clk)
if (reset) begin
iStall <= #1 1'b0;
oVld <= #1 1'b0;
end
else begin
iStall <= #1 iStallD;
oVld <= #1 oVldD;
end
always @(posedge clk)
if (reset) begin
iPtr <= #1 {DR+1{1'b0}};
oPtr <= #1 {DR+1{1'b0}};
end
else begin
iPtr <= #1 iPtrD;
oPtr <= #1 oPtrD;
end
// ポインタがちょうど一周すればFull(これ以上は受け付けることができない)
// Fullであっても出ていくデータがあればnot Fullに出来るがタイミングアークが切れなくなる(パス型)
assign iStallD = (iPtrD[DR] != oPtrD[DR])
& (iPtrD[DR-1:0] == oPtrD[DR-1:0]);
// ポインタが少しでも違えばBusy(not Empty)
assign oVldD = (iPtrD != oPtrD);
// LintのWarning(Bit長不一致)を消すには、見にくくなるがiAlloc → {{DR-1{1'b0}}, iAlloc}
assign iPtrD = iPtr + iAlloc;
assign oPtrD = oPtr + oAlloc;
// ************************** FUNCTIONS and TASKS ******************************
endmodule
// *****************************************************************************
回路デザイン > 設計例 [FIFO] > ここで扱うFIFO 次のページ(Threshold対応FIFO) このページのTOP ▲
[1]
パス型(S?)の連結を組み合わせれば、2の累乗以外のFIFOも設計できます。FIFO深さが大きいと遅延に大きな影響を与えるのでそんなに使用することはないでしょう。
ただし、ポインター管理の部分を消去できるので、2,3段程度のFIFOでは積極的に利用した方がいいと思います。