UVM sequence机制源码探微

1. sequence基础

1.1. sequence机制相关class的继承关系

  Sequence机制是UVM最核心的机制之一,sequence机制最基本的思想是将激励的产生和驱动分开来,这样可以极大提高验证平台的复用性和产生激励的灵活性。要发送的激励即transaction在sequence中产生,通过sequencer这个中介送到driver,driver把激励驱动到DUT中去并通过sequencer将response返回给sequence。其中sequence和sequencer的相关class继承关系如下图所示:
在这里插入图片描述

1.2. sequence基本用法

  我们可以通过如下方式来实现一个简单的sequence:

class my_sequence extends uvm_sequence #(my_transaction);
	virtual task body();
		my_transaction my_trans;
		my_trans = my_transaction::type_id::creat("my_trans");
		start_item(my_trans);
		assert(my_trans.randomize());
		finish_item(my_trans);
		`uvm_info("my_sequence", "send one transaction ..", UVM_LOW)
	endtask
endclass

  在这个sequence中首先创建一个my_transaction类型的实例,其次调用start_item(),然后对这个transaction进行随机化的操作,最后调用finish_item()。UVM还提供了`uvm_do系列宏来更方便的实现transaction实例的创建和随机化:

class my_sequence extends uvm_sequence #(my_transaction);
	virtual task body();
		`uvm_do(my_transaction);
		`uvm_info("my_sequence", "send one transaction ..", UVM_LOW)
	endtask
endclass

  在验证平台中可以直接在某testcase的run_phase中显式调用start()来启动这个sequence,start()会调用sequence中的body()来执行:

my_sequence seq;
seq = my_sequence::type_id::creat("seq");
seq.start(sequencer);

  还可以通过uvm_config_db将此sequence设置为对应sequencer的某task phase如main_phase的"default_sequence",这样当UVM平台运行到该sequencer的main_phase时就会调用default_sequence即my_sequence的start()来启动my_sequence,达到在phase中隐式执行sequence的目的:

virtual function void my_test::build_phase(uvm_phase phase);
	super.build_phase(phase);
	uvm_config_db #(uvm_object_wrapper)::set(this, "env.agt.sqr.main_phase", "default_sequence", my_sequence::type_id::get());
endfunction

1.3. virtual sequence & virtual sequencer

  当在同一个testcase中需要多个sequence来发送不同的transaction的时候,尤其是这些sequence要发送的激励需要满足某些时序要求的时候,UVM提供了virtual sequence来进行多个sequence间的同步和调度。由于同一个sequencer类型只能发送固定类型的transaction,对于不同sequence要发送的不同transaction类型,当然也是需要其各自对应的sequencer,因此可以通过virtual sequencer提供句柄在virtual sequence中来指向不同类型的sequencer:

class my_vsequencer extends uvm_sequencer;
	`uvm_component_utils(my_vsequencer);
	my_sequencer sqr;
	my_sequencer1 sqr1;
endclass

  在testcase中我们可以将其实例化并分别让它们指向不同的sequencer:

class my_test extends uvm_test;
	my_env env;
	my_vsequencer vsqr;
	
	function build_phase(uvm_phase phase);
		vsqr = my_vsequencer::type_id::creat("vsqr", this);
		...
	endfunction
	
	function connect_phase(uvm_phase phase);
		vsqr.sqr = env.agt.sqr;
		vsqr.sqr1 = env.agt1.sqr;
		...
	endfunction
endclass

  在virtual sequence中可以将my_vsequencer声明为p_sequencer并分别使用virtual sequencer中的sqr和sqr1来分别执行my_sequence和my_sequence1:

class my_vsequence extends uvm_sequence;
	`uvm_object_utils(my_vsequence)
	`uvm_declare_p_sequencer(my_vsequencer)
	task body();
		my_sequence seq;
		my_sequence1 seq1;
		`uvm_do_on(seq, p_sequencer.sqr);
		`uvm_do_on(seq1, p_sequencer.sqr1);
	endtask
endclass

1.4. sequence library

  顾名思义,sequence library就是一系列sequence的集合,其实它本质上还是一个sequence。我们可以启动一个sequence library通过设置算法来令其执行一组sequence,例如我们可以新建一个名为my_seq_lib的sequence library:

class my_seq_lib extends uvm_sequence_library #(my_transaction);
  `uvm_object_utils(my_seq_lib)
  `uvm_sequence_library_utils(my_seq_lib);
  
  function new(string name="my_seq_lib");
    super.new(name);
    init_sequence_library();
  endfunction

endclass

  在创建my_seq_lib类时需要指明其能发送的transaction类型,因此其中所有的sequence都是发送相同固定的transaction类型的,然后在new()函数中调用init_sequence_library()并且使用宏`uvm_sequence_library_utils进行注册。接下来向my_seq_lib中添加名为my_seqsequence:

class my_seq extends uvm_sequence #(my_transaction);
  `uvm_object_utils(my_seq)
  `uvm_add_to_seq_lib(my_seq, my_seq_lib);
  
  virtual task body();
  ...
  endtask
endclass

  可以直接在sequence中使用宏`uvm_add_to_seq_lib把其加入到某sequence library,一个sequence可以被加入到多个sequence library中,此外还可以通过直接调用sequence library提供的函数add_typewide_sequence()/add_sequence()来向其中加入sequences。
  此后我们可以通过uvm_config_db将my_seq_lib作为sequencer的default_sequence:

function void my_test::build_phase();
  super.build_phase(phase);

  uvm_config_db #(uvm_object_wrapper)::set(this, "env.agt_sqr.main_phase", "default_sequence", my_seq_lib::type_id::get());
endfunction

  此外我们可以通过配置sequence library的selectime_mode等变量来决定选择sequence的算法以及要执行多少次(参考后面关于sequence library的源码部分)。

2. transaction的发送

2.1. `uvm_do系列宏

  UVM提供了`uvm_do系列宏,我们在sequence中使用这些宏可以指定当前要执行的sequence_item或者sequence的sequencer,priority,以及一些变量在随机化时的约束条件:

//uvm_sequence_defines.svh
`define uvm_do(SEQ_OR_ITEM) \
  `uvm_do_on_pri_with(SEQ_OR_ITEM, m_sequencer, -1, {})
  
`define uvm_do_on(SEQ_OR_ITEM, SEQR) \
  `uvm_do_on_pri_with(SEQ_OR_ITEM, SEQR, -1, {})

`define uvm_do_pri(SEQ_OR_ITEM, PRIORITY) \
  `uvm_do_on_pri_with(SEQ_OR_ITEM, m_sequencer, PRIORITY, {})

`define uvm_do_with(SEQ_OR_ITEM, CONSTRAINTS) \
  `uvm_do_on_pri_with(SEQ_OR_ITEM, m_sequencer, -1, CONSTRAINTS)
  
`define uvm_do_on_pri(SEQ_OR_ITEM, SEQR, PRIORITY) \
  `uvm_do_on_pri_with(SEQ_OR_ITEM, SEQR, PRIORITY, {})

`define uvm_do_on_with(SEQ_OR_ITEM, SEQR, CONSTRAINTS) \
  `uvm_do_on_pri_with(SEQ_OR_ITEM, SEQR, -1, CONSTRAINTS)

`define uvm_do_pri_with(SEQ_OR_ITEM, PRIORITY, CONSTRAINTS) \
  `uvm_do_on_pri_with(SEQ_OR_ITEM, m_sequencer, PRIORITY, CONSTRAINTS)

  这些宏都是调用的同一个宏`uvm_do_on_pri_with(SEQ_OR_ITEM, m_sequencer, PRIORITY, CONSTRAINTS),其定义如下:

//uvm_sequence_defines.svh
`define uvm_do_on_pri_with(SEQ_OR_ITEM, SEQR, PRIORITY, CONSTRAINTS) \
  begin \
  uvm_sequence_base __seq; \
  `uvm_create_on(SEQ_OR_ITEM, SEQR) \
  if (!$cast(__seq,SEQ_OR_ITEM)) start_item(SEQ_OR_ITEM, PRIORITY);\
  if ((__seq == null || !__seq.do_not_randomize) && !SEQ_OR_ITEM.randomize() with CONSTRAINTS ) begin \
    `uvm_warning("RNDFLD", "Randomization failed in uvm_do_with action") \
  end\
  if (!$cast(__seq,SEQ_OR_ITEM)) finish_item(SEQ_OR_ITEM, PRIORITY); \
  else __seq.start(SEQR, this, PRIORITY, 0); \
  end

  这个宏有四个参数,第一个参数SEQ_OR_ITEM表示我们可以用这个宏来处理sequence_item或者是sequence类型,第二个参数SEQR可以指定使用哪个sequencer来执行当前sequence_item/sequence,第三个参数PRIORITY可以设置优先级,最后一个参数CONSTRAINTS可以给sequence_item或者sequence中的一些参数在随机化时设置约束条件。
  这个宏首先调用`uvm_create_on(SEQ_OR_ITEM, SEQR)创建一个SEQ_OR_ITEM实例,然后判断其是否是一个sequence类型,若不是,调用start_item()和finish_item()来处理这个sequence_item,否则调用start()。宏uvm_create_on定义如下:

//uvm_sequence_defines.svh
`define uvm_create_on(SEQ_OR_ITEM, SEQR) \
  begin \
  uvm_object_wrapper w_; \
  w_ = SEQ_OR_ITEM.get_type(); \
  $cast(SEQ_OR_ITEM , create_item(w_, SEQR, `"SEQ_OR_ITEM`"));\
  end

  在`uvm_creat_on()中使用get_type()拿到一个uvm_object_wrapper类型的句柄,这个get_type()最初是定义在uvm_object类里的一个静态函数,当我们使用`uvm_object_utils宏在factory中将SEQ_OR_ITEM(无论是sequence或者是sequence_item类型)注册时会重载这个函数,返回该类型的唯一句柄me(具体可参看UVM factory机制源码探微),然后将该句柄作为第一个参数调用create_item():

//uvm_sequence_base.svh
class uvm_sequence_base extends uvm_sequence_item;
  ...
  protected function uvm_sequence_item create_item(uvm_object_wrapper type_var, 
                                                   uvm_sequencer_base l_sequencer, string name);

    uvm_coreservice_t cs = uvm_coreservice_t::get();                                                     
    uvm_factory factory=cs.get_factory();
    $cast(create_item,  factory.create_object_by_type( type_var, this.get_full_name(), name ));

    create_item.set_item_context(this, l_sequencer);
  endfunction
  ...
endclass

  这个函数定义在uvm_sequence_base类中,使用factory机制创建一个SEQ_OR_ITEM类型句柄赋值给create_item,这个create_item就是这个函数的返回值,随后调用set_item_context():

//uvm_sequence_item.svh
class uvm_sequence_item extends uvm_transaction;
  protected  bit                m_use_sequence_info;
  protected  int                m_depth = -1;
  protected  uvm_sequencer_base m_sequencer;
  protected  uvm_sequence_base  m_parent_sequence;
  ...
  function void set_item_context(uvm_sequence_base  parent_seq,
                                 uvm_sequencer_base sequencer = null);
     set_use_sequence_info(1);
     if (parent_seq != null) set_parent_sequence(parent_seq);
     if (sequencer == null && m_parent_sequence != null) sequencer = m_parent_sequence.get_sequencer();
     set_sequencer(sequencer); 
     if (m_parent_sequence != null) set_depth(m_parent_sequence.get_depth() + 1); 
     reseed();      
  endfunction
  
  function void set_parent_sequence(uvm_sequence_base parent);
    m_parent_sequence = parent;
  endfunction
  
  virtual function void set_sequencer(uvm_sequencer_base sequencer);
    m_sequencer = sequencer;
    m_set_p_sequencer();
  endfunction

  function uvm_sequencer_base get_sequencer();
    return m_sequencer;
  endfunction

  virtual function void m_set_p_sequencer();
    return;
  endfunction
  
  function void set_depth(int value);
    m_depth = value;
  endfunction

  function int get_depth();
    // If depth has been set or calculated, then use that
    if (m_depth != -1) begin
      return (m_depth);
    end

    // Calculate the depth, store it, and return the value
    if (m_parent_sequence == null) begin
      m_depth = 1;
    end else begin
      m_depth = m_parent_sequence.get_depth() + 1;
    end
    return (m_depth);
  endfunction 
  ...
endclass

  函数set_item_context()是定义在uvm_sequence_item类中的,它有两个参数,第一个参数是uvm_sequence_base类型的,给当前sequence_item/sequence指定一个parent sequence,这里传入的是this句柄,即调用`uvm_do宏的sequence,第二个参数是指定一个sequencer。在函数内部首先调用set_use_sequence_info(),这里就是把bit类型的用于debug的变量m_use_sequence_info置为1。接下来判断若传入参数parent_seq不为null,则调用set_parent_sequence()函数把parent_seq赋值给m_parent_sequence,这是一个定义在uvm_sequence_item类中的uvm_sequence_base类型的句柄,指向当前sequence_item/sequence的一个parent sequence。若传入的sequencer为null且现在m_parent_sequence不为null,则调用get_sequencer()拿到parent sequence的m_sequencer句柄,m_sequencer是定义在uvm_sequence_item类中的uvm_sequencer_base类型的句柄,指向用来执行当前sequence_item/sequence的sequencer。随后调用set_sequencer()把parent sequence的sequencer赋值给当前的sequence_item/sequence,并且调用m_set_p_sequencer(),这个virtual函数此时为空,会直接返回,我们在后面会提到它。最后若m_parent_sequence不为null,则调用set_depth()把当前sequence_item/sequence的m_depth的m_depth在其parent sequence的基础上加1。m_depth是定义在uvm_sequence_item类中的一个int类型变量,默认初始值为-1,若当前sequence没有任何parent sequence,则其m_depth=1。他的子sequence_item/sequence在此基础上依次加1。最后调用reseed()来为sequence_item/sequence设置一个seed,这里用到了UVM seeding机制的一个函数,它基于类型和实例层次名来生成随机化种子。
  总结:在宏`uvm_create_on中会在factory中拿到sequence_item/sequence类型的实例句柄SEQ_OR_ITEM然后调用函数set_item_context()分别设置其m_parent_sequence,m_sequencer和m_depth等相关信息。
  实际上UVM还提供了`uvm_creat()和`uvm_send()系列宏,`uvm_creat()就是直接调用`uvm_create_on(),`uvm_send()系列宏也是在其内部调用了start()或者start_item()/finish_item(),这里不再赘述。

2.2. start()

  UVM定义了一个枚举变量uvm_sequence_state,表示sequence当前不同的执行状态:

//uvm_object_globals.svh
typedef enum
{
  UVM_CREATED   = 1,
  UVM_PRE_START = 2,
  UVM_PRE_BODY  = 4,
  UVM_BODY      = 8,
  UVM_POST_BODY = 16,
  UVM_POST_START= 32,
  UVM_ENDED     = 64,
  UVM_STOPPED   = 128,
  UVM_FINISHED  = 256
} uvm_sequence_state;

  若`uvm_create_on中拿到的SEQ_OR_ITEM句柄类型是一个sequence,则接下来会调用start():

//uvm_sequence_base.svh
class uvm_sequence_base extends uvm_sequence_item;
  protected uvm_sequence_state m_sequence_state;
  local     int                m_priority = -1;
  protected int m_sqr_seq_ids[int];
  protected bit children_array[uvm_sequence_base];
  protected uvm_sequence_item response_queue[$];
  ...
  virtual task start (uvm_sequencer_base sequencer,
                      uvm_sequence_base parent_sequence = null,
                      int this_priority = -1,
                      bit call_pre_post = 1);
    bit                  old_automatic_phase_objection;
     
    set_item_context(parent_sequence, sequencer);

    if (!(m_sequence_state inside {UVM_CREATED,UVM_STOPPED,UVM_FINISHED})) begin
      uvm_report_fatal("SEQ_NOT_DONE", 
         {"Sequence ", get_full_name(), " already started"},UVM_NONE);
    end

    if (m_parent_sequence != null) begin
       m_parent_sequence.children_array[this] = 1;
    end

    if (this_priority < -1) begin
      uvm_report_fatal("SEQPRI", $sformatf("Sequence %s start has illegal priority: %0d",
                                           get_full_name(),
                                           this_priority), UVM_NONE);
    end
    if (this_priority < 0) begin
       if (parent_sequence == null) this_priority = 100;
       else this_priority = parent_sequence.get_priority();
    end

    // Check that the response queue is empty from earlier runs
    clear_response_queue();

    m_priority           = this_priority;

`ifdef UVMKIT_TR_RECORDING
    if (m_sequencer != null) begin
       integer handle;
       uvm_tr_stream stream;
       if (m_parent_sequence == null) begin
          stream = m_sequencer.get_tr_stream(get_name(), "Transactions");
          handle = m_sequencer.begin_tr(this, get_name());
          m_tr_recorder = uvm_recorder::get_recorder_from_handle(handle);
       end else begin
          stream = m_sequencer.get_tr_stream(get_root_sequence_name(), "Transactions");
          handle = m_sequencer.begin_child_tr(this, 
                                              (m_parent_sequence.m_tr_recorder == null) ? 0 : m_parent_sequence.m_tr_recorder.get_handle(), 
                                              get_root_sequence_name());
          m_tr_recorder = uvm_recorder::get_recorder_from_handle(handle);
       end
    end
`endif

    // Ensure that the sequence_id is intialized in case this sequence has been stopped previously
    set_sequence_id(-1);
    // Remove all sqr_seq_ids
    m_sqr_seq_ids.delete();

    // Register the sequence with the sequencer if defined.
    if (m_sequencer != null) begin
      void'(m_sequencer.m_register_sequence(this));
    end

    // Change the state to PRE_START, do this before the fork so that
    // the "if (!(m_sequence_state inside {...}" works
    m_sequence_state = UVM_PRE_START;
    fork
      begin
        m_sequence_process = process::self();

        // absorb delta to ensure PRE_START was seen
        #0;

        // Raise the objection if enabled
        // (This will lock the uvm_get_to_lock_dap)
        if (get_automatic_phase_objection()) begin
           m_safe_raise_starting_phase("automatic phase objection");
        end
         
        pre_start();

        if (call_pre_post == 1) begin
          m_sequence_state = UVM_PRE_BODY;
          #0;
          pre_body();
        end

        if (parent_sequence != null) begin
          parent_sequence.pre_do(0);    // task
          parent_sequence.mid_do(this); // function
        end

        m_sequence_state = UVM_BODY;
        #0;
        body();

        m_sequence_state = UVM_ENDED;
        #0;

        if (parent_sequence != null) begin
          parent_sequence.post_do(this);
        end

        if (call_pre_post == 1) begin
          m_sequence_state = UVM_POST_BODY;
          #0;
          post_body();
        end

        m_sequence_state = UVM_POST_START;
        #0;
        post_start();

        // Drop the objection if enabled
        if (get_automatic_phase_objection()) begin
           m_safe_drop_starting_phase("automatic phase objection");
        end
         
        m_sequence_state = UVM_FINISHED;
        #0;

      end
    join

`ifdef UVMKIT_TR_RECORDING
    if (m_sequencer != null) begin      
      m_sequencer.end_tr(this);
    end
`endif
        
    // Clean up any sequencer queues after exiting; if we
    // were forcibly stoped, this step has already taken place
    if (m_sequence_state != UVM_STOPPED) begin
      if (m_sequencer != null)
        m_sequencer.m_sequence_exiting(this);
    end

    #0; // allow stopped and finish waiters to resume

    if ((m_parent_sequence != null) && (m_parent_sequence.children_array.exists(this))) begin
       m_parent_sequence.children_array.delete(this);
    end

    old_automatic_phase_objection = get_automatic_phase_objection();
    m_init_phase_daps(1);
    set_automatic_phase_objection(old_automatic_phase_objection);
  endtask

  function int get_priority();
    return m_priority;
  endfunction
  
  virtual function void clear_response_queue();
    response_queue.delete();
  endfunction
  ...
endclass

  start()有四个参数,分别可以指定sequencer,parent sequence,priority和call_pre_post。首先调用set_item_context()设置当前sequencesequencer和parent sequence等相关信息。若m_sequence_state不是UVM_CREATED/UVM_STOPPED/UVM_FINISHED这几种,则表示当前sequence正在执行,此时会报错。然后把parent sequence的children_array[this]置为1,children_array[]是定义在uvm_sequence_base类中以uvm_sequence_base类型为索引的一个联合数组,储存的是当前sequence的子sequence信息。另一个int类型变量m_priority表示当前sequence在同一sequencer进行仲裁时的优先级,数值越大优先级越高,初始默认值是-1。若当前sequence没有parent sequence,则m_priority赋值为100,否则调用get_priority()函数拿到其parent sequence的m_priority并配置给自身。接下来调用clear_response_queue()来清空队列response_queue[$],这个队列储存的是uvm_sequence_item类型的从driver返回来的response,这里目的是要清空当前sequence在前面执行过程中储存的response。
  接下来被`UVMKIT_TR_RECORDING宏包裹的部分是关于在debug的时候更好的trace transaction的数据的部分代码,这里我们直接略过。然后调用set_sequence_id()给m_sequence_id赋值为-1,m_sequence_id是定义在uvm_sequence_item类中的int类型变量,表示当前sequence的一个独一无二的id号:

//uvm_sequence_item.svh
class uvm_sequence_item extends uvm_transaction;
  local      int                m_sequence_id = -1;
  ...
  function void set_sequence_id(int id);
    m_sequence_id = id;
  endfunction
  ...
endclass

  接下来在start()中会把数组m_sqr_seq_ids[]清空,这是一个定义在uvm_sequence_base类中的以int类型为索引的联合数组,其中储存的是当前sequence在某sequencer(每一个sequencer也会有一个id,作为索引)中所分配的id号(一个sequence可能对应多个sequencer,一个sequencer也可能被用于执行多个sequence)。如my_sqr1和my_sqr2的id号分别为1和2,my_sqr1可以执行my_seq1和my_seq2,分别给他们分配id为3和4,同时my_sqr2也可以执行my_seq1,给my_seq1分配id为5,则:
my_seq1.m_sqr_seq_ids[1] = 3 //索引为my_sqr1的id,值为其给my_seq1分配的id
my_seq1.m_sqr_seq_ids[2] = 5 //索引为my_sqr2的id,值为其给my_seq1分配的id
my_seq2.m_sqr_seq_ids[1] = 4 //索引为my_sqr1的id,值为其给my_seq2分配的id

  随后若m_sequencer不为null(此时这个m_sequencer指向的就是我们在最开始调用start()时传入的第一个参数sequencer),则调用其m_register_sequence()函数,传入的参数为this指针,指向当前调用start()的这个sequence实例,这个函数是定义在uvm_sequencer_base类中的:

//uvm_sequencer_base.svh
class uvm_sequencer_base extends uvm_component;
  local static int              g_sequence_id = 1;
  protected uvm_sequence_base   reg_sequences[int];
  ...
endclass

function int uvm_sequencer_base::m_register_sequence(uvm_sequence_base sequence_ptr);

  if (sequence_ptr.m_get_sqr_sequence_id(m_sequencer_id, 1) > 0)
    return sequence_ptr.get_sequence_id();
  
  sequence_ptr.m_set_sqr_sequence_id(m_sequencer_id, g_sequence_id++);
  reg_sequences[sequence_ptr.get_sequence_id()] = sequence_ptr;
  return sequence_ptr.get_sequence_id();
endfunction

  在该函数中会分别用到m_get_sqr_sequence_id()和m_set_sqr_sequence_id()函数,代码如下:

//uvm_sequence_base.svh
class uvm_sequence_base extends uvm_sequence_item;  
  local static int              g_sequence_id = 1;
  ...
  function int m_get_sqr_sequence_id(int sequencer_id, bit update_sequence_id);
    if (m_sqr_seq_ids.exists(sequencer_id)) begin
      if (update_sequence_id == 1) begin
        set_sequence_id(m_sqr_seq_ids[sequencer_id]);
      end
      return m_sqr_seq_ids[sequencer_id];
    end

    if (update_sequence_id == 1)
      set_sequence_id(-1);

    return -1;
  endfunction

  function void m_set_sqr_sequence_id(int sequencer_id, int sequence_id);
    m_sqr_seq_ids[sequencer_id] = sequence_id;
    set_sequence_id(sequence_id);
  endfunction
  ...
endclass

  函数m_get_sqr_sequence_id()会在数组m_sqr_seq_ids[]中寻找以传入的sequencer_id为索引的值,若存在且第二个参数update_sequence_id为1,则返回该值,否则把该sequence的m_sequence_id设置为默认值-1并返回该值。这里由于m_sqr_seq_ids[]刚被清空,所以会返回-1。随后把m_sequencer_id和g_sequence_id加1的值分别作为参数调用m_set_sqr_sequence_id()存入数组m_sqr_seq_ids[]。int类型的静态变量g_sequence_id定义在uvm_sequencer_base类中,初始值为1,这个变量是系统中所有sequencer共享的,当任何一个sequencer有一个新的sequence要register的时候,这个值就会加1,从而分配给该sequence一个独一无二的新id。
  回到m_register_sequence()中,接下来会把当前sequence的指针以sequence_id为索引存入数组reg_sequences[],这是一个定义在uvm_sequencer_base类中的以int类型为索引,值为uvm_sequence_base类型的联合数组,sequencer可以用这个数组通过sequence_id来找到登记在该sequencer名下的某sequence的实例。实际上sequencer调用函数m_register_sequence()就是把m_sqr_seq_ids[]和reg_sequences[]进行更新,储存登记在其名下的要执行的sequence相关信息。
  接下来会把m_sequence_state设置为UVM_PRE_START并且在fork…join中启动一个新的进程process来执行一系列函数/任务,在执行这些函数/任务之前,会调用get_automatic_phase_objection()来做一些phase机制objection的操作:

//uvm_sequence_base.svh
class uvm_sequence_base extends uvm_sequence_item;
  ...
    function new (string name = "uvm_sequence");
    super.new(name);
    m_sequence_state = UVM_CREATED;
    m_wait_for_grant_semaphore = 0;
    m_init_phase_daps(1);
  endfunction

  // Automatic Phase Objection DAP
  local uvm_get_to_lock_dap#(bit) m_automatic_phase_objection_dap;
  // Starting Phase DAP
  local uvm_get_to_lock_dap#(uvm_phase) m_starting_phase_dap;

  // Function- m_init_phase_daps
  // Either creates or renames DAPS
  function void m_init_phase_daps(bit create);
     string apo_name = $sformatf("%s.automatic_phase_objection", get_full_name());
     string sp_name = $sformatf("%s.starting_phase", get_full_name());

     if (create) begin
        m_automatic_phase_objection_dap = uvm_get_to_lock_dap#(bit)::type_id::create(apo_name, get_sequencer());
        m_starting_phase_dap = uvm_get_to_lock_dap#(uvm_phase)::type_id::create(sp_name, get_sequencer());
     end
     else begin
        m_automatic_phase_objection_dap.set_name(apo_name);
        m_starting_phase_dap.set_name(sp_name);
     end
  endfunction : m_init_phase_daps
  
  function void set_automatic_phase_objection(bit value);
     m_automatic_phase_objection_dap.set(value);
  endfunction : set_automatic_phase_objection
  
  function bit get_automatic_phase_objection();
     return m_automatic_phase_objection_dap.get();
  endfunction : get_automatic_phase_objection
  ...
endclass

  这个函数会调用m_automatic_phase_objection_dap的get(),这是一个uvm_get_to_lock_dap#(bit)数据类型的变量,UVM的Data Access Policy(DAP)的一个参数化类,这个独特的数据类型的特点是我们可以通过set()给其设置一个其参数所代表数据类型的值,如此处为bit类型的数值,一旦调用get()后,就返回当前值并处于lock状态,不可以再被设置为别的值。m_automatic_phase_objection_dap会在sequence调用new()的时候调用m_init_phase_daps()来创建出一个自身实例,此时由于并没有给它设置一个值,所以get()函数返回0。我们可以在sequence中调用set_automatic_phase_objection(1)把m_automatic_phase_objection_dap的值置为1。与之类似的还有一个同样数据类型的变量m_starting_phase_dap,它也会在m_init_phase_daps()被实例化,只不过其参数是一个uvm_phase类型,我们只能给其设置一个uvm_phase类型的句柄,而不是bit类型。
  回到start()任务中,若我们在调用start()之前调用了m_automatic_phase_objection_dap的set()函数给其bit值置为1,则调用m_safe_raise_starting_phase()来自动为starting_phase 提起一个objection:

//uvm_sequence_base.svh
class uvm_sequence_base extends uvm_sequence_item;
  ...
  `ifndef UVM_NO_DEPRECATED
 `define UVM_DEPRECATED_STARTING_PHASE
`endif

`ifdef UVM_DEPRECATED_STARTING_PHASE
  // DEPRECATED!! Use get/set_starting_phase accessors instead!
  uvm_phase starting_phase;
  // Value set via set_starting_phase
  uvm_phase m_set_starting_phase;
  // Ensures we only warn once per sequence
  bit m_warn_deprecated_set;
`endif 
   
  function uvm_phase get_starting_phase();
`ifdef UVM_DEPRECATED_STARTING_PHASE
     begin
        bit throw_error;

        if (starting_phase != m_set_starting_phase) begin
           if (!m_warn_deprecated_set) begin
              `uvm_warning("UVM_DEPRECATED", "'starting_phase' is deprecated and not part of the UVM standard.  See documentation for uvm_sequence_base::set_starting_phase")
              m_warn_deprecated_set = 1;
           end
           
           if (m_starting_phase_dap.try_set(starting_phase))
             m_set_starting_phase = starting_phase;
           else begin
              uvm_phase dap_phase = m_starting_phase_dap.get();
              `uvm_error("UVM/SEQ/LOCK_DEPR",
                         {"The deprecated 'starting_phase' variable has been set to '",
                          (starting_phase == null) ? "<null>" : starting_phase.get_full_name(), 
                          "' after a call to get_starting_phase locked the value to '",
                          (dap_phase == null) ? "<null>" : dap_phase.get_full_name(), 
                          "'.  See documentation for uvm_sequence_base::set_starting_phase."})
           end
        end

     end
`endif
     return m_starting_phase_dap.get();
  endfunction : get_starting_phase

  function void set_starting_phase(uvm_phase phase);
`ifdef UVM_DEPRECATED_STARTING_PHASE
     begin
        if (starting_phase != m_set_starting_phase) begin
           if (!m_warn_deprecated_set) begin
              `uvm_warning("UVM_DEPRECATED", 
                           {"The deprecated 'starting_phase' variable has been set to '",
                            starting_phase.get_full_name(),
                            "' manually.  See documentation for uvm_sequence_base::set_starting_phase."})
              m_warn_deprecated_set = 1;
           end

           starting_phase = phase;
           m_set_starting_phase = phase;
        end
     end
`endif
           
     m_starting_phase_dap.set(phase);
  endfunction : set_starting_phase
  
  function void m_safe_raise_starting_phase(string description = "",
                                            int count = 1);
     uvm_phase starting_phase = get_starting_phase();
     if (starting_phase != null)
       starting_phase.raise_objection(this, description, count);
  endfunction : m_safe_raise_starting_phase
  ...
endclass

  在函数m_safe_raise_starting_phase()中调用get_starting_phase()来拿到starting_phase的句柄,若之前已经通过调用set_starting_phase()给m_starting_phase_dap赋值,我们可以直接拿到starting_phase的句柄从而raise一个objection。若之前没有调用过set_starting_phase(),这里会调用m_starting_phase_dap的try_put()函数,由于之前从未调用过get(),所以这个DAP还没有被lock住,可以直接把starting_phase赋值给它,最后调用get()拿到starting_pahse句柄并返回。这里可以有两种方式来调用set_starting_phase(),第一种是直接在sequence中调用start()任务之前显式调用set_starting_phase(),另外一种是隐式调用,把当前sequence设置为对应sequencer的某task phase如main_phase的default_sequence,这样在执行到main_phase时会自动调用set_starting_phase(),我们后面会研究相关代码。同样的,在post_start()执行完毕后,若get_automatic_phase_objection()返回1,start()会调用m_safe_drop_starting_phase()来drop掉objection。关于在sequence中raise/drop objection,我们还可以在基类sequence的callback pre_body/start()和post_body/start()中来raise和drop objection。
  在start()任务中,之后会顺序执行一系列(callback)函数/任务,并更新m_sequence_state到相应状态,我们可以在sequence中重载这些callback来实现一些特定目的,各函数/任务执行顺序如下:
v| starting_phase.raise_objection     if (automatic_phase_objection ==1)
v| seq.pre_start()   (task)
v| seq.pre_body()  (task)        if call_pre_post==1
v|  parent_seq.pre_do(0)  (task)    if parent_sequence!=null
v|  parent_seq.mid_do(this)  (func)   if parent_sequence!=null
v| seq.body   (task)     YOUR STIMULUS CODE
v|  parent_seq.post_do(this)   (func)  if parent_sequence!=null
v| seq.post_body()   (task)       if call_pre_post==1
v| seq.post_start()    (task)
v| starting_phase.drop_objection      if (automatic_phase_objection ==1)
  若sequence正常执行结束的话(m_sequence_state == UVM_FINISHED),调用m_sequencer的m_sequence_exiting()函数:

//uvm_sequencer_base.svh
function void uvm_sequencer_base::m_unregister_sequence(int sequence_id);
  if (!reg_sequences.exists(sequence_id))
    return;
  reg_sequences.delete(sequence_id);
endfunction 

function void uvm_sequencer_base::remove_sequence_from_queues(
                                       uvm_sequence_base sequence_ptr);
  int i;
  int seq_id;
  
  seq_id = sequence_ptr.m_get_sqr_sequence_id(m_sequencer_id, 0);
  
  // Remove all queued items for this sequence and any child sequences
  i = 0;
  do 
    begin
      if (arb_sequence_q.size() > i) begin
        if ((arb_sequence_q[i].sequence_id == seq_id) ||
            (is_child(sequence_ptr, arb_sequence_q[i].sequence_ptr))) begin
          if (sequence_ptr.get_sequence_state() == UVM_FINISHED)
            `uvm_error("SEQFINERR", $sformatf("Parent sequence '%s' should not finish before all items from itself and items from descendent sequences are processed.  The item request from the sequence '%s' is being removed.", sequence_ptr.get_full_name(), arb_sequence_q[i].sequence_ptr.get_full_name()))
          arb_sequence_q.delete(i);
          m_update_lists();
        end
        else begin
          i++;
        end
      end
    end
  while (i < arb_sequence_q.size());
  
  // remove locks for this sequence, and any child sequences 
  i = 0;
  do
    begin
      if (lock_list.size() > i) begin
        if ((lock_list[i].get_inst_id() == sequence_ptr.get_inst_id()) ||
            (is_child(sequence_ptr, lock_list[i]))) begin
          if (sequence_ptr.get_sequence_state() == UVM_FINISHED)
            `uvm_error("SEQFINERR", $sformatf("Parent sequence '%s' should not finish before locks from itself and descedent sequences are removed.  The lock held by the child sequence '%s' is being removed.",sequence_ptr.get_full_name(), lock_list[i].get_full_name()))
          lock_list.delete(i);
          m_update_lists();
        end
        else begin
          i++;
        end
      end
    end
  while (i < lock_list.size());
  
  // Unregister the sequence_id, so that any returning data is dropped
  m_unregister_sequence(sequence_ptr.m_get_sqr_sequence_id(m_sequencer_id, 1));
endfunction

function void uvm_sequencer_base::m_sequence_exiting(uvm_sequence_base sequence_ptr);
  remove_sequence_from_queues(sequence_ptr);
endfunction

  这个函数会直接调用remove_sequence_from_queues()函数,这个函数做的事情就是把当前已经执行完毕的sequence从sequencer的仲裁队列和lock队列中删除并调用m_unregister_sequence()将其从reg_sequences[]数组中删除。
  在start()任务中的收尾阶段,会将该sequence从parent sequence的children_array[]数组中删除,最后拿到automatic_phase_objection的值重新调用m_init_phase_daps(1)并设置给m_automatic_phase_objection_dap。

2.3. start_item()

  前面提到,在宏`uvm_do_on_pri_with()中,若要执行的SEQ_OR_ITEM的类型不是uvm_sequence_base类型(即不是一个sequence),则调用start_item()和finish_item()任务来执行这个sequence_item,我们先来看start_item():

//uvm_sequence_base.svh
class uvm_sequence_base extends uvm_sequence_item;
  ...
  virtual task start_item (uvm_sequence_item item,
                           int set_priority = -1,
                           uvm_sequencer_base sequencer=null);
    uvm_sequence_base seq;
     
    if(item == null) begin
      uvm_report_fatal("NULLITM",
         {"attempting to start a null item from sequence '",
          get_full_name(), "'"}, UVM_NONE);
      return;
    end
          
    if($cast(seq, item)) begin
      uvm_report_fatal("SEQNOTITM",
         {"attempting to start a sequence using start_item() from sequence '",
          get_full_name(), "'. Use seq.start() instead."}, UVM_NONE);
      return;
    end
          
    if (sequencer == null)
        sequencer = item.get_sequencer();
        
    if(sequencer == null)
        sequencer = get_sequencer();   
        
    if(sequencer == null) begin
        uvm_report_fatal("SEQ",{"neither the item's sequencer nor dedicated sequencer has been supplied to start item in ",get_full_name()},UVM_NONE);
       return;
    end

    item.set_item_context(this, sequencer);

    if (set_priority < 0)
      set_priority = get_priority();
    
    sequencer.wait_for_grant(this, set_priority);

    if (sequencer.is_auto_item_recording_enabled()) begin
      void'(sequencer.begin_child_tr(item, 
                                     (m_tr_recorder == null) ? 0 : m_tr_recorder.get_handle(), 
                                     item.get_root_sequence_name(), "Transactions"));
    end
    
    pre_do(1);

  endtask
  ...

endclass

  任务start_item()通过三个参数来分别指定要执行的uvm_sequence_item类型的对象,priority和sequencer,若传入的sequence_item对象句柄为null,或者该对象句柄指向的是一个uvm_sequence_base对象(即是一个sequence)则报错返回。若传入sequencer参数为null,则调用get_sequencer()拿到sequence_item或者是当前sequence的m_sequencer,若均为null,则报错返回。接着调用set_item_context()传入this指针将当前的sequence设置为该sequence_item的parent sequence,将刚才拿到的m_sequencer句柄设置为它的m_sequencer。若没有设置priority参数或者传入的priority值小于0,则调用get_priority()拿到当前sequence的m_priority,然后作为第二个参数调用m_sequencer的wait_for_grant(),sequencer中有一套仲裁机制,来决定sequence的执行顺序,这个函数返回以后,最后调用callback pre_do(1)。

2.4. sequencer的仲裁机制

  Sequence机制提供了枚举类型UVM_SEQ_ARB_TYPE来表示不同的仲裁算法:

//uvm_object_globals.svh
typedef enum
{
  UVM_SEQ_ARB_FIFO,
  UVM_SEQ_ARB_WEIGHTED,
  UVM_SEQ_ARB_RANDOM,
  UVM_SEQ_ARB_STRICT_FIFO,
  UVM_SEQ_ARB_STRICT_RANDOM,
  UVM_SEQ_ARB_USER
} uvm_sequencer_arb_mode;
typedef uvm_sequencer_arb_mode UVM_SEQ_ARB_TYPE;

  其中UVM_SEQ_ARB_FIFO表示先到先得,UVM_SEQ_ARB_WEIGHTED根据每个请求的priority权重来随机选择优先执行哪个sequence,UVM_SEQ_ARB_RANDOM则是纯粹随机选择,UVM_SEQ_ARB_STRICT_FIFO是根据priority来把所有请求分成不同的优先级,具有相同最高优先级的请求则遵循先到先得的原则,UVM_SEQ_ARB_STRICT_RANDOM是根据priority来把所有请求分成不同的优先级,然后从具有相同最高优先级的请求中随机选择一个,UVM_SEQ_ARB_USER是用户自定义算法。UVM默认采用UVM_SEQ_ARB_FIFO,我们可以通过调用sequencer的set_arbitration()函数来设置当前sequencer采用哪种仲裁算法:

//uvm_sequencer_base.svh
class uvm_sequencer_base extends uvm_component;
  typedef enum {SEQ_TYPE_REQ,
                SEQ_TYPE_LOCK,
                SEQ_TYPE_GRAB} seq_req_t;
  ...              
  local uvm_sequencer_arb_mode  m_arbitration = UVM_SEQ_ARB_FIFO;
  ...
  local static int              g_request_id;
  ...
endclass
function void uvm_sequencer_base::set_arbitration(UVM_SEQ_ARB_TYPE val);
  m_arbitration = val;
endfunction

class uvm_sequence_request;
  bit        grant;
  int        sequence_id;
  int        request_id;
  int        item_priority;
  process    process_id;
  uvm_sequencer_base::seq_req_t  request;
  uvm_sequence_base sequence_ptr;
endclass

  在uvm_sequencer_base中定义了一个枚举变量seq_req_t,表示request的请求类型如SEQ_TYPE_REQ和SEQ_TYPE_LOCK,此外UVM还提供了一个类uvm_sequence_request,这个类里面记录了当前一笔请求的sequence_id/request_id/request类型/对应sequence实例指针,request id等信息,这个request id是定义在uvm_sequencer_base类中的一个静态int类型变量,这个变量也是所有sequencer共享,每当系统中有一个新的uvm_sequence_request实例被创建,都会给它分配一个独一无二的新的request id。
  现在来看任务wait_for_grant():

//uvm_sequencer_base.svh
task uvm_sequencer_base::wait_for_grant(uvm_sequence_base sequence_ptr,
                                        int item_priority = -1,
                                        bit lock_request = 0);
  uvm_sequence_request req_s;
  int my_seq_id;

  if (sequence_ptr == null)
    uvm_report_fatal("uvm_sequencer",
       "wait_for_grant passed null sequence_ptr", UVM_NONE);

  my_seq_id = m_register_sequence(sequence_ptr);
  
  // If lock_request is asserted, then issue a lock.  Don't wait for the response, since
  // there is a request immediately following the lock request
  if (lock_request == 1) begin
    req_s = new();
    req_s.grant = 0;
    req_s.sequence_id = my_seq_id;
    req_s.request = SEQ_TYPE_LOCK;
    req_s.sequence_ptr = sequence_ptr;
    req_s.request_id = g_request_id++;
    req_s.process_id = process::self();
    arb_sequence_q.push_back(req_s);
  end
      
  // Push the request onto the queue
  req_s = new();
  req_s.grant = 0;
  req_s.request = SEQ_TYPE_REQ;
  req_s.sequence_id = my_seq_id;
  req_s.item_priority = item_priority;
  req_s.sequence_ptr = sequence_ptr;
  req_s.request_id = g_request_id++;
  req_s.process_id = process::self();
  arb_sequence_q.push_back(req_s);
  m_update_lists();

  // Wait until this entry is granted
  // Continue to point to the element, since location in queue will change
  m_wait_for_arbitration_completed(req_s.request_id);

  // The wait_for_grant_semaphore is used only to check that send_request
  // is only called after wait_for_grant.  This is not a complete check, since
  // requests might be done in parallel, but it will catch basic errors
  req_s.sequence_ptr.m_wait_for_grant_semaphore++;
endtask

  这个任务有三个参数,分别是等待被仲裁的sequence实例,priority和是否为lock request。首先传入的sequence实例指针不能为mull,然后调用m_register_sequence()函数拿到其sequence id,若这是一个lock request,则创建一个uvm_sequence_request类型实例req_s,把它的request设置为SEQ_TYPE_LOCK类型,并且把sequence_id,新的request_id(原request_id基础上加1)等信息储存起来,然后放入队列arb_sequence_q[]。然后创建一个新的uvm_sequence_request类型实例,把它的request设置为SEQ_TYPE_REQ类型并把sequence_id,priority,新的request_id等信息储存起来放入队列arb_sequence_q[],这个队列定义在uvm_sequencer_base中,用来存放等待被仲裁的所有uvm_sequence_request类型实例句柄,随后调用m_update_lists()来把int型变量m_lock_arb_size值加1,这个变量同样定义在uvm_sequencer_base中,它和int型变量m_arb_size配合用来判断当前仲裁队列arb_sequence_q[]中是否有任何更新。

//uvm_sequencer_base.svh
class uvm_sequencer_base extends uvm_component;
  // queue of sequences waiting for arbitration
  protected uvm_sequence_request arb_sequence_q[$];
  protected int                 m_lock_arb_size;  // used for waiting processes
  protected int                 m_arb_size;
  ...
endclass

function void uvm_sequencer_base::m_update_lists();
  m_lock_arb_size++;
endfunction

  随后将当前request的sequence_id作为参数调用m_wait_for_arbitration_completed()等待当前request仲裁结束:

//uvm_sequencer_base.svh
class uvm_sequencer_base extends uvm_component;
  protected bit                 arb_completed[int];
  ...
endclass

task uvm_sequencer_base::m_wait_for_arbitration_completed(int request_id);
  int lock_arb_size;
  
  // Search the list of arb_wait_q, see if this item is done
  forever 
    begin
      lock_arb_size  = m_lock_arb_size;
      
      if (arb_completed.exists(request_id)) begin
        arb_completed.delete(request_id);
        return;
      end
      wait (lock_arb_size != m_lock_arb_size);
    end
endtask

  这个任务内部是一个forever循环,每当仲裁队列有更新时,他就会检查数组arb_completed[]中是否有以传入的request_id为索引的内容,若找到则将其删除并返回。联合数组arb_completed[]定义在uvm_sequencer_base类中,以int类型为索引储存bit类型的值,用来记录哪些sequence_id已经仲裁完成。很明显,在当前这笔request被仲裁之前,m_wait_for_arbitration_completed()会一直等在这里,一旦仲裁完成就会返回,然后相应sequence的变量m_wait_for_grant_semaphore会被加1,这个int型变量定义在uvm_sequence_base类中,在sequence调用new()的时候会被初始化为0,仅仅是用来保证sequencer在调用send_request()之前需要先调用wait_for_grant()。
  当我们在sequence中调用start_item(),其中会调用wait_for_grant()来创建一些新的request并且调用m_wait_for_arbitration_completed()来等待数组arb_completed[]被更新,那这个事情谁来做呢?我们一般在driver的run_phase中会调用get_next_item():seq_item_port.get_next_item(req);实际上这里调用的是uvm_sequencer中的get_next_item()任务:

//uvm_sequencer.svh
class uvm_sequencer #(type REQ=uvm_sequence_item, RSP=REQ)
                                   extends uvm_sequencer_param_base #(REQ, RSP);
  bit sequence_item_requested;
  bit get_next_item_called;
  ...
endclass
task uvm_sequencer::get_next_item(output REQ t);
  REQ req_item;

  // If a sequence_item has already been requested, then get_next_item()
  // should not be called again until item_done() has been called.

  if (get_next_item_called == 1)
    uvm_report_error(get_full_name(),
      "Get_next_item called twice without item_done or get in between", UVM_NONE);
  
  if (!sequence_item_requested)
    m_select_sequence();

  // Set flag indicating that the item has been requested to ensure that item_done or get
  // is called between requests
  sequence_item_requested = 1;
  get_next_item_called = 1;
  m_req_fifo.peek(t);
endtask

  其中的bit类型变量sequence_item_requested和get_next_item_called在每次调用get_next_item()之后都会被置为1,在item_done()被调用时会把它们清零,防止在item_done()被调用之前多次调用get_next_item()引发问题。这其中最关键的就是调用m_select_sequence()任务,在这个任务中会真正进行所有request的仲裁工作。最后从m_req_fifo中试图拿到一笔sequence_item,这是一个定义在uvm_sequencer_param_base #()中的uvm_tlm_fifo #(REQ)类型接口,sequence_item就是通过这个接口来传输。来看m_select_sequence():

//uvm_sequencer_base.svh
task uvm_sequencer_base::m_select_sequence();
   int selected_sequence;

    // Select a sequence
    do begin
      wait_for_sequences();
      selected_sequence = m_choose_next_request();
      if (selected_sequence == -1) begin
        m_wait_for_available_sequence();
      end
    end while (selected_sequence == -1);
    // issue grant
    if (selected_sequence >= 0) begin
      m_set_arbitration_completed(arb_sequence_q[selected_sequence].request_id);
      arb_sequence_q.delete(selected_sequence);
      m_update_lists();
    end
endtask

  这个任务定义在uvm_sequencer_base类中,其中的while循环中会首先调用wait_for_sequences()任务,这个任务就是单纯的UVM仿真time slot延时,目的是为了消除可能存在的竞争冒险,可以略过。然后调用m_choose_next_request()函数来选择一个sequence,若返回的sequence_id为默认值-1,说明当前仲裁队列中没有合适的request,接着调用任务m_wait_for_available_sequence()来等待仲裁队列request更新。一旦拿到有效的request,则会调用m_set_arbitration_completed()把数组arb_completed[]相应记录更新,然后将这个request从队列arb_sequence_q[$]中删除并更新m_lock_arb_size的值。先来看函数m_choose_next_request():

//uvm_sequencer_base.svh
function int uvm_sequencer_base::m_choose_next_request();
  int i, temp;
  int avail_sequence_count;
  int sum_priority_val;
  integer avail_sequences[$];
  integer highest_sequences[$];
  int highest_pri;
  string  s;

  avail_sequence_count = 0;

  grant_queued_locks();

  i = 0;
  while (i < arb_sequence_q.size()) begin
     if ((arb_sequence_q[i].process_id.status == process::KILLED) ||
         (arb_sequence_q[i].process_id.status == process::FINISHED)) begin
        `uvm_error("SEQREQZMB", $sformatf("The task responsible for requesting a wait_for_grant on sequencer '%s' for sequence '%s' has been killed, to avoid a deadlock the sequence will be removed from the arbitration queues", this.get_full_name(), arb_sequence_q[i].sequence_ptr.get_full_name()))
         remove_sequence_from_queues(arb_sequence_q[i].sequence_ptr);
         continue;
     end

    if (i < arb_sequence_q.size())
      if (arb_sequence_q[i].request == SEQ_TYPE_REQ)
        if (is_blocked(arb_sequence_q[i].sequence_ptr) == 0)
          if (arb_sequence_q[i].sequence_ptr.is_relevant() == 1) begin
            if (m_arbitration == UVM_SEQ_ARB_FIFO) begin
              return i;
            end
            else avail_sequences.push_back(i);
          end

    i++;
  end

  // Return immediately if there are 0 or 1 available sequences
  if (m_arbitration == UVM_SEQ_ARB_FIFO) begin
    return -1;
  end
  if (avail_sequences.size() < 1)  begin
    return -1;
  end
  
  if (avail_sequences.size() == 1) begin
    return avail_sequences[0];
  end
  
  // If any locks are in place, then the available queue must
  // be checked to see if a lock prevents any sequence from proceeding
  if (lock_list.size() > 0) begin
    for (i = 0; i < avail_sequences.size(); i++) begin
      if (is_blocked(arb_sequence_q[avail_sequences[i]].sequence_ptr) != 0) begin
        avail_sequences.delete(i);
        i--;
      end
    end
    if (avail_sequences.size() < 1)
      return -1;
    if (avail_sequences.size() == 1)
      return avail_sequences[0];
  end

  //  Weighted Priority Distribution
  // Pick an available sequence based on weighted priorities of available sequences
  if (m_arbitration == UVM_SEQ_ARB_WEIGHTED) begin
    sum_priority_val = 0;
    for (i = 0; i < avail_sequences.size(); i++) begin
      sum_priority_val += m_get_seq_item_priority(arb_sequence_q[avail_sequences[i]]);
    end
    
    temp = $urandom_range(sum_priority_val-1, 0);

    sum_priority_val = 0;
    for (i = 0; i < avail_sequences.size(); i++) begin
      if ((m_get_seq_item_priority(arb_sequence_q[avail_sequences[i]]) + 
           sum_priority_val) > temp) begin
        return avail_sequences[i];
      end
      sum_priority_val += m_get_seq_item_priority(arb_sequence_q[avail_sequences[i]]);
    end
    uvm_report_fatal("Sequencer", "UVM Internal error in weighted arbitration code", UVM_NONE);
  end
  
  //  Random Distribution
  if (m_arbitration == UVM_SEQ_ARB_RANDOM) begin
    i = $urandom_range(avail_sequences.size()-1, 0);
    return avail_sequences[i];
  end

  //  Strict Fifo
  if ((m_arbitration == UVM_SEQ_ARB_STRICT_FIFO) || m_arbitration == UVM_SEQ_ARB_STRICT_RANDOM) begin
    highest_pri = 0;
    // Build a list of sequences at the highest priority
    for (i = 0; i < avail_sequences.size(); i++) begin
      if (m_get_seq_item_priority(arb_sequence_q[avail_sequences[i]]) > highest_pri) begin
        // New highest priority, so start new list
        highest_sequences.delete();
        highest_sequences.push_back(avail_sequences[i]);
        highest_pri = m_get_seq_item_priority(arb_sequence_q[avail_sequences[i]]);
      end
      else if (m_get_seq_item_priority(arb_sequence_q[avail_sequences[i]]) == highest_pri) begin
        highest_sequences.push_back(avail_sequences[i]);
      end
    end

    // Now choose one based on arbitration type
    if (m_arbitration == UVM_SEQ_ARB_STRICT_FIFO) begin
      return(highest_sequences[0]);
    end
    
    i = $urandom_range(highest_sequences.size()-1, 0);
    return highest_sequences[i];
  end

  if (m_arbitration == UVM_SEQ_ARB_USER) begin
    i = user_priority_arbitration( avail_sequences);

    // Check that the returned sequence is in the list of available sequences.  Failure to
    // use an available sequence will cause highly unpredictable results.
    highest_sequences = avail_sequences.find with (item == i);
    if (highest_sequences.size() == 0) begin
      uvm_report_fatal("Sequencer",
          $sformatf("Error in User arbitration, sequence %0d not available\n%s",
                    i, convert2string()), UVM_NONE);
    end
    return(i);
  end
    
  uvm_report_fatal("Sequencer", "Internal error: Failed to choose sequence", UVM_NONE);

endfunction

  这个函数会首先调用grant_queued_locks():

//uvm_sequencer_base.svh
function void uvm_sequencer_base::grant_queued_locks();
  // first remove sequences with dead lock control process
  begin
	  uvm_sequence_request q[$];
	  q = arb_sequence_q.find(item) with (item.request==SEQ_TYPE_LOCK && item.process_id.status inside {process::KILLED,process::FINISHED});
	  foreach(q[idx]) begin
		`uvm_error("SEQLCKZMB", $sformatf("The task responsible for requesting a lock on sequencer '%s' for sequence '%s' has been killed, to avoid a deadlock the sequence will be removed from the arbitration queues", this.get_full_name(), q[idx].sequence_ptr.get_full_name()))
		
		remove_sequence_from_queues(q[idx].sequence_ptr);
	  end	
  end
  
  // now move all is_blocked() into lock_list
  begin
	uvm_sequence_request leading_lock_reqs[$],blocked_seqs[$],not_blocked_seqs[$];  
	int q1[$];
	int b=arb_sequence_q.size(); // index for first non-LOCK request
	q1 = arb_sequence_q.find_first_index(item) with (item.request!=SEQ_TYPE_LOCK);
	if(q1.size())
		b=q1[0];  
	if(b!=0) begin // at least one lock
		leading_lock_reqs = arb_sequence_q[0:b-1]; // set of locks; arb_sequence[b] is the first req!=SEQ_TYPE_LOCK	
		// split into blocked/not-blocked requests
		foreach(leading_lock_reqs[i]) begin
			uvm_sequence_request item = leading_lock_reqs[i];
			if(is_blocked(item.sequence_ptr)!=0) begin
				blocked_seqs.push_back(item);
                        end
			else begin
				not_blocked_seqs.push_back(item);
                                lock_list.push_back(item.sequence_ptr);  // added per Mantis 5005
                        end
		end
		
		if(b>arb_sequence_q.size()-1)
			arb_sequence_q=blocked_seqs;
		  else
			arb_sequence_q={blocked_seqs,arb_sequence_q[b:arb_sequence_q.size()-1]};
	  
		foreach(not_blocked_seqs[idx]) begin
			//lock_list.push_back(not_blocked_seqs[idx].sequence_ptr);  // taken out per Mantis 5005
			m_set_arbitration_completed(not_blocked_seqs[idx].request_id);
		end
	
		// trigger listeners if lock list has changed
		if(not_blocked_seqs.size()) 	
			m_update_lists();	
	end	
  end
endfunction

  函数grant_queued_locks()会首先在整个request仲裁队列arb_sequence_q[$]中寻找是否有request为SEQ_TYPE_LOCK类型并且其当前process_state处于KILLED或者FINISHED状态,然后调用remove_sequence_from_queues()来删除这些requests:

//uvm_sequencer_base.svh
function void uvm_sequencer_base::remove_sequence_from_queues(
                                       uvm_sequence_base sequence_ptr);
  int i;
  int seq_id;
  
  seq_id = sequence_ptr.m_get_sqr_sequence_id(m_sequencer_id, 0);
  
  // Remove all queued items for this sequence and any child sequences
  i = 0;
  do 
    begin
      if (arb_sequence_q.size() > i) begin
        if ((arb_sequence_q[i].sequence_id == seq_id) ||
            (is_child(sequence_ptr, arb_sequence_q[i].sequence_ptr))) begin
          if (sequence_ptr.get_sequence_state() == UVM_FINISHED)
            `uvm_error("SEQFINERR", $sformatf("Parent sequence '%s' should not finish before all items from itself and items from descendent sequences are processed.  The item request from the sequence '%s' is being removed.", sequence_ptr.get_full_name(), arb_sequence_q[i].sequence_ptr.get_full_name()))
          arb_sequence_q.delete(i);
          m_update_lists();
        end
        else begin
          i++;
        end
      end
    end
  while (i < arb_sequence_q.size());
  
  // remove locks for this sequence, and any child sequences 
  i = 0;
  do
    begin
      if (lock_list.size() > i) begin
        if ((lock_list[i].get_inst_id() == sequence_ptr.get_inst_id()) ||
            (is_child(sequence_ptr, lock_list[i]))) begin
          if (sequence_ptr.get_sequence_state() == UVM_FINISHED)
            `uvm_error("SEQFINERR", $sformatf("Parent sequence '%s' should not finish before locks from itself and descedent sequences are removed.  The lock held by the child sequence '%s' is being removed.",sequence_ptr.get_full_name(), lock_list[i].get_full_name()))
          lock_list.delete(i);
          m_update_lists();
        end
        else begin
          i++;
        end
      end
    end
  while (i < lock_list.size());
  
  // Unregister the sequence_id, so that any returning data is dropped
  m_unregister_sequence(sequence_ptr.m_get_sqr_sequence_id(m_sequencer_id, 1));
endfunction

  函数remove_sequence_from_queues()很简单,若当前传入的sequence指针及其子sequence的m_state为UVM_FINISHED状态,表明其已经执行完毕,将它们分别从仲裁队列arb_sequence_q[$]和lock_list[$]以及数组reg_sequences[]中删除,并且更新m_lock_arb_size的值。队列lock_list[$]设定义在uvm_sequencer_base类中的uvm_sequence_base类型的队列,储存的是当前sequencer被哪些sequence锁定。

//uvm_sequencer_base.svh
class uvm_sequencer_base extends uvm_component;
  protected uvm_sequence_base   lock_list[$];
  ...
endclass

  我们可以在sequence中调用lock()/grab()来获得sequencer的优先使用权并一直占有该sequencer,直到调用unlock()/ungrab()来解除独占权。

//uvm_sequencer_base.svh
task uvm_sequencer_base::m_lock_req(uvm_sequence_base sequence_ptr, bit lock);
  int my_seq_id;
  uvm_sequence_request new_req;
  
  if (sequence_ptr == null)
    uvm_report_fatal("uvm_sequence_controller",
                     "lock_req passed null sequence_ptr", UVM_NONE);

  my_seq_id = m_register_sequence(sequence_ptr);
  new_req = new();
  new_req.grant = 0;
  new_req.sequence_id = sequence_ptr.get_sequence_id();
  new_req.request = SEQ_TYPE_LOCK;
  new_req.sequence_ptr = sequence_ptr;
  new_req.request_id = g_request_id++;
  new_req.process_id = process::self();
  
  if (lock == 1) begin
    // Locks are arbitrated just like all other requests
    arb_sequence_q.push_back(new_req);
  end else begin
    // Grabs are not arbitrated - they go to the front
    // TODO:
    // Missing: grabs get arbitrated behind other grabs
    arb_sequence_q.push_front(new_req);
    m_update_lists();
  end

  // If this lock can be granted immediately, then do so.
  grant_queued_locks();
  
  m_wait_for_arbitration_completed(new_req.request_id);
endtask
  

// m_unlock_req
// ------------
// Called by a sequence to request an unlock.  This
// will remove a lock for this sequence if it exists

function void uvm_sequencer_base::m_unlock_req(uvm_sequence_base sequence_ptr);
  if (sequence_ptr == null) begin
    uvm_report_fatal("uvm_sequencer",
                     "m_unlock_req passed null sequence_ptr", UVM_NONE);
  end
 
  begin
	  int q[$];
	  int seqid=sequence_ptr.get_inst_id();
	  q=lock_list.find_first_index(item) with (item.get_inst_id() == seqid);
	  if(q.size()==1) begin
	      lock_list.delete(q[0]);
		  grant_queued_locks(); // grant lock requests 
		  m_update_lists();	 
	  end
	  else
		  uvm_report_warning("SQRUNL", 
           {"Sequence '", sequence_ptr.get_full_name(),
            "' called ungrab / unlock, but didn't have lock"}, UVM_NONE);  

  end
endfunction


// lock
// ----

task uvm_sequencer_base::lock(uvm_sequence_base sequence_ptr);
  m_lock_req(sequence_ptr, 1);
endtask


// grab
// ----

task uvm_sequencer_base::grab(uvm_sequence_base sequence_ptr);
  m_lock_req(sequence_ptr, 0);
endtask


// unlock
// ------

function void uvm_sequencer_base::unlock(uvm_sequence_base sequence_ptr);
  m_unlock_req(sequence_ptr);
endfunction


// ungrab
// ------

function void  uvm_sequencer_base::ungrab(uvm_sequence_base sequence_ptr);
  m_unlock_req(sequence_ptr);
endfunction

  lock()和grab()都是调用m_lock_req()任务,二者唯一的区别就是传入的第二个参数lock前者为1而后者为0,在m_lock_req()中会创建一个uvm_sequence_request实例,把request设置为SEQ_TYPE_LOCK类型,接下来若参数lock为1,则把该实例指针放到队列arb_sequence_q[$]最后,否则放在队列最前面并更新m_lock_arb_size的值。这么做的区别在于:
  lock()操作需要等待当前sequence和其它先提起lock/grab请求的sequences执行完毕才轮到自己。而grab()操作只需要等待当前sequence执行完毕就会开始占有sequencer,不必等待其它先提起lock/grab请求的sequences。
在解除占用的时候函数unlock()和ungrab()是一样的,都会调用m_unlock_req(),这个函数会把参数传入的sequence句柄从lock_list[$]中删除,然后调用grant_queued_locks()并且更新m_lock_arb_size的值。
  回到grant_queued_locks()中,函数检查队列arb_sequence_q[$]中是否存在request不为SEQ_TYPE_LOCK类型的请求,这里有三种情况:

  1. 队列arb_sequence_q[$]中全部都是SEQ_TYPE_LOCK类型的请求,此时函数会遍历这个队列调用is_blocked()来检查lock_list[$]中是否有相关记录,若lock_list[$]为空会直接返回0,若记录不是当前检查的这个sequence或其parent sequence,说明有其它sequence提交了lock请求,而当前的这个请求被block住了,is_blocked()会返回1,那么把这个request放入blocked_seqs[$],否则把这个request分别放入队列not_blocked_seqs[$]和lock_list[$](进入队列lock_list[$]的请求就可以独占当前sequencer了),然后把blocked_seqs[$]中的请求转移到arb_sequence_q[$]等待下一次调用grant_queued_locks()被仲裁。对于not_blocked_seqs[$]中的请求,则调用m_set_arbitration_completed()把他们对应的arb_completed[]记录置为1,表明仲裁完成,最后更新m_lock_arb_size的值。
  2. 队列arb_sequence_q[$]中第一个请求不是SEQ_TYPE_LOCK类型的,其索引为0,则lock操作需要等待该请求执行完毕,等待下次调用grant_queued_locks()被仲裁。
  3. 队列arb_sequence_q[$]中第一个不是SEQ_TYPE_LOCK类型的请求索引大于0,说明至少有一个SEQ_TYPE_LOCK类型请求排在队列最前面,则调用is_blocked()遍历排在前面的这些SEQ_TYPE_LOCK类型的请求,若返回1,把这个request放入blocked_seqs[$],否则把这个request分别放入队列not_blocked_seqs[$]和lock_list[$]。对于not_blocked_seqs[$]队列中的请求,把它们从仲裁队列arb_sequence_q[$]中删除,调用m_set_arbitration_completed()把它们对应的arb_completed[]记录置为1,表明仲裁完成,最后更新m_lock_arb_size的值。
//uvm_sequencer_base.svh
function bit uvm_sequencer_base::is_blocked(uvm_sequence_base sequence_ptr);

  if (sequence_ptr == null)
    uvm_report_fatal("uvm_sequence_controller",
                     "is_blocked passed null sequence_ptr", UVM_NONE);

    foreach (lock_list[i]) begin
      if ((lock_list[i].get_inst_id() != 
           sequence_ptr.get_inst_id()) &&
          (is_child(lock_list[i], sequence_ptr) == 0)) begin
        return 1;
      end
    end 
    return 0;
endfunction

function void uvm_sequencer_base::m_set_arbitration_completed(int request_id);
  arb_completed[request_id] = 1;
endfunction

  回到函数m_choose_next_request()中,在调用grant_queued_locks()把排在仲裁队列前面的SEQ_TYPE_LOCK类型的请求放入队列lock_list[$]之后,会遍历仲裁队列arb_sequence_q[$]把当前process state为KILLED或者FINISHED的请求调用remove_sequence_from_queues()分别从arb_sequence_q[$]和lock_list[$]中删除。遍历仲裁队列arb_sequence_q[$]时,若request为SEQ_TYPE_REQ,调用is_blocked()返回0,并且is_relevant()返回值为1时,若此时仲裁算法为默认的UVM_SEQ_ARB_FIFO,则直接返回当前的request,因为这是仲裁队列中第一个符合条件的request。若采用别的仲裁算法,则把这些符合上述条件的requests放入队列avail_sequences[$]等待后续算法处理。
  这里is_relevant()是定义在uvm_sequence_base类中的函数,表示当前sequence是否可以参与仲裁,默认返回值为1,我们可以在sequence中重载这个函数,决定sequence被执行的时机。若所有的sequence的is_relevant()返回值都是0,sequencer会调用wait_for_relevant()任务来等待sequence再次有效。我们在重载is_relevant()时可以同时重载wait_for_relevant()来决定sequence再次有效的时机。

class uvm_sequence_base extends uvm_sequence_item;
  ...
  virtual function bit is_relevant(); 
    is_rel_default = 1;
    return 1;
  endfunction

  virtual task wait_for_relevant();
    event e;
    wait_rel_default = 1;
    if (is_rel_default != wait_rel_default)
      uvm_report_fatal("RELMSM", 
        "is_relevant() was implemented without defining wait_for_relevant()", UVM_NONE);
    @e;  // this is intended to never return
  endtask
  ...
endclass

  在函数m_choose_next_request()中,若avail_sequences[$]为空则直接返回-1,若其中只有一个request,则直接返回这个request句柄。接着遍历avail_sequences[$],再次调用is_blocked(),若返回值不为0,则从avail_sequences[$]中删除该request。之后若avail_sequences[$]为空则直接返回-1,若其中只有一个request,则直接返回这个request句柄。接着根据不同的仲裁算法在avail_sequences[$]找到相应的request并返回,关于这些算法具体实现,这里不再赘述。
  接着来看m_wait_for_available_sequence():

//uvm_sequencer_base.svh
task uvm_sequencer_base::m_wait_for_available_sequence();
  int i;
  int is_relevant_entries[$];

  // This routine will wait for a change in the request list, or for
  // wait_for_relevant to return on any non-relevant, non-blocked sequence
  m_arb_size = m_lock_arb_size;

  for (i = 0; i < arb_sequence_q.size(); i++) begin
    if (arb_sequence_q[i].request == SEQ_TYPE_REQ) begin
      if (is_blocked(arb_sequence_q[i].sequence_ptr) == 0) begin
        if (arb_sequence_q[i].sequence_ptr.is_relevant() == 0) begin
          is_relevant_entries.push_back(i);
        end
      end
    end
  end

  // Typical path - don't need fork if all queued entries are relevant
  if (is_relevant_entries.size() == 0) begin
    m_wait_arb_not_equal();
    return;
  end

  fork  // isolate inner fork block for disabling
    begin
      fork
        begin
          fork
              begin
                // One path in fork is for any wait_for_relevant to return
                m_is_relevant_completed = 0;
                
                for(i = 0; i < is_relevant_entries.size(); i++) begin
                fork
                    automatic int k = i;
                    
                  begin
                    arb_sequence_q[is_relevant_entries[k]].sequence_ptr.wait_for_relevant();
                    if ($realtime != m_last_wait_relevant_time) begin
                       m_last_wait_relevant_time = $realtime ;
                       m_wait_relevant_count = 0 ;
                    end
                    else begin
                       m_wait_relevant_count++ ;
                       if (m_wait_relevant_count > m_max_zero_time_wait_relevant_count) begin
                          `uvm_fatal("SEQRELEVANTLOOP",$sformatf("Zero time loop detected, passed wait_for_relevant %0d times without time advancing",m_wait_relevant_count))
                       end
                    end
                    m_is_relevant_completed = 1;
                  end
                join_none
                  
                end
                wait (m_is_relevant_completed > 0);
              end
              
            // The other path in the fork is for any queue entry to change
            begin
              m_wait_arb_not_equal();
            end
          join_any
        end
      join_any
      disable fork;
    end
  join
endtask

  在这个任务中首先遍历队列arb_sequence_q[$]把request为SEQ_TYPE_REQ类型并且调用is_blocked()返回值为0但是is_relevant()返回值为0的requests在队列arb_sequence_q[$]中的索引存入int型队列is_relevant_entries[$],若这个队列为空,则说明当前所有request都是有效的,则调用m_wait_arb_not_equal()等待m_lock_arb_size的值更新(说明仲裁队列arb_sequence_q[$]有更新)后直接返回。若队列is_relevant_entries[$]不为空,说明当前有request是无效的,通过fork…join_any启动并行线程,一方面调用m_wait_arb_not_equal()等待m_lock_arb_size的值更新,若有更新函数会杀掉fork进程立即返回。另一方面对队列is_relevant_entries[$]中所有requests使用fork…join_none在同一时间并行调用wait_for_relevant(),等该函数返回后检查仿真时间是否向前推进,若仿真时间一直没有向前推进,则每次执行到这里m_wait_relevant_count值都会加1,当执行次数超过m_max_zero_time_wait_relevant_count时会报错,m_max_zero_time_wait_relevant_count默认值是10,这段代码是为了防止仿真block在这里,最后把m_is_relevant_completed置为1,表示某个request的wait_for_relevant()已经返回,这样仲裁队列中就会又有relevant的requests,函数也会杀掉fork进程立即返回。

2.5. finish_item()

  通常来说,在start_item()之后必须调用finish_item(),中间可以做些随机化的操作,来看finish_item():

//uvm_sequence_base.svh
class uvm_sequence_base extends uvm_sequence_item;
  ...
  virtual task finish_item (uvm_sequence_item item,
                            int set_priority = -1);

    uvm_sequencer_base sequencer;
    
    sequencer = item.get_sequencer();

    if (sequencer == null) begin
        uvm_report_fatal("STRITM", "sequence_item has null sequencer", UVM_NONE);
    end

    mid_do(item);
    sequencer.send_request(this, item);
    sequencer.wait_for_item_done(this, -1);

    if (sequencer.is_auto_item_recording_enabled()) begin
      sequencer.end_tr(item);
    end
    `ifndef AMD_DISABLE_SEQ_BASE_END_EVENT_TRIGGER
    else begin
      item.end_event.trigger();
    end
    `endif
    post_do(item);

  endtask
  ...
endclass

  在finish_item()任务中,首先调用get_sequencer()拿到当前sequence_item的m_sequencer句柄,然后执行callback mid_do(),接着分别调用send_request()和wait_for_item_done(),最后执行callback post_do()。来看send_request():

//uvm_sequencer_param_base.svh
function void uvm_sequencer_param_base::send_request(uvm_sequence_base sequence_ptr,
                                                     uvm_sequence_item t,
                                                     bit rerandomize = 0);
  REQ param_t;

  if (sequence_ptr == null) begin
    uvm_report_fatal("SNDREQ", "Send request sequence_ptr is null", UVM_NONE);
  end

  if (sequence_ptr.m_wait_for_grant_semaphore < 1) begin
    uvm_report_fatal("SNDREQ", "Send request called without wait_for_grant", UVM_NONE);
  end
  sequence_ptr.m_wait_for_grant_semaphore--;
  
  if ($cast(param_t, t)) begin
    if (rerandomize == 1) begin
      if (!param_t.randomize()) begin
        uvm_report_warning("SQRSNDREQ", "Failed to rerandomize sequence item in send_request");
      end
    end
    if (param_t.get_transaction_id() == -1) begin
      param_t.set_transaction_id(sequence_ptr.m_next_transaction_id++);
    end
    m_last_req_push_front(param_t);
  end else begin
    uvm_report_fatal(get_name(),$sformatf("send_request failed to cast sequence item"), UVM_NONE);
  end

  param_t.set_sequence_id(sequence_ptr.m_get_sqr_sequence_id(m_sequencer_id, 1));
  t.set_sequencer(this);
  if (m_req_fifo.try_put(param_t) != 1) begin
    uvm_report_fatal(get_full_name(), "Concurrent calls to get_next_item() not supported. Consider using a semaphore to ensure that concurrent processes take turns in the driver", UVM_NONE);
  end

  m_num_reqs_sent++;
  // Grant any locks as soon as possible
  grant_queued_locks();
endfunction

  这个函数定义在uvm_sequencer_param_base #()中,首先检查m_wait_for_grant_semaphore是否为0,这个变量就是保证在每次调用send_request()之前已经调用过wait_for_grant(),在每次执行wait_for_grant()时相应request被仲裁之后加1,之后才能调用send_request()并在其中将变量的值减1。若函数第三个参数rerandomize为1,则对sequence_item做随机化操作。这里之所以要重新随机化,是因为若直接对uvm_sequence_item类型的t随机化时,我们真正要发送的继承自uvm_sequence_item类型的REQ类型中的某些变量是不会做随机化的。接着调用get_transaction_id()拿到sequence_item的m_transaction_id,若其为默认值-1,则把当前sequence中的变量m_next_transaction_id值加1作为参数调用set_transaction_id()赋值给m_transaction_id。integer类型变量m_transaction_id定义在uvm_transaction类中,代表每一笔transaction的id。int型变量m_next_transaction_id定义在uvm_sequence_base类中,用来记录当前sequence要发送的transaction的m_transaction_id。

//uvm_transaction.svh
virtual class uvm_transaction extends uvm_object;
  ...
  local integer m_transaction_id = -1;
  ...
endclass
// set_transaction_id
function void uvm_transaction::set_transaction_id(integer id);
    m_transaction_id = id;
endfunction

// get_transaction_id
function integer uvm_transaction::get_transaction_id();
    return (m_transaction_id);
endfunction

//uvm_sequence_base.svh
class uvm_sequence_base extends uvm_sequence_item;
  ...
  int                m_next_transaction_id = 1;
  ...
endclass

  接下来调用m_last_req_push_front()把要发送的这个sequence_item放入队列m_last_req_buffer[$]:

//uvm_sequencer_param_base.svh
class uvm_sequencer_param_base #(type REQ = uvm_sequence_item,
                                 type RSP = REQ) extends uvm_sequencer_base;
  REQ m_last_req_buffer[$];
  protected int m_num_last_reqs = 1;
  ...
endclass
function void uvm_sequencer_param_base::m_last_req_push_front(REQ item);
  if(!m_num_last_reqs)
    return;
 
  if(m_last_req_buffer.size() == m_num_last_reqs)
    void'(m_last_req_buffer.pop_back());

  this.m_last_req_buffer.push_front(item);
endfunction

  队列m_last_req_buffer[$]定义在uvm_sequencer_param_base #()中,存放的是通过该sequencer发送的最后的一些sequence_item句柄,能存放多少个由int型变量m_num_last_reqs决定,默认值是1,若当前队列已经存满,则把队列最后面的一个sequence_item句柄弹出,将当前要发送的sequence_item存入队列最前面位置。
  回到函数send_request()中,设置好要发送sequence_item的sequence_id,调用try_put()向m_req_fifo中放入要发送的sequence_item,sequencer的get_next_item()会从中取走它交在driver。接下来int型变量m_num_reqs_sent的值加1,用来记录当前发送了多少个sequence_item,最后调用grant_queued_locks(),尽快处理仲裁队列中出现的lock请求。
  回到finish_item()中来,当send_request()将sequence_item发送出去之后,接着调用wait_for_item_done():

//uvm_sequencer_base.svh
task uvm_sequencer_base::wait_for_item_done(uvm_sequence_base sequence_ptr,
                                            int transaction_id);
  int sequence_id;

  sequence_id = sequence_ptr.m_get_sqr_sequence_id(m_sequencer_id, 1);
  m_wait_for_item_sequence_id = -1;
  m_wait_for_item_transaction_id = -1;

  if (transaction_id == -1)
    wait (m_wait_for_item_sequence_id == sequence_id);
  else
    wait ((m_wait_for_item_sequence_id == sequence_id &&
           m_wait_for_item_transaction_id == transaction_id));
endtask

  这个函数定义在uvm_sequencer_base中,在其中会把两个变量m_wait_for_item_sequence_id和m_wait_for_item_transaction_id分别置为-1,然后等待sequence_id和transaction_id的值被复位为-1,实际上是在等待item_done()被调用,我们后面会讨论。
  因而,start_item()与finish_item()中各函数/任务执行顺序如下:
//| sequencer.wait_for_grant(prior) (task)   \ start_item  \
//| parent_seq.pre_do(1) (task)        /        \
//|                             `uvm_do* macros
//| parent_seq.mid_do(item) (func)       \       /
//| sequencer.send_request(item) (func)      \ finish_item /
//| sequencer.wait_for_item_done() (task)     /
//| parent_seq.post_do(item) (func)      /

3. sequencer与driver的交互

3.1. put_response()

  我们在driver中调用get_next_item()拿到sequence_item并做出相应处理之后,要接着调用rsp.set_id_info(req)把request的transaction_id和sequence_id设置给response,然后调用seq_item_port.item_done(rsp),这里实际上执行的是uvm_sequencer的item_done():

//uvm_sequencer.svh
function void uvm_sequencer::item_done(RSP item = null);
  REQ t;

  // Set flag to allow next get_next_item or peek to get a new sequence_item
  sequence_item_requested = 0;
  get_next_item_called = 0;
  
  if (m_req_fifo.try_get(t) == 0) begin
    uvm_report_fatal(get_full_name(), {"Item_done() called with no outstanding requests.",
      " Each call to item_done() must be paired with a previous call to get_next_item()."});
  end else begin
    m_wait_for_item_sequence_id = t.get_sequence_id();
    m_wait_for_item_transaction_id = t.get_transaction_id();
  end
  
  if (item != null) begin
    seq_item_export.put_response(item);
  end

  // Grant any locks as soon as possible
  grant_queued_locks();
endfunction

  首先从m_req_fifo试图拿到一个sequence_item,若成功则把其sequence_id和transaction_id分别赋值给m_wait_for_item_sequence_id和m_wait_for_item_transaction_id表示已经成功取到该sequence_id对应的这笔transaction,此时wait_for_item_done()中的wait()语句被触发。若参数传入的item句柄不为null,则调用 seq_item_export.put_response(),最后再次仲裁队列中的lock请求。这里实际上调用的是uvm_sequencer_param_base #()中的put_response()函数:

//uvm_sequencer_param_base.svh
function void uvm_sequencer_param_base::put_response (RSP t);
  uvm_sequence_base sequence_ptr;
  
  if (t == null) begin
    uvm_report_fatal("SQRPUT", "Driver put a null response", UVM_NONE);
  end

  m_last_rsp_push_front(t);
  m_num_rsps_received++;

  // Check that set_id_info was called
  if (t.get_sequence_id() == -1) begin
`ifndef CDNS_NO_SQR_CHK_SEQ_ID
    uvm_report_fatal("SQRPUT", "Driver put a response with null sequence_id", UVM_NONE);
`endif
    return;
  end
    
  sequence_ptr = m_find_sequence(t.get_sequence_id());

  if (sequence_ptr != null) begin
    // If the response_handler is enabled for this sequence, then call the response handler
    if (sequence_ptr.get_use_response_handler() == 1) begin
      sequence_ptr.response_handler(t);
      return;
    end
    
    sequence_ptr.put_response(t);
  end
  else begin
    uvm_report_info("Sequencer", 
                    $sformatf("Dropping response for sequence %0d, sequence not found.  Probable cause: sequence exited or has been killed", 
                              t.get_sequence_id()));
  end
endfunction

function void uvm_sequencer_param_base::m_last_rsp_push_front(RSP item);
  if(!m_num_last_rsps)
    return;
 
  if(m_last_rsp_buffer.size() == m_num_last_rsps)
    void'(m_last_rsp_buffer.pop_back());

  this.m_last_rsp_buffer.push_front(item);
endfunction

  函数开始调用m_last_rsp_push_front(),这个跟之前的m_last_req_push_front()函数类似,把最后发送的response存放在RSP类型的队列m_last_rsp_buffer[$]中。用int型变量m_num_rsps_received记录发送的response数量。接着检查参数item的sequence_id是否为-1,若是(说明在driver中调用item_done()之前没有调用set_id_info()为response设置相关信息),函数直接返回。接下来若sequence的get_use_response_handler()返回1,则直接调用response_handler()并返回,我们后面会研究这个response_handler。否则,调用sequence的put_response():

//uvm_sequence_base.svh
class uvm_sequence_base extends uvm_sequence_item;
  ...
  protected uvm_sequence_item response_queue[$];
  protected int               response_queue_depth = 8;
  ...
  virtual function void put_base_response(input uvm_sequence_item response);
    if ((response_queue_depth == -1) ||
        (response_queue.size() < response_queue_depth)) begin
      response_queue.push_back(response);
      return;
    end
    if (response_queue_error_report_disabled == 0) begin
      uvm_report_error(get_full_name(), "Response queue overflow, response was dropped", UVM_NONE);
    end
  endfunction
  
  virtual function void put_response (uvm_sequence_item response_item);
    put_base_response(response_item); // no error-checking
  endfunction
  ...
endclass

  这个函数会直接调用put_base_response(),这里会首先判断负责存放response的uvm_sequence_item类型的队列response_queue[$]的大小,其大小由int型变量 response_queue_depth决定,默认值是8,若response_queue_depth的值是-1,表示该队列可以无限存放response,若当前队列未满,则向其中存入通过参数传入的response句柄。

3.2. get_response()

  我们在sequence中发送完一个item之后等待driver返回一个response,通常会调用get_response():

//uvm_sequence.svh
virtual class uvm_sequence #(type REQ = uvm_sequence_item,
                             type RSP = REQ) extends uvm_sequence_base;
  ...
  virtual task get_response(output RSP response, input int transaction_id = -1);
    uvm_sequence_item rsp;
    get_base_response( rsp, transaction_id);
    $cast(response,rsp);
  endtask
  ...
endclass

  任务get_response()会直接调用get_base_response():

//uvm_sequence_base.svh
class uvm_sequence_base extends uvm_sequence_item;
  ...
  virtual task get_base_response(output uvm_sequence_item response, input int transaction_id = -1);

    int queue_size, i;

    if (response_queue.size() == 0)
      wait (response_queue.size() != 0);

    if (transaction_id == -1) begin
      response = response_queue.pop_front();
      return;
    end

    forever begin
      queue_size = response_queue.size();
      for (i = 0; i < queue_size; i++) begin
        if (response_queue[i].get_transaction_id() == transaction_id) 
          begin
            $cast(response,response_queue[i]);
            response_queue.delete(i);
            return;
          end
      end
      wait (response_queue.size() != queue_size);
    end
  endtask
  ...
endclass

  若当前response_queue[$]队列中没有response,则先等待直到有response被放入该队列。通过第二个参数transaction_id可以拿到与之相对于的response,若参数为默认值-1,则从response_queue[$]最前面取出一个response并返回。若指定了参数transaction_id不为-1,则每次response_queue[$]有更新,就遍历该队列找到与之匹配的response并将其从队列中删除。

3.3. response_handler

  通过之前的分析可以看出,在sequence中通过get_response()来取得response依赖于put_response()把response放入response_queue[$],若driver中response产生不及时,则sequence中get_response()就会一直阻塞在那里,进而导致无法放下一笔transaction,而driver也会被阻塞在这里。此时可以使用response_handler,将get_response()和put_response()的依赖性分开,在之前分析的uvm_sequencer_param_base #()::put_response()中,若sequence调用get_use_response_handler()返回值为1,会直接执行response_handler()函数并返回,而不会再去sequence的put_response()函数,从而可以避免等待response_queue[$]中的response。

//uvm_sequence_base.svh
class uvm_sequence_base extends uvm_sequence_item;
  ...
  local bit m_use_response_handler;
  ...
  function void use_response_handler(bit enable);
    m_use_response_handler = enable;
  endfunction

  function bit get_use_response_handler();
    return m_use_response_handler;
  endfunction

  virtual function void response_handler(uvm_sequence_item response);
    return;
  endfunction
  ...
endclass

  函数get_use_response_handler()会返回变量m_use_response_handler的值,这是一个定义在uvm_sequence_base类中的bit型变量,默认值是0,我们可以通过调用use_response_handler(1)把变量m_use_response_handler置为1,同时可以通过重载函数response_handler()来处理每一笔response。

4. 其它

4.1. p_sequencer

  由于sequence是一个object类型,在仿真过程中是动态分配内存的,我们若想从sequence中访问UVM hireachy中其它component节点中的资源非常困难,因此需要一个中介句柄,其对应的sequencer恰巧就可以作为一个component类型的句柄。刚好在sequence中有一个指向其对应sequencer的uvm_sequencer_base类型的句柄m_sequencer,我们可以在sequence中使用m_sequencer这个句柄来访问UVM平台其它component节点的资源。但是这么做还存在一个问题,由于m_sequencer是uvm_sequencer_base类型,我们平台中使用的sequencer是从uvm_sequencer_base类型继承而来的,这样m_sequencer是没有办法访问到我们扩展类sequencer中自定义的一些变量。例如我们现在定义一个sequencer:

class my_sqr extends uvm_sequencer;
  int num;
  ...
endclass

  很显然m_sequencer是没有办法访问my_sqr中的num的。为了解决这个问题,我们可以使用p_sequencer,在sequence中使用宏`uvm_declare_p_sequencer(my_sqr)来声明当前sequence的p_sequencer为my_sqr类型:

//uvm_sequence_defines.svh
`define uvm_declare_p_sequencer(SEQUENCER) \
  SEQUENCER p_sequencer;\
  virtual function void m_set_p_sequencer();\
    super.m_set_p_sequencer(); \
    if( !$cast(p_sequencer, m_sequencer)) \
        `uvm_fatal("DCLPSQ", \
        $sformatf("%m %s Error casting p_sequencer, please verify that this sequence/sequence item is intended to execute on this type of sequencer", get_full_name())) \
  endfunction 

  这个宏首先声明一个通过参数传入的SEQUENCER类型的p_sequencer句柄,然后重载函数m_set_p_sequencer(),在其中把m_sequencer赋值给p_sequencer句柄,这样可以使用p_sequencer句柄来访问my_sqr的变量num。函数m_set_p_sequencer()定义在uvm_sequence_item类中,当其被重载后,在调用set_sequencer()时会调用这个函数来同时设置m_sequencer和p_sequencer,而set_sequencer()又会在set_item_context()执行时被调用。在执行start(),start_item(),create_item()等时都会调用set_item_context(),最终会调用set_sequencer()来设置m_sequencer和p_sequencer。

4.2. default_sequence

  前面提到过我们可以通过uvm_config_db机制把某个sequence设置为其对应sequencer的某task phase的default_sequence,UVM phase机制主要执行m_traverse()函数(参看UVM phase机制源码探微),来看task phase的m_traverse()函数:

//uvm_task_phase.svh
virtual class uvm_task_phase extends uvm_phase;
  ...
  function void m_traverse(uvm_component comp,
                           uvm_phase phase,
                           uvm_phase_state state);
      uvm_sequencer_base seqr;
      ...
      case (state)
        UVM_PHASE_STARTED: begin
		  ...
          if ($cast(seqr, comp))
            seqr.start_phase_sequence(phase);
          end
      ...
endclass

  在phase_state为UVM_PHASE_STARTED阶段,会判断当前的component是否是一个uvm_sequencer_base类型(即是否为一个sequencer),若是,则调用该sequencer的start_phase_sequence():

//uvm_sequencer_base.svh
function void uvm_sequencer_base::start_phase_sequence(uvm_phase phase);
  uvm_resource_pool            rp = uvm_resource_pool::get();
  uvm_resource_types::rsrc_q_t rq;
  uvm_sequence_base            seq;
  uvm_coreservice_t cs = uvm_coreservice_t::get();
  uvm_factory                  f = cs.get_factory();
  
  // Has a default sequence been specified?
  rq = rp.lookup_name({get_full_name(), ".", phase.get_name(), "_phase"},
                      "default_sequence", null, 0);
  uvm_resource_pool::sort_by_precedence(rq);
  
  // Look for the first one if the appropriate type
  for (int i = 0; seq == null && i < rq.size(); i++) begin
    uvm_resource_base rsrc = rq.get(i);
    
    uvm_resource#(uvm_sequence_base)  sbr;
    uvm_resource#(uvm_object_wrapper) owr;

    // uvm_config_db#(uvm_sequence_base)?
    // Priority is given to uvm_sequence_base because it is a specific sequence instance
    // and thus more specific than one that is dynamically created via the
    // factory and the object wrapper.
    if ($cast(sbr, rsrc) && sbr != null) begin
      seq = sbr.read(this);
      if (seq == null) begin
        `uvm_info("UVM/SQR/PH/DEF/SB/NULL", {"Default phase sequence for phase '",
                                             phase.get_name(),"' explicitly disabled"}, UVM_FULL)
        return;
      end
    end
    
    // uvm_config_db#(uvm_object_wrapper)?
    else if ($cast(owr, rsrc) && owr != null) begin
      uvm_object_wrapper wrapper;

      wrapper = owr.read(this);
      if (wrapper == null) begin
        `uvm_info("UVM/SQR/PH/DEF/OW/NULL", {"Default phase sequence for phase '",
                                             phase.get_name(),"' explicitly disabled"}, UVM_FULL)
        return;
      end

      if (!$cast(seq, f.create_object_by_type(wrapper, get_full_name(),
                                              wrapper.get_type_name()))
          || seq == null) begin
        `uvm_warning("PHASESEQ", {"Default sequence for phase '",
                                  phase.get_name(),"' %s is not a sequence type"})
        return;
      end
    end
  end
  
  if (seq == null) begin
    `uvm_info("PHASESEQ", {"No default phase sequence for phase '",
                           phase.get_name(),"'"}, UVM_FULL)
    return;
  end
  
  `uvm_info("PHASESEQ", {"Starting default sequence '",
                         seq.get_type_name(),"' for phase '", phase.get_name(),"'"}, UVM_FULL)
  
  seq.print_sequence_info = 1;
  seq.set_sequencer(this);
  seq.reseed();
  seq.set_starting_phase(phase);
  
  if (!seq.do_not_randomize && !seq.randomize()) begin
    `uvm_warning("STRDEFSEQ", {"Randomization failed for default sequence '",
                               seq.get_type_name(),"' for phase '", phase.get_name(),"'"})
    return;
  end
  
  fork begin
    uvm_sequence_process_wrapper w = new();
    // reseed this process for random stability
    w.pid = process::self();
    w.seq = seq;
    w.pid.srandom(uvm_create_random_seed(seq.get_type_name(), this.get_full_name()));
    m_default_sequences[phase] = w;
    // this will either complete naturally, or be killed later
    seq.start(this);
    m_default_sequences.delete(phase);
  end
  join_none
  
endfunction

  在这个函数里首先判断是否有uvm_sequence_base类型通过uvm_config_db被设置为"default_sequence",然后用factory机制拿到sequence的一个实例句柄,为其设置sequencer等信息,然后调用set_starting_phase()函数为starting_phase赋值,最后用fork…join_none启动新线程,在其中启动新的进程调用start()来执行这个sequence。来看set_starting_phase()函数:

//uvm_sequence_base.svh
class uvm_sequence_base extends uvm_sequence_item;
  ...
`ifndef UVM_NO_DEPRECATED
 `define UVM_DEPRECATED_STARTING_PHASE
`endif

`ifdef UVM_DEPRECATED_STARTING_PHASE
  // DEPRECATED!! Use get/set_starting_phase accessors instead!
  uvm_phase starting_phase;
  // Value set via set_starting_phase
  uvm_phase m_set_starting_phase;
  // Ensures we only warn once per sequence
  bit m_warn_deprecated_set;
`endif 
  ...
  
  function void set_starting_phase(uvm_phase phase);
`ifdef UVM_DEPRECATED_STARTING_PHASE
     begin
        if (starting_phase != m_set_starting_phase) begin
           if (!m_warn_deprecated_set) begin
              `uvm_warning("UVM_DEPRECATED", 
                           {"The deprecated 'starting_phase' variable has been set to '",
                            starting_phase.get_full_name(),
                            "' manually.  See documentation for uvm_sequence_base::set_starting_phase."})
              m_warn_deprecated_set = 1;
           end

           starting_phase = phase;
           m_set_starting_phase = phase;
        end
     end
`endif
           
     m_starting_phase_dap.set(phase);
  endfunction : set_starting_phase
  ...
endclass

  这个函数定义在uvm_sequence_base类中,被UVM_DEPRECATED_STARTING_PHASE宏包裹,UVM目前不推荐这主要目的就是把参数传入的phase句柄赋值给同样定义在该类中的uvm_phase类型的starting_phase,这个starting_phase句柄典型用法就是我们可以在一个sequence的body()中利用它来raise/drop objection。例如:

task my_seq::body();
  if (starting_phase != null) 
    starting_phase.raise_objection(this);
  ...
  if (starting_phase != null) 
    starting_phase.drop_objection(this);
endtask

  由此可见,在sequence中使用starting_phase的前提是该sequence已经被设置为某sequencer的default_sequence,或者starting_phase已经被我们手动显式赋值。

4.3. sequence library

  sequence library是直接继承自uvm_sequence的类型:

//uvm_sequence_library.svh
class uvm_sequence_library #(type REQ=uvm_sequence_item,RSP=REQ) extends uvm_sequence #(REQ,RSP);
  ...
endclass

4.3.1. `uvm_sequence_library_utils

  在创建一个新的sequence library类时,我们要使用宏`uvm_sequence_library_utils()对其进行注册:

//uvm_sequence_defines.svh
`define uvm_sequence_library_utils(TYPE) \
\
   static protected uvm_object_wrapper m_typewide_sequences[$]; \
   \
   function void init_sequence_library(); \
     foreach (TYPE::m_typewide_sequences[i]) \
       sequences.push_back(TYPE::m_typewide_sequences[i]); \
   endfunction \
   \
   static function void add_typewide_sequence(uvm_object_wrapper seq_type); \
     if (m_static_check(seq_type)) \
       TYPE::m_typewide_sequences.push_back(seq_type); \
   endfunction \
   \
   static function void add_typewide_sequences(uvm_object_wrapper seq_types[$]); \
     foreach (seq_types[i]) \
       TYPE::add_typewide_sequence(seq_types[i]); \
   endfunction \
   \
   static function bit m_add_typewide_sequence(uvm_object_wrapper seq_type); \
     TYPE::add_typewide_sequence(seq_type); \
     return 1; \
   endfunction

  这个宏首先声明了一个静态的uvm_object_wrapper类型队列m_typewide_sequences[$],其次声明了一个函数init_sequence_library(),这个函数会遍历m_typewide_sequences[$]把其中的元素放入队列sequences[$],这个uvm_object_wrapper类型队列里面储存了所有已被加入该sequence library的sequence的实例。

//uvm_sequence_library.svh
class uvm_sequence_library #(type REQ=uvm_sequence_item,RSP=REQ) extends uvm_sequence #(REQ,RSP);
  ...
  protected uvm_object_wrapper sequences[$];
  ...
endclass

  接下来定义了静态函数add_typewide_sequence(),若m_static_check()的返回值为1则将参数传入的该sequence实例句柄存入m_typewide_sequences[$]。因为参数都是uvm_object_wrapper类型的,所以我们在手动调用该函数添加sequence时需要调用sequence.get_type()或者sequence::type_id::get()拿到该sequence注册在factory中的唯一实例句柄作为参数,这个跟我们在之前分析`uvm_do系列宏的时候使用`uvm_creat_on创建sequence/sequence_item的用法类似,不再赘述。来看m_static_check():

//uvm_sequence_library.svh
function bit uvm_sequence_library::m_check(uvm_object_wrapper seq_type, this_type lib);
  uvm_object obj;
  uvm_sequence_base seq;
  uvm_root top;
  uvm_coreservice_t cs;   
  string name;
  string typ;
  obj = seq_type.create_object();
  name = (lib == null) ? type_name : lib.get_full_name();
  typ = (lib == null) ? type_name : lib.get_type_name();
  cs = uvm_coreservice_t::get();   
  top = cs.get_root();

  if (!$cast(seq, obj)) begin
    `uvm_error_context("SEQLIB/BAD_SEQ_TYPE",
        {"Object '",obj.get_type_name(),
        "' is not a sequence. Cannot add to sequence library '",name,
        "'"},top)
     return 0;
  end
  return 1;
endfunction

function bit uvm_sequence_library::m_static_check(uvm_object_wrapper seq_type);
  if (!m_check(seq_type,null))
    return 0;
  foreach (m_typewide_sequences[i])
    if (m_typewide_sequences[i] == seq_type)
      return 0;
  return 1;
endfunction

  函数m_static_check()会先调用m_check()来检查传入参数是否为一个uvm_sequence_base类型(即检查其是否为一个sequence),若不是则返回0,此时函数m_static_check()直接返回。否则遍历队列m_typewide_sequences[$]检查该sequence是否已经被添加,若是则返回0,否则函数add_typewide_sequence()将其存入m_typewide_sequences[$]。
  此外`uvm_sequence_library_utils宏还分别定义了两个静态函数add_typewide_sequences()和m_add_typewide_sequence(),二者都是通过调用add_typewide_sequence()来向队列m_typewide_sequences[$]添加sequence,所不同的是这两个函数前者参数是一个队列,可以批量添加sequence,而后者会有一个返回值1。
  除了手动调用函数添加sequence,我们还可以在sequence中使用宏`uvm_add_to_seq_lib来实现:

//uvm_sequence_defines.svh
`define uvm_add_to_seq_lib(TYPE,LIBTYPE) \
   static bit add_``TYPE``_to_seq_lib_``LIBTYPE =\
      LIBTYPE::m_add_typewide_sequence(TYPE::get_type());

  这个宏很简单,就是调用sequence library的静态函数m_add_typewide_sequence()并使用一个新的静态变量来接收其返回值,这个bit类型变量是根据宏传入的名字来声明,如uvm_add_to_seq_lib(my_seq, my_seq_lib),则此变量名为add_my_seq_to_seq_lib_my_seq_lib。

4.3.2. uvm_sequence_library_cfg

  UVM sequence library 提供了一个uvm_sequence_lib_mode枚举类型的变量selection_mode来决定它选择执行sequence的算法,以及int类型变量min_random_count和max_random_count决定其每次执行的sequence/sequence_item的数量,在这两个变量之间随机出的数字会作为sequence/sequence_item执行数量:

//uvm_object_globals.svh
typedef enum
{
  UVM_SEQ_LIB_RAND,
  UVM_SEQ_LIB_RANDC,
  UVM_SEQ_LIB_ITEM,
  UVM_SEQ_LIB_USER
} uvm_sequence_lib_mode;

//uvm_sequence_library.svh
class uvm_sequence_library #(type REQ=uvm_sequence_item,RSP=REQ) extends uvm_sequence #(REQ,RSP);
  ...
  uvm_sequence_lib_mode selection_mode;
  int unsigned min_random_count=10;
  int unsigned max_random_count=10;
  ...
endclass

  其中UVM_SEQ_LIB_RAND是随机选择sequence来执行,UVM_SEQ_LIB_RANDC是把相应数量的sequence随机排序执行一次,在所有sequence都被执行完毕之前不会有sequence被执行第二次,UVM_SEQ_LIB_ITEM是发送sequence library自己定义的sequence_item,而不是执行sequence,UVM_SEQ_LIB_USER是用户自定义sequence选择算法。
  我们可以通过对某个sequence library实例中的这些变量直接赋值,也可以通过uvm_config_db对某sequence library类型进行设置从而影响其所有实例,但是程序中多次使用uvm_config_db代码冗长重复又容易出错,因而UVM提供了uvm_sequence_library_cfg类:

class uvm_sequence_library_cfg extends uvm_object;
  `uvm_object_utils(uvm_sequence_library_cfg)
  uvm_sequence_lib_mode selection_mode;
  int unsigned min_random_count;
  int unsigned max_random_count;
  function new(string name="",
               uvm_sequence_lib_mode mode=UVM_SEQ_LIB_RAND,
               int unsigned min=1,
               int unsigned max=10);
    super.new(name);
    selection_mode = mode;
    min_random_count = min;
    max_random_count = max;
  endfunction
endclass

  uvm_sequence_library_cfg类中包含了这三个变量,可以在实例化时通过new()来一次性配置,然后仅需要一次uvm_config_db的调用,例如:

function void my_test::build_phase(uvm_phase phase);
  uvm_sequence__library_cfg cfg;
  super.build_phase(phase);

  cfg = new("", UVM_SEQ_LIB_RANDC, 10, 20);
  uvm_config_db #(uvm_object_wrapper)::set(this, "env.agt.sqr.main_phase", "default_sequence", my_seq_lib::type_id::get());

  uvm_config_db #(uvm_sequence_library_cfg)::set(this, "env.agt.sqr.main_phase", "default_sequence.config", cfg);

endfunction

4.3.3. body()

  sequence library本质上还是一个sequence,那么关键还要看其body() task:

//uvm_sequence_library.svh
task uvm_sequence_library::body();

  uvm_object_wrapper wrap;
  uvm_phase starting_phase = get_starting_phase();
   
  if (m_sequencer == null) begin
    `uvm_fatal("SEQLIB/VIRT_SEQ", {"Sequence library 'm_sequencer' handle is null; ",
      " no current support for running as a virtual sequence."})
     return;
  end

  if (sequences.size() == 0) begin
    `uvm_error("SEQLIB/NOSEQS", "Sequence library does not contain any sequences. Did you forget to call init_sequence_library() in the constructor?")
    return;
  end

  if (do_not_randomize)
    m_get_config();

  m_safe_raise_starting_phase({"starting sequence library ",get_full_name()," (", get_type_name(),")"});

  `uvm_info("SEQLIB/START",
     $sformatf("Starting sequence library %s in %s phase: %0d iterations in mode %s",
      get_type_name(),
      (starting_phase != null ? starting_phase.get_name() : "unknown"),
      sequence_count, selection_mode.name()),UVM_LOW)

   `uvm_info("SEQLIB/SPRINT",{"\n",sprint(uvm_default_table_printer)},UVM_FULL)

    case (selection_mode)

      UVM_SEQ_LIB_RAND: begin
        valid_rand_selection.constraint_mode(1);
        valid_sequence_count.constraint_mode(0);
        for (int i=1; i<=sequence_count; i++) begin
          if (!randomize(select_rand)) begin
            `uvm_error("SEQLIB/RAND_FAIL", "Random sequence selection failed")
            break;
          end
          else begin
            wrap = sequences[select_rand];
          end
          execute(wrap);
        end
        valid_rand_selection.constraint_mode(0);
        valid_sequence_count.constraint_mode(1);
      end

      UVM_SEQ_LIB_RANDC: begin
        uvm_object_wrapper q[$];
        valid_randc_selection.constraint_mode(1);
        valid_sequence_count.constraint_mode(0);
        for (int i=1; i<=sequence_count; i++) begin
          if (!randomize(select_randc)) begin
            `uvm_error("SEQLIB/RANDC_FAIL", "Random sequence selection failed")
            break;
          end
          else begin
            wrap = sequences[select_randc];
          end
          q.push_back(wrap);
        end
        valid_randc_selection.constraint_mode(0);
        valid_sequence_count.constraint_mode(1);
        foreach(q[i])
          execute(q[i]);
        valid_randc_selection.constraint_mode(0);
        valid_sequence_count.constraint_mode(1);
      end

      UVM_SEQ_LIB_ITEM: begin
        for (int i=1; i<=sequence_count; i++) begin
          wrap = REQ::get_type();
          execute(wrap);
        end
      end

      UVM_SEQ_LIB_USER: begin
        for (int i=1; i<=sequence_count; i++) begin
          int user_selection;
          user_selection = select_sequence(sequences.size()-1);
          if (user_selection >= sequences.size()) begin
            `uvm_error("SEQLIB/USER_FAIL", "User sequence selection out of range")
            wrap = REQ::get_type();
          end
          else begin
            wrap = sequences[user_selection];
          end
          execute(wrap);
        end
      end

      default: begin
        `uvm_fatal("SEQLIB/RAND_MODE", 
           $sformatf("Unknown random sequence selection mode: %0d",selection_mode))
      end
     endcase

  `uvm_info("SEQLIB/END",{"Ending sequence library in phase ",
            (starting_phase != null ? starting_phase.get_name() : "unknown")},UVM_LOW)
 
  `uvm_info("SEQLIB/DSTRB",$sformatf("%p",seqs_distrib),UVM_HIGH)

  m_safe_drop_starting_phase({"starting sequence library ",get_full_name()," (", get_type_name(),")"});

endtask

  首先通过get_starting_phase()拿到starting_phase句柄,若m_sequencer为null,或者队列sequences[$]为空(说明尚未向其中加入过任何sequence)则直接报错返回。若变量do_not_randomize为1,则调用m_get_config(),bit型变量do_not_randomize定义在uvm_sequence_base类中,若被置为1,则sequence在调用`uvm_do系列宏和`uvm_rand_send系列宏以及作为default_sequence启动时不做随机化操作。

//uvm_sequence_base.svh
class uvm_sequence_base extends uvm_sequence_item;
  ...
  bit do_not_randomize;
  ...
endclass

  若该变量为默认值0,则执行过程中需要做随机化操作,UVM在sequence library类中重载了SV随机化callback pre_randomize():

//uvm_sequence_library.svh
function void uvm_sequence_library::pre_randomize();
  m_get_config();
endfunction

  在sequence library随机化之前也会调用m_get_config()函数,这样无论做不做随机化操作,在sequence library被执行之前m_get_config()都会被调用一次 :

//uvm_sequence_library.svh
function void uvm_sequence_library::m_get_config();

  uvm_sequence_library_cfg cfg;
  string phase_name;
  uvm_phase starting_phase = get_starting_phase();
   
  if (starting_phase != null) begin
    phase_name = {starting_phase.get_name(),"_phase"};
  end
  if (uvm_config_db #(uvm_sequence_library_cfg)::get(m_sequencer, 
                                        phase_name,
                                        "default_sequence.config",
                                        cfg) ) begin
    selection_mode = cfg.selection_mode; 
    min_random_count = cfg.min_random_count; 
    max_random_count = cfg.max_random_count; 
  end
  else begin
    void'(uvm_config_db #(int unsigned)::get(m_sequencer, 
                                        phase_name,
                                        "default_sequence.min_random_count",
                                        min_random_count) );

    void'(uvm_config_db #(int unsigned)::get(m_sequencer, 
                                        phase_name,
                                        "default_sequence.max_random_count",
                                        max_random_count) );

    void'(uvm_config_db #(uvm_sequence_lib_mode)::get(m_sequencer, 
                                        phase_name,
                                        "default_sequence.selection_mode",
                                        selection_mode) );
  end

  if (max_random_count == 0) begin
    `uvm_warning("SEQLIB/MAX_ZERO",
       $sformatf("max_random_count (%0d) zero. Nothing will be done.",
       max_random_count))
    if (min_random_count > max_random_count)
      min_random_count = max_random_count;
  end
  else if (min_random_count > max_random_count) begin
    `uvm_error("SEQLIB/MIN_GT_MAX",
       $sformatf("min_random_count (%0d) greater than max_random_count (%0d). Setting min to max.",
       min_random_count,max_random_count))
    min_random_count = max_random_count;
  end
  else begin
    if (selection_mode == UVM_SEQ_LIB_ITEM) begin
      uvm_sequencer #(REQ,RSP) seqr;
      uvm_object_wrapper lhs = REQ::get_type();
      uvm_object_wrapper rhs = uvm_sequence_item::get_type();
      if (lhs == rhs) begin
        `uvm_error("SEQLIB/BASE_ITEM", {"selection_mode cannot be UVM_SEQ_LIB_ITEM when ",
          "the REQ type is the base uvm_sequence_item. Using UVM_SEQ_LIB_RAND mode"})
        selection_mode = UVM_SEQ_LIB_RAND;
      end
      if (m_sequencer == null || !$cast(seqr,m_sequencer)) begin
        `uvm_error("SEQLIB/VIRT_SEQ", {"selection_mode cannot be UVM_SEQ_LIB_ITEM when ",
          "running as a virtual sequence. Using UVM_SEQ_LIB_RAND mode"})
        selection_mode = UVM_SEQ_LIB_RAND;
      end
    end
  end

endfunction

  m_get_config()函数会首先拿先调用get_starting_phase()拿到starting_phase句柄,然后通过uvm_config_db的get()试图获取uvm_sequence_library_cfg句柄,若之前已经通过uvm_config_db的set()设置过,则拿到配置类句柄后将其分别赋值给三个变量。若之前没有通过uvm_config_db的set()设置过uvm_sequence_library_cfg,则检查其是否通过uvm_config_db分别配置并拿到相应的值。若max_random_count值被设置为0且min_random_count大于max_random_count,则把二者都置为0。若max_random_count值不为0且min_random_count大于max_random_count,则报错并把二者都设置为smax_random_count的值。接下来检测若selection_mode被设置为UVM_SEQ_LIB_ITEM且sequence library的transaction类型为uvm_sequence_item时,则报错并把selection_mode强制设置为UVM_SEQ_LIB_RAND。若selection_mode被设置为UVM_SEQ_LIB_ITEM且m_sequencer指向的是virtual sequencer,则同样报错并把selection_mode强制设置为UVM_SEQ_LIB_RAND。
  回到body()中,调用m_safe_raise_starting_phase()来raise objection,接下来根据随机算法来选择sequence执行,最后调用m_safe_drop_starting_phase()来drop objection。关于sequence选择算法这里定义了几个变量和约束条件:

class uvm_sequence_library #(type REQ=uvm_sequence_item,RSP=REQ) extends uvm_sequence #(REQ,RSP);
   ...
   rand  int unsigned sequence_count = 10;
   rand  int unsigned select_rand;
   randc bit [15:0] select_randc;
   ...
   constraint valid_rand_selection {
         select_rand inside {[0:sequences.size()-1]};
   }
      constraint valid_randc_selection {
         select_randc inside {[0:sequences.size()-1]};
   }
      constraint valid_sequence_count {
      sequence_count inside {[min_random_count:max_random_count]};
   }
   ...
 endclass

  当sequence library在执行之前进行随机化时,这些约束条件是默认有效的,因而会首先随机化出一个介于min_random_count和max_random_count之间的sequence_count。回到body()中,当selection_mode为:

  1. UVM_SEQ_LIB_RAND,把valid_sequence_count约束关闭而valid_rand_selection打开,随机出sequence_count个(已经随机出数值)select_rand的值,这些select_rand就是相应sequence在队列sequences[$]中的索引,随后调用execute()执行这些sequence。
  2. UVM_SEQ_LIB_RANDC,把valid_sequence_count约束关闭而valid_randc_selection打开,随机出sequence_count个(已经随机出数值)randc类型的select_randc的值,然后将它们存入uvm_object_wrapper类型的一个临时队列q[$],从队列中取出这些sequence句柄并调用execute()执行。
  3. UVM_SEQ_LIB_ITEM,直接调用execute()执行squence_count次当前sequence library的sequence_item。
  4. UVM_SEQ_LIB_USER,每次调用select_sequence()选出一个队列sequences[$]索引,一共选出sequence_count个索引并调用execute()执行其对应的sequence,若索引超出sequences[$]大小,则调用execute()执行当前sequence library的sequence_item。select_sequence()函数默认定义如下,其中定义了一个静态变量counter,每次在前一次的基础上加1并返回,若其值达到参数传入的最大值max,则从0重新开始。用户可以重载这个函数来实现自己的算法。
//uvm_sequence_library.svh
function int unsigned uvm_sequence_library::select_sequence(int unsigned max);
  static int unsigned counter;
  select_sequence = counter;
  counter++;
  if (counter >= max)
    counter = 0;
endfunction

  最后来看下task execute():

//uvm_sequence_library.svh
task uvm_sequence_library::execute(uvm_object_wrapper wrap);

  uvm_object obj;
  uvm_sequence_item seq_or_item;
  uvm_sequence_base seq_base;
  REQ req_item;
  
  uvm_coreservice_t cs = uvm_coreservice_t::get();                                                     
  uvm_factory factory=cs.get_factory();

  obj = factory.create_object_by_type(wrap,get_full_name(),
           $sformatf("%s:%0d",wrap.get_type_name(),sequences_executed+1));

  if (!$cast(seq_base, obj)) begin
     // If we're executing an item (not a sequence)
     if (!$cast(req_item, obj)) begin
        // But it's not our item type (This can happen if we were parameterized with
        // a pure virtual type, because we're getting get_type() from the base class)
        `uvm_error("SEQLIB/WRONG_ITEM_TYPE", {"The item created by '", get_full_name(), "' when in 'UVM_SEQ_LIB_ITEM' mode doesn't match the REQ type which  was passed in to the uvm_sequence_library#(REQ[,RSP]), this can happen if the REQ type which was passed in was a pure-virtual type.  Either configure the factory overrides to properly generate items for this sequence library, or do not execute this sequence library in UVM_SEQ_LIB_ITEM mode."})
         return;
     end
  end
   
  void'($cast(seq_or_item,obj)); // already qualified, 
   
  `uvm_info("SEQLIB/EXEC",{"Executing ",(seq_or_item.is_item() ? "item " : "sequence "),seq_or_item.get_name(),
                           " (",seq_or_item.get_type_name(),")"},UVM_FULL)
  seq_or_item.print_sequence_info = 1;
  `uvm_rand_send(seq_or_item)
  seqs_distrib[seq_or_item.get_type_name()] = seqs_distrib[seq_or_item.get_type_name()]+1;

  sequences_executed++;

endtask

  这个task首先调用factory的create_object_by_type()拿到sequence/sequence_item实例句柄,若其既不是sequence,也不是sequence library指定的sequence_item类型,则直接报错返回。接下来使用$cast将该句柄赋值给其父类uvm_sequence_item类型句柄seq_or_item并调用宏`uvm_rand_send()执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值