1、是什么??
DPI(Direct Programming Interface),全称直接编程接口,是SV与其它语言(C/C++)交互的接口,基本上是唯一接口了。
2、为什么??
说完是什么,那么我们可以考虑一下了,为什么需要这个接口呢?或者说为什么SV/UVM需要与其它语言产生交互呢?
其实就是SV和C之间的一种转换关系。
这就涉及到了一些应用场景,我们可以说几个看看。
(1)我们知道,在模块级乃至子系统级的验证,使用SV就完全够用了,而在更上面的层级,例如系统级,多采用大量的C代码组成。那么为了完成测试用例从子系统到系统级的复用,我们最好在子系统级开始就有意识地建立支持C测试的环境,并且使用一些基于C的测试用例,这样才能使得整体具有更好的复用性。
(2)处理器的需要。子系统测试时,不一定本身自带了处理器的硬件实例(Verilog编写的),因此在构建子系统时还要考虑如何模拟外部处理器对子系统的访问行为。
当然,我们直接加上一个外部的处理器实例是完全可以的,等验证完子系统的功能后再撤掉就行。注意,这个处理器实例也是verilog编写的。
但是,这种方法也存在了一些弊端:即使这个外部处理器再小巧,但硬件体积还是过大了,肯定会影响整个仿真的速度;针对不同子系统的预留接口(APB/AHB/AXI等),处理器子系统也要进行对应的调整吧;这种真实的处理器,必然要处理C代码,所以要进行编译、转换成二进制bin文件以及下载到memory中,还是太麻烦;还需要额外的启动配置文件,而且仿真时需要额外的额时间进行初始化。
总结一下真实处理器的弊端,那就是麻烦,处理C代码也繁琐,影响了仿真速度。
既然这样,必然引出了解决办法。
刚刚说到,硬件的体积大、速度慢,那么转换成软件如何?这样既能保证复用,又可以模拟一些常规处理器的行为。
可以考虑一下,既然要实现这些,我们就要考虑一下处理器的功能了:支持各种总线接口的读写;支持中断响应;支持时间等待;支持多核并行处理;支持系统复位;支持上电和掉电行为。
那么这样一个软件环境,可以用SV来搭建,既然还要处理一下C语言,所以也必然需要DPI-C接口,这就是第二种场景。
3、怎么做??
说完了应用场景,该说说怎么实现了。
此时,我们要明白两个问题:SV和C之间的数据类型不是完全一致的,所以肯定需要转换;SV和C之间的控制信号又该如何传递呢?
给个简单例子吧,先看为敬。
import “DPI-C” function int factorial(input int i);
program automatic test;
initial beign
for(int i=1;i<=10;i++)
$display(“%0d != %0d”, i, factorial(i));
end
endprogram
好,我们看出来了,这是SV一侧导入C方法的示例,需要用import,后面的function是表示了这个导入到SV一侧的C方法在这一侧的名称及类型。
通常,带返回值的C函数会被映射成一个SV函数;void类型的C函数会被映射成一个SV任务或者void函数。
那么,如果我想在导入的同时修改一下姓名呢?
Import “DPI-C” test = function void my_test();
参数方向
对于参数方向,支持input, output以及inout。Ref则不被支持。
当然默认是input类型。
Import “DPI-C” function int addmul(input int a, b, output int sum);
数据映射关系
这个就很重要了,表示了SV和C交互时的数据类型转换关系。
另外需要提醒一下,C中本身是不具有这些交互类型的,需要在使用前添加头文件
#include <svdpi.h>才可以
乍一看很复杂
那么我们举一个例子来看看
首先是C语言一侧
下面是SV一侧的方法
下面一些具体方法就不写了,我们注意到,bit[N:0]与bit还是不一样的,还有一点可以注意一下,即使是bit[0:0]与bit,虽然在SV一侧是相同的(假定都是输入),但是到了C一侧,它们的类型分别是const svBitVecVal*与svBit。
SV中也能直接应用C的一些数学函数,这一点还是很方便的。
import “DPI-C” function read sin(input real r);
四值逻辑变量的转换关系
上面的看起来都比较常规,接下来这个就有意思了。我们知道,C语言是软件环境,所以只会有0和1,那么它怎么才能表示诸如logic等四值变量呢??
这个很巧妙,假设SV一侧有一个logic f,那么它在C一侧,会用一个无符号的字节来保存。分为aval和bval,其中aval保存在最低位,bval保存在紧邻的高位。
下面给出转换表
两位表示一位,所以1’b0在C中就是0x0,1’b1在C中是0x1,1’bz在C中是0x2,1’bx在C中是0x3。
那么logic[31:0] lword,就更有意思了,我们采用一对32bit的变量来表示,第一个aval[31:0]包含低32位的数值,bval[31:0]包含了紧邻高位的数值,表示如下:
所以在导入数组时,需要同时赋值aval和bval,例如data[31:0],例如要将data[0]=1,那么需要data[0].aval=1, data[0].bval=0;
此时要说明一下,在导入方式上,
import “DPI-C” context task core0_thread();
这个关键词表示的是关联导入,其实分为三种导入方法,pure、context以及generic。
如果一个函数严格根据输入来计算输出,跟外部环境没有其它交互,那么就是pure的;如果使用了全局变量,那它就不会是pure方法了,但如果它也没有调用任何的PLI,所以其实也不必声明为context(因为关联类型会导致额外的开销),所以可以声明为generic(缺省下为此类型)。
从SV中导出
这个就比较简单了
注意后面不需要加括号和参数,如果需要改变名字,则可以
export “DPI-C” dpi_writew = task writew;
而在C一侧,则只需要extern void sv_display();就可以了
当然有参数还是要加的
extern void dpi_writew(unsigned int addr, unsigned int data);
extern void dpi_readw(unsigned int addr, unsigned int *data);
好了,最基本的就是这些了,具体的以后再说。
自我总结。
———————————————————大量引用了SV的绿皮书以及芯片验证漫游指南