// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Check for unused/undriven signals // // Code available from: http://www.veripool.org/verilator // //************************************************************************* // // Copyright 2004-2018 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. // //************************************************************************* // V3Undriven's Transformations: // // Netlist: // Make vector for all variables // SEL(VARREF(...))) mark only some bits as used/driven // else VARREF(...) mark all bits as used/driven // Report unused/undriven nets // //************************************************************************* #include "config_build.h" #include "verilatedos.h" #include #include #include #include #include #include "V3Global.h" #include "V3String.h" #include "V3Undriven.h" #include "V3Ast.h" //###################################################################### // Class for every variable we may process class UndrivenVarEntry { // MEMBERS AstVar* m_varp; // Variable this tracks bool m_usedWhole; // True if whole vector used bool m_drivenWhole; // True if whole vector driven std::vector m_flags; // Used/Driven on each subbit enum { FLAG_USED = 0, FLAG_DRIVEN = 1, FLAGS_PER_BIT = 2 }; VL_DEBUG_FUNC; // Declare debug() public: // CONSTRUCTORS explicit UndrivenVarEntry(AstVar* varp) { // Construction for when a var is used UINFO(9, "create "<width()*FLAGS_PER_BIT); for (int i=0; iwidth()*FLAGS_PER_BIT; i++) { m_flags[i] = false; } } ~UndrivenVarEntry() {} private: // METHODS inline bool bitNumOk(int bit) const { return bit>=0 && (bit*FLAGS_PER_BIT < (int)m_flags.size()); } inline bool usedFlag(int bit) const { return m_usedWhole || m_flags[bit*FLAGS_PER_BIT + FLAG_USED]; } inline bool drivenFlag(int bit) const { return m_drivenWhole || m_flags[bit*FLAGS_PER_BIT + FLAG_DRIVEN]; } enum BitNamesWhich { BN_UNUSED, BN_UNDRIVEN, BN_BOTH }; string bitNames(BitNamesWhich which) { string bits=""; bool prev = false; int msb = 0; // bit==-1 loops below; we do one extra iteration so end with prev=false for (int bit=(m_flags.size()/FLAGS_PER_BIT)-1; bit >= -1; --bit) { if (bit>=0 && ((which == BN_UNUSED && !usedFlag(bit) && drivenFlag(bit)) || (which == BN_UNDRIVEN && usedFlag(bit) && !drivenFlag(bit)) || (which == BN_BOTH && !usedFlag(bit) && !drivenFlag(bit)))) { if (!prev) { prev=true; msb = bit; } } else if (prev) { AstBasicDType* bdtypep = m_varp->basicp(); int lsb = bit+1; if (bits != "") bits += ","; if (lsb==msb) { bits += cvtToStr(lsb+bdtypep->lsb()); } else { if (bdtypep->littleEndian()) { bits += cvtToStr(lsb+bdtypep->lsb())+":"+cvtToStr(msb+bdtypep->lsb()); } else { bits += cvtToStr(msb+bdtypep->lsb())+":"+cvtToStr(lsb+bdtypep->lsb()); } } prev = false; } } return "["+bits+"]"; } public: void usedWhole() { UINFO(9, "set u[*] "<name()<name()<name()<name()<prettyName(); return VString::wildmatch(prettyName.c_str(), regexp.c_str()); } void reportViolations() { // Combine bits into overall state AstVar* nodep = m_varp; if (!nodep->isParam() && !nodep->isGenVar()) { bool allU=true; bool allD=true; bool anyU=m_usedWhole; bool anyD=m_drivenWhole; bool anyUnotD=false; bool anyDnotU=false; bool anynotDU=false; for (unsigned bit=0; bitisIfaceRef()) { // For interface top level we don't do any tracking // Ideally we'd report unused instance cells, but presumably a signal inside one // would get reported as unused } else if (allU && allD) { // It's fine } else if (!anyD && !anyU) { // UNDRIVEN is considered more serious - as is more likely a bug, // thus undriven+unused bits get UNUSED warnings, as they're not as buggy. if (!unusedMatch(nodep)) { nodep->v3warn(UNUSED, "Signal is not driven, nor used: "<prettyName()); nodep->fileline()->modifyWarnOff(V3ErrorCode::UNUSED, true); // Warn only once } } else if (allD && !anyU) { if (!unusedMatch(nodep)) { nodep->v3warn(UNUSED, "Signal is not used: "<prettyName()); nodep->fileline()->modifyWarnOff(V3ErrorCode::UNUSED, true); // Warn only once } } else if (!anyD && allU) { nodep->v3warn(UNDRIVEN, "Signal is not driven: "<prettyName()); nodep->fileline()->modifyWarnOff(V3ErrorCode::UNDRIVEN, true); // Warn only once } else { // Bits have different dispositions bool setU=false; bool setD=false; if (anynotDU && !unusedMatch(nodep)) { nodep->v3warn(UNUSED, "Bits of signal are not driven, nor used: "<prettyName() <v3warn(UNUSED, "Bits of signal are not used: "<prettyName() <v3warn(UNDRIVEN, "Bits of signal are not driven: "<prettyName() <fileline()->modifyWarnOff(V3ErrorCode::UNUSED, true); // Warn only once if (setD) nodep->fileline()->modifyWarnOff(V3ErrorCode::UNDRIVEN, true); // Warn only once } } } }; //###################################################################### // Undriven state, as a visitor of each AstNode class UndrivenVisitor : public AstNVisitor { private: // NODE STATE // Netlist: // AstVar::user1p -> UndrivenVar* for usage var, 0=not set yet AstUser1InUse m_inuser1; // Each always: // AstNode::user2p -> UndrivenVar* for usage var, 0=not set yet AstUser2InUse m_inuser2; // STATE std::vector m_entryps[3]; // Nodes to delete when we are finished bool m_inBBox; // In black box; mark as driven+used AstNodeFTask* m_taskp; // Current task AstAlways* m_alwaysp; // Current always if combo, otherwise NULL // METHODS VL_DEBUG_FUNC; // Declare debug() UndrivenVarEntry* getEntryp(AstVar* nodep, int which_user) { if (!(which_user==1 ? nodep->user1p() : nodep->user2p())) { UndrivenVarEntry* entryp = new UndrivenVarEntry(nodep); //UINFO(9," Associate u="<name()<user1p(entryp); else if (which_user==2) nodep->user2p(entryp); else nodep->v3fatalSrc("Bad case"); return entryp; } else { UndrivenVarEntry* entryp = (UndrivenVarEntry*)(which_user==1 ? nodep->user1p() : nodep->user2p()); return entryp; } } void warnAlwCombOrder(AstNodeVarRef* nodep) { AstVar* varp = nodep->varp(); if (!varp->isParam() && !varp->isGenVar() && !varp->isUsedLoopIdx() && !m_inBBox // We may have falsely considered a SysIgnore as a driver && !VN_IS(nodep, VarXRef) // Xrefs might point at two different instances && !varp->fileline()->warnIsOff(V3ErrorCode::ALWCOMBORDER)) { // Warn only once per variable nodep->v3warn(ALWCOMBORDER, "Always_comb variable driven after use: "<prettyName()); varp->fileline()->modifyWarnOff(V3ErrorCode::ALWCOMBORDER, true); // Complain just once for any usage } } // VISITORS virtual void visit(AstVar* nodep) { for (int usr=1; usr<(m_alwaysp?3:2); ++usr) { // For assigns and non-combo always, do just usr==1, to look for module-wide undriven etc // For non-combo always, run both usr==1 for above, and also usr==2 for always-only checks UndrivenVarEntry* entryp = getEntryp(nodep, usr); if (nodep->isInput() || nodep->isSigPublic() || nodep->isSigUserRWPublic() || (m_taskp && (m_taskp->dpiImport() || m_taskp->dpiExport()))) { entryp->drivenWhole(); } if (nodep->isOutput() || nodep->isSigPublic() || nodep->isSigUserRWPublic() || nodep->isSigUserRdPublic() || (m_taskp && (m_taskp->dpiImport() || m_taskp->dpiExport()))) { entryp->usedWhole(); } } // Discover variables used in bit definitions, etc iterateChildren(nodep); } virtual void visit(AstArraySel* nodep) { // Arrays are rarely constant assigned, so for now we punt and do all entries iterateChildren(nodep); } virtual void visit(AstSliceSel* nodep) { // Arrays are rarely constant assigned, so for now we punt and do all entries iterateChildren(nodep); } virtual void visit(AstSel* nodep) { AstNodeVarRef* varrefp = VN_CAST(nodep->fromp(), NodeVarRef); AstConst* constp = VN_CAST(nodep->lsbp(), Const); if (varrefp && constp && !constp->num().isFourState()) { for (int usr=1; usr<(m_alwaysp?3:2); ++usr) { UndrivenVarEntry* entryp = getEntryp(varrefp->varp(), usr); int lsb = constp->toUInt(); if (m_inBBox || varrefp->lvalue()) { // Don't warn if already driven earlier as "a=0; if(a) a=1;" is fine. if (usr==2 && m_alwaysp && entryp->isUsedNotDrivenBit(lsb, nodep->width())) { UINFO(9," Select. Entryp="<<(void*)entryp<drivenBit(lsb, nodep->width()); } if (m_inBBox || !varrefp->lvalue()) entryp->usedBit(lsb, nodep->width()); } } else { // else other varrefs handled as unknown mess in AstVarRef iterateChildren(nodep); } } virtual void visit(AstNodeVarRef* nodep) { // Any variable for (int usr=1; usr<(m_alwaysp?3:2); ++usr) { UndrivenVarEntry* entryp = getEntryp(nodep->varp(), usr); bool fdrv = nodep->lvalue() && nodep->varp()->attrFileDescr(); // FD's are also being read from if (m_inBBox || nodep->lvalue()) { if (usr==2 && m_alwaysp && entryp->isUsedNotDrivenAny()) { UINFO(9," Full bus. Entryp="<<(void*)entryp<drivenWhole(); } if (m_inBBox || !nodep->lvalue() || fdrv) entryp->usedWhole(); } } // Don't know what black boxed calls do, assume in+out virtual void visit(AstSysIgnore* nodep) { bool prevMark = m_inBBox; m_inBBox = true; iterateChildren(nodep); m_inBBox = prevMark; } virtual void visit(AstAlways* nodep) { AstAlways* prevAlwp = m_alwaysp; { AstNode::user2ClearTree(); if (nodep->keyword() == VAlwaysKwd::ALWAYS_COMB) UINFO(9," "<keyword() == VAlwaysKwd::ALWAYS_COMB) m_alwaysp = nodep; else m_alwaysp = NULL; iterateChildren(nodep); if (nodep->keyword() == VAlwaysKwd::ALWAYS_COMB) UINFO(9," Done "<::iterator it = m_entryps[1].begin(); it != m_entryps[1].end(); ++it) { (*it)->reportViolations(); } for (int usr=1; usr<3; ++usr) { for (std::vector::iterator it = m_entryps[usr].begin(); it != m_entryps[usr].end(); ++it) { delete (*it); } } } }; //###################################################################### // Undriven class functions void V3Undriven::undrivenAll(AstNetlist* nodep) { UINFO(2,__FUNCTION__<<": "<