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类型,必须确保兼容。

SystemVerilogC(输入)C(输出)
bytecharchar*
shortintshort intshort int*
intintint*
longintlong long intlong int*
shortrealfloatfloat*
realdoubledouble*
stringconst char*char**
string[N]const char*char**
bitsvBit or unsigned charsvBit* or unsigned char*
logic,regsvLogic or unsigned charsvLogic* or unsigned char*
bit[N:0]const svBitVecVal*svBitVecVal*
reg[N:0]orlogic[N:0]const svLogicVecVal*svLogicVecVal*
open array[]const svOpenArrayHandlesvOpenArrayHandle
chandleconst 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

输出

1
s2=hello,s3=hello

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

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

canvas

nginx代理

© - 2021  大专栏|粤ICP备18064926号-2

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐