平时在使用UVM的get_response和put_response握手机制来获取uvm_driver反馈的一个例子如下。
想想如果有这么一个场景,加入sequence的body里使用fork...join_none启动左边的进程,那么在右边还没有执行到put_response(rsp)步骤的时候,左边已经退出了body()方法。那么问题来了,等uvm_driver执行到put_response(rsp)的时候会发生什么事情呢?
答案:sequencer打印信息,表示找不到原来的sequence了,但不会报错。这样的话,如果我们有一些需要在sequence的get_response或response_handler里处理的检查就会被忽略掉了。
我们分几个步骤说一下:
1. sequence如何注册到sequencer
sequence启动的本质是调用sequence里的start(xxx)方法,并将sequencer句柄作为参数传入给m_sequencer。在start(xxx)方法里会调用m_sequencer的m_register_sequence(xxx)函数来将sequence注册到sequencer的reg_sequences关联数组里。
uvm_sequence_base task方法关键源码为:
virtual task start (uvm_sequencer_base sequencer,
uvm_sequence_base parent_sequence = null,
int this_priority = -1,
bit call_pre_post = 1);
... // 此处省略一些代码
// Register the sequence with the sequencer if defined.
if (m_sequencer != null) begin
void'(m_sequencer.m_register_sequence(this));
end
... // 此处省略一些代码,执行body内容等
// Clean up any sequencer queues after exiting; if we
// were forcibly stoped, this step has already taken place
if (m_sequence_state != STOPPED) begin
if (m_sequencer != null)
m_sequencer.m_sequence_exiting(this);
end
... // 此处省略一些代码
endtask
uvm_sequencer_base的m_register_sequence函数源码如下。sequence的sequence_id时sequencer通过static g_sequence_id变量全局分配的,也就是每个sequence都有1个sequence_id,而且它们是互不相同的,不管是否在同1个sequencer上启动。顺便也说下transaction_id好了,sequence里发送的每个transaction都打上1个transaction_id的编号,用来标识这个sequence里独一无二的编号,但需要注意的是这个transaction_id不是全局独一无二的,不同sequence可以拥有一样的transaction_id。这点可以从给transaction打编号的变量m_next_transaction_id看出,它的定义为:int m_next_transaction_id = 1,是个class内部local变量。因此想要唯一找到1个transaction,需要用sequence_id和transaction_id两个来定位。这也顺便解释了为什么我们需要在put_response之前需要调用transaction里的set_id_info(xxx)函数的原因,就是将sequence_id和transaction_id传递给另一个transaction。
function int m_register_sequence(uvm_sequence_base sequence_ptr);
if (sequence_ptr.m_get_sqr_sequence_id(m_sequencer_id, 1) > 0)
return sequence_ptr.get_sequence_id();
sequence_ptr.m_set_sqr_sequence_id(m_sequencer_id, g_sequence_id++);
reg_sequences[sequence_ptr.get_sequence_id()] = sequence_ptr;
return sequence_ptr.get_sequence_id();
endfunction
reg_sequences关联数组的定义为:
protected uvm_sequence_base reg_sequences[int];
set_id_info函数的定义为:
function void set_id_info(uvm_sequence_item item);
if (item == null) begin
uvm_report_fatal(get_full_name(), "set_id_info called with null parameter", UVM_NONE);
end
this.set_transaction_id(item.get_transaction_id());
this.set_sequence_id(item.get_sequence_id());
endfunction
2. sequence如何从sequencer注册表里删除
我们从步骤1里sequence的start源码里看到,在body执行完之后,会判断m_sequence_state状态不等于STOPPED,只要sequence正常执行完,m_sequence_state就是FINISHED,STOPPED是给kill用的。因此会调用m_sequencer的m_sequence_exiting(xxx)函数,它的定义如下:
function void uvm_sequencer_base::m_sequence_exiting(uvm_sequence_base sequence_ptr);
remove_sequence_from_queues(sequence_ptr);
endfunction
m_sequence_exiting函数会继续调用remove_sequence_from_queues函数,定义如下:
function void uvm_sequencer_base::remove_sequence_from_queues(
uvm_sequence_base sequence_ptr);
... // 此处省略一些代码
// Unregister the sequence_id, so that any returning data is dropped
m_unregister_sequence(sequence_ptr.m_get_sqr_sequence_id(m_sequencer_id, 1));
endfunction
remove_sequence_from_queues函数的最后一行会调用m_unregister_sequence函数,定义如下。可以看出sequencer在这里面会将sequence从它的注册表(reg_sequences)里移除掉。
function void uvm_sequencer_base::m_unregister_sequence(int sequence_id);
if (!reg_sequences.exists(sequence_id))
return;
reg_sequences.delete(sequence_id);
endfunction
3. uvm_driver调用put_response(xxx)背后机制
在uvm_driver里调用seq_item_export.put_response(rsp)的时候,它会调用uvm_sequencer_param_base类里的put_response,uvm_sequencer_param_base里的put_response定义如下:
根据上述可以看出,在328行处根据rsp里携带的sequence_id去调用m_find_sequence函数在reg_sequences关联数组里找对应sequence的句柄,如果找到了就返回sequence句柄,没有找到的话,就直接返回null。m_unregister_sequence函数定义为:
function void uvm_sequencer_base::m_unregister_sequence(int sequence_id);
if (!reg_sequences.exists(sequence_id))
return;
reg_sequences.delete(sequence_id);
endfunction
然后继续看uvm_sequencer_param_base里put_response函数的330行,找到sequence的话,sequence_ptr为不是null,因此会根据是否使能use_response_handler来执行333行或337行。如果没有找到sequence,sequence_ptr就是null,会上报没有找到sequence的打印信号。
4. 总结
相信读者们从步骤1~3读完之后,就已经知道如何分析出答案了吧。所以之后在uvm_driver里调用seq_item_export.put_response(item)时要注意,要确保对应sequence的body仍然没有结束,否则对方就无法收到了,造成意想不到的情况。