SystemVerilog Testbench Lab5: broad spectrum verification(下)

续《SystemVerilog Testbench Lab5: broad spectrum verification(上)》

Generator Class

Generator类要做的事情:(1)通过随机的方式产生待发送的数据,即随机激励;(2)将产生的数据拷贝一份后放进信箱里,以便于和其他进程通信。

`ifndef INC_GENERATOR_SV
`defien INC_GENERATOR_SV

class Generator;
	string name;
	Packet pkt2send;
	pkt_mbox	out_box[];	// []声明的是一个含有多个元素的数组,此时为空

	extern function new(string name = "Receiver");
	extern virtual task gen();
	extern virtual task start();
endclass

function Generator::new(string name);
	if(TRACE_ON) $display("[TRACE]%t %s: %m", $realtime, name);
	this.name = name;	// 等式右边的name为入口参数,string类型
	this.pkt2send = new();	// 为其对象pkt2send分配空间
	this.out_box = new[16];	// 调用new[]来分配空间,同时传递数组的深度(即16)
	foreach(this.out_box[i])	// 动态数组一一初始化
		this.out_box[i] = new();
endfunction
// 用randomize方法随机化激励
task Generator::gen();
	static int pkt_generated = 0;
	if(TRACE_ON) $display("[TRACE]%t %s: %m", $realtime, this.name);
	this.pkt2send.name = $psprintf("rcvdPkt[%0d]", pkt_generated++);
	if( !pkt2send.randomize() ) begin
		$dispaly("\n%m\n[ERROR]%t Randomization Failed!\n", $realtime);
		$finish;
	end
endtask:gen
// 控制产生激励的个数,以及复制激励到信箱中
task Generator::start()
	if(TRACE_ON) $display("[TRACE]%t %s: %m", $realtime, this.name);
	fork
		for(int i=0; run_for_n_packets<=0 || i < run_for_n_packets; i++) begin
			this.gen();
			begin
				Packet pkt = new this.pkt2send;	// 注意此处是this.pkt2send。光写pkt2send是不够的!!!
				this.out_box[pkt.sa].put(pkt);	// 每个入口有一个单独的数组用于存储源地址与自身相同的包
			end
		end
	join_none
endtask:start

`endif
  • Generator::new()中需要创建16个mailbox,用new[16]为16个mailbox分配空间,并调用new()方法一一初始化,否则在使用out_box时会报错并提示:使用之前未分配空间(见下图)。不new的ERROR
  • 疑惑:其他进程中使用mailbox之前调用new()方法了吗?默认使用了new()方法,原因参考下面这段话。用户可以自定义new方法赋初值,SV默认为未自定义的对象分配空间和初始化。

SV默认会为每个类创建一个new函数,并初始化对象(二值变量为0,四值变量为X)。用户一般自定义new函数赋初值,分配空间可以交给SV来做。

  • 由于动态数组在声明时未真正分配内存,分配内存的方法有:①使用new[]方法分配内存,再另外初始化;②直接列表初始化动态数组,隐含了内存分配内存。

Scoreboard Class

Scoreboard要做的事情是:(1)接收分别来自Drivers和Receivers的数据;(2)当Drivers数据的da与Receivers数据的da一致时,才将数据的尺寸和内容进行比较。

`ifndef INC_SCOREBOARD_SV
`ifndef INC_SCOREBOARD_SV

class Scoreboard;
	string name;
	event  DONE;
	Packet refPkt[$];
	Packet pkt2send, pkt2cmp;
	pkt_mbox	driver_mbox, receiver_mbox;

	extern function new(string name = "Scoreboard", pkt_mbox driver_mbox = null, recevier_mbox = null);
	extern virtual task start();
	extern virtual task check();	// compare
endclass

function Scoreboard::new(string name, pkt_mbox driver_mbox, recevier_mbox);
	this.name = name;
	if(driver_mbox == null) driver_mbox = new();		// 为入口参数driver_mbox分配空间
	this.driver_mbox = diver_mbox;
	if(receiver_mbox == null) receiver_mbox = new();	// 为入口参数recevier_mbox分配空间
	this.receiver_mbox = receiver_mbox;
endfunction

task Scoreboard::start();
	fork
		forever begin
			this.receiver_mbox.get(this.pkt2cmp);
			while( this.driver_mbox.num() ) begin
				Packet pkt;
				this.driver_mbox.get(pkt);
				this.refPkt.push_back(pkt);
			end	
			this.check();
		end
	join_none
endtask:start

task Scoreboard::check();
	int index[$];
	string message;
	static int pkt_checked = 0;
	index = this.refPkt.find_first_index() with (item.da == this.pkt2cmp.da);
	if(index.size() <= 0) begin
		$display("\n%m\n[ERROR]%t %s not found in Reference Queue\n", $realtime, this.pkt2cmp.name);
		this.pkt2cmp.display("ERROR");
		$finish;
	end
	this.pkt2send = this.refPkt[inedx[0]];
	this.refPkt.delete(index[0]);
	if(!this.pkt2send.compare(this.pkt2cmp, message)) begin
		$display("\n%m\n[ERROR]%t Packet #%0d %s\n", $realtime, pkt_checked++, message);
		this.pkt2send.display("[ERROR]");
		this.pkt2cmp.display("[ERROR]");
		$finish;
	end
	$display("\n%m\n[NOTE]%t Packet #%0d %s\n", $realtime, pkt_checked++, message);
	if(pkt_checked >= run_for_n_packets)
		-> this.DONE;
endtask:check

`endif
  • 信箱方法mailbox.num():查询mailbox中数据的个数。
  • 队列方法queue.find_first_index() with (condition):返回队列中第一个满足某个条件的元素的索引值;queue.delete(n):删除队列中索引为n的元素,若n缺省则删除整个队列中的元素。队列索引值从0开始
  • Scoreboard的最终目的是比较来自divers与receivers的数据包,实现的过程是:Receiver类中的信箱在不停地接收从DUT发出的数据,Drivers类中的信箱在不停地将待传输的数据原样拷贝给Scoreboard;sb先从receiver_mbox中取出一个数据放到pkt2cmp中,然后把driver_mbox中的数据一个个取出,存到refPkt队列里;在refPkt队列中找到第一个与pkt2cmp的目的地址相同的元素,如果该元素不存在的话则报错,否则将这第一个元素赋值给pkt2send,并且从原参考队列中删除它;调用Packet的compare()方法比较pkt2sendpkt2cmp,如果比较结果不正确则打印不一致的原因和两个数据包的sa、da、payload并退出仿真,否则打印成功的信息;如果最后统计到的匹配的个数与发送的数据包的个数相同,则触发一个DONE event来“优雅地”结束仿真。

testbench

test.sv中创建了16个Driver、16个Receiver、1个Generator、1个Scoreboard,并通过信箱将各个组件连在一起。

// test.sv 
program automatic test();
	int run_for_n_packets;
	int TRACE_ON = 0;
	
	`include "Packet.sv"
	`include "router_test.h"
	`include "DriverBase.sv"
	`include "Driver.sv"
	`include "ReceiverBase.sv"
	`include "Receiver.sv"
	`include "Generator.sv"
	`include "Scoreboard.sv"

	semaphore 	sem[];	// 16 semaphores for 16 drivers
	Driver 		drvr[];	// 16 drivers
	Receiver	rcvr[];	// 16 receivers
	Generator	gen;
	Scoreboard	sb;

	initial begin
		run_for_n_packets = 2000;
		
		sem  = new[16];
		drvr = new[16];
		rcvr = new[16];
		gen  = new();
		sb   = new();
		// function new(int keyCount = 0)
		foreach(sem[i]) sem[i] = new(1);
		// function new(string name, int port_id, semaphore sem[], pkt_mbox in_box, out_box, virtual rtouer_io.TB rtr_io);
		foreach(drvr[i]) drvr[i] = new($sformatf("drvr[%0d]", i), i, sem, gen.out_box[i], sb.driver_mbox, rtr_io);
		// function Receiver::new(string name, int port_id, pkt_mbox out_box, virtual router_io.TB rtr_io);
		foreach(rcvr[i]) rcvr[i] = new($sformatf("rcvr[%0d]", i), i, sb.receiver_mbox, rtr_io);

		reset();
		gen.start();
		sb.start();
		foreach(drvr[i]) drvr.start();	// non-blocking assignment in task start
		foreach(rcvr[i]) rcvr.start();	// non-blocking assignment in task start

		wait(sb.DONE.triggered);
	end

	task reset();
		if (TRACE_ON) $display("[TRACE]%t :%m", $realtime);
    	rtr_io.reset_n = 1'b0;
    	rtr_io.cb.frame_n <= '1;	// 注意:这里缺省了位宽,如果误写成1,那么只有最低位被置1,其他位全为0!!!
    	rtr_io.cb.valid_n <= '1;
    	##2 rtr_io.cb.reset_n <= 1'b1;
    	repeat(15) @(rtr_io.cb);
	endtask:reset
endprogram:test
  • 疑惑1driver_mbox、receiver_mbox各自只有一个,而Drivers、Receivers各有16个,怎么确保多个进程向同一个信箱放数据不会产生冲突?不会。虽然foreach执行16次start任务,但是start()内是blocking assignment 个人认为数据进入mailbox中并不一定要是串行的,两封信件同时到达信箱就同时收下。信箱的作用是收信,只要信收到了就完成任务了,因而即使多个进程同时调用put()方法也不会产生冲突。
  • 疑惑2:16个drivers进程往同一个dirver_mbox里放数据时,究竟哪一个driver的数据被先放进去?放的顺序不会影响对比的结果,因为总是用pkt2cmp.dadirver_mbox的数据分类,而且入口的sem也限制了同一时刻不会发生多个输入端口往同一个输出端口发送数据的情况发生。SV是怎么存数据的?
  • event——事件:用来同步多个并发的进程(比如,某个进程等待着事件,而另一个进程触发这个事件)。
    • 在sb中,首先通过event event_name定义了一个事件,若所有数据都匹配,则通过-> event_name触发该事件。 触发形式类似于边沿触发,触发沿到来时会结束当前所有由于等待这个事件而被阻塞的进程。
    • triggered()是查询事件是否被触发的方法,即event_name.triggered(),若被触发则返回1。等待事件触发用@event_name或者wait(event_name.triggered())
  • 最后验证的时候竟然因为reset()写错了而导致用例failed!!!具体错误原因是frame_n、valid_n赋值时写成了rtr_io.cb.xxx_n <= 1'b1,即只有最低位置1其他位全部置0,dut没有获得一个正确的初始工作状态,造成用例对比失败。

make bad

运行错误地rtl代码得到了错误的输出结果,log文件打印的错误提示如下图所示,可见有两组不同的数据包被送到了同一个目的端口da = 1,源端口分别是sa = 15sa = 14。而且sa = 15先送到了da = 1端口,而输出端口da = 1接收到的数据实际上是由sa = 14发送的。
比对失败的log信息

  • debug思路:sorceboard根据pkt2cmp.da从所有已经发送的pkt2send中匹配到第一个有相同的da的数据包,如果找到则将该pkt2send从队列中删除。旗语的仲裁机制已经保证了两个不同的输入不可能同时驱动一个相同的输出,而现在仿真出现了这种情况,可能的原因有2个:
    • 旗语没有起到有效的仲裁作用?不应该。因为验证正确代码是可以PASS的,旗语的用法无误。
    • 错误的sa = 15, da = 1的数据包没有被有效删除?回到log和波形中核对sa = 15, da = 1sa = 14, da = 1对应的数据包分别什么时候出现,发现:
    • 14050nssa = 15就向da = 1端发送数据了,但是da = 1一直未收到该笔数据,因而这笔数据一直在pkt2send的队列里保存着;
      sa=15,da=1第一次出现在pkt2send的队列中
      错误对比数据传输分析
    • da = 1端口再接收到数据时,已经是由sa = 14向在26450ns时刻向da = 1发送的数据了。这个数据包被da = 1正常接收且进入socreboard中参与比较。
      sa=14,da=1第一次出现在pkt2send的队列里
      正确对比数据传输分析
  • 综上,bad rtl错在sa = 15da = 1发送数据,但是数据并未被da = 1接收到,因而要修改bad rtl中这部分的路由逻辑!
  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值