20. virtual sequencerを組み込んでみた

モデルひな形生成ツール「gen_uvm_model.rb」を使って生成したモデルをベースに、追加モデルはないのですが、agentが2つあるので、virtual sequencerを組み込んで動かしてみた、という内容のエントリーです。

virtual sequencerを記述する

`ifndef TB_VSEQR`define TB_VSEQRclass 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 : newendclass`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_ENVclass 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_phaseendclass`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 endtaskendclass

ポイントがいくつかあります。まず、各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) endfunctionendclass

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のモデルの動作を、一カ所で管理する仕組み」とでも言えば伝わる…でしょうか。