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を起動するのに、

  1. initial my_task;
  2. initial my_task; 


と書く代わりに、二つの平行プロセスを同時に起動するforkを使い、

  1. fork  
  2.         my_task;
  3.         my_task;
  4. join 


と書くことができます。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参照を防止するようにしてください。

  1. module test;
  2.         logic a;
  3.         function automatic  void f( input integer a1);
  4.                 $display("function a1=%x",a1);
  5.                 fork
  6.                         automatic integer a2=a1;
  7.                         $display("a2=%x",a2);
  8.                         begin
  9.                                 repeat(2) begin
  10.                                         @(a);
  11.                                         $display("a changed %d",$time);
  12.                                 end
  13.                         end
  14.                         a<=1;
  15.                         a <=#10 0;
  16.                         $display("Hi2 time=%d",$time);
  17.                         #1 $display("Hi4 time=%d",$time);
  18.                         #20 t1;
  19.                 join_none
  20.         endfunction
  21.  
  22.         task t1();
  23.                 $display("Hi Task %d",$time);
  24.                 #10;
  25.                 $display("Hi task2 %d",$time);
  26.                 fork
  27.                         $display("Hi3 A %d",$time);
  28.                         #10 $display("Hi3 B %d",$time);
  29.                 join_none
  30.         endtask
  31.  
  32.        
  33.        
  34.         integer li;
  35.         initial begin
  36.                 $monitor ("a=%b %d",a,$time);
  37.                 li=32'h1234_5678;
  38.                 f(li);
  39.                 $display("HiHi2");
  40.        
  41.         end
  42.        
  43.  
  44. endmodule

結果です。

***** 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次元のアンパック配列と見ることもできます。  
配列の順序は、最初にアンパックの方が先に来てその後にパック配列の修飾になります。下の図を見ると分かりやすいでしょう。

[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:

  1. ref には4番目のポート定義です。(input/output/inout) なので input 等は、付加しません。
  2. ref が付いていない方のfunctionにもinput がついていませんね。SVでは、ポート定義を定義しないときは、前の定義を継承しdefaultは、input になります。この例の場合、暗黙的に input になります。
  3. void は戻り値がないという意味です。その意味では、task と同じになりますが、function のセマンティクス制限(Delay/Event不可、ノンブロッキング不可/taskを呼べない)上でよいのであれば、シミュレータは、task よりも高速に動作するはずです。
  4. ref portは、論理合成できません。テストベンチ専用です。
  5. function のautomatic宣言 は、必須ではありません。なくてもよいです。
  6. ref port は、automatic変数に適用してもよいです。下記のautomatics宣言と同じ制限を受けます。
    1. 手続き構文以外使えない。
    2. Non-Blocking文では使用不可
    3. 手続き系継続代入文では使用不可(force/assign/release/deassign)
  7. ポート定義は、呼び出し側と呼び出され側で完全に一致する必要があります。
  8. ref portに渡せるのは、variable 系ならば、パック配列、アンパック配列を問わず渡せます。しかしnet系は渡せません。


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:

  1. SVでは、functionもoutput/inout宣言できます。ただし、
    1. 手続き構文以外使えない。
    2. 手続き系継続代入文では使用不可(force/assign/release/deassign)

    の制限があります。

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ではこの部分が曖昧でしたが、明確に規定されました。

  1. トップモジュールからスタートします。
  2. 下階層をエラボレートしていきます。このとき、generateは展開しないで、それ以外の部分をできる限り展開します。このとき出会ったparameter/defparamは、パラメータオーバライドや、defparamで置き換えられ最終値になります。言い換えると、defparamの評価時、解決されないSCOPEでは、defparamの置き換えが遅延されます。
  3. 2.で出会ったgenerate ブロックを評価します。このとき、2のステップが発生したら、2から、繰り返し、エラボレートするべきリストがなくなるまで2,3を繰り返します。


例です。かなり複雑な動きをします.

   なお、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] ****