1 四值数据: interger,logic ,reg,wire ; 二值数据: byte,shortint,int,longint,bit
2 有符号: byte shortint int longint , integer; 无符号: bit logic reg wire
3 数据类型转化:1 隐式转换;2 显示转换 ->静态转换: 转换表达式前加上单引号,不会对转换值做检查,转换失败也不知道;动态转换$cast(tgt,src)
4 数组:
//定宽数组
int lo_high [0:16];
//初始化和赋值
int ascend[4] = '{0,2,3,4};
aascend = '{4{8}};
// package
bit [3][7:0] b_pack; // 占一个word存储空间
// unpackage
bit [7:0] b_unpack[3];// 占三个word存储空间
5 动态数组: 定宽数组的宽度在编译的时候就确定了,动态数组的宽度是在程序运行时确定的; 可以在仿真运行时灵活的调节数组的大小;需要用new[] 分配内存空间,方括号传递数组宽度
6 关联数组: 当需要一个非常大的地址空间寻址时,该数组职位实际写入的元素分配空间。类似于字典。
bit [31:0] assoc[int];
7 结构体: 只是一个数据结合,将若干个变量组合到一个struct结构中去。可以通过typedef 创建一个新的类型
8 枚举: 提高可读性和维护性
9 字符串: 使用string 来保存
10 变量的生命周期分为动态和静态
局部的变量的生命周期同其作用域共存亡,例如function task 中的变量,在方法调用结束后,变量生命终结。
全局变量伴随着程序的执行开始到结束一直存在,例如module中的变量为全局变量,由于实在模拟硬件信号,所以是静态生命周期。
如果数据变量被声明为automatic,那么进入该进程/方法后,automatic变量会被创建,离开该进程、方法后,automatic 变量会被销毁,static变量,会被多个方法或进程所共享。
function static int static_cnt(a);
static int cnt = 0
cnt += a;
return cnt;
endfunction
function automatic int auto_cnt(a);
int = 0;
endfunction
对于automatic方法,其内部的所有变量默认也是automatic,即伴随automatic方法的生命周期建立和销毁。
对于static 方法,其内部所有变量默认也是static类型。
对于static变量,用户在声明变量的时候应该同时对其做初始化,而初始化之后伴随它生命周期发生一次,并不会随着方法调用被多次初始化。
module interface task function 之外声明的变量拥有静态的生命周期,即存在于整个仿真阶段
在module interface 和program内部声明,且在task 或function外部声明的变量也是static的,其作用域在该块中。
1 module/endmodule, interface/endinterface可以视为硬件世界。program/endprogram和class/endclass视为软件世界。
2 initial块只执行一次,initial和always在仿真一开始同时执行,不同initial和always之间在执行顺序上没有顺序。
3 函数参数列表指定input, output,inout, 应用参数ref; 可返回或不返回数值。默认的数据类型为logic
4 interface: 将信号封装在同一个接口中,便于维护; 接口的端口列表只需要定义时钟,复位等公共信号。
5 采样和数据驱动:默认情况下时钟对于电路驱动会添加一个无限小时间(delta-cycle)的延迟。
6 为了避免采样的竞争问题,在驱动的时候,添加相应的人为延迟,用来模拟真实的延迟行为;在采样时间前的某段时刻进行采样;
7 可以通过modport 来进一步限定信号传输方向,避免连接错误。
8 可以在interface中声明clocking(时序块),基于时钟周期对信号进行驱动和采样,消除信号竞争。
9 program将验证部分与设计部分进行有效隔离,如果testbench 中只有一个program,则会在执行完该program中最后一个initial块后自动结束仿真,如果有多个,等待最后一个initial后,结束仿真。
1 Verilog的例化和systemVerilog 类的例化: 相同点都是通过‘模板’ 来创建内存实例;不同点是Verilog的例化是静态的,即在编译链接的时候就完成了,而SV class 的例化是动态的,可以发生在任何时间点,例化方式更加灵活
2 创建对象会开辟内存空间,用来存放成员变量和方法; 创建对象可以通过自定义的构造函数来完成变量的初始化和其他初始操作。
3 构建函数new()是系统预定义函数,不需要指定返回值,函数隐式返回例化后的对象指针。
4 静态变量:与module 和 interface不同,在class中声明的变量其默认类型为动态变量,其生命周期起始于对象创建,终止于对象销毁; 如果使用static来声明class内的变量,则其为静态变量,生命周期起始于编译阶段,贯穿整个仿真。可以直接引用class::var,或者通过例化对象引用object.var。
5 静态方法: class 中的定义的方法默认为动态方法,我可以通过关键字static修改其类型为静态方法。静态方法内可以声明动态变量,到那时不能使用类的动态成员变量。原因是在调用静态方法时,可能并没有创建具体的对象,因此没有为动态成员变量开辟空间。静态方法可以使用类的静态变量,因为静态方法同静态变量一样在编译阶段就已经为其分配好了内存空间。
6 类成员变量的访问权限: protected 只有该类或者子类可以方法成员变量,外部不能访问。local 只有该类可以访问成员,子类或者外部无法访问。
7 this 是代表当前实例的,super 表示调用父类的变量和方法
8 子类定义new函数时,应该先调用父类的new函数即super.new()。如果父类的new函数没有参数,子类也可以省略调用,而系统在编译的时候会自动添加super.new()
9 创建对象的顺序:子类的实例对象在初始化时,首先会调用父类的构造函数,当父类构造函数完成时,会将子类实例对象中的各个成员变量按照它们定义时显式的默认值初始化; 成员变量默认赋值后,才会进入用户定义的new函数中执行剩余初始化代码。
10 父类和子类有同名的变量和方法是允许的,在引用的时候是根据句柄类型来确定作用域的。
11 父类句柄指向子类对象,访问的范围限定在父类。
12 句柄可以作为形式参数通过方法来完成对象指针的传递。
13 package 提供一种命名空间,域名索引"::" 操作符。
14 类型转换分为静态和动态转换
15 动态转化:类句柄向下转换,即从父类句柄转换为子类句柄
16 父类句柄赋值给一个子类句柄并不是总是非法的,
17 不管父类句柄是否指向一个子类对象,sv的编译器对这种直接赋值是禁止的
18 $cast(tgt,src) 可以实现句柄类型的动态转换。 $cast(tgt,src)会检查句柄指向的对象类型,而不仅只检查句柄本身,一旦源对象和目的句柄是同一类型,或者是目的句柄的扩展类,$cast函数执行成功
19 将编译阶段可以确定下来调用方法所处作用域的方法称为静态绑定
20 动态绑定是在调用方法时,根据句柄指向对象的类型,再动态指向应该调用的方法。动态绑定需要声明virtual(虚方法)
21 虚方法的继承需要遵循相同的参数和返回类型,否则子类定义的方法归于同名不同参的其他方法。
22 浅拷贝(shallow copy): 创建两个对象,在创建p2对象时,从p1拷贝其成员变量。**只拷贝数据变量,对于任务和函数,采用类似引用的方式,即共用同一内存空间。
Packet p1;
Packet p2;
p1 = new;
p2 = new p1;
句柄拷贝:只将p1赋值给p2,那么依然只有一个对象
Packet p1;
p1 = new;
p2 = p1;
23 深拷贝(deep copy)
对象拷贝,对对象中成员变量和方法同一分配新的内存空间。
24 回调函数(callback)
在不修改原始类的情况下注入新的代码,可以在定义父类的时候,预留回调函数入口,使得在继承的子类中填充回调函数,完成对父类方法的修改。
1 使用rand关键词表明随机属性;randc 周期属性,即所有可能值都赋值后随机值不能重复。
2 SV 预定义的类随机函数:std::randomization
3 constraint 也同随机变量在类中声明。
4 关键词dist 可以在约束中产生随机数值的权重分布
5 := 表示值范围内的每一个值的权重都是相同的; :/操作符表示权重要平均分到值范围的每一个值
rand int src,dst;
constraint c_dist {
src dist {0:=40,[1:3]:=60};
// src = 0 weight = 40 / 220
// src = 1 weight = 60 / 220
dst dist {0:/40,[1:3]:/60};
// dst = 0 weight = 40/100;
// dst = 1 weight = 20/100;
}
6 inside 约束运算符
7 条件约束->,或者if-else
7 约束关闭和打开,可以选择constraint_mode()函数打开或关闭约束。下面代码如果不禁止任何一个约束,那么randomize 会失败,length 为0
class Packet;
rand int length;
constraint c_short {
length inside {[0:1]};
}
constraint c_long {
length inside {[1000:2000]}
}
endclass
Package p;
initial begin
p = new();
// disbale short constraint
p.c_short.constraint_mode(0)
assert(p.randomize());
end
8 内嵌约束(外部约束): 可以使用randomize() with 来增加额外的约束,内部约束和外部约束应该协调,如果相互违背,那么随机数求解就会失败。可以声明约束为soft( 软约束),即出现冲突,软约束的优先级最低,其约束会失效。
9 随机函数: 有时需要在调用randomize之前或者之后执行一些操作,sv提供randomize的回调函数pre_randomize 和 post_randomize用来定义随机化前后的行为。
10 随机数函数: r a n d o m 平 均 分 布 , 返 回 32 位 有 符 号 随 机 数 ; random 平均分布,返回32位有符号随机数; random平均分布,返回32位有符号随机数; $urandom() ,返回32位无符号随机数; $ $urandom_range() 在指定范围内的平均分布。
11 随机化个别变量:调用randomize() 可以变量,指定变量后其他变量不会被随机化。**没有被rand 修饰的的变量也可以作为randomize的参数而被随机化 **
class p;
byte low;
rand byte med;
endclass
initial begin
p t;
t = new();
t.randomize();
t.randomize(low);
t.randomize(med);
end
12 数组约束
1 硬件仿真每个模块都是独立的线程(thread),模块在仿真一开始并行执行,
2 线程之间的通信:线程需要同步并交换数据;线程需要等待另一个;多个线程可能需要同时访问同一资源;线程之间可能需要交换数据。
3 verilog 边沿触发 @event 等待事件,->触发事件
4 电平触发wait(e1.triggered())。可以防止阻塞。
5 semaphore 可以实现对同一资源的互斥访问,new() 创建一个带单个或多个钥匙的semaphore,使用get获取钥匙,put放回钥匙
6 mailbox 实现线程之间信息传递,mailbox需要new创建对象,例化时可以选择一个参数来限定存储数量,如何size为0或者没有指定,mailbox无限大;put把数据放到mailbox,get从mailbox移除数据。如果mailbox 满,put会阻塞;如果mailbox 空,get会阻塞;peek可以从mailbox获取拷贝数据而不是移除
1 代码覆盖率:
行覆盖率: 每行代码是否执行过
条件覆盖率: 每个条件中的逻辑操作是否覆盖
分支覆盖率: if case while 的各个分支情况
翻转覆盖率: 信号是否从0到1或从1到0翻转
状态机覆盖率: 各种转台进入的次数,状态之间的跳转情况
2 断言覆盖率: 仿真器会记录断言的先决条件是否触发,以及判断语句的成功或者失败
3 功能覆盖率: 设计的各项功能是否都实现了
4 验证阶段可分为单元验证阶段,集成验证阶段和系统验证阶段
单元验证阶段,关心的是模块功能和模块质量,此时出口条件为代码覆盖率。一般业界常用出口条件为:行覆盖率达到100%,分支覆盖率为100%,条件覆盖率达到95%,状态机覆盖率达到90%
集成验证阶段,关心的系统的功能,以及模块与模块之间的接口,此时出口条件为功能覆盖率。一般业内常用的出口条件为:功能覆盖率到达90%,对没有覆盖率的需要给出合理说明。
5 A cover group is similar to a class- you define it once and the instantiate it one or more times.
A cover group can be defined in a class or at the program or module level.
6 覆盖率是如何收集的?当覆盖点上指定一个变量或者表达式时,system Verilog 会创建很多个仓 bin来记录每个数值被捕捉到的次数。
7 个体仓和总体覆盖率
计算一个点上的覆盖率,需要确定所有可能取值的个数,也被称为域,一个仓中可能由一个或者多个值。
覆盖率就是采样值的数目除以域中仓的数目。例如3bit变量覆盖点的域为0:7,仿真如果有7个仓被采样到,那么这个点的覆盖率为7/8。这些点组合在一起就构成了一个组的覆盖率,所有组组合一起就给出整个仿真数据库的覆盖率。
8 自动创建仓
auto_bin_max 指名自动创建仓的最大数目,默认值为64。如果覆盖点变量或表达式的值超出了指定的最大值,system Verilog 会将值域范围平均分配给auto_bin_max个仓,例如16bit变量有65536个可能值,所以64个bin中的每一个都覆盖了1024个值。
9 命名覆盖点的仓
指定仓名:
covergroup a;
len: coverpoint tr.kind{
bins zero = {0}; // 表示kind=0
bins lo ={[1:3],5}; // 1个仓代表1:3和5的值,lo仓有一个值出现过,那么这个仓会被认为是覆盖过,即使其他没有出现也一样
bins hi[] = {[8:$]}; // 8个独立的仓8...15
}
endgroup
Note:
coverpoint 使用的是{},这时因为仓的命名是声明语句而非程序语句,后者采用begin…end.
10 忽略数值
在某些覆盖点上,可能始终得不到全部可能值。例如,一个4bit变量可能只用来存放6个值,如果采用自动建仓,那么覆盖率始终不会超过75%。可以用ignore_bins排除掉那些不用计算功能覆盖率的数值。
bit [2:0] low_ports_0_5;
covergroup coverport;
coverpoint low_ports_0_5{
ignore_bins hi = {6,7}; //忽略最后两个仓
}
endgroup
11 不合法的仓
有些采样值不仅应该忽略,而且如果出现还应该报错。
bit [2:0] low_ports_0_5;
covergroup coverport;
coverpoint low_ports_0_5{
illegal_bins hi = {6,7}; //如果出现会报错
}
endgroup
12 交叉覆盖率
同时测量两个或两个以上覆盖点的值。
对交叉覆盖仓进行标号
让交叉覆盖仓的名称更加有可读性,可以对每个覆盖点的仓进行单独的标号。
排除部分交叉仓
在交叉覆盖中,可以用binsof和intersect分别指定覆盖点和数值集。
covergroup cov;
port: coverpoint tr.port{
....
}
kind: coverpoint tr.kind{
....
}
cross kind,port{
ignore_bins h1=binsof(port)intersect{7};
}
endgroup
Note:
binsof 使用小括号(),intersect指定的是一个范围,使用大括号
13 从总体覆盖率的度量中排除部分覆盖率
一个组的覆盖率是基于所有简单覆盖点和交叉覆盖率。如果只希望对一个coverpoint 上的变量或者表达式进行采样,而这个coverpoint将会被用到cross语句中,那么可以将这个coverpoint的权重设置为0,这样就不会对总体覆盖率造成影响。
covergroup cov;
kind: coverpoint tr.kind{
option.weight=5 // 在总体中所占的分量
...
}
port: coverpoint tr.port{
option.weight=0; //在总体中不占任何分量
...
}
cross kind,port{
option.weight = 10; // 给予更高的分量
}
endgroup
14 通用覆盖组
covergroup cov(int mid);
coverpoint port{
bins lo={[0:mid-1]};
bins hi={[mid:$]};
}
endgroup
cov cpa,cpb;
initial begin
cpa = new(5);
cpb = new(10);
end
15 覆盖组的注释
可以在覆盖率报告中增加注释以使报告更容易分析。
covergroup cov;
type_option.comment="section 3.2";
coverpoint port;
endgroup
16 覆盖阈值
打印空仓
默认情况下,覆盖率报告指挥给出带有采样值的仓,实际更感兴趣的是那些没有采样值的仓。cross_num_print_missing选项可以让仿真和报告工具给出所有的仓,尤其是那些没有命中的仓。
option.cross_num_print_missing = 1000;
17 覆盖率目标
一个覆盖组或覆盖点的目标是达到该组或该点被认为已经完全覆盖的水平,默认情况是100%的覆盖率
option.goal = 90; // 只需要部分覆盖率即可
18 仿真过程进行覆盖率的统计
从全局层面上,使用$get_coverage()可以得到所有覆盖组的总覆盖率,会返回一个0-100的实数。
例子:
covergroup a;
type_option.weight = 1;
endgroup
covergroup b;
type_option.weight = 1;
endgroup
initial begin
a a_inst = new()
b b_inst = new();
#1000;
end
// 总覆盖率($get_coverage()) = (a_inst_score*a_weight + b_inst_score*b_weight)/2
get_coverage()用于一个覆盖组所有实例的覆盖率,用法为:
cov:get_coverage()
get_inst_coverage() 返回一个特定覆盖组实例的覆盖率,用法为:
cgInst.get_inst_coverage();
如果只希望得到单个实例的覆盖率,需要指定option.per_instance=1