覆盖方法
从上面的UVM类型的注册到对象的创建,读者知道了利用factory中注册号的类型,可以通过类型T(类型“箱子”)或者类型名Tname(字符串)来进行。与之类似的,factory也提供了覆盖(override)的特性,用户也可以通过类型覆盖或者实例名覆盖两种方式进行:
● set_inst_override(uvm_object_wrapper override_type, string inst_path)
● set_type_override(uvm_object_wrapper override_type)
上面这两种方法由类型箱子uvm_component_registry和uvm_object_registry来提供。而从
static function void set_inst_override(uvm_object_wrapper override_type,
string inst_path,
uvm_component parent=null);
string full_inst_path;
uvm_coreservice_t cs = uvm_coreservice_t::get();
uvm_factory factory=cs.get_factory();
...
factory.set_inst_override_by_type(get(),override_type,inst_path);
endfunction
static function void set_type_override (uvm_object_wrapper override_type,
bit replace=1);
uvm_coreservice_t cs = uvm_coreservice_t::get();
uvm_factory factory=cs.get_factory();
factory.set_type_override_by_type(get(),override_type,replace);
endfunction
uvm-1.2/base/uvm_registry.svh
在这里介绍覆盖方法时,我们的例码以通过类型覆盖方法set_type_override来说明。
module factory_override;
import uvm_pkg::*;
`include "uvm_macros.svh"
class comp1 extends uvm_component;
`uvm_component_utils(comp1)
function new(string name="comp1", uvm_component parent=null);
super.new(name, parent);
$display($sformatf("comp1:: %s is created", name));
endfunction: new
virtual function void hello(string name);
$display($sformatf("comp1:: %s said hello!", name));
endfunction
endclass
class comp2 extends comp1;
`uvm_component_utils(comp2)
function new(string name="comp2", uvm_component parent=null);
super.new(name, parent);
$display($sformatf("comp2:: %s is created", name));
endfunction: new
function void hello(string name);
$display($sformatf("comp2:: %s said hello!", name));
endfunction
endclass
comp1 c1, c2;
initial begin
comp1::type_id::set_type_override(comp2::get_type());
c1 = new("c1");
c2 = comp1::type_id::create("c2", null);
c1.hello("c1");
c2.hello("c2");
end
endmodule
输出结果:
comp1:: c1 is created
comp1:: c2 is created
comp2:: c2 is created
comp1:: c1 said hello!
comp2:: c2 said hello!
在上面的例码中,comp2类型覆盖了comp1类型
comp1::type_id::set_type_override(comp2::get_type());
紧接着对c1和c2对象进行了创建,可以从输出结果看到的是c1的所属类型仍然是comp1,c2的所属类型则变为了comp2。这说明了factory的覆盖机制只会影响通过factory方法来创建的对象。所以,通过type_id::create()和factory的类型覆盖可以实现对象类型在例化时的灵活替换。在上面的例子中,有几点隐藏的重点需要读者注意:
1. 在例化c2之前,首先应该用comp2来替换comp1的类型。只有先完成了类型替换,才可以在后面的例化时由factory选择正确的类型。
2. 上面的例码中较好地反映了一些实际情况。首先在声明c2时,由于verifier不知道今后可能存在覆盖的情况,所以类型为comp1。在后面发生了类型替换以后,如果原有的代码不做更新,那么c2句柄的类型仍然为comp1,却指向了comp2类型的对象。这就要求,comp2应该是comp1的子类,只有这样,句柄指向才是合法安全的。
3. c2在调用hello()方法时,由于首先是comp1类型,那么会查看comp1::hello(),又由于该方法在定义时被指定为虚函数,这就通过了多态性的方法调用,转而调用了comp2::hello()函数。因此,显示的结果也是“comp2:: c2 said hello!”。
而在uvm_default_factory提供的方法set_type_override_by_type()中,我们可以抽取其要义部分。
在该方法中,会首先看已经置于uvm_default_factory的一个队列uvm_factory_override m_type_overrides[$],该队列中的单元uvm_factory_override保存了原始类型和覆盖类型。那么在执行覆盖方法时,从下面源代码可以看到主要在于首先检查是否该原类型已经被覆盖过,如果是的话,那么会对已经覆盖的信息进行更新,这可以满足一种类型被多次覆盖的情况,而最后只按照最终覆盖类型为准;如果该类型原先没有被覆盖过,则会新创建一个覆盖类型,用来保存原始类型和覆盖类型的信息。
function void uvm_default_factory::set_type_override_by_type (
uvm_object_wrapper original_type,
uvm_object_wrapper override_type,
bit replace=1);
bit replaced;
...
// check for existing type override
foreach (m_type_overrides[index]) begin
if (m_type_overrides[index].orig_type == original_type ||
(m_type_overrides[index].orig_type_name != "<unknown>" &&
m_type_overrides[index].orig_type_name != "" &&
m_type_overrides[index].orig_type_name == original_type.get_type_name())) begin
...
replaced = 1;
m_type_overrides[index].orig_type = original_type;
m_type_overrides[index].orig_type_name = original_type.get_type_name();
m_type_overrides[index].ovrd_type = override_type;
m_type_overrides[index].ovrd_type_name = override_type.get_type_name();
end
end
// make a new entry
if (!replaced) begin
uvm_factory_override override;
verride = new(.orig_type(original_type),
.orig_type_name(original_type.get_type_name()),
.full_inst_path("*"),
.ovrd_type(override_type));
m_type_overrides.push_back(override);
end
endfunction
uvm-1.2/base/uvm_factory.svh
在有了注册类型词典和覆盖类型队列的信息之后,可以从下面这幅图中看出,当c2通过factory创建时,会查看被创建类型是否已经被覆盖,如果被覆盖则从uvm_default_factory::m_type_overrides中取得覆盖类型的信息。从这个例子来看comp2类型已经覆盖了comp1类型,因此最终创建的类型属于comp2类型。
无论是对uvm_component还是对uvm_object进行覆盖,无论是通过类型覆盖还是通过实例路径覆盖,覆盖的机制与上面的例子都是相近的。接下来,我们再来就着上面覆盖时需要注意的编码要求,给出读者一些建议,使得覆盖可以避免一些错误,顺利实现。
确保正确覆盖的代码要求
从之前的例码可以看出factory提供了简单有效的方式来定制验证环境,同时也易于环境的更新修改。为了保证环境中例化的组件都可以享受到工厂覆盖的便利,达到更多的代码复用,读者应该养成习惯:
- 将UVM环境中所有的新类都注册到工厂中,并通过工厂来创建对象。
通过这一方法可以提高验证结构的灵活性,因为注册类是在编译时段完成的(不影响运行速度),而创建类也只是额外引入了类索引的方式创建对象实现了正确的按类创建。
- 引入通过工厂注册后的类包(class package)。
这一点容易理解,如果在一些环境没有引入包和其定义的类,那么则无法通过该类来声明,或者通过该类来创建对象。常见的错误有,在顶层环境中没有引入定义test类的包,这使得run_test()方法无法从工厂中识别注册过的类,因此提示错误,无法识别要运行的test名称。
- 通过工厂创建对象时,句柄名称应该同传递到create()方法中的字符串名称相同。
无论是通过层次路径名称来覆盖还是配置,将例化组件的句柄名称同创建时create()方法中的字符串名称保持一致,都可以带来显而易见的好处。这种做法会使得调试时减少更多的困惑,是环境建立尽量简单。
- 由于覆盖是采用last-wins模式,因此要注意在同一个顶层build_phase()中覆盖方法应发生在对象创建之前。
覆盖的配置应该在build_phase中调用,由于build_phase的调用顺序是自顶向下的,而覆盖的作用是采用last-wins模式,所以顶层的覆盖如果与底层的覆盖冲突,那么顶层的覆盖会最终有效。这种方式也符合环境复用的观点。但是,如果在同一个build_phase函数中,既有覆盖方法也有创建方法,并且都引用同一个注册类,那么就应该保证覆盖方法先发生,再而调用创建方法,这也符合了处于同一函数先后执行的顺序。
- 为了尽量保证运行时覆盖类可以替换原始类,覆盖类最好是原始类的子类,而调用成员方法也应当声明为虚方法。
这一点可以从上面的例码中得到证明,由于原始代码中的句柄c2的类型为comp1,如果在覆盖之后,c2则指向了comp2对象。为了保证句柄类型正确,comp2应当为comp1的子类型,同时为了通过多态正确调用方法,comp1中的方法应当声明为虚方法。
- 另外一种确保运行时覆盖类句柄类型正确的方式,则需要通过$cast()进行动态类型转换。
还是上面的例子,也可以将c2声明为uvm_component类型句柄,而在创建了覆盖类comp2对象后,应该将c2句柄类型转换为factory中注册的"comp1"类型。这种转换会按照情况而定,如果“comp1”类型没有被覆盖,那么转换的句柄类型应该comp1类型;如果"comp2"类型覆盖了"comp1"类型,那么转换的句柄类型应该为comp2类型。通过$cast()进行动态类型转换,也可以保证运行时句柄调用的类型正确。
再看工厂设计模式
浏览完UVM工厂后,读者有没有觉得它就像一个大大的乐高世界一样,一旦把那些组件装载盒子注册之后,接下来的UVM环境搭建就变得更加容易、更方便日后维护了。这离不开facotory的三个核心要素:注册、创建和覆盖。
下一节《核心基类》我们将着重了解UVM核心基类所提供的基本方法。
谢谢你对路科验证的关注,也欢迎你分享和转发真正的技术价值,你的支持是我们保持前行的动力。