UVMの環境構築第3回では、ドライバーの定義(作成)方法について解説していきます。
なお、ソースコードはGitHubに公開しています。
シリーズ目次
UVMの環境構築!シリーズの目次は、第1回 解説編の一番下をご覧ください。
ドライバーの概要
ドライバーはuvm_driverまたはそのサブクラスを継承して定義します。
ドライバーはシーケンサーからトランザクションを取得し、シグナルレベルに変換します。そして、DUTをドライブします。
DUTのドライブはvirtual interfaceを使用します。DUTのポートに接続したinterfaceと、ドライバーに定義したvirtual interfaceはつながっています(やり方はmy_driver_base.svで説明します)。したがって、ドライバーのvirtual interfaceの信号を変化させると、DUTのポートにも反映されます。
ドライバーの定義方法
ドライバーを定義する際は、DUTによらずお決まりで書くことがあります。したがって、次の手順で記述するとよいです。
- uvm_driverを継承し、お決まりの部分だけ記述したベースクラスmy_driver_baseを定義
- my_driver_baseを継承し、DUTに対応させたmy_driverを定義
また、シーケンサーからトランザクションを取得するTLMポートは、uvm_driverで以下のseq_item_portとして定義されているため、ユーザーが新たに定義する必要はありません。
1
2
3
4
5
6
| class uvm_driver #(type REQ=uvm_sequence_item, type RSP=REQ) extends uvm_component;
uvm_seq_item_pull_port #(REQ, RSP) seq_item_port;
...
...
endclass |
class uvm_driver #(type REQ=uvm_sequence_item, type RSP=REQ) extends uvm_component;
uvm_seq_item_pull_port #(REQ, RSP) seq_item_port;
...
...
endclass
my_driver_base
それでは、ソースコードを見ながら、ドライバーの定義方法について説明します。
my_driver_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
29
| virtual class my_driver_base #(type REQ = uvm_sequence_item, type RSP = REQ)
extends uvm_driver #(REQ, RSP);
VIF_DRIVER vif;
/* Constructor */
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
/* Build phase */
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if (!uvm_config_db #(VIF_DRIVER)::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);
forever begin
get_drive_item();
end
endtask
/* Sub class must implement this behaviour */
pure virtual task get_drive_item();
endclass |
virtual class my_driver_base #(type REQ = uvm_sequence_item, type RSP = REQ)
extends uvm_driver #(REQ, RSP);
VIF_DRIVER vif;
/* Constructor */
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
/* Build phase */
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if (!uvm_config_db #(VIF_DRIVER)::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);
forever begin
get_drive_item();
end
endtask
/* Sub class must implement this behaviour */
pure virtual task get_drive_item();
endclass
- 1~2行目:uvm_driverを継承して、my_driver_baseを定義します。REQはトランザクションのタイプです。RSPはシーケンサーに戻すトランザクションのタイプです。
- 4行目:virtual interfaceを定義します。pkg.svの中で、インターフェースのタイプVIF_DRIVERを次のように定義しています。
1
2
| /* Typedef */
typedef virtual my_if VIF_DRIVER; |
/* Typedef */
typedef virtual my_if VIF_DRIVER;
- 6~9行目:コンストラクタを定義します。
- 11~17行目:Build phaseを定義します。super.build_phase(phase)は必ず必要です。uvm_config_dbにより、virtual interfaceの設定をします。
- 19~24行目:Run phaseでは、シーケンサーからトランザクションを受け取り、DUTをドライブするメソッドget_drive_item()を無限に繰り返します。
- 26~27行目:my_driver_baseのサブクラス(my_driver)では、必ずget_drive_item()の動作を実装する必要があります。
my_driver_baseはDUTに依存しません。あらかじめmy_driver_baseを用意しておくことで、効率的にドライバーを定義することができます。
my_driver
my_driver_baseを継承して、DUTに対応したmy_driverを定義します。
my_driver.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_driver extends my_driver_base #(my_item);
`uvm_component_utils(my_driver)
/* Constructor */
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
/* Get transaction and drive DUT in Run phase */
task get_drive_item();
seq_item_port.get_next_item(req);
drive_item(req);
seq_item_port.item_done();
endtask
/* Drive DUT */
task drive_item(input REQ item);
@ (posedge vif.clk);
vif.rst <= item.rst;
@ (posedge vif.clk);
vif.a_in <= item.a_in;
vif.b_in <= item.b_in;
vif.start <= 1'b1;
@ (posedge vif.clk);
vif.start <= 1'b0;
for (int i = 0; i < `BW_A; i++)
@ (posedge vif.clk);
endtask
endclass |
class my_driver extends my_driver_base #(my_item);
`uvm_component_utils(my_driver)
/* Constructor */
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
/* Get transaction and drive DUT in Run phase */
task get_drive_item();
seq_item_port.get_next_item(req);
drive_item(req);
seq_item_port.item_done();
endtask
/* Drive DUT */
task drive_item(input REQ item);
@ (posedge vif.clk);
vif.rst <= item.rst;
@ (posedge vif.clk);
vif.a_in <= item.a_in;
vif.b_in <= item.b_in;
vif.start <= 1'b1;
@ (posedge vif.clk);
vif.start <= 1'b0;
for (int i = 0; i < `BW_A; i++)
@ (posedge vif.clk);
endtask
endclass
- 1行目:my_driver_baseを継承して、my_driverを定義します。トランザクションのタイプREQは、my_itemにします。my_itemは、my_item.svで定義しています。
- 2行目:UVMマクロを書きます。
- 3~6行目:コンストラクタを定義します。
- 7~12行目:get_drive_item()の動作を実装します。
- seq_item_port.get_next_item(req)で、シーケンサーからトランザクションを取得します。
- drive_item(req)で、DUTをドライブします。
- seq_item_port.item_done()で、トランザクションについての処理が完了したことを通知します。
- 13~26行目:virtual interfaceの信号を操作します。このvirtual interfaceはuvm_config_dbによって、DUTに接続されたinterfaceとつながっています。したがって、このメソッドでDUTがドライブされます。
まとめ
今回は、ドライバーの定義方法について解説しました。シリーズを通してご覧いただけると、UVM検証環境が構築できるようになりますので、ぜひ他のコンポーネントの解説もご覧ください。
シリーズ目次はこちら