Factory是UVM世界中承载着对象实例化和重载(override)等作用的重要机制。深入了解Factory机制的实现方式,有助于我们在实际工程中更好地应用和调试。本文介绍Factory实现方式的思路是,从应用代码入手,逐渐深入到Factory机制的相关源代码,从而剖析UVM的这套工厂模式。
01 Global Factory
UVM工厂的“模子”通过抽象类uvm_factory定义。uvm_factory中定义了一系列纯虚函数,主要包括以下几种功能:重载、创建、查找、调试等。这些函数根据参数可以分为基于名字(name-based)和基于类型(type-based)两类接口。也就是说,每一种相同的功能都有两种不同的实现方式,一种是基于名字的实现,一种是基于类型的实现。通过这些函数,就能大致看出UVM工厂的模样了。
这里建议首先使用基于类型的函数,因为存在的错误能够在编译阶段就暴露出来,但如果使用基于名字的函数接口,一些简单的错误都有可能被藏到函数被调用的时候才暴露出来。
UVM使用uvm_default_factory作为uvm_factory的默认实现,在uvm_default_factory中定义了用于记录重载信息的数据结构(如下图所示),并实现了在uvm_factory类中定义好的各个纯虚函数。
当重载功能的函数被调用,重载信息会被打包成uvm_factory_override,然后记录到上面图中的队列数据结构中。大致的过程是:当set_inst/type_override_by_name函数被调用时,都会首先通过m_type_names关联数组去找到对应的类型代理,如果成功映射到类型代理,就根据是set_inst还是set_type分别将重载信息记录到m_inst_override_queues和m_type_overrides中;如果没有成功映射,则根据type_name有没有通配符,将重载信息分别记录到m_wildcard_inst_overrides或m_inst_override_name_queues中。当set_inst/type_override_by_type函数被调用,这个过程会简单很多,重载信息会被直接记录到m_inst_override_queues或m_type_overrides队列中。
当创建功能的函数被调用,factory会首先通过查找功能的函数find_override_by_type/name去找到当前要创建的类型需不需要被override。这个过程就是去上图中记录重载信息的数据结构中查找,查找过程是个递归的过程,这个过程比较复杂,篇幅原因就不展开描述了,之后才返回真正需要被实例化的类型的代理。最后,由返回来的类型代理去new一个对象,才完成整个创建过程。
02 Registry
将新定义的uvm_object或者uvm_component类注册到Factory是使用Factory机制的前提。但为什么要注册以及“注册”到底指的是什么,可能是困扰很多新手的问题。解决这个问题,我们需要先把宏uvm_object_utils(class_name)或者uvm_component_utils (class_name)先展开来看看,到底做了些什么动作。这里以uvm_object_utilsz为例,uvm_component_utils宏的展开大同小异。
type_id
从上图可以看到,所谓的“注册到Factory”,首要的是在新定义的类里面定义了type_id。做个勘误,在Factory机制基础篇中提到的”type_id是静态成员”有误,type_id是一个类型的重命名。type_id通过typedef映射的类型是参数化uvm_object_registry#(packet, "packet"),这也是前面一直说的“类型代理”的原型。
每一个参数化的uvm_object_registry全局唯一例化,并且只有在第一次被访问的时候(即调用其get函数)才会被实例化,并同时通过调用default_factory的register函数来记录到工厂的类型队列中。换句话说也就是,并不是调用了宏就完成了注册,只有在当前类被实例化的时候,该参数化的uvm_object_registry才会被真正的注册到工厂中。
此外,宏还提供了静态函数get_type,用来通过类名直接获得type_id句柄。get_type函数会被经常用到,比如在重载类型的时候,通常是通过该函数来获得type_id句柄,然后将其作为type-based重载函数的参数。
type_name
type_name变量和get_type_name函数的定义,只有在使用uvm_object_utils(class_name)或uvm_component_utils(class_name)宏的时候会被自动加入,在使用uvm_object_param_utils(class_name #(arg1, arg2))或uvm_component_param_utils(class_name #(arg1, arg2))宏的时候是不会被自动加入的。我认为可能是因为带了参数之后,这个type_name变得不好统一和定义吧,不过UVM是允许用户自己去为参数化类加这一部分跟type_name相关的代码的(可参考Factory基础篇的代码示例)。
type_name是一个常量字符串,不可被修改,且是静态成员。type_name的值默认就是类名。通过get_type_name函数可以获得type_name,可以被用来作为name-based重载函数的参数。
03 Proxy
理顺实例化的过程,除了上面介绍的全局工厂和宏展开,还需要了解类型代理是如何工作的。类型代理对应到两个类,uvm_object_registry和uvm_component_registry,它们都衍生自uvm_object_wrapper。本节以uvm_object_registry为例展开介绍。
uvm_object_registry有两个主要的函数,分别是:
virtual function uvm_object create_object (string name="");
static function T create (string name="", uvm_component parent=null, string co
似曾相识,这里的create函数就是在实际应用中我们调用class_name::type_id::create来做实例化的那个create。type_id就是第二小节中介绍,通过宏引入的对参数化后的uvm_obejct_registry的重命名。当create被调用,它会通过uvm_coreservice获取全局工厂的句柄(即第一节介绍的uvm_default_factory),然后调用工厂的创建函数create_object_by_type。这样,就相当于是将创建对象的订单提交到了工厂,由工厂去查找当前要实例化的类是否被重载(回到第一小节),最终由工厂找到的真正要实例化的类型代理的create_object函数完成实例化。
简而言之,整个利用Factory机制实例化对象的过程实际上是在代理和工厂之间绕了一圈,我们通过代理提交实例化请求,代理会将该请求发给工厂,工厂找到重载类型的代理,再由该代理完成实例化,最后返回对象。
原作者:JKZHAN