SystemVerilog語法小結
1、包的定义:
package _name;
……
endpackage
- 引用包时必须先行导入,通配符导入:
import _name::*;
- 端口列表中依然要引用包的名称。除非首先将包导入$unit编译单元,即可将import语句放到每个文件模块或接口定义之前。此外,`include “_name.pkg”应该放在文件的开始。
- 为了能够综合,包中定义的任务和函数声明为automatic
- packet內部不能包含interface
2、verilog允许在命令的begin...end或fork...join块中声明局部变量。通常用于声明一个临时变量进行循环控制。sv扩展了v,允许在未命令块中声明变量,但不能被层次化引用。
3、关键字timeunit和timeprecision指定时间单位和精度说明。这两条声明必须在其它任何声明语句之前,紧随模块、接口或程序的声明之后指定
4、数据类型
关键字logic并不是一个变量类型,它是一个数据类型,但当它单独使用时,则暗示这是一个变量。
- wire类型的缺省为logic数据类型。
wire [31:0] bus等价于wire logic [31:0] bus
- 其它数据类型:
bit #——1位两态整数
byte #——8位
shortint #——16位
int #——32位
longint #——64位
byte、shortint、int、longint的缺省为有符号数,unsigned关键字定义无符号,只允许在类型关键字后声明。而在verilog中缺省为无符号,显式声明有符号signed
- 两态数据类型从逻辑0开始仿真,四态类型缺省为逻辑值x;四态类型任何位的x和z赋值给两态变量时对应位会轮换为逻辑0
- 当四态或两态数据类型没有显式指定数据类型是变量还是线网时,则推断为隐式变量
- logic [7:0] a等价于var logic [7:0] a
- sv两态变量特别定义为从逻辑0开始仿真而且不引起仿真事件
5、使用verilog case语句的常用建模风格就是把default分支的输出赋为逻辑X,综合会把缺省的逻辑X赋值看成一个特殊标志,表示对于其他条件选择项以外的所有条件,输出都为无关值。综合会将条件选择项的译码逻辑进行优化,不会考虑缺省分支条件表达式的值如何译码。
6、静态和自动变量
- 在verilog中,通过声明整个任务或者函数是自动的,来声明自动变量(automatic)。自动任务或函数中的所有变量都是动态的。
- sv包括static和automatic,允许任何变量被显示地声明。这个声明是变量声明的一部分,可以在任务、函数、begin...end块中。这些单元中所有的存储方式都缺省为静态的。如果一个任务和函数被声明为自动的,则所有变量的存储方式默认为自动的,除非被显式地声明为静态的。
7、sv的内嵌初始化
- sv对v进行扩展,在任务、函数声明的变量可以有内嵌初始值,内嵌初始值将在仿真开始前进行一次赋值。对任务或者函数的再次调用不会重新初始化静态变量。(注:在任务或函数中初始化静态变量是不可综合的;内嵌初始化是发生在仿真开始之前,而不是发生在函数调用时)
- 对自动变量的内嵌初始化,在每次任务和函数调用时都会对其初始值赋值。(动态变量初始化是可综合的)
- 在verilog-2001加入了一种方便的方式进行变量初始化——可以在变量声明是进行初始化:
- integer i = 5; //声明一个i变量,并初始化。动作发生跟其他在仿真0时刻执行的initial块、always块中的初始化的顺序一样,都是不确定的。
- sv规定所有内嵌初始化先于在仿真时刻0执行的事件。sv变量内嵌初始化不引发仿真事件
8、强制类型转换(不会检查合法性)
- <type>’(<expression>)——将一个值强制转换成任何数据类型,例:
- 7+int’(2.0*3.0)
- <size>’(<expression>)——将一个值强制转换成任意向量宽度,例:
- y = a + b +16’(2)
- <sign>’(<expression>)——将一个值成有符号或无符号数
- 动态强制类型转换系统函数$cast,在运行时进行待转换数据的检查:语法如下:
$cast(dest_var, soruce_exp);
$cast试图将源表达式赋给目标变量。如果赋值是无效的,将会报告一个运行时错误,并且目标变量保持不变。如果转换成功$cast返回1,否则返回0并且不改变目标变量。
9、常数,verilog提供三种类型的常数:parameter,specparam和localsparam
- parameter是一个可以在确立时使用defparam或者内嵌参数重定义进行重新定义的常数
- specaparam是一个可以在确立时从SDF文件中重定义的常数
- localparam是确立期常数,不能重定义。但是它的值可以基于其他常数。
- sv提供关键字const常数声明,声明时必须包含数据类型
10、sv提供关键字typedef建立用户自定义类型
enum关键字建立枚举数据类型,例:
typedef enum {first,second} state_t;
state_t a; //与enum {first,second} a;等价
- 注意:当枚举类型定义从包中导入时,只有类型名被导入,枚举列表中的数值标签不会被导入,因此不会在枚举类型名导入的命名范围中可见。为了使枚举类型标签可见,可以显式导入每个标签,或用通配符导入整个包。
- 枚举类型列表中的标签在其作用域内必须是唯一的
- 枚举类型的默认基类是int,可显式地声明对其改变,例:
enum logic[2:0] {WAIT =3’b001,……} state;
- 如果显式定义枚举类型的枚举标签赋值,那么这个值的宽度必须与基类宽度相符
- 标签数不能超过基类所能代表的宽度
11、一个枚举类型只可以进行下列赋值:
- 枚举类型中的一个标签
- 同类枚举类型的其他变量(即用同样枚举类型声明的一个变量)
- 通过cast转换成枚举类型变量的数值
12、枚举类型的专用系统任务和方法
- <枚举变量名>.first——返回指定变量枚举列表中的第一个成员的值
- .last——返回最后一个成员的值
- .next(<N>)——返回下一个(N个)成员的值
- .prev(<N>)——返回上一个成员的值
- .num——返回变量的枚举列表中元素个数
- .name——返回枚举变量中代表这个值的字符器
13、结构体,声明:
struct {
……
} _name;
- C允许在关键字和左大括号之间选用一个标记,sv不允许。
- 将结构体声明变量或类型是可选择的。隐式为var。注意:虽然结构体整体可以被声明为类型,但是结构体内不能使用类型
- 结构体在实例化时可以对其成员初始化。使用大括号’{}内的一组值,大括号之间的值的个数必须与成员的个数一致。例:
typedef struct{
……
} instruction_word_t;
instruction_word_t IW = ‘{100,5,8’hff,0};
- 整个结构体可以使用结构体表达式进行赋值即使用’{};结构体表达式也可以指定被赋值的结构体名称用冒号隔开成员名称和要赋的值,例:
IW= ‘{address:0,opcode:8’hff,a:100,b:5};
- 注意不能混合使用成员名称和成员顺序;可以有default:对剩余赋值
14、压缩结构体:
- 默认情况下,结构体是非压缩的。可以使用关键字packed显式地声明一个压缩结构体,压缩结构体按照指定的顺序以相应的位来存储结构体成员,压缩结构体被当作一个向量存储,结构体的第一个成员在向量的最左边。向量的最低位是结构体最后一个成员最低位。(注意:压缩结构体只能包含整数值。所谓整数值就是可以表示为如byte、int这样的向量以及用bit或logic创建的向量值)
- 压缩结构体以向量形式存储,对整个结构体的操作也是以向量形式,因此对向量的算术操作、逻辑操作以及任何其他操作都可以用于压缩结构体。
- 压缩结构体可以使用关键字signed或unsigned来声明,这些修饰影响到整个结构体在算术或相关的操作中,作为一个向量如何识别,但不影响结构体的成员如何识别。
15、联合体:
- 联合体只存储一个元素,这个元素可以有多种表示方法,每种表示可以是不同的数据类型
- 联合体只存储一个值。(注意:如果从非压缩联合体中读取的成员不同于上次写入的成员,可能导致不确定的结果)
- 非压缩联合体是不可综合的
- 一个联合体可以声明为tagged。标签联合体包含一个存储“标签”的隐含成员,它代表着存储数值的最后一个联合体成员的名称。例:
data = tagged i 5; //在data.i中存5,并设置隐含标签
d_out = data.i; //从联合体中读值
d_out = data.r; //错误;成员与联合体的隐含标签不匹配
16、压缩联合体:
- 在压缩联合体中,每个联合体成员的位数都必须是相同的。可综合
- 压缩联合体只能存储整数值
- 联合体允许数据以一种格式写入,而以另一种不同的格式读取
- 压缩的标签联合体只允许从与上一个标签表达式写入到联合体中的成员相匹配的同一个成员中读值
17、数组:
- 非压缩数组,例:wire [7:0] table [3:0];复制非压缩数组时,等号的左边和右边必须有同样的构架和类型。也就是说复制的元素位数及个数必须一致
- 压缩数组,例:logic [3:0][7:0] data;压缩数据像向量一样存储,可进行拼接操作、算术操作、关系操作、位操作以及逻辑操作
- 压缩数据初始化可以是一个常数、常数的拼接或常数的复制,例:
logic [3:0][7:0] a = 32’h0;
logic [3:0][7:0] a = {16’hz,16’h0};
logic [3:0][7:0] a = {16{2’b0}};
- 对于非压缩数组初始化,在声明时可以使用’{}内的值序列进行初始化,例:
int d1[0:1][0:3] = ‘{‘{7,3,0,5},’{2,0,1,6}};
int d1[0:1][0:3] = ‘{2{7,3,0,5}};
也可以在’{}内使用default:赋值
- 注意数组声明的区别:
int lo_hi[0:15] 与 int lo_hi[16]是等价的,必须注意的是:
int m[1:0][2:0] = ‘{‘{0,1,2},’{3,4,5}},括号中第一组给的是m[1],且第二组中第一个给m[0][2],依此类推;即:
m[1][2] = 0 m[1][1]=1 m[1][0]=2 m[0][2]=3 m[0][1]=4 m[0][0]=5
int m[2][3] = ‘{‘{0,1,2},’{3,4,5}}则是将第一组组m0,且第一组中第一个给m[0][0],即:
m[0][0] = 0 m[0][1]=1 m[0][2]=2 m[1][0]=3 m[1][1]=4 m[1][2]=5
为什么?
就为m[2][3]等价于m[0:1][0:2]
- 动态数组
声明时使用空的下标[],用new[]操作符来分配空间,例:
int dyn[];
dyn = new[5]; //分配5个元素
dyn = new[20](dyn); //分配20个元素,并将原来的元素复制到新分//配的数组空间,原来的空间被释放
dyn = new[100] //分配100个元素空间,原来的空间被释放,数据被删除
18、sv增加了foreach循环,它可用来对一维或多维数组中的元素进行迭代。foreach循环的自变量是数组名,它后面是方括号内用逗号隔开的循环变量列表。每个循环变量对应于数组的一个维度。例:
int sum[1:8][1:3];
foreach(sum(i,j))
sum[i][j] = i + j ;
function [15:0] gen_crc(logic [15:0][7:0]d);
foreach(gen_crc[i]) gen_crc[i] = ^d[i];
19、sv包括3个特有的过程块:always_comb、always_latch、always_ff
- always_comb的后面不需要指明敏感表,软件可以自动推断出来。
- always_comb块会在仿真的零时刻自动触发。不管推断出的敏感表中的信号是否发生变化,这样的自动求值都会发生
- always_comb过程块对块内读取的信号和块内调用的函数读取的信号都敏感
20、任务和函数的改进
- 在verilog中,任务或函数中有多条语句时必须写在begin...end之间,sv则不用。
- 在verilog中,函数名本身就是一个与该函数类型相同的变量。函数的返回值通过对函数名的赋值产生。而在sv中,增加一条return语句返回数值,return优先级更高。并且使用return语句可以在执行流的任何时刻退出任务或函数
- 函数可以显式地声明为void类型,表示函数没有返回值
- 函数除有input外可以有output和inout的形式参数。这样一来,虽然空函数没有返回值,仍然可以说传送调用函数所产生的变化。
- sv中,增加了使用形式参数的名称而不是顺序来传递参数值的功能。
- sv允许函数没有形式参数,而verilog要求至少有一个输入形式参数,即使这个参数值从未在函数中使用过。
- sv任务和函数声明的形式参数的缺省方向为input。一旦声明了一个方向,后面的参数为声明的方向。
- sv允许任务与函数为每个形式参数定义可选的值。当调用任务或函数时,不一定非得给缺省的参数传递参数值,不给定,则在任务或函数调用时使用缺省值
- 自动任务和函数可以使用ref参数通过引用传递数值
- 通过将形式参数声明为const ref类型,可以将引用形式参数声明为只允许对引用的对象进行读操作。
- ref参数的一个重要特点是任务中的逻辑会对信号在调用程序内发生的变化敏感。也就是说ref参数会一直跟着调用参数变化,而采用input的方式的形参只是在调用任务时将参数复制,之后对其不敏感
- sv允许用关键字endtask和endfunction指定名称,结尾处指定的名称必须与对应的任务或函数的名称一致
21、sv增加“++”、“—”运算符,是阻塞赋值。在非阻塞赋值行为时,避免使用,只能用在逻辑建模中。
22、for循环中声明局部变量(为自动变量):
for(bit [4:0] i =0; i<=15;i++)
多重for循环赋值:
for(int i=1, int j=0; i*j<128; i++,j+=3)
23、增加do...while循环,當while循環不滿足時,跳出循環體
24、新的跳转语句:
- contiune:跳转到循环的结尾然后执行循环控制
- break:立即中止循环的执行
- return:可以在任务或函数执行流的任意时间执行,执行后立即从任务或函数中退出,无需到达任务或函数的结尾
25、sv可以在任意过程语句前面指定一个标号:
<label>: <statement>
26、verilog规定case语句必须按照列举顺序计算条件。即case的选项间存储优先级,选项之间必须保证相斥才能去掉优先级判定。sv为case语句提供unique和priority修饰符。
- unique语句指定:
只有一个条件选项与条件表达式匹配
必须有一个条件选项与条件表达式匹配
当指定为unique时,软件工具必须在语法上进一步检查以保证每个条件选项都是相斥的。如果在运行时有多个条件选项与条件表达式相匹配,则工具会执行发生运行错误。
如果所有的条件选项都不能和条件表达式匹配,也没有缺省条件时,软件工具报告运行期警告。
(注意:unique是在运行中进行检测,错误检查质量取决于测试向量的完成性)
- priority
至少有一个条件选项的值与条件表达式匹配
如果有多个条件选项的值与条件表达式匹配,必须执行第一个匹配分支
27、对if...else也增加unique和priority选项。(检测及报错与上述相似)
- unique表示条件排列的顺序并不重要。软件工具会将推断出的顺序优先级优先掉。
- priority表示分支的次序是重要的。软件工具必须按照原有次序进行计算
28、sv提供简化网表的方法:
- .name:只需要指出端口名称,sv会自动地将与该名称一到处的线网或变量连接到端口上,例.data与verilog的.data(data)相同(为了推断与命名端口的连接,线网或变量必须与端口名和端口向量宽度一致
- 隐式的.*端口连接:表示模块实例中所有名称相同的端口和线网(或变量是自动连接在一起的
- alias语句:表示两个不同名称对同一个线网的引用,即对线网别名化。限制:只有线网类型可以别名化,变量不能别名化;进行别名化的两个线网类型必须相同;进行别名化的两个线网的位数必须相同。alias可以推断出线网声明,并不需要在别名操作中显式的声明每个线网。
29、sv的接口:
- 接口声明:
interface if_name;
……
endinterface
- 在顶层见表中实例化:
if_name bus( ); //接口实例,实例名是bus_name
processor pro(
.bus(bus), //接口连接
……
);
- 模块定义:
module processor(
if_name bus, //接口端口,模块定义有一个if_name端口
其他端口定义
);
- 模块端口可以显式的声明为特定接口类型,可以把接口名称当作端口类型使用。显式命令的接口端口只可以连接到同一名称的接口上,其他不同定义的接口连接到这个端口就会报错。特定接口声明:
module <模块名称> (<接口名称> <端口名称))
- 通用接口端口:
通用接口端口用关键字interface定义类型,而不是使用特定接口类型的名称。语法:
module <模块名称> (interface <端口名称>);
- sv支持:
在一个地方——接口中定义通信所需各个信号和端口
在接口中定义通信协议
在接口中直接建立协议校验和其他验证程序
- 接口类型的端口,不管是通用还是显式的,都必须连接到接口实例或其他接口端口上,如果未连接,则会产生错误
- 在有接口类型端口的模块中,接口内部的信号必须用端口名进行访问,语法如下:
<端口名称>.<内部接口信号名称>
- sv接口定义时,可以定义接口信号的不同接入方式。关键字modport的定义描述了接口表示的端口的接入方式。接口中可以有任意数量的modport定义,而每个定义描述了各个模块是如何访问接口中的信号。
注意:modport定义中不包含向量位数和类型,只定义连接模块将信号看成是input、output、双向inout还是ref
- 在模块实例中选择modport
当模块被实例化并且接口实例连接到模拟实例的端口时,可以指定接口的特定modport,方式如下:
<接口实例名称>.<modport名称>
- 在模块端口声明中选择modport,方法:
<接口名称>.<modport名称>
注意,上述两种式只能选择其中一种。
- 如果没有在接口连接中指定modport,那接口中所有的线网信号将默认是双向inout信号,而所有的变量将默认为是ref类型
- sv可以在接口中声明任务和函数,模块通过modport连接到接口上,那么接口方法必须用关键字import指定,导入(import)定义是在接口中指定为modport定义的一部分,基本语法:
modport(import<任务或函数的名称))
也可以导入任务或函数的完整原型:
modport( import task<任务名称>(<任务的形式参数>));
modport( import task<函数名称>(<函数的形式参数>));
模块可以通过modport访问接口方法,方式:
<接口的端口名>.<方法名>
30、队列:
队列的声明使用:[$],例:
int q[$] = {3,4};
q.insert(j,m); //在队列的第j个元素之前插入m,m可以是队列或
//数,两个都可以使用变量
q.push_front(m) //在队列前面插入
j = q.pop_back //从队列末尾弹出一个元素
q.push_back(m) //在队列的末尾插入
j=q.pop_front //从队列前面弹出
q.delete() //删除整个队列
//队列也可以类似于位拼接的操作
q = {q[0],j,q[1:$]}
31、数组的方法:
- 最常用的sum:对数组中的所有元素求和,缺省情况下,其和的位宽与数组元素位宽一致,可以与其它位宽的数进行比较而达到想要的结果位宽,或使用适当的with表达式改变位宽
- 其它:product(积),and,or,xor
- 随机选取一个元素的方法:$urandom_range($size(array)-1)
- 数据定位方法:array.min、max、unique(具有惟一值的元素),注意它们返回的是一个队列而非标量
- find:例
intd[] = '{9,1,8,3,4,4},tq[$];
tq = d.find with (item>3) //{9,8,4,4}
tq = d.find_index with (a >3) //{0,2,4,5}
- 数组排序:d.reverse() 翻转;d.sort() 正序;d.rsort() 倒序; d.shuffle() 随机
SV面向对象的编程
1、verilog的例化是静态的,像硬件一样在仿真的时候不会变化,只有信号值在改变。而SV中,激励对象不断地以创建并且用来驱动DUT(device under test),检查结果。SV的类在使用前必须先例化,例:
class Transaction;
……
endclass
Transaction tr; //声明一个句柄
tr = new(); //为一个Transaction对象分配空间
tr = null; //清除句柄
new函数为构造函数,可以在类中定义,每一个新的对象都会调用构造函数
function new( );
……
endfuction
2、在SV的类中可以创建一个静态变量(默认使用自动存储),该变量将被这个类的所有实例所共享,并且它的使用范围仅限于这个类。例:
class Transaction;
static int count = 0;
……
endclass
引用静态变量时,除了使用句柄方法外,还可以直接使用类作用域操作符:
Transaction::count
3、关键词this用来引用类一级的对象。即改变寻找的作用域到方法顶层的作用域
4、类中的方法默认使用自动存储
$sformat ( output_var , format_string [ , list_of_arguments ] )
$sformat always interprets its second argument, and only its second argument, as a format string. This format argument can be a string literal, such as "data is %d", or may be a an expression of integral, unpacked array of byte, or string data types whose content is interpreted as the formatting string
$sformatf ( format_string [ , list_of_arguments ] )
The system function $sformatf behaves like $sformat except that the string result is passed back as the function result value for $sformatf, not placed in the first argument as for $sformat. Thus $sformatf can be used where a string value would be valid.