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


开始引入reference model、monitor、scoreboard等验证平台的组件,在这些组件之间信息的传递是基于transaction的

1、加入transaction

  • transaction是抽象的概念。物理协议中的数据交换都是以帧或包为单位的,在一帧或一个包中要定义好各项参数,每个包的大小不一样。很少会有协议是以bit或byte为单位进行数据交换。
  • 以以太网为例,每个包的大小至少是64byte。这个包中要包括源地址、目的地址、包的类型、整个包的CRC校验数据等。
  • transaction就是用于模拟这种实际情况,一笔transaction就是一个包。在不同的验证平台中会有不同的transaction。一个简单的transaction的定义如下:
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; //前面所有数据的校验值
	constraint pload_cons{
		pload.size >= 46;
		pload.size <= 1500; //数据大小限制在46~1500byte
	}
	function bit[31:0] calc_crc();
		return 32'h0;
	endfunction
	function void post_randomize();//当某个类的实例的randomize函数被调用后执行该函数
		crc = calc_crc;
	endfunction
	`uvm_object_utils(my_transaction)
	function new(string name = "my_transaction");
		super.new(name);
	endfunction
endclass
  • UVM所有transaction都从uvm_sequence_item派生,只有这样才可使用sequence机制。
  • 代码使用了uvm_object_utils实现factory机制。在整个仿真期间my_driver是一直存在的,而my_transaction有生命周期,在仿真的某一时间产生,经过driver驱动再经过reference model处理,最终由scoreboard比较完成后其生命周期就结束了。这种类都是派生自uvm_object或uvm_object的派生类,UVM中具有这种特征的类都要使用uvm_object_utils宏来实现。

当完成transaction的定义后, 就可以在my_driver中实现基于transaction的驱动

task my_driver::main_phase(uvm_phase phase);
	my_transaction tr;for(int i = 0; i < 2; i++) begin
		tr = new("tr");
		assert(tr.randomize() with {pload.size == 200;}); //随机化tr
		drive_one_pkt(tr);//将tr的内容驱动到DUT的端口上
	end
	…
endtask
task my_driver::drive_one_pkt(my_transaction tr);
	bit [47:0] tmp_data;
	bit [7:0] data_q[$];
	//push dmac to data_q
	tmp_data = tr.dmac;
	for(int i = 0; i < 6; i++) begin
		data_q.push_back(tmp_data[7:0]);//将tr的数据压入队列data_q中,相当于打包成一个byte流
		tmp_data = (tmp_data >> 8); //

	end
	//push smac to data_q//push ether_type to data_q//push payload to data_q//push crc to data_q
	tmp_data = tr.crc;
	for(int i = 0; i < 4; i++) begin
		data_q.push_back(tmp_data[7:0]);
		tmp_data = (tmp_data >> 8);
	end
	`uvm_info("my_driver", "begin to drive one pkt", UVM_LOW);
	repeat(3) @(posedge vif.clk);
	while(data_q.size() > 0) begin
		@(posedge vif.clk);
		vif.valid <= 1'b1;
		vif.data <= data_q.pop_front(); //将data_q中所有数据弹出并驱动
	end
	@(posedge vif.clk);
	vif.valid <= 1'b0;
	`uvm_info("my_driver", "end drive one pkt", UVM_LOW);
endtask

2、加入env

  • 在验证平台加入组件之前假设这些组件已经定义好,需要考虑在验证平台的什么位置对它们进行实例化,可引入一个容器类解决这个问题——在它里面实例化各个组件。在调用run_test时传递的参数不再是my_driver而是这个容器类,即让UVM自动创建这个容器类的实例
  • 在UVM中这个容器类称为uvm_env
claa my_env extends uvm_env;
	my_driver drv;
	function new(string name = "my_env", uvm_component parent);
		super.new(name, parent);
	endfunction
	virtual function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		drv = my_driver::type_id::create("drv", this); //factory机制的实例化方式
	endfunction
	`uvm_component_utils(my_env)
endclass
  • 所有的env应该派生自uvm_env,且与my_driver一样是component类,应使用uvm_component_utils宏来实现factory的注册。
  • 上例drv的实例化方式是只有使用factory机制注册过的类才能使用这种方式实例化,才能进而使用factory机制中的重载功能。在drv实例化时,传递了两个参数名字drv和this指针,表示my_env。
  • 验证平台中的组件在实例化时都应该使用type_name::type_id::create的方式。
  • 之前通过new函数进行实例化有两个参数:实例名和parent。上例my_driver在uvm_env中实例化,所以它的父结点(parent)就是my_env;UVM通过parent的形式建立起了树形的组织结构,由run_test创建的实例是树根(my_env),且树根名为uvm_test_top(固定名)。树根生长出枝叶(my_driver),这个过程需要在my_env的build_phase中手动实现。无论是树根/树叶,都必须由uvm_component或其派生类继承而来。

整棵UVM树的结构如图所示:
在这里插入图片描述

  • 加入my_env后,整个验证平台中存在两个build_phase(my_env和my_driver)。在UVM的树形结构中,build_phase的执行遵照从树根到树叶的顺序,即先执行my_env的build_phase,再执行my_driver的build_phase。当把整棵树的build_phase都执行完毕后,再执行后面的phase。
  • my_driver在验证平台中的层次结构发生了变化——从树根变成了树叶,所以在top_tb中使用config_db机制传递virtual my_if时要改变相应的路径;同时,run_test的参数也从my_driver变为了my_env
initial begin
 run_test("my_env");
end
initial begin
	uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.drv", "vif", input_if);
end

set函数的第二个参数从uvm_test_top变为了uvm_test_top.drv,其中uvm_test_top是UVM自动创建的树根的名字,而drv则是在my_env的build_phase中实例化drv时传递过去的名字。如果在实例化drv时传递的名字是my_drv,那么set函数的第二个参数中也应该是my_drv:

class my_env extends uvm_env
…
	drv = my_driver::type_id::create("my_drv", this);
… 
endclass
module top_tb;
… 
initial begin
	uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.my_drv", "vif", inpu t_if);
end
endmodule

3、加入monitor

  • 验证平台通过monitor组件监测DUT的行为,只有知道DUT的输入输出信号变化之后,才能根据这些信号变化来判定DUT的行为是否正确
  • driver负责把transaction级别的数据转变成DUT的端口级别并驱动给DUTmonitor的行为与其相对,用于收集DUT的端口数据并将其转换成transaction交给后续的组件处理
    monitor的定义:
class my_monitor extends uvm_monitor;
	virtual my_if vif;
	`uvm_component_utils(my_monitor)
	function new(string name = "my_monitor", uvm_component parent = null);
		super.new(name, parent);
		if(!uvm_config_db#(virtual my_if)::get(this, "", "vif", vif))
			`uvm_fatal("my_monitor", "virtual interface must be set for vif!!!")
	endfunction
	extern task main_phase(uvm_phase phase);
	extern task collect_one_pkt(my_transaction tr);
endclass
task my_monitor::main_phase(uvm_phase phase);
	my_transaction tr;
	while(1) begin
		tr = new("tr");
		collect_one_pkt(tr);
	end
endtask
task my_monitor::collect_one_pkt(my_transaction tr);
	bit[7:0] data_q[$];
	int psize;
	while(1) begin
		@(posedge vif.clk);
		if(vif.valid) break;
	end
	`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
	//pop dmac
	for(int i = 0; i < 6; i++) begin
		tr.dmac = {tr.dmac[39:0], data_q.pop_front()};
	end
	//pop smac
	...
	//pop ether_type
	...
	//pop payload
	...
	//pop crc
	for(int i = 0; i < 4; i++) begin
		tr.crc = {tr.crc[23:0], data_q.pop_front()};
	end
	`uvm_info("my_monitor", "end collect one pkt, print it:", UVM_LOW);
	tr.my_print(); //收集完transaction后打印出来
endtask
  • 所有的monitor类应该派生自uvm_monitor
  • 与driver类似,在my_monitor中也需要有一个virtual my_if;
  • uvm_monitor在仿真中是一直存在的,它是一个component,要使用uvm_component_utils宏注册;
  • 由于monitor需要时刻收集数据,所以在main_phase中使用while(1)循环来实现这一目的。

完成monitor的定义后,可以再env中对其进行实例化:

class my_env extends uvm_env;
	my_driver drv;
	my_monitor i_mon; //监测DUT的输入口
	my_monitor o_mon; //监测DUT的输出口
	...
	virtual function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		drv = my_driver::type_id::create("drv", this);
		i_mon = my_monitor::type_id::create("i_mon", this);
		o_mon = my_monitor::type_id::create("o_mon", this);
	endfunction
	...
endclass

使用monitor的原因:

  • 第一,在一个大型的项目中,driver根据某一协议发送数据,而monitor根据这种协议收集数据, 如果driver和monitor由不同人员实现,那么可以大大减少其中任何一方对协议理解的错误;
  • 第二,在实现代码重用时,使用monitor是非常有必要的。

加入monitor的UVM树结构如图所示:
在这里插入图片描述
在env中实例化monitor后,要在top_tb中使用config_db将input_if和output_if传递给两个monitor:

initial begin
	uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.drv", "vif", input_if);
	uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.i_mon", "vif", input_if);
	uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.o_mon", "vif", output_if);
end

4、封装成agent

可以发现driver和monitor的代码高度相似,其本质是因为它们处理的是同一种协议,在同样规则下做着不同的事情。由于两者的相似性,UVM中通常将二者封装在一起成为一个agent。因此不同的agent就代表了不同的协议。

class my_agent extends uvm_agent;
	my_driver drv;
	my_monitor mon;
	function new(string name, 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);
	`uvm_component_utils(my_agent)
endclass
function void my_agent::build_phase(uvm_phase phase);
	super.build_phase(phase);
	if(is_active == UVM_ACTIVE) begin
		drv = my_driver::type_id::create("drv", this);
	end
	mon = my_monitor::type_id::create("mon", this);
endfunction
function void my_agent::connect_phase(uvm_phase phase);
	super.connect_phase(phase);
endfunction
  • 所有agent都要派生自uvm_agent类且是component类,应使用uvm_component_utils宏来实现factory注册。
  • build_phase根据is_active的值决定是否创建driver的实例,它是uvm_agent的一个成员变量,从UVM的源代码中可以找到它的原型如下:uvm_active_passive_enum is_active = UVM_ACTIVE;
  • uvm_active_passive_enum是一个枚举类型变量, 其定义为:typedef enum bit { UVM_PASSIVE=0, UVM_ACTIVE=1 } uvm_active_passive_enum;
  • 这个枚举变量仅有两个值:UVM_PASSIVE和UVM_ACTIVE。在uvm_agent中is_active的值默认为UVM_ACTIVE,这种模式下是需要实例化driver的。 对于UVM_PASSIVE模式:如下图所示,在输出端口上不需要驱动任何信号,只需要监测信号。在这种情况下端口上只需要monitor,所以driver可以不用实例化。
    在这里插入图片描述

在把driver和monitor封装成agent后,在env中需要实例化agent,而不需要直接实例化driver和monitor:

class my_env extends uvm_env;
	my_agent i_agt; //声明i_agt和o_agt
	my_agent o_agt;
	...
	virtual function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		i_agt = my_agent::type_id::create("i_agt", this); //实例化
		o_agt = my_agent::type_id::create("o_agt", this);
		i_agt.is_active = UVM_ACTIVE; //指定工作模式
		o_agt.is_active = UVM_PASSIVE;
	endfunction
	...
endclass

此时UVM树更新为:
在这里插入图片描述
由于agent的加入,driver和monitor的层次结构改变了,在top_tb中使用config_db设置virtual my_if时要注意改变路径:

initial begin
	uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.i_agt.drv", "vif", input_if);
	uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.i_agt.mon", "vif", input_if);
	vm_config_db#(virtual my_if)::set(null, "uvm_test_top.o_agt.mon", "vif", output_if);
end

在加入my_agent后,UVM的树形结构越来越清晰:

  • 只有uvm_component才能作为树的结点,像my_transaction这种使用uvm_object_utils宏实现的类不能作为UVM树的结点。
  • 在my_env的build_phase中创建i_agt和o_agt的实例;在agent中创建driver和monitor的实例也是在build_phase中。build_phase从树根到树叶的执行顺序建立一棵完整的UVM树
  • UVM要求UVM树最晚在build_phase时段完成,如果在build_phase后的某个phase实例化一个component,UVM会报错。但不是只能在build_phase中执行实例化的动作,还可以在new函数中执行实例化的动作:
function new(string name, uvm_component parent);
	super.new(name, parent);
	if (is_active == UVM_ACTIVE) begin
		drv = my_driver::type_id::create("drv", this);
	end
	mon = my_monitor::type_id::create("mon", this);
endfunction

但这样做无法通过直接赋值的方式向uvm_agent传递is_active的值。在my_env的build_phase(或new函数)中向i_agt和o_agt的is_active赋值不会产生效果。因此i_agt和o_agt都工作在active模式(is_active默认值是UVM_ACTIVE),这与预想相悖。可以在my_agent实例化之前使用config_db语句传递is_active的值解决这个问题:

class my_env extends uvm_env;
	virtual function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		uvm_config_db#(uvm_active_passive_enum)::set(this,"i_agt","is_active",UVM_ACTIVE);
		uvm_config_db#(uvm_active_passive_enum)::set(this,"o_agt","is_active",UVM_PASSIVE);
	endfunction
endclass
class my_agent extends uvm_agent;
	function new(string name, uvm_component parent);
		super.new(name, parent);
		uvm_config_db#(uvm_active_passive_enum)::get(this, "", "is_active", is_active);
		if(is_active == UVM_ACTIVE) begin
			drv = my_driver::type_id::create("drv", this);
		end
		mon = my_monitor::type_id::create("mon", this);
	endfunction
endclass

因此,建议仅在build_phase中完成实例化

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值