结构体:Verilog的最大缺陷之一就是没有数据结构。而在SV中可以使用struct语句进行结构的创建。
结构体功能&特点:数据的集合,将若干变量组合到一个struct结构定义中、可综合、可通过端口进行传递。但如果想生成带约束的随机数据时,应该使用类。可以使用typedef来创建新的且可以共享的类型,并利用新类型声明更多的变量。
例如:
<----创建一个pixel类型的结构体---->
struct {bit [7:0] r,g,b} pixel;
<----若想在端口和程序中共享它,则必须使用typedef来创建---->
typedef struct {bit [7:0] r,g,b} pixel_s;
一:结构体细化还可分为packed结构体、unpacked结构体和union(联合)结构体,为了方便比较我们把packed结构体和unpacked结构体放在一起比较。
1:packed与unpacked结构体代码及其仿真
<--------unpacked结构体-------->
module top;
initial begin
typedef struct {
bit [7:0] r;
bit [7:0] g;
bit [7:0] b;
} pixel_s;
pixel_s ps;
ps = '{'b10,'b10,'b10}; //给结构体ps赋值,这里需要注意的是,赋值方式与unpacked数组的赋值方式是一致的,如果去掉花括号外边的'符号编译不会通过!!!!
$display("ps = %0d %0d %0d",ps.r,ps.g,ps.b);
end
endmodule
仿真 : ps = 2 2 2
<---------packed结构体--------->
module top;
initial begin
typedef struct packed {
bit [7:0] r;
bit [7:0] g;
bit [7:0] b;
} pixel_s;
pixel_s ps;
ps = {'b10,'b10,'b10}; //给结构体ps赋值,这里需要注意的是,赋值方式与packed数组的赋值方式是一致的,如果加上会强制转换成unpacked形式
$display("ps = %0d %0d %0d",ps.r,ps.g,ps.b);
end
endmodule
仿真 : ps = 0 0 2 //注:若赋值时并没有加位宽则默认32bit,又因为是packed类型,会覆盖掉前16bit的值,若想避免这种情况发生,则需要在赋值时加入位宽 例如:
ps = {8'b10,8'b10,8'b10};
二:union(联合)结构体
看代码更直观
Part1
typedef struct packed{
bit [7:0] r;
bit [7:0] g;
bit [7:0] b;
} pixel_rgb_a; //创建r,g,b的packed结构体
typedef struct packed{
bit [7:0] y;
bit [7:0] u;
bit [7:0] v;
} pixel_yuv_a; //创建另一个含有y,u,v的packed结构体
typedef union packed{
pixel_rgb_a rgb;
pixel_yuv_a yuv;
} pixel_a; //创建包含rgb和yuv结构体的联合结构体
pixel_rgb_a rgb_test;
pixel_yuv_a yuv_test;
pixel_a union_test; //声明句柄
<---------------------------------------------------------------------------->
Part2
initial begin
rgb.test.r = 8'h11;
rgb.test.g = 8'h22;
rgb.test.b = 8'h33; //对rgb结构体进行赋值
$display("RGB is R = %h, G = %h, B = is %h",rgb.test.r,rgb.test.g,rgb.test.b); //打印结果为 R = 11,G = 22,C = 33
<---------------------------------------------------------------------------->
Part3
yuv_test.y = 8'h77;
yuv_test.u = 8'h88;
yuv_test.v = 8'h99;
$display("YUV is Y = %h, U = %h, V = is %h",yuv.test.r,yuv.test.g,yuv.test.b); //打印结果为 Y = 77,U = 88,V = 99
<---------------------------------------------------------------------------->
Part4
pixe_test = 0;
$display("RGB is R = %h, G = %h, B = is %h",union_test.rgb.r,union_test.rgb.g,union_test.rgb.b); //猜是什么 结果是R = 0,G = 0,C = 0
$display("YUV is Y = %h, U = %h, V = is %h",union_test.yuv.y,union_test.yuv.u,union_test.yuv.v); //结果是Y = 0,U = 0,V = 0
<---------------------------------------------------------------------------->
Part5
pixel_test.rgb.r = 8'hab;
pixel_test.rgb.g = 8'hcd;
pixel_test.rgb.b = 8'hef;
$display("YUV is Y = %h, U = %h, V = is %h",union_test.yuv.y,union_test.yuv.u,union_test.yuv.v); //结果是Y = ab,U = cd,V = ef
end
总结:
从Part4、5可以看出联合结构体内的各个结构体是相互影响的。更明确来说联合结构体内部是是共享同一段内存。
(1)联合结构体可以定义多个不同类型的成员,联合结构体的大小由其中最大的成员决定
(2)修改联合结构体其中的某个变量会覆盖或取代其他变量的值。
(3)联合结构体所有变量公用同一块内存,变量之间互斥
(4)结构体与联合结构体的优缺点比较:
1:结构体重所有变量是共存的,比较全面 缺点:结构体内存空间分配是粗糙的,不管用不用都会分配内存。
2:联合结构体中各变量是互斥的, 优点:对于内存的使用灵活,节省内存空间
参考:温水里的一直青蛙
三:类型转换
在SV中,我们有例如byte、int等等多种数据类型,那我们就不可避免的需要在他们之间进行转换
(1)如果原变量和目标变量的bit位分布完全相同,例如若int类型和枚举类型都为4个字节长度时,他们之间就可以直接相互赋值。
(2)如果bit位分布不同,例如字节数组和字数组,则需要使用流操作符对bit分布重新安排
流操作符 << 和 >> 用在赋值表达式的右边,后面带表达式、结构或数组。它的作用是将其后的数据打包成一个bit流,操作符 >> 把数据从左至右变成流,<< 把数据从右至左变成流。指定一个片段宽度,把源数据按照这个宽度分段以后再转变成流。这里不能将bit流结果直接赋值给unpacked数组,应该在赋值表达式的左边使用bit流操作符把bit流拆分到unpacked数组中。
(1)流的基本操作:
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 将数组向右打包成整型 00001010_00001011_00001100_00001101
h = {<<{j}}; //b030d050 将数组向左打包成整型 10110000_00110000_11010000_01010000 位倒序
h = {<<byte{j}}; //0d0c0b0a 字节倒序
g = {<<byte{j}}; //'{'ha,'hb,'hc,'hd} 拆分成数组
b = {<<{8'b0011_0101}}; //1010_1100 按位倒序
b = {<<4{8'b0011_0101}}; //0101_0011 按半字节倒序
{>>{q,r,s,t}} = j; //10,11,12,13 将j分散到四个字节变量里面
h = {>>{t,s,r,q}}; //0d0c0b0a 将字节集中到h里面
end
如果需要打包或拆分数组,可以使用流操作符来完成具有不同尺寸元素的数组间的转换。对于定宽数组、动态数组和队列都可以这样。
(2)使用流操作符进行队列间的转换
initial begin
bit [15:0] wq[$] = {16'h1234,16'h5678};
bit [7:0] bq[$];
bq = {>>{wq}}; //'{'h12,'h34,'h56,'h78} 将字数组转换成字节数组
end
<----------------------------------------------------->
initial begin
bit [15:0] wq[$]
bit [7:0] bq[$] = {8'h98,8'h76,8'h54,8'h32};
wq = {>>{bq}}; //'{'h9876,'h5432} 将字节数组转换成字数组
end
(3)使用流操作符在结构和数组间进行转换
initial begin
typedef struct{
int a ;
byte b;
shortint c;
int d;
} my_struct_s;
my_struct_s st = '{32'haaaa_aaaa,8'hbb,16'hcccc,32'hdddd_dddd};
byte b[];
b = {>>{st}}; //将结构转换成字节数组
end
<-------------------------------------------------->
initial begin
typedef struct{
int a ;
byte b;
shortint c;
int d;
} my_struct_s;
my_struct_s st
byte 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}}; //将字节数组转换成结构
end
四:静态转换与动态转换
(1)静态转换操作不对转换值进行检查。如下,转换时指定目标类型,并在需要转换的表达式前加上单引号即可。注意,Verilog对整数和实数类型,或者不同位宽的向量之间进行隐式转换。
在整型和实数型之间转换
int i;
real r;
i = int '(10.0-0.1); // 10 转换是非强制的 这里会有些疑问? 什么是强制什么又是非强制?
r = real'(42); // 42 转换是非强制的
(2)动态转换使用函数$cast进行转换
五:枚举类型
枚举创建了一种强大的变量类型,它仅限于一些特定名称的集合,例如指令中的操作码或者状态机中的状态名。例如ADD、IDLE等,这些名称有利于编写和维护代码。定义常量的另一种方法是使用参数。但是参数需要对每个数值进行单独的定义,而枚举类型却可以自动为列表中的每一个名称分配不同的数值。
(1)最简单的枚举类型声明包含了一个常量名称列表以及一个或多个变量,例如
enum {RED,BLUE,GREEN} color;
//枚举类型enum经常和typedef搭配使用,便于用户自定义枚举类型的共享使用
//枚举类型可以保证一些非期望值不会出现,降低了风险
例: typedef enum {RED,BLUE,GREEN} color;
//创建代表0,1,2的数据类型
typedef enum {INIT,DECODE,IDLE} fsmstate_e;//每一个枚举变量都对应一个且只有一个数值,默认值为int类型且从0开始累加,依次为0,1,2。当然我们也可以自定义枚举值,例如:
typedef enum {INIT,DECODE = 2,IDLE} fsmstate_e;//依次为0,2,3。
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()); //使用内建的name()函数,可以得到枚举变量值对应的字符串。
end
需要注意的是,由于枚举变量的缺省值为int类型,而其缺省值是0,所以在给枚举变量赋值时应该小心, 初始化赋值为0会与枚举变量赋值发生冲突而导致语法错误,因此我们把0指定给第一个枚举变量可以避免这个错误
//The bad example
typedef enum {FIRST = 1,SECOND,THIRD} number;
number nb;
//The great example
typedef enum {BAD_O = 0,FIRST = 1,SECOND,THIRD} number;
number nb;
(2)枚举类型的子程序
sv提供了一些可以遍历枚举类型的函数
(1)first() //返回第一个枚举常量
(2)last () //返回最后一个枚举变量
(3)next () //返回下一个枚举变量
(4)next (N) //返回后面第N个枚举常量
(5)prev () //返回前一个枚举变量
(6)prev (N) //返回前N个枚举变量
//当达到枚举变量列表的头或者尾时,函数next和prev会自动以环形方式绕回
//遍历所有枚举成员的方法
typedef enum {RED,BLUE,GREEN} color;
color co;
co = co.first; //先返回第一个值,开个头!
do //进入do-while循环,开始通过co.next遍历枚举变量成员
begin
$display(“CO = %0d/%s”,co,co.name);
co = co.next;
end
while(co != co.first); //当回环时遍历完成
(3)枚举类型的转换
枚举类型的缺省类型为双状态int,可以使用简单的赋值表达式把枚举变量的值赋给例如int类型的非枚举变量。但是在sv中不允许在没有进行显示类型转换的情况下把整型变量赋值给枚举变量。这样做的目的是能让你意识到可能存在数值越界的情况。
例如:整型和枚举类型之间的相互赋值
typedef enum {RED,BLUE,GREEN} COLOR_E;
COLOR_E color,c2;
int c;
initial begin
color = BLUE; //赋一个已知的合法值
c = color //直接将枚举类型转换成整型是允许的
c ++;
if(!$cast(color,c)) //使用显示转换$cast将整型变量赋值给枚举变量,因为动态转换方式有检查的功能
$display("cast failed for c = %0d",c);
$display("color is %0d /%s",color,color.name);
c ++;
c2 = COLOR_E'(c); //使用静态方式将整型变量转换为枚举变量不可取,因为c此时可能已经越界,毕竟枚举类型此时最大的数值才是2,而且静态转换不会做类型检查,应避免这种转换方式
end
六:常量
sv中有好几种类型的常量。在verilog中创建常量的最典型的方法是使用文本宏。它的好处是:宏具有全局作用范围并且可以用于位段和类型定义。而缺点也正是因为宏具有全局作用范围,在只需要一个局部常量时可能会引发冲突。
在sv中我们可以通过如下方法来定义常量
(1)在单独的程序包中声明,然后include到其他模块中使用。
(2)可以通过传递参数(parameter)的形式引入常量,也可以在模块内部定义全局变量parameter,但只能在单个模块使用。
(3)还可以通过typedef来实现模块内部变量的共享。
(4)sv中也支持const修饰符,允许在变量声明时对其进行初始化,但不能在过程代码中改变其值。
七:字符串
sv中的string类型可以用来保存长度可变的字符串,且单个字符时byte类型(二值逻辑有符号)。字符串使用的是动态存储的方式,所以不用担心存储空间会全部用完。
字符串操作方法:
string s;
initial begin
s = "IEEE";
$display(s.getc(N)); //返回位置N上的字节
//$display(s.getc(0)); //返回位置0上的字节73(‘I’) ASCII码
$display("%s",s.getc(0)); //返回I
$display(s.toupper()); //显示所有字符大写的字符串
$display(s.tolower()); //显示所有字符小写的字符串
s.putc(s.len()-1,“-”); //putc(M,C)把字节C写到字符串M上,且M必须介于0和len所给出的长度之间,将空格变为“-”
s = {s,"P1800"}; //{}符号用于串接字符串 IEEE-P1800
$display(s.substr(2,5)); //substr(start,end)提取出从位置start到end之间的所有字符 显示EE-P
my_log($psprintf("%s %5d",s,42)); //创建临时字符串
end
task my_log(string message); //将信息打印到日志里
$display("@%0t: %s",$time,message);
endtask