目录
在sequence中使用config_db
*在sequence中获取参数
config_db机制set函数的目标是一个component,或者说之前所有获取参数的操作都是在一个component中进行的。sequence机制是UVM中最强大的机制之一,config_db机制也对sequence机制提供了支持,可以在sequence中获取参数。
能够调用config_db::get的前提是已经进行了设置。sequence本身是一个uvm_object,它无法像uvm_component那样出现在UVM树中,从而很难确定在对其进行设置时的第二个路径参数。所以在sequence中使用config_db::get函数得到参数的最大障碍是路径问题。
在UVM中使用get_full_name()可以得到一个component的完整路径,同样,此函数也可以在一个sequence中被调用,尝试着在一个sequence的body中调用此函数并打印出返回值,其结果大体如下:uvm_test_top.env.i_agt.sqr.case0_sequence
这个路径由两部分组成:此sequence的sequencer的路径和实例化此sequence时传递的名字。可使用如下方式为一个sequence传递参数:
function void my_case0::build_phase(uvm_phase phase);
…
uvm_config_db#(int)::set(this,"env.i_agt.sqr.*","count",9);
…
endfunction
set函数的第二个路径参数里出现了通配符,是因为sequence在实例化时名字一般是不固定的,而且有时是未知的(如使用default_sequence启动的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设置参数就比较简单。有了在top_tb中设置virtual interface的经验,读者在这里可以使用类似的方式为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
上例中是向scoreboard中传递了一个cmp_en的参数。除了向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.*
,而不是uvm_test_top.env0.i_agt.sqr.*
,所以在设置时第二个参数应该是前者。
*wait_modified的使用
上节例子向scoreboard传递cmp_en的参数,scoreboard可根据此参数决定对收到的transaction是否进行检查。做异常用例测试时常用到这种方式。关键是如何在scoreboard中获取这个参数。
前面章节中scoreboard都是在build_phase中调用get函数,并调用的前提是参数已经被设置过。一个sequence是在task phase中运行的,当其设置一个参数时,其时间往往是不固定的。
针对这种不固定的设置参数方式,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));
`uvm_info("my_scoreboard", $sformatf("cmp_en value modified,
the new value is %0d", 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));
`uvm_info("drv0_seq", $sformatf("send_en value modified,
the new value is %0d", send_en));
end
join_none
…
endtask
endclass
response的使用
*put_response与get_response
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); //notice
`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); //notice
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); //notice
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)是会出错的。
response机制的原理是driver将rsp推送给sequencer,而sequencer内部维持一个队列,当有新的response进入时就推入此队列。但此队列的大小是有限制的,默认情况下大小为8。队列中有8个response时,如果driver再次向此队列推送新的response,UVM就会给出如下错误提示:
UVM_ERROR @ 1753500000: uvm_test_top.env.i_agt.sqr@@case0_sequence
[uvm_test_top.env.i_agt.sqr.case0_sequence] Response queue overflow, response was dropped
因此,如果在driver中每个transaction后都发送一个response,而sequence又没能及时get_response,sequencer中的response队列就存在溢出的风险。
*response handler与另类的response
get_response和put_response一一对应。在sequence中启动get_response时,进程就会阻塞直到response_queue中被放入新的记录。如果driver能马上将response通过put_response方式传回sequence,那么sequence被阻塞的进程会得到释放,可以接着发送下一个transaction给driver。
但假如driver需要延时较长的一段时间才能将transaction传回在此期间,driver希望能够继续从sequence得到新的transaction并驱动它,但由于sequence被阻塞在了那里,根本不可能发出新的transaction。
发生上述情况的主要原因为sequence中发送transaction与get_response在同一个进程中执行,假如将二者分开,在不同进程中运行将会得到不同结果。这种情况需使用response_handler:
class case0_sequence extends uvm_sequence #(my_transaction);
…
virtual task pre_body();
use_response_handler(1); //notice
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类型的指针,需首先将其通过cast转换变成my_transaction类型,之后就可根据rsp的值来决定后续sequence的行为。
put/get_response或response_handler,都是新建一个transaction并将其返回给sequence。事实上当一个uvm_do语句执行完毕后,其第一个参数并不是一个空指针,而是指向刚刚被送给driver的transaction。利用这一点可以实现一种另类的response:
task my_driver::main_phase(uvm_phase phase);
…
while(1) begin
seq_item_port.get_next_item(req);
drive_one_pkt(req);
req.frm_drv = "this is information from driver";
seq_item_port.item_done();
end
endtask
driver中向req中的成员变量赋值,而sequence则检测这个值:
class case0_sequence extends uvm_sequence #(my_transaction);
…
virtual task body();
…
repeat (10) begin
`uvm_do(m_trans)
`uvm_info("seq", $sformatf("get information from driver: %0s",
m_trans.frm_drv), UVM_MEDIUM)
end
…
endtask
`uvm_object_utils(case0_sequence)
endclass
这种另类的response在很多总线的driver中用到。
*rsp与req类型不同
前面所有例子的response类型都与req类型相同。UVM也支持response与req类型不同的情况。
uvm_driver、uvm_sequencer与uvm_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在定义时都要传入两个参数:
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);
之后,可以使用put_response来发送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);
rsp.information = "driver information";
seq_item_port.put_response(rsp);
seq_item_port.item_done();
end
endtask
使用get_response来接收response:
class case0_sequence extends uvm_sequence #(my_transaction, your_transaction);
…
virtual task body();
…
repeat (10) begin
`uvm_do(m_trans)
get_response(rsp);
`uvm_info("seq", $sformatf("response information is: %0s",
rsp.information), UVM_MEDIUM)
end
…
endtask
`uvm_object_utils(case0_sequence)
endclass
除了put/get_response外,也可以使用response handler,这与req及rsp类型相同时完全一样。