sequence机制的作用是为了从driver中剥离出产生激励的功能。在不同的测试用例中,将不同的sequence设置成sequencer的main_phase的default_sequence。当sequencer执行到main_phase时,发现有default_sequence,那么就会启动sequence(default也是调用start任务启动sequence)。
//直接设置sequence的类类型为default_sequence
uvm_config_db#(uvm_object_wrapper)::set(this,
"env.i_agt.sqr.main_phase",
"default_sequence",
case0_sequence::type_id::get());
//实例化sequence对象,再设置default_sequence
function 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
当一个sequence启动后会自动执行sequence的body任务。除了body任务外,还会自动调用sequence的pre_body和post_body任务。
在同一个sequencer上启动多个sequence时,哪个sequence先执行,这就涉及到seq的优先级和UVM仲裁算法。
`uvm_do_pri(m_trans, 100)
`uvm_do_pri_with(m_trans, 200, {m_trans.pload.size < 500;})
seq0.start(env.i_agt.sqr, null, 100);
UVM中仲裁算法:
SEQ_ARB_FIFO:先进先出,不考虑优先级(默认)
SEQ_ARB_WEIGHTED:加权仲裁
SEQ_ARB_RANDOM:随机选择
SEQ_ARB_STRICT_FIFO:严格按照优先级,当有多个同一优先级,按照先进先出
SEQ_ARB_STRICT_RANDOM:严格按照优先级,当有多个同一优先级,随机从最高优先级中选择
SEQ_ARB_USER:用户自定义新的仲裁算法
通常transaction和sequence的优先级和UVM仲裁算法配合使用。如果想按照sequence或者transaction的优先级进行发送激励,则要讲UVM仲裁算法设置为SEQ_ARB_STRICT_FIFO或者SEQ_ARB_STRICT_RANDOM。仲裁算法通过sequencer的set_arbitration来实现。
env.i_agt.sqr.set_arbitration(SEQ_ARB_STRICT_FIFO);
Lock操作就是sequencer在开始相应这个lock请求,此后sequencer会一直连续发送此sequence的transaction,直到unlock操作被调用。从效果上来看,此sequencer的所有权并没有被所有的sequence共享,而是被申请lock操作的sequence独占了。代码如下,在lock语句之后,一直发送sequence1的transaction,直到unlock语句被调用。
class sequence1 extends uvm_sequence #(my_transaction);
…
virtual task body();
…
repeat (3)
begin
`uvm_do_with(m_trans, {m_trans.pload.size < 500;})
`uvm_info("sequence1", "send one transaction", UVM_MEDIUM)
end
lock();
`uvm_info("sequence1", "locked the sequencer ", UVM_MEDIUM)
repeat (4)
begin
`uvm_do_with(m_trans, {m_trans.pload.size < 500;})
`uvm_info("sequence1", "send one transaction", UVM_MEDIUM)
end
`uvm_info("sequence1", "unlocked the sequencer ", UVM_MEDIUM)
unlock();
repeat (3)
begin
`uvm_do_with(m_trans, {m_trans.pload.size < 500;})
`uvm_info("sequence1", "send one transaction", UVM_MEDIUM)
end
…
endtask
…
endclass
如果两个sequence都使用lock任务来获取sequencer的所有权,先获得所有权的sequence在执行完毕后才会将所有权交还给另外一个sequence。
grab操作也是用于暂时用于sequencer的所有权,grab操作比lock操作优先级更高。lock请求是被插入sequencer仲裁队列的最后面,等到它时,他前面的仲裁请求都已经结束了。grap请求则被放入sequencer仲裁队列的最前面,它几乎是已发出就拥有了sequencer的所有权。
如果两个sequence同时使用grab任务获取sequencer的所有权,则此情形和两sequence同时调用lock函数一样,在先获得所有权的sequence执行完毕后才会将所有权交还给另一个试图获取所有权的sequence。
如果一个sequence在使用grap任务获取sequencer的所有权前,另一个sequence已经使用lock任务获得了sequencer的所有权,则grap任务会一直等待lock的释放。grap任务会插队,但是绝对不会打断别人正在进行的事情。
通过lock/grab任务,sequence可以独占sequencer,强行sequencer发送自己产生的transaction。同样的,UVM也提供了措施使sequence可以在一定时间不参与仲裁。
sequencer在仲裁时,会查看sequence的is_relevant函数的返回结果。如果为1,说明此sequence有效,否则无效。可以通过重载is_relevant函数来使sequence失效。
class sequence0 extends uvm_sequence #(my_transaction);
my_transaction m_trans;
int num;
bit has_delayed;
…
virtual function bit is_relevant();
if((num >= 3)&&(!has_delayed)) return 0;
else return 1;
endfunction
virtual task body();
fork
repeat (10)
begin
num++;
`uvm_do(m_trans)
`uvm_info("sequence0", "send one transaction", UVM_MEDIUM)
end
while(1)
begin
if(!has_delayed)
begin
if(num >= 3)
begin
`uvm_info("sequence0", "begin to delay", UVM_MEDIUM)
#500000;
has_delayed = 1'b1;
`uvm_info("sequence0", "end delay", UVM_MEDIUM)
break;
end
else #1000;
end
end
join
…
endtask
…
endclass
上述代码中sequence在发送3个transaction后开始变为无效,延时500000时间单位后又开始有效。
除了is_relevant外,sequence还有一个任务wait_for_relevant也与sequence的有效性相关。当sequencer发现sequence无效时,会调用其wait_for_relevant任务。在wait_for_relevant中,必须将使sequence无效的条件清除(当不清除sequence无效状态时,在sequencer调用sequence的wait_for_relevant后,发现sequence依然处于无效状态,那么会继续调用wait_for_relevant任务,系统将陷入死循环)。因此is_relevant和wait_for_relevant一般应成对重载,不能只重载一个。
uvm_do系列宏主要有以下8个:
uvm_do_on用于显式地指定使用哪个sequencer发送此transaction。他有两个参数,第一个是transaction的指针,第二个是sequencer的指针。当在sequence中使用uvm_do等宏时,其默认的sequencer就是此sequence启动时为其指定的sequencer,sequence将这个sequencer的指针放在其成员变量m_sequencer中。
uvm_do系列宏中,其第一个参数除了可以是transaction的指针外,还可以是某个serquence的指针。当第一个参数是transaction时,他会调用start_item和finish_item(参考6.3.4);当第一个参数是sequence时,他调用sequence的start任务。
//uvm_do宏等价于如下代码
`uvm_do_on(tr, this.m_sequencer)
uvm_do_on_pri的第三个参数是优先级,这个数值必须是一个大于等于-1的整数。数值越大,优先级越高。
`uvm_do_on_pri(tr, this, 100)
uvm_do_on_with的第三个参数是约束。
`uvm_do_on_with(tr, this, {tr.pload.size==100;} )
uvm_do_on_pri_with,有四个参数,是所有uvm_do宏中参数最多的一个。uvm_do系列宏的其他七个宏都是用uvm_do_on_pri_with宏来实现的。
`uvm_do_on_pri_with(tr, this, 100, {tr.pload.size==100;} )
除了uvm_do宏产生transaction,还可以使用uvm_create宏与uvm_send宏来产生。
相比uvm_do系列宏,使用uvm_create/uvm_send/uvm_send_pri系列宏更加灵活,可以对transaction进行控制。
uvm_rand_send宏与uvm_send宏类似,唯一的区别是它会对transaction进行随机化。这个宏使用的前提是transaction已经被分配了空间(已经被实例化)。
uvm_rand_send,uvm_send系列宏的意义主要在于,如果一个transaction占用的内存比较大,那么可能希望前后两次发送的transaction都使用同一块内存,只是其中的内容可以不同,这样比较节省内存。
宏隐藏了细节,方便了用户的使用,但是也给用户带来了困扰:宏到底做了什么事情?
使用uvm_do系列宏就相当于使用以下代码:
virtual task bodu();
tr=new("tr");
start_item(tr);
assert(tr.randomize() with {tr.pload.size() == 200;
});
finish_item(tr);
endtask
uvm_do宏封装了从transaction实例化到发送的一系列操作,封装的越多,则其灵活性越差。为了增加uvm_do系列宏的功能,UVM提供了三个接口:pre_do,mid_do和post_do(类似于回调函数)。
start_item和finish_item的使用需要配合pre_do,mid_do和post_do一同使用。
如果有需求需要产生不同类型的sequence,那么可以使用嵌套sequence。所谓嵌套sequence,即在一个sequence的body中,启动其他的sequence。通过嵌套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
在transaction的定义中,通常使用rand来对变量进行修饰,说明在调用randomize时要对此字段进行随机化。
sequence中也可以添加任意多的rand修饰符,此sequence作为底层的sequence供顶层sequence调用。
一个sequencer只能产生一种类型的transaction,一个sequence如果想要在此sequencer上启动,那么其所产生的transaction的类型必须是这种transaction或者派生自这种transaction。这里需注意的是嵌套sequence,由于包含多个sequence,必须保证嵌套sequence中每个sequence产生的transaction与sequencer上允许的transaction是同一种transaction,此时嵌套sequence才可以在此sequencer上启动。
在sequencer上发送两个或者多个不同的transaction:将sequencer中允许发送的transaction定义为父类(uvm_sequence_item),在嵌套seuqnece中每个sequence上发送的transaction继承自uvm_sequence_item,即可实现在同一个sequencer上发送不同transaction(sequence中的transaction是sequencer上transaction的子类)。这里对其他组件带来的影响是,driver组件中的transaction同样需要声明为uvm_sequence_item类型。
考虑一种情况,如何在sequence中访问sequencer中的成员变量呢?
一种方法是使用m_sequencer。m_sequencer是属于每个sequence的成员变量,其指向该sequence启动的sequencer。如果直接使用m_sequencer访问sequencer的变量,会引起编译错误。因为m_seuqencer是uvm_sequencer_base(uvm_sequencer的基类)类型,而不是my_sequencer类型。使用前需要在my_sequence中通过case转换将m_sequencer转换成my_sequencer类型,这样就可以引用sequencer中定义的变量。
virtual task body();
my_sequencer x_sequencer;
…
$cast(x_sequencer, m_sequencer);
repeat (10)
begin
`uvm_do_with(m_trans, {m_trans.dmac == x_sequencer.dmac;
m_trans.smac == x_sequencer.smac;})
end
…
endtask
另一种方法是使用p_sequencer。UVM内置了一个宏uvm_declare_p_sequencer(SEQUENCER),该宏声明了一个SEQUENCER类型的成员变量。使用此宏的本质是声明了一个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访问sequencer中的成员变量。
sequence作为一个类,可以从其中派生其他sequence的。同一个项目中各sequence都是类似的,所以可以将很多公用的函数或者任务下载base_sequence中,其他的sequence都从此sequence派生。
普通的sequence这样使用没有任何问题,但是对于使用了uvm_declare_p_sequence声明的base_sequence,在派生的sequence中是否也要使用此宏重新声明p_sequencer?答案是不需要重新声明,因为uvm_declare_p_sequence的实质是在base_sequence中声明了一个成员变量p_sequencer,当其他sequence从其派生时,子类sequence会继承父类的成员变量p_sequencer,所以无需再声明一次。如果在子类sequence中再声明一次,系统也不会报错,此时相当于声明了两个p_sequencer,一个属于父类,一个属于子类。
利用全局事件进行同步
event send_over; //global event
class drv0_seq extends uvm_sequence #(my_transaction);
…
virtual task body();
…
`uvm_do_with(m_trans, {m_trans.pload.size == 1500;})
->send_over;
repeat (10)
begin
`uvm_do(m_trans)
`uvm_info("drv0_seq", "send one transaction", UVM_MEDIUM)
end
…
endtask
endclass
class drv1_seq extends uvm_sequence #(my_transaction);
…
virtual task body();
@send_over;
repeat (10)
begin
`uvm_do(m_trans)
`uvm_info("drv1_seq", "send one transaction", UVM_MEDIUM)
end
…
endtask
endclass
如上述代码中,drv0_seq在发送一个m_trans后,触发全局事件send_overdrv1_seq才会开始发送transaction。
上节中使用了全局变量进行同步,但是,除非有必要,尽量不要使用全局变量。事实上,上节中使用全局变量只实现了一次同步,如果要进行多次同步呢?
实现sequence之间的同步最好的方式是使用virtual sequence。虚拟的sequence本身不发送transaction,只是控制其他sequence,起统一调度作用。为了使用virtual sequence,一般需要一个virtual sequencer。virtual sequencer里面包含指向其他真实sequencer的指针。由于virtual sequence/sequencer根本不直接产生transaction,所以virtual sequence/sequencer在定义时根本无需指明要发送的transaction数据类型。
class my_vsqr extends uvm_sequencer;
my_sequencer p_sqr0;
my_sequencer p_sqr1;
…
endclass
在base_test中,实例化vsqr,并将相应的sequencer赋值给vseq中的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 sequence中则可以使用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是uvm_do_on宏用的最多的地方。virtual sequence中的body任务代码是顺序执行的,在其中启动seq会自动顺序执行,同时可以使用fork…join系列语句对不同seq启动进行控制。
另外,virtual sequence的使用可以减少config_db语句的使用(设置sequence为启动sequencer的main_phase的default sequence)。virtual sequence作为一种特殊的sequence,也可以在其中启动其他的virtual sequence。
在sequence中可以使用starting_phase来控制验证平台的关闭。除了手动启动sequence时为starting_phase赋值外,只有将此sequence作为sequencer的某动态运行phase的default_sequence时,其starting_phase才不为null。如果将某些sequence作为uvm_do宏的参数,那么此sequence的starting_phase是为null的,在此sequence中使用starting_phase的raise_objection是没有任何用处的。
在有virtual sequence之后,有三个地方可以控制objection。一个是普通的sequence,二是中间层的virtual sequence。三是最顶层的virtual sequence。
一般来说只在最顶层的virtual sequence中控制objection。因为virtual sequence是起统一调度作用的,这种统一调度不只体现在transaction,也应该体现在objection的控制上。
class case0_vseq extends uvm_sequence;
virtual task body();
drv_seq dseq[4];
for(int i = 0; i < 4; i++)
fork
automatic int j = i;
`uvm_do_on(dseq[j], p_sequencer.p_sqr[j]);
join_none
endtask
endclass
在for循环中使用fork join_none,其等同于以下代码:
class case0_vseq extends uvm_sequence;
virtual task body();
drv_seq dseq[4];
fork
uvm_do_on(dseq[0], p_sequencer.p_sqr[0]);
join_none
fork
uvm_do_on(dseq[1], p_sequencer.p_sqr[1]);
join_none
fork
uvm_do_on(dseq[2], p_sequencer.p_sqr[2]);
join_none
fork
uvm_do_on(dseq[3], p_sequencer.p_sqr[3]);
join_none
//wait fork; wait all 4-thread end
endtask
endclass
上述代码启动了四个线程,系统并不会等这4个sequence执行完毕就返回了。执行完成之后,系统会清理这4个sequence之前占据的内存空间。因此,这个四个线程只是启动,并没有完成就被系统杀掉了。要避免这个问题,需要在线程后添加wait fork语句。
sequence机制是UVM中强大的机制之一,config_db机制也对sequence机制提供了支持,可以在sequence中获取参数。sequence本身是一个object,无法像uvm_component那样出现在UVM树中,所以在sequence中获取参数最大的问题是路径问题。
可以使用get_full_name得到一个component的完整路径,同样的,此函数也可以在一个sequence中被调用。以下为sequence中调用此函数打印的结果:
uvm_test_top.env.i_agt.sqr.case0_sequence
这个路径有两部分组成:此sequence的sequencer的路径+此sequence实例化时传递的名字。因此可以使用如下方式为一个sequencer传递参数:
uvm_config_db#(int)::set(this, "env.i_agt.sqr.*", "count", 9);
在set函数的第二个参数出现了通配符,因为sequence在实例化名字一般是不固定的,而且有时是未知的(如使用default_sequence启动的sequence的名字就是未知的),所以使用通配符。在sequence中使用如下函数获取参数:
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
这里需要说明的是第一个参数,由于sequence不是一个component,所以第一个参数不能是this。只能使用null,或者uvm_root::get()。
在sequence中获取参数与设置参数类似:
//向scb中设置参数cmp_en
uvm_config_db#(bit)::set(uvm_root::get(), "uvm_test_top.env0.scb", "cmp_en", 0);
//向sequence中设置参数first_start
uvm_config_db#(bit)::set(uvm_root::get(), "uvm_test_top.v_sqr.*", "first_start", 0);
一个sequence是在task phase中运行的,当其设置一个参数时,其时间往往是不固定的。真毒这种不固定的设置参数的方式,UVM中提供了wait modified任务。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));
`uvm_info("my_scoreboard", $sformatf("cmp_en value modified, the new value is %0d", cmp_en), UVM_LOW)
end
…
join
endtask
上述代码中,wait_modified检测到参数值被设置后,立刻调用config_db::get得到新的参数。与get函数一样,除了可以在一个component中使用,还可以在一个sequence中使用wait_modified任务。
sequence提供了一种sequence→sequencer→driver的单向数据传输机制。在一个复杂的验证平台中,sequence需要根据driver对transaction的反应来决定要发送的transaction,换而言之,sequence需要得到driver的一个反馈。sequence机制提供对这种反馈的支持,它允许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
在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的参数。
通常来说,一个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会出错。
response机制的原理是,driver将rsp推送给sequencer,而sequencer内部维持一个队列,当有新的response进入时,就推入此对垒。默认情况下,此队列的大小为8。当队列中元素满时,driver再次向此队列推送新的response,UVM会报错。
driver发送response和sequence获取response是在同一个进程中进行的。如果想将两者分离开来,在不同的进程中运行将会看到不同的结果,这种情况需要使用response_handler。
class case0_sequence extends uvm_sequence #(my_transaction);
…
virtual task pre_body();
use_response_handler(1);
endtask
virtual function void response_handler(uvm_sequence_item response);
if(!$cast(rsp, response))
`uvm_error("seq", "can't cast")
else
begin
`uvm_info("seq", "get one response", UVM_MEDIUM)
rsp.print();
end
endfunction
virtual task body();
if(starting_phase != null)
starting_phase.raise_objection(this);
repeat (10)
begin
`uvm_do(m_trans)
end
#100;
if(starting_phase != null)
starting_phase.drop_objection(this);
endtask
`uvm_object_utils(case0_sequence)
endclass
response handler功能默认是关闭的,如果要是用response_handler,首先要调用use_response_handler函数,打开sequence的response handler功能。
打开response_handler功能后,用户需要重载函数response_handler,此函数的参数是一个uvm_sequence_item类型的指针,需要首先将其通过case转换成my_transaction类型,之后就可以根据rsp的值来决定后续sequencer的行为。
以上例子中response与req都是相同的情况,UVM也支持response与req类型不同的情况。driver,sequencer,sequence的原型分别是:
class uvm_driver #(type REQ=uvm_sequence_item, type RSP=REQ) extends uvm_component;
class uvm_sequencer #(type REQ=uvm_sequence_item, RSP=REQ) extends uvm_sequencer_param_base #(REQ, RSP);
virtual class uvm_sequence #(type REQ = uvm_sequence_item, type RSP = REQ) extends uvm_sequence_base;
默认情况下response与req的类型是相同的,如果要使用rsp与req不同的情况,需要在定义driver,sequencer,sequence在定义时都要传入两个参数。
sequence library:一系列sequence的集合。sequence_library类的原型为:
class uvm_sequence_library #(type REQ=uvm_sequence_item, RSP=REQ) extends uvm_sequence #(REQ, RSP);
sequence library派生自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时要注意:
一个sequence library在定义后,如果没有其他任何的sequence注册到其中,是没有任何意义的。使用uvm_add_to_seq_lib宏将sequence加入某个sequence library中。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)
virtual task body();
repeat(10)
begin
`uvm_do(req)
`uvm_info("seq0", "this is seq0", 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
执行上述代码后,UVM会随机从加入simple_seq_library中的sequence中选择几个并顺序启动他们。
上节中,sequence library随机从其sequence队列中选择几个执行,这是由变量selection_mode决定的。selection_mode是一个枚举类型,一共有四个值:
typedef enum
{
UVM_SEQ_LIB_RAND, //完全随机
UVM_SEQ_LIB_RANDC, //周期随机
UVM_SEQ_LIB_ITEM, //自己产生transaction,不执行其sequence队列中的sequence
UVM_SEQ_LIB_USER //用户自定义的算法,需要用户重载select_sequence函数
} uvm_sequence_lib_mode;
设置selection_mode的数值:
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
重载select_sequecne函数:
class simple_seq_library extends uvm_sequence_library#(my_transaction);
...
virtual function int unsigned select_sequence(int unsigned max);
static int unsigned index[$];
static bit inited;
int value;
if(!inited)
begin
for(int i = 0; i <= max; i++)
begin
if((sequences[i].get_type_name() == "seq0") || (sequences[i].get_type_name() == "seq1") || (sequences[i].get_type_name() == "seq3"))
index.push_back(i);
end
inited = 1;
end
value = $urandom_range(0, index.size() - 1);
return index[value];
endfunction
endclass
select_sequence会传入一个max,select_sequence函数必须返回一个介于0~max之间的数值。如果sequences队列的大小为 4,那么传入的max数值应该是3而不是4。
sequence library会在min_random_count和max_random_count之间随意选择一个树来作为执行次数。可以通过设定这两个值来改变迭代次数。
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);
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
除了使用专门的cfg外,还可以使用在build_phase中直接对lib中变量进行赋值来配置lib变量。
function void my_case0::build_phase(uvm_phase phase);
simple_seq_library seq_lib;
super.build_phase(phase);
seq_lib = new("seq_lib");
seq_lib.selection_mode = UVM_SEQ_LIB_RANDC;
seq_lib.min_random_count = 10;
seq_lib.max_random_count = 15;
uvm_config_db#(uvm_sequence_base)::set(this,
"env.i_agt.sqr.main_phase",
"default_sequence",
seq_lib);
endfunction
参考文献:
UVM实战(卷Ⅰ)张强 编著 机械工业出版社