プログラム (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も同様です。

-verilogHDLの仕様が曖昧ということでありません。また、"バグではなく仕様です"、と言った類の言い訳でもありません。明確に、意図して無規定を謳っています。実は、verilog HDL シミュレータが高速な理由がここにあります。


SVで違うのは、新設されたprogramとmodule間で、実行順序が確定的になることです。program(テストベンチ)内、または、module(デザイン)内のレースは、これまでと同じく、レースが存在する可能性については、変わりません。


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は、module と同様にインスタンス化することが出来ます。
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=0ns

program実行は、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

$exitとは

これをinitialの途中からでもできるようにしたのが、$exitです。$exitは、プログラム由来のスレッドからしか呼ぶことはできません。($exitをモジュールのinitial構文上で呼ぶことはできません。) 表面的な終了の仕方を見ると、$finishと区別がつかないと思います。


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の違い

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は、元々は、ハードウェアのモデリング用途です。

もしも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で動きます。)