1、基础知识
1. 使用SystemVerilog语言有什么好处?
SystemVerilog语言提供了统一的硬件设计和硬件验证语言,有以下几个主要特点:
支持覆盖率、断言、面向对象编程和受约束的随机验证来搭建验证环境;
2. 设计元素(Design element)
设计元素在SystemVerilog中指module、program、interface、checker、package、primitive或configuration。这些概念分别用关键字: module, program, interface, checker, package, primitive, and config。设计元素是对设计电路和验证环境进行建模的主要模块,这些模块提供了一个容器,用于声明和执行特定的任务。
Module:主要用于代表设计模块,但也可以作为验证代码的容器和作为验证模块与设计模块交互的容器。
Program:主要用于对验证环境进行建模。Programs块主要有三个基本用处:1. 提供验证环境执行的入口点;2. 创建一个封装数据、task和function的作用范围;3. 用于指定代码在Reactive区域调度。Program把设计模块和验证环境分离开来,让它们在time slot的不同region里顺序执行,因此可以避免设计和验证的时序竞争情况。Program块可以包含data declarations、class definitions、subroutine definitions、object instances、one or more initial或final procedures。它不能包含always procedure、primitive instance、module instance、interface instance或other program instances。
Interface:Interface用于将设计模块和设计模块连接起来,也用于将设计模块和验证模块连接起来。它的功能挺强大的,除了module例化外,其它都可以在它内部使用,意味着它可以包含initial和always块、task、function、SVA、covergroups、classes、parameter、constant和其它interface。
Checker:checker是封装了断言和建模代码的验证块。Checker目的是用于验证库单元或作为创建形式化验证中使用的抽象辅助模型的块。
Package:package提供了一个声明空间,这个空间可以被其他块共享。其他块可以使用import语法来包含package中的声明。
Primitive:Primitives用于代表低层级的门级电路和开关。SystemVerilog包含许多内建的primitive类型。设计者可以使用user-defined primitives(UDPs)来增补内建primitives。
Configuration:SystemVerilog提供了指定设计configurations的能力,通过指定module例化的bingding信息来指定SystemVerilog源代码。
3. 仿真时间单位和精度
时间单位(time unit)和时间精度(time precision)可以用两种方式指定:
使用`timescale编译指导符:给全部设计元素指定默认的时间单位和精度,但如果某些设计元素内部包含有timeunit和timeprecision,那么将采用它们内部的。如果有多个`timescale,文件排布不同,可能会影响仿真结果。
使用timeunit和timeprecision关键字:timeunit和timeprecision可以在设计元素内直接指定,不管文件如何排布,都不会影响该设计元素内的仿真结果。
4. event simulation (基于事件的仿真)
SystemVerilog语言是根据离散事件执行模型来定义的。SystemVerilog是由执行线程或process组成的。Process对象拥有状态,并可以根据它们输入的变化来产生输出。Process是并发调度的,例如initial程序。还有其他process包含但不限制于primitives、intial、always、always_comb、always_latch和always_ff procedure、continuous assignments、asynchronous task、procedural assignment statements(过程赋值语句)。
在SystemVerilog仿真中,net或variable的任何状态改变都被认为是一次update event。process对update events很敏感,当有执行update event时,所有对该event敏感的processes都将按照任意顺序进行evaluation。process的evaluation也被认为是event,称为evaluation event。
5. Time slot
在特定时间的所有调度事件定义一个time slot。在仿真时,按照时间顺序执行并删除当前仿真time slot中的所有事件后,才能移动到下一个非空的time slot继续进行仿真。Time slot按大类分主要有5个:Preponed、Active、Observed、Reactive和Postponed。
我们常用的#1step指的就是在进入当前slot时去采样,即在当前time slot的preponed region采样。在preponed region采样等价于在previous postponed region采样。
#0会使本应该在active events region执行的process(event)被调度到inactive region去执行。
$monitor和$strobe是在Postponed region执行的。
2、数据类型
1. 区别data type和data object
data type类似于class type,然后data object类似于class object,相当于一个entity。所以data type可以用于声明data object。每一个data object是一个命名的entity,它带有一个data value和data type。比如:
int是一个data type;int a=1; //a就是一个data object;
2. 区别singular和aggregate类型
Data type可以被分类为singular或aggregate。除了unpacked structure、unpacked union、unpacked arrary,其它的data type都是singular type。unpacked structure、unpacked union、unpacked arrary都是aggregate type。一个singular variable或expression代表一个value、symbol或handle。Aggregate expressions和variables代表一组singular value。之所以这样分类,是因为方便operators和functions可以更方便的引用这些data types。
3. 区别nets和variables类型
Data objects主要可以分为两组:variables和nets。这两组的不同之处在于它们赋值和保持value的方式。
Net可以被一个或多个continuous assignment、primitive output、module port赋值。variable可以被一个或多个procedural statements赋值(包括procedural continuous assignment)。
Net可以在declaration的时候,implicit连续赋值。但variable如果在declaration的时候也给了assignment,那也只是相当于给该variable初始化,而不是continuous assignment。
有两个不同类型的net types:built-in和user-defined。Net type主要是模拟物理连线,因此net通常不能store value(除了trireg net)。Net的值取决于它的drivers,例如continuous assignment或gate。如果没有driver的话,那么它的值将会是高组态。built-in的net types有:wire、tri、tri0、tri1、trireg、triand、trior、wand、wor、supply0、supply1和uwire。
wire和tri是相同的语法和功能,提供两个名字主要是用于建模指示不同的目的。wire用于被single gate或continuous assignment驱动的nets,tri用于被多个drivers驱动的nets。如果有多个drivers具有相同的logical conflicts,那么结果就是x态。
Variable是data storage element的抽象,它可以储存value。
4. 区别Scalar和Vector
在reg、logic或bit定义中,scalar是1bit位宽;vector是多bit位宽。
5. packed arrays和unpacked array的区别
Packed array是声明在数据标识符名字之前的维度,可以是1维或多维的,1维的也称为向量vector。Unpacked arrary是声明在数据标识符名字之后的维度,可以1维或多维的。
Packed arrays可以将vector分为subfields,这样可以方便的访问到array中的元素。因此,packed array需要连续的bit存储。
固定size的unpacked arrary用以下两种方式声明都可以的:
int Array[0:7][0:31]; // array declaration using rangesint Array[8][32]; // array declaration using sizes
6. 多维数组
多维数组是数组中包含数组。多维数组可以用包括多维来定义的。在标识符名字之前的为packed维度,在标识符名字之后的称为unpacked维度。
在使用多维数组时,packed维度在unpacked维度之后,并且最右边的维度变化最快,如:
bit [1:10] v1 [1:5]; // 1 to 10 varies most rapidlybit v2 [1:5] [1:10]; // 1 to 10 varies most rapidlybit [1:5] [1:10] v3; // 1 to 10 varies most rapidlybit [1:5] [1:6] v4 [1:7] [1:8]; // 1 to 6 varies most rapidly, followed by 1 to 5, then 1 to 8 and then 1 to 7
可以使用typedef分阶段定义多个packed维度的数组,如:
typedef bit [1:5] bsix;bsix [1:10] v5; // 1 to 5 varies most rapidly
可以使用typedef分阶段定义多个unpacked维度的数组,如:
typedef bsix mem_type [0:3]; // array of four 'bsix' elementsmem_type ba [0:7]; // array of eight 'mem_type' elements
Part-select指的是选择一维packed数组中某1bit或多个连续bit。
Slice指的是选择一个数组中一个或多个连续的elements。
part-select和slice选择的size必须是常数,但位置可以是变量,如:
int i = bitvec[j +: k]; // k must be constant.int a[x:y], b[y:z], e;a = {b[c -: d], e}; // d must be constant
7. unpacket和packet数组访问方式
数组的index中如果是超出数组大小或包含任何的x/z态,那么该index就是无效的。
用invalid index从unpacked数组读取内容,结果如下:
用invalid index写到数组中,应该是不执行任何操作,除了queue中[$+1]的元素操作或者associative array创建新元素。
用invalid index从packed 数组读取内容,也就是从vector、packed array、packed structure、parameter或concatenation变量中选取某1-bit。用于寻址bit的index如果超出变量范围或有x态或有z态,那么对于4-state bit则返回x,对于2-state bit则返回0。对于scalar、real变量和real参数进行part-select是无效的。
8. 数组类型
动态数组(Dynamic arrary)是unpacked数组,它的大小在放着运行时是可以改变的。动态数组的大小是通过new[]或者赋值来获得的。
动态数组适用于连续且数组大小动态变化的情况。而关联数组(associative arrarys)可以用于需要存储的数据是分散的。
队列(queue)是大小可变,并且有顺序的数组。Index=0表示第一个元素,index=$表示最后一个元素。在定义时,可以选择队列的最大个数,如果在使用时超过这个最大个数,会有信息提示。对queue[$+1]进行读写是可以的,也就queue有这个例外,其它数组的话,如果没有提前搞好size,直接这样搞会报错的。queue的内置方法:
size()、insert()、delete()、pop_front()、pop_back()、push_front()、push_back()
SystemVerilog给数组也提供了一些新的系统函数用于返回数组的一些信息,有$left, $right, $low, $high, $increment, $size, $dimensions和$unpacked_dimensions。
SystemVerilog给数组提供了几个内建操控方法,用于对数组进行searching、ordering和reduction。
array locator methods:用于对unpacked 数组操作,包含find()、find_index()、find_first()、find_first_index()、find_last()、find_last_index()。这几个方法后面必须带有with (xxx)。min()、max()、unique()、unique()后面的with关键字可以被省略。
arrary ordering methods:用于对unpacked 数组的reorder操作,有reverse()、sort()、rsort()、shuffle()。
array reduction methods:用于对unpacked 数组的操作,有sum()、product()、and()、or()、xor()。
3、类(class)
1. class内容
class里面包含data和对data进行操作的subroutines(functions and tasks)。class的data称为class properties(类属性),subroutines称为methods(方法)。两者都是class的members。
class相当于是定义了一个data type。object是该class的instance。Object handle是持有该class instance的变量。
class里method的生命周期必须为automatic,如果定义成static是非法的(这里不是指static task,而是task内static的variables或arguments)
2. class构造函数
SystemVerilog提供了class的构造函数,支持在创建object的时候对instance进行初始化。
如果用户没有显示指定new方法,那么隐藏的new方法将会被自动加上。在子类的new方法里应该首先call父类的new构造函数。
super.new需要在子类构造函数中第一条语句就执行,这是因为superclass需要在子class之前被初始化,如果用户没有提供superclass的初始化,那么compiler会自动插入super.new。
3. class的static属性
class中的static属性可以直接引用,不需要该通过class的instance。
class中的methods也可以是static,static method不能访问non-static 成员变量(properties或methods),但它可以访问同个class内的static 成员变量。如果它访问了non-static 成员变量或this关键字句柄,那么会报编译错误。static methods也不能是virtual。
4. shallow copy和deep copy
shallow copy只是复制原有object的properties,但如果原有object里含有object handler,也只是复制handler的值,不会把handler object里的properties也进行复制。Shallow copy也不会创建新的coverage objects(covergroup instances)。
如果要做到连handler里的properties都复制,那么需要deep_copy,deep copy需要user自己实现的。
5. 多级构造函数
如果多层class继承的话,那么在构造函数里需要调用父类的构造函数。
如果父类的构造函数需要参数,那么有两种方法:1. 用super.new(xx)传进去;2. 在继承父类class时就传进去的,用这种方式,在constructor里就别加super.new(xx)了。
父类的new函数如果不需要参数(没有参数或参数带默认值),那么在子类构造函数里对super.new()可写可不写,不写的话,compiler会自动插入。如果父类的构造函数需要传参数,那么compiler自动插入也只是为super.new(),也不会给你传参数值进去的(除非在继承时指定),这样simulator会报错。
记住:如果将new定义为local,那么该类将不可以被继承的。
在子类构造函数中需要第一条调用父类构造函数super.new()中,要等super.new()执行完之后,才会做子类的property初始化,如果property有指定初始值,那么就赋值,如果没有指定,那就是undefined的。最后才会继续执行子类构造函数中super.new()以下的语句。因此在给父类构造函数传输参数,如果用子类定义的变量,那么结果会是undefined的,因此,来不及初始化啊。例子如下:
class C;int c1 = 1;int c2 = 1;int c3 = 1;function new(int a);c2 = 2;c3 = a;endfunctionendclassclass D extends C;int d1 = 4;int d2 = c2;int d3 = 6;function new;super.new(d3);endfunctionendclass
D类对象创建完成后,这些属性的值如下:
c1的值是1;
c2的值是2,这是因为构造函数赋值发生在属性初始化之后;
c3的值是不确定的,这是因为D的构造函数调用传递了d3的值,这个值在super.new(d3)调用时是未定义的;
d1的值是4;
d2的值是2,这是因为super.new调用先于d2初始化;
d3的值是6;
2865