文章目录
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中调用此函数,以下几种情形:
- uvm_config_db #(case_cfg)::set(this, “env.agt.drv”, “case_cfg”, cfg); (常见的推荐用法)
则inst_name值为"uvm_test_top.env.agt.drv"。 - uvm_config_db #(case_cfg)::set(, “env.agt.drv”, “case_cfg”, cfg);
则inst_name值为"uvm_top.env.agt.drv"。 - 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()是没有办法拿到这笔资源的。
总结:
- 之所以要求调用uvm_config_db的set()和get()函数时对应的第三个参数filed_name一定要一致,是因为这个字符串参数会在set()中调用new()创建这笔资源实例时作为其name参数传入,从而作为数组rtab[]的索引,而get()函数会通过其第三个参数filed_name在rtab[]中搜寻这笔资源所在的uvm_queue类型队列,若二者不一致(哪怕只相差一个字符),则难以搜寻到对应的这笔资源。
- 若在某component的build_phase中调用uvm_config_db #(T)::set()对同一目标component设置同一笔资源,第一个参数表示的component节点离uvm_root最近的那个具有最高优先级。若在其它phase如run-time phase中,则在仿真时间靠后的设置具有较高优先级(不论set()的第一个参数component节点是在UVM hireachy哪个层次)。
- 若同一component如在testcase中对同一笔资源先后设置两次,则后设置的资源具有较高优先级。
- 若处于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类并做了一些扩展,二者的区别主要在于:
- uvm_config_db的set(),除了指定该笔资源的name和scope外,还可以通过指定一个component类型指针表明设置该笔资源的UVM component节点,UVM据此判断同类型资源从不同节点设置时的优先级。这在验证平台开发和维护过程中可以方便工程师的工作,比如我们在env里边配置了一些cfg句柄或者virtual interface,或者是一些VIP的环境被集成到我们的验证平台时,可以直接在testcase中通过uvm_config_db来重载env或者VIP中的相关配置,而不必深入到env和VIP中更改代码(事实上一般也不能这么做),从而避免直接更改代码可能引发的其它意想不到的问题。
- uvm_resource_db的set()(没有限制),write_by_name/type()(该笔资源必须已在资源池中,否则调用无任何影响)函数,则除了指定该笔资源的name和scope外,不必给出一个component类型指针,其第四个参数uvm_object类型指针只是用来给出debug信息。
- uvm_config_db的get()函数同样需要指定一个component指针,资源的配置和获取都是受到以component为基础的scope的限制的,虽然可以使用通配符来扩大范围,但毕竟范围之外的component都是无权访问相关资源的,这样可以有效避免使用共享的全局资源引发的问题。
- 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[$]。