diff --git a/include/verilated.cpp b/include/verilated.cpp index c58dde7f4..9cbb58ffc 100644 --- a/include/verilated.cpp +++ b/include/verilated.cpp @@ -3957,6 +3957,7 @@ std::unique_ptr VerilatedModel::traceConfig() const { retu // cppcheck-suppress unusedFunction // Used by applications uint32_t VerilatedVarProps::entSize() const VL_MT_SAFE { + if (m_entSize) return m_entSize; uint32_t size = 1; switch (vltype()) { case VLVT_PTR: size = sizeof(void*); break; @@ -4073,6 +4074,27 @@ VerilatedVar* VerilatedScope::varInsert(const char* namep, void* datap, bool isP return &(m_varsp->find(namep)->second); } +VerilatedVar* VerilatedScope::varInsertSized(const char* namep, void* datap, bool isParam, + VerilatedVarType vltype, int vlflags, int udims, + uint32_t entSize...) VL_MT_UNSAFE { + if (!m_varsp) m_varsp = new VerilatedVarNameMap; + VerilatedVar var(namep, datap, vltype, static_cast(vlflags), udims, 0, + isParam, entSize); + + va_list ap; + va_start(ap, entSize); + for (int i = 0; i < udims; ++i) { + const int msb = va_arg(ap, int); + const int lsb = va_arg(ap, int); + var.m_unpacked[i].m_left = msb; + var.m_unpacked[i].m_right = lsb; + } + va_end(ap); + + m_varsp->emplace(namep, std::move(var)); + return &(m_varsp->find(namep)->second); +} + VerilatedVar* VerilatedScope::forceableVarInsert(const char* namep, void* datap, bool isParam, VerilatedVarType vltype, int vlflags, void* forceReadSignalData, diff --git a/include/verilated.h b/include/verilated.h index a4535f2b4..59ae1ad8c 100644 --- a/include/verilated.h +++ b/include/verilated.h @@ -137,7 +137,9 @@ enum VerilatedVarType : uint8_t { VLVT_UINT64, // AKA QData VLVT_WDATA, // AKA VlWide VLVT_STRING, // C++ string - VLVT_REAL // AKA double + VLVT_REAL, // AKA double + VLVT_STRUCT, // SystemVerilog unpacked struct + VLVT_UNION // SystemVerilog unpacked union }; enum VerilatedVarFlags : uint32_t { @@ -154,7 +156,8 @@ enum VerilatedVarFlags : uint32_t { VLVF_CONTINUOUSLY = (1 << 11), // Is continously assigned VLVF_FORCEABLE = (1 << 12), // Forceable VLVF_SIGNED = (1 << 13), // Signed integer - VLVF_BITVAR = (1 << 14) // Four state bit (vs two state logic) + VLVF_BITVAR = (1 << 14), // Four state bit (vs two state logic) + VLVF_NET = (1 << 15) // Net object }; // IEEE 1800-2023 Table 20-6 @@ -781,6 +784,9 @@ public: // But internals only - called from verilated modules, VerilatedSyms void exportInsert(int finalize, const char* namep, void* cb) VL_MT_UNSAFE; VerilatedVar* varInsert(const char* namep, void* datap, bool isParam, VerilatedVarType vltype, int vlflags, int udims, int pdims, ...) VL_MT_UNSAFE; + VerilatedVar* varInsertSized(const char* namep, void* datap, bool isParam, + VerilatedVarType vltype, int vlflags, int udims, uint32_t entSize, + ...) VL_MT_UNSAFE; VerilatedVar* forceableVarInsert(const char* namep, void* datap, bool isParam, VerilatedVarType vltype, int vlflags, void* forceReadSignalData, const char* forceReadSignalName, diff --git a/include/verilated_sym_props.h b/include/verilated_sym_props.h index c124fb9e9..11e2fe8b8 100644 --- a/include/verilated_sym_props.h +++ b/include/verilated_sym_props.h @@ -76,6 +76,7 @@ class VerilatedVarProps VL_NOT_FINAL { const uint32_t m_magic; // Magic number const VerilatedVarType m_vltype; // Data type const VerilatedVarFlags m_vlflags; // Direction + const uint32_t m_entSize; // Element size in bytes, or 0 to derive from type std::vector m_unpacked; // Unpacked array ranges std::vector m_packed; // Packed array ranges VerilatedRange m_packedDpi; // Flattened packed array range @@ -104,10 +105,12 @@ class VerilatedVarProps VL_NOT_FINAL { // CONSTRUCTORS protected: friend class VerilatedScope; - VerilatedVarProps(VerilatedVarType vltype, VerilatedVarFlags vlflags, int udims, int pdims) + VerilatedVarProps(VerilatedVarType vltype, VerilatedVarFlags vlflags, int udims, int pdims, + uint32_t entSize = 0) : m_magic{MAGIC} , m_vltype{vltype} - , m_vlflags{vlflags} { + , m_vlflags{vlflags} + , m_entSize{entSize} { // Only preallocate the ranges initUnpacked(udims, nullptr); initPacked(pdims, nullptr); @@ -119,12 +122,14 @@ public: VerilatedVarProps(VerilatedVarType vltype, int vlflags) : m_magic{MAGIC} , m_vltype{vltype} - , m_vlflags(VerilatedVarFlags(vlflags)) {} // Need () or GCC 4.8 false warning + , m_vlflags(VerilatedVarFlags(vlflags)) // Need () or GCC 4.8 false warning + , m_entSize{0} {} VerilatedVarProps(VerilatedVarType vltype, int vlflags, Unpacked, int udims, const int* ulims) : m_magic{MAGIC} , m_vltype{vltype} - , m_vlflags(VerilatedVarFlags(vlflags)) { // Need () or GCC 4.8 false warning + , m_vlflags(VerilatedVarFlags(vlflags)) // Need () or GCC 4.8 false warning + , m_entSize{0} { initUnpacked(udims, ulims); } // With packed @@ -132,14 +137,16 @@ public: VerilatedVarProps(VerilatedVarType vltype, int vlflags, Packed, int pdims, const int* plims) : m_magic{MAGIC} , m_vltype{vltype} - , m_vlflags(VerilatedVarFlags(vlflags)) { // Need () or GCC 4.8 false warning + , m_vlflags(VerilatedVarFlags(vlflags)) // Need () or GCC 4.8 false warning + , m_entSize{0} { initPacked(pdims, plims); } VerilatedVarProps(VerilatedVarType vltype, int vlflags, Unpacked, int udims, const int* ulims, Packed, int pdims, const int* plims) : m_magic{MAGIC} , m_vltype{vltype} - , m_vlflags(VerilatedVarFlags(vlflags)) { // Need () or GCC 4.8 false warning + , m_vlflags(VerilatedVarFlags(vlflags)) // Need () or GCC 4.8 false warning + , m_entSize{0} { initUnpacked(udims, ulims); initPacked(pdims, plims); } @@ -164,6 +171,7 @@ public: bool isDpiCLayout() const { return ((m_vlflags & VLVF_DPI_CLAY) != 0); } bool isSigned() const { return ((m_vlflags & VLVF_SIGNED) != 0); } bool isBitVar() const { return ((m_vlflags & VLVF_BITVAR) != 0); } + bool isNet() const { return ((m_vlflags & VLVF_NET) != 0); } int udims() const VL_MT_SAFE { return m_unpacked.size(); } int pdims() const VL_MT_SAFE { return m_packed.size(); } int dims() const VL_MT_SAFE { return pdims() + udims(); } @@ -265,6 +273,8 @@ protected: // CONSTRUCTORS VerilatedVar(const char* namep, void* datap, VerilatedVarType vltype, VerilatedVarFlags vlflags, int udims, int pdims, bool isParam); + VerilatedVar(const char* namep, void* datap, VerilatedVarType vltype, + VerilatedVarFlags vlflags, int udims, int pdims, bool isParam, uint32_t entSize); VerilatedVar(const char* namep, void* datap, VerilatedVarType vltype, VerilatedVarFlags vlflags, int udims, int pdims, bool isParam, std::unique_ptr forceControlSignals); @@ -296,6 +306,13 @@ inline VerilatedVar::VerilatedVar(const char* namep, void* datap, VerilatedVarTy , m_datap{datap} , m_namep{namep} , m_isParam{isParam} {} +inline VerilatedVar::VerilatedVar(const char* namep, void* datap, VerilatedVarType vltype, + VerilatedVarFlags vlflags, int udims, int pdims, bool isParam, + uint32_t entSize) + : VerilatedVarProps{vltype, vlflags, udims, pdims, entSize} + , m_datap{datap} + , m_namep{namep} + , m_isParam{isParam} {} inline VerilatedVar::VerilatedVar( const char* namep, void* datap, VerilatedVarType vltype, VerilatedVarFlags vlflags, int udims, int pdims, bool isParam, diff --git a/include/verilated_vpi.cpp b/include/verilated_vpi.cpp index 8abbc5205..65afe20f3 100644 --- a/include/verilated_vpi.cpp +++ b/include/verilated_vpi.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -68,6 +69,25 @@ constexpr unsigned VL_VPI_LINE_SIZE_ = 8192; //====================================================================== // Implementation +static const char* _vl_vpi_find_unescaped_dot(const char* posp) { + for (; *posp; ++posp) { + if (*posp == '\\') { + while (*posp && *posp != ' ') ++posp; + if (!*posp) return nullptr; + } else if (*posp == '.') { + return posp; + } + } + return nullptr; +} + +static std::string _vl_vpi_member_local_name(const char* const namep) { + const char* localp = namep; + const char* posp = namep; + while ((posp = _vl_vpi_find_unescaped_dot(posp))) localp = ++posp; + return localp; +} + // Base VPI handled object class VerilatedVpio VL_NOT_FINAL { // CONSTANTS @@ -201,6 +221,9 @@ public: } const VerilatedVar* varp() const { return m_varp; } const VerilatedScope* scopep() const { return m_scopep; } + bool isStructOrUnion() const { + return varp()->vltype() == VLVT_STRUCT || varp()->vltype() == VLVT_UNION; + } // Returns the number of the currently indexed dimension (starting at -1 for none). int32_t indexedDim() const { return m_indexedDim; } // Returns whether the currently indexed dimension is unpacked. @@ -363,6 +386,8 @@ class VerilatedVpioVar VL_NOT_FINAL : public VerilatedVpioVarBase { uint32_t m_entSize = 0; // memoized variable size uint32_t m_bitOffset = 0; int32_t m_partselBits = -1; // Part-select width, -1 means no part-select active + std::string m_name; + std::string m_fullNameOverride; protected: void* m_varDatap = nullptr; // varp()->datap() adjusted for array entries @@ -373,6 +398,17 @@ public: : VerilatedVpioVarBase{varp, scopep} { m_entSize = varp->entSize(); m_varDatap = varp->datap(); + if (_vl_vpi_find_unescaped_dot(varp->name())) { + m_name = _vl_vpi_member_local_name(varp->name()); + } + } + VerilatedVpioVar(const VerilatedVar* varp, const VerilatedScope* scopep, void* datap, + const std::string& name, const std::string& fullname) + : VerilatedVpioVarBase{varp, scopep} { + m_entSize = varp->entSize(); + m_varDatap = datap; + m_name = name; + m_fullNameOverride = fullname; } explicit VerilatedVpioVar(const VerilatedVpioVar* vop) : VerilatedVpioVarBase{vop} { @@ -380,6 +416,8 @@ public: m_entSize = vop->m_entSize; m_varDatap = vop->m_varDatap; m_index = vop->m_index; + m_name = vop->m_name; + m_fullNameOverride = vop->m_fullNameOverride; m_partselBits = vop->m_partselBits; m_bitOffset = vop->m_bitOffset; // Not copying m_prevDatap, must be nullptr @@ -395,15 +433,22 @@ public: uint32_t bitOffset() const override { return m_bitOffset; } int32_t partselBits() const { return m_partselBits; } uint32_t bitSize() const { + if (isStructOrUnion() && !isIndexedDimUnpacked()) return 0; if (m_partselBits >= 0) return static_cast(m_partselBits); return VerilatedVpioVarBase::bitSize(); } uint32_t size() const override { + if (isStructOrUnion() && !isIndexedDimUnpacked()) return 0; if (m_partselBits >= 0) return static_cast(m_partselBits); return VerilatedVpioVarBase::size(); } + const VerilatedRange* rangep() const override { + if (isStructOrUnion() && !isIndexedDimUnpacked()) return nullptr; + return VerilatedVpioVarBase::rangep(); + } uint32_t entSize() const { return m_entSize; } const std::vector& index() const { return m_index; } + const char* name() const override { return m_name.empty() ? varp()->name() : m_name.c_str(); } // Create a part-selected view of this variable with the given bit range [hi:lo]. VerilatedVpioVar* withPartSelect(int32_t hi, int32_t lo) const { if (isIndexedDimUnpacked()) return nullptr; @@ -456,22 +501,43 @@ public: return ret; } + VerilatedVpioVar* withMember(const VerilatedVar* memberVarp) const { + const char* const parentName = varp()->name(); + const std::string memberName = memberVarp->name(); + const size_t parentLen = std::strlen(parentName); + + void* const parentDatap = varp()->datap(); + void* const memberDatap = memberVarp->datap(); + if (VL_UNLIKELY(!parentDatap) || VL_UNLIKELY(!memberDatap)) return nullptr; + const auto offset + = static_cast(memberDatap) - static_cast(parentDatap); + + const std::string localName = _vl_vpi_member_local_name(memberVarp->name()); + + return new VerilatedVpioVar{memberVarp, scopep(), + static_cast(varDatap()) + offset, localName, + std::string{fullname()} + memberName.substr(parentLen)}; + } uint32_t type() const override { uint32_t type; // TODO have V3EmitCSyms.cpp put vpiType directly into constant table switch (varp()->vltype()) { case VLVT_REAL: type = vpiRealVar; break; case VLVT_STRING: type = vpiStringVar; break; + case VLVT_STRUCT: type = varp()->isNet() ? vpiStructNet : vpiStructVar; break; + case VLVT_UNION: type = varp()->isNet() ? vpiUnionNet : vpiUnionVar; break; default: type = varp()->isBitVar() ? vpiBitVar : vpiReg; break; } - if (isIndexedDimUnpacked()) return vpiRegArray; + if (isIndexedDimUnpacked()) + return isStructOrUnion() && varp()->isNet() ? vpiNetArray : vpiRegArray; return type; } const char* fullname() const override { - static thread_local std::string t_out; - t_out = std::string{scopep()->name()} + "." + name(); - for (auto idx : index()) t_out += "[" + std::to_string(idx) + "]"; - return t_out.c_str(); + m_fullname = m_fullNameOverride.empty() + ? std::string{scopep()->name()} + "." + varp()->name() + : m_fullNameOverride; + for (auto idx : index()) m_fullname += "[" + std::to_string(idx) + "]"; + return m_fullname.c_str(); } void* prevDatap() const { return m_prevDatap; } void* varDatap() const override { return m_varDatap; } @@ -590,25 +656,63 @@ public: } }; +class VerilatedVpioMemberIter final : public VerilatedVpio { + const VerilatedScope* const m_scopep; + const VerilatedVarNameMap* const m_varsp; + VerilatedVarNameMap::const_iterator m_it; + VerilatedVpioVar* m_varp; + const std::string m_namePrefix; + bool m_started = false; + + static std::string namePrefix(const VerilatedVpioVar* vop) { + return std::string{vop->varp()->name()} + "."; + } + + vpiHandle atEnd() { + delete this; // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle + return nullptr; + } + +public: + explicit VerilatedVpioMemberIter(const VerilatedVpioVar* vop) + : m_scopep{vop->scopep()} + , m_varsp{m_scopep->varsp()} + , m_varp{new VerilatedVpioVar{vop}} + , m_namePrefix{namePrefix(vop)} {} + ~VerilatedVpioMemberIter() override { VL_DO_CLEAR(delete m_varp, m_varp = nullptr); } + // cppcheck-suppress duplInheritedMember + static VerilatedVpioMemberIter* castp(vpiHandle h) { + return dynamic_cast(reinterpret_cast(h)); + } + uint32_t type() const override { return vpiIterator; } + vpiHandle dovpi_scan() override { + if (VL_UNLIKELY(!m_varsp)) return atEnd(); + if (VL_UNLIKELY(!m_started)) { + m_it = m_varsp->begin(); + m_started = true; + } else if (VL_LIKELY(m_it != m_varsp->end())) { + ++m_it; + } + for (; m_it != m_varsp->end(); ++m_it) { + const char* const name = m_it->second.name(); + if (std::strncmp(name, m_namePrefix.c_str(), m_namePrefix.length()) != 0) continue; + // Only direct members, not grandchildren + if (_vl_vpi_find_unescaped_dot(name + m_namePrefix.length())) continue; + VerilatedVpioVar* const memberp = m_varp->withMember(&(m_it->second)); + if (VL_UNLIKELY(!memberp)) continue; + return memberp->castVpiHandle(); + } + return atEnd(); + } +}; + class VerilatedVpioModule final : public VerilatedVpioScope { public: explicit VerilatedVpioModule(const VerilatedScope* modulep) : VerilatedVpioScope{modulep} { // Look for '.' not inside escaped identifier - const std::string scopename = m_fullname; - std::string::size_type pos = std::string::npos; - size_t i = 0; - while (i < scopename.length()) { - if (scopename[i] == '\\') { - while (i < scopename.length() && scopename[i] != ' ') ++i; - ++i; // Proc ' ', it should always be there. Then grab '.' on next cycle - } else { - while (i < scopename.length() && scopename[i] != '.') ++i; - if (i < scopename.length()) pos = i++; - } - } - if (VL_UNLIKELY(pos == std::string::npos)) m_toplevel = true; + if (VL_UNLIKELY(!_vl_vpi_find_unescaped_dot(m_fullname))) m_toplevel = true; } // cppcheck-suppress duplInheritedMember static VerilatedVpioModule* castp(vpiHandle h) { @@ -1770,6 +1874,8 @@ const char* VerilatedVpiError::strFromVpiObjType(PLI_INT32 vpiVal) VL_PURE { }; // clang-format on if (VL_UNCOVERABLE(vpiVal < 0)) return names[0]; + // vpiUnionNet is outside the otherwise contiguous SystemVerilog object type range. + if (vpiVal == vpiUnionNet) return "vpiUnionNet"; if (vpiVal <= vpiAutomatics) return names[vpiVal]; if (vpiVal >= vpiPackage && vpiVal <= vpiPropFormalDecl) return sv_names1[(vpiVal - vpiPackage)]; @@ -2135,6 +2241,7 @@ void VerilatedVpiError::selfTest() VL_MT_UNSAFE_ONE { SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiEnumVar); SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiStructVar); SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiUnionVar); + SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiUnionNet); SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiBitVar); SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiClassObj); SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiChandleVar); @@ -2404,6 +2511,162 @@ static bool vl_vpi_parse_indices(std::string& name, std::vector& indi return true; } +static const VerilatedScope* _vl_vpi_top_port_scopep(const VerilatedScope* const scopep) { + if (VL_UNLIKELY(!scopep)) return nullptr; + if (scopep->type() != VerilatedScope::SCOPE_MODULE) return nullptr; + if (std::strcmp(scopep->name(), "TOP") == 0) return nullptr; + if (_vl_vpi_find_unescaped_dot(scopep->name())) return nullptr; + return Verilated::threadContextp()->scopeFind("TOP"); +} + +static bool _vl_vpi_find_dotted_var(const std::string& scopename, const std::string& basename, + const VerilatedScope*& scopep, const VerilatedVar*& varp, + std::string& fullname) { + if (scopename.empty()) return false; + + // Unpacked struct/union members are exposed as synthetic vars whose names contain dots + // (e.g. "mystruct.member"), so the boundary between the scope and the variable name is + // ambiguous. Walk the boundary leftward, moving one scope segment at a time onto the front + // of the dotted variable name, and try each candidate scope. An exhausted scope means the + // variable lives in the toplevel "TOP" scope. + std::string dottedName = basename; + std::string dottedScope = scopename; + while (true) { + const std::string::size_type lastDot = dottedScope.rfind('.'); + dottedName = (lastDot == std::string::npos ? dottedScope : dottedScope.substr(lastDot + 1)) + + "." + dottedName; + dottedScope.resize(lastDot == std::string::npos ? 0 : lastDot); + scopep = Verilated::threadContextp()->scopeFind(dottedScope.empty() ? "TOP" + : dottedScope.c_str()); + if (scopep) { + if (const VerilatedScope* const topScopep = _vl_vpi_top_port_scopep(scopep)) { + if (const VerilatedVar* const topVarp = topScopep->varFind(dottedName.c_str())) { + scopep = topScopep; + varp = topVarp; + fullname = dottedScope.empty() ? dottedName : dottedScope + "." + dottedName; + return true; + } + } + varp = scopep->varFind(dottedName.c_str()); + if (varp) return true; + } + if (lastDot == std::string::npos) return false; + } +} + +static VerilatedVpioVar* _vl_vpi_handle_member_by_name(const std::string& name, + const VerilatedVpioVar* vop) { + const VerilatedScope* const scopep = vop->scopep(); + if (VL_UNLIKELY(!scopep)) return nullptr; + const std::string memberName = std::string{vop->varp()->name()} + "." + name; + const VerilatedVar* const memberVarp = scopep->varFind(memberName.c_str()); + if (!memberVarp) return nullptr; + return vop->withMember(memberVarp); +} + +static VerilatedVpioVar* _vl_vpi_handle_apply_indices(VerilatedVpioVar* vop, + const std::vector& indices) { + for (const PLI_INT32 index : indices) { + VerilatedVpioVar* const nextVop = vop->withIndex(index); + VL_DO_CLEAR(delete vop, vop = nullptr); + if (!nextVop) return nullptr; + vop = nextVop; + } + return vop; +} + +static bool _vl_vpi_parse_optional_indices(std::string& name, std::vector& indices) { + indices.clear(); + if (name.empty()) return false; + if (name.back() != ']') return true; + + VlVpiBitRange bitRange; + return vl_vpi_parse_indices(name, indices, &bitRange) && !bitRange.valid; +} + +static void _vl_vpi_split_dotted_name(const std::string& name, std::vector& parts) { + parts.clear(); + const char* const namep = name.c_str(); + const char* beginp = namep; + const char* posp = beginp; + while ((posp = _vl_vpi_find_unescaped_dot(posp))) { + parts.emplace_back(beginp, posp - beginp); + beginp = ++posp; + } + parts.emplace_back(beginp); +} + +static VerilatedVpioVar* +_vl_vpi_handle_indexed_member_from_scope(const VerilatedScope* const scopep, + const std::vector& parts, + const size_t firstPart) { + if (VL_UNLIKELY(!scopep) || VL_UNLIKELY(firstPart >= parts.size())) return nullptr; + + std::string baseName = parts[firstPart]; + std::vector indices; + if (!_vl_vpi_parse_optional_indices(baseName, indices)) return nullptr; + + const VerilatedScope* varScopep = scopep; + const VerilatedVar* baseVarp = nullptr; + std::string fullnameOverride; + if (const VerilatedScope* const topScopep = _vl_vpi_top_port_scopep(scopep)) { + if (const VerilatedVar* const topVarp = topScopep->varFind(baseName.c_str())) { + varScopep = topScopep; + baseVarp = topVarp; + fullnameOverride = std::string{scopep->name()} + "." + baseName; + } + } + if (!baseVarp) baseVarp = scopep->varFind(baseName.c_str()); + if (!baseVarp) return nullptr; + + VerilatedVpioVar* baseVop + = fullnameOverride.empty() + ? new VerilatedVpioVar{baseVarp, varScopep} + : new VerilatedVpioVar{baseVarp, varScopep, baseVarp->datap(), + _vl_vpi_member_local_name(baseVarp->name()), + fullnameOverride}; + VerilatedVpioVar* vop = _vl_vpi_handle_apply_indices(baseVop, indices); + if (!vop) return nullptr; + + for (size_t i = firstPart + 1; i < parts.size(); ++i) { + std::string memberName = parts[i]; + if (!_vl_vpi_parse_optional_indices(memberName, indices)) { + VL_DO_CLEAR(delete vop, vop = nullptr); + return nullptr; + } + + VerilatedVpioVar* const memberVop = _vl_vpi_handle_member_by_name(memberName, vop); + VL_DO_CLEAR(delete vop, vop = nullptr); + if (!memberVop) return nullptr; + + vop = _vl_vpi_handle_apply_indices(memberVop, indices); + if (!vop) return nullptr; + } + return vop; +} + +static VerilatedVpioVar* +_vl_vpi_handle_dotted_indexed_member_by_name(const std::string& scopeAndName) { + std::vector parts; + _vl_vpi_split_dotted_name(scopeAndName, parts); + if (parts.size() < 2) return nullptr; + + for (size_t firstPart = parts.size() - 1; firstPart > 0; --firstPart) { + std::string scopeName = parts[0]; + for (size_t i = 1; i < firstPart; ++i) scopeName += "." + parts[i]; + const VerilatedScope* const scopep + = Verilated::threadContextp()->scopeFind(scopeName.c_str()); + if (!scopep) continue; + if (VerilatedVpioVar* const vop + = _vl_vpi_handle_indexed_member_from_scope(scopep, parts, firstPart)) { + return vop; + } + } + + const VerilatedScope* const topScopep = Verilated::threadContextp()->scopeFind("TOP"); + return _vl_vpi_handle_indexed_member_from_scope(topScopep, parts, 0); +} + // for obtaining handles vpiHandle vpi_handle_by_name(PLI_BYTE8* namep, vpiHandle scope) { @@ -2425,7 +2688,9 @@ vpiHandle vpi_handle_by_name(PLI_BYTE8* namep, vpiHandle scope) { const VerilatedVar* varp = nullptr; const VerilatedScope* scopep; + std::string fullnameOverride; const VerilatedVpioScope* const voScopep = VerilatedVpioScope::castp(scope); + const VerilatedVpioVar* const voVarp = VerilatedVpioVar::castp(scope); if (0 == std::strncmp(scopeAndName.c_str(), "$root.", std::strlen("$root."))) { scopeAndName.erase(0, std::strlen("$root.")); @@ -2433,6 +2698,12 @@ vpiHandle vpi_handle_by_name(PLI_BYTE8* namep, vpiHandle scope) { const bool scopeIsPackage = VerilatedVpioPackage::castp(scope) != nullptr; scopeAndName = std::string{voScopep->fullname()} + (scopeIsPackage ? "" : ".") + scopeAndName; + } else if (voVarp && voVarp->isStructOrUnion()) { + if (VerilatedVpioVar* const memberp + = _vl_vpi_handle_member_by_name(scopeAndName, voVarp)) { + return memberp->castVpiHandle(); + } + scopeAndName = std::string{voVarp->fullname()} + "." + scopeAndName; } { // This doesn't yet follow the hierarchy in the proper way @@ -2491,8 +2762,18 @@ vpiHandle vpi_handle_by_name(PLI_BYTE8* namep, vpiHandle scope) { } if (!varp) { scopep = Verilated::threadContextp()->scopeFind(scopename.c_str()); - if (!scopep) return nullptr; - varp = scopep->varFind(basename.c_str()); + if (scopep) { varp = scopep->varFind(basename.c_str()); } + // Unpacked struct members are exposed as synthetic variables with dotted names. + // Exact unindexed member names can be found directly; indexed member paths need the + // component walker so array indices are applied before member offsets. + if (!varp + && !_vl_vpi_find_dotted_var(scopename, basename, scopep, varp, fullnameOverride)) { + if (VerilatedVpioVar* const memberp + = _vl_vpi_handle_dotted_indexed_member_by_name(scopeAndName)) { + return memberp->castVpiHandle(); + } + return nullptr; + } } } if (!varp) return nullptr; @@ -2501,6 +2782,11 @@ vpiHandle vpi_handle_by_name(PLI_BYTE8* namep, vpiHandle scope) { vpiHandle resultHandle; if (varp->isParam()) { resultHandle = (new VerilatedVpioParam{varp, scopep})->castVpiHandle(); + } else if (!fullnameOverride.empty()) { + resultHandle + = (new VerilatedVpioVar{varp, scopep, varp->datap(), + _vl_vpi_member_local_name(varp->name()), fullnameOverride}) + ->castVpiHandle(); } else { resultHandle = (new VerilatedVpioVar{varp, scopep})->castVpiHandle(); } @@ -2642,6 +2928,11 @@ vpiHandle vpi_iterate(PLI_INT32 type, vpiHandle object) { if (vop) return ((new VerilatedVpioRegIter{vop})->castVpiHandle()); return nullptr; } + case vpiMember: { + const VerilatedVpioVar* const vop = VerilatedVpioVar::castp(object); + if (!vop || !vop->isStructOrUnion()) return nullptr; + return ((new VerilatedVpioMemberIter{vop})->castVpiHandle()); + } case vpiParameter: { const VerilatedVpioScope* const vop = VerilatedVpioScope::castp(object); if (VL_UNLIKELY(!vop)) return nullptr; @@ -2734,6 +3025,11 @@ PLI_INT32 vpi_get(PLI_INT32 property, vpiHandle object) { if (VL_UNLIKELY(!vop)) return vpiUndefined; return vop->varp()->isSigned(); } + case vpiPacked: { + const VerilatedVpioVarBase* const vop = VerilatedVpioVarBase::castp(object); + if (VL_LIKELY(vop && vop->isStructOrUnion())) return 0; + [[fallthrough]]; + } default: VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported property %s, nothing will be returned", __func__, VerilatedVpiError::strFromVpiProp(property)); diff --git a/src/V3AstNodeDType.h b/src/V3AstNodeDType.h index 4600911d2..fcb2183ba 100644 --- a/src/V3AstNodeDType.h +++ b/src/V3AstNodeDType.h @@ -174,6 +174,7 @@ public: bool isStreamableFixedAggregate() const; bool containsUnpackedStruct() const; int widthStream() const; + string vlEnumType() const; // Return VerilatedVarType: VLVT_UINT32, etc static int uniqueNumInc() { return ++s_uniqueNum; } const char* charIQWN() const { return (isString() ? "N" : isWide() ? "W" : isDouble() ? "D" : isQuad() ? "Q" : "I"); diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 85b077053..ec26f24a9 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -703,9 +703,14 @@ string AstVar::vlArgType(bool named, bool forReturn, bool forFunc, const string& return ostatic + dtypep()->cType(oname, forFunc, asRef); } -string AstVar::vlEnumType() const { +string AstNodeDType::vlEnumType() const { string arg; - const AstBasicDType* const bdtypep = basicp(); + const AstNodeDType* dtypep = skipRefp(); + while (const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) { + dtypep = adtypep->subDTypep()->skipRefp(); + } + const AstBasicDType* const bdtypep = dtypep->basicp(); + const AstNodeUOrStructDType* const sdtypep = VN_CAST(dtypep, NodeUOrStructDType); const bool strtype = bdtypep && bdtypep->keyword() == VBasicDTypeKwd::STRING; if (bdtypep && bdtypep->keyword() == VBasicDTypeKwd::CHARPTR) { return "VLVT_PTR"; @@ -715,6 +720,8 @@ string AstVar::vlEnumType() const { arg += "VLVT_STRING"; } else if (isDouble()) { arg += "VLVT_REAL"; + } else if (sdtypep && !sdtypep->packed()) { + arg += VN_IS(sdtypep, StructDType) ? "VLVT_STRUCT" : "VLVT_UNION"; } else if (widthMin() <= 8) { arg += "VLVT_UINT8"; } else if (widthMin() <= 16) { @@ -730,6 +737,8 @@ string AstVar::vlEnumType() const { return arg; } +string AstVar::vlEnumType() const { return dtypep()->vlEnumType(); } + string AstVar::vlEnumDir() const { string out; if (isInout()) { @@ -759,6 +768,7 @@ string AstVar::vlEnumDir() const { if (AstBasicDType* const basicp = dtypep()->skipRefp()->basicp()) { if (basicp->keyword() == VBasicDTypeKwd::BIT) out += "|VLVF_BITVAR"; } + if (isNet()) out += "|VLVF_NET"; return out; } diff --git a/src/V3EmitCSyms.cpp b/src/V3EmitCSyms.cpp index 79add9f3b..bcf79c164 100644 --- a/src/V3EmitCSyms.cpp +++ b/src/V3EmitCSyms.cpp @@ -176,26 +176,26 @@ class EmitCSyms final : EmitCBaseVisitorConst { return pos != std::string::npos ? scpname.substr(pos + 1) : scpname; } - static std::tuple getDimensions(const AstVar* const varp) { + static std::tuple getDimensions(const AstNodeDType* const rootDtypep) { int pdim = 0; int udim = 0; std::string bounds; - if (const AstBasicDType* const basicp = varp->basicp()) { - // Range is always first, it's not in "C" order - for (AstNodeDType* dtypep = varp->dtypep(); dtypep;) { - // Skip AstRefDType/AstTypedef, or return same node - dtypep = dtypep->skipRefp(); - if (const AstNodeArrayDType* const adtypep = VN_CAST(dtypep, NodeArrayDType)) { - bounds += " ,"; - bounds += std::to_string(adtypep->left()); - bounds += ","; - bounds += std::to_string(adtypep->right()); - if (VN_IS(dtypep, PackArrayDType)) - pdim++; - else - udim++; - dtypep = adtypep->subDTypep(); - } else { + // Range is always first, it's not in "C" order + for (const AstNodeDType* dtypep = rootDtypep; dtypep;) { + // Skip AstRefDType/AstTypedef, or return same node + dtypep = dtypep->skipRefp(); + if (const AstNodeArrayDType* const adtypep = VN_CAST(dtypep, NodeArrayDType)) { + bounds += " ,"; + bounds += std::to_string(adtypep->left()); + bounds += ","; + bounds += std::to_string(adtypep->right()); + if (VN_IS(dtypep, PackArrayDType)) + pdim++; + else + udim++; + dtypep = adtypep->subDTypep(); + } else { + if (const AstBasicDType* const basicp = dtypep->basicp()) { if (basicp->isRanged()) { bounds += " ,"; bounds += std::to_string(basicp->left()); @@ -203,13 +203,33 @@ class EmitCSyms final : EmitCBaseVisitorConst { bounds += std::to_string(basicp->right()); pdim++; } - break; // AstBasicDType - nothing below, 1 } + break; // Non-array leaf } } return {pdim, udim, bounds}; } + static std::tuple getDimensions(const AstVar* const varp) { + return getDimensions(varp->dtypep()); + } + + static int getUnpackedElements(const AstNodeDType* rootDtypep) { + int elements = 1; + for (const AstNodeDType* dtypep = rootDtypep; dtypep;) { + dtypep = dtypep->skipRefp(); + const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType); + if (!adtypep) break; + elements *= adtypep->elementsConst(); + dtypep = adtypep->subDTypep(); + } + return elements; + } + + static bool needsEmittedEntSize(const std::string& vlEnumType) { + return vlEnumType == "VLVT_STRUCT" || vlEnumType == "VLVT_UNION"; + } + static std::pair isForceControlSignal(const AstVar* const signalVarp) { // __VforceRd should not show up here because it is never public, but just in case it does, // it should be skipped because forceableVarInsert creates its VerilatedVar. @@ -225,15 +245,47 @@ class EmitCSyms final : EmitCBaseVisitorConst { return std::pair{false, ""}; } + static void appendVarProperties(std::string& stmt, const std::string& vlEnumType, + const std::string& vlEnumDir, const int udim, const int pdim, + const std::string& bounds, const std::string& entSize = "") { + stmt += vlEnumType; // VLVT_UINT32 etc + stmt += ", "; + stmt += vlEnumDir; // VLVD_IN etc + stmt += ", "; + stmt += std::to_string(udim); + if (!entSize.empty()) { + stmt += ", "; + stmt += entSize; + } else { + stmt += ", "; + stmt += std::to_string(pdim); + } + stmt += bounds; + stmt += ")"; + } + static std::string memberVlEnumDir(const AstVar* const varp, + const AstNodeDType* const dtypep) { + std::string out = "((" + varp->vlEnumDir() + ") & ~(VLVF_SIGNED|VLVF_BITVAR))"; + const AstNodeDType* const skipDTypep = dtypep->skipRefp(); + if (skipDTypep->isSigned()) out += "|VLVF_SIGNED"; + if (const AstBasicDType* const basicp = skipDTypep->basicp()) { + if (basicp->keyword() == VBasicDTypeKwd::BIT) out += "|VLVF_BITVAR"; + } + return out; + } + static std::string insertVarStatement(const ScopeVarData& svd, const AstScope* const scopep, const AstVar* const varp, const int udim, const int pdim, const std::string& bounds) { - std::string stmt; - stmt += protect("__Vscopep_" + svd.m_scopeName) + "->varInsert(\""; - stmt += V3OutFormatter::quoteNameControls(protect(svd.m_varBasePretty)) + '"'; - const std::string varName = VIdProtect::protectIf(scopep->nameDotless(), scopep->protect()) + "." + protect(varp->name()); + const std::string vlEnumType = varp->vlEnumType(); + const bool needsEntSize = needsEmittedEntSize(vlEnumType); + + std::string stmt; + stmt += protect("__Vscopep_" + svd.m_scopeName); + stmt += needsEntSize ? "->varInsertSized(\"" : "->varInsert(\""; + stmt += V3OutFormatter::quoteNameControls(protect(svd.m_varBasePretty)) + '"'; if (!varp->isParam()) { stmt += ", &("; @@ -250,18 +302,87 @@ class EmitCSyms final : EmitCBaseVisitorConst { stmt += "))), true, "; } - stmt += varp->vlEnumType(); // VLVT_UINT32 etc - stmt += ", "; - stmt += varp->vlEnumDir(); // VLVD_IN etc - stmt += ", "; - stmt += std::to_string(udim); - stmt += ", "; - stmt += std::to_string(pdim); - stmt += bounds; - stmt += ")"; + const std::string entSize = needsEntSize + ? "sizeof(" + varName + ") / " + + std::to_string(getUnpackedElements(varp->dtypep())) + : ""; + appendVarProperties(stmt, vlEnumType, varp->vlEnumDir(), udim, pdim, bounds, entSize); return stmt; } + static std::string insertDTypeVarStatement(const ScopeVarData& svd, + const AstScope* const scopep, + const std::string& prettyName, + const std::string& cName, + const AstNodeDType* const dtypep, const int udim, + const int pdim, const std::string& bounds) { + const std::string vlEnumType = dtypep->vlEnumType(); + const bool needsEntSize = needsEmittedEntSize(vlEnumType); + std::string stmt; + stmt += protect("__Vscopep_" + svd.m_scopeName); + stmt += needsEntSize ? "->varInsertSized(\"" : "->varInsert(\""; + stmt += V3OutFormatter::quoteNameControls(prettyName) + '"'; + stmt += ", &("; + stmt += VIdProtect::protectIf(scopep->nameDotless(), scopep->protect()); + stmt += "."; + stmt += cName; + stmt += "), false, "; + const std::string varName + = VIdProtect::protectIf(scopep->nameDotless(), scopep->protect()) + "." + cName; + const std::string entSize + = needsEntSize + ? "sizeof(" + varName + ") / " + std::to_string(getUnpackedElements(dtypep)) + : ""; + appendVarProperties(stmt, vlEnumType, memberVlEnumDir(svd.m_varp, dtypep), udim, pdim, + bounds, entSize); + return stmt; + } + + static void addUOrStructMemberVars(std::vector& stmts, const ScopeVarData& svd, + const AstScope* const scopep, + const std::string& prettyPrefix, const std::string& cPrefix, + const AstNodeUOrStructDType* const sdtypep) { + for (const AstMemberDType* itemp = sdtypep->membersp(); itemp; + itemp = VN_AS(itemp->nextp(), MemberDType)) { + const AstNodeDType* const itemDTypep = itemp->dtypep(); + const std::string prettyName + = prettyPrefix + "." + AstNode::vpiName(itemp->shortName()); + const std::string cName = cPrefix + "." + itemp->nameProtect(); + const std::tuple dimensions = getDimensions(itemDTypep); + const int pdim = std::get<0>(dimensions); + const int udim = std::get<1>(dimensions); + const std::string bounds = std::get<2>(dimensions); + stmts.emplace_back(insertDTypeVarStatement(svd, scopep, prettyName, cName, itemDTypep, + udim, pdim, bounds) + + ";"); + if (const AstNodeUOrStructDType* const subp + = VN_CAST(itemDTypep->skipRefp(), NodeUOrStructDType)) { + if (!subp->packed()) + addUOrStructMemberVars(stmts, svd, scopep, prettyName, cName, subp); + } else if (VN_IS(itemDTypep->skipRefp(), UnpackArrayDType)) { + addUnpackedArrayUOrStructMemberVars(stmts, svd, scopep, prettyName, cName, + itemDTypep); + } + } + } + + static void addUnpackedArrayUOrStructMemberVars(std::vector& stmts, + const ScopeVarData& svd, + const AstScope* const scopep, + const std::string& prettyPrefix, + const std::string& cPrefix, + const AstNodeDType* const dtypep) { + const AstNodeDType* const skipDTypep = dtypep->skipRefp(); + if (const AstUnpackArrayDType* const adtypep = VN_CAST(skipDTypep, UnpackArrayDType)) { + addUnpackedArrayUOrStructMemberVars(stmts, svd, scopep, prettyPrefix, cPrefix + "[0]", + adtypep->subDTypep()); + } else if (const AstNodeUOrStructDType* const sdtypep + = VN_CAST(skipDTypep, NodeUOrStructDType)) { + if (!sdtypep->packed()) + addUOrStructMemberVars(stmts, svd, scopep, prettyPrefix, cPrefix, sdtypep); + } + } + std::string insertForceableVarStatement(const ScopeVarData& svd, const AstScope* const scopep, const AstVar* const varp, const int udim, const int pdim, const std::string& bounds) { @@ -1061,6 +1182,16 @@ std::vector EmitCSyms::getSymCtorStmts() { const std::string stmt = insertVarStatement(svd, scopep, varp, udim, pdim, bounds) + ";"; add(stmt); + if (const AstNodeUOrStructDType* const sdtypep + = VN_CAST(varp->dtypeSkipRefp(), NodeUOrStructDType)) { + if (!sdtypep->packed()) { + addUOrStructMemberVars(stmts, svd, scopep, svd.m_varBasePretty, + protect(varp->name()), sdtypep); + } + } else if (VN_IS(varp->dtypeSkipRefp(), UnpackArrayDType)) { + addUnpackedArrayUOrStructMemberVars(stmts, svd, scopep, svd.m_varBasePretty, + protect(varp->name()), varp->dtypep()); + } } } } diff --git a/test_regress/t/t_vpi_var.cpp b/test_regress/t/t_vpi_var.cpp index 3afaf92ca..7c0eac4f4 100644 --- a/test_regress/t/t_vpi_var.cpp +++ b/test_regress/t/t_vpi_var.cpp @@ -38,6 +38,8 @@ #endif +#include + #ifdef VERILATOR #include "verilated.h" #endif @@ -258,6 +260,626 @@ int _mon_check_big() { return 0; } +int _mon_check_unpacked_struct_members() { + const char* p; + PLI_INT32 d; + t_vpi_value tmpValue; + + // unpacked struct port members + tmpValue.format = vpiIntVal; + { + TestVpiHandle vh101 = VPI_HANDLE("unpacked_struct_port"); + CHECK_RESULT_NZ(vh101); + d = vpi_get(vpiType, vh101); + CHECK_RESULT(d, vpiStructVar); + CHECK_RESULT(vpi_get(vpiSize, vh101), 0); + CHECK_RESULT(vpi_get(vpiPacked, vh101), 0); + CHECK_RESULT_Z(vpi_handle(vpiLeftRange, vh101)); + p = vpi_get_str(vpiType, vh101); + CHECK_RESULT_CSTR(p, "vpiStructVar"); + + TestVpiHandle vh102 + = vpi_handle_by_name((PLI_BYTE8*)"t.unpacked_struct_port.member_a", NULL); + CHECK_RESULT_NZ(vh102); + CHECK_RESULT(vpi_get(vpiType, vh102), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh102), 7); + p = vpi_get_str(vpiName, vh102); + CHECK_RESULT_CSTR(p, "member_a"); + p = vpi_get_str(vpiFullName, vh102); + CHECK_RESULT_CSTR(p, "t.unpacked_struct_port.member_a"); + CHECK_RESULT_NZ(vpi_handle(vpiLeftRange, vh102)); + CHECK_RESULT_Z(vpi_iterate(vpiMember, vh102)); + CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"member_a", vh102)); + + TestVpiHandle vh103 + = vpi_handle_by_name((PLI_BYTE8*)"t.unpacked_struct_port.member_b", NULL); + CHECK_RESULT_NZ(vh103); + CHECK_RESULT(vpi_get(vpiType, vh103), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh103), 1); + + TestVpiHandle vh104 = vpi_handle_by_name((PLI_BYTE8*)"member_c", vh101); + CHECK_RESULT_NZ(vh104); + CHECK_RESULT(vpi_get(vpiType, vh104), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh104), 16); + + CHECK_RESULT_Z(vpi_iterate(vpiMember, nullptr)); + TestVpiHandle vh105 = vpi_iterate(vpiMember, vh101); + CHECK_RESULT_NZ(vh105); + CHECK_RESULT(vpi_get(vpiType, vh105), vpiIterator); + std::set memberNames; + while (TestVpiHandle member = vpi_scan(vh105)) { + memberNames.insert(vpi_get_str(vpiName, member)); + } + vh105.freed(); // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle + CHECK_RESULT(memberNames.count("member_a"), 1); + CHECK_RESULT(memberNames.count("member_b"), 1); + CHECK_RESULT(memberNames.count("member_c"), 1); + + s_vpi_value putValue; + putValue.format = vpiIntVal; + putValue.value.integer = 0x35; + vpi_put_value(vh102, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh102, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x35); + + putValue.value.integer = 0x4321; + vpi_put_value(vh104, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh104, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x4321); + + // Members resolve even without the toplevel scope prefix + TestVpiHandle vh106 + = vpi_handle_by_name((PLI_BYTE8*)"unpacked_struct_port.member_a", NULL); + CHECK_RESULT_NZ(vh106); + CHECK_RESULT(vpi_get(vpiType, vh106), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh106), 7); + vpi_get_value(vh106, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x35); + + TestVpiHandle vh107 + = vpi_handle_by_name((PLI_BYTE8*)"$root.t.unpacked_struct_port.member_a", NULL); + CHECK_RESULT_NZ(vh107); + CHECK_RESULT(vpi_get(vpiType, vh107), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh107), 7); + + TestVpiHandle vh108 = VPI_HANDLE(""); + CHECK_RESULT_NZ(vh108); + TestVpiHandle vh109 + = vpi_handle_by_name((PLI_BYTE8*)"unpacked_struct_port.member_b", vh108); + CHECK_RESULT_NZ(vh109); + CHECK_RESULT(vpi_get(vpiType, vh109), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh109), 1); + + TestVpiHandle vh100 + = vpi_handle_by_name((PLI_BYTE8*)"t.unpacked_struct_port.member_c[7:0]", NULL); + CHECK_RESULT_NZ(vh100); + CHECK_RESULT(vpi_get(vpiType, vh100), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh100), 8); + + CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"t.unpacked_struct_port[7:0]", NULL)); + + CHECK_RESULT_Z( + vpi_handle_by_name((PLI_BYTE8*)"t.unpacked_struct_port.no_such_member", NULL)); + } + + // Synthetic member vars preserve type-derived flags from the member dtype + { + TestVpiHandle vh114 = VPI_HANDLE("member_flags_struct_signal"); + CHECK_RESULT_NZ(vh114); + CHECK_RESULT(vpi_get(vpiType, vh114), vpiStructVar); + + TestVpiHandle vh115 = vpi_handle_by_name((PLI_BYTE8*)"unsigned_member", vh114); + CHECK_RESULT_NZ(vh115); + CHECK_RESULT(vpi_get(vpiType, vh115), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh115), 7); + CHECK_RESULT(vpi_get(vpiSigned, vh115), 0); + + TestVpiHandle vh116 = vpi_handle_by_name((PLI_BYTE8*)"signed_member", vh114); + CHECK_RESULT_NZ(vh116); + CHECK_RESULT(vpi_get(vpiType, vh116), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh116), 7); + CHECK_RESULT(vpi_get(vpiSigned, vh116), 1); + + TestVpiHandle vh117 = vpi_handle_by_name((PLI_BYTE8*)"bit_member", vh114); + CHECK_RESULT_NZ(vh117); + CHECK_RESULT(vpi_get(vpiType, vh117), vpiBitVar); + CHECK_RESULT(vpi_get(vpiSize, vh117), 1); + CHECK_RESULT(vpi_get(vpiSigned, vh117), 0); + } + + // unpacked union port members + { + TestVpiHandle vh110 = VPI_HANDLE("unpacked_union_port"); + CHECK_RESULT_NZ(vh110); + CHECK_RESULT(vpi_get(vpiType, vh110), vpiUnionVar); + CHECK_RESULT(vpi_get(vpiSize, vh110), 0); + CHECK_RESULT(vpi_get(vpiPacked, vh110), 0); + p = vpi_get_str(vpiType, vh110); + CHECK_RESULT_CSTR(p, "vpiUnionVar"); + + TestVpiHandle vh111 = vpi_handle_by_name((PLI_BYTE8*)"union_byte0", vh110); + CHECK_RESULT_NZ(vh111); + CHECK_RESULT(vpi_get(vpiType, vh111), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh111), 8); + + TestVpiHandle vh112 = vpi_iterate(vpiMember, vh110); + CHECK_RESULT_NZ(vh112); + std::set unionMembers; + while (TestVpiHandle member = vpi_scan(vh112)) { + unionMembers.insert(vpi_get_str(vpiName, member)); + } + vh112.freed(); // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle + CHECK_RESULT(unionMembers.count("union_byte0"), 1); + CHECK_RESULT(unionMembers.count("union_byte1"), 1); + } + + // nested unpacked struct port members + { + TestVpiHandle vh120 = VPI_HANDLE("nested_struct_port"); + CHECK_RESULT_NZ(vh120); + CHECK_RESULT(vpi_get(vpiType, vh120), vpiStructVar); + + // The nested struct member is itself a struct + TestVpiHandle vh121 = vpi_handle_by_name((PLI_BYTE8*)"outer_inner", vh120); + CHECK_RESULT_NZ(vh121); + CHECK_RESULT(vpi_get(vpiType, vh121), vpiStructVar); + CHECK_RESULT(vpi_get(vpiSize, vh121), 0); + + // Leaf reachable via the full hierarchical name + TestVpiHandle vh122 + = vpi_handle_by_name((PLI_BYTE8*)"t.nested_struct_port.outer_inner.inner_x", NULL); + CHECK_RESULT_NZ(vh122); + CHECK_RESULT(vpi_get(vpiType, vh122), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh122), 4); + + // Leaf reachable relative to the intermediate struct handle + TestVpiHandle vh123 = vpi_handle_by_name((PLI_BYTE8*)"inner_y", vh121); + CHECK_RESULT_NZ(vh123); + CHECK_RESULT(vpi_get(vpiType, vh123), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh123), 4); + + // Iterating the outer struct yields direct members only, not grandchildren + TestVpiHandle vh124 = vpi_iterate(vpiMember, vh120); + CHECK_RESULT_NZ(vh124); + std::set nestedMembers; + while (TestVpiHandle member = vpi_scan(vh124)) { + nestedMembers.insert(vpi_get_str(vpiName, member)); + } + vh124.freed(); // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle + CHECK_RESULT(nestedMembers.count("outer_a"), 1); + CHECK_RESULT(nestedMembers.count("outer_inner"), 1); + CHECK_RESULT(nestedMembers.count("inner_x"), 0); + + TestVpiHandle vh125 = vpi_handle_by_name((PLI_BYTE8*)"t.sub.subsig1", NULL); + CHECK_RESULT_NZ(vh125); + CHECK_RESULT(vpi_get(vpiType, vh125), vpiReg); + + // Write through a leaf of the nested struct + s_vpi_value putValue; + putValue.format = vpiIntVal; + putValue.value.integer = 0xa; + vpi_put_value(vh122, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh122, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0xa); + } + + // unpacked struct port with multidimensional packed-array members + { + TestVpiHandle vh130 = VPI_HANDLE("struct_with_packed_arrays_port"); + CHECK_RESULT_NZ(vh130); + CHECK_RESULT(vpi_get(vpiType, vh130), vpiStructVar); + + TestVpiHandle vh131 = vpi_handle_by_name((PLI_BYTE8*)"packed_matrix", vh130); + CHECK_RESULT_NZ(vh131); + CHECK_RESULT(vpi_get(vpiType, vh131), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh131), 512); + + TestVpiHandle vh132 = vpi_handle_by_index(vh131, 2); + CHECK_RESULT_NZ(vh132); + CHECK_RESULT(vpi_get(vpiSize, vh132), 32); + + TestVpiHandle vh133 = vpi_handle_by_index(vh132, 2); + CHECK_RESULT_NZ(vh133); + CHECK_RESULT(vpi_get(vpiSize, vh133), 8); + + TestVpiHandle vh134 = vpi_handle_by_name((PLI_BYTE8*)"reverse_matrix", vh130); + CHECK_RESULT_NZ(vh134); + CHECK_RESULT(vpi_get(vpiType, vh134), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh134), 128); + + TestVpiHandle vh135 = vpi_handle_by_index(vh134, -2); + CHECK_RESULT_NZ(vh135); + CHECK_RESULT(vpi_get(vpiSize, vh135), 8); + } + + // port array of unpacked structs + { + TestVpiHandle vh140 = VPI_HANDLE("struct_array_port"); + CHECK_RESULT_NZ(vh140); + CHECK_RESULT(vpi_get(vpiType, vh140), vpiRegArray); + + TestVpiHandle vh141 = vpi_handle_by_index(vh140, 0); + CHECK_RESULT_NZ(vh141); + CHECK_RESULT(vpi_get(vpiType, vh141), vpiStructVar); + + TestVpiHandle vh142 = vpi_handle_by_name((PLI_BYTE8*)"member_c", vh141); + CHECK_RESULT_NZ(vh142); + CHECK_RESULT(vpi_get(vpiType, vh142), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh142), 16); + + s_vpi_value putValue; + putValue.format = vpiIntVal; + putValue.value.integer = 0x6789; + vpi_put_value(vh142, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh142, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x6789); + + TestVpiHandle vh144 = vpi_handle_by_index(vh140, 1); + CHECK_RESULT_NZ(vh144); + TestVpiHandle vh145 = vpi_handle_by_name((PLI_BYTE8*)"member_c", vh144); + CHECK_RESULT_NZ(vh145); + putValue.value.integer = 0x2468; + vpi_put_value(vh145, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh145, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x2468); + vpi_get_value(vh142, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x6789); + + TestVpiHandle vh146 + = vpi_handle_by_name((PLI_BYTE8*)"struct_array_port[1].member_c", nullptr); + CHECK_RESULT_NZ(vh146); + vpi_get_value(vh146, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x2468); + + TestVpiHandle vh143 = vpi_iterate(vpiMember, vh141); + CHECK_RESULT_NZ(vh143); + std::set memberNames; + while (TestVpiHandle member = vpi_scan(vh143)) { + memberNames.insert(vpi_get_str(vpiName, member)); + } + vh143.freed(); // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle + CHECK_RESULT(memberNames.count("member_a"), 1); + CHECK_RESULT(memberNames.count("member_b"), 1); + CHECK_RESULT(memberNames.count("member_c"), 1); + } + + // multidimensional port array of unpacked structs + { + TestVpiHandle vh150 = VPI_HANDLE("struct_matrix_port"); + CHECK_RESULT_NZ(vh150); + CHECK_RESULT(vpi_get(vpiType, vh150), vpiRegArray); + CHECK_RESULT(vpi_get(vpiSize, vh150), 6); + + TestVpiHandle vh151 = vpi_handle_by_index(vh150, 1); + CHECK_RESULT_NZ(vh151); + CHECK_RESULT(vpi_get(vpiType, vh151), vpiRegArray); + CHECK_RESULT(vpi_get(vpiSize, vh151), 3); + + TestVpiHandle vh152 = vpi_handle_by_index(vh151, 2); + CHECK_RESULT_NZ(vh152); + CHECK_RESULT(vpi_get(vpiType, vh152), vpiStructVar); + + TestVpiHandle vh153 = vpi_handle_by_name((PLI_BYTE8*)"member_c", vh152); + CHECK_RESULT_NZ(vh153); + CHECK_RESULT(vpi_get(vpiType, vh153), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh153), 16); + p = vpi_get_str(vpiName, vh153); + CHECK_RESULT_CSTR(p, "member_c"); + p = vpi_get_str(vpiFullName, vh153); + { + const std::string expectedFullName + = std::string{vpi_get_str(vpiFullName, vh152)} + ".member_c"; + CHECK_RESULT_CSTR(p, expectedFullName.c_str()); + } + + s_vpi_value putValue; + putValue.format = vpiIntVal; + putValue.value.integer = 0x1357; + vpi_put_value(vh153, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh153, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x1357); + + TestVpiHandle vh154 + = vpi_handle_by_name((PLI_BYTE8*)"struct_matrix_port[1][2].member_c", nullptr); + CHECK_RESULT_NZ(vh154); + vpi_get_value(vh154, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x1357); + } + + // unpacked struct net port members + { + TestVpiHandle vh190 = VPI_HANDLE("wire_unpacked_struct_port"); + CHECK_RESULT_NZ(vh190); + CHECK_RESULT(vpi_get(vpiType, vh190), vpiStructNet); + CHECK_RESULT(vpi_get(vpiPacked, vh190), 0); + p = vpi_get_str(vpiType, vh190); + CHECK_RESULT_CSTR(p, "vpiStructNet"); + + TestVpiHandle vh191 = vpi_handle_by_name((PLI_BYTE8*)"member_a", vh190); + CHECK_RESULT_NZ(vh191); + CHECK_RESULT(vpi_get(vpiType, vh191), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh191), 7); + + s_vpi_value putValue; + putValue.format = vpiIntVal; + putValue.value.integer = 0x25; + vpi_put_value(vh191, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh191, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x25); + + TestVpiHandle vh192 = vpi_iterate(vpiMember, vh190); + CHECK_RESULT_NZ(vh192); + std::set memberNames; + while (TestVpiHandle member = vpi_scan(vh192)) { + memberNames.insert(vpi_get_str(vpiName, member)); + } + vh192.freed(); // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle + CHECK_RESULT(memberNames.count("member_a"), 1); + CHECK_RESULT(memberNames.count("member_b"), 1); + CHECK_RESULT(memberNames.count("member_c"), 1); + } + + // net array of unpacked structs + { + TestVpiHandle vh193 = VPI_HANDLE("wire_struct_array_port"); + CHECK_RESULT_NZ(vh193); + CHECK_RESULT(vpi_get(vpiType, vh193), vpiNetArray); + + TestVpiHandle vh194 = vpi_handle_by_index(vh193, 0); + CHECK_RESULT_NZ(vh194); + CHECK_RESULT(vpi_get(vpiType, vh194), vpiStructNet); + CHECK_RESULT(vpi_get(vpiPacked, vh194), 0); + + TestVpiHandle vh195 = vpi_handle_by_name((PLI_BYTE8*)"member_c", vh194); + CHECK_RESULT_NZ(vh195); + CHECK_RESULT(vpi_get(vpiType, vh195), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh195), 16); + + s_vpi_value putValue; + putValue.format = vpiIntVal; + putValue.value.integer = 0x579b; + vpi_put_value(vh195, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh195, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x579b); + } + + // non-port array of unpacked structs + { + TestVpiHandle vh160 = VPI_HANDLE("struct_array_signal"); + CHECK_RESULT_NZ(vh160); + CHECK_RESULT(vpi_get(vpiType, vh160), vpiRegArray); + + TestVpiHandle vh161 = vpi_handle_by_index(vh160, 1); + CHECK_RESULT_NZ(vh161); + CHECK_RESULT(vpi_get(vpiType, vh161), vpiStructVar); + + TestVpiHandle vh162 = vpi_handle_by_name((PLI_BYTE8*)"member_c", vh161); + CHECK_RESULT_NZ(vh162); + CHECK_RESULT(vpi_get(vpiType, vh162), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh162), 16); + + s_vpi_value putValue; + putValue.format = vpiIntVal; + putValue.value.integer = 0xace0; + vpi_put_value(vh162, &putValue, NULL, vpiNoDelay); + + TestVpiHandle vh163 = vpi_handle_by_name( + const_cast(TestSimulator::rooted("struct_array_signal[1].member_c")), + nullptr); + CHECK_RESULT_NZ(vh163); + vpi_get_value(vh163, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0xace0); + } + + // array of unpacked structs with unpacked-array members + { + TestVpiHandle vh170 = VPI_HANDLE("parent_struct_array"); + CHECK_RESULT_NZ(vh170); + CHECK_RESULT(vpi_get(vpiType, vh170), vpiRegArray); + CHECK_RESULT(vpi_get(vpiSize, vh170), 2); + + TestVpiHandle vh171 = vpi_handle_by_index(vh170, 0); + CHECK_RESULT_NZ(vh171); + CHECK_RESULT(vpi_get(vpiType, vh171), vpiStructVar); + + TestVpiHandle vh172 = vpi_handle_by_index(vh170, 1); + CHECK_RESULT_NZ(vh172); + CHECK_RESULT(vpi_get(vpiType, vh172), vpiStructVar); + + TestVpiHandle vh173 = vpi_handle_by_name((PLI_BYTE8*)"tail_array", vh171); + CHECK_RESULT_NZ(vh173); + CHECK_RESULT(vpi_get(vpiType, vh173), vpiRegArray); + CHECK_RESULT(vpi_get(vpiSize, vh173), 4); + + TestVpiHandle vh174 = vpi_handle_by_index(vh173, 3); + CHECK_RESULT_NZ(vh174); + CHECK_RESULT(vpi_get(vpiType, vh174), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh174), 8); + + s_vpi_value putValue; + putValue.format = vpiIntVal; + putValue.value.integer = 0xaa; + vpi_put_value(vh174, &putValue, NULL, vpiNoDelay); + + TestVpiHandle vh17a = vpi_handle_by_name((PLI_BYTE8*)"trailing_children", vh171); + CHECK_RESULT_NZ(vh17a); + CHECK_RESULT(vpi_get(vpiType, vh17a), vpiRegArray); + CHECK_RESULT(vpi_get(vpiSize, vh17a), 2); + + TestVpiHandle vh17b = vpi_handle_by_index(vh17a, 2); + CHECK_RESULT_NZ(vh17b); + CHECK_RESULT(vpi_get(vpiType, vh17b), vpiStructVar); + + TestVpiHandle vh17c = vpi_handle_by_name((PLI_BYTE8*)"child_leaf", vh17b); + CHECK_RESULT_NZ(vh17c); + CHECK_RESULT(vpi_get(vpiType, vh17c), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh17c), 8); + putValue.value.integer = 0x11; + vpi_put_value(vh17c, &putValue, NULL, vpiNoDelay); + + TestVpiHandle vh175 = vpi_handle_by_name((PLI_BYTE8*)"scalar", vh172); + CHECK_RESULT_NZ(vh175); + CHECK_RESULT(vpi_get(vpiType, vh175), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh175), 16); + putValue.value.integer = 0x55cc; + vpi_put_value(vh175, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh175, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x55cc); + + vpi_get_value(vh174, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0xaa); + vpi_get_value(vh17c, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x11); + + TestVpiHandle vh176 = vpi_handle_by_name((PLI_BYTE8*)"children", vh172); + CHECK_RESULT_NZ(vh176); + CHECK_RESULT(vpi_get(vpiType, vh176), vpiRegArray); + CHECK_RESULT(vpi_get(vpiSize, vh176), 2); + + TestVpiHandle vh177 = vpi_handle_by_index(vh176, 3); + CHECK_RESULT_NZ(vh177); + CHECK_RESULT(vpi_get(vpiType, vh177), vpiStructVar); + + TestVpiHandle vh178 = vpi_handle_by_name((PLI_BYTE8*)"child_leaf", vh177); + CHECK_RESULT_NZ(vh178); + CHECK_RESULT(vpi_get(vpiType, vh178), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh178), 8); + putValue.value.integer = 0x5a; + vpi_put_value(vh178, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh178, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x5a); + + TestVpiHandle vh179 + = vpi_handle_by_name(const_cast(TestSimulator::rooted( + "parent_struct_array[1].children[3].child_leaf")), + nullptr); + CHECK_RESULT_NZ(vh179); + vpi_get_value(vh179, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x5a); + + TestVpiHandle vh17d = vpi_handle_by_name((PLI_BYTE8*)"trailing_children", vh172); + CHECK_RESULT_NZ(vh17d); + TestVpiHandle vh17e = vpi_handle_by_index(vh17d, 2); + CHECK_RESULT_NZ(vh17e); + TestVpiHandle vh17f = vpi_handle_by_name((PLI_BYTE8*)"child_leaf", vh17e); + CHECK_RESULT_NZ(vh17f); + putValue.value.integer = 0x6b; + vpi_put_value(vh17f, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh17f, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x6b); + + TestVpiHandle vh17g + = vpi_handle_by_name(const_cast(TestSimulator::rooted( + "parent_struct_array[1].trailing_children[2].child_leaf")), + nullptr); + CHECK_RESULT_NZ(vh17g); + vpi_get_value(vh17g, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0x6b); + + CHECK_RESULT_Z(vpi_handle_by_name(const_cast(TestSimulator::rooted( + "parent_struct_array[1].children[3:2].child_leaf")), + nullptr)); + } + + // array of unpacked structs with members requiring different C++ alignments + { + TestVpiHandle vh180 = VPI_HANDLE("aligned_struct_array"); + CHECK_RESULT_NZ(vh180); + CHECK_RESULT(vpi_get(vpiType, vh180), vpiRegArray); + CHECK_RESULT(vpi_get(vpiSize, vh180), 2); + + TestVpiHandle vh181 = vpi_handle_by_index(vh180, 1); + CHECK_RESULT_NZ(vh181); + CHECK_RESULT(vpi_get(vpiType, vh181), vpiStructVar); + + TestVpiHandle vh182 = vpi_handle_by_name((PLI_BYTE8*)"word_member", vh181); + CHECK_RESULT_NZ(vh182); + CHECK_RESULT(vpi_get(vpiType, vh182), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh182), 32); + + TestVpiHandle vh183 = vpi_handle_by_name((PLI_BYTE8*)"quad_member", vh181); + CHECK_RESULT_NZ(vh183); + CHECK_RESULT(vpi_get(vpiType, vh183), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh183), 64); + + TestVpiHandle vh184 = vpi_handle_by_name((PLI_BYTE8*)"real_member", vh181); + CHECK_RESULT_NZ(vh184); + CHECK_RESULT(vpi_get(vpiType, vh184), vpiRealVar); + + TestVpiHandle vh185 = vpi_handle_by_name((PLI_BYTE8*)"string_member", vh181); + CHECK_RESULT_NZ(vh185); + CHECK_RESULT(vpi_get(vpiType, vh185), vpiStringVar); + + TestVpiHandle vh186 = vpi_handle_by_name((PLI_BYTE8*)"nested_member", vh181); + CHECK_RESULT_NZ(vh186); + CHECK_RESULT(vpi_get(vpiType, vh186), vpiStructVar); + + TestVpiHandle vh187 = vpi_handle_by_name((PLI_BYTE8*)"child_leaf", vh186); + CHECK_RESULT_NZ(vh187); + CHECK_RESULT(vpi_get(vpiType, vh187), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh187), 8); + } + + // Array stride for a struct whose C++ size needs tail padding after a nested struct + { + TestVpiHandle vh188a = VPI_HANDLE("alignment_stride_array"); + CHECK_RESULT_NZ(vh188a); + CHECK_RESULT(vpi_get(vpiType, vh188a), vpiRegArray); + CHECK_RESULT(vpi_get(vpiSize, vh188a), 2); + + TestVpiHandle vh188b = vpi_handle_by_index(vh188a, 1); + CHECK_RESULT_NZ(vh188b); + CHECK_RESULT(vpi_get(vpiType, vh188b), vpiStructVar); + + TestVpiHandle vh188c = vpi_handle_by_name((PLI_BYTE8*)"tail", vh188b); + CHECK_RESULT_NZ(vh188c); + CHECK_RESULT(vpi_get(vpiType, vh188c), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh188c), 8); + + s_vpi_value putValue; + putValue.format = vpiIntVal; + putValue.value.integer = 0xc3; + vpi_put_value(vh188c, &putValue, NULL, vpiNoDelay); + vpi_get_value(vh188c, &tmpValue); + CHECK_RESULT(tmpValue.value.integer, 0xc3); + } + + TestVpiHandle vh188 = VPI_HANDLE("nested_struct_port"); + CHECK_RESULT_NZ(vh188); + CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"outer_inner.no_such", vh188)); + CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"t.\\no.such escaped.member", nullptr)); + + // Unpacked struct member with an escaped identifier containing a dot + { + TestVpiHandle vh200 = VPI_HANDLE("escaped_member_struct_signal"); + CHECK_RESULT_NZ(vh200); + CHECK_RESULT(vpi_get(vpiType, vh200), vpiStructVar); + + TestVpiHandle vh201 = vpi_handle_by_name((PLI_BYTE8*)"\\a.b ", vh200); + CHECK_RESULT_NZ(vh201); + CHECK_RESULT(vpi_get(vpiType, vh201), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh201), 8); + + TestVpiHandle vh202 + = vpi_handle_by_name((PLI_BYTE8*)"t.escaped_member_struct_signal.\\a.b ", nullptr); + CHECK_RESULT_NZ(vh202); + CHECK_RESULT(vpi_get(vpiType, vh202), vpiReg); + CHECK_RESULT(vpi_get(vpiSize, vh202), 8); + + TestVpiHandle vh203 = vpi_iterate(vpiMember, vh200); + CHECK_RESULT_NZ(vh203); + std::set escapedMembers; + while (TestVpiHandle member = vpi_scan(vh203)) { + escapedMembers.insert(vpi_get_str(vpiName, member)); + } + vh203.freed(); // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle + CHECK_RESULT(escapedMembers.count("\\a.b "), 1); + CHECK_RESULT(escapedMembers.count("plain_after"), 1); + } + + return 0; +} + int _mon_check_var() { TestVpiHandle vh1 = VPI_HANDLE("onebit"); CHECK_RESULT_NZ(vh1); @@ -1543,6 +2165,9 @@ extern "C" int mon_check() { printf("-mon_check()\n"); #endif +#if !defined(T_VPI_VAR2) && !defined(T_VPI_VAR3) + if (int status = _mon_check_unpacked_struct_members()) return status; +#endif if (int status = _mon_check_mcd()) return status; if (int status = _mon_check_callbacks()) return status; if (int status = _mon_check_value_callbacks()) return status; @@ -1607,6 +2232,7 @@ int main(int argc, char** argv) { uint64_t sim_time = 1100; contextp->debug(0); contextp->commandArgs(argc, argv); + VerilatedVpi::selfTest(); const std::unique_ptr topp{new VM_PREFIX{contextp.get(), // Note null name - we're flattening it out diff --git a/test_regress/t/t_vpi_var.v b/test_regress/t/t_vpi_var.v index e115a1c4d..ea66b0712 100644 --- a/test_regress/t/t_vpi_var.v +++ b/test_regress/t/t_vpi_var.v @@ -16,7 +16,9 @@ module t (/*AUTOARG*/ // Outputs x, // Inputs - clk, a + clk, a, unpacked_struct_port, unpacked_union_port, nested_struct_port, + struct_array_port, struct_matrix_port, struct_with_packed_arrays_port, + wire_unpacked_struct_port, wire_struct_array_port ); `ifdef VERILATOR @@ -54,6 +56,99 @@ extern "C" int mon_check(); // verilator lint_on ASCRANGE reg unpacked_only[7:0]; + typedef struct { + logic [6:0] member_a; + logic member_b; + logic [15:0] member_c; + } unpacked_struct_t; + + input unpacked_struct_t unpacked_struct_port /*verilator public_flat_rw*/; + input wire unpacked_struct_t wire_unpacked_struct_port /*verilator public_flat_rw*/; + + typedef union { + logic [7:0] union_byte0; + logic [7:0] union_byte1; + } unpacked_union_t; + + input unpacked_union_t unpacked_union_port /*verilator public_flat_rw*/; + + typedef struct { + logic [3:0] inner_x; + logic [3:0] inner_y; + } inner_struct_t; + + typedef struct { + logic [7:0] outer_a; + inner_struct_t outer_inner; + } nested_struct_t; + + input nested_struct_t nested_struct_port /*verilator public_flat_rw*/; + + input unpacked_struct_t struct_array_port [1:0] /*verilator public_flat_rw*/; + input unpacked_struct_t struct_matrix_port [1:0][2:0] /*verilator public_flat_rw*/; + input wire unpacked_struct_t wire_struct_array_port [1:0] /*verilator public_flat_rw*/; + unpacked_struct_t struct_array_signal [1:0] /*verilator public_flat_rw*/; + + typedef struct { + logic [6:0] unsigned_member; + logic signed [6:0] signed_member; + bit bit_member; + } member_flags_struct_t; + + member_flags_struct_t member_flags_struct_signal /*verilator public_flat_rw*/; + + typedef struct { + logic [7:0] child_leaf; + } child_struct_t; + + typedef struct { + logic [15:0] scalar; + child_struct_t children [3:2]; + logic [7:0] tail_array [2:5]; + child_struct_t trailing_children [3:2]; + } parent_struct_t; + + parent_struct_t parent_struct_array [1:0] /*verilator public_flat_rw*/; + + typedef struct { + logic [7:0] \a.b ; + logic [7:0] plain_after; + } escaped_member_struct_t; + + escaped_member_struct_t escaped_member_struct_signal /*verilator public_flat_rw*/; + + typedef struct { + logic [31:0] word_member; + logic [63:0] quad_member; + real real_member; + string string_member; + child_struct_t nested_member; + } aligned_struct_t; + + aligned_struct_t aligned_struct_array [1:0] /*verilator public_flat_rw*/; + + typedef struct { + logic [63:0] quad_leaf; + logic [7:0] byte_tail; + } stride_inner_struct_t; + + typedef struct { + logic [7:0] lead; + stride_inner_struct_t nested; + logic [7:0] tail; + } stride_outer_struct_t; + + stride_outer_struct_t alignment_stride_array [1:0] /*verilator public_flat_rw*/; + + // verilator lint_off ASCRANGE + typedef struct { + logic [0:15][0:3][7:0] packed_matrix; + logic [8:-7][3:-4] reverse_matrix; + } struct_with_packed_arrays_t; + // verilator lint_on ASCRANGE + + input struct_with_packed_arrays_t struct_with_packed_arrays_port /*verilator public_flat_rw*/; + reg [7:0] text_byte /*verilator public_flat_rw @(posedge clk) */; reg [15:0] text_half /*verilator public_flat_rw @(posedge clk) */; reg [31:0] text_word /*verilator public_flat_rw @(posedge clk) */; @@ -166,6 +261,7 @@ extern "C" int mon_check(); if (text != "lorem ipsum") $stop; if (str1 != "something a lot longer than hello") $stop; if (real1 > 123456.7895 || real1 < 123456.7885 ) $stop; + if (alignment_stride_array[1].tail != 8'hc3) $stop; end always @(posedge clk) begin