- 三、objection机制与phase的运行
3.1 objection机制概念说明
objection机制往往控制着uvm phase的运行,objection的字面意思为反对,实际上,UVM验证方法学在phase机制上的建模,使用了一种“举手”的机制,也就是在由各个组件(uvm_component)组成的成员中,通过投票的方式,决定当前阶段是否通过,如果所有组件在某个阶段都参与了举手投票,即存在phase.raise_objection(this),那么就一定要等到“放手”,也就是phase.drop_objection(this)。才能表明这个组件同意完成该phase阶段,只有所有的组件都完成“放手”,这个phase阶段才是真正结束,才可以进行下一个phase的举手表决。
如果不参与投票的组件phase,uvm自然也就不会考虑这个组件是否“放手”,即不会考虑该组件是否同意结束组件,因为其并没有参与投票。也就是说,如果其他组件有参与到投票机制中,个别组件没有参与投票,但是会在当前phase中做一些内容或者动作,那么uvm只会考虑参与投票的phase是否全部同意该phase结束,如果都执行了drop_objection,那么就会结束该phase的运行,并不会考虑为参与投票的phase结果,如果未参与投票组件的phase运行完成,那么就是正常完成,如果未参与投票的组件phase未完成,(一般的,会在这样的组件phase中写while 1循环),那么uvm也不会管他,直接结束该phase的运行。
如果所有的组件在某个phase中都没有过raise和drop objection,会怎么样?uvm会认为当前phase无投票方,会直接结束该phase,进入下一个phase,也即是说,在同一个phase阶段,需要至少有一个组件参与到投票,也就是要raise objection和drop objection一次。
3.2 objection机制对于task phase的影响
在动态运行时间的phase中,使用raise objection和drop objection,可以很好的控制仿真的运行,结合不同的task phase,说明这个过程,假设在driver中存在main phase,且参与举手投票。
1. class driver extends uvm_driver#(uvm_sequence_item);
2. ......
3. extern virtual task drive_pkt();
4. extern virtual task reset_phase(uvm_phase phase);
5. extern virtual task main_phase(uvm_phase phase);
6. ......
7. endclass
8.
9. ......
10. task driver::main_phase(uvm_phase phase);
11. super.main_phase(phase);
12. phase.raise_objection(this);
13. seq_item_port.get_next_item(seq);
14. drive_pkt();
15. seq_item_port.item_done();
16. phase.drop_objection(this);
17. endtask
而在monitor中:
1. class monitor extends uvm_monitor;
2. ......
3. extern virtual task sample_pkt();
4. extern virtual task reset_phase(uvm_phase phase);
5. extern virtual task main_phase(uvm_phase phase);
6. ......
7. endclass
8.
9. ......
10. task monitror::main_phase(uvm_phase phase);
11. while (1) begin
12. sample_pkt();
13. end
14. endtask
此时monitor中的main_phase没有raise和drop objection,而是存在死循环,那么uvm会执行完driver的main_phase的举手机制后,也就是执行完drive_pkt后的drop_objection,就会进入后续phase(post_main_phase),同时直接kill掉monitor main_phase中的死循环。
如果monitor中没有死循环,其内容也会正常执行,只不过如果monitor内部的代码运行时间超过driver的raise和drop,那么剩余的phase代码则不会执行,而是直接kill,如果monitor中运行时间没有超过driver,那么则会正常执行完,因为monitor在driver执行放手之前就已经执行完该phase了。
那么对于run_phase,情况会怎么样?实际上,run_phase和12个子phase之间是并行执行的,也就是说,run_phase如果没有raise和drop objection,那么仿真运行完全靠12个子phase中的raise和drop控制。那么如果run_phase中存在raise和drop,那么仿真启动和停止,完全由run_phase控制,比如driver中有如下代码:
1. class driver extends uvm_driver#(uvm_sequence_item);
2. ......
3. extern virtual task drive_pkt();
4. extern virtual task run_phase(uvm_phase phase);
5. extern virtual task main_phase(uvm_phase phase);
6. ......
7. endclass
8.
9. ......
10. task driver::main_phase(uvm_phase phase);
11. super.main_phase(phase);
12. phase.raise_objection(this);
13. drive_pkt();
14. phase.drop_objection(this);
15. endtask
16.
17. task driver::run_phase(uvm_phase phase);
18. super.run_phase(phase);
19. while (1) begin
20. #10;
21. end
22. endtask
那么当main_phase运行结束后,run_phase也就结束了。
如果将main_phase的raise和drop放到run_phase中,那么main_phase将不会执行,因为验证组件中所有的组件在main_phase中都没有参与投票,uvm直接跳过main_phase,在run_phase中存在raise和drop,那么就由run_phase控制仿真的结束。
那么如果在run_phase和main_phase中都有raise和drop呢?此时就取决于raise和drop之间的延时内容了,也即是说,如果run_phase的时间超过了12个子phase的所有raise和drop的时长之和,那么运行完run_phase,仿真才结束;如果run_phase的raise和drop时长较短,短到来不及执行组件的main_phase,那么自然main_phase没来的及执行,仿真就会结束。也就是说,如果run_phase中存在raise和drop,那么验证环境的phase运行完全由run_phase控制。
总结一下:
- 同一个phase中,参与投票的组件,uvm会等到所有的组件结束raise和drop,会直接跳转到下一个phase,而对于没有参与投票的组件,uvm会直接终止该组件phase的运行,raise和drop是通过phase的传参uvm_phase phase控制。
- 如果所有组件在某个phase上都没有参与投票,那么uvm会直接结束该phase,进入下一个phase,即使组件在当前phase中存在延时类的代码,因此phase如果像运行,要至少进行一次raise和drop。
- 如果一个验证环境中,某个组件的run_phase存在raise和drop,那么仿真环境完全由run_phase控制,其他子phase中如果由raise和drop,那么完全取决于和run_phase之间的运行时间,时间run_phase较长,那么会执行main_phase,如果run_phase较短以至于来不及执行main_phase,那么仿真也会直接停止。
- 一般情况下,不会在run_phase中控制仿真的运行。
- 可以想象,如果在raise和drop objection之间写了死循环,会直接导致仿真挂死,raise和drop objection写在死循环内部则不会导致挂死。或者如果在raise和drop objection之间写了阻塞语句,如等待某个事件或者信号的触发,或者阻塞性的从端口获取某个数据包,都会导致仿真挂死。或者,在sequence的启动同样需要提起objection,那么在如果存在driver和sequence通过get_response和put_response的方式做响应交互,如果sequence在get_response的时候,一直等不到driver put_reponse,或者sequence没有得到相应数量的rsp(通常rsp仲裁队列深度为8),都会从在objection挂住的可能性。
3.3 延时phase运行延时-set_drain_time
想象一个情况,在仿真过程中,DUT往往具备一定的时延,也就是说,一个激励输入给DUT,DUT往往会经过一定时间的处理,那么如果在driver驱动完时序后,phase机制的运行直接将仿真结束,那么将无法采样到正确的结果,因为DUT此时还没有输出,从而得到环境bug,即如下图所示。
uvm考虑了这种情况,为每个phase设置了单独的set_drain_time方法。例如要在main_phase中增加延时:
1. class driver extends uvm_driver#(uvm_sequence_item);
2. ......
3. extern virtual task drive_pkt();
4. extern virtual task run_phase(uvm_phase phase);
5. extern virtual task main_phase(uvm_phase phase);
6. ......
7. endclass
8.
9. ......
10. task driver::main_phase(uvm_phase phase);
11. super.main_phase(phase);
12. phase.phase_done.set_drain_time(this, 200);
13. phase.raise_objection(this);
14. drive_pkt();
15. phase.drop_objection(this);
16. endtask
17.
18. endtask
19.
如上述代码第12行,使用phase.phase_done.set_drain_time(this,200),第二个参数为要设置的drain_time时间,也就是main_phase在结束drop后,会在延时200ns。
这里解释下原理,实际上,在raise和drop的过程中,phase会调用phase_done的raise和drop,phase_done是uvm_phase类的一个成员变量,通常情况下,set_drain_time为0,不会进行drain,如果设置了drain_time,执行drop objection的时候就会有延时。
总结一下:
- uvm phase机制为每一个phase提供了set_drain_time方法,使用为:phase.phase_done.set_drain_time(this, 100)
- phase_done是uvm_phase类中的一个成员变量,执行phase.raise_objection或者phase.drop_objection,实际上是执行phase_done的raise_objection和drop_objection。
- drain_time默认值为0,通过set_drain_time方法改变该值,改变过后,会将当前phase延迟一个设定的drain_time时间。
3.4 objection的最佳控制位置和调试
phase机制的启动和控制实际上还是比较复杂的,一般经验的做法是,对于每个task phase,只在一个组件中进行raise和drop,一般在sequence中控制raise和drop会是一种较为简便的方法,如果在其他组件中对phase机制进行控制,会增加组件之间的phase的调试难度,一般不会在run_phase中控制phase的raise和drop。在sequence中控制,实际上是在sequencer的某个动态运行的phase中(一般是main_phase)进行控制,通用的做法是,在sequence的pre_body任务中,将获取到starting_phase的句柄,如果句柄非空,则启动raise_objection,在post_body任务中进行drop,例如如下代码块:
1. class my_sequence extends uvm_sequence#(uvm_sequence_item);
2. my_trainsaction my_tr;
3. `uvm_object_utils(my_sequence)
4. extern function void new(string name="my_sequence");
5. extern virtual task pre_body();
6. extern virtual task body();
7. extern virtual task post_body();
8. endclass
9.
10. function void my_sequence::new(string name="my_sequence");
11. super.new(name);
12. endfunction:new
13.
14. task my_sequence::pre_body();
15. if (starting_phase != null) begin
16. starting_phase.raise_objection(this);
17. end
18. endtask:pre_body
19.
20. task my_sequence::body();
21. repeat(5) begin
22. `uvm_do(my_tr);
23. end
24. endtask:body
25.
26. task my_sequence::post_body();
27. if (starting_phase != null) begin
28. starting_phase.drop_objection(this);
29. end
30. endtask:pre_body
在tc中,通过default的方式启动,如如下代码:
1. class my_tc extends base_test;
2. my_sequence my_seq;
3. `uvm_component_utils(my_tc)
4. extern function void new(string name="my_tc", uvm_component parent);
5. extern virtual function void build_phase(uvm_phase phase);
6. ......
7. endclass
8.
9. function my_tc::new(string name="my_tc", uvm_component parent);
10. super.new(name, parent);
11. my_seq = my_sequence::type_id::create("my_seq");
12. endfunction:new
13.
14. function my_tc::build_phase(uvm_phase phase);
15. super.build_phase(phase);
16. uvm_configdb#(uvm_object_wrapper)::set(this, "env.i_agt.sqr.main_phase", "defualt", my_sequence::type_id::get());
17. endfunction:build_phase
实际上,执行default sequence的过程,会自动执行sequencer中main phased的:
1. task sequencer::main_phase(uvm_phase phase);
2. ......
3. seq.starting_phase = phase;
4. seq.start(this);
5. ......
6. endtask:main_phase
7.
或者在TC中的main phase直接指定:
1. class my_tc extends base_test;
2. my_sequence my_seq;
3. `uvm_component_utils(my_tc)
4. function void new(string name="my_tc", uvm_component parent);
5. super.new(name, parent);
6. my_seq = my_sequence::type_id::create("my_seq");
7. endfunction
8. ......
9. virtual task main_phase(uvm_phase phase);
10. phase.phase_done.set_drain_time(this, 200);
11. super.main_phase(phase);
12. my_seq.starting_phase = phase;
13. my_seq.start(env.i_agt.sqr);
14. endtask
15. ......
16. endclass
此外,objection机制还提供了调试手段,可以通过vcs命令行键入:
<vcs command> +UVM_OBJECTION_TRACE
此时会在屏幕打印objection的调试输出。
另外,phase.raise_objection(this, “xxxx”, 2)还存在参数,同理drop_objection也是一样:phase.drop_objection(this, “xxxx”, 2)。第二个参数为一个字符串,也可以为空,也就是当前raise/drop objection的名字,第三个参数为当前raise/drop objection的次数。
uvm通过树形结构管理objection,当一个组件提起objection,uvm会检查当前component一直到最顶层uvm_top的所有objection的数量,统计活跃值。