1 SV重载机制
1.1 类的封装
一般而言,类里的变量/方法有两种被访问的方式:(1),在定义类时,在类的内部直接使用变量/方法;(2),当类已经被例化后,通过class_inst.num或者class_inst.method()的方法使用类的变量和方法。因此确定变量/方法的访问权限很有必要。
类的封装特性使得类可以根据需要来确定外部访问的权限级别,一般可以将变量声明为以下三种形式:
public: 子类和外部均可以访问(默认);
protected: 只有当前类或者子类可以访问,外部无法访问;
local: 只有当前类可以访问,子类和外部均无法访问;
举例:默认情况下,通过class_inst.num就可以访问class_inst实例的下属变量num,因此默认的变量申明都是public;但是如果num被声明成local类型,那么只有class_inst的内部方法可以访问num,其子类不能以继承的方式访问它,也不可以通过class_inst.num这种外部方式访问它,否则VCS工具会报错:Could not find member 'num' in class 'xxx'。
1.2 类的继承
类的继承特性使得子类可以使用父类的成员变量(variable)或方法(method),当我们在子类实例上调用某一个变量或者方法时,分两种情况说明:
(1): 子类中没有同名的变量或方法,那么编译器会去父类中查找该变量或方法并且使用它。也就是说:在子类中可以直接使用父类的变量和方法。
(2): 子类中有同名的变量或方法,那么在子类中通过super.value或者super.method的方法才能使用父类的变量或方法。(如果不使用super., 会默认使用子类中的变量或方法,那么相当于父类中的变量或方法会被子类覆盖掉)
什么是成员覆盖:
覆盖的前提是父类和子类中所关心的变量或方法名相同。如果子类没有使用super.来强调同名的变量或方法是父类的,那么父类中的同名变量或方法会被子类覆盖。当然也可以通过this.来强调同名的变量或方法是子类的。
当引用某一变量或者方法时,如果没有用.super或者.this的方式指明作用域,则会根据由近到远的原则查找该变量和方法:
(a): 查看变量/方法是否是函数内部定义的变量/方法;
(b): 查看变量/方法是否是当前类定义的变量/方法;
(c): 查看变量/方法是否是父类定义的变量/方法;
除此之外,构造函数(new)的继承需要额外说明:
如果一个类没有定义new函数,那么默认的new函数会被自动定义;子类在定义new函数时,首先调用父类的new函数即super.new(...);那么在调用子类new函数创建对象时,遵循如下顺序:
(1):首先调用父类的new()函数,将父类变量初始化;
(2):然后执行父类new()函数中自定义的代码;
(3):接着将子类变量初始化:按定义时显示的默认值初始化,无默认值则不被初始化;
(4):最后执行子类new()函数中自定义的代码;
1.3 类的多态
1.3.1 虚方法
虚方法是多态的主要特征。关于虚方法,简而言之就是用父类句柄指向子类对象,然后通过父类的句柄调用实际子类的成员方法。
前提:父类和子类中有同名的method;
场景:"父类句柄指向子类对象"的常见场景如下:
(a): 在子类中调用一些上层的方法时,往往会有参数传递,参数传递时容易发生隐式转换,即将子类对象的句柄赋给父类句柄,从而使得父类句柄指向了子类对象;
(b): 在构建上层框架(比如方法学)时,为了通用性,很多句柄都是用父类对象声明;当用户使用上层框架中的方法时,很容易将自己创建的子类对象的句柄赋给上层框架中的父类句柄,从而使得父类句柄指向了子类对象;
如果父类和子类中有同名的method,且父类中的method声明为virtual,那么当通过"指向子类对象的父类句柄"调用method时,SV的虚方法重载机制会检测到句柄指向的对象是子类,从而只调用子类中的task/function。
缺点:一旦在父类中定义了某虚方法,那么在子类中实现此方法时,要求子类方法和父类虚方法的函数名,参数名都要一样,而且不能增删参数,否则多态就不适用了;因此这要求在构建父类虚方法时必须提前做好计划,全面考虑子类实现此方法时的不同情况。
1.3.2 句柄转换
尽管通过虚方法的声明使得"在指定子类对象的父类句柄上调用子类方法时,可以正确地找到该方法”,但是,"指向子类对象的父类句柄"仍然有一些变量和方法无法通过虚方法来解决索引问题:
(a): 父类没有定义,只有在子类中定义了的方法; (虚方法只能解决父类和子类同时存在method的问题)
(b): 父类没有声明,只有在子类中声明了的变量; (虚方法只针对method)
(c): 父类和子类同时声明了的变量; (虚方法只针对method)
通过$cast(target, source)方法解决上面的三个问题。$cast(target, source)执行成功的前提是:"source指向的对象"和句柄target是同一类型,或者"source指向的对象"是句柄target的扩展类。
因此source一般是"指向子类对象的父类句柄",target一般是"用子类声明的句柄",完成转换后,就可以通过target句柄来访问子类的变量或方法。
示例1如下:
class axi_sbx_item extends uvm_object; // define variable typedef axi_sbx_item this_type_t; this_type_t original_item; this_type_t split_item; function void split(); if(!$cast(this.split_item, this.clone())) `uvm_error("split", "cast failed!") ...... this.split_item.original_item = this; endfunction endclass
this指axi_sbx_item类创建的对象;由于clone()是uvm的方法,uvm事先不知道axi_sbx_item的存在,因此this.clone()必定返回"指向子类对象的父类句柄",而this.split_item是用子类声明的句柄,执行$cast(this.split_item, this.clone())后,this.split_item就指向了clone出的对象;
1.3.3 一个类的多态的例子
class base_test; int data;
int crc;
virtual function base_test copy(); // 在父类构造copy()方法: base_test t = new(); // (1): 首先创建父类对象,由于在父类中实现,因此该对象类型为base_test; copy(t); // (2): 然后复制变量,由于在(1)中创建的对象为base_test型,所以copy_data()方法的参数类型也是base_test; return t; endfucntion
virtual function void copy_data(base_test t); // copy_data()用来复制父类变量; t.data = data;
t.crc = crc; endfunction endclass ------------------------------------------------------------------------------- class my_test extends base_test; int id;
function base_test copy(); // 为了实现虚方法重载,子类方法/参数的名字和类型要与父类保持一致,因此被迫声明为base_test类型;
my_test t = new(); // (1):首先创建子类对象,在创建子类对象时,会自动调用super.new()方法; copy_data(t); // (2): return t; endfucntion
function void copy_data(base_test t); // 为了实现虚方法重载,子类方法/参数的名字和类型要与父类保持一致,因此被迫声明为base_test类型; my_test h;
super.copy_data(t); //
$cast(h, t);
h.id = id;
endfunction endclass -------------------------------------------------------------------------------
module tb;
my_test m_t1;
my_test m_t2;
initial begin
m_t1 = new();
$cast(m_t2, m_t1.copy());
......
end
endmodule
相比于UVM中通过Field Automation提供了比较简单的对象复制,SV中需要我们人工定义copy()方法来实现对象复制,上面的例子就是SV中用于复制对象的代码;
(1):将成员复制函数copy_data()和新对象生成函数copy()分为两个方法,便于子类的继承和方法的复用;
(2):从自顶向下构建copy()/copy_data()方法的角度来看,首先我们在父类中定义copy()/copy_data()方法,实现父类新对象的生成以及父类成员变量的复制。但是为什么父类的copy()/copy_data()方法要声明为virtual呢?如我们在【1.3.1 虚方法】中所述:如果想在"指向子类对象的父类句柄"上调用copy()/copy_data()方法来复制对象,必须要将父类的copy()/copy_data()方法声明为virtual,否则子类的变量无法复制;
(3):一旦将父类的copy()/copy_data()方法声明成virtual,麻烦也就接踵而至,因为虚方法要求子类方法/参数的名字和类型必须与父类一致,因此子类方法的类型也必须声明为base_test;那么子类在调用copy()方法时,返回一个"指向子类对象的父类句柄",必须通过$cast()方法实现句柄转换。
2 UVM重载机制
2.0 factory的基础知识
UVM的重载依赖于factory,factory的意义:在uvm中之所以用factory机制创建对象,主要是利用factory提供的重载机制,使得在不修改原有验证环境层次和验证事务的同时,实现对环境内的component或object的覆盖。
UVM中有一个独立于uvm继承关系的类:uvm_coreservice_t,该类内置了UVM世界核心的组件和方法,主要包括:
(1): 唯一的uvm_factory实例(singleton mode),用来注册,覆盖和例化;
(2): 全局的report_server;
(3): 全局的tr_database;
(4): get_root()方法用来返回当前UVM环境的结构顶层对象;
ucli模式下,在verdi的Object窗口中可以看到uvm_coreservice_t的实例,如下:
这里只分析关于uvm_factory的代码。在src/base/uvm_factory.svh中,定义了两个class,如下:
virtual class uvm_factory;
......
endclass
class uvm_default_factory extends uvm_factory; // 稍后的代码中,会用到父类句柄指向子类对象,通过virtual function实现重载
......
endclass
uvm_factory只是一个抽象类,声明了许多pure virtual method,但没有具体实现;UVM默认使用uvm_default_factory为全局提供服务,通过句柄factory可以访问它;
class uvm_default_factory提供的方法有很多,这里先分析主要的两个:
(1): register()方法
function void uvm_default_factory::register(uvm_object_wrapper obj); if (obj == null) begin uvm_report_fatal ("NULLWR", "Attempting to register a null object with the factory", UVM_NONE); end if (obj.get_type_name() != "" && obj.get_type_name() != "<unknown>") begin if (m_type_names.exists(obj.get_type_name())) ...... else m_type_names[obj.get_type_name()] = obj; end if (m_types.exists(obj)) begin if (obj.get_type_name() != "" && obj.get_type_name() != "<unknown>") ......;
end else begin m_types[obj] = 1; if(m_inst_override_name_queues.exists(obj.get_type_name())) begin m_inst_override_queues[obj] = new; m_inst_override_queues[obj].queue = m_inst_override_name_queues[obj.get_type_name()].queue; m_inst_override_name_queues.delete(obj.get_type_name()); end if(m_wildcard_inst_overrides.size()) begin if(! m_inst_override_queues.exists(obj))
m_inst_override_queues[obj] = new; foreach (m_wildcard_inst_overrides[i]) begin if(uvm_is_match( m_wildcard_inst_overrides[i].orig_type_name, obj.get_type_name())) m_inst_override_queues[obj].queue.push_back(m_wildcard_inst_overrides[i]); end end end endfunction
通过factory.register(...)方法,在该类没有被注册过或者覆盖过时:
将该类例化后的对象句柄(obj)放置到关联数组m_type_names[string]中,其中键为实例对象的句柄字符串,值为实例对象的句柄; // m_type_names[obj.get_type_name()] = obj;
将该类例化后的对象句柄(obj)放置到关联数据m_types[uvm_object_wrapper]中,其中键为实例对象的句柄,值为1; // m_types[obj] = 1;
因此所谓注册并不是将需要注册的类抽象放置在某个地方,而是通过将需要注册的类例化(this_type me=new())来完成注册的,由于一种类型(class)在通过宏调用时只注册一次,那么在不考虑覆盖的前提下,factory就将每一个类对应的实例对象(me)都放置到关联数组m_type_names[string]中。
除此之外我们还关心注册时override相关的操作:
m_inst_override_queues[$]:保存通过方法set_type_override_by_type()添加的信息;
对一个class注册时,会对内部的m_wildcard_inst_override和m_inst_override_name_queue进行检查,如果新注册的类的name在这两个queue中,会删除相应的queue,然后添加到m_inst_override_queues[$]中;
(2): create_component_by_type()方法
function uvm_component uvm_default_factory::create_component_by_type (uvm_object_wrapper requested_type, string parent_inst_path="", string name, uvm_component parent); string full_inst_path; if (parent_inst_path == "") full_inst_path = name; else if (name != "") full_inst_path = {parent_inst_path,".",name}; else full_inst_path = parent_inst_path; m_override_info.delete(); requested_type = find_override_by_type(requested_type, full_inst_path); return requested_type.create_component(name, parent); endfunction
create_component_by_type()方法首先检查在该路径中(full_inst_path)需要被例化的对象,是否受到了"类型覆盖"或"实例覆盖"的影响,这一步由find_override_by_type实现;然后通过create_component()方法创建对象;分析如下:
(a):find_override_by_type()
这个方法的第一个参数是静态成员变量me,第二个参数是full_inst_path;这个方法最重要的地方在于:会去uvm_factory_override m_type_overrides[$]中查找ovrd_type是不是就是要查找的类型,那么m_type_overrides[$]中的信息是什么时候被添加进去的呢?在【2.2 在例化前设置覆盖对象和类型】中有具体分析,简而言之就是如果用户有使用set_type_override_by_type(...)方法,那么original_type和override_type的信息会被放到m_type_overrides[$]中;
在本例中就相当于是看new_example_comp是不是等于example_comp,如果不等于就说明example_comp被覆盖过,所以会调用find_override_by_type()函数,这相当于是递归调用,只是这次调用传入的第一个参数是专属于new_example_comp的 uvm_component_registry#(new_example_comp, “new_example_comp”)的静态成员变量me,第二个参数是字符串example_comp。
(b):create_component()
create_component()是class uvm_object_wrapper的方法;
2.1 将类注册到factory
2.1.1 实现方式
对于component类型,用下面的宏将class example_comp注册到factory:`uvm_component_utils(example_comp)
对于object类型, 用下面的宏将class example_object注册到factory:`uvm_object_utils(example_object)
为了方便,这里仅仅以component为例说明,即分析`uvm_component_utils(example_comp):
2.1.2 分析`uvm_component_utils(example_comp)
uvm_component_utils()可以分解为如下两个宏:
`define uvm_component_utils(T) \
`m_uvm_component_registry_internal(T,T) \ (macro 1)
`m_uvm_get_type_name_func(T) \ (macro 2)
注意:宏一般如下定义:`define AWUSER_VFID_RANGE 4:1
(a): 宏在编译时会被替换,出现AWUSER_VFID_RANGE的地方都会被文本替换成4:1;
(b): 宏只能写在一行,如果宏的内容过长,应该用反斜杠隔开便于阅读;
因此,当在class example_comp中通过`uvm_component_utils(example_comp)宏注册example_comp时,相当于把宏的展开平铺在class example_comp中。
(1): macro 1
`define m_uvm_component_registry_internal(T,S) \ \ typedef uvm_component_registry #(T,`"S`") type_id; \ \ static function type_id get_type(); \ // 相当于在class example_comp中定义了get_type()方法, return type_id::get(); \ // 用于获取XXXXX endfunction \ \ virtual function uvm_object_wrapper get_object_type(); \ // 相当于在class example_comp中定义了get_object_type()方法, return type_id::get(); \ // 用于获取XXXXX endfunction \
由于注册时传递进来的component类型不同,因此uvm提供了一个参数化的类:class uvm_component_registry #(type T, string Tname);然后根据用户注册进来的example_comp类,定义一个专属于example_comp的类,叫type_id;
结论:相当于在example_comp class下定义了一个uvm_component_registry #(example_comp, "example_comp")类,并命名为type_id;
注意只是定义了type_id这个class,并没有用这个class去声明任何变量;
另外虽然别的class在注册时也会定义type_id这个class,但不用担心命名冲突,因为都是在自己要注册的class中去定义的type_id;
为了弄清楚type_id这个类有哪些变量和方法,需要看参数化的类class uvm_component_registry #(type T, string Tname),如下:
class uvm_component_registry #(type T=uvm_component, string Tname="<unknown>") extends uvm_object_wrapper; typedef uvm_component_registry #(T,Tname) this_type; const static string type_name = Tname; virtual function string get_type_name(); // 相当于在class example_comp中定义了get_type_name()方法, return type_name; // 用于获取XXXXX endfunction local static this_type me = get(); // 最关键的一行,由于me是静态变量,因此在编译时就通过get()方法初始化me; static function this_type get(); if(me == null) begin uvm_coreservice_t cs = uvm_coreservice_t::get(); uvm_factory factory=cs.get_factory(); me = new; factory.register(me); end return me; endfunction static function T create(string name, uvm_component parent, string contxt=""); uvm_object obj; uvm_coreservice_t cs = uvm_coreservice_t::get(); uvm_factory factory=cs.get_factory(); if (contxt == "" && parent != null) contxt = parent.get_full_name(); obj = factory.create_component_by_type(get(), contxt, name, parent); if (!$cast(create, obj)) begin ...... end endfunction
如上所述,我们在class example_comp中定义了它专属的type_id类,也就是class uvm_component_registry #(example_comp, "example_comp");
这里专属的type_id类的特殊之处在于:申明了一个类型为本身的静态对象句柄(me),通过静态函数(get())来创建唯一的一个对象,并将其赋值给对象句柄(me);同时还将构造函数申明为私有成员函数,防止外部调用构造函数来创建对象。因此,此对象句柄(me)就可以作为访问此单例的的一个窗口,也可以用静态函数返回值来访问此单例的对象句柄。
由于me是type_id类型的静态变量,因此在编译时,就会将me初始化,因此自然会调用get()方法,调用get()方法主要做两件事:
(1): 创建type_id类型的对象实例,句柄为me;
(2): 将创建的对象注册到factory中;正如【2.0 factory的基础知识】中的分析,所谓注册就是将专属的type_id放入到m_type_names[string]这个关联数组中;
因此在compile阶段,准确的说是在elaborate阶段,VCS工具就已经为注册到factory的类创建了专属的type_id实例,并将该专属的type_id实例注册到了factory中。因此在VCS工具中,在0时刻就能够看到m_type_names[string]中已经存放了注册类的信息。
到这里,我们已经将class example_comp注册到了factory中,注册的作用在哪里体现呢?这个问题在【2.3 对象创建】将会具体分析注册时如何影响对象创建的。
另外一个值得思考的问题是:我们将class example_comp的专属类type_id注册到m_type_names[string],而不是将example_comp直接注册到m_type_names[string]中,那么type_id和example_comp的关系像什么呢?
type_id相当于是一个生产example_comp实例的模具,由于type_id是一个参数化的类,因此必须实例化后才能携带example_comp的信息;通俗的理解就是每个注册进来的类的"大小"都不一样,因此要先制造出这个模具(专属的type_id实例),然后才能用这个模具去生产产品(example_comp实例);所以每一个专属的type_id都会实例化,然后放到m_type_names[string]中;
有了这个模具(type_id实例),在build_phase阶段,我们只需要按这个模具的开关(调用create(...)),既可以生产example_comp实例;
(2): macro 2
`define m_uvm_get_type_name_func(T) const static string type_name = `"T`"; virtual function string get_type_name(); return type_name; endfunction
由于会在class example_comp extends uvm_component中使用宏`uvm_component_utils(example_comp),因此相当于在class example_comp中定义了上述type_name变量和get_type_name()方法,通过在class example_comp内部或者实例上调用get_type_name()方法,可以获取到字符串型的类名:"example_comp";
2.2 在例化前设置覆盖对象和类型(可选)
系统中存在一个uvm_default_factory类型的全局变量factory,factory提供了很多方法来完成覆盖(override);
通过类名完成类的覆盖
factory.set_type_override_by_type(uvm_object_wrapper original_type, //被重载的类型,例如axi_normal_monitor::get_type() uvm_object_wrapper override_type, //要重载的类型,例如axi_abnorm_monitor::get_type() bit replace = 1);
通过类名完成实例的覆盖 factory.set_inst_override_by_type(uvm_object_wrapper original_type, uvm_object_wrapper override_type, string full_inst_path);
通过实例名完成类的覆盖
factory.set_type_override_by_name(string original_type_name, string override_type_name, bit replace = 1); 通过实例名完成实例的覆盖 factory.set_inst_override_by_name(string original_type_name, string override_type_name, bit full_inst_path);
不同方法的区别在于,既可以通过类名完成覆盖,也可以通过实例名完成覆盖。
作为应用,我们无需关心这些方法的具体实现,但作为源码分析,这里以factory.set_type_override_by_type为例说明factory是如何实现重载的,如下:
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; // 比较original_type和override_type的类型是否相同,如果相同则没有必要override; if (original_type == override_type) begin if (original_type.get_type_name() == "" || original_type.get_type_name() == "<unknown>") uvm_report_warning("TYPDUP", {"Original and override type ", "arguments are identical"}, UVM_NONE); else uvm_report_warning("TYPDUP", {"Original and override type ", "arguments are identical: ", original_type.get_type_name()}, UVM_NONE); end // 检查original_type和override_type是否已经注册到factory中,如果没有则在这里注册; if (!m_types.exists(original_type)) register(original_type); if (!m_types.exists(override_type)) register(override_type); // check for existing type override foreach (m_type_overrides[index]) begin if (m_type_overrides[index].orig_type == original_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 string msg; msg = {"Original object type '",original_type.get_type_name(), "' already registered to produce '", m_type_overrides[index].ovrd_type_name,"'"}; if (!replace) begin msg = {msg, ". Set 'replace' argument to replace the existing entry."}; uvm_report_info("TPREGD", msg, UVM_MEDIUM); return; end msg = {msg, ". Replacing with override to produce type '", override_type.get_type_name(),"'."}; uvm_report_info("TPREGR", msg, UVM_MEDIUM); replaced = 1; // class uvm_factory_override类有四个成员变量: m_type_overrides[index].orig_type = original_type; // uvm_object_wrapper orig_type; m_type_overrides[index].orig_type_name = original_type.get_type_name(); // string orig_type_name; m_type_overrides[index].ovrd_type = override_type; // uvm_object_wrapper ovrd_type; m_type_overrides[index].ovrd_type_name = override_type.get_type_name(); // string ovrd_type_name; end end // make a new entry if (!replaced) begin // 如果original_type之前没被覆盖过: uvm_factory_override override; override = 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_default_factory下属的队列:uvm_factory_override m_type_overrides[$],该队列用于存放uvm_factory_override类型对象的句柄,每一个对象/句柄都保存了原始类型和覆盖类型的信息;
因此当通过set_type_override_by_type(original_type, override_type)方法实现覆盖时,首先会遍历m_type_overrides[$],检查original_type是否被覆盖过:
如果original_type有被覆盖过,现在需要用override_type对original_type再次进行覆盖,那么会将最新的original_type信息和override_type信息更新到m_type_overrides[$]中;
如果original_type没被覆盖过,则新创建一个override条目,存放当前的original_type信息和override_type信息,并将新建的override条目push到m_type_overrides[$]中;
因此覆盖完成后,original_type信息和override_type信息就一定能在m_type_overrides[$]中找到,在之后创建对象时,也一定会用到m_type_overrides[$]里面的覆盖信息。
2.3 对象创建
(1): 对于component类型,有如下两种方法创建对象:
c1 = comp_class_name::type_id::create(string_name, uvm_component_parent) c1 = factory.create_component_by_name("requested_class", this.get_full_name(), name)
如上所述,第一种方法在调用type_id::create(...)创建对象时,本质上也是调用factory的create_component_by_name(...)方法;
到这里,我们分析了注册,覆盖和创建的细节。当我们在build_phase阶段通过example_comp::type_id::create("example_comp", this)创建对象时:第一个::是类型的域索引,找到与example_comp相对应的type_id实例;第二个::是静态方法的域索引,找到静态方法create(...);也就是说VCS首先会去找example_comp自己专属的代理类实例type_id,然后调用type_id的create(....)方法,因此关注上面的静态方法create(),在调用create()方法时:
(a): 获取全局唯一的uvm_factory的句柄factory;
(b): 通过factory的create_component_by_type(get(), contxt, name, parent)方法创建对象,并将对象句柄返回给create()方法;create()方法有四个参数:
(b.1): get(),调用get()将创建类型为type_id的对象,句柄是me;并将对象me注册到factory中;
(d):
(2): 对于object类型,有如下两种方法创建对象:
o1 = obj_class_name::type_id::create(string_name) o1 = factory.create_object_by_name("requested_class", this.get_full_name(), name)
说明同上。
3 常用的UVM重载
和SystemVerilog的重载类似,重载的目的都是为了在定义上层通用代码框架时留给底层用户足够多的接口函数,使得用户不需要修改上层代通用码框架,就可以将底层自己的代码参与到上层中,这符合方法学设计的初衷;
对于SystemVerilog,在定义上层通用代码框架时,会预先在上层通用代码框架将method A声明为virtual,然后再使用method A,从而搭建完整的上层通用代码框架;用户在使用时,由于上层的virtual声明,用户只需实现自己的method A,不用做其他任何事情,用户的method A就会替代上层的virtual method A,参与到上层通用代码框架中;
对于uvm,由于factory机制,uvm提供了更加丰富的重载方式,但目的都是一样:用户不需要修改上层代通用码框架,就可以将底层自己的代码参与到上层中;所谓上册与下层,只是笼统的说法,上层既可以只uvm方法学,也可以指testbench中固定不变的代码结构;
另外需要注意的是,为了保证重载成功,重载类需要继承与待重载的类。
3.1 重载transaction
class normal_sequence extends uvm_sequence #(my_transaction); `uvm_object_utils(normal_sequence) my_transaction m_trans; virtual task body(); repeat(10) begin `uvm_do(m_trans); end #100; endtask endclass -------------------------------------------------------------------------------------- function void my_case0::build_phase(uvm_phase phase);
...... super.build_phase(phase); factory.set_type_override_by_type(my_transaction::get_type(), new_transaction::get_type());
...... endfunction
场景:在normal_sequence中默认使用的trans是my_transaction,在某些场景下,我们仅想扩展my_transaction的功能,但不想重新写一个sequence;
用法:有了factory机制后,我们只需要在my_transaction的基础上继承一个新的new_transaction,然后在test层的build_phase中通过factory提供的方法实现重载;
3.2 重载sequence
class main_sequence extends uvm_sequence #(my_transacton); ...... virtual task body(); normal_sequence seq; repeat(10) begin `uvm_do(seq) end endtask ...... endclass -------------------------------------------------------------------------------------- function void my_case0::build_phase(uvm_phase phase); ...... super.build_phase(phase); factory.set_type_override_by_type(normal_sequence::get_type(), abnormal_sequence::get_type()); ...... endfunction
场景:在main_sequence中默认使用的sequence是normal_sequence,在某些场景下,我们仅想扩展normal_sequence的功能,但不想改变main_sequence中的代码;
用法:有了factory机制后,我们只需要在normal_sequence的基础上继承一个新的abnormal_sequence,然后在test层的build_phase中通过factory提供的方法实现重载;
3.3 重载component
包括driver,monitor,scoreboard,refmodel等都可以重载,以scoreboard为例:
function void env::build_phase(uvm_phase phase); ...... normal_scoreboard sbx; sbx = normal_scoreboard::type_id::create("sbx"); ...... endfunction ------------------------------------------------------------------------------------ function void my_case0::build_phase(uvm_phase); ...... factory.set_type_override_by_type(normal_scoreboard::get_type(), abnormal_scoreboard::get_type()); ...... endfunction
场景:在env中默认使用的scoreboard是normal_scoreboard,在某些场景下,比如使能的RTL的ECC feature,我们仅想扩展normal_scoreboard的功能,但不想改变env中的代码;
用法:有了factory机制后,我们只需要在normal_scoreboard的基础上继承一个新的abnormal_scoreboard,然后在test层的build_phase中通过factory提供的方法实现重载;
4 UVM对uvm_object的支持
to be continue......