书接上回:
sv标准研读第三章-设计和验证的building block
第6章 数据类型
6.1概览
- Sv logic值和强度的设置
- Sv的net变量声明
- Sv单一变量的声明
- Sv的常量
- Sv数据的scope和lifetime
- Sv的数据兼容性
- Sv的类型操作符和类型转换
6.2 数据类型和数据对象
数据类型:声明数据结构
数据对象:具有数据值和关联的数据类型
6.3值的设置
6.3.1 logic值
Sv支持4种值的设置:
- 0:代表逻辑0或者false
- 1:代表逻辑1或者true
- X:代表unknow的逻辑值
- Z:代表高阻状态
0/1逻辑互补。注意,在gate输入或者expression中,z的作用效果和x一样,但是如果是MOS电路,它可以传递z值。
Logic数据类型可以用于声明4态的数据类型。
Sv中的数据类型可以划分为4种:4态数据类型、2态数据类型、event数据类型(没有存储空间)、real数据类型。
6.3.2 强度
具体见28章。强度可以用来修饰net类型的数据,并且强度不被解析为数据类型的一部分。
强度也分为两种:charge strength(仅用在trireg类型的数据)和drive strength(仅用在连续赋值语句中或者gate声明中,具体见第28章)。
6.3.2.1 charge strength
仅用于trireg net数据上。强度有3种:
small、medium、large
默认强度是medium。
举例:
6.3.2.2 drive strength
具体见第10章和第28章。用于net的在连续赋值语句中。
6.4 单一和聚合类型
数据类型分为单一类型和聚合类型。
聚合类型:unpacked 结构体、unpacked union、unpacked array
单一类型:剩余所有。
6.5 nets和variables
数据对象有两种:nets和variables。两种区别:
net:可以被1个或者多个连续赋值/primitive output/ports驱动。多驱的结果是由net类型的解析函数决定的,但是net不能在过程语句中赋值。在port,如果一端是net,另一端是var来驱动,那么隐含了连续赋值。force语句可以override net值,release之后,net值回到解析值。
var可以被1个或者多个过程语句驱动,包括过程连续赋值,var的值最终由最后一次驱动决定。var可以被1次连续赋值或者1个port驱动。对于结构体和数组,每一个元素可以认为是独立的,因此,允许对某部分元素进行过程赋值,而对另外的元素进行连续赋值,但是同一个元素不能既被过程赋值,又被连续赋值,也不能连续赋值多次。
var初始化或者过程连续赋值都被认为是过程赋值。force语句会override过程赋值语句,但是force语句既不是连续赋值也不是过程赋值。
var作为port的input声明时,会被解析为连续赋值,这样的话就会造成对input port var的赋值是非法的;当var连接到port实例的output时,会被解析为连续赋值,这样的话也会造成对该var的额外过程赋值或者连续赋值是非法的。
var不能连接到inout port。var加上ref port类型就可以被share到很多ports,具体见23.3.3节。
举例:
Abc这个结构体的如下赋值是合法的:
上面abc里的成员C被连续赋值,成员A的每bit作为port的output声明,成员B的每bit是作为port的input声明,同时还被非阻塞赋值。但是下面的操作就是非法的:
因为成员C不能被多次连续赋值,成员A的bit[3]不能既被连续赋值(port声明)又同时被过程赋值。
如果给连续赋值加上除了st0/st1/stx/stz之外的强度,那么编译器应该报warning。automatic类型转换适用于任何赋值语句中,且强度会丢失。
和net不同的是,var在声明的时候不能包含任何隐式的连续赋值,var在声明时的赋值会被解析为初始化,而不是连续赋值。例如:
数据在使用前一定要先声明,隐式net除外(见6.10节)。
在同一个命名空间内,不允许重复声明同一个名字(这个名字是net/var或者其他)。
6.6 net类型
net类型有两种:内建和用户自定义的。net类型不存储值(除了trireg),它的值是由驱动它的值决定的,比如连续赋值或者gate赋值。如果net没有连接,那么它的值是z(除了trireg)。
内建net类型有:
6.6.1 wire/tri nets
wire:可以被单个gate驱动,可以被连续赋值;
tri:可以被多个driver驱动。
当多个强度相同的驱动发生逻辑冲突时会产生x。
如果wire/tri被多驱,且每个驱动源的强度相同,那么真值表如下:
6.6.2 unresolved nets
有:uwire
只能被一个driver驱动。多驱会报错。
6.6.3 wired nets
有:wor/wand/trior/triand。允许多驱。
wor/trior:任何一个驱动为1,结果为1;
wand/triand:任何一个驱动为0,结果为0.
当多驱的每一个驱动源强度相同时,他们的真值表如下:
6.6.4 trireg net
trireg可以存储值,并且它有两种状态:
driven state:多个驱动源中只要有一个驱动源的值是0/1/x,那么就会传播到trireg中。driven state trireg的强度有:supply/strong/pull/weak
capacitive state:当多个驱动源都为z时,那么trireg会保持之前的驱动值,即z态不会传播到trireg中。capacitive state trireg的强度有:small/medium/large。
举例:
分析:在仿真0时刻,wire a=1,wire b=1,假设与门输出为1,那么wire a连接了与门输出和wire c,因此wire c=strong 1,wireb=1连接了wire c和trireg d,因此trireg d=strong1。
在仿真10时刻,wire a变成了0,断开了与门输出和wire c,那么wire c变成了高阻态z,但是z并不会传播到trireg d,因此trireg d还是保持之前的驱动值1,但是强度变成了medium。
6.6.4.1 capacitive networks
capacitive networks:2个及以上trireg的连接,且trireg处于capacitive state。
举例:
分析:上面的图中,trireg_la表示强度是large,trireg_me1/trireg_me2表示强度是medium,trireg_sm表示强度是small。
在仿真0时刻,wire a/b/c/d的值都是1,此时所有nmos管都是连通的,所以trireg_la/sm/me1/me2都为1;
在仿真10时刻,wire b变成了0,那么trainf1_2和1_1都断开,那么trireg_sm和trireg_me2断开了与外界的连接,因此这俩进入capacitive状态,保持之前的驱动值1。
在仿真20时刻,wire c变成了0,那么trireg_la就会被驱动成0.
在仿真30时刻,wire d变成了0,那么trireg_me1就会被驱动成0.
在仿真40时刻,wire a变成了0,那么trireg_la和trireg_me1就断开了与外界的连接,进入capacitive状态,保持之前的驱动值0.
在仿真50时刻,wire b变成了1,连接了trireg_sm和trireg_me2,但是因为trireg_la=0,强度比trireg_sm大,所以trireg_sm也变成了0;因为trireg_me1和trireg_me2强度相等,但是1个值为0,1个值为1,根据表6-2的规律,最终二者都会变成x。
举例2:
分析:上图中trireg_la表示强度是large,trireg_sm表示强度是small
在仿真0时刻,wire a=strong 1,wire b/c=1,此时trireg_la/trireg_sm都是strong 1;
在仿真10时刻,wire b变成了0,断开了trireg_la与wire a的连接,但是trireg_la和trireg_sm还是保持连接,此时它们的强度会将1等级,变成large 1;
在仿真20时刻,wire c变成了0,断开了trireg_la和trireg_sm的连接,此时trireg_la不会再和trireg_sm分享large 1了,而是自己保持large 1,而trireg_sm变成了small 1.
在仿真30时刻,wire c变成了1,此时trireg_la和trireg_sm依旧共享large1;
在仿真40时刻,wire c变成了0,trireg_sm又回到small 1.
以上现象总结:在电容性network里,电荷强度会从更larger的trireg net传递给small的trireg net。
6.6.4.2 ideal capacitive state and charge decay
Trireg net可以永远保持它的值,也可以随着时间的推移而衰减,具体内容参见28.16.2的内容。
6.6.5 tri0/tri1 nets
tri0:电阻下拉到0,没有驱动时,值为0,强度为pull;有驱动时,强度和值要结合来看;
tri1:电阻上拉到1,没有驱动时,值为1,强度为pull;有驱动时,强度和值要结合来看。
假设驱动源的强度都是strong,下面是真值表:
注意:上述结果的强度都是strong,只有当两个驱动都是z时,强度值是pull。
6.6.6 supply nets
supply0/supply1的强度都是supply。
6.6.7用户自定义nettypes
nettype:类似于typedef,但是只能用在net。
在用nettype定义自定义数据类型时,data type必须给出,且data type有一定的限制,必须是下面其中的一种:
(1)4态整型,包括:packed array/packed structure/packed union
(2)2态整型,包括成员是2态数据类型的packed array/packed structure/packed union
(3)real/shortreal
(4)成员是有效net或者nettype数据类型的固定大小的unpacked array/unpacked structure/unpacked union
Atomic net:值的更新和解析会作为一个整体进行。
用户自定义的nettype就是一个Atomic net;
一个logic net也是一个Atomic net;
但是一个logic vector net就不是Atomic net,因为每个成员都是独立的。
Atomic net的值可能是singular的,也可能是aggregate的,但是在设计中只允许连接到单个点。
Nettype的解析:当该net的驱动值发生变化时,就会在active region或者reactive region发起一个update event。Nettype允许有多个驱动,任何一个驱动值发生了变化,都会执行一遍解析过程。因此如果有多个驱动值都发生了变化,那么可能会存在不确定性。
两个不同的nettype可以有相同的data type。
Nettype的值可以被force override掉,一旦release,就会回到解析值。
举例:
举例2:使用带参数的class声明nettype
6.6.8 generic interconnect
Sv允许构建generic nets。
当net/port声明为interconnect类型时,就表明它是一个typeless或者generic net。这种net/port只能用于port/terminal连接,而不能用于过程语句或者任何连续赋值以及过程连续赋值语句中。也不能用于表达式中,除非表达式的左边也是interconnect net类型。在一个interconnect数组中,允许每bit被解析成不同的net type。
举例:
举例2:下面的例子展示了interconnect的妙用:
6.7 net声明
6.7.1内建net类型的声明
本小节只介绍不使用自定义nettype且只声明的net类型,如果想了解内建net类型的赋值,参考第10章。
声明可以包含以下信息:delay值、drive/change强度、数据类型
任意4态的数据类型都可以声明成net,举例:
如果在声明时没有指定数据类型,默认是logic类型,举例:
如果需要把net声明成interconnect,那么需要满足以下几个条件:
- 不能有data type,但是可以是packed/unpacked
- 不能指定drive/charge强度
- 不能有赋值语句
- 最多只能指定1个delay值。
Net声明的几个条件:
- 可以是4态整型类型,包间packed 数组和packed结构体
- 可以是固定size的unpacked数组或者unpacked 结构体,其每一个成员都必须是有效的net数据类型。
在net/port声明时不允许带有reg关键字,下面的写法会报错:
Net类型的默认初始值是z,但是trireg net是个例外,它的默认初始值是x。
举例:
6.7.2自定义nettype的声明
自定义的nettype允许实现任意的解析函数。
举例:
6.7.3 自定义nettype类型的初始化
自定义nettype类型的解析函数在0时刻至少要激活一次。
自定义nettype类型的初始值会在任意initial/always块开始前赋好。初始值取决于data type,参考表6-7。
6.8 var声明
声明语法:
声明方式1:data_type inst;举例:
声明方式2:var [data_type]inst;(没有写data_type时,默认是logic)举例:
var声明时可以赋初始值,举例:
静态var在声明时赋初始值要先于任何initial/always块。
初始值不仅仅可以是常量,还可以包含run-time表达式,包括动态内存分配,比如通过new方法给类句柄赋初始值,通过$urandom系统函数给static var赋随机值。
如果没有赋初始值,那么var的默认值如下:
net/var都可以赋值为负值,byte/shortint/int/integer/longint默认都是有符号数,其他type也可以通过显式声明变成signed。
6.9vector declarations
reg/logic/bit默认是unsigned(除非声明为signed,或者连接到一个signed的port),默认是1bit位宽,被称为scalar
超过1bit位宽的称为vector,vector可以理解为是scalar的packed array.
6.9.1 specifying vectors
对vector取特定的范围vector[msb, lsb]
msb/lsb:必须是整数;可以是正数、负数、0;不能是x/z;lsb可以大于/等于/小于msb。
vector的最大长度是65536.
举例:
6.9.2 vector net accessibility
声明时可以加上vectored/scalared关键字。
加上vectored关键字后,就不能进行bit-select/part-select/指定strength
加上scalared关键字后,能进行bit-select/part-select/指定strength
6.10 implicit declaration
6.7/6.8是做显式声明。还有以下几种隐式声明:
(1)port表达式里的标识符就是一种net隐式声明,具体见23.2.2.1小节
(2)原语上午终端列表、module/interface/program/static checker里的port连接列表里的标识符是一种net隐式声明
(3)连续赋值语句左侧的标识符在该scope下前面并没有声明过,是一种net隐式声明。
6.11 integer data types
shortint | 2态 | 16bit | signed |
int | 2态 | 32bit | signed |
longint | 2态 | 64bit | signed |
byte | 2态 | 8bit | signed |
bit | 2态 | 1bit | unsigned |
logic | 4态 | 1bit | unsigned |
reg | 4态 | 1bit | unsigned |
integer | 4态 | 32bit | signed |
time | 4态 | 64bit | unsigned |
6.11.1 integer类型
整数类型可以用在:单一的整数类型/packed array(见7.4章)/packed structure(见7.2章)/packed union/enum/time。
6.11.2 2态和4态数据类型
4态数据类型包括:logic/reg/interger/time。
2态数据占存储更少,仿真更快。
无符号数位扩展,高位补0;有符号数位扩展,高位补符号位;位缩短,高位截断;4态转2态,x/z变成0.
6.11.3 有符号和无符号整型
byte/shortint/int/longint/integer默认都是有符号数;
bit/reg/logic/time默认都是无符号数。
可以在声明时加上unsigned/signed关键字显示声明为无符号/有符号数。
举例:
6.12 real/shortreal/realtime
real:双精度
shortreal:单精度
realtime类似于real
6.12.1 操作数和real数字
对real数字或者操作数使用logical/relational操作符计算的结果是单bit的值,下面几种情形不能使用real数:
- 想要用real变量实现边沿触发的事件;
- 想要对real变量做bit-sel/part-sel;
- 想要对vector做bit-sel/part-sel时索引用real。
6.12.2 转化
实数可以转化为整数。规则是四舍五入到最近的整数,而不是截断。如果是0.5,会朝着远离0的方向舍入。
当给一个real赋值时,会隐式的发生转化过程,x/z会变成0.
6.13 void数据类型
void关键字声明的数据类型表示这是一个不存在的数据,可以用在function的return中,表示没有返回值。也可以用在tagged union中。
6.14 chandle 数据类型
chandle数据类型一般表示使用DPI传递的指针的存储。具体见第35章。
默认初始值是null。只能用在下面几种场景:
- 操作符:只有【chandle】==/!=/===/!==【c handle/null】
- 用作布尔值:chandle=null时布尔值为0,其余为1
- 赋值:【chandle】=【chandle/null】
- 可以用在关联数组里,但是顺序在不同工具下不同。
- 可以用在class里
- 可以用在参数传递中
- 可以作为function的返回值
chandle不能用在:
- port
- 不能赋给其他类型的变量
- 不能用在敏感列表里或者事件表达式中
- 不能连续赋值
- 不能用在untagged union
- 不能用在packed类型
6.15 class
class变量可以有class对象的句柄。
6.16 string数据类型
string变量的单个字符的数据类型是byte。
string literal不等于string variable;前者的长度是8bit的整数倍,赋值为整数变量时可以补0或者截断,后者长度不限,且不会发生截断。当给string变量赋值一个string literal时,string literal会隐式转化为string类型;当表达式里包含string 类型的操作数时,string literal也会隐式转化为string类型。
string的索引从0开始;空字符用“”表示,索引空字符串变量是越界访问。
string变量不能包含“\0”这个特殊字符,遇到这个字符会被忽略。
string声明格式如下:
string的默认初始值是“”,它的长度是0.
string literal既可以赋值给一个string类型的变量,也可以赋值给一个整型变量。当赋值给一个整型变量时,如果整型变量的位宽小于string literal字符长度*8,那么string literal的左边会被截断,如果前者的位宽大于后者,那么左边会填充0.
整型变量也可以赋值给一个string变量,但是需要cast,cast后string变量可能会发生grow/shrink来满足整数值的需求,当整数值不是8的整数倍时,左边会填充0.
举例:
当把string literal赋值给一个string 变量时,会发生以下几步:
- 忽略“\0”
- 如果经过第一步之后string literal是空的,那么会给string变量赋一个空字符串
- 否则,会把剩下的字符赋值给string变量
将一个整型值赋值给一个string变量有以下几个步骤:
- 首先看整型值是不是8的整数倍,不是的话就会在左边填充0.
- 接下来整型值就和string literal的处理方式一样。
举例:
举例2:
可以用于string变量的操作符有:
- str1==str2:【两个操作数可以都是string类型的变量;也可以是一个是string变量,一个是string literal,此时会发生convert;如果两个操作数都是string literal,那么久等同于处理两个整型数据。】
- str1!=str2:和==类似
- str1</<=/>/>=str2:字典序比较;【和==类似】
- {str1,str2,…,strn}:strx可以是string类型的变量,也可以是string literal;都是string literal时,会当做整型值一样处理;有string类型的变量时,string literal会隐式转化为string类型。
- {multiplier{str}}:multiplier只要是整型的表达式就可以,不一定非要是整型值;str可以是string类型的变量,也可以是string literal。当str是string类型的变量时,那么就会复制N份str;当str是string literal且multiplier是常量时,那么就会做数字的复制,最终的结果会转化为string类型。
- str[idx]:返回一个byte,即字符的ASCII码。当idx超出str的长度,返回0.
- str.method(…):调用str的方法
具体方法见6.16.1-6.16.15
6.16.1 len()
作用:返回字符串的长度,如果是“”空字符串,返回0
6.16.2 putc()
将第i个字符替换为c
如果i超出了str的字符串范围,那么就不会对str造成变化;如果c是0,也不会对str造成变化。
6.16.3 getc()
返回str的第i个字符
如果i索引值超出了str的索引违反,那么返回0.
6.16.4 toupper()
返回字符串的大写,原来的字符串并不会变
6.16.5 tolower()
返回字符串的小写,原来的字符串并不会变
6.16.6 compare()
str.compare(s)
字典序比较str和s
6.16.7 icompare
和compare唯一的区别是:不区分大小写
6.16.8 substr()
返回str的第i到第j个字符
如果i<0,j<I,j>str.len(),返回空字符串
6.16.9 atoi()/atohex()/atooct()/atobin()
atoi():将数字字符串转化为十进制整数,数字字符串里可以写下划线_,如果没有数字字符串,那么返回0,但是如果数字字符串加上了:符号/size/’/base,它不会解析;
atohex():和atoi()的区别是转化成十六进制数;
atooct():和atoi()的区别是转化成八进制数;
atobin():和atoi()的区别是转化成二进制数;
注意:如果字符串数字大于32bit,那么会截断,且不会报warning
6.16.10 atoreal()
atoreal():将数字字符串转化为real,如果没有数字字符串,那么返回0。
6.16.11 itoa()
将十进制数字转化成字符串,不会返回,而是存储
6.16.12 hextoa()
将十六进制数字转化成字符串,不会返回,而是存储
6.16.13 octtoa()
将八进制数字转化成字符串,不会返回,而是存储
6.16.14 bintoa()
将二进制数字转化成字符串,不会返回,而是存储
6.16.15 realtoa()
将real数字转化成字符串,不会返回,而是存储
6.17 event数据类型
event用来做两个及以上线程的同步。事件的触发可以通过9.4.2的控制语句来控制。
event变量可以被赋值,也可以被赋值为null(此时,多个线程之间的关联被破坏),也可以和另一个event变量进行比较;如果在声明时没有赋初始值,那么它将会被初始化为new sync object。具体见15.5节
举例:
6.18自定义数据类型
举例:
typedef的作用实际上是将已经存在的数据类型换了个名字。
如果复杂的数据类型要做cast,那么可以使用自定义数据类型的名字,自定义数据类型代表的数据类型只能是unpacked array类型。
上面这个图片实际上就是声明一个变量intP来代表int这个类型,且它必须出现在使用intP声明数据前,intP被称之为type_identifier。
不能对type_identifier做hierachical的引用。但是有一种情况例外,如果在interface里定义了一个自定义数据类型,然后另一个scope通过port传入了这个interface,在这个scope里去引用这个interface里的自定义数据类型是不会被看做是hierachical引用的,这样的操作是允许的,并且允许在这个scope里重新去redefine这个自定义数据类型,这样的typedef被称之为interface based typedef。
自定义数据类型可以用来定义enum/struct/union/interface class/class,在用自定义数据类型去声明内容时,一定要先声明这个自定义数据类型。即:
最后一行说明了在声明自定义数据类型时,可以不写具体所代表的数据类型,只要它前面已经写了就可以了。
6.19 enum类型
枚举类型实际是声明了一系列整型常量的名字。枚举数据类型的引用/display也可以通过使用枚举的名字而不是枚举值。
在枚举类型里可以不指定具体的数据类型,那么默认的数据类型是int,如果要声明成其他的数据类型,那么就需要显式的声明。
举例:
light1/2就是两个枚举变量,每个变量里面有3个成员,成员的数据类型是int。
如果显式声明了枚举成员的数据类型是2态的,但又给枚举成员赋值为x/z,那么就会报错,如下:
如果显式声明了枚举成员的数据类型是4态的,但又给枚举成员赋值为x/z,那么就不会报错;但是如果给某个成员赋值为x/z后,后面的成员又没有赋值,那么会报错:
如果不显式给枚举成员赋值,那么成员的值是从0开始依次递增1。
在给枚举成员赋值时,可以给部分成员赋值,而不给其他成员赋值。赋值时可以使用paramter/local parameter/genvar/其他enum常量/常量function,不可以使用hierachical名或者const变量。没有赋值的部分成员是前一个成员值递增1。如果对最大值递增那么会报错。
枚举的名字和整数值都必须是独一无二的。如果给同一个名字设置了两个值或者给两个不同的名字设置了同一个值,那么就会报错。举例:
如果枚举的第一个成员没有赋值,那么默认赋值为0。
当将一个整型值表达式cast到一个enum类型时,需要评估一下这个整型值表达式。如果评估后,枚举成员的值超出了枚举数据类型的范围,那么就会报错。如果枚举的数据类型是unsigned,那么在cast的过程中很可能会发生:截断并且截断的位是非0。如果枚举的数据类型是signed,那么在cast的过程中很可能会发生:截断并且截断的位不是符号位。如果这个整型值表达式是一个sized literal常量,那么如果这个size和enum的size不一致就会报错,即使整型值在enum size范围内。
6.19.1 定义一个新的枚举类型
可以结合typedef去自定义一个枚举类型。
6.19.2 枚举类型的范围
在声明枚举变量时,枚举成员的名字可以有以下几种形式:
举例:
上面定义的E1枚举里有9个成员,名字分别是:add/sub0/…/sub4/jump6/jump7/jump8,成员的值分别是:10/11/12/13/14/15/16/17/18。
上面定义的vr枚举里有5个成员,名字分别是:register0/register1/register2/register3/register4,成员的值分别是:1/2/10/11/12。
6.19.3 类型检查
枚举类型属于是强类型,因此在做类型转换时,有一定的要求。将枚举类型转换成其他类型时,这个类型只能也是枚举类型,否则就只能通过cast来强制转换了。
在表达式中使用枚举可以看做是常量,并且表达式的结果可以任意赋值给一个整型变量。
枚举变量在做赋值、参数传递、关系操作时都会进行类型检查。枚举类型可以自动转化为整型值,但是将表达式赋值给枚举变量时就必须要显式cast。
6.19.4 在数字表达式中的枚举类型
在数字表达式中使用的是枚举值。
在表达式中使用枚举名或者枚举变量时,会自动转化为枚举的基本数据类型。如果将非枚举类型赋值给枚举类型变量则必须使用cast。使用cast转化为enum类型时将不会考虑值的有效性。
6.19.5枚举类型的方法
6.19.5.1 first()
返回枚举变量第一个成员的值。
6.19.5.2 last()
返回枚举变量最后一个成员的值。
6.19.5.3 next()
返回第N个成员的下一个成员的值。若N是最后一个,返回第一个成员的值;若N不存在,则返回枚举类型的默认初始值。
6.19.5.4 prev()
返回第N个成员的上一个成员的值。若N是第一个,返回最后一个成员的值;若N不存在,则返回枚举类型的默认初始值。
6.19.5.5 num()
返回枚举变量成员的数量
6.19.5.6 name()
返回给定枚举值对应的名字。若值在枚举变量中不存在,返回空字符串。
6.19.5.7 使用枚举方法
6.20常数
常数不能被改变。sv提供了3种在ela-time时的常量:parameter/localparam/specparam,提供了1种在run-time时的常量:const。
3种parameter常量可以被初始化为literal:
sv提供了很多方法对parameter常量设置值。在声明的时候,可能被赋值为默认值。module/interface/program里的parameter可以被override,通过下面的方式:
- 按顺序依次对module/interface/program里的parameter赋值;
- 通过名字对module/interface/program里的parameter赋值;
- 在defparam语句块里,通过hierarchical路径名给module/interface/program里的parameter重新定义。
6.20.1 parameter声明
parameter可以出现在module/interface/program/class/package里,也可以出现在module/interface/program/class的端口上。当出现在端口上时,parameter和localparam的作用是一样的;当parameter出现在class里时,它的作用和localparam也是一样的。当parameter出现在generate/package/编译单元里时,就变成了localparam。
在端口上写参数时,可以省略关键字parameter,如下:
当写parameter list时,后面的parameter可以依赖前面的parameter。
在port声明的parameter是没有默认值的,因此实例化的时候就需要指定参数的值,如果没有指定,工具就不能隐式的例化;在class里也是如此。
6.20.2 value parameter
参数可以指定类型,也可以指定范围,但必须满足下面的规则:
- 当没有指定类型和范围时,默认该参数的类型和范围将设置为这个参数最后一次赋值的类型和范围。
- 如果指定了范围但是没有指定类型时,那么它是unsigned,参数的符号不受override值影响。
- 如果指定了类型但是没有指定范围,那么它的范围由最后一次赋值决定。
- 如果指定了范围,且是有符号的,那么它的符号和范围不会被override掉。
- 如果没有指定范围,但是是有符号类型或者没有指定类型,那么它的范围的msb=1。
- 如果没有指定范围,但是是有符号类型或者没有指定类型,那么它的范围的msb=31。
当参数显示声明了类型时,在赋值或者ovrd时右边表达式的类型必须和参数类型兼容。
real和整数之间的转换对于parameter同样适用。
整数类型的parameter允许进行bit选择和part选择。
parameter/localparam/specparam可以被赋值为:literal表达式、parameter、localparam、specparam、genvar、枚举名、常量function。允许引用package,不允许使用hierarchical 名。specparam可以被赋值为:包含多个specparam的表达式。
parameter也可以被声明为聚合类型,包括unpacked array/unpacked structure;一旦被声明为了聚合类型,在赋值或者override的时候就要以一个整体来赋值或者ovrd,是不能对单个成员进行赋值或者ovrd的,但是在表达式中是可以使用单个成员的。
6.20.2.1 $
可以对整型类型的parameter赋值为$,表示这是一个无界范围的整数。举例:
sv提供了一个系统函数来查看一个常量是不是$,这个系统函数是:
如果是的话,返回1,否则返回0。
6.20.3 type parameters
parameter也可以声明为一个类型,可以用在module/interface/program的port上,然后例化时里面的数据对象的类型就可以使用这个类型。
在进行赋值或者ovrd时,右边表达式也必须是一个数据类型。
type parameter可以引用package,但是不允许引用hierarchical names。
type parameter不能被ovrd为defparam。
6.20.4 local parameter
local parameter和parameter类似,区别是:local parameter不能被defparam语句或者parameter实例赋值语句修改。local parameter可以被赋值为常量表达式。
local parameter可以在generate/package/class/编译单元里声明,在这些block里,local parameter=parameter。
local parater也可以在module的参数端口列表里声明。在这个列表里,local parameter到下一个paramter之间的参数都是local paramter。
6.20.5 specify parameter
specify parameter声明的是一种特殊参数,用于提供timing和delay值,可以出现在任何表达式中,只要这个表达式没有赋值给一个parameter并且这个表达式也不是一个声明的part range。
specify parameter可以出现在specify block或者module里。
specify parameter可以被赋值为常量表达式。
specify parameter可以在常量表达式中使用。
specify parameter不可以被ovrd,但是可以被sdf修改。
specify parameter和parameter常量之间不可以互换。如果表达式中有了specify parameter,那么这个表达式就不能赋值给一个parameter或者local parameter了。
specify parameter和parameter两者的区别:
specify parameter可以指定范围,但是必须遵守以下规则:
- 当没有指定范围时,它的范围就是最后一次赋值时值的范围。
- 当指定了范围时,它的范围就是声明时的范围,不会被ovrd。
6.20.6 const常量
和parameter不同的是,paramter常量是在elaboration阶段被赋值,而const常量是在run-time被赋值。
带了const关键字的静态常量可以被赋值为literal/parameter/local parameter/genvar/enum name/常量函数等。可以使用hierachical结构。
带了const关键字的automatic常量可以被赋值为任何表达式,此时不带const关键字也可以。
class的例化句柄也可以在声明时带上const关键字。这标志着这个对象就不能被ovrd了,同时也要求new函数的参数必须是常量表达式。但是这个对象里的非const成员还是可以被ovrd的。
6.21 scope和lifetime
- 定义在module/program/interface/checker/task/function之外的变量都被认为是编译单元的local变量,并且拥有static生命周期,在整个simulation阶段都存在。
- 定义在module/program/interface/checker内部但是在task/function/process外面的变量是module/program/interface/checker的local变量,默认是static,有着静态生命周期。
定义在 static task/function/block里的变量是static task/function/block的local变量,默认里面的变量是static,具有静态生命周期。但是这些变量也可以被显式声明为automatic变量。这些变量的声明周期和task/function的调用周期一样,和block的周期一样。并且在调用task/function实体或者block实体时会被初始化。如果用户只想要执行1次初始化过程,那么就必须加上static关键字。举例:
- 定义在 automatic task/function/block里的变量生命周期和automatic task/function/block一样。默认里面的变量是automatic,但是这些变量也可以被显式声明为static变量。task/function/block默认也是automatic,里面的变量默认也是automatic。
- fork join块的生命周期是包含了所有进程的生命周期。
可以对static变量进行hierarchical引用,但是如果static 变量定义在了一个没有名字的block里,是不能引用了。
在动态sized array里的automatic变量不能被赋值为:非阻塞赋值/连续赋值/过程连续赋值。
非静态类属性不能被赋值为:连续赋值/过程连续赋值。
automatic变量只能在过程块里被引用。
6.22类型兼容
类型的兼容包含5个等级:matching、equivalent、assignment compatible、cast compatible、and nonequivalent.用户可以通过系统函数$typename来定义类型等级。
数据类型的作用域应该包括:hierarchical实例的作用域。
为了在同一个module/interface/program/checker的多个例化实体间类型matching/ equivalent,那么需要在更高level定义一个class/enum/unpacked structure/unpacked union类型。
6.22.1 matching类型
matching类型如下:
- built-in类型和它本身;
- 使用typedef或者type parameter时,定义的类型别名和类型本身;例如:
来自同一条声明语句声明的enum/struct/unition类型的多个对象;例如:
用typedef定义了一种数据类型,由这个自定义数据类型声明的多个对象,例如:
位宽/符号/状态完全相同的两种数据对象;例如:
typedef定义的数组:都是packed数组或者都是unpacked数组,包括:定长数组、动态数组、关联数组、队列等,这些数组的index类型、元素类型都是matching的。对于定长数组,msb/lsb必须一样。举例:
加上signed/unsigned关键字,但是并没有改变它原本的符号,例如:
在package里用typedef定义的enum/struct/union/class类型,总是和enum/struct/union/class本身match,不管这些类型通过import方式来自哪里。
6.22.2 equivalent types
需满足下面条件的其中一种:
- 只要match,一定equivalent.
- 没有名字的enum/unpacked struct/unpacked union类型和该类型本身是equivalent。例如:
packed array/packed structure/packed union/整型类型和含有bit数相等的类型是equivalent的,不管是不是2态还是4态,也不管是不是signed/unsigned,例如:
Unpacked定长数组和类型(该类型有着和它equivalent元素类型和相同的size)equivalent,例如:
动态数组、关联数组、队列和类型(该类型同样也是动态数组、关联数组、队列,且元素也是equivalent,如果都是关联数组,要求关联数组的index也必须是equivalent,)是equivalent。
6.22.3赋值兼容
所有的整型类型都是赋值兼容的,赋值兼容允许不同类型之间出现cast,但是cast可能会产生截断或者舍入情况。
enumerate类型可以不使用cast转化为整型。
6.22.4 cast兼容
整型类型可以通过cast转化为enumerate类型。
6.22.5类型非兼容
除了上面的几种,剩下的都是类型不兼容。例如类句柄、interface class句柄、chandle和其他类型都不兼容。
6.22.6 matching nettypes
nettype和它本身是match的;
用nettype定义的另外的名字,由这个名字定义的类型之间也是match的,例如:
6.23 类型操作符
在声明变量时,可以加上type关键字,但是必须放在var关键字或net类型关键字的后面,例如:
当type放在表达式里使用时,代表的是结果的类型,这个表达式不能求值,不能包含任何hierarchical引用,也不能包含任何动态对象元素的引用。
type也可以用于数据类型:
type用在相等、不等、case相等、case不等的比较语句中时,只能比较类型。只有是match的类型,才会认为是相等的。
6.24 cast
6.24.1 cast操作符
数据类型在做转换时,一定要带上’关键符号,表示cast。
举例:
举例2:如果expr_1和cast_t1是赋值兼容的,expr_2和cast_t2是赋值兼容的,那么下面的表达式:
等同于:
因此,temp1=expr_1实际上发生了一种隐式转换,等同于cast_t1’(expr_1)操作。
如果cast_t1是正整数常量表达式,那么expr_1就会截断或者填充到其对应的size,如果size为0,那么要报错。
举例:
还可以强制改变符号:
当改变size时,符号不能变,且size内的每一个元素的类型是:如果是2态,就是bit类型,如果是4态,就是logic类型;
Signed’(x)和$signed(x)作用效果一样;
Unsigned’(x)和$unsigned(x)作用效果一样。
举例:
Shortreal类型转换成int或者32 bit时,会有舍入情况发生,参见6.12节。因此可能发生信息丢失,如果不想丢失信息,可以使用$shortrealtobits,参见20.5节。
当unpacked 结构体转换为packed结构体时,结构体中的第一个field占据MSB。
举例:
Union的size是最大成员的size。
$itor, $rtoi, $bitstoreal, $realtobits, $signed, and $unsigned也用于类型转换。
6.24.2 $cast动态转换
$cast系统函数可以作为function或者task来使用,语法如下:
当做function还是task来使用的区别是:
如果当做task使用,当赋值无效时,会产生runtime error,并且dest_var保持不变;
如果当做function使用,当赋值有效时,返回1,赋值无效时,返回0,且dest_var保持不变,且不会产生runtime error。
注意:$cast不会进行类型检查,参见8.16小节。
举例:
作用是将5赋值给black。如果不用$cast,这种赋值是非法的。
上面的转换还可以写成:
但是上面这个写法是compile阶段的cast,所以在runtime过程中,总是会成功的cast,即使表达式的值超出了枚举的范围,也不会报错。
在使用这两种方式时,要千万小心。
可以利用$cast来检查赋值是否成功,举例:
6.24.3 bit stream cast
Bit stream类型包括以下几种:
- 任意整型、packed类型、string类型
- 上述类型的unpacked数组、结构体和class
- 上述类型的动态大小数组(包括动态数组、联合数组、队列)
举例:
当A是bit-stream type,B也是bit-stream type时,上述转换是合法的。
转换过程可以分为两个步骤:
- 首先将A转换为一个generic packed值,该值的bit位数量和A相等,如果A中包含4态的数据,那么整个generic packed值就是4态的,否则是2态。
- 将generic packed值转换为B,如果B中有部分bit是2态的,而generic packed值对应的bit是4态的,那么最终B的这些bit会转换为2态。
注意:动态数组、队列、字符串类型(字符串会看做动态数据类型)转换为packed类型时,索引0对应MSB;
当联合数组转换为packed类型时,会按照索引的顺序进行packed,且第一个索引占据MSB。但是联合数组和class不能作为转换的dest type。如果class handle里有local/protected成员,那么不能作为source type,除非这个class handle是this。
Source type和dest type都可以包含一个或者多个动态大小数据,例如一个结构体里包含了一个动态数组,后面跟着一个队列。当Source type包含动态大小数据时,那么会被全部包含在bit stream中;当dest type里包含无界动态大小类型时,那么转换过程是一个贪婪过程:首先会计算Source type的大小,然后减去dest type中固定大小数据的size,然后会把dest type里的第一个动态大小数据的size调整为剩余的size,其他的动态数据的size为空。
Packed过程都是按照从左到右的顺序。
如果Source type和dest type里每一个数据的type都是unpacked且Source type和dest type对应的size不同,那么会发生compile错误。
如果Source type和dest type里都包含动态数据,那么一旦大小不匹配,就会报错。
举例:
bit stream cast可以用于不同聚合类型之间的转换,例如两个结构体之间的转换,结构体与数组之间的转换,结构体与队列之间的转换。举例:
举例2:
6.25 参数化的数据类型
参数化的数据类型是通过实现参数化的类实现的。
举例: