专栏首页根究FPGA读一本书 | systemverilog之内建数据类型(下)

读一本书 | systemverilog之内建数据类型(下)

数据类型

01

内建数据类型

Logic的引入背景

相比于verilog仍和net区分的如此清楚,在sv中新引入了一个数据类型logic,他们的区别和联系在于:

  1. verilog作为硬件描述语言,倾向于设计人员自身懂得所描述的电路中那些变量应该被视为寄存器,而那些变量被视为线网(wire),这不但有利于后端综合工具综合,也便于阅读和理解。
  2. sv(verilog3.0)作为侧重于验证的语言,并不十分关心logic对应的逻辑应该被综合为寄存器还是线网,因为logic被使用的环境是验证环境,logic只会作为单纯的变量进行赋值操作,而这些变量也只属于软件环境构建。
  3. logic被推出的另外一个原因也是为了方便验证人员驱动和连接硬件模块,而省去考虑究竟该使用reg还是wire的精力、节省时间的同时也避免了出错的可能。

bit

与logic对应的是bit类型,他们均可建立矢量类型(vector),区别在于:

  1. logic为四值逻辑,0、1、x(不确定)、z(高阻态,不受驱动)
  2. bit为二值逻辑,只可为0、1

要点

  1. sv为何在四值逻辑的基础上还要引入二值逻辑? 因为sv在一开始被设计的时候,就期望将硬件的世界与软件的世界分离开,硬件的世界指的就是硬件设计,所以四值逻辑属于硬件逻辑,而软件的世界即验证环境,更多的是二值逻辑。
  2. 常见的四值逻辑与二值逻辑 四值逻辑: integer、logic、reg、net-type(例如wire、tri) 二值逻辑: bit、byte、shortint、int、longint (注意区分interger和int、integer表示32位的四值逻辑,名字越长表示四值逻辑,而且integer的初始化值为:x)
  3. 常见的有符号和无符号类型 有符号: byte、shortint、int、longint、integer 无符号: bit、logic、reg、net-type

exp

logic [7:0]  logic_vec=8’b1000_0000;
logic [7:0]  logic_vec=8'b1000_0000;
bit  [7:0]  bit_vec=8'b1000_0000;
byte signed_vec=8'b1000_0000;
initial begin
$display("logic_vec=%d",logic_vec);
$display(  "bit_vec=%d",bit_vec);
$display("signed_vec=%d",signed_vec);
$stop();
end

猜一下运行结果是多少?

在遇到这些变量类型时,应注意他们的逻辑类型和符号类型,因为在变量运算中,应该尽量避免两种不一致的变量的进行操作,进而导致意外的错误!

exp

byte signed_vec=8'b1000_0000;  //有符号的8位数
bit [8:0]result_vec;            //无符号的9位数
  
initial begin   
result_vec = signed_vec;  
 #10;  
$display("@1 result_vec= 'h%x",result_vec);  
 #20;  
result_vec=unsigned'(signed_vec);    //转为无符号数
$display("@2 result_vec= 'h%x",result_vec);  
$stop();  
end  

运行结果为:

有符号数signed_vec转为无符号数,首先是用自己的符号位拓宽一位,变为:9’b1_1000_0000。

在编码时,一定要注意操作符左右两侧的符号类型保持一致,如果不一致,首先将其转换为同一类型再进行运算。

对于转换方式,分为静态转换和动态转换,静态转换即在需要转换的表达式前加上单引号“`”即可,仅在编译时完成检查,该方式并不会对转换值做检查,如果转换失败也无从得知。动态转换$cast(tgt,src)在仿真转换过程中检查。

静态转换和动态转换均需要操作符号或系统函数介入,统称为显式转换(expilicit transfer)。

而不需要进行转换的一些操作,称为隐式转换(implicit transfer)。

exp

logic [3:0] x_vec=4'b111x;  //四值逻辑
bit  [2:0] b_vec;          //二值逻辑
 //隐式转换
 initial begin
$display("@1 x_vec='b%b",x_vec);
b_vec=x_vec;
 $display("@2 b_vec='b%b",b_vec);
$stop;
 end

运行结果:

首先“111x”会截掉高位,x四值逻辑会转化为二值逻辑,关键在于x,x变为0!

在不同数据类型进行操作时要注意变量的:

  1. 逻辑数值类型
  2. 符号类型
  3. 矢量位宽

02

数组部分

数组声明

int lo_hi[0:15]; //16个int类型变量,数组中有16个元素,从int [0]--->[15]

int c_style[16];

多维数组声明和使用

int array2[0:7][0:3]; //完整声明

int array3[8][4]; //紧凑声明,8:高维度,4:低维度

array2[7][3]=1; //设置最后一个元素为1

初始化和赋值

int ascend[4] = `{0,1,2,3};

//注意单引号的使用,对4个元素初始化,默认是从低到高,如果是非合并类型的(数据不是紧挨着存储的),那么就需要用一个“`”来赋值,队列类型数据是连续存放的,无需使用“`”。

int descend[5];
descend=`{4,3,2,1,0};   //为5个元素赋值
descend[0:2]=`{5,6,7};   //为前3个元素赋值
ascend=`{4{8}};            //4个值全为8
descend=`{9,8,default:-1011}; //{9,8,-1011,-1010,-1011}

存储空间变量

下面的两个变量,都可以表示24bit的数据容量,那么以硬件的角度出发,哪种方式的所需存储空间更小?

  1. bit [3][7:0] b_pack; //合并型,3x8类型矢量,3:高纬度,8:低维度
  2. bit [7:0] b_unpack[3]; //非合并型,同上

合并型: 所有的维度都写到变量的左边

非合并型:数组的任何一个维度写到变量的右边,所有空间连续的存储,更加节省空间。

b_unpack实际会占据3个WORD的存储空间,但是b_pack则只会占据一个WORD的存储空间。

exp

如果用logic类型来存储上面的数据,即24bit,且分别声明变量为:

logic [3][7:0] l_pack;
logic [7:0]   l_unpack[3];

那么他们占据的实际存储空间为:______WORD,______WORD。

解析:

“logic [3][7:0] l_pack;”为合并型变量,存储空间连续,表面上看是占据:3*8个bit,实际上每个variable存在0、1两种可能情况,所以需要48bit(>32bit),也就是2WORD。

“logic [7:0] l_unpack[3];”为非合并型变量,存储空间不连续,但是每一组需要8*2=16bit<32bit(1WORD),所以需要3WORD实际存储空间。

03

基本数组操作

for和foreach

exp

initial begin
bit [31:0] src[5],dst[5];  //定义两个非合并型5*32数组,行数为5,列数为32
for(int i=0;i<$size(src);i++)   //获取src行数(5),
src[i]=i;
foreach(dst[j])   //对于foreach而言,默认创建j
dst[j]=src[j]*2;
end

复制和比较

对于复制,可以使用复制符号“=”直接进行数组的复制;

对于比较,在不适用循环的情况下,也可以使用“==”或者“!=”来比较数组的内容,不过结果仅限于内容相同或不相同的情形。

exp

bit[31:0]src[5]=`{0,1,2,3,4};  //5行32列的非合并型存储空间
bit [31:0]dst[5]=`{5,4,3,2,1};
if(src==dst)
$display(“src==dst”);
else
$display(“src!=dst”);
src[0]=5;   //修改src数组中的第一个元素,src[0实际上指向的是数组的首地址,与第一个元素相同。

动态数组

定宽数组类型:数组宽度在编译时就已确定,如果在程序运行时再确定数组的宽度就需要使用动态数组。

动态数组:最大特点就是在仿真时灵活调节数组的大小,即存储量。

动态数组在一开始声明时,需要使用“[]”来声明,而数组此时是空的,empty,其后需要使用“new[x]”来分配空间,x表示分配的存储空间列数(宽度)。

此外,也可以在调用“new[]”时将数组名也一并传递,将已有数组的值复制到新数组中。

exp

int d1[],d2[];  //声明动态数组
initial begin
        d1=new[5];  //分配5个int类型元素
        foreach(d1[j]) 
d1[j]=j;  //对d1[5]进行初始化
d2=d1;             //复制一个数组
d2[0]=5;          //修改复制值
$display(“d1[0],d2[0]”);  //显示0,5
        d1=new[20](d1);  //分配20个数并进行复制
/*attention:复制的结果是d1的最低的五个元素为0,1,2,3,4,其余为0*/
        d1=new[100];  //重新分配100个数值,旧值不在存在。
        d1.delete();              //删除所有元素,释放空间
/*也可以用d1=new[0]或d1=`{},注意new[0]中必须有0*/
end

exp

下面的例子中,d1初始为{1,2,3}的物理存储空间是否还在?

int d1=`{1,2,3};  //非合并类型变量
int cp[]=d1;
d1=new[3];

解析:不在,因为是把d1的内容,而new[3]重新分配,释放了之前的地址空间。

04

队列

【队列】:结合了链表和数组的优点,可以在任何地方添加或删除元素,并通过索引实现对任意元素的访问。

队列的声明格式为:name[],表示队列元素的标号从0到

队列不需要new[]去创建空间,因为new[]只作用于动态数组,而使用队列的方法为增减元素,一开始其空间为0。

队列的一个简单使用是通过其自带的push_back()和pop_front()的结合来实现类fifo function。

push_back():从后面给fifo写入一个数据;

pop_front():从前面拿出一个数据。

exp

`timescale 1ns/1ns
module veri();
int j=1;
int v1[$]={3,4};
int v2[$]={0,2,5};  // v1[$]声明列表并初始化,the same to v2[$]
//队列赋值不需要使用“`”,说明是合并类型储存方式
initial begin
#20;
v2.insert(1,j);  //在地址1前存储单元插入j,{0,1,2,5}
foreach(v2[i])
$display("v2[%d] is %d",i,v2[i]);
#10;
v2.insert(3,v1);  //在v2的地址3前插入列表v1,{0,1,2,3,4,5}
foreach(v2[i])  $display("v2[%d] is %d",i,v2[i]);
#10; 
v2.delete(1);       //1为address,相当于指针,删除地址1的元素,{0,2,3,4,5}
  
foreach(v2[i])  $display("v2[%d] is %d",i,v2[i]);
/*在没有1时,即默认情况,删除v2中所有元素*/
$stop();
end
endmodule

运行结果为:

exp

下列操作运行速度更快(通过自有方法)

v2.push_front(6);  //在队列头部插入6,à{6,0,2,3,4,5}
luck=v2.pop_back();  //从队列尾部取出给luck

两个EXP运行结果为:

05

关联数组

如果只是偶尔需要创建一个大容量的数组,那么动态数组就足够了,但如果需要一个超大容量的呢?动态数组的限制在于其存储空间一开始就被固定下来,对于稀疏类型的超大容量的数组,该方式存在着浪费,因为很有可能该大容量的数组中有相当多的数据不会被存储和访问。

关联数组:可以用来保存稀疏矩阵的元素,当对一个非常大的地址空间寻址时,该数组只为实际写入的元素分配空间(潜台词就是没有用到的地址就不会有存储空间映射),这种实现方式比定宽数组或动态数组所占用的空间资源要小得多。

在其他软件中,如martlab中典型的哈希结构(Hash)or词典结构(Dictionary),可以灵活而赋予键值(key)和数值(value)。

exp

bit [63:0] assoc[int] ,idx=1;
/*int规定了索引的数值类型*/
/*整体声明了一个关联数组,里面存储的数据是一个64位无符号类型数据,int表示索引他的是一个int type*/
repeat(64) begin
        assoc[idx]=idx;
        idx=idx<<1;
end
foreach(assoc[i])   //使用foreach遍历数组
        $display(“assoc[%h] = %h”,i,assoc[i]);
//使用函数遍历数组
if(assoc.first(idx)) begin    //得到第一个索引
        do
                 $display(“assoc[%h] is %h”,idx,assoc[idx]);
        while(assoc.next(idx));           //get next index
end
//找到并删除第一个元素
assoc.first(idx);                //删去标号地址对应的元素
assoc.delete(idx);

06

结构体struct

verilog最大的缺陷之一是没有数据结构,在sv中可以使用struct语句创建结构体,与c语言类似。

不过struct的功能较少,只是一个数据的集合,其常用方式是将若干相关的变量组合到一个struct结构定义中。

伴随着typedef可用来创建新的类型,并利用新类型来声明更多的变量。

exp

struct { bit  [7:0] r , g , b; }   pixel;       //rgb三个变量是分开存放的,非合并型
/***创建了一个pixel结构体***/
//为了共享该类型,通过typedef创建新类型
typedef struct { bit [7:0] r,g,b; } pixel_s;
pixel_s my_pixel;    //声明变量
my_pixel= `{‘h10,’h10,’h10};  //结构体类型的赋值

何时用单引号“`”?

如果是非合并类型的(数据不是紧邻存储),在赋值时需要使用“`”来赋值,队列是连续存放,赋值时无需使用“`”。

07

枚举类型

规范的操作代码和指令(例如ADD、WRITE、MULTI)等有利于代码的编写和维护,它比直接使用’h10这样的常量使用起来具有更好的可读性和可维护性。

枚举类型enum常和typedef搭配使用,便于用户自定义的枚举类型的共享使用,同时枚举类型的出现保证了一些非期望值的出现,降低了设计风险。

exp

typedef enum{INIT,DECODE,IDLE} fsmstate_e;   //e表示枚举类型
fsmstate_e psate,nstate;  //声明自定义类型变量
 
initial begin
        case(psate)
                 IDLE:      nstate=INIT;                //数据赋值
INIT:       nstate=DECODE;
                 default:    nstate=IDLE;
$display(“Next state is %s”,nstate.name());  //显示状态名
end

需要注意:枚举值缺省为从0开始递增的整数,也可以自定义枚举值,如:

typedef enum {INIT,DECODE=2,IDLE} fsmstate_e; //0----2----3

08

字符串

所有与字符串相关的处理,都是用string来保存和处理,与字符串处理相关的还包括字符串的格式化函数,即如何形成一个想要的句子?可以使用sv系统方法sformat(),也可以使用$display()。

exp

string s;   //字串符定义,输出化为NULL,而非“\0”
initial begin
        s=”IEEE ”;  //5个char:I-E-E-E-E-_
        $display(s.getc(0));   //显示地址0对应的char,即I
        $display(s.tolower());   //显示小写ieee
s_putc(s.len()-1,”-”);       //将空格替换为“-”
s={s,”P1800”};               //IEEE-P1800拼接
$display(s.substr(2,5));  //显示EE-P
//创建临时字符串
my_log($psprintf(“%s %5d”,s,42));  //生成要打印的字符串
end
task my_log( string message );
//将信息打印到日志中
$display(“@%05:%s”,$time,meesage);
endtask

09

运行环境搭建(以队列为例)

运行环境为VCS,编写Makefile脚本以便于处理,需要注意在编译选项中一定要打开打开“-sverilog”选项,分享一下自己的Makefile脚本:

.PHONY: com sim run_verdi clean
OUTPUT = veri
export demo_name=$(OUTPUT)
#coverage command
CM            = -cm line+fsm+cond+branch+tgl
CM_NAME = -cm_name $(OUTPUT)
CM_DIR  = -cm_dir ./$(OUTPUT).vdb
#vpd file name
VPD_NAME=+vpdfile+$(OUTPUT).vpd
#compile command
VCS= -vcs +v2k -sverilog -debug_all -timescale=1ns/1ns                \
 ${CM} ${CM_DIR} ${CM_NAME}                         \
       -LDFLAGS                                     \
     -rdynamic                                      \
  -P  ${Verdi_HOME}/share/PLI/VCS/LINUX64/novas.tab                \
               ${Verdi_HOME}/share/PLI/VCS/LINUX64/pli.a               \
      +vcs+lic+wait                                 \
      -full64                                                       \
    +notimingcheck                                  \
   +nospecify                                       \
  +vcs+flush+all                                    \
  -o $(OUTPUT)                                      \
  -l compile.log                                    \
  -f file_list.


SIM=./$(OUTPUT)                                     \
 +autoflush                                         \
  -l  $(OUTPUT).log                                 \
 -gui &
  
com:
${VCS}
sim:
${SIM}
run_verdi:
verdi -f file_list.f -nologo -ssf $(OUTPUT).fsdb &
cov:
dve -covdir $(OUTPUT).vdb &
clean:
rm -rf {OUTPUT} ucli.key inter.*  div.* csrc DVEfiles compile.log ${OUTPUT} $(OUTPUT).vdb $(OUTPUT).daidir

本文分享自微信公众号 - 根究FPGA(gh_08b5d93f8fa5),作者:叫什么好呢啊

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-09-29

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 读一本书 | systemverilog之内建数据类型

    相比于verilog将仍和net区分的如此清楚,在sv中新引入了一个数据类型logic,他们的区别和联系在于:

    根究FPGA
  • SystemVerilog教程之数据类型1

      我们知道,Verilog中,有两种基本的数据类型:reg和wire,reg在always、initial、task和funciton中被赋值,wire使用a...

    猫叔Rex
  • svlib文档翻译(第一至四章)

    本文版权所有©Verilab Inc. 2014-2015。本文作为svlib使用库的附加信息。本文件允许无限复制,但必须包括第一章的全部内容,不得修改。

    空白的贝塔
  • UVM

    UVM模型 ? 《UVM实战》主要介绍UVM的使用。全书详尽介绍了UVM的factory机制、sequence机制、phase机制、objection机制及寄存...

    瓜大三哥
  • C#核编之内建数据类型

    这个随笔中的重点之一是说明:C#中所提供的关键字都是相应系统类型的简化符号(如int是System.Int32类型的简化符号) 一、内建数据类型与层级结构 所有...

    郑小超.
  • SystemVerilog语言简介

    SystemVerilog是一种硬件描述和验证语言(HDVL),它基于IEEE1364-2001 Verilog硬件描述语言(HDL),并对其进行了扩展,包括扩...

    FPGA开源工作室
  • 验证仿真提速系列--SystemVerilog编码层面提速的若干策略

    随着设计复杂度和规模增加,验证平台复杂度跟着增加。验证平台的仿真速度问题成为验证过程中一个重要问题。

    IC验证
  • 使用SystemVerilog简化FPGA中的接口

    FPGA工程师们应该都会吐槽Verilog的语法,相当的不友好,尤其是对于有很多接口的模块,像AXI4/AXI-Lite这种常用的总线接口,动不动就好几十根线,...

    猫叔Rex
  • 【日更计划086】数字IC基础题【HDL部分】

    reg和wire是Verilog中就存在的两种数据类型,而logic是SystemVerilog中引入的新数据类型。

    空白的贝塔
  • SystemVerilog和UVM到底是啥关系?

    UVM提供了丰富的基类库和验证方法学,并且被主流的EDA工具、IP供应商和设计公司采用。现在,使用SystemVerilog基本上等同于使用UVM验证。

    AsicWonder
  • 【日更计划103】数字IC基础题【SV部分】

    DPI是直接编程接口的缩写,它是SystemVerilog和C/C++等外语编程语言之间的接口。DPI允许在接口两边的语言之间直接进行跨语言函数调用。在C语言中...

    空白的贝塔
  • 【UVM COOKBOOK】DUT-Testbench Connections

    UVM testbench对象不能直接连接到DUT信号来驱动或采样。driver和monitor组件对象与DUT之间的连接是通过一个或多个具有静态信号端口的BF...

    空白的贝塔
  • 【日更计划088】数字IC基础题【SV部分】

    struct表示不同数据类型的集合。例如:在下面的示例中,我们定义了一个名为instruction_s的struct,该struct由24位地址和8位操作码构成...

    空白的贝塔
  • svlib文档翻译(第五章)

    SystemVerilog语言本身提供了许多字符串操作。然而,经验表明,内置方法不足以满足工作中的字符串处理任务,svlib提供了进一步的操作集来帮助满足这些需...

    空白的贝塔
  • 【日更计划094】数字IC基础题【SV部分】

    function中不能使用任何延时语句。上面的例子中,function调用了一个耗时的task,这是非法的。

    空白的贝塔
  • 【UVM COOKBOOK】Testbench Architecture【一】

    UVM testbench 是使用SystemVerilog(动态)类对象与SystemVerilog(静态)接口和结构化层次结构中的模块交互构建的。层次结构...

    空白的贝塔
  • 两态数据类型

    Verilog有两种基本数据类型:变量(reg)和网线(wire),这是四态的数据类型(0、1、X、Z)。

    数字芯片社区
  • Linux | “搭建verilog学习环境”

    写这一篇文章的初衷一个是分享给各位想进入IC领域的读者以及感谢一位贵人。VerilogHDL是国内目前最流行的硬件描述语言。关于硬件描述语言的问题,这里并不多谈...

    LogicPanda
  • SystemVerilog中的callback(回调)

    在第二次systemverilog实验中,我看到有同学用到了callback函数,今天就是简单讲讲这个方法。

    数字IC小站

扫码关注云+社区

领取腾讯云代金券