UVM的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每一次互动的最小粒度内容。

  • driver和sequencer之间的TLM通信参数是sequence item类

  • sequencer从sequence拿到的item的类型,和sequencer发送给drver,以及driver接收到的数据类型,必须是严格一致的。

  • uvm_component_item和uvm_sequence都是基于uvm_object,它们不同于uvm_component只应当在build阶段作为UVM环境进行创建和配置,而是可以在任何阶段创建。
    在这里插入图片描述

  • 明确划分模块职责的话,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并且发送出去。
class flat_seq extends uvm_sequence;
  rand int length;  //随机变量
  rand int addr;
  rand int data[];
  rand bit write;
  rand int delay;  
  constraint cstr{  //随机变量constraint
    data.size() == length;
  foreach(data[i]) soft data[i] == i;
  soft addr == 'h100;
  soft write == 1;
  delay inside {[1:5]};
  };
  `uvm_object_utils(flat_seq) //factory注册
  ...                         //省略的new函数
  task body();
    bus_trans tmp;
    foreach(data[i]) begin
      tmp = new();
      tmp.randomize() with {
        data == local::data[i];
        addr == local::addr + i<<2;
        write == local::write;
        delay == local::delay;
      };
      tmp.print();
    end
  endtask
endclass
 
class bus_trans extends uvm_sequence_item;
  rand bit write;
  rand int data;
  rand int addr;
  rand int delay;
  static int id_num;
  `uvm_object_utils_begin(bus_trans)
    `uvm_field_int...
   `uvm_object_utils_end
  ...
endclass

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

上面的代码,是在flat_sequence中,不断去new一个bus_trans,然后给他赋值data、addr、write和delay。 可以将一段完整发生在数据传输中的、更长的数据都“收编”在一个bus_trans类中(item 中),提高这个item粒度的抽象层次。

hierarchical sequence

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

class hier_seq extends uvm_sequence;
  `uvm_object_utils(hier_seq)
  function new(string name = "hier_seq");
    super.new(name);
  endfunction
  task body();
    bus_trans t1,t2;
    flat_seq s1,s2;
    `uvm_do_with(t1,{length == 2;})
    fork
      `uvm_do_with(s1, {length == 5;})
      `uvm_do_with(s2, {length == 8;})
    join
    `uvm_do_with(t2, {length == 3;})
  endtask
endclass

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

sequencer与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_port 和sequence::seq_item_export 就行了。即在connect_phase中通过:driver::seq_item_port.connect(sequencer::seq_item_export) 完成。这一对端口功能主要用来实现driver与sequence的request获取和response返回。

通常情况下,只需要连接 driver::seq_item_port 和sequence::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

事务传输实例

 
//bus transaction item definition
class bus_trans extends uvm_sequence_item;
  rand int data;
  `uvm_object_utils_begin(bus_trans)
    `uvm_field_int(data, UVM_ALL_ON)
   `uvm_object_utils_end
   ...
endclass
 //声明sequence并
class flat_seq extends uvm_sequence;
  `uvm_object_utils(flat_seq)
  ...
  task body();
    //创建了一个sequence item tmp
    uvm_sequence_item tmp;
    bus_trans req,rsp;
    //调用函数 create_item, 接下来生成的item, 挂载到m_sequence上
    tmp = create_item(bus_trans::get_type(), m_sequencer,"req");
    //create_item返回的是一个父类的句柄,需要通过cast转换成子类
    void'($cast(req, tmp));
    //若在创建的时候没有上面声明item挂载的sequence,则在start_item阶段,默认挂载至sequence所在的sequencer下
    start_item(req);
    //倘若没有做子类父类的转化,那么不能访问子类对象且不说,在父类进行randomize的时候,由于randomize是虚方法,父类调用该方法同时也会对子类的成员变量进行随机化。
    req.randomize with {data == 10;};
    `uvm_info("SEQ", $sformat("sent a item \n %s", req.sprint()), UVM_LOW)
    finish_item(req);
    //ret_response方法原型中,get的变量是父类句柄,所以为了能访问子类方法sprint(),下面还需要做父类到子类的转换
    get_response(tmp); //driver消化完item,发送一个item_done的response,若item_done(rsp)有句柄送到sequence的fifo中就可以通过get_response来获取句柄,否则不能获取(会被get_response blocking住)。
    void'($cast(rsp, tmp));
    `uvm_info("SEQ", $sformat("got a item \n %s", rsp.sprint()), UVM_LOW)
  endtask
endclass
 

接上面的代码:

class sequencer extends uvm_sequence;
  `uvm_component_utils(sequencer)
  ...
endclass
//在drv声明端口,并实现get和返回rsp的方法
class driver extends uvm_driver;
  `uvm_component_utils(driver)
  ...
  task run_phase(uvm_phase phase);
    REQ tmp;
    bus_trans req,rsp;
    seq_item_port.get_next_item(tmp);
    //拿到父类的句柄,需要转化成子类的对象
    void'($cast(req, tmp));
    `uvm_info("DRV", $sformatf("got a item \n %s", req.sprint()), UVM_LOW)
    void'(cast(rsp, req.clone()));//子类方法的clone,克隆完返回的还是父类的句柄,需要转化成子类
    rsp.set_sequence_id(req.get_sequence_id());
    rsp.data += 100;
    seq_item_port.item_done(rsp);
    `uvm_info("DRV", $sformatf("sent a item \n %s", rsp.sprint()), UVM_LOW)
  endtask
endclass
 //在环境里链接drv和sqr
class env extends uvm_env;
  //在顶层声明
  sequencer sqr;
  driver drv;
  `uvm_component_utils(env)
  ...
  function void build_phase(uvm_phase phase);
    //在build_phase创建
    sqr = sequencer::type_id::create("sqr", this);
    drv = driver::type_id::create("drv", this);
  endfunction
  function void connect_phase(uvm_phase phase);
    //在connect_phase连接,连接第一对端口,第二对端口可以不连
    drv.seq_item_port.connect(sqr.seq_item_export);
  endfunction
endclass
//在uvm_tes里对sequence进行挂载
class test1 extends uvm_test;
  env e;
  `uvm_component_utils(test1)
  ...
  function void build_phase(uvm_phase phase);
    //例化env
    e = env::type_id::create("e", this); 
  endfunction 
  task run_phase(uvm_phase phase);
    flat_seq seq;
    phase.raise_objection(phase);
    seq = new();  //创建一个sequence
    seq.start(e.sqr); //将sequence挂载到env中的sequencer上
    //sequence挂载到sequencer上之后,就会自动运行其中的body任务了
    phase.drop_objection(phase);
  endtask
endclass

在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。

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。

在环境里链接drv和sqr

drv.seq_item_port.connect(sqr.seq_item_export);

在uvm_tes里对sequence进行挂载

 phase.raise_objection(phase);
    seq = new();  //创建一个sequence
    seq.start(e.sqr); //将sequence挂载到env中的sequencer上
    //sequence挂载到sequencer上之后,就会自动运行其中的body任务了
    phase.drop_objection(phase);

对于
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。

sequence和sequencer

将sequence挂载到sequencer
将sequence挂载到sequencer上:

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

第一个变量,是指明sequence要挂载到哪个sequencer上面;第二个sequence是默认当前的sequence是没有parent sequence的;第三个参数默认值是-1,会使得该sequence如果有parent_sequence会继承其优先级值,如果它是顶层sequence,则其优先级会被自动设置为100,用户也可以自己指定优先级数值;第四个参数建议使用默认值,这样body() 函数预定义的回调函数,uvm_sequence::pre_body()和uvm_sequence::post_body()两个方法会在uvm_sequence::body()的前后执行。

将item挂载到sequencer
发送方法是将item挂载到sequencer上:

    uvm_sequence::start_item(uvm_sequence_item item, int set_priority = -1, uvm_sequencer_base sequencer = null);

    uvm_sequence::finish_item(uvm_sequence_item item, int set_priority = -1);

    在使用上面的一堆start&finish item方法时,需要使用create_item来对item进行创建,以及对其进行随机化处理。

对比start()方法和start_item()/finish_item()方法,要注意,它们面向的挂载对象是不同的。

不同的宏,可能会包含创建对象的过程,也可能不会创建对象。比如uvm_do/uvm_do_with会创建对象,而`uvm_send则不会创建对象,也不会将对象做随机处理,因此需要了解它们各自包含的执行内容和顺序。

`uvm_do宏使用的场景只有在sequence中才可以使用,因为其中涉及了一些调用sequence中定义的方法,因此不能在其他例如uvm_test、uvm_env中调用sequence相关的宏定义。
宏定义使用实例
·uvm_do系列宏中,其第一个参数除了可以是transcation的指针外,还可以是某个sequence的指针

class child_seq extends uvm_sequence;
  ...
  task body();
    bus_trans req;
    `uvm_create(req)
    `uvm_rand_send_with(req, {data == 10};)
    //上面两行宏定义也可以合并成一条:uvm_do_with(req, {data == 10};)
  endtask
endclass
 
class top_seq extends uvm_sequence;
  ...
  task body();
    child_seq cseq;   //sequence
    bus_trans req;    //item
    // send child sequence via start()
    `uvm_do(cseq)
    // send sequence item
    `uvm_do_with(req, {data == 20;}) 
    //使用宏定义后,发送sequence和item和之前相比,减少了很多代码
  endtask
endclass

在多个sequence挂载到一个seuqencer的情况下,uvm_sequencer类自建了仲裁机制来保证多个sequence在同时挂载到sequencer时,可以按照仲裁规则允许特定sequence中的item优先通过
在实际使用中,我们可以通过uvm_sequencer::set_arbitration(UVM_SEQ_ARB_TYPE val)函数来设置仲裁模式,这里的仲裁模式UVM_SEQ_ARB_TYPE有如下几种值可以选择:

    ※ UVM_SEQ_ARB_FIFO:默认模式,来自sequences的发送请求,按照FIFO先进先出的方式被依次授权,和优先级没有关系,谁先申请,谁就获得。

    ※ UVM_SEQ_ARB_STRICT_FIFO:先看优先级,再看顺序,优先级高的先发送,优先级一致的,优先授权先申请的sequence。

    ※ UVM_SEQ_ARB_WEIGHTED:不同sequence的发送请求,按照它们的优先级权重随机授权。

    ※ UVM_SEQ_ARB_RANDOM:不同的请求会被随机授权,而无视它们的抵达顺序和优先级。

    ※ UVM_SEQ_ARB_STRICT_RANDOM:不同的请求,会按照它们的最高优先级随机授权,与抵达时间无关。

    ※ UVM_SEQ_ARB_USER:用户可以自定义仲裁方法user_priority_arbitration()来裁定哪个sequence的请求被优先授权。
class bus_trans extends uvm_sequence_item;
  rand int data;
  ...
endclass
 
class child_seq extends uvm_sequence;
  rand int base;
  ...
  task body();
    bus_trans req;
    repeat(2) `uvm_do_with(req, {data inside {[base: base+9]};})
  endtask
endclass
 
class top_seq extends uvm_sequence;
  ...
  task body();
    child_seq seq1,seq2,seq3;
    m_sequencer.set_arbitration(UVM_SEQ_ARB_STRICT_FIFO);
    fork
      `uvm_do_pri_with(seq1, 500, {base == 10;})
      `uvm_do_pri_with(seq2, 500, {base == 20;})
      `uvm_do_pri_with(seq3, 300, {base == 30;})
    join
  endtask
endclass
 
class sequencer extends uvm_sequencer;
  ...
endclass
 
class driver extends uvm_driver;
  ...
  task run_phase(uvm_phase phase);
    REQ tmp;
    bus_trans req;
    forever begin
      seq_item_port.get_next_item(tmp);
      void'($cast(req, tmp));
      `uvm_info("DRV", 
          $sformatf("got a item %0d from parent sequence %s", 
                    req.data, req.get_parent_sequence().get_name()), UVM_LOW)
      seq_item_port.item_done();
    end
  endtask
endclass
 
class env extends uvm_env;
  sequencer sqr;
  driver drv;
  ...
  function void build_phase(uvm_phase phase);
    sqr = sequencer::type_id::create("sqr", this);
    drv = driver::type_id::create("drv",this);
  endfunction
  function void connect_phase(uvm_phase phase);
    drv.seq_item_port.connect(sqr.seq_item_eport);
  endfunction
endclass
 
class test1 extends uvm_test;
  env e;
  ...
  task run_phase(uvm_phase phase);
    top_seq seq;
    phase.raise_objection(phase);
    seq = new();
    seq.start(e.sqr);
    phase.drop_objection(phase);
  endtask
endclass

virtual sequence

virtual sequence可以承载不同目标sequencer的sequence群落,组织协调这些sequence的方式类似于高层次的hierarchical sequence。virtual sequencer和普通的sequencer有很大的不同,它起到了桥接其他sequencer的作用,即virtual sequencer是一个链接所有底层sequencer句柄的地方,是一个中心化的路由器。

    virtual sequencer本身不会传送item数据对象,因此virtual sequencer不需要与任何driver进行TLM链接。所以用户需要在顶层connect阶段,做好virtual sequencer中各个sequencer句柄与底层sequencer实体对象的一一对接,避免句柄悬空。

这种中心化的协调方式,使得顶层环境在场景创建和激励控制方面更加得心应手,而且在代码后期维护中,测试场景的可读性也得到了提高。

使用virtual sequence时容易遇到的错误:

    ※ 需要区分virtual sequence 同其它普通sequennce(element sequence、hierarchical sequence)

    ※ 需要区分virtual sequencer同其它底层负责传送数据对象的sequencer。

    ※ 在virtual sequence中记得使用宏`uvm_declare_p_sequencer来创建正确类型的p_sequencer变量,方便接下来各个目标的sequencer索引。

    ※ 在顶层环境中记得创建virtual sequencer,并且完成virtual sequencer中各个sequencer句柄与底层sequencer的跨层次链接,避免句柄悬空。

virtual sequence示例

class mcdf_normal_seq extends uvm_sequence;
	`uvm_object_utils(mcdf_normal_seq)
	`uvm_declare_p_sequencer(mcdf_virtual_sequencer)
	...
	task body();
		clk_rst_seq clk_seq;
		reg_cfg_seq cfg_seq;
		data_trans_seq data_seq;
		fmt_slv_cfg_seq fmt_seq;
		// 配置formatter slave agent
		`uvm_do_on(fmt_seq,p_sequencer.fmt_sqr)
		// 打开时钟并完成复用
		`uvm_do_on(clk_seq,p_sequencer.cr_sqr)
		// 配置MCDF寄存器
		`uvm_do_on(cfg_seq,p_sequencer.reg_sqr)
		// 传送channel数据包
		fork
			`uvm_do_on(data_seq,p_sequencer.chnl_sqr0)
			`uvm_do_on(data_seq,p_sequencer.chnl_sqr1)
			`uvm_do_on(data_seq,p_sequencer.chnl_sqr2)
		join
	endtask
endclass

m_sequencer是一个父类句柄,是uvm_seq自己预定义的
p_sequencer是一个子类句柄,在这个实例中它的类型就是mcdf_virtual_sequencer,和m_sequencer不一样,它不是预定义好的,是新鲜定义的,定义它的宏内容完成了两部。

定义了成员变量的类型mcdf_virtual_sequencer p_sequencer;
进行了句柄转换$cast(p_sequencer,m_sequencer);把m_sqr父类句柄转换成了子类句柄p_sqr。不论是m_sqr还是p_sqr都指向virtual_sqr,当然virtual_sqr是一个子类的类型

原文链接:https://blog.csdn.net/qq_42419590/article/details/121425345

class mcdf_normal_seq extends uvm_sequence;
	`uvm_object_utils(mcdf_normal_seq)
	`uvm_declare_p_sequencer(mcdf_virtual_sequencer)
	...
	task body();
		clk_rst_seq clk_seq;
		reg_cfg_seq cfg_seq;
		data_trans_seq data_seq;
		fmt_slv_cfg_seq fmt_seq;
		// 配置formatter slave agent
		`uvm_do_on(fmt_seq,p_sequencer.fmt_sqr)
		// 打开时钟并完成复用
		`uvm_do_on(clk_seq,p_sequencer.cr_sqr)
		// 配置MCDF寄存器
		`uvm_do_on(cfg_seq,p_sequencer.reg_sqr)
		// 传送channel数据包
		fork
			`uvm_do_on(data_seq,p_sequencer.chnl_sqr0)
			`uvm_do_on(data_seq,p_sequencer.chnl_sqr1)
			`uvm_do_on(data_seq,p_sequencer.chnl_sqr2)
		join
	endtask
endclass

m-sequencer和p-sequencer

m_sequencer是定义在uvm_sequencer_item中的,uvm_sequencer_base类型的句柄,也就是说
m_sequencer是uvm_sequencer_item的成员变量
(可以详细看uvm_sequence_item的源码)
m_sequencer是指向uvm_sequencer_base的句柄

任何派生于uvm_sequencer_item的类都会拥有一个m_sequencer

可以理解为,通过sequence_item来构造子弹的时候,系统帮我们实现声明了一把可以用的枪,这个枪就是sequencer_base,如果我们只是构造这个子弹,那么这个子弹和枪之间没有关系,但当我们通过sequence(弹夹)调用start_item的时候,系统就会为我们把子弹和枪联系起来。建立了这种联系后,我们后面才能调用枪(sequencer)的那三个通信函数(start_item、randomize、finish_item)。这个链接关于SEQUENCE源码解读很好

总体来讲,就是uvm_sequence_item会声明一个默认的sequencer(m-sequencer)。并在执行start_item等操作时被调用。

在这里插入图片描述
**`uvm_declare_p_sequencer(my_sequencer)**宏干了两件事:

声明了一个sequencer类型的句柄p_sequencer
将m_sequencer句柄通过$cast(p_sequencer,m_sequencer)转化为p_sequencer 类型的句柄。

总体讲,p_sequencer解决的问题:
m_sequencer虽然默认指向了用户定义的sequencer,
但是因为类型不一致的原因,无法通过m_sequencer引用用户定义的sequecer的成员变量等。

virtual sequencer与virtual sequence

对于顶层的测试环境, 测试序列所要协调的不再只是面向一个sequencer的sequence群,而是要面向多个sequencer的sequence群, 面向不同sequencer的sequence群如何挂载到不同的sequencer上呢? 这就需要用到virtual sequence和virtual sequencer来解决。对于单一的sequencer下的sequence群,其挂接较为简单,即通过uvm_sequence::start()来挂载root sequence,而在内部的child sequence可以通过宏`uvm_do()来实现。

该链接表述较为全面,带有实例(CSDN博主「SD.ZHAI」)

  • virtual sequence:承载不同目标sequencer的sequence群落,实现sequence同步;virtual
    sequence一般只会挂载到virtual
    sequencer上,且没有自己的sequence_item,只用于控制其他的sequence执行顺序,起统一调度作用。
  • virtual sequencer:桥接其它sequencer,即连接所有底层sequencer的句柄(指针),是一个中心化的路由器。virtual
    sequencer本身并不传送item数据对象,因此不需要与driver进行TLM连接。所以用户需在顶层的connect阶段做好virtual
    sequencer中各个sequencer句柄与sequencer实体对象的一一连接,避免句柄悬空。

在这里插入图片描述

1.创建virtual sequencer与virtual sequence
2.在virtual sequence中定义sequence句柄,在body( )任务中控制这些序列;

通过宏uvm_declare_p_sequencer( )来绑定virtual sequencer同时声明p_sequencer,并操作virtual sequencer中的内容; 通过宏uvm_do_on(
)将虚序列器句柄与虚序列句柄相连接

3.在环境/测试用例中连接sequencer到virtual sequencer

在build_phase阶段创建并例化virtual sequencer;将不同agt场景下的sqr发送的default
sequence设置为null,关闭相应的sequence;

在connect_phase阶段,将被控制的sequencers与virtual sequencer关联一起;

`在env或test中配置virtual_sequence为default sequence,在需要的phase阶段执行virtual
sequence;

sequence中使用config::db

在sequence中获取参数

能够调用config_db::get的前提是已经进行了set。sequence本身是一个uvm_object,它无法像uvm_component那样出现在 UVM树中,从而很难确定在对其进行设置时的第二个路径参数。所以在sequence中使用config_db::get函数得到参数的最大障碍是路径问题。

在UVM中使用get_full_name()可以得到一个component的完整路径,同样的,此函数也可以在一个sequence中被调用,尝试 着在一个sequence的body中调用此函数,并打印出返回值,其结果大体如下:

原文链接:添加链接描述

response的使用

put_response与get_response

sequence机制提供了一种sequence→sequencer→driver的单向数据传输机制。但是在复杂的验证平台中,sequence需要根据 driver对transaction的反应来决定接下来要发送的transaction,换言之,sequence需要得到driver的一个反馈。sequence机制提供对这 种反馈的支持,它允许driver将一个response返回给sequence。

如果需要使用response,那么在sequence中需要使用get_response任务:

1.uvm_do_on用于显式指定那个sequencer发送此transaction。第一个是transaction 指针,第二个是sequencer 指针。
2)使用uvm_do 时,默认的sequencer 就是此sequence 启动时为其指定的sequencer,sequence 将这个sequencer 的指针放在其成员变量m_sequencer 中。

  • 24
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值