// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Clock Domain Crossing Lint // // Code available from: http://www.veripool.org/verilator // //************************************************************************* // // Copyright 2003-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. // //************************************************************************* // V3Cdc's Transformations: // // Create V3Graph-ish graph // Find all negedge reset flops // Trace back to previous flop // //************************************************************************* #include "config_build.h" #include "verilatedos.h" #include "V3Global.h" #include "V3Cdc.h" #include "V3Ast.h" #include "V3Graph.h" #include "V3Const.h" #include "V3EmitV.h" #include "V3File.h" #include #include #include #include #include #include #include #define CDC_WEIGHT_ASYNC 0x1000 // Weight for edges that feed async logic //###################################################################### class CdcBaseVisitor : public AstNVisitor { public: VL_DEBUG_FUNC; // Declare debug() }; //###################################################################### // Graph support classes class CdcEitherVertex : public V3GraphVertex { AstScope* m_scopep; AstNode* m_nodep; AstSenTree* m_srcDomainp; AstSenTree* m_dstDomainp; bool m_srcDomainSet:1; bool m_dstDomainSet:1; bool m_asyncPath:1; public: CdcEitherVertex(V3Graph* graphp, AstScope* scopep, AstNode* nodep) : V3GraphVertex(graphp), m_scopep(scopep), m_nodep(nodep) , m_srcDomainp(NULL), m_dstDomainp(NULL) , m_srcDomainSet(false), m_dstDomainSet(false) , m_asyncPath(false) {} virtual ~CdcEitherVertex() {} // ACCESSORS virtual FileLine* fileline() const { return nodep()->fileline(); } AstScope* scopep() const { return m_scopep; } AstNode* nodep() const { return m_nodep; } AstSenTree* srcDomainp() const { return m_srcDomainp; } void srcDomainp(AstSenTree* nodep) { m_srcDomainp = nodep; } bool srcDomainSet() const { return m_srcDomainSet; } void srcDomainSet(bool flag) { m_srcDomainSet = flag; } AstSenTree* dstDomainp() const { return m_dstDomainp; } void dstDomainp(AstSenTree* nodep) { m_dstDomainp = nodep; } bool dstDomainSet() const { return m_dstDomainSet; } void dstDomainSet(bool flag) { m_dstDomainSet = flag; } bool asyncPath() const { return m_asyncPath; } void asyncPath(bool flag) { m_asyncPath = flag; } }; class CdcVarVertex : public CdcEitherVertex { AstVarScope* m_varScp; int m_cntAsyncRst; bool m_fromFlop; public: CdcVarVertex(V3Graph* graphp, AstScope* scopep, AstVarScope* varScp) : CdcEitherVertex(graphp, scopep, varScp), m_varScp(varScp), m_cntAsyncRst(0), m_fromFlop(false) {} virtual ~CdcVarVertex() {} // ACCESSORS AstVarScope* varScp() const { return m_varScp; } virtual string name() const { return (cvtToHex(m_varScp)+" "+varScp()->name()); } virtual string dotColor() const { return fromFlop() ? "green" : cntAsyncRst() ? "red" : "blue"; } int cntAsyncRst() const { return m_cntAsyncRst; } void cntAsyncRst(int flag) { m_cntAsyncRst=flag; } bool fromFlop() const { return m_fromFlop; } void fromFlop(bool flag) { m_fromFlop = flag; } }; class CdcLogicVertex : public CdcEitherVertex { bool m_hazard:1; bool m_isFlop:1; public: CdcLogicVertex(V3Graph* graphp, AstScope* scopep, AstNode* nodep, AstSenTree* sensenodep) : CdcEitherVertex(graphp,scopep,nodep) , m_hazard(false), m_isFlop(false) { srcDomainp(sensenodep); dstDomainp(sensenodep); } virtual ~CdcLogicVertex() {} // ACCESSORS virtual string name() const { return (cvtToHex(nodep())+"@"+scopep()->prettyName()); } virtual string dotColor() const { return hazard() ? "black" : "yellow"; } bool hazard() const { return m_hazard; } void setHazard(AstNode* nodep) { m_hazard = true; nodep->user3(true); } void clearHazard() { m_hazard = false; } bool isFlop() const { return m_isFlop; } void isFlop(bool flag) { m_isFlop = flag; } }; //###################################################################### class CdcDumpVisitor : public CdcBaseVisitor { private: // NODE STATE //Entire netlist: // {statement}Node::user3 -> bool, indicating not hazard std::ofstream* m_ofp; // Output file string m_prefix; virtual void visit(AstNode* nodep) { *m_ofp<user3()) *m_ofp<<" %%"; else *m_ofp<<" "; *m_ofp<prettyTypeName()<<" "<op1p()); m_prefix = lastPrefix + "2:"; iterateAndNextNull(nodep->op2p()); m_prefix = lastPrefix + "3:"; iterateAndNextNull(nodep->op3p()); m_prefix = lastPrefix + "4:"; iterateAndNextNull(nodep->op4p()); m_prefix = lastPrefix; } public: // CONSTUCTORS CdcDumpVisitor(AstNode* nodep, std::ofstream* ofp, const string& prefix) { m_ofp = ofp; m_prefix = prefix; iterate(nodep); } virtual ~CdcDumpVisitor() {} }; //###################################################################### class CdcWidthVisitor : public CdcBaseVisitor { private: int m_maxLineno; size_t m_maxFilenameLen; virtual void visit(AstNode* nodep) { iterateChildren(nodep); // Keeping line+filename lengths separate is much faster than calling ascii().length() if (nodep->fileline()->lineno() >= m_maxLineno) { m_maxLineno = nodep->fileline()->lineno()+1; } if (nodep->fileline()->filename().length() >= m_maxFilenameLen) { m_maxFilenameLen = nodep->fileline()->filename().length()+1; } } public: // CONSTUCTORS explicit CdcWidthVisitor(AstNode* nodep) { m_maxLineno = 0; m_maxFilenameLen = 0; iterate(nodep); } virtual ~CdcWidthVisitor() {} // ACCESSORS int maxWidth() { size_t width=1; width += m_maxFilenameLen; width += 1; // The : width += cvtToStr(m_maxLineno).length(); width += 1; // Final : return static_cast(width); } }; //###################################################################### // Cdc class functions class CdcVisitor : public CdcBaseVisitor { private: // NODE STATE //Entire netlist: // AstVarScope::user1p -> CdcVarVertex* for usage var, 0=not set yet // AstVarScope::user2 -> bool Used in sensitivity list // {statement}Node::user1p -> CdcLogicVertex* for this statement // AstNode::user3 -> bool True indicates to print %% (via V3EmitV) AstUser1InUse m_inuser1; AstUser2InUse m_inuser2; AstUser3InUse m_inuser3; // STATE V3Graph m_graph; // Scoreboard of var usages/dependencies CdcLogicVertex* m_logicVertexp; // Current statement being tracked, NULL=ignored AstScope* m_scopep; // Current scope being processed AstNodeModule* m_modp; // Current module AstSenTree* m_domainp; // Current sentree bool m_inDly; // In delayed assign int m_inSenItem; // Number of senitems string m_ofFilename; // Output filename std::ofstream* m_ofp; // Output file uint32_t m_userGeneration; // Generation count to avoid slow userClearVertices int m_filelineWidth; // Characters in longest fileline // METHODS void iterateNewStmt(AstNode* nodep) { if (m_scopep) { UINFO(4," STMT "<hasClocked()) { // To/from a flop m_logicVertexp->isFlop(true); m_logicVertexp->srcDomainp(m_domainp); m_logicVertexp->srcDomainSet(true); m_logicVertexp->dstDomainp(m_domainp); m_logicVertexp->dstDomainSet(true); } iterateChildren(nodep); m_logicVertexp = NULL; if (0 && debug()>=9) { UINFO(9, "Trace Logic:\n"); nodep->dumpTree(cout, "-log1: "); } } } CdcVarVertex* makeVarVertex(AstVarScope* varscp) { CdcVarVertex* vertexp = reinterpret_cast(varscp->user1p()); if (!vertexp) { UINFO(6,"New vertex "<user1p(vertexp); if (varscp->varp()->isUsedClock()) {} if (varscp->varp()->isPrimaryIO()) { // Create IO vertex - note it's relative to the pointed to var, not where we are now // This allows reporting to easily print the input statement CdcLogicVertex* ioVertexp = new CdcLogicVertex(&m_graph, varscp->scopep(), varscp->varp(), NULL); if (varscp->varp()->isWritable()) { new V3GraphEdge(&m_graph, vertexp, ioVertexp, 1); } else { new V3GraphEdge(&m_graph, ioVertexp, vertexp, 1); } } } if (m_inSenItem) { varscp->user2(true); // It's like a clock... // TODO: In the future we could mark it here and do normal clock tree glitch checks also } else if (varscp->user2()) { // It was detected in a sensitivity list earlier // And now it's used as logic. So must be a reset. vertexp->cntAsyncRst(vertexp->cntAsyncRst()+1); } return vertexp; } void warnAndFile(AstNode* nodep, V3ErrorCode code, const string& msg) { static bool told_file = false; nodep->v3warnCode(code,msg); if (!told_file) { told_file = 1; std::cerr<fileline()<<" "<hasCombo()) { // Source flop logic in a posedge block is OK for reset (not async though) if (m_logicVertexp && !nodep->fileline()->warnIsOff(V3ErrorCode::CDCRSTLOGIC)) { UINFO(8,"Set hazard "<setHazard(nodep); } } } string spaces(int level) { string out; while (level--) out+=" "; return out; } string pad(unsigned column, const string& in) { string out = in; while (out.length()6) m_graph.dump(); if (debug()>6) m_graph.dumpDotFilePrefixed("cdc_pre"); // m_graph.removeRedundantEdges(&V3GraphEdge::followAlwaysTrue); // This will MAX across edge weights // m_graph.dumpDotFilePrefixed("cdc_simp"); // analyzeReset(); } int filelineWidth() { if (!m_filelineWidth) { CdcWidthVisitor visitor (v3Global.rootp()); m_filelineWidth = visitor.maxWidth(); } return m_filelineWidth; } //---------------------------------------- // RESET REPORT void analyzeReset() { // Find all async reset wires, and trace backwards // userClearVertices is very slow, so we use a generation count instead m_graph.userClearVertices(); // user1: uint32_t - was analyzed generation for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp=itp->verticesNextp()) { if (CdcVarVertex* vvertexp = dynamic_cast(itp)) { if (vvertexp->cntAsyncRst()) { m_userGeneration++; // Effectively a userClearVertices() UINFO(8, " Trace One async: "<user()>=m_userGeneration) return NULL; // Processed - prevent loop vertexp->user(m_userGeneration); CdcEitherVertex* mark_outp = NULL; UINFO(9," Trace: "<asyncPath(false); if (CdcLogicVertex* vvertexp = dynamic_cast(vertexp)) { // Any logic considered bad, at the moment, anyhow if (vvertexp->hazard() && !mark_outp) mark_outp = vvertexp; // And keep tracing back so the user can understand what's up } else if (CdcVarVertex* vvertexp = dynamic_cast(vertexp)) { if (mark) vvertexp->asyncPath(true); // If primary I/O, it's ok here back if (vvertexp->varScp()->varp()->isPrimaryInish()) { // Show the source "input" statement if it exists for (V3GraphEdge* edgep = vertexp->inBeginp(); edgep; edgep = edgep->inNextp()) { CdcEitherVertex* eFromVertexp = static_cast(edgep->fromp()); eFromVertexp->asyncPath(true); } return NULL; } // Also ok if from flop, but partially trace the flop so more obvious to users if (vvertexp->fromFlop()) { for (V3GraphEdge* edgep = vertexp->inBeginp(); edgep; edgep = edgep->inNextp()) { CdcEitherVertex* eFromVertexp = static_cast(edgep->fromp()); eFromVertexp->asyncPath(true); } return NULL; } } for (V3GraphEdge* edgep = vertexp->inBeginp(); edgep; edgep = edgep->inNextp()) { CdcEitherVertex* eFromVertexp = static_cast(edgep->fromp()); CdcEitherVertex* submarkp = traceAsyncRecurse(eFromVertexp, mark); if (submarkp && !mark_outp) mark_outp = submarkp; } if (mark) vertexp->asyncPath(true); return mark_outp; } void dumpAsync(CdcVarVertex* vertexp, CdcEitherVertex* markp) { AstNode* nodep = vertexp->varScp(); *m_ofp<<"\n"; *m_ofp<<"\n"; CdcEitherVertex* targetp = vertexp; // One example destination flop (of possibly many) for (V3GraphEdge* edgep = vertexp->outBeginp(); edgep; edgep = edgep->outNextp()) { CdcEitherVertex* eToVertexp = static_cast(edgep->top()); if (!eToVertexp) targetp = eToVertexp; if (CdcLogicVertex* vvertexp = dynamic_cast(eToVertexp)) { if (vvertexp->isFlop() // IE the target flop that is upsetting us && edgep->weight() >= CDC_WEIGHT_ASYNC) { // And this signal feeds an async reset line targetp = eToVertexp; //UINFO(9," targetasync "<name()<<" "<<" from "<name()<name()<<" "<nodep()->fileline()<nodep(),V3ErrorCode::CDCRSTLOGIC,"Logic in path that feeds async reset, via signal: "+nodep->prettyName()); dumpAsyncRecurse(targetp, "", " ",0); } bool dumpAsyncRecurse(CdcEitherVertex* vertexp, const string& prefix, const string& sep, int level) { // level=0 is special, indicates to dump destination flop // Return true if printed anything // If mark, also mark the output even if nothing hazardous below if (vertexp->user()>=m_userGeneration) return false; // Processed - prevent loop vertexp->user(m_userGeneration); if (!vertexp->asyncPath() && level!=0) return false; // Not part of path // Other logic in the path string cont = prefix+sep; string nextsep = " "; for (V3GraphEdge* edgep = vertexp->inBeginp(); edgep; edgep = edgep->inNextp()) { CdcEitherVertex* eFromVertexp = static_cast(edgep->fromp()); if (dumpAsyncRecurse(eFromVertexp, cont, nextsep, level+1)) { nextsep = " | "; } } // Dump single variable/logic block // See also OrderGraph::loopsVertexCb(V3GraphVertex* vertexp) AstNode* nodep = vertexp->nodep(); string front = pad(filelineWidth(),nodep->fileline()->ascii()+":")+" "+prefix+" +- "; if (VN_IS(nodep, VarScope)) { *m_ofp<prettyName()<srcDomainp(), true); if (debug()) { CdcDumpVisitor visitor (nodep, m_ofp, front+"DBG: "); } } nextsep = " | "; if (level) *m_ofp<(vertexp)) { // Now that we've printed a path with this hazard, don't bother to print any more // Otherwise, we'd get a path for almost every destination flop vvertexp->clearHazard(); } return true; } //---------------------------------------- // EDGE REPORTS void edgeReport() { // Make report of all signal names and what clock edges they have // // Due to flattening, many interesting direct-connect signals are // lost, so we can't make a report showing I/Os for a low level // module. Disabling flattening though makes us consider each // signal in it's own unique clock domain. UINFO(3,__FUNCTION__<<": "<verticesNextp()) { if (CdcVarVertex* vvertexp = dynamic_cast(itp)) { UINFO(9, " Trace One edge: "< ofp (V3File::new_ofstream(filename)); if (ofp->fail()) v3fatal("Can't write "< report; // Sort output by name for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp=itp->verticesNextp()) { if (CdcVarVertex* vvertexp = dynamic_cast(itp)) { AstVar* varp = vvertexp->varScp()->varp(); if (1) { // varp->isPrimaryIO() string what = "wire"; if (varp->isPrimaryIO()) what = varp->direction().prettyName(); std::ostringstream os; os.setf(std::ios::left); // Module name - doesn't work due to flattening having lost the original // so we assume the modulename matches the filebasename string fname = vvertexp->varScp()->fileline()->filebasename() + ":"; os<<" "<varScp()->prettyName(); os<<" SRC="; if (vvertexp->srcDomainp()) V3EmitV::verilogForTree(vvertexp->srcDomainp(), os); os<<" DST="; if (vvertexp->dstDomainp()) V3EmitV::verilogForTree(vvertexp->dstDomainp(), os); os<::iterator it = report.begin(); it!=report.end(); ++it) { *ofp << *it; } } void edgeDomainRecurse(CdcEitherVertex* vertexp, bool traceDests, int level) { // Scan back to inputs/outputs, flops, and compute clock domain information UINFO(8,spaces(level)<<" Tracein "<user()>=m_userGeneration) return; // Mid-Processed - prevent loop vertexp->user(m_userGeneration); // Variables from flops already are domained if (traceDests ? vertexp->dstDomainSet() : vertexp->srcDomainSet()) return; // Fully computed typedef std::set SenSet; SenSet senouts; // List of all sensitivities for new signal if (CdcLogicVertex* vvertexp = dynamic_cast(vertexp)) { if (vvertexp) {} // Unused } else if (CdcVarVertex* vvertexp = dynamic_cast(vertexp)) { // If primary I/O, give it domain of the input AstVar* varp = vvertexp->varScp()->varp(); if (varp->isPrimaryIO() && varp->isNonOutput() && !traceDests) { senouts.insert( new AstSenTree(varp->fileline(), new AstSenItem(varp->fileline(), AstSenItem::Combo()))); } } // Now combine domains of sources/dests if (traceDests) { for (V3GraphEdge* edgep = vertexp->outBeginp(); edgep; edgep = edgep->outNextp()) { CdcEitherVertex* eToVertexp = static_cast(edgep->top()); edgeDomainRecurse(eToVertexp, traceDests, level+1); if (eToVertexp->dstDomainp()) senouts.insert(eToVertexp->dstDomainp()); } } else { for (V3GraphEdge* edgep = vertexp->inBeginp(); edgep; edgep = edgep->inNextp()) { CdcEitherVertex* eFromVertexp = static_cast(edgep->fromp()); edgeDomainRecurse(eFromVertexp, traceDests, level+1); if (eFromVertexp->srcDomainp()) senouts.insert(eFromVertexp->srcDomainp()); } } // Convert list of senses into one sense node AstSenTree* senoutp = NULL; bool senedited = false; for (SenSet::iterator it=senouts.begin(); it!=senouts.end(); ++it) { if (!senoutp) senoutp = *it; else { if (!senedited) { senedited = true; senoutp = senoutp->cloneTree(true); } senoutp->addSensesp((*it)->sensesp()->cloneTree(true)); } } // If multiple domains need to do complicated optimizations if (senedited) { senoutp = VN_CAST(V3Const::constifyExpensiveEdit(senoutp), SenTree); } if (traceDests) { vertexp->dstDomainSet(true); // Note it's set - domainp may be null, so can't use that vertexp->dstDomainp(senoutp); if (debug()>=9) { UINFO(9,spaces(level)+" Tracedst "<srcDomainSet(true); // Note it's set - domainp may be null, so can't use that vertexp->srcDomainp(senoutp); if (debug()>=9) { UINFO(9,spaces(level)+" Tracesrc "<sensesp(); if (!m_domainp || m_domainp->hasCombo() || m_domainp->hasClocked()) { // IE not hasSettle/hasInitial iterateNewStmt(nodep); } m_domainp = NULL; AstNode::user2ClearTree(); } virtual void visit(AstNodeVarRef* nodep) { if (m_scopep) { if (!m_logicVertexp) nodep->v3fatalSrc("Var ref not under a logic block"); AstVarScope* varscp = nodep->varScopep(); if (!varscp) nodep->v3fatalSrc("Var didn't get varscoped in V3Scope.cpp"); CdcVarVertex* varvertexp = makeVarVertex(varscp); UINFO(5," VARREF to "<lvalue()) { new V3GraphEdge(&m_graph, m_logicVertexp, varvertexp, 1); if (m_inDly) { varvertexp->fromFlop(true); varvertexp->srcDomainp(m_domainp); varvertexp->srcDomainSet(true); } } else { if (varvertexp->cntAsyncRst()) { //UINFO(9," edgeasync "<name()<<" to "<name()<<" to "<lsbp(), Const)) setNodeHazard(nodep); iterateChildren(nodep); } virtual void visit(AstNodeSel* nodep) { if (!VN_IS(nodep->bitp(), Const)) setNodeHazard(nodep); iterateChildren(nodep); } // Ignores virtual void visit(AstInitial* nodep) { } virtual void visit(AstTraceInc* nodep) { } virtual void visit(AstCoverToggle* nodep) { } virtual void visit(AstNodeDType* nodep) { } //-------------------- // Default virtual void visit(AstNodeMath* nodep) { setNodeHazard(nodep); iterateChildren(nodep); } virtual void visit(AstNode* nodep) { iterateChildren(nodep); } public: // CONSTUCTORS explicit CdcVisitor(AstNode* nodep) { m_logicVertexp = NULL; m_scopep = NULL; m_modp = NULL; m_domainp = NULL; m_inDly = false; m_inSenItem = 0; m_userGeneration = 0; m_filelineWidth = 0; // Make report of all signal names and what clock edges they have string filename = v3Global.opt.makeDir()+"/"+v3Global.opt.prefix()+"__cdc.txt"; m_ofp = V3File::new_ofstream(filename); if (m_ofp->fail()) v3fatal("Can't write "<=1) edgeReport(); // Not useful to users at the moment if (0) { *m_ofp<<"\nDBG-test-dumper\n"; V3EmitV::verilogPrefixedTree(nodep, *m_ofp, "DBG ",40,NULL,true); *m_ofp<