OOP:Object-Oriented Programming,有兩點個人認為適合驗證環境的搭建:1)Property(變量)和Method(function/task)的封裝,其實是BFM模型更方便的應
用。2)繼承(Inheritance)與合成(Composition)非常適合一個架構的搭建。
在SV中,類可以定義在program, module, package中,但是一般一個類或幾個相關的類會單獨寫在一個文件中。最終在program中調用。而且在SV中,所有成員
默認都是public類型的,除非顯示的用local/protected來聲明。實際上,SV class中的變量應該盡可能的用public並且rand,來增加可控性。
local/protected都可以針對property和method來說明,local表示member只對該class的對象可見,extend出的subclass也是不可見的。
但是該class中non-local的method調用了local的method或者property,
inherit之后的method也是可以正常執行的。
protected表示member對該class和它的subclass都是可以見的。 對外部的大環境不可見。
基類的指針可以直接指向擴展類的對象,但是只能調用擴展類中基類的那一部分,
基類的指針也可以直接轉換為擴展類的指針,通過$cast函數,此時的基類指針指向的已經是擴展類對象,通過cast,轉變為擴展類指針,可以調用擴展類中自有的元素。
SV默認會為每個類創建一個new函數,並初始化對象(二值變量為0,四值變量為X)。用戶一般自定義new函數賦初值,分配空間可以交給SV來做。
顯式的釋放一個句柄指向的對象的空間 tr=null。
在SV中可以用關鍵字(Static)創建靜態變量,可以讓類有一個類級別的全局變量。一般子初始化時即賦值,還可以建一個靜態的方法,來單獨的操作靜態變量。靜態變
量可以通過類名::變量名的方式來引用。類中的其他變量都默認是automatic的修飾符,而且可以通過this.變量名,來直接調用類一級別的變量。在類的外
部定義方法時,可以用關鍵字extern來在類中聲明,后在類外通過類名::方法名的方式來定義。
當類作為一個function的傳遞參數時,此時不加ref傳遞的是一個句柄的復制量。在下邊的例子中,tr只有一個t的句柄的復制量,所以是Null,之后tr的改變和t沒有直
接關系。
Transaction t; function void create(Transaction tr);
initial begin tr = new ();
creat(t); tr.addr = 42;
$display(t.addr); endfunction
end
一個對象的復制,可以通過簡單的new函數來實現,也可以通過一種自定義的copy函數來實現,這種實現方式在類包含中,很適用。
new函數,當要復制的對象中還包含另一個類的對象時,會直接復制另一個對象的所有東西,包括id,句柄。
Transaction src, dst; function Transaction copy
initial begin copy = new();
src = new; copy.addr = addr;
dst = new src; copy.stats = stats.copy();
end endfunction
copy函數,在每一個類中都定義,在使用時,遞歸調用。這是id號都會刷新,而且因為對象都是new來構造出來的,所以更符合設計意圖。
在一個類中例化另一個類(Composition),這時對方法的調用:ststs.startT.function,為了確保在編譯時,不會因為編譯順序而造成某個類還不能被識別,可以在文
件開頭加 typedef class Statistics, 來先聲明類Statistics。而且此時外層類的new函數中,必須包含內層類的new函數。這種composition的結構非常適合
去構建一個大的驗證框架,因為它可以將各個部分聯系起來。
類的繼承(Inheritance),擴展類擁有基類的一切變量與方法,在擴展類中調用基類的變量與函數,可以通過super.+變量/方法來調用。在擴展類的構造函數中,應該
先對基類先進行new函數。在擴展類的對象中調用對象時,SV比較句柄的類型來決定調用的是基類function還是擴展類function。但是當function是virtual
類型時,SV會根據對象的類型來決定調用基類function還是擴展類function。推薦在需要擴展的基類中使用virtual function的形式,這樣符合OOP的多
態概念,也有利於向下轉換。
class Transaction; class extends Transaction;
rand bit [31:0]src,dst,data[8]; rand bit bad_crc;
bit [31:0]crc; virtual function void calc_crc();
virual function void cal_crc(); super.calc_crc();
crc = src^dst^data.xor; if(bad_crc) crc = ~crc;
endfunction endfunction
endclass: Transaction endclass:Badtr
Transaction tr;
Badtr bad;
initial begin
tr = new ();
tr.calc_crc (); //調用基類類型
bad = new();
bad.calc_crc (); //調用擴展類類型
tr = bad; //基類句柄可以直接指向擴展類對象
tr.calc_crc (); //由對象類型來決定,所以調用BadTr.calc_crc
$cast(bad , tr); //將指向擴展類對象的基類句柄指向擴展類句柄。只有這種情況下,可以成功,$cast非零
bad.calc_crc (); //由對象類型決定,所以調用BadTr.cac_crc
end
BluePrint pattern:其實是類的繼承在可重用驗證環境中的一種典型用法。以Transaction為例,定義一個Transaction的基類,然后再定義一個Transaction的擴展
類,然后在頂層Test中,指定Generator需要哪一個Transaction。其實和UVM中的sequencer調用sequence如出一轍。
class Generator; class Environment;
mailbox gen2drv; Generator gen;
Transaction tr; Driver drv;
function new (input mailbox gen2drv) mailbox gen2drv;
this.gen2drv = gen2drv; function void build; //new函數的構建,mailbox, drv, gen
tr = new(); task run();
endfunction task wrap_up() ;
task run; endclass
Transaction tr1;
forever begin
assert(tr.randomize() );
tr1 = tr.copy(); //copy一份到driver
gen2drv.put (tr1);
end
endtask
endclass
program automatic test;
Environment env;
initial begin
env = new();
env.build(); //new函數
begin
BadTr bad = new();
env.gen.tr = bad; //通過類的頂層關系來調用,Transaction的替換。
end
env.run();
env.wrap_up();
end
endprogram
CallBack:在頂層中定義一個Task,在Driver中調用。便於直接添加task,並且因為是在頂層,所以更好針對項目來定制。
class Driver;
Driver_cbs cbs[$]; //用於存放擴展類的句柄,這個擴展類可以使driver也可以是scoreboard,只要是需要回調的。
task run();
bit drop;
Transaction tr;
forever begin
drop = 0;
agt2drvget(tr);
foreach (cbs[i]) cbs[i].pre_tx(tr, drop); //前回調
if(!drop) continue;
transmit (tr); //最基本的driver就負責一種發送transaction
foreach (cbs[i]) cbs[i].post_tx(tr); //后回調
end
endtask
class Driver_cbs_drop extends Driver_cbs;
scoreboard scb;
virtual task pre_tx (ref Transaction tr, ref bit drop) //從一個純虛類繼承而來,之所以使用純虛類,為了能夠重載
scb.save_expected (tr); //與scoreboard連接起來,
drop = ($urandom_range (0,99) == 0); //隨機丟掉一個transaction
endclass
program autmatic test; class Scoreboard;
Environment env; Transaction scb[$];
initial begin function void save_expect (input Transaction tr)
env= new(); function void compare_actual( input Trans.. tr)
env.build(); enclass //一個簡單的scoreboard模型
begin
Driver_cbs_drop dcd = new();
env.drv.cbs.push_back(dcd); //在頂層加入一個task
end
end
endprogram
純虛類和純虛方法:只為繼承的基類和方法。純虛方法+pure聲明,只需要定義一個prototype,並且只能在純虛類中定義,必須繼承才能使用。同樣純虛類,可以定
義非純虛方法,但是還是必須繼承才能例化。
virtual class BaseTr;
static int count;
int id;
function new();
id = count ++;
endfunction
pure virtual function bit compare(input BaseTr to);
pure virtual function BaseTr copy(input BaseTr to = null)
endclass
還有一個問題:在基類中定義虛方法時,copy函數可以進一步優化。因為虛函數在基類和擴展類中,必須參數一致,返回類型一樣,所以建議將copy_data單獨分出
來。copy_data中傳入指向擴展對象的指針,copy函數內進行new。copy的返回類型可以是基類指針,所以也可以是擴展類對象。
class BadTr extends Transaction
rand bit bad_crc;
virtual function void copy_data(input Transaction tr);
BadTr bad;
super.copy_data(tr); //這樣返回的還是基類句柄,因為copy_data是virtual function
$cast(bad,tr)
bad.bad_crc = bad_crc; //此處返回的應該是基類指針,也可以是擴展類對象
endfunction
。。。。。。。
virtual function void copy(input Transaction1 to == null); //新的copy函數,更加健壯
BadTr bad;
if(to == null)
bad = new();
else bad = to;
copy_data(bad); //擴展類對象。
return bad;
endfunction
基類句柄可以直接指向擴展類對象,此時基類的句柄可以賦值給擴展類的句柄$cast(base,extends),來訪問擴展類中的變量。
如果基類句柄沒有指向擴展類對象,那這個復制會報錯。