From babb4a071fb58571becde841de4d134b12ae43c1 Mon Sep 17 00:00:00 2001 From: Jakub Michalski Date: Mon, 15 Jun 2026 17:16:25 +0200 Subject: [PATCH] Support user-provided DPI-C function declarations Signed-off-by: Jakub Michalski --- docs/guide/extensions.rst | 19 +++++++++++++++++++ src/V3AstNodeOther.h | 8 ++++++++ src/V3EmitCBase.cpp | 4 ++++ src/V3EmitCSyms.cpp | 8 ++++++-- src/V3PreProc.cpp | 13 +++++++++++++ src/V3Task.cpp | 1 + src/verilog.l | 1 + src/verilog.y | 9 +++++++++ test_regress/t/t_dpi_decl.cpp | 22 ++++++++++++++++++++++ test_regress/t/t_dpi_decl.py | 19 +++++++++++++++++++ test_regress/t/t_dpi_decl.v | 20 ++++++++++++++++++++ test_regress/t/t_dpi_decl_bad.out | 5 +++++ test_regress/t/t_dpi_decl_bad.py | 16 ++++++++++++++++ test_regress/t/t_dpi_decl_bad.v | 14 ++++++++++++++ 14 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 test_regress/t/t_dpi_decl.cpp create mode 100755 test_regress/t/t_dpi_decl.py create mode 100644 test_regress/t/t_dpi_decl.v create mode 100644 test_regress/t/t_dpi_decl_bad.out create mode 100755 test_regress/t/t_dpi_decl_bad.py create mode 100644 test_regress/t/t_dpi_decl_bad.v diff --git a/docs/guide/extensions.rst b/docs/guide/extensions.rst index 8e8d5d183..3cc41ea46 100644 --- a/docs/guide/extensions.rst +++ b/docs/guide/extensions.rst @@ -294,6 +294,25 @@ or "`ifdef`"'s may break other tools. (if appropriate :vlopt:`--coverage` flags are passed) after being disabled earlier with :option:`/*verilator&32;coverage_off*/`. +.. option:: /*verilator&32;dpi_c_decl ""*/ + + Specifies C function declaration that will be emitted for given DPI-C + function into the Verilator-generated __Dpi.h header, replacing the declaration + that Verilator would build by default using the Verilog function signature. + + This enables use of C functions with types not specified in the + standard. For example, it enables use of functions that return ``char*``: + + .. code-block:: sv + + module t; + import "DPI-C" function string getenv(input string arg) /*verilator dpi_c_decl "char* getenv(const char*)"*/; + + initial begin + $display("%s", getenv("HOME")); + end + endmodule + .. option:: /*verilator&32;fargs */ For Verilator developers only. When a source file containing these `fargs` diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index 3c25a9992..d2bbf21e4 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -94,6 +94,7 @@ class AstNodeFTask VL_NOT_FINAL : public AstNode { // @astgen op4 := scopeNamep : Optional[AstScopeName] string m_name; // Name of task string m_cname; // Name of task if DPI import + string m_dpiCDecl; // Custom DPI-C function declaration string m_ifacePortName; // Interface port name for out-of-block definition (IEEE 25.8) uint64_t m_dpiOpenParent = 0; // DPI import open array, if !=0, how many callees bool m_taskPublic : 1; // Public task @@ -199,6 +200,9 @@ public: void dpiOpenChild(bool flag) { m_dpiOpenChild = flag; } bool dpiTask() const { return m_dpiTask; } void dpiTask(bool flag) { m_dpiTask = flag; } + bool dpiCDeclOverride() const { return !m_dpiCDecl.empty(); } + const string& dpiCDecl() const { return m_dpiCDecl; } + void dpiCDecl(const string& cDecl) { m_dpiCDecl = cDecl; } bool isConstructor() const { return m_isConstructor; } void isConstructor(bool flag) { m_isConstructor = flag; } bool isHideLocal() const { return m_isHideLocal; } @@ -511,6 +515,7 @@ class AstCFunc final : public AstNode { string m_rtnType; // void, bool, or other return type string m_argTypes; // Argument types string m_ifdef; // #ifdef symbol around this function + string m_cDecl; // Custom DPI-C function declaration VBoolOrUnknown m_isConst; // Function is declared const (*this not changed) bool m_isStatic : 1; // Function is static (no need for a 'this' pointer) bool m_isTrace : 1; // Function is related to tracing @@ -640,6 +645,9 @@ public: void dpiImportPrototype(bool flag) { m_dpiImportPrototype = flag; } bool dpiImportWrapper() const { return m_dpiImportWrapper; } void dpiImportWrapper(bool flag) { m_dpiImportWrapper = flag; } + bool dpiCDeclOverride() const { return !m_cDecl.empty(); } + const string& dpiCDecl() const { return m_cDecl; } + void dpiCDecl(const string& cDecl) { m_cDecl = cDecl; } bool isCoroutine() const { return m_rtnType == "VlCoroutine"; } void recursive(bool flag) { m_recursive = flag; } bool recursive() const { return m_recursive; } diff --git a/src/V3EmitCBase.cpp b/src/V3EmitCBase.cpp index f1680a40a..2e93700a5 100644 --- a/src/V3EmitCBase.cpp +++ b/src/V3EmitCBase.cpp @@ -137,6 +137,10 @@ void EmitCBaseVisitorConst::emitCDefaultConstructor(const AstNodeModule* const m void EmitCBaseVisitorConst::emitCFuncHeader(const AstCFunc* funcp, const AstNodeModule* modp, bool withScope) { if (funcp->slow()) putns(funcp, "VL_ATTR_COLD "); + if (funcp->dpiCDeclOverride()) { + putns(funcp, funcp->dpiCDecl() + ";\n"); + return; + } if (!funcp->isDestructor()) { putns(funcp, funcp->rtnTypeVoid()); puts(" "); diff --git a/src/V3EmitCSyms.cpp b/src/V3EmitCSyms.cpp index bcf79c164..baf500103 100644 --- a/src/V3EmitCSyms.cpp +++ b/src/V3EmitCSyms.cpp @@ -1434,8 +1434,12 @@ void EmitCSyms::emitDpiHdr() { if (!firstImp++) puts("\n// DPI IMPORTS\n"); putsDecoration(nodep, "// DPI import" + sourceLoc + "\n"); } - putns(nodep, "extern " + nodep->rtnTypeVoid() + " " + nodep->nameProtect() + "(" - + cFuncArgs(nodep) + ");\n"); + if (nodep->dpiCDeclOverride()) { + putns(nodep, "extern " + nodep->dpiCDecl() + ";\n"); + } else { + putns(nodep, "extern " + nodep->rtnTypeVoid() + " " + nodep->nameProtect() + "(" + + cFuncArgs(nodep) + ");\n"); + } } puts("\n"); diff --git a/src/V3PreProc.cpp b/src/V3PreProc.cpp index 67e2cf3f9..342797a72 100644 --- a/src/V3PreProc.cpp +++ b/src/V3PreProc.cpp @@ -545,6 +545,19 @@ void V3PreProcImp::comment(const string& text) { if (arg.size() && baseCmd == "public_flat_rw_on") baseCmd += "_sns"; // different cmd for applying sensitivity if (!printed) insertUnreadback("/*verilator " + baseCmd + "*/ " + arg + " /**/"); + } else if (VString::startsWith(cmd, "dpi_c_decl")) { + // "/*verilator dpi_c_decl foo(bar) */" -> "/*verilator dpi_c_decl*/ \"foo(bar)\"" + string baseCmd = cmd.substr(0, std::strlen("dpi_c_decl")); + string::size_type startOfArg = baseCmd.size(); + while (std::isspace(cmd[startOfArg])) startOfArg++; + string arg = cmd.substr(startOfArg); + if (arg.empty()) { + fileline()->v3warn( + BADVLTPRAGMA, + "No function declaration provided in /*verilator dpi_c_decl*/ meta-comment"); + return; + } + if (!printed) insertUnreadback("/*verilator " + baseCmd + "*/ \"" + arg + "\" /**/"); } else { if (!printed) insertUnreadback("/*verilator " + cmd + "*/"); } diff --git a/src/V3Task.cpp b/src/V3Task.cpp index 72e779d1e..28768e305 100644 --- a/src/V3Task.cpp +++ b/src/V3Task.cpp @@ -1060,6 +1060,7 @@ class TaskVisitor final : public VNVisitor { // Add DPI Import to top, since it's a global function m_topScopep->scopep()->addBlocksp(funcp); + funcp->dpiCDecl(nodep->dpiCDecl()); if (!makePortList(nodep, funcp)) return nullptr; return funcp; } diff --git a/src/verilog.l b/src/verilog.l index 36058b0a6..d274148ff 100644 --- a/src/verilog.l +++ b/src/verilog.l @@ -825,6 +825,7 @@ vnum {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5} "/*verilator coverage_block_off*/" { FL; return yVL_COVERAGE_BLOCK_OFF; } "/*verilator coverage_off*/" { FL_FWD; PARSEP->lexFileline()->coverageOn(false); FL_BRK; } "/*verilator coverage_on*/" { FL_FWD; PARSEP->lexFileline()->coverageOn(true); FL_BRK; } + "/*verilator dpi_c_decl*/" { FL; return yVL_DPI_C_DECL; } // The "foo(bar)" is converted by the preproc "/*verilator forceable*/" { FL; return yVL_FORCEABLE; } "/*verilator full_case*/" { FL; return yVL_FULL_CASE; } "/*verilator hier_block*/" { FL; return yVL_HIER_BLOCK; } diff --git a/src/verilog.y b/src/verilog.y index db145ea8e..378c4f402 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -780,6 +780,7 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"}) %token yVL_CLOCKER "/*verilator clocker*/" %token yVL_CLOCK_ENABLE "/*verilator clock_enable*/" %token yVL_COVERAGE_BLOCK_OFF "/*verilator coverage_block_off*/" +%token yVL_DPI_C_DECL "/*verilator dpi_c_decl*/" %token yVL_FORCEABLE "/*verilator forceable*/" %token yVL_FULL_CASE "/*verilator full_case*/" %token yVL_HIER_BLOCK "/*verilator hier_block*/" @@ -4928,6 +4929,14 @@ dpi_import_export: // ==IEEE: dpi_import_export $5->dpiPure($3 == iprop_PURE); $5->dpiImport(true); GRAMMARP->checkDpiVer($1, *$2); v3Global.dpi(true); } + | yIMPORT yaSTRING dpi_tf_import_propertyE dpi_importLabelE function_prototype yVL_DPI_C_DECL yaSTRING ';' + { $$ = $5; + if (*$4 != "") $5->cname(*$4); + $5->dpiContext($3 == iprop_CONTEXT); + $5->dpiPure($3 == iprop_PURE); + $5->dpiImport(true); + if (*$7 != "") $5->dpiCDecl(*$7); + GRAMMARP->checkDpiVer($1, *$2); v3Global.dpi(true); } | yIMPORT yaSTRING dpi_tf_import_propertyE dpi_importLabelE task_prototype ';' { $$ = $5; if (*$4 != "") $5->cname(*$4); diff --git a/test_regress/t/t_dpi_decl.cpp b/test_regress/t/t_dpi_decl.cpp new file mode 100644 index 000000000..5cf783a39 --- /dev/null +++ b/test_regress/t/t_dpi_decl.cpp @@ -0,0 +1,22 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* +#include "Vt_dpi_decl__Dpi.h" + +char* func(const char* arg) { + static char str[] = "abc"; + return str; +} +// some functions (e.g. getenv() from glibc) may have additional specifier, lack of it in the +// declaration will cause build errors +char* func_with_specifier(const char* arg) throw() { + static char str[] = "efd"; + return str; +} diff --git a/test_regress/t/t_dpi_decl.py b/test_regress/t/t_dpi_decl.py new file mode 100755 index 000000000..50cb5e61f --- /dev/null +++ b/test_regress/t/t_dpi_decl.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') +test.pli_filename = "t/t_dpi_decl.cpp" + +test.compile(v_flags2=[test.pli_filename]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_dpi_decl.v b/test_regress/t/t_dpi_decl.v new file mode 100644 index 000000000..1f5ae21c7 --- /dev/null +++ b/test_regress/t/t_dpi_decl.v @@ -0,0 +1,20 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +`define stop $stop +`define checks(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got='%s' exp='%s'\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); + +module t; + import "DPI-C" function string func(input string arg) /*verilator dpi_c_decl "char* func(const char*)"*/; + import "DPI-C" function string func_with_specifier(input string arg) /*verilator dpi_c_decl "char* func_with_specifier(const char*) throw()"*/; + + initial begin + `checks(func("arg"), "abc"); + `checks(func_with_specifier("arg"), "efd"); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_dpi_decl_bad.out b/test_regress/t/t_dpi_decl_bad.out new file mode 100644 index 000000000..9a6dc9fde --- /dev/null +++ b/test_regress/t/t_dpi_decl_bad.out @@ -0,0 +1,5 @@ +%Error-BADVLTPRAGMA: t/t_dpi_decl_bad.v:8:57: No function declaration provided in /*verilator dpi_c_decl*/ meta-comment + 8 | import "DPI-C" function string func(input string arg) /*verilator dpi_c_decl*/; + | ^~~~~~~~~~~~~~~~~~~~~~~~ + ... For error description see https://verilator.org/warn/BADVLTPRAGMA?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_dpi_decl_bad.py b/test_regress/t/t_dpi_decl_bad.py new file mode 100755 index 000000000..bdec1e4cd --- /dev/null +++ b/test_regress/t/t_dpi_decl_bad.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.lint(fails=True, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_dpi_decl_bad.v b/test_regress/t/t_dpi_decl_bad.v new file mode 100644 index 000000000..1d6466065 --- /dev/null +++ b/test_regress/t/t_dpi_decl_bad.v @@ -0,0 +1,14 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +module t; + import "DPI-C" function string func(input string arg) /*verilator dpi_c_decl*/; + + initial begin + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule