芯片验证 | UVM的phase机制

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

最近在尝试搭建UVM的环境,作为一个入门新手,关于phase的概念不是很清晰,这里记录一下。

看了蛮多的Blog,内容都很不错。最后还是打开了白皮书,书的内容更加的全面。以下内容全部来自白皮书。


phase机制

1 task phase与function phase

UVM中的phase,按照其是否消耗仿真时间($time打印出的时间)的特性,可以分成两大类,

  • 一类是function phase,如build_phase、connect_phase等,这些phase都不耗费仿真时间,通过函数来实现;
  • 另外一类是task phase,如run_phase等,它们耗费仿真时间,通过任务来实现

给DUT施加激励、监测DUT的输出都是在这些phase中完成的。在图5-1中,灰色背景所示的是task phase,其他为function phase。

上述所有的phase都会按照图中的顺序自上而下自动执行:

文件:src/ch5/section5.1/5.1.1/my_case0.sv
4 class my_case0 extends base_test;
5   string tID = get_type_name();
11  virtual function void build_phase(uvm_phase phase);
12    super.build_phase(phase);
13     uvm_info(tID, "build_phase is executed", UVM_LOW)
14  endfunction
15 …
26  virtual function void start_of_simulation_phase(uvm_phase phase);
27    super.start_of_simulation_phase(phase);
28    uvm_info(tID, "start_of_simulation_phase is executed", UVM_LOW)
29  endfunction
30 31  virtual task run_phase(uvm_phase phase);
32    `uvm_info(tID, "run_phase is executed", UVM_LOW)
33  endtask
34 35  virtual task pre_reset_phase(uvm_phase phase);
36    `uvm_info(tID, "pre_reset_phase is executed", UVM_LOW)
37  endtask
79  virtual task post_shutdown_phase(uvm_phase phase);
80    `uvm_info(tID, "post_shutdown_phase is executed", UVM_LOW)
81  endtask
82 83  virtual function void extract_phase(uvm_phase phase);
84    super.extract_phase(phase);
85    `uvm_info(tID, "extract_phase is executed", UVM_LOW)
86  endfunction
98  virtual function void final_phase(uvm_phase phase);
99    super.final_phase(phase);
100   `uvm_info(tID, "final_phase is executed", UVM_LOW)
101   endfunction
102 103 104 endclass

运行上述代码,可以看到各phase被依次执行。

在这些phase中,令人疑惑的是task phase。

  • 对于function phase来说,在同一时间只有一个phase在执行
  • 但是task phase中,run_phase和pre_reset_phase等12个小的phase并行运行

后者称为动态运行(run-time)的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来对应原来方法学中的仿真阶段,这为迁移提供了便利。

2 动态运行phase

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

UVM为什么引入这12个小的phase呢?

分成小的phase是为了实现更加精细化的控制。

reset、configure、main、shutdown四个phase是核心,这四个phase通常模拟DUT的正常工作方式,

  • 在reset_phase对DUT进行复位、初始化等操作,
  • 在configure_phase则进行DUT的配置,
  • DUT的运行主要在main_phase完成,
  • shutdown_phase则是做一些与DUT断电相关的操作。

通过细分实现对DUT更加精确的控制。假设要在运行过程中对DUT进行一次复位(reset)操作,在没有这些细分的phase之前,这种操作要在scoreboard、reference model等加入一些额外的代码来保证验证平台不会出错。

但是有了这些小的phase之后,分别在scoreboard、reference model及其他部分(如driver、monitor等)的reset_ phase写好相关代码,之后如果想做一次复位操作,那么只要通过phase的跳转,就会自动跳转回reset_phase。

3 phase的执行顺序

1.1节笼统地说明了phase是自上而下执行的,而在3.5.4节时曾经提到过,build_ phase是一种自上而下执行的。但这两种“自上而下”是有不同含义的。

1.1节中的自上而下是时间的概念,不同的phase按照图5-1中所示的phase顺序自上而下执行。而3.5.4节所说的自上而下是空间的概念,即在图3-2中,先执行的是my_case的build_phase,其次是env的build_phase,一层层往下执行。这种自上而下的顺序其实是唯一的选择。

心中有着S的视觉

对于UVM树来说,共有三种顺序可以选择,一是自上而下,二是自下而上,三是随机序。最后一种方式是不受人控制的,在编程当中,这种不受控制的代码越少越好。因此可以选择的无非就是自上而下或者自下而上。

假如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完成,当然也包括build_phase了。

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

无论是自上而下还是自下而上,都只适应于UVM树中有直系关系的component对于同一层次的、具有兄弟关系的component,如driver与monitor,它们的执行顺序如何呢?

一种猜测是按照实例化的顺序。如代码清单5-3中,A_inst0到A_inst3的build_phase是顺序执行的,这种猜测是错误的。

通过分析源代码,读者可以发现执行顺序是按照字典序的。这里的字典序的排序依据new时指定的名字。假如monitor在new时指定的名字为aaa,而driver的名字为bbb,那么将会先执行monitor的build_phase。

反之若monitor为mon,driver为drv,那么将会先执行driver的build_phase。如下面的代码:

文件:ch5/section5.1/5.1.3/brother/my_env.sv
4 class my_env extends uvm_env;
5 6   A   A_inst0;
7   A   A_inst1;
8   A   A_inst2;
9   A   A_inst3;
16   virtual function void build_phase(uvm_phase phase);
17     super.build_phase(phase);
18 19     A_inst0 = A::type_id::create("dddd", this);
20     A_inst1 = A::type_id::create("zzzz", this);
21     A_inst2 = A::type_id::create("jjjj", this);
22     A_inst3 = A::type_id::create("aaaa", this);
23 24   endfunction
25 26   `uvm_component_utils(my_env)
27 endclass

其中A的代码为:

文件:ch5/section5.1/5.1.3/brother/A.sv
3 class A extends uvm_component;
12 endclass
13 14 function void A::build_phase(uvm_phase phase);
15   super.build_phase(phase);
16   `uvm_info("A", "build_phase", UVM_LOW)
17 endfunction
18 19 function void A::connect_phase(uvm_phase phase);
20   super.connect_phase(phase);
21   `uvm_info("A", "connect_phase", UVM_LOW)
22 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,那么应该立即修改代码,杜绝这种依赖性在代码中出现。

类似run_phase、main_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时刻执行完毕:

所以,更准确的说法是自下而上的启动,同时在运行。

文件:src/ch5/section5.1/5.1.3/phase_wait/A.sv
19 task A::main_phase(uvm_phase phase);
20   phase.raise_objection(this);
21   `uvm_info("A", "main phase start", UVM_LOW)
22   #100;
23   `uvm_info("A", "main phase end", UVM_LOW)
24   phase.drop_objection(this);
25 endtask
26 
27 task A::post_main_phase(uvm_phase phase);
28   phase.raise_objection(this);
29   `uvm_info("A", "post main phase start", UVM_LOW)
30   #300;
31   `uvm_info("A", "post main phase end", UVM_LOW)
32   phase.drop_objection(this);
33 endtask

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

文件:src/ch5/section5.1/5.1.3/phase_wait/B.sv
13 task B::main_phase(uvm_phase phase);
14   phase.raise_objection(this);
15   `uvm_info("B", "main phase start", UVM_LOW)
16   #200;
17   `uvm_info("B", "main phase end", UVM_LOW)
18   phase.drop_objection(this);
19 endtask
20 21 task B::post_main_phase(uvm_phase phase);
22   phase.raise_objection(this);
23   `uvm_info("B", "post main phase start", UVM_LOW)
24   #200;
25   `uvm_info("B", "post main phase end", UVM_LOW)
26   phase.drop_objection(this);
27 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的状态。

这个过程如图5-2所示。

无论从A还是B的角度来看,都存在一段空白等待时间。但是从整个验证平台的角度来看,各个task phase之间是没有任何空白的。

上述的这种同步不仅适用于不同component的动态运行(run-time)phase之间,还适用于run_phase与run_phase之间。

这两种同步都是不同component之间的相同phase之间的同步。除了这两种同步外,还存在一种run_phase与post_shutdown_phase之间的同步。

这种同步的特殊之处在于,它是同一个component的不同类型phase(两类task phase,即run_phase与run-time phase)之间的同步,即同一个component的run_phase与其post_ shutdown_phase全部完成才会进入下一个phase(extract_phase)。

例如,假设整个验证平台中只在A中控制objection:

文件:src/ch5/section5.1/5.1.3/phase_wait2/A.sv
19 task A::post_shutdown_phase(uvm_phase phase);
20   phase.raise_objection(this);
21   `uvm_info("A", "post shutdown phase start", UVM_LOW)
22   #300;
23   `uvm_info("A", "post shutdown phase end", UVM_LOW)
24   phase.drop_objection(this);
25 endtask
26 27 task A::run_phase(uvm_phase phase);
28   phase.raise_objection(this);
29   `uvm_info("A", "run phase start", UVM_LOW)
30   #200;
31   `uvm_info("A", "run phase end", UVM_LOW)
32   phase.drop_objection(this);
33 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机制密切相关。关于这一点,请参考5.2.1节。

4 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中采用的是深度优先的原则,对于图3-2中的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: 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。

如果读者的代码中要求scoreboard的build_phase先于driver的build_phase执行,或者要求两者的顺序反过来,那么应该立即修改这种代码,去除这种对顺序的要求。

5 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对其做的最重要的事情就是3.5.3节所示的自动获取通过config_db::set设置的参数。如果要关掉这个功能,可以在自己的build_phase中不调用super.build_phase。

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

来源:UVM源代码
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语句,如第2章中所有的super.main_phase都可以去掉。

当然,这个结论只适用于直接扩展自uvm_component的类如果是扩展自用户自定义的类,如base_test类,且在其某个phase,如connect_phase中定义了一些重要内容,那么在具体测试用例的connect_phase中就不应该省略super.connect_phase。

6 build阶段出现UVM_ERROR停止仿真

如果使用config_db::get无法得到virtual interface,就会直接调用uvm_fatal结束仿真。由于virtual interface对于一个driver来说是必须的,所以这种uvm_fatal直接退出的使用方式是非常常见的。

但是,事实上,如果这里使用uvm_error,也会退出:

文件:src/ch5/section5.1/5.1.6/my_driver.sv
12      virtual function void build_phase(uvm_phase phase);
13         super.build_phase(phase);
14         if(!uvm_config_db#(virtual my_if)::get(this, "", "vif", vif))
15            `uvm_fatal("my_driver", "virtual interface must be set for vif!!!")
16         `uvm_error("my_driver", "UVM_ERROR test")
17      endfunction

如上所示的代码运行时会给出如下错误提示:

# UVM_ERROR my_driver.sv(16) @ 0: uvm_test_top.env.i_agt.drv [my_driver] UVM_ ERROR test
# UVM_FATAL @ 0: reporter [BUILDERR] stopping due to build errors

这里给出的uvm_fatal是UVM内部自定义的。在end_of_elaboration_phase及其前的phase中,如果出现了一个或多个UVM_ERROR,那么UVM就认为出现了致命错误,会调用uvm_fatal结束仿真。

UVM的这个特性在小型设计中体现不出优势,但是在大型设计中,这一特性非常有用。大型设计中,真正仿真前的编译、优化可能会花费一个多小时的时间。

完成编译、优化后开始仿真,几秒钟后,出现一个uvm_fatal就停止仿真。当修复了这个问题后,再次重新运行,发现又有一个uvm_fatal出现。

如此反复,可能会耗费大量时间。但是如果将这些uvm_fatal替换为uvm_error,将所有类似的问题一次性暴露出来,一次性修复,这会极大缩减时间,提高效率。

7 phase的跳转

在之前的所有表述中,各个phase都是顺序执行的,前一个phase执行完才执行后一个。但是并没有介绍过当后一个phase执行后还可以再执行一次前面的phase。

而“跳转”这个词则完全打破了这种观念:phase之间可以互相跳来跳去。

phase的跳转是比较高级的功能,这里仅举一个最简单的例子,实现main_phase到reset_phase的跳转。

假如在验证平台中监测到reset_n信号为低电平,则马上从main_phase跳转到reset_ phase。driver的代码如下:

文件:src/ch5/section5.1/5.1.7/my_driver.sv
23 task my_driver::reset_phase(uvm_phase phase);
24   phase.raise_objection(this);
25   `uvm_info("driver", "reset phase", UVM_LOW)
26   vif.data <= 8'b0;
27   vif.valid <= 1'b0;
28   while(!vif.rst_n)
29     @(posedge vif.clk);
30   phase.drop_objection(this);
31 endtask
32 33 task my_driver::main_phase(uvm_phase phase);
34   `uvm_info("driver", "main phase", UVM_LOW)
35   fork
36     while(1) begin
37       seq_item_port.get_next_item(req);
38       drive_one_pkt(req);
39       seq_item_port.item_done();
40     end
41     begin
42       @(negedge vif.rst_n);
43       phase.jump(uvm_reset_phase::get());
44     end
45   join
46 endtask

reset_phase主要做一些清理工作,并等待复位完成。main_phase中一旦监测到reset_n为低电平,则马上跳转到reset_phase。

在top_tb中,控制复位信号代码如下:

文件:src/ch5/section5.1/5.1.7/top_tb.sv
43 initial begin
44   rst_n = 1'b0;
45   #1000;
46   rst_n = 1'b1;
47   #3000;
48   rst_n = 1'b0;
49   #3000;
50   rst_n = 1'b1;
51 end

在my_case中控制objection代码如下:

文件:src/ch5/section5.1/5.1.7/my_case0.sv
14 task my_case0::reset_phase(uvm_phase phase);
15   `uvm_info("case0", "reset_phase", UVM_LOW)
16 endtask
17 18 task my_case0::main_phase(uvm_phase phase);
19   phase.raise_objection(this);
20   `uvm_info("case0", "main_phase", UVM_LOW)
21   #10000;
22   phase.drop_objection(this);
23 endtask

运行上述的例子,则显示:

# UVM_INFO my_case0.sv(15) @ 0: uvm_test_top [case0] reset_phase
# UVM_INFO my_driver.sv(25) @ 0: uvm_test_top.env.i_agt.drv [driver] reset phase
# UVM_INFO my_case0.sv(20) @ 1100: uvm_test_top [case0] main_phase
# UVM_INFO my_driver.sv(34) @ 1100: uvm_test_top.env.i_agt.drv [driver] main phase
# UVM_INFO /home/landy/uvm/uvm-1.1d/src/base/uvm_phase.svh(1314) @ 4000: repo-
rter[PH_JUMP] phase main (schedule uvm_sched, domain uvm) is jumping to
phase reset
# UVM_WARNING @ 4000: main_objection [OBJTN_CLEAR] Object 'uvm_top' cleared
ob jection counts for main_objection
# UVM_INFO my_case0.sv(15) @ 4000: uvm_test_top [case0] reset_phase
# UVM_INFO my_driver.sv(25) @ 4000: uvm_test_top.env.i_agt.drv [driver] reset
phase
# UVM_INFO my_case0.sv(20) @ 7100: uvm_test_top [case0] main_phase
# UVM_INFO my_driver.sv(34) @ 7100: uvm_test_top.env.i_agt.drv [driver] main phase

很明显,整个验证平台都从main_phase跳转到了reset_phase。在上述运行结果中,出现了一个UVM_WARNING。这是因为在my_driver中调用jump时,并没有把my_case0中提起的objection进行撤销。加入跳转后,整个验证平台phase的运行图实现变为如图5-3所示形式。

图中灰色区域的phase在整个运行图中出现了两次。

跳转中最难的地方在于跳转前后的清理和准备工作。如上面的运行结果中的警告信息就是因为没有及时对objection进行清理。对于scoreboard来说,这个问题可能尤其严重。

在跳转前,scoreboard的expect_queue中的数据应该清空,同时要容忍跳转后DUT可能输出一些异常数据。在my_driver中使用了jump函数,它的原型是:

来源:UVM源代码
function void uvm_phase::jump(uvm_phase phase);

jump函数的参数必须是一个uvm_phase类型的变量。在UVM中,这样的变量共有如下几个:

来源:UVM源代码
uvm_build_phase::get();
uvm_connect_phase::get();
uvm_end_of_elaboration_phase::get();
uvm_start_of_simulation_phase::get();
uvm_run_phase::get();
uvm_pre_reset_phase::get();
uvm_reset_phase::get();
uvm_post_reset_phase::get();
uvm_pre_configure_phase::get();
uvm_configure_phase::get();
uvm_post_configure_phase::get();
uvm_pre_main_phase::get();
uvm_main_phase::get();
uvm_post_main_phase::get();
uvm_pre_shutdown_phase::get();
uvm_shutdown_phase::get();
uvm_post_shutdown_phase::get();
uvm_extract_phase::get();
uvm_check_phase::get();
uvm_report_phase::get();
uvm_final_phase::get();

但并不是所有的phase都可以作为jump的参数。如代码清单5-10中将jump的参数替换为uvm_build_phase::get(),那么运行验证平台后会给出如下结果:

UVM_FATAL /home/landy/uvm/uvm-1.1d/src/base/uvm_root.svh(922) @ 4000: reporte
r [RUNPHSTIME] The run phase must start at time 0, current time is 4000. No non
-zero delays are allowed before run_test(), and pre-run user defined phases ma y
not consume simulation time before the start of the run phase.

所以往前跳转到从build到start_of_simulation的function phase是不可行的。如果把参数替换为uvm_run_phase::get(),也是不可行的:

UVM_FATAL /home/landy/uvm/uvm-1.1d/src/base/uvm_phase.svh(1697) @ 4000: reporte
r [PH_BADJUMP] phase run is neither a predecessor or successor of phase main or is non
-existant, so we cannot jump to it.  Phase control flow is now undefined so the
simulation must terminate

UVM会提示出run_phase不是main_phase的先驱phase或者后继phase。这非常容易理解。在图5-1中,run_phase是与12个动态运行的phase并行运行的,不存在任何先驱或者后继的关系。

那么哪些phase可以作为jump的参数呢?在代码清单5-14中,uvm_pre_reset_ phase::get()后的所有phase都可以。

代码清单5-10中从main_phase跳转到reset_phase是一种向前跳转,这种向前跳转中,只能是main_phase前的动态运行phase中的一个。

除了向前跳转外,还可以向后跳转。如从main_phase跳转到shutdown_phase。

在向后跳转中,除了动态运行的phase外,还可以是函数phase,如可以从main_phase跳转到final_phase。

8 phase机制的必要性

Verilog中有非阻塞赋值和阻塞赋值,相对应的,在仿真器中要实现分为NBA区域和Active区域,这样在不同的区域做不同的事情,可以避免因竞争关系导致的变量值不确定的情况。

同样的,验证平台是很复杂的,要搭建一个验证平台是一件相当繁杂的事情,要正确地掌握并理顺这些步骤是一个相当艰难的过程。

举一个最简单的例子,一个env下面会实例化agent、scoreboard、reference model等,agent下面又会有sequencer、driver、monitor。

并且,这些组件之间还有连接关系,如agent中monitor的输出要送给scoreboard或reference model,这种通信的前提是要先将reference model和scoreboard连接在一起。那么可以:

scoreboard = new;
reference_model = new;
reference_model.connect(scoreboard);
agent = new;
agent.driver = new;
agent.monitor = new;
agent.monitor.connect(scoreboard);

这里面反应出来的问题就是最后一句话一定要放在最后写,因为连接的前提是所有的组件已经实例化。但是,reference_model.connect(scoreboard)的要求则没有那么高,只需要在上述代码中reference_model = new之后任何一个地方编写即可。可以看出,代码的书写顺序会影响代码的实现。

若要将代码顺序的影响降低到最低,可以按照如下方式编写:

scoreboard = new;
reference_model = new;
agent = new;
agent.driver = new;
agent.monitor = new;
reference_model.connect(scoreboard);
agent.monitor.connect(scoreboard);

只要将连接语句放在最后两行写就没有关系了。UVM采用了这种方法,它将前面实例化的部分都放在build_phase来做而连接关系放在connect_phase来做,这就是phase最初始的来源。

在不同时间做不同的事情,这就是UVM中phase的设计哲学。但是仅仅划分成phase是不够的,phase的自动执行功能才极大方便了用户。

在代码清单5-16中,当new语句执行完成后,后面的connect语句肯定就会自动执行。现引入phase的概念,将前面new的部分包裹进build_phase里面把后面的connect语句包裹进connect_phase里面,很自然的,当build_phase执行结束就应该自动执行connect_phase。phase的引入在很大程度上解决了因代码顺序杂乱可能会引发的问题。

遵循UVM的代码顺序划分原则(如build_phase做实例化工作,connect_phase做连接工作等),可以在很大程度上减少验证平台开发者的工作量,使其从一部分杂乱的工作中解脱出来。

简直是太神奇!!!

9 phase的调试

UVM的phase机制是如此的复杂,如果碰到问题后每次都使用uvm_info在每个phase打印不同的信息显然是不能满足要求的。

UVM提供命令行参数UVM_PHASE_TRACE来对phase机制进行调试,其使用方式为:

<sim command> +UVM_PHASE_TRACE
这个命令的输出非常直观,下面列出了部分输出信息:
# UVM_INFO /home/landy/uvm/uvm-1.1d/src/base/uvm_phase.svh(1124) @ 0: reporter
[PH/TRC/STRT] Phase 'uvm.uvm_sched.reset' (id=184) Starting phase
# UVM_INFO /home/landy/uvm/uvm-1.1d/src/base/uvm_phase.svh(1203) @ 0: reporter
[PH/TRC/SKIP] Phase 'uvm.uvm_sched.reset' (id=184) No objections raised, skipping
phase
# UVM_INFO /home/landy/uvm/uvm-1.1d/src/base/uvm_phase.svh(1381) @ 0: reporter
[PH/TRC/DONE] Phase 'uvm.uvm_sched.reset' (id=184) Completed phase
# UVM_INFO /home/landy/uvm/uvm-1.1d/src/base/uvm_phase.svh(1403) @ 0: reporter
[PH/TRC/SCHEDULED] Phase 'uvm.uvm_sched.post_reset' (id=196) Scheduled from phase
uvm.uvm_sched.reset
# UVM_INFO /home/landy/uvm/uvm-1.1d/src/base/uvm_phase.svh(1124) @ 0: reporter
[PH/TRC/STRT] Phase 'uvm.uvm_sched.post_reset' (id=196) Starting phase
# UVM_INFO /home/landy/uvm/uvm-1.1d/src/base/uvm_phase.svh(1203) @ 0: reporter
[PH/TRC/SKIP] Phase 'uvm.uvm_sched.post_reset' (id=196) No objections raised,
skipping phase
# UVM_INFO /home/landy/uvm/uvm-1.1d/src/base/uvm_phase.svh(1381) @ 0: reporter
[PH/TRC/DONE] Phase 'uvm.uvm_sched.post_reset' (id=196) Completed phase

10 超时退出

在验证平台运行时,有时测试用例会出现挂起(hang up)的情况。在这种状态下,仿真时间一直向前走,driver或者monitor并没有发出或者收到transaction,也没有UVM_ ERROR出现。

一个测试用例的运行时间是可以预计的,如果超出了这个时间,那么通常就是出错了。在UVM中通过uvm_root的set_timeout函数可以设置超时时间:

文件:src/ch5/section5.1/5.1.10/base_test.sv
18 function void base_test::build_phase(uvm_phase phase);
19    super.build_phase(phase);
20    env  =  my_env::type_id::create("env", this);
21    uvm_top.set_timeout(500ns, 0);
22 endfunction

set_timeout函数有两个参数,第一个参数是要设置的时间,第二个参数表示此设置是否可以被其后的其他set_timeout语句覆盖。如上的代码将超时的时间定为500ns。

如果达到500ns时,测试用例还没有运行完毕,则会给出一条uvm_fatal的提示信息,并退出仿真。

默认的超时退出时间是9200s,是通过宏UVM_DEFAULT_TIMEOUT来指定的:

来源:UVM源代码
`define UVM_DEFAULT_TIMEOUT 9200s

除了可以在代码中设置超时退出时间外,还可以在命令行中设置:

<sim command> +UVM_TIMEOUT=<timeout>,<overridable>

其中timeout是要设置的时间,overridable表示能否被覆盖,其值可以是YES或者NO。如将超时退出时间设置为300ns,且可以被覆盖,代码如下:

<sim command> +UVM_TIMEOUT="300ns, YES"
目录
相关文章
|
6月前
|
测试技术 Shell 开发者
UVM与验证环境一文通
UVM与验证环境一文通
365 0
|
6月前
|
安全 数据安全/隐私保护 Android开发
AVB源码学习(二):Uboot阶段AVB2.0校验流程
AVB源码学习(二):Uboot阶段AVB2.0校验流程
389 0
|
3月前
|
监控 关系型数据库 PostgreSQL
两阶段提交(2PC, Two-Phase Commit)
【8月更文挑战第24天】
307 9
|
3月前
|
4月前
|
中间件 数据库
|
4月前
|
中间件
|
5月前
|
芯片
芯片验证 | UVM的objection机制
芯片验证 | UVM的objection机制
158 1
|
5月前
|
芯片
芯片验证 | UVM的domain机制
芯片验证 | UVM的domain机制
82 0
|
6月前
|
安全 Android开发
AVB源码学习(三):AVB2.0 Init阶段安全启动流程
AVB源码学习(三):AVB2.0 Init阶段安全启动流程
332 0
|
前端开发
【前端验证】对uvm_info宏的进一步封装尝试
【前端验证】对uvm_info宏的进一步封装尝试