[SystemVerilog]即時アサーションでコネクティビティをチェックする。

#はじめに。
こんにちは。 @tethys_seesaa です。
皆さんはSystemVerilogアサーション(SVA)をお使いでしょうか？ **自分は使っていません。**
スクラッチで、複数サイクルにまたがるプロパティやシーケンスを書き、アサーションとしてシミュレーションで使うのはリスキーなんですよねぇ。
欲張って書こうとすると、意図しないシーケンスがヒットしたり、シミュレーションがパスってもアサーションがエラーだったりと、大抵は **お世話する子が2倍になる** 印象です。
HDL+αを狙うと、SVAは簡単に書けそうでついつい手を出しがちですが、開発工数とのトレードオフを考えるとあまりやりたくありません。むろん、そういう命令が来たら従うほかしかない身分ですけどね。
やるとしたら、ベンダのライブラリを使ってbindし、限定的な範囲に留める感じですかねー。今じゃバスシステムもAXIとかデファクトですし、それだったらベンダから購入した方が効率的と思います。それをbindするのも一仕事ですし、それが面白いか面白くないかは人によりけりと思いますのでそこまでは言及しません。
#即時アサーション
というわけでSVAなんですけど、使いたいときはたまに使います。
SVA=コンカレントアサーションで学ぶ人が多いというか、セミナー等だとコンカレントアサーションの内容がほぼ全てといった印象だから、ここでは、 **即時アサーション(イミディエイトアサーション)** にフォーカスします。

対象は、RTLのコネクティビティ(接続性)チェックです。

コネクティビティチェックってどうやってやりますかね。
VHDLせよVerilogにせよ、比較的大きな規模の回路となると、コードでコネクティビティをチェックするのは結構大変です。
地味な作業ですが、回避するのは難しいです。
自分の場合、隣のプロジェクトチームが二つのクロックを逆につないでいることが **ES時に発覚し、** リスピンを余儀なくされたのを目の当たりにしましたことがあります。いやー、大変そうですねあはは。
今回紹介する方法は、その工夫の **手段の一つ** です。
#デザイン(DUT)
まずこれはDUTとテストで共通して使うコードです。
2を底とする対数を出します。このコードだとDEPTHは2のべき乗に限られます。
従って、WIDTHは8となります。以下、DUTもテストもデータのビット幅は8bitとします。

```verilog:common.sv
function int log2(int v);
  if (v <= 1)
    return 1;
  v = v-1;
  for (log2=0; v>0; log2++)
    v >>= 1;
endfunction

parameter DEPTH = 256;
parameter WITDH = log2(DEPTH);

typedef logic [WITDH-1:0] data_t;
```
次はDUTです。
ざっくり説明すると、二つの信号をANDするモジュールと、二つの信号を加算するモジュールを置き、
上位モジュールのcoreでどちらかの出力信号を選択します。i_sel=1だとAND、i_sel=0だと加算を選択します。
もし、わかりにくかったらブロック図とか作成して追記しますから、コメントでリクエスト下さい。
今回は、ここの選択部分が、トップ階層まで接続されているかをチェックします。

```verilog:design.sv
`timescale 1ns/1ps

module design_top # (
  parameter WITDH = log2(DEPTH)
  )(
  input  logic   rst_n, clk,
  input  logic   i_sel,
  input  data_t  i_a, i_b,
  output data_t  o_c
  );

  core u_core(.*);

endmodule

module core  # (
  parameter WITDH = log2(DEPTH)
  )(
  input  logic   rst_n, clk,
  input  logic   i_sel,
  input  data_t  i_a, i_b,
  output data_t  o_c
  );

  data_t and_c, add_c;

  m_and u_and(.o_c(and_c), .*);
  m_add u_add(.o_c(add_c), .*);

  always_comb
    if(i_sel)
      o_c = and_c;
    else
      o_c = add_c;

endmodule

module m_and  # (
  parameter    WITDH = log2(DEPTH)
  )(
  input  logic   rst_n, clk,
  input  data_t  i_a, i_b,
  output data_t  o_c
  );

  always_ff @(negedge rst_n, posedge clk)
    if(!rst_n)
      o_c <= '0;
    else
      o_c <= i_a & i_b;

endmodule

module m_add # (
  parameter WITDH = log2(DEPTH)
  )(
  input  logic   rst_n, clk,
  input  data_t  i_a, i_b,
  output data_t  o_c
  );

  function  data_t f_add (data_t a, data_t b);
    logic [WITDH:0] t;
    t = a + b;
    return t[WITDH-1:0];
  endfunction

  always_ff @(negedge rst_n, posedge clk)
    if(!rst_n)
      o_c <= '0;
    else
      o_c <= f_add(i_a, i_b);

endmodule
```

#テスト
テキトーにリセット、クロック、i_selを発生させて、二つの8bit信号をランダムに入力させ、
i_selが変化したら、テキトーなタイミングでコネクティビティをチェックします。

コネクティビティをチェックしているのは、connect_check()タスクです。

ここで、即時アサーションでコネクティビティを表明し、成立しなかったらconnect_error(" ")タスクをコールします。
シミュレーションを実行すると、connect_check()タスクで$displayで書かれてあるところが、コンソールにドカドカ出てきて、シミュレーションが終了すると思います。

if(i_sel)を、わざと **if(!i_sel)** と書き直すと、i_selが変化してしばらくしてシミュレーションがエラーで終了します。

```verilog:tb.sv
`timescale 1ns/1ps

module tb();

  parameter CYC = 1_000_000;
  parameter AP  = 525;
  parameter AN  = 2400;
  parameter CHK = 10;

  logic   rst_n, clk;
  logic   i_sel;
  data_t  i_a, i_b;
  data_t  o_c;

  event e;

  design_top dut (.*);

  task gen_rst();
        rst_n = '0;
    #7  rst_n = '1;
  endtask

  task gen_clk();
    clk = '0;
    forever
      #5 clk = ~clk;
  endtask

  task gen_data();
    i_a = '0;
    i_b = '0;
    forever @(posedge clk) begin
      i_a = $random();
      i_b = $random();
    end
  endtask

  task gen_sel(int p, n);
    i_sel = '0;
    forever begin
      repeat(p) begin
        i_sel = '1;
        @(posedge clk);
      end
      repeat(n) begin
        i_sel = '0;
        @(posedge clk);
      end
    end
  endtask

  task sel_p();
    forever begin
      @(posedge i_sel);
      #CHK -> e;
    end
  endtask

  task sel_n();
    forever begin
      @(negedge i_sel);
      #CHK -> e;
    end
  endtask

  task ctrl_test(int cycle);
    repeat(cycle)
      @(posedge clk);
    $finish(2);
  endtask

  task connect_check();
    forever begin
      $display("Connectivity Check start.");
      @(e);
      if(i_sel)
        assert (o_c == dut.u_core.u_and.o_c)
          else connect_error("m_and");
      else
        assert (o_c == dut.u_core.u_add.o_c)
          else connect_error("m_add");
      $display("Connectivity Check end.");
    end
  endtask

  task  connect_error(string m);
    $display ("ERROR! : %s Connectivity is wrong.", m);
    $finish(1);
  endtask

  initial begin
    fork
      gen_rst();
      gen_clk();
      gen_data();
      gen_sel(AP, AN);
      ctrl_test(CYC);
      sel_p();
      sel_n();
      connect_check();
    join
  end

endmodule
```

#というわけで、
いかがでしたでしょうか。
このぐらいの規模だと即時アサーション絡みのコードが冗長に思えるかもしれません。
このテストのやり方が効果を発揮するのは、回路の規模が大きくなったり、同一モジュールを多くインスタンスしているときなどになるかと思います。
上記の場合、SVA自体も記述するのが大変になってくるので、Excelとかのスプレッドシートで視認性を向上させ、そこからVBAなりを使ってSVAに落とし込むといったやり方がいい思います。
Excelですと、他のお仕事(ピンチェックとか)にも応用が利き、手動で行う部分を減らしてプロジェクト全体のバグ削減が見込めるでしょう。
自分はLinuxが開発のメインプラットフォームなので、ExcelをPythonで読み込んでSVAに出力させています。
#ちなみに、
これくらいは誰か既に考えているだろうと思って調べてみたら、それに近いコードがVerification Academyに載ってました。
https://forum.verificationacademy.com/forum/verification-methodology-discussion-forum/systemverilog-and-other-languages-forum/25805-connectivity-checking
`uvm_errorを使ってUVMのフローに組み込んでいるようですね。UVMを使えばオサレに見えるかもしれません。
#おわりに。
各方面を調べてみると、コネクティビティチェックはやはり課題となっているようで、フォーマル検証ツールを俯瞰すると、コネクティビティチェックのSVAを自動で生成する機能が備わっているものが多いです。
フォーマル検証ツールだと、検証に実行するまでの準備に時間がかかるので、この機能はとても便利そうに見えます。皆さん是非使って下さい。つーか、 **使えよお前ら。**
##おまけ
MentorGraphics Questaだと、以下でたぶん動きます。

```bash
vlib work
vlog -mfcu ./common.sv ./design.sv ./tb.sv
vsim -c tb
```

Synopsys VCSだとたぶんこんな感じ。

```bash
vcs -sverilog ./common.sv ./design.sv ./tb.sv
./simv
```

Cadence Incisiveはこうかなあ。

```bash
irun  ./common.sv ./design.sv ./tb.sv
```

これら、SystemVerilogコードはSublime Text 2で書きました。
二度言いますね、これら、SystemVerilogコードは **Sublime Text 2** で書きました。