SystemVerilog Tutorial
Last
Updated Aug.7.2013
下記は、SystemVerilog Tutorial(P1800 LRM 2012 )です。 VeritakSVは、未だリリースの予定をお話しできる段階ではありません。 )
目次ページへ
16.SystemVerilogの新機能
16.1 fork join / fork join_any/ fork join_none
16.1.1 平行プロセスjoin_anyとjoin_noneの追加
verilogでは、fork joinで挟まれたステートメントは、平行プロセスになります。
たとえば、my_taskを起動するのに、
と書く代わりに、二つの平行プロセスを同時に起動するforkを使い、
と書くことができます。joinは、合流するという意味合いです。二つのプロセスの終了を待ってjoin以下が始まります。
SVでは、さらにjoin_anyとjoin_noneが追加されています。
ベンチです。
module fork_test2; task my_task; $display("Hi %d ",$time); endtask initial begin fork//:Fork_Join #5 my_task;//process1 #10 my_task;//process2 join $display("Join Any Time=%d",$time); end initial begin fork//:Fork_Any #5 my_task;//process1 #10 my_task;//process2 join_any $display("Join Any Time=%d",$time); end initial begin fork//:Fork_None #5 my_task;//process1 #10 my_task;//process2 join_none $display("Join None Time=%d",$time); end endmodule
結果です。join_noneの方は、すぐに fork- join_noneを抜けていることが分かります。しかし、タスクの起動は、時刻#5と#10でしっかり行われています。この記述は、なにかtaskを起動しておいて、main()
を処理する形の定石かもしれません。
join_anyの方は、#5と#10のスレッドの内、早いほうのスレッドが終了した時点で抜けます。
fork-join は、fork-join内の全てのスレッドの完了を待って抜けます。
Loading vpi.dll Load Done. ***** Veritak SV Engine Version 0.021 Build Dec.30.2007 ***** Join None Time= 0 Hi 5 Hi 5 Join Any Time= 5 Hi 5 Hi 10 Join Any Time= 10 Hi 10 Hi 10 **** Test Done. Total 13.00[msec] ****
join_noneは、子プロセスの終了を待たずに、起動します。スルーしているかのように見えますが。親プロセス、子プロセス共、平行に走ります。(正確には、親プロセスのブロッキング文の実行後に子プロセスはスタートします。)いわば、”ノンブロキング”なプロセス形態です。
join_anyは、子プロセスのうち最初に終了したプロセスの後(どれでもよい)、親プロセスが始まります。従い、終了していない子プロセスと親プロセスとで平行に走ります。使い道としては、たとえばタイムアウト処理に使えそうです。
fork join_xx を使うと、複雑なプロセスが記述できます。
ベンチで少し詳しく動作を見ていきましょう。
module top; initial begin fork do begin #1; $display("1st thread running time=%d",$time); end while($time<10); join_none // join_non なので、do while を起動してすぐに下に行く fork begin #3; $display("2nd thread finished time=%d",$time); end begin #4; $display("3rd thread finished time=%d",$time); end join_any //join_any なので、一つ起動したら下に行く // disable fork; end endmodule
***** Veritak SV Engine Version 0.031 Build Jun.23.2008 ***** 1st thread running time= 1 1st thread running time= 2 2nd thread finished time= 3 1st thread running time= 3 3rd thread finished time= 4 1st thread running time= 4 1st thread running time= 5 1st thread running time= 6 1st thread running time= 7 1st thread running time= 8 1st thread running time= 9 1st thread running time= 10 sim_finished **** Test Done. Total 23.00[msec] ****
このベンチでは、3つのスレッドが起動していることが分かりますね。
最後の コメントアウトしているdisable fork をイネーブルしてみましょう。
***** Veritak SV Engine Version 0.031 Build Jun.23.2008 ***** 1st thread running time= 1 1st thread running time= 2 2nd thread finished time= 3 sim_finished **** Test Done. Total 10.00[msec] ****
disable forkは、現在のスレッドが起動した全ての子スレッドをdisable します。disable fork の行にやってくるのは、fork -any 中の早く終了するスレッド#3が終わった時点になります。その時点で、現在のinitial
スレッドが起動した全てのスレッドを disable します。 disable されたスレッドは、消滅します。従って、予約イベントも取り消され、終了するという訳です。従って、1st
2nd 3rd スレッド全てが終了されます。ここでdisable という意味は、停止というよりは、消滅です。消えてなくなると思って構いません。
ここで問題です。特定の一つのスレッドをdisable するには、どうしたらよいでしょうか?
それには、ラベルをつけて disable すればよいですね。下では、THREAD1というラベルをつけて それを disable
しています。
module top; initial begin fork :THREAD1 do begin #1; $display("1st thread running time=%d",$time); end while($time<10); join_none : THREAD1 fork begin #3; $display("2nd thread finished time=%d",$time); end begin #4; $display("3rd thread finished time=%d",$time); end join_any disable THREAD1; $display("THEAD1 Disabled"); end endmodule
16.1.3 disable と disable fork の違い
結果です。disable THREAD1を行うことで、THREAD1は、意図通りdisableされました。
***** Veritak SV Engine Version 0.031 Build Jun.23.2008 ***** 1st thread running time= 1 1st thread running time= 2 2nd thread finished time= 3 THEAD1 Disabled 3rd thread finished time= 4 sim_finished **** Test Done. Total 12.00[msec] ****
ところで、このやり方は注意することがあります。disable は、Staticなスコープ中の全スレッドに対して作用してしまいます。
次のベンチでその辺を見てみましょう。
同じタスクを平行プロセスで起動しています。
(このとき引数が意味を持つためには、task は、automatic である必要がありました。さもないと引数は、Static変数で上書きされてしまうのでしたね。)
module top1; initial begin fork fork_processes(4); fork_processes(7); join_none end task automatic fork_processes(int delay); fork : a begin #delay; $display("Thread finished"); end join_any : a //disable a; endtask endmodule
結果です。
***** Veritak SV Engine Version 0.031 Build Jun.23.2008 ***** Thread finished Thread finished sim_finished **** Test Done. Total 11.00[msec] ****
コメントアウトしている disable a; をイネーブルしてみましょう。
すると、
***** Veritak SV Engine Version 0.031 Build Jun.23.2008 ***** Thread finished sim_finished **** Test Done. Total 7.00[msec] ****
disable a; で、aスレッドを disable しています。このとき、同じスコープ名のスレッドが2個起動していることに注意してください。 disable
は、この2個のスレッドに対して作用します。つまり、どちらのスレッドもdisable
されてしまいます。したがって、Thread finished は、2個でません。2個目が出る前に消されてしまいます。
そこで、disable の代わりにdisable fork を使ってみます。
module top1; initial begin fork fork_processes(4); fork_processes(7); join_none end task automatic fork_processes(int delay); fork : a begin #delay; $display("Thread finished"); end join_any : a disable fork; endtask endmodule
disable fork は、現在のスレッドから生まれた子供、孫..に対してのみ作用します。同じスコープであっても自分が起動していないスレッドのことは関知しません。
***** Veritak SV Engine Version 0.031 Build Jun.23.2008 ***** Thread finished Thread finished sim_finished **** Test Done. Total 11.00[msec] ****
16.1.4 wait fork
wait fork は、現在のスレッドが生成した全てのスレッドの完了を待ちます。
下の fork_blocks タスクでは、4つのtask を起動しています。wait fork は、現在のスレッドfork_block
が起動した4つのスレッドの完了を待ちます(ブロックします)。
全てが完了すると下に行きfork_blocks を抜けます。
i
module top1; initial begin fork fork_blocks(1); fork_blocks(4); join $display("All Done. time=%d",$time); end task automatic fork_blocks(int a); fork fork_processes(4); fork_processes(7); join_none fork fork_processes(14); fork_processes(17); join_none $display("Join None a=%d time=%d",a,$time); // if (a>3) begin wait fork; $display("wait fork time=%d",$time); // end $display("Fork_blocks end Time=%d",$time); endtask task automatic fork_processes(int delay); fork : a begin #delay; $display("Thread finished Time=%d",$time); end join_any : a endtask endmodule
***** Veritak SV Engine Version 0.031 Build Jun.23.2008 *****
Join None a= 1 time= 0
Join None a= 4 time= 0
Thread finished Time= 4
Thread finished Time= 4
Thread finished Time= 7
Thread finished Time= 7
Thread finished Time= 14
Thread finished Time= 14
Thread finished Time= 17
wait folk time= 17
Fork_blocks end Time= 17
Thread finished Time= 17
wait folk time= 17
Fork_blocks end Time= 17
All Done. time= 17
sim_finished
**** Test Done. Total 59.00[msec] ****
なお、引数を指定して、特定のスレッドだけ、wait fork をイネーブルさせることもできます。 作用は、同じスコープでも現在のスレッドのみに働きます。
16.1.2 fork 中の automatic 変数
次の例は、難しいです。ここでの問題はmが不定(undetermined,シミュレータ依存)になることです。
initial for( int j = 1; j <= 3; ++j ) fork automatic int k = j; // local copy, k, for each value of j begin #(k); $display( "time=%d k=%d ",$time,k ); end begin automatic int m =j;// the value of m is undetermined $display("time=%d m=%d",$time,m); end join_none
1)for loop内のint
jは、automatic変数で、下位スコープから参照可能です。スコープとは、可視範囲のことで、SVのautomatic変数はC++のそれに準じた扱いになります。ここで、intは、2値integerです。
2)for loop下のfork -join_noneは、ノンブロッキングプロセスですから、イタレーション3回分の3つのプロセスが時刻0で起動します。つまり時刻0時点では3つのダイナミックインスタンスプロセスが存在します。このイタレーションループ中、fork 下のautomatic
内部変数kは、jで初期化されます。kは、起動したインスタンスプロセスNoと言ってもよいでしょう。
3)fork下のステートメントは、独立した各々プロセスとして起動されますが、その実行は、スケジュールされます。スケジュールは(時刻0には違いないのですが、)実装依存です。なので、参照するjがいつの時点のjなのかは、不定になります。しかし、kは、プロセス起動時にイニシャライズされるので、子プロセスからは、自分のプロセスのkを参照することができます。
結果です。
Loading vpi.dll
Load Done.
***** Veritak SV Engine Version 0.021 Build Dec.30.2007 *****
time= 0 m= 4
time= 0 m= 4
time= 0 m= 4
time= 1 k= 1
time= 2 k= 2
time= 3 k= 3
**** Test Done. Total 10.00[msec] ****
16.1.4 Function中のfork/join_none
次のソースのように、fork-join_none中では、
verilog HDLでのfunction制限事項
1)イベントは書けない
2)ディレイは書けない
3)taskは、起動できない
4)ノンブロッキング文は使用できない
という制限が、fork-join_none内ではなくなります。function自体が、時間0で戻る事(時間を消費しない)は、従来と同じです。fork-join_noneは、いわば、ノンブロッキングなプロセスになりますが、そこで参照される変数については、気をつける必要があります。例えば、下のfunction
引き数a1は、automaticであり、
その生存期間は、function returnで返るまでです。functionが、return で抜けた後も、fork-join_noneプロセスは、独立プロセスとして生き続けることに注意してください。fork-join_noneのプロセス生成と、block_itemsの初期化は、functionが生きている間に行われますが。起動は、function
return の後、イベント文(#delay,@(x)等)のブロッキング文に出会ってからです。つまり、fork
-join_none内に、a1を参照するコードがあると、dangling参照になってしまいます。
下のように、block_itemの宣言で、a1をa2に初期化コピーすることで、dangling参照を防止するようにしてください。
結果です。
***** Veritak SV Engine Version 0.27 Build no.27.2009 ***** function a1=12345678 HiHi2 Hi2 time= 0 a2=12345678 a changed 0 a=1 0 Hi4 time= 1 a changed 10 a=0 10 Hi Task 20 Hi task2 30 Hi3 A 30 Hi3 B 40 **** Test Done. Total 7.00[msec] ****
16.3 配列
16.3.1 パックド配列
パックド配列と、アンパックド配列の2種類があります。大きな違いは、内部データの連続性です。パックドタイプは、連続していることが保証されますが、アンパックドの場合は、内部的な配置は、ベンダー依存になり内部データが連続している保証はありません。 このことは、DPIを使いこなす上でも重要です。
reg [7:0] C ;//パック配列の基本形態です。1次元のパック配列
ところで、SystemVerilogでは、reg をlogic と書いてもなんら意味的に違いはありません。
logic [7:0] C ;//パック配列の基本形態です。
パックド配列の次元を増やすには、変数の左側を増やします。
logic [3:0][7:0] C ;//パック配列の基本形態です。
2次元のパック配列になりました。このデータの内部的な並びは、厳密に決まっていて、データCは、
module mem3; logic [3:0][7:0] C,D; initial begin C=32'hdead_beaf; D={C[3],C[2],C[1],C[0]};//こう書いても同じ D={C[3][7:0], C[2][7:0],C[1][7:0],C[0][7:0]}; if (D ===C) $display("Pass.");//assert(D===C); else $display("Fail"); end endmodule
となります。つまりパックド配列というのは、内部的にもベクタそのものです。事実
次が成り立ちます。
module mem4; logic [3:0][7:0] C; logic [31:0] E; integer k,i; initial begin C=32'hdead_beaf; for ( i=0;i< 4;i++) for ( k=0;k<8;k++) E[i*8+k]=C[i][k]; if (C ===E) $display("Pass E=%h",E);//assert(C===E); else $display("Fail %b",E); if (C[ 3:0] !==E) $display(" Fail"); if (C[0 +:4] !==E) $display("Fail"); if (C[3 -:4] !==E) $display("Fail"); end endmodule
となります。Cも内部的な表現は、Eと同じです。次元が異なる配列の比較,C===Eは、トリッキーですが、処理系は、エラーを出さないと思います。このようにビットの連続性が確保されているのが、アンパック配列との大きな違いです。
<VCDダンプ>
VCDダンプにおいては、アンパック配列はVerilogHDLと同様にダンプされませんが、パック配列は、多次元であっても一次元のreg で宣言したかのように(上の例で、Cは、[31:0]
Cと宣言したかのように)ダンプされます。
<用途>
パック配列は、LRM上は、2^16乗 65 536 まではサポートすることになっています。これ以上は、実装依存です。 巨大な配列は、アンパックにした方がよいでしょう。
<パック配列になりえるものは>
シングルビットのデータタイプに限られます。(bit, logic, reg) 。パック構造体、パック配列が再帰的になりえます。
<符号は>
パック配列の符号は、シングルベクタと見たときに宣言の符号が採用されます。スライスは、基本的にunsigned
です。
16.3.1 アンパック配列
アンパック配列宣言は、変数宣言の右側に[ ] があります。下で、配列CとDは、アンパック配列です。ここで、C[4]は、C[0:3]
と同じ意味でC[0:3] と書いても同じです。Cの宣言と似ていますね。左側に書いてある[3:0][7:0]
は、2次元のパック配列です。つまり、Cは、2次元のパック配列を要素を4個持った1次元のアンパックド配列になります。
verilog HDL では、よく、メモリを [7:0] mem [0 :1023] という風に表現しましたが、これは、1次元のパックド配列を1024個持った1次元のアンパック配列と見ることもできます。
配列の順序は、最初にアンパックの方が先に来てその後にパック配列の修飾になります。下の図を見ると分かりやすいでしょう。
C | [31:0] | 備考 | |||
3 | 2 | 1 | 0 | ||
[31:17] | [23:16] | [15:8] | [7:0] | ||
C[0] | C[0][3][7:0] | C[0][2][7:0] | アンパック配列間 たとえば、C[0],C[1]間は内部的には連続していない(実装依存) | ||
C[1] | C[1][3][7:0] | ||||
C[2] | C[2][3][7:0] | C[2][2][7:0] | C[2][1][7:0] | C[2][0][7:0] | パック配列内ではビットは連続しているたとえば、C[2][0][7]の1ビットMSB側は、C[2][1][0]に配置される |
C[3] | C[3][3][7:0] | C[3][0][7:0] |
パック次元まで修飾すれば、上で説明したように内部的にはベクタそのものになりますから、自由に演算ができますが、アンパックの次元では、ビットが連続していないために、演算は極めて限定的なものしか許されません。これを例で見ていきましょう。
下で最初のC[0]に対する代入は、C[0]とすることで、一つのパック配列を限定できるので自由な演算(定数や、変数の演算)が可能です。
しかし、次のアンパック配列に対するアンパック配列の代入 D=C;は、パック配列をまたぐものなので、限定的な演算しか許されません。この場合、DもCも同じlタイプのlogicで、同じ次元の配列ですから、問題ありません。(Verilog HDLでは、この演算は許されませんでした。)
次の C!=Dも同様ですが、。アンパック配列間で可能な演算は、比較演算のみです。C=0;あるいは、C=D+1等は、NGです。
module mem5; logic [3:0][7:0] C[4],D[4];//equals to logic [3:0][7:0] C[0:3],logic [3:0][7:0] D[0:3]; logic [31:0] E; integer k,i,j; initial begin C[0]=32'h4321_beaf; C[1]=32'h0123_ffff; C[2]=32'hfedc_abcd; C[3]=32'h1234_5678; D=C;//同じ次元の同じタイプのコピー if (C !=D) $display("Fail");//同じ次元の同じタイプの比較 else $display("Pass"); $display("C[0]=%h D[0]=%h",C[0],D[0]); $display("C[1]=%h D[1]=%h",C[1],D[1]); $display("C[2]=%h D[2]=%h",C[2],D[2]); $display("C[3]=%h D[3]=%h",C[3],D[3]); $display("\nDisplay Digits."); for ( i=0;i< 4;i++) for (j=0;j<4;j++) for (k=0;k<8; k=k+4) $display("C[%1d][%1d][%1d +:4]=%h",i,j,k,C[i][j][k +:4]); end endmodule
このベンチの結果です。
Loading vpi.dll Load Done. ***** Veritak SV Engine Version 0.023 Build Feb.8.2008 ***** Pass C[0]=4321beaf D[0]=4321beaf C[1]=0123ffff D[1]=0123ffff C[2]=fedcabcd D[2]=fedcabcd C[3]=12345678 D[3]=12345678 Display Digits. C[0][0][0 +:4]=f C[0][0][4 +:4]=a C[0][1][0 +:4]=e C[0][1][4 +:4]=b C[0][2][0 +:4]=1 C[0][2][4 +:4]=2 C[0][3][0 +:4]=3 C[0][3][4 +:4]=4 C[1][0][0 +:4]=f C[1][0][4 +:4]=f C[1][1][0 +:4]=f C[1][1][4 +:4]=f C[1][2][0 +:4]=3 C[1][2][4 +:4]=2 C[1][3][0 +:4]=1 C[1][3][4 +:4]=0 C[2][0][0 +:4]=d C[2][0][4 +:4]=c C[2][1][0 +:4]=b C[2][1][4 +:4]=a C[2][2][0 +:4]=c C[2][2][4 +:4]=d C[2][3][0 +:4]=e C[2][3][4 +:4]=f C[3][0][0 +:4]=8 C[3][0][4 +:4]=7 C[3][1][0 +:4]=6 C[3][1][4 +:4]=5 C[3][2][0 +:4]=4 C[3][2][4 +:4]=3 C[3][3][0 +:4]=2 C[3][3][4 +:4]=1 **** Test Done. Total 31.00[msec] **** Press any key to continue
アンパック配列では、代入も同じタイプの同じ配列次元の同じスライス幅のみ可能です。(通ってしまう処理系もありますが、厳密にはキャスト(後述)が必要です。)
module mem6; logic [3:0][7:0] C[4],D[4]; logic [31:0] E; integer k,i; initial begin C[0]=32'hbeaf; C[1]=32'hffff; C[2]=32'habcd; C[3]=32'h1234; D[1: 2]=C[1:2]; if (C ===D) $display("Fail"); else $display("Pass"); if (D[1:2] !=C[1:2] ) $display("Fail"); if (D[1:2] !==C[1:2]) $display("Fail"); if( C[2:3] ==D[1:2]) $display("Fail"); if( C[2:3] ===D[1:2]) $display("Fail"); //D[1:2] =D; //NG アンパック次元が一致していない //D[1:2] =D[1:3]; //NG スライス幅が一致していない //D[1:2] =0; //NG アンパック次元が一致していない $display("C[0]=%h D[0]=%h",C[0],D[0]); $display("C[1]=%h D[1]=%h",C[1],D[1]); $display("C[2]=%h D[2]=%h",C[2],D[2]); $display("C[3]=%h D[3]=%h",C[3],D[3]); end endmodule
16.3.2 配列クエリ システムタスク
$size,$dimensions,$unpacked_dimensions,$left,$right,$low,$high,$increment
があります。戻り値は、.integer です。defaultの次元指定は、1です。次元指定をしなかったときは、1と解釈されます。次元指定の仕方は、1が最も変化の遅い次元が1で、以降、次元が上がります。,アンパック項(変数の右側)が最も変化が遅く、パック項(変数の左)が変化が速くなるので、次のような指定になります。
//次元指定 3 4 1 2
reg [1:13][3:1] u [4:2][3]; // パック次元 アンパック次元
1) $size
指定した次元の要素の合計数を返します。 上の例で、$size(u) は、4を返します。これは、$size(u,1)と同義です。
2) $right
指定した次元の右側の要素を返します。 上の例で、$right(u,1) は、2を返します。
3)$left
指定した次元の左側の要素を返します。上の例で、$left(u,1) は、4を返します。
4)$low
$left >$right なら $right の値が返ります。
5)$high
$left >$rightなら $leftの値が返ります。
6)$dimensions
次元数が返ります。次元指定引数はありません。
7)$unpacked_dimensions
アンパック次元数が返ります。次元指定引数はありません。
8)$increment
$left >=$right なら+1 そうでないなら-1が返ります。
module main; typedef int unsigned byte10 [10]; sub #(.T(byte10)) dut2(); endmodule module sub #(type T=int ) () ; T F; reg [15:0] s; reg [1:13][3:1] h; reg [1:13][3:1] u [4:2][3]; initial begin $display("$size(s)=%2d",$size(s)); $display("$unpacked_dimensions(s)=%2d",$unpacked_dimensions(s)); $display("$dimensions(s)=%2d",$dimensions(s)); $display("$left(s)=%2d",$left(s)); $display("$right(s)=%2d",$right(s)); $display("$low(s)=%2d",$low(s)); $display("$high(s)=%2d",$high(s)); $display("$increment(s)=%2d",$increment(s)); $display("$bits(s)=%2d",$bits(s)); $display("\n$size(F)=%2d",$size(F));//int は、パック1次元とカウントされる $display("$unpacked_dimensions(F)=%2d",$unpacked_dimensions(F)); $display("$dimensions(F)=%2d",$dimensions(F)); $display("$left(F)=%2d",$left(F)); $display("$right(F)=%2d",$right(F)); $display("$low(F)=%2d",$low(F));//アンパック次元の[10]は、[0:9] と解釈される $display("$high(F)=%2d",$high(F)); $display("$increment(F)=%2d",$increment(F)); $display("$bits(F)=%2d",$bits(F)); $display("\n$left(F,2)=%2d",$left(F,2)); $display("$right(F,2)=%2d",$right(F,2)); $display("$low(F,2)=%2d",$low(F,2)); $display("$high(F,2=%2d",$high(F,2)); $display(",$increment(F,2)=%2d",$increment(F,2)); $display("\n$size(h)= %2d",$size(h)); $display("$unpacked_dimensions(h)=%2d",$unpacked_dimensions(h)); $display("$dimensions(h)=%2d",$dimensions(h)); $display("$left(h)=%2d",$left(h)); $display("$right(h)=%2d",$right(h)); $display("$low(h)=%2d",$low(h)); $display("$high(h)=%2d",$high(h)); $display("$increment(h)=%2d",$increment(h)); $display("$bits(h)=%2d",$bits(h)); $display("\n$size(h,2)=%2d",$size(h,2)); $display("$left(h,2)=%2d",$left(h,2)); $display("$right(h,2)=%2d",$right(h,2)); $display("$low(h,2)=%2d",$low(h,2)); $display("$high(h,2)=%2d",$high(h,2)); $display("$increment(h,2)=%2d",$increment(h,2)); $display("\n$size(u)= %2d",$size(u)); $display("$unpacked_dimensions(h,1)=%2d",$unpacked_dimensions(u)); $display("$dimensions(u)=%2d",$dimensions(u)); $display("$left(u)=%2d",$left(u)); $display("$right(u)=%2d",$right(u)); $display("$low(u)=%2d",$low(u)); $display("$high(u)=%2d",$high(u)); $display("$increment(u)=%2d",$increment(u)); $display("$bits(u)=%2d",$bits(u)); $display("\n$size(u,2)=%2d",$size(u,2)); $display("$left(u,2)=%2d",$left(u,2)); $display("$right(u,2)=%2d",$right(u,2)); $display("$low(u,2)=%2d",$low(u,2)); $display("$high(1,2)=%2d",$high(u,2)); $display("$increment(u,2)=%2d",$increment(u,2)); $display("\n$size(u,3)=%2d",$size(u,3)); $display("$left(u,3)=%2d",$left(u,3)); $display("$right(u,3)=%2d",$right(u,3)); $display("$low(u,3)=%2d",$low(u,3)); $display("$high(u,3)=%2d",$high(u,3)); $display("$increment(u,3)=%2d",$increment(u,3)); $display("\n$size(u,4)=%2d",$size(u,4)); $display("$left(u,4)=%2d",$left(u,4)); $display("$right(u,4)=%2d",$right(u,4)); $display("$low(u,4)=%2d",$low(u,4)); $display("$high(u,4)=%2d",$high(u,4)); $display("$increment(u,4)=%2d",$increment(u,4)); F=f(); for (int i=0;i<$size(F);i++) begin $display("F[%2d]=%b",i, F[i]); end end function automatic T f(); for( int i=0; i<$size(f); i++ ) begin f[i]=$random; $display("result[%2d]=%x",i, f[i]); end endfunction :f endmodule
***** Veritak SV Engine Version 0.13 Build Mar.20.2009 ***** $size(s)=16 $unpacked_dimensions(s)= 0 $dimensions(s)= 1 $left(s)=15 $right(s)= 0 $low(s)= 0 $high(s)=15 $increment(s)= 1 $bits(s)=16 $size(F)=10 $unpacked_dimensions(F)= 1 $dimensions(F)= 2 $left(F)= 0 $right(F)= 9 $low(F)= 0 $high(F)= 9 $increment(F)=-1 $bits(F)=320 $left(F,2)=31 $right(F,2)= 0 $low(F,2)= 0 $high(F,2=31 ,$increment(F,2)= 1 $size(h)= 13 $unpacked_dimensions(h)= 0 $dimensions(h)= 2 $left(h)= 1 $right(h)=13 $low(h)= 1 $high(h)=13 $increment(h)=-1 $bits(h)=39 $size(h,2)= 3 $left(h,2)= 3 $right(h,2)= 1 $low(h,2)= 1 $high(h,2)= 3 $increment(h,2)= 1 $size(u)= 3 $unpacked_dimensions(h,1)= 2 $dimensions(u)= 4 $left(u)= 4 $right(u)= 2 $low(u)= 2 $high(u)= 4 $increment(u)= 1 $bits(u)=351 $size(u,2)= 3 $left(u,2)= 0 $right(u,2)= 2 $low(u,2)= 0 $high(1,2)= 2 $increment(u,2)=-1 $size(u,3)=13 $left(u,3)= 1 $right(u,3)=13 $low(u,3)= 1 $high(u,3)=13 $increment(u,3)=-1 $size(u,4)= 3 $left(u,4)= 3 $right(u,4)= 1 $low(u,4)= 1 $high(u,4)= 3 $increment(u,4)= 1 result[ 0]=12153524 result[ 1]=c0895e81 result[ 2]=8484d609 result[ 3]=b1f05663 result[ 4]=06b97b0d result[ 5]=46df998d result[ 6]=b2c28465 result[ 7]=89375212 result[ 8]=00f3e301 result[ 9]=06d7cd0d F[ 0]=00010010000101010011010100100100 F[ 1]=11000000100010010101111010000001 F[ 2]=10000100100001001101011000001001 F[ 3]=10110001111100000101011001100011 F[ 4]=00000110101110010111101100001101 F[ 5]=01000110110111111001100110001101 F[ 6]=10110010110000101000010001100101 F[ 7]=10001001001101110101001000010010 F[ 8]=00000000111100111110001100000001 F[ 9]=00000110110101111100110100001101 **** Test Done. Total 0.00[msec] ****
Note:
・固定幅の配列(integer, shortint, longint, byte)は、次元1が定義済みです。
・$size等は、コンスタントファンクションとしてエラボレーション時に使用することが出来ます。
16.4. function /task のref port
16.4.1 function/task のref port は参照渡し
ref あり/なしでの二つのfunctionの違いを見てみましょう
module ref_link29; logic [31:0][31:0] a; initial begin a[0]=32'hdead_beaf; //call w/o ref func func_wo_ref(a); $display("a[0]= %x",a[0]); //call func w/ ref func_w_ref(a); $display("a[0]= %x",a[0]); end function automatic void func_w_ref(ref logic [31:0][31:0] a); a[0]=32'h10000; endfunction function automatic void func_wo_ref( logic [31:0][31:0] a); a[0]=32'h10000; endfunction endmodule
結果です。
***** Veritak SV Engine Version 0.030 Build May.22.2008 *****
a[0]=deadbeaf
a[0]=00010000
**** Test Done. Total 16.00[msec] ****
ref が付くと pass by reference
(参照渡し)
ref が付かないと pass by value (値渡し)
になります。値渡しの方は、実体がCOPYで渡されます。一方,ref
の方は、(VeritakSVの内部実装では、)実体のポインタを渡しています。function内部でa[0]に書き込みを行っていますが、ref
の方は、呼び出し側の実体に対してWriteしていることになります。一方、ref
なしの方は、(通常のVerilogも同様ですが、)ローカル変数に対してWriteすることになるので、呼び出し側の実体には影響しません。
TIPS:
16.4.2 参照渡しの速度
次のベンチは、巨大な配列をfunctionに渡しています。このとき、ref ポートで渡しているので、実体のコピーは起こらず、ポインタだけのコピーで済みます。実際、このベンチを実行してみると1msもかかりません。
module ref_link_30; logic [31:0][31:0] a [0:100][0:100]; integer i,j; initial begin a[0][0][0]=32'hdead_beaf; repeat(1000) begin j=func(a); end i=a[0][0][0]; if (a[0][0][0] !==10000) $display("Fail"); $display("i=%d j=%d",i,j); end function automatic [31:0] func( ref logic [31:0][31:0] a [0:100][0:100]); a[0][0][0]=10000; func=a[0][0][0]; endfunction endmodule
refをinout にしただけです。しかし、inoutの方は実行してみると数秒かかってしまいます。巨大な実体(101x101x32x32ビットのメモリ)のコピーに時間がかかってしまうためです。 inoutは、値渡しのCOPYです。functionが呼ばれるときにCOPYされ、functionから戻るときにも実体分のCOPYが起こります。つまり、functionの呼び出し前後で計2回分の実体コピーが起こります。これに対してref
portでは、ポインタだけのコピーで済みますから巨大な配列では圧倒的に速いという訳です。
module ref_link_30; logic [31:0][31:0] a [0:100][0:100]; integer i,j; initial begin a[0][0][0]=32'hdead_beaf; repeat(1000) begin j=func(a); end i=a[0][0][0]; if (a[0][0][0] !==10000) $display("Fail"); $display("i=%d j=%d=%d",i,j); end function automatic [31:0] func( inout logic [31:0][31:0] a [0:100][0:100]); a[0][0][0]=10000; func=a[0][0][0]; endfunction endmodule
TIPS:
の制限があります。
16.5 automatic とstatic
16.5.1 static変数は、スレッドローカルではない
SVでは、まず押さえておきたい重要な概念です。Verilog1995では、automatic変数はなくstaticの概念しかありません。一般のプログラミング言語の常識からはかけ離れた仕様です。Hardwareは、生まれたり死んだりしないので、いままでVerilogHDLで書いたきた人も意識することはなく、この事は気づかなかったかもしれません。しかしテストベンチ、特にSV風にテストベンチを書こうとすると、やはり普通のプログラミング言語のように書きたい場合が多いのでautomaticが頻繁に登場することになります。
まずはベンチから見ていきます。
module automatic_test; initial begin fork task_call(1); task_call(2); join_none end task task_call(integer i); #1; $display("i=%d",i); endtask endmodule
結果です。
***** Veritak SV Engine Version 0.030 Build May.22.2008 ***** i= 2 i= 2 **** Test Done. Total 0.00[msec] ****
この結果は、ModelSimと同じになりましたが、i=1,i=1になる処理系もあるかもしれません。プロセスは、二つ同時に起動されプロセスインスタンスとしては、確かに二つ存在するのですが、i自体は、staticであるが為にローカル変数ではなく、二つのtaskで共通なオブジェクトi
になります。そのために上書きされてしまいます。つまりここでの結果は、最後に書かれた方を表示していることになります。
意図した通りに動かすには、task にautomatic を付加します。
module automatic_test2; initial begin fork task_call(1); task_call(2); join_none end task automatic task_call(integer i); #1; $display("i=%d",i); endtask endmodule
***** Veritak SV Engine Version 0.030 Build May.22.2008 ***** i= 1 i= 2 **** Test Done. Total 0.00[msec] ****
16.5.2automaticは生成時に初期化される
16.5.2.1 automaticで再帰が書ける
自分自身を呼び出す関数を再帰関数と言います。次の関数は、足し算をする関数です。
module automatic_test1; initial begin for (int i=1;i<=4;i=i+1) begin $display("sum[%2d]=%2d",i,func(i)); end end function automatic int func( int n); if (n <=1) return 1; return n+func(n-1););//自分を呼び出す endfunction endmodule
殆どCと同じように書けますね。実行結果です。
***** Veritak SV Engine Version 0.030 Build May.22.2008 *****
sum[ 1]= 1 sum[ 2]= 3
sum[ 3]= 6
sum[ 4]=10
**** Test Done. Total 12.00[msec] ****
さて、ここで問題です。この関数が何回呼ばれたかを数えたいとします。そこで、次のように記述を追加してみました。
function automatic int func( int n); int counter;//追加 ++counter;//追加 $display("n=%2d counter=%2d",n,counter);//追加 if (n <=1) return 1; return n+func(n-1); endfunction
結果です。
***** Veritak SV Engine Version 0.030 Build May.22.2008 *****
n= 1 counter= 1
sum[ 1]= 1
n= 2 counter= 1
n= 1 counter= 1
sum[ 2]= 3
n= 3 counter= 1
n= 2 counter= 1
n= 1 counter= 1
sum[ 3]= 6
n= 4 counter= 1
n= 3 counter= 1
n= 2 counter= 1
n= 1 counter= 1
sum[ 4]=10
**** Test Done. Total 28.00[msec] ****
カウンタが1から進みませんね。一体これは、どういうことでしょうか?
functionの属性がautomatic なので、counterの属性もautomaticになります。
automatic変数は、default初期値で生成の度、SIM実行中に初期化される規則になっています。この場合functionが呼ばれる度にcounterは生まれ2値 intの初期値0で初期化されます。(これがintegerなら、4値32'hxで初期化されます。) 従って、++counterで常に1になってしまう、という訳です。
そこで couterだけは、staticだよ、とコンパイラに教えてやります。
function automatic int func( int n); static int counter;//追加 ++counter;//追加 $display("n=%2d counter=%2d",n,counter);//追加 if (n <=1) return 1; return n+func(n-1); endfunction
すると、今度は、
***** Veritak SV Engine Version 0.030 Build May.22.2008 *****
n= 1 counter= 1
sum[ 1]= 1
n= 2 counter= 2
n= 1 counter= 3
sum[ 2]= 3
n= 3 counter= 4
n= 2 counter= 5
n= 1 counter= 6
sum[ 3]= 6
n= 4 counter= 7
n= 3 counter= 8
n= 2 counter= 9
n= 1 counter=10
sum[ 4]=10
**** Test Done. Total 25.00[msec] ****
期待通りになりました。static変数は、シミュレーションが始まる前に初期化され、シミュレーションが終わるまで死ぬことはありません。なので、値はづっと保持、常に参照が可能です。
16.5.3 static変数は常に参照が可能/ automaticはそうでない スコープとは、
で、次の例では、automaticファンクション中の static 変数 counter を階層参照しています。 再帰などによりfunctionは、複数のローカル変数が存在することに注意してください。automatic変数は、functionが呼び出されたとき各々のfunction スタック中に存在し、function からのreturn でスタックが開放されたときに自動的に消滅します。ですから、func.n としても、どのfunctionのローカル変数n かは区別できないですね。一方static 変数は、再帰されたとしても、共通のオブジェクトですので、特定できます。また、static変数は、SIM開始時には、初期値は、確定しており以降、消滅する心配がありません。ですので、常に参照が可能です。
ところで、for ループ中のint i もautomatic 変数です。この場合のautomatic
変数の参照は、常に下位スコープから可能です。この辺のルールはC言語と同様です。関数がスタックで実装されていることを考えれば、上位スコープは、存在していることが保証されます。しかし、上位スコープから見た下位スコープは、呼び出し前なら、存在しませんし、return後なら、やはり存在しませんね。そういう訳で、この辺の振る舞いは、言語に依存しないと思います。
module automatic_test1; initial begin for (int i=1;i<=4;i=i+1) begin $display("sum[%2d]=%2d",i,func(i));//automatic i は、↑なので参照可能 $display("func.count=%d",func.counter); //static変数は、常に参照可能 end // for ループの外、ここからでは、iは、見えない(参照不可)。ここからiを呼び出してもコンパイルエラーとなる。 end function automatic int func( int n); static int counter; ++counter; $display("n=%2d counter=%2d",n,counter); if (n <=1) return 1; return n+func(n-1); endfunction endmodule
16.6 ループ構文の制御 16.6.1 break/continue SVでは、continue, break,及び return が追加されています。機能は、Cとほぼ同じといっていいでしょう。
verilog HDLでは、continue, break構文は存在せず、スレッドの消滅指示であるdisableを使うしかありませんでした。下は、Verilog HDLによるcontinue/ break を模したコードです。
module for_loop; int sum=0; initial begin begin :BREAK for (int i=0;i< 10000000;i++) begin:CONTINUE sum=sum+1; if (i<10000000) disable CONTINUE; else if (i <0) disable BREAK; end $display("sum=%d",sum); end end endmodule
これを、SV風に書き換えてみます。
module for_loop; int sum=0; initial begin for (int i=0;i< 10000000;i++) begin sum=sum+1; if (i<10000000) continue;//disable CONTINUE; else if (i <0) break;//disable BREAK; end $display("sum=%d",sum); end endmodule
disable とbreak/continue では速度の桁が違います。上記ベンチ比較では数十倍違う結果になります。disable は重いスレッド処理が必要ですが、break/continueは、 単にジャンプ文だけで実装できることによります。 16.6.2 for 文 for 文が次のように拡張されています。複数のイニシャライズ文、およびステップ文を置くことができます。
module for_loop; int sum=0; initial begin for (int i=0,j=0;i< 10;i++,j++) begin $display("i=%dj=%d",i,j); sum++; end $display("sum=%d",sum); end endmodule
for ループ内のローカル変数は、異なるスコープで置くことができます。
下では、4回、ローカル変数i を宣言していますが、実体は、全く別で、影響は相互にありません。参照は、最も近い上方のスコープのiが参照されます。
しかし、階層間で同じi を使うのは、あまり気持ちのよいものではないですね。最近の言語C#でも禁止されているようですし、避けたほうがよいでしょう。
module for_loop; initial begin fork for (int i = 0; i< 2; i++) begin for ( int i = 0;i<5;i++) begin $display("inner loop i = %d",i ); end $display("outer loop i = %d",i ); end for (int i = 0; i< 2; i++) begin for ( int i = 0;i<5;i++) begin $display("inner loop i = %d",i ); end $display("outer loop i = %d",i ); end join_none end endmodule
16.6.3 do while 文
SVでは、do while も使えます。
文法は、
do statement_or_null while ( expression ) ;
です。
ベンチ例です。このように、C風に書けます。
module do_while_test; int i; initial begin i=7; do begin $display("i=%d",i); if (i >30) break; else if (i >4) continue; $display("i=%d",i); end while (--i); $display("End"); end endmodule
16.6.4 generate構文
2001では、仕様が曖昧であったためにいくつか混乱がありました。2005では、エラボレーションの順序に関しても明確に仕様化されました。
また、構文も拡張されており、generateで再帰インスタンス化が可能になりました。
それでは、2001との違いを見ていきましょう。
16.6.4.1 generate-endgenerateは、オプション
generate-endgenerateで囲む必要はありません。
また、for ループでは、genvarのインライン宣言が使えます。(ただし、generate
for 文では、複数のイニシャライズ構文や、ステップ構文は使えません。)
module top; parameter SIZE = 2; //generate for (genvar i=0; i<SIZE; i++) begin:B1 M1 N1(); for (genvar j=0; j<SIZE; j++) begin:B2 M2 N2(); for (genvar k=0; k<SIZE; k++) begin:B3 M3 N3(); end end if (i>0) begin:B4 for (genvar m=0; m<SIZE; m++) begin:B5 M4 N4(); end end end //endgenerate endmodule module M1; initial $display("%m"); endmodule module M2; initial $display("%m"); endmodule module M3; initial $display("%m"); endmodule module M4; initial $display("%m"); endmodule
結果です。$display("%m");で階層名を出力しています。generateで複雑なインスタンス化を行う場合、コンパイラは、generate中の解釈の様子をコンパイル中、普通は、出力しないと思うので、こんな感じで、スコープ名を出力してデバッグすることになります。
***** Veritak SV Engine Version 0.033 Build Se.8.2008 ***** top.B1[1].B4.B5[1].N4 top.B1[1].B4.B5[0].N4 top.B1[1].B2[1].B3[1].N3 top.B1[1].B2[1].B3[0].N3 top.B1[1].B2[1].N2 top.B1[1].B2[0].B3[1].N3 top.B1[1].B2[0].B3[0].N3 top.B1[1].B2[0].N2 top.B1[1].N1 top.B1[0].B2[1].B3[1].N3 top.B1[0].B2[1].B3[0].N3 top.B1[0].B2[1].N2 top.B1[0].B2[0].B3[1].N3 top.B1[0].B2[0].B3[0].N3 top.B1[0].B2[0].N2 top.B1[0].N1 **** Test Done. Total 32.00[msec] ****
16.6.4.2 implicit ラベル名は、LRMが規定している
上のベンチのラベル名を除いてみましょう。
module top; parameter SIZE = 2; genvar i,j,k,m; for (i=0; i<SIZE; i++) begin M1 N1(); for (j=0; j<SIZE; j++) begin M2 N2(); for (k=0; k<SIZE; k++) begin M3 N3(); end end if (i>0) for (m=0; m<SIZE; m++) M4 N4(); end endmodule module M1; initial $display("%m"); endmodule module M2; initial $display("%m"); endmodule module M3; initial $display("%m"); endmodule module M4; initial $display("%m"); endmodule
結果です。
2001では、暗黙ラベルは、Vendor間ごとに勝手な名前をつけられて互換性がありませんでしたが、genblkN (Nは、1,2,3..)という風な名前の命名規則が定義されています。従い、暗黙ラベル名は、Vendorによらず同じになります。また、複文でなければ、特にbegin-endで囲む必要もなくなりました。
***** Veritak SV Engine Version 0.033 Build Se.8.2008 ***** top.genblk1[1].genblk2.genblk1[1].N4 top.genblk1[1].genblk2.genblk1[0].N4 top.genblk1[1].genblk1[1].genblk1[1].N3 top.genblk1[1].genblk1[1].genblk1[0].N3 top.genblk1[1].genblk1[1].N2 top.genblk1[1].genblk1[0].genblk1[1].N3 top.genblk1[1].genblk1[0].genblk1[0].N3 top.genblk1[1].genblk1[0].N2 top.genblk1[1].N1 top.genblk1[0].genblk1[1].genblk1[1].N3 top.genblk1[0].genblk1[1].genblk1[0].N3 top.genblk1[0].genblk1[1].N2 top.genblk1[0].genblk1[0].genblk1[1].N3 top.genblk1[0].genblk1[0].genblk1[0].N3 top.genblk1[0].genblk1[0].N2 top.genblk1[0].N1 **** Test Done. Total 43.00[msec] ****
16.6.4.3 再帰インスタンス化が可能
モジュールから、自分を呼び出してインスタンス化することができます。勿論、終了判定で、再帰は帰結することが必要ですし、トップモジュールを再帰インスタンス化することはできません。
次の例は、再帰インスタンス化を行った例です。ソートの並列化アルゴリズムでバイトニックソートを実装しています。
モジュールbiton_sort が自分自身を呼び出しています。このモジュールは論理合成可能です。
module TestBench; parameter DATA_WIDTH = 8; parameter DATA_ARRAY_SIZE_EXP = 3; // array is 2^DATA_ARRAY_SIZE_POW elements // data organized in following way: // single long vector: { data3[3:0], data2[3:0], data1[3:0], data0[3:0]} parameter DATA_ARRAY_MSB = (1<<DATA_ARRAY_SIZE_EXP) * DATA_WIDTH -1; logic [DATA_ARRAY_MSB:0] a=0; wire [DATA_ARRAY_MSB:0] data_in =a;// {4'h1, 4'h3, 4'h5, 4'h2, 4'hf, 4'hb, 4'h0,4'ha }; wire [DATA_ARRAY_MSB:0] data_out; biton_sort #( .DATA_WIDTH(DATA_WIDTH), .DATA_ARRAY_SIZE_EXP(DATA_ARRAY_SIZE_EXP) ) biton_sort_1 (.data_in(data_in), .data_out(data_out) ); initial begin repeat (3) begin a={$random,$random}; #10; $display("data_in =%h",data_in); $display("data_out=%h",data_out); disp; end end task disp ; $write("data_in :"); for (int i=0;i< 8;i++) $write("%2h ", data_in[ i*DATA_WIDTH +:DATA_WIDTH]); $display(""); $write("data_out:"); for (int i=0;i< 8;i++) $write("%2h ", data_out[ i*DATA_WIDTH +:DATA_WIDTH]); $display("\n"); endtask endmodule module biton_sort ( data_in,data_out); // Global parameters parameter DATA_WIDTH = 4; parameter DATA_ARRAY_SIZE_EXP = 2; parameter DATA_ARRAY_SIZE = (1<<DATA_ARRAY_SIZE_EXP); parameter DATA_ARRAY_MSB = (DATA_ARRAY_SIZE * DATA_WIDTH) -1; parameter DATA_ARRAY_SIZE_EXP_M1 = DATA_ARRAY_SIZE_EXP-1; parameter DATA_UPPER_HALF_OFFSET = DATA_WIDTH * (DATA_ARRAY_SIZE>>1); input [DATA_ARRAY_MSB:0] data_in; output [DATA_ARRAY_MSB:0] data_out; wire [DATA_ARRAY_MSB:0] data_conq; if(DATA_ARRAY_SIZE_EXP>1) begin: nest wire [DATA_UPPER_HALF_OFFSET-1:0] data_sort_1; wire [DATA_UPPER_HALF_OFFSET-1:0] data_sort_0; wire [DATA_UPPER_HALF_OFFSET-1:0] data_rev_0; wire [DATA_UPPER_HALF_OFFSET-1:0] data_rev_1; biton_sort #( .DATA_WIDTH(DATA_WIDTH), .DATA_ARRAY_SIZE_EXP(DATA_ARRAY_SIZE_EXP_M1) ) biton_sort_0 (.data_in(data_in[ DATA_UPPER_HALF_OFFSET-1:0]), .data_out(data_sort_0) ); biton_reverse #(.DATA_WIDTH(DATA_WIDTH), .DATA_ARRAY_SIZE_EXP(DATA_ARRAY_SIZE_EXP_M1) ) biton_reverse_1_0 ( .data_in(data_in[ DATA_ARRAY_MSB:DATA_UPPER_HALF_OFFSET]), .data_out(data_rev_0) ); biton_sort #(.DATA_WIDTH(DATA_WIDTH), .DATA_ARRAY_SIZE_EXP(DATA_ARRAY_SIZE_EXP_M1) ) biton_sort_1 (.data_in(data_rev_0),.data_out(data_rev_1)); biton_reverse #( .DATA_WIDTH(DATA_WIDTH), .DATA_ARRAY_SIZE_EXP(DATA_ARRAY_SIZE_EXP_M1) ) biton_reverse_1_1 ( .data_in(data_rev_1), .data_out(data_sort_1) ); assign data_conq = {data_sort_1, data_sort_0}; end else begin: last assign data_conq = data_in; end biton_merge #( .DATA_WIDTH(DATA_WIDTH), .DATA_ARRAY_SIZE_EXP(DATA_ARRAY_SIZE_EXP) ) biton_merge_1 (.data_in(data_conq),.data_out(data_out)); endmodule module biton_bn ( data_in,data_out); // Global parameters parameter DATA_WIDTH = 4; parameter DATA_ARRAY_SIZE_EXP = 2; // array is 2^DATA_ARRAY_SIZE_POW elements this is equal to N for B(n) bitonic sort operator localparam DATA_ARRAY_SIZE = (1<<DATA_ARRAY_SIZE_EXP); // array is 2^DATA_ARRAY_SIZE_POW elements localparam DATA_ARRAY_MSB = (1<<DATA_ARRAY_SIZE_EXP) * DATA_WIDTH -1; parameter DATA_UPPER_HALF_OFFSET = DATA_WIDTH * (DATA_ARRAY_SIZE>>1); input [DATA_ARRAY_MSB:0] data_in; output reg [DATA_ARRAY_MSB:0] data_out; for(genvar i=0;i<(DATA_ARRAY_SIZE>>1);i=i+1) begin: comp always_comb begin if( data_in[ (i+1)*DATA_WIDTH-1 : i*DATA_WIDTH] > data_in[ (i+1)*DATA_WIDTH-1+DATA_UPPER_HALF_OFFSET : i*DATA_WIDTH+DATA_UPPER_HALF_OFFSET] ) begin // swap data_out[ (i+1)*DATA_WIDTH-1 : i*DATA_WIDTH] = data_in [ (i+1)*DATA_WIDTH-1+DATA_UPPER_HALF_OFFSET : i*DATA_WIDTH+DATA_UPPER_HALF_OFFSET]; data_out[ (i+1)*DATA_WIDTH-1+DATA_UPPER_HALF_OFFSET : i*DATA_WIDTH+DATA_UPPER_HALF_OFFSET] = data_in [ (i+1)*DATA_WIDTH-1 : i*DATA_WIDTH]; end else begin // keep the same data_out[ (i+1)*DATA_WIDTH-1 : i*DATA_WIDTH] = data_in [ (i+1)*DATA_WIDTH-1 : i*DATA_WIDTH]; data_out[ (i+1)*DATA_WIDTH-1+DATA_UPPER_HALF_OFFSET : i*DATA_WIDTH+DATA_UPPER_HALF_OFFSET] = data_in [(i+1)*DATA_WIDTH-1+DATA_UPPER_HALF_OFFSET : i*DATA_WIDTH+DATA_UPPER_HALF_OFFSET]; end end end endmodule module biton_merge ( data_in,data_out); // Global parameters parameter DATA_WIDTH = 4; parameter DATA_ARRAY_SIZE_EXP = 2; // array is 2^DATA_ARRAY_SIZE_POW elements this is equal to N for B(n) bitonic sort operator // Local parameters parameter DATA_ARRAY_SIZE = (1<<DATA_ARRAY_SIZE_EXP); // array is 2^DATA_ARRAY_SIZE_POW elements parameter DATA_ARRAY_MSB = (DATA_ARRAY_SIZE * DATA_WIDTH) -1; parameter DATA_ARRAY_SIZE_EXP_M1 = DATA_ARRAY_SIZE_EXP-1; parameter DATA_UPPER_HALF_OFFSET = DATA_WIDTH * (DATA_ARRAY_SIZE>>1); input [DATA_ARRAY_MSB:0] data_in; output [DATA_ARRAY_MSB:0] data_out; wire [DATA_ARRAY_MSB:0] data_div; biton_bn #( .DATA_WIDTH(DATA_WIDTH), .DATA_ARRAY_SIZE_EXP(DATA_ARRAY_SIZE_EXP) ) biton_bn_1 ( .data_in(data_in), .data_out(data_div) ); generate if(DATA_ARRAY_SIZE_EXP>1) begin: nest wire [DATA_UPPER_HALF_OFFSET-1:0] data_merge_1; wire [DATA_UPPER_HALF_OFFSET-1:0] data_merge_0; biton_merge #( .DATA_WIDTH(DATA_WIDTH), .DATA_ARRAY_SIZE_EXP(DATA_ARRAY_SIZE_EXP_M1) ) biton_merge_0( .data_in(data_div[ DATA_UPPER_HALF_OFFSET-1:0]), .data_out(data_merge_0) ); biton_merge #( .DATA_WIDTH(DATA_WIDTH), .DATA_ARRAY_SIZE_EXP(DATA_ARRAY_SIZE_EXP_M1) ) biton_merge_1 (.data_in(data_div[ DATA_ARRAY_MSB:DATA_UPPER_HALF_OFFSET]), .data_out(data_merge_1) ); assign data_out = {data_merge_1, data_merge_0}; end else begin: last assign data_out = data_div; end endgenerate endmodule module biton_reverse (data_in,data_out); // Global parameters parameter DATA_WIDTH = 4; parameter DATA_ARRAY_SIZE_EXP = 2; // array is 2^DATA_ARRAY_SIZE_POW elements this is equal to N for B(n) bitonic sort operator // Local parameters localparam DATA_ARRAY_SIZE = (1<<DATA_ARRAY_SIZE_EXP); // array is 2^DATA_ARRAY_SIZE_POW elements localparam DATA_ARRAY_MSB = (DATA_ARRAY_SIZE * DATA_WIDTH) -1; input [DATA_ARRAY_MSB:0] data_in; output [DATA_ARRAY_MSB:0] data_out; for(genvar i=0;i<DATA_ARRAY_SIZE;i=i+1) begin: rev assign data_out[ (i+1)*DATA_WIDTH-1 : i*DATA_WIDTH] = data_in[ (DATA_ARRAY_SIZE-i)*DATA_WIDTH-1 : (DATA_ARRAY_SIZE-i-1)*DATA_WIDTH]; end endmodule
結果です。
***** Veritak SV Engine Version 0.033 Build Se.8.2008 ***** data_in =c0895e8112153524 data_out=c089815e35241512 data_in :24 35 15 12 81 5e 89 c0 data_out:12 15 24 35 5e 81 89 c0 data_in =b1f056638484d609 data_out=f0d6b18484635609 data_in :09 d6 84 84 63 56 f0 b1 data_out:09 56 63 84 84 b1 d6 f0 data_in =46df998d06b97b0d data_out=dfb9998d7b460d06 data_in :0d 7b b9 06 8d 99 df 46 data_out:06 0d 46 7b 8d 99 b9 df **** Test Done. Total 63.00[msec] ****
16.6.4.4 エラボレーションの順序
エラボレーションとは、いろいろな意味があると思いますが、おおよそ、構文解析後で、コードジェネレートの前の工程、位の意味で捉えておけばよいと思います。このステージでは、
を行っています。
generateを使う場合の問題は、defparamの相互使用です。2001ではこの部分が曖昧でしたが、明確に規定されました。
例です。かなり複雑な動きをします.
なお、generate下のdefparamは、generate 階層外のparameterを置き換えることはできません。
module m; m1 n(); endmodule module m1; parameter p = 2; defparam m.n.p = 1; initial $display("p=%d %m" ,p); generate if (p == 1) begin : m defparam m.n.m.n.p = 100; m2 n(); end endgenerate endmodule module m2; parameter p = 3; initial $display("p=%d %m",p); endmodule
結果です。
***** Veritak SV Engine Version 0.033 Build Se.8.2008 ***** p= 1 m.n p= 100 m.n.m.n **** Test Done. Total 6.00[msec] ****
16.7 イベント
16.7.1 Verilog HDLでの使い方
Verilog HDLでは、次のようにイベント変数を宣言、->で明示的にトリガをかけることができました。
module event_test; event a;//イベント変数の宣言
initial begin
@a;//トリガ待ち
end
initial begin ->a;//トリガする
end
endmodule
ここでの問題はraceがあるということです。raceの問題は、SVになっても常に付きまといます。スレッド挙動の正しい理解が必要となりますし、複雑な仕様と挙動から使いこなすのは難しいと思います。
それを解説する前に、少し復習です。
イベントドリブンの基本は、「変化」です。Verilog HDLでは、@で変化を捕まえました。ところで、SystemVerilog
for Verificationを読んでると頻繁にblock/unblock するという文に出くわします。この「ブロックする/ブロックを解除する」とは、どういうことでしょうか?
16.7.2 @は、立ち止まって変化を待つ、と読む
@(expression);// A
という構文は、上のように読むとよいと思います。A行に来て、初めて変化を待ちます。一旦止まるので、ブロックするという訳です。unblockは、変化を受けてA行の次の行に移ることを表しています。この構文は、エッジトリガであることに注意してください。
一方、
wait (expression) statements;
という構文もあります。
これは、基本的には、レベルセンシティブですが、次のように書き換えることもできます。
while (1 !== expression)
begin
@(expression);
end
statement;//Q.E.D.
つまり最初の構文がTrueだと@をスルーして次の構文に行ってしまう訳です。これを指して「ブロックされないで次の構文に行く」という言い方もできます。
しかし、Falseと評価されると、やはり@(expression)でブロックされ、変化待ちになります。変化があると、whileの頭で、Trueかどうか判断するという風に動作します。
さてraceの話に戻ると、initial で最初に@(a);が実行されるとブロックされます。下の
initial ->a; でトリガがかかるとunblockされます。しかし、initial プロセスの実行順は、実装依存ですので、別なシミュレータでは、initail
->a; が先に実行され、@(a); が後に実行される可能性もあります。この場合。意図した最初の ->a
では、トリガがかかりません。トリガを検出するためには、最初に@(a); その後に->a;
が実行される必要があります。
これを確実にrace フリーにするためには、Verilog HDLでは、時間をずらすか#0を追加します。
module event_test; event a;//イベント変数の宣言
initial begin
@a;//トリガ待ち
end
initial begin #0; ->a;//トリガする
end
endmodule
#0では、ブロックされますから、他のスレッドの方にシミュレータは、行きます。この場合、@a トリガ待ちの方が確実に先になります。ただ、#0の多用は、goto文の多用に似てスマートではありません。
SVでは、これに対する改善策が盛り込まれています。
16.7.3 ->> ノンブロッキング領域でのトリガ
->>は、ノンブロッキング領域でトリガします。具体的には、<=と同様に、ノンブロッキングキューにキューイングされます。 全回路の=の代入動作や、プロパゲートが終わった後に、ノンブロッキングキューから取り出され、イベントがトリガされます。
module event_test2; event a,b; int j; initial begin j=0; repeat(4) begin ->> #j a;//ノンブロッキング領域におけるトリガ ->b;// VerilogHDLによる従来のトリガ j++; $display("Triggered %d",$time); #30; $display(""); end end always @a begin//a は、ノンブロッキングなので、bより後にトリガされる $display(" Unblocked a %d",$time); end always @b begin $display(" Unblocked b %d",$time); end endmodule
ここで、
->> a;
-> b;
では、構文としては、bの方が後ですが、->>により、イベントの信号はキューイングされ、同時刻でも必ず a イベントの方が後になります。
また、時刻Delayを指定することもOptionで可能なので、未来へのイベントも楽に作成できます。(このときもノンブロッキングキューに登録されます。)
C:\Users\tak.sugawara\Documents\Visual Studio 2005\veritak_sv52\test\event_test2.v(1)::event_test2 Verilogのシミュレーションの準備が完了しました。スタートは,Goボタンを押してください。 ***** Veritak SV Engine Version 0.11 Build Feb.8.2008 ***** Triggered 0 Unblocked b 0 Unblocked a 0 Triggered 30 Unblocked b 30 Unblocked a 31 Triggered 60 Unblocked b 60 Unblocked a 62 Triggered 90 Unblocked b 90 Unblocked a 93 **** Test Done. Total 3.00[msec] ****
16.7.4 triggered() プロパティ
verilog HDLの eventは、瞬間的であって、レベルセンシティブであるwait(a)
は期待通りに動きません。SVでは、event 変数に.triggerd() プロパティをつけることで、 レベルセンシティブ動作が可能になります。
module event_test3; event a ; initial begin int j=0; repeat(3) begin ->> #j a; j++; $display("Triggered %d",$time); #30; $display(""); end end initial begin repeat(3) begin wait(a.triggered()) ;//wait(a.triggered); でもよい $display(" Hi %d",$time); #10; end end endmodule
ここで注意するべきは、triggered() がtrueを返すのは、トリガが伝播してからそのタイムスロット期間中までです。次の時刻に移る直前に内部でfalseにリセットされます。なお triggreed()は,
triggered だけでも動作しますが、メソッド的な意味を込めてここでは、triggered()と記述しました。
C:\Users\tak.sugawara\Documents\Visual Studio 2005\veritak_sv52\test\event_test3.v(1)::event_test3 Verilogのシミュレーションの準備が完了しました。スタートは,Goボタンを押してください。 ***** Veritak SV Engine Version 0.11 Build Feb.8.2008 ***** Triggered 0 Hi 0 Triggered 30 Hi 31 Triggered 60 Hi 62 **** Test Done. Total 0.00[msec] ****
wait (xx.triggered()) の使い方の注意点
16.7.5 event変数配列
配列として定義することもできます。
module event_test4; event a [10][6];//a[ 0:9][0:5] に同じ initial begin int j=0; repeat(3) begin ->> #j a[4][3]; j++; $display("Triggered %d",$time); #30; $display(""); end end initial begin repeat(3) begin wait(a[4][3].triggered()) ;//wait(a[4][3].triggered); でもよい $display(" Hi %d",$time); #10; end end endmodule
16.7.6 task 引数にできる
event 変数は、task の引数にすることもできます。functionの引数にすることはできません。
module event_test5; event a ; initial begin int j=0; repeat(4) begin trigger_task(a,j);//call task $display("Triggered %d",$time); #30; $display(""); end end task automatic trigger_task(event e,ref int j); ->> #j a; j++; endtask initial begin repeat(4) begin wait(a.triggered()) ;//wait(a.triggered); でもよい $display(" Hi %d",$time); #10; end end endmodule
結果です。
C:\Users\tak.sugawara\Documents\Visual Studio 2005\veritak_sv52\test\event_test5.v(1)::event_test5 Verilogのシミュレーションの準備が完了しました。スタートは,Goボタンを押してください。 ***** Veritak SV Engine Version 0.11 Build Feb.8.2008 ***** Triggered 0 Hi 0 Triggered 30 Hi 31 Triggered 60 Hi 62 Triggered 90 Hi 93 **** Test Done. Total 0.00[msec] ****
16.7.7 wait_order
イベントの順序が正しいかどうかを判定する構文です。
wait_order(a,b,c....) action_block
になっています。
module event_test9; event \赤 ,\青 ,\黄 ; initial begin ->> \赤 ; #10; ->> \青 ; #10; ->> \黄 ; #10; end initial begin wait_order(\赤 ,\青 ,\黄 ) $display("OK! 赤 青 黄 %d",$time); else $display("Out of Order. %d",$time); end endmodule
これを実行すると、
C:\Users\tak.sugawara\Documents\Visual Studio 2005\veritak_sv52\test\event_test9.sv(1)::event_test2 Verilogのシミュレーションの準備が完了しました。スタートは,Goボタンを押してください。 ***** Veritak SV Engine Version 0.11 Build Feb.8.2008 ***** OK! 赤 青 黄 20 **** Test Done. Total 0.00[msec] ****
module event_test10; event \赤 ,\青 ,\黄 ; initial begin #10; ->> \青 ; #10; ->> \赤 ; #10; ->> \青 ; #10; ->> \黄 ; #10; end initial begin wait_order(\赤 ,\青 ,\黄 ) $display("OK! 赤 青 黄 %d",$time); else $display("Out of Order. %d",$time); end endmodule
イベント順序が正しくないと、else 項が実行されます。
**** Test Done. Total 0.00[msec] ****C:\Users\tak.sugawara\Documents\Visual Studio 2005\veritak_sv52\test\event_test10.sv(1)::event_test2 Verilogのシミュレーションの準備が完了しました。スタートは,Goボタンを押してください。 ***** Veritak SV Engine Version 0.11 Build Feb.8.2008 ***** Out of Order. 10 **** Test Done. Total 0.00[msec] ****
また、true時の構文を省いて、else 項だけにすることもできます。
wait_order(\赤 ,\青 ,\黄 ) // $display("OK! 赤 青 黄 %d",$time); else $display("Out of Order. %d",$time);
注意として、既にトリガしたイベントが繰り返されてもfalse にはなりません。
16.7.8 event 変数の操作
event 変数について、いくつかの演算が定義されています。仕様は、複雑です。あまり使うことはないと思います。
16.7.8.1 event 変数のマージ
event変数の代入演算子"="は、特別で、 マージを意味します。a=b; で、bとaのマージになります。
正確には、後で定義しますが、まずは、例で見ましょう。
module event_test11; event a, b, c; initial begin a = b; #20; -> a; // also triggers b #1; $display(""); -> b; // also triggers a end initial begin #10; repeat(10) begin @a; $display("a triggered %d",$time); end end initial begin #10; repeat(10) begin @b; $display("b triggered %d",$time); end end endmodule
C:\Users\tak.sugawara\Documents\Visual Studio 2005\veritak_sv52\test\event10.sv(1)::event_test11 Verilogのシミュレーションの準備が完了しました。スタートは,Goボタンを押してください。 ***** Veritak SV Engine Version 0.11 Build Feb.8.2008 ***** a triggered 20 b triggered 20 a triggered 21 b triggered 21 **** Test Done. Total 2.00[msec] ****
->a ;でbもトリガしていることが分かると思います。また、->b で、aもトリガされています。これは、イベント変数a,b
とも=の操作により、同じ内部オブジェクトを指すためです。
同様に次も例でも、内部オブジェクトの参照の共有が行われます。
module event11; event a, b, c; initial begin a = c;//cとaのマージ b = a;//aとbのマージ #20; -> a; //aをトリガすることは、b/cもトリガする #1; $display(""); -> b; //bをトリガすることは、a/cもトリガする #1; $display(""); -> c; //cをトリガすることは、a/bもトリガする。 end initial begin #10;//マージ前にセンスしないような意図 repeat(10) begin @a; $display("a triggered %d",$time); end end initial begin #10;//マージ前にセンスしないような意図 repeat(10) begin @b; $display("b triggered %d",$time); end end initial begin #10;//マージ前にセンスしないような意図 repeat(10) begin @c; $display("c triggered %d",$time); end end endmodule
C:\Users\tak.sugawara\Documents\Visual Studio 2005\veritak_sv52\test\event11.sv(1)::event11 Verilogのシミュレーションの準備が完了しました。スタートは,Goボタンを押してください。 ***** Veritak SV Engine Version 0.11 Build Feb.8.2008 ***** a triggered 20 b triggered 20 c triggered 20 a triggered 21 b triggered 21 c triggered 21 a triggered 22 b triggered 22 c triggered 22 **** Test Done. Total 4.00[msec] ****
しかし、次の例は、上の例にDelayを追加変更しただけですが、全く別な動きになります。
module event12; event a, b, c; initial begin #20;//センス後に a = c;//cとaのマージ b = a;//aとbのマージ -> a; //aをトリガすることは、cをトリガする #1; $display(""); -> b; //bをトリガすることは、cをトリガする #1; $display(""); -> c; //cをトリガすることは、cをトリガする。 end initial begin // #10;//センス後にマージする意図 repeat(10) begin @a; $display("a triggered %d",$time); end end initial begin // #10;//センス後にマージする意図 repeat(10) begin @b; $display("b triggered %d",$time); end end initial begin // #10;//センス後にマージする意図 repeat(10) begin @c; $display("c triggered %d",$time); end end endmodule
結果は、予想を裏切るものです。
C:\Users\tak.sugawara\Documents\Visual Studio 2005\veritak_sv52\test\event12.sv(1)::event12 Verilogのシミュレーションの準備が完了しました。スタートは,Goボタンを押してください。 ***** Veritak SV Engine Version 0.11 Build Feb.8.2008 ***** c triggered 20 c triggered 21 c triggered 22 **** Test Done. Total 3.00[msec] ****
代入される側のブロックされたプロセスに対しては、もはやマージは行われません。この例の場合は、aもbも、代入する側のcのオブジェクトを参照することになり上記の結果となります。最初の二つの例では、a/bともブロックされる前に"="
演算が行われたのが違いです。代入された後は、もはや、元のa,b に対応するプロセスに対して独立にトリガを送ることはできなくなります。
複雑な仕様ですね。
16.7.8.2 event変数に対する操作
null 代入は、内部オブジェクトへの参照のクリアと解釈するとよいと思います。次の例では、nullが代入されたあとは、@はブロックされたままになり、アンブロックされることはありません。
module event13; event E1; initial begin ->>E1; #10; E1 = null; #10; ->>E1;//No Effect end initial begin repeat(3) begin wait( E1.triggered() ); // undefined $display(" Hi L%d",$time); #1; end end initial begin repeat(3) begin @( E1 ); // undefined $display(" Hi @%d",$time); #1; end end endmodule
結果です。null代入後はトリガされません。
C:\Users\tak.sugawara\Documents\Visual Studio 2005\veritak_sv52\test\event13.sv(1)::event13 Verilogのシミュレーションの準備が完了しました。スタートは,Goボタンを押してください。 ***** Veritak SV Engine Version 0.11 Build Feb.8.2008 ***** Hi L 0 Hi @ 0 **** Test Done. Total 3.00[msec] ****
比較演算子も導入されています。(Veritakの実装では、内部オブジェクトへのポインタの比較としています。)
最後の例は、いままでの説明の総合です。
module event14; event E1,E2; initial begin if (E1 !=E2) $display("E1 とE2は、違う %d",$time); ->>E1; #10; ->>E2; #10; E2=E1; if (E1==E2) $display("E1とE2は同じ %d",$time); ->>E1; #10; E1 = null; if (E1 !=E2) $display("E1とE2は違う %d",$time); #10; ->>E2; end initial begin repeat(4) begin @( E1 ); // undefined $display(" Hi @E1 %d",$time); #1; end end initial begin repeat(4) begin @( E2 ); // undefined $display(" Hi @E2 %d",$time); #1; end end endmodule
結果です。
C:\Users\tak.sugawara\Documents\Visual Studio 2005\veritak_sv52\test\event14.sv(1)::event14 Verilogのシミュレーションの準備が完了しました。スタートは,Goボタンを押してください。 ***** Veritak SV Engine Version 0.11 Build Feb.8.2008 ***** E1 とE2は、違う 0 Hi @E1 0 Hi @E2 10 E1とE2は同じ 20 Hi @E1 20 E1とE2は同じ 30 Hi @E1 40 **** Test Done. Total 3.00[msec] ****
16.100 システムファンクション
16.100.1 $bits
コンパイル時にビット幅を評価する関数です。下の例のように宣言済みのビット幅を求めることもできます。
module bits; sub #(3) sub1(); endmodule module sub; parameter int WIDTH=10; logic [WIDTH:0] a; logic [$bits(a)*2:0] b; logic [$bits(b) :1] c; logic [WIDTH:0] mem [$bits(a)][$bits(b)]; logic [$bits(mem):0] packed_reg; initial begin $display("a=%4dbits",$bits(a)); $display("b=%4dbits",$bits(b)); $display("c=%4dbits",$bits(c)); $display("mem=%4dbits",$bits(mem)); $display("mem[0]=%3dbits",$bits(mem[0])); $display("mem[0][0]=%3dbits",$bits(mem[0][0])); $display("mem[0][0][0 +:2]=%3dbits",$bits(mem[0][0][0 +:2])); $display("packed_reg=%4dbits",$bits(packed_reg)); end endmodule
***** Veritak SV Engine Version 0.12 Build Feb.18.2009 ***** a= 4bits b= 9bits c= 9bits mem= 144bits mem[0]= 36bits mem[0][0]= 4bits mem[0][0][0 +:2]= 2bits packed_reg= 145bits **** Test Done. Total 16.00[msec] ****
$bitsは、2値/4値、パック/アンパック等には関係しません。ツールの実装にも依存しません。純粋にユーザが宣言したビット幅の集合の結果を返します。従ってメモリの配置実態とは異なることに注意してください。(DPIを駆使するとき、この理解は重要です。)
module main; typedef int byte10 [0:9]; sub #(.T(byte10), .P(1)) dut2(); endmodule module sub #(type T=int ,parameter P=10) () ; //1bit Override by main module parameter M=P+10;//11bits typedef struct { logic valid;//1bits bit [M:1] data;//11bits T F;//320bits bit [1:13][3:1] h1 [4:2][3];//351bits =13*3*3*3 } MyType; MyType my_struct; typedef bit [$bits(MyType):1] my_reg; initial begin $write("%1d+%1d+%3d+%3d =",$bits(my_struct.valid),$bits(my_struct.data),$bits(my_struct.F),$bits(my_struct.h1)); $display("$bits(my_struct)=%2d",$bits(my_struct)); if ($bits(MyType) !==$bits(my_struct)) $display("Fail"); $display("%d",$bits(MyType)); $display("%d",$bits(my_reg)); end endmodule
***** Veritak SV Engine Version 0.13 Build Mar.20.2009 *****
1+11+320+351 =$bits(my_struct)=683
683
683
**** Test Done. Total 0.00[msec] ****
16.101 クラス
オブジェクト指向の中核を担うのは、クラスです。SVのそれは、C++のクラスよりもJAVAのクラスに似ています。それでは、早速、SVのクラスの使い方と実装の裏側を見ていきましょう。
16.101.1 最小のクラス
下は、最小のクラスです。中身がないクラスです。class 〜 endclass までが、クラスの宣言になります。
class min_class; //中身がない endclass
クラスの中身には、色々入れることができます。例えば、
class min_class; logic a; int m; endclass
クラスの宣言は、module の内側や、外側のどちらでも置けます。
class min_class; logic a; int m; endclass module test; endmodule
16.101.2 クラスの呼び出し方
下のmodule test で、min_class 型のAという変数を宣言しています。min_class というのは、reg/logic/wire と同じように"型"になります。reg/logic/wire は、ビルドインの型ですが、クラスは、自分型の型です。ですから、自分が定義したクラスの種類分だけ、型が増えることになります。
class min_class; logic a; int m; endclass module test; min_class A; endmodule
クラス宣言の構文解析においては、クラス名は、 reg /logic と同じように、"型"
として扱われます。ですので、呼び出し前にクラス名が定義されていることが必要です。"型"が定義されていないと構文解析の段階でエラーになります。
module test; min_class A;//構文解析時は、min_classがクラス名だとは分からない endmodule class min_class;//ここに定義しても logic a; int m; endclass
C:\Users\tak.sugawara\Documents\Visual Studio 2005\veritak_sv52\test\min_class3.v(5):: Parse Error.yy=syntax error, unexpected ';', expecting '(' 構文解析でエラーがあります。
ところで、Aは、min_class オブジェクトそのものではなく、いわゆるハンドルになります。下のinteger a ;と対比してみましょう。aは、integerで、32ビット4値分のメモリを実際に食いますから、aは、オブジェクト(実体)そのものです。しかし、クラス型の変数は、全てmin_class 型に対するポインタ(一般的には、ハンドルが必ずしもポインタにはならないのですが、Veritakの実装ではポインタにしているので、以降の説明ではポインタ(アドレスを保持する変数)という表現を使うことをお許しください。)になります。C++風に書き直すなら
module test; min_class A;//C++風に書き直すと、暗黙にポインタ宣言Null初期化 min_class* A=0; integer a;//暗黙に a=32'hx; endmodule
となります。この場合、Aは、シミュレーション開始前に、コンパイラが暗黙にnullに初期化されます。
(min_class *A=0; です。 これは、a が、32'bxに初期化されるのと同じフェーズで実行されます。) それでは、 min_classの実体=オブジェクトは、どこにあるのでしょうか? 実は、この段階では、どこにもありません。試しに次のソースでクラス内のメンバーのmにアクセスしてみましょう。
class min_class; logic a; int m; endclass module test; min_class A; initial begin A.m=0;//min_classのメンバーmに0を書きたい end endmodule
このソースでコンパイルは通ります。しかし、RUNすると
C:\Users\tak.sugawara\Documents\Visual Studio 2005\veritak_sv52\test\min_class4.v(12)::Fatal Error : Null ポインタアクセスエラーです。0ns
のように、
A.m=0;
のところで、実行時エラーになってしまいます。(この実行時エラーは、VerilogHDLでは殆どお目にかかったことがないと思います。VerilogHDLは、そのほとんどがSTATICなオブジェクトですので、エラーの検出もコンパイル時に可能だからです。)
クラスは、ダイナミックなオブジェクトです。 C言語のmalloc, C++/C#/JAVA言語 のnewのように、使用したいときにOSからメモリをもらってインスタンス化(実体化)する必要があります。SIMエンジンは、実体化した先のアドレスをハンドルAが保持していると考えます。 C言語で書くと A->mでアクセスしようとします。このときAに入っているアドレスは、まだ、Nullですので、アドレスNull(0)にオブジェクトが入っているように見えてしまいます。OSがこの不正なアクセスを検出する場合もあるでしょうし、SIMエンジンのNullポインタチェックで実行時エラーにする場合もあるでしょう。いずれにしても、このエラーは、コンパイル時ではなく、実行時に起きます。
なので、オブジェクトをメモリ上に確保してA変数にそのアドレスを入れてやらなくてはいけません。そのための構文が、new 構文です。OSからメモリをもらってインスタンス化)します。左辺にインスタンス化するハンドル名、右辺にnew
と書きます。
class min_class; logic a; int m; endclass module test; min_class A;// C++風に書き直すと、暗黙にポインタ宣言Null初期化 min_class* A=0; initial begin A=new;//インスタンス化 A= new min_class();クラス内部メンバーlogic a; int m;が実体化される A.m=0;//min_classのメンバーmに0を書きたい A->m=0; $display("m=%d",A.m);//値の表示 $display("m=%d",A->m); end endmodule
これで、初めてをAを介してmin_classの中身にアクセスできるようになります。 クラスを書いて、アクセスできるようになるまで随分面倒なような気がしますね。 new で クラス内部のメンバーオブジェクト、ここでは、a と mも実体化(メモリ上に占有する)ようになります。このクラスの場合、aは、4値1ビット、mは、2値32ビットのオブジェクトです。勿論、他にもシミュレータ内部の付帯情報も付加されるので、見かけのオブジェクトサイズ以上にメモリは消費します。一方 A変数は、クラスオブジェクトサイズの影響は全く受けず,32ビット環境であれば、32ビットの生ポインタさえあれば事足ります。 オブジェクトの大きさによらずオブジェクトのアドレスを保持すればよいだけですので、環境が決まれば常に一定のサイズ(Ex.32ビット=4バイト)だけ消費します。クラスへのアクセスは、このハンドルを介して行うことになります。
C:\Users\tak.sugawara\Documents\Visual Studio 2005\veritak_sv52\test\min_class4.v(7)::test Verilogのシミュレーションの準備が完了しました。スタートは,Goボタンを押してください。 ***** Veritak SV Engine Version 0.12 Build Feb.18.2009 ***** m= 0 **** Test Done. Total 0.00[msec] ****
まとめると、
になります。VerilogHDLは、いままでその殆どが、SIM開始から終わりまで生き続けるSTATICなオブジェクトです。これだけだと、テストベンチの生成がやりずらいので、ダイナミックなクラスが導入されました。ダイナミックですので、SIM中に生まれたり死んだりするところが厄介ですが、SVでは、Cのfree()や、delete
の類は、ありません。生成は、ユーザの仕事ですが、消去に関しては、シミュレータがやってくれます。
16.101.3 クラスの継承
systemverilog クラスの継承は、単一継承のみです。これは、JAVAと同じですね。継承の概念を見ていきましょう。
次は継承の例です。class E_class extends min_class;
で、E_class は、min_class を継承することを宣言しています。
class min_class ; logic a; int m; endclass class E_class extends min_class;//E_class は、min_classを継承する int n; int k; endclass module test; E_class E; initial begin E=new; E.k=10;//E_class のメンバーに書く E.m=100;//min_classのメンバーに書く $display("E.k=%3d E.m=%3d",E.k,E.m);//読み出しも可能 end endmodule
継承されたクラスのオブジェクトレイアウトは、次のようになっており、min_classのデータを全て含みます。つまりmin_classは、E_classの部分集合になっています。また、ハンドルEは、E_classオブジェクトの先頭アドレスを指しますが、これは、同時にmin_classオブジェクトの先頭アドレスでもあります。
同様にして、E_classがさらに継承されたクラスのオブジェクトは、破線部のように、E_classを含むオブジェクトになるでしょう。クラスにすると、このようにして、いくらでも拡張できます。
この性質は、クラスを部品と考えると都合がよいことになります。ある部品を共通に使い、自分仕様にカスタマイズしたい部分は、親クラスを継承して、自分仕様の部分だけ、データを追加すれば良いのです。このような元になるクラス方を親クラス(またはsuperクラス)、継承してできたクラスの方を派生クラス(子クラス、またはサブクラス)という言い方もされます。一般にクラスの継承イメージは下図の跳び箱のようになりますが、データのレイアウト(オブジェクトのメモリ配置)は、上のように、共通の先頭アドレスをポイントすることでアクセスできることに注意してください。クラスの構造が決まれば、オブジェクト構造が決定でき、全てのメンバーは、
先頭アドレス+ 定数オフセット
でアクセスできます。 このオブジェクトの先頭アドレスがいわゆるthisアドレスになります。ですからクラスのハンドルは、このthis
ポインタです。(これは、Veritakの実装がそうなっているだけの話であってLRMが規定するところではありません。しかしながら、SVの場合は、単一継承のみであり、他のシミュレータでも同じように考えて差し支えないと思います。)
上のコードでは、ハンドルは、E_classになっていますので、親クラスのデータにもアクセスできます。最初は、ちょっと奇異に見えますが、上の包含関係を頭に置くと理解できると思います。
16.101.4 クラスの代入
16.101.4.1 子クラスから親クラスへ代入できる
オブジェクトとハンドルの関係をさらに詳しく見ましょう。親クラスのハンドルに子クラスのハンドルを代入できます。
class min_class ; logic a; int m; endclass class E_class extends min_class;//E_class は、min_classを継承する int n; int k; endclass module test; E_class E; min_class base; initial begin E=new; E.k=1000;//E_class のメンバーに書く E.m=10;//min_class のメンバーに書く $display("E.k=%3d E.m=%3d",E.k,E.m);//読み出しも可能 base=E;//親クラスに代入 $display("base.m=%3d",base.m);//問題なく表示される end endmodule
C:\Users\tak.sugawara\Documents\Visual Studio 2005\veritak_sv52\test\min_class23.v(12)::test Verilogのシミュレーションの準備が完了しました。スタートは,Goボタンを押してください。 ***** Veritak SV Engine Version 0.13 Build Mar.20.2009 ***** E.k=1000 E.m= 10 base.m= 10 **** Test Done. Total 3.00[msec] ****
これは、下図のように理解できます。Eとbaseは、それぞれ同じオブジェクトアドレスを指しています。ただし、ハンドルbaseの方は、min_class領域だけにアクセスできるハンドルです。
試しに
$display("base.m=%3d",base.k);
を最後に追加してみると
C:\Users\tak.sugawara\Documents\Visual Studio 2005\veritak_sv52\test\min_class24.v(25)::Error: base.k 宣言が見つかりません。
コンパイル時にエラーとなります。
それでは、親クラスのハンドルを子クラスのハンドルに代入できるでしょうか?
16.101.4.2 親クラスから子クラスへは代入できない
module test; E_class E; min_class base; initial begin base=new; E=base;//親クラスから子クラスの代入は、コンパイルエラー end endmodule
これは、コンパイル時エラーになります。
C:\Users\tak.sugawara\Documents\Visual Studio 2005\veritak_sv52\test\min_class25.v(18)::Error : 左辺のタイプは右辺に代入できません。
仮に、もしこれを代入可能にしてしまうと、E.kのように、min_classには、存在していないオブジェクトにアクセスされる可能性があります。 子クラスから親クラスへの代入は、常に包含関係を満足するために、Warningなしに正しい構文ですが、逆は、包含関係が成立しないので不可です。
16.101.4.3 ダイナミックキャスト
C++のdynamic_castに相当する機能です。
class min_class ; logic a; int m; endclass class E_class extends min_class;//E_class は、min_classを継承する int n; int k; endclass module test; E_class E,Ecopy; min_class base; initial begin E=new; E.k=1000;//E_class のメンバーに書く E.m=10;//min_class のメンバーに書く $display("E.k=%3d E.m=%3d",E.k,E.m);//読み出しも可能 base=E;//親クラスに代入 $display("base.m=%3d",base.m);//問題なく表示される $cast(Ecopy,base);//C++ 風に書くと E_copy=dynamic_cast<E_class*>(base); $display("Ecopy.k=%3d Ecopy.m=%3d",Ecopy.k,Ecopy.m);//読み出しも可能 end endmodule
$castで、変換可能な場合、ハンドルの値がコピーされます。この様子は、下図になります。base=E;でハンドルがコピーされます。同じオブジェクトアドレスを指すことに注意してください。代入は、親クラスのハンドルがコピーされるだけでオブジェクトそのものは、全く関係がなくなんら変化しません。これに対し子クラスへの代入は正当なものかは、実行時にならないとわからないので、実行時の型を検査するtask/functionである$cast で検査します。この例の場合、元がE_classオブジェクトですので、問題なくキャストされ、ハンドルEと全く同じようにアクセスできます。
***** Veritak SV Engine Version 0.13 Build Mar.20.2009 ***** E.k=1000 E.m= 10 base.m= 10 Ecopy.k=1000 Ecopy.m= 10 **** Test Done. Total 3.00[msec] ****
なお、C++のdynamic_castでは、例外が送出されることがありますが、SVでは、例外は勿論エラーにもなりません。検査して、castができないときは、null
になります。またfunctionとして使う場合は、$cast不成功は、0、成功は、0以外の値が返ります。
module test; E_class E,Ecopy; min_class base; initial begin E=new; E.k=1000;//E_class のメンバーに書く E.m=10;//min_class のメンバーに書く $display("E.k=%3d E.m=%3d",E.k,E.m);//読み出しも可能 base=E;//親クラスに代入 $display("base.m=%3d",base.m);//問題なく表示される if($cast(Ecopy,base))begin//キャストが成功の場合は、Ecopyに代入される。 $display("Ecopy.k=%3d Ecopy.m=%3d",Ecopy.k,Ecopy.m);//読み出しも可能 end else $display("プログラムエラー");//Ecopyはnullが代入される end endmodule
***** Veritak SV Engine Version 0.13 Build Mar.20.2009 ***** E.k=1000 E.m= 10 base.m= 10 Ecopy.k=1000 Ecopy.m= 10 **** Test Done. Total 3.00[msec] ****
まとめ
16.101.4.4 オブジェクトコンポジション
クラス中のメンバーとしてクラスハンドルを持つ場合を考えます。E_classに部品として、クラスcomponent
を追加しました。
class min_class ; logic a; int m; endclass class component; int j; endclass class E_class extends min_class;//E_class は、min_classを継承する int n; int k; component combo1;//オブジェクトコンポジション endclass module test; E_class E; initial begin E=new; E.k=1000;//E_class のメンバーに書く E.m=10;//min_class のメンバーに書く $display("E.k=%3d E.m=%3d",E.k,E.m);//読み出しも可能 E.combo1.j=1;//E_classの部品でcomponent クラスのメンバーjに値を書きたい end endmodule
上のソースは、コンパイルは通りますが、RUNすると実行エラーが出ます。
***** Veritak SV Engine Version 0.13 Build Mar.20.2009 ***** E.k=1000 E.m= 10 C:\Users\tak.sugawara\Documents\Visual Studio 2005\veritak_sv52\test\min_class28.v(31)::Fatal Error : Null ポインタアクセスエラーです。0ns
ここでの問題は、component がインスタンス化されていないことです。クラスハンドルだけではインスタンス化されませんでしたね。
次のように修正すれば、大丈夫です。
class min_class ; logic a; int m; endclass class component; int j; endclass class E_class extends min_class;//E_class は、min_classを継承する int n; int k; component combo1;//オブジェクトコンポジション endclass module test; E_class E; initial begin E=new; //E_classのインスタンス化 E.k=1000;//E_class のメンバーに書く E.m=10;//min_class のメンバーに書く E.combo1=new;//componentのインスタンス化 $display("E.k=%3d E.m=%3d",E.k,E.m);//読み出しも可能 E.combo1.j=1; $display("E.combo1.j=%3d",E.combo1.j); end endmodule
図にすると分かりやすいと思います。上の宣言では、E_class は、componentクラスを部品として含みかのように見えます。しかしE_classオブジェクトメモリイメージで含まれるのは、componentクラスのハンドルであってオブジェクトそのものではありません。componentクラスのオブジェクトは、E_classオブジェクトとは別なところにあり、それはcombo1
というハンドルを使って参照されることになります。
E_classクラスの newによって、自動的に componentクラスが new されることははなく、どちらもユーザの仕事になります。
上の図のイメージを確認するために、少し追加してみます。
class min_class ; logic a; int m; endclass class component; int j; endclass class E_class extends min_class;//E_class は、min_classを継承する int n; int k; component combo1;//オブジェクトコンポジション endclass module test; E_class E; component C1; initial begin E=new; //E_classのインスタンス化 E.k=1000;//E_class のメンバーに書く E.m=10;//min_class のメンバーに書く E.combo1=new;//componentのインスタンス化 $display("E.k=%3d E.m=%3d",E.k,E.m);//読み出しも可能 E.combo1.j=1; $display("E.combo1.j=%3d",E.combo1.j); C1=E.combo1;//ハンドルのコピー `ifdef Veritak $display("E_classの参照カウント=%3d componentの参照カウント=%3d",$RefCount(E),$RefCount(C1)); `endif E=null;//唯一の参照であるEをnullするのでEオブジェクトはここで消滅 `ifdef Veritak $display("E_classの参照カウント=%3d componetの参照カウント=%3d",$RefCount(E),$RefCount(C1)); `endif $display("C1.j=%3d 私は未だ生きています.",C1.j); end endmodule
***** Veritak SV Engine Version 0.13 Build Mar.20.2009 ***** E.k=1000 E.m= 10 E.combo1.j= 1 E_classの参照カウント= 1 componentの参照カウント= 2 E_classの参照カウント= 0 componetの参照カウント= 1 C1.j= 1 私は未だ生きています. **** Test Done. Total 3.00[msec] ****
これも図にしてみると下図のようになります。C1でハンドルの代入が行われると、component
クラスのオブジェクトは、一つですが、
参照先は、クラス内のcombo1 とクラス外のC1で計2つになります。Veritakの内部の参照カウントは、$RefCountで拾うことができ、確かに参照カウントは2になっています。次にE=null;
とすると、E_classオブジェクトの唯一の参照がなくなりますから、E_classオブジェクトは、シミュレータのガーべジコレクションによりdelete
されます。この結果componetオブジェクト参照は、C1だけになります。(参照カウントは、1になります。) しかし、未だ参照がありますから、シミュレータはdeleteしません。このように、外部の参照が行われるとオブジェクトコンポジションは、その生存期間が個別に異なることになります。 これも継承との違いですね。
まとめ
クラスの合成方法として、
の2種類があります。オブジェクトコンポジションでは、個別のnew が必要になります。
16.101.5 コンストラクタ
上のコードは、あまり美しくありません。newした後に、メンバーの初期化や、部品クラスのnew
を行っているのはスマートではないです。クラスをブラックボックスの部品として扱いたいので、new
後は、これらの初期化処理は、終わっているようにしたいですね。そうすれば、クラスのユーザは、内部の仕様の詳細を知らなくてもよいことになります。そのための構文がコンストラクタです。次のコードは、コンストラクタを使って書き直したものです。
class min_class ; logic a; int m; function new ;//コンストラクタ m=10; endfunction endclass class component; int j; function new;//コンストラクタ j=1; endfunction endclass class E_class extends min_class;//E_class は、min_classを継承する int n; int k; component combo1;//オブジェクトコンポジション function new ;//コンストラクタ super.new;//親クラスのnew 。なくても暗黙的に呼ばれる k=1000; n=10; combo1=new; endfunction endclass module test; E_class E; initial begin E=new; //E_classのインスタンス化 E_classの function new を呼び出す $display("E.k=%3d E.m=%3d",E.k,E.m);//読み出しも可能 $display("E.combo1.j=%3d",E.combo1.j); end endmodule
***** Veritak SV Engine Version 0.13 Build Mar.20.2009 ***** E.k=1000 E.m= 10 E.combo1.j= 1 **** Test Done. Total 0.00[msec] ****
new というfunctionは、特別で、インスタンス化したときに呼び出されます。この中で初期化処理を書いてしまえばスッキリします。注意するべきは、親クラスを呼ぶ場合です。super.new
を使って呼び出しますが、この構文は、function new 中の一番目に記述しなければなりません。でないとコンパイルエラーとなります。記述しなくても暗黙的に呼び出されますので、superに渡す引数がなければ、特に記述する必要はありません。
継承がある場合、このようにして、次々にsuperが呼び出されるので、最初に実行されるのは、一番ルートの基底クラスから実行されることになります。以降順番に子クラスに降りてきます。この構築順序は、オブジェクトを構築する上で必然的な順序です。後で仮想関数の話がでてきますが、実質的に、コンストラクタ中では、自分より子供の仮想関数を呼び出すことはできないことに注意してください。
引数付のコンストラクタの例です。
class min_class ; logic a; int m; function new(int i) ;//コンストラクタ m=i+9; endfunction endclass class component; int j; function new(int i);//コンストラクタ j=i; endfunction endclass class E_class extends min_class;//E_class は、min_classを継承する int n; int k; component combo1;//オブジェクトコンポジション function new(int i) ;//コンストラクタ super.new(i);//親クラスのnew なくても暗黙的に呼ばれる k=i+999; n=i+9; combo1=new(i); endfunction endclass module test; E_class E; initial begin for (int i=0;i<3;i++) begin E=new(i); //E_classのインスタンス化 disp(E); end end function void disp(input E_class E); $display("E.k=%3d E.m=%3d",E.k,E.m);//読み出しも可能 $display("E.combo1.j=%3d",E.combo1.j); $display(""); endfunction endmodule
***** Veritak SV Engine Version 0.13 Build Mar.20.2009 ***** E.k=999 E.m= 9 E.combo1.j= 0 E.k=1000 E.m= 10 E.combo1.j= 1 E.k=1001 E.m= 11 E.combo1.j= 2 **** Test Done. Total 0.00[msec] ****
単に定義する場所が変わっただけですが、E_class を使うユーザから見ると、大分すっきりしました。コンストラクタは、functionであることに注意してください。つまり、functionの要請事項、イベントや、時間コントロールの構文は置けないことに注意してください。
ところで、function disp も、クラスの中に入れ込めると、もっとすっきりしそうですね。 それが、メソッドになります。
16.101.6 メソッド
16.101.6.1 メンバー関数
上のコードでmodule 中のdispを少し書き換えてクラスに入れ込みました。
class min_class ; logic a; int m; function new(int i) ;//コンストラクタ a=1'b1; m=i+9; endfunction function void disp_my_data( );//メンバー関数 $display("min_class Data: a=%b m=%3d ",a,m); endfunction endclass class component; int j; function new(int i);//コンストラクタ j=i; endfunction endclass class E_class extends min_class;//E_class は、min_classを継承する int n; int k; component combo1;//オブジェクトコンポジション function new(int i) ;//コンストラクタ super.new(i);//親クラスのnew なくても暗黙的に呼ばれる k=i+999; n=i+9; combo1=new(i); endfunction function void disp_my_data();//メンバー関数 super.disp_my_data( );//親クラスは、superで呼ぶ $display("E_class Data: k=%3d n=%3d ",k,n); $display("E_class Data: combo1.j=%3d",combo1.j); $display(""); endfunction endclass module test; E_class E; initial begin for (int i=0;i<3;i++) begin E=new(i); //E_classのインスタンス化 E.disp_my_data();//E_classのメンバー関数の起動 end end endmodule
***** Veritak SV Engine Version 0.13 Build Mar.20.2009 ***** min_class Data: a=1 m= 9 E_class Data: k=999 n= 9 E_class Data: combo1.j= 0 min_class Data: a=1 m= 10 E_class Data: k=1000 n= 10 E_class Data: combo1.j= 1 min_class Data: a=1 m= 11 E_class Data: k=1001 n= 11 E_class Data: combo1.j= 2
module 側に書く文が減り、クラス側に移っただけですが、クラスを使う側から見たmodule は、簡単になっていることが分かると思います。disp_my_data() は、E_class にもその親クラスのmin_classにも定義されていますが、それぞれ別々なfunctionとして評価され、正当な構文です。
16.101.6.2 仮想関数
virtual を付けたところが仮想関数になります。 disp_my_dataという同名の関数が親クラスと子クラスで定義されています。
これを親クラスのハンドルに代入すると、オブジェクトの型に応じて、functionが自動的に選びだされます。
class min_class ; logic a; int m; function new(int i) ;//コンストラクタ a=1'b1; m=i+9; endfunction virtual function void disp_my_data( );//仮想関数 $display("min_class Data: a=%b m=%3d ",a,m); $display(""); endfunction endclass class component; int j; function new(int i);//コンストラクタ j=i; endfunction endclass class E_class extends min_class;//E_class は、min_classを継承する int n; int k; component combo1;//オブジェクトコンポジション function new(int i) ;//コンストラクタ super.new(i);//親クラスのnew なくても暗黙的に呼ばれる k=i+999; n=i+9; combo1=new(i); endfunction virtual function void disp_my_data();//仮想関数 //super.disp_my_data( ); $display("E_class Data: k=%3d n=%3d ",k,n); $display("E_class Data: combo1.j=%3d",combo1.j); $display(""); endfunction endclass module test; min_class Base; initial begin for (int i=0;i<3;i++) begin Base=gen_instance(i ); //E_classのインスタンス化 E_classまたは min_classの生成 Base.disp_my_data(); //E_classのメンバー関数の起動、仮想関数の仕組みによりオブジェクトの型に応じて起動する end end function min_class gen_instance(int i); E_class E; min_class M; if ({$random}%2==1) begin E=new (i); return E; end else begin M=new(i); return M; end endfunction endmodule
***** Veritak SV Engine Version 0.13 Build Mar.20.2009 ***** min_class Data: a=1 m= 9 E_class Data: k=1000 n= 10 E_class Data: combo1.j= 1 E_class Data: k=1001 n= 11 E_class Data: combo1.j= 2 **** Test Done. Total 0.00[msec] ****
試しに、親クラスのdisp_my_data()にvirtual を付けない場合、
// virtual function void disp_my_data( );//メンバー関数 $display("min_class Data: a=%b m=%3d ",a,m); $display(""); endfunction
親クラスのdisp_my_data() しか呼び出されなくなります。
***** Veritak SV Engine Version 0.13 Build Mar.20.2009 ***** min_class Data: a=1 m= 9 min_class Data: a=1 m= 10 min_class Data: a=1 m= 11 **** Test Done. Total 0.00[msec] ****
あたかも、親クラスのハンドルを使ってオブジェクトそのものが、関数を選んでいるかのように見えます。
通常の関数と異なるのは、コンパイル時に呼び出す関数が決定しているのではなく、実行時に、オブジェクトに対応した関数が呼び出されることです。
コンパイラはどの関数が呼ばれるかは、コンパイル時、把握していません(決定できません。)。
for (int i=0;i<3;i++) begin Base=gen_instance(i ); //E_classのインスタンス化 E_classまたは min_classの生成 Base.disp_my_data(); //E_classのメンバー関数の起動、仮想関数の仕組みによりオブジェクトの型に応じて起動する end
仮想関数を使うには、同じ関数名、同じ引数の個数、同じ引数の型、同じビット幅であることが必要になります。一致しないと仮想関数とは認識されません。
クラスを使うと、継承や、仮想関数を使うことで、親クラスの部品には、殆ど手を着けずに、
できるところが便利なのです。
16.101.7 メモリ回収機構(ガーベジコレクション)
LRMは、この辺について、全く規定していないのですが、シミュレータの実装では、参照カウントを使ったものが多いのではないかと思います。 参照カウントによるメモリ回収は、ユーザ側は、大抵の場合は、気にしなくてよいのですが、まれに問題も起こりえます。VeritakSVでも参照カウントを使っているので、ここでは、参照カウントの仕組みを説明します。
16.101.7.1 参照カウントの問題点
Wikipediaが詳しいです。
http://ja.wikipedia.org/wiki/%E5%8F%82%E7%85%A7%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88
そこで、上のJAVA ソースをsystemverilog に書き換えてみます。例えば、次のようになります。
typedef class B;//定義前に呼び出されるのでBがクラスであることを宣言 class A ; B b;//typedefでBは、クラスであると宣言済み endclass :A class B ; A a; endclass :B module test; A a; B b; initial begin a = new ;//1) b = new ;//2) a.b = b; b.a = a; `ifdef Veritak $display("a の参照カウント=%d bの参照カウント=%d",$RefCount(a),$RefCount(b)); `endif a = null; b = null; // 1) 2)で作成したAとBのオブジェクトは到達不可能にもかかわらず、参照カウントは1 end endmodule
これを実行すると。
***** Veritak SV Engine Version 0.13 Build Mar.20.2009 ***** a の参照カウント= 2 bの参照カウント= 2 **** Test Done. Total 0.00[msec] ****
となります。ところが、initial が終わった時点では、内部の参照カウントは1のままになります。ソース上では、これを確かめる手段がないので、次のようなベンチに書き換えてみます。
typedef class B;//定義前に呼び出されるのでBがクラスであることを宣言 class A ; B b;//typedefでBは、クラスであると宣言済み endclass :A class B ; A a; endclass :B module test; initial begin repeat(100000000) make_linked_list;//1億回 automatic functionを呼ぶ end function automatic void make_linked_list(); A a; B b; a = new ;//1) b = new ;//2) a.b = b; b.a = a; // `ifdef Veritak // $display("a の参照カウント=%d bの参照カウント=%d",$RefCount(a),$RefCount(b)); // `endif a = null; b = null; // 1) 2)で作成したAとBのオブジェクトは到達不可能にもかかわらず、参照カウントは1 endfunction endmodule
このソースを実行して、落ちるシミュレータは、参照カウント方式です。シミュレータのプロセスメモリをtask
managerで観測して、どんどんメモリが増えていくのがわかると思います。循環参照があると開放されないのですね。これを開放するには、
全部nullにしてしまえば良いわけですが、実際には、次でも十分です。
function automatic void make_linked_list(); A a; B b; a = new ;//1) b = new ;//2) a.b = b; b.a = a; a.b = null; b.a = null; // a=null;//b=nullは、SCOPEから抜けるとき(function return 時)に自動実行される(automatic 変数)、参照カウントは、0になり開放される。 endfunction
これで、メモリ増加は、なくなりました。
linked_list の場合に、このような問題が生じるので、SVでは、ビルトインのlinked_list
クラスが用意されています。また、キュー配列が言語仕様として備わっているので、現実的には、これらを使えば問題に遭遇することはないでしょう。
16.101.8 パラメタライズド クラス
型を引数として、渡すクラスです。C++ テンプレートのように動作します。次の例は、STL vector
もどきのクラスです。
この例では、抽象化した型Tで、記述しています。vector #(.T(My_Type) ) byte_vec;でインスタンス化していますが、My_Type
でインスタンス化しています。このとき型、byte でクラスvector は、インスタンス化されます。
class vector #(type T=byte,parameter int Initial_Alloc_size=1); local T ptr []; local int allocated_size; local int i; const local int unsigned Initial_Allocated_Size=Initial_Alloc_size; function new;//コンストラクタ allocated_size=0; i=0; endfunction function void push_back(input T data); if (!allocated_size) begin ptr=new [ Initial_Allocated_Size];//最初は、 Initial_Allocated_Sizeでアロケート end if ( i==allocated_size) begin allocated_size =ptr.size()*2;//次回は、現在の2倍サイズで ptr=new [allocated_size](ptr);//Reallocする。 //$display("new allocated_size=%d",allocated_size); end ptr[i]=data; i++; endfunction function T at(input int unsigned index); return ptr[index]; endfunction function int size(); return i; endfunction endclass module dynamic_array; typedef byte My_Type; vector #(.T(My_Type) ) byte_vec; My_Type b1; initial begin byte_vec=new; $display("size=%d",byte_vec.size()); b1=0; repeat(10) begin byte_vec.push_back(b1); b1++; $display("size=%d",byte_vec.size()); end for (int i=0;i< byte_vec.size();i++) begin $display("byte_vec[%2d]=%2x",i,byte_vec.at(i)); end end endmodule
***** Veritak SV Engine Version 0.13 Build Mar.20.2009 ***** size= 0 size= 1 size= 2 size= 3 size= 4 size= 5 size= 6 size= 7 size= 8 size= 9 size= 10 byte_vec[ 0]=00 byte_vec[ 1]=01 byte_vec[ 2]=02 byte_vec[ 3]=03 byte_vec[ 4]=04 byte_vec[ 5]=05 byte_vec[ 6]=06 byte_vec[ 7]=07 byte_vec[ 8]=08 byte_vec[ 9]=09 **** Test Done. Total 4.00[msec] ****
16.102 typedef
c言語のtypedef に似ていますが、SVでは、packed/unpacked と型のパラメータ渡しの概念が追加されて、複雑な仕様になっています。
16.102.1 構文エラーの回避
例えば、function の戻り値として、配列で戻したいとしたら、次のように書きたくなりますね。
function automatic integer [10] f(); for( int i=0; i<10; i++ ) begin f[i] =$random; end return f; endfunction :f
しかし、これでは、構文エラーになってしまいます。integer 配列 を戻すという構文は、BNFで定義されていないからです。こういう場合、integer
[10]という型を新たに定義してしまえばよいです。
module dynamic_array; typedef integer unsigned UINT10 [10];//10個のinteger unsignedを持つ配列の型をUINT10と定義する UINT10 F; initial begin F=f(); for (int i=0;i<10;i++) begin $display("F[%2d]=%d",i, F[i]); end end function automatic UINT10 f(); for( int i=0; i<10; i++ ) begin f[i] =$random; end return f; endfunction :f endmodule
結果です。
Verilogのシミュレーションの準備が完了しました。スタートは,Goボタンを押してください。 ***** Veritak SV Engine Version 0.13 Build Mar.20.2009 ***** F[ 0]= 303379748 F[ 1]=3230228097 F[ 2]=2223298057 F[ 3]=2985317987 F[ 4]= 112818957 F[ 5]=1189058957 F[ 6]=2999092325 F[ 7]=2302104082 F[ 8]= 15983361 F[ 9]= 114806029 **** Test Done. Total 0.00[msec] ****