uvm中sequence机制详解

在uvm中加入了一个重要的机制即sequence机制用于产生激励将trans、driver的功能进一步简单化,以实现最大程度的复用。其中sequence机制包括sequence和sequencer。

一、sequence机制实现通信

1、第一步

创建sequence类继承于uvm_sequence,创建sequencer类继承于uvm_sequencer代码如下:

class my_sequence extends uvm_sequence #(my_trans);
    `uvm_object_utils(my_sequence)
    my_trans tr;
    
    function new(string name = “my_sequence”);
        super.new(name);
    endfunction

    virtual task body();
        repeat(10) begin
            `uvm_do(tr);   //start sequence
        end
        #1000;
    endtask
    
endclass
class sequencer extends uvm_sequencer#(my_trans);
    `uvm_component_utils(my_sequencer)
    function new(string name ="my_sequencer");
        super.new(name);
    endfunction
    
endclass

我们可以看到sequence是属于object,而sequencer属于component,并且在其中没有进行连接

2、第二步

在顶层例化并连接

  • 完整的传输路径是sequence产生一个随机化过的trans,将其发送到sequencer中,sequencer将其送到driver中,driver再将其驱动到dut,这样一个完整的数据激励产生并传输的路径便已经完整。因此我们需要将trans与sequence连接,sequence与sequencer连接,sequencer与driver分别连接。

如代码一所示在sequence中例化trans并传参进入sequence,sequence在顶层通过启动start函数可以与对对应的sequencer连接(也可以通过设置sequencer的default_sequence以及使用p_sequencer指向对应的sequencer,再start调用的方式),同时在顶层也会通过内置的port端口完成drv与sqr的连接,示例代码如下:

function void my_agent::connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    if(is_active == UVM_ACTIVE) begin
        drv.seq_item_port.connect(sqr.seq_item_export); //注意顺序,port在前
    end
    
endfunction
task my_env::main_phase();
    my_sequence seq;
    phase.raise_objection(this);
    seq = my_sequence::type_id::create("seq");
    seq.start(my_agent.aqr);
    phase.drop_objection(this);
endtask

3、第三步

实际传输

  • 实际传输过程中,seq向sqr发送数据前会先发送请求,sqr会将该请求放入仲裁队列中,sqr会时刻检测drv以及seq是否发送请求,当drv以及seq一起发送请求时,数据传输开始,当只有seq发送请求时,sqr会将该请求放入仲裁队列中并等待drv的请求在开始传输数据,而只要收到drv的请求,sqr立马进入等到seq请求的状态,seq请求一到立刻开始传输。

seq请求产生的方式为启动,而drv通过seq_item_port.get_next_item(req)来产生请求,实际关系如下图:

二、sequence启动方式

2.1 start手动启动

配合`uvm_do宏或者p_sequencer挂载到start的m_sequencer中即可启动seq到指定的seq,或者只执行start_item finish_item

如上面代码所示,可以通过例化seq并挂载到sqr的方式进行手动启动:

my_sequence seq;
    phase.raise_objection(this);
    seq = my_sequence::type_id::create("seq");
    seq.start(my_agent.aqr);
    phase.drop_objection(this);

  • sequence.strat(sequencer,parent_sequence,优先级) ,第一个参数是需要挂载的sequencer;第二个是parent_sequence,一般传入this或者不传入;第三个是优先级;第四个call_pre_post默认为1,则自动执行pre_body/ post_body()函数
  • start 执行 pre_start,body等函数。此时就完成了sequence的启动过程。
  • 所有sequence都要在sequencer中启动,当sequence启动的时候, 在sequence的 task start 内,会调用 set_item_context() 函数,在 set_item_context() 函数内,会调用 set_sequencer() 函数完成挂载,给m_sequencer赋值
  • set_sequencer 函数 会使m_sequencer 句柄指向执行当前 sequence 的 sequencer,即指向 env.vsqr
  • 如果没有指定挂载的sequencer,则挂载到parent_sequence的sequencer上
     

其中`uvm_do宏分为:

参考(UVM基础-sequence系列宏_uvm_do_Lucky是一名ICer的博客-CSDN博客

这个系列宏实际上集成度较为高,因为宏定义并没有给出seq/tr在哪个sequencer上启动,实际上,该系列宏都是从默认的m_sequencer上启动的,也就是通过seq.start函数传递的sqr指针。

uvm_do更多的是用在一个父sequence对子sequence的启动上:父sequence的do函数会被执行,同时不需要执行子sequence的pre_body()和post_body()。

这些宏可以写在seqeunce中的body函数里,用于发送tr或者做sequence的嵌套使用。

2.2 `uvm_do_on

`uvm_do_on系列宏可以指定sequence或者transaction在哪个sqr上启动,特别是如果系统中包含多个sequencer,顶层通过virtual sequencer控制,可以通过在sequence中使用`uvm_declare_p_sequencer(my_sqr)来指定sequence的启动位置(此时p_sequencer指向my_sqr,可以调用其中内容)。`uvm_do_on系列宏包括:

 事实上,无论是`uvm_do系列宏还是`uvm_do_on系列宏,底层都是`uvm_do_on_pri_with这个参数最全的宏定义,不一样的宏分别对这个宏做了参数固化,比如`uvm_do:

`uvm_do_on_pri_with(tr_or_seq, m_sequencer, -1, {})      

此时,sequencer默认传递为m_sequencer,优先级为-1,即不具备优先级,约束为空,不具备约束。同理,其他宏也在此宏定义上做了参数的固化:
最后,我们来分析一下`uvm_do_on_pri_with的源码:

 这段源码先申明了一个uvm_sequence_base类型的_seq,然后通过`uvm_create_on宏将其实例化出来,并指定在参数SEQR上实例化(指定m_sequencer为SEQR),然后有一个if条件,思考一下,如果SEQ_OR_ITEM参数此时如果是transcation,会发生什么?

       这个问题的答案很简单,如果是transaction,cast会返回0,条件成立,执行start_item,此时start_item参数为指定的transaction,并设定优先级。为什么cast会为0?还记得transaction继承自哪个类吗?实际上,transaction继承自uvm_sequence_item,也就是uvm_sequence_base的父类,因此父类的继承类cast给子类的继承类,当然无法cast,返回0。

       中间判断了如果transaction执行不了randomize,会报warning,可以不用管它,然后又是一段判断,条件和上一个if一致,如果是transaction,执行finish_item,指定transaction和优先级。如果参数是seq,那么else分支会调用seq的start函数。

分析过后,实际上可以得到一个总结:

  • `uvm_do系列宏的底层是`uvm_do_on_pri_with宏,只不过将参数进行了固化;
  • `uvm_do_on_pri_with宏会实际上是调用start_item和finish_item方法,并且只有在参数是transaction的时候会调用。
  • 如果参数是sequence,会调用sequence的start任务,start任务会接着调用sequence的pre_body,body,post_body,如果子sequence的body中的uvm_do系列宏传递的是transaction,那么会调用start_item和finish_item。
  • 如果参数是sequence,最终会调用transaction为参数的uvm_do系列宏,并落在start_item和finish_item任务上。
  • start_item和finish_item任务的参数只能是transaction,不能是sequence。

事实上,除了uvm_do系列宏之外,sequence还提供了专门用于发送sequence系列宏,主要包括:uvm_do_seq,uvm_do_seq_with,这两个宏底层是uvm_do和uvm_do_with宏,对其参数进行了固化,并且只能传参seq,并且需要指定sqr。用的比较少,不过多讨论。

2.3 default_sequence自动启动

est中将sequence通过config_db机制set到相应的sequencer(virtual sequencer)的main_phase中

在build_phase中将某个sequence配置成某个sequencer的动态运行的phase中(如main_phase)的default_sequence, 那么在仿真执行到main_phase的时候,squencer的default_sequence就会启动

// 方式一

function void my_case0::build_phase(uvm_phase phase);
	super.build_phase(phase);
	`uvm_config_db#(ubm_object_wrapper)::set(this,  // 第一个参数是指定seqr的路径        
					"env.in_agent.sqr.main_phase",  // 第二个参数是指定要执行的phase阶段
					  		   "default_sequence",  // 第三个参数是指定你的seq
				   sequence_base::type_id::get());  // uvm_config_db设置default_sequencer
endfunction
// 方式二

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

最后谈一下default_sequence的启动方式。这种启动方式来自OVM,通过将某个sequence配置为default_sequence,可以使得该sequence在仿真开始之后被自动启动。

之所以不建议用default_sequence,大致有这么一些原因:

  • 设置default_sequence是在connect_phase或者build_phase中完成,这就意味着将测试用例跟Testbench的构建混在了一起,在环境架构上不干净;
  • 使用default_sequence隐藏了太多信息,比如用户并不是很明确sequence在哪个时候被启动,甚至不知道是不是被其他同事配置了default_sequence,一旦出问题很难调试;
  • 无法在同一个sequencer上启动其他sequence,而这个需求又是经常存在的。

三、uvm宏使用

3.1 uvm_create以及uvm_send

3.1.1 uvm_create以及uvm_create_on

除了使用uvm_do系列宏可以发送激励之外,uvm还提供了`uvm_create或者`uvm_create_on,用于生成transaction的实例,然后结合`uvm_send宏将transaction发送出去。`uvm_create(seq_or_item),参数是sequence或者transaction,`uvm_create_on(seq_of_item, SEQR),参数有两个,除了要传输的sequence或者transaction之外,还有一个sequencer的指针。实际上,`uvm_create宏的底层就是`uvm_create_on宏:

这个宏先申明了一个uvm_object_wrapper的变量w_,这个类和factory机制有关,然后通过调用SEQ_OR_ITEM的get_type函数返回uvm_object_wrapper的指针,赋值给w_,然后通过cast方法,调用create_item函数,将实例化的类赋值给SEQ_OR_ITEM,那么关键在于create_item:

这个函数位于基类uvm_sequence_base中,会先声明uvm_factory,然后通过create_object_by_type的方式实例化类,实际上是通过类名字符串进行创建实例,并cast给create_item返回值。因此`uvm_create的创建方式,实际上是调用了factory机制进行实例创建,因此是支持factory机制的重载的。

那么也可以给出总结:

  • `uvm_create的底层是`uvm_create_on,对其参数进行了封装
  • `uvm_create的逻辑是通过factory机制创建sequence或者item的实例,支持sequence的重载和item的重载
  • 用这个宏的方式创建实例,等价于seq_or_item::type_id::create(“name”)创建实例的方式。
  • 如果使用new函数创建seq或者item的实例,那么不支持重载。

另外,还有一个专门用于生成seq的宏:uvm_create_seq,底层是uvm_create_on宏,对参数做了固化,用的比较少,不做过多讨论。

3.1.2 uvm_send以及uvm_send_pri

这个宏定义用于发送sequence或者item,用法是直接将seq或者item作为宏的参数传递给`uvm_send,`uvm_send_pri宏除了要传递seq或者item之外,还要指明优先级,实际上,`uvm_send的底层是`uvm_send_pri,只不过优先级传参为-1:

 分析一下`uvm_send_pri的源码:

  这个宏和uvm_do系列宏的逻辑一样,如果是item会调用start_item和finish_item,如果是sequence,会调用sequence的start函数。

       值得注意的是uvm_send宏不会对transaction进行随机化,因此如果有随机需求,需要在宏外调用randomize对tr或者seq进行随机,然后再使用uvm_send。


3.1.3 uvm_rand_send

   这个系列宏可以对transaction进行随机化,并发送,也就是在uvm_send的基础上集成了seq或者tr的随机。这个系列宏有四个:

    事实上,这几个宏的底层都是`uvm_rand_send_pri_with,其他宏在其基础上做了参数固化,对于`uvm_rand_send_pri_with的源码分析,逻辑和其他的宏一样,只不过这个宏除了调用start_item和finish_item之外,还需要调用get_sequencer来获取sequencer,因此,这一系列宏需要和uvm_create系列宏配合使用。

3.2 start_item和finish_item

如果不想用sequence的宏来声明产生transaction的调度,用户也可以直接调用start_item和finish_item方法,启动transaction的调度,需要注意的是,这个方法只能用来传输transaction,不能用来传输sequence,而且在传输transaction之前,需要先实例化transaction,无论是直接调用new函数的方式,还是调用factory机制实例化。

       transaction可以进行随机,需要单独写出来,因为start_item和finish_item不具备随机功能。可以写在两个方法之前,也可以写在两个方法中间。

       start_item和finish_item可以指定优先级,作为第二参数传递,如果不指定优先级,那么优先级参数默认为-1,即不具备优先级。通过一段代码说明这个的用法,假设有sequence:

1.	class my_sequence extends uvm_sequence(uvm_sequence_item);
2.	        `uvm_object_utils(my_sequence)
3.	        `uvm_declare_p_sequencer(my_sequencer)
4.	 ......
5.	        virutal task body();
6.	              my_transaction my_tr;
7.	              my_tr = new("my_tr");
8.	              //my_tr = my_transaction::type_id::create("my_tr");
9.	              //`uvm_create(my_tr) or `uvm_create_on(my_tr, p_sequencer);
10.	              assert(my_tr.randomize() with {my_tr.data_size==200;});
11.	              start_item(my_tr);
12.	              //start_item(my_tr, 200); //指定优先级
13.	              //assert(my_tr.randomize() with {my_tr.data_size==200;});//随机化可以写在中间
14.	              finish_item(my_tr);
15.	              //finish_item(my_tr, 200);//指定优先级
16.	        endtask:body()
17.	......
18.	endclass

  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 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、付费专栏及课程。

余额充值