Verilog数据类型
Verilog语言提供两种基本的数据类型,即变量类型(variables)和线网类型(nets),这两种类型都是四值逻辑,即可表示0、1、X和Z值。
- reg,integer和time等变量类型可以用来存储组合逻辑或者时序逻辑的值。
- wire,wor,wand和其它线网类型用来连接硬件模块,以及提供特殊的线网逻辑解决方案。
线网类型(nets)
线网类型用于建模结构化描述中的连接线路和总线线网的值由线网的驱动器决定,驱动器可以是门或模块的实例或者连续赋值的输出。
变量类型(variables)
变量类型保存在initial、always、task和function内赋的值。变量类型只能用过程赋值来赋值。类型是integer 或time的变量—般像有相同数量位数的reg一样运转。在表达式中integer的值作为有符号数处理,而reg或time的值作为无符号数处理。用reg描述逻辑,integer描述循环变量和计算,real在系统模型中使用,time和realtime保存测试装置的仿真时间。
数据类型
除了对Verilog的数据类型进行支持外,SystemVerilog又新添加了部分数据类型使得SV更加灵活方便。
但是不同的是,Verilog并没有严格区分信号的类型,变量和线网类型均是四值逻辑。SystemVerilog将硬件信号区分为 “类型” 和 “数据类型” 。
类型即表示该信号为变量(variables)或者线网类型(nets)。
对于线网类型赋值只能使用连续赋值语句(assign)。对于变量类型赋值可以使用连续赋值、或者过程赋值,这里是与Verilog不相同的一点。
数据类型则表示该数据是四值逻辑( logic)还是二值逻辑(bit) 。
内建数据类型
SystemVerilog中的内建数据类型主要有两类,按照四值逻辑的类型和二值逻辑的类型划分可进行以下分类:
- 四值逻辑类型: integer、logic、reg、net-type (例如wire.tri)。
- 二值逻辑类型: byte、shortint、 int、 longint、bit。
如果按照有符号和无符号的类型进行划分,那么可以将常见的变量类型划分为:
- 有符号类型: byte、shortint、int、longint、 integer。
- 无符号类型: bit、logic、reg.net-type(例如wire、tri)。
logic类型(四值逻辑类型)
SystenVerilog对经典的reg数据类型进行了改进,使得它除了作为一个变量以外,还可以被连续赋值、门单元和模块所驱动。为了与寄存器类型相区别,这种改进的数据类型被称为logic。任何使用线网的地方均可以使用logic,但要求logic不能有多个结构性的驱动,例如在对双向总线建模的时候。此时,需要使用线网类型,例如 wire,SystemVerilog 会对多个数据来源进行解析以后确定最终值。
由于logic类型只能有一个驱动,所以你可以使用它来查找网单中的漏洞。把你所有的信号都声明为logic而不是reg或wire,如果存在多个驱动,那么编译时就会出现错误。当然,有些信号你本来就希望它有多个驱动,例如双向总线,这些信号就需要被定义成线网类型,例如 wire。
在SystemVerilog中,我们可以直接使用logic(数据类型)来达到通用的存储硬件数据的目的:
logic resetN; ll a 1-bit wide 4-state variable logic [63:0]data;ll a 64-bit wide variable logic [0: 7] array [0:255]; ll an array of 8-bit variables
logic虽然只是表示数据类型,而在声明时,它默认会表示变量类型(variable) ,用户也可以显示声明其类型:
var logic [ 63:0]addr; ll a 64-bit wide variable wire logic [63:0]data ; ll a 64-bit wide net
二值逻辑数据类型
SystemVerilog添加了二值逻辑数据类型,用来对在RTL更高抽象级的模型建模,例如系统级或者事务级模型,这些二值逻辑数据类型包括:
bit : 1位二值逻辑变量 byte :8位二值逻辑,类似于C语言char类型 shortint : 16位二值逻辑,类似于C语言short类型 int : 32位二值逻辑,类似于C语言int类型 longint : 64位二值逻辑,类似于C语言longlong类型
在RTL级别,X值用来捕捉设计错误,例如寄存器未初始化,Z值用来表示未连接或者三态的设计逻辑。但是在系统级或者事务级,Z和X很少被使用。logic类型默认为变量类型,bit类型默认也为变量类型。
logic或者bit构成的向量(vector)是无符号类型。integer、byte、shortint、int、longint为有符号类型。可以在有符号类型后添加unsigned来表示无符号类型,例如:byte有符号类型,表示的数值范围是[-128,127]。byte unsigned表示无符号类型,等同于bit[ 7:0],表示的数值范围是[0,255]。
在构建验证环境总线功能模型(BFM,Bus-Functional Model)时,无需关注硬件底层逻辑(X或者Z值),所以可使用二值逻辑实现。同样,SystemVerilog在与C发生语言交互时,也可以使用二值逻辑来使得两种语言的边界数据传输更为简单。
数据类型的仿真行为
四值逻辑变量例如reg.logic或者integer等,在仿真开始时的初值为X。二值逻辑变量例如bit等,在仿真开始时的初值为0。
如果四值逻辑与二值逻辑的数据类型之间发生的默认转换,那么Z和X值将转换为0,如下图所示:
二值逻辑也可以用来实现可综合电路,只是二值逻辑由于没有×和Z值,因此可能会出现仿真行为同综合电路结果不一致的情况。
其他的数据类型
SystemVerilog添加void类型来表示空类型,经常用在函数定义时表示不会返回数值,同C语言的void使用方法。
SystemVerilog添加shortreal表示32位单精度浮点类型,同C语言的float;而Verilog的real类型表示双精度浮点类型,同C语言的double。
自定义类型
Verilog语言无法扩展变量和线网类型。SystemVerilog则提供了特性使得用户可以构建更高抽象层的数据类型。就同C语言一样,用户可以利用已有的数据类型来定义新的数据类型,一旦定义了新的数据类型,则此类型的变量则可以声明。
typedef int unsigned uint; ... uint a,b; ll two variables of type uint
为了使代码更易读和维护,通常对于自定义的类型,都习惯添加”_t"的后缀用来表示它是一个自定义类型(type) 。
枚举类型
枚举类型(enum)提供方法来描述抽象变量的合法值范围,其每一个值都需要提供一个用户自定义的名字。Verilog语言自身不提供枚举类型,为了提供类似于枚举类型可实现的便利,采用parameter常量来表示可取的值范围,或者使用`define来定义各个合法值对应的宏名称。
`define H_FRONT 11'd16 `define H_SYNC 11'd96 `define H_BACK 11'd48 `define H_DISP 11'd640 `define H_TOTAL 11'd800
默认的枚举类型是int,即32位的二值逻辑数据类型。为了能够更准确地描述硬件,SystemVerilog 允许指明枚举类型的数据类型,例如:
enun bit {TRUE,FALSE) Boolean; enun logic [1:0] { WAITE,LOAD,READY } state;
如果一个枚举类型数值被赋值,那么所赋的值必须符合其数据类型。
enum logic [2:0]{ WAITE- 3b001, LOAD - 3'b010, READY - 3'b100 } state ;
如果枚举类型是四值逻辑数据类型,那么对枚举值赋为X或者Z也是合法的。
enun logic {ON=1'b1,OFF=1'bz } out;
枚举类型也可以声明为自定义类型,这就使得可以用同一个枚举类型来声明多个变量或者线网。
typedef enum {WAITE,LOAD,READY} states_t; states_t state, next_state ;
如果枚举类型并没有伴随着typedef,那么该枚举类型指的则是一个匿名枚举类型。多数的Verilog或者SystemVerilog 数据类型之间可以通过不同类型的赋值完成"隐式"的数据转换。同时需要注意的是,对于枚举类型的赋值,赋值操作的左右两侧应该尽量为相同的枚举类型。
结构体类型
你可以把若干变量组合到一个结构中。例如创建了一个名为pixel的结构,它有三个无符号的字节变量,分别代表红,绿和蓝。
struct (bit [7:o]r,g,b: } pixel;
设计或者验证的数据经常会有逻辑相关的数据信号组,例如一个总线协议的所有控制信号,或者在一个状态控制器中用到的所有的信号。Verilog语言没有方便的特性可以将相关的信号收集整理到一个信号组中。SystemVerilog 添加了同C一样的结构体struct,而结构体的成员可以是任何变量类型,包括自定义类型或者其它常量类型。
正是由于结构体是变量的合集,因此结构体类型的变量也可以用来索引到其内部的变量,索引方式同C语言一致:结构体类型默认也是变量类型,用户也可以显示声明其为var或者wire类型。
结构体变量可以通过索引其各个成员做依次的成员赋值,也可以通过分号和花括号{}来实现整体赋初值。
Iw.b = 5 ; Iw.opcode = 8' hFE; or Iw= '{100,3,8' hFF,0} ; Iw = ' { address:0, opcode : 8' hFF, a:100,b:5} ;
字符串类型
Verilog语言对于字符串的处理手段非常有限。SystemVerilog 引入了string类型用来容纳可变长度的字符串。字符串类型变量的存储单元为byte类型。字符串类型变量长度为N时,其字符成员索引值为从O到N-1。不同于C函数,**字符串结尾没有"空字符"即null字符"\0"。**字符串的内存是动态分配的,用户无需担心内存空间管理。
处理字符串的常用函数
- 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} ** :将字符串转变为十进制、十六进制、八进制或者二进制数据。
str = "123"; int i = str .atoi() ; ll assigns 123 to i .
数组
SystemVerilog 提供了更加多样的数组类型,功能上也大大增强。
定宽数组的声明和初始化
Verilog 要求在声明中必须给出数组的上下界。因为几乎所有数组都使用0作为索引下界,所以SysternVerilog 允许只给出数组宽度的便捷声
明方式,跟C语言类似。
int demo1[0:15]; int demo2[16];
可以通过在变量名后面指定维度的方式来创建多维定宽数组。
int array2 [o:7][o:3];//完整的声明 int array3 [8][4];//紧凑的声明 array2[7][3]=1;/设置最后一个元素
如果你的代码试图从一个越界的地址中读取数据,那么SystemVerilog将返回数组元素类型的缺省值。对于一个元素为四状态类型的数组,例如 logic,返回的是x,而对于双状态类型例如 int或bit,则返回0。这适用于所有数组类型,包括定宽数组、动态数组﹑关联数组和队列,也同时适用于地址中含有X或Z的情况。线网在没有驱动的时候输出是Z。
常量数组
一个单引号加大括号来初始化数组。你可以一次性地为数组的部分或所有元素赋值。在大括号前标上重复次数可以对多个元素重复赋值,还可以为那些没有显式赋值的元素指定一个缺省值。
int A [4]='{0,1,2,3);//对4个元素进行初始化 int B[5]; B = '{4,3,2,1,0};//为5个元素赋值
合并数组
对某些数据类型,你可能希望既可以把它作为一个整体来访间,也可以把它分解成更小的单元。例如,有一个32比特的寄存器,有时候希望把它看成四个8比特的数据,有时候则希望把它看成单个的无符号数据。SystemVerilog 的合并数组就可以实现这个功能,它既可以用作数组,也可以当成单独的数据。与非合并数组不同的是,它的存放方式是连续的比特集合,中间没有任何闲置的空间。
声明合并数组时,合并的位和数组大小作为数据类型的一部分必须在变量名前面指定。数组大小定义的格式必须是[msb:lsb],而不是[size]。
下例中的变量bytes是一个有4个字节的合并数组,使用单独的32比特的字来存放:
合并和非合并数组可以混合使用。你可能会使用数组来表示存储单元,这些单元可以按比特、字节或长字的方式进行存取。
合并数组和非合并数组的选择应该根据需要进行决定,当你需要和标量进行相互转换时,使用合并数组会非常方便。例如,你可能需要以字节或字为单位对存储单元进行操作。如果你需要等待数组中的变化,则必须使用合并数组。
动态数组
SystemVerilog 提供了动态数组类型,可以在仿真时分配空间或调整宽度,这样在仿真中就可以使用最小的存储量。动态数组在声明时使用空的下标[]。这意味着数组的宽度不在编译时给出,而在程序运行时再指定。
数组在最开始时是空的,所以你必须调用new[]操作符来分配空间,同时在方括号中传递数组宽度。可以把数组名传给new[]构造符,并把已有数组的值复制到新数组里。
int dyn[],d2[]; //声明动态数组 initial begin dyn=new[5];//分配5个元素 foreach (dyn[j]) dyn[j]=j;//对元素进行初始化 d2= dyn; //复制一个动态数组 d2[o]=5; //修改复制值 end+
队列
SystemVerilog引进了一种新的数据类型—队列,它结合了链表和数组的优点。队列与链表相似,可以在一个队列中的任何地方增加或删除元素,这类操作在性能上的损失比动态数组小得多,因为动态数组需要分配新的数组并复制所有元素的值。队列与数组相似,可以通过索引实现对任元素的访问,而不需要像链表那样去遍历目标元素之前的所有元素。
队列的声明是使用带有美元符号的下标:[] 。 队 列 元 素 的 编 号 从 0 到 ]。队列元素的编号从0到]。队列元素的编号从0到。例2.19示范了如何使用方法( method)在队列中增加和删除元素。注意队列的常量(literal)只有大括号而没有数组常量中开头的单引号。
SystemVerilog 的队列类似于标准模板库中的双端队列。你通过增加元素来创建队列,SystemVerilog 会分配额外的空间以便你能够快速插入新元素。当元素增加到超过原有空间的容量时,SystemVerilog 会自动分配更多的空间。其结果是,你可以扩大或缩小队列,但不用像动态数组那样在性能上付出很大代价。SystemVerilog会随时记录闲置的空间。注意不要对队列使用构造函数new[]。
int j=1, q2[$]= {3,4}, //队列的常量不需要使用“''” q[$]={0,2,5};// {0,2,5}
队列中的元素是连续存放的,所以在队列的前面后面存取数据非常方便。无论队列有多大,操作所耗费的时间都是一样的。可以把定宽或动态数组的值复制给队列。
References
- 路科验证西电课程PPT
- 《SystemVerilog验证-测试平台编写指南》