02. 汎用比較器 for UVM

いわゆるスコアボード(scoreboard)です。一部の実装にUVMクラスライブラリを使用しています。

仕様

  • 期待値と観測値を入力すると、自動で比較を行う
    • 比較順序
      • 順番に比較
        • 期待値x番目と観測値x番目を比較。一致したアイテムは削除。不一致→Error
      • 順番に依存しない比較(網羅比較)
        • 特定のタイミングで、複数の期待値と複数の観測値を網羅比較。一致したアイテムは削除。不一致Errorは発生しない
      • マーク比較
        • 同じフラグを持ったアイテム同士を比較。一致したアイテムは削除。不一致→Error
  • 比較方法
    • アイテム同士の全比較
    • アイテム同士の部分比較
      • `uvm_xxx_utils_begin - endの間に記述するuvm_field_xxxで、比較したくないメンバに UVM_NOCOMPAREを記述する

実装(レベル4)

  • 更新内容
    • mark比較を実装しました。実装制限がありますので、以下を参照してください。
  • 制限
    • 「余った期待値」アイテムはreport_phaseで表示されません。「余った観測値」は期待値不在時に発生し、観測値monitorから書き込まれた時点で比較してError表示されます。
    • 比較アイテム(type T)は、uvm_objectから派生したclassをextends定義したclassであって、class内に「mark」という名前のメンバを定義してください。このメンバの型は自由ですが、 gp_scoreboardの2つ目のparameter "M" に指定する必要があります。具体的には、gp_scoreboardをインスタンスするときと、build_phaseでcreateするときに指定してください。
    • markという名前、`defineで名前定義させても良かったかも…。

制限部の実装例

--- class trans_item extends uvm_object;

    :
  bit [31:0] mark;
    :
  `uvm_object_utils_begin(trans_item)
    `uvm_field_int(mark,  UVM_DEFAULT|UVM_NOCOMPARE)
    :

--- class env_top extends uvm_env;

    :
  gp_scoreboard #(trans_item, bit [31:0]) scoreboard;
    :
  function void build_phase(uvm_phase phase);
    :
    scoreboard = gp_scoreboard #(trans_item, bit [31:0])::type_id::create("scoreboard", this);
    :
`ifndef _GP_SCOREBOARD
`define _GP_SCOREBOARD
`uvm_analysis_imp_decl(_obs)
`uvm_analysis_imp_decl(_exp)
class gp_scoreboard #(type T=uvm_object, type M=uvm_object) extends uvm_scoreboard;
  uvm_analysis_imp_obs #(T, gp_scoreboard#(T,M)) ap_obs;
  uvm_analysis_imp_exp #(T, gp_scoreboard#(T,M)) ap_exp;
  protected bit   disable_scoreboard;
  protected byte  mode;
  protected event insert_obs_e;

protected T marked_db[M]; /// aa for marked item, used for mode2

  `uvm_component_param_utils_begin(gp_scoreboard#(T,M))
    `uvm_field_int(disable_scoreboard, UVM_DEFAULT)
    `uvm_field_int(mode, UVM_DEFAULT)
  `uvm_component_utils_end
  // queue x 2
  T obs_q [$], exp_q [$];

T obs_err_q [$], exp_err_q [$]; /// for keep error items

  function new (string name, uvm_component parent);
    super.new(name, parent);
    // gen analysis port
    ap_obs = new("ap_obs", this);
    ap_exp = new("ap_exp", this);
    // initialize members
    disable_scoreboard = 1'b0;
    mode = 8'h0;
  endfunction : new
  /// 期待値書き込みメソッド
  /////////////////////////////
  function void write_exp(T data, M mark=null);
    uvm_report_info("SCRBD", "write expected data");
    case(mode)
      8'h00, 8'h01 : exp_q.push_back(data);
      8'h02   : marked_db[data.mark] = data;
      default : exp_q.push_back(data);
    endcase
  endfunction
  /// 観測値書き込みメソッド
  /////////////////////////////
  function void write_obs(T data, M mark=null);
    uvm_report_info("SCRBD", "write observed data");
    case(mode)
      8'h00, 8'h01 : obs_q.push_back(data);
      8'h02   : mode2(data, mark);
      default : obs_q.push_back(data);
    endcase
    if(mode==8'h01 || mode==8'h02) -> insert_obs_e;
  endfunction
  // UVM run phase
  task run_phase(uvm_phase phase);
    if(disable_scoreboard==0)begin
      case(mode)
        8'h00 : mode0;
        8'h01 : mode1;
        8'h02 : ; // none
        default : mode0;
      endcase
    end
  endtask
  // UVM report phase
  virtual function void report_phase(uvm_phase phase);
    if(mode==8'h00)begin
      obs_q = obs_err_q;
      exp_q = exp_err_q;
    end
    if(obs_q.size()!=0)begin
      uvm_report_info("SCRBD", "observed data queue is not empty");
      foreach (obs_q[i]) obs_q[i].print;
    end
    if(exp_q.size()!=0)begin
      uvm_report_info("SCRBD", "expected data queue is not empty");
      foreach (exp_q[i]) exp_q[i].print;
    end
  endfunction
  ///////////////////////////////////////////////////
  /// compare mode0
  /// 順次比較
  virtual task mode0;
    forever begin
      wait(obs_q.size()!=0);
      if(exp_q[0].compare(obs_q[0]))begin
        uvm_report_info("SCRBD", "data compare OK");
        obs_q.delete(0);
        exp_q.delete(0);
      end else begin
        obs_err_q.push_back(obs_q.pop_front());
        exp_err_q.push_back(exp_q.pop_front());
      end
    end
  endtask
  /// compare mode1
  /// 網羅比較
  ///   観測値、期待値それぞれにmatching_index_qを用意し、比較でマッチした
  ///   indexを管理。網羅比較後、該当indexを削除する。
  virtual task mode1;
    uvm_comparer comparer;
    int unsigned obs_index_q[$], exp_index_q[$];
    comparer = new;
    comparer.show_max = 0;
    forever begin
      @insert_obs_e;
      obs_index_q.delete();
      exp_index_q.delete();
      /// compare all obs - all exp
      foreach (obs_q[i]) begin
        foreach (exp_q[j]) begin
          //if(obs_q[i].compare(exp_q[j]))begin
          if(obs_q[i].compare(exp_q[j], comparer))begin
            obs_index_q.push_back(i);
            exp_index_q.push_back(j);
          end
        end
      end
      /// delete matching item
      foreach (obs_index_q[i]) begin
        obs_q.delete(obs_index_q[i]);
      end
      foreach (exp_index_q[i]) begin
        exp_q.delete(exp_index_q[i]);
      end
    end
  endtask
  /// compare mode2
  /// マーク比較
  ///   期待値を、指定したindexで管理し、同じindexの観測値が入力されたら
  ///   比較を行う。
  virtual function void mode2(T data, M mark);
    if(marked_db.exists(data.mark))begin
      data.compare(marked_db[data.mark]);
    end else begin
      uvm_report_info("SCRBD", "---------- following Observed Data exist, but Related Expected Data is not found. ----------");
      data.print;
    end
  endfunction
endclass
`endif

実装(レベル3)

  • 更新内容
    • 網羅比較機能を実装しました。期待値queueと観測値queueを網羅比較します。比較処理は、観測値側のwriteメソッドを叩く時に発生するeventで行います。網羅比較なので、コンペアエラーは起こ しません。
    • report_phaseを実装しました。
      • 順番比較モードのときは、コンペアエラー発生時の期待値と観測値をprintで表示させます。
      • 網羅比較モードのときは、比較一致したアイテムをqueueから抜いていきますので、Simulation終了時に余ったアイテムたちをprintします。
    • 以下の制御メンバを実装しました。
      • bit disable_scoreboard(初期値はenable)
      • byte mode
        • 0:順番比較
        • 1:網羅比較
  • 遊び方
    • レベル2とほとんど変わりません。上記2つの制御メンバを切り替えてみてください。
`ifndef _GP_SCOREBOARD
`define _GP_SCOREBOARD
`uvm_analysis_imp_decl(_obs)
`uvm_analysis_imp_decl(_exp)
class gp_scoreboard #(type T=uvm_object, type M=uvm_object) extends uvm_scoreboard;
  uvm_analysis_imp_obs #(T, gp_scoreboard#(T,M)) ap_obs;
  uvm_analysis_imp_exp #(T, gp_scoreboard#(T,M)) ap_exp;
  protected bit   disable_scoreboard;
  protected byte  mode;
  protected event insert_obs_e;

protected T marked_db[M]; /// aa for marked item, used for mode2

  `uvm_component_param_utils_begin(gp_scoreboard#(T,M))
    `uvm_field_int(disable_scoreboard, UVM_DEFAULT)
    `uvm_field_int(mode, UVM_DEFAULT)
  `uvm_component_utils_end
  // queue x 2
  T obs_q [$], exp_q [$];

T obs_err_q [$], exp_err_q [$]; /// for keep error items

  function new (string name, uvm_component parent);
    super.new(name, parent);
    // gen analysis port
    ap_obs = new("ap_obs", this);
    ap_exp = new("ap_exp", this);
    // initialize members
    disable_scoreboard = 1'b0;
    mode = 8'h0;
  endfunction : new
  /// 期待値書き込みメソッド
  /////////////////////////////
  function void write_exp(T data, M mark=null);
    uvm_report_info("SCRBD", "write expected data");
    case(mode)
      8'h00, 8'h01 : exp_q.push_back(data);
      8'h02   : marked_db[mark] = data;
      default : exp_q.push_back(data);
    endcase
  endfunction
  /// 観測値書き込みメソッド
  /////////////////////////////
  function void write_obs(T data, M mark=null);
    uvm_report_info("SCRBD", "write observed data");
    case(mode)
      8'h00, 8'h01 : obs_q.push_back(data);
      8'h02   : mode2(data, mark);
      default : obs_q.push_back(data);
    endcase
    if(mode==8'h01 || mode==8'h02) -> insert_obs_e;
  endfunction
  // UVM run phase
  task run_phase(uvm_phase phase);
    if(disable_scoreboard==0)begin
      case(mode)
        8'h00 : mode0;
        8'h01 : mode1;
        8'h02 : ; // none
        default : mode0;
      endcase
    end
  endtask
  // UVM report phase
  virtual function void report_phase(uvm_phase phase);
    if(mode==8'h00)begin
      obs_q = obs_err_q;
      exp_q = exp_err_q;
    end
    if(obs_q.size()!=0)begin
      uvm_report_info("SCRBD", "observed data queue is not empty");
      foreach (obs_q[i]) obs_q[i].print;
    end
    if(exp_q.size()!=0)begin
      uvm_report_info("SCRBD", "expected data queue is not empty");
      foreach (exp_q[i]) exp_q[i].print;
    end
  endfunction
  ///////////////////////////////////////////////////
  /// compare mode0
  /// 順次比較
  virtual task mode0;
    forever begin
      wait(obs_q.size()!=0);
      if(exp_q[0].compare(obs_q[0]))begin
        uvm_report_info("SCRBD", "data compare OK");
        obs_q.delete(0);
        exp_q.delete(0);
      end else begin
        obs_err_q.push_back(obs_q.pop_front());
        exp_err_q.push_back(exp_q.pop_front());
      end
    end
  endtask
  /// compare mode1
  /// 網羅比較
  ///   観測値、期待値それぞれにmatching_index_qを用意し、比較でマッチした
  ///   indexを管理。網羅比較後、該当indexを削除する。
  virtual task mode1;
    uvm_comparer comparer;
    int unsigned obs_index_q[$], exp_index_q[$];
    comparer = new;
    comparer.show_max = 0;
    forever begin
      @insert_obs_e;
      obs_index_q.delete();
      exp_index_q.delete();
      /// compare all obs - all exp
      foreach (obs_q[i]) begin
        foreach (exp_q[j]) begin
          //if(obs_q[i].compare(exp_q[j]))begin
          if(obs_q[i].compare(exp_q[j], comparer))begin
            obs_index_q.push_back(i);
            exp_index_q.push_back(j);
          end
        end
      end
      /// delete matching item
      foreach (obs_index_q[i]) begin
        obs_q.delete(obs_index_q[i]);
      end
      foreach (exp_index_q[i]) begin
        exp_q.delete(exp_index_q[i]);
      end
    end
  endtask
  /// compare mode2
  /// マーク比較
  ///   期待値を、指定したindexで管理し、同じindexの観測値が入力されたら
  ///   比較を行う。
  virtual function void mode2(T data, M mark);
    if(marked_db.exists(mark))begin
      data.compare(marked_db[mark]);
    end else begin
      uvm_report_info("SCRBD", "---------- following Marked Expected Data is not found. ----------");
      mark.print;
    end
  endfunction
endclass
`endif

実装(レベル2)

  • 更新内容
    • class名をgp(general purpose)_scoreboardに変更しました
    • analysis_portを追加しました
      • ap_obs:analysis_port for observed value(観測値)
      • ap_exp:analysis_port for expected value(期待値)
    • ifdefでうっかり多重読み込みを排除しました。
    • 観測値待ち処理を、while文からwait文に変更しました。
  • 遊び方
    • 前回(レベル1)とは全く変わります
    • 期待値を書き込むcomponentと、観測値を書き込むcomponentにanalysis_portを持たせておき、connect_phaseで接続します。
      • 例:xxx_env.xxx_agent.xxx_monitor.ap.connect(scoreboard.ap_obs); ///観測値側
      • 例:xxx_env.xxx_agent.xxx_monitor.ap.connect(scoreboard.ap_exp); ///期待値側
      • ※例示したインスタンスは適当です。同じモニタに2つのポートをつなぐことはないでしょう
  • 仕様制限
    • 現在のインプリでは、「期待値は入っている」前提で処理しています。期待値がなくても観測値を入れるとqueueからアイテムを取り出すので、emptyだとErrorが発生します。
  • その他
    • 現時点のコードは、使いこなせる方は好きに使ってください。
`ifndef _GP_SCOREBOARD
`define _GP_SCOREBOARD
`uvm_analysis_imp_decl(_obs)
`uvm_analysis_imp_decl(_exp)
class gp_scoreboard #(type T=uvm_object) extends uvm_scoreboard;
  uvm_analysis_imp_obs #(T, gp_scoreboard#(T)) ap_obs;
  uvm_analysis_imp_exp #(T, gp_scoreboard#(T)) ap_exp;
  `uvm_component_param_utils(gp_scoreboard#(T))
  // queue x 2
  T data_q [$];
  T exp_q  [$];
  function new (string name, uvm_component parent);
    super.new(name, parent);
    // gen analysis port
    ap_obs = new("ap_obs", this);
    ap_exp = new("ap_exp", this);
  endfunction : new
  function void write_obs(T data);
    uvm_report_info("SCRBD", "write data");
    data_q.push_back(data);
  endfunction
  function void write_exp(T data);
    uvm_report_info("SCRBD", "write exp");
    exp_q.push_back(data);
  endfunction
  task run_phase(uvm_phase phase);
    T tmp_data0, tmp_data1;
    uvm_report_info("SCRBD", "Called run_phase");
    forever begin
      //while(data_q.size()==0) #1;
      wait(data_q.size()!=0);
      tmp_data0 = new;
      tmp_data1 = new;
      tmp_data0 = data_q.pop_front();
      tmp_data1 = exp_q.pop_front();
      if(tmp_data1.compare(tmp_data0))begin
        uvm_report_info("SCRBD", "data compare OK");
      end
      tmp_data0 = null;
      tmp_data1 = null;
    end
  endtask
endclass
`endif

実装(レベル1)

とりあえず実装してみました。TLMのポートは使っていません。(ヘボくてすみません)

どこが汎用なのかというと、比較するデータ型をパラメータ化しているので、比較したいデータの型をユーザが指定できるところです。

遊んでみたい方は使ってみてください。

データを比較するメンバが固定(決まっている)のときは、以下に示すtrans_itemの「xxx_utils_begin ~ end」のところで通常はUVM_DEFAULTにしているところを、UVM_DEFAULT | UVM_NOCOMPARE、 とすれば比較対象から外れる(はず)です。

遊び方:

  1. uvm_env classにインスタンスします。
    1. sample_scrbd #(trans_item) scoreboard;
    2. ここで、trans_itemは「比較したいデータを固まりにしたclass名」を書きます。
  2. build_phaseに以下を追加します。
    1. scoreboard = sample_scrbd #(trans_item)::type_id::create("scoreboard", this);
  3. モニタやシーケンスをscoreboardにつなぎます。やり方は
    1. モニタなら
      1. モニタにscoreboardをインスタンスします
      2. uvm_envのconnect_phaseで、uvm_envにインスタンスしたscoreboard=xxx_env/xxx_agent/xxx_monitor/scoreboard; みたいな書き方でつなぎます
    2. シーケンスなら
      1. 該当するsequencerに、上記モニタと同じような書き方で追加します
      2. シーケンスからは、p_sequencer.scoreboard.xxxというアクセスができるはずです
  4. trans_itemをnewします
  5. trans_itemに値をセットします
  6. scoreboardにセットします
    1. 観測値なら、scoreboard.write_data(trans_item)
    2. 期待値なら、scoreboard.write_exp(trans_item)
  7. 4~6を、Simulation中繰り返します。比較は自動で行われ、
    1. 比較OKなら [SCRBD] data compare OK
    2. 比較NGなら reporter [MISCMP] xxx が2行出ます

3番のつなぎ方は、virtual interfaceの感覚に似ています。

適切な書き方だと、モニタにTLMのポートを持っておいて、それとスコアボードのTLMポートをつないで動かす方が、元のモニタを変更せずに済むのでその方がいいのですが、それは次回トライします。

以下の記述で、最後の方でnullを入れているのは、その方が無駄にメモリを使わない気がしたのでそうしています。

class sample_scrbd #(type T=uvm_object) extends uvm_scoreboard;
  `uvm_component_param_utils(sample_scrbd#(T))
  // queue x 2
  T data_q [$];
  T exp_q  [$];
  function new (string name, uvm_component parent);
    super.new(name, parent);
  endfunction : new
  function void write_data(T data);
    uvm_report_info("SCRBD", "write data");
    data_q.push_back(data);
  endfunction
  function void write_exp(T data);
    uvm_report_info("SCRBD", "write exp");
    exp_q.push_back(data);
  endfunction
  task run_phase(uvm_phase phase);
    T tmp_data0, tmp_data1;
    uvm_report_info("SCRBD", "Called run_phase");
    forever begin
      while(data_q.size()==0) #1;
      tmp_data0 = new;
      tmp_data1 = new;
      tmp_data0 = data_q.pop_front();
      tmp_data1 = exp_q.pop_front();
      if(tmp_data1.compare(tmp_data0))begin
        uvm_report_info("SCRBD", "data compare OK");
      end
      tmp_data0 = null;
      tmp_data1 = null;
    end
  endtask
endclass