【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、睡眼惺忪;
      看到几个帖子兴冲冲;
      百般实验一场空.
    4. 在SV调用C、C调用C++时,涉及到的输入形参、输出结果的传递,要如何进行,这个地方会 涉及到细节的指针的使用,信息不好搜集,自个儿试错debug很痛苦…

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

    主要是以下几点内容:

    1. g++封装成动态库

    2. SV中使用动态库

    3. SV和C的数据类型转换(实现输入输出数据传递)

      太曲折了!T_T

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中需要用 extern 声明外部的C++函数! ⭐️

      目的是:①声明此函数是来自外部库的;②声明函数的返回值类型!

      因为实际使用中发现,不加在C中用extern声明C++的函数,只要在编译时链接了C++的库,其实也能编译通过而不会报错,但这些外部C++函数就会默认为32位返回值类型;因此,若C++函数事实上是64位返回值类型等,C中未用extern声明,C中调用外部函数时就获取外部函数返回值的高位数据了(高位舍弃了)。

      【血与泪的教训…】

    3. C中不需要也不能 include C++的头文件,因为C语法中无法识别C++的语法,include后反而会报错;应当:

      1. g++ 把C++文件compile成 .so(动态链接库);
      2. gcc把C文件以及C++的.so一起compile成新的动态库.so 或 可执行文件,根据需要即可(我们要用于后续DPI,因此是compile成 新的动态库).
  • C中调用C++函数,输入、输出数据的传递方式

    方法一:以函数形参的形式,给C++传递变量;以返回值的形式,获得计算后的结果,这个思路很简单。

    这里要提的是 方法二
    “用指针传入、传出数据” 的方法中,要注意的地方——指针的使用;算C/C++的基础编程概念,但可能会没注意导致bug的产生:

    • OOP编程时,指针不会忘记new;但 基本数据类型的指针,常常会忘记new,而直接往里塞数据,造成错误…

      e.g.

      #include<iostream>
      using namespace std;
      void getdata(int *addr){
      	*addr = 10;
      }
      void main(){
      	int *p;		// 基本数据类型 指针也得new
      	getdata(p);	// p无法获得10,因为p还没有new或malloc().
      	p = new(int)	// 或 p=(int*)malloc(sizeof(int));
      	getdata(p);	// p可以获得10
      }
      

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++函数用 extern 修饰,即可直接调用C++程序中的函数,然后把 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<iostream>
    using namespace std;
    typedef unsigned long long int u64;
        
    // 可以在 {} 内包含多个C++函数,或者只在.h中声明extern "C" 即可
    extern "C"{
    	 u64 helloworld_cpp(){
    		cout<<"Hello world!\n"<<endl;
            u64 data = 0x1234567891234567;
            return data;
    	}
    }
    
  • C code内容 —— DPI真实调用的内容

    // C.c
    #include<stdio.h>
    typedef unsigned long long int u64;
        
    // 若不用extern声明helloworld_cpp(),编译不报错
    // 但helloworld_cpp() 默认是int返回值,高位数据会丢失!!!
    extern u64 helloworld_cpp();
        
    void helloworld(){
        u64 data;
    	data = helloworld_cpp();
    }
    
  • 具体的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:	#务必用 -full64 !
    	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
    

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

4.1 基础概念了解

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

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

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

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

    x86、arm常用小端模式,故我们得默认按小端去算。

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

    是用 小端模式 去放数据,举个例子吧。

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

    如下方的C程序例子:64位数据 d a t a = 0 x 1234 _ 5678 _ 9 a b c _ d e f 0 data=0x1234\_5678\_9abc\_def0 data=0x1234_5678_9abc_def0
    其现实memory中分配的存储空间是 [ 7012080 , 7012087 ] [7012080, 7012087] [7012080,7012087]的8个字节;但低32位数据(0x9abcdef0),先存,放在起始地址 7012080 7012080 7012080中;高32位数据(0x12345678),后存,放在起始地址 7012084 7012084 7012084中.
    在这里插入图片描述
    注:要用u32的指针去输出u64内部数据各部分;因为指针+1 增加的地址是此指针对应数据类型宽度. i.e. u32指针+1,地址会增加4字节;u64指针+1,地址就增加了8字节.

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

见绿皮书上的表格,如下:
在这里插入图片描述
注意两个问题:

  1. SV没有指针;

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

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

  3. SV的 bit类型 是可以自定义数据位宽的,但C中对应的 svBitVecVal*类型是固定数据尾位宽的——本质是取了宏名的 int*指针类型,因此 SV与C的数据传输,就是要在这两个类型中进行“指针类型强制转换”

    • 点1:

      可以进入 synopsys/vcs/include/svdpi.h 文件,查看 SV数据类型映射到C上的 svBitVecVal*类型 是个啥。

    • 点2:

      bit是SV的二值类型,单bit值只有0、1;
      reg是四值类型,单bit值可以取为 0、1、x、z;
      故用bit类型进行SV与C/C++的传输足以。

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

以下的Demo功能,SV通过形参,调用C++获得不同的64bit的数据!

  • 实现思路:
    1. C++用返回值,与C程序交互;
    2. C使用指针,与SV交互.
4.3.1 C 程序给 SV传 大于32位的数据
  • 用C++的话,别忘了用C进行封装;详细过程在前文,不赘述.

  • C++程序

    #include<iostream>
    using namespace std;
    typedef unsigned long long int u64;
    // compile the C++ function with C rule
    extern "C"{
    	u64 getdata_cpp(int type){
    	    u64 data;
    	    if(type == 0) data = 0x123456789abcdef0;
    	    else if(type == 2)data = 0x0fedcba987654321;
    	    return data;
    	}
    }
    
  • C程序

    #include<stdio.h>
    //每个人路径不同,自己在VCS的安装目录下找这个文件的路径
    #include "synopsys/vcs/include/svdpi.h" 
    typedef unsigned longlong u64;
    
    // declare the C++ function in C
    extern u64 getdata_cpp(int type);
    void getdata_c( const svBitVecVal *type_t, const svBitVecVal *data_t){
        // get 32-bit data from SV
        int type = *type_t;
        // get 64-bit data from C++
        u64 data = getdata_cpp(type);
        // send 64-bit data to SV
        u64 *p = (u64*)data_t;
        *p = data;
        // 错误的写法:
        // data_t = &data;
        //因为SV的结果指针式不会改变的,C里改了没用,SV还是收不到数据.
    }
    
  • 在SV中写

    import "DPI-C" task getdata_c(input bit[1:0] type_t, output bit [63:0] data_t);  		// 千万别漏了SV的数据类型 bit,和输出 output关键字
    module tb;
        bit [1:0] tmp_type;
        bit [63:0] tmp_data;
        getdata(tmp_type, tmp_data);
        $display( $sformatf("the data from C++ is %u", tmp_data) );
    endmodule
    
  • SV需要用动态库的方式调用C,完成DPI的使用,具体过程上文已提,故不赘述.

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

若需要SV与C/C++之间传输128位、甚至更多的数据,都是ok的:

  1. SV端,定义的reg数据类型位宽可以自定义;
  2. C/C++ 端,数据接口是 int* 指针,自行用 “小端模式” 自己算地址,然后把数据取出来就行了。
  • 18
    点赞
  • 66
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
FaceNet是一个人脸识别系统,它使用深度学习算法来生成人脸的嵌入向量,这些向量可以用于人脸识别和验证。FaceNet由Google开发,其算法主要基于卷积神经网络和三元组损失函数。 如果你想使用C++来实现人脸比对,可以使用开源的Dlib库,它提供了一个现成的人脸识别模块,可以用来比对两张人脸的相似度。以下是一个简单的示例程序: ```c++ #include <dlib/image_processing/frontal_face_detector.h> #include <dlib/image_processing.h> #include <dlib/gui_widgets.h> #include <dlib/image_io.h> #include <iostream> #include <vector> #include <string> using namespace dlib; using namespace std; int main(int argc, char** argv) { if (argc < 3) { cout << "Usage: compare_faces <image1> <image2>" << endl; return 0; } frontal_face_detector detector = get_frontal_face_detector(); shape_predictor sp; deserialize("shape_predictor_68_face_landmarks.dat") >> sp; std::vector<matrix<rgb_pixel>> faces; for (int i = 1; i <= 2; ++i) { matrix<rgb_pixel> img; load_image(img, argv[i]); std::vector<rectangle> dets = detector(img); std::vector<full_object_detection> shapes; for (unsigned long j = 0; j < dets.size(); ++j) { full_object_detection shape = sp(img, dets[j]); shapes.push_back(shape); } std::vector<matrix<rgb_pixel>> face_chips; extract_image_chips(img, get_face_chip_details(shapes), face_chips); for (int j = 0; j < face_chips.size(); ++j) { matrix<rgb_pixel> face = face_chips[j]; faces.push_back(move(face)); } } if (faces.size() != 2) { cout << "Error: couldn't detect two faces in the input images." << endl; return 0; } typedef matrix<float, 0, 1> sample_type; typedef radial_basis_kernel<sample_type> kernel_type; typedef decision_function<kernel_type> dec_funct_type; typedef normalized_function<dec_funct_type> funct_type; std::vector<sample_type> samples; for (int i = 0; i < faces.size(); ++i) { matrix<float, 0, 1> face_descriptor = mean(mat(faces[i])); samples.push_back(face_descriptor); } funct_type learned_function; deserialize("dlib_face_recognition_resnet_model_v1.dat") >> learned_function; double distance = length(samples[0] - samples[1]); cout << "Distance between faces: " << distance << endl; return 0; } ``` 在这个示例程序中,我们首先使用Dlib的人脸检测器和面部特征点检测器来提取两张图片中的人脸,并对其进行裁剪和归一化。然后,我们使用FaceNet的预训练模型来计算两张人脸的嵌入向量,并计算它们之间的欧几里得距离作为相似度分数。 请注意,这只是一个简单的示例程序,实际应用中可能需要进行更多的优化和精度控制。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值