Files
verilator/src/V3EmitCSyms.cpp
T
Geza Lore 8546d5db06 Internals: Move the eval loop into the runtime library (#8225)
The loops modelling the SystemVerilog scheduling regions are no longer
generated. They now live in 'VerilatedEvalLoop' in the runtime library.
The generated model holds one as a member, passing itself to it, and
exposes each evaluation entry point to it as a pure virtual method on
VerilatedModel. The model's 'eval' and 'eval_step' remain the top level
entry points, and are backward compatible.

V3Sched no longer emits '_eval' or '_eval_settle', etc.. Instead every
evaluation entry point called from the runtime is enumerated by 'VEval',

Scheduling creates all entry points, for all scheduling regions, even if
they are empty, and the runtime eval loop calls everything
unconditionally. If regions are empty, this is simply a call to an empty
function. This will hurt performance on very small models, but should
not be noticeable on anything meaningful, so it is likely best to keep
to reduce complexity.

A scheduling entry points evaluate a single iteration and returns
whether it did any work, they are effectively the previous
`_eval_phase_*` functions.
2026-08-27 07:53:02 -04:00

1680 lines
73 KiB
C++

// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Emit C++ for tree
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of either the GNU Lesser General Public License Version 3
// or the Perl Artistic License Version 2.0.
// SPDX-FileCopyrightText: 2003-2026 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
#include "V3EmitC.h"
#include "V3EmitCBase.h"
#include "V3ExecGraph.h"
#include "V3LanguageWords.h"
#include "V3StackCount.h"
#include "V3Stats.h"
#include <algorithm>
#include <cstring>
#include <map>
#include <unordered_map>
#include <vector>
VL_DEFINE_DEBUG_FUNCTIONS;
//######################################################################
// Symbol table emitting
// Some handy short-hands to reduce verbosity
static constexpr auto symClassName = &EmitCUtil::symClassName;
static constexpr auto topClassName = &EmitCUtil::topClassName;
class EmitCSyms final : EmitCBaseVisitorConst {
// NODE STATE
// Cleared on Netlist
// AstNodeModule::user1() -> bool. Set true __Vconfigure called
const VNUser1InUse m_inuser1;
// TYPES
struct ScopeData final {
const AstNode* m_nodep;
const std::string m_symName;
const std::string m_prettyName;
const std::string m_defName;
const int m_timeunit;
std::string m_type; // TODO: this should be an enum
ScopeData(const AstNode* nodep, const std::string& symName, const std::string& prettyName,
const std::string& defName, int timeunit, const std::string& type)
: m_nodep{nodep}
, m_symName{symName}
, m_prettyName{prettyName}
, m_defName{defName}
, m_timeunit{timeunit}
, m_type{type} {}
};
struct ScopeFuncData final {
const AstScopeName* const m_scopep;
const AstCFunc* const m_cfuncp;
const AstNodeModule* const m_modp;
ScopeFuncData(const AstScopeName* scopep, const AstCFunc* funcp, const AstNodeModule* modp)
: m_scopep{scopep}
, m_cfuncp{funcp}
, m_modp{modp} {}
};
struct ScopeVarData final {
const std::string m_scopeName;
const std::string m_varBasePretty;
const AstVar* const m_varp;
const AstNodeModule* const m_modp;
const AstScope* const m_scopep;
ScopeVarData(const std::string& scopeName, const std::string& varBasePretty,
const AstVar* varp, const AstNodeModule* modp, const AstScope* scopep)
: m_scopeName{scopeName}
, m_varBasePretty{varBasePretty}
, m_varp{varp}
, m_modp{modp}
, m_scopep{scopep} {}
};
using ScopeNames = std::map<const std::string, ScopeData>;
using ScopeModPair = std::pair<const AstScope*, AstNodeModule*>;
using ModVarPair = std::pair<const AstNodeModule*, const AstVar*>;
// Vars with more dims take the residual per-statement path.
static constexpr int VPI_TABLE_MAX_DIMS = 3;
// STATE
AstCFunc* m_cfuncp = nullptr; // Current function
AstNodeModule* m_modp = nullptr; // Current module
std::vector<ScopeModPair> m_scopes; // Every scope by module
std::vector<AstCFunc*> m_dpis; // DPI functions
std::vector<ModVarPair> m_modVars; // Each public {mod,var}
std::map<const std::string, ScopeFuncData> m_scopeFuncs; // Each {scope,dpi-export-func}
std::map<const std::string, ScopeVarData> m_scopeVars; // Each {scope,public-var}
ScopeNames m_scopeNames; // Each unique AstScopeName. Dpi scopes added later
ScopeNames m_dpiScopeNames; // Each unique AstScopeName for DPI export
ScopeNames m_vpiScopeCandidates; // All scopes for VPI
// The actual hierarchy of scopes
std::map<const std::string, std::vector<std::string>> m_vpiScopeHierarchy;
int m_coverBins = 0; // Global coverage bin number for non-object helper functions
// Counts bins within the current module. Coverage storage is also emitted
// on each module object so no-inline instances keep independent counters
// when forcePerInstance is used.
int m_modCoverBins = 0; // Per-module coverage bin number
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();
void emitDpiImp();
void emitSplit(std::vector<std::string>& stmts, const std::string& name, size_t max_stmts);
std::vector<std::string> getSymCtorStmts();
std::vector<std::string> getSymDtorStmts();
static size_t stmtCost(const std::string& stmt) {
if (stmt.empty()) return 0;
if (VString::startsWith(stmt, "/")) return 0;
return static_cast<size_t>(AstNode::INSTR_COUNT_SYM);
}
static void nameCheck(AstNode* nodep) {
// Prevent GCC compile time error; name check all things that reach C++ code
if (nodep->name().empty()) return;
const AstCFunc* const cfuncp = VN_CAST(nodep, CFunc);
if (!cfuncp || (!cfuncp->isConstructor() && !cfuncp->isDestructor())) {
const std::string rsvd = V3LanguageWords::isKeyword(nodep->name());
if (rsvd != "") {
// Generally V3Name should find all of these and throw SYMRSVDWORD.
// We'll still check here because the compiler errors
// resulting if we miss this warning are SO nasty
nodep->v3error("Symbol matching " + rsvd
+ " reserved word reached emitter,"
" should have hit SYMRSVDWORD: "
<< nodep->prettyNameQ());
}
}
}
static std::string scopeSymString(const std::string& scpname) {
std::string out = scpname;
std::string::size_type pos;
while ((pos = out.find("__PVT__")) != std::string::npos) out.replace(pos, 7, "");
if (VString::startsWith(out, "TOP__DOT__")) out.replace(0, 10, "");
if (VString::startsWith(out, "TOP.")) out.replace(0, 4, "");
while ((pos = out.find('.')) != std::string::npos) out.replace(pos, 1, "__");
while ((pos = out.find("__DOT__")) != std::string::npos) out.replace(pos, 7, "__");
return out;
}
static string scopeDecodeIdentifier(const std::string& scpname) {
std::string::size_type pos = std::string::npos;
// Remove hierarchy
size_t i = 0;
// always makes progress
while (i < scpname.length()) {
if (scpname[i] == '\\') {
while (i < scpname.length() && scpname[i] != ' ') ++i;
++i; // Proc ' ', it should always be there. Then grab '.' on next cycle
} else {
while (i < scpname.length() && scpname[i] != '.') ++i;
if (i < scpname.length()) pos = i++;
}
}
return pos != std::string::npos ? scpname.substr(pos + 1) : scpname;
}
// 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::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;
}
};
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)) {
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()) {
d.packedLR.emplace_back(basicp->left(), basicp->right());
++d.pdim;
}
}
break; // Non-array leaf
}
}
return d;
}
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) {
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<bool, std::string> 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.
for (const std::string forceControlSuffix : {"__VforceEn", "__VforceVal", "__VforceRd"}) {
const std::size_t suffixPos = signalVarp->name().find(forceControlSuffix);
const bool suffixFound = suffixPos != std::string::npos;
const bool suffixIsAtEnd
= suffixPos + forceControlSuffix.length() == signalVarp->name().length();
if (!(suffixFound && suffixIsAtEnd)) continue;
const std::string baseSignalName = signalVarp->name().substr(0, suffixPos);
return std::pair<bool, std::string>{true, baseSignalName};
}
return std::pair<bool, std::string>{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) {
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 += ", &(";
stmt += varName;
stmt += "), false, ";
} else if (varp->vlEnumType() == "VLVT_STRING"
&& !VN_IS(varp->subDTypep(), UnpackArrayDType)) {
stmt += ", const_cast<void*>(static_cast<const void*>(";
stmt += varName;
stmt += ".c_str())), true, ";
} else {
stmt += ", const_cast<void*>(static_cast<const void*>(&(";
stmt += varName;
stmt += "))), true, ";
}
const std::string entSize = needsEntSize
? "sizeof(" + varName + ") / "
+ std::to_string(getUnpackedElements(varp->dtypep()))
: "";
appendVarProperties(stmt, vlEnumType, varp->vlEnumDir(), udim, pdim, bounds, entSize);
return stmt;
}
// tryBuildTableEntry() classifies each public var once, so the caller does
// not re-derive (and risk desyncing) the forceable-eligibility test.
enum class TableEntryKind : uint8_t {
TABLE_ROW, // Registered via a VlVarTableEntry[] table row
FORCEABLE_RESIDUAL, // Residual, via insertForceableVarStatement()
PLAIN_RESIDUAL, // Residual, via insertVarStatement() (+ struct/array expansion)
};
// 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::FORCEABLE_RESIDUAL;
}
if (udim + pdim > VPI_TABLE_MAX_DIMS) return TableEntryKind::PLAIN_RESIDUAL;
const std::string vlEnumType = varp->vlEnumType();
if (needsEmittedEntSize(vlEnumType))
return TableEntryKind::PLAIN_RESIDUAL; // struct/union whole
// Params are often 'static constexpr' (offsetof is invalid on those);
// string params also need a runtime .c_str().
if (varp->isParam()) return TableEntryKind::PLAIN_RESIDUAL;
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::TABLE_ROW;
}
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<std::string>& 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 VarDims dims = getVarDims(itemDTypep);
stmts.emplace_back(insertDTypeVarStatement(svd, scopep, prettyName, cName, itemDTypep,
dims.udim, dims.pdim, boundsString(dims))
+ ";");
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<std::string>& 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) {
std::string stmt;
stmt += protect("__Vscopep_" + svd.m_scopeName) + "->forceableVarInsert(\"";
stmt += V3OutFormatter::quoteNameControls(protect(svd.m_varBasePretty)) + '"';
const std::string varName = VIdProtect::protectIf(scopep->nameDotless(), scopep->protect())
+ "." + protect(varp->name());
assert(!varp->isParam()); // Forceable params do not make sense
stmt += ", &(";
stmt += varName;
stmt += "), false, ";
stmt += varp->vlEnumType(); // VLVT_UINT32 etc
stmt += ", ";
stmt += varp->vlEnumDir(); // VLVD_IN etc
stmt += ", &(";
stmt += varName + "__VforceRd";
stmt += "), \"" + V3OutFormatter::quoteNameControls(protect(svd.m_varBasePretty))
+ "__VforceRd" + '"';
stmt += ", {";
for (const std::string forceControlSuffix : {"__VforceEn", "__VforceVal"}) {
const std::string enableSignalKey
= getKeyName(scopep, varp->name() + forceControlSuffix);
const std::map<const std::string, ScopeVarData>::const_iterator itpair
= m_scopeVars.find(enableSignalKey);
if (itpair == m_scopeVars.end()) {
varp->v3fatalSrc("Signal "
<< varp->prettyNameQ()
<< " is marked forceable, but the force control signal '"
<< varp->name() << forceControlSuffix
<< "' can not be found in m_scopeVars with key '"
<< enableSignalKey << "'.");
}
const ScopeVarData& svd = itpair->second;
const AstScope* const scopep = svd.m_scopep;
const AstVar* const varp = svd.m_varp;
const VarDims dims = dimsFor(svd);
stmt
+= insertVarStatement(svd, scopep, varp, dims.udim, dims.pdim, boundsString(dims));
if (forceControlSuffix == "__VforceEn") stmt += ",";
}
stmt += "}";
stmt += ", ";
stmt += std::to_string(udim);
stmt += ", ";
stmt += std::to_string(pdim);
stmt += bounds;
stmt += ")";
return stmt;
}
static std::string getKeyName(const AstScope* const scopep, const std::string& signal_name) {
// Copies the process from `varsExpand` which created the keys in the first place, in order
// signal can be found.
std::string whole = scopep->name() + "__DOT__" + signal_name;
std::string scpName;
if (VString::startsWith(whole, "__DOT__TOP")) whole.replace(0, 10, "");
const std::string::size_type dpos = whole.rfind("__DOT__");
if (dpos != std::string::npos) { scpName = whole.substr(0, dpos); }
const std::string scpSym = scopeSymString(VName::dehash(scpName));
return scpSym + " " + signal_name;
}
bool baseSignalIsValid(const AstScope* const scopep, const AstVar* const controlSignalVarp,
const std::string& baseSignalName) const {
const std::string baseSignalKey = getKeyName(scopep, baseSignalName);
const std::map<const std::string, ScopeVarData>::const_iterator baseSignalIt
= m_scopeVars.find(baseSignalKey);
if (baseSignalIt == m_scopeVars.end()) {
// This means that the signal is forceable, but not public, so only the force control
// signals show up in the m_scopeVars, but not the base signal itself.
// Expect this branch not to be hit, because `baseSignalIsPublic` is checked before
// this, so this function will not get called in that case.
return false;
} else {
const AstVar* const baseSignalVarp = baseSignalIt->second.m_varp;
if (m_scopeVars.count(baseSignalName) > 1) {
baseSignalVarp->v3fatalSrc(
"Found Signal " << baseSignalName
<< ", but also found at least one other signal with name"
<< baseSignalName << " occurring in the same scope.");
return false;
}
if (!baseSignalVarp->isForceable()) {
controlSignalVarp->v3fatalSrc(
"Found signal " << controlSignalVarp->prettyNameQ()
<< " which is a force control signal, but the base signal "
<< baseSignalVarp->prettyNameQ()
<< " is not marked as forceable.");
return false;
}
}
return true;
}
bool baseSignalIsPublic(const AstScope* const scopep,
const std::string& baseSignalName) const {
const std::string baseSignalKey = getKeyName(scopep, baseSignalName);
const std::map<const std::string, ScopeVarData>::const_iterator baseSignalIt
= m_scopeVars.find(baseSignalKey);
if (baseSignalIt == m_scopeVars.end()) {
return false;
} else {
const AstVar* const baseSignalVarp = baseSignalIt->second.m_varp;
// Should not actually occur if the variable is in the m_scopeVars
if (!baseSignalVarp->isSigPublic()) return false;
}
return true;
}
bool forceControlSignalsAreValid(const AstScope* const scopep,
const AstVar* const baseSignalVarp) const {
constexpr std::array<const char*, 2> forceControlSuffixes = {"__VforceEn", "__VforceVal"};
return std::all_of(
forceControlSuffixes.begin(), forceControlSuffixes.end(),
[scopep, baseSignalVarp, this](const std::string& forceControlSuffix) {
const std::string controlSignalName = baseSignalVarp->name() + forceControlSuffix;
const std::string controlSignalKey = getKeyName(scopep, controlSignalName);
const std::size_t controlSignalCount = m_scopeVars.count(controlSignalKey);
if (controlSignalCount == 0) {
baseSignalVarp->v3fatalSrc("Signal "
<< baseSignalVarp->prettyNameQ()
<< " is marked forceable, but the control signal '"
<< controlSignalName
<< "' can not be found in m_scopeVars with key '"
<< controlSignalKey << "'.");
return false;
}
if (controlSignalCount > 1) {
baseSignalVarp->v3fatalSrc("The control signal '"
<< controlSignalName << "' for forceable signal "
<< baseSignalVarp->prettyNameQ()
<< " occurs several times within the same scope.");
return false;
}
return true;
});
}
/// (scp, m_vpiScopeCandidates, m_scopeNames) -> m_scopeNames
/// Look for parent scopes of scp in m_vpiScopeCandidates (separated by __DOT__ or ".")
/// Then add/update entry in m_scopeNames if not already there
void varHierarchyScopes(std::string scp) {
std::string::size_type prd_pos = scp.rfind('.');
std::string::size_type dot_pos = scp.rfind("__DOT__");
while (!scp.empty()) {
const auto scpit = m_vpiScopeCandidates.find(scopeSymString(scp));
if ((scpit != m_vpiScopeCandidates.end())
&& (m_scopeNames.find(scp) == m_scopeNames.end())) {
// If not in m_scopeNames, add it, otherwise just update m_type
const auto pair = m_scopeNames.emplace(scpit->second.m_symName, scpit->second);
if (!pair.second) pair.first->second.m_type = scpit->second.m_type;
}
// resize and advance pointers
if ((prd_pos < dot_pos || prd_pos == string::npos) && dot_pos != string::npos) {
scp.resize(dot_pos);
dot_pos = scp.rfind("__DOT__");
} else {
if (prd_pos == string::npos) break;
scp.resize(prd_pos);
prd_pos = scp.rfind('.');
}
}
}
void varsExpand() {
// We didn't have all m_scopes loaded when we encountered variables, so expand them now
// It would be less code if each module inserted its own variables.
// Someday.
for (const ScopeModPair& smPair : m_scopes) {
const AstScope* const scopep = smPair.first;
const AstNodeModule* const smodp = smPair.second;
for (const ModVarPair& mvPair : m_modVars) {
const AstNodeModule* const modp = mvPair.first;
const AstVar* const varp = mvPair.second;
if (modp != smodp) continue;
// Need to split the module + var name into the
// original-ish full scope and variable name under that scope.
// The module instance name is included later, when we
// know the scopes this module is under
std::string whole = scopep->name() + "__DOT__" + varp->name();
std::string scpName;
std::string varBase;
if (VString::startsWith(whole, "__DOT__TOP")) whole.replace(0, 10, "");
const std::string::size_type dpos = whole.rfind("__DOT__");
if (dpos != std::string::npos) {
scpName = whole.substr(0, dpos);
varBase = whole.substr(dpos + std::strlen("__DOT__"));
} else {
varBase = whole;
}
// UINFO(9, "For " << scopep->name() << " - " << varp->name() << " Scp "
// << scpName << "Var " << varBase);
const std::string varBasePretty = AstNode::vpiName(VName::dehash(varBase));
const std::string scpPretty = AstNode::prettyName(VName::dehash(scpName));
const std::string scpSym = scopeSymString(VName::dehash(scpName));
// UINFO(9, " scnameins sp " << scpName << " sp " << scpPretty << " ss "
// << scpSym);
if (v3Global.opt.vpi()) varHierarchyScopes(scpName);
m_scopeNames.emplace( //
std::piecewise_construct, //
std::forward_as_tuple(scpSym), //
std::forward_as_tuple(varp, scpSym, scpPretty, "<null>", 0, "SCOPE_OTHER"));
m_scopeVars.emplace( //
std::piecewise_construct, //
std::forward_as_tuple(scpSym + " " + varp->name()), //
std::forward_as_tuple(scpSym, varBasePretty, varp, modp, scopep));
}
}
}
void buildVpiHierarchy() {
for (const auto& itpair : m_scopeNames) {
const std::string symName = itpair.second.m_symName;
std::string above = symName;
if (VString::startsWith(above, "TOP.")) above.replace(0, 4, "");
while (!above.empty()) {
const std::string::size_type pos = above.rfind("__");
if (pos == std::string::npos) break;
above.resize(pos);
if (m_vpiScopeHierarchy.find(above) != m_vpiScopeHierarchy.end()) {
m_vpiScopeHierarchy[above].push_back(symName);
break;
}
}
m_vpiScopeHierarchy[symName] = std::vector<string>();
}
}
// VISITORS
void visit(AstNetlist* nodep) override {
// Collect list of scopes
iterateChildrenConst(nodep);
varsExpand();
if (v3Global.opt.vpi()) buildVpiHierarchy();
if (v3Global.dpi()) {
// add dpi scopes to m_scopeNames if not already there
for (const auto& scp : m_dpiScopeNames) m_scopeNames.emplace(scp.first, scp.second);
}
// Sort by names, so line/process order matters less
std::stable_sort(m_scopes.begin(), m_scopes.end(),
[](const ScopeModPair& a, const ScopeModPair& b) {
return a.first->name() < b.first->name();
});
std::stable_sort(m_dpis.begin(), m_dpis.end(), //
[](const AstCFunc* ap, const AstCFunc* bp) {
if (ap->dpiImportPrototype() != bp->dpiImportPrototype()) {
return bp->dpiImportPrototype();
}
return ap->name() < bp->name();
});
// Output
if (!m_dpiHdrOnly) {
// Must emit implementation first to determine number of splits
emitSymImp(nodep);
emitSymHdr();
}
if (v3Global.dpi()) {
emitDpiHdr();
if (!m_dpiHdrOnly) emitDpiImp();
}
}
void visit(AstConstPool* nodep) override {} // Ignore
void visit(AstNodeModule* nodep) override {
nameCheck(nodep);
VL_RESTORER(m_modp);
VL_RESTORER(m_modCoverBins);
m_modp = nodep;
// Restart bin numbering for the object-local coverage array of this
// module class.
m_modCoverBins = 0;
iterateChildrenConst(nodep);
}
void visit(AstCellInlineScope* nodep) override {
if (!v3Global.opt.vpi()) return;
const std::string type = (nodep->origModName() == "__BEGIN__") ? "SCOPE_OTHER" //
: "SCOPE_MODULE";
const std::string name = nodep->scopep()->shortName() + "__DOT__" + nodep->name();
const int timeunit = m_modp->timeunit().powerOfTen();
m_vpiScopeCandidates.emplace( //
std::piecewise_construct, //
std::forward_as_tuple(scopeSymString(name)), //
std::forward_as_tuple(nodep, scopeSymString(name), AstNode::vpiName(name),
type == "SCOPE_MODULE" ? nodep->origModName() : "<null>",
timeunit, type));
}
void visit(AstScope* nodep) override {
if (VN_IS(m_modp, Class)) return; // The ClassPackage is what is visible
nameCheck(nodep);
m_scopes.emplace_back(nodep, m_modp);
if (v3Global.opt.vpi() && !nodep->isTop()) {
const std::string type = VN_IS(nodep->modp(), Package) ? "SCOPE_PACKAGE" //
: "SCOPE_MODULE";
const int timeunit = m_modp->timeunit().powerOfTen();
m_vpiScopeCandidates.emplace( //
std::piecewise_construct, //
std::forward_as_tuple(scopeSymString(nodep->name())), //
std::forward_as_tuple(nodep, scopeSymString(nodep->name()),
AstNode::vpiName(nodep->shortName()),
nodep->modp()->origName(), timeunit, type));
}
iterateChildrenConst(nodep);
}
void visit(AstScopeName* nodep) override {
const std::string name = nodep->scopeSymName();
// UINFO(9, "scnameins sp " << nodep->name() << " sp " << nodep->scopePrettySymName()
// << " ss" << name);
const int timeunit = m_modp ? m_modp->timeunit().powerOfTen() : 0;
m_dpiScopeNames.emplace( //
std::piecewise_construct, //
std::forward_as_tuple(name), //
std::forward_as_tuple(nodep, name, nodep->scopePrettySymName(), "<null>", timeunit,
"SCOPE_OTHER"));
if (nodep->dpiExport()) {
UASSERT_OBJ(m_cfuncp, nodep, "ScopeName not under DPI function");
m_scopeFuncs.emplace( //
std::piecewise_construct, //
std::forward_as_tuple(name + " " + m_cfuncp->name()), //
std::forward_as_tuple(nodep, m_cfuncp, m_modp));
} else {
// Note emplace does not construct when duplicate key
m_dpiScopeNames.emplace( //
std::piecewise_construct, //
std::forward_as_tuple(nodep->scopeDpiName()), //
std::forward_as_tuple(nodep, nodep->scopeDpiName(), nodep->scopePrettyDpiName(),
"<null>", timeunit, "SCOPE_OTHER"));
}
}
void visit(AstVar* nodep) override {
nameCheck(nodep);
iterateChildrenConst(nodep);
// Record if public, ignoring locals
if ((nodep->isSigUserRdPublic() || nodep->isSigUserRWPublic()) && !m_cfuncp) {
m_modVars.emplace_back(m_modp, nodep);
}
}
void visit(AstNodeCoverDecl* nodep) override {
// Assign both global and module-local bin numbers. Most generated
// coverage uses module-local counters, but static/package/class helper
// functions may have no vlSelf and still need the global array.
if (!nodep->dataDeclNullp()) { // else duplicate we don't need code for
nodep->binNum(m_coverBins);
m_coverBins += nodep->size();
nodep->localBinNum(m_modCoverBins);
m_modCoverBins += nodep->size();
}
}
void visit(AstCFunc* nodep) override {
nameCheck(nodep);
if (nodep->dpiImportPrototype() || nodep->dpiExportDispatcher()) m_dpis.push_back(nodep);
VL_RESTORER(m_cfuncp);
m_cfuncp = nodep;
iterateChildrenConst(nodep);
}
//---------------------------------------
void visit(AstConst*) override {}
void visit(AstNode* nodep) override { iterateChildrenConst(nodep); }
public:
explicit EmitCSyms(AstNetlist* nodep, bool dpiHdrOnly)
: m_dpiHdrOnly{dpiHdrOnly} {
iterateConst(nodep);
}
};
void EmitCSyms::emitSymHdr() {
UINFO(6, __FUNCTION__ << ": ");
openNewOutputHeaderFile(symClassName(), "Symbol table internal header");
puts("//\n");
puts("// Internal details; most calling programs do not need this header,\n");
puts("// unless using verilator public meta comments.\n");
ofp()->putsGuard();
puts("\n");
ofp()->putsIntTopInclude();
puts("#include \"verilated.h\"\n");
if (v3Global.needTraceDumper()) {
for (const string& base : v3Global.opt.traceSourceLangs())
puts("#include \"" + base + ".h\"\n");
}
if (v3Global.opt.usesProfiler()) puts("#include \"verilated_profiler.h\"\n");
puts("\n// INCLUDE MODEL CLASS\n");
puts("\n#include \"" + topClassName() + ".h\"\n");
puts("\n// INCLUDE MODULE CLASSES\n");
for (AstNodeModule *nodep = v3Global.rootp()->modulesp(), *nextp; nodep; nodep = nextp) {
nextp = VN_AS(nodep->nextp(), NodeModule);
if (VN_IS(nodep, Class)) continue; // Class included earlier
putns(nodep, "#include \"" + EmitCUtil::prefixNameProtect(nodep) + ".h\"\n");
}
if (v3Global.dpi()) {
puts("\n// DPI TYPES for DPI Export callbacks (Internal use)\n");
std::set<std::string> types; // Remove duplicates and sort
for (const auto& itpair : m_scopeFuncs) {
const AstCFunc* const funcp = itpair.second.m_cfuncp;
if (!funcp->dpiExportImpl()) continue;
const std::string cbtype
= protect(v3Global.opt.prefix() + "__Vcb_" + funcp->cname() + "_t");
const std::string functype = funcp->rtnTypeVoid() + " (*) (" + cFuncArgs(funcp) + ")";
types.emplace("using " + cbtype + " = " + functype + ";\n");
}
for (const std::string& type : types) puts(type);
}
puts("\n// SYMS CLASS (contains all model state)\n");
puts("class alignas(VL_CACHE_LINE_BYTES) " + symClassName()
+ " final : public VerilatedSyms {\n");
ofp()->putsPrivate(false); // public:
puts("// INTERNAL STATE\n");
puts(topClassName() + "* const __Vm_modelp;\n");
if (v3Global.needTraceDumper()) {
// __Vm_dumperp is local, otherwise we wouldn't know what design's eval()
// should call a global dumpperp
puts("bool __Vm_dumping = false; // Dumping is active\n");
puts("VerilatedMutex __Vm_dumperMutex; // Protect __Vm_dumperp\n");
puts(v3Global.opt.traceClassLang()
+ "* __Vm_dumperp VL_GUARDED_BY(__Vm_dumperMutex) = nullptr;"
" /// Trace class for $dump*\n");
}
if (v3Global.opt.trace()) {
puts("bool __Vm_activity = false;"
" ///< Used by trace routines to determine change occurred\n");
puts("uint32_t __Vm_baseCode = 0;"
" ///< Used by trace routines when tracing multiple models\n");
}
if (v3Global.hasEvents()) {
if (v3Global.assignsEvents()) {
puts("std::vector<VlAssignableEvent> __Vm_triggeredEvents;\n");
} else {
puts("std::vector<VlEvent*> __Vm_triggeredEvents;\n");
}
}
if (v3Global.hasClasses()) puts("VlDeleter __Vm_deleter;\n");
puts("bool& __Vm_didInit;\n");
if (v3Global.opt.mtasks()) {
puts("\n// MULTI-THREADING\n");
puts("VlThreadPool* __Vm_threadPoolp;\n");
puts("bool __Vm_even_cycle__ico = false;\n");
puts("bool __Vm_even_cycle__act = false;\n");
puts("bool __Vm_even_cycle__nba = false;\n");
}
if (v3Global.opt.profExec()) {
puts("\n// EXECUTION PROFILING\n");
puts("VlExecutionProfiler* const __Vm_executionProfilerp;\n");
}
if (v3Global.opt.profPgo()) {
puts("\n// PGO PROFILING\n");
puts("VlPgoProfiler<" + std::to_string(ExecMTask::numUsedIds()) + "> _vm_pgoProfiler;\n");
}
puts("\n// MODULE INSTANCE STATE\n");
for (const ScopeModPair& itpair : m_scopes) {
const AstScope* const scopep = itpair.first;
const AstNodeModule* const modp = itpair.second;
if (VN_IS(modp, Class)) continue;
const std::string name = EmitCUtil::prefixNameProtect(modp);
ofp()->printf("%-30s ", name.c_str());
putns(scopep, VIdProtect::protectIf(scopep->nameDotless(), scopep->protect()) + ";\n");
}
if (m_coverBins) {
puts("\n// COVERAGE\n");
puts(v3Global.opt.threads() > 1 ? "std::atomic<uint32_t>" : "uint32_t");
puts(" __Vcoverage[");
puts(std::to_string(m_coverBins));
puts("];\n");
}
if (!m_scopeNames.empty()) { // Scope names
puts("\n// SCOPE NAMES\n");
for (const auto& itpair : m_scopeNames) {
const ScopeData& sd = itpair.second;
putns(sd.m_nodep, "VerilatedScope* " + protect("__Vscopep_" + sd.m_symName) + ";\n");
}
}
if (v3Global.opt.vpi()) {
puts("\n// SCOPE HIERARCHY\n");
puts("VerilatedHierarchy __Vhier;\n");
}
puts("\n// CONSTRUCTORS\n");
puts(symClassName() + "(VerilatedContext* contextp, const char* namep, " + topClassName()
+ "* modelp);\n");
puts("~" + symClassName() + "();\n");
for (const std::string& funcName : m_splitFuncNames) { puts("void " + funcName + "();\n"); }
puts("\n// METHODS\n");
puts("const char* name() const { return TOP.vlNamep; }\n");
if (v3Global.hasEvents()) {
if (v3Global.assignsEvents()) {
puts("void fireEvent(VlAssignableEvent& event) {\n");
} else {
puts("void fireEvent(VlEvent& event) {\n");
}
puts("if (VL_LIKELY(!event.isTriggered())) {\n");
if (v3Global.assignsEvents()) {
puts("__Vm_triggeredEvents.push_back(event);\n");
} else {
puts("__Vm_triggeredEvents.push_back(&event);\n");
}
puts("}\n");
puts("event.fire();\n");
puts("}\n");
puts("void clearTriggeredEvents() {\n");
if (v3Global.assignsEvents()) {
puts("for (auto& event : __Vm_triggeredEvents) event.clearTriggered();\n");
} else {
puts("for (const auto eventp : __Vm_triggeredEvents) eventp->clearTriggered();\n");
}
puts("__Vm_triggeredEvents.clear();\n");
puts("}\n");
}
if (v3Global.needTraceDumper()) {
if (!optSystemC()) puts("void _traceDump();\n");
puts("void _traceDumpOpen();\n");
puts("void _traceDumpClose();\n");
}
if (v3Global.opt.savable()) {
puts("void " + protect("__Vserialize") + "(VerilatedSerialize& os);\n");
puts("void " + protect("__Vdeserialize") + "(VerilatedDeserialize& os);\n");
}
puts("};\n");
ofp()->putsEndGuard();
closeOutputFile();
}
void EmitCSyms::emitSymImpPreamble() {
puts("\n");
// Includes
puts("#include \"" + EmitCUtil::pchClassName() + ".h\"\n");
puts("\n");
// Declarations for DPI Export implementation functions
bool needsNewLine = false;
for (const auto& itpair : m_scopeFuncs) {
const AstCFunc* const funcp = itpair.second.m_cfuncp;
if (!funcp->dpiExportImpl()) continue;
emitCFuncDecl(funcp, itpair.second.m_modp);
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) {
if (!v3Global.opt.vpi()) return;
if (destroy) {
stmts.emplace_back("// Tear down scope hierarchy");
} else {
stmts.emplace_back("// Set up scope hierarchy");
}
const std::string method = destroy ? "remove" : "add";
for (const auto& itpair : m_scopeNames) {
if (itpair.first == "TOP") continue;
const ScopeData& sd = itpair.second;
const std::string& name = sd.m_prettyName;
const std::string& scopeType = sd.m_type;
if (name.find('.') != string::npos) continue;
if (scopeType != "SCOPE_MODULE" && scopeType != "SCOPE_PACKAGE") continue;
const std::string id = protect("__Vscopep_" + sd.m_symName);
stmts.emplace_back("__Vhier." + method + "(0, " + id + ");");
}
for (const auto& itpair : m_vpiScopeHierarchy) {
const std::string fromName = scopeSymString(itpair.first);
const std::string fromId = protect("__Vscopep_" + m_scopeNames.at(fromName).m_symName);
for (const std::string& name : itpair.second) {
const std::string toName = scopeSymString(name);
const std::string toId = protect("__Vscopep_" + m_scopeNames.at(toName).m_symName);
stmts.emplace_back("__Vhier." + method + "(" + fromId + ", " + toId + ");");
}
}
if (destroy) {
stmts.emplace_back("// Clear keys from hierarchy map after values have been removed");
stmts.emplace_back("__Vhier.clear();");
}
}
std::vector<std::string> EmitCSyms::getSymCtorStmts() {
std::vector<std::string> stmts;
const auto add = [&stmts](const std::string& stmt) { stmts.emplace_back(stmt); };
{
uint64_t stackSize = V3StackCount::count(v3Global.rootp());
if (v3Global.opt.debugStackCheck()) stackSize += 1024 * 1024 * 1024;
V3Stats::addStat("Size prediction, Stack (bytes)", stackSize);
// TODO: 'm_statVarScopeBytes' is always 0, AstVarScope doesn't reach here (V3Descope)
V3Stats::addStat("Size prediction, Heap, from Var Scopes (bytes)", m_statVarScopeBytes);
V3Stats::addStat(V3Stats::STAT_MODEL_SIZE, stackSize + m_statVarScopeBytes);
add("// Check resources");
add("Verilated::stackCheck(" + std::to_string(stackSize) + ");");
}
add("// Setup sub module instances");
for (const ScopeModPair& itpair : m_scopes) {
const AstScope* const scopep = itpair.first;
const AstNodeModule* const modp = itpair.second;
if (modp->isTop()) continue;
const std::string name = V3OutFormatter::quoteNameControls(
VIdProtect::protectWordsIf(scopep->prettyName(), scopep->protect()));
add(protect(scopep->nameDotless()) + ".ctor(this, \"" + name + "\");");
}
if (v3Global.opt.profPgo()) {
add("// Configure profiling for PGO\n");
if (!v3Global.opt.hierChild()) {
add("_vm_pgoProfiler.writeHeader(_vm_contextp__->profVltFilename());");
}
if (v3Global.opt.mtasks()) {
v3Global.rootp()->topModulep()->foreach([&](const AstExecGraph* execGraphp) {
for (const V3GraphVertex& vtx : execGraphp->depGraphp()->vertices()) {
const ExecMTask& mt = static_cast<const ExecMTask&>(vtx);
add("_vm_pgoProfiler.addCounter(" + std::to_string(mt.id()) + ", \""
+ mt.hashName() + "\");");
}
});
}
}
add("// Configure time unit / time precision");
if (!v3Global.rootp()->timeunit().isNone()) {
const std::string unit = std::to_string(v3Global.rootp()->timeunit().powerOfTen());
add("_vm_contextp__->timeunit(" + unit + ");");
}
if (!v3Global.rootp()->timeprecision().isNone()) {
const std::string prec = std::to_string(v3Global.rootp()->timeprecision().powerOfTen());
add("_vm_contextp__->timeprecision(" + prec + ");");
}
add("// Setup each module's pointers to their submodules");
for (const auto& i : m_scopes) {
const AstScope* const scopep = i.first;
const AstNodeModule* const modp = i.second;
const AstScope* const abovep = scopep->aboveScopep();
if (!abovep) continue;
const std::string protName = VIdProtect::protectWordsIf(scopep->name(), scopep->protect());
std::string stmt;
if (VN_IS(modp, ClassPackage)) {
// ClassPackage modules seem to be a bit out of place, so hard code...
stmt += "TOP";
} else {
stmt += VIdProtect::protectIf(abovep->nameDotless(), abovep->protect());
}
stmt += ".";
stmt += protName.substr(protName.rfind('.') + 1);
stmt += " = &";
stmt += VIdProtect::protectIf(scopep->nameDotless(), scopep->protect()) + ";";
add(stmt);
}
add("// Setup each module's pointer back to symbol table (for public functions)");
for (const ScopeModPair& i : m_scopes) {
const AstScope* const scopep = i.first;
AstNodeModule* const modp = i.second;
// first is used by AstCoverDecl's call to __vlCoverInsert
const bool first = !modp->user1();
modp->user1(true);
add(VIdProtect::protectIf(scopep->nameDotless(), scopep->protect()) + "."
+ 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 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);
if (v3Global.dpi()) {
for (const std::string vfinal : {"0", "1"}) {
add("// Setup export functions - final: " + vfinal);
for (const auto& itpair : m_scopeFuncs) {
const ScopeFuncData& sfd = itpair.second;
const AstScopeName* const scopep = sfd.m_scopep;
const AstCFunc* const funcp = sfd.m_cfuncp;
const AstNodeModule* const modp = sfd.m_modp;
if (!funcp->dpiExportImpl()) continue;
std::string stmt;
stmt += protect("__Vscopep_" + scopep->scopeSymName()) + "->exportInsert(";
stmt += vfinal + ", \"";
// Not protected - user asked for import/export
stmt += V3OutFormatter::quoteNameControls(funcp->cname());
stmt += "\", (void*)(&";
stmt += EmitCUtil::prefixNameProtect(modp);
stmt += "__";
stmt += funcp->nameProtect();
stmt += "));";
add(stmt);
}
}
}
// 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");
// Keyed by '\0'-joined row text so identical rows share one table.
std::unordered_map<std::string, std::string> tableByRows;
int tableCounter = 0;
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;
}
std::string row;
const TableEntryKind kind
= tryBuildTableEntry(svd, varp, scopep, modClassName, dims, row);
switch (kind) {
case TableEntryKind::TABLE_ROW: rows.emplace_back(row); break;
case TableEntryKind::FORCEABLE_RESIDUAL: {
const std::string bounds = boundsString(dims);
residual.emplace_back(insertForceableVarStatement(svd, scopep, varp, dims.udim,
dims.pdim, bounds)
+ ";");
break;
}
case TableEntryKind::PLAIN_RESIDUAL: {
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());
}
break;
}
default: v3fatalSrc("Bad case");
}
}
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;
}
std::vector<std::string> EmitCSyms::getSymDtorStmts() {
std::vector<std::string> stmts;
const auto add = [&stmts](const std::string& stmt) { stmts.emplace_back(stmt); };
emitScopeHier(stmts, true);
if (v3Global.needTraceDumper()) add("if (__Vm_dumping) _traceDumpClose();");
if (v3Global.opt.profPgo()) {
add("_vm_pgoProfiler.write(\"" + topClassName()
+ "\", _vm_contextp__->profVltFilename());");
}
add("// Tear down scopes");
for (const auto& itpair : m_scopeNames) {
const ScopeData& sd = itpair.second;
const std::string id = protect("__Vscopep_" + sd.m_symName);
add("VL_DO_CLEAR(delete " + id + ", " + id + " = nullptr);");
}
add("// Tear down sub module instances");
for (const ScopeModPair& itpair : vlstd::reverse_view(m_scopes)) {
const AstScope* const scopep = itpair.first;
const AstNodeModule* const modp = itpair.second;
if (modp->isTop()) continue;
add(protect(scopep->nameDotless()) + ".dtor();");
}
return stmts;
}
void EmitCSyms::emitSplit(std::vector<std::string>& stmts, const std::string& name,
size_t maxCost) {
size_t nSubFunctions = 0;
// Reduce into a balanced tree of sub-function calls until we end up with a single statement
while (stmts.size() > 1) {
size_t nSplits = 0;
const size_t nStmts = stmts.size();
for (size_t splitStart = 0, splitEnd = 0; splitStart < nStmts; splitStart = splitEnd) {
// Gather up at at most 'maxCost' worth of statements in this split,
// but always at least 2 (if less than 2, the reduction makes no
// progress and the loop will not terminate).
size_t cost = 0;
while (((cost < maxCost) || (splitEnd - splitStart < 2)) && splitEnd < nStmts) {
cost += stmtCost(stmts[splitEnd++]);
}
UASSERT(splitStart < splitEnd, "Empty split");
// Create new sub-function and emit current range of statementss
const std::string nStr = std::to_string(nSubFunctions++);
// Name of sub-function we are emitting now
const std::string funcName = symClassName() + "__" + name + "__" + nStr;
m_splitFuncNames.emplace_back(funcName);
// Open split file
openNewOutputSourceFile(funcName, true, true, "Symbol table implementation internals");
// Emit header
emitSymImpPreamble();
// Open sub-function definition in the split file
puts("void " + symClassName() + "::" + funcName + "() {\n");
// Emit statements
for (size_t j = splitStart; j < splitEnd; ++j) {
ofp()->putsNoTracking(" ");
ofp()->putsNoTracking(stmts[j]);
ofp()->putsNoTracking("\n");
}
// Close sub-function
puts("}\n");
// Close split file
closeOutputFile();
// Replace statements with a call to the sub-function
stmts[nSplits++] = funcName + "();";
}
// The statements at the front are now the calls to the sub-functions, drop the rest
stmts.resize(nSplits);
}
}
void EmitCSyms::emitSymImp(const AstNetlist* netlistp) {
UINFO(6, __FUNCTION__ << ": ");
// Get the body of the constructor and destructor
std::vector<std::string> ctorStmts = getSymCtorStmts();
std::vector<std::string> dtorStmts = getSymDtorStmts();
// Check if needs splitting and if so split into sub-functions
if (const size_t maxCost = static_cast<size_t>(v3Global.opt.outputSplitCFuncs())) {
size_t totalCost = 200; // Starting from 200 to consider all other contents in main file
if (totalCost <= maxCost) {
for (const std::string& stmt : ctorStmts) {
totalCost += stmtCost(stmt);
if (totalCost > maxCost) break;
}
}
if (totalCost <= maxCost) {
for (const std::string& stmt : dtorStmts) {
totalCost += stmtCost(stmt);
if (totalCost > maxCost) break;
}
}
// Split them if needed
if (totalCost > maxCost) {
v3Global.useParallelBuild(true); // Splitting files, so using parallel build.
emitSplit(ctorStmts, "ctor", maxCost);
emitSplit(dtorStmts, "dtor", maxCost);
}
}
openNewOutputSourceFile(symClassName(), true, true, "Symbol table implementation internals");
emitSymImpPreamble();
emitVarTables();
// Constructor
const std::string ctorArgs
= "VerilatedContext* contextp, const char* namep, " + topClassName() + "* modelp";
puts(symClassName() + "::" + symClassName() + "(" + ctorArgs + ")\n");
puts(" : VerilatedSyms{contextp}\n");
puts(" // Setup internal state of the Syms class\n");
puts(" , __Vm_modelp{modelp}\n");
puts(" , __Vm_didInit{modelp->m_didInit}\n");
if (v3Global.opt.mtasks()) {
puts(" , __Vm_threadPoolp{static_cast<VlThreadPool*>(contextp->threadPoolp())}\n");
}
if (v3Global.opt.profExec()) {
puts(" , __Vm_executionProfilerp{static_cast<VlExecutionProfiler*>(contextp->"
"enableExecutionProfiler(&VlExecutionProfiler::construct))}\n");
}
if (v3Global.opt.profPgo() && !v3Global.opt.libCreate().empty()) {
puts(" , _vm_pgoProfiler{" + std::to_string(v3Global.currentHierBlockCost()) + "}\n");
}
{
const AstScope* const scopep = netlistp->topScopep()->scopep();
puts(" // Setup top module instance\n");
puts(" , " + protect(scopep->nameDotless()) + "{this, namep}\n");
}
puts("{\n");
for (const std::string& stmt : ctorStmts) {
ofp()->putsNoTracking(" ");
ofp()->putsNoTracking(stmt);
ofp()->putsNoTracking("\n");
}
puts("}\n");
// Destructor
puts("\n" + symClassName() + "::~" + symClassName() + "() {\n");
for (const std::string& stmt : dtorStmts) {
ofp()->putsNoTracking(" ");
ofp()->putsNoTracking(stmt);
ofp()->putsNoTracking("\n");
}
puts("}\n");
// Methods
if (v3Global.needTraceDumper()) {
if (!optSystemC()) {
puts("\nvoid " + symClassName() + "::_traceDump() {\n");
puts("const VerilatedLockGuard lock{__Vm_dumperMutex};\n");
// Caller checked for __Vm_dumperp non-nullptr
puts("__Vm_dumperp->dump(VL_TIME_Q());\n");
puts("}\n");
}
puts("\nvoid " + symClassName() + "::_traceDumpOpen() {\n");
puts("const VerilatedLockGuard lock{__Vm_dumperMutex};\n");
puts("if (VL_UNLIKELY(!__Vm_dumperp)) {\n");
puts("__Vm_dumperp = new " + v3Global.opt.traceClassLang() + "();\n");
puts("__Vm_modelp->trace(__Vm_dumperp, 0, 0);\n");
puts("const std::string dumpfile = _vm_contextp__->dumpfileCheck();\n");
puts("__Vm_dumperp->open(dumpfile.c_str());\n");
puts("__Vm_dumping = true;\n");
puts("}\n");
puts("}\n");
puts("\nvoid " + symClassName() + "::_traceDumpClose() {\n");
puts("const VerilatedLockGuard lock{__Vm_dumperMutex};\n");
puts("__Vm_dumping = false;\n");
puts("VL_DO_CLEAR(delete __Vm_dumperp, __Vm_dumperp = nullptr);\n");
puts("}\n");
}
if (v3Global.opt.savable()) {
for (const bool& de : {false, true}) {
const std::string classname = de ? "VerilatedDeserialize" : "VerilatedSerialize";
const std::string funcname = protect(de ? "__Vdeserialize" : "__Vserialize");
const std::string op = de ? ">>" : "<<";
puts("\nvoid " + symClassName() + "::" + funcname + "(" + classname + "& os) {\n");
puts("// Internal state\n");
if (v3Global.opt.trace()) puts("os" + op + "__Vm_activity;\n");
puts("os " + op + " __Vm_didInit;\n");
puts("// Module instance state\n");
for (const ScopeModPair& itpair : m_scopes) {
const AstScope* const scopep = itpair.first;
const std::string scopeName
= VIdProtect::protectIf(scopep->nameDotless(), scopep->protect());
puts(scopeName + "." + funcname + "(os);\n");
}
puts("}\n");
}
}
closeOutputFile();
}
//######################################################################
void EmitCSyms::emitDpiHdr() {
UINFO(6, __FUNCTION__ << ": ");
openNewOutputHeaderFile(topClassName() + "__Dpi",
"Prototypes for DPI import and export functions.");
puts("//\n");
puts("// Verilator includes this file in all generated .cpp files that use DPI functions.\n");
puts("// Manually include this file where DPI .c import functions are declared to ensure\n");
puts("// the C functions match the expectations of the DPI imports.\n");
ofp()->putsGuard();
puts("\n");
puts("#include \"svdpi.h\"\n");
puts("\n");
puts("#ifdef __cplusplus\n");
puts("extern \"C\" {\n");
puts("#endif\n");
puts("\n");
int firstExp = 0;
int firstImp = 0;
for (const AstCFunc* const nodep : m_dpis) {
if (!nodep->dpiExportDispatcher() && !nodep->dpiImportPrototype()) continue;
const std::string sourceLoc = VIdProtect::ifNoProtect(" at " + nodep->fileline()->ascii());
if (nodep->dpiExportDispatcher()) {
if (!firstExp++) puts("\n// DPI EXPORTS\n");
putsDecoration(nodep, "// DPI export" + sourceLoc + "\n");
} else {
if (!firstImp++) puts("\n// DPI IMPORTS\n");
putsDecoration(nodep, "// DPI import" + sourceLoc + "\n");
}
if (nodep->dpiCDeclOverride()) {
putns(nodep, "extern " + nodep->dpiCDecl() + ";\n");
} else {
putns(nodep, "extern " + nodep->rtnTypeVoid() + " " + nodep->nameProtect() + "("
+ cFuncArgs(nodep) + ");\n");
}
}
puts("\n");
puts("#ifdef __cplusplus\n");
puts("}\n");
puts("#endif\n");
ofp()->putsEndGuard();
closeOutputFile();
}
//######################################################################
void EmitCSyms::emitDpiImp() {
UINFO(6, __FUNCTION__ << ": ");
openNewOutputSourceFile(topClassName() + "__Dpi", false, true,
"Implementation of DPI export functions");
puts("//\n");
puts("// Verilator compiles this file in when DPI functions are used.\n");
puts("// If you have multiple Verilated designs with the same DPI exported\n");
puts("// function names, you will get multiple definition link errors from here.\n");
puts("// This is an unfortunate result of the DPI specification.\n");
puts("// To solve this, either\n");
puts("// 1. Call " + topClassName() + "::{export_function} instead,\n");
puts("// and do not even bother to compile this file\n");
puts("// or 2. Compile all __Dpi.cpp files in the same compiler run,\n");
puts("// and #ifdefs already inserted here will sort everything out.\n");
puts("\n");
puts("#include \"" + topClassName() + "__Dpi.h\"\n");
puts("#include \"" + topClassName() + ".h\"\n");
puts("\n");
for (const AstCFunc* const nodep : m_dpis) {
if (!nodep->dpiExportDispatcher()) continue;
const std::string name = nodep->name();
const std::string sourceLoc = VIdProtect::ifNoProtect(" at " + nodep->fileline()->ascii());
// Prevent multi-definition if used by multiple models
puts("#ifndef VL_DPIDECL_" + name + "_\n");
puts("#define VL_DPIDECL_" + name + "_\n");
putns(nodep, nodep->rtnTypeVoid() + " " + name + "(" + cFuncArgs(nodep) + ") {\n");
puts("// DPI export" + sourceLoc + "\n");
putns(nodep, "return " + topClassName() + "::" + name + "(");
std::string comma;
for (const AstNode* stmtp = nodep->argsp(); stmtp; stmtp = stmtp->nextp()) {
if (const AstVar* const portp = VN_CAST(stmtp, Var)) {
if (portp->isIO() && !portp->isFuncReturn()) {
puts(comma);
comma = ", ";
putns(portp, portp->name());
}
}
}
puts(");\n");
puts("}\n");
puts("#endif\n");
puts("\n");
}
closeOutputFile();
}
//######################################################################
// EmitC class functions
void V3EmitC::emitcSyms(bool dpiHdrOnly) {
UINFO(2, __FUNCTION__ << ":");
EmitCSyms{v3Global.rootp(), dpiHdrOnly};
}