組み込みテスト
組み込みテストは #[test(test_name)] アトリビュートでマークすることができます。マークされたブロックはテストとして認識され、 veryl test コマンドによって実行されます。
組み込みテストの記述方法は3種類あります。
- ネイティブテスト
- SystemVerilogテスト
- cocotb テスト
ネイティブテストはVerylの組み込みシミュレータを使用し、SystemVerilogテストとcocotbテストは外部のRTLシミュレータを使用します。veryl test で使用される外部RTLシミュレータについては シミュレータ を参照してください。すべてのテスト種別で、--wave オプションを指定すると波形を生成できます。
#[ignore] 属性を追加することでデフォルトでは無視するテストを指定できます。無視されたテストは --ignored オプションで実行できます。--include-ignored オプションを使うと通常のテストと無視されたテストの両方を実行します。
テスト時のみ有効になるコードパスは、#[ifdef]/#[ifndef] 属性と、テスト用に定義された名前を組み合わせることで有効化できます。名前は Veryl.toml の [test].defines フィールド、または veryl test の --define NAME (-D NAME) オプションで定義できます。両者はマージされ、SystemVerilog テストと cocotb テストでは同じ名前が外部シミュレータにも渡されます。
#[test(test_ignored)]
#[ignore]
module test_ignored {
inst clk: $tb::clock_gen;
inst rst: $tb::reset_gen ( clk );
initial {
rst.assert();
$finish();
}
}
ネイティブテスト
ネイティブテストでは、SystemVerilogの埋め込みや外部フレームワークを使わずに、Verylで直接テストベンチを記述できます。#[test(test_name)] 属性が付けられたモジュール(embed 宣言なし)がネイティブテストとして扱われます。
以下のテストベンチコンポーネントが利用可能です。
$tb::clock_gen— クロック信号生成器(オプションで#(period: N)パラメータを指定可能)$tb::reset_gen— リセット信号生成器(オプションで#(cycles: N)パラメータを指定可能)$tb::file— 出力ファイルを書き込むためのファイルハンドル$tb::random— 乱数生成器(値の型をジェネリック引数として指定)
組み込みのコンポーネントに加えて、Rust で記述した検証コンポーネントを $comp 名前空間を通じて使用できます。コンポーネントを使う を参照してください。
また、以下のシステム関数が initial ブロック内で使用できます。
$assert(condition)、$assert(condition, format, args...)— シミュレーション中にアサーションを検査します。失敗するとシミュレーションは即座に停止し、テストは失敗として報告されます。formatは$displayと同じ書式のフォーマット文字列で、argsがそこへ埋め込む値を与えます。$assert_continue(condition)、$assert_continue(condition, format, args...)—$assertと同じですが、失敗後もシミュレーションが続行されるため、1回の実行で複数の失敗をまとめて収集できます。テストは引き続き失敗として報告されます。$finish()— シミュレーションを終了
基本的な例
module Counter (
clk: input clock ,
rst: input reset ,
cnt: output logic<32>,
) {
always_ff {
if_reset {
cnt = 0;
} else {
cnt += 1;
}
}
}
#[test(test_counter)]
module test_counter {
inst clk: $tb::clock_gen;
inst rst: $tb::reset_gen ( clk );
var cnt: logic<32>;
inst dut: Counter (
clk: clk,
rst: rst,
cnt: cnt,
);
initial {
rst.assert();
clk.next(10);
$assert(cnt == 32'd10);
$finish();
}
}
テストベンチメソッド
clock_gen はクロックサイクルを進める next メソッドを提供します。
clk.next()— クロックを1サイクル進めるclk.next(N)— クロックをNサイクル進める
reset_gen はリセットをアサートする assert メソッドを提供します。
rst.assert()— クロックに同期してリセットをアサートrst.assert(duration)— 指定された期間リセットをアサート
リセット期間はインスタンス化時にも設定できます。
#[test(test_reset_cycles_param)]
module test_reset_cycles_param {
inst clk: $tb::clock_gen;
inst rst: $tb::reset_gen #( cycles: 5 ) ( clk );
// ...
initial {
rst.assert();
// ...
}
}
テストベンチ内の関数呼び出し
clk.next などのテストベンチメソッドはユーザー定義関数から呼び出すことができます。
#[test(test_function_call)]
module test_function_call {
inst clk: $tb::clock_gen;
inst rst: $tb::reset_gen ( clk );
var cnt: logic<32>;
// inst dut: Counter (clk, rst, cnt);
function step_n (
n: input logic<32>,
) {
clk.next(n);
}
initial {
rst.assert();
step_n(5);
step_n(5);
$assert(cnt == 32'd10);
$finish();
}
}
階層参照
DUT 内部の信号は、階層パスを通して initial ブロックから読み出せます。パスはテストモジュールのインスタンスから始まり、. でネストしたインスタンスを辿ります。観測のために内部信号をトップレベルまで引き出す必要はありません。
module Sub (
clk: input clock ,
rst: input reset ,
din: input logic<4>,
) {
var internal_reg: logic<4>;
always_ff {
if_reset {
internal_reg = 0;
} else {
internal_reg = din + 1;
}
}
}
module Top (
clk: input clock ,
rst: input reset ,
din: input logic<4>,
) {
inst u_sub: Sub ( clk, rst, din );
}
#[test(test_hier)]
module test_hier {
inst clk: $tb::clock_gen;
inst rst: $tb::reset_gen ( clk );
var din: logic<4>;
inst dut: Top ( clk, rst, din );
initial {
rst.assert();
din = 4'b0001;
clk.next();
$assert(dut.u_sub.internal_reg == 4'h2, "unexpected value");
$display("internal_reg = %h", dut.u_sub.internal_reg);
$finish();
}
}
参照した値は他の式と同じように使えます。例えば $assert や $display の引数、if の条件、ビット選択の対象などです。
以下の制限があります。
- 階層参照はテストモジュールの
initialブロックの中でのみ使えます。always_combのような RTL のコンテキストや、テストモジュールでないモジュールの中で使うとinvisible_identifierになります。 - 関数の本体は RTL からの呼び出しと共有されるため、関数の中では使えません。
- インスタンス配列は階層パスの一部にできません。
階層参照はその信号への参照として数えられません。そのため、階層参照でしか読まれない信号は unused_variable として報告されます。これは #[allow(unused_variable)] 属性で抑制できます。
ファイル出力
$tb::file はネイティブテスト中に出力ファイルを書き込むためのファイルハンドルです。clock_gen や reset_gen とは異なり var で宣言し、initial ブロック内で開いて書き込み、閉じます。
f.open(name)— ファイルnameを書き込み用に開く(既存の内容は切り詰める)f.append(name)— ファイルnameを書き込み用に開く(既存の内容に追記する)f.write(format, args...)—$displayと同じ書式でフォーマットしたテキストを書き込むf.flush()— バッファされた出力をディスクへフラッシュするf.close()— ファイルを閉じる
#[test(test_file)]
module test_file {
var f: $tb::file;
initial {
f.open("out.txt");
f.write("hex=%h dec=%d\n", 8'hAB, 8'd42);
f.close();
f.append("out.txt");
f.write("appended\n");
f.flush();
f.close();
$finish();
}
}
他の $tb::* コンポーネントと同様に、$tb::file は #[test] モジュール内でのみ使用できます。
乱数の生成
$tb::random はネイティブテスト用の乱数生成器です。値の型は宣言時にジェネリック引数として与えます。型は64ビット以下の2値の整数型でなければなりません。例えば u8–u64、i8–i64、bbool、または N が64以下の bit<N> です。4値の型(logic / lbool)、浮動小数点型、64ビットを超える幅は拒否されます。
var で宣言し、initial ブロックの中で使います。
r.seed(value)— シードを設定するr.get()— 要素型の全範囲にわたる一様乱数を返すr.get_range(min, max)— 両端を含む範囲min..=maxの一様乱数を返すr.get_seed()— 現在のシードを返す
#[test(test_random)]
module test_random {
var r: $tb::random::<u32>;
var x: u32 ;
initial {
r.seed(42);
x = r.get();
x = r.get_range(0, 99);
$finish();
}
}
特定のビット幅を使う場合は、先に型に束縛します(bit<N> をジェネリック引数として直接書くことはできません)。
#[test(test_random_width)]
module test_random_width {
gen my_t: type = bit<12>;
var r : $tb::random::<my_t>;
var x : my_t ;
initial {
x = r.get(); // 12-bit random
$finish();
}
}
--seed と [test].seed のどちらも指定しない場合、各生成器は実行ごとにランダムな基準シードから初期化されるため、実行を跨いだ再現性はありません。--seed、[test].seed、または r.seed(...) で明示的にシードを与えると、生成される系列が再現可能になります。他の $tb::* コンポーネントと同様に、$tb::random は #[test] モジュールの中でのみ使えます。
SystemVerilogテスト
SystemVerilog テストは inline 指定子で記述することができます。ブロックのトップレベルモジュールはテスト名と同じでなければなりません。
$info、$warning、$error、$fatal システム関数によるメッセージは Veryl コンパイラにより実行ログとして表示されます。$error と $fatal の呼び出しはテストの失敗として扱われます。
以下の例では SystemVerilog のソースコードを embed 宣言で埋め込み、テストとしてマークしています。
#[test(test1)]
embed (inline) sv{{{
module test1;
initial begin
assert (0) else $error("error");
end
endmodule
}}}
cocotb テスト
cocotb テストは cocotb 指定子で記述することができます。テスト対象のモジュール名は #[test] アトリビュートの第二引数で指定します。
#[test(test1, ModuleA)]
embed (cocotb) py{{{
# cocotb code
}}}