最終実装(レベル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); endfunctionuvm_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 \ \endclassSFXはサフィックスです。頭につけるのはプレフィックスですね。
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_beginparameterizeってよくわかんないんだけど…という方、簡単な説明と例を「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 endtaskSimulation時に自動で呼び出される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 endfunctionSimulation終了後、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 endtaskmode=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 endmode=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 endfunctionmode=2のメソッドです。
見たとおり、連想配列をアクセスして期待値があれば比較を行い、期待値がなければメッセージを表示させています。
処理機構としては、メモリアクセスの検証にも使えますし、複雑なI/Fの検証で、入力したパタンと出力されるパタンの順番が不確定だけど、というケースにも使うことを想定しています。markの型は変更可能 にして、汎用性を持たせています。markというメンバ名は「固定」なのは…手抜きです。すみません。