棋子 · 2022年07月25日

IC验证er一起学点设计模式(1)---单例模式

1 说两句大背景

众所周知,目前IC验证行业使用最主流的语言是SystemVerilog,这个语言有一个重要特点就是它是面向对象的语言。对于面向对象的语言,想要把代码写得更“牛逼”,其实就绕不开一个概念叫“设计模式”。

设计模式是个啥呢?

简单地说,就是在人类软件开发历史长河之中,经过一代代的程序员踩坑和迭代,总结提炼出的各种具体问题的代码解决方案

本文由“壹伴编辑器”提供技术支持

那一年,写代码的种种经验还一直是口口相传散落在江湖中;

突然一个名字叫“GOF”的4个人正式成团出道;

合著了一本名为《设计模式》的书;

在其中总结出了23种经典的软件设计模式;

至此开启了人类对软件设计模式探索的浪潮!!!

image.png

本文由“壹伴编辑器”提供技术支持

这23种经典的设计模式,可分为3大类,在此先列出他们的分类和名字,先转转混个眼熟:

1.创建型模式(5种,创建对象的不同玩法)

单例模式、建造者模式、工厂方法模式、抽象工厂模式、原型模式。

2.行为型模式(11种,类和对象交互和职责分配)

策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

3.结构型模式(7种,类和对象的组合的处理)

适配器模式、装饰模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

以SystemVerilog或其他面向对象的语言为主要武器的IC验证工程师们,对设计模式的学习理解,虽然不需要像真正的软件开发工程师理解的那么多,但是稍微的简单了解一些,也许就会对你的编码思想,代码风格产生不错的收益。

今天《IC验证er一起学点设计模式》这个系列,就一起来聊聊关于设计模式在验证中应用。当然对于初学者来说,本系列话题的文章只做了解即可,可以不用关注这么多。刚开始还是要先以把需求和功能顺利实现出来为第一目标,后面有精力再慢慢地提高代码的优雅性。

2 什么是单例模式

有了大背景,今天我们就直奔主题,先说说单例模式。

见名知义,“单例”的“单”,同“单身狗”的“单”,代表独一。

“单例”的“例”,代表实例,也就是类(非抽象类)创建的对象。即创建独一无二的实例的模式。

这个模式说白了,就是在一个软件系统中,针对某个类,保证系统中有且仅有一个实例,然后再提供你一个全局访问点,谁想用就通过访问点访问即可。

直接上代码,先认识一下:

image.png

单例模式实现方法其实有很多种,有细节区别,核心思想一样,我们以如上的一种实现聊聊足矣。

我们定义的jerry_singleton这个类,就是我们想做成单例模式的类。在这个类中,除了Jerry定义出的这些方法以外,可以自定义实现具体功能的核心资源代码,这里我们就直接省略了,我们主要看单例模式实现核心。

这段代码是有“套路”的,寥寥几行,内涵丰富。

关键点如下:

1、第3行,jerry_singleton这个类,声明了自己的句柄,叫m_inst。而且修饰为static和local(只能当前类访问,儿子都不让见)。

2、第4行,定义了get() 静态函数,这个函数是单例模式的核心,里面会做一个判断:如果m_inst(我们第1步中声明的jerry_singleton类的句柄)没有指向任何实例,那么我们就通过new创建一个给m_inst,把m_inst作为函数返回值。如果判断已经指向某个实例了(第1步中m_inst声明local和仅仅在此get函数中有new的动作,就保证了这里“某个”实例只可能是jerry_singleton这个类的实例),那就不需要再new了,直接返回即可。这个函数就保证了实例不会二次创建。

3、第6行,new函数,protected类型(只自己和儿子可见,外人不可见)。堵死了外界不用get()函数,而直接使用new函数创造实例的可能性。

4、本来,到上一步可以完成了,使用时候,只需要通过“jerry_singleton::get()”即可获得单实例,进而对其中定义的资源进行调用。

第18行是一种经常锦上添花的写法:

A、首先,一定要想清楚,J_SIG是什么?

   J_SIG不是一个字符串,它是一个jerry_singleton类型的句柄!

   这句话让这个句柄直接指向了那个通过get函数返回的实例。

B、然后,大家注意,这句话,写在class外边!也即是我们之前讲的$unit空间,可看作J_SIG全局可见,可依照名字直接调用!(前文回顾:人人都会用到,但是大部分人不清楚是什么的“神秘空间”

C、最后,神来之笔,顺手写上了一个const,把句柄J_SIG进行了常量限制,也就是只读变量。这样这个J_SIG句柄就不能再指向其他的对象了!

   这样简单的一句话下来,你便可以在验证平台任何地方(相同编译单元域),直接使用J_SIG这个句柄, 就可以方便访问到jerry_singleton类的唯一实例!

说到这里忍不住顺便来一嘴子,UVM源代码中有这么一句:

const uvm_root uvm_top = uvm_root::get();

是不是很熟悉呢!我们平时使用的“uvm_top”和今天的“J_SIG”一样的玩法。

看完例子3句话再总结下实现单例的核心:

类内部自new对象

new的时候保证唯一性

提供全局访问点

3 单例模式优劣

有人问了,这个玩意有什么用?

优秀的推销人员,一般先不会直接给你说产品有什么用,而是先甩出已经拿下的一堆大客户再说。

所以,你问我单例模式有什么用?我希望你先别问我,请你在UVM源代码目录下grep一下例如“::get()”或者 "function .* get();"等类似的关键词,只是随便追一追,看看是不是大把的单例模式?

看看他们怎么用,再来问我有没有用?

image.png

如同下面录屏视频中Jerry在uvm1.2源代码里grep的简单操作,可以大体看出例如uvm各种phase、uvm_reg_cbs和uvm_callback相关的一些类、uvm_resource_pool、uvm_root,等等都用了单例模式。

值得一提的是,在UVM1.2中更是单独设计了一个单例类uvm_coreserice_t,把uvm_factory、report_server等重要的共享的信息都带上。

image.png

哈哈,当然,我们也简单地聊聊单例有什么好处。

1.唯一性

验证平台中,有的信息我们希望是唯一存在的,比如想收集到不同组件的关键信息进行统一存放或管理,如果存在多个对象,就不能得到一个完整唯一的信息,存在风险。此时可以考虑用单例方式优雅地解决。比如uvm_root、各种phase,这些实例如果不全局唯一,就会存在风险和麻烦。

2.省内存

假如你的验证平台,有一个类里面的内容很多很庞大,偏偏许多组件都想使用它里面的东西,用的时候每个组件都需要把这个组件声明和new一下,创建了这么多对象,显然是非常浪费内存空间,用单例就可以很好地节省。

3.全局访问更方便

从上一小节的代码中可以看出,我们要访问单例中的内容变得非常简单,不需要在任何组件类中声明例化,随用即可。

4.资源和数据共享

因为全局的访问方便和唯一性,通过单例,不同的组件可以在不关联的情况下,直接实现资源的共享或通信。这在搭建更高层次的验证平台和统一的时候可以提高很多效率。例如你的验证平台中有一些通用功能的公共函数,这些函数甚至可以在不同级别的验证平台中公用,那完全可以考虑为单例模式。

本文由“壹伴编辑器”提供技术支持

话说回来,盐好吃也不能多吃,单例也是有缺点的,不是啥情况都能用的,比如说虽然你想资源共享你的某个类,但是需要根据不同的参数例化不同的对象,在这种情况下,单例模式就不是很友好了,即便是节省了内存或方便了访问,无法满足实际应用需求也是徒劳。

大家可以结合前面提到的UVM源代码中单例的应用进行其优劣思考,但是也不要局限在其中,多想想自己设计的验证平台中哪些更适合用单例模式,然后应用之。

好了,时光如水,又到了说再见的时候了,祝大家天天开心,吃好喝好,越来越牛逼~

作者:Jerry
文章来源:杰瑞IC验证

推荐阅读
怎么在sequence中调用agent中的函数以及如何快速实验你的想法?
验证仿真提速系列--认识“时间”与平台速度定量分析
验证仿真提速系列--SystemVerilog编码层面提速的若干策略

更多IC设计技术干货请关注IC设计技术专栏。
迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。
推荐阅读
关注数
11380
内容数
1224
主要交流IC以及SoC设计流程相关的技术和知识
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息