For Good FPGA Design

UVMの環境構築!(4) SequencerとSequence

UVMの環境構築第4回では、シーケンサーとシーケンスの定義(作成)方法について解説していきます。

なお、ソースコードはGitHubに公開しています。

目次

シリーズ目次

UVMの環境構築!シリーズの目次は、第1回 解説編の一番下をご覧ください。

シーケンサーの概要

シーケンサーはuvm_sequencerまたはそのサブクラスを継承して定義します。

シーケンサーは、ドライバーからの要求に対してトランザクションを生成します。uvm_sequencerには次のようにTLM exportが定義されているため、ユーザーはこれをそのまま使うことができます。ただし、このポート名が登場するのは、シーケンサーをインスタンスするエージェントです。

1
2
3
4
5
6
class uvm_sequencer #(type REQ=uvm_sequence_item, RSP=REQ) extends uvm_sequencer_param_base #(REQ, RSP);
  ...
   uvm_seq_item_pull_imp #(REQ, RSP, this_type) seq_item_export;
  ...
  ...
endclass
図1 UVMにおけるシーケンサー

トランザクションを生成するための手順はシーケンスに定義されています。

シーケンサーの定義方法

シーケンサーの定義は通常下記のみです。複雑な処理はベースクラスのuvm_sequencerがやってくれます。

my_sequencer.sv

1
2
3
4
5
6
7
8
9
10
class my_sequencer extends uvm_sequencer #(my_item);
 
    `uvm_component_utils(my_sequencer)
 
    /* Constructor */
    function new(string name, uvm_component parent);
        super.new(name, parent);
    endfunction
 
endclass
  • 1~2行目:uvm_sequencerを継承して、my_sequencerを定義します。トランザクションのタイプをmy_itemにします。
  • 3行目:UVMマクロを書きます。
  • 5~8行目:コンストラクタを定義します。

シーケンスの概要

シーケンスはuvm_sequenceまたはそのサブクラスを継承して定義します。シーケンスはオブジェクトです。コンポーネントではありません。

シーケンサーはコンポーネント
シーケンスはオブジェクト

シーケンスの作成=テストシナリオの作成です。例えば、下に示すmy_sequence1では、1回目はリセット状態での動作、2回目以降はリセット解除しての動作、というシナリオになっています。

シーケンスの定義方法

複数のテストシナリオを作成することを想定して、共通する部分を、シナリオのベースクラスとして定義します。

my_sequence_base.sv

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class my_sequence_base extends uvm_sequence #(my_item);
 
    rand int    n_trial;
    uvm_phase   phase;
 
    `uvm_object_utils_begin(my_sequence_base)
        `uvm_field_int(n_trial, UVM_DEFAULT)
    `uvm_object_utils_end
 
    /* Constrain the number of trials */
    constraint C_N_TRIAL { n_trial inside { [`N_TRIAL_MIN : `N_TRIAL_MAX] }; }
 
    /* Constructor */
    function new(string name = "my_sequence_base");
        super.new(name);
    endfunction
 
    /* Pre body (called once before Body) */
    task pre_body();
        phase = get_starting_phase();
        if (phase != null)
            phase.raise_objection(this, "my_sequence_base");
    endtask;
 
    /* Post body (called once after Body) */
    task post_body();
        if (phase != null)
            phase.drop_objection(this, "my_sequence_base");
    endtask
 
endclass
  • 1行目:uvm_sequenceを継承して、my_sequence_baseを定義します。トランザクションのタイプをmy_itemにします。
  • 3行目:試行回数 n_trial を定義してみました。n_trial をランダム値にするためrandをつけています。
  • 4行目:phaseは18行目、25行目のところで使います。
  • 6~8行目:UVMマクロとフィールドマクロを書きます。シーケンスはオブジェクトなので、`uvm_object_utilsです。
  • 10~11行目:n_trial のランダム値に制約を与えます。制約の値はmy_definitions.svhで次のように定義しました。
1
2
3
/* Sequence */
`define N_TRIAL_MIN    10
`define N_TRIAL_MAX    20
  • 13~16行目:コンストラクタを定義します。
  • 18~23行目:UVMでは、シナリオであるbody()タスクの前に、pre_body()タスクが呼ばれます。pre_body()タスクでは、raise_objection()を呼びます。UVMでは、raise_objection()が呼ばれていないと、シミュレーションの時間を進めることができません。つまり信号をパタパタと変化させることができません。
  • 25~29行目:UVMでは、body()タスクの後に、post_body()タスクが呼ばれます。post_body()タスクでは、drop_objection()を呼びます。raise_objection()でオブジェクション数をカウントアップし、drop_objection()でオブジェクション数をカウントダウンします。オブジェクション数が0になると、シミュレーションを終了します。

my_sequence1.sv

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class my_sequence1 extends my_sequence_base;
 
    `uvm_object_utils(my_sequence1)
 
    /* Constructor */
    function new(string name = "my_sequence1");
        super.new(name);
    endfunction
 
    /* Body */
    virtual task body();
        /* Reset */
        `uvm_do_with(req, {rst == 1;})
 
        /* Reset release and run */
        repeat(n_trial)
            `uvm_do_with(req, {rst == 0;})
    endtask;
 
endclass
  • 1行目:my_sequence_baseを継承して、my_sequence1を定義します。
  • 3行目:UVMマクロを書きます。シーケンスはオブジェクトなので、`uvm_object_utilsです。
  • 5~8行目:コンストラクタを定義します。
  • 10~18行目:body()タスクを定義します。ここがテストシナリオになります。今回は、1回目はリセット状態での動作、2回目以降はリセット解除しての動作というシナリオにしました。
  • 13行目、17行目:`uvm_do()マクロまたは、`uvm_do_with()マクロを使用します。`uvm_do()マクロは次の動作をします。
    • ドライバーからトランザクションの要求があるまで待つ。
    • トランザクションの要求に対し、トランザクションを生成し、そのフィールドに乱数を発生させる。
    • トランザクションをシーケンサーに送信する。シーケンサーはそれをドライバーに送信する。
    • ドライバーの完了通知 item_done() が呼ばれるのを待つ。
  • 13行目、17行目:`uvm_do_with()マクロは、乱数発生のときに制約を追加する際に使用します。今回の例では、rstを1または0にするように制約をかけています。

まとめ

今回は、シーケンサーとシーケンスの定義方法について解説しました。第3回のドライバーと合わせることで、DUTをドライブすることができるようになりました。

シリーズを通してご覧いただけると、UVM検証環境が構築できるようになりますので、ぜひ他のコンポーネントの解説もご覧ください。

シリーズ目次はこちら

アバター画像
この記事を書いた人
ジーノ。大手電機メーカーで、基板設計の全般と、FPGAの設計に従事した経験を活かし、FPGAについて情報発信中。
RTL設計、シミュレーション、タイミング・クロージャ、FPGAまわりのハードウェア開発まで、幅広く取り扱っております。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA