一、概述
?????????在進行仿真時,有時候一部分參考模型(reference model)來自于Matlab,這就需要通過某種方法調用并運行Matlab的參考模型。verilog并不支持直接調用Matlab,但是可以通過DPI接口調用C函數,而Matlab又預留了為C開放的API接口,因此在SV中調用Matlab可以通過如下步驟來實現:
- Verilog通過DPI調用C,為C中某些變量賦值
- 從verilog中傳遞來的變量通過適當的類型轉換傳遞到Matlab中
- ?C通過API啟動Matlab并調用Matlab中的函數/模型
- Matlab函數運算結果返回C,并通過適當的數據類型轉換返回給verilog
二、verilog 與 C 通信
1、數據格式轉換??
????????C中并沒有SV中的一些變量類型,例如bit,reg等,因此SV與C通信首先需要將SV中的數據類型轉化為C可以識別的類型,部分典型變量類型的對應關系如下所示(這些類型都定義在svdpi.h頭文件中):
SystemVerilog | C(input) | C(output) |
---|---|---|
byte | char | char* |
int | int | int* |
real | double | double* |
reg[N:0]/logic[N:0] | const svLogicVecVal* | svLogicVecVal* |
bit[N:0] | const svBitVecVal* | svBitVecVal* |
open array[] | svOpenArrayHandle | svOpenArrayHandles |
2、C側代碼編寫
?????????C側除了要包含一些要用到的基礎的頭文件(例如stdio.h)以外,還需要包含上述提到的svdpi.h頭文件:
#include "svdpi.h"
????????該頭文件中定義了SV與C通信的類型轉換,以及C對這些數據類型的操作方法。
????????另外,C側代碼并不一定需要main函數,Verilog僅把C代碼當成task或function調用。例如,在C側編寫函數如下:
int factorial(int i)
{if(i <= 1) return 1;else return(i * factorial(i - 1));
}
3、Verilog側代碼編寫
????????若想在Verilog中使用編寫的C函數,則需要在進行導入(注意導入函數的可見范圍):
import "DPI-C" function int factorial(input int i);
? ? ? ? 之后便可以在可見范圍內的module等地方使用該函數了,例如:
module test;
int result;
......
initial begin
result = factorial(5);
......
end
endmodule
?三、C 與 Matlab 通信
1、C側代碼編寫
1)啟動Matlab引擎
????????C可以通過Matlab引擎指針來啟動Matlab引擎,該引擎由Matlab軟件包含的engine.h提供:
#include "engine.h"
????????由于C啟動Matlab需要用到Matlab引擎指針,之后便可以使用engOpen函數獲取引擎指針,例如:
Engine *ep;
if(!(ep = engOpen("\0")))printf("\nCan't start Matlab engine!\n");
????????engOpen函數原型如下:?
#include "engine.h"
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函數,該函數原型如下:
#include "matrix.h"
mxArray *mxCreateDoubleMatrix(mwSize m, mwSize n, mxComplexity ComplexFlag);
????????其中,第一個參數和第二個參數代表創建m行n列的矩陣空間,第三個參數指定矩陣為實矩陣(mxREAL)還是復矩陣(mxCOMPLEX)。
3)C向Matlab傳輸數據
????????結合前一節可以知道,若想向Matlab傳遞數據,首先應將C中普通類型的變量賦值到mxArray類型的變量中,此時需要用到mxSetPr函數,該函數原型如下:
#include "matrix.h"
void mxSetPr(mxArray *pm, double *pr);
????????其中,第一個參數為mxArray類型的指針,第二個參數為double類型的指針。(在Matlab2018a版本以后,mxSetPr函數不再被建議使用,而應該使用mxSetDoubles函數)
????????此外,當double類型指針未被分配空間時,則首先需要使用mxCalloc函數對其動態分配內存空間(注意不能使用calloc或malloc函數),其原型如下:
#include "matrix.h"
#include <stdlib.h>
void *mxCalloc(mwSize n, mwSize size);
????????其中,第一個參數為分配內存的單元數量,第二個參數為每個單元的大小(通常搭配sizeof函數使用),如果成功,函數返回動態內存的起始位置,否則返回NULL。
????????接下來就需要將mxArray類型的變量傳遞至Matlab中,此時需要用到engPutVariable函數,該函數原型如下:
#include "engine.h"
int engPutVariable(Engine *ep, const char *name, const mxArray *pm);
????????其中,第一個參數為引擎指針,第二個參數為Matlab中變量的名字,第三個參數為mxArray類型變量的指針,當操作成功時返回1,否則返回0
??需要注意的是,如果指定的名字在Matlab中不存在,則會在Matlab中創建該名字的變量并為其賦值,如果該名字已存在,則會將原變量替換為新變量,此外,傳遞的變量最大為2GB。
4)C調用Matlab程序
????????Matlab提供了從C傳遞指令的engEvalString函數,其原型如下所示:
#include "engine.h"
int engEvalString(Engine *ep, const char *string);
? ? ? ??其中,第一個參數為引擎指針,第二個參數為指令字符串,若引擎關閉或指針為空則返回1,否則返回0,即使Matlab并不能識別該指令。
????????通過該函數,我們便可以像在Matlab console中執行指令一樣調用Matlab函數(.m文件),但需要注意的是,在調用函數或模型前,首先需要將Matlab的工作路徑設置正確,例如:
//--假設在/sv_matlab/demo/目錄下存放有func_add.m文件,用于將兩個數相加
engEvalString("path('/sv_matlab/demo/',path);");
//--在C中直接調用上述函數將兩數相加,前提是Matlab中已經有a,b兩個變量
engEvalString("func_add(a,b)");
5)C從Matlab獲取數據
????????我們需要先從Matlab獲取變量至C中的mxArray類型變量中,之后再將mxArray類型變量中的數據提取到C的普通類型變量中。首先,我們需要使用engGetVariable函數獲取mxArray變量,該函數原型如下:
#include "engine.h"
mxArray *engGetVariable(Engine *ep, const char *name);
????????其中,第一個參數為引擎指針,第二個參數為Matlab中變量名字的字符串,當指定的名稱字符串不存在時,函數返回NULL,否則返回指向該變量的mxArray指針。
????????之后,我們需要使用mxGetPr函數從mxArray類型變量中獲取值,該函數原型如下:
#include "matrix.h"
mxDouble *mxGetPr(const mxArray *pm);
????????其參數為mxArray類型變量的指針,如果指定的mxArray類型變量的指針為NULL,則返回函數NULL,否則返回指向mxArray中存儲數據的指針。
6)釋放內存
????????在C和Matlab處理完相應的數據之后,可能需要為一些變量之前分配的空間釋放其內存以防止內存泄漏。
????????使用mxFree函數釋放由mxCalloc分配的內存,其函數原型如下:
#include "matrix.h"
void mxFree(void *ptr);
????????使用mxDestroyArray函數釋放由mxCreateDoubleMatrix分配的內存,其函數原型如下:
#include "matrix.h"
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):
`timescale 1ns/1ps
import "DPI-C" function real func_add(const real a, const real b);
module test;real val1;real val2;real result;initial beginval1 = 0.3;val2 = 1.1;result = func_add(val1, val2); //調用C函數$display("SV got result = %0f", result);#10;$finish();end
endmodule
????????然后是C側代碼:
#include <stdlib.h>
#include <stdio.h>
#include "svdpi.h"
#include "engine.h"
#include "matrix.h"
double func_add(double a, double b)
{double *val1;double *val2;double *result;val1 = (double *)mxCalloc(1, sizeof(double));val2 = (double *)mxCalloc(1, sizeof(double));*val1 = a;*val2 = b;mxArray = *mxarr_val1 = mxCreateDoubleMatrix(1, 1, mxREAL);mxArray = *mxarr_val2 = mxCreateDoubleMatrix(1, 1, mxREAL);mxArray = *mxarr_result = NULL;Engine *ep;if(!(ep = engOpen("\0")))printf("\nCan't start Matlab engine!\n");else{engEvalString("path('/sv_matlab/demo/',path);");mxSetPr(mxarr_val1, val1);mxSetPr(mxarr_val2, val2);engPutVariable(ep, "a", mxarr_val1);engPutVariable(ep, "b", mxarr_val2);engEvalString(ep, "result = func_demo(a,b)");mxarr_result = engGetVariable(ep, "result");result = mxGetPr(mxarr_result);return *result;}
}
????????最后是Matlab側代碼:
function result = func_demo(a, b)result = a + b;
return
因為涉及到調用C函數,因此在跑仿真之前需要先對C文件進行編譯,例在/sv_matlab/demo/c目錄下有自己編寫的C函數,則需要按如下方式對其編譯:
gcc demo/c/func_add.c -o demo/c/libdpi.so \-I /appl/tools/cadence/XCELIUM2009/tools.lnx86/inca/include/ \-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函數。