UVMシーケンスで使われるm_sequncerとp_sequnecerについてあまり説明がなされていなくて、シーケンスを作る時に混乱する事があるようなので説明しようと思う。何かの参考になれば幸いである。
m_sequencer
m_sequencerはuvm_sequnce_itemクラスにprotectedでuvm_sequencer_baseとしてクラスメンバ定義されている。uvm_sequneceはuvm_sequence_baseから継承したクラスで、uvm_sequence_baseはuvm_sequence_itemから継承したクラスである。
【シーケンスのクラス継承図】
なのでuvm_sequennceを継承したユーザーテストシーケンスは何も宣言せずとも初めからm_sequencerをクラスメンバとして持っている。protectedのクラスメンバなのでuvm_sequence_itemを祖先にもつ継承クラスからしか参照できない。このように外から見えないように隠されているからなのかクラスリファレンスマニュアル(HTML版)でもm_sequencerに関しての記述がなされていない。
以下uvm_sequence_itemでのm_sequencerのクラスメンバー宣言。
class uvm_sequence_item extends uvm_transaction;
protected uvm_sequencer_base m_sequencer;
...
uvm_sequence_baseはuvm_sequence_itemを継承。そしてstart()タスクをメンバー追加。
virtual class uvm_sequence_base extends uvm_sequence_item;
virtual task start (uvm_sequencer_base sequencer,
uvm_sequence_base parent_sequence = null,
int this_priority = -1,
bit call_pre_post = 1);
...
uvm_sequenceはuvm_sequence_baseを継承。
virtual class uvm_sequence #(type REQ = uvm_sequence_item, type RSP = REQ) extends uvm_sequence_base;
ユーザーテストシーケンスはuvm_sequenceを継承。
class user_test_sequence extends uvm_sequence;
...
ユーザーテストシーケンスはuvm_sequence_baseでメンバー定義されたstart()がコールされて開始されるが、その引数としてシーケンサーのハンドルを渡す。そしてその時にそのユーザーシーケンスが実行されるシーケンサーへのハンドルがm_sequencerにセットされる。
user_test_sequence test_seq = user_test_sequence::type_id::create("test_seq");
test_seq( .sequencer(sqr_h), .parent_sequence(null) );
m_sequencerにシーケンサーのハンドル情報がセットされるのでシーケンスはそのシーケンサーにシーケンスアイテム(トランザクション)を送ることができるのである。下位のシーケンスではUVMのフレームワークが勝手にそれをやってくれるのでユーザーが特に意識する必要は無いが、下位のシーケンスをスタートさせる上位のシーケンスではstart()で受け取ったシーケンサーのハンドル情報を下位のシーケンスに受け渡す必要がある。その時にユーザーはm_sequencerを使う必要がある。具体的には下位のシーケンスをスタートさせる時に、m_sequencerを引数としてstart()を実行する。
class user_test_sequence extends uvm_sequence;
virtual task body();
sub_sequence sub_seq = sub_sequence::type_id::create("sub_seq");
sub_seq.start( .sequencer(this.m_sequencer), .parent_sequence(this) ); // Pass m_sequencer to the sub sequence
...
以下、sequencerのhandleをsequenceのm_sequnecerに受け渡すイメージ図。
m_sequencerのタイプ
上記の様にm_sequencerのタイプはuvm_sequncer_baseである。一方、Agentの中の実際のシーケンサーのタイプはuvm_sequencerを継承した何かしらのタイプである。オブジェクト指向プログラミングなので継承先のハンドルを継承元のタイプのハンドルに代入する事は許されている。なのでAgentのシーケンサーのハンドルをシーケンスのm_sequencerに渡すこと自体に問題はない。
【シーケンサーのクラス継承図】
そしてシーケンスではUVMフレームワークがuvm_sequence_baseで持っているメンバを使ってシーケンスアイテムをシーケンサに渡してテストを実行してくれる。問題となるのはユーザーがuvm_sequencerを継承して新たに追加したメンバ関数や変数をシーケンス内で使う必要がある場合である。
典型的な例はシーケンサーとしてバーチャルシーケンサーを使用する場合である。バーチャルシーケンサーでは複数のシーケンサーのハンドルが新たなメンバーとして追加されている。バーチャルシーケンス、バーチャルシーケンサーに関しては「UVM Sequence Start c. バーチャルシーケンスとバーチャルシーケンサーを使用」を参照。
class Virtual_Sequencer extends uvm_sequencer;
uvm_sequencer_base sqr1_h;
uvm_sequencer_base sqr2_h;
...
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); // <==============| Cast of uvm_sequncer
seq1.start( .sequenncer(v_seqr_h.sqr1_h), .parent_sequence(this) );
seq2.start( .sequenncer(v_seqr_h.sqr2_h), .parent_sequence(this) );
...
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) ); // <============| Pass Virtual Sequncer in Env
...
この例のバーチャルシーケンサーではuvm_sequencerを継承して2種類のシーケンサーのハンドル(sqr1_h, sqr2_h)が追加されている。テストではそのバーチャルシーケンサーのハンドルをバーチャルシーケンスに渡してスタートさせている。バーチャルシーケンスでは2種類の子シーケンスをスタートさせているが、その際にそれぞれのシーケンサー(のハンドル)を渡す必要がある。m_sequencerのタイプはuvm_sequencer_baseなので継承で追加された2種類の本当のシーケンサーのハンドル情報sqr1_h, sqr2_hをメンバーとして持っていない。なのでm_sequencerそのままではsqr1_h, sqr2_hを渡すことができない。ではどうしているかと言うと$cast()を使ってm_sequencerのタイプをVirtual_Sequencerにタイプキャストしてv_seqr_hに代入している。v_seqr_hのタイプはVirtual_Sequencerなのでシーケンサーのハンドルのクラスメンバsqr1_h, sqr2_hを持っている。これで晴れて子シーケンスにそれぞれのシーケンサーのハンドルを渡すことができた。
この様に継承でユーザーがuvm_sequencerに追加した関数や変数へはm_sequencerそのままではアクセスできないのである。
get_sequencer()
ここまでm_sequencerについていろいろ書いたが、少し調べたところ、どうやらuvm_sequence_itemに、get_sequencer()というm_sequencerを返す関数が定義されている様である。
class uvm_sequence_item extends uvm_transaction;
protected uvm_sequencer_base m_sequencer;
// Function: get_sequencer
//
// Returns a reference to the default sequencer used by this sequence.
function uvm_sequencer_base get_sequencer();
return m_sequencer;
endfunction
本来シーケンサーへの参照はm_sequencerではなくget_sequencer()を使うべきだ。get_sequencer()はクラスリファレンスマニュアル(HTML版)にもちゃんと公開されている。
get_sequencer
function uvm_sequencer_base get_sequencer()
Returns a reference to the default sequencer used by this sequence.
いろいろな資料でm_sequencerを使っている事も多いが、本来下位シーケンスのスタートは以下のようにget_sequencer()を使うべきである。
class user_test_sequence extends uvm_sequence;
virtual task body();
sub_sequence sub_seq = sub_sequence::type_id::create("sub_seq");
sub_seq.start( .sequencer(this.get_sequencer()), .parent_sequence(this) ); // Pass sequencer to the sub sequence with get_sequencer()
オブジェクト指向プログラミングではクラスの変数メンバへのアクセスは関数を介して行う事が推奨されている。これは実装とインターフェースを分ける為である。実装が変わったとしてもインターフェスとしての関数が変わらなければそのまま使えるからである。或いはインターフェースが変わらなければ実装を変える事が出来るとも言える。その点からも直接m_sequencerを使うのではなくget_sequencer()を使う方が望ましい。
p_sequencer
いろいろなUVMの資料の中にp_sequencerと言うのを見た事がないだろうか。これもシーケンスの中で使われるが、m_sequencerとの違いが良く分からなかったりする人も多いと思うので説明しておこう。
p_sequencerはm_sequencerと違いuvm_sequence_baseのメンバではないので最初から何もせずにユーザーのテストシーケンスのメンバになっている訳ではない。p_sequencerをシーケンスのメンバとして使うには`uvm_declare_p_sequencer(SEQUENCER)のUVMマクロを記述する必要がある。SEQUENCERはp_sequencerに指定したいシーケンサーのタイプ(クラス名)である。
class mysequence extends uvm_sequence#(mydata);
`uvm_object_utils(mysequence)
`uvm_declare_p_sequencer(some_seqr_type) // <=======To use p_sequencer
task body;
//Access some variable in the user's custom sequencer
if(p_sequencer.some_variable) begin
...
end
endtask
endclass
これでsome_seqr_typeと言うタイプでp_sequencerがmysequenceのメンバになった。
実際のマクロの内容を見てみると以下が書いてある。
`define uvm_declare_p_sequencer(SEQUENCER) \
SEQUENCER p_sequencer;\
virtual function void m_set_p_sequencer();\
super.m_set_p_sequencer(); \
if( !$cast(p_sequencer, m_sequencer)) \
`uvm_fatal("DCLPSQ", \
$sformatf("%m %s Error casting p_sequencer, please verify that this sequence/sequence item is intended to execute on this type of sequencer", get_full_name())) \
endfunction
要するにSEQUENCERで指定したタイプのp_sequencerを作って、m_sequencerを$cast()でキャストしてp_sequencerに入れているのである。
これは正に上記でバーチャルシーケンスでバーチャルシーケンサーに対して行っていた事である。なので上記バーチャルシーケンスは以下の様にも書くことができる。
class Virtual_Sequence extends uvm_sequence;
`uvm_declare_p_sequencer(Virtual_Sequencer) // <=======Use p_sequencer for Virtual_Sequencer
//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(p_sequencer.sqr1_h), .parent_sequence(this) );
seq2.start( .sequenncer(p_sequencer.sqr2_h), .parent_sequence(this) );
この様にp_sequencerはuvm_sequencer_baseからの継承でシーケンサーに追加されたメンバにアクセスしたい場合に、`uvm_declare_p_sequencerでシーケンスのメンバに登録して使うのである。
但し、p_sequencerの使用はあまり推奨されていない。p_sequencerを使うとシーケンスがシーケンサーに依存してしまうからである。元々シーケンサー依存のバーチャルシーケンスで使うのであれば良いが、通常のシーケンスではなるべくシーケンサー依存にならないようにするべきである。