了解完phase机制后,发现这不了解objection极其不合适,于是好吧好吧,再学习一点。
以下内容全部来自白皮书。
1 objection与task phase
objection字面的意思就是反对、异议。在验证平台中,可以通过drop_objection来通知系统可以关闭验证平台。
当然,在撤销之前首先要raise_objection。想象一下,如果读者与别人交流时事先并没有提出异议,然后忽然说:我撤销刚才的反对意见(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就可以自动执行,代码如下:
文件:src/ch5/section5.2/5.2.1/objection1/my_case0.sv 14 task my_case0::main_phase(uvm_phase phase); 15 phase.raise_objection(this); 16 #100; 17 phase.drop_objection(this); 18 endtask 19 20 task my_case0::run_phase(uvm_phase phase); 21 for(int i = 0; i < 9; i++) begin 22 #10; 23 `uvm_info("case0", "run_phase is executed", UVM_LOW) 24 end 25 endtask
在上述代码运行结果中,可以看到“run_phase is executed”被输出了9次。反之,如果上述run_phase与main_phase中的内容互换:
文件:src/ch5/section5.2/5.2.1/objection2/my_case0.sv 14 task my_case0::main_phase(uvm_phase phase); 15 for(int i = 0; i < 9; i++) begin 16 #10; 17 `uvm_info("case0", "main_phase is executed", UVM_LOW) 18 end 19 endtask 20 21 task my_case0::run_phase(uvm_phase phase); 22 phase.raise_objection(this); 23 #100; 24 phase.drop_objection(this); 25 endtask
由运行结果中可以看到,没有任何“main_phase is executed”输出。因此对于run_phase来说,有两个选择可以使其中的代码运行:
第一是其他动态运行的phase中有objection被提起。在这种情况下,运行时间受其他动态运行phase中objection控制,run_ phase只能被动地接受。
第二是在run_phase中raise_objection。这种情况下运行时间完全受run_phase控制。
component、phase与objection是UVM运行的基础,其相互关系也是比较难以理解的。如果读者看了上面的例子依然对这三者的关系很迷惑,那么可以参照接下来这个有趣的例子。
如图5-4所示为一个env与model、scb组成的大楼,每一层就是一个phase(为了方便起见,图中并没有将12个动态运行的phase全部列出,而只列出了reset_phase等4个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)中的电,所以各个井中如果有机器,那么它们依然在正常运转。
如果所有的房间中都没有僵尸,那么它直接断电并进入下一层,在这种情况下,所有的机器只发出一声轰鸣声,便被紧急终止了。这棵UVM植物一层一层地消灭僵尸,一直到消灭完底层post_shutdown_phase中的僵尸。
此时,12个动态运行phase全部结束,它们中的僵尸全部被消灭完毕。这棵UVM植物并不是立即进入到extract_phase,而是开始查看所有的井(run_phase)中是否有僵尸,如果有那么就开始消灭它们,一直到所有的僵尸消失,否则直接断掉井中的电,所有井中正在运转的机器停止运转。当run_phase中的僵尸也被消灭完毕后,开始进入extract_phase。
所以,欲使每一层中的机器(代码)运转起来,只要在这一层的任何一个房间(任意一个component)中加入一个僵尸(raise_objection)即可。如果僵尸永远不能消失(phase.raise_objection与phase.drop_objection之间是一个无限循环),那么就会一直停留在这一层。
2 参数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呢?
文件:src/ch5/section5.2/5.2.2/my_case0.sv 35 function void my_case0::build_phase(uvm_phase phase); 36 phase.raise_objection(this); 37 super.build_phase(phase); 38 phase.drop_objection(this); 39 endfunction
运行上述代码后系统并不会报错。不过,一般不会这么用。phase的引入是为了解决何时结束仿真的问题,它更多面向main_phase等task phase,而不是面向function phase。
3 控制objection的最佳选择
在整棵UVM树中,树的结点是如此之多,那么在什么地方控制objection最合理呢?driver中、monitor中、agent中、scoreboard中抑或是env中?
在第2章的例子中,最初是在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:
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
由于无限循环的特性,phase.drop_objection永远不会被执行到。
一种常见的思维是将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中进行控制。在2.3.6节中,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语句。
第二种,如在第2章中介绍的例子那样,在sequence中提起sequencer的objection,当sequence完成后,再撤销此objection。
以上两种方式在验证平台中都有应用。其中用得最多的是第二种,这种方式是UVM提倡的方式。 UVM的设计哲学就是全部由sequence来控制激励的生成,因此一般情况下只在sequence中控制objection。
4 set_drain_time的使用
无论任何功能的模块,都有其处理延时。如图5-5a所示,0时刻DUT开始接收输入,直到p时刻才有数据输出。
在sequence中,n时刻发送完毕最后一个transaction,如果此时立刻drop_objection,那么最后在n+p时刻DUT输出的包将无法接收到。
因此,在sequence中,最后一个包发送完毕后,要延时p时间才能drop_objection:
在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
UVM为所有的objection设置了drain_time这一属性。所谓drain_time,用5.2.1节中最后的例子来说,就是当所有的僵尸都被消灭后,UVM植物并不马上进入下一层,而是等待一段时间,在这段时间内,那些正在运行的机器依然在正常地运转,时间一到才会进入下一层。drain_time的设置方式为:
文件:src/ch5/section5.2/5.2.4/base_test.sv 24 task base_test::main_phase(uvm_phase phase); 25 phase.phase_done.set_drain_time(this, 200); 26 endtask
phase_done是uvm_phase内定义的一个成员变量:
来源:UVM源代码 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,结束仿真。
5 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:
文件:src/ch5/section5.2/5.2.5/my_case0.sv 10 virtual task body(); 11 if(starting_phase != null) 12 starting_phase.raise_objection(this, "case0 objection", 2); 13 #10000; 14 if(starting_phase != null) 15 starting_phase.drop_objection(this, "case0 objection", 2); 16 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的数量。