VHDL テストベンチ / Verilog テストベンチ書くのなんてもうやめときなよ・・・

# VHDL/Verilogの検証
[FPGA/ASIC](https://jp.mathworks.com/discovery/fpga.html)などのディジタル回路設計者はVHDL/Verilogで回路記述をした後、テストを行うのに[テストベンチ](https://jp.mathworks.com/discovery/vhdl-testbench.html)を作成されていると思います。

スティミュラスと期待値はExcelなどで計算して、そのデータをReadするVHDL/Verilogのテストベンチを書いている人が多いんじゃないでしょうか。

アサーションも使いたいという人は、System VerilogやPSLなどを使っているかもしれませんね。

この記事は、コード生成や高位合成なんて信用出来ないので、VHDL/Verilog回路記述は手書きするけど、テストベンチだけ自動生成で楽したいという方向けの内容となっています。

# テストベンチは重要
テストベンチは言うまでもなく非常に重要で、これが間違っていると何をテストしているのやらわからなくなってしまいます。これがバグっていると、回路記述のバグを見つけることもできません。データのタイミングやビット精度も含めて、間違いなく記述する必要があります。

[Wilson research Group](https://blogs.sw.siemens.com/verificationhorizons/2022/10/30/part-3-the-2022-wilson-research-group-functional-verification-study/)による調査だと、FPGA設計プロジェクトにおいて、おおよそ半分の工数が検証に使われているようなので、それにかかる手間暇は馬鹿にならないですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477651/23cb20ca-48c9-4995-4c91-7c4faf93eb6d.png)

# 手書きだと絶対に間違う
言うまでもなく手書きでコードを書くと絶対に間違いが生じます。書き間違いをしたことが無いなんて完璧人間がいたら、拝みに行って銅像立ててあげたいです。

間違えないためには・・・最近ローコードやノーコード開発なんてのも流行っているようですが、そんなものはFPGA設計には無いと思うので、自動生成してしまいましょう。

# テストベンチを自動生成する
MathWorksのMATLAB/SimulinkのオプションHDL Coderはそもそも、回路記述を自動生成するツールなんですが、テストベンチも自動生成できちゃいます。

ただ、このHDL Coder、テストベンチ生成しようとすると、回路記述まで生成しようとして面倒なことになるので、テストベンチ「**だけ**」生成するには、HDL Coderをちょっとダマして使ってあげます。

サンプルとして、フィルタのシミュレーションをするモデルを作成しました。
入力信号はSin波のスウィープ信号で、可視化は時間軸応答。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477651/0f3dd3ad-13c6-5e9b-c093-6bd1db5bcc3e.png)


データ型のパラメータ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477651/5bbc357e-ee21-413e-9d70-3d0ba87b53fb.png)

Lowpass Filterブロックのパラメータ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477651/723e31fc-f265-dbf5-6f45-76d140e1d169.png)

シミュレーション結果は期待したとおりになっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477651/d1165ff9-f0d5-44da-fc42-4fca5909a196.png)

テストベンチを生成するために設計対象部分をサブシステム化します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477651/4cd861b5-c9aa-925d-8088-2d75a444565e.png)

サブシステム化後のモデルで、設計対象のVHDL/Verilogファイル名と同じになるように、サブシステム名を変更します。ここではDUTとしています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477651/c5c2300c-2439-a10c-9154-c14368afadd8.png)

もう一つ階層を作成して、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477651/63550491-90fd-6ff8-2df1-c75acc990385.png)

ここで作成したサブシステムのHDLブロックプロパティを [No HDL] つまり、HDL生成しない設定にします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477651/12892882-5bb5-ecdf-23d1-388523ec7fd8.png)

メニューを選択してテストベンチを生成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477651/f35eee3f-1ab8-5aee-0659-fef1c693871c.png)

# 生成されたテストベンチ

<details><summary>生成されたテストベンチ（▲をクリックで展開）</summary>

```Verilog: DUT_tb.v
// -------------------------------------------------------------
// 
// File Name: hdlsrc\sample_testbench\DUT_tb.v
// Created: 2023-02-28 18:05:25
// 
// Generated by MATLAB 9.13 and HDL Coder 4.0
// 
// 
// -- -------------------------------------------------------------
// -- Rate and Clocking Details
// -- -------------------------------------------------------------
// Model base rate: 2.26757e-05
// Target subsystem base rate: 2.26757e-05
// 
// -------------------------------------------------------------


// -------------------------------------------------------------
// 
// Module: DUT_tb
// Source Path: 
// Hierarchy Level: 0
// 
// -------------------------------------------------------------

`timescale 1 ns / 1 ns

module DUT_tb;



  reg  clk;
  reg  reset;
  wire enb;
  wire Out1_done;  // ufix1
  wire rdEnb;
  wire Out1_done_enb;  // ufix1
  reg [15:0] Out1_addr;  // ufix16
  wire Out1_active;  // ufix1
  reg  check1_done;  // ufix1
  wire snkDonen;
  wire resetn;
  wire tb_enb;
  reg  tb_enb_delay;
  wire ce_out;
  wire Out1_enb;  // ufix1
  wire Out1_lastAddr;  // ufix1
  reg [15:0] Data_Type_Conversion_out1_addr;  // ufix16
  wire Data_Type_Conversion_out1_active;  // ufix1
  wire Data_Type_Conversion_out1_enb;  // ufix1
  wire [15:0] Data_Type_Conversion_out1_addr_delay_1;  // ufix16
  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
  wire signed [23:0] Out1;  // sfix24_En21
  wire [15:0] Out1_addr_delay_1;  // ufix16
  reg signed [31:0] fp_Out1_expected;  // sfix32
  reg signed [23:0] Out1_expected;  // sfix24_En21
  reg signed [31:0] status_Out1_expected;  // sfix32
  wire signed [23:0] Out1_ref;  // sfix24_En21
  reg  Out1_testFailure;  // ufix1
  wire testFailure;  // ufix1


  assign Out1_done_enb = Out1_done & rdEnb;



  assign Out1_active = Out1_addr != 16'b1010110001000100;



  assign #2 enb = rdEnb;

  assign snkDonen =  ~ check1_done;



  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

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

  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 ce_out = enb & (rdEnb & tb_enb_delay);



  assign Out1_enb = ce_out & Out1_active;



  // Count limited, Unsigned Counter
  //  initial value   = 0
  //  step value      = 1
  //  count to value  = 44100
  always @(posedge clk or posedge reset)
    begin : c_2_process
      if (reset == 1'b1) begin
        Out1_addr <= 16'b0000000000000000;
      end
      else begin
        if (Out1_enb) begin
          if (Out1_addr >= 16'b1010110001000100) begin
            Out1_addr <= 16'b0000000000000000;
          end
          else begin
            Out1_addr <= Out1_addr + 16'b0000000000000001;
          end
        end
      end
    end



  assign Out1_lastAddr = Out1_addr >= 16'b1010110001000100;



  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 Data_Type_Conversion_out1_active = Data_Type_Conversion_out1_addr != 16'b1010110001000100;



  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  = 44100
  always @(posedge clk or posedge reset)
    begin : DataTypeConversion_process
      if (reset == 1'b1) begin
        Data_Type_Conversion_out1_addr <= 16'b0000000000000000;
      end
      else begin
        if (Data_Type_Conversion_out1_enb) begin
          if (Data_Type_Conversion_out1_addr >= 16'b1010110001000100) begin
            Data_Type_Conversion_out1_addr <= 16'b0000000000000000;
          end
          else begin
            Data_Type_Conversion_out1_addr <= Data_Type_Conversion_out1_addr + 16'b0000000000000001;
          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;

  DUT u_DUT (.In1(In1),  // sfix24_En22
             .Out1(Out1)  // sfix24_En21
             );

  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, rdEnb, tb_enb_delay)
    begin
      if (tb_enb_delay == 0) begin
        Out1_expected <= 24'bx;
      end
      else if (rdEnb == 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


```
</details>

<details><summary>スティミュラスデータ（▲をクリックで展開）</summary>

```: In1.dat
400000
3ffe54
3ff948
3ff0cf
3fe4de
3fd567
3fc25f
3fabb9
3f916a
3f7367
3f51a3
3f2c13
3f02ad
3ed566
3ea432
 省略

```

</details>

<details><summary>期待値データ（▲をクリックで展開）</summary>

```: Out1_expected.dat
00263a
011270
0409e3
0a5d72
13fa13
1e548d
254b8f
261652
21d7a9
1d0c9a
1bd52a
1e7b48
21ae93
222a99
1feb70
1db5c9
1dcf26
1fab44
20eaeb
202fb5
 省略

```

</details>

# 所要時間は記事書くのも含めて30分ぐらい

回路性能に影響しないテストベンチなんてシコシコ書いてないで自動生成して楽しましょう。

# 参考文献
[HDL Coder製品情報](https://jp.mathworks.com/products/hdl-coder.html)