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);
参考资料
- 维基百科
- DPI基础知识