UVM config_db机制源码探微

1. uvm_config_db 基础

  UVM config机制是其提供的一种在验证平台中进行资源配置和获取的方法,在复杂的平台中,不同testcase对各种参数可能会有不同的配置要求。UVM提供了一个全局的资源池来存储这些配置资源,我们可以通过config机制将各种类型的资源存入其中,如int型,string型,uvm_object类型等等,还可以设置这些资源的访问范围scope,即验证平台中的哪些component节点可以访问,而另外的节点则无权访问。

1.1. 简单set

  来看一个简单的例子,我们在testcase里使用uvm_config_db的set()将pkt_num配置给driver:

function void my_test::build_phase(uvm_phase phase);
	super.build_phase(phase);
	uvm_config_db #(int)::set(this, "env.agt.drv", num, 10);
	...
endfunction

  静态函数set()在被调用时需要带上范围解析符::,它有四个参数,第一个参数是uvm_component类型,一般填入this,若这个参数为空,则会自动设置为uvm_root类型的uvm_top。第二个参数为要配置资源所在的实例路径,和第一个参数配合定位到相关component节点,上面的代码还可以改成:
  uvm_config_db #(int)::set(“uvm_test_top.env”, “agt.drv”, num, 10);
第三个参数是一个string标志符,第四个参数是要配置的值。
  在driver中使用uvm_config_db的get()拿到这个配置:

class my_driver extends uvm_driver;
	int pkt_num;
	function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		uvm_config_db #(int)::get(this, "", num, pkt_num);
		...
	endfunction
	
	task run();
		for (int i=0; i<pkt_num; ++i) begin
			...
		end
	endtask
	...
endclass

  静态函数get()和set()类似,也有四个参数,第一个参数一般填入this,第二个参数为空,第三个参数是string类型标志符,必须与set()的第三个参数一致,第四个参数则是要设置的变量,set()第四个参数的值会赋值给这个变量。

1.2. 多重set

  在上面的例子中我们在testcase中设置driver的pkt_num的值,那如果从env中也对driver的pkt_num设置了另一个值呢?

function void my_env::build_phase(uvm_phase phase);
	super.build_phse(phase);
	uvm_config_db #(int)::set(this, "agt.drv", num, 20);
	...
endfunction

  答案是driver中pkt_num拿到的值依然是在testcase中设置的10,离uvm_root越近的节点设置的同一资源具有较高的优先级。
  若在testcase中对driver的pkt_num设置了两次(实践中基本不会这么用,这里只是举例说明问题)如下:

function void my_test::build_phase(uvm_phase phase);
	super.build_phase(phase);
	uvm_config_db #(int)::set(this, "env.agt.drv", num, 10);
	uvm_config_db #(int)::set(this, "env.agt.drv", num, 20);
	...
endfunction

  则driver中pkt_num会拿到的值为20,即在同一节点以先后顺序设置的资源,后设置的具有较高优先级。

2. uvm_resource

2.1. uvm_resource_base

  UVM提供了一个参数类uvm_resource #(type T=int)来储存不同类型的资源,这个类继承自uvm_resource_base类:

//uvm_resource.svh
virtual class uvm_resource_base extends uvm_object;
  protected string scope;
  protected bit modified;
  protected bit read_only;
  
  uvm_resource_types::access_t access[string];
  int unsigned precedence;

  static int unsigned default_precedence = 1000;
  ...
  function new(string name = "", string s = "*");
    super.new(name);
    set_scope(s);
    modified = 0;
    read_only = 0;
    precedence = default_precedence;
  endfunction

  pure virtual function uvm_resource_base get_type_handle();
  ...
  function void set_scope(string s);
    scope = uvm_glob_to_re(s);
  endfunction
  ...
endclass

  uvm_resource_base类中定义了字符串变量scope,int型变量precedence,bit类型的modified和read_only,此外还有以字符串为索引,储存内容为uvm_resource_types::access_t类型的联合数组access[](用来记录该笔资源被不同accessor访问的记录,请参考下文关于debug的部分),以及默认值为1000的静态变量default_precedence,其new()函数会将第二个字符串参数s作为参数调用set_scope(),然后将modified和read_only清零并把precedence设置为default_precedence的值。set_scope()会把字符串s经过uvm_glob_to_re()函数处理后返回的字符串设置给scope,uvm_glob_to_re()是UVM通过DPI接口调用的C函数,主要功能就是把字符串处理成正则形式,可以用正则表达式来匹配。纯虚函数get_type_handle()会在uvm_resource类中被重载。

2.2. uvm_resource_types

  此外,uvm_resource_base类中还定义了一个uvm_resource_types类中的变量access_t:

//uvm_resource.svh
class uvm_resource_types;

  // types uses for setting overrides
  typedef bit[1:0] override_t;
  typedef enum override_t { TYPE_OVERRIDE = 2'b01,
                            NAME_OVERRIDE = 2'b10 } override_e;

   // general purpose queue of resourcex
  typedef uvm_queue#(uvm_resource_base) rsrc_q_t;

  // enum for setting resource search priority
  typedef enum { PRI_HIGH, PRI_LOW } priority_e;

  // access record for resources.  A set of these is stored for each
  // resource by accessing object.  It's updated for each read/write.
  typedef struct
  {
    time read_time;
    time write_time;
    int unsigned read_count;
    int unsigned write_count;
  } access_t;

endclass

  这个结构体access_t中定义了资源被读写的时间和访问次数,用来记录每个资源被访问的信息。uvm_resource_types类中还定义了枚举类型override_t,用来表示资源被override的形式,枚举类型priority_e表示存储的资源优先级高低,存储类型为uvm_resource_base的uvm_queue的句柄rsrc_q_t。

2.3. uvm_resource #(T)

  来看uvm_resource类:

//uvm_resource.svh
class uvm_resource #(type T=int) extends uvm_resource_base;
  typedef uvm_resource#(T) this_type;

  // singleton handle that represents the type of this resource
  static this_type my_type = get_type();

  // Can't be rand since things like rand strings are not legal.
  protected T val;
  ...
  function new(string name="", scope="");
    super.new(name, scope);
    
`ifndef UVM_NO_DEPRECATED
begin
	for(int i=0;i<name.len();i++) begin
		if(name.getc(i) inside {".","/","[","*","{"}) begin
			`uvm_warning("UVM/RSRC/NOREGEX", $sformatf("a resource with meta characters in the field name has been created \"%s\"",name))
			break;
		end	
	end	
end

`endif    
  endfunction

  static function this_type get_type();
    if(my_type == null)
      my_type = new();
    return my_type;
  endfunction

  function uvm_resource_base get_type_handle();
    return get_type();
  endfunction
  ...
endclass

  uvm_resource类中定义了T类型的变量val,用来存储类型T的资源的值。重载其父类的get_type_handle()函数直接返回静态函数get_type()的返回值,这里是一个singleton模式,get_type()会返回该类型在全局中的唯一实例句柄。new()函数直接调用父类uvm_resource_base的new()来设置name和scope。

2.3.1. set系列函数

  uvm_resource类提供了三个函数set(),set_override()和set_priority(),这三个函数都是首先调用uvm_resource_pool中的静态函数get()拿到全局资源池uvm_resource_pool的唯一句柄然后分别调用其相应函数,对于uvm_resource_pool类中的这些函数分析请参考后文。

//uvm_resource.svh
class uvm_resource #(type T=int) extends uvm_resource_base;
  ...
  function void set();
    uvm_resource_pool rp = uvm_resource_pool::get();
    rp.set(this);
  endfunction

  function void set_override(uvm_resource_types::override_t override = 2'b11);
    uvm_resource_pool rp = uvm_resource_pool::get();
    rp.set(this, override);
  endfunction

  function void set_priority (uvm_resource_types::priority_e pri);
    uvm_resource_pool rp = uvm_resource_pool::get();
    rp.set_priority(this, pri);
  endfunction
  ...
endfunction

2.3.2. get系列函数

  静态函数get_by_name()调用的uvm_resource_pool类中的get_by_name()函数,所不同的是后者多出的第三个参数my_type正是该uvm_resource #(T)类的全局唯一静态实例句柄。拿到uvm_resource_base类型句柄后调用$cast将其向下赋值给uvm_resource #(T)类型句柄并返回。

//uvm_resource.svh
class uvm_resource #(type T=int) extends uvm_resource_base;
  ...
  static function this_type get_by_name(string scope,
                                        string name,
                                        bit rpterr = 1);

    uvm_resource_pool rp = uvm_resource_pool::get();
    uvm_resource_base rsrc_base;
    this_type rsrc;
    string msg;

    rsrc_base = rp.get_by_name(scope, name, my_type, rpterr);
    if(rsrc_base == null)
      return null;

    if(!$cast(rsrc, rsrc_base)) begin
      if(rpterr) begin
        $sformat(msg, "Resource with name %s in scope %s has incorrect type", name, scope);
        `uvm_warning("RSRCTYPE", msg);
      end
      return null;
    end

    return rsrc;
    
  endfunction

  static function this_type get_by_type(string scope = "",
                                        uvm_resource_base type_handle);

    uvm_resource_pool rp = uvm_resource_pool::get();
    uvm_resource_base rsrc_base;
    this_type rsrc;
    string msg;

    if(type_handle == null)
      return null;

    rsrc_base = rp.get_by_type(scope, type_handle);
    if(rsrc_base == null)
      return null;

    if(!$cast(rsrc, rsrc_base)) begin
      $sformat(msg, "Resource with specified type handle in scope %s was not located", scope);
      `uvm_warning("RSRCNF", msg);
      return null;
    end

    return rsrc;

  endfunction
  ...
endclass

  静态函数get_by_type()同样是调用的uvm_resource_pool类中的get_by_type()函数,拿到uvm_resource_base类型句柄后调用$cast将其向下赋值给uvm_resource #(T)类型句柄并返回。
  此外,uvm_resource类还提供了一个静态函数get_highest_precedence():

//uvm_resource.svh
class uvm_resource #(type T=int) extends uvm_resource_base;
  ...
  static function this_type get_highest_precedence(ref uvm_resource_types::rsrc_q_t q);

    this_type rsrc;
    this_type r;
    int unsigned i;
    int unsigned prec;
    int unsigned first;

    if(q.size() == 0)
      return null;

    first = 0;
    rsrc = null;
    prec = 0;

    // Locate first resources in the queue whose type is T
    for(first = 0; first < q.size() && !$cast(rsrc, q.get(first)); first++);

    // no resource in the queue whose type is T
    if(rsrc == null)
      return null;

    prec = rsrc.precedence;

    // start searching from the next resource after the first resource
    // whose type is T
    for(int i = first+1; i < q.size(); ++i) begin
      if($cast(r, q.get(i))) begin
        if(r.precedence > prec) begin
          rsrc = r;
          prec = r.precedence;
        end
      end
    end

    return rsrc;

  endfunction
  ...
endclass

  这个函数是从参数传入的uvm_queue类型句柄的队列中首先找到第一个类型为uvm_resource #(T)的元素,然后记录下其precedence,然后遍历队列其余的元素找出相同类型的precedence最高的元素并返回。

2.3.3. write()/read()

  uvm_resource类还提供了两个函数read()和write()来对其中存储的val进行读写操作:

//uvm_resource.svh
class uvm_resource #(type T=int) extends uvm_resource_base;
  ...
  function T read(uvm_object accessor = null);
    record_read_access(accessor);
    return val;
  endfunction
  
  function void write(T t, uvm_object accessor = null);

    if(is_read_only()) begin
      uvm_report_error("resource", $sformatf("resource %s is read only -- cannot modify", get_name()));
      return;
    end

    // Set the modified bit and record the transaction only if the value
    // has actually changed.
    if(val == t)
      return;

    record_write_access(accessor);

    // set the value and set the dirty bit
    val = t;
    modified = 1;
  endfunction
  ...
endclass

  read()函数首先调用record_read_access()记录下这次读操作的信息并返回返回val的值。write()函数会首先检查变量read_only是否为1,若是,则报错直接返回。若写入的值与当前val一致,则直接返回,否则调用record_write_access()记录下这次写操作并将val更新为新写入的值,将modified置为1。函数record_write_access()留待下文关于debug的部分分析。

3. uvm_resource_pool

3.1. 数据存储结构

  UVM提供了一个全局共享资源池uvm_resource_pool:

uvm_resource.svh
class uvm_resource_pool;

  static local uvm_resource_pool rp = get();

  uvm_resource_types::rsrc_q_t rtab [string];
  uvm_resource_types::rsrc_q_t ttab [uvm_resource_base];
  ...
  local function new();
  endfunction

  static function uvm_resource_pool get();
    if(rp == null)
      rp = new();
    return rp;
  endfunction
  ...
endclass

  uvm_resource_pool类自成一体,没有继承自任何类型。其静态函数get()返回该类型在全局的唯一实例。主要储存资源的数据类型是两个联合数组rtab[]和ttab[],二者分别以字符串类型的name和uvm_resource_base类型的type为索引,储存内容为uvm_resource_types类中的静态uvm_queue类型的rsrc_q_t(我们暂且将rsrc_q_t理解为SV中的队列,下文中提到这个类型均以队列代称),也就是说我们可以通过name或者type为索引将一笔uvm_resource #(T)类型的资源分别存入以name/type为索引的队列rsrc_q_t中,很明显,以同一name为索引的这个队列也可以存入不同type的其它资源(他们的name是相同的),同理以同一type为索引的队列中也可以存入不同name的其它资源(他们的type是相同的)。UVM源码中给出了非常形象的数据结构示意图:
在这里插入图片描述

3.2. set系列函数

  前面提到uvm_resource类的set系列函数以及uvm_resource_pool类中定义的如set_override(),set_name_override()以及set_type_override()都是调用的uvm_resource_pool类的set()函数:

//uvm_resource.svh
class uvm_resource_pool;
  ...
  function void set (uvm_resource_base rsrc, 
                     uvm_resource_types::override_t override = 0);

    uvm_resource_types::rsrc_q_t rq;
    string name;
    uvm_resource_base type_handle;

`ifdef UVM_LOOKUP_SCOPE_FOR_FIELDS
//`ifndef UVM_ORIGINAL_LOOKUP_SCOPE
    int   i;
    string field_name;
`endif

    // If resource handle is ~null~ then there is nothing to do.
    if(rsrc == null)
      return;

    // insert into the name map.  Resources with empty names are
    // anonymous resources and are not entered into the name map
    name = rsrc.get_name();
    if(name != "") begin
      if(rtab.exists(name))
        rq = rtab[name];
      else
        rq = new();

      // Insert the resource into the queue associated with its name.
      // If we are doing a name override then insert it in the front of
      // the queue, otherwise insert it in the back.
      if(override & uvm_resource_types::NAME_OVERRIDE)
        rq.push_front(rsrc);
      else
        rq.push_back(rsrc);

      rtab[name] = rq;

`ifdef UVM_LOOKUP_SCOPE_FOR_FIELDS
//`ifndef UVM_ORIGINAL_LOOKUP_SCOPE
      // Following will add the overhead whenever the resource is added but the overhead will well justifiable 
      // while doing apply_config_settings (mainly for bigger testbenches). 
      // The idea is to avoid looking for the scope for the resources which are not in the component and 
      // hence reduce calls to match_scope functions.

      // Just look for "[" or "." and save that in rtab_arr_obj table. This is the resource table that has resource name 
      // which has "[" or "." in it (that is array type or object type).
       
      // does name have brackets [] in it?
      for (i = 0; i < name.len(); i++)
        if (name[i] == "[" || name[i] == ".")
          break;

      if (i < name.len()) begin
        field_name = name.substr(0,i-1); // Extract the field name i.e. name before "[" or "."
	rtab_arr_obj[field_name][name] = 1; // If resource name is same but scope was different, 
	                                    // we don't need to create new entry at second dimension of array.
      end
`endif

    end

    // insert into the type map
    type_handle = rsrc.get_type_handle();
    if(ttab.exists(type_handle))
      rq = ttab[type_handle];
    else
      rq = new();

    // insert the resource into the queue associated with its type.  If
    // we are doing a type override then insert it in the front of the
    // queue, otherwise insert it in the back of the queue.
    if(override & uvm_resource_types::TYPE_OVERRIDE)
      rq.push_front(rsrc);
    else
      rq.push_back(rsrc);
    ttab[type_handle] = rq;

  endfunction
  ...
endclass

  函数set()有两个参数,第一个参数是uvm_resource_base类型句柄rsrc,指向要被存储的资源类uvm_resource #(T),第二个参数是uvm_resource_types类中的静态变量override_t,表示该笔资源在存到相应队列时是否要override之前已存在的资源。首先检查rsrc是否为null,若是则直接返回,否则调用get_name()返回字符串变量m_leaf_name,此时这个m_leaf_name其实就是我们在创建uvm_resource #(T)类实例调用new()时传入的第一个字符串参数。get_name()是uvm_object中定义的函数,返回其中的字符串变量m_leaf_name,在uvm_resource_base的new()函数中会调用uvm_object的new()函数,在其中把传入的字符串变量name赋值给m_leaf_name。若get_name()返回值name不是空字符串,则判断数组rtab[]是否已经有了以这个字符串为索引的内容,若没有则调用new()创建一个uvm_resource_types::rsrc_q_t类型队列,若override_t是NAME_OVERRIDE,则将第一个参数传入的rsrc句柄存入该队列最前端,否则存入该队列最后面,然后将该队列以name为索引存入数组rtab[]。
  接下来调用get_type_handle()拿到该uvm_resource #(T)类唯一对象句柄type_handle,若数组ttab[]中没有以该句柄为索引的内容,则调用new()创建一个uvm_resource_types::rsrc_q_t类型队列,若override_t是TYPE_OVERRIDE,则将第一个参数传入的rsrc句柄存入该队列最前端,否则存入该队列最后面,最后将该队列以type_handle为索引存入数组ttab[]。
  此外,uvm_resource_pool还定义了两个函数set_priority_type()和set_priority_name():

//uvm_resource.svh
class uvm_resource_pool;
  ...
  function void set_priority_type(uvm_resource_base rsrc,
                                  uvm_resource_types::priority_e pri);

    uvm_resource_base type_handle;
    string msg;
    uvm_resource_types::rsrc_q_t q;

    if(rsrc == null) begin
      uvm_report_warning("NULLRASRC", "attempting to change the serach priority of a null resource");
      return;
    end

    type_handle = rsrc.get_type_handle();
    if(!ttab.exists(type_handle)) begin
      $sformat(msg, "Type handle for resrouce named %s not found in type map; cannot change its search priority", rsrc.get_name());
      uvm_report_error("RNFTYPE", msg);
      return;
    end

    q = ttab[type_handle];
    set_priority_queue(rsrc, q, pri);
  endfunction

  function void set_priority_name(uvm_resource_base rsrc,
                                  uvm_resource_types::priority_e pri);

    string name;
    string msg;
    uvm_resource_types::rsrc_q_t q;

    if(rsrc == null) begin
      uvm_report_warning("NULLRASRC", "attempting to change the serach priority of a null resource");
      return;
    end

    name = rsrc.get_name();
    if(!rtab.exists(name)) begin
      $sformat(msg, "Resrouce named %s not found in name map; cannot change its search priority", name);
      uvm_report_error("RNFNAME", msg);
      return;
    end

    q = rtab[name];
    set_priority_queue(rsrc, q, pri);

  endfunction
  ...
endclass

  二者分别通过传入的资源句柄的type或name为索引在数组ttab[]或rta[]中找到相应的储存相关资源的队列,然后将uvm_resource_types::priority_e作为参数调用set_priority_queue():

//uvm_resource.svh
class uvm_resource_pool;
  ...
  local function void set_priority_queue(uvm_resource_base rsrc,
                                         ref uvm_resource_types::rsrc_q_t q,
                                         uvm_resource_types::priority_e pri);

    uvm_resource_base r;
    int unsigned i;

    string msg;
    string name = rsrc.get_name();

    for(i = 0; i < q.size(); i++) begin
      r = q.get(i);
      if(r == rsrc) break;
    end

    if(r != rsrc) begin
      $sformat(msg, "Handle for resource named %s is not in the name name; cannot change its priority", name);
      uvm_report_error("NORSRC", msg);
      return;
    end

    q.delete(i);

    case(pri)
      uvm_resource_types::PRI_HIGH: q.push_front(rsrc);
      uvm_resource_types::PRI_LOW:  q.push_back(rsrc);
    endcase

  endfunction
  ...
endclass

  这个函数会把要设置的资源rsrc在队列中的位置根据priority_e的值进行调整,若priority_e为PRI_HIGH,则放入队列最前端,若priority_e为PRI_LOW,则放入队列最后。

3.3. get_by_name()/get_by_type()

  uvm_resource_pool提供了可以通过字符串scope和name来找到数组中相应资源类的函数get_by_name():

//uvm_resource.svh
class uvm_resource_pool;
  ...
  function uvm_resource_base get_by_name(string scope = "",
                                         string name,
                                         uvm_resource_base type_handle,
                                         bit rpterr = 1);

    uvm_resource_types::rsrc_q_t q;
    uvm_resource_base rsrc;

    q = lookup_name(scope, name, type_handle, rpterr);

    if(q.size() == 0) begin
      push_get_record(name, scope, null);
      return null;
    end

    rsrc = get_highest_precedence(q);
    push_get_record(name, scope, rsrc);
    return rsrc;
    
  endfunction
  ...
endclass

  首先调用lookup_name()来找到符合条件的队列句柄:

//uvm_resource.svh
class uvm_resource_pool;
  ...
  function uvm_resource_types::rsrc_q_t lookup_name(string scope = "",
                                                    string name,
                                                    uvm_resource_base type_handle = null,
                                                    bit rpterr = 1);
    uvm_resource_types::rsrc_q_t rq;
    uvm_resource_types::rsrc_q_t q;
    uvm_resource_base rsrc;
    uvm_resource_base r;

     // ensure rand stability during lookup
     begin
	process p = process::self();
	string s;
	if(p!=null) s=p.get_randstate();
	q=new();
	if(p!=null) p.set_randstate(s);
     end

     
    // resources with empty names are anonymous and do not exist in the name map
    if(name == "")
      return q;

    // Does an entry in the name map exist with the specified name?
    // If not, then we're done
    if(!rtab.exists(name)) begin
	    if(rpterr) void'(spell_check(name));	
		return q;
    end	

    rsrc = null;
    rq = rtab[name];
    for(int i=0; i<rq.size(); ++i) begin 
      r = rq.get(i);
      // does the type and scope match?
      if(((type_handle == null) || (r.get_type_handle() == type_handle)) &&
          r.match_scope(scope))
        q.push_back(r);
    end

    return q;

  endfunction
  ...
endclass

  若传入的字符串name为空,则返回一个空队列。若数组rtab[]中没有相关记录且rpterr为1,则调用spell_check()并返回一个空队列,spell_check()会检查传入的字符串与数组中的索引相比较并给出信息。否则遍历以name为索引的这个队列,若参数传入的type_handle为空或者与队列中某个元素一致,并且match_scope()返回1,则这些元素放入一个uvm_resource_types::rsrc_q_t类型队列q并返回q。函数match_scope()会调用函数uvm_re_match()来检测scope是否可以与参数传入的字符串匹配。
  回到函数get_by_name(),若lookup_name()返回的队列为空,则调用push_get_record()记录下这次访问信息并直接返回。否则调用get_highest_precedence()取得优先级最高的那笔uvm_resource #(T)句柄并记录相关访问信息后返回这句柄。这个get_highest_precedence()函数定义在uvm_resource_pool类中,与之前分析的uvm_resource类中的静态函数get_highest_precedence()并不是同一个但是代码几乎一样,这里不再赘述。
总结:函数get_by_name()通过传入的字符串参数scope和name以及type句柄(若有)在uvm_resource_pool中寻找符合的uvm_resource #(T)资源类型并返回优先级最高的那个。
  uvm_resource_pool还提供了可以通过字符串scope和uvm_resource_base类型句柄type_handle来找到数组中相应资源类的函数get_by_type():

//uvm_resource.svh
class uvm_resource_pool;
  ...
  function uvm_resource_base get_by_type(string scope = "",
                                         uvm_resource_base type_handle);

    uvm_resource_types::rsrc_q_t q;
    uvm_resource_base rsrc;

    q = lookup_type(scope, type_handle);

    if(q.size() == 0) begin
      push_get_record("<type>", scope, null);
      return null;
    end

    rsrc = q.get(0);
    push_get_record("<type>", scope, rsrc);
    return rsrc;
    
  endfunction
  ...
endclass

  首先调用lookup_type()取得符合条件的资源队列句柄q,若q为空队列,则调用push_get_record()记录下这次访问信息并返回。否则返回q中第一个元素并同样记录下这次访问信息。函数lookup_type()定义在

class uvm_resource_pool;
  ...
  function uvm_resource_types::rsrc_q_t lookup_type(string scope = "",
                                                    uvm_resource_base type_handle);

    uvm_resource_types::rsrc_q_t q = new();
    uvm_resource_types::rsrc_q_t rq;
    uvm_resource_base r;
    int unsigned i;

    if(type_handle == null || !ttab.exists(type_handle)) begin
      return q;
    end

    rq = ttab[type_handle];
    for(int i = 0; i < rq.size(); ++i) begin 
      r = rq.get(i);
      if(r.match_scope(scope))
        q.push_back(r);
    end

    return q;

  endfunction

  若传入的参数type_handle为空句柄,或者数组ttab[]中不存在关于type_handle的信息,则直接返回uvm_resource_types::rsrc_q_t类型的空队列q。否则遍历ttab[]数组中以type_handle为索引的队列rq,若元素调用match_scope()返回值为1,则将其放入uvm_resource_types::rsrc_q_t类型的队列q,最后返回q。
  总结:函数get_by_type()通过传入的字符串参数scope和uvm_resource_base类型句柄type在uvm_resource_pool中寻找符合的uvm_resource #(T)资源类型并返回排在最前面的那个。

4. uvm_resource_db

  参数类uvm_resource_db #(T)也是自成一派,没有继承自任何类型。其中的静态函数get_by_type()和get_by_name()直接调用与其具有相同类型参数的uvm_resource #(T)类中的同名静态函数,最终调用uvm_resource_pool类中的同名函数,上文已经提及相关函数,此处不再赘述。

//uvm_resource_db.svh
class uvm_resource_db #(type T=uvm_object);

  typedef uvm_resource #(T) rsrc_t;

  protected function new();
  endfunction

  static function rsrc_t get_by_type(string scope);
    return rsrc_t::get_by_type(scope, rsrc_t::get_type());
  endfunction

  static function rsrc_t get_by_name(string scope,
                                     string name,
                                     bit rpterr=1);

    return rsrc_t::get_by_name(scope, name, rpterr);
  endfunction
  ...
endclass

4.1. set()系列函数

4.1.1. set()

  uvm_resource_db的静态函数set()是其最常用的函数之一:

//uvm_resource_db.svh
class uvm_resource_db #(type T=uvm_object);
  ...
  static function void set(input string scope, input string name,
                           T val, input uvm_object accessor = null);

    rsrc_t rsrc = new(name, scope);
    rsrc.write(val, accessor);
    rsrc.set();

    if(uvm_resource_db_options::is_tracing())
      m_show_msg("RSRCDB/SET", "Resource","set", scope, name, accessor, rsrc);
  endfunction
  ...
endclass

  set()有四个参数,前两个参数分别是string类型的scope和name,在函数内部uvm_resource #(T)根据这两个参数调用new()来创建一个自身实例对象rsrc,第三个参数是T类型的值val,第四个参数是uvm_object类型的accessor,主要是用来记录debug信息的(后文会分析相关内容),调用uvm_resource #(T)类的write()函数将val写入同时记录相关操作信息,然后调用set()将这笔资源句柄rsrc存入资源池。
  与set()函数类似的还有一个静态函数set_anonymous():

//uvm_resource_db.svh
class uvm_resource_db #(type T=uvm_object);
  ...
    static function void set_anonymous(input string scope,
                                     T val, input uvm_object accessor = null);

    rsrc_t rsrc = new("", scope);
    rsrc.write(val, accessor);
    rsrc.set();

    if(uvm_resource_db_options::is_tracing())
      m_show_msg("RSRCDB/SETANON","Resource", "set", scope, "", accessor, rsrc);
  endfunction
  ...
endclass

  这个函数与set()相比,不需要传入第二个参数name,uvm_resource #(T)在调用new()时参数name为空字符串,因而调用set()时只会存入数组ttab[]而不会存入rtab[]。

4.1.2. set_override()

  同样可以通过静态函数set_override()来使得当前这笔资源被存入相关队列最前端:

//uvm_resource_db.svh
class uvm_resource_db #(type T=uvm_object);
  ...
  static function void set_override(input string scope, input string name,
                                    T val, uvm_object accessor = null);
    rsrc_t rsrc = new(name, scope);
    rsrc.write(val, accessor);
    rsrc.set_override();

    if(uvm_resource_db_options::is_tracing())
      m_show_msg("RSRCDB/SETOVRD", "Resource","set", scope, name, accessor, rsrc);
  endfunction
  ...
endclass

  在set_override()中,用set_override()函数来取代set(),默认override_t为2’b11,会把当前这笔资源分别存入相关name队列和type队列的最前端。另外还可以通过静态函数set_override_type()和set_override_name()分别将当前这笔资源存入相关type队列最前端(同时name队列最后端)和name队列最前端(type队列最后端):

//uvm_resource_db.svh
class uvm_resource_db #(type T=uvm_object);
  ...
    static function void set_override_type(input string scope, input string name,
                                         T val, uvm_object accessor = null);
    rsrc_t rsrc = new(name, scope);
    rsrc.write(val, accessor);
    rsrc.set_override(uvm_resource_types::TYPE_OVERRIDE);

    if(uvm_resource_db_options::is_tracing())
      m_show_msg("RSRCDB/SETOVRDTYP","Resource", "set", scope, name, accessor, rsrc);
  endfunction

  static function void set_override_name(input string scope, input string name,
                                  T val, uvm_object accessor = null);
    rsrc_t rsrc = new(name, scope);
    rsrc.write(val, accessor);
    rsrc.set_override(uvm_resource_types::NAME_OVERRIDE);

    if(uvm_resource_db_options::is_tracing())
      m_show_msg("RSRCDB/SETOVRDNAM","Resource", "set", scope, name, accessor, rsrc);
  endfunction
  ...
endclass

4.2. write_by_name()/write_by_type()

  uvm_resource_db类提供了静态函数write_by_name()和write_by_type():

//uvm_resource_db.svh
class uvm_resource_db #(type T=uvm_object);
  ...
  static function bit write_by_name(input string scope, input string name,
                                    input T val, input uvm_object accessor = null);

    rsrc_t rsrc = get_by_name(scope, name);

    if(uvm_resource_db_options::is_tracing())
      m_show_msg("RSRCDB/WR","Resource", "written", scope, name, accessor, rsrc);

    if(rsrc == null)
      return 0;

    rsrc.write(val, accessor);

    return 1;

  endfunction

  static function bit write_by_type(input string scope,
                                    input T val, input uvm_object accessor = null);

    rsrc_t rsrc = get_by_type(scope);

    if(uvm_resource_db_options::is_tracing())
      m_show_msg("RSRCDB/WRTYP", "Resource","written", scope, "", accessor, rsrc);

    if(rsrc == null)
      return 0;

    rsrc.write(val, accessor);

    return 1;
  endfunction
  ...
endclass

  二者的区别在于前者的第二个参数需要传入一个字符串类型的name,而后者不需要,在函数中,前者调用get_by_name()而后者调用get_by_type()来取得我们想要写入的资源uvm_resource #(T)类型储存在uvm_resource_pool中的实例句柄rsrc,若该笔资源尚未被储存在资源池即rsrc为null,则直接返回0。否则调用write()更新该笔资源的val并返回1。可以看出这两个函数主要是用来更新资源池中已有资源的val,对于尚未被储存的资源来说,调用这两个函数并不会起什么作用。

4.3. read_by_name()/read_by_type()

  uvm_resource_db类还提供了静态函数read_by_name()和read_by_type()来从资源池中获取相应的资源:

//uvm_resource_db.svh
class uvm_resource_db #(type T=uvm_object);
  ...
  static function bit read_by_name(input string scope,
                                   input string name,
                                   inout T val, input uvm_object accessor = null);

    rsrc_t rsrc = get_by_name(scope, name);

    if(uvm_resource_db_options::is_tracing())
      m_show_msg("RSRCDB/RDBYNAM","Resource", "read", scope, name, accessor, rsrc);

    if(rsrc == null)
      return 0;

    val = rsrc.read(accessor);

    return 1;
  
  endfunction

  static function bit read_by_type(input string scope,
                                   inout T val,
                                   input uvm_object accessor = null);
    
    rsrc_t rsrc = get_by_type(scope);

    if(uvm_resource_db_options::is_tracing())
      m_show_msg("RSRCDB/RDBYTYP", "Resource","read", scope, "", accessor, rsrc);

    if(rsrc == null)
      return 0;

    val = rsrc.read(accessor);

    return 1;

  endfunction
  ...
endclass

  与相关write函数类似,这两个函数也是前者的参数中多了一个字符串类型的name,在函数中二者调用get_by_name()和get_by_type()从资源池中拿到相应的uvm_resource #(T)类型的句柄rsrc,若该笔资源尚未被储存在资源池即rsrc为null,则直接返回0。否则调用read()读取该笔资源的val赋值给inout类型参数val并返回1。

5. uvm_config_db

  uvm_config_db类继承自uvm_resource_db类:

//uvm_config_db.svh
class uvm_config_db#(type T=int) extends uvm_resource_db#(T);

  // Internal lookup of config settings so they can be reused
  // The context has a pool that is keyed by the inst/field name.
  static uvm_pool#(string,uvm_resource#(T)) m_rsc[uvm_component];

  // Internal waiter list for wait_modified
  static local uvm_queue#(m_uvm_waiter) m_waiters[string];
  ...
endclass

  其中定义了两个静态联合数组m_rsc[]和m_waiters[]。其中m_rsc[]以uvm_component类型为索引,储存内容为uvm_pool类型:

//uvm_pool.svh
class uvm_pool #(type KEY=int, T=uvm_void) extends uvm_object;

  const static string type_name = "uvm_pool";

  typedef uvm_pool #(KEY,T) this_type;

  static protected this_type m_global_pool;
  protected T pool[KEY];


  // Function: new
  //
  // Creates a new pool with the given ~name~.

  function new (string name="");
    super.new(name);
  endfunction
  ...
endclass

  uvm_pool类是UVM提供的一种数据封装类型,类似于SV的联合数组或者python里的字典,其数据是以键-值对的形式存储,可以通过键KEY来索引其储存的值T。uvm_config_db里的静态数组m_rsc[]储存的uvm_pool类型以字符串为索引,储存内容为uvm_resource#(T)类型句柄。
  联合数组m_waiters[]以字符串为索引,储存内容为uvm_queue类型,而queue中储存的内容是m_uvm_waiter类型:

//uvm_config_db.svh
//Internal class for config waiters
class m_uvm_waiter;
  string inst_name;
  string field_name;
  event trigger;
  function new (string inst_name, string field_name);
    this.inst_name = inst_name;
    this.field_name = field_name;
  endfunction
endclass

  m_uvm_waiter类中只有两个字符串变量inst_name和field_name以及event变量trigger,其new()函数将两个参数分别赋值给inst_name和field_name。这个类型主要是用来表示在通过uvm_config_db来获取资源时的一些等待事件,如必须要等待该资源已经被set()存储在资源池中。

5.1. set()

  uvm_config_db类提供了静态函数set()来向资源池中存入资源:

//uvm_config_db.svh
class uvm_config_db#(type T=int) extends uvm_resource_db#(T);
  ...
  static function void set(uvm_component cntxt,
                           string inst_name,
                           string field_name,
                           T value);

    uvm_root top;
    uvm_phase curr_phase;
    uvm_resource#(T) r;
    bit exists;
    string lookup;
    uvm_pool#(string,uvm_resource#(T)) pool;
    string rstate;
    uvm_coreservice_t cs = uvm_coreservice_t::get();
     
    //take care of random stability during allocation
    process p = process::self();
    if(p != null) 
  		rstate = p.get_randstate();
  		
    top = cs.get_root();

    curr_phase = top.m_current_phase;

    if(cntxt == null) 
      cntxt = top;
    if(inst_name == "") 
      inst_name = cntxt.get_full_name();
    else if(cntxt.get_full_name() != "") 
      inst_name = {cntxt.get_full_name(), ".", inst_name};

    if(!m_rsc.exists(cntxt)) begin
      m_rsc[cntxt] = new;
    end
    pool = m_rsc[cntxt];

    // Insert the token in the middle to prevent cache
    // oddities like i=foobar,f=xyz and i=foo,f=barxyz.
    // Can't just use '.', because '.' isn't illegal
    // in field names
    lookup = {inst_name, "__M_UVM__", field_name};

    if(!pool.exists(lookup)) begin
       r = new(field_name, inst_name);
       pool.add(lookup, r);
    end
    else begin
      r = pool.get(lookup);
      exists = 1;
    end
      
    if(curr_phase != null && curr_phase.get_name() == "build")
      r.precedence = uvm_resource_base::default_precedence - (cntxt.get_depth());
    else
      r.precedence = uvm_resource_base::default_precedence;

    r.write(value, cntxt);

    if(exists) begin
      uvm_resource_pool rp = uvm_resource_pool::get();
      rp.set_priority_name(r, uvm_resource_types::PRI_HIGH);
    end
    else begin
      //Doesn't exist yet, so put it in resource db at the head.
      r.set_override();
    end

    //trigger any waiters
    if(m_waiters.exists(field_name)) begin
      m_uvm_waiter w;
      for(int i=0; i<m_waiters[field_name].size(); ++i) begin
        w = m_waiters[field_name].get(i);
        if(uvm_re_match(uvm_glob_to_re(inst_name),w.inst_name) == 0)
           ->w.trigger;  
      end
    end

    if(p != null)
    	p.set_randstate(rstate);

    if(uvm_config_db_options::is_tracing())
      m_show_msg("CFGDB/SET", "Configuration","set", inst_name, field_name, cntxt, r);
  endfunction
  ...
endclass

  这个函数有四个参数,第一个参数是uvm_component类型的cntxt,指出在哪个component中设置的这个资源,第二和第三个参数分别是字符串类型的inst_name和field_name,最后一个参数是要存入的资源类型T的值val。set()函数首先拿到一个process的句柄p,然后在函数开头和结尾分别调用get_randstate()和set_randstate(),这是两个SV的函数,此举是为了保证在set()函数执行过程中保持随机数发生器的稳定。接下来根据前两个参数决定inst_name,例如我们在testcase的build_phase中调用此函数,以下几种情形:

  1. uvm_config_db #(case_cfg)::set(this, “env.agt.drv”, “case_cfg”, cfg); (常见的推荐用法)
    则inst_name值为"uvm_test_top.env.agt.drv"。
  2. uvm_config_db #(case_cfg)::set(, “env.agt.drv”, “case_cfg”, cfg);
    则inst_name值为"uvm_top.env.agt.drv"。
  3. uvm_config_db #(case_cfg)::set(this, “”, “case_cfg”, cfg);
    则inst_name值为"uvm_top"。

  若数组m_rsc[]中没有以cntxt为索引的相关内容(很明显,第一次调用的时候肯定是没有的),那么调用new()函数创建一个以cntxt为索引的uvm_pool类型的句柄pool,接下来将inst_name和failed_name字符串中间加上字符"M_UVM"拼接起来,之所以中间要以特定字符串隔开是避免后面检索匹配时引起歧义,将拼接后的字符串赋值给lookup,以我们上面的第一种常见用法为例,此时lookup的值为"uvm_test_top.env.agt.drv__M_UVM__case_cfg"。若pool中不存在以lookup为索引的资源(很明显此时肯定是没有的),则将field_name和inst_name分别作为new()的两个参数name和scope创建一个uvm_resource#(T)类型的实例r(此即我们要写入的资源,其name=“case_cfg”,scope=“uvm_test_top.env.agt.drv”),调用add()以字符串lookup为索引,将r存入pool中。若这是第二次在testcase中调用set():
  uvm_config_db #(case_cfg)::set(this, “env.agt.drv”, “case_cfg”, new_cfg);
  第二次设置的是new_cfg,则由于inst_name和lookup都与第一次设置相同,所以直接在pool中以lookup为索引检索到第一次设置的资源将其赋值给句柄r,并把bit型变量exists置为1。
  接下来若set()函数是在build_phase()中被调用,则设置该笔资源的precedence为default_precedence减去cntxt的get_depth()返回值,这就是说,调用set()的component离uvm_top越远,其precedence越小,换句话说,处于UVM平台hireachy顶端的component设置的同样的资源具有较高优先级。注意,这个规则起作用的前提条件是在build_phase()中调用set(),否则,该笔资源r的precedence会被设置为default_precedence。然后调用write()将这笔资源的val更新并记录相关访问信息。
  接下来若exists为0(即这笔资源是第一次被存储),则直接调用set_override()将其分别存入name队列和type队列最前端。否则将uvm_resource_types::PRI_HIGH作为第二个参数调用uvm_resource_pool中的函数set_priority_name()将其更新到name队列最前端。由此可见,同一component中按先后顺序调用set()对同一资源进行设置,最后设置的资源具有最高优先级(无论是不是在build_phase中都成立)
  最后检查数组m_waiters[]中是否有储存以field_name为索引的队列,若有则调用uvm_re_match()比较二者inst_name是否匹配,若返回1则触发trigger事件。这段代码主要是为了处理任务wait_modified()中的阻塞事件,在我们调用get()获取某项资源之前,可以调用该任务来确认该资源已经通过set()被存入资源池:

//uvm_config_db.svh
class uvm_config_db#(type T=int) extends uvm_resource_db#(T);
  ...
  static task wait_modified(uvm_component cntxt, string inst_name,
      string field_name);
    process p = process::self();
    string rstate = p.get_randstate();
    m_uvm_waiter waiter;
    uvm_coreservice_t cs = uvm_coreservice_t::get();

    if(cntxt == null)
      cntxt = cs.get_root();
    if(cntxt != cs.get_root()) begin
      if(inst_name != "")
        inst_name = {cntxt.get_full_name(),".",inst_name};
      else
        inst_name = cntxt.get_full_name();
    end

    waiter = new(inst_name, field_name);

    if(!m_waiters.exists(field_name))
      m_waiters[field_name] = new;
    m_waiters[field_name].push_back(waiter);

    p.set_randstate(rstate);

    // wait on the waiter to trigger
    @waiter.trigger;
  
    // Remove the waiter from the waiter list 
    for(int i=0; i<m_waiters[field_name].size(); ++i) begin
      if(m_waiters[field_name].get(i) == waiter) begin
        m_waiters[field_name].delete(i);
        break;
      end
    end 
  endtask
endclass

  在wait_modified()中将inst_name和field_name作为参数new()函数创建一个m_uvm_waiter类型实例waiter,若数组m_waiters[]中不存在以field_name为索引的uvm_queue型队列,则创建一个uvm_queue型队列实例并将waiter存入,此后等待waiter中的trigger事件被触发(调用set()设置相关资源时会被触发),该事件被触发后,从m_waiters[field_name]对应的队列中将该waiter句柄删除。

5.2. get()

  与set()相对应,uvm_config_db提供了get()函数来获取资源池中的资源:

//uvm_config_db.svh
class uvm_config_db#(type T=int) extends uvm_resource_db#(T);
  ...
  static function bit get(uvm_component cntxt,
                          string inst_name,
                          string field_name,
                          inout T value);
//TBD: add file/line
    int unsigned p;
    uvm_resource#(T) r, rt;
    uvm_resource_pool rp = uvm_resource_pool::get();
    uvm_resource_types::rsrc_q_t rq;
    uvm_coreservice_t cs = uvm_coreservice_t::get();

    if(cntxt == null) 
      cntxt = cs.get_root();
    if(inst_name == "") 
      inst_name = cntxt.get_full_name();
    else if(cntxt.get_full_name() != "") 
      inst_name = {cntxt.get_full_name(), ".", inst_name};
 
    rq = rp.lookup_regex_names(inst_name, field_name, uvm_resource#(T)::get_type());
    r = uvm_resource#(T)::get_highest_precedence(rq);
    
    if(uvm_config_db_options::is_tracing())
      m_show_msg("CFGDB/GET", "Configuration","read", inst_name, field_name, cntxt, r);

    if(r == null)
      return 0;

    value = r.read(cntxt);

    return 1;
  endfunction
  ...
endclass

  与set()函数的四个参数一致,get()也有四个参数,这里不再一一赘述。首先也是在函数中根据参数取得inst_name,然后分别将inst_name,field_name和uvm_resource#(T)的句柄作为参数调用uvm_resource_pool的lookup_regex_names():

//uvm_resource.svh
class uvm_resource_pool;
  ...
  function uvm_resource_types::rsrc_q_t lookup_regex_names(string scope,
                                                           string name,
                                                           uvm_resource_base type_handle = null);
      return lookup_name(scope, name, type_handle, 0);
  endfunction
  ...
endclass

  这个函数直接返回lookup_name()函数的返回值,后者会根据传入的参数在资源池的name队列rtab[]中拿到以name为索引的uvm_queue类型队列句柄,然后遍历这个队列找到与参数传入的scope匹配的元素并将其放入一个uvm_queue类型队列q,最后返回q。由此可见,若某一笔资源只被存入资源池以type为索引的数组ttab[]而没有被存入rtb[]中,调用uvm_config_db #(T)::get()是没有办法拿到这笔资源的。
总结:

  1. 之所以要求调用uvm_config_db的set()和get()函数时对应的第三个参数filed_name一定要一致,是因为这个字符串参数会在set()中调用new()创建这笔资源实例时作为其name参数传入,从而作为数组rtab[]的索引,而get()函数会通过其第三个参数filed_name在rtab[]中搜寻这笔资源所在的uvm_queue类型队列,若二者不一致(哪怕只相差一个字符),则难以搜寻到对应的这笔资源。
  2. 若在某component的build_phase中调用uvm_config_db #(T)::set()对同一目标component设置同一笔资源,第一个参数表示的component节点离uvm_root最近的那个具有最高优先级。若在其它phase如run-time phase中,则在仿真时间靠后的设置具有较高优先级(不论set()的第一个参数component节点是在UVM hireachy哪个层次)。
  3. 若同一component如在testcase中对同一笔资源先后设置两次,则后设置的资源具有较高优先级。
  4. 若处于UVM hireachy同一级别的component如agt和scb在其build_phase中分别对drv用set()设置了同一笔资源,则以build_phase后执行的那个component中设置的资源具有较高优先级(因为在set()中会调用set_override()将这笔资源写入rtab[]),build_phase执行顺序以其名字字符串排序顺序执行。

6. 其它

6.1. 总结

6.1.1. 各常用函数调用关系

  uvm_config_db和uvm_resource_db各常用函数在各个类间调用关系如下:
在这里插入图片描述

6.1.2. uvm_config_db与uvm_resource_db的异同点

  根据上面的分析,我们发现其实uvm_config_db和uvm_resource_db对资源的配置和读取都是通过全局唯一的共享资源池uvm_resource_pool实现的,uvm_config_db类继承自uvm_resource_db类并做了一些扩展,二者的区别主要在于:

  1. uvm_config_db的set(),除了指定该笔资源的name和scope外,还可以通过指定一个component类型指针表明设置该笔资源的UVM component节点,UVM据此判断同类型资源从不同节点设置时的优先级。这在验证平台开发和维护过程中可以方便工程师的工作,比如我们在env里边配置了一些cfg句柄或者virtual interface,或者是一些VIP的环境被集成到我们的验证平台时,可以直接在testcase中通过uvm_config_db来重载env或者VIP中的相关配置,而不必深入到env和VIP中更改代码(事实上一般也不能这么做),从而避免直接更改代码可能引发的其它意想不到的问题。
  2. uvm_resource_db的set()(没有限制),write_by_name/type()(该笔资源必须已在资源池中,否则调用无任何影响)函数,则除了指定该笔资源的name和scope外,不必给出一个component类型指针,其第四个参数uvm_object类型指针只是用来给出debug信息。
  3. uvm_config_db的get()函数同样需要指定一个component指针,资源的配置和获取都是受到以component为基础的scope的限制的,虽然可以使用通配符来扩大范围,但毕竟范围之外的component都是无权访问相关资源的,这样可以有效避免使用共享的全局资源引发的问题。
  4. uvm_resource_db的read_by_name/type()函数同样可以靠scope或name来获取相关资源,但是其使用比较灵活,scope字符串不必设置成以UVM hierarchy的component节点为基础的形式,可以设置任意有效字符来标记和读取这笔资源。

  综上所述,在某component节点中配置相关资源时,若有明确的目标component,推荐使用uvm_config_db相关函数。若想在一个sequence中直接获取相关配置,除了使用p_sequencer获得相关资源外(可参阅UVM sequence机制源码探微相关内容),还可以直接使用uvm_resource_db来进行配置和获取相关资源,但是在使用时要注意其scope和name字符串的创建。

6.2. debug信息

6.2.1. UVM_CONFIG/RESOURCE_DB_TRACE

  在调用uvm_config_db的set()/get()等函数时时,这些函数最后会判断uvm_config_db_options::is_tracing()的返回值,若为1则调用m_show_msg()把当前操作的细节打印出来:

class uvm_resource_db #(type T=uvm_object);
  ...
  protected static function void m_show_msg(
          input string id,
          input string rtype,
          input string action,
          input string scope,
          input string name,
          input uvm_object accessor,
          input rsrc_t rsrc);

          T foo;
          string msg=`uvm_typename(foo);

          $sformat(msg, "%s '%s%s' (type %s) %s by %s = %s",
              rtype,scope, name=="" ? "" : {".",name}, msg,action,
              (accessor != null) ? accessor.get_full_name() : "<unknown>",
              rsrc==null?"null (failed lookup)":rsrc.convert2string());

          `uvm_info(id, msg, UVM_LOW)
  endfunction
  ...
endclass

  静态函数m_show_msg()定义在uvm_resource_db类中,就是根据输入的参数把对一波资源的操作信息打印出来。来看uvm_config_db_options类:

//uvm_config_db.svh
class uvm_config_db_options;
   
  static local bit ready;
  static local bit tracing;
  ...
  static function bit is_tracing();
    if (!ready) init();
    return tracing;
  endfunction
  
  static local function void init();
     uvm_cmdline_processor clp;
     string trace_args[$];
     
     clp = uvm_cmdline_processor::get_inst();

     if (clp.get_arg_matches("+UVM_CONFIG_DB_TRACE", trace_args)) begin
        tracing = 1;
     end

     ready = 1;
  endfunction

endclass

  这个类主要就是为uvm_config_db中的函数提供debug信息的开关。其中定义了两个静态bit型变量ready和tracing。静态函数is_tracing()首先检查ready是否为0,若是则调用静态函数init()进行初始化,init()会检测仿真命令行是否传入“UVM_CONFIG_DB_TRACE”字符串,若是则把tracing置为1,然后is_tracing()返回tracing的值,表示当前需要打印出debug信息。除了通过在命令行传入字符串来开启debug信息打印功能之外,uvm_config_db_options还提供了另外两个静态函数turn_on_tracing()和turn_off_tracing()来分别控制该功能的开启和关闭:

//uvm_config_db.svh
class uvm_config_db_options;
  ...
  static function void turn_on_tracing();
     if (!ready) init();
    tracing = 1;
  endfunction

  static function void turn_off_tracing();
     if (!ready) init();
    tracing = 0;
  endfunction
  ...
endclass

  与之类似,UVM还提供了uvm_resource_db_options类来控制uvm_resource_db中相关函数debug信息的打印功能,我们既可以通过在仿真命令行传入字符串“UVM_RESOURCE_DB_TRACE”来开启该功能,也可以通过直接调用uvm_resource_db_options::turn_on_tracing()和uvm_resource_db_options::turn_off_tracing()来分别开启和关闭debug信息的打印功能。

6.2.2. check_config_usage()

  之前我们在分析uvm_resource #()类的write()/read()函数时,在对每一笔资源进行读写操作时会分别调用record_write_access()和record_read_access():

//uvm_resource.svh
virtual class uvm_resource_base extends uvm_object;
  ...
  function void record_read_access(uvm_object accessor = null);

    string str;
    uvm_resource_types::access_t access_record;

    // If an accessor object is supplied then get the accessor record.
    // Otherwise create a new access record.  In either case populate
    // the access record with information about this access.  Check
    // first to make sure that auditing is turned on.

    if(!uvm_resource_options::is_auditing())
      return;

    // If an accessor is supplied, then use its name
	// as the database entry for the accessor record.
	// Otherwise, use "<empty>" as the database entry.
    if(accessor != null)
      str = accessor.get_full_name();
    else
      str = "<empty>";

    // Create a new accessor record if one does not exist
    if(access.exists(str))
      access_record = access[str];
    else
      init_access_record(access_record);

    // Update the accessor record
    access_record.read_count++;
    access_record.read_time = $realtime;
    access[str] = access_record;

  endfunction
  
  function void record_write_access(uvm_object accessor = null);

    string str;

    // If an accessor object is supplied then get the accessor record.
    // Otherwise create a new access record.  In either case populate
    // the access record with information about this access.  Check
    // first that auditing is turned on

    if(uvm_resource_options::is_auditing()) begin
      if(accessor != null) begin
        uvm_resource_types::access_t access_record;
        string str;
        str = accessor.get_full_name();
        if(access.exists(str))
          access_record = access[str];
        else
          init_access_record(access_record);
        access_record.write_count++;
        access_record.write_time = $realtime;
        access[str] = access_record;
      end
    end
  endfunction
  ...
  function void init_access_record (inout uvm_resource_types::access_t access_record);
    access_record.read_time = 0;
    access_record.write_time = 0;
    access_record.read_count = 0;
    access_record.write_count = 0;
  endfunction
endclass

  若函数uvm_resource_options::is_auditing()返回值为1,则首先调用参数传入的uvm_object类型的句柄accessor的get_full_name()函数取得name字符串,然后检查该笔资源的数组access[]中是否有关于这个accessor内容(name为索引),这个access[]我们之前在分析uvm_resource_base类时就看到过,其中储存的是该笔资源被每一个accessor访问的信息。若access[]中不存在关于这个accessor访问此笔资源的内容,则创建一个uvm_resource_types::access_t类型的实例access_record用于记录这个accessor对这笔资源的访问信息并调用init_access_record()对其进行初始化,把所有记录信息归零。最后把这笔access_record记录信息的write_count或者read_count加1并更新对应时间,这次访问信息就被记录完毕。我们检查这笔资源的数组access[]就会知道哪个accessor在什么时间对其进行了几次读写操作。
  UVM提供了uvm_resource_options类来控制是否会开启访问信息的记录功能:

//uvm_resource.svh
class uvm_resource_options;

  static local bit auditing = 1;

  static function void turn_on_auditing();
    auditing = 1;
  endfunction

  static function void turn_off_auditing();
    auditing = 0;
  endfunction

  static function bit is_auditing();
    return auditing;
  endfunction
endclass

  在uvm_resource_options类中定义了一个静态bit类型变量auditing,默认值为1,静态函数is_auditing()会返回这个变量的值。我们还可以分别通过直接调用静态函数uvm_resource_options::turn_on_auditing()和uvm_resource_options::turn_off_auditing()来动态开启和关闭对平台中资源访问信息的记录功能。
  在资源的配置和获取过程中,UVM是通过我们传入的scope字符串来存储和查找是否有该笔资源,一旦在函数调用时出现拼写失误,就会出现意想不到的问题,UVM并不会给出拼写有误的提示,这个时候去逐个检查验证平台的所有相关代码是很麻烦的一件事情,我们可以在testcase的build_phase之后如connect_phase(一般资源配置和获取都在build_phase中)中调用check_config_usage():

//uvm_component.svh
function void uvm_component::check_config_usage ( bit recurse=1 );
  uvm_resource_pool rp = uvm_resource_pool::get();
  uvm_queue#(uvm_resource_base) rq;

  rq = rp.find_unused_resources();

  if(rq.size() == 0)
    return;

  uvm_report_info("CFGNRD"," ::: The following resources have at least one write and no reads :::",UVM_INFO);
  rp.print_resources(rq, 1);
endfunction

  这个函数首先会调用uvm_resource_pool的find_unused_resources()函数:

//uvm_resource.svh
class uvm_resource_pool;
  ...
  function uvm_resource_types::rsrc_q_t find_unused_resources();

    uvm_resource_types::rsrc_q_t rq;
    uvm_resource_types::rsrc_q_t q = new;
    int unsigned i;
    uvm_resource_base r;
    uvm_resource_types::access_t a;
    int reads;
    int writes;

    foreach (rtab[name]) begin
      rq = rtab[name];
      for(int i=0; i<rq.size(); ++i) begin
        r = rq.get(i);
        reads = 0;
        writes = 0;
        foreach(r.access[str]) begin
          a = r.access[str];
          reads += a.read_count;
          writes += a.write_count;
        end
        if(writes > 0 && reads == 0)
          q.push_back(r);
      end
    end

    return q;

  endfunction
endclass

  函数find_unused_resources()会遍历资源池数组rtab[]中的每一笔资源,检查数组access[]中储存的所有不同accessor对其的访问信息,若某资源只有写入记录而从未有过任何读记录,则将其放入队列q中,最后返回q。
  回到check_config_usage()中,当拿到资源池中所有的从未被读取过的资源后,调用uvm_resource_pool的print_resources()函数将这些信息打印出来,我们可以根据这些提示信息进行debug,函数print_resources()定义在uvm_resource_pool类中,其中会对打印信息的格式做一些设置,这里不再赘述。
  此外,我们还可以直接调用uvm_resources.dump(1)将储存在资源池数组ttab[]中的所有资源打印出来:

//uvm_resource.svh
class uvm_resource_pool;
  ...
  function void dump(bit audit = 0);

    uvm_resource_types::rsrc_q_t rq;
    string name;

    `uvm_info("UVM/RESOURCE/DUMP","\n=== resource pool ===",UVM_NONE)

    foreach (rtab[name]) begin
      rq = rtab[name];
      print_resources(rq, audit);
    end

    `uvm_info("UVM/RESOURCE/DUMP","=== end of resource pool ===",UVM_NONE)

  endfunction
endclass
...
//----------------------------------------------------------------------
// static global resource pool handle
//----------------------------------------------------------------------
const uvm_resource_pool uvm_resources = uvm_resource_pool::get();

6.2.2. dump_get_records()

  uvm_resource_pool还提供了一个函数dump_get_records()用来记录所有通过调用get_by_name/type()而留下的访问记录:

//uvm_resource.svh
class get_t;
  string name;
  string scope;
  uvm_resource_base rsrc;
  time t;
endclass
...
class uvm_resource_pool;
  ...
  get_t get_record [$];
  ...
  function void dump_get_records();

    get_t record;
    bit success;
    string qs[$];

    qs.push_back("--- resource get records ---\n");
    foreach (get_record[i]) begin
      record = get_record[i];
      success = (record.rsrc != null);
      qs.push_back($sformatf("get: name=%s  scope=%s  %s @ %0t\n",
               record.name, record.scope,
               ((success)?"success":"fail"),
               record.t));
    end
    `uvm_info("UVM/RESOURCE/GETRECORD",`UVM_STRING_QUEUE_STREAMING_PACK(qs),UVM_NONE)
  endfunction
  ...
endclass

  UVM提供了一个类get_t用来记录访问相关的字符串类型的name和scope,以及访问到的这笔资源指针和时间。函数dump_get_records()会遍历uvm_resource_pool中定义的get_t类型的队列get_record[$]并打印出所有相关信息。
  我们之前在分析函数get_by_name/type()时提到过,调用这两个函数在资源池中寻找特定的一笔资源时,会调用push_get_record():

//uvm_resource.svh
class uvm_resource_pool;
  ...
  function void push_get_record(string name, string scope,
                                  uvm_resource_base rsrc);
    get_t impt;

    // if auditing is turned off then there is no reason
    // to save a get record
    if(!uvm_resource_options::is_auditing())
      return;

    impt = new();

    impt.name  = name;
    impt.scope = scope;
    impt.rsrc  = rsrc;
    impt.t     = $realtime;

    get_record.push_back(impt);
  endfunction
  ...
endclass

  这个函数就是创建一个get_t类型实例,并把参数传入的name,scope以及这笔资源句柄和时间信息记录下来然后存入队列get_record[$]。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值