本文
昔UVMのテストーシーケンスをスタートさせる方法を調べた際に、あまりちゃんと説明していなかったり、資料によってまちまちだったりして良く分からず混乱したので以下にまとめておく。大きく分けて2種類
がある。おススメはマニュアルスタートの1d。バーチャルシーケンス、バーチャルシーケンサーやデフォルトシーケンスを使わずに簡単でかつテストの再利用性が良く、テストベンチ構造の変化に影響を受けないので。特にシンプルなテストの場合には。
1. マニュアルスタート
Test階層のrun_phase()内で明示的にスタートさせたいシーケンスをインスタンス化後に<sequence name>.start()でスタートさせる方法。
.start()への引数としてはシーケンサー(のハンドル)とそのシーケンスをスタートしている上位のシーケンス(のハンドル)で通常はthisだが、最上位つまりTest階層のrun_phase()でのスタートの場合は指定なしか或いはnullを指定する。
以下、uvm_sequence_baseのstart()の引数の定義。
virtual task start (
uvm_sequencer_base sequencer,
uvm_sequence_base parent_sequence=null,
int this_priority=-1,
bit call_pre_post=1
);
a. シーケンサーの直接指定
シーケンスをスタートさせるターゲットとなるシーケンサーを直接階層パスで指定。
class Test extends uvm_test;
Env env;
virtual task run_phase(uvm_phase phase);
Sequence1 seq1 = Sequence1::type_id::create("seq1");
Sequence2 seq2 = Sequence2::type_id::create("seq2");
seq1.start( .sequencer(env.agent1.seqr) );
seq2.start( .sequencer(env.agent2.seqr) );
メリット:
- 直接的で分かりやすくシンプル。
デメリット:
- 再利用性に難あり。Test階層がEnv階層以下の構造を知る必要があり、Env階層以下のAgentやSequencerの名前や構造が変わったらこの部分も変えなければならない。
b. バーチャルシーケンスを使用
バーチャルシーケンスを使用した方法の資料も結構見かける。
ターゲットとなるシーケンサーを指定しない(或いはnullを指定した)バーチャルシーケンスをスタートする。実際に走るシーケンスはバーチャルシーケンス内でスタートされる。ターゲットとなるシーケンサーはバーチャルシーケンス内のシーケンサーハンドルに事前に設定する必要あり。
テストによってスタートするシーケンスを変える場合には直接バーチャルシーケンスを変えるか、ファクトリーオバーライドで実際にスタートするシーケンスを置換する。
class Test extends uvm_test;
Env env;
virtual task run_phase(uvm_phase phase);
Virtual_Sequence v_seq = Virtual_Sequence::type_id::create("v_seq");
v_seq.seqr1_h = env.agent1.seqr;
v_seq.seqr2_h = env.agent2.seqr;
v_seq.start( .sequencer(null) );
class Virtual_Sequence extends uvm_sequence;
uvm_sequencer_base seqr1_h;
uvm_sequencer_base seqr2_h;
virtual task body();
Sequence1 seq1 = Sequence1::type_id::create("seq1");
Sequence2 seq2 = Sequence2::type_id::create("seq2");
seq1.start( .sequencer(seqr1_h), .parent_sequence(this) );
seq2.start( .sequencer(seqr2_h), .parent_sequence(this) );
メリット:
- バーチャルシーケンサーは不要で構造は少し簡単(1-cに比べて)。
デメリット:
- 1-a同様Test階層がEnv階層以下の構造を知る必要があり、再利用性に難あり。
- 余分なコンポーネントであるバーチャルシーケンスを作る必要あり。ファクトリーオーバーライドを使用しない場合はテストの数だけバーチャルシーケンスも作る必要あり。
- ファクトリーオーバーライドが良く分からない人にとっては複雑で難しい。(ファクトリーオーバーライドでスタートするシーケンスを選ぶ場合。)
c. バーチャルシーケンスとバーチャルシーケンサーを使用
バーチャルシーケンスに加えてバーチャルシーケンサーを使用した資料もあり。
バーチャルシーケンスをスタートさせる際に対象シーケンサー指定をnullではなくEnv階層内のバーチャルシーケンサーを指定するようにする。
実際のシーケンサーのハンドルはEnv階層のconnect_phase()でバーチャルシーケンサーに渡す。
1-b同様、テストによってスタートするシーケンスを変える場合には直接バーチャルシーケンスを変えるか、ファクトリーオバーライドで実際にスタートするシーケンスを置換する。
class Virtual_Sequencer extends uvm_sequencer;
uvm_sequencer_base seqr1_h;
uvm_sequencer_base seqr2_h;
class Env extends uvm_env;
Agent agent1, agent2;
Virtual_Sequencer v_seqr;
virtual function connect_phase(uvm_phase phase);
v_seqr.seqr1_h = agent1.seqr;
v_seqr.seqr2_h = agent2.seqr;
class Test extends uvm_test;
Env env;
virtual task run_phase(uvm_phase phase);
Virtual_Sequence v_seq = Virtual_Sequence::type_id::create("v_seq");
v_seq.start( .sequencer(env.v_seqr) );
class Virtual_Sequence extends uvm_sequence;
Virtual_Sequencer v_seqr_h;
virtual task body();
Sequence1 seq1 = Sequence1::type_id::create("seq1");
Sequence2 seq2 = Sequence2::type_id::create("seq2");
$cast(v_seqr_h, this.m_sequencer);
seq1.start( .sequenncer(v_seqr_h.seqr1_h), .parent_sequence(this) );
seq2.start( .sequenncer(v_seqr_h.seqr2_h), .parent_sequence(this) );
メリット:
- 1-a,bとは違い再利用性は良い。Test階層がEnv階層より下位の構造やコンポーネントの名前を知る必要がない。
デメリット:
- 余分なコンポーネントであるバーチャルシーケンスをテスト数分複数作る必要あり。(ファクトリーオーバーライドでテストシーケンスを変えない場合)
- 更に別途バーチャルシーケンサーも作る必要があり、構造が複雑化する。
- ファクトリーオーバーライドが良く分からない人にとっては複雑で難しい。(ファクトリーオーバーライドでスタートするシーケンスを選ぶ場合。)
d. Envにシーケンサーのハンドルを持つ方法(おすすめ)
Env階層にバーチャルシーケンサーではなく、実際のAgentのシーケンサーへのハンドルを直接作る。ハンドルのタイプはAgentのsequencerではなくuvm_sequencer_baseにしておく。それにより汎用性が増し、AgentやSequencerが変わった場合にも変えずに済む。
Test階層でシーケンスをスタートさせる際にはそのシーケンサー(のハンドル)を指定してスタートさせる。
そもそもバーチャルシーケンスはまだしも何でバーチャルシーケンサーなんて作ったんだ。端からいらないんじゃないだろうか。。。
class Env extends uvm_env;
Agent agent1 , agent2 ;
uvm_sequencer_base seqr1_h, seqr2_h;
virtual function connect_phase(uvm_phase phase);
this.seqr1_h = agent1.seqr;
this.seqr2_h = agent2.seqr;
class Test extends uvm_test;
Env env;
virtual task run_phase(uvm_phase phase);
Sequence1 seq1 = Sequence1::type_id::create("seq1");
Sequence2 seq2 = Sequence2::type_id::create("seq2");
seq1.start( .sequencer(env.seqr1_h) );
seq2.start( .sequencer(env.seqr2_h) );
メリット:
- バーチャルシーケンスやバーチャルシーケンサーは必要なく構造が簡単。
- 再利用性が良い。1-a,bとは違いTest階層がEnv階層より下位の構造やコンポーネントの名前を知る必要がない。
デメリット:
- あまり紹介されていないやり方で恐らくどの資料にも載っていない。
Base TestでSequencerハンドルの取得(a/b/c/d)
上記1-a/b/c/dのすべてのマニュアルスタートに適用できるSequencerハンドル取得のやり方として、Base TestにSequencerへのハンドルを持って、connect_phase()でそのハンドルにsequencerの割り当てをするようにしておくと良い。各テストはBase Testを継承する。
class Base_Test extends uvm_test;
Env env;
uvm_sequencer_base seqr1_h, seqr2_h, v_seqr_h;
virtual task connect_phase(uvm_phase phase);
//----- (1-a/b Case) -----------
this.seqr1_h = env.agent1.seqr;
this.seqr2_h = env.agent2.seqr;
//------------------------------
//----- (1-d Case) -------------
this.seqr1_h = env.seqr1_h;
this.seqr2_h = env.seqr2_h;
//------------------------------
//----- (1-c Case) -------------
this.v_seqr_h = env.v_seqr;
//------------------------------
class Test extends Base_Test;
virtual task run_phase(uvm_phase phase);
//---- (1-a/b/d Case) ------------------------------
Sequence1 seq1 = Sequence1::type_id::create("seq1");
Sequence2 seq2 = Sequence2::type_id::create("seq2");
seq1.start( .sequencer(this.seqr1_h) );
seq2.start( .sequencer(this.seqr2_h) );
//--------------------------------------------------
//---- (1-c Case) ----------------------------------
Virtual_Sequence v_seq = Virtual_Sequence::type_id::create("v_seq");
v_seq.start( .sequencer(this.v_seqr_h) );
//--------------------------------------------------
こうしておくことでもしEnvやそれが持つSequencerハンドルの名前やその構造が変わったとしてもBase Testだけを変えれば良く、他のテストは一切変える必要がない。
もっと言うとSequenceのcreateとstartもBase Testに移動して各テストではsequenceをfactory overrideで入れ替えのみにすれば良いが、ここでの本質ではないので、ここではその方法は省略。
EnvでSequencerハンドルを取得する関数を作る(a/b/d)
又、1-dではEnvにシーケンサーのハンドルを作ったが、ハンドルその物ではなくシーケンサーのハンドルを取得する関数を作るようにすると更に良い。それにより1-a/bの場合でもTestはEnv以下の構造やシーケンサーの名前を知らなくても済む。
更にそれを上記のBase TestでSequencerハンドルを取得する方法を適用するとBase Test自体もEnv以下の構造やシーケンサーの名前を知る必要がなくなるので尚良い。
class Env extends uvm_env;
Agent agent1 , agent2 ;
virtual function uvm_sequencer_base get_seqr1_h();
return (agent1.seqr);
endfunction
virtual function uvm_sequencer_base get_seqr2_h();
return (agent2.seqr);
endfunction
class Base_Test extends uvm_test;
Env env;
uvm_sequencer_base seqr1_h, seqr2_h;
virtual task connect_phase(uvm_phase phase);
this.seqr1_h = env.get_seqr1(); //env.agent1.seqr;
this.seqr2_h = env.get_seqr2(); //env.agent2.seqr;
2. 自動でスタート
Testのbuild_phase()でシーケンサーの設定パラメータである"default_sequence"にシーケンスを設定して、そのシーケンサーのrun_phase()で勝手にスタートさせる。Testのrun_phase()内でシーケンスのstart()を明示的にコールはしない。
OVMの時代にケイデンスがこんなやり方でやっていたと思う。なので少し古い資料だとこの様なデフォルトシーケンスを使っている場合がある。その名残りで未だにこのやり方を紹介している資料も存在するが、現在ではこのやり方は推奨されていない。なのでこのやり方はやらないようにしましょう。
class Test extends uvm_test;
virtual function build_phase(uvm_phase phase);
uvm_config_db#(uvm_object_wrapper)::set
( this,
"env.agent1.seqr.run_phase",
"default_sequence",
Sequence1::type_id::get()
);
- メリット : バーチャルシーケンスやバーチャルシーケンサーは必要なく構造が簡単。
- メリット : 割とよく知られたやり方で時々資料に載っている。
- デメリット : Test階層がEnv階層以下の構造を知る必要がある。
- デメリット : デフォルトシーケンスやコンフィグレーションデータベースの概念をよく知らない人にとっては難しい。
参考リンク