仮想インターフェース (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;
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を宣言しています。
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<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;
全体のソースは、以下です。
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) は、カウンタモジュールで、その機能は、
たったこれだけの機能ですが、この機能を検証するテストベンチを書いてみましょう。期待値と異なる場合は、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