SystemVerilog基本語法總結(上)


SystemVerilog基本語法總結(上)

在總結SV的語法之前,先分享一些關於SV的筆試題目,這樣更顯得具有針對性的總結。

a. 驗證中,代碼覆蓋率是指(衡量哪些設計代碼在激活觸發,而哪一些則一直處於非激活狀態的統計數據)。
b. SystemVerilog中,從一個類派生一個新類的關鍵字是 (extends)
c. SystemVerilog中,仿真器運行一個用例需要建立多個子線程,這些子線程結束時間各不相同,此時需要使用 (wait fork)語句來等待所有的線程結束
d. SystemVerilog中,int_data[]={9,1,8,3,4,4};執行data.reverse()操作后,data[]的值應該是( {4,4,3,8,1,9})。執行data.rsort()操作后,data[]的值應該是( {9,8,4,4,3,1})。
e. SystemVerilog中,使用隨機函數產生隨機數賦值給信號a[11:0],隨機范圍為3~255:( $urandom_range(3,255)
f. SystemVerilog創建一個數據類型為int的動態數組a:( int a[]),創建一個數據類型為int的隊列b:( int b[$]
g. SystemVerilog中,如何在int類型的隊列queue的后面插入數據data:( {queue, data}
h. SystemVerilog中,rand int src;constrain c_dist{0 :=40, [1:2] :=60};},此時0,1,2的權重分別是多少? 40, 60, 60
i. SystemVerilog中,rand bit[5:0]; constraint c_data {data inside {[$:5},[30:$]},那么變量data的取值范圍是( {[0:5], [30:63]}

 

數據類型

l 合並數組和非合並數組

1)合並數組:

存儲方式是連續的,中間沒有閑置空間。例如,32bit的寄存器,可以看成是4個8bit的數據,或者也可以看成是1個32bit的數據。

表示方法:數組大小和位,必須在變量名前指定,數組大小必須是[msb:lsb] ;如 Bit [3:0] [7:0] bytes ;

2)二維數組和合並數組識別:

合並數組: bit [3:0] [7:0] arrys; 大小在變量名前面放得,且降序

二維數組: int arrays[0:7] [0:3] ; 大小在變量名后面放得,可降序可升序

位寬在變量名前面,用於識別合並和非合並數組,位寬在后面,用於識別數組中元素個數。

3)非合並數組

一般仿真器存放數組元素時使用32bit的字邊界,byte、shortint、int都放在一個字中。非合並數組:字的地位存放變量,高位不用。

表示方法:Bit [7:0] bytes;

4)合並數組和非合並數組的選擇

(1)當需要以字節或字為單位對存儲單元操作。

(2)當需要等待數組中變化的,則必須使用合並數組。

例如測試平台需要通過存儲器數據的變化來喚醒,需要用到@,@只能用於標量或者合並數組。

Bit[3:0] [7:0] barray[3] ; 表示合並數組,合並數組中有3個元素,每個元素時8bit,4個元素可以組成合並數組

可以使用barry[0]作敏感信號。

l 動態數組

隨機事物不確定大小。

使用方法:數組在開始是空的,同時使用new[]來分配空間,在new[n]指定元素的個數。

   int dyn[];

   dyn = new[5];     //分配5個元素空間

   dyn.delete() ;     //釋放空間

l 隊列

在隊列中增加或刪除元素比較方便。

l 關聯數組

當你需要建立一個超大容量的數組。關聯數組,存放稀疏矩陣中的值。

表示方法:采用在方括號中放置數據類型的形式聲明:

Bit[63:0] assoc[bit[63:0]];

l 常量:

1 ) Verilog 推薦使用文本宏。

好處:全局作用范圍,且可以用於位段或類型定義

缺點:當需要局部常量時,可能引起沖突。

2 ) Parameter

作用范圍僅限於單個module

3 ) Systemverilog:

參數可以在多個模塊里共同使用,可以用typedef 代替單調乏味的宏。

過程語句

l 可以在for循環中定義變量,作用范圍僅在循環內部

for(int i=0;i<10;i++)

array[i] =i;

l 任務、函數及void函數

1 ) 區別:(這個任務和函數的區別也是面試經常會被問到的基礎,所以請參考本網站Verilog HDL 任務和函數的區別與聯系)

Verilog中task 和function最重要的區別是:task可以消耗時間而function不能。函數中不能使用#100的延時或@的阻塞語句,也不能調用任務;

Systemverilog中函數可以調用任務,但只能在fork join_none生成的線程中。

2 ) 使用:

如果有一個不消耗時間的systemverilog任務,應該把它定義成void函數;這樣它可以被任何函數或任務調用。
從最大靈活性角度考慮,所有用於調用的子程序都應該被定義成函數而非任務,以便被任何其它任務或函數調用。(因為定義成任務,函數調用任務很有限制)

l 類靜態變量

作用:

1 ) 類的靜態變量,可以被這個類的對象實例所共享。

當你想使用全局變量的時候,應該先想到創建一個類的靜態變量

靜態變量在聲明的時候初始化。

2 ) 類的每一個實例都需要從同一個對象獲取信息。

l 靜態方法

作用:當靜態變量很多的時候,操作它們的代碼是一個很大的程序,可以用在類中創建一個靜態方法讀寫靜態變量,但是靜態方法不能讀寫非靜態變量。

l ref高級的參數類型

ref 參數傳遞為引用而不是復制。ref比 input 、output、inout更好用。

Function void print_checksum(const ref bit [31:0] a[ ]);

1 )也可以不用ref進行數組參數傳遞,這時數組會被復制到堆棧區,代價很高。

2 )用帶ref 進行數組參數傳遞,僅僅是引用,不需要復制;向子程序傳遞數組時,應盡量使用ref以獲得最佳性能,如果不希望子程序改變數組的值,可以使用const ref。

3 )ref參數,用ref 傳遞變量;可以在任務里修改變量而且,修改結果對調用它的函數可見,相對於指針的功能。

l return語句

增加了return語句。task任務由於發現了錯誤而需要提前返回,如果不這樣,那么任務中剩下的語句就必須被放到一個else條件語句中。體會下

task load_array(int len. Ref int array[ ]);

If(len<0)  begin

  $display(“Bad len”);

  return;

//任務中其它代碼

 endtask

l 局部數據存儲 automatic作用

Verilog中由於任務中局部變量會使靜態存儲區,當在多個地方調用同一個任務時,不同線程之間會竄用這些局部變量。

Systemverilog中,module和program塊中,缺省使用靜態存儲;如果想使用自動存儲,需加入automatic關鍵詞。

測試平台

l Interface

背景 :

一個信號可能連接幾個設計層次,如果增加一個信號,必須在多個文件中定義和連接。接口可以解決這些問題。

好處:

如果希望在接口中增加一個信號,不需要改變其他模塊,如TOP模塊。

使用方法:

(1)接口中去掉信號的方向類型;

(2)DUT 和測試平台中,信號列表中采用接口名,例化一個名字

注意:

因為去掉了方向類型,接口中不需要考慮方向信號,簡單的接口,可以看做是一組雙向信號的集合。這些信號使用logic類型[d1] 。

雙向信號為何可以使用logic呢?

這里的雙向,只是概念上的雙向,不想verilog中databus多驅動的雙向。

雙向信號如何做接口?

(1)仲裁器的簡單接口

Interface arb_if( input bit clk);

     logic [1:0] grant,request;

     logic rst;

endinterface

DUT 使用接口:

module arb(arb_if arbif);

always @(posedge arbif.clk or negedge arbif.rst)

    …

endmodule

(2)DUT 不采用接口,測試平台中使用接口(推薦)

DUT 中源代碼不需要修改,只需要再top中,將接口連接到端口上。

   module top;

       bit clk;

       always #2 clk =~clk;

       arb_if arbif(clk);

       arb_port al(.grant(arbif.grant),

                    .request(arbif.grant),

                    .rst(arbif.rst),

                    .clk(arbif.clk)

                );

      test t1(arbif);

   endmodule

n. Modport

背景:

端口的連接方式包含了方向信息,編譯器依次來檢查連續錯誤;接口使用無信號的連接方式。Modport將接口中信號分組並指定方向。

例子:

l 在總線設計中使用modport

並非接口中每個信號都必須連接。Data總線接口中就解決不了,個人覺得?

因為data是一個雙驅動

l 時鍾塊

作用:

一旦定義了時鍾塊,測試平台就可以采用@arbif.cb等待時鍾,而不需要描述確切的時鍾信號和邊沿,即使改變了時鍾塊中的時鍾或邊沿,也不需要修改測試代碼

應用:

將測試平台中的信號,都放在clocking 中,並指定方向(以測試平台為參考的方向)。並且在modprot test(clocking cb,

最完整的接口:

Interface arb_if(input bit clk);

   logic[1:0] grant,request;

   logic rst;

   clocking cb @(posedge clk);

       output request;

       input grant;

   endclocking

   modport test (clocking cb,

                output rst);

   modport dut (input clk, request,rst,

           output grant);

 endinterface

變化:將request 和grant移動到時鍾塊中去了,test中沒有使用了。

l 接口中的雙向信號

Interface master_if(input bit clk);  //在類中為了,不使用有符號數,常用bit[]定義變量

   wire [7:0] data;

   clocking cb@(posedge clk);

        inout data;

   endclocking

modport TEST(clocking cb);

endinterface

program test(master_if mif);

initial begin

   mif.cb.data <= ‘z;

@mif.cb;

$display(mif.cb.data); //總線中讀數據

@mif.cb;

mif.cb.data <= 8’h5a; //驅動總線

@mif.cb;

mif.cb.data <= ‘z; //釋放總線

注:

(1)interface 列表中clk 采用的是input bit clk;為什么要用bit?

(2)時鍾塊 clocking cb 中,一般將testbench中需要的信號,方向指定在這里;

而在modprot 指定test信號方向的時候,采用clocking cb。

(3)interface中信號,不一定都用logic,也可采用wire(雙驅動);systemverilog

中如果采用C代碼的風格(參數列表中方向和類型寫一起),必須采用logic類型

(4)現在的風格,DUT 沒才用clocking cb ,測試平台和DUT的時鍾如何統一?

l 激勵時序

DUT和測試平台之間時序必須密切配合。

l 測試平台和設計間的競爭狀態

好的風格:

使用非阻塞賦值可以減少競爭。

systemverilog驗證中initial 中都采用<= 賦值,而等待延遲采用@arbif.cb等待一個周期來實現。

而verilog中采用的風格時,initial 中采用 =阻塞賦值,沿時可以采用#2,等實現。

因此時鍾發生器,只能放在module 中,而不能放在program中

l program中不能使用always塊

測試平台可以使用initial 但不能使用always,使用always 模塊不能正常工作。

原因:測試平台的執行過程是進過初始化、驅動和響應等步驟后結束仿真。

如果確實需要一個always塊,可以使用initial forever 來完成。比如:在產生時鍾時。

l 類中static變量

背景:

如果一個變量需要被其他對象所共享,如果沒有OPP,就需要創建全局變量,這樣會污染全局名字空間,導致你想定義局部變量,但變量對每個人都是可見的。

1)作用:

類中static變量,將被這個類的所有實例(對象)所共享,使用范圍僅限於這個類。

例:class transaction;

            static int count=0;

                int id;

         endclass

       trasaction tr1,tr2;

Id不是靜態變量,所以每個trasaction對象都有自己的id;count 是靜態變量,所有對象只有一個count變量。

如何用?

當你打算創建一個全局變量的時候,首先考慮創建一個類的靜態變量。

2)static變量的引用

句柄或類名加::

  static 變量的初始化

static變量通常在聲明時初始化。不能在構造函數中初始化,因為每一個新的對象都會調用構造函數。

l 靜態句柄:

背景:當類的每一個對象,都需要從同一個對象(另一個類)中獲取信息的時候。如果定義成非靜態句柄,則每個對象都會有一份copy,造成內存浪費。

l 靜態方法

背景:

當使用更多靜態變量的時候,操作他們的代碼會很長。

作用:

可以在類中創建一個靜態方法用於讀寫靜態變量。

注:systemverilog不允許,靜態方法讀寫非靜態變量。

l 類之外的方法

背景:解決類太長的問題。類最好控制在一頁內,如果方法很都很長。

l This

背景:如果在類很深的底層作用域,卻想引用類一級的對象。在構造函數中最常見。

作用:this指向類一級變量

l 如何做類,類做多大?

上限:類不能太大

當類中存在多處相同的代碼,你需要將這段代碼做成當前類的一個成員函數或父類的成員函數。

下限:類不能太小

類太小,增加了層次。

方法:如果一個小類只被例化了一次,可以將它合並到父類中去。

l 動態對象

概念區分:方法中修改對象 和修改句柄

修改對象——將對象的變量重新賦值。

修改句柄——在任務中new()對象。

1) 當你將對象傳遞給方法

背景:句柄,new()后變成對象,在將其作為參數傳遞給方法。

實質和作用:

傳遞的是句柄。這個方法可以讀取對象中的值;也以改變對象中的值

2) 修改標量變量的值

背景:在方法的參數中,前面加ref;(用ref傳遞,ref傳遞的是變量的地址)。

作用:

方法可以修改變量的值,並將修改的值,傳遞給主程序。

引申:

方法可以改變對象,即使沒有使用ref 修飾句柄。

因為傳遞的是句柄,句柄是地址。不要將句柄和對象混為一談,如果傳遞的是對象,對象是單向的,那方法以外也不能傳遞回來。可以這樣理解吧。

讀寫對象中的值:

例:

Task  transmit(Transcation t);

     cbbus.rx_data <= t.data;

     t.stats.startT  =  $time;     //在任務中,改變了對象

endtask

trancation t;

initilal beign

t = new();

t.addr = 42;

transmit(t);

end

既然傳遞的是句柄,那數據就沒傳過去,如何讀取值?

答:主程序中new()創建了一個對象,而句柄是指向對象的指針,傳遞的是句柄,transmit中也指向了對象,所以transmit中可以讀寫對象。

3) 在任務中修改句柄

背景:

在方法中,參數為句柄,前面加ref。

作用:

可以在方法中new()對象,並將初始化放在方法中;在主程序中僅僅調用。

注意:正確的事物發生器,參數是帶ref的句柄

function void create(ref transaction tr)

endfunction

方法的參數是句柄,句柄前有ref 和沒ref的差別:

沒ref,在方法中不能new()該句柄的對象,因為沒ref,句柄是不能傳遞到主程序的; 有ref,可以在方法中new()該句柄的對象。

原因:沒ref傳遞的是句柄,不能修改句柄,有ref,傳遞的是句柄的地址,可以修改句柄。

例子:

function void create( Transcation tr)

   tr = new();    不正確

   tr.addr = 42;

endfunction

Transcation t;

initial begin

  Create(t);

$diasplay (t.addr);

end

l 程序中修改對象

背景:

應該在循環中,new()多個對象,而不是先new()對象再循環發送事物。

作用:

創建多個對象

正確產生器,創建多個對象:

Task generator (int n);

Transaction t;

Repeat(n) begin

   t=new();

   t.addr =$random();

   transmit(t);

endtask

將new()放在循環內,這樣創建了許多對象。

l 對象的復制

目的:防止對象的方法修改原始對象的值。或在一個發生器中保留約束。

分兩種情況,類中不包含其他類的句柄和包含

方法:

1) 使用new復制一個對象——簡易復制(shallow copy)

Transaction src,dst;

src = new()  //

dst = new src  //復制

局限:

如果類中包含一個指向另外一個類的句柄,那么只有最高一級的對象被new復制,下層的對象都不會被復制。

會出現意想不到的錯誤。

當前類中變量和句柄被復制,這樣兩個對象,都有指向另外一個類的對象statistic(會帶來意想不到的錯誤),但是statistic沒有被復制。如果其中一個transaction對象,修改了statistic對象值,會影響到另一個transaction看到static的值。

2) 簡單的復制函數

如何實現:

Copy函數一般放在類內部,函數名為該類的一個句柄,copy函數中new()對象。

局限:

類中不包含其他類。

3) 深層的復制函數 ——深層copy

目的:解決類中包含另外一個類,copy帶來的問題。

實現:

在copy函數中,將調用另一個類的copy函數,賦值給該句柄;同時需要為statistic類和層次結構中每一個類增加一個copy()方法;copy函數的ID域也要保持一致,copy函數,copy本類,所以ID也要++.

Copy.stats = stats.copy();

Id =count++;

約束

l 約束塊中,只能包含表達式,不能賦值。

1)dist權重分布

dist帶有一個值的列表及相應的權重,中間用:= 或 :/分開。值或權重可以是常量或變量。權重的和不必是100.

:= 表示范圍內,每一個值的權重是相同的;

     表示范圍內,權重要均勻分布

2)Inside

產生一個值的集合,在值的集合中取隨機值時,機會相等。

3)在集合中使用數組

l 條件約束

Systemverilog支持兩種關系操作 –>和if—else

—>可產生和case效果類似的語句塊,可以用於枚舉類型的表達式。

l 雙向約束

l 控制多個約束塊

作用:可以打開或關閉某個約束

可以使用內建的Handle.constraint.constraint_mode()打開或關閉。

l 內嵌約束

背景:很多測試只會在代碼的一個地方隨機化對象,但是約束越來越復雜時,

Systemverilog可以使用randomized with 來增加額外的約束,這和在類里增加的約束是等效的。

l Pre_randomize 和post_randomize函數

有時候需要再調用randomize()之前或之后立即執行一些操作。

隨機化前:設置類里的一些非隨機變量(如上下限、權重),

隨機化后:計算數據的誤差矯正值。

l 約束的技巧

1) 約束中使用變量

2) 使用非隨機值

如果一套約束在已產生了幾乎所有想要的激勵向量,但還缺少幾種。

可以使用rand_mode把這些變量設置為非隨機變量。

l 數組約束

Systemverilog可以用foreach對數組中的每一個元素進行約束。

線程及線程間的通信

l 測試平台使用許多並發執行的線程。測試平台隸屬於程序塊。

Systemverilog引入兩種新的創建線程的方法—fork…join_none和fork…join_any

1) 使用fork…join_none來產生線程

在調度其內部語句時,父線程繼續執行。

2) 使用fork…join_any實現線程同步

在調度塊內語句,當第一個語句執行完,父線程才繼續執行。

l 動態線程

Systemverilog中可以動態創建線程。

用法:

fork…join_none放在了任務中,而不是包含兩個線程。

原因:

主程序中有連個線程:發送和檢測線程。但是不能同時啟動,發送事物后,才能檢測,否則還未產生數據,就開始檢測;但是檢測又不能阻塞下一次發送事物的線程。所以fork…join_none 放在了檢測task 任務(后作用的線程中)中,

例:測試平台產生隨機事物並發送到DUT中,DUT把事物返回到測試平台。測試平台必須等到事物完成,但同時不希望停止隨機事物的發送。

program automatic test(bus_ifc.Tbbus);

task check_trans(Transaction tr);

fork

 begin

  wait(bus.cb.addr == tr.addr);

 end

join_none

endtask

initial begin

repreat(10)  begin

    tr= new();

    assert.(tr.randomize());

    //把事物發送到DUT中

    Transmit(tr);

   //等待DUT的回復

   check_trans(tr);

end

#100;

end

endprogram

l 並發線程中務必使用自動變量來保持數值。

l #0 延遲,使得當前線程必須等到fork…join_none語句中產生的線程執行完后,才得以運行。

l 停止線程

  停止單個線程

使用fork …join_any 后加disable。

3) 停止多個線程

Disable fork 能停止從當前線程中衍生出來得所有子線程。

應該使用fork …join 把目標代碼包含起來,以限制Disable fork的作用范圍

 


免責聲明!

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



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