uvm_sequenceのstart()メソッドでシーケンススタート

UVMでuvm_sequenceを開始させるには以下の2通りの方法があります。
1. [uvm_config_dbのdefault_sequeneを使う](https://qiita.com/triggerfish/items/4ab7a296f8ecbe992fe1)
2. uvm_seuqnceのstart()メソッドを使う

本稿では後者について説明します。

---
本稿では直接的にuvm_sequenceを実行する方法を説明します。UVMでは本方法が主流で、もう一つの方法であるuvm_config_dbを使用したものと比べて簡単かつ柔軟性が高いのですが、WEB上の解説記事ではあまり触れられてないように思います。UVMの前身であるOVM時代にはサポートされていなかったことが影響しているのだと思います。

# 使い方
(UVMに不慣れな方は、以下でuvm_sequenceとuvm_sequencerの2つが登場することに注意してください。似ている名前ですが異なるものです) uvm_sequenceのstart()メソッドで、目的のuvm_sequencerのオブジェクトを引数に指定するだけです。一つのuvm_sequencer上で複数のuvm_sequenceを走らせることも可能です。この時の調停方法は対象のuvm_sequencerの設定に依存します。デフォルトでは早い者勝ちです。詳細はいずれ機会があれば説明したいと思います。

```
uvm_sequence.start(uvm_sequencer);
```

start()の説明はこれだけです。では早速プログラム例を見ていきましょう。

# プログラム例
サンプルプログラムです。EDA Playgroundに登録してありますので、以下から実行することができます。
https://www.edaplayground.com/x/3mMj

```verilog

import uvm_pkg::*;
`include "uvm_macros.svh"

class my_sequence_item_c extends uvm_sequence_item;
  string str;
  `uvm_object_utils(my_sequence_item_c)

  function new(string name = "my_sequence_item_c");
    super.new(name);
  endfunction
endclass


class my_sequence_c extends uvm_sequence #(my_sequence_item_c);
  `uvm_object_utils(my_sequence_c)

  function new(string name = "my_sequence_c");
    super.new(name);
  endfunction

  virtual task body();
    req = my_sequence_item_c::type_id::create("req");

    start_item(req);
    req.str = {get_full_name(), " --- 1st call"};
    finish_item(req);

    start_item(req);
    req.str = {get_full_name(), " --- 2nd call"};
    finish_item(req);
  endtask
endclass


class my_driver_c extends uvm_driver #(my_sequence_item_c);
  `uvm_component_utils(my_driver_c)

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction

  virtual task run_phase(uvm_phase phase);
    super.run_phase(phase);
    forever begin
      seq_item_port.get_next_item(req);
      #10ns;
      `uvm_info(get_type_name(), $sformatf("Passed string = %s", req.str), UVM_NONE);
      seq_item_port.item_done();
    end
  endtask
endclass


class my_agent_c extends uvm_component;
  my_driver_c my_driver;
  uvm_sequencer #(my_sequence_item_c) my_sequencer;

  `uvm_component_utils(my_agent_c)

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    my_sequencer = uvm_sequencer#(my_sequence_item_c)::type_id::create("my_sequencer", this);
    my_driver = my_driver_c::type_id::create("my_driver", this);
  endfunction

  virtual function void connect_phase(uvm_phase phase);
    my_driver.seq_item_port.connect(my_sequencer.seq_item_export);
  endfunction
endclass


class my_env_c extends uvm_env;
  my_agent_c my_agent;

  `uvm_component_utils(my_env_c)

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    my_agent = my_agent_c::type_id::create("my_agent", this);
  endfunction
endclass


class my_test_c extends uvm_test;
  `uvm_component_utils(my_test_c)

  my_env_c my_env;

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    my_env = my_env_c::type_id::create("my_env", this);
  endfunction

  virtual task run_phase(uvm_phase phase);
    uvm_root uvm_root_hdl = uvm_root::get();
    uvm_sequencer #(my_sequence_item_c) my_sequencer;

    my_sequence_c my_sequence_1 = my_sequence_c::type_id::create("my_sequence_1");
    my_sequence_c my_sequence_2 = my_sequence_c::type_id::create("my_sequence_2");
    my_sequence_c my_sequence_3 = my_sequence_c::type_id::create("my_sequence_3");

    $cast(my_sequencer, uvm_root_hdl.find("uvm_test_top.my_env.my_agent.my_sequencer"));
    if (my_sequencer == null) begin
      `uvm_error(get_type_name(), "The sequencer was not found")
    end
 
    phase.raise_objection(this);
    fork
      my_sequence_1.start(my_sequencer);
      my_sequence_2.start(my_sequencer);
    join
    my_sequence_3.start(my_sequencer);
    phase.drop_objection(this);
  endtask
endclass


module top();
  initial begin
    run_test("my_test_c");
  end
endmodule
```

重要なのはmy_test_c::run_phase()のみです。my_sequence_cを3つ、異なるオブジェクトとしてインスタンス化しています。オブジェクションを上げてからmy_sequence_1とmy_sequence_2はfork joinで同時にスタートさせます。この2つはuvm_sequencerにより調停されて順番に処理されます。両方のシーケンス実行が終了すると最後にmy_sequence_3が実行されます。最後にオブジェクションを落としてテストを終了させます。

以下が実行結果です。my_sequence_1とmy_sequence_2が並列に処理され、両方が終了した後にsequence_3が実行されていることが読み取れます。

```plaintext
# KERNEL: UVM_INFO @ 0: reporter [RNTST] Running test my_test_c...
# KERNEL: UVM_INFO /home/runner/testbench.sv(48) @ 10: uvm_test_top.my_env.my_agent.my_driver [my_driver_c] Passed string = uvm_test_top.my_env.my_agent.my_sequencer.my_sequence_1 --- 1st call
# KERNEL: UVM_INFO /home/runner/testbench.sv(48) @ 20: uvm_test_top.my_env.my_agent.my_driver [my_driver_c] Passed string = uvm_test_top.my_env.my_agent.my_sequencer.my_sequence_2 --- 1st call
# KERNEL: UVM_INFO /home/runner/testbench.sv(48) @ 30: uvm_test_top.my_env.my_agent.my_driver [my_driver_c] Passed string = uvm_test_top.my_env.my_agent.my_sequencer.my_sequence_1 --- 2nd call
# KERNEL: UVM_INFO /home/runner/testbench.sv(48) @ 40: uvm_test_top.my_env.my_agent.my_driver [my_driver_c] Passed string = uvm_test_top.my_env.my_agent.my_sequencer.my_sequence_2 --- 2nd call
# KERNEL: UVM_INFO /home/runner/testbench.sv(48) @ 50: uvm_test_top.my_env.my_agent.my_driver [my_driver_c] Passed string = uvm_test_top.my_env.my_agent.my_sequencer.my_sequence_3 --- 1st call
# KERNEL: UVM_INFO /home/runner/testbench.sv(48) @ 60: uvm_test_top.my_env.my_agent.my_driver [my_driver_c] Passed string = uvm_test_top.my_env.my_agent.my_sequencer.my_sequence_3 --- 2nd call
# KERNEL: UVM_INFO /home/build/vlib1/vlib/uvm-1.2/src/base/uvm_objection.svh(1271) @ 60: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase
# KERNEL: UVM_INFO /home/build/vlib1/vlib/uvm-1.2/src/base/uvm_report_server.svh(856) @ 60: reporter [UVM/REPORT/SERVER] 
```

以下各クラスの要点を説明します。

## my_sequence_item_c
uvm_sequenceからuvm_sequencerを介してuvm_driverに渡されるデータを格納するクラスです。今回の例ではDUTはありませんので、簡単にstringをメンバとして持たせるのみにしました。

```verilog
class my_sequence_item_c extends uvm_sequence_item;
  string str;
  `uvm_object_utils(my_sequence_item_c)

  function new(string name = "my_sequence_item_c");
    super.new(name);
  endfunction
endclass
```

## my_sequence_c
body()がシーケンスの本体です。ここではシーケンスアイテムを2回渡しています。両方ともget_full_name()に呼び出し回数を示す文字列を連結したものです。
ちなみにuvm_config_dbを使用したスタートの時のようなobjectionの制御は通常はここでは行いません。本シーケンスをstart()する側で行ったほうがシンプルになるからです。

```verilog
class my_sequence_c extends uvm_sequence #(my_sequence_item_c);
  `uvm_object_utils(my_sequence_c)

  function new(string name = "my_sequence_c");
    super.new(name);
  endfunction

  virtual task body();
    req = my_sequence_item_c::type_id::create("req");

    start_item(req);
    req.str = {get_full_name(), " --- 1st call"};
    finish_item(req);

    start_item(req);
    req.str = {get_full_name(), " --- 2nd call"};
    finish_item(req);
  endtask
endclass
```

## my_driver_c
uvm_sequenceからuvm_sequencerを介してuvm_sequence_itemを受け取ります。重要なのはrun_phaseタスクのみです。本例ではDUTを駆動せず、uvm_sequenceのデータメンバーをログに出力するのみです。オブジェクションの効果を見るためにデータを受け取ってから、時間を10ns進めています。

```verilog
  virtual task run_phase(uvm_phase phase);
    super.run_phase(phase);
    forever begin
      seq_item_port.get_next_item(req);
      #10ns;
      `uvm_info(get_type_name(), $sformatf("Passed string = %s", req.str), UVM_NONE);
      seq_item_port.item_done();
    end
  endtask
```

## my_agent_c
my_driver_cとuvm_sequencerをインスタンス化して接続しています。

```verilog
  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    my_sequencer = uvm_sequencer#(my_sequence_item_c)::type_id::create("my_sequencer", this);
    my_driver = my_driver_c::type_id::create("my_driver", this);
  endfunction

  virtual function void connect_phase(uvm_phase phase);
    my_driver.seq_item_port.connect(my_sequencer.seq_item_export);
  endfunction
```

## my_env_c
my_agenct_cをインスタンス化しています。特に解説するところはありません。


## my_test_c
前述した通りです。
重要なのはmy_test_c::run_phase()のみです。my_sequence_cを3つ、異なるオブジェクトとしてインスタンス化しています。オブジェクションを上げてからmy_sequence_1とmy_sequence_2はfork joinで同時にスタートさせます。この2つはuvm_sequencerにより調停されて順番に処理されます。両方のシーケンス実行が終了すると最後にmy_sequence_3が実行されます。最後にオブジェクションを落としてテストを終了させます。

```verilog
  virtual task run_phase(uvm_phase phase);
    uvm_root uvm_root_hdl = uvm_root::get();
    uvm_sequencer #(my_sequence_item_c) my_sequencer;

    my_sequence_c my_sequence_1 = my_sequence_c::type_id::create("my_sequence_1");
    my_sequence_c my_sequence_2 = my_sequence_c::type_id::create("my_sequence_2");
    my_sequence_c my_sequence_3 = my_sequence_c::type_id::create("my_sequence_3");

    $cast(my_sequencer, uvm_root_hdl.find("uvm_test_top.my_env.my_agent.my_sequencer"));
    if (my_sequencer == null) begin
      `uvm_error(get_type_name(), "The sequencer was not found")
    end
 
    phase.raise_objection(this);
    fork
      my_sequence_1.start(my_sequencer);
      my_sequence_2.start(my_sequencer);
    join
    my_sequence_3.start(my_sequencer);
    phase.drop_objection(this);
  endtask
```

# 参考文献
 "3.10.3 Starting a Sequence on a Sequencer". [Universal Verification Methodology(UVM) 1.2 User's Guide](http://www.accellera.org/images//downloads/standards/uvm/uvm_users_guide_1.2.pdf). pp.48-50.