PLI 学习
介绍
参考文献:
- Verilog PLI已死( 可能), SystemVerilog DPI当立_sutherland hdl-CSDN博客
下面的表格非常详细的讲述了DPI和VPI的区别,总结一下:
- DPI不是VPI的超集,两个不能完全替代
- DPI适合verilog和c相互传递数据,互相调用的场景。
- VPI最早为EDA工具开发,可以实现对设计数据结构的遍历访问。
使用VPI的步骤(以hello word为例)
参考文献:
- linux下gcc编译生成.out,.o,.a,.so文件_gcc *.a *.o-CSDN博客
-
下面使用的设计文件(testbench.sv
)如下:
`timescale 1ns / 1ns
module test;
initial
begin
$hello;
#10 $finish;
end
endmodule
普通方式(一起编译)
这种方式,将c和verilog文件一起编译。可以分成4步:
⚠这种方式,不同通过file_list提供c文件,c文件只能在命令行中提供。因为vcs默认file_list中全是verilog文件。
-
定义系统任务或函数
文件名:
hello_vpi.c
#include <stdlib.h> /* ANSI C standard library */ #include <stdio.h> /* ANSI C standard input/output library */ #include <stdarg.h> /* ANSI C standard arguments library */ #include "vpi_user.h" /* IEEE 1364 PLI VPI routine library */ /********************************************************************** * calltf routine *********************************************************************/ PLI_INT32 PLIbook_hello_calltf(PLI_BYTE8 *user_data) { vpi_printf("\nHello World!\n\n"); return(0); } /********************************************************************** * $hello Registration Data * (add this function name to the vlog_startup_routines array) *********************************************************************/ void PLIbook_hello_register() { s_vpi_systf_data tf_data; tf_data.type = vpiSysTask; tf_data.sysfunctype = 0; tf_data.tfname = "$hello"; tf_data.calltf = PLIbook_hello_calltf; tf_data.compiletf = NULL; tf_data.sizetf = NULL; tf_data.user_data = NULL; vpi_register_systf(&tf_data); }
-
写一个c语言的
calltf routine
,当仿真器遇到用户自定义的系统任务或函数时,就会根据calltf routine
调用指定的c函数文件名:
veriuser_VCS.tab
$hello call=PLIbook_hello_calltf data=
-
注册用户自定义的系统任务或函数,告诉仿真器任务或函数名,以及相关的
calltf routine
文件名:
vpi_user.c
#include "vpi_user.h" /* prototypes of the PLI registration routines */ extern void PLIbook_hello_register(); void (*vlog_startup_routines[])() = { /*** add user entries here ***/ PLIbook_hello_register, 0 /*** final entry must be 0 ***/ };
-
编译c文件
comp: - vcs -full64 +v2k -sverilog -debug_access+all -top testbench -l compile.log \ testbench.sv hello_vpi.c -P veriuser_VCS.tab sim: - ./simv -l sim.log clean: - \rm -rf *.log simv simv.daidir *.a *.o *.so csrc *.key
这样一个简单的Hello world的例子就完成了。
静态库
verdi提供的生成波形的函数,本质上也是利用vpi实现的。观察到使用verdi生成波形时通常需要在vcs编译时加入如下语句:
-P ${VERDI_HOME}/share/PLI/VCS/LINUX64/novas.tab ${VERDI_HOME}/share/PLI/VCS/LINUX64/pli.a
.a
是linux的静态库文件,.so
是linux的动态库文件。可以参考:linux下gcc编译生成.out,.o,.a,.so文件_gcc *.a *.o-CSDN博客
为了方便调用自定义系统函数或任务,我们也采用提供静态连接库的方法。改动后的Makefile如下:
libhello.a: hello_vpi.c vpi_user.c
- gcc -c hello_vpi.c vpi_user.c -I ${VCS_HOME}/include
- ar -cr libhello.a hello_vpi.o vpi_user.o
comp_s: libhello.a
- vcs -full64 +v2k -sverilog -debug_access+all -top testbench -l compile.log \
testbench.sv -P veriuser_VCS.tab libhello.a
sim:
- ./simv -l sim.log
clean:
- \rm -rf *.log simv simv.daidir *.a *.o *.so csrc *.key
这样就完成了静态库的编译和调用,可以在验证环境中自由使用自定义的系统任务或函数。
动态库-sv_lib
同样的我们也可以使用动态库的方式。
首先需要知道simv
文件可以在执行的时候指定库路径和库名称:
-
-sv_root .
: 指定.
为库所在路径 -
-sv_lib libhello
(可以省略库后缀.so
):指定加载库文件libhello.so
官方文档将
-sv_lib
选项用于DPI,但是实际验证也可以用于载入VPI,和-load
选项差别不大
采用动态库方式Makefile文件如下:
libhello.so: hello_vpi.c vpi_user.c
- gcc -fPIC -c hello_vpi.c vpi_user.c -I ${VCS_HOME}/include
- gcc -shared -o libhello.so hello_vpi.o vpi_user.o
#libhello.so: hello_vpi.c vpi_user.c
# - gcc -fPIC -shared -o libhello.so -c hello_vpi.c vpi_user.c -I ${VCS_HOME}/include
comp_d: libhello.so
- vcs -full64 +v2k -sverilog -debug_access+all -top testbench -l compile.log \
testbench.sv -P veriuser_VCS.tab
sim:
- ./simv -l sim.log -sv_root . -sv_lib libhello
clean:
- \rm -rf *.log simv simv.daidir *.a *.o *.so csrc *.key
还可以使用-sv_liblist bootstrap_file
,载入多个库文件
#!SV_LIBRARIES
myclibs/lib1
myclibs/lib3
动态库-load
官方推荐使用-load
载入VPI的库,经过测试可以使用-sv_lib代替-load,但是不能反过来。
-
-load
可以多次指定不同的库 -
-load
可以指定带目录结构的库
libhello.so: hello_vpi.c vpi_user.c
- gcc -fPIC -c hello_vpi.c vpi_user.c -I ${VCS_HOME}/include
- gcc -shared -o libhello.so hello_vpi.o vpi_user.o
#libhello.so: hello_vpi.c vpi_user.c
# - gcc -fPIC -shared -o libhello.so -c hello_vpi.c vpi_user.c -I ${VCS_HOME}/include
comp_d: libhello.so
- vcs -full64 +v2k -sverilog -debug_access+all -top testbench -l compile.log \
testbench.sv -P veriuser_VCS.tab
sim:
- ./simv -l sim.log -load ./libhello.so
clean:
- \rm -rf *.log simv simv.daidir *.a *.o *.so csrc *.key
小结
优点 | 缺点 | |
---|---|---|
普通方式 | 编译脚本简单 | 当c文件多的时候,难以维护 |
静态库 | 使用方便简单 | 需要先编译静态库 |
动态库 | 可执行文件体积小,方便多个程序之间共享。 可以单独更新动态库,不重新编译主程序。 |
需要先编译动态库,还需要在运行时指定库和库路径 |
三种方法各有优缺点,但从ASIC验证人员角度来说,cmodel通常文件多且较为复杂,并不在乎可执行文件的体积大小。因此优先推荐静态库的方法。
具体使用的时候,推荐将c文件和设计文件分开管理,分开编译。
使用DPI的步骤(以hello world为例)
普通方式(一起编译)
testbench.sv
module testbench;
import "DPI-C" context function int c_hello(input string name, input int age);
export "DPI-C" function sv_hello;
function void sv_hello();
$display("sv_hello");
endfunction
initial begin
c_hello("testbench", 18);
end
endmodule
sv_main.c
#include <svdpi.h>
#include <vpi_user.h>
#include <stdio.h>
extern void sv_hello();
int c_hello(const char *name, int age) {
vpi_printf("c_hello: %s, %d\n", name, age);
sv_hello();
return 0;
}
Makefile
comp:
- vcs -full64 +v2k -sverilog -LDFLAGS -Wl,--no-as-needed -debug_access+all -top testbench -l compile.log \
sv_main.c testbench.sv
sim:
- ./simv -l sim.log
clean:
- \rm -rf *.log simv simv.daidir *.a *.o *.so csrc *.key
⚠ 如果在import的函数中使用了export的函数,则import的时候必须使用context
关键字。
动态库-sv_lib
可以分成两步。首先编译动态库,然后再仿真的时候调用。
⚠ 经过测试不能使用-load
代替-sv_lib
。
Makefile
libsv_main.so: sv_main.c
- gcc -fPIC -c sv_main.c -I ${VCS_HOME}/include
- gcc -shared -o sv_main.so sv_main.o
#libsv_main.so: sv_main.c
# - gcc -fPIC -shared -o libsv_main.so -c sv_main.c -I ${VCS_HOME}/include
comp:
- vcs -full64 +v2k -sverilog -LDFLAGS -Wl,--no-as-needed -debug_access+all -top testbench -l compile.log \
testbench.sv
sim: linbsv_main.so
- ./simv -l sim.log -sv_root . -sv_lib sv_main
clean:
- \rm -rf *.log simv simv.daidir *.a *.o *.so csrc *.key