Verilog テストベンチ - MATLAB & Simulink

Verilog テストベンチ

Verilog テストベンチとは?

VerilogまたはVHDLのテストベンチ(test bench)とは、FPGAやLSI/ASIC開発において、VerilogまたはVHDLで記述した回路が期待通りの動作をしているか確認するための、一連のファイルのことです。テストベンチファイルには、入力信号、クロック、リセット信号などを記述し、それらの信号を回路記述したデザインファイルに送り、出力を期待値と照合することで設計の検証を行います。

図1. テストベンチとHDLシミュレーター、デザインファイル

図1. テストベンチとHDLシミュレーター、デザインファイル

通常は回路のデザインファイルと同じ言語で記述されます。デザインファイルは、論理合成・配置配線を行い、回路としてFPGAやASICに実装されるのに対し、テストベンチファイルはシミュレーションでのみ使用されます。テストベンチは通常、実機に実装する前に実行して、設計の欠陥を発見するのに役立て、回路の品質と信頼性を向上させることができます。

VerilogやVHDLで記述するデジタル回路においては、回路を動作させるためにクロック、リセット、クロックイネーブルなどの信号を入力する必要があります。入力信号は、時系列のベクトルデータとして定義します。テストベンチにおいて、クロック、リセット、クロックイネーブルに加えて、デザインファイル用の入力信号のことをスティミュラス(Stimulus)、入力信号と出力信号の期待値をテストベクタ(Test vector)と呼びます。また、動作を検証するためには、デザインファイルの出力を期待値のデータと照合して合否を判定する条件を記述します。条件には、出力信号の期待される値、許容誤差、タイミングチェックなどが含まれます。

図2. テストベンチのファイル構成

図2. テストベンチのファイル構成

// -------------------------------------------------------------
// 
// File Name: DUT_tb.v
// 
// -------------------------------------------------------------

`timescale 1 ns / 1 ns

module DUT_tb;

  reg  clk;
  reg  reset;
  wire clk_enable;
  wire Out1_done;  // ufix1
  wire rdEnb;
  wire Out1_done_enb;  // ufix1
  reg [5:0] Out1_addr;  // ufix6
  wire Out1_active;  // ufix1
  reg [5:0] Data_Type_Conversion_out1_addr;  // ufix6
  wire Data_Type_Conversion_out1_active;  // ufix1
  reg  tb_enb_delay;
  wire Data_Type_Conversion_out1_enb;  // ufix1
  wire [5:0] Data_Type_Conversion_out1_addr_delay_1;  // ufix6
  reg signed [31:0] fp_In1;  // sfix32
  reg signed [23:0] rawData_In1;  // sfix24_En22
  reg signed [31:0] status_In1;  // sfix32
  reg signed [23:0] holdData_In1;  // sfix24_En22
  reg signed [23:0] In1_offset;  // sfix24_En22
  wire signed [23:0] In1;  // sfix24_En22
  reg  check1_done;  // ufix1
  wire snkDonen;
  wire resetn;
  wire tb_enb;
  wire ce_out;
  wire signed [23:0] Out1;  // sfix24_En18
  wire Out1_enb;  // ufix1
  wire Out1_lastAddr;  // ufix1
  wire [5:0] Out1_addr_delay_1;  // ufix6
  reg signed [31:0] fp_Out1_expected;  // sfix32
  reg signed [23:0] Out1_expected;  // sfix24_En18
  reg signed [31:0] status_Out1_expected;  // sfix32
  wire signed [23:0] Out1_ref;  // sfix24_En18
  reg  Out1_testFailure;  // ufix1
  wire testFailure;  // ufix1

  assign Out1_done_enb = Out1_done & rdEnb;
  assign Out1_active = Out1_addr != 6'b101100;
  assign Data_Type_Conversion_out1_active = Data_Type_Conversion_out1_addr != 6'b101100;
  assign Data_Type_Conversion_out1_enb = Data_Type_Conversion_out1_active & (rdEnb & tb_enb_delay);

  // Count limited, Unsigned Counter
  //  initial value   = 0
  //  step value      = 1
  //  count to value  = 44
  always @(posedge clk or posedge reset)
    begin : DataTypeConversion_process
      if (reset == 1'b1) begin
        Data_Type_Conversion_out1_addr <= 6'b000000;
      end
      else begin
        if (Data_Type_Conversion_out1_enb) begin
          if (Data_Type_Conversion_out1_addr >= 6'b101100) begin
            Data_Type_Conversion_out1_addr <= 6'b000000;
          end
          else begin
            Data_Type_Conversion_out1_addr <= Data_Type_Conversion_out1_addr + 6'b000001;
          end
        end
      end
    end

  assign #1 Data_Type_Conversion_out1_addr_delay_1 = Data_Type_Conversion_out1_addr;

  // Data source for In1
  initial
    begin : In1_fileread
      fp_In1 = $fopen("In1.dat", "r");
      status_In1 = $rewind(fp_In1);
    end

  always @(Data_Type_Conversion_out1_addr_delay_1, rdEnb, tb_enb_delay)
    begin
      if (tb_enb_delay == 0) begin
        rawData_In1 <= 24'bx;
      end
      else if (rdEnb == 1) begin
        status_In1 = $fscanf(fp_In1, "%h", rawData_In1);
      end
    end

  // holdData reg for Data_Type_Conversion_out1
  always @(posedge clk or posedge reset)
    begin : stimuli_Data_Type_Conversion_out1
      if (reset) begin
        holdData_In1 <= 24'bx;
      end
      else begin
        holdData_In1 <= rawData_In1;
      end
    end

  always @(rawData_In1 or rdEnb)
    begin : stimuli_Data_Type_Conversion_out1_1
      if (rdEnb == 1'b0) begin
        In1_offset <= holdData_In1;
      end
      else begin
        In1_offset <= rawData_In1;
      end
    end

  assign #2 In1 = In1_offset;
  assign snkDonen =  ~ check1_done;
  assign resetn =  ~ reset;
  assign tb_enb = resetn & snkDonen;

  // Delay inside enable generation: register depth 1
  always @(posedge clk or posedge reset)
    begin : u_enable_delay
      if (reset) begin
        tb_enb_delay <= 0;
      end
      else begin
        tb_enb_delay <= tb_enb;
      end
    end

  assign rdEnb = (check1_done == 1'b0 ? tb_enb_delay :  1'b0);
  assign #2 clk_enable = rdEnb;

  initial
    begin : reset_gen
      reset <= 1'b1;
      # (20);
      @ (posedge clk)
      # (2);
      reset <= 1'b0;
    end

  always 
    begin : clk_gen
      clk <= 1'b1;
      # (5);
      clk <= 1'b0;
      # (5);
      if (check1_done == 1'b1) begin
        clk <= 1'b1;
        # (5);
        clk <= 1'b0;
        # (5);
        $stop;
      end
    end

  DUT u_DUT (.clk(clk),
             .reset(reset),
             .clk_enable(clk_enable),
             .In1(In1),  // sfix24_En22
             .ce_out(ce_out),
             .Out1(Out1)  // sfix24_En18
             );

  assign Out1_enb = ce_out & Out1_active;

  // Count limited, Unsigned Counter
  //  initial value   = 0
  //  step value      = 1
  //  count to value  = 44
  always @(posedge clk or posedge reset)
    begin : c_2_process
      if (reset == 1'b1) begin
        Out1_addr <= 6'b000000;
      end
      else begin
        if (Out1_enb) begin
          if (Out1_addr >= 6'b101100) begin
            Out1_addr <= 6'b000000;
          end
          else begin
            Out1_addr <= Out1_addr + 6'b000001;
          end
        end
      end
    end

  assign Out1_lastAddr = Out1_addr >= 6'b101100;
  assign Out1_done = Out1_lastAddr & resetn;

  // Delay to allow last sim cycle to complete
  always @(posedge clk or posedge reset)
    begin : checkDone_1
      if (reset) begin
        check1_done <= 0;
      end
      else begin
        if (Out1_done_enb) begin
          check1_done <= Out1_done;
        end
      end
    end

  assign #1 Out1_addr_delay_1 = Out1_addr;

  // Data source for Out1_expected
  initial
    begin : Out1_expected_fileread
      fp_Out1_expected = $fopen("Out1_expected.dat", "r");
      status_Out1_expected = $rewind(fp_Out1_expected);
    end

  always @(Out1_addr_delay_1, ce_out, tb_enb_delay)
    begin
      if (tb_enb_delay == 0) begin
        Out1_expected <= 24'bx;
      end
      else if (ce_out == 1) begin
        status_Out1_expected = $fscanf(fp_Out1_expected, "%h", Out1_expected);
      end
    end

  assign Out1_ref = Out1_expected;

  always @(posedge clk or posedge reset)
    begin : Out1_checker
      if (reset == 1'b1) begin
        Out1_testFailure <= 1'b0;
      end
      else begin
        if (ce_out == 1'b1 && Out1 !== Out1_ref) begin
          Out1_testFailure <= 1'b1;
          $display("ERROR in Out1 at time %t : Expected '%h' Actual '%h'", $time, Out1_ref, Out1);
        end
      end
    end

  assign testFailure = Out1_testFailure;

  always @(posedge clk)
    begin : completed_msg
      if (check1_done == 1'b1) begin
        if (testFailure == 1'b0) begin
          $display("**************TEST COMPLETED (PASSED)**************");
        end
        else begin
          $display("**************TEST COMPLETED (FAILED)**************");
        end
      end
    end

endmodule  // DUT_tb

デザインファイルはFPGA/ASIC実装が前提で、論理合成を行う必要があるので、論理合成可能な記述を行う必要があります。他方、テストベンチファイルは論理合成を行わずに、シミュレーションのみで使用されるファイルのため、論理合成できない様々な記述、例えばファイルI/Oや文字列表示などの記述を行うことができます。

テストベンチで扱うことができる記述スタイル(ファイルI/Oや標準入出力)

テストベンチはシミュレーションのみで使用されるため、論理合成出来ない記述をすることも可能です。例えばテストベクタは、別ファイルに記述したデータをファイルI/O記述を使って読み込みます。

Verilog HDLではシステムタスクによる標準入出力、ファイルI/O、三角関数などの数学関数を扱う関数が用意されています。$fopen, $fclose, $fscanf, $display, $monitorなどがあります。ファイルからデータを読み込むには次のように記述します。

fp = $fopen("data_in0.dat", "r");
outputData = $fscanf(fp, "%h", dataFormat);

エラーや合否判定結果は次のように$displayを使って記述し、シミュレータに文字列を出力します。

if (ce_out == 1'b1 && Out1 !== Out1_ref) begin
      Out1_testFailure <= 1'b1;
      $display("ERROR at time %t : Expected '%h' Actual '%h'", $time, expextedData, outputData);
end

VHDLには同様の機能として、TEXT I/O機能があります。これを利用するためには、パッケージを呼び出す必要があります。

LIBRARY IEEE;
USE IEEE.std_logic_textio.ALL;
LIBRARY STD;
USE STD.textio.ALL;

次にファイルを呼び出して、ファイルから1行ずつデータを取得します。

VARIABLE l: LINE;
FILE fp: TEXT open READ_MODE is "data_in0.dat";
READLINE(fp, l);  -- 1行読み出す
HREAD(l, outputData);  -- 読み出した16進数値を変数に代入

エラーや合否判定は次のようにREPORTを使って記述し、シミュレータに文字列を出力します。

IF ce_out = '1' AND Out1_signed /= Out1_ref THEN
      Out1_testFailure <= '1';
      ASSERT FALSE
      REPORT "Error : Expected " & to_hex(expectedData) & (" Actual " & to_hex(outputData))
      SEVERITY ERROR;
END IF;

テストベンチの肝はスティミュラスと期待値

テストベンチを作成する際には、まずクロック、リセット、クロックイネーブルなどの回路を制御する信号を定義します。次に、回路を駆動するためのスティミュラスを考え、その処理結果となる期待値を作成して行きます。

スティミュラスを作る際に重要なこととして、次のことを考慮します。

  • 実際のユースケースに沿った信号となっていること
  • 設計対象の回路をできるだけ網羅的にテストする信号となっていること
    • HDLシミュレータの中には網羅性=カバレッジ率を測定できるものもがあります。
    • 100%のカバレッジを満たすのは現実的に難しいので、妥協やツールの手助けも必要です。
  • 実行時間がかかりすぎないこと
    • 網羅的なテストをするために長尺の信号を作成して、シミュレーションに1年かかってしまうようでは、現実的に使用することができないので、実行時間を考慮する必要があります。

期待値との比較を行う際に重要なこととして、次のことを考慮します。

  • クロックサイクル、ビット精度を正確に表現した値となっていること
    • クロックサイクルで動作する設計対象内のパイプライン処理を考慮したレイテンシが正確に反映されている必要があります。でないと設計対象の出力と期待値のタイミングがずれてしまい、照合結果が合わなくなります。
    • 丸め誤差を含めたビット精度を正確に表現していないと、期待値と出力の間に誤差が発生する可能性があります。
  • 結果の合格、不合格を判断できる内容となっていること
    • 期待値と出力を照合して、合格、不合格を判断し、文字列を出力するなどして合否結果を示します。

コード例

always @(posedge clk)
   begin : completed_msg
     if (check1_done == 1'b1) begin
       if (testFailure == 1'b0) begin
         $display("**************TEST COMPLETED (PASSED)**************");
       end
        else begin
         $display("**************TEST COMPLETED (FAILED)**************");
       end
      end
    end
図3. 結果を文字列で示すHDLシミュレータ実行画面

図3. 結果を文字列で示すHDLシミュレータ実行画面

テストベンチを実行するHDLシミュレータ

テストベンチの実行には、VHDL/Verilogコードを実行するHDLシミュレータを利用します。ASICやFPGA開発用にEDAソフトウェアベンダー各社からHDLシミュレータが販売されています。

  • SIEMENS社(旧Mentor社) ModelSim, Questa advanced simulator
  • Cadence社 Xcelium(旧Incisive)
  • Synopsys社 VCS®
  • Aldec社 Rivera-PRO™, Active-HDL™

フリーのHDLシミュレータも入手できます。

AMD社はFPGA設計ツールVivado®にHDLシミュレータを同梱しており、追加料金無しで使用できます。

図4. AMD Vivado SimulatorでHDLシミュレーションを行った結果

図4. AMD Vivado SimulatorでHDLシミュレーションを行った結果

テストベンチの重要性

Wilson research Groupによる調査では、FPGAプロジェクトで検証に使われた時間の中央値は40-50%という結果が出ています。設計を効率化するCベース高位合成やモデルベースデザイン (モデルベース開発、MBD)などの新しい手法だけでなく、検証においても効率化手法に対する関心が高まっています。アサーション・ベース検証、UVM、モデルベースデザインについての詳細は後述します。

図5. 検証に割かれるFPGAプロジェクト時間の割合

図5. 検証に割かれるFPGAプロジェクト時間の割合

テストを効率化する新しい検証手法

テストを効率化するために、次のような新しい検証手法が使用されています。

  • アサーション・ベース検証 (ABV):設計対象やそのインターフェイスに期待される内部動作を定義しておき、自動的にそれを監視しながらシミュレーションを実行する検証手法です。SVA (SystemVerilog Assertions)やPSL (Property Specification Language)が代表的な言語です。
  • UVM (Universal Verification Methodology):再利用性による効率化を目的に、検証分野で推奨されている技術、ルール、慣習、規律等をコードとして具体化したSystemVerilogのクラス・ライブラリーです。
  • モデルベースデザイン (モデルベース開発、MBD):設計対象および影響を与える周辺環境も含めたシステムレベルでモデリングしたものを、シミュレーション~自動コード生成・テストベンチ生成を行って設計する手法です。ブロック線図状態遷移図で描いたモデルから、等価なVHDL/Verilogコードの回路記述およびテストベンチを自動生成します。

テストベンチの作成例

いくつかのフォーマットで作成されたテストベンチの作成例を見てみましょう。

VHDL/Verilogテストベンチ

このテストベンチはテストベンチ本体、スティミュラスと期待値のデータ、合計3種類のファイルで構成しています。これらのファイルと設計対象のファイルをHDLシミュレータで実行して、期待通りの動作をしているかどうかの検証を行います。

// -------------------------------------------------------------
// 
// File Name: DUT_tb.v
// 
// -------------------------------------------------------------

`timescale 1 ns / 1 ns

module DUT_tb;

  reg  clk;
  reg  reset;
  wire clk_enable;
  wire Out1_done;  // ufix1
  wire rdEnb;
  wire Out1_done_enb;  // ufix1
  reg [5:0] Out1_addr;  // ufix6
  wire Out1_active;  // ufix1
  reg [5:0] Data_Type_Conversion_out1_addr;  // ufix6
  wire Data_Type_Conversion_out1_active;  // ufix1
  reg  tb_enb_delay;
  wire Data_Type_Conversion_out1_enb;  // ufix1
  wire [5:0] Data_Type_Conversion_out1_addr_delay_1;  // ufix6
  reg signed [31:0] fp_In1;  // sfix32
  reg signed [23:0] rawData_In1;  // sfix24_En22
  reg signed [31:0] status_In1;  // sfix32
  reg signed [23:0] holdData_In1;  // sfix24_En22
  reg signed [23:0] In1_offset;  // sfix24_En22
  wire signed [23:0] In1;  // sfix24_En22
  reg  check1_done;  // ufix1
  wire snkDonen;
  wire resetn;
  wire tb_enb;
  wire ce_out;
  wire signed [23:0] Out1;  // sfix24_En18
  wire Out1_enb;  // ufix1
  wire Out1_lastAddr;  // ufix1
  wire [5:0] Out1_addr_delay_1;  // ufix6
  reg signed [31:0] fp_Out1_expected;  // sfix32
  reg signed [23:0] Out1_expected;  // sfix24_En18
  reg signed [31:0] status_Out1_expected;  // sfix32
  wire signed [23:0] Out1_ref;  // sfix24_En18
  reg  Out1_testFailure;  // ufix1
  wire testFailure;  // ufix1

  assign Out1_done_enb = Out1_done & rdEnb;
  assign Out1_active = Out1_addr != 6'b101100;
  assign Data_Type_Conversion_out1_active = Data_Type_Conversion_out1_addr != 6'b101100;
  assign Data_Type_Conversion_out1_enb = Data_Type_Conversion_out1_active & (rdEnb & tb_enb_delay);

  // Count limited, Unsigned Counter
  //  initial value   = 0
  //  step value      = 1
  //  count to value  = 44
  always @(posedge clk or posedge reset)
    begin : DataTypeConversion_process
      if (reset == 1'b1) begin
        Data_Type_Conversion_out1_addr <= 6'b000000;
      end
      else begin
        if (Data_Type_Conversion_out1_enb) begin
          if (Data_Type_Conversion_out1_addr >= 6'b101100) begin
            Data_Type_Conversion_out1_addr <= 6'b000000;
          end
          else begin
            Data_Type_Conversion_out1_addr <= Data_Type_Conversion_out1_addr + 6'b000001;
          end
        end
      end
    end

  assign #1 Data_Type_Conversion_out1_addr_delay_1 = Data_Type_Conversion_out1_addr;

  // Data source for In1
  initial
    begin : In1_fileread
      fp_In1 = $fopen("In1.dat", "r");
      status_In1 = $rewind(fp_In1);
    end

  always @(Data_Type_Conversion_out1_addr_delay_1, rdEnb, tb_enb_delay)
    begin
      if (tb_enb_delay == 0) begin
        rawData_In1 <= 24'bx;
      end
      else if (rdEnb == 1) begin
        status_In1 = $fscanf(fp_In1, "%h", rawData_In1);
      end
    end

  // holdData reg for Data_Type_Conversion_out1
  always @(posedge clk or posedge reset)
    begin : stimuli_Data_Type_Conversion_out1
      if (reset) begin
        holdData_In1 <= 24'bx;
      end
      else begin
        holdData_In1 <= rawData_In1;
      end
    end

  always @(rawData_In1 or rdEnb)
    begin : stimuli_Data_Type_Conversion_out1_1
      if (rdEnb == 1'b0) begin
        In1_offset <= holdData_In1;
      end
      else begin
        In1_offset <= rawData_In1;
      end
    end

  assign #2 In1 = In1_offset;
  assign snkDonen =  ~ check1_done;
  assign resetn =  ~ reset;
  assign tb_enb = resetn & snkDonen;

  // Delay inside enable generation: register depth 1
  always @(posedge clk or posedge reset)
    begin : u_enable_delay
      if (reset) begin
        tb_enb_delay <= 0;
      end
      else begin
        tb_enb_delay <= tb_enb;
      end
    end

  assign rdEnb = (check1_done == 1'b0 ? tb_enb_delay :  1'b0);
  assign #2 clk_enable = rdEnb;

  initial
    begin : reset_gen
      reset <= 1'b1;
      # (20);
      @ (posedge clk)
      # (2);
      reset <= 1'b0;
    end

  always 
    begin : clk_gen
      clk <= 1'b1;
      # (5);
      clk <= 1'b0;
      # (5);
      if (check1_done == 1'b1) begin
        clk <= 1'b1;
        # (5);
        clk <= 1'b0;
        # (5);
        $stop;
      end
    end

  DUT u_DUT (.clk(clk),
             .reset(reset),
             .clk_enable(clk_enable),
             .In1(In1),  // sfix24_En22
             .ce_out(ce_out),
             .Out1(Out1)  // sfix24_En18
             );

  assign Out1_enb = ce_out & Out1_active;

  // Count limited, Unsigned Counter
  //  initial value   = 0
  //  step value      = 1
  //  count to value  = 44
  always @(posedge clk or posedge reset)
    begin : c_2_process
      if (reset == 1'b1) begin
        Out1_addr <= 6'b000000;
      end
      else begin
        if (Out1_enb) begin
          if (Out1_addr >= 6'b101100) begin
            Out1_addr <= 6'b000000;
          end
          else begin
            Out1_addr <= Out1_addr + 6'b000001;
          end
        end
      end
    end

  assign Out1_lastAddr = Out1_addr >= 6'b101100;
  assign Out1_done = Out1_lastAddr & resetn;

  // Delay to allow last sim cycle to complete
  always @(posedge clk or posedge reset)
    begin : checkDone_1
      if (reset) begin
        check1_done <= 0;
      end
      else begin
        if (Out1_done_enb) begin
          check1_done <= Out1_done;
        end
      end
    end

  assign #1 Out1_addr_delay_1 = Out1_addr;

  // Data source for Out1_expected
  initial
    begin : Out1_expected_fileread
      fp_Out1_expected = $fopen("Out1_expected.dat", "r");
      status_Out1_expected = $rewind(fp_Out1_expected);
    end

  always @(Out1_addr_delay_1, ce_out, tb_enb_delay)
    begin
      if (tb_enb_delay == 0) begin
        Out1_expected <= 24'bx;
      end
      else if (ce_out == 1) begin
        status_Out1_expected = $fscanf(fp_Out1_expected, "%h", Out1_expected);
      end
    end

  assign Out1_ref = Out1_expected;

  always @(posedge clk or posedge reset)
    begin : Out1_checker
      if (reset == 1'b1) begin
        Out1_testFailure <= 1'b0;
      end
      else begin
        if (ce_out == 1'b1 && Out1 !== Out1_ref) begin
          Out1_testFailure <= 1'b1;
          $display("ERROR in Out1 at time %t : Expected '%h' Actual '%h'", $time, Out1_ref, Out1);
        end
      end
    end

  assign testFailure = Out1_testFailure;

  always @(posedge clk)
    begin : completed_msg
      if (check1_done == 1'b1) begin
        if (testFailure == 1'b0) begin
          $display("**************TEST COMPLETED (PASSED)**************");
        end
        else begin
          $display("**************TEST COMPLETED (FAILED)**************");
        end
      end
    end

endmodule  // DUT_tb

-- -------------------------------------------------------------
-- 
-- File Name: DUT_tb.vhd
-- -------------------------------------------------------------

LIBRARY IEEE;
USE IEEE.std_logic_textio.ALL;
USE IEEE.std_logic_1164.ALL;
USE IEEE.numeric_std.ALL;
LIBRARY STD;
USE STD.textio.ALL;
LIBRARY work;
USE work.DUT_pkg.ALL;
USE work.DUT_tb_pkg.ALL;

ENTITY DUT_tb IS
END DUT_tb;

ARCHITECTURE rtl OF DUT_tb IS

  -- Component Declarations
  COMPONENT DUT
    PORT( clk                             :   IN    std_logic;
          reset                           :   IN    std_logic;
          clk_enable                      :   IN    std_logic;
          In1                             :   IN    std_logic_vector(23 DOWNTO 0);  -- sfix24_En22
          ce_out                          :   OUT   std_logic;
          Out1                            :   OUT   std_logic_vector(23 DOWNTO 0)  -- sfix24_En18
          );
  END COMPONENT;

  -- Component Configuration Statements
  FOR ALL : DUT
    USE ENTITY work.DUT(rtl);

  -- Signals
  SIGNAL clk                              : std_logic;
  SIGNAL reset                            : std_logic;
  SIGNAL clk_enable                       : std_logic;
  SIGNAL Out1_done                        : std_logic;  -- ufix1
  SIGNAL rdEnb                            : std_logic;
  SIGNAL Out1_done_enb                    : std_logic;  -- ufix1
  SIGNAL Out1_addr                        : unsigned(5 DOWNTO 0);  -- ufix6
  SIGNAL Out1_active                      : std_logic;  -- ufix1
  SIGNAL Data_Type_Conversion_out1_addr   : unsigned(5 DOWNTO 0);  -- ufix6
  SIGNAL Data_Type_Conversion_out1_active : std_logic;  -- ufix1
  SIGNAL tb_enb_delay                     : std_logic;
  SIGNAL Data_Type_Conversion_out1_enb    : std_logic;  -- ufix1
  SIGNAL Data_Type_Conversion_out1_addr_delay_1 : unsigned(5 DOWNTO 0);  -- ufix6
  SIGNAL rawData_In1                      : signed(23 DOWNTO 0);  -- sfix24_En22
  SIGNAL holdData_In1                     : signed(23 DOWNTO 0);  -- sfix24_En22
  SIGNAL In1_offset                       : signed(23 DOWNTO 0);  -- sfix24_En22
  SIGNAL In1                              : signed(23 DOWNTO 0);  -- sfix24_En22
  SIGNAL In1_1                            : std_logic_vector(23 DOWNTO 0);  -- ufix24
  SIGNAL check1_done                      : std_logic;  -- ufix1
  SIGNAL snkDonen                         : std_logic;
  SIGNAL resetn                           : std_logic;
  SIGNAL tb_enb                           : std_logic;
  SIGNAL ce_out                           : std_logic;
  SIGNAL Out1                             : std_logic_vector(23 DOWNTO 0);  -- ufix24
  SIGNAL Out1_enb                         : std_logic;  -- ufix1
  SIGNAL Out1_lastAddr                    : std_logic;  -- ufix1
  SIGNAL Out1_signed                      : signed(23 DOWNTO 0);  -- sfix24_En18
  SIGNAL Out1_addr_delay_1                : unsigned(5 DOWNTO 0);  -- ufix6
  SIGNAL Out1_expected                    : signed(23 DOWNTO 0);  -- sfix24_En18
  SIGNAL Out1_ref                         : signed(23 DOWNTO 0);  -- sfix24_En18
  SIGNAL Out1_testFailure                 : std_logic;  -- ufix1

BEGIN
  u_DUT : DUT
    PORT MAP( clk => clk,
              reset => reset,
              clk_enable => clk_enable,
              In1 => In1_1,  -- sfix24_En22
              ce_out => ce_out,
              Out1 => Out1  -- sfix24_En18
              );

  Out1_done_enb <= Out1_done AND rdEnb;
  Out1_active <= '1' WHEN Out1_addr /= to_unsigned(16#2C#, 6) ELSE '0';
  Data_Type_Conversion_out1_active <= '1' WHEN Data_Type_Conversion_out1_addr /= to_unsigned(16#2C#, 6) ELSE '0';
  Data_Type_Conversion_out1_enb <= Data_Type_Conversion_out1_active AND (rdEnb AND tb_enb_delay);

  -- Count limited, Unsigned Counter
  --  initial value   = 0
  --  step value      = 1
  --  count to value  = 44
  DataTypeConversion_process : PROCESS (clk, reset)
  BEGIN
    IF reset = '1' THEN
      Data_Type_Conversion_out1_addr <= to_unsigned(16#00#, 6);
    ELSIF clk'EVENT AND clk = '1' THEN
      IF Data_Type_Conversion_out1_enb = '1' THEN
        IF Data_Type_Conversion_out1_addr >= to_unsigned(16#2C#, 6) THEN 
          Data_Type_Conversion_out1_addr <= to_unsigned(16#00#, 6);
        ELSE 
          Data_Type_Conversion_out1_addr <= Data_Type_Conversion_out1_addr + to_unsigned(16#01#, 6);
        END IF;
      END IF;
    END IF;
  END PROCESS DataTypeConversion_process;


  Data_Type_Conversion_out1_addr_delay_1 <= Data_Type_Conversion_out1_addr AFTER 1 ns;

  -- Data source for In1
  In1_fileread: PROCESS (Data_Type_Conversion_out1_addr_delay_1, tb_enb_delay, rdEnb)
    FILE fp: TEXT open READ_MODE is "In1.dat";
    VARIABLE l: LINE;
    VARIABLE read_data: std_logic_vector(23 DOWNTO 0);

  BEGIN
    IF tb_enb_delay /= '1' THEN
    ELSIF rdEnb = '1' AND NOT ENDFILE(fp) THEN
      READLINE(fp, l);
      HREAD(l, read_data);
    END IF;
    rawData_In1 <= signed(read_data(23 DOWNTO 0));
  END PROCESS In1_fileread;

  -- holdData reg for Data_Type_Conversion_out1
  stimuli_Data_Type_Conversion_out1_process: PROCESS (clk, reset)
  BEGIN
    IF reset = '1' THEN
      holdData_In1 <= (OTHERS => 'X');
    ELSIF clk'event AND clk = '1' THEN
      holdData_In1 <= rawData_In1;
    END IF;
  END PROCESS stimuli_Data_Type_Conversion_out1_process;

  stimuli_Data_Type_Conversion_out1_1: PROCESS (rawData_In1, rdEnb)
  BEGIN
    IF rdEnb = '0' THEN
      In1_offset <= holdData_In1;
    ELSE
      In1_offset <= rawData_In1;
    END IF;
  END PROCESS stimuli_Data_Type_Conversion_out1_1;

  In1 <= In1_offset AFTER 2 ns;
  In1_1 <= std_logic_vector(In1);
  snkDonen <=  NOT check1_done;
  resetn <=  NOT reset;
  tb_enb <= resetn AND snkDonen;

  -- Delay inside enable generation: register depth 1
  u_enable_delay_process: PROCESS (clk, reset)
  BEGIN
    IF reset = '1' THEN
      tb_enb_delay <= '0';
    ELSIF clk'event AND clk = '1' THEN
      tb_enb_delay <= tb_enb;
    END IF;
  END PROCESS u_enable_delay_process;

  
  rdEnb <= tb_enb_delay WHEN check1_done = '0' ELSE '0';
  clk_enable <= rdEnb AFTER 2 ns;

  reset_gen: PROCESS 
  BEGIN
    reset <= '1';
    WAIT FOR 20 ns;
    WAIT UNTIL clk'event AND clk = '1';
    WAIT FOR 2 ns;
    reset <= '0';
    WAIT;
  END PROCESS reset_gen;

  clk_gen: PROCESS 
  BEGIN
    clk <= '1';
    WAIT FOR 5 ns;
    clk <= '0';
    WAIT FOR 5 ns;
    IF check1_done = '1' THEN
      clk <= '1';
      WAIT FOR 5 ns;
      clk <= '0';
      WAIT FOR 5 ns;
      WAIT;
    END IF;
  END PROCESS clk_gen;

  Out1_enb <= ce_out AND Out1_active;

  -- Count limited, Unsigned Counter
  --  initial value   = 0
  --  step value      = 1
  --  count to value  = 44
  c_3_process : PROCESS (clk, reset)
  BEGIN
    IF reset = '1' THEN
      Out1_addr <= to_unsigned(16#00#, 6);
    ELSIF clk'EVENT AND clk = '1' THEN
      IF Out1_enb = '1' THEN
        IF Out1_addr >= to_unsigned(16#2C#, 6) THEN 
          Out1_addr <= to_unsigned(16#00#, 6);
        ELSE 
          Out1_addr <= Out1_addr + to_unsigned(16#01#, 6);
        END IF;
      END IF;
    END IF;
  END PROCESS c_3_process;

  Out1_lastAddr <= '1' WHEN Out1_addr >= to_unsigned(16#2C#, 6) ELSE '0';
  Out1_done <= Out1_lastAddr AND resetn;

  -- Delay to allow last sim cycle to complete
  checkDone_1_process: PROCESS (clk, reset)
  BEGIN
    IF reset = '1' THEN
      check1_done <= '0';
    ELSIF clk'event AND clk = '1' THEN
      IF Out1_done_enb = '1' THEN
        check1_done <= Out1_done;
      END IF;
    END IF;
  END PROCESS checkDone_1_process;

  Out1_signed <= signed(Out1);
  Out1_addr_delay_1 <= Out1_addr AFTER 1 ns;

  -- Data source for Out1_expected
  Out1_expected_fileread: PROCESS (Out1_addr_delay_1, tb_enb_delay, ce_out)
    FILE fp: TEXT open READ_MODE is "Out1_expected.dat";
    VARIABLE l: LINE;
    VARIABLE read_data: std_logic_vector(23 DOWNTO 0);

  BEGIN
    IF tb_enb_delay /= '1' THEN
    ELSIF ce_out = '1' AND NOT ENDFILE(fp) THEN
      READLINE(fp, l);
      HREAD(l, read_data);
    END IF;
    Out1_expected <= signed(read_data(23 DOWNTO 0));
  END PROCESS Out1_expected_fileread;

  Out1_ref <= Out1_expected;

  Out1_signed_checker: PROCESS (clk, reset)
  BEGIN
    IF reset = '1' THEN
      Out1_testFailure <= '0';
    ELSIF clk'event AND clk = '1' THEN
      IF ce_out = '1' AND Out1_signed /= Out1_ref THEN
        Out1_testFailure <= '1';
        ASSERT FALSE
          REPORT "Error in Out1_signed: Expected " & to_hex(Out1_ref) & (" Actual " & to_hex(Out1_signed))
          SEVERITY ERROR;
      END IF;
    END IF;
  END PROCESS Out1_signed_checker;

  completed_msg: PROCESS (clk)
  BEGIN
    IF clk'event AND clk = '1' THEN
      IF check1_done = '1' THEN
        IF Out1_testFailure = '0' THEN
          ASSERT FALSE
            REPORT "**************TEST COMPLETED (PASSED)**************"
            SEVERITY NOTE;
        ELSE
          ASSERT FALSE
            REPORT "**************TEST COMPLETED (FAILED)**************"
            SEVERITY NOTE;
        END IF;
      END IF;
    END IF;
  END PROCESS completed_msg;

END rtl;

400000
3bfb6f
306e33
1ecad5
0947fe
f29982
dd997a
ccec7c
c2ac63
c02466
c5a72b
d28404
e51cee
fb1a6e
11b6fc
26185f
35abd1
3e7942
3f6333
384b08
2a153a
168cc4
00295c
e9bfc1
d62619
c7d809
c0a610
c17a7e
ca3b77
d9cdeb
ee392a
04e6a9
1af5e7
2d99d5
3a73f3
3fe17a
3d30d7
32b882
21cc58
0c92a6
f5be67
e03772
cebbd2
c38645
c00555

000000
000000
000005
000077
000408
0014ed
004be0
00cd4b
01ac06
02b7b3
0368ba
032177
01ae22
ff8e83
fda881
fc99b3
fc523d
fc5bed
fc7483
fccde8
fdbb12
ff39c2
00e1d6
023f68
032708
03adf4
03e59c
03b64d
02feee
01c946
005287
fee3b3
fdaae7
fcbb1c
fc2504
fc02e9
fc68ff
fd4f4b
fe8f54
fff668
01579d
028e99
037903
03f5ad
03ee57

※Simulinkモデルから自動生成されたテストベンチです。

VHDL/Verilogテストベンチ

Simulinkモデルで作成されたHDLテストベンチです。上側に置かれているブロック線図モデル(DUT)と、下側に置かれているHDLコードをシミュレーションするブロック(DUT_vs)に対して、ダイナミックに等価性検証を行う仕組みになっています。HDLコードの実行はHDLシミュレータで行われ、HDLシミュレータとSimulinkが連携(Co-simulation)して動作します。入力信号の種類や特性は、ブロックパラメータで変更できるため、スティミュラスの変更が容易というメリットがあります。

図6. コシミュレーションモデル外観

図6. コシミュレーションモデル外観

System Verilog DPI コンポーネント生成およびUVMテストベンチ

テストベンチをスティミュラス/期待値のデータセットではなく、Simulinkモデルと等価なビヘイビア記述として生成する機能がSystem Verilog DPIです。もともとSystem VerilogにはDPI-C(Direct Programming Interface)と呼ばれるCコードの関数を、簡単な記述で呼び出す非常に便利な機能があります。System Verilog DPI コンポーネント生成機能は、この機能を用いてHDLシミュレーション可能な、MATLABやSimulinkモデルから生成したビヘイビア記述のCコードと、ラッパーとなるSystem Verilogコードを自動生成します。

大規模ASICの開発では、System Verilogで記述されるUVM(Universal Verification Methodology)を使用して、フレームワーク化したテストベンチにおいて、そこで使用されるコンポーネントを再利用しやすいようにライブラリ化しています。MATLAB/Simulinkモデルから、このUVMのフレームワークで使用できるコンポーネントを生成することができます。

System Verilog DPI コンポーネント生成について:HDL Verifier > Verification with UVM and SystemVerilog Components

FPGA実機のテストベンチ

HDLコードでのシミュレーションの利点は、クロックサイクルで手軽に回路記述をテストできる点です。その際、任意のポイントで波形をモニターでき、詳細なレベルまで回路動作を確認することができます。欠点は、回路規模やスティミュラスのデータ量の増大によって、テストベンチ作成やシミュレーション実行の時間が増大してしまうことです。FPGA実機でのテストは、モニターできるポイントやサンプル数には物理的な成約があるものの、そのような問題を解決してくれる側面があります。

ロジック アナライザー実機をFPGAボードに接続

オシロスコープよりも、たくさんのチャンネルを同時に測定できるデジタル信号の解析に特化した測定器です。FPGAのピンにプローブを接続して波形をモニターしてデバッグします。実機で生じている問題をリアルタイムで解析できる利点がある一方、不具合の再現性に難がある点や、プローブを接続して波形表示の設定をするなどの手間がかかる点が欠点です。キーサイト テクノロジー社、テクトロニクス社など測定機器メーカーが販売しています。

インサーキットテスタ

FPGAの内部信号を取得してPC上で表示する機能です。追加でFPGAのI/Oピンを設定したり、高価な測定器を接続したりすることなく、動作中のデバイスの内部信号を、FPGA内部のRAMにロギングし、取得後データをPCにアップロードしてモニターします。HDLコードを論理合成する前に、予め取得ポイントの設定を行っておく必要があります。この機能はFPGAメーカーおよび関係各社が提供しています。

  • AMD社 Integrated Logic Analyzer
  • Intel社 Signal Tap Logic Analyzer
  • Microchip社 SmartDebug
  • MathWorks社 FPGA Data Capture
図7. FPGA Data Capture

図7. FPGA Data Capture

FPGA-In-the-Loop(FIL)

モデルベースデザイン (モデルベース開発、MBD)で利用されるモデルとFPGA間の等価性検証手法の一つです。この機能では、USB-JTAG/Ethernet/PCI Expressなどのインターフェイスにより通信を行い、MATLAB/Simulinkで作成したテストベンチを使用してFPGAを動作させて検証を行います。HDLコシミュレーションよりも大幅に高速に動作するため、膨大な量のスティミュラスを使用したり、検証時間を短縮したりする効果があります。

図8. FPGA-In-the-Loop

図8. FPGA-In-the-Loop

PC上で動作するMATLABおよびSimulinkはFPGAと比較すると実行速度が遅いので、FPGAはスティミュラスを待ちながら動作することになります。そのため、リアルタイムでのタイミングやペリフェラルを含めた動作検証というよりも、論理の正しさを検証するための機能となっています。

AXI Manager

AXI ManagerもFPGA-In-the-Loopと同様にPCからFPGAにアクセスする機能ですが、リアルタイムで動作するオンボードFPGA内のレジスタや接続されているDDRメモリに対してRead/Writeを行ってデバッグをするために利用します。

本機能のインターフェイスもUSB-JTAG/Ethernet/PCI Expressを選択できます。

図9. AXI Manager

図9. AXI Manager


参考: FPGA、ASIC、および SoC 開発向け MATLAB, HDL Coder, HDL Verifier, Vision HDL Toolbox, MATLAB Coder, Simulink Coder