テストベンチのテクニック集

(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] ←──── 書き込み
└─────────────┘
graph LR subgraph テストベンチ direction LR REG[TB側ドライバ
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 文は回数の決まったループです。終了を待たずにループから抜け出したいときは、beginend にブロック名をつけ、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)

← 起動信号 TRIG
DUT
終了信号 DONE →

テストベンチ記述

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
→ 論理合成ツール → 検証対象(合成後の回路)
↓ シミュレータ ↓ シミュレータ
期待値ファイル
(検証対象の出力)
← 比較 → 検証対象の出力
graph TD subgraph 自動期待値比較フロー STIMULUS[入力パターン生成] --> DUT[設計回路] STIMULUS --> GOLDEN(期待値ファイル
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ブロック図

端子説明
RES1非同期リセット(入力)
CLK (1Hz)1クロック(入力)
SEC14秒桁(出力)
SEC10310秒桁(出力)
MIN14分桁(出力)
MIN10310分桁(出力)

期待値ファイル "pattern"(MIN10 MIN1 SEC10 SEC1)

MIN10MIN1SEC10SEC1
00000001010111
00000001011000
00000001011001
00000010000000
00000010000001
00000010000010
00000010000011
タイミングチャート(取り込みタイミング)
     ←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
一致比較↑    ↑    ↑    ↑    ↑    ↑
    
期待値ファイル "pattern" → 期待値を格納する配列 "mem"

(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に出力される値をシミュレーションで求め、空欄に記入する。

シミュレーション結果表

DINRDWRDIO
動作1write0110010110
read zzzz100110
動作2read 0011100x1x
動作3writezzzz01zzzz
read zzzz10zzzz

シミュレーション波形(bidir)

時間軸: 0 〜 10000以上
信号名          |← 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

設問 10〜11ページで解説した期待値の比較を実行する。回路記述に誤りがあるので、途中でエラーを表示する。そのときの回路出力と、期待値をひかえる。
たいへん
よくでき
ました!!

検証対象・ファイル構成

  • 回路記述: timer.v, count60.v
  • テストベンチ: timer_test.v
  • 期待値ファイル: pattern

エラー表示例(空欄に記入する):

DUT = 00 : 00  (回路出力)
PAT = 01 : 00  (期待値)

期待値ファイル "pattern"(MIN10 MIN1 SEC10 SEC1)

MIN10MIN1SEC10SEC1
00000001010111
00000001011000
00000001011001
00000010000000
00000010000001
00000010000010
00000010000011

シミュレーション波形(TIMER)

時間軸: 0 〜 50000以上(CLK: 1Hz相当)
信号名      | 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コードとして扱われ、連接演算でファイル名を動的生成できる
もふねこ

お疲れさま!テストベンチの各種テクニック、マスターできたかな?🐾
ファイル入出力やメモリの扱いなど、実践的な検証には欠かせない技術だよ!