目录
Sequence action macros for pre-existing items
新手上路
如果按照交通道路的车流来打比方,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的执行顺序;