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 构造(如
always
、assign
,见之前的计数器或 FSM 示例)。
2. PLI 的组成
PLI 包括三个主要接口,分别提供不同功能:
- TF(Task and Function)子程序:
- 处理 Verilog 系统任务/函数的参数和上下文。
- 示例:
tf_getp(n)
获取任务参数,tf_mipname()
获取模块路径。 - 用途:实现简单的任务逻辑,如参数读取、日志记录。
- ACC(Access)子程序:
- 访问 Verilog 模型的内部数据结构(如信号、模块)。
- 示例:
acc_fetch_value(handle)
读取信号值,acc_next_child(handle)
遍历子模块。 - 用途:复杂的数据访问,如信号监控、层次分析。
- 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 使用方式
- Verilog 代码:
- 定义系统任务/函数,如
$my_task(arg1, arg2)
。 - 在仿真中调用。
- C/C++ 代码:
- 使用 TF/ACC 子程序实现任务逻辑。
- 包含仿真器提供的头文件(如
acc_user.h
、pli.h
)。 - 注册 PLI 任务到仿真器。
- PLI 注册:
- 通过
veriusertfs
数组或pli.tab
文件关联 Verilog 任务和 C 函数。 - 示例(
pli.tab
):$my_task call=my_task_calltf acc=rw:%1,rw:%2
- 编译与运行:
- 使用仿真器(如 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. 注意事项
- 不可综合:
- PLI(包括 TF 和 ACC)仅用于仿真,不能综合为硬件。
- 综合设计请参考之前的可综合 Verilog 示例(如计数器、状态机)。
- 仿真器依赖:
- 需要包含仿真器提供的 PLI 头文件(如
pli.h
、acc_user.h
)。 - 配置 PLI 表和链接库(如 VCS 的
-lpli
)。
- 性能:
- 频繁调用 PLI 任务可能降低仿真速度。
- 优化:缓存数据(如模块路径),减少文件 I/O。
- 调试:
- 使用
tf_error
或acc_error_flag
报告错误。 - 验证参数类型和路径(如
tf_nodeinfo
、acc_fetch_fullname
)。
- 现代替代:
- SystemVerilog DPI(Direct Programming Interface)比 PLI 更简洁,推荐用于新项目:
verilog import "DPI-C" function void print_value(string name, int value);
7. 扩展建议
- 日志文件输出:
- 修改示例将输出写入文件:
c FILE *fp = fopen("log.txt", "a"); fprintf(fp, "[%.2f ns] %s (%s): %d\n", time, path, sig_name, value); fclose(fp);
- 与前端交互:
- 通过 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())); });
- 信号监控:
- 结合 ACC 子程序监控值变化:
c handle sig = acc_handle_tfarg(1); acc_vcl_add(sig, monitor_callback, NULL, vcl_value_change);
- 复杂测试平台:
- 实现自动激励生成或结果检查:
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)的集成方案,请提供更多细节,我可以进一步定制!