diff --git a/include/verilated.cpp b/include/verilated.cpp index d981f7c0a..92de3fd29 100644 --- a/include/verilated.cpp +++ b/include/verilated.cpp @@ -4135,6 +4135,50 @@ VerilatedVar* VerilatedScope::varInsert(const char* namep, void* datap, bool isP return &(m_varsp->find(namep)->second); } +void VerilatedScope::varsInsertFromTable(const VlVarTableEntry* entp, size_t n, + void* basep) VL_MT_UNSAFE { + // Table-driven equivalent of a run of varInsert()/varInsertSized() calls; see VlVarTableEntry. + if (!m_varsp) m_varsp = new VerilatedVarNameMap; + uint8_t* const base = static_cast(basep); + for (size_t i = 0; i < n; ++i) { + const VlVarTableEntry& e = entp[i]; + void* const datap = base + e.byteOffset; + const VerilatedVarFlags vlflags = static_cast(e.vlflags); + VerilatedVar var{e.namep, datap, e.vltype, vlflags, e.udims, e.pdims, /*isParam=*/false}; + for (int d = 0; d < e.udims; ++d) { + var.m_unpacked[d].m_left = e.dims[2 * d]; + var.m_unpacked[d].m_right = e.dims[2 * d + 1]; + } + for (int d = 0; d < e.pdims; ++d) { + var.m_packed[d].m_left = e.dims[2 * (e.udims + d)]; + var.m_packed[d].m_right = e.dims[2 * (e.udims + d) + 1]; + } + // Recompute the flattened DPI packed range now dims are known (see + // VerilatedVarProps::initPacked) + if (e.pdims == 1) { + var.m_packedDpi = var.m_packed.front(); + } else if (e.pdims > 1) { + int packedSize = 1; + for (int d = 0; d < e.pdims; ++d) packedSize *= var.m_packed[d].elements(); + var.m_packedDpi = VerilatedRange{packedSize - 1, 0}; + } + m_varsp->emplace(e.namep, std::move(var)); + } +} + +void VerilatedScope::scopesConstructFromTable(const VlScopeTableEntry* entp, size_t n, + VerilatedSyms* symsp) VL_MT_UNSAFE { + // Table-driven equivalent of a run of 'new VerilatedScope{...}' statements; see + // VlScopeTableEntry. The generated Syms class derives VerilatedSyms as its sole primary + // base at offset 0, so symsp doubles as the base for the offsetof-baked member addresses. + uint8_t* const base = reinterpret_cast(symsp); + for (size_t i = 0; i < n; ++i) { + const VlScopeTableEntry& e = entp[i]; + VerilatedScope** const slotp = reinterpret_cast(base + e.ptrOffset); + *slotp = new VerilatedScope{symsp, e.namep, e.identp, e.defnamep, e.timeunit, e.type}; + } +} + VerilatedVar* VerilatedScope::varInsertSized(const char* namep, void* datap, bool isParam, VerilatedVarType vltype, int vlflags, int udims, uint32_t entSize...) VL_MT_UNSAFE { diff --git a/include/verilated.h b/include/verilated.h index 73c7e5b2a..b3ce78068 100644 --- a/include/verilated.h +++ b/include/verilated.h @@ -160,6 +160,21 @@ enum VerilatedVarFlags : uint32_t { VLVF_NET = (1 << 15) // Net object }; +// One VPI-visible variable, consumed by VerilatedScope::varsInsertFromTable(); +// replaces per-variable varInsert() calls, which compiles faster at scale. +struct VlVarTableEntry final { + static constexpr int kMaxDims = 3; // Max packed+unpacked dims a table row holds + const char* namep; // VPI-facing (protected) variable name, string literal + uint32_t byteOffset; // offsetof of storage member from module instance base + VerilatedVarType vltype; + uint32_t vlflags; // Direction + flags (VLVD_*/VLVF_*) + uint8_t udims; // udims + pdims <= kMaxDims + uint8_t pdims; + // (left,right) pairs: unpacked dims first, then packed; int32_t since large + // unpacked memories exceed int16 range + int32_t dims[kMaxDims * 2]; +}; + // IEEE 1800-2023 Table 20-6 enum class VerilatedAssertType : uint8_t { ASSERT_TYPE_CONCURRENT = (1 << 0), @@ -756,6 +771,8 @@ public: // But for internal use only // Verilator scope information class // Used for internal VPI implementation, and introspection into scopes +struct VlScopeTableEntry; // Defined below VerilatedScope; used by scopesConstructFromTable() + class VerilatedScope final { public: enum Type : uint8_t { @@ -792,6 +809,9 @@ public: // But internals only - called from verilated modules, VerilatedSyms void* forceReadSignalData, const char* forceReadSignalName, std::pair forceControlSignals, int udims, int pdims...) VL_MT_UNSAFE; + 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; // ACCESSORS const char* name() const VL_MT_SAFE_POSTINIT { return m_namep; } const char* identifier() const VL_MT_SAFE_POSTINIT { return m_identifierp; } @@ -807,6 +827,17 @@ public: // But internals only - called from verilated modules, VerilatedSyms Type type() const { return m_type; } }; +// One scope, consumed by VerilatedScope::scopesConstructFromTable(); replaces +// per-scope 'new VerilatedScope{...}' statements, which compiles faster at scale. +struct VlScopeTableEntry final { + uint32_t ptrOffset; // offsetof of the target __Vscopep_* member within the Syms object + const char* namep; // Scope suffix name (protected), string literal + const char* identp; // Identifier with escapes removed (protected) + const char* defnamep; // Definition name (SCOPE_MODULE only), else "" + int8_t timeunit; // Timeunit in negative power-of-10 + VerilatedScope::Type type; +}; + class VerilatedHierarchy final { public: static void add(const VerilatedScope* fromp, const VerilatedScope* top); diff --git a/include/verilatedos.h b/include/verilatedos.h index f866a36ce..d7e251987 100644 --- a/include/verilatedos.h +++ b/include/verilatedos.h @@ -368,6 +368,7 @@ extern "C" void __gcov_dump(); #define __STDC_FORMAT_MACROS // Now that C++ requires these standard types the vl types are deprecated +#include // offsetof (used by generated VlVarTableEntry tables) #include #include #include diff --git a/src/V3EmitCSyms.cpp b/src/V3EmitCSyms.cpp index baf500103..7a535df43 100644 --- a/src/V3EmitCSyms.cpp +++ b/src/V3EmitCSyms.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include VL_DEFINE_DEBUG_FUNCTIONS; @@ -108,10 +109,16 @@ class EmitCSyms final : EmitCBaseVisitorConst { const bool m_dpiHdrOnly; // Only emit the DPI header std::vector m_splitFuncNames; // Split file names VDouble0 m_statVarScopeBytes; // Statistic tracking + // name -> initializer rows, built in getSymCtorStmts() + std::vector>> m_varTables; + // Single VlScopeTableEntry[] table for all scopes, built in getSymCtorStmts() + std::string m_scopeTableName; + std::vector m_scopeTableRows; // METHODS void emitSymHdr(); void emitSymImpPreamble(); + void emitVarTables(); void emitScopeHier(std::vector& stmts, bool destroy); void emitSymImp(const AstNetlist* netlistp); void emitDpiHdr(); @@ -176,42 +183,63 @@ class EmitCSyms final : EmitCBaseVisitorConst { return pos != std::string::npos ? scpname.substr(pos + 1) : scpname; } - static std::tuple getDimensions(const AstNodeDType* const rootDtypep) { - int pdim = 0; + // Encounter order of one dtype-chain walk: unpacked (outer), then packed, + // then a ranged basic leaf. + struct VarDims final { + int pdim = 0; // Packed array dims plus a ranged basic leaf int udim = 0; - std::string bounds; + std::vector> unpackedLR; // (left,right), outer-first + std::vector> packedLR; // (left,right), inner then leaf + + // Shared order used by both boundsString() and the table-row dv[] fill. + std::vector> flatten() const { + std::vector> flat(unpackedLR); + flat.insert(flat.end(), packedLR.cbegin(), packedLR.cend()); + return flat; + } + }; + // Vars with more dims take the residual per-statement path. + static constexpr int VPI_TABLE_MAX_DIMS = 3; + static VarDims getVarDims(const AstNodeDType* const rootDtypep) { + VarDims d; // 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++; + if (VN_IS(dtypep, PackArrayDType)) { + d.packedLR.emplace_back(adtypep->left(), adtypep->right()); + ++d.pdim; + } else { + d.unpackedLR.emplace_back(adtypep->left(), adtypep->right()); + ++d.udim; + } dtypep = adtypep->subDTypep(); } else { if (const AstBasicDType* const basicp = dtypep->basicp()) { if (basicp->isRanged()) { - bounds += " ,"; - bounds += std::to_string(basicp->left()); - bounds += ","; - bounds += std::to_string(basicp->right()); - pdim++; + d.packedLR.emplace_back(basicp->left(), basicp->right()); + ++d.pdim; } } break; // Non-array leaf } } - return {pdim, udim, bounds}; + return d; } - static std::tuple getDimensions(const AstVar* const varp) { - return getDimensions(varp->dtypep()); + static VarDims dimsFor(const ScopeVarData& svd) { return getVarDims(svd.m_varp->dtypep()); } + + // Only needed on the residual path; the table path uses dims.flatten() directly. + static std::string boundsString(const VarDims& d) { + std::string bounds; + for (const std::pair& lr : d.flatten()) { + bounds += " ,"; + bounds += std::to_string(lr.first); + bounds += ","; + bounds += std::to_string(lr.second); + } + return bounds; } static int getUnpackedElements(const AstNodeDType* rootDtypep) { @@ -310,6 +338,80 @@ class EmitCSyms final : EmitCBaseVisitorConst { return stmt; } + // True if addUOrStructMemberVars()/addUnpackedArrayUOrStructMemberVars() + // would expand this var (residual path only). + static bool varTriggersStructExpansion(const AstVar* const varp) { + const AstNodeDType* const dtypep = varp->dtypeSkipRefp(); + if (const AstNodeUOrStructDType* const sdtypep = VN_CAST(dtypep, NodeUOrStructDType)) { + return !sdtypep->packed(); + } + if (VN_IS(dtypep, UnpackArrayDType)) { + const AstNodeDType* elemp = dtypep; + while (const AstUnpackArrayDType* const adtypep + = VN_CAST(elemp->skipRefp(), UnpackArrayDType)) { + elemp = adtypep->subDTypep(); + } + if (const AstNodeUOrStructDType* const sdtypep + = VN_CAST(elemp->skipRefp(), NodeUOrStructDType)) { + return !sdtypep->packed(); + } + } + return false; + } + + // Computed once here so the forceable-eligibility check can't be + // re-derived (and desynced) by the caller. + enum class TableEntryKind : uint8_t { kTableRow, kForceableResidual, kPlainResidual }; + + // Builds one VlVarTableEntry row for 'varp', or reports which residual + // path it must take instead. + TableEntryKind tryBuildTableEntry(const ScopeVarData& svd, const AstVar* const varp, + const AstScope* const scopep, + const std::string& modClassName, const VarDims& dims, + std::string& rowOut) const { + const int pdim = dims.pdim; + const int udim = dims.udim; + if (varp->isForceable() && forceControlSignalsAreValid(scopep, varp)) { + return TableEntryKind::kForceableResidual; + } + if (udim + pdim > VPI_TABLE_MAX_DIMS) return TableEntryKind::kPlainResidual; + + const std::string vlEnumType = varp->vlEnumType(); + if (needsEmittedEntSize(vlEnumType)) + return TableEntryKind::kPlainResidual; // struct/union whole + if (varTriggersStructExpansion(varp)) + return TableEntryKind::kPlainResidual; // member expansion + // Params are often 'static constexpr' (offsetof is invalid on those); + // string params also need a runtime .c_str(). + if (varp->isParam()) return TableEntryKind::kPlainResidual; + + const std::string name = V3OutFormatter::quoteNameControls(protect(svd.m_varBasePretty)); + // nameProtect() (not protect(name())) so the offsetof member matches the + // emitted struct field: a primary I/O port keeps its unprotected name + // under --protect-ids, whereas protect() would always hash it. + const std::string member = varp->nameProtect(); + const std::string dir = varp->vlEnumDir(); + // Flat dim (left,right) ints in varInsert() order: unpacked then packed. + std::vector dv; + for (const std::pair& lr : dims.flatten()) { + dv.push_back(lr.first); + dv.push_back(lr.second); + } + + std::string row = "{\"" + name + "\", "; + row += "offsetof(" + modClassName + ", " + member + "), "; + row += vlEnumType + ", "; + row += "(" + dir + "), "; + row += std::to_string(udim) + ", " + std::to_string(pdim) + ", {"; + for (int i = 0; i < VPI_TABLE_MAX_DIMS * 2; ++i) { + if (i) row += ", "; + row += std::to_string(i < static_cast(dv.size()) ? dv[i] : 0); + } + row += "}}"; + rowOut = row; + return TableEntryKind::kTableRow; + } + static std::string insertDTypeVarStatement(const ScopeVarData& svd, const AstScope* const scopep, const std::string& prettyName, @@ -348,12 +450,9 @@ class EmitCSyms final : EmitCBaseVisitorConst { 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); + const VarDims dims = getVarDims(itemDTypep); stmts.emplace_back(insertDTypeVarStatement(svd, scopep, prettyName, cName, itemDTypep, - udim, pdim, bounds) + dims.udim, dims.pdim, boundsString(dims)) + ";"); if (const AstNodeUOrStructDType* const subp = VN_CAST(itemDTypep->skipRefp(), NodeUOrStructDType)) { @@ -425,11 +524,9 @@ class EmitCSyms final : EmitCBaseVisitorConst { const ScopeVarData& svd = itpair->second; const AstScope* const scopep = svd.m_scopep; const AstVar* const varp = svd.m_varp; - const std::tuple dimensions = getDimensions(varp); - const int pdim = std::get<0>(dimensions); - const int udim = std::get<1>(dimensions); - const std::string bounds = std::get<2>(dimensions); - stmt += insertVarStatement(svd, scopep, varp, udim, pdim, bounds); + const VarDims dims = dimsFor(svd); + stmt + += insertVarStatement(svd, scopep, varp, dims.udim, dims.pdim, boundsString(dims)); } stmt += ","; // Find __VforceVal @@ -449,11 +546,9 @@ class EmitCSyms final : EmitCBaseVisitorConst { const ScopeVarData& svd = itpair->second; const AstScope* const scopep = svd.m_scopep; const AstVar* const varp = svd.m_varp; - const std::tuple dimensions = getDimensions(varp); - const int pdim = std::get<0>(dimensions); - const int udim = std::get<1>(dimensions); - const std::string bounds = std::get<2>(dimensions); - stmt += insertVarStatement(svd, scopep, varp, udim, pdim, bounds); + const VarDims dims = dimsFor(svd); + stmt + += insertVarStatement(svd, scopep, varp, dims.udim, dims.pdim, boundsString(dims)); } stmt += "}"; @@ -986,6 +1081,50 @@ void EmitCSyms::emitSymImpPreamble() { needsNewLine = true; } if (needsNewLine) puts("\n"); + + // So split ctor sub-functions in other translation units can reference + // the VPI variable tables defined below. + if (!m_varTables.empty() || !m_scopeTableRows.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"); + } + puts("\n"); + } +} + +void EmitCSyms::emitVarTables() { + if (m_varTables.empty() && m_scopeTableRows.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. + puts("#if defined(__GNUC__)\n"); + puts("# pragma GCC diagnostic push\n"); + puts("# pragma GCC diagnostic ignored \"-Winvalid-offsetof\"\n"); + puts("#endif\n"); + for (const auto& kv : m_varTables) { + puts("extern const VlVarTableEntry " + kv.first + "[] = {\n"); + for (const std::string& row : kv.second) { + ofp()->putsNoTracking(" "); + ofp()->putsNoTracking(row); + ofp()->putsNoTracking(",\n"); + } + puts("};\n"); + } + if (!m_scopeTableRows.empty()) { + puts("extern const VlScopeTableEntry " + m_scopeTableName + "[] = {\n"); + for (const std::string& row : m_scopeTableRows) { + ofp()->putsNoTracking(" "); + ofp()->putsNoTracking(row); + ofp()->putsNoTracking(",\n"); + } + puts("};\n"); + } + puts("#if defined(__GNUC__)\n"); + puts("# pragma GCC diagnostic pop\n"); + puts("#endif\n"); } void EmitCSyms::emitScopeHier(std::vector& stmts, bool destroy) { @@ -1110,21 +1249,30 @@ std::vector EmitCSyms::getSymCtorStmts() { + protect("__Vconfigure") + "(" + (first ? "true" : "false") + ");"); } - add("// Setup scopes"); - for (const auto& itpair : m_scopeNames) { - const ScopeData& sd = itpair.second; - std::string stmt; - stmt += protect("__Vscopep_" + sd.m_symName) + " = new VerilatedScope{this, \""; - stmt += V3OutFormatter::quoteNameControls( - VIdProtect::protectWordsIf(sd.m_prettyName, true)); - stmt += "\", \""; - stmt += V3OutFormatter::quoteNameControls(protect(scopeDecodeIdentifier(sd.m_prettyName))); - stmt += "\", \""; - stmt += V3OutFormatter::quoteNameControls(sd.m_defName); - stmt += "\", "; - stmt += std::to_string(sd.m_timeunit); - stmt += ", VerilatedScope::" + sd.m_type + "};"; - add(stmt); + // Every scope has the same construction shape, so all fold into one table with no + // residual; offsetof bakes the target __Vscopep_* member address into each row. + if (!m_scopeNames.empty()) { + add("// Setup scopes"); + const std::string symClass = symClassName(); + for (const auto& itpair : m_scopeNames) { + const ScopeData& sd = itpair.second; + std::string row + = "{offsetof(" + symClass + ", " + protect("__Vscopep_" + sd.m_symName) + "), \""; + row += V3OutFormatter::quoteNameControls( + VIdProtect::protectWordsIf(sd.m_prettyName, true)); + row += "\", \""; + row += V3OutFormatter::quoteNameControls( + protect(scopeDecodeIdentifier(sd.m_prettyName))); + row += "\", \""; + row += V3OutFormatter::quoteNameControls(sd.m_defName); + row += "\", "; + row += std::to_string(sd.m_timeunit); + row += ", VerilatedScope::" + sd.m_type + "}"; + m_scopeTableRows.emplace_back(std::move(row)); + } + m_scopeTableName = symClass + "__VpiScopeTable"; + add("VerilatedScope::scopesConstructFromTable(" + m_scopeTableName + ", " + + std::to_string(m_scopeNames.size()) + ", this);"); } emitScopeHier(stmts, false); @@ -1154,45 +1302,98 @@ std::vector EmitCSyms::getSymCtorStmts() { } } - // It would be less code if each module inserted its own variables. Someday. + // Relies on m_scopeVars being sorted so each VPI scope's vars are + // contiguous. Tables are deduplicated by row content, not by scope, so + // distinct scopes mapping to the same C++ class (e.g. --public-flat-rw) + // still share a table when their rows are identical. if (!m_scopeVars.empty()) { add("// Setup public variables"); - for (const auto& itpair : m_scopeVars) { - const ScopeVarData& svd = itpair.second; - const AstScope* const scopep = svd.m_scopep; - const AstVar* const varp = svd.m_varp; - const std::tuple dimensions = getDimensions(varp); - const int pdim = std::get<0>(dimensions); - const int udim = std::get<1>(dimensions); - const std::string bounds = std::get<2>(dimensions); + // Keyed by '\0'-joined row text so identical rows share one table. + std::unordered_map tableByRows; + int tableCounter = 0; - const std::pair isForceControlResult = isForceControlSignal(varp); - const bool currentSignalIsForceControlSignal = isForceControlResult.first; - const std::string baseSignalName = isForceControlResult.second; - if (currentSignalIsForceControlSignal && baseSignalIsPublic(scopep, baseSignalName) - && baseSignalIsValid(scopep, varp, baseSignalName)) { - continue; - } + auto it = m_scopeVars.cbegin(); + while (it != m_scopeVars.cend()) { + const std::string scopeName = it->second.m_scopeName; + const AstScope* const instScopep = it->second.m_scopep; + const AstNodeModule* const modp = it->second.m_modp; + const std::string modClassName = EmitCUtil::prefixNameProtect(modp); - if (varp->isForceable() && forceControlSignalsAreValid(scopep, varp)) { - const std::string stmt - = insertForceableVarStatement(svd, scopep, varp, udim, pdim, bounds) + ";"; - add(stmt); - } else { - 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); + std::vector rows; + std::vector residual; + + for (; it != m_scopeVars.cend() && it->second.m_scopeName == scopeName; ++it) { + const ScopeVarData& svd = it->second; + const AstScope* const scopep = svd.m_scopep; + UASSERT(scopep == instScopep && svd.m_modp == modp, + "VPI scope '" << scopeName << "' spans multiple C++ instances"); + const AstVar* const varp = svd.m_varp; + const VarDims dims = dimsFor(svd); + + // Force-control signals fold into the base signal's forceable insert. + const std::pair fc = isForceControlSignal(varp); + if (fc.first && baseSignalIsPublic(scopep, fc.second) + && baseSignalIsValid(scopep, varp, fc.second)) { + continue; + } + + std::string row; + const TableEntryKind kind + = tryBuildTableEntry(svd, varp, scopep, modClassName, dims, row); + switch (kind) { + case TableEntryKind::kTableRow: rows.emplace_back(row); break; + case TableEntryKind::kForceableResidual: { + const std::string bounds = boundsString(dims); + residual.emplace_back(insertForceableVarStatement(svd, scopep, varp, dims.udim, + dims.pdim, bounds) + + ";"); + break; + } + case TableEntryKind::kPlainResidual: { + const std::string bounds = boundsString(dims); + residual.emplace_back( + insertVarStatement(svd, scopep, varp, dims.udim, dims.pdim, bounds) + ";"); + if (const AstNodeUOrStructDType* const sdtypep + = VN_CAST(varp->dtypeSkipRefp(), NodeUOrStructDType)) { + if (!sdtypep->packed()) { + addUOrStructMemberVars(residual, svd, scopep, svd.m_varBasePretty, + protect(varp->name()), sdtypep); + } + } else if (VN_IS(varp->dtypeSkipRefp(), UnpackArrayDType)) { + addUnpackedArrayUOrStructMemberVars(residual, svd, scopep, + svd.m_varBasePretty, + protect(varp->name()), varp->dtypep()); } - } else if (VN_IS(varp->dtypeSkipRefp(), UnpackArrayDType)) { - addUnpackedArrayUOrStructMemberVars(stmts, svd, scopep, svd.m_varBasePretty, - protect(varp->name()), varp->dtypep()); + break; + } } } + + if (!rows.empty()) { + const size_t rowCount = rows.size(); + std::string key; + for (const std::string& r : rows) { + key += r; + key += '\0'; + } + const auto itt = tableByRows.find(key); + std::string tableName; + if (itt != tableByRows.end()) { + tableName = itt->second; + } else { + tableName = modClassName + "__VpiVarTable" + std::to_string(tableCounter++); + tableByRows.emplace(std::move(key), tableName); + m_varTables.emplace_back(tableName, std::move(rows)); + } + + std::string call + = protect("__Vscopep_" + scopeName) + "->varsInsertFromTable(" + tableName + + ", " + std::to_string(rowCount) + ", &(" + + VIdProtect::protectIf(instScopep->nameDotless(), instScopep->protect()) + + "));"; + add(call); + } + for (const std::string& r : residual) add(r); } } @@ -1308,6 +1509,7 @@ void EmitCSyms::emitSymImp(const AstNetlist* netlistp) { openNewOutputSourceFile(symClassName(), true, true, "Symbol table implementation internals"); emitSymImpPreamble(); + emitVarTables(); // Constructor const std::string ctorArgs