[UVM源代码研究] 聊聊uvm_sequence中常用的宏以及方法

[UVM源代码研究] 聊聊uvm_sequence中常用的宏以及方法

1. 引言

我们在sequence中往driver发包时,最常用的方法就是使用uvm_do()系列宏,偶尔会使用方法start_item()和finish_item()的组合,极个别场景下会 使用uvm_create()uvm_send()宏,极极个别场景下还会看到create_item()方法的使用,这些宏和方法有什么区别,各自使用场景有什么局限和注意事项,他们分别定义在了UVM源代码中的哪个类中,本文尝试结合UVM源代码一次讲清楚。

2. 源代码分析

首先我们看下UVM源代码中uvm_sequence相关类的继承关系,如图1所示。

图1 UVM中uvm_sequnece类的继承关系

在这里插入图片描述

uvm_sequence从uvm_object继承而来,所以它不具备uvm_component的phase机制,它的生命周期从创建到执行完成后就结束了,有可能在一个phase内完成,也可能横跨多个phase.
下面我们再看看其父类链条中的uvm_transaction、uvm_sequence_item、uvm_sequence_base以及uvm_sequence本身分别提供了哪些属性和方法,方便我们从整体上把握引言中提到的那几个宏和方法的使用。

2.1 uvm_transaction

UVM源代码 /src/base/uvm_transaction.svh 中关于uvm_transaction的描述如下:

//------------------------------------------------------------------------------
//
// CLASS: uvm_transaction
//
// The uvm_transaction class is the root base class for UVM transactions.
// Inheriting all the methods of uvm_object, uvm_transaction adds a timing and
// recording interface.
//
// This class provides timestamp properties, notification events, and transaction
// recording support. 
//
// Use of this class as a base for user-defined transactions
// is deprecated. Its subtype, <uvm_sequence_item>, shall be used as the
// base class for all user-defined transaction types. 
// 
// The intended use of this API is via a <uvm_driver> to call <uvm_component::accept_tr>,
// <uvm_component::begin_tr>, and <uvm_component::end_tr> during the course of
// sequence item execution. These methods in the component base class will
// call into the corresponding methods in this class to set the corresponding
// timestamps (accept_time, begin_time, and end_tr), trigger the
// corresponding event (<begin_event> and <end_event>, and, if enabled,
// record the transaction contents to a vendor-specific transaction database.
//
// Note that start_item/finish_item (or `uvm_do* macro) executed from a
// <uvm_sequence #(REQ,RSP)> will automatically trigger
// the begin_event and end_events via calls to begin_tr and end_tr. While
// convenient, it is generally the responsibility of drivers to mark a
// transaction's progress during execution.  To allow the driver to control
// sequence item timestamps, events, and recording, you must add
// +define+UVM_DISABLE_AUTO_ITEM_RECORDING when compiling the UVM package. 
// Alternatively, users may use the transaction's event pool, <events>,
// to define custom events for the driver to trigger and the sequences to wait on. Any
// in-between events such as marking the begining of the address and data
// phases of transaction execution could be implemented via the
// <events> pool.

概括的讲,就是说uvm_transaction这个类中主要提供了一些记录transaction相关时间戳的功能,在uvm_sequence中调用相关宏或者方法时会自动调用相关时间戳方法,如图2所示。

图2 src/seq/uvm_sequence_base.svh中的相关代码截图

在这里插入图片描述

正如uvm_transaction类描述中所提到的,我们可以用过+define+UVM_DISABLE_AUTO_ITEM_RECORDING这个宏的方式不让uvm_sequence来主动记录transaction相关时间戳,而是在uvm_driver中手动的记录,但是通常我们也不会这么做。
这么看来uvm_transaction这个类中提供的功能我们基本上不会显式的调用的,所以基本上不太需要关心。
uvm_trsanction.svh这个文件是在 src/base 目录下的,其他几个文件都是定义在 src/seq/ 目录下的,由此可见UVM的sequence机制uvm_trsanction参与的并不多,主要靠下面介绍的3个类来实现的。

2.2 uvm_sequence_item

uvm_sequence_item的代码截图如图3所示。

图3 src/seq/uvm_sequence_item.svh中的相关代码截图

在这里插入图片描述

可以看到我们前面经常提到的m_sequencer就是声明在这里的,也就是说到uvm_sequence_item这一层就已经绑定了uvm_seuqencer的概念了,也就是说uvm_sequence_item类型的transaction最终都会给到sequencer再到driver中,关于transaction从产生到driver获取的详细流程参见之前的文章:数字验证大头兵:[UVM源代码研究] sequence、 sequencer与driver的通信(uvm-1.2版)
uvm_sequence_item类中定义的其他几个比较常用的方法如图4所示。

图4 src/seq/uvm_sequence_item.svh中的相关代码截图

在这里插入图片描述

这个set_item_context()会在调用sequence的start()/start_item()方法时被调用,即启动sequence的时候就会执行sequencer的赋值。

2.3 uvm_sequence_base

我们常用的一些sequence中的方法基本上都是定义在这个类中,
UVM源代码 /src/seq/uvm_sequence_base.svh 中关于uvm_sequence_base的描述如下

//------------------------------------------------------------------------------
//
// CLASS: uvm_sequence_base
//
// The uvm_sequence_base class provides the interfaces needed to create streams
// of sequence items and/or other sequences.
//
// A sequence is executed by calling its <start> method, either directly
// or invocation of any of the `uvm_do_* macros.
// 
// Executing sequences via <start>:
// 
// A sequence's <start> method has a ~parent_sequence~ argument that controls
// whether <pre_do>, <mid_do>, and <post_do> are called *in the parent*
// sequence. It also has a ~call_pre_post~ argument that controls whether its
// <pre_body> and <post_body> methods are called.
// In all cases, its <pre_start> and <post_start> methods are always called.
// 
// When <start> is called directly, you can provide the appropriate arguments
// according to your application.
//
// The sequence execution flow looks like this
// 
// User code
//
//| sub_seq.randomize(...); // optional
//| sub_seq.start(seqr, parent_seq, priority, call_pre_post)
//|
//
// The following methods are called, in order
//
//|
//|   sub_seq.pre_start()        (task)
//|   sub_seq.pre_body()         (task)  if call_pre_post==1
//|     parent_seq.pre_do(0)     (task)  if parent_sequence!=null
//|     parent_seq.mid_do(this)  (func)  if parent_sequence!=null
//|   sub_seq.body               (task)  YOUR STIMULUS CODE
//|     parent_seq.post_do(this) (func)  if parent_sequence!=null
//|   sub_seq.post_body()        (task)  if call_pre_post==1
//|   sub_seq.post_start()       (task)
// 
//
// Executing sub-sequences via `uvm_do macros:
//
// A sequence can also be indirectly started as a child in the <body> of a
// parent sequence. The child sequence's <start> method is called indirectly
// by invoking any of the `uvm_do macros.
// In thise cases, <start> is called with
// ~call_pre_post~ set to 0, preventing the started sequence's <pre_body> and
// <post_body> methods from being called. During execution of the
// child sequence, the parent's <pre_do>, <mid_do>, and <post_do> methods
// are called.
//
// The sub-sequence execution flow looks like
// 
// User code
//
//|
//| `uvm_do_with_prior(seq_seq, { constraints }, priority)
//|
//
// The following methods are called, in order
//
//|
//|   sub_seq.pre_start()         (task)
//|   parent_seq.pre_do(0)        (task)
//|   parent_req.mid_do(sub_seq)  (func)
//|     sub_seq.body()            (task)
//|   parent_seq.post_do(sub_seq) (func)
//|   sub_seq.post_start()        (task)
//|
//
// Remember, it is the *parent* sequence's pre|mid|post_do that are called, not
// the sequence being executed. 
//
// 
// Executing sequence items via <start_item>/<finish_item> or `uvm_do macros:
// 
// Items are started in the <body> of a parent sequence via calls to
// <start_item>/<finish_item> or invocations of any of the `uvm_do
// macros. The <pre_do>, <mid_do>, and <post_do> methods of the parent
// sequence will be called as the item is executed.
//
// The sequence-item execution flow looks like
// 
// User code
//
//| parent_seq.start_item(item, priority);
//| item.randomize(...) [with {constraints}];
//| parent_seq.finish_item(item);
//|
//| or
//|
//| `uvm_do_with_prior(item, constraints, priority)
//|
//
// The following methods are called, in order
//
//|
//|   sequencer.wait_for_grant(prior) (task) \ start_item  \
//|   parent_seq.pre_do(1)                 (task) /                \
//|                                                        `uvm_do* macros
//|   parent_seq.mid_do(item)             (func) \                 /
//|   sequencer.send_request(item)    (func)  \ finish_item /
//|   sequencer.wait_for_item_done()  (task)  /
//|   parent_seq.post_do(item)            (func) /
// 
// Attempting to execute a sequence via <start_item>/<finish_item>
// will produce a run-time error.
//------------------------------------------------------------------------------

其实看完上面的描述我们基本上就对uvm_sequence中发包的相关几个宏和方法有了比较清晰的了解,也就是说我们之前提到的uvm_sequence中的发包方法基本上都是定义在uvm_sequence_base这个类中的。至于uvm_do系列宏,其实就是对这些方法执行次序的封装组合而已,我们放在最后面将,下面我们依次看看uvm_sequence_base中定义的这些方法。
图5是start()方法的部分代码截图,

图5 src/seq/uvm_sequence_base.svh中的start()方法

在这里插入图片描述

这个是启动sequence最基本的方法,通常我们只会显式的把sequence对应的sequencer作为第一个参数进行传递调用,后面三个参数都使用缺省值,除非我们需要用到parent_sequence,甚至其中的pre_do/mid_do/post_do方法,或者说在同一个sequencer上同时启动多个存在优先级关系的sequence,再或者我们需要手动的关闭对应sequence中的pre_body()和post_body()的执行。而pre_start()和post_start()作为UVM内置的callback函数则一定会在start()执行的前后依次被顺序执行,可以通过override的方式来增加我们需要预处理(pre_start)、或者善后处理(post_start)的代码。
图6是lock()和grab()两个任务用于sequence需要进行sequencer的独占访问申请,如果一个sequence中需要连续发送多个transaction中间不想被打断(所谓打断,指的是sequencer穿插着被别的sequence占用),就可以用到lock/unlock、grab/ungrab方法。

图6 src/seq/uvm_sequence_base.svh中的lcok()/grab()方法

在这里插入图片描述

图7的create_item()用于在sequence中创建指定类型的uvm_seuqence_item,创建uvm_seuqence_item的同时将这个uvm_seuqence_item与对应的sequencer绑定(调用了上文在uvm_sequence_item中定义的set_item_context方法)起来了。这里需要注意的时create_item()比我们直接通过item_name::type_id::create()创建uvm_seuqence_item多了一步绑定sequencer的操作,只有绑定了sequencer的uvm_seuqence_item才能发送出去,这一点一定要注意,后面我们还会提及这个概念。

图7 src/seq/uvm_sequence_base.svh中的create()方法

在这里插入图片描述

图8是start_item()方法的代码截图,start_item()不承担创建transaction的责任,并且要求transaction一定要创建好了才能调用start_item(),否则会报uvm_fatal错误(742-747行)。 与create_item()调用相比有一个比较重要的区别就是start_item()通常不需要显式的指定sequencer这个参数,而是通过get_sequencer()的方式自动获取item所在sequence对应的sequencer(这个默认值为m_sequencer,会在sequence启动的时候传递进来实际的sequencer句柄),而create_item()的时候必须显式的执行sequencer的值,当然也可以直接用m_sequencer作为参数。772行的wait_for_grant()就是等待sequencer授权再往后推进。

图8 src/seq/uvm_sequence_base.svh中的start_item()方法

在这里插入图片描述

图9是finish_item()方法的代码截图,执行的就是将item发送到对应的sequencer上,再等待driver执行完毕返回给sequencer(wait_for_item_done)。

图9 src/seq/uvm_sequence_base.svh中的finish_item()方法

在这里插入图片描述

这里我们还看到uvm_sequence_base中定义的很多方法都有个特点就是调用其对应的sequencer中的同名方法,如下面这些代码片段中的方法都是如此。

 task lock(uvm_sequencer_base sequencer = null);
    if (sequencer == null)
      sequencer = m_sequencer;

    if (sequencer == null)
      uvm_report_fatal("LOCKSEQR", "Null m_sequencer reference", UVM_NONE);

    sequencer.lock(this);
  endtask

  task grab(uvm_sequencer_base sequencer = null);
    if (sequencer == null) begin
      if (m_sequencer == null) begin
        uvm_report_fatal("GRAB", "Null m_sequencer reference", UVM_NONE);
      end
      m_sequencer.grab(this);
    end
    else begin
      sequencer.grab(this);
    end
  endtask

  function void  unlock(uvm_sequencer_base sequencer = null);
    if (sequencer == null) begin
      if (m_sequencer == null) begin
        uvm_report_fatal("UNLOCK", "Null m_sequencer reference", UVM_NONE);
      end
      m_sequencer.unlock(this);
    end else begin
      sequencer.unlock(this);
    end
  endfunction

  function void  ungrab(uvm_sequencer_base sequencer = null);
    unlock(sequencer);
  endfunction

  function bit is_blocked();
    return m_sequencer.is_blocked(this);
  endfunction

  function bit has_lock();
    return m_sequencer.has_lock(this);
  endfunction

  virtual task wait_for_grant(int item_priority = -1, bit lock_request = 0);
    if (m_sequencer == null) begin
      uvm_report_fatal("WAITGRANT", "Null m_sequencer reference", UVM_NONE);
    end
    m_sequencer.wait_for_grant(this, item_priority, lock_request);
  endtask

  virtual function void send_request(uvm_sequence_item request, bit rerandomize = 0);
    if (m_sequencer == null) begin
        uvm_report_fatal("SENDREQ", "Null m_sequencer reference", UVM_NONE);
      end
    m_sequencer.send_request(this, request, rerandomize);
  endfunction

  virtual task wait_for_item_done(int transaction_id = -1);
    if (m_sequencer == null) begin
        uvm_report_fatal("WAITITEMDONE", "Null m_sequencer reference", UVM_NONE);
      end
    m_sequencer.wait_for_item_done(this, transaction_id);
  endtask

所以想要弄清楚uvm_sequence_base中定义的这些方法,需要结合uvm_sequencer中的相关方法定义来看。
uvm_sequence_base中的方法就介绍到这里。

2.4 uvm_sequence

uvm_sequence类的部分代码如图10所示。

图10 src/seq/uvm_sequence.svh的部分代码

在这里插入图片描述

uvm_sequence在其父类uvm_sequence_base的基础上主要补充了以下两点内容:
增加了参数REQ和RSP用于定义在sequencer、driver之间传递的transaction类型
定义了全局变量req和rsp
定义了方法get_response
关于第三点定义的这个get_response()非常的实用,方法如图11所示,该方法可以用于在sequence中获取driver通过put_response返回的transaction,实现基本的读功能,完成sequence <——> sequencer<——>driver环路上的req/rsp数据交换。

图11 src/seq/uvm_sequence.svh中的get_response()方法

在这里插入图片描述

这里还需要注意的是req和rsp只是声明了变量并没有创建,如果要使用还需要自行创建。
这样我们就把uvm_sequence类继承关系上的4个类uvm_transaction、uvm_sequence_item、uvm_sequence_base、uvm_sequence都粗略的review了一遍了,其中涉及到发包的几个方法也都有讲述功能,下面我们在看看跟sequence收发包相关的几个宏。

2.5 收发包相关的几个宏

我们先看下uvm_do系列宏,主要包含以下几个宏:

  1. `uvm_do_on_pri_with
  2. `uvm_do_on_with
  3. `uvm_do_on_pri
  4. `uvm_do_pri_with
  5. `uvm_do_on
  6. `uvm_do_pri
  7. `uvm_do_with
  8. `uvm_do

可以理解为在uvm_do的基础上对_on(指定sequencer)、_with(对包随机做约束)、_pri(指定优先级)做的组合,宏增加不同的功能就需要增加对应的参数,不指定相关通能则使用缺省参数,可以认为uvm_do宏系列中的2-8都是uvm_do_on_pri_with的特例,我们只需要把uvm_do_on_pri_with搞清楚就行了。

图12 /src/macros/uvm_sequence_defines.svh中`uvm_do_on_pri_with的定义

在这里插入图片描述

关于uvm_do_on_pri_with宏的特点总结如下:

  1. 这个宏有四个参数,第一个参数SEQ_OR_ITEM表示我们可以用这个宏来处理sequence_item或者是sequence类型,第二个参数SEQR可以指定使用哪个sequencer来执行当前sequence_item/sequence,第三个参数PRIORITY可以设置优先级,最后一个参数CONSTRAINTS可以给sequence_item或者sequence中的一些参数在随机化时设置约束条件。
  2. 第一个参数可以是sequence也可以是sequence_item,区别在于如果是sequence则不会执行start_item()和finish_item()而是执行208行的start启动sequence操作。
  3. 202行会执行创建操作,并且会执行sequencer,所以我们调用uvm_do系列宏发包或启动sequence时传递的sequence_or_item没有必要创建甚至做约束,因为他还会再次执行创建操作,之前的创建和约束都会失效。关于uvm_create_on宏如图13所示。其中调用的create_item()方法是定义在uvm_sequence_base中的,所以只能在sequence中调用uvm_create_on宏,而不能在继承自uvm_component的相关组件中(例如testcase)调用。另外uvm_create宏可以不指定sequencer,使用的就是默认的m_sequencer,适合用在与sequencer存在对应关系的sequence中,而在virtual_sequence中如果要用create_on宏的话则必须指定sequencer。
  4. 204行的代码似乎有点问题,do_not_randomize这个字段是定义在uvm_sequence_base中的,用于控制不让sequence在启动之前执行随机(但是感觉即使把do_not_randomize设为1也不会影响后面randomize语句的执行)。

图13 /src/macros/uvm_sequence_defines.svh中`uvm_create_on的定义

在这里插入图片描述

这里我们就可以得到一个基本的收发包宏和方法的公式

 `uvm_do_on_pri_with() = `uvm_create_on() + start_item() + randomize() + finish_item()

我们可以根据对包灵活的选择这些宏和方法的组合,常见的场景如下:

  1. req没有创建,只是声明了,就可以用下面的组合
`uvm_create(req)
start_item(req);
req.randomize();
finish_item(req);
//或者直接用`uvm_do宏
`uvm_do(req)
  1. req已经创建完成,只想随机之后发送给driver,可以用下面的组合
start_item(req);
req.randomize();
finish_item(req);
  1. req已经创建并且对其中的相关字段进行了约束赋值,可以用下面的组合
start_item(req);
finish_item(req)

除了上面这些常用的宏和方法外,UVM源代码还定义了一些宏,首先看下`uvm_send系列宏:

  1. `uvm_rand_send_pri_with
  2. `uvm_rand_send_with
  3. `uvm_rand_send_pri
  4. `uvm_send_pri
  5. `uvm_rand_send
  6. `uvm_send

乍一看有点类似于uvm_do系列宏后缀的排列组合,但是后缀又有些区别,uvm_send系列宏一般跟uvm_create是配合使用的,uvm_create执行的时候会指定item所在的sequencer,所以在uvm_send执行的时候就不需要指定sequencer了,所以不需要_on的后缀。_rand表示是否需要随机,_pri指定优先级,_with指定随机约束。uvm_rand_send_pri_with宏定义如图14所示,与uvm_do_on_pri_with区别就在于少了一步uvm_create_on的执行而已。

图14 /src/macros/uvm_sequence_defines.svh中`uvm_rand_send_pri_with的定义

在这里插入图片描述

所以我们不难得出以下等效公式。

`uvm_do_on_pri_with() = `uvm_create_on() + `uvm_rand_send_pri_with()
`uvm_send() = start_item() + finish_item() //也可以用`uvm_send_pri()指定优先级

本节就把uvm_sequence中发包可能设计的所有宏方法进行了介绍,实际使用中可以根据场景的需要选择何时的宏和方法的组合。

3. 总结

本文通过对UVM源代码中发包相关的宏和方法的介绍,对uvm_sequence类继承路径上的相关类、方法进行了走马观花式的讲解,相信以后大家在uvm_sequence中发包的时候不管是调用`uvm_do宏还是各种方法,对UVM中打包执行的代码应该能有个比较深刻的认识了。

  • 22
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值