天天看點

system verilog程式設計題_SystemVerilog基本文法總結(上)

資料類型

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的作用範圍