访问者模式
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()函数:
审核编辑:汤梓红
-
UVM
+关注
关注
0文章
182浏览量
19167 -
数据结构
+关注
关注
3文章
573浏览量
40121 -
设计模式
+关注
关注
0文章
53浏览量
8626
原文标题:UVM设计模式 (六)访问者模式、uvm_phase、uvm objection、process control
文章出处:【微信号:数字芯片设计工程师,微信公众号:数字芯片设计工程师】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论