[UVM源代码研究] 聊聊UVM源代码中的DPI函数
引言
之前的一篇文章 数字验证大头兵:[UVM源代码研究] 聊聊寄存器模型的后门访问 中提到进行寄存器后门访问的时候用到了UVM源代码中自带的DPI函数uvm_hdl_read(),于是乎打算写一篇文章把UVM源代码中涉及到的DPI函数彻底梳理一遍。
SystemVerilog DPI,全称SystemVerilog直接编程接口 (英语:SystemVerilog Direct Programming Interface)是SystemVerilog与其他外来编程语言的接口。能够使用的语言包括C语言、C++、SystemC等。直接编程接口由两个层次构成:SystemVerilog层和外来语言层,两个层次相互分离。对于SystemVerilog方面,另一边使用的编程语言是透明的,它只需要保证函数名和函数参数返回值类型的匹配即可。SystemVerilog和外来语言的编译器各自并不需要分析另一种语言的代码,两者各自独立编译。由于不触及SystemVerilog层,因此支持使用不同的语言。不过,目前SystemVerilog仅为C语言定义了外来语言层。所以,每个函数DPI的文件都有.svh和*.cc或*.c 两个文件。
dpi目录文件介绍
UVM源代码中的dpi目录包含文件如图1所示。
图1 /src/dpi目录文件结构
uvm_dpi.cc和uvm_dpi.svh
uvm_dpi.cc文件如图2所示,其中include了所有C语言相关的源文件。
图2 /src/dpi/uvm_dpi.cc文件内容
相应的uvm_dpi.svh则是所有DPI相关的sv代码的文件集合,如图3所示。
图3 /src/dpi/uvm_dpi.svh文件内容
可以看到uvm_dpi.svh中除了include相关几个sv文件之外,还做了宏的统一管理,一旦打开了UVM_NO_DPI宏,那么其中包含的三个文件中的NO_DPI宏就统一打开了,这样所有的DPI函数就都不能正常使用了,当然也可以分别独立打开每一个NO_DPI宏。
如此一来,我们只需要把上面提到的这两个文件都编译了就可以使用相应的DPI函数功能了,uvm_pkg.sv中包含文件uvm_dpi.svh,而http://uvm_dpi.cc需要在uvm_pkg.sv之外单独编译,所以在旧版本的仿真器编译时通常我们需要分别编译uvm_pkg.sv和http://uvm_dpi.cc两个文件,以VCS为例,早起版本我们通常需要像图4一样添加编译参数。
图4 vcs编译工具添加支持UVM DPI的编译参数
如今得益于多数仿真工具已经将UVM源代码库集成到了仿真工具的安装路径下了,并且支持相关的参数来简化仿真器的配置项,诸如上面一些列UVM相关的参数可以直接使用下面一句话来替换完成。
vcs -ntb_opts uvm-1.2
VCS仿真器会自动将指定的UVM版本相关的源代码全部编译,大大简化了仿真工具的参数配置过程。
dpi目录下剩余三组c/cc和svh后缀文件分别对应三种DPI的应用,下面我们分别展开介绍。
uvm_hdl.svh和uvm_hdl.c
UVM源代码实现的后门访问HDL路径的函数基本都包含在uvm_hdl文件中,我们可以在uvm中实现HDL的后门访问,具体包括的function有uvm_hdl_check_path,uvm_hdl_deposit, uvm_hdl_force,uvm_hdl_release,uvm_hdl_read, task 有uvm_hdl_force_time。 下面分别介绍下这些函数/任务的作用和使用示例。
uvm_hdl_check_path
// Function: uvm_hdl_check_path
//
// Checks that the given HDL ~path~ exists. Returns 0 if NOT found, 1 otherwise.
//
function int uvm_hdl_check_path(string path)
检查字符串表示的HDL路径是否存在,存在返回1,不存在返回0。
uvm_hdl_check_path的使用示例参考图5所示的代码段。
图5 uvm_hdl_check_path使用示例
执行结果如图6所示。
图6 uvm_hdl_check_path示例执行结果
uvm_hdl_check_path函数的典型应用场景可以用来判断某些通过宏或者generate选择性例化的instance是否有被例化来执行相应的后续处理代码。
下面几个DPI函数涉及到对HDL路径上的几个信号进行值操作,这里对几个信号的关系进行简单的说明,涉及到如下三个信号。
output wire[4:0] da_xtal_ib_ctrl;
input wire[4:0] da_xtal_ib_ctrl_func;
output reg[4:0] xtal_ib_ctrl;
他们之间的关系是:
-
da_xtal_ib_ctrl的值由da_xtal_ib_ctrl_func通过assign连续赋值更新
-
da_xtal_ib_ctrl_func的值通过接口传递由xtal_ib_ctrl更新
-
xtal_ib_ctrl的值由always语句通过时序逻辑更新
uvm_hdl_deposit
// Function: uvm_hdl_deposit
//
// Sets the given HDL ~path~ to the specified ~value~.
// Returns 1 if the call succeeded, 0 otherwise.
//
import "DPI-C" context function int uvm_hdl_deposit(string path, uvm_hdl_data_t value);
uvm_hdl_deposit函数具有两个参数。第一个参数是字符串表示的HDL路径信号,第二个参数是要发送到信号上的值。
uvm_hdl_deposit的使用示例如图7所示。
图7 uvm_hdl_deposit代码示例
仿真结果波形如图8所示。
图8 uvm_hdl_deposit代码示例运行结果
从波形中我们可以看到虽然da_xtal_ib_ctrl信号定义成了wire类型并且通过连续赋值语句由信号da_xtal_ib_ctrl_func进行赋值,但是使用uvm_hdl_deposit赋值了之后连续赋值语句就无效了,该值被保存了下来,执行效果有点类似于force。但是等待5us后又对da_xtal_ib_ctrl_func信号采用了uvm_hdl_deposit赋了一个新值,发现da_xtal_ib_ctrl的值也跟着更新了,可见uvm_hdl_deposit的作用还不同于force功能,他像是给HDL中的某个信号进行赋值,知道该信号在HDL中的赋值逻辑发生变化时才会再次更新值,这样就不会被连续赋值语句覆盖了。最后通过配置寄存器又将xtal_ib_ctrl的值进行更新进而覆盖了之前deposit的值。
uvm_hdl_force/uvm_hdl_release
// Function: uvm_hdl_force
//
// Forces the ~value~ on the given ~path~. Returns 1 if the call succeeded, 0 otherwise.
//
import "DPI-C" context function int uvm_hdl_force(string path, uvm_hdl_data_t value);
// Function: uvm_hdl_release
//
// Releases a value previously set with <uvm_hdl_force>.
// Returns 1 if the call succeeded, 0 otherwise.
//
import "DPI-C" context function int uvm_hdl_release(string path);
这个函数的功能类似于我们在systemverilog中的force语法,只不过force的对象是HDL hierarchy,而uvm_hdl_force的对象是一个表示HDL hierarchy的字符串,,它也有与force对应的release功能的函数uvm_hdl_release。uvm_hdl_force跟uvm_hdl_deposit的区别在于后者能够被新的赋值所覆盖,而前者在被uvm_hdl_release释放之前信号值永远不会被覆盖。
我们紧接着uvm_hdl_deposit代码后面补充uvm_hdl_force/uvm_hdl_release代码示例,如图9所示。
图9 uvm_hdl_force/uvm_hdl_release代码示例
运行结果波形如图10所示。
图10 uvm_hdl_force/uvm_hdl_release代码示例运行结果
图中结果可以看到uvm_hdl_force会强制改变信号的值,执行uvm_hdl_release释放了又会被连续赋值语句覆盖之前force的值,而在uvm_hdl_force和uvm_hdl_release之间执行的uvm_hdl_deposit则被忽略了。
uvm_hdl_force/uvm_hdl_release相比于force/release的好处是将HDL hierarchy封装成了字符串的形式,不仅有利于将路径作为参数进行传递,同时还规避了package中不允许出现HDL hierarchy的语法问题,这样我们就可以在被package包含的class文件中对HDL hierarchy 进行操作了。
uvm_hdl_release_and_read
// Function: uvm_hdl_release_and_read
//
// Releases a value previously set with <uvm_hdl_force>.
// Returns 1 if the call succeeded, 0 otherwise. ~value~ is set to
// the HDL value after the release. For 'reg', the value will still be
// the forced value until it has been procedurally reassigned. For 'wire',
// the value will change immediately to the resolved value of its
// continuous drivers, if any. If none, its value remains as forced until
// the next direct assignment.
//
import "DPI-C" context function int uvm_hdl_release_and_read(string path, inout uvm_hdl_data_t value);
uvm_hdl_release_and_read相比于uvm_hdl_release简单粗暴的释放信号的驱动,对wire和reg类型做了不同的处理,对于wire类型直接释放信号的驱动,连续赋值语句能够将新值立即drive给该信号更新信号值,而对于reg类型需要再次驱动该信号才能更新该值,并且在释放驱动的一瞬间用接口上的inout参数value来更新信号release后的值(实际实验发现这个value并没有被更新上去,不知道是使用的方法不正确,还是代码有漏洞)。
代码示例如图11所示。
图11 uvm_hdl_release_and_read代码示例
运行结果波形如图12所示。
图12 uvm_hdl_release_and_read代码示例运行结果
在图12标记的时间点1、2、3我们分别对三个信号da_xtal_ib_ctrl, da_xtal_ib_ctrl_func, xtal_ib_ctrl执行了uvm_hdl_force,时间点4、5、6依次执行了uvm_hdl_release_and_read,并且我们调用uvm_hdl_release_and_read传递的value参数都是1,都是事与愿违,无论是wire还是reg类型,这个value值都没有在release时设置到对应的信号。从执行uvm_hdl_release_and_read的结果我们可以看到对于wire信号da_xtal_ib_ctrl, da_xtal_ib_ctrl_func释放之后立马值就被HDL的连续赋值更新了,不管是通过assign还是接口连线,而reg类型的xtal_ib_ctrl释放后由于没有赋值则没有更新值。
uvm_hdl_force_time
// Function: uvm_hdl_force_time
//
// Forces the ~value~ on the given ~path~ for the specified amount of ~force_time~.
// If ~force_time~ is 0, <uvm_hdl_deposit> is called.
// Returns 1 if the call succeeded, 0 otherwise.
//
task uvm_hdl_force_time(string path, uvm_hdl_data_t value, time force_time = 0);
if (force_time == 0) begin
void'(uvm_hdl_deposit(path, value));
return;
end
if (!uvm_hdl_force(path, value))
return;
#force_time;
void'(uvm_hdl_release_and_read(path, value));
endtask
这个uvm_hdl_force_time实际上并没有专门的C函数实现,他是对uvm_hdl_deposit, uvm_hdl_force和uvm_hdl_release_and_read的组合实现,表示force某个信号为特定值之后一段时间后再释放。需要注意的是,当force时间为0时相当于执行了uvm_hdl_deposit,当force时间超过0时release用的是uvm_hdl_release_and_read,这样对于wire类型就会产生两种不同的force效果,因为uvm_hdl_deposit对于连续赋值语句之后等号右边的表达式有新值时才会更新赋值,而不会立刻执行连续赋值的结果。
uvm_hdl_read
// Function: uvm_hdl_read()
//
// Gets the value at the given ~path~.
// Returns 1 if the call succeeded, 0 otherwise.
//
import "DPI-C" context function int uvm_hdl_read(string path, output uvm_hdl_data_t value);
这个我们在之前的文章将寄存器后门访问的时候已经有过讲解,表示通过后门读取HDL路径上的信号值,不限于寄存器。
这样我们关于uvm_hdl.svh中涉及到的函数任务就讲解完成了,uvm_hdl.c文件中的具体C代码实现我们这里就不展开讲解了,感兴趣的可以自行研究。
uvm_regex.svh和uvm_regex.cc
这两个文件实现了UVM中的正则表达相关的函数,这个使用场景很有限,这里就不做介绍了。
uvm_svcmd_dpi.svh和uvm_svcmd_dpi.cc
UVM中有需要从cmmand line获取输入参数的需求就可以调用这里的几个DPI函数,但是由于 t e s t test testplusargs和 v a l u e s values valuesplusargs的存在,这里的DPI函数也不太常用,所以这里就不做介绍了。
总结
本文主要介绍了UVM源代码src/dpi/uvm_hdl.svh中的几个通过HDL hierarchy后门访问RTL的方法,提供了一种UVM环境与DUT交互的新思路。