UVM学习笔记–sequence和sequencer

UVM学习笔记–sequence和sequencer1.UVMsequence机制的意义UVM的sequence机制最大的作用就是将testcase和testbench分离开来。对一个项目而言,testbench是相对稳定的框架,而针对各个module要有不同的测试内容,所以具体的testcase的差异非常大。在UVM中,test和sequence类总是成对出现,实现了testbench和具体的testcase的结合。test类…

大家好,欢迎来到IT知识分享网。

1. UVM sequence机制的意义

UVM的sequence机制最大的作用就是将test case和testbench分离开来。 对一个项目而言,testbench是相对稳定的框架,而针对各个module要有不同的测试内容,所以具体的test case 的差异非常大。在UVM中, test和sequence类总是成对出现,实现了testbench和具体的test case的结合。test类中可以针对具体的测试内容对testbench做一些差异化配置,在sequence类中则是实现test case的具体细节。

所以,大的项目大的团队中,做testbench(test 类以及以下的uvm树结构等)的是一个团队,对具体module做test case的是另一团队。UVM sequence机制很好的支持不同团队任务的的分割和结合。

transaction,sequence,sequencer,driver的相互关系参考下图

Sequence_Transactions

2. UVM sequence细节要点

2.1 sequence内包含的成员函数

sequence启动后,会根据参数设置情况,自动执行pre_start(), pre_body(), parent_seq.pre_do(),parent_seq.mid_do(), body(), parent_seq.post_do(),  post_body, post_start()等函数/任务。

seq.start (m_sequencer, null, , 1);
 
// The following methods will be called in start()
seq.pre_start();            (task)       
seq.pre_body();             (task)  if call_pre_post == 1
   parent_seq.pre_do()      (task)   if parent_seq != null
   parent_seq.mid_do(this)  (func)   if parent_seq != null
seq.body()                  (task)   your code
   parent_seq.post_do(this) (func)   if parent_seq != null
seq.post_body()             (task)   if call_pre_post == 1
sub_seq.post_start()        (task)

2.2 sequence的三种启动方式

(1)使用start任务.

task my_case0::main_phase(uvm_phase phase);
   case0_sequence cseq;
   cseq = new("cseq");
   cseq.starting_phase = phase;
   cseq.start(env.i_agt.sqr);
endtask 

(2)使用uvm_config_db#(uvm_object_wrapper)配置default_sequence

uvm_config_db#(uvm_object_wrapper)::set(this,
                                    "env.i_agt.sqr.main_phase",
                                    "default_sequence",
                                    case0_sequence::type_id::get());

(3)使用uvm_config_db#(uvm_sequence_base)配置default_sequence

function void my_case0::build_phase(uvm_phase phase);
    case0_sequence cseq;
    super.build_phase(phase);

    cseq = new("cseq");
    uvm_config_db#(uvm_sequence_base)::set(this,
                                           "env.i_agt.sqr.main_phase",
                                           "default_sequence",
                                           cseq);
endfunction

该示例在uvm1.1可以正常运行,uvm1.2中有问题。

default_sequence最终也是调用sequence的start任务。在uvm_sequence_base基类中的start任务原型是:

virtual task start (
   	uvm_sequencer_base 	sequencer,	  	
   	uvm_sequence_base 	parent_sequence	 = 	null,
   	int 	this_priority	 = 	-1,
   	bit 	call_pre_post	 = 	1
)

parent_sequence控制这三个任务/函数会不会执行: parent_seq.pre_do() (task),parent_seq.mid_do(this) (func),parent_seq.post_do(this) (func) 。

call_pre_post控制着pre_body,post_body任务的执行与否。

this_priority是发送给sequencer的transaction的优先级值。只能是大于-1的整数。

2.3 sequence的仲裁机制

在同一个sequencer上可以启动多个sequence,每个sequence在启动时可以指定一个priority值,priority值越大优先级越高。设置方法:

`uvm_do_pri(m_trans, 100)
`uvm_do_pri_with(m_trans, 200, {m_trans.pload.size < 500;})

sequencer默认的仲裁算法是SEQ_ARB_FIFO。所有仲裁算法有6种:

  • UVM_SEQ_ARB_FIFO : 按transaction的到达sequencer的顺序,先收先处理(FIFO),不考虑优先级(default)
  • UVM_SEQ_ARB_RANDOM  : 完全从待处理的仲裁队列中随机选择,而无视它们的抵达顺序和优先级。
  • UVM_SEQ_ARB_STRICT_FIFO : 严格按照优先级顺序,从仲裁队列中选择优先级最高的transaction执行。如果仲裁队列中有多个相同优先级的,则按FIFO原则选择其中最先到达的。 
  • UVM_SEQ_ARB_STRICT_RANDOM : 严格按照优先级顺序,从仲裁队列中选择优先级最高的transaction执行。如果仲裁队列中有多个相同优先级的,则从中随机选择。 
  • UVM_SEQ_ARB_WEIGHTED  : 这是最难理解的一个,其结果就是高优先级的sequence有更大可能获得sequencer的仲裁权。具体的仲裁过程是:

           a.将仲裁队列中所有transaction的priority值求和得到sum值
           b.之后随机产生一个在0到sum之间的一个threshold值
           c.从仲裁队列的最前面开始,依次对每个transaction计算priority的加权值(将所有该transaction之前的priority值求和)
           d.取加权值最先大于threshold的那个transaction

  • UVM_SEQ_ARB_USER  : 使用用户自定义的仲裁方法.

注意: 在UVM 1.2, 带这些宏“UVM_”前缀; 在 UVM 1.1,不带“UVM_“前缀.

使用sequencer的成员函数set_arbitration来配置优先级算法:

 env.i_agt.sqr.set_arbitration(SEQ_ARB_STRICT_FIFO);

 仲裁算法详细讲解可参考:UVM Tutorial for Candy Lovers – 26. Sequence Arbitration

2.4 sequence对sequencer的占用或失效

1. 用lock()和grab()排他性占用sequencer

在sequence的body()中使用lock()…unlock()或grab()…ungrab(), 可以让在其中间发送的所有transaction连续的获取sequencer的仲裁,从而连续的发送到driver而不被别是sequence打断。 

二者的区别仅在仲裁机制不一样:
                 lock会放在仲裁队列的末尾,sequencer将原来就在仲裁队列里的transaction发送完后才执行lock的transaction,一旦lock占用sequencer后,只有unlock后才释放。
                 grab会放在仲裁队列的最前面,sequencer会立即执行grab的transaction,同样一旦grab占用sequencer后,只有ungrab后才释放。

     grab实时性强,是插队操作。unlock则不是。

 2. sequence的is_revelant函数和wait_for_relevant任务。

  • sequencer在仲裁时, 会查看sequence的is_relevant函数的返回结果, 为1说明此sequence有效并参加仲裁, 否则无效。
  • 可以通过重载is_relevant函数来使sequence失效。
  • 当sequencer将所有有效transaction发送完毕后,它会调用处于无效状态的sequence的wait_for_relevant任务。当wait_for_relevant返回后,sequencer还会继续调用is_relevant函数。
  • 在wait_for_relevant任务中,user一定要清除sequence的无效状态。否则系统会进入死循环。

2.5 sequence里的宏及start/finish_item任务

1.uvm_do 相关宏

  • 在sequence的body中使用uvm_do系列宏,可以自动完成transaction的创建,随机化和发送。
  • uvm_do系列宏对transaction和sequence都能支持。
  • 如参数是transaction时它会调用start_item&finish_item任务。
  • 如果是sequence,它会调用start任务。
  • 所有uvm_do宏均由uvm_do_on_pri_with而来,如下图示:
`uvm_do(SEQ_OR_ITEM)
`uvm_do_pri(SEQ_OR_ITEM, PRIORITY)
`uvm_do_with(SEQ_OR_ITEM, CONSTRAINTS)
`uvm_do_pri_with(SEQ_OR_ITEM, PRIORITY, CONSTRAINTS)

//uvm_do_on 发到指定的sequencer
`uvm_do_on(SEQ_OR_ITEM, SEQR)
`uvm_do_on_pri(SEQ_OR_ITEM, SEQR, PRIORITY)
`uvm_do_on_with(SEQ_OR_ITEM, SEQR, CONSTRAINTS)
`uvm_do_on_pri_with(SEQ_OR_ITEM, SEQR, PRIORITY, CONSTRAINTS)

uvm_do macros

2. uvm_create及uvm_send系列宏

  • 如果需要在sequence中灵活可控的产生及发送transaction,可以uvm_create及uvm_send系列宏。和uvm_do相比更灵活。
  • `uvm_create(SEQ_OR_ITEM): 工厂模式创建一个transaction或sequence,只后用户应该随机化,或指定某个值给创建的trans。
  • `uvm_create(SEQ_OR_ITEM,SEQR): 和uvm_create宏一样,并且指定了对应的sequencer。
  • `uvm_send(SEQ_OR_ITEM): 将有uvm_create创建的tr/seq发送出去,其他send宏如下:
`uvm_send_pri(SEQ_OR_ITEM, PRIORITY)

`uvm_rand_send(SEQ_OR_ITEM)
`uvm_rand_send_pri(SEQ_OR_ITEM, PRIORITY)
`uvm_rand_send_with(SEQ_OR_ITEM, CONSTRAINTS)
`uvm_rand_send_pri_with(SEQ_OR_ITEM, PRIORITY, CONSTRAINTS

3. start_item和finish_item任务。 

  • `uvm_do及`uvm_send宏的最终实现均是依靠start_item和finish_item任务来实现。调这两个任务可指定优先级参数。
  • start_item里会调用wait_for_grant及parent_seq.pre_do任务。
  • finish_item里会调用mid_do,send_request,wait_for_item_down及post_do等
create_item(item)                                   \
sequencer.wait_for_grant(prior) (task) \ start_item  \
parent_seq.pre_do(1)            (task) /              \
                                                        `uvm_do* macros
parent_seq.mid_do(item)         (func) \              /
sequencer.send_request(item)    (func)  \finish_item /
sequencer.wait_for_item_done()  (task)  /
parent_seq.post_do(item)        (func) /

 2.6 sequence的嵌套/层次化,继承/派生

嵌套(nested)即在sequence的body中使用另外已经定义好的sequence。当然需要他们均有一致的transaction类型。执行顺序参考下图。seq2在seq1的body中启动,seq1.start()中参数parent_sequence为空,seq2.start()中参数parent_sequence为this。

sequence flow

常用的嵌套,也称作层次化(sequence hierarchy), 在实践中会广泛用到。 通常对简单基本的测试场景放在一些基础sequence里,复杂场景的sequence均有许多基础sequence组合而成。如下图所示:

Sequence_Hierarchy

正常一种sequence->sequencer->driver只支持一种类型的trans,如果要在同一sequence中向sequencer发出不同类型的trans,可以:

                  1)sequencerdriver声明时要使用基类uvm_sequence_item

                  2)driver,需要将从seq_item_port收到的req使用cast进行向下类型转换。

继承:一般把公用的函数或任务放在一个base_sequence,其他sequence均从它派生出来。

3.virtual sequence/sequencer

3.1 p_sequencer

sequence类里有一个uvm_sequencer_base类型m_sequencer指针,当sequence和sequencer关联后,m_sequencer会自动指向该sequencer,但通过m_sequencer不能直接使用seqr里的变量,否则会出现编译错误。只能使用cast强制向子类转换后,才能通过m_sequencer.xxx来访问该seqr内的xxx变量。

UVM引入p_sequencer,可以自动的实现上面所述的cast动作,从而可以在sequence中自由使用关联sequencer内的变量。

p_sequencer并不是UVM自动地在sequence的创建的,需要用户使用`uvm_declare_p_sequencer宏声明,之后UVM会自动实现指向seqr及cast的动作。

3.2 什么是virtual sequence/sequencer

在实际应用中,dut往往是很复杂的系统,不单单只有一种接口。而我们testbench中的driver只能驱动一种接口,对应一种transaction的sequence。如果需要对多个接口同时进行激励,就需要的virtual sequence/sequencer。

Virtual sequencer 的特点:含有sub sequencer的句柄,用以控制这些sub sequencer。它并不和任何driver相连 Virtual sequencer,本身并不处理具体的trans/seq。

Virtual sequence 的作用:和virtual sequencer相关联的就是virtual sequence,它的作用是协调不同的subsequencer中sequence的执行顺序。

Virtual 的含义: 这里的virtual 区别用 system Verilog中用在function/task/class声明前,用于修饰的virtual。virtual sequence/sequencer的virtual主要是指这种sequence/sequencer不像直接作用在具体driver上的sequence/sequencer,它不处理具体的transaction,主要是来做不同类型sequence间的控制和调度的。

UVM学习笔记--sequence和sequencer

3.3 实现virtual sequence/sequencer的步骤

    1 :定义virtual sequencer,里面包含各个env里的子sequencer类型的指针。   
    2 :在base_test里实现virtual sequencer的例化,和sub sequencer的连接。
        > base_test作为uvm_test_top,即uvm树形结构的最顶层,负责chip_env和virtual sequencer的规划。 
        > 实例化virtual sequencer。
        > 将virtual sequencer与各env的sequencer连接在一起,具体实现是通过function connect_phase中将virtual sequencer的中个sub sequencer的指针,指向各个具体的sequencer。
    3 :定义virtual sequence。 
        > 在里边对多个sequence实例化。
        > 要声明指向对应的virtual sequencer的p_sequencer,用于使用virtual sequencer中的sub sequencer句柄。
        > 利用`uvm_do_on类型宏,在指定的sub sequencer上运行具体的sequence。 
        注意:`uvm_do这样的宏,针对的处理对象,不仅仅是transaction,还可以处理sequence。
    4 :在具体test定义中,利用uvm_config_db将virtual sequencer的default_sequence设置为具体的virtual sequence。

  • *良好的代码规范是只在顶层virtual sequence中raise/drop objection,避免在各底层sequence中使用raise/drop objection引起的混乱。

4. 在sequence中使用config_db

4.1  sequence的完整路径

除了在uvm树中的component中用config_db外,在sequence中也可以用,虽然sequence是一个uvm_object类。用config_db时,关键在path的参数,一个sequence的完整路径,均是有该sequence关联的sequencer和该sequence的实例对象名组成。例如如:
                   sequencer的路径  +  sequence实例名
 
            uvm_test_top.env.a_agent.a_sequencer.test0_sequence

4.2 在sequence中get参数

   1. 在test中设置sequence中参数count如:
               uvm_config_db#(int)::set(this, “env.i_agt.sqr.*”, “count”, 9); 
        注意: 由于sequence实例化名字不固定,路径中对应的sequence实例化名字要使用通配符。
   2. 在sequence中使用get:  
             uvm_config_db#(int)::get(null,get_full_name(),”count”,count); 
        注意:其中第一个参数不能使用this指针,因为sequence不是component,是能使用null/uvm_root::get();

4.3 在sequence中向component或sequence中set参数

   1. 在sequence的body中set参数:
         uvm_comfig_db#(bit)::set(uvm_root::get(),”uvm_test_top,env.scb”,”en_compare”,1);
  2. 在component/sequence中get的方法:
       由于component都是在build phase中调用get函数,如果需要在main phase中get该参数,就需要使用wait_modified任务不停检测参数的变化,然后再使用get获取参数。

      fork
        //在这个进程中不停检测参数的变化
        while(1)  begin
            uvm_comfig_db#(bit)::wait_modified(this,“”,“en_compare”)
            void`(uvm_config_db#(bit)::get(this,"","en_compare",en_compare));
        end
        
        // 该component的主体内容
        begin
          ......
        end        
      join      

5. sequence的response

5.1 成对使用get_reponse/put_reponse任务:

   1. 在sequence中使用uvm_do后,使用get_reponse(rsp)获得从driver返回的reponse
   2. 在driver中,返回一个rsp的方法:

 while(1) begin
     seq_item_port.get_next_item(req);
     drive_it_to_vif(req);
     rsp = new("rsp")  // 创建 rsp
     rsp.set_id_info(req);// 将req的id等信息复制给rsp
     seq_item_port.put_response(rsp); // 返回rsp
     seq_item_port.item_done();  // 也可省掉put_reponse的调用,直接使用item_done返回,如:
                                 // seq_item_port.item_done(rsp)
 end

5.2 返回多个response

driver可以一次返回多个response,要在driver中多次调用put_response,在sequence中多次调用get_response.
 sequencer内部为rsp维护一个队列,默认深度为8,所以多次返回超过8各rsp,有溢出的风险。

5.3 response的类型

response的类型默认是和request的类型一样的,如需返回不同类型的response,需要在声明sequence->sequencer->driver类时传入req和rsp两种参数类型。

5.4  非阻塞形式获取response

在sequence中使用get_response获取rsp,是阻塞的方式。如需非阻塞方式获取,需要使用response_handler,它会自动的在另外一进程中接受response.
    1.在sequence中的pre_body()中使用use_reponse_handler()打开该功能。
    2.重载response_handler函数(类似中断服务程序,需要写入对rsp处理的内容),注意:要在里面使用cast强制类型转换

6. sequence library

flow of sequence from library to driver

6.1 seq lib是sequence的集合,也是继承自uvm_sequence基类:

   class uvm_sequence_library #(type REQ=uvm_sequence_item,RSP=REQ) extends uvm_sequence #(REQ,RSP);
    1.声明seq lib时要指明所产生的transaction类型, 
    2.seq lib 的new函数中要调用init_sequence_library
    3.要调用uvm_sequence_library_utils将seq lib注册。
    4.在单个sequence中要使用宏uvm_add_to_seq_lib将其加入指定seq lib中。
    5.可以一对多,也可多对一
    6.将seq lib设为sequencer的default sequence

其他将seq加入seq lib的方法可参考: UVM Sequence Library – Usage, Advantages, and Limitations

6.2 选择seq的算法:

    UVM_SEQ_LIB_RAND: 完全随机
    UVM_SEQ_LIB_RANDC: random cycle order,先随机排序,再按顺序执行,再保证每个seq执行一遍后,执行剩余次数。
    UVM_SEQ_LIB_ITEM: 不执行里面的seq,而是自己产生trans,等同于一普通seq
    UVM_SEQ_LIB_USER: 用户自定义,需重载select_sequence函数。
    使用config_db 配置方法:

uvm_config_db#(uvm_sequence_lib_mode)::set(this,
                                          "env.i_agt.sqr.main_phase",
                                          "default_sequence.selection_mode",
                                           UVM_SEQ_LIB_RANDC);  
  • 设置seq的执行次数:min_random_count & max_random_count, 默认都是10

6.3 sequence library的执行过程

seqence library和sequence,也是调用start任务执行,只后顺序执行seq lib的pre_start,pre_body, body,post_body,post_start。在seq lib的body中,按照配置的random的方式启动各个sequence。在调用start任务启动sequence时,给参数call_pre_post传入0值,因此每个sequence的pre_body 和 post_body不会运行。

UVM学习笔记--sequence和sequencer

6.4 使用uvm自带的sequence_library_cfg 统一配置lib里的参数


参考

  1. UVM实战(卷1) (张强 著)
  2. uvm_users_guide_1.2.pdf in uvm_1.2 release
  3. UVM Tutorial for Candy Lovers – 26. Sequence Arbitration
  4. www.learnuvmverification.com : UVM Sequences and Transactions Application
  5. How to execute sequences via start()
  6. UVM Sequence Library – Usage, Advantages, and Limitations

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/11933.html

(0)

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信