[SystemVerilog]ランダム値の出現確率を変える。

こんにちは、@tethys_seesaaです。
#はじめに。
SystemVerilogといえば制約付きランダムが有名ですけども、実際使うと、制約の範囲内でどれくらい値がばらまかれているのか把握してないといけないよねとか思います。
いろいろなやり方があると思いますが、個人的には`covergroup`をよく使っています。カバレッジドリブン検証ってやつでしょうか。
そんなことはさておき、ここでは、covergroupを使って、ランダム値の出現確率を変えてみましょう。

#例えば、
covergroupベースでこんなコード走らせてみます。
これは収束するまで相当時間がかかります。実はこのコード、各社のシミュレーターのランダム値出力エンジンの性格の差が如実に表れます。あ、制約ないですねコレ。

```verilog:tb.sv
program tb();
  bit clk;
  logic [7:0] a,b,c;
  event sim_end;

  class abc_class;
    rand logic [7:0] ra, rb, rc;

    covergroup abc_cg @(posedge clk) ;
      a_point : coverpoint a {
        bins a_min    = { 'h00 } ;
        bins a_middle = { ['h01:'hfe] };
        bins a_max    = { 'hff };
      }
      b_point : coverpoint b {
        bins b_min    = { 'h00 } ;
        bins b_middle = { ['h01:'hfe] };
        bins b_max    = { 'hff };
      }
      c_point : coverpoint c {
        bins c_min    = { 'h00 } ;
        bins c_middle = { ['h01:'hfe] };
        bins c_max    = { 'hff };
      }
      abc_point : cross a_point, b_point, c_point;
    endgroup

    function new();
      abc_cg = new();
    endfunction

  endclass

  abc_class abc = new();

  task clk_gen();
    forever  #1  clk = ~clk;
  endtask

  task data_gen();
    forever @(posedge clk) begin
      void'(abc.randomize());
      a = abc.ra;
      b = abc.rb;
      c = abc.rc;
      // $display("a : 0x%x b : 0x%x c : 0x%x", a, b, c);
    end
  endtask

  task cov_correction();
    real number_of_coveage;
    forever @(posedge clk) begin
      number_of_coveage = abc.abc_cg.abc_point.get_coverage();
      // $display("Coverage is %f", number_of_coveage);
      if(number_of_coveage == 100) ->sim_end;
    end
  endtask

  task sim_ctrl();
    @(sim_end);
    $finish(2);
  endtask

  initial begin
    fork
      clk_gen();
      data_gen();
      cov_correction();
      sim_ctrl();
    join
  end

endprogram
```

このコードのシミュレーションに時間がかかるのは、コーナーケース到達がとてもまれだからです。
a=='h00, b=='h00, c=='h00とか、a=='hFF, b=='h00, c=='hFFとかですね。

#じゃあどうするか。
早期に収束させるには`dist`を使う手があります。コーナーケース出現の重みを中間値より大きくします
例えばこんな制約を与えてみます。

```verilog
    constraint weight{
      ra dist {
        'h00        := 100,
        ['h80:'hfe] := 10,
        'hff        := 100
      };
      rb dist {
        'h00        := 100,
        ['h80:'hfe] := 10,
        'hff        := 100
      };
      rc dist {
        'h00        := 100,
        ['h80:'hfe] := 10,
        'hff        := 100
      };
    }
```

これを使うと、あっという間にシミュレーションは終了しますが、今度はコーナーケースを頻出して、中間値が十分に出てこないジレンマに陥ります。
この辺、検証対象によってケースバイケースですから、コレはコレでよいのかもしれませんが、もうちょっと中間値を出したいと思うときは、distの重みを適当に調節しなくてはなりません。
ただ、distの重みの値は自由につけられるがゆえ、調節はトライアンドエラーとなりけっこう面倒くさいです。
distを静的に与えるのは面白くないので、SystemVerilogの機能でもうちょっと制御できないかなと考えてみました。
#get_coverage()
ここではcovergroupのget_coverage()を使ってみます。
既に最初のコードで出てきていますが、get_coverage()はcoverpoint毎にヒットしたアイテムの数を示し、最小値は0、最大値は100となります。
狙いは、 **get_coverage()の返値をフィードバックして、ランダムの出現確率を変えていく** ことです。

以下は、全て最初のコードのclass内に追加します。
まずはdistの重みを変数化します。

```verilog
    int  wa_min    = 100;
    int  wa_middle = 100;
    int  wa_max    = 100;
    int  wb_min    = 100;
    int  wb_middle = 100;
    int  wb_max    = 100;
    int  wc_min    = 100;
    int  wc_middle = 100;
    int  wc_max    = 100;

    constraint weight{
      ra dist {
        'h00        := wa_min,
        ['h80:'hfe] := wa_middle,
        'hff        := wa_max
      };
      rb dist {
        'h00        := wb_min,
        ['h80:'hfe] := wb_middle,
        'hff        := wb_max
      };
      rc dist {
        'h00        := wc_min,
        ['h80:'hfe] := wc_middle,
        'hff        := wc_max
      };
    }
```

次に、coverpointを新たに追加します。get_coverage()がbinsまで指定できればこれをする必要が無いのですが…。

```verilog
  localparam BUCKET = 16;

    covergroup abc_cg @(posedge clk) ;
      a_cp_min    : coverpoint a { bins a         = { 'h00 } ; }
      a_cp_middle : coverpoint a { bins a[BUCKET] = { ['h01:'hfe] }; }
      a_cp_max    : coverpoint a { bins a         = { 'hff }; }
      b_cp_min    : coverpoint b { bins b         = { 'h00 } ; }
      b_cp_middle : coverpoint b { bins b[BUCKET] = { ['h01:'hfe] }; }
      b_cp_max    : coverpoint b { bins b         = { 'hff }; }
      c_cp_min    : coverpoint c { bins c         = { 'h00 } ; }
      c_cp_middle : coverpoint c { bins c[BUCKET] = { ['h01:'hfe] }; }
      c_cp_max    : coverpoint c { bins c         = { 'hff }; }

      a_point : coverpoint a {
        bins a_min    = { 'h00 } ;
        bins a_middle = { ['h01:'hfe] };
        bins a_max    = { 'hff };
      }
      b_point : coverpoint b {
        bins b_min    = { 'h00 } ;
        bins b_middle = { ['h01:'hfe] };
        bins b_max    = { 'hff };
      }
      c_point : coverpoint c {
        bins c_min    = { 'h00 } ;
        bins c_middle = { ['h01:'hfe] };
        bins c_max    = { 'hff };
      }
      abc_point : cross a_point, b_point, c_point;
    endgroup
```

次に、それぞれのアイテムのカバレッジを抽出し、pre_randomize()で重みを変化させます。
get_coverage()の返値はreal型なので、ここでは$rtoi()を使ってみました。

```verilog
    function int weight_set(int weight);
      real c;
      case(weight)
        wa_middle : c = abc_cg.a_cp_middle.get_coverage();
        wb_middle : c = abc_cg.b_cp_middle.get_coverage();
        wc_middle : c = abc_cg.c_cp_middle.get_coverage();
      endcase
      return 100 - $rtoi(c);
    endfunction

    function void pre_randomize();
      wa_middle = weight_set(wa_middle);
      wb_middle = weight_set(wb_middle);
      wc_middle = weight_set(wc_middle);
    endfunction
```

#どんな動きをしているか
最初は、最大値/中間値/最小値いずれも同等に値を出力していきます。
中間値はget_coverage()によって、distの重みが小さくなっていくため、シミュレーション時間の進行につれて相対的にコーナーケースの出現確率が大きくなって、シミュレーションが終わります。
BUCKETの値を大きくすればするほど、シミュレーションの時間は長くなっていきます。
#おわりに。
covergroupは、発生するbinsの構成によっては大きなメモリ空間をとるのでシミュレーション時間が長くなる場合があり記述に一工夫必要になります。今回は、比較的フリーダムででかそうなcovergroupに対して、それに応じたランダム値を与えるようなことやってみました。
実際のカバレッジドリブン検証では、`cross`を使う場合には`binsof`と`intersect`を使って重複部分を削減させたり、それ以外でも、`illegal_bins`を使って早期に検証対象を叩き潰したり、`wildcard`でどうでもいいところはまとめてひとつにしたり、covergroupのグルーピングを工夫して、要らないところは`ignore_bins`指定したりして早期に収束させるところを目指していくといいかもしれません。