08日目~ランダム転送と期待値自動比較

8日目になりました。これまでは、やっていること自体はそう大したことをしていません。技術的な土台を作り上げるためですので。今回は、すこし面白いことをしようと思います。ランダムデータを転送し、 読みだして期待値比較させますが、期待値比較は「自動化」します。sequenceの中で書き込みデータをランダム生成し、それを配列に入れておいて、ライトを行ったあとにリードしてリードデータを if 文で比 較することはできますが、例えば1000回のライトリードコンペアを行うとして、

  • 1000回ライト後、1000回リードして、1000回コンペア

するのは簡単ですね。比較処理は、foreach で回せます。それでは次のはどうでしょう?

  • ランダム数ライト後、同じ数だけリードして、同じ数だけコンペアし、この合計を1000回行う

この場合、task化しなければとてもやってられませんね。ではさらに複雑化させて、アドレスをランダム化させたらどうなるでしょうか?この場合、どのアドレスにどのデータを書いたか、sequence内で保持し ておかなければ、比較できませんね。言葉で表現するのは簡単だけれど、実際にどうやってコーディングしよう…となると、この辺からスキルの差が生まれてきたりします。

今回は、このような複雑なテストケースであっても、容易に比較ができることを説明します。そのためには、このサイトで無償公開している「汎用比較器」を用います。

汎用比較器(スコアボード)を組み込む

スコアボードを使うためには、「analysis_port」というのをモニタに実装する必要があります。今回は、マスタのモニタを使います。また汎用比較器では、比較に使用するデータはクラスに入れて使う仕様なの で、データを格納するクラスを定義します。このファイルは、モデル依存なので、model ディレクトリの中に置いて、includeファイル sample_model.svhに加えます。

class sample_scrbd_item extends uvm_object;
  bit [7:0] addr, data;
  `uvm_object_utils_begin(sample_scrbd_item)
    `uvm_field_int(addr, UVM_DEFAULT)
    `uvm_field_int(data, UVM_DEFAULT)
  `uvm_object_utils_end
  function new (string name="sample_scrbd_item");
    super.new(name);
  endfunction
endclass

ここで、uvm_field_int ですが、これはクラスに定義したメンバ(addr, data)に対して、特殊な属性を付加するマクロです。UVM_DEFAULTと書かれたところが、その属性をセットするところで、種類はたくさ んあります。今回は、汎用比較器の中でこの「属性」を利用しているので、定義しています。この属性値については、後日どこかのエントリーで書いてみたいと思います。

上記クラスは、モデル依存のファイルですので、モデル置き場(model/ )に置くとします。すると、sample_model.svhは以下のようになります。

`include "sample_seq_item.sv"
`include "sample_master_seq_lib.sv"
`include "sample_slave_seq_lib.sv"
`include "sample_scrbd_item.sv"
`include "sample_master_driver.sv"
`include "sample_master_monitor.sv"
`include "sample_master_sequencer.sv"
`include "sample_master_agent.sv"
`include "sample_slave_driver.sv"
`include "sample_slave_sequencer.sv"
`include "sample_slave_agent.sv"
`include "sample_env.sv"

このクラスを使って、analysis_portでやり取りするための定義をします。変更内容は

  1. 期待値用のanalysis_portを組み込む(太字)
  2. 観測値用のanalysis_portを組み込む(太字)
  3. analysis_portを通して、スコアボードへ書き込む(黄色)
class sample_master_monitor extends uvm_monitor;
  virtual sample_if vif;
  uvm_analysis_port #(sample_scrbd_item) ap_write;
  uvm_analysis_port #(sample_scrbd_item) ap_read;
  event scrbd_e;  //イベント定義
  `uvm_component_utils(sample_master_monitor)
  function new (string name, uvm_component parent);
    super.new(name, parent);
    ap_write = new("ap_write", this);
    ap_read  = new("ap_read", this);
  endfunction
  function void build_phase(uvm_phase phase);
    bit status;
    super.build_phase(phase);
    status = uvm_config_db#(virtual sample_if)::get(this, "", "vif", vif);
    if(status==1'b0)
      uvm_report_fatal("NOVIF", {"virtual interface must be set for: ",get_full_name(),".vif"});
  endfunction
  task run_phase(uvm_phase phase);
    string s_trans_kind;
    uvm_report_info("MONITOR", "Hi");
    fork scrbd_write; join_none  //スコアボード処理タスクを飛ばす
    forever begin
      @(posedge vif.valid);
      wait(vif.ready===1'b1);
      -> scrbd_e;    // イベント発行
      if(vif.write===1'b1)
        uvm_report_info("MON", $sformatf("write addr=%02xh wdata=%02xh", vif.addr, vif.wdata));
      else if(vif.write===1'b0)
        uvm_report_info("MON", $sformatf("read  addr=%02xh rdata=%02xh", vif.addr, vif.rdata));
      else
        uvm_report_info("MON", $sformatf("signal write is unknown..."));
    end
  endtask
  task scrbd_write;
    sample_scrbd_item item;
    forever begin
      @scrbd_e;
      item = new();
      item.addr = vif.addr;
      if(vif.write===1'b1)begin
        item.data = vif.wdata; // write側のanalysis_port
        ap_write.write(item);
      end else if(vif.write===1'b0)begin
        item.data = vif.rdata;
        ap_read.write(item);   // read側のanalysis_port
      end
    end
  endtask
endclass

つぎに、テストベンチ側のuvm_envクラスを定義します。クラス名はtb_envとでもしましょうか。ついでに、テストベンチ系ファイルを、tb というディレクトリに入れることにします。gp_scoreboardには、2 つのanalysis_portがあります。monitorに追加したanalysis_port2つと、gp_scoreboardにある2つのanalysis_portをつなぎます。

class tb_env extends uvm_env;
  sample_env    sample_model;  //インスタンス名はenvでもいいが、もし複数のモデルを組み込むならば、個々に判別できる
                               //名前でなければならないため、このようなインスタンス名にしている
  gp_scoreboard #(sample_scrbd_item) sample_scrbd;  // 汎用スコアボード
  `uvm_component_utils(tb_env)
  function new (string name, uvm_component parent);
    super.new(name, parent);
  endfunction
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    sample_model = sample_env::type_id::create("sample_model", this);
    sample_scrbd = gp_scoreboard#(sample_scrbd_item)::type_id::create("sample_scrbd", this);
  endfunction

// monitorとscoreboardのanalysis_portを接続する

  function void connect_phase(uvm_phase phase);
    sample_model.master.monitor.ap_write.connect(sample_scrbd.ap_exp); //期待値用
    sample_model.master.monitor.ap_read.connect(sample_scrbd.ap_obs);  //観測地用
  endfunction
endclass

汎用比較器、gp_scoreboard.svを用意します。こちらからどうぞ。rev1.2が最新です。

ファイルをどこかに置いて、tb_top.svにincludeします。太字が変更箇所です。

`timescale 1ps/1ps
module tb_top;
  // UVM class library
  `include "uvm_macros.svh"
  import uvm_pkg::*;
  // common library
  `include "gp_scoreboard.sv"
  // uvm user code
  `include "sample_model.svh"
  `include "tb_env.sv"
  `include "sample_test.sv"
  /////////////////////////////////////

次に、uvm_testクラスを修正します。いままでは、sample_envを直接組み込んでいましたが、あるべき姿としては、sample_envはtb_envに組み込まれる方が適切です。sample_test.svを修正します。 sample_test2のクラスも、同様に修正してください。なお、せっかくクラスを使っているのだから、extendsでやらないのか?と思われる方もいると思います。その通りなのですが、ステップを踏んでいくこと で「面倒な手間を省く」ことも知ってほしいので、意図的です。

class sample_test extends uvm_test;
  `uvm_component_utils(sample_test)
  tb_env tb;
  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,
        "tb.sample_model.master.sequencer.run_phase", "default_sequence",
        write_read_seq::type_id::get());
    uvm_config_db#(uvm_object_wrapper)::set(this,
        "tb.sample_model.slave.sequencer.run_phase", "default_sequence",
        normal_response_seq::type_id::get());
    tb = tb_env::type_id::create("tb", this);
  endfunction
  task run_phase(uvm_phase phase);
    uvm_report_info("TEST", "Hello World");
    uvm_top.print_topology();
  endtask
endclass

実行とログ(その1)

コンパイルして実行します。

# UVM_INFO @ 300: uvm_test_top.tb.sample_model.master.monitor [MON] write addr=10h wdata=5ah

# UVM_INFO @ 300: uvm_test_top.tb.sample_scrbd [SCRBD] write expected data

# UVM_INFO @ 600: uvm_test_top.tb.sample_model.master.monitor [MON] read addr=10h rdata=5ah

# UVM_INFO @ 600: uvm_test_top.tb.sample_scrbd [SCRBD] write observed data

# UVM_INFO @ 600: uvm_test_top.tb.sample_scrbd [SCRBD] data compare OK

ログの部分抽出ですが、スコアボード(SCRBD)のログが出ていることがわかります。期待値を書き込んだメッセージ、観測値を書き込んだメッセージ、そして比較結果が表示されます。

ランダム転送の検討

商用シミュレータなら簡単にできるのですが、無償で使えるmodelsim-aseでは「ただのランダム」しか発生できません。具体的に言うと、SystemVerilogの制約 constraintが使えませんので、ちょっと工夫が必要 です。この文章を書いている時点では、まだ考え中なのです。

それでは、手順を踏んで組み込んでみましょう。

  1. バス仕様がアドレス8bitかつデータバス8bitなので、0番地から255番地まで、データはランダムで発行します。
  2. ランダム回数かつ同じアドレスには書かず、同じ回数リードするがアクセスする順番はライトと同じではないものを実現してみます。

1番の仕様

write_read_all というsequenceを書いてみたいと思います。

class write_read_all_seq extends sample_master_base_seq;
  write_seq _write;
  read_seq  _read;
  `uvm_object_utils(write_read_all_seq)
  function new (string name="write_read_all_seq");
    super.new(name);
  endfunction
  virtual task body();
    int i;
    for(i=0; i<256; i=i+1)begin
      `uvm_create(_write)
      _write.addr = i;
      _write.data = $urandom_range(255,0);
      `uvm_send(_write)
    end
    for(i=0; i<256; i=i+1)begin
      `uvm_create(_read)
      _read.addr = i;
      `uvm_send(_read)
    end
  endtask
endclass
他に、sample_slave_driver.svも修正してください。(これはバグ修正)
seq_item_port.item_done(req);  //reqを消してください
uvm_testクラスの追加ですが、このページの一番下を御覧ください。記述量削減したコードを掲載しています。

1番の仕様のログ

長いので、部分的に掲載します。連続ライト→連続リードのため、以下は連続リードの一部だけ抜き出しています。

# UVM_INFO @ 227300: uvm_test_top.tb.sample_scrbd [SCRBD] write observed data

# UVM_INFO @ 227300: uvm_test_top.tb.sample_scrbd [SCRBD] data compare OK. addr=77h

# UVM_INFO @ 227600: uvm_test_top.tb.sample_model.master.monitor [MON] read addr=78h rdata=b1h

# UVM_INFO @ 227600: uvm_test_top.tb.sample_scrbd [SCRBD] write observed data

# UVM_INFO @ 227600: uvm_test_top.tb.sample_scrbd [SCRBD] data compare OK. addr=78h

# UVM_INFO @ 228100: uvm_test_top.tb.sample_model.master.monitor [MON] read addr=79h rdata=37h

# UVM_INFO @ 228100: uvm_test_top.tb.sample_scrbd [SCRBD] write observed data

# UVM_INFO @ 228100: uvm_test_top.tb.sample_scrbd [SCRBD] data compare OK. addr=79h

# UVM_INFO @ 228700: uvm_test_top.tb.sample_model.master.monitor [MON] read addr=7ah rdata=4dh

# UVM_INFO @ 228700: uvm_test_top.tb.sample_scrbd [SCRBD] write observed data

# UVM_INFO @ 228700: uvm_test_top.tb.sample_scrbd [SCRBD] data compare OK. addr=7ah

# UVM_INFO @ 228900: uvm_test_top.tb.sample_model.master.monitor [MON] read addr=7bh rdata=9fh

# UVM_INFO @ 228900: uvm_test_top.tb.sample_scrbd [SCRBD] write observed data

# UVM_INFO @ 228900: uvm_test_top.tb.sample_scrbd [SCRBD] data compare OK. addr=7bh

# UVM_INFO @ 229300: uvm_test_top.tb.sample_model.master.monitor [MON] read addr=7ch rdata=6eh

# UVM_INFO @ 229300: uvm_test_top.tb.sample_scrbd [SCRBD] write observed data

# UVM_INFO @ 229300: uvm_test_top.tb.sample_scrbd [SCRBD] data compare OK. addr=7ch

2番の仕様

write_read_rand_all というsequenceを書いてみたいと思います。コードを説明しておくと、

  1. 0~255まで順番に配列に格納します(アドレス用)
  2. 連続ライトする回数を、$urandom_rangeで生成します。
  3. 1番の配列から、ランダムにインデックスをアクセスして、転送用アドレスとして取得します。
  4. このアドレスを、配列に格納しておきます。
  5. 2番で得た回数分、ライトします。
  6. 終わったら、同じ数だけリードします。ただしこのとき、4番で得た配列にランダムにアクセスし、アドレスを取得します。
class write_read_rand_all_seq extends sample_master_base_seq;
  write_seq _write;
  read_seq  _read;
  `uvm_object_utils(write_read_rand_all_seq)
  function new (string name="write_read_rand_all_seq");
    super.new(name);
  endfunction
  virtual task body();
    int i, remain, len;
    bit [7:0] addr_q[$], read_addr_q[$];
    // アドレス生成
    for(i=0; i<256; i=i+1)begin
      addr_q.push_back(i);
    end
    while(addr_q.size() != 0)begin
      len = $urandom_range(16,1);  // 1-16のrandom値生成
      if( (addr_q.size()-len) < 0 ) len = remain;
      //remain = remain - len;
      $display("==================================================================");
      uvm_report_info("SEQ", $sformatf("Transaction Start. length=%0d", len));
      for(i=0; i<len; i=i+1)begin
        bit [7:0] addr;
        `uvm_create(_write)
        addr = gen_rand_addr(addr_q);
        read_addr_q.push_back(addr);
        _write.addr = addr;
        _write.data = $urandom_range(255,0);
        `uvm_send(_write)
      end
      $display("------------------------------------------------------------------");
      for(i=0; i<len; i=i+1)begin
        `uvm_create(_read)
        _read.addr = gen_rand_addr(read_addr_q);
        `uvm_send(_read)
      end
    end
  endtask
  function bit [7:0] gen_rand_addr(ref bit [7:0] addr_src_q[$]);
    int index;
    bit [7:0] rval;
    //$display("remain=%0d", addr_src_q.size());
    index = $urandom_range(addr_src_q.size()-1,0);
    rval = addr_src_q[index];
    addr_src_q.delete(index);
    return rval;
  endfunction
endclass

このテストを実行するためには、他にも手を加える必要があります。ちょっと綺麗な作りでないです。すみません。

1.gp_scoreboardをmode2で動かします。そのためには…

sample_master_monitor.svを修正します。

  task scrbd_write;
    sample_scrbd_item item;
    forever begin
      @scrbd_e;
      item = new();
      item.addr = vif.addr;
      if(vif.write===1'b1)begin
        item.data = vif.wdata; // write側のanalysis_port
        item.`_GP_SCOREBOARD_MODE2_MARK = vif.addr;
        ap_write.write(item);
      end else if(vif.write===1'b0)begin
        item.data = vif.rdata;
        item.`_GP_SCOREBOARD_MODE2_MARK = vif.addr;
        ap_read.write(item);   // read側のanalysis_port
      end
    end
  endtask

2.tb_top.svに define を加えます。

`timescale 1ps/1ps

`define _GP_SCOREBOARD_MODE2_MARK mark
module tb_top;

3.tb_env.svを修正します。

class tb_env extends uvm_env;
  sample_env    sample_model;
  gp_scoreboard #(sample_scrbd_item, bit[7:0]) sample_scrbd;
  `uvm_component_utils(tb_env)
  function new (string name, uvm_component parent);
    super.new(name, parent);
  endfunction
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    uvm_config_db#(int)::set(this, "sample_scrbd", "mode", 2);
    sample_model = sample_env::type_id::create("sample_model", this);
    sample_scrbd = gp_scoreboard#(sample_scrbd_item, bit[7:0])::type_id::create("sample_scrbd", this);
  endfunction
  function void connect_phase(uvm_phase phase);
    sample_model.master.monitor.ap_write.connect(sample_scrbd.ap_exp); //期待値用
    sample_model.master.monitor.ap_read.connect(sample_scrbd.ap_obs);  //観測地用
  endfunction
endclass

4.sample_scrbd_item.svを修正します。

class sample_scrbd_item extends uvm_object;
  bit [7:0] `_GP_SCOREBOARD_MODE2_MARK;
  bit [7:0] addr, data;
  `uvm_object_utils_begin(sample_scrbd_item)
    `uvm_field_int(addr, UVM_DEFAULT)
    `uvm_field_int(data, UVM_DEFAULT)
  `uvm_object_utils_end
  function new (string name="sample_scrbd_item");
    super.new(name);
  endfunction
endclass

たぶんこれらの変更で動くはずです。(ちょっとバグ潰しながら書いていたので、抜けがあるかもしれません)

2番の仕様のログ

長いので、部分的に掲載します。アドレスへのアクセス順番がランダムなのがわかると思います。

# ==================================================================

# UVM_INFO @ 297200: uvm_test_top.tb.sample_model.master.sequencer@@write_read_rand_all_seq [SEQ] Transaction Start. length=7

# UVM_INFO @ 297900: uvm_test_top.tb.sample_model.master.monitor [MON] write addr=bah wdata=0bh

# UVM_INFO @ 297900: uvm_test_top.tb.sample_scrbd [SCRBD] write expected data

# UVM_INFO @ 298900: uvm_test_top.tb.sample_model.master.monitor [MON] write addr=e1h wdata=7dh

# UVM_INFO @ 298900: uvm_test_top.tb.sample_scrbd [SCRBD] write expected data

# UVM_INFO @ 299500: uvm_test_top.tb.sample_model.master.monitor [MON] write addr=90h wdata=6bh

# UVM_INFO @ 299500: uvm_test_top.tb.sample_scrbd [SCRBD] write expected data

# UVM_INFO @ 300200: uvm_test_top.tb.sample_model.master.monitor [MON] write addr=74h wdata=07h

# UVM_INFO @ 300200: uvm_test_top.tb.sample_scrbd [SCRBD] write expected data

# UVM_INFO @ 300900: uvm_test_top.tb.sample_model.master.monitor [MON] write addr=03h wdata=49h

# UVM_INFO @ 300900: uvm_test_top.tb.sample_scrbd [SCRBD] write expected data

# UVM_INFO @ 301900: uvm_test_top.tb.sample_model.master.monitor [MON] write addr=e9h wdata=94h

# UVM_INFO @ 301900: uvm_test_top.tb.sample_scrbd [SCRBD] write expected data

# UVM_INFO @ 302200: uvm_test_top.tb.sample_model.master.monitor [MON] write addr=50h wdata=d4h

# UVM_INFO @ 302200: uvm_test_top.tb.sample_scrbd [SCRBD] write expected data

# ------------------------------------------------------------------

# UVM_INFO @ 302800: uvm_test_top.tb.sample_model.master.monitor [MON] read addr=e1h rdata=7dh

# UVM_INFO @ 302800: uvm_test_top.tb.sample_scrbd [SCRBD] write observed data

# UVM_INFO @ 302800: uvm_test_top.tb.sample_scrbd [SCRBD] data compare OK. addr=e1h

# UVM_INFO @ 303400: uvm_test_top.tb.sample_model.master.monitor [MON] read addr=50h rdata=d4h

# UVM_INFO @ 303400: uvm_test_top.tb.sample_scrbd [SCRBD] write observed data

# UVM_INFO @ 303400: uvm_test_top.tb.sample_scrbd [SCRBD] data compare OK. addr=50h

# UVM_INFO @ 304400: uvm_test_top.tb.sample_model.master.monitor [MON] read addr=e9h rdata=94h

# UVM_INFO @ 304400: uvm_test_top.tb.sample_scrbd [SCRBD] write observed data

# UVM_INFO @ 304400: uvm_test_top.tb.sample_scrbd [SCRBD] data compare OK. addr=e9h

# UVM_INFO @ 304800: uvm_test_top.tb.sample_model.master.monitor [MON] read addr=90h rdata=6bh

# UVM_INFO @ 304800: uvm_test_top.tb.sample_scrbd [SCRBD] write observed data

# UVM_INFO @ 304800: uvm_test_top.tb.sample_scrbd [SCRBD] data compare OK. addr=90h

# UVM_INFO @ 305600: uvm_test_top.tb.sample_model.master.monitor [MON] read addr=bah rdata=0bh

# UVM_INFO @ 305600: uvm_test_top.tb.sample_scrbd [SCRBD] write observed data

# UVM_INFO @ 305600: uvm_test_top.tb.sample_scrbd [SCRBD] data compare OK. addr=bah

# UVM_INFO @ 306300: uvm_test_top.tb.sample_model.master.monitor [MON] read addr=03h rdata=49h

# UVM_INFO @ 306300: uvm_test_top.tb.sample_scrbd [SCRBD] write observed data

# UVM_INFO @ 306300: uvm_test_top.tb.sample_scrbd [SCRBD] data compare OK. addr=03h

# UVM_INFO @ 307000: uvm_test_top.tb.sample_model.master.monitor [MON] read addr=74h rdata=07h

# UVM_INFO @ 307000: uvm_test_top.tb.sample_scrbd [SCRBD] write observed data

# UVM_INFO @ 307000: uvm_test_top.tb.sample_scrbd [SCRBD] data compare OK. addr=74h

記述量を減らしたuvm_testクラス記述

これまでの分と、今回追加した2つのuvm_testクラスの定義になります。

`define set_seq(INST,SEQ) \
  uvm_config_db#(uvm_object_wrapper)::set(this,`"INST`","default_sequence",SEQ::type_id::get());
`define uvm_test_head(NAME) \
  class NAME extends sample_test_base; \
    `uvm_component_utils(NAME) \
    function new (string name=`"NAME`", uvm_component parent=null); \
      super.new(name,parent); \
    endfunction
//-------------------------------------------------------------
virtual class sample_test_base extends uvm_test;
  `uvm_component_utils(sample_test_base)
  tb_env tb;
  function new (string name="sample_test_base", uvm_component parent=null);
    super.new(name,parent);
  endfunction
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    tb = tb_env::type_id::create("tb", this);
  endfunction
  task run_phase(uvm_phase phase);
    uvm_report_info("TEST", "Hello World");
    uvm_top.print_topology();
  endtask
endclass
//-------------------------------------------------------------
`uvm_test_head(sample_test)
  function void build_phase(uvm_phase phase);
    `set_seq(tb.sample_model.master.sequencer.run_phase, write_read_seq)
    `set_seq(tb.sample_model.slave.sequencer.run_phase,  normal_response_seq)
    super.build_phase(phase);
  endfunction
endclass
//-------------------------------------------------------------
`uvm_test_head(sample_test2)
  function void build_phase(uvm_phase phase);
    `set_seq(tb.sample_model.master.sequencer.run_phase, write_read_seq)
    `set_seq(tb.sample_model.slave.sequencer.run_phase,  random_response_seq)
    super.build_phase(phase);
  endfunction
endclass
//-------------------------------------------------------------
`uvm_test_head(sample_test3)
  function void build_phase(uvm_phase phase);
    `set_seq(tb.sample_model.master.sequencer.run_phase, write_read_all_seq)
    `set_seq(tb.sample_model.slave.sequencer.run_phase,  random_response_seq)
    super.build_phase(phase);
  endfunction
endclass
//-------------------------------------------------------------
`uvm_test_head(sample_test4)
  function void build_phase(uvm_phase phase);
    `set_seq(tb.sample_model.master.sequencer.run_phase, write_read_rand_all_seq)
    `set_seq(tb.sample_model.slave.sequencer.run_phase,  random_response_seq)
    super.build_phase(phase);
  endfunction
endclass