SystemVerilog Testbench Lab1: verification flow

个人认为自学最大的门槛在于:用怎样的形式来输出所学知识,以保证对所学知识的理解是深刻的。我选择写博客来记录自学过程中的疑惑和答案。

SystemVerilog Testbench Lab系列博客将作为我这段时间学习sv的输出,希望能够学会sv在验证中的使用。Synopsys的sv_lab是学习sv的入门资料(EETOP上可以搜到),这一系列的博客都将基于此展开。

了解DUT

创建测试平台之前,阅读rtl代码了解DUT。我觉得验证人员需要关注的是DUT实现了什么功能,不必过分关注实现的细节。
在Synopsys的lab中,DUT是一个路由器,简单而言其功能是:router共有16个输入端口,每个端口可以并行工作,按照destination address、paddings、data的顺序并行地发送数据;router根据路由逻辑,将输入端口的数据送到对应的输出端口。其发送数据与接收数据的时序图分别如下面两张图所示。
input port timing diagram
output port timing diagram

构建SV测试平台

interface

接口模块将test program和dut连接起来,需要定义信号的类型、信号的位宽、信号的方向等。

// interface 
interface router_io(input bit clk);
	// signals type
	logic reset_n;
	logic [15:0] frame_n;
	logic [15:0] valid_n;
	……
	logic [15:0] busy_n;
	// signals direction - synchronous to 'clk'
	clocking cb @(posedge clk);
		output reset_n;
		output frame_n;
		output valid_n;
		……
		input busy_n;
	endclocking:cb
	// signal direction - asynchronous to others
	modport TB(clocking cb, output reset_n);
endinterface:router_io
  • logic为四态逻辑,默认值为X,在sv中比reg和wire更加常用。
    • reg只能在always、initial等过程块中赋值;wire只能用assign连续赋值
    • logic既能在过程块中赋值,也能连续赋值。
  • bit为两态逻辑,默认值为0。当sv中不需要变量拥有四个状态时,则定义为bit。
  • 同步信号在clocking中定义,信号的方向在modport中定义。
    • 此处的信号方向是针对test program而言的,与dut方向相反。

test program

测试程序给定了dut需要完成的动作,在端口定义时将上述interface模块中的modport包含进来。可以理解为,test program的入口参数是一个router_io类型的interface。

// test program
program automatic test(router_io.TB rtr_io);
	initial begin
		$vcdplus;
		reset();
	end
	// concrete task
	task reset();
		rtr_io.reset_n = 1'b0;	// '=' and '<=' lead to the same results
		rtr_io.cb.frame_n <= '1;
		rtr_io.cb.valid_n <= '1;
		##2						// delay two clock cycles ???
		rtr_io.cb.reset_n <= 1'b1;
		repeat(15) @(rtr_io.cb);// repeat 15 clock cycles
	endtask:reset
endprogram:test
  • automatic关键字表明数据的生命周期是动态的。对于automatic而言,将test定义为automatic意味着其内部的所有变量默认也是automatic的(即所有变量随着test program的建立/销毁而建立/销毁)。
    • 定义module、interface、package或者program时,限定automaticstatic关键字的目的是使得在过程块中声明的变量有统一默认的生命周期。
    • 对于static类型的变量,用户在声明变量时应同时对其初始化,而且初始化只会伴随它的生命周期发生一次,并不会随着方法调用被多次初始化。
  • @(rtr_io.cb)是更新仿真时间的方式,括号里是在TB参数列表中例化的interface名和clocking名。
    • 上述语句表示更新单次时钟周期;若更新多个周期则在前面加repeat(n)n表示时钟周期数。

harness file

实际上就是顶层文件,例化上述几个模块,构成一个完整的测试平台。test program端口的输入信号来自interface,dut的端口信号定义成interface相关的hierarchy形式。

// harness file
`timescale 1ns/1ps
module router_test_top;
	parameter simulation_cycle=100;	// clock cycle
	bit SystemClock;
	router_io top_io(SystemClock);	// intansitate interface
	test t(top_io); 				// add program
	router dut(						// intansitate dut 
		.clock(top_io.clk),
		.reset_n(top_io.reset_n),
		……
		.busy_n(top_io.busy_n)
		);
	initial begin
		$timeformat(-9, 1, "ns", 10);
		SystemClock = 0;
		……
	end
endmodule
  • 底层中分别例化interface、test program、dut,并且通过interface连接起来。
  • $timeformat是sv的内部函数,定义仿真时间。其中的4个参数分别是单位、精度、显示后缀、显示位宽,详见link

一些疑惑

  • ##2是sv中的延时语句,##运算符在testbench中用于延迟特定时钟沿或时钟周期单位。通过仿真验证此处的作用是等第2个时钟上升沿到来。
  • 图1是时钟SystemClock初始值为0的仿真波形,reset_n等待了1.5个clock cycle;图2是时钟SystemClock初始值为1的仿真波形,reset_n等待了2个clock cycle。
  • 注意:未点亮的信号为interface中时钟块内的信号(图上半部分),点亮的信号为interface的端口信号(图下半部分)。
	clocking cb @(posedge clk); // above 
		output reset_n;
		output frame_n;
		output valid_n;
		……
	endclocking:cb
	……						// below 
	logic reset_n;			
	logic [15:0] frame_n;	// unknown at initial state in Fig1
	logic [15:0] valid_n;
	……

图1
图2

  • 对比两张图中的frame_n和valid_n信号,发现interface中时钟块内的信号立即有效,因为task在仿真0时刻时直接给时钟块内的信号赋值;而interface端口的信号有效时刻与时钟上升沿同步,行为类似于在时钟上升沿传递信号值。
  • reset_ncb.reset_n的关系是怎样的?
  • 显而易见的是,reset_n定义在时钟块之外,与clk是异步的;而cb.reset_n定义在时钟块之内,与clk是同步的。在原task的基础上,尝试将task中的reset_n改成cb.reset_n,得到以下编译报错:时钟块中的信号使用非法的阻塞赋值。
	……
	rtr_io.reset_n = 1'b0;	->	rtr_io.cb.reset_n = 1'b0;
	……
Error: Clocking block output rtr_io.cb.reset_n is illeagally driven by a blocking assignment.
  • 根据提示修改阻塞赋值为非阻塞赋值,得到的波形如下:可以看出若时钟块外的reset_n无赋值,则cb.reset_n在时钟上升沿将信号值传递给reset_n。frame_n和valid_n信号的赋值情况与之类似。
	……
	rtr_io.reset_n = 1'b0;	->	rtr_io.cb.reset_n <= 1'b0;
	……

图3

  • 在原task的基础上,将##2延时语句注释,仿真波形如下:时钟上升沿到来之前reset_n为0,时钟上升沿到来之后reset_n为1,即cb.reset_n在时钟上升沿将信号值传递给reset_n。因而可以认为:若cb.reset_n为未知值,则reset_n保持异步值;否则,reset_n在时钟有效沿到来时寄存cd.reset_n的值(此时是同步的)。
	……
		##2		->		// ##2 
	……

图4

  • 在原task的基础上,将##2延时语句、cb.reset_n信号拉高语句注释,得到如下的仿真波形:cb.reset_n一直是未知值,reset_n也一直保持异步值0,印证了上述观点。
	……
		##2		->		// ##2						
		rtr_io.cb.reset_n <= 1'b1;	->	// rtr_io.cb.reset_n <= 1'b1;
	……	

图5

  • 6
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值