SystemVerilog中的$cast()向下型別轉換
技術標籤:IC驗證
在之前也介紹過關於$cast()的一些介紹,這篇文章將更加詳細地介紹$cast()向下型別轉換。
在UVM中經常可以看到$cast的身影,這是SV的build-in task之一,當然它還有function的版本,這裡不討論。說到這,不得不提到“型別轉換”這個術語,SV和很多其他語言一樣,都支援特定型別間的相互轉換。SV型別轉換分兩種方法,一種叫靜態型別轉換,另一種稱之為動態型別轉換。靜態型別轉換的語法類似為:
int a = 2;
real b;
b = real'(a);
這種靜態型別轉換一般是不會檢查轉換是否合法的,因此具有一定的危險性。但是$cast的task卻不是這樣,它在執行時將進行型別檢查,如果轉換失敗,會產生執行時錯誤。
$cast可以對不同的內建型別進行轉換,用的更多的是不同層次之間類的轉換。在這種父類於子類之間的轉換裡, 父類站的高,子類在底下,從父類向子類的轉換,稱為向下型別轉換,而子類向父類的轉換稱為向上型別轉換。向上型別轉換是安全的,而反之則是不安全的。
原因在於子類既然繼承了父類,就擁有父類的一切屬性,除此之外,子類還有自己獨特的個性,這些是父類沒有的。當進行向上型別轉換時,相當於父類的控制代碼指向子類物件,這樣的話控制代碼仍然能對子類物件與父類相同的屬性進行訪問。但是反過來,如果向下型別轉換也那麼自由,當試圖把子類的控制代碼指向父類的物件會發生什麼呢?父類本來在記憶體裡就劃好了一小塊地盤,但是因為子類含有比父類更豐富的屬性,它很有可能會訪問父類並不包含的資源,越界了。父類就好像北京的行政區域,子類非要去訪問到河北的地界,河北就不同意了,手機還得算個漫遊。這就會造成嚴重的記憶體溢位,所以向下型別是需要有嚴格的型別檢查的,阻止非法轉換。
很多人看到上面的話不止一次了,但是卻不知道什麼情況下向下型別轉換才會成功。來看一個例子,父親有兩個孩子,孩子0有輛車,而孩子1有架飛機:
class father;
string m_name;
function new (string name);
m_name = name;
endfunction : new
function void print ();
$display("Hello %s", m_name);
endfunction : print
endclass : father
class child0 extends father;
string car = "car";
function new (string name);
super.new(name);
endfunction : new
endclass : child0
class child1 extends father;
string plane = "plane";
function new (string name);
super.new(name);
endfunction : new
endclass : child1
現在來小試牛刀:
module top;
father f;
child0 c0;
child1 c1;
child1 c2;
initial begin
f = new("father");
f.print();
c0 = new("child0");
f = c0;
f.print();
c1 = new("child1");
f = c1;
f.print();
end
endmodule : top
這裡的child賦值給father的語句全部都是向上型別轉換,是安全的,因此直接用=號就能進行轉換,最後打印出來的結果是:
father
child0
child1
假如說反過來,我們這樣:
c1 = f;
或者使用了$cast但是指向物件型別和想轉換的型別有區別:
$cast(c0, f);
都是會失敗的。只有當前父類指標指向的物件和待轉換物件的型別一致時,cast才會成功。我們把上面的程式碼改一改:
initial begin
f = new("father");
f.print();
c0 = new("child0");
f = c0;
f.print();
c1 = new("child1");
f = c1;
f.print();
c1.plane = "J10";
$cast(c2, f);
c2.print();
$display("has %s", c2.plane);
end
這時候轉換成功,因為c1和c2的型別是相同的。這裡玩了個小動作,把c1的plane換成了殲十,打印出來的結果:
Hello father
Hello child0
Hello child1
Hello child1
has J10
千萬小心這樣的控制代碼傳遞,會造成兩個控制代碼同時指向一個物件,當使用其中一個控制代碼對物件的內容進行修改,另一個控制代碼再次訪問的時候就會發現值已經被改變了。同時也可以看出cast的複製效果只是個shadow copy, 這就是為什麼UVM的copy函式在$cast之後還要再對物件裡的屬性進行逐個取出來賦值的原因,這樣的deep copy才能從根本上阻斷源和目的的聯絡。
那麼這樣的型別轉換有什麼好處呢?通常的型別轉換是一種格式的需求,但是繼承類之間的相互轉換,卻有更多的便宜。將獨特的子類轉換為父類的型別,更注重通用性,共性越多,重用性越好。就好像你從宿舍去教室,他從宿舍去食堂,你們到大門這截路大家都能走。但是終究還是要分道揚鑣,這時候要處理具體的內容就需要將父類控制代碼型別轉換為子類型別才能訪問子類特有的資源。
在uvm中的運用
在uvm中運用到子類和父類的轉換很廣泛,主要在driver,monitor,sequence裡面使用,主要的方式有以下兩種:程式碼例項如下
-
driver要通過sqr的seq_item_port介面獲取get_next_item,獲得預設是uvm_sequence_item型別(父類,也可以指定對應的transfer),但是傳輸物件實際是使用者定義的子類transfer的物件,所以要獲得transfer裡面的成員變數,要用cast。
-
driver獲取到uvm_sequence_item型別轉換成transfer物件之後,如果呼叫clone,copy等uvm定義的函式,返回的物件的控制代碼依然是父類的控制代碼,要再次呼叫cast,轉換成子類的物件。
transer req,rsp;
uvm_sequence_item item;
seq_item_port.get_next_item(item); //通過介面預設獲取到的item是sequence item型別的,但是指向的是transfer子類物件
void'(cast(req,item)); //轉換成使用者定義的transfer子類控制代碼
void'($cast(rsp,req.clone())); //呼叫req的clone函式返回的是object的父父類的控制代碼,用轉換成子類的控制代碼
rsp.child_j = 1; //最後才可以訪問子類transfer裡面的成員變數
微信公眾號
建立了一個微信公眾號“Andy的ICer之路”,此公眾號主要分享數字IC相關的學習經驗,文章主要在公眾號上發,csdn會盡量同步更新,有興趣的朋友可以關注一下!