UVM实战 卷I学习笔记2——为验证平台加入各个组件(2)


话接上文UVM实战 卷I学习笔记2——为验证平台加入各个组件(1)

5、加入reference model

reference model用于完成和DUT相同的功能,其输出被scoreboard接收,用于和DUT的输出相比较。DUT如果很复杂,那么reference model也会相当复杂。本章的DUT很简单, 所以reference model也简单:

class my_model extends uvm_component;
	uvm_blocking_get_port #(my_transaction) port;
	uvm_analysis_port #(my_transaction) ap;
	extern funciton new(string name, uvm_component parent);
	extern function void build_phase(uvm_phase phase);
	extern virtual task main_phase(uvm_phase phase);
	`uvm_component_utils(my_model)
endclass
function my_model::new(string name, uvm_component parent);
	super.new(name, parent);
endfunction
function void my_model::build_phase(uvm_phase phase);
	super.build_phase(phase);
	port = new("port", this);
	ap = new("ap", this);
endfunction
task my_model::main_phase(uvm_phase phase);//复制一份从i_agt得到的tr并传递给后级scoreboard中
	my_transaction tr;
	my_transaction new_tr;
	super.main_phase(phase);
	while(1) begin
		port.get(tr);
		new_tr = new("new_tr");
		new_tr.my_copy(tr);
		`uvm_info("my_model", "get one transaction, copy and print it:", UVM_LOW)
		new_tr.my_print();
		ap.write(new_tr);
	end
endtask

my_copy是一个在my_transaction中定义的函数, 实现两个my_transaction的复制,其代码为:

function void my_copy(my_transaction tr);
	if(tr == null)
		`uvm_fatal("my_transaction", "tr is null!!!!")
	dmac = tr.dmac;
	smac = tr.smac;
	ether_type = tr.ether_type;
	pload = new[tr.pload.size()];
	for(int i = 0; i < pload.size(); i++) begin
		pload[i] = tr.pload[i];
	end
	crc = tr.crc;
endfunction

完成my_model的定义后,需要将其在my_env中实例化。其实例化方式与agent、driver相似。加入my_model后,整棵UVM树如图所示:
在这里插入图片描述

  • my_transaction的传递方式:my_model是从i_agt中得到my_transaction,并把
    my_transaction传递给my_scoreboard。UVM通常使用TLM(Transaction Level Modeling)实现component之间transaction级别的通信
  • 要实现通信需要考虑两点:第一,数据是如何发送的?第二,数据是如何接收的?在UVM的transaction级别的通信中,数据的发送有多种方式,其中一种是使用uvm_analysis_port。在my_monitor中定义如下变量:uvm_analysis_port #(my_transaction) ap;
  • uvm_analysis_port是一个参数化的类,其参数就是这个analysis_port需要传递的数据类型, 在例子中是my_transaction。声明了ap后,需要在monitor的build_phase中将其实例化:
virtual function void build_phase(uvm_phase phase);
	...
	ap = new("ap", this);
endfunction

在main_phase中,当收集完一个transaction后,需要将其写入ap中:

task my_monitor::main_phase(uvm_phase phase);
	my_transaction tr;
	while(1) begin
		tr = new("tr");
		collect_one_pkt(tr);
		ap.write(tr); //write是uvm_analysis_port的内建函数
	end
endtask
  • 至此,在my_monitor中需要为transaction通信准备的工作已经全部完成。
  • UVM的transaction级通信的数据接收方式有多种,其中一种是用uvm_blocking_get_port。是参数化的类,其参数是要在其中传递的transaction的类型。在my_model中定义了一个端口,并在build_phase中对其进行实例化。在main_phase中,通过port.get任务来得到从i_agt的monitor中发出的transaction。
  • 在my_monitor和my_model中定义并实现了各自的端口之后,通信的功能并没有实现,还需要在my_env中使用fifo将两个端口联系在一起。在my_env中定义一个fifo,并在build_phase中将其实例化:fifo的类型是uvm_tlm_analysis_fifo,它也是参数化的类,其参数是存储在其中的transaction的类型,这里是my_transaction。
	uvm_tlm_analysis_fifo #(my_transaction) agt_mdl_fifo;
	...
	agt_mdl_fifo = new("agt_mdl_fifo", this);

之后,在connect_phase中将fifo分别与my_monitor中的analysis_port和my_model中的blocking_get_port相连:

function void my_env::connect_phase(uvm_phase phase);
	super.connect_phase(phase);
	i_agt.ap.connect(agt_mdl_fifo.analysis_export);
	mdl.port.connect(agt_mdl_fifo.blocking_get_export);
endfunction
  • connect_phase也是UVM内建的一个phase,在build_phase执行完成之后马上执行。但与build_phase不同的是,它的执行顺序并不是从树根到树叶,而是从树叶到树根(自底向上)——先执行driver和monitor的connect_phase,再执行agent的connect_phase,最后执行env的connect_phase。
  • 上例为何需要fifo?为何不能直接把my_monitor中的analysis_port和my_model中的blocking_get_port相连?由于analysis_port是非阻塞的,ap.write函数调用完成后马上返回,不会等待数据被接收。假如当write函数调用时,blocking_get_port正在忙而没有准备好接收新数据时,此时被write函数写入的my_transaction就需要一个暂存的位置——即fifo
  • 在上例的连接中用到了i_agt的一个成员变量ap,它的定义与my_monitor中ap的定义完全一样:uvm_analysis_port #(my_transaction) ap;
  • 与my_monitor中的ap不同的是不需要对my_agent中的ap进行实例化,只需要在my_agent的connect_phase中将monitor的值赋给它, 相当于是一个指向my_monitor的ap的指针:
function void my_agent::connect_phase(uvm_phase phase);
	super.connect_phase(phase);
	ap = mon.ap;
endfunction

my_agent的connect_phase的执行顺序早于my_env的connect_phase的执行顺序,从而保证执行到i_agt.ap.connect语句时,i_agt.ap不是一个空指针——connect_phase自底向上的意义所在。

6、加入scoreboard

在验证平台中加入了reference model和monitor之后,最后一步是加入scoreboard。my_scoreboard的代码如下:

class my_scoreboard extends uvm_scoreboard;
	my_transaction expect_queue[$];
	uvm_blocking_get_port #(my_transaction) exp_port;
	uvm_blocking_get_port #(my_transaction) act_port;
	`uvm_component_utils(my_scoreboard)
	extern function new(string name, uvm_component parent = null);
	extern virtual function void build_phase(uvm_phase phase);
	extern virtual task main_phase(uvm_phase phase);
endclass

function my_scoreboard::new(string name, uvm_component parent = null);
	super.new(name, parent);
endfunction

function void my_scoreboard::build_phase(uvm_phase phase);
	super.build_phase(phase);
	exp_port = new("exp_port", this);
	act_port = new("act_port", this);
endfunction

task my_scoreboard::main_phase(uvm_phase phase);
	my_transaction get_export, get_actual, tmp_tran;
	bit result;
	super.main_phase(phase);
	fork
		while(1) begin
			exp_port.get(get_expect);
			expect_queue.push_back(get_expect);
		end
		while(1) begin
			act_port.get(get_actual);
			if(expect_queue.size() > 0) begin
				tmp_tran = expect_queue.pop_front();
				result = get_actual.my_compare(tmp_tran);
				if(result) begin
					`uvm_info("my_scoreboard", "Compare SUCCESSFULLY", UVM_LOW)
				end
				else begin
					`uvm_error("my_scoreboard", "Compare FAILED")
					$display("the expect pkt is");
					tmp_tran.my_print();
					$display("the actual pkt is");
					get_actual.my_print();
				end
			end
	join	
endtask

//my_compare函数:逐字段比较两个my_transaction并给出最终的比较结果
function bit my_compare(my_transaction tr);
	bit result;
	if(tr == null)
		`uvm_fatal("my_transaction", "tr is null!!!!")
	result = ((dmac == tr.dmac) && (smac == tr.smac) && (ether_type == tr.ether_type)
				&& (crc == tr.crc));
	if(pload.size() != tr.pload.size())
		result = 0;
	else
		for(int i = 0; i < pload.size(); i++) begin
			if(pload[i] != tr.pload[i])
				result = 0;
		end
	return result;
endfunction
  • my_scoreboard要比较的数据来源于reference model和o_agt的monitor,前者通过exp_port获取,后者通过act_port获取。
  • 在main_phase中通过fork建立起了两个进程,一个处理exp_port的数据,收到数据后把数据放到expect_queue;另外一个处理act_port(DUT的输出)的数据,收到数据后调用my_transaction的my_compare函数比较两组收到的数据。
  • 比较的前提是exp_port要比act_port先收到数据。由于DUT处理数据需要延时,而reference model是基于高级语言的处理,一般不需要延时,因此可保证这个前提的实现。
  • 完成my_scoreboard的定义后,也需要在my_env中将其实例化。此时,整棵UVM树如图所示:
    在这里插入图片描述

7、加入field_automation机制

  • 前文引入my_mointor时在my_transaction中加入了my_print函数;引入reference model时加入my_copy
    函数;引入scoreboard时加入my_compare函数。

  • 上述三个函数虽然各自不同,但对于不同的transaction来说都是类似的: 它们都需要逐字段地对transaction进行某些操作

  • field_automation机制可通过某些规则自动实现这些函数,通过使用uvm_field系列宏来实现:

class my_transaction extends uvm_sequence_item;
	rand bit[47:0] dmac;
	rand bit[47:0] smac;
	rand bit[15:0] ether_type;
	rand byte pload[];
	rand bit[31:0] crc;
	...
	`uvm_object_utils_begin(my_transaction)
		`uvm_field_int(dmac, UVM_ALL_ON)
		`uvm_field_int(smac, UVM_ALL_ON)
		`uvm_field_int(ether_type, UVM_ALL_ON)
		`uvm_field_array_int(ploadm, UVM_ALL_ON)
		`uvm_field_int(crc, UVM_ALL_ON)
	`uvm_object_utils_end
	...
endclass
  • 使用uvm_object_utils_begin和uvm_object_utils_end实现my_transaction的factory注册,在这两个宏中间使用uvm_field宏注册所有字段。uvm_field系列宏随着transaction成员变量的不同而不同。
  • 使用上述宏注册之后,可以直接调用copy、 compare、 print等函数而无需自己定义。这极大简化了验证平台的搭建,提高了效率。例如下例:
task my_model::main_phase(uvm_phase phase);
	my_transaction tr;
	my_transaction new_tr;
	super.main_phase(phase);
	while(1) begin 
		port.get(tr);
		new_tr = new("new_tr");
		new_tr.copy(tr);
		`uvm_info("my_model", "get one transaction, copy and print it:", UVM_LOW)
		new_tr.print();
		ap.write(new_tr);
	end
endtask
  • 引入field_automation机制的另外一大好处是简化了driver和monitor
  • 在前文my_driver的drv_one_pkt任务和my_monitor的collect_one_pkt任务代码很长,但几乎是一些重复性的代码。使用field_automation机制后,drv_one_pkt任务可以简化为:
task my_driver::drive_one_pkt(my_transaction tr);
	byte unsigned data_q[];
	int data_size;
	data_size = tr.pack_bytes(data_q) / 8;//将tr所有字段变成byte流放入data_q中
	`uvm_info("my_driver", "begin to drive one pkt", UVM_LOW)
	repeat(3) @(posedge vif.clk);
	for(int i = 0; i < data_size; i++) begin
		@(posedge vif.clk);
		vif.valid <= 1'b1;
		vif.data <= data_q[i];
	end
	@(posedge vif.clk);
	vif.valid <= 1'b0;
	`uvm_info("my_driver", "end drive one pkt", UVM_LOW);
endtask
  • 在把所有字段变成byte流放入data_q中时,字段按照uvm_field系列宏书写的顺序排列。上述代码中是先放入dmac,再依次放入smac、ether_type、pload、crc。
  • my_monitor的collect_one_pkt可以简化成:
task my_monitor::collect_one_pkt(my_transaction tr);
	byte unsigned data_q[$];
	byte unsigned data_array[];
	logic [7:0] data;
	logic valid = 0;
	int data_size;
	...
	`uvm_info("my_monitor", "begin to collect one pkt", UVM_LOW);
	while(vif.valid) begin
		data_q.push_back(vif.data);
		@(posedge vif.clk);
	end
	data_size = data_q.size();
	data_array = new[data_size];
	for ( int i = 0; i < data_size; i++ ) begin
		data_array[i] = data_q[i];
	end
	tr.pload = new[data_size - 18]; //da sa, e_type, crc
	data_size = tr.unpack_bytes(data_array) / 8;
	`uvm_info("my_monitor", "end collect one pkt", UVM_LOW);
endtask
  • 使用unpack_bytes函数将data_q中的byte流转换成tr中的各个字段。这个函数的输入参数必须是一个动态数组,所以需要先把收集到的、放在data_q中的数据复制到一个动态数组中。由于tr中的pload是一个动态数组,所以需要在调用unpack_bytes之前指定其大小, 这样unpack_bytes函数才能正常工作。
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值