※2018/02/11 文中の説明文を一部修正しました
今回は、以下のことを実施していきます。ボリューム多めかもしれません。難しいかもしれません。
という流れになります。
sequence_itemとは、トランザクションの発行に必要な情報を格納するクラスです。
xxx_seq_itemと略したり、xxx_transferと書く場合もあります。UVMのサンプルでは後者です。
とりあえず、適当にアドレスとデータを定義してみます。
class sample_seq_item extends uvm_sequence_item;
byte addr, data;
bit write;
`uvm_object_utils(sample_seq_item)
function new (string name="sample_seq_item_inst");
super.new(name);
endfunction
endclass
商用シミュレーターを使っている方は、アドレスとデータ定義の先頭に "rand" をつけておくといいです。modelsim-aseは、この rand を扱えないので、ここでは定義しません。
ここでちょっとだけ、UVMクラスライブラリを参照しようと思います。
uvm_driverは
class uvm_driver #(type REQ=uvm_sequence_item,
type RSP=REQ) extends uvm_component;
:
uvm_seq_item_pull_port #(REQ, RSP) seq_item_port;
こんな風に定義されています。パラメータとして、「型」を指定することになっています。REQ, RSPと書かれたところです。
そして、seq_item_port というポートが宣言されています。
一方、uvm_sequencerは
class uvm_sequencer #(type REQ=uvm_sequence_item, RSP=REQ)
extends uvm_sequencer_param_base #(REQ, RSP);
:
uvm_seq_item_pull_imp #(REQ, RSP, this_type) seq_item_export;
というように、ちょっと複雑に見える定義なのですが、こちらもパラメータとして、「型」を定義することになっています。そして、seq_item_export というポートが宣言されています。
まあとりあえず、「そういうもんなんだ」くらいに捉えておいてください。上記を元にして、4日目で記述したdriverとsequencerを修正します。
--- sample_driver.sv
class sample_driver extends uvm_driver #(sample_seq_item);
--- sample_sequencer.sv
class sample_sequencer extends uvm_sequencer #(sample_seq_item);
どちらも、class定義行を修正するだけです。渡す「型」は、上の方で定義したsequence_itemのクラス名です。
次に、driverとsequencerをつなぎます。つながないと、初めに説明した「流れ」が作れないためです。修正するのは、uvm_agentです。
function void connect_phase(uvm_phase phase);
if(get_is_active() == UVM_ACTIVE)begin
driver.seq_item_port.connect(sequencer.seq_item_export);
end
endfunction
このコードを、build_phaseの次に追加します。太字のところは、先ほどUVMで定義された各クラスのコード引用にあったポートです。名前的には、ポートが出し側でエクスポートが受け側となります。 黄色のところは、UVMクラスライブラリで以下のように定義されているのを使っています。
virtual class uvm_agent extends uvm_component;
uvm_active_passive_enum is_active = UVM_ACTIVE;
:
virtual function uvm_active_passive_enum get_is_active();
return is_active;
endfunction
何に使うかは、ここでは置いておきます。とりあえず初期値は「is_active = UVM_ACTIVE」ですので、uvm_agentに追記するコードは条件がtrueとなり、driverとsequencerがつながることになります。
とりあえずこんな感じで。
--- sample_if.sv
interface sample_if(input logic clk, rstz);
logic write; // 1:write, 0:read
logic valid;
logic [7:0] addr, data;
endinterface
ここが結構わかりづらいところかもしれません。いや、ぼく自身もOVM手法のときより「わかりづらい」気がしました。でも、考え方としては新しい方(ここで説明する方法)がいいかなぁということ で、以下に説明します。
まずinterfaceですが、これは単純に言えば「moduleとmoduleの接続の橋渡しをする人」なわけです。それともう一つ役割があって、クラスの中で「仮想接続」することができます。クラスの中には interfaceを置けないのですが、「仮想」つまりvirtual的に置くことはできるんです。それを virtual interface と呼んでいます。
それではまず、テストベンチトップにinterfaceを組み込みましょう。
--- tb_top.sv
`timescale 1ps/1ps
module tb_top;
// UVM class library
`include "uvm_macros.svh"
import uvm_pkg::*;
// uvm user code
`include "sample_model.svh"
`include "sample_test.sv"
/////////////////////////////////////
logic clk, rstz;
sample_if vif(clk,rstz);
// clk
initial begin
clk <= 1'b1;
#100;
forever #50 clk <= ~clk;
end
// rstz
initial begin
rstz <= 1'b0;
#80 rstz <= 1'b1;
end
initial begin
uvm_config_db#(virtual sample_if)::set(uvm_root::get(), "*.env.*", "vif", vif);
run_test();
end
endmodule
黄色いところを追加しました。なんとなく動かしてみせるために、それっぽくリセットとクロックも作りました。
initial文中の謎めいた記述ですが、これはUVMに組み込まれている「データベース」の一種です。指定したオブジェクトの、指定した範囲の、指定したフィールド名(変数、メンバ)に、このmoduleに組 み込んだ「vif」をセットする、という意味です。
ちょっと難しいかもですが、UVMにあるubusサンプルを少し変えています(第二引数のところ)。これで動作するのを確認し、またenvをenbにするとFatalする(後述)するのを確認したので、第二引数 の意味は正しいと思います。
こんな感じになります。
class sample_driver extends uvm_driver #(sample_seq_item);
virtual sample_if vif;
`uvm_component_utils(sample_driver)
function new (string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
bit status;
super.build_phase(phase);
status = uvm_config_db#(virtual sample_if)::get(this, "", "vif", vif);
if(status==1'b0)
uvm_report_fatal("NOVIF", {"virtual interface must be set for: ",get_full_name(),".vif"});
endfunction
task run_phase(uvm_phase phase);
uvm_report_info("DRIVER", "Hi");
endtask
endclass
なお、monitorにも virtual interface 組み込みが必要になってきますが、その記述は driver と全く同じなので、記述例は省略します。
sequenceは、テストベクタ・テストシナリオとも呼んだりします。sequence記述のお勧めは、
--- sample_seq_lib.sv
最初のクラス、sample_base_seqを定義して、共通となる記述を埋め込みます。
このクラスを virtual class とすることで、このクラス自体を直接使う(インスタンス)できないようにしています。
いくつかポイントを書いておきます。サンプルコードは説明の下に掲載しています。
なお、商用シミュレーターを使用していて、randomize()メソッドが使える場合には、通常以下のようにします。
家の環境ではrandomize()メソッドが使えず、記述の検証が行えないため、詳細は記述しません。
※uvm-1.2を使用した場合、以下サンプルコード内のpre_body, post_bodyの if文ブロックがまるまる使えなくなります。このケースの回避策は00a. uvm-1.2を試すを参考にしてください。
virtual class sample_base_seq extends uvm_sequence #(sample_seq_item);
function new(string name="sample_base_seq");
super.new(name);
do_not_randomize = 1;
endfunction
virtual task pre_body();
if (starting_phase!=null) begin
`uvm_info(get_type_name(),
$sformatf("%s pre_body() raising %s objection",
get_sequence_path(),
starting_phase.get_name()), UVM_MEDIUM);
starting_phase.raise_objection(this);
end
endtask
// Drop the objection in the post_body so the objection is removed when
// the root sequence is complete.
virtual task post_body();
if (starting_phase!=null) begin
`uvm_info(get_type_name(),
$sformatf("%s post_body() dropping %s objection",
get_sequence_path(),
starting_phase.get_name()), UVM_MEDIUM);
starting_phase.drop_objection(this);
end
endtask
endclass
//------------------------------------------------------------------------
class write_seq extends sample_base_seq;
`uvm_object_utils(write_seq)
function new (string name="write_seq");
super.new(name);
endfunction
virtual task body();
$display("Hello SEQ");
`uvm_create(req)
req.write <= 1'b1;
req.addr <= 8'h10;
req.data <= 8'h55;
`uvm_send(req)
#1000;
endtask
endclass
sequence → sequencerへ渡された seq_item をdriverで受け取って、virtual interfaceを経由して実際に動かす記述を追加します。
task run_phaseを更新します。
task run_phase(uvm_phase phase);
uvm_report_info("DRIVER", "Hi");
vif.valid <= 1'b0;
@(posedge vif.rstz); // wait reset negate
forever begin
seq_item_port.get_next_item(req); // wait seq_item from sequence (via sequencer)
@(posedge vif.clk); // sync clk
vif.valid <= 1'b1;
vif.write <= req.write;
vif.addr <= req.addr;
vif.data <= req.data;
@(posedge vif.clk);
vif.valid <= 1'b0;
seq_item_port.item_done(rsp);
end
endtask
一応monitorの記述も書いておきます。
class sample_monitor extends uvm_monitor;
virtual sample_if vif;
`uvm_component_utils(sample_monitor)
function new (string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
bit status;
super.build_phase(phase);
status = uvm_config_db#(virtual sample_if)::get(this, "", "vif", vif);
if(status==1'b0)
uvm_report_fatal("NOVIF", {"virtual interface must be set for: ",get_full_name(),".vif"});
endfunction
task run_phase(uvm_phase phase);
string s_trans_kind;
uvm_report_info("MONITOR", "Hi");
forever begin
@(posedge vif.valid);
if(vif.write===1'b1)
s_trans_kind = "write";
else if(vif.write===1'b0)
s_trans_kind = "read";
else
s_trans_kind = "Unknown";
uvm_report_info("MON", $sformatf("%s addr=%02xh data=%02xh", s_trans_kind, vif.addr, vif.data));
end
endtask
endclass
このファイルは、以下のようになります。
`include "sample_seq_item.sv"
`include "sample_seq_lib.sv"
`include "sample_driver.sv"
`include "sample_monitor.sv"
`include "sample_sequencer.sv"
`include "sample_agent.sv"
`include "sample_env.sv"
# UVM_INFO @ 0: uvm_test_top.env [ENV] Hello ENV
# UVM_INFO @ 0: uvm_test_top.env.agent [AGENT] Hi
# UVM_INFO @ 0: uvm_test_top.env.agent.sequencer [SEQR] Hi
# UVM_INFO @ 0: uvm_test_top.env.agent.sequencer [SEQR] default_sequence is uvm_random_sequence
# UVM_INFO @ 0: uvm_test_top.env.agent.monitor [MONITOR] Hi
# UVM_INFO @ 0: uvm_test_top.env.agent.driver [DRIVER] Hi
# UVM_INFO model//sample_seq_lib.sv(13) @ 0: uvm_test_top.env.agent.sequencer@@write_seq [write_seq] write_seq pre_body() raising run objection
# Hello SEQ
# UVM_INFO @ 200: uvm_test_top.env.agent.monitor [MON] write addr=10h data=55h
# UVM_INFO model//sample_seq_lib.sv(25) @ 1300: uvm_test_top.env.agent.sequencer@@write_seq [write_seq] write_seq post_body() dropping run objection
# UVM_INFO ../uvm-1.1d/src/base/uvm_objection.svh(1268) @ 1300: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase