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