Systemverilog 語法總結(中)
上一個博客分享了SV基本的概念,這一博客繼續分享,等下一個博客分享一個公司的驗證的筆試題目。
l 事件
背景:
Verilog中當一個線程在一個事件上發生阻塞的同時,正好另一個線程觸發了這個事件,則競爭就出現了。如果觸發線程先於阻塞線程,則觸發無效(觸發是一個零寬度的脈沖)。
解決方法:
Systemverilog 引入了triggered()函數,用於檢測某個事件是否已被觸發過,包括正在觸發。線程可以等待這個結果,而不用在@操作符上阻塞。
例子:
event e1,e2;
Initial begin
->e1;
@e2;
end
Initial begin
->e2;
@e1;
end
上面的代碼,假設先執行第一個塊,再執行第二個塊。第一個塊會阻塞在@e2(阻塞先執行),直到e2觸發,再運行(觸發后執行);在執行第二個塊時,會阻塞在@e1,但是e1已經觸發(觸發先執行,阻塞后執行,觸發是個零寬度的脈沖,會錯過第一個事件而鎖住)
解決方法:用wait(e1.triggered())來代替阻塞@e1,如果先觸發,也可以執行。
l 等待多個事件
最好的辦法是:采用線程計數器來等待多個線程。
l 旗語
get()可以獲取一個或多個鑰匙,put()可以返回一個或多個鑰匙。try_get()獲取一個旗語而不被阻塞。
l 信箱
背景:如何在兩個線程中傳遞信息?考慮發生器需要創建很多事物並傳遞給驅動器的情況。
問題:如果使用發生器的線程去調用驅動器的任務。這樣,發生器需要知道驅動器的層次化路徑(類的層次化),降低了代碼的可重用性;還迫使發生器和驅動器同一速率運行,當一個發生器需控制多個驅動器時會發生同步問題。
解決辦法:把驅動器和發生器當成各個處理事物的對象,之間通過信道交換數據。信道允許驅動器和發生器異步操作;引入問題:你可能傾向於僅僅使用一個共享的數據或隊列,但這樣,編寫實現線程間的讀寫和阻塞代碼會很困難。解決辦法:可以使用systemverilog中的信箱。把信箱看出一個具有源端和收端的FIFO.
操作:
1)信箱的容量可以指定,new(size),size限制信箱中的條目,size為0,或沒指定,則信箱是無限大。
2)put()放數據,get()可以移出數據。peek()可以獲取信箱中數據的copy而不移出。
3)信箱中可以放句柄,而不是對象。
漏洞:在循環外只創建一個對象,然后使用循環對對象隨機化,信箱中是句柄,最終得到的是一個含有多個句柄的信箱,多個句柄都指向同一個對象。
解決辦法:在循環中,創建多個對象。
l 異步線程間使用信箱
背景:
很多情況下,由信箱連接的兩個線程應該步調一致,這樣生產方 才不至於跑到消費方前。 好處:最好層的generator需要等待低層的數據發完后才能結束。測試平台能精確知道所有激勵發出去的時間。
兩種情況
兩個線程同步,需要額外的握手信號。否則,出現生產方運行到結束,消費方還啟動。
信箱容量為1,兩個線程同步
因阻塞,連個線程不需要握手信箱
3) 容量不為1,線程間同步
需要使用握手信號,以使producer不超前於consumer;如果consumer超前於prodecer會阻塞。
解決辦法
1) 使用定容信箱和peek實現線程同步:(比較好)
消費方:consumer 使用信箱方法peek()獲取信箱里的數據的copy而不將其移出,當consumer處理完數據后,便使用get()移出數據。
特點:信箱容量定義為1,不需要握手信號。
calss consumer
repeat(n)begin
Mbx.peek(i);
$display(“consumer:after get( )”,i);
Mbx.get(i);
end
endcalss
如果直接使用get()替代peek(),那么事務會被立刻移出,這樣可能會在consumer完成事務前,producer生成新的數據。
2)使用信箱和事件實現線程同步
使用邊沿敏感的阻塞語句@handshake 代替電平觸發wait(handshake.triggered())。
因為:線程中任務run()使用循環,事件阻塞只能使用@handshake。
局限:如果遇到producer線程的阻塞和consumer線程的觸發同時發生,則可能出現次序上的問題。
3)使用兩個信箱實現線程同步
使用另一個信箱把consumer的完成信息發回給producer。
目的:在producer線程中,處理完事物后,用一個get()來阻塞。
特點:信箱容量大於1.
maibox mbx,rtn;
class prodecer
for(int i=0; i<4;i++) begin
….
mbx.put(i);
rtn.get(i);
…
end
endclass
class consumer
repeat(3) begin
….
mbx.get(i);
rtn.put(-i);
…
end
endclass
說明:信箱的構造函數中mbx =new();rtn =new(),信箱容量為無窮大。如何實現同步?
雖然信箱容量為無窮大,producer線程發完一個數據后遇到get()會阻塞,不能放入第二個數據;等到consumer得到第一個數據並且處理完后,通過另一個信箱返回一個數據,producer才繼續放第二個數據。
因為get()得到數據后,將信箱中數據取出。表象:信箱容量定義為無窮大,但是實際上也是producer放一個數據,consumer取一個數據;然后producer再放第二個數據,依次類推。
這樣確保producer不會超前於consumer線程,而將數據都寫入信箱。
4) 其他的同步技術
通過變量或旗語阻塞也可以實現握手。事件是最簡單的結構,其次是通過變量阻塞。旗語相當於第2個信箱,但是沒有交換信息。Systemverilog中的信箱比其他技術要差,原因是無法在producer放入第一個事務時,讓它阻塞。Producer一直比consumer提前一個事務的時間。
l Wait(handshake.triggered())和@handshake 使用范圍
1) Wait(handshake.triggered()),用於等待一個事件;
2) 循環中等待事件,只能用@handshake
3) 兩個線程的同步,一般任務run()使用循環,所以只能使用@handshake。
注意事項:
1) 在循環中,等待事件不能用Wait(handshake.triggered()),因為如果事件觸發一次,wait()語句一直為真,進入不斷的循環。下一次循環中,不會阻塞。
2) @handshake 如果觸發事件,先於等待事件。會等不到事件,因為(事件觸發,是一個零寬度的脈沖)
OOP的高級編程技巧
l 繼承
背景:
為總線事務增加一個錯誤功能並帶可變延時的復雜類。方法如下:
1) 使用合成,即在類中例化另一個類型的類。有時候很難將功能分成獨立的部分。如果使用合成,則需要為正確和錯誤事務分別創建不同的類,正確類的測試平台需要重寫以處理錯誤類的對象。
2)使用擴展類
作用:
當需要增加事務,而對現有的測試代碼修改越少越好,。例如增加錯誤注入功能。
擴展類和類合成區別:
擴展類解決,增加新事務,使用類合成中,大量修改代碼的麻煩。
如何使用:
擴展類共享基類的變量和子程序。
1)基本類中的方法,需標記為virtual,這樣擴展類中才可以重新定義。擴展類中函數,和基類中函數名一樣時,通過supper.函數名,調用基類中函數。Systemverilog中不允許supper.supper.new方式經行多層調用。
2)如果基類構造函數new()有參數,那么擴展類,必須有一個構造函數,並在構造函數的第一行調用基類的構造函數。
class basel
function new(input int var);
this.var = var;
endfunction
endclass
class extended extends basel
function new(input int var);
super.new(var);
endfunction
endclass
3)OOP規則指出:基類的句柄,也可以指向擴展類的對象。(好好體會)
l 藍圖模式
1)背景:一個簡單的發生器,通過信箱將數據傳遞給驅動器。
class generator
mailbox gen2drv;
transaction tr;
function new(input mailbox gen2drv)
this.gen2drv = gen2drv;
endfunction
task run;
forever begin
tr = new();
assert(tr.randmize);
gen2drv.put(tr); //mail.put(x)
end
endtask
endclass
存在問題:這個例子在循環內部創建事務對象,而不是在循環外部,避免了測試平台常見的錯誤。new()放在循環外部,錯誤原因是,mailbox中放入的是句柄,而不能是對象,所有的句柄都指向同一個對象。(1)任務run創建了一個事物並立即隨機化,意味着事務使用了默認的所有約束。要修改,必須要修改transaction類。(2)無法使用擴展
解決辦法:將tr的創建和初始化分開,使用藍圖模式。
另一個問題:如果簡單的把創建和初始化分開,而放在循環外部,而避免測試平台錯誤(P200),如何解決?藍圖模式如何解決
2)藍圖模式概念:
首先構建一個對象藍圖(金屬模),然后修改它的約束,甚至可以用擴展對象替換它,隨機化這個藍圖時,就得到想賦予的隨機值;然后復制這個對象,將copy發給下游。
藍圖:是一個鈎子,允許你改變發生器類的行為而無需修改其類代碼。藍圖對象在一個地方構建(new()),在另一個地方(任務run)使用
3)P200與P221相對比分析:重要
藍圖模式,也就比new()在循環外地generator多了一個copy函數。問題(1)藍圖模式,new()在循環外,也只有一個對象,而mailbox中放入的只能是句柄,如何解決常見的平台錯誤?
因為copy,是對象的復制,而不是句柄的復制。這樣藍圖模式只有一個句柄,但是隨機化后,copy,相當於再循環中創建了許多對象。而測試平台常見錯誤的本質是,只創建了一個對象。這樣就避免了問題。
(2)藍圖模式下,因為只有一個ID號,那么任務run循環中,下發了許多數據,這些只有一個ID號了?
因為copy是對象的復制,所以在copy中ID號也會增加。下發的每個數據,都有各自的ID號。
l 使用擴展的transaction
為了注入錯誤,需要將藍圖對象transaction變成badtransaction(改變藍圖)。必須在環境的創建和運行階段之間完成這個操作。注意:所有的badTr引用都在這一個文件中,這樣就不需要改變environment類或generator類。
env.build();
begin
badtr bad = new();
env.gen.blueprint = bad;
end
env.run
目的是:將一個對象取代另一個對象。new()后都是對象了,將對象賦值給對象,這是什么寫法?不是復制呀?復制本質是將一個句柄指向一個對象。
解釋:上述是句柄的復制,將擴展類句柄bad賦值給基類句柄blueprint,這樣基類句柄指向擴展類對象,后面的代碼調用的時候,就直接指向擴展類bad了,改變了藍圖。
l env.new()和env.build()區別
env.new()僅僅new()函數
env.build()是將各個模塊new(),並傳達一些參數,通過這些參數將環境的各個模塊,連接起來。P213
l $cast 作類型向下轉換
背景:基類句柄可以指向擴展類對象,不需要額外的代碼; 擴展類句柄指向基類對象,一般情況下會出錯,但有時候是可以的,前提是基類句柄指向了它的一個擴展類對象。
作用:擴展類句柄指向基類對象時,使用$cast()函數。在非法的情況下,不會編譯報錯,會返回了一個0.
$cast做任務使用時,systemverilog會在運行時,檢查源對象類型和目的對象類型不匹配,會報錯;
cast做函數使用時,運行時,仍做類型檢查,在不匹配時,不會報錯,cast 做函數使用時,運行時,仍做類型檢查,在不匹配時,不會報錯,cast做函數使用時,運行時,仍做類型檢查,在不匹配時,不會報錯,函數返回0.
前面所述:基類句柄可以指向任何它的擴展類的對象、
1) 基類句柄指向擴展類對象——出現情況:修改藍圖,不改過多代碼,增加功能
Transaction tr; //基類句柄
BadTr bad; //擴展類句柄
Bad = new();
Tr = bad; // 基類句柄指向擴展類對象
tr.display; //掉用的是擴展類的方法
2) 擴展類句柄指向基類對象——出現情況:基類virtual 方法copy函數,它的繼承類中copy函數
將基類句柄賦值給擴展類句柄,使擴展類句柄指向基類對象,一般編譯器會出錯,不能運行,所以非常小心;只有基類句柄指向擴展類對象時,再將擴展類句柄指向基類對象時,不出錯。為了檢測基類句柄是否指向了擴展對象,並且不讓編譯器報錯,可以使用$cast()函數檢測。
當把擴展類句柄指向基類對象時,發生什么?
Tr= new();
Bad = tr; //擴展類句柄指向基類句柄
上述會發生錯誤,編譯不會被通過。因為有些屬性在基類中不存在;但是擴展類句柄指向基類句柄不總是非法的(見下面代碼,是可以的),當基類句柄指向一個擴展類對象時是允許的。
Transcation tr;
BadTr bad,bad2;
Bad= new();
Tr = bad; //基類句柄指向擴展類對象
$cast(bad2,tr); //擴展類句柄指向基類對象
if(!$cast(bad2,tr);
$display(“cannot assign tr to bad2”);
$display(bad2.bad_crc);
l 句柄類型和對象類型差異(書中翻譯的不准,type of handdle 和 object)
個人理解:
Transaction tr; 句柄tr類型是transaction
句柄類型:關鍵字
對象類型:類中成員的類型差異
l 虛方法和多態
多態:多個程序使用一個共同的名字的現象。(虛方法的重寫與實現)
多態解決問題:計算機建構面臨的一個問題。讓物理內存很小的情況下,讓處理器能夠對很大的地址空間尋址。針對這個問題引入了虛擬內存。
虛擬方法繼承劣勢:
基類使用了虛擬方法,擴展類也必須使用相同的“簽名”,擴展類中虛擬子程序不能增加或刪除參數,這意味着必須提前做好規划。
l 對象復制
1) 因為是virtual 函數,擴展類中copy方法也必須是transaction型的,
但是要copy的是badtr類型的,所以要new一個bad
帶有copy 的事物基類。
class transaction ;
rand bit[31:0] src,dst,data[8];
bit[31:0] crc;
virtual function transaction copy ();
copy = new();
copy.src = src;
copy.dst = dst;
copy.data = data;
copy.crc = crc;
endfunction
endclass
帶有copy的擴展類
calss badtr extends transaction
rand bit bad_crc;
virtual function badtr copy(); //錯誤
virtual function transaction copy();
Badtr bad;
Bad = new();
Bad.src = src;
bad.dst = dst;
bad.data = data;
bad.crc = crc;
Bad.bad_crc = bad_crc;
return bad;
endfunction
endclass
2)優化途徑一,創建一個獨立的函數copy_data,這樣每個類只負責copy其局部變量,即擴展類中的copy函數用super.copy_data(tr),代替了基類中變量的復制。代碼的重用性提高。P8.22
$cast(bad,tr); //擴展類句柄指向基類句柄
使用的情況: 因為virtual 函數,在繼承中,虛擬函數必須和基類中名稱和參數也一致。這樣擴展類中copy_data函數參數仍然是transaction類型的tr,這樣出現了參數是基類句柄,但是copy_data函數內要作的確實擴展類的成員,就要將基類句柄參數賦值給擴展類句柄,
要將擴展類badtr類型的數據返回,所以必須用$cast(bad,tr)。
優化途徑二,最好的。前面的copy子程序都會創建一個新對象,改進的一種方法就是指定復制對象的存放地址。
virtual function transaction copy(transaction to =null);
if(to == null)
copy = new();
else
copy = to;
copy_data(copy);
endfunction
l 抽象類和純虛方法
背景:驗證的目標之一是創建多個項目共享的代碼。
目的:systemverilog 有兩種方法創建共享的基類:抽象類和純虛方法
Virtual class (抽象類):可以被擴展但是不能被直接例化。
Pure virtual function(純虛方法):沒有實體的方法原型,相當於一個聲明。
1) 由抽象類擴展而來的類,只有在所以的虛擬方法都有實體的時候才能被例化。
2) 純虛方法只能在抽象類中定義。
3) 抽象類中,純虛方法是沒實體的,非純虛方法最好也不寫實體。
l 回調
背景:測試平台目的:創建一個不做任何修改就能在所有測試中使用的驗證環境。要做到這點的關鍵是測試平台使用鈎子,(什么是鈎子?)鈎子作用,在不修改原始類的情況下注入新的代碼。采用virtual 方法,也可以在擴展類中覆蓋基類方法,但是需要重復原方法的所有代碼,並且它的修改將傳播到它的所有擴展類中。
作用:回調就是一個鈎子,在不修改原始類的情況下注入新的代碼。
實現:回調任務在頂層中創建,在最低級即驅動器中調用。這樣驅動器不需要知道測試的任何信息,它只需要使用一個可以在測試中擴展的通用類。
1) 使用回調注入干擾
回調的一個常見用法就是注入干擾,例如引入一個錯誤或者延遲。下面測試平台使用回調對象,隨機地丟棄數據包。
擴展類是如何作用的?在擴展的回調類中注入錯誤,如何在驅動器中作用的?
關鍵是數據隊列的作用,驅動器中使用了,回調基類的數據隊列
回調基類是抽象類,在擴展的回調類中加入錯誤注入,而drive驅動類中,是回調基類的數據隊列,在環境中將擴展類句柄讓入驅動器類,回調基類的數據隊列中。
begin // Create error injection callback
Driver_cbs_drop dcd = new();
env.drv.cbs.push_back(dcd); // Put into driver
end
與前面擴展類作用的差異?
前面代碼,要使擴展類中增加代碼,需要使基類句柄指向擴展類句柄。
l 驅動器類:
下面的代碼如何解釋
2)回調也可以想scoreboard 發送數據或收集功能覆蓋率。
優點:
你可能想過將scoreboard和功能覆蓋數據組置於一個事物處理器中,通過郵箱連接到測試平台中,這是一種笨拙的方法。原因如下:測試平台組件幾乎都是被動和異步的,組件只有在測試平台給他數據的時候才被喚醒,而且不會主動地向下游事物處理器傳遞信息。
麻煩:
a)這樣一個需要同時監視多個郵箱的事物處理器復雜了;
b)你可能在多個地方采集數據,但是事物處理器設計用來處理單個數據源回調