UVM中response的使用详解
put_response与get_response
sequence机制提供了一种sequence→sequencer→driver的单向数据传输机制。但是在复杂的验证平台中,sequence需要根据driver对transaction的反应来决定接下来要发送的transaction,换言之,sequence需要得到driver的一个反馈。sequence机制提供对这种反馈的支持,它允许driver将一个response返回给sequence。
如果需要使用response,那么在sequence中需要使用get_response任务:
//my_case0.sv
3 class case0_sequence extends uvm_sequence #(my_transaction);
…
10 virtual task body();
…
13 repeat (10) begin
14 `uvm_do(m_trans)
15 get_response(rsp); //
16 `uvm_info("seq", "get one response", UVM_MEDIUM)
17 rsp.print();
18 end
或 repeat (10) begin
`uvm_create(m_trans)//可以利用uvm_create和uvm_send的优点
//assert(ip_tr.randomize() with {ip_tr.src_ip == 'h9999; ip_tr.dest_ip == 'h10000;}) //
assert(m_trans.randmoize());
p_sz = m_trans.pload.size();
{m_trans.pload[p_sz-2],
m_trans.pload[p_sz-1]}
= num;
`uvm_send(m_trans)
end
…
22 endtask
23
24 `uvm_object_utils(case0_sequence)
25 endclass
在driver中,则需要使用put_response任务:
//my_driver.sv
22 task my_driver::main_phase(uvm_phase phase);
…
27 while(1) begin
28 seq_item_port.get_next_item(req); //seq_item_port.get_next_item(req);
29 drive_one_pkt(req); //drive_one_pkt(req);
30 rsp = new("rsp"); //void'($cast(rsp, req.clone()));
31 rsp.set_id_info(req); //rsp.set_sequence_id(req.get_sequence_id());
32 seq_item_port.put_response(rsp); //seq_item_port.put_response(rsp);
33 seq_item_port.item_done(); //seq_item_port.item_done();
34 end
35 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); //1
seq_item_port.put_response(rsp); //2
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); //1
rsp.print();
get_response(rsp); //2
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:
//my_case0.sv
3 class case0_sequence extends uvm_sequence #(my_transaction);
…
10 virtual task pre_body();
11 use_response_handler(1); //打开response的response_handler功能
12 endtask
13
14 virtual function void response_handler(uvm_sequence_item response); //当打开response_handler功能后,用户需要
15 if(!$cast(rsp, response)) //重载虚函数response_handler。此函数的参数是一个uvm_sequence_item类型的指针,需要
16 `uvm_error("seq", "can't cast")//首先将其通过cast转换为my_transactiono类型,之后就可以根据rsp的值来决定后续
17 else begin //sequence的行为。
18 `uvm_info("seq", "get one response", UVM_MEDIUM)
19 rsp.print();
20 end
21 endfunction
22
23 virtual task body();
24 if(starting_phase != null)
25 starting_phase.raise_objection(this);
26 repeat (10) begin
27 `uvm_do(m_trans)
28 end
29 #100;
30 if(starting_phase != null)
31 starting_phase.drop_objection(this);
32 endtask
33
34 `uvm_object_utils(case0_sequence)
35 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:
//my_driver.sv
22 task my_driver::main_phase(uvm_phase phase);
…
27 while(1) begin
28 seq_item_port.get_next_item(req);
29 drive_one_pkt(req);
30 req.frm_drv = "this is information from driver"; //
31 seq_item_port.item_done();
32 end
33 endtask
driver中向req中的成员变量赋值,而sequence则检测这个值:
//my_case0.sv
3 class case0_sequence extends uvm_sequence #(my_transaction);
…
10 virtual task body();
…
13 repeat (10) begin
14 `uvm_do(m_trans)
15 `uvm_info("seq", $sformatf("get information from driver: %0s", m_trans.frm_drv), UVM_MEDIUM) //
16 end
…
20 endtask
21
22 `uvm_object_utils(case0_sequence)
23 endclass
这种另类的response在很多总线的driver中用到。读者可以参考7.1.1节的内容。
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:
//my_driver.sv
22 task my_driver::main_phase(uvm_phase phase);
…
27 while(1) begin
28 seq_item_port.get_next_item(req);
29 drive_one_pkt(req);
30 rsp = new("rsp"); //
31 rsp.set_id_info(req); //
32 rsp.information = "driver information";//
33 seq_item_port.put_response(rsp); //
34 seq_item_port.item_done();
35 end
36 endtask
使用get_response来接收response:
//my_case0.sv
3 class case0_sequence extends uvm_sequence #(my_transaction, your_transaction);
…
10 virtual task body();
…
13 repeat (10) begin
14 `uvm_do(m_trans)
15 get_response(rsp); //
16 `uvm_info("seq", $sformatf("response information is: %0s", rsp.information), UVM_MEDIUM)
17 end
…
21 endtask
22
23 `uvm_object_utils(case0_sequence)
24 endclass
除了put/get_response外,也可以使用response handler,这与req及rsp类型相同时完全一样。
关于response的函数:
// Function: use_response_handler
//
// When called with enable set to 1, responses will be sent to the response
// handler. Otherwise, responses must be retrieved using get_response.
//
// By default, responses from the driver are retrieved in the sequence by
// calling get_response.
//
// An alternative method is for the sequencer to call the response_handler
// function with each response.
function void use_response_handler(bit enable);
m_use_response_handler = enable;
endfunction
// Function: get_use_response_handler
//
// Returns the state of the use_response_handler bit.
function bit get_use_response_handler();
return m_use_response_handler;
endfunction
// Function: response_handler
//
// When the use_response_handler bit is set to 1, this virtual task is called
// by the sequencer for each response that arrives for this sequence.
virtual function void response_handler(uvm_sequence_item response);
return;
endfunction
// Function: set_response_queue_error_report_disabled
//
// By default, if the response_queue overflows, an error is reported. The
// response_queue will overflow if more responses are sent to this sequence
// from the driver than get_response calls are made. Setting value to 0////
// disables these errors, while setting it to 1 enables them.////
function void set_response_queue_error_report_disabled(bit value);
response_queue_error_report_disabled = value;
endfunction
// Function: get_response_queue_error_report_disabled
//
// When this bit is 0 (default value), error reports are generated when
// the response queue overflows. When this bit is 1, no such error
// reports are generated.
function bit get_response_queue_error_report_disabled();
return response_queue_error_report_disabled;
endfunction
// Function: set_response_queue_depth
//
// The default maximum depth of the response queue is 8. These method is used////
// to examine or change the maximum depth of the response queue.////
//
// Setting the response_queue_depth to -1 indicates an arbitrarily deep////
// response queue. No checking is done.////
function void set_response_queue_depth(int value);
response_queue_depth = value;
endfunction
// Function: get_response_queue_depth
//
// Returns the current depth setting for the response queue.////
function int get_response_queue_depth();
return response_queue_depth;
endfunction
// Function: clear_response_queue
//
// Empties the response queue for this sequence.////
virtual function void clear_response_queue();
response_queue.delete();
endfunction
// channel driver
class chnl_driver extends uvm_driver #(chnl_trans);
local virtual chnl_intf intf;
`uvm_component_utils(chnl_driver)
function new (string name = "chnl_driver", uvm_component parent);
super.new(name, parent);
endfunction
function void set_interface(virtual chnl_intf intf);
if(intf == null)
$error("interface handle is NULL, please check if target interface has been intantiated");
else
this.intf = intf;
endfunction
task run_phase(uvm_phase phase);
fork
this.do_drive();
this.do_reset();
join
endtask
task do_reset();
forever begin
@(negedge intf.rstn);
intf.ch_valid <= 0;
intf.ch_data <= 0;
intf.ch_data_p <= 0;
end
endtask
task do_drive();
chnl_trans req, rsp;
@(posedge intf.rstn);
forever begin
seq_item_port.get_next_item(req);
this.chnl_write(req);
void'($cast(rsp, req.clone()));//////
rsp.rsp = 1;
rsp.set_sequence_id(req.get_sequence_id());//////
seq_item_port.item_done(rsp);//////
end
endtask
task chnl_write(input chnl_trans t);
foreach(t.data[i]) begin
@(posedge intf.clk);
intf.drv_ck.ch_valid <= 1;
intf.drv_ck.ch_data <= t.data[i];
intf.drv_ck.ch_data_p <= get_parity(t.data[i]);
@(negedge intf.clk);
wait(intf.ch_wait === 'b0);
`uvm_info(get_type_name(), $sformatf("sent data 'h%8x", t.data[i]), UVM_HIGH)
repeat(t.data_nidles) chnl_idle();
end
repeat(t.pkt_nidles) chnl_idle();
endtask
task chnl_idle();
@(posedge intf.clk);
intf.drv_ck.ch_valid <= 0;
intf.drv_ck.ch_data <= 0;
intf.drv_ck.ch_data_p <= 0;
endtask
function get_parity(bit[31:0] data);
return ^data;
endfunction
endclass: chnl_driver
class chnl_data_sequence extends uvm_sequence #(chnl_trans);
rand int pkt_id = 0;
rand int ch_id = -1;
rand int data_nidles = -1;
rand int pkt_nidles = -1;
rand int data_size = -1;
rand int ntrans = 10;
rand int data[];
constraint cstr{
soft pkt_id == 0;
soft ch_id == -1;
soft data_nidles == -1;
soft pkt_nidles == -1;
soft data_size == -1;
soft ntrans == 10;
soft data.size() == data_size;
foreach(data[i]) soft data[i] == -1;
};
`uvm_object_utils_begin(chnl_data_sequence)
`uvm_field_int(pkt_id, UVM_ALL_ON)
`uvm_field_int(ch_id, UVM_ALL_ON)
`uvm_field_int(data_nidles, UVM_ALL_ON)
`uvm_field_int(pkt_nidles, UVM_ALL_ON)
`uvm_field_int(data_size, UVM_ALL_ON)
`uvm_field_int(ntrans, UVM_ALL_ON)
`uvm_object_utils_end
`uvm_declare_p_sequencer(chnl_sequencer)
function new (string name = "chnl_data_sequence");
super.new(name);
endfunction
task body();
repeat(ntrans) send_trans();
endtask
task send_trans();
chnl_trans req, rsp;
`uvm_do_with(req, {local::ch_id >= 0 -> ch_id == local::ch_id;
local::pkt_id >= 0 -> pkt_id == local::pkt_id;
local::data_nidles >= 0 -> data_nidles == local::data_nidles;
local::pkt_nidles >= 0 -> pkt_nidles == local::pkt_nidles;
local::data_size >0 -> data.size() == local::data_size;
foreach(local::data[i]) local::data[i] >= 0 -> data[i] == local::data[i];
})
this.pkt_id++;
`uvm_info(get_type_name(), req.sprint(), UVM_HIGH)
get_response(rsp);//////
`uvm_info(get_type_name(), rsp.sprint(), UVM_HIGH)
assert(rsp.rsp)
else $error("[RSPERR] %0t error response received!", $time);
endtask
function void post_randomize();
string s;
s = {s, "AFTER RANDOMIZATION \n"};
s = {s, "=======================================\n"};
s = {s, "chnl_data_sequence object content is as below: \n"};
s = {s, super.sprint()};
s = {s, "=======================================\n"};
`uvm_info(get_type_name(), s, UVM_HIGH)
endfunction
endclass: chnl_data_sequence