// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Break always into sensitivity active domains // // Code available from: https://verilator.org // //************************************************************************* // // Copyright 2003-2019 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. // //************************************************************************* // V3Active's Transformations: // // Note this can be called multiple times. // Create a IACTIVE(initial), SACTIVE(combo) // ALWAYS: Remove any-edges from sense list // If no POS/NEG in senselist, Fold into SACTIVE(combo) // Else fold into SACTIVE(sequent). // OPTIMIZE: When support async clocks, fold into that active if possible // INITIAL: Move into IACTIVE // WIRE: Move into SACTIVE(combo) // //************************************************************************* #include "config_build.h" #include "verilatedos.h" #include "V3Global.h" #include "V3Active.h" #include "V3Ast.h" #include "V3EmitCBase.h" #include "V3Const.h" #include "V3SenTree.h" // for SenTreeSet #include VL_INCLUDE_UNORDERED_MAP //***** See below for main transformation engine //###################################################################### // Collect existing active names class ActiveBaseVisitor : public AstNVisitor { protected: VL_DEBUG_FUNC; // Declare debug() }; class ActiveNamer : public ActiveBaseVisitor { private: typedef std::map ActiveNameMap; // STATE AstScope* m_scopep; // Current scope to add statement to AstActive* m_iActivep; // For current scope, the IActive we're building AstActive* m_cActivep; // For current scope, the SActive(combo) we're building SenTreeSet m_activeSens; // Sen lists for each active we've made typedef vl_unordered_map ActiveMap; ActiveMap m_activeMap; // Map sentree to active, for folding. // METHODS void addActive(AstActive* nodep) { UASSERT_OBJ(m_scopep, nodep, "NULL scope"); m_scopep->addActivep(nodep); } // VISITORS virtual void visit(AstScope* nodep) { m_scopep = nodep; m_iActivep = NULL; m_cActivep = NULL; m_activeSens.clear(); m_activeMap.clear(); iterateChildren(nodep); // Don't clear scopep, the namer persists beyond this visit } virtual void visit(AstSenTree* nodep) { // Simplify sensitivity list V3Const::constifyExpensiveEdit(nodep); VL_DANGLING(nodep); } // Empty visitors, speed things up virtual void visit(AstNodeStmt* nodep) { } //-------------------- // Default virtual void visit(AstNode* nodep) { // Default: Just iterate iterateChildren(nodep); } // METHODS public: AstScope* scopep() { return m_scopep; } AstActive* getCActive(FileLine* fl) { if (!m_cActivep) { m_cActivep = new AstActive( fl, "combo", new AstSenTree(fl, new AstSenItem(fl, AstSenItem::Combo()))); m_cActivep->sensesStorep(m_cActivep->sensesp()); addActive(m_cActivep); } return m_cActivep; } AstActive* getIActive(FileLine* fl) { if (!m_iActivep) { m_iActivep = new AstActive( fl, "initial", new AstSenTree(fl, new AstSenItem(fl, AstSenItem::Initial()))); m_iActivep->sensesStorep(m_iActivep->sensesp()); addActive(m_iActivep); } return m_iActivep; } AstActive* getActive(FileLine* fl, AstSenTree* sensesp) { // Return a sentree in this scope that matches given sense list. AstActive* activep = NULL; AstSenTree* activeSenp = m_activeSens.find(sensesp); if (activeSenp) { ActiveMap::iterator it = m_activeMap.find(activeSenp); UASSERT(it != m_activeMap.end(), "Corrupt active map"); activep = it->second; } // Not found, form a new one if (!activep) { AstSenTree* newsenp = sensesp->cloneTree(false); activep = new AstActive(fl, "sequent", newsenp); activep->sensesStorep(activep->sensesp()); UINFO(8," New ACTIVE "<v3warn(INITIALDLY, "Delayed assignments (<=) in initial or final block\n" <warnMore()<<"... Suggest blocking assignments (=)"); } else if (m_check == CT_LATCH) { // Suppress. Shouldn't matter that the interior of the latch races } else { nodep->v3warn(COMBDLY, "Delayed assignments (<=) in non-clocked" " (non flop or latch) block\n" <warnMore()<<"... Suggest blocking assignments (=)"); } AstNode* newp = new AstAssign(nodep->fileline(), nodep->lhsp()->unlinkFrBack(), nodep->rhsp()->unlinkFrBack()); nodep->replaceWith(newp); nodep->deleteTree(); VL_DANGLING(nodep); } } virtual void visit(AstAssign* nodep) { if (m_check == CT_SEQ) { AstNode* las = m_assignp; m_assignp = nodep; iterateAndNextNull(nodep->lhsp()); m_assignp = las; } } virtual void visit(AstVarRef* nodep) { AstVar* varp = nodep->varp(); if (m_check == CT_SEQ && m_assignp && !varp->isUsedLoopIdx() // Ignore loop indices && !varp->isTemp()) { // Allow turning off warnings on the always, or the variable also if (!m_alwaysp->fileline()->warnIsOff(V3ErrorCode::BLKSEQ) && !m_assignp->fileline()->warnIsOff(V3ErrorCode::BLKSEQ) && !varp->fileline()->warnIsOff(V3ErrorCode::BLKSEQ)) { m_assignp->v3warn(BLKSEQ, "Blocking assignments (=) in sequential (flop or latch) block\n" <warnMore()<<"... Suggest delayed assignments (<=)"); m_alwaysp->fileline()->modifyWarnOff(V3ErrorCode::BLKSEQ, true); // Complain just once for the entire always varp->fileline()->modifyWarnOff(V3ErrorCode::BLKSEQ, true); } } } //-------------------- virtual void visit(AstNode* nodep) { iterateChildren(nodep); } public: // CONSTRUCTORS ActiveDlyVisitor(AstNode* nodep, CheckType check) { m_alwaysp = nodep; m_check = check; m_assignp = NULL; iterate(nodep); } virtual ~ActiveDlyVisitor() {} }; //###################################################################### // Active class functions class ActiveVisitor : public ActiveBaseVisitor { private: // NODE STATE // Each call to V3Const::constify // AstNode::user4() Used by V3Const::constify, called below // STATE ActiveNamer m_namer; // Tracking of active names AstCFunc* m_scopeFinalp; // Final function for this scope bool m_itemCombo; // Found a SenItem combo bool m_itemSequent; // Found a SenItem sequential // VISITORS virtual void visit(AstScope* nodep) { // Create required actives and add to scope UINFO(4," SCOPE "<fileline()); nodep->unlinkFrBack(); wantactivep->addStmtsp(nodep); } virtual void visit(AstAssignAlias* nodep) { // Relink to CACTIVE, unless already under it UINFO(4," ASSIGNW "<fileline()); nodep->unlinkFrBack(); wantactivep->addStmtsp(nodep); } virtual void visit(AstAssignW* nodep) { // Relink to CACTIVE, unless already under it UINFO(4," ASSIGNW "<fileline()); nodep->unlinkFrBack(); wantactivep->addStmtsp(nodep); } virtual void visit(AstCoverToggle* nodep) { // Relink to CACTIVE, unless already under it UINFO(4," COVERTOGGLE "<fileline()); nodep->unlinkFrBack(); wantactivep->addStmtsp(nodep); } virtual void visit(AstFinal* nodep) { // Relink to CFUNC for the final UINFO(4," FINAL "<bodysp()) { // Empty, Kill it. nodep->unlinkFrBack()->deleteTree(); VL_DANGLING(nodep); return; } ActiveDlyVisitor dlyvisitor (nodep, ActiveDlyVisitor::CT_INITIAL); if (!m_scopeFinalp) { m_scopeFinalp = new AstCFunc( nodep->fileline(), "_final_"+m_namer.scopep()->nameDotless(), m_namer.scopep()); m_scopeFinalp->argTypes(EmitCBaseVisitor::symClassVar()); m_scopeFinalp->addInitsp( new AstCStmt(nodep->fileline(), EmitCBaseVisitor::symTopAssign()+"\n")); m_scopeFinalp->dontCombine(true); m_scopeFinalp->formCallTree(true); m_scopeFinalp->slow(true); m_namer.scopep()->addActivep(m_scopeFinalp); } nodep->unlinkFrBack(); m_scopeFinalp->addStmtsp(new AstComment(nodep->fileline(), nodep->typeName(), true)); m_scopeFinalp->addStmtsp(nodep->bodysp()->unlinkFrBackWithNext()); nodep->deleteTree(); VL_DANGLING(nodep); } // METHODS void visitAlways(AstNode* nodep, AstSenTree* oldsensesp, VAlwaysKwd kwd) { // Move always to appropriate ACTIVE based on its sense list if (oldsensesp && oldsensesp->sensesp() && VN_IS(oldsensesp->sensesp(), SenItem) && VN_CAST(oldsensesp->sensesp(), SenItem)->isNever()) { // Never executing. Kill it. UASSERT_OBJ(!oldsensesp->sensesp()->nextp(), nodep, "Never senitem should be alone, else the never should be eliminated."); nodep->unlinkFrBack()->deleteTree(); VL_DANGLING(nodep); return; } // Read sensitivities m_itemCombo = false; m_itemSequent = false; iterateAndNextNull(oldsensesp); bool combo = m_itemCombo; bool sequent = m_itemSequent; if (!combo && !sequent) combo=true; // If no list, Verilog 2000: always @ (*) if (combo && sequent) { if (!v3Global.opt.bboxUnsup()) { nodep->v3error("Unsupported: Mixed edge (pos/negedge) and activity (no edge) sensitive activity list"); } sequent = false; } AstActive* wantactivep = NULL; if (combo && !sequent) { // Combo: Relink to ACTIVE(combo) wantactivep = m_namer.getCActive(nodep->fileline()); } else { // Sequential: Build a ACTIVE(name) // OPTIMIZE: We could substitute a constant for things in the sense list, for example // always (posedge RESET) { if (RESET).... } we know RESET is true. // Summarize a long list of combo inputs as just "combo" #ifndef __COVERITY__ // Else dead code on next line. if (combo) oldsensesp->addSensesp (new AstSenItem(nodep->fileline(), AstSenItem::Combo())); #endif wantactivep = m_namer.getActive(nodep->fileline(), oldsensesp); } // Delete sensitivity list if (oldsensesp) { oldsensesp->unlinkFrBackWithNext()->deleteTree(); VL_DANGLING(oldsensesp); } // Move node to new active nodep->unlinkFrBack(); wantactivep->addStmtsp(nodep); // Warn and/or convert any delayed assignments if (combo && !sequent) { if (kwd == VAlwaysKwd::ALWAYS_LATCH) { ActiveDlyVisitor dlyvisitor (nodep, ActiveDlyVisitor::CT_LATCH); } else { ActiveDlyVisitor dlyvisitor (nodep, ActiveDlyVisitor::CT_COMBO); } } else if (!combo && sequent) { ActiveDlyVisitor dlyvisitor (nodep, ActiveDlyVisitor::CT_SEQ); } } virtual void visit(AstAlways* nodep) { // Move always to appropriate ACTIVE based on its sense list UINFO(4," ALW "<=9) nodep->dumpTree(cout, " Alw: "); if (!nodep->bodysp()) { // Empty always. Kill it. nodep->unlinkFrBack()->deleteTree(); VL_DANGLING(nodep); return; } visitAlways(nodep, nodep->sensesp(), nodep->keyword()); } virtual void visit(AstAlwaysPublic* nodep) { // Move always to appropriate ACTIVE based on its sense list UINFO(4," ALWPub "<=9) nodep->dumpTree(cout, " Alw: "); visitAlways(nodep, nodep->sensesp(), VAlwaysKwd::ALWAYS); } virtual void visit(AstSenGate* nodep) { AstSenItem* subitemp = nodep->sensesp(); UASSERT_OBJ(subitemp->edgeType() == VEdgeType::ET_ANYEDGE || subitemp->edgeType() == VEdgeType::ET_POSEDGE || subitemp->edgeType() == VEdgeType::ET_NEGEDGE, nodep, "Strange activity type under SenGate"); iterateChildren(nodep); } virtual void visit(AstSenItem* nodep) { if (nodep->edgeType() == VEdgeType::ET_ANYEDGE) { m_itemCombo = true; // Delete the sensitivity // We'll add it as a generic COMBO SenItem in a moment. nodep->unlinkFrBack()->deleteTree(); VL_DANGLING(nodep); } else if (nodep->varrefp()) { // V3LinkResolve should have cleaned most of these up if (!nodep->varrefp()->width1()) { nodep->v3error("Unsupported: Non-single bit wide signal pos/negedge sensitivity: " <varrefp()->prettyNameQ()); } m_itemSequent = true; nodep->varrefp()->varp()->usedClock(true); } } // Empty visitors, speed things up virtual void visit(AstNodeMath* nodep) {} virtual void visit(AstVarScope* nodep) {} //-------------------- virtual void visit(AstNode* nodep) { iterateChildren(nodep); } public: // CONSTRUCTORS explicit ActiveVisitor(AstNetlist* nodep) { m_scopeFinalp = NULL; m_itemCombo = false; m_itemSequent = false; iterate(nodep); } virtual ~ActiveVisitor() {} }; //###################################################################### // Active class functions void V3Active::activeAll(AstNetlist* nodep) { UINFO(2,__FUNCTION__<<": "<= 3); }