SystemVerilog基本語法總結(中)


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)你可能在多個地方采集數據,但是事物處理器設計用來處理單個數據源回調

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM