プログラム (program)
概要
module は、基本的なビルディングブロックであり、そこにテストベンチとハードウェアの記述が混在したのが従来の記述スタイルでした。
SVでは、moduleは、ハードウェアの記述に使い、テストベンチは、program で記述するというのが、推奨される記述スタイルになります。
従来通り、テストベンチもmodule内にテストベンチを記述するスタイルも可能ですが、programの使用では、以下のメリットがあります。
典型的な 構造は、Top moduleの下に test bechがあり、テストされるハードウェア(Device Under Test)があるという形になります。
programは、moduleをインスタンス化することができないので、階層のトップは、これまで通り、moduleになります。
テストベンチ間ーデザイン間のレースフリー
レース(race)というのは、実行順序がシミュレータ間、あるいは、同じシミュレータでも、バージョン/最適化の有無等により、実行順序が変わってしまうものです。 最も単純な例としては、
module test; reg a,b; initial a = 0; initial b = a; initial begin #1; $display("Value a=%b Value of b=%b",a,b); end endmoduleこの例において、bの値は、シミュレータで違う可能性があります。1'bx を表示するものもあれば、1'b0になるものもありどちらもリーガルです。LRMは、module内外initialの実行順序を規定していません。これについてはSVも同様です。
下は、タイムスロットの概念図です。各時刻でのイベントは、時刻でソートされ各時刻に対するキューを持っています。各時刻については、さらに、タイムスロットと呼ばれる、イベントキューがあります。SVのLRMでは、実に17もの実行フェーズ(region)を規定していて、細かく動作や実行順が定められています。
-VeritakSVにおいても、若干の違いはあるのですが17regionを使用しています。 従来は数regionsしかありませんでしたので、パフォーマンスにヒットするのは避けがたいのですが、数百行以下の小規模ソースでない限り目立った変化はないと思います。
以下の図で、ActiveRegionSetと描かれたグレーの部分が、従来部で、ReActiveRegionSetと書かれた赤い部分がprogram用に新しく追加された部分になります。
ユーザは、この図を詳細に理解する必要はありません。従来も、ブロッキングアサインメント・ノンブロッキングアサインメント位しか、違いを意識することはなかったと思います。それらが、program内で書いたものと、module内で書いたものでは、別フェーズで実行される位の意味で捉えておけばよいと思います。
programa のインスタンス化
programは、モジュールと同様に記述することができます。
program prog1; initial $display("SVプログラムの世界へようこそ"); //always always_ff..は、不可 endprogramしかし、program内には、always 系のプロセスは、置くことができません。従い、initial でタスクを生成しながら、テストベンチを作るというのが、基本的なスタイルになると思います。
***** Veritak SV32 Engine Version 432 Build Dec 31 2012***** SVプログラムの世界へようこそ ---------- シミュレーションを終了します。time=0ns
program prog(input a); reg b; initial begin b=a; $display("program Value test.a=%b Value of b=%b",test.a,b); end endprogram module mod(input a); reg b; initial begin b=a; $display("module test.a=%b Value of b=%b",test.a,b); end endmodule module test; reg a; prog dut(a); mod mut(a); initial a = 0; endmodule
program-module間の実行順序
上では、インスタンス化したmoduleとprogramとで、同じ内容のinitialが記述されています。 module側では、上で説明した通り、bの値は、initialの実行順の影響を受け、シミュレータ毎に違う値を表示してしまう可能性があります。しかしながら、programa側では、シミュレータによらず、値は、b=0に表示されます。これは、タイムスロットの項で述べたように、moduleと、programでは、同じ時刻でも、実行順序が規定されている為です。
基本的には、module内を先に実行し、図でデザイン(Activeマイナーループ)の安定化後に(module内イベントが無くなった後に)program内に制御が移ります。program内では、従来とDualなregion(ReActiveマイナーループre-active,re-inactive,re-nba..)の安定化後、program内の結果がポートを通じて module内に伝播します。その結果、module内のActiveマイナーループに入って、最初からやり直す場合もあるでしょうし、また逆にイベントがなく次のタイムスロットに行く場合もあるでしょう。
***** Veritak SV32 Engine Version 432 Build Dec 31 2012***** module test.a=0 Value of b=0 program Value test.a=0 Value of b=0 ---------- シミュレーションを終了します。time=0nsprogram実行は、designのマイナーループ(Active Regions)後、ReActive Regionsで行われる
上のベンチで、28行目 a=0;をa<=0;と書き換えて実行してみます。すると結果は、シミュレータによらず確定的になり、
***** Veritak SV32 Engine Version 432 Build Dec 31 2012***** module test.a=x Value of b=x program Value test.a=0 Value of b=0
になります。a<=0;により、ノンブロッキング文の代入は、design領域(module側)全ステートメントの実行後に起こるので、b=a;時点では、いずれにしても 1'bxです。
一方、program内のinitialは、design(module側)のノンブロッキング代入後に実行されます。従って、このベンチは、レースフリーです。
program portの挙動(clockingブロックがない場合)
プログラム自体は、インスタンスを持つ事が出来ないので、プログラムは、常にmoduleからインスタンス化される側にあります。
このような場合の挙動を次の例で見てみます。
module top; logic r; wire dw1, dw2; initial begin r = 0; #10 r = 1;//Active end assign dw1 = r;//Active p p_i(dw2, dw1); always @(dw2) //Active $display("dw2 is %b %t", dw2,$time); endmodule program p(output pw2, input pw1); assign #1 pw2 = pw1;//ReActive initial forever begin @(pw1);//ReActive $display("pw1=%b %t",pw1,$time); end initial begin #100;//Reactive $exit();//ProgramDerivedThreadのみ可 end endprogram
5行目のrの変化は、8行目 dw1に Active Regionで伝わります。モジュール内のこの変化は、同時刻にプログラムポートに伝わります。Reactive Regionで pw1は、変化し、8行目pw2は、、#1後のReactive Regionで変化します。その変化は、@(pw1)でまず、伝わり、ポートを通じて モジュール側にも伝わります。同時刻のprogram内のイベントが尽きたので、もう一度メジャーループに戻り、Active Regionで、@(pw2)から次の行に行きます。
まとめとして、
program内信号(変数およびネット)は、すべてReactive Regionsでのみで動く program
netとして扱われます。一方、program外で宣言された信号は(module/interface/package/$unitも)すべて、design
nets になります。主にActive Regionsで扱われます。
階層アクセス
モジュール側からは、programに対し、階層的なアクセスはできません。一方、programの側からは、moduleは、可視であり、階層的なアクセスが可能です。
これは、テストベンチの側(program)が、デザインを制御下に置く、ということと、デザイン自体は、テストベンチの影響を受けないようにするという考え方が背景にあります。
デザイン側では、プログラム側からのアクセスされた場合は、Reactive Region で動きます。通常のアクセスでは、Active Region なので、Design Netは、二つのRegionで動く可能性があります。一方program 信号は、module側からの階層アクセスはありえないので、ReActive Regionでのみ動きます。
module top; reg r; task t1; r=0;//moduleから呼ばれたらActive Region/ programから呼ばれたらReActive Region #2;// moduleから呼ばれたらActive Region/ programから呼ばれたらReActive Region r<=1;//moduleから呼ばれたらNBA Region/ programから呼ばれたらReNBA Region endtask function f1; r=!r;//moduleから呼ばれたらActive Region/ programから呼ばれたらReActive Region endfunction initial begin t1; f1; end prg test_bench(); endmodule program prg; initial begin #10 top.t1; top.f1; end endprogram
programのmoduleの終了の仕方の違い
moduleだけしかない場合には、イベントが尽きるか$finishが呼ばれまで、無限に走りますが、programがある場合には、programの全initial文が末端に達すると終了します。
-programの方は、テストベンチを想定しており、全initial文が末端に達した=テスト終了ということでしょう。
次の例では、ループは、#1000まで走り続けますが、
module prog14; initial fork forever begin #10; $display("Hi ループ中です。"); end #1000 $finish; join_none initial #31; endmodule
同じソースで module をprogramにしただけのソースでは、#31で終了します。
***** Veritak SV32 Engine Version 433 Build Jan 3 2013***** Hi ループ中です。 Hi ループ中です。 Hi ループ中です。 ---------- シミュレーションを終了します。time=31ns(2行目のinitialは、fork-join_noneによりtime0で終了します。12行目のinitialは、#31で終了します。結果として、#31ですべてのinitialが終了するので、終了となります。)
(終了させる内部的な手順は、全initialが末端に達したら、program由来のすべてのスレッドを終了させて($exit)から、暗黙の$finishを呼んで終了させています。)
fork -join_non あるいは、fork-join_anyで生成してスレッドが走っている最中は、停止させたくない場合は、
次のように wait fork で全ての生成スレッドの終了を待ちます。
program prog14b; initial begin fork forever begin #10; $display("Hi ループ中です。"); end #1000 $finish; join_none wait fork;//このinitialで生成したすべてのスレッドの終了を待つ end initial #31; endprogram
... Hi ループ中です。 Hi ループ中です。 Hi ループ中です。 Hi ループ中です。 Hi ループ中です。 Hi ループ中です。 Info: $finishコマンドを実行します。time=1000ns
program prog16; initial fork forever begin #10; $display("Hi ループ中です。"); end #1000 $finish; join_none initial #31; initial #21 $exit(); endprogram
***** Veritak SV32 Engine Version 433 Build Jan 3 2013***** Hi ループ中です。 Hi ループ中です。 ---------- シミュレーションを終了します。time=21ns
programとmoduleの違いをまとめると以下のようになります。
用途 | タイムスロット内フェーズの呼称 | 階層可視 | インスタンス化 | 暗黙インスタンス化 | always 系プロセス | 終了 | $exit による終了 | |
module | design | Active Region | programを覗くことは不可 | 可能 | 可能 | 可能 | イベントが無くなるか$finish | 不可 |
program | test bench | ReActive Region | 自由にmodule内を覗くことができる。 | programは、program/module をインスタンス化できない | 可能 | 不可 | program内全initialが末端まで達した もしくは、$exit/$finish |
可能 |
alwaysは、元々は、ハードウェアのモデリング用途です。
もしもalways文を許したとすると、全initialの終了=テストの終了とすることはできず、自動終了の形がとれないことになります。テストベンチ=テストシナリオというシーケンシャル的なものと相容れないものかもしれません。(initial forever で機能的には、同等の記述ができます。)
暗黙のインスタンス化
moduleと同様に、programも暗黙のインスタンス化ができます。ただし、programは、他のprogramやmoduleをインスタンス化できないので、nested
moduleのような階層化は、できません。
module prog28; bit clk=0; int counter; always #10 clk=~clk; always_ff @(posedge clk) begin counter&le=counter+1; $display(" module: counter=%h %t",counter,$time); end program my_prog; initial fork forever begin @(posedge clk); $display("program counter=%h %t",counter,$time); end #40 ; join_any endprogram :my_prog endmodule : prog28
***** Veritak SV32 Engine Version 433 Build Jan 7 2013***** module: counter=00000000 10 program counter=00000001 10 module: counter=00000001 30 program counter=00000002 30 ---------- シミュレーションを終了します。time=40ns
上のプログラムでは、fork -join_anyで、二つのスレッドを生成しています。一つは、forever
ループで、もうひとつは、#40です。#40後、下のスレッドは、終了し、fork を起動した親スレッドに制御が移ります。するとそこは、initial
の末端 かつ唯一のプログラムスレッドなので、シミュレーション全体が終了します。
上の結果で、module側のカウンタ表示と program側のカウンタ表示が一つ違うことに注意してください。
(module側は、Active Region、program側は、ReActive Regionで動きます。)