[UVM源代码研究] UVM的field_automation实现的print()函数如何灵活控制打印数组元素的数量
引言
实际工作中我们经常会遇到如下问题:
我们使用内置的print()函数打印一个包含多个数组、队列元素变量的transaction时,这里以can_txrx_transfer为例,默认只会打印开始5个和最后5个元素,如图1所示,如果我们想要查看更多的元素值甚至完整的所有元素内容,有什么办法呢?
图1 can_txrx_transfer打印结果
第一种方法,也是我们最为熟悉的,就是重写这个can_txrx_transfer中的do_print()函数,想要打印什么内容,以什么格式打印都可以自己决定,这个就不在这里介绍了。
第二种方法,我们能不能借助现有的UVM源代码内容,看看其中实现只打印前后5个元素是在哪里设定的,通过修改设定能够实现打印更多的数组、队列内容。
源代码溯源分析
首先我们看看can_txrx_transfer里面我们是怎么注册的,如图2所示
图2 can_txrx_transfer中的field automation注册
可以看到,除了我们要研究的
`uvm_field_queue_int
其他地方还有定义
`uvm_field_array_int
这个就源于变量定义成队列还是动态数组了,分析方法相同,这里我们就以uvm_field_queue_int
为例进行分析。
`uvm_field_queue_int溯源
uvm_field_queue_int
宏定义在文件uvm_object_defines.svh中,如图3所示
图3 uvm_object_defines.svh中定义的`uvm_field_queue_int宏
进一步查看M_UVM_FIELD_QDA_INT
宏的定义,如图4所示
图4 uvm_object_defines.svh中定义的`M_UVM_FIELD_QDA_INT宏
我们选择将于print()无关的内容进行了折叠,这里case的参数what_是在uvm_field_utils_begin
宏中定义的,如图5所示
图5 uvm_object_defines.svh中定义的`uvm_field_utils_begin宏
这里就涉及到了本次问题讨论的第一个难点,就是单独的看M_UVM_FIELD_QDA_INT
宏定义是没有任何意义的,必须跟uvm_field_utils_begin
宏定义结合来看,结合起来本质上调用的就是一个_m_uvm_field_automation()函数,函数中的第二个参数what_就表示需要调用的field_automation函数类型,比如我们这里需要调用print()函数,那么print函数最终会转化为调用_m_uvm_field_automation(.what_(UVM_PRINT)),这里就需要结合uvm_object.svh中的print()函数相关的代码来看了,如图6所示
图6 uvm_object.svh中定义的print()函数相关的代码
可以看到我们在调用print()函数时,最终打印在调用do_print()之前会调用我们在图5提到的_m_uvm_field_automation函数,进而调用我们在图4提到的M_UVM_FIELD_QDA_INT
中case语句选择的UVM_PRINT中的内容。这里print()函数还有个uvm_printer类型的参数,是指定我们选择的打印器类型,这个我们后面会讲,这里我们先继续讲图4中的代码,只要我们没有在uvm_field_queue_int
中指定FLAG为UVM_NOPRINT,图4中的宏uvm_print_array_int3()
就会被调用,uvm_print_array_int3
定义在uvm_printer_defines.svh中如图7所示
图7 uvm_printer_defines.svh中定义的uvm_print_array_int3宏
进一步查看uvm_print_array_int3
调用的uvm_print_qda_int4
,如图8所示
图8 uvm_printer_defines.svh中定义的uvm_print_qda_int4宏
这里我多截取了一部门内容,即图中代码104-105行,我们不难发现对于静态数组最终也会调用uvm_print_qda_int4
宏,所以对于(静态/动态)数组、队列分析其元素打印格式的方法时类似的。下面我们来分析图8中的代码执行过程。
109-110行定义了uvm_printer和uvm_printer_knobs类型的局部变量P__和k__用于接受函数传进来的参数P以及P中包含的字段knobs。
这个参数P就是我们在图4调用uvm_print_array_int3
传递的参数_m_uvm_status_contrainer.printer,而这个_m_uvm_status_contrainer定义在uvm_object.svh中,如图9所示
图9 uvm_object.svh中定义的_m_uvm_status_contrainer变量
_m_uvm_status_contrainer变量的类型uvm_status_container的定义如图10所示
图10 uvm_misc.svh中定义的uvm_status_container类
可以看到uvm_status_container包含了一些自动化实现相关的状态信息,而我们要用到的uvm_printer只是其中的一个字段而已。
我们接着看uvm_status_container里的uvm_printer在哪里赋值的,这就要回到图6中调用print()函数时905行将参数printer赋值给了uvm_status_container中的printer,而如果我们调用print()函数时不指定printer参数,就会用默认的uvm_default_printer赋值。
uvm_default_printer溯源
关于这个uvm_default_printer,他实际上是定义在uvm_object_globals.svh中的一个全局变量,相关定义如图11所示
图11 uvm_object_globals.svh中uvm_default_printer的相关定义
可以看到uvm_default_printer本质上是一个uvm_table_printer类型的全局变量,进一步查看uvm_table_printer的定义,如图12所示
图12 uvm_printer.svh中uvm_table_printer的定义
从图12中不难看出uvm_table_printer继承自uvm_printer,uvm_table_printer规定了打印出来的格式类似于一张表格,并且为了进行格式的对其使用calculate_max_width()函数约束了表格中各种项的最大宽度,如图13所示。
图13 uvm_printer.svh中的calculate_max_width()函数
而决定了打印格式的是emit()函数,uvm_printer是个virtual类,其中定义了各种virtual function供派生类实现具体功能,想要使用必须继承产生特定打印格式的类,而emit()就是派生类中需要override的决定最终打印格式的类。uvm_printer中还例化了一个uvm_printer_knobs类型的实例knobs,如图14所示的
图14 uvm_printer.svh中的uvm_printer_knobs实例
uvm_printer_knobs的定义如图15所示
图15 uvm_printer.svh中uvm_printer_knobs的定义
uvm_printer_knobs决定了我们使用的打印机(每一种从uvm_printer派生出的类都是一种特定类型的打印机)内的细节设置,这里就包含了我们在调用print()打印数组格式元素的数量的两个变量begin_elements和end_elements,begin_elements决定了我们打印数组的前多少个数据(-1表示不做现在全部打印),end_elements决定了打印数组的最后多少个数据,而这部分功能的具体实现就要回到我们在遥远的图8中所展示的`uvm_print_qda_int4宏,其中的变量k__就是我们所使用的打印机uvm_default_printer中定义的knobs变量层层传递到宏里的。
这样一来,我们就理清楚要想实现精确控制field_automation里打印数组元素的数量,我们应该如何进行设置,例如这里我们希望打印数组的前8个元素以及后10个元素,我们就可以对我们使用的uvm_default_printer中的knobs变量进行如图16所示的配置实现。
图16 配置uvm_default_printer
这里需要注意的是uvm_default_printer是一个全局变量,所以我们可以在任何地方引用,另外我们配置uvm_default_printer需要在我们执行任何打印之前,因为之后改了配置,新的print()才会生效,所以我们一般会把uvm_default_printer的配置放在test_base的build_phase中进行。
新的打印结果如图17所示
图17 print()新配置的打印结果
总结
本文从我们在UVM中常见的内置打印函数print()数组/队列打印时元素数据量显示不够引申出UVM源代码内容如何实现打印机制的讨论,层层递进分析了UVM源代码内容实现打印机制的相关宏和类中的重要代码,进而实现了灵活配置field_automation中数组/队列元素打印数量的灵活配置。