##はじめに
前回はHollow Worldを出すまで到達しました。今回は実際にDUT(検証対象)を入れていきます。
UVMの解説だと、TLMがぁシーケンサーがぁドライバがぁ…とかありますが、それはまず置いておいて、ここでは普通のSystemVerilogのモデル、DUTをUVMの検証環境に載せてみます。
ここでは、APBバス通信をサンプルにやってみます。
##コード
###共通コード
UVMライブラリのインポートと、APBのアドレス/データ幅を決めます。
`include "uvm_macros.svh"
import uvm_pkg::*;
parameter MEM_DEPTH = 256;
parameter ADR_WIDTH = 32;
parameter DAT_WIDTH = 32;
typedef logic [ADR_WIDTH-1:0] addr_t;
typedef logic [DAT_WIDTH-1:0] data_t;
###DUT
中にレジスタアレイを持つAPBスレーブモジュールです。
module apb_slave #(
parameter OFFSET = 32'h0000_0000,
parameter MW = 32
)(
input logic rst_n, clk,
input logic sel,
input logic enable,
input logic write,
input addr_t addr,
input data_t wdata,
output data_t rdata
);
addr_t addr_d;
data_t [(MW-1) : 0 ] mem ;
always_ff @(negedge rst_n, posedge clk)
if(! rst_n)
addr_d <= '0;
else
addr_d <= addr;
always_ff @(negedge rst_n, posedge clk)
if(! rst_n)
mem <= '0;
else
if(write && sel && enable && (addr_d>=OFFSET) && (addr_d< OFFSET + MW) )
mem[addr_d - OFFSET ] <= wdata;
always_comb begin
if(sel && (! write) && enable && (addr_d>=OFFSET) && (addr_d < OFFSET + MW) )
rdata = mem[addr_d - OFFSET];
else
rdata = '0;
end
endmodule
###モデル
リードライト機能を持つAPBマスターモデルです。
module abp_master(
input logic rst_n, clk,
output logic sel,
output logic enable,
output logic write,
output addr_t addr,
output data_t wdata,
input data_t rdata
);
task init();
addr = '0;
sel = '0;
enable = '0;
write = '0;
wdata = 'z;
endtask
task do_write(int addr_i, data_t wdata_i);
@(posedge clk);
addr <= #1 addr_i;
sel <= #1 1'b1;
enable <= #1 1'b0;
write <= #1 1'b1;
wdata <= #1 wdata_i;
@(posedge clk)
enable <= #1 1'b1;
@(posedge clk)
addr <= #1 '0;
sel <= #1 '0;
enable <= #1 '0;
write <= #1 '0;
wdata <= #1 'z;
endtask
task do_read(int addr_i, output data_t rdata_o);
@(posedge clk)
addr <= #1 addr_i;
sel <= #1 1'b1;
enable <= #1 1'b0;
write <= #1 1'b0;
@(posedge clk)
enable <= #1 1'b1;
@(posedge clk)
rdata_o = rdata;
addr <= #1 '0;
sel <= #1 '0;
enable <= #1 '0;
write <= #1 '0;
endtask
initial
init();
endmodule
###テスト
UVMのフローに沿って記述します。DUT、モデルのインスタンスとクロック発生を実施しています。テスト内容は、ランダムデータの書き込み読み出しと値の確認を行っています。UVMのクラス部分は後述します。
module tb();
logic rst_n, clk;
logic sel;
logic enable;
logic write;
addr_t addr;
data_t wdata;
data_t rdata;
apb_slave #(.OFFSET(32'h0001_0000), .MW(MEM_DEPTH))
dut (.*);
abp_master tu(.*);
task clk_gen();
clk = 0;
forever
#5 clk = ~ clk;
endtask
class test_base extends uvm_test;
`uvm_component_utils(test_base)
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_test", "Reset Start", UVM_MEDIUM)
rst_n <= '0;
repeat(5) @(posedge clk);
rst_n <= '1;
repeat(5) @(posedge clk);
`uvm_info("base_test", "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 test0 extends test_base;
`uvm_component_utils(test0)
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 rdata, wdata;
data_t mem_t [int];
phase.raise_objection(this);
`uvm_info("test0", "Start", UVM_MEDIUM);
for (int i = 0; i < 256; i=i+4) begin
addr = base_addr + i;
wdata = $urandom_range(32'hFFFF,0);
tu.do_write(addr, wdata); // Write data to DUT
mem_t[addr] = wdata; // Write data to TB
`uvm_info("test0",
$sformatf("Write addr == 0x%8h, wdata == 0x%8h", addr, wdata),
UVM_MEDIUM);
end
for (int i = 0; i < 256; i=i+4) begin
addr = base_addr + i;
tu.do_read(addr, rdata);
if(rdata != mem_t[addr]) begin
`uvm_error("Conpare Error",
$sformatf("Read addr == 0x%8h, exdata == 0x%8h, rdata == 0x%8h", addr, mem_t[addr], rdata));
end
else begin
`uvm_info("Compare Success",
$sformatf("Read addr == 0x%8h, exdata == 0x%8h, rdata == 0x%8h", addr, mem_t[addr], rdata),
UVM_MEDIUM);
end
end
phase.drop_objection(this);
endtask
endclass
initial
fork
clk_gen();
run_test();
join_none
endmodule
##テストについて
UVMのクラスでは、uvm_testクラスから継承したtest_baseクラスと、さらにそれを継承したtest0クラスがあります。uvm_component_utilsへのクラス名の登録と、function newは前回と同様のおまじないです。
function void build_phaseもおまじないと思って下さい。
で、taskの説明の前に、UVMのフロー(フェイジング)についてちょっと説明しておきます。
UVMではテストフローが決められており、それぞれのフローに相当するクラスを継承してコーディングすれば、UVMのフローでテストが実行されます。UVMは以下のフローを持ちます。
https://verificationacademy.com/cookbook/phasing
より引用。
先に書いたfunction void build_phaseが図の最初にある「Build Phases」に相当します。
タスクの部分は、「Run Phases」に相当します。Run Phasesは図のように細々としたステップがありますが、特に記述しなければ、UVMのデフォルトのステップで実行されます。
さてコードに戻り、taskではreset_phaseとmain_phaseをチョイスして、テストシナリオを記述しています。test_baseクラスではreset_phaseにてリセットの実行、main_phaseではメッセージを出すだけとなっています。
test0クラスではmain_phaseでテスト内容を記述しています。
###テストの実行
+UVM_TESTNAME=test_baseでテスト実行するとこんな感じでFATALを出します。
UVM_FATAL tb.sv(53) @ 95: uvm_test_top [test_base] No test
これは、test_baseクラスではreset_phaseでDUTのリセット後、main_phaseではテストを実行していないため、uvm_fatalがコールされ、FATALとなります。
+UVM_TESTNAME=test0で実施すると、テストが正常実行されます。test0クラスはtest_baseクラスを継承しているため、reset_phaseが親クラスで実行されたあと、main_phaseでテストが実行されるためです。
##おわりに
UVMはテスト手順が決まっていて、そこを拡張してピースを当てはめると、一応それらしいUVMテストが実施できます。
次はいつになるかわかりませんが、モデルをUVMドライバ化して、DUTとInterfaceで接続していくところをやってみようかと思います。