タスクでテストベンチを効率化する

(1)ここで学ぶ内容

概要

  • テストベンチの記述効率を高める task について理解する
  • CPUのバス動作など task の記述例を学ぶ

目標

以下の項目を理解し説明できる

  • task の定義と呼び出し
  • task の引き数
  • task によるテストベンチの記述
  • task と function の共通点と相違点

修了判定1

本文に示した基本タスクの動作確認をする

⚠️ 修了判定:バスReadアクセスのtask化

設問:CPUバスのReadアクセス(指定したアドレスからデータを読み出す)を行う task である cpu_read を記述せよ。アドレスを引数で受け取り、読み出したデータを別の引数で返すこと。

解答例

task cpu_read;
    input  [7:0] adr;    // 読み出し先アドレス
    output [7:0] dat;    // 読み出したデータ
    begin
        // アドレスと制御信号をセット
        ADDR = adr;
        CS_N = 1'b0;
        RD_N = 1'b0;
        WR_N = 1'b1;
        
        // 1サイクル待ってデータを取得
        @(posedge CLK);
        dat = DATA_OUT;
        
        // 制御信号をネゲート
        CS_N = 1'b1;
        RD_N = 1'b1;
        @(posedge CLK);
    end
endtask

修了判定2

データ入力付きカウンタの、データ入力動作をタスクで記述する

修了判定3

シミュレータを使って、データ入力タスクの動作確認を行う
条件:修了判定2を先に実施する

普通のプログラミングではサブルーチンをつかって効率的に処理を表現しています。VerilogHDLのタスクもサブルーチンと同じように使えます。タスクを覚えるとテストベンチがすっきりかけます。

まず実験:バスの書き込みと読み出しをtaskにまとめる

このページの(7)では、固定の遅延だけでバス動作を組み立てています。ここでは同じ端子構成をクロック同期の回路として用意し、書き込み・読み出し・同時アクセスをtaskにまとめた、動かせるサンプルで確認します。読み出した値は output 引き数で呼び出し側へ返します。T1のカウンタ実習を先に動かしておくと、判定の形が同じなので読みやすくなります。

4レジスタのバスモデルと自動判定テストベンチをダウンロード(ZIP)

回路の仕様

  • clk の立ち上がりで動作します。rst_n=0 で非同期に全レジスタと dout を0へ戻します。
  • a[1:0] で8ビットのレジスタ4本を選びます。
  • web=0 のサイクルで、選んだレジスタへ din を書き込みます(負論理)。
  • reb=0 のサイクルで、選んだレジスタの値を dout へ出します(負論理)。
  • 同じサイクルで web=0 と reb=0 を与えると、dout には書き込み前の値が出ます。
回路本体 regfile4.v を見る
`timescale 1ns/1ps
`default_nettype none
// Four 8-bit registers. web and reb are active low.
module regfile4 (
    input  wire       clk,
    input  wire       rst_n,
    input  wire [1:0] a,
    input  wire [7:0] din,
    input  wire       web,
    input  wire       reb,
    output reg  [7:0] dout
);
    reg [7:0] mem [0:3];
    integer i;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            for (i = 0; i < 4; i = i + 1) mem[i] <= 8'h00;
        end else if (!web) begin
            mem[a] <= din;
        end
    end

    // The read port is clocked too, so a read in the same cycle as a write
    // returns the value stored before that write.
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) dout <= 8'h00;
        else if (!reb) dout <= mem[a];
    end
endmodule
`default_nettype wire
テストベンチ tb_regfile4.v を見る
`timescale 1ns/1ps
`default_nettype none
module tb_regfile4;
    reg clk = 0;
    reg rst_n, web, reb;
    reg [1:0] a;
    reg [7:0] din;
    wire [7:0] dout;
    reg [7:0] got;
    integer checks = 0;

    regfile4 dut(.clk(clk), .rst_n(rst_n), .a(a), .din(din),
                 .web(web), .reb(reb), .dout(dout));
    always #5 clk = ~clk;

    task check;
        input [7:0] actual, expected;
        begin
            checks = checks + 1;
            if (actual !== expected)
                $fatal(1, "FAIL: check=%0d actual=%h expected=%h time=%0t",
                       checks, actual, expected, $time);
        end
    endtask

    // Write: drive on the falling edge so the rising edge samples stable inputs.
    // #1 is specific to this testbench's 10 ns clock and 1 ns time unit.
    task bus_write;
        input [1:0] addr;
        input [7:0] data;
        begin
            @(negedge clk);
            a = addr; din = data; web = 1'b0; reb = 1'b1;
            @(posedge clk);
            #1 web = 1'b1;
        end
    endtask

    // Read: the value read is returned to the caller through an output argument.
    task bus_read;
        input  [1:0] addr;
        output [7:0] data;
        begin
            @(negedge clk);
            a = addr; reb = 1'b0; web = 1'b1;
            @(posedge clk);
            #1 data = dout;
            reb = 1'b1;
        end
    endtask

    // Write and read the same address in one cycle.
    task bus_write_read;
        input  [1:0] addr;
        input  [7:0] data;
        output [7:0] read_data;
        begin
            @(negedge clk);
            a = addr; din = data; web = 1'b0; reb = 1'b0;
            @(posedge clk);
            #1 read_data = dout;
            web = 1'b1; reb = 1'b1;
        end
    endtask

    initial begin
        $dumpfile("regfile4.vcd");
        $dumpvars(0, tb_regfile4);
        rst_n = 1; web = 1; reb = 1; a = 2'd0; din = 8'h00;
        #1 rst_n = 0;
        #1 check(dout, 8'h00);                    // 1 asynchronous reset clears dout
        @(negedge clk); rst_n = 1;
        bus_read(2'd0, got); check(got, 8'h00);   // 2-5 all registers start at zero
        bus_read(2'd1, got); check(got, 8'h00);
        bus_read(2'd2, got); check(got, 8'h00);
        bus_read(2'd3, got); check(got, 8'h00);
        bus_write(2'd0, 8'h03);
        bus_write(2'd1, 8'h5A);
        bus_write(2'd2, 8'hFC);
        bus_write(2'd3, 8'hA5);
        bus_read(2'd0, got); check(got, 8'h03);   // 6-9 each address keeps its own value
        bus_read(2'd1, got); check(got, 8'h5A);
        bus_read(2'd2, got); check(got, 8'hFC);
        bus_read(2'd3, got); check(got, 8'hA5);
        @(negedge clk); a = 2'd1; din = 8'h00; web = 1'b1; reb = 1'b1;
        @(posedge clk); #1;
        bus_read(2'd1, got); check(got, 8'h5A);   // 10 no write while web is high
        bus_write_read(2'd2, 8'h11, got);
        check(got, 8'hFC);                        // 11 same-cycle read returns the old value
        bus_read(2'd2, got); check(got, 8'h11);   // 12 the write is visible one cycle later
        @(negedge clk); a = 2'd0; reb = 1'b1;
        @(posedge clk); #1 check(dout, 8'h11);    // 13 dout holds while reb is high
        @(negedge clk); rst_n = 1'b0;
        #1 check(dout, 8'h00);                    // 14 asynchronous reset during traffic
        @(negedge clk); rst_n = 1'b1;
        bus_read(2'd3, got); check(got, 8'h00);   // 15 the registers were cleared as well
        bus_write(2'd3, 8'h7E);
        bus_read(2'd3, got); check(got, 8'h7E);   // 16 writes work again after release
        $display("PASS: regfile4 bus tasks (%0d checks)", checks);
        $finish;
    end

    initial begin
        #2000;
        $fatal(1, "FAIL: testbench timeout");
    end
endmodule
`default_nettype wire

実行方法

ZIP内の2ファイルがあるフォルダで実行します。回路名を置換する必要のない完成例です。

iverilog -g2012 -Wall -s tb_regfile4 -o sim.vvp regfile4.v tb_regfile4.v
vvp sim.vvp

Windows / Icarus Verilog 13.0で2026年10月26日に実行し、PASS: regfile4 bus tasks (16 checks) と波形ファイル regfile4.vcd の生成を確認しました。合成・配置配線・実機の確認は含みません。

3本のtaskの役割

刺激と判定を分けておくと、シナリオ側は「何を試すか」だけになります
task引き数していること
bus_writeinput アドレス・データ立ち下がりで信号を変え、立ち上がりで取り込ませてから web を戻す
bus_readinput アドレス/output データ読み出した値を呼び出し側の変数へ返す
bus_write_readinput 2つ/output 1つ同じサイクルで書き込みと読み出しを同時に行う

このページの修了判定1にある cpu_read と同じで、読み出し値は output 引き数で受け取ります。呼び出し側は bus_read(2'd1, got); と書き、続けて got を期待値と比べるだけになります。

16件の判定で見ていること

  1. 非同期リセットで dout が0になる
  2. リセット後は4本すべてが0である(判定2〜5)
  3. 4本に別々の値を書き、アドレスごとに読み戻せる(判定6〜9)
  4. web=1 の間はアドレスとデータを変えても書き換わらない
  5. 同じサイクルの書き込みと読み出しでは、読み出し側に書き込み前の値が出る
  6. その書き込みは次のサイクルの読み出しで見える
  7. reb=1 の間は dout が前の値を保持する
  8. 動作中の非同期リセットで出力とレジスタが0に戻り、解除後は再び書ける(判定14〜16)

入力はクロックの立ち下がりで変更し、判定は立ち上がりの1ns後に行います。この1nsはこの例の10ns周期・1ns単位に合わせたシミュレーション上の待ち時間で、実回路の遅延を測った値ではありません。比較には !== を使い、不定値Xも不一致として検出します。終わらない場合は2000nsでタイムアウトします。

固定遅延で書いた(7)との違い

(7)の write_reg は #25 や #200 という固定の遅延でWEBのパルスを作ります。検証対象の想定タイミングが変わると、taskの中の数値を書き直すことになります。クロックに同期して刺激を変えるtaskにしておけば、周期を変えても同じtaskで試験でき、判定の位置もクロックを基準に決められます。どちらが正しいというより、何を基準に時間を決めるかの違いです。

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

テストベンチは「書いたら終わり」ではなく、不具合を入れたときに落ちることまで確かめます。以下は回路側とテストベンチ側をわざと壊したコピーを、同じ手順で実行した結果です。

2026年10月26日 Windows / Icarus Verilog 13.0での実行結果
壊した場所結果
回路の web の極性を反転FAIL: check=6 actual=00 expected=03 で異常終了
書き込み先を mem[0] に固定FAIL: check=6 actual=a5 expected=03 で異常終了
読み出しtaskの #1 を削除FAIL: check=6 actual=00 expected=03 で異常終了

3つ目に注目してね。立ち上がりと同時に dout を読むと、ノンブロッキング代入の更新前の値を取ってしまいます。それでも判定2〜5は失敗しません。読み出した値も期待値も0で、たまたま一致するからです。0以外を書いた後の判定6で初めて落ちます。試験データを全部0から始めると、この種のずれは隠れたままになります。

確認問題

同じサイクルで web=0 と reb=0 を与えたとき、dout に書き込み前の値が出るのはなぜでしょうか。

答え

読み出し側のalways文は、同じ立ち上がりで mem[a] を参照します。書き込み側はノンブロッキング代入で同じ時刻に更新されるため、参照されるのは更新前の値です。この回路ではそうなりますが、同時アクセスで新しい値を出す仕様にすることもできます。どちらにするかを決めていないと、読み出し側の設計と食い違ったままテストが書かれてしまいます。

この後の章で、taskの引き数と function との違いを順に見ていきます。T4の期待値比較と不明値Xの実習にも進めます。

(2)taskとは

initial 文の begin〜end の中に記述されたテストベンチは、記述の順番に実行されます。

記述の中で同じ処理を何回も実行することがあれば、この処理をまとめることが出来ます。そしてまとめた処理に名前をつけて、initial 文側で呼び出すことでテストベンチを簡潔にまとめることが出来ます。

この場合処理ABCが二か所で呼び出されています。戻り場所はそれぞれ呼び出した場所になります。VerilogHDLではこのような仕組みをタスクと呼びます。

initial 文(呼び出し側)

begin
1
処理ABC ←呼び出し
2
処理ABC ←呼び出し
3
end

処理ABC(タスク定義)

task
A
B
C
endtask

(3)taskの定義と呼び出し

タスクを利用するときにはタスクを定義する記述とテスト入力側で呼び出す記述が必要です。タスクもモジュールアイテムですので、モジュール内で定義します。タスクの呼び出しはステートメントですので、initial 文などの中で記述します。

テストベンチの構成

module
印加する信号の reg 宣言
テスト対象呼び出し
task
    taskの定義
endtask
initial begin
  ・テスト入力作成
  ・taskの呼び出し
end
endmodule

taskの定義

task タスク名;
    引数宣言
    内部信号宣言
begin
    処理の記述
end
endtask

taskの呼び出し

タスク名(引き数, 引き数, ...);
  • タスクの定義は task で始まり endtask で終わる
  • task に続いて、タスク名・引き数宣言・内部信号を宣言する
  • 引数や内部信号のないタスクも存在する
  • タスクの処理は多くの場合2行以上になるので begin〜end が必要
  • 引数のないタスクではタスク名とセミコロンだけで呼び出せる

(4)taskによるテストベンチ記述例

タスクの簡単な記述例を紹介します。アップダウンカウンタのテストベンチに利用した例です。このカウンタに対し、リセット→カウントアップ→リセット→カウントダウンを行っています。

アップダウン・カウンタ

端子説明
CK立ち上がりで up/down
RES1で非同期リセット
UP1のとき up、0のとき down
Qカウンタ出力(4bit)
reg  CK, RES, UP;

initial begin           // テスト入力
    UP  = 0;            // 初期設定
    reset_counter;      // カウンタのリセット
    UP  = 1;            // カウントアップ
    #(STEP*20);
    reset_counter;      // カウンタのリセット
    UP  = 0;            // カウントダウン
    #(STEP*20);
    $finish;
end

タスクの定義(カウンタのリセット)

task reset_counter;
begin
          RES = 0;
    #STEP RES = 1;
    #STEP RES = 0;
end
endtask
リセット波形(RES)
←STEP→←STEP→
        |‾‾‾‾|
RES ____| 1  |____
            
  • モジュール内で宣言した信号 RES に対し、タスク内で参照・代入できる
  • タスクの中に遅延を記述できるため、呼び出すたびにシミュレーション時刻が進む

(5)taskの引き数

引数とはタスクの呼び出し側と処理側でデータのやり取りをする仕組みです。引数はタスク名に続いて宣言します。モジュールのポートと同じく input, output, inout の3種類があります。

引数の方向

宣言意味
inputタスクの中で参照する(呼び出し側→タスク)
outputタスクの中で代入する(タスク→呼び出し側)
inoutタスクの中で参照/代入する

taskの定義(構造)

task タスク名;
    引数宣言
    内部信号宣言
begin
    処理の記述
end
endtask

taskの呼び出し

タスク名(引き数, 引き数, ...);

引数ありタスクの記述例

task count_updown;
input dir;
begin
    UP = dir;
    #(STEP*20);
end
endtask

呼び出し例

reset_counter;
count_updown(1'b1);
reset_counter;
count_updown(1'b0);
  • 呼び出し側で実際に与える引数 → 実引き数
  • 定義側で宣言した引数 → 仮引き数
実引き数に使用できる信号の種類(引数方向別)
引数方向実引き数に使用できる型
input定数、ネット型(wire)、レジスタ型(reg)
outputレジスタ型(reg)だけ
inoutレジスタ型(reg)だけ

(6)taskの効果的な利用

タスクは本格的なテストベンチの作成に効果的です。例えばCPUに接続される周辺チップを設計し、検証しているとします。CPUはこのチップに対し内部レジスタへの書き込みを行います。また、内部レジスタの読み出しを行います。

バスモデルの概念図
CPU
ADDR ═══════════════════
DATA ═══════════════════
CTRL ═══════════════════
検証対象
(内部レジスタ)
■ reg
■ reg

書き込みや読み出しの一連の動作をタスクで記述 → バスモデル
システム全体のボードレベル検証にとても役立つ

(7)taskによるバス動作の記述

検証対象の回路を想定して、タスクの記述例を紹介します。この回路には8ビットの内部レジスタが4つあります。これらを区別するための2ビットのアドレス信号A、8ビットのデータ入力DIN、データ出力DOUTがあります。また読み出し信号REB、書き込み信号WEBがあり、これらは0で動作します。

検証対象回路(DUT)

端子幅説明
A2アドレス入力
DIN8データ入力
DOUT8データ出力
REB1読み出し信号(0で動作)
WEB1書き込み信号(0で動作)

書き込みタイミング(3クロック/1データ)

     ←100nS→
CLK   _|‾|_____|‾|_____
    ←25nS→
A[1:0]  ‾‾‾‾addr‾‾‾‾‾‾
    ←25nS→
DIN[7:0] ‾‾‾data‾‾‾‾‾‾
    ←25nS→←200nS→←25nS→
WEB ‾‾‾‾‾‾‾|_____|‾‾‾‾
        
T5 図1:taskを使わない場合の図。テストベンチ initialブロック内に、ADDR=0 DATA=A、ADDR=1 DATA=B、ADDR=2 DATA=Cの3つの記述があり、それぞれ制御信号を直接記述する。
T5 図1:taskを使わない場合の図。横に長い図はスクロールして確認できます。
T5 図2:taskを使った場合の図。テストベンチ initialブロック内からtask cpu_write バスの制御手順をカプセル化を3回呼び出し、write 0 A、write 1 B、write 2 Cと添えられている。
T5 図2:taskを使った場合の図。横に長い図はスクロールして確認できます。

バスモデルの記述

// 接続信号宣言
reg  [1:0] A;
reg  [7:0] DIN;
reg        REB, WEB;
wire [7:0] DOUT;

// レジスタへの書き込みタスク
task write_reg;
input [1:0] addr;
input [7:0] data;
begin
    # 25  A   = addr;
    # 25  DIN = data;
    # 25  WEB = 0   ;
    #200  WEB = 1   ;
    # 25          ;
end
endtask

// タスクの呼び出し
initial begin
    WEB = 1;
    write_reg( 2'h0, 8'h03 );
    write_reg( 2'h2, 8'hFC );
end
  • システムクロック3周期(300ns)で1データの書き込み
  • 0番レジスタに03、2番レジスタにFCを書き込む

(8)task と function の共通点

task と function には以下の共通点があります。

  1. 内部信号を宣言して使用できる(parameterも宣言できる)
  2. 仮引き数・実引き数がある
  3. 定義部と呼び出し部がある
  4. モジュール内に記述する(モジュール外に記述するとエラー)
  5. モジュール内の信号にアクセスできる(ただし function では実行時に誤りを起こしやすいため非推奨)

task — 内部信号の宣言例

task TEMP;
    input a;
    reg   b;
begin
    b = 1;
    ...
endtask

function — 内部信号の宣言例

function FUNC;
    input a;
    reg   b;
begin
    b = 1;
    ...
endfunction

(2)仮引き数・実引き数の例(task)

task TEMP;
    input a;
    ...
endtask

initial begin
    TEMP( EN );   // EN が実引き数
    ...
end

(2)仮引き数・実引き数の例(function)

function FUNC;
    input a;
    ...
endfunction

assign Q = FUNC( EN );  // EN が実引き数

(4)モジュール内に記述(task)

module XYZ;
    ...
    task TEMP;
        input a;
        ...
    endtask
    ...
endmodule

(4)モジュール内に記述(function)

module XYZ;
    ...
    function FUNC;
        input a;
        ...
    endfunction
    ...
endmodule

(5)モジュール内の信号アクセス(task)

module XYZ;
    ...
    reg a;

    task TEMP;
        ...      // a に代入できる
    endtask
    ...
endmodule

(5)モジュール内の信号アクセス(function)

module XYZ;
    ...
    reg a;

    function FUNC;
        ...
        FUNC = a;  // 実行時の誤りを起こしやすい
    endfunction
    ...
endmodule
function では引数宣言せずにモジュール内の信号を参照するのは避けること

(9)task と function の相違点

taskfunction
(1)引き数 入力・出力・双方向すべてOK 入力だけ
(2)戻り値の数 output 宣言すればいくつでも ファンクション名に代入するので1つだけ
(3)遅延の記述 記述できる 記述できない(文法エラーになる)
(4)呼び出し側 ステートメント 式
(5)主な用途 テストベンチ 組み合わせ回路

(1)引き数

// task: 入力・出力・双方向すべてOK
task TEMP;
    input  a;
    output b;
    inout  c;
    ...
endtask
// function: 入力だけ
function FUNC;
    input a;
    ...
endfunction

(2)戻り値の数

// task: output宣言すればいくつでも
task TEMP;
    output a, b, c;
    ...
endtask
// function: ファンクション名に代入→1つだけ
function FUNC;
    ...
    FUNC = 1;
endfunction

(3)遅延の記述

// task: 遅延を記述できる
task TEMP;
    ...
    #STEP a = 0;   // OK
    ...
endtask

(3)遅延の記述

// function: 遅延を記述できない
function FUNC;
    ...
    #STEP FUNC = 1; // エラー
endfunction

(4)呼び出し側 — task はステートメント

initial begin
    ...
    COUNT( EN );   // ステートメントとして呼び出す
    ...
end

(4)呼び出し側 — function は式

always @( posedge CK )
    Q <= FUNC( EN );  // 式として使用できる
// 代入文の右辺、入力引き数、モジュール接続の入力ポートなど

(5)主な用途 — task

task WRITE_REG;
    input ADDR;
    input DATA;
    ...
endtask  // テストベンチ向き

(5)主な用途 — function

function [3:0] SUM;
    ...
    SUM = A + B;
endfunction  // 組み合わせ回路向き
task で順序回路、function でテストベンチを書くことも可能

(10)ワンポイント・アドバイス

テストベンチも「階層構造」

テストベンチ
initial begin
  ...
  応用タスク呼び出し
  ...
end
⇄
応用タスク
  • 基本タスクを呼び出して機能を起動
⇄
基本タスク
  • 内部レジスタの読み/書き
  • 外部ファイルのアクセス
↕
検証対象

テストベンチ本体から検証対象に直接アクセスせず、すべてタスクを通じて制御することで効率的な検証が行える

(11)修了判定1

設問 基本タスクの動作確認 — 本文に示したタスク write_reg と、ここに示すタスク read_reg の動作確認をする。ファイル:basic_task.v

read_reg タスクの定義

task read_reg;
input  [1:0] addr;
output [7:0] data;
begin
    # 25   A   = addr;
    #100   REB = 0   ;
    #150   data = DOUT;
           REB = 1   ;
    # 25          ;
end
endtask

読み出しタイミング(3クロック/1データ)

     ←100nS→
CLK   _|‾|_____|‾|_____
    ←25nS→
A[1:0]  ‾‾‾‾addr‾‾‾‾‾‾
         ←100nS→←150nS→←25nS→
REB  ‾‾‾‾‾‾‾|____|‾‾‾‾‾‾‾‾
DOUT[7:0]        DOUT出力
                 ↑dataへ取り込み
        

シミュレーション波形(修了判定1)

時間軸: 0 〜 2000以上 [ns]
信号名波形の変化(各ステップ)
CLK高速クロック繰り返し
A[1:0]01231302
DIN[7:0]10a5f703—
WEB書き込み期間(0で動作)読み出し期間
REB1(非活性)0で活性(読み出し)
DOUT[7:0]zz(出力なし)a50310f7

シミュレーション出力ログ(抜粋)

   0 CLK=1 DIN=xx WEB=1 REB=1 DOUT=zz
  25 CLK=1 A=0 DIN=xx WEB=1 REB=1 DOUT=zz
  50 CLK=0 A=0 DIN=10 WEB=1 REB=1 DOUT=zz
  75 CLK=0 A=0 DIN=10 WEB=1 REB=1 DOUT=zz
 100 CLK=1 A=0 DIN=10 WEB=0 REB=1 DOUT=zz
 150 CLK=0 A=0 DIN=10 WEB=0 REB=1 DOUT=zz
 200 CLK=0 A=0 DIN=10 WEB=0 REB=1 DOUT=zz
 250 CLK=0 A=0 DIN=10 WEB=0 REB=1 DOUT=zz
 275 CLK=0 A=0 DIN=10 WEB=1 REB=1 DOUT=zz
 300 CLK=1 A=0 DIN=10 WEB=1 REB=1 DOUT=zz
 325 CLK=1 A=1 DIN=10 WEB=1 REB=1 DOUT=zz
 350 CLK=0 A=1 DIN=a5 WEB=1 REB=1 DOUT=zz
 375 CLK=0 A=1 DIN=a5 WEB=0 REB=1 DOUT=zz
 400 CLK=1 A=1 DIN=a5 WEB=0 REB=1 DOUT=zz
 450 CLK=0 A=1 DIN=a5 WEB=0 REB=1 DOUT=zz
 500 CLK=1 A=1 DIN=a5 WEB=0 REB=1 DOUT=zz
 550 CLK=0 A=1 DIN=a5 WEB=0 REB=1 DOUT=zz
 575 CLK=0 A=1 DIN=a5 WEB=1 REB=1 DOUT=zz
 600 CLK=1 A=1 DIN=a5 WEB=1 REB=1 DOUT=zz
 625 CLK=1 A=2 DIN=a5 WEB=1 REB=1 DOUT=zz
 650 CLK=0 A=2 DIN=f7 WEB=1 REB=1 DOUT=zz
 675 CLK=0 A=2 DIN=f7 WEB=0 REB=1 DOUT=zz
 700 CLK=1 A=2 DIN=f7 WEB=0 REB=1 DOUT=zz
 750 CLK=0 A=2 DIN=f7 WEB=0 REB=1 DOUT=zz
 800 CLK=1 A=2 DIN=f7 WEB=0 REB=1 DOUT=zz
 850 CLK=0 A=2 DIN=f7 WEB=0 REB=1 DOUT=zz
 875 CLK=0 A=2 DIN=f7 WEB=1 REB=1 DOUT=zz
 900 CLK=1 A=2 DIN=f7 WEB=1 REB=1 DOUT=zz
 925 CLK=1 A=3 DIN=f7 WEB=1 REB=1 DOUT=zz
 950 CLK=0 A=3 DIN=03 WEB=1 REB=1 DOUT=zz
 975 CLK=0 A=3 DIN=03 WEB=0 REB=1 DOUT=zz
1000 CLK=1 A=3 DIN=03 WEB=0 REB=1 DOUT=zz
1050 CLK=0 A=3 DIN=03 WEB=0 REB=1 DOUT=zz
1100 CLK=1 A=3 DIN=03 WEB=0 REB=1 DOUT=zz
1150 CLK=0 A=3 DIN=03 WEB=0 REB=1 DOUT=zz
1175 CLK=0 A=3 DIN=03 WEB=1 REB=1 DOUT=zz
1200 CLK=1 A=3 DIN=03 WEB=1 REB=1 DOUT=zz
1225 CLK=1 A=1 DIN=03 WEB=1 REB=1 DOUT=zz
1250 CLK=0 A=1 DIN=03 WEB=1 REB=1 DOUT=zz
1300 CLK=1 A=1 DIN=03 WEB=1 REB=1 DOUT=zz
1325 CLK=1 A=1 DIN=03 WEB=1 REB=0 DOUT=a5
1350 CLK=0 A=1 DIN=03 WEB=1 REB=0 DOUT=a5
1400 CLK=0 A=1 DIN=03 WEB=1 REB=0 DOUT=a5
1450 CLK=0 A=1 DIN=03 WEB=1 REB=0 DOUT=a5
1475 CLK=0 A=1 DIN=03 WEB=1 REB=1 DOUT=zz
1500 CLK=1 A=1 DIN=03 WEB=1 REB=1 DOUT=zz
1525 CLK=1 A=3 DIN=03 WEB=1 REB=1 DOUT=zz
1550 CLK=0 A=3 DIN=03 WEB=1 REB=1 DOUT=zz
1600 CLK=1 A=3 DIN=03 WEB=1 REB=0 DOUT=03
1625 CLK=1 A=3 DIN=03 WEB=1 REB=0 DOUT=03
1650 CLK=0 A=3 DIN=03 WEB=1 REB=0 DOUT=03
1700 CLK=0 A=3 DIN=03 WEB=1 REB=0 DOUT=03
1750 CLK=0 A=3 DIN=03 WEB=1 REB=0 DOUT=03
1775 CLK=0 A=3 DIN=03 WEB=1 REB=1 DOUT=zz
1800 CLK=1 A=3 DIN=03 WEB=1 REB=1 DOUT=zz
1825 CLK=1 A=0 DIN=03 WEB=1 REB=1 DOUT=zz
1850 CLK=0 A=0 DIN=03 WEB=1 REB=1 DOUT=zz
1900 CLK=1 A=0 DIN=03 WEB=1 REB=0 DOUT=10
1925 CLK=1 A=0 DIN=03 WEB=1 REB=0 DOUT=10
1950 CLK=0 A=0 DIN=03 WEB=1 REB=0 DOUT=10
2000 CLK=1 A=0 DIN=03 WEB=1 REB=0 DOUT=10
2050 CLK=0 A=0 DIN=03 WEB=1 REB=0 DOUT=10
2075 CLK=0 A=0 DIN=03 WEB=1 REB=1 DOUT=zz
2100 CLK=1 A=0 DIN=03 WEB=1 REB=1 DOUT=zz
2125 CLK=1 A=2 DIN=03 WEB=1 REB=1 DOUT=zz
2150 CLK=0 A=2 DIN=03 WEB=1 REB=1 DOUT=zz
2200 CLK=1 A=2 DIN=03 WEB=1 REB=0 DOUT=f7
2225 CLK=1 A=2 DIN=03 WEB=1 REB=0 DOUT=f7
2250 CLK=0 A=2 DIN=03 WEB=1 REB=0 DOUT=f7
2300 CLK=1 A=2 DIN=03 WEB=1 REB=0 DOUT=f7
2350 CLK=0 A=2 DIN=03 WEB=1 REB=0 DOUT=f7
2375 CLK=0 A=2 DIN=03 WEB=1 REB=1 DOUT=zz

(12)修了判定2

設問 波形の入力信号を生成するタスク data_load を完成する

COUNT4LD 回路の仕様

RESLDCKQ
1XX0
01↑D
00↑Q₀+1
data_load タスクのタイミング(クロック2周期)
     ←STEP→
CK   _|‾|______|‾|___
LD   ‾‾‾‾|___|‾‾‾‾‾‾
D        ← 4'h9 →
            
クロック2周期分のタスク

data_load タスクの記述(完成形)

reg         CK, RES, LD;  // カウンタ入力
reg  [3:0]  D;            // カウンタ入力
wire [3:0]  Q;            // カウンタ出力

// 検証対象(カウンタ)接続
COUNT4LD C1( CK, RES, LD, D, Q );

// データのロードを行うタスク
// 呼び出し例: data_load( 4'h9 );
task data_load;
input [3:0] din;
begin
    #(STEP/2)  LD = 1;
               D  = din;
    #STEP      LD = 0;
    #(STEP/2);
end
endtask

(13)修了判定3

設問 シミュレータを使って、タスク data_load の動作を確認する。条件:修了判定2を先に実施する
ファイル種別ファイル名
回路記述ファイルcount4ld.v
テストベンチ・ファイルcount4ld_test.v

シミュレーション波形(修了判定3)

時間軸: 0 〜 40000以上
信号名変化の様子
CK高速クロック繰り返し
RES最初だけ短時間 HIGH(非同期リセット)
LDdata_load 呼び出し時に 1 → 0 と変化
D[3:0]0 → 9(0x9)→ 6(0x6)と変化
Q[3:0]0 1 2 3 4 5 6 7 8 9 a b c d e f | 0 1 2 3 4 5 9 a b c d e f | 0 1 2 6 7 8 9 a b c d e f ...

シミュレーション出力ログ(抜粋)

     0 CK=1 RES=0 LD=0 D=0 Q=x
   500 CK=0 RES=1 LD=0 D=0 Q=0
  1000 CK=1 RES=1 LD=0 D=0 Q=0
  1500 CK=0 RES=0 LD=0 D=0 Q=0
  2000 CK=1 RES=0 LD=0 D=0 Q=1
  2500 CK=0 RES=0 LD=0 D=0 Q=1
  3000 CK=1 RES=0 LD=0 D=0 Q=2
  3500 CK=0 RES=0 LD=0 D=0 Q=2
  4000 CK=1 RES=0 LD=0 D=0 Q=3
  4500 CK=0 RES=0 LD=0 D=0 Q=3
  5000 CK=1 RES=0 LD=0 D=0 Q=4
  5500 CK=0 RES=0 LD=0 D=0 Q=4
  6000 CK=1 RES=0 LD=0 D=0 Q=5
  6500 CK=0 RES=0 LD=0 D=0 Q=5
  7000 CK=1 RES=0 LD=0 D=0 Q=6
  7500 CK=0 RES=0 LD=0 D=0 Q=6
  8000 CK=1 RES=0 LD=0 D=0 Q=7
  8500 CK=0 RES=0 LD=0 D=0 Q=7
  9000 CK=1 RES=0 LD=0 D=0 Q=8
  9500 CK=0 RES=0 LD=0 D=0 Q=8
 10000 CK=1 RES=0 LD=0 D=0 Q=9
 10500 CK=0 RES=0 LD=0 D=0 Q=9
 11000 CK=1 RES=0 LD=0 D=0 Q=a
 11500 CK=0 RES=0 LD=0 D=0 Q=a
 12000 CK=1 RES=0 LD=0 D=0 Q=b
 12500 CK=0 RES=0 LD=0 D=0 Q=b
 13000 CK=1 RES=0 LD=0 D=0 Q=c
 13500 CK=0 RES=0 LD=0 D=0 Q=c
 14000 CK=1 RES=0 LD=0 D=0 Q=d
 14500 CK=0 RES=0 LD=0 D=0 Q=d
 15000 CK=1 RES=0 LD=0 D=0 Q=e
 15500 CK=0 RES=0 LD=0 D=0 Q=e
 16000 CK=1 RES=0 LD=0 D=0 Q=f
 16500 CK=0 RES=0 LD=0 D=0 Q=f
 17000 CK=1 RES=0 LD=0 D=0 Q=0
 17500 CK=0 RES=0 LD=0 D=0 Q=0
 18000 CK=1 RES=0 LD=0 D=0 Q=1
 18500 CK=0 RES=0 LD=0 D=0 Q=1
 19000 CK=1 RES=0 LD=0 D=0 Q=2
 19500 CK=0 RES=0 LD=0 D=0 Q=2
 20000 CK=1 RES=0 LD=0 D=0 Q=3
 20500 CK=0 RES=0 LD=0 D=0 Q=3
 21000 CK=1 RES=0 LD=0 D=0 Q=4
 21500 CK=0 RES=0 LD=0 D=0 Q=4
 22000 CK=1 RES=0 LD=1 D=9 Q=9  ← data_load(9) ロード
 22500 CK=0 RES=0 LD=1 D=9 Q=9
 23000 CK=1 RES=0 LD=0 D=9 Q=9
 23500 CK=0 RES=0 LD=0 D=9 Q=9
 24000 CK=1 RES=0 LD=0 D=9 Q=a
 24500 CK=0 RES=0 LD=0 D=9 Q=a
 25000 CK=1 RES=0 LD=0 D=9 Q=b
 25500 CK=0 RES=0 LD=0 D=9 Q=b
 26000 CK=1 RES=0 LD=0 D=9 Q=c
 26500 CK=0 RES=0 LD=0 D=9 Q=c
 27000 CK=1 RES=0 LD=0 D=9 Q=d
 27500 CK=0 RES=0 LD=0 D=9 Q=d
 28000 CK=1 RES=0 LD=0 D=9 Q=e
 28500 CK=0 RES=0 LD=0 D=9 Q=e
 29000 CK=1 RES=0 LD=0 D=9 Q=f
 29500 CK=0 RES=0 LD=0 D=9 Q=f
 30000 CK=1 RES=0 LD=0 D=9 Q=0
 30500 CK=0 RES=0 LD=0 D=9 Q=0
 31000 CK=1 RES=0 LD=0 D=9 Q=1
 31500 CK=0 RES=0 LD=0 D=9 Q=1
 32000 CK=1 RES=0 LD=1 D=6 Q=6  ← data_load(6) ロード
 32500 CK=0 RES=0 LD=1 D=6 Q=6
 33000 CK=1 RES=0 LD=0 D=6 Q=6
 33500 CK=0 RES=0 LD=0 D=6 Q=6
 34000 CK=1 RES=0 LD=0 D=6 Q=7
 34500 CK=0 RES=0 LD=0 D=6 Q=7
 35000 CK=1 RES=0 LD=0 D=6 Q=8
 35500 CK=0 RES=0 LD=0 D=6 Q=8
 36000 CK=1 RES=0 LD=0 D=6 Q=9
 36500 CK=0 RES=0 LD=0 D=6 Q=9
 37000 CK=1 RES=0 LD=0 D=6 Q=a
 37500 CK=0 RES=0 LD=0 D=6 Q=a
 38000 CK=1 RES=0 LD=0 D=6 Q=b
 38500 CK=0 RES=0 LD=0 D=6 Q=b
 39000 CK=1 RES=0 LD=0 D=6 Q=c
 39500 CK=0 RES=0 LD=0 D=6 Q=c
 40000 CK=1 RES=0 LD=0 D=6 Q=d
 40500 CK=0 RES=0 LD=0 D=6 Q=d
 41000 CK=1 RES=0 LD=0 D=6 Q=e
 41500 CK=0 RES=0 LD=0 D=6 Q=e
 42000 CK=1 RES=0 LD=0 D=6 Q=f
 42500 CK=0 RES=0 LD=0 D=6 Q=f
 43000 CK=1 RES=0 LD=0 D=6 Q=0
 43500 CK=0 RES=0 LD=0 D=6 Q=0

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

taskで刺激と判定を分ける

繰り返す操作を apply_reset、drive_input、check_output のようなtaskに分けると、試験シナリオの目的が読みやすくなるよ。taskに期待値を引数で渡し、「どう操作するか」と「何が正しいか」を分離しよう。

この教材では、負エッジで刺激を変え、正エッジ後にNBAの更新が終わってから判定する形を使うよ。#1 を使うときは timescale とクロック周期を明示する。固定の待ち時間を足すだけでは、サイクル数を間違えた試験を正しくはできないんだ。

複数の並行プロセスから同じtaskを呼ぶなら、ローカル変数の共有にも注意する。task automatic は呼び出しごとにローカル変数を持つけれど、同じ信号を複数taskから同時に駆動する競合まで解決するものではないよ。まずは一つの刺激プロセスから順番に呼ぶ構成で始めよう。

📌 まとめ

  • task はサブルーチンのように繰り返し処理をまとめてテストベンチを簡潔にする
  • task の定義は task〜endtask。引数は input/output/inout の3方向
  • task 内部には遅延・内部信号・parameter を記述できる
  • バスモデルとして活用すると、ボードレベル検証が効率的になる
  • タスクを「基本タスク」「応用タスク」と階層化することで大規模テストベンチを整理できる
  • function との主な違い: 引数方向・戻り値数・遅延記述の可否・呼び出し構文(ステートメント vs 式)
もふねこ

お疲れさま!タスクを使ったテストベンチの記述、理解できたかな?🐾
タスクをうまく使えば、複雑な検証シナリオもスッキリと書けるようになるね!

テンプレートを別の回路へ使うとき

固定時間で待つtaskと、クロック数で待つtaskは区別しよう。次はSystemVerilog形式の宣言例だよ。`-g2012` で使い、clkはテストベンチ内で駆動しておく。断片なので、これだけを単独でコンパイルするものではないよ。
task automatic wait_clk(input integer n);
    repeat (n) @(posedge clk);
endtask
このtaskは最後のエッジを待った時点で帰る。DUTのNBA更新後の値を読むには、判定側の時刻の約束も必要だよ。クロック停止に備えた独立タイムアウトも残そう。`automatic` は呼び出しごとの局所変数を分ける指定で、taskが消費する時間をなくすものではないよ。 ログの単位をそろえるには `$timeformat(-9, 1, " ns", 10);` のように表示形式を指定できる。これは `%t` の表示を整える設定で、シミュレーションの時間精度そのものは変えないんだ。合格件数・失敗件数を数えるなら、全判定の後で失敗が0か確認し、異常時は非ゼロで終了するようにしよう。 期待値にもXが入ると、`===` で実際値のXと一致してしまうことがある。既知値を期待する試験では期待値のX/Zも検査する。例は [T6の修了判定2](/T6_Practice_of_Circuit_Verification#section8) へ。