UVM验证平台搭建案例2 | UVM笔记9

Posted by Kion on February 20, 2020

这段时间将SystemVerilog及UVM复习了一遍,完成了一些练习,对验证平台的搭建也有了更深的掌握与理解。

下面将之前一个验证平台改造成基于UVM的验证平台。

代码

driver

对于driver,删掉原来用于与generator通讯的mailbox。因为UVM的driver类,提供了TLM通讯接口seq_item_port,与sequencer的seq_item_export相连,即可起到原本mailbox的作用。接口原本是通过构造函数传进来,这里采用uvm_config_db,后面在test中统一配置验证平台中的接口。

`define dinf vif.drvmod 
class driver extends uvm_driver # (transaction);
	virtual inf vif;
	
	`uvm_component_utils(driver)
	
	function new(string name="driver", uvm_component parent);
		super.new(name, parent);
	endfunction
	
	virtual function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH);
		/*连接接口信号,原本是通过构造函数传进来*/
		uvm_config_db#(virtual inf)::get(this,"","vif",vif);
		if (vif==null) begin
			`uvm_fatal($sformatf("%m"),"Virtual interface to driver must be set!");
		end
	endfunction
	
	extern virtual task main_phase(uvm_phase phase);
	extern virtual task send(transaction tr);
	extern virtual task rst();
	
endclass

task driver::main_phase(uvm_phase phase);
	int i=0;
	`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH);
	/*复位操作*/
	this.rst();
	/*驱动信号至DUT*/
	forever begin
		`dinf.drvcb.wr_en <= 0;
		`dinf.drvcb.rd_en <= 0;
		seq_item_port.get_next_item(req);
		send(req);
		seq_item_port.item_done();
		`uvm_info($sformatf("%m"),$sformatf("No.%2d Transaction drived!",i++),UVM_LOW);
		`uvm_info($sformatf("%m"),{"\n",req.sprint()},UVM_LOW);
	end
endtask
/*用于驱动到的任务*/
task driver::send(transaction tr);
	@(`dinf.drvcb);

	`dinf.drvcb.addr <= tr.addr;
	if (tr.wr_en) begin
		`dinf.drvcb.wr_en <= tr.wr_en;
		`dinf.drvcb.wdata <= tr.wdata;
		@(`dinf.drvcb);
	end
	if (tr.rd_en) begin
		`dinf.drvcb.rd_en <= tr.rd_en;
		@(`dinf.drvcb);
		tr.rdata = `dinf.drvcb.rdata;
	end
	
endtask


task driver::rst();
	wait(`dinf.reset == 1);
	`dinf.drvcb.wr_en <= 0;
	`dinf.drvcb.rd_en <= 0;
	`dinf.drvcb.addr <= 0;
	`dinf.drvcb.wdata <= 0;
	`uvm_info($sformatf("%m"),"Reset completed!",UVM_LOW);
	wait(`dinf.reset == 0);
	repeat(5)	@(`dinf.drvcb);
endtask
transaction/sequence/sequencer

transaction类变化不大。sequence代替了原本generator的作用,sequencer则在sequence与driver之间起到调度作用。加入sequencer,就可以更方便地管理不同的sequence。sequencer这个组件很简单,这里之间用uvm_sequencer即可,无需派生。

class transaction extends uvm_sequence_item;
	rand bit [1:0] addr;
	rand bit wr_en;
	rand bit rd_en;
	rand bit [7:0] wdata;
	bit [7:0] rdata;
	static int cnt=0;
	
	constraint wr_rd {wr_en != rd_en;};
	
	`uvm_object_utils_begin(transaction)
		`uvm_field_int(addr, UVM_ALL_ON)
		`uvm_field_int(wr_en, UVM_ALL_ON)
		`uvm_field_int(rd_en, UVM_ALL_ON)
		`uvm_field_int(wdata, UVM_ALL_ON)
		`uvm_field_int(rdata, UVM_ALL_ON)
	`uvm_object_utils_end
	
	function new (string name="transaction");
		super.new();
		cnt++;
	endfunction
endclass

class seqce extends uvm_sequence # (transaction);
	`uvm_object_utils(seqce)
	
	transaction tr;
	int times;
	
	function new(string name="seqce");
		super.new(name);
	endfunction
	
	virtual task pre_body();
		/*在生成transaction前确定生成的数目*/
		uvm_config_db#(int)::get(null,get_full_name(),"times", times);
		if (times==0)
			`uvm_fatal($sformatf("%m"),"Times must be set!");
	endtask
	
	virtual task body();
		if (starting_phase!=null) starting_phase.raise_objection(this);
		repeat (this.times) begin
			`uvm_do(tr);
		end
		#1000;
		if (starting_phase!=null) starting_phase.drop_objection(this);
	endtask;
endclass

typedef uvm_sequencer # (transaction) seqcer;
input monitor

原本的验证平台中,input/output的收集全部放在一个monitor中,这里将其分开,使得分工更加明确。input monitor收集写操作的事务,output monitor收集读操作的事务。两者都封装在各自的agent类之中。

`define minf vif.monmod
class imonitor extends uvm_monitor;
	`uvm_component_utils(imonitor)
	transaction tr;
	
	virtual inf vif;
	/*用于TLM通讯的端口,另一端是agent的端口*/
	uvm_analysis_port #(transaction) ap;
	
	function new(string name="imonitor", uvm_component parent);
		super.new(name,parent);
	endfunction
	
	virtual function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH);
		uvm_config_db#(virtual inf)::get(this,"","vif",vif);
		if (vif==null) begin
			`uvm_fatal($sformatf("%m"),"Virtual interface to driver must be set!");
		end
		ap = new("ap",this);
	endfunction
	
	extern virtual task main_phase(uvm_phase phase);
	extern virtual task collect(transaction tr);
	
	
endclass

task imonitor::main_phase(uvm_phase phase);
	`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH);
	forever begin
		tr = new();
		/*收集事务*/
		collect(tr);
		/*发送事务*/
		ap.write(tr);
	end
endtask

task imonitor::collect(transaction tr);
			
	wait((`minf.moncb.wr_en));
	tr.wr_en = `minf.moncb.wr_en;
	tr.addr = `minf.moncb.addr;
	tr.wdata = `minf.moncb.wdata;
	tr.rd_en = `minf.moncb.rd_en;
	
	@(`minf.moncb);
	
endtask
output monitor
`define minf vif.monmod
class omonitor extends uvm_monitor;
	`uvm_component_utils(omonitor)
	transaction tr;
	
	virtual inf vif;
	uvm_analysis_port #(transaction) ap;
	
	function new(string name="omonitor", uvm_component parent);
		super.new(name,parent);
	endfunction
	
	virtual function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH);
		uvm_config_db#(virtual inf)::get(this,"","vif",vif);
		if (vif==null) begin
			`uvm_fatal($sformatf("%m"),"Virtual interface to driver must be set!");
		end
		ap = new("ap",this);
	endfunction
	
	extern virtual task main_phase(uvm_phase phase);
	extern virtual task collect(transaction tr);
	
endclass

task omonitor::main_phase(uvm_phase phase);
	`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH);
	forever begin
		tr = new();
		collect(tr);
		ap.write(tr);
	end
endtask

task omonitor::collect(transaction tr);
	wait((`minf.moncb.rd_en));
	tr.wr_en = `minf.moncb.wr_en;
	tr.addr = `minf.moncb.addr;
	tr.wdata = `minf.moncb.wdata;
	tr.rd_en = `minf.moncb.rd_en;
	@(`minf.moncb);
	tr.rdata = `minf.moncb.rdata;				
endtask
input agent / output agent

input agent中除了input monitor 还有sequencer以及driver;output agent中只有output monitor。同时agent作为更大的容器,含有与外界通讯的端口,一端连接monitor,一端连接TLM FIFO。

/*input agent*/
typedef uvm_sequencer # (transaction) seqcer;

class agent extends uvm_agent;
	`uvm_component_utils(agent)
	driver drv;
	seqcer sqr;
	imonitor mon;
	/*TLM端口*/
	uvm_analysis_port # (transaction) ap;
	
	function new(string name="agent",uvm_component parent);
		super.new(name, parent);	
	endfunction
	
	extern virtual function void build_phase(uvm_phase phase);
	extern virtual function void connect_phase(uvm_phase phase);
	
endclass

function void agent::build_phase(uvm_phase phase);
	`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH);
	super.build_phase(phase);
	/*ACTIVE模式,多了driver及sequncer*/
	if (is_active==UVM_ACTIVE) begin
		drv = driver::type_id::create("drv",this);
		sqr = seqcer::type_id::create("sqr",this);
	end 
	mon = imonitor::type_id::create("mon",this);
	ap = new("ap", this);
endfunction

function void agent::connect_phase(uvm_phase phase);
	`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH);
	if (is_active==UVM_ACTIVE) begin
		drv.seq_item_port.connect(sqr.seq_item_export);
	end
	ap = mon.ap;
endfunction
        
/*output agent*/
class oagent extends uvm_agent;
	`uvm_component_utils(oagent)
	
	omonitor mon;
	
	uvm_analysis_port # (transaction) ap;
	
	function new(string name="oagent",uvm_component parent);
		super.new(name, parent);	
	endfunction
	
	extern virtual function void build_phase(uvm_phase phase);
	extern virtual function void connect_phase(uvm_phase phase);
	
endclass

function void oagent::build_phase(uvm_phase phase);
	`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH);
	super.build_phase(phase);
	
	mon = omonitor::type_id::create("mon",this);
	ap = new("ap", this);
endfunction

function void oagent::connect_phase(uvm_phase phase);
	`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH);
	
	ap = mon.ap;
endfunction
scoreboard

scoreboard中用TLM FIFO代替了原本mailbox,通过两个端口,分别获得input monitor以及output monitor的数据,然后进行比较。同时设置了一个覆盖率检测,检查DUT中是否所有地址都被验证平台覆盖。

class scoreboard extends uvm_scoreboard;
	`uvm_component_utils(scoreboard)
	transaction act,exp;
	bit [7:0] mem[4];
	/*通讯端口*/
	uvm_blocking_get_port # (transaction) ap_exp;
	uvm_blocking_get_port # (transaction) ap;
	/*覆盖率检查*/
	covergroup addr_cov;
		coverpoint act.addr; 
	endgroup
	
	function new(string name="scoreboard",uvm_component parent);
		super.new(name,parent);
		addr_cov = new();
	endfunction
	
	extern virtual function void build_phase(uvm_phase phase);
	extern virtual task main_phase(uvm_phase phase);
	
endclass


function void scoreboard::build_phase(uvm_phase phase);
	super.build_phase(phase);
	`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH);
	ap = new("ap",this);
	ap_exp= new("ap_exp",this);
	/*初始化用于储存真实值的memory*/
	foreach (mem[i])
		mem[i] = 8'hFF;
endfunction

task scoreboard::main_phase(uvm_phase phase);
	int i=0;
	`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH);
	forever begin
		fork 
			forever begin
			/*从input monitor中获取写事务,更新memory的值*/
				ap_exp.get(exp);
				mem[exp.addr] = exp.wdata;
				i++;
			end
		join_none
		/*从output monitor中获得读事务,检测正确性*/
		ap.get(act);
		$display("%m No.%2d Transaction got!",i);
		if (mem[act.addr] == act.rdata) begin
			`uvm_info($sformatf("%m"),$sformatf("SUCCESS No.%d \n ~~ ACT%d EXP%d",i++,act.rdata,mem[act.addr]),UVM_LOW);
		end
		else begin
			`uvm_info($sformatf("%m"),$sformatf("FAIL No.%d \n ~~ ACT%d EXP%d",i++,act.rdata,mem[act.addr]),UVM_LOW);
		end
		/*采样覆盖率*/
		addr_cov.sample();				
	end		
endtask
environment

在environment中实例化agent,scoreboard,并做好端口连接。

`include "transaction.sv"
`include "seqce.sv"
`include "driver.sv"
`include "imonitor.sv"
`include "omonitor.sv"
`include "agent.sv"
`include "oagent.sv"

`include "scoreboard.sv"
class environment extends uvm_env;
	`uvm_component_utils(environment)

	agent iagt;
	oagent oagt;
	scoreboard sb;
	/*用于monitor与scoreboard间通讯的TLM FIFO*/
	uvm_tlm_analysis_fifo # (transaction) oagt2sb;
	uvm_tlm_analysis_fifo # (transaction) iagt2sb;
	
	function new(string name="environment", uvm_component parent);
		super.new(name,parent);
	endfunction
	
	extern virtual function void build_phase(uvm_phase phase);
	extern virtual function void connect_phase(uvm_phase phase);
	
endclass

function void environment::build_phase(uvm_phase phase);
	super.build_phase(phase);
	`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH);
	iagt = agent::type_id::create("iagt",this);
	oagt = oagent::type_id::create("oagt",this);
	sb = scoreboard::type_id::create("sb",this);
	iagt.is_active = UVM_ACTIVE;
	oagt2sb = new("oagt2sb",this);
	iagt2sb = new("iagt2sb",this);
	/*启动sequence*/
	uvm_config_db#(uvm_object_wrapper)::set(this,"iagt.sqr.main_phase","default_sequence",seqce::get_type());
	
endfunction

function void  environment::connect_phase(uvm_phase phase);
	`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH);
	/*连接FIFO端口*/
	oagt.ap.connect(oagt2sb.analysis_export);
	iagt.ap.connect(iagt2sb.analysis_export);
	sb.ap.connect(oagt2sb.blocking_get_export);
	sb.ap_exp.connect(iagt2sb.blocking_get_export);
endfunction
test_base

基本测试类。实例化env,配置接口,测试次数,打印平台结构。

`include "environment.sv"
class test_base extends uvm_test;
	`uvm_component_utils(test_base)
	environment	env;
	
	rand int times;
	virtual inf vif;
	
	function new(string name="test_base",uvm_component parent);
		super.new(name,parent);
	endfunction
	
	virtual function void build_phase(uvm_phase phase);
		`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH);
		super.build_phase(phase);
		env = environment::type_id::create("env",this);
		/*配置接口*/
		uvm_config_db#(virtual inf)::get(this,"","vif",vif);
		uvm_config_db#(virtual inf)::set(this,"env.iagt.drv","vif",vif);
		uvm_config_db#(virtual inf)::set(this,"env.oagt.mon","vif",vif);
		uvm_config_db#(virtual inf)::set(this,"env.iagt.mon","vif",vif);
		/*随机测试次数*/
		times = $urandom_range(10,20);
		uvm_config_db#(int)::set(this,"env.iagt.sqr","times", times);
	endfunction
	
	
	virtual function void start_of_simulation_phase(uvm_phase phase);
		super.start_of_simulation_phase(phase);
		`uvm_info("TRACE", $sformatf("%m"), UVM_HIGH);
		uvm_top.print_topology();
		factory.print();
	endfunction
	
endclass
top

顶层文件,连接DUT与验证平台。

`timescale 1ns/100ps;
module top;
	`include "uvm_macros.svh"
	import uvm_pkg::*;
	
	`include "interface.sv"
	`include "test_base.sv"
	`include "design.sv"
	
	reg clk;
	reg reset;
	inf intf(clk,reset);
	
	
	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)
	);
	
	initial begin
		run_test();
	end
	
	initial begin
		uvm_config_db#(virtual inf)::set(null,"uvm_test_top","vif",intf);
	end
	
	initial begin
		clk = 0;
		forever begin
			#50 clk = ~clk;
		end
	end
	
	initial begin
		reset = 0;
		#100;
		reset = 1;
		#100;
		reset = 0;
	end
	
	initial begin
		$dumpfile("dump.vcd");$dumpvars;
	end
endmodule

总结

在改造的过程中,有一个感触,不用去费心思想各个组件的运行关系了。原本的验证平台中需要event控制仿真结束,而何时触发event,就又得加入其他的判断条件。而以哪个组件的运行终止作为最后判断标准又得斟酌。用UVM就非常方便了,直接编写在相应的phase中,UVM自动帮我调度。phase运行完成仿真自然结束了。仿真意外结束,看看运行到哪个phase,也更方便定位错误。