☰
Icarus Verilog VPI 模块机制与 Verilog-A 数学库(va_math)开发指南
2026/10/4 1:44:33 网站建设 项目流程
  • 编译器
  • 硬件仿真
  • EDA

【免费下载链接】iverilog

Icarus Verilog

项目地址:https://gitcode.com/gh_mirrors/iv/iverilog
点击查看免费下载

本篇指南以 Icarus Verilog 开发者文档 Documentation/developer/guide/vpi/index.rst 及其两个子文档(vpi.rst、va_math.rst)为核心骨架,讲解 Icarus Verilog 中 VPI(Verilog Procedural Interface,PLI 2.0 的一部分)模块的运行时加载机制、模块搜索路径、内置核心模块,以及随发行版附带的 Verilog-A 数学库va_math的完整函数与常量清单。读完本文,你将掌握 VPI 模块的导出符号约定(vlog_startup_routines与vpip_set_callback)、如何编译与加载.vpi模块、如何通过VPI_TRACE环境变量调试 VPI 调用,并能在自己的 Verilog/Verilog-A 仿真中直接使用$ln、$sqrt、$sin等数学系统函数与\M_PI` 等数学常量。

一、VPI 模块的本质:把一组 PLI 应用打包成一个共享库

Icarus Verilog 的 VPI 接口工作机制与传统的 PLI 1.0(TF/ACC)不同:它不是把一个个 PLI 应用单独加载,而是把一组 PLI 应用源码编译链接成一个单一的 VPI 模块(.vpi共享对象),再由运行时按需加载。这是理解 Icarus Verilog VPI 架构的第一条原则。

在 Documentation/developer/guide/vpi/vpi.rst 中对此有明确说明:VPI 模块由一系列 PLI 应用的编译代码链接而成(连同这些应用依赖的其他库),最终产出一个带两个导出符号的模块:

  • vpip_set_callback函数;
  • vlog_startup_routines数组。

这两个符号就是运行时与该模块之间的唯一“握手协议”。

1.1 加载协议:跳转表 + 启动例程表

调用 VPI 模块的产品(通常是仿真运行时vvp)加载模块后,会执行以下两步:

  1. 定位并调用vpip_set_callback函数,向模块传递一张“跳转表”(jump table,即指向 VPI 例程的函数指针集合),使模块能够访问由产品实现的 VPI 例程(如vpi_get_value、vpi_put_value、vpi_register_systf等);
  2. 定位vlog_startup_routines表,并按顺序调用表中列出的所有启动例程(startup routines)。

一个产品可以同时链接多个模块。此时所有模块都会被链接进来,各模块的启动例程按顺序依次被调用——因此模块之间是“先后注册、互不干扰”的关系,注册顺序即加载顺序。

从源码看,这套协议在 vvp/vpi_modules.cc 中实现。vpip_load_module()函数的执行流程与文档描述完全对应:

  • 先按模块名构造候选文件路径,用stat()探测文件是否存在;
  • 用ivl_dlopen()打开共享库(vvp/vpi_modules.cc);
  • 在 Windows/Cygwin 平台上查找并调用vpip_set_callback符号,传入vpi_routines跳转表与vpip_routines_version版本号做兼容性校验(vvp/vpi_modules.cc);
  • 用ivl_dlsym()查找vlog_startup_routines表(vvp/vpi_modules.cc),若找不到则报错no vlog_startup_routines;
  • 将 DLL 记入内部dll_list(退出时统一dlclose),然后置VPI_MODE_REGISTER模式,依次执行表中每个启动例程,最后恢复VPI_MODE_NONE(vvp/vpi_modules.cc)。

1.2 启动例程表:模块作者必须提供

vlog_startup_routines是以 0 结尾的函数指针数组,模块作者负责把它编译进模块。例如 examples/hello_vpi.c 中的典范写法:

void (*vlog_startup_routines[])() = { my_hello_register, 0 };

启动例程的职责是向运行时注册本模块提供的系统任务与系统函数。一个模块可以实现任意多个任务/函数,因此一个模块完全可以作为一个“系统任务库”来组织——这正是va_math、v2005_math等内置模块的组织方式。注册的核心调用是vpi_register_systf(),例如 vpi/va_math.c 中的va_math_register()正是把所有数学函数注册为vpiSysFunc/vpiRealFunc类型的系统函数。

二、模块搜索路径:VPI_MODULE_PATH 环境变量

产品通过模块名称加载模块时,需要知道去哪里找文件。文档规定:产品使用环境变量VPI_MODULE_PATH,它是一个以冒号:分隔的目录列表,即模块搜索路径。当按名称请求一个模块时,运行时按顺序扫描该路径,直到定位到模块文件为止。

需要特别指出当前仓库实现中的一个命名差异:从源码看,vvp/vpi_modules.cc 实际读取的环境变量名为IVERILOG_VPI_MODULE_PATH(vvp/vvp.man.in 手册页中也写作IVERILOG_VPI_MODULE_PATH=/some/path:/some/other/path)。阅读文档时可将两者视为同一概念的“文档名/实现名”关系,配置环境时以仓库实现名为准。

实现细节(vvp/vpi_modules.cc):

  • 搜索路径分隔符在类 Unix 平台为:,在 Windows(MINGW/Cygwin)为;;
  • 除环境变量外,编译期宏MODULE_DIR1、MODULE_DIR2指定的默认目录也会加入搜索路径(vvp/vpi_modules.cc);
  • 路径表容量上限为VPIP_MODULE_PATH_MAX(64 条),超出会报错退出;
  • 若模块名中包含目录分隔符(/或 Windows 下的\),则被视作完整路径直接使用,依次尝试name、name.vpi、name.vpl;否则在搜索路径的每个目录中依次尝试name.vpi、name.vpl(vvp/vpi_modules.cc)。

除了环境变量,vvp命令行还提供两个直接控制搜索路径的选项(vvp/main.cc):

-M path VPI module directory (向搜索路径追加一个目录) -M - Clear VPI module path (清空搜索路径) -m module Load vpi module (按名加载模块)

对应到源码,-M path调用vpip_add_module_path(),-M -调用vpip_clear_module_paths()并同时禁用默认路径。因此一个典型的运行命令形如:

vvp -M. -mhello_vpi hello_vpi

这正是 examples/hello_vpi.vl 注释中给出的用法:-M.把当前目录加入搜索路径,-mhello_vpi加载当前目录下的hello_vpi.vpi模块。

三、随发行版附带的核心 VPI 模块

文档明确列出以下特殊模块名属于 Icarus Verilog 核心发行版的一部分,它们实现了标准系统任务/函数:

模块名用途
system.vpi核心系统任务/函数($display、$finish、$monitor等),总是随运行时加载
v2005_math.vpi2005 版标准数学系统函数
v2009.vpi2009 版(SystemVerilog 相关)系统任务/函数
va_math.vpiVerilog-A 数学系统函数(详见本文第五、六章)
vhdl_sys.vpi支持 VHDL 目标所需的私有系统函数
vhdl_textio.vpi支持 VHDLtextio所需的私有系统函数

其中system.vpi始终加载,为设计中的系统任务调用提供实现;其余模块按需通过-m加载。这些模块的实现位于仓库的 vpi/ 目录下,例如vpi/sys_*.c系列文件对应system.vpi的各项系统任务,vpi/v2005_math.c对应v2005_math.vpi,vpi/vhdl_table.c、vpi/vhdl_textio.c对应 VHDL 辅助模块。

四、编译 VPI 模块

编译 VPI 模块的完整教程见用户文档 Documentation/usage/vpi.rst(即原文档中:doc:Using VPI <../../../usage/vpi>`` 指向的页面,位于仓库根目录下的Documentation/usage/vpi.rst)。其核心要点是:把模块源码当作 DLL/共享对象来编译和链接。

手工方式(Linux + gcc,前提是vpi_user.h头文件与libvpi.a库已安装,且编译器能找到它们):

gcc -c -fpic hello.c gcc -shared -o hello.vpi hello.o -lvpi

推荐方式(跨平台、更省事,所有受支持系统上通用):

iverilog-vpi hello.c

iverilog-vpi工具接收模块的源码文件作为命令行参数,自动带上正确的编译选项、系统相关库与链接选项,生成hello.vpi。其底层实现可参考 driver-vpi/iverilog-vpi.sh(POSIX shell 脚本)与 driver-vpi/main.c(Windows 原生 C 实现,还支持--name=、-I、-D、-l、--cflags、--ldflags、--ldlibs、--install-dir等附加选项)。

一个最小可用的模块源码模板(改自 examples/hello_vpi.c):

#include <vpi_user.h> static PLI_INT32 my_hello_calltf(char *xx) { vpi_printf("Hello World, from VPI.\n"); return 0; } static void my_hello_register() { s_vpi_systf_data tf_data; tf_data.type = vpiSysTask; tf_data.tfname = "$my_hello"; tf_data.calltf = my_hello_calltf; tf_data.compiletf = 0; tf_data.sizetf = 0; vpi_register_systf(&tf_data); } void (*vlog_startup_routines[])() = { my_hello_register, 0 };

配套的 Verilog 测试文件 examples/hello_vpi.vl 展示了完整的编译与运行闭环:

iverilog -ohello_vpi hello_vpi.vl # 编译设计 vvp -M. -mhello_vpi hello_vpi # 加载模块并运行

关于iverilog-vpi与-m选项的更细参数说明,可继续阅读 Documentation/usage/vpi.rst 与 Documentation/usage/command_line_flags.rst。

五、追踪 VPI 调用:VPI_TRACE 环境变量

当 VPI 模块行为异常、需要调试问题时,vvp提供了 VPI 调用追踪能力。文档给出的启用方法非常简单——设置VPI_TRACE环境变量,其值为写入追踪文本的文件路径:

setenv VPI_TRACE /tmp/foo.txt

(bash 语法下等价写法为export VPI_TRACE=/tmp/foo.txt。)

从源码看,该机制在 vvp/vpi_priv.cc 中实现,有两个值得注意的增强细节:

  • 若VPI_TRACE的值为单个字符-,追踪文本会直接写到stdout(方便快速查看);
  • 否则按指定路径以写模式打开文件,并用setvbuf(..., _IOLBF, ...)设置为行缓冲,确保追踪文本及时落盘。

文档同时给出两条重要的实践忠告:

  1. 追踪输出非常冗长(verbose),日常运行不要开启,只在调试时临时启用;
  2. 追踪消息的格式不稳定——作者明确表示格式会随需求(甚至心情)改变,因此不要尝试用软件解析该格式,它只适合人类阅读排错。

六、va_math:Verilog-A 数学库

va_math.vpi是随 Icarus Verilog 发行版附带的 Verilog-A 数学库模块,其说明文档为 Documentation/developer/guide/vpi/va_math.rst,实现源码为 vpi/va_math.c。它把 Verilog-A 标准提供的数学函数全部实现为Verilog-D 系统函数:函数名保持不变,只是按 Verilog-D 的惯例在名称前加$前缀。该库基于 GNU GPL(v2 或更高版本)许可发布。

6.1 标准 Verilog-A 数学函数全表

文档完整列出了库所实现的函数(参数x、y均为数值):

系统函数含义
$ln(x)自然对数
$log10(x)常用对数(十进制对数)
$exp(x)指数
$sqrt(x)平方根
$min(x,y)最小值
$max(x,y)最大值
$abs(x)绝对值
$floor(x)向下取整
$ceil(x)向上取整
$pow(x,y)幂运算(x 的 y 次方)
$sin(x)正弦
$cos(x)余弦
$tan(x)正切
$asin(x)反正弦
$acos(x)反余弦
$atan(x)反正切
$atan2(y,x)y/x 的反正切(四象限)
$hypot(x,y)斜边长度sqrt(x**2 + y**2)
$sinh(x)双曲正弦
$cosh(x)双曲余弦
$tanh(x)双曲正切
$asinh(x)反双曲正弦
$acosh(x)反双曲余弦
$atanh(x)反双曲正切

关于参数与结果的约束,文档给出了明确的边界说明:

  • 库对x、y的唯一限制是它们必须是数值(number),不能是常量字符串;
  • 其余参数限制完全交给底层 C 数学库决定;
  • 多数 C 库对无法用实数表示的结果返回+-Inf或NaN;
  • 所有函数都返回 real(实数)类型结果。

6.2 源码层面的实现佐证

阅读 vpi/va_math.c 可以印证上述行为:

  • 模块把函数按参数个数分为两组:单参数函数表va_single_data与双参数函数表va_double_data(vpi/va_math.c),表中以{名字, C函数指针}形式组织,以{0,0}结尾;
  • 参数检查在compiletf(va_single_argument_compiletf/va_double_argument_compiletf)阶段完成:缺参数、多参数会通过va_error_message()报错并vpi_control(vpiFinish, 1)结束仿真;va_process_argument()会拒绝字符串常量参数,报错信息为"%s cannot process strings"(vpi/va_math.c);
  • 真正计算在calltf阶段完成:用vpi_get_value()取实参,调用底层 C 数学函数计算,再用vpi_put_value(..., vpiRealVal, ..., vpiNoDelay)把 real 结果写回调用点(vpi/va_math.c);
  • 注册时统一使用type = vpiSysFunc、sysfunctype = vpiRealFunc,并通过vpip_make_systf_system_defined()标记为系统定义函数(vpi/va_math.c);
  • 模块通过cbEndOfSimulation回调在仿真结束时释放所有分配的用户数据(vpi/va_math.c);
  • 与文档的对应关系上需要注意:从源码头部注释看,大部分数学函数(对数、三角、双曲、幂、开方等)已迁移到v2005_math模块(Most of the math functions have been moved to v2005_math. This allows them to be supported separately from the Verilog-A specific functions.,见 vpi/va_math.c),当前va_math自身保留实现的是$abs、$max、$min等 Verilog-A 特定函数;$max/$min在系统缺少 C99 的fmax/fmin时还会退回到模块内置的va_fmax/va_fmin等价实现(vpi/va_math.c)。因此实际仿真中,加载-m va_math与-m v2005_math常配合使用以获得完整函数集。

6.3 标准 Verilog-A 数学常量

Verilog-A 数学常量通过包含头文件constants.vams获得,该文件位于标准 include 目录(当前仓库中即根目录下的 constants.vams)。较新版本的 Icarus Verilog(0.9.devel 及之后)自动把这个目录追加到 include 文件搜索列表的末尾,因此无需手动指定-I路径即可\include "constants.vams"`。

文档列出的数学常量完整清单如下:

宏数值含义
`M_PIπ
`M_TWO_PI2π
`M_PI_2π/2
`M_PI_4π/4
`M_1_PI1/π
`M_2_PI2/π
`M_2_SQRTPI2/√π
`M_E自然常数 e
`M_LOG2E以 2 为底 e 的对数
`M_LOG10E以 10 为底 e 的对数
`M_LN2e 为底 2 的对数(ln 2)
`M_LN10e 为底 10 的对数(ln 10)
`M_SQRT2√2
`M_SQRT1_21/√2

实际定义可在 constants.vams 中逐条核对,例如`define M_PI 3.14159265358979323846、`define M_TWO_PI 6.28318530717958647693等,均以高精度十进制小数给出。头文件还通过`ifdef CONSTANTS_VAMS宏做了包含保护,避免重复定义。

6.4 使用 va_math 库

使用方式极其简洁,只需两步(对应文档原文“Just add-m va_mathto your iverilog command line/command file and\includetheconstants.vams` file as needed.”):

  1. 在iverilog命令行(或命令文件)中加入-m va_math;
  2. 需要用到数学常量时,在源码中`include "constants.vams"。

例如:

`include "constants.vams" module math_demo; real r; initial begin r = $sqrt(2.0) * `M_PI; // 系统函数 + 数学常量 $display("result = %f", r); $finish; end endmodule

编译运行时配合-m va_math(以及需要时-m v2005_math)加载对应模块即可。值得一提的是,该库在仓库中还配套了ivtest/下的回归测试体系,可作为验证函数行为的参考用例。

七、总结

Icarus Verilog 的 VPI 体系由“单一共享模块 + 两个导出符号”构成:模块作者把多个 PLI 应用链接成一个.vpi文件,导出vlog_startup_routines启动例程表(注册系统任务/函数)与vpip_set_callback(接收运行时的 VPI 例程跳转表);运行时vvp通过-m按名加载、按IVERILOG_VPI_MODULE_PATH(文档中写作VPI_MODULE_PATH)搜索模块,并在加载前先执行全部启动例程完成注册。调试时可用VPI_TRACE输出调用追踪(注意其格式不稳定、仅供人工阅读)。发行版内置的system.vpi、v2005_math.vpi、v2009.vpi、va_math.vpi及 VHDL 辅助模块覆盖了标准系统任务与数学函数;其中va_math将 Verilog-A 的 23 个数学函数以$前缀形式引入 Verilog-D,并随 constants.vams 提供 14 个数学常量——这使 Verilog-A 风格的模拟算法可以直接在 Icarus Verilog 仿真中落地运行。

  • 编译器
  • 硬件仿真
  • EDA

【免费下载链接】iverilog

Icarus Verilog

项目地址:https://gitcode.com/gh_mirrors/iv/iverilog
点击查看免费下载
上一篇:ITI/ICS-Security-Tools项目:工业控制系统(ICS)实验室建设全指南
下一篇:WebGui入门教程:5分钟快速搭建WebAssembly即时模式GUI应用

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询