// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Block code ordering // // Code available from: http://www.veripool.org/verilator // //************************************************************************* // // Copyright 2003-2017 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. // // Verilator is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // //************************************************************************* // V3Order's Transformations: // // Compute near optimal scheduling of always/wire statements // Make a graph of the entire netlist // // Add master "*INPUTS*" vertex. // For inputs on top level // Add vertex for each input var. // Add edge INPUTS->var_vertex // // For seq logic // Add logic_sensitive_vertex for this list of SenItems // Add edge for each sensitive_var->logic_sensitive_vertex // For AssignPre's // Add vertex for this logic // Add edge logic_sensitive_vertex->logic_vertex // Add edge logic_consumed_var_PREVAR->logic_vertex // Add edge logic_vertex->logic_generated_var (same as if comb) // Add edge logic_vertex->generated_var_PREORDER // Cutable dependency to attempt to order dlyed // assignments to avoid saving state, thus we prefer // a <= b ... As the opposite order would // b <= c ... require the old value of b. // For Logic // Add vertex for this logic // Add edge logic_sensitive_vertex->logic_vertex // Add edge logic_generated_var_PREORDER->logic_vertex // This insures the AssignPre gets scheduled before this logic // Add edge logic_vertex->consumed_var_PREVAR // Add edge logic_vertex->consumed_var_POSTVAR // Add edge logic_vertex->logic_generated_var (same as if comb) // For AssignPost's // Add vertex for this logic // Add edge logic_sensitive_vertex->logic_vertex // Add edge logic_consumed_var->logic_vertex (same as if comb) // Add edge logic_vertex->logic_generated_var (same as if comb) // // For comb logic // For comb logic // Add vertex for this logic // Add edge logic_consumed_var->logic_vertex // Add edge logic_vertex->logic_generated_var // Mark it cutable, as circular logic may require // the generated signal to become a primary input again. // // // // Rank the graph starting at INPUTS (see V3Graph) // // Visit the graph's logic vertices in ranked order // For all logic vertices with all inputs already ordered // Make ordered block for this module // For all ^^ in same domain // Move logic to ordered activation // When we have no more choices, we move to the next module // and make a new block. Add that new activation block to the list of calls to make. // //************************************************************************* #include "config_build.h" #include "verilatedos.h" #include #include #include #include #include #include #include #include #include #include #include "V3Global.h" #include "V3File.h" #include "V3Ast.h" #include "V3Graph.h" #include "V3List.h" #include "V3SenTree.h" #include "V3Stats.h" #include "V3EmitCBase.h" #include "V3Const.h" #include "V3Order.h" #include "V3OrderGraph.h" #include "V3EmitV.h" class OrderMoveDomScope; //###################################################################### // Functions for above graph classes void OrderGraph::loopsVertexCb(V3GraphVertex* vertexp) { if (debug()) cout<<"-Info-Loop: "<(vertexp)) { cerr<nodep()->fileline()<<" "<nodep()->typeName()<(vertexp)) { cerr<varScp()->fileline()<<" "<varScp()->prettyName()< m_readyDomScopeE;// List of next ready dom scope V3List m_readyVertices; // Ready vertices with same domain & scope private: bool m_onReadyList; // True if DomScope is already on list of ready dom/scopes AstSenTree* m_domainp; // Domain all vertices belong to AstScope* m_scopep; // Scope all vertices belong to OrderLoopId m_inLoop; // Loop member of typedef pair, AstScope*> DomScopeKey; typedef std::map DomScopeMap; static DomScopeMap s_dsMap; // Structure registered for each dom/scope pairing public: OrderMoveDomScope(OrderLoopId inLoop, AstSenTree* domainp, AstScope* scopep) : m_onReadyList(false), m_domainp(domainp), m_scopep(scopep), m_inLoop(inLoop) {} OrderMoveDomScope* readyDomScopeNextp() const { return m_readyDomScopeE.nextp(); } OrderLoopId inLoop() const { return m_inLoop; } AstSenTree* domainp() const { return m_domainp; } 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 (DomScopeMap::iterator it=s_dsMap.begin(); it!=s_dsMap.end(); ++it) { delete it->second; } s_dsMap.clear(); } V3List& readyVertices() { return m_readyVertices; } static OrderMoveDomScope* findCreate (OrderLoopId inLoop, AstSenTree* domainp, AstScope* scopep) { const DomScopeKey key = make_pair(make_pair(inLoop,domainp),scopep); DomScopeMap::iterator iter = s_dsMap.find(key); if (iter != s_dsMap.end()) { return iter->second; } else { OrderMoveDomScope* domScopep = new OrderMoveDomScope(inLoop, domainp, scopep); s_dsMap.insert(make_pair(key, domScopep)); return domScopep; } } string name() const { return (string("MDS:") +" lp="+cvtToStr(inLoop()) +" d="+cvtToStr((void*)domainp()) +" s="+cvtToStr((void*)scopep())); } }; OrderMoveDomScope::DomScopeMap OrderMoveDomScope::s_dsMap; inline ostream& operator<< (ostream& lhs, const OrderMoveDomScope& rhs) { lhs<=WV_MAX) varscp->v3fatalSrc("Bad Case\n"); OrderVarVertex* vertexp = m_vertexp[type]; if (!vertexp) { UINFO(6,"New vertex "<v3fatalSrc("Bad Case\n"); } m_vertexp[type] = vertexp; } else { if (createdp) *createdp=false; } return vertexp; } public: // CONSTRUCTORS OrderUser() { for (int i=0; ivarScp()->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. // Transformation: // while (newClockerMarked) // check all assignments // if RHS is marked as clocker: // mark LHS as clocker as well. // newClockerMarked = true; // // In addition it also check whether clock and data signals are mixed, and // produce a CLKDATA warning if so. // class OrderClkMarkVisitor : public AstNVisitor { private: bool m_hasClk; // flag indicating whether there is clock signal on rhs bool m_inClocked; // Currently inside a sequential block bool m_newClkMarked; // Flag for deciding whether a new run is needed bool m_inAss; // Currently inside of a assignment int m_childClkWidth; // If in hasClk, width of clock signal in child int m_rightClkWidth; // Clk width on the RHS // METHODS static int debug() { static int level = -1; if (VL_UNLIKELY(level < 0)) level = v3Global.opt.debugSrcLevel(__FILE__); return level; } virtual void visit(AstNodeAssign* nodep) { m_hasClk = false; if (AstVarRef* varrefp = nodep->rhsp()->castVarRef()) { this->visit(varrefp); m_rightClkWidth = varrefp->width(); if (varrefp->varp()->attrClocker() == AstVarAttrClocker::CLOCKER_YES) { if (m_inClocked) { varrefp->v3warn(CLKDATA, "Clock used as data (on rhs of assignment) in sequential block "<rhsp()->iterateAndNext(*this); m_rightClkWidth = m_childClkWidth; m_inAss = false; } // do the marking if (m_hasClk) { if (nodep->lhsp()->width() > m_rightClkWidth) { nodep->v3warn(CLKDATA, "Clock is assigned to part of data signal "<< nodep->lhsp()<lhsp()->width() <lhsp()->castVarRef(); if (lhsp && (lhsp->varp()->attrClocker() == AstVarAttrClocker::CLOCKER_UNKNOWN)) { lhsp->varp()->attrClocker(AstVarAttrClocker::CLOCKER_YES); // mark as clocker m_newClkMarked = true; // enable a further run since new clocker is marked UINFO(5, "node is newly marked as clocker by assignment "<varp()->attrClocker() == AstVarAttrClocker::CLOCKER_YES) { if (m_inClocked) { nodep->v3warn(CLKDATA, "Clock used as data (on rhs of assignment) in sequential block "<prettyName()); } else { m_hasClk = true; m_childClkWidth = nodep->width(); // Pass up UINFO(5, "node is already marked as clocker "<lhsp()->iterateAndNext(*this); int lw = m_childClkWidth; nodep->rhsp()->iterateAndNext(*this); int rw = m_childClkWidth; m_childClkWidth = lw + rw; // Pass up } } virtual void visit(AstNodeSel* nodep) { if (m_inAss) { nodep->iterateChildren(*this); // Pass up result width if (m_childClkWidth > nodep->width()) m_childClkWidth = nodep->width(); } } virtual void visit(AstSel* nodep) { if (m_inAss) { nodep->iterateChildren(*this); if (m_childClkWidth > nodep->width()) m_childClkWidth = nodep->width(); } } virtual void visit(AstReplicate* nodep) { if (m_inAss) { nodep->iterateChildren(*this); if (nodep->rhsp()->castConst()) { m_childClkWidth = m_childClkWidth * nodep->rhsp()->castConst()->toUInt(); } else { m_childClkWidth = nodep->width(); // can not check in this case. } } } virtual void visit(AstActive* nodep) { m_inClocked = nodep->hasClocked(); nodep->iterateChildren(*this); m_inClocked = false; } virtual void visit(AstNode* nodep) { nodep->iterateChildren(*this); } public: // CONSTUCTORS explicit OrderClkMarkVisitor(AstNode* nodep) { m_hasClk = false; m_inClocked = false; m_inAss = false; m_childClkWidth = 0; m_rightClkWidth = 0; do { m_newClkMarked = false; nodep->accept(*this); } while (m_newClkMarked); } virtual ~OrderClkMarkVisitor() {} }; //###################################################################### // The class used to check if the assignment has clocker inside. class OrderClkAssVisitor : public AstNVisitor { private: bool m_clkAss; // There is signals marked as clocker in the assignment // METHODS static int debug() { static int level = -1; if (VL_UNLIKELY(level < 0)) level = v3Global.opt.debugSrcLevel(__FILE__); return level; } virtual void visit(AstNodeAssign* nodep) { if (AstVarRef* varrefp = nodep->lhsp()->castVarRef() ) if (varrefp->varp()->attrClocker() == AstVarAttrClocker::CLOCKER_YES) { m_clkAss = true; UINFO(6, "node was marked as clocker "<rhsp()->iterateChildren(*this); } virtual void visit(AstVarRef* nodep) { if (nodep->varp()->attrClocker() == AstVarAttrClocker::CLOCKER_YES) { m_clkAss = true; UINFO(6, "node was marked as clocker "<iterateChildren(*this); } public: // CONSTUCTORS explicit OrderClkAssVisitor(AstNode* nodep) { m_clkAss = false; nodep->accept(*this); } virtual ~OrderClkAssVisitor() {} // METHODS bool isClkAss() {return m_clkAss;} }; //###################################################################### // Order class functions class OrderVisitor : public AstNVisitor { private: // NODE STATE // Forming graph: // Entire Netlist: // AstVarScope::user1p -> OrderUser* for usage var // {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 // STATE OrderGraph m_graph; // Scoreboard of var usages/dependencies SenTreeFinder m_finder; // Find global sentree's and add them AstSenTree* m_comboDomainp; // Combo activation tree AstSenTree* m_deleteDomainp;// Delete this from tree AstSenTree* m_settleDomainp;// Initial activation tree OrderInputsVertex* m_inputsVxp; // Top level vertex all inputs point from OrderSettleVertex* m_settleVxp; // Top level vertex all settlement vertexes point from OrderLogicVertex* m_logicVxp; // Current statement being tracked, NULL=ignored AstTopScope* m_topScopep; // Current top scope being processed AstScope* m_scopetopp; // Scope under TOPSCOPE AstNodeModule* m_modp; // Current module AstScope* m_scopep; // Current scope being processed AstActive* m_activep; // Current activation block bool m_inSenTree; // Underneath AstSenItem; any varrefs are clocks bool m_inClocked; // Underneath clocked block bool m_inClkAss; // Underneath AstAssign bool m_inPre; // Underneath AstAssignPre bool m_inPost; // Underneath AstAssignPost OrderLogicVertex* m_activeSenVxp; // Sensitivity vertex deque m_orderUserps; // All created OrderUser's for later deletion. // STATE... for inside process OrderLoopId m_loopIdMax; // Maximum BeginLoop id number assigned vector m_pmlLoopEndps; // processInsLoop: End vertex for each color vector m_pomLoopMoveps;// processMoveLoop: Loops next nodes are under AstCFunc* m_pomNewFuncp; // Current function being created int m_pomNewStmts; // 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 vector m_unoptflatVars; // Vector of variables in UNOPTFLAT loop private: // STATS V3Double0 m_statCut[OrderVEdgeType::_ENUM_END]; // Count of each edge type cut // TYPES enum VarUsage { VU_NONE=0, VU_CON=1, VU_GEN=2 }; // METHODS static int debug() { static int level = -1; if (VL_UNLIKELY(level < 0)) level = v3Global.opt.debugSrcLevel(__FILE__); return level; } void iterateNewStmt(AstNode* nodep) { if (m_scopep) { UINFO(4," STMT "<sensesp()) nodep->v3fatalSrc("NULL"); // If inside combo logic, ignore the domain, we'll assign one based on interconnect AstSenTree* startDomainp = m_activep->sensesp(); if (startDomainp->hasCombo()) startDomainp=NULL; 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); nodep->iterateChildren(*this); m_logicVxp = NULL; } } OrderVarVertex* newVarUserVertex(AstVarScope* varscp, WhichVertex type, bool* createdp=NULL) { if (!varscp->user1p()) { OrderUser* newup = new OrderUser(); m_orderUserps.push_back(newup); varscp->user1p(newup); } OrderUser* up = (OrderUser*)(varscp->user1p()); OrderVarVertex* varVxp = up->newVarUserVertex(&m_graph, m_scopep, varscp, type, createdp); return varVxp; } V3GraphEdge* findEndEdge(V3GraphVertex* vertexp, AstNode* errnodep, OrderLoopEndVertex*& evertexpr) { // Given a vertex, find the end block corresponding to it // Every vertex should have a pointer to the end block (one hopes) for (V3GraphEdge* edgep = vertexp->outBeginp(); edgep; edgep = edgep->outNextp()) { if (OrderLoopEndVertex* evertexp = dynamic_cast(edgep->top())) { evertexpr = evertexp; return edgep; } } errnodep->v3fatalSrc("Loop-broken vertex doesn't have pointer to LoopEndVertex: "<lhsp()->castVarRef()) { if (varrefp->varp()->attrClocker() == AstVarAttrClocker::CLOCKER_YES) { return true; } } return false; } void process(); void processCircular(); typedef deque VertexVec; void processInputs(); void processInputsInIterate(OrderEitherVertex* vertexp, VertexVec& todoVec); void processInputsOutIterate(OrderEitherVertex* vertexp, VertexVec& todoVec); void processSensitive(); void processDomains(); void processDomainsIterate(OrderEitherVertex* vertexp); void processEdgeReport(); void processMove(); void processMoveClear(); void processMoveBuildGraph(); void processMoveBuildGraphIterate (OrderMoveVertex* moveVxp, V3GraphVertex* vertexp, int weightmin); void processMovePrepScopes(); void processMovePrepReady(); void processMoveReadyOne(OrderMoveVertex* vertexp); void processMoveDoneOne(OrderMoveVertex* vertexp); void processMoveOne(OrderMoveVertex* vertexp, OrderMoveDomScope* domScopep, int level); void processMoveLoopPush(OrderLoopBeginVertex* beginp); void processMoveLoopPop(OrderLoopBeginVertex* beginp); void processMoveLoopStmt(AstNode* newSubnodep); OrderLoopId processMoveLoopCurrent(); string cfuncName(AstNodeModule* modp, AstSenTree* domainp, AstScope* scopep, AstNode* forWhatp) { modp->user3Inc(); int funcnum = modp->user3(); string name = (domainp->hasCombo() ? "_combo" : (domainp->hasInitial() ? "_initial" : (domainp->hasSettle() ? "_settle" : (domainp->isMulti() ? "_multiclk" : "_sequent")))); name = name+"__"+scopep->nameDotless()+"__"+cvtToStr(funcnum); if (v3Global.opt.profileCFuncs()) { name += "__PROF__"+forWhatp->fileline()->profileFuncname(); } return name; } void nodeMarkCircular(OrderVarVertex* vertexp, OrderEdge* edgep) { AstVarScope* nodep = vertexp->varScp(); OrderLogicVertex* fromLVtxp = NULL; OrderLogicVertex* toLVtxp = NULL; if (edgep) { fromLVtxp = dynamic_cast(edgep->fromp()); toLVtxp = dynamic_cast(edgep->top()); } // if ((fromLVtxp && fromLVtxp->nodep()->castInitial()) || (toLVtxp && toLVtxp->nodep()->castInitial())) { // IEEE does not specify ordering between initial blocks, so we can do whatever we want // We especially do not want to evaluate multiple times, so do not mark the edge circular } else { nodep->circular(true); ++m_statCut[vertexp->type()]; if (edgep) ++m_statCut[edgep->type()]; // if (vertexp->isClock()) { // Seems obvious; no warning yet //nodep->v3warn(GENCLK,"Signal unoptimizable: Generated clock: "<prettyName()); } else if (nodep->varp()->isSigPublic()) { nodep->v3warn(UNOPT,"Signal unoptimizable: Feedback to public clock or circular logic: "<prettyName()); if (!nodep->fileline()->warnIsOff(V3ErrorCode::UNOPT)) { nodep->fileline()->modifyWarnOff(V3ErrorCode::UNOPT, true); // Complain just once // Give the user an example. bool tempWeight = (edgep && edgep->weight()==0); if (tempWeight) edgep->weight(1); // Else the below loop detect can't see the loop m_graph.reportLoops(&OrderEdge::followComboConnected, vertexp); // calls OrderGraph::loopsVertexCb if (tempWeight) edgep->weight(0); } } else { // We don't use UNOPT, as there are lots of V2 places where it was needed, that aren't any more // First v3warn not inside warnIsOff so we can see the suppressions with --debug nodep->v3warn(UNOPTFLAT,"Signal unoptimizable: Feedback to clock or circular logic: "<prettyName()); if (!nodep->fileline()->warnIsOff(V3ErrorCode::UNOPTFLAT)) { nodep->fileline()->modifyWarnOff(V3ErrorCode::UNOPTFLAT, true); // Complain just once // Give the user an example. bool tempWeight = (edgep && edgep->weight()==0); if (tempWeight) edgep->weight(1); // Else the below loop detect can't see the loop m_graph.reportLoops(&OrderEdge::followComboConnected, vertexp); // calls OrderGraph::loopsVertexCb if (tempWeight) edgep->weight(0); if (v3Global.opt.reportUnoptflat()) { // Report candidate variables for splitting reportLoopVars(vertexp); // Do a subgraph for the UNOPTFLAT loop OrderGraph loopGraph; m_graph.subtreeLoops(&OrderEdge::followComboConnected, vertexp, &loopGraph); loopGraph.dumpDotFilePrefixedAlways("unoptflat"); } } } } } //! 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(); m_unoptflatVars.clear(); reportLoopVarsIterate (vertexp, vertexp->color()); AstNode::user3ClearTree(); m_graph.userClearVertices(); // May be very large vector, so only report the "most important" // elements. Up to 10 of the widest cerr<varScp()->varp(); cerr<fileline()<<" "<prettyName()<width()<<", fanout " <fanout()<varScp()->varp(); cerr<fileline()<<" "<prettyName() <<", width "<width() <<", fanout "<fanout()<user()) return; // Already done vertexp->user(1); if (OrderVarStdVertex* vsvertexp = dynamic_cast(vertexp)) { // Only reporting on standard variable vertices AstVar* varp = vsvertexp->varScp()->varp(); if (!varp->user3()) { string name = varp->prettyName(); if ((varp->width() != 1) && (name.find("__Vdly") == string::npos) && (name.find("__Vcell") == string::npos)) { // Variable to report on and not yet done m_unoptflatVars.push_back(vsvertexp); } varp->user3Inc(); } } // Iterate through all the to and from vertices of the same color for (V3GraphEdge* edgep = vertexp->outBeginp(); edgep; edgep = edgep->outNextp()) { if (edgep->top()->color() == color) { reportLoopVarsIterate(edgep->top(), color); } } for (V3GraphEdge* edgep = vertexp->inBeginp(); edgep; edgep = edgep->inNextp()) { if (edgep->fromp()->color() == color) { reportLoopVarsIterate(edgep->fromp(), color); } } } // VISITORS virtual void visit(AstNetlist* nodep) { { AstUser4InUse m_inuser4; // Used only when building tree, so below nodep->iterateChildren(*this); } // We're finished, complete the topscopes if (m_topScopep) { process(); m_topScopep=NULL; } } virtual void visit(AstTopScope* nodep) { // Process the last thing we're finishing if (m_topScopep) nodep->v3fatalSrc("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 = NULL; m_topScopep = nodep; m_scopetopp = nodep->scopep(); // Find sentree's m_finder.main(m_topScopep); // 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(nodep->fileline(), combp); pushDeletep(combp); // Cleanup when done AstSenTree* settlep = new AstSenTree (nodep->fileline(), // Gets cloned() so ok if goes out of scope new AstSenItem(nodep->fileline(), AstSenItem::Settle())); m_settleDomainp = m_finder.getSenTree(nodep->fileline(), settlep); pushDeletep(settlep); // 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 = "<iterateChildren(*this); // Done topscope, erase extra user information // user1p passed to next process() operation AstNode::user3ClearTree(); AstNode::user4ClearTree(); } virtual void visit(AstNodeModule* nodep) { m_modp = nodep; nodep->iterateChildren(*this); m_modp = NULL; } virtual void visit(AstScope* nodep) { UINFO(4," SCOPE "<user1p(m_modp); // Iterate nodep->iterateChildren(*this); m_scopep = NULL; } virtual void visit(AstActive* nodep) { // Create required activation blocks and add to module UINFO(4," ACTIVE "<hasClocked(); // Grab the sensitivity list if (nodep->sensesStorep()) nodep->v3fatalSrc("Senses should have been activeTop'ed to be global!"); nodep->sensesp()->accept(*this); // Collect statements under it nodep->iterateChildren(*this); m_activep = NULL; } virtual void visit(AstVarScope* nodep) { // Create links to all input signals if (m_modp->isTop() && nodep->varp()->isInput()) { OrderVarVertex* varVxp = newVarUserVertex(nodep, WV_STD); new OrderEdge(&m_graph, m_inputsVxp, varVxp, WEIGHT_INPUT); } } virtual void visit(AstNodeVarRef* nodep) { if (m_scopep) { AstVarScope* varscp = nodep->varScopep(); if (!varscp) nodep->v3fatalSrc("Var didn't get varscoped in V3Scope.cpp\n"); if (m_inSenTree) { // Add CLOCK dependency... This is a root of the tree we'll trace if (nodep->lvalue()) nodep->v3fatalSrc("How can a sensitivity be setting a var?\n"); OrderVarVertex* varVxp = newVarUserVertex(varscp, WV_STD); varVxp->isClock(true); new OrderEdge(&m_graph, varVxp, m_activeSenVxp, WEIGHT_MEDIUM); } else { if (!m_logicVxp) nodep->v3fatalSrc("Var ref not under a logic block\n"); // 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->lvalue()) { gen = !(varscp->user4() & VU_GEN); } else { 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 worring about it for us con = false; } if (m_inClkAss && (varscp->varp()->attrClocker()) != AstVarAttrClocker::CLOCKER_YES) { con = false; UINFO(4, "nodep used as clock_enable "<nodep()<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() == AstVarAttrClocker::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(AstSenTree* nodep) { // Having a node derived from the sentree isn't required for // correctness, it mearly 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); } nodep->iterateChildren(*this); } m_inSenTree = false; } } virtual void visit(AstAlways* nodep) { iterateNewStmt(nodep); } virtual void visit(AstAlwaysPost* nodep) { m_inPost = true; iterateNewStmt(nodep); m_inPost = false; } virtual void visit(AstAlwaysPublic* nodep) { iterateNewStmt(nodep); } virtual void visit(AstAssignAlias* nodep) { iterateNewStmt(nodep); } virtual void visit(AstAssignW* nodep) { OrderClkAssVisitor visitor(nodep); m_inClkAss = visitor.isClkAss(); iterateNewStmt(nodep); m_inClkAss = false; } virtual void visit(AstAssignPre* nodep) { OrderClkAssVisitor visitor(nodep); m_inClkAss = visitor.isClkAss(); m_inPre = true; iterateNewStmt(nodep); m_inPre = false; m_inClkAss = false; } virtual void visit(AstAssignPost* nodep) { OrderClkAssVisitor visitor(nodep); m_inClkAss = visitor.isClkAss(); m_inPost = true; iterateNewStmt(nodep); m_inPost = false; m_inClkAss = false; } virtual void visit(AstCoverToggle* nodep) { iterateNewStmt(nodep); } virtual void visit(AstInitial* nodep) { // 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*) { // Ignore for now // We should detect what variables are set in the function, and make // settlement code for them, then set a global flag, so we call "settle" // on the next evaluation loop. } //-------------------- // Default virtual void visit(AstNode* nodep) { nodep->iterateChildren(*this); } public: // CONSTUCTORS OrderVisitor() { m_topScopep = NULL; m_scopetopp = NULL; m_modp = NULL; m_scopep = NULL; m_activep = NULL; m_inSenTree = false; m_inClocked = false; m_inClkAss = false; m_inPre = m_inPost = false; m_comboDomainp = NULL; m_deleteDomainp = NULL; m_settleDomainp = NULL; m_settleVxp = NULL; m_inputsVxp = NULL; m_activeSenVxp = NULL; m_logicVxp = NULL; m_pomNewFuncp = NULL; m_loopIdMax = LOOPID_FIRST; m_pomNewStmts = 0; if (debug()) m_graph.debug(5); // 3 is default if global debug; we want acyc debugging } virtual ~OrderVisitor() { // Stats for (int type=0; type::iterator it=m_orderUserps.begin(); it!=m_orderUserps.end(); ++it) { delete *it; } m_graph.debug(V3Error::debugDefault()); } void main(AstNode* nodep) { nodep->accept(*this); } }; //###################################################################### // Clock propagation void OrderVisitor::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 while (!todoVec.empty()) { OrderEitherVertex* vertexp = todoVec.back(); todoVec.pop_back(); processInputsOutIterate(vertexp, todoVec); } } void OrderVisitor::processInputsInIterate(OrderEitherVertex* vertexp, VertexVec& todoVec) { // Propagate PrimaryIn through simple assignments if (vertexp->user()) return; // Already processed if (0 && debug()>=9) { UINFO(9," InIIter "<(vertexp)) { vvertexp->nodep()->dumpTree(cout,"- TT: "); } } vertexp->user(1); // Processing // First handle all inputs to this vertex, in most cases they'll be already processed earlier // Also, determine if this vertex is an input int inonly = 1; // 0=no, 1=maybe, 2=yes until a no for (V3GraphEdge* edgep = vertexp->inBeginp(); edgep; edgep=edgep->inNextp()) { OrderEitherVertex* frVertexp = (OrderEitherVertex*)edgep->fromp(); processInputsInIterate(frVertexp, todoVec); if (frVertexp->isFromInput()) { if (inonly==1) inonly = 2; } else if (dynamic_cast(frVertexp)) { // Ignore post assignments, just for ordering } else { //UINFO(9," InItStopDueTo "<user()<2) { // Set it. Note may have already been set earlier, too UINFO(9," Input reassignment: "<isFromInput(true); vertexp->user(2); // 2 means on list // Can't work on out-edges of a node we know is an input immediately, // as it might visit other nodes before their input state is resolved. // So push to list and work on it later when all in-edges known resolved todoVec.push_back(vertexp); } //UINFO(9," InIdone "<user()==3) return; // Already out processed //UINFO(9," InOIter "<isFromInput()) v3fatalSrc("processInputsOutIterate only for input marked vertexes"); vertexp->user(3); // out-edges processed { // Propagate PrimaryIn through simple assignments, followint target of vertex for (V3GraphEdge* edgep = vertexp->outBeginp(); edgep; edgep=edgep->outNextp()) { OrderEitherVertex* toVertexp = (OrderEitherVertex*)edgep->top(); if (OrderVarStdVertex* vvertexp = dynamic_cast(toVertexp)) { processInputsInIterate(vvertexp, todoVec); } if (OrderLogicVertex* vvertexp = dynamic_cast(toVertexp)) { if (vvertexp->nodep()->castNodeAssign()) { processInputsInIterate(vvertexp, todoVec); } } } } } //###################################################################### // Circular detection void OrderVisitor::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()) { if (OrderVarStdVertex* vvertexp = dynamic_cast(itp)) { if (vvertexp->isClock() && !vvertexp->isFromInput()) { // If a clock is generated internally, we need to do another // loop through the entire evaluation. This fixes races; see // t_clk_dpulse test. // // This all seems to hinge on how the clock is generated. If // it is generated by delayed assignment, we need the loop. If // it is combinatorial, we do not (and indeed it will break // other tests such as t_gated_clk_1. if (!v3Global.opt.orderClockDly()) { UINFO(5,"Circular Clock, no-order-clock-delay "<isDelayed()) { UINFO(5,"Circular Clock, delayed "<outBeginp(); edgep; edgep=edgep->outNextp()) { if (edgep->weight()==0) { // was cut OrderEdge* oedgep = dynamic_cast(edgep); if (!oedgep) vvertexp->varScp()->v3fatalSrc("Cuttable edge not of proper type"); UINFO(6," CutCircularO: "<name()<inBeginp(); edgep; edgep = edgep->inNextp()) { if (edgep->weight()==0) { // was cut OrderEdge* oedgep = dynamic_cast(edgep); if (!oedgep) vvertexp->varScp()->v3fatalSrc("Cuttable edge not of proper type"); UINFO(6," CutCircularI: "<name()<verticesNextp()) { if (OrderVarStdVertex* vvertexp = dynamic_cast(itp)) { if (vvertexp->varScp()->varp()->isInput()) { //UINFO(0," scsen "<outBeginp(); edgep; edgep=edgep->outNextp()) { if (OrderEitherVertex* toVertexp = dynamic_cast(edgep->top())) { if (edgep->weight() && toVertexp->domainp()) { //UINFO(0," "<domainp()<domainp()->hasCombo()) { vvertexp->varScp()->varp()->scSensitive(true); } } } } } } } } void OrderVisitor::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\n"); processDomainsIterate(vertexp); } } void OrderVisitor::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. // Combo logic may be pushed into a seq domain if all its inputs are the same domain, // else, if all inputs are from flops, it's end-of-sequential code // else, it's full combo code if (!vertexp->inLoop()) vertexp->inLoop(LOOPID_NOTLOOPED); if (vertexp->domainp()) return; // Already processed, or sequential logic UINFO(5," pdi: "<(vertexp); AstSenTree* domainp = NULL; UASSERT(m_comboDomainp, "not preset"); if (vvertexp && vvertexp->varScp()->varp()->isInput()) { domainp = m_comboDomainp; } if (vvertexp && vvertexp->varScp()->isCircular()) { domainp = m_comboDomainp; } if (!domainp) { for (V3GraphEdge* edgep = vertexp->inBeginp(); edgep; edgep = edgep->inNextp()) { OrderEitherVertex* fromVertexp = (OrderEitherVertex*)edgep->fromp(); if (edgep->weight() && fromVertexp->domainMatters() ) { UINFO(9," from d="<<(void*)fromVertexp->domainp()<<" "<hasSettle() // or, we can ignore being in the settle domain || domainp->hasInitial()) { domainp = fromVertexp->domainp(); } else if (domainp->hasCombo()) { // Once in combo, keep in combo; already as severe as we can get } else if (fromVertexp->domainp()->hasCombo()) { // Any combo input means this vertex must remain combo domainp = m_comboDomainp; } else if (fromVertexp->domainp()->hasSettle() || fromVertexp->domainp()->hasInitial()) { // Ignore that we have a constant (initial) input } else if (domainp != fromVertexp->domainp()) { // Make a domain that merges the two domains bool ddebug = debug()>=9; if (ddebug) { cout<addSensesp(newtree2p); newtree2p=NULL; // Below edit may replace it V3Const::constifyExpensiveEdit(newtreep); // Remove duplicates newtreep->multi(true); // Comment that it was made from 2 clock domains domainp = m_finder.getSenTree(domainp->fileline(), newtreep); if (ddebug) { UINFO(0," dnew ="<dumpTree(cout); UINFO(0," find ="<dumpTree(cout); cout<deleteTree(); VL_DANGLING(newtreep); } } } // next input edgep // Default the domain // This is a node which has only constant inputs, or is otherwise indeterminate. // It should have already been copied into the settle domain. Presumably it has // inputs which we never trigger, or nothing it's sensitive to, so we can rip it out. if (!domainp && vertexp->scopep()) { domainp = m_deleteDomainp; } } // vertexp->domainp(domainp); if (vertexp->domainp()) { UINFO(5," done d="<<(void*)vertexp->domainp() <<(vertexp->domainp()->hasCombo()?" [COMB]":"") <<(vertexp->domainp()->isMulti()?" [MULT]":"") <<" "< logp (V3File::new_ofstream(filename)); if (logp->fail()) v3fatalSrc("Can't write "< report; for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp=itp->verticesNextp()) { if (OrderVarVertex* vvertexp = dynamic_cast(itp)) { string name (vvertexp->varScp()->prettyName()); if (dynamic_cast(itp)) name += " {PRE}"; else if (dynamic_cast(itp)) name += " {POST}"; else if (dynamic_cast(itp)) name += " {PORD}"; else if (dynamic_cast(itp)) name += " {STL}"; ostringstream os; os.setf(ios::left); os<<" "<<(void*)(vvertexp->varScp())<<" "<domainp(); if (sentreep) V3EmitV::verilogForTree(sentreep, os); report.push_back(os.str()); } } *logp<<"Signals and their clock domains:"<::iterator it=report.begin(); it!=report.end(); ++it) { *logp<<(*it)<verticesNextp()) { if (OrderLogicVertex* lvertexp = dynamic_cast(itp)) { OrderMoveVertex* moveVxp = new OrderMoveVertex(&m_pomGraph, lvertexp); moveVxp->m_pomWaitingE.pushBack(m_pomWaiting, moveVxp); // Cross link so we can find it later lvertexp->moveVxp(moveVxp); } } // Build edges between logic vertices for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp=itp->verticesNextp()) { if (OrderLogicVertex* lvertexp = dynamic_cast(itp)) { OrderMoveVertex* moveVxp = lvertexp->moveVxp(); processMoveBuildGraphIterate(moveVxp, lvertexp, 0); } } } void OrderVisitor::processMoveBuildGraphIterate (OrderMoveVertex* moveVxp, V3GraphVertex* vertexp, int weightmin) { // Search forward from given logic vertex, making new edges based on moveVxp for (V3GraphEdge* edgep = vertexp->outBeginp(); edgep; edgep=edgep->outNextp()) { if (edgep->weight()!=0) { // was cut int weight = weightmin; if (weight==0 || weight>edgep->weight()) weight=edgep->weight(); if (OrderLogicVertex* toLVertexp = dynamic_cast(edgep->top())) { // Path from vertexp to a logic vertex; new edge // Note we use the last edge's weight, not some function of multiple edges new OrderEdge(&m_pomGraph, moveVxp, toLVertexp->moveVxp(), weight); } else { // Keep hunting forward for a logic node processMoveBuildGraphIterate(moveVxp, edgep->top(), weight); } } } } //###################################################################### // Moving void OrderVisitor::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) // Move any node with all inputs ready to a "ready" graph mapped by domain and then scope // While waiting graph ! empty (and also known: something in ready graph) // For all scopes in domain of top ready vertex // For all vertexes in domain&scope of top ready vertex // Make ordered activation block for this module // Add that new activation to the list of calls to make. // Move logic to ordered active // Any children that have all inputs now ready move from waiting->ready graph // (This may add nodes the for loop directly above needs to detext) processMovePrepScopes(); processMovePrepReady(); // New domain... another loop UINFO(5," MoveIterate\n"); while (!m_pomReadyDomScope.empty()) { // Start with top node on ready list's domain & scope OrderMoveDomScope* domScopep = m_pomReadyDomScope.begin(); OrderMoveVertex* topVertexp = domScopep->readyVertices().begin(); // lintok-begin-on-ref UASSERT(topVertexp, "domScope on ready list without any nodes ready under it"); // Work on all scopes ready inside this domain while (domScopep) { UINFO(6," MoveDomain l="<domainp()<readyVertices().begin()) { // lintok-begin-on-ref processMoveOne(vertexp, domScopep, 1); } // Done with scope/domain pair, pick new scope under same domain, or NULL if none left OrderMoveDomScope* domScopeNextp = NULL; for (OrderMoveDomScope* huntp = m_pomReadyDomScope.begin(); huntp; huntp = huntp->readyDomScopeNextp()) { if (huntp->domainp() == domScopep->domainp()) { domScopeNextp = huntp; break; } } domScopep = domScopeNextp; } } UASSERT (m_pomWaiting.empty(), "Didn't converge; nodes waiting, none ready, perhaps some input activations lost."); // Cleanup memory processMoveClear(); } void OrderVisitor::processMovePrepScopes() { UINFO(5," MovePrepScopes\n"); // Create a OrderMoveDomScope every domain/scope pairing for (OrderMoveVertex* vertexp = m_pomWaiting.begin(); vertexp; vertexp=vertexp->pomWaitingNextp()) { AstSenTree* domainp = vertexp->logicp()->domainp(); AstScope* scopep = vertexp->logicp()->scopep(); OrderLoopId inLoop = vertexp->logicp()->inLoop(); // Create the dom pairing for later lookup OrderMoveDomScope* domScopep = OrderMoveDomScope::findCreate(inLoop, domainp, scopep); vertexp->domScopep(domScopep); } } void OrderVisitor::processMovePrepReady() { // Make list of ready nodes UINFO(5," MovePrepReady\n"); for (OrderMoveVertex* vertexp = m_pomWaiting.begin(); vertexp; ) { OrderMoveVertex* nextp = vertexp->pomWaitingNextp(); if (vertexp->isWait() && vertexp->inEmpty()) { processMoveReadyOne(vertexp); } vertexp = nextp; } } void OrderVisitor::processMoveReadyOne(OrderMoveVertex* vertexp) { // Recursive! // Move one node from waiting to ready list vertexp->setReady(); // Remove node from waiting list vertexp->m_pomWaitingE.unlink(m_pomWaiting, vertexp); // Add to ready list (indexed by domain and scope) vertexp->m_readyVerticesE.pushBack(vertexp->domScopep()->m_readyVertices, vertexp); vertexp->domScopep()->ready (this); } void OrderVisitor::processMoveDoneOne(OrderMoveVertex* vertexp) { // Move one node from ready to completion vertexp->setMoved(); // Unlink from ready lists vertexp->m_readyVerticesE.unlink(vertexp->domScopep()->m_readyVertices, vertexp); vertexp->domScopep()->movedVertex (this, vertexp); // Don't need to add it to another list, as we're done with it // Mark our outputs as one closer to ready for (V3GraphEdge* edgep = vertexp->outBeginp(), *nextp; edgep; edgep=nextp) { nextp = edgep->outNextp(); OrderMoveVertex* toVertexp = (OrderMoveVertex*)edgep->top(); UINFO(9," Clear to "<<(toVertexp->inEmpty()?"[EMP] ":" ") <unlinkDelete(); VL_DANGLING(edgep); if (toVertexp->inEmpty()) { // If destination node now has all inputs resolved; recurse to move that vertex // This is thus depth first (before width) which keeps the resulting executable's d-cache happy. processMoveReadyOne(toVertexp); } } } void OrderVisitor::processMoveOne(OrderMoveVertex* vertexp, OrderMoveDomScope* domScopep, int level) { UASSERT(vertexp->domScopep() == domScopep, "Domain mismatch; list misbuilt?\n"); OrderLogicVertex* lvertexp = vertexp->logicp(); AstScope* scopep = lvertexp->scopep(); UINFO(5," POSmove l"<domainp(); AstNode* nodep = lvertexp->nodep(); AstNodeModule* modp = scopep->user1p()->castNodeModule(); UASSERT(modp,"NULL"); // Stashed by visitor func if (nodep->castUntilStable()) { nodep->v3fatalSrc("Not implemented"); } else if (nodep->castSenTree()) { // Just ignore sensitivities, we'll deal with them when we move statements that need them } else { // Normal logic // Make or borrow a CFunc to contain the new statements if (v3Global.opt.profileCFuncs() || (v3Global.opt.outputSplitCFuncs() && v3Global.opt.outputSplitCFuncs() < m_pomNewStmts)) { // Put every statement into a unique function to ease profiling or reduce function size m_pomNewFuncp = NULL; } if (!m_pomNewFuncp && domainp != m_deleteDomainp) { string name = cfuncName(modp, domainp, scopep, nodep); m_pomNewFuncp = new AstCFunc(nodep->fileline(), name, scopep); m_pomNewFuncp->argTypes(EmitCBaseVisitor::symClassVar()); m_pomNewFuncp->symProlog(true); m_pomNewStmts = 0; if (domainp->hasInitial() || domainp->hasSettle()) m_pomNewFuncp->slow(true); scopep->addActivep(m_pomNewFuncp); // Where will we be adding the call? AstActive* callunderp = new AstActive(nodep->fileline(), name, domainp); processMoveLoopStmt(callunderp); // Add a top call to it AstCCall* callp = new AstCCall(nodep->fileline(), m_pomNewFuncp); callp->argTypes("vlSymsp"); callunderp->addStmtsp(callp); UINFO(6," New "<unlinkFrBack(); if (domainp == m_deleteDomainp) { UINFO(4," Ordering deleting pre-settled "<addStmtsp(nodep); if (v3Global.opt.outputSplitCFuncs()) { // Add in the number of nodes we're adding EmitCBaseCounterVisitor visitor(nodep); m_pomNewStmts += visitor.count(); } } } processMoveDoneOne (vertexp); } inline void OrderVisitor::processMoveLoopPush(OrderLoopBeginVertex* beginp) { UINFO(6," LoopPush "<nodep()->v3fatalSrc("processMoveLoopPop with no push'ed loops"); OrderLoopBeginVertex* topBeginp = m_pomLoopMoveps.back(); if (topBeginp != beginp) beginp->nodep()->v3fatalSrc("processMoveLoopPop had different vertex then one expected, got="<m_pomReadyDomScope, this); } } inline void OrderMoveDomScope::movedVertex(OrderVisitor* ovp, OrderMoveVertex* vertexp) { // Mark one vertex as finished, remove from ready list if done UASSERT(m_onReadyList, "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); } } //###################################################################### // Top processing void OrderVisitor::process() { // Dump data m_graph.dumpDotFilePrefixed("orderg_pre"); // Break cycles. Each strongly connected subgraph (including cutable // edges) will have its own color, and corresponds to a loop in the // original graph. However the new graph will be acyclic (the removed // edges are actually still there, just with weight 0). UINFO(2," Acyclic & Order...\n"); m_graph.acyclic(&V3GraphEdge::followAlwaysTrue); m_graph.dumpDotFilePrefixed("orderg_acyc"); // Assign ranks so we know what to follow // Then, sort vertices and edges by that ordering m_graph.order(); m_graph.dumpDotFilePrefixed("orderg_order"); // This finds everything that can be traced from an input (which by // definition are the source clocks). After this any vertex which was // traced has isFromInput() true. UINFO(2," Process Clocks...\n"); processInputs(); // must be before processCircular UINFO(2," Process Circulars...\n"); processCircular(); // must be before processDomains // Assign logic verticesto new domains UINFO(2," Domains...\n"); processDomains(); m_graph.dumpDotFilePrefixed("orderg_domain"); if (debug() && v3Global.opt.dumpTree()) processEdgeReport(); UINFO(2," Construct Move Graph...\n"); processMoveBuildGraph(); if (debug()>=4) m_pomGraph.dumpDotFilePrefixed("ordermv_start"); // Different prefix (ordermv) as it's not the same graph m_pomGraph.removeRedundantEdges(&V3GraphEdge::followAlwaysTrue); m_pomGraph.dumpDotFilePrefixed("ordermv_simpl"); UINFO(2," Move...\n"); processMove(); // Any SC inputs feeding a combo domain must be marked, so we can make them sc_sensitive UINFO(2," Sensitive...\n"); processSensitive(); // must be after processDomains // Dump data m_graph.dumpDotFilePrefixed("orderg_done"); if (0 && debug()) { string dfilename = v3Global.opt.makeDir()+"/"+v3Global.opt.prefix()+"_INT_order.tree"; const VL_UNIQUE_PTR logp (V3File::new_ofstream(dfilename)); if (logp->fail()) v3fatalSrc("Can't write "<= 3); }