系统理论
1、 验证任务和目标
- 模块级(module)-> 子系统级(subsystem)-> 系统级(chip)
- 按时 保质 低耗
2、验证的周期
- 创建验证计划
- 开发验证环境
- 给测试激励
- 当给到一定数量的激励测试的时候,就可以准备回归测试了,就是将已有的所有测试序列都执行一次
- 流片后要逃逸分析
3、功能描述文档
- 系统工程师写的,里面包括了
- 接口信息:如果是标准接口就不需要写的太详细,如果是自定义的就要详细写出时序
- 结构信息:将模块进一步细分为各个功能组件,以及包含组件之间的逻辑关系
- 交互信息:模块会被集成到更高一级的子系统中,所以在文档之中,要包含模块交互的示意图,在必要的时候也要给出交互信号之间的准确时序
4、验证的方法
- 动态仿真(dynamic simulation)
- 通过测试序列和激励生成器给如待验设计适当的激励,判断输出是否符合预期
- 要配合仿真波形
- 进一步划分为
- 定向测试(directed test)
- 随机测试 (random test)
- 参考模型检查 (reference model check)
- 断言检查 (assertion check)
- 静态检查(formal check)
- 通过工具辅助,发现设计中存在的问题
- 进一步划分
- 语法检查(syntax check)
- 语义检查(linting check)
- 在设计的可行性上面做深入检查
- 可能产生的X值以及受其影响的设计部分
- 跨时钟域检查(CDC,Cross-clock Domain Check)
- 形式验证(formal verification)
- 等价检查(EC, Equivalent Check):用来保证两个电路的行为是等价的,用来检查不同抽象级的电路是否一致,如RTL和网表
- 属性检查(PC, Property Check)又称为模型检查(MC,Model Check)。电路的行为通过验证语言来描述其属性(property),随后通过静态方式来证明在所有状态空间下都满足该条件,否则举出反例(counter example)来证明设计行为不符合属性描述(property description)
- 虚拟模型(virtual prototype)
- 建模准备
- 芯片定义
- 高级别结构
- 用户用例
- RTL模块
- 虚拟模型
- 将功能表述转换为System C模型
- 将RTL转换为System C模型
- 结构和性能分析
- 平台开发
- 虚拟平台
- 结构、性能以及功耗报告
- 驱动和固件开发
- OS、中间件和应用层开发
- 反馈给硬件设计
- 建模准备
- 硬件加速(hardware acceleration)
- 电源功耗(power consumption)
- 功能验证(PA, Power Aware):主要包括有UPF(Unified Power Format)或者是CPF(Comment Power Format),通过与仿真器结合,模拟电源域的开关进行设计检查
- 功能预测与优化:通过第三方功耗分析工具,结合仿真数据(FSDB/VCD/SAIF),进行功耗预测,并且给出分析结果
- 性能评估(performance evaluation):还是一个新颖的概念,没有统一的标准
- 负载测试(load testing)
- 压力测试(stress testing)
- 浸泡测试(soak testing)
- 尖峰冲击测试(spike testing)
- 配置测试(configuration testing)
- 隔断测试(isolation testing)
SV语言
1、数据类型
-
verilog中有寄存器类型(reg)和线网类型(wire)
-
sv中
-
四值逻辑:integer(默认是x)、logic、reg、net-type
-
二值逻辑:byte、shortint、int(默认是0)、longint、bit
-
有符号类型:byte、shortint、int、longint、integer
-
无符号:bit、logic、reg、net-type
-
-
在SV的世界里面,期望将硬件的世界和软件的世界分开,软件的世界里面更多的是二值逻辑,硬件的多是四值逻辑
-
显示的类型转换T’(),T是你的目标类型,加上‘;编译的时候做检查
-
$cast(T, S)类型转换; 仿真的时候做检查
-
逻辑数值类型,符号类型,位宽
2、定宽数组
-
初始化,内存空间不是连续的,初始化需要用`,如果是连续的,就不需要
-
int a[4] =`{1, 2, 3, 4};
-
int a[4]; a = `{1, 2, 3, 4}; 设置初始值
-
int a[4]; a[0:2] = `{1, 2, 3};前三个有初始值
-
int a; a = {4{8}};4个值都设置为8
-
int a; a = `{1, default :-1};
-
合并和非合并
- 位宽写在变量名右边,是非合并的,写在左边的是合并的
- 分开的情况下,高纬度的永远在右边,合起来写的话,高位在左边
-
赋值和比较
- ==,就可以直接比较里面的所有元素了
-
3、for和foreach
-
initial begin bit [31:0] src[5], dst[5]; //这个地方就是32位是连在一起的,5个32是分开的,看清位宽写在哪里 for (int i = 0; i < $size(src); i++) //size(src, 1), 这个地方的1,是默认的,就是最高位 src[i] = i; foreach (dst[j]) //这个地方写法就是这样的 dst[j] = src[j] * 2; end
4、动态数组
-
int dyn[], d2[];//声明动态数组,都是非合并的 initial begin dyn = new[5];//分配5个元素 foreach(dyn[j]) dyn[j] = j; //元素初始化 d2 = dyn;//赋值动态数组 d2[0] = 5; dyn = new[20](dyn) //分配20个数值并进行复制, 前5个是原来的值 dyn = new[100] //重新分配100个数值,而旧值不存在,默认的值是0 dyn.delete//删除所有元素 end
5、队列
-
int j = 1, q2[$] = {3, 4}; q[$] = {1, 3, 4}; //因为内存空间是连续的,所以不用`来赋值 initial begin q.insert(1, j);//在3前面插入j,这里的j就是1{1, 1, 3, 4} q.insert(3, q2);// {1, 1, 3, 3, 4, 4} q.delete(1);//删除第一个元素 q.push_front(6);//头插入 j = q.pop_back();//尾取 q.push_back(8);//尾插入 j = q.pop_front();//头取 foreach (q[j]) $display(q[j]); q.delete();//删除整个队列 end
6、关联数组
-
bit [63:0] assoc[int], idx = 1; repeat (64) begin //对稀疏分布的元素进行初始化 assoc[idx] = idx; idx = idx << 1; end foreach (assoc[i]) $display("assoc[%h] = %h", i, assoc[i]); if (assoc.first(idx)) begin do $display("assoc[%h] = %h", idx, assoc[idx]); while(assoc.next(idx)); end assoc.first(idx); assoc.delete(idx);
7、结构体struct
-
和c语言是一样的,没有typedef的话,就是匿名的结构体,有的话,就有对应的类型名字
-
typedef struct {bit [7:0] r, g, b;} pixel_s; pixel_s my_pixel;
-
-
可以指定为packed,没有的话默认是unpacked
8、枚举类型
-
enum枚举,默认的枚举类型是int,即32位的二值逻辑数据类型(也可以是四值类型的),为了更加准确的描述硬件,sv允许指明枚举类型的数据类型
-
enum bit {one, two, three} you; enum logic [2:0] {one = 3'h1, two=3'h2, three=3'h2} state;
-
9、字符串类型
-
string s; initial begin s = "IEEE "; $display(s.getc(0)); //"I" $display(s.tolower()); //ieee s.putc(s.len() -1, "-"); //空格变为- s = {s, "P1800"};//拼接 $display(s.substr(2, 5)) my_log($sformatf("%s %5d", s, 42)); end task my_log(string message); $display("@%0t: %s", $time, message) endtask
10、硬件过程块
-
salways是描述硬件的行为的, always中不可以初始化变量,初始化是软件的概念,是initial中的
-
initial和always是并行执行的,是为了测试而生的,相当于是{}
-
function int double(input a)//可以和c++一样给默认值,a = 0,这里写了input,没写也是input默认是输入,所以如果是输出的话要标出来 return 2*a; endfunction //纯粹的数字或者是逻辑运算
-
task mytask(output logic [31:0] x, input logic y) xxx endtask //task内置了耗时语句,function中没有,task可能要等待什么事件的发生
-
只有task可以调用task,其他的不行,就是因为task是耗时的,function是及时的,所以在一个实时的系统中调用一个耗时的东西是不合逻辑的
-
生命周期automatic动态和static静态,默认的function类型是static的
-
//局部就是动态的,function里面的变量是动态的,function调用完了就消亡了 function automatic int auto_cnt(input a) int cnt = 0; cnt += a; return cnt; endfunction function static int static_cnt(input a)//这里是不会消亡的 static int cnt = 0; cnt += a; return cnt; endfunction function int def_cnt(input a) //默认的函数的是static的 static int cnt = 0; cnt += a; return cnt; endfunction
-
11、接口
-
interface arb_if(input bit clk); logic [1:0] grant, request; logic rst; endinterface module arb(arb_if arbif); .. always @(posedge arbif.clk or posedge arbif.rst) begin end endmodule
-
接口中的clocking
-
clocking bus @(posedge clock1) default input #10ns output #2ns //输入信号前10ns采样,输出信号2ns后采样(默认是上升沿)除非自己声明了 input data, ready, enable; output negedge ack; input #1step addr; endclocking
-
12、系统函数的调用
- $stop()仿真暂停了
- $finish()仿真结束了
- $exit()退出
13、pragram隐式结束
-
program pgml; initial begin : procl xxx end endprogram
-
如果program里面有forever的语句,不能够自己隐式结束,那么就需要自己调用$exit()来显式结束了
-
program可以看做是软件的领地,所以硬件里面的always,module和interface都不要出现
14、打印信息
- d i s p l a y ( 消息级别 ) 、 display(消息级别)、 display(消息级别)、warning(警告级别) 、 e r r o r ( 错误级别 ) 、 error(错误级别)、 error(错误级别)、fatal(严重级别)
- %x(十六进制)、%d(十进制)、%b(二进制)、%s(字符串)、%t(时间)
15、breakpoint
- 设置断点,在某一行前面点了一下,就是设置断点,不是说运行完了这一行停下来,而是到了这一行就停了
16、类
-
类的创建
-
可以自定义构造函数(constructor)
-
class Transaction; logic [31:0] addr, crc, data[8]; function new(); //没有返回值,没有写的,就是空的而已,也是允许的,返回的是指针,和c里面一样 addr = 3; foreach (data[i]) data[i] = 5; endfunction endclass
-
new()这个地方也可以接受参数
-
SV对于对象是自动销毁的,所以一个对象的句柄一定不能都丢失,就是不要造成指向null
-
class的声明中变量其默认类型是动态变量,其声明周期是在仿真开始后的某时间点开始到某时间点结束。具体来讲,其声明周期始于对象创建,终于对象销毁
-
如果用static来声明class内的变量,那么静态变量的声明开始于编译阶段,贯穿于整个仿真阶段
-
声明静态变量,那么可以用class::var来直接引用,就和c++中一样的
-
同时也可以用static声明一个静态方法,可以在静态方法中使用静态变量,但是不可以使用动态变量,因为静态的东西在编译阶段就存在了,但是动态的成员空间只有在使用的时候才会为其开辟空间,所以不可以调用的
-
-
类的成员
- 类可以通过protected和local等关键字来设置成员变量和成员方法的外部访问权限。所以封装属性在设计模式中称之为开放封闭原则(OCP,Open Closed Principle)
- 如果没有指定访问类型,那么成员的默认类型是public的,子类和外部都是可以访问的
- 如果指明了访问类型是protected,那么只有该类或者子类可以访问成员,外部无法访问
- 如果指明了访问类型是local,那么只有该类可以访问成员,子类和外部均无法访问
- 类和结构体的区别,类在声明之后需要调用构造函数才会构建对象实体,二struct在变量声明时已经开辟了内存,类出了声明变量成员,还可以声明方法(function/task),而struct则不能
- 所以的盒子里面都可以定义类
-
类的继承
-
class cat; bit xx; function ; xxx endfunction endclass class black_cat extends cat; function new(); this.color = black; endfunction endclass
-
如果是信号要加上virtual
-
通过super来索引父类的同名函数
-
有同名的变量名或者是方法的时候,以子类的作用域为准,同时可以通过super来调用父类的变量或者是方法
-
17、包
-
之前的东西都封装到一个大的房子下面,就相当于是namespace
-
package regs_pkg; `include "stimulator.sv" //原地展开 `include "monitor.sv" endpackage //调用 module mcdf_tb; regs_pkg::monitor mon2 = new(); endmodule //也可以import packagename,就和c++中的namespace一样的,using namespace
18、随机约束和分布
-
要随机什么
- 器件配置:寄存器和系统信号
- 环境配置:随机化验证环境,例如合理的时钟和外部反馈信号
- 原始输入数据:数据包的长度、带宽、数据间的顺序
- 延时:握手信号之间的时序关系
- 协议异常:如果反馈信号出现异常,那么设计是否可以保持后续数据处理的稳定性
-
声明随机变量的类
-
class Packet; rand bit [31:0] src; //整个是随机的,一幅牌54张,每次都是1/54 randc bit [7:0] kind; //这是每次抽了牌之后,分母减一 rand bit [7:0] low; constraint c {src > 10; src < 15;} endclass Packet p; initial begin p = new(); p.randomize() //随机,这个地方可以传递变量的,如randomize(low),即使是没有被rand修饰也行 end
-
class data; rand bit [2:0] month; rand bit [4:0] day; rand int year; constranit c_date { month inside {[1:12]}; //inside 表示遂于某一个集合 day inside {[1:31]}; year inside {[0:12], [2010:2030]}; } endclass
-
rand int src, dst; constraint c_dist { src dist {0:=40, [1:3] :=60}// := 每一份都是40, :/ 总共是那么多 dst dist {0:/40, [1:3]:/60} }
-
//使用$来指定最大值和最小值 rand bit [6:0] b; //0 <= b <= 127 rand bit [5:0] e; //0 <= e <= 63 constraint c_range { b inside {[$:4], [20:$]}; e inside {[$:2], [29:$]}; }
-
//通过-> 或者是if-else类让一个约束表达式在特定时刻生效 constraint c_io { (io_space_mode) -> addr[31] == 1'b1; if (op == 1'b1) len inside {[1:32]}; else len == 1'b1; } classname.c_io.constraint_mode(0)//关闭c_io这个约束块 classname.randomize() with {addr >= 50; addr <= 1500;} //通过randomize() with来增加额外的约束
-
-
随机函数
- 如果需要在randmoize()之前或者是之后立即执行一些操作,SV提供了两个预定义的void类型函数pre_randmoize()和post_randmoize()
- $random()平均分布,返回32位有符号随机数
- $urandom()平均分布,返回32位无符号随机数
- $urandom_range()在指定范围内的平均分布
-
数组的约束
-
class dyn_size; rand logic [31:0] d[]; constraint d_size {d.size() inside {[1:10]};} //还有sum(), product(), and(), or(), xor() endclass
-
class good_sum5; rand uint len[]; constraint len_value {foreach(len[i]) len[i] inside {[1:19]};} endclass
-
//初始化数组来产生唯一元素值 class randc8; randc bit [7:0] va; endclass class LittleUniqueArrya; bit [7:0] ua[64]; function void pre_randomize(); randc rc8; //放在外面定义 foreach(ua[i]) begin assert(rc8.randomize()) ua[i] = rc8.val; end endfunction endclass
-
随机句柄数组的声明一定要加上rand,在里面创建对象
-
-
随机序列
-
randsequence : 产生事务序列
-
initial begin for (int i = 0; i < 15; i++)begin randsequence (stream) stream : cfg_read :=1 | io_read :=2 | men_read := 5; cfg_read : {cfg_read_task;} | {cfg_read_task;} cfg_read; men_read : {men_read;} | {men_read_task;} men_read; io_read : {io_read_task;} | {io_read_task;} io_read; endsequence end end
-
-
randcase : 建立决策树
-
initial begin int len; randcase 1 : len = $urandom_range(0, 2); // 10%:0 1 or 2 8 : len = $urandom_range(3, 5); // 80%:3 4 or 5 1 : len = $urandom_range(6, 7); // 10%: 6 or 7 endcase $display("len = %0d", len); end
-
randsequence 和randcase是针对轻量级的随机控制的应用,而我们可以通过定义随机类取代上述随机控制的功能,并且由于类的继承使得在后期维护代码时更加方便
-
randsequence的相关功能我们在协调激励组件和测试用例时,可能会用到
-
randcase则对应着随机约束中的dist权重约束+ if-else条件约束的组合
-
-
19、线程
-
线程的使用
- 如果按照软件的思维来说,各个模块(module)就是独立运行的线程(thread),模块(线程)在仿真一开始的时候就开始并行执行了,除了每个线程会按照在自己内部产生的事件来触发过程语句块之外,也同时依靠相邻模块之间的信号变化来完成模块之间的线程同步。
-
什么是线程
- 线程就是独立运行的程序
- 线程需要被触发,可以结束或者不结束
- 在module中的initial和always,都可以看做独立的线程,他们会在仿真0时刻开始,而选择结束或者是不结束
- 硬件中大多都是always语句,可以看作是独立的线程,而这些线程会一直占用仿真资源,因此他们不会结束
- 软件平台中的验证环境都需要由initial语句块去创建,而在仿真过程中,验证环境中的对象可以创建和销毁,因此在软件测试中的资源占用是动态的
- begin end 和 fork join
- begin end是顺序执行的 ,而fork join是并发的
- fork join (所有的执行完了,才会退出)还包括了 fork join_any(任意一个执行完了就会退出)和fork join_none(直接就退出了)
- 线程的执行是呈现树状结构的,就是任何的线程都由自己的父线程
- 父线程可以开辟若干个子线程,父线程可以暂停或者终止子线程
- 子线程终止的时候,父线程可以继续执行
- 父线程终止的时候,其所开辟的所有子线程都应当会终止
-
线程的控制
-
在sv,当程序中的initial块全部执行完毕了,仿真器就退出了
-
如果我们希望等待fork块中的所有线程执行完毕在退出,可以使用wait fork
-
fork //线程1 //线程2 //线程3 join_none wait fork
-
-
disable 结束需要停止的线程
-
fork : timeout_block begin wait(bus.cb.addr == tr.addr); $display("@%0t: Addr match %d", $time, tr.addr); end #TIME_OUT $display("@%0t: ERROR: timeout", $time); join_any disable timeout_block; end join_none;
-
disable fork 可以停止当前线程中衍生出来的所有子线程,可以给fork命名,这样就可以明确的disable它了
-
-
-
线程间的通信(IPC, Interprocess Communication)
-
verilog中,一个线程总是要等待一个带@操作符的时间,这个操作符是边沿敏感的,所以它总是阻塞着,等待事件的变化
-
其他的线程可以通过->操作符来触发事件,结束对第一个线程阻塞
-
用triggered()更加安全
-
-> e1; wait (e2.triggered()); -> e2; wait(e1.triggered()); //只要被触发过了,就不会引起阻塞
-
线程之间的执行需要按照一定的顺序
-
对于线程多次通知的需求,应该使用@,不能在使用wait(event.triggered())。这个是由于event被触发之后,它的状态会使得被触发事件的状态一直是1‘b1,而且没有方法可以清楚这个状态
-
-
semaphore旗语
-
可以实现对统一资源的访问控制
-
对于初学者而言,无论线程之间在共享什么资源,都应该使用semaphore等资源访问控制的手段
-
有三个基本操作
-
new() : 创建一个或者是多个钥匙
-
get() : 获取一个或者是多个钥匙
-
put() : 可以返回一个或者是多个钥匙
-
program automatic test(bus_ifc.TB bus); semaphore sem; initial begin sem = new(1); //分配一个钥匙 fork sequencer(); //产生两个总线事务 sequencer(); join end task squencer; repeat($urandom %10) @bus.cb; //等待0-9个周期 sendTrans(); //执行总线事务 endtask task sendTrans; sem.get(1); //获取总线钥匙 @bus.cb; //信号驱动到总线上 bus.cb.addr <= t.addr; xxx sem.put(1); //放回钥匙 endtask endprogram
-
线程之间的资源共享要遵循互斥访问(mutex access)原则
-
-
-
-
mailbox信箱
-
传递信息的
-
和queue有相近之处
-
是一种对象,也需要用new()来例化,例化时可以选择一个参数size来限定其存储的最大量,如果size=0或者是没有指定,那么信箱无限大的。
-
put往信箱里面放东西,get从信箱里面取东西
-
如果满了,put会阻塞,如果空了,get会阻塞
-
peek可以从信箱里面拷贝数据,而不移除它
-
program automatic bounded; mailbox mbx; initial begin mbx = new(1); fork for (int i = 1; i < 4 ; i++) begin $display("Producer: before put(%0d)", i); mbx.put(i); $display("Producer: after put(%0d)", i); end repeat(4) begin int j; #1ns mbx.get(j); $display("Cosumer: after get(%0d)", j); end join end endprogram
-
mailbox必须通过new,而队列只需要声明
-
mailbox可以将不同的数据类型存储,不过不建议这么做,对于队列来讲,内部存储的元素必须是一致的
-
mailbox的存取方法put和get是阻塞的,就是它们不一定是立即返回的,而队列的方法是立即返回的wait(queue.size()>0)
-
try_put/try_get/try_peek非阻塞方法
-
mailbox #(int) 指定里面存的是int类型
-
20、覆盖
-
覆盖率反馈回路
- 可以使用一个反馈回路来分析覆盖率的结果,并决定采取哪一个行动来达到100%的覆盖率
- 首先的选择是使用更多的种子来运行现有的测试程序
- 当大量的种子依然对于覆盖率增长没有帮助的时候,需要建立新的约束
- 只有在确实需要的时候才会求助于创建定向的测试
-
代码覆盖率
- 行覆盖率:多少行被执行过了
- 路径覆盖率:在穿过代码和表达式的路径中有哪些被执行过了
- 翻转覆盖率: 哪些单位比特变量的值为0或者1
- 状态机覆盖率: 状态机哪些状态和状态转换已经被访问过了
- 断言覆盖率:
- 一当检测到问题,仿真就直接结束了
-
覆盖组(covergroup)
-
一个covergroup可以有多个coverpoint,且全部在同一时间采集
-
可以定义在类里面,也可以在interface或者module中
-
一个类里面可以有多个covergroup,必须要被例化了才可以使用
-
class Transactor; Transaction tr; mailbox mbx_in; covergroup CovPort; // 也可以写成CovPort @(trans_ready)等待事件的触发 coverpoint tr.port; endgroup function new(mailbox mbx_in); CovPort = new(); this.mbx_in = mbx_in; endfunction task main; forever begin tr = mbx_in.get; ifc.cb.port <= tr.port; ifc.cb.data <= tr.data; CovPort.sample(); end endtask endclass
-
与sample()相比,使用事件触发的好处在于你可以借助于已有的事件
-
-
-
coverpoint
-
当你在coverpoint中指定采样一个变量或者表达式的时候,SV会创建很多的“仓bin”来记录每个数值被捕捉到的次数
-
这些bin是衡量功能覆盖率的基本单位
-
covergroup可以定义多个coverpoint,coverpoint中可以自定义多个cover bin或者SV帮助自动定义多个cover bin
-
当covergroup采样的时候,SV都会在一个或者多个cover bin中留下标记,用来采样时变量的数值和匹配的cover bin。
-
bin
-
为了计算这个coverpoint上面的覆盖率,首先需要确定的可能数值的个数,被称为域
-
覆盖率就是采样值除以bin的数目。如一个3bit的变量的域就是0:7,正常情况下就会被分配8个域,如果仿真的过程中有7个值被采样到了,那么覆盖率就是7/8
-
SV会默认为coverpoint创建bin,用户也可以自己定义bin
-
如果没有自己指定bin,域的范围又太大了,就会默认64个bin,平均分为这个域
-
用户可以通过covergroup的选项auto_bin_max来指定自动创建的bin的最大数目
-
建议自己指定bin
-
covergroup CovPort; options.auto_bin_max = 8; //所有的coverpoint auto_bin 都是8 coverpoint tr.point { options.auto_bin_max = 2;} //特定的coverpoint auto_bin = 2,中括号外面没有分号了 endgroup
-
covergroup CovKind; coverpoint tr.kind { bins zero = {0}; // 一个仓代表kind == 0 bins lo = {[1:3], 5}; //一个代表1:3和5 bins hi[] = {[8:$]}; //8个仓代表8:15 bins misc = default; //剩下的值 }//没有分号 endgroup
-
-
-
iff条件覆盖
-
用关键字iff来给coverpoint来添加条件
-
常用于复位期间关闭覆盖以忽略不合理的条件触发
-
也可以用start和stop函数来控制covergroup各个独立实例
-
covergroup CoverPort; coverpoint port iff (!bus_if.reset); endgroup initial begin CovPort ck = new(); #1ns; ck.stop(); #100ns bus_if.reset = 0; ck.start(); end
-
//记录翻转次数 covergroup CoverPort; coverpoint port { bins t1 = (0 => 1), (0 => 2), (0 => 3); } endgroup
-
bit [2:0] port; covergroup CovPort; coverpoint port { wildcard bins even = {3'b??0}; wildcard bins odd = {3'b??1}; } endgroup //用关键字来创建多个状态或者是翻转 //任何x、z或者是?都会被当成是0或者1的通配符
-
bit [2:0] low_ports_0_5; covergroup CovPort; coverpoint low_port_0_5 { ignore_bins hi = {[6, 7]}; //忽略6,7,不记录覆盖率当中 illegal_bins hi = {[6, 7]}; //如果出现6和7就报错 } endgroup
-
class Transaction; rand bit [3:0] kind; rand bit [3:0] port; endclass Transaction Tr; covergroup CovPort; kind : coverpoint tr.kind; port : coverpoint tr.port; cross kind, port; //记录多个变量之间的组合情况 endgroup
-
covergroup Covport; port : coverpoint tr.port { bins port[] = {[0:$]}; } kind : coverpoint tr.kind { bins zero = {0}; bins lo = {[1:3]}; bins hi = {[8:$]}; bins misc = default; } cross port, kind { ignore_bins hi = binsof(port) intersect {7}; ignore_bins md = binsof(port) intersect {0} && binsof(kind) intersect {[9:11]}; ignore_bins lo = binsof(kind.lo); } endgroup
-
class Transaction; rand bit a, b; endclass covergroup CrossBinNames; a : coverpoint tr.a { bins a0 = {0}; bins a1 = {1}; option.weight = 0; //不计算覆盖率 } b : coverpoint tr.b { bins b0 = {0}; bins b1 = {1}; option.weight = 0; } ab : cross a, b { bins a0b0 = binsof(a.a0) && binsof(b.b0); bins a1b0 = binsof(a.a1) && binsof(b.b0); bins b1 = binsof(b.b1); } endgroup
-
class Transaction; rand bit a, b; endclass covergroup CrossBinsofIntersect; a : coverpoint tr.a { option.weight = 0; } this coverpoint b : coverpoint tr.b { option.weight = 0; } this coverpoint ab : cross a, b { bins a0b0 = binsof(a) intersect{0} && binsof(b) intersect {0}; bins a1b1 = binsof(a) intersect{1} && binsof(b) intersect {1}; bins b1 = binsof(b) intersect{1}; }
-
//如果对一个covergroup例化了多次,那么默认的情况下SV会将所有实例的覆盖率合并到一起,如果需要单独列出每一个covergroup的覆盖率,需要设置参数 covergroup CoverLength; coverpoint tr.length; option.per_instance = 1; endgroup
-
//注释 covergroup CoverPort (int lo, hi, string comment); option.comment = comment; option.per_instacne = 1; coverpoint port { bins range = {[lo:hi]}; } endgroup CoverPort cp_lo = new(0, 3, "LOW PORT NUMBERS"); ;overPort cp_hi = new(4, 7, "HIGH PORT NUMBERS");
-
默认的情况下,数值采样了1次就可以计入有效的bin,可以通过修改at_lease来修改每一个bin的数值最少采样次数,如果低于这个次数,那么就不会被计入bin中
-
option.at_least可以在covergroup中来声明来影响所有的coverpoint,也可以在coverpoint值影响整个point里面的bin
-
covergroup CovPort; coverpoint port; option.goal = 90; endgroup //目标的覆盖率,默认是100
-
sample();//采样 get_coverage()/get_inst_coverage();//获取覆盖率,0-100的real数值 set_inst_name(string)//设置covergroup的名称 start()/stop()//开始或者是关闭采样 $get_coverage()//获取总体的覆盖率
-
21、类的转换
- 类的转换分为静态转换和动态转换
- 隐式转换:如赋值语句的右边是4位的bit,左侧是5位的bit,隐式转换就会先做位宽拓展,然后在赋值
- 动态转换:当我们使用类句柄的向下转换,即从父类句柄转换位子类句柄的时候,需要进行动态的转换,否则就会出现编译错误
- 如果将子类句柄赋值给了父类句柄时候,编译器会认为是合法的(金字塔结构),但是当用子类句柄和父类句柄调用相同对象的成员的时候,可能会出现不同的表现。
- 父类给子类先进行cast
22、虚方法
-
class basic_test; int fin; int def = 10; function new(); $display("basic_test: new"); endfunction task test(); $display("basic_test:: test"); endtask endclass class test_wr extends basic_test; int def = 20; function new(); super.new(); $display("test_wr::new"); endfunction task test(); super.test(); $display("test_wr::test"); endtask endclass //355页
-
子类覆盖(override)的方法并不会继承父类同名的方法,只有通过super.method()才会显示的调用
-
虚方法定义之后,系统会在执行程序的时候,检查句柄指向的是什么对象,去调用什么函数
-
如果父类中的方法会被覆盖,就应该要被声明为virtual,尽量在底层的父类声明
23、对象的拷贝
-
test_wr a, b; a = new(); a = b; //这个是句柄的赋值
-
想要拷贝对象,需要先new,然后挨个赋值
-
virtual funciton void cop_data(basic_test t); t.def = def; t.fin = fin; endfunction virtual function basic_test copy(); basic_test a = new(); copy_data(a); return a;
-
class mailbox #(type T = int); //这里默认的T就是int,调用的时候可以改的 local T = queue;
UVM
1、类库地图
- 组件的创建和访问
- 环境的结构创建、组件之间的连接和运行
- 不同阶段的顺序安排
- 激励的生成、传递和控制
- 测试的报告机制
2、UVM分类
- 核心基类
- 工厂(factory)类
- 事务(tansaction)和序列类
- 结构创建(structure creation)类
- 环境结构(environment component)类
- 通信管道(channel)类
- 信息报告(message report)类
- 寄存器模型(register model)类
- 线程同步(thread synchronization)类
- 事务接口(transaction interface)类
3、工厂机制
-
验证环境分为uvm_component(环境层次)和uvm_object(环境属性和数据传输)
-
component继承于object
-
要先注册,在创建
-
验证环境
- generator
- stimulator
- monitor
- agenet
- checker/reference model
- environment
- test
-
com_type::type_id::create(string name, uvm_component parent); //uvm_component com_type::type_id::create(string name) //uvm_object
-
class comp1 extends uvm_component; `uvm_component_utils(comp1) function new(string name = "comp1", uvm_component parent = null); super.new(name, parent); $display($sformatf("%s is created", name)); endfunction : new function void build_phase(uvm_phase phase); super.build_phase(phase); endfunction : build_phase endclass class obj1 extends uvm_object; `uvm_object_utils(obj1); function new(string name = "obj1"); super.new(name); $display($sformatf("%s is created", name)); endfunction : new endclass comp1 c1, c2; obj1 o1, o2; initial begin c1 = new("c1"); o1 = new("o1"); c2 = comp1::type_id::create("c2", null); o2 = obj1:;type_id::create("o2"); end
心得
- 在设计或者是验证的过程首先要遵守的就是top to down的思想,将一个大的系统分成一个个的模块,在具体实现的过程中要遵守的是down to top的思想,就是将一个个的模块设计好,实现了在拼接成一个系统。