SystemVerilog数据类型学习

第二章 数据类型

2.1 内建数据类型

2.1.1 逻辑(logic)类型

wire:线网类型
reg:寄存器类型
logiclogic是对reg数据类型的改进,使得它除了作为一个变量之外,还可以被连续赋值、门单元和模块所驱动,代替wirereg,但只能有一个驱动,多个驱动如双向总线需使用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
  • 对于双状态类型例如intbit,则返回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,采用强制类型转换
  • 34
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值