SystemVerilog中关于DPI章节的翻译

35. Direct Programming Interface

需求

随着时代的发展,如今的芯片规模愈来愈大,哪怕模块级的验证环境也须要至关长的build时间,各类仿真工具也在改进编译和运行性能,还发明了增量编译。但不管如何turnaround的时间仍是比较长,并且方法越复杂越容易出错。而DPI-C则比较简单,可以解决某些场景下的问题。程序员

适用范围

DPI-C比较适用于SV和外部语言间的“简单数据“交互编程

翻译约定

subroutine == 子例程 == task and function
task == 子任务
function == 子函数
foreign language == 外部编程语言
在某些状况下会保留英文表述不作翻译数组

通用

下面章节主要包括:安全

  • DPI 子任务和子函数
  • DPI层
  • 导入和导出子函数
  • 导入和导出子任务
  • 禁用 DPI 子任务和子函数

35.2 总述

本章节主要描述了DPI以及接口中SV层的细节。
DPI是SV和外部编程语言的接口。它包含两个独立的层次: SV层 和 外部编程语言层。两侧是被彻底隔离的,也就是说它们彼此是透明的, 外部编译语言和SV侧彻底无关。不管是SV编译器仍是外部编程语言编译器都不会分析对方的代码。不一样编程语言应该均可以被同样的SV层支持。但目前SV只定义了对C的支持。数据结构

这个接口的动机是双面的。方法学上的要求是整个系统能够包含多种语言而不只仅是SV,另外一方面是已有的代码(好比C,C++)可以被很容易地集成进来。多线程

DPI遵循black-box原则。描述和实现隔离。真正的实现方法对于系统的其余部分是透明的。 由于外部语言对SV来说也是透明的,不过这个标准目前只定义了C连接语义。 SV和C经过子函数为基本单元来封装和交互。因此任何子函数均可以被当成黑盒子,它的实现或者在SV中,或者在外部编程语言中,它的实现修改了但无需修改调用侧。socket

35.2.1 tasks/functions

DPI容许语言之间的相互子函数调用。展开来说就是,SV能够调用外部编程语言里定义和实现的子函数,对SV来说就是imported functions; 外部编程语言能够调用SV里定义和实现的子函数,对SV来说就是exported functions,须要在SV中做导出声明。DPI容许经过子函数的参数和结果来在两个域中传递数据。这个接口没有固有的开销。编程语言

外部语言代码也可调用SV task, 原生的SV代码也可能调用imported tasks.
imported tasks和原生的SV task在语义上是同样的:ide

  • 无返回值
  • 能够消耗仿真时间

全部DPI 子函数都是不消耗仿真时间的,DPI没法提供除数据交换和控制权转移之外的同步手段.函数

每一个imported的subroutine都必须先声明。
import声明能够写在任何SV subroutine容许的地方。同一个外部subroutine能够被多处调用。
imported subroutine能够有0个可多个 input, output, inout 参数。
imported tasks只能返回void value; imported function能够返回值或void value.

35.2.2 Data types

SV 数据类型必须是可以跨越SV和外部语言的类型, 称为富类型:

  • void, byte, shortint, int, longint, real, shortreal, chandle, string
  • scalar value of bit and logic
  • packed arrays, structs, uniions composed of types bit and logic
  • emumeration types (interpreted as the associated real type)
  • types constructed from the following constructs:

    • struct
    • union(packed only)
    • unpacked array
    • typedef

下面是一些注意点:

  • enum 不能被直接支持,实际上会被解释为enum type关联的类型
  • 在exported DPI subroutine里, 声明形参为dynamic array容易出错
  • SV data的实际内存结构对于SV是透明的。

Function的返回值必须是small values:

  • void, byte, shortint, int, longint, real, shortreal, chandle, string
  • scalar value of bit and logic

而imported function的形参能够是open arrays

35.2.2.1 内存数据结构

DPI并无对于数据在内存里的存在形式作任何约束, 因此这是平台相关的,和SV无关

35.3 DPI的两层

DPI由独立的两层组成: SV层和外部编译语言层。他们不相互依赖。虽然SV能够支持各类编译语言,但目前SV标准只对C作了详细定义。不一样的外部编程语言能够要求SV实现使用适合的函数调用协议,参数传递,连接机制。但这对于用户来说是透明的。SV标准只要求仿真器的实现者支持C协议和连接。

35.3.1 SV layer

不依赖于外面究竟是什么语言。外部语言的实际函数调用规则和参数传递机制对于SV来说是透明和无关的。SV同等看待因此外部语言接口。SV侧的接口语义和外部编译语言侧的接口无关。

35.3.2 foreign language layer

定义实参是如何被传递的, SV data(如logic, packed)是如何表示的。如何与类C的类型转换
对于不一样的外部语言,SV端的代码应该是同样的

35.4 imported and exported functions的全局命名空间

每一个imported subroutine最终最会有一个全局符号标志;而每一个exported subroutine会定义一个全局符号标志。
它们必须惟一。它们遵循C的命名规则,必须以字母或_开头。
import和export声明时能够定义显式地一个全局名字。它必须是以开头以空格结尾

export "DPI-C" f_plus = function \f+ ; // "f+" exported as "f_plus"
export "DPI-C" function f; // "f" exported under its own name
import "DPI-C" init_1 = function void \init[1] (); // "init_1" is a linkage name
import "DPI-C" \begin = function void \init[2] (); // "begin" is a linkage name

相同C标识符的多个export 声明是容许,前提是它们在不一样的scope里

35.5 imported tasks and functions

35.5.1 属性 (properties)

pure
若是一个function的返回值只依赖于它的输入参数,且没有反作用, 那么它能够声明为pure
imported task毫不能声明为pure
context
一个imported subroutine要调用exported subroutines, 或要获取SV data objects(e.g., via VIP calls)而不是它的实际值, 那么须要声明它为context。
若是没有描述,那么subroutine应该不访问SV data objects; 不过它能够进行一些有反作用的行为,好比写文件,操做一个全局变量
compiler不会对这些properties进行检查, 因此使用它们要当心

35.5.1.1 instant completion of imported functions

它们当即被执行,而且无耗时

35.5.1.2 input, output, inout

input: 不会被修改
ouput: output的初始值是不肯定的,因此imported function不该该依赖于它
inout: imported function能够获得inout变量的初始值。imported function对于inout参数的修改能够被function外面看到

35.5.1.4 内存管理

外部语言和SV各自负责本身内存的申请和释放
一个比较复杂且容许的状况是一个imported function申请了一块内存并把handle传到SV里, 而后SV调用另外一个imported function来释放它

35.5.1.5 重入 (Reentrancy of imported tasks)

对于imported task的调用可能会致使自身进程暂停。这种状况发生在imported task又调用带有delay或event wait的exported task时。
因此有可能一个imported C code把多个线程同时激活。关于标准的重入规则是由C端决定。可使用一些标准的多线程安全库,或者静态变量来作控制

35.5.1.6 C++异常 (C++ exceptions)

可使用C++,但前提是在language边界遵照C连接约定。
C++异常不该该传播到subroutine之外, 若是异常传递到SV里,那么这是一个未定义的行为

35.5.2 Pure functions

pure function特色之一是若是它的返回不须要那么能够把pure function调用去掉。或者它的输入没变,那么能够不用从新计算。
只有没有output和inout形参的nonvoid function能够被声明为pure
声明为pure的function没有任何反作用
返回只依赖于输入值
对于这种funciton的调用能够被SV compiler优化。或者当input没变时,能够直接使用以前的值
pure function应该不直接或间接(好比调用其余function)执行下面动做:

  • 文件操做
  • 对任何事物的读写,包括I/O, 环境变量,来自操做系统,程序,进程的对象, shared memory, socket等
  • 访问任何永久变量,好比全局的或静态的变量

35.5.3 context tasks and functions

当imported subroutine要求调用上下文须要被知道时,调用者要采起特别的指令来提供这样的上下文。特殊的指令好比建立一个指向目前实例的内部变量。
当有当context被显式指定时,才会作这样的instrumentation。

exported subroutine必须知道它被调用时的上下文, 包括当它们被imported subroutine调用时。
imported subroutine能够在调用exported subroutine前,先调用svSetScope来显式地设定上文。不然,exported sobroutine的上下文就是调用import subroutine时的实例所在上下文。因为一个imported subroutine会存在于多个实例化后的做用域(scope)里, 因此展开(elaboration)后会有多个exported subroutine的实例(instances).若是不调用svSetScope, 那么这些exported instances的上下文就是调用imported subroutine的做用域(instantiated scope)

外部语言能够经过一些其余接口(如 VPI callback)来调用svSetScope或其余DPI相关的scope APIs, 而后也能够在一个指定的做用域(instantiated scope)里调用exported subroutine。
DPI的scope相关APIs的行为和DPI exported subroutine的调用由各simulator决定,DPI spec不作规定

在SV里最开始调用imported subroutine的地方称为调用链初始点(root of the call chain)

上下文属性会从SV里应用到每个imported subroutine. 这意味着根部的或调用链中间的imported call不必定可以把它的上下文环境传递到它的下一个import call.
因此一个无上下文环境的imported subroutine不可以调用一个SV exported subroutine. 这样的行为会致使出错

下面是关于imported call chain的一些特性:

  • 下面动做决定导入调用链(import call chain)的上下文值:

    • 当SV subroutine调用一个导入DPI subroutine, 一个导入声明所在的实例化做用域的上下文就为这个导入调用链(imprt call chain)建立好了
    • 当处于导入调用链(import call chain)中的一个routine调用svSetScope并传入一个合法参数时,调用链的上下文就被设置成svSetScope参数所示上下文
    • 当一个导入调用链中调用一个导出的SV subroutine完成并返回时,调用链的上下文回到调用前的值
  • 仿真器须要管理DPI的上下文,须要检测控制权如何在SV和外部语言间传递。若是用户代码里经过如C里的setjmp/longjmp等结构来从一个导出的调用链(SV)回到它的导入调用链的调用者(C), 那么它的结果是未定义的。(例如:CA -> SVA -> CB, CB里经过setjmp/longjmp回到CA)
  • 一个特定的import subroutine是否上下文相关是由它自身声明时的context属性决定的。这个属性不会传递到它的后续的import function (译者注:这个地方是function而不是subroutine?)
  • import call的上下文特性没法动态改变
  • 上下文特性和调用链绑定,而不是和imported subroutine; 所以,一个subroutine能够在一个调用链中是context,而在另外一个调用链中non-context。

一个没有定义为context的imported subroutine只访问它的实现参数。因此不是仿真器优化的障碍。而context的imported subroutine可以经过VPI和export subroutine来访问任何SV data objects, 因此会形成SV compiler的优化障碍。

只有context imported subroutine是被特殊装置地,并只作保守的优化。只有这种subroutine能够在安全地其余subroutines, 包括VPI或exported SV subroutines; 不然调用VPI和exported SV subroutine的行为是不可预测的,若是被调用者须要的上下文没有普查正确设置,那么会形成崩溃。定义一个context import subroutine并不会使simulator的其余接口自动可用。好比VPI访问仍是依赖于正确的实现机制。DPI 调用不会自动建立或提供任何句柄或特定环境给其余接口用。这个是用户的责任。

context imported subroutines老是隐性地有一个完整实例名的做用域(scope)。这个做用域定义了哪些SV subroutines能够被importored subroutine直接调用;也只有这此在相同做用域的exported subroutine能够被直接调用。若是要调用其余的exported SV subroutine, imported subroutine须要先修改它的目前做用域。

相关的DPI functions能够容许imported subroutines来获取或接口用它的做用域, 如svGetScope(), svSetScope, svGetNameFromScope, svGetScopeFromName。

35.5.4 Import declarations

全部imported subroutine都须要被声明。
imprted subroutine和SV的subroutine类似,能够有0个或多个input, output或inout形参。imported functions能够返回一个或0个(void)值。imported tasks老是返回一个int值,在外部语言里至关于一个int function。
(译者: 原文中说是做为DPI disable protocol的一部分,没搞明白)

dpi_import_export ::=                                                         // from A.2.6
        import dpi_spec_string [ dpi_function_import_property ] [ c_identifier = ] dpi_function_proto ;
        | import dpi_spec_string [ dpi_task_import_property ] [ c_identifier = ] dpi_task_proto ;
        | export dpi_spec_string [ c_identifier = ] function function_identifier ;
        | export dpi_spec_string [ c_identifier = ] task task_identifier ;
dpi_spec_string ::= "DPI-C" | "DPI"
dpi_function_import_property ::= context | pure
dpi_task_import_property ::= context
dpi_function_proto  ::= function_prototype
dpi_task_proto ::= task_prototype
function_prototype ::= function data_type_or_void function_identifier [ ( [ tf_port_list ] ) ]
task_prototype ::= task task_identifier [ ( [ tf_port_list ] ) ]             // from A.2.7

21) dpi_function_proto return types are restricted to small values, per 35.5.5
22) Formals of dpi_function_proto and dpi_task_proto cannot use pass by reference mode and class types cannot be
passed at all; see 35.5.6 for a description of allowed types for DPI formal arguments.

import 声明描述了subroutine名, function返回值类型,和它的形参的类型和方向。它也可以提供形参的默认值。形参名是可选的,除非须要按名称绑定参数。
imported function能够有contextpure属性; imported tasks能够有context属性

导入声明至关于定义一个subroutine名, 因此多个相同的subroutine名是不容许导入同一做用域的。

  • dpi_spec_string*能够取"DPI-C"或“DPI”。“DPI"是用于指示使用不建议用的sv packed array传输语义。这种语义下,参数是使用它在仿真器里的表示法来传递,而不是规一化的形式。
  • c_identifier*是这个subroutine在外部语言里的连接名(linkage name). 若是没有提供的话就和它的SV subroutine名相同。

对于一个c_identifier, 全部声明都必须使用相同的类型签名(type signature), 包括返回值类型, 每一个参数的数目, 顺序, 方向, 类型。类型包括维数,数组的边界和维数。类型签名也包括pure/context限定符, 和dpi_spec_string的值。

在多个不一样做用域中能够有一样imported或exported subroutine的多个不一样声明;所以, 参数名和默认值可能不一样。

正式的参数名要区别packed和unpacked数组维数。

限定符ref不能用在import声明中。实际的实现方法只决取于外部语言层。实现方法对于SV边是透明的。

下面是一些声明的例子:

import "DPI -C " function void myInit();

// from standard math library
import "DPI -C " pure function real sin(real);

// from standard C library: memory management
import "DPI -C " function chandle malloc(int size); // standard C function
import "DPI -C " function void free(chandle ptr); // standard C function

// abstract data structure: queue
import "DPI -C " function chandle newQueue(input string name_of_queue);

// Note the following import uses the same foreign function for
// implementation as the prior import, but has different SystemVerilog name
// and provides a default value for the argument.
import "DPI -C " newQueue=function chandle newAnonQueue(input string s=null);
import "DPI -C " function chandle newElem(bit [15:0]);
import "DPI -C " function void enqueue(chandle queue, chandle elem);
import "DPI -C " function chandle dequeue(chandle queue);

// miscellanea
import "DPI -C " function bit [15:0] getStimulus();
import "DPI -C ” context function void processTransaction(chandle elem, output logic [64:1] arr [0:63]);
import "DPI -C " task checkResults(input string s, bit [511:0] packet)

35.5.5 Function result

imported function的返回值类型被限定为"small values":

  • void , byte , shortint , int , longint , real , shortreal , chandle , and string
  • Scalar values of type bit and logic

一样的限定也适用于exported function的返回值类型上

35.5.6 Types of formal argument (形参类型)

SV中丰富的数据类型能够被做为形参用于imported和export的例程。一般,C兼容的类型,packed类型,用户定义的类型和他们的组合均可以用做DPI例程的形参。
下面是SV中容许的全部能够做为DPI例程形参的类型:

  • void, byte, shortint, int, longint, real, shortreal, chandle, time, integer, string
  • bit, logic
  • 由bit, logic组成的packed数组,结构体或联合体。在外部语言侧,全部packed类型数据都被译成1维的数组。
  • 枚举类型被解释成同枚举自己相同的类型
  • 新类型能够同下面几种方法构建

    • struct
    • union (packed only)
    • Unpacked array
    • typedef

下面是一些注意事项:

  • 枚举类型不能被直接支持。代替地,枚举数据的实际类型被使用
  • SV没有规定 packed或unpacked结构体,数组的实际内存存放结构。unpacked数据和它们的打包方式有管,而打包方式和C编译器有关。
  • 在导出的DPI例程中, 不容许使用动态数组作为形参
  • SV数据类型在内存中的实际存放结构对于SV语义来说是透明的。它和外部语言有关。但咱们能够大约知道SV数据类型是如何实现的。除了unpacked array,对导入例程的形参类型并无什么限制。SV实现方式限制了哪些unpacked arrays会被当成固定大小的数组。(虽然它也能够被传输成非固定大小的open array). 实际的变量和实现相关,而open array提供了一种实现无关的方法。

35.5.6.1 Open arrays

不管是packed维,或是unpacked维,或者二者均可以保留为空。这样的数据就叫open array. Open array提供了一种针对不一样数据大小的通用解决方案。
导入函数的形参能够被描述成open array. 而导出函数不可以使用它做为形参。open array是惟一的一种放松参数匹配规则的方法。实际参数就能够处理不一样大小的数据,从而达到代码通用化的目标。
虽然数组的packed部分能够是任意大小的,形参的变长维只能有一个是packed维。 这不是很是严厉的, packed类型都等价于一个一维packed数组,因此只有一个是可变的就至关于整个均可变。而unpacked维则没有限制。
若是形参有一个变长的packed维, 那么它将匹配任何packed维的实参。若是形参的unpacked维是变长的,那么它要求实参拥有同样的维数,只不过对应的具体维上长度是可变的。
下面是一些合法形参的例子:

logic
bit [8:1]
bit []
bit [7:0] array8x10 [1:10]
logic [31:0] array32xN []
logic [] arrayNx3 [3:1]
bit [] arrayNxN []

下面是导入函数声明例子

import "DPI-C" function void f1(input logic [127:0]);
import "DPI-C" function void f2(logic [127:0] i []); //open array of 128-bit

下面是利用open array来匹配不一样长度实参的例子

typedef struct {int i; ...} MyType;
import "DPI-C" function void f3(input MyType i [][]);

MyType a_10x5 [11:20][6:2];
MyType a_64x8 [64:1][-1:-8];

f3(a_10x5);
f3(a_64x8);

35.6 调用导入函数

调用导入函数和使用SV原生函数是同样的。因此导入函数声明和SV原生函数是同样的规范。特别地,带有默认值的参数在调用时能够省略;若是形参命名则能够用名字来作绑定。

35.6.1 变量传递

变量传递的规则是“所见即所得"。形参的计算顺序遵循SV通用规则。
变量的兼容性和强制转换都与SV原生函数同样。若是须要进行强制转换,那么会建立临时变量,并把它传递给实际变量。对于input和inout变量, 临时变量会被初始化成强制转换后的值。对于output和inout变量,实际值会被强制转换后而后赋给临时变量。临时变量和实际值之间的赋值遵循SV的赋值和强制转换通用规则。
在SV侧, 导入函数的输入值和调用者无关。形参输出值在开始是末定义的,而后被实际值赋值(可能发生强制转换)。导入函数不会改变输入变量的原始值。
在SV侧,变量传递的语义是: input变量是"copy-in", output变量是”copy-out", inout变量是"copy-in"和“copy-out"。 这里的"copy-in"和”copy-out"不是暗示它们的实际实现方式,而是指假想的赋值。
变量传递的真正实现对SV侧是透明的。SV并不清楚它其实是值传递仍是引用传递。它的实际实现方式是由外部语言定义的。

35.6.1.1 WYSIWYG原则

WYSIWYS(所见即所得)原则保证了导入函数的形参类型:实际变量被要求要和形参彻底相同,除了open array. 形参只和导入函数的声明现场有关。
没有编译器(C或者SV)能够在形参和实参类型间作强制转换。调用者的形参是被定义在另一种语言里,因此它们相互间没法可见。用户须要理解并保证它们是匹配的类型。
open array中的可变长维度会有实参的实际长度。形参中的变长,packed的维度会拥有实际参数的范围。变长, packed维度会被归一化 ([15:8] -> [8:0]). 变长范围在调用现场才能肯定。其余信息都在函数声明中定义了。

所以, 若是一个形参被定义成bit [15:8] b [], 那么它描述了一个unpacked array, 它的每一个元素也是一个packed bit array, 数据绑定范围是15到8. 实际参数的长度会被绑定到它的unpacked部分。
有时候容许传递一个动态数组会为导入function和task的实参。原则同SV里传递动态数组给原生function和task。

35.6.2 output和inout变量的值变化

仿真器负责处理output和inout变量的值变化。这样的值改变会被仿真器探测到,并被返回到SV代码。
对于output和inout变量, 值传播会当即发现一旦控制权从导入函数返回。若是有多个变量返回,那么值传播的顺序遵循SV的通用规则。

35.7 导出函数

DPI容许其余语言调用SV函数。函数和变量兼容性要求和导入函数同样。声明SV函数被导出并不改变它的语义和行为;没有什么反作用除了当它被用于DPI 调用链中(好比调用者又被SV导入并调用)。

能够被其余语言调用的SV函数须要被声明为export。 声明必须在函数被定义的做用域里。每一个函数只能被export声明一次。
一个重要的限制是: 类的成员函数不能被导出, 其余均可以。
同导入声明同样, 卖出声明能够定义一个可选的C标识符(用于其余语言)

dpi_import_export ::=         //from A.2.6
     ...
     | export dpi_sepc_string [c_identifier=] function function_identifier;
     ...
dpi_spec_string ::= "DPI-C" | "DPI"

35.8 导出任务

SV容许外部语言能够调用tasks. 同函数同样, 这样的task被定义为导出taks。
上面对于导出函数的全部描述均可应用于导出task。包括合法的定义做用域和可选的C标识符。

从一个导入的函数里调用一个导出的task是非法的。这个语义和原生SV语义是一致的,函数不能调用task。

只有导入的task拥有context属性时才能够被导入的task调用。

导出task和导出函数的一个不一样处是SV task没有返回值。导出task的返回值是一个int类型值,用来指示是否有disable用于当前线程。
类似的,导入task也会返回一个int类型值来的说明导入task是否收到一个disable.

35.9 禁用DPI task和function

使用disable语句能够禁用正在执行的DPI调用链。当导入的例程被禁用时,C代码会遵循一个简单的关闭协议。协议会容许C代码去执行必要的资源释放清理工程,好比关闭文件句柄,关闭VPI句柄,释放堆内存。

当一个disable语句做用于某个目标或它的父范围域,那么咱们就称这个导入task或function在disable状态。一个导入task或function若是调用了一个导出的task或function, 那么在进入disable状态前我必须从调用中返回。这个协议的关键之处在于被关闭的导入task或function将确认他们被关闭了。task或function能够经过svIsDisabledState()来肯定它是否在disable状态。

这个协议由以下部分组成:
a) 当一个导出的task被disable时,返回1, 不然返回0;
b) 当一个导入的task被disable时,返回1, 不然返回0
c) 当导入的function被disable时, 它将会先调用svAckDisabledState()
d) 一旦导入的task或function进入disable状态,它将不容许再调用任何导出的task或function

b), c), d)是强制行为。DPI的输写者须要确保正常的行为。
a)是由SV仿真器来保证的。此外仿真器也会检查b), c), d)是否被遵照。若是任何协议没被遵照,仿真器应该提示致命错误。

DPI的另一侧包含一个禁用协议,该协议由用户代码与仿真器一块儿实现。禁用协议容许外部模型参与SystemVerilog禁用处理。参与方法是经过DPI task的特殊返回值和特殊API调用来完成。特殊的返回值不须要更改SV代码中导入或导出DPI task的调用语法。虽然仿真器保证了导出task的返回值,但对于导入task,DPI另外一侧必须确保返回正确的值。对导入task的调用与对SV原生task的调用是没法区分的。一样,对代码中的导出task的调用与对非SV task的调用是没法区分的。若是导出的task自己是禁用的目标,则当导出task返回时,其父项导入的task不被视为处于disable状态。在这种状况下,导出的任务应返回值0,而且对svIsDisabledState()的调用也应返回0。当DPI导入的子例程因为被禁用而返回时,其输出和inout参数的值未定义。一样,当导入的函数因为禁用而返回时,函数返回值是不肯定的。 C程序员能够从禁用的函数中返回值,而C程序员能够将任意值写入导入例程的output和inout参数位置。可是,若是禁用有效,SV仿真器没有义务将任何此值传播到调用SystemVerilog代码中。

相关文章
相关标签/搜索