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.tabveriusertfs)关联 C 函数。

TF 子程序分类与常用列表

TF 子程序按功能分为以下几类,常用子程序包括功能描述和用法示例:

  1. 参数访问
  • 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);
  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);
  1. 时间操作
  • tf_gettime():获取当前仿真时间(基于时间单位)。
    • 示例:double time = tf_gettime() / 1000.0;(转换为 ns)
  • tf_gettimeunit():获取模块的时间单位。
    • 示例:int unit = tf_gettimeunit();(如 -9 表示 ns)
  • tf_gettimeprecision():获取仿真时间精度。
    • 示例:int precision = tf_gettimeprecision();
  1. 控制与错误
  • tf_dostop():暂停仿真,进入交互模式。
    • 示例:tf_dostop();
  • tf_error(char *fmt, ...):报告错误信息。
    • 示例:tf_error("无效参数: %d", val);
  • tf_warning(char *fmt, ...):报告警告信息。
    • 示例:tf_warning("值超出范围");
  1. 字符串与格式化
  • tf_sprintf(char *buf, char *fmt, ...):格式化字符串到缓冲区。
    • 示例:tf_sprintf(buf, "值: %d", tf_getp(1));

使用步骤

  1. 编写 Verilog 代码
  • 定义系统任务/函数,如 $log_signal(name, value)
  • 在仿真中调用任务。
  1. 编写 C/C++ 代码
  • 使用 TF 子程序处理参数(如 tf_getp)。
  • 实现任务逻辑(如记录值、输出到文件)。
  • 注册 PLI 任务(通过 veriusertfspli.tab)。
  1. 配置 PLI
  • 创建 pli.tab 文件,指定任务和 C 函数映射。
  • 示例:
    $log_signal call=log_signal_calltf acc=rw:%1,rw:%2
  1. 编译与运行
  • 使用仿真器(如 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 函数。

注意事项

  1. 不可综合
  • TF 子程序仅用于仿真,不能综合为硬件。
  • 综合设计请参考之前的可综合 Verilog 示例(如计数器、状态机)。
  1. 仿真器配置
  • 需要包含仿真器提供的 PLI 头文件(如 pli.hvpi_user.h)。
  • 确保链接 PLI 库(VCS:-lpli,ModelSim:-pli)。
  1. 性能
  • 频繁调用 TF 子程序(如每周期调用)可能降低仿真速度。
  • 优化:缓存值(如 tf_mipname)或批量写入文件。
  1. 调试
  • 使用 tf_error 报告错误,如参数缺失。
  • 验证参数类型:tf_nodeinfo 检查位宽或类型。
  1. 兼容性
  • TF 子程序基于 Verilog-2001/2005,主流仿真器(VCS、ModelSim、QuestaSim)支持。
  • 不同仿真器可能有细微差异,需参考工具文档。

扩展建议

  1. 多格式支持
  • 扩展 $log_signal 支持更多格式(如八进制):
    c if (strcmp(format, "octal") == 0) { snprintf(value_str, 32, "%o", sig_value); }
  1. 动态修改值
  • 实现任务 $set_signal 修改信号值:
    verilog $set_signal(counter, 8'hFF);
    c int set_signal_calltf(char *user_data) { tf_putp(1, tf_getp(2)); // 设置第一个参数为第二个参数的值 return 0; }
  1. 与前端交互
  • 将日志输出通过 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())); });
  1. 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_getptf_getcstringptf_gettime 记录信号值。TF 子程序不可综合,硬件设计需使用可综合 Verilog(如之前的计数器或 FSM 示例)。

如果你需要深入讲解特定 TF 子程序(如 tf_putp)、与特定仿真器(如 VCS)的集成、或与前端(如 JavaScript 网页)交互的具体实现,请提供更多细节,我可以进一步定制!

类似文章

发表回复

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