目录
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的跳转可以只局限于验证平台的一部分。