具体视频教程B站链接,点击可以观看
验证工作:
设计团队和验证团队会根据功能需求做各自相应的计划,design plan,verification plan;同步进行
testbench工作量非常大
DUT即被测对象
本章目标
1、描述DUT功能
2、确定DUT的信号
3、画出时序图
下图是一个DUT的例子,描述的是一个点对点,无缓冲的16入16出的路由器。
DUT内部就是简单的转发
下图是对这个DUT的描述
包括:上升沿采样输入数据,数据头,目的地,一帧数据标志,数据有效信号等
输入输出都是串行;
点对点,无缓冲;
下面是DUT的输入时序图
描述的是:帧起始信号拉低,输入数据的第一个bit。帧起始信号拉高,输入数据的最后一个bit;
valid低电平表示数据有效
下图是输出时序图
复位之后等待15个周期,确保有效复位
本章目标:
1、创建SV模板
2、编写SV测试代码
3、编译和仿真
验证只能是对某些验证完了,不可能全部都验证完,它是无底洞
验证的几个阶段
下图是整个testbench的结构图,非常有用,牢记
下面展示通过模板建立SV的测试文件
system verilog建立有一个ntb模板
1、顶层harness文件,来自DUT由模板生成
2、interfce接口文件(用于连接DUT、test program、采样监视器)
3、test program测试驱动文件(基本上方针的驱动信号来自于这里)
4、DUT文件
注:基本没人用这个模板,都是自己建立,具体请看这章的后面关于harness文件的建立
下图说明了SV接口文件的创建方法
1、module router_test_top也就是验证的顶层文件,一般使用router_tb命名。
2、ntb模板生成router_test_top文件
3、从上图的harness文件复制一份创建接口文件
4、将复制的接口文件的wire转变为logic,并把输入的时钟作为接口文件的输入时钟(logic后面数据类型有解释)
下图定义了test program的接口文件端口,将接口连接到test program驱动。
默认接口信号是异步的
同步信号放在同步时钟块
通过clocking block可以创建同步信号,通过modport连接test program;
output din代表输出,但对于DUT是输入信号
modport定义了输入输出方向
注意同步里的reset_n和异步的不一样
上图的modport传到了这里,可以看到具体interface连接test program的方式
一个test program的例子
1、通过cb.reset_n调用的是同步的reset_n;
2、#n 代表绝对延时多少时间
3、两个#代表延时多少个时钟周期
驱动同步信号
1、必须用非阻塞
2、## num代表周期
3、异步信号驱动是没有意义的,一般用于复位
采样同步信号
没有延迟
不能采输出信号din,对于testbench来看是输出,对于DUT是输入
更新仿真事件
异步信号更新和verilog一样
同步信号更新,有两种写法,
SV写法是因为cb中定义了posedge
同步信号如果使用verilog的风格,那么必须加一个额外的异步信号
创建hrness文件
一般就用router_tb表示
ntb模板生成router_test_top文件,然后复制一份之后对其修改
完整的harness文件创建
reg变成bit 二值逻辑,wire变成logic已在interface中改变
1、实例化接口,传入时钟
2、实例化test program连接interface
3、使用接口连接DUT
VCS一些命令
本章目标:
了解SV的数据类型
大小写敏感
空格忽略
数据格式:
1、2值逻辑
2、4值逻辑
二值逻辑 0 、 1
四值逻辑赋0、1、x、z,四值赋值二值就是0
字节、短整型、整型、长整型
integer 和 int的区别在于一个是四值逻辑,一个是二值逻辑
sv logic类似wire和reg
字符型
数组型
固定数组
c[2][3]二维数组
default:-1,数组元素都设置-1
[31:] a[2][3],维度分布为3,1,2;
计算dimensions(a),$size(a,i+1) ,i+1代表维度
下图
动态数组
以前关于队列的定义,是不允许在中间部位进行操作的
SV这里不同
联合数组
先声明、然后赋值,就会分配一个内存
delete释放索引号
索引可以是数字,字符串
数组的遍历
q里面元素大于三放入SQ, 另外是找到大于3的元素的索引号
seed可以控制产生随机数的序列,seed1和seed2是不同的序列
randcase控制随机值的数量
typedef是重命名
'表示强制类型转换,
-2,-1,0,1,2 是举例,不一定
运算符
第一种写法不够全面,判断的结果不唯一,x态vcs无法确定不知道该输出什么
下面的例子
task消耗时间,function是仿真0时刻完成
ref类似于C语言的指针,对应内存里面的一个存储空间,传递ref相当于传了地址
task传入值的地址,每个task之间这个值变化了都会相应变化
function
没有传地址就是拷贝了一份
function只有输入没有输出但是有返回值
声明automatic 变量 sum,那么后面的调用,sum每次都会从初始值变化
声明static变量后面调用会相互影响,它分配了固定内存,
动态变量在子程序中,每次调用都会从它的初始值开始调用,而不管他在函数中经历了什么变化;静态变量会从变化后的值继续改变。
task可以调用function
const ref,对这个值的地址const,后面改变不了这个值
声明端口时要把方向都指明清楚
代码生存周期
task里面root.n才能用testbench全局的n变量
initial begin可以加一个名字
结构体:不同数据打包
这个写的很不好,结构不清晰
interface,test,bfm叫做bus function model
SV DPI打开的原因,提供EDA软件调试
一般sv已经包含了这些c++特性,用户用不到
self -check 里面只有有效数据
不包含地址等
错误原因:同步信号用了异步驱动,同步信号要用非阻塞,驱动接口输入信号不允许
同步信号的驱动例子
采样同步信号
用cb同步信号
采集信号下降沿,更新到D
下图时间标注的位置错了
#代表延时多少时间
##代表延时多少周期
并发操作
线程
产生并发线程
fork join
fork join_any
fork join_none
共享父变量
statement0 和sta1,sta2 并行sta1,sta2之间顺序 执行
statement3运行顺序看使用哪一种 join
C是一个线程,a,b一样,D一个父线程,两个子线程,一个顺序
一个语句默认是加了一个begin end
fork join指的是statement3要执行等stament1和sta2运行完
@等同步时钟
运行ready的队列
不能工作
下面是一个类似的程序,VCS下跑一遍
放在program里编译
while里面没有时间更新,$time无法判断
#5在等待
所以while那里死循环
else #4对时间更新
结果输出仿真时间是4
同样下面那个块没有执行
公司里面的license,轮流用的话需要释放license
ctrl+z
释放license
ps可以查看到后台运行
重新获取用fg
下面这样可以
下面a,b的值
仿真代码
结果a=4,b=8
fork join里面两个并发,vcs会执行上一个,再下一个
另外这里的变量是静态的
一个例子为什么仿真时间是0
VCS代码
for循环展开,相当于很多send,
initial begin end是父线程,没有时间更新
父线程阻塞子线程
send是子线程,0时刻打印显示display
#1 进入wait状态
父线程执行到end结束了,所以#1执行不了了
所以仿真时间是0
下面的例子去掉了automatic,变成static
打印结果变化了
上面的例子运行,仿真时间还是0
代码
oop概念
未声明automtic的变量都是静态的
i++ 在0时刻 执行 更新 完了,所以都是15
下面的例子换了形式?
不推荐这样加入fork join_none
展开循环
VCS仿真输出
针对上面这个例子加一个动态变量
就变成这样了
展开循环
针对上面的例子使用wait fork父线程会等子线程
这种形式也可以
VCS输出
也可以这样
看门狗,等待某个信号,关闭所有fork线程
防止进入硬件进入某状态无法恢复
示例
disable fork会关闭当前的所有fork,所以下面第一个fork也运行不了
disable 名字可以只关闭这个名字的fork,第一个fork可以运行
testbench的调试
面向对象封装
变量,对变量的操作
在类里面是全局的
创建对象
对象的内存创建使用new()
句柄
对象内存的创建通过new();
句柄是对象的标识
句柄实际上是一种指向某种资源的指针,但与指针又有所不同:指针对应着一个数据在内存中的地址,得到了指针就可以自由地修改该数据。Windows并不希望一般程序修改其内部数据结构,因为这样太不安全。所以Windows给每个使用GlobalAlloc等函数声明的内存区域指定一个句柄(本质上仍是一个指针,但不要直接操作它),平时你只是在调用API函数时利用这个句柄来说明要操作哪段内存。当你需要对某个内存进行直接操作时,可以使用GlobalLock锁住这段内存并获得指针来直接进行操作。
对象成员
封装起来就是集成
定义了local就只能在类里给它赋值
对象之间的赋值,pkt1的内存被释放了
pkt1指向了pkt2
class里定义一个静态变量
类的对象可以共享这个变量
数据类型是类的话,数组操作都可以
声明一个队列,队列内容是句柄
声明一个类,方法只声明了原型,
具体操作放在其他文件
比如task实体放在外面,只进行了声明,类的名字加双冒号,函数属于这个类
在类里创建虚拟接口,实体接口的句柄,传到类里,通过这个虚拟接口操作实体接口。驱动和采样实体接口信号,具体后面的章节有说明
下面这个是错的
没有使用ref,那么传入test的是a的copy,而a是一个句柄,那么if条件判断为真,创建一个内存,但是这个内存里面没有val,因此执行下面的赋值报错
由于a new()已经分配了空间,所以它是一个指针,传入的是地址,因此和前面的一样
上一章的一个说明如下
在构造体new()里传递虚拟接口,实体接口的句柄,通过这个虚拟接口操作实体接口。驱动和采样实体接口信号
创建虚拟接口的步骤
1、定义一个实体接口
2、连接接口
3、通过类里的new构造器传递实体接口,并驱动或采用信号
4、虚拟接口连接实体接口
面向对象随机
为什么使用随机
验证是没有尽头的
一个约束顺序的例子
约束顺序
solve before
先生成flag再生成addr
调用随机函数的影响
pre预操作设置随机的约束
post后操作根据结果产生随机
右边为例程
下面的例子crc是根据前面的结果来的
随机序列
用于状态机的检查
alu_test:setup test;都对应了一个状态
状态机验证
这样写用的不多,一般一个个状态去写,更清晰
生成序列的多种写法
面向对象的继承
派生类
mypacket为派生类,集成packet的属性和方法,还可以增加自己的属性,运行父类的同名函数,使用super.
派生一个子类,多态
local不能在派生出的子类里用
protected可以在父子类里用,但不能在其他范围用
没有function new,或者有function new没参数。VCS会插入super new(必须要参数),但是需自己加入super new的参数,否则报错
正确写法如下
线程的内部通信
声明事件,等事件,触发事件
等待testbench覆盖率达到100%,Done完成终止仿真
Semaphores是SV的一个类,使用需要new一下,线程执行先从semaphores桶里拿出钥匙,防止多个线程同时对同一个硬件信号进行处理。
Semaphores的定义
创建一个桶,放指定的钥匙数量进去,
邮箱
mailbox,一个线程传入,一个线程接受,没有数据线程等待,
放入信息进mailbox
task put 放不进去等待
try_put拿不到就算了
task get如果拿不到等待
try_get拿不到就算了
bins的定义:状态bin,互相关bin,传送bin;
coverage也要例化
每个bins验证一段变量范围, 如sa要验证0-15的端口,看看输入输出端口有没有正常发送接收hit到
coverage建模
一般在scoreboard里来
drive产生数据包,monitor接受数据包
scoreboard比较
状态bin的命名是自动的
coverage计算