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

[] [] []


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

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

(2) 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) begin

    dut_reg <= dut_in;

end

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

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

initial begin

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

  @(posedge clk);

  dut_in <= 1'b1;

  ...

end

 

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

always @(posedge clk) begin

    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

このとき、下図のようにテストベンチにブロッキング代入(=)を使ったときと同じようなタイミングで入力されてしまう。

◇◇◇

入力は決め打ちで入れるだけでなく、DUTの出力に応じて入力値を変えると柔軟にテストシナリオが書ける

DUTの出力に応じて入力値を変える

initial begin

  dut_in = 16'h0000;

  // DUTの出力を待つ

  while(dut_ready!==1) @(posedge clk);

  dut_in <= #(TCO) 16'h55aa;

 @(posedge clk);

  // DUTの出力によって入力値を変える

  error_in <= #(TCO) (dut_out!==16'h55aa)? 1'b1:1'b0; 

 @(posedge clk);

  // DUTの出力によって表示を変える

  if(dut_out===16'hzzzz)

    $display("-- ERROR");

  else

    $display("-- dut_out=%h",dut_out);

  ...

end

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


[] [] []