こんにちは、@tethys_seesaaです。
#はじめに。
SystemVerilogといえば制約付きランダムが有名ですけども、実際使うと、制約の範囲内でどれくらい値がばらまかれているのか把握してないといけないよねとか思います。
いろいろなやり方があると思いますが、個人的にはcovergroup
をよく使っています。カバレッジドリブン検証ってやつでしょうか。
そんなことはさておき、ここでは、covergroupを使って、ランダム値の出現確率を変えてみましょう。
#例えば、
covergroupベースでこんなコード走らせてみます。
これは収束するまで相当時間がかかります。実はこのコード、各社のシミュレーターのランダム値出力エンジンの性格の差が如実に表れます。あ、制約ないですねコレ。
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
を使う手があります。コーナーケース出現の重みを中間値より大きくします
例えばこんな制約を与えてみます。
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の重みを変数化します。
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まで指定できればこれをする必要が無いのですが…。
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()を使ってみました。
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
指定したりして早期に収束させるところを目指していくといいかもしれません。