From f5a226a183da220c7034b3e71519c12ec8ec87b3 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sun, 17 Oct 2021 11:40:44 +0100 Subject: [PATCH] Partial clean up of V3Order. No functional change intended. This is a partial cleanup of V3Order with the aim of increasing clarity: - Split the initial OrderGraph building and the actual ordering process into separate classes (OrderVisitor -> OrderBuildVisitor + OrderProcess) - Remove all the historical cruft from the graph building phase (now in OrderBuildVisitor), and add more assertions for assumptions. - Change the dot styling of OrderGraph to use shapes and more easily distinguishable colors. - Expand vague comments, remove incorrect comments, and add more. - Replace some old code with cleaner C++11 constructs. - Move code about a bit so logically connected sections are closer to each other, scope some definitions where they are used rather than file scope. - The actual ordering process (now in OrderProcess) is still largely unchanged. The generated code is identical to before (within the limits of the exiting non-determinism). --- src/V3Ast.cpp | 6 +- src/V3Ast.h | 34 +- src/V3AstNodes.h | 12 +- src/V3Class.cpp | 5 +- src/V3Order.cpp | 1286 +++++++++++++++++++++++--------------------- src/V3OrderGraph.h | 16 +- src/V3SenTree.h | 27 +- src/astgen | 24 +- 8 files changed, 742 insertions(+), 668 deletions(-) diff --git a/src/V3Ast.cpp b/src/V3Ast.cpp index 0fecb49a5..d1e1123c6 100644 --- a/src/V3Ast.cpp +++ b/src/V3Ast.cpp @@ -1276,9 +1276,9 @@ AstNodeDType* AstNode::findVoidDType() const { } //###################################################################### -// AstNVisitor +// AstNDeleter -void AstNVisitor::doDeletes() { - for (AstNode* nodep : m_deleteps) nodep->deleteTree(); +void AstNDeleter::doDeletes() { + for (AstNode* const nodep : m_deleteps) nodep->deleteTree(); m_deleteps.clear(); } diff --git a/src/V3Ast.h b/src/V3Ast.h index a65b1656f..cec0f4a26 100644 --- a/src/V3Ast.h +++ b/src/V3Ast.h @@ -1251,31 +1251,39 @@ public: // clang-format on //###################################################################### -// AstNVisitor -- Allows new functions to be called on each node -// type without changing the base classes. See "Modern C++ Design". +// Node deleter, deletes all enqueued AstNode* on destruction, or when +// explicitly told to do so. This is useful when the deletion of removed +// nodes needs to be deferred to a later time, because pointers to the +// removed nodes might still exist. -class AstNVisitor VL_NOT_FINAL { -private: +class AstNDeleter VL_NOT_FINAL { // MEMBERS - std::vector m_deleteps; // Nodes to delete when doDeletes() called -protected: - friend class AstNode; + std::vector m_deleteps; // Nodes to delete public: // METHODS - /// At the end of the visitor (or doDeletes()), delete this pushed node - /// along with all children and next(s). This is often better to use - /// than an immediate deleteTree, as any pointers into this node will - /// persist for the lifetime of the visitor + + // Enqueue node for deletion on next 'doDelete' (or destruction) void pushDeletep(AstNode* nodep) { UASSERT_STATIC(nodep, "Cannot delete nullptr node"); m_deleteps.push_back(nodep); } - /// Call deleteTree on all previously pushDeletep()'ed nodes + + // Delete all previously pushed nodes (by callint deleteTree) void doDeletes(); + // Do the deletions on destruction + virtual ~AstNDeleter() { doDeletes(); } +}; + +//###################################################################### +// AstNVisitor -- Allows new functions to be called on each node +// type without changing the base classes. See "Modern C++ Design". + +class AstNVisitor VL_NOT_FINAL : public AstNDeleter { + friend class AstNode; + public: - virtual ~AstNVisitor() { doDeletes(); } /// Call visit()s on nodep void iterate(AstNode* nodep); /// Call visit()s on nodep diff --git a/src/V3AstNodes.h b/src/V3AstNodes.h index b109c4928..9a409fd92 100644 --- a/src/V3AstNodes.h +++ b/src/V3AstNodes.h @@ -2300,9 +2300,9 @@ class AstScope final : public AstNode { private: // An AstScope->name() is special: . indicates an uninlined scope, __DOT__ an inlined scope string m_name; // Name - AstScope* m_aboveScopep; // Scope above this one in the hierarchy (nullptr if top) - AstCell* m_aboveCellp; // Cell above this in the hierarchy (nullptr if top) - AstNodeModule* m_modp; // Module scope corresponds to + AstScope* const m_aboveScopep; // Scope above this one in the hierarchy (nullptr if top) + AstCell* const m_aboveCellp; // Cell above this in the hierarchy (nullptr if top) + AstNodeModule* const m_modp; // Module scope corresponds to public: AstScope(FileLine* fl, AstNodeModule* modp, const string& name, AstScope* aboveScopep, AstCell* aboveCellp) @@ -2321,8 +2321,8 @@ public: string nameDotless() const; string nameVlSym() const { return ((string("vlSymsp->")) + nameDotless()); } AstNodeModule* modp() const { return m_modp; } - void addVarp(AstNode* nodep) { addOp1p(nodep); } - AstNode* varsp() const { return op1p(); } // op1 = AstVarScope's + void addVarp(AstVarScope* nodep) { addOp1p((AstNode*)nodep); } + AstVarScope* varsp() const { return VN_AS(op1p(), VarScope); } // op1 = AstVarScope's void addActivep(AstNode* nodep) { addOp2p(nodep); } AstNode* blocksp() const { return op2p(); } // op2 = Block names void addFinalClkp(AstNode* nodep) { addOp3p(nodep); } @@ -2367,6 +2367,8 @@ public: : ASTGEN_SUPER_VarScope(fl) , m_scopep{scopep} , m_varp{varp} { + UASSERT_OBJ(scopep, fl, "Scope must be non-null"); + UASSERT_OBJ(varp, fl, "Var must be non-null"); m_circular = false; m_trace = true; dtypeFrom(varp); diff --git a/src/V3Class.cpp b/src/V3Class.cpp index e317a3480..cd7fdb6f7 100644 --- a/src/V3Class.cpp +++ b/src/V3Class.cpp @@ -141,8 +141,9 @@ public: if (VN_IS(moved.first, NodeFTask)) { moved.second->addActivep(moved.first->unlinkFrBack()); } else if (VN_IS(moved.first, Var)) { - AstVarScope* scopep = VN_AS(moved.first->user1p(), VarScope); - moved.second->addVarp(scopep->unlinkFrBack()); + AstVarScope* const scopep = VN_AS(moved.first->user1p(), VarScope); + scopep->unlinkFrBack(); + moved.second->addVarp(scopep); } } } diff --git a/src/V3Order.cpp b/src/V3Order.cpp index 98cb21f7e..d4e4db5b4 100644 --- a/src/V3Order.cpp +++ b/src/V3Order.cpp @@ -108,7 +108,71 @@ #include #include -static bool domainsExclusive(const AstSenTree* fromp, const AstSenTree* top); +//###################################################################### +// Utilities + +static bool domainsExclusive(const AstSenTree* fromp, const AstSenTree* top) { + // Return 'true' if we can prove that both 'from' and 'to' cannot both + // be active on the same eval pass, or false if we can't prove this. + // + // This detects the case of 'always @(posedge clk)' + // and 'always @(negedge clk)' being exclusive. It also detects + // that initial/settle blocks and post-initial blocks are exclusive. + // + // Are there any other cases we need to handle? Maybe not, + // because these are not exclusive: + // always @(posedge A or posedge B) + // always @(negedge A) + // + // ... unless you know more about A and B, which sounds hard. + + const bool toInitial = top->hasInitial() || top->hasSettle(); + const bool fromInitial = fromp->hasInitial() || fromp->hasSettle(); + if (toInitial != fromInitial) return true; + + const AstSenItem* fromSenListp = fromp->sensesp(); + const AstSenItem* toSenListp = top->sensesp(); + + UASSERT_OBJ(fromSenListp, fromp, "sensitivity list empty"); + UASSERT_OBJ(toSenListp, top, "sensitivity list empty"); + + if (fromSenListp->nextp()) return false; + if (toSenListp->nextp()) return false; + + const AstNodeVarRef* fromVarrefp = fromSenListp->varrefp(); + const AstNodeVarRef* toVarrefp = toSenListp->varrefp(); + if (!fromVarrefp || !toVarrefp) return false; + + // We know nothing about the relationship between different clocks here, + // so give up on proving anything. + if (fromVarrefp->varScopep() != toVarrefp->varScopep()) return false; + + return fromSenListp->edgeType().exclusiveEdge(toSenListp->edgeType()); +} + +// Predicate returning true if the LHS of the given assignment is a signal marked as clocker +static bool isClockerAssignment(AstNodeAssign* nodep) { + class Visitor final : public AstNVisitor { + public: + bool m_clkAss = false; // There is signals marked as clocker in the assignment + private: + // METHODS + VL_DEBUG_FUNC; // Declare debug() + virtual void visit(AstNodeAssign* nodep) override { + if (const AstVarRef* const varrefp = VN_CAST(nodep->lhsp(), VarRef)) { + if (varrefp->varp()->attrClocker() == VVarAttrClocker::CLOCKER_YES) { + m_clkAss = true; + UINFO(6, "node was marked as clocker " << varrefp << endl); + } + } + iterateChildren(nodep->rhsp()); + } + virtual void visit(AstNodeMath*) override {} // Accelerate + virtual void visit(AstNode* nodep) override { iterateChildren(nodep); } + } visitor; + visitor.iterate(nodep); + return visitor.m_clkAss; +} //###################################################################### // Functions for above graph classes @@ -125,120 +189,6 @@ void OrderGraph::loopsVertexCb(V3GraphVertex* vertexp) { } } -//###################################################################### - -class OrderMoveDomScope final { - // Information stored for each unique loop, domain & scope trifecta -public: - V3ListEnt m_readyDomScopeE; // List of next ready dom scope - V3List m_readyVertices; // Ready vertices with same domain & scope -private: - bool m_onReadyList = false; // True if DomScope is already on list of ready dom/scopes - const AstSenTree* m_domainp; // Domain all vertices belong to - const AstScope* m_scopep; // Scope all vertices belong to - - using DomScopeKey = std::pair; - using DomScopeMap = std::map; - static DomScopeMap s_dsMap; // Structure registered for each dom/scope pairing - -public: - OrderMoveDomScope(const AstSenTree* domainp, const AstScope* scopep) - : m_domainp{domainp} - , m_scopep{scopep} {} - OrderMoveDomScope* readyDomScopeNextp() const { return m_readyDomScopeE.nextp(); } - const AstSenTree* domainp() const { return m_domainp; } - const AstScope* scopep() const { return m_scopep; } - void ready(OrderVisitor* ovp); // Check the domScope is on ready list, add if not - void movedVertex( - OrderVisitor* ovp, - OrderMoveVertex* vertexp); // Mark one vertex as finished, remove from ready list if done - // STATIC MEMBERS (for lookup) - static void clear() { - for (const auto& itr : s_dsMap) delete itr.second; - s_dsMap.clear(); - } - V3List& readyVertices() { return m_readyVertices; } - static OrderMoveDomScope* findCreate(const AstSenTree* domainp, const AstScope* scopep) { - const DomScopeKey key = std::make_pair(domainp, scopep); - const auto iter = s_dsMap.find(key); - if (iter != s_dsMap.end()) { - return iter->second; - } else { - OrderMoveDomScope* domScopep = new OrderMoveDomScope(domainp, scopep); - s_dsMap.emplace(key, domScopep); - return domScopep; - } - } - string name() const { - return (string("MDS:") + " d=" + cvtToHex(domainp()) + " s=" + cvtToHex(scopep())); - } -}; - -OrderMoveDomScope::DomScopeMap OrderMoveDomScope::s_dsMap; - -inline std::ostream& operator<<(std::ostream& lhs, const OrderMoveDomScope& rhs) { - lhs << rhs.name(); - return lhs; -} - -//###################################################################### -// Order information stored under each AstNode::user1p()... - -// Types of vertex we can create -enum WhichVertex : uint8_t { WV_STD, WV_PRE, WV_PORD, WV_POST, WV_MAX }; - -class OrderUser final { - // Stored in AstVarScope::user1p, a list of all the various vertices - // that can exist for one given variable -private: - std::array m_vertexp; // Vertex of each type (if non nullptr) -public: - // METHODS - OrderVarVertex* newVarUserVertex(V3Graph* graphp, AstScope* scopep, AstVarScope* varscp, - WhichVertex type, bool* createdp = nullptr) { - UASSERT_OBJ(type < WV_MAX, varscp, "Bad case"); - OrderVarVertex* vertexp = m_vertexp[type]; - if (!vertexp) { - UINFO(6, "New vertex " << varscp << endl); - if (createdp) *createdp = true; - switch (type) { - case WV_STD: vertexp = new OrderVarStdVertex(graphp, scopep, varscp); break; - case WV_PRE: vertexp = new OrderVarPreVertex(graphp, scopep, varscp); break; - case WV_PORD: vertexp = new OrderVarPordVertex(graphp, scopep, varscp); break; - case WV_POST: vertexp = new OrderVarPostVertex(graphp, scopep, varscp); break; - default: varscp->v3fatalSrc("Bad case"); - } - m_vertexp[type] = vertexp; - } else { - if (createdp) *createdp = false; - } - return vertexp; - } - - // CONSTRUCTORS - OrderUser() { - for (auto& vertexp : m_vertexp) vertexp = nullptr; - } - ~OrderUser() = default; -}; - -//###################################################################### -// Comparator classes - -//! Comparator for width of associated variable -struct OrderVarWidthCmp { - bool operator()(OrderVarStdVertex* vsv1p, OrderVarStdVertex* vsv2p) { - return vsv1p->varScp()->varp()->width() > vsv2p->varScp()->varp()->width(); - } -}; - -//! Comparator for fanout of vertex -struct OrderVarFanoutCmp { - bool operator()(OrderVarStdVertex* vsv1p, OrderVarStdVertex* vsv2p) { - return vsv1p->fanout() > vsv2p->fanout(); - } -}; - //###################################################################### // The class is used for propagating the clocker attribute for further // avoiding marking clock signals as circular. @@ -252,8 +202,8 @@ struct OrderVarFanoutCmp { // In addition it also check whether clock and data signals are mixed, and // produce a CLKDATA warning if so. // + class OrderClkMarkVisitor final : public AstNVisitor { -private: bool m_hasClk = false; // flag indicating whether there is clock signal on rhs bool m_inClocked = false; // Currently inside a sequential block bool m_newClkMarked; // Flag for deciding whether a new run is needed @@ -287,7 +237,6 @@ private: } } } - virtual void visit(AstVarRef* nodep) override { if (m_inAss && nodep->varp()->attrClocker() == VVarAttrClocker::CLOCKER_YES) { if (m_inClocked) { @@ -340,7 +289,6 @@ private: } virtual void visit(AstNode* nodep) override { iterateChildren(nodep); } -public: // CONSTRUCTORS explicit OrderClkMarkVisitor(AstNode* nodep) { do { @@ -349,41 +297,509 @@ public: } while (m_newClkMarked); } virtual ~OrderClkMarkVisitor() override = default; + +public: + static void process(AstNetlist* nodep) { OrderClkMarkVisitor{nodep}; } }; //###################################################################### -// The class checks if the assignment generates a clock. +// Order information stored under each AstNode::user1p()... + +class OrderUser final { + // Stored in AstVarScope::user1p, a list of all the various vertices + // that can exist for one given scoped variable +public: + // TYPES + enum class VarVertexType : uint8_t { // Types of vertices we can create + STD = 0, + PRE = 1, + PORD = 2, + POST = 3 + }; -class OrderClkAssVisitor final : public AstNVisitor { private: - bool m_clkAss = false; // There is signals marked as clocker in the assignment - // METHODS - VL_DEBUG_FUNC; // Declare debug() - virtual void visit(AstNodeAssign* nodep) override { - if (const AstVarRef* varrefp = VN_CAST(nodep->lhsp(), VarRef)) { - if (varrefp->varp()->attrClocker() == VVarAttrClocker::CLOCKER_YES) { - m_clkAss = true; - UINFO(6, "node was marked as clocker " << varrefp << endl); - } - } - iterateChildren(nodep->rhsp()); - } - virtual void visit(AstVarRef*) override { - // Previous versions checked attrClocker() here, but this breaks - // the updated t_clocker VCD test. - // If reenable this visitor note AstNodeMath short circuit below - } - virtual void visit(AstNodeMath*) override {} // Accelerate - virtual void visit(AstNode* nodep) override { iterateChildren(nodep); } + // Vertex of each type (if non nullptr) + std::array(VarVertexType::POST) + 1> m_vertexps; public: - // CONSTRUCTORS - explicit OrderClkAssVisitor(AstNode* nodep) { iterate(nodep); } - virtual ~OrderClkAssVisitor() override = default; // METHODS - bool isClkAss() const { return m_clkAss; } + OrderVarVertex* getVarVertex(V3Graph* graphp, AstScope* scopep, AstVarScope* varscp, + VarVertexType type) { + const unsigned idx = static_cast(type); + OrderVarVertex* vertexp = m_vertexps[idx]; + if (!vertexp) { + switch (type) { + case VarVertexType::STD: + vertexp = new OrderVarStdVertex(graphp, scopep, varscp); + break; + case VarVertexType::PRE: + vertexp = new OrderVarPreVertex(graphp, scopep, varscp); + break; + case VarVertexType::PORD: + vertexp = new OrderVarPordVertex(graphp, scopep, varscp); + break; + case VarVertexType::POST: + vertexp = new OrderVarPostVertex(graphp, scopep, varscp); + break; + } + m_vertexps[idx] = vertexp; + } + return vertexp; + } + + // CONSTRUCTORS + OrderUser() { m_vertexps.fill(nullptr); } + ~OrderUser() = default; }; +//###################################################################### +// OrderBuildVisitor builds the ordering graph of the entire netlist, and +// removes any nodes that are no longer required once the graph is built + +class OrderBuildVisitor final : public AstNVisitor { + // TYPES + enum VarUsage : uint8_t { VU_CON = 0x1, VU_GEN = 0x2 }; + using VarVertexType = OrderUser::VarVertexType; + + // NODE STATE + // AstVarScope::user1 -> OrderUser instance for variable (via m_orderUser) + // AstVarScope::user2 -> VarUsage within logic blocks + AstUser1InUse user1InUse; + AstUser2InUse user2InUse; + AstUser1Allocator m_orderUser; + + // STATE + // The ordering graph built by this visitor + OrderGraph* const m_graphp = new OrderGraph; + // Singleton vertex that all top level inputs depend on + OrderInputsVertex* const m_inputsVxp = new OrderInputsVertex{m_graphp, nullptr}; + // Singleton DPI Export trigger event vertex + OrderVarVertex* const m_dpiExportTriggerVxp + = v3Global.rootp()->dpiExportTriggerp() + ? getVarVertex(v3Global.rootp()->dpiExportTriggerp(), VarVertexType::STD) + : nullptr; + + OrderLogicVertex* m_activeSenVxp = nullptr; // Sensitivity vertex for clocked logic + OrderLogicVertex* m_logicVxp = nullptr; // Current loic block being analyzed + + // Current AstScope being processed + AstScope* m_scopep = nullptr; + // Sensitivity list for non-combinational logic (incl. initial and settle), + // nullptr for combinational logic + AstSenTree* m_domainp = nullptr; + + bool m_inClocked = false; // Underneath clocked AstActive + bool m_inClkAss = false; // Underneath AstNodeAssign to clock + bool m_inPre = false; // Underneath AstAssignPre + bool m_inPost = false; // Underneath AstAssignPost/AstAlwaysPost + bool m_inPostponed = false; // Underneath AstAlwaysPostponed + + // METHODS + VL_DEBUG_FUNC; // Declare debug() + + void iterateLogic(AstNode* nodep) { + UASSERT_OBJ(!m_logicVxp, nodep, "Should not nest"); + // Reset VarUsage + AstNode::user2ClearTree(); + // Create LogicVertex for this logic node + m_logicVxp = new OrderLogicVertex(m_graphp, m_scopep, m_domainp, nodep); + // If this logic has a clocked activation, add a link from the sensitivity list LogicVertex + // to this LogicVertex. + if (m_activeSenVxp) new OrderEdge(m_graphp, m_activeSenVxp, m_logicVxp, WEIGHT_NORMAL); + // Gather variable dependencies based on usage + iterateChildren(nodep); + // Finished with this logic + m_logicVxp = nullptr; + } + + OrderVarVertex* getVarVertex(AstVarScope* varscp, VarVertexType type) { + return m_orderUser(varscp).getVarVertex(m_graphp, m_scopep, varscp, type); + } + + // VISITORS + virtual void visit(AstSenTree* nodep) override { + // This should only find the global AstSenTrees under the AstTopScope, which we ignore + // here. We visit AstSenTrees separately when encountering the AstActive that references + // them. + UASSERT_OBJ(!m_scopep, nodep, "AstSenTrees should have been made global in V3ActiveTop"); + } + virtual void visit(AstScope* nodep) override { + UASSERT_OBJ(!m_scopep, nodep, "Should not nest"); + m_scopep = nodep; + iterateChildren(nodep); + m_scopep = nullptr; + } + virtual void visit(AstActive* nodep) override { + UASSERT_OBJ(!nodep->sensesStorep(), nodep, + "AstSenTrees should have been made global in V3ActiveTop"); + UASSERT_OBJ(m_scopep, nodep, "AstActive not under AstScope"); + UASSERT_OBJ(!m_logicVxp, nodep, "AstActive under logic"); + UASSERT_OBJ(!m_inClocked && !m_activeSenVxp && !m_domainp, nodep, "Should not nest"); + + m_inClocked = nodep->sensesp()->hasClocked(); + + // Analyze variable references in sensitivity list. Note that non-clocked sensitivity lists + // don't reference any variables (have no clocks), so the sensitivity list vertex would + // have no incoming dependencies and is hence redundant, therefore we only do this for + // clocked sensitivity lists. + if (m_inClocked) { + // Add LogicVertex for the sensitivity list of this AstActive. + m_activeSenVxp = new OrderLogicVertex(m_graphp, m_scopep, nodep->sensesp(), nodep); + // Analyze variables in the sensitivity list + iterateChildren(nodep->sensesp()); + } + + // Ignore the sensitivity domain for combinational logic. We will assign combinational + // logic to a domain later, based on the domains of incoming variables. + if (!nodep->sensesp()->hasCombo()) m_domainp = nodep->sensesp(); + + // Analyze logic underneath + iterateChildren(nodep); + + // + m_inClocked = false; + m_activeSenVxp = nullptr; + m_domainp = nullptr; + } + virtual void visit(AstNodeVarRef* nodep) override { + // As we explicitly not visit (see ignored nodes below) any subtree that is not relevant + // for ordering, we should be able to assert this: + UASSERT_OBJ(m_scopep, nodep, "AstVarRef not under scope"); + UASSERT_OBJ(m_logicVxp || m_activeSenVxp, nodep, + "AstVarRef not under logic nor sensitivity list"); + AstVarScope* const varscp = nodep->varScopep(); + UASSERT_OBJ(varscp, nodep, "Var didn't get varscoped in V3Scope.cpp"); + if (!m_logicVxp) { + // Variable reference in sensitivity list. Add clock dependency. + + UASSERT_OBJ(!nodep->access().isWriteOrRW(), nodep, + "How can a sensitivity list be writing a variable?"); + // Add edge from sensed VarStdVertex -> to sensitivity list LogicVertex + OrderVarVertex* const varVxp = getVarVertex(varscp, VarVertexType::STD); + varVxp->isClock(true); + new OrderEdge(m_graphp, varVxp, m_activeSenVxp, WEIGHT_MEDIUM); + } else { + // Variable reference in logic. Add data dependency. + + // Check whether this variable was already generated/consumed in the same logic. We + // don't want to add extra edges if the logic has many usages of the same variable, + // so only proceed on first encounter. + const bool prevGen = varscp->user2() & VU_GEN; + const bool prevCon = varscp->user2() & VU_CON; + + // Compute whether the variable is produced (written) here + bool gen = false; + if (!prevGen && nodep->access().isWriteOrRW()) { + gen = true; + if (m_inPostponed) { + // IEE 1800-2017 (4.2.9) forbids any value updates in the postponed region, but + // Verilator generated trigger signals for $strobe are cleared after the + // display is executed. This is both safe to ignore (because their single read + // is in the same AstAlwaysPostponed, just prior to the clear), and is + // necessary to ignore to avoid a circular logic (UNOPTFLAT) warning. + UASSERT_OBJ(prevCon, nodep, "Should have been consumed in same process"); + gen = false; + } + } + + // Compute whether the value is consumed (read) here + bool con = false; + if (!prevCon && nodep->access().isReadOrRW()) { + con = true; + if (prevGen && !m_inClocked) { + // Dangerous assumption: + // If a variable is consumed in the same combinational process that produced it + // earlier, consider it something like: + // foo = 1 + // foo = foo + 1 + // and still optimize. Note this will break though: + // if (sometimes) foo = 1 + // foo = foo + 1 + // TODO: Do this properly with liveness analysis (i.e.: if live, it's consumed) + // Note however that this construct is not nicely synthesizable (yields + // latch?). + con = false; + } + + // TODO: Explain how the following two exclusions are useful + if (varscp->varp()->attrClockEn() && !m_inPre && !m_inPost && !m_inClocked) { + // 'clock_enable' attribute on this signal: user's worrying about it for us + con = false; + } + if (m_inClkAss + && (varscp->varp()->attrClocker() != VVarAttrClocker::CLOCKER_YES)) { + // 'clocker' attribute on some other signal in same logic: same as + // 'clock_enable' attribute above + con = false; + UINFO(4, "nodep used as clock_enable " << varscp << " in " + << m_logicVxp->nodep() << endl); + } + } + + // Variable is produced + if (gen) { + // Update VarUsage + varscp->user2(varscp->user2() | VU_GEN); + // Add edges for produced variables + if (!m_inClocked || m_inPost) { + // Combinational logic + OrderVarVertex* const varVxp = getVarVertex(varscp, VarVertexType::STD); + // Add edge from producing LogicVertex -> produced VarStdVertex + if (m_inPost) { + new OrderPostCutEdge(m_graphp, m_logicVxp, varVxp); + // Mark the VarVertex as being produced by a delayed (non-blocking) + // assignment. This is used to control marking internal clocks + // circular, which must only happen if they are generated by delayed + // assignment. + varVxp->isDelayed(true); + UINFO(5, " Found delayed assignment (post) " << varVxp << endl); + } else if (varscp->varp()->attrClocker() == VVarAttrClocker::CLOCKER_YES) { + // If the variable has the 'clocker' attribute, avoid making it + // circular by adding a hard edge instead of normal cuttable edge. + new OrderEdge(m_graphp, m_logicVxp, varVxp, WEIGHT_NORMAL); + } else { + new OrderComboCutEdge(m_graphp, m_logicVxp, varVxp); + } + + // Add edge from produced VarPostVertex -> to producing LogicVertex + + // For m_inPost: + // Add edge consumed_var_POST->logic_vertex + // This prevents a consumer of the "early" value to be scheduled + // after we've changed to the next-cycle value + // ALWAYS do it: + // There maybe a wire a=b; between the two blocks + OrderVarVertex* const postVxp = getVarVertex(varscp, VarVertexType::POST); + new OrderEdge(m_graphp, postVxp, m_logicVxp, WEIGHT_POST); + } else if (m_inPre) { // AstAssignPre + // Add edge from producing LogicVertex -> produced VarPordVertex + OrderVarVertex* const ordVxp = getVarVertex(varscp, VarVertexType::PORD); + new OrderEdge(m_graphp, m_logicVxp, ordVxp, WEIGHT_NORMAL); + // Add edge from producing LogicVertex -> produced VarStdVertex + OrderVarVertex* const varVxp = getVarVertex(varscp, VarVertexType::STD); + new OrderEdge(m_graphp, m_logicVxp, varVxp, WEIGHT_NORMAL); + } else { + // Sequential (clocked) logic + // Add edge from produced VarPordVertex -> to producing LogicVertex + OrderVarVertex* const ordVxp = getVarVertex(varscp, VarVertexType::PORD); + new OrderEdge(m_graphp, ordVxp, m_logicVxp, WEIGHT_NORMAL); + // Add edge from producing LogicVertex-> to produced VarStdVertex + OrderVarVertex* const varVxp = getVarVertex(varscp, VarVertexType::STD); + new OrderEdge(m_graphp, m_logicVxp, varVxp, WEIGHT_NORMAL); + } + } + + // Variable is consumed + if (con) { + // Update VarUsage + varscp->user2(varscp->user2() | VU_CON); + // Add edges + if (!m_inClocked || m_inPost) { + // Combinational logic + OrderVarVertex* const varVxp = getVarVertex(varscp, VarVertexType::STD); + // Add edge from consumed VarStdVertex -> to consuming LogicVertex + new OrderEdge(m_graphp, varVxp, m_logicVxp, WEIGHT_MEDIUM); + } else if (m_inPre) { + // AstAssignPre logic + // Add edge from consumed VarPreVertex -> to consuming LogicVertex + // This one is cutable (vs the producer) as there's only one such consumer, + // but may be many producers + OrderVarVertex* const preVxp = getVarVertex(varscp, VarVertexType::PRE); + new OrderPreCutEdge(m_graphp, preVxp, m_logicVxp); + } else { + // Sequential (clocked) logic + // Add edge from consuming LogicVertex -> to consumed VarPreVertex + // Generation of 'pre' because we want to indicate it should be before + // AstAssignPre + OrderVarVertex* const preVxp = getVarVertex(varscp, VarVertexType::PRE); + new OrderEdge(m_graphp, m_logicVxp, preVxp, WEIGHT_NORMAL); + // Add edge from consuming LogicVertex -> to consumed VarPostVertex + OrderVarVertex* const postVxp = getVarVertex(varscp, VarVertexType::POST); + new OrderEdge(m_graphp, m_logicVxp, postVxp, WEIGHT_POST); + } + } + } + } + virtual void visit(AstDpiExportUpdated* nodep) override { + // This is under a logic block (AstAlways) sensitive to a change in the DPI export trigger. + // We just need to add an edge to the enclosing logic vertex (the vertex for the + // AstAlways). + OrderVarVertex* const varVxp = getVarVertex(nodep->varScopep(), VarVertexType::STD); + new OrderComboCutEdge(m_graphp, m_logicVxp, varVxp); + // Only used for ordering, so we can get rid of it here + nodep->unlinkFrBack(); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + } + virtual void visit(AstCCall* nodep) override { + // Calls to 'context' imported DPI function may call DPI exported functions + if (m_dpiExportTriggerVxp && nodep->funcp()->dpiImportWrapper() + && nodep->funcp()->dpiContext()) { + UASSERT_OBJ(m_logicVxp, nodep, "Call not under logic"); + new OrderEdge(m_graphp, m_logicVxp, m_dpiExportTriggerVxp, WEIGHT_NORMAL); + } + iterateChildren(nodep); + } + + //--- Logic akin to SystemVerilog Processes (AstNodeProcedure) + virtual void visit(AstInitial* nodep) override { // + iterateLogic(nodep); + } + virtual void visit(AstAlways* nodep) override { // + iterateLogic(nodep); + } + virtual void visit(AstAlwaysPost* nodep) override { + UASSERT_OBJ(!m_inPost, nodep, "Should not nest"); + m_inPost = true; + iterateLogic(nodep); + m_inPost = false; + } + virtual void visit(AstAlwaysPostponed* nodep) override { + UASSERT_OBJ(!m_inPostponed, nodep, "Should not nest"); + m_inPostponed = true; + iterateLogic(nodep); + m_inPostponed = false; + } + virtual void visit(AstFinal* nodep) override { // LCOV_EXCL_START + nodep->v3fatalSrc("AstFinal should have been removed already"); + } // LCOV_EXCL_STOP + + //--- Logic akin go SystemVerilog continuous assignments + virtual void visit(AstAssignAlias* nodep) override { // + iterateLogic(nodep); + } + virtual void visit(AstAssignW* nodep) override { + UASSERT_OBJ(!m_inClkAss, nodep, "Should not nest"); + m_inClkAss = isClockerAssignment(nodep); + iterateLogic(nodep); + m_inClkAss = false; + } + virtual void visit(AstAssignPre* nodep) override { + UASSERT_OBJ(!m_inClkAss && !m_inPre, nodep, "Should not nest"); + m_inClkAss = isClockerAssignment(nodep); + m_inPre = true; + iterateLogic(nodep); + m_inPre = false; + m_inClkAss = false; + } + virtual void visit(AstAssignPost* nodep) override { + UASSERT_OBJ(!m_inClkAss && !m_inPost, nodep, "Should not nest"); + m_inClkAss = isClockerAssignment(nodep); + m_inPost = true; + iterateLogic(nodep); + m_inPost = false; + m_inClkAss = false; + } + + //--- Verilator concoctions + virtual void visit(AstAlwaysPublic* nodep) override { // + iterateLogic(nodep); + } + virtual void visit(AstCoverToggle* nodep) override { // + iterateLogic(nodep); + } + + //--- Ignored nodes + virtual void visit(AstVar*) override {} + virtual void visit(AstVarScope* nodep) override {} + virtual void visit(AstCell*) override {} // Only interested in the respective AstScope + virtual void visit(AstTypeTable*) override {} + virtual void visit(AstConstPool*) override {} + virtual void visit(AstClass*) override {} + virtual void visit(AstCFunc*) override { + // Calls to DPI exports handled with AstCCall. /* verilator public */ functions are + // ignored for now (and hence potentially mis-ordered), but could use the same or + // similar mechanism as DPI exports. Every other impure function (including those + // that may set a non-local variable) must have been inlined in V3Task. + } + + //--- + virtual void visit(AstNode* nodep) override { iterateChildren(nodep); } + + // CONSTRUCTOR + OrderBuildVisitor(AstNetlist* nodep) { + // Enable debugging (3 is default if global debug; we want acyc debugging) + if (debug()) m_graphp->debug(5); + + // Add edges from the InputVertex to all top level input signal VarStdVertex + for (AstVarScope* vscp = nodep->topScopep()->scopep()->varsp(); vscp; + vscp = VN_AS(vscp->nextp(), VarScope)) { + if (vscp->varp()->isNonOutput()) { + OrderVarVertex* varVxp = getVarVertex(vscp, VarVertexType::STD); + new OrderEdge(m_graphp, m_inputsVxp, varVxp, WEIGHT_INPUT); + } + } + + // Build the rest of the graph + iterate(nodep); + } + virtual ~OrderBuildVisitor() = default; + +public: + // Process the netlist and return the constructed ordering graph. It's 'process' because + // this visitor does change the tree (removes some nodes related to DPI export trigger). + static std::unique_ptr process(AstNetlist* nodep) { + return std::unique_ptr{OrderBuildVisitor{nodep}.m_graphp}; + } +}; + +//###################################################################### + +class OrderProcess; + +class OrderMoveDomScope final { + // Information stored for each unique loop, domain & scope trifecta +public: + V3ListEnt m_readyDomScopeE; // List of next ready dom scope + V3List m_readyVertices; // Ready vertices with same domain & scope +private: + bool m_onReadyList = false; // True if DomScope is already on list of ready dom/scopes + const AstSenTree* m_domainp; // Domain all vertices belong to + const AstScope* m_scopep; // Scope all vertices belong to + + using DomScopeKey = std::pair; + using DomScopeMap = std::map; + static DomScopeMap s_dsMap; // Structure registered for each dom/scope pairing + +public: + OrderMoveDomScope(const AstSenTree* domainp, const AstScope* scopep) + : m_domainp{domainp} + , m_scopep{scopep} {} + OrderMoveDomScope* readyDomScopeNextp() const { return m_readyDomScopeE.nextp(); } + const AstSenTree* domainp() const { return m_domainp; } + const AstScope* scopep() const { return m_scopep; } + // Check the domScope is on ready list, add if not + void ready(OrderProcess* opp); + // Mark one vertex as finished, remove from ready list if done + void movedVertex(OrderProcess* opp, OrderMoveVertex* vertexp); + // STATIC MEMBERS (for lookup) + static void clear() { + for (const auto& itr : s_dsMap) delete itr.second; + s_dsMap.clear(); + } + V3List& readyVertices() { return m_readyVertices; } + static OrderMoveDomScope* findCreate(const AstSenTree* domainp, const AstScope* scopep) { + const DomScopeKey key = std::make_pair(domainp, scopep); + const auto iter = s_dsMap.find(key); + if (iter != s_dsMap.end()) { + return iter->second; + } else { + OrderMoveDomScope* domScopep = new OrderMoveDomScope(domainp, scopep); + s_dsMap.emplace(key, domScopep); + return domScopep; + } + } + string name() const { + return (string("MDS:") + " d=" + cvtToHex(domainp()) + " s=" + cvtToHex(scopep())); + } +}; + +OrderMoveDomScope::DomScopeMap OrderMoveDomScope::s_dsMap; + +inline std::ostream& operator<<(std::ostream& lhs, const OrderMoveDomScope& rhs) { + lhs << rhs.name(); + return lhs; +} + //###################################################################### // ProcessMoveBuildGraph @@ -616,95 +1032,36 @@ public: }; //###################################################################### -// Order class functions +// OrderProcess class -class OrderVisitor final : public AstNVisitor { -private: +class OrderProcess final : AstNDeleter { // NODE STATE - // Forming graph: - // Entire Netlist: - // AstVarScope::user1u -> OrderUser* for usage var (via m_orderUser) - // {statement}Node::user1p-> AstModule* statement is under - // USER4 Cleared on each Logic stmt - // AstVarScope::user4() -> VarUsage(gen/con/both). Where already encountered signal - // Ordering (user3/4/5 cleared between forming and ordering) - // AstScope::user1p() -> AstNodeModule*. Module this scope is under - // AstNodeModule::user3() -> Number of routines created - // Each call to V3Const::constify - // AstNode::user4() Used by V3Const::constify, called below - AstUser1InUse m_inuser1; - AstUser2InUse m_inuser2; - AstUser3InUse m_inuser3; - // AstUser4InUse m_inuser4; // Used only when building tree, so below - - AstUser1Allocator m_orderUser; + // AstNodeModule::user3 -> int: Number of AstCFuncs created under this module + // AstNode::user4 -> Used by V3Const::constifyExpensiveEdit + AstUser3InUse user3InUse; // STATE - OrderGraph m_graph; // Scoreboard of var usages/dependencies - SenTreeFinder m_finder; // Find global sentree's and add them - AstSenTree* m_comboDomainp = nullptr; // Combo activation tree - AstSenTree* m_deleteDomainp = nullptr; // Delete this from tree - OrderInputsVertex* m_inputsVxp = nullptr; // Top level vertex all inputs point from - OrderVarVertex* m_dpiExportTriggerVxp = nullptr; // DPI Export trigger condition vertex - OrderLogicVertex* m_logicVxp = nullptr; // Current statement being tracked, nullptr=ignored - AstTopScope* m_topScopep = nullptr; // Current top scope being processed - AstScope* m_scopetopp = nullptr; // Scope under TOPSCOPE - AstNodeModule* m_modp = nullptr; // Current module - AstScope* m_scopep = nullptr; // Current scope being processed - AstActive* m_activep = nullptr; // Current activation block - bool m_inSenTree = false; // Underneath AstSenItem; any varrefs are clocks - bool m_inClocked = false; // Underneath clocked block - bool m_inClkAss = false; // Underneath AstAssign - bool m_inPre = false; // Underneath AstAssignPre - bool m_inPost = false; // Underneath AstAssignPost - bool m_inPostponed = false; // Underneath AstAssignPostponed - OrderLogicVertex* m_activeSenVxp = nullptr; // Sensitivity vertex - // STATE... for inside process + OrderGraph& m_graph; // The ordering graph + OrderInputsVertex& m_inputsVtx; // The singleton OrderInputsVertex + SenTreeFinder m_finder; // Global AstSenTree manager + AstSenTree* const m_comboDomainp; // The combinational domain AstSenTree + AstSenTree* const m_deleteDomainp; // Dummy AstSenTree indicating needs deletion + AstScope& m_scopetop; // The top level AstScope + AstCFunc* m_pomNewFuncp = nullptr; // Current function being created int m_pomNewStmts = 0; // Statements in function being created V3Graph m_pomGraph; // Graph of logic elements to move V3List m_pomWaiting; // List of nodes needing inputs to become ready -protected: friend class OrderMoveDomScope; V3List m_pomReadyDomScope; // List of ready domain/scope pairs, by loopId std::vector m_unoptflatVars; // Vector of variables in UNOPTFLAT loop -private: // STATS std::array m_statCut; // Count of each edge type cut - // TYPES - enum VarUsage : uint8_t { VU_NONE = 0, VU_CON = 1, VU_GEN = 2 }; - // METHODS VL_DEBUG_FUNC; // Declare debug() - void iterateNewStmt(AstNode* nodep) { - if (m_scopep) { - UINFO(4, " STMT " << nodep << endl); - // VV***** We reset user4p() - AstNode::user4ClearTree(); - UASSERT_OBJ(m_activep && m_activep->sensesp(), nodep, "nullptr"); - // If inside combo logic, ignore the domain, we'll assign one based on interconnect - AstSenTree* startDomainp = m_activep->sensesp(); - if (startDomainp->hasCombo()) startDomainp = nullptr; - m_logicVxp = new OrderLogicVertex(&m_graph, m_scopep, startDomainp, nodep); - if (m_activeSenVxp) { - // If in a clocked activation, add a link from the sensitivity to this block - // Add edge logic_sensitive_vertex->logic_vertex - new OrderEdge(&m_graph, m_activeSenVxp, m_logicVxp, WEIGHT_NORMAL); - } - nodep->user1p(m_modp); - iterateChildren(nodep); - m_logicVxp = nullptr; - } - } - - OrderVarVertex* newVarUserVertex(AstVarScope* varscp, WhichVertex type, - bool* createdp = nullptr) { - return m_orderUser(varscp).newVarUserVertex(&m_graph, m_scopep, varscp, type, createdp); - } - void process(); void processCircular(); using VertexVec = std::deque; @@ -759,9 +1116,9 @@ private: void nodeMarkCircular(OrderVarVertex* vertexp, OrderEdge* edgep) { // To be marked circular requires being a clock assigned in a delayed assignment, or // having a cutable in or out edge, none of which is true for the DPI export trigger. - UASSERT(vertexp != m_dpiExportTriggerVxp, - "DPI expor trigger should not be marked circular"); - AstVarScope* nodep = vertexp->varScp(); + AstVarScope* const nodep = vertexp->varScp(); + UASSERT(nodep != v3Global.rootp()->dpiExportTriggerp(), + "DPI export trigger should not be marked circular"); OrderLogicVertex* fromLVtxp = nullptr; OrderLogicVertex* toLVtxp = nullptr; if (edgep) { @@ -831,17 +1188,17 @@ private: } } - //! Find all variables in an UNOPTFLAT loop - //! - //! Ignore vars that are 1-bit wide and don't worry about generated - //! variables (PRE and POST vars, __Vdly__, __Vcellin__ and __VCellout). - //! What remains are candidates for splitting to break loops. - //! - //! node->user3 is used to mark if we have done a particular variable. - //! vertex->user is used to mark if we have seen this vertex before. - //! - //! @todo We could be cleverer in the future and consider just - //! the width that is generated/consumed. + // Find all variables in an UNOPTFLAT loop + // + // Ignore vars that are 1-bit wide and don't worry about generated + // variables (PRE and POST vars, __Vdly__, __Vcellin__ and __VCellout). + // What remains are candidates for splitting to break loops. + // + // node->user3 is used to mark if we have done a particular variable. + // vertex->user is used to mark if we have seen this vertex before. + // + // @todo We could be cleverer in the future and consider just + // the width that is generated/consumed. void reportLoopVars(OrderVarVertex* vertexp) { m_graph.userClearVertices(); AstNode::user3ClearTree(); @@ -852,7 +1209,11 @@ private: // May be very large vector, so only report the "most important" // elements. Up to 10 of the widest std::cerr << V3Error::warnMore() << "... Widest candidate vars to split:\n"; - std::stable_sort(m_unoptflatVars.begin(), m_unoptflatVars.end(), OrderVarWidthCmp()); + std::stable_sort(m_unoptflatVars.begin(), m_unoptflatVars.end(), + [](OrderVarStdVertex* vsv1p, OrderVarStdVertex* vsv2p) -> bool { + return vsv1p->varScp()->varp()->width() + > vsv2p->varScp()->varp()->width(); + }); std::unordered_set canSplitList; int lim = m_unoptflatVars.size() < 10 ? m_unoptflatVars.size() : 10; for (int i = 0; i < lim; i++) { @@ -870,7 +1231,10 @@ private: } // Up to 10 of the most fanned out std::cerr << V3Error::warnMore() << "... Most fanned out candidate vars to split:\n"; - std::stable_sort(m_unoptflatVars.begin(), m_unoptflatVars.end(), OrderVarFanoutCmp()); + std::stable_sort(m_unoptflatVars.begin(), m_unoptflatVars.end(), + [](OrderVarStdVertex* vsv1p, OrderVarStdVertex* vsv2p) -> bool { + return vsv1p->fanout() > vsv2p->fanout(); + }); lim = m_unoptflatVars.size() < 10 ? m_unoptflatVars.size() : 10; for (int i = 0; i < lim; i++) { OrderVarStdVertex* vsvertexp = m_unoptflatVars[i]; @@ -918,313 +1282,33 @@ private: if (edgep->fromp()->color() == color) reportLoopVarsIterate(edgep->fromp(), color); } } - // VISITORS - virtual void visit(AstNetlist* nodep) override { - { - AstUser4InUse m_inuser4; // Used only when building tree, so below - iterateChildren(nodep); - } - // We're finished, complete the topscopes - if (m_topScopep) { - process(); - m_topScopep = nullptr; - } - } - virtual void visit(AstTopScope* nodep) override { - // Process the last thing we're finishing - UASSERT_OBJ(!m_topScopep, nodep, "Only one topscope should ever be created"); - UINFO(2, " Loading tree...\n"); - // VV***** We reset userp() - AstNode::user1ClearTree(); - AstNode::user3ClearTree(); - m_graph.clear(); - m_activep = nullptr; - m_topScopep = nodep; - m_scopetopp = nodep->scopep(); - // ProcessDomainsIterate will use these when it needs to move - // something to a combodomain. This saves a ton of find() operations. - AstSenTree* combp - = new AstSenTree(nodep->fileline(), // Gets cloned() so ok if goes out of scope - new AstSenItem(nodep->fileline(), AstSenItem::Combo())); - m_comboDomainp = m_finder.getSenTree(combp); - pushDeletep(combp); // Cleanup when done - // Fake AstSenTree we set domainp to indicate needs deletion - m_deleteDomainp = new AstSenTree(nodep->fileline(), - new AstSenItem(nodep->fileline(), AstSenItem::Settle())); - pushDeletep(m_deleteDomainp); // Cleanup when done - UINFO(5, " DeleteDomain = " << m_deleteDomainp << endl); - // Base vertices - m_activeSenVxp = nullptr; - m_inputsVxp = new OrderInputsVertex(&m_graph, nullptr); - if (AstVarScope* const dpiExportTrigger = v3Global.rootp()->dpiExportTriggerp()) { - m_dpiExportTriggerVxp = newVarUserVertex(dpiExportTrigger, WV_STD); - } - // - iterateChildren(nodep); - // Done topscope, erase extra user information - // user1p passed to next process() operation - AstNode::user3ClearTree(); - AstNode::user4ClearTree(); - } - virtual void visit(AstNodeModule* nodep) override { - VL_RESTORER(m_modp); - { - m_modp = nodep; - iterateChildren(nodep); - } - } - virtual void visit(AstClass*) override {} - virtual void visit(AstScope* nodep) override { - UINFO(4, " SCOPE " << nodep << endl); - m_scopep = nodep; - m_logicVxp = nullptr; - m_activeSenVxp = nullptr; - nodep->user1p(m_modp); - // Iterate - iterateChildren(nodep); - m_scopep = nullptr; - } - virtual void visit(AstActive* nodep) override { - // Create required activation blocks and add to module - UINFO(4, " ACTIVE " << nodep << endl); - m_activep = nodep; - m_activeSenVxp = nullptr; - m_inClocked = nodep->hasClocked(); - // Grab the sensitivity list - UASSERT_OBJ(!nodep->sensesStorep(), nodep, - "Senses should have been activeTop'ed to be global!"); - iterate(nodep->sensesp()); - // Collect statements under it - iterateChildren(nodep); - m_activep = nullptr; - m_activeSenVxp = nullptr; - m_inClocked = false; - } - virtual void visit(AstVarScope* nodep) override { - // Create links to all input signals - UASSERT_OBJ(m_modp, nodep, "Scope not under module"); - if (m_modp->isTop() && nodep->varp()->isNonOutput()) { - OrderVarVertex* varVxp = newVarUserVertex(nodep, WV_STD); - new OrderEdge(&m_graph, m_inputsVxp, varVxp, WEIGHT_INPUT); - } - } - virtual void visit(AstNodeVarRef* nodep) override { - if (m_scopep) { - AstVarScope* varscp = nodep->varScopep(); - UASSERT_OBJ(varscp, nodep, "Var didn't get varscoped in V3Scope.cpp"); - if (m_inSenTree) { - // Add CLOCK dependency... This is a root of the tree we'll trace - UASSERT_OBJ(!nodep->access().isWriteOrRW(), nodep, - "How can a sensitivity be setting a var?"); - OrderVarVertex* varVxp = newVarUserVertex(varscp, WV_STD); - varVxp->isClock(true); - new OrderEdge(&m_graph, varVxp, m_activeSenVxp, WEIGHT_MEDIUM); - } else { - UASSERT_OBJ(m_logicVxp, nodep, "Var ref not under a logic block"); - // What new directions is this used - // We don't want to add extra edges if the logic block has many usages of same var - bool gen = false; - bool con = false; - if (nodep->access().isWriteOrRW() && !m_inPostponed) - gen = !(varscp->user4() & VU_GEN); - if (nodep->access().isReadOrRW()) { - con = !(varscp->user4() & VU_CON); - if ((varscp->user4() & VU_GEN) && !m_inClocked) { - // Dangerous assumption: - // If a variable is used in the same activation which defines it first, - // consider it something like: - // foo = 1 - // foo = foo + 1 - // and still optimize. This is the rule verilog-mode assumes for /*AS*/ - // Note this will break though: - // if (sometimes) foo = 1 - // foo = foo + 1 - con = false; - } - if (varscp->varp()->attrClockEn() && !m_inPre && !m_inPost && !m_inClocked) { - // clock_enable attribute: user's worrying about it for us - con = false; - } - if (m_inClkAss - && (varscp->varp()->attrClocker() != VVarAttrClocker::CLOCKER_YES)) { - con = false; - UINFO(4, "nodep used as clock_enable " << varscp << " in " - << m_logicVxp->nodep() << endl); - } - } - if (gen) varscp->user4(varscp->user4() | VU_GEN); - if (con) varscp->user4(varscp->user4() | VU_CON); - // Add edges - if (!m_inClocked || m_inPost) { - // Combo logic - { // not settle and (combo or inPost) - if (gen) { - // Add edge logic_vertex->logic_generated_var - OrderVarVertex* varVxp = newVarUserVertex(varscp, WV_STD); - if (m_inPost) { - new OrderPostCutEdge(&m_graph, m_logicVxp, varVxp); - // Mark the vertex. Used to control marking - // internal clocks circular, which must only - // happen if they are generated by delayed - // assignment. - UINFO(5, - " Found delayed assignment (post) " << varVxp << endl); - varVxp->isDelayed(true); - } else { - // If the lhs is a clocker, avoid marking that as circular by - // putting a hard edge instead of normal cuttable - if (varscp->varp()->attrClocker() - == VVarAttrClocker::CLOCKER_YES) { - new OrderEdge(&m_graph, m_logicVxp, varVxp, WEIGHT_NORMAL); - } else { - new OrderComboCutEdge(&m_graph, m_logicVxp, varVxp); - } - } - // For m_inPost: - // Add edge consumed_var_POST->logic_vertex - // This prevents a consumer of the "early" value to be scheduled - // after we've changed to the next-cycle value - // ALWAYS do it: - // There maybe a wire a=b; between the two blocks - OrderVarVertex* postVxp = newVarUserVertex(varscp, WV_POST); - new OrderEdge(&m_graph, postVxp, m_logicVxp, WEIGHT_POST); - } - if (con) { - // Add edge logic_consumed_var->logic_vertex - OrderVarVertex* varVxp = newVarUserVertex(varscp, WV_STD); - new OrderEdge(&m_graph, varVxp, m_logicVxp, WEIGHT_MEDIUM); - } - } - } else if (m_inPre) { - // AstAssignPre logic - if (gen) { - // Add edge logic_vertex->generated_var_PREORDER - OrderVarVertex* ordVxp = newVarUserVertex(varscp, WV_PORD); - new OrderEdge(&m_graph, m_logicVxp, ordVxp, WEIGHT_NORMAL); - // Add edge logic_vertex->logic_generated_var (same as if comb) - OrderVarVertex* varVxp = newVarUserVertex(varscp, WV_STD); - new OrderEdge(&m_graph, m_logicVxp, varVxp, WEIGHT_NORMAL); - } - if (con) { - // Add edge logic_consumed_var_PREVAR->logic_vertex - // This one is cutable (vs the producer) as there's - // only one of these, but many producers - OrderVarVertex* preVxp = newVarUserVertex(varscp, WV_PRE); - new OrderPreCutEdge(&m_graph, preVxp, m_logicVxp); - } - } else { - // Seq logic - if (gen) { - // Add edge logic_generated_var_PREORDER->logic_vertex - OrderVarVertex* ordVxp = newVarUserVertex(varscp, WV_PORD); - new OrderEdge(&m_graph, ordVxp, m_logicVxp, WEIGHT_NORMAL); - // Add edge logic_vertex->logic_generated_var (same as if comb) - OrderVarVertex* varVxp = newVarUserVertex(varscp, WV_STD); - new OrderEdge(&m_graph, m_logicVxp, varVxp, WEIGHT_NORMAL); - } - if (con) { - // Add edge logic_vertex->consumed_var_PREVAR - // Generation of 'pre' because we want to indicate - // it should be before AstAssignPre - OrderVarVertex* preVxp = newVarUserVertex(varscp, WV_PRE); - new OrderEdge(&m_graph, m_logicVxp, preVxp, WEIGHT_NORMAL); - // Add edge logic_vertex->consumed_var_POST - OrderVarVertex* postVxp = newVarUserVertex(varscp, WV_POST); - new OrderEdge(&m_graph, m_logicVxp, postVxp, WEIGHT_POST); - } - } - } - } - } - virtual void visit(AstDpiExportUpdated* nodep) override { - // This is under an AstAlways, sensitive to a change in the DPI export trigger. We just - // need to add an edge to the enclosing logic vertex (the vertex for the AstAlways). - OrderVarVertex* const varVxp = newVarUserVertex(nodep->varScopep(), WV_STD); - new OrderComboCutEdge(&m_graph, m_logicVxp, varVxp); - // Only used for ordering, so we can get rid of it here - nodep->unlinkFrBack(); - VL_DO_DANGLING(pushDeletep(nodep), nodep); - } - virtual void visit(AstCCall* nodep) override { - // Calls to 'context' imported DPI function may call DPI exported functions - if (m_dpiExportTriggerVxp && nodep->funcp()->dpiImportWrapper() - && nodep->funcp()->dpiContext()) { - UASSERT_OBJ(m_logicVxp, nodep, "Call not under logic"); - new OrderEdge(&m_graph, m_logicVxp, m_dpiExportTriggerVxp, WEIGHT_NORMAL); - } - iterateChildren(nodep); - } - virtual void visit(AstSenTree* nodep) override { - // Having a node derived from the sentree isn't required for - // correctness, it merely makes the graph better connected - // and improves graph algorithmic performance - if (m_scopep) { // Else TOPSCOPE's SENTREE list - m_inSenTree = true; - if (nodep->hasClocked()) { - if (!m_activeSenVxp) { - m_activeSenVxp = new OrderLogicVertex(&m_graph, m_scopep, nodep, m_activep); - } - iterateChildren(nodep); - } - m_inSenTree = false; - } - } - virtual void visit(AstAlwaysPost* nodep) override { - m_inPost = true; - iterateNewStmt(nodep); - m_inPost = false; - } - virtual void visit(AstAlwaysPostponed* nodep) override { - VL_RESTORER(m_inPostponed); - m_inPostponed = true; - iterateNewStmt(nodep); - } - virtual void visit(AstAlways* nodep) override { iterateNewStmt(nodep); } - virtual void visit(AstAlwaysPublic* nodep) override { iterateNewStmt(nodep); } - virtual void visit(AstAssignAlias* nodep) override { iterateNewStmt(nodep); } - virtual void visit(AstAssignW* nodep) override { - OrderClkAssVisitor visitor{nodep}; - m_inClkAss = visitor.isClkAss(); - iterateNewStmt(nodep); - m_inClkAss = false; - } - virtual void visit(AstAssignPre* nodep) override { - OrderClkAssVisitor visitor{nodep}; - m_inClkAss = visitor.isClkAss(); - m_inPre = true; - iterateNewStmt(nodep); - m_inPre = false; - m_inClkAss = false; - } - virtual void visit(AstAssignPost* nodep) override { - OrderClkAssVisitor visitor{nodep}; - m_inClkAss = visitor.isClkAss(); - m_inPost = true; - iterateNewStmt(nodep); - m_inPost = false; - m_inClkAss = false; - } - virtual void visit(AstCoverToggle* nodep) override { iterateNewStmt(nodep); } - virtual void visit(AstInitial* nodep) override { - // We use initials to setup parameters and static consts's which may be referenced - // in user initial blocks. So use ordering to sort them all out. - iterateNewStmt(nodep); - } - virtual void visit(AstCFunc*) override { - // Calls to DPI exports handled with AstCCall. /* verlator public */ functions are - // ignored for now (and hence potentially mis-ordered), but could use the same or - // similar mechanism as DPI exports. Every other impure function (including those - // that may set a non-local variable) must have been inlined in V3Task. - } - //-------------------- - virtual void visit(AstNode* nodep) override { iterateChildren(nodep); } -public: - // CONSTRUCTORS - OrderVisitor() { - if (debug()) m_graph.debug(5); // 3 is default if global debug; we want acyc debugging + // Only for member initialization in constructor + static OrderInputsVertex& findInputVertex(OrderGraph& graph) { + for (V3GraphVertex* vtxp = graph.verticesBeginp(); vtxp; vtxp = vtxp->verticesNextp()) { + if (auto* ivtxp = dynamic_cast(vtxp)) return *ivtxp; + } + VL_UNREACHABLE } - virtual ~OrderVisitor() override { + + // Only for member initialization in constructor + static AstSenTree* makeDeleteDomainSenTree(FileLine* fl) { + // TODO: Using "Never" instead of "Settle" causes a test failure, it probably shouldn't ... + return new AstSenTree{fl, new AstSenItem{fl, AstSenItem::Settle{}}}; + } + + // CONSTRUCTOR + OrderProcess(AstNetlist* netlistp, OrderGraph& graph) + : m_graph{graph} + , m_inputsVtx{findInputVertex(graph)} + , m_finder{netlistp} + , m_comboDomainp{m_finder.getComb()} + , m_deleteDomainp{makeDeleteDomainSenTree(netlistp->fileline())} + , m_scopetop{*netlistp->topScopep()->scopep()} { + pushDeletep(m_deleteDomainp); + } + + ~OrderProcess() { // Stats for (int type = 0; type < OrderVEdgeType::_ENUM_END; type++) { const double count = double(m_statCut[type]); @@ -1232,85 +1316,46 @@ public: V3Stats::addStat(string("Order, cut, ") + OrderVEdgeType(type).ascii(), count); } } - // Destruction - m_graph.debug(V3Error::debugDefault()); } - void main(AstNode* nodep) { iterate(nodep); } + +public: + // Order the logic + static void main(AstNetlist* netlistp, OrderGraph& graph) { + OrderProcess{netlistp, graph}.process(); + } }; -//###################################################################### -// General utilities - -static bool domainsExclusive(const AstSenTree* fromp, const AstSenTree* top) { - // Return 'true' if we can prove that both 'from' and 'to' cannot both - // be active on the same eval pass, or false if we can't prove this. - // - // This detects the case of 'always @(posedge clk)' - // and 'always @(negedge clk)' being exclusive. It also detects - // that initial/settle blocks and post-initial blocks are exclusive. - // - // Are there any other cases we need to handle? Maybe not, - // because these are not exclusive: - // always @(posedge A or posedge B) - // always @(negedge A) - // - // ... unless you know more about A and B, which sounds hard. - - const bool toInitial = top->hasInitial() || top->hasSettle(); - const bool fromInitial = fromp->hasInitial() || fromp->hasSettle(); - if (toInitial != fromInitial) return true; - - const AstSenItem* fromSenListp = fromp->sensesp(); - const AstSenItem* toSenListp = top->sensesp(); - - UASSERT_OBJ(fromSenListp, fromp, "sensitivity list empty"); - UASSERT_OBJ(toSenListp, top, "sensitivity list empty"); - - if (fromSenListp->nextp()) return false; - if (toSenListp->nextp()) return false; - - const AstNodeVarRef* fromVarrefp = fromSenListp->varrefp(); - const AstNodeVarRef* toVarrefp = toSenListp->varrefp(); - if (!fromVarrefp || !toVarrefp) return false; - - // We know nothing about the relationship between different clocks here, - // so give up on proving anything. - if (fromVarrefp->varScopep() != toVarrefp->varScopep()) return false; - - return fromSenListp->edgeType().exclusiveEdge(toSenListp->edgeType()); -} - //###################################################################### // OrderMoveDomScope methods // Check the domScope is on ready list, add if not -inline void OrderMoveDomScope::ready(OrderVisitor* ovp) { +inline void OrderMoveDomScope::ready(OrderProcess* opp) { if (!m_onReadyList) { m_onReadyList = true; - m_readyDomScopeE.pushBack(ovp->m_pomReadyDomScope, this); + m_readyDomScopeE.pushBack(opp->m_pomReadyDomScope, this); } } // Mark one vertex as finished, remove from ready list if done -inline void OrderMoveDomScope::movedVertex(OrderVisitor* ovp, OrderMoveVertex* vertexp) { +inline void OrderMoveDomScope::movedVertex(OrderProcess* opp, OrderMoveVertex* vertexp) { UASSERT_OBJ(m_onReadyList, vertexp, "Moving vertex from ready when nothing was on que as ready."); if (m_readyVertices.empty()) { // Else more work to get to later m_onReadyList = false; - m_readyDomScopeE.unlink(ovp->m_pomReadyDomScope, this); + m_readyDomScopeE.unlink(opp->m_pomReadyDomScope, this); } } //###################################################################### -// OrderVisitor - Clock propagation +// OrderProcess methods -void OrderVisitor::processInputs() { +void OrderProcess::processInputs() { m_graph.userClearVertices(); // Vertex::user() // 1 if input recursed, 2 if marked as input, // 3 if out-edges recursed // Start at input vertex, process from input-to-output order VertexVec todoVec; // List of newly-input marked vectors we need to process - todoVec.push_front(m_inputsVxp); - m_inputsVxp->isFromInput(true); // By definition + todoVec.push_front(&m_inputsVtx); + m_inputsVtx.isFromInput(true); // By definition while (!todoVec.empty()) { OrderEitherVertex* vertexp = todoVec.back(); todoVec.pop_back(); @@ -1318,7 +1363,7 @@ void OrderVisitor::processInputs() { } } -void OrderVisitor::processInputsInIterate(OrderEitherVertex* vertexp, VertexVec& todoVec) { +void OrderProcess::processInputsInIterate(OrderEitherVertex* vertexp, VertexVec& todoVec) { // Propagate PrimaryIn through simple assignments if (vertexp->user()) return; // Already processed if (false && debug() >= 9) { @@ -1358,7 +1403,7 @@ void OrderVisitor::processInputsInIterate(OrderEitherVertex* vertexp, VertexVec& // UINFO(9, " InIdone " << vertexp << endl); } -void OrderVisitor::processInputsOutIterate(OrderEitherVertex* vertexp, VertexVec& todoVec) { +void OrderProcess::processInputsOutIterate(OrderEitherVertex* vertexp, VertexVec& todoVec) { if (vertexp->user() == 3) return; // Already out processed // UINFO(9, " InOIter " << vertexp << endl); // First make sure input path is fully recursed @@ -1387,7 +1432,7 @@ void OrderVisitor::processInputsOutIterate(OrderEitherVertex* vertexp, VertexVec //###################################################################### // OrderVisitor - Circular detection -void OrderVisitor::processCircular() { +void OrderProcess::processCircular() { // Take broken edges and add circular flags // The change detect code will use this to force changedets for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp = itp->verticesNextp()) { @@ -1432,7 +1477,7 @@ void OrderVisitor::processCircular() { } } -void OrderVisitor::processSensitive() { +void OrderProcess::processSensitive() { // Sc sensitives are required on all inputs that go to a combo // block. (Not inputs that go only to clocked blocks.) for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp = itp->verticesNextp()) { @@ -1456,7 +1501,7 @@ void OrderVisitor::processSensitive() { } } -void OrderVisitor::processDomains() { +void OrderProcess::processDomains() { for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp = itp->verticesNextp()) { OrderEitherVertex* vertexp = dynamic_cast(itp); UASSERT(vertexp, "Null or vertex not derived from EitherVertex"); @@ -1464,7 +1509,7 @@ void OrderVisitor::processDomains() { } } -void OrderVisitor::processDomainsIterate(OrderEitherVertex* vertexp) { +void OrderProcess::processDomainsIterate(OrderEitherVertex* vertexp) { // The graph routines have already sorted the vertexes and edges into best->worst order // Assign clock domains to each signal. // Sequential logic is forced into the same sequential domain. @@ -1475,7 +1520,6 @@ void OrderVisitor::processDomainsIterate(OrderEitherVertex* vertexp) { UINFO(5, " pdi: " << vertexp << endl); OrderVarVertex* vvertexp = dynamic_cast(vertexp); AstSenTree* domainp = nullptr; - UASSERT(m_comboDomainp, "not preset"); if (vvertexp && vvertexp->varScp()->varp()->isNonOutput()) domainp = m_comboDomainp; if (vvertexp && vvertexp->varScp()->isCircular()) domainp = m_comboDomainp; if (!domainp) { @@ -1548,7 +1592,7 @@ void OrderVisitor::processDomainsIterate(OrderEitherVertex* vertexp) { //###################################################################### // OrderVisitor - Move graph construction -void OrderVisitor::processEdgeReport() { +void OrderProcess::processEdgeReport() { // Make report of all signal names and what clock edges they have const string filename = v3Global.debugFilename("order_edges.txt"); const std::unique_ptr logp{V3File::new_ofstream(filename)}; @@ -1581,19 +1625,19 @@ void OrderVisitor::processEdgeReport() { for (const string& i : report) *logp << i << '\n'; } -void OrderVisitor::processMoveClear() { +void OrderProcess::processMoveClear() { OrderMoveDomScope::clear(); m_pomWaiting.reset(); m_pomReadyDomScope.reset(); m_pomGraph.clear(); } -void OrderVisitor::processMoveBuildGraph() { +void OrderProcess::processMoveBuildGraph() { // Build graph of only vertices UINFO(5, " MoveBuildGraph\n"); processMoveClear(); - m_pomGraph - .userClearVertices(); // Vertex::user->OrderMoveVertex*, last edge added or nullptr=none + // Vertex::user->OrderMoveVertex*, last edge added or nullptr=none + m_pomGraph.userClearVertices(); OrderMoveVertexMaker createOrderMoveVertex(&m_pomGraph, &m_pomWaiting); ProcessMoveBuildGraph serialPMBG(&m_graph, &m_pomGraph, @@ -1604,7 +1648,7 @@ void OrderVisitor::processMoveBuildGraph() { //###################################################################### // OrderVisitor - Moving -void OrderVisitor::processMove() { +void OrderProcess::processMove() { // The graph routines have already sorted the vertexes and edges into best->worst order // Make a new waiting graph with only OrderLogicVertex's // (Order is preserved in the recreation so the sorting is preserved) @@ -1654,7 +1698,7 @@ void OrderVisitor::processMove() { processMoveClear(); } -void OrderVisitor::processMovePrepReady() { +void OrderProcess::processMovePrepReady() { // Make list of ready nodes UINFO(5, " MovePrepReady\n"); for (OrderMoveVertex* vertexp = m_pomWaiting.begin(); vertexp;) { @@ -1664,7 +1708,7 @@ void OrderVisitor::processMovePrepReady() { } } -void OrderVisitor::processMoveReadyOne(OrderMoveVertex* vertexp) { +void OrderProcess::processMoveReadyOne(OrderMoveVertex* vertexp) { // Recursive! // Move one node from waiting to ready list vertexp->setReady(); @@ -1681,7 +1725,7 @@ void OrderVisitor::processMoveReadyOne(OrderMoveVertex* vertexp) { } } -void OrderVisitor::processMoveDoneOne(OrderMoveVertex* vertexp) { +void OrderProcess::processMoveDoneOne(OrderMoveVertex* vertexp) { // Move one node from ready to completion vertexp->setMoved(); // Unlink from ready lists @@ -1707,7 +1751,7 @@ void OrderVisitor::processMoveDoneOne(OrderMoveVertex* vertexp) { } } -void OrderVisitor::processMoveOne(OrderMoveVertex* vertexp, OrderMoveDomScope* domScopep, +void OrderProcess::processMoveOne(OrderMoveVertex* vertexp, OrderMoveDomScope* domScopep, int level) { UASSERT_OBJ(vertexp->domScopep() == domScopep, vertexp, "Domain mismatch; list misbuilt?"); const OrderLogicVertex* lvertexp = vertexp->logicp(); @@ -1716,17 +1760,17 @@ void OrderVisitor::processMoveOne(OrderMoveVertex* vertexp, OrderMoveDomScope* d << " s=" << cvtToHex(scopep) << " " << lvertexp << endl); AstActive* newActivep = processMoveOneLogic(lvertexp, m_pomNewFuncp /*ref*/, m_pomNewStmts /*ref*/); - if (newActivep) m_scopetopp->addActivep(newActivep); + if (newActivep) m_scopetop.addActivep(newActivep); processMoveDoneOne(vertexp); } -AstActive* OrderVisitor::processMoveOneLogic(const OrderLogicVertex* lvertexp, +AstActive* OrderProcess::processMoveOneLogic(const OrderLogicVertex* lvertexp, AstCFunc*& newFuncpr, int& newStmtsr) { AstActive* activep = nullptr; AstScope* const scopep = lvertexp->scopep(); AstSenTree* const domainp = lvertexp->domainp(); AstNode* nodep = lvertexp->nodep(); - AstNodeModule* const modp = VN_AS(scopep->user1p(), NodeModule); // Stashed by visitor func + AstNodeModule* const modp = scopep->modp(); UASSERT(modp, "nullptr"); if (VN_IS(nodep, SenTree)) { // Just ignore sensitivities, we'll deal with them when we move statements that need them @@ -1794,7 +1838,7 @@ AstActive* OrderVisitor::processMoveOneLogic(const OrderLogicVertex* lvertexp, return activep; } -void OrderVisitor::processMTasksInitial(InitialLogicE logic_type) { +void OrderProcess::processMTasksInitial(InitialLogicE logic_type) { // Emit initial/settle logic. Initial blocks won't be part of the // mtask partition, aren't eligible for parallelism. // @@ -1813,11 +1857,11 @@ void OrderVisitor::processMTasksInitial(InitialLogicE logic_type) { lastScopep = initp->scopep(); } AstActive* newActivep = processMoveOneLogic(initp, initCFunc /*ref*/, initStmts /*ref*/); - if (newActivep) m_scopetopp->addActivep(newActivep); + if (newActivep) m_scopetop.addActivep(newActivep); } } -void OrderVisitor::processMTasks() { +void OrderProcess::processMTasks() { // For nondeterminism debug: V3Partition::hashGraphDebug(&m_graph, "V3Order's m_graph"); @@ -1893,7 +1937,7 @@ void OrderVisitor::processMTasks() { // of the MTask graph. FileLine* rootFlp = v3Global.rootp()->fileline(); AstExecGraph* execGraphp = new AstExecGraph(rootFlp); - m_scopetopp->addActivep(execGraphp); + m_scopetop.addActivep(execGraphp); v3Global.rootp()->execGraphp(execGraphp); // Create CFuncs and bodies for each MTask. @@ -1952,7 +1996,7 @@ void OrderVisitor::processMTasks() { //###################################################################### // OrderVisitor - Top processing -void OrderVisitor::process() { +void OrderProcess::process() { // Dump data m_graph.dumpDotFilePrefixed("orderg_pre"); @@ -2020,12 +2064,16 @@ void OrderVisitor::process() { //###################################################################### // Order class functions -void V3Order::orderAll(AstNetlist* nodep) { +void V3Order::orderAll(AstNetlist* netlistp) { UINFO(2, __FUNCTION__ << ": " << endl); - { - OrderClkMarkVisitor markVisitor{nodep}; - OrderVisitor visitor; - visitor.main(nodep); - } // Destruct before checking + // Propagate 'clocker' attribute through logic + OrderClkMarkVisitor::process(netlistp); + // Build ordering graph + std::unique_ptr orderGraph = OrderBuildVisitor::process(netlistp); + // Order the netlist + OrderProcess::main(netlistp, *orderGraph.get()); + // Reset debug level + orderGraph.get()->debug(V3Error::debugDefault()); + // Dump tree V3Global::dumpCheckGlobalTree("order", 0, v3Global.opt.dumpTreeLevel(__FILE__) >= 3); } diff --git a/src/V3OrderGraph.h b/src/V3OrderGraph.h index 56e44bc99..963aa0415 100644 --- a/src/V3OrderGraph.h +++ b/src/V3OrderGraph.h @@ -167,6 +167,7 @@ public: virtual string name() const override { return "*INPUTS*"; } virtual string dotColor() const override { return "green"; } virtual string dotName() const override { return ""; } + virtual string dotShape() const override { return "invhouse"; } virtual bool domainMatters() override { return false; } }; @@ -193,7 +194,9 @@ public: return (cvtToHex(m_nodep) + "\\n " + cvtToStr(nodep()->typeName())); } AstNode* nodep() const { return m_nodep; } - virtual string dotColor() const override { return "yellow"; } + virtual string dotShape() const override { + return VN_IS(m_nodep, Active) ? "doubleoctagon" : "rect"; + } }; class OrderVarVertex VL_NOT_FINAL : public OrderEitherVertex { @@ -221,6 +224,7 @@ public: bool isClock() const { return m_isClock; } void isDelayed(bool flag) { m_isDelayed = flag; } bool isDelayed() const { return m_isDelayed; } + virtual string dotShape() const override { return "ellipse"; } }; class OrderVarStdVertex final : public OrderVarVertex { @@ -238,7 +242,7 @@ public: virtual string name() const override { return (cvtToHex(varScp()) + "\\n " + varScp()->name()); } - virtual string dotColor() const override { return "skyblue"; } + virtual string dotColor() const override { return "grey"; } virtual bool domainMatters() override { return true; } }; class OrderVarPreVertex final : public OrderVarVertex { @@ -256,7 +260,7 @@ public: virtual string name() const override { return (cvtToHex(varScp()) + " PRE\\n " + varScp()->name()); } - virtual string dotColor() const override { return "lightblue"; } + virtual string dotColor() const override { return "green"; } virtual bool domainMatters() override { return false; } }; class OrderVarPostVertex final : public OrderVarVertex { @@ -274,7 +278,7 @@ public: virtual string name() const override { return (cvtToHex(varScp()) + " POST\\n " + varScp()->name()); } - virtual string dotColor() const override { return "CadetBlue"; } + virtual string dotColor() const override { return "red"; } virtual bool domainMatters() override { return false; } }; class OrderVarPordVertex final : public OrderVarVertex { @@ -292,7 +296,7 @@ public: virtual string name() const override { return (cvtToHex(varScp()) + " PORD\\n " + varScp()->name()); } - virtual string dotColor() const override { return "NavyBlue"; } + virtual string dotColor() const override { return "blue"; } virtual bool domainMatters() override { return false; } }; @@ -307,7 +311,7 @@ class OrderMoveVertex final : public V3GraphVertex { OrderMoveDomScope* m_domScopep; // Domain/scope list information protected: - friend class OrderVisitor; + friend class OrderProcess; friend class OrderMoveVertexMaker; // These only contain the "next" item, // for the head of the list, see the same var name under OrderVisitor diff --git a/src/V3SenTree.h b/src/V3SenTree.h index 243d493fa..2cc62d06b 100644 --- a/src/V3SenTree.h +++ b/src/V3SenTree.h @@ -82,21 +82,24 @@ private: public: // CONSTRUCTORS SenTreeFinder() - : m_topScopep{v3Global.rootp()->topScopep()} { + : SenTreeFinder(v3Global.rootp()) {} + + explicit SenTreeFinder(AstNetlist* netlistp) + : m_topScopep{netlistp->topScopep()} { // Gather existing global SenTrees - for (AstSenTree* nodep = m_topScopep->senTreesp(); nodep; - nodep = VN_AS(nodep->nextp(), SenTree)) { - m_trees.add(nodep); + for (AstNode* nodep = m_topScopep->senTreesp(); nodep; nodep = nodep->nextp()) { + m_trees.add(VN_AS(nodep, SenTree)); } } // METHODS + + // Return a global AstSenTree that matches given SenTree. + // If no such global AstSenTree exists create one and add it to the stored AstTopScope. AstSenTree* getSenTree(AstSenTree* senTreep) { - // Return a global SenTree that matches given SenTree. If no such global - // SenTree exists, create one and add it to the stored TopScope. AstSenTree* treep = m_trees.find(senTreep); - // Not found, form a new one if (!treep) { + // Not found, form a new one treep = senTreep->cloneTree(false); m_topScopep->addSenTreep(treep); UINFO(8, " New SENTREE " << treep << endl); @@ -104,6 +107,16 @@ public: } return treep; } + + // Return the global combinational AstSenTree. + // If no such global SenTree exists create one and add it to the stored AstTopScope. + AstSenTree* getComb() { + FileLine* const fl = m_topScopep->fileline(); + AstSenTree* const combp = new AstSenTree{fl, new AstSenItem{fl, AstSenItem::Combo()}}; + AstSenTree* const resultp = getSenTree(combp); + VL_DO_DANGLING(combp->deleteTree(), combp); // getSenTree clones, so can delete + return resultp; + } }; #endif // Guard diff --git a/src/astgen b/src/astgen index 97534f102..579a7f014 100755 --- a/src/astgen +++ b/src/astgen @@ -362,21 +362,19 @@ def read_types(filename): match = re.search(r'^\s*(class|struct)\s*(\S+)', line) if match: classn = match.group(2) - inh = "" match = re.search(r':\s*public\s+(\S+)', line) - if match: - inh = match.group(1) - # print("class "+classn+" : "+inh) - if classn == "AstNode": - inh = "" - if re.search(r'Ast', inh) or classn == "AstNode": + supern = match.group(1) if match else "" + assert classn != "AstNode" or supern == "", "AstNode can't have a superclass" + if re.search(r'Ast', supern) or classn == "AstNode": + if supern == "AstNDeleter": + continue classn = re.sub(r'^Ast', '', classn) - inh = re.sub(r'^Ast', '', inh) - Classes[classn] = inh - if inh != '': - if inh not in Children: - Children[inh] = {} - Children[inh][classn] = 1 + supern = re.sub(r'^Ast', '', supern) + Classes[classn] = supern + if supern != '': + if supern not in Children: + Children[supern] = {} + Children[supern][classn] = 1 def read_stages(filename):