数据类型
内建数据模型
SV对Verilog是兼容的,Verilog的数据类型SV都包括。例如net的wire,variable中的reg,integer,real等
相对于Verilog将寄存器类型reg和线网(net)类型,如wire,SV中引入了logic数据类型。
Verilog便硬件描述,所以注重的是声明的变量是寄存器类型还是线网类型。
SV偏验证,不关心logic对应逻辑应该被综合成为寄存器类型还是线网类型,只会作为单纯的变量进行赋值。
logic类型为四值逻辑,分别是0、1、x、z。x代表不确定,可能为0也可能为1;z代表高阻态,表示没有被驱动。四值逻辑可以看作是硬件的世界。
bit类型为二值逻辑,分别是0、1。二值逻辑可以看作是软件的世界。
- 四值逻辑
integer
logic
reg
net-type(wire、tri)
- 二值逻辑
byte
short int
int
long int
bit
- 有无符号划分
有符号:byte、short int、int、long int、integer
无符号:bit、logic、reg、net-type(wire、tri)
- 赋值与比较
赋值:阻塞赋值“=”,非阻塞赋值“<=”
比较:等于“==”, 不等于“!=”
- 显式转换
静态转换:在转换的表达式前面加上单引号,该方式不会对转换值做检查
动态转换:$cast(src,dst), 转换失败会在仿真的时候报错
- 隐式转换
logic[3:0] a = 4'b111x;
bit[2:0] b;
b = a; //b的值应该为110,四值赋值给二值逻辑类型时,x变成0,z也会变成0
在不同类型进行操作时要注意:
1、逻辑数值类型:是四值还是二值
2、符号的类型
3、矢量的位宽
定宽数组
- 数组声明
一维数组:
int array[0:15]; //16个int类型元素
int array1[16]; //C语言风格
二维数组:
int array2[0:7][0:3]; //完整声明
int array3[8][4]; //紧凑声明
- 初始化和赋值
//声明的同时,对4个元素初始化
int array[4] = '{0,1,2,3};
//声明和赋值分开操作
int array[4];
array = '{1,2,3,4};
//4个值全部赋值为1
array = '{4{1}};
//部分元素使用默认赋值
array = '{1,2,default:0}; //{1,2,0,0}
- pack/unpack 合并型数组/非合并型数组
//合并型数组
bit[3][7:0] b_pack;
logic[3][7:0] l_pack;
合并型数组可以实现连续的存储,赋值时不需要用 '{ }
p_pack:表示3行x8列的数组,[3]是高维度,[7:0]的低维度(第一维)
数组中,数据排列为{b_pack[2],b_pack[1],b_pack[0]},其中每个b_pack为8个bit;
bit是二值逻辑,每位bit只占据1位。故24位(8bit * 3)只占据一个word(一般一个word为32bit)的存储空间;
l_pack:因为是非合并型,每一个元素占用一个WORD空间,但是有效空间只有8bit。
//非合并型数组
bit[7:0] b_unpack[3]; //形式2 int array[7:0][1023:0] 也是非组合型数组,[7:0]是高维度
logic[7:0] l_unpack[3];
存储的数据都是相互独立(不连续)的,赋值时需要用 '{ }。
b_unpack: [3]是高维度,bit 是二值逻辑,表示占据了三个word的空间,每个占据低8位空间
l_unpack:logic是4值逻辑,每一位logic需要2bit表示,所以24bit logic就需要48位来表示。即需要两个word来存储。
- 数组基本操作:for/foreach/
initial begin
bit[31:0] b;
int array[3] = {1,1,1};
for(int i = 0; i < $size(b); i++) //for循环进行赋值
b[i] = i;
foreach(array[j]) //foreach循环遍历
$display(j,array[j]);
end
动态数组
定宽数组:宽度编译时确定
动态数组:运行时再确定宽度
动态数组声明时用[],之后使用new[]来分配空间
int array[];
initial begin
...
array = new[5];//分配5个元素
...
end
关联数组
关联数组:用于保存稀疏矩阵的元素。当你对一个非常大的地址空间寻址时,该数组只为实际写入的元素分配空间,这种实现方法所需的空间比定宽或动态数组所占用的空间要小得多。
当使用超大容量的数组时,使用关联数组会更好一些。动态数组面临超大容量的情景,可能存在着浪费的情况,因为很有可能该大容量数组中有相当多的数据不会被存储和访问。而关联数组可以用来保存稀疏矩阵的元素,该数组只为实际写入的元素分配空间。
其他语言也有类似的数据存储结构,如哈希(hash)和python中的词典(dictionary),可以灵活赋予键值(key)和数值(value)。
bit [63:0] assoc[int],idx = 1;
repeat (64) begin //对稀疏分布的元素进行初始化
assoc[idx] = idx; //assoc[1] = 1
idx = idx << 1; //assoc[2] = 2,assoc[3] =4,...
end
forrach (assoc[i]) //使用foreach遍历数组
$display("assoc[%h] = %h",i,assoc[i]);
//找到并删除第一个元素
assoc.first(idx);
assoc.delete(idx);
遍历使用:first/last/next/prev
队列
队列的声明使用[$],队列元素的标号从0到 $ 。
队列不需要new[]来创建空间。
//队列声明
int array [$];
initial begin
for(int i = 0 ; i < 10 ; i++ ) begin
array.insert(i,i);
end
end
队列除了可以像数组一样通过index直接访问到内部元素,一般还会通过内建的一系列方法来对其进行操作。常用的几个内建方法列举如下:
函数 | 功能描述 |
---|---|
size() | 返回队列中元素个数 |
insert(index, item) | 将数据item插入到队列的index处。如果index为负数或存在x/z态或大于队列的size,则无法插入成功,会报warning。 |
delete([index]) | 删除队列中index处数据。当参数没有指定,则会将整个队列的数据都清空。 |
pop_front() | 将队列的第一个元素弹出。若是由一个空队列调用,则会返回元素默认值,且可能会报warning。 |
pop_back() | 将队列的最后一个元素弹出。若是由一个空队列调用,则会返回元素默认值,且可能会报warning。 |
push_front(item) | 将数据item添加到队列开头 |
push_back(item) | 将数据item添加到队列最后 |
结构体
Verilog没有数据结构,而在SV中可以使用struct语句创建结构体。struct只是一个数据的集合,伴随typedef可以用来创建新的类型。
typedef struct { //用户自定义类型的结构体
logic [7:0] a, b;
logic [31:0] address;
} Instruction_word_t;
Instruction_word_t IW; //用IW代替结构体名
//调用结构体内部变量
IW.a
IW.adress
将一个结构体声明为用户自定义类型不分配存储空间,在调用 (或实例化) 这个结构体时才会分配存储空间,示例中在Instruction_word_t IW;会给结构体分配存储空间
如果结构体不用typedef声明,它会被当作匿名结构体引用
module struct_tb;
typedef struct{
logic [7:0] a, b;
logic [3:0] all_one;
bit flag;
}Instruction_t;
Instruction_t IW = '{100, 8'hF0, '1, 0}; //实例化方式1
Instruction_t IW = '{a:10, b:8'h0F, all_one:2, flag:0}; //实例化方式2
initial begin //打印结构体中的内容
$display ("\n\t the a is %b", IW.a);
$display ("\n\t the b is %b", IW.b);
$display ("\n\t the all_one is %b", IW.all_one);
$display ("\n\t the flag is %b", IW.flag);
end
endmodule
枚举
enum经常和typedef搭配使用
module enumeration_basic;
//enum {red, green, yellow} traffic_lights;
typedef enum {red, green, yellow} traffic_lights;
traffic_lights light;
initial begin
$display("default light_name=%s, light=%0d",light.name,light);
light = red ;
#1;
light = yellow;
#1;
light = green;
#1;
end
initial begin
$monitor ("time %0t ns, ligh_name=%s, light=%0d",$time,light.name,light);
end
endmodule
如果枚举没有初始化,默认是int 型,则从第一个标识符开始,顺次赋给标识符0、1、2 …
例如要遍历一个枚举类型的数据,使用简单的for循环或者foreach循环是不行的,可以使用如下例中所示的 first() 以及 next() 方法.
SV中枚举类型提供了以下方法供用户使用:
first() //returns the first member of theenumeration.
last() //returns the last member of theenumeration.
next() //returns the next element of theenumeration.
next(N) //returns the Nth next element.
prev() //returns the previous element ofthe enumeration.
prev(N) //returns the Nth previous element.
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);
字符串
Verilog语言对于字符串的处理手段非常有限。SV引入了string类型用来容纳可变长度的字符串。
SV中字符串的特点:
字符串类型变量的存储单元为byte类型( 8位二值逻辑)。
字符串类型变量长度为N时,其字符成员索引值为从0到N-1
不同于C语言,字符串结尾没有空字符“\0”
字符串的内存是动态分配的,用户无需担心内存空间管理。
typedef logic [15:0] r_t;
r_t r;
integer i = 1;
string b = "" ;//声明赋予初值。定义的字符串默认值是空字符串
string a = {"Hi",b} ;//利用连接符号{}
r = r_t' (a) ; //字节类型转换,字符串变成向量类型
b = string'(r); //向量转换成字符串
b = "Hi";
b = { 5{ "Hi"}}; //b ={"Hi","Hi","Hi","Hi","Hi"}常量复制
a = { i{ "Hi"}}; //(非常量复制)a里面的i个Hi
a = {i{b}};
a = {a,b} ;
a = { "Hi",b};
b = {"H",""} ;//"H"。""是空字符串
a[0]=-"h";//等同于a[0]= "cough”
str.len()//返回字符串的长度。
str.putc(i, c)//将第i个字符替换为字符c,等同于str[i]=c。
str.getc(i)//返回第i个字符。
str.substr(i,j)//将从第i个字符到第j个字符的字符串返回。
str. {atoi(), atohex(), atooct, atobin}//将字符串转变为十进制、十六进制、八进制或者二进制数据。
string s ;
initial begin
s ="IEEE";
$display (s.getc(0)); //显示'I' 显示第几个字符
$display (s.tolower()); //显示ieee。把字符串小写
s.putc(s.len () -1, "-"); //将空格变为'-'。将最后一个字符变成'-'
s = {s, "P1800"}; //"IEEE-P1800"
$display(s.substr (2,.5));//显示EE-P
end