System Verilog芯片驗證 System Verilog語言
1、數據類型
Verilog本身是來做硬件描述,是對硬件本身的行為進行建模。 SystemVerilog是Verilog的生命延續,.sv是對SystemVerilog進行編譯,.v是對Verilog進行編譯,SystemVerilog文件對Verilog是完全兼容的,所以把.v文件改成.sv文件進編譯是允許的,SystemVerilog是側重於Verification的語言。
SystemVerilog是Verilog的3.0版本,相比較Verilog將寄存器類型(register)即reg(存儲數據)和線網類型(net)即wire(模擬硬件連線)分得很清楚,在sysytem verilog中引入了新的數據類型logic,他們的區分和聯系在於:
1、Verilog作為硬件描述語言,傾向於設計人員自身懂得所描述的電路中哪些變量應該被實現為reg,而哪些變量應該被實現為wire。這不但有利於后端綜合工具,也更便於閱讀和理解。而System Verilog作為側重於驗證的語言,並不十分關切logic對應的邏輯應該綜合為reg和wire,因為logic被使用的場景如果是驗證環境,那么它只會作為單純的變量進行賦值操作,而這些變量也只屬於軟件環境的構建。
2、logic類型被推出的另外一個原因也是為了方便驗證人員驅動和鏈接硬件模塊、而省去考慮究竟該用reg和wire的精力,這即節省了時間,也避免了出錯的可能,logic可能被綜合為reg也可能會綜合為wire。 與logic相對應的是bit類型,它們均可以構建矢量類型(vector),logic為四值邏輯,即可以表述為0、1、X、Z(沒有被驅動),bit為二值邏輯,只可以表述為0和1。之所以要引入二值邏輯,因為在systemVerilog里期望將硬件的世界與軟件的世界分開,在硬件的事件里更多的是用logic(四值事件),在軟件的事件里更多的是用bit去做(二值事件)。
四值邏輯類型:integer(32位)、logic、reg、net-type 與Verilog里面某些變量對應
二值邏輯類型:byte、shortint、int(32位)、longint、bit 與C里面有些變量對應
如果按照有符號和無符號的類型進行划分,那么可以將常見的變量類型划分為:
有符號類型:byte、shortint、int、longint、integer
無符號類型:bit、logic、reg、net-type (例如wire、tri)
//example 已知程序求display的結果
logic [7:0] logic_vec = 8’b 1000_0000; bit [7:0] bit_vec = 8’b 1000_0000; byte signed_vec = 8’b1000_0000; initial begin $display(“logic_vec = %d”, logic_vec ); $display (“bit_vec = %d”, bit_vec ); $display (“signed_vec = %d”, signed_vec ); end
結果分別是128,128 ,-128,$display調用byte,因為是有符號數,最高位1代表負數,那結果應該是0,可是8位有符號數的取值為-128 ~ 127,從-127~127都可以正常表示,他們只需要7位,而0對應的有兩個,為了解決數值一一映射的問題,-128由1000_0000表示。
2、內建數據類型
盡量避免兩種不一致的變量(不同數據類型、有無符號、數據位寬)進行操作,導致意外的錯誤,需要強制類型轉換。
靜態轉化用:想要的數據類型 ’(),轉成無符號數:unsigned’()
//example 求兩次display的值
byte signed_vec = 8’b 1000_0000 ; //有符號8位
bit [8:0] result_vec; //無符號9位
initial begin
result_vec = signed_vec; //直接把有符號數賦給無符號數是不對的
$display(“@1 result_vec = ‘h%x”, result_vec); //以十六進制輸出
result_vec = unsigned’(signed_vec); //強制類型轉換后再賦值(高位默認填0)
$display(“@2 result_vec = ‘h%x”,result_vec); end
答案:’h180, ’h080
動態轉化用$cast(tgt,scr)。 靜態類型轉換一般是不會檢查轉換是否合法的,因此具有一定的危險性。如i = int’ (10.0-0.1), 但是動態轉換函數$cast卻不是這樣,它在運行時將進行類型檢查,如果轉換失敗,會產生運行錯誤。動態轉化會在方針的時候做操作,而靜態轉化是在編譯的時候做操作。靜態轉換和動態類型轉換都需要操作符號和系統函數介入,我們統稱為顯示轉換。不需要進行轉換的操作叫隱式轉換。
如下博客里寫得非常好
https://blog.csdn.net/seabeam/article/details/47841539
四值邏輯X、Z變為二值邏輯只會轉化為0,不同長度的類型賦值會把高位截掉。
//example 求兩次display的結果
logic [3:0] x_vec = ‘b111x; //4位寬4值邏輯
bit [2:0] b_vec; //3位寬兩值邏輯
initial begin $display(“@1 x_vec =’b%b , x_vec”); b_vec = x_vec; $display(“@2 b_bec=’b%b” , b_vec); end
答案 ‘b110,高位被截掉
3、數組聲明
方法: 數據類型+名字+寬度
int lo_hi [0:15]; //16 位int [0]...[15] int c_style [16]; //16 位int [0]...[15]
多維數組的聲明和使用
int array2 [0:7][0:3]; //完整聲明
int array3 [8] [4]; //緊湊聲明 左高右低,高維度為8,低維度為4
array2 [7][3]=1; //賦值最后一個元素
//初始化和賦值
int ascend [4] = ’{0,1,2,3}; //對4個元素初始化
int descend [5];
descend = ‘ {4,3,2,1,0} ; //為5個元素賦值
descend [0:2] =’{5,6,7} ; //定向賦值
ascend = ‘{4{8}} ; //四個值全為8
descend = ‘{9,8, default:1 };// {9, 8 ,-1, -1, -1}
存儲空間考量,以節省空間為導向,中括號放前面是連續存儲的,放后面是非連續存儲的。
//下面兩個數組都可以表示24bit的數據容量,但實際硬件容量的角度出發,哪種方式的存儲空間更小呢?(8bit是一個byte,32bits是一個word)
bit [3][7:0] b_pack ; //合並型,會分配連續的24bit的內存空間,占據32bits空間,系統是總是以一個word整數倍分配空間
bit [7:0] b_unpack[3] ; //非合並型,會分配三個不連續(並行的)的內存空間,每個內存空間有8個bit, 占據3個word的存儲空間;

//如果使用logic類型來存儲24bit的數據,實際的存儲空間應該是多少呢?
logic [3][7:0] 1_pack; //由於是四值類型,故占用連續的48bit,即2個word就夠了
logic [7:0] 1_unpack[3]; //3是分開存儲的,而 8是連續存儲的,需要3個word
如何判斷最高維度和最低維度
int [8][4] array [16][2] //1 2 3 4維度為16 2 8 4,最高維度為4
4、for循環和foreach循環
initial begin
bit [31:0] scr[5] dst[5]; //數組聲明另一種聲明方式,數據類型,寬度,名字,寬度
for (int i=0; i<$size(scr) ; i++) //調用系統函數,$size (src,1),如果不寫即$size(src)默認為最高維度
src[i] = i; 對數組做循環時,用foreach比較方便 foreach ( dst [ j ] ) //默認創建為J的變量為維度的索引,如果為 dst [ j ] = src [ j ] *2; //bit [31:0] scr[10:6] 用foreach寫的比較方便 end
5、基本數組操作復制和比較
對於賦值,可以利用賦值符號“=”直接進行數組的復制。
對於比較,可以用“==”或者“!==”來比較數組的內容。
數組復制用單引號
bit [31:0] src[5] = ‘{0,1,2,3,4}; dst[5] =’{5,4,3,2,1}; if (src==dst) $display(“src == dst”); //比較數組 else $dispaly (“sec !=dst”); dst = src; //數組復制 src[0] = 5; //修改數組中的一個元素
6、動態數組
定寬數組類型的寬度在編譯時就確定了,但如果在程序運行時數組的寬度不需要立刻定下來,就需要使用動態數組了,動態數組最大的特點即是可以在仿真運行時靈活調節數組的大小。
動態數組在一開始聲明時,需要利用‘[ ]’來聲明,而數組此時是空的,即0容量,編譯時候是空的,其后,需要使用new []來分配空間,在方括號中傳遞數組的寬度(個數)。
此外,也可以在調用new[ ] 時將數組名也一並傳遞,將已有數組的值復制到新的數組中。
int dyn[], d2[]; //聲明動態數組,當前元素數量為0
initial begin
dyn = new[5]; //分配5個元素的空間 foreach (dyn[j]) dyn[j] = j; //對元素初始化 d2 = dyn; //復制一個動態數組,只是值一樣,空間是一樣的,d2開始是0個元素,復制之后是5個元素 d2[0] = 5; //修改復制的值,d2和dyn有着各自獨立的空間,復制並不會改變其值 $display( dyn[0], d2[0] ); //顯示數值0和5 dyn = new[20](dyn); //增加分配20個數值並進行復制,小括號里為低位填充為dyn, 最后結果為0,1,2,3,4..00000 dyn = new[100]; //重新分配100個數值,而舊值不復存在 dyn.delete(); //刪除所有元素也可以為dyn=new[0] 或dyn=’{ }; end
intager整形的默認型為X, int整形的默認型為0
7、隊列
隊列結合了鏈表(C語言里那樣)和數組的優點。
鏈表:當前元素可以索引到上個元素或者下個元素。
隊列:在任何地方可以添加/插入/刪除元素,可以通過索引的方式對任何元素進行訪問。
隊列的聲明是使用帶美元的符號[$],隊列元素的標號是從0到$
隊列不需要new[ ]去創建空間,創建隊列其默認為空,你只需要使用隊列的方法為其增減元素,一開始其空間為0。
隊列的一個簡單使用即是通過其自帶的方法push_back()和pop_front()的結合來實現FIFO的用法。push_back()從后面往里推一個數據,push_frount從前面往里加一個數據,pop_front()從前面往外拿一個數據,pop_back()從后面往外拿一個數據。
int j = 1, q2[$] = {3,4}, q [$] = {0,2,5}; //隊列不使用“’”,聲明和初始化
initial begin
q.insert ( 1 , j ); // {0,1,2,5},在1后插入j,1表示位置1(0-3位置) q.insert (3, q2); //{0,1,2,3,4,5} 在q中插入隊列q2 q.delete (1); // {0,2,3,4,5} 刪除第一個元素 //下列從操作運行速度更快(自有方法) q.push_front (6) ; // 結果為{6,0,2,3,4,5} 從隊列頭部插入 j = q.pop_back (); //結果為{6,0,2,3,4} 從隊列尾部彈出 q.push_back (8); //結果為{6,0,2,3,4,8} 從隊列尾部插入 j= q.pop_front ( ); //結果為{0,2,3,4,8} 從隊列頭部彈出次南 foreach ( q[i] ) $display ( q [i] ); //打印整個隊列 q.delete () ; //刪除整個隊列 end
8、關聯數組 --- 為了超大容量數組來設計(為了保存稀疏矩陣來設計)
仿真器采用樹或者哈希表的形式存放關聯數組。
如果只是需要偶爾需要創建一個大容量數組,那么動態數組就足夠了,但是如果你需要一個超大容量的呢? (比如模擬存儲空間,如果有200000個data,對於內存負擔很大)(又不如對一個有着幾個G字節尋址范圍的處理器進行建模,在典型的測試中,這幾個處理器可能只訪問了用來存放可執行代碼核數據的幾百行或幾千個字節,這種情況下對幾G字節的存儲空間進行分配和初始化顯然是浪費的)。動態數組的限制在於其存儲空間在以開始創建時即被固定下來,那么對於超大容量的數組中有相當多的數據不會被存儲和訪問。
我們在真正的測試里,在很大的數組空間內,不會真正的存放很多數。只是截取非常有限的一部分來測試空間。意味着這些大量的空白部分不會去測試到,不需要白白浪費掉這些空白空間,故設計關聯數組。不希望空白空間在內存中被開辟出來。
如上圖,只希望0、3、42、1000、4521、200000空間被開辟出來,其他的空間是有數的,但是不需要訪問。
關聯數組,可以用來保存稀疏矩陣的元素,當你對一個非常大的地址空間尋址時,該數組只為實際寫入的元素分配空間,這種實現方法所需要的空間比定寬或動態數組所占用的空間要小得多。
此外,關聯數組有其他靈活的應用,在其他軟件語言也有類似的數據存儲結構,perl里面被稱之為哈希(Hash)或者python里被稱作詞典(Dictionary),可以靈活賦予鍵值(Key)和數值(Value)
索引值不需要成為整形,只要是數據類型就OK,即使是字符串。
bit [63:0] assoc [int], idx = 1; //關聯數組內容的數據類型為bit[63:0],idx數據類型也相同
repeat (64) begin //對稀疏元素進行初始化
assoc[idx] = idx; //1,2,4,8....
idx = idx << 1;
end foreach ( assoc[i] ) //使用foreach遍歷數組,不一定按照一定順序進行遍歷,可以利用.sort()進行排序 //索引值i是int類型 $display( “assoc[%h] = %h” , i , assoc[i] ); //使用函數遍歷數組 if (assoc.first(idx)) begin // .first()返回第一個索引值,判斷對應的空間是否為空 do $display(“assoc[%h]=%h”, idx, assoc[idx]); while(assoc.next(idx)); //get next index .next()返回下一個索引 end //找到並刪除第一個元素 assoc.first(idx); //返回第一個數組元素的索引值 assoc.delete(idx); // 刪除該索引值對應的空間 //關聯數組也可以用字符串索引進行尋址,使用字符串索引讀取文件,並建立關聯數組,可以實現字符串到數字的映射。 int switch[string],min_address,max_address; initial begin int i,r,file; string s; file = $fopen(“switch.txt”,r); while(! $feof(fire) ) begin r=$fscanf (fire, “%d %s”, i, s); switch[s] = i; end $fclose(fire); //獲取最小地址值,缺省為0 min_address=switch[“min_address”] //獲取最大地址值,缺省為1000 if(switch.exists(“max_address”)) max_address = switch[“max_address”] else max_address =1000 //打印數組的所有元素 foreach(switch [s]) $display(“switch[ ‘ %s ’ ] = %0d ”, s ,switch[s] ); end
9、結構體
Verilog最大缺陷之一是沒有數據結構,在SV中可以使用struct語言創建結構,和C語言類似。
不過struct的功能少,它只是一個數據的集合,其通常的使用方式是將若干相關的變量組合到一個struct結構定義中。
伴隨typedef(自定義新的數據類型)可以用來創建新的類型,並利用新類型來聲明更多變量,常常伴隨結構體struct使用。
struct {bit [7:0] r, g, b;} pixel; //創建一個pixel結構體,里面有r,g,b三個變量
//這樣做太冗余,我們可以直接創建數據類型,提高復用性
//為了共享該類型,通過typedef來創建類型
typedef struct {bit [7:0] r, g, b; } pixel_s; //把此結構體命名為pixel_s 表示
//_s 是書寫習慣,表示數據類型
pixel_s my_pixel; //聲明變量 my_pixel = ‘{ ‘h10, ‘h10, ‘h10 }; //結構體類型的賦值
什么時候用單引號做初始化,什么時候用單引號做初始化。非合並型,數據不是緊挨着存放的,就用‘{ },如動態數組和結構體。合並型的,數據是緊挨着存放的,用{ },如隊列里面
10、軟件常用類型--枚舉類型
規范的操作碼和指令例如ADD、WRITE、IDLE等有利於代碼的編寫和維護,它比直接使用’h01這樣的常量使用起來可讀性和可維護性更好。
枚舉類型enum經常和typedef搭配使用,由此便於用戶自定義枚舉類型的共享使用。
枚舉類型的出現保證了一些非期望值得出現,降低設計風險,多用於寫狀態機。
typedef enum {INIT, DECODE, IDLE} fsmstate_e; //枚舉類型是三個確定的字符串?組成的
fsmstate_e pstate, nstate; //聲明自定義變量pastate, nstate
//他們的數據類型是fsmstate_e;
case (pstate) IDLE: nstate = INIT; //數值賦值 INIT: nstate = DECODE; default : nstate = IDLE; endcase $display(“Next state is %s”,nstate.name()); // .name()顯示狀態名
問題:INIT,DECODE,IDLE如果是整型int的話,值是多少呢?0,1,2
如果用整型直接賦值,那么合法的范圍是多少呢?該賦值行為不合法,枚舉類型可以直接賦值給整型,整型不可以直接賦值給枚舉類型,需要進行強制類型轉換。
10、字符串
VHDL/Verilog 沒有字符串設計,非常難受。
所有與字符串相關的處理,請使用string來保存和處理。
與字符串處理相關的還包括字符串的格式化函數,即如何形成一個你想要的字符串句子呢?可以使用SV系統方法$sformatf(), 如果你只需要將它打印輸出,那么就使用$display()吧。
string s; //聲明了字符串,SV里面沒有類似於C語言空字符“\0”這個概念
initial begin
s = “IEEE ”; //設置了5個字符串,有一個空字符 ” ” $display ( s.getc(0) ); //字符串函數,顯示’I’ $display ( s.tolower() ); //引用字符串函數,全顯示小寫ieee s.putc(s.len()-1,”_”); // len()表示長度,長度-1表示最后一個字符,將空格變為’-’ s = {s, ”P1800”}; // ”IEEE-P1800” 字符串拼接 $display(s.substr(2,5)); //顯示 EE-P,起始位置2,終止位置5,拿到子字符串顯示 //創建一個臨時字符串並將其打印 my_log ( $sformatf (“%s %5d”,s,42) ); // 格式化聲明字符串,$sformatf(相當於printf)會返回一個字符串,生成一個字符串 end
task my_log(string message); //打印消息 $dispaly(“@%0t: %s”, $time,message); endtask
11、過程塊和方法
硬件過程塊initial 和always,initial是不可綜合的(不是硬件行為,本身設計就是為了做測試),always是可綜合的。
SV有一部分語句是放在硬件里,有一部分是放在軟件里用的。
為了區分軟件世界和硬件世界,我們先引申出一個概念域(scope),為了區分硬件世界、軟件世界,我們將定義的軟件變量或者例化的硬件所在的空間成之為域。
因此,module/endmodule, interface/endinterface可以被視為硬件世界,program/endprogram和class/endclass可以被視為軟件世界,掌握了這一概念有助於我們接下來分析initial和always的使用域。
always
always為了描述硬件行為,而在使用時需要明晰是時序邏輯電路還是組合邏輯電路,時序邏輯電路敏感列表里要有時鍾,@(event)是為了模擬硬件信號的觸發行為,always可能被綜合成三個電路:寄存器、鎖存器、組合邏輯電路,always經常被用在module里。
不同always之間是並行執行的,不同的硬件塊肯定是並行執行的。
不可以在always里初始化變量,初始化變量是軟件的行為,硬件行為叫復位,一般在定義變量時進行初始化。
initial
initial只執行一次。和always塊一樣,initial模塊無法被延遲執行,即在仿真一開始它們都會同時執行,而不同的initial和always之間在執行順序上是沒有順序可言的,硬件塊之間都是並行執行的,initial不可綜合,不應該出現在disign文件里,initial就是為了測試而生的,由於測試需要按照時間的順序即軟件的方式來完成,所以initial便可以實現這一要求。
Verilog時代,所有的測試語句都可以放置在initial中,為了便於統一管理測試順序,建議將有關測試語句都放置在同一個initial中。
initial過程塊可以在module、interface和program中使用。
對於過程塊的書寫方式,請記住用begin..end將其作用域包住。這一建議同樣適用於稍后提到大的控制語句、循環語句等等,初學者可以將其對應於C語言中的花括號{},便於記憶。
function
SV函數定義同C語言非常類似。可以在參數列表中指定輸入參數(input)、輸出參數(output)、輸入輸出參數(inout)或者引用參數(ref)。 可以返回數值或者不返回數值(void)。
function int double(inout a); //double為函數名,返回值是int
return 2*a; endfunction initial begin $diaplay(“double of %0d is %0d”, 10 ,double(10)); end
除此之外,function還有以下屬性。
1、默認數據類型為logic,例如inout[7:0] addr
2、數組可以作為形式參數傳遞。
3、function可以返回或者不返回結果,如果返回即需要用關鍵詞return,如果不返回則應該在聲明function時采用void function()。
4、如果驗證世界里用到了函數和task,記住他們只能傳遞變量,不能傳遞線網型、硬件信號、硬件存儲器,只有在數據變量可以在形式參數列表中被聲明為ref類型,而線網類型則不能被聲明為ref類型,也就是說只能傳遞數據,一定不能傳遞硬件的信號,存儲器,why?
5、在使用ref時(和inout有點類似,但不完全類似),有時候為了保護數據對象,只被讀取不被寫入,可以通過const的方式來限定ref聲明的參數。
6、在聲明參數時,可以給入默認的值,例如input[7:0] addr=0,同時在調用時如果省略該參數的傳遞,那么默認值即會被傳遞給function
task
task無法通過return來返回結果,因此只能通過output、inout或者ref的參數來返回。
task內可以置入耗時語句,而function不能,這是最重要的,常見的耗時語句包括@event、wait event、#delay等。
task mytask1 (output logic [31:0] x ,input logic y);
....
endtask
function和task的建議使用方式:
1、對於初學者,傻瓜式用法即全部采用task來定義方法,因為它可以內置常用的耗時語句,返回值可以寫在變量列表里。
2、對於有經驗的使用者,請今后對這兩種方法類型加以區別,在非耗時的方法定義時使用function,在內置耗時語句(等待某些事件)時使用task,這么做的好處是在遇到了這兩種方法定義時,就可以知道function只能用於純粹的數字或者邏輯運算,而task則可能會被用於需要耗時的信號采樣或者驅動場景。
3、如果要調用function,function和task均可對其調用;而調用task只能是task,function不能調用task,因為編譯器不能將有延時的task編譯到有延時的function里,編譯會報錯。
typedef struct {
bit [1:0] cmd; bit [7:0] addr; bit [31:0] data; } trans; //trans是結構類型 function automatic void op_copy(trans t, trans s); //賦值函數,automatic如果不知道先不管,后面會介紹 t=s; //為什么失敗了呢?函數變量模塊沒有指明方向,默認是input類型,function automatic void op_copy(input trans t, input trans s)相當於t沒有返回到參數t中,因為只是輸入,退出之后t仍然停留在000,用ref可以修改它,或者用output明確方向。 endfunction initial begin trans s; //trans類型的變量s trans t; //trans類型的變量t
s.cmd = ‘h1; s.addr = ‘h10; s.data = ‘h100; op_copy(t,s); t.cmd = ‘h2; //把t里的cmd賦值為2 end
最后的變量t的數值為{‘h2, ‘h0, ‘h0},為什么傳參失敗了呢?見上面代碼注釋
12、變量生命周期
在SV中,我們將數據的生命周期分為動態(局部變量)(automatic)和靜態(全局變量)(static)。
局部變量的生命周期同其所在域共存亡,例如function/task中的臨時變量,在其方法調用結束后,臨時變量的生命也將終結,所以他們是動態生命周期。
wire/reg等硬件元素的都是全局變量,生命周期從仿真到結束都有,在Verilog中不需要被聲明static,但是這些變量確實是static類型。
全局變量即伴隨着程序執行開始到結束一直存在,例如module中的變量某人情況下全部為全局變量,用戶也可以理解為module中的變量由於存在模糊硬件信號,所以它們是靜態生命周期。
如果數據變量被聲明為automatic,那么在進入該進程/方法后,automatic變量會被創建,而在離開該進程/方法后,automatic變量會被銷毀,而static變量在仿真開始時即會被創建,而在進程/方法執行過程中,自身不會被銷毀,而可以被多個進程和方法所共享,是全局的。
Veirlog是硬件設計語言所有的肯定都是static類型,而做verification需要用到動態數據類型來做高層次驗證。
module //開始
function automatic int auto_cnt(input a); //automatic為動態變量,局部變量類型
int cnt =0; //垃圾值會被清除
cnt +=a; return cnt; endfunction function static int static_cnt(input a); //指明了變量類型,靜態的,垃圾值不會被清除 static int cnt =0; cnt += a; return cnt; endfunciton
function static int def_cnt(input a); //如果不指定變量類型,則自動為靜態變量static int cnt =0; //空間未釋放,垃圾值會存在 cnt += a; return cnt; endfunciton endmodule //結束 initial begin $display(“@1 auto_cnt = %0d”,auto_cnt(1)); //結果為 @1 auto_cnt =1 $display(“@2 auto_cnt = %0d”,auto_cnt(1)); //結果為 @1 auto_cnt =1 $display(“@1 auto_cnt = %0d”,static_cnt(1)); //結果為 @1 static_cnt =1 $display(“@2 auto_cnt = %0d”,static_cnt(1)); //結果為 @1 static_cnt =2 $display(“@1 auto_cnt = %0d”,def_cnt(1)); //結果為 @1 def_cnt =1 $display(“@2 auto_cnt = %0d”,def_cnt(1)); //結果為 @1 def_cnt =2 end
在module、program、interface、task、function之外聲明的變量都是靜態生命周期,即存在於整個仿真階段,這同C定義的靜態變量一致。
在module、interface和program內部聲明,且在task、process或者function外部聲明的變量也是static變量,且作用域在該塊中。
在module、program和interface中定義的task、function默認都是static類型。
在過程塊中(task、function、process)定義的變量句跟隨他的作用域,即過程塊的類型。
如果過程塊為static,則他們也默認為static,反之亦然,這些變量也可以由用戶顯式聲明為automatic或者static。
為了使得在過程塊中聲明的變量有同一默認的聲明周期,可以在定義module、interface、package或者static來區分,對於上述程序塊默認的聲明周期類型為static。
總之,該定義為automatic的地方一定要定義成automatic。
電路信號就是靜態的,驗證是在硬件語言引入軟件工程的特征,即動態。
13、設計例化和鏈接
// modulel為硬件域,在定義時候需要標注方向、位寬和端口名。
module mcdt(
input clk_i, input rstn_i, input [31:0] ch0_data_i, input ch0_valid_i, output ch0_ready_o, output [4:0] ch0_margin_o, input [31:0] ch1_data_i, input ch1_valid_i, output ch1_ready_o, output [4:0] ch1_margin_o, input [31:0] ch2_data_i, input ch2_valid_i, output ch2_ready_o, output [4:0] ch2_margin_o, output [31:0] mcdt_data_o, output mcdt_val_o, output [1:0] mcdt_id_o );
// 設計例化,首先給出tb的信號列表,然后給出design名稱 例化模塊的名稱{}
// ,. deisgn的端口名 ( tb的信號列表)
module tb1;
reg clk; reg rstn; reg [31:0] ch0_data; reg ch0_valid; wire ch0_ready; wire [4:0] ch0_margin; reg [31:0] ch1_data; reg ch1_valid; wire ch1_ready; wire [4:0] ch1_margin; reg [31:0] ch2_data; reg ch2_valid; wire ch2_ready; wire [4:0] ch1_margin; reg [31:0] ch2_data; reg ch2_valid; wire ch2_ready; wire [4:0] ch2_margin; wire [31:0] mcdt_data; wire mcdt_val; wire [1:0] mcdt_id; mcdt dut( .clk_i(clk) ,.rstn_i(rstn) ,.ch0_data_i(ch0_data) ,.ch0_valid_i(ch0_valid) ,.ch0_ready_o(ch0_ready) ,.ch0_margin_o(ch0_margin) ,.ch1_data_i(ch1_data) ,.ch1_valid_i(ch1_valid) ,.ch1_ready_o(ch1_ready) ,.ch1_margin_o(ch1_margin) ,.ch2_data_i(ch2_data) ,.ch2_vaild_i(ch2_margin) ,.mcdt_data_o(mcdt_data) ,.mcdt_val_o(mcdt_val) ,.mcdt_id_o(mcdt_id) );
模塊連接
在testbench中的鏈接(connection)指的是有硬件模塊參與作為信號驅動方(drive)或者負載方(load)。
Tb中常見的鏈接有兩個硬件模塊中的鏈接,譬如實例A與實例B的鏈接,可由logic類型完成鏈接;如果是硬件模塊與TB中發送數據激勵的鏈接,則需要考慮數據激勵一端如何正確產生數據並發送至DUT一側,同時數據激勵一端也需要對DUT反饋信號做出相應。
如上圖ready為接收DUT端激勵成功的響應,未成功繼續發送,發送成功響應為1。