Support user-provided DPI-C function declarations (#7626) (#7893)

Fixes #7626.
This commit is contained in:
Jakub Michalski 2026-07-08 14:22:24 +02:00 committed by GitHub
parent 93d0443998
commit 646dcd3838
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 157 additions and 2 deletions

View File

@ -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 "<C function declaration>"*/
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 <arguments>*/
For Verilator developers only. When a source file containing these `fargs`

View File

@ -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; }

View File

@ -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(" ");

View File

@ -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");

View File

@ -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 + "*/");
}

View File

@ -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;
}

View File

@ -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; }

View File

@ -780,6 +780,7 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"})
%token<fl> yVL_CLOCKER "/*verilator clocker*/"
%token<fl> yVL_CLOCK_ENABLE "/*verilator clock_enable*/"
%token<fl> yVL_COVERAGE_BLOCK_OFF "/*verilator coverage_block_off*/"
%token<fl> yVL_DPI_C_DECL "/*verilator dpi_c_decl*/"
%token<fl> yVL_FORCEABLE "/*verilator forceable*/"
%token<fl> yVL_FULL_CASE "/*verilator full_case*/"
%token<fl> yVL_HIER_BLOCK "/*verilator hier_block*/"
@ -4928,6 +4929,14 @@ dpi_import_export<nodep>: // ==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);

View File

@ -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;
}

19
test_regress/t/t_dpi_decl.py Executable file
View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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