[UVM源代码研究] UVM report机制分析(uvm-1.2版)
引子:如何定制一款个性化的打印格式
如果使用默认的打印格式,我们执行以下代码:
`uvm_info语句
实际打印结果格式如下:
`uvm_info打印结果
打印内容包含了下面几个方面:
- severity信息(UVM_INFO)
- 打印位置(文件…/env/my_case0.sv的第43行)
- 打印仿真时间 (0ns)
- 打印信息所在组件在树形结构中的位置 (uvm_test_top)
- ID信息 (BUILD_PHASE)
- 打印message实体(case0 on going)
也就是说UVM源代码已经帮我们做了处理,将我们执行的uvm_info
内的内容输出固定的打印格式到终端上,如果我们想调整打印的内容格式,那就需要对UVM中的report机制有个比较深入系统的了解,下面我们就以uvm_info
为例看下UVM源代码具体是怎么样一个执行过程。
`uvm_info宏的定义
src/macros/uvm_message_defines.svh中的源代码
src/macros/uvm_message_defines.svh中的源代码
113-115行这个行我们没有定义,这里就不讨论应用场景了。
这个宏封装的主体执行的代码就是117-118这两行代码。
uvm_report_enabled
src/base/uvm_report_object.svh中的源代码
src/base/uvm_globals.svh中的源代码
函数定义位置:对于这个uvm_report_enabled函数我们搜索UVM源代码文件发现有两个文件中有定义,分别如右图所示。对于uvm_globals.svh中的定义,我们分析其代码实现通过之前学习的知识发现其本质调用的是uvm_root单例中的uvm_report_enabled函数,而uvm_root的继承关系又是从uvm_component extends uvm_report_object继承而来,这样一来,uvm_report_enabled函数的实现根源是在uvm_report_object类中,之所以要在uvm_globals.svh中做一层封装的目的是使得那些不是从uvm_report_object继承而来的类(比如uvm_object派生而来的类如uvm_sequence、uvm_transaction等)中也能调用该函数。
函数定义内容: uvm_report_object 类中122行又调用了get_report_verbosity_level函数来跟verbosity进行比较,沿着调用函数的线路图依次追溯会发现这里条件表达式比较的是根据我们调用`uvm_*宏传递的严重性等级参数(severity)以及id所获取的冗余度阈值( +UVM_VERBOSIT=参数,可以被severity/id override)与我们传递的冗余度参数(verbosity)进行比较,当冗余度参数值大于冗余度阈值时则函数返回0,也就是uvm_message_defines.svh中的118行uvm_report_info不会被执行,例如我们设置的verbosity是UVM_HIGH(枚举值为300,定义在/src/base/uvm_object_globals.svh中),如果verbosity的阈值设置低于300(如UVM_LOW=100)则不会打印。对于severity为UVM_WARNING、UVM_ERROR、UVM_FATAL的情况传递的verbosity值为UVM_NONE(值为0),则函数始终返回1,始终都会打印。
枚举类型:uvm_severity和uvm_verbosity
src/base/uvm_object_globals.svh中的源代码
uvm_report_info
src/macros/uvm_message_defines.svh中的源代码
src/base/uvm_report_object.svh中的源代码
src/base/uvm_report_message.svh中的源代码
如过uvm_report_enabled返回值为1,那我们就需要执行uvm_report_info函数,而这个函数通过层层调用,最终执行的又是uvm_report_handler中的process_report_message函数,并且将uvm_report_info函数参数也通过report_message变量传递给了process_report_message函数。
process_report_message
src/base/uvm_report_handler.svh中的源代码
process_report_message就是四种severity等级执行打印的核心代码了,调用uvm_report_info传入的参数都保存在了report_message中。
要想了解process_report_message的执行内容,我们还需要知道uvm_report_handler中定义的如右图所示的一些联合数组,这里联合数组里存放的就是我们通过factory机制进行override的相关信息。
uvm_report_handler中的数据结构
src/base/uvm_report_handler.svh中的源代码
我们以uvm_id_verbosities_array类型的联合数组severity_id_verbosities为例讲解下其实现过程。
severity_id_verbosities本质上是一个以uvm_severity类型为key以uvm_pool#(string, int) 类型为value的联合数组,这里更像一个二维数组,因为uvm_pool本身就类似于一个数组结构,可以通过add函数往里添加内容。UVM中这种数据结构很常见,想要弄明白UVM源代码一定要掌握这种数据结构。
544-550行是对severity_id_verbosities通过调用set_severity_id_verbosity进行元素添加,实现将severity索引的键值对(id, verbosity)添加到severity对应的uvm_pool#(string, int )中。
438-454行则是severity_id_verbosities元素值的使用场景,通过传递参数severity和id来获取全局设置的verbosity阈值。441判断该severity有没有被override过(即调用set_severity_id_verbosity 重置severity/id对应的verbosity阈值),有override过则通过442-444行返回设置的verbosity阈值,如果没有则看id_verbosities这个键值对是否被override过(原理跟severity_id_verbosities 类似),有则返回这个id被override的verbosity阈值。如果以上该severity和id都没用被override过verbosity阈值,那么就返回m_max_verbosity_level,而这个变量就是我们通过+UVM_VERBOSITY=或者通过调用set_verbosity_level设定的verbosity阈值,如果以上两者都没有设置过,那默认值是UVM_MEDIUM。
以上介绍的就是关于通过severity和id来override verbosity的相关代码的实现,这其实也是factory机制在report机制中的一种实现,其他联合数组的分析方法类似,这里就不做赘述了。
src/base/uvm_report_handler.svh中的源代码
src/base/uvm_report_server.svh中的源代码
我们继续回到process_report_message的分析,313-332行可以认为都是对内部是否override进行的一些分析判断,将最终的结果存放到report_message中,有了上文对uvm_report_handler的分析这里的实现细节我们就不做赘述了。
最终我们的目标锁定在了uvm_report_server中process_report_message函数(与uvm_report_handler中同名的函数)。
而uvm_report_server中process_report_message函数执行的关键代码就在628行调用的execute_report_message函数,其余代码可以认为是在做一些打印内容相关变量(显示内容以及控制变量)的处理。
execute_report_message
src/base/uvm_object_globals.svh中的源代码
src/base/uvm_report_server.svh中的源代码
注释中的645行这句话很好的解答了我们在一开始提出的问题,就是我们可以通过overload execute_report_message这个函数来定制具体的message处理行为。
这个函数根据report_message中指定的uvm_action执行具体的设定内容,uvm_action包含的行为见下图中的枚举类型,而我们最关注的打印格式则由函数调用时传入的composed_message变量所指定,这样我们不得不回到该变量所生成的地方试图对该变量的格式进行一些修改,这样我们就不用在override源代码时做过大的改动了。
composed_message
src/base/uvm_report_server.svh中的源代码
src/base/uvm_report_server.svh中的源代码
uvm_report_server.svh中的626行通过调用compose_report_message产生了composed_message这么个字符串,通过查看compose_report_message这个函数我们显而易见的就发现了这就是我们看到`uvm_info所打印的格式的排版处。
于是我们得出这么一个结论:要想定制一款个性化的打印格式,我们只需要从uvm_report_server继承一个我们自定义的my_report_server类,然后对compose_report_message做override来输出我们自定的字符串数据即可。
另外完成了我们自定义的report_server类之后还需要通过调用set_server来将其设置为UVM环境使用的report_server,这是uvm_report_server类中的静态函数,可以直接使用类作用符::来直接调用
my_report_server
自定义的my_report_server
my_report_server的应用设置
实际打印结果
自定义的my_report_server只需要对uvm_report_server中的compose_report_message做override,我们只是想测试下能否按照我们的预期打印字符串格式,所以我们没有必要对这个函数做太多的修改,我们基本保留了他的绝大多数内容,只是在最终打印的字符串上做一些细微的处理,比如在每个信息段中间插入一些” | ”分隔符,最终的打印效果以及相关的代码实现如图所示。
src/base/uvm_report_object.svh中的源代码
src/base/uvm_report_handler.svh中的源代码
设置固定冗余度阈值
case中的两条打印信息
实际打印结果
这里还涉及到几个知识点:
- my_report_server并没有直接从uvm_report_server继承,而是从其派生类uvm_default_report_server继承而来,因为UVM中默认使用的就是这个派生类来执行打印的,其中定义了一些变量在compose_report_message会使用。
- 为了实现将某个打印信息的verbosity高于verbosity阈值的信息打印出来,我们可以通过调用uvm_report_object中定义的set_report_id_verbosity来改变其对应的verbosity阈值。case中的47行实现了将severity为UVM_INFO,ID为”BUILD_PHASE_1”的打印的冗余度阈值设置为UVM_DEBUG,这样我们在case中的第50代码就可以被打印,而第49行的冗余度阈值并没有被修改,使用的是默认的冗余度阈值UVM_MEDIUM则不会被打印,因为其verbosity为UVM_DEBUG>UVM_MEDIUM.
以上调用set_report_id_verbosity 函数还包含两点隐藏信息:
- uvm_test继承自uvm_component,uvm_component又继承自uvm_report_object,所以testcase里可以直接调用uvm_report_object里的函数
- uvm_report_object 里的set_report_id_verbosity 本质上是调用的我们前
面讲的uvm_report_handler里的set_severity_id_verbosity,因而根据我们前面对其中的联合数组severity_id_verbosities的分析知道是可以重载相应的冗余度阈值
总结
以上就是以uvm_info
宏作为切入点,以完成自定义的打印格式为目标,对uvm-1.2源代码中的report机制进行了走马观花一般的介绍。