UVM phase机制源码探微

1. uvm_phase

  UVM的phase机制秉承在不同时间做不同事情的设计哲学,主要提供了function和task两种类型的phase,在function phase中主要做一些实例化,各组件的连接以及仿真结束后的统计、report等工作,这些事情是不需要耗费仿真时间的;主要的仿真过程是在task phase即run_phase中运行的,与run_phase同步运行的还有12个run-time task phase, 这些细分的phase可以实现对仿真行为更加精细的控制,比如在reset_phase对DUT做reset,configure_phase对DUT做一些配置, 在main_phase中灌输激励等等,各个function phase是严格按照下图中从上到下的顺序来执行的(图中蓝色为function phase,绿色为task phase):
                    在这里插入图片描述
                      图1.1 uvm_phase graph


2. uvm_phase的运行和调度

  我们知道,UVM通过phase的调度来进行不同阶段的仿真,那当我们跑一个testcase的时候,验证平台是怎么开始仿真的呢?我们一般会在平台的顶层module TB里边来调用run_test()开始仿真:

//uvm_root.svh
task uvm_root::run_test(string test_name="");
  ...
  process phase_runner_proc;
  ...
    if (test_name != "") begin
  	uvm_coreservice_t cs = uvm_coreservice_t::get();                                                     
  	uvm_factory factory=cs.get_factory();
	  
    if(m_children.exists("uvm_test_top")) begin
      uvm_report_fatal("TTINST",
          "An uvm_test_top already exists via a previous call to run_test", UVM_NONE);
      #0; // forces shutdown because $finish is forked
    end
    $cast(uvm_test_top, factory.create_component_by_name(test_name,
          "", "uvm_test_top", null));
   ...
  fork begin
    // spawn the phase runner task
    phase_runner_proc = process::self();
    uvm_phase::m_run_phases();
  end
  join_none
  #0; // let the phase runner start
  
  wait (m_phase_all_done == 1);
  
  // clean up after ourselves
  phase_runner_proc.kill();
  ...
  if (finish_on_completion)
    $finish;
endtask

  在这个函数中UVM会通过传入的命令行参数+UVM_TESTNAME=xxx来获取testcase的名字,然后创建一个uvm_test_top的component节点,这个uvm_test_top句柄指向当前仿真testcase class的实例,随后会在其中例化env及整个平台hirearchy。此外,SV中有process进程的用法,uvm会用fork…join_none来启动一个进程phase_runner_proc,在其中调用m_run_phases(),在这个task中完成进行对phase的部分调度(之所以说是部分调度,在于其实还有一个background process在同步运行,下文会提到),同时uvm一直等待所有phase全部完成的信号m_phase_all_done,一旦该信号被拉高则杀掉phase_runner_proc进程,调用$finish结束本次仿真。

//uvm_phase.svh
class uvm_phase extends uvm_object;
   local static mailbox #(uvm_phase) m_phase_hopper = new();
   extern static task m_run_phases();
   ...
endclass

task uvm_phase::m_run_phases();
  uvm_root top;
  uvm_coreservice_t cs;
  cs = uvm_coreservice_t::get();
  top = cs.get_root();

  // initiate by starting first phase in common domain
  begin
    uvm_phase ph = uvm_domain::get_common_domain();
    void'(m_phase_hopper.try_put(ph));
  end

  forever begin
    uvm_phase phase;
    m_phase_hopper.get(phase);
    fork
      begin
        phase.execute_phase();
      end
    join_none
    #0;  // let the process start running
  end
endtask

  m_run_phases()是定义在uvm_phase类的静态task,其调用get_common_domain()来获得一个uvm_phase类型的句柄,然后将其放入邮箱m_phase_hopper中,这个邮箱是static类型的,存放的对象是uvm_phase类型,UVM平台中所有的phase实例对象会共用这一个邮箱。随后在forever循环中该邮箱会不停地被检查,一旦有新的phase放入,则马上取出来用fork…join_none启动新线程调用该phase的execute_phase()。

2.1. get_common_domain()

  这个函数做的事情就是把所有的function phase和task phase按照先后顺序排排队,安排好先后顺序,按照图1.1中所示的那样编成一个domain,取个名字叫做"common",作为uvm default的domain。这个函数中先检查m_common_domain是否为null,我们可以大致猜到这是一个common_domain的实例句柄,很明显这里是采用了单例模式,若系统中已经存在这个实例,则直接返回该实例句柄。若是第一次调用该函数,m_common_domain为null,则调用new()函数创建一个句柄为domain,名字为"common"的实例,然后调用add()函数将8个function phase和run_phase按顺序加入,add()函数做的其实就是给这些phase排排队加入名为"common_domain"的phase运行图,我们后面会详细来看add()函数的实现。之后把该domain句柄分别存入以"common"为索引的数组m_domains和赋给m_common_domain。get_uvm_domain()实际上就是获取12个run-time task phase的运行图,最后会再次调用add()将其加入m_common_domain的运行图中,with_phase参数传入的是之前run_phase的句柄,表明12个run-time phase是以跟run_phase并行运行的方式加入到m_common_domain中。

  static function uvm_domain get_common_domain();

    uvm_domain domain;
    uvm_phase schedule;

    if (m_common_domain != null)
      return m_common_domain;

    domain = new("common");
    domain.add(uvm_build_phase::get());
    domain.add(uvm_connect_phase::get());
    domain.add(uvm_end_of_elaboration_phase::get());
    domain.add(uvm_start_of_simulation_phase::get());
    domain.add(uvm_run_phase::get());
    domain.add(uvm_extract_phase::get());
    domain.add(uvm_check_phase::get());
    domain.add(uvm_report_phase::get());
    domain.add(uvm_final_phase::get());
    m_domains["common"] = domain;
    ...
    m_common_domain = domain;

    domain = get_uvm_domain();
    m_common_domain.add(domain,
                     .with_phase(m_common_domain.find(uvm_run_phase::get())));
    
    return m_common_domain;
  endfunction

2.1.1. phase_type & phase_state

  为了更方便的对phase进行调度,UVM定义了不同的phase_type,如UVM_PHASE_NODE代表所有的(无论function/task phase)phase的一个实例(节点);UVM_PHASE_DOMAIN代表在其中定义了不同phase线性/并行运行关系的一组phase的运行图(类似于从上海发往北京的高铁调度时刻表,phase就像是运行在其间的一列列班车,哪个时间段该发行哪班车,都会清清楚楚记录在时刻表里,当然高铁不可能每一个时刻都会发出一班车,不像phase之间是无缝衔接的),其中可以包含许多的UVM_PHASE_NODE;而UVM_PHASE_SCHEDULE表示一个微缩版的UVM_PHASE_DOMAIN,它其中只定义了12个run-time task phase的运行图,等等。

//uvm_object_globals.svh
typedef enum { UVM_PHASE_IMP,
               UVM_PHASE_NODE,
               UVM_PHASE_TERMINAL,
               UVM_PHASE_SCHEDULE,
               UVM_PHASE_DOMAIN,
               UVM_PHASE_GLOBAL
} uvm_phase_type;

  此外UVM还定义了phase_state,用来表示某phase当前的不同运行状态,如UVM_PHASE_SYNCING表示不同domain的run-time task phase正在进行同步,UVM_PHASE_EXECUTING表示当前phase正在执行,等等。

//uvm_object_globals.svh
   typedef enum { UVM_PHASE_UNINITIALIZED = 0,
                  UVM_PHASE_DORMANT      = 1,
                  UVM_PHASE_SCHEDULED    = 2,
                  UVM_PHASE_SYNCING      = 4,
                  UVM_PHASE_STARTED      = 8,
                  UVM_PHASE_EXECUTING    = 16,
                  UVM_PHASE_READY_TO_END = 32,
                  UVM_PHASE_ENDED        = 64,
                  UVM_PHASE_CLEANUP      = 128,
                  UVM_PHASE_DONE         = 256,
                  UVM_PHASE_JUMPING      = 512
                  } uvm_phase_state;

2.1.2. uvm_phase class

2.1.2.1. new()

  uvm_phase类继承自uvm_object,在其中定义了一些变量如m_phase_type和m_state, 分别是uvm_phase_type和uvm_phase_state类型的,uvm_phase类型的句柄m_parent指向的是该phase的上一个层级,其中还定义了uvm_phase类型的m_end_node,这个变量是用来表示一个uvm_domain和uvm_schedule的结束节点。此外,两个bit类型的联合数组以uvm_phase类型为索引,用来记录各个phase间的顺序关系,其中m_predecessors[]用来记录当前phase的所有前置phase,比如若当前phase是一个connect_phase,则build_phase在其之前运行,是它的一个前置phase,则connect_phase的实例中m_predecessors[build_phase] = 1;同理,m_successors[]用于记录当前phase的所有后继phase,对于一个build_phase实例来说,其m_successors[connect_phase] = 1;

//uvm_phase.svh
class uvm_phase extends uvm_object;
  protected uvm_phase_type m_phase_type;
  protected uvm_phase      m_parent;     // our 'schedule' node [or points 'up' one level]
  uvm_phase                m_imp;        // phase imp to call when we execute this node

  local uvm_phase_state    m_state;
  ...
  protected bit  m_predecessors[uvm_phase];
  protected bit  m_successors[uvm_phase];
  protected uvm_phase m_end_node;
  ...
endclass

function uvm_phase::new(string name="uvm_phase",
                        uvm_phase_type phase_type=UVM_PHASE_SCHEDULE,
                        uvm_phase parent=null);
  super.new(name);
  m_phase_type = phase_type;

  // The common domain is the only thing that initializes m_state.  All
  // other states are initialized by being 'added' to a schedule.
  if ((name == "common") &&
      (phase_type == UVM_PHASE_DOMAIN))
    m_state = UVM_PHASE_DORMANT;
   
  m_parent = parent;
  ...
  if (parent == null && (phase_type == UVM_PHASE_SCHEDULE ||
                         phase_type == UVM_PHASE_DOMAIN )) begin
    //m_parent = this;
    m_end_node = new({name,"_end"}, UVM_PHASE_TERMINAL, this);
    this.m_successors[m_end_node] = 1;
    m_end_node.m_predecessors[this] = 1;
  end
endfunction

  来看uvm_phase类的new()函数,它有三个参数,在创建一个uvm_phase类型的实例时分别传入名字,phase_type和和parent,它分别把传入的phase_type和parent赋给m_phase_type和m_parent,若传入的phase_type是UVM_PHASE_SCHEDULE或UVM_PHASE_DOMAIN,表明当前正在创建一个多phase的运行图(domain/schedule),则调用new()创建一个m_end_node的实例对象,这个m_end_node的phase_type是UVM_PHASE_TERMINAL类型,parent就是这个运行图,然后把m_end_node作为这个运行图的后继phase记录下来,这段代码的意图就是一旦我们想创建一个新的domain/schedule对象,就需要先给它安排一个UVM_PHASE_TERMINAL类型的节点,无论运行图内部怎么调度这些phase,最终运行完毕总是要结束的。

2.1.2.1. uvm_common_phases

  UVM的build_phase类和final_phase类继承自uvm_topdown_phase类,之所以叫"topdown"是因为在UVM平台hireachy各个component节点实例化的时候是自上而下执行的,与之相反其余的function phase类型都继承自uvm_bottomup_phase类,比如connect_phase的执行在UVM平台hireachy各个component节点间是自下而上执行。此外run_phase和12个run-time phase类都继承自uvm_task_phase类。这几种类型new()函数中phase_type参数都会传入UVM_PHASE_IMP来创建一个phase实例。其中的关键是traverse()函数,其中会根据当前phase执行的状态来调用当前component的不同函数,其中UVM预先定义了许多callback如phase_started()/phase_ended(),用户可以在component中来扩展这些callback,这些callback会在所有的phase(包括所有function和task phase)执行前后进行调用。我们注意到,在当前component的traverse()执行到最后的时候,会检查它所有的子节点并调用traverse()来以此执行此phase的内容,所以我们说继承自uvm_bottomup_phase的phase如build_phase的执行是自上而下的。与之相反,uvm_bottomup_phase类的traverse()函数是先在其所有子节点中调用traverse()执行,最后执行当前component,因而继承自uvm_bottomup_phase的phase如connect_phase的执行是自下而上的。uvm_task_phase的执行顺序与uvm_bottomup_phase类似,也是自下而上发起,但却是同时执行的,并不会等待子节点phase执行完毕才会执行上一级节点。此外,function phase在UVM_PHASE_EXECUTING状态,会调用execute()函数,这个函数会启动一个新进程并调用exec_func()。

//uvm_topdown_phase.svh
virtual class uvm_topdown_phase extends uvm_phase;
  // Function: new
  //
  // Create a new instance of a top-down phase
  //
  function new(string name);
    super.new(name,UVM_PHASE_IMP);
  endfunction
  
    virtual function void traverse(uvm_component comp,
                                 uvm_phase phase,
                                 uvm_phase_state state);
    string name;
    uvm_domain phase_domain = phase.get_domain();
    uvm_domain comp_domain = comp.get_domain();

    if (m_phase_trace)
    `uvm_info("PH_TRACE",$sformatf("topdown-phase phase=%s state=%s comp=%s comp.domain=%s phase.domain=%s",
          phase.get_name(), state.name(), comp.get_full_name(),comp_domain.get_name(),phase_domain.get_name()),
          UVM_DEBUG)

    if (phase_domain == uvm_domain::get_common_domain() ||
        phase_domain == comp_domain) begin
        case (state)
          UVM_PHASE_STARTED: begin
            comp.m_current_phase = phase;
            comp.m_apply_verbosity_settings(phase);
            comp.phase_started(phase);
            end
          UVM_PHASE_EXECUTING: begin
            if (!(phase.get_name() == "build" && comp.m_build_done)) begin
              uvm_phase ph = this; 
              comp.m_phasing_active++;
              if (comp.m_phase_imps.exists(this))
                ph = comp.m_phase_imps[this];
              ph.execute(comp, phase);
              comp.m_phasing_active--;
            end
            end
          UVM_PHASE_READY_TO_END: begin
            comp.phase_ready_to_end(phase);
            end
          UVM_PHASE_ENDED: begin
            comp.phase_ended(phase);
            comp.m_current_phase = null;
            end
          default:
            `uvm_fatal("PH_BADEXEC","topdown phase traverse internal error")
        endcase
    end
    if(comp.get_first_child(name))
      do
        traverse(comp.get_child(name), phase, state);
      while(comp.get_next_child(name));
  endfunction
  
  // Function: execute
  //
  // Executes the top-down phase ~phase~ for the component ~comp~. 
  //
  virtual function void execute(uvm_component comp,
                                          uvm_phase phase);
    // reseed this process for random stability
    process proc = process::self();
    proc.srandom(uvm_create_random_seed(phase.get_type_name(), comp.get_full_name()));

    comp.m_current_phase = phase;
    exec_func(comp,phase);
  endfunction
endclass 

//uvm_common_phases.svh
class uvm_build_phase extends uvm_topdown_phase;
   virtual function void exec_func(uvm_component comp, uvm_phase phase);
      comp.build_phase(phase); 
   endfunction
   local static uvm_build_phase m_inst;
   static const string type_name = "uvm_build_phase";

   // Function: get
   // Returns the singleton phase handle
   //
   static function uvm_build_phase get();
      if(m_inst == null)
         m_inst = new();
      return m_inst; 
   endfunction
   protected function new(string name="build");
      super.new(name); 
   endfunction
   virtual function string get_type_name();
      return type_name;
   endfunction
endclass

  以uvm_build_phase为例,其实exec_func()就是调用了我们定义在某component的build_phase来执行。其中定义了uvm_build_phase类型的静态变量m_inst,代表这个phase的一个实例,当调用静态函数get()时会返回该唯一实例对象句柄。而task phase在UVM_PHASE_EXECUTING状态,也会调用execute()函数,这个函数会用fork…join_none启动一个新进程并调用exec_task(),在exec_task()返回前用m_num_procs_not_yet_returned来记录当前有多少个component在运行这个phase,以判断何时可以结束该phase。与function phase类似,exec_task()也是定义在task phase中调用当前component的同名task phase执行,get()函数也会返回该phase唯一实例对象句柄m_inst。

//uvm_task_phase.svh
virtual class uvm_task_phase extends uvm_phase
  ...
  virtual function void execute(uvm_component comp,
                                          uvm_phase phase);
    fork
      begin
        process proc;

        // reseed this process for random stability
        proc = process::self();
        proc.srandom(uvm_create_random_seed(phase.get_type_name(), comp.get_full_name()));

        phase.m_num_procs_not_yet_returned++;

        exec_task(comp,phase);

        phase.m_num_procs_not_yet_returned--;

      end
    join_none

  endfunction
endclass

2.1.3. uvm_domain class

  uvm_domain类继承自uvm_phase,其中定义了以字符串为索引的内容为uvm_domain类型的静态联合数组m_domains[],在uvm_domain的new()函数中首先调用super即uvm_phase类型的new()函数创建一个phase_type为UVM_PHASE_DOMAIN的实例,然后把传入的名字作为索引,将该实例对象存入m_domains[]。

//uvm_domain.svh
class uvm_domain extends uvm_phase;
  static local uvm_domain m_common_domain;
  static local uvm_domain m_domains[string];
  ...
  function new(string name);
    super.new(name,UVM_PHASE_DOMAIN);
    if (m_domains.exists(name))
      `uvm_error("UNIQDOMNAM", $sformatf("Domain created with non-unique name '%s'", name))
    m_domains[name] = this;
  endfunction
  ...
endclass

2.1.4. get_uvm_domain()

  这个函数就是将12个run-time phases的运行顺序排好然后返回一个uvm_domain类型的运行图句柄m_uvm_domain。

//uvm_domain.svh
class uvm_domain extends uvm_phase;
    static local uvm_domain m_uvm_domain; // run-time 
    static local uvm_phase m_uvm_schedule;
  ...
  static function void add_uvm_phases(uvm_phase schedule);
    schedule.add(uvm_pre_reset_phase::get());
    schedule.add(uvm_reset_phase::get());
    schedule.add(uvm_post_reset_phase::get());
    schedule.add(uvm_pre_configure_phase::get());
    schedule.add(uvm_configure_phase::get());
    schedule.add(uvm_post_configure_phase::get());
    schedule.add(uvm_pre_main_phase::get());
    schedule.add(uvm_main_phase::get());
    schedule.add(uvm_post_main_phase::get());
    schedule.add(uvm_pre_shutdown_phase::get());
    schedule.add(uvm_shutdown_phase::get());
    schedule.add(uvm_post_shutdown_phase::get());
  endfunction

  // Function: get_uvm_domain
  //
  // Get a handle to the singleton ~uvm~ domain
  //
  static function uvm_domain get_uvm_domain();
  
    if (m_uvm_domain == null) begin
      m_uvm_domain = new("uvm");
      m_uvm_schedule = new("uvm_sched", UVM_PHASE_SCHEDULE);
      add_uvm_phases(m_uvm_schedule);
      m_uvm_domain.add(m_uvm_schedule);
    end
    return m_uvm_domain;
  endfunction
endclass

  静态函数get_uvm_domain()首先分别创建了名为"uvm"和"uvm_sched"的对象并分别赋值给uvm_domain和uvm_phase类型的句柄m_uvm_domain和m_uvm_schedule,二者phase_type分别为UVM_PHASE_COMMON和UVM_PHASE_SCHEDULE,然后调用add_uvm_phases()函数,将12个run-time phases通过add()函数按先后顺序加入m_uvm_schedule中,此时m_uvm_schedule就是一个运行图,最后再将这个运行图schedule加入m_uvm_domain这个domain中,最后回到get_common_domain()中,把m_uvm_domain加入到m_common_domain中,同时设置with_phase为run_phase,就是把m_uvm_domain中的这12个run-time phase和run_phase同步运行。

2.2. add()

  我们在之前调用add()函数时无外乎是四种情况,分别为
1, domain.add(uvm_build_phase::get()); //向uvm_common_domain中添加funtion phase和run_phase
2, schedule.add(uvm_pre_reset_phase::get()); //向m_uvm_schedule中添加12个run-time phases
3, m_uvm_domain.add(m_uvm_schedule); //将m_uvm_schedule加入m_uvm_domain
4, m_common_domain.add(domain,
        .with_phase(m_common_domain.find(uvm_run_phase::get()))); //将包含12个run-time phases 的m_uvm_domain加入uvm_common_domain并使它们跟run_phase同步运行
  来看add()函数,我们隐去了其中关于debug的部分:

function void uvm_phase::add(uvm_phase phase,
                             uvm_phase with_phase=null,
                             uvm_phase after_phase=null,
                             uvm_phase before_phase=null);
  uvm_phase new_node, begin_node, end_node, tmp_node;
  uvm_phase_state_change state_chg;

  if (phase == null)
      `uvm_fatal("PH/NULL", "add: phase argument is null")

  if (with_phase != null && with_phase.get_phase_type() == UVM_PHASE_IMP) begin
    string nm = with_phase.get_name();
    with_phase = find(with_phase);
    if (with_phase == null)
      `uvm_fatal("PH_BAD_ADD",
         {"cannot find with_phase '",nm,"' within node '",get_name(),"'"})
  end

  if (before_phase != null && before_phase.get_phase_type() == UVM_PHASE_IMP) begin
    string nm = before_phase.get_name();
    before_phase = find(before_phase);
    if (before_phase == null)
      `uvm_fatal("PH_BAD_ADD",
         {"cannot find before_phase '",nm,"' within node '",get_name(),"'"})
  end

  if (after_phase != null && after_phase.get_phase_type() == UVM_PHASE_IMP) begin
    string nm = after_phase.get_name();
    after_phase = find(after_phase);
    if (after_phase == null)
      `uvm_fatal("PH_BAD_ADD",
         {"cannot find after_phase '",nm,"' within node '",get_name(),"'"})
  end

  if (with_phase != null && (after_phase != null || before_phase != null))
    `uvm_fatal("PH_BAD_ADD",
       "cannot specify both 'with' and 'before/after' phase relationships")

  if (before_phase == this || after_phase == m_end_node || with_phase == m_end_node)
    `uvm_fatal("PH_BAD_ADD",
       "cannot add before begin node, after end node, or with end nodes")

  // If we are inserting a new "leaf node"
  if (phase.get_phase_type() == UVM_PHASE_IMP) begin
    uvm_task_phase tp;
    new_node = new(phase.get_name(),UVM_PHASE_NODE,this);
    new_node.m_imp = phase;
    begin_node = new_node;
    end_node = new_node;

    // The phase_done objection is only required
    // for task-based nodes
    if ($cast(tp, phase)) begin
       if (new_node.get_name() == "run") begin
         new_node.phase_done = uvm_test_done_objection::get();
       end
       else begin
         new_node.phase_done = uvm_objection::type_id::create({phase.get_name(), "_objection"});
       end
    end

  end
  // We are inserting an existing schedule
  else begin
    begin_node = phase;
    end_node   = phase.m_end_node;
    phase.m_parent = this;
  end

  // If 'with_phase' is us, then insert node in parallel
  /*
  if (with_phase == this) begin
    after_phase = this;
    before_phase = m_end_node;
  end
  */

  // If no before/after/with specified, insert at end of this schedule
  if (with_phase == null && after_phase == null && before_phase == null) begin
    before_phase = m_end_node;
  end

  // INSERT IN PARALLEL WITH 'WITH' PHASE
  if (with_phase != null) begin
    begin_node.m_predecessors = with_phase.m_predecessors;
    end_node.m_successors = with_phase.m_successors;
    foreach (with_phase.m_predecessors[pred])
      pred.m_successors[begin_node] = 1;
    foreach (with_phase.m_successors[succ])
      succ.m_predecessors[end_node] = 1;
  end
  
  
  // INSERT BEFORE PHASE
  else if (before_phase != null && after_phase == null) begin
    begin_node.m_predecessors = before_phase.m_predecessors;
    end_node.m_successors[before_phase] = 1;
    foreach (before_phase.m_predecessors[pred]) begin
      pred.m_successors.delete(before_phase);
      pred.m_successors[begin_node] = 1;
    end
    before_phase.m_predecessors.delete();
    before_phase.m_predecessors[end_node] = 1;
  end
  

  // INSERT AFTER PHASE
  else if (before_phase == null && after_phase != null) begin
    end_node.m_successors = after_phase.m_successors;
    begin_node.m_predecessors[after_phase] = 1;
    foreach (after_phase.m_successors[succ]) begin
      succ.m_predecessors.delete(after_phase);
      succ.m_predecessors[end_node] = 1;
    end
    after_phase.m_successors.delete();
    after_phase.m_successors[begin_node] = 1;
  end
  

  // IN BETWEEN 'BEFORE' and 'AFTER' PHASES
  else if (before_phase != null && after_phase != null) begin
    if (!after_phase.is_before(before_phase)) begin
      `uvm_fatal("PH_ADD_PHASE",{"Phase '",before_phase.get_name(),
                 "' is not before phase '",after_phase.get_name(),"'"})
    end
    // before and after? add 1 pred and 1 succ
    begin_node.m_predecessors[after_phase] = 1;
    end_node.m_successors[before_phase] = 1;
    after_phase.m_successors[begin_node] = 1;
    before_phase.m_predecessors[end_node] = 1;
    if (after_phase.m_successors.exists(before_phase)) begin
      after_phase.m_successors.delete(before_phase);
      before_phase.m_predecessors.delete(after_phase);
    end
  end // if (before_phase != null && after_phase != null)

  // Transition nodes to DORMANT state
  if (new_node == null)
    tmp_node = phase;
  else
    tmp_node = new_node;

  state_chg = uvm_phase_state_change::type_id::create(tmp_node.get_name());
  state_chg.m_phase = tmp_node;
  state_chg.m_jump_to = null;
  state_chg.m_prev_state = tmp_node.m_state;
  tmp_node.m_state = UVM_PHASE_DORMANT;
  `uvm_do_callbacks(uvm_phase, uvm_phase_cb, phase_state_change(tmp_node, state_chg)) 
endfunction

  对于第一种调用,函数首先会判断参数中phase是否为null,然后依次判断各个参数with_phase/after_phase/before_phase是否为null,我们可以略过这些语句。接下来判断传入的phase是否为UVM_PHASE_IMP类型,很明显uvm_build_phase::get()返回值符合这个条件,于是创建一个UVM_PHASE_NODE为phase_type的uvm_phase类型的新实例对象new_node,这个new_node的唯一对象句柄m_imp就指向当前传入的phase_type为UVM_PHASE_IMP的对象,这个phase_type为UVM_PHASE_NODE的实例句柄会替代参数传入的phase_type为UVM_PHASE_IMP的对象存入phase的运行图。接下来把new_node分别赋值给begin_node和end_node。接下来判断当前phase是否是一个task phase类型,若是,则在其中实例化uvm_objection的对象,这里暂且按下不表。接下来判断若with_phase/after_phase/before_phase均为null,则before_phase指向m_end_node,这个m_end_node就是当前调用add()函数的phase_type为UVM_PHASE_DOMAIN或者UVM_PHASE_SCHEDULE的结束节点。这里before_phase指向某个运行图(domain/schedule)的结束节点,意图很明显,就是要把当前phase加入到这个运行图的m_end_node节点之前,作为运行图的最后一个phase。后面的代码判断若before_phase不为null,则
1, 把当前运行图的m_end_node.m_predecessors[]作为新加入phase的m_predecessors[];
2, 把m_end_node作为新加入phase的m_successors,即phase.m_successors[m_end_node]=1;
3, 对于m_end_node的所有m_predecessors,删除m_end_node作为他们的m_successors的记录,并把新加入的phase作为它们的m_successors;
4, 把m_end_node的所有m_predecessors记录删除,并把新加入的phase作为其唯一的m_predecessors。
  对于第一种情况,若调用domain.add(uvm_build_phase::get());之后,则运行图如下:
   
                    在这里插入图片描述

  若依次调用add()添加所有function phase和run_phase到uvm_common_domain之后,所形成的运行图如如下:
                  在这里插入图片描述

  第二种调用情况类似,是把12个run-time task phases加入到m_uvm_schedule中形成顺序运行图,如下:
                  在这里插入图片描述

  接下来看第三种调用add()的方式:m_uvm_domain.add(m_uvm_schedule);将m_uvm_schedule加入到m_uvm_domain的运行图中,由于传入的phase参数不是UVM_PHASE_IMP类型,所以函数会直接将before_phase指向m_uvm_domain的m_end_node,将m_uvm_schedule放在m_uvm_domain::m_end_node之前:
                  在这里插入图片描述

  第四种调用方式给参数with_phase传入了find()返回值,这个find()函数是定义在uvm_phase类中的函数,它会调用m_find_predecessor()和m_find_successor()函数遍历当前domain中的所有前置和后置phase,返回要查找phase的唯一实例句柄m_imp。当with_phase传入run_phase的实例句柄时,add()函数做了以下事情:
1, 把run_phase的所有m_predecessors作为m_uvm_domain的m_predecessors;
2, 把run_phase的所有m_successors作为m_uvm_domain::m_end_node的m_successors;
3, 对于run_phase所有的m_predecessors,把m_uvm_domain作为它们的m_successors;
4, 对于run_phase所有的m_successors,把m_uvm_domain::m_end_node作为它们的m_predecessors。
  最终调用get_common_domain()所形成的m_common_domain的phase运行图如下:
        在这里插入图片描述

2.3. execute_phase()

  在task m_run_phases()中调用uvm_domain::get_common_domain()拿到UVM default domain m_common_domain的句柄ph后,会把ph放入邮箱m_phase_hopper,forever循环从邮箱中拿到ph后会启动fork…join_none线程调用execute_phase()。该task代码如下:

//uvm_phase.svh
task uvm_phase::execute_phase();

  uvm_task_phase task_phase;
  uvm_root top;
  uvm_phase_state_change state_chg;
  uvm_coreservice_t cs;

  cs = uvm_coreservice_t::get();
  top = cs.get_root();

  // If we got here by jumping forward, we must wait for
  // all its predecessor nodes to be marked DONE.
  // (the next conditional speeds this up)
  // Also, this helps us fast-forward through terminal (end) nodes
  foreach (m_predecessors[pred])
    wait (pred.m_state == UVM_PHASE_DONE);


  // If DONE (by, say, a forward jump), return immed
  if (m_state == UVM_PHASE_DONE)
    return;
  ...

  // If we're a schedule or domain, then "fake" execution
  if (m_phase_type != UVM_PHASE_NODE) begin
    state_chg.m_prev_state = m_state;
    m_state = UVM_PHASE_STARTED;
    `uvm_do_callbacks(uvm_phase, uvm_phase_cb, phase_state_change(this, state_chg))

    #0;

    state_chg.m_prev_state = m_state;
    m_state = UVM_PHASE_EXECUTING;
    `uvm_do_callbacks(uvm_phase, uvm_phase_cb, phase_state_change(this, state_chg))

    #0;
  end


  else begin // PHASE NODE

    //---------
    // STARTED:
    //---------
    state_chg.m_prev_state = m_state;
    m_state = UVM_PHASE_STARTED;
    `uvm_do_callbacks(uvm_phase, uvm_phase_cb, phase_state_change(this, state_chg))

    m_imp.traverse(top,this,UVM_PHASE_STARTED);
    m_ready_to_end_count = 0 ; // reset the ready_to_end count when phase starts
    #0; // LET ANY WAITERS WAKE UP


    //if (m_imp.get_phase_type() != UVM_PHASE_TASK) begin
    if (!$cast(task_phase,m_imp)) begin

      //-----------
      // EXECUTING: (function phases)
      //-----------
      state_chg.m_prev_state = m_state;
      m_state = UVM_PHASE_EXECUTING;
      `uvm_do_callbacks(uvm_phase, uvm_phase_cb, phase_state_change(this, state_chg))

      #0; // LET ANY WAITERS WAKE UP
      m_imp.traverse(top,this,UVM_PHASE_EXECUTING);

    end
    else begin
        m_executing_phases[this] = 1;

        state_chg.m_prev_state = m_state;
        m_state = UVM_PHASE_EXECUTING;
        `uvm_do_callbacks(uvm_phase, uvm_phase_cb, phase_state_change(this, state_chg))

        fork : master_phase_process
          begin
  
            m_phase_proc = process::self();
  
            //-----------
            // EXECUTING: (task phases)
            //-----------
            task_phase.traverse(top,this,UVM_PHASE_EXECUTING);
  
            wait(0); // stay alive for later kill
  
          end
        join_none
  
        uvm_wait_for_nba_region(); //Give sequences, etc. a chance to object
  
        // Now wait for one of three criterion for end-of-phase.
        fork
          begin // guard
          
           fork
             // JUMP
             begin
                wait (m_premature_end);
                `UVM_PH_TRACE("PH/TRC/EXE/JUMP","PHASE EXIT ON JUMP REQUEST",this,UVM_DEBUG)
             end
  
             // WAIT_FOR_ALL_DROPPED
             begin
               bit do_ready_to_end  ; // bit used for ready_to_end iterations
               // OVM semantic: don't end until objection raised or stop request
               if (phase_done.get_objection_total(top) ||
                   m_use_ovm_run_semantic && m_imp.get_name() == "run") begin
                 if (!phase_done.m_top_all_dropped)
                   phase_done.wait_for(UVM_ALL_DROPPED, top);
                 `UVM_PH_TRACE("PH/TRC/EXE/ALLDROP","PHASE EXIT ALL_DROPPED",this,UVM_DEBUG)
               end
               else begin
                  if (m_phase_trace) `UVM_PH_TRACE("PH/TRC/SKIP","No objections raised, skipping phase",this,UVM_LOW)
               end
               
               wait_for_self_and_siblings_to_drop() ;
               do_ready_to_end = 1;
                  
               //--------------
               // READY_TO_END:
               //--------------
 
               while (do_ready_to_end) begin
                 uvm_wait_for_nba_region(); // Let all siblings see no objections before traverse might raise another 
                 `UVM_PH_TRACE("PH_READY_TO_END","PHASE READY TO END",this,UVM_DEBUG)
                 m_ready_to_end_count++;
                 if (m_phase_trace)
                   `UVM_PH_TRACE("PH_READY_TO_END_CB","CALLING READY_TO_END CB",this,UVM_HIGH)
                 state_chg.m_prev_state = m_state;
                 m_state = UVM_PHASE_READY_TO_END;
                 `uvm_do_callbacks(uvm_phase, uvm_phase_cb, phase_state_change(this, state_chg))
                 if (m_imp != null)
                   m_imp.traverse(top,this,UVM_PHASE_READY_TO_END);
                  
                 uvm_wait_for_nba_region(); // Give traverse targets a chance to object 

                 wait_for_self_and_siblings_to_drop();
                 do_ready_to_end = (m_state == UVM_PHASE_EXECUTING) && (m_ready_to_end_count < max_ready_to_end_iter) ; //when we don't wait in task above, we drop out of while loop
               end
             end
  
             // TIMEOUT
             begin
               if (this.get_name() == "run") begin
                  if (top.phase_timeout == 0)
                    wait(top.phase_timeout != 0);
                  if (m_phase_trace)
                    `UVM_PH_TRACE("PH/TRC/TO_WAIT", $sformatf("STARTING PHASE TIMEOUT WATCHDOG (timeout == %t)", top.phase_timeout), this, UVM_HIGH)
                  `uvm_delay(top.phase_timeout)
                  if ($time == `UVM_DEFAULT_TIMEOUT) begin
                     if (m_phase_trace)
                       `UVM_PH_TRACE("PH/TRC/TIMEOUT", "PHASE TIMEOUT WATCHDOG EXPIRED", this, UVM_LOW)
                     foreach (m_executing_phases[p]) begin
                        if ((p.phase_done != null) && (p.phase_done.get_objection_total() > 0)) begin
                           if (m_phase_trace)
                             `UVM_PH_TRACE("PH/TRC/TIMEOUT/OBJCTN", 
                                           $sformatf("Phase '%s' has outstanding objections:\n%s", p.get_full_name(), p.phase_done.convert2string()),
                                           this,
                                           UVM_LOW)
                        end
                     end
                        
                     `uvm_fatal("PH_TIMEOUT",
                                $sformatf("Default timeout of %0t hit, indicating a probable testbench issue",
                                          `UVM_DEFAULT_TIMEOUT))
                  end
                  else begin
                     if (m_phase_trace)
                       `UVM_PH_TRACE("PH/TRC/TIMEOUT", "PHASE TIMEOUT WATCHDOG EXPIRED", this, UVM_LOW)
                     foreach (m_executing_phases[p]) begin
                        if ((p.phase_done != null) && (p.phase_done.get_objection_total() > 0)) begin
                           if (m_phase_trace)
                             `UVM_PH_TRACE("PH/TRC/TIMEOUT/OBJCTN", 
                                           $sformatf("Phase '%s' has outstanding objections:\n%s", p.get_full_name(), p.phase_done.convert2string()),
                                           this,
                                           UVM_LOW)
                        end
                     end
                        
                     `uvm_fatal("PH_TIMEOUT",
                                $sformatf("Explicit timeout of %0t hit, indicating a probable testbench issue",
                                          top.phase_timeout))
                  end
                  if (m_phase_trace)
                    `UVM_PH_TRACE("PH/TRC/EXE/3","PHASE EXIT TIMEOUT",this,UVM_DEBUG)
               end // if (this.get_name() == "run")
               else begin
                  wait (0); // never unblock for non-run phase
               end
             end // if (m_phase_trace)

  
           join_any
           disable fork;
        
          end
  
        join // guard

    end

  end

  m_executing_phases.delete(this);

  //---------
  // JUMPING:
  //---------

  // If jump_to() was called then we need to kill all the successor
  // phases which may still be running and then initiate the new
  // phase.  The return is necessary so we don't start new successor
  // phases.  If we are doing a forward jump then we want to set the
  // state of this phase's successors to UVM_PHASE_DONE.  This
  // will let us pretend that all the phases between here and there
  // were executed and completed.  Thus any dependencies will be
  // satisfied preventing deadlocks.
  // GSA TBD insert new jump support

  if (m_phase_type == UVM_PHASE_NODE) begin

    if(m_premature_end) begin
      if(m_jump_phase != null) begin 
        state_chg.m_jump_to = m_jump_phase;
      
        `uvm_info("PH_JUMP",
              $sformatf("phase %s (schedule %s, domain %s) is jumping to phase %s",
               get_name(), get_schedule_name(), get_domain_name(), m_jump_phase.get_name()),
              UVM_MEDIUM);
      end
      else begin
        `uvm_info("PH_JUMP",
              $sformatf("phase %s (schedule %s, domain %s) is ending prematurely",
               get_name(), get_schedule_name(), get_domain_name()),
              UVM_MEDIUM);
      end
  
  
      #0; // LET ANY WAITERS ON READY_TO_END TO WAKE UP
      if (m_phase_trace)
        `UVM_PH_TRACE("PH_END","ENDING PHASE PREMATURELY",this,UVM_HIGH)
    end
    else begin
      // WAIT FOR PREDECESSORS:  // WAIT FOR PREDECESSORS:
      // function phases only
      if (task_phase == null)
        m_wait_for_pred();
    end
  
    //-------
    // ENDED:
    //-------
    // execute 'phase_ended' callbacks
    if (m_phase_trace)
      `UVM_PH_TRACE("PH_END","ENDING PHASE",this,UVM_HIGH)
    state_chg.m_prev_state = m_state;
    m_state = UVM_PHASE_ENDED;
    `uvm_do_callbacks(uvm_phase, uvm_phase_cb, phase_state_change(this, state_chg))
    if (m_imp != null)
      m_imp.traverse(top,this,UVM_PHASE_ENDED);
    #0; // LET ANY WAITERS WAKE UP
  
  
    //---------
    // CLEANUP:
    //---------
    // kill this phase's threads
    state_chg.m_prev_state = m_state;
    if(m_premature_end) m_state = UVM_PHASE_JUMPING;
    else m_state = UVM_PHASE_CLEANUP ;
    `uvm_do_callbacks(uvm_phase, uvm_phase_cb, phase_state_change(this, state_chg))
    if (m_phase_proc != null) begin
      m_phase_proc.kill();
      m_phase_proc = null;
    end
    #0; // LET ANY WAITERS WAKE UP
    if (phase_done != null)
      phase_done.clear();
  end

  //------
  // DONE:
  //------
  m_premature_end = 0 ;
  if(m_jump_fwd || m_jump_bkwd) begin
    if(m_jump_fwd) begin
      clear_successors(UVM_PHASE_DONE,m_jump_phase);
    end
    m_jump_phase.clear_successors();
    uvmkit_gettimeofday(phase_finish_time);
    void'(uvmkit_timeval_subtract(phase_diff_time, phase_finish_time, phase_start_time));
    `uvm_info("TIMED_PHASE",
              $sformatf("'%s' phase took a total of %f seconds",
                        this.get_full_name(),
                        uvmkit_timeval_to_real(phase_diff_time)),
              UVM_LOW)

  end
  else begin

    if (m_phase_trace)
      `UVM_PH_TRACE("PH/TRC/DONE","Completed phase",this,UVM_LOW)
    state_chg.m_prev_state = m_state;
    m_state = UVM_PHASE_DONE;
    `uvm_do_callbacks(uvm_phase, uvm_phase_cb, phase_state_change(this, state_chg))
    m_phase_proc = null;
    uvmkit_gettimeofday(phase_finish_time);
    void'(uvmkit_timeval_subtract(phase_diff_time, phase_finish_time, phase_start_time));
    `uvm_info("TIMED_PHASE",
              $sformatf("'%s' phase took a total of %f seconds",
                        this.get_full_name(),
                        uvmkit_timeval_to_real(phase_diff_time)),
              UVM_LOW)

    #0; // LET ANY WAITERS WAKE UP
  end
  #0; // LET ANY WAITERS WAKE UP
  if (phase_done != null)
    phase_done.clear();

//-----------
// SCHEDULED:
//-----------
  if(m_jump_fwd || m_jump_bkwd) begin
    void'(m_phase_hopper.try_put(m_jump_phase));
    m_jump_phase = null;
    m_jump_fwd = 0;
    m_jump_bkwd = 0;
    uvmkit_gettimeofday(phase_finish_time);
    void'(uvmkit_timeval_subtract(phase_diff_time, phase_finish_time, phase_start_time));
    `uvm_info("TIMED_PHASE",
              $sformatf("'%s' phase took a total of %f seconds",
                        this.get_full_name(),
                        uvmkit_timeval_to_real(phase_diff_time)),
              UVM_LOW)

  end
  // If more successors, schedule them to run now
  else if (m_successors.size() == 0) begin
    top.m_phase_all_done=1;
  end 
  else begin
    // execute all the successors
    foreach (m_successors[succ]) begin
      if(succ.m_state < UVM_PHASE_SCHEDULED) begin
        state_chg.m_prev_state = succ.m_state;
        state_chg.m_phase = succ;
        succ.m_state = UVM_PHASE_SCHEDULED;
        `uvm_do_callbacks(uvm_phase, uvm_phase_cb, phase_state_change(succ, state_chg))
        #0; // LET ANY WAITERS WAKE UP
        void'(m_phase_hopper.try_put(succ));
        if (m_phase_trace)
          `UVM_PH_TRACE("PH/TRC/SCHEDULED",{"Scheduled from phase ",get_full_name()},succ,UVM_LOW)
      end
    end
  end

endtask

  第一次调用这个task是句柄m_common_domain,首先我们需要等待调用这个task的phase的m_predecessors[]执行完毕,即它们的m_state 为UVM_PHASE_DONE,这里m_common_domain是没有任何前置phase的,所以略过。若当前phase的m_state是UVM_PHASE_DONE,表明已经执行过了,task会直接return。若当前phase_type不是UVM_PHASE_NODE,即对于domain/schedule这两张phase运行图来说,会进行"fake" execution,task只会将其m_state分别转换为UVM_PHASE_STARTED和UVM_PHASE_EXECUTING,而并不会真的做什么事情。在task的最后判断m_jump_fwd和m_jump_bkwd是否为1,这两个变量是跟domain的jump操作有关的,这里它们的值都是0,则对于当前phase的所有后续phase m_successors,判断其m_state是否已经走到了UVM_PHASE_SCHEDULED这一步,若没有,则将其m_state置为UVM_PHASE_SCHEDULED并分别将后续phase的句柄放入m_phase_hopper邮箱。对于m_common_domain来说,它的后续phase只有一个即build_phase,于是这里就会将m_common_domain运行图中的build_phase唯一实例句柄放入m_phase_hopper。
  在task m_run_phases()中forever循环从邮箱中拿到build_phase的句柄,会马上用fork…join_none启动新线程调用build_phase的execute_phase()执行。由于这次调用execute_phase()的句柄类型的phase_type是UVM_PHASE_NODE,而且是function phase,所以会依次将其m_state进入UVM_PHASE_STARTED、UVM_PHASE_EXECUTING和UVM_PHASE_ENDED状态,并将这些状态作为参数调用traverse()。traverse()我们之前已经研究过,这里传入的第一个参数是top,即uvm_root的唯一实例,则执行这些phase的component是从uvm_root作为入口,对于build_phase是自上而下执行的。在后面的状态中执行一些清理的工作,这里不再赘述。依然是task最后,会检查build_phase的后续phase m_successors[]并将其放入m_phase_hopper邮箱来执行,这样UVM中的各个function phase就这样按固定顺序依次执行。直到执行到最后m_common_domain的m_end_node不再有m_successors,则把变量m_phase_all_done置为1,UVM会调用$finish结束仿真。
在执行到start_of_simulation_phse的时候,该phase有两个后继phase,分别为run_phase和m_uvm_domain,他们会分别被放入m_phase_hopper邮箱并被取走调用fork…join_none分别启动并行的新线程来调用各自的execute_phase(),在m_uvm_domain执行execute_phase()时,并不会真的执行,所以也不会有任何仿真延时,它也会在task最后把后继phase句柄即m_uvm_schedule放入邮箱并被取走开启新线程调用execute_phase(),m_uvm_schedule同样也不会真的被执行,而是继续把后继phase即pre_reset_phase实例句柄放入邮箱,当新的并行线程被启动调用execute_phase()时,这个phase_type为UVM_PHASE_NODE的phase会被执行。所有这一切都是并行发生的,所以我们说run_phase和12个run-time phase是同时被并行启动运行的。
  在运行task phase时,execute_phase()中会在调用traverse()执行当前task phase的同时启动数个并行进程,同时监测当前phase的objection和TIMEOUT,一旦达到退出条件,则马上杀掉当前进程进入下一个task phase。因此,在运行task phase时设置合理的objection和timeout以保证phase正常运行非常重要。

3. objection机制

  UVM的objection机制是配合task phase来用的,在task phase如run_phase中若整个平台中没有raise任何objection,则UVM运行到这里会马上跳转到下一个phase,不会执行run_phase里的任何内容。

3.1. uvm_objection class

  uvm_objection类扩展自uvm_report_object类,其中定义了一个存储内容为uvm_objection类型的静态队列,在new()函数中会把当前调用该函数的component/object句柄存入队列m_objections。此外,在uvm_objection类中还定义了一个uvm_objection_events类型的索引为uvm_object的联合数组m_events[],uvm_objection_events类中定义了数个event变量。联合数组m_source_count[]和m_total_count[]均是以uvm_object类型为索引,记录的是raise objection的sequence或者component节点的objection数量和总的objection数量,联合数组m_drain_time[]以uvm_object类型为索引,记录的是当前sequence或者component节点的objection的drain_time。

//uvm_objection.svh
class uvm_objection_events;
  int waiters;
  event raised;
  event dropped;
  event all_dropped;
endclass

class uvm_objection extends uvm_report_object;
  `uvm_register_cb(uvm_objection, uvm_objection_callback)

  protected int     m_source_count[uvm_object];
  protected int     m_total_count [uvm_object];
  protected time    m_drain_time  [uvm_object];
  protected uvm_objection_events m_events [uvm_object];
  /*protected*/ bit     m_top_all_dropped;

  protected uvm_root m_top;
     
  static uvm_objection m_objections[$];
  ...
  function new(string name="");
    super.new(name);
    ...
    m_objections.push_back(this);
  endfunction
  ...
endclass

3.2. raise_objection

  一般我们在某个sequence或者test的run_phase中调用raise_objection(),如phase.raise_objection(),当前phase会调用phase_done的raise_objection()函数,phase_done是定义在uvm_phase中的uvm_objection类型的句柄,在我们调用add()函数添加各个task phase时,就会例化出各个task phase的uvm_objection类型句柄并赋值给phase_done。

//uvm_phase.svh
class uvm_phase extends uvm_object;  
  uvm_objection phase_done;
  ...
endclass
function void uvm_phase::add(uvm_phase phase,
                             uvm_phase with_phase=null,
                             uvm_phase after_phase=null,
                             uvm_phase before_phase=null);
  if (phase.get_phase_type() == UVM_PHASE_IMP) begin
    uvm_task_phase tp;
    ...
    // The phase_done objection is only required
    // for task-based nodes
    if ($cast(tp, phase)) begin
       if (new_node.get_name() == "run") begin
         new_node.phase_done = uvm_test_done_objection::get();
       end
       else begin
         new_node.phase_done = uvm_objection::type_id::create({phase.get_name(), "_objection"});
       end
    end
  end
  ...
endfunction

function void uvm_phase::raise_objection (uvm_object obj, 
                                                   string description="",
                                                   int count=1);
  if (phase_done != null)
    phase_done.raise_objection(obj,description,count);
  else
    m_report_null_objection(obj, description, count, "raise");
endfunction

  定义在uvm_objection类中的raise_objection()函数有三个参数,第一个参数uvm_object类型obj,我们可以在一个sequence或者某component节点中使用this指针传入当前的sequence或者component,第二个参数可以输入一些字符串类型的描述,第三个参数是要raise的objection数量,默认是1。若参数obj为null,则obj为uvm_root的唯一实例m_top,然后把obj作为前两个参数调用m_raise()函数。

//uvm_objection.svh
class uvm_objection extends uvm_report_object;
  virtual function void raise_objection (uvm_object obj=null,
                                         string description="",
                                         int count=1);
    if(obj == null)
      obj = m_top;
    m_cleared = 0;
    m_top_all_dropped = 0;
    m_raise (obj, obj, description, count);
  endfunction

  function void m_raise (uvm_object obj,
                         uvm_object source_obj,
                         string description="",
                         int count=1);
    int idx;
    uvm_objection_context_object ctxt;

    // Ignore raise if count is 0
    if (count == 0)
      return;

    if (m_total_count.exists(obj))
      m_total_count[obj] += count;
    else 
      m_total_count[obj] = count;

    if (source_obj==obj) begin
      if (m_source_count.exists(obj))
        m_source_count[obj] += count;
      else
        m_source_count[obj] = count;
    end
  
    if (m_trace_mode)
      m_report(obj,source_obj,description,count,"raised");

    raised(obj, source_obj, description, count);
    ...

    if (ctxt == null) begin
        // If there were no drains, just propagate as usual

        if (!m_prop_mode && obj != m_top)
          m_raise(m_top,source_obj,description,count);
        else if (obj != m_top)
          m_propagate(obj, source_obj, description, count, 1, 0);
    end
    else begin
        // Otherwise we need to determine what exactly happened
        int diff_count;

        // Determine the diff count, if it's positive, then we're
        // looking at a 'raise' total, if it's negative, then
        // we're looking at a 'drop', but not down to 0.  If it's
        // a 0, that means that there is no change in the total.
        diff_count = count - ctxt.count;

        if (diff_count != 0) begin
            // Something changed
            if (diff_count > 0) begin
                // we're looking at an increase in the total
                if (!m_prop_mode && obj != m_top)
                  m_raise(m_top, source_obj, description, diff_count);
                else if (obj != m_top)
                  m_propagate(obj, source_obj, description, diff_count, 1, 0);
            end
            else begin
                // we're looking at a decrease in the total
                // The count field is always positive...
                diff_count = -diff_count;
                if (!m_prop_mode && obj != m_top)
                  m_drop(m_top, source_obj, description, diff_count);
                else if (obj != m_top)
                  m_propagate(obj, source_obj, description, diff_count, 0, 0);
            end
        end

        // Cleanup
        ctxt.clear();
        m_context_pool.push_back(ctxt);
    end
        
  endfunction
endclass

  在其中定义了一个uvm_objection_context_object类型的变量ctxt,在uvm_objection_context_object类中定义了uvm_object类型的变量obj和source_obj,字符串description以及数量count,其实就是记录下我们每次raise_objection或者drop_objection的参数信息。

//uvm_objection.svh
class uvm_objection_context_object;
    uvm_object obj;
    uvm_object source_obj;
    string description;
    int    count;
    uvm_objection objection;

    // Clears the values stored within the object,
    // preventing memory leaks from reused objects
    function void clear();
        obj = null;
        source_obj = null;
        description = "";
        count = 0;
        objection = null;
    endfunction : clear
endclass

  回到m_raise()函数,若传入count为0,函数直接返回。若是第一次在my_seq中raise objection,则m_total_count[my_seq]=count,若不是,则以该obj为索引的m_total_count数量加上此次要raise的objection数量。由于传入的obj和source_obj是同一个参数m_top,所以m_source_count[my_seq]=count,若不是,则略过,所以m_source_count的索引只会记录在其中raise objection的sequence或者component节点。接下来函数调用raised():

//uvm_objection.svh
class uvm_objection extends uvm_report_object;
  virtual function void raised (uvm_object obj,
                                uvm_object source_obj,
                                string description,
                                int count);
    uvm_component comp;
    if ($cast(comp,obj))    
      comp.raised(this, source_obj, description, count);
    `uvm_do_callbacks(uvm_objection,uvm_objection_callback,raised(this,obj,source_obj,description,count))
    if (m_events.exists(obj))
       ->m_events[obj].raised;
  endfunction
endclass

  若传入的obj参数是component类型,会调用component的callback raised(),我们可以在某component中来重载这个callback。若m_events中有当前obj的索引内容,则触发其中的raised事件。
  回到m_raise()中,此时ctxt为null,且m_prop_mode为1(default 1),则调用m_propagate():

//uvm_objection.svh
class uvm_objection extends uvm_report_object;
  unction void m_propagate (uvm_object obj,
                             uvm_object source_obj,
                             string description,
                             int count,
                             bit raise,
                             int in_top_thread);
    if (obj != null && obj != m_top) begin
      obj = m_get_parent(obj);
      if(raise)
        m_raise(obj, source_obj, description, count);
      else
        m_drop(obj, source_obj, description, count, in_top_thread);
    end
  endfunction

  function uvm_object m_get_parent(uvm_object obj);
    uvm_component comp;
    uvm_sequence_base seq;
    if ($cast(comp, obj)) begin
      obj = comp.get_parent();
    end
    else if ($cast(seq, obj)) begin
       obj = seq.get_sequencer();
    end
    else
      obj = m_top;
    if (obj == null)
      obj = m_top;
    return obj;
  endfunction
endclass

  若当前传入obj不是m_top,则调用m_get_parent()取得其父节点的句柄。这个obj经过层层参数传递,实际上还是我们在一开始调用raise_objection()时的sequence或者component,若是obj是一个sequence,则m_get_parent()返回该sequence的sequencer,若是一个component节点,则直接返回其父节点。然后在m_propagate()中再次调用m_raise()函数,不过这次obj参数即为其父节点句柄。经过层层递归调用m_raise(),直到uvm_root节点,此时
 m_source_count[my_seq] = 1;
 m_total_count[env.agt.sqr] = 1;
 m_total_count[env.agt] = 1;
 m_total_count[env] = 1;
 m_total_count[m_top] = 1;
  当我们在某个seq或者component中raise objection数为count时,UVM会将count记录在以自身为索引的m_source_count[]中。当m_prop_mode默认为1时其会将count以当前节点的所有上级节点为索引记录在m_total_count[]中,m_prop_mode为0时只会将自身和顶层uvm_root为索引记录在m_total_count[]中,不会记录中间节点。

3.3. drop_objection

  同样在某sequence或者component中drop_objection()时,也会在其中调用drop_done的drop_objection()函数:

//uvm_phase.svh
function void uvm_phase::drop_objection (uvm_object obj, 
                                                  string description="",
                                                  int count=1);
  if (phase_done != null)
    phase_done.drop_objection(obj,description,count);
  else
    m_report_null_objection(obj, description, count, "drop");
endfunction

//uvm_objection.svh
class uvm_objection extends uvm_report_object;
  virtual function void drop_objection (uvm_object obj=null,
                                        string description="",
                                        int count=1);
    if(obj == null)
      obj = m_top;
    m_drop (obj, obj, description, count, 0);
  endfunction


  // Function- m_drop

  function void m_drop (uvm_object obj,
                        uvm_object source_obj,
                        string description="",
                        int count=1,
                        int in_top_thread=0);

    // Ignore drops if the count is 0
    if (count == 0)
      return;

    if (!m_total_count.exists(obj) || (count > m_total_count[obj])) begin
      if(m_cleared)
        return;
      uvm_report_fatal("OBJTN_ZERO", {"Object \"", obj.get_full_name(), 
        "\" attempted to drop objection '",this.get_name(),"' count below zero"});
      return;
    end

    if (obj == source_obj) begin
      if (!m_source_count.exists(obj) || (count > m_source_count[obj])) begin
        if(m_cleared)
          return;
        uvm_report_fatal("OBJTN_ZERO", {"Object \"", obj.get_full_name(), 
          "\" attempted to drop objection '",this.get_name(),"' count below zero"});
        return;
      end
      m_source_count[obj] -= count;
    end

    m_total_count[obj] -= count;

    if (m_trace_mode)
      m_report(obj,source_obj,description,count,"dropped");
    
    dropped(obj, source_obj, description, count);
  
    // if count != 0, no reason to fork
    if (m_total_count[obj] != 0) begin
      if (!m_prop_mode && obj != m_top)
        m_drop(m_top,source_obj,description, count, in_top_thread);
      else if (obj != m_top) begin
        this.m_propagate(obj, source_obj, description, count, 0, in_top_thread);
      end

    end
    else begin
        uvm_objection_context_object ctxt;
        if (m_context_pool.size())
          ctxt = m_context_pool.pop_front();
        else
          ctxt = new;

        ctxt.obj = obj;
        ctxt.source_obj = source_obj;
        ctxt.description = description;
        ctxt.count = count;
        ctxt.objection = this;
        // Need to be thread-safe, let the background
        // process handle it.

        // Why don't we look at in_top_thread here?  Because
        // a re-raise will kill the drain at object that it's
        // currently occuring at, and we need the leaf-level kills
        // to not cause accidental kills at branch-levels in
        // the propagation.

        // Using the background process just allows us to
        // separate the links of the chain.
        m_scheduled_list.push_back(ctxt);

    end // else: !if(m_total_count[obj] != 0)

  endfunction
endclass

  若传入参数obj不是m_top,uvm_objection的drop_objection()会把obj作为前两个参数继续调用m_drop(),若传入的参数count为0,函数直接返回。若m_total_count[]中不存在obj为索引的objection数量记录,或者当前传入的count数量超出了m_total_count[obj],函数直接返回。bit m_cleared用来记录当前objection是否已经全部被drop掉,我们会在每次调用raise_objectiion时候把这个bit置为0,若这个bit为1,则函数也直接返回。当前m_drop()的前两个参数obj和source_obj相等,将m_source_count[obj]减去count,同时将m_total_count[obj]也减去count。接下来调用dropped()函数:

//uvm_objection.svh
class uvm_objection extends uvm_report_object;  
  virtual function void dropped (uvm_object obj,
                                 uvm_object source_obj,
                                 string description,
                                 int count);
    uvm_component comp;
    if($cast(comp,obj))    
      comp.dropped(this, source_obj, description, count);
    `uvm_do_callbacks(uvm_objection,uvm_objection_callback,dropped(this,obj,source_obj,description,count))
    if (m_events.exists(obj))
       ->m_events[obj].dropped;
  endfunction
endclass

  若obj是一个component类型,则调用component的callback dropped(),我们可以在component中扩展这个callback。此外会触发m_events[obj]的dropped事件。
  回到m_drop()中来,若减去这次drop的objection数量之后m_total_count[obj]不为0且m_prop_mode为1,则调用m_propagate()并把第五个参数传入0,表示这是一次drop操作,m_propagate()在之前我们已经见过,这个函数就是从当前的obj开始遍历所有父节点直到uvm_root,根据第五个参数对这些父节点进行raise或者drop objection操作。

3.4. drain_time

  若当前调用m_drop()减去这次drop的objection数量之后m_total_count[obj]为0,说明对obj来说,所有的objection都已经被dropped了,当前phase是否可以直接结束进入下一个phase中呢,答案是否定的,UVM会等待执行当前phase的其他节点中的objection被dropped,那若所有节点的所有objection都已经被dropped了,是否可以结束当前phase呢?答案依然是否定的,若某节点中当前phase设置drain_time,则在objection都被dropped之后,该phase会等待drain_time时间之后才会结束。这个drain_time考虑到了验证平台激励和DUT输出可能会存在延时,比如我们在driver的main_phase中输入向DUT灌输激励之后drop_objection,DUT处理这些激励需要一些延时,如果此时直接结束当前phase,monitor就会漏掉最后的一些激励处理之后的结果,所以phase会在所有的objection被dropped之后再延长drain_time时间才会结束。

//uvm_objection.svh
class uvm_objection extends uvm_report_object;  
  function void set_drain_time (uvm_object obj=null, time drain);
    if (obj==null)
      obj = m_top;
    m_drain_time[obj] = drain;
  endfunction
endclass

  通过调用set_drain_time()函数可以设置当前obj的drain_time,将其存入以obj为索引的联合数组中。若传入参数obj为null,则设置m_top的drain_time。
  回到m_drop()中,若当前m_total_count[obj]为0,我们还需要等待当前phase的drain_time时间才能真正结束phase,此时会检查m_context_pool的内容,若有存储直接弹出最前面的一个,若没有,则创建一个uvm_objection_context_object类型的对象ctxt,并把obj/source_obj/description/count和this指针分别赋值给其成员变量,最后将其放入队列m_scheduled_list[ ] 。 其 中 m c o n t e x t p o o l [ ]。其中m_context_pool[ ]mcontextpool[]中存放的是用过的uvm_objection_context_object指针,从这里拿跟new()一个没有太大区别。

//uvm_objection.svh
class uvm_objection extends uvm_report_object;
  local static uvm_objection_context_object m_context_pool[$];
  
`ifndef UVM_USE_PROCESS_CONTAINER   
  local process m_drain_proc[uvm_object];
`else
  local process_container_c m_drain_proc[uvm_object];
`endif

  local static uvm_objection_context_object m_scheduled_list[$];
  local uvm_objection_context_object m_scheduled_contexts[uvm_object];
  local uvm_objection_context_object m_forked_list[$];
  local uvm_objection_context_object m_forked_contexts[uvm_object];
  ...
endclass

  队列m_scheduled_list[$]存放的是当前在等待drain_time的objection信息,联合数组m_scheduled_contexts[]以uvm_object为索引,存放的是关于uvm_object的在等待drain_time的objection信息,队列m_forked_list[$]存放的是当前已经启动fork线程等待drain_time的objection信息,联合数组m_forked_contexts[]以uvm_object为索引,存放的是关于uvm_object的已经启动fork线程在等待drain_time的objection信息。此外联合数组m_drain_proc[]存放的是以uvm_object为索引的等待drain_time的进程句柄。
  等待phase的drain_time的进程其实一直作为backgroud进程从仿真一开始调用uvm_root::run_test()就启动了,甚至比调用m_run_phases()还要早,在run_test()中调用静态函数m_init_objections()启动该进程,该函数会用fork…join_none调用m_execute_scheduled_forks()。

//uvm_root.svh
task uvm_root::run_test(string test_name="");
  uvm_objection::m_init_objections();
endtask

//uvm_objection.svh
class uvm_objection extends uvm_report_object;
  static function void m_init_objections();
    fork 
      uvm_objection::m_execute_scheduled_forks();
    join_none
  endfunction

  // background process; when non
  static task m_execute_scheduled_forks();
    while(1) begin
      wait(m_scheduled_list.size() != 0);
      if(m_scheduled_list.size() != 0) begin
          uvm_objection_context_object c;
          uvm_objection o;
          // Save off the context before the fork
          c = m_scheduled_list.pop_front();
          // A re-raise can use this to figure out props (if any)
          c.objection.m_scheduled_contexts[c.obj] = c;
          // The fork below pulls out from the forked list
          c.objection.m_forked_list.push_back(c);
          // The fork will guard the m_forked_drain call, but
          // a re-raise can kill m_forked_list contexts in the delta
          // before the fork executes.
          fork : guard
              automatic uvm_objection objection = c.objection;
              begin
                  // Check to maike sure re-raise didn't empty the fifo
                  if (objection.m_forked_list.size() > 0) begin
                      uvm_objection_context_object ctxt;
	              ctxt = objection.m_forked_list.pop_front();
                      // Clear it out of scheduled
                      objection.m_scheduled_contexts.delete(ctxt.obj);
                      // Move it in to forked (so re-raise can figure out props)
                      objection.m_forked_contexts[ctxt.obj] = ctxt;
                      // Save off our process handle, so a re-raise can kill it...
`ifndef UVM_USE_PROCESS_CONTAINER		     
                      objection.m_drain_proc[ctxt.obj] = process::self();
`else
		     begin
			process_container_c c = new(process::self());
			objection.m_drain_proc[ctxt.obj]=c;
		     end
`endif		     
                      // Execute the forked drain
                      objection.m_forked_drain(ctxt.obj, ctxt.source_obj, ctxt.description, ctxt.count, 1);
                      // Cleanup if we survived (no re-raises)
                      objection.m_drain_proc.delete(ctxt.obj);
                      objection.m_forked_contexts.delete(ctxt.obj);
                      // Clear out the context object (prevent memory leaks)
                      ctxt.clear();
                      // Save the context in the pool for later reuse
                      m_context_pool.push_back(ctxt);
                  end
              end
          join_none : guard
      end
    end
  endtask
endclass

  静态task m_execute_scheduled_forks()中使用while无限循环,一旦队列m_scheduled_list[$]不为空,则从中取出uvm_objection_context_object类型句柄放入m_scheduled_contexts[obj]和队列m_forked_list[$],这些句柄记录的是当前obj在drop_objection时需要等待drain_time的信息。随即使用fork…join_none并行线程取出m_forked_list[$]的句柄并清除m_forked_list[$]和m_scheduled_contexts[obj]相关内容,同时启动m_drain_proc[obj]调用m_forked_drain()来执行等待任务,等待该task返回以后清除m_forked_drain[obj]和m_forked_contexts[obj]。

//uvm_objection.svh
class uvm_objection extends uvm_report_object;
  task m_forked_drain (uvm_object obj,
                       uvm_object source_obj,
                       string description="",
                       int count=1,
                       int in_top_thread=0);

      int diff_count;

      if (m_drain_time.exists(obj))
        `uvm_delay(m_drain_time[obj])
      
      if (m_trace_mode)
        m_report(obj,source_obj,description,count,"all_dropped");
      
      all_dropped(obj,source_obj,description, count);
          
          // wait for all_dropped cbs to complete
      wait fork;

      /* NOT NEEDED - Any raise would have killed us!
      if(!m_total_count.exists(obj))
        diff_count = -count;
      else
        diff_count = m_total_count[obj] - count;
      */

      // we are ready to delete the 0-count entries for the current
      // object before propagating up the hierarchy. 
      if (m_source_count.exists(obj) && m_source_count[obj] == 0)
        m_source_count.delete(obj);
          
      if (m_total_count.exists(obj) && m_total_count[obj] == 0)
        m_total_count.delete(obj);

      if (!m_prop_mode && obj != m_top)
        m_drop(m_top,source_obj,description, count, 1);
      else if (obj != m_top)
        m_propagate(obj, source_obj, description, count, 0, 1);

  endtask

  virtual task all_dropped (uvm_object obj,
                            uvm_object source_obj,
                            string description,
                            int count);
    uvm_component comp;
    if($cast(comp,obj))    
      comp.all_dropped(this, source_obj, description, count);
    `uvm_do_callbacks(uvm_objection,uvm_objection_callback,all_dropped(this,obj,source_obj,description,count))
    if (m_events.exists(obj))
       ->m_events[obj].all_dropped;
    if (obj == m_top)
      m_top_all_dropped = 1;
  endtask
endclass

  在task m_forked_drain()中,若当前obj已经设置了drain_time,会首先等待m_drain_time[obj]时间,然后调用all_dropped()函数,若obj是component类型,该函数中会首先调用all_dropped() callback,我们可以在某component中重载这个callback,其次会触发m_events[obj]的all_dropped事件,若当前obj是m_top,则把m_top_all_dropped置为1。回到task m_forked_drain()中,接着把m_source_count[]和m_total_count[]中关于obj的部分清除,若m_prop_mode为1则调用m_propagate()继续drop所有父类节点的objection,直到m_top的所有objection被dropped。
  若某obj在等待drain_time期间,又有objection被raised,该如何处理?回到之前的m_raise()中:

//uvm_objection.svh
class uvm_objection extends uvm_report_object;
  function void m_raise (uvm_object obj,
                         uvm_object source_obj,
                         string description="",
                         int count=1);
    int idx;
    uvm_objection_context_object ctxt;

      // Handle any outstanding drains...

    // First go through the scheduled list
    idx = 0;
    while (idx < m_scheduled_list.size()) begin
        if ((m_scheduled_list[idx].obj == obj) &&
            (m_scheduled_list[idx].objection == this)) begin
            // Caught it before the drain was forked
            ctxt = m_scheduled_list[idx];
            m_scheduled_list.delete(idx);
            break;
        end
        idx++;
    end

    // If it's not there, go through the forked list
    if (ctxt == null) begin
        idx = 0;
        while (idx < m_forked_list.size()) begin
            if (m_forked_list[idx].obj == obj) begin
                // Caught it after the drain was forked,
                // but before the fork started
                ctxt = m_forked_list[idx];
                m_forked_list.delete(idx);
                m_scheduled_contexts.delete(ctxt.obj);
                break;
            end
            idx++;
        end
    end

    // If it's not there, go through the forked contexts
    if (ctxt == null) begin
        if (m_forked_contexts.exists(obj)) begin
            // Caught it with the forked drain running
            ctxt = m_forked_contexts[obj];
            m_forked_contexts.delete(obj);
            // Kill the drain
`ifndef UVM_USE_PROCESS_CONTAINER	   
            m_drain_proc[obj].kill();
            m_drain_proc.delete(obj);
`else
            m_drain_proc[obj].p.kill();
            m_drain_proc.delete(obj);
`endif
	   
        end
    end

    if (ctxt == null) begin
        // If there were no drains, just propagate as usual

        if (!m_prop_mode && obj != m_top)
          m_raise(m_top,source_obj,description,count);
        else if (obj != m_top)
          m_propagate(obj, source_obj, description, count, 1, 0);
    end
    else begin
        // Otherwise we need to determine what exactly happened
        int diff_count;

        // Determine the diff count, if it's positive, then we're
        // looking at a 'raise' total, if it's negative, then
        // we're looking at a 'drop', but not down to 0.  If it's
        // a 0, that means that there is no change in the total.
        diff_count = count - ctxt.count;

        if (diff_count != 0) begin
            // Something changed
            if (diff_count > 0) begin
                // we're looking at an increase in the total
                if (!m_prop_mode && obj != m_top)
                  m_raise(m_top, source_obj, description, diff_count);
                else if (obj != m_top)
                  m_propagate(obj, source_obj, description, diff_count, 1, 0);
            end
            else begin
                // we're looking at a decrease in the total
                // The count field is always positive...
                diff_count = -diff_count;
                if (!m_prop_mode && obj != m_top)
                  m_drop(m_top, source_obj, description, diff_count);
                else if (obj != m_top)
                  m_propagate(obj, source_obj, description, diff_count, 0, 0);
            end
        end

        // Cleanup
        ctxt.clear();
        m_context_pool.push_back(ctxt);
    end
        
  endfunction
endclass

  首先检查队列m_scheduled_list[$]中是否有当前obj的记录信息,若有(说明此时已经drop所有objection并在等待drain_time)则取出赋值给uvm_objection_context_object类型的ctxt句柄并删除队列中相关记录,若没有接着检查队列m_forked_list[$],若有则同样取出赋值给ctxt句柄并删除队列中相关记录,若没有则检查数组m_forked_contexts[]是否有相关信息,若有(说明已经进入等待fork线程)则同样取出赋值给ctxt句柄并删除队列中相关记录,同时杀掉m_drain_proc[obj]停止等待obj的drain_time进程。当ctxt得到记录信息,比较当前raise_objection的count和ctxt中的count大小,若raise_objection的count较大,则直接将差值count作为参数调用m_propagate()来raise objection,若raise_objection的count较小,则直接将差值count作为参数调用m_propagate()来drop objection,若相等,则略过
  回头来看uvm_phase::execute_phase()中在m_state为UVM_PHASE_EXECUTING时,若当前运行的是一个task phase时,一方面会用fork…join_none启动并行线程,在其中启动一个process来执行traverse(),另一方面也会再启动两个并行线程,一个用来检测当前phase的total_objection,另一个会检测timeout相关设置,以判断是否可以退出fork线程进入phase的下一个执行阶段。值得一提的是,这个timeout是定义在uvm_root中的一个time类型变量phase_timeout,目的是为了防止仿真一直block在某个phase中而设置的退出机制。这个default timeout是9200s,我们可以通过调用set_timeout()来重置这个phase_timeout时间:

//uvm_root.svh
function void uvm_root::set_timeout(time timeout, bit overridable=1);
  static bit m_uvm_timeout_overridable = 1;
  if (m_uvm_timeout_overridable == 0) begin
    uvm_report_info("NOTIMOUTOVR",
      $sformatf("The global timeout setting of %0d is not overridable to %0d due to a previous setting.",
         phase_timeout, timeout), UVM_NONE);
    return;
  end
  m_uvm_timeout_overridable = overridable;
  phase_timeout = timeout;
endfunction

4. phase的高级应用

4.1. jump()

  UVM提供了在12个run-time phases间进行phase跳转运行的函数jump(),如我们在main_phase中可以调用jump()跳转到reset_phase: phase.jump(uvm_reset_phase.get());

//uvm_phase.svh
function void uvm_phase::end_prematurely() ;
   m_premature_end = 1 ;
endfunction

function void uvm_phase::jump(uvm_phase phase);
   set_jump_phase(phase) ;
   end_prematurely() ;
endfunction

  jump()函数会先后调用set_jump_phase()和end_prematurely(),后者很简单就是把变量m_premature_end置为1,这个bit会控制exectute_phase()中当前task phase的运行进入jumping状态。来看set_jump_phase():

//uvm_phase.svh
function void uvm_phase::set_jump_phase(uvm_phase phase) ;
  uvm_phase d;

  if ((m_state <  UVM_PHASE_STARTED) ||
      (m_state >  UVM_PHASE_ENDED) )
  begin
   `uvm_error("JMPPHIDL", { "Attempting to jump from phase \"",
      get_name(), "\" which is not currently active (current state is ",
      m_state.name(), "). The jump will not happen until the phase becomes ",
      "active."})
  end



  // A jump can be either forward or backwards in the phase graph.
  // If the specified phase (name) is found in the set of predecessors
  // then we are jumping backwards.  If, on the other hand, the phase is in the set
  // of successors then we are jumping forwards.  If neither, then we
  // have an error.
  //
  // If the phase is non-existant and thus we don't know where to jump
  // we have a situation where the only thing to do is to uvm_report_fatal
  // and terminate_phase.  By calling this function the intent was to
  // jump to some other phase. So, continuing in the current phase doesn't
  // make any sense.  And we don't have a valid phase to jump to.  So we're done.

  d = m_find_predecessor(phase,0);
  if (d == null) begin
    d = m_find_successor(phase,0);
    if (d == null) begin
      string msg;
      $sformat(msg,{"phase %s is neither a predecessor or successor of ",
                    "phase %s or is non-existant, so we cannot jump to it.  ",
                    "Phase control flow is now undefined so the simulation ",
                    "must terminate"}, phase.get_name(), get_name());
      `uvm_fatal("PH_BADJUMP", msg);
    end
    else begin
      m_jump_fwd = 1;
      `uvm_info("PH_JUMPF",$sformatf("jumping forward to phase %s", phase.get_name()),
                UVM_DEBUG);
    end
  end
  else begin
    m_jump_bkwd = 1;
    `uvm_info("PH_JUMPB",$sformatf("jumping backward to phase %s", phase.get_name()),
              UVM_DEBUG);
  end
  
  m_jump_phase = d;
endfunction

  若当前phase的m_state还没有走到UVM_PHASE_STARTED或者已经处于UVM_PHASE_ENDED状态,则无法进行phase的跳转,函数直接返回。接着调用m_find_predecessor()在当前phase的所有m_predecessors[]中寻找要跳转的目标phase,若找到则把m_jump_bkwd置为1并把该phase句柄赋值给m_jump_phase;若没有找到,则调用m_find_successor()在该phase的所有m_successors[]中寻找目标phase,若找到则把m_jump_fwd置为1并把该phase句柄赋值给m_jump_phase,若没有找到,则报错。
  回到uvm_phase::execute_phase()中,当前phase的m_state为UVM_PHASE_ENDED之后,即当前task phase已经执行完毕。若m_premature_end为1,则把m_state设置为UVM_PHASE_JUMPING,否则设置为UVM_PHASE_CLEANUP,然后做一些当前phase进程的清理工作。若m_jump_fwd为1,则phase会向前跳转,则把调用clear_successors()把当前phase到目标phase之间的phase从m_successors[]中删去。接下来,若m_jump_fwd或者m_jump_bkwd为1,则将m_jump_phase放入m_phase_hopper邮箱中等待运行,之后吧m_jump_phase赋值为null,把m_jump_fwd和m_jump_bkwd赋值为0。

4.2. 创建新的domain

4.2.1. set_domain()

  UVM有一个default domain叫做m_common_domain,代表8个function phase和run_phase的运行图,另外还包括一个与run_phase并行运行的m_uvm_domain,这个m_uvm_domain代表12个run-time phases的运行图。在m_uvm_domain之外,我们还可以创建新的domain,可以使新domain的12个run-time phases独立运行,而不必与m_uvm_domain中的phase同步。通常我们可以在某component中调用new()函数来创建一个新的domain:
  uvm_domain my_domain = new(“my_domain”);
  set_domain(my_domain);
  set_domain()是定义在uvm_component类中的函数,在其中会调用define_domain()将新domain的句柄传入。若参数hier为default 1,

//uvm_component.svh
function void uvm_component::set_domain(uvm_domain domain, int hier=1);

  // build and store the custom domain
  m_domain = domain;
  define_domain(domain);
  if (hier)
    foreach (m_children[c])
      m_children[c].set_domain(domain);
endfunction

function void uvm_component::define_domain(uvm_domain domain);
  uvm_phase schedule;
  //schedule = domain.find(uvm_domain::get_uvm_schedule());
  schedule = domain.find_by_name("uvm_sched");
  if (schedule == null) begin
    uvm_domain common;
    schedule = new("uvm_sched", UVM_PHASE_SCHEDULE);
    uvm_domain::add_uvm_phases(schedule);
    domain.add(schedule);
    common = uvm_domain::get_common_domain();
    if (common.find(domain,0) == null)
      common.add(domain,.with_phase(uvm_run_phase::get()));
  end

endfunction

  其中会首先调用find_by_name()在新domain中寻找是否有名为"uvm_sched"的phase,很明显我们的新domain还没有添加任何phase,所以为null。接下来会创建一个phase_type为UVM_PHASE_SCHEDULE的phase赋值给句柄schedule,并调用add_uvm_phases()将12个run-time phases加入schedule,再将schedule添加进新建的domain中。最后,将新的my_domain加入UVM的default domain m_common_domain中。其实这个过程跟m_uvm_domain的创建过程一样,加入新的my_domain之后,会形成如下图中的运行图(两个domain的run-time phases并不会同步运行):
在这里插入图片描述

4.2.2. sync()

  我们可以通过调用sync()来对不同doumain的runtime-phase进行同步,uvm_phase类中有一个队列m_sync[$],其中存储的是需要与当前phase同步的phases。

//uvm_phase.svh
class uvm_phase extends uvm_object;
  local uvm_phase m_sync[$];
  ...
endclass
function void uvm_phase::sync(uvm_domain target,
                              uvm_phase phase=null,
                              uvm_phase with_phase=null);
  if (!this.is_domain()) begin
    `uvm_fatal("PH_BADSYNC","sync() called from a non-domain phase schedule node");
  end
  else if (target == null) begin
    `uvm_fatal("PH_BADSYNC","sync() called with a null target domain");
  end
  else if (!target.is_domain()) begin
    `uvm_fatal("PH_BADSYNC","sync() called with a non-domain phase schedule node as target");
  end
  else if (phase == null && with_phase != null) begin
    `uvm_fatal("PH_BADSYNC","sync() called with null phase and non-null with phase");
  end
  else if (phase == null) begin
    // whole domain sync - traverse this domain schedule from begin to end node and sync each node
    int visited[uvm_phase];
    uvm_phase queue[$];
    queue.push_back(this);
    visited[this] = 1;
    while (queue.size()) begin
      uvm_phase node;
      node = queue.pop_front();
      if (node.m_imp != null) begin
        sync(target, node.m_imp);
      end
      foreach (node.m_successors[succ]) begin
        if (!visited.exists(succ)) begin
          queue.push_back(succ);
          visited[succ] = 1;
        end
      end
    end
  end else begin
    // single phase sync
    // this is a 2-way ('with') sync and we check first in case it is already there
    uvm_phase from_node, to_node;
    int found_to[$], found_from[$];
    if(with_phase == null) with_phase = phase;
    from_node = find(phase);
    to_node = target.find(with_phase);
    if(from_node == null || to_node == null) return;
    found_to = from_node.m_sync.find_index(node) with (node == to_node);
    found_from = to_node.m_sync.find_index(node) with (node == from_node);
    if (found_to.size() == 0) from_node.m_sync.push_back(to_node);
    if (found_from.size() == 0) to_node.m_sync.push_back(from_node);
  end
endfunction
  1. sync()函数有三个参数,第一个参数传入想要被同步的新domain句柄,第二个参数传入要被同步的phase,第三个参数传入的phase句柄表示要与新domain中的哪个phase进行同步。若我们需要同步m_common_domain中的pre_reset_phase和my_domain中的main_phase,则可以这么调用:
      uvm_domain common_domain = uvm_domain::get_common_domain();
      common_domain.sync(my_domain,uvm_pre_reset_phase::get(),uvm_main_phase::get());
    sync()函数会分别在这两个要被同步的phase的队列m_sync[$]中检索是否已经有了对方的信息,很明显这里没有,那么就把对方的phase句柄分别存入自己的m_sync[$]中。
  2. 若调用时第三个参数with_phase为null,如
      common_domain.sync(my_domain,uvm_pre_reset_phase::get());
    则with_phase为pre_reset_phase,两个domain的pre_reset_phase的m_sync[$]中会分别放入一条对方的记录。
  3. 若调用时只提供第一个参数,如
      common_domain.sync(my_domain);
    则会将两个domain的所有12个run-time phase进行sync。
      回到uvm_phase::execute_phase()中,若当前phase的m_state运行到UVM_PHASE_SYNCING,若m_sync中记录有需要sync的信息,则会一直等待需要sync的这些phase的m_state运行到UVM_PHASE_SYNCING才会继续往下一个状态运行,UVM通过这种方式实现phase间的同步。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值