systemverilog
第二章 数据类型
一、内建数据类型
1、逻辑(logic)类型
SystemVerilog中有两种基本数据类型:变量和线网。各自有四种取值:0、1、x、z。RTL代码使用变量来存放组合和时序值,线网可以用来连接设计中的不同部分,例如门和模块实例。
syestemverilog对reg数据类型进行了改进,使得它除了作为一个变量外,还可以被连续赋值、门单元和模块驱动,这种数据类型被称为logic。任何使用线网的地方都可以使用logic,但logic不能多驱,例如在对双向总线建模时。
2、双状态数据类型
bit b 双状态,单比特;
int unsigned ui 双状态,32bit无符号整数;
int i 双状态,32bit有符号整数;
byte b8 双状态,8bit有符号整数;
shortint s 双状态,8bit有符号整数;
longint l 双状态,64bit有符号整数;
integer i4 四状态,32bit有符号整数;
time t 四状态,64bit无符号整数;
real r 双状态,双精度浮点数;
在把双状态变量连接到被测设计,尤其是被测设计的输出时务必要小心。如果被测设计试图产生X或Z,这些值会被转换成双状态值,而测试代码可能永远也察觉不了。使用($isunknow)操作符,可以在表达式的任意位出现X或Z时返回1,如例2.3所示。
例2.3 对四状态值的检查。
if($isunknow(iport) == 1)
$display (
"@%0t: 4-state value detected on iport %b",$time,iport);
/*使用格式符%0t和参数 time 可以打印出当前的仿真时间,打印的格式在
$timeformat()子程序中指定。*/
二、定宽数组
1.定宽数组的声明和初始化
例2.4 定宽数组的声明:
int lo_hi[0:15]; //16个整数[0]...[15]
int c_style[15]; //16个整数[0]...[15]
例2.5 声明并使用多维数组:
int array2[0:7][0:3]; //完整的声明
int array3[8][4]; //紧凑的声明
array2[7][3] = 1; //设置最后一个元素
如果你的代码试图从一个越界的地址读取数据,那么systemverilog将返回数组元素类型的缺省值。对于四状态类型的数组,例如logic返回X,对于二状态类型例如int,则返回0。这使用于所有数组,也适用于地址中含有X或Z都情况。线网在没有驱动的时候输出是Z。
2.常量数组
使用了一个单引号加大括号的方法初始化数组。
例2.7 初始化一个数组
int ascend[4] = '{0,1,2,3}; //对四个元素进行初始化
int descend[5];
descend = '{4,3,2,1,0}; //为五个元素赋值
descend[0:2] = '{5,6,7}; //为前三个元素赋值
ascend = '{4{8}}; //四个值全为8
descend = '{9,8,default:1}; //{98,1,1,1}
三、数组操作
1 、操作数组最 常用的方式是使用for或foreach循环,foreach循环中只要指定数组名并在其后面的方括号给出索引变量,SV会自动遍历数组中的元素。
例 2.8 在数组操作中使用for和foreach
initial begin
bit [31:0] src[5],dst[5];
for (int i = 0;i<$size(src);i++)
src[i] = i;
foreach (dst[j])
dst[j] = src[j]*2; //dst 的值是 src 的两倍
end
2、数组操作——复制与比较
例 2.13 数组的复制于比较操作
initial begin
bit [31:0] src[5] = '{0,1,2,3,4};
dst[5] = '{5,4,3,2,1};
//两个数组的聚合比较
if(src == dst)
$display("src == dst");
else
$display("src != dst");
//把 src 所有元素值复制给 dst
dst = src ;
//只改变一个元素的值
src[0] = 5;
//所有元素的值是否相等(否!)
$display("src %s dst",(src == dst) ? "==" : "!=");
//使用数组片段对第 1-4 个元素进行比较
$display("src[1:4] %s dst[1:4]",
(src[1:4] == dst[1:4]) ? "==" : "!=");
3、合并数组与非合并数组
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[0:2] ;
4)合并数组于非合并数组的选择
(1)当需要以字节或字为单位对存储单元操作。
(2)当需要等待数组中变化的,则必须使用合并数组。
例如测试平台需要通过存储器数据的变化来唤醒,需要用到@,@只能用于标量或者合并数组。
Bit[3:0] [7:0] barray [0:2] ; 表示合并数组,合并数组中有3个元素,每个元素时8bit,4个元素可以组成合并数组可以使barry[0]作敏感信号。
四、动态数组
动态数组在声明时使用空的下标[ ],所以必须调用 new[ ]操作符来分配空间,同时在方括号中传递数组宽度。可以吧数组名传给 new[ ]构造符,并把已有数组的值复制到新数组里,如例 2.17 所示。
例2.17 使用动态数组
int dyn[],d2[]; //声明动态数组
initial begin
dyn = new[5]; //A:分配5个元素
foreach (dyn[j]) dyn[j] = j; //B:对元素进行初始化
d2 = dyn; //C:复制一个动态数组
d2[0] = 5; //D:修改复制值
$display(dyn[0],d2[0]); //E:显示数值(0和5)
dyn = new[20](dyn); //F:分配2个整数值并进行复制
dyn = new[100]; //G:分配100个新的整数值
dyn.delete(); //H:删除所有元素
end
五、队列
可以在队列中的任何位置删除或增加元素,队列的声明是使用带有美元符号的下标:[$]。不要对队列使用构造函数 new[ ]。
例2.19 队列的操作
int j = 1,
q2[$] = {3,4}, //队列的常量不需要使用“'”
q[$] = {0,2,5};
initial begin
q.insert(1,j); //{0,1,2,5}在 2 之前插入 1
q.insert(3,q2); //{0,1,2,3,4,5}在q中插入一个队列
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} j = 5
q.push_back(8); //{6,0,2,3,4,8}在队列末尾插入
j = q.pop_front; //{0,2,3,4,8} j = 6
foreach (q[i])
$display(q[i]); //打印整个队列
q.delete(); //删除整个队列
end
六、关联数组
当建立一个超大容量数组时,关联数组用于保存稀疏矩阵的元素。
关联数组采用在方括号中放置数据类型的形式来声明,例如[int]或[packet]。
例 2.21 关联数组的声明、初始化和使用
initial begin
bit [63:0] assoc[bit[63:0]],idx = 1;
//d对稀疏分布的元素进行初始化
repeat (64) begin //循环64次
assoc[idx] = idx;
idx = idx << 1; // << 左移符号
end
//使用foreach遍历数组
foreach (assoc[i])
$display("assoc[%h]",i,assoc[i]);
//使用函数遍历数组
if(assoc.first(idx))
begin //得到第一个索引
do
$display("assoc[%h]",idx,assoc[idx]);
while (assoc.next(idx)); //得到下一个索引
end
//找到并删除第一个元素
assoc.first(idx);
assoc.delete(idx);
$display("The array now has %0d elements",assoc.num);
end
例 2.22 使用带字符串索引的关联数组
/*
输入文件的内容如下:
42 min_address
1492 max_address
*/
int switch[string],min_address,max_address;
initial begin
int i,r,file;
string s;
file = $fopen("switch.txt","r"); //$fopen打开文字文件,"r"只读
while (! $feof(file)) begin //$feof(file),文件结束符(文件结束为非0值,文件没结束则为0)
r = $fscanf(file,"%d %s",i,s); //$fscanf函数,每次只读一行
switch[s] = i;
end
$fclose(file); //关闭文件
//获取最小地址值,缺省为0
min_address = switch["min_address"];
//获取最大地址值,缺省为1000
if(switch.exists("max_address")) //使用exists函数判断switch数组中是否含有“max_address”
max_address = switch["max_address"]; //含有的话,将内容的地址赋值给 max_address
else
max_address = 1000; //否则,地址值为1000
//打印数组的所有元素
foreach (switch[s])
$display("switch['%s'] = %0d",s,switch[s]); //打印数组的内容和相应的地址
end
七、数组的方法
1、数组定位方法
在非合并数组中查找数据,可以使用数组定位方法,这些方法返回值通常是一个队列。
例 2.25 数组定位方法:min.max.unique
int f[6] = '{1,6,2,6,8,6};
int d[] = '{2,4,6,8,10};
int q[$] = {1,3,5,7},tq[$];
tq = q.min(); //定位最小值 1
tq = d.max(); //定位最大值 10
tq = f.unique(); //排除掉重复数值{1,2,6,8}
2.26 数组定位方法:find
int d[] = '{9,1,8,3,4,4},tq[$];
//找出所有大于 3 的元素
tq = d.find with (item > 3); // {9,8,4,4}
//等效代码
tq.delete();
foreach (d[i])
if (d[i] > 3)
tq.push_back(d[i]);
tq = d.find_index with (item > 3); //{0,2,4,5}
tq = d.find_first with (item > 99); //{} 没有找到
tq = d.find_first_idex with (item == 8);//{2} d[2] = 8
tq = d.find_last with (item == 4); //{4}
tq = d.find_last_index with (item == 4);//{5} d[5] = 4
2、数组的排序
对元素进行正排序、逆排序或打乱他们的顺序。
例 2.29 对数组排序
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} 随机排序
例 2.30 对结构数组进行排序
struct packed {byte red,green,blue;} c[];
initial begin
c= new[100]; //分配 100 个像素
foreach(c[i])
c[i] = $urandom; //填上随机数
c.sort with (item.red); //只对红色(red)像素进行排序
//先对绿色像素后对蓝色像素排序
c.sort(x) with (x.green,x.blue);
end
八、使用 typedef 创建新的类型
typedef可以用来创建新的数据类型,用户自定义的最有用的类型是双状态的32比特的无符号整数。
例 2.34 unit的定义
typedef bit [31:0] unit; //32比特双状态无符号数
typedef int unsigned unit; //等效的定义
九、创建用户自定义结构
1、使用struct创建新类型
你可以把若干变量组合到一个结构中。例 2.36 创建了一个名为 pixel 的结构,它有三个无符号的字节变量,分别代表红、绿和蓝。
例 2.36 创建一个 pixel 类型
struct {bit [7:0] r,g,b} pixel;
例 2.36 中的声明只是创建了一个 pixel 变量。想要在端口和程序中共享它,则必须创建一个新的类型,如例 2.37 所示。
例 2.37 pixel 结构
typedef struct {bit [7:0] r,g,b} pixel;
pixel_s my_pixel; //后缀“_s”方便用户识别自定义类型
2、对结构进行初始化
可以在声明或过程赋值语句中把多个值赋给一个结构体。
例 2.38 对struct 类型进行初始化
initial begin
typedef steuct {int a;
byte b;
shortint c;
int d;} my_struct_s;
my_steuct_s st = '{32'haaaa_aaad,
8'hbb
16'hcccc
32'hdddd_dddd};
$display("str = %x %x %x %x",st,a,st.b,st.c,st.d);
end
十、类型转换
SV数据类型具有多样性,因此可能需要在它们之间进行转换。如果源变量和目标变量的比特分布完全相同,例如整数和枚举类型,那么他们之间可以直接相互赋值。如果比特位分布不同,例如字节数组和字数组,则需要使用流操作符对比特分布重新安排。
1、静态转换
静态转换操作不对转换值进行检查。
例 2.41 在整型和实型之间进行静态转换
int i;
real r;
i = '{10.0 - 0.1};
r = '{42}; //非强制转换
2、流操作符
流操作符 << 和 >> 用在赋值表达式的右边,后面带表达式、结构或数组。流操作符用于把其后的数据打包成一个比特流。操作符 >> 把数据从左至右变成流,而 << 则把数据从右至左变成流,如 2.42 所示。
例 2.42 基本的操作符
initial begin
int h;
bit [7:0]b,g[4],j[4] = '{8'ha,8'hb,8'hc,8'hd};
bit [7:0] q,r,s,t;
h = { >> {j}}; // 0a0b0c0d 把数组打包成整型
h = { << {j}}; //b030d050 位倒序
h = { << byte{j}}; //0d0c0b0a 字节倒序
g = { << byte{j}}; //0d,0c,0b,0a 拆分成数组
b = { << {8'b0011_0101}}; //1010_1100 位倒序
b = { << 4{8'b0011_0101}}; //0101_0011 半字节倒序
{ >> {q,r,s,t}} = j; //把 j 分散到四个字节变量里
h = { >>{t,s,q,r}}; //把字节集中到 h 里
end
例 2.44 使用流操符在结构和数组直接进行转换
initial begin
typedef struct {int a;
byte b;
shortint c;
int d}; ny_struct_s;
my_struct_s st = '{32'haaaa_aaaa,
8'hbb,
16'hcccc,
32'hdddd_dddd};
byte b[];
//将结构转换成字节数组
b = { >> {st}}; //{aa aa aa aa bb cc cc dd dd dd dd}
//将字节数组转换成结构
b = '{8'h11,8'h22,8'h33,8'h44,8'h55,8'h66,8'h77,
8'h88,8'h99,8'haa,8'hbb};
st = { >> {b}}; //st = 11223344,55,6677,8899aabb
end
十一、枚举类型
枚举类型可以自动为列表中的每个名称分配不同的数值。
例 2.45 一个简单的枚举类型
enum {RED,BLUE,GREEN} color;
例 2.46 枚举类型
// 创建代表 0,1,2 的数据类型
typedef enum {INIT,DECODE,IDLE} fsmstate_e;
fsmstate_e pstate,nstate; //声明自定义类型变量
initial begin
case(pstate)
IDLE : nstate = INIT; //数据赋值
INIT : nstate = DECODE;
default : nstate = IDLE;
endcase
$display("Next state is %s",nstate.name()); //显示状态的符号数
end
使用后缀“_e”表示枚举类型。
1、定义枚举值
枚举值缺省为从 0 开始递增的整数。
例 2.47 指定枚举值。
typedef enum {INIT,BECODE = 2,IDLE} fsmyype_e; //{0,2,3}
如果没有特别指出,枚举类型会被当作 int 类型存储。由于 int 类型的缺省值是0,所以在给枚举常量赋值时务必要小心。
2、枚举类型的子程序
SV中提供了一些可以遍历枚举类型的函数。
(1)、first()返回第一个枚举常量。
(2)、last()返回最后一个枚举常量。
(3)、next()返回下一个枚举常量。
(4)、next(N)返回第N个枚举常量。
(5)、prev()返回前一个枚举常量。
(6)、prev(N)返回以前第N个枚举变量。
3、枚举类型的转换
枚举类型的缺省类型为双状态 int 。可以使用简单的赋值表达式把枚举变量的值直接赋给非枚举变量如 int 。但SV不允许在没有进行显示类型转换的情况下把整型变量赋值给枚举变量。SV要求显式类型转换的目的在于让你意识到可能存在的数值越界情况。
例 2.51 整型和枚举类型之间的相互赋值
typedef enum {RED,BLUE,GREEN} COLOR_E;
COLOR_E color,c2;
int c;
initial begin
color = BLUE; //赋一个已知的合法值
c = color; //将枚举类型转换成整型(1)
c++; //整型递增(2)
if(!$cast(color,c)) //将整型显式转换回枚举类型
$display("Cast failed for c = %0d",c);
$display("Color is %0d /%s",color,color.name);
c++; // 3 对于枚举类型已经越界
c2 = COLOR_E'(c); //不做类型检查
$display("c2 is %0d/%s",c2,c2.name);
end
十二、字符串
SV中的 string 类型可以保存长度可变的字符串,字符串使用动态的存储方式,所以不用担心存储空间会全部用完。
例 2.53 字符串方法
string s;
initial begin
s = "IEEE ";
$display(s.getc(0)); //显示:73('I')
$display(s.tolower()); //显示:ieee
s.putc(s.len()-1,"-"); //将空格变成'-'
s = {s,"P1800"}; //"IEEE-P1800"
$display(s.substr(2,5)); //显示:EE-P
创建临时字符串,注意格式
my_log($psprintf("%s %5d",s,42));
end
task my_log(string message);
//把信息打印到日里
$display("@%0t: %s",$time,message);
endtask
函数 getc(N) 返回位置 N 上的字节,toupper 返回一个所有字符大写的字符串,tolower 返回一个小写的字符串。大括号{}用于串接字符串。任务 putc(M,C) 把字节 C 写到字符串的 M 位上,M 必须介于 0 和 len 所给出的长度之间。函数 substr(start,end) 提取出从位置 start 到 end 之间的所有字符。函数 $psprintf() 返回一个格式化的临时字符串,并且可以直接传递给其他子程序。