Clock domain crossing checks

This commit is contained in:
Wilson Snyder 2010-01-07 16:41:19 -05:00
parent a03a540156
commit bf860b21d7
17 changed files with 907 additions and 32 deletions

View File

@ -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]

View File

@ -193,6 +193,7 @@ descriptions in the next sections for more information.
--bbox-unsup Blackbox unsupported language features
--bin <filename> Override Verilator binary
--cc Create C++ output
--cdc Clock domain crossing analysis
--compiler <compiler-name> 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+<var>+<value> Set preprocessor define
+incdir+<dir> Directory to search for includes
+libext+<ext>+[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<compiler-name>
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:

View File

@ -155,6 +155,7 @@ RAW_OBJS = \
V3Broken.o \
V3Case.o \
V3Cast.o \
V3Cdc.o \
V3Changed.o \
V3Clean.o \
V3ClkGater.o \

629
src/V3Cdc.cpp Normal file
View File

@ -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 <cstdio>
#include <cstdarg>
#include <unistd.h>
#include <algorithm>
#include <iomanip>
#include <vector>
#include <deque>
#include <list>
#include <memory>
#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 "<<nodep<<endl);
m_logicVertexp = new CdcLogicVertex(&m_graph, m_scopep, nodep, m_domainp);
if (m_domainp && m_domainp->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 "<<varscp<<endl);
vertexp = new CdcVarVertex(&m_graph, m_scopep, varscp);
varscp->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()<column) out += ' ';
return out;
}
void analyze();
void analyzeReset();
void edgeReport() {
// Make report of all signal names and what clock edges they have
UINFO(3,__FUNCTION__<<": "<<endl);
// Trace all sources and sinks
for (int traceDests=0; traceDests<2; ++traceDests) {
UINFO(9, " Trace Direction "<<(traceDests?"dst":"src")<<endl);
m_graph.userClearVertices(); // user1: bool - was analyzed
for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp=itp->verticesNextp()) {
if (CdcVarVertex* vvertexp = dynamic_cast<CdcVarVertex*>(itp)) {
UINFO(9, " Trace One edge: "<<vvertexp<<endl);
edgeDomainRecurse(vvertexp, traceDests, 0);
}
}
}
string filename = v3Global.opt.makeDir()+"/"+v3Global.opt.prefix()+"__cdc_edges.txt";
const auto_ptr<ofstream> ofp (V3File::new_ofstream(filename));
if (ofp->fail()) v3fatalSrc("Can't write "<<filename);
*ofp<<"Edge Report for "<<v3Global.opt.prefix()<<endl;
deque<string> report; // Sort output by name
for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp=itp->verticesNextp()) {
if (CdcVarVertex* vvertexp = dynamic_cast<CdcVarVertex*>(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<<" "<<setw(8)<<whatp;
os<<" "<<setw(40)<<vvertexp->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<string>::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 "<<vertexp<<endl);
if (vertexp->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<AstSenTree*> SenSet;
SenSet senouts; // List of all sensitivities for new signal
if (CdcLogicVertex* vvertexp = dynamic_cast<CdcLogicVertex*>(vertexp)) {
if (vvertexp) {} // Unused
}
else if (CdcVarVertex* vvertexp = dynamic_cast<CdcVarVertex*>(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 "<<vertexp);
if (senoutp) V3EmitV::verilogForTree(senoutp, cout); cout<<endl;
}
} else {
vertexp->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 "<<vertexp);
if (senoutp) V3EmitV::verilogForTree(senoutp, cout); cout<<endl;
}
}
}
CdcEitherVertex* traceAsyncRecurse(CdcEitherVertex* vertexp, uint32_t did_value, bool mark);
void dumpAsync(CdcVarVertex* vertexp, CdcEitherVertex* markp);
void dumpAsyncRecurse(CdcEitherVertex* vertexp, const string& prefix, const string& blank);
void warnAndFile(AstNode* nodep, V3ErrorCode code, const string& msg) {
static bool told_file = false;
nodep->v3warnCode(code,msg);
if (!told_file) {
told_file = 1;
cerr<<V3Error::msgPrefix()<<" See details in "<<m_ofFilename<<endl;
}
*m_ofp<<"%Warning-"<<code.ascii()<<": "<<nodep->fileline()<<" "<<msg<<endl;
}
void clearNodeSafe(AstNode* nodep) {
// Need to not clear if warnings are off (rather than when report it)
// as bypassing this warning may turn up another path that isn't warning off'ed.
if (m_logicVertexp && !nodep->fileline()->warnIsOff(V3ErrorCode::CDCRSTLOGIC)) {
UINFO(9,"Clear safe "<<nodep<<endl);
m_logicVertexp->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 "<<nodep<<endl);
m_scopep = nodep;
m_logicVertexp = NULL;
nodep->iterateChildren(*this);
m_scopep = NULL;
}
virtual void visit(AstActive* nodep, AstNUser*) {
// Create required blocks and add to module
UINFO(4," BLOCK "<<nodep<<endl);
m_domainp = nodep->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 "<<varscp<<endl);
// We use weight of one; if we ref the var more than once, when we simplify,
// the weight will increase
if (nodep->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 "<<filename);
m_ofFilename = filename;
*m_ofp<<"CDC Report for "<<v3Global.opt.prefix()<<endl;
*m_ofp<<"Each dump below traces a variable from a flop back through logic.\n";
*m_ofp<<"First the variable is listed, then the logic that creates it, then all variables\n";
*m_ofp<<"feeding that logic, recursively backwards to the sourcing flop(s).\n";
*m_ofp<<"%% Indicates nodes considered potentially unsafe.\n";
nodep->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<<endl; }
}
virtual ~CdcVisitor() {
if (m_ofp) { delete m_ofp; m_ofp = NULL; }
}
};
//######################################################################
// Cdc class functions
class CdcDumpVisitor : public CdcBaseVisitor {
private:
// NODE STATE
//Entire netlist:
// {statement}Node::user3p -> bool, indicating not safe
ofstream* m_ofp; // Output file
string m_prefix;
virtual void visit(AstNode* nodep, AstNUser*) {
*m_ofp<<m_prefix;
if (nodep->user3()) *m_ofp<<" %%";
else *m_ofp<<" ";
*m_ofp<<nodep->prettyTypeName()<<" "<<endl;
string lastPrefix = m_prefix;
m_prefix = lastPrefix + "1:";
nodep->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__<<": "<<endl);
//if (debug()>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<CdcVarVertex*>(itp)) {
if (vvertexp->cntAsyncRst()) {
m_graph.userClearVertices(); // user1: bool - was analyzed
UINFO(9, " Trace One async: "<<vvertexp<<endl);
// Twice, as we need to detect, then propagate
CdcEitherVertex* markp = traceAsyncRecurse(vvertexp, 1, false);
if (markp) { // Mark is non-NULL if something bad on this path
UINFO(9, " Trace One bad! "<<vvertexp<<endl);
traceAsyncRecurse(vvertexp, 2, true);
m_graph.userClearVertices(); // user1: bool - was analyzed
dumpAsync(vvertexp, markp);
}
}
}
}
}
CdcEitherVertex* CdcVisitor::traceAsyncRecurse(CdcEitherVertex* vertexp, uint32_t did_value, bool mark) {
// Return vertex of any dangerous stuff attached, or NULL if OK
// If mark, also mark the output even if nothing dangerous below
CdcEitherVertex* mark_outp = NULL;
UINFO(9," Trace: "<<vertexp<<endl);
if (vertexp->user()>=did_value) return false; // Processed - prevent loop
vertexp->user(did_value);
if (CdcLogicVertex* vvertexp = dynamic_cast<CdcLogicVertex*>(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<CdcVarVertex*>(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: "<<vertexp->cntAsyncRst()<<" Target: "<<targetp->fileline()<<endl;
dumpAsyncRecurse(vertexp," +--", " | ");
}
void CdcVisitor::dumpAsyncRecurse(CdcEitherVertex* vertexp, const string& prefix, const string& blank) {
// Return true if any dangerous stuff attached
// If mark, also mark the output even if nothing dangerous below
if (vertexp->user()) return; // Processed - prevent loop
vertexp->user(1);
if (!vertexp->asyncPath()) return; // Not part of path
*m_ofp<<V3OutFile::indentSpaces(40)<<" "<<blank<<"\n";
// Dump single variable/logic block
// See also OrderGraph::loopsVertexCb(V3GraphVertex* vertexp)
{
AstNode* nodep = vertexp->nodep();
string front = pad(40,nodep->fileline()->ascii()+":");
front += " "+prefix+" ";
if (nodep->castVarScope()) *m_ofp<<front<<"Variable: "<<nodep->prettyName()<<endl;
else {
V3EmitV::verilogPrefixedTree(nodep, *m_ofp, prefix+" ", true);
if (debug()) {
CdcDumpVisitor visitor (nodep, m_ofp, front+"DBG: ");
}
}
}
// Now do the other logic in the path
for (V3GraphEdge* edgep = vertexp->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__<<": "<<endl);
CdcVisitor visitor (nodep);
}

37
src/V3Cdc.h Normal file
View File

@ -0,0 +1,37 @@
// -*- C++ -*-
//*************************************************************************
// DESCRIPTION: Verilator: Break always into sensitivity block domains
//
// 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.
//
//*************************************************************************
#ifndef _V3CDC_H_
#define _V3CDC_H_ 1
#include "config_build.h"
#include "verilatedos.h"
#include "V3Error.h"
#include "V3Ast.h"
//============================================================================
class V3Cdc {
public:
static void cdcAll(AstNetlist* nodep);
};
#endif // Guard

View File

@ -50,7 +50,8 @@ class EmitVBaseVisitor : public EmitCBaseVisitor {
virtual void puts(const string& str) = 0;
virtual void putbs(const string& str) = 0;
virtual void putfs(AstNode* nodep, const string& str) = 0;
virtual void putfs(AstNode* nodep, const string& str) = 0; // Fileline and node %% mark
virtual void putqs(AstNode* nodep, const string& str) = 0; // Fileline quiet w/o %% mark
virtual void putsNoTracking(const string& str) = 0;
virtual void putsQuoted(const string& str) {
// Quote \ and " for use inside C programs
@ -68,16 +69,16 @@ class EmitVBaseVisitor : public EmitCBaseVisitor {
virtual void visit(AstModule* nodep, AstNUser*) {
putfs(nodep, "module "+modClassName(nodep)+";\n");
nodep->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<<str; }
virtual void putbs(const string& str) { m_os<<str; }
virtual void putfs(AstNode*, const string& str) { putbs(str); }
virtual void putsNoTracking(const string& str) { m_os<<str; }
public:
virtual void puts(const string& str) { putsNoTracking(str); }
virtual void putbs(const string& str) { puts(str); }
virtual void putfs(AstNode*, const string& str) { putbs(str); }
virtual void putqs(AstNode*, const string& str) { putbs(str); }
public:
EmitVStreamVisitor(AstNode* nodep, ostream& os)
: m_os(os) {
nodep->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<<chr;
} else {
if (m_column == 0) {
m_column = 10;
m_os<<m_prefixFl->ascii()+":";
m_os<<V3OutFile::indentSpaces(40-(m_prefixFl->ascii().length()+1));
m_os<<" ";
m_os<<m_prefix;
}
m_column++;
m_os<<chr;
}
}
public:
void prefixFl(FileLine* fl) { m_prefixFl = fl; }
FileLine* prefixFl() const { return m_prefixFl; }
int column() const { return m_column; }
EmitVPrefixedFormatter(ostream& os, const string& prefix)
: V3OutFormatter("__STREAM", true), m_os(os), m_prefix(prefix) {
m_column = 0;
m_prefixFl = v3Global.rootp()->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);
}

View File

@ -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

View File

@ -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)<<msg),V3Error::v3errorStr()));
#define v3warnCode(code,msg) v3errorEnd(((V3Error::v3errorPrep(code)<<msg),V3Error::v3errorStr()));
#define v3warn(code,msg) v3warnCode(V3ErrorCode::code,msg)
#define v3info(msg) v3warn(INFO,msg)
#define v3fatal(msg) v3warn(FATAL,msg)
#define v3error(msg) v3warn(ERROR,msg)

View File

@ -269,9 +269,11 @@ const char* V3OutFormatter::indentStr(int num) {
static char str[MAXSPACE+20];
char* cp = str;
if (num>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++ = ' ';

View File

@ -100,7 +100,7 @@ private:
stack<int> 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:

View File

@ -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*) {}

View File

@ -340,7 +340,8 @@ private:
virtual void visit(AstUdpTable* nodep, AstNUser*) {
UINFO(5,"UDPTABLE "<<nodep<<endl);
if (!v3Global.opt.bboxUnsup()) {
nodep->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;

View File

@ -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; }

View File

@ -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; }

View File

@ -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());

View File

@ -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;

View File

@ -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