Verilog TF 子程序列表
Verilog TF(Task and Function)子程序列表
Verilog TF(Task and Function)子程序是 Verilog PLI(Programming Language Interface)的一部分,允许用户通过 C/C++ 程序与 Verilog 仿真器交互,主要用于操作系统任务(system tasks)或系统函数(system functions)的参数和行为。TF 子程序由 IEEE 1364 标准定义,主要用于仿真环境(不可综合),常用于测试平台、调试工具或与外部程序交互。
以下是 Verilog TF 子程序的概述、分类、常见子程序列表、使用注意事项,以及一个简单的示例代码。TF 子程序与 ACC 子程序(之前讨论)类似,但专注于系统任务/函数的调用和参数处理,而 ACC 更侧重于访问 Verilog 模型的内部结构。
TF 子程序概述
- 作用:
- TF 子程序用于在 C/C++ 中处理 Verilog 系统任务(如
$my_task(arg1, arg2)
)或系统函数(如$my_func(arg1)
)的参数、返回值和调用行为。 - 提供对任务/函数调用上下文的访问,如参数值、实例信息、仿真时间等。
- 使用场景:
- 自定义系统任务(如
$monitor
的增强版)。 - 读取/修改任务参数值。
- 控制仿真流程(如暂停、错误处理)。
- 与外部工具交互(如生成波形文件、与前端通信)。
- 不可综合性:
- TF 子程序仅用于仿真环境(如 VCS、ModelSim),无法综合为硬件。
- 综合代码应使用纯 Verilog 构造(如
always
、assign
)。 - 调用方式:
- 在 Verilog 代码中定义系统任务/函数(如
$my_task
)。 - 在 C/C++ 中实现对应的 TF 子程序,处理参数和逻辑。
- 通过 PLI 注册表(
pli.tab
或veriusertfs
)将 C 函数与 Verilog 任务关联。
TF 子程序分类
TF 子程序按功能分为以下几类(基于 IEEE 1364-2005 标准):
- 参数访问:
- 获取或设置系统任务/函数的参数值。
- 示例:
tf_getp
、tf_putp
。
- 实例信息:
- 获取任务/函数的调用实例、模块路径等。
- 示例:
tf_mipname
、tf_nodeinfo
。
- 时间操作:
- 获取仿真时间、时间刻度。
- 示例:
tf_gettime
、tf_gettimeunit
。
- 控制与错误:
- 控制任务执行、报告错误。
- 示例:
tf_dostop
、tf_error
。
- 字符串与格式化:
- 处理字符串参数或格式化输出。
- 示例:
tf_strgetp
、tf_sprintf
。
常见 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_getrealp(int n)
:获取第n
个参数的浮点值。- 用法:
double val = tf_getrealp(1);
- 用法:
tf_putrealp(int n, double val)
:设置第n
个参数的浮点值。- 用法:
tf_putrealp(1, 3.14);
- 用法:
tf_strgetp(int n, int format)
:获取第n
个参数的字符串值。- 用法:
char *str = tf_strgetp(1, 'b');
(获取二进制字符串)
- 用法:
- 实例信息:
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()
:获取当前仿真时间(以时间单位为基准)。- 用法:
int time = tf_gettime();
- 用法:
tf_gettimeunit()
:获取当前模块的时间单位。- 用法:
int unit = tf_gettimeunit();
(返回如-9
表示 ns)
- 用法:
tf_gettimeprecision()
:获取仿真精度。- 用法:
int precision = tf_gettimeprecision();
- 用法:
- 控制与错误:
tf_dostop()
:暂停仿真,进入交互模式。- 用法:
tf_dostop();
- 用法:
tf_error(char *fmt, ...)
:报告错误信息。- 用法:
tf_error("Invalid parameter: %d", val);
- 用法:
tf_warning(char *fmt, ...)
:报告警告信息。- 用法:
tf_warning("Value out of range");
- 用法:
- 字符串与格式化:
tf_sprintf(char *buf, char *fmt, ...)
:格式化字符串到缓冲区。- 用法:
tf_sprintf(buf, "Value: %d", tf_getp(1));
- 用法:
tf_getcstringp(int n)
:获取第n
个参数的 C 字符串。- 用法:
char *str = tf_getcstringp(1);
- 用法:
- 任务控制:
tf_nump()
:获取任务/函数的参数数量。- 用法:
int num = tf_nump();
- 用法:
tf_synchronize()
:强制仿真同步到当前时间。- 用法:
tf_synchronize();
- 用法:
使用 TF 子程序的注意事项
- 仿真专用:
- TF 子程序仅用于仿真环境(如 VCS、ModelSim),不可综合为硬件。
- 综合代码需使用可综合 Verilog(如
always
、assign
)。
- 环境配置:
- 需要包含
vpi_user.h
或工具特定的 PLI 头文件:c #include "pli.h" // 工具特定,可能为 vpi_user.h
- 链接仿真器提供的 PLI 库(如
-lpli
)。
- 性能开销:
- 频繁调用 TF 子程序(如参数读取)可能降低仿真速度。
- 优化:缓存参数句柄,避免重复调用
tf_getp
。
- 兼容性:
- TF 子程序基于 Verilog-2001/2005 标准,现代仿真器支持良好。
- 不同仿真器(如 VCS、QuestaSim)可能有细微差异,需查阅文档。
- 错误处理:
- 使用
tf_error
和tf_warning
报告问题。 - 检查参数有效性:
c if (tf_nump() < 1) tf_error("Missing argument");
示例:使用 TF 子程序实现自定义任务
以下是一个简单的 Verilog 和 C 代码示例,展示如何使用 TF 子程序实现一个自定义系统任务 $print_count
,打印计数器值和模块路径。
Verilog 代码(test.v):
module test (
input wire clk,
input wire rst_n,
output reg [3:0] count
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) count <= 4'b0;
else count <= count + 1;
end
// 调用自定义 PLI 任务
always @(posedge clk) begin
$print_count(count);
end
endmodule
module tb_test;
reg clk, rst_n;
wire [3:0] count;
test uut (.clk(clk), .rst_n(rst_n), .count(count));
initial begin
clk = 0;
rst_n = 0;
#20 rst_n = 1;
#100 $finish;
end
always #5 clk = ~clk; // 10ns 周期
endmodule
C 代码(print_count.c):
#include "pli.h" // 假设仿真器提供此头文件
#include <stdio.h>
// PLI 任务实现
int print_count_calltf(char *user_data) {
int count_val = tf_getp(1); // 获取第一个参数(count)
char *module_path = tf_mipname(); // 获取模块路径
double sim_time = tf_gettime() / 1000.0; // 转换为 ns
printf("Time: %.2f ns, Module: %s, Count: %d\n",
sim_time, module_path, count_val);
return 0;
}
// PLI 注册函数
void print_count_register() {
s_tfcell veriusertfs[] = {
{usertask, 0, 0, 0, print_count_calltf, 0, "$print_count"},
{0} // 结束标志
};
tf_setuptfs(veriusertfs);
}
pli.tab 文件:
$print_count call=print_count_calltf acc=rw:%1
编译与运行(以 VCS 为例):
# 编译
vcs -full64 -P pli.tab test.v print_count.c -o simv
# 运行
./simv
输出示例:
Time: 5.00 ns, Module: tb_test.uut, Count: 1
Time: 15.00 ns, Module: tb_test.uut, Count: 2
Time: 25.00 ns, Module: tb_test.uut, Count: 3
...
说明:
- Verilog 模块
test
是一个 4 位计数器。 - 每次时钟上升沿,调用
$print_count(count)
。 - C 代码使用
tf_getp
获取count
值,tf_mipname
获取模块路径,tf_gettime
获取仿真时间。 - 输出格式化时间、模块路径和计数器值。
扩展建议
- 多参数处理:
- 支持多个参数,检查参数数量:
c if (tf_nump() < 2) tf_error("Expected 2 arguments"); int arg1 = tf_getp(1); int arg2 = tf_getp(2);
- 字符串参数:
- 处理字符串输入:
verilog $print_count("Count is:", count);
c char *prefix = tf_getcstringp(1); int count_val = tf_getp(2); printf("%s %d\n", prefix, count_val);
- 与前端交互:
- 将 TF 子程序的输出写入文件,供前端(如 JavaScript)读取:
c FILE *fp = fopen("count.txt", "a"); fprintf(fp, "%.2f %s %d\n", tf_gettime() / 1000.0, tf_mipname(), tf_getp(1)); fclose(fp);
- 替代方案:
- 现代设计推荐使用 SystemVerilog DPI 替代 TF 子程序,接口更简单:
verilog import "DPI-C" function void print_count(input int count); always @(posedge clk) print_count(count);
c void print_count(int count) { printf("Count: %d\n", count); }
注意事项
- 不可综合:
- TF 子程序仅用于仿真,不能用于逻辑综合(如 Vivado、Quartus)。
- 综合代码参考之前的可综合计数器或状态机示例。
- 工具依赖:
- TF 子程序实现因仿真器(VCS、ModelSim、QuestaSim)而异。
- 确保正确配置 PLI 表和链接库。
- 性能:
- 频繁调用 TF 子程序可能降低仿真速度。
- 优化:缓存参数值,减少
tf_getp
调用。
- 调试:
- 使用
tf_error
和tf_warning
报告问题。 - 验证模块路径:
tf_mipname
。
参考与资源
- 标准:IEEE 1364-2005 Verilog PLI 标准,定义 TF 子程序。
- 书籍:
- 《Verilog PLI Handbook》(Stuart Sutherland):TF 和 PLI 详细指南。
- 《Writing Testbenches》(Janick Bergeron):测试平台开发。
- 工具文档:
- Synopsys VCS User Guide:PLI 配置和 TF 子程序。
- Mentor QuestaSim User Manual:TF 支持。
- 开源资源:
- GitHub 上有 PLI 示例代码和教程。
总结
TF 子程序是 Verilog PLI 的核心,用于仿真环境中处理系统任务/函数的参数和行为,适合测试平台和调试工具开发。上述 $print_count
示例展示了如何使用 tf_getp
、tf_mipname
和 tf_gettime
实现信号监控。TF 子程序不可综合,硬件设计需使用可综合 Verilog(如之前的计数器或 FSM 示例)。
如果需要更复杂的 TF 示例(如多参数处理、与前端交互或集成到特定仿真器),或想转向 SystemVerilog DPI,请提供具体需求!我也可以结合可综合 Verilog 代码,扩展为带 PLI 监控的完整测试平台。