Dskpimc? · 2023年04月23日 · 北京市

UVM中使用put_response的一个注意点

平时在使用UVM的get_response和put_response握手机制来获取uvm_driver反馈的一个例子如下。

image.png

想想如果有这么一个场景,加入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定义如下:

image.png

根据上述可以看出,在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仍然没有结束,否则对方就无法收到了,造成意想不到的情况。

作者:谷公子
文章来源:CSDN

推荐阅读

更多Arm AMBA 协议集技术干货请关注Arm AMBA 协议集技术专栏。
迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。
推荐阅读
关注数
11636
内容数
1230
主要交流IC以及SoC设计流程相关的技术和知识
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息