SV 12 SV与C语言的接口


前言

SV使用DPI直接编程接口,能更简单地连接C、C++。一旦你声明或者使用 import 语句导入了一个C子程序,可以像调用SV子程序一样调用它,C代码也可以调用SV的子程序。
前半部分以数据为中心,后半部分以控制为中心。


1 传递简单的数值

1.1 传递整数和实数类型

SV调用C语言子程序factorial

import "DPI-C" function int factorial(input int i);
program automatic test;
   initial begin
      for (int i = 1; i<=10; i++)
          $display("%0d != %0d", i, factorial(i));
   end
endprogram
C语言factorial函数
int factorial (int i) {
  if(i<=1) return 1;
  else return i* factorial(i-1);
}

1.2 导入声明 import

import 声明定义了C任务和函数的原型,但是用的是SV的数据类型,带有返回值的C函数会被映射成一个SV函数。 void类型的C函数则被映射成一个SV任务或者void函数。
如果C函数名和SV的命名冲突,可以在导入时赋予新的函数名。
C函数expect映射到SV的fexpect,expect是全局有效,fexpect是SV中局部有效。
子程序不可以被重载,例如,不能在expect函数中导入一次实型参数,然后再导入一次整型参数。

改变导入函数的名字
program automatic test;
   // 改变C函数名"test""my_test"
   import "DPI-C" test=function void my_test();
   initial begin
   // C函数与关键词同名,需要修改函数名
   import "DPI-C" \expect=function int fexpect();
   ...
       if(actual != fexpect()) $display("ERROR");
   ...
   end
endprogram

1.3 参数方向

导入的C子程序,缺省的情况,参数的方向是 input(数据从SV流向C函数);支持 output和inout,不支持 ref 。
为了减少C代码中的漏洞,可以将输入参数定义为const,一旦对输入变量进行写操作,C语言编译器就会报错。

C语言factorial函数
int factorial (const int i) {
  if(i<=1) return 1;
  else return i* factorial(i-1);
}

1.4 参数类型

通过DPI传递的每个变量有两个相匹配的定义,一个是SV,一个是C语言。SV仿真器不能比较数据类型,因为它无法读取C代码。
在这里插入图片描述
SV的bit类型映射到C语言svBit,

1.5 导入 数学库 函数

直接调用C语言数学函数库中的多个函数,而不需要使用C封装wrapper,这样就减少了需要编写的代码量。

import "DPI-C" function real sin(input real r);
...
initial $display("sin(0)=%f",sin(0.0));

2 连接简单的C子程序

2.1 使用静态变量的计数器

# include<svdpi.h>  // SV DPI结构和方法的定义
void counter7(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;  // 加载数值, i是双状态7bit位宽的变量
              else          count++;    // 计数
              count &=0x7f;             //最高位清0
              * o = count; // 
}

使用静态存储的7位计数器的测试平台

import "DPI-C" function void counter7(output bit[6:0] out,
                                      input  bit[6:0] in,
                                      input  bit reset, load);
program automatic counter;
    bit[6:0] out, in;
    bit      reset, load;
    initial begin
      $monitor("SV: out=%3d, in=%3d, reset=%0d, load=%0d\n", out, in, reset, load);
      reset = 0;
      load  = 0;
      in    = 126;
      out   = 42;
      counter7(out, in, reset, load);
      #10 reset = 1;
      counter7(out, in, reset, load); //复位
      ...
    end
endprogram                                      

2.2 chandle数据类型

chandle数据类型 允许你在SV代码中存储一个C、C++指针。

# include<svdpi.h>
# include<malloc.h>
# include<veriuser.h> //io_print 
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*count,
               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;            // 最高位置0
      * count = inst -> cnt;         // 赋值给输出变量
      io_print("C: count=%d, i=%d, reset=%d, load=%d\n", *count, *i, reset, load);
      这个子程序再同时调试 C 和 SV 代码的时候有用
      }
该计数器测试平台和使用静态变量的计数器测试平台相比, 首先,该计数器在使用前需要创建,其次计数器在时钟边沿上升调用,在时钟下降沿加载激励,以免避免信号竞争。

2.3 值得压缩 packed

字符串“DPI-C”表明你在使用压缩值的表示方式。
出于性能的考虑,SV仿真器可能 不会再调用了一个DPI函数之后屏蔽变量未使用的高位,所以SV变量的值可能会产生错误,在C语音中需要确保这些变量的正确使用。
如果需要在 位 bit 和 字 word之间进行转换,便可以使用宏 SV_PACKED_DATA_NELEMS

2.4 四状态数值

在这里插入图片描述

2.5 从双状态数值转换到四状态数值

SV导入声明中把双状态类型改为四状态类型,如修改 bit 和 int 类型为 logic 和integer。
任何对这些参数的引用都需要使用 .aval 前缀以便正确地访问数据。
当从四状态变量中读取数据的时候,需要检查 bval 位是否存在X或Z值。

3 调用C++程序

3.1 C++中的计数器

3.2 静态方法

DPI 只能调用 静态的C或 C++方法,即在连接时已经存在的方法。但是C++的方法,在运行时还不存在。
解决方法: 创建可以与 C++动态对象 和 方法通信 的静态方法,

静态方法和链接
extern "C" void* counter7_new()  
代码extern “C” 告诉C++编译器,送入链接器的外部信息应当使用C调用风格,并不能执行名字调整。
{
    return new Counter7;
}
// 调用一个计数器实例,并传递信号值
extern "C" void Counter7( void*        inst,
                          svBitVecVal* i,
                          const svBitVecVal*i,
                          const svBit reset,
                          const svBit load)
{
    Counter7 * c7 = (Counter7 *) inst;
    c7 -> counter7_signal(count, i, reset, load);
}

从测试平台的角度来看,C++计数器看起来就像每一个实例都独立保存计数值的计数器。

3.3 和事务级(transaction level)C++ 模型通信

当需要 创建复杂设备的模型 的时候,例如 处理器 和 网络设备,使用事务级通信会使仿真的速度加快。
使用方法通信的C++计数器

C++ 事务级计数器的静态封装 wrapper

4 共享简单数组

SV 和 C 中传递 标量和向量。C模型 可能会读入一个数组,执行一些计算然后返回另一个数组作为执行结果。

4.1 一维数组—双状态 p345

4.2 一维数组—四状态

5 开放数组 open array

在SV和C之间共享数组的时候。

  1. 为了得到最快的仿真速度,反向工程的方式分析出数组在SV的存储方式,在C中根据数组的内存映射方式进行操作。 容易出错,有任何一个数组的大小由变化,必须重寻编写和调试C代码。
  2. 更稳妥的方法是使用 “开放数组 open array” 和 对应的SV子程序 操作他们。

5.1 基本的开放数组

SV 和 C 使用开放数组传递一个简单的数组,SV import 语句中使用空白的【】来表明要传递的是一个开放数组。

调用带有开放数组的C子程序的测试平台
import "DPI-C" function void fib_oa(output bit [31:0] data[]);
program automatic test;
    bit [31:0] data [20];
    initial begin
       fib_oa(data);
       foreach (data[i])
           $display(i, data[i]);
    end
endprogram
使用基本开放数组的C代码
void fib_oa( const svOpenArrayHandle data_oa){
    int i, *data;
    data = (int *) svGetArrayPtr(data_oa);
    data[0] = 1;
    data[1] = 1;
    for (i=2; i<=20; i++)
        data[i] = data[i-1] + data[i-2];
}

5.2 开放数组的方法

svdpi.h 中定义了很多可以访问开放数组的内容和范围 的DPI方法。
仅对定义位 svOpenArrayHadle 的开放数组句柄起作用,而不适用于svBitVecVal 或 svLogicVecVal类型的指针。
在这里插入图片描述

5.3 传递大小 未定义的开放数组

C函数使用了svLow 和 svHigh 方法来确定数组的方位。

调用参数为 多维开放数组的C函数的测试平台
import "DPI-C" function void mydisplay(inout int h[][]);
program automatic test;
   int a[6:1][8:3];  此处字范围由高到低
   ...

方法svLow(handle, dimension) 返回指定位数的最小索引值。如对[6:1]数组,svLow(h,1)返回1;
svHigh(h,1)返回6.
svLeft 和 svRight 方法返回数组声明中的左边索引值和右边索引值
svGetArrElemPtr2 方法的调用返回一个指向 二维数组某个元素的指针。

参数为多维开放数组的C代码
void mydisplay(const svOpenArrayHandle h){

}

5.4 DPI中压缩(packed)的开放数组

一个开放数组 被视为 拥有一个压缩的维度 和 一个或多个非压缩的维度

压缩开放数组的测试平台
import "DPI-C" function void view_pack(input bit[63:0] b64[]);
program automatic test;
    bit [1:0][0:3][6:-1] bpack[9:1];
...
使用压缩开放数组的C代码
void view_pack(const svOpenArrayHandle h){
   int i;
   ...
}

svGetArrayElemPtr1 类型强制转换成 long long int 类型

6 共享复合类型

类属性的内存映射方式,SV和C不一致,不能直接共享对象
解决方法: 为了达到共享对象,必须在两边创建相似的结构,并且使用压缩和解压缩方法对这两种格式进行转换。

6.1 SV和C之间传递结构

C将char视为有符号变量,

 共享结构的C代码
 typedef struct { 
     unsigned char b, g, r; // x86小尾序
     // unsigned char r, g, b;  //SPARC格式
} * p_rgb;
 共享结构的 测试平台
 typedef struct packed {bit[7:0] r,g,b;} RGB_T;
 import "DPI-C" function void invert(inout RGB_T pstruct);
 program automatic test;
 class RGB;
    rand bit[7:0] r, g, b;
    function void display(string preix="");
       $display("%s RGB = %x, %x, %x", prefix, r, g, b);
    endfunction
    // 将类成员压缩到一个结构中
    function RGB_T pack();
       pack.r=r; pack.g=g; pack.b=b;
    endfunction
    // 将结构解压后赋值给类成员
    function void unpack(RGB_T pstruct);
       r=pstruct.r; g=pstruct.g; b=pstruct.b;
    endfunction
    initial begin
        RGB pixel;
        RGB_T pstruct;

        pixel = new();
        repeat(5) begin
             assert(pixel.randomize()); 创建随机像素
             pixel.display("\nSV:before"); 打印像素值
             pstruct = pixel.pack(); 转换为结构
             invert(pstruct); 调用C函数将位取反
             pixel.unpack(pstruct); 把结构解压后赋值给类
             pixel.display("SV: after");
        end
    end
endprogram

6.2 SV和C之间传递字符串

为结构的符合值传递一个字符串

7 纯导入方法和关联导入方法

导入方法 分为 纯导入方法、关联导入 和 通用导入
纯函数 仅依赖于他们的输入,更外部环境没有交互,不访问全局或静态变量,不会进行文件操作,不跟函数体以外的事务如操作系统、进程、共享内存和套接字等有交互。

调用关联导入方法时,需要记录调用的上下文环境,给仿真器带来额外的开支。—不建议

8 在C中与SV通信

在C中调用SV的方法

8.1 一个简单的导出方法

导出一个SV函数
module block;
    import "DPI-C" context function void c_display();
    export "DPI-C" function sv_display;  禁止带任何返回值声明或者参数,也不能加函数声明使用的空阔号。

    function void sv_display();
    endfunction
endmodule
在C中调用一个SV导出函数
extern void sv_display();
void c_display() {
    io_printf("c:c_display");
    sv_display();
}

8.2 调用SV函数的C函数

函数不耗时

SV模块
module memory;
   import "DPI-C" function read_file(string fname);
   export "DPI-C" function mem_build; 没有类型定义或者参数
   ..
   initial read_file("mem.dat");
   
   function mem_build(input int size);
   endfunction
endmodule
C代码
extern void mem_build(int);
int read_file(char * fname){
 ...
}

8.3 调用SV任务的C任务

真实的内存模型 有读写之类消耗时间的操作,需使用任务建模。

SV模块
module memory;
   import "DPI-C" function read_file(string fname);
   export "DPI-C" task mem_read;
   export "DPI-C" task mem_write;
   export "DPI-C" function mem_build; 没有类型定义或者参数
   ..
   initial read_file("mem.dat");
   
   function mem_build(input int size);
   endfunction
   task mem_read(input int addr, output int data);
        #20 data = mem[addr];
   endtask
   task mem_write(input int addr, output int data);
        #10  mem[addr] = data;
   endtask
endmodule
C代码
extern void mem_build(int);
extern void mem_write(int,int);
extern void mem_read(int,int*)
int read_file(char * fname){
     int cmd;
     FILE*file;

     file = fopen(fname, "r");
     while(!feof(file)) {
        cmd = fgetc(file);
        switch(cmd){
           case 'M':{
               int hi;
               fscanf(file, "% d %d", &hi);
               mem_build(hi);
               break;
           }
            case 'R':{
               int addr, data, exp;
               fscanf(file, "%c %d %d", &cmd, &addr, &data);
               mem_read(addr, &exp);
               if (data!=exp)
                    io_printf("C: Data=%d, exp=%d\n",data,exp);
               break;
           }
           case 'W':{
               int addr, data;
               fscanf(file, "%c %d %d", &cmd, &addr, &data);
               mem_write(addr, data);
               break;
           }
        }
     
     }
     fclose(file);
}

在这里插入图片描述

8.4 调用对象中的方法 P357

你可以导出SV方法,但是定义在类中的方法除外。类似限制SV导入静态C函数。
解决方法: 在SV和C代码之间传递一个对象引用。跟C指针不同的是,SV句柄不能通过DPI传递。定义一个句柄数组,然后再两种语言之间传递数组的索引。

8.5 上下文context的含义

导入函数的上下文是该函数定义所在的位置,比如$unit、模块、program或者package作用域scope,
把一个函数导入两个不同的作用域,对应的C代码会一句import语句所在位置的上下文执行。

module top;
    import "DPI-C" context function void c_display();
    export "DPI-C" function sv_display;  

    block b1();
    initial c_display();
    
    function void sv_display();
        $display("SV:top");
    endfunction
endmodule

module block;
    import "DPI-C" context function void c_display();
    export "DPI-C" function sv_display;  禁止带任何返回值声明或者参数,也不能加函数声明使用的空阔号。
    initial c_display();
    function void sv_display();
          $display("SV:block");
    endfunction
endmodule

在这里插入图片描述

8.6 设置 导入函数的作用域

如同SV代码可以再局部作用域调用方法,导入的C方法也可以再它默认的上下文之外调用方法。
使用 svGetScope 方法可以获得当前作用域的句柄,然后就可以再对 svGetScope的调用中使用该句柄,是的C代码认为它处在另一个上下文中。

获取和设置上下文的C代码
extern void sv_display();
svScope my_scope;

void save_my_scope() {
   my_scope=svGetScope();
}

void c_display() {
  // 打印当前作用域
  io_printf("\nC: c_display called from scope %s\n", svGetNameFromScope(svGetScope()));
  // 设置新的作用域
  svGetScope(my_scope);
  io_printf("C: calling %s.sv_display\n", svGetNameFromScope(svGetScope()));
  sv_display();
}
调用获取和设置上下文方法的模块
module block;
    import "DPI-C" context function void c_display();
    import "DPI-C" context function void save_my_scope();
    export "DPI-C" function sv_display; 
    
    initial begin
       save_my_scope();
       c_display();
    end
    function void sv_display();
          $display("SV:%m");
    endfunction
endmodule
module top;
    import "DPI-C" context function void c_display();
    export "DPI-C" function sv_display;  

    block b1();
    initial #1 c_display();
    
    function void sv_display();
        $display("SV:top");
    endfunction
endmodule

在这里插入图片描述

9 与其它语言交互 P364

写了一个Perl脚本的C封装,SV部分代码与调用C语言的代码相同。
在这里插入图片描述


总结

使用DPI-C代码可以调用 SV子程序,同时允许外部应用程序控制仿真过程。
难点在于将SV的数据类型映射到C,共享结构和类的时候。

  • 2
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值