範囲
このページは、 FIFOsに関する 5 ページ シリーズの3 番目であり、 baseline single clock FIFOの Verilog での実装を示しています。これは portable codeを作成する場合に役立ちますが、このページの主な目的は FIFO がどのように動作するかを繰り返すことです。そこで、「standard FIFO」と FWFT FIFOの実装を示します。ただし、最初に、両方の FIFOs が使用する dual port RAM が必要です。
dual port RAM
これは、 inferenceのおかげで RAM を実装した Verilog module です。どの synthesizer でもこれが正しく行われる可能性は非常に高いですが、望ましくない種類の RAM (block RAM 対 distributed RAM) が生成される可能性があるため、 synthesizer directivesを追加する必要がある場合があります。または、 dual port RAM用に FPGA ベンダーが提供する IP を使用することもできます。
moduleは次のとおりです。
module dualport_ram #(parameter depth = 64,
log2_depth = 6,
width = 8
)
(
input clk,
input [(log2_depth-1):0] wr_addr,
input [(log2_depth-1):0] rd_addr,
output reg [(width-1):0] rd_data,
input [(width-1):0] wr_data,
input rd_en,
input wr_en
);
reg [(width-1):0] inferred_ram[0:(depth-1)];
always @(posedge clk)
begin
if (wr_en)
inferred_ram[wr_addr] <= wr_data;
if (rd_en)
rd_data <= inferred_ram[rd_addr];
end
endmodule
「standard FIFO」
これで、「standard FIFO」を実装する module (つまり、 FWFT FIFOではない) を確認する準備が整いました。
module fifo
#(parameter depth = 64, // Must equal 2^log2_depth exactly
log2_depth = 6,
width = 8
)
(
input clk,
input rst,
input wr_en,
input [(width-1):0] din,
input rd_en,
output [(width-1):0] dout,
output reg full,
output reg empty
);
reg [log2_depth:0] next_words_in_ram; // Combinatorial
reg [log2_depth:0] words_in_ram;
reg [(log2_depth-1):0] rd_addr;
reg [(log2_depth-1):0] wr_addr;
wire fetch_data, commit_data;
assign fetch_data = rd_en && !empty;
assign commit_data = wr_en && !full;
always @(*)
if (commit_data && !fetch_data)
next_words_in_ram <= words_in_ram + 1;
else if (!commit_data && fetch_data)
next_words_in_ram <= words_in_ram - 1;
else
next_words_in_ram <= words_in_ram;
always @(posedge clk)
begin
words_in_ram <= next_words_in_ram;
full <= (next_words_in_ram == depth);
empty <= (next_words_in_ram == 0);
if (fetch_data)
rd_addr <= rd_addr + 1;
if (commit_data)
wr_addr <= wr_addr + 1;
if (rst)
begin
empty <= 1;
full <= 1;
words_in_ram <= 0;
rd_addr <= 0;
wr_addr <= 0;
end
end
dualport_ram
#(.depth(depth), .log2_depth(log2_depth), .width(width)) dp_ins
(.clk(clk), .wr_addr(wr_addr),
.rd_addr(rd_addr),
.wr_en(commit_data),
.rd_en(fetch_data),
.wr_data(din),
.rd_data(dout)
);
endmodule
"depth" および "log2_depth" パラメーターに関して、唯一の重要な使用上の注意が上部のコメントに記載されています。 depth は 2log2_depthと等しくなければなりません。
この module の操作は非常に簡単です。 @fetch_data は @rd_enに似ていますが、 @empty が考慮されています。したがって、 @fetch_data は @rd_enの安全なバージョンであり、 @rd_en と @empty が同時に High であっても (これは違法です)、 @rd_en は無視されるため、悪いことは何も起こりません。
@commit_data は、 @full を考慮することにより、同様に @wr_en の安全なバージョンです。
@words_in_ramの次の値である @next_words_in_ramは、 @fetch_data と @commit_data に基づいて combinatorial functionとして計算されます ( always @(*) ステートメントに注意してください)。 @next_words_in_ram は、次のコード スニペットで複数の registers の値を生成するために使用されます。
always @(posedge clk)
begin
words_in_ram <= next_words_in_ram;
full <= (next_words_in_ram == depth);
empty <= (next_words_in_ram == 0);
[ ... ]
@full と @empty がここで定義されていることに注意してください。
この FIFO の実装が dual-clock FIFOと比較して非常に簡単なのは、 @words_in_ram をこのように定義し、 FIFOの両側で同じ register を使用できることです。
コードの次は、 @rd_addr と @wr_addrの更新、そして @rstの句です。 @empty と @full の両方が resetの結果として High に変化しますが、 reset が解放されると @full は Low に戻ることに注意してください。
コーディング スタイルに関する注意: @rst が High の場合、 if statementの begin-end 句の割り当てが以前に行われた可能性のある割り当てをオーバーライドするため、 @rst は実際にすべての registers を初期値にリセットします。これは最も一般的なコーディング スタイルではありませんが、すべての registers がリセットされるわけではない場合には明らかな利点があります。この特定のケースはこの利点を示していませんが、このページを参照してください。
最後に、 dual port RAMの instantiation があります。
FWFT FIFO
このページに示されているように、「standard FIFO」を FWFT FIFOに変換するのは非常に簡単です。しかし、その直接的な実装により、いくつかの興味深い点について議論することができます。
module fwft_fifo
#(parameter depth = 64, // Must equal 2^log2_depth exactly
log2_depth = 6,
width = 8
)
(
input clk,
input rst,
input wr_en,
input [(width-1):0] din,
input rd_en,
output [(width-1):0] dout,
output reg full,
output reg empty
);
reg [log2_depth:0] next_words_in_ram; // Combinatorial
reg [log2_depth:0] words_in_ram;
reg [(log2_depth-1):0] rd_addr;
reg [(log2_depth-1):0] wr_addr;
reg has_more_words;
wire fetch_data, commit_data;
assign fetch_data = (rd_en || empty) && has_more_words;
assign commit_data = wr_en && !full;
always @(*)
if (commit_data && !fetch_data)
next_words_in_ram <= words_in_ram + 1;
else if (!commit_data && fetch_data)
next_words_in_ram <= words_in_ram - 1;
else
next_words_in_ram <= words_in_ram;
always @(posedge clk)
begin
words_in_ram <= next_words_in_ram;
full <= (next_words_in_ram == depth);
has_more_words <= (next_words_in_ram != 0);
if (fetch_data)
rd_addr <= rd_addr + 1;
if (commit_data)
wr_addr <= wr_addr + 1;
if (fetch_data)
empty <= 0;
else if (rd_en)
empty <= 1;
if (rst)
begin
empty <= 1;
full <= 1;
words_in_ram <= 0;
has_more_words <= 0;
rd_addr <= 0;
wr_addr <= 0;
end
end
dualport_ram
#(.depth(depth), .log2_depth(log2_depth), .width(width)) dp_ins
(.clk(clk), .wr_addr(wr_addr),
.rd_addr(rd_addr),
.wr_en(commit_data),
.rd_en(fetch_data),
.wr_data(din),
.rd_data(dout)
);
endmodule
最初に、 @has_more_wordsである新しい registerがあることに注意してください。その定義を上記の「standard FIFO」と比較して、 @has_more_words が @emptyの logical NOT であることを確信してください。
次に、 @fetch_data の定義が変更されていることに注意してください。代わりに @has_more_words によって保護されるようになりました (当然のことですが)。
assign fetch_data = (rd_en || empty) && has_more_words;
@empty は、 FWFT FIFOでは「@dout は無効です」という意味であることを思い出してください。したがって、この割り当ては、 @rd_enに加えて、 output が有効ではなく、メモリ配列に読み取るデータがある場合は、先に進んでそれを行うことを意味します。それが first wordの falling through です。
そして最後に、 @empty を割り当てるための logic が次のように変更されました。
always @(posedge clk)
if (fetch_data)
empty <= 0;
else if (rd_en)
empty <= 1;
これは簡単に言うと、メモリ アレイからワードが読み取られると、次の clock cycleで @empty が Low に変化します。これは明らかに新しい有効なデータが存在するためです。ただし、それが起こらず、それでも @rd_en が有効になっている場合、 application logic は利用可能な最後のワードを取得したので、 @empty をハイに変更します。 @rd_en が高く、 @fetch_data が低い場合、 @has_more_words は確実に低いことに注意してください (上記の @fetch_data の割り当てを参照)。これが、この状態が FIFOの最後のワードを読み取ることと同等である理由です。
FIFOsに関するこのシリーズの 3 ページ目はこれで終わりです。次のページでは、 「standard FIFO」を data acquisition アプリケーションで使用できるように調整する方法を示します。