前言上一篇讨论了一些关联内容并且简单的看了看clone和create的源码,这一篇咱们接着往下聊由copy函数来看看object的域注册机制。
【芯片验证】UVM源码计划(二) —— 一起研究下create/clone/copy
在本篇开始前先补充一位大佬关于上一篇的评论,详情可在阅读原文的评论区查看:
感谢指正与指导,果然芯片验证这件事深奥无比其乐无穷,没有经验或者结合实际深入分析过,很多时候即使读源码也只能知其然不知其所以然。因此在这里还是要重申一下,因为我自己的经验并不充分,对很多地方的理解是很有局限性甚至错误的,大家请批判性的阅读。
COPY函数的实现
先看一下uvm_object中copy函数的实现:
// copy
// ----
function void uvm_object::copy (uvm_object rhs);
//For cycle checking
static int depth;
if((rhs !=null) && (uvm_global_copy_map.get(rhs) != null)) begin
return;
end
if(rhs==null) begin
uvm_report_warning("NULLCP", "A null object was supplied to copy; copy is ignored", UVM_NONE);
return;
end
uvm_global_copy_map.set(rhs, this);
++depth;
__m_uvm_field_automation(rhs, UVM_COPY, "");
do_copy(rhs);
--depth;
if(depth==0) begin
uvm_global_copy_map.clear();
end
endfunction关于uvm_global_copy_map和depth的地方咱们先不管,删繁就简把这两个相关代码搞掉之后就简单多了:
// copy
// ----
function void uvm_object::copy (uvm_object rhs);
if(rhs==null) begin
uvm_report_warning("NULLCP", "A null object was supplied to copy; copy is ignored", UVM_NONE);
return;
end
__m_uvm_field_automation(rhs, UVM_COPY, "");
do_copy(rhs);
endfunction也就是copy函数就做了三步事情:检查输入的rhs是否已经new()分配了空间,调用__m_uvm_field_automation(rhs, UVM_COPY, "")执行复制操作(注意是把传入的rhs复制给自身哈,别搞反了),然后通过do_copy再执行补充的复制操作。这个时候我们来看看这三个函数的定义:
extern function void copy (uvm_object rhs);
extern virtual function void do_copy (uvm_object rhs);
extern virtual function void __m_uvm_field_automation (uvm_object tmp_data__, int what__, string str__);一件很有意思的事,do_copy和__m_uvm_field_automation是virtual function而copy是非virtual function,这里暗含着什么意思呢?就是说使用者不要在继承类里重载copy,你去搞do_copy和__m_uvm_field_automation就好了,copy函数就这么放着你别动(这里就不得不说我之前做的gen_uvm_agent脚本有问题了,回头我改了去)。而__m_uvm_field_automation和do_copy根据后面阅读源码可以知道,前者是类内的field注册时自动重载的,那么自然没有注册的field又想进行复制那自然就在do_copy里去搞了。同样的道理,其他各类操作也是这样设置的:
function string uvm_object::sprint(uvm_printer printer=null);
...
__m_uvm_field_automation(null, UVM_PRINT, "");
do_print(printer);
...
endfunction
function bit uvm_object::compare (uvm_object rhs,
uvm_comparer comparer=null);
...
__m_uvm_field_automation(rhs, UVM_COMPARE, "");
dc = do_compare(rhs, comparer);
...
endfunction
function void uvm_object::m_pack (inout uvm_packer packer);
...
__m_uvm_field_automation(null, UVM_PACK, "");
do_pack(packer);
...
endfunction就不一一进行列举啦大家看的时候明白这个思路就可以,回到copy的主线上来。do_copy的描述写的很清楚是user-definable的,也就是说你自己看情况去补充他:
// Function: do_copy
//
// The do_copy method is the user-definable hook called by the copy method.
// A derived class should override this method to include its fields in a copy
// operation.
//
// A typical implementation is as follows:
//
//| class mytype extends uvm_object;
//| ...
//| int f1;
//| function void do_copy (uvm_object rhs);
//| mytype rhs_;
//| super.do_copy(rhs);
//| $cast(rhs_,rhs);
//| field_1 = rhs_.field_1;
//| endfunction
//
// The implementation must call ~super.do_copy~, and it must $cast the rhs
// argument to the derived type before copying.所以说此时的重心就是看下__m_uvm_field_automation(null, UVM_PACK, "")是如何实现的。在uvm_object文件中这是一个空函数:
// __m_uvm_field_automation
// ------------------
function void uvm_object::__m_uvm_field_automation (uvm_object tmp_data__,
int what__,
string str__ );
return;
endfunction显然我们从名字也能看出来,这个必然是在field注册时进行重载的,所以咱们转头到uvm_object_define.svh文件里看看,很轻松就找到了位置:
`define uvm_field_utils_begin(T) \
function void __m_uvm_field_automation (uvm_object tmp_data__, \
int what__, \
string str__); \
begin \
T local_data__; /* Used for copy and compare */ \
...没错,就是在我们进行field注册时UVM帮我们完成的这个函数!这个时候我们带入实例看一下就更清楚了,巧了源码里就提供了我们常见的用法:
//| class my_trans extends uvm_sequence_item;
//|
//| cmd_t cmd;
//| int addr;
//| int data[$];
//| my_ext ext;
//| string str;
//|
//| `uvm_object_utils_begin(my_trans)
//| `uvm_field_enum (cmd_t, cmd, UVM_ALL_ON)
//| `uvm_field_int (addr, UVM_ALL_ON)
//| `uvm_field_queue_int(data, UVM_ALL_ON)
//| `uvm_field_object (ext, UVM_ALL_ON)
//| `uvm_field_string (str, UVM_ALL_ON)
//| `uvm_object_utils_end
//|
//| function new(string name="mydata_inst");
//| super.new(name);
//| endfunction
//|
//| endclass那么结合`uvm_object_utils_end的源码:
`define uvm_object_utils_end \
end \
endfunction \我们就知道了,中间这个field注册区完成的就是这个__m_uvm_field_automation function。那么咱们进一步的简化举例,这是一个my_trans类中只有一个int类型数据my_addr,然后把注册宏展开并且只保留copy的部分:
`uvm_object_utils_begin(my_trans)
`uvm_field_int(my_addr, UVM_ALL_ON)
`uvm_object_utils_end展开的结果就是这样,注册宏变成了一个完整的__m_uvm_field_automation函数:
function void __m_uvm_field_automation (uvm_object tmp_data__, int what__, string str__);
begin
my_trans local_data__; /* Used for copy and compare */
...
super.__m_uvm_field_automation(tmp_data__, what__, str__);
if(tmp_data__ != null) begin
if(!$cast(local_data__, tmp_data__)) return;
end
...
begin//`uvm_field_int start
case (what__)
...
UVM_COPY:
begin
if(local_data__ == null) return;
if(!((FLAG)&UVM_NOCOPY)) my_addr = local_data__.my_addr;
end
...
endcase
end//`uvm_field_int end
...
end
endfunction看一下这个函数的实现:创建了一个my_trans类型的数据local_data__,并在确定了输入的tmp_data__非空后通过$cast将local_data__指向了tmp_data__,这么做为了避免类型冲突;super.__m_uvm_field_automation(tmp_data__, what__, str__),确保本类型基类中声明和赋值的各项成员变量也进行了递归的拷贝;而后case(what_),copy中调用传参传的what是什么呢?是UVM_COPY,所以就执行了下面的UVM_COPY分支;在分支里确认了local_data__是否为空,如果tmp_data__为空则不会进行$cast操作那么local_data__则必然为空那么就直接退出(因为没有拷贝源了呀),否则的话就将local_data__.my_addr拷贝给自己的my_addr;
[/ol]if(!((FLAG)&UVM_NOCOPY)) my_addr = local_data__.my_addr这句话单独说一下,省的后面忘了。FLAG传的是啥呢?一般来说就是UVM_ALL_ON,具体是啥在这:
parameter UVM_DEFAULT = 'b000010101010101;
parameter UVM_ALL_ON = 'b000000101010101;
parameter UVM_FLAGS_ON = 'b000000101010101;
parameter UVM_FLAGS_OFF = 0;
//Values are or'ed into a 32 bit value
//and externally
parameter UVM_COPY = (1所以这里!((FLAG)&UVM_NOCOPY) = !('b101010101&'b10) = !('b0) = 'b1,所以if(!((FLAG)&UVM_NOCOPY)) begin-end块内的代码才会执行。如果在注册时传的flag是UVM_ALL_ON | UVM_NOCOPY,那么!((FLAG)&UVM_NOCOPY)就变成了!((FLAG)&UVM_NOCOPY) = !('b101010111&'b10) = !('b1) = 'b0,所以copy功能就没有实现。
OK这一套下来,通过注册机制实现copy函数的行为就非常明确。每个`uvm_field_int(my_addr, UVM_ALL_ON)都是这样一段begin-case-endcase-end代码,所以每一个注册的field都自动实现了copy功能。从这里也能看出来,UVM中实现的copy函数是以深拷贝为目的的,拷贝源与拷贝结果都有各自的空间。那么对于复杂结构比如其中的类实例是怎么操作以保证深拷贝的呢?
object内类实体注册顾名思义,就是在一个uvm_object内声明了另外一个object并且在`uvm_object_utils里进行注册(这里用注册可能容易有歧义,但是我也不知道用哪个词更准确,不要跟factory注册弄混了就可以)。最典型的场景就是agent里声明drv/mon/sqr并且进行域注册,毕竟uvm_component本质上也是uvm_object:
//| class my_comp extends uvm_component;
//|
//| my_comp_cfg cfg;
//|
//| `uvm_component_utils_begin(my_comp)
//| `uvm_field_object (cfg, UVM_ALL_ON)
//| `uvm_object_utils_end
//|
//| function new(string name="my_comp_inst", uvm_component parent=null);
//| super.new(name);
//| endfunction
//|
//| endclass那么此时我们用的哪个宏来注册?上文中也写到了:
`uvm_field_object (cfg, UVM_ALL_ON)于是找到这段宏的定义,我们还是重点看copy的实现:
`define uvm_field_object(ARG,FLAG) \
begin \
case (what__) \
...
UVM_COPY: \
begin \
if(local_data__ == null) return; \
if(!((FLAG)&UVM_NOCOPY)) begin \
if((FLAG)&UVM_REFERENCE || local_data__.ARG == null) ARG = local_data__.ARG; \
else begin \
uvm_object l_obj; \
if(local_data__.ARG.get_name() == "") local_data__.ARG.set_name(`"ARG`"); \
l_obj = local_data__.ARG.clone(); \
if(l_obj == null) begin \
`uvm_fatal("FAILCLN", $sformatf("Failure to clone %s.ARG, thus the variable will remain null.", local_data__.get_name())); \
end \
else begin \
$cast(ARG, l_obj); \
ARG.set_name(local_data__.ARG.get_name()); \
end \
end \
end \
end \
...
endcase \
end可以看到这段代码里有两个执行分支:if((FLAG)&UVM_REFERENCE || local_data__.ARG == null) ARG = local_data__.ARG;l_obj = local_data__.ARG.clone();...$cast(ARG, l_obj);
[/ol]这两个分支都很有意思,为了便于理解咱们先看第二个分支:
uvm_object l_obj; \
if(local_data__.ARG.get_name() == "") local_data__.ARG.set_name(`"ARG`"); \
l_obj = local_data__.ARG.clone(); \
if(l_obj == null) begin \
`uvm_fatal("FAILCLN", $sformatf("Failure to clone %s.ARG, thus the variable will remain null.", local_data__.get_name())); \
end \
else begin \
$cast(ARG, l_obj); \
ARG.set_name(local_data__.ARG.get_name()); \
end \创建了 uvm_object l_obj,而后注意执行了clone()函数:l_obj = local_data__.ARG.clone(),这说明了两点:对于类对象在拷贝的时候也是执行的深拷贝,clone本身就分配了地址空间使拷贝双方彻底分离;如果类对象是uvm_component类,那么走到这就会报UVM_ERROR;
[/ol]那么类对象是uvm_component类怎么办呢?我们去看第一个分支:
if((FLAG)&UVM_REFERENCE || local_data__.ARG == null) ARG = local_data__.ARG;UVM_REFERENCE在哪里呢,在刚刚的代码里但是被注释掉了:
//parameter UVM_DEEP = (1仔细在uvm_object_globals.svh里查找,发现在下面重新定义了一次:
// Enum: uvm_recursion_policy_enum
//
// Specifies the policy for copying objects.
//
// UVM_DEEP - Objects are deep copied (object must implement copy method)
// UVM_SHALLOW - Objects are shallow copied using default SV copy.
// UVM_REFERENCE - Only object handles are copied.
typedef enum {
UVM_DEFAULT_POLICY = 0,
UVM_DEEP = 'h400,
UVM_SHALLOW = 'h800,
UVM_REFERENCE = 'h1000
} uvm_recursion_policy_enum;注意哈,在枚举里定义和在类里直接定义其实没有本质区别,再仔细一看数值,跟注释掉那里数据没有什么不同所以我不清楚为啥要重新写一次。看这里的解释就明白了,UVM_REFERENCE的意思就是进行句柄拷贝。
所以回头看这里就清晰了很多,uvm_component走下面的分支因为clone的存在会报错,那么就要走上面的分支怎么才能走上面的分支呢?需要在component field自动化注册时这样写:
`uvm_field_object (ex_comp, UVM_ALL_ON | UVM_REFERENCE)仅仅这样还不行,还要满足一个条件就是被拷贝对象内部的这个component还没有type_id::create是一个空句柄,那么接下来才会进行一个空句柄之间的句柄拷贝而不会报错。否则类中的component类实例拷贝就会进入到下面的报错分支。
在这里大家自行感受一下就好了,我个人的看法是UVM希望使用者尽力避免component之间的任何拷贝行为,你最好把那些数值啥的放到cfg_object文件里去随便复制,但是别来搞uvm_component。这里就说这么多好啦,更多的细节大家还是通过阅读源码来挖掘吧。
uvm_global_copy_map与depth最后我们回到刚刚被删繁就简去掉的那部分uvm_global_copy_map与depth代码,看看这俩在起什么作用:
// copy
// ----
function void uvm_object::copy (uvm_object rhs);
//For cycle checking
static int depth;
if((rhs !=null) && (uvm_global_copy_map.get(rhs) != null)) begin
return;
end
if(rhs==null) begin
uvm_report_warning("NULLCP", "A null object was supplied to copy; copy is ignored", UVM_NONE);
return;
end
uvm_global_copy_map.set(rhs, this);
++depth;
__m_uvm_field_automation(rhs, UVM_COPY, "");
do_copy(rhs);
--depth;
if(depth==0) begin
uvm_global_copy_map.clear();
end
endfunctionuvm_global_copy_map结构本身很简单,他就是个字典结构罢了:
class uvm_copy_map;
local uvm_object m_map[uvm_object];
function void set(uvm_object key, uvm_object obj);
m_map[key] = obj;
endfunction
function uvm_object get(uvm_object key);
if (m_map.exists(key))
return m_map[key];
return null;
endfunction
function void clear();
m_map.delete();
endfunction
function void delete(uvm_object v);
m_map.delete(v);
endfunction
endclass所以我不太理解UVM为啥不做一个通用的uvm_object的字典结构,而要单独在这做一下。所以这个uvm_global_copy_map的作用也就很明显了啊,如果发现两个object已经进行过拷贝了那就不用搞了:
if((rhs !=null) && (uvm_global_copy_map.get(rhs) != null)) begin
return;
end那么说怎么拷贝时会发现两者已经拷贝过了呢?类A里可能类B的实例,类B里可能有类C实例,估计拷贝过程中保不齐哪个就拷贝过了?其实我不是太清楚这里的含义,不过行为是非常明确的,不行咱就看看他的注释自行体会下吧:
//------------------------------------------------------------------------------
//
// CLASS- uvm_copy_map
//
//
// Internal class used to map rhs to lhs so when a cycle is found in the rhs,
// the correct lhs object can be bound to it.
//------------------------------------------------------------------------------关于这点,评论区的大佬解释的更加清楚准确,膜拜学习:
然后再说这个depth,他是个所有uvm_object copy方法共享的静态变量:
static int depth;还是刚刚说的,uvm_object类A里可能类B的实例bb,类B里可能有类C实例cc,那么执行A aa1.copy(aa2)这样的操作时,在__m_uvm_field_automation(rhs, UVM_COPY, "")里必然会调用到aa1.bb = aa2.bb.clone(),而clone = create + copy,所以就会进入到B类的copy函数中。因为类A和类B共享depth所以depth会由1赋值为2,标记着当前递归克隆进行的深度。
系列文章入口——
【芯片设计】SoC 101(一):绪论 | 【芯片设计】FIFO漫谈(零)从无处不在的FIFO开始说起 | 【芯片设计】计算机体系结构(一)虚拟内存 | 【芯片设计】深入理解AMBA总线(零)绪论
| 【芯片设计】握手协议的介绍与时序说明 | 【芯片设计】复位那些小事 —— 复位消抖 | 【芯片设计】快速入门数字芯片设计(一)Introduction | 【芯片验证】UVM源码计划(零)下定决心读源码前的自测环节
| 【芯片设计】异步电路碎碎念(一) 到底什么是异步电路
| 【芯片设计】从RTL到GDS(一):Introduction
| 其他文章链接——
【芯片验证】sva_assertion: 15道助力飞升的断言练习 | 【芯片验证】可能是RTL定向验证的巅峰之作 | 【芯片验证】RTL仿真中X态行为的传播 —— 从xprop说起 | 【芯片验证】年轻人的第一个systemVerilog验证环境全工程与解析 |
【芯片设计】verilog中有符号数和无符号数的本质探究
| 【芯片设计】论RTL中always语法的消失术 | 【芯片设计】代码即注释,注释即代码 | 【芯片设计】700行代码的risc处理器你确实不能要求太多了 |
入职芯片开发部门后,每天摸鱼之外的时间我们要做些什么呢 | 如何计算系统的outstanding 和 burst length? | 芯片搬砖日常·逼死强迫症的关键词不对齐事件 | 熟人社会里,一群没有社会价值的局外人 |
|