芯片验证 | UVM的objection机制

简介: 芯片验证 | UVM的objection机制

了解完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的数量。

目录
相关文章
|
7月前
|
测试技术 Shell 开发者
UVM与验证环境一文通
UVM与验证环境一文通
401 0
|
6月前
|
芯片 存储 算法
芯片验证 | Formal验证技术总结
芯片验证 | Formal验证技术总结
210 0
芯片验证 | Formal验证技术总结
|
6月前
|
测试技术 芯片 开发者
芯片验证 | UVM的phase机制
芯片验证 | UVM的phase机制
298 0
|
6月前
|
芯片
芯片验证 | UVM的domain机制
芯片验证 | UVM的domain机制
110 0
|
6月前
|
存储 算法 C语言
芯片验证 | SystemVerilog使用简介
芯片验证 | SystemVerilog使用简介
149 0
|
7月前
MSR04X1 串行通信模块程序调入方案
MSR04X1 串行通信模块程序调入方案
|
7月前
|
数据建模 测试技术 Shell
【原型验证】SoC 原型验证环境说明
【原型验证】SoC 原型验证环境说明
208 0
|
前端开发
【前端验证】对uvm_info宏的进一步封装尝试
【前端验证】对uvm_info宏的进一步封装尝试
UART子系统(八)UART驱动情景分析_注册
UART子系统(八)UART驱动情景分析_注册
80 1
UART子系统(八)UART驱动情景分析_注册
|
算法 测试技术 异构计算
FPGA:逻辑功能的仿真与验证
⭐本专栏针对FPGA进行入门学习,从数电中常见的逻辑代数讲起,结合Verilog HDL语言学习与仿真,主要对组合逻辑电路与时序逻辑电路进行分析与设计,对状态机FSM进行剖析与建模。
128 0
FPGA:逻辑功能的仿真与验证