Sequences

目录

 

新手上路

Sequence item

create and use a sequence

使用`uvm_do_*宏来运行sequence item

通过start()方法来运行sequence

通过`uvm_do_*来运行sequence

Sequence action macros for pre-existing items

Virtual Sequence 

Using the sequence library

sequencer的仲裁机制


新手上路

如果按照交通道路的车流来打比方,sequence就是道路,sequence item是道路上行驶的货车,sequencer是目的地的关卡,而driver便是最终目的地卸货的地方。从软件实施的层面来讲,这里的货车是从sequence一端出发的,再经过了sequencer,最终抵达了driver,经过driver的卸货,每一辆的货车也就完成了它的使命。而driver对每一件到站的货物,经过它的扫面处理,将它分解为更小的信息量,提供给DUT.

 

可以看到,sequence item是每一次driver与DUT互动的最小粒度内容。例如,DUT如果是一个slave端,driver扮演master去访问DUT的寄存器,那么sequence item的需要定义的数据信息至少包括访问地址、命令码、数据和状态值,这样的信息在driver取得以后,会用时序的方式在interface一侧激励送给DUT。按照一般总线做寄存器访问的方式,这样的访问在时序上大致会保持几个时钟周期,直至数据传送完毕,而由driver再准备发起下一次的操作 .

那么对于一个sequence而言,它会产生多个sequence item,也可以产生多个sequence。从产生层次来看,sequence item是最小粒度,它可以由sequence生成,而相关sequence也可以进一步组织和实现层次化,最终由更上层的sequence进行调度。这么看来,sequence可以看做是产生激励内容的载体 . 

在sequence与driver之间起到桥梁作用的是sequencer。由于sequencer与driver均是component组件,它们之间的通信也是通过TLM端口实现的。TLM端口在例化中需要对通信参数进行指定这里的通信参数即sequence item种类。由于这一限制,使得sequencer到driver的传输数据类型不能改变,同时与sequencer挂接的sequence内创建的sequence item类型也应为指定类型。这就跟一个投币机的原理有一些类似,如果顾客投递的是1块钱,那么它会被识别到相应的储币区域,而如果顾客投递的是5毛钱,那么它肯定会被区别对待的 

在激励驱动链的最后一道关卡是driver。这个家伙胃口还蛮挑剔的,它就跟投币机一样,只认真了同一个类型的“钢镚儿”。对于常见的用法,driver往往是一个sequence item消化完,报告给sequencer和sequence,同时再请求消化下一个sequence item。所以,driver看起来永远喂不饱,同时还又对食物很挑剔。在消化每一个sequence item之前,该item中的数据是已经随机化好的,所以每个item一般都是各不相同的。driver自己并不会轻易修改item中的值,它会把item中的数据按照与DUT连接接口的物理协议按照时序关系驱动到端口上面。例如对于一个标准的写操作,driver不但需要按照时序依次驱动地址总线、命令码总线和数据总线,还应该等待从端的返回信号和状态值,这样才算完成了一次数据写传输。又如果是一个读操作,driver还应该在驱动完地址总线和命令码总线之后,等待返回信号、状态值和读出的数据,并且在有需要的情况下,将读出的数据再写回到sequence item中,通过sequencer最后返回给sequence. 

从类的继承性来看,uvm_sequence_item和uvm_sequence是基于uvm_object,这不同于uvm_component可以在build阶段作为UVM环境的“不动产”创建和配置,而是可以在任何的阶段创建。这种类的继承带来的UVM应用区别在于:

  • 由于无法判定环境在run阶段运行什么时间点会创建sequence和挂载(attach)到sequencer上面,所以无法通过简单的UVM结构来识别sequence的运行阶段

  • 正是由于uvm_object独立于build阶段之外,这使得用户可以有选择地、动态地在合适的时间点挂载想要的sequence和sequence item

  • 考虑到uvm_sequence和uvm_sequence_item并不处于UVM结构当中,所以顶层在做配置时,无法按照层次关系直接配置到sequence中。而如果sequence一旦活动起来,那么它必须挂载到一个sequencer上,这样sequence可以依赖于sequencer的结构关系,间接通过sequencer来获取顶层的配置和更多的顶层信息

 

sequencer之所以作为一个“路由”管道,停在sequence和driver之间,看重的是它的两个特点:

  • sequencer作为一个组件,它可以通过TLM端口与driver传送item对象。

  • sequencer在面向多个并行sequence时,它有充分的仲裁机制来实现合理分配item传送来模拟并行item数据传送至driver的测试场景;

 

在sequence、sequencer与driver之间发生的数据传送请求应由谁首先发出?而数据流向又是从谁到谁呢? 首先认清的是,数据传送机制采用的是get模式(采用get方法,也就是TLM端口的initiator为driver),而不是put模式.如果是put模式,那么应该是sequencer将数据put到driver,而如果是get模式,那么应当是driver从sequencer获取item之所以选择get模式,UVM是基于下面的考虑

  • 如果是get模式,那么当item从sequence产生,穿过sequencer到达driver时,我们就可以结束该传输(假如不需要返回值的话)。而如果是put模式,则必须是sequencer将item传送至driver,同时必须收到返回值才可以发起下一次的传输。这从效率上看,是有差别的。

  • 如果需要让sequencer拥有仲裁特性,可以使得多个sequence同时挂载到sequencer上面,那么get模式更符合“工学设计”。这是因为driver作为initiator,一旦发出get请求,它会先通过sequencer,继而获得仲裁后的item

 sequence的职责是个“水池”,那么用水的权利应该交给终端的driver!

 

Sequence item

sequence item 由一系列激励变量组成,这些变量都被定义为rand/randc,并且带有约束.sequence item在sequence中被随机化。 

激励变量主要包括以下几部分:

  • 控制信息(control information)-控制变量的类型和位宽;
  • 载荷信息(payload information)-传输的数据内容;
  • 配置信息(configuration information)-操作的模式,报错等;
  • 分析信息(analysis information)-用于从DUT获取的数据,如读取的信息、response等;

由于分析信息是用来捕获来自DUT的数据,所以除了分析信息外,其他的信息都要被定义为rand/randc

sequence item与SV testbench中的transation的作用是相同的,都是将要发送的数据封装在类里。sequence item 在sequence中被随机化,而transation在generator中被随机化。随机化后,sequence通过sequencer和driver发送到DUT中,而transation通过driver发送到DUT中。sequence item可以通过继承uvm_sequence_item得到,它具备UVM核心基类所必需的的数据操作方法,例如copy、clone、compare等.

看下面一个完整的sequence item例子(我们注意到sequence item不需要使用宏在工厂中注册):

class mem_seq_item extends uvm_sequence_item;//sequence item不需要在factory中注册
  //Control Information
  rand bit [3:0] addr;
  rand bit       wr_en;
  rand bit       rd_en;
   
  //Payload Information
  rand bit [7:0] wdata;
   
  //Analysis Information
       bit [7:0] rdata;
     
  //Utility and Field macros,
  `uvm_object_utils_begin(mem_seq_item)//这也是一种注册宏
    `uvm_field_int(addr,UVM_ALL_ON)
    `uvm_field_int(wr_en,UVM_ALL_ON)
    `uvm_field_int(rd_en,UVM_ALL_ON)
    `uvm_field_int(wdata,UVM_ALL_ON)
  `uvm_object_utils_end
   
  //Constructor
  function new(string name = "mem_seq_item");
    super.new(name);
  endfunction
   
  //constaint, to generate any one among write and read
  constraint wr_rd_c { wr_en != rd_en; };
   
endclass

sequence item中的方法:

create()

创建一个对象并返回句柄,格式如下:

seq_item = mem_seq_item::type_id::create();

句柄名=类名::type_id::create();

print() 

深度打印,格式如下:

seq_item.print(); 

对象名.print();

来看例子:

class mem_seq_item extends uvm_sequence_item;
  //Control Information
  rand bit [3:0] addr;
  rand bit       wr_en;
  rand bit       rd_en;
   
  //Payload Information
  rand bit [7:0] wdata;
   
  //Analysis Information
       bit [7:0] rdata;
   
  //Utility and Field macros,
  `uvm_object_utils_begin(mem_seq_item)
    `uvm_field_int(addr,UVM_ALL_ON)
    `uvm_field_int(wr_en,UVM_ALL_ON)
    `uvm_field_int(rd_en,UVM_ALL_ON)
    `uvm_field_int(wdata,UVM_ALL_ON)
  `uvm_object_utils_end
   
  //Constructor
  function new(string name = "mem_seq_item");
    super.new(name);
  endfunction
   
  //constaint, to generate any one among write and read
  constraint wr_rd_c { wr_en != rd_en; };
   
endclass
 
//-------------------------------------------------------------------------
//Simple TestBench to create and randomize sequence item
//-------------------------------------------------------------------------
module seq_item_tb;
   
  //instance
  mem_seq_item seq_item;
   
  initial begin
    //create method
    seq_item = mem_seq_item::type_id::create();//创建对象
     
    //randomizing the seq_item
    seq_item.randomize();//随机化对象
     
    //printing the seq_item
    seq_item.print();//打印  
  end 
endmodule

 结果:

注意下面几点: 

(1)UVM要求item的创建和随机化都应该发生在sequence的body()任务中,而不是在sequencer或者driver中;

(2)按照item对象的生命周期来区分,它的生命应该开始于sequence中的创建,而后经历了随机化和穿越sequencer最终到达driver,直到被driver消化之后,它的生命周期一般来讲才算寿终正寝。之所以要突出这一点,是因为一些用户在实际中,会不恰当地直接操作item对象,直接修改其中的数据,或者将它的句柄发送给其它组件使用,这就无形中修改了item的基因,或者延长了一个item对象的寿命。这种不合适的对象操作方式是用户需要注意的,可以取代的方式则是合理利用copy和clone等方法

再来看下面一个例子:

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(write,UVM_ALL_ON)
   `uvm_field_int(data,UVM_ALL_ON)
   `uvm_field_int(delay,UVM_ALL_ON)
   `uvm_field_int(addr,UVM_ALL_ON)
 `uvm_object_utils_end
 
 function new(string name="bus_trans");
   super.new(name);
 endfunction
 
 virtual function void do_print(uvm_printer printer);
   super.do_print(printer);
   `uvm_info (get_type_name(),"执行do_print!",UVM_MEDIUM)
 endfunction
 
 constraint addr_range{addr > 3;
                        addr < 16;}
 constraint data_range{data > 23;
                       data < 120;}
 constraint delay_range{delay inside {1,3,5,7,8};}
endclass

module test;
 bus_trans bs1;
 initial begin
   bs1=bus_trans::type_id::create("bs1");
   bs1.randomize();//随机化
   bs1.print();
 end
endmodule

 

create and use a sequence

将sequence可以分类为:

  • 扁平类(flat sequence),这一类中往往只用来组织更细小的粒度,即sequence item实例构成的组织。

  • 层次类(hierarchical sequence),这一类则是由更高层的sequence用来组织底层的sequence,进而让这些sequence或者按照顺序的方式,或者按照并行的方式,挂载到同一个sequencer上

  • 虚拟类(virtual sequence),这一类则是最终控制整个测试场景的方式,鉴于整个环境中往往存在不同种类的sequencer和其对应的sequence,我们需要一个虚拟的sequence来协调顶层的测试场景。之所以称这个方式为virtual sequence,是因为该序列本身并不固定挂载于某一种sequencer类型上,而是它会将其内部的各种不同类型的sequence最终挂载到不同的目标sequencer上面。这也是最大的不同于hierarchical sequence的一点.

下面讨论层次类sequence: 

sequence可以由多个子sequence组成.例如,其中一个子sequence执行读/写操作,另一个执行复位操作,还有一个为DUT提供激励。其中每一个sequence都对应于一个sequence item,所以一个sequence可以说就是包含多个sequence item的容器

现在面对sequence library中的多个sequence,我们有下面几种选择:

(1)使用现存的sequence分别驱动激励到DUT;

(2)将现存的sequence组合成一个新的sequence,并按照顺序将它们在新的sequence中排序;

sequence item在sequence中随机化,一个sequence可以包括多个子sequence.

(1)运行sequence使用start()方法或者`uvm_do()宏

(2)运行sequence item使用start_item()/finish_item()方法或者`uvm_do()宏

`uvm_do()宏会根据需要运行的是sequence或sequence item而分别调用start()方法或start_item()方法;

一个sequence主要包括两部分:(1)body()方法,编写sequence所要执行的操作

body()方法中的主要操作包括:

  • 通过方法create_item()创建request item对象;

  • 调用start_item()准备发送item;

  • 在完成发送item之前对item进行随机处理;

  • 调用finish_item()完成item发送;

  • 有必要的情况下可以从driver那里获取response item;

 (2)该sequence执行所在的sequencer;//不一定存在

来看下面一个例子:

class base_sequence extends uvm_sequence #(my_data);
   `uvm_object_utils (base_sequence)
   `uvm_declare_p_sequencer (my_sequencer)//指明所在的sequencer
 
   my_data  data_obj;//sequence item 类
   int unsigned      n_times = 1;
 
   function new (string name = "base_sequence");
      super.new (name);
   endfunction
 
   virtual task pre_body ();
      `uvm_info ("BASE_SEQ", $sformatf ("Optional code can be placed here in pre_body()"), UVM_MEDIUM)
      if (starting_phase != null)
         starting_phase.raise_objection (this);
   endtask
 
   virtual task body ();//主体方法用来随机化sequence item
      `uvm_info ("BASE_SEQ", $sformatf ("Starting body of %s", this.get_name()), UVM_MEDIUM)
      data_obj = my_data::type_id::create ("data_obj");
 
      repeat (n_times) begin
         start_item (data_obj);
         assert (data_obj.randomize ());//随机化sequence item
         finish_item (data_obj);
      end
      `uvm_info (get_type_name (), $sformatf ("Sequence %s is over", this.get_name()), UVM_MEDIUM)
   endtask
 
   virtual task post_body ();
      `uvm_info ("BASE_SEQ", $sformatf ("Optional code can be placed here in post_body()"), UVM_MEDIUM)
      if (starting_phase != null) 
         starting_phase.drop_objection (this);
   endtask

pre_body() 和 post_body() 两个方法可选,用来执行在执行主体方法body()前后的一些操作;

再来看下面的例子:

typedef enum {CLKON,CLKOFF,RESET} cmd_t;
class bus_trans extends uvm_sequence_item;
 rand int addr;
 rand int data;
 rand bit write;
 rand bit read;
 rand cmd_t cmd;
 
 `uvm_field_int_begin(bus_trans)
   `uvm_field_int(addr,UVM_ALL_ON)
   `uvm_field_int(write,UVM_ALL_ON)
   `uvm_field_int(read,UVM_ALL_ON)
   `uvm_field_enum(cmd_t,cmd,UVM_ALL_ON)
 `uvm_field_int_end
 
 function new(string name="bus_trans");
   super.new(name);
 endfunction
 
 constraint wr {write != read;}
 constraint data_range {data > 23;}
endclass

class rst_seq extends uvm_sequence #(bus_trans);
 `uvm_object_utils(rst_seq)
  rand int freq;
  
  function new(string name="rst_seq");
    super.new(name);
  endfunction
  
  virtual task body();
    bus_trans req;
	`uvm_do_with(req,{cmd == CLKON;data == freq;})
	`uvm_do_with(req,{cmd == RESET;})//在sequence中用宏来启动sequence item
  endtask
endclass

class test_seq extends uvm_sequence #(bus_trans);
 `uvm_object_utils(test_seq)
  rand int chn1;
  
  function new(string name="test_seq");
    super.new(name);
  endfunction
  
  virtual task body();
    bus_trans req;
	`uvm_do_with(req,{cmd == CLKOFF;data == chn1;})
	`uvm_do_with(req,{cmd == RESET;})//在sequence中用宏来启动sequence item
  endtask
endclass

class top_seq extends uvm_sequence;//顶层sequence
 `uvm_object_utils(top_seq)

  
  function new(string name="top_seq");
    super.new(name);
  endfunction
  
  virtual task body();
    rst_seq clkseq;
	test_seq testseq;
	`uvm_do_with(clkseq,{freq == 150;})
	`uvm_do_with(testseq,{chn1 == 0;})//在顶层sequence中用宏来启动子sequence
  endtask
endclass

上面的例子中首先定义了两个子sequence:rst_seq和test_seq;然后在top_seq中运行了这两个子seq,注意这里运行子sequence的方式是使用UVM宏!

总的来说层次sequence的作用就是对各个子sequence进行协调,这一点与后面要讲的virtual sequence是相同的,但是要注意两者的区别: 层次sequence面对的对象是同一个sequencer,即层次sequence本身也挂载到一个sequencer上,而virtual sequence内部不同的sequence允许面向不同的sequencer,但是virtual sequence本身并不挂载到某个具体的sequencer上,一般它和virtual sequencer一起作用!!这一点在后面将会具体叙述.

使用`uvm_do_*宏来运行sequence item

在前面,我们在sequence中运行sequence item是使用start_item()/finish_item()方法,也可以直接使用uvm宏`uvm_do,宏会自动调用start_item()/finish_item()方法.

宏完成了三个步骤

  • sequence或者sequence item的创建

  • sequence或者sequence item的随机化

  • sequence或者sequence item的传送

uvm_sequence的宏(既可以执行sequence item也可以执行sequence)如下表所示:

来看下面的例子:

class base_sequence extends uvm_sequence #(my_data);
  `uvm_object_utils (base_sequence)
 
  ...
  virtual task body ();
      `uvm_info ("BASE_SEQ", $sformatf ("Starting body of %s", this.get_name()), UVM_MEDIUM)
      `uvm_do (req)//req是my_data的实例
      `uvm_do_with (req, { data == 8'h4e;
                           addr == 8'ha1; })
      `uvm_do_pri (req, 9)
      `uvm_do_pri_with (req, 3, { data == 8'hc5; })
      `uvm_info ("BASE_SEQ", $sformatf ("Sequence %s is over", this.get_name()), UVM_MEDIUM)
   endtask
  ...
endclass

 需要注意的是,`uvm_do_*宏使用的是内建的默认sequencer,如果想sequence在自建的sequencer上执行,则需要使用`uvm_do_on_*,则上面的宏变为:

`uvm_do_on          (SEQ_OR_ITEM, SEQR)//第2个参数为sequencer
`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中所有宏的定义都需要用到关键字`define,如下:

`define uvm_do (SEQ_OR_ITEM) \
  `uvm_do_on_pri_with (SEQ_OR_ITEM, m_sequencer, -1 {})
 
`define uvm_do_on_with (SEQ_OR_ITEM, SEQR, CONSTRAINTS) \
  `uvm_do_on_pri_with (SEQ_OR_ITEM, SEQR, -1, CONSTRAINTS)

通过start()方法来运行sequence

sequence派生自uvm_object,它可以在任何组件的main_phase或run_phase中启动,之前看到的sequence是在test的run_phase中通过调用start( )方法来执行的,要启动一个 sequence 非常的简单,第一步就是把这个 sequence 给实例化,
第二步就是调用 sequence 的 start 任务,调用时要传入一个 sequencer 参数,如下:

my_sequence my_seq;
my_seq = my_sequence::type_id::create(“my_seq”);
my_seq.start(sequencer);

当这个 sequence 启动起来的时候,这个 sequence 的 body 就会自动执行,我们可以想像,在 my_seq.start()的实现中肯定会有这么一句:

my_seq.body();

下面让我们来具体看一下start()方法

virtual task start ( uvm_sequencer_base   sequencer,//sequence执行所在的sequencer
                     uvm_sequence_base    parent_sequence = null,//父类sequence,默认为null,决定外部的sequence中的pre_do、mid_do、post_do等方法是否执行
                     int                  this_priority = -1,//优先级
                     bit                  call_pre_post = 1 );//call_pre_post为1会调用当前,sequence的pre_body()和post_body()方法
//后3个参数是可选的

下面我们来看一下,当sequence调用start()时,内部方法的执行情况:

seq.randomize (...); // optional
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 如果父类sequence不为null,则在内部会调用父类sequence的方法
   parent_seq.mid_do(this)  (func)   if parent_seq != null
seq.body()                  (task)   your code//调用sequence的body方法
   parent_seq.post_do(this) (func)   if parent_seq != null
seq.post_body()             (task)   if call_pre_post == 1
sub_seq.post_start()        (task)

首先我们来看call_pre_post参数的影响,该参数默认为1;先定义一个sequence,如下:

// Let's just declare a base sequence from which we can have a child sequence
// This will help to prove execution order of pre_do, mid_do and post_do tasks
class base_seq extends uvm_sequence;
 
  // Factory registration and new function is assumed to be written next
  ...
 
  // For every method during the body() execution flow, we'll simply add a display statement to track 
  // how each method in this sequence is getting executed
  virtual task pre_body();
    `uvm_info (get_type_name(), "pre_body()", UVM_LOW)
  endtask
 
  virtual task pre_do(bit is_item);
    `uvm_info (get_type_name(), "pre_do()", UVM_LOW)
  endtask
 
  virtual function void mid_do(uvm_sequence_item this_item);
    `uvm_info (get_type_name(), "mid_do()", UVM_LOW)
  endfunction
 
  virtual task body();
    `uvm_info (get_type_name(), "body()", UVM_LOW)
  endtask
 
  virtual function void post_do(uvm_sequence_item this_item);
    `uvm_info (get_type_name(), "post_do()", UVM_LOW)
  endfunction
 
  virtual task post_body();
    `uvm_info (get_type_name(), "post_body()", UVM_LOW)
  endtask
endclass

在test中执行上面的sequence,如下:

// Define a base test that will launch our base sequence
class base_test extends uvm_test;
  ...
 
  // Instantiate the base_seq and start it on default sequencer
  virtual task run_phase (uvm_phase phase);
    phase.raise_objection(this);
    base_seq bs = base_seq::type_id::create ("bs", this);
    bs.start (null);     //call_pre_post默认为1。第一个参数为null表示在默认的sequencer上执行
    phase.drop_objection(this);
  endtask
endclass

在UVM中,objection一般伴随着sequence的启动,所以一般用在component的main_phase中,只有在sequence出现的地方才会启动objection(raise_objection)和撤销objection(drop_objection),用来结束当前的phase;

 结果如下:

可以看到执行了pre_body()和post_body()方法;下面我们将call_pre_post改为0:

class base_test extends uvm_test;
  ...
 
  // Call the same sequence with call_pre_post argument set to 0
  // This will show the difference in result compared to previous example
  virtual task run_phase (uvm_phase phase);
    base_seq bs = base_seq::type_id::create ("bs", this);
    bs.start (null, null, 0, 0);  // call_pre_post is given 0
  endtask
endclass

 call_pre_post为0,所以不会执行pre_body()和post_body()方法;结果如下:

这表明 start方法中的call_pre_post 参数是用来控制是否使用body方法的回调

下面我们来讨论第2个参数parent,首先定义两个sequence:child1_seq和child2_seq.child1_seq和child2_seq都继承base_seq。其中在child1_seq中包含了child2_seq.如下:

// The child sequence will have its own pre_body task to see pre_body() method of which parent is called
class child1_seq extends base_seq;
 
  // Again, to track the message lets just print this into the log
  virtual task pre_body();
    `uvm_info (get_type_name(), "pre_body()", UVM_LOW)
  endtask
 
  // Definition of all other methods like base class should come here

  virtual task body();
    child2_seq cs = child2_seq::type_id::create ("cs");
    cs.start (null);   // parent arg is by default null
  endtask
 
endclass


class child2_seq extends base_seq;
  ...
  virtual task pre_body();
    `uvm_info (get_type_name(), "pre_body()", UVM_LOW)
  endtask
 
  // Definition of all other methods like base class should come here
endclass

我们在test中执行child1_seq,结果如下:

可以看到, child2_seq作为child1_seq的子sequence,在执行child1_seq时,child2_seq也会执行;

将上面child1_seq中body()方法中child2_seq的参数修改为child1_seq,如下:

class child1_seq extends base_seq;
  ...
 
  virtual task body();
    child2_seq cs = child2_seq::type_id::create ("cs");
    cs.start (null, this);     // Give handle of current seq as parent to child2
  endtask
  ...
endclass

test中的运行结果为 :

可以看到,child1_seq中的pre_do()、mid_do()、post_do()方法执行了。也就是说start()方法的第二个参数parent指的是子sequence的外层sequence,而不是在定义类时的父类sequence!

通过`uvm_do_*来运行sequence

sequence内部的子sequence的执行除了可以用start()方法外,还可以使用sequence宏。使用宏的好处是可以添加约束,但是这样会失去对pre_body()和post_body()执行的控制

当使用sequence宏时,相当于start()方法中的参数call_pre_post为0,这意味着pre_body()和post_body()方法永远不会执行

 

我们来看一个例子,首先定义一个base_sequence,并且3个sequence继承于base_sequence,如下:

class base_sequence extends uvm_sequence #(my_data);
   `uvm_object_utils (base_sequence)
   `uvm_declare_p_sequencer (my_sequencer)
 
   function new (string name = "base_sequence");
      super.new (name);
   endfunction
 
   virtual function void mid_do (uvm_sequence_item this_item);
      `uvm_info (get_type_name (), "Executing mid_do", UVM_MEDIUM)
   endfunction
 
   virtual task pre_do (bit is_item);
      `uvm_info (get_type_name (), "Executing pre_do", UVM_MEDIUM)
   endtask
 
   virtual function void post_do (uvm_sequence_item this_item);
      `uvm_info (get_type_name (), "Executing post_do", UVM_MEDIUM)
   endfunction
 
   virtual task body ();
      starting_phase.raise_objection (this);
      `uvm_info ("BASE_SEQ", $sformatf ("Starting body of %s", this.get_name()), UVM_MEDIUM)
      `uvm_info ("BASE_SEQ", $sformatf ("Sequence %s is over", this.get_name()), UVM_MEDIUM)
      starting_phase.drop_objection (this);
   endtask
endclass
class seq1 extends base_sequence;
   `uvm_object_utils (seq1)
 
   seq2 m_seq2;//seq2作为seq1的子sequence
 
   virtual task pre_start ();
      `uvm_info (get_type_name (), "Executing pre_start()", UVM_MEDIUM)
   endtask
 
   function new (string name = "seq1");
      super.new (name);
   endfunction
 
   virtual task body ();
      starting_phase.raise_objection (this);
      m_seq2 = seq2::type_id::create ("m_seq2");
      `uvm_info ("SEQ1", "Starting seq1", UVM_MEDIUM)
      #10;
      `uvm_do_pri_with (m_seq2, ,{})//执行seq2,不带约束
      `uvm_info ("SEQ1", "Ending seq1", UVM_MEDIUM)
      starting_phase.drop_objection (this);
   endtask
 
endclass

 

class seq2 extends base_sequence;
   `uvm_object_utils (seq2)
 
   seq3 m_seq3;//seq3作为seq2的子sequence
 
   function new (string name = "seq2");
      super.new (name);
   endfunction
 
   virtual task pre_body ();
      `uvm_info (get_type_name(), "Executing pre_body", UVM_MEDIUM)
   endtask      
 
   virtual task body ();
      m_seq3 = seq3::type_id::create ("m_seq3");
      `uvm_info ("SEQ2", "Starting seq2", UVM_MEDIUM)
      #10;
      `uvm_do_pri_with (m_seq3, ,{})//执行seq3
      `uvm_info ("SEQ2", "Ending seq2", UVM_MEDIUM)
   endtask
 
   virtual task post_body ();
      `uvm_info (get_type_name(), "Executing post_body", UVM_MEDIUM)
   endtask    
 
endclass
class seq3 extends base_sequence;
   `uvm_object_utils (seq3)
 
   function new (string name = "seq3");
      super.new (name);
   endfunction
 
   virtual task pre_body ();
      `uvm_info (get_type_name(), "Executing pre_body", UVM_MEDIUM)
   endtask      
 
   virtual task body ();
      `uvm_info ("SEQ3", "Starting seq3", UVM_MEDIUM)
      #10;
      `uvm_info ("SEQ3", "Ending seq3", UVM_MEDIUM)
   endtask
 
   virtual task post_body ();
      `uvm_info (get_type_name(), "Executing post_body", UVM_MEDIUM)
   endtask     
 
   virtual task post_start ();
      `uvm_info (get_type_name (), "Executing post_start", UVM_MEDIUM)
   endtask 
endclass

执行seq1的顺序如下:

- SEQ1 : pre_start()  
- go to the body() of SEQ1
- Display "[SEQ1] Starting seq1"
- Start SEQ2                // 不会调用pre_start()和 pre_body() 
- Call parent's (SEQ1) pre_do
- Display "[seq1] Executing pre_do"
- Call parent's (SEQ1) mid_do
- Display "[seq1] Executing mid_do"
- Call body() of SEQ2 
- Display "[SEQ2] Starting seq2"
- Start SEQ3 
...

总结,执行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)配置为sequencer的main_phase中的default_sequence 

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

让sequencer在运行到 main_phase 时自动启动前面定义的 case0_sequence。这里就是 sequence 的自动启动了。当运行到 main_phase 时,sequencer 会自动执行下面的语句:

task my_sequencer::main_phase(uvm_phase phase);
case0_sequence my_seq;
super.main_phase(phase);
my_seq = new("my_seq");
my_seq.starting_phase = phase;
my_seq.start(this);
endtask


 

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

unction 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

(4)使用UVM宏 

注:上面两种配置default_sequence的方法一般在component的build_phase中或顶层模块中使用,这和其他配置的位置是相同的.

Sequence action macros for pre-existing items

前面我们讨论了`uvm_do_*,它会自动创建sequence对象并随机化然后将随机化后的对象发送给sequencer。但是,对于已经存在的随机化的sequence的发送,则需要用到`uvm_send宏。两者之间的区别就是后者不会创建对象也不会随机化。看下面例子:

class seq1 extends base_sequence;
   `uvm_object_utils (seq1)
 
   seq2     m_seq2;           
   my_data  m_data0;          // This data object will be created by us
   my_data  m_data1;          // This data object will be left to uvm_do to create
   my_data  m_data2;          // Will be used with `uvm_send
 
   virtual task pre_start ();
      `uvm_info (get_type_name (), "Executing pre_start()", UVM_MEDIUM)
   endtask
 
   function new (string name = "seq1");
      super.new (name);
      m_data0 = my_data::type_id::create ("m_data0");
   endfunction
 
   virtual task body ();
      starting_phase.raise_objection (this);//这句的意思?
      `uvm_info ("SEQ1", "Starting seq1", UVM_MEDIUM)
 
      // reg的创建、随机化和发送都是由 uvm_do 完成
      // call `uvm_create and generate the object, randomize it and send to sequencer
      `uvm_info ("SEQ1", "uvm_do (req) - Create, randomize and send req", UVM_MEDIUM)
      `uvm_do (req)
 
      // If uvm_do is called again, then the same object will be randomized again and sent
      `uvm_info ("SEQ1", "uvm_do (req) - Randomize again, and send", UVM_MEDIUM)
      `uvm_do (req)
 
      // m_data0 已经创建,故只需要随机化和发送
      `uvm_info ("SEQ1", "uvm_do (m_data0) - Data already exists, simply randomize and send", UVM_MEDIUM)
      `uvm_do (m_data0)
      // m_data1 was not created above, so it will be created, randomized and sent
      `uvm_info ("SEQ1", "uvm_do (m_data1) - Data was not created, so create it, randomize and send", UVM_MEDIUM)
      `uvm_do (m_data1)
 
      // req already exists, but will not be randomized 
      `uvm_info ("SEQ1", "uvm_send (req) - req already exists from previous create, Send it without randomization", UVM_MEDIUM)
      `uvm_send (req)
 
`ifdef RUNTIME_ERR
      // m_data2并没有创建,所以会报错
      `uvm_send (m_data2) 
`enddif
 
      `uvm_info ("SEQ1", "uvm_send (req) - Manually create, randomize and send", UVM_MEDIUM)
      // Create the object, randomize it and  send to sequencer
      `uvm_create (m_data2)//创建并随机化m_data2
      void'(m_data2.randomize ());
      `uvm_send (m_data2)//这时不会报错
 
      `uvm_info ("SEQ1", "uvm_send_pri (req) - Send with priority", UVM_MEDIUM)
      `uvm_send_pri (m_data2, 72)
      `uvm_info ("SEQ1", "Ending seq1", UVM_MEDIUM)
 
      // Start the next sequence - will be discussed later
      `uvm_do (m_seq2)
      starting_phase.drop_objection (this);
   endtask
 
endclass

Virtual Sequence 

virtual sequence是一个容器(container),里面包括了多个在不同sequencer上执行的sequence,与之对应的是virtual sequencer,virtual sequence需要在virtual sequencer上执行,虚sequencer内部包括了多个实际的sequencer。例如,一个SOC设计可能需要一系列的sequence用来驱动不同的接口,这些sequence在各自的sequencer上执行,因此控制这些sequence的最好方式就是使用虚sequence。之所以称之为虚sequence,是因为它不和任何具体的sequence item关联。 

Virtual sequencer含有sub sequencer的句柄,用以控制这些sub sequencer;它并不和任何driver相连,本身并不处理具体的sequence或sequence item;与普通的sequencer相比,它起到了桥接具体的sequencer的作用;

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间的控制和调度的
 

来看下面的例子:

class my_virtual_seq extends uvm_sequence;//定义一个虚sequence
    `uvm_object_utils (my_virtual_seq)
    `uvm_declare_p_sequencer (my_virtual_sequencer)//声明该虚sequence执行所在的虚sequencer
 
    function new (string name = "my_virtual_seq");
      super.new (name);
    endfunction
 
    apb_rd_wr_seq   m_apb_rd_wr_seq;//虚sequence包含3个sequence
    wb_reset_seq   m_wb_reset_seq;
    pcie_gen_seq   m_pcie_gen_seq;
 
    task pre_body();
      m_apb_rd_wr_seq = apb_rd_wr_seq::type_id::create ("m_apb_rd_wr_seq");
      m_wb_reset_seq  = wb_reset_seq::type_id::create ("m_wb_reset_seq");
      m_pcie_gen_seq  = pcie_gen_seq::type_id::create ("m_pcie_gen_seq");
    endtask
 
    task body();
      ...
      m_apb_rd_wr_seq.start (p_sequencer.m_apb_seqr);//启动每一个sequence,并在对应的sequencer上执行
      fork
        m_wb_reset_seq.start (p_sequencer.m_wb_seqr);//可以用start()也可以用宏
        m_pcie_gen_seq.start (p_sequencer.m_pcie_seqr);
      join 
      ...
    endtask
  endclass

 虚sequence和普通的sequence一样都继承了uvm_sequence;

使用宏`uvm_declare_p_sequencer定义了一个p_sequencer句柄,该句柄指向my_virtual_sequencer,表明了该virual sequence对应的virtual sequencer,该virtual sequencer中包含了多个具体的sequencer

每一个sequence都在相应的sequencer上执行,每一个相应sequencer都可以被p_sequencer调用

一旦定义了虚sequence,就可以在test上启动该虚sequence,如下:

class my_test extends uvm_test;
    `uvm_component_utils (my_test)
 
    my_env   m_env;
 
    ...
 
    task run_phase (uvm_phase phase);
      my_virtual_seq m_vseq = my_virtual_seq::type_id::create ("m_vseq");
      phase.raise_objection (this);
      m_vseq.start (m_env.m_virtual_seqr);//这和普通的sequence启动没什么区别,将virtual sequence挂载到virtual sequencer上
      phase.drop_objection (this);
    endtask
  endclass

再来看下面一个简单的例子:

typedef class mcdf_virtual_sequencer;
//子sequence定义,分属于不同的sequencer
//clk_rst_seq
//reg_cfg_seq
//定义virtual sequence
class mcdf_virtual_sequence extends uvm_sequence;
 `uvm_object_utils(mcdf_virtual_sequence)
 `uvm_declare_p_sequencer(mcdf_virtual_sequencer) //声明该virtual sequence对应的virtual sequencer
 
 function new(string name="mcdf_virtual_sequence");
   super.new(name);
 endfunction
 //....
 virtual task body();
  clk_rst_seq clk_seq;
  reg_cfg_seq reg_seq;
  
  `uvm_do_on(clk_seq,p_sequencer.cr_sqr)
  `uvm_do_on(reg_seq,p_sequencer.reg_sqr)
 endtask
endclass

//定义virtual sequencer,它包含了上面所用到的具体sequencer的句柄
class mcdf_virtual_sequencer extends uvm_sequencer;
 `uvm_component_utils(mcdf_virtual_sequencer)
 cr_master_sequencer cr_sqr;
 reg_master_sequencer reg_sqr;
 
 function new(string name="mcdf_virtual_sequencer",uvm_component parent);
   super.new(name,parent);
 endfunction
 
endclass

//定义子sequencer
class cr_master_sequencer extends uvm_sequencer;
 `uvm_component_utils(cr_master_sequencer)
 
  function new(string name="cr_master_sequencer",uvm_component parent);
    super.new(name,parent);
  endfunction
  
endclass

class reg_master_sequencer extends uvm_sequencer;
 `uvm_component_utils(reg_master_sequencer)
 
  function new(string name="reg_master_sequencer",uvm_component parent);
    super.new(name,parent);
  endfunction
  
endclass

//在test中创建virtual sequence和virtual sequencer的对象,并启动virtual sequence
class test1 extends uvm_test;
 `uvm_component_utils(test1)
  mcdf_virtual_sequence seq;
  mcdf_virtual_sequencer sqr;
 function new(string name="test1",uvm_component parent);
   super.new(name,parent);
 endfunction
 
 virtual task run_phase(uvm_phase phase);
   super.run_phase(phase);
   seq=mcdf_virtual_sequence::type_id::create("seq");//创建对象可以放在build_phase中
   sqr=mcdf_virtual_sequencer::type_id::create("sqr");
   
   phase.raise_objection(phase);
   seq.start(sqr);//在virtual sequencer上启动virtual sequence
   phase.drop_objection(phase);
 endtask
endclass

 这个例子中,在virtual sequence中启动了各个子sequence,并将它们挂载到对应的sequencer上;这里需要区分的是,尽管最后在test1中将virtual sequence挂载到了virtual sequencer上,但是这种挂载的根本目的在于给virtual sequence提供一个中心化的sequencer路由,而在virtual sequence中使用宏`uvm_declare_p_sequencer定义了一个成员变量p_sequencer,virtual sequence可以使用该变量,进一步调用该变量所指向的virtual sequencer中的具体sequencer,从而将子sequence与子sequencer一一对应。

 你也可以在虚sequence中直接创建每个sequence执行所在的sequencer句柄,这样就不需要使用虚sequencer了。如下:

 class my_virtual_seq extends uvm_sequence;
    `uvm_object_utils (my_virtual_seq)
    ...
    apb_sequencer   m_apb_seqr;
    reg_sequencer   m_reg_seqr;
    wb_sequencer   m_wb_seqr;
 
    apb_rd_wr_seq   m_apb_rd_wr_seq;
    wb_reset_seq   m_wb_reset_seq;
    pcie_gen_seq   m_pcie_gen_seq;
 
    virtual task pre_body();
      m_apb_rd_wr_seq = apb_rd_wr_seq::type_id::create ("m_apb_rd_wr_seq");
      ...
      // Instantiate other sequences here
    endtask
 
    virtual task body ();
      m_apb_rd_wr_seq.start (m_apb_seqr);
      ...
    endtask
  endclass

这样在test中启动虚sequence之前,你必须首先分配所有的sequencer句柄,才能启动虚sequence。如下:

class my_test extends uvm_test;
    ...
 
    virtual task run_phase (uvm_phase phase);
      my_virtual_seq m_vseq = my_virtual_seq::type_id::create ("m_vseq");//创建虚sequence
      phase.raise_objection (this);
 
      // Assign all sequencer handles
      m_vseq.m_apb_seqr = m_env.m_apb_agent.m_apb_seqr;//先分配每一个sequencer,才能启动虚sequence
      ...
 
      m_vseq.start (null);
      phase.drop_objection (this);
    endtask
  endclass

也可以在test的build_phase()和connect_phase()中分别实现创建和分配步骤;

实现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引起的混乱

 

Using the sequence library

一个验证会包含成百上千个test,每个test中又会包括多个sequence以执行不同的任务。你可以创建一个顶层的sequence,在它的body()方法中产生其他的子sequence。因此,UVM提供了uvm_sequence_library来容纳这一系列的sequence,这些sequence在uvm_sequence_library 中注册,这样就可以随时调用它们。一个sequence libarary可以包含多个sequence,同样一个sequence可以在多个library中注册!

sequence library 的创建格式如下:

class my_seq_lib_v2 extends uvm_sequence_library #(my_data);
   `uvm_object_utils (my_seq_lib_v2)
   `uvm_sequence_library_utils (my_seq_lib_v2)//需要加上这个宏
 
   function new (string name="my_seq_lib_v2");
      super.new (name);
      init_sequence_library();//需要在构造方法中调用这个方法
   endfunction
endclass

注意创建这个类与其他类的不同,需要调用`uvm_sequence_library_utils ( )宏和init_sequence_library ( )方法

每一个sequence library中的sequence都可以独立的在不同的sequencer上运行;你也可以在test中创建sequence library的对象,并向其中添加子sequence;一般将sequence library设为默认的sequence(default sequence)用来执行其中的sequence.如下例:

class feature_test extends base_test;
   `uvm_component_utils (feature_test)
 
   my_seq_lib m_seq_lib0;//sequence library 句柄
   seq1 m_seq1;//3个子sequence
   seq2 m_seq2;
   seq3 m_seq3;
 
   function new (string name, uvm_component parent = null);
      super.new (name, parent);
   endfunction 
 
   function void build_phase (uvm_phase phase);
      super.build_phase (phase);
 
      m_seq_lib0 = my_seq_lib::type_id::create ("m_seq_lib0");//创建sequence library对象
   endfunction
 
   virtual task configure_phase (uvm_phase phase);
      super.configure_phase (phase); 
      `uvm_info ("CFG_PHASE", "Add sequences to library", UVM_MEDIUM)
      m_seq_lib0.selection_mode = UVM_SEQ_LIB_RANDC;//设置sequence library模式
      m_seq_lib0.min_random_count = 5;//设置library调用时执行的sequence数目
      m_seq_lib0.max_random_count = 10;
 
      m_seq_lib0.add_typewide_sequence (m_seq1.get_type());//向sequence library添加子sequence类
      m_seq_lib0.add_typewide_sequence (m_seq2.get_type());
      m_seq_lib0.add_typewide_sequence (m_seq3.get_type());
      m_seq_lib0.init_sequence_library();
   endtask
 
   function void start_of_simulation_phase (uvm_phase phase);
      super.start_of_simulation_phase (phase);
      uvm_config_db#(uvm_sequence_base)::set(this,"m_top_env.m_seqr0.main_phase",
                                                   "default_sequence", m_seq_lib0);
  //将sequence library设为默认的sequence,它会自动启动,而不需要用到start方法
  //注意,这样设置好像只能将sequence设置为sequencer路径下的main_phase中的default_sequence
   endfunction
endclass

结果如下:

sequencer的仲裁机制

多个sequence可以在同一个sequencer上并行运行,因此我们需要用到sequence arbitration来判决sequence运行的先后顺序,用来决定哪个sequence的sequence item优先发送。如下面的例子,我们怎么知道sequence的运行顺序呢?

class my_test extends uvm_test;
  ...
  virtual task run_phase (uvm_phase phase);
    ...
    fork
      m_seq1.start (m_sequencer);
      m_seq2.start (m_sequencer);
      m_seq3.start (m_sequencer);
    join
    ...
  endtask
endclass

通过sequencer调用set_arbitration()方法来决定sequence的执行顺序,该方法的定义如下:

function void set_arbitration(
UVM_SEQ_ARB_TYPE val
)

方法的参数为判决模式;因此,UVM为sequencer提供了6种判决模式

UVM_SEQ_ARB_FIFO

不管sequence的优先级,按照在fork...join中的顺序,依次执行;

UVM_SEQ_ARB_RANDOM

 不管sequence的优先级,以随机顺序依次执行;

UVM_SEQ_ARB_STRICT_FIFO

根据sequence的优先级来执行,对于相同优先级的sequence,排在前面的先执行;

UVM_SEQ_ARB_STRICT_RANDOM 

 根据sequence的优先级来执行,对于相同优先级的sequence会随机执行;

UVM_SEQ_ARB_WEIGHTED

随机执行sequence,优先级最高的最有可能最先执行;

UVM_SEQ_ARB_USER 

创建用户自己的执行顺序,你需要做的就是自定义一个sequencer,然后调用user_priority_arbitration()方法来自定义sequence的执行顺序; 

  • 5
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值