第二章 数据类型
2.1 内建数据类型
2.1.1 逻辑(logic)类型
wire
:线网类型
reg
:寄存器类型
logic
:logic
是对reg
数据类型的改进,使得它除了作为一个变量之外,还可以被连续赋值、门单元和模块所驱动,代替wire
及reg
,但只能有一个驱动,多个驱动如双向总线需使用wire
//RAM modelling in SystemVerilog
module mema (r_wb,addr,d_q);
input r_wb;
input [ 7 : 0 ] addr;
inout [ 7 : 0 ] d_q;
logic r_wb;
logic [ 7 : 0 ] addrlogic [7:0] d_q; // erro! should be declared: wire [7:0] d_q;
logic [ 7 : 0 ] mem [ 0 : 255 ];
always @(r_wb or addr)
if ( ! r_wb)
mem[addr] = d_q;
else
d_q = mem[addr];
endmodule
2.1.2 双状态数据类型
bit b;
// 双状态,单比特
bit [31:0] b32;
// 双状态,32比特无符号整数
bit signed [7:0] b8
// 双状态,8比特有符号整数
int unsigned ui;
// 双状态,32比特无符号整数
int i;
// 双状态,32比特有符号整数
byte b8;
// 双状态,8比特有符号整数
shortint s;
// 双状态,16比特有符号整数
longint 1;
// 双状态,64比特有符号整数
integer i4;
// 四状态,32比特有符号整数
time t;
// 四状态,64比特有符号整数
real r;
// 双状态,双精度浮点数
$isunknown
操作符可以在表达式的任意位出现X或Z时返回1
if ($isunknown(iport) == 1)
$display("@%0t:4-state value detected on iport %b", $time, iport);
2.2 定宽数组
2.2.1 定宽数组的声明和初始化
-
定宽数组的声明
int lo_hi[0:15];
// 16个整数[0]…[15]
int c_style[16];
// 16个整数[0]…[15] -
多维数组声明
int array2[0:7][0:3];
// 完整的声明
int array3[8][4];
// 紧凑的声明
array2[7][3] = 1;
// 设置最后一个元素
如果试图从一个越界的地址中读取数据,SystemVerilog将返回数组元素型的缺省值
- 对于一个元素为四状态类型的数组,例如
logic
,返回的是X - 对于双状态类型例如
int
或bit
,则返回0 wire
型在没驱动的时候输出是Z
2.2.2 常量数组
int ascend[4] = '{0,1,2,3}; // 对四个元素进行初始化
int descend[5];
descend = '{4,3,2,1,0}; // 为5个元素赋值
descend[0:2] = '{5,6,7}; // 为前三个元素赋值
ascend = '{4{8}}; // 4个元素全为8
descend = '{9,8,default:1}; // {9,8,1,1,1}
2.2.3 基本的数组操作(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;
end
遍历多维数组
对于二维数组:int md[2][3] = '{'{0,1,2},'{3,4,5}}
foreach(md[i,j])
:遍历顺序为 md[0][0] md[0][1] md[0][2] md[1][0] md[1][1] md[1][2]
foreach(md[i])
:遍历第一个维度 md[0][] md[1][]
foreach(md[,j])
:遍历第二个维度 md[][0] md[][1] md[][2]
foreach循环会遍历原始声明中的数组范围
对于数组f[5]等同于f[0:4]:foreach(f[i])
等同于for(int i = 0; i <= 4; i++)
对于数组rev[6:2]:foreach[rev[i]]
等同于for(int i = 6; i >= 2; i--)
2.2.4 基本的数组操作(聚合复制和聚合比较)
- 可以在不使用循环的情况下对数组进行聚合比较和聚合复制
- 对于数组 bit [31:0] src[5] = '{0,1,2,3,4}, dst[5] = '{5,4,3,2,1};
1.聚合比较==
和!=
:src == dst
或者src[1:4] != dst[1:4]
返回true或false
2.聚合复制=
:dst = src
或者dst[1:4] = src[1:4]
2.2.5 同时使用位下标和数组下标
- 对于数组bit [31:0] src[5] = '{5{5}};
$displayb(src[0][2:1])
显示'b10
2.2.6 非合并数组
- 很多SystemVerilog仿真器在存放数组元素时使用32比特的字边界,所以byte,shortint和int都是存放在一个字之中,而longint则存放在两个字中
- 在非合并数组中,字的低位用来存放数据,高位则不使用。
- 非合并数组声明:
bit[7:0] b_unpack[3];
b_unpack[0]
| | | |76543210|
b_unpack[1]
| | | |01234567|
2.2.7 合并数组
- 可以把合并数组当成一个整体来访问也可以分解成更小的单元
- 声明合并数组时,合并的位和数组大小作为数据类型的一部分必须在变量名前面指定
- 数组大小定义的格式必须是
[msb:lsb]
,而不是[size]
- 合并数组声明:
bit [3:0] [7:0] bytes;
四个字节组装成32比特
bytes[3]
bytes[1][6]
bytes |76543210|76543210|76543210|76543210|
$display(bytes,,bytes[3],,bytes[1][6])
- 合并数组与非合并数组混用
bit [3:0] [7:0] barray[3]; // 声明3*32比特数组
barray[0] = 32'h0123_4567;
barray[1][3] = 8'h01;
barray[2][2][6] = 1'b1;
barray[0]
barray[0]|76543210|76543210|76543210|76543210|
barray[1][3]
barray[1]|76543210|76543210|76543210|76543210|
barray[2][2][6]
barray[2]|76543210|76543210|76543210|76543210|
注意由于barray[3]
这个维度是非合并的,不能使用barray
访问数组整体,至少要有一个下标
2.2.8 合并数组和非合并数组的选择
1.和标量进行相互转换时,例如需要以字节或字为单位对存储单元进行操作
2.需要等待数组中的变化,如@
操作符只能用于标量或者合并数组,@(barray[0] or barray[1] or barray[2])
如果是合并数组则可以直接@(barray)
2.3 动态数组
- 定宽数组:其宽度在编译的时候就确定
- 动态数组:其宽度在程序运行的时候再指定
int dyn[],d2[] // 声明动态数组
initial begin
dyn = new[5]; // 分配5个元素的空间给动态数组
foreach(dyn[j]) dyn[j] = j; // 对数组元素进行初始化
d2 = dyn; // 复制动态数组的值给d2
d2[0] = 5; // 修改d2[0]的值
$display(dyn[0],d2[0]); // 0,5
dyn = new[20](dyn); // 分配20个元素的新空间给dyn并将原来dyn数组的5个元素复制过来,最后释放dyn数组原有的5个元素
dyn = new[100]; // 分配100个元素的新空间给dyn,释放原有的20个元素的空间,
// 旧值不复存在
dyn.delete(); // 删除所有元素
end
2.4 队列
q2[$] = {3,4}, q[$] = {0,2,5};
initial begin
q.insert(1,1); // 在队列q索引1处插入值1:{0,1,2,5}
q.insert(3,q2); // 在队列索引3处插入队列q2:{0,1,2,3,4,5}
q.delete(1); // 删除索引1处的值:{0,2,3,4,5}
// 下面的操作执行速度很快
q.push_front(6); // 队列前端插入 6:{6,0,2,3,4,5}
q.pop_back; // 队列后端出队 5:{6,0,2,3,4}
q.push_back(8); // 队列后端插入 8:{6,0,2,3,4,8}
q.pop_front; // 队列前端出队 6:{0,2,3,4,8}
foreach(q[i])
$display(q[i]);
q.delete(); // 删除整个队列
end
等同于上述代码的另一种写法:
q2[$] = {3,4}, q[$] = {0,2,5};
initial begin
q = {q[0],1,q[1:$]}; // 在队列q索引1处插入值1:{0,1,2,5}
q = {q[0:2],q2,q[3:$]}; // 在队列索引3处插入队列q2:{0,1,2,3,4,5}
q = {q[0], q[2:$]}; // 删除索引1处的值:{0,2,3,4,5}
// 下面的操作执行速度很快
q = {6,q}; // 队列前端插入 6:{6,0,2,3,4,5}
q = q[0:$-1]; // 队列后端出队 5:{6,0,2,3,4}
q = {q,8}; // 队列后端插入 8:{6,0,2,3,4,8}
q = q[1:$]; // 队列前端出队 6:{0,2,3,4,8}
foreach(q[i])
$display(q[i]);
q = {}; // 删除整个队列
end
2.5 关联数组
SV提供了关联数组类型,用来保存稀疏型的数据。与其他数组不同的是,关联数组有利于使用零散的存储空间,关联数组存储数据并不是连续的存储空间。
关联数组声明方式:data_type array_name[index_type]
integer i_array[*]; // 整数关联数组(未指定索引),不推荐使用*这种风格
bit [20:0] array_b[string]; // 21位向量的关联数组,使用字符串类型作为索引
event ev_array[myClass]; // 事件类型的关联数组,使用类myClass索引
关联数组的一些方法
num() //返回数组长度
delete() //删除指定元素或者所有元素
exists() //检查是否元素存在,存在返回1,否则返回0
first() //将指定的索引变量赋值为数组第一个索引的值
last() //将指定的索引变量赋值为数组最后一个索引的值
next() //索引变量被赋值为下一个条目的索引
prev() //索引变量被赋值为上一个条目的索引
注意
1.简单的for循环不能遍历关联数组,需要使用foreach遍历数组,还可以使用内建的first()和next()函数。
2.读取不存在的关联数组元素,4值逻辑返回x,2值逻辑返回0。
3.关联数组的索引可以是多种数据类型:string、int,甚至是class以及用户自定义的数据类型。
initial begin
bit [63:0] assoc[int],idx =1;
// 对稀疏矩阵的元素进行初始化
repeat (64) begin
assoc[idx] =idx;
idx = idx << 1;
end
// 使用foreach遍历数组
foreach (assoc[i])
$display("assoc[%h]= %h", i, assoc[i]);
// 使用函数遍历数组
if (assoc.first(idx))
begin // 得到第一个索引
do
$display("assoc[%h]=%h",idx, assoc[idx]);
while (assoc.next(idx)); // 得到下一个索引
end
// 找到并删除第一个元素
assoc.first(idx);
assoc.delete(idx);
$display("The array now has %0delements", assoc.num);
end
2.6 链表
SystemVerilog提供了链表数据结构,类似标准模板库(STL)的列表容器。避免使用
2.7 数组的方法
如果不带参数,圆括号可以省略
2.7.1 数组的缩减方法
把一个数组缩减成一个值:sum
(和),product
(积),and
,or
,xor
2.7.2 数组定位方法
针对定宽数组、动态数组、队列,返回一个队列:min
,max
,unique
(返回数组中具有唯一队列的值)
int f[6] = '{1,6,2,6,8,6};
f.unique(); // {1,6,2,8}
find
int d[] = '{9,1,8,3,4,4};
d.find with (item > 3); // 值大于3的值:{9,8,4,4}
d.find_index with (item > 3); // 值大于3的索引:{0,2,4,5}
d.find_first with (item > 3); // 第一个大于3的值:{9}
d.find_first_index with (item > 3); // 第一个大于3的值的索引:{0}
d.find_last with (item > 3); // 最后一个大于3的值:{4}
d.find_last_index with (item > 3); // 最后一个大于3的值的索引:{5}
2.7.3 数组的排序
reverse(); // 翻转,不能带with条件语句
sort(); // 左小右大
rsort(); // 左大右小
shuffle(); // 随机打乱,不能带with条件语句
2.8 选择存储类型
略
2.9 使用typedef
创建新类型
类似CPP的语法
typedef reg [7:0] reg8_t
创建了个reg[7:0]的新类型reg8_t
2.10 使用struct创建用户自定义结构
类似CPP的语法
struct {bit[7:0] r,g,b;} pixel_t; // 创建类型
pixel_t my_pixel; // 声明
initial begin
my_pixel = '{8'h00,
8'h00,
8'h00
}; // 初始化
end
my_pixel.r;
my_pixel.g;
my_pixel.b; // 获取struct内的元素
2.11 类型转换
2.11.1 静态转换(编译时转换)
静态转换有三种,分别是数据类型强制转换、向量宽度强制转换和符号强制转换,格式分别为:
数据类型强制转换:<type>'(<expression>)
7 + int'(2.0 * 3.0); //将(2.0 * 3.0)的结果转换成整型然后加7
向量宽度强制转换:<size>'(<expression>)
logic [15:0] a, b, y; y = a + b ** 16'(2); // 将文本值2强制转换为16位宽
符号强制转换:<sign>'(<expression>)
shortint a, b;
inty;
y = y - singed'({a, b}); // 将拼接结果强制转换成有符号值
静态强制转换是编译时的转换,转换的操作总会运行,而不会检查结果的有效性
2.11.2 动态类型强制转换
-
系统函数
$cast
是动态的,并且在运行时进行带转换数值的检查 -
动态强制类型转换的格式为:
$cast(dest_var, source_var)
系统函数$cast
有两个变量:目标变量和源变量
下面几种情况会导致无效的强制类型转换:
- 将real类型转换成int类型,而实数太大,没有办法用int来描述
int radius, area;
always @(posedge clock)
$cast (area, 3.154 * radius ** 2); //强制转换操作符的结果被转换为area类型将一个数值转换成枚举类型,而它不在枚举类型的合法值列表中
typedef enum {s1, s2, s3} state_t;
state_t state, next_state;
always_latch begin
$cast (next_state, state + 1);
end
- $cast可以作为任务调用也可以作为函数调用,作为任务调用时,如果转换不成功,会报告运行时错误,但作为函数调用时,不会报告运行时错误
系统函数$cast具有返回值,如果转换成功,返回1;转换失败,返回0
例如:
typedef enum {s1, s2, s3} state_t;
state_t state, next_state;
always_comb begin
status = $cast(next_state, state + 1);
if(status == 0) //如果强制转换不成功
next_state = s1;
end
$cast
函数不能和直接修改源表达式的操作符(++, +=)一块用
$cast
函数主要用途是将表达式的结果赋给枚举类型变量
静态强制转换是可综合的,动态强制转换由于一些综合工具可能不支持$cast系统函数
2.11.3 流操作符(<< 和 >>)
- 流操作符用在赋值表达式的右边,后面带表达式、结构或数组。
- 流操作符用于把其后面的数据打包成一个比特流
>>
把数据从左至右变成流,而<<
把数据从右至左变成流
initial begin
int h;
bit [7:0] b, g[4], j[4] = '{8'ha, 8'hb, 8'hc, 8'hd}; // 'b0000_1010_0000_1011_0000_1100_0000_1101
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, r, q}}; //把字节集中到h
end
2.12 枚举类型
用于指令中的操作码或状态机
- 使用内建的
name()
函数,可以得到枚举变量值对应的字符串
// 创建代表0,1,2的数据类型
typedef enum {INT,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
2.12.1 定义枚举值
- 枚举值缺省为从0开始的递增整数,也可以定义自己的枚举值
typedef enum {INIT,DECODE=2,IDLE} fsmtype_e;
// 0,2,3
注意 枚举值开始位置为0能避免一些错误
2.12.2 枚举类型的子程序
一些可以遍历枚举类型的函数
first() // 返回第一个枚举常量
last() // 返回最后一个枚举常量
next() // 返回下一个枚举常量
next(N) // 返回以后第N个枚举变量
prev() // 返回前一个枚举变量
prev(N) // 返回以前第N个枚举变量
// next和prev会以自动环形方式绕回
一般使用do...while
循环来遍历所有值
typedef enum {RED,BLUE,GREEN} color_e;
color_e color;
color = color.first;
do
begin
$display("Color = %0d/%s",color,color.name);
color = color.next;
end
while(color != color.first); // 环形绕回时即完成
2.12.3 枚举类型的转换
- 枚举类型的缺省类型为双状态
int
- 可以直接将枚举类型值赋给非枚举变量如int
int
类型赋值给枚举类型需要显示类型转换$cast
,这样可以检查是否越界
typedef enum {RED,BLUE,GREEN} COLOR_E;
COLOR_E color,c2;
int c;
initial begin
color = BLUE;
c = color;
c++;
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
2.13 常量
允许在变量声明时对其进行初始化,但不能在过程之中改变其值
initial begin
const byte colon = ":";
...
end
2.14 字符串
string类型可以用来保存长度可变的字符串,单个字符为byte
类型
string s;
// 声明
getc(N)
// 返回位置N上的字节
toupper
// 返回一个所有字符大写的字符串
tolower
// 返回一个小写的字符串
putc(M,C)
// 把字节C写到字符串的M位上,M必须介于0和给出的字符串长度之间
substr(start,end)
// 提取出从位置start到end之间的所有字符
2.15 表达式位宽
bit[7:0] b8;
bit one = 1'b1; // 单比特
$displayb(one+one); // A:1+1=0
b8 = one+one;
$displayb(b8); // B:1+1=2
$displayb(one+one+2'b0); // C:1+1=2,使用了常量
$displayb(2'(one)+one); // D:1+1=2,采用强制类型转换