V3EmitCSyms: table-driven VPI symbol registration and scope construction

This commit is contained in:
Nick Brereton 2026-07-14 11:07:52 -04:00
parent 3b43b304a3
commit 992d1dd0ff
4 changed files with 359 additions and 81 deletions

View File

@ -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<uint8_t*>(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<VerilatedVarFlags>(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<uint8_t*>(symsp);
for (size_t i = 0; i < n; ++i) {
const VlScopeTableEntry& e = entp[i];
VerilatedScope** const slotp = reinterpret_cast<VerilatedScope**>(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 {

View File

@ -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<VerilatedVar*, VerilatedVar*> 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 "<null>"
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);

View File

@ -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 <cstddef> // offsetof (used by generated VlVarTableEntry tables)
#include <cstdint>
#include <cinttypes>
#include <cmath>

View File

@ -26,6 +26,7 @@
#include <algorithm>
#include <cstring>
#include <map>
#include <unordered_map>
#include <vector>
VL_DEFINE_DEBUG_FUNCTIONS;
@ -108,10 +109,16 @@ class EmitCSyms final : EmitCBaseVisitorConst {
const bool m_dpiHdrOnly; // Only emit the DPI header
std::vector<std::string> m_splitFuncNames; // Split file names
VDouble0 m_statVarScopeBytes; // Statistic tracking
// name -> initializer rows, built in getSymCtorStmts()
std::vector<std::pair<std::string, std::vector<std::string>>> m_varTables;
// Single VlScopeTableEntry[] table for all scopes, built in getSymCtorStmts()
std::string m_scopeTableName;
std::vector<std::string> m_scopeTableRows;
// METHODS
void emitSymHdr();
void emitSymImpPreamble();
void emitVarTables();
void emitScopeHier(std::vector<std::string>& 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<int, int, std::string> 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<std::pair<int, int>> unpackedLR; // (left,right), outer-first
std::vector<std::pair<int, int>> packedLR; // (left,right), inner then leaf
// Shared order used by both boundsString() and the table-row dv[] fill.
std::vector<std::pair<int, int>> flatten() const {
std::vector<std::pair<int, int>> 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<int, int, std::string> 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<int, int>& 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<int> dv;
for (const std::pair<int, int>& 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<int>(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<int, int, std::string> 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<int, int, std::string> 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<int, int, std::string> 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<std::string>& stmts, bool destroy) {
@ -1110,21 +1249,30 @@ std::vector<std::string> EmitCSyms::getSymCtorStmts() {
+ protect("__Vconfigure") + "(" + (first ? "true" : "false") + ");");
}
// 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 stmt;
stmt += protect("__Vscopep_" + sd.m_symName) + " = new VerilatedScope{this, \"";
stmt += V3OutFormatter::quoteNameControls(
std::string row
= "{offsetof(" + symClass + ", " + protect("__Vscopep_" + sd.m_symName) + "), \"";
row += 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);
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,48 +1302,101 @@ std::vector<std::string> 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<int, int, std::string> 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<std::string, std::string> tableByRows;
int tableCounter = 0;
const std::pair<bool, std::string> isForceControlResult = isForceControlSignal(varp);
const bool currentSignalIsForceControlSignal = isForceControlResult.first;
const std::string baseSignalName = isForceControlResult.second;
if (currentSignalIsForceControlSignal && baseSignalIsPublic(scopep, baseSignalName)
&& baseSignalIsValid(scopep, varp, baseSignalName)) {
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);
std::vector<std::string> rows;
std::vector<std::string> 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<bool, std::string> fc = isForceControlSignal(varp);
if (fc.first && baseSignalIsPublic(scopep, fc.second)
&& baseSignalIsValid(scopep, varp, fc.second)) {
continue;
}
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);
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(stmts, svd, scopep, svd.m_varBasePretty,
addUOrStructMemberVars(residual, svd, scopep, svd.m_varBasePretty,
protect(varp->name()), sdtypep);
}
} else if (VN_IS(varp->dtypeSkipRefp(), UnpackArrayDType)) {
addUnpackedArrayUOrStructMemberVars(stmts, svd, scopep, svd.m_varBasePretty,
addUnpackedArrayUOrStructMemberVars(residual, 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);
}
}
return stmts;
}
@ -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