PLI 学习

fengbohan1 发布于 23 天前 86 次阅读


PLI 学习

介绍

参考文献:

  1. Verilog PLI已死( 可能), SystemVerilog DPI当立_sutherland hdl-CSDN博客

下面的表格非常详细的讲述了DPI和VPI的区别,总结一下:

  • DPI不是VPI的超集,两个不能完全替代
  • DPI适合verilog和c相互传递数据,互相调用的场景。
  • VPI最早为EDA工具开发,可以实现对设计数据结构的遍历访问。

SystemVerilog DPI与Verilog PLI功能比较.png

使用VPI的步骤(以hello word为例)

参考文献:

  1. 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文件。

  1. 定义系统任务或函数

    文件名: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);
    }
    
  2. 写一个c语言的calltf routine​,当仿真器遇到用户自定义的系统任务或函数时,就会根据calltf routine​调用指定的c函数

    文件名:veriuser_VCS.tab

    $hello      call=PLIbook_hello_calltf data=
    
  3. 注册用户自定义的系统任务或函数,告诉仿真器任务或函数名,以及相关的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 ***/
    };
    
  4. 编译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​可以指定带目录结构的库

image-20241122151559-yfxcjw8.png

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