第五段:uvm_factory的分析(二次更新版)

 

 

初版基础

1)基础用法:

a. 注册

      当定义一个类的时候,它的类型必须要注册,UVM已经提供了专用的宏。

      `uvm_component_utils(class_type_name)

      `uvm_component_param_utils(class_type_name #(params))

      `uvm_object_utils(class_type_name)

      `uvm_object_param_utils(class_type_name #(params))

      这四个宏中两个是为参数化的类准备的,另外两个是为非参数化的类注册用的。

b. 实例化对象

      在对component或object型对象进行实例化的时候要使用静态方法create(),不能采用new()去实例化。也就是要用如下这个非常奇怪的实例化格式:

object_name = class_type::type_id::create("object_name",this);

c. 重载override

      最后就是在需要override的时候,按照类型或者名称去override原来的对象。

      set_inst_override_by_type(original_type, override_type, full_inst_path )

      set_inst_override_by_name(original_type_name, override_type_name, full_inst_path )

                       

                       

2)项目升级用法:与上一致。

3)底层原理分析:

      uvm_factory用于制造(创建)UVM对象和组件。在给定的模拟中只有工厂的一个实例。用户定义的对象和组件类型通过typedef或宏调用在工厂注册,如uvm_default_factory::Usage中解释的那样。工厂为用户定义的对象和组件生成并存储轻量级代理:对象的uvm_object_registry #(T,Tname)和组件的uvm_component_registry #(T,Tname)。每个代理只知道如何创建它所表示的对象或组件的实例,因此在内存使用方面非常高效。当用户请求一个新对象或组件从工厂(例如vm_factory:: create_object_by_type),工厂将决定什么类型的对象列阿特根据其配置,然后问这种特点的代理来创建一个实例,这是返回给用户。

                       

      工厂使用的代理组件和对象类。为了避免为注册的每个组件和对象创建实例的开销,工厂持有轻量级包装器或代理。当对新对象发出请求时,工厂调用代理来创建它表示的对象。

                       

                       

                       

      向工厂注册给定的代理对象obj。代理对象是它所代表的组件或对象的轻量级替代品。当工厂需要创建给定类型的对象时,它调用代理的create_object或create_component方法来完成此任务。在执行基于名称的操作时,工厂调用代理的get_type_name方法,以便在随后对create_component_by name(或者create_object_by_name)的调用中匹配requested_type_name参数。如果代理对象的get_type_name方法返回空字符串,那么基于名称的查找将被有效禁用。

      使用使用这个工厂包括三个基本操作:(相当于new+重载)

      a. 向工厂注册对象和组件类型

      b. 设计组件以使用工厂创建对象或组件

      c. 在组件内部和外部使用类型和实例重写配置工厂。

二次补充:(参考了知乎上大神的知识)

      最近工作的杂事很多,感觉很久没有更新了,结果看着发现自己过去对factory的理解太肤浅,索性专门抽出时间好好研究了一下。(旧文章我有空的时候还会修改一下)

      文章正文开始:

      从根上讲,UVM的factory机制要从编程的设计模式开始理解,uvm这部分源代码用到了三种设计模式:代理,单例和工厂。具体看下图,红色箭头就是构造uvm_object的过程。(图片来自知乎花火同学)

      流程是这样的:uvm先通过registry这个代理,来调用factory,factory搞了个关联数组来记录信息,最后通过记录的信息来把我们真正要的object实例化出来。

      说得更通俗一点就是:本来生个孩子,只要爹妈随地配合一下就生出来了。现在我们为了让生孩子这个过程规范化,安排了个人来组织这场活动,这个组织者就是registry。组织者把爹妈领进工厂factory,在工厂factory里找张表记录一下将出生的孩子的信息,这张表就是关联数组。记录后,根据这张表,安排爹妈完成这个活动。

      接下来拆分开:

      首先:先谈proxy。代理说白了就是,在我们真正要建的类(class A)的上面,套个套子,这个套子是个新的类,也就是我们所说的代理,有了代理以后,我们就把原先直接对类A的操作,变成隔着代理类这个套子来做操作。

      在UVM里面 ,真正要实例化的那些uvm_object 和uvm_component,在这个过程中用到了一种uvm_registry registry#(T)类,就是proxy,一个object对应一个registry#(T)。

      补充一句,uvm_registry也就是我们在实例化object的时候调用语句里的那个type_id。

      为了更好地理解,先来个简单的代码例子来说明下代理模式:

      我们真正要操作的类叫RealSubject,我们通过Proxy类来操作它。用Proxy的好处是可以在操作RealSubject的时候,可以调用一些其它打辅助的类,比如在这个例子里面我们调用了叫Record的类,用来记录操作RealSubject的系统时间。如图:

补充源码:

#pragma once

#include <time.h>
using namespace std;

class Subject
{
public:
	Subject() {};
};

class RealSubject :public Subject
{
public:
	RealSubject() {}
	void create_method_in_RealSubject() {
		cout << "call func() in RealSubject" << endl;
	}
};

class Record
{
public:
	Record() {}
	void getTime() {
		time_t now = time(0);
		char* str_time = ctime(&now);
		cout << "current time:" << str_time;//c_time之后自带了换行,不用再<<endl;了
	}
};

// 代理类
class Proxy :public Subject
{
private:
	RealSubject* realSubject;
	Record* record;
public:
	Proxy() {
		realSubject = new RealSubject();//在proxy的构造函数里面构造realSubject和Record
		record = new Record();
	}
	void create_method_in_proxy() {
		record->getTime();//在proxy中构造realSubject前后使用record类的getTime记录时间
		realSubject->create_method_in_RealSubject();
		record->getTime();
	}

};
#include <iostream>
#include "subject.h"

int main()
{
	Proxy proxy;
	proxy.create_method_in_proxy();

	system("pause");
	return 0;
}

      接下来是单例模式

      之所以要讲这个是因为UVM的registry是单例模式构造的,也就是registry在整个工程里面只有一个。这个部分理解起来比较简单,单例模式的那些通过锁来保证线程安全的细节我们先不关心。

      我们现在只通过一个最简单的例子来理解一下。下面这个例子是懒汉模式的,懒汉就是不用单例的时候不构造,等到要用的时候才构造,与之相对的是饿汉,就是不管用不用得上,都先把这个单例构造出来。

      UVM_registry源代码里面的实现也是懒汉模式的。

补充源码:singleton.h

#pragma once

#include <iostream>
using namespace std;

class singleton {
private:
    singleton() {
        cout << "test" << endl;
    };
    static singleton* ptr;// = NULL;//需要在cpp里面声明这个ptr = NULL;不然会报错,并且不能在头文件这里声明
public:
    static singleton* get() {
        if (ptr == NULL) {
            ptr = new singleton();
            return ptr;
        }
        else
            return ptr;
    };
    void func();
};

补充源码:singleton.cpp

#include "singleton.h"

singleton* singleton::ptr = NULL;

补充源码:main.cpp

#include "singleton.h"

int main() {

    singleton* s0 = singleton::get();
    return 0;
}

      接下来是我用上面俩设计模式的方法,模拟了一个uvm里面通过registry来构造object的过程。 

补充源码:main.app    

#include "uvm_proxypattern_pseudocode.h"

int main(){
    objectwrapper<obj_d1> obj_wrapper1; //使用proxy模式,只造出个wrapper,而不是先造obj
    
    map<string, objectwrapper<obj_d1>> map1; // 用map来模仿sv里的联合数组
    auto temp_pr = make_pair("obj_d1",obj_wrapper1);
    auto map_ptr = map1.insert(temp_pr);//往这个联合数组里面放东西,key放字符串"obj_d1",内容放刚才造出来的wrapper
    map<string, objectwrapper<obj_d1>> ::iterator itr;
    itr = map1.find("obj_d1");
    if (itr != map1.end()) //在联合数组里面找到我们要造的类名"obj_d1"
    {
        (map1.at("obj_d1")).createobj(); //然后根据这个类名来在wrapper中真正地造出我们要的类obj_d1
        
    }


    return 0;
}

补充源码:object.h  

#pragma once

#include <iostream>
#include <map>
using namespace std;

class object {
public:
    virtual void print() {};

};

class obj_d1 :public object {
public:
    obj_d1() {//构造函数
        cout << "use user-defined constructor of obj_d1" << endl;
    };
    virtual void print() {
        cout << "obj_d1 instance already constructed" << endl;
    };

};


template<typename T1>
class objectproxy {
public:
    virtual T1 createobj() {
        T1 t1;
        return t1;
    };

};

template<typename T1>
class objectwrapper : public objectproxy<T1>{
public:
    virtual T1 createobj() {
        cout << "createobj of ojectwrapper" << endl;
        T1 t1;
        t1.print();
        return t1;
    };
    
};

      我们要做的事情是这样的(其中object 和 objectrproxy模拟uvm里面的层次结构而已,可以忽略)

      我们要弄出一个objectwrapper用来作为obj_d1的代理,然后我们要搞出一张关联数组,key存要实例化的类的名字,内容存objectwrapper对象。

      构建obj_d1是这么做的,先在关联数组里面查看key,在key中找到"obj_d1"之后,调用这个key对应的wrapper对象的createobj函数,在createobj函数里面实现对obj_d1的具体实例化。

      其实上面这个流程基本上模拟了uvm的factory里面的实现过程,还存在比较大的几个不同在于:

      1. 上面的registry没有用单例做,其实是可以做的,如下:

template<typename T1>
class objectregistry : public objectproxy<T1> {
private:
    objectregistry() {
        T1 t1;
        cout << "create T1 in objectregistry" << endl;
    };
    static inline objectregistry<T1>* me = NULL; //  这里简直了,使用C++17就能用inline 就可以将static的指针在类内指定成NULL 这种东西也太偏了

public:
    static objectregistry<T1>* get() {//get()通过单例模式的懒汉方法来造个registry<>的单例
        if (me == NULL)
        {  
            me = new objectregistry();
            cout << "造好单例objectregistry" << endl;
            return me;
        }
        else
        {
            return me;
        }
    };

    static T1 createObj() {
        T1 t_h;
        return t_h;
    };//准备用来替代上面的new
};

      之所以我没有在上面的例子直接这么用单例,是因为C++对模板类声明指针是比较麻烦的,它需要用这样的句子来实现模板类的指针:

static inline objectregistry<T1>* me = NULL;

      并且要选择用C++17的编译器才能实现。

      反观sv,要往一个类里传类就方便多了,只要#(T) 就可以了。这大概可以算是sv少有的居然会比C++方便的地方了吧。(毕竟java用的还是这么广泛的嘛,嘻嘻)

      2. 在真正的uvm源代码里,uvm是通过调用uvm_factory来构造和注册那张表的。通过一层uvm_factory的好处是可以利用uvm_factory的内部函数来实现对这张表的替换等操作,比如我们常见的uvm里面对各种组件的override就是这么实现,override的根本是通过factory实现了对关联数组的修改。

      好吧,能想到的大概就这些了,干完收工!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱吃辣椒的年糕

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值