Verilog TF 子程序
Verilog TF 子程序中文讲解
Verilog TF(Task and Function,任务与函数)子程序是 Verilog PLI(编程语言接口,Programming Language Interface)的一部分,用于在仿真环境中通过 C/C++ 程序与 Verilog 系统任务(如 $my_task(arg1)
)或系统函数(如 $my_func(arg1)
)交互。TF 子程序由 IEEE 1364-2005 标准定义,主要用于仿真(不可综合),常用于测试平台开发、信号监控、调试或与外部工具(如前端界面)交互。
以下是 TF 子程序的中文讲解,包括功能概述、分类、常用子程序、使用步骤、注意事项,以及一个实用示例代码,帮助你快速理解和应用 TF 子程序。如果你需要特定子程序的深入讲解或与前端(如 JavaScript)集成的方案,请进一步说明!
TF 子程序概述
- 作用:
- TF 子程序允许 C/C++ 程序处理 Verilog 系统任务/函数的参数、返回值和调用上下文。
- 提供对参数值、模块路径、仿真时间等的访问,适合自定义仿真功能。
- 使用场景:
- 自定义系统任务,如记录信号值(类似
$monitor
)。 - 动态修改任务参数。
- 与外部工具交互,如生成日志文件或通过网络发送数据到前端。
- 不可综合:
- TF 子程序仅用于仿真环境(如 VCS、ModelSim、QuestaSim),不能综合为硬件。
- 综合设计请参考之前的可综合 Verilog 示例(如计数器、状态机)。
- 调用方式:
- 在 Verilog 中定义系统任务/函数(如
$my_task
)。 - 在 C/C++ 中实现对应的 TF 子程序,处理参数和逻辑。
- 通过 PLI 注册表(
pli.tab
或veriusertfs
)关联 C 函数。
TF 子程序分类与常用列表
TF 子程序按功能分为以下几类,常用子程序包括功能描述和用法示例:
- 参数访问:
tf_getp(int n)
:获取第n
个参数的整数或逻辑值。- 示例:
int val = tf_getp(1);
(获取$my_task(arg1)
的arg1
值)
- 示例:
tf_putp(int n, int val)
:设置第n
个参数的值。- 示例:
tf_putp(1, 42);
(将参数设为 42)
- 示例:
tf_strgetp(int n, int format)
:获取第n
个参数的字符串值(如二进制或十六进制)。- 示例:
char *str = tf_strgetp(1, 'b');
(获取二进制字符串)
- 示例:
tf_getrealp(int n)
:获取第n
个参数的浮点值。- 示例:
double val = tf_getrealp(1);
- 示例:
tf_getcstringp(int n)
:获取第n
个参数的 C 字符串。- 示例:
char *str = tf_getcstringp(1);
- 示例:
- 实例与上下文:
tf_mipname()
:获取任务调用所在的模块实例路径。- 示例:
char *path = tf_mipname();
(返回如"top.module1"
)
- 示例:
tf_spname()
:获取任务/函数名称。- 示例:
char *name = tf_spname();
(返回如"$my_task"
)
- 示例:
tf_nodeinfo(int n, s_tfnodeinfo *info)
:获取第n
个参数的节点信息(如类型、位宽)。- 示例:
c s_tfnodeinfo info; tf_nodeinfo(1, &info);
- 示例:
- 时间操作:
tf_gettime()
:获取当前仿真时间(基于时间单位)。- 示例:
double time = tf_gettime() / 1000.0;
(转换为 ns)
- 示例:
tf_gettimeunit()
:获取模块的时间单位。- 示例:
int unit = tf_gettimeunit();
(如-9
表示 ns)
- 示例:
tf_gettimeprecision()
:获取仿真时间精度。- 示例:
int precision = tf_gettimeprecision();
- 示例:
- 控制与错误:
tf_dostop()
:暂停仿真,进入交互模式。- 示例:
tf_dostop();
- 示例:
tf_error(char *fmt, ...)
:报告错误信息。- 示例:
tf_error("无效参数: %d", val);
- 示例:
tf_warning(char *fmt, ...)
:报告警告信息。- 示例:
tf_warning("值超出范围");
- 示例:
- 字符串与格式化:
tf_sprintf(char *buf, char *fmt, ...)
:格式化字符串到缓冲区。- 示例:
tf_sprintf(buf, "值: %d", tf_getp(1));
- 示例:
使用步骤
- 编写 Verilog 代码:
- 定义系统任务/函数,如
$log_signal(name, value)
。 - 在仿真中调用任务。
- 编写 C/C++ 代码:
- 使用 TF 子程序处理参数(如
tf_getp
)。 - 实现任务逻辑(如记录值、输出到文件)。
- 注册 PLI 任务(通过
veriusertfs
或pli.tab
)。
- 配置 PLI:
- 创建
pli.tab
文件,指定任务和 C 函数映射。 - 示例:
$log_signal call=log_signal_calltf acc=rw:%1,rw:%2
- 编译与运行:
- 使用仿真器(如 VCS)编译 Verilog 和 C 代码。
- 运行仿真,验证输出。
示例:自定义任务 $log_signal
记录信号值
以下是一个实用示例,实现系统任务 $log_signal
,记录信号值到文件,支持自定义格式。
Verilog 代码 (test.v):
module test (
input wire clk,
input wire rst_n,
output reg [7:0] counter
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) counter <= 8'b0;
else counter <= counter + 1;
end
// 每周期调用 PLI 任务记录 counter 值
always @(posedge clk) begin
$log_signal("Counter", counter, "hex");
end
endmodule
module tb_test;
reg clk, rst_n;
wire [7: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 代码 (log_signal.c):
#include "pli.h"
#include <stdio.h>
#include <string.h>
FILE *log_file = NULL;
// PLI 任务实现
int log_signal_calltf(char *user_data) {
// 打开日志文件
if (log_file == NULL) {
log_file = fopen("signal_log.txt", "w");
if (log_file == NULL) {
tf_error("无法打开日志文件");
return 1;
}
}
// 检查参数数量
if (tf_nump() != 3) {
tf_error("需要 3 个参数:信号名、值、格式");
return 1;
}
// 获取参数
char *sig_name = tf_getcstringp(1); // 信号名
int sig_value = tf_getp(2); // 信号值
char *format = tf_getcstringp(3); // 格式 ("hex", "binary", "decimal")
double sim_time = tf_gettime() / 1000.0; // 仿真时间 (ns)
char *module_path = tf_mipname(); // 模块路径
// 格式化值
char value_str[32];
if (strcmp(format, "hex") == 0) {
snprintf(value_str, 32, "%02x", sig_value);
} else if (strcmp(format, "binary") == 0) {
snprintf(value_str, 32, "%08b", sig_value);
} else {
snprintf(value_str, 32, "%d", sig_value);
}
// 写入日志文件
fprintf(log_file, "[%.2f ns] %s (%s): %s\n",
sim_time, module_path, sig_name, value_str);
fflush(log_file);
return 0;
}
// PLI 注册
void log_signal_register() {
s_tfcell veriusertfs[] = {
{usertask, 0, 0, 0, log_signal_calltf, 0, "$log_signal"},
{0} // 结束
};
tf_setuptfs(veriusertfs);
}
pli.tab 文件:
$log_signal call=log_signal_calltf acc=rw:%1,rw:%2,rw:%3
编译与运行(以 VCS 为例):
# 编译
vcs -full64 -P pli.tab test.v log_signal.c -o simv
# 运行
./simv
输出文件 (signal_log.txt):
[5.00 ns] tb_test.uut (Counter): 01
[15.00 ns] tb_test.uut (Counter): 02
[25.00 ns] tb_test.uut (Counter): 03
...
代码说明:
- Verilog:计数器模块
test
,每周期调用$log_signal("Counter", counter, "hex")
。 - C 代码:
tf_nump()
验证参数数量(3 个)。tf_getcstringp(1)
获取信号名(”Counter”)。tf_getp(2)
获取计数器值。tf_getcstringp(3)
获取格式(”hex”)。tf_gettime()
和tf_mipname()
提供时间和模块路径。- 格式化输出写入
signal_log.txt
。 - PLI 注册:通过
veriusertfs
映射$log_signal
到 C 函数。
注意事项
- 不可综合:
- TF 子程序仅用于仿真,不能综合为硬件。
- 综合设计请参考之前的可综合 Verilog 示例(如计数器、状态机)。
- 仿真器配置:
- 需要包含仿真器提供的 PLI 头文件(如
pli.h
或vpi_user.h
)。 - 确保链接 PLI 库(VCS:
-lpli
,ModelSim:-pli
)。
- 性能:
- 频繁调用 TF 子程序(如每周期调用)可能降低仿真速度。
- 优化:缓存值(如
tf_mipname
)或批量写入文件。
- 调试:
- 使用
tf_error
报告错误,如参数缺失。 - 验证参数类型:
tf_nodeinfo
检查位宽或类型。
- 兼容性:
- TF 子程序基于 Verilog-2001/2005,主流仿真器(VCS、ModelSim、QuestaSim)支持。
- 不同仿真器可能有细微差异,需参考工具文档。
扩展建议
- 多格式支持:
- 扩展
$log_signal
支持更多格式(如八进制):c if (strcmp(format, "octal") == 0) { snprintf(value_str, 32, "%o", sig_value); }
- 动态修改值:
- 实现任务
$set_signal
修改信号值:verilog $set_signal(counter, 8'hFF);
c int set_signal_calltf(char *user_data) { tf_putp(1, tf_getp(2)); // 设置第一个参数为第二个参数的值 return 0; }
- 与前端交互:
- 将日志输出通过 socket 传输到前端(如 JavaScript):
c int sockfd = socket(AF_INET, SOCK_STREAM, 0); send(sockfd, value_str, strlen(value_str), 0);
javascript // Node.js 示例 const net = require('net'); const client = net.createConnection({ port: 12345 }, () => { client.on('data', data => console.log(data.toString())); });
- SystemVerilog DPI 替代:
- 现代设计推荐使用 DPI,接口更简洁:
verilog import "DPI-C" function void log_signal(string name, int value, string format); always @(posedge clk) log_signal("Counter", counter, "hex");
c void log_signal(const char *name, int value, const char *format) { printf("[%.2f ns] %s: %d\n", tf_gettime() / 1000.0, name, value); }
参考资源
- 标准:IEEE 1364-2005 Verilog PLI 标准。
- 书籍:
- 《Verilog PLI Handbook》(Stuart Sutherland):TF 子程序详解。
- 《Writing Testbenches》(Janick Bergeron):测试平台应用。
- 工具文档:
- Synopsys VCS User Guide:PLI 配置。
- Mentor QuestaSim User Manual:TF 支持。
- 开源资源:GitHub 上的 PLI 示例代码。
总结
Verilog TF 子程序用于仿真环境中处理系统任务/函数的参数和上下文,适合开发测试平台或调试工具。示例 $log_signal
展示了如何使用 tf_getp
、tf_getcstringp
和 tf_gettime
记录信号值。TF 子程序不可综合,硬件设计需使用可综合 Verilog(如之前的计数器或 FSM 示例)。
如果你需要深入讲解特定 TF 子程序(如 tf_putp
)、与特定仿真器(如 VCS)的集成、或与前端(如 JavaScript 网页)交互的具体实现,请提供更多细节,我可以进一步定制!