diff --git a/include/verilated.cpp b/include/verilated.cpp index 0c1e2e4dc..361801e27 100644 --- a/include/verilated.cpp +++ b/include/verilated.cpp @@ -3740,11 +3740,17 @@ void VerilatedContext::statsPrintSummary() VL_MT_UNSAFE { // VerilatedContext:: Methods - scopes void VerilatedContext::scopesDump() const VL_MT_SAFE { - const VerilatedLockGuard lock{m_impdatap->m_nameMutex}; - VL_PRINTF_MT(" scopesDump:\n"); - for (const auto& i : m_impdatap->m_nameMap) { - const VerilatedScope* const scopep = i.second; - scopep->scopeDump(); + { + const VerilatedLockGuard lock{m_impdatap->m_nameMutex}; + VL_PRINTF_MT(" scopesDump:\n"); + for (const auto& i : m_impdatap->m_nameMap) { + const VerilatedScope* const scopep = i.second; + scopep->scopeDump(); + } + } + { + const VerilatedLockGuard lock{m_impdatap->m_ifaceRefMutex}; + for (const auto& i : m_impdatap->m_ifaceRefMap) i.second.ifaceRefDump(); } VL_PRINTF_MT("\n"); } @@ -3774,6 +3780,31 @@ const VerilatedScopeNameMap* VerilatedContext::scopeNameMap() VL_MT_SAFE { return &(impp()->m_impdatap->m_nameMap); } +void VerilatedContextImp::ifaceRefInsert(const VerilatedIfaceRef& ifaceRef) VL_MT_SAFE { + // Slow ok - called once/interface-reference at construction + const VerilatedLockGuard lock{m_impdatap->m_ifaceRefMutex}; + m_impdatap->m_ifaceRefMap.emplace(ifaceRef.fullname(), ifaceRef); +} +void VerilatedContextImp::ifaceRefErase(const std::string& fullname, + const VerilatedScope* scopep) VL_MT_SAFE { + // Slow ok - called once/interface-reference at destruction + const VerilatedLockGuard lock{m_impdatap->m_ifaceRefMutex}; + const auto it = m_impdatap->m_ifaceRefMap.find(fullname); + // Models sharing an instance name collide on the key; only erase our own, + // so tearing one down leaves another's live reference registered + if (it != m_impdatap->m_ifaceRefMap.end() && it->second.scopep() == scopep) { + m_impdatap->m_ifaceRefMap.erase(it); + } +} +const VerilatedIfaceRef* +VerilatedContext::ifaceRefFind(const char* namep) const VL_MT_SAFE_POSTINIT { + // Thread safe only assuming this is called only after model construction completed + const VerilatedLockGuard lock{m_impdatap->m_ifaceRefMutex}; + const auto& it = m_impdatap->m_ifaceRefMap.find(namep); + if (VL_UNLIKELY(it == m_impdatap->m_ifaceRefMap.end())) return nullptr; + return &it->second; +} + //====================================================================== // VerilatedContext:: Methods - trace @@ -4307,6 +4338,41 @@ void VerilatedScope::scopesConstructFromTable(const VlScopeTableEntry* entp, siz } } +// Prefix with the model instance name, as VerilatedScope's constructor does +static std::string vl_ifaceRefFullname(const VerilatedSyms* symsp, const char* suffixp) { + const char* const prefixp = symsp->name(); + std::string out{prefixp}; + if (*prefixp && *suffixp) out += '.'; + out += suffixp; + return out; +} + +void VerilatedScope::ifaceRefsInsertFromTable(const VlIfaceRefTableEntry* entp, size_t n, + VerilatedSyms* symsp) VL_MT_UNSAFE { + // Use the model's own context; at destruction threadContextp() may be another's + VerilatedContextImp* const impp = symsp->_vm_contextp__->impp(); + uint8_t* const base = reinterpret_cast(symsp); + for (size_t i = 0; i < n; ++i) { + const VlIfaceRefTableEntry& e = entp[i]; + const VerilatedScope* const scopep + = *reinterpret_cast(base + e.ptrOffset); + impp->ifaceRefInsert( + VerilatedIfaceRef{scopep, e.namep, vl_ifaceRefFullname(symsp, e.suffixp), e.modportp}); + } +} + +void VerilatedScope::ifaceRefsEraseFromTable(const VlIfaceRefTableEntry* entp, size_t n, + const VerilatedSyms* symsp) VL_MT_UNSAFE { + VerilatedContextImp* const impp = symsp->_vm_contextp__->impp(); + uint8_t* const base = reinterpret_cast(const_cast(symsp)); + for (size_t i = 0; i < n; ++i) { + const VlIfaceRefTableEntry& e = entp[i]; + const VerilatedScope* const scopep + = *reinterpret_cast(base + e.ptrOffset); + impp->ifaceRefErase(vl_ifaceRefFullname(symsp, e.suffixp), scopep); + } +} + VerilatedVar* VerilatedScope::varInsertSized(const char* namep, void* datap, bool isParam, VerilatedVarType vltype, int vlflags, int udims, uint32_t entSize...) VL_MT_UNSAFE { @@ -4452,6 +4518,12 @@ void VerilatedScope::scopeDump() const { } } +void VerilatedIfaceRef::ifaceRefDump() const VL_MT_SAFE_POSTINIT { + VL_PRINTF_MT(" IFACEREF %p: %s -> %s", this, fullname(), scopep()->name()); + if (hasModport()) VL_PRINTF_MT(".%s", modport()); + VL_PRINTF_MT("\n"); +} + void VerilatedHierarchy::add(const VerilatedScope* fromp, const VerilatedScope* top) { VerilatedImp::hierarchyAdd(fromp, top); } diff --git a/include/verilated.h b/include/verilated.h index 1176784ca..20e251a40 100644 --- a/include/verilated.h +++ b/include/verilated.h @@ -100,6 +100,9 @@ class VerilatedFstC; class VerilatedFstSc; class VerilatedScope; class VerilatedScopeNameMap; +class VerilatedIfaceRef; +class VerilatedIfaceRefMap; +struct VlIfaceRefTableEntry; template class VerilatedTrace; class VerilatedTraceBaseC; @@ -874,6 +877,9 @@ public: const VerilatedScope* scopeFind(const char* namep) const VL_MT_SAFE; const VerilatedScopeNameMap* scopeNameMap() VL_MT_SAFE; + // Internal: Find interface reference by fully qualified path + const VerilatedIfaceRef* ifaceRefFind(const char* namep) const VL_MT_SAFE_POSTINIT; + // Internal: Serialization setup static constexpr size_t serialized1Size() VL_PURE { return sizeof(m_s); } void* serialized1Ptr() VL_MT_UNSAFE { return &m_s; } @@ -903,6 +909,32 @@ public: // But for internal use only virtual const char* name() const = 0; }; +// An interface reference port, and the concrete interface it is connected to. +// Used for VPI; references are not scopes, so are not in VerilatedScopeNameMap. +class VerilatedIfaceRef final { + const VerilatedScope* m_scopep = nullptr; // Concrete interface referred to + const char* m_namep = ""; // Name of the reference port + // Fully qualified path; owned, as the instance name prefix is set at construction + std::string m_fullname; + const char* m_modportp = ""; // Modport name, or "" if none +public: + VerilatedIfaceRef() = default; + VerilatedIfaceRef(const VerilatedScope* scopep, const char* namep, const std::string& fullname, + const char* modportp) + : m_scopep{scopep} + , m_namep{namep} + , m_fullname{fullname} + , m_modportp{modportp} {} + ~VerilatedIfaceRef() = default; + // ACCESSORS + const VerilatedScope* scopep() const VL_MT_SAFE_POSTINIT { return m_scopep; } + const char* name() const VL_MT_SAFE_POSTINIT { return m_namep; } + const char* fullname() const VL_MT_SAFE_POSTINIT { return m_fullname.c_str(); } + const char* modport() const VL_MT_SAFE_POSTINIT { return m_modportp; } + bool hasModport() const VL_MT_SAFE_POSTINIT { return m_modportp[0] != '\0'; } + void ifaceRefDump() const VL_MT_SAFE_POSTINIT; +}; + //=========================================================================== // Verilator scope information class // Used for internal VPI implementation, and introspection into scopes @@ -914,8 +946,9 @@ public: enum Type : uint8_t { SCOPE_MODULE, SCOPE_OTHER, - SCOPE_PACKAGE - }; // Type of a scope, currently only module and package are interesting + SCOPE_PACKAGE, + SCOPE_INTERFACE + }; // Type of a scope, currently only module, package and interface are interesting private: // Fastpath: VerilatedSyms* const m_symsp; // Symbol table @@ -948,6 +981,10 @@ public: // But internals only - called from verilated modules, VerilatedSyms void varsInsertFromTable(const VlVarTableEntry* entp, size_t n, void* basep) VL_MT_UNSAFE; static void scopesConstructFromTable(const VlScopeTableEntry* entp, size_t n, VerilatedSyms* symsp) VL_MT_UNSAFE; + static void ifaceRefsInsertFromTable(const VlIfaceRefTableEntry* entp, size_t n, + VerilatedSyms* symsp) VL_MT_UNSAFE; + static void ifaceRefsEraseFromTable(const VlIfaceRefTableEntry* entp, size_t n, + const VerilatedSyms* symsp) VL_MT_UNSAFE; // ACCESSORS const char* name() const VL_MT_SAFE_POSTINIT { return m_namep; } const char* identifier() const VL_MT_SAFE_POSTINIT { return m_identifierp; } @@ -964,6 +1001,15 @@ public: // But internals only - called from verilated modules, VerilatedSyms VerilatedContext* contextp() const { return m_symsp->_vm_contextp__; } }; +// One interface reference, consumed by VerilatedScope::ifaceRefsInsertFromTable() +struct VlIfaceRefTableEntry final { + uint32_t ptrOffset; // offsetof of the referred-to __Vscopep_* member within the Syms object + const char* namep; // Name of the reference port + // Path within the model; as VlScopeTableEntry::namep, instance name prepended at construction + const char* suffixp; + const char* modportp; // Modport name, or "" if none +}; + // One scope, consumed by VerilatedScope::scopesConstructFromTable(); replaces // per-scope 'new VerilatedScope{...}' statements, which compiles faster at scale. struct VlScopeTableEntry final { diff --git a/include/verilated_imp.h b/include/verilated_imp.h index b03b86d16..bdfe29c0c 100644 --- a/include/verilated_imp.h +++ b/include/verilated_imp.h @@ -207,6 +207,11 @@ protected: // Used by scopeInsert, scopeFind, scopeErase, scopeNameMap mutable VerilatedMutex m_nameMutex; // Protect m_nameMap VerilatedScopeNameMap m_nameMap VL_GUARDED_BY(m_nameMutex); + + // Map of + // Used by ifaceRefInsert, ifaceRefFind, ifaceRefErase, ifaceRefMap + mutable VerilatedMutex m_ifaceRefMutex; // Protect m_ifaceRefMap + VerilatedIfaceRefMap m_ifaceRefMap VL_GUARDED_BY(m_ifaceRefMutex); }; //====================================================================== @@ -270,6 +275,10 @@ public: // But only for verilated*.cpp void scopeInsert(const VerilatedScope* scopep) VL_MT_SAFE; void scopeErase(const VerilatedScope* scopep) VL_MT_SAFE; + // METHODS - interface references - INTERNAL only for verilated*.cpp + void ifaceRefInsert(const VerilatedIfaceRef& ifaceRef) VL_MT_SAFE; + void ifaceRefErase(const std::string& fullname, const VerilatedScope* scopep) VL_MT_SAFE; + // METHODS - file IO - INTERNAL only for verilated*.cpp IData fdNewMcd(const char* filenamep) VL_MT_SAFE_EXCLUDES(m_fdMutex) { diff --git a/include/verilated_syms.h b/include/verilated_syms.h index 949dbde9e..e40187e2f 100644 --- a/include/verilated_syms.h +++ b/include/verilated_syms.h @@ -72,4 +72,13 @@ public: ~VerilatedHierarchyMap() = default; }; +// Map of sorted interface reference names to the concrete interface they refer to +// Keyed by value, as the full name is built at construction +// This is a class instead of typedef/using to allow forward declaration in verilated.h +class VerilatedIfaceRefMap final : public std::map> { +public: + VerilatedIfaceRefMap() = default; + ~VerilatedIfaceRefMap() = default; +}; + #endif // Guard diff --git a/include/verilated_vpi.cpp b/include/verilated_vpi.cpp index cae47ca31..c5ec0f68f 100644 --- a/include/verilated_vpi.cpp +++ b/include/verilated_vpi.cpp @@ -363,11 +363,13 @@ protected: public: explicit VerilatedVpioScope(const VerilatedScope* scopep) : m_scopep{scopep} { - m_fullname = m_scopep->name(); - if (std::strncmp(m_fullname, "TOP.", 4) == 0) m_fullname += 4; + m_fullname = vpiFullnamep(m_scopep->name()); m_name = m_scopep->identifier(); m_defname = m_scopep->defname(); } + static const char* vpiFullnamep(const char* namep) VL_PURE { + return (std::strncmp(namep, "TOP.", 4) == 0) ? namep + 4 : namep; + } ~VerilatedVpioScope() override = default; // cppcheck-suppress duplInheritedMember static VerilatedVpioScope* castp(vpiHandle h) { @@ -721,6 +723,17 @@ public: uint32_t type() const override { return vpiModule; } }; +class VerilatedVpioInterface final : public VerilatedVpioScope { +public: + explicit VerilatedVpioInterface(const VerilatedScope* scopep) + : VerilatedVpioScope{scopep} {} + // cppcheck-suppress duplInheritedMember + static VerilatedVpioInterface* castp(vpiHandle h) { + return dynamic_cast(reinterpret_cast(h)); + } + uint32_t type() const override { return vpiInterface; } +}; + class VerilatedVpioModuleIter final : public VerilatedVpio { const std::vector* m_vec; std::vector::const_iterator m_it; @@ -778,6 +791,8 @@ public: return (new VerilatedVpioScope{modp})->castVpiHandle(); } else if (itype == VerilatedScope::SCOPE_MODULE) { return (new VerilatedVpioModule{modp})->castVpiHandle(); + } else if (itype == VerilatedScope::SCOPE_INTERFACE) { + return (new VerilatedVpioInterface{modp})->castVpiHandle(); } } } @@ -803,6 +818,90 @@ public: uint32_t type() const override { return vpiPackage; } }; +class VerilatedVpioModport final : public VerilatedVpio { + const VerilatedScope* const m_scopep; // Interface the modport is within + const char* const m_name; // Modport name + const std::string m_fullname; // Interface full name + "." + modport name + +public: + VerilatedVpioModport(const VerilatedScope* scopep, const char* namep) + : m_scopep{scopep} + , m_name{namep} + , m_fullname{std::string{VerilatedVpioScope::vpiFullnamep(scopep->name())} + "." + namep} { + } + ~VerilatedVpioModport() override = default; + // cppcheck-suppress duplInheritedMember + static VerilatedVpioModport* castp(vpiHandle h) { + return dynamic_cast(reinterpret_cast(h)); + } + uint32_t type() const override { return vpiModport; } + const VerilatedScope* scopep() const { return m_scopep; } + const char* name() const override { return m_name; } + const char* fullname() const override { return m_fullname.c_str(); } + // IEEE 1800-2023 37.15 + const char* defname() const override { return m_name; } +}; + +class VerilatedVpioIfaceRef final : public VerilatedVpio { + // Held by value, as a handle may outlive the model that registered it + const VerilatedIfaceRef m_ifaceRef; + +public: + explicit VerilatedVpioIfaceRef(const VerilatedIfaceRef& ifaceRef) + : m_ifaceRef{ifaceRef} {} + ~VerilatedVpioIfaceRef() override = default; + // cppcheck-suppress duplInheritedMember + static VerilatedVpioIfaceRef* castp(vpiHandle h) { + return dynamic_cast(reinterpret_cast(h)); + } + uint32_t type() const override { return vpiRefObj; } + const VerilatedIfaceRef* ifaceRefp() const { return &m_ifaceRef; } + const char* name() const override { return m_ifaceRef.name(); } + const char* fullname() const override { + return VerilatedVpioScope::vpiFullnamep(m_ifaceRef.fullname()); + } + // IEEE 1800-2023 37.15: modport name, else the interface definition name + const char* defname() const override { + return m_ifaceRef.hasModport() ? m_ifaceRef.modport() : m_ifaceRef.scopep()->defname(); + } + vpiHandle actual() const { + if (m_ifaceRef.hasModport()) { + return (new VerilatedVpioModport{m_ifaceRef.scopep(), m_ifaceRef.modport()}) + ->castVpiHandle(); + } + return (new VerilatedVpioInterface{m_ifaceRef.scopep()})->castVpiHandle(); + } +}; + +class VerilatedVpioInterfaceIter final : public VerilatedVpio { + const std::vector* m_vec; + std::vector::const_iterator m_it; + +public: + explicit VerilatedVpioInterfaceIter(const std::vector& vec) + : m_vec{&vec} { + m_it = m_vec->begin(); + } + ~VerilatedVpioInterfaceIter() override = default; + // cppcheck-suppress duplInheritedMember + static VerilatedVpioInterfaceIter* castp(vpiHandle h) { + return dynamic_cast(reinterpret_cast(h)); + } + uint32_t type() const override { return vpiIterator; } + vpiHandle dovpi_scan() override { + while (true) { + if (m_it == m_vec->end()) { + delete this; // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle + return nullptr; + } + const VerilatedScope* const scopep = *m_it++; + if (scopep->type() == VerilatedScope::SCOPE_INTERFACE) { + return (new VerilatedVpioInterface{scopep})->castVpiHandle(); + } + } + } +}; + class VerilatedVpioInstanceIter final : public VerilatedVpio { const std::vector* m_vec; std::vector::const_iterator m_it; @@ -1999,6 +2098,9 @@ const char* VerilatedVpiError::strFromVpiMethod(PLI_INT32 vpiVal) VL_PURE { "vpiStmt" }; // clang-format on + // SystemVerilog relations are numbered far above the Verilog ones + if (vpiVal == vpiActual) return "vpiActual"; + if (vpiVal >= vpiPackage && vpiVal <= vpiPropFormalDecl) return strFromVpiObjType(vpiVal); if (vpiVal > vpiStmt || vpiVal < vpiCondition) return "*undefined*"; return names[vpiVal - vpiCondition]; } @@ -2402,6 +2504,8 @@ void VerilatedVpiError::selfTest() VL_MT_UNSAFE_ONE { SELF_CHECK_ENUM_STR(strFromVpiMethod, vpiCondition); SELF_CHECK_ENUM_STR(strFromVpiMethod, vpiStmt); + SELF_CHECK_ENUM_STR(strFromVpiMethod, vpiActual); + SELF_CHECK_ENUM_STR(strFromVpiMethod, vpiInterface); SELF_CHECK_ENUM_STR(strFromVpiCallbackReason, cbValueChange); SELF_CHECK_ENUM_STR(strFromVpiCallbackReason, cbAtEndOfSimTime); @@ -2771,6 +2875,10 @@ vpiHandle vpi_handle_by_name(PLI_BYTE8* namep, vpiHandle scope) { const VerilatedVpioScope* const voScopep = VerilatedVpioScope::castp(scope); const VerilatedVpioVar* const voVarp = VerilatedVpioVar::castp(scope); + // Not scopes, so no name resolves relative to them; must not fall through to + // the unprefixed lookup below, which would resolve from the top level + if (VerilatedVpioIfaceRef::castp(scope) || VerilatedVpioModport::castp(scope)) return nullptr; + if (0 == std::strncmp(scopeAndName.c_str(), "$root.", std::strlen("$root."))) { scopeAndName.erase(0, std::strlen("$root.")); } else if (voScopep) { @@ -2798,8 +2906,15 @@ vpiHandle vpi_handle_by_name(PLI_BYTE8* namep, vpiHandle scope) { if (scopep->type() == VerilatedScope::SCOPE_PACKAGE) { return (new VerilatedVpioPackage{scopep})->castVpiHandle(); } + if (scopep->type() == VerilatedScope::SCOPE_INTERFACE) { + return (new VerilatedVpioInterface{scopep})->castVpiHandle(); + } return (new VerilatedVpioScope{scopep})->castVpiHandle(); } + if (const VerilatedIfaceRef* const ifaceRefp + = Verilated::threadContextp()->ifaceRefFind(scopeAndName.c_str())) { + return (new VerilatedVpioIfaceRef{*ifaceRefp})->castVpiHandle(); + } std::string basename = scopeAndName; std::string scopename; std::string::size_type prevpos = std::string::npos; @@ -2959,6 +3074,24 @@ vpiHandle vpi_handle(PLI_INT32 type, vpiHandle object) { const int32_t val = vop->index().back(); return (new VerilatedVpioConst{val})->castVpiHandle(); } + case vpiActual: { + if (const VerilatedVpioIfaceRef* const vop = VerilatedVpioIfaceRef::castp(object)) { + return vop->actual(); + } + VL_VPI_WARNING_(__FILE__, __LINE__, + "%s: Unsupported vpiHandle '%p' for type '%s', nothing will be returned", + __func__, object, VerilatedVpiError::strFromVpiMethod(type)); + return nullptr; + } + case vpiInterface: { + if (const VerilatedVpioModport* const vop = VerilatedVpioModport::castp(object)) { + return (new VerilatedVpioInterface{vop->scopep()})->castVpiHandle(); + } + VL_VPI_WARNING_(__FILE__, __LINE__, + "%s: Unsupported vpiHandle '%p' for type '%s', nothing will be returned", + __func__, object, VerilatedVpiError::strFromVpiMethod(type)); + return nullptr; + } case vpiScope: { const VerilatedVpioVarBase* const vop = VerilatedVpioVarBase::castp(object); if (VL_UNLIKELY(!vop)) return nullptr; @@ -3025,6 +3158,15 @@ vpiHandle vpi_iterate(PLI_INT32 type, vpiHandle object) { if (it == map->end()) return nullptr; return ((new VerilatedVpioModuleIter{it->second})->castVpiHandle()); } + case vpiInterface: { + // IEEE 1800-2023 37.5: interfaces are a one-to-many of a module + const VerilatedVpioScope* const vop = VerilatedVpioScope::castp(object); + const VerilatedHierarchyMap* const map = VerilatedImp::hierarchyMap(); + const VerilatedScope* const modp = vop ? vop->scopep() : nullptr; + const auto it = vlstd::as_const(map)->find(const_cast(modp)); + if (it == map->end()) return nullptr; + return ((new VerilatedVpioInterfaceIter{it->second})->castVpiHandle()); + } case vpiInternalScope: { const VerilatedVpioScope* const vop = VerilatedVpioScope::castp(object); const VerilatedHierarchyMap* const map = VerilatedImp::hierarchyMap(); diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index 949bbc2a7..9e38d1bf3 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -684,7 +684,7 @@ class AstCell final : public AstNode { // @astgen op2 := paramsp : List[AstPin] // List of parameter assignments // @astgen op3 := rangep : List[AstRange] // Range(s) for arrayed instances; multi-dim chains // via nextp() - // @astgen op4 := intfRefsp : List[AstIntfRef] // List of interface references, for tracing + // @astgen op4 := intfRefsp : List[AstIntfRef] // List of interface references, for tracing/VPI // // @astgen ptr := m_modp : Optional[AstNodeModule] // [AfterLink] Pointer to module instanced FileLine* m_modNameFileline; // Where module the cell instances token was @@ -1351,13 +1351,21 @@ public: }; class AstIntfRef final : public AstNode { // An interface reference - string m_name; // Name of the reference + string m_name; // Hierarchical path of the reference + string m_baseName; // Final component of m_name, i.e. the reference port name + string m_modportName; // "" = no modport, else name of the modport referenced public: - AstIntfRef(FileLine* fl, const string& name) + AstIntfRef(FileLine* fl, const string& name, const string& baseName, const string& modportName) : ASTGEN_SUPER_IntfRef(fl) - , m_name{name} {} - string name() const override VL_MT_STABLE { return m_name; } + , m_name{name} + , m_baseName{baseName} + , m_modportName{modportName} {} ASTGEN_MEMBERS_AstIntfRef; + void dump(std::ostream& str = std::cout) const override; + void dumpJson(std::ostream& str = std::cout) const override; + string name() const override VL_MT_STABLE { return m_name; } + string baseName() const { return m_baseName; } + string modportName() const { return m_modportName; } }; class AstLibrary final : public AstNode { // Parents: NETLIST diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 9faf92315..0e4aaf854 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -1653,6 +1653,16 @@ AstNodeExpr* AstInsideRange::newAndFromInside(AstNodeExpr* exprp, AstNodeExpr* l bp->fileline()->modifyWarnOff(V3ErrorCode::CMPCONST, true); return new AstLogAnd{fileline(), ap, bp}; } +void AstIntfRef::dump(std::ostream& str) const { // LCOV_EXCL_START + Super::dump(str); + if (baseName() != "") str << " base=" << baseName(); + if (modportName() != "") str << " mp=" << modportName(); +} // LCOV_EXCL_STOP +void AstIntfRef::dumpJson(std::ostream& str) const { + dumpJsonStrFunc(str, baseName); + dumpJsonStrFunc(str, modportName); + dumpJsonGen(str); +} bool AstJumpBlock::getPurityRecurse() const { for (AstNode* stmtp = this->stmtsp(); stmtp; stmtp = stmtp->nextp()) { if (!stmtp->isPure()) return false; diff --git a/src/V3EmitCSyms.cpp b/src/V3EmitCSyms.cpp index ba7773846..bf20e0f71 100644 --- a/src/V3EmitCSyms.cpp +++ b/src/V3EmitCSyms.cpp @@ -61,6 +61,18 @@ class EmitCSyms final : EmitCBaseVisitorConst { , m_timeunit{timeunit} , m_type{type} {} }; + struct IfaceRefData final { + const AstScope* const m_scopep; // Concrete interface scope referred to + const std::string m_suffix; // Path relative to the model instance + const std::string m_name; // Name of the reference port + const std::string m_modportName; // "" = no modport + IfaceRefData(const AstScope* scopep, const std::string& suffix, const std::string& name, + const std::string& modportName) + : m_scopep{scopep} + , m_suffix{suffix} + , m_name{name} + , m_modportName{modportName} {} + }; struct ScopeFuncData final { const AstScopeName* const m_scopep; const AstCFunc* const m_cfuncp; @@ -102,6 +114,7 @@ class EmitCSyms final : EmitCBaseVisitorConst { ScopeNames m_scopeNames; // Each unique AstScopeName. Dpi scopes added later ScopeNames m_dpiScopeNames; // Each unique AstScopeName for DPI export ScopeNames m_vpiScopeCandidates; // All scopes for VPI + std::vector m_ifaceRefs; // Each interface reference, for VPI // The actual hierarchy of scopes std::map> m_vpiScopeHierarchy; int m_coverBins = 0; // Global coverage bin number for non-object helper functions @@ -117,6 +130,8 @@ class EmitCSyms final : EmitCBaseVisitorConst { // Single VlScopeTableEntry[] table for all scopes, built in getSymCtorStmts() std::string m_scopeTableName; std::vector m_scopeTableRows; + std::string m_ifaceRefTableName; + std::vector m_ifaceRefTableRows; // METHODS void emitSymHdr(); @@ -689,6 +704,54 @@ class EmitCSyms final : EmitCBaseVisitorConst { } } + void collectIfaceRefs(const AstScope* nodep) { + const AstCell* const cellp = nodep->aboveCellp(); + // Exclude classes inside interfaces; the cell's modp is the interface, not the Class + if (!cellp || !VN_IS(cellp->modp(), Iface) || !VN_IS(nodep->modp(), Iface)) return; + + // vpiName() to match the scope table these are looked up alongside. Inlining + // flattens the hierarchy but leaves the inlined levels in the scope name, which + // therefore carries the full enclosing path. + const std::string path = AstNode::vpiName(nodep->name()); + const std::string instName = AstNode::vpiName(cellp->origName()); + UASSERT_OBJ(path.length() > instName.length() && VString::endsWith(path, instName), nodep, + "Interface scope name " << path << " does not end with instance name " + << instName); + const std::string parentPath = path.substr(0, path.length() - instName.length()); + + for (AstIntfRef* intfRefp = cellp->intfRefsp(); intfRefp; + intfRefp = VN_AS(intfRefp->nextp(), IntfRef)) { + const std::string refName = AstNode::vpiName(intfRefp->name()); + // Assume only references under the same parent scope reference the + // same interface. Same limitation as the trace path in V3TraceDecl. + if (!VString::startsWith(refName, parentPath)) continue; + m_ifaceRefs.emplace_back(nodep, refName, AstNode::vpiName(intfRefp->baseName()), + intfRefp->modportName()); + } + } + + void buildIfaceRefTable() { + if (m_ifaceRefs.empty()) return; + const std::string symClass = symClassName(); + for (const IfaceRefData& ird : m_ifaceRefs) { + const std::string scopeSym = scopeSymString(ird.m_scopep->name()); + // Only reference scopes that actually made it into the scope table + if (m_scopeNames.find(scopeSym) == m_scopeNames.end()) continue; + std::string row + = "{offsetof(" + symClass + ", " + protect("__Vscopep_" + scopeSym) + "), \""; + row += V3OutFormatter::quoteNameControls(VIdProtect::protectWordsIf(ird.m_name, true)); + row += "\", \""; + row += V3OutFormatter::quoteNameControls( + VIdProtect::protectWordsIf(ird.m_suffix, true)); + row += "\", \""; + row += V3OutFormatter::quoteNameControls( + VIdProtect::protectWordsIf(ird.m_modportName, true)); + row += "\"}"; + m_ifaceRefTableRows.emplace_back(std::move(row)); + } + if (!m_ifaceRefTableRows.empty()) m_ifaceRefTableName = symClass + "__VpiIfaceRefTable"; + } + void buildVpiHierarchy() { for (const auto& itpair : m_scopeNames) { const std::string symName = itpair.second.m_symName; @@ -778,6 +841,7 @@ class EmitCSyms final : EmitCBaseVisitorConst { if (v3Global.opt.vpi() && !nodep->isTop()) { const std::string type = VN_IS(nodep->modp(), Package) ? "SCOPE_PACKAGE" // + : VN_IS(nodep->modp(), Iface) ? "SCOPE_INTERFACE" // : "SCOPE_MODULE"; const int timeunit = m_modp->timeunit().powerOfTen(); m_vpiScopeCandidates.emplace( // @@ -786,6 +850,7 @@ class EmitCSyms final : EmitCBaseVisitorConst { std::forward_as_tuple(nodep, scopeSymString(nodep->name()), AstNode::vpiName(nodep->shortName()), nodep->modp()->origName(), timeunit, type)); + collectIfaceRefs(nodep); } iterateChildrenConst(nodep); } @@ -1046,19 +1111,22 @@ void EmitCSyms::emitSymImpPreamble() { // So split ctor sub-functions in other translation units can reference // the VPI variable tables defined below. - if (!m_varTables.empty() || !m_scopeTableRows.empty()) { + if (!m_varTables.empty() || !m_scopeTableRows.empty() || !m_ifaceRefTableRows.empty()) { for (const auto& kv : m_varTables) { puts("extern const VlVarTableEntry " + kv.first + "[];\n"); } if (!m_scopeTableRows.empty()) { puts("extern const VlScopeTableEntry " + m_scopeTableName + "[];\n"); } + if (!m_ifaceRefTableRows.empty()) { + puts("extern const VlIfaceRefTableEntry " + m_ifaceRefTableName + "[];\n"); + } puts("\n"); } } void EmitCSyms::emitVarTables() { - if (m_varTables.empty() && m_scopeTableRows.empty()) return; + if (m_varTables.empty() && m_scopeTableRows.empty() && m_ifaceRefTableRows.empty()) return; puts("\n// VPI VARIABLE/SCOPE TABLES\n"); // offsetof on the (non-standard-layout) generated module/Syms classes is well // defined on all supported compilers but warns; suppress just here. @@ -1084,6 +1152,15 @@ void EmitCSyms::emitVarTables() { } puts("};\n"); } + if (!m_ifaceRefTableRows.empty()) { + puts("extern const VlIfaceRefTableEntry " + m_ifaceRefTableName + "[] = {\n"); + for (const std::string& row : m_ifaceRefTableRows) { + ofp()->putsNoTracking(" "); + ofp()->putsNoTracking(row); + ofp()->putsNoTracking(",\n"); + } + puts("};\n"); + } puts("#if defined(__GNUC__)\n"); puts("# pragma GCC diagnostic pop\n"); puts("#endif\n"); @@ -1237,6 +1314,14 @@ std::vector EmitCSyms::getSymCtorStmts() { + std::to_string(m_scopeNames.size()) + ", this);"); } + // After the scopes above, as each row points at an already-built VerilatedScope + buildIfaceRefTable(); + if (!m_ifaceRefTableRows.empty()) { + add("// Setup interface references"); + add("VerilatedScope::ifaceRefsInsertFromTable(" + m_ifaceRefTableName + ", " + + std::to_string(m_ifaceRefTableRows.size()) + ", this);"); + } + emitScopeHier(stmts, false); if (v3Global.dpi()) { @@ -1374,6 +1459,13 @@ std::vector EmitCSyms::getSymDtorStmts() { add("_vm_pgoProfiler.write(\"" + topClassName() + "\", _vm_contextp__->profVltFilename());"); } + // Before the scopes below, as each row names a scope being torn down + if (!m_ifaceRefTableRows.empty()) { + add("// Tear down interface references"); + add("VerilatedScope::ifaceRefsEraseFromTable(" + m_ifaceRefTableName + ", " + + std::to_string(m_ifaceRefTableRows.size()) + ", this);"); + } + add("// Tear down scopes"); for (const auto& itpair : m_scopeNames) { const ScopeData& sd = itpair.second; diff --git a/src/V3Interface.cpp b/src/V3Interface.cpp index 0d5fe47fb..6be3361d5 100644 --- a/src/V3Interface.cpp +++ b/src/V3Interface.cpp @@ -1,6 +1,6 @@ // -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* -// DESCRIPTION: Verilator: Interface references for tracing +// DESCRIPTION: Verilator: Interface references for tracing and VPI // // Code available from: https://verilator.org // @@ -64,7 +64,12 @@ class InlineIntfRefVisitor final : public VNVisitor { if ((cellp = VN_CAST(fromVarp->user1p(), Cell)) || (cellp = irdtp->cellp())) { varp->user1p(cellp); const string alias = m_scope + "__DOT__" + pinp->name(); - cellp->addIntfRefsp(new AstIntfRef{pinp->fileline(), alias}); + // Prefer the port's own dtype; the source may have no modport + const AstIfaceRefDType* const portIrdtp = VN_CAST(varp->dtypep(), IfaceRefDType); + const string modportName + = portIrdtp ? portIrdtp->modportName() : irdtp->modportName(); + cellp->addIntfRefsp( + new AstIntfRef{pinp->fileline(), alias, pinp->name(), modportName}); } } @@ -92,7 +97,10 @@ class InlineIntfRefVisitor final : public VNVisitor { string alias; if (!m_scope.empty()) alias = m_scope + "__DOT__"; alias += varlp->name(); - cellp->addIntfRefsp(new AstIntfRef{varlp->fileline(), alias}); + const AstIfaceRefDType* const lirdtp = VN_CAST(varlp->dtypep(), IfaceRefDType); + const string modportName = lirdtp ? lirdtp->modportName() : ""; + cellp->addIntfRefsp( + new AstIntfRef{varlp->fileline(), alias, varlp->origName(), modportName}); } //-------------------- void visit(AstNodeExpr*) override {} // Accelerate diff --git a/src/V3Interface.h b/src/V3Interface.h index bfe702dd0..735591c1f 100644 --- a/src/V3Interface.h +++ b/src/V3Interface.h @@ -1,6 +1,6 @@ // -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* -// DESCRIPTION: Verilator: Interface references for tracing +// DESCRIPTION: Verilator: Interface references for tracing and VPI // // Code available from: https://verilator.org // diff --git a/src/V3TraceDecl.cpp b/src/V3TraceDecl.cpp index e0e99340f..923a5cd81 100644 --- a/src/V3TraceDecl.cpp +++ b/src/V3TraceDecl.cpp @@ -773,9 +773,10 @@ class TraceDeclVisitor final : public VNVisitor { // Save the mapping from the path of the reference to the scope m_pathToScopep.emplace(refName, nodep); - // No more need for AstIntfRef - intfRefp->unlinkFrBack(); - VL_DO_DANGLING(intfRefp->deleteTree(), intfRefp); + // No more need for AstIntfRef, unless V3EmitCSyms wants it for VPI + if (!v3Global.opt.vpi()) { + VL_DO_DANGLING(intfRefp->unlinkFrBack()->deleteTree(), intfRefp); + } } } } diff --git a/src/Verilator.cpp b/src/Verilator.cpp index 524e2b77a..76dbe1be9 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -327,7 +327,10 @@ static void process() { } } - if (v3Global.opt.trace()) V3Interface::interfaceAll(v3Global.rootp()); + // Interface references feed trace file aliases and VPI name resolution + if (v3Global.opt.trace() || v3Global.opt.vpi()) { + V3Interface::interfaceAll(v3Global.rootp()); + } // --PRE-FLAT OPTIMIZATIONS------------------ diff --git a/test_regress/t/t_vpi_dump.out b/test_regress/t/t_vpi_dump.out index ea04db1f8..65b59c73d 100644 --- a/test_regress/t/t_vpi_dump.out +++ b/test_regress/t/t_vpi_dump.out @@ -77,14 +77,12 @@ t (vpiModule) t vpiDefName=t subsig1 (vpiReg) t.cond_scope.sub_wrap_gen.my_sub.subsig1 subsig2 (vpiReg) t.cond_scope.sub_wrap_gen.my_sub.subsig2 vpiParameter: - intf_arr[0] (vpiModule) t.intf_arr[0] vpiDefName=TestInterface + intf_arr[0] (vpiInterface) t.intf_arr[0] vpiReg: addr (vpiReg) t.intf_arr[0].addr - vpiParameter: - intf_arr[1] (vpiModule) t.intf_arr[1] vpiDefName=TestInterface + intf_arr[1] (vpiInterface) t.intf_arr[1] vpiReg: addr (vpiReg) t.intf_arr[1].addr - vpiParameter: outer_scope[1] (vpiGenScope) t.outer_scope[1] vpiReg: scoped_param (vpiParameter) t.outer_scope[1].scoped_param vpiConstType=vpiDecConst diff --git a/test_regress/t/t_vpi_interface_ref.cpp b/test_regress/t/t_vpi_interface_ref.cpp new file mode 100644 index 000000000..3976577a0 --- /dev/null +++ b/test_regress/t/t_vpi_interface_ref.cpp @@ -0,0 +1,436 @@ +// -*- 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 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* + +#include "sv_vpi_user.h" + +#include +#include +#include +#include +#include + +// These require the above. Comment prevents clang-format moving them +#include "TestSimulator.h" +#include "TestVpi.h" + +int errors = 0; + +#define TEST_STRINGIFY_(x) #x +#define TEST_STRINGIFY(x) TEST_STRINGIFY_(x) + +// Path of the test top. With TEST_MODEL_NAME the model is constructed under +// that instance name, so every path is prefixed with it. +static std::string test_top() { + std::string top; +#ifdef TEST_MODEL_NAME + top = std::string{TEST_STRINGIFY(TEST_MODEL_NAME)} + "."; +#endif + top += TestSimulator::top(); + return top; +} + +// Report with the object path rather than a line number, so the failure +// messages are stable against edits to this file +static void check_failed(const std::string& msg) { + std::cout << "%Error: " << msg << std::endl; + ++errors; +} + +static void check_type(vpiHandle handle, const std::string& what, PLI_INT32 expected) { + const PLI_INT32 got = vpi_get(vpiType, handle); + if (got != expected) { + check_failed("vpi_get(vpiType, " + what + ") = " + strFromVpiObjType(got) + ", expected " + + strFromVpiObjType(expected)); + } +} + +static void check_fullname(vpiHandle handle, const std::string& what, + const std::string& expected) { + const char* const got = vpi_get_str(vpiFullName, handle); + if (!got) { + check_failed("vpi_get_str(vpiFullName, " + what + ") = NULL, expected '" + expected + "'"); + } else if (expected != got) { + check_failed("vpi_get_str(vpiFullName, " + what + ") = '" + got + "', expected '" + + expected + "'"); + } +} + +// Read a 32-bit variable relative to scope, checking it reads back as expected +static void check_read(vpiHandle scope, const std::string& scopeName, const std::string& varName, + uint32_t expected) { + const std::string what = scopeName + "." + varName; + const TestVpiHandle vh = vpi_handle_by_name(const_cast(varName.c_str()), scope); + if (!vh) { + check_failed("vpi_handle_by_name('" + varName + "', <" + scopeName + ">) = NULL"); + return; + } + + s_vpi_value value; + value.format = vpiIntVal; + vpi_get_value(vh, &value); + const uint32_t got = static_cast(value.value.integer); + if (got != expected) { + char buf[256]; + std::snprintf(buf, sizeof(buf), "read '%s' = 0x%08x, expected 0x%08x", what.c_str(), got, + expected); + check_failed(buf); + } +} + +// Write a 32-bit variable relative to scope +static void put_var(vpiHandle scope, const std::string& scopeName, const std::string& varName, + uint32_t newval) { + const TestVpiHandle vh = vpi_handle_by_name(const_cast(varName.c_str()), scope); + if (!vh) { + check_failed("vpi_handle_by_name('" + varName + "', <" + scopeName + ">) = NULL"); + return; + } + + s_vpi_value value; + value.format = vpiIntVal; + value.value.integer = newval; + vpi_put_value(vh, &value, NULL, vpiNoDelay); +} + +static void check_name(vpiHandle handle, const std::string& what, const std::string& expected) { + const char* const got = vpi_get_str(vpiName, handle); + if (!got) { + check_failed("vpi_get_str(vpiName, " + what + ") = NULL, expected '" + expected + "'"); + } else if (expected != got) { + check_failed("vpi_get_str(vpiName, " + what + ") = '" + got + "', expected '" + expected + + "'"); + } +} + +static void check_defname(vpiHandle handle, const std::string& what, const std::string& expected) { + const char* const got = vpi_get_str(vpiDefName, handle); + if (!got) { + check_failed("vpi_get_str(vpiDefName, " + what + ") = NULL, expected '" + expected + "'"); + } else if (expected != got) { + check_failed("vpi_get_str(vpiDefName, " + what + ") = '" + got + "', expected '" + expected + + "'"); + } +} + +// An interface reference and a modport are not scopes, so a name must not +// resolve relative to them. In particular this must not silently fall back +// to resolving from the top level, which would return an unrelated object. +static void check_not_a_scope(vpiHandle handle, const std::string& what) { + // "some_intf_var" exists in the interface, "top_collide" only at the top + // level. Neither may be found relative to this handle. + for (const char* const varName : {"some_intf_var", "top_collide"}) { + const TestVpiHandle vh = vpi_handle_by_name(const_cast(varName), handle); + if (vh) { + const char* const got = vpi_get_str(vpiFullName, vh); + check_failed("vpi_handle_by_name('" + std::string{varName} + "', <" + what + ">) = '" + + (got ? got : "") + "', expected NULL"); + } + } +} + +// vpiActual of an interface reference yields the concrete interface, or, for +// a modport-typed reference, the modport within it. Follow vpiInterface in +// that case so we always end up at the interface instance. +// Caller must release the result. +static vpiHandle concrete_of(vpiHandle refh, const std::string& what) { + check_type(refh, what, vpiRefObj); + check_not_a_scope(refh, what); + // IEEE 1800-2023 37.15: modport name for a modport-typed reference + check_defname(refh, what, "SomeModport"); + + vpiHandle actualh = vpi_handle(vpiActual, refh); + if (!actualh) { + check_failed("vpi_handle(vpiActual, " + what + ") = NULL"); + return NULL; + } + + // The port is modport-typed, so vpiActual is the modport + const std::string actWhat = "vpiActual of " + what; + check_type(actualh, actWhat, vpiModport); + check_fullname(actualh, actWhat, test_top() + ".concrete_intf.SomeModport"); + check_name(actualh, actWhat, "SomeModport"); + if (vpi_get_str(vpiDefName, actualh)) check_defname(actualh, actWhat, "SomeModport"); + check_not_a_scope(actualh, actWhat); + + vpiHandle intfh = vpi_handle(vpiInterface, actualh); + vpi_release_handle(actualh); + if (!intfh) { + check_failed("vpi_handle(vpiInterface, " + actWhat + ") = NULL"); + return NULL; + } + + const std::string intfWhat = "vpiInterface of " + actWhat; + check_type(intfh, intfWhat, vpiInterface); + check_fullname(intfh, intfWhat, test_top() + ".concrete_intf"); + return intfh; +} + +// A plain interface port has no modport, so vpiActual is the concrete +// interface directly rather than a modport. Caller must release the result. +static vpiHandle concrete_of_plain(const std::string& refName) { + const TestVpiHandle refh = vpi_handle_by_name(const_cast(refName.c_str()), NULL); + if (!refh) { + check_failed("vpi_handle_by_name('" + refName + "') = NULL"); + return NULL; + } + const std::string what = "'" + refName + "'"; + check_type(refh, what, vpiRefObj); + check_not_a_scope(refh, what); + // IEEE 1800-2023 37.15: interface definition name when there is no modport + check_defname(refh, what, "SomeIntf"); + + vpiHandle actualh = vpi_handle(vpiActual, refh); + if (!actualh) { + check_failed("vpi_handle(vpiActual, " + what + ") = NULL"); + return NULL; + } + const std::string actWhat = "vpiActual of " + what; + check_type(actualh, actWhat, vpiInterface); + check_fullname(actualh, actWhat, test_top() + ".concrete_intf"); + return actualh; +} + +// vpi_handle_by_name() of the full path to an interface reference, then on to +// the concrete interface. Caller must release the result. +static vpiHandle concrete_by_full_name(const std::string& refName) { + const TestVpiHandle refh = vpi_handle_by_name(const_cast(refName.c_str()), NULL); + if (!refh) { + check_failed("vpi_handle_by_name('" + refName + "') = NULL"); + return NULL; + } + const std::string what = "'" + refName + "'"; + check_name(refh, what, "intf_ref"); + check_fullname(refh, what, refName); + return concrete_of(refh, what); +} + +// vpi_handle_by_name() of just "intf_ref" relative to a handle for the +// instance containing it, which yields the interface reference directly. +// Caller must release the result. +static vpiHandle concrete_by_relative_name(const std::string& scopeName) { + const TestVpiHandle scopeh + = vpi_handle_by_name(const_cast(scopeName.c_str()), NULL); + if (!scopeh) { + check_failed("vpi_handle_by_name('" + scopeName + "') = NULL"); + return NULL; + } + + const TestVpiHandle refh = vpi_handle_by_name(const_cast("intf_ref"), scopeh); + if (!refh) { + check_failed("vpi_handle_by_name('intf_ref', <" + scopeName + ">) = NULL"); + return NULL; + } + const std::string what = "'intf_ref' in '" + scopeName + "'"; + check_fullname(refh, what, scopeName + ".intf_ref"); + return concrete_of(refh, what); +} + +static int mon_check() { + const std::string top = test_top(); + const std::string concrete = top + ".concrete_intf"; + const std::string barScope = top + ".bar"; + const std::string fooScope = top + ".bar.foo"; + const std::string barRef = barScope + ".intf_ref"; + const std::string fooRef = fooScope + ".intf_ref"; + + // Baseline: the concrete interface instance resolves and reads back the + // values set by the initial block + const TestVpiHandle concreteh + = vpi_handle_by_name(const_cast(concrete.c_str()), NULL); + if (!concreteh) { + check_failed("vpi_handle_by_name('" + concrete + "') = NULL"); + } else { + check_type(concreteh, "'" + concrete + "'", vpiInterface); + check_read(concreteh, concrete, "some_intf_var", 0x11112222); + check_read(concreteh, concrete, "other_intf_var", 0x33334444); + } + + // vpi_handle(vpiActual, ) reaches the concrete + // interface, from which the interface variables are accessible + { + const TestVpiHandle intfh = concrete_by_full_name(barRef); + if (intfh) { + const std::string what = "concrete via '" + barRef + "'"; + check_read(intfh, what, "some_intf_var", 0x11112222); + check_read(intfh, what, "other_intf_var", 0x33334444); + put_var(intfh, what, "some_intf_var", 0x55556666); + } + } + + // Same one level deeper, and the write above must be visible here + { + const TestVpiHandle intfh = concrete_by_full_name(fooRef); + if (intfh) { + const std::string what = "concrete via '" + fooRef + "'"; + check_read(intfh, what, "some_intf_var", 0x55556666); + check_read(intfh, what, "other_intf_var", 0x33334444); + } + } + + // Relative lookup of just "intf_ref" from a handle to the instance + { + const TestVpiHandle intfh = concrete_by_relative_name(fooScope); + if (intfh) { + const std::string what = "concrete via 'intf_ref' in '" + fooScope + "'"; + check_read(intfh, what, "some_intf_var", 0x55556666); + put_var(intfh, what, "other_intf_var", 0x77778888); + check_read(intfh, what, "other_intf_var", 0x77778888); + } + } + + // Relative lookup one level up, and the writes above must be visible + { + const TestVpiHandle intfh = concrete_by_relative_name(barScope); + if (intfh) { + const std::string what = "concrete via 'intf_ref' in '" + barScope + "'"; + check_read(intfh, what, "some_intf_var", 0x55556666); + check_read(intfh, what, "other_intf_var", 0x77778888); + // Final values, checked by the Verilog side + put_var(intfh, what, "some_intf_var", 0xfeedface); + put_var(intfh, what, "other_intf_var", 0xdeadbeef); + } + } + + { + const TestVpiHandle scopeh + = vpi_handle_by_name(const_cast(fooScope.c_str()), NULL); + if (scopeh) { + const TestVpiHandle actualh = vpi_handle(vpiActual, scopeh); + if (actualh) { check_failed("vpi_handle(vpiActual, <" + fooScope + ">) = non-NULL"); } + const TestVpiHandle intfh = vpi_handle(vpiInterface, scopeh); + if (intfh) { check_failed("vpi_handle(vpiInterface, <" + fooScope + ">) = non-NULL"); } + } + } + + // The interface must be enumerable by type from the scope containing it + { + const TestVpiHandle toph = vpi_handle_by_name(const_cast(top.c_str()), NULL); + TestVpiHandle it = toph ? vpi_iterate(vpiInterface, toph) : NULL; + if (!it) { + check_failed("vpi_iterate(vpiInterface, <" + top + ">) = NULL"); + } else { + check_type(it, "vpi_iterate(vpiInterface, <" + top + ">)", vpiIterator); + bool found = false; + while (vpiHandle ih = vpi_scan(it)) { + const char* const fn = vpi_get_str(vpiFullName, ih); + if (fn && concrete == fn) found = true; + check_type(ih, "vpiInterface iteration item", vpiInterface); + vpi_release_handle(ih); + } + it.freed(); + if (!found) { + check_failed("vpi_iterate(vpiInterface, <" + top + ">) did not yield '" + concrete + + "'"); + } + } + } + + { + TestVpiHandle it = vpi_iterate(vpiInterface, NULL); + if (it) { + if (vpiHandle ih = vpi_scan(it)) { + check_failed("vpi_iterate(vpiInterface, NULL) yielded a handle"); + vpi_release_handle(ih); + } else { + it.freed(); // vpi_scan at end released it + } + } + } + + { + const TestVpiHandle leafh + = vpi_handle_by_name(const_cast(concrete.c_str()), NULL); + if (!leafh) { + check_failed("vpi_handle_by_name('" + concrete + "') = NULL"); + } else { + const TestVpiHandle it = vpi_iterate(vpiInterface, leafh); + if (it) check_failed("vpi_iterate(vpiInterface, ) = non-NULL"); + } + } + + // A plain interface port: vpiActual is the interface, not a modport + { + const TestVpiHandle intfh = concrete_of_plain(fooScope + ".plain_ref"); + if (intfh) { + const std::string what = "concrete via '" + fooScope + ".plain_ref'"; + check_read(intfh, what, "some_intf_var", 0xfeedface); + check_read(intfh, what, "other_intf_var", 0xdeadbeef); + } + } + + // All of the above wrote through the concrete interface, so the writes + // must also be visible on the concrete instance path + if (concreteh) { + check_read(concreteh, concrete, "some_intf_var", 0xfeedface); + check_read(concreteh, concrete, "other_intf_var", 0xdeadbeef); + } + + return errors; +} + +//====================================================================== + +static PLI_INT32 value_change(t_cb_data* datap) { + // Some simulators also report the declaration initializer as a change; + // only the write in the initial block, which sets it, means run now + if (!datap->value || !datap->value->value.integer) return 0; + if (mon_check()) { + std::cout << "%Error: t_vpi_interface_ref.cpp: C Test failed with " << errors + << " error(s)" << std::endl; + vpi_control(vpiStop); + } + return 0; +} + +static PLI_INT32 start_of_sim(t_cb_data* /*datap*/) { + const std::string watched = test_top() + ".run_mon_check"; + TestVpiHandle varh = vpi_handle_by_name(const_cast(watched.c_str()), NULL); + if (!varh) { + check_failed("vpi_handle_by_name('" + watched + "') = NULL"); + vpi_control(vpiStop); + return 0; + } + + static s_vpi_time vpi_time; + vpi_time.type = vpiSuppressTime; + static s_vpi_value vpi_value; + vpi_value.format = vpiIntVal; + + static s_cb_data cb_data{}; + cb_data.reason = cbValueChange; + cb_data.cb_rtn = &value_change; + cb_data.obj = varh; + cb_data.time = &vpi_time; + cb_data.value = &vpi_value; + cb_data.user_data = NULL; + TestVpiHandle callback_h = vpi_register_cb(&cb_data); + varh.freed(); // Callback holds it + return 0; +} + +void vpi_compat_bootstrap(void) { + static s_vpi_time vpi_time; + vpi_time.high = 0; + vpi_time.low = 0; + vpi_time.type = vpiSimTime; + + s_cb_data cb_data{}; + cb_data.reason = cbStartOfSimulation; + cb_data.cb_rtn = &start_of_sim; + cb_data.obj = NULL; + cb_data.time = &vpi_time; + cb_data.value = NULL; + cb_data.index = 0; + cb_data.user_data = NULL; + TestVpiHandle callback_h = vpi_register_cb(&cb_data); +} + +void (*vlog_startup_routines[])() = {vpi_compat_bootstrap, 0}; diff --git a/test_regress/t/t_vpi_interface_ref.py b/test_regress/t/t_vpi_interface_ref.py new file mode 100755 index 000000000..eb7dd62ff --- /dev/null +++ b/test_regress/t/t_vpi_interface_ref.py @@ -0,0 +1,26 @@ +#!/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 + +# Not Icarus eligible +test.scenarios("vlt_all", "xrun") + +test.compile( + make_top_shell=False, + make_pli=True, + verilator_flags2=[ + "--binary --vpi --no-l2name --public-flat-rw", + test.pli_filename, + ], +) + +test.execute(use_libvpi=True) + +test.passes() diff --git a/test_regress/t/t_vpi_interface_ref.v b/test_regress/t/t_vpi_interface_ref.v new file mode 100644 index 000000000..6fd55f8ef --- /dev/null +++ b/test_regress/t/t_vpi_interface_ref.v @@ -0,0 +1,97 @@ +// 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: 2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +// Test VPI access to an interface reference port. A reference is found by +// its fully qualified path, or relative to a handle for the instance that +// declares it. Interface variables are not accessed through the reference +// itself; vpi_handle(vpiActual, ...) reaches the concrete interface, or the +// modport for a modport-typed port, and the variables are accessed there. + +interface SomeIntf; + + logic [31:0] some_intf_var; + logic [31:0] other_intf_var; + + // inout, as the VPI test writes as well as reads through the reference + modport SomeModport(inout some_intf_var, inout other_intf_var); + +endinterface + +// Deepest module, reached via "t.bar.foo". plain_ref is a plain interface +// port with no modport, whose vpiActual is the interface itself rather than a +// modport; intf_ref is modport-typed. +module Foo ( + SomeIntf.SomeModport intf_ref, + SomeIntf plain_ref +); + + logic [31:0] foo_saw_some; + logic [31:0] foo_saw_other; + logic [31:0] foo_saw_plain; + + // Read the interface variables through the interface reference + always_comb foo_saw_some = intf_ref.some_intf_var; + always_comb foo_saw_other = intf_ref.other_intf_var; + always_comb foo_saw_plain = plain_ref.some_intf_var; + +endmodule + +// Intermediate module, so the interface reference is passed down a level +module Bar ( + SomeIntf.SomeModport intf_ref, + SomeIntf plain_ref +); + + Foo foo ( + .intf_ref (intf_ref), + .plain_ref(plain_ref) + ); + +endmodule + +// top_collide is a port, so it lands in the TOP scope. A name looked up +// relative to an interface reference or modport must not fall back to +// searching there, which would return this unrelated object. +module t ( + input logic [31:0] top_collide +); + + SomeIntf concrete_intf (); + + Bar bar ( + .intf_ref (concrete_intf), + .plain_ref(concrete_intf) + ); + + // The C code registers a value change callback on this, and runs mon_check() + // when it changes. Write it immediately before the #1 below, so that every + // value mon_check() reads is already seeded. + logic run_mon_check = 1'b0; + + initial begin + concrete_intf.some_intf_var = 32'h1111_2222; + concrete_intf.other_intf_var = 32'h3333_4444; + + run_mon_check = 1'b1; + #1; + + // mon_check() wrote these via vpiActual of the interface references + if (concrete_intf.some_intf_var != 32'hfeed_face) begin + $write("%%Error: some_intf_var = %h\n", concrete_intf.some_intf_var); + $stop; + end + if (concrete_intf.other_intf_var != 32'hdead_beef) begin + $write("%%Error: other_intf_var = %h\n", concrete_intf.other_intf_var); + $stop; + end + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule : t diff --git a/test_regress/t/t_vpi_interface_ref_hier.out b/test_regress/t/t_vpi_interface_ref_hier.out new file mode 100644 index 000000000..5ffe41f6b --- /dev/null +++ b/test_regress/t/t_vpi_interface_ref_hier.out @@ -0,0 +1,10 @@ + scopesDump: + SCOPE 0x#: t + SCOPE 0x#: t.foo + VAR 0x#: foo_saw_plain + VAR 0x#: foo_saw_some + SCOPE 0x#: t.sub + SCOPE 0x#: t.sub.concrete_intf + VAR 0x#: some_intf_var + +*-* All Finished *-* diff --git a/test_regress/t/t_vpi_interface_ref_hier.py b/test_regress/t/t_vpi_interface_ref_hier.py new file mode 100755 index 000000000..ae8ac4641 --- /dev/null +++ b/test_regress/t/t_vpi_interface_ref_hier.py @@ -0,0 +1,22 @@ +#!/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 + +# The interface is passed by a downward hierarchical reference from a sibling +# instance, which is not registered as an interface reference; see the .v +test.scenarios("vlt") + +test.compile(verilator_flags2=["--binary", "--vpi", "--public-flat-rw"]) + +test.execute() + +test.files_identical(test.run_log_filename, test.golden_filename, is_logfile=True, strip_hex=True) + +test.passes() diff --git a/test_regress/t/t_vpi_interface_ref_hier.v b/test_regress/t/t_vpi_interface_ref_hier.v new file mode 100644 index 000000000..fc566ea5d --- /dev/null +++ b/test_regress/t/t_vpi_interface_ref_hier.v @@ -0,0 +1,57 @@ +// 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: 2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +// The interface lives in a sibling instance and is passed to Foo by a +// downward hierarchical reference. Interface references are only registered +// under the scope that instantiates the interface, so these are not visible +// through VPI, and scopesDump() shows no IFACEREF entries. See the TODO in +// V3TraceDecl. + +interface SomeIntf; + + logic [31:0] some_intf_var; + + modport SomeModport(inout some_intf_var); + +endinterface + +module Foo ( + SomeIntf.SomeModport intf_ref, + SomeIntf plain_ref +); + + logic [31:0] foo_saw_some; + logic [31:0] foo_saw_plain; + + always_comb foo_saw_some = intf_ref.some_intf_var; + always_comb foo_saw_plain = plain_ref.some_intf_var; + +endmodule + +module Sub; + + SomeIntf concrete_intf (); + +endmodule + +module t; + + Sub sub (); + + Foo foo ( + .intf_ref (sub.concrete_intf), + .plain_ref(sub.concrete_intf) + ); + + initial begin + $c("Verilated::scopesDump();"); + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule : t diff --git a/test_regress/t/t_vpi_interface_ref_name.py b/test_regress/t/t_vpi_interface_ref_name.py new file mode 100755 index 000000000..c5e5c194c --- /dev/null +++ b/test_regress/t/t_vpi_interface_ref_name.py @@ -0,0 +1,28 @@ +#!/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 + +# As t_vpi_interface_ref, but the model is constructed with a non-empty +# instance name, so every VPI path is prefixed with it. Interface references +# must pick up that prefix just as scopes do, as the name is not known until +# construction. +test.scenarios('vlt_all') +test.top_filename = "t/t_vpi_interface_ref.v" +test.pli_filename = "t/t_vpi_interface_ref.cpp" + +test.compile(make_pli=True, + verilator_flags2=[ + "--binary --vpi --no-l2name --public-flat-rw", "--main-top-name myinst", + "-CFLAGS -DTEST_MODEL_NAME=myinst", test.pli_filename + ]) + +test.execute(use_libvpi=True) + +test.passes() diff --git a/test_regress/t/t_vpi_interface_ref_nopublic.out b/test_regress/t/t_vpi_interface_ref_nopublic.out new file mode 100644 index 000000000..c47c4e3b5 --- /dev/null +++ b/test_regress/t/t_vpi_interface_ref_nopublic.out @@ -0,0 +1,3 @@ + scopesDump: + +*-* All Finished *-* diff --git a/test_regress/t/t_vpi_interface_ref_nopublic.py b/test_regress/t/t_vpi_interface_ref_nopublic.py new file mode 100755 index 000000000..ec9080280 --- /dev/null +++ b/test_regress/t/t_vpi_interface_ref_nopublic.py @@ -0,0 +1,24 @@ +#!/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 + +# As t_vpi_interface_ref_scopes, but without --public-flat-rw, so nothing is +# VPI visible and no scope reaches the scope table. Interface references to a +# scope that is not in the table must be dropped, leaving an empty dump. +test.scenarios("vlt") +test.top_filename = "t/t_vpi_interface_ref_scopes.v" + +test.compile(verilator_flags2=["--binary", "--vpi"]) + +test.execute() + +test.files_identical(test.run_log_filename, test.golden_filename, is_logfile=True, strip_hex=True) + +test.passes() diff --git a/test_regress/t/t_vpi_interface_ref_scopes.out b/test_regress/t/t_vpi_interface_ref_scopes.out new file mode 100644 index 000000000..c4d9b7738 --- /dev/null +++ b/test_regress/t/t_vpi_interface_ref_scopes.out @@ -0,0 +1,12 @@ + scopesDump: + SCOPE 0x#: t + SCOPE 0x#: t.concrete_intf + VAR 0x#: some_class + VAR 0x#: some_intf_var + SCOPE 0x#: t.foo + VAR 0x#: foo_saw_plain + VAR 0x#: foo_saw_some + IFACEREF 0x#: t.foo.intf_ref -> t.concrete_intf.SomeModport + IFACEREF 0x#: t.foo.plain_ref -> t.concrete_intf + +*-* All Finished *-* diff --git a/test_regress/t/t_vpi_interface_ref_scopes.py b/test_regress/t/t_vpi_interface_ref_scopes.py new file mode 100755 index 000000000..3970ebfb5 --- /dev/null +++ b/test_regress/t/t_vpi_interface_ref_scopes.py @@ -0,0 +1,20 @@ +#!/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("vlt") + +test.compile(verilator_flags2=["--binary", "--vpi", "--public-flat-rw"]) + +test.execute() + +test.files_identical(test.run_log_filename, test.golden_filename, is_logfile=True, strip_hex=True) + +test.passes() diff --git a/test_regress/t/t_vpi_interface_ref_scopes.v b/test_regress/t/t_vpi_interface_ref_scopes.v new file mode 100644 index 000000000..515c4e594 --- /dev/null +++ b/test_regress/t/t_vpi_interface_ref_scopes.v @@ -0,0 +1,57 @@ +// 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: 2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +interface SomeIntf; + + logic [31:0] some_intf_var; + + // A class inside an interface gets its own scope under the interface's + // cell. That scope is not the interface, so must not register any of the + // interface's references; only the handle appears as a variable below. + class SomeClass; + int some_class_var; + function int get(); + return some_class_var; + endfunction + endclass + + SomeClass some_class = new; + + modport SomeModport(inout some_intf_var); + +endinterface + +module Foo ( + SomeIntf.SomeModport intf_ref, + SomeIntf plain_ref +); + + logic [31:0] foo_saw_some; + logic [31:0] foo_saw_plain; + + always_comb foo_saw_some = intf_ref.some_intf_var; + always_comb foo_saw_plain = plain_ref.some_intf_var; + +endmodule + +module t; + + SomeIntf concrete_intf (); + + Foo foo ( + .intf_ref (concrete_intf), + .plain_ref(concrete_intf) + ); + + initial begin + $c("Verilated::scopesDump();"); + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule : t diff --git a/test_regress/t/t_vpi_interface_ref_trace.py b/test_regress/t/t_vpi_interface_ref_trace.py new file mode 100755 index 000000000..e27b5d234 --- /dev/null +++ b/test_regress/t/t_vpi_interface_ref_trace.py @@ -0,0 +1,26 @@ +#!/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 + +# As t_vpi_interface_ref, but with tracing also enabled. Trace and VPI both +# consume the AstIntfRef nodes that describe interface references, so check +# they do not interfere. +test.scenarios('vlt_all') +test.top_filename = "t/t_vpi_interface_ref.v" +test.pli_filename = "t/t_vpi_interface_ref.cpp" + +test.compile(make_pli=True, + verilator_flags2=[ + "--binary --vpi --no-l2name --public-flat-rw --trace-vcd", test.pli_filename + ]) + +test.execute(use_libvpi=True) + +test.passes()