• SystemVerilog(1):数据类型


    工作中偶尔要写测试pattern和bus的性能测试,还是懂一点SystemVerilog好,不需要学得和验证一样精通,只希望能懂点基本的。声明:SystemVerilog系列博客是纯小白的笔记和流水账,没有任何营养价值,请谨慎阅读!

    1、logic和bit

    SV作为验证语言,不关心变量对应的逻辑应该被综合为寄存器还是线网,同时为了方便DV(IC验证)驱动和连接硬件模块,省去考虑reg和wire的精力,于是新引入了logic和bit。也就是说硬件端的reg和wire,在写SV时可以就写成是logic或bit,它们都是无符号型数据类型。

    注意:logic和bit不能是多驱,即如果硬件端用的是inout wire类型,则就不能用logic/bit,只能老老实实的用wire。

    1.1 logic和bit的比较

    之所以有了四值逻辑logic,还要引入二值逻辑bit,是因为软件的世界即验证环境更多的是二值逻辑。相比四值逻辑logic,二值逻辑bit有利于提高仿真器的性能并减少内存的使用量。

    //**************************************************************************
    // *** 名称 : SV_02_bit_vs_logic.v
    // *** 描述 : logic(4值)和bit(2值)的比较:
    //**************************************************************************
    module SV_02_logic_vs_bit;
    
    initial begin: logic_vs_bit
        bit                         bit_num;
        logic                       logic_num;
        $display("--------< test start >--------");
        //---------------------------------------------------
        logic_num   = 'b1;          $display("logic_num = %d", logic_num);
        bit_num     = logic_num;    $display("bit_num = %d", bit_num);
        //  # logic_num = 1
        //  # bit_num = 1
        //---------------------------------------------------
        logic_num   = 'b0;          $display("logic_num = %d", logic_num);
        bit_num     = logic_num;    $display("bit_num = %d", bit_num);
        //  # logic_num = 0
        //  # bit_num = 0
        //---------------------------------------------------
        logic_num   = 'bx;          $display("logic_num = %d", logic_num);
        bit_num = logic_num;        $display("bit_num = %d", bit_num);
        //  # logic_num = x
        //  # bit_num = 0
        //---------------------------------------------------
        logic_num   = 'bz;          $display("logic_num = %d", logic_num);
        bit_num     = logic_num;    $display("bit_num = %d", bit_num);
        //  # logic_num = z
        //  # bit_num = 0
    end
    
    
    
    endmodule

    1.2 四值逻辑的检查

    如果DUT试图产生 x 或 z,采用 bit 后这些值会被转换为 0 或 1,使用 $isunknow 可以在表达式的任意位出现 x 或 z 时返回 1。

    if($isunknow(iport)==1)
        $display("@%0t: 4-state value detected on iport %b", $time, iport);

    2、有符号数

    大多数时候我们用的都是无符号数,logic 和 bit 也是属于无符号数,但有些时候我们需要用到有符号数,也要注意它们的区别。

    2.1 byte和bit的比较

    无符号数即所有位宽都是有效数字,例如 8'hff 表示的是 255。

    有符号数的最高位是符号位,最高位为 1 表示负数,例如byte类型的最大值为 8'h0111_1111=127,最小值为 8'b1000_0000=-128,第二小的值为 8'b1111_1111=-127,最大的负数值为 8'b1000_0001=-1。

    //**************************************************************************
    // *** 名称 : SV_01_byte_vs_bit.v
    // *** 描述 : byte(有符号数)和bit(无符号数)的比较
    //**************************************************************************
    module SV_01_byte_vs_bit;
    
    initial begin: signed_vs_unsigned
        byte                    byte_num;
        bit     [7:0]           bit_num;
        $display("--------< test start >--------");
        byte_num = 'b1000_0000; $display("byte_num = %d", byte_num);
        bit_num  = byte_num;    $display("bit_num  = %d", bit_num);
    end
    //  # --------< test start >--------
    //  # byte_num = -128
    //  # bit_num  = 128
    
    
    endmodule

    2.2 数据类型转换

    数据类型的多样性意味着可能需要在它们之间进行转换,转换分为隐式转换和显式转换两种,显示转换又分为静态转换和动态转换。

    2.2.1 隐式转换

    隐式转换,即直接赋值过去,不推荐这种转换,往往会带来意外的错误。

    例1:

    byte byte_num = 8'b1000_000;
    bit [8:0] bit_num;
    initial begin
        bit_num = byte_num;
        $display("bit_num = 'h%x", bit_num); //'h180,最高位符号位会复制1位
        bit_num = unsigned'(byte_num);
        $display("bit_num = 'h%x", bit_num); //'h080,正确转换,去掉了符号
    end

    例2:

    logic [3:0] logic_num = 4'b111x;
    big   [2:0] bit_num;
    initial begin
        bit_num = logic_num;
        $display("bit_num = 'b%b", bit_num); //'b110,最高位被截掉
    end                                      //x值(或z值)转为了0

    2.2.2 显式转换

    显式转换,即通过操作符号或者系统函数介入实现转换。

    (1)静态转换

    上面的例1中的 unsigned'(byte_num) 通过操作符号实现的转换便是静态转换,这种转换不会进行任何类型的检查,所以转换结果也可能会越界,并且我们难以察觉。

    (2)动态转换

    使用动态转换函数 $cast(a,b) 将右边的变量 b 的值赋值给 a,它会对转换时的越界进行检查,如果赋值成功, $cast 返回 1,如果因数值越界而导致赋值失败,则不进行转换,函数返回 0。

    if($cast(a,b))
        $display("cast sucessful !", b);
    else
        $display("cast failed for b = %0d" !!!, b);

     如果把 $cast  当成 task 使用并且操作失败,那仿真时会打印出错误信息。

    3、数组

    3.1 定宽数组

    SystemVerilog中的数组表示方法多了一种紧凑声明,它是和完整声明完全等价的。

    3.1.1 单维数组和多维数组

    SystemVerilog中的数组表示方法多了一种紧凑声明,它是和完整声明完全等价的。如果试图从一个越界的地址中读取数据,那么 SV 将返回数组元素类型的缺省值:如果元素是 logic 型,返回 x;如果元素是 int/bit,返回 0。

    (1)单维数组

    int array1[0:15];   //完整声明:16个32位宽的有符号整数[0]...[15]
    int array1[16];     //紧凑声明:16个32位宽的有符号整数[0]...[15]

    (2)多维数组

    int array2 [0:7][0:3];     //完整声明:8行4列个32位宽的有符号整数
    int array2 [8][4];         //紧凑声明:8行4列个32位宽的有符号整数
    array2 [7][3]=1;           //设置最后一个元素的值为1

    3.1.2 合并数组和非合并数组

    (1)合并数组

    合并数组的存放方式是元素和元素之间是紧挨着的,声明时其元素个数写在变量名后面。

    bit [3] [7:2] b_pack; //合并数组

    (2)非合并数组

    非合并数组的存放方式是每个元素都需要单独占据一个字节,可能会有空间的浪费,声明时其元素个数写在变量名前面。上面的单维数组和多维数组的示例都是非合并数组的写法,但由于int等关键字刚好占据一个字节,因此int型不会造成空间的浪费。

    bit [7:0] b_unpack[3]; //非合并数组

    合并数组的存放方式是元素和元素之间是紧挨着的,而非合并数组的每个元素都需要单独占据一个字节,会有空间的浪费。

    3.1.3 数组初始化

    写法:一个单引号加大括号来初始化数组,可以一次性地为数组的部分或所有元素赋值。

    int array3[4] = '{0,1,2,3}; //定义时初始化初值
    
    int array4[5];              //定义5个的32位宽的有符号整数
    array4 = `{4,3,2,1,0};      //为这5个元素赋值
    array4[0:2] = `{5,6,7};     //为前3个元素赋值
    array4 = `{5{8}};           //赋值5个8
    array4 = `{9,8,default:1};  //前两个元素赋值9和8,其余元素赋值为1

    3.1.4 for和foreach

    操作数组的最常见的方式是使用for或foreach循环,foreach循环中会自动声明内部的索引变量。

    initial
        bit [31:0] src[5];             //定义5个32位宽的无符号整数
        for(int i=0;i<$size(src);i++)  //$size返回的是数组的宽度,即5
            src[i] = i;                //元素值为0,1,2,3,4
        foreach(dst[j])                //j效果等同于上面的i
            dst[j] = src[j] * 2;       //数组dst中的元素值是数组src中的元素值的2倍
    end                                //元素值为0,2,4,6,8

    在多维数组中,foreach的写法有点不一样,不同维度的变量可以写在一起,用中括号括起来,像 [i,j] ,写成 [i] 表示只遍历第一个维度,写成 [ ,j] 表示只遍历第二个维度。

    initial
        int md[2][3]=`{`{9,8,5},`{2,1,1}};//定义2行3列个元素,并初始化
        foreach(md[i,j])
            $display("md[%0d][%0d]=%0d",i,j,md[i][j]);
    end
    //打印结果如下所示:
    //md[0][0]=9
    //md[0][0]=8
    //md[0][0]=5
    //md[0][0]=2
    //md[0][0]=1
    //md[0][0]=1

    3.2 动态数组

    如果直到程序运行之前都还不知道数组的宽度,则可以使用动态数组。动态数组在声明时使用空的 [ ] ,必须调用 new[ ] 操作符来分配空间,意思是其宽度不在编译时给出,而在程序运行时再指定。

    int dyn[] dyn2[];         //声明动态数组
    initial begin
        dyn  = new[5];         //分配5个元素
        foreach(dyn[j])
            dyn[j]=j;         //数组初始化
        dyn2 = dyn;           //数组复制,定宽数组复制给动态数组
        dyn  = new[20](dyn);  //重新分配为20个新元素,并将旧的5个值赋值给其前5个元素
        dyn  = new[100];      //重新分配为100个新元素,旧值不复存在
        dyn.delete();         //删除所有元素
    end

    只要基本数据类型相同(例如都是int),定宽数组就可以赋值给动态数组;如果元素个数相同,那么动态数组也可以赋值给定宽数组。当把定宽数组复制给动态数组时,可以省略 new[ ]。

    3.3 队列

    队列的声明是使用带有美元符号的下表:[$],注意不要对队列使用构造函数 new[ ] 。此外队列的初始化也不需要使用“ ` ”。

    int j     = 1;
    int q1[$] = {3,4};     //队列初始化不需要用`
    int q2[$] = {0,2,5};   //队列初始化不需要用`
    
    initial
        q1.insert(1,j);          //在第1个元素前插入j:{0,1,2,5}
        q1.insert(3,q2);         //在第3个元素前插入队列q2:{0,1,2,3,4,5}
        q1 = {q1[$:2],j,q1[3:$]};//在第2/3个元素中间插入j:{0,1,2,1,3,4,5}
    
        q1.push_front(6);        //在队列最前面插入6:{6,0,1,2,1,3,4,5}
        q1 = {7,q1};             //在队列最前面插入7:{7,6,0,1,2,1,3,4,5}
        q1.push_back(8);         //在队列最后面插入8:{7,6,0,1,2,1,3,4,5,8}
        q1 = {q1,9};             //在队列最后面插入9:{7,6,0,1,2,1,3,4,5,8,9}
    
        j = q1.pop_front;        //j等于队列最前面的元素7
        j = q1[0];               //j等于队列最前面的元素7
        j = q1.pop_back;         //j等于队列最后面的元素9
        j = q1[$];               //j等于队列最后面的元素9
    
        q1.delete(1);            //删除队列的第1个元素:{6,0,1,2,1,3,4,5,8,9}
        q1.delete();             //删除整个队列
        q2 = {};                 //删除整个队列
    end

    可以把定宽数组或动态数组的值复制给队列。 

    4、struct结构体

    结构体只是一个数据的集合,原始的写法如下:

    struct{bit[7:0] r,g,b;} pixel;

    然而这并没有声明卵用,要想在端口和程序中共享它,必须创建一个新的类型:typedef:

    typedef struct{bit[7:0] r,g,b;} pixel;//声明结构体pixel
    pixel u_pixel = {8'h00,8'hff,8'hff};  //共享为u_pixel,并初始化

    可以把整数 i 和实数 f 存放在同一个结构体中,可以使用 typedef 创建联合:

    typedef union {int i; real f;} union_struct; //创建联合结构体
    union_struct u_union_struct;                 //共享
    u_union_struct.f=0.0                         //数值设置为浮点形式

    可以为节约空间,把结构体写成合并结构:

    typedef struct packed{bit[7:0] r, g, b;} pixel;
    pixel u_pixel;

    下面举了几个例子,说明 struct 结构体的使用:

    //**************************************************************************
    // *** 名称 : SV_04_struct.v
    // *** 描述 : struct结构体的使用
    //**************************************************************************
    module SV_04_struct;
    
    initial begin: struct_type
        //声明结构体my_struct
        typedef struct {
            bit[ 7:0]           addr;
            bit[31:0]           data;
            int                 id;
        } my_struct;
        
        //声明3个结构体
        my_struct u_struct_1, u_struct_2, u_struct_3;
        
        $display("--------< test start >--------");
        //--------------------------------------------------- 按序赋值
        u_struct_1 = '{'h10, 'h1122_3344, 'h1000};
        //全部打印用p,结果打印为10进制
        //u_struct_1 data is '{addr:16, data:287454020, id:4096}
        $display("u_struct_1 data is %p", u_struct_1);
        //--------------------------------------------------- 按名赋值
        u_struct_2.addr        = 'h20;
        u_struct_2.data        = 'h5566_7788;
        u_struct_2.id          = 'h2000;
        //u_struct_2 data is 2000
        $display("u_struct_2 data is %0h", u_struct_2.id);
        //--------------------------------------------------- struct赋值
        u_struct_3             = u_struct_2; //总体赋值
        u_struct_3.data        = 'h99AA_BBCC;//变量修改
        u_struct_3.id          = 'h3000;     //变量修改
        //u_struct_3 data is '{addr:32, data:2578103244, id:12288}
        $display("u_struct_3 data is %p", u_struct_3);
    
    end
    
    
    
    endmodule

    5、enum枚举

    枚举类型 enum 仅限于一些特定名称的集合,能够自动为列表中的每个名称分配不同的数值。使用内建的 name( ) 函数可以得到枚举变量值对应的字符串。

    typedef enum{INIT, DECODE, IDLE} state_name;
    state_name state_c state_n; //声明自定义类型变量
    
    initial
        case(state_c)
            IDLE:   state_n = INIT;  //结构体赋值
            INIT:   state_n = DECODE;
            default:state_n = IDLE;
        endcase
        $display("next state is %s", state_n.name()); //显示状态机名称
    end

    enum 可以自己定义枚举值,如果枚举值缺省,则为从 0 开始递增的整数(默认为 int 类型),例如下面的代码中使用 INIT 代表缺省值 0,DECODE 代表定义值 2,IDLE 代表缺省值 1。

    typedef num(INIT, DECODE=2, IDLE) state_name;

    下面举了几个例子,说明 enum 枚举类型的使用:

    //**************************************************************************
    // *** 名称 : SV_03_enum.v
    // *** 描述 : enum枚举的使用
    //**************************************************************************
    module SV_03_enum;
    
    initial begin: enum_type
        //定义枚举类型my_enum,IDLE,START,PROC,END 默认对应 0123
        typedef enum {IDLE,START,PROC,END} my_enum;
    
        //声明两个新的枚举类型
        my_enum u_enum_1,u_enum_2;
        
        $display("--------< test start >--------");
        //---------------------------------------------------
        u_enum_1 = IDLE;   //必须赋值框定的变量,否则出错,未赋值则打印不准
        $display("u_enum_1 = %0d (int)", u_enum_1);           //u_enum_1 = 0 (int)
        $display("u_enum_1 = %s (string)", u_enum_1);         //u_enum_1 = IDLE (string)
        $display("u_enum_1 = %s (string)", u_enum_1.name());  //u_enum_1 = IDLE (string)
                                                              //.name是显示转换,可以省略
        //---------------------------------------------------
        u_enum_2 = my_enum'(1); //用原始enum进行赋值,不能直接写1
        $display("u_enum_1 = %0d (int)", u_enum_2);           //u_enum_1 = 1 (int)
        $display("u_enum_1 = %s (string)", u_enum_2);         //u_enum_1 = START (string)
        //---------------------------------------------------
        u_enum_2 = my_enum'(4); //4超过了原始enum默认框定范围,打印是错的
                                 //可以改成{IDLE=1,START=2,PROC=3,END=4}来修改对应值
        $display("u_enum_1 = %0d (int)", u_enum_2);           //u_enum_1 = 4 (int)
        $display("u_enum_1 = %s (string)", u_enum_2);         //u_enum_1 = 4 (string)
    end
    
    
    
    endmodule

    6、string字符串

    SystemVerilog中的 string 类型可以用来保存长度可变的字符串,$sformatf( ) $psprintf( ) 用于创建临时字符串,常常用于字符拼接。

    //**************************************************************************
    // *** 名称 : SV_05_string.v
    // *** 描述 : string字符串的使用
    //**************************************************************************
    module SV_05_string;
    
    initial begin: string_format
        string s1, s2, s3, s4, s5, s6;//定义字符串
        int i; //定义int
        $display("--------< test start >--------");
        //--------------------------------------------------- 赋值字符串
        s1 = "Welcome";
        s2 = "https://www.cnblogs.com/xianyufpga/";
        //s1 content: Welcome
        $display("s1 content: %s", s1);
        //s2 content: https://www.cnblogs.com/xianyufpga/
        $display("s2 content: %s", s2);
        
        //--------------------------------------------------- 花括号拼接
        s3 = {s1, " to ", s2};
        //s3 content: Welcome to https://www.cnblogs.com/xianyufpga/
        $display("s3 content: %s", s3);
        
        //--------------------------------------------------- 函数法拼接:sformatf
        s4 = $sformatf("%s to %s", s1, s2);
        //s4 content: Welcome to https://www.cnblogs.com/xianyufpga/
        $display("s4 content: %s", s4); 
        
        //--------------------------------------------------- 函数法拼接:psprintf
        s5 = $psprintf("%s to %s", s1, s2);
        //s5 content: Welcome to https://www.cnblogs.com/xianyufpga/
        $display("s5 content: %s", s5);
        
        //--------------------------------------------------- int转string:itoa
        i = 2022;
        s6.itoa(i);
        //s6 content: 2022
        $display("s6 content: %s", s6);
    end
    
    
    endmodule

    参考资料:

    [1] 路科验证V2教程

    [2] 绿皮书:《SystemVerilog验证 测试平台编写指南》第2版

  • 相关阅读:
    python通过fake_useragent循环输出你需要的user-agent
    php来进行cc防护
    destoon7.0 蜘蛛访问统计插件
    Redis来限制用户某个时间段内访问的次数
    数据结构(十):复杂图-加权有向图,最短路径
    数据结构(十):复杂图-加权无向图,最小生成树
    数据结构(十):复杂图-有向图,拓扑图
    数据结构(十):图
    数据结构(九):并查集
    数据结构(八):优先队列-索引优先
  • 原文地址:https://www.cnblogs.com/xianyufpga/p/16413727.html
Copyright © 2020-2023  润新知