testbenchが実際に何か
testbenchは単なるVerilog moduleです。合成可能moduleとの違いは、中身です:
- ポートを持たない - シミュレーション階層の頂点。
- DUT(design under test)を インスタンス化 する。
initialとalwaysブロックからDUTの入力を 駆動 する。$display、$monitor、またはVCDダンプでDUTの出力を 観察 する。$finishでシミュレーションを 終了 する。
それが仕事の全部です。特別な「testbench」キーワードはなく、何かがtestbenchだと分かるのは、それが何をするかを読むことによってです。
標準骨格
これが全パターンです。5つのセクション、コピペしやすく、適応しやすい。
組み合わせDUT(このadderのような)にはクロックは要りません。順序DUTには必要で、それが次のレイヤーです。
順序DUTのクロック生成
DUTがclk入力を持つとき、testbenchがそれを生成しなければなりません。標準のワンライナー:
reg clk = 0;
always #5 clk = ~clk;
これはclkを5時間単位ごとにトグルします。各トグルはクロック周期の半分で、完全周期は10単位(1サイクル = 5の高 + 5の低)です。タイムスケールが1ns / 1ps(ほとんどのシミュレータのデフォルト)なら、100MHzクロックです。
モデル化したいものに基づいて半周期を選んでください。同じタイムスケールで10MHzクロックなら#50。200MHzなら#2.5(またはタイムスケールを変える)。
resetシーケンス
同期設計は、時刻0後数サイクル間高で保持され、その後解除されるresetが必要です。慣例的な形:
reg reset = 1; // アサート状態で開始
initial begin
// 数クロック分resetを保持。
#20 reset = 0;
end
これは時間20までresetを高に保持し、その後落とします。resetが落ちる頃には、クロックがいくつかのサイクルを終え、すべてのflip-flopがreset値を捕捉し、設計は既知のstateにあります。
active-low reset(resetではなくreset_n)には、初期値を反転:
reg reset_n = 0; // active-low: 0はアサートを意味する
initial begin
#20 reset_n = 1;
end
完全な順序testbench
クロック、reset、刺激を組み合わせ:
これが4ビットカウンタの完全なtestbenchです。Runを押すと、カウンタがresetから出て、イネーブル中カウントアップし、enableが落ちると一時停止し、上がると再開し、その後停止する様子が見えます。
構造について3つの注目点:
- 3つの異なる活動の場:クロックジェネレータ(1つの
always)、刺激(1つのinitial)、モニタ(別のalways)。各々が1つの仕事を持つ。 - 刺激ブロックの
#遅延は時間単位で測られる - クロックサイクルではありません。クロック周期が10単位なら、#10はちょうど1サイクルです。代わりに@(posedge clk)を使うtestbenchもあり、周期に関わらず1サイクル進めます。 - モニタは
always @(posedge clk)ブロックから$displayを使う。 毎サイクル出力します。より洗練された出力には$monitorに切り替えます(次のドキュメントで扱う)。
サイクルベースの刺激
時には「N単位待つ」より「N サイクル待つ」の方が読みやすいことがあります:
initial begin
@(posedge clk); // 次のクロックエッジを待つ
reset = 0;
repeat (5) @(posedge clk); // さらに5サイクル待つ
enable = 1;
repeat (8) @(posedge clk);
enable = 0;
repeat (10) @(posedge clk);
$finish;
end
@(posedge clk)は次のクロック立ち上がりエッジまでブロックします。repeat (N) @(posedge clk);はNサイクル待ちます。このスタイルはクロック周期に依存しません。クロック周波数を変えても、刺激はサイクル単位で同じことをします。
初期のtestbenchでは#遅延がシンプルです。複数のクロック速度で実行される可能性のあるプロダクションtestbenchでは、サイクルベースの方が安全なスタイルです。
自己チェックテスト
これまでのtestbenchは何が起こったかを出力し、あなたに出力を読ませる作りです。自己チェックtestbenchは代わりに出力を チェック し、合否をレポートします:
initial begin
#1;
a = 10; b = 20;
#1;
if (sum !== 30) begin
$display("FAIL: 10 + 20 = %0d (expected 30)", sum);
$finish;
end
a = 250; b = 5;
#1;
if (sum !== 255) begin
$display("FAIL: 250 + 5 = %0d (expected 255)", sum);
$finish;
end
$display("PASS");
$finish;
end
安全のために!==(ケース不等価演算子)を使ってください。オペランドが未知ビットを持つときにxを返しません。パターンは:入力を駆動し、安定するのを待ち、期待と比較し、合否をレポートする。
自己チェックはリグレッションスイートで貴重です。数千のテストが無人で走り、失敗だけが注目を必要とします。
次に読むもの
これで任意のmoduleに十分なtestbench形状が揃いました。次のドキュメントはtestbench内に入るツールを扱います。よりリッチなテキスト出力のDisplay and Monitor、グラフィカルな波形デバッグのDumpfile and VCD、そしてシミュレーション時間が壁時計時間とどう関係するかを正確に制御するTimescale and Delaysです。
よくある質問
Verilogのtestbenchとは?
testbenchは、通常ポートを持たないVerilog moduleで、その目的はDUT(design under test)を駆動することだけです。DUTをインスタンス化し、クロックを生成し、initialとalwaysブロックで刺激を駆動し、$display/$monitorで出力を観察し、オプションでVCD波形をダンプし、$finishでシミュレーションを終了します。
Verilog testbenchでクロックを生成するには?
標準パターンは1行:always #5 clk = ~clk;(reg clk = 0;を事前に宣言)。これはclkを5シミュレーション時間単位ごとにトグルし、10単位の周期(タイムスケールがナノ秒なら100MHzクロック)を与えます。#5は半周期で、半分はclkが高、半分は低です。
VerilogのDUTとは?
DUTはDesign Under Testの略で、testbenchが駆動しているmoduleです。慣例として、testbench内のDUTインスタンスはdutまたはu_dutと名付けます:my_module dut(.clk(clk), .reset(reset), .in(in), .out(out));。名前は単なるラベルで、重要なのはインスタンス化されてポートがtestbenchのsignalに接続され、testbenchがそれらのsignalを駆動することです。
Verilogシミュレーションはどれくらいの長さ実行すべき?
検証したいすべてを実行するのに十分長く、その後$finishします。ほとんどのtestbenchは明示的な時間制限(#1000 $finish)を設定して、決して来ないイベントを待ってシミュレーションがハングしないようにします。そのウィンドウ内で、刺激を駆動し、DUTを安定させ、理想的には出力が期待と一致しない場合にFAILを表示する自己チェックのif文を含めます。