SV——SV与C的接口

在Verilog中,通过VPI可以引用C程序,听说挺复杂的(,当然我不会)。在SV中引入了DPI(direct programming Interface),可以通过在SV中简单的设置,就可以引用C语言。

1. SV简单引用C

先看一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// tb.sv
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
//factorial.c
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);
// 改名为new_name

如果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
// tb.sv
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();  // 将结构体指针声明称chandle
import "DPI-C" function void counter7(input chandle inst,// 将结构体指针声明称chandle
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
// $monitor("@ %0t SV:out2=%3d,in2=%3d,reset=%3d,load=%3d\n",$time,out2,in2,reset,load);
// $monitor("@ %0t SV:out1=%3d,in1=%3d,reset=%3d,load=%3d\n",$time,out1,in1,reset,load);
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>

//data : OUTPUT
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
// C代码
typedef struct{
unsigned char r,g,b;
}* s_rgb;
void invert(s_rgb rgb){
...
}
1
2
3
4
5
6
// SV
typedef struct {bit[7:0] r,g,b;} RGB; //将C的结构体定义成SV的结构体
import "DPI-C" function void invert(inout RGB rgb); //使用SV结构体
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

输出

1
s2=hello,s3=hello

从输出看一看到,test函数既返回了string值,也修改了传入的参数s2.

7. C也可以调用SV的函数。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!