电子说
观察者模式
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这种面向对象编程的语言,借鉴了大量的软件设计模式,提高了平台的复用和扩展。
通过这些学习可以发现,设计模式的目的就是解耦。创建型模式是将创建和使用代码解耦,结构型模式是将不同功能代码解耦,行为型模式是将不同的行为代码解耦。借助设计模式,我们利用更好的代码结构,将一大坨代码拆分成职责更单一的小类,让其满足开闭原则、高内聚松耦合等特性,以此来控制和应对代码的复杂性,提高代码的可扩展性。
审核编辑:刘清
全部0条评论
快来发表一下你的评论吧 !