入力はクロックに同期させる

[] [] []


ストベンチの入出力タイミングは、

 DUTへの入力はクロックから遅らせる

 DUTの出力の観測タイミングはクロックと同時

が基本である。もちろん非同期な入力信号はこうする必要はないのだが、同期設計してあればクロック周期以下の時間をシミュレーションしても意味がないので、クロック同期で入力した方がよい。

初心者はよく入力波形をそのまま記述してしまうが、クロックが複数ある場合などはこの書き方では無理がある。

×// 入力波形をそのまま記述

parameter TCLK_HALF = 5; // クロック半周期

initial

    begin

                 clk = 1'b1; enb = 1'b0; din = 16'd0;

    #(TCLK_HALF) clk = 1'b0; enb = 1'b0; din = 16'd0;

    #(TCLK_HALF) clk = 1'b1; enb = 1'b0; din = 16'd0;

    #(TCLK_HALF) clk = 1'b0; enb = 1'b1; din = 16'd5;

    #(TCLK_HALF) clk = 1'b1; enb = 1'b1; din = 16'd5;

    #(TCLK_HALF) clk = 1'b0; enb = 1'b1; din = 16'd6;

    #(TCLK_HALF) clk = 1'b1; enb = 1'b1; din = 16'd6;

    ...

    end

そこで、クロックを個別に作っておいて、入力をこれらのクロックに同期させるとよい。

// クロックの生成

parameter TCLK_HALF = 5; // クロック半周期

initial

    begin

    clk = 1'b1;

    forever #(TCLK_HALF) clk = ~clk;

    end

// クロックに同期させた入力

initial

    begin

    enb = 1'b0; din = 16'd0;     // 初期値入力

    repeat(2) @(posedge clk);    // 2clk進める

    enb <= 1'b1; din <= 16'd5;   // 入力を変化させる

    @(posedge clk);              // 1clk進める

    din <= 16'd6;                // 入力を変化させる

    ...

    end

ここで@(posedge clk);後の代入はノンブロッキング代入(<=)を使う。もしテストベンチでブロッキング代入(=)を使うと、DUTモジュール内のノンブロッキング代入(<=)より常に先に実行されるので下図のようなタイミングになる。

×// テストベンチにブロッキング代入を使う

initial

    begin

    dut_in = 1'b0;

    @(posedge clk);

    dut_in = 1'b1;

    ...

    end

// DUTモジュール内の記述

always @(posedge clk or negedge rst_n)

    begin

    if(~rst_n)

      dut_reg <= 1'b0;

    else

      dut_reg <= dut_in;

    end

このタイミングでも間違いではないが、下図のようにDUTジュール内の転送タイミングと同様にしておいた方がわかりやすい。

// テストベンチにノンブロッキング代入を使う

initial

    begin

    dut_in = 1'b0;     // 初期値入力

    @(posedge clk);

    dut_in <= 1'b1;

    ...

    end

// DUTモジュール内の記述

always @(posedge clk or negedge rst_n)

    begin

    if(~rst_n)

      dut_reg <= 1'b0;

    else

      dut_reg <= dut_in;

    end

◇◇

RTL記述はゼロ遅延なのでこれでよいが、配置配線後の遅延付きネットリストに対してもテストベンチが使えるようにするため、さらにノンブロッキング代入の右辺に遅延をつけた方がよい。遅延値(TCO)はタイミング設計値を設定するのが正確だが、機能検証としてはクロック半周期にしておけば大抵は十分である。

// テストベンチに遅延がついたノンブロッキング代入を使う

parameter TCO = TCLK_HALF; // 入力の遅延

initial

    begin

    dut_in = 1'b0;     // 初期値入力

    @(posedge clk)

    dut_in <= #(TCO) 1'b1;

    ...

    end

ここで、右辺に遅延をつけるのと文全体に遅延をつけるのとでは次の違いがある。

 dut_in <= #(TCO) dut_out;  ←現在のdut_outの値をTCO後に代入する

 #(TCO) dut_in <= dut_out;  TCO後のdut_outの値をTCO後に代入する

右辺がDUTの出力を含む場合はクロック立ち上がり時点の値を使いたいので、文全体でなく右辺に遅延をつけておく。

ちなみにRTL記述のシミュレーションでも入力に遅延をつけておいた方がよい。なぜなら、FPGA内のPLLのシミュレーションモデルでノンブロッキング代入で記述されていた場合に、遅延値がゼロ(DELAY=0)のときでもテストベンチから入れたクロック(clk)FPGA内部のクロック(PLL出力clk0)が同一でなく順序がついた異なる信号になるためである。

// PLLのシミュレーションモデル

time clk0_delay=DELAY; // ゼロ遅延のときDELAY=0

...

always @(clk)

    begin

    clk0 <= #(clk0_delay) clk;

    end

このときテストベンチから入れたclkよりもFPGA内部のclk0 (PLL出力)の方が順序が後になるので、下図のようにテストベンチにブロッキング代入(=)を使ったときと同じようなタイミングで入力されてしまう。

◇◇◇

入力は決め打ちで入れるだけでなく、DUT出力に合わせて入力すれば自由なテストシナリオが書ける

// DUTの出力に合わせて入力する

initial

    begin

    dut_in = 16'h0000;

    while(dut_ready!==1) @(posedge clk); // DUTの出力を待つ

    dut_in <= #(TCO) 16'h55aa;

  @(posedge clk);

    error_in <= #(TCO) (dut_out!==16'h55aa)? 1'b1:1'b0;  // DUTの出力によって入力値を変える

  @(posedge clk);

    if(dut_out===16'bzzzz)

        $display("-- ERROR");

    else

        $display("-- dut_out=%h",dut_out); // DUTの出力によって表示を変える

    ...

    end

ここで!=====1,0の他にz,xも比較できる関係演算子で、論理合成向けの記述では使わないがテストベンチでは使う。(補足 - z,xに対する比較演算子とcase)


[] [] []