UVMの環境構築第5回では、コレクターの定義(作成)方法について解説していきます。
なお、ソースコードはGitHub に公開しています。
シリーズ目次
UVMの環境構築!シリーズの目次は、第1回 解説編の一番下をご覧ください。
コレクターの概要
コレクターはuvm_component を継承して定義します。uvm_collectorというのはUVMでは定義されていません 。
UVMでは、モニター が
DUTのレスポンスをサンプリングし、 トランザクションに変換し、 他のコンポーネントに送信する
と定義されています。その他モニターでは、レスポンスのチェックやカバレッジ計算を行うため、非常に盛りだくさんです。
そこで、DUTのレスポンスのサンプリング→トランザクションに変換→他のコンポーネントに送信 を専門に行う コンポーネントとしてコレクター を定義し、モニターから分離するのが一般的となっています。
DUTのレスポンスの取得はvirtual interface を使用します。DUTのポートに接続したinterfaceと、コレクターに定義したvirtual interfaceはつながっています(やり方はmy_collector_base.sv で説明します)。したがって、DUTのポートの信号が変化すると、コレクターのvirtual interfaceの信号にも反映されます。
図1 UVMにおけるコレクター
コレクターの定義方法
コレクターを定義する際は、DUTによらずお決まりで書くことがあります 。したがって、次の手順で記述するとよいです。
uvm_componentを継承し、お決まりの部分だけ記述したベースクラスmy_collector_baseを定義 my_collector_baseを継承し、DUTに対応させたmy_collectorを定義
my_collector_base
それでは、ソースコードを見ながら、コレクターの定義方法について説明します。
my_collector_base.sv
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class my_collector_base # ( type TR = uvm_seqeuence_item) extends uvm_component;
VIF_COLLECTOR vif;
uvm_analysis_port # ( TR) analysis_port;
/* Constructor */
function new ( string name, uvm_component parent) ;
super .new ( name, parent) ;
analysis_port = new ( "analysis_port" , this ) ;
endfunction
/* Connect phase */
function void connect_phase ( uvm_phase phase) ;
super .connect_phase( phase) ;
if ( ! uvm_config_db # ( VIF_COLLECTOR) :: get( this , get_full_name( ) , "vif" , vif) )
`uvm_error( "VIF_NOT_SET" , { "No interface assigned to " , get_full_name( ) , ".vif" } ) ;
endfunction
/* Run phase */
task run_phase( uvm_phase phase) ;
collect_response( ) ;
endtask
/* Sub class must implement this behaviour */
virtual task collect_response( ) ; endtask
endclass
class my_collector_base #(type TR = uvm_seqeuence_item) extends uvm_component;
VIF_COLLECTOR vif;
uvm_analysis_port #(TR) analysis_port;
/* Constructor */
function new(string name, uvm_component parent);
super.new(name, parent);
analysis_port = new("analysis_port", this);
endfunction
/* Connect phase */
function void connect_phase (uvm_phase phase);
super.connect_phase(phase);
if (!uvm_config_db #(VIF_COLLECTOR)::get(this, get_full_name(), "vif", vif))
`uvm_error("VIF_NOT_SET", {"No interface assigned to ", get_full_name(), ".vif"});
endfunction
/* Run phase */
task run_phase(uvm_phase phase);
collect_response();
endtask
/* Sub class must implement this behaviour */
virtual task collect_response(); endtask
endclass
1行目 :uvm_componentを継承してmy_collector_baseを定義します。トランザクションのタイプをTRとして変更できるようにします。3行目 :virtual interfaceを定義します。pkg.svの中で、インターフェースのタイプVIF_COLLECTORを次のように定義しています。
1
typedef virtual my_if VIF_COLLECTOR;
typedef virtual my_if VIF_COLLECTOR;
4行目 :DUTのレスポンスを詰め込んだトランザクションを、他のコンポーネントに送信するためのAnalysis portを定義します。6~10行目 :コンストラクタを定義します。この中でanalysis_portのインスタンスを作成します。12~18行目 :Connect phaseを定義します。uvm_config_dbにより、virtual interfaceの設定をします。20~23行目 :Run phaseを定義します。DUTのレスポンスを収集するcollect_resopnse()を呼びます。collect_response()の中身は、サブクラスで実装する必要があります。
my_collector
my_collector_baseを継承して、DUTに対応したmy_collectorを定義します。
my_collector.sv
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class my_collector extends my_collector_base # ( my_item) ;
`uvm_component_utils( my_collector)
/* Constructor */
function new ( string name, uvm_component parent) ;
super .new ( name, parent) ;
endfunction
/* Collect response in Run phase */
task collect_response ( ) ;
my_item data;
data = my_item:: type_id:: create( "response" ) ;
forever begin
@ ( vif.cb) begin
if ( vif.start == 1'b1 ) begin
data.a_in = vif.a_in;
data.b_in = vif.b_in;
data.rst = vif.rst;
end
if ( vif.rst == 1'b0 ) begin /* Reset released */
if ( vif.valid == 1'b1 ) begin
data.mult_out = vif.mult_out;
analysis_port.write( data) ;
end
end
end
end
endtask
endclass
class my_collector extends my_collector_base #(my_item);
`uvm_component_utils(my_collector)
/* Constructor */
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
/* Collect response in Run phase */
task collect_response ();
my_item data;
data = my_item::type_id::create("response");
forever begin
@(vif.cb) begin
if (vif.start == 1'b1) begin
data.a_in = vif.a_in;
data.b_in = vif.b_in;
data.rst = vif.rst;
end
if (vif.rst == 1'b0) begin /* Reset released */
if (vif.valid == 1'b1) begin
data.mult_out = vif.mult_out;
analysis_port.write(data);
end
end
end
end
endtask
endclass
1行目 :my_collector_baseを継承して、my_collectorを定義します。トランザクションのタイプはmy_itemにしました。今回は、シーケンサー、ドライバー間で使っているトランザクションmy_itemにしましたが、テスト環境によっては、同じにする必要はありません。なお、my_itemは、my_item.sv で定義しています。3行目 :UVMマクロを書きます。5~8行目 :コンストラクタを定義します。10~33行目 :collect_response()の中身を実装します。クロッキングブロックvif.cbのイベント待ち解除のとき、vif.startが1であれば入力値を取り込み、vif.valid = 1であれば出力値を取り込みます。そして、入力値・出力値をトランザクションとしてanalysis_portから送信します。クロッキングブロックのイベント待ち解除は、DUTのレスポンスが確定した後に起こるので、安全にレスポンスをサンプリングできます。
まとめ
今回は、コレクターの定義方法について解説しました。シリーズを通してご覧いただけると、UVM検証環境が構築できるようになりますので、ぜひ他のコンポーネントの解説もご覧ください。
シリーズ目次はこちら