验证平台搭建案例(1) | Systemverilog 笔记 10

Posted by Kion on January 25, 2020

下面搭建一个验证内存设计的平台。

Generator用于生成随机化的事务,并发送给Driver。Driver收到事务后,通过Interface发送给DUT。Monitor监控DUT的测试结果,并把Interface信号转换成事务发送给Scoreboard。Environment是一个包含了Generator,Driver,Monitor,Scoreboard的容器,负责验证环境的有序运行。Test则是一个更大的容器,包含了Environment以及其他的初始化配置。Top是最顶层的文件,用于连接DUT以及验证平台。

下面按照上述逻辑顺序给出平台搭建代码。

定义transaction类。

class transaction;
	/*定义事务项目*/
	rand bit [1:0] addr;
	rand bit wr_en;
	rand bit rd_en;
	rand bit [7:0] wdata;
	bit [7:0] rdata;
	bit [1:0] cnt;
	/*由于读写不能同时操作,所以施加约束*/
	constraint wr_rd_c {wr_en != rd_en;};
endclass

定义generator类。

class generator;
/* 发生器用于生成随机化的事务;并向驱动器发送这些事务 */

	/*定义一个事务句柄*/
	rand transaction trans;
	
	/* 定义信箱 */
	mailbox gen2driv;
	
	/* 定义最大事务数 */
	int repeat_count;
	
	/*生成结束事件*/
	event ended;
	
	/* 从evn类获得信箱 */
	function new(mailbox gen2driv,event ended);
		this.gen2driv = gen2driv;
		this.ended = ended;
	endfunction
	
	/* main task,用来生成并随机化数据包,放入信箱 */
	task main();
		repeat (repeat_count) begin
			trans = new();
			if (!trans.randomize()) $fatal("Gen::trans randomization failed");
			gen2driv.put(trans);
		end
		-> ended;
	endtask

endclass

定义interface。

interface mem_intf(input logic clk,reset);
/* 接口用于将信号捆绑,并指定方向,同步信号 */
	
	logic[1:0] addr;
	logic wr_en;
	logic rd_en;
	logic [7:0] wdata;
	logic [7:0] rdata;
	
	/* 驱动器时钟块 */
	clocking driver_cb @(posedge clk);
		default input #1 output #1;
		output addr;
		output wr_en;
		output rd_en;
		output wdata;
		input rdata;
	endclocking
	
	/* 监视器时钟块 */
	clocking monitor_cb @(posedge clk);
		default input #1 output #1;
		input addr;
		input wr_en;
		input rd_en;
		input wdata;
		input rdata;
	endclocking
	
	/* 驱动器modport */
	modport DRIVER (clocking driver_cb, input clk, reset);
	
	/* 监视器modport */
	modport MONITOR (clocking monitor_cb, input clk, reset);
	
	
endinterface

定义driver类。

`define DRIV_IF mem_vif.DRIVER.driver_cb
class driver;
/* 驱动器用于接受发生器生成的事务,并通过接口驱动给DUT */
	
	/*创建虚接口句柄*/
	virtual mem_intf mem_vif;
	
	/* 创建信箱句柄 */
	mailbox gen2driv;
	
	/*事务数目*/
	int no_transactions;
	
	/* 构造函数 */
	function new(virtual mem_intf mem_vif, mailbox gen2driv);
		/*获取接口和信箱*/
		this.mem_vif = mem_vif;
		this.gen2driv = gen2driv;
	endfunction
	
	/*清零任务*/
	task reset;
		wait(mem_vif.reset);
		$display("-------[DRIVER] Reset Started-------");
		`DRIV_IF.wr_en <= 0;
		`DRIV_IF.rd_en <= 0;
		`DRIV_IF.addr <= 0;
		`DRIV_IF.wdata <= 0;
		wait(!mem_vif.reset);
		$display("--------[DRIVER] Reset Ended--------");
	endtask
	
	/* 将事务项目驱动到接口信号 */
	task main;
		forever begin
			transaction trans;
			`DRIV_IF.wr_en <= 0;
			`DRIV_IF.rd_en <= 0;
			gen2driv.get(trans);
			$display("--------- [DRIVER-TRANSFER: %0d] ---------",no_transactions);
			
			@(posedge mem_vif.DRIVER.clk);
				`DRIV_IF.addr <= trans.addr;
			if (trans.wr_en) begin
				`DRIV_IF.wr_en <= trans.wr_en;
				`DRIV_IF.wdata <= trans.wdata;
				$display("\tADDR = %0h \tWDATA = %0h", trans.addr, trans.wdata);
				@(posedge mem_vif.DRIVER.clk);
			end
			
			if (trans.rd_en) begin
				`DRIV_IF.rd_en <= trans.rd_en;
				@(posedge mem_vif.DRIVER.clk);
				`DRIV_IF.rd_en <= 0;
				@(posedge mem_vif.DRIVER.clk);
				trans.rdata = `DRIV_IF.rdata;
				$display("\tADDR = %0h \tRDATA = %0h", trans.addr,`DRIV_IF.rdata);
			end
			$display("-------------------------");
			no_transactions++;
		end
	endtask
endclass

定义monitor类。

`define MON_IF mem_vif.MONITOR.monitor_cb
class monitor;
/* 采样接口信号,并转化信号级活动为事务级活动;
将采样到的事务通过信箱送往记分板; */

	virtual mem_intf mem_vif;
	
	mailbox mon2scb;
	
	function new(virtual mem_intf mem_vif, mailbox mon2scb);
		this.mem_vif = mem_vif;
		this.mon2scb = mon2scb;
	endfunction
	
	/* 主任务,实现采样逻辑,并送往记分板 */
	task main;
		forever begin
			transaction trans;
			trans = new();
			
			@(posedge mem_vif.MONITOR.clk);
			wait(`MON_IF.rd_en || `MON_IF.wr_en);
			trans.addr = `MON_IF.addr;
			trans.wr_en = `MON_IF.wr_en;
			trans.wdata = `MON_IF.wdata;
			if (`MON_IF.rd_en) begin
				trans.rd_en = `MON_IF.rd_en;
				@(posedge mem_vif.MONITOR.clk);
				@(posedge mem_vif.MONITOR.clk); 
				trans.rdata = `MON_IF.rdata;
			end
			mon2scb.put(trans);
		end
	endtask
endclass

定义scoreboard类。

class scoreboard;
/* 接收监视器发送的采样数据;
如果是读事务,比较读到的数据和局部内存数据;
如果是写事务,局部内存则会进行储存 */
	
	mailbox mon2scb;
	
	int no_transactions;
	
	bit [7:0] mem[4];
	
	function new(mailbox mon2scb);
		this.mon2scb = mon2scb;
		foreach(mem[i]) mem[i] = 8'hFF;
	endfunction
	
	
	task main;
		transaction trans;
		
		forever begin
			//#50;
			mon2scb.get(trans);
			/* 读数据 */
			if (trans.rd_en) begin
				if (mem[trans.addr] != trans.rdata)
					$error("[SCB-FAIL] Addr = %0h,\n \t   Data :: Expected = %0h Actual = %0h",
					trans.addr,mem[trans.addr],trans.rdata);
				else
					$display("[SCB-PASS] Addr = %0h,\n \t   Data :: Expected = %0h Actual = %0h",
					trans.addr,mem[trans.addr],trans.rdata);
			end
			/* 写数据 */
			else if (trans.wr_en)
				mem[trans.addr] = trans.wdata;
			
			no_transactions++;
		end
	endtask

endclass

定义environment类。

`include"transaction.sv"
`include"generator.sv"
`include"driver.sv"
`include "monitor.sv"
`include "scoreboard.sv"

class environment;
/* env是包含了发生器,驱动器和信箱的类 */
	
	generator gen;
	driver driv;
	monitor mon;
	scoreboard scb;
	
	mailbox gen2driv;
	mailbox mon2scb;
	
	/* 发生器和测试的同步事件 */
	event gen_ended;
	
	virtual mem_intf mem_vif;
	
	function new(virtual mem_intf mem_vif);
		/* 从测试类中获取接口 */
		this.mem_vif = mem_vif;
		
		/* 创建信箱句柄 */
		gen2driv = new();
		mon2scb = new();
		
		/* 实例化发生器和驱动器 */
		gen = new(gen2driv,gen_ended);
		driv = new(mem_vif,gen2driv);
		mon = new(mem_vif,mon2scb);
		scb = new(mon2scb);
	endfunction
	
	/* 发生器和驱动器的活动按照以下三部分 */
	task pre_test();
		driv.reset();
	endtask
	
	task test();
		fork
			gen.main();
			driv.main();
			mon.main();
			scb.main();
		join_any
	endtask
	
	task post_test();
		wait(gen_ended.triggered);
		wait(gen.repeat_count == driv.no_transactions);
		wait(gen.repeat_count == scb.no_transactions);
	endtask
	
	/* 使用run任务启动上述活动 */
	task run;
		pre_test();
		test();
		post_test();
		$finish;
	endtask
endclass

定义test代码块。

`include"environment.sv"
program test(mem_intf intf);
	/* 创建环境,配置验证条件,初始化验证 */
	
	environment env;
	
	initial begin
		/* 实例化环境类 */
		env = new(intf);
		
		/* 设置事务数目 */
		env.gen.repeat_count = 10;
		
		/* 启动环境 */
		env.run();
	end
	
endprogram

定义tbench_top模块。

`include "interface.sv"
`include "test.sv"
`include "design.sv"
module tbench_top;
/* 最顶层的文件,用于连接DUT和验证平台;
包含DUT,测试以及接口实例;
接口能将DUT与验证平台相连 */
	
	bit clk;
	bit reset;
	
	/* 实例化接口 */
	mem_intf intf(clk,reset);
	
	/* 实例化DUT,并配置接口 */
	memory DUT (
		.clk(intf.clk),
		.reset(intf.reset),
		.addr(intf.addr),
		.wr_en(intf.wr_en),
		.rd_en(intf.rd_en),
		.wdata(intf.wdata),
		.rdata(intf.rdata)
	);
	
	/* 连接测试接口 */
	test t1(intf);
	
	always #5 clk = ~clk;
	
	initial begin
		reset = 1;
		#5 reset=0;
	end
	
	initial begin
		$dumpfile("dump.vcd");$dumpvars;
	end
endmodule

下面是运行结果。

1eYkrT.png

参考