目录
进阶应用
嵌套的sequence
在一个sequence的body中,除了可以使用uvm_do宏产生transaction外,还可以启动另外一个sequence,这就是嵌套的sequence:
class case0_sequence extends uvm_sequence #(my_transaction);
…
virtual task body();
crc_seq cseq;
long_seq lseq;
…
repeat (10) begin
cseq = new("cseq");
cseq.start(m_sequencer);
lseq = new("lseq");
lseq.start(m_sequencer);
end
…
endtask
…
endclass
也可以使用uvm_do宏来完成这些事:
repeat (10) begin
`uvm_do(cseq)
`uvm_do(lseq)
end
- uvm_do系列宏中,其第一个参数除了可以是transaction的指针外,还可以是某个sequence的指针。当第一个参数是transaction时,调用start_item和finish_item;当第一个参数是sequence时,它调用此sequence的start任务。
- 除了uvm_do宏外,前面介绍的uvm_send宏、uvm_rand_send宏、uvm_create宏,其第一个参数都可以是sequence的指针。唯一例外的是start_item与finish_item,这两个任务的参数必须是transaction的指针。
在sequence中使用rand类型变量
在transaction的定义中,通常使用rand来对变量进行修饰,说明在调用randomize时要对此字段进行随机化。而在sequence中也可以使用rand修饰符。
有如下的sequence,它有成员变量ldmac:
class long_seq extends uvm_sequence#(my_transaction);
rand bit[47:0] ldmac;
…
virtual task body();
my_transaction tr;
`uvm_do_with(tr, {tr.crc_err == 0;
tr.pload.size() == 1500;
tr.dmac == ldmac;})
tr.print();
endtask
endclass
这个sequence可以作为底层的sequence被顶层的sequence调用:
class case0_sequence extends uvm_sequence #(my_transaction);
…
virtual task body();
long_seq lseq;
…
repeat (10) begin
`uvm_do_with(lseq, {lseq.ldmac == 48'hFFFF;})
end
…
endtask
…
endclass
在sequence中定义rand类型变量以向产生的transaction传递约束时,变量的名字要与transaction中相应字段的名字不同。
transaction类型的匹配
- 一个sequencer只能产生一种类型的transaction,一个sequence如果要想在此sequencer上启动,那么其所产生的transaction的类型必须是这种transaction或者派生自这种transaction。
- 嵌套sequence的前提是,在套里面的所有sequence产生的transaction都可以被同一个sequencer所接受。
- 如果要将两个截然不同的transaction交给同一个sequencer,需要将sequencer和driver能够接受的数据类型设置为uvm_sequence_item:
class my_sequencer extends uvm_sequencer #(uvm_sequence_item);
class my_driver extends uvm_driver#(uvm_sequence_item);
在sequence中可以交替发送my_transaction和your_transaction:
class case0_sequence extends uvm_sequence;
my_transaction m_trans;
your_transaction y_trans;
…
virtual task body();
…
repeat (10) begin
`uvm_do(m_trans)
`uvm_do(y_trans)
end
…
endtask
`uvm_object_utils(case0_sequence)
endclass
这样带来的问题是,由于driver中接收的数据类型是uvm_sequence_item,如果它要使用my_transaction或者your_transaction中的成员变量,必须使用cast转换:
task my_driver::main_phase(uvm_phase phase);
my_transaction m_tr;
your_transaction y_tr;
…
while(1) begin
seq_item_port.get_next_item(req);
if($cast(m_tr, req)) begin
drive_my_transaction(m_tr);
`uvm_info("driver", "receive a transaction whose type is my_transaction", UVM_MEDIUM)
end
else if($cast(y_tr, req)) begin
drive_your_transaction(y_tr);
`uvm_info("driver", "receive a transaction whose type is your_transaction", UVM_MEDIUM)
end
else begin
`uvm_error("driver", "receive a transaction whose type is unknown")
end
seq_item_port.item_done();
end
endtask
p_sequencer的使用
如果在sequencer中使用config_db::get得到了两个成员变量的值,如:
class my_sequencer extends uvm_sequencer #(my_transaction);
bit[47:0] dmac;
bit[47:0] smac;
…
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
void'(uvm_config_db#(bit[47:0])::get(this, "", "dmac", dmac));
void'(uvm_config_db#(bit[47:0])::get(this, "", "smac", smac));
endfunction
`uvm_component_utils(my_sequencer)
endclass
之后sequence在发送transaction时,必须将目的地址设置为dmac,源地址设置为smac。可以使用以下方法在sequence的body中得到这两个变量的值:
class case0_sequence extends uvm_sequence #(my_transaction);
…
virtual task body();
…
repeat (10) begin
`uvm_do_with(m_trans, {m_trans.dmac == p_sequencer.dmac;
m_trans.smac == p_sequencer.smac;})
end
…
endtask
endclass
上述代码中uvm_declare_p_sequencer(SEQUENCER)宏声明了一个SEQUENCER类型的成员变量,如在定义sequence时,使用此宏声明sequencer的类型:
class case0_sequence extends uvm_sequence #(my_transaction);
my_transaction m_trans;
`uvm_object_utils(case0_sequence)
`uvm_declare_p_sequencer(my_sequencer)
…
endclass
则相当于声明了如下的成员变量:
class case0_sequence extends uvm_sequence #(my_transaction);
my_sequencer p_sequencer;
…
endclass
UVM之后会自动将m_sequencer通过cast转换成p_sequencer。这个过程在pre_body()之前就完成了。因此在sequence中可以直接使用成员变量p_sequencer来引用dmac和smac。
sequence的派生与继承
- sequence作为一个类,是可以从其中派生其他sequence的,由于在同一个项目中各sequence都是类似的,所以可以将很多公用的函数或者任务写在base sequence中,其他sequence都从此sequence派生。
- 但对于那些使用了uvm_declare_p_sequence声明p_sequencer的base sequence,在派生
的sequence中则不需要调用此宏声明p_sequencer。因为uvm_declare_p_sequence的实质是在base sequence中声明了一个成员变量p_sequencer。当其他的sequence从其派生时,p_sequencer依然是新的sequence的成员变量,所以无须再声明一次了。
virtual sequence
实现sequence之间同步的最好的方式就是使用virtual sequence。从字面上理解,即虚拟的sequence。虚拟的意思就是它根本就不发送transaction,它只是控制其他的sequence,起统一调度的作用。为了使用virtual sequence,一般需要一个virtual sequencer。virtual sequencer里面包含指向其他真实sequencer的指针:
class my_vsqr extends uvm_sequencer;
my_sequencer p_sqr0;
my_sequencer p_sqr1;
…
endclass
在base_test中,实例化vsqr,并将相应的sequencer赋值给vsqr中的sequencer的指针:
class base_test extends uvm_test;
my_env env0;
my_env env1;
my_vsqr v_sqr;
…
endclass
function void base_test::build_phase(uvm_phase phase);
super.build_phase(phase);
env0 = my_env::type_id::create("env0", this);
env1 = my_env::type_id::create("env1", this);
v_sqr = my_vsqr::type_id::create("v_sqr", this);
endfunction
function void base_test::connect_phase(uvm_phase phase);
v_sqr.p_sqr0 = env0.i_agt.sqr;
v_sqr.p_sqr1 = env1.i_agt.sqr;
endfunction
在virtual sequene中则可以使用uvm_do_on系列宏来发送transaction:
class case0_vseq extends uvm_sequence;
`uvm_object_utils(case0_vseq)
`uvm_declare_p_sequencer(my_vsqr)
…
virtual task body();
my_transaction tr;
drv0_seq seq0;
drv1_seq seq1;
…
`uvm_do_on_with(tr, p_sequencer.p_sqr0, {tr.pload.size == 1500;})
`uvm_info("vseq", "send one longest packet on p_sequencer.p_sqr0", UVM_MEDIUM)
fork
`uvm_do_on(seq0, p_sequencer.p_sqr0);
`uvm_do_on(seq1, p_sequencer.p_sqr1);
join
…
endtask
endclass
virtual sequence和virtual sequencer中virtual的来源意思为:它们各自并不产生transaction,而只是控制其他的sequence为相应的sequencer产生transaction。virtual sequence和virtual sequencer只是起一个调度的作用。
由于根本不直接产生transaction,所以virtual sequence和virtual sequencer在定义时根本无需指明要发送的transaction数据类型。
此外,使用virtual sequence还需注意:
- objection的控制:我们可以只在最顶层的virtual sequence中控制objection。因为virtual sequence是起统一调度作用的,这种统一调度不只体现在transaction上,也应该体现在objection的控制上。在验证平台中使用objection时,经常会出现没有按照预期结束仿真的情况。这种情况下就需要层层地查找哪里有objection被提起了,哪里有objection被撤销了。如果大家约定俗成都只在最顶层的virtual sequence中控制objection,那么在问题时,只查找最顶层的virtual sequence即可,从而大大提高效率。
- 线程的使用:在virtual sequence中尽量避免使用fork join_none,可以使用wait fork或者fork join来代替。
config_db的使用
在sequence中获取参数
在sequence中以如下的方式调用config_db::get函数:
class case0_sequence extends uvm_sequence #(my_transaction);
…
virtual task pre_body();
if(uvm_config_db#(int)::get(null, get_full_name(), "count", count))
`uvm_info("seq0", $sformatf("get count value %0d via config_db", count), UVM_MEDIUM)
else
`uvm_error("seq0", "can't get count value!")
endtask
…
endclass
在get函数原型中,第一个参数必须是一个component,而sequence不是一个component,
所以这里不能使用this指针,只能使用null或者uvm_root::get()。当使用null时,UVM会自动将其替换为uvm_root::get(),再加上第二个参数get_full_name(),就可以完整地得到此sequence的路径,从而得到参数。
在sequence中设置参数
在sequence中可以使用config_db::set为UVM树中的任意结点传递参数:
class case0_vseq extends uvm_sequence;
…
virtual task body();
…
fork
`uvm_do_on(seq0, p_sequencer.p_sqr0);
`uvm_do_on(seq1, p_sequencer.p_sqr1);
begin
#10000;
uvm_config_db#(bit)::set(uvm_root::get(), "uvm_test_top.env0.scb",
"cmp_en", 0);
#10000;
uvm_config_db#(bit)::set(uvm_root::get(), "uvm_test_top.env0.scb",
"cmp_en", 1);
end
join
…
endtask
endclass
除了向component中传递参数外,也可以向sequence中传递参数:
class drv0_seq extends uvm_sequence #(my_transaction);
my_transaction m_trans;
bit first_start;
`uvm_object_utils(drv0_seq)
function new(string name= "drv0_seq");
super.new(name);
first_start = 1;
endfunction
virtual task body();
void'(uvm_config_db#(bit)::get(uvm_root::get(), get_full_name(), "first_start", first_start));
if(first_start)
`uvm_info("drv0_seq", "this is the first start of the sequence", UVM_MEDIUM)
else
`uvm_info("drv0_seq", "this is not the first start of the sequence",UVM_MEDIUM)
uvm_config_db#(bit)::set(uvm_root::get(), "uvm_test_top.v_sqr.*", "first_start", 0);
…
endtask
endclass
这个sequence向自己传递了一个参数:first_start。在一次仿真中,当此sequence第一次启动时,其first_start值为1;当后面再次启动时,其first_start为0。根据first_start值的不同,可以在body中有不同的行为。
这里需要注意的是,由于此sequence在virtual sequence中被启动,所以其get_full_name的结果应该是uvm_test_top.v_sqr.*。
wait_modified的使用
在向scoreboard传递参数时,scoreboard可以根据收到的参数参数决定是否对收到的transaction进行检查。在做一些异常用例测试的时候,经常用到这种方式。
UVM中提供了wait_modified任务,它的参数有三个,与config_db::get的前三个参数完
全一样。当它检测到第三个参数的值被更新过后,它就返回,否则一直等待在那里。其调用方式如下:
task my_scoreboard::main_phase(uvm_phase phase);
…
fork
while(1) begin
uvm_config_db#(bit)::wait_modified(this, "", "cmp_en");
void'(uvm_config_db#(bit)::get(this, "", "cmp_en", cmp_en));
end
…
join
endtask
在上述代码中,wait_modified与main_phase中的其他进程在同一时刻被fork起来,当检测到参数值被设置后,立刻调用config_db::get得到新的参数。其他进程可以根据新的参数值决定后续的比对策略。
与get函数一样,除了可以在一个component中使用外,还可以在一个sequence中调用wait_modified任务:
class drv0_seq extends uvm_sequence #(my_transaction);
…
virtual task body();
bit send_en = 1;
fork
while(1) begin
uvm_config_db#(bit)::wait_modified(null, get_full_name(), "send_en");
void'(uvm_config_db#(bit)::get(null, get_full_name, "send_en", send_en));
end
join_none
…
endtask
endclass
response的使用
- put_response与get_response
如果sequence需要得到driver的一个反馈。则driver可以将一个response返回给sequence。
如果需要使用response,那么在sequence中需要使用get_response任务:
class case0_sequence extends uvm_sequence #(my_transaction);
…
virtual task body();
…
repeat (10) begin
`uvm_do(m_trans)
get_response(rsp);
`uvm_info("seq", "get one response", UVM_MEDIUM)
rsp.print();
end
…
endtask
`uvm_object_utils(case0_sequence)
endclass
在driver中,则需要使用put_response任务:
task my_driver::main_phase(uvm_phase phase);
…
while(1) begin
seq_item_port.get_next_item(req);
drive_one_pkt(req);
rsp = new("rsp");
rsp.set_id_info(req);
seq_item_port.put_response(rsp);
seq_item_port.item_done();
end
endtask
这里的关键是设置set_id_info函数,它将req的id等信息复制到rsp中。由于可能存在多个sequence在同一个sequencer上启动的情况,只有设置了rsp的id等信息,sequencer才知道将response返回给哪个sequence。
除了使用put_response外,UVM还支持直接将response作为item_done的参数:
while(1) begin
seq_item_port.get_next_item(req);
drive_one_pkt(req);
rsp = new("rsp");
rsp.set_id_info(req);
seq_item_port.item_done(rsp);
end
- response的数量问题
通常来说,一个transaction对应一个response,但是事实上,UVM也支持一个transaction对应多个response的情况,在这种情况下,在sequence中需要多次调用get_response,而在driver中,需要多次调用put_response:
task my_driver::main_phase(uvm_phase phase);
while(1) begin
seq_item_port.get_next_item(req);
drive_one_pkt(req);
rsp = new("rsp");
rsp.set_id_info(req);
seq_item_port.put_response(rsp);
seq_item_port.put_response(rsp);
seq_item_port.item_done();
end
endtask
class case0_sequence extends uvm_sequence #(my_transaction);
virtual task body();
repeat (10) begin
`uvm_do(m_trans)
get_response(rsp);
rsp.print();
get_response(rsp);
rsp.print();
end
endtask
endclass
当存在多个response时,将response作为item_done参数的方式就不适用了。由于一个transaction只能对应一个item_done,所以使用多次item_done(rsp)是会出错的。
- rsp与req类型不同
如果要使用不同类型的rsp与req,那么driver、sequencer与sequence在定义时都要传入两个参数:
class my_driver extends uvm_driver#(my_transaction, your_transaction);
class my_sequencer extends uvm_sequencer #(my_transaction, your_transaction);
class case0_sequence extends uvm_sequence #(my_transaction, your_transaction);
sequence library
- 随机选择sequence
所谓sequence library,就是一系列sequence的集合,其派生自uvm_sequence,从本质上说它是一个sequence。它根据特定的算法随机选择注册在其中的一些sequence,并在body中执行这些sequence。
一个sequence library的定义如下:
class simple_seq_library extends uvm_sequence_library#(my_transaction);
function new(string name= "simple_seq_library");
super.new(name);
init_sequence_library();
endfunction
`uvm_object_utils(simple_seq_library)
`uvm_sequence_library_utils(simple_seq_library);
endclass
在定义sequence library时有三点要特别注意:
一是从uvm_sequence派生时要指明此sequence library所产生的transaction类型,这
点与普通的sequence相同;
二是在其new函数中要调用init_sequence_library,否则其内部的候选sequence队列就是空的;
三是要调用uvm_sequence_library_utils注册。
一个sequence library在定义之后,如果没有其他任何的sequence注册到其中,是没有任何意义的。一个sequence在定义时使用宏uvm_add_to_seq_lib来将其加入某个sequence library中:
class seq0 extends uvm_sequence#(my_transaction);
…
`uvm_object_utils(seq0)
`uvm_add_to_seq_lib(seq0, simple_seq_library)
virtual task body();
repeat(10) begin
`uvm_do(req)
`uvm_info("seq0", "this is seq0", UVM_MEDIUM)
end
endtask
endclass
uvm_add_to_seq_lib有两个参数,第一个是此sequence的名字,第二个是要加入的sequence library的名字。一个sequence可以加入多个不同的sequence library中:
class seq0 extends uvm_sequence#(my_transaction);
`uvm_object_utils(seq0)
`uvm_add_to_seq_lib(seq0, simple_seq_library)
`uvm_add_to_seq_lib(seq0, hard_seq_library)
virtual task body();
repeat(10) begin
`uvm_do(req)
`uvm_info("seq0", "this is seq0", UVM_MEDIUM)
end
endtask
endclass
同样的,可以有多个sequence加入同一sequence library中:
class seq1 extends uvm_sequence#(my_transaction);
…
`uvm_object_utils(seq1)
`uvm_add_to_seq_lib(seq1, simple_seq_library)
virtual task body();
repeat(10) begin
`uvm_do(req)
`uvm_info("seq1", "this is seq1", UVM_MEDIUM)
end
endtask
endclass
当sequence与sequence library定义好后,可以将sequence library作为sequencer的default sequence:
function void my_case0::build_phase(uvm_phase phase);
super.build_phase(phase);
uvm_config_db#(uvm_object_wrapper)::set(this,
"env.i_agt.sqr.main_phase",
"default_sequence",
simple_seq_library::type_id::get());
endfunction
- 控制选择算法
sequence library随机从其sequence队列中选择几个执行。这是由其变量selection_mode决定的,这个变量的有以下几种算法:
- UVM_SEQ_LIB_RAND:完全的随机。
- UVM_SEQ_LIB_RANDC:将加入其中的sequence随机排一个顺序,然后按照此顺序执行。这可以保证每个sequence执行一遍,在所有的sequence被执行完一遍之前,不会有sequence被执行第二次,其配置方式如下:
function void my_case0::build_phase(uvm_phase phase);
…
uvm_config_db#(uvm_sequence_lib_mode)::set(this,
"env.i_agt.sqr.main_phase",
"default_sequence.selection_mode",
UVM_SEQ_LIB_RANDC);
endfunction
- UVM_SEQ_LIB_ITEM的意思是sequence library并不执行其sequence队列中的sequence,而是自己产生transaction。换言之,sequence library在此种情况下就是一个普通的sequence,只是其产生的transaction除了定义时施加的约束外,没有任何额外的约束。
- UVM_SEQ_LIB_USER是用户自定义选择的算法。
- 控制执行次数
执行的次数是由sequence library内部的两个变量控制的:
int unsigned min_random_count=10;
int unsigned max_random_count=10;
sequence library会在min_random_count和max_random_count之间随意选择一个数来作为执行次数。这里只能选择10。当selection_mode为UVM_SEQ_LIB_ITEM时,将会产生10个item;为其他模式时,将会顺序启动10个sequence。可以设置这两个值为其他值来改变迭代次数:
function void my_case0::build_phase(uvm_phase phase);
…
uvm_config_db#(uvm_object_wrapper)::set(this,
"env.i_agt.sqr.main_phase",
"default_sequence",
simple_seq_library::type_id::get());
uvm_config_db#(uvm_sequence_lib_mode)::set(this,
"env.i_agt.sqr.main_phase",
"default_sequence.selection_mode",
UVM_SEQ_LIB_ITEM);
uvm_config_db#(int unsigned)::set(this,
"env.i_agt.sqr.main_phase",
"default_sequence.min_random_count",
5);
uvm_config_db#(int unsigned)::set(this,
"env.i_agt.sqr.main_phase",
"default_sequence.max_random_count",
20);
endfunction
上述设置将会产生最多20个,最少5个transaction。
- 使用sequence_library_cfg
UVM提供了一个类uvm_sequence_library_cfg来对sequence library进行配置。它一共有三个成员变量:
class uvm_sequence_library_cfg extends uvm_object;
`uvm_object_utils(uvm_sequence_library_cfg)
uvm_sequence_lib_mode selection_mode;
int unsigned min_random_count;
int unsigned max_random_count;
…
endclass
通过配置如上三个成员变量,并将其传递给sequence library就可对sequence library进行配置:
function void my_case0::build_phase(uvm_phase phase);
uvm_sequence_library_cfg cfg;
super.build_phase(phase);
cfg = new("cfg", UVM_SEQ_LIB_RANDC, 5, 20);
uvm_config_db#(uvm_object_wrapper)::set(this,
"env.i_agt.sqr.main_phase",
"default_sequence",
simple_seq_library::type_id::get());
uvm_config_db#(uvm_sequence_library_cfg)::set(this,
"env.i_agt.sqr.main_phase",
"default_sequence.config",
cfg);
endfunction