[UVM源代码研究] 聊聊uvm_tlm_analysis_fifo这个特殊的uvm_component(uvm-1.2版)

引言

uvm_tlm_analysis_fifo是我们在进行TLM通信时常用的中介,不仅省去了我们在target端要实现write通信方法的烦恼,同时还解决了同一个target实现多个同名通信方法时必须繁琐的使用`uvm_analysis_imp_decl宏及其配套的代码的问题,那么在具体使用的时候,同学们有时难免会产生以下疑惑:

  1. uvm_tlm_analysis_fifo从uvm_component继承而来,但为什么我们从来没有看到其中关于phase的定义和使用?
  2. 既然uvm_tlm_analysis_fifo从uvm_component继承而来,那为什么我们在创建的时候使用的new(“fifo_name”, this)而不是::type_id::create(“fifo_name”, this)
  3. uvm_tlm_analysis_fifo作为一个uvm_component的派生类,创建时new(“fifo_name”, this)的第二个参数可以缺省不写吗?

以上三个问题通常都会颠覆我们在UVM基础学习阶段的一些认知,通过对源代码的剖析,我们能够对factory机制以及uvm_component的使用场景有更为深刻的认识。

factory机制

在UVM基础知识部分,教科书上都会说明,要想实现某个uvm class(例如uvm_user_type)的factory机制,必须具备以下两个基本条件:

  1. 将类注册到工厂 uvm_object_utils()uvm_component_utils();
  2. 创建对象 uvm_user_type::type_id::create(“”[, container_component]);//对于uvm_component第二个参数必须指定,且通常用this获取当前component所在的component指针

我们这里以uvm_component为例讲解其中的实现机制(其实我们在之前的文章中已经具体讲解过factory机制的实现原理,看过的同学可以快速掠过,详细内容参见 [UVM源代码研究] 浅谈UVM factory机制的实现原理

`uvm_component_utils宏定义部分相关的代码如下:

图1 /src/macros/uvm_object_defines.sv中的相关宏定义

在这里插入图片描述

图2 /src/macros/uvm_object_defines.sv中的相关宏定义

在这里插入图片描述

由此我们不难发现`uvm_component_utils宏包含了在对应uvm_component中使用typedef定义了一个跟uvm_component类型相关的参数化的uvm_component_registry类type_id,如果一来我们才能使用类作用符::引用其中定义的类type_id,即

uvm_user_type::type_id

那对于type_id这个类,其本质是个参数化的uvm_component_registry类型,其中包含了create函数的定义

图3 /src/base/uvm_registry.sv中的类uvm_component_registery中create函数实现

在这里插入图片描述

这样我们在实现factory机制创建uvm_component派生类时本质上调用的就是上图中的create函数,而这里factory机制实现的核心便是115行代码使用在uvm工厂中决定最终创建的类的类型是什么类型(这里就涉及到查找override表看该uvm_component的派生类是否被override过),即最终调用的是哪个类中的构造函数new.

于是我们不难得出如下结论:

  1. 只有使用`uvm_component_utils宏将该类进行注册,才可以使用类作用符调用其中的type_id::create()函数,并且create函数的第二个参数不可缺省
  2. 只有调用type_id::create()函数才可以从uvm的工厂中寻找到我们最终要创建的类型,进而调用该类型中的构造函数创建该类的实例

以上两点缺一不可,而如果直接调用new构造函数创建实例的话就跳过了在uvm工厂中寻找override关系的这个过程,直接创建该类型的实例,自然也就无法实现factory机制了。

uvm_tlm_analysis_fifo的创建

我们再看看uvm_tlm_analysis_fifo类及其父类的定义

图4 /src/tlm1/uvm_tlm_fifos.sv中代码片段

在这里插入图片描述

图5 /src/tlm1/uvm_tlm_fifos.sv中代码片段

在这里插入图片描述

图6 /src/tlm1/uvm_tlm_fifo_base.sv中代码片段
在这里插入图片描述

我们翻遍uvm_tlm_analysis_fifo及其父类都没找到包含uvm_component_utils宏注册的代码,那么自然我们就不能使用uvm_tlm_analysis_fifo::type_id::create()来创建uvm_tlm_analysis_fifo实例了,这就回答了引言中的第二个问题,为什么uvm_tlm_analysis_fifo在创建的时候不能使用type_id::create()的方法进行创建,进而就无法在uvm_tlm_analysis_fifo类型中实现factory机制了,这里我们不难发现UVM源代码中定义的uvm_component派生类型都没有使用宏uvm_component_utils进行注册,例如uvm_env、uvm_test、uvm_driver等等,只不过我们通常都不会直接使用这些类型创建实例,我们通常都会从这些类派生出新的类型并将其注册,这样我们又可以从这些派生类中派生出新的类,并通过在每个派生类中使用uvm_component_utils进行宏注册最终在这些派生类中实现factory机制。同理uvm_tlm_analysis_fifo中自然也不会实现factory机制,如果我们需要从uvm_tlm_analysis_fifo派生出新的类我们则需要进行宏注册方便后续代码通过factory机制实现override,BUT,uvm_tlm_analysis_fifo这个类对我们来说已经够用了,我们根本不需要对其进行派生乃至override,我们使用他仅仅是作为TLM机制的一个中间数据传输媒介,所以我们在创建uvm_tlm_analysis_fifo类型实例时调用的是其中的构造函数new。

图4中uvm_tlm_analysis_fifo的构造函数new()的第二个参数包含了缺省值null,于是我们在创建的时候就可以省略第二个参数,这就回答了引言中的第三个问题,那么省略第三个参数对我们的UVM环境运行会有什么影响呢?

我们通过前面文章中对UVM源代码的分析知道UVM在生长出UVM树形结构主要借助的就是parent-children的对应关系,而如果把parent指定为null的话,那么这个uvm_component的parent就会被认为是uvm_top,即uvm_test_top的parent,于是这个uvm_component就会导致uvm环境出现了个双top结构,对于某些情况的uvm_component这么写会导致uvm环境中hierarchy的错乱,无法进行正常的configure_db::set和get。虽然UVM源代码中从uvm_component派生类继承的类的构造函数的parent参数都会给定一个缺省值null,但一般我们自定义的类的构造函数的参数parent都不会给缺省值,就是防止创建的时候不指定parent,如图8所示。

图7 /src/comps/vum_env.svh中的构造函数new

在这里插入图片描述

图8 自定义的env的代码片段

在这里插入图片描述

而对于uvm_tlm_analysis_fifo则比较特殊,因为我们通常不会在uvm_tlm_analysis_fifo中创建出任何的uvm_component,所以uvm_tlm_analysis_fifo所在层次的hierarchy一般也不会影响到UVM环境的运行,所以理论上我们在创建uvm_tlm_analysis_fifo实例时是可以缺省参数parent的,这样UVM环境的拓扑结构就会如图9所示,uvm_tlm_analysis_fifo跟uvm_test_top在同一个hierarchy中并列。虽然这么使用并不会影响UVM环境的运行,但实际使用过程中我们一般还是会指定uvm_tlm_analysis_fifo的parent为this,保证UVM树形结构的统一性。

图9 UVM环境的拓扑结构

在这里插入图片描述

最后回到引言中问的第一个问题,为什么uvm_tlm_analysis_fifo要从uvm_component继承而来,而不是直接从uvm_object,既然我们在uvm_tlm_analysis_fifo都没有使用过phase机制?

那这就要回到我们对uvm_component使用场景的两大根基说起了,uvm_component除了使用其phase机制外,另一个重要的应用就是TLM机制,也就是说TLM机制必须使用在两个uvm_component之间进行,这个就是TLM机制的实现有关了,详细解释参见 TLM里的各种port、export、imp使用研究 中关于TLM机制中initiator和target的通信原理介绍 。这样就回答了我们引言中的第一个问题。

关于uvm_tlm_analysis_fifo的使用详解可以参见之前的文章 数字验证大头兵:[UVM源代码研究] 我们每天都在用的uvm_tlm_analysis_fifo内部是如何工作的

总结

uvm_tlm_analysis_fifo的使用主要有以下三个特点:

  1. uvm_tlm_analysis_fifo从uvm_component继承而来,主要是为了使用其TLM机制,作为两个uvm_component派生类之间通信的桥梁。而phase机制作为uvm_component另一大应用在uvm_tlm_analysis_fifo中并未实现任何代码内容。
  2. uvm_tlm_analysis_fifo代码实现并未使用`uvm_component_utils进行注册,因而我们创建实例时不能使用::type_id::create(“fifo_name”, this),只能使用new(“fifo_name”, this)
  3. uvm_tlm_analysis_fifo的构造函数中参数parent有缺省值null,创建时new(“fifo_name”, this)的第二个参数可以缺省不写,并且由于uvm_tlm_analysis_fifo的特殊性,其中并不会实例化任何component,并且也不会使用在uvm_config_db:::set/get的hierarchy中,只会作为树叶出现在UVM树形结构中,所以我们即使不指定其parent也不会影响到整个UVM环境的运行,但是不推荐这么使用。
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐