diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in index 47288dec6..f5ccd3131 100644 --- a/src/Makefile_obj.in +++ b/src/Makefile_obj.in @@ -254,6 +254,7 @@ RAW_OBJS = \ V3Undriven.o \ V3Unknown.o \ V3Unroll.o \ + V3VariableOrder.o \ V3Waiver.o \ V3Width.o \ V3WidthSel.o \ diff --git a/src/V3AstUserAllocator.h b/src/V3AstUserAllocator.h index f00e35550..637b6d2aa 100644 --- a/src/V3AstUserAllocator.h +++ b/src/V3AstUserAllocator.h @@ -33,7 +33,7 @@ template class AstUserAllocatorBase VL private: std::vector m_allocated; - inline T_Data* getUserp(T_Node* nodep) const { + inline T_Data* getUserp(const T_Node* nodep) const { // This simplifies statically as T_UserN is constant. In C++17, use 'if constexpr'. if (T_UserN == 1) { const VNUser user = nodep->user1u(); @@ -100,6 +100,13 @@ public: } return *userp; } + + // Get a reference to the user data + T_Data& operator()(const T_Node* nodep) { + T_Data* userp = getUserp(nodep); + UASSERT_OBJ(userp, nodep, "Missing User data on const AstNode"); + return *userp; + } }; // User pointer allocator classes. T_Node is the type of node the allocator should be applied to diff --git a/src/V3EmitC.cpp b/src/V3EmitC.cpp index 4e7f7cdae..3d573409d 100644 --- a/src/V3EmitC.cpp +++ b/src/V3EmitC.cpp @@ -123,7 +123,7 @@ class EmitCImp final : EmitCFunc { } } } - void emitParams(AstNodeModule* modp, bool init, bool* firstp, string& sectionr) { + void emitParams(AstNodeModule* modp, bool init, string& sectionr) { bool anyi = false; for (AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) { if (const AstVar* varp = VN_CAST(nodep, Var)) { @@ -207,6 +207,99 @@ class EmitCImp final : EmitCFunc { emitCFuncDecl(funcp, modp); } } + void emitVarDecls(const AstNodeModule* modp) { + // Output a list of variable declarations + + std::vector varList; + bool lastAnon = false; // initial value is not important, but is used + + const auto emitCurrentList = [this, &varList, &lastAnon]() { + if (varList.empty()) return; + + if (lastAnon) { // Output as anons + const int anonMembers = varList.size(); + const int lim = v3Global.opt.compLimitMembers(); + int anonL3s = 1; + int anonL2s = 1; + int anonL1s = 1; + if (anonMembers > (lim * lim * lim)) { + anonL3s = (anonMembers + (lim * lim * lim) - 1) / (lim * lim * lim); + anonL2s = lim; + anonL1s = lim; + } else if (anonMembers > (lim * lim)) { + anonL2s = (anonMembers + (lim * lim) - 1) / (lim * lim); + anonL1s = lim; + } else if (anonMembers > lim) { + anonL1s = (anonMembers + lim - 1) / lim; + } + if (anonL1s != 1) + puts("// Anonymous structures to workaround compiler member-count bugs\n"); + auto it = varList.cbegin(); + for (int l3 = 0; l3 < anonL3s && it != varList.cend(); ++l3) { + if (anonL3s != 1) puts("struct {\n"); + for (int l2 = 0; l2 < anonL2s && it != varList.cend(); ++l2) { + if (anonL2s != 1) puts("struct {\n"); + for (int l1 = 0; l1 < anonL1s && it != varList.cend(); ++l1) { + if (anonL1s != 1) puts("struct {\n"); + for (int l0 = 0; l0 < lim && it != varList.cend(); ++l0) { + emitVarDecl(*it); + ++it; + } + if (anonL1s != 1) puts("};\n"); + } + if (anonL2s != 1) puts("};\n"); + } + if (anonL3s != 1) puts("};\n"); + } + // Leftovers, just in case off by one error somewhere above + for (; it != varList.cend(); ++it) emitVarDecl(*it); + } else { // Output as nonanons + for (const auto& pair : varList) emitVarDecl(pair); + } + + varList.clear(); + }; + + // Emit variables in consecutive anon and non-anon batches + for (const AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) { + if (const AstVar* const varp = VN_CAST_CONST(nodep, Var)) { + if (varp->isIO() || varp->isSignal() || varp->isClassMember() || varp->isTemp() + || (varp->isParam() && !VN_IS(varp->valuep(), Const))) { + const bool anon = isAnonOk(varp); + if (anon != lastAnon) emitCurrentList(); + lastAnon = anon; + varList.emplace_back(varp); + } + } + } + + // Emit final batch + emitCurrentList(); + } + void emitVarCtors(const AstNodeModule* modp) { + ofp()->indentInc(); + for (const AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) { + if (const AstVar* const varp = VN_CAST_CONST(nodep, Var)) { + const AstBasicDType* const dtypep = VN_CAST(varp->dtypeSkipRefp(), BasicDType); + if (!dtypep) continue; + if (varp->isIO() && varp->isSc()) { + puts(", "); + puts(varp->nameProtect()); + puts("("); + putsQuoted(varp->nameProtect()); + puts(")\n"); + } + if (dtypep->keyword().isMTaskState()) { + puts(", "); + puts(varp->nameProtect()); + puts("("); + iterate(varp->valuep()); + puts(")\n"); + } + } + } + ofp()->indentDec(); + } // Medium level void emitCtorImp(AstNodeModule* modp); @@ -248,9 +341,8 @@ void EmitCImp::emitCoverageDecl(AstNodeModule*) { void EmitCImp::emitCtorImp(AstNodeModule* modp) { puts("\n"); - bool first = true; string section; - emitParams(modp, true, &first, section /*ref*/); + emitParams(modp, true, section); const string modName = prefixNameProtect(modp); @@ -264,9 +356,8 @@ void EmitCImp::emitCtorImp(AstNodeModule* modp) { } else { puts(modName + "::" + modName + "(const char* _vcname__)\n"); puts(" : VerilatedModule(_vcname__)\n"); - first = false; // printed the first ':' } - emitVarCtors(&first); + emitVarCtors(modp); puts(" {\n"); @@ -500,15 +591,8 @@ void EmitCImp::emitInt(AstNodeModule* modp) { emitTypedefs(modp->stmtsp()); - string section; - section = "\n// PORTS\n"; - emitVarList(modp->stmtsp(), EVL_CLASS_IO, "", section /*ref*/); - - section = "\n// LOCAL SIGNALS\n"; - emitVarList(modp->stmtsp(), EVL_CLASS_SIG, "", section /*ref*/); - - section = "\n// LOCAL VARIABLES\n"; - emitVarList(modp->stmtsp(), EVL_CLASS_TEMP, "", section /*ref*/); + puts("\n// DESIGN SPECIFIC STATE\n"); + emitVarDecls(modp); puts("\n// INTERNAL VARIABLES\n"); if (!VN_IS(modp, Class)) { // Avoid clang unused error (& don't want in every object) @@ -518,12 +602,10 @@ void EmitCImp::emitInt(AstNodeModule* modp) { ofp()->putsPrivate(false); // public: emitCoverageDecl(modp); // may flip public/private - section = "\n// PARAMETERS\n"; - ofp()->putsPrivate(false); // public: - emitVarList(modp->stmtsp(), EVL_CLASS_PAR, "", - section /*ref*/); // Only those that are non-CONST - bool first = true; - emitParams(modp, false, &first, section /*ref*/); + { + string section = "\n// PARAMETERS\n"; + emitParams(modp, false, section); + } if (!VN_IS(modp, Class)) { puts("\n// CONSTRUCTORS\n"); @@ -588,8 +670,16 @@ void EmitCImp::emitImpTop() { void EmitCImp::emitImp(AstNodeModule* modp) { puts("\n//==========\n"); if (m_slow) { - string section; - emitVarList(modp->stmtsp(), EVL_CLASS_ALL, prefixNameProtect(modp), section /*ref*/); + // Emit static variable definitions + const string prefix = prefixNameProtect(modp); + for (const AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) { + if (const AstVar* const varp = VN_CAST_CONST(nodep, Var)) { + if (varp->isStatic()) { + puts(varp->vlArgType(true, false, false, prefix)); + puts(";\n"); + } + } + } if (!VN_IS(modp, Class)) emitCtorImp(modp); if (!VN_IS(modp, Class)) emitConfigureImp(modp); if (!VN_IS(modp, Class)) emitDestructorImp(modp); diff --git a/src/V3EmitCBase.cpp b/src/V3EmitCBase.cpp index f955f99dc..39bc76b0f 100644 --- a/src/V3EmitCBase.cpp +++ b/src/V3EmitCBase.cpp @@ -114,7 +114,7 @@ void EmitCBaseVisitor::emitCFuncDecl(const AstCFunc* funcp, const AstNodeModule* if (!funcp->ifdef().empty()) puts("#endif // " + funcp->ifdef() + "\n"); } -void EmitCBaseVisitor::emitVarDecl(const AstVar* nodep, const string& prefixIfImp, bool asRef) { +void EmitCBaseVisitor::emitVarDecl(const AstVar* nodep, bool asRef) { const AstBasicDType* const basicp = nodep->basicp(); bool refNeedParens = VN_IS(nodep->dtypeSkipRefp(), UnpackArrayDType); @@ -199,7 +199,7 @@ void EmitCBaseVisitor::emitVarDecl(const AstVar* nodep, const string& prefixIfIm && name.substr(name.size() - suffix.size()) == suffix; if (beStatic) puts("static VL_THREAD_LOCAL "); } - puts(nodep->vlArgType(true, false, false, prefixIfImp, asRef)); + puts(nodep->vlArgType(true, false, false, "", asRef)); puts(";\n"); } } diff --git a/src/V3EmitCBase.h b/src/V3EmitCBase.h index b9362991c..043214a53 100644 --- a/src/V3EmitCBase.h +++ b/src/V3EmitCBase.h @@ -75,11 +75,18 @@ public: return modp == v3Global.rootp()->constPoolp()->modp(); } + static bool isAnonOk(const AstVar* varp) { + return v3Global.opt.compLimitMembers() != 0 // Enabled + && !varp->isStatic() // Not a static variable + && !varp->isSc() // Aggregates can't be anon + && (varp->basicp() && !varp->basicp()->isOpaque()); // Aggregates can't be anon + } + static AstCFile* newCFile(const string& filename, bool slow, bool source); string cFuncArgs(const AstCFunc* nodep); void emitCFuncHeader(const AstCFunc* funcp, const AstNodeModule* modp, bool withScope); void emitCFuncDecl(const AstCFunc* funcp, const AstNodeModule* modp, bool cLinkage = false); - void emitVarDecl(const AstVar* nodep, const string& prefixIfImp, bool asRef = false); + void emitVarDecl(const AstVar* nodep, bool asRef = false); void emitModCUse(AstNodeModule* modp, VUseType useType); // CONSTRUCTORS diff --git a/src/V3EmitCFunc.cpp b/src/V3EmitCFunc.cpp index 14a82ea82..e27f628a9 100644 --- a/src/V3EmitCFunc.cpp +++ b/src/V3EmitCFunc.cpp @@ -28,90 +28,9 @@ // We use a static char array in VL_VALUE_STRING constexpr int VL_VALUE_STRING_MAX_WIDTH = 8192; -//###################################################################### -// Establish mtask variable sort order in mtasks mode - -class EmitVarTspSorter final : public V3TSP::TspStateBase { -private: - // MEMBERS - const MTaskIdSet& m_mtaskIds; // Mtask we're ordering - static unsigned s_serialNext; // Unique ID to establish serial order - unsigned m_serial; // Serial ordering -public: - // CONSTRUCTORS - explicit EmitVarTspSorter(const MTaskIdSet& mtaskIds) - : m_mtaskIds(mtaskIds) { // Cannot be {} or GCC 4.8 false warning - m_serial = ++s_serialNext; // Cannot be ()/{} or GCC 4.8 false warning - } - virtual ~EmitVarTspSorter() = default; - // METHODS - virtual bool operator<(const TspStateBase& other) const override { - return operator<(dynamic_cast(other)); - } - bool operator<(const EmitVarTspSorter& other) const { return m_serial < other.m_serial; } - const MTaskIdSet& mtaskIds() const { return m_mtaskIds; } - virtual int cost(const TspStateBase* otherp) const override { - return cost(dynamic_cast(otherp)); - } - virtual int cost(const EmitVarTspSorter* otherp) const { - int cost = diffs(m_mtaskIds, otherp->m_mtaskIds); - cost += diffs(otherp->m_mtaskIds, m_mtaskIds); - return cost; - } - // Returns the number of elements in set_a that don't appear in set_b - static int diffs(const MTaskIdSet& set_a, const MTaskIdSet& set_b) { - int diffs = 0; - for (int i : set_a) { - if (set_b.find(i) == set_b.end()) ++diffs; - } - return diffs; - } -}; - -unsigned EmitVarTspSorter::s_serialNext = 0; - //###################################################################### // EmitCFunc -void EmitCFunc::emitCtorSep(bool* firstp) { - if (*firstp) { - puts(" : "); - *firstp = false; - } else { - puts(", "); - } - if (ofp()->exceededWidth()) puts("\n "); -} - -void EmitCFunc::emitVarCtors(bool* firstp) { - if (!m_ctorVarsVec.empty()) { - ofp()->indentInc(); - if (*firstp) puts("\n"); - for (const AstVar* varp : m_ctorVarsVec) { - const AstBasicDType* const dtypep = VN_CAST(varp->dtypeSkipRefp(), BasicDType); - if (!dtypep) { - puts("// Skipping array: "); - puts(varp->nameProtect()); - puts("\n"); - } else if (dtypep->keyword().isMTaskState()) { - emitCtorSep(firstp); - puts(varp->nameProtect()); - puts("("); - iterate(varp->valuep()); - puts(")"); - } else { - emitCtorSep(firstp); - puts(varp->nameProtect()); - puts("("); - putsQuoted(varp->nameProtect()); - puts(")"); - } - } - puts("\n"); - ofp()->indentDec(); - } -} - bool EmitCFunc::emitSimpleOk(AstNodeMath* nodep) { // Can we put out a simple (A + B) instead of VL_ADD_III(A,B)? if (nodep->emitSimpleOperator() == "") return false; @@ -514,192 +433,6 @@ void EmitCFunc::displayNode(AstNode* nodep, AstScopeName* scopenamep, const stri displayEmit(nodep, isScan); } -void EmitCFunc::emitVarList(AstNode* firstp, EisWhich which, const string& prefixIfImp, - string& sectionr) { - // Put out a list of signal declarations - // in order of 0:clocks, 1:vluint8, 2:vluint16, 4:vluint32, 5:vluint64, 6:wide, 7:arrays - // This aids cache packing and locality - // - // Largest->smallest reduces the number of pad variables. Also - // experimented with alternating between large->small and small->large - // on successive Mtask groups, but then when a new mtask gets added may - // cause a huge delta. - // - // TODO: Move this sort to an earlier visitor stage. - VarSortMap varAnonMap; - VarSortMap varNonanonMap; - - for (int isstatic = 1; isstatic >= 0; isstatic--) { - if (prefixIfImp != "" && !isstatic) continue; - for (AstNode* nodep = firstp; nodep; nodep = nodep->nextp()) { - if (const AstVar* varp = VN_CAST(nodep, Var)) { - bool doit = true; - switch (which) { - case EVL_CLASS_IO: doit = varp->isIO(); break; - case EVL_CLASS_SIG: - doit = ((varp->isSignal() || varp->isClassMember()) && !varp->isIO()); - break; - case EVL_CLASS_TEMP: doit = (varp->isTemp() && !varp->isIO()); break; - case EVL_CLASS_PAR: - doit = (varp->isParam() && !VN_IS(varp->valuep(), Const)); - break; - case EVL_CLASS_ALL: doit = true; break; - default: v3fatalSrc("Bad Case"); - } - if (varp->isStatic() ? !isstatic : isstatic) doit = false; - if (doit) { - const int sigbytes = varp->dtypeSkipRefp()->widthAlignBytes(); - int sortbytes = 9; - if (varp->isUsedClock() && varp->widthMin() == 1) { - sortbytes = 0; - } else if (VN_IS(varp->dtypeSkipRefp(), UnpackArrayDType)) { - sortbytes = 8; - } else if (varp->basicp() && varp->basicp()->isOpaque()) { - sortbytes = 7; - } else if (varp->isScBv() || varp->isScBigUint()) { - sortbytes = 6; - } else if (sigbytes == 8) { - sortbytes = 5; - } else if (sigbytes == 4) { - sortbytes = 4; - } else if (sigbytes == 2) { - sortbytes = 2; - } else if (sigbytes == 1) { - sortbytes = 1; - } - const bool anonOk - = (v3Global.opt.compLimitMembers() != 0 // Enabled - && !varp->isStatic() && !varp->isIO() // Confusing to user - && !varp->isSc() // Aggregates can't be anon - && (varp->basicp() - && !varp->basicp()->isOpaque()) // Aggregates can't be anon - ); - - if (anonOk) { - varAnonMap[sortbytes].push_back(varp); - } else { - varNonanonMap[sortbytes].push_back(varp); - } - } - } - } - } - - if (!varAnonMap.empty() || !varNonanonMap.empty()) { - if (!sectionr.empty()) { - puts(sectionr); - sectionr = ""; - } - VarVec anons; - VarVec nonanons; - emitVarSort(varAnonMap, &anons); - emitVarSort(varNonanonMap, &nonanons); - emitSortedVarList(anons, nonanons, prefixIfImp); - } -} - -void EmitCFunc::emitVarSort(const VarSortMap& vmap, VarVec* sortedp) { - UASSERT(sortedp->empty(), "Sorted should be initially empty"); - if (!v3Global.opt.mtasks()) { - // Plain old serial mode. Sort by size, from small to large, - // to optimize for both packing and small offsets in code. - for (const auto& itr : vmap) { - for (VarVec::const_iterator jt = itr.second.begin(); jt != itr.second.end(); ++jt) { - sortedp->push_back(*jt); - } - } - return; - } - - // MacroTask mode. Sort by MTask-affinity group first, size second. - using MTaskVarSortMap = std::map; - MTaskVarSortMap m2v; - for (VarSortMap::const_iterator it = vmap.begin(); it != vmap.end(); ++it) { - const int size_class = it->first; - const VarVec& vec = it->second; - for (const AstVar* varp : vec) { m2v[varp->mtaskIds()][size_class].push_back(varp); } - } - - // Create a TSP sort state for each MTaskIdSet footprint - V3TSP::StateVec states; - for (MTaskVarSortMap::iterator it = m2v.begin(); it != m2v.end(); ++it) { - states.push_back(new EmitVarTspSorter(it->first)); - } - - // Do the TSP sort - V3TSP::StateVec sorted_states; - V3TSP::tspSort(states, &sorted_states); - - for (V3TSP::StateVec::iterator it = sorted_states.begin(); it != sorted_states.end(); ++it) { - const EmitVarTspSorter* statep = dynamic_cast(*it); - const VarSortMap& localVmap = m2v[statep->mtaskIds()]; - // use rbegin/rend to sort size large->small - for (VarSortMap::const_reverse_iterator jt = localVmap.rbegin(); jt != localVmap.rend(); - ++jt) { - const VarVec& vec = jt->second; - for (VarVec::const_iterator kt = vec.begin(); kt != vec.end(); ++kt) { - sortedp->push_back(*kt); - } - } - VL_DO_DANGLING(delete statep, statep); - } -} - -void EmitCFunc::emitSortedVarList(const VarVec& anons, const VarVec& nonanons, - const string& prefixIfImp) { - string curVarCmt; - // Output anons - { - const int anonMembers = anons.size(); - const int lim = v3Global.opt.compLimitMembers(); - int anonL3s = 1; - int anonL2s = 1; - int anonL1s = 1; - if (anonMembers > (lim * lim * lim)) { - anonL3s = (anonMembers + (lim * lim * lim) - 1) / (lim * lim * lim); - anonL2s = lim; - anonL1s = lim; - } else if (anonMembers > (lim * lim)) { - anonL2s = (anonMembers + (lim * lim) - 1) / (lim * lim); - anonL1s = lim; - } else if (anonMembers > lim) { - anonL1s = (anonMembers + lim - 1) / lim; - } - if (anonL1s != 1) - puts("// Anonymous structures to workaround compiler member-count bugs\n"); - auto it = anons.cbegin(); - for (int l3 = 0; l3 < anonL3s && it != anons.cend(); ++l3) { - if (anonL3s != 1) puts("struct {\n"); - for (int l2 = 0; l2 < anonL2s && it != anons.cend(); ++l2) { - if (anonL2s != 1) puts("struct {\n"); - for (int l1 = 0; l1 < anonL1s && it != anons.cend(); ++l1) { - if (anonL1s != 1) puts("struct {\n"); - for (int l0 = 0; l0 < lim && it != anons.cend(); ++l0) { - const AstVar* varp = *it; - emitVarDecl(varp, prefixIfImp); - ++it; - } - if (anonL1s != 1) puts("};\n"); - } - if (anonL2s != 1) puts("};\n"); - } - if (anonL3s != 1) puts("};\n"); - } - // Leftovers, just in case off by one error somewhere above - for (; it != anons.end(); ++it) { - const AstVar* varp = *it; - emitVarDecl(varp, prefixIfImp); - } - } - // Output nonanons - for (const AstVar* varp : nonanons) { - if (varp->isIO() && varp->isSc()) { m_ctorVarsVec.push_back(varp); } - AstBasicDType* const basicp = varp->basicp(); - if (basicp && basicp->keyword().isMTaskState()) { m_ctorVarsVec.push_back(varp); } - emitVarDecl(varp, prefixIfImp); - } -} - void EmitCFunc::emitCCallArgs(AstNodeCCall* nodep) { bool comma = false; if (nodep->funcp()->isLoose() && !nodep->funcp()->isStatic()) { diff --git a/src/V3EmitCFunc.h b/src/V3EmitCFunc.h index 534739694..295aeee0a 100644 --- a/src/V3EmitCFunc.h +++ b/src/V3EmitCFunc.h @@ -114,12 +114,8 @@ public: class EmitCFunc VL_NOT_FINAL : public EmitCBaseVisitor { private: - using VarVec = std::vector; - using VarSortMap = std::map; // Map size class to VarVec - bool m_suppressSemi; AstVarRef* m_wideTempRefp; // Variable that _WW macros should be setting - VarVec m_ctorVarsVec; // All variables in constructor order int m_labelNum; // Next label number int m_splitSize; // # of cfunc nodes placed into output file int m_splitFilenum; // File number being created, 0 = primary @@ -155,18 +151,6 @@ public: void displayArg(AstNode* dispp, AstNode** elistp, bool isScan, const string& vfmt, bool ignore, char fmtLetter); - enum EisWhich : uint8_t { - EVL_CLASS_IO, - EVL_CLASS_SIG, - EVL_CLASS_TEMP, - EVL_CLASS_PAR, - EVL_CLASS_ALL - }; - void emitVarList(AstNode* firstp, EisWhich which, const string& prefixIfImp, string& sectionr); - static void emitVarSort(const VarSortMap& vmap, VarVec* sortedp); - void emitSortedVarList(const VarVec& anons, const VarVec& nonanons, const string& prefixIfImp); - void emitVarCtors(bool* firstp); - void emitCtorSep(bool* firstp); bool emitSimpleOk(AstNodeMath* nodep); void emitIQW(AstNode* nodep) { // Other abbrevs: "C"har, "S"hort, "F"loat, "D"ouble, stri"N"g @@ -242,7 +226,7 @@ public: for (AstNode* subnodep = nodep->argsp(); subnodep; subnodep = subnodep->nextp()) { if (AstVar* varp = VN_CAST(subnodep, Var)) { - if (varp->isFuncReturn()) emitVarDecl(varp, ""); + if (varp->isFuncReturn()) emitVarDecl(varp); } } @@ -271,7 +255,7 @@ public: virtual void visit(AstVar* nodep) override { UASSERT_OBJ(m_cfuncp, nodep, "Cannot emit non-local variable"); - emitVarDecl(nodep, ""); + emitVarDecl(nodep); } virtual void visit(AstNodeAssign* nodep) override { diff --git a/src/V3EmitCModel.cpp b/src/V3EmitCModel.cpp index 8796a9c4c..78565b568 100644 --- a/src/V3EmitCModel.cpp +++ b/src/V3EmitCModel.cpp @@ -90,7 +90,7 @@ class EmitCModel final : public EmitCFunc { for (const AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) { if (const AstVar* const varp = VN_CAST_CONST(nodep, Var)) { if (varp->isPrimaryIO()) { // - emitVarDecl(varp, "", /* asRef: */ true); + emitVarDecl(varp, /* asRef: */ true); } } } diff --git a/src/V3VariableOrder.cpp b/src/V3VariableOrder.cpp new file mode 100644 index 000000000..ea88cd98f --- /dev/null +++ b/src/V3VariableOrder.cpp @@ -0,0 +1,207 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Variable ordering +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// Copyright 2003-2021 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* +// V3VariableOrder's Transformations: +// +// Each module: +// Order module variables +// +//************************************************************************* + +#include "config_build.h" +#include "verilatedos.h" + +#include "V3Global.h" +#include "V3VariableOrder.h" +#include "V3Ast.h" +#include "V3AstUserAllocator.h" +#include "V3EmitCBase.h" +#include "V3TSP.h" + +#include +#include + +//###################################################################### +// Establish mtask variable sort order in mtasks mode + +class VarTspSorter final : public V3TSP::TspStateBase { +private: + // MEMBERS + const MTaskIdSet& m_mtaskIds; // Mtask we're ordering + static unsigned s_serialNext; // Unique ID to establish serial order + unsigned m_serial; // Serial ordering +public: + // CONSTRUCTORS + explicit VarTspSorter(const MTaskIdSet& mtaskIds) + : m_mtaskIds(mtaskIds) { // Cannot be {} or GCC 4.8 false warning + m_serial = ++s_serialNext; // Cannot be ()/{} or GCC 4.8 false warning + } + virtual ~VarTspSorter() = default; + // METHODS + virtual bool operator<(const TspStateBase& other) const override { + return operator<(dynamic_cast(other)); + } + bool operator<(const VarTspSorter& other) const { return m_serial < other.m_serial; } + const MTaskIdSet& mtaskIds() const { return m_mtaskIds; } + virtual int cost(const TspStateBase* otherp) const override { + return cost(dynamic_cast(otherp)); + } + virtual int cost(const VarTspSorter* otherp) const { + int cost = diffs(m_mtaskIds, otherp->m_mtaskIds); + cost += diffs(otherp->m_mtaskIds, m_mtaskIds); + return cost; + } + // Returns the number of elements in set_a that don't appear in set_b + static int diffs(const MTaskIdSet& set_a, const MTaskIdSet& set_b) { + int diffs = 0; + for (int i : set_a) { + if (set_b.find(i) == set_b.end()) ++diffs; + } + return diffs; + } +}; + +unsigned VarTspSorter::s_serialNext = 0; + +class VariableOrder final { + // NODE STATE + // AstVar::user1() -> attributes, via m_attributes + AstUser1InUse m_user1InUse; // AstVar + + struct VarAttributes { + uint32_t stratum; // Roughly equivalent to alignment requirement, to avoid padding + bool anonOk; // Can be emitted as part of anonymous structure + }; + + AstUser1Allocator m_attributes; // Attributes used for sorting + + //###################################################################### + + // Simple sort + void simpleSortVars(std::vector& varps) { + stable_sort(varps.begin(), varps.end(), + [this](const AstVar* ap, const AstVar* bp) -> bool { + if (ap->isStatic() != bp->isStatic()) { // Non-statics before statics + return bp->isStatic(); + } + const auto& attrA = m_attributes(ap); + const auto& attrB = m_attributes(bp); + if (attrA.anonOk != attrB.anonOk) { // Anons before non-anons + return attrA.anonOk; + } + return attrA.stratum < attrB.stratum; // Finally sort by stratum + }); + } + + // Sort by MTask-affinity first, then the same as simpleSortVars + void tspSortVars(std::vector& varps) { + // Map from "MTask affinity" -> "variable list" + std::map> m2v; + for (AstVar* const varp : varps) { m2v[varp->mtaskIds()].push_back(varp); } + + // Create a TSP sort state for each unique MTaskIdSet, except for the empty set + V3TSP::StateVec states; + for (const auto& pair : m2v) { + if (pair.first.empty()) continue; + states.push_back(new VarTspSorter(pair.first)); + } + + // Do the TSP sort + V3TSP::StateVec sortedStates; + V3TSP::tspSort(states, &sortedStates); + + varps.clear(); + + // Helper function to sort given vector, then append to 'varps' + const auto sortAndAppend = [this, &varps](std::vector& subVarps) { + simpleSortVars(subVarps); + for (AstVar* const varp : subVarps) { varps.push_back(varp); } + }; + + // Enumerate by sorted MTaskIdSet, sort within the set separately + for (const V3TSP::TspStateBase* const stateBasep : sortedStates) { + const VarTspSorter* const statep = dynamic_cast(stateBasep); + sortAndAppend(m2v[statep->mtaskIds()]); + VL_DO_DANGLING(delete statep, statep); + } + + // Finally add the variables with no known MTask affinity + sortAndAppend(m2v[MTaskIdSet()]); + } + + void orderModuleVars(AstNodeModule* modp) { + std::vector varps; + + // Unlink all module variables from the module, compute attributes + for (AstNode *nodep = modp->stmtsp(), *nextp; nodep; nodep = nextp) { + nextp = nodep->nextp(); + if (AstVar* const varp = VN_CAST(nodep, Var)) { + // Unlink, add to vector + varp->unlinkFrBack(); + varps.push_back(varp); + // Compute attributes up front + auto& attributes = m_attributes(varp); + // Stratum + const int sigbytes = varp->dtypeSkipRefp()->widthAlignBytes(); + attributes.stratum = (varp->isUsedClock() && varp->widthMin() == 1) ? 0 + : VN_IS(varp->dtypeSkipRefp(), UnpackArrayDType) ? 8 + : (varp->basicp() && varp->basicp()->isOpaque()) ? 7 + : (varp->isScBv() || varp->isScBigUint()) ? 6 + : (sigbytes == 8) ? 5 + : (sigbytes == 4) ? 4 + : (sigbytes == 2) ? 2 + : (sigbytes == 1) ? 1 + : 9; + // Anonymous structure ok + attributes.anonOk = EmitCBaseVisitor::isAnonOk(varp); + } + } + + if (!varps.empty()) { + // Sort variables + if (!v3Global.opt.mtasks()) { + simpleSortVars(varps); + } else { + tspSortVars(varps); + } + + // Insert them back under the module, in the new order, but at + // the front of the list so they come out first in dumps/XML. + auto it = varps.cbegin(); + AstVar* const firstp = *it++; + for (; it != varps.cend(); ++it) firstp->addNext(*it); + if (AstNode* const stmtsp = modp->stmtsp()) { + stmtsp->unlinkFrBackWithNext(); + firstp->addNext(stmtsp); + } + modp->addStmtp(firstp); + } + } + +public: + static void processModule(AstNodeModule* modp) { VariableOrder().orderModuleVars(modp); } +}; + +//###################################################################### +// V3VariableOrder static functions + +void V3VariableOrder::orderAll() { + UINFO(2, __FUNCTION__ << ": " << endl); + for (AstNodeModule* modp = v3Global.rootp()->modulesp(); modp; + modp = VN_CAST(modp->nextp(), NodeModule)) { + VariableOrder::processModule(modp); + } + V3Global::dumpCheckGlobalTree("variableorder", 0, v3Global.opt.dumpTreeLevel(__FILE__) >= 3); +} diff --git a/src/V3VariableOrder.h b/src/V3VariableOrder.h new file mode 100644 index 000000000..0b84c9894 --- /dev/null +++ b/src/V3VariableOrder.h @@ -0,0 +1,30 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Variable ordering +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// Copyright 2003-2021 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* + +#ifndef VERILATOR_V3VARIABLEORDER_H_ +#define VERILATOR_V3VARIABLEORDER_H_ + +#include "config_build.h" +#include "verilatedos.h" + +//============================================================================ + +class V3VariableOrder final { +public: + static void orderAll(); +}; + +#endif // Guard diff --git a/src/Verilator.cpp b/src/Verilator.cpp index 842fa04a9..b61275ccc 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -96,6 +96,7 @@ #include "V3Undriven.h" #include "V3Unknown.h" #include "V3Unroll.h" +#include "V3VariableOrder.h" #include "V3Waiver.h" #include "V3Width.h" @@ -507,6 +508,9 @@ static void process() { // Must be before V3EmitC V3CUse::cUseAll(); + // Order variables + V3VariableOrder::orderAll(); + // emitcInlines is first, as it may set needHInlines which other emitters read V3EmitC::emitcInlines(); V3EmitC::emitcSyms(); diff --git a/test_regress/t/t_dpi_var.pl b/test_regress/t/t_dpi_var.pl index e7f19cd2a..7f3485f5a 100755 --- a/test_regress/t/t_dpi_var.pl +++ b/test_regress/t/t_dpi_var.pl @@ -18,10 +18,10 @@ compile( ); if ($Self->{vlt_all}) { - file_grep("$out_filename", qr/\/i); - file_grep("$out_filename", qr/\/i); - file_grep("$out_filename", qr/\/i); - file_grep("$out_filename", qr/\/i); + file_grep("$out_filename", qr/\/i); + file_grep("$out_filename", qr/\/i); + file_grep("$out_filename", qr/\/i); + file_grep("$out_filename", qr/\/i); } execute( diff --git a/test_regress/t/t_dpi_var_vlt.pl b/test_regress/t/t_dpi_var_vlt.pl index a9d16c3c4..476882c77 100755 --- a/test_regress/t/t_dpi_var_vlt.pl +++ b/test_regress/t/t_dpi_var_vlt.pl @@ -20,10 +20,10 @@ compile( ); if ($Self->{vlt_all}) { - file_grep("$out_filename", qr/\/i); - file_grep("$out_filename", qr/\/i); - file_grep("$out_filename", qr/\/i); - file_grep("$out_filename", qr/\/i); + file_grep("$out_filename", qr/\/i); + file_grep("$out_filename", qr/\/i); + file_grep("$out_filename", qr/\/i); + file_grep("$out_filename", qr/\/i); } execute( diff --git a/test_regress/t/t_inst_tree_inl1_pub0.pl b/test_regress/t/t_inst_tree_inl1_pub0.pl index ec48a4e06..9a8a6aa92 100755 --- a/test_regress/t/t_inst_tree_inl1_pub0.pl +++ b/test_regress/t/t_inst_tree_inl1_pub0.pl @@ -18,9 +18,9 @@ compile( ); if ($Self->{vlt_all}) { - file_grep("$out_filename", qr/\/i); - file_grep("$out_filename", qr/\/i); - file_grep("$out_filename", qr/\/i); + file_grep("$out_filename", qr/\/i); + file_grep("$out_filename", qr/\/i); + file_grep("$out_filename", qr/\/i); } execute( diff --git a/test_regress/t/t_unopt_combo_isolate.pl b/test_regress/t/t_unopt_combo_isolate.pl index 1fd8571d3..cd9c893bb 100755 --- a/test_regress/t/t_unopt_combo_isolate.pl +++ b/test_regress/t/t_unopt_combo_isolate.pl @@ -19,11 +19,11 @@ compile( if ($Self->{vlt_all}) { file_grep($Self->{stats}, qr/Optimizations, isolate_assignments blocks\s+5/i); - file_grep("$out_filename", qr/\/i); - file_grep("$out_filename", qr/\/i); - file_grep("$out_filename", qr/\/i); - file_grep("$out_filename", qr/\/i); - file_grep("$out_filename", qr/\/i); + file_grep("$out_filename", qr/\/i); + file_grep("$out_filename", qr/\/i); + file_grep("$out_filename", qr/\/i); + file_grep("$out_filename", qr/\/i); + file_grep("$out_filename", qr/\/i); } execute( diff --git a/test_regress/t/t_unopt_combo_isolate_vlt.pl b/test_regress/t/t_unopt_combo_isolate_vlt.pl index 8010fe402..a71d1a004 100755 --- a/test_regress/t/t_unopt_combo_isolate_vlt.pl +++ b/test_regress/t/t_unopt_combo_isolate_vlt.pl @@ -19,11 +19,11 @@ compile( if ($Self->{vlt_all}) { file_grep($Self->{stats}, qr/Optimizations, isolate_assignments blocks\s+5/i); - file_grep("$out_filename", qr/\/i); - file_grep("$out_filename", qr/\/i); - file_grep("$out_filename", qr/\/i); - file_grep("$out_filename", qr/\/i); - file_grep("$out_filename", qr/\/i); + file_grep("$out_filename", qr/\/i); + file_grep("$out_filename", qr/\/i); + file_grep("$out_filename", qr/\/i); + file_grep("$out_filename", qr/\/i); + file_grep("$out_filename", qr/\/i); } execute(