数据类型
1.内建数据类型
逻辑数值类型(四值或二值),四值逻辑中的x或z转换为二值逻辑时自动变为0;
符号:signed和unsigned,bit,logic,reg,net-type无符号,注意无符号数据与有符号数据间的转换;有符号数据赋给高一位的无符号数据时,符号位宽展一位,无符号数据赋给高一位的无符号数据时加一个0.
2.枚举类型
枚举类型可以直接转化为整形,但整形不可以隐式的转换给枚举类型,可以采用'()形式,另外可以采用$cast( , )进行检查。
枚举类型缺省状态下的枚举值:缺省状态从0开始递增(没有特别指明的情况下,枚举类型会被当成int类型存储),可以自己定义但对第一个值的定义最好为0;默认枚举类型是int,也就是32为二值逻辑
3.字符串
str.len()/ getc(x) /toupper()/tolower()/putc( , )/substr( , );atoi()/atohex() / atooct()/atobin()字符串转换为整数(返回一个32bit整数),对于atoi, 字符串将被看作十进制;对于atohex,十六进制;对于atooct,八进制;对于atob,二进制;itoa()整形转字符串,整形是十进制的。
字符串类型变量的存储单元是
byte
型
,
空字符串表示为
s3={s1.len()+s2.len(){“”}}
字符串的拼接与输出:{s1,s2},需要注意的是,字符串如果一开始不创建空间,采用拼接的方式会默认创建空间,但是如果不采用拼接时,需要一开始开辟空间
$sformat(“%s,to,%s”,s1,s2)或者$psprintf()
4.结构体
默认非组合型结构体,采用{};加packed变为组合型结构体采用'{}和{}均可;结构体变量索引,采用”.”;结构体默认变量类型是var,但是也可将其声明为wire,将两个输出变成一个结构体,与interface对比。
非组合结构体和组合结构体在赋值方面的差异和组合型数组和非组合型数组相同,并且非组合结构体占据的空间比组合结构体占据的跟多。如果要做随机化的化,两种类型的变量均要声明为rand,并且针对非组合结构体的成员变量可以单独声明为rand(单独随机化成员变量),而对于组合型结构体来说,不能单独声明其成员变量为rand,只能整体随机化
5.typedef
自定义结构体和枚举类型,添加后缀_t表示自定义结构类型。在enum/struct类型声明,如果不加typedef的化,enum{A}B,那么{A}是“匿名类型”,B是枚举变量;添加了typedef的化就变为B就通过typedef变为枚举类型,可在后面进行复用。
6.数组的一些操作
6.1组合型数组与非组合型数组
维度的概念:非组合:wire[7:0] table[3:0]; int a[7:0][3:0];组合:logic[3:0][7:0] data,数组维度声明在变量左侧是组合型数组,在右侧的是非组合型数组(仅当两个限定都在中间时是组合,两个限定在一块时左边是高维度,两个限定不在一起时右边是高纬度,对于混合型数组,比如byte[3:0][1:0]arry3[5:0][7:0]表示的是6*8*4*2的四维数组)
赋值:
对于组合型数组来说可以把不同维度、不同元素数量的数组间可以进行直接赋值(位宽补全或者截取),但是对于非组合型数组来说必须必须要求维度和元素相同,否则的化只能对逐一元素进行赋值。
非组合型数组可以采用'{}进行赋值,但是需要注意这种赋值一个'{}代表对一个维度赋值,多维时需要多个'{},比如'{‘h11,’h22,’h33,’h44}就不能赋给bit word [3:0][7:0]而要采用'{0: ‘{‘h11},1: ‘{‘h22},'{‘h33},'{‘h44}};可以采用foreach对组合型数组和非组合型数组之间的转化,两个数组即使维度不一样也可以转化,缺少的添0,多的截去,但是不可以直接将两个数组用等号转化
UVM
:多维数组不支持域的自动化
6.2数组的复制与比较
==和!=结果仅仅是内容相同或者不同,=可以用来对数组赋值。在verilog中两个操作数之间存在”==”和“===”,而在过程语句块中(initial,always)中对变量赋值存在阻塞赋值(=,串行依次并行同时,立马赋值)和非阻塞赋值(“<=”,串行并行同时执行,等待延迟结束后赋值)
6.3遍历数组
for和foreach,对于foreach什么时候用begin..end;对于二位数组的遍历来说,foreach(sum[i,j]) begin…end就等价于foreach (sum[m]) begin foreach(sum[m][n]) begin… end end
foreach轮询数组的顺序:对于队列或者动态数组,循环时是从index索引是从开始;对于定长数组,取决于数组声明的索引顺序,比如arr[3:1]表示索引值是从3到1,对于关联数组根据索引值的类型从小到大
6.4数组运算、定位、排序、随机化
缩进:数组名.sum/product/and/or/xor在对数组进行运算过程中需要注意数组中元素的位宽。
定位:数组名.unique(),max(),min(),注意返回的是一个int类型的队列而不是一个标量; 数组名.find_(frist/last_index) with(item>3); d.sum with (item>7)其中item>7返回的是1或者0
排序:数组名.reverse(),sort(),rsort(),shuffle(),注意reverse()和shuffle()作用范围是整个数组,因此不能加条件语句with,剩下两个可以加(shuffle对数组元素进行随机排序)。
随机化:下面的几种数组均可以使用
$urandom_rang($size(arry)-1)
,从数组中随机选择一个元素;对于队列和动态数组可以采用
$urandom_rang(arry.size()-1)
使得随机化的数组在指定范围内平均分布
;$random
与
$urandom
之间的差别在于前者返回的是有符号的随机数后者返回的无符号的随机数
;
对于数组来说,采用
$urandom()
需要对数组的每一个元素逐一随机化,但是采用
std::randomize()
可以对数组整体进行随机化。
7.关联数组
索引类型可以为任何数据类型,对索引值的大小和顺序都不做要求;存放数据不连续,索引时即使地址不存在该数据也不会报错,建议用if语句,遍历可以采用foreach和数名.frist(idx);frist还可以替换成delete,可删除任何一个元素;注意idx用法,比如对于foreach遍历关联数组时,最终显示时idx的排序对于整形(关联数组定义为整形)来说是按1234的顺序,对于字符串(关联数组定义为字符串)来说是按abcd的顺序。举例:bit[63:0]assoc[bit[63:0]]
8.对列
不需要new开辟空间,赋值时不需要’,q[$]={0,2,5};队列名.push/pop_front/back(),队列名.insert(delete)();对列可以删除单独某个元素,但是动态数组只能全部删除,
清
空队列和
清
空动态数组的方式类似,采用q.delete()或者q={}
9.动态数组
dyn=new[20](dyn),假设动态数组一开始有五个值,分别是1-5,那么new[20]新创建了20个元素,并且前五个元素是之前的1-5后续元素均为0 ,new[dyn1.size()](dyn1);dyn.delete(),dyn=new[0],dyn='{}删除所有元素,null无法实现对动态数组的删除,null仅仅当与句柄(类)有关时实现;dyn='{1}也可以,动态数组是与非组合型数组类比;dyn='{1,2,3,4} 可以不单独开辟空间直接赋值,在赋值过程中开辟空间;
10.定宽数组
int ascend[4] = ‘{0,1,2,3}
关于数组和队列间的对比:
(1
)关联数组在声明时同队列一样无需声明大小,动态数组可以动态创建大小。
(2
)数组存储的时候,合并数组连续存储,非合并数组根据每个高纬度进行单独存储,采用logic
进行存储的时候需要两个bit
位进行存储,且这两个bit
是连续的(针对非合并数组种的低纬度)
11.数据间的转换
显示转换: 比如int'(4.0),将一个实数转化为整形,不会检查;$cast(a,b),将b类型转化为a类型;应用:子类的句柄可以直接赋值给父类,但是父类不可以直接赋给子类,需要通过$cast函数进行操作(也就是需要检查)
隐式转换:不需要转换符号或者系统函数,这种转换需要注意位宽和符号
12.拼接符{}
拼接过程中一定要注意位宽,比如’h11223344和{‘h11,’h22,’h33,’h44}的赋值是不一样的,程序会对后面的拼接认为每一个位宽均是32位;实际上就是组合型数组与非组合型数组的赋值,对于非组合型数组可以直接采用'{}进行操作,但是对于组合型数组使用{}需要注意位宽,如果不限定位宽会默认每个变量均是32位;注意对列的拼接与对列的取值之间的差异,一个用{},一个用[],详细见p30
关于{}
的用法总结:除了上述用法以外,还可以用于复制{4{w}}
;描述覆盖点和仓时;定义枚举类型和结构体时。
13. 系统函数
$dimensions(); $left( , ); $right( , ); $low( , ); $high( , ); $size( ; ); $increment( ; ); $bits() 除了第一个和最后一个系统函数意外,其他系统函数变量均为两个,左侧为数组名右侧为尺寸。比如:logic [1:2] [7:0] word [0:3] [4:1];$left(word,1); $finish直接退出仿真,$stop暂停仿真。
14. 过程块和方法
always/initial
always描述硬件,initial描述软件,两者均无法被延迟执行(仿真一开始就会执行),不同initial和always之间也没有顺序而言。
input/output/inout/ref
(ref
是引用,input output
会复制变量)
inout方向会在方法调用中完成入口处由外部变量到形式参数的拷贝,并且在方法退出的时候,由形式参数到外部变量的拷贝,一共两次拷贝;ref则是将外部变量本身传递进入,不在发生形式参数到外部变量的拷贝,ref可以理解为指针。如果对某个外部变量进行持续跟踪,那么应该使用ref方向,并且在task中跟踪。
对于ref:有如下应用:(1)如同C++中的句柄或者指针,是变量的入口地址:引用数组将其传递到子程序;子程序修改ref参数变量时,其变化对于外部是立即可见的,比如在总线上进行相关的赋值操作时,如果使用output修饰,对参数的赋值往往需要等待总线上这一次的任务完成后才可返回,而使用ref的化,其参数的变化是立刻的,常常与fork-join搭配p55;在任务中修改句柄 p123,想修改参数的值的时候,需要在参数前面加ref,如果不加ref的化,此参数默认方向是input,在方法内部对该方法的调用不会被调用该方法的代码看到,可能会出现错误;通过引用来进行数组参数的传递(函数声明时将其声明为数组类型在数组较大的时候往往会造成性能上的问题,p59);
线程的通讯中将
mailbox
替换成队列,对队列操作时同样需要用
ref
限定(
2
)采用
const ref
,在向子程序传递数组时,限定子程序无法修改数组值
(3)需要注意线网类型无法使用ref修饰
function/task
(1)function非耗时,task内置耗时语句(@event,wait event,#),task可以调用function和task,function不建议调用task(仅仅能在fork…join none语句成的线程中调用);function在声明的时候需要指定返回值,void+return,在内置阻塞语句的情况下,task不能用return返回结果,返回数值只能依靠参数列表中的参数;
*
需要注意,
return
可以在
task
中使用,只是不建议这样做,在其中使用会立刻退出该
task
(包括
void function
,使用
return
也会立刻返回,如果
function
没有
void
,那么在退出的同时还会返回数值。
另外
return
一般也是用于发现错误时提前返回
。)
(2)二者均可以在module/package/interface/program中定义
(3)function参数类型默认logic+input
(4)阻塞语句一般在task中(阻塞方法耗时),非阻塞语句task和function中都可以使用
automatic/static
(1)在module,program,interface外部定义默认静态变量;module,program,interface内部定义默认+其中function+task外部的情况下任然是静态变量;module,program,interface内部定义的function+task默认是静态方法。class内部定义默认动态。
(2)在静态或动态的大范围内可以定义相反的小范围。
(3)静态变量和方法的生命周期是从仿真加载到结束和都存在,而对于动态变量和方法来说,其生命周期是随着对象的创建而存在的,对象一旦销毁,其成员空间也释放。
(4)建议对module和interface中方法声明时默认添加automatic,这是由于这些静态方法中的变量默认时静态的
,
在调用他们的时候会遇到静态变量内存共享而出现不同的线程在同样时间中调用相同方法引起结果干扰的问题
。
(5)在声明program时建议直接对program使用automatic,否则的化否则在其中定义变量的时候默认是静态的,他从仿真一开始就存在,在初始化和多次调用中会出现问题(多次调用的时候可能会出现覆盖的问题)。
module/interface/program/package
对于program来说,其相当于软件的部分,其中不因该出现与硬件相关的过程语句和实例,例如always,module,interface等,此外其中也不应该出现其他program例化的语句。program内部在定义变量赋值时采用阻塞赋值语句,在调用外部硬件信号时应该使用非阻塞赋值语句。其结束有两种情况,一种是隐式,等到所有initial语句全部执行完后(program中的各个initial并行执行),程序会结束。另一种是显式结束,调用$exit。这种情况出现在program中某个initial存在forever语句,也就是该initial语句无法结束,在这种情况下程序会一直允许,所以需要在这个initial之外的另一个initial A中加入$exit语句,使得A强制结束。这样的化,等到A执行完后program就结束了。如果在program中需要起到类似于always的作用,可以通过initial forever来代替。