UVM学习笔记--sequence和sequencer(转)

https://blog.csdn.net/wonder_coole/article/details/90665876

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的相互关系参考下图

image.png

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)

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 hierarchy), 在实践中会广泛用到。 通常对简单基本的测试场景放在一些基础sequence里,复杂场景的sequence均有许多基础sequence组合而成。如下图所示:

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

              1)sequencer和driver声明时要使用基类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间的控制和调度的。

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

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不会运行。

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

参考:

版权声明:本文为CSDN博主「wonder_coole」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/wonder_coole/java/article/details/90665876

你可能感兴趣的:(UVM学习笔记--sequence和sequencer(转))