观察者模式
observer pattern: 对象之间定义一个一对多的依赖关系,当一个对象改变的时候,所有依赖对象都会自动收到通知。
观察目标( subject )和观察者( observer )是一对多的关系。有时候观察者模式也叫做发布-订阅模式( publisher-subscriber )。观察者模式将观察者和被观察者代码解耦。
示例: 股民investor作为观察者,股票stock作为观察目标。当股价大于20或者小于10时,观察者将会收到通知,执行各自的函数。
可以看到,在stock中调用notifyobserver,实际是调用investor中重写的update函数。update函数对于不同观察者,可以有不同的独立的实现。将代码中 变化的部分(增加、减少观察者,对观察结果的处理函数) 和 不变的部分(发送通知给观察者) 进行了很好的分离,实现代码最小化改动,达到解耦的目的。
uvm_subscriber
uvm中内建了uvm_subscriber类,可以被当作观察者或者订阅者使用。
一般用在构建功能覆盖率的收集。伪代码如下:
订阅者订阅monitor中收集到的transaction,覆盖率模块,参考模型,scoreboard都是订阅者。每当monitor收集到新的transaction,自动调用write函数,将transaction广播出去(uvm_analysis_port是一个广播的port,可以对应多个接收者)至于write函数如何实现,monitor并不关心,每个订阅者的write实现不同。在覆盖率类中write具体实现就是调用sample函数,收集覆盖率。 uvm通过connect函数将tlm端口连接,在订阅者和发布者之间建立了联系。 具体分析见下一节。
** tlm**
uvm对观察者模式进行了扩充,加入了各种端口类,作为一个中介,专门负责订阅者和发布者建立联系。
如下示例,env中有三个component(a_inst, b_inst, c_inst), 其中a作为发布者,b,c作为订阅者。
1. 示例
在uvm树形结构中,我们会看到端口被当作component放入了a_inst的m_children成员变量中,如下:
2. 将端口加入树形结构
先分析下为什么port会被加入到uvm树结构中。
如下,a_ap 是一个 uvm_analysis_port#(my_transaction)类型的端口。调用new函数传入name = a_ap, parent = this (a_inst)
uvm_analysis_port#(my_transaction)继承于uvm_port_base。uvm_prot_base的new函数会创建一个 **m_comp ** (uvm_port_component类型)的实例,这个实例是一个参数化的类,传入了一个端口类型,这个端口类型就是a_ap的类型。
如下,m_comp被创建时,同时也会为 **m_port **赋值,这个句柄指向a_ap的实例。
uvm_port_component继承于uvm_port_component_base, uvm_port_component_base继承于uvm_component。在uvm_port_component_base中,super.new传入的name = a_ap, parent = a_inst
所以,并不是a_ap这个端口实例被加入到了uvm树形结构,因为 a_ap不属于component ,无法加入树形结构。只是a_ap的成员变量m_comp加入到了树形结构,而这个m_comp加入树形结构用的是 a_ap的name和a_ap的parent, 代表a_ap加入了树形结构。同时m_comp里也有成员变量 m_port ,指向a_ap的实例。在sequence中无法使用tlm端口,一般借助sequencer的端口或者使用mailbox代替。
3. connect函数
connect()函数的实现:
a_ap.connect(b_inst.b_imp)a_ap.connect(c_inst.c_imp)后,会在a_ap中的m_provided_by ( 联合数组,索引是 provider名字,值是 provider的实例,此处是imp型的端口 ) 加入记录,m_provided_by[b_imp] = b_imp m_provided_by[c_imp] = c_imp
4. write函数
a_ap是uvm_analysis_port型的端口,a_ap.write调用的write函数在uvm_analysis_port类中定义
analysis_port是广播型的端口,通过for循环遍历 m_imp_list ( 存放 imp型的端口 ), 执行 tif.write 函数( 调用每个 imp端口的 write函数 )
b_imp是uvm_analysi_imp#(my_transaction,b)型的端口,构造函数new会传入b的句柄,所以其内部成员变量 **m_imp **指向 **class b **的实例。
tif.write其实就是调用 m_imp.write , 也就是 class b/ class c 中定义的write函数。
5. m_imp_list
在class uvm_root中,当执行完connect_phase后,会调用do_resolve_bingding函数。这个主体作用是从下往上遍历uvm树形结构,执行resolve_bindings函数
resolve_bindings函数是uvm_component函数中的空虚函数,agent,driver类型的component没有实际操作,但在uvm_port_component中重写了。uvm_port_component调用m_port的resolve_bindings函数。
a_ap在resolve_bindings函数中遍历联合数组 m_provided_by ,调用m_add_list将和a_ap connect相连的imp端口放入m_imp_list中。
至于上面提到的,将端口加入树形结构的作用就在这里体现了:端口加入树形结构,才会无遗漏的被遍历循环到。至于为什么不在调用connect函数时直接加入m_imp_list中,这是为了解决connect的传递行为( port_b.connect(imp); port_a.connect(port b); )
在观察者模式的示例中,addobserver 函数相当于uvm中的 connect 函数,对 **m_observer_hash **的遍历相当于uvm中 **m_imp_list **的遍历。uvm加入了更丰富的端口类,来实现这些功能。uvm还有很多其他端口和端口的方法,这里不在展开。综上,uvm中tlm机制是观察者模式和内建端口类的结合。
总结
uvm作为一种方法学,提供了一种实际工程的验证平台架构。在将近7万行的源代码中,囊括了sequence机制、factory机制、phase机制、寄存器模式等内容。uvm采用systemverilog这种面向对象编程的语言,借鉴了大量的软件设计模式,提高了平台的复用和扩展。
通过这些学习可以发现,设计模式的目的就是解耦。创建型模式是将创建和使用代码解耦,结构型模式是将不同功能代码解耦,行为型模式是将不同的行为代码解耦。借助设计模式,我们利用更好的代码结构,将一大坨代码拆分成职责更单一的小类,让其满足开闭原则、高内聚松耦合等特性,以此来控制和应对代码的复杂性,提高代码的可扩展性。
如何打开eip文件,eip格式文件怎么打开
浅谈分离LVDS信号设计技术
虚拟机:Hadoop集群的测试
中国香港科技大学成功研发出一种环保型可充电液体燃料
针对ISM应用的混频器充分发挥LTCC技术的优势
UVM设计模式之观察者模式解读
医院触摸式紧急呼叫求助板电路图
喜讯!庆科信息荣获“智光杯”全屋智能及商用系统优秀新供应链奖
电动汽车充电服务市场痛点如何解决
借市场之力 推动半导体创新
稳石机器人荣获STIF2023 第四届国际科创节“年度数智化创新典范奖”
微软美国商城最新补货 Xbox Series S,示脱销或无货状态
UCSI框架是由哪些部分组成的?UCSI实现必须经过HLK测试吗?
学底层狠无聊?
普通运放的仪表放大器
这款儿童安全座椅,是在用心守护孩子
加密货币发展的十大趋势分析
电源完整性设计之ESR对反谐振的影响
基于MCU的智能家用热水器设计
语音激光无线通信系统的应用方案及实现