forked from github/verilator
Add toggle coverage
This commit is contained in:
parent
6b46da0240
commit
77405ddded
4
Changes
4
Changes
@ -5,7 +5,9 @@ indicates the contributor was also the author of the fix; Thanks!
|
||||
|
||||
* Verilator 3.700***
|
||||
|
||||
** Add /*verilator coverage_on/_off */.
|
||||
** Add --coverage_toggle for toggle coverage analysis.
|
||||
|
||||
*** Add /*verilator coverage_on/_off */ to bracket coverage regions.
|
||||
|
||||
*** Optimize two-level shift and and/or trees, +23% on one test.
|
||||
|
||||
|
@ -183,6 +183,7 @@ descriptions in the next sections for more information.
|
||||
--compiler <compiler-name> Tune for specified C++ compiler
|
||||
--coverage Enable all coverage
|
||||
--coverage-line Enable line coverage
|
||||
--coverage-toggle Enable toggle coverage
|
||||
--coverage-user Enable PSL/SVL user coverage
|
||||
-D<var>[=<value>] Set preprocessor define
|
||||
--debug Enable debugging
|
||||
@ -296,7 +297,8 @@ functions to avoid error C1061.
|
||||
|
||||
=item --coverage
|
||||
|
||||
Enables all forms of coverage, alias for --coverage-line, --coverage-user.
|
||||
Enables all forms of coverage, alias for "--coverage-line --coverage-toggle
|
||||
--coverage-user".
|
||||
|
||||
=item --coverage-line
|
||||
|
||||
@ -318,6 +320,37 @@ Note Verilator may over-count combinatorial (non-clocked) blocks when those
|
||||
blocks receive signals which have had the UNOPTFLAT warning disabled; for
|
||||
most accurate results do not disable this warning when using coverage.
|
||||
|
||||
=item --coverage-toggle
|
||||
|
||||
Specifies signal toggle coverage analysis code should be inserted.
|
||||
|
||||
Every bit of every signal in a module has a counter inserted. The counter
|
||||
will increment on every edge change of the corresponding bit.
|
||||
|
||||
Signals that are part of tasks or begin/end blocks are considered local
|
||||
variables and are not covered. Signals that begin with underscores, are
|
||||
integers, or are very wide (>256 bits total storage across all dimensions)
|
||||
are also not covered.
|
||||
|
||||
Hierarchy is compressed, such that if a module is instantiated multiple
|
||||
times, coverage will be summed for that bit across ALL instantiations of
|
||||
that module.
|
||||
|
||||
Verilator makes a minimally-intelligent decision about what clock domain
|
||||
the signal goes to, and only looks for edges in that clock domain. This
|
||||
means that edges may be ignored if it is known that the edge could never be
|
||||
seen by the receiving logic. This algorithm may improve in the future.
|
||||
The net result is coverage may be lower than what would be seen by looking
|
||||
at traces, but the coverage is a more accurate representation of the
|
||||
quality of stimulus into the design.
|
||||
|
||||
There may be edges counted near time zero while the model stabilizes. It's
|
||||
a good practice to zero all coverage just before releasing reset to prevent
|
||||
counting such behavior.
|
||||
|
||||
A /*verilator coverage_off/on */ comment pair can be used around signals
|
||||
that do not need toggle analysis, such as RAMs and register files.
|
||||
|
||||
=item --coverage-user
|
||||
|
||||
Enables user inserted functional coverage. Currently, all functional
|
||||
|
@ -140,6 +140,7 @@ RAW_OBJS = \
|
||||
V3Combine.o \
|
||||
V3Const__gen.o \
|
||||
V3Coverage.o \
|
||||
V3CoverageJoin.o \
|
||||
V3Dead.o \
|
||||
V3Delayed.o \
|
||||
V3Depth.o \
|
||||
|
@ -223,6 +223,13 @@ private:
|
||||
nodep->unlinkFrBack();
|
||||
wantactivep->addStmtsp(nodep);
|
||||
}
|
||||
virtual void visit(AstCoverToggle* nodep, AstNUser*) {
|
||||
// Relink to CACTIVE, unless already under it
|
||||
UINFO(4," COVERTOGGLE "<<nodep<<endl);
|
||||
AstActive* wantactivep = m_namer.getCActive(nodep->fileline());
|
||||
nodep->unlinkFrBack();
|
||||
wantactivep->addStmtsp(nodep);
|
||||
}
|
||||
virtual void visit(AstFinal* nodep, AstNUser*) {
|
||||
// Relink to CFUNC for the final
|
||||
UINFO(4," FINAL "<<nodep<<endl);
|
||||
|
@ -905,7 +905,7 @@ void AstNode::dumpTreeFile(const string& filename, bool append) {
|
||||
editCountSetLast();
|
||||
}
|
||||
|
||||
void AstNode::v3errorEnd(ostringstream& str) {
|
||||
void AstNode::v3errorEnd(ostringstream& str) const {
|
||||
if (this && m_fileline) {
|
||||
ostringstream nsstr;
|
||||
nsstr<<str.str();
|
||||
|
@ -731,7 +731,7 @@ public:
|
||||
AstNode* addNextNull(AstNode* newp); // Returns this, adds to end of list, NULL is OK
|
||||
void addNextHere(AstNode* newp); // Adds after speced node
|
||||
void replaceWith(AstNode* newp); // Replace current node in tree with new node
|
||||
void v3errorEnd(ostringstream& str);
|
||||
void v3errorEnd(ostringstream& str) const;
|
||||
virtual void dump(ostream& str=cout);
|
||||
AstNode* unlinkFrBack(AstNRelinker* linkerp=NULL); // Unlink this from whoever points to it.
|
||||
AstNode* unlinkFrBackWithNext(AstNRelinker* linkerp=NULL); // Unlink this from whoever points to it, keep entire next list with unlinked node
|
||||
|
@ -393,6 +393,12 @@ void AstNodeFTask::dump(ostream& str) {
|
||||
}
|
||||
void AstCoverDecl::dump(ostream& str) {
|
||||
this->AstNode::dump(str);
|
||||
if (this->dataDeclNullp()) {
|
||||
str<<" -> ";
|
||||
this->dataDeclNullp()->dump(str);
|
||||
} else {
|
||||
if (binNum()) { str<<" bin"<<dec<<binNum(); }
|
||||
}
|
||||
}
|
||||
void AstCoverInc::dump(ostream& str) {
|
||||
this->AstNode::dump(str);
|
||||
|
@ -97,6 +97,10 @@ struct AstArraySel : public AstNodeSel {
|
||||
:AstNodeSel(fl, fromp, bitp) {
|
||||
if (fromp) widthSignedFrom(fromp);
|
||||
}
|
||||
AstArraySel(FileLine* fl, AstNode* fromp, int bit)
|
||||
:AstNodeSel(fl, fromp, new AstConst(fl,bit)) {
|
||||
if (fromp) widthSignedFrom(fromp);
|
||||
}
|
||||
ASTNODE_NODE_FUNCS(ArraySel, ARRAYSEL)
|
||||
virtual void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs) {
|
||||
V3ERROR_NA; /* How can from be a const? */ }
|
||||
@ -320,6 +324,10 @@ public:
|
||||
|| varType()==AstVarType::REG || varType()==AstVarType::INTEGER); }
|
||||
bool isTemp() const { return (varType()==AstVarType::BLOCKTEMP || varType()==AstVarType::MODULETEMP
|
||||
|| varType()==AstVarType::STMTTEMP || varType()==AstVarType::XTEMP); }
|
||||
bool isToggleCoverable() const { return ((isIO() || isSignal())
|
||||
&& varType()!=AstVarType::INTEGER
|
||||
// Wrapper would otherwise duplicate wrapped module's coverage
|
||||
&& !isSc() && !isPrimaryIO()); }
|
||||
bool isStatementTemp() const { return (varType()==AstVarType::STMTTEMP); }
|
||||
bool isMovableToBlock() const { return (varType()==AstVarType::BLOCKTEMP || isFuncLocal()); }
|
||||
bool isPure() const { return (varType()==AstVarType::XTEMP); }
|
||||
@ -1002,6 +1010,7 @@ struct AstCoverDecl : public AstNodeStmt {
|
||||
// Parents: {statement list}
|
||||
// Children: none
|
||||
private:
|
||||
AstCoverDecl* m_dataDeclp; // [After V3CoverageJoin] Pointer to duplicate declaration to get data from instead
|
||||
string m_typeText;
|
||||
string m_text;
|
||||
string m_hier;
|
||||
@ -1012,8 +1021,14 @@ public:
|
||||
: AstNodeStmt(fl) {
|
||||
m_text = comment; m_typeText = type; m_column = column;
|
||||
m_binNum = 0;
|
||||
m_dataDeclp = NULL;
|
||||
}
|
||||
ASTNODE_NODE_FUNCS(CoverDecl, COVERDECL)
|
||||
virtual bool broken() const {
|
||||
if (m_dataDeclp && !m_dataDeclp->brokeExists()) return true;
|
||||
if (m_dataDeclp && m_dataDeclp->m_dataDeclp) v3fatalSrc("dataDeclp should point to real data, not be a list"); // Avoid O(n^2) accessing
|
||||
return false; }
|
||||
virtual void cloneRelink() { if (m_dataDeclp && m_dataDeclp->clonep()) m_dataDeclp = m_dataDeclp->clonep()->castCoverDecl(); }
|
||||
virtual void dump(ostream& str);
|
||||
virtual int instrCount() const { return 1+2*instrCountLd(); }
|
||||
virtual bool maybePointedTo() const { return true; }
|
||||
@ -1032,6 +1047,10 @@ public:
|
||||
&& comment()==samep->castCoverDecl()->comment()
|
||||
&& column()==samep->castCoverDecl()->column()); }
|
||||
virtual bool isPredictOptimizable() const { return false; }
|
||||
void dataDeclp(AstCoverDecl* nodep) { m_dataDeclp=nodep; }
|
||||
// dataDecl NULL means "use this one", but often you want "this" to indicate to get data from here
|
||||
AstCoverDecl* dataDeclNullp() const { return m_dataDeclp; }
|
||||
AstCoverDecl* dataDeclThisp() { return dataDeclNullp()?dataDeclNullp():this; }
|
||||
};
|
||||
|
||||
struct AstCoverInc : public AstNodeStmt {
|
||||
@ -1060,6 +1079,30 @@ public:
|
||||
AstCoverDecl* declp() const { return m_declp; } // Where defined
|
||||
};
|
||||
|
||||
struct AstCoverToggle : public AstNodeStmt {
|
||||
// Toggle analysis of given signal
|
||||
// Parents: MODULE
|
||||
// Children: AstCoverInc, orig var, change det var
|
||||
AstCoverToggle(FileLine* fl, AstCoverInc* incp, AstNode* origp, AstNode* changep)
|
||||
: AstNodeStmt(fl) {
|
||||
setOp1p(incp);
|
||||
setOp2p(origp);
|
||||
setOp3p(changep);
|
||||
}
|
||||
ASTNODE_NODE_FUNCS(CoverToggle, COVERTOGGLE)
|
||||
virtual int instrCount() const { return 3+instrCountBranch()+instrCountLd(); }
|
||||
virtual V3Hash sameHash() const { return V3Hash(); }
|
||||
virtual bool same(AstNode*) const { return true; }
|
||||
virtual bool isGateOptimizable() const { return false; }
|
||||
virtual bool isPredictOptimizable() const { return true; }
|
||||
virtual bool isOutputter() const { return false; } // Though the AstCoverInc under this is an outputter
|
||||
// but isSplittable() true
|
||||
AstCoverInc* incp() const { return op1p()->castCoverInc(); }
|
||||
void incp(AstCoverInc* nodep) { setOp1p(nodep); }
|
||||
AstNode* origp() const { return op2p(); }
|
||||
AstNode* changep() const { return op3p(); }
|
||||
};
|
||||
|
||||
struct AstGenCase : public AstNodeCase {
|
||||
// Generate Case statement
|
||||
// Parents: {statement list}
|
||||
|
@ -282,6 +282,26 @@ private:
|
||||
}
|
||||
nodep->deleteTree(); nodep = NULL;
|
||||
}
|
||||
virtual void visit(AstCoverToggle* nodep, AstNUser*) {
|
||||
//nodep->dumpTree(cout,"ct:");
|
||||
//COVERTOGGLE(INC, ORIG, CHANGE) ->
|
||||
// IF(ORIG ^ CHANGE) { INC; CHANGE = ORIG; }
|
||||
AstNode* incp = nodep->incp()->unlinkFrBack();
|
||||
AstNode* origp = nodep->origp()->unlinkFrBack();
|
||||
AstNode* changep = nodep->changep()->unlinkFrBack();
|
||||
AstIf* newp = new AstIf(nodep->fileline(),
|
||||
new AstXor(nodep->fileline(),
|
||||
origp,
|
||||
changep),
|
||||
incp, NULL);
|
||||
// We could add another IF to detect posedges, and only increment if so.
|
||||
// It's another whole branch though verus a potential memory miss.
|
||||
// We'll go with the miss.
|
||||
newp->addIfsp(new AstAssign(nodep->fileline(),
|
||||
changep->cloneTree(false),
|
||||
origp->cloneTree(false)));
|
||||
nodep->replaceWith(newp); nodep->deleteTree(); nodep=NULL;
|
||||
}
|
||||
virtual void visit(AstInitial* nodep, AstNUser*) {
|
||||
AstNode* cmtp = new AstComment(nodep->fileline(), nodep->typeName());
|
||||
nodep->replaceWith(cmtp);
|
||||
|
@ -50,12 +50,29 @@ private:
|
||||
// STATE
|
||||
bool m_checkBlock; // Should this block get covered?
|
||||
AstModule* m_modp; // Current module to add statement to
|
||||
bool m_inToggleOff; // In function/task etc
|
||||
FileMap m_fileps; // Column counts for each fileline
|
||||
string m_beginHier; // AstBegin hier name for user coverage points
|
||||
|
||||
//int debug() { return 9; }
|
||||
|
||||
// METHODS
|
||||
const char* varIgnoreToggle(AstVar* nodep) {
|
||||
// Return true if this shouldn't be traced
|
||||
// See also similar rule in V3TraceDecl::varIgnoreTrace
|
||||
string prettyName = nodep->prettyName();
|
||||
if (!nodep->isToggleCoverable())
|
||||
return "Not relevant signal type";
|
||||
if (prettyName.c_str()[0] == '_')
|
||||
return "Leading underscore";
|
||||
if (prettyName.find("._") != string::npos)
|
||||
return "Inlined leading underscore";
|
||||
if ((nodep->width()*nodep->arrayElements()) > 256) return "Wide bus/array > 256 bits";
|
||||
// We allow this, though tracing doesn't
|
||||
// if (nodep->arrayp(1)) return "Unsupported: Multi-dimensional array";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
AstCoverInc* newCoverInc(FileLine* fl, const string& hier,
|
||||
const string& type, const string& comment) {
|
||||
int column = 0;
|
||||
@ -81,6 +98,105 @@ private:
|
||||
m_modp = NULL;
|
||||
}
|
||||
|
||||
// VISITORS - TOGGLE COVERAGE
|
||||
virtual void visit(AstNodeFTask* nodep, AstNUser*) {
|
||||
bool oldtog = m_inToggleOff;
|
||||
{
|
||||
m_inToggleOff = true;
|
||||
nodep->iterateChildren(*this);
|
||||
}
|
||||
m_inToggleOff = oldtog;
|
||||
}
|
||||
virtual void visit(AstVar* nodep, AstNUser*) {
|
||||
nodep->iterateChildren(*this);
|
||||
if (m_modp && !m_inToggleOff
|
||||
&& nodep->fileline()->coverageOn() && v3Global.opt.coverageToggle()) {
|
||||
const char* disablep = varIgnoreToggle(nodep);
|
||||
if (disablep) {
|
||||
UINFO(4, " Disable Toggle: "<<disablep<<" "<<nodep<<endl);
|
||||
} else {
|
||||
UINFO(4, " Toggle: "<<nodep<<endl);
|
||||
// There's several overall ways to approach this
|
||||
// Treat like tracing, where a end-of-timestamp action sees all changes
|
||||
// Works ok, but would be quite slow as need to reform vectors before the calls
|
||||
// Convert to "always @ (posedge signal[#]) coverinc"
|
||||
// Would mark many signals as clocks, precluding many later optimizations
|
||||
// Convert to "if (x & !lastx) CoverInc"
|
||||
// OK, but we couldn't later detect them to schedule where the IFs get called
|
||||
// Convert to "AstCoverInc(CoverInc...)"
|
||||
// We'll do this, and make the if(...) coverinc later.
|
||||
|
||||
// Compute size of the problem
|
||||
int dimensions = 0;
|
||||
for (AstRange* arrayp=nodep->arraysp(); arrayp; arrayp = arrayp->nextp()->castRange()) {
|
||||
dimensions++;
|
||||
}
|
||||
|
||||
// Add signal to hold the old value
|
||||
string newvarname = (string)"__Vtogcov__"+nodep->shortName();
|
||||
AstVar* chgVarp = new AstVar (nodep->fileline(), AstVarType::MODULETEMP, newvarname, nodep);
|
||||
m_modp->addStmtp(chgVarp);
|
||||
|
||||
// Create bucket for each dimension * bit.
|
||||
// This is necessarily an O(n^2) expansion, which is why
|
||||
// we limit coverage to signals with < 256 bits.
|
||||
vector<int> selects_docs; selects_docs.resize(dimensions);
|
||||
vector<int> selects_code; selects_code.resize(dimensions);
|
||||
toggleVarRecurse(nodep, chgVarp, nodep->arraysp(),
|
||||
0, selects_docs, selects_code );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void toggleVarRecurse(AstVar* nodep, AstVar* chgVarp, AstRange* arrayp,
|
||||
int dimension, vector<int>& selects_docs, vector<int>& selects_code) {
|
||||
if (arrayp) {
|
||||
for (int index=arrayp->lsbConst(); index<=arrayp->msbConst()+1; index++) {
|
||||
// Handle the next dimension, if any
|
||||
selects_docs[dimension] = index;
|
||||
selects_code[dimension] = index - arrayp->lsbConst();
|
||||
toggleVarRecurse(nodep, chgVarp, arrayp->nextp()->castRange(),
|
||||
dimension+1, selects_docs, selects_code);
|
||||
}
|
||||
} else { // No more arraying - just each bit in the width
|
||||
if (nodep->rangep()) {
|
||||
for (int bitindex_docs=nodep->lsb(); bitindex_docs<nodep->msb()+1; bitindex_docs++) {
|
||||
toggleVarBottom(nodep, chgVarp,
|
||||
dimension, selects_docs, selects_code,
|
||||
true, bitindex_docs);
|
||||
}
|
||||
} else {
|
||||
toggleVarBottom(nodep, chgVarp,
|
||||
dimension, selects_docs, selects_code,
|
||||
false, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
void toggleVarBottom(AstVar* nodep, AstVar* chgVarp,
|
||||
int dimension, vector<int>& selects_docs, vector<int>& selects_code,
|
||||
bool bitsel, int bitindex_docs) {
|
||||
string comment = nodep->name();
|
||||
AstNode* varRefp = new AstVarRef(nodep->fileline(), nodep, false);
|
||||
AstNode* chgRefp = new AstVarRef(nodep->fileline(), chgVarp, true);
|
||||
// Now determine the name of, and how to get to the bit of this slice
|
||||
for (int dim=0; dim<dimension; dim++) {
|
||||
// Comments are strings, not symbols, so we don't need __BRA__ __KET__
|
||||
comment += "["+cvtToStr(selects_docs[dim])+"]";
|
||||
varRefp = new AstArraySel(nodep->fileline(), varRefp, selects_code[dim]);
|
||||
chgRefp = new AstArraySel(nodep->fileline(), chgRefp, selects_code[dim]);
|
||||
}
|
||||
if (bitsel) {
|
||||
comment += "["+cvtToStr(bitindex_docs)+"]";
|
||||
int bitindex_code = bitindex_docs - nodep->lsb();
|
||||
varRefp = new AstSel(nodep->fileline(), varRefp, bitindex_code, 1);
|
||||
chgRefp = new AstSel(nodep->fileline(), chgRefp, bitindex_code, 1);
|
||||
}
|
||||
AstCoverToggle* newp = new AstCoverToggle (nodep->fileline(),
|
||||
newCoverInc(nodep->fileline(), "", "v_toggle", comment),
|
||||
varRefp, chgRefp);
|
||||
m_modp->addStmtp(newp);
|
||||
}
|
||||
|
||||
// VISITORS - LINE COVERAGE
|
||||
virtual void visit(AstIf* nodep, AstNUser*) {
|
||||
UINFO(4," IF: "<<nodep<<endl);
|
||||
@ -154,13 +270,16 @@ private:
|
||||
// (Currently ignored for line coverage, since any generate iteration
|
||||
// covers the code in that line.)
|
||||
string oldHier = m_beginHier;
|
||||
bool oldtog = m_inToggleOff;
|
||||
{
|
||||
m_inToggleOff = true;
|
||||
if (nodep->name()!="") {
|
||||
m_beginHier = m_beginHier + (m_beginHier!=""?".":"") + nodep->name();
|
||||
}
|
||||
nodep->iterateChildren(*this);
|
||||
}
|
||||
m_beginHier = oldHier;
|
||||
m_inToggleOff = oldtog;
|
||||
}
|
||||
|
||||
// VISITORS - BOTH
|
||||
@ -178,6 +297,7 @@ public:
|
||||
// Operate on all modules
|
||||
m_checkBlock = true;
|
||||
m_beginHier = "";
|
||||
m_inToggleOff = false;
|
||||
rootp->iterateChildren(*this);
|
||||
}
|
||||
virtual ~CoverageVisitor() {}
|
||||
|
134
src/V3CoverageJoin.cpp
Normal file
134
src/V3CoverageJoin.cpp
Normal file
@ -0,0 +1,134 @@
|
||||
//*************************************************************************
|
||||
// DESCRIPTION: Verilator: Netlist (top level) functions
|
||||
//
|
||||
// Code available from: http://www.veripool.org/verilator
|
||||
//
|
||||
// AUTHORS: Wilson Snyder with Paul Wasson, Duane Gabli
|
||||
//
|
||||
//*************************************************************************
|
||||
//
|
||||
// Copyright 2003-2008 by Wilson Snyder. This program is free software; you can
|
||||
// redistribute it and/or modify it under the terms of either the GNU
|
||||
// General Public License or the Perl Artistic License.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
//*************************************************************************
|
||||
// COVERAGEJOIN TRANSFORMATIONS:
|
||||
// If two COVERTOGGLEs have same VARSCOPE, combine them
|
||||
//*************************************************************************
|
||||
|
||||
|
||||
#include "config_build.h"
|
||||
#include "verilatedos.h"
|
||||
#include <cstdio>
|
||||
#include <cstdarg>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
#include "V3Global.h"
|
||||
#include "V3CoverageJoin.h"
|
||||
#include "V3Hashed.h"
|
||||
#include "V3Stats.h"
|
||||
|
||||
//######################################################################
|
||||
// CoverageJoin state, as a visitor of each AstNode
|
||||
|
||||
class CoverageJoinVisitor : public AstNVisitor {
|
||||
private:
|
||||
// NODE STATE
|
||||
// V3Hashed
|
||||
// AstCoverToggle->VarRef::user4() // V3Hashed calculation
|
||||
|
||||
//AstUser4InUse In V3Hashed
|
||||
|
||||
// TYPES
|
||||
typedef vector<AstCoverToggle*> ToggleList;
|
||||
|
||||
// STATE
|
||||
ToggleList m_toggleps; // List of of all AstCoverToggle's
|
||||
|
||||
V3Double0 m_statToggleJoins; // Statistic tracking
|
||||
|
||||
//int debug() { return 9; }
|
||||
|
||||
// METHODS
|
||||
void detectDuplicates() {
|
||||
UINFO(9,"Finding duplicates\n");
|
||||
// Note uses user4
|
||||
V3Hashed hashed; // Duplicate code detection
|
||||
// Hash all of the original signals we toggle cover
|
||||
for (ToggleList::iterator it = m_toggleps.begin(); it != m_toggleps.end(); ++it) {
|
||||
AstCoverToggle* nodep = *it;
|
||||
hashed.hashAndInsert(nodep->origp());
|
||||
}
|
||||
// Find if there are any duplicates
|
||||
for (ToggleList::iterator it = m_toggleps.begin(); it != m_toggleps.end(); ++it) {
|
||||
AstCoverToggle* nodep = *it;
|
||||
if (nodep->backp()) { // nodep->backp() is null if we already detected it's a duplicate and unlinked it
|
||||
// Want to choose a base node, and keep finding duplicates that are identical
|
||||
// This prevents making chains where a->b, then c->d, then b->c, as we'll find a->b, a->c, a->d directly.
|
||||
while (1) {
|
||||
V3Hashed::iterator dupit = hashed.findDuplicate(nodep->origp());
|
||||
if (dupit == hashed.end()) break;
|
||||
//
|
||||
AstNode* duporigp = hashed.iteratorNodep(dupit);
|
||||
// Note hashed will point to the original variable (what's duplicated), not the covertoggle,
|
||||
// but we need to get back to the covertoggle which is immediately above, so:
|
||||
AstCoverToggle* removep = duporigp->backp()->castCoverToggle();
|
||||
if (!removep) nodep->v3fatalSrc("CoverageJoin duplicate of wrong type");
|
||||
UINFO(8," Orig "<<nodep<<" -->> "<<nodep->incp()->declp()<<endl);
|
||||
UINFO(8," dup "<<removep<<" -->> "<<removep->incp()->declp()<<endl);
|
||||
// The CoverDecl the duplicate pointed to now needs to point to the original's data
|
||||
// IE the duplicate will get the coverage number from the non-duplicate
|
||||
AstCoverDecl* datadeclp = nodep->incp()->declp()->dataDeclThisp();
|
||||
removep->incp()->declp()->dataDeclp (datadeclp);
|
||||
UINFO(8," new "<<removep->incp()->declp()<<endl);
|
||||
// Mark the found node as a duplicate of the first node
|
||||
// (Not vice-versa as we have the iterator for the found node)
|
||||
removep->unlinkFrBack(); pushDeletep(removep); removep=NULL;
|
||||
// Remove node from comparison so don't hit it again
|
||||
hashed.erase(dupit);
|
||||
m_statToggleJoins++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// VISITORS
|
||||
virtual void visit(AstNetlist* nodep, AstNUser*) {
|
||||
// Find all Coverage's
|
||||
nodep->iterateChildren(*this);
|
||||
// Simplify
|
||||
detectDuplicates();
|
||||
}
|
||||
virtual void visit(AstCoverToggle* nodep, AstNUser*) {
|
||||
m_toggleps.push_back(nodep);
|
||||
nodep->iterateChildren(*this);
|
||||
}
|
||||
//--------------------
|
||||
virtual void visit(AstNodeMath* nodep, AstNUser*) {} // Accelerate
|
||||
virtual void visit(AstNode* nodep, AstNUser*) {
|
||||
nodep->iterateChildren(*this);
|
||||
}
|
||||
|
||||
public:
|
||||
// CONSTUCTORS
|
||||
CoverageJoinVisitor(AstNetlist* nodep) {
|
||||
nodep->accept(*this);
|
||||
}
|
||||
virtual ~CoverageJoinVisitor() {
|
||||
V3Stats::addStat("Coverage, Toggle points joined", m_statToggleJoins);
|
||||
}
|
||||
};
|
||||
|
||||
//######################################################################
|
||||
// Coverage class functions
|
||||
|
||||
void V3CoverageJoin::coverageJoin(AstNetlist* rootp) {
|
||||
UINFO(2,__FUNCTION__<<": "<<endl);
|
||||
CoverageJoinVisitor visitor (rootp);
|
||||
}
|
37
src/V3CoverageJoin.h
Normal file
37
src/V3CoverageJoin.h
Normal file
@ -0,0 +1,37 @@
|
||||
// -*- C++ -*-
|
||||
//*************************************************************************
|
||||
// DESCRIPTION: Verilator: Coverage modules/signals together
|
||||
//
|
||||
// Code available from: http://www.veripool.org/verilator
|
||||
//
|
||||
// AUTHORS: Wilson Snyder with Paul Wasson, Duane Gabli
|
||||
//
|
||||
//*************************************************************************
|
||||
//
|
||||
// Copyright 2003-2008 by Wilson Snyder. This program is free software; you can
|
||||
// redistribute it and/or modify it under the terms of either the GNU
|
||||
// General Public License or the Perl Artistic License.
|
||||
//
|
||||
// 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 _V3COVERAGEJOIN_H_
|
||||
#define _V3COVERAGEJOIN_H_ 1
|
||||
#include "config_build.h"
|
||||
#include "verilatedos.h"
|
||||
#include "V3Error.h"
|
||||
#include "V3Ast.h"
|
||||
|
||||
//============================================================================
|
||||
|
||||
class V3CoverageJoin {
|
||||
public:
|
||||
// CREATORS
|
||||
static void coverageJoin(AstNetlist* rootp);
|
||||
};
|
||||
|
||||
#endif // Guard
|
@ -191,7 +191,7 @@ public:
|
||||
virtual void visit(AstCoverDecl* nodep, AstNUser*) {
|
||||
puts("__vlCoverInsert("); // As Declared in emitCoverageDecl
|
||||
puts("&(vlSymsp->__Vcoverage[");
|
||||
puts(cvtToStr(nodep->binNum())); puts("])");
|
||||
puts(cvtToStr(nodep->dataDeclThisp()->binNum())); puts("])");
|
||||
// If this isn't the first instantiation of this module under this
|
||||
// design, don't really count the bucket, and rely on SystemPerl to
|
||||
// aggregate counts. This is because Verilator combines all
|
||||
@ -208,7 +208,7 @@ public:
|
||||
}
|
||||
virtual void visit(AstCoverInc* nodep, AstNUser*) {
|
||||
puts("++(vlSymsp->__Vcoverage[");
|
||||
puts(cvtToStr(nodep->declp()->binNum()));
|
||||
puts(cvtToStr(nodep->declp()->dataDeclThisp()->binNum()));
|
||||
puts("]);\n");
|
||||
}
|
||||
virtual void visit(AstCReturn* nodep, AstNUser*) {
|
||||
|
@ -93,7 +93,9 @@ class EmitCSyms : EmitCBaseVisitor {
|
||||
}
|
||||
virtual void visit(AstCoverDecl* nodep, AstNUser*) {
|
||||
// Assign numbers to all bins, so we know how big of an array to use
|
||||
nodep->binNum(m_coverBins++);
|
||||
if (!nodep->dataDeclNullp()) { // else duplicate we don't need code for
|
||||
nodep->binNum(m_coverBins++);
|
||||
}
|
||||
}
|
||||
// NOPs
|
||||
virtual void visit(AstNodeStmt*, AstNUser*) {}
|
||||
|
@ -159,6 +159,7 @@ class EmitVBaseVisitor : public EmitCBaseVisitor {
|
||||
}
|
||||
virtual void visit(AstCoverDecl*, AstNUser*) {} // N/A
|
||||
virtual void visit(AstCoverInc*, AstNUser*) {} // N/A
|
||||
virtual void visit(AstCoverToggle*, AstNUser*) {} // N/A
|
||||
|
||||
void visitNodeDisplay(AstNode* nodep, AstNode* filep, const string& text, AstNode* exprsp) {
|
||||
putbs(nodep->verilogKwd());
|
||||
|
@ -372,6 +372,9 @@ private:
|
||||
virtual void visit(AstAssignW* nodep, AstNUser*) {
|
||||
iterateNewStmt(nodep, NULL, NULL);
|
||||
}
|
||||
virtual void visit(AstCoverToggle* nodep, AstNUser*) {
|
||||
iterateNewStmt(nodep, "CoverToggle", "CoverToggle");
|
||||
}
|
||||
virtual void visit(AstTraceInc* nodep, AstNUser*) {
|
||||
bool lastslow = m_inSlow;
|
||||
m_inSlow = true;
|
||||
|
@ -607,6 +607,7 @@ void V3Options::parseOptsList(FileLine* fl, int argc, char** argv) {
|
||||
else if ( !strcmp (sw, "-cc") ) { m_outFormatOk = true; m_systemC = false; m_systemPerl = false; }
|
||||
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; }
|
||||
else if ( onoff (sw, "-coverage-user", flag/*ref*/) ){ m_coverageUser = flag; }
|
||||
else if ( onoff (sw, "-covsp", flag/*ref*/) ) { } // TBD
|
||||
else if ( onoff (sw, "-debug-check", flag/*ref*/) ){ m_debugCheck = flag; }
|
||||
@ -854,6 +855,7 @@ V3Options::V3Options() {
|
||||
|
||||
m_autoflush = false;
|
||||
m_coverageLine = false;
|
||||
m_coverageToggle = false;
|
||||
m_coverageUser = false;
|
||||
m_debugCheck = false;
|
||||
m_dumpTree = false;
|
||||
|
@ -89,6 +89,7 @@ class V3Options {
|
||||
bool m_assert; // main switch: --assert
|
||||
bool m_autoflush; // main switch: --autoflush
|
||||
bool m_coverageLine; // main switch: --coverage-block
|
||||
bool m_coverageToggle;// main switch: --coverage-toggle
|
||||
bool m_coverageUser; // main switch: --coverage-func
|
||||
bool m_debugCheck; // main switch: --debug-check
|
||||
bool m_dumpTree; // main switch: --dump-tree
|
||||
@ -159,7 +160,7 @@ class V3Options {
|
||||
void addIncDir(const string& incdir);
|
||||
void addLibExt(const string& libext);
|
||||
void optimize(int level);
|
||||
void coverage(bool flag) { m_coverageLine = m_coverageUser = flag; }
|
||||
void coverage(bool flag) { m_coverageLine = m_coverageToggle = m_coverageUser = flag; }
|
||||
bool onoff(const char* sw, const char* arg, bool& flag);
|
||||
static bool wildmatchi(const char* s, const char* p);
|
||||
static string getenvStr(const char* envvar, const char* defaultValue);
|
||||
@ -188,8 +189,9 @@ class V3Options {
|
||||
bool stats() const { return m_stats; }
|
||||
bool assertOn() const { return m_assert; } // assertOn as "assert" may be defined
|
||||
bool autoflush() const { return m_autoflush; }
|
||||
bool coverage() const { return m_coverageUser || m_coverageLine; }
|
||||
bool coverage() const { return m_coverageLine || m_coverageToggle || m_coverageUser; }
|
||||
bool coverageLine() const { return m_coverageLine; }
|
||||
bool coverageToggle() const { return m_coverageToggle; }
|
||||
bool coverageUser() const { return m_coverageUser; }
|
||||
bool debugCheck() const { return m_debugCheck; }
|
||||
bool dumpTree() const { return m_dumpTree; }
|
||||
|
@ -674,6 +674,9 @@ private:
|
||||
iterateNewStmt(nodep);
|
||||
m_inPost = false;
|
||||
}
|
||||
virtual void visit(AstCoverToggle* nodep, AstNUser*) {
|
||||
iterateNewStmt(nodep);
|
||||
}
|
||||
virtual void visit(AstCFunc*, AstNUser*) {
|
||||
// Ignore for now
|
||||
// We should detect what variables are set in the function, and make
|
||||
|
@ -150,7 +150,14 @@ private:
|
||||
virtual void visit(AstAlways* nodep, AstNUser*) {
|
||||
// Add to list of blocks under this scope
|
||||
UINFO(4," Move "<<nodep<<endl);
|
||||
AstAlways* clonep = nodep->cloneTree(false);
|
||||
AstNode* clonep = nodep->cloneTree(false);
|
||||
m_scopep->addActivep(clonep);
|
||||
clonep->iterateChildren(*this); // We iterate under the *clone*
|
||||
}
|
||||
virtual void visit(AstCoverToggle* nodep, AstNUser*) {
|
||||
// Add to list of blocks under this scope
|
||||
UINFO(4," Move "<<nodep<<endl);
|
||||
AstNode* clonep = nodep->cloneTree(false);
|
||||
m_scopep->addActivep(clonep);
|
||||
clonep->iterateChildren(*this); // We iterate under the *clone*
|
||||
}
|
||||
@ -279,6 +286,9 @@ private:
|
||||
virtual void visit(AstAlways* nodep, AstNUser*) {
|
||||
movedDeleteOrIterate(nodep);
|
||||
}
|
||||
virtual void visit(AstCoverToggle* nodep, AstNUser*) {
|
||||
movedDeleteOrIterate(nodep);
|
||||
}
|
||||
virtual void visit(AstNodeFTask* nodep, AstNUser*) {
|
||||
movedDeleteOrIterate(nodep);
|
||||
}
|
||||
|
@ -624,6 +624,7 @@ private:
|
||||
|
||||
AstVarScope* findDuplicateTable(AstVarScope* vsc1p) {
|
||||
// See if another table we've created is identical, if so use it for both.
|
||||
// (A more 'modern' way would be to instead use V3Hashed::findDuplicate)
|
||||
AstVar* var1p = vsc1p->varp();
|
||||
for (deque<AstVarScope*>::iterator it = m_modTableVscs.begin(); it!=m_modTableVscs.end(); ++it) {
|
||||
AstVarScope* vsc2p= *it;
|
||||
|
@ -59,6 +59,7 @@ private:
|
||||
// METHODS
|
||||
const char* varIgnoreTrace(AstVar* nodep) {
|
||||
// Return true if this shouldn't be traced
|
||||
// See also similar rule in V3Coverage::varIgnoreToggle
|
||||
string prettyName = nodep->prettyName();
|
||||
if (!nodep->isTrace())
|
||||
return "Verilator trace_off";
|
||||
|
@ -37,6 +37,7 @@
|
||||
#include "V3Combine.h"
|
||||
#include "V3Const.h"
|
||||
#include "V3Coverage.h"
|
||||
#include "V3CoverageJoin.h"
|
||||
#include "V3Dead.h"
|
||||
#include "V3Delayed.h"
|
||||
#include "V3Depth.h"
|
||||
@ -311,6 +312,12 @@ void process () {
|
||||
v3info("Command Line disabled gate optimization with -Og/-O0. This may cause ordering problems.");
|
||||
}
|
||||
|
||||
// Combine COVERINCs with duplicate terms
|
||||
if (v3Global.opt.coverage()) {
|
||||
V3CoverageJoin::coverageJoin(v3Global.rootp());
|
||||
v3Global.rootp()->dumpTreeFile(v3Global.debugFilename("coveragejoin.tree"));
|
||||
}
|
||||
|
||||
// Remove unused vars
|
||||
V3Const::constifyAll(v3Global.rootp());
|
||||
V3Dead::deadifyAll(v3Global.rootp(), true);
|
||||
|
@ -259,7 +259,7 @@ sub new {
|
||||
$self->{stats} ||= "$self->{obj_dir}/V".$self->{name}."__stats.txt";
|
||||
$self->{status_filename} ||= "$self->{obj_dir}/V".$self->{name}.".status";
|
||||
$self->{run_log_filename} ||= "$self->{obj_dir}/vl_sim.log";
|
||||
$self->{coverage_filename} ||= "$self->{obj_dir}/V".$self->{name}."_coverage.pl";
|
||||
$self->{coverage_filename} ||= "$self->{obj_dir}/vl_coverage.pl";
|
||||
($self->{top_filename} = $self->{pl_filename}) =~ s/\.pl$/\.v/;
|
||||
if (!$self->{make_top_shell}) {
|
||||
$self->{top_shell_filename} = $self->{top_filename};
|
||||
@ -484,9 +484,10 @@ sub inline_checks {
|
||||
my $fh = IO::File->new("<$self->{top_filename}");
|
||||
while (defined(my $line = $fh->getline)) {
|
||||
if ($line =~ /CHECK/) {
|
||||
if ($line =~ /CHECK_COVER *\( *([---0-9]+) *, *"([^"]+)" *, *(\d+) *\)/) {
|
||||
my $lineno=($. + $1); my $hier=$2; my $count=$3;
|
||||
if ($line =~ /CHECK_COVER *\( *([---0-9]+) *, *"([^"]+)" *, *("([^"]+)" *,|) *(\d+) *\)/) {
|
||||
my $lineno=($. + $1); my $hier=$2; my $comment=$4; my $count=$5;
|
||||
my $regexp = "\001l\002".$lineno;
|
||||
$regexp .= ".*\001o\002".quotemeta($comment) if $comment;
|
||||
$regexp .= ".*\001h\002".quotemeta($hier);
|
||||
$regexp .= ".*' ".$count;
|
||||
if ($contents !~ /$regexp/) {
|
||||
|
23
test_regress/t/t_cover_toggle.pl
Executable file
23
test_regress/t/t_cover_toggle.pl
Executable file
@ -0,0 +1,23 @@
|
||||
#!/usr/bin/perl
|
||||
if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# Copyright 2003-2008 by Wilson Snyder. This program is free software; you can
|
||||
# redistribute it and/or modify it under the terms of either the GNU
|
||||
# General Public License or the Perl Artistic License.
|
||||
|
||||
compile (
|
||||
verilator_flags2 => [$Self->{v3}?'--sp --coverage-toggle --stats':''],
|
||||
);
|
||||
|
||||
execute (
|
||||
check_finished=>1,
|
||||
);
|
||||
|
||||
# Read the input .v file and do any CHECK_COVER requests
|
||||
inline_checks();
|
||||
|
||||
file_grep ($Self->{stats}, qr/Coverage, Toggle points joined\s+25/i);
|
||||
|
||||
ok(1);
|
||||
1;
|
138
test_regress/t/t_cover_toggle.v
Normal file
138
test_regress/t/t_cover_toggle.v
Normal file
@ -0,0 +1,138 @@
|
||||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// This file ONLY is placed into the Public Domain, for any use,
|
||||
// without warranty, 2008 by Wilson Snyder.
|
||||
|
||||
module t (/*AUTOARG*/
|
||||
// Inputs
|
||||
clk
|
||||
);
|
||||
|
||||
input clk;
|
||||
|
||||
reg toggle; initial toggle=0;
|
||||
|
||||
integer cyc; initial cyc=1;
|
||||
wire [7:0] cyc_copy = cyc[7:0];
|
||||
wire toggle_up;
|
||||
|
||||
alpha a1 (/*AUTOINST*/
|
||||
// Outputs
|
||||
.toggle_up (toggle_up),
|
||||
// Inputs
|
||||
.clk (clk),
|
||||
.toggle (toggle),
|
||||
.cyc_copy (cyc_copy[7:0]));
|
||||
alpha a2 (/*AUTOINST*/
|
||||
// Outputs
|
||||
.toggle_up (toggle_up),
|
||||
// Inputs
|
||||
.clk (clk),
|
||||
.toggle (toggle),
|
||||
.cyc_copy (cyc_copy[7:0]));
|
||||
|
||||
beta b1 (/*AUTOINST*/
|
||||
// Inputs
|
||||
.clk (clk),
|
||||
.toggle_up (toggle_up));
|
||||
|
||||
off o1 (/*AUTOINST*/
|
||||
// Inputs
|
||||
.clk (clk),
|
||||
.toggle (toggle));
|
||||
|
||||
reg [1:0] memory[121:110];
|
||||
|
||||
reg [1023:0] largeish;
|
||||
// CHECK_COVER_MISSING(-1)
|
||||
|
||||
always @ (posedge clk) begin
|
||||
if (cyc!=0) begin
|
||||
cyc <= cyc + 1;
|
||||
memory[cyc + 'd100] <= memory[cyc + 'd100] + 2'b1;
|
||||
toggle <= '0;
|
||||
if (cyc==3) begin
|
||||
toggle <= '1;
|
||||
end
|
||||
if (cyc==4) begin
|
||||
toggle <= '0;
|
||||
end
|
||||
else if (cyc==10) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
module alpha (/*AUTOARG*/
|
||||
// Outputs
|
||||
toggle_up,
|
||||
// Inputs
|
||||
clk, toggle, cyc_copy
|
||||
);
|
||||
|
||||
// t.a1 and t.a2 collapse to a count of 2
|
||||
|
||||
input clk;
|
||||
|
||||
input toggle;
|
||||
// CHECK_COVER(-1,"TOP.v.a*",4)
|
||||
// 2 edges * (t.a1 and t.a2)
|
||||
|
||||
input [7:0] cyc_copy;
|
||||
// CHECK_COVER(-1,"TOP.v.a*","cyc_copy[0]",22)
|
||||
// CHECK_COVER(-2,"TOP.v.a*","cyc_copy[1]",10)
|
||||
// CHECK_COVER(-3,"TOP.v.a*","cyc_copy[2]",4)
|
||||
// CHECK_COVER(-4,"TOP.v.a*","cyc_copy[3]",2)
|
||||
// CHECK_COVER(-5,"TOP.v.a*","cyc_copy[4]",0)
|
||||
// CHECK_COVER(-6,"TOP.v.a*","cyc_copy[5]",0)
|
||||
// CHECK_COVER(-7,"TOP.v.a*","cyc_copy[6]",0)
|
||||
// CHECK_COVER(-8,"TOP.v.a*","cyc_copy[7]",0)
|
||||
|
||||
reg toggle_internal;
|
||||
// CHECK_COVER(-1,"TOP.v.a*",4)
|
||||
// 2 edges * (t.a1 and t.a2)
|
||||
|
||||
output reg toggle_up;
|
||||
// CHECK_COVER(-1,"TOP.v.a*",4)
|
||||
// 2 edges * (t.a1 and t.a2)
|
||||
|
||||
always @ (posedge clk) begin
|
||||
toggle_internal <= toggle;
|
||||
toggle_up <= toggle;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module beta (/*AUTOARG*/
|
||||
// Inputs
|
||||
clk, toggle_up
|
||||
);
|
||||
|
||||
input clk;
|
||||
|
||||
input toggle_up;
|
||||
// CHECK_COVER(-1,"TOP.v.b1","toggle_up",2)
|
||||
|
||||
/* verilator public_module */
|
||||
|
||||
always @ (posedge clk) begin
|
||||
if (0 && toggle_up) begin end
|
||||
end
|
||||
endmodule
|
||||
|
||||
module off (/*AUTOARG*/
|
||||
// Inputs
|
||||
clk, toggle
|
||||
);
|
||||
|
||||
// verilator coverage_off
|
||||
input clk;
|
||||
// CHECK_COVER_MISSING(-1)
|
||||
|
||||
// verilator coverage_on
|
||||
input toggle;
|
||||
// CHECK_COVER(-1,"TOP.v.o1","toggle",2)
|
||||
|
||||
endmodule
|
Loading…
Reference in New Issue
Block a user