20. virtual sequencerを組み込んでみた
モデルひな形生成ツール「gen_uvm_model.rb」を使って生成したモデルをベースに、追加モデルはないのですが、agentが2つあるので、virtual sequencerを組み込んで動かしてみた、という内容のエントリーです。
virtual sequencerを記述する
`ifndef TB_VSEQR
`define TB_VSEQR
class tb_vseqr extends uvm_sequencer;
sample_master_sequencer seqr0;
sample_master_sequencer seqr1;
`uvm_component_utils(tb_vseqr)
function new (string name, uvm_component parent);
super.new(name,parent);
endfunction : new
endclass
`endif
あれ?同じsequencerを2つインスタンスしている?って思いませんか?ぼくも書いていて「あれ?いいんだっけ?」と一瞬思いました。モデルはこんな構成になっているんです。
↓ 新しいGoogleサイトだと行間が空きすぎるので、秀丸で書いてキャプチャーしたのですが、目一杯拡大されてしまい見づらくてすみません…。
これでわかると思うのですが、同じsequencerが2つインスタンスされていますよね。この2つを「別々に」ではなくて、「関連を持たせて動かす」ということをするのに、virtual sequencerを使うことになります。もしも、「いや、完全に独立させて動かせればいいんです」ということであれば、virtual sequencerはいらなくて。uvm_testの記述の中で、それぞれのsequencerに「driverの振る舞いを定義した」sequenceをdefault sequenceとして指定してあげればいいんです。
それでは続き。先ほど記述したvirtual sequencerを、
- テストベンチ用のuvm_envクラスにインスタンスし
- build_phase内で「生成」し
- connect_phase内で「sequencerを接続」
します。
`ifndef TB_ENV
`define TB_ENV
class tb_env extends uvm_env;
/// Instanced several model Env
sample_env sample;
/// virtual sequencer
tb_vseqr vseqr;
/// Instanced Verification Components
/// - scoreboard
/// - monitor
/// - virtual sequencer, etc...
///////////////////////////////////////////////////////////////
`uvm_component_utils(tb_env)
function new (string name, uvm_component parent);
super.new(name,parent);
endfunction : new
function void build_phase(uvm_phase phase);
super.build_phase(phase);
uvm_config_db#(int)::set(this, "sample", "num_masters", 2);
sample = sample_env::type_id::create("sample", this);
vseqr = tb_vseqr::type_id::create("vseqr", this);
endfunction : build_phase
function void connect_phase(uvm_phase phase);
/// connect a scoreboard to a monitor
/// connect model's sequencer to "sequencer in the virtual_sequencer"
vseqr.seqr0 = sample.masters[0].sequencer;
vseqr.seqr1 = sample.masters[1].sequencer;
endfunction : connect_phase
endclass
`endif
次は、「virtual sequence」を記述します。シーケンサーではなくてシーケンスです。
まずはbase_vseqを記述します。
virtual class base_vseq extends uvm_sequence;
`uvm_object_utils(base_vseq)
`uvm_declare_p_sequencer(tb_vseqr)
function new(string name="base_vseq");
super.new(name);
do_not_randomize = 1; // randomizeできるシミュレータを使っている人はこの行を削除
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
ポイントは、
`uvm_declare_p_sequencer(tb_vseqr)
です。virtual sequenceの中に、virtual sequencerへのポインターを定義します。これがないとうまく動きません(後述)。do_not_randomize=1は、randomizeを実行できる環境の方には関係ないので削除してください。
次に、上記base_vseqを使って、具体的にvirtual sequenceを記述します。
class sample0_vseq extends base_vseq;
// instance sequences what you want to use
master_single_write_upper_seq upper_seq;
master_single_write_lower_seq lower_seq;
`uvm_object_utils(sample0_vseq)
function new(string name="sample0_vseq");
super.new(name);
endfunction
virtual task body;
upper_seq = master_single_write_upper_seq::type_id::create("upper_seq");
lower_seq = master_single_write_lower_seq::type_id::create("lower_seq");
fork
begin
uvm_report_info(get_name(), "call upper_seq");
`uvm_do_on(upper_seq, p_sequencer.seqr0);
`uvm_do_on(upper_seq, p_sequencer.seqr0);
`uvm_do_on(upper_seq, p_sequencer.seqr0);
`uvm_do_on(upper_seq, p_sequencer.seqr0);
end
begin
uvm_report_info(get_name(), "call lower_seq");
`uvm_do_on(lower_seq, p_sequencer.seqr1);
`uvm_do_on(lower_seq, p_sequencer.seqr1);
`uvm_do_on(lower_seq, p_sequencer.seqr1);
`uvm_do_on(lower_seq, p_sequencer.seqr1);
end
join
endtask
endclass
ポイントがいくつかあります。まず、各agentにぶら下がるdriverを動かすために定義しているsequenceを「インスタンス」します。
master_single_write_upper_seq upper_seq;
master_single_write_lower_seq lower_seq;
次に、task body内で、使いたいsequenceを「生成」
upper_seq = master_single_write_upper_seq::type_id::create("upper_seq");
lower_seq = master_single_write_lower_seq::type_id::create("lower_seq");
します。
そして、
`uvm_do_on(upper_seq, p_sequencer.seqr0);
のように実行します。`uvm_do_onは、sequenceを「どのsequencerに投入するか」を指定するmacroです。`uvm_do系ではできません。
こちらが参考になるかと思います。
これで、virtual sequenceが書けました。あとは、これをuvm_testで指定すればOK。
`uvm_test_head(sample_vseqtest)
function void build_phase(uvm_phase phase);
super.build_phase(phase);
`set_seq(tb.vseqr.run_phase, sample0_vseq)
endfunction
endclass
macroは↓のように定義しています。記述を大量に書く場合にはmacroなどの「省力化」が欲しくなりますよね。
`define set_seq(INST,SEQ) \
uvm_config_db#(uvm_object_wrapper)::set(this,`"INST`","default_sequence",SEQ::type_id::get());
`define uvm_test_head(NAME) \
class NAME extends base_test; \
`uvm_component_utils(NAME) \
function new (string name=`"NAME`", uvm_component parent=null); \
super.new(name,parent); \
endfunction
あとは、vsim実行時に +UVM_TESTNAME=sample_vseqtest のように打って実行すれば動作します。
以上になります。ちょっと手続きが多い感じではありますが、このような手法でvirtual sequencerを定義して、virtual sequenceを動かせるようになります。もし、誰かに「virtual sequenceって何?」と聞かれたら、05. virtual sequencerを参考にしてください。複数のagentを…という表現が伝わらないかもしれないときは、「例えば、USBのモデルとEtherのモデルとPCIeのモデルの動作を、一カ所で管理する仕組み」とでも言えば伝わる…でしょうか。