1.定寬數組
1.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}
1.2數組的基本操作for和foreach
$size函數會自動返回數組的寬度。foreach循環只需要指定數組名稱並在其后面的方括號中給出索引量,SystemVerilog便會自動遍歷數組中的元素。
module test_enum(); initial begin bit[31:0] src[5],dst[5]; int i,j; //無需對i,j進行類型定義
for(int i=0;i< $size(src);i++) begin src[i]=i; $display("src[%0d]=%0d",i,src[i]); end foreach(dst[j]) begin dst[j]=src[j]*2; //dst的值是src的兩倍
$display("dst[%0d]=%0d",j,dst[j]); end
endmodule
注:對多維數組foreach循環方括號的下標並不是我們想象的[i][j],而是[i,j]。
module test_enum(); initial begin byte twoD[4][6]; foreach(twoD[i,j]) twoD[i][j]=i*10+j; foreach(twoD[i]) //遍歷第一個維度 begin $write("%0d:",i); foreach(twoD[,j]) //遍歷第二個維度 $write("%3d",twoD[i][j]); //利用位寬來表示空格 $display; //$display會自動換行
end
end
endmodule
1.3基本的數組操作——復制和比較
module test_enum(); bit[31:0] src[5]='{0,1,2,3,4},
dst[5]='{5,4,3,2,1}; //賦初始值放在外面
initial
begin
//兩個數組的聚合比較
if(src == dst) $display("src == dst"); else $display("src != dst"); dst=src; //將src賦給dst
src[0]=5; //將src的第一個元素賦值為5
$display("src %s dst",(src == dst)? "==":"!="); //以這種方式來比較,所有元素的值是否相等
$display("src[1:4] %s dst[1:4]",(src[1:4] == dst[1:4]) ? "==":"!="); end
endmodule
1.4數組的幾種表達方式
1)同時使用數組下標和位下標
例2.14 打印出數組的第一個元素(二進制101)、它的最低位(1)以及緊接的高兩位(二進制10)。
initial begin bit [31:0] src[5]='{5{5}}; $displayb (src[0], //'b101或'd5 src[0][0], //'b1 src[0][2:1]); //'b10 end
2)合並數組
聲明合並數組時,合並的位和數組大小作為數據類型的一部分必須在變量名前面指出。數組大小定義的格式必須是[msb:lsb],而不是[size]。
bit [3:0] [7:0] bytes; //四個字節合並的數組,使用單獨的32比特的字來存放。
bytes=32'hCafe_Data;
$display (bytes, //顯示所有的32比特
bytes[3], //最高位字節“CA”
bytes[3][7]); //最高字節的最高比特位“1”
bit [3:0][7:0] barray[3]; //合並3*32比特
barray[2]; //32比特的數據
barray[2][3]; //8比特的數據
barray[2][3][7]; //單比特的數據
2.動態數組
對於定寬數組,其寬度在編譯時就確定了;在不知道數組的寬度情況下采用動態數組。
動態數組在聲明時使用空下標[ ],數組在最開始時是空的,必須使用new[ ]操作符來分配空間,同時在方括號中傳遞數組寬度。
module test_enum(); int dyn[],d2[]; //聲明動態數組
initial
begin dyn=new[5]; //dyn的寬度為5,分配5個元素
foreach(dyn[j]) dyn[j]=j; //對元素進行初始化
d2=dyn; //復制動態數組
d2[0]=5; //修改復制值
$display("%d %d",dyn[0],d2[0]); //顯示數值0,5
dyn=new[20](dyn); //給dyn分配20個整數值並將前五個值進行復制
$display("%d %d",dyn[3],dyn[19]); //3,0
dyn=new[100]; //分配100個整數值給dyn,舊值不復存在
dyn.delete(); //刪除所有元素
end
endmodule
3.隊列
SystemVerilog引進了一種新的數據類型隊列。在一個隊列中的任何一個地方增加或刪除元素,這類操作在性能上的損失要比動態數組小的多,因為動態數組需要分配新的數組並復制所有元素。
隊列的聲明是使用的帶有美元符號的下標[ $ ],隊列元素的編號從0到$。注意隊列的常量(literal)只有大括號而沒有數組常量中開頭的單引號。

1 module test_enum(); 2 int j=1, 3 q2[$]={3,4}, //隊列的常量不需要使用單引號' 4 q[$]={0,2,5}; //{0,2,5} 5 initial 6 begin 7 j=q2[$]; //j=4 8 j=q2[0]; //j=3 9 q.insert(1,1); //在第1位插入1{0,1,2,5} 10 q.insert(2,3); //在第2位插入3{0,1,3,2,5} 11 q.delete(1); //刪除第一位{0,3,2,5} 12 //下面的操作執行速度很快 13 q.push_front(6); //最前面插入6{6,0,3,2,5} 14 j=q.pop_back; //j=5 {6,0,3,2} 15 q.push_back(8); //在最后插入8{6,0,3,2,8} 16 j=q.pop_front; //j=6{0,3,2,8} 17 foreach(q[i]) 18 $display("%0d",q[i]); //打印整個隊列 19 q.delete(); //等價於命令q={};刪除整個隊列 20 end 21 endmodule
注意:把 $放在一個范圍表達式的左邊,那么 $將代表最小值[ $:2]等價於[0:2],將 $放在一個范圍表達式的右邊,那么 $將代表最小值[1: $]等價於[1:2]。
4.關聯數組
如果你只是需要對一個有着幾個G字節尋址范圍的處理器進行建模。在典型的測試中,這個處理器可能只訪問了用來存放可執行代碼和數據的幾百或幾千個字節,這種情況下對幾個G字節的存儲空間進行分配和初始化顯然是浪費的。
仿真器一般采用32位地址線或者64位數據作為索引的數據包,顯然這是有一定的額外開銷的。
關聯數組采用在方括號中放置數據類型的形式來進行聲明。

1 module test_enum(); 2 bit[63:0] assoc[bit[63:0]],idx=1; //64個bit[63:0] 關聯數組assoc 3 4 repeat(64) begin //對1,2,4,8,16等等的稀疏元素進行初始化。 5 assoc[idx]=idx; 6 idx=idx<<1; 7 end 8 9 foreach(assoc[i]) //foreach遍歷數組 10 $display("assoc[%0d]=%0d",i,assoc[i]); 11 if(assoc.first(idx)) //使用函數遍歷數組 12 begin //得到第一個索引 13 do 14 $display("assoc[%h]=%h",idx,assoc[idx]); 15 while(assoc.next(idx)); //得到下一個索引 16 end 17 18 assoc.first(idx); //找到並刪除第一個元素 19 assoc.delete(idx); 20 $display("the array now has %0d elements",assoc.num); 21 end 22 endmodule
5.鏈表
SystemVerilog提供了鏈表數據結構,但是應該避免使用它,因為SystemVerilog提供的隊列更加高效易用。
6.數組的方法
6.1數組的縮減方法
基本的數組縮減方法就是把一個數組縮減成一個值。最常用的方法就是求和sum,除此之外還有product(乘)and(與)or(或)xor(異或)等。
在進行數組壓縮的時候,應該特別重要的一點需要注意,那就是位寬的問題。
例:數組求和

1 module test_enum(); 2 bit on[10]; //單比特數組 3 int total; 4 initial begin 5 foreach(on[i]) 6 on[i]=i; //on[i]的值為0或1 7 $display("on.sum=%0d",on.sum); //on.sum是單比特無符號的數 打印出單比特和 on.sum=1 8 $display("on.sum=%0d",on.sum+32'd0); //on.sum是32比特數 打印出32比特和 on.sum=5 9 //由於total是32比特變量,所以數組的和也是32比特變量 10 total=on.sum; 11 $display ("total=%0d",total); //total=5 12 //將數組和一個32比特數進行比較 13 if (on.sum>=32'd5) //條件成立 14 $display ("sum has 5 or more 1's''); 15 //使用帶32比特有符號運算的with表達式 16 $display("int sum=%0d",on.sum with (int'(item))); //利用with來限定on.sum的數據類型,這是一種比較好用的方式 17 end 18 endmodule
SystemVerilog中,對定寬數組、隊列、動態數組和關聯數組可以使用 $urandom_range( $size(array)-1)來選取隨機一個元素,而對於隊列和動態數組還可以使用 $urandom_range(array.size()-1)。
6.2數組定位方法
數組定位方法min、max、unique、find

1 module test_enum(); 2 int f[6]={1,6,2,6,8,6}, 3 d[]='{2,4,6,8,10}, 4 q[$ ]={1,3,5,7}, 5 tq[$]; 6 initial 7 begin 8 tq=q.min(); //求最小值{1} 9 foreach(tq[i]) 10 $display("min:tq[%0d]=%0d",i,tq[i]); 11 tq=q.max(); //求最大值{7} 12 foreach(tq[i]) 13 $display("max:tq[%0d]=%0d",i,tq[i]); 14 15 tq=f.unique(); //求數組中唯一值的隊列 16 foreach(tq[i]) 17 $display("unique:tq[%0d]=%0d",i,tq[i]); 18 19 tq=d.find with (item>3); //利用find函數做操作 20 foreach(tq[i]) 21 $display("find:tq[%0d]=%0d",i,tq[i]); 22 tq.delete(); //等價的操作 23 foreach(d[i]) 24 if(d[i]>3) 25 tq.push_back(d[i]); 26 foreach(tq[i]) 27 $display("tq[%0d]=%0d",i,tq[i]); 28 29 tq=d.find_index with (item>3); //輸出的是index索引也就是第幾位的值 30 foreach(tq[i]) 31 $display("tq[%0d]=%0d",i,tq[i]); 32 end 33 endmodule
注意:item被稱為重復參數,它代表了數組中一個單獨的元素,item是缺省的名字,你也可以指定別的名字。下面四種情況是等價的。
tq=d.find_first with (item==4); tq=d.find_first() with (item==4); tq=d.find_first(item) with (item==4); tq=d.find_first(x) with (x==4);
當數組的縮減方法和條件語句with結合使用時,sum操作符的結果是條件表達式為真的次數。

1 module test_enum(); 2 int count,total, 3 d[]='{9,1,8,3,4,4}; 4 initial begin 5 count=d.sum with (item>7); //比較表達式返回0或1 6 total=d.sum with ((item>7)*item); 7 $display("count=%0d total=%0d",count,total); //2,17 8 9 count=d.sum with (item<8); 10 total=d.sum with (item<8?item:0); 11 $display("count=%0d total=%0d",count,total);//4,12 12 count=d.sum with (item==4); 13 $display("count=%0d",count); //2 14 end 15 endmodule
6.3數組的排列
SystemVerilog有幾個可以改變數組中元素順序的方法。包括反向、正序、逆序、隨機。
int d[]='{9,1,8,3,4,4};
d.reverse(); //反向'{4,4,3,8,1,9}
d.sort(); //正序{1,3,4,4,8,9}
d.rsort(); //逆序'{9,8,4,4,3,1}
d.shuffle(); //隨機'{9,4,3,8,1,4}
6.4使用數組定位方法建立記分板
7.選擇存儲類型
其實數據類型的選擇是多方面的,我們要考慮靈活性、存儲器用量、速度、排序和數據結構等多種方面,在我們以后的應用中,我們將會深入地理解每種不同的數據類型的利弊。
8.使用typedef創建新的類型
在原有數據類型之上定義新的數據類型。為了不混淆,本書約定所有用戶自定義類型都帶后綴“_t”。
parameter opsize=8; typedef reg[opsize-1:0] opreg_t; opreg_t op_a,op_b; typedef bit[31:0] uint; typedef int unsigned uint; //等價的兩種方式
對於新的數組定義並不是很明顯。需要把數組的下標放在新的數組名稱中。
typedef int fixed_array5[5]; fixed_array5 f5; initial
begin foreach(f5[i]) f5[i]=i; end
9.創建用戶自定義結構
在SystemVerilog中,引入了數據結構的概念。struct只是把數據組織在一起,只是一個數據的集合。
9.1使用struct創建新類型
struct可以把若干個變量組合到一起。我們統一將struct創建的新類型用“_s”來表示。

1 typedef struct{bit[7:0] r, g,b;} pixel_s; 2 pixel_s my_pixel; 3 4 initial 5 begin 6 typedef struct {int a, 7 byte b, 8 shortint c;} my_struct_s; 9 my_struct_s st='{32'haaaaaaaa, 10 8'hbb, 11 16'hcccc}; 12 $display("st=%x %x %x",st.a,st.b,st.c); 13 end
9.2創建可容納不同類型的聯合
聯合體,通常意義上來講就是同一位置放置不同類型的數據。如果需要以若干不同的格式對同一寄存器進行頻繁讀寫時,聯合體相當有用。我們約定以“_u”為后綴。
typedef union { int i; real f;} num_u; num_u un; un.f=0.0; //把數值設為浮點形式
9.3合並結構
通過一個例子來描述一下合並結構(packed)可以節省存儲空間。
typedef struct packed {bit [7:0] r,g,b} pixel_p_s; pixel_p_s my_pixel;
10.類型轉換
10.1靜態轉換
靜態轉換不對轉換值進行檢查。如果越界的話,我們也不能察覺。
基本轉換格式:type’(val)。
例: 在整形和實型之間進行靜態轉換
int i; real r; i=int'(10.0-0.1); //轉換是非強制的 r=real'(42); //轉換是非強制的
10.2動態轉換
動態轉換函數$cast允許對越界的數值進行檢查,如果不越界返回1,否則返回0。
10.3流操作符
流操作符>>和<<用於把其后的數據打包成一個比特流。>>是把數據從左到右變成數據流,<<是把數據從右到左變成數據流。

1 initial begin 2 int h; 3 bit [7:0] b, 4 g[4], 5 j[4]='{8'ha,8'hb,8'hc,8'hd}; 6 bit [7:0] q,r,s,t; 7 8 h={>>{j}}; //0a0b0c0d把數組打包成整型 9 h={<<{j}}; //b030d050位倒序 10 h={<<byte{j}}; //0d0c0b0a字節倒序 11 b={<<{8'b0011_0101}}; //10101100位倒序 12 b={<<4 {8'b0011_0101}};//0101_0011半字節倒序 13 {>>{q,r,s,t}}=j; //將j分散到四個字節變量里 14 h={>>{t,s,r,q}}; //將四個字節集中到h里 15 end
11.枚舉類型
最簡單的枚舉類型聲明包含了一個常量名稱列表以及一個或多個變量。
利用內建函數name()可以得到枚舉變量值對應的字符串。我們統一用后綴“_e”來表示枚舉的數據類型。
11.1定義枚舉值
枚舉值缺省為從0開始遞增的整數,可以自己定義枚舉值。通常在我們把0值給枚舉常量,可以避免一些不必要的錯誤。
11.2枚舉類型的子程序
(1)first() 返回第一個枚舉變量
(2)last() 返回最后一個枚舉變量
(3)next() 返回下一個枚舉變量
(4)next(N) 返回以后第N個枚舉變量
(5)prev() 返回前一個枚舉變量
(6)prev(N) 返回以前第N個枚舉變量
遍歷所有的枚舉成員(注意對枚舉類型值的定義),當到達枚舉常量列表的頭或尾時,函數next和prev會自動以環形方式繞回。
11.3枚舉類型的轉換
枚舉類型的缺省類型為雙狀態的int。可以通過簡單的賦值表達式把枚舉變量直接賦值給變量int。
不允許直接把int賦值給枚舉變量,這種是出於越界情況的考慮。

1 module test_enum(); 2 typedef enum {RED,BLUE,GREEN} COLOR_E; 3 COLOR_E color,c2; 4 int c; 5 6 initial 7 begin 8 color=BLUE; //賦一個已知的合法值 9 c=color; //將枚舉類型轉換成整型 10 c++; //整型遞增 11 if(!$cast(color,c)) //將整型顯示轉換回枚舉類型 12 $display("cast failed for c=%0d",c); 13 $display("color is %0d/%s",color,color.name); 14 c++; //對於枚舉類型已經越界 15 c2=COLOR_E'(c); //不做類型檢查 16 $display("c2 is %0d/%s",c2,c2.name); 17 if(!$cast(color,c)) 18 $display("cast failed for c=%0d",c); 19 end 20 endmodule
結果:
#color is 2/GREEN #c2 is 3/ #cast failed for c=3
$cast(color,c)將int型動態轉化為枚舉類型,如果沒有越界返回1,否則返回0;界內(0,1,2),3已經越界了。
12.常量
SystemVerilog中支持const修飾符,允許在變量聲明時對其進行初始化,但不能在過程代碼中改變其值。
13.字符串
SystemVerilog中的string類型可以用來保存長度可變的字符串。單個字節是byte類型。字符串使用動態的存儲方式,所以不用擔心存儲空間會全部用完。

1 module test_enum(); 2 string s; 3 4 initial begin 5 s="IEEE "; 6 $display(s.getc(0));//顯示:73('I') 7 $display(s.tolower()); //顯示:ieee 8 9 s.putc(s.len()-1,"-"); //將空格變為'-' 10 s={s,"P1800"}; //“IEEE-P1800” 11 12 $display(s.substr(2,5)); //顯示:EE-P 13 //創建臨時字符串,注意格式 14 my_log($psprintf("%s %5d",s,42)); 15 end 16 17 task my_log (string message); 18 //把信息打印到日志里 19 $display("@%0t:%s",$time, message); 20 endtask 21 endmodule
getc(N) 返回位置N上的字節
tolower()返回一個小寫的字符串
putc(M,C)把字節C寫到字符串的M位上,M必須介於0和len所給出的長度之間。
substr(start,end),讀取從位置start到end之間的所有字符。
參考資料:SystemVerilog驗證:測試平台編寫指南