07日目~テストの構成と追加

6日目までで、とりあえずマスタとスレーブが協調動作するまで扱いました。

7日目は、テストの増やし方について説明していきたいと思います。

uvm_test

uvm_testは、1つ1つが「従来のテストベクタ」に相当します。10個の検証項目があれば、10個のuvm_testを定義することになります。

それでは、複数のモデルを動かして検証するとき、uvm_testクラスはいくつ必要か。「1つ」です。UVMモデルの数に関係なく、1つの検証項目につき、1つのuvm_testクラスを定義します。

uvm_sequence

uvm_sequenceは、「テストベクタ・テストシナリオに相当する」と書いた気がしますが、表現を変えようと思います。それは、「モデルの振る舞いを定義するファイル」。sequenceの一般的な意味は「順序」 です。モデルをどう動かすのかを示した「順序」を記述するファイル、それがuvm_sequenceです。そして、uvm_sequenceは部品化できます。部品化して、ライブラリ化できます。しかしどうライブラリ化す るのか、どういうメリットがあるのか。今回は、sequenceの階層化、というのを扱ってみます。

uvm_testとuvm_sequence

uvm_testクラスは、UVMテストベンチの最上位階層になります。そして、uvm_envクラスをインスタンスすることで、モデルを組み込んでいます。そして、各モデルのdefault_sequenceを定義することで、各モ デルの振る舞いを指定します。

複数のuvm_testの扱い方

一般的にテストベンチの構成によっては、「テスト項目ごとに再コンパイル、エラボレーションが必要」ということもありますね。小さなテスト環境やDUTならいいのですが、巨大なシステムLSIだと、再コン パイル+エラボレーションだけで数十分かかることはよくあります。

ところでUVMでまとめたテスト環境では、とてもシンプルです。すべてのuvm_testクラスを一緒にコンパイル、エラボレーションしてしまいます。vsim実行時に、+UVM_TESTNAME=xxx という引数を渡すこ とで、Simulationが走ります。違うuvm_testを実行するときは、+UVM_TESTNAME=xxx の指定を変えるだけでいいんです。なぜこれが可能なのか。それは、事前に該当するuvm_testクラスをコンパイルしてい るからで、異なるテストはuvm_testクラス名も異なります。テスト名のバッティングは起こりませんので、事前にすべてコンパイルしておくことができます。

モデルの挙動を、テキストファイルを読み込ませて行う形式のものでは、読み込むファイル名は基本的に固定です。したがって、テストを切り替える際には前の該当ファイルを消去して、どこからかファイルを 「同じ名前」でコピーして実行します。verilog時代ではこの技が便利だったのではと思いますが、ファイルシステムの関係で、LSFなどの仕組みを用いたとき、該当ファイルを消して、違う内容のファイルを 「同じ名前」でコピーしたとしても、それが反映されなくて無駄な時間を消費する、ということも発生します。

ところがUVMの仕組みだと、「すでにコンパイル済み」であるので、このようなファイルシステムと複数マシン間の時間的なズレによる問題が発生しないので、確実にテストを実施することができます。その意 味では、UVMのテスト実行の仕組みは優秀であるといえます。

テストパタンを追加する

それでは、具体的にテストパタンを追加してみましょう。マスタとスレーブの動きは、sequenceで制御しますので、まずはseq_libファイルから。sequenceは「階層化」できますので、1つずつ順番にいきま す。

まずはマスタ側から(sample_master_seq_lib.sv)

class write_seq

を修正します。6日目で暫定で入れたリードは外しました。そして、このsequenceを「他のsequenceから」呼び出して使えるように、メンバを追加しました。

class write_seq extends sample_master_base_seq;
  bit [7:0] addr, data;
  `uvm_object_utils(write_seq)
  function new (string name="write_seq");
    super.new(name);
  endfunction
  virtual task body();
    `uvm_create(req)
    req.write  = 1'b1;
    req.addr   = this.addr;
    req.wdata  = this.data;
    `uvm_send(req)
  endtask
endclass

class read_seq

を追加します。

class read_seq extends sample_master_base_seq;
  bit [7:0] addr, rdata;
  `uvm_object_utils(read_seq)
  function new (string name="read_seq");
    super.new(name);
  endfunction
  virtual task body();
    `uvm_create(req)
    req.write  = 1'b0;
    req.addr   = this.addr;
    `uvm_send(req)
    this.rdata = req.rdata;
  endtask
endclass

class write_read_seq

で、上記write_seqとread_seqを呼び出して使ってみようと思います。

class write_read_seq extends sample_master_base_seq;
  write_seq _write;
  read_seq  _read;
  `uvm_object_utils(write_read_seq)
  function new (string name="write_read_seq");
    super.new(name);
  endfunction
  virtual task body();
    bit [7:0] addr, data;
    addr = 8'h10;
    data = 8'h5A;
    `uvm_create(_write)
    _write.addr = addr;
    _write.data = data;
    `uvm_send(_write)
    `uvm_create(_read)
    _read.addr  = addr;
    `uvm_send(_read)
  endtask
endclass

つぎにスレーブ側(sample_slave_seq_lib.sv)

class random_response_seqを追加します。太字のところがrandom値生成記述になっています。

class random_response_seq extends sample_slave_base_seq;
  `uvm_object_utils(random_response_seq)
  function new (string name="random_response_seq");
    super.new(name);
  endfunction
  virtual task body();
    forever begin
      `uvm_create(req)
      req.wait_cycle <= $urandom_range(8,0);
      `uvm_send(req)
    end
  endtask
endclass

最後にuvm_test(sample_test.sv)

従来のsample_testに加え、sample_test2を定義しました。

違いは、slave側sequencerに指定するsequenceが

  • normal_response
  • random_response

が異なるだけです。

class sample_test extends uvm_test;
  `uvm_component_utils(sample_test)
  sample_env env;
  function new (string name="sample_test", uvm_component parent=null);
    super.new(name,parent);
  endfunction
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    uvm_config_db#(uvm_object_wrapper)::set(this,
        "env.master.sequencer.run_phase", "default_sequence",
        write_read_seq::type_id::get());
    uvm_config_db#(uvm_object_wrapper)::set(this,
        "env.slave.sequencer.run_phase", "default_sequence",
        normal_response_seq::type_id::get());
    env = sample_env::type_id::create("env", this);
  endfunction
  task run_phase(uvm_phase phase);
    uvm_report_info("TEST", "Hello World");
    uvm_top.print_topology();
  endtask
endclass
//-------------------------------------------------------------
class sample_test2 extends uvm_test;
  `uvm_component_utils(sample_test2)
  sample_env env;
  function new (string name="sample_test2", uvm_component parent=null);
    super.new(name,parent);
  endfunction
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    uvm_config_db#(uvm_object_wrapper)::set(this,
        "env.master.sequencer.run_phase", "default_sequence",
        write_read_seq::type_id::get());
    uvm_config_db#(uvm_object_wrapper)::set(this,
        "env.slave.sequencer.run_phase", "default_sequence",
        random_response_seq::type_id::get());
    env = sample_env::type_id::create("env", this);
  endfunction
  task run_phase(uvm_phase phase);
    uvm_report_info("TEST", "Hello World");
    uvm_top.print_topology();
  endtask
endclass

コンパイル

いつも通りにコンパイルします。

実行

uvm_testクラスを2つ定義しているので、二つのテストをさらっと実行することができます。

  1. vsim -c tb_top +UVM_TESTNAME=sample_test -do "run -all;quit"
  2. vsim -c tb_top +UVM_TESTNAME=sample_test2 -do "run -all;quit"

2つの違いは、スレーブの応答タイミングが「固定(1)」かランダム「0~8」かです。波形で見てみましょう。

これは、応答タイミングが固定のもの。

これは、応答タイミングがランダムのもの。