入力はクロックに同期させる
テストベンチの入出力タイミングは、
(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文)