[UVM源代码研究] 聊聊寄存器模型的后门访问

[UVM源代码研究] 聊聊寄存器模型的后门访问

引言

我们在之前的文章 [UVM源代码研究] 当我们使用寄存器模型里的寄存器调用write/read方法,数据包是如何在寄存器模型、adapter、sequencer中传递的 一文中 介绍了读写寄存器时UVM源代码的实现过程,对于后门访问只是一笔带过,不是所有的寄存器都支持后门访问,并且如果rtl的寄存器路径比较分散的话也不利于寄存器模型后门路径的设置,所以现实工作中使用后门访问的场景确实比较有限。但是后门访问有时可以大大简化我们的验证工作,尤其是在有些不得不使用寄存器模型后门访问的场景下又显得尤为重要(比如在check_phase这样一个function phase里试图获取DUT寄存器的值),本文就带着大家一起窥探下源代码中是如何实现寄存器模型的后门访问的。
寄存器模型相关的基本概念在之前的相关文章中已经详细讲解过了,这里就不多赘述了,具体可以参考以下几篇文章:

[UVM源代码研究] 当我们使用寄存器模型里的寄存器调用write/read方法,数据包是如何在寄存器模型、adapter、sequencer中传递的

[UVM源代码研究] 我们在使用UVM寄存器模型内建的sequence检查寄存器时UVM源代码都执行了些啥?

[UVM源代码研究] 聊聊寄存器模型中的期望值(desired value)、镜像值(mirrored value)以及DUT中的实际值(actual value)的相关概念及方法

[UVM源代码研究] 谈谈寄存器模型中predict

UVM源代码溯源

我们以后门写寄存器为例结合具体实例讲解下UVM源代码是如何实现寄存器模型的后门访问的。

源代码中涉及到的相关方法首先是write任务,如图1所示

图1 src/reg/uvm_reg.svh中的write()任务

在这里插入图片描述

wirte()中的核心方法是do_write(),其他内容可以认为是在等待寄存器的访问权限(Xtomic()任务)和更新期望值(set()函数)。do_write()任务如图2所示。

图2 src/reg/uvm_reg.svh中的do_write任务

在这里插入图片描述

do_write()我们分5大块来讲解其中UVM_BACKDOOR相关的代码,首先是第1步调用get_backdoor来获取uvm_reg_backdoor的句柄,uvm_reg_backdoor类的定义和描述如图2所示。

图3 src/reg/uvm_reg_backdoor.svh中uvm_reg_backdoor类的定义与描述

在这里插入图片描述

简单概括下这个类的功能就是作为用户使用backdoor的方式访问寄存器或者memory的积累,用户如果不想通过sv或者内置的DPI方式去后门访问寄存器模型的话,就可以从该类派生出自定义的backdoor访问类,override其中的相关方法来实现自定义形式的后门访问,默认情况下UVM源代码中是使用sv语法的DPI方式来实现寄存器的后门访问的,这个实现方法我们后面会具体讲,等于说我们要进行后面访问都是通过uvm_reg_backdoor或者其派生类来实现的,后面我们还会讲其中相关方法的具体实现。

继续回到图2中的第一步,我们是通过get_backdoor()来获取uvm_reg_backdoor的句柄的,get_backdoor()的定义如图4所示。

图4 src/reg/uvm_reg.svh中的get_backdoor函数

在这里插入图片描述

如果我们未曾对backdoor进行任何的设置(没有自定义的uvm_reg_backdoor模型),那么默认的m_backdoor就是null,inherited缺省值为1,图4中1368行就会调用该uvm_reg所在的寄存器模型uvm_reg_block中的get_backdoor()函数,如图5所示。

图5 src/reg/uvm_reg_block.svh中的get_backdoor函数

在这里插入图片描述

看看这里定义的get_parent()函数的描述

图6 src/reg/uvm_reg_block.svh中的get_parent函数

在这里插入图片描述

对于顶层的寄存器模型来说,get_parent()返回的就是null,也就是说寄存器模型中的寄存器会自下而上层层判断有没有自定义的uvm_reg_backdoor句柄,如果都没有,图2中的代码段1执行的get_backdoor()函数最终返回的就是null。于是乎代码段2中执行的就是backdoor_read()任务,而backdoor_read()中又调用了backdoor_read_func()函数,代码截图如图7所示。

图7 src/reg/uvm_reg.svh中的backdoor_read相关方法

在这里插入图片描述

get_full_hdl_path获取寄存器的HDL路径层次,函数声明和实现如图8、图9所示

图8 src/reg/uvm_reg.svh中的get_full_hdl_path函数声明

在这里插入图片描述

图9 src/reg/uvm_reg.svh中的get_full_hdl_path函数实现

在这里插入图片描述

这里我们还需要有uvm_hdl_path_concat这个类的背景知识,如图10所示。

图10 src/reg/uvm_reg_model.svh中uvm_hdl_path_concat类的声明与实现

在这里插入图片描述

uvm_hdl_path_concat本质上又是用来收集uvm_hdl_path_slice组成的动态数组,这个动态数组用来抽象描述寄存器在HDL中相关的一些列属性。

uvm_hdl_path_slice是一个结构体,其声明描述和定义如图11所示。

图11 src/reg/uvm_reg_model.svh中uvm_hdl_path_slice结构体的声明与实现

在这里插入图片描述

好了,经过上面这一番摸着石头过河一般的类、结构体、函数、任务的轮番突袭,我宣布我已经懵掉了,有必要跳出代码的汪洋,站在一个高的视角宏观的俯瞰一下UVM源代码在组织寄存器模型后门访问相关的类和方法的思路,而不必拘泥于代码的细枝末节。

首先寄存器模型将HDL路径以字符串的形式存储在队列中,这个队列又作为uvm_object_string_pool键值对中的值存储在hdl_paths_pool中,默认的键(key)是"RTL"。如图12所示。

图12 src/reg/uvm_reg_block.svh中存储HDL路径的键值对

在这里插入图片描述

我们可以通过调用add_hdl_path来添加寄存器模型的HDL路径,如图13所示,添加HDL路径可以指定不同的键值来对寄存器HDL路径进行分组,实现同一个寄存器模型存储多个不同的HDL路径,这是为了应对同样的寄存器模块在RTL中有多个instance分布在不同的hierarchy下,就可以通过键值来管理寄存器模型的多个HDL hierarchy,来实现一对多的映射,缺省情况下都是存储在"RTL"这个键下面。如果有多个HDL路径,可以一次read/write/peek/poke多个物理寄存器(如果write或poke的值相同)。但是请注意,即使在不同的HDL路径上您有多个不同的读取值,read和peek也只会返回路径上的第一个读取值。

图13 src/reg/uvm_reg_block.svh中add_hdl_path函数

在这里插入图片描述

HDL路径如何存储在队列以及对应的键值对中可以借用糖果盒项目中的图14形象的描述清楚,图示表明我们可以通过寄存器模型的函数configure或add_hdl_path来往键值对中添加对应的HDL路径组,configure本质上也是调用的add_hdl_path。add_hdl_path只是创建了某个键值对组合,键是某种寄存器HDL路径分组当场确定下来,而值只会往path队列中push一个函数参数设置的string类型的path值,后续还可以使用add_hdl_path继续往对应的path队列中添加HDL路径。可以使用clear_hdl_path来清楚指定的或者所有的HDL路径分组。

图14 寄存器模型中的HDL路径队列池功能函数描述

在这里插入图片描述

图11中的代码59行有个联合数组root_hdl_paths,该变量的功能有点类似于hdl_paths_pool,只不过由于联合数组的值只有一个string,他不像hdl_paths_pool对某一个HDL路径可以存储path队列,root_hdl_paths只能存一个path值,并且如果设定了root_hdl_paths,它会覆盖该HDL路径下通过add_hdl_path添加的所有path队列,即该寄存器模型对应的该HDL路径下只会有一个hdl_path值,准确的讲应该是寄存器模型才获取某个寄存器的的HDL路径时会优先看有没有设置root_path,没有才会去看hdl_paths_pool中对应的path队列的第一个值。root_hdl_paths可以满足我们绝大多数寄存器模型的工作场景,可以通过set_hdl_path_root设置root_hdl_paths值。

经过上面的分析,我们已经清楚了寄存器模型(uvm_reg_block)与RTL中的寄存器模块(HDL路径)的映射关系了,是通过add_hdl_path或者set_hdl_path_root来设置的,如果存在嵌套的寄存器模型(uvm_reg_block),也是通过add_hdl_path来获取当前寄存器模型的HDL绝对路径,不需要进行路径拼接。那么寄存器模型中的寄存器和RTL中的寄存器是如何建立映射关系的呢?这就涉及到图10、11提到的类uvm_hdl_path_concat和uvm_hdl_path_slice。

首先,uvm_reg中定义了一个与uvm_reg_block中hdl_paths_pool类似的键值对m_hdl_paths_pool,如图15所示,区别在于m_hdl_paths_pool的值是一个uvm_hdl_path_concat队列,而hdl_paths_pool的值是个string队列,两者的键都是string类型,表示的意义也都是相同的,用来表示寄存器模型对应的HDL路径分组信息,默认为"RTL"。

图15 src/reg/uvm_reg.svh中定义的键值对m_hdl_paths_pool

在这里插入图片描述

想要理解uvm_hdl_path_concat,我们首先要理解uvm_hdl_path_slice的概念,寄存器模型使用uvm_hdl_path_slice这个结构体来描述寄存器的物理信息,从图11中可以看到寄存器的物理信息包括寄存器的路径path(这个path表示的是一个相对路径,相对的是寄存器模型的path,最终寄存器的绝对路径是将寄存器模型的路径和寄存器的路径拼接起来的)、寄存器的偏移地址offset以及寄存器的大小size。 而从图10可以看到uvm_hdl_path_concat中定义了uvm_hdl_path_slice类型的动态数组,用于存放对应同一块物理存储器相关的寄存器物理信息。

uvm_reg类中也存在add_hdl_path()这么个函数,如图16所示,它不同于uvm_reg_block中往hdl_paths_pool中存放string类型的path值,它往m_hdl_paths_pool键值对中添加uvm_hdl_path_slice数组,这个数组最终会用于产生一个uvm_hdl_path_concat变量concat压到m_hdl_paths_pool对应的值中。图16中UVM源代码通过一个例子很好的演示了如何将一个包含了多个uvm_reg_field信息的uvm_reg添加到uvm_hdl_path_slice数组中,所以准确的讲uvm_hdl_path_slice是用来描述寄存器模型的最小单元uvm_reg_field信息的。

图16 src/reg/uvm_reg.svh中的add_hdl_path函数描述

在这里插入图片描述

图16 src/reg/uvm_reg.svh中的add_hdl_path函数实现

在这里插入图片描述

除了通过调用add_hdl_path批量将寄存器物理信息添加到寄存器模型中,uvm_reg还提供了一个函数add_hdl_path_slice这么个函数用于将单个寄存器的物理信息添加到寄存器模型中。如果add_hdl_path_slice的第一个参数为1,则会创建一个新的uvm_hdl_path_concat来存储指定的切片,否则会存到uvm_hdl_path_concat队列的最后一个元素中。

图17 src/reg/uvm_reg.svh中的add_hdl_path_slice函数

在这里插入图片描述

除了调用add_hdl_path_slice来手动添加寄存器的物理信息,我们还可以在寄存器configure的时候将其path信息添加进去,configure函数如图18所示,本质上也是调用的add_hdl_path_slice函数。

图18 src/reg/uvm_reg.svh中的configure函数

在这里插入图片描述

我们还是借用糖果盒项目中的图来说明了如何将每个寄存器的HDL路径存储在池和队列中,如图19所示。

图19 如何将每个寄存器的HDL路径存储在m_hdl_paths_pool池和uvm_hdl_path_concat队列中

在这里插入图片描述

如此一来,我们便知道如何配置某个寄存器的后门访问的HDL路径了,首先通过set_hdl_path_root或者add_hdl_path或者configure的时候设置寄存器模型的HDL路径,然后单个寄存器的相对路径可以在对寄存器进行configure的时候配置或者手动调用add_hdl_path批量配置或者调用add_hdl_path_slice配置单个寄存器的物理信息。

有了上面这些背景知识,我们再回到我们之前卡住的图9中的get_full_hdl_path函数,这个函数的目的就是获取寄存器模型中的对应寄存器所在的寄存器组(默认"RTL")包含的该寄存器的物理信息并存放在paths队列中带回到调用该函数的backdoor_read_func()函数中,如果不做多次映射 这里的path在图9所示的get_full_hdl_path函数的1528-1559行对应的代码中已经做了拼接,表示的是寄存器在HDL中的绝对路径。

图7中2752行到2791行就是在遍历这个paths队列中的所有uvm_hdl_path_concat类型的元素,2755行到2774行又是在遍历单个uvm_hdl_path_concat元素中uvm_hdl_path_slice动态数组中的所有uvm_hdl_path_slice元素(uvm_reg_field),并在2776行将这些slice拼接成一个完整的寄存器值。7778-2779行将后门读回的寄存器值保存在rw中返回给调用backdoor_read_func()函数的backdoor_read()方法,最终返回给do_read()方法。2781-2787行用来检查如果同一个寄存器对应多个不同的path,那么要保证这个寄存器的值一致,否则就报错。

通过这两个foreach代码段,实现了遍历寄存器模型中某个特定寄存器对应的所有后门路径的物理信息,在这些遍历代码中还用到了一个核心函数uvm_hdl_read(),这是一个通过DPI-C导入的外部函数,函数声明如图20所示。可以看到函数原型根据参数和返回值的不同对应着两个同名的不同的函数原型,这也算是函数重载(overload)的一种典型应用,编译器在处理的时候会将它们编译成两个不同的函数分别分配响应的空间,这个不同于我们在UVM中常提到的override(覆盖),这属于软件设计里的思想,但这里特别拿出来说明下还是希望大家不要混淆这两个概念。

图20 /src/dpi/uvm_hdl.svh中DPI实现的uvm_hdl_read()函数声明

在这里插入图片描述

这里通过宏UVM_HDL_NO_DPI提供了一个函数选择,默认情况下没有打开上面的宏,如果编译的时候加了参数+define+UVM_HDL_NO_DPI,那么函数主体变为图21所示的内容,实际上执行的时候就会报错,提示你要去掉上面这个define,否则就不支持backdoor访问。

图21 /src/dpi/uvm_hdl.svh中非DPI实现的uvm_hdl_read()函数定义

在这里插入图片描述

uvm_hdl_read()的函数主体是使用C语言实现的,如图22所示,这里我们就不做具体分析了,我们可以简单理解成UVM源代码通过systemverilog语法中的DPI-C间接使用C语言实现了对寄存器HDL中的某个路径进行访问,相当于把string类型的HDL path转化为真实的hierarchy访问HDL内部register。

图22 /src/dpi/uvm_hdl.c内部的uvm_hdl_read函数实现

在这里插入图片描述

uvm_hdl_read()访问成功返回1,访问失败返回0,这个返回信息会给到rw中的status变量一起返回到调用backdoor_read()方法的图2中的2268行,也就是执行后门写首先会执行后门读的操作,如果后门读失败了就直接返回跳过后门写的行为。图2中的代码段3是将后门读回的寄存器的值更新到该寄存器的所有uvm_reg_field的镜像值,代码段4就是将寄存器rw中的配置值通过DPI-C的方式使用后门写入到寄存器对应的HDL路径上,同时更新寄存器模型的镜像值,分析方法类似于backdoor_read(),这里就不做赘述了。

do_write()任务后门还有些callback以及打印相关的代码,这里也不展开讲解了,不影响咱们对后门操作总体思路的理解,如此一来咱们对于UVM源代码中的后门写寄存器的流程就有了比较清晰深入的理解,至于后门读那就更简单了,分析方法跟后门写差不多,这里就不展开讨论了。

总结

本文通过对寄存器模型后门写寄存器的流程分析,讲解了后门访问寄存器涉及到的多个类、结构体、方法,对寄存器模型内部如果存储寄存器的物理信息有了比较清晰的认识,想要实现一次正确的寄存器后门访问,首先需要通过set_hdl_path_root或者add_hdl_path或者configure的时候设置寄存器模型的HDL路径,然后需要实现后门访问的单个寄存器的相对路径可以在对寄存器进行configure的时候配置或者手动调用add_hdl_path批量配置或者调用add_hdl_path_slice配置单个寄存器的物理信息,这样就可以通过后门来访问对应绝对路径的寄存器了。

这里有一点需要注意的是,有些寄存器我们在spec上虽然定义了名称,但是在实际的HDL coding的时候被打散成各种uvm_reg_field了,并没有一个与spec相对应的寄存器名。这时候我们在configure的时候就不能简单的configure单个寄存器,因为实际的hierarchy中并没有这样的寄存器,我们需要将该寄存器包含的所有uvm_reg_field以uvm_hdl_path_slice的形式通过add_hdl_path的形式配置给该寄存器,这样我们在执行backdoor_read和和write的时候才能正确执行,否则就会报对应backdoor hierarchy不存在的warning信息,转而使用frontdoor实现,关于这一点可以参考文章最后代码示例中的jb_recipe_reg寄存器的后门访问配置。

最后以一段糖果盒代码来结束寄存器模型后门访问的讨论,供大家参考学习。

class jelly_bean_base_test extends uvm_test;
   `uvm_component_utils( jelly_bean_base_test )
 
   jelly_bean_env                   jb_env;
   jelly_bean_env_config            jb_env_cfg;
   jelly_bean_agent_config          jb_agent_cfg;
   jelly_bean_partnership_reg_block jb_partnership_reg_block;
 
   function new( string name, uvm_component parent );
      super.new( name, parent );
   endfunction: new
 
   function void build_phase( uvm_phase phase );
      jelly_bean_recipe_reg jb_recipe_reg;
 
      super.build_phase( phase );
      jb_partnership_reg_block = jelly_bean_partnership_reg_block::type_id::create( "jb_partnership_reg_block" );
      jb_partnership_reg_block.configure( .hdl_path( "top.dut" ) );//配置寄存器模型的路径
      jb_partnership_reg_block.build();
      jb_recipe_reg = jb_partnership_reg_block.jb_reg_blocks[1].jb_recipe_reg;
      jb_recipe_reg.clear_hdl_path();
      jb_recipe_reg.add_hdl_path_slice( .name( "color_and_flavor" ), .offset( 0 ), .size( 5 ) );//添加寄存器对应的reg_field的物理信息
      jb_recipe_reg.add_hdl_path_slice( .name( "extra.sugar_free" ), .offset( 5 ), .size( 1 ) );//添加寄存器对应的reg_field的物理信息
      jb_recipe_reg.add_hdl_path_slice( .name( "extra.sour"       ), .offset( 6 ), .size( 1 ) );//添加寄存器对应的reg_field的物理信息
      jb_partnership_reg_block.lock_model(); // finalize the address mapping
 
   // ... omit ...
 
endclass: jelly_bean_base_test
  • 55
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
UVM(Universal Verification Methodology)寄存器模型是一用于验证芯片寄存器功能的标准方法。它提供了一个统一的、可重用的框架,用于建立和管理寄存器模型,以及执行寄存器访问和验证。 UVM寄存器模型的主要组成部分包括寄存器模型寄存器层次结构、寄存器操作和寄存器验证环境。 1. 寄存器模型UVM寄存器模型是一个抽象的表示,用于描述芯片内部的寄存器寄存器字段。它提供了一种结构化的方式来定义寄存器的属性、寄存器字段的位宽和访问权限等。 2. 寄存器层次结构:UVM寄存器模型支持多层级的寄存器结构,可以通过层级关系来描述芯片内部的寄存器模块和子模块。这样可以更好地组织和管理寄存器模型,并提供寄存器之间的相互作用和访问。 3. 寄存器操作:UVM提供了一系列的API,用于执行寄存器读写操作。通过这些API,可以向寄存器模型发送读写请求,并获取响应。同时,还可以对寄存器访问进行配置和控制,如重置、写入默认值等。 4. 寄存器验证环境:UVM寄存器模型可以与其他验证环境进行集成,以验证寄存器功能的正确性。通过使用事务级建模(TLM)接口,可以将寄存器操作与其他验证组件进行交互,并进行功能验证、覆盖率分析和错误注入等。 总之,UVM寄存器模型提供了一种规范化的方法来描述和验证芯片寄存器功能。它具有可重用性、灵活性和扩展性,并能与其他验证组件进行集成,从而提高验证效率和可靠性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值