UVM知识点总结-sequence

sequence及相关组件

基本的点

(1)uvm_sequence_item可以在uvm_sequece的任何阶段进行创建(2)sequence一旦活动起来就必须挂载在一个sequencer,通过这种方式,sequence可以获取所挂载的sequencer的实例的句柄,由于sequencer是组件,那么sequence就可以通过sequencer的句柄间接的获得sequencer的一些成员变量,而这些成员变量一方面可以是sequencer自己的,也可以是通过config_db从顶层进行配置的。(3)sequence的任务生成和随机化事务;driver消化事务,驱动激励的时序;item可以控制事务的一些时序,其自身带有一些比如delay函数,但是需要注意的是item的时序往往并不代表接口的最终驱动时序,接口的驱动时序是由driver/sequencer/item多重决定(4)sequencer与driver连接的模式是sequencer从driver进行get的模式,一方面是在于如果采用driver往sequencer中put的模式的化,实际上需要put操作结束后等到从sequencer中接收到回馈消息这个过程才会结束,这样操作效率会下降;另一方面在于采用get语句使得sequencer呈现一种被动形式,他直接去等driver从它这里去拿item,这样可以实现多个sequence挂在sequencer中

item

(1)事务(比如item)也是需要进行function new()构建函数的操作(2)item创建和随机化应该发生在sequence中的body任务中(3)从sequencer发送到driver过来事务(相当于req)不应修改,修改的应当是是rsp信号。(4)创建item的方法有uvm_object::create()(也就是type::id的create);uvm_sequence::create_item();

sequence

一些基本的操作:(1)一般来说整个sequence有如下操作:body任务前需要对当前sequence通过function new进行构建;body任务涉及创建了sequence或item,完成随机化,将数据传送到sequencer上(上述步骤也可以通过`uvm_do_with实现),从driver中获取rsp信号(可选,根据后面driver是否put到rsp信号或者item_done过程中是否rsp信号的句柄)(2)在test中进行挂载,将sequence挂载在environmet中的sequencer中,一旦挂载在上面,sequence的body任务就会自动执行,但是需要注意的是挂载并不意味着会随机化sequence。

sequence与config_db:config_db机制中是通过层次化的字符串来实现配置的,但是这里uvm_sequence及uvm_sequence_item并不在uvm结构中,所以无法按照层次关系进行顶层配置,但是可以利用config_db进行一些参数的设置/传递等操作,有如下几个方面(1)进行参数的传递:在组件中进行set操作(比如sequencer):uvm_config_db#(int)::set(this,"env.i_agt.sqr.*","count",9),注意,这里第二个参数使用了通配符,这是由于往往sequence实例化的对象名称是不固定的或未知的;在sequence进行get操作uvm_config_db#(int)::get(null,get_full_name(),"count",count),这里需要注意的是,前面实际上是相当在组件中进行操作,也就是正常的操作,而在这里sequence不在是一个组件,因此这里实际上不能在像之前那样使用指针this,第一个参数可以写null或者uvm_root::get()(就算写了null也会默认调用uvm_root::get()),而第二个参数写get_full_name类似与在componet中一样的操作等到sequence完整的路径(这个操作可以在sequence的body中进行,也可以pre_body中进行)(2)在sequence中设置参数:在set过程中需要注意sequence实际上是在virtual sequence中启动,所以其get_full_name的结果实际上是uvm_test_top.v_sqr.*(而不是uvm_test_top.env0.i_agt.sqr.*),通配符表示其实例名称可能不唯一,(白皮书p202页有问题,set和get的先后顺序问题)(3)wait_modified的使用(这个操作除了在sequence中使用同样可以在component中使用),比如uvm_config_db #(bit)::wait_modified(null,get_full_name(),"sen_en")表示的是参数send_en的值被更新后他就会返回,否则会一直在那里等待。这个语句后面往往会跟一些config_db机制中的的get语句(get到sen_en),也就是表示对参数的获取是有条件的,仅仅当sen_en参数发生变化时他才会进行获取参数。对于component的config_db机制get参数的前提是这个参数已经set过,通过这种方式能够实现

sequence层次化:flat sequence:(1)往往仅包含最小粒度的事务(2)body函数在将sequence挂到sequencer上以后,就会自动执行(3)下面举一个body任务:bus_trans tmp;tmp=new();tmp.randomize() with{data==lock::data}。这里的local::data代表的是flat_seq中的data数据,而前面的data是bus_trans中的data,这里的操作实际上先随机化sequence中的随机变量,而sequence中的随机变量又会影响到item中的随机变量,也就是一种层次化的随机过程(4)对于flat sequence和item之间的准则是与事务属性的相关内容变主要放在事务中(bus_trans),而对数据的一些具体操作放在sequence中(flat_sequence),数据传送的粒度要大一些,这样sequence才有更多的操作空间。Hierarchical sequence:更高层次的sequence嵌套底层的sequence或者item,并将这些事务挂载在同一个sequencer上(Hierarchical sequence对Hierarchical sequence/flat sequence/itemj均可以嵌套)。virtual sequence上面两种类均是sequence均是挂载在同一个sequencer上,但是virtual sequence可以实现内部的sequence挂在不同的sequencer,从而实现对不同sequence的调度,virtual sequence是为了控制整个测试场景的,virtual sequence包含全局所有的sequence。而在顶层环境中,virtual_sequencer相当于一个集中,他将各个组件中的各个sequencer的实例句柄集中放在了这里,一旦找到virtual_sequencer的句柄,就可以通过其句柄找到其他组件的句柄。virtual sequence出现的意义在于实际上,我们想将各个部分的sequence集中到一个统一的virtual sequence中(以方便管理),但是这个virtual sequence中的各个sequence实际上又需要挂载在不同的sequencer上,为了实现这一状态,我们将virtual sequence挂载在了virtual sequencer上,而virtual sequencer中有各个sequencer的句柄,通过这种方式可以实现virtual sequence中的不同sequence挂载在不同的sequencer上。

virtual sequence实现的步骤:(1)`uvm_declare_p_sequencer(mcdf_virtual_sequencer),通过宏声明一个子类句柄p_sequencer,是mcdf_virtual_sequencer类型,后续可以通过mcdf_virtual_sequencer p_sequencer定义了一个p_sequencer(p_sequencer是这个宏所在的sequence挂载的sequencer的实例)(2)`uvm_do_on(fmt_seq,p_sequencer.fmt_sqr)的操作是将fmt_seq(sequence,正常的sequence)挂载在fmt_sqr(sequencer,正常的sequencer)上(需要注意的是前面两步实际上就是将各个sequence集中到所谓的virtual sequence中,也就是使用上面两个语句的sequence称为一个virtual sequence,他集中了各个sequence)(3)顶层环境中将sequencer和virtual sequencer连接,virt_sqr.cr_sqr=cr_agt.sqr(4)test层次中进行通过virtual sequence例化seq后进行seq与virtual sequencer连接。

需要指出的是,virtual sequence是实现sequence之间同步的重要工具,对于一些简单的sequence也可以设置一个全局的事件进行同步,但是这种方法非常不好,不建议使用;此外virtual sequence作为一种特殊的sequence,在其中除了可以启动正常的sequence以外,还可以启动其他的virtual sequence

layering sequence:

sequenceitem的发送(实际上就是sequencesequencer之间的交流)

一些基本的点:sequence和item之间的层次化:一个sequence中可以包含多个有序组织起来的item,另外,由于这些item在创建后需要进行随机化,因此sequence在声明的过程中需要预留一些可供外部进行随机化的变量,这些随机变量一方面通过层间传递约束最终实现对item对象的随机变量进行控制,另一方面对item对象之间加以组织和时序控制。比如假设有sequence A,A中有rand a;同样有sequence B,B中执行如下语句:A aa,那么后续就可以在B中通过aa.a调用A中的随机变量,具体例子参见白皮书184; 事务类型的匹配:一个sequencer只能含有一种事务类型,如果item和sequence想在这个sequencer上挂载,item或者sequence的事务类型必须是sequencer的事务类型或者是其子类,通过将sequencer和driver接收的事务类型设置为#(uvm_sequence_item)可以不同事务类型挂载在同一个sequencer上的问题,但是需要注意此时driver通过get_next_item得到的req信号是父类,进行对象访问时需要进行类型转化。

sequence的发送:对于sequence,创建后(名称为cseq)可以通过cseq.start(m_sequencer,this)操作进行,实际上这个操作相当于将cseq挂载在该语句所在的sequence A所挂载的sequencer上,也就是执行cseq中具体事务发送的部分;该函数的原型是uvm_sequence::start(uvm_sequencer_base sequencer,uvm_sequence_base parent_sequence=null,int this_priority=-1,bit call_pre_post=1),第一个参数表示A所挂载的sequencer的句柄(这个句柄用m_sequencer表示,且不需要预定义);对于第二个参数,如果不给的化就是代表默认值,而这里用this表示的是其优先级别和这个语句所在的类(与A的优先级一样)是一样的,如果不给的化代表这个sequence没有上层嵌套的sequence;如果指定了第三个参数,假如sequence挂载在sequencer上,那么当前sequence不仅仅可以访问到sequencer,还可以访问到嵌套该sequence的高级别的sequence,第三个参数默认值为-1时,如果该sequence有高一级的sequence,那么将继承高一级的sequence的优先级,如果是顶部的sequence,那么默认优先级为100,当然用户也可以自己定义;第四个参数用来控制pre_body和post_body是否执行,如果为0的化pre_body和post_body就不会执行。

另外需要注意在sequence的发送过程中,尽量不要使用fork join_none/join_any语句,这可能会导致sequence没有执行完就被系统自动清理掉,可以使用fork join;如果非要使用fork join_none吗,建议在join_none语句后面加上wait fork

item的发送:start_item(req);   req.randomize with ***;    finish_item(req);在start_item和finish_item之间进行随机化;需要注意的是req信号可能是经过类型转化后的,其函数原型是uvm_sequence::start_item(uvm_sequence_item item,int set_priority=-1,uvm_sequencer_base sequencer=null),第二个参数任然表示优先级,对于第三个参数表示的是如果想将item和这个item所对应的sequence挂载在不同的sequencer上就需要指定这个参数;uvm_sequence::finish_item(uvm_sequence_item item,int set_priority=-1)。实际上在start_item和随机化之前还有执行item对应的parent_sequence的方法pre_do,开始执行item_done后还会执行parent_sequence的mid_do,此外还有send_request和wait_for_item_done,最后执行parent_sequence的post_do。这里可以定义一些pre_do等方法,但是不建议做,会扰乱层次。另外send_request和wait_for_item_done(与driver中的item_done相匹配)均是sequencer的方法,而finish_item是sequence的方法,sequence通过finish_item可以调用到sequencer的send_request和wait_for_item_done方法,这是由于sequence挂载在sequencer时可以拿到sequencer的句柄m_sequencer,因此可以这样操作。(需要注意的是上述pre_do/mid_do/post_do实际上在sequence的发送过程中也有,pre_do/mid_do在sequence的body任务执行之前,而post_do在body任务执行之后)

一些对比:两者发送过程的对比:sequence的发送start执行后实际上是将sequence挂载在相应的sequencer上,因此会自动执行body任务,而start_item/finish_item实际上是将事务挂载在sequence上,两者挂载的对象是不同的。两个发送过程的流程对比参见讲义,总的来说就是发送sequence时,子一级的sequence会调用高级sequence的pre/mid/post_do函数,而发送item时,会调用item所挂载的sequence的pre/mid/post_do函数。采用宏进行发送与手动发送之间的对比:采用手动发送可以往事务中传递一些值,参考白皮书p195。

sequencersequence

序列宏(发送事务的宏操作):(1)uvm_do(item/sequence),两种事务都可用其进行操作,这里封装了事务的创建/随机化/pre_do/mid_do/post_do/wait_for_grant及其后续操作(2)uvm_do(item/sequence)_with在前者的基础上封装了约束操作(3)如果针对不同的sequence,想要同时发送可以通过宏来设置优先级别来实现(`uvm_do_pri)(4)此外还有`uvm_do_on_pri,这里的操作主要是实现对某个sequence挂载如果没有on的化,以item为例,item是在某个sequence中的,如果没有on的情况下,执行相关宏的操作,默认是将这些item挂载所在的sequence对应的sequencer中(5)需要注意,相关宏的操作只能怪再sequence中进行,因此在顶层test针对例化的sequence或item任然要使用start或者start.item操作进行发送事务(挂载操作)

仲裁:多个事务挂载在同一个sequencer上时可以通过下列语句进行仲裁m_sequencer.set_arbitration(UVM_SEQ_ARB_STRICT_FIFO),先根据优先级,优先级相同的情况下根据FIFO来进行裁决。注意发起者是m_sequencer(sequence启动后挂载在的sequencer的句柄),执行语句中UVM_SEQ_ARB是固定的,除此以外可以根据FIFO(默认模式,先后顺序进行执行);WEIGHTED

锁定:锁定语句有两种,一种是lock,一种是grab。(1)sequence在获取到优先级后,如果此sequence进行了lock操作,那么一定会等到他所有的事务发送完才会结束,比如seg1和seg2中同时间每个seg发送三个事务,在一开始需要根据优先级判断谁先发送,但是如果 seq1进行了lock,一旦seq1开始发送了,一定会把他的三个事务发送完后在进行seq2的事务发送,但是需要注意的是对于sequence进行 lock以后,一定要进行unlock(sequence发送完事务)以防止死锁。(2)grab的作用与lock类似,不同的是grab相当于加了一个最高优先级的限定,如果同一时间内有多个sequence进行等待,那么无论别的sequence的优先级如何,只要对该sequence进行了grab操作,那么此时一定是该sequence拿到优先级(3)需要注意的是,进行优先级的重写分配是在每次事务进行发送前,也就是要等单个事务发送完才会进行优先级的重写分配,而这种锁定操作相当于单个事务发送完后的重写优先级分配任然锁定在了被锁定的sequence,这种锁定会持续到该sequence的全部事务发送完。锁定是针对单个事务发送之间的时间间隔,而单个事务发送的过程中任何sequence都无法打断当前事务的发送。

有效性:除了前面提到的lock和grab对sequence的锁定作用,还存在is_relevant/wait_for_relevant任务。对于is_relevant来说,可以通过virtual function bit is_relevant();来定义,如果返回值为1,表示is_relevant任务所在的sequence是有效的,如果返回值为0,则表示是无效的。wait_for_relevant任务的内容往往起是将sequence中无效的条件清除的作用,wait_for_relevant是在sequencer中没有相关的sequence挂载在sequencer上时会自动调用。比如,在某一时刻,seq1和seq2同时挂载在sequencer上,但是seq1中存在is_relevant语句,那么在seq1上的事务发送到一定程度时,is_relevant返回0,seq1失效不再执行,此时seq2继续挂载在sequencer上。经过一段时间后seq2的事务全部发送完。此时sequencer上已经没有sequence挂载,此时会自动调用wait_for_relevant,wait_for_relevant起到消除seq1失效条件的作用,因此seq1再次执行。总的来说,seq1失效是自己控制的,但是重新变得有效起来却是收到其他seq2(其他sequence)的影响。

sequencer和driver(TLM端口连接)

一些基本的点:(1)driver中需要采用forever语句(2)driver和sequencer之间的端口主要其的作用是driver从sequencer中获取req并且返回rsp信号(3)对于sequencer来说,driver获取事务的过程中sequencer往往只是起了一个中间渠道的作用,真正的事务还是源自于sequence的产生(4)uvm_sequencer和uvm_driver这两者实际上都是参数化的类,可以对参数化的类进行设置,如果不设置的化,默认是父类的句柄(也就是数据类型和uvm_sequence_item中的类型相同),需要注意的是,在这种默认传递父类句柄的情况,实际上是把父类句柄传递给了子类对象,并且访问子类对象,此时需要将父类句柄转换成子类的才可以实现对子类对象的访问,需要调用系统函数$cast(5)考虑如下的情况,假如有两个sequence,都往sequencer中发送数据,随后driver从中get到返回rsp,那么在sequencer将相关返回给sequence时由于其中存在两个信号,那么实际该怎么去判断呢?实际上每个rsp都有一个id,通过这个id能将不同sequence发到个sequencer的信号区分开,但是实际上在进行clone时这个id并不复制,因此需要通过rsp执行set_sequence操作得到发送过去的req信号的id(rsp是req克隆出来的,rsp.set_sequence_id(req.get_sequence_id()),也就是将req的id进行set到rsp上),如果不进行上述操作,那么rsp的id就默认没有,此时rsp就会存放在FIFO中而不往sequence传递.

端口:(1)对于driver的端口uvm_seq_item_pull_port #(REQ,RSP) seq_item_port(2)对于sequencer:uvm_seq_item_pull_import #(REQ,RSP) seq_item_port

方法:(1)task get_next_item(output REQ req_arg),driver从sequencer中采取阻塞的方式获取下一个item,req_arg是REQ信号的句柄。(2)function void item_done(input RSP rsp_arg=null),driver将事务消化完的信息发送给sequencer,如果rsp为空的化(RSP信号可以选择,此时 rsp_arg=null,也就表示rsp句柄为空),也就是仅仅实现上述功能;如果rsp不为空的情况下,实际上是将上述信息(rsp信号)同时返回给sequencer中的fifo,随后sequence从FIFO中将上述信息从FIFO中get回去(需要指出的是,如果这里的rsp信号不为空,也就是他会往sequencer中返回信号,那么此时sequence一定要sequencer中get,否则sequencer中的FIFO会写满)。(3)function void put_response(input RSP rsp_arg),这是一个独立的非阻塞的形式发送responce的过程,成功返回1失败返回0,理论上只要有rsp信号就可以发送,有的时候我们没有在item_done 语句中给出具体的RSP地址,也就是没有put reponse的过程,此时可以通过此语句独立的进行put。(4)由于driver和sequencer的端口实际上是 analysis port与analysis import之间的传输,因此可以调用write任务进行传输rsp

通讯时序:start_item(一般立即返回):很多情况下start_item可以立即返回的原因在于仅仅有一个item要挂载在sequnecer上,因此此时sequencer可以立马执行仲裁(wait_for_grant),在item挂载在sequencer上后就可以拿到sequencer的句柄,此后在finish_item中就可以通过前面拿到的句柄执行request的发送和wait_for_item_done操作。finish_item:(1)finish_item是一个阻塞的过程,阻塞他的是randomize和driver中的get_next_item,(2)finish_item的持续:前面两个条件满足后,进入finish_item后,finish_item这个过程持续分为两种情况:假如sequence中没有get_response操作,那么此时finish_item仅仅持续到driver完成got item的操作,就完成了一个loop;假如sequence中存在get_response,finish_item同样是持续到driver完成got item的操作,但是此时一个loop并没有结束,需要等driver完成item_done操作以后一个loop才会结束(也就是不仅仅持续got item,还要持续item done结束)sequencer:sequencer对事务进行操作需要有两个条件:(1)等待sequence将事务准备好(2)等待driver的item_done信号,这有两者都准备好了才会对事务进行操作。而这里的start_item(req)信号实际上就是表示开始准备事务,另外,对于事务来说,需要对其进行随机化,但是事务实际上还没有真正的准备好,因此此时可以进行随机化(也就是req.randomize with(data==10)语句),而随后的finish_item表示事务真正的准备好了,也就是此时开始真正等待sequencer的仲裁。最后的get_response实际上表示的是sequencer主动的从diver一侧获取rsp信号来看其第二个条件是否已经好了(这一语句可有可无,但是get回来的是一个父类的句柄,为了下面对子类rsp进行访问,需要类型转化;另外如果有这个语句,那么在driver一侧就一定要有对rsp信号put的操作)

对事务进行操作时返回的句柄是父类句柄:(1)sequencer进行get_responce操作get回来的句柄是父类的(2)事务的创建过程中采用create_item会返回一个父类的句柄(3)clone函数,rsp是由req克隆而来(4)driver通过get_next_item从sequencer中获取item,如果req没有特殊指定是什么形式的参数化类的化,那么get到的req是父类的句柄(5)m_sequencer是uvm_sequence类型,也就是他是一个父类的句柄,而p_sequencer是子类的句柄(不是UVM中预定义的成员变量,m_sequencer是在事务进行定义时就会存在的,是相应的sequencer的句柄,而p_sequencer是相应sequencer的实例,必须通过前面的句柄对相应sequencer的子类的的sequencer进行访问,但是m_sequencer又是父类的,因此实际上需要进行一个类型转化,在调用`uvm_declare_p_sequencer(mcdf_virtual_sequencer);时就会完成类型的转化,在调用上述宏是实际上完成了两个操作,第一是声明一个该宏所在sequence所挂载的m_sequencer的对象p_sequencer(也就是m_sequencer p_sequencer),完成类型转化$cast(p_sequencer,m_sequencer)

  • 2
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
UVM 中,sequence 的 response 用于表示 sequence 执行的结果或产生的数据。一般情况下,response 是一个包含所需信息的类对象,可以包含任何需要传递给其他组件或者验证环境的数据。 在 sequence 中,可以通过以下方式设置 response: 1. 在 sequence 中定义一个 response 对象,并在需要的时候为其赋值。 2. 将 response 对象作为任务或函数的参数传递给其他组件,以便其他组件可以读取该对象的值。 另外,response 对象通常被定义为一个 transaction 类,其中包含需要传递的所有字段。这样可以方便地传递多个数据项,并且可以轻松地扩展和维护代码。 例如,以下是一个简单的 sequence 示例,展示了如何使用 response: ```systemverilog class MySequence extends uvm_sequence #(MyTransaction); `uvm_object_utils(MySequence) task body(); MyTransaction txn; // 设置 txn 的字段值 // ... // 设置 response response = txn; endtask endclass ``` 在上述示例中,`MyTransaction` 是一个自定义的 transaction 类,它包含了需要传递的字段。在 `body()` 函数中,我们创建了一个 `MyTransaction` 对象 `txn` 并设置其字段的值。然后,将其赋值给 `response` 对象。 这样,在 sequence 执行完成后,其他组件就可以通过读取 `response` 对象来获取执行结果或产生的数据。 希望这个回答能够帮到你!如果还有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值