説明書
最終実装(レベル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つ、することがあります。
- 期待値を生成するclassに、analysis_portをインスタンスします。実装済みであれば不要です。
- 観測値を取得するclassに、analysis_portをインスタンスします。実装済みであれば不要です。
- テストベンチのuvm_env classの下に、上記2つのclassがインスタンスされていなければなりません。
- 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というメンバ名は「固定」なのは…手抜きです。すみません。