##はじめに
前回はuvm_driverクラスを継承し、モデルのインスタンスパスから直接taskをコールするなんちゃってUVMドライバを作成しました。
今回は、APBマスターモデルをuvm_driverクラスを継承したクラス化に変えようと思います。
前回のコードは、uvm_testとuvm_driverをごちゃ混ぜにしたコードになってしまいました。ここでは、 uvm_testはテストシナリオ、uvm_driverは検証対象(DUT)とのインターフェイス と、クラスの役割を分離します(当たり前だ)。
##その前に!
UVMに関するいろんな資料を眺めていると、検証道具一覧紹介みたいな印象を持ち、いつもお腹いっぱいで常に敬遠し続けてきました。
よって、今回はトラディショナルなピンレベルの検証環境から小刻みにUVMライクに抽象化していく方向で進んでいます。
ほとんど我流で進めていますんで、公開したコードの中でもっとシンプルに書けるよ的なご指摘があると助かります。
##コード
###共通コード、interface、DUT
前回と同じです。
###テスト
マスターモデルが消えて、マスターモデルのメソッドはuvm_driverクラスを継承したapb_drvに押し込みました。class部分の詳細は後述します。
module tb();
apb_t apb_bus();
apb_slave #(.OFFSET(32'h0001_0000)) dut (.apb_slv(apb_bus), .*);
/*****************Deleted*******************
abp_master tu(.apb_mst(apb_bus), .*);
*****************Deleted*******************/
task clk_gen();
apb_bus.clk = 0;
forever
#5 apb_bus.clk = ~ apb_bus.clk;
endtask
class apb_drv extends uvm_driver;
`uvm_component_utils(apb_drv)
virtual apb_t v_apb_bus;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if(! uvm_config_db#(virtual apb_t)::get(this, "", "v_apb_bus", v_apb_bus))
`uvm_fatal("VIF instance error", {"virtual interface cannot get", get_full_name(), ".v_apb_bus"} );
endfunction
virtual task reset_phase(uvm_phase phase);
phase.raise_objection(this);
`uvm_info("apb_drv", "Reset Start", UVM_MEDIUM)
this.init();
v_apb_bus.rst_n = '0;
repeat(5) @(posedge v_apb_bus.clk);
v_apb_bus.rst_n = '1;
repeat(5) @(posedge v_apb_bus.clk);
`uvm_info("apb_drv", "Reset End", UVM_MEDIUM)
phase.drop_objection(this);
endtask
virtual task init();
v_apb_bus.addr = '0;
v_apb_bus.sel = '0;
v_apb_bus.enable = '0;
v_apb_bus.write = '0;
v_apb_bus.wdata = 'z;
endtask
virtual task do_write(int addr_i, data_t wdata_i);
@(posedge v_apb_bus.clk);
v_apb_bus.addr <= #1 addr_i;
v_apb_bus.sel <= #1 1'b1;
v_apb_bus.enable <= #1 1'b0;
v_apb_bus.write <= #1 1'b1;
v_apb_bus.wdata <= #1 wdata_i;
@(posedge v_apb_bus.clk)
v_apb_bus.enable <= #1 1'b1;
@(posedge v_apb_bus.clk)
v_apb_bus.addr <= #1 '0;
v_apb_bus.sel <= #1 '0;
v_apb_bus.enable <= #1 '0;
v_apb_bus.write <= #1 '0;
v_apb_bus.wdata <= #1 'z;
endtask
virtual task do_read(int addr_i, output data_t rdata_o);
@(posedge v_apb_bus.clk)
v_apb_bus.addr <= #1 addr_i;
v_apb_bus.sel <= #1 1'b1;
v_apb_bus.enable <= #1 1'b0;
v_apb_bus.write <= #1 1'b0;
@(posedge v_apb_bus.clk)
v_apb_bus.enable <= #1 1'b1;
@(posedge v_apb_bus.clk)
rdata_o = v_apb_bus.rdata;
v_apb_bus.addr <= #1 '0;
v_apb_bus.sel <= #1 '0;
v_apb_bus.enable <= #1 '0;
v_apb_bus.write <= #1 '0;
endtask
endclass
class base_test extends uvm_test;
`uvm_component_utils(base_test)
apb_drv drv;
int base_addr = 'h1_0000;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
drv = apb_drv::type_id::create("drv", this);
uvm_config_db #(virtual apb_t)::set(this, "*", "v_apb_bus", apb_bus);
endfunction
virtual task main_phase(uvm_phase phase);
data_t wdata;
data_t mem_t [int];
phase.raise_objection(this);
`uvm_info("base_test", "Start", UVM_MEDIUM);
for (int i = 0; i < 'h100; i += 4) begin
apb_bus.addr = base_addr + i;
wdata = $urandom_range(32'hFFFF, 0);
drv.do_write(apb_bus.addr, wdata);
mem_t[apb_bus.addr] = wdata;
`uvm_info("base_test",
$sformatf("Write addr == 0x%8h, wdata == 0x%8h", apb_bus.addr, wdata),
UVM_MEDIUM);
end
for (int i = 0; i < 'h100; i += 4) begin
apb_bus.addr = base_addr + i;
drv.do_read(apb_bus.addr, apb_bus.rdata);
if(apb_bus.rdata != mem_t[apb_bus.addr]) begin
`uvm_error("Conpare Error",
$sformatf("Read addr == 0x%8h, exdata == 0x%8h, rdata == 0x%8h", apb_bus.addr, mem_t[apb_bus.addr], apb_bus.rdata));
end
else begin
`uvm_info("Compare Success",
$sformatf("Read addr == 0x%8h, exdata == 0x%8h, rdata == 0x%8h", apb_bus.addr, mem_t[apb_bus.addr], apb_bus.rdata),
UVM_MEDIUM);
end
end
phase.drop_objection(this);
endtask
endclass
initial
fork
clk_gen();
run_test();
join_none
endmodule
##テストについて
###virtual interface
クラス化されたマスターモデル(UVMドライバ)は、virtual interfaceを通じて接続します。
https://verificationacademy.com/sessions/uvm-connecting-env-dut/rte/japanese-pdf
より引用。
この図では、uvm_driverは無くuvm_test以下にuvm_envとuvm_agentがありますが、コードではuvm_testの以下にuvm_driverがあると思って下さい。
virtual interfaceは、インスタンスパスにかかわらず、どのクラスからも呼び出すことのできる機能です。ただし、virtualの名の通り、クラスで接続するにはそれなりの手続きが必要です。
まず、uvm_driverを継承したapb_drvの最初にvirtual interfaceを宣言します。
virtual apb_t v_apb_bus;
インスタンスは、build_phaseで、コンフィギュレーションデータベース(uvm_config_db)からgetすることで行われます。getできなかった時のために、uvm_fatalでキャッチしておくといいでしょう。
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if(! uvm_config_db#(virtual apb_t)::get(this, "", "v_apb_bus", v_apb_bus))
`uvm_fatal("VIF instance error", {"virtual interface cannot get", get_full_name(), ".v_apb_bus"} );
endfunction
コンフィギュレーションデータベースとは、UVMで宣言されたりインスタンス化された変数やオブジェクトを、検証環境で登録しておくためのデータベースです。つまり、クラスを飛び越えて検証全体で管理します。
getということは、putやsetか何かでコンフィギュレーションデータベースにあらかじめ登録しておかないとuvm_fatalがコールされてしまいます。じゃあ、どこで登録されているか?それはuvm_testクラスのbuild_phaseになります。
function void build_phase(uvm_phase phase);
super.build_phase(phase);
drv = apb_drv::type_id::create("drv", this);
uvm_config_db #(virtual apb_t)::set(this, "*", "v_apb_bus", apb_bus);
endfunction
はい、setというのが出てきましたね。この辺は以下の図イメージになります。
https://verificationacademy.com/sessions/uvm-connecting-env-dut/rte/japanese-pdf
より引用。
uvm_envとuvm_agentは先と同様uvm_driverと思って下さい。
このset/getのやり取りでインスタンスが行われます。このset/get関数の引数はあまりよく調べてませんが、二番目の引数がクラスのスコープ範囲を表すっぽいです。つまり、uvm_testでsetを"*"とすると、トップダウンで全体を見渡せるようです。
uvm_driverもuvm_testもuvm_componentを継承したクラスなのに、一方で、こういった階層も意識しなくてはいけません。オブジェクト指向を恐れるなかれ!ってレベルじゃねーぞ。
##おわりに
以上で、マスターモデルをクラス化し、virtual interfaceを介して、DUTと接続することができました。接続方法が普通のモジュールインスタンスとは全く異なるので、この辺にハードルを感じます。
次回は資料によくあるようなuvmクラスを継承したクラスのpackage化を試みてみます。