Fix finding single DPI exports from other scopes

This commit is contained in:
Wilson Snyder 2026-02-24 19:06:05 -05:00
parent e976424efa
commit 9bc88ff1bc
7 changed files with 132 additions and 24 deletions

View File

@ -3464,11 +3464,10 @@ void Verilated::mkdir(const char* dirname) VL_MT_UNSAFE {
void Verilated::quiesce() VL_MT_SAFE {
// Wait until all threads under this evaluation are quiet
// THREADED-TODO
}
int Verilated::exportFuncNum(const char* namep) VL_MT_SAFE {
return VerilatedImp::exportFind(namep);
return VerilatedImp::exportFindNum(namep);
}
void Verilated::endOfThreadMTaskGuts(VerilatedEvalMsgQueue* evalMsgQp) VL_MT_SAFE {
@ -3573,7 +3572,7 @@ VerilatedScope::~VerilatedScope() {
void VerilatedScope::exportInsert(int finalize, const char* namep, void* cb) VL_MT_UNSAFE {
// Slowpath - called once/scope*export at construction
// Insert a exported function into scope table
const int funcnum = VerilatedImp::exportInsert(namep);
const int funcnum = VerilatedImp::exportInsert(namep, cb);
if (!finalize) {
// Need two passes so we know array size to create
// Alternative is to dynamically stretch the array, which is more code, and slower.
@ -3630,6 +3629,25 @@ VerilatedVar* VerilatedScope::varFind(const char* namep) const VL_MT_SAFE_POSTIN
return nullptr;
}
void* VerilatedScope::exportFind(const VerilatedScope* scopep, int funcnum) VL_MT_SAFE {
if (VL_UNLIKELY(!scopep)) return exportFindNullError(funcnum);
// If function is registered only once across all scopes, fast path it.
// UVM for example expects to find uvm_polling_value_change_notify
// from a different scope than where decared.
VL_DEBUG_IFDEF(assert(funcnum < VerilatedImp::exportFlatCbs().size()););
{
void* const cbp = VerilatedImp::exportFlatCbs()[funcnum];
if (VL_LIKELY(cbp)) return cbp;
}
// Else specific scope-based export call
if (VL_LIKELY(funcnum < scopep->m_funcnumMax)) {
// m_callbacksp must be declared, as Max'es are > 0
void* const cbp = scopep->m_callbacksp[funcnum];
if (VL_LIKELY(cbp)) return cbp;
}
return scopep->exportFindError(funcnum); // LCOV_EXCL_LINE
}
void* VerilatedScope::exportFindNullError(int funcnum) VL_MT_SAFE {
// Slowpath - Called only when find has failed
const std::string msg = ("Testbench C called '"s + VerilatedImp::exportName(funcnum)

View File

@ -744,15 +744,7 @@ public: // But internals only - called from verilated modules, VerilatedSyms
void scopeDump() const;
void* exportFindError(int funcnum) const VL_MT_SAFE;
static void* exportFindNullError(int funcnum) VL_MT_SAFE;
static void* exportFind(const VerilatedScope* scopep, int funcnum) VL_MT_SAFE {
if (VL_UNLIKELY(!scopep)) return exportFindNullError(funcnum);
if (VL_LIKELY(funcnum < scopep->m_funcnumMax)) {
// m_callbacksp must be declared, as Max'es are > 0
return scopep->m_callbacksp[funcnum];
} else { // LCOV_EXCL_LINE
return scopep->exportFindError(funcnum); // LCOV_EXCL_LINE
}
}
static void* exportFind(const VerilatedScope* scopep, int funcnum) VL_MT_SAFE;
Type type() const { return m_type; }
};

View File

@ -433,6 +433,10 @@ protected:
ExportNameMap m_exportMap VL_GUARDED_BY(m_exportMutex);
int m_exportNext VL_GUARDED_BY(m_exportMutex) = 0; // Next export funcnum
// No guard, as init-time loaded
std::vector<void*> m_exportFlatCbs; // Exports when only single scope registered
std::vector<bool> m_exportFlatMulti; // Multiple scopes registerd; cannot use m_exportScopes
// CONSTRUCTORS
VerilatedImpData() = default;
};
@ -542,8 +546,8 @@ public:
// in the design that also happen to have our same callback function.
// Rather than a 2D map, the integer scheme saves 500ish ns on a likely
// miss at the cost of a multiply, and all lookups move to slowpath.
static int exportInsert(const char* namep) VL_MT_SAFE {
// Slow ok - called once/function at creation
private:
static int exportInsertName(const char* namep) VL_MT_SAFE {
const VerilatedLockGuard lock{s().m_exportMutex};
const auto it = s().m_exportMap.find(namep);
if (it == s().m_exportMap.end()) {
@ -553,15 +557,37 @@ public:
return it->second;
}
}
static int exportFind(const char* namep) VL_MT_SAFE {
public:
static int exportInsert(const char* namep, void* cb) VL_MT_SAFE {
const int funcnum = VerilatedImp::exportInsertName(namep);
const VerilatedLockGuard lock{s().m_exportMutex};
// Slow ok - called once/function at creation
if (funcnum >= s().m_exportFlatCbs.size()) {
s().m_exportFlatCbs.resize(funcnum + 1);
s().m_exportFlatMulti.resize(funcnum + 1);
}
if (!s().m_exportFlatMulti[funcnum]) {
if (s().m_exportFlatCbs[funcnum] == cb) { // Duplicate
} else if (!s().m_exportFlatCbs[funcnum]) { // First
s().m_exportFlatCbs[funcnum] = cb;
} else { // Multiple registrants
s().m_exportFlatCbs[funcnum] = nullptr;
s().m_exportFlatMulti[funcnum] = true;
}
}
return funcnum;
}
static int exportFindNum(const char* namep) VL_MT_SAFE {
const VerilatedLockGuard lock{s().m_exportMutex};
const auto& it = s().m_exportMap.find(namep);
if (VL_LIKELY(it != s().m_exportMap.end())) return it->second;
const std::string msg = ("%Error: Testbench C called "s + namep
+ " but no such DPI export function name exists in ANY model");
const std::string msg = "%Error: Testbench C called "s + namep
+ " but no such DPI export function name exists in ANY model";
VL_FATAL_MT("unknown", 0, "", msg.c_str());
return -1;
}
static const std::vector<void*>& exportFlatCbs() VL_MT_SAFE { return s().m_exportFlatCbs; }
static const char* exportName(int funcnum) VL_MT_SAFE {
// Slowpath; find name for given export; errors only so no map to reverse-map it
const VerilatedLockGuard lock{s().m_exportMutex};

View File

@ -7,15 +7,23 @@
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
module t;
s s();
s s ();
other other ();
import "DPI-C" context function void dpix_run_tests();
initial dpix_run_tests();
import "DPI-C" context function void dpix_run_tests();
initial dpix_run_tests();
endmodule
module s;
export "DPI-C" task dpix_task;
task dpix_task();
$write("Hello in %m\n");
endtask
export "DPI-C" task dpix_task;
task dpix_task();
$write("Hello in %m\n");
endtask
endmodule
module other;
export "DPI-C" task dpix_task;
task dpix_task();
$write("Hello in %m\n");
endtask
endmodule

View File

@ -0,0 +1,25 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2010 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
#include <verilated.h>
//======================================================================
#include "Vt_dpi_export_scope_flat__Dpi.h"
#ifdef NEED_EXTERNS
extern "C" {
extern void dpix_task();
}
#endif
//======================================================================
void dpix_run_tests() {
dpix_task(); // Wrong scope
}

View File

@ -0,0 +1,18 @@
#!/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: 2024 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('simulator')
test.compile(v_flags2=["--binary", test.pli_filename])
test.execute()
test.passes()

View File

@ -0,0 +1,21 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// 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: 2020 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
module t;
s s ();
import "DPI-C" context function void dpix_run_tests();
initial dpix_run_tests();
endmodule
module s;
export "DPI-C" task dpix_task;
task dpix_task();
$write("Hello in %m\n");
endtask
endmodule