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


phase机制

task phase与function phase

UVM中的phase按照其是否消耗仿真时间($time打印出的时间)的特性,可以分成一类是function phase(如build_phase、connect_phase等),这些phase都不耗费仿真时间,通过函数来实现; 另一类是task phase(如run_phase等),它们耗费仿真时间,通过任务来实现。 给DUT施加激励、监测DUT的输出都是在这些phase中完成的。下图中灰色背景所示的是taskphase,其他为function phase。
在这里插入图片描述

class my_case0 extends base_test;
	string tID = get_type_name();
	…
	virtual function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		`uvm_info(tID, "build_phase is executed", UVM_LOW)
	endfunction
	…
	virtual function void start_of_simulation_phase(uvm_phase phase);
		super.start_of_simulation_phase(phase);
		`uvm_info(tID, "start_of_simulation_phase is executed", UVM_LOW)
	endfunction
	virtual task run_phase(uvm_phase phase);
		`uvm_info(tID, "run_phase is executed", UVM_LOW)
	endtask
	virtual task pre_reset_phase(uvm_phase phase);
		`uvm_info(tID, "pre_reset_phase is executed", UVM_LOW)
	endtask
	…
	virtual task post_shutdown_phase(uvm_phase phase);
		`uvm_info(tID, "post_shutdown_phase is executed", UVM_LOW)
	endtask
	virtual function void extract_phase(uvm_phase phase);
		super.extract_phase(phase);
		`uvm_info(tID, "extract_phase is executed", UVM_LOW)
	endfunction
	…
	virtual function void final_phase(uvm_phase phase);
		super.final_phase(phase);
		`uvm_info(tID, "final_phase is executed", UVM_LOW)
	endfunction
endclass

运行上述代码可以看到各phase被依次执行。对于function phase来说,在同一时间只有一个phase在执行;但task phase中的12个小的phase并行运行。后者称为动态运行的phase。对于task phase,从全局的观点来看其顺序大致如下:

fork
	begin
		run_phase();
	end
	begin
		pre_reset_phase();
		reset_phase();
		post_reset_phase();
		pre_configure_phase();
		configure_phase();
		post_configure_phase();
		pre_main_phase();
		main_phase();
		post_main_phase();
		pre_shutdown_phase();
		shutdown_phase();
		post_shutdown_phase();
	end
join

UVM提供了如此多的phase,在一般的应用中,无论是function phase还是task phase都不会将它们全部用上。 使用频率最高的是build_phase、connect_phase和main_phase。这么多phase除了方便验证人员将不同的代码写在不同的phase外,还有利于其他验证方法学向UVM迁移。一般的验证方法学都会把仿真分成不同的阶段,但这些阶段的划分通常没有UVM分得这么多、这么细致。所以一般来说,当其他验证方法学向UVM迁移时,总能找到一个phase来对应原来方法学中的仿真阶段,这为迁移提供了便利

动态运行phase

动态运行phase是UVM1.0引入的新的phase,其他phase则在UVM1.0之前(即UVM1.0EA版和OVM中)就已经存在了。

UVM为什么引入这12个小phase呢?分成小的phase是为了实现更加精细化的控制。reset、configure、main、shutdown四个phase是核心,通常用于模拟DUT的正常工作方式,在reset_phase对DUT进行复位、初始化等操作,在configure_phase进行DUT的配置,DUT的运行主要在main_phase完成,shutdown_phase是做一些与DUT断电相关的操作。通过细分实现对DUT更加精确的控制。假设要在运行过程中对DUT进行一次复位操作,在没有这些细分的phase之前,这种操作要在scoreboard、reference model等加入一些额外的代码来保证验证平台不会出错。但有了这些小phase之后,分别在scoreboard、reference model及其他部分的reset_phase写好相关代码,之后如果想做一次复位操作,那么只要通过phase的跳转,就会自动跳转回reset_phase。

phase的执行顺序

假如UVM不使用自上而下的方式执行build_phase会是什么情况:UVM的设计哲学就是在build_phase中做实例化的工作,driver和monitor都是agent的成员变量,所以它们的实例化都要在agent的build_phase中执行。如果在agent的build_phase之前执行driver的build_phase,此时driver还根本没有实例化,所以调driver.build_phase只会引发错误。

UVM是在build_phase中做实例化工作实例化指的是uvm_component及其派生类变量的实例化,假如在其他phase实例化一个uvm_component,那么系统会报错。uvm_object的实例化则可以在任何phase完成

除了自上而下的执行方式外,UVM的phase还有一种执行方式是自下而上。事实上,除了build_phase之外,所有不耗费仿真时间的phase(即function phase)都是自下而上执行的。如对于connect_phase即先执行driver和monitor的connect_phase,再执行agent的connect_phase。

无论是自上而下还是自下而上,都只适应于UVM树中有直系关系的component。对于同一层次的、具有兄弟关系的component,它们的执行顺序如何呢?通过分析源代码可以发现执行顺序是按照字典序的。这里的字典序的排序依据new时指定的名字。假如monitor在new时指定的名字为aaa,而driver的名字为bbb,那么将会先执行monitor的build_phase。反之若monitor为mon,driver为drv,那么将会先执行driver的build_phase:

class my_env extends uvm_env;
	A A_inst0;
	A A_inst1;
	A A_inst2;
	A A_inst3;
	…
	virtual function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		A_inst0 = A::type_id::create("dddd", this);
		A_inst1 = A::type_id::create("zzzz", this);
		A_inst2 = A::type_id::create("jjjj", this);
		A_inst3 = A::type_id::create("aaaa", this);
	endfunction
	`uvm_component_utils(my_env)
endclass

其中A的代码为:

class A extends uvm_component;
	…
endclass
	function void A::build_phase(uvm_phase phase);
		super.build_phase(phase);
		`uvm_info("A", "build_phase", UVM_LOW)
	endfunction
	function void A::connect_phase(uvm_phase phase);
		super.connect_phase(phase);
		`uvm_info("A", "connect_phase", UVM_LOW)
	endfunction

输出的结果将会是:

# UVM_INFO A.sv(16) @ 0: uvm_test_top.env.aaaa [A] build_phase
# UVM_INFO A.sv(16) @ 0: uvm_test_top.env.dddd [A] build_phase
# UVM_INFO A.sv(16) @ 0: uvm_test_top.env.jjjj [A] build_phase
# UVM_INFO A.sv(16) @ 0: uvm_test_top.env.zzzz [A] build_phase
# UVM_INFO A.sv(21) @ 0: uvm_test_top.env.aaaa [A] connect_phase
# UVM_INFO A.sv(21) @ 0: uvm_test_top.env.dddd [A] connect_phase
# UVM_INFO A.sv(21) @ 0: uvm_test_top.env.jjjj [A] connect_phase
# UVM_INFO A.sv(21) @ 0: uvm_test_top.env.zzzz [A] connect_phase

可以清晰地看出无论是自上而下(build_phase)还是自下而上(connect_phase)的phase,其执行顺序都与实例化的顺序无关,而是严格按照实例化时指定名字的字典序

只是这个顺序是在UVM1.1d源代码中找到的,UVM并未保证一直会是这个顺序。如果代码的执行必须依赖于这种顺序,例如要求必须先执行driver的build_phase再执行monitor的build_phase,那么应该立即修改代码,杜绝这种依赖性在代码中出现。

task_phase是按照自下而上的顺序执行的。但与前面function phase自下而上执行不同的是,这种task phase是耗费时间的,所以它并不是等到“下面”的phase(如driver的run_phase)执行完才执行“上面”的phase(如agent的run_phase),而是将这些run_phase通过fork…join_none的形式全部启动。所以,更准确的说法是自下而上的启动且同时在运行

对于同一component,其12个run-time的phase是顺序执行的,但仅仅是顺序执行,并不是前面一个phase执行完就立即执行后一个phase。以main_phase和post_main_phase为例,对于A component来说,其main_phase在0时刻开始执行,100时刻执行完毕:

task A::main_phase(uvm_phase phase);
	phase.raise_objection(this);
	`uvm_info("A", "main phase start", UVM_LOW)
	#100;
	`uvm_info("A", "main phase end", UVM_LOW)
	phase.drop_objection(this);
endtask
task A::post_main_phase(uvm_phase phase);
	phase.raise_objection(this);
	`uvm_info("A", "post main phase start", UVM_LOW)
	#300;
	`uvm_info("A", "post main phase end", UVM_LOW)
	phase.drop_objection(this);
endtask

对于B component来说,其main_phase在0时刻开始执行,200时刻执行完毕:

task B::main_phase(uvm_phase phase);
	phase.raise_objection(this);
	`uvm_info("B", "main phase start", UVM_LOW)
	#200;
	`uvm_info("B", "main phase end", UVM_LOW)
	phase.drop_objection(this);
endtask
task B::post_main_phase(uvm_phase phase);
	phase.raise_objection(this);
	`uvm_info("B", "post main phase start", UVM_LOW)
	#200;
	`uvm_info("B", "post main phase end", UVM_LOW)
	phase.drop_objection(this);
endtask

此时整个验证平台的main_phase才执行完毕,接下来执行post_main_phase,即A和B的post_main_phase都是在200时刻开始执行。假设A的post_main_phase执行完毕需要300个时间单位,而B只需要200个时间单位,无论是A或者B,其后续都没有其他耗时间的phase了,整个验证平台会在500时刻关闭。上述代码的执行结果如下:

# UVM_INFO B.sv(15) @ 0: uvm_test_top.env.B_inst [B] main phase start
# UVM_INFO A.sv(21) @ 0: uvm_test_top.env.A_inst [A] main phase start
# UVM_INFO A.sv(23) @ 100: uvm_test_top.env.A_inst [A] main phase end
# UVM_INFO B.sv(17) @ 200: uvm_test_top.env.B_inst [B] main phase end
# UVM_INFO B.sv(23) @ 200: uvm_test_top.env.B_inst [B] post main phase start
# UVM_INFO A.sv(29) @ 200: uvm_test_top.env.A_inst [A] post main phase start
# UVM_INFO B.sv(25) @ 400: uvm_test_top.env.B_inst [B] post main phase end
# UVM_INFO A.sv(31) @ 500: uvm_test_top.env.A_inst [A] post main phase end

A的main_phase在100时刻结束,其post_main_phase在200时刻开始执行。100~200时刻,A处于等待B的状态,除了等待不做任何事情。B的post_main_phase在400时刻结束,之后就处于等待A的状态。无论A还是B都存在一段空白等待时间。但从整个验证平台的角度来看,各个task phase之间是没有任何空白的。

这种同步不仅适用于不同component的动态运行(run-time)phase之间,还适用于run_phase与run_phase之间。这两种同步都是不同component之间的相同phase之间的同步。除此之外,还存在一种run_phase与post_shutdown_phase之间的同步,其特殊之处在于它是同一个component的不同类型phase(即run_phase与run-time phase)之间的同步,即同一个component的run_phase与其post_shutdown_phase全部完成才会进入下一个phase(extract_phase)。假设整个验证平台中只在A中控制objection:

task A::post_shutdown_phase(uvm_phase phase);
	phase.raise_objection(this);
	`uvm_info("A", "post shutdown phase start", UVM_LOW)
	#300;
	`uvm_info("A", "post shutdown phase end", UVM_LOW)
	phase.drop_objection(this);
endtask
task A::run_phase(uvm_phase phase);
	phase.raise_objection(this);
	`uvm_info("A", "run phase start", UVM_LOW)
	#200;
	`uvm_info("A", "run phase end", UVM_LOW)
	phase.drop_objection(this);
endtask

上述代码中,post_shutdown_phase在300时刻完成,而run_phase在200时刻完成。验证平台进入extract_phase的时刻是300。从整个验证平台的角度来说,只有所有component的run_phase和post_shutdown_phase都完成才能进入extract_phase。无论是run-time phase之间的同步,还是run_phase与post_shutdown_phase之间的同步,或者是run_phase与run_phase之间的同步,它们都与objection机制密切相关

UVM树的遍历

在这里插入图片描述
如图,除了兄弟关系的component,还有叔侄关系的component,如my_scoreboard与my_driver,从树的层次结构上来说,scoreboard级别是高于driver的,但这两者build_phase的执行顺序其实也是不确定的。这两者的执行顺序除了上节提到的字典序外,还用到了图论中树的遍历方式:广度优先或是深度优先。

  • 广度优先,指的是如果i_agt的build_phase执行完毕后,接下来执行的是其兄弟component的build_phase,当所有兄弟的build_phase执行完毕后,再执行其孩子的build_phase。
  • 深度优先,指的是如果i_agt的build_phase执行完毕后,它接下来执行的是其子类的build_phase,如果子类还有子类,那么再继续执行下去,直到整棵以i_agt为树根的UVM子树的build_phase执行完毕,之后再执行i_agt的兄弟的build_phase。

UVM中采用深度优先的原则,对于scoreboard及driver的build_phase的执行顺序,i_agt实例化时名字为“i_agt”,而scb为“scb”,那么i_agt的build_phase先执行,在执行完毕后,接下来执行driver、monitor及sequencer的build_phase。当全部执行完毕后再执行scoreboard的build_phase:

# UVM_INFO my_agent.sv(29) @ 0: uvm_test_top.env.i_agt [agent] build_phase
# UVM_INFO my_driver.sv(16) @ 0:1 uvm_test_top.env.i_agt.drv [driver] build_phase
# UVM_INFO my_agent.sv(29) @ 0: uvm_test_top.env.o_agt [agent] build_phase
# UVM_INFO my_scoreboard.sv(23) @ 0: uvm_test_top.env.scb [scb] build_phase

反之,如果i_agt实例化时是bbb而scb为aaa,则会先执行scb的build_phase,再执行i_agt的build_phase,接下来是driver、monitor及sequencer的build_phase。

super.phase的内容

在前面代码中有时出现super.xxxx_phase语句,有时又不会。 如在main_phase中有时出现super.main_phase,有时又不会;在build_phase中一般会出现super.build_phase。那么uvm_component在其各个phase中都默认做了哪些事情呢?哪些phase应该加上super.xxxx_phase,哪些又可以不加呢?

对于build_phase来说,uvm_component对其做的最重要的事情就是自动获取通过config_db::set设置的参数。如果要关掉这个功能,可以在自己的build_phase中不调用super.build_phase

除了build_phase外,UVM在其他phase中几乎没有做任何相关的事情:

function void uvm_component::connect_phase(uvm_phase phase);
	connect();
	return;
endfunction
function void uvm_component::start_of_simulation_phase(uvm_phase phase);
	start_of_simulation();
	return;
endfunction
function void uvm_component::end_of_elaboration_phase(uvm_phase phase);
	end_of_elaboration();
	return;
endfunction
task uvm_component::run_phase(uvm_phase phase);
	run();
	return;
endtask
function void uvm_component::extract_phase(uvm_phase phase);
	extract();
	return;
endfunction
function void uvm_component::check_phase(uvm_phase phase);
	check();
	return;
endfunction
function void uvm_component::report_phase(uvm_phase phase);
	report();
	return;
endfunction
function void uvm_component::connect(); return; endfunction
function void uvm_component::start_of_simulation(); return; endfunction
function void uvm_component::end_of_elaboration(); return; endfunction
task uvm_component::run(); return; endtask
function void uvm_component::extract(); return; endfunction
function void uvm_component::check(); return; endfunction
function void uvm_component::report(); return; endfunction
function void uvm_component::final_phase(uvm_phase phase); return; endfunction
task uvm_component::pre_reset_phase(uvm_phase phase); return; endtask
task uvm_component::reset_phase(uvm_phase phase); return; endtask
task uvm_component::post_reset_phase(uvm_phase phase); return; endtask
task uvm_component::pre_configure_phase(uvm_phase phase); return; endtask
task uvm_component::configure_phase(uvm_phase phase); return; endtask
task uvm_component::post_configure_phase(uvm_phase phase); return; endtask
task uvm_component::pre_main_phase(uvm_phase phase); return; endtask
task uvm_component::main_phase(uvm_phase phase); return; endtask
task uvm_component::post_main_phase(uvm_phase phase); return; endtask
task uvm_component::pre_shutdown_phase(uvm_phase phase); return; endtask
task uvm_component::shutdown_phase(uvm_phase phase); return; endtask
task uvm_component::post_shutdown_phase(uvm_phase phase); return; endtask

由如上代码可以看出除build_phase外,在写其他phase时完全可以不加上super.xxxx_phase语句。这个结论只适用于直接扩展自uvm_component的类。如果是扩展自用户自定义的类,如base_test类,且在其某个phase,如connect_phase中定义了一些重要内容,那么在测试用例的connect_phase中就不应省super.connect_phase。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值