【芯片验证】UVM源码计划(一)从component::type_id::create()开始看起
继续上一篇接着在object和component里耕耘收获,这一篇和下一篇来读一下object的注册机制以及在此机制下如何实现的copy功能,顺带着把create/clone/copy相关的源码以及关联知识进行一下探讨。
拷贝类型
先做一个关联探讨,关于SystemVerilog中的句柄拷贝/浅拷贝/深拷贝。这几个词在出入门的时候是必学的,不过在后续工作中反而没那么频繁的使用,先看下查到的相关定义(也不一定准确):
· 句柄拷贝(Handle Copy):句柄拷贝指的是将一个对象的句柄(即引用)复制给另一个变量。这种拷贝不会创建新的对象实例,而是让两个变量指向同一个对象。这意味着通过任何一个变量对对象进行的修改都会反映在另一个变量中。
· 浅拷贝(Shallow Copy):浅拷贝创建一个新对象,并将原对象的简单数据成员(如整数、字符串等)的值复制到新对象中。但对于复杂数据成员(如类实例、动态数组等),它只复制句柄,而不会创建新的实例。这意味着新对象与原对象共享复杂数据成员。
· 深拷贝 (Deep Copy):深拷贝不仅创建一个新对象,还会递归地复制原对象中的所有复杂数据成员,确保新对象完全独立于原对象。深拷贝通常需要为类定义一个深拷贝方法。
尽管现在有些地方只做深浅拷贝的区分,将句柄拷贝和浅拷贝放在一起,不过个人觉得还是做细致的区分比较好,所以就在这里说说我自己的理解。
句柄拷贝很明确就是句柄指向,把句柄a指向句柄b如果句柄b指向了具体实例空间那么句柄a也指向过去,如果句柄b是空句柄那么句柄a也会变成空句柄(印象里做过实验,哪怕后面句柄b指向了实体空间,句柄a仍旧是空句柄)。$cast(a, b)完成的也是句柄a和句柄b的句柄拷贝,关于这点就不多说了前面写的比较详细。
然后是浅拷贝,它和深拷贝一样都会创建一个新的对象(也就是开辟一个类型的实体空间出来),然后成员的值都复制过去。然后你看上面的释义里说了很长一句“但对于复杂数据成员(如类实例、动态数组等),它只复制句柄,而不会创建新的实例。这意味着新对象与原对象共享复杂数据成员”,在我的理解里很简单,就是复制时全部成员都是通过" = "复制的:
A.x = B.x;
A.y = B.y;
...
那么如果x、y是int类型的数那么自然就把值复制过去了且二者没有关联了,而如果A.x/B.x是一个类X的实例就不一样了,相当于是A.x句柄指向了B.x句柄。同样的如果是动态数组这种需要new()来开辟空间的,这里同样是一个句柄的指向拷贝。所以总结一句话,浅拷贝就是内部成员全部用" = "复制一遍,那么行为也就很明确了。还有一个问题是浅拷贝会不会递归复制基类中的值,这个很多时候看你怎么组织的代码,带着super嵌套所以会递归复制基类中的值。
深拷贝相对而言就简单了,就是拷贝的两者完全做切割,在一个完全独立的新空间搞出一个一模一样的实例且和被拷贝的实例没有任何关联。
在UVM中,使用$cast()来进行句柄拷贝,而copy()方法默认进行深拷贝,浅拷贝的概念实际上被淡化了。个人认为这是合理的,因为句柄拷贝是拷贝二者完全关联共用同一实例空间,深拷贝是二者完全切割使用两块独立空间,行为非常明确不易出错,而浅拷贝是二者主体空间切割但是复杂数据成员空间共用,这东西弄不好就出错。在后面看源码时就能看到,UVM确实不怎么想搞也不推荐浅拷贝的行为,copy()方法主体为深拷贝还有一个犄角旮旯用了句柄拷贝。
clone和copy
UVM在uvm_object中设置了两个拷贝相关的方法copy和clone,这两个方法的区别大家也必然都很清楚:clone = create + copy:
// clone
// -----
function uvm_object uvm_object::clone();
uvm_object tmp;
tmp = this.create(get_name());
if(tmp == null)
uvm_report_warning("CRFLD", $sformatf("The create method failed for %s, object cannot be cloned", get_name()), UVM_NONE);
else
tmp.copy(this);
return(tmp);
endfunction
copy方法的具体操作我们下一篇在聊。目前可以简单的认为,copy方法的tmp(拷贝结果)必须是已经new()过的实例(create本质上就是new)而不能是类型的空句柄,而clone方法的拷贝结果可以是空句柄也可以是实例。那么此时1X同学提出了一个问题:
为什么uvm_object可以copy也可以clone,而其派生出的uvm_componet只能copy不能clone呢?
// clone
// ------
function uvm_object uvm_component::clone ();
`uvm_error("ILLCLN", $sformatf("Attempting to clone '%s'. Clone cannot be called on a uvm_component. The clone target variable will be set to null.", get_full_name()))
return null;
endfunction
这个问题的直接回答很简单:因为uvm_componet不允许直接create,那自然就不能执行clone了:
function uvm_object uvm_component::create (string name ="");
`uvm_error("ILLCRT",
"create cannot be called on a uvm_component. Use create_component instead.")
return null;
endfunction
继续深究,为什么uvm_componet不可以通过create()也就是直接new()来创建呢?个人的理解是这样:
第一是因为每个componet都是UVM梳妆环境中的一环,都是有parent和childs的(至少有其一)。所以componet new的时候是带着parent传参的,但是uvm_object类这层则没有parent概念,因此uvm_object的virtual function uvm_object create (string name="")没有parent这个传参。而后还记得之前的结论吧:父类中以virtual定义的方法,子类中重载时接口必须一致,因此自uvm_object继承而来的uvm_componet的create方法的接口也只能是这样的:
extern virtual function uvm_object create (string name="");
那么new()需要parent,create()无法提供parent,所以create()不能用。那有人就说不对呀,create()用不了的话不是还有create_component()可以用吗,重载成clone = create_component + copy不就得了?那么就是我想的第二点。
看下create_component方法可以发现,它是调用的factory机制:
// create_component
// ----------------
function uvm_component uvm_component::create_component (string requested_type_name,
string name);
return factory.create_component_by_name(requested_type_name, get_full_name(),
name, this);
endfunction
那么也就是说拷贝结果必须在factory机制下注册了才可以,而clone/copy/create都没有提供factory机制注册操作,那自然用create_component + copy重载clone的操作也不可行。而且你都用供factory机制了说明type_id::create完成了,你都分配好拷贝结果的空间了那还有用clone的必要么?其实说白了,UVM希望你在进行uvm_componet的拷贝操作时是向通过type_id::create把拷贝源和拷贝结果都创建好分配好空间明确好parent和childs,然后通过copy()把变量值搞过去(如果变量中还有uvm_componet那这个事就好玩了,后面咱们看着源码说)。
那么综合上面所述,UVM禁用了uvm_componet的clone但是保留了copy的操作也是合理的。
|