芯片验证 | 理解SystemVerilog DPI并不难

简介: 芯片验证 | 理解SystemVerilog DPI并不难

SystemVerilog DPI概念

**Verilog中使用编程语言接口 PLI( Program Language Interface)编程语言接口来和C语言程序交互,它提供了一套C语言函数,**我们可以调用这些集成函数编写软件C程序。RTL代码编译的时候,这些软件C程序也会集成到仿真环境中。

仿真运行后,使用系统任务调用的方式,就可以去访问仿真中的数据结构,也就是说PLI提供一个使得用户自带C函数能够在运行时间访问仿真数据结构的接口。


Accellera在2003年4月发布了包括DPI在内的SystemVerilog 3.1标准,随后在3.1A版本中进一步对DPI进行了加强。systemverilog中使用DPI( Direct Programming Interface),更加简单地连接C、C++或者其他的非Verilog语言。

你只需要使用import语句把C函数导入到,就可以像调用systemverilog的子程序一样来使用它。

使用DPI, 用户无需再像Verilog PLI那样, 事先编写系统任务/函数名称, 然后通过复杂的PLI库间接传递数值回C函数。但是DPI不能直接访问仿真数据结构的内部,这限制了DPI的应用。


SystemVerilog DPI,全称SystemVerilog直接编程接口 (英语:SystemVerilog Direct Programming Interface)是SystemVerilog与其他外来编程语言的接口。

能够使用的语言包括C语言、C++、SystemC等

直接编程接口由两个层次构成:SystemVerilog层和外来语言层。

两个层次相互分离。对于SystemVerilog方面,另一边使用的编程语言是透明的,但它并不关注这一点。

SystemVerilog和外来语言的编译器各自并不需要分析另一种语言的代码。由于不触及SystemVerilog层,因此支持使用不同的语言。不过,目前SystemVerilog仅为C语言定义了外来语言层。

DPI 数据类型映射

SystemVerilog和C之间的数据交换通常使用DPI-C接口完成,该接口标准化类型对应关系和基本API(另请参见仿真器安装路径下的svdpi.h)。

大多数SystemVerilog数据类型在C语言中具有直接的对应关系,而其他(例如,4值类型,数组)需要DPI-C定义的类型和API。 查看下表完整的数据类型映射关系。

SystemVerilog C (input ) C (output )
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], logic[N:0] const svLogicVecVal* svLogicVecVal*
unsized array[] const svOpenArrayHandle svOpenArrayHandle
chandle const void* void*

一个简单的例子:

#include <stdio.h>
#include <stdlib.h>
#include "svdpi.h"
int add() {
  int a = 10, b = 20;
  a = a + b;
  io_printf("Addition Successful and Result = %d\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\n", a);
  return a;
}
module tb_dpi;
  import "DPI-C" function int add();
  import "DPI-C" function int sleep(input int secs);
  int j;
  
  initial
  begin
    $display("Entering in SystemVerilog Initial Block");
    #20
    j = add();
    $display("Value of J = %d", j);
    $display("Sleeping for 3 seconds with Unix function");
    sleep(3);
    $display("Exiting from SystemVerilog Initial Block");
    #5 $finish;
  end
  
endmodule

如上面的例子所示,就是这么简单,C代码是C代码,SV代码中只需要使用关键字import将C代码的中的函数导入即可。上一章讲到SystemVerilog代码与C语言之间的分离基于使用函数作为SV中的自然封装单元,对SV代码来说C中的函数是黑盒,是不透明的。

DPI 各种使用方式

  • 通过import定义的函数可以在任何位置
  • 在Verilog模块中
  • 在SystemVerilog interface中
  • 在SystemVerilog package中
  • 在SystemVerilog“编译单元”中
  • import声明必须具有参数的原型
  • 必须与C函数中的参数数完全匹配
  • 必须指定兼容的数据类型
  • 可以在多个位置导入相同的C功能
  • 每个原型必须完全相同
  • 更好的方法是在包中定义一个导入

C函数可以作为Verilog任务或函数导入

import "DPI-C" function real sin(real in); //sin function in C math lib
import "DPI-C" task file_write(input string data, output reg status);

1. 从C向SV导入函数作为一个函数(function)使用

#include <math.h>
#include <svdpi.h>
 
double Sin(double r)
{
        return sin(r);
}
module incremental_call_test;
 
  `define PAI 3.14159265358979323846;
    import "DPI-C"  pure function real cos (real n1);
    import "DPI-C"  pure function real sinh (real n2);
 
    initial begin
               
      for(int i=0;i<=90;i++)
          $display("cos=%f sinh=%f",cos(i*`PAI/180),sinh(i*`PAI/180));
       
    end
  
endmodule

顺便说一句,在上面的例子中,在C端函数中调用sin运行时,用户定义Sin函数是有点多余。可以直接从SystemVerilog调用这些C标准库的函数,例如cos函数的import。

2. 从C向SV导入函数作为一个任务(task)使用

import "DPI-C" task file_write(input string data, output reg status);
export "DPI-C" function sv_inc;
export "DPI-C" task delay_task;

3. 从SV导出函数(function)到C程序

#include "stdio.h" 
#include "svdpi.h" 
extern void export_func(void); 
void import_func() 
{ 
export_func(); 
} 
program main; 
export "DPI-C" function export_func; 
import "DPI-C" function void import_func(); 
function void export_func(); 
$display("SV: Hello from SV "); 
endfunction 
initial 
begin 
import_func(); 
end 
endprogram

4. 从SV导出任务(task)到C程序

#include "svdpi.h"
#include "vcsuser.h"
extern void delay_task();
extern long long int get_sv_time(); 
void import_task()
{
 
        io_printf("C:  current time = %ld #10 waiting\\\\n",get_sv_time());
        delay_task_by_parameter(10);
        io_printf("C: I'am back. current time = %ld 5 CLK waiting\\\\n",get_sv_time());
        wait_n_clks(5);
        io_printf("C: I'am back. current time = %ld event trigger waiting\\\\n",get_sv_time());
        wait_trigger();
        io_printf("C: I'am back. current time = %ld level High waiting\\\\n",get_sv_time());
        wait_level_high();
        io_printf("C: I'am back. current time = %ld C import task end\\\\n",get_sv_time());
 
}
`timescale 1ns/1ns
module export_test1;
 
   import "DPI-C"  context task import_task();
   export "DPI-C"  function  get_sv_time;
   export "DPI-C"  task delay_task_by_parameter;
   export "DPI-C"  task wait_n_clks;
   export "DPI-C"  task wait_trigger;
   export "DPI-C"  task wait_level_high;
 
   event ev;
   reg level=0;   
 
   function longint get_sv_time();
      return $time;
   endfunction
 
   reg clk=0;
   always #1 clk=~clk;
 
   task delay_task_by_parameter( input longint d);
      #(d);
   endtask;
 
   task wait_n_clks( input int n);
      repeat(n) begin
         @(negedge clk);
      end
   endtask
 
   task wait_trigger();
      @(ev);
   endtask
 
   task wait_level_high();
      wait(level==1);
   endtask
 
   initial begin
      #1;
      fork
         import_task();
         #40 level=1;
         repeat(21) #2 $display("......SV is running. current time = %2d",$time);
         #30 ->ev;
      join
      $display("fork join. %d",$time);
      $finish();
   end
 
endmodule

在上面例子中,SV和C都只使用了函数,但是不能从函数调用任务与DPI相同。

与verilog一样,只能编写任务以处理延迟和等待事件,因此在这种情况下,这是任务。

相反,如果不等待延迟或事件,使用函数有利于提高速度。函数的实现使用C堆栈帧,在许多实现中,它比任务更快,因为不需要线程处理(上下文保存)。

在SystemVerilog中,如上例所示,可以使用不带返回值的void函数。因此如果没有延迟或等待事件,则应使用函数。

5. 互相调用的情况(TODO: c中调用sv函数,sv中调用C中含有sv函数的函数)

6 绑定别名

module gloval_name_space;
 
    import "DPI-C"  context task sv_import_task ();
  
endmodule
module gloval_name_space1;
 
    import "DPI-C"  context C_task1=task sv_import_task ();
 
endmodule
 
module gloval_name_space2;
 
    import "DPI-C"  context C_task2=task sv_import_task ();
 
endmodule

从C的命名空间是全局可见的。 SV sv_import_task中的声明变为等于C的链接名称。

对于所有模块,名称必须是唯一的。 C的链接是全局性的。

因此,上面是一种别名C链接名称的方法。 SV上的名称相同,但在C上,使用别名C_task1和C_task2执行链接。

(sv_import_task是不可见的)

同理,在export的函数中也可使用别名:

export "DPI-C" f_plus = function f ; // "f" exported as "f_plus"
export "DPI-C" function f; // "f" exported under its own name

参数传递和返回值

在C和SV中有两种传递参数的方法:

  • 按值传递:被调用者函数将使用来自调用者的参数的副本
  • 通过引用传递:被调用者函数将使用来自调用者的参数的指针/引用

如果函数正在更改其参数的值,则仅当参数通过引用传递时,更改才会在函数外部可见。 当参数按值传递时,对函数内部完成的参数的任何更改都不会在其外部可见。

在SystemVerilog中,按值或按引用传递由参数方向确定。

在C中,通过值或引用传递是由参数类型是否为指针确定的。

默认情况下,SV和C都按值传递参数。

导入的C函数参数可以作为输入,输出或inout(双向)

  • 输入的行为就像在调用时复制到C函数中一样,C函数不应修改输入参数
  • 当函数返回时,输出的行为就像复制到Verilog中一样
    Inouts的行为就像在调用中复制一样,并在返回时复制出来

除非另有说明,否则假定参数为输入

SV提供了丰富的数据类型可以作为参数:

  • void, byte, shortint, int, longint, real, shortreal, chandle, time, integer, and string
  • Scalar values of type bit and logic
  • Packed arrays, structs, and unions composed of types bit and logic
  • Types constructed from the supported types with the help of the constructs: struct , union , Unpacked array , typedef

也提供了丰富的返回值数据类型:

  • void, byte, shortint, int, longint, real, shortreal, chandle, and string
  • Scalar values of type bit and logic
  • Restrictions apply for import and export functions

DPI 导入函数的分类

Pure, Context and Generic C Functions

Pure C函数

作为pure函数,函数的结果必须仅仅依赖于通过形参传递进来的数值。Pure函数的优点在于仿真器可以执行优化以改进仿真性能。

Pure函数不能使用全局或者静态变量,不能执行文件I/O操作,不能访问操作系统环境变量,不能调用来自Verilog PLI 库的函数。

只有没有输出或者inout的非void函数(必须有return值)可以被指定成pure。

Pure函数不能作为Verilog任务导入。下面的例子声明了一个导入的C函数为pure函数:

import "DPI" pure function real sin(real in); // function in C math library

Context C函数

context C函数明确函数声明所在工作域的Verilog的层次。可以是void函数,可以有输出和inout参数,可以从C库调用函数(用于文件I/O等),可以调用PLI库中的许多函数,

这使得被导入的C函数能够调用来自PLI或者VPI库的函数,从而DPI函数可以充分利用PLI的优势特性,比如写仿真器的log文件以及Verilog源代码打开的文件。context任务声明的样例如下:

import "DPI" context task print(input int file_id, input bit [127:0] data);

Generic C函数

本文把那些既没有明确声明为pure,也没有声明为context的函数称为generic函数(SystemVerilog标准没有给除了pure或context之外的函数特定的称呼)。

generic C函数可以作为Verilog函数或者Verilog任务导入。任务或者函数可以由输入、输出以及inout的参数。函数可以有一个返回值,或者声明为void。

generic C函数不允许调用Verilog PLI函数,不能访问除了参数以外的任何数据,只能修改这些参数。

注意!

正确的声明导入的函数为pure还是context是用户的责任。

缺省情况下,DPI函数假定是generic函数。调用一个不正确声明成pure的C函数可能返回不正确或者不一致的结果,导致不可预测的运行时错误,甚至于让仿真崩溃。

同样,如果一个C函数访问Verilog PLI库或者其他API库,却没有声明为context函数,会导致不可预见的仿真结果甚至仿真崩溃。

常用数据类型映射

SV byte -> C char

import "DPI-C" function void compute_byte(input byte i_value, output byte result);
import "DPI-C" function byte get_byte(input byte i_value);
void compute_byte(const char i_value, char* result);
char get_byte(const char i_value);

SV shortint -> C short int

import "DPI-C" function void compute_shortint(input shortint i_value, output shortint result);
import "DPI-C" function shortint get_shortint(input shortint i_value);
void compute_shortint(const short int i_value, short int* result);
short int get_shortint(const short int i_value);

SV int -> C int

import "DPI-C" function void compute_int(input int i_value, output int result);
import "DPI-C" function int get_int(input int i_value);
void compute_int(const int i_value, int* result);
int get_int(const int i_value);

SV longint -> C long int

import "DPI-C" function void compute_longint(input longint i_value, output longint result);
import "DPI-C" function longint get_longint(input longint i_value);
void compute_longint(const long int i_value, long int* result);
long int get_longint(const long int i_value);

SV real -> C double

import "DPI-C" function void compute_real(input real i_value, output real result);
import "DPI-C" function real get_real(input real i_value);
void compute_real(const double i_value, double* result);
double get_real(const double i_value);

SV string -> C char*

import "DPI-C" function void compute_string(input string i_value, output string result);
import "DPI-C" function string get_string(input string i_value);
void compute_string(const char* i_value, char** result);
char* get_string(const char* i_value);

SV chandle -> C void*

import "DPI-C" function void compute_chandle(output chandle result);
import "DPI-C" function chandle get_chandle();
import "DPI-C" function void call_chandle(input chandle i_value, output int result);
void compute_chandle(void** result);
void** get_chandle();
void call_chandle(const void* i_value, int* o_value);

SV bit -> C bit

import "DPI-C" function void compute_bit(input bit i_value, output bit result);
import "DPI-C" function bit get_bit(input bit i_value);
void compute_bit(const svBit i_value, svBit* result);
svBit get_bit(const svBit i_value);

SV bit[n:0] -> C svBitVecVal

import "DPI-C" function void compute_bit_vector(input bit[`BIT_ARRAY_SIZE - 1 : 0] i_val, output bit[`BIT_ARRAY_SIZE - 1 : 0] result);
import "DPI-C" function bit[`BIT_ARRAY_SIZE - 1 : 0] get_bit_vector(input bit[`BIT_ARRAY_SIZE - 1 : 0] i_val);
void compute_bit_vector(const svBitVecVal* i_value, svBitVecVal* result);
svBitVecVal get_bit_vector(const svBitVecVal* i_value);

SV logic -> C svLogic

import "DPI-C" function void compute_logic(input logic i_value, output logic result);
import "DPI-C" function logic get_logic(input logic i_value);
void compute_logic(const svLogic i_value, svLogic* result);
svLogic get_logic(const svLogic i_value);

SV reg -> C svLogic

import "DPI-C" function void compute_reg(input reg i_value, output reg result);
import "DPI-C" function reg  get_reg(input reg i_value);
void compute_reg(const svLogic i_value, svLogic* result);
svLogic get_reg(const svLogic i_value);

SV logic[n:0] -> C svLogicVecVal

import "DPI-C" function void compute_logic_vector(input logic[`LOGIC_ARRAY_SIZE - 1 : 0] i_val, output logic[`LOGIC_ARRAY_SIZE - 1 : 0] result, input int asize);
svLogicVecVal*  get_logic_vector(const svLogicVecVal* i_value, int asize);

SV reg[n:0] -> C svLogicVecVal

import "DPI-C" function void compute_reg_vector(input reg[`REG_ARRAY_SIZE - 1 : 0] i_val, output reg[`REG_ARRAY_SIZE - 1 : 0] result, input int asize);
void compute_reg_vector(const svLogicVecVal* i_value, svLogicVecVal* result, int asize);

SV int[] -> C svOpenArrayHandle

import "DPI-C" function void compute_unsized_int_array(input int i_value[], output int result[]);
void compute_unsized_int_array(const svOpenArrayHandle i_value, svOpenArrayHandle result);

SV struct -> C struct

`define BIT_ARRAY_SIZE 16
typedef struct {
  byte aByte;
  int anInt;
  bit aBit;
  longint aLongInt;
  bit[`BIT_ARRAY_SIZE-1:0] aBitVector;
} dpi_c_ex_s;
import "DPI-C" function void compute_struct(input dpi_c_ex_s i_value, output dpi_c_ex_s result);
typedef struct dpi_c_ex_s {
  char aChar;
  int anInt;
  svBit aBit;
  long int aLongInt;
  svBitVecVal aBitVector;
} dpi_c_ex_s;
void compute_struct(const dpi_c_ex_s* i_value, dpi_c_ex_s* output);

参考资料

目录
相关文章
|
测试技术 Shell 开发者
UVM与验证环境一文通
UVM与验证环境一文通
1347 0
|
7月前
|
算法 测试技术 编译器
使用 Synopsys VCS 生成 SystemVerilog DPI 组件的 HDL 验证器,将 SystemVerilog DPI 组件从 MATLAB 生成用于 Synopsys VCS 模拟
使用 Synopsys VCS 生成 SystemVerilog DPI 组件的 HDL 验证器,将 SystemVerilog DPI 组件从 MATLAB 生成用于 Synopsys VCS 模拟
264 0
|
芯片 存储 算法
芯片验证 | Formal验证技术总结
芯片验证 | Formal验证技术总结
885 0
芯片验证 | Formal验证技术总结
|
前端开发 Python
【前端验证】通用型顺序比对的uvm scoreboard组件编写
【前端验证】通用型顺序比对的uvm scoreboard组件编写
656 0
|
测试技术 芯片 开发者
芯片验证 | UVM的phase机制
芯片验证 | UVM的phase机制
1153 0
|
存储 自然语言处理 测试技术
SV学习笔记(一)
SV学习笔记(一)
583 1
|
芯片
芯片验证 | UVM的domain机制
芯片验证 | UVM的domain机制
356 0
|
Java 关系型数据库 测试技术
SV学习笔记(四)
SV学习笔记(四)
1095 0
|
存储 人工智能 固态存储
芯片设计 | 什么是 NVMe?
芯片设计 | 什么是 NVMe?
968 0

热门文章

最新文章