📞 SystemVerilog DPI —— 验证工程师的“跨语言翻译官”

在芯片验证中,我们有时需要借助 C/C++ 的强大能力:比如运行复杂的算法模型、调用现成的数学库、或者与硬件模拟器外的系统交互。

但是,SystemVerilog 本身是硬件描述和验证语言,不能直接执行 C 代码。怎么办?SystemVerilog 的 DPI(Direct Programming Interface,直接编程接口) 就是专门解决这个问题的——它像一个“跨语言翻译官”,让 SystemVerilog 和 C/C++ 能够互相调用,实现“1+1>2”的效果。

今天,我们就来彻底搞懂 DPI 的核心概念和用法,让你轻松地在验证环境中引入 C 代码。


为什么需要 DPI?

想象一下,你要验证一个图像处理芯片,需要用到一个复杂的图像压缩算法。用 SystemVerilog 从头实现这个算法既费时又容易出错,而这个算法可能已经有成熟的 C 库了。如果能直接在 SystemVerilog 测试平台中调用 C 函数,那该多方便!DPI 正是为此而生。

DPI 的优势

  • 复用现有代码:直接使用已经调试好的 C/C++ 模型,避免重复劳动。
  • 高性能:C 代码执行效率高,适合复杂的计算任务。
  • 灵活性:可以调用操作系统 API,实现文件操作、网络通信等 SystemVerilog 本身难以完成的功能。
  • 简单易用:相比传统的 PLI/VPI,DPI 不需要了解复杂的内部机制,只需简单的声明即可。

DPI 的两个方向

DPI 支持双向通信:

  1. 从 SystemVerilog 调用 C 函数(使用 import 声明)
  2. 从 C 调用 SystemVerilog 函数(使用 export 声明)

这两种方向都需要在 SystemVerilog 代码中做相应的声明。


1. 从 SystemVerilog 调用 C 函数(import)

如果你想让 SystemVerilog 调用一个 C 函数,需要用 import 声明这个函数的原型。语法如下:

import "DPI-C" [function/task] 函数名 (参数列表);
  • "DPI-C" 表示使用 C 语言的调用约定(也可以使用 DPI,但通常都用 DPI-C)。
  • 可以声明为 function(返回一个值,无时间消耗)或 task(可以消耗时间,比如调用 $time)。
  • 参数列表中的类型可以是 SystemVerilog 的基本类型(intbytestring 等),也可以是指针(通过 chandle 类型)。

简单例子:调用 C 函数打印消息

假设我们有一个 C 函数 system_init,它打印一条初始化信息。我们希望从 SystemVerilog 的测试模块中调用它。

SystemVerilog 代码(tb_top.sv)

module tb_top;
    // 声明要从 C 导入的函数
    import "DPI-C" function void system_init();

    initial begin
        $display("[%0t] 开始调用 C 函数...", $time);
        system_init();  // 调用 C 函数
        $display("[%0t] 调用完成", $time);
    end
endmodule

C 代码(dpi_example.c)

#include <stdio.h>

// 这里的函数名必须与 SystemVerilog 中声明的名称一致
void system_init() {
    printf("系统初始化中...\n");
}

如何运行(以常见仿真器为例):

  1. 将 C 代码编译成共享库(.so 或 .dll),例如:gcc -shared -o libdpi.so dpi_example.c
  2. 启动仿真器时指定链接该库,例如:vsim -sv_lib libdpi tb_top(不同仿真器命令略有不同)

输出

[0] 开始调用 C 函数...
系统初始化中...
[0] 调用完成

很简单吧!就像调用 SystemVerilog 自己的函数一样。

参数传递:输入、输出、返回值

C 函数可以有输入、输出参数,也可以有返回值。SystemVerilog 的参数类型和 C 的类型需要对应好。常用的对应关系:

SystemVerilog 类型 C 类型
byte char
int int
longint long long
shortint short
real double
string const char*(注意内存管理)
bit unsigned charsvBit
logic svLogic
chandle void*(用于传递指针)

例子:带参数和返回值的函数

C 函数:计算两个整数的和,并返回结果。

int add(int a, int b) {
    return a + b;
}

SystemVerilog 调用:

module tb;
    import "DPI-C" function int add(int a, int b);

    initial begin
        int result;
        result = add(3, 5);
        $display("3 + 5 = %0d", result);
    end
endmodule

输出

3 + 5 = 8

注意

  • 参数传递默认是值传递(C 函数得到的是值的拷贝),但你可以通过指针(如 int*)实现引用传递,这时需要在 SystemVerilog 端使用 inoutoutput 关键字,并配合 chandle 或直接传递数组等。
  • 对于字符串,C 函数接收的是 const char*,但要注意字符串的生命周期,通常由 SystemVerilog 管理。

2. 从 C 调用 SystemVerilog 函数(export)

有时候,C 代码需要反过来调用 SystemVerilog 中定义的函数。比如,C 模型在某个事件发生时,想通知验证环境。这时就需要在 SystemVerilog 中用 export 声明该函数,然后在 C 中通过特定的接口调用它。

语法

export "DPI-C" function 函数名;

或者对于 task(可能耗时):

export "DPI-C" task 任务名;

例子:C 调用 SV 函数生成随机数

假设我们有一个 C 函数,它需要调用 SystemVerilog 的随机函数来获取一个随机数。

SystemVerilog 代码(tb.sv)

module tb;
    // 定义一个函数,准备被 C 调用
    function int get_random(int max);
        return $urandom_range(max, 0);
    endfunction

    // 导出这个函数,让 C 能看到
    export "DPI-C" function get_random;

    // 其他代码...
endmodule

C 代码(call_sv.c)

#include <stdio.h>
#include "svdpi.h"  // 必须包含此头文件,它定义了 DPI 相关的类型和函数

// 声明要调用的 SV 函数(函数名需与 SV 中一致)
extern int get_random(int max);

void use_random() {
    int val = get_random(100);
    printf("C 调用 SV 获得随机数:%d\n", val);
}

注意:在 C 中调用 SV 函数时,需要包含 svdpi.h,该头文件由仿真器提供,定义了数据类型和调用约定。编译时需链接仿真器的 DPI 库。

运行:和之前类似,将 C 编译成共享库,仿真时加载即可。当 C 代码调用 get_random 时,它会进入 SV 的函数执行并返回结果。

为什么需要 export?

因为 C 代码需要知道 SV 函数的地址和调用方式,export 声明告诉仿真器将这个函数暴露给外部,并生成相应的封装代码。


更高级的话题

1. 开放数组(Open Arrays)

当需要传递大小可变的数组时,可以使用 SystemVerilog 的开放数组类型(如 int array[]),在 C 端对应 svOpenArrayHandle。这需要更复杂的处理,包括获取数组维度、数据指针等。

2. 内存管理

  • 从 SV 传递字符串给 C:C 收到的是 const char*,只读;如果 C 需要修改或存储字符串,应该拷贝一份。
  • 从 C 返回动态分配的字符串给 SV:SV 不负责释放,需要 C 自己管理内存,或者使用 svSetScope 等机制。
  • 使用 chandle 传递 C 指针:chandle 是一个不透明的指针,SV 只能存储和传递,不能解引用。这样可以安全地在 SV 中保存 C 对象句柄。

3. 多线程安全性

DPI 调用可能是并发的(例如多个线程同时调用同一个函数),需要在 C 代码中考虑线程安全。通常建议 C 函数是可重入的。


总结

  • DPI 是 SystemVerilog 与 C/C++ 互操作的桥梁。
  • import:让 SV 调用 C 函数。
  • export:让 C 调用 SV 函数。
  • 使用时需要遵循数据类型映射规则,并正确编译链接。
  • DPI 简单易用,大大增强了验证环境的灵活性和复用性。

希望这篇讲解能让你对 DPI 有个清晰的认识。在实际项目中,你可以用它来集成参考模型、加速仿真、与外部系统交互,让验证工作如虎添翼!

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐