diff --git a/Changes b/Changes index 861609fcf..91048143f 100644 --- a/Changes +++ b/Changes @@ -26,6 +26,8 @@ indicates the contributor was also the author of the fix; Thanks! *** Add VARHIDDEN warning when signal name hides module name. +*** Add experimental clock domain crossing checks. + **** Support optional cell parenthesis, bug179. [by Byron Bradley] **** Support for loop i++, ++i, i--, --i, bug175. [by Byron Bradley] diff --git a/bin/verilator b/bin/verilator index e49b4bb19..a88cf99a3 100755 --- a/bin/verilator +++ b/bin/verilator @@ -193,6 +193,7 @@ descriptions in the next sections for more information. --bbox-unsup Blackbox unsupported language features --bin Override Verilator binary --cc Create C++ output + --cdc Clock domain crossing analysis --compiler Tune for specified C++ compiler --coverage Enable all coverage --coverage-line Enable line coverage @@ -254,6 +255,7 @@ descriptions in the next sections for more information. +define++ Set preprocessor define +incdir+ Directory to search for includes +libext++[ext]... Extensions for finding modules + +notimingchecks Ignored =head1 ARGUMENTS @@ -308,6 +310,17 @@ output files. Specifies C++ without SystemC output mode; see also --sc and --sp. +=item --cdc + +Experimental. Perform some clock domain crossing checks and issue related +warnings. Additional warning information is written to the file +{prefix}__cdc.txt. Generally used with --lint-only. + +Currently only checks that asynchronous flop reset terms come from primary +inputs (a check that most other CDC tools missed, so it was put here). If +you have interest in adding more traditional CDC checks, please contact the +authors. + =item --compiler I Enables tunings and work-arounds for the specified C++ compiler. @@ -992,6 +1005,7 @@ In certain optimization modes, it also creates: {prefix}__Syms.cpp // Global symbol table C++ {prefix}__Syms.h // Global symbol table header {prefix}__Trace.cpp // Wave file generation code (--trace) + {prefix}__cdc.txt // Clock Domain Crossing checks (--cdc) {prefix}__stats.txt // Statistics (--stats) It also creates internal files that can be mostly ignored: diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in index f45dcdbc4..e667bf5e5 100644 --- a/src/Makefile_obj.in +++ b/src/Makefile_obj.in @@ -155,6 +155,7 @@ RAW_OBJS = \ V3Broken.o \ V3Case.o \ V3Cast.o \ + V3Cdc.o \ V3Changed.o \ V3Clean.o \ V3ClkGater.o \ diff --git a/src/V3Cdc.cpp b/src/V3Cdc.cpp new file mode 100644 index 000000000..d5ade9936 --- /dev/null +++ b/src/V3Cdc.cpp @@ -0,0 +1,629 @@ +//************************************************************************* +// DESCRIPTION: Verilator: Clock Domain Crossing Lint +// +// Code available from: http://www.veripool.org/verilator +// +// AUTHORS: Wilson Snyder with Paul Wasson, Duane Gabli +// +//************************************************************************* +// +// Copyright 2003-2010 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include "V3Global.h" +#include "V3Cdc.h" +#include "V3Ast.h" +#include "V3Graph.h" +#include "V3Const.h" +#include "V3EmitV.h" +#include "V3File.h" + +//###################################################################### + +class CdcBaseVisitor : public AstNVisitor { +public: + static int debug() { + static int level = -1; + if (VL_UNLIKELY(level < 0)) level = v3Global.opt.debugSrcLevel(__FILE__); + return level; + } +}; + +//###################################################################### +// Support classes + +class CdcEitherVertex : public V3GraphVertex { + AstScope* m_scopep; + AstNode* m_nodep; + AstSenTree* m_srcDomainp; + bool m_srcDomainSet; + AstSenTree* m_dstDomainp; + bool m_dstDomainSet; + bool m_asyncPath; +public: + CdcEitherVertex(V3Graph* graphp, AstScope* scopep, AstNode* nodep) + : V3GraphVertex(graphp), m_scopep(scopep), m_nodep(nodep) + , m_srcDomainp(NULL), m_srcDomainSet(false) + , m_dstDomainp(NULL), m_dstDomainSet(false) + , m_asyncPath(false) {} + virtual ~CdcEitherVertex() {} + // Accessors + 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 (cvtToStr((void*)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_safe; +public: + CdcLogicVertex(V3Graph* graphp, AstScope* scopep, AstNode* nodep, AstSenTree* sensenodep) + : CdcEitherVertex(graphp,scopep,nodep), m_safe(true) { srcDomainp(sensenodep); dstDomainp(sensenodep); } + virtual ~CdcLogicVertex() {} + // Accessors + virtual string name() const { return (cvtToStr((void*)nodep())+"@"+scopep()->prettyName()); } + virtual string dotColor() const { return safe() ? "black" : "yellow"; } + bool safe() const { return m_safe; } + void clearSafe(AstNode* nodep) { m_safe = false; nodep->user3(true); } +}; + +//###################################################################### +// Cdc class functions + +class CdcVisitor : public CdcBaseVisitor { +private: + // NODE STATE + //Entire netlist: + // AstVarScope::user1p -> CdcVarVertex* for usage var, 0=not set yet + // {statement}Node::user1p -> CdcLogicVertex* for this statement + // AstNode::user3 -> bool True indicates to print %% (via V3EmitV) + AstUser1InUse m_inuser1; + 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_senNumber; // Number of senitems + string m_ofFilename; // Output filename + ofstream* m_ofp; // Output file + + // METHODS + void iterateNewStmt(AstNode* nodep) { + if (m_scopep) { + UINFO(4," STMT "<hasClocked()) { // To/from a flop + m_logicVertexp->srcDomainp(m_domainp); + m_logicVertexp->srcDomainSet(true); + m_logicVertexp->dstDomainp(m_domainp); + m_logicVertexp->dstDomainSet(true); + } + m_senNumber = 0; + nodep->iterateChildren(*this); + m_logicVertexp = NULL; + + if (0 && debug()>=9) { + UINFO(9, "Trace Logic:\n"); + nodep->dumpTree(cout, "-log1: "); + } + } + } + + CdcVarVertex* makeVarVertex(AstVarScope* varscp) { + CdcVarVertex* vertexp = (CdcVarVertex*)(varscp->user1p()); + if (!vertexp) { + UINFO(6,"New vertex "<user1p(vertexp); + if (varscp->varp()->isIO() && varscp->scopep()->isTop()) {} + if (varscp->varp()->isUsedClock()) {} + } + if (m_senNumber > 1) vertexp->cntAsyncRst(vertexp->cntAsyncRst()+1); + return vertexp; + } + + string pad(unsigned column, const string& in) { + string out = in; + while (out.length()verticesNextp()) { + if (CdcVarVertex* vvertexp = dynamic_cast(itp)) { + UINFO(9, " Trace One edge: "< ofp (V3File::new_ofstream(filename)); + if (ofp->fail()) v3fatalSrc("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() + const char* whatp = "wire"; + if (varp->isPrimaryIO()) whatp = (varp->isInout()?"inout":varp->isInput()?"input":"output"); + + ostringstream os; + os.setf(ios::left); + os<<" "<varScp()->prettyName(); + os<<" SRC="; + if (vvertexp->srcDomainp()) V3EmitV::verilogForTree(vvertexp->srcDomainp(), os); + os<<" DST="; + if (vvertexp->dstDomainp()) V3EmitV::verilogForTree(vvertexp->dstDomainp(), os); + os<<"\n"; + report.push_back(os.str()); + } + } + } + sort(report.begin(), report.end()); + for (deque::iterator it = report.begin(); it!=report.end(); ++it) { + *ofp << *it; + } + } + + string spaces(int level) { string out; while (level--) out+=" "; return out; } + + 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()) return; // Mid-Processed - prevent loop + vertexp->user(true); + // Variables from flops already are domained + if (traceDests ? vertexp->dstDomainSet() : vertexp->srcDomainSet()) return; // Fully computed + + typedef 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->isInput() && !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 = (CdcEitherVertex*)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 = (CdcEitherVertex*)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 = V3Const::constifyExpensiveEdit(senoutp)->castSenTree(); + } + 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 "<v3warnCode(code,msg); + if (!told_file) { + told_file = 1; + cerr<fileline()<<" "<fileline()->warnIsOff(V3ErrorCode::CDCRSTLOGIC)) { + UINFO(9,"Clear safe "<clearSafe(nodep); + } + } + + // VISITORS + virtual void visit(AstNodeModule* nodep, AstNUser*) { + m_modp = nodep; + nodep->iterateChildren(*this); + m_modp = NULL; + } + virtual void visit(AstScope* nodep, AstNUser*) { + UINFO(4," SCOPE "<iterateChildren(*this); + m_scopep = NULL; + } + virtual void visit(AstActive* nodep, AstNUser*) { + // Create required blocks and add to module + UINFO(4," BLOCK "<sensesp(); + if (!m_domainp || m_domainp->hasCombo() || m_domainp->hasClocked()) { // IE not hasSettle/hasInitial + iterateNewStmt(nodep); + } + m_domainp = NULL; + } + virtual void visit(AstNodeVarRef* nodep, AstNUser*) { + if (m_scopep) { + if (!m_logicVertexp) nodep->v3fatalSrc("Var ref not under a logic block\n"); + AstVarScope* varscp = nodep->varScopep(); + if (!varscp) nodep->v3fatalSrc("Var didn't get varscoped in V3Scope.cpp\n"); + 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 { + new V3GraphEdge(&m_graph, varvertexp, m_logicVertexp, 1); + } + } + } + virtual void visit(AstAssignDly* nodep, AstNUser*) { + m_inDly = true; + nodep->iterateChildren(*this); + m_inDly = false; + } + virtual void visit(AstSenItem* nodep, AstNUser*) { + // Note we look at only AstSenItems, not AstSenGate's + // The gating term of a AstSenGate is normal logic + m_senNumber++; + nodep->iterateChildren(*this); + } + virtual void visit(AstAlways* nodep, AstNUser*) { + iterateNewStmt(nodep); + } + virtual void visit(AstCFunc* nodep, AstNUser*) { + iterateNewStmt(nodep); + } + virtual void visit(AstSenGate* nodep, AstNUser*) { + // First handle the clock part will be handled in a minute by visit AstSenItem + // The logic gating term is delt with as logic + iterateNewStmt(nodep); + } + virtual void visit(AstAssignAlias* nodep, AstNUser*) { + iterateNewStmt(nodep); + } + virtual void visit(AstAssignW* nodep, AstNUser*) { + iterateNewStmt(nodep); + } + + // Math that shouldn't cause us to clear safe + virtual void visit(AstConst* nodep, AstNUser*) { } + virtual void visit(AstReplicate* nodep, AstNUser*) { + nodep->iterateChildren(*this); + } + virtual void visit(AstConcat* nodep, AstNUser*) { + nodep->iterateChildren(*this); + } + virtual void visit(AstNot* nodep, AstNUser*) { + nodep->iterateChildren(*this); + } + virtual void visit(AstSel* nodep, AstNUser*) { + if (!nodep->lsbp()->castConst()) clearNodeSafe(nodep); + nodep->iterateChildren(*this); + } + virtual void visit(AstNodeSel* nodep, AstNUser*) { + if (!nodep->bitp()->castConst()) clearNodeSafe(nodep); + nodep->iterateChildren(*this); + } + + // Ignores + virtual void visit(AstInitial* nodep, AstNUser*) { } + virtual void visit(AstTraceInc* nodep, AstNUser*) { } + virtual void visit(AstCoverToggle* nodep, AstNUser*) { } + + //-------------------- + // Default + virtual void visit(AstNodeMath* nodep, AstNUser*) { + clearNodeSafe(nodep); + nodep->iterateChildren(*this); + } + virtual void visit(AstNode* nodep, AstNUser*) { + nodep->iterateChildren(*this); + } + +public: + // CONSTUCTORS + CdcVisitor(AstNode* nodep) { + m_logicVertexp = NULL; + m_scopep = NULL; + m_modp = NULL; + m_domainp = NULL; + m_inDly = false; + m_senNumber = 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()) v3fatalSrc("Can't write "<accept(*this); + analyze(); + //edgeReport(); // Not useful at the moment + + if (0) { *m_ofp<<"\nDBG-test-dumper\n"; V3EmitV::verilogPrefixedTree(nodep, *m_ofp, "DBG ",true); *m_ofp< bool, indicating not safe + ofstream* m_ofp; // Output file + string m_prefix; + + virtual void visit(AstNode* nodep, AstNUser*) { + *m_ofp<user3()) *m_ofp<<" %%"; + else *m_ofp<<" "; + *m_ofp<prettyTypeName()<<" "<op1p()->iterateAndNext(*this); + m_prefix = lastPrefix + "2:"; + nodep->op2p()->iterateAndNext(*this); + m_prefix = lastPrefix + "3:"; + nodep->op3p()->iterateAndNext(*this); + m_prefix = lastPrefix + "4:"; + nodep->op4p()->iterateAndNext(*this); + m_prefix = lastPrefix; + } + +public: + // CONSTUCTORS + CdcDumpVisitor(AstNode* nodep, ofstream* ofp, const string& prefix) { + m_ofp = ofp; + m_prefix = prefix; + nodep->accept(*this); + } + virtual ~CdcDumpVisitor() {} +}; + +//###################################################################### + +void CdcVisitor::analyze() { + UINFO(3,__FUNCTION__<<": "<6) m_graph.dump(); + if (debug()>6) m_graph.dumpDotFilePrefixed("cdc_pre"); + m_graph.removeRedundantEdgesSum(&V3GraphEdge::followAlwaysTrue); + m_graph.dumpDotFilePrefixed("cdc_simp"); + // + analyzeReset(); +} + +void CdcVisitor::analyzeReset() { + // Find all async reset wires, and trace backwards + for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp=itp->verticesNextp()) { + if (CdcVarVertex* vvertexp = dynamic_cast(itp)) { + if (vvertexp->cntAsyncRst()) { + m_graph.userClearVertices(); // user1: bool - was analyzed + UINFO(9, " Trace One async: "<user()>=did_value) return false; // Processed - prevent loop + vertexp->user(did_value); + + if (CdcLogicVertex* vvertexp = dynamic_cast(vertexp)) { + // Any logic considered bad, at the moment, anyhow + if (!vvertexp->safe() && !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()->isInput()) return false; + // Also ok if from flop + if (vvertexp->fromFlop()) return false; + } + + for (V3GraphEdge* edgep = vertexp->inBeginp(); edgep; edgep = edgep->inNextp()) { + CdcEitherVertex* eFromVertexp = (CdcEitherVertex*)edgep->fromp(); + CdcEitherVertex* submarkp = traceAsyncRecurse(eFromVertexp, did_value, mark); + if (submarkp && !mark_outp) mark_outp = submarkp; + } + + if (mark) vertexp->asyncPath(true); + return mark_outp; +} + +//---------------------------------------------------------------------- + +void CdcVisitor::dumpAsync(CdcVarVertex* vertexp, CdcEitherVertex* markp) { + AstNode* nodep = vertexp->varScp(); + *m_ofp<<"\n"; + *m_ofp<<"\n"; + AstNode* targetp = vertexp->nodep(); + if (V3GraphEdge* edgep = vertexp->outBeginp()) { + CdcEitherVertex* eToVertexp = (CdcEitherVertex*)edgep->top(); + targetp = eToVertexp->nodep(); + } + warnAndFile(markp->nodep(),V3ErrorCode::CDCRSTLOGIC,"Logic in path that feeds async reset, via signal: "+nodep->prettyName()); + *m_ofp<<"Fanout: "<cntAsyncRst()<<" Target: "<fileline()<user()) return; // Processed - prevent loop + vertexp->user(1); + if (!vertexp->asyncPath()) return; // Not part of path + + *m_ofp<nodep(); + string front = pad(40,nodep->fileline()->ascii()+":"); + front += " "+prefix+" "; + if (nodep->castVarScope()) *m_ofp<prettyName()<inBeginp(); edgep; edgep = edgep->inNextp()) { + CdcEitherVertex* eFromVertexp = (CdcEitherVertex*)edgep->fromp(); + dumpAsyncRecurse(eFromVertexp, blank+" +--", blank+" | "); + } +} + +//###################################################################### +// Cdc class functions + +void V3Cdc::cdcAll(AstNetlist* nodep) { + UINFO(2,__FUNCTION__<<": "<iterateChildren(*this); - putfs(nodep, "endmodule\n"); + putqs(nodep, "endmodule\n"); } virtual void visit(AstNodeFTask* nodep, AstNUser*) { putfs(nodep, nodep->isFunction() ? "function":"task"); puts(" "); puts(nodep->name()); puts(";\n"); - putfs(nodep, "begin\n"); + putqs(nodep, "begin\n"); // Only putfs the first time for each visitor; later for same node is putqs nodep->stmtsp()->iterateAndNext(*this); - putfs(nodep, "end\n"); + putqs(nodep, "end\n"); } virtual void visit(AstBegin* nodep, AstNUser*) { @@ -88,17 +89,17 @@ class EmitVBaseVisitor : public EmitCBaseVisitor { virtual void visit(AstGenerate* nodep, AstNUser*) { putfs(nodep, "generate\n"); nodep->iterateChildren(*this); - putfs(nodep, "end\n"); + putqs(nodep, "end\n"); } virtual void visit(AstFinal* nodep, AstNUser*) { putfs(nodep, "final begin\n"); nodep->iterateChildren(*this); - putfs(nodep, "end\n"); + putqs(nodep, "end\n"); } virtual void visit(AstInitial* nodep, AstNUser*) { putfs(nodep,"initial begin\n"); nodep->iterateChildren(*this); - putfs(nodep, "end\n"); + putqs(nodep, "end\n"); } virtual void visit(AstAlways* nodep, AstNUser*) { putfs(nodep,"always "); @@ -106,7 +107,7 @@ class EmitVBaseVisitor : public EmitCBaseVisitor { else nodep->sensesp()->iterateAndNext(*this); putbs(" begin\n"); nodep->bodysp()->iterateAndNext(*this); - putfs(nodep,"end\n"); + putqs(nodep,"end\n"); } virtual void visit(AstNodeAssign* nodep, AstNUser*) { nodep->lhsp()->iterateAndNext(*this); @@ -139,7 +140,7 @@ class EmitVBaseVisitor : public EmitCBaseVisitor { putfs(nodep,"@("); for (AstNode* expp=nodep->sensesp(); expp; expp = expp->nextp()) { expp->accept(*this); - if (expp->nextp()) putfs(expp->nextp()," or "); + if (expp->nextp()) putqs(expp->nextp()," or "); } puts(")"); } @@ -165,7 +166,7 @@ class EmitVBaseVisitor : public EmitCBaseVisitor { } } nodep->itemsp()->iterateAndNext(*this); - putfs(nodep,"endcase\n"); + putqs(nodep,"endcase\n"); } virtual void visit(AstCaseItem* nodep, AstNUser*) { if (nodep->condsp()) { @@ -173,7 +174,7 @@ class EmitVBaseVisitor : public EmitCBaseVisitor { } else putbs("default"); putfs(nodep,": begin "); nodep->bodysp()->iterateAndNext(*this); - putfs(nodep,"end\n"); + putqs(nodep,"end\n"); } virtual void visit(AstComment* nodep, AstNUser*) { puts((string)"// "+nodep->name()+"\n"); @@ -253,7 +254,7 @@ class EmitVBaseVisitor : public EmitCBaseVisitor { m_suppressSemi = false; puts(") begin\n"); nodep->bodysp()->iterateAndNext(*this); - putfs(nodep,"end\n"); + putqs(nodep,"end\n"); } virtual void visit(AstRepeat* nodep, AstNUser*) { putfs(nodep,"repeat ("); @@ -277,11 +278,11 @@ class EmitVBaseVisitor : public EmitCBaseVisitor { puts(") begin\n"); nodep->ifsp()->iterateAndNext(*this); if (nodep->elsesp()) { - putfs(nodep,"end\n"); - putfs(nodep,"else begin\n"); + putqs(nodep,"end\n"); + putqs(nodep,"else begin\n"); nodep->elsesp()->iterateAndNext(*this); } - putfs(nodep,"end\n"); + putqs(nodep,"end\n"); } virtual void visit(AstStop* nodep, AstNUser*) { putfs(nodep,"$stop;\n"); @@ -437,12 +438,12 @@ class EmitVBaseVisitor : public EmitCBaseVisitor { // Terminals virtual void visit(AstVarRef* nodep, AstNUser*) { putfs(nodep,nodep->hiername()); - puts(nodep->varp()->name()); + puts(nodep->varp()->prettyName()); } virtual void visit(AstVarXRef* nodep, AstNUser*) { putfs(nodep,nodep->dotted()); puts("."); - puts(nodep->varp()->name()); + puts(nodep->varp()->prettyName()); } virtual void visit(AstConst* nodep, AstNUser*) { putfs(nodep,nodep->num().ascii(true,true)); @@ -458,13 +459,13 @@ class EmitVBaseVisitor : public EmitCBaseVisitor { virtual void visit(AstVar* nodep, AstNUser*) { putfs(nodep,nodep->verilogKwd()); puts(" "); - nodep->dtypep()->iterateChildren(*this); puts(" "); + nodep->dtypep()->iterateAndNext(*this); puts(" "); puts(nodep->name()); puts(";\n"); } virtual void visit(AstActive* nodep, AstNUser*) { m_sensesp = nodep->sensesp(); - nodep->iterateChildren(*this); + nodep->stmtsp()->iterateAndNext(*this); m_sensesp = NULL; } virtual void visit(AstVarScope*, AstNUser*) {} @@ -501,8 +502,8 @@ class EmitVFileVisitor : public EmitVBaseVisitor { virtual void puts(const string& str) { ofp()->puts(str); } virtual void putbs(const string& str) { ofp()->putbs(str); } virtual void putfs(AstNode*, const string& str) { putbs(str); } + virtual void putqs(AstNode*, const string& str) { putbs(str); } virtual void putsNoTracking(const string& str) { ofp()->putsNoTracking(str); } - public: EmitVFileVisitor(AstNode* nodep, V3OutFile* ofp) { m_ofp = ofp; @@ -518,12 +519,12 @@ class EmitVStreamVisitor : public EmitVBaseVisitor { // MEMBERS ostream& m_os; // METHODS - virtual void puts(const string& str) { m_os<accept(*this); @@ -531,6 +532,75 @@ public: virtual ~EmitVStreamVisitor() {} }; +//###################################################################### +// Emit to a stream (perhaps stringstream) + +class EmitVPrefixedFormatter : public V3OutFormatter { + ostream& m_os; + string m_prefix; // What to print at beginning of each line + int m_column; // Rough location; need just zero or non-zero + FileLine* m_prefixFl; + // METHODS + virtual void putcOutput(char chr) { + if (chr == '\n') { + m_column = 0; + m_os<ascii()+":"; + m_os<ascii().length()+1)); + m_os<<" "; + m_os<fileline(); // NETLIST's fileline instead of NULL to avoid NULL checks + } + virtual ~EmitVPrefixedFormatter() { + if (m_column) puts("\n"); + } +}; + +class EmitVPrefixedVisitor : public EmitVBaseVisitor { + // MEMBERS + EmitVPrefixedFormatter m_formatter; // Special verilog formatter (Way down the inheritance is another unused V3OutFormatter) + bool m_user3mark; // nodep->user3() if set means mark with %% + // METHODS + virtual void putsNoTracking(const string& str) { m_formatter.putsNoTracking(str); } + virtual void puts(const string& str) { m_formatter.puts(str); } + // We don't use m_formatter's putbs because the tokens will change filelines + // and insert returns at the proper locations + virtual void putbs(const string& str) { m_formatter.puts(str); } + virtual void putfs(AstNode* nodep, const string& str) { putfsqs(nodep,str,false); } + virtual void putqs(AstNode* nodep, const string& str) { putfsqs(nodep,str,true); } + void putfsqs(AstNode* nodep, const string& str, bool quiet) { + if (m_formatter.prefixFl() != nodep->fileline()) { + m_formatter.prefixFl(nodep->fileline()); + if (m_formatter.column()) puts("\n"); // This in turn will print the m_prefixFl + } + if (!quiet && nodep->user3()) puts("%%"); + putbs(str); + } + +public: + EmitVPrefixedVisitor(AstNode* nodep, ostream& os, const string& prefix, bool user3mark) + : m_formatter(os, prefix), m_user3mark(user3mark) { + if (user3mark) { AstUser3InUse::check(); } + nodep->accept(*this); + } + virtual ~EmitVPrefixedVisitor() {} +}; + //###################################################################### // EmitV class functions @@ -556,3 +626,7 @@ void V3EmitV::emitv() { void V3EmitV::verilogForTree(AstNode* nodep, ostream& os) { EmitVStreamVisitor(nodep, os); } + +void V3EmitV::verilogPrefixedTree(AstNode* nodep, ostream& os, const string& prefix, bool user3mark) { + EmitVPrefixedVisitor(nodep, os, prefix, user3mark); +} diff --git a/src/V3EmitV.h b/src/V3EmitV.h index 89447593b..47056839d 100644 --- a/src/V3EmitV.h +++ b/src/V3EmitV.h @@ -33,6 +33,7 @@ class V3EmitV { public: static void emitv(); static void verilogForTree(AstNode* nodep, ostream& os=cout); + static void verilogPrefixedTree(AstNode* nodep, ostream& os, const string& prefix, bool user3percent); }; #endif // Guard diff --git a/src/V3Error.h b/src/V3Error.h index 5a84104fa..ab0f150f0 100644 --- a/src/V3Error.h +++ b/src/V3Error.h @@ -56,6 +56,7 @@ public: CASEOVERLAP, // Case statements overlap CASEWITHX, // Case with X values CASEX, // Casex + CDCRSTLOGIC, // Logic in async reset path CMPCONST, // Comparison is constant due to limited range COMBDLY, // Combinatorial delayed assignment STMTDLY, // Delayed statement @@ -95,7 +96,7 @@ public: // Warnings " FIRST_WARN", "BLKANDNBLK", - "CASEINCOMPLETE", "CASEOVERLAP", "CASEWITHX", "CASEX", "CMPCONST", + "CASEINCOMPLETE", "CASEOVERLAP", "CASEWITHX", "CASEX", "CDCRSTLOGIC", "CMPCONST", "COMBDLY", "STMTDLY", "SYMRSVDWORD", "GENCLK", "IMPERFECTSCH", "IMPLICIT", "IMPURE", "LITENDIAN", "MULTIDRIVEN", "REDEFMACRO", @@ -185,7 +186,8 @@ inline void v3errorEnd(ostringstream& sstr) { V3Error::v3errorEnd(sstr); } // These allow errors using << operators: v3error("foo"<<"bar"); // Careful, you can't put () around msg, as you would in most macro definitions -#define v3warn(code,msg) v3errorEnd(((V3Error::v3errorPrep(V3ErrorCode::code)<MAXSPACE) num=MAXSPACE; - while (num>=8) { - *cp++ = '\t'; - num -= 8; + if (!m_verilog) { // verilogPrefixedTree doesn't want tabs + while (num>=8) { + *cp++ = '\t'; + num -= 8; + } } while (num>0) { *cp++ = ' '; diff --git a/src/V3File.h b/src/V3File.h index 01f7441c6..e4bf6f2e4 100644 --- a/src/V3File.h +++ b/src/V3File.h @@ -100,7 +100,7 @@ private: stack m_parenVec; // Stack of columns where last ( was int endLevels(const char* strg); - static const char* indentStr(int levels); + const char* indentStr(int levels); void putcNoTracking(char chr); public: diff --git a/src/V3Inst.cpp b/src/V3Inst.cpp index d06d894d3..2645ab4a8 100644 --- a/src/V3Inst.cpp +++ b/src/V3Inst.cpp @@ -122,6 +122,12 @@ private: nodep->unlinkFrBack()->deleteTree(); nodep=NULL; } + virtual void visit(AstUdpTable* nodep, AstNUser*) { + if (!v3Global.opt.bboxUnsup()) { + nodep->v3error("Unsupported: Verilog 1995 UDP Tables. Use --bbox-unsup to ignore tables."); + } + } + // Save some time virtual void visit(AstNodeAssign*, AstNUser*) {} virtual void visit(AstAlways*, AstNUser*) {} diff --git a/src/V3LinkResolve.cpp b/src/V3LinkResolve.cpp index 2d0e3de83..d91c6d9dd 100644 --- a/src/V3LinkResolve.cpp +++ b/src/V3LinkResolve.cpp @@ -340,7 +340,8 @@ private: virtual void visit(AstUdpTable* nodep, AstNUser*) { UINFO(5,"UDPTABLE "<v3error("Unsupported: Verilog 1995 UDP Tables. Use --bbox-unsup to ignore tables."); + // We don't warn until V3Inst, so that UDPs that are in libraries and + // never used won't result in any warnings. } else { // Massive hack, just tie off all outputs so our analysis can proceed AstVar* varoutp = NULL; diff --git a/src/V3Options.cpp b/src/V3Options.cpp index 8faa120db..c923d6820 100644 --- a/src/V3Options.cpp +++ b/src/V3Options.cpp @@ -660,6 +660,7 @@ void V3Options::parseOptsList(FileLine* fl, int argc, char** argv) { else if ( onoff (sw, "-bbox-sys", flag/*ref*/) ) { m_bboxSys = flag; } else if ( onoff (sw, "-bbox-unsup", flag/*ref*/) ) { m_bboxUnsup = flag; } else if ( !strcmp (sw, "-cc") ) { m_outFormatOk = true; m_systemC = false; m_systemPerl = false; } + else if ( onoff (sw, "-cdc", flag/*ref*/) ) { m_cdc = flag; } else if ( onoff (sw, "-coverage", flag/*ref*/) ) { coverage(flag); } else if ( onoff (sw, "-coverage-line", flag/*ref*/) ){ m_coverageLine = flag; } else if ( onoff (sw, "-coverage-toggle", flag/*ref*/) ){ m_coverageToggle = flag; } diff --git a/src/V3Options.h b/src/V3Options.h index 18eb07489..ee5ea96f9 100644 --- a/src/V3Options.h +++ b/src/V3Options.h @@ -96,6 +96,7 @@ class V3Options { bool m_autoflush; // main switch: --autoflush bool m_bboxSys; // main switch: --bbox-sys bool m_bboxUnsup; // main switch: --bbox-unsup + bool m_cdc; // main switch: --cdc bool m_coverageLine; // main switch: --coverage-block bool m_coverageToggle;// main switch: --coverage-toggle bool m_coverageUser; // main switch: --coverage-func @@ -208,6 +209,7 @@ class V3Options { bool autoflush() const { return m_autoflush; } bool bboxSys() const { return m_bboxSys; } bool bboxUnsup() const { return m_bboxUnsup; } + bool cdc() const { return m_cdc; } bool coverage() const { return m_coverageLine || m_coverageToggle || m_coverageUser; } bool coverageLine() const { return m_coverageLine; } bool coverageToggle() const { return m_coverageToggle; } diff --git a/src/Verilator.cpp b/src/Verilator.cpp index 93b0d7f5b..8dab53bb2 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -50,6 +50,7 @@ #include "V3EmitV.h" #include "V3Expand.h" #include "V3File.h" +#include "V3Cdc.h" #include "V3Gate.h" #include "V3Graph.h" #include "V3GenClk.h" @@ -352,6 +353,11 @@ void process () { V3Dead::deadifyAll(v3Global.rootp(), true); v3Global.rootp()->dumpTreeFile(v3Global.debugFilename("const.tree")); + // Clock domain crossing analysis + if (v3Global.opt.cdc()) { + V3Cdc::cdcAll(v3Global.rootp()); + } + // Reorder assignments in pipelined blocks if (v3Global.opt.oReorder()) { V3Split::splitReorderAll(v3Global.rootp()); diff --git a/test_regress/t/t_cdc_async_bad.pl b/test_regress/t/t_cdc_async_bad.pl new file mode 100755 index 000000000..6528c54ff --- /dev/null +++ b/test_regress/t/t_cdc_async_bad.pl @@ -0,0 +1,25 @@ +#!/usr/bin/perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2009 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. + +compile ( + v_flags => ['--cdc --lint-only'], + verilator_make_gcc => 0, + fails => 1, + expect=> +'%Warning-CDCRSTLOGIC: t/t_cdc_async_bad.v:\d+: Logic in path that feeds async reset, via signal: TOP->v.rst2_bad_n +%Warning-CDCRSTLOGIC: Use "/\* verilator lint_off CDCRSTLOGIC \*/" and lint_on around source to disable this message. +%Warning-CDCRSTLOGIC: See details in obj_dir/t_cdc_async_bad/Vt_cdc_async_bad__cdc.txt +%Warning-CDCRSTLOGIC: t/t_cdc_async_bad.v:\d+: Logic in path that feeds async reset, via signal: TOP->v.rst3_bad_n +%Error: Exiting due to.*', + ); + +file_grep ("$Self->{obj_dir}/V$Self->{name}__cdc.txt", qr/CDC Report/); + +ok(1); +1; diff --git a/test_regress/t/t_cdc_async_bad.v b/test_regress/t/t_cdc_async_bad.v new file mode 100644 index 000000000..5587464b9 --- /dev/null +++ b/test_regress/t/t_cdc_async_bad.v @@ -0,0 +1,72 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2009 by Wilson Snyder. + +module t (/*AUTOARG*/ + // Outputs + q0, q1, q2, q3, q4, q5, + // Inputs + clk, d, rst0_n + ); + input clk; + input d; + + // OK -- from primary + input rst0_n; + output wire q0; + Flop flop0 (.q(q0), .rst_n(rst0_n), .clk(clk), .d(d)); + + // OK -- from flop + reg rst1_n; + always @ (posedge clk) rst1_n <= rst0_n; + output wire q1; + Flop flop1 (.q(q1), .rst_n(rst1_n), .clk(clk), .d(d)); + + // Bad - logic + wire rst2_bad_n = rst0_n | rst1_n; + output wire q2; + Flop flop2 (.q(q2), .rst_n(rst2_bad_n), .clk(clk), .d(d)); + + // Bad - logic in submodule + wire rst3_bad_n; + Sub sub (.z(rst3_bad_n), .a(rst0_n), .b(rst1_n)); + output wire q3; + Flop flop3 (.q(q3), .rst_n(rst3_bad_n), .clk(clk), .d(d)); + + // OK - bit selection + reg [3:0] rst4_n; + always @ (posedge clk) rst4_n <= {4{rst0_n}}; + output wire q4; + Flop flop4 (.q(q4), .rst_n(rst4_n[1]), .clk(clk), .d(d)); + + // Bad - logic, but waived + // verilator lint_off CDCRSTLOGIC + wire rst5_waive_n = rst0_n & rst1_n; + // verilator lint_on CDCRSTLOGIC + output wire q5; + Flop flop5 (.q(q5), .rst_n(rst5_waive_n), .clk(clk), .d(d)); + + initial begin + $display("%%Error: Not a runnable test"); + $stop; + end + +endmodule + +module Flop ( + input clk, + input d, + input rst_n, + output q); + + always @ (posedge clk or negedge rst_n) begin + if (!rst_n) q <= 1'b0; + else q <= d; + end +endmodule + +module Sub (input a, b, + output z); + wire z = a|b; +endmodule