説明書

最終実装(レベル4のもの)についての説明書です。仕様書ではありません。いや、仕様書みたいなもんですが、もう少し噛み砕いて書きたいので、説明書と表記しています。

制限事項

  • レベル4実装では、mode2の比較モードを使わなくても、比較用アイテムの中に mark というメンバが必要でした。これを修正したバージョンをGitHubに置きました。(2014/02/06)
    • mode2を使うときは、`define _GP_SCOREBOARD_MODE2_MARK mark (markという名前は任意) を定義し、比較に使うアイテムの中に、defineで定義した名前(ここでは mark )のメンバを 置いて、そのメンバの型を、スコアボードのインスタンス時とcreate時に渡してください。
  • 今回のバージョンを、rev1.1とします。

概要

このスコアボードの動作概要を説明します。スコアボードは、Simulation中にリアルタイムに比較処理を行うコンポーネントです。2つのanalysis_portを持っており、一方を観測値処理系、他方を期待値処理系 につなぎます。観測値処理系と、期待値処理系は、それぞれ write メソッドを用いてスコアボードに書き込みます。書き込まれたデータは、それぞれのqueue配列に格納されます。

以下は構造イメージです。細かいところは違っています。細かく書くとわかりづらくなるので。図の左側のオレンジ色のひし形は、analysys_portの表現(であってますかね?)のつもりです。

どうやってつなぐのか

  • 準備
    • 4つ、することがあります。
    1. 期待値を生成するclassに、analysis_portをインスタンスします。実装済みであれば不要です。
    2. 観測値を取得するclassに、analysis_portをインスタンスします。実装済みであれば不要です。
    3. テストベンチのuvm_env classの下に、上記2つのclassがインスタンスされていなければなりません。
    4. gp_scoreboardをインスタンスします。
  • 接続例の前提
    • 期待値生成classのインスタンスが、model0.agent.monitor.ap(apはanalysis_portのインスタンス名)とします
    • 観測値取得classのインスタンスが、xxx_if_monitor.apとします
    • gp_scoreboardのインスタンスが、xxx_if_scoreboardとします。
  • 接続
    • uvm_envのconnect_phaseで、以下のように記述します。
  function void connect_phase(uvm_phase phase);
    model0.agent.monitor.ap.connect(xxx_if_scoreboard.ap_obs);
    xxx_if_monitor.ap.connect(xxx_if_scoreboard.ap_exp);
  endfunction

どうやって動かすのか

  • uvm_envのbuild_phaseで、動作モードを設定します。
uvm_config_db#(int)::set(this, "xxx_if_scoreboard", "mode", 1);
  • 共通動作
    • 期待値生成側から、期待値を write メソッドで書き込みます。上記の「接続」ができていると、期待値生成側で実行した write メソッドは、gp_scoreboardの write_exp メソッドを実行したことと等 価な動作になります。書き込んだモノは、期待値格納queue配列に入ります。
    • 観測値取得側から、観測値を write メソッドで書き込みます。gp_scoreboardの write_obs メソッドを実行したことと等価な動作になります。書き込んだモノは、観測値格納queue配列に入ります。
  • mode=0(初期値)は、順次比較です。以下の動作をします。(gp_scoreboard内の task mode0 が動きます)
    • 比較動作は、「観測値」が書き込まれる毎に行います。
    • 期待値が格納されていないとき、または期待値の格納数より観測値の格納数の方が多い時は、スコアボードは空の期待値queueから期待値を取り出そうとしてErrorが発生します。
    • 比較OKのときは、メッセージが表示されますが、何を比較した結果なのかまでは表示していません。
    • 比較NGのときは、不一致したことを示すメッセージが表示されます。
    • Simulation終了時、不一致した期待値と観測値を表示します。
  • mode=1は、網羅比較です。以下の動作をします。(gp_scoreboard内の task mode1 が動きます)
    • 比較動作は、観測値が書き込まれる毎に、すべての期待値と比較します。比較OKのときは、比較に使用した観測値と期待値を、それぞれのqueueから削除します。書き込まれた観測値にマッチする 期待値がないときは、観測値はqueueに残ります。
    • 比較OKでも、NGでも、メッセージは表示しません。(OK時のメッセージは検討します)
    • Simulation終了時、queueに残った(余った)期待値と観測値を表示しますので、そのログから動作不具合や用意した期待値を確認してください。
  • mode=2は、mark比較です。以下の動作をします。(gp_scoreboard内の function mode2 を呼び出します)
    • 比較動作は、同じmark値の期待値と観測値で行います。
    • 期待値生成側、観測値取得側双方において、比較データを格納するclassの中に mark という名前のメンバが必要です。markの「型」は、gp_scoreboardのインスタンス時にparameterで指定しま す。(例:bit [31:0]など)
    • 期待値は、queueに格納されず、mark値をindexに持った連想配列(aa)に格納されます。queueではないので、同じmark値を持った期待値を複数回書き込むと、その都度上書きされます。
    • 観測値を書き込むと、観測値のclassから mark を取り出し、同じ mark を持った期待値と比較しようとします。
      • 同じ mark を持った期待値があれば、比較を行い、比較NGのときだけメッセージが表示されます。
      • 同じ mark を持った期待値がなければ、Error表示を行い、観測値を表示します。

コードの説明

格納されたデータを比較するモードは3つ用意しています。スコアボードのmode変数に0か1か2をセットすることで、処理の仕方が決まります。

比較モードによって、比較エラー時に「エラー」表示するモードと、表示しないモードがあります。それは、比較モードの特性と関連があります。

コードを分けて説明していきます。…っとその前に、1つクエスチョンです。このgp_scoreboard.svには、いくつclassが定義されているでしょうか?

→ 答えは「3つ」です。種明かしをします。

`uvm_analysis_imp_decl(_obs)
`uvm_analysis_imp_decl(_exp)

があります。defineマクロを呼び出しています。この中身なんですが、classなんです。このdefineマクロを実行すると、classが定義されます。

`define uvm_analysis_imp_decl(SFX) \
class uvm_analysis_imp``SFX #(type T=int, type IMP=int) \
  extends uvm_port_base #(uvm_tlm_if_base #(T,T)); \
  `UVM_IMP_COMMON(`UVM_TLM_ANALYSIS_MASK,`"uvm_analysis_imp``SFX`",IMP) \
  function void write( input T t); \
    m_imp.write``SFX( t); \
  endfunction \
  \
endclass

SFXはサフィックスです。頭につけるのはプレフィックスですね。

class uvm_analysis_imp``SFXが、gp_scoreboardだと

class uvm_analysis_imp_obs ...

class uvm_analysis_imp_exp ...

となるわけです。defineマクロの引数を、今回のように展開したいときは `` (バッククォート2つ)を使う…勉強になりますね。

そんなわけで、3つのclassが定義されたファイル、ということになります。

このclass展開が前提となって、

uvm_analysis_imp_obs #(T, gp_scoreboard#(T,M)) ap_obs;
uvm_analysis_imp_exp #(T, gp_scoreboard#(T,M)) ap_exp;

の2行が成立します。

  `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

これはよくみるコードですが、pamameterizeしたUVMのclassでは、

`uvm_component_utils

ではなく、

`uvm_component_param_utils

を使うところがポイント。

なお、この "param" 系のマクロは以下があります。(uvm-1.1d)

`uvm_sequencer_param_utils
`uvm_sequencer_param_utils_begin
`uvm_object_param_utils
`uvm_object_param_utils_begin
`uvm_component_param_utils
`uvm_component_param_utils_begin

parameterizeってよくわかんないんだけど…という方、簡単な説明と例を「05. classの使い方」に書いてありますので、そちらを見てください。

  // queue x 2
  T obs_q [$], exp_q [$];
  T obs_err_q [$], exp_err_q [$];       /// for keep error items

観測値と期待値、それぞれ2種類のqueueを定義しています。error用は、mode0で使用します。

  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

いわゆるコンストラクタです。disable_scoreboardやmodeの値をここで初期化しています。もちろんメンバ定義時にセットしてもいいのですが、なんとなくその方法が嫌いなので、ここで初期化しています。

  /// 期待値書き込みメソッド
  /////////////////////////////
  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

期待値書き込みメソッドです。modeによって、queueに書き込むか、連想配列に書き込むかを選択しています。

  /// 観測値書き込みメソッド
  /////////////////////////////
  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

観測値書き込みメソッドです。modeによって動作を変えています。mode=2のときのevent発行は不要です(消すのを忘れていました)。

  // 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

Simulation時に自動で呼び出されるrun_phaseです。mode=2のときは、write_obsメソッド側で直接mode2メソッドを呼び出しているので、ここでは何もしません。でもちょっと格好悪いので、直すかもしれませ ん。

  // 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

Simulation終了後、Simulatorが停止する前に呼び出されるreport_phaseです。処理内容はシンプルそのものです。

  ///////////////////////////////////////////////////
  /// 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

mode=0のメソッドです。

比較してOKならOK表示だけ(.printするオプションも、Debug時には有効かもですが)。

比較してNGなら、Errorメッセージは.compareメソッド側で勝手に出すので、Error管理queueへ移動させています。

  /// 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

mode=1のメソッドです。

網羅比較なので、foreachで回します。一致したモノについては、一度indexを保持しておいて、その後でqueueから削除しています。一致した時点で消せばいいのでは?という考えもあり、それでも良さそうな のですが、ループ条件に使っているメンバをループ処理内でイジることはバグにつながったりするので、あえてしていません。

  /// 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

mode=2のメソッドです。

見たとおり、連想配列をアクセスして期待値があれば比較を行い、期待値がなければメッセージを表示させています。

処理機構としては、メモリアクセスの検証にも使えますし、複雑なI/Fの検証で、入力したパタンと出力されるパタンの順番が不確定だけど、というケースにも使うことを想定しています。markの型は変更可能 にして、汎用性を持たせています。markというメンバ名は「固定」なのは…手抜きです。すみません。