UVMシーケンスのm_sequnecerとp_sequncerについて、あとget_sequencer()とか

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_ref_sequence.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/471497/ef984208-067e-83c4-79b7-fa157c9472c0.gif)
【シーケンスのクラス継承図】

https://verificationacademy.com/verification-methodology-reference/uvm/docs_1.2/html/files/overviews/sequences-txt.html

なのでuvm_sequennceを継承したユーザーテストシーケンスは何も宣言せずとも初めからm_sequencerをクラスメンバとして持っている。protectedのクラスメンバなのでuvm_sequence_itemを祖先にもつ継承クラスからしか参照できない。このように外から見えないように隠されているからなのか[クラスリファレンスマニュアル](https://www.accellera.org/images/downloads/standards/uvm/UVM_Class_Reference_Manual_1.2.pdf)（[HTML版](https://verificationacademy.com/verification-methodology-reference/uvm/docs_1.2/html/index.html)）でもm_sequencerに関しての記述がなされていない。
以下uvm_sequence_itemでのm_sequencerのクラスメンバー宣言。

```verilog:m_sequencerのクラスメンバー宣言
class uvm_sequence_item extends uvm_transaction;
  protected uvm_sequencer_base  m_sequencer;
  ...
```
uvm_sequence_baseはuvm_sequence_itemを継承。そしてstart()タスクをメンバー追加。

```verilog:uvm_sequence_itemを継承
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を継承。

```verilog:uvm_sequence_baseを継承
virtual class uvm_sequence #(type REQ = uvm_sequence_item, type RSP = REQ) extends uvm_sequence_base;
```
ユーザーテストシーケンスはuvm_sequenceを継承。

```verilog:uvm_sequenceを継承
class user_test_sequence extends uvm_sequence;
  ...
```
ユーザーテストシーケンスはuvm_sequence_baseでメンバー定義されたstart()がコールされて開始されるが、その引数としてシーケンサーのハンドルを渡す。そしてその時にそのユーザーシーケンスが実行されるシーケンサーへのハンドルがm_sequencerにセットされる。

```verilog:テストシーケンスのスタート
  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()を実行する。

```verilog:下位シーケンスのスタート
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.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/471497/4a63a6a4-1e09-9e52-bdc9-09a8769ce783.png)


## m_sequencerのタイプ
上記の様にm_sequencerのタイプはuvm_sequncer_baseである。一方、Agentの中の実際のシーケンサーのタイプはuvm_sequencerを継承した何かしらのタイプである。オブジェクト指向プログラミングなので継承先のハンドルを継承元のタイプのハンドルに代入する事は許されている。なのでAgentのシーケンサーのハンドルをシーケンスのm_sequencerに渡すこと自体に問題はない。
![uvm_ref_sequencer.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/471497/31fa8949-92ff-3e46-e763-6defe7aa67ea.gif)
【シーケンサーのクラス継承図】

https://verificationacademy.com/verification-methodology-reference/uvm/docs_1.2/html/files/overviews/sequencers-txt.html

そしてシーケンスではUVMフレームワークがuvm_sequence_baseで持っているメンバを使ってシーケンスアイテムをシーケンサに渡してテストを実行してくれる。問題となるのはユーザーがuvm_sequencerを継承して新たに追加したメンバ関数や変数をシーケンス内で使う必要がある場合である。
典型的な例はシーケンサーとしてバーチャルシーケンサーを使用する場合である。バーチャルシーケンサーでは複数のシーケンサーのハンドルが新たなメンバーとして追加されている。バーチャルシーケンス、バーチャルシーケンサーに関しては[「UVM Sequence Start　c. バーチャルシーケンスとバーチャルシーケンサーを使用」](https://qiita.com/vega77/items/b14815341873da583ea7#c-%E3%83%90%E3%83%BC%E3%83%81%E3%83%A3%E3%83%AB%E3%82%B7%E3%83%BC%E3%82%B1%E3%83%B3%E3%82%B9%E3%81%A8%E3%83%90%E3%83%BC%E3%83%81%E3%83%A3%E3%83%AB%E3%82%B7%E3%83%BC%E3%82%B1%E3%83%B3%E3%82%B5%E3%83%BC%E3%82%92%E4%BD%BF%E7%94%A8)を参照。

```verilog:バーチャルシーケンサー
class Virtual_Sequencer extends uvm_sequencer;
  uvm_sequencer_base sqr1_h;
  uvm_sequencer_base sqr2_h;
  ...
```
```verilog:バーチャルシーケンス
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) );
    ...
```
```verilog:バーチャルシーケンスのスタート
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を継承して２種類のシーケンサーのハンドル（sqr1_h, sqr2_h）が追加されている。テストではそのバーチャルシーケンサーのハンドルをバーチャルシーケンスに渡してスタートさせている。バーチャルシーケンスでは２種類の子シーケンスをスタートさせているが、その際にそれぞれのシーケンサー（のハンドル）を渡す必要がある。m_sequencerのタイプはuvm_sequencer_baseなので継承で追加された２種類の本当のシーケンサーのハンドル情報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を返す関数が定義されている様である。

```verilog:get_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()は[クラスリファレンスマニュアル](https://www.accellera.org/images/downloads/standards/uvm/UVM_Class_Reference_Manual_1.2.pdf)（[HTML版](https://verificationacademy.com/verification-methodology-reference/uvm/docs_1.2/html/index.html)）にもちゃんと公開されている。

https://verificationacademy.com/verification-methodology-reference/uvm/docs_1.2/html/files/seq/uvm_sequence_item-svh.html#uvm_sequence_item.get_sequencer

> get_sequencer
> function uvm_sequencer_base get_sequencer()
> Returns a reference to the default sequencer used by this sequence.

いろいろな資料でm_sequencerを使っている事も多いが、本来下位シーケンスのスタートは以下のようにget_sequencer()を使うべきである。

```verilog: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に指定したいシーケンサーのタイプ（クラス名）である。

```verilog:シーケンスで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のメンバになった。
実際のマクロの内容を見てみると以下が書いてある。

```verilog:
`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に入れているのである。
これは正に上記でバーチャルシーケンスでバーチャルシーケンサーに対して行っていた事である。なので上記バーチャルシーケンスは以下の様にも書くことができる。

```verilog:バーチャルシーケンス
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を使うとシーケンスがシーケンサーに依存してしまうからである。元々シーケンサー依存のバーチャルシーケンスで使うのであれば良いが、通常のシーケンスではなるべくシーケンサー依存にならないようにするべきである。