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

(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つのこと

  1. リセットのある側は仕様どおり。 good_sum=2 は3+5+9+1を4ビットで折り返した値で、テストベンチが仕様から計算した期待値と !== で一致しました。
  2. リセットのない側はXのまま。 bad_sum=xxxx です。Xに何を足してもXなので、最初の1つのXが後ろの計算に伝わり続けます。これがX伝搬で、期待値比較で検出できます。
  3. 条件に入った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値をそのまま比べる === / !== を使う理由はここにあります。

わざと壊して、テストが落ちることを確かめる

2026年10月26日 Windows / Icarus Verilog 13.0での実行結果
壊した場所結果
リセットで 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] ←──── 書き込み
└─────────────┘
T4 図1:双方向端子をテストベンチからつなぐときの図。テストベンチ側で宣言したドライバ用のregが三状態のassignを通ってテストベンチ側のwireにつながり、そのwireが双方向接続でDUTのinout端子につながる。
T4 図1:双方向端子をテストベンチからつなぐときの図。横に長い図はスクロールして確認できます。

テストベンチ記述

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)

← 起動信号 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
→ 論理合成ツール → 検証対象(合成後の回路)
↓ シミュレータ ↓ シミュレータ
期待値ファイル
(検証対象の出力)
← 比較 → 検証対象の出力
T4 図2:自動期待値比較フローの図。入力パターン生成が設計回路と期待値ファイル expected.datに渡る。設計回路は回路出力、期待値ファイルは期待値読み出しとなり、比較器 OUT !== EXPECTに入り、不一致でエラーメッセージ表示 ERROR、一致でパスとなる。
T4 図2:自動期待値比較フローの図。横に長い図はスクロールして確認できます。

期待値を用いた検証は多くの場合、論理合成の前後(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   |                                            |‾
    

実習で確かめる、つまずきの補足

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コードとして扱われ、連接演算でファイル名を動的生成できる
もふねこ

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