このように、C風に書けます。generate構文2001では、仕様が曖昧であったためにいくつか混乱がありました。2005では、エラボレーションの順序に関しても明確に仕様化されました。構文も拡張されており、generateで再帰インスタンス化が可能になりました。それでは、2001との違いを見ていきましょう。generate-endgenerateは、オプションgenerate-endgenerateで囲む必要はありません。ループでは、genvarのインライン宣言が使えます。文では、複数のイニシャライズ構文や、ステップ構文は使えません。$display("%m");endmodule結果です。$display("%m");で階層名を出力しています。generateで複雑なインスタンス化を行う場合、コンパイラは、generate中の解釈の様子をコンパイル中、普通は、出力しないと思うので、こんな感じで、スコープ名を出力してデバッグすることになります。ラベル名は、LRMが規定している上のベンチのラベル名を除いてみましょう。$display("%m");endmodule結果です。(Nは、1,2,3..)という風な名前の命名規則が定義されています。従い、暗黙ラベル名は、Vendorによらず同じになります。複文でなければ、特にbegin-endで囲む必要もなくなりました。再帰インスタンス化が可能モジュールから、自分を呼び出してインスタンス化することができます。終了判定で、再帰は帰結することが必要ですし、トップモジュールを再帰インスタンス化することはできません。次の例は、再帰インスタンス化を行った例です。ソートの並列化アルゴリズムでバイトニックソートを実装しています。自分自身を呼び出しています。このモジュールは論理合成可能です。エラボレーションの順序エラボレーションとは、いろいろな意味があると思いますが、おおよそ、構文解析後で、コードジェネレートの前の工程、位の意味で捉えておけばよいと思います。generateを使う場合の問題は、defparamの相互使用です。2001ではこの部分が曖昧でしたが、明確に規定されました。トップモジュールからスタートします。下階層をエラボレートしていきます。このとき、generateは展開しないで、それ以外の部分をできる限り展開します。このとき出会ったparameter/defparamは、パラメータオーバライドや、defparamで置き換えられ最終値になります。言い換えると、defparamの評価時、解決されないSCOPEでは、defparamの置き換えが遅延されます。ブロックを評価します。このとき、2のステップが発生したら、2から、繰り返し、エラボレートするべきリストがなくなるまで2,3を繰り返します。mをエラボレートします。パラメータを評価します。pは、2から1に置き換えられます。m.n.mを生成します。defparamを評価します。スコープm.n.m.n.pは、解決しないので、保留にします。スコープm.n.m.nを生成します。pは、4のdefparamが利いて100に置き換えられます。階層外のparameterを置き換えることはできません。%m",p);endmodule結果です。HDLでは、次のようにイベント変数を宣言、->で明示的にトリガをかけることができました。->a;//トリガするendendmoduleここでの問題はraceがあるということです。raceの問題は、SVになっても常に付きまといます。スレッド挙動の正しい理解が必要となりますし、複雑な仕様と挙動から使いこなすのは難しいと思います。それを解説する前に、少し復習です。イベントドリブンの基本は、「変化」です。HDLでは、@で変化を捕まえました。するという文に出くわします。Aという構文は、上のように読むとよいと思います。A行に来て、初めて変化を待ちます。止まるので、ブロックするという訳です。unblockは、変化を受けてA行の次の行に移ることを表しています。この構文は、エッジトリガであることに注意してください。statements;という構文もあります。これは、基本的には、レベルセンシティブですが、次のように書き換えることもできます。@(expression);endstatement;//Q.E.D.つまり最初の構文がTrueだと@をスルーして次の構文に行ってしまう訳です。これを指して「ブロックされないで次の構文に行く」という言い方もできます。Falseと評価されると、やはり@(expression)でブロックされ、変化待ちになります。変化があると、whileの頭で、Trueかどうか判断するという風に動作します。最初に@(a);が実行されるとブロックされます。トリガがかかるとunblockされます。後に実行される可能性もあります。トリガがかかりません。実行される必要があります。HDLでは、時間をずらすか#0を追加します。->a;//トリガするendendmodule#0では、ブロックされますから、他のスレッドの方にシミュレータは、行きます。トリガ待ちの方が確実に先になります。#0の多用は、goto文の多用に似てスマートではありません。SVでは、これに対する改善策が盛り込まれています。ノンブロッキング領域でのトリガ->>は、ノンブロッキング領域でトリガします。具体的には、<=と同様に、ノンブロッキングキューにキューイングされます。全回路の=の代入動作や、プロパゲートが終わった後に、ノンブロッキングキューから取り出され、イベントがトリガされます。イベントの方が後になります。時刻Delayを指定することもOptionで可能なので、未来へのイベントも楽に作成できます。(このときもノンブロッキングキューに登録されます。2005\veritak_sv52\test\event_test2.v(1)::event_test2Verilogのシミュレーションの準備が完了しました。スタートは,Goボタンを押してください。期待通りに動きません。レベルセンシティブ動作が可能になります。trueを返すのは、トリガが伝播してからそのタイムスロット期間中までです。次の時刻に移る直前に内部でfalseにリセットされます。動作しますが、メソッド的な意味を込めてここでは、triggered()と記述しました。2005\veritak_sv52\test\event_test3.v(1)::event_test3Verilogのシミュレーションの準備が完了しました。スタートは,Goボタンを押してください。配列として定義することもできます。引数にすることもできます。functionの引数にすることはできません。endendmodule結果です。2005\veritak_sv52\test\event_test5.v(1)::event_test5Verilogのシミュレーションの準備が完了しました。スタートは,Goボタンを押してください。wait_orderイベントの順序が正しいかどうかを判定する構文です。action_blockになっています。2005\veritak_sv52\test\event_test9.sv(1)::event_test2Verilogのシミュレーションの準備が完了しました。スタートは,Goボタンを押してください。2005\veritak_sv52\test\event_test10.sv(1)::event_test2Verilogのシミュレーションの準備が完了しました。スタートは,Goボタンを押してください。項だけにすることもできます。変数について、いくつかの演算が定義されています。使うことはないと思います。bとaのマージになります。正確には、後で定義しますが、まずは、例で見ましょう。2005\veritak_sv52\test\event10.sv(1)::event_test11Verilogのシミュレーションの準備が完了しました。スタートは,Goボタンを押してください。;でbもトリガしていることが分かると思います。aもトリガされています。とも=の操作により、同じ内部オブジェクトを指すためです。同様に次も例でも、内部オブジェクトの参照の共有が行われます。//cをトリガすることは、a/bもトリガする。2005\veritak_sv52\test\event11.sv(1)::event11Verilogのシミュレーションの準備が完了しました。スタートは,Goボタンを押してください。****しかし、次の例は、上の例にDelayを追加変更しただけですが、全く別な動きになります。//cをトリガすることは、cをトリガする。endendendmodule結果は、予想を裏切るものです。2005\veritak_sv52\test\event12.sv(1)::event12Verilogのシミュレーションの準備が完了しました。スタートは,Goボタンを押してください。****代入される側のブロックされたプロセスに対しては、もはやマージは行われません。この例の場合は、aもbも、代入する側のcのオブジェクトを参照することになり上記の結果となります。演算が行われたのが違いです。対応するプロセスに対して独立にトリガを送ることはできなくなります。代入は、内部オブジェクトへの参照のクリアと解釈するとよいと思います。次の例では、nullが代入されたあとは、@はブロックされたままになり、アンブロックされることはありません。endendmodule結果です。null代入後はトリガされません。2005\veritak_sv52\test\event13.sv(1)::event13Verilogのシミュレーションの準備が完了しました。スタートは,Goボタンを押してください。****比較演算子も導入されています。(Veritakの実装では、内部オブジェクトへのポインタの比較としています。)最後の例は、いままでの説明の総合です。endendmodule結果です。2005\veritak_sv52\test\event14.sv(1)::event14Verilogのシミュレーションの準備が完了しました。スタートは,Goボタンを押してください。$bitsコンパイル時にビット幅を評価する関数です。下の例のように宣言済みのビット幅を求めることもできます。****$bitsは、2値/4値、パック/アンパック等には関係しません。ツールの実装にも依存しません。純粋にユーザが宣言したビット幅の集合の結果を返します。従ってメモリの配置実態とは異なることに注意してください。(DPIを駆使するとき、この理解は重要です。クラスオブジェクト指向の中核を担うのは、クラスです。SVのそれは、C++のクラスよりもJAVAのクラスに似ています。それでは、早速、SVのクラスの使い方と実装の裏側を見ていきましょう。最小のクラス下は、最小のクラスです。中身がないクラスです。クラスの宣言になります。//中身がないendclassクラスの中身には、色々入れることができます。内側や、外側のどちらでも置けます。型のAという変数を宣言しています。同じように"型"になります。ビルドインの型ですが、クラスは、自分型の型です。自分が定義したクラスの種類分だけ、型が増えることになります。呼び出し前にクラス名が定義されていることが必要です。"型"が定義されていないと構文解析の段階でエラーになります。'('構文解析でエラーがあります。オブジェクトそのものではなく、いわゆるハンドルになります。;と対比してみましょう。aは、integerで、32ビット4値分のメモリを実際に食いますから、aは、オブジェクト(実体)そのものです。型に対するポインタ(一般的には、ハンドルが必ずしもポインタにはならないのですが、Veritakの実装ではポインタにしているので、以降の説明ではポインタ(アドレスを保持する変数)という表現を使うことをお許しください。a=32'hx;endmoduleとなります。この場合、Aは、シミュレーション開始前に、コンパイラが暗黙にnullに初期化されます。32'bxに初期化されるのと同じフェーズで実行されます。実は、この段階では、どこにもありません。試しに次のソースでクラス内のメンバーのmにアクセスしてみましょう。endendmoduleこのソースでコンパイルは通ります。ポインタアクセスエラーです。0nsのように、A.m=0;のところで、実行時エラーになってしまいます。(この実行時エラーは、VerilogHDLでは殆どお目にかかったことがないと思います。VerilogHDLは、そのほとんどがSTATICなオブジェクトですので、エラーの検出もコンパイル時に可能だからです。)クラスは、ダイナミックなオブジェクトです。newのように、使用したいときにOSからメモリをもらってインスタンス化(実体化)する必要があります。SIMエンジンは、実体化した先のアドレスをハンドルAが保持していると考えます。A->mでアクセスしようとします。このときAに入っているアドレスは、まだ、Nullですので、アドレスNull(0)にオブジェクトが入っているように見えてしまいます。OSがこの不正なアクセスを検出する場合もあるでしょうし、SIMエンジンのNullポインタチェックで実行時エラーにする場合もあるでしょう。いずれにしても、このエラーは、コンパイル時ではなく、実行時に起きます。オブジェクトをメモリ上に確保してA変数にそのアドレスを入れてやらなくてはいけません。OSからメモリをもらってインスタンス化)します。endendmoduleこれで、初めてをAを介してmin_classの中身にアクセスできるようになります。クラスを書いて、アクセスできるようになるまで随分面倒なような気がしますね。mも実体化(メモリ上に占有する)ようになります。このクラスの場合、aは、4値1ビット、mは、2値32ビットのオブジェクトです。他にもシミュレータ内部の付帯情報も付加されるので、見かけのオブジェクトサイズ以上にメモリは消費します。A変数は、クラスオブジェクトサイズの影響は全く受けず,32ビット環境であれば、32ビットの生ポインタさえあれば事足ります。オブジェクトの大きさによらずオブジェクトのアドレスを保持すればよいだけですので、環境が決まれば常に一定のサイズ(Ex.32ビット=4バイト)だけ消費します。クラスへのアクセスは、このハンドルを介して行うことになります。2005\veritak_sv52\test\min_class4.v(7)::testVerilogのシミュレーションの準備が完了しました。スタートは,Goボタンを押してください。****まとめると、クラスは、ダイナミックなオブジェクト。生成するクラスにアクセスするには、ハンドルを介して行う。VerilogHDLは、いままでその殆どが、SIM開始から終わりまで生き続けるSTATICなオブジェクトです。これだけだと、テストベンチの生成がやりずらいので、ダイナミックなクラスが導入されました。生成は、ユーザの仕事ですが、消去に関しては、シミュレータがやってくれます。クラスの継承は、単一継承のみです。これは、JAVAと同じですね。継承の概念を見ていきましょう。継承することを宣言しています。endendmodule継承されたクラスのオブジェクトレイアウトは、次のようになっており、min_classのデータを全て含みます。min_classは、E_classの部分集合になっています。ハンドルEは、E_classオブジェクトの先頭アドレスを指しますが、これは、同時にmin_classオブジェクトの先頭アドレスでもあります。同様にして、E_classがさらに継承されたクラスのオブジェクトは、破線部のように、E_classを含むオブジェクトになるでしょう。クラスにすると、このようにして、いくらでも拡張できます。この性質は、クラスを部品と考えると都合がよいことになります。ある部品を共通に使い、自分仕様にカスタマイズしたい部分は、親クラスを継承して、自分仕様の部分だけ、データを追加すれば良いのです。このような元になるクラス方を親クラス(またはsuperクラス)、継承してできたクラスの方を派生クラス(子クラス、またはサブクラス)という言い方もされます。一般にクラスの継承イメージは下図の跳び箱のようになりますが、データのレイアウト(オブジェクトのメモリ配置)は、上のように、共通の先頭アドレスをポイントすることでアクセスできることに注意してください。定数オフセットでアクセスできます。このオブジェクトの先頭アドレスがいわゆるthisアドレスになります。(これは、Veritakの実装がそうなっているだけの話であってLRMが規定するところではありません。SVの場合は、単一継承のみであり、他のシミュレータでも同じように考えて差し支えないと思います。)上のコードでは、ハンドルは、E_classになっていますので、親クラスのデータにもアクセスできます。最初は、ちょっと奇異に見えますが、上の包含関係を頭に置くと理解できると思います。子クラスから親クラスへ代入できるオブジェクトとハンドルの関係をさらに詳しく見ましょう。親クラスのハンドルに子クラスのハンドルを代入できます。2005\veritak_sv52\test\min_class23.v(12)::testVerilogのシミュレーションの準備が完了しました。スタートは,Goボタンを押してください。****これは、下図のように理解できます。Eとbaseは、それぞれ同じオブジェクトアドレスを指しています。ハンドルbaseの方は、min_class領域だけにアクセスできるハンドルです。宣言が見つかりません。コンパイル時にエラーとなります。endendmoduleこれは、コンパイル時エラーになります。左辺のタイプは右辺に代入できません。仮に、もしこれを代入可能にしてしまうと、E.kのように、min_classには、存在していないオブジェクトにアクセスされる可能性があります。子クラスから親クラスへの代入は、常に包含関係を満足するために、Warningなしに正しい構文ですが、逆は、包含関係が成立しないので不可です。ダイナミックキャストC++のdynamic_castに相当する機能です。endendmodule$castで、変換可能な場合、ハンドルの値がコピーされます。この様子は、下図になります。base=E;でハンドルがコピーされます。同じオブジェクトアドレスを指すことに注意してください。代入は、親クラスのハンドルがコピーされるだけでオブジェクトそのものは、全く関係がなくなんら変化しません。この例の場合、元がE_classオブジェクトですので、問題なくキャストされ、ハンドルEと全く同じようにアクセスできます。****なお、C++のdynamic_castでは、例外が送出されることがありますが、SVでは、例外は勿論エラーにもなりません。functionとして使う場合は、$cast不成功は、0、成功は、0以外の値が返ります。if($cast(Ecopy,base))begin//キャストが成功の場合は、Ecopyに代入される。オブジェクトコンポジションクラス中のメンバーとしてクラスハンドルを持つ場合を考えます。endendmodule上のソースは、コンパイルは通りますが、RUNすると実行エラーが出ます。ポインタアクセスエラーです。インスタンス化されていないことです。クラスハンドルだけではインスタンス化されませんでしたね。次のように修正すれば、大丈夫です。endendmodule図にすると分かりやすいと思います。componentクラスを部品として含みかのように見えます。E_classオブジェクトメモリイメージで含まれるのは、componentクラスのハンドルであってオブジェクトそのものではありません。いうハンドルを使って参照されることになります。されることははなく、どちらもユーザの仕事になります。上の図のイメージを確認するために、少し追加してみます。****これも図にしてみると下図のようになります。クラス外のC1で計2つになります。Veritakの内部の参照カウントは、$RefCountで拾うことができ、確かに参照カウントは2になっています。この結果componetオブジェクト参照は、C1だけになります。(参照カウントは、1になります。参照がありますから、シミュレータはdeleteしません。このように、外部の参照が行われるとオブジェクトコンポジションは、その生存期間が個別に異なることになります。これも継承との違いですね。継承による方法クラスハンドルをメンバーに持つ方法(オブジェクトコンポジション)の2種類があります。コンストラクタ上のコードは、あまり美しくありません。行っているのはスマートではないです。後は、これらの初期化処理は、終わっているようにしたいですね。すれば、クラスのユーザは、内部の仕様の詳細を知らなくてもよいことになります。そのための構文がコンストラクタです。次のコードは、コンストラクタを使って書き直したものです。super.new;//親クラスのnew。いうfunctionは、特別で、インスタンス化したときに呼び出されます。この中で初期化処理を書いてしまえばスッキリします。注意するべきは、親クラスを呼ぶ場合です。中の一番目に記述しなければなりません。ないとコンパイルエラーとなります。記述しなくても暗黙的に呼び出されますので、superに渡す引数がなければ、特に記述する必要はありません。継承がある場合、このようにして、次々にsuperが呼び出されるので、最初に実行されるのは、一番ルートの基底クラスから実行されることになります。以降順番に子クラスに降りてきます。この構築順序は、オブジェクトを構築する上で必然的な順序です。後で仮想関数の話がでてきますが、実質的に、コンストラクタ中では、自分より子供の仮想関数を呼び出すことはできないことに注意してください。引数付のコンストラクタの例です。使うユーザから見ると、大分すっきりしました。コンストラクタは、functionであることに注意してください。functionの要請事項、イベントや、時間コントロールの構文は置けないことに注意してください。クラスの中に入れ込めると、もっとすっきりしそうですね。それが、メソッドになります。中のdispを少し書き換えてクラスに入れ込みました。簡単になっていることが分かると思います。その親クラスのmin_classにも定義されていますが、それぞれ別々なfunctionとして評価され、正当な構文です。付けたところが仮想関数になります。disp_my_dataという同名の関数が親クラスと子クラスで定義されています。これを親クラスのハンドルに代入すると、オブジェクトの型に応じて、functionが自動的に選びだされます。呼び出されなくなります。****あたかも、親クラスのハンドルを使ってオブジェクトそのものが、関数を選んでいるかのように見えます。通常の関数と異なるのは、コンパイル時に呼び出す関数が決定しているのではなく、実行時に、オブジェクトに対応した関数が呼び出されることです。コンパイラはどの関数が呼ばれるかは、コンパイル時、把握していません(決定できません。end仮想関数を使うには、同じ関数名、同じ引数の個数、同じ引数の型、同じビット幅であることが必要になります。一致しないと仮想関数とは認識されません。親クラスの性質で、残したいところはそのままに仕様を変えたいところはカスタマイズできるところが便利なのです。メモリ回収機構(ガーベジコレクション)LRMは、この辺について、全く規定していないのですが、シミュレータの実装では、参照カウントを使ったものが多いのではないかと思います。参照カウントによるメモリ回収は、ユーザ側は、大抵の場合は、気にしなくてよいのですが、まれに問題も起こりえます。VeritakSVでも参照カウントを使っているので、ここでは、参照カウントの仕組みを説明します。参照カウントの問題点Wikipediaが詳しいです。