UVM实战 卷I学习笔记8——UVM验证平台的运行(3)


objection机制

*objection与task phase

objection字面的意思就是反对、异议。在验证平台中,可通过drop_objection来通知系统可以关闭验证平台。当然,在撤销之前首先要raise_objection

task main_phase(uvm_phase phase);
	phase.raise_objection(this);
	…
	phase.drop_objection(this);
endtask

进入到某一phase时UVM会收集此phase提出的所有objection,并实时监测所有objection是否已经被撤销了,当发现所有都已经撤销后就会关闭此phase进入下一个phase。当所有的phase都执行完毕后,就会调用$finish来将整个的验证平台关掉。

如果UVM发现此phase没有提起任何objection,那么将会直接跳转到下一个phase中。假如验证平台中只有driver提起了异议,而monitor等都没有提起,代码如下所示:

task driver::main_phase(uvm_phase phase);
	phase.raise_objection(this);
	#100;
	phase.drop_objection(this);
endtask
task monitor::main_phase(uvm_phase phase);
	while(1) begin
	…
	end
endtask

driver和monitor的代码都是可以执行的。当进入到monitor后,系统会监测到已经有objection被提起了,所以会执行monitor中的代码。过了100个单位时间后,driver中的objection被撤销。此时UVM监测发现所有objection都被撤销了(因为只有driver raise_objection),于是UVM会直接“杀死”monitor中的无限循环进程,并跳到下一个phase,即post_main_phase()。假设进入main_phase的时刻为0,那么进入post_main_phase的时刻就为100。

如果driver根本就没有raise_objection,并且所有其他component的main_phase里面也没有raise_objection,即driver变成如下情况:

task driver::main_phase(uvm_phase phase);
	#100;
endtask

那么在进入main_phase时UVM发现没有任何objection被提起,虽然driver中有延时100个单位时间的代码,monitor中有一个无限循环,UVM也都不理会,它会直接跳转到post_main_phase,假设进入main_phase的时刻为0,那么进入post_main_phase的时刻还是为0。UVM用户一定要注意:如果想执行一些耗费时间的代码,那么要在此phase下任意一个component中至少提起一次objection

上述结论只适用于12个run-time的phase,对run_phase则不适用。由于run_phase与动态运行的phase是并行运行的,如果12个动态运行的phase有objection被提起,那么run_phase根本不需要raise_objection就可以自动执行,代码如下:

task my_case0::main_phase(uvm_phase phase);
	phase.raise_objection(this);
	#100;
	phase.drop_objection(this);
endtask
task my_case0::run_phase(uvm_phase phase);
	for(int i = 0; i < 9; i++) begin
	#10;
	`uvm_info("case0", "run_phase is executed", UVM_LOW)
	end
endtask

上述代码运行结果是“run_phase is executed”被输出了9次。如果上述run_phase与main_phase中的内容互换:

task my_case0::main_phase(uvm_phase phase);
	for(int i = 0; i < 9; i++) begin
		#10;
		`uvm_info("case0", "main_phase is executed", UVM_LOW)
	end
endtask
task my_case0::run_phase(uvm_phase phase);
	phase.raise_objection(this);
	#100;
	phase.drop_objection(this);
endtask

运行结果是没有任何“main_phase is executed”输出。因此对于run_phase来说,有两个选择可以使其中的代码运
行:第一是其他动态运行的phase中有objection被提起。这种情况下运行时间受其他动态运行phase中objection控制。第二是在run_phase中raise_objection。这种情况下运行时间完全受run_phase控制

component、phase与objection是UVM运行的基础,其相互关系也是比较难以理解的,那么可以参照接下来这个有趣的例子——经典“植物大战僵尸”
在这里插入图片描述

  • 如图所示为一个env与model、scb组成的大楼,每层就是一个phase(没有将12个动态运行的phase全部列出),每层都有三个房间,最外层最大的就是env,而其中又包含了model与scb两个房间,换句话说,由于env是model和scb的父结点,所以model与scb房间其实是房中房。在env、model、scb三个房间中,分别有一个历史遗留的井run_phase(OVM遗留的),可以直通楼顶
  • 在每层的每个房间及各个房间的井中,都有可能存在着僵尸(objection) 及需要通电才能运转的机器(在每个phase中写的代码) 。整大楼处于断电的状态。
  • 一棵叫UVM的植物经历start_of_simulation_phase后于0时刻进入到最顶层(12层):pre_reset_phase。 进入后它首先为本层所有房间及所有井(run_phase)通电,如果房间及井中有机器就会运转起来。这棵植物在通电完毕后开始检测各个房间中有没有僵尸(是否raise_objection),如果任意一个房间中有僵尸,那么就开始消灭这些僵尸直到所有僵尸消失(drop_objection)。当所有僵尸被消灭后就断掉这一层各个房间的电,所有正在运转的机器将会停止运转,然后这棵UVM植物进入下一层。需要注意的是它只断掉所有房间的电,而没有断掉所有的井(run_phase)中的电,所以各个井中如果有机器,那么它们依然在正常运转。(run_phase和12个run_time phase是并行运行的)
  • 如果所有房间都没有僵尸,那么它直接断电并进入下一层,这种情况下所有机器只发出一声轰鸣声,便被紧急终止了。
  • 这棵UVM植物一层一层地消灭僵尸,直到消灭完底层post_shutdown_phase中的僵尸。此时12个动态运行phase全部结束。UVM并不是立即进入到extract_phase,而是开始查看所有的井(run_phase)中是否有僵尸,如果有那么就开始消灭它们直到所有僵尸消失,否则直接断掉井中的电, 所有井中正在运转的机器停止运转。当run_phase中的僵尸也被消灭完毕后,开始进入extract_phase
  • 所以,欲使每一层中的机器(代码)运转起来,只要在这一层的任何一个房间(任意一个component)中加入一个僵尸(raise_objection) 即可。如果僵尸永远不能消失(phase.raise_objection与phase.drop_objection之间是一个无限循环),那么就会一直停留在这一层。

*参数phase的必要性

在UVM中所有phase的函数/任务参数中,都有一个phase:task main_phase(uvm_phase phase);

输入参数中的phase是什么意思?为什么要加入这样一个东西?因为要便于在任何component的main_phase中都能raise_objection,而要raise_objection则必须通过phase.raise_objection来完成,所以必须将phase作为参数传递到main_phase等任务中。如果没有这个phase参数,那么想要提起一个objection就会比较麻烦。

那么类似build_phase等function phase是否可以提起和撤销objection:运行下面代码后系统并不会报错。不过一般不会这么用。phase的引入是为了解决何时结束仿真的问题,更多面向main_phase等task phase,而不是面向function phase。

function void my_case0::build_phase(uvm_phase phase);
	phase.raise_objection(this);
	super.build_phase(phase);
	phase.drop_objection(this);
endfunction

控制objection的最佳选择

在整棵UVM树中树的结点如此之多,那么在什么地方控制objection最合理呢?driver中、monitor中、agent中、scoreboard中或是env中?在之前例子中最初是在driver中raise_objection,但事实上在driver中raise_objection的时刻并不多。这是因为driver中通常都是一个无限循环的代码,如下所示:

task driver::main_phase(uvm_phase phase);
	while(1) begin
		seq_item_port.get_next_item(req);//drive the interface according to the information in req
	end
endtask

如果是在while(1)的前面raise_objection,在while循环的end后面drop_objection:由于无限循环的特性,phase.drop_objection永远不会被执行到。

task driver::main_phase(uvm_phase phase);
	phase.raise_objection(this);
	while(1) begin
		seq_item_port.get_next_item(req);//drive the interface according to the information in req
	end
	phase.drop_objection(this);
endtask

一种常见的思维是将raise_objection放在get_next_item之后,这样就可以避免无限循环的问题:

task driver::main_phase(uvm_phase phase);
	while(1) begin
		seq_item_port.get_next_item(req);
		phase.raise_objection(this);//drive the interface according to the information in req
		phase.drop_objection(this);
	end
endtask

但关键问题是如果其他地方没有raise_objection的话,那么UVM会不等到get_next_item执行完成就已经跳转到post_main_phase。在monitor和reference model中都有类似的情况,它们都是无限循环的。因此一般不会在driver和monitor中控制objection

一般来说,在一个实际的验证平台中,通常会在以下两种objection的控制策略中选择一种:

第一种是在scoreboard中进行控制。前面的例子中scoreboard的main_phase被做成一个无限循环。如果要在scoreboard中控制objection,则需要去除这个无限循环,通过config_db::set的方式设置收集到的transaction的数量pkt_num,当收集到足够数量的transaction后跳出循环:

task my_scoreboard::main_phase(uvm_phase phase);
	phase.raise_objection(this);
	fork
	while (1) begin
		exp_port.get(get_expect);
		expect_queue.push_back(get_expect);
	end
	for(int i = 0; i < pkt_num; i++) begin
		act_port.get(get_actual);
		…
	end
	join_any
	phase.drop_objection(this);
endtask

上述代码将原本的fork…join语句改为了fork…join_any。当收集到足够的transaction后,第二个进程终结,从而跳出fork…join_any,执行drop_objection语句。

第二种:在sequence中提起sequencer的objection,当sequence完成后再撤销此objection

以上两种方式在验证平台中都有应用。其中用得最多的是第二种,这种方式是UVM提倡的方式。UVM的设计哲学就是全部由sequence来控制激励的生成,因此一般情况下只在sequence中控制objection。

set_drain_time的使用

无论任何功能的模块都有其处理延时。如图(a)所示,0时刻DUT开始接收输入,直到p时刻才有数据输出。
在这里插入图片描述
在sequence中,n时刻发送完毕最后一个transaction,如果此时立刻drop_objection,那么最后在n+p时刻DUT输出的包将无法接收到。因此在sequence中,最后一个包发送完毕后,要延时p时间才能drop_objection

virtual task body();
	if(starting_phase != null)
		starting_phase.raise_objection(this);
	repeat (10) begin
		`uvm_do(m_trans)
	end
	#100;
	if(starting_phase != null)
		starting_phase.drop_objection(this);
endtask

要延时的时间与激励有很大关系。图(a)中处理的是短包,所以延时只有p;图(b)中处理的是长包,延时的时间大于图(a)。在随机发送激励时,延时的大小也是随机的。所以无法精确地控制延时,只能根据激励选择一个最大的延时。

这种延时对于所有sequence来说都是必须的,如果在每个sequence中都这样延时显然是不合理的。如果某天DUT对于同样的激励,其处理延时变大,那就要修改所有的延时大小。

考虑到这种情况,UVM为所有的objection设置了drain_time这一属性。所谓drain_time,就是当所有僵尸都被消灭后,UVM植物并不马上进入下一层,而是等待一段时间, 在这段时间内那些正在运行的机器依然在正常地运转,时间一到才会进入下一层。drain_time的设置方式为:

task base_test::main_phase(uvm_phase phase);
	phase.phase_done.set_drain_time(this, 200);
endtask

phase_done是uvm_phase内定义的一个成员变量:uvm_objection phase_done; // phase done objection

当调用phase.raise_objection或者phase.drop_objection时,其实质是调用phase_done的raise_objection和drop_objection。当UVM在main_phase检测到所有objection被撤销后,接下来会检查有没有设置drain_time。如果没有设置则马上进入到post_main_phase,否则延迟drain_time后再进入post_main_phase。如果在post_main_phase及其后都没有提起objection,那么最终会前进到final_phase,结束仿真。

为了检测drain_time的效果,在case0_sequence中使用uvm_info打印出drop_objection,同时在my_case0中打印出进入post_main_phase和final_phase的时间:

class case0_sequence extends uvm_sequence #(my_transaction);
…
	virtual task body();
		…
		#10000;
		`uvm_info("case0_sequence", "drop objection", UVM_LOW)
		…
	endtask
…
endclass
task my_case0::post_main_phase(uvm_phase phase);
	`uvm_info("my_case0", "enter post_main phase", UVM_LOW)
endtask
function void my_case0::final_phase(uvm_phase phase);
	`uvm_info("my_case0", "enter final phase", UVM_LOW)
endfunction

运行上述代码,可以得到如下结果:

# UVM_INFO my_case0.sv(14) @ 10000: uvm_test_top.env.i_agt.sqr@@case0_sequence 
[case0_sequence] drop objection
# UVM_INFO my_case0.sv(45) @ 10200: uvm_test_top [my_case0] enter post_main phase
# UVM_INFO my_case0.sv(49) @ 10200: uvm_test_top [my_case0] enter final phase

可以看到在10000时刻drop_objection,直到10200时刻才进入post_main_phase,两者之间的时间差200就是在base_test中设置的drain_time。

drain_time属于uvm_objection的一个特性。如果只在main_phase中调用set_drain_time函数设置drain_time,但是在其他phase,如configure_phase中没有设置,那么在configure_phase中所有的objection被撤销后会立即进入post_configure_phase。也就是一个phase对应一个drain_time,并非所有phase共享一个drain_time在没有设置的情况下,drain_time的默认值为0

*objection的调试

与phase的调试一样,UVM同样提供了命令行参数来进行objection的调试:

<sim command> +UVM_OBJECTION_TRACE

对上节的例子加入此命令行参数后的部分输出如下:

# UVM_INFO @ 0: main_objection [OBJTN_TRC] Object uvm_test_top.env.i_agt.sqr.case0_sequence 
raised 1 objection(s): count=1 total=1
# UVM_INFO @ 10000: main_objection [OBJTN_TRC] Object uvm_test_top.env.i_agt.sqr.case0_sequence
dropped 1 objection(s): count=0 total=0

在调用raise_objection时count=1表示此次只提起了这一个objection。可使用如下方式一次提起两个objection:

virtual task body();
	if(starting_phase != null)
	starting_phase.raise_objection(this, "case0 objection", 2);
	#10000;
	if(starting_phase != null)
		starting_phase.drop_objection(this, "case0 objection", 2);
endtask

raise_objection的第二个参数是字符串,可以为空,第三个参数为objection的数量。drop_objection的后两个参数与此类似。此时,加入UVM_OBJECTION_TRACE命令行参数的输出结果变为:

# UVM_INFO @ 0: main_objection [OBJTN_TRC] Object uvm_test_top.env.i_agt.sqr.case0_sequence raised 2 
objection(s) (case0 objection): count=2 total=2
# UVM_INFO @ 10000: main_objection [OBJTN_TRC] Object uvm_test_top.env.i_agt.sqr.case0_sequence 
dropped 2 objection(s) (case0 objection): count=0 total=0

除了上述有用信息外,还会输出一些冗余的信息:

# UVM_INFO @ 0: main_objection [OBJTN_TRC] Object uvm_test_top.env.i_agt.sqr added 2 objection(s) to 
its total (raised from source object, case0 objection):count=0 total=2
# UVM_INFO @ 0: main_objection [OBJTN_TRC] Object uvm_test_top.env.i_agt added 2 objection(s) to 
its total (raised from source object, case0 objection):count=0 total=2#UVM_INFO @ 10000: main_objection [OBJTN_TRC] Object uvm_test_top.env.i_agt.sqr subtracted 2 
objection(s) from its total (dropped from source object, case0 objection): count=0 total=0
# UVM_INFO @ 10000: main_objection [OBJTN_TRC] Object uvm_test_top.env.i_agt subtracted 2 
objection(s) from its total (dropped from source object, case0 objection): count=0 total=0

这是因为UVM采用的是树形结构来管理所有的objection。当有一个objection被提起后,会检查从当前component一直到最顶层的uvm_top的objection的数量。上述输出结果中的total就是整个验证平台中所有活跃的( 被提起且没有被撤销的)objection的数量。

domain的应用

domain简介

domain是UVM中一个用于组织不同组件的概念。假设DUT分成两个相对独立的部分,这两个独立的部分可以分别复位、配置、启动,但如果没有domain的概念,那么这两块独立的部分必须同时在reset_phase复位,同时在configure_phase配置,同时进入main_phase开始正常工作。这种协同性当然是没有问题的,但没有体现出独立性。下图画出了这两个部分的driver位于同一domain的情况。
在这里插入图片描述

默认情况下,验证平台中所有component都位于一个名字为common_domain的domain中。若要体现出独立性,那么两个部分的reset_phase、configure_phae、main_phase等就不应该同步。此时就应让其中的一部分从common_domain中独立出来,使其位于不同的domain中。下图列出两个driver位于不同domain的情况。
在这里插入图片描述
domain把两块时钟域隔开,之后两个时钟域内各个动态运行(run_time)的phase就可以不必同步。注意,这里domain只能隔离run-time的phase,对于其他phase还是同步的,即两个domain的run_phase依然是同步的,其他的function phase也是同步的

*多domain的例子

若将某个component置于某个新的domain中,可以使用如下的方式:

class B extends uvm_component;
	uvm_domain new_domain;
	`uvm_component_utils(B)
	function new(string name, uvm_component parent);
		super.new(name, parent);
		new_domain = new("new_domain");
	endfunction
	virtual function void connect_phase(uvm_phase phase);
		set_domain(new_domain);
	endfunction
…
endclass

上述代码新建了一个domain并将其实例化。在connect_phase中通过set_domain将B加入到此domain中。 set_domain函数的原型:function void uvm_component::set_domain(uvm_domain domain, int hier=1);

其第二个参数表示是否递归调用,如果为1则B及其子类都将全部加入到new_domain中。由于子类的实例化一般在build_phase中完成,所以这里一般在connect_phase中调用set_domain

当B加入到new_domain后与其他component(默认位于common domain中)的动态运行phase异步了。在B中:

task B::reset_phase(uvm_phase phase);
	phase.raise_objection(this);
	`uvm_info("B", "enter into reset phase", UVM_LOW)
	#100;
	phase.drop_objection(this);
endtask
task B::post_reset_phase(uvm_phase phase);
	`uvm_info("B", "enter into post reset phase", UVM_LOW)
endtask
task B::main_phase(uvm_phase phase);
	phase.raise_objection(this);
	`uvm_info("B", "enter into main phase", UVM_LOW)
	#500;
	phase.drop_objection(this);
endtask
task B::post_main_phase(uvm_phase phase);
	`uvm_info("B", "enter into post main phase", UVM_LOW)
endtask

在A中:

task A::reset_phase(uvm_phase phase);
	phase.raise_objection(this);
	`uvm_info("A", "enter into reset phase", UVM_LOW)
	#300;
	phase.drop_objection(this);
endtask
task A::post_reset_phase(uvm_phase phase);
	`uvm_info("A", "enter into post reset phase", UVM_LOW)
endtask
task A::main_phase(uvm_phase phase);
	phase.raise_objection(this);
	`uvm_info("A", "enter into main phase", UVM_LOW)
	#200;
	phase.drop_objection(this);
endtask
task A::post_main_phase(uvm_phase phase);
	`uvm_info("A", "enter into post main phase", UVM_LOW)
endtask

在base_test中将A和B实例化:

class base_test extends uvm_test;
	A A_inst;
	B B_inst;
	…
endclass
function void base_test::build_phase(uvm_phase phase);
	super.build_phase(phase);
	A_inst = A::type_id::create("A_inst", this);
	B_inst = B::type_id::create("B_inst", this);
endfunction

运行上述代码后,可以得到如下结果:

# UVM_INFO B.sv(20) @ 0: uvm_test_top.B_inst [B] enter into reset phase
# UVM_INFO A.sv(18) @ 0: uvm_test_top.A_inst [A] enter into reset phase
# UVM_INFO B.sv(26) @ 100: uvm_test_top.B_inst [B] enter into post reset phase
# UVM_INFO B.sv(31) @ 100: uvm_test_top.B_inst [B] enter into main phase
# UVM_INFO A.sv(24) @ 300: uvm_test_top.A_inst [A] enter into post reset phase
# UVM_INFO A.sv(29) @ 300: uvm_test_top.A_inst [A] enter into main phase
# UVM_INFO A.sv(35) @ 500: uvm_test_top.A_inst [A] enter into post main phase
# UVM_INFO B.sv(37) @ 600: uvm_test_top.B_inst [B] enter into post main phase

可以清晰地看到, A和B的动态运行phase已经完全异步了。

*多domain中phase的跳转

上节A和B分别位于不同的domain中,在此种情况下phase的跳转将只局限于某一个domain中。A和base_test的代码与上节相同,B的代码变更为:

class B extends uvm_component;
	uvm_domain new_domain;
	bit has_jumped;
	`uvm_component_utils(B)
	function new(string name, uvm_component parent);
		super.new(name, parent);
		new_domain = new("new_domain");
		has_jumped = 0; //notice
	endfunction
	virtual function void connect_phase(uvm_phase phase);
		set_domain(new_domain);
	endfunction
…
endclass
task B::reset_phase(uvm_phase phase);
	phase.raise_objection(this);
	`uvm_info("B", "enter into reset phase", UVM_LOW)
	#100;
	phase.drop_objection(this);
endtask
task B::main_phase(uvm_phase phase);
	phase.raise_objection(this);
	`uvm_info("B", "enter into main phase", UVM_LOW)
	#500;
	if(!has_jumped) begin
		phase.jump(uvm_reset_phase::get());
		has_jumped = 1'b1; //notice
	end
	phase.drop_objection(this);
endtask

由B的main_phase中跳转至reset_phase。has_jumped控制着跳转只进行一次。运行上述代码后可以得到如下结果:

# UVM_INFO B.sv(24) @ 0: uvm_test_top.B_inst [B] enter into reset phase
# UVM_INFO A.sv(18) @ 0: uvm_test_top.A_inst [A] enter into reset phase
# UVM_INFO B.sv(31) @ 100: uvm_test_top.B_inst [B] enter into main phase
# UVM_INFO A.sv(24) @ 300: uvm_test_top.A_inst [A] enter into post reset phase
# UVM_INFO A.sv(29) @ 300: uvm_test_top.A_inst [A] enter into main phase
# UVM_INFO A.sv(35) @ 500: uvm_test_top.A_inst [A] enter into post main phase
# UVM_INFO /home/landy/uvm/uvm-1.1d/src/base/uvm_phase.svh(1314) @ 600: reporter [PH_JUMP] phase main
(schedule uvm_sched, domain new_domain) is jumping to phase reset
# UVM_INFO B.sv(24) @ 600: uvm_test_top.B_inst [B] enter into reset phase
# UVM_INFO B.sv(31) @ 700: uvm_test_top.B_inst [B] enter into main phase

可以看到B两次进入了reset_phase和main_phase,而A只进入了一次。domain的应用使得phase的跳转可以只局限于验证平台的一部分

  • 8
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值