UVMでuvm_sequenceを開始させるには以下の2通りの方法があります。
- uvm_config_dbのdefault_sequeneを使う
- 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
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が実行されていることが読み取れます。
# 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をメンバとして持たせるのみにしました。
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()する側で行ったほうがシンプルになるからです。
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進めています。
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をインスタンス化して接続しています。
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が実行されます。最後にオブジェクションを落としてテストを終了させます。
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. pp.48-50.