数据类型
非组合结构体和组合结构体在赋值方面的差异和组合型数组和非组合型数组相同,并且非组合结构体占据的空间比组合结构体占据的跟多。如果要做随机化的化,两种类型的变量均要声明为rand,并且针对非组合结构体的成员变量可以单独声明为rand(单独随机化成员变量),而对于组合型结构体来说,不能单独声明其成员变量为rand,只能整体随机化
赋值:对于组合型数组来说可以把不同维度、不同元素数量的数组间可以进行直接赋值(位宽补全或者截取),但是对于非组合型数组来说必须必须要求维度和元素相同,否则的化只能对逐一元素进行赋值。
非组合型数组可以采用'{}进行赋值,但是需要注意这种赋值一个'{}代表对一个维度赋值,多维时需要多个'{},比如'{'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遍历数组
foreach轮询数组的顺序:对于队列或者动态数组,循环时是从index索引是从开始;对于定长数组,取决于数组声明的索引顺序,比如arr[3:1]表示索引值是从3到1,对于关联数组根据索引值的类型从小到大
6.4数组运算、定位、排序、随机化
索引类型可以为任何数据类型,对索引值的大小和顺序都不做要求;存放数据不连续,索引时即使地址不存在该数据也不会报错,建议用if语句,遍历可以采用foreach和数名.frist(idx);frist还可以替换成delete,可删除任何一个元素;注意idx用法,比如对于foreach遍历关联数组时,最终显示时idx的排序对于整形(关联数组定义为整形)来说是按1234的顺序,对于字符串(关联数组定义为字符串)来说是按abcd的顺序。举例:bit[63:0]assoc[bit[63:0]]
不需要new开辟空间,赋值时不需要',q[$]={0,2,5};队列名.push/pop_front/back(),队列名.insert(delete)();对列可以删除单独某个元素,但是动态数组只能全部删除,清空队列和清空动态数组的方式类似,采用q.delete()或者q={}
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} 可以不单独开辟空间直接赋值,在赋值过程中开辟空间;
关于数组和队列间的对比:
(1)关联数组在声明时同队列一样无需声明大小,动态数组可以动态创建大小。
(2)数组存储的时候,合并数组连续存储,非合并数组根据每个高纬度进行单独存储,采用logic进行存储的时候需要两个bit位进行存储,且这两个bit是连续的(针对非合并数组种的低纬度)
拼接过程中一定要注意位宽,比如'h11223344和{'h11,'h22,'h33,'h44}的赋值是不一样的,程序会对后面的拼接认为每一个位宽均是32位;实际上就是组合型数组与非组合型数组的赋值,对于非组合型数组可以直接采用'{}进行操作,但是对于组合型数组使用{}需要注意位宽,如果不限定位宽会默认每个变量均是32位;注意对列的拼接与对列的取值之间的差异,一个用{},一个用[],详细见p30
关于{}的用法总结:除了上述用法以外,还可以用于复制{4{w}};描述覆盖点和仓时;定义枚举类型和结构体时。
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来代替。