UVM设计模式之访问者模式

电子说

1.3w人已加入

描述

访问者模式

Visitor Pattern: 允许一个或者多个操作应用到一组对象上,解耦操作和对象本身。换言之,如果component的数据结构是比较稳定的,但其是易于变化的,那么使用访问者模式是个不错的选择。

常见的访问者模式有五种角色:

(1) Vistor(抽象访问者):为该对象结构中具体元素角色声明一个访问操作接口。

(2) ConcreteVisitor(具体访问者):每个具体访问者都实现了Vistor中定义的操作。

(3) Element(抽象元素):定义了一个accept操作,以Visitor作为参数。

(4) ConcreteElement(具体元素):实现了Element中的accept()方法,调用Vistor的访问方法以便完成对一个元素的操作。

(5) ObjectStructure(对象结构):可以是组合模式,也可以是集合;能够枚举它包含的元素;提供一个接口,允许Vistor访问它的元素。

举例:老师教学反馈得分大于等于85分或者学生成绩大于等于90分,则可以获得优秀奖;如果老师论文数目大于等于2、学生论文数目大于等于1,则可以获得1万元的现金。在这个例子中,老师和学生就是Element,他们的数据结构稳定不变。从上面的描述中,我们发现,对数据结构的操作是多变的,一会儿评选成绩,一会儿评选科研,这样就适合使用访问者模式来分离数据结构和操作。

两种操作,class GradeSelection class ResearchSelection 重写实现 visit()函数

数据结构

class Teacher class Studnet中调用 visitor.visit(),不需要特别指明哪一种操作。

数据结构

数据结构

访问者模式适用于对一组结构固定的组件统一地加入一个新的操作,如上例,将student1,student2,teacher1,teacher2这四个共同属性(都继承于Element)的组件,加入ObjectStructure中,此例中的组件关系是 前后线性的关系 ,依次加入element_q队列中。当需要对所有组件执行researchselection操作时,ObjectStructure调用accept(researchselection)即可,会自动遍历各个组件执行这个researchselection的操作。如果需要切换其他操作,只需更改ObjectStructure中accept的参数即可。添加新的操作也很方便,不需要更改组件,只需要在外部重写visitor()函数,具有很好的扩展性,实现组件与操作的解耦。

UVM_PHASE

UVM的环境具有明确的结构,各个组件统一继承于uvm_component, 组成 ** tree structure** 。每个子类component重写各个phase函数,每个component中的phase函数相当于 visitor(), 很符合使用访问者模式的条件。但是uvm的phase机制实现和上述介绍的示例还有很大区别,component中的phase是在自身内部实现的,而不是放在类外部;对于执行同一个phase,树形结构中的component不是简单的依次执行,有 top-down,down-top和并行执行 ;对于同一个componet中的phase, 有不消耗时间的 function phase , 也有消耗时间的 task phase , 有依次执行的,也有并列执行的(run_phase和12个run-time phase)。所以存在 两个维度 ,一个uvm_component的维度,根据单例模式中的parent-child关系构建了树状结构;一个phase维度,将每个phase以node的形式放入domain中,统一调度。UVM还支持objection机制,drain_time,timeout,多domain,进程同步,phase的jump,phase_debug等操作,所以简单的访问者模式无法满足要求。

下面根据源码对uvm_phase从这两个维度分析:

1. phase的执行顺序

在第二篇,提到了uvm_root中函数run_test()根据工厂模式创建testcase的实例。接下来run_test()调用uvm_phase中的静态函数 m_run_phase() 开始执行各个phase。这里用到了SV内建的class process,提供了精细控制进程的方法。

uvm_domain调用静态函数 get_common_domain() , 从名字上可以看到这是获得一个domain。uvm_domain继承于uvm_phase,是phase的一个容器。

comon_domain调用add函数依次加入不消耗时间的 function phase , 一共9个。第一个uvm_build_phase::get()获得build_phase的实例,uvm_build_phase和uvm_root一样采用了 单例模式 ,所以全局只有一个build_phase,通过 uvm_build_phaes::get()获得。所以我们平常在不同component中的数据结构传入的phase,其实指向的都是那个唯一的uvm_build_phaes::get() 实例。其他phase也一样,实例全局唯一。

add()函数实现了将不同phase以node的形式加入,node有predecessor,successor的概念,构成链表结构。类似component中parent,child的关系,构成树状结构。根据add加入的顺序,组织了先后顺序。所以connect_phase_node的predecessor是build_phase_node, successor是end_of_elaboration_phase.

get_uvm_domain()获得另一个domain:uvm_domain。uvm_domain中加入的是task phase, 一共12个。然后common_domain再将uvm_domain和run_phase同时加入,实现rum_phase和12个小phase组成的run-time phase并行执行。

到此,各个phase的执行顺序就固定了。如果没有单独创建domain,那么只有common_domain和uvm_domain这两个domain会被默认创建。结构如下: 

数据结构

回到m_run_phases(), 有一个forever begin ... end一直循环从m_phae_hopper.get()获得phase执行exectute_phase()。最开始m_phase_hopper( 一个放入uvm_phase类型的mailbox)放入的是common domain,common domain执行完毕后,会将successor build phase 放入m_phase_hopper中;在forever循环中m_phae_hopper.get()获得build phase,执行完build phase最后会放入build phase的successor connect phase,直到遍历完所有phase。

数据结构

2. component的执行顺序

接上面, phase.execute_phase() 的具体实现会根据不同的uvm_phase_type和uvm_phase_state走不同的分支。
uvm_build_phase, uvm_final_phase继承于 uvm_topdown_phase , 其余function phase继承于 uvm_downtop_phase , task phase继承于 uvm_task_phase 。

对于build_phase, 函数exectue_phase会调用 m_imp.traverse(top,this,UVM_PHASE_EXECUTING) , traverse()函数在uvm_topdown_phase中定义,build_phase从top to down的执行顺序也是在这里实现的。

最开始traverse传入的是top,也就是最顶层uvm_root,②处调用get_first_child获得uvm_test_top(在之前的run_test中已被创建),递归调用traverse函数,执行ph.execute(uvm_test_top, phase), 实际调用的是comp.build_phase, 也就是uvm_test_top的 builde_phase()。builde_phase会创建uvm_test_top的child env。执行完ph.exectute,再次执行traverse函数,此时传入的component是env, 执行env的build_phase,创建env的child agt。一直递归循环,实现所有component的创建。

对于继承uvm_downtop_phase的phase,则是从底部开始循环。相比uvm_topdowun_phase, 将递归函数traverse放在ph.execute前面 ,便实现了down to top的顺序。

对于继承uvm_task_phase的函数,虽然递归函数traverse放在ph.execut前面,也是down to top的顺序,但是fork ... join_none让不同component的同一种phase函数在不同process上同时执行,实际效果是一块同时运行的。

所以对于component中phase的执行遍历,是根据调用递归函数遍历child完成的。在uvm_root中的find()函数,也是递归调用函数完成遍历。

数据结构

uvm objection

uvm objection涉及对进程的控制,先介绍一下systemverilog提供的class process。( 计算机体系中的进程,线程和内核的调度有关,而仿真平台是跑在仿真软件上的,由 simulation kernel进行调度 (IEEE 4.Scheduling semantics clause) 按照时间片执行,以下进程,线程不做区分,统一叫做进程 )

SV中的fork相关函数可以创建新的进程,但是对于进程的管理, 只有 wait fork, disable identifier, disable fork这些。其他语言中一般都有专门管理进程的操作方法,比如python中的multiprocessing模块。所以SV中加入了一个class process,提供了更精细的进程管理。class process并不可以用于创建进程,只可以在initial begin ..end,always,fork等创建进程的语句中通过 process::self() 获取该进程; status() 获得进程状态, kill() 终止进程及子进程, suspend() 挂起进程, resume() 恢复进程, srandom(int seed) 设置进程的随机种子。

数据结构

uvm objection机制的源代码实现不再探究,总结需要注意的几点:

每个phase的实例是唯一的,每个phase在创建的时候,自动创建了属于这个phase的objection。

对于消耗时间的task phase,其中必须raise_objection, 才会执行,否者直接退出。

对于同一个phase,可以多次raise_objection, 但是raise_objection和drop_objection必须成对存在。只有raise数量等于dorp数量时才会退出这个phase。

raise_objection前面不可以有消耗时间的语句,也就是刚进入phase的0时刻,就需要检测到raise_objection, 否则直接退出这个phase。

对于run_phase和并行的12个task phase, 如果在run_phase中raise_objection,但是main_phase没有raise_obejection,那么main_phase直接退出。如果在main_phase有raise_obejection,run_phase没有raise_objection,run_phase也会执行。所以尽量run_phase和12个小phase不要同时使用,以免出错。

通过简单的代码使用process来实现一下UVM中的objection:

数据结构

do_monitor是一个无线循环,在driver_main_phase中控制objection的raise和drop。

如果line42加上时间延迟,则会直接退出main_phase,进入下一个phase. 如果注释掉line43行也是直接退出main_phase,进入下一个phase. 打印结果:数据结构

如果加上line49, line51,main_phase则无法退出。打印结果: 

数据结构

uvm_visitor

UVM1.2中新加入了访问者模式的基础类,供使用者扩展使用:

数据结构

uvm_visitor: 提供了visit()方法,以及begin_v(),end_v()两个hook。

重写visit方法,简单的打印component的full_name.

数据结构

uvm_visitor_adapter: 提供了accept函数,用于实现visitor和component的连接,并对每个component调用visti方法。

数据结构

env中创建visitor, adapter的实例,accept传入的是env这个comonent,打印处uvm_test_top.env

数据结构

对于component的组织调用顺序,用户可以自定义structure。UVM提供了三种sturcture, 对应的adapter,如下:

数据结构

使用的top to down的structure, 从上到下遍历component调用visit()函数:

数据结构

  审核编辑:汤梓红

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分