欢迎光临散文网 会员登陆 & 注册

UVM基础-Sequence、Sequencer(一)

2022-12-06 04:13 作者:不吃葱的酸菜鱼  | 我要投稿

目录

        Sequence、Sequencer、Driver大局观

        Sequence和item

                item与sequence的关系

                flat sequence

                hierarchical sequence

        sequence与driver的关系

                事务传输实例

                        实例

                        flat_seq

                事务传输过程分析

        对于IC验证的新晋工程师进入到一个公司,大概率不会让你上来就写验证环境,以及验证环境的修改。但是会让你一上来就写一些sequence,以及一些test。因此,sequence这一部分是很重要的。

Sequence、Sequencer、Driver大局观

整个序列组件之间的数据传输可以如下描述:

        ① sequence 对象会产生目标数量的sequence item对象。借助于SV的随机化和sequence item对随机化的支持,使得产生的每个sequence item对象中的数据内容都不相同。产生的sequence item会经过sequencer再流向driver。

        ② driver陆续得到每一个sequence item,经过数据解析,将数据按照与DUT的物理接口协议写入到接口上,对DUT形成有效激励。

        ③ 如果需要,driver在解析完一个sequence item后,它可以将最后的状态信息歇写回sequence item对象再返回给sequencer,最终抵达sequence对象一侧。这样就可以让sequence知道driver和DUT互动的状态(如果有需要的话)。

        sequence item是driver与DUT每一次互动的最小粒度内容。例如DUT如果是一个slave端,driver扮演一个master去访问DUT的寄存器,那么sequence item需要定义的数据信息至少包括访问地址、命令码、数据和状态值,这样的信息在driver取得后,会通过时序方式在interface一侧发起激励送至DUT。        

        用户除了可以在声明sequence item时添加必要的成员变量,也可以添加对这些成员变量进行操作的成员方法。这些添加了的成员变量,需要充分考虑在通过sequence传递到driver前是否需要随机化。

        对于一个sequence而言,它会产生多个sequence item,也可以产生多个sequence。从产生层次上来看,sequence item是最小粒度,它可以由sequence生成,而相关sequence也可以进一步组织继而实现层次化,最终由更上层的sequence进行调度。

        sequence与driver之间起到桥梁作用的是sequence。由于sequence和driver均是component组件,它们之间的通信也是通过TLM端口实现的。TLM端口是实现组件和组件之间的通信,driver和sequencer之间的TLM通信参数是sequence item类。由于这一限制,使得sequencer到driver的传输数据类型不能改变,同时与sequencer连接的sequence创建的sequence item类型也应该为指定类型。 也就是说,sequencer从sequence拿到的item的类型,和sequencer发送给drver,以及driver接收到的数据类型,必须是严格一致的。

        driver不应该轻易修改item中的值,它会把item中的数据按照与DUT的物理协议时序关系驱动到接口上面。

         uvm_component_item和uvm_sequence都是基于uvm_object,它们不同于uvm_component只应当在build阶段作为UVM环境进行创建和配置,而是可以在任何阶段创建

        由于无法判定环境在run阶段什么时间点会创建sequence和将其产生的sequence item 挂载(attach)到sequencer上面,所以无法通过UVM环境结构或者phase机制来识别sequence的运行阶段。也正是因为uvm_object是独立于build阶段之外的,所以用户可以有选择地动态地在合适时间点挂载所需要的sequence和item

        uvm_sequence和uvm_sequence_item不是组件,所以无法通过config_db按照层次关系对其进行配置。因此要用一个trick:sequence一旦活动起来,它必须挂载到一个sequencer上(发送item),也就是sequence能够获取sequencer的句柄,通过句柄来访问sequencer中的成员变量或等信息,那么这样sequence可以依赖于sequencer的结构关系,间接通过sequencer来获取顶层的配置和更多信息。

        明确划分模块职责的话,sequence应该只负责生成item的内容, 而不应该控制item的时序,而驱动激励时序的任务应当由driver完成。

sequencer之所以作为一个组件,设立在sequence和driver之间,主要有两个原因:

        ① sequencer作为一个组件,它可以通过TLM端口与driver传送driver对象。

        ② sequencer在面向多个并行sequence时,它有充分的仲裁机制来合理分配和传送item,继而实现并行item数据传送至driver的测试场景。

数据传送机制:(数据传输,get还是put)

        数据传送采用的是get模式不是put模式。如果是put模式,那么应该是sequence将数据put至driver,而如果是get模式,那么应该是driver从sequencer获取item。

选择get模式的原因:

        ① 如果是get模式,当item从sequence产生,穿过sequencer到达driver时,我们就可以结束该传输。如果是put模式,则必须是sequencer将item传送至driver,同时必须收到返回值才可以发起下一次传输,从效率上看,两者具有差别。

        ② 如果需要让sequencer具有仲裁特性,可以使得多个sequence同时挂载到sequencer上面,那么get模式更符合设计。这是因为driver作为initiator,一旦发出get请求,它会先通过sequencer,然后获得仲裁后的item

Sequence和item

        sequence指的是uvm_sequence类,而item指的是uvm_sequence_item类,简称为sequence和item。item是基于uvm_object类,这表明了它具备UVM核心基类所必要的数据操作方法,例如copy()、clone()、compare()、record()等。

item通常应该具备一下数据成员:

        ① 控制类:总线协议上的读写类型、数据长度、传送模式等。

        ② 负载类:一般指数据总线上的数据包。

        ③ 配置类:用来控制driver的驱动行为,例如命令driver的发送间隔或者有无错误插入。

        ④ 调试类:用来标记一些额外信息方便调试,例如对象的实例序号,创建时间等。

item使用注意事项:

        ※ 如果数据域属于需要用来做驱动,那么用户应考虑定义为rand类型,同时按照驱动协议给出合适的constraint。

        ※ 由于item本身的数据属性,为了充分利用UVM域声明的特性,建议将必要的数据成员都通过`uvm_field_automation机制来声明,以便后续uvm_object基本数据方法的自动实现。

        ※ UVM要求item的创建和随机化都应该发生在sequence的body()任务中,而不是在sequencer和driver中。

        ※ 按照item的周期来说,它应该始于sequence的body()方法,而后经过随机化,穿越sequencer到达driver,直到被driver吸收,到此就结束了。如果要对item进行修改数据,不应当直接进行修改,这会无形的增加item的寿命,正确做法是利用copy或者clone函数来复制一份再做处理。

item与sequence的关系

        一个sequence可以包含一些有序组织起来的item实例,考虑到item在创建后需要被随机化,sequence在声明时也需要预留一些可供外部随机化的变量。

sequence可以被区分为常见的三类:

        扁平类(flat sequence):这一类往往只用来组织更小的粒度,即item实例构成的组织。

        层次类(hierarchical sequence):这一类往往是由更高层的sequence用来组织底层的sequence,进而让这些sequence或者按照顺序方式,或者按照并行方式,挂载到同一个sequencer上。

        虚拟类(virtual sequence):这个类是最重要的,它是最终控制整个测试场景的方式,由于整个环境中往往存在不用种类的sequencer和其对应的sequence,我们需要一个虚拟的sequence来协调顶层的测试场景。之所以称这个方式为virtual sequence,是因为该序列本身并不会固定挂载于某一种sequencer类型上,而是将其内部不同类型的sequence最终挂载到不同的目标sequencer上面。

flat sequence

一般对于flat sequence而言,它包含的信息有:

        ※ sequence item以及相关的constraint用来关联生成的item之间的关系,从而完善出一个flat sequence的时序形态。

        ※ 除了限制sequence item的内容,各个item之间的时序信息也需要由flat sequence给定,例如何时生成下一个item并且发送至driver。

        ※ 对于需要与driver握手的情况(读写操作),或者等待monitor事件从而做出反应。都需要相应具体事件,从而创建对应的item并且发送出去。

        在uvm_sequence中写的task body,当sequence挂载到sequencer之后,body任务会自动运行,就像组件里面的run一样。

        上面的代码,是在flat_sequence中,不断去new一个bus_trans,然后给他赋值data、addr、write和delay,事实上这样做的话,flat_sequence不仅要考虑数据包的长度和地址,还要考虑数据包的内容,要做的事情太多了。 

        我们可以将一段完整发生在数据传输中的、更长的数据都“收编”在一个bus_trans类中(item 中),提高这个item粒度的抽象层次,一旦有了更成熟的、更合适切割的item,上层的flat sequence在使用过程中也更加方便。

hierarchical sequence

        Hierarchical sequence区别于flat sequence的地方在于,它可以使用其他sequence,还有item,这么做是为了创建更丰富的激励场景。

        通过层次嵌套关系,可以让hierarchical sequence使用其他hierarchical sequence、flat sequence和sequence item,如果底层的sequence和item粒度合适,那么就可以充分复用他们,来实现更为丰富的激励场景。

        这里用了uvm_do_with宏,这个宏定义出来,主要做了三件事:① 创建sequence或者item;② 对里面的成员变量进行随机化; ③ 传送数据到sequencer上。

sequence与driver的关系

为了便于item传输,UVM专门定义了匹配的TLM端口供sequencer和driver使用:

        ※ uvm_seq_item_pull_port #(type REQ=int, type RSP = REQ)

        ※ uvm_seq_item_pull_export #(type REQ=int, type RSP = REQ)

        ※ uvm_seq_item_pull_imp #(type REQ=int, type RSP = REQ, type imp=int)

由于driver是请求发起端,所以在driver一侧例化了两种端口:

        ※ uvm_seq_item_pull_port #(REP, RSP ) seq_item_port

        ※ uvm_analysis_port #(RSP) rsp_port   // 专门广播response的,一对多端口

而sequencer一侧则为请求的响应端,在sequencer一侧例化了对应的两种端口:

        ※ uvm_seq_item_pull_imp #(REQ, RSP, this_type) seq_item_export

        ※ uvm_analysis_export #(RSP) rsp_export

        对于第三种sequence部分的端口需要详解一下,虽然sequence_item_export叫做export,但是它是被定义为imp,也就是TLM传输的终点。而sequence作为TLM通信数据传输的终点,为什么要给uvm_analysis端口定义为export呢?因为在我们的理解中,export是TLM通信数据传输的中间节点而不是终点。这是因为uvm_analysis端口内置了一个存储RSP的FIFO而FIFO上是有imp端口的,因此uvm_analysis端口的export接到内置的FIFO的imp端口上就形成了终点。这也是为什么我们成sequencer就是TLM通信传输的终点的原因。

        显然,从上面我们可以看到,sequence和driver的各自的两个端口其实是和对方的端口成对的。uvm_seq_item_pull_port #(REP, RSP ) seq_item_port 和 uvm_seq_item_pull_imp #(REQ, RSP, this_type) seq_item_export成对;uvm_analysis_port #(RSP) rsp_port 和 uvm_analysis_export #(RSP) rsp_export成对。因此需要在connect_phase中将端口进行连接。

        通常情况下,只需要连接 driver::seq_item_portsequence::seq_item_export 就行了。即在connect_phase中通过:driver::seq_item_port.connect(sequencer::seq_item_export) 完成。这一对端口功能主要用来实现driver与sequence的request获取和response返回。

seq_item_port可以调用很多方法:

        ※ task get_next_item(output REQ req_arg): 采取blocking的方式等待从sequenne获取下一个item。

        ※ task try_next_item(output REQ reg_arg): 采取nonblocking的方式从sequence获取item,如果立即返回的结果req_arg为null,则表示sequence还没有准备好。

        ※ function void item_done(input RSP rsp_arg=null): 用来通知sequence当前的sequence item已经消化完毕,可以选择性的传递RSP参数,返回状态值。

        ※ task wait_for_sequence(): 等待当前的sequence直到产生下一个有效的item。此任务往往和try_next_item一起使用。

        ※ function bit has_do_available(): 如果当前的sequence准备好而且可以获取下一个有效的item,则返回1,否则返回0。

        ※ function void put_response(input RSP rsp_arg): 采取nonblocking方式发送response,如果成功则返回1,否则返回0。

        平时用的比较多的任务和方法,就是上面加粗的任务和方法。

        driver消化完当前的request后,可以通过item_done(input RSP rsp_arg=null)方法来告知sequence 此次传输已经结束,参数中的RSP可以选择填入,返回相应的状态值。

事务传输实例

实例

        在sequence发送item的时候,就会给item记录一个sequence_id,表示该item是由哪个sequence发送的,这样在一个sequencer同时收到两个不同的sequence发送的item,然后发送给driver,driver再返回这两个item的response给sequencer,让sequencer把对应的response送给对应的sequence,就需要利用这个sequence_id。rsp.set_sequence_id(req.get_sequence_id())这个就是通过获取request的id,给response,来保证发送对应request的sequence能得到对应自己的response。

        sequence_id不做域的自动化的话,在使用clone函数的时候是不会进行clone的,默认为0。

注意一下,在声明drvier和sequence时,没有给他们下类型的定义,所以默认是sequence_item类型属于父类的句柄,因此在get_next_item之后,需要将父类的句柄转换成子类的句柄,这步可以通过在声明class时,定义类型#(RSP)来改变。但是req.clone()获得的句柄默认是uvm_object类型,因此必须通过父类到子类的转换。

flat_seq

flat_seq作为动态创建的数据生成载体,它的主任务flat_seq::body()做了如下的几件事情:

        ※ 通过方法create_item()创建了request item对象;

        ※ 调用start_item()准备发送item;

        ※ 在完成发送item之前对item进行随机处理;

        ※ 调用finish_item()完成item发送;

        ※ 有必要的情况下,可以从driver处获得response item。

事务传输过程分析

在定义driver时,它的主任务driver::run_phase()需要做如下处理:

        ① 通过seq_item_port.get_next_item(REQ)从sequencer获取有效的request item。

        ② 从request item中获取数据,进而产生数据激励。

        ③ 对request item进行克隆生成新的对象response item

        ④ 修改response item中的数据成员,最终通过seq_item_port.item_done(RSP)将response item对象返回给sequence

        对于uvm_sequence::get_response(RSP)和uvm_driver::item_done(RSP)这种成对的操作,是可选的,可以选择获取或者不获取,但是要成对出现。

        在高层环境中,应该在connect_phase中完成driver到sequencer的TLM端口连接,比如上面代码中env::connect_phase()中通过drv.seq_item_port.connect(sqr.seq_item_export)完成了driver与sequencer之间的连接。

        在完成了flat_seq、sequencer、driver、env的定义之后,到了test1层,需要考虑挂起objection防止仿真在run_phase的时候提前退出。

        使用uvm_sequence::start(SEQUENCER)来完成sequence到sequencer的挂载操作。当多个sequence试图挂载到同一个sequencer时,需要在sequencer上添加仲裁功能

        在sequence创建item之前,首先需要将sequence挂载到sequencer上,上面整个sequence从create_item到最后和driver握手成功的get_response的运作都在sequence的body()中。Driver的get_next_item到item_done都在driver的run_phase()中。

        sequencer做仲裁,是在driver发起get_next_item()时,才开始仲裁选择哪个item。如果只有一个sequence挂载到sequencer上,那直接用就行了。在sequencer将通过的权限交给某一个底层的sequence之前,目标sequence中的item应该完成随机化,继而在获取sequencer的通过权限后,执行finish_item()。

        finish_item()会等到driver发挥item_done()才会结束。

对每个item而言,它起始于create_item(),继而通过start_item()尝试从sequencer获取可以通过的权限。如果driver没有了item可用,将调用get_next_item()来尝试从sequencer一侧获取item。

        为了统一起见,用户可以不在定义sequencer或者driver时指定sequence item类型, 使用默认的REQ = uvm_sequence_item,但是用户需要注意在driver一侧的类型转换,例如对get_next_item(REQ)的返回值REQ句柄做出动态类型转换,等到正确类型之后再进行接下来的操作。


UVM基础-Sequence、Sequencer(一)的评论 (共 条)

分享到微博请遵守国家法律