##はじめに
前回で、UVMドライバを使ったテストまで行きました。
テスト階層がいろいろ長くなってきたので、今回はclass毎にファイルを作り、パッケージ化を行います。
ファイルで分離化されたことで、クラスのスコープ範囲を意識してコードを作らなくてはいけません。 前回よりも今回の方がストレスがかかりました。
##コード
###共通コード、DUT、Interface
これまでと同様です。
###package
コードをimportしたりincludeして、apb_pkgというパッケージを作成します。uvmと共通コード、後述するドライバとテストを入れています。
拡張子の解釈がいろいろあると思いますが、ここではclassのみ書かれているコードを.svhとしました。
package apb_pkg;
import uvm_pkg::*;
`include "uvm_macros.svh"
`include "common.sv"
`include "apb_drv.svh"
`include "test_lib.svh"
endpackage
###ドライバ
前回のtb.svから、apb_drvクラスを移します。
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
###テスト
base_testクラスを移して、追記・削除を一部分行っています。
まず、削除したのは、build_phaseにあるコンフィギュレーションデータベース登録(set)です。これは、tb.svに移すことにしました。
追加したのは、uvm_table_printerクラスをインスタンスし、end_of_elaboration_phaseメソッド(おなじない)で、クラス階層を表示させるようにしました。このメソッド名で推定する限り、テストモジュールがbuildし終わり、テストが走る前のフェイズで実行されると思われます。表示内容は後述します。
また、apb_drvクラスが別ファイルに移動したので、apb_drvクラスで呼び出しているinterface部分の信号名には、drv.とアタマをつけます。
class base_test extends uvm_test;
`uvm_component_utils(base_test)
apb_drv drv;
uvm_table_printer printer;
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);
endfunction
function void end_of_elaboration_phase(uvm_phase phase);
`uvm_info(get_type_name(), this.sprint(printer), UVM_MEDIUM);
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
drv.v_apb_bus.addr = base_addr + i;
wdata = $urandom_range(32'hFFFF, 0);
drv.do_write(drv.v_apb_bus.addr, wdata);
mem_t[drv.v_apb_bus.addr] = wdata;
`uvm_info("base_test",
$sformatf("Write addr == 0x%8h, wdata == 0x%8h", drv.v_apb_bus.addr, wdata),
UVM_MEDIUM);
end
for (int i = 0; i < 'h100; i += 4) begin
drv.v_apb_bus.addr = base_addr + i;
drv.do_read(drv.v_apb_bus.addr, drv.v_apb_bus.rdata);
if(drv.v_apb_bus.rdata != mem_t[drv.v_apb_bus.addr]) begin
`uvm_error("Conpare Error",
$sformatf("Read addr == 0x%8h, exdata == 0x%8h, rdata == 0x%8h", drv.v_apb_bus.addr, mem_t[drv.v_apb_bus.addr], drv.v_apb_bus.rdata));
end
else begin
`uvm_info("Compare Success",
$sformatf("Read addr == 0x%8h, exdata == 0x%8h, rdata == 0x%8h", drv.v_apb_bus.addr, mem_t[drv.v_apb_bus.addr], drv.v_apb_bus.rdata),
UVM_MEDIUM);
end
end
phase.drop_objection(this);
endtask
endclass
###tb.sv(テストトップ)
apb_pkgをimportし、base_testクラスで削除したコンフィギュレーションデータベースの登録をinital部分で実施しています。setの第一引数(uvm_top)はUVM環境でのテストトップ階層を表すおまじないです。
いかにもUVMらしい少ない行数になりましたね。
module tb();
import uvm_pkg::*;
import apb_pkg::*;
apb_t apb_bus();
apb_slave #(.OFFSET(32'h0001_0000)) dut (.apb_slv(apb_bus), .*);
task clk_gen();
apb_bus.clk = 0;
forever
#5 apb_bus.clk = ~ apb_bus.clk;
endtask
initial begin
uvm_config_db #(virtual apb_t)::set(uvm_top, "*", "v_apb_bus", apb_bus);
fork
clk_gen();
run_test();
join_none
end
endmodule
##コンフィギュレーションデータベースについて
コンフィギュレーションデータベースのメソッドについて自分なりに整理しておきます。
uvm_config_db #(virtual apb_t)::set(uvm_top, "*", "v_apb_bus", apb_bus);
uvm_config_db#(virtual apb_t)::get(this, "", "v_apb_bus", v_apb_bus);
コンフィギュレーションデータベースはsetで設定し、getで取得するのは前回も述べました。
第三引数はフィールド名、第四引数は信号名を表します。フィールド名は検証環境全体で、コンフィギュレーションデータベースと紐付けるために利用されるので、文字列になっています。よって、"apb_bus"や"v_"といったワイルドカードでマッチさせることができます。
第四引数はそのクラス・モジュール内で使われている信号名となります。
つまり、フィールド名で紐つけて階層をまたがったコンフィグレーションのマッチングを行っているようです。 第一、第二引数はまだよくわかっていません。 この辺はもう少し環境を組み上げてから理解しようかと思っています。
##テスト実行
シミュレーションがRunする前に、base_testクラスで記述したend_of_elaboration_phaseメソッドが走り、コンソールに以下のような感じでクラス構造が表示されます。
Name Type Size Value
------------------------------------------------------
uvm_test_top base_test - @462
drv apb_drv - @470
rsp_port uvm_analysis_port - @487
seq_item_port uvm_seq_item_pull_port - @478
------------------------------------------------------
なるほど… 全くわからん。
##シミュレーションコマンド
`includeが出てきたので、+incdirを使ってこんな感じでやれば動くと思います。common.svは検証対象(DUT)でも使うので、再度指定します。
###VCSの場合
vcs \
-R \
-full64 \
-sverilog \
-ntb_opts uvm \
+incdir+./ \
apb_pkg.sv common.sv apbif.sv apb_slave.sv tb.sv \
+UVM_TESTNAME=base_test
###Incisiveの場合
irun \
-64 \
-uvm \
+incdir+./ \
apb_pkg.sv common.sv apbif.sv apb_slave.sv tb.sv \
+UVM_TESTNAME=base_test
###QuestaSimの場合
vlib work
vlog +incdir+./ apb_pkg.sv common.sv apbif.sv apb_slave.sv tb.sv
vsim -64 -c tb -do "run -all" +UVM_TESTNAME=base_test
ISim?知らない子ですね…。
##おわりに
今回、SystemVerilogのOOP部分のスコープやUVM独特のフローを身につけていかないと結構厳しいところがありました。 こんなもん本当に役に立つのかはさておき、 オープンライブラリですので、元のコードと突き合わせつつ、理解を深める必要がありそうです。
次回は、シーケンサーを登場させて、ドライバとつなげてみようかと思います。