SystemVerilog Testbench Lab3: self-checking

Tenstbench的基本组件包括generator、driver、monitor、checker。组件自底向上可以分为3个层次:

  • signal layer
    • driver:将抽象层次的数据变成带有时序的信号,送到DUT端口
    • monitor:将带有时序信息的端口信号转换成抽象的数据,送到上层处理
  • encapsulation layer
    • generator:产生灌入DUT的激励,如随机激励、带约束的激励
    • reference model:以更抽象的方式实现与DUT相同的功能
    • checker:比较RM与DUT的输出,常用scoreboard来统计比较结果
  • function layer
    • testcase:各种可能场景的测试用例,可以认为其层次在testbench之上

Lab3关注的是上述组件中的monitor和checker,在Lab2的基础上通过自顶向下的方式完善整个testbench。

Top-level test env

Top-level的initial块调用所有待完成的任务,包括前面两个lab中的reset()、gen()、send(),新增recv()check()。这里要注意的是,send()recv()是并发执行的进程。

	……
	logic [7:0] pkt2cmp_payload[$];
	initial begin
		reset();
		repeat(run_for_n_packets) begin
			gen();
			fork			// send and recv are parallel process
				send();
				recv();
			join
			check();		// compare results of recv and gen
		end
	end
	……

Monitor

recv transactor

send()类似,recv()也是transactor,即不直接与DUT通信,而是调用monitor来收集数据。

	task recv();
		get_payload();	// call monitor driver
	endtask: recv

recv monitor

与DUT直接通信的monitor是get_payload任务,任务可以分成以下两部分:

  1. 等待输出帧frameo_n有效,当frameo_n有效之后进入下一步,若输出帧长时间无效则退出程序。
  2. 输出帧有效后,检测valido_n是否有效,若有效则将输出结果缓存起来:1)假设frameo_n一直有效,则在收到8个数据以后结束for循环,将这一byte的数据添加到数据包中;2)假设frameo_n变成无效状态,若此时正好接收到8个数据,则同样把数据添加到数据包中并结束任务,否则打印错误信息并结束仿真。
  • 若valido_n无效会出现什么样的结果? for-loop中所有的if都不满足的话,不采集输出端的数据,直接执行@(rtr_io.cb)即更新仿真时间。这也是 i 的递增必须发生在valido_n有效之后的原因,如果valido_n无效时 i 也递增的话,会出现接收的数据非byte对齐的情况。
  • 何时跳出for-loop?i = 8即不满足for循环条件i < 8时,跳出for-loop。注意: 执行return结束了整个任务,肯定也跳出了for-loop,但执行return的必要条件是i = 8,仍然是在不满足for循环条件时跳出循环的。
  • 何时跳出forever-loop?直到frameo_n无效且此时数据是byte对齐的,才能跳出forever-loop。注意:执行$finish是因为发生了error,不能认为正确跳出了循环。
	task get_payload();
		pkt2cmp_payload.delete();
		// waiting for negedge of frameo_n
		fork		// limit the work domain of "disable fork"
		  	begin: wd_timer_fork	// keep fork...join_any and disable fork working squentially
				fork: frameo_wd_timer
					@(negedge rtr_io.cb.frameo_n[da]);
					begin
						repeat(1000) @(rtr_io.cb);
						$display("\n%m\n [ERROR]%t Frame signal timed out!",$realtime);
						$finish;
					end
				join_any: frameo_wd_timer
				disable fork;	// terminate the child-process above
		  	end: wd_timer_fork
		join
		// sampling output data from DUT
		forever begin
			logic[7:0] datum;
			for(int i=0; i<8;  ) begin	// warning hits: without for_step
				if(!rtr_io.cb.valido_n[da]) 
					datum[i++] = rtr_io.cb.dout[i];
				if(rtr_io.cb.frameo_n[da])
					if(i==8) begin
						pkt2cmp_payload.push_back(datum);
						return;		// exit task
					end
					else begin
						$display("\n%m\n [ERROR]%t Packet not byte aligned!",$realtime);
						$finish;	// exit simulation
					end
				@(rtr_io.cb);				
			end
			pkt2cmp_payload.push_back(datum);
		end
	endtask: get_payload
  • fork…join内的语句块称为子进程,执行这段fork…join块的代码称为父进程
    • fork..join的执行顺序是:子进程全部执行完毕后再执行父进程。
    • fork..join_any:子进程中的任何一个执行完毕后执行父进程。
    • fork..join_none:父进程不会被阻塞,与所有子进程并发执行。参考link
    • 注意:fork…join_any中任一子进程结束则开始执行父进程,此时其他子进程仍在并发执行!
    • disable fork:用来终止调用进程的所有活跃进程(杀伤力大)。常和fork…join_any同时使用,提前结束进程。参考link
    • wait fork:引起调用进程阻塞直到它的所有子进程结束。常和for循环、fork…join_none同时使用,并行启动多个进程。
  • $dispaly任务中第一个参数为打印格式,后续参数为输出列表。参考link
    • 这里的%m表示打印当前display语句所在的模块层次,%t表示打印当前的仿真时间。
    • $time返回一个64bit的整数来表示的当前仿真时刻值,该时刻以模块的仿真时间尺度(见timescale)为基准。
    • $realtime返回的时间数字是一个实型数,该数字也是以时间尺度为基准的。
  • system verilog增加了return声明,用于从一个非void函数中返回数值或者从一个void函数或任务返回。
    • return声明可以在任务或函数执行流程的任意一点执行。
    • 当return声明执行后,任务或者函数立即退出而不需要执行到任务或者函数的末尾。

Checker

compare function

创建任务check()将收到的数据包与源数据包比较,其中调用了自定义函数compare()比较两个数据包的尺寸和数据内容。只有当尺寸和内容都一致时,才会得到传输正确的结果,函数返回1;否则返回0,并打印错误信息。

	function compare(ref string message0);
		if(payload.size() != pkt2cmp_payload.size()) begin
			message0 = "Payload Size Mismatch:\n";
          	message0 = { message0, $sformatf("payload.size() = %0d, pkt2cmp_payload.size() = %0d\n", 
          				payload.size(), pkt2cmp_payload.size()) };
          	return (0);
      	end
      	if(payload == pkt2cmp_payload) ;
      	else begin
      		message0 = "Payload Content Mismatch:\n";
          	message0 = { message0, $sformatf("Packet Sent: %p \n Packet Received: %p", 
          				payload, pkt2cmp_payload) };
          	return (0);
      	end
      	message0 = "Successfully Compared";
      	return(1);
     endfunction: compare 
  • ref关键字采用标准的地址传递,即引用地址而非引用值!参考link
    • compare(ref string message0)的形参为字符串message,函数定义时为message开辟一段内存空间
    • 函数被调用时,实参将指向形参message所在的空间,并对空间中的内容进行修改
    • 函数调用结束后,该内存空间中的值已经被修改了
  • $sformatf的作用是整理字符串的打印格式,而非直接打印字符串。参考link
    • %0d中,d表示以十进制格式输出,在%和d之间加上0使输出数据时总是用最少的位数来显示表达式的当前值
    • %p可用于打印聚合表达式,例如解压缩结构、队列、数组和联合数组等。参考link

check task

在checker中定义一个字符串变量,用于比较结果的打印,以及一个计数器来统计对比结果。如果发现数据包发送不正确,则打印具体的错误信息,并结束仿真;如果对比结果显示数据包发送正确,则统计发送的数据包的个数或者编号。

	  task check();
      	string message1;
      	static int pkts_checked = 0;
      	if ( !compare(message1) ) begin
      		$display("\n%m\n[ERROR]%t Packet #%0d %s\n", $realtime, pkts_checked, message1);
      		$finish;
      	end
      	$display("[NOTE]%t Packet #%0d %s", $realtime, pkts_checked++, message1);
  	endtask: check
  • message1即为实参,调用compare函数时,message1将指向前面的message0的内存空间,并对内存中的数据做修改。
  • pkt_checked定义为static型变量,表示对所有对象都是可见的,被多个对象共享,在实例内外对它的修改都能生效。参考link

make bad

为了验证compare方法的正确性,既要保证对于正确的rtl代码能得出successfully的结果,也要确保错误的rtl能够给出mismatch的结果。原教程中给出了一个bad rtl,通过make bad可以编译错误的代码。从仿真结果来看,当[sa]=15作为输入端向[da]=8端口输出数据时,输出端数据全为X态。
bad rtl waveform

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值