Sequences

目录

 

Sequence item

create and use a sequence

How to use `uvm_do sequence macros ?

How to execute sequences via start( )

Executing sequence macros

Sequence action macros for pre-existing items

Virtual Sequence 

Using the sequence library

Sequence Arbitration


如果按照交通道路的车流来打比方,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模式,而不是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得到

看下面一个完整的sequence item例子:

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

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等方法

create and use a sequence

将sequence可以分类为:

  • 扁平类(flat 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()前后的一些操作;

当运行sequence时,sequence通常会生成一个标志raise a flag)以便让testbench知道它正在运行;在sequence结束时,该标志位会降低以便让testbench知道sequence运行结束了,此时可以结束仿真.如果没有标志位,这就让testbench的其他组件能够终止仿真,这有可能会导致sequence的执行突然终止。为了避免这种情况,通常将标志位写在testbench或者body()方法中。

How to use `uvm_do sequence macros ?

在前面,我们在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)

How to execute sequences via start( )

sequence是在test的run_phase中通过调用start( )方法来执行的,下面让我们来具体看一下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
   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);
    base_seq bs = base_seq::type_id::create ("bs", this);
    bs.start (null);     //call_pre_post默认为1。第一个参数为null表示在默认的sequencer上执行
  endtask
endclass

 结果如下:

可以看到执行了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()方法;结果如下:

下面我们来讨论第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!

Executing sequence macros

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

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

来看下面的例子:

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);
        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,表明了该虚sequence执行所在的虚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启动没什么区别
      phase.drop_objection (this);
    endtask
  endclass

 你也可以在虚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()中分别实现创建和分配步骤;

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

结果如下:

Sequence Arbitration

多个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的执行顺序; 

猜你喜欢

转载自blog.csdn.net/bleauchat/article/details/90692635