テストベンチのテクニック集
(1)ここで学ぶ内容
概要
- 双方向端子のテストや期待値の自動比較など、テストベンチの記述テクニックを学ぶ
目標
以下の項目を記述できる
- 入力信号の印加遅延
- 双方向端子の記述
disableを用いたループ制御- 期待値の自動比較
- 文字列を扱った処理
修了判定1
双方向端子の検証を体験する
修了判定2
期待値の自動比較テストベンチを用いて、不具合のある回路を検証し、症状を確認する
テストベンチはプログラミングだという話はもう聞いたと思います。プログラミングと同じようにテストベンチにもいろいろな記述テクニックがあります。とても役に立つ記述テクニックがありますので、しっかり覚えてください。
まず実験:期待値比較で不明値Xを捕まえる
期待値との自動比較は、値が違うときだけでなく、値が決まっていないときにも効きます。ただし効かない場合もあります。ここでは、リセットのある加算とリセットのない加算を同じ回路に入れて、不明値Xがどこまで見つかるかを動かせるサンプルで確認します。Xそのものの扱いはシミュレーションモデルの章でも扱っています。
X伝搬の観測用回路と自動判定テストベンチをダウンロード(ZIP)
回路の仕様
good_accとgood_sumはrst_n=0で0に戻します。以後dinを足し続けます(4ビットで折り返し)。bad_accにはリセットがありません。同じ加算をします。- 条件に使う内部信号
modeにもリセットがありません。set_mode=1のサイクルの立ち上がりで1になります。 bad_flagはif (mode)で1か0を出します。
回路本体 x_demo.v を見る
`timescale 1ns/1ps
`default_nettype none
// Two accumulators with the same arithmetic. good_* is reset, bad_* is not.
// mode has no reset either, which is what hides the defect from the waveform.
module x_demo (
input wire clk,
input wire rst_n,
input wire [3:0] din,
input wire set_mode,
output reg [3:0] good_sum,
output reg [3:0] bad_sum,
output reg bad_flag
);
reg [3:0] good_acc;
reg [3:0] bad_acc; // no reset: starts as X in simulation
reg mode; // no reset: starts as X in simulation
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
good_acc <= 4'd0;
good_sum <= 4'd0;
end else begin
good_acc <= good_acc + din;
good_sum <= good_acc + din;
end
end
always @(posedge clk) begin
bad_acc <= bad_acc + din;
bad_sum <= bad_acc + din;
if (set_mode) mode <= 1'b1;
end
// An if statement treats an X condition as false, so bad_flag stays a
// clean 0 even while mode itself is unknown.
always @(posedge clk) begin
if (mode) bad_flag <= 1'b1;
else bad_flag <= 1'b0;
end
endmodule
`default_nettype wire
テストベンチ tb_x_demo.v を見る
`timescale 1ns/1ps
`default_nettype none
// This testbench documents three observations about unknown values:
// 1. the reset path matches the specification,
// 2. the unreset path returns X, which !== detects,
// 3. an X condition is hidden behind a clean 0 output.
module tb_x_demo;
reg clk = 0;
reg rst_n, set_mode;
reg [3:0] din;
wire [3:0] good_sum, bad_sum;
wire bad_flag;
reg [3:0] expected = 4'd0;
integer checks = 0;
reg guard_on = 0;
x_demo dut(.clk(clk), .rst_n(rst_n), .din(din), .set_mode(set_mode),
.good_sum(good_sum), .bad_sum(bad_sum), .bad_flag(bad_flag));
always #5 clk = ~clk;
task check;
input [3:0] actual, want;
begin
checks = checks + 1;
if (actual !== want)
$fatal(1, "FAIL: check=%0d actual=%b want=%h time=%0t",
checks, actual, want, $time);
end
endtask
task check_bit;
input actual, want;
begin
checks = checks + 1;
if (actual !== want)
$fatal(1, "FAIL: check=%0d actual=%b want=%b time=%0t",
checks, actual, want, $time);
end
endtask
// Confirms that a value really is unknown, which is the point of the demo.
task expect_unknown;
input [3:0] value;
begin
checks = checks + 1;
if (!$isunknown(value))
$fatal(1, "FAIL: check=%0d expected an unknown value, got %b time=%0t",
checks, value, $time);
end
endtask
// One cycle of stimulus, then compare the reset path with the specification.
task step;
input [3:0] value;
begin
@(negedge clk);
din = value;
@(posedge clk);
#1;
expected = (expected + value) & 4'hF;
check(good_sum, expected);
expect_unknown(bad_sum);
end
endtask
// Any X on the reset path is a real defect, so stop as soon as one appears.
always @(posedge clk) begin
#2 if (guard_on && $isunknown(good_sum))
$fatal(1, "FAIL: good_sum became unknown (%b) at %0t", good_sum, $time);
end
initial begin
$dumpfile("x_demo.vcd");
$dumpvars(0, tb_x_demo);
rst_n = 1; set_mode = 0; din = 4'd0;
#1 rst_n = 0;
#1 check(good_sum, 4'd0); // 1 reset clears the good path
expect_unknown(bad_sum); // 2 reset does not reach the bad path
@(negedge clk); rst_n = 1; guard_on = 1;
step(4'd3); // 3-4
step(4'd5); // 5-6
step(4'd9); // 7-8 wraps at 16
step(4'd1); // 9-10
// The condition inside the DUT is unknown, yet the output is a clean 0.
checks = checks + 1;
if (!$isunknown(dut.mode))
$fatal(1, "FAIL: check=%0d mode was expected to be unknown", checks);
check_bit(bad_flag, 1'b0); // 12 X-optimism hides the unknown mode
$display(" observed: dut.mode=%b bad_flag=%b bad_sum=%b good_sum=%h",
dut.mode, bad_flag, bad_sum, good_sum);
@(negedge clk); set_mode = 1'b1;
@(posedge clk); #1;
check_bit(dut.mode, 1'b1); // 13 mode is known from now on
check_bit(bad_flag, 1'b0); // 14 the flag still used the old mode
@(posedge clk); #1;
check_bit(bad_flag, 1'b1); // 15 the flag follows mode one cycle later
expect_unknown(bad_sum); // 16 the unreset adder stays unknown
$display("PASS: x_propagation (%0d checks)", checks);
$finish;
end
initial begin
#2000;
$fatal(1, "FAIL: testbench timeout");
end
endmodule
`default_nettype wire
実行方法
iverilog -g2012 -Wall -s tb_x_demo -o sim.vvp x_demo.v tb_x_demo.v
vvp sim.vvp
Windows / Icarus Verilog 13.0で2026年10月26日に実行した結果です。
observed: dut.mode=x bad_flag=0 bad_sum=xxxx good_sum=2
PASS: x_propagation (16 checks)
この1行から読み取れる3つのこと
- リセットのある側は仕様どおり。
good_sum=2は3+5+9+1を4ビットで折り返した値で、テストベンチが仕様から計算した期待値と!==で一致しました。 - リセットのない側はXのまま。
bad_sum=xxxxです。Xに何を足してもXなので、最初の1つのXが後ろの計算に伝わり続けます。これがX伝搬で、期待値比較で検出できます。 - 条件に入ったXは、出力にXを出さない。
dut.mode=xなのにbad_flag=0です。if文は条件が不明値のとき偽として扱い、else側を実行します。そのため波形にXが現れません。
3つ目は「Xの楽観視」と呼ばれる挙動です。波形にXが見えないことは、条件が確定している証明にはなりません。実機で同じ信号が1と判定されれば、シミュレーションで一度も通らなかった側の処理が動きます。
ここで偽になるのは1ビットの条件だからです。多ビットの条件式は、確定した1のビットがあれば真になることもあります。「Xを含む条件はすべて偽」とは覚えないでね(O8の補足)。
X検査のないテストベンチは合格してしまう
同じ回路に対して、bad_flag が0かどうかだけを見る簡易テストベンチを実行すると、こうなります。
PASS: naive check (bad_flag is 0)
終了コードは0で、合格の表示です。これは回路が正しいことではなく、この試験ではXを検出できないことを示しています。T1の修了判定と同じで、PASSは「書いた試験に一致した」という意味しか持たないんだ。
比較演算子を != にすると見えなくなる
good_acc のリセットを外した回路(good_sum がXのままになる不具合)で、テストベンチ側の比較を !== から != に変え、X監視ブロックも外して実行した結果です。
observed: dut.mode=x bad_flag=0 bad_sum=xxxx good_sum=x
PASS: x_propagation (16 checks)
出力がXのまま、16件すべて合格と表示されました。!= はオペランドに不明値が含まれると結果自体が不明値になり、if はそれを偽として扱うため、不一致の分岐に入りません。4値をそのまま比べる === / !== を使う理由はここにあります。
わざと壊して、テストが落ちることを確かめる
| 壊した場所 | 結果 |
|---|---|
リセットで good_sum を0にしない | リセット中の判定で FAIL: check=1 actual=xxxx want=0(2ns) |
リセットで good_acc を0にしない | X監視が FAIL: good_sum became unknown (xxxx) at 17000 で異常終了 |
加算を din+1 に変更 | FAIL: check=3 actual=0100 want=3 で異常終了 |
テストベンチ側でやっておくこと
- 期待値比較は
!==で行い、Xを不一致として扱う。 - 重要な信号は
$isunknownで監視し、Xが出た時点で$fatalで止める。このサンプルでは、リセットを解除した後のgood_sumを監視しています。 - 出力だけでなく、条件に使う信号も仕様に基づいて初期化・リセットする。出力のXは比較で見つかりますが、条件のXは見つからないことがあります。
- 「実機では0から始まるから大丈夫」を前提にしない。実機の初期値に頼った設計は、シミュレーションで確認できる範囲が狭くなります。
確認問題
bad_flag の判定を if (mode) から if (mode === 1'b1) に書き換えると、シミュレーションの結果はどう変わるでしょうか。
答え
出力は変わりません。mode がXのとき mode === 1'b1 は偽に確定するので、どちらでも else 側が実行され bad_flag は0になります。変わるのは意図の伝わり方だけで、Xを見つけられるようにはなりません。=== は4値をそのまま比べる演算子で、合成時の扱いはツールに依存します。Xを見つけたいなら、判定する側(テストベンチ)で $isunknown を使うか、リセットを足して条件を確定させます。
期待値の作り方と比較のタイミングは、このページの(7)〜(11)で順に説明します。手を動かして試すなら実習集の回路に意図した不具合を入れて、テストが落ちるかを確かめてみてね。
(2)入力信号の印加遅延
HDLで設計する回路は素子の遅延量を0として記述します。したがってテストベンチの記述では遅延が0であることを考慮する必要があります。
非同期リセット同期イネーブル付き4ビットカウンタに対し、クロックの立ち上がりと同時にリセット信号やイネーブル信号が変化している場合、シミュレーション結果が不確実になります。そこで確実に動作させるために入力信号をクロックエッジから DELAY だけずらして記述します。
カウンタの回路記述
reg [3:0] CNT4;
always @( posedge CK or posedge RES ) begin
if( RES==1'b1 )
CNT4 <= 4'h0;
else if( ENBL==1'b1 )
CNT4 <= CNT4 + 4'h1;
end
テストベンチ
parameter STEP=1000, DELAY=100;
initial begin
RES = 0; ENBL = 0;
#STEP;
#DELAY RES = 1;
#STEP RES = 0;
ENBL = 1;
...
end
←#STEP→
←#DELAY→
CK _____|‾|_|‾|_|‾|_|‾|_|‾|_
RES ______|‾‾‾‾‾‾‾‾|____________
ENBL ________________|‾‾‾‾‾‾‾‾‾‾
CNT4 x x x 0 1 2
DELAYはクロック周期の10分の1程度(例: STEP=1000 に対し DELAY=100)- クロックCKとENBL変化点がずれるため、確実に動作する
- ゲートレベル検証時にもほぼそのまま使用できる
(3)双方向端子の検証1
検証対象には入力と出力を共通にした双方向端子をもつものがあります。8ビットの双方向端子DIOがあります。この端子にはwire宣言した信号を接続します。さらにこのwire信号に別に宣言したreg信号を接続します。
回路図(検証対象 TAISHO U1)
TAISHO U1 ┌─────────────┐ │outen │──→ DIO[7:0] ──────→ 読み出し │ │ wire │ ←────┤8 DIO[7:0] │ │ reg │ │←── DIN[7:0] ←──── 書き込み └─────────────┘
テストベンチ記述
wire [7:0] DIO;
reg [7:0] DIN;
assign DIO = DIN;
TAISHO U1( .DIO(DIO), ... );
initial begin
// 入力状態
DIN = 8'h00;
#STEP DIN = 8'h5f;
#STEP DIN = 8'hd0;
...
// 出力状態
#STEP DIN = 8'hzz;
#STEP if ( DIO!==KITAICHI )
...
end
- 入力状態:
DINへの代入により入力を変化させる →DIOを通じて回路に入力 - 出力状態:
DINをハイインピーダンス(8'hzz)に設定 →DIOが回路の出力になる - 衝突時には不定値になるため、方向制御回路の不具合を検出しやすい
(4)双方向端子の検証2
双方向端子の検証には、検証対象の双方向バッファと同じような回路をテストベンチに持たせる方法もあります。
回路図
TAISHO U1 ┌─────────────┐ U1.outen │outen ───────┼──────────────┐ │ ←───┤ reg │ │ │ DIN[7:0] ◄──┤ │DIO[7:0] │ bufif0 │ │ wire ─────┼──────────────┘ │ DIO[7:0] │ └─────────────┘
テストベンチ記述
wire [7:0] DIO;
reg [7:0] DIN;
bufif0 ( DIO[0], DIN[0], U1.outen );
bufif0 ( DIO[1], DIN[1], U1.outen );
...
bufif0 ( DIO[7], DIN[7], U1.outen );
TAISHO U1( .DIO(DIO), ... );
initial begin
// 入力状態
DIN = 8'h00;
#STEP DIN = 8'h5f;
#STEP DIN = 8'hd0;
...
// 出力状態
#STEP if ( DIO!==KITAICHI )
...
end
bufif0:制御信号が0のときONとなるバッファ(プリミティブゲート、インスタンス名省略可)- ポートリスト:出力、入力、制御信号の順
- 制御信号は回路内部の出力バッファ制御信号
outenを引き出して接続 - この方法は
DINにハイインピーダンスを代入する手間が省けるが、信号衝突が起きないため厳密なチェックには不向き
(5)ループ制御
for 文は回数の決まったループです。終了を待たずにループから抜け出したいときは、begin〜end にブロック名をつけ、disable 文を使います。
処理ブロックへのブロック名付加
begin: ブロック名
...
end
処理の強制終了
disable ブロック名;
disable タスク名;
ループから脱出
begin: THISLOOP
for ( i=0; i<256; i=i+1 )
begin
...
if ( ... ) disable THISLOOP;
...
end
end
// 次の処理
ループの残りの処理をスキップ
for ( i=0; i<256; i=i+1 )
begin: THISLOOP
...
if ( ... ) disable THISLOOP;
...
end
(6)ループ制御の記述例
処理の終了を待つ部分にループ制御を利用した例です。処理のクロック数が確定できない場合、終了信号 DONE を監視しながら、処理待ちの上限 MAX を設けることで無限ループを防ぎます。
検証対象(DUT)
テストベンチ記述
reg TRIG; // 起動信号
wire DONE; // 終了信号
parameter STEP = 1000; // クロック周期
parameter MAX =10000; // 処理待ちの上限
integer i; // ループ変数
DUT D1( .TRIG(TRIG), .DONE(DONE), ... );
initial begin
...
#STEP TRIG = 1; // 処理の起動
begin: wait_done // 処理の終了待ち
for ( i=0; i<MAX; i=i+1 )
#STEP if ( DONE==1'b1 )
disable wait_done;
end
if ( DONE==1'b0 ) $display( "Error" );
...
end
(7)期待値の生成と比較
シミュレーションによる回路の動作確認は波形や文字を観測して行うのが一般的ですが、大量のデータを画面上で確認することは困難で不確実です。そこであらかじめ正常動作したときの出力を期待値として用意しておき、回路出力と比較することで動作確認ができます。
検証対象(回路記述)Y=A&B |
→ 論理合成ツール → | 検証対象(合成後の回路) |
|---|---|---|
| ↓ シミュレータ | ↓ シミュレータ | |
| 期待値ファイル (検証対象の出力) |
← 比較 → | 検証対象の出力 |
期待値を用いた検証は多くの場合、論理合成の前後(RTLとゲートレベル)の検証に使われます。まず記述された回路の動作確認を行い、正常ならば期待値を抽出。論理合成後のゲート回路でも同じテストベンチを用いて出力を保存し、これらのファイルを比較することで動作の一致を確認します。
(8)期待値の保存
例として60分タイマーのカウンタ部 TIMER を使います。RESは非同期リセット信号で全桁を0に初期化します。CLKには1HZの信号が入力され立ち上がりで出力がカウントアップします。
TIMERブロック図
| 端子 | 幅 | 説明 |
|---|---|---|
| RES | 1 | 非同期リセット(入力) |
| CLK (1Hz) | 1 | クロック(入力) |
| SEC1 | 4 | 秒桁(出力) |
| SEC10 | 3 | 10秒桁(出力) |
| MIN1 | 4 | 分桁(出力) |
| MIN10 | 3 | 10分桁(出力) |
期待値ファイル "pattern"(MIN10 MIN1 SEC10 SEC1)
| MIN10 | MIN1 | SEC10 | SEC1 |
|---|---|---|---|
| 000 | 0000 | 101 | 0111 |
| 000 | 0000 | 101 | 1000 |
| 000 | 0000 | 101 | 1001 |
| 000 | 0001 | 000 | 0000 |
| 000 | 0001 | 000 | 0001 |
| 000 | 0001 | 000 | 0010 |
| 000 | 0001 | 000 | 0011 |
←STEP→
CLK _|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_
SEC1 7 8 9 0 1 2 3
SEC10 5 0
MIN1 0 1
MIN10 0
取り込み↑ ↑ ↑ ↑ ↑ ↑
(9)期待値保存の記述
parameter STEP = 1000; // クロックの周期
parameter DELAY = 100; // 入力信号の遅延
parameter DELAY2 = 2; // 期待値取り込み位置
parameter NUM = 3600+100; // 計時の1周+α
integer i, fd; // ファイル変数など
initial begin
RES = 0;
#DELAY RES = 1; // リセット作成
#STEP RES = 0;
#(STEP*NUM) // 計時の1周+αで終了
$finish;
end
initial begin
fd = $fopen( "pattern" );
#(STEP-DELAY2);
for( i=0; i<NUM; i=i+1 )
#STEP $fstrobe( fd, "%b%b%b%b",
MIN10, MIN1, SEC10, SEC1 );
end
$fopenで期待値ファイル "pattern" を新規作成#(STEP-DELAY2)でクロックの立ち上がり直前まで遅延for文で1周期ごとに$fstrobeでファイルに書き出し- 取り込み位置は遅延のついた合成後の回路検証も考慮している
(10)期待値の比較
前ページと同じ TIMER 回路に対し、期待値ファイル "pattern" を $readmemb でレジスタ配列 "mem" に読み込み、1クロックごとに出力と期待値を比較します。
←STEP→
CLK _|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_
SEC1 7 8 9 0 1 2 3
SEC10 5 0
MIN1 0 1
MIN10 0
一致比較↑ ↑ ↑ ↑ ↑ ↑
(11)期待値比較の記述
parameter STEP = 1000; // クロックの周期
parameter DELAY = 100; // 入力信号の遅延
parameter DELAY2 = 2; // 期待値取り込み位置
parameter NUM = 3600+100; // 計時の1周+α
integer i, fd; // ファイル変数など
reg [13:0] mem[0:NUM-1]; // 期待値格納配列
reg [13:0] tmp; // 期待値表示用
initial begin
RES = 0;
#DELAY RES = 1; // リセット作成
#STEP RES = 0;
#(STEP*NUM) // 計時の1周+αで終了
$finish;
end
initial begin
$readmemb( "pattern", mem );
#(STEP-DELAY2);
for( i=0; i<NUM; i=i+1 )
#STEP if ( {MIN10, MIN1, SEC10, SEC1} !== mem[i] ) begin
tmp = mem[i];
$display( "Error! DUT=%h%h:%h%h PAT=%h%h:%h%h",
MIN10, MIN1, SEC10, SEC1,
tmp[13:11], tmp[10:7], tmp[6:4], tmp[3:0] );
$finish;
end
end
- 期待値と不一致のとき表示例:
Error! DUT=00:00 PAT=01:00 - DUT = 検証対象の出力、PAT = 期待値
- 比較位置を次のクロックの立ち上がり直前にすることで、合成後回路の遅延に対応
(12)文字列
VerilogHDLではプログラミング言語と同じように文字列を扱うことができます。扱える文字は英数字と一部の記号で、1文字8ビットのASCIIコードで表現します。
基本的な文字列定数
reg [31:0] str;
str = "GOOD";
| "GOOD" → str[31:0] | |||
|---|---|---|---|
| 'G' 8'h47 | 'O' 8'h4f | 'O' 8'h4f | 'D' 8'h44 |
文字列の代入と表示
reg [8*4:1] str1, str2;
initial begin
str1 = "HDL";
str2 = "hdLab";
$display( "%s", str1 );
$display( "%s", str2 );
end
| str1[32:1](3文字 → 上位に0補完) | |||
|---|---|---|---|
| 8'h00 | 'H' 8'h48 | 'D' 8'h44 | 'L' 8'h4c |
| str2[32:1](5文字 → 上位が欠落) | |||
| 'd' 8'h64 | 'L' 8'h4c | 'a' 8'h61 | 'b' 8'h62 |
連接演算によるファイル名生成
parameter filename = "sample";
initial begin
$readmemh ( { filename, ".hex" } , mem1 );
...
$writememh( { filename, ".out" } , mem2 );
end
パラメータ宣言で文字列定数を宣言し、連接演算({ })で拡張子の異なるファイル名を動的に生成できます。
(13)修了判定1
bidir 回路の仕様
- 内部に4ビットのレジスタを持ち、読み書きできる回路
- データ信号が双方向
- WR = 1 で書き込み
- RD = 1 で読み出し
- 回路記述:
bidir.v - テストベンチ:
bidir_test.v
表のような信号を順次与えたとき、読み出し状態でDIOに出力される値をシミュレーションで求め、空欄に記入する。
シミュレーション結果表
| DIN | RD | WR | DIO | ||
|---|---|---|---|---|---|
| 動作1 | write | 0110 | 0 | 1 | 0110 |
| read | zzzz | 1 | 0 | 0110 | |
| 動作2 | read | 0011 | 1 | 0 | 0x1x |
| 動作3 | write | zzzz | 0 | 1 | zzzz |
| read | zzzz | 1 | 0 | zzzz |
シミュレーション波形(bidir)
信号名 |← 0 5000 10000→
DIN[3:0] | 0110 | 0011
DIN[3] | ‾‾‾‾‾‾‾‾ |___
DIN[2] | ‾‾‾‾‾‾‾‾ | ‾
DIN[1] | ___ |___
DIN[0] | ___ | ‾‾
RD |______‾‾‾‾‾‾‾‾‾‾‾‾‾
WR | ‾‾‾‾‾‾_______________
DIO[3:0] | 0110 0110 0x1x 0011
DIO[3] | ‾‾‾‾ ‾‾‾‾
DIO[2] | ‾‾‾‾ ‾‾‾‾ ←赤(不定)
DIO[1] | ___
DIO[0] | ___
※ 動作2の read 時、DIN=0011 が入力状態のまま残っているため DIO[3:0] に不定値(0x1x)が発生する
(14)修了判定2
検証対象・ファイル構成
- 回路記述:
timer.v,count60.v - テストベンチ:
timer_test.v - 期待値ファイル:
pattern
エラー表示例(空欄に記入する):
PAT = 01 : 00 (期待値)
期待値ファイル "pattern"(MIN10 MIN1 SEC10 SEC1)
| MIN10 | MIN1 | SEC10 | SEC1 |
|---|---|---|---|
| 000 | 0000 | 101 | 0111 |
| 000 | 0000 | 101 | 1000 |
| 000 | 0000 | 101 | 1001 |
| 000 | 0001 | 000 | 0000 |
| 000 | 0001 | 000 | 0001 |
| 000 | 0001 | 000 | 0010 |
| 000 | 0001 | 000 | 0011 |
シミュレーション波形(TIMER)
信号名 | 0 10000 20000 30000 40000 50000
CLK | ‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_(高速クロック繰り返し)
RES | |‾|_______________________________________
MIN10 | 0
MIN1 | 0
SEC10 | 0 |1 |2 |3 |4 |5
SEC1 | 0 1 2 3 4 5 6 7 8 9 0 1 2...(毎秒カウントアップ)
SEC_CARRY | |‾
実習で確かめる、つまずきの補足
PASSの条件、未知値、valid、タイムアウト
テストを走らせたことと合格したことを分けよう。期待値を仕様から作り、actual !== expected でX/Zを含む不一致を検出し、失敗では $fatal(1, ...) で非ゼロ終了する。最後に無条件でPASSを出すTBでは、途中のFAILが埋もれてしまうんだ。
結果にvalidが付く場合は、まず出力validが期待通りか確認し、その値が1のときだけデータを比較するよ。valid=0のサイクルのデータが任意という仕様なら、そこを勝手に0と決めてテストしない。入力データを変え、validの隙間も入れて、データとvalidのずれを検出しよう。
クロック停止や応答が来ない不具合にはタイムアウトが必要。待機中にクロックそのものが止まっても終了できるよう、別initialの #...; $fatal(...) も用意するよ。期待値の計算にDUTの同じコードを丸写しすると同じバグを見逃すため、仕様の表や独立した計算から求めよう。
FAIL後は最初の不一致時刻から、入力→内部状態→出力をVCDで追う。ログには時刻、試験名、期待値、実値を残してね。T1の40チェック と 実習集 を変更して、意図したバグをTBが捕まえるかも試してみよう。
📌 まとめ
- 入力信号はクロックエッジから
DELAY(周期の1/10程度)だけずらして印加することで、シミュレーション結果が確実になる - 双方向端子の検証には「ハイインピーダンス代入法」と「
bufif0接続法」の2方式がある disable文でループを途中脱出・残り処理スキップができる- 期待値による自動比較(
$fstrobeで保存 →$readmembで読み込み → 一致比較)でRTL/ゲートレベル検証を効率化できる - 文字列は8ビットASCIIコードとして扱われ、連接演算でファイル名を動的生成できる
お疲れさま!テストベンチの各種テクニック、マスターできたかな?🐾
ファイル入出力やメモリの扱いなど、実践的な検証には欠かせない技術だよ!
