• 【Chips】如何用DPI调用C++程序,并成功仿真


    Title:如何用DPI调用C++程序,并成功仿真

    • 前言

      之前试了用DPI调用C程序,很方便,两行解决:

      1. 一行在Verilog/SV中加import "DPI-C" function int 函数名
      2. 一行在VCS compile中补上此C文件名;

      上周五因需要,计划用DPI调用C++程序,结果!好多好多bug!找了整整一天!折磨!

      为什么会这么久、这么痛苦嘞?

      1. 网上DPI的信息少,google上都不多;
      2. 搜来搜去,title起的是《DPI调用C/C++程序》,结果通篇只能调C程序,右半边的C++压根不能用好吧,编译过没哈?就忽悠人…
      3. 好不容易找到一些信息,都解决不了问题,太general或者 无效啊无效!
        刷了一天google、睡眼惺忪;
        看到几个帖子兴冲冲;
        百般实验一场空.

      好在最后捣鼓出来了。俺很高兴,记录一下!(默认是 Linux下

      主要是 g++封装成动态库、SV中使用动态库的过程,太曲折了!

    1 基本概念

    1.1 编译、链接、执行

    A 基本flow与文件后缀

    源代码文件 -> 编译 -> 链接 -> 执行.

    • Windows下:

      1. C文件分别 编译,分别得到 .obj文件;
      2. 多个 .obj文件 静态链接 后得到 .lib文件,多个 .obj文件 动态链接 后得到 .dll文件;
      3. 各库文件(静态库或动态库),再次链接 后可得到可执行文件 .exe
    • Linux下:

      1. C文件分别 编译,分别得到 .o 中间文件(是 可以单独执行 的);

      2. 多个 .o文件 静态链接 后得到 .a 静态库文件,多个 .o文件 动态链接 后得到 .so 共享的动态库文件;

        共享库必须放在特定系统目录下,不然需手动指定.

      3. 各库文件,再次链接 后可得到可执行文件(无后缀);

    多语言混合编程时:可以各自分别编译成.o文件,再链接成可执行文件.

    B g++ 下编译、链接语法

    以Linux为例.

    • g++进行 编译 语法

      g++ -c file1.cpp file2.cpp ... filen.cpp 把文件们 分别 编译成库文件.o

    • g++进行 静态链接 的语法

      用linux ar 指令,没用到故没看,可见:linux ar命令 —— CSDN xuhongning

    • g++ 生成可执行文件

      g++ file.cpp -o target 把所有文件 链接成一个target 可执行文件
      顺序可以自己打乱,但是 -o 后面接的一定是target 文件.

    • g++进行 动态链接 的语法

      生成动态链接库g++ filename.cpp -fPIC -shared -o target.so 把文件 链接 生成 动态链接库 target.so

      动态链接库 可以嵌套g++ -o target -L./lib -lcpp
      使用动态链接库得到 可执行文件 target.

      • 其中option解释:

        1. -fPIC :生成动态链接库;

        2. -shared :编译为位置独立的代码,否则动态链接库动态载入时是以代码拷贝方式满足多进程,不是真正的代码共享.

        3. -L 后直接紧跟(无空格!坑死我了!)动态lib库的目录path;

          若不写,系统默认会去:/lib/usr/lib/usr/local/lib三个系统path下查找依赖的动态库;否则必须自己用-Lxxx指定(即使是当前路径下,也得用-L指定).

        4. -l后直接紧跟(无空格!)动态lib的名字(不是文件名!

          e.g. 动态lib的文件名为 libmath.so,则此动态链接库的lib名是math

        注意: 利用动态库,新生成 可执行文件 或 新的动态库 后(为方便,称新生成的东西为target),使用时,可能会因 “查询不到动态库” 而失败.

        (报错:cannot open shared object file: No such file or directory)——这是因为:

        1. 虽然 g++ 支持用相对路径或-L 来指定 待生成动态库、现有动态库的path,但在shell中、VCS中 执行可执行文件或引用现有动态库时,不支持沿用之前g++ 链接时的相对路径或-L 访问嵌套动态库的 path!(是不是很离谱

          故,最后执行“可执行文件”或“用VCS调用动态库”时,需要把所有用到的动态库都放在 当前路径下.

          可用linux指令 ldd 文件 来检查 此文件所需的动态lib 的路径是否可found!

        2. 所有用到的动态库,都得在target的目录下 或系统lib目录下(上面提到的三个lib目录).

        3. 要单独执行target的话,须cd到target 目录下 执行,不可用相对路径执行
          i.e.:./target ✔️ ;./myfile/target

      • 部分Reference

        gcc/g++ 链接库的编译与链接 —— CSDN surgewong

        GCC 命令行详解 -L 指定库的路径 -l 指定需连接的库名 —— cnblogs

    1.2 Linux下C与C++的文件后缀

    • .c.cc.cpp 后缀的区别

      主要是给compiler识别用的。

      .c 是C文件;

      C++是 .cc.cpp:unix系统用 .cc;非unix系统用 .cpp;其实都是C++文件,实际上可以混用.

    1.3 DPI 是什么

    • 目的

      verilog中有一些内建的系统调用,如:$display(...)sformatf(...) ;那若我想在verilog中调用自定义的C程序咋办?

      可以用 PLI接口,也可以用 VPI 接口,也可以用 DPI接口.

    • PLI、VPI、DPI

      1. PLI (verilog Programming Language Interface) ,是Verilog HDL的simulator environment的一个API协议,可以在verilog中调用C程序. 本来捏是 PLI1.0,但是用起来挺麻烦的,于是优化了一下,进化成了 PLI 2.0 ——VPI.

      2. VPI (Verilog Procedial Interface) ,也是用于 Verilog HDL调用C程序的接口协议,是PLI的新版本,已收录于IEEE 1364,比PLI 1.0调用C程序的方法更简单点,但还是挺麻烦的,于是在VPI上面封装一层,第三个接口出现 ——DPI.

      3. DPI (Direct Programming Interface),比VIP调用C语言更简单,但是少了一些功能性.

      PLI 和 DPI 的内容此处略,后续另文写。它们仨给我最大的感觉就是:

      PLI在verilog中调用最简单的 “helloworld” 的C程序,需要5步:

      1. 写C routine,其中 调用PLI
      2. 把C的function associate 到system task上;
      3. 登记此system task(使env认识它);
      4. 把C程序compile、link一下;
      5. 在HDL中 调用system task来执行 C程序。

      VPI调用 “helloworld” 的C程序,需要4步,少了上面的第二步.

      DPI调用 “helloworld” 的C程序,只需要3步!

      1. 正常写C程序,然后把C程序compile、link一下;
      2. RTL中加:import "DPI-C" function void helloword();
      3. 在RTL中调用即可.

      C程序本来就要编译;RTL中本来就要call程序;四舍五入一下,DPI中call简单的C程序,只需要1步——加个 import ... 就好了,是不是很方便~ ( ̄▽ ̄)"

    2 如何在DPI中调用C++

    • 基本flow

      DPI是不能直接调用C++的,只能调用C程序。

      实现思路 是:将C++程序用C进行封装、编译成动态库,再用DPI进行调用.

      以下【2.1】、【2.2】是循序渐进的.

    2.1 C中如何调用C++程序

    • C中调用C++

      1. 写个C++文件,把C++的函数用 extern "C"{...} 括起来进行定义

        意思是:这段C++代码在编译时,要按C的规则来进行编译,这样后续C程序才能调用这段C++的代码。

      2. C中不需要也不能 include C++的头文件,因为C语法中没有关键字 extern,include后反而会报错;应当:

        1. g++ 把C++文件compile成 .so(动态链接库);
        2. gcc把C文件以及C++的.so一起compile成新的动态库.so 或 可执行文件,根据需要即可(我们要用于后续DPI,因此是compile成 新的动态库).

    2.2 用VCS通过DPI调用C++程序

    • 基本概念

      SV是systemverilog;
      我用的EDA是VCS;
      我VCS用的是 two-step flow(即compile+simulation).

    • 前提精要

      1. DPI 只能call C程序;

        否则 VCS compile不会报错,但仿真会报错找不到RTL内 import的DPI函数…

      2. VCS直接编译C文件来实现DPI时(i.e. 用vcs c文件来直接编译C/C++文件),因为VCS会根据后缀调用gccg++来编译(C文件就调用gcc,C++文件就调用g++),故我们最后用C封装后的程序文件后缀只能为.c,不能写为.cc.cpp

        否则 VCS compile不会报错,但仿真会报错找不到RTL内 import的DPI函数…

        这俩情况,导致我找bug找得半死…

        P.S. 后续我们并不会用VCS来编译C文件,这里只是顺嘴提一下。

      3. 最后对C文件 进行动态链接实现DPI调用的 C动态库时,生成.so必须用gcc 而不是 g++

    • 具体步骤:

      1. C++ code 内的函数 extern 修饰;

      2. C++ code用 g++ 编译链接成动态库 A.so

      3. C程序直接调用C++code中的函数,然后把 C程序、C++的动态库 一起 gcc 封装成 动态库B.so

        注意:不需要也不能 在C code中include C++文件,我们是用C++的动态库进行编译、链接的.

      4. 在SV或Verilog中加语句:import "DPI-C" function int 函数名();

        返回值可以自己调;
        C函数内若是 void返回值类型,就使用SV的task类型而不是funciton.

      5. VCS的compile语句正常写,不需要用VCS来编译C coode,但要使用:vcs -full64

        不然后续仿真会报错:shared library access error: ELFCLASS64,这是因为 C程序默认是64位的,VCS不加-full64 却变成32位的了…

      6. VCS的simulation语句,要调用C程序的动态库 B.so:仿真语句用 simv -sv_lib B,即可完成DPI对C++的调用!

        注意:simv中 -sv_lib 后 不加动态库文件名的后缀

    • 部分Reference

      vcs中systemverilog和c/c++联合仿真 —— CSDN kevindas
      它讲了 如何“g++生成动态链接库”,并如何用VCS实现 “在SV中用DPI调用动态库.so” 的写法.

    3 具体Demo例子【VCS用DPI调用C++】

    • C++内容 ——期望调用的C++程序

      // CPP.cpp
      #include
      using namespace std;
      extern "C"{
      	void helloworld_cpp(){
      	    cout<<"Hello world!\n"<
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
  • C code内容 —— DPI真实调用的内容

    // C.c
    #include
    void helloworld(){
    	helloworld_cpp();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
  • 具体的Makefile Demo(可用!)

    // makefile
    TOP = top_dut.v top_tb.v
    OPT = -sverilog						# if need using SV
    TIMESCALE = "1ns/1ns"
    
    .PHONY: all Clib vcs simv clean
    all: clean Clib vcs simv
    
    Clib:
    	g++ CPP.cpp -m64 -fPIC -shared -o libCPP.so
    	gcc C.c -m64 -fPic -shared -o libC.so -L./ -lCPP 
    	#居然不能用libc.so为文件名,母鸡why...那就用libC.so吧
    vcs:
    	vcs -full64 -debug_access+all -timescale=${TIMESCALE} ${OPT} ${TOP} -q
    simv:
    	simv -lca -l simv.log -sv_lib libC
    clean:
    	rm -rf csrc/
    	rm -rf simv.daidir/
    	rm -rf ucli.key vc_hdrs.h simv.*
    	rm -rf libcpp.so libc.so
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

4 用DPI在SV、C/C++间进行数据交互

4.1 基础概念了解

  • 大端模式和小端模式——多字节数据内不同字节之间的存放优先顺序,字节内是按“大端”的。

    无争议的点:数据都是从低地址往高地址开始放的,只有堆栈式倒着生长的.

    大端模式先存数据的高位部分:即高位在低memory地址,低位在高memory地址;

    小端模式先存数据的低位部分:即低位在低memory地址,高位在高memory地址;

    x86、arm常用小端模式,可以默认按小端去算。

  • C/C++的数据存储格式

    是用 小端模式 去放数据.

    例如:64位数据,占据8个字节;则数据的高位字节的data,再放低位字节的data.

    如下方的C程序例子:64位数据 0 x 1000 _ 0000 _ 0000 _ 000 0x1000\_0000\_0000\_000 0x1000_0000_0000_000,其分配的存储空间是 [ 7012080 , 7012087 ] [7012080, 7012087] [7012080,7012087]的8个字节地址空间,但低地址 7012080 7012080 7012080存储的4个字节内容是data的低位部分(全0),高地址 7012084 7012084 7012084存储的4个字节内容是data的高位部分( 0 x 8000 _ 000 0x8000\_000 0x8000_000).

在这里插入图片描述

注:要用u32的指针去输出u64内部数据各部分;因为指针+1 增加的地址是此指针对应数据类型宽度. i.e. u32指针+1,地址会增加4字节;u64指针+1,地址就增加了8字节.

4.2 SV与C/C++类型的对应

见绿皮书上的表格,如下:

在这里插入图片描述

注意两个问题:

  1. SV没有指针;

  2. DPI不支持返回复杂的数据类型.

    因此,返回复杂的C/C++处理后的结果,不能用返回值,得用 SV的数组 ⇔ \Leftrightarrow C的指针! Demo见下面.

4.3 用SV调用DPI 与C/C++交互数据 【Demo】

  • 用C++的话,别忘了用C进行封装;详细过程在前文,不赘述.

  • C程序

    #include
    #include "synopsys/vcs/include/svdpi.h" //每个人路径不同,自己在VCS的安装目录下找这个文件的路径
    typedef unsigned longlong u64;
    void getdata( const svBitVecVal *data_in, const svBitVecVal *data_out){
        printf("%u\n", *data_in);
        *data_out = 32;
        *(data_out+1) = 64;
        printf("%lld\n", *data_out);
        // set 64 bit data_out as :
        // 0x0000_0040_0000_0020
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
  • 在SV中写

    import "DPI-C" task getdata(input bit[31:0] data_in. output bit[63:0] data_out);
    module tb;
        bit [31:0] data_in;
        bit [63:0] data_out;
        data_in = 129;
        getdata(data_in, data_out);
        $display( $sformatf("the data_in is %u", data_in) );
        $display( $sformatf("the data_out is %u %u", data_out[63:32], data_out[31:0]) );
    endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
  • SV需要用动态库的方式调用C,完成DPI的使用,具体过程上文已提,故不赘述.

成功运行~ ( ̄▽ ̄)"!!!

  • 相关阅读:
    VM16Pro的Win10虚拟机安装Linux子系统Kali
    一文学会使用Bazel构建C++项目
    Flask框架——项目可安装化
    大转盘流程
    Redis 速度快的原因
    《QT+PCL第六章》点云配准icp系列5
    C. The Third Problem Codeforces Round #804 (Div. 2)
    PHP Session
    2. 内核解压-关中断进入svc模式
    机器学习——逻辑回归(LR)
  • 原文地址:https://blog.csdn.net/Hide_in_Code/article/details/126344652