仮想インターフェース (Virtual interface)

概要

名前からするとC++仮想関数の類と想起されますが、それとは全く関係ありません。

-もし私が名前をつけるとするなら、実行時インターフェースインスタンス参照/変数、というネーミングにするでしょう。 

ハードウェアは、生まれたり死んだりしない、コンパイル時に全てが決まる静的な(Static)世界です。

-それ故に論理合成可能とも言えます。

一方、オジェクト指向下の検証環境では、クラスを中心とする動的(Dynamic)な世界になります。これは、C++やJAVA等の、オブジェクト指向プログラミング言語の世界と同じです。これを、つなぐ唯一の橋が、Virtual Interfaceという構図になっています。




Verilog HDL下においては、テストベンチの側もStaticに作っていました。つまり、テストベンチの側を取り替えて別なコンフィグレーションで走らせたいときは、別なプロジェクトにして、`ifdef や、$value$plusargs等を使い、ソースを書き換えながら対応していました。逆に言うと、接続を変えるには、何らかのソースの書き換えが必要となっていました。この問題の本質は、コンパイル時に全ての接続が決まってしまうことにあります。もし、テストベンチとハードウェアを切り離し、テストベンチの側は、対象のハードウェアとは別に、独立した形で開発できれば、全てのコンフィグレーションを自由に接続して、一回のRUNでテストを網羅できることになります。加えて、オブジェクト指向で開発することによって、ハードウェアに依存しないあるいは、依存度が少ない検証部品を活用することができます。次回の別プロジェクトでは、再利用できる部分が多く、結果として、効率アップが期待できる..テストベンチの側をダイナミックにする要請は、こんな背景ではないかと思います。


ですから、一回限りのハードウェア検証で今後、検証の機会がない、ということであれば、UVM等の検証メソドロジーの上に検証環境を構築する必要はないかもしれません。新しいパラダイムで、検証環境を構築するのは、パラダイムの理解を含めて時間のかかることですから。


virtual interface の例- コンパイル時と実行時の違い

下14,15行目で、interfaceのインスタンス化を行っています。
このインスタンス名を、30,31行目で、virtual interface変数にコピーしています。このvirtual interface変数は、10行目で
宣言していますが、初期状態はnull です。interfaceのインスタンス名を代入して、初めて使えるようになります。

10行目の宣言は、interfaceを省略して、 virutal if1 v1,v2 だけでも可能です。ここで注意がいるのは、innterface型名、ここでは、if1 が必要になることです。これは、virtual が付かない宣言と決定的に違うので注意してください。つまり指定interface型だけが代入できるということです。

30、31行目では、実は、スコープIF1/IF2が指す内部オブジェクトのポインタが渡されます。moduleポートで、interfaceのインスタンスを渡していたのとの違いは何でしょうか?決定的に異なるのは、moduleポートは、静的な結合でありどのスコープが渡されたかは、コンパイル時に決定され実行時に変更することはできないことです。一方v1/v2は、コンパイル時は、null 状態で初期化されていて実行時に変更することが出来ます。

33行目で、これをさらにtaskに渡していますが、渡された側のtaskにとっては、もはや特定のインスタンスではないことに注意してください。一旦virtual interface変数にコピーされれば、後は、例えば、taskで、汎用的な処理を書くことが出来ます。


module top;

        bit clk=0;//CLOCK元
        always #10 clk=~clk;

        interface if1(input clk);
                int counter=0;
                bit enable=0;
                always @(posedge clk) begin
                        if (enable) counter<=counter+1;
                end
        endinterface:if1

        if1 IF1(clk);//interfaceのインスタンス化
        if1 IF2(clk);//interfaceのインスタンス化


        program ;//暗黙インスタンス化
                virtual interface if1 v1,v2;//初期状態はnull

                task automatic random_walk(virtual interface if1 V,int rep);//automatic上書き防止
                        repeat(rep) begin
                                #($urandom_range(7,1)*100);//program 領域なのでraceの心配無用
                                V.enable=~V.enable;//program 領域なのでraceの心配無用
                        end

                endtask

                initial begin
                        v1=IF1;//インスタンス名を代入 代入して初めて使える
                        v2=IF2;//インスタンス名を代入 代入して初めて使える

                        //シーケンシャル
                                random_walk(v1,10);
                                random_walk(v2,10);

                        //Go パラレル   
                        fork 
                                random_walk(v1,20);
                                random_walk(v2,10);
                        join
                        //implicit $finish call
                end                                     

        endprogram      


endmodule :top;





ここで、上記をクラスを使ったものに書き換えてみましょう。一旦、virtual interface変数にコピーしまえば、後は、それを参照するだけの処理が多いのでクラス生成時に引数として渡してしまうのが常套手段です。

module top;

        bit clk=0;//CLOCK元
        always #10 clk=~clk;

        interface if1(input clk);
                int counter=0;
                bit enable=0;
                always @(posedge clk) begin
                        if (enable) counter<=counter+1;
                end
        endinterface:if1

        if1 IF1(clk);//interfaceのインスタンス化
        if1 IF2(clk);//interfaceのインスタンス化


        program ;//暗黙インスタンス化

                
        
                test_driver td1,td2;//クラスハンドル宣言

                initial begin
                        td1=new(IF1);//クラス生成
                        td2=new(IF2);//クラス生成
                        $monitor("IF1.enable =%b IF2.enable=%b %t",IF1.enable,IF2.enable,$time);
        

                        //シーケンシャル
                                td1.random_walk(4);
                                td2.random_walk(4);

                        //Go パラレル   
                        fork 
                                td1.random_walk(3);
                                td2.random_walk(6);
                        join
                        //implicit $finish call
                end                                     

        endprogram      


endmodule :top
少しだけ、上よりも読み易くなりました。22行目で、クラスtest_driver のハンドル td1,td2を宣言しています。
クラス生成時、25/26行目で、インスタンス名を渡しています。

クラスの定義は、 下です。このクラスは、インターフェース型 if1を指定してはいますが、特定のインスタンスについての処理ではありません。
このようにして、ハードウェアとテストベンチの分離を行うのがSVスタイルです。
	class test_driver;
			virtual interface if1 v1;//初期状態はnull

			function new(virtual interface if1 v_);//class コンストラクタで初期化するのが常套
				v1=v_;
			endfunction
		

			task  random_walk(int rep);//class のtask/functionは、default automatic
				repeat(rep) begin
					#($urandom_range(7,1)*100);//program domain なのでraceの心配無用
					v1.enable=~v1.enable;//program domain なのでraceの心配無用
				end

			endtask

	endclass :test_driver
		
***** Veritak SV32 Engine Version 435 Build Feb  9 2013*****

IF1.enable =0 IF2.enable=0                    0
IF1.enable =1 IF2.enable=0                  500
IF1.enable =0 IF2.enable=0                  600
IF1.enable =1 IF2.enable=0                 1000
IF1.enable =0 IF2.enable=0                 1300
IF1.enable =0 IF2.enable=1                 1600
IF1.enable =0 IF2.enable=0                 2300
IF1.enable =0 IF2.enable=1                 2900
IF1.enable =0 IF2.enable=0                 3300
IF1.enable =1 IF2.enable=1                 3500
IF1.enable =0 IF2.enable=1                 3600
IF1.enable =0 IF2.enable=0                 3700
IF1.enable =1 IF2.enable=0                 3800
IF1.enable =1 IF2.enable=1                 4300
IF1.enable =1 IF2.enable=0                 5000
IF1.enable =1 IF2.enable=1                 5200

---------- シミュレーションを終了します。time=5700ns

上のinitial の部分は、さらにwrapper クラスを作ることで、少しだけ読み易く出来ます。

module top;

	bit clk=0;//CLOCK元
	always #10 clk=~clk;

	interface if1(input clk);
		int counter=0;
		bit enable=0;
		always @(posedge clk) begin
			if (enable) counter<=counter+1;
		end
	endinterface:if1

	if1 IF1(clk);//interfaceのインスタンス化
	if1 IF2(clk);//interfaceのインスタンス化


	program ;//暗黙インスタンス化

		
	
		test_driver_top tdp;//test bench top

		initial begin
			tdp=new(IF1,IF2);//テストベンチクラスのインスタンス化
			$monitor("IF1.enable =%b IF2.enable=%b %t",IF1.enable,IF2.enable,$time);
			tdp.seq_par_test();//テスト起動	
			//implicit $finish call
		end					

	endprogram	


endmodule :top;


テストベンチ全体をクラスにまとめています。

	class test_driver;
			virtual interface if1 v1;//初期状態はnull

			function new(virtual interface if1 v_);//class コンストラクタで初期化するのが常套
				v1=v_;
			
			endfunction
		

			task  random_walk(int rep);//class のtask/functionは、default automatic
			
                        repeat(rep) begin
					#($urandom_range(7,1)*100);//program domain なのでraceの心配無用
					v1.enable=~v1.enable;//program domain なのでraceの心配無用
				end

			endtask

	endclass :test_driver
		
	class test_driver_top;
			test_driver td1,td2;
			
			function new (virtual interface if1 v1,v2);
				 td1=new (v1);
				 td2=new (v2);
			endfunction

			task seq_test(int rep);//シーケンシャル
				td1.random_walk(rep);
				td2.random_walk(rep);
			endtask

			task parallel_test(int rep);
				fork 	//Go パラレル	
					td1.random_walk(rep);
					td2.random_walk(rep*2);
				join
			endtask 
	
			task seq_par_test;
				seq_test(3);
				parallel_test(3);
			endtask
	endclass :test_driver_top

上をもう少し、ジェネリックな形にしてみましょう。インターフェースのインスタンス数をNUM_OF_INSTANCESとして、
トップmoduleは、以下のようになります。SVらしい体裁になってきました。 

program側に立ってみると、クラスにテストは、押し付けて(委譲して)、クラスのインスタンス宣言化と生成、テストの起動だけを行っています。自分がテストの流れをコントロールすることはありません。

module top;
        parameter int NUM_OF_INSTANCES=3;

//clock
        bit clk=0;//CLOCK元
        always #10 clk=~clk;

//インターフェース
        interface if1(input clk);
                int counter=0;
                bit enable=0;
                always @(posedge clk) begin
                        if (enable) counter≤=counter+1;
                end
        endinterface:if1

        
        if1 IF_ARRAY[NUM_OF_INSTANCES] (clk);//インターフェースのインスタンス化



        program;//暗黙インスタンス化

                virtual interface if1 ifa[NUM_OF_INSTANCES];
                test_driver_top #(NUM_OF_INSTANCES)  tdp;//test bench top

                initial begin
                        
                        tdp=new(IF_ARRAY);//
                        tdp.seq_par_test();//テスト起動 
                        //implicit $finish call
                end                                     

        endprogram      


endmodule :top;



クラス側は、仕事をどんどん押し付けられる側です。仕様変更が来ることは、予想できるので、できるだけ汎用化しておいて、変更に耐えられるように構築しておこう、という考え方です。このため、クラスの中身は、抽象化の方向にあり読む側からみると難しくなるように感じられます。

クラスは、以下のようにまとめられます。


	class test_driver;
			virtual interface if1 v1;//初期状態はnull
			int my_num;

			function new(virtual interface if1 v_,int num);//class コンストラクタで初期化するのが常套
				v1=v_;
				my_num=num;
			endfunction
		

			task  random_walk(int rep);//class のtask/functionは、default automatic
			
                        repeat(rep) begin
					$display("my num=%1d: v1.enable=%b %m %d",my_num,v1.enable,$time);
					#($urandom_range(7,1)*100);//program domain なのでraceの心配無用
					v1.enable=~v1.enable;//program domain なのでraceの心配無用
				
				end

			endtask

	endclass :test_driver
		
	class  test_driver_top #(parameter int NUM_OF_INSTANCES=3);
		
			test_driver q[NUM_OF_INSTANCES];
			
			function new (virtual interface if1 ifa[NUM_OF_INSTANCES]);
				 foreach (ifa[i])  	q[i]=new (ifa[i],i);
			endfunction

			task seq_test(int rep);//シーケンシャル
				foreach (q[i]) begin
					q[i].random_walk(rep);
				end
			endtask

			task parallel_test(int rep);
				foreach (q[i]) begin
					fork 	//Go パラレル
						automatic int k=i;//Thrの同時起動(fork)定石パターン
						begin
							$display("Go parallel k=%d",k);
							q[k].random_walk(rep);
						end
					join_none
				end
				wait fork;//全Thr終了待ち
				$display("All threads finished!");
			endtask 
	
			task seq_par_test;
				seq_test(3);
				$display("\n");
				parallel_test(3);
			endtask
	endclass :test_driver_top


これで、階層トップのパラメータNUM_OF_INSTANCESを変えることで、インスタンス数によらず、テストが可能になりました。

ところで、まだトップレベルmoduleとインターフェース周りが整理されていません。インターフェースには、動作と信号を全て書いてしまっているので、次のように、動作のカウンタモジュールと信号インターフェースに分けます。

interface if1(input clk);//インターフェース
        int counter=0;
        bit enable=0;
        
endinterface:if1


module counter (interface c1);//カウンタモジュール
        always @(posedge c1.clk) begin
                if (c1.enable) c1.counter<=c1.counter+1;
        end

endmodule:counter

カウンタをアレー化する部分とprogramは、次のように書きました。


module counter_array #(parameter int NUM_OF_INSTANCES=3)  (interface IF_ARRAY[NUM_OF_INSTANCES]) ;

        for (genvar g=0;g&ltNUM_OF_INSTANCES;g++) counter c(IF_ARRAY[g]) ;//カウンタモジュールとインタフェースの接続

endmodule :counter_array

program automatic test #(parameter int NUM_OF_INSTANCES=3) (interface IF_ARRAY[NUM_OF_INSTANCES]) ;


        test_driver_top #(NUM_OF_INSTANCES)  tdp;//クラスハンドル宣言
        
        initial begin
                        
                tdp=new(IF_ARRAY);//インタフェースをテストベンチに渡す
                tdp.seq_par_test();//テスト起動 
                //implicit $finish call
        end                     
endprogram :test

トップレベルモジュールは、以下になります。大分すっきりしました。


module top;
	parameter int NUM_OF_INSTANCES=3;

//clock
	bit clk=0;//CLOCK元
	always #10 clk=~clk;

//インターフェース
	if1 IF_ARRAY[NUM_OF_INSTANCES] (clk);//インターフェースのインスタンス化

//カウンタ
	counter_array #(NUM_OF_INSTANCES) counter_objects(IF_ARRAY);//カウンタのインスタンス化

//テストベンチ
	test #(NUM_OF_INSTANCES) test_drivers(IF_ARRAY);
endmodule :top;


全体のソースは、以下です。

class test_driver;
        virtual interface if1 v1;//初期状態はnull
        int my_num;
                        

        function new(virtual interface if1 v_,int num);//class コンストラクタで初期化するのが常套
                v1=v_;
                my_num=num;
        endfunction
                

        task  random_walk(int rep);//class のtask/functionは、default automatic
                        
        repeat(rep) begin
                        $display("my num=%1d: v1.enable=%b %m %d",my_num,v1.enable,$time);
                        #($urandom_range(7,1)*100);//program domain なのでraceの心配無用
                        v1.enable=~v1.enable;//program domain なのでraceの心配無用
                end

        endtask

endclass :test_driver
                
class  test_driver_top #(parameter int NUM_OF_INSTANCES=3);
                
        test_driver q[NUM_OF_INSTANCES];
                        
        function new (virtual interface if1 ifa[NUM_OF_INSTANCES]);
                 foreach (ifa[i])       q[i]=new (ifa[i],i);
        endfunction

        task seq_test(int rep);//シーケンシャル
                foreach (q[i]) begin
                        q[i].random_walk(rep);
                end
        endtask

        task parallel_test(int rep);
                foreach (q[i]) begin
                        fork    //Go パラレル
                                automatic int k=i;//Thrの同時起動(fork)定石パターン
                                begin
                                        $display("Go parallel k=%d",k);
                                        q[k].random_walk(rep);
                                end
                        join_none
                end
                wait fork;//全Thr終了待ち
                $display("All threads finished!");
        endtask 
        
        task seq_par_test;
                seq_test(3);
                $display("\n");
                parallel_test(3);
        endtask

endclass :test_driver_top

interface if1(input clk);//インターフェース
        int counter=0;
        bit enable=0;
        
endinterface:if1


module counter (interface c1);//カウンタモジュール
        always @(posedge c1.clk) begin
                if (c1.enable) c1.counter<=c1.counter+1;
        end

endmodule:counter       


module counter_array #(parameter int NUM_OF_INSTANCES=3)  (interface IF_ARRAY[NUM_OF_INSTANCES]) ;

        for (genvar g=0;g < NUM_OF_INSTANCES;g++) counter c(IF_ARRAY[g]) ;//カウンタモジュールとインタフェースの接続

endmodule :counter_array

program automatic test #(parameter int NUM_OF_INSTANCES=3) (interface IF_ARRAY[NUM_OF_INSTANCES]) ;


        test_driver_top #(NUM_OF_INSTANCES)  tdp;//クラスハンドル宣言
        
        initial begin
                        
                tdp=new(IF_ARRAY);//インタフェースをテストベンチに渡す
                tdp.seq_par_test();//テスト起動 
                //implicit $finish call
        end                     
endprogram :test

module top;
        parameter int NUM_OF_INSTANCES=3;

//clock
        bit clk=0;//CLOCK元
        always #10 clk=~clk;

//インターフェース
        if1 IF_ARRAY[NUM_OF_INSTANCES] (clk);//インターフェースのインスタンス化

//カウンタ
        counter_array #(NUM_OF_INSTANCES) counter_objects(IF_ARRAY);//カウンタのインスタンス化

//テストベンチ
        test #(NUM_OF_INSTANCES) test_drivers(IF_ARRAY);
endmodule :top;

これを実行させると、

***** Veritak SV32 Engine Version 435 Build Feb 15 2013*****

my num=0: v1.enable=0 $unit.test_driver.random_walk                    0
my num=0: v1.enable=1 $unit.test_driver.random_walk                  300
my num=0: v1.enable=0 $unit.test_driver.random_walk                 1000
my num=1: v1.enable=0 $unit.test_driver.random_walk                 1400
my num=1: v1.enable=1 $unit.test_driver.random_walk                 1800
my num=1: v1.enable=0 $unit.test_driver.random_walk                 2100
my num=2: v1.enable=0 $unit.test_driver.random_walk                 2700
my num=2: v1.enable=1 $unit.test_driver.random_walk                 2800
my num=2: v1.enable=0 $unit.test_driver.random_walk                 3300


Go parallel k=          0
my num=0: v1.enable=1 $unit.test_driver.random_walk                 3800
Go parallel k=          1
my num=1: v1.enable=1 $unit.test_driver.random_walk                 3800
Go parallel k=          2
my num=2: v1.enable=1 $unit.test_driver.random_walk                 3800
my num=0: v1.enable=0 $unit.test_driver.random_walk                 4000
my num=1: v1.enable=0 $unit.test_driver.random_walk                 4000
my num=2: v1.enable=0 $unit.test_driver.random_walk                 4100
my num=1: v1.enable=1 $unit.test_driver.random_walk                 4100
my num=0: v1.enable=1 $unit.test_driver.random_walk                 4200
my num=2: v1.enable=1 $unit.test_driver.random_walk                 4600
All threads finished!

---------- シミュレーションを終了します。time=4800ns

しかし、未だインターフェースの部分が合成可能ではありません。これを合成可能とするために、リセット信号を追加します。

interface if1(input clk);//インターフェース
	int counter;
	logic enable;
	logic sync_reset;
	
endinterface:if1


module counter (interface c1);//カウンタモジュール
	always @(posedge c1.clk) begin
		if (c1.sync_reset) c1.counter <=0;
		else if (c1.enable) c1.counter <=c1.counter+1;
	end

endmodule:counter	

テストベンチクラスに制御信号をリセットするtaskを追加します。


        task reset(int rep);
                foreach (q[i])  begin
                        fork    //Go パラレル
                                automatic int k=i;//Thrの同時起動(fork)定石パターン
                                begin
                                        q[k].reset(rep);
                                end
                        join_none
                end
                wait fork;//全Thr終了待ち
                $display("Reset Done! %t",$time);
        endtask



インターフェースをドライブする直接のtaskは、


	task reset (int rep);
		v1.sync_reset=1;//コントロールを初期化
		v1.enable=1;
		$display("my num=%1d: v1.sync_reset=%b %m %d",my_num,v1.sync_reset,$time);
		repeat (rep) begin
			#(10*rep);
		end
		v1.sync_reset=0;


	endtask

program ドメンインなので、その必要はないですが、インターフェース中の信号は、automatic 属性ではないので、ノンブロッキング文が可能です。

-virtual interfaceを通して見る信号は、static 属性になります。なお、LRM2012で、クラスメンバーに対するノンブロッキング禁止の制限はなくなりました。しかしながら、真にダイナミックなオブジェクトdynamic arrayや、queueは、依然として禁止です。

	task reset (int rep);
		v1.sync_reset <=1;//コントロールを初期化
		v1.enable <=1;
		$display("my num=%1d: v1.sync_reset=%b %m %d",my_num,v1.sync_reset,$time);
		repeat (rep) begin
			#(10*rep);
		end
		v1.sync_reset <=0;


	endtask

と書いてもOKです。核になるハードウェアの制御信号を追加したのですが、変更した部分は、これが殆どで、煩わしいポート接続の記述変更は全く必要ありません。

-VerilogHDLでは、論理階層の深い部分の変更は、ポート接続変更という仕事が発生していました。

また、counter という信号を 次のようにint(32bit) からbyte(8bit)にしても、
 

 byte counter;

変更部は、そこだけで済みポートビット幅の記述変更は不要です。

また、テストベンチも、機械的にタスクを追加する作業だけで済みました。全体ソースと結果は、以下です。


class test_driver;
	virtual interface if1 v1;//初期状態はnull
	int my_num;
			

	function new(virtual interface if1 v_,int num);//class コンストラクタで初期化するのが常套
		v1=v_;
		my_num=num;
	endfunction
		

	task  random_walk(int rep);//class のtask/functionは、default automatic
			
      	repeat(rep) begin
			$display("my num=%1d: v1.enable=%b %m %d",my_num,v1.enable,$time);
			#($urandom_range(7,1)*100);//program domain なのでraceの心配無用
			v1.enable=~v1.enable;//program domain なのでraceの心配無用
		end

	endtask

	task reset (int rep);
		v1.sync_reset=1;//コントロールを初期化
		v1.enable=1;
		$display("my num=%1d: v1.sync_reset=%b %m %d",my_num,v1.sync_reset,$time);
		repeat (rep) begin
			#(10*rep);
		end
		v1.sync_reset=0;


	endtask

endclass :test_driver
		
class  test_driver_top #(parameter int NUM_OF_INSTANCES=3);
		
	test_driver q[NUM_OF_INSTANCES];
			
	function new (virtual interface if1 ifa[NUM_OF_INSTANCES]);
		 foreach (ifa[i])  	q[i]=new (ifa[i],i);
	endfunction

	task seq_test(int rep);//シーケンシャル
		foreach (q[i]) begin
			q[i].random_walk(rep);
		end
	endtask

	task parallel_test(int rep);
		foreach (q[i]) begin
			fork 	//Go パラレル
				automatic int k=i;//Thrの同時起動(fork)定石パターン
				begin
					$display("Go parallel k=%d",k);
					q[k].random_walk(rep);
				end
			join_none
		end
		wait fork;//全Thr終了待ち
		$display("All threads finished!");
	endtask 

	task reset(int rep);
		foreach (q[i])  begin
			fork 	//Go パラレル
				automatic int k=i;//Thrの同時起動(fork)定石パターン
				begin
					q[k].reset(rep);
				end
			join_none
		end
		wait fork;//全Thr終了待ち
		$display("Reset Done! %t",$time);
	endtask
	
	task seq_par_test;
		reset(3);//カウンタをリセット
		seq_test(3);//シーケンシャルテスト
		$display("\n");
		parallel_test(3);//パラレルテスト
	endtask

endclass :test_driver_top

interface if1(input clk);//インターフェース
	int counter;
	logic enable;
	logic sync_reset;
	
endinterface:if1


module counter (interface c1);//カウンタモジュール
	always @(posedge c1.clk) begin
		if (c1.sync_reset) c1.counter <=0;
		else if (c1.enable) c1.counter <=c1.counter+1;
	end

endmodule:counter	


module counter_array #(parameter int NUM_OF_INSTANCES=3)  (interface IF_ARRAY[NUM_OF_INSTANCES]) ;

	for (genvar g=0;g < NUM_OF_INSTANCES;g++) counter c(IF_ARRAY[g]) ;//カウンタモジュールとインタフェースの接続

endmodule :counter_array

program automatic test #(parameter int NUM_OF_INSTANCES=3) (interface IF_ARRAY[NUM_OF_INSTANCES]) ;


	test_driver_top #(NUM_OF_INSTANCES)  tdp;//クラスハンドル宣言
	
	initial begin
			
		tdp=new(IF_ARRAY);//インタフェースをテストベンチに渡す
		tdp.seq_par_test();//テスト起動	
		//implicit $finish call
	end			
endprogram :test

module top;
	parameter int NUM_OF_INSTANCES=3;

//clock
	logic clk=0;//CLOCK元
	always #10 clk=~clk;

//インターフェース
	if1 IF_ARRAY[NUM_OF_INSTANCES] (clk);//インターフェースのインスタンス化

//カウンタ
	counter_array #(NUM_OF_INSTANCES) counter_objects(IF_ARRAY);//カウンタのインスタンス化

//テストベンチ
	test #(NUM_OF_INSTANCES) test_drivers(IF_ARRAY);
endmodule :top;



***** Veritak SV32 Engine Version 435 Build Feb 15 2013*****

my num=0: v1.sync_reset=1 $unit.test_driver.reset                    0
my num=1: v1.sync_reset=1 $unit.test_driver.reset                    0
my num=2: v1.sync_reset=1 $unit.test_driver.reset                    0
Reset Done!                   90
my num=0: v1.enable=1 $unit.test_driver.random_walk                   90
my num=0: v1.enable=0 $unit.test_driver.random_walk                  390
my num=0: v1.enable=1 $unit.test_driver.random_walk                 1090
my num=1: v1.enable=1 $unit.test_driver.random_walk                 1490
my num=1: v1.enable=0 $unit.test_driver.random_walk                 1890
my num=1: v1.enable=1 $unit.test_driver.random_walk                 2190
my num=2: v1.enable=1 $unit.test_driver.random_walk                 2790
my num=2: v1.enable=0 $unit.test_driver.random_walk                 2890
my num=2: v1.enable=1 $unit.test_driver.random_walk                 3390


Go parallel k=          0
my num=0: v1.enable=0 $unit.test_driver.random_walk                 3890
Go parallel k=          1
my num=1: v1.enable=0 $unit.test_driver.random_walk                 3890
Go parallel k=          2
my num=2: v1.enable=0 $unit.test_driver.random_walk                 3890
my num=0: v1.enable=1 $unit.test_driver.random_walk                 4090
my num=1: v1.enable=1 $unit.test_driver.random_walk                 4190
my num=0: v1.enable=0 $unit.test_driver.random_walk                 4190
my num=2: v1.enable=1 $unit.test_driver.random_walk                 4490
my num=1: v1.enable=0 $unit.test_driver.random_walk                 4690
my num=2: v1.enable=0 $unit.test_driver.random_walk                 4990
All threads finished!

---------- シミュレーションを終了します。time=5190ns

これで、クラスへの移植作業は終わりました。テストベンチをクラスにすると、仮想メソッドが使えます。また、SVクラスだけ導入されている変数のランダム化という技も使えるようになります。 仮想メソッドの使用は、差分プログラミング、変数のランダム化は、セルフチェックテストベンチの開発において威力を発揮します。

Checkerを追加する

ここでは、簡単にカウンタの機能をチェックするテストベンチ(checker)を追加してみましょう。

検証対象(Device Under Test) は、カウンタモジュールで、その機能は、

  1. sync_reset (High) でリセットされcounter(値)が0にリセットされる
  2. enable がアサートされるとカウントアップ、デアサートでホールド

たったこれだけの機能ですが、この機能を検証するテストベンチを書いてみましょう。期待値と異なる場合は、Failを$displayするものとします。

追加したクラスは、次のようになります。

class test_checker;
        virtual interface if1 v1;//初期状態はnull
        int my_num;
        int counter;
        time last_time;                 

        function new(virtual interface if1 v_,int num);//class コンストラクタで初期化するのが常套
                v1=v_;
                my_num=num;
                
        endfunction
                
        task check_counter_by_clk;
                forever begin
                        @(posedge v1.clk);
                        if (v1.sync_reset)      begin
                                counter=0;
                        end else if (v1.enable) begin
                                counter=counter+1;
                        end
                        if (counter !==v1.counter) $display("Fatal Error Expected %x Got %x my_num=%d %t",counter,v1.counter,my_num,$time);
                end
        endtask





        task  check();
                        
                        check_counter_by_clk();//forever
                
        endtask



endclass :test_checker
                
class  test_checker_top #(parameter int NUM_OF_INSTANCES=3);
                
        test_checker q[NUM_OF_INSTANCES];
                        
        function new (virtual interface if1 ifa[NUM_OF_INSTANCES]);
                 foreach (ifa[i])       q[i]=new (ifa[i],i);
        endfunction



        task check();

                
                        foreach (q[i]) begin
                                fork    //Go パラレル
                                        automatic int k=i;//Thrの同時起動(fork)定石パターン
                                        begin
                                                q[k].check();
                                        end
                                join_none
                        end
                        wait fork;//全Thr終了待ち
        
        endtask 


endclass :test_checker_top


追加変更した programは、次のようになります。


program automatic test #(parameter int NUM_OF_INSTANCES=3) (interface IF_ARRAY[NUM_OF_INSTANCES]) ;


	test_driver_top #(NUM_OF_INSTANCES)  tdp;//クラスハンドル宣言
	test_checker_top #(NUM_OF_INSTANCES)  tcp;//クラスハンドル宣言
	initial begin
			
		tdp=new(IF_ARRAY);//インタフェースをテストベンチに渡す
		tcp=new(IF_ARRAY);//インタフェースをテストベンチに渡す
		
		fork 
			tcp.check();//checker を駆動 無限ループ
			tdp.seq_par_test();//driverを起動
			begin //タイムアウト
				#10000;
				$display("Time Out! ");
			end
		join_any	
		//implicit $finish call
	end			
endprogram :test

12,13,14行が並列プロセスで、checkerは、無限ループ、ドライバは、テストが終わったら返ってきますが、何らかのトラブルで処理が滞っているとタイムアウトのスレッドが先に終了して、タイムアウトで終了します。join_anyにより、一つでも子スレッドが終わるとinitialの最後まで行くので、programの終了=全体の処理終了となります。

このように、クラスは、テストベンチの部品となります。部品を差し替えるように、クラスを交換すれば、簡単に別なシナリオで動かすこと可能になります。

さらにジェネリックにする

counter の型をトップで指定できるようにしてみました。下のソース206行目で、指定しています。

class test_driver #(parameter type ctype=int);
	virtual interface if1 #(ctype) v1;//初期状態はnull
	int my_num;
	int counter;			



	function new(virtual interface if1 #(ctype) v_,int num);//class コンストラクタで初期化するのが常套
		v1=v_;
		my_num=num;
	endfunction
		

	task  random_walk(int rep);//class のtask/functionは、default automatic
			
      	repeat(rep) begin
			$display("my num=%1d: v1.enable=%b %m %d",my_num,v1.enable,$time);
			#($urandom_range(7,1)*100);//program domain なのでraceの心配無用
			v1.enable=~v1.enable;//program domain なのでraceの心配無用
		end
		
	endtask

	task reset (int rep);
		v1.sync_reset=1;//コントロールを初期化
		v1.enable=1;
		$display("my num=%1d: v1.sync_reset=%b %m %d",my_num,v1.sync_reset,$time);
		repeat (rep) begin
			@(negedge   v1.clk);//program内レースの回避
		end
		v1.sync_reset=0;
		$display("reset done my_num=%d %t",my_num,$time);
	
	endtask

endclass :test_driver
		
class  test_driver_top #(parameter int NUM_OF_INSTANCES=3,type ctype=int);
		
	test_driver #(ctype) q[NUM_OF_INSTANCES];
			
	function new (virtual interface if1 ifa[NUM_OF_INSTANCES]);
		 foreach (ifa[i])  	q[i]=new (ifa[i],i);
	endfunction

	task seq_test(int rep);//シーケンシャル
		foreach (q[i]) begin
			q[i].random_walk(rep);
		end
	endtask

	task parallel_test(int rep);
		foreach (q[i]) begin
			fork 	//Go パラレル
				automatic int k=i;//Thrの同時起動(fork)定石パターン
				begin
					$display("Go parallel k=%d",k);
					q[k].random_walk(rep);
				end
			join_none
		end
		wait fork;//全Thr終了待ち
		$display("All threads finished!");
	endtask 

	task reset(int rep);
		foreach (q[i])  begin
			fork 	//Go パラレル
				automatic int k=i;//Thrの同時起動(fork)定石パターン
				begin
					q[k].reset(rep);
				end
			join_none
		end
		wait fork;//全Thr終了待ち
		$display("Reset Done! %t",$time);
	endtask
	
	task seq_par_test;
		reset(3);//カウンタをリセット
		seq_test(3);//シーケンシャルテスト
		$display("\n");
		parallel_test(3);//パラレルテスト
	endtask

endclass :test_driver_top

class test_checker #(parameter type ctype =int);
	virtual interface if1 #(ctype) v1;//初期状態はnull
	int my_num;
	ctype counter;
	time last_time;			

	function new(virtual interface if1 #(ctype) v_,int num);//class コンストラクタで初期化するのが常套
		v1=v_;
		my_num=num;
			$display("$bits counter=%d",$bits(counter));
	endfunction
		
	task check_counter_by_clk;
		forever begin
			@(posedge v1.clk);
			if (v1.sync_reset)	begin
				counter=0;
 			end else if (v1.enable) begin
				counter=counter+1;
			end
			if (counter !==v1.counter) $display("Fatal Error Expected %x Got %x my_num=%d %t",counter,v1.counter,my_num,$time);
		end
	endtask





	task  check();
			
			check_counter_by_clk();//forever
		
	endtask



endclass :test_checker
		
class  test_checker_top #(parameter int NUM_OF_INSTANCES=3,type ctype=int);
		
	test_checker #(ctype) q[NUM_OF_INSTANCES];
			
	function new (virtual interface if1 ifa[NUM_OF_INSTANCES]);
		$display("$bits=%d",$bits(ctype));
		 foreach (ifa[i])  	q[i]=new (ifa[i],i);
	endfunction



	task check();

		
			foreach (q[i]) begin
				fork 	//Go パラレル
					automatic int k=i;//Thrの同時起動(fork)定石パターン
					begin
						q[k].check();
					end
				join_none
			end
			wait fork;//全Thr終了待ち
	
	endtask 


endclass :test_checker_top

interface if1 #(parameter type ctype=int) (input clk);//インターフェース
	ctype counter;
	logic enable;
	logic sync_reset;
	

endinterface:if1


module counter (interface c1);//カウンタモジュール
	always @(posedge c1.clk) begin

		if (c1.sync_reset)	c1.counter <=0;
 		else if (c1.enable) c1.counter <=c1.counter+1;
	end

endmodule:counter	


module counter_array #(parameter int NUM_OF_INSTANCES=3)  (interface IF_ARRAY[NUM_OF_INSTANCES]) ;

	for (genvar g=0;g <NUM_OF_INSTANCES;g++) counter c(IF_ARRAY[g]) ;//カウンタモジュールとインタフェースの接続

endmodule :counter_array



program automatic test #(parameter int NUM_OF_INSTANCES=3 ,type ctype=int) (interface IF_ARRAY[NUM_OF_INSTANCES]) ;


	test_driver_top #(NUM_OF_INSTANCES,ctype)  tdp;//クラスハンドル宣言
	test_checker_top #(NUM_OF_INSTANCES,ctype)  tcp;//クラスハンドル宣言
	initial begin
		$display("$bits=%d",$bits(ctype));	
		tdp=new(IF_ARRAY);//インタフェースをテストベンチに渡す
		tcp=new(IF_ARRAY);//インタフェースをテストベンチに渡す
		
		fork 
			tcp.check();//checker を駆動 無限ループ
			tdp.seq_par_test();//driverを起動
			begin //タイムアウト
				#10000;
				$display("Time Out! ");
			end
		join_any	
		//implicit $finish call
	end			
endprogram :test

module top;
	parameter int NUM_OF_INSTANCES=3;
	typedef logic [127:0]   	ctype;


//clock
	logic clk=0;//CLOCK元
	always #10 clk=~clk;

//インターフェース
		if1 #(ctype) IF_ARRAY[NUM_OF_INSTANCES]   (clk);//インターフェースのインスタンス化

//カウンタ
	counter_array #(NUM_OF_INSTANCES) counter_objects(IF_ARRAY);//カウンタのインスタンス化

//テストベンチ
		test #(NUM_OF_INSTANCES,ctype) test_drivers(IF_ARRAY);



	
endmodule :top;



***** Veritak SV32 Engine Version 435 Build Feb 19 2013*****

$bits=        128
$bits=        128
$bits counter=        128
$bits counter=        128
$bits counter=        128
my num=0: v1.sync_reset=1 $unit.test_driver.reset                    0
my num=1: v1.sync_reset=1 $unit.test_driver.reset                    0
my num=2: v1.sync_reset=1 $unit.test_driver.reset                    0
reset done my_num=          0                   60
reset done my_num=          1                   60
reset done my_num=          2                   60
Reset Done!                   60
my num=0: v1.enable=1 $unit.test_driver.random_walk                   60
my num=0: v1.enable=0 $unit.test_driver.random_walk                  360
my num=0: v1.enable=1 $unit.test_driver.random_walk                 1060
my num=1: v1.enable=1 $unit.test_driver.random_walk                 1460
my num=1: v1.enable=0 $unit.test_driver.random_walk                 1860
my num=1: v1.enable=1 $unit.test_driver.random_walk                 2160
my num=2: v1.enable=1 $unit.test_driver.random_walk                 2760
my num=2: v1.enable=0 $unit.test_driver.random_walk                 2860
my num=2: v1.enable=1 $unit.test_driver.random_walk                 3360


Go parallel k=          0
my num=0: v1.enable=0 $unit.test_driver.random_walk                 3860
Go parallel k=          1
my num=1: v1.enable=0 $unit.test_driver.random_walk                 3860
Go parallel k=          2
my num=2: v1.enable=0 $unit.test_driver.random_walk                 3860
my num=1: v1.enable=1 $unit.test_driver.random_walk                 4060
my num=2: v1.enable=1 $unit.test_driver.random_walk                 4360
my num=0: v1.enable=1 $unit.test_driver.random_walk                 4460
my num=1: v1.enable=0 $unit.test_driver.random_walk                 4660
my num=0: v1.enable=0 $unit.test_driver.random_walk                 4660
my num=2: v1.enable=0 $unit.test_driver.random_walk                 5060
All threads finished!

---------- シミュレーションを終了します。time=5360ns