SystemVerilog——SV与C的接口
8种机械键盘轴体对比
本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选?
在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
| 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
| 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 <malloc.h>
typedef struct{ // 计数器结构体
unsigned char cnt;
}c7;
void* () { // 返回一个结构体指针,指向计数器
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=%dn",*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=%3dn",$time,out2,in2,reset,load);
// $monitor("@ %0t SV:out1=%3d,in1=%3d,reset=%3d,load=%3dn",$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=%3dn",$time,out1,in1,reset,load);
counter7(inst2,out2,in2,reset,load);
$display("@ %0t SV:out2=%3d,in2=%3d,reset=%3d,load=%3dn",$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调用CC++子程序,模型的抽象层次不同调用方式不同。
3.1 C++中的计数器
用类实现计数器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| 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* ()
{
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
| 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* (){
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 ();
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 <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=%sn",s2,s3);
end
endprogram
|
输出
从输出看一看到,test函数既返回了string值,也修改了传入的参数s2.
7. C也可以调用SV的函数。
canvas
nginx代理
© - 2021 大专栏|粤ICP备18064926号-2