##はじめに
前回の終わりに、モデルをUVMドライバ化と書きましたが、interfaceも含めて一気に書くと、 自分がたぶん忘れるわかりにくくなると思いました。
ここではワンクッション置いて、interfaceを入れ、さらにuvmクラスを入れようと思います。
##コード
###共通コード
前回と同じです。
###interface
全部の信号をinterfaceにまとめ、検証対象(DUT)のメソッドを入れてみました。
interface apb_t();
logic rst_n, clk;
logic sel;
logic enable;
logic write;
addr_t addr;
data_t wdata;
data_t rdata;
function logic get_sel();
return sel;
endfunction
function logic get_en();
return enable;
endfunction
function logic get_rw();
return write;
endfunction
function data_t get_data();
return wdata;
endfunction
function data_t put_data(data_t data_i);
rdata = data_i;
return rdata;
endfunction
modport master (
input rst_n, clk,
output enable, sel, write, addr, wdata,
input rdata
);
modport slave (
import get_en, get_rw, get_sel, get_data, put_data,
input rst_n, clk,
input enable, sel, write, addr, wdata,
output rdata
);
endinterface
##DUT
interfaceに応じて、信号名を変えています。
module apb_slave #(
parameter OFFSET = 32'h0000_0000
)(
apb_t.slave apb_slv
);
localparam MW = 256;
addr_t addr_d;
data_t [(MW -1) : 0 ] mem ;
always_ff @(negedge apb_slv.rst_n, posedge apb_slv.clk)
if(! apb_slv.rst_n)
addr_d <= '0;
else
addr_d <= apb_slv.addr;
always_ff @(negedge apb_slv.rst_n, posedge apb_slv.clk)
if(! apb_slv.rst_n)
mem <= '0;
else
if(apb_slv.get_en() && apb_slv.get_sel() && apb_slv.get_rw() && (addr_d>=OFFSET) && (addr_d< OFFSET + MW) )
mem[addr_d - OFFSET ] <= apb_slv.get_data();
always_comb begin
if(apb_slv.get_en() && apb_slv.get_sel() && (! apb_slv.get_rw()) && (addr_d>=OFFSET) && (addr_d < OFFSET + MW) )
apb_slv.put_data( mem [addr_d - OFFSET] );
else
apb_slv.rdata = '0;
end
endmodule
###モデル
こちらもinterfaceに合わせます。
module abp_master(
apb_t.master apb_mst
);
task init();
apb_mst.addr = '0;
apb_mst.sel = '0;
apb_mst.enable = '0;
apb_mst.write = '0;
apb_mst.wdata = 'z;
endtask
task do_write(int addr_i, data_t wdata_i);
@(posedge apb_mst.clk);
apb_mst.addr <= #1 addr_i;
apb_mst.sel <= #1 1'b1;
apb_mst.enable <= #1 1'b0;
apb_mst.write <= #1 1'b1;
apb_mst.wdata <= #1 wdata_i;
@(posedge apb_mst.clk)
apb_mst.enable <= #1 1'b1;
@(posedge apb_mst.clk)
apb_mst.addr <= #1 '0;
apb_mst.sel <= #1 '0;
apb_mst.enable <= #1 '0;
apb_mst.write <= #1 '0;
apb_mst.wdata <= #1 'z;
endtask
task do_read(int addr_i, output data_t rdata_o);
@(posedge apb_mst.clk)
apb_mst.addr <= #1 addr_i;
apb_mst.sel <= #1 1'b1;
apb_mst.enable <= #1 1'b0;
apb_mst.write <= #1 1'b0;
@(posedge apb_mst.clk)
apb_mst.enable <= #1 1'b1;
@(posedge apb_mst.clk)
rdata_o = apb_mst.rdata;
apb_mst.addr <= #1 '0;
apb_mst.sel <= #1 '0;
apb_mst.enable <= #1 '0;
apb_mst.write <= #1 '0;
endtask
initial
init();
endmodule
###テスト
intterfaceを入れたので、モジュール内で宣言する信号が無くなりました。後は変わりません。UVMのクラス部分は後述します。
module tb();
apb_t apb_bus();
apb_slave #(.OFFSET(32'h0001_0000)) dut (.apb_slv(apb_bus), .*);
abp_master tu(.apb_mst(apb_bus), .*);
task clk_gen();
apb_bus.clk = 0;
forever
#5 apb_bus.clk = ~ apb_bus.clk;
endtask
class base_drv extends uvm_driver;
`uvm_component_utils(base_drv)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
endfunction
virtual task reset_phase(uvm_phase phase);
phase.raise_objection(this);
`uvm_info("base_drv", "Reset Start", UVM_MEDIUM)
apb_bus.rst_n = '0;
repeat(5) @(posedge apb_bus.clk);
apb_bus.rst_n = '1;
repeat(5) @(posedge apb_bus.clk);
`uvm_info("base_drv", "Reset End", UVM_MEDIUM)
phase.drop_objection(this);
endtask
virtual task main_phase(uvm_phase phase);
phase.raise_objection(this);
`uvm_info("test", "Start", UVM_MEDIUM);
`uvm_fatal(get_type_name(), "No test");
phase.drop_objection(this);
endtask
endclass
class test_drv extends base_drv;
`uvm_component_utils(test_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);
endfunction
virtual task main_phase(uvm_phase phase);
data_t wdata;
data_t mem_t [int];
phase.raise_objection(this);
`uvm_info("test_drv", "Start", UVM_MEDIUM);
for (int i = 0; i < 'h100; i += 4) begin
apb_bus.addr = base_addr + i;
wdata = $urandom_range(32'hFFFF, 0);
tu.do_write(apb_bus.addr, wdata);
mem_t[apb_bus.addr] = wdata;
`uvm_info("test_drv",
$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;
tu.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
class base_test extends uvm_test;
`uvm_component_utils(base_test)
test_drv drv;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
drv = test_drv::type_id::create("drv", this);
endfunction
endclass
initial
fork
clk_gen();
run_test();
join_none
endmodule
##テストについて
いろんな資料を見ると、uvm_driverというクラスがDUTとのピンレベルでのやり取りを行うようです。しかし、前回まで見てきたように、これまでDUTを叩いていたのはuvm_testクラスです。UVMのソースコードを拝見すると、uvm_testクラスとuvm_driverクラスは「uvm_component」というクラスを継承しています。
https://verificationacademy.com/sessions/uvm-connecting-components/rte/japanese-pdf
より引用。
ということで、uvm_testでもピンレベルのやり取りが可能です。 この辺の緩さがなんとも微妙ですね。 よって、このコードでは、継承するクラスをuvm_testクラスからuvm_driverクラスに変更しただけです(名前を変えただけ)。
その代わり、uvm_driverに変更したからuvm_testクラスを持ってきて継承し、テストを記述しなくてはなりません。その辺が、「class base_test extends uvm_test...」辺りの記述になります。
このクラス内では、先のtest_drvクラスを宣言し、インスタンスします。
uvm_componentクラスを継承してきたクラスは、new関数でインスタンスするのではなく、createと呼ばれるメソッド(おなじない)でインスタンスします。書式はあんな感じです。
##おわりに
ということで、uvm_driverクラスを使って、uvm_driverに似た何かを作ってみました。APBマスターモデルをクラス化しないと、uvm_driverとは 個人的に言えるとは思えません。
というわけで、次回はいつかAPBマスターをクラス化し、UVMドライバとしてDUTとinterfaceを介してつないでみたいと思います。