在Verilog中,通过VPI可以引用C程序,听说挺复杂的(,当然我不会)。在SV中引入了DPI(direct programming Interface),可以通过在SV中简单的设置,就可以引用C语言。
1. SV简单引用C
先看一个例子
| import "DPI-C" function int factorial(input int i);
program test; initial begin for(int i=0;i<=10;i++) $display("factorial(%d) is %d",i,factorial(i)); end endprogram
int factorial(int i) { if(i<=1) return 1; else return factorial(i-1)*i; }
|
用VCS直接编译这两个文件就行了。
1.1 导入声明
import将C函数引入SV,但使用的数据类型是SV的,这里就需要将C的类型转化成对应的SV数据类型。
如果函数名跟SV中已有的函数冲突,还可以改名,如下:
1 2
| import "DPI-C" new_name=function int factorial(input int i);
|
如果c程序的类型是void,那么可以将C函数映射成task或者返回类型为void的function。
在SV中允许声明子程序的地方都可以导入函数,可以将导入函数理解成定义了一个函数,只不过是用C语言写的。
也可以将import声明放在package中,如后导入package就可以。
1.2 参数方向
在import函数时可以设置的参数的输入输出方向有 input、output,不支持ref类型。
input:一般情况下参数方向是input;
output:当参数是指针(可以对输出数据进行修改)的时候,方向为output。但如果是const指针参数,说明不会改变输入对象,那么参数是input。
1.3 参数类型
通过DPI传递的每个变量都有两个数据类型,一个是C类型,一个是SV类型,必须确保兼容。
SystemVerilog |
C(输入) |
C(输出) |
byte |
char |
char* |
shortint |
short int |
short int* |
int |
int |
int* |
longint |
long long int |
long int* |
shortreal |
float |
float* |
real |
double |
double* |
string |
const char* |
char** |
string[N] |
const char* |
char** |
bit |
svBit or unsigned char |
svBit* or unsigned char* |
logic,reg |
svLogic or unsigned char |
svLogic* or unsigned char* |
bit[N:0] |
const svBitVecVal* |
svBitVecVal* |
reg[N:0]orlogic[N:0] |
const svLogicVecVal* |
svLogicVecVal* |
open array[] |
const svOpenArrayHandle |
svOpenArrayHandle |
chandle |
const void* |
void* |
对于函数的返回类型,SV LRM中规定只能是void、byte、shortint、int、longint、real、shortreal、chandel和string、bit、logic。不能是bit[6:0]这样的向量。
C的结构类型定义在头文件svdpi.h中,比如svBit。如果在C代码中使用了这些类型,那么需要在C中#include svdpi.h
1.4 导入数学库函数
1 2 3 4 5 6 7
| import "DPI-C" function real sin(real a); program test; initial begin for(int j=0;j<=5;j++) $display(sin(j)); end endprogram
|
如上面的方式导入sin函数,可以直接在SV中使用。
2. 连接简单的C子程序
2.1 使用静态变量的计数器
下面是一个7位计数器。
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include <svdpi.h> void counter7_static(svBitVecVal* o, const svBitVecVal* i, const svBit reset, const svBit load) {
static unsigned char count=0; if(reset) count = 0; else if(load) count=*i; else count++; count&=0x7f; *o = count; }
|
在本例中7位的计数器保存在8位的字符型变量中,所以要使最高位无效。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import "DPI-C" counter7_static=function void counter7_static(output bit[6:0] out, input bit[6:0] in,bit reset,bit load);
program tb; bit [6:0] in,out; bit load,reset;
initial begin $monitor("SV:out=%3d,in=%3d,reset=%3d,load=%3d",out,in,reset,load); reset = 0; load=0; in=126; out=42; counter7_static(out,in,reset,load); #10 reset=1; counter7_static(out,in,reset,load); #10 load=1; reset=0; counter7_static(out,in,reset,load); end endprogram
|
输出:
1 2 3
| SV:out= 1,in=126,reset= 0,load= 0 SV:out= 0,in=126,reset= 1,load= 0 SV:out=126,in=126,reset= 0,load= 1
|
2.2 chandle数据类型
上面计数器保存在静态变量中,如果只需要一个计数器,可以这样;但如果需要多个计数器,那么下一个计数器会覆盖之前的计数值,所以在C代码中不要把变量保存在静态变量中。需要动态申请
在SV中chandle类型可以存储一个C/C++指针,这个指针指向对象的类型是任意的,可以是结构体。。。
C中如果返回void*指针类型,那么在SV中对应chandle。
C中的参数是结构体指针类型,那么SV中对应chandle。(这一条不是很确定,可以定义为chandle,也可以如下面第6节中所述)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #include <svdpi.h> #include <malloc.h> typedef struct{ unsigned char cnt; }c7;
void* counter7_new() { c7* c = (c7*)malloc(sizeof(c7)); c->cnt=0; return c; }
void counter7(c7* inst, svBitVecVal* o, const svBitVecVal* i, const svBit reset, const svBit load) {
if(reset) inst->cnt=0; else if(load) inst->cnt=*i; else inst->cnt++; inst->cnt&=0x7f; *o = inst->cnt; io_printf("C:count=%d,i=%d,reset=%d,load=%d\n",*o,*i,reset,load); }
|
上面程序中定义了一个结构体来保存计数器;counter7_new()函数用来动态申请计数器的内存,保证每个计数器唯一。调用counter7函数的时候将相应的计数器指针(inst)作为参数传入其中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| import "DPI-C" function chandle counter7_new(); import "DPI-C" function void counter7(input chandle inst, output bit[6:0] out, input bit[6:0] in, input bit reset, input bit load );
program automatic tb; bit [6:0] in1,in2,out1,out2; bit load,reset,clk; chandle inst1,inst2;
initial begin
inst1=counter7_new(); inst2=counter7_new(); clk=0; reset = 0; load=0; fork forever #5 clk = ~clk; forever @(posedge clk) begin counter7(inst1,out1,in1,reset,load); $display("@ %0t SV:out1=%3d,in1=%3d,reset=%3d,load=%3d\n",$time,out1,in1,reset,load);
counter7(inst2,out2,in2,reset,load); $display("@ %0t SV:out2=%3d,in2=%3d,reset=%3d,load=%3d\n",$time,out2,in2,reset,load); end join_none in1=120; in2=20; @(negedge clk) load=1; @(negedge clk) reset=1; @(negedge clk) reset=0; @(negedge clk) load=0; repeat(5) @(posedge clk); @(negedge clk) $finish; end initial begin $vcdpluson; end endprogram
|
计数器结构体的指针,在SV中对应chandle数据类型。
3. 调用C++子程序
在SV中可以使用DPI调用C\C++子程序,模型的抽象层次不同调用方式不同。
3.1 C++中的计数器
用类实现计数器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #include <svdpi.h> class Counter{ public: Counter() { cnt = 0; } void counter7_signal ( svBitVecVal* count, const svBitVecVal* i, const svBit reset, const svBit load ) { if(reset) cnt=0; else if(load) cnt=*i; else cnt++; cnt &=0x7f; *count = cnt; } private: unsigned char cnt; };
|
3.2 静态方法
1 2 3 4 5 6 7 8 9 10 11 12
| extern "C" void* counter7_new() { return new Counter; } extern "C" void counter7 ( void* inst, svBitVecVal* out, const svBitVecVal* in, const svBit reset, const svBit load ) { Counter* c7=(Counter*) inst; c7->counter7_signal(out,in,reset,load); delete c7; }
|
通过DPI只能调用静态的C、C++方法,即链接时已经存在的方法。C++对象在链接的时候还不存在,那么方法也就不存在。
解决方法:通过创建与C++动态对象和方法通信的静态方法,也就是上面的两个方法。将对象的指针传入函数。
**extern “C”**告诉C++编译器,送入链接器的外部信息应当使用C调用风格,并且不能执行名字调整name mangling。(基本支持函数重载的语言都需要进行name mangling。mangling的目的就是为了给重载的函数不同的签名,以避免调用时的二义性调用。)
可以直接使用第2节中的tb。
3.3 和事务级C++模型通信
事务级通信中用的是方法,而不是信号和时钟。
1.先用C++实现事务级的计数器。
1 2 3 4 5 6 7 8 9 10 11 12
| #include <svdpi.h>
class Counter7{ public: Counter7(){cnt=0;} void reset(){cnt=0;} void load(const svBitVecVal* i){cnt=*i;cnt&=0x7f;} void count(){cnt++;cnt&=0x7f;} int get(){return cnt;} private: unsigned char cnt; };
|
2.将动态方法包装成静态方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| extern "C"{
void* counter7_new(){ io_printf("call new()\n"); return new Counter7; } void counter7_reset(void* inst){ io_printf("call counter7_reset()\n"); Counter7 * c7=(Counter7*) inst; c7->reset(); } void counter7_load(void* inst,const svBitVecVal* i){ io_printf("call counter7_load()\n"); Counter7 * c7=(Counter7*) inst; c7->load(i); } void counter7_count(void* inst){ io_printf("call counter7_count()\n"); Counter7 * c7=(Counter7*) inst; c7->count(); } int counter7_get(void* inst){ io_printf("call counter7_get()\n"); Counter7 * c7=(Counter7*) inst; return c7->get(); } }
|
3.在测试平台中将方法引入
注意get()函数的返回值,它应该返回bit[6:0] 7位数据,但是规定导入函数只能返回void、byte、shortint、int、longint、real、shortreal、chandel和string、bit、logic。不能是bit[6:0]这样的向量。
1 2 3 4 5
| import "DPI-C" function chandle counter7_new(); import "DPI-C" function void counter7_reset(input chandle inst); import "DPI-C" function void counter7_load(input chandle inst,input bit[6:0] i); import "DPI-C" function void counter7_count(input chandle inst); import "DPI-C" function int counter7_get(input chandle inst);
|
4.为了方便使用,将引入的方法封装成类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class Counter7; chandle inst; string id; function new(string str); id=str; inst = counter7_new(); endfunction:new function void reset(); counter7_reset(inst); endfunction:reset function void count(); counter7_count(inst); endfunction:count function load(bit[6:0] i); counter7_load(inst,i); endfunction:load function bit[6:0] get(); bit[6:0] tmp = counter7_get(inst); $display("@%0t : [%s] count is %d",$time,id,tmp); return tmp; endfunction:get endclass:Counter7
|
在测试平台中使用计数器的时候,定义一个Counter7对象,然后调用它的方法就行了。
5.测试平台
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| program automatic tb; bit clk; bit[6:0] out1,out2; Counter7 c1,c2; initial begin forever #5 clk = ~clk; end initial begin clk = 0; c1=new("c1"); c2=new("c2"); fork begin @(posedge clk) c1.reset(); @(posedge clk) c1.load(100); repeat(10) @(posedge clk)begin c1.count(); out1=c1.get(); end end begin @(posedge clk) c2.reset(); @(posedge clk) c2.load(10); repeat(10) @(posedge clk)begin c2.count(); out2=c2.get(); end end join_none repeat(20) @(posedge clk); $finish; end endprogram
|
6.输出
两个计数器并行计数,各记各的,互不干扰。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| call new() call new() call counter7_reset() call counter7_reset() call counter7_load() call counter7_load() call counter7_count() call counter7_get() @25000 : [c1] count is 101 call counter7_count() call counter7_get() @25000 : [c2] count is 11 call counter7_count() call counter7_get() @35000 : [c1] count is 102 call counter7_count() call counter7_get() @35000 : [c2] count is 12 call counter7_count() call counter7_get()
|
4. 共享简单数组
4.1 双状态一维数组
1 2 3 4 5 6 7 8 9 10 11 12
| #include <svdpi.h> #include <veriuser.h>
void fib(svBitVecVal data[20]){ int i; data[0] = 1; data[1] = 1; for( i=2;i<20;i++) { data[i] = data[i-1] + data[i-2]; } }
|
这里的参数是svBitVecVal,没有星号。
1 2 3 4 5 6 7 8 9 10
| import "DPI-C" function void fib(output bit[31:0] data[20]);
program automatic tb; bit[31:0] data[20]; bit[7:0] da; initial begin fib(data); foreach(data[i]) $display("%d : %d",i,data[i]); end endprogram
|
5. 开放数组 open array
开放数组定义在svdpi.h中,在C代码中,可以通过开放数组,操作任何大小的数组。
6. 传递特殊结构
6.1 SV和C之间传递结构体参数
1 2 3 4 5 6 7
| typedef struct{ unsigned char r,g,b; }* s_rgb; void invert(s_rgb rgb){ ... }
|
1 2 3 4 5 6
| typedef struct {bit[7:0] r,g,b;} RGB; import "DPI-C" function void invert(inout RGB rgb); program automatic tb; .. endprogram
|
6.2 传递字符串
从C程序向SV返回字符串。
最简单的方法是字符串返回值;
还有一种是char类型的参数。**
下面程序同时用了这两种方法。
1 2 3 4 5 6 7 8 9 10
| #include "svdpi.h" #include "veriuser.h"
char* test(const char* in,char** out) { static char * s; s=in; *out=in; return s; }
|
1 2 3 4 5 6 7 8 9 10
| import "DPI-C" function string test(input string in,output string out); program automatic tb; string s1 = "hello"; string s2; string s3; initial begin s3 = test(s1,s2); $display("s2=%s,s3=%s\n",s2,s3); end endprogram
|
输出
从输出看一看到,test函数既返回了string值,也修改了传入的参数s2.
7. C也可以调用SV的函数。