最終実装(レベル4のもの)についての説明書です。仕様書ではありません。いや、仕様書みたいなもんですが、もう少し噛み砕いて書きたいので、説明書と表記しています。
このスコアボードの動作概要を説明します。スコアボードは、Simulation中にリアルタイムに比較処理を行うコンポーネントです。2つのanalysis_portを持っており、一方を観測値処理系、他方を期待値処理系 につなぎます。観測値処理系と、期待値処理系は、それぞれ write メソッドを用いてスコアボードに書き込みます。書き込まれたデータは、それぞれのqueue配列に格納されます。
以下は構造イメージです。細かいところは違っています。細かく書くとわかりづらくなるので。図の左側のオレンジ色のひし形は、analysys_portの表現(であってますかね?)のつもりです。
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_config_db#(int)::set(this, "xxx_if_scoreboard", "mode", 1);
格納されたデータを比較するモードは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というメンバ名は「固定」なのは…手抜きです。すみません。