UVM实验3

1. 实验目的

1.在之前的monitorchecker的通信,以及checkerreference model之间的通信,都是通过mailbox以及在上层进行其句柄的传递实现的。我们在接下来的实验要求中,需要大家使用TLM端口进行通信,做逐步的通信元素和方法的替换
UVM实验3_第1张图片2.涉及到通信的有各个agent里的monitormcdf_checkermailbox的通信;以及mcdf_fefmodmcdf_cheker里的do_data_compare之间的通信。
3.为了熟悉多项通信和通信管道的用法,对于monitormailbox,实验里用到的是单向/多向通信;对于refmoddo_data_compare,实验里用到的是通信管道

2. TLM单向通信和多向通信

1.monitorinitiator端,需要在monitor处实现port的声明例化以及通信方法的调用,具体代码如下(以chnl_agent里的monitor为例):

1.port的声明:
uvm_blocking_put_port #(mon_data_t) mon_bp_port;
2.例化:端口不属于uvm_component,也不属于uvm_object,它的父类继承于uvm_void,port不在factory工厂里,所以例化不能用create,只能用new来做
mon_bp_port = new(“mon_bp_port”, this);
3.通信方法的调用:
mon_bp_port.put(m);

	class chnl_monitor extends uvm_component;
		local virtual chnl_intf intf;
		uvm_blocking_put_port #(mon_data_t) mon_bp_port;
		//port的声明,mon_bp_port代表了monitor的端口
		
		`uvm_component_utils(chnl_monitor)
		
		function new(string name = "chnl_monitor", uvm_component parent);
			super.new(name, parent);
			mon_bp_port = new("mon_bp_port", this);
			//port的例化,由于端口不属于组件,所以只能用new()例化,注意传递的参数
		endfunction
		
		function void set_interface(virtual chnl_intf intf);
			if(intf == null)
				$error("interface handle is NULL, please check if target interface has been intantiated");
			else	
				this.intf = intf;
		endfunction
		
		task run_phase(uvm_phase phase);
			this.mon_trans();
		endtask
		
		task mon_trans();
			mon_data_t m;
			forever begin	
				@(posedge intf.clk iff (intf.mon_ck.ch_valid === 'b1 && intf.mon_ck.ch_ready === 'b1));
				m.data = intf.mon_ck.ch_data;
				mon_bp_port.put(m);
				//通过port端口传递数据,直接调用imp端的方法put();
				`uvm_info(get_type_name(), $sformatf("monitored channel data 'h%8x", m.data), UVM_HIGH)
			end
		endtask
	endclass:chnl_monitor	

2.mcdf_checker里的mailbox是target端,且接收多个port端的信息,所以需要实现的是imp的多向通信,多项通信比单向通信多一步宏声明。具体步骤1.宏声明;2.imp端口的声明;3.imp端口的例化;4.在imp端口实现对应的方法(供port端使用),具体代码如下:

`uvm_blocking_put_imp_decl(SFX)
`uvm_nonblocking_put_imp_decl(SFX)
`uvm_biocking
_get _imp_decl(SFX)
`uvm_nonbiocking_get_imp_decl(SFX)
`uvm_blocking
_peek_imp_decl(SFX)
`uvm_nonblocking_put_imp_decl(SFX)
uvm讲义119和红宝书337有更多宏声明的例子

task put(T t)
function bit tyr_put(T t)
function bit can_put()
红宝书337有imp端方法名的例子

红宝书337有imp端方法名的例子

	//1.宏声明
	`uvm_blocking_put_imp_decl(_reg)
	`uvm_blocking_put_imp_decl(_chnl0)
	`uvm_blocking_put_imp_decl(_chnl1)
	`uvm_blocking_put_imp_decl(_chnl2)
	`uvm_blocking_put_imp_decl(_fmt)
	
	`uvm_blocking_get_peek_imp_decl(_chnl0)
	`uvm_blocking_get_peek_imp_decl(_chnl1)
	`uvm_blocking_get_peek_imp_decl(_chnl2)
	
	`uvm_blocking_get_imp_decl(_reg)
	
	//MCDF checker (scoreboard)
	class mcdf_checker extends uvm_scoreboard;
		local int err_count;
		local int total_count;
		local int chnl_count[3];
		local virtual chnl_intf chnl_vif[3];
		local virtual arb_intf arb_vif;
		local virtual mcdf_intf mcdf_vif;
		local mcdf_refmod refmod;
		
		//2.imp端口的声明
		uvm_blocking_put_imp_reg #(reg_trans, mcdf_checker) reg_bp_imp;
		uvm_blocking_put_imp_chnl0 #(mon_data_t, mcdf_checker) chnl0_bp_imp;
		uvm_blocking_put_imp_chnl1 #(mon_data_t, mcdf_checker) chnl1_bp_imp;
		uvm_blocking_put_imp_chnl2 #(mon_data_t, mcdf_checker) chnl2_bp_imp;
		uvm_blocking_put_imp_fmt #(fmt_trans, mcdf_checker) fmt_bp_imp;
		
		uvm_blocking_get_peek_imp_chnl0 #(mon_data_t, mcdf_checker) chnl0_bgpk_imp;
		uvm_blocking_get_peek_imp_chnl1 #(mon_data_t, mcdf_checker) chnl1_bgpk_imp;
		uvm_blocking_get_peek_imp_chnl2 #(mon_data_t, mcdf_checker) chnl2_bgpk_imp;
		
		uvm_blocking_get_imp_reg #(reg_trans, mcdf_checker) reg_bg_imp;
		
		mailbox #(mon_data_t) chnl_mbs[3];
		mailbox #(fmt_trans) fmt_mb;
		mailbox #(reg_trans) reg_mb;
		
		uvm_blocking_get_port #(fmt_trans) exp_bg_ports[3];
		
		`uvm_component_utils(mcdf_checker)
		
		function new(string name = "mcdf_checker", uvm_component parent);
			super.new(name, parent);
			this.err_count = 0;
			this.total_count = 0;
			foreach(this.chnl_count[i]) this.chnl_count[i] = 0;
			//3.imp端口的例化,new()函数例化
			chnl0_bp_imp = new("chnl0_bp_imp", this);
			chnl1_bp_imp = new("chnl1_bp_imp", this);
			chnl2_bp_imp = new("chnl2_bp_imp", this);
			fmt_bp_imp   = new("fmt_bp_imp", this);
			reg_bp_imp   = new("reg_bp_imp", this);
			
			chnl0_bgpk_imp = new("chnl0_bgpk_imp", this);
			chnl1_bgpk_imp = new("chnl1_bgpk_imp", this);
			chnl2_bgpk_imp = new("chnl2_bgpk_imp", this);
			
			reg_bg_imp     = new("reg_bg_imp", this);
			
			foreach(exp_bg_ports[i]) exp_bg_ports[i] = new($sformatf("exp_bg_ports[%0d], i", this));
		endfunction
		...
		//4.imp端方法的实现
		task put_chnl0(mon_data_t t);
			chnl_mbs[0].put(t);
		endtask
		task put_chnl1(mon_data_t t);
			chnl_mbs[1].put(t);
		endtask
		task put_chnl2(mon_data_t t);
			chnl_mbs[2].put(t);
		endtask
		task put_fmt(fmt_trans t);
			fmt_mb.put(t);
		endtask
		task put_reg(reg_trans t);
			reg_mb.put(t);
		endtask
		
		task peek_chnl0(output mon_data_t t);
			chnl_mbs[0].peek(t);
		endtask
		task peek_chnl1(output mon_data_t t);
			chnl_mbs[1].peek(t);
		endtask
		task peek_chnl2(output mon_data_t t);
			chnl_mbs[2].peek(t);
		endtask
		task get_chnl0(output mon_data_t t);
			chnl_mbs[0].get(t);
		endtask
		task get_chnl1(output mon_data_t t);
			chnl_mbs[1].get(t);
		endtask
		task get_chnl2(output mon_data_t t);
			chnl_mbs[2].get(t);
		endtask
	endcase:mcdf_checker

3.端口的连接,以mcdf_env里的monitor和mailbox的端口连接为例:

	class mcdf_env extends uvm_env;
		chnl_agent chnl_agts[3];
		reg_agent fmt_agts[3];
		fmt_agent fmt_agts[3];
		mcdf_checker chker;
		mcdf_coverage cvrg;
		
		`uvm_component_utils(mcdf_env)
		
		function new(string name = "mcdf_env", uvm_component parent);
			super.new(name, parent);
		endfunction
		
		function void build_phase(uvm_phase phase);
			super.build_phase(phase);
			this.chker = mcdf_checker::type_id::create("chker", this);
			foreach(chnl_agts[i]) begin
				this.chnl_agts[i] = chnl_agent::type_id::create($sformatf("chnl_agts[%0d]", i), this);
			end
			this.reg_agts[i] = reg_agent::type_id::create("reg_agts", this);
			this.fmt_agts[i] = fmt_agent::type_id::create("fmt_agts", this);
			this.cvrg = mcdf_coverage::type_id::create("cvrg", this);
		endfunction
		
		function void connect_phase(uvm_phase phase);
			super.connect_phase(phase);
			//在connect_phase里实现端口的连接
			chnl_agts[0].monitor.mon_bp_port.connect(chker.chnl0_bp_imp);
			chnl_agts[1].monitor.mon_bp_port.connect(chker.chnl1_bp_imp);
			chnl_agts[2].monitor.mon_bp_port.connect(chker.chnl2_bp_imp);
			reg_agt.monitor.mon_bp_port.connect(chker.reg_bp_imp);
			fmt_agt.monitor.mon_bp_port.connect(chker.fmt_bp_imp);
		endfunction
		
		task run_phase(uvm_phase phase);
		endtask
		
		function void report_phase(uvm_phase phase);
			super.report_phase(phase);
		endfunction
	endclass:mcdf_env

3. TLM通信管道

1.使用uvm_tlm_fifo类,帮助我们自动实现方法,且自带buffer(mailbox),依然是1.声明;2.例化;3.连接
声明的时候声明uvm_tlm_fifo即可,例:uvm_tlm_fifo #(fmt_trans) out_tlm_fifos[3];

uvm_tlm_fifo提供的put,get及peek对应的端口如下:
uvm_put_imp(类型) #(T, this_type) blocking_put_export(实例)
uvm_put_imp(类型) #(T, this_type) nonblocking_put_export(实例)

更多端口见讲义124或者红宝书347

uvm_tlm_fifo的声明与例化
uvm_tlm_fifo属于组件类,可以使用create创建,此处用的new()创建。

	class mcdf_refmod extends uvm_component;
		local virtual mcdf_intf intf;
		mcdf_reg_t regs[3];
		
		uvm_blocking_get_port #(reg_trans) reg_bg_port;
		uvm_blocking_get_peek_port #(mon_data_t) in_bgpk_ports[3];
		
		//1.uvm_tlm_fifo的声明
		uvm_tlm_fifo #(fmt_trans) out_tlm_fifos[3];
		
		`uvm_component_utils(mcdf_refmod)
		
		function new (string name = "mcdf_refmod", uvm_component parent);
			super.new(name, parent);
			foreach(in_bgpk_ports[i]) in_bgpk_ports[i] = new($sformatf("in_bgpk_ports[%0d]", i), this);

			//2.例化
			foreach(out_tlm_fifos[i]) out_tlm_fifos[i] = new($sformatf("out_tlm_fifos[%0d]", i), this);
		endfunction
		
		task run_phase(uvm_phase phase);
			fork
				do_reset();
				this.do_reg_update();
				do_packet(0);
				do_packet(1);
				do_packet(2);
			join
		endtask
		
		task do_reg_update();
			reg_trans t;
			forever begin
				this.reg_bg_port.get(t);
				if(t.addr[7:4] == 0 && t.cmd == `WRITE) begin
					this.regs[t.addr[3:2].en = t.data[0];
					this.regs[t.addr[3:2].prio = t.data[2:1];
					this.regs[t.addr[3:2].len = t.data[5:3];
				end
				else if(t.addr[7:4] == 1 && t.cmd == `READ) begin
					this .regs[t.addr[3:2]].avail = t.data[7:0];
				end
			end
		endtask
		
		task do_packet(int id);
			fmt_trans ot;
			mon_data_t it;
			forever begin
				this.in_bgpk_ports[id].peek(it);
				ot = new();
				ot.length = 4 << (this.get_field_value(id, RW_LEN) & 'b11);
				ot.data = new[ot.length];
				ot.ch_id = id;
				foreach(ot.data[m]) begin
					this.in_bgpk_ports[id].get(it);
					ot.data[m] = it.data;
				end
				this.out_tlm_fifos[id].put(ot);
			end
		endtask
		
		function int get_field_value(int id, mcdf_field_t f);
			case(f)
				RW_LEN: return regs[id].len;
				RW_PRIO: return regs[id].prio;
				RW_EN: return regs[id].en;
				RD_AVAIL: return regs[id].avail;
			endcase
		endfunction
		
		task do_reset();
			forever begin
				@(negedge intf.rstn);
				foreach(regs[i]) begin
					regs[i].len ='h0;
					regs[i].prio ='h3;
					regs[i].en ='h1;
					regs[i].avail ='h20;
				end
			end
		endtask
		
		function void set_interface(virtual fmt_intf intf);
			if(intf == null)
				$error("interface handle is NULL, please check if target interface has been intantiated");
			else	
				this.intf = intf;
		endfunction
	endclass:mcdf_refmod

2.port端的声明与例化(在tlm单向通信与双向通信里有详细代码)

//port声明
uvm_blocking_get_imp_reg #(reg_trans, mcdf_checker) reg_bg_imp;
//port例化
foreach(exp_bg_ports[i]) exp_bg_ports[i] = new($sformatf("exp_bg_ports[%0d], i", this));

3.连接,两边的端口类型一定要一致(blocking,get)
调用实例out_tlm_fifos[i]里的端口进行连接,具体fifo内置的其他端口见讲义或者红宝书

foreach(exp_bg_ports[i]) begin
	exp_bg_ports[i].connect(refmod.out_tlm_fifos[i].blocking_get_export);
end

4. UVM回调类

接下来,我们将练习uvm_callback的定义,连接和使用方式。由此,我们可以将原有的mcdf_data_consistence_basic_test和mcdf_full_random_test的类实现方式(即类继承方式)修改为回调函数的实现方式,帮助同学们认识,完成类的复用除了可以使用继承,还可以使用回调函数。

4.1 通过继承的方式完成类的复用:

1.先声明一个父类,完成组件的例化以及端口的连接,变量配置等,配置方法用virtual task
2.再用子类去继承父类,给子类配置对应的测试方法

	class mcdf_base_test extends uvm_test;
		...
		// do register configuration
		virtual task do_reg();
		endtask
			
		// do external formatter down stream slave configuration
		virtual task do_formatter();
		endtask
			
		// do data transition from 3 channel slaves
		virtual task do_data();
		endtask
		...
	endclass:mcdf_base_test

	//类的继承完成类的复用
	class mcdf_data_consistence_basic_test extends mcdf_base_test;
		...
		task do_reg();
			bit[31:0] wr_val, rd_val;
			// slv0 with len=8, prio=0, en=1
			wr_val = (1<<3) + (0<<1) + 1;
			this.write_reg(`SLV0_RW_ADDR, wr_val);
			this.read_reg(`SLV0_RW_ADDR, rd_val);
			void'(this.diff_value(wr_val, rd_val, "SLV0_WR_REG"));
			
			// slv1 with len=16, prio=1, en=1
			wr_val = (2<<3) + (1<<1) + 1;
			this.write_reg(`SLV1_RW_ADDR, wr_val);
			this.read_reg(`SLV1_RW_ADDR, rd_val);
			void'(this.diff_value(wr_val, rd_val, "SLV1_WR_REG"));
			
			// slv2 with len=32, prio=2, en=1
			wr_val = (3<<3) + (2<<1) + 1;
			this.write_reg(`SLV2_RW_ADDR, wr_val);
			this.read_reg(`SLV2_RW_ADDR, rd_val);
			void'(this.diff_value(wr_val, rd_val, "SLV2_WR_REG"));
			
			// send IDLE command
			this.idle_reg();
		endtask
		
		task do_formatter();
			void'(fmt_gen.randomize() with {fifo == LONG_FIFO; bandwidth == HIGH_WIDTH;});
			fmt_gen.start();
		endtask
		
		task.do_data();
			void'(chnl_gens[0].randomize() with {ntrans==100; ch_id==0;data_nidles==0; pkt_nidles==1; data_size==8;});
			void'(chnl_gens[1].randomize() with {ntrans==100; ch_id==1;data_nidles==1; pkt_nidles==4; data_size==16;});
			void'(chnl_gens[2].randomize() with {ntrans==100; ch_id==2;data_nidles==2; pkt_nidles==8; data_size==32;});
			fork
				chnl_gens[0].start();
				chnl_gens[1].start();
				chnl_gens[2].start();
			join
			#10us; // wait until all data haven been transfered through MCDF
		endtask
	endclass:mcdf_data_consistence_basic_test

4.2 通过回调函数完成类的复用:

1.定义回调函数类class cb_mcdf_base extends uvm_callback,该回调函数里的测试方法定义为虚方法(方便子类继承,从而实现不同的回调函数)

	typedef class mcdf_base_test;
	
	class cb_mcdf_base extends uvm_callback;
		`uvm_object_utils(cb_mcdf_base)
		mcdf_base_test test;
		//后面cb_mcdf_data_consistence_basic要用到顶层test里的方法实现配置方法,所以需要声明顶层test的句柄
		function new(string name = "cb_mcdf_base");
			super.new(name);
		endfunction
		
		virtual task cb_do_reg();
		endtask
		
		virtual task cb_da_reg();
		endtask
		
		virtual task cb_do_data();
		endtask
	endclass

2.将回调函数cb_mcdf_base与mcdf_base_test做绑定:
`uvm_register_cb(mcdf_base_test, cb_mcdf_base)

	class mcdf_base_test extends uvm_test;
		chnl_generator chnl_gens[3];
		reg_generator reg_gen;
		fmt_generator fmt_gen;
		mcdf_env env;
		local int timeout = 10; // 10 * ms
		virtual chnl_intf ch0_vif;
		virtual chnl_intf ch1_vif;
		virtual chnl_intf ch2_vif;
		virtual reg_intf reg_vif;
		virtual arb_intf arb_vif;
		virtual fmt_intf fmt_vif;
		virtual mcdf_intf mcdf_vif;
		
		`uvm_component_utils(mcdf_base_test)
		`uvm_register_cb(mcdf_base_test, cb_mcdf_base)//绑定
		
		function new(string name = "mcdf_base_test", uvm_component parent);
			super.new(name, parent);
		endfunction

3.在mcdf_base_test类里的各个虚方法里插入回调函数cb_mcdf_base里的方法

		class mcdf_base_test extends uvm_test;
			...
 			//do register configuration
			virtual task do_reg();
				`uvm_do_callbacks(mcdf_base_test, cb_mcdf_base, cb_do_reg())
			endtask
			
			// do external formatter down stream slave configuration
			virtual task do_formatter();
				`uvm_do_callbacks(mcdf_base_test, cb_mcdf_base, cb_do_formatter())
			endtask
			
			// do data transition from 3 channel slaves
			virtual task do_data();
				`uvm_do_callbacks(mcdf_base_test, cb_mcdf_base, cb_do_data)
			endtask
			...
		endclass

4.定义子类的回到函数类class cb_mcdf_data_consistence_basic extends cb_mcdf_base;
用该子类完成对应的test,需要在该子类里填充对应的方法配置

	class cb_mcdf_data_consistence_basic extends cb_mcdf_base;
		`uvm_object_utils(cb_mcdf_data_consistence_basic)
		function new(string name = "cb_mcdf_data_consistence_basic");
			super.new(name);
		endfunctin
		task cb_do_reg();
			bit[31:0] wr_val, rd_val;
			// slv0 with len=8, prio=0, en=1
			wr_val = (1<<3) + (0<<1) + 1;
			test.write_reg(`SLV0_RW_ADDR, wr_val);
			test.read_reg(`SLV0_RW_ADDR, rd_val);
			void'(this.diff_value(wr_val, rd_val, "SLV0_WR_REG"));
			
			// slv1 with len=16, prio=1, en=1
			wr_val = (2<<3) + (1<<1) + 1;
			test.write_reg(`SLV1_RW_ADDR, wr_val);
			test.read_reg(`SLV1_RW_ADDR, rd_val);
			void'(this.diff_value(wr_val, rd_val, "SLV1_WR_REG"));
			
			// slv2 with len=32, prio=2, en=1
			wr_val = (3<<3) + (2<<1) + 1;
			test.write_reg(`SLV2_RW_ADDR, wr_val);
			test.read_reg(`SLV2_RW_ADDR, rd_val);
			void'(this.diff_value(wr_val, rd_val, "SLV2_WR_REG"));
			
			// send IDLE command
			test.idle_reg();
		endtask
		
		task cb_do_formatter();
			void'(fmt_gen.randomize() with {fifo == LONG_FIFO; bandwidth == HIGH_WIDTH;});
			fmt_gen.start();
		endtask
		
		task.cb_do_data();
			void'(chnl_gens[0].randomize() with {ntrans==100; ch_id==0;data_nidles==0; pkt_nidles==1; data_size==8;});
			void'(chnl_gens[1].randomize() with {ntrans==100; ch_id==1;data_nidles==1; pkt_nidles==4; data_size==16;});
			void'(chnl_gens[2].randomize() with {ntrans==100; ch_id==2;data_nidles==2; pkt_nidles==8; data_size==32;});
			fork
				test.chnl_gens[0].start();
				test.chnl_gens[1].start();
				test.chnl_gens[2].start();
			join
			#10us; // wait until all data haven been transfered through MCDF
		endtask
	endclass

5.定义对应的test类:class cb_mcdf_data_consistence_basic_test extends mcdf_base_test
先例化,再添加:将回调函数子类添加至mcdf_base_test:uvm_callbacks #(mcdf_base_test)::add(this, cb);
利用该test类完成对回到函数类的使用,
不太明白cb.test = this
我的想法是cb继承于父类cb_mcdf_base,父类有mcdf_base_test的句柄test,但是仅仅只是句柄,没有指向一个对象,将this赋值给cb.test,那么cb.test才指向了具体的test实例,才能用test句柄调用test里面的方法。

class cb_mcdf_data_consistence_basic_test extends mcdf_base_test;
		cb_mcdf_data_consistence_basic cb;
		`uvm_component_utils("cb_mcdf_data_consistence_basic_test", uvm_component parent)
		
		function new(string name = "cb_mcdf_data_consistence_basic_test", uvm_component, parent);
			super.new(name, parent);
		endfunction
		
		function void build_phase(uvm_phase phase);
			super.build_phase(phase);
			cb = cb_mcdf_data_consistence_basic::type_id::create("cb");//例化
			uvm_callbacks #(mcdf_base_test)::add(this, cb);//添加
		endfunction
		
		function void connect_phase(uvm_phase phase);
			super.connect_phase(phase);
			cb.test = this;//不太明白?
		endfunction
	endclass:cb_mcdf_data_consistence_basic_test

5. UVM仿真控制方法

九大phase里有一个end_of_elaboration_phase(),它的执行在build_phase,connect_phase执行之后,run_phase执行之前,它的工作主要是在环境构建以及组件连接好之后,对环境再做一些配置:
1.设置消息冗余度为UVM_HIGH,以此允许更多低级别的信息打印出来。
2.设置仿真退出的最大数值,即如果uvm_error数量超过其指定值,那么仿真会退出。该变量默认值为-1,表示仿真不会伴随uvm_error退出。
3.设置仿真的最大时间长度

class mcdf_base_test extends uvm_test;
	...
	function void end_of_elaboration_phase(uvm_phase phase);
		super.end_of_elaboration_phase(phase);
		uvm_root::get().set_report_verbosity_level_hier(UVM_HIGH);
		uvm_root::get().set_report_max_quit_count(1);
		uvm_root::get().set_timeout(10ms);
	endfunction
	...

6 TLM端口通信缓存与信箱的比较

1.端口可以做缓存也可以不做缓存
UVM实验3_第2张图片
2.在使用analysis port时,端口支持一对多,且支持空发(广播模式 ),原因是write()为函数,具有立即返回的特点
UVM实验3_第3张图片
3.跨层次不容易出现错误,而句柄就容易出错(不需要更新顶层,自己的地方不用改,修改的地方做出修改的人一般也会改好)
UVM实验3_第4张图片UVM实验3_第5张图片
补充:在进行跨层次的连接时,一定不要超过两层
比如env通过句柄访问monitor的var变量:agent.monitor.var,此时为两层,不能再多了
UVM实验3_第6张图片

7 回调与继承的应用分析

回调函数:修改原有类的某些行为,允许后来人继承的基础上添加一些代码,做一些局部手术

8 UVM回调函数分析

你可能感兴趣的:(SV学习,材料工程,笔记,经验分享,学习)