テストベンチのテクニック集
(1)ここで学ぶ内容
概要
- 双方向端子のテストや期待値の自動比較など、テストベンチの記述テクニックを学ぶ
目標
以下の項目を記述できる
- 入力信号の印加遅延
- 双方向端子の記述
disableを用いたループ制御- 期待値の自動比較
- 文字列を扱った処理
修了判定1
双方向端子の検証を体験する
修了判定2
期待値の自動比較テストベンチを用いて、不具合のある回路を検証し、症状を確認する
テストベンチはプログラミングだという話はもう聞いたと思います。プログラミングと同じようにテストベンチにもいろいろな記述テクニックがあります。とても役に立つ記述テクニックがありますので、しっかり覚えてください。
(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] ←──── 書き込み └─────────────┘
reg data_out;reg out_en;]
WIRE[TB側ネットwire data_inout;]
REG -->|assign data_inout = out_en ? data_out : 1'bz;| WIRE
WIRE <-->|双方向接続| DUT_PORT[DUT 双方向端子inout data]
end
style DUT_PORT fill:#e8f5e9,stroke:#388e3c,stroke-width:2px;
style REG fill:#e3f2fd,stroke:#1976d2;
テストベンチ記述
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 |
→ 論理合成ツール → | 検証対象(合成後の回路) |
|---|---|---|
| ↓ シミュレータ | ↓ シミュレータ | |
| 期待値ファイル (検証対象の出力) |
← 比較 → | 検証対象の出力 |
expected.dat) DUT --> OUT[回路出力] GOLDEN --> EXPECT[期待値読み出し] OUT --> COMP{{比較器
if (OUT !== EXPECT)}}
EXPECT --> COMP
COMP -->|不一致| ERR[エラーメッセージ表示("ERROR!");]
COMP -->|一致| PASS[パス]
end
style DUT fill:#e8f5e9,stroke:#388e3c,stroke-width:2px;
style ERR fill:#ffebee,stroke:#c62828;
期待値を用いた検証は多くの場合、論理合成の前後(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 | |‾
📌 まとめ
- 入力信号はクロックエッジから
DELAY(周期の1/10程度)だけずらして印加することで、シミュレーション結果が確実になる - 双方向端子の検証には「ハイインピーダンス代入法」と「
bufif0接続法」の2方式がある disable文でループを途中脱出・残り処理スキップができる- 期待値による自動比較(
$fstrobeで保存 →$readmembで読み込み → 一致比較)でRTL/ゲートレベル検証を効率化できる - 文字列は8ビットASCIIコードとして扱われ、連接演算でファイル名を動的生成できる
お疲れさま!テストベンチの各種テクニック、マスターできたかな?🐾
ファイル入出力やメモリの扱いなど、実践的な検証には欠かせない技術だよ!
