在线咨询
eetop公众号 创芯大讲堂 创芯人才网
切换到宽版

EETOP 创芯网论坛 (原名:电子顶级开发网)

手机号码,快捷登录

手机号码,快捷登录

找回密码

  登录   注册  

快捷导航
搜帖子
楼主: asic_wang

[原创] UVM/OVM中的factory---------个人总结

[复制链接]
 楼主| 发表于 2014-7-15 19:51:52 | 显示全部楼层
4 factory的本质
4.1 软件工程设计模式中的factory的定义
    对“四人帮”的《设计模式》书中工厂模式的定义,大意是:工厂模式定义了一个接口,这个接口是为创建对象而存在,而且它可以让对象的子类来决定对象的创建。工厂方法把对象的实例化推迟到自己的子类中。
    如果不结合实例,“四人帮”的书总是难以理解。就我个人而言,这本书看过几遍,但是看不懂,对书中的理解总是在碰到实际的例子的时候再去看看是否和书中的某个模式能对上号。我觉得如果软件工程方面的实践过少、没有软件架构设计的经历,这本书大部分东西完全看不懂;好在其中有几个最常用的模式相比较而言还是容易一点,factory模式就是其中之一。
 楼主| 发表于 2014-7-15 20:15:40 | 显示全部楼层
以下是我能想到factory的实例。前提如下:
    1)假设有一个餐馆提供快餐服务,大家可以通过订餐函数来得到一份盒饭
    2)目前餐馆能提供鸡肉套餐、鸭肉套餐和牛肉套餐
    3)每种套餐都由“取材”,“清洗”,“烹饪”,“起锅打包”四部组成,只有“取材”步骤依赖于套餐名;且每种材料都会定义“清洗”,“烹饪”,“起锅打包” 这三个步骤
    4)取材的结果是“鸡肉”对象,“鸭肉”对象,“牛肉”对象,它们都是以类型“材料”为基类
    为了服务客户,餐馆类最开始可能是这个样子的:
    class 餐馆 ;  ///类型名字为 餐馆
         套餐 function 订餐(string 套餐名);////函数返回值:盒饭,函数名:订餐,函数入参:套餐名
               材料  base ; /////定义一个材料句柄,用于hold取材步骤的结果
               ////step 1:取材
               case(套餐名)
                     “鸡肉”:base = new 鸡肉 ;
                     “鸭肉”   base = new 鸭肉 ;
                     “牛肉”   base = new 牛肉 ;
               endcase
               ////step 2:清洗
               base.清洗();
               ////step 3:烹饪
               base.烹饪();
               ////step 4:起锅打包
               base.打包();
               return base ;///经过上述处理就变成套餐了
         endfunction
     endclass

     客户订一份鸡肉餐过程如下:
     1)找个一个餐馆,也就是get a inst of  “餐馆”:餐馆_inst
     2)餐馆_inst.订餐(“鸡肉”)
 楼主| 发表于 2014-7-15 20:25:29 | 显示全部楼层
上述的实例将会产生factory模式的需求。
      可以想想,用 设计模式 的一些术语来列一下上述这种实现有如下一些缺点:
      1)在对实现编程,而不是接口编程
           因为取材过程需要调用new,而且使用new返回的特定对象,这就意味着在对实现在编程
      2)对扩展封闭,对修改开放
           如果餐馆新增一个鱼套餐或者因为牛肉太贵不提供牛肉套餐了,你是不是要修改case语句?
 楼主| 发表于 2014-7-15 20:39:40 | 显示全部楼层
本帖最后由 asic_wang 于 2014-7-15 20:49 编辑

对策:使用面向对象的基本对策,即找到变化的部分,将其封装,留下固定部分。
     上述例子的变化部分不言而喻,就是那个取材部分的case语句!事实上,在使用具有面向对象特性的语言进行设计的时候,case语句往往意味着你这里的东西是会带来扩展性、维护性问题的。
      改进将是直接的,把取材部分拧出来,然后封装成一个class,我们这个类叫做工厂,你没有看错,工厂的前身就是如此简单和直接。
      第一次改进后的效果:
      class 餐馆 ;  ///类型名字为 餐馆
         factory   factory_inst; /////假设有一个专门的取材工厂
         套餐 function 订餐(string 套餐名);////函数返回值:盒饭,函数名:订餐,函数入参:套餐名
               材料  base ; /////定义一个材料句柄,用于hold取材步骤的结果
               ////step 1:取材
               base = factory_inst.create(套餐名);
               ////step 2:清洗
               base.清洗();
               ////step 3:烹饪
               base.烹饪();
               ////step 4:起锅打包
               base.打包();
               return base ;///经过上述处理就变成套餐了
         endfunction
     endclass

     class factory ;////新增的取材工厂
          材料  function create(套餐名);
               材料  base ;
               case(套餐名)
                     “鸡肉”:base = new 鸡肉 ;
                     “鸭肉”   base = new 鸭肉 ;
                     “牛肉”   base = new 牛肉 ;
               endcase
               return base ;
          endfunction
     endclass
     
     这种改变看似仅仅挪动了case语句,但是它带来的好处就是把修改点集中在factory中,让不会改变的固定部分变得简洁。
     可能你会说,这也叫工厂模式?如此而已?我想说,这个目前来说只能叫一个好的面向对象编程习惯,还不能称之为一个模式,但正是这个习惯造就了以下更加完美的factory。
 楼主| 发表于 2014-7-15 21:10:43 | 显示全部楼层
本帖最后由 asic_wang 于 2014-7-15 22:41 编辑

显然,问题依然存在,那就是class factory中的case语句。针对接口编程的规则意味着我们应该如下改变factory的create函数:
    class factory ;////鸡肉取材工厂
          材料  function create(套餐名);
               根据套餐名来自动生成一个所需的对象 ;
          endfunction
     endclass
     
     这样的话,case语句没有了,但是create函数中“根据套餐名来自动生成一个所需的对象”该如何做才能达到case语句的效果呢?
     换个角度,类自己知道自己会create成什么样子。什么意思?意思是鸡肉工厂一定知道自己create成鸡肉,而不是牛肉或鸭肉!话到这里,思路清晰了,没错那就是用继承机制来替换case语句的效果。
     第二次改进的效果 :factory模式的第一种变体
      class 餐馆 ;  ///类型名字为 餐馆
         factory   factory_inst; /////假设有一个专门的取材工厂
         套餐 function 订餐();////函数返回值:盒饭,函数名:订餐
               材料  base ; /////定义一个材料句柄,用于hold取材步骤的结果
               ////step 1:取材
               base = factory_inst.create();
               ////step 2:清洗
               base.清洗();
               ////step 3:烹饪
               base.烹饪();
               ////step 4:起锅打包
               base.打包();
               return base ;///经过上述处理就变成套餐了
         endfunction
     endclass

     virtual class factory ;////定义取材工厂的接口
          pure 材料  function create();
     endclass     

     class 鸡肉factory extends factory;
          材料  function create();
               return 鸡肉;
          endfunction
     endclass

     class 鸭肉factory extends factory;
          材料  function create();
               return 鸭肉;
          endfunction
     endclass

     class 牛肉factory extends factory;
          材料  function create();
               return 牛肉;
          endfunction
     endclass

     如此一来,客户的订餐流程变成了
     1)找个餐馆,告诉你想要鸡肉套餐
     2)餐馆工作人员去取材工厂如菜市场取来鸡肉
     3)餐馆完成套餐递给你
     persudo code就是:
     step1:餐馆_inst = new 餐馆 ;
     step2:chicken_factory = new 鸡肉factory ;
     step3:餐馆_inst.factory_inst = chicken_factory ;
     step4:  餐馆_inst.订餐();

     来看看现在是改进点:
     1)餐馆的订餐函数不用再改了,是在对factory的create接口编程
     2)增加一个鱼套餐;ok没问题:
          class 鱼factory extends factory ;
              材料  function create();
                  return 鱼;
              endfunction
          endclass
     persudo code就是:
     step1:餐馆_inst = new 餐馆 ;
     step2:chicken_factory = new 鱼factory ;
     step3:餐馆_inst.factory_inst = chicken_factory ;
     step4:  餐馆_inst.订餐();

     减少菜品更没问题,不用就是了。
 楼主| 发表于 2014-7-15 21:21:34 | 显示全部楼层
上述订餐的4个step中,关键一步就是第step2:
     persudo code:
     step1:餐馆_inst = new 餐馆 ;
     step2:chicken_factory = new 鱼factory或者鸭肉factory或者鸡肉factory或者牛肉factory ;
     step3:餐馆_inst.factory_inst = chicken_factory ;
     step4:  餐馆_inst.订餐();

     就是这一步把工厂模式的定义中的“把对象的实例化推迟到自己的子类中”这句话体现的淋漓尽致!
     顺便提一句,在我们做ASIC verification时,上述的这4步订餐步骤往往对应着testcase中的configure的某些部分。
 楼主| 发表于 2014-7-15 21:37:26 | 显示全部楼层
本帖最后由 asic_wang 于 2014-7-15 21:39 编辑

还有没有别的方法可以达到同样的效果,且看factory模式的第二种变体
      我们想想,除了用class可以封装东西,还有什么可以?不要忘记我们在学习C语言时的一个好的编程习惯:用函数封装功能点或者变化点!很容易在学习了面向对象语言之后,什么东西都想着用继承来搞定,不要忘记了函数也是一个利器!
      那我们就尝试着用函数来封装变化点吧。
      class 餐馆 ;  ///类型名字为 餐馆
         套餐 function 订餐(套餐名);////函数返回值:盒饭,函数名:订餐,函数入参:套餐名
               材料  base ; /////定义一个材料句柄,用于hold取材步骤的结果
               ////step 1:取材
               base = create(套餐名);///这次用自己的一个函数来封装自己的变化点
               ////step 2:清洗
               base.清洗();
               ////step 3:烹饪
               base.烹饪();
               ////step 4:起锅打包
               base.打包();
               return base ;///经过上述处理就变成套餐了
         endfunction

         材料  function create(套餐名);
               材料  base ;
               case(套餐名)
                     “鸡肉”:base = new 鸡肉 ;
                     “鸭肉”   base = new 鸭肉 ;
                     “牛肉”   base = new 牛肉 ;
               endcase
               return base ;
          endfunction

     endclass

     显然,我们遇到了需要去除case语言影响的同样难题!解决方案很简单,既然是同样的难题那就用同样的方法:继承!
     用继承改进后的factory模式的第二种变体:
     virtual class 餐馆
         套餐 function 订餐();////函数返回值:盒饭,函数名:订餐
               材料  base ; /////定义一个材料句柄,用于hold取材步骤的结果
               ////step 1:取材
               base = create();///这次用自己的一个函数来封装自己的变化点
               ////step 2:清洗
               base.清洗();
               ////step 3:烹饪
               base.烹饪();
               ////step 4:起锅打包
               base.打包();
               return base ;///经过上述处理就变成套餐了
         endfunction

         pure 材料  function create(套餐名);
     endclass   
     
     class 鸡肉餐馆 extends 餐馆;
          材料  function create();
                  return  “鸡肉” ;
          endfunction   
     endclass

     class 鸭肉餐馆 extends 餐馆;
          材料  function create();
                  return  “鸭肉” ;
          endfunction   
     endclass

     class 牛肉餐馆 extends 餐馆;
          材料  function create();
                  return  “牛肉” ;
          endfunction   
     endclass

     对应的订餐perdudo code就是:
     step1:餐馆_inst = new 鸡肉餐馆 ;
     step2:  餐馆_inst.订餐();
 楼主| 发表于 2014-7-15 21:42:31 | 显示全部楼层
本帖最后由 asic_wang 于 2014-7-15 21:45 编辑

对于上述两种变体,第一种变体就好比说餐馆只有一家,有很多种食材市场,你需要挑一个食材市场得到食材,餐馆只负责帮你加工;而第二种变体就好比说有很多家不同种类的餐馆,你主动挑一家。
发表于 2014-7-15 22:30:56 | 显示全部楼层
这段似乎是以比喻来讲更生动,其实我倒觉得复杂化了。比喻宜于短小精悍,这么长的一大段,看到一半以后已经记不清原始为什么要比喻成鸡鸭牛了。考虑到人的视觉主导性,应该尽量缩减到在一屏不滚屏的范围内说清楚一个知识点。要打比方也在一屏内开始一屏内结束。
-- 愚见
 楼主| 发表于 2014-7-15 22:37:20 | 显示全部楼层
用一个UVM中的例子来说明其对应的factory中的知识点:
     
      cpu_bus_driver = pcie_bus_driver::type_id::create();
      1)create就是暴露给用户的编程接口,相等于取材create函数;注意,不是用new函数了,这是根本所在,一用new函数那必定就是生成一个确确实实的cpu_bus_driver对象而不是pcie_bus_driver对象
      2)pcie_bus_driver就相当于鸡肉餐馆等,而cpu_bus_driver就相当于餐馆基类
      显然,UVM所用的factory模式和第二种变体较为接近。

      type_id的引入是对factory模式的再一次提升!为何如此说?
      再次以前面的那个餐馆实例来说:
      假设现在的餐馆提供的鸡肉套餐是土鸡肉做的,突然间土鸡肉出问题,找个替代品吧,用乌骨鸡肉代替。
      那么按照变体二的实现方式,需要新定义一个土鸡肉餐馆,我们画出一个继承树形结构来就是如下形式:
                                                   餐馆
                                                     |
                     ——————————— |————————
                    |                |               |                      |
              土鸡肉餐馆   乌骨鸡肉餐馆   鸭肉餐馆        牛肉餐馆
      我们知道土鸡和乌骨鸡都是鸡,所以我们希望树形结构如下可能更好:
                                                   餐馆
                                                     |
                                |——————  |————————
                                |                   |                      |
                           鸡肉餐馆         鸭肉餐馆        牛肉餐馆
                                |
                     |———————|   
                     |                    |
               土鸡肉餐馆       乌骨鸡肉餐馆

         从第一个树形图演变到第二个树形结构就是所谓的UVM中的override机制;举例来说,如果对应的factory中的override map的一个entry是:
             原类型             override类型
          “鸡肉餐馆”          “乌骨鸡肉餐馆”
          那么
          餐馆_handle = 鸡肉餐馆::type_id::create() 实际上相比前面的factory第二种变体形式多了一个步骤,那就是lookup override map这一步。
          而在UVM执行type_id::create()的时候,包含了以下几步的调用:
          1)调用type_id的create函数,也就是uvm_registry#(鸡肉餐馆)的create函数
          2)查询override map的“鸡肉餐馆”entry,得到“乌骨鸡餐馆”
          2)调用factory中的create_by_type(type = 乌骨鸡餐馆)
          3)调用乌骨鸡餐馆的create函数,这就是为什么每个uvm_object都要实现create函数的根本原因,这个uvm_object类及其派生类中的create函数的调用过程也很好的诠释了“把对象的实例化推迟到自己的子类中”

        由此可见,UVM中的factory机制比通常的factory机制更近一步,更加完美!
        顺便说一句,每个类都需要像uvm factory进行注册,而这个注册的思想多少借鉴了另一个设计模式:observer模式,这个模式也相对比较容易理解,就不在此记录了。
         
        至此,我个人心中的uvm factory的内部机制就是如此,欢迎大家贡献自己的看法,谢谢!
您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

站长推荐 上一条 /2 下一条


小黑屋| 手机版| 关于我们| 联系我们| 在线咨询| 隐私声明| EETOP 创芯网
( 京ICP备:10050787号 京公网安备:11010502037710 )

GMT+8, 2024-11-22 17:00 , Processed in 0.026330 second(s), 7 queries , Gzip On, Redis On.

eetop公众号 创芯大讲堂 创芯人才网
快速回复 返回顶部 返回列表