Verilog PLI 简介

Verilog PLI 简介(中文)

Verilog PLI(Programming Language Interface,编程语言接口)是 Verilog 硬件描述语言(HDL)提供的一种机制,允许用户通过 C/C++ 程序与 Verilog 仿真器交互,用于扩展仿真功能。PLI 是 IEEE 1364 标准的一部分,主要用于仿真环境(不可综合),广泛应用于测试平台开发、信号监控、调试和与外部工具(如前端界面)集成。

以下是对 Verilog PLI 的简明介绍,包括其作用、组成、应用场景、使用方式、注意事项,以及一个简单的示例,帮助初学者快速理解 PLI 的核心概念。


1. PLI 作用与功能

  • 作用
  • PLI 提供了一种桥梁,让 C/C++ 程序访问和操作 Verilog 仿真器中的数据(如信号值、模块层次、仿真时间)。
  • 用户可以通过 PLI 定义自定义系统任务或函数(如 $my_task),实现复杂仿真功能。
  • 主要功能
  • 信号访问与修改:读取或设置信号值(如寄存器、网线)。
  • 层次遍历:访问设计中的模块、端口、实例等。
  • 仿真控制:获取时间、暂停仿真、报告错误。
  • 外部交互:将仿真数据输出到文件或网络,供前端(如 JavaScript)使用。
  • 不可综合
  • PLI 仅用于仿真(如 VCS、ModelSim、QuestaSim),不能综合为硬件。
  • 综合设计需使用纯 Verilog 构造(如 alwaysassign,见之前的计数器或 FSM 示例)。

2. PLI 的组成

PLI 包括三个主要接口,分别提供不同功能:

  1. TF(Task and Function)子程序
  • 处理 Verilog 系统任务/函数的参数和上下文。
  • 示例:tf_getp(n) 获取任务参数,tf_mipname() 获取模块路径。
  • 用途:实现简单的任务逻辑,如参数读取、日志记录。
  1. ACC(Access)子程序
  • 访问 Verilog 模型的内部数据结构(如信号、模块)。
  • 示例:acc_fetch_value(handle) 读取信号值,acc_next_child(handle) 遍历子模块。
  • 用途:复杂的数据访问,如信号监控、层次分析。
  1. VPI(Verilog Procedural Interface)
  • 更现代的接口(Verilog-2001 引入),提供更灵活的对象访问和控制。
  • 示例:vpi_get_value(handle) 获取值,vpi_handle_by_name(name) 获取对象句柄。
  • 用途:现代仿真工具更推荐 VPI,接口更简洁。

:本文重点介绍 TF 和 ACC(传统 PLI,基于 IEEE 1364-1995/2001),因为它们在传统 Verilog 项目中更常见。VPI 更适合 SystemVerilog 或现代工具。


3. PLI 应用场景

  • 测试平台开发
  • 自定义任务(如 $dump_signal)记录信号值到文件或波形。
  • 自动生成激励信号或检查输出。
  • 调试工具
  • 实时监控信号变化(如 $monitor 的增强版)。
  • 遍历模块层次,分析设计结构。
  • 与外部工具交互
  • 将仿真数据通过文件或网络(如 socket)传输到前端(如 JavaScript 网页)。
  • 示例:将信号值实时显示在网页 UI 上。
  • 仿真控制
  • 动态暂停仿真、修改参数或注入错误。

4. PLI 使用方式

  1. Verilog 代码
  • 定义系统任务/函数,如 $my_task(arg1, arg2)
  • 在仿真中调用。
  1. C/C++ 代码
  • 使用 TF/ACC 子程序实现任务逻辑。
  • 包含仿真器提供的头文件(如 acc_user.hpli.h)。
  • 注册 PLI 任务到仿真器。
  1. PLI 注册
  • 通过 veriusertfs 数组或 pli.tab 文件关联 Verilog 任务和 C 函数。
  • 示例(pli.tab):
    $my_task call=my_task_calltf acc=rw:%1,rw:%2
  1. 编译与运行
  • 使用仿真器(如 VCS、ModelSim)编译 Verilog 和 C 代码。
  • 运行仿真,验证功能。

5. 简单示例:自定义 PLI 任务 $print_value

以下示例实现一个系统任务 $print_value,打印信号值和仿真时间。

Verilog 代码 (test.v)

module test (
    input wire clk,
    input wire rst_n,
    output reg [3:0] counter
);
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) counter <= 4'b0;
        else counter <= counter + 1;
    end

    // 每周期调用 PLI 任务
    always @(posedge clk) begin
        $print_value("Counter", counter);
    end
endmodule

module tb_test;
    reg clk, rst_n;
    wire [3:0] counter;

    test uut (.clk(clk), .rst_n(rst_n), .counter(counter));

    initial begin
        clk = 0;
        rst_n = 0;
        #20 rst_n = 1;
        #100 $finish;
    end

    always #5 clk = ~clk; // 10ns 周期
endmodule

C 代码 (print_value.c)

#include "pli.h" // 仿真器特定头文件
#include <stdio.h>

// PLI 任务实现
int print_value_calltf(char *user_data) {
    if (tf_nump() != 2) {
        tf_error("需要 2 个参数:信号名、值");
        return 1;
    }

    char *sig_name = tf_getcstringp(1); // 获取信号名
    int value = tf_getp(2);             // 获取信号值
    double time = tf_gettime() / 1000.0; // 获取仿真时间 (ns)
    char *path = tf_mipname();          // 获取模块路径

    printf("[%.2f ns] %s (%s): %d\n", time, path, sig_name, value);
    return 0;
}

// PLI 注册
void print_value_register() {
    s_tfcell veriusertfs[] = {
        {usertask, 0, 0, 0, print_value_calltf, 0, "$print_value"},
        {0} // 结束
    };
    tf_setuptfs(veriusertfs);
}

pli.tab 文件

$print_value call=print_value_calltf acc=rw:%1,rw:%2

编译与运行(以 VCS 为例)

# 编译
vcs -full64 -P pli.tab test.v print_value.c -o simv
# 运行
./simv

输出示例

[5.00 ns] tb_test.uut (Counter): 1
[15.00 ns] tb_test.uut (Counter): 2
[25.00 ns] tb_test.uut (Counter): 3
...

说明

  • Verilog:计数器模块 test,每周期调用 $print_value("Counter", counter)
  • C 代码
  • tf_nump() 验证参数数量。
  • tf_getcstringp(1) 获取信号名,tf_getp(2) 获取值。
  • tf_gettime()tf_mipname() 提供时间和模块路径。
  • 打印格式化输出。
  • PLI 注册:通过 veriusertfs 映射 $print_value 到 C 函数。

6. 注意事项

  1. 不可综合
  • PLI(包括 TF 和 ACC)仅用于仿真,不能综合为硬件。
  • 综合设计请参考之前的可综合 Verilog 示例(如计数器、状态机)。
  1. 仿真器依赖
  • 需要包含仿真器提供的 PLI 头文件(如 pli.hacc_user.h)。
  • 配置 PLI 表和链接库(如 VCS 的 -lpli)。
  1. 性能
  • 频繁调用 PLI 任务可能降低仿真速度。
  • 优化:缓存数据(如模块路径),减少文件 I/O。
  1. 调试
  • 使用 tf_erroracc_error_flag 报告错误。
  • 验证参数类型和路径(如 tf_nodeinfoacc_fetch_fullname)。
  1. 现代替代
  • SystemVerilog DPI(Direct Programming Interface)比 PLI 更简洁,推荐用于新项目:
    verilog import "DPI-C" function void print_value(string name, int value);

7. 扩展建议

  1. 日志文件输出
  • 修改示例将输出写入文件:
    c FILE *fp = fopen("log.txt", "a"); fprintf(fp, "[%.2f ns] %s (%s): %d\n", time, path, sig_name, value); fclose(fp);
  1. 与前端交互
  • 通过 socket 将数据发送到前端(如 JavaScript 网页):
    c int sockfd = socket(AF_INET, SOCK_STREAM, 0); send(sockfd, buffer, strlen(buffer), 0);
    javascript const net = require('net'); const client = net.createConnection({ port: 12345 }, () => { client.on('data', data => console.log(data.toString())); });
  1. 信号监控
  • 结合 ACC 子程序监控值变化:
    c handle sig = acc_handle_tfarg(1); acc_vcl_add(sig, monitor_callback, NULL, vcl_value_change);
  1. 复杂测试平台
  • 实现自动激励生成或结果检查:
    verilog $generate_stimulus(counter);

8. 参考资源

  • 标准:IEEE 1364-2005 Verilog PLI 标准。
  • 书籍
  • 《Verilog PLI Handbook》(Stuart Sutherland):PLI 详解。
  • 《Writing Testbenches》(Janick Bergeron):测试平台开发。
  • 工具文档
  • Synopsys VCS User Guide:PLI 配置。
  • Mentor QuestaSim User Manual:TF/ACC 支持。
  • 开源资源:GitHub 上的 PLI 示例代码。

总结

Verilog PLI(包括 TF 和 ACC 子程序)是扩展仿真功能的强大工具,适合测试平台、调试和外部交互。示例 $print_value 展示了 TF 子程序的基本用法,打印信号值和时间。PLI 不可综合,硬件设计需使用可综合 Verilog(如之前的计数器或 FSM)。

如果你需要深入某个 PLI 子程序(如 tf_putp)、与特定仿真器(如 VCS)的配置、或与前端(如 JavaScript)的集成方案,请提供更多细节,我可以进一步定制!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注