• Verilog & Matlab 联合仿真


    一、概述

             在进行仿真时,有时候一部分参考模型(reference model)来自于Matlab,这就需要通过某种方法调用并运行Matlab的参考模型。verilog并不支持直接调用Matlab,但是可以通过DPI接口调用C函数,而Matlab又预留了为C开放的API接口,因此在SV中调用Matlab可以通过如下步骤来实现:

    1. Verilog通过DPI调用C,为C中某些变量赋值
    2. 从verilog中传递来的变量通过适当的类型转换传递到Matlab中
    3.  C通过API启动Matlab并调用Matlab中的函数/模型
    4. Matlab函数运算结果返回C,并通过适当的数据类型转换返回给verilog

    二、verilog 与 C 通信

    1、数据格式转换  

            C中并没有SV中的一些变量类型,例如bit,reg等,因此SV与C通信首先需要将SV中的数据类型转化为C可以识别的类型,部分典型变量类型的对应关系如下所示(这些类型都定义在svdpi.h头文件中):

    SystemVerilogC(input)C(output)
    bytecharchar*
    intintint*
    realdoubledouble*
    reg[N:0]/logic[N:0]const svLogicVecVal*svLogicVecVal*
    bit[N:0]const svBitVecVal*svBitVecVal*
    open array[]svOpenArrayHandlesvOpenArrayHandles

    2、C侧代码编写

             C侧除了要包含一些要用到的基础的头文件(例如stdio.h)以外,还需要包含上述提到的svdpi.h头文件:

        #include "svdpi.h"

            该头文件中定义了SV与C通信的类型转换,以及C对这些数据类型的操作方法。

            另外,C侧代码并不一定需要main函数,Verilog仅把C代码当成task或function调用。例如,在C侧编写函数如下:

    1. int factorial(int i)
    2. {
    3. if(i <= 1) return 1;
    4. else return(i * factorial(i - 1));
    5. }

    3、Verilog侧代码编写

            若想在Verilog中使用编写的C函数,则需要在进行导入(注意导入函数的可见范围):

    import "DPI-C" function int factorial(input int i);

            之后便可以在可见范围内的module等地方使用该函数了,例如:

    1. module test;
    2. int result;
    3. ......
    4. initial begin
    5. result = factorial(5);
    6. ......
    7. end
    8. endmodule

     三、C 与 Matlab 通信

    1、C侧代码编写

    1)启动Matlab引擎

            C可以通过Matlab引擎指针来启动Matlab引擎,该引擎由Matlab软件包含的engine.h提供:

    #include "engine.h"

            由于C启动Matlab需要用到Matlab引擎指针,之后便可以使用engOpen函数获取引擎指针,例如:

    1. Engine *ep;
    2. if(!(ep = engOpen("\0")))
    3. printf("\nCan't start Matlab engine!\n");

            engOpen函数原型如下: 

    1. #include "engine.h"
    2. Engine *engOpen(const char *startcmd);

            其参数为启动指令字符串,若在Windows环境下,则启动指令必须为空,在Linux环境下,启动指令为空时在当前主机启动,若启动指令为主机名,则在指定主机上启动,若为其它Matlab指令字符串,则Matlab会在启动时执行该指令。该函数返回Engine指针,若启动失败则返回NULL。

    2)定义数据类型

            此外,Matlab中数据以矩阵的形式存储,因此还要包含定义矩阵类型以及操作方法的头文件matrix.h:

    #include "matrix.h"

            Matlab中以矩阵形式存储数据,将C中的变量传递给Matlab(或反过来)时,需要定义Matlab能够识别的矩阵形式的变量,即mxArray类型,通常定义mxArray类型变量为指针变量,例如:

    mxArray *mxarr_ptr = NULL;

            在定义完mxArray类型指针变量之后,可能还需要指定其大小和类型,需要用到mxCreateDoubleMatrix函数,该函数原型如下:

    1. #include "matrix.h"
    2. mxArray *mxCreateDoubleMatrix(mwSize m, mwSize n, mxComplexity ComplexFlag);

            其中,第一个参数和第二个参数代表创建m行n列的矩阵空间,第三个参数指定矩阵为实矩阵(mxREAL)还是复矩阵(mxCOMPLEX)。

    3)C向Matlab传输数据

            结合前一节可以知道,若想向Matlab传递数据,首先应将C中普通类型的变量赋值到mxArray类型的变量中,此时需要用到mxSetPr函数,该函数原型如下:

    1. #include "matrix.h"
    2. void mxSetPr(mxArray *pm, double *pr);

            其中,第一个参数为mxArray类型的指针,第二个参数为double类型的指针。(在Matlab2018a版本以后,mxSetPr函数不再被建议使用,而应该使用mxSetDoubles函数)

            此外,当double类型指针未被分配空间时,则首先需要使用mxCalloc函数对其动态分配内存空间(注意不能使用calloc或malloc函数),其原型如下:

    1. #include "matrix.h"
    2. #include
    3. void *mxCalloc(mwSize n, mwSize size);

            其中,第一个参数为分配内存的单元数量,第二个参数为每个单元的大小(通常搭配sizeof函数使用),如果成功,函数返回动态内存的起始位置,否则返回NULL。

            接下来就需要将mxArray类型的变量传递至Matlab中,此时需要用到engPutVariable函数,该函数原型如下:

    1. #include "engine.h"
    2. int engPutVariable(Engine *ep, const char *name, const mxArray *pm);

            其中,第一个参数为引擎指针,第二个参数为Matlab中变量的名字,第三个参数为mxArray类型变量的指针,当操作成功时返回1,否则返回0

      需要注意的是,如果指定的名字在Matlab中不存在,则会在Matlab中创建该名字的变量并为其赋值,如果该名字已存在,则会将原变量替换为新变量,此外,传递的变量最大为2GB。

    4)C调用Matlab程序

            Matlab提供了从C传递指令的engEvalString函数,其原型如下所示:

    1. #include "engine.h"
    2. int engEvalString(Engine *ep, const char *string);

            其中,第一个参数为引擎指针,第二个参数为指令字符串,若引擎关闭或指针为空则返回1,否则返回0,即使Matlab并不能识别该指令。

            通过该函数,我们便可以像在Matlab console中执行指令一样调用Matlab函数(.m文件),但需要注意的是,在调用函数或模型前,首先需要将Matlab的工作路径设置正确,例如:

    1. //--假设在/sv_matlab/demo/目录下存放有func_add.m文件,用于将两个数相加
    2. engEvalString("path('/sv_matlab/demo/',path);");
    3. //--在C中直接调用上述函数将两数相加,前提是Matlab中已经有a,b两个变量
    4. engEvalString("func_add(a,b)");

    5)C从Matlab获取数据

            我们需要先从Matlab获取变量至C中的mxArray类型变量中,之后再将mxArray类型变量中的数据提取到C的普通类型变量中。首先,我们需要使用engGetVariable函数获取mxArray变量,该函数原型如下:

    1. #include "engine.h"
    2. mxArray *engGetVariable(Engine *ep, const char *name);

            其中,第一个参数为引擎指针,第二个参数为Matlab中变量名字的字符串,当指定的名称字符串不存在时,函数返回NULL,否则返回指向该变量的mxArray指针。

            之后,我们需要使用mxGetPr函数从mxArray类型变量中获取值,该函数原型如下:

    1. #include "matrix.h"
    2. mxDouble *mxGetPr(const mxArray *pm);

            其参数为mxArray类型变量的指针,如果指定的mxArray类型变量的指针为NULL,则返回函数NULL,否则返回指向mxArray中存储数据的指针。

    6)释放内存

            在C和Matlab处理完相应的数据之后,可能需要为一些变量之前分配的空间释放其内存以防止内存泄漏。

            使用mxFree函数释放由mxCalloc分配的内存,其函数原型如下:

    1. #include "matrix.h"
    2. void mxFree(void *ptr);

            使用mxDestroyArray函数释放由mxCreateDoubleMatrix分配的内存,其函数原型如下:

    1. #include "matrix.h"
    2. void mxDestroyArray(mxArray *pm);

    7)关闭Matlab引擎

             在处理完所有数据并释放完成内存后,可以使用前面提到的engEvalString函数来关闭Matlab引擎,例如:

    engEvalString(ep,"close");
    

    2、Matlab侧代码编写

            对于Verilog调用Simulink Model进行联合仿真的情况,其中一个方法就是,可以将Matlab中所有的变量,无论是输入还是输出,均打包成一个函数包,这样我们就可以通过engEvalString函数来调用整个模型。

    四、Verilog & Matlab交互示例

            我们以从verilog中向Matlab中传递两个变量并将两个变量在Matlab中相加后返回给Verilog为例,再梳理一边该流程。首先是Verilog侧代码(这里用的是SV):

    1. `timescale 1ns/1ps
    2. import "DPI-C" function real func_add(const real a, const real b);
    3. module test;
    4. real val1;
    5. real val2;
    6. real result;
    7. initial begin
    8. val1 = 0.3;
    9. val2 = 1.1;
    10. result = func_add(val1, val2); //调用C函数
    11. $display("SV got result = %0f", result);
    12. #10;
    13. $finish();
    14. end
    15. endmodule

            然后是C侧代码:

    1. #include
    2. #include
    3. #include "svdpi.h"
    4. #include "engine.h"
    5. #include "matrix.h"
    6. double func_add(double a, double b)
    7. {
    8. double *val1;
    9. double *val2;
    10. double *result;
    11. val1 = (double *)mxCalloc(1, sizeof(double));
    12. val2 = (double *)mxCalloc(1, sizeof(double));
    13. *val1 = a;
    14. *val2 = b;
    15. mxArray = *mxarr_val1 = mxCreateDoubleMatrix(1, 1, mxREAL);
    16. mxArray = *mxarr_val2 = mxCreateDoubleMatrix(1, 1, mxREAL);
    17. mxArray = *mxarr_result = NULL;
    18. Engine *ep;
    19. if(!(ep = engOpen("\0")))
    20. printf("\nCan't start Matlab engine!\n");
    21. else{
    22. engEvalString("path('/sv_matlab/demo/',path);");
    23. mxSetPr(mxarr_val1, val1);
    24. mxSetPr(mxarr_val2, val2);
    25. engPutVariable(ep, "a", mxarr_val1);
    26. engPutVariable(ep, "b", mxarr_val2);
    27. engEvalString(ep, "result = func_demo(a,b)");
    28. mxarr_result = engGetVariable(ep, "result");
    29. result = mxGetPr(mxarr_result);
    30. return *result;
    31. }
    32. }

            最后是Matlab侧代码:

    1. function result = func_demo(a, b)
    2. result = a + b;
    3. return

    因为涉及到调用C函数,因此在跑仿真之前需要先对C文件进行编译,例在/sv_matlab/demo/c目录下有自己编写的C函数,则需要按如下方式对其编译:

    1. gcc demo/c/func_add.c -o demo/c/libdpi.so \
    2. -I /appl/tools/cadence/XCELIUM2009/tools.lnx86/inca/include/ \
    3. -std=c99 -fPIC -shared -leng -lmx -lmex

    其中参数:

    -o 指定生成的结果文件,结果文件名为libdpi.so,SV将默认查找这个库内的C函数

    -I 指定编译包含的头文件库(这里要用到包含svdpi.h的路径),由仿真工具提供,这里以XCELIUM为例

    -fPIC 告诉编译器产生与位置无关代码

    -shared 表示生成库能被别的程序链接

    -leng -lmx 和 -lmex 编译matlab库所需的参数,主要用于编译和链接

    在编译完成之后,便可以启动SV的仿真了,如果C函数没有发生变化,则重复SV侧仿真无需再次编译C函数。

     

  • 相关阅读:
    python模拟:实现抛硬币Python实现模拟抛硬币的随机结果
    ArkTS编程语法基础,让你成为HarmonyOS开发高手
    【RCNN系列】Faster RCNN
    selenium鼠标操作方法
    Linux ARM平台开发系列讲解(IIC) 2.7.3 I2C设备驱动分析
    利用Redis实现向量相似度搜索:解决文本、图像和音频之间的相似度匹配问题
    Spring MVC的控制器是不是单例模式
    简述linux系统中软件包管理系统
    Azure + React + ASP.NET Core 项目笔记一:项目环境搭建(一)
    【无人机】太阳能伪卫星VoLTE无人机设计(Matlab代码实现)
  • 原文地址:https://blog.csdn.net/qq_21842097/article/details/127923559