UVM response_handler和get_response机制
很多UVM用户平时更多的使用get_response()方式去获得uvm_driver的response,但get_response有些缺点:由于 get_response() 是一种阻塞方法,它会阻塞直到收到来自 UVM 驱动程序 (put_response()) 的响应。因此,如果我们使用 get_response() 方法实现并按此顺序将 start_item()、finish_item() 和 finally get_response() 放置在序列的 body() 方法中,这将导致非流水线序列实现,因为下一个序列项可以只有在 get_response() 方法收到前一个请求序列项的响应项后,才提供给 UVM 驱动程序。
为了实现背靠背的传输,我们可以使用response_handler()机制,它可以将sequence中发送request和处理response两个操作分开处理,尽量减少耦合。例子如下流程图。
1. 如何使用response_handler
如果想要实现response_handler机制的话,第一步需要在要使用这个机制的sequence里使能它,即调用use_response_handler(1)函数把m_use_response_handler变量设置为1就行,m_use_response_handler是uvm_sequence_base类里的一个成员变量,默认值为0,也就是不打开。第二步需要override uvm_sequence_base类里的response_handler函数,实现自己对response任意处理的需求。它的原型为:
// Function: response_handler
// When the use_reponse_handler bit is set to 1, this virtual method is called
// by the sequencer for each response that arrives for this sequence.
virtual function void response_handler(uvm_sequence_item response);
return;
endfunction
response_handler对uvm_driver没有任何影响,也就是不可见,uvm_driver仍然用之前的put_response机制就行。
2. response_handler背后机制
sequence里的response_handler(xxx)函数在uvm_driver调用seq_item_export.put_response(rsp)的时会自动被调用执行,故此我们从seq_item_export.put_response(rsp)讲起。
uvm_driver调用put_response(rsp)时,会调用uvm_sequencer_param_base里的put_response(rsp)函数,这个函数的定义为:
313行到326行就是对传参进来的rsp做一些检查。328行会根据rsp里携带的sequence_id去调用m_find_sequence函数在reg_sequences关联数组里找对应sequence的句柄,如果找到了就返回sequence句柄,没有找到的话,就直接返回null,并报告没有找到原始sequence的信息。通常我们返回的是非null的,因此会进入330行到337行。如何避免返回null,在我的另一篇博客中说了,有兴趣读者可以瞄一眼(UVM中使用put_response的一个注意点)。在332行,会判断m_use_response_handler变量的值,如果为1就执行333行sequence里的response_handler函数,如果为0就执行337行response_handler的put_response。可想而知,这是1个关键分叉点。我们分别介绍这两个方法。
2.1 sequence_ptr.response_handler(t)
sequence里的response_handler()函数我们在第一小节里也提到了,原型是个空函数,需要用到这个机制的用户自己去override它来实现需要的功能。
2.2 sequence_ptr.put_response(t)
在uvm_sequence_base类的put_response函数又会继续调用put_base_response函数,它们俩的代码为:
virtual function void put_response (uvm_sequence_item response_item);
put_base_response(response_item); // no error-checking
endfunction
virtual function void put_base_response(input uvm_sequence_item response);
if ((response_queue_depth == -1) ||
(response_queue.size() < response_queue_depth)) begin
response_queue.push_back(response);
return;
end
if (response_queue_error_report_disabled == 0) begin
uvm_report_error(get_full_name(), "Response queue overflow, response was dropped", UVM_NONE);
end
endfunction
可以看出,在put_base_response函数里会将uvm_driver送过的rsp放到response_queue队列了,response_queue队列定义为:protected uvm_sequence_item response_queue[$]。另外说下response_queue_depth这个int类型变量,它的默认值为8,也就是response_queue队列默认只能放8个rsp,如果超过的话,会被直接忽视掉(用户可以打开response_queue_error_report_disabled来报错)。但如果用户想要让response_queue队列可以放更多的rsp呢?需要调用uvm_sequence_base里的set_response_queue_depth(xx)来设置新值,xx就是传进去的int类型数值。也可以调用get_response_queue_depth()来返回当前设置的值。
response_queue队列里的值会给get_reponse(xxx)使用。
3. get_response背后机制
在sequence中调用get_response(xxx)的时候,对调用uvm_sequence_base类里的get_base_response(xxx)方法。
virtual task get_response(output RSP response, input int transaction_id = -1);
uvm_sequence_item rsp;
get_base_response( rsp, transaction_id);
$cast(response,rsp);
endtask
get_base_response(xxx)方法的代码如下:
从以上代码可以看出,get_reponse(xxx)就是从response_queue队列里拿数据,response_queue里的数据是put_response在没有使能response_handler机制情况下放进去的。因此用户要注意一旦采用response_handler机制后,在当前sequence里一定不能用get_response,反则它会get不到response,一直block在get_base_response方法的991行。如果transaction_id为-1,也就是用户没有指定要得到特定transaction_id的response时,get_base_response会默认返回response_queue里的第一个response,类似于FIFO。如果transaction_id不为-1,那么get_base_response会在response_queue里检索,直到找到1个匹配对应transaction_id的response为止。