2012-04-13 03:08:20 +02:00
|
|
|
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
2006-08-26 13:35:28 +02:00
|
|
|
//*************************************************************************
|
|
|
|
|
// DESCRIPTION: Verilator: Netlist (top level) functions
|
|
|
|
|
//
|
2019-11-08 04:33:59 +01:00
|
|
|
// Code available from: https://verilator.org
|
2006-08-26 13:35:28 +02:00
|
|
|
//
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
//
|
2025-01-01 14:30:25 +01:00
|
|
|
// Copyright 2003-2025 by Wilson Snyder. This program is free software; you
|
2020-03-21 16:24:24 +01:00
|
|
|
// can redistribute it and/or modify it under the terms of either the GNU
|
2009-05-04 23:07:57 +02:00
|
|
|
// Lesser General Public License Version 3 or the Perl Artistic License
|
|
|
|
|
// Version 2.0.
|
2020-03-21 16:24:24 +01:00
|
|
|
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
2006-08-26 13:35:28 +02:00
|
|
|
//
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
// COVERAGE TRANSFORMATIONS:
|
2019-05-19 22:13:13 +02:00
|
|
|
// At each IF/(IF else)/CASEITEM,
|
|
|
|
|
// If there's no coverage off on the block below it,
|
|
|
|
|
// or a $stop
|
|
|
|
|
// Insert a COVERDECL node in the module.
|
|
|
|
|
// (V3Emit reencodes into per-module numbers for emitting.)
|
|
|
|
|
// Insert a COVERINC node at the end of the statement list
|
|
|
|
|
// for that if/else/case.
|
2006-08-26 13:35:28 +02:00
|
|
|
//
|
|
|
|
|
//*************************************************************************
|
2019-10-05 02:17:11 +02:00
|
|
|
|
2023-10-18 04:50:27 +02:00
|
|
|
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
|
|
|
|
|
|
2023-10-18 12:37:46 +02:00
|
|
|
#include "V3Coverage.h"
|
|
|
|
|
|
2025-02-19 22:42:23 +01:00
|
|
|
#include "V3EmitV.h"
|
2025-10-30 00:26:46 +01:00
|
|
|
#include "V3UniqueNames.h"
|
2025-02-19 22:42:23 +01:00
|
|
|
|
|
|
|
|
#include <list>
|
2020-08-15 16:03:34 +02:00
|
|
|
#include <unordered_map>
|
2018-10-14 19:43:24 +02:00
|
|
|
|
2022-09-18 21:53:42 +02:00
|
|
|
VL_DEFINE_DEBUG_FUNCTIONS;
|
|
|
|
|
|
2025-08-19 23:02:10 +02:00
|
|
|
class ExprCoverageEligibleVisitor final : public VNVisitorConst {
|
2025-03-20 00:01:31 +01:00
|
|
|
// STATE
|
|
|
|
|
bool m_eligible = true;
|
|
|
|
|
|
2025-07-16 18:07:34 +02:00
|
|
|
static bool elemDTypeEligible(const AstNodeDType* dtypep) {
|
|
|
|
|
dtypep = dtypep->skipRefp();
|
2025-08-19 23:02:10 +02:00
|
|
|
if (const AstNodeDType* const dtp = dtypep->virtRefDTypep()) {
|
2025-07-16 18:07:34 +02:00
|
|
|
if (!elemDTypeEligible(dtp)) return false;
|
|
|
|
|
}
|
2025-08-19 23:02:10 +02:00
|
|
|
if (const AstNodeDType* const dtp = dtypep->virtRefDType2p()) {
|
2025-07-16 18:07:34 +02:00
|
|
|
if (!elemDTypeEligible(dtp)) return false;
|
|
|
|
|
}
|
|
|
|
|
return !VN_IS(dtypep, ClassRefDType);
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-21 13:55:38 +01:00
|
|
|
void visit(AstNodeVarRef* nodep) override {
|
2025-08-19 23:02:10 +02:00
|
|
|
const AstNodeDType* const dtypep = nodep->varp()->dtypep();
|
2025-07-16 18:07:34 +02:00
|
|
|
// Class objects and references not supported for expression coverage
|
2025-03-21 13:55:38 +01:00
|
|
|
// because the object may not persist until the point at which
|
|
|
|
|
// coverage data is gathered
|
|
|
|
|
// This could be resolved in the future by protecting against dereferrencing
|
|
|
|
|
// null pointers when cloning the expression for expression coverage
|
2025-07-16 18:07:34 +02:00
|
|
|
if (dtypep && elemDTypeEligible(dtypep)) {
|
2025-08-19 23:02:10 +02:00
|
|
|
iterateChildrenConst(nodep);
|
2025-07-16 18:07:34 +02:00
|
|
|
} else {
|
|
|
|
|
m_eligible = false;
|
2025-03-21 13:55:38 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-20 00:01:31 +01:00
|
|
|
void visit(AstNode* nodep) override {
|
2025-03-21 13:55:38 +01:00
|
|
|
if (!nodep->isExprCoverageEligible()) {
|
|
|
|
|
m_eligible = false;
|
|
|
|
|
} else {
|
2025-08-19 23:02:10 +02:00
|
|
|
iterateChildrenConst(nodep);
|
2025-03-21 13:55:38 +01:00
|
|
|
}
|
2025-03-20 00:01:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
// CONSTRUCTORS
|
2025-10-30 00:26:46 +01:00
|
|
|
explicit ExprCoverageEligibleVisitor(AstNode* nodep) { iterateConst(nodep); }
|
2025-03-20 00:01:31 +01:00
|
|
|
~ExprCoverageEligibleVisitor() override = default;
|
|
|
|
|
|
|
|
|
|
bool eligible() { return m_eligible; }
|
|
|
|
|
};
|
|
|
|
|
|
2006-08-26 13:35:28 +02:00
|
|
|
//######################################################################
|
|
|
|
|
// Coverage state, as a visitor of each AstNode
|
|
|
|
|
|
2022-01-02 19:56:40 +01:00
|
|
|
class CoverageVisitor final : public VNVisitor {
|
2006-08-26 13:35:28 +02:00
|
|
|
// TYPES
|
2021-03-13 00:10:45 +01:00
|
|
|
using LinenoSet = std::set<int>;
|
2006-08-26 13:35:28 +02:00
|
|
|
|
2025-02-19 22:42:23 +01:00
|
|
|
struct CoverTerm final {
|
|
|
|
|
AstNodeExpr* m_exprp; // Expression branch term
|
|
|
|
|
bool m_objective; // Term objective
|
|
|
|
|
std::string m_emitV; // V3EmitV string for cover point comment
|
|
|
|
|
CoverTerm(AstNodeExpr* exprp, bool objective, const string& emitV)
|
|
|
|
|
: m_exprp{exprp}
|
|
|
|
|
, m_objective{objective}
|
2025-07-04 00:59:32 +02:00
|
|
|
, m_emitV{emitV} {}
|
2025-02-19 22:42:23 +01:00
|
|
|
};
|
|
|
|
|
using CoverExpr = std::deque<CoverTerm>;
|
|
|
|
|
using CoverExprs = std::list<CoverExpr>;
|
|
|
|
|
|
2024-01-20 21:06:46 +01:00
|
|
|
struct ToggleEnt final {
|
2021-11-26 23:55:36 +01:00
|
|
|
const string m_comment; // Comment for coverage dump
|
2022-11-13 21:33:11 +01:00
|
|
|
AstNodeExpr* m_varRefp; // How to get to this element
|
|
|
|
|
AstNodeExpr* m_chgRefp; // How to get to this element
|
|
|
|
|
ToggleEnt(const string& comment, AstNodeExpr* vp, AstNodeExpr* cp)
|
2020-08-16 15:55:36 +02:00
|
|
|
: m_comment{comment}
|
|
|
|
|
, m_varRefp{vp}
|
|
|
|
|
, m_chgRefp{cp} {}
|
2020-11-17 01:56:16 +01:00
|
|
|
~ToggleEnt() = default;
|
2019-05-19 22:13:13 +02:00
|
|
|
void cleanup() {
|
2020-08-15 16:12:55 +02:00
|
|
|
VL_DO_CLEAR(m_varRefp->deleteTree(), m_varRefp = nullptr);
|
|
|
|
|
VL_DO_CLEAR(m_chgRefp->deleteTree(), m_chgRefp = nullptr);
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
2009-11-05 04:31:53 +01:00
|
|
|
};
|
|
|
|
|
|
2024-01-20 21:06:46 +01:00
|
|
|
struct CheckState final { // State save-restored on each new coverage scope/block
|
2020-08-15 19:11:27 +02:00
|
|
|
bool m_on = false; // Should this block get covered?
|
|
|
|
|
bool m_inModOff = false; // In module with no coverage
|
|
|
|
|
int m_handle = 0; // Opaque handle for index into line tracking
|
|
|
|
|
const AstNode* m_nodep = nullptr; // Node establishing this state
|
2020-11-17 01:56:16 +01:00
|
|
|
CheckState() = default;
|
2020-08-16 20:55:46 +02:00
|
|
|
bool lineCoverageOn(const AstNode* nodep) const {
|
2020-05-31 21:52:17 +02:00
|
|
|
return m_on && !m_inModOff && nodep->fileline()->coverageOn()
|
|
|
|
|
&& v3Global.opt.coverageLine();
|
|
|
|
|
}
|
2025-02-19 22:42:23 +01:00
|
|
|
bool exprCoverageOn(const AstNode* nodep) const {
|
|
|
|
|
return m_on && !m_inModOff && nodep->fileline()->coverageOn()
|
|
|
|
|
&& v3Global.opt.coverageExpr();
|
|
|
|
|
}
|
2020-05-31 21:52:17 +02:00
|
|
|
};
|
|
|
|
|
|
2025-02-19 22:42:23 +01:00
|
|
|
enum Objective : uint8_t { NONE, SEEKING, ABORTED };
|
|
|
|
|
|
2014-03-29 16:04:13 +01:00
|
|
|
// NODE STATE
|
|
|
|
|
// Entire netlist:
|
2019-05-19 22:13:13 +02:00
|
|
|
// AstIf::user1() -> bool. True indicates ifelse processed
|
2025-09-29 16:25:25 +02:00
|
|
|
// AstIf/AstLoopTest::user2() -> bool. True indicates coverage-generated
|
2022-01-02 19:56:40 +01:00
|
|
|
const VNUser1InUse m_inuser1;
|
2025-02-19 22:42:23 +01:00
|
|
|
const VNUser2InUse m_inuser2;
|
2025-10-30 00:26:46 +01:00
|
|
|
V3UniqueNames m_exprTempNames; // For generating unique temporary variable names used by
|
|
|
|
|
// expression coverage
|
2025-11-06 14:23:35 +01:00
|
|
|
std::unordered_map<AstNodeExpr*, AstVar*> m_funcTemps;
|
2014-03-29 16:04:13 +01:00
|
|
|
|
2024-10-10 00:12:55 +02:00
|
|
|
// STATE - across all visitors
|
|
|
|
|
int m_nextHandle = 0;
|
|
|
|
|
|
|
|
|
|
// STATE - for current visit position (use VL_RESTORER)
|
2020-05-31 21:52:17 +02:00
|
|
|
CheckState m_state; // State save-restored on each new coverage scope/block
|
2020-08-16 15:55:36 +02:00
|
|
|
AstNodeModule* m_modp = nullptr; // Current module to add statement to
|
2025-10-30 00:26:46 +01:00
|
|
|
AstNodeFTask* m_ftaskp = nullptr; // Current function/task
|
2025-02-19 22:42:23 +01:00
|
|
|
AstNode* m_exprStmtsp = nullptr; // Node to add expr coverage to
|
|
|
|
|
bool m_then = false; // Whether we're iterating the then or else branch
|
|
|
|
|
// when m_exprStmtps is an AstIf
|
|
|
|
|
CoverExprs m_exprs; // List of expressions that can reach objective
|
|
|
|
|
Objective m_seeking = NONE; // Seeking objective for expression coverage
|
|
|
|
|
bool m_objective = false; // Expression objective
|
|
|
|
|
bool m_ifCond = false; // Visiting if condition
|
2020-08-16 15:55:36 +02:00
|
|
|
bool m_inToggleOff = false; // In function/task etc
|
2020-04-14 04:51:35 +02:00
|
|
|
string m_beginHier; // AstBegin hier name for user coverage points
|
2024-10-10 00:12:55 +02:00
|
|
|
|
|
|
|
|
// STATE - cleared each module
|
|
|
|
|
std::unordered_map<std::string, uint32_t> m_varnames; // Uniquify inserted variable names
|
|
|
|
|
std::unordered_map<int, LinenoSet> m_handleLines; // Line numbers for given m_stateHandle
|
2008-06-10 03:25:10 +02:00
|
|
|
|
2006-08-26 13:35:28 +02:00
|
|
|
// METHODS
|
2009-01-21 22:56:50 +01:00
|
|
|
|
2025-08-19 23:02:10 +02:00
|
|
|
const char* varIgnoreToggle(const AstVar* nodep) {
|
2019-05-19 22:13:13 +02:00
|
|
|
// Return true if this shouldn't be traced
|
|
|
|
|
// See also similar rule in V3TraceDecl::varIgnoreTrace
|
2020-04-14 04:51:35 +02:00
|
|
|
if (!nodep->isToggleCoverable()) return "Not relevant signal type";
|
2019-05-19 22:13:13 +02:00
|
|
|
if (!v3Global.opt.coverageUnderscore()) {
|
2021-06-21 00:32:57 +02:00
|
|
|
const string prettyName = nodep->prettyName();
|
2020-04-14 04:51:35 +02:00
|
|
|
if (prettyName[0] == '_') return "Leading underscore";
|
|
|
|
|
if (prettyName.find("._") != string::npos) return "Inlined leading underscore";
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
2021-03-30 00:54:51 +02:00
|
|
|
if ((nodep->width() * nodep->dtypep()->arrayUnpackedElements())
|
|
|
|
|
> static_cast<uint32_t>(v3Global.opt.coverageMaxWidth())) {
|
|
|
|
|
return "Wide bus/array > --coverage-max-width setting's bits";
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
|
|
|
|
// We allow this, though tracing doesn't
|
|
|
|
|
// if (nodep->arrayp(1)) return "Unsupported: Multi-dimensional array";
|
2020-08-15 16:12:55 +02:00
|
|
|
return nullptr;
|
2008-12-12 21:34:02 +01:00
|
|
|
}
|
|
|
|
|
|
2025-08-04 14:29:56 +02:00
|
|
|
AstCoverInc* newCoverInc(FileLine* fl, AstNodeCoverDecl* const declp,
|
2020-05-31 21:52:17 +02:00
|
|
|
const string& trace_var_name) {
|
2022-11-10 03:45:14 +01:00
|
|
|
AstCoverInc* const incp = new AstCoverInc{fl, declp};
|
2023-03-04 01:26:15 +01:00
|
|
|
if (!trace_var_name.empty()
|
|
|
|
|
&& v3Global.opt.traceCoverage()
|
|
|
|
|
// No module handle to trace inside classes
|
|
|
|
|
&& !VN_IS(m_modp, Class)) {
|
2022-11-10 03:45:14 +01:00
|
|
|
FileLine* const fl_nowarn = new FileLine{incp->fileline()};
|
|
|
|
|
fl_nowarn->modifyWarnOff(V3ErrorCode::UNUSEDSIGNAL, true);
|
|
|
|
|
AstVar* const varp = new AstVar{fl_nowarn, VVarType::MODULETEMP, trace_var_name,
|
|
|
|
|
incp->findUInt32DType()};
|
2025-03-03 02:02:55 +01:00
|
|
|
varp->setIgnoreSchedWrite(); // Ignore the increment output, so no UNOPTFLAT
|
2019-10-27 14:27:18 +01:00
|
|
|
varp->trace(true);
|
2022-09-15 20:43:56 +02:00
|
|
|
m_modp->addStmtsp(varp);
|
2025-05-23 02:29:32 +02:00
|
|
|
UINFO(5, "New coverage trace: " << varp);
|
2022-11-20 21:06:49 +01:00
|
|
|
AstAssign* const assp = new AstAssign{
|
|
|
|
|
incp->fileline(), new AstVarRef{incp->fileline(), varp, VAccess::WRITE},
|
|
|
|
|
new AstAdd{incp->fileline(), new AstVarRef{incp->fileline(), varp, VAccess::READ},
|
|
|
|
|
new AstConst{incp->fileline(), AstConst::WidthedValue{}, 32, 1}}};
|
2022-09-17 14:48:51 +02:00
|
|
|
AstNode::addNext<AstNode, AstNode>(incp, assp);
|
2019-10-27 14:27:18 +01:00
|
|
|
}
|
|
|
|
|
return incp;
|
|
|
|
|
}
|
2025-08-19 23:02:10 +02:00
|
|
|
string traceNameForLine(const AstNode* nodep, const string& type) {
|
2020-05-31 15:05:02 +02:00
|
|
|
string name = "vlCoverageLineTrace_" + nodep->fileline()->filebasenameNoExt() + "__"
|
|
|
|
|
+ cvtToStr(nodep->fileline()->lineno()) + "_" + type;
|
2023-10-28 14:38:02 +02:00
|
|
|
if (const uint32_t suffix = m_varnames[name]++) name += "_" + cvtToStr(suffix);
|
2020-05-31 15:05:02 +02:00
|
|
|
return name;
|
2006-08-26 13:35:28 +02:00
|
|
|
}
|
2020-05-31 21:52:17 +02:00
|
|
|
|
|
|
|
|
// Line tracking
|
|
|
|
|
void createHandle(const AstNode* nodep) {
|
|
|
|
|
// Start tracking lines for the given handling node
|
|
|
|
|
// If and if's else have separate handles for same nodep,
|
|
|
|
|
// so nodep cannot have a pointer to a unique handle
|
|
|
|
|
m_state.m_on = true;
|
|
|
|
|
m_state.m_handle = ++m_nextHandle;
|
|
|
|
|
// Ensure line numbers we track are in the same file as this block
|
|
|
|
|
// so track via nodep
|
|
|
|
|
m_state.m_nodep = nodep;
|
2025-05-23 02:29:32 +02:00
|
|
|
UINFO(9, "line create h" << m_state.m_handle << " " << nodep);
|
2020-05-31 21:52:17 +02:00
|
|
|
}
|
|
|
|
|
void lineTrack(const AstNode* nodep) {
|
2025-02-19 22:42:23 +01:00
|
|
|
if (m_state.lineCoverageOn(nodep) && !m_ifCond
|
2020-05-31 21:52:17 +02:00
|
|
|
&& m_state.m_nodep->fileline()->filenameno() == nodep->fileline()->filenameno()) {
|
|
|
|
|
for (int lineno = nodep->fileline()->firstLineno();
|
|
|
|
|
lineno <= nodep->fileline()->lastLineno(); ++lineno) {
|
|
|
|
|
UINFO(9, "line track " << lineno << " for h" << m_state.m_handle << " "
|
2025-05-23 02:29:32 +02:00
|
|
|
<< m_state.m_nodep);
|
2020-05-31 21:52:17 +02:00
|
|
|
m_handleLines[m_state.m_handle].insert(lineno);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
static string linesFirstLast(const int first, const int last) {
|
|
|
|
|
if (first && first == last) {
|
|
|
|
|
return cvtToStr(first);
|
|
|
|
|
} else if (first && last) {
|
|
|
|
|
return cvtToStr(first) + "-" + cvtToStr(last);
|
|
|
|
|
} else {
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
string linesCov(const CheckState& state, const AstNode* nodep) {
|
|
|
|
|
// Return comma separated list of ranged numbers
|
|
|
|
|
string out;
|
|
|
|
|
const LinenoSet& lines = m_handleLines[state.m_handle];
|
|
|
|
|
int first = 0;
|
|
|
|
|
int last = 0;
|
2020-11-11 04:10:38 +01:00
|
|
|
for (int linen : lines) {
|
2020-05-31 21:52:17 +02:00
|
|
|
if (!first) {
|
2020-11-11 04:10:38 +01:00
|
|
|
first = last = linen;
|
|
|
|
|
} else if (linen == last + 1) {
|
2020-05-31 21:52:17 +02:00
|
|
|
++last;
|
|
|
|
|
} else {
|
|
|
|
|
if (!out.empty()) out += ",";
|
|
|
|
|
out += linesFirstLast(first, last);
|
2020-11-11 04:10:38 +01:00
|
|
|
first = last = linen;
|
2020-05-31 21:52:17 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (first) {
|
|
|
|
|
if (!out.empty()) out += ",";
|
|
|
|
|
out += linesFirstLast(first, last);
|
|
|
|
|
}
|
2025-05-23 02:29:32 +02:00
|
|
|
UINFO(9, "lines out " << out << " for h" << state.m_handle << " " << nodep);
|
2020-05-31 21:52:17 +02:00
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
|
2008-12-10 23:10:03 +01:00
|
|
|
// VISITORS - BOTH
|
2022-09-16 12:22:11 +02:00
|
|
|
void visit(AstNodeModule* nodep) override {
|
2021-11-26 23:55:36 +01:00
|
|
|
const AstNodeModule* const origModp = m_modp;
|
2020-12-06 19:49:44 +01:00
|
|
|
VL_RESTORER(m_modp);
|
2020-08-25 03:10:43 +02:00
|
|
|
VL_RESTORER(m_state);
|
2025-10-30 00:26:46 +01:00
|
|
|
VL_RESTORER(m_exprTempNames);
|
2025-11-06 14:23:35 +01:00
|
|
|
VL_RESTORER(m_funcTemps);
|
2024-10-10 00:12:55 +02:00
|
|
|
createHandle(nodep);
|
|
|
|
|
m_modp = nodep;
|
2025-11-26 12:09:29 +01:00
|
|
|
m_state.m_inModOff = false; // Haven't made top shell, so tops are real tops
|
2024-10-10 00:12:55 +02:00
|
|
|
if (!origModp) {
|
|
|
|
|
// No blocks cross (non-nested) modules, so save some memory
|
|
|
|
|
m_varnames.clear();
|
|
|
|
|
m_handleLines.clear();
|
2020-01-20 19:27:27 +01:00
|
|
|
}
|
2024-10-10 00:12:55 +02:00
|
|
|
iterateChildren(nodep);
|
2006-08-26 13:35:28 +02:00
|
|
|
}
|
Internals: Make AstAssignW a procedural statement (#6280) (#6556)
Initial idea was to remodel AssignW as Assign under Alway. Trying that
uncovered some issues, the most difficult of them was that a delay
attached to a continuous assignment behaves differently from a delay
attached to a blocking assignment statement, so we need to keep the
knowledge of which flavour an assignment was until V3Timing.
So instead of removing AstAssignW, we always wrap it in an AstAlways,
with a special `keyword()` type. This makes it into a proper procedural
statement, which is almost equivalent to AstAssign, except for the case
when they contain a delay. We still gain the benefits of #6280 and can
simplify some code. Every AstNodeStmt should now be under an
AstNodeProcedure - which we should rename to AstProcess, or an
AstNodeFTask). As a result, V3Table can now handle AssignW for free.
Also uncovered and fixed a bug in handling intra-assignment delays if
a function is present on the RHS of an AssignW.
There is more work to be done towards #6280, and potentially simplifying
AssignW handing, but this is the minimal change required to tick it off
the TODO list for #6280.
2025-10-14 10:05:19 +02:00
|
|
|
void visit(AstAlways* nodep) override {
|
|
|
|
|
if (nodep->keyword() == VAlwaysKwd::CONT_ASSIGN) {
|
|
|
|
|
// Don't want line coverage for it, iterate for expression/toggle coverage only
|
|
|
|
|
iterateChildren(nodep);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
iterateProcedure(nodep);
|
|
|
|
|
}
|
2022-09-16 12:22:11 +02:00
|
|
|
void visit(AstNodeProcedure* nodep) override { iterateProcedure(nodep); }
|
2025-09-29 16:25:25 +02:00
|
|
|
void visit(AstLoop* nodep) override {
|
|
|
|
|
UASSERT_OBJ(!nodep->contsp(), nodep, "'contsp' only used before LinkJump");
|
2025-05-19 15:35:35 +02:00
|
|
|
VL_RESTORER(m_state);
|
|
|
|
|
VL_RESTORER(m_inToggleOff);
|
|
|
|
|
m_inToggleOff = true;
|
|
|
|
|
createHandle(nodep);
|
|
|
|
|
iterateAndNextNull(nodep->stmtsp());
|
|
|
|
|
if (m_state.lineCoverageOn(nodep)) {
|
2025-08-04 14:29:56 +02:00
|
|
|
AstCoverOtherDecl* const declp
|
|
|
|
|
= new AstCoverOtherDecl{nodep->fileline(), "v_line/" + m_modp->prettyName(),
|
|
|
|
|
"block", linesCov(m_state, nodep), 0};
|
|
|
|
|
m_modp->addStmtsp(declp);
|
2025-05-19 15:35:35 +02:00
|
|
|
AstNode* const newp
|
2025-08-04 14:29:56 +02:00
|
|
|
= newCoverInc(nodep->fileline(), declp, traceNameForLine(nodep, "block"));
|
2025-05-19 15:35:35 +02:00
|
|
|
insertProcStatement(nodep, newp);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-29 16:25:25 +02:00
|
|
|
void visit(AstLoopTest* nodep) override {
|
|
|
|
|
if (nodep->user2SetOnce()) return;
|
|
|
|
|
lineTrack(nodep);
|
|
|
|
|
if (m_state.lineCoverageOn(nodep) && nodep->backp()->nextp() == nodep) {
|
|
|
|
|
AstCoverOtherDecl* const declp
|
|
|
|
|
= new AstCoverOtherDecl{nodep->fileline(), "v_line/" + m_modp->prettyName(),
|
|
|
|
|
"block", linesCov(m_state, nodep), 0};
|
|
|
|
|
m_modp->addStmtsp(declp);
|
|
|
|
|
AstNode* const newp
|
|
|
|
|
= newCoverInc(nodep->fileline(), declp, traceNameForLine(nodep, "block"));
|
|
|
|
|
nodep->addHereThisAsNext(newp);
|
|
|
|
|
createHandle(nodep);
|
|
|
|
|
}
|
|
|
|
|
iterateChildren(nodep);
|
|
|
|
|
}
|
2025-05-19 15:35:35 +02:00
|
|
|
|
2022-09-16 12:22:11 +02:00
|
|
|
void visit(AstNodeFTask* nodep) override {
|
2025-10-30 00:26:46 +01:00
|
|
|
VL_RESTORER(m_ftaskp);
|
|
|
|
|
VL_RESTORER(m_exprTempNames);
|
2025-11-06 14:23:35 +01:00
|
|
|
VL_RESTORER(m_funcTemps);
|
2025-10-30 00:26:46 +01:00
|
|
|
m_ftaskp = nodep;
|
2020-05-31 21:52:17 +02:00
|
|
|
if (!nodep->dpiImport()) iterateProcedure(nodep);
|
|
|
|
|
}
|
2025-02-19 22:42:23 +01:00
|
|
|
|
|
|
|
|
void insertProcStatement(AstNode* nodep, AstNode* stmtp) {
|
|
|
|
|
if (AstNodeProcedure* const itemp = VN_CAST(nodep, NodeProcedure)) {
|
|
|
|
|
itemp->addStmtsp(stmtp);
|
|
|
|
|
} else if (AstNodeFTask* const itemp = VN_CAST(nodep, NodeFTask)) {
|
|
|
|
|
itemp->addStmtsp(stmtp);
|
2025-09-29 16:25:25 +02:00
|
|
|
} else if (AstLoop* const itemp = VN_CAST(nodep, Loop)) {
|
2025-02-19 22:42:23 +01:00
|
|
|
itemp->addStmtsp(stmtp);
|
|
|
|
|
} else if (AstIf* const itemp = VN_CAST(nodep, If)) {
|
|
|
|
|
if (m_then) {
|
|
|
|
|
itemp->addThensp(stmtp);
|
|
|
|
|
} else {
|
|
|
|
|
itemp->addElsesp(stmtp);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
nodep->v3fatalSrc("Bad node type");
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-05-19 15:35:35 +02:00
|
|
|
void iterateProcedure(AstNode* nodep) {
|
2020-08-25 03:10:43 +02:00
|
|
|
VL_RESTORER(m_state);
|
2025-02-19 22:42:23 +01:00
|
|
|
VL_RESTORER(m_exprStmtsp);
|
2020-08-25 03:10:43 +02:00
|
|
|
VL_RESTORER(m_inToggleOff);
|
2025-12-17 19:51:40 +01:00
|
|
|
// skip properties for expresison coverage
|
|
|
|
|
if (!VN_IS(nodep, Property)) m_exprStmtsp = nodep;
|
2024-10-10 00:12:55 +02:00
|
|
|
m_inToggleOff = true;
|
|
|
|
|
createHandle(nodep);
|
|
|
|
|
iterateChildren(nodep);
|
|
|
|
|
if (m_state.lineCoverageOn(nodep)) {
|
|
|
|
|
lineTrack(nodep);
|
2025-08-04 14:29:56 +02:00
|
|
|
AstCoverOtherDecl* const declp
|
|
|
|
|
= new AstCoverOtherDecl{nodep->fileline(), "v_line/" + m_modp->prettyName(),
|
|
|
|
|
"block", linesCov(m_state, nodep), 0};
|
|
|
|
|
m_modp->addStmtsp(declp);
|
2024-10-10 00:12:55 +02:00
|
|
|
AstNode* const newp
|
2025-08-04 14:29:56 +02:00
|
|
|
= newCoverInc(nodep->fileline(), declp, traceNameForLine(nodep, "block"));
|
2025-02-19 22:42:23 +01:00
|
|
|
insertProcStatement(nodep, newp);
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
2008-12-12 21:34:02 +01:00
|
|
|
}
|
2020-05-31 21:52:17 +02:00
|
|
|
|
|
|
|
|
// VISITORS - TOGGLE COVERAGE
|
2022-09-16 12:22:11 +02:00
|
|
|
void visit(AstVar* nodep) override {
|
2018-05-11 02:55:37 +02:00
|
|
|
iterateChildren(nodep);
|
2020-05-31 21:52:17 +02:00
|
|
|
if (m_modp && !m_inToggleOff && !m_state.m_inModOff && nodep->fileline()->coverageOn()
|
2020-04-14 04:51:35 +02:00
|
|
|
&& v3Global.opt.coverageToggle()) {
|
2021-11-13 19:50:44 +01:00
|
|
|
const char* const disablep = varIgnoreToggle(nodep);
|
2019-05-19 22:13:13 +02:00
|
|
|
if (disablep) {
|
2025-05-23 02:29:32 +02:00
|
|
|
UINFO(4, " Disable Toggle: " << disablep << " " << nodep);
|
2019-05-19 22:13:13 +02:00
|
|
|
} else {
|
2025-05-23 02:29:32 +02:00
|
|
|
UINFO(4, " Toggle: " << nodep);
|
2019-05-19 22:13:13 +02:00
|
|
|
// 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.
|
2008-12-12 21:34:02 +01:00
|
|
|
|
2019-05-19 22:13:13 +02:00
|
|
|
// Add signal to hold the old value
|
2025-05-16 14:24:57 +02:00
|
|
|
const string newvarname = "__Vtogcov__"s + m_beginHier + nodep->shortName();
|
2022-11-10 03:45:14 +01:00
|
|
|
FileLine* const fl_nowarn = new FileLine{nodep->fileline()};
|
|
|
|
|
fl_nowarn->modifyWarnOff(V3ErrorCode::UNUSEDSIGNAL, true);
|
2021-11-13 19:50:44 +01:00
|
|
|
AstVar* const chgVarp
|
2022-11-10 03:45:14 +01:00
|
|
|
= new AstVar{fl_nowarn, VVarType::MODULETEMP, newvarname, nodep};
|
2022-09-15 20:43:56 +02:00
|
|
|
m_modp->addStmtsp(chgVarp);
|
2008-12-12 21:34:02 +01:00
|
|
|
|
2019-05-19 22:13:13 +02:00
|
|
|
// 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.
|
2009-11-05 04:31:53 +01:00
|
|
|
|
2024-01-29 02:24:28 +01:00
|
|
|
ToggleEnt newvec{""s, new AstVarRef{fl_nowarn, nodep, VAccess::READ},
|
2022-11-10 03:45:14 +01:00
|
|
|
new AstVarRef{fl_nowarn, chgVarp, VAccess::WRITE}};
|
2025-05-23 23:38:34 +02:00
|
|
|
toggleVarRecurse(nodep->dtypeSkipRefp(), 0, newvec, nodep);
|
2019-05-19 22:13:13 +02:00
|
|
|
newvec.cleanup();
|
|
|
|
|
}
|
|
|
|
|
}
|
2008-12-12 21:34:02 +01:00
|
|
|
}
|
|
|
|
|
|
2025-08-04 14:29:56 +02:00
|
|
|
void toggleVarBottom(const ToggleEnt& above, const AstVar* varp, const VNumRange& range) {
|
2025-05-16 14:24:57 +02:00
|
|
|
const std::string hierPrefix
|
|
|
|
|
= (m_beginHier != "") ? AstNode::prettyName(m_beginHier) + "." : "";
|
2025-08-04 14:29:56 +02:00
|
|
|
AstCoverToggleDecl* const declp
|
|
|
|
|
= new AstCoverToggleDecl{varp->fileline(), "v_toggle/" + m_modp->prettyName(),
|
|
|
|
|
hierPrefix + varp->name() + above.m_comment, range};
|
|
|
|
|
m_modp->addStmtsp(declp);
|
2022-11-20 21:06:49 +01:00
|
|
|
AstCoverToggle* const newp = new AstCoverToggle{
|
2025-08-04 14:29:56 +02:00
|
|
|
varp->fileline(), newCoverInc(varp->fileline(), declp, ""),
|
2025-05-23 23:38:34 +02:00
|
|
|
above.m_varRefp->cloneTree(false), above.m_chgRefp->cloneTree(false)};
|
2022-09-15 20:43:56 +02:00
|
|
|
m_modp->addStmtsp(newp);
|
2009-11-05 04:31:53 +01:00
|
|
|
}
|
|
|
|
|
|
2025-05-23 23:38:34 +02:00
|
|
|
void toggleVarRecurse(const AstNodeDType* const dtypep, const int depth, // per-iteration
|
|
|
|
|
const ToggleEnt& above, const AstVar* const varp) { // Constant
|
2025-08-04 14:29:56 +02:00
|
|
|
if (const AstBasicDType* const basicp = VN_CAST(dtypep, BasicDType)) {
|
|
|
|
|
toggleVarBottom(above, varp, basicp->nrange());
|
2021-11-26 23:55:36 +01:00
|
|
|
} else if (const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) {
|
2020-12-07 03:13:56 +01:00
|
|
|
for (int index_docs = adtypep->lo(); index_docs <= adtypep->hi(); ++index_docs) {
|
2021-06-21 00:32:57 +02:00
|
|
|
const int index_code = index_docs - adtypep->lo();
|
2024-01-29 02:24:28 +01:00
|
|
|
ToggleEnt newent{above.m_comment + "["s + cvtToStr(index_docs) + "]",
|
2022-08-30 07:02:39 +02:00
|
|
|
new AstArraySel{varp->fileline(),
|
2025-05-23 23:38:34 +02:00
|
|
|
above.m_varRefp->cloneTree(false), index_code},
|
2022-08-30 07:02:39 +02:00
|
|
|
new AstArraySel{varp->fileline(),
|
2025-05-23 23:38:34 +02:00
|
|
|
above.m_chgRefp->cloneTree(false), index_code}};
|
|
|
|
|
toggleVarRecurse(adtypep->subDTypep()->skipRefp(), depth + 1, newent, varp);
|
2019-05-19 22:13:13 +02:00
|
|
|
newent.cleanup();
|
|
|
|
|
}
|
2021-11-26 23:55:36 +01:00
|
|
|
} else if (const AstPackArrayDType* const adtypep = VN_CAST(dtypep, PackArrayDType)) {
|
2020-12-07 03:13:56 +01:00
|
|
|
for (int index_docs = adtypep->lo(); index_docs <= adtypep->hi(); ++index_docs) {
|
2021-11-26 23:55:36 +01:00
|
|
|
const AstNodeDType* const subtypep = adtypep->subDTypep()->skipRefp();
|
2021-06-21 00:32:57 +02:00
|
|
|
const int index_code = index_docs - adtypep->lo();
|
2024-01-29 02:24:28 +01:00
|
|
|
ToggleEnt newent{above.m_comment + "["s + cvtToStr(index_docs) + "]",
|
2025-05-23 23:38:34 +02:00
|
|
|
new AstSel{varp->fileline(), above.m_varRefp->cloneTree(false),
|
2022-08-30 07:02:39 +02:00
|
|
|
index_code * subtypep->width(), subtypep->width()},
|
2025-05-23 23:38:34 +02:00
|
|
|
new AstSel{varp->fileline(), above.m_chgRefp->cloneTree(false),
|
2022-08-30 07:02:39 +02:00
|
|
|
index_code * subtypep->width(), subtypep->width()}};
|
2025-05-23 23:38:34 +02:00
|
|
|
toggleVarRecurse(adtypep->subDTypep()->skipRefp(), depth + 1, newent, varp);
|
2019-05-19 22:13:13 +02:00
|
|
|
newent.cleanup();
|
|
|
|
|
}
|
2021-11-26 23:55:36 +01:00
|
|
|
} else if (const AstStructDType* const adtypep = VN_CAST(dtypep, StructDType)) {
|
2022-12-21 01:22:42 +01:00
|
|
|
if (adtypep->packed()) {
|
|
|
|
|
for (AstMemberDType* itemp = adtypep->membersp(); itemp;
|
|
|
|
|
itemp = VN_AS(itemp->nextp(), MemberDType)) {
|
2025-08-19 23:02:10 +02:00
|
|
|
const AstNodeDType* const subtypep = itemp->subDTypep()->skipRefp();
|
2022-12-21 01:22:42 +01:00
|
|
|
const int index_code = itemp->lsb();
|
2025-05-23 23:38:34 +02:00
|
|
|
ToggleEnt newent{
|
|
|
|
|
above.m_comment + "."s + itemp->name(),
|
|
|
|
|
new AstSel{varp->fileline(), above.m_varRefp->cloneTree(false), index_code,
|
|
|
|
|
subtypep->width()},
|
|
|
|
|
new AstSel{varp->fileline(), above.m_chgRefp->cloneTree(false), index_code,
|
|
|
|
|
subtypep->width()}};
|
|
|
|
|
toggleVarRecurse(subtypep, depth + 1, newent, varp);
|
2022-12-21 01:22:42 +01:00
|
|
|
newent.cleanup();
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
for (AstMemberDType* itemp = adtypep->membersp(); itemp;
|
|
|
|
|
itemp = VN_AS(itemp->nextp(), MemberDType)) {
|
|
|
|
|
AstNodeDType* const subtypep = itemp->subDTypep()->skipRefp();
|
|
|
|
|
AstNodeExpr* const varRefp = new AstStructSel{
|
2025-05-23 23:38:34 +02:00
|
|
|
varp->fileline(), above.m_varRefp->cloneTree(false), itemp->name()};
|
2022-12-21 01:22:42 +01:00
|
|
|
AstNodeExpr* const chgRefp = new AstStructSel{
|
2025-05-23 23:38:34 +02:00
|
|
|
varp->fileline(), above.m_chgRefp->cloneTree(false), itemp->name()};
|
2022-12-21 01:22:42 +01:00
|
|
|
varRefp->dtypep(subtypep);
|
|
|
|
|
chgRefp->dtypep(subtypep);
|
2024-01-29 02:24:28 +01:00
|
|
|
ToggleEnt newent{above.m_comment + "."s + itemp->name(), varRefp, chgRefp};
|
2025-05-23 23:38:34 +02:00
|
|
|
toggleVarRecurse(subtypep, depth + 1, newent, varp);
|
2022-12-21 01:22:42 +01:00
|
|
|
newent.cleanup();
|
|
|
|
|
}
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
2021-11-26 23:55:36 +01:00
|
|
|
} else if (const AstUnionDType* const adtypep = VN_CAST(dtypep, UnionDType)) {
|
2019-09-09 13:50:21 +02:00
|
|
|
// Arbitrarily handle only the first member of the union
|
2021-11-26 23:55:36 +01:00
|
|
|
if (const AstMemberDType* const itemp = adtypep->membersp()) {
|
2021-11-13 19:50:44 +01:00
|
|
|
AstNodeDType* const subtypep = itemp->subDTypep()->skipRefp();
|
2024-02-08 03:03:32 +01:00
|
|
|
if (adtypep->packed()) {
|
|
|
|
|
ToggleEnt newent{above.m_comment + "."s + itemp->name(),
|
2025-05-23 23:38:34 +02:00
|
|
|
above.m_varRefp->cloneTree(false),
|
|
|
|
|
above.m_chgRefp->cloneTree(false)};
|
|
|
|
|
toggleVarRecurse(subtypep, depth + 1, newent, varp);
|
2024-02-08 03:03:32 +01:00
|
|
|
newent.cleanup();
|
|
|
|
|
} else {
|
|
|
|
|
AstNodeExpr* const varRefp = new AstStructSel{
|
2025-05-23 23:38:34 +02:00
|
|
|
varp->fileline(), above.m_varRefp->cloneTree(false), itemp->name()};
|
2024-02-08 03:03:32 +01:00
|
|
|
AstNodeExpr* const chgRefp = new AstStructSel{
|
2025-05-23 23:38:34 +02:00
|
|
|
varp->fileline(), above.m_chgRefp->cloneTree(false), itemp->name()};
|
2024-02-08 03:03:32 +01:00
|
|
|
varRefp->dtypep(subtypep);
|
|
|
|
|
chgRefp->dtypep(subtypep);
|
|
|
|
|
ToggleEnt newent{above.m_comment + "."s + itemp->name(), varRefp, chgRefp};
|
2025-05-23 23:38:34 +02:00
|
|
|
toggleVarRecurse(subtypep, depth + 1, newent, varp);
|
2024-02-08 03:03:32 +01:00
|
|
|
newent.cleanup();
|
|
|
|
|
}
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
2024-02-08 14:44:27 +01:00
|
|
|
} else if (VN_IS(dtypep, QueueDType)) {
|
|
|
|
|
// Not covered
|
2020-04-14 04:51:35 +02:00
|
|
|
} else {
|
2019-05-19 22:13:13 +02:00
|
|
|
dtypep->v3fatalSrc("Unexpected node data type in toggle coverage generation: "
|
2020-04-14 04:51:35 +02:00
|
|
|
<< dtypep->prettyTypeName());
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
2008-12-12 21:34:02 +01:00
|
|
|
}
|
2025-05-19 15:35:35 +02:00
|
|
|
bool includeCondToBranchRecursive(const AstNode* const nodep) {
|
|
|
|
|
const AstNode* const backp = nodep->backp();
|
|
|
|
|
if (VN_IS(backp, Cond) && VN_AS(backp, Cond)->condp() != nodep) {
|
|
|
|
|
return includeCondToBranchRecursive(backp);
|
|
|
|
|
} else if (VN_IS(backp, Sel) && VN_AS(backp, Sel)->fromp() == nodep) {
|
|
|
|
|
return includeCondToBranchRecursive(backp);
|
2025-09-29 16:25:25 +02:00
|
|
|
} else if (VN_IS(backp, NodeAssign) && VN_AS(backp, NodeAssign)->rhsp() == nodep) {
|
2025-05-19 15:35:35 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2008-12-12 21:34:02 +01:00
|
|
|
|
2008-12-10 23:10:03 +01:00
|
|
|
// VISITORS - LINE COVERAGE
|
2025-05-19 15:35:35 +02:00
|
|
|
void visit(AstCond* nodep) override {
|
2025-05-23 02:29:32 +02:00
|
|
|
UINFO(4, " COND: " << nodep);
|
2025-05-19 15:35:35 +02:00
|
|
|
|
|
|
|
|
if (m_seeking == NONE) coverExprs(nodep->condp());
|
|
|
|
|
|
|
|
|
|
if (m_state.lineCoverageOn(nodep) && VN_IS(m_modp, Module)
|
|
|
|
|
&& includeCondToBranchRecursive(nodep)) {
|
|
|
|
|
VL_RESTORER(m_seeking);
|
|
|
|
|
// Disable expression coverage in sub-expressions, since they were already visited
|
|
|
|
|
m_seeking = ABORTED;
|
|
|
|
|
|
|
|
|
|
const CheckState lastState = m_state;
|
|
|
|
|
createHandle(nodep);
|
|
|
|
|
iterate(nodep->thenp());
|
|
|
|
|
lineTrack(nodep);
|
|
|
|
|
AstNodeExpr* const thenp = nodep->thenp()->unlinkFrBack();
|
2025-08-04 14:29:56 +02:00
|
|
|
AstCoverOtherDecl* const thenDeclp
|
|
|
|
|
= new AstCoverOtherDecl{thenp->fileline(), "v_branch/" + m_modp->prettyName(),
|
|
|
|
|
"cond_then", linesCov(m_state, nodep), 0};
|
|
|
|
|
m_modp->addStmtsp(thenDeclp);
|
|
|
|
|
nodep->thenp(new AstExprStmt{
|
|
|
|
|
thenp->fileline(),
|
|
|
|
|
newCoverInc(nodep->fileline(), thenDeclp, traceNameForLine(nodep, "cond_then")),
|
|
|
|
|
thenp});
|
2025-05-19 15:35:35 +02:00
|
|
|
m_state = lastState;
|
|
|
|
|
createHandle(nodep);
|
|
|
|
|
iterate(nodep->elsep());
|
|
|
|
|
AstNodeExpr* const elsep = nodep->elsep()->unlinkFrBack();
|
2025-08-04 14:29:56 +02:00
|
|
|
AstCoverOtherDecl* const elseDeclp
|
|
|
|
|
= new AstCoverOtherDecl{thenp->fileline(), "v_branch/" + m_modp->prettyName(),
|
|
|
|
|
"cond_else", linesCov(m_state, nodep), 1};
|
|
|
|
|
m_modp->addStmtsp(elseDeclp);
|
|
|
|
|
nodep->elsep(new AstExprStmt{
|
|
|
|
|
elsep->fileline(),
|
|
|
|
|
newCoverInc(nodep->fileline(), elseDeclp, traceNameForLine(nodep, "cond_else")),
|
|
|
|
|
elsep});
|
2025-05-19 15:35:35 +02:00
|
|
|
|
|
|
|
|
m_state = lastState;
|
|
|
|
|
} else {
|
|
|
|
|
lineTrack(nodep);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-05-31 21:52:17 +02:00
|
|
|
// Note not AstNodeIf; other types don't get covered
|
2022-09-16 12:22:11 +02:00
|
|
|
void visit(AstIf* nodep) override {
|
2025-02-19 22:42:23 +01:00
|
|
|
if (nodep->user2()) return;
|
|
|
|
|
|
2025-05-23 02:29:32 +02:00
|
|
|
UINFO(4, " IF: " << nodep);
|
2020-05-31 21:52:17 +02:00
|
|
|
if (m_state.m_on) {
|
2019-05-19 22:13:13 +02:00
|
|
|
// An else-if. When we iterate the if, use "elsif" marking
|
2021-06-21 00:32:57 +02:00
|
|
|
const bool elsif
|
2022-09-15 20:43:56 +02:00
|
|
|
= nodep->thensp() && VN_IS(nodep->elsesp(), If) && !nodep->elsesp()->nextp();
|
2021-10-22 14:56:48 +02:00
|
|
|
if (elsif) VN_AS(nodep->elsesp(), If)->user1(true);
|
2021-06-21 00:32:57 +02:00
|
|
|
const bool first_elsif = !nodep->user1() && elsif;
|
|
|
|
|
const bool cont_elsif = nodep->user1() && elsif;
|
|
|
|
|
const bool final_elsif = nodep->user1() && !elsif && nodep->elsesp();
|
2020-05-31 21:52:17 +02:00
|
|
|
//
|
|
|
|
|
// Considered: If conditional is on a different line from if/else then we
|
|
|
|
|
// can show it as part of line coverage of the statement
|
|
|
|
|
// above. Otherwise show it based on what is inside.
|
|
|
|
|
// But: Seemed too complicated, and fragile.
|
2021-06-21 00:32:57 +02:00
|
|
|
const CheckState lastState = m_state;
|
2020-05-31 21:52:17 +02:00
|
|
|
CheckState ifState;
|
|
|
|
|
CheckState elseState;
|
|
|
|
|
{
|
2025-02-19 22:42:23 +01:00
|
|
|
VL_RESTORER(m_exprStmtsp);
|
|
|
|
|
VL_RESTORER(m_then);
|
|
|
|
|
m_exprStmtsp = nodep;
|
|
|
|
|
m_then = true;
|
2020-05-31 21:52:17 +02:00
|
|
|
createHandle(nodep);
|
2022-09-15 20:43:56 +02:00
|
|
|
iterateAndNextNull(nodep->thensp());
|
2020-05-31 21:52:17 +02:00
|
|
|
lineTrack(nodep);
|
|
|
|
|
ifState = m_state;
|
|
|
|
|
}
|
|
|
|
|
m_state = lastState;
|
|
|
|
|
{
|
2025-02-19 22:42:23 +01:00
|
|
|
VL_RESTORER(m_exprStmtsp);
|
|
|
|
|
VL_RESTORER(m_then);
|
|
|
|
|
m_exprStmtsp = nodep;
|
|
|
|
|
m_then = false;
|
2020-05-31 21:52:17 +02:00
|
|
|
createHandle(nodep);
|
|
|
|
|
iterateAndNextNull(nodep->elsesp());
|
|
|
|
|
elseState = m_state;
|
|
|
|
|
}
|
|
|
|
|
m_state = lastState;
|
2019-05-19 22:13:13 +02:00
|
|
|
//
|
2020-05-31 21:52:17 +02:00
|
|
|
// If both if and else are "on", and we're not in an if/else, then
|
|
|
|
|
// we do branch coverage
|
|
|
|
|
if (!(first_elsif || cont_elsif || final_elsif) && ifState.lineCoverageOn(nodep)
|
|
|
|
|
&& elseState.lineCoverageOn(nodep)) {
|
|
|
|
|
// Normal if. Linecov shows what's inside the if (not condition that is
|
|
|
|
|
// always executed)
|
2025-05-23 02:29:32 +02:00
|
|
|
UINFO(4, " COVER-branch: " << nodep);
|
2025-08-04 14:29:56 +02:00
|
|
|
AstCoverOtherDecl* const ifDeclp
|
|
|
|
|
= new AstCoverOtherDecl{nodep->fileline(), "v_branch/" + m_modp->prettyName(),
|
|
|
|
|
"if", linesCov(ifState, nodep), 0};
|
|
|
|
|
m_modp->addStmtsp(ifDeclp);
|
|
|
|
|
nodep->addThensp(
|
|
|
|
|
newCoverInc(nodep->fileline(), ifDeclp, traceNameForLine(nodep, "if")));
|
2020-05-31 21:52:17 +02:00
|
|
|
// The else has a column offset of 1 to uniquify it relative to the if
|
|
|
|
|
// As "if" and "else" are more than one character wide, this won't overlap
|
|
|
|
|
// another token
|
2025-08-04 14:29:56 +02:00
|
|
|
AstCoverOtherDecl* const elseDeclp
|
|
|
|
|
= new AstCoverOtherDecl{nodep->fileline(), "v_branch/" + m_modp->prettyName(),
|
|
|
|
|
"else", linesCov(elseState, nodep), 1};
|
|
|
|
|
m_modp->addStmtsp(elseDeclp);
|
|
|
|
|
nodep->addElsesp(
|
|
|
|
|
newCoverInc(nodep->fileline(), elseDeclp, traceNameForLine(nodep, "else")));
|
2020-05-31 21:52:17 +02:00
|
|
|
}
|
|
|
|
|
// If/else attributes to each block as non-branch coverage
|
|
|
|
|
else if (first_elsif || cont_elsif) {
|
2025-05-23 02:29:32 +02:00
|
|
|
UINFO(4, " COVER-elsif: " << nodep);
|
2020-05-31 21:52:17 +02:00
|
|
|
if (ifState.lineCoverageOn(nodep)) {
|
2025-08-04 14:29:56 +02:00
|
|
|
AstCoverOtherDecl* const elsifDeclp = new AstCoverOtherDecl{
|
|
|
|
|
nodep->fileline(), "v_line/" + m_modp->prettyName(), "elsif",
|
|
|
|
|
linesCov(ifState, nodep), 0};
|
|
|
|
|
m_modp->addStmtsp(elsifDeclp);
|
|
|
|
|
|
|
|
|
|
nodep->addThensp(newCoverInc(nodep->fileline(), elsifDeclp,
|
2022-09-15 20:43:56 +02:00
|
|
|
traceNameForLine(nodep, "elsif")));
|
2020-05-31 21:52:17 +02:00
|
|
|
}
|
|
|
|
|
// and we don't insert the else as the child if-else will do so
|
|
|
|
|
} else {
|
|
|
|
|
// Cover as separate blocks (not a branch as is not two-legged)
|
|
|
|
|
if (ifState.lineCoverageOn(nodep)) {
|
2025-05-23 02:29:32 +02:00
|
|
|
UINFO(4, " COVER-half-if: " << nodep);
|
2025-08-04 14:29:56 +02:00
|
|
|
AstCoverOtherDecl* const ifDeclp = new AstCoverOtherDecl{
|
|
|
|
|
nodep->fileline(), "v_line/" + m_modp->prettyName(), "if",
|
|
|
|
|
linesCov(ifState, nodep), 0};
|
|
|
|
|
m_modp->addStmtsp(ifDeclp);
|
|
|
|
|
nodep->addThensp(
|
|
|
|
|
newCoverInc(nodep->fileline(), ifDeclp, traceNameForLine(nodep, "if")));
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
2020-05-31 21:52:17 +02:00
|
|
|
if (elseState.lineCoverageOn(nodep)) {
|
2025-05-23 02:29:32 +02:00
|
|
|
UINFO(4, " COVER-half-el: " << nodep);
|
2025-08-04 14:29:56 +02:00
|
|
|
AstCoverOtherDecl* const elseDeclp = new AstCoverOtherDecl{
|
|
|
|
|
nodep->fileline(), "v_line/" + m_modp->prettyName(), "else",
|
|
|
|
|
linesCov(elseState, nodep), 1};
|
|
|
|
|
m_modp->addStmtsp(elseDeclp);
|
|
|
|
|
|
|
|
|
|
nodep->addElsesp(newCoverInc(nodep->fileline(), elseDeclp,
|
2020-05-31 21:52:17 +02:00
|
|
|
traceNameForLine(nodep, "else")));
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
|
|
|
|
}
|
2020-05-31 21:52:17 +02:00
|
|
|
m_state = lastState;
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
2025-02-19 22:42:23 +01:00
|
|
|
VL_RESTORER(m_ifCond);
|
|
|
|
|
m_ifCond = true;
|
|
|
|
|
iterateAndNextNull(nodep->condp());
|
2025-05-23 02:29:32 +02:00
|
|
|
UINFO(9, " done HANDLE " << m_state.m_handle << " for " << nodep);
|
2006-08-26 13:35:28 +02:00
|
|
|
}
|
2022-09-16 12:22:11 +02:00
|
|
|
void visit(AstCaseItem* nodep) override {
|
2020-05-31 21:52:17 +02:00
|
|
|
// We don't add an explicit "default" coverage if not provided,
|
|
|
|
|
// as we already have a warning when there is no default.
|
2025-05-23 02:29:32 +02:00
|
|
|
UINFO(4, " CASEI: " << nodep);
|
2020-05-31 21:52:17 +02:00
|
|
|
if (m_state.lineCoverageOn(nodep)) {
|
2020-08-25 03:10:43 +02:00
|
|
|
VL_RESTORER(m_state);
|
2024-10-10 00:12:55 +02:00
|
|
|
createHandle(nodep);
|
|
|
|
|
iterateAndNextNull(nodep->stmtsp());
|
|
|
|
|
if (m_state.lineCoverageOn(nodep)) { // if the case body didn't disable it
|
|
|
|
|
lineTrack(nodep);
|
2025-05-23 02:29:32 +02:00
|
|
|
UINFO(4, " COVER: " << nodep);
|
2025-08-04 14:29:56 +02:00
|
|
|
AstCoverOtherDecl* const declp
|
|
|
|
|
= new AstCoverOtherDecl{nodep->fileline(), "v_line/" + m_modp->prettyName(),
|
|
|
|
|
"case", linesCov(m_state, nodep), 0};
|
|
|
|
|
m_modp->addStmtsp(declp);
|
|
|
|
|
nodep->addStmtsp(
|
|
|
|
|
newCoverInc(nodep->fileline(), declp, traceNameForLine(nodep, "case")));
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
|
|
|
|
}
|
2006-08-26 13:35:28 +02:00
|
|
|
}
|
2022-09-16 12:22:11 +02:00
|
|
|
void visit(AstCover* nodep) override {
|
2025-05-23 02:29:32 +02:00
|
|
|
UINFO(4, " COVER: " << nodep);
|
2020-08-25 03:10:43 +02:00
|
|
|
VL_RESTORER(m_state);
|
2024-10-10 00:12:55 +02:00
|
|
|
m_state.m_on = true; // Always do cover blocks, even if there's a $stop
|
|
|
|
|
createHandle(nodep);
|
|
|
|
|
iterateChildren(nodep);
|
|
|
|
|
if (!nodep->coverincsp() && v3Global.opt.coverageUser()) {
|
|
|
|
|
// Note the name may be overridden by V3Assert processing
|
|
|
|
|
lineTrack(nodep);
|
2025-08-04 14:29:56 +02:00
|
|
|
AstCoverOtherDecl* const declp
|
|
|
|
|
= new AstCoverOtherDecl{nodep->fileline(), "v_user/" + m_modp->prettyName(),
|
|
|
|
|
"cover", linesCov(m_state, nodep), 0};
|
|
|
|
|
declp->hier(m_beginHier);
|
|
|
|
|
m_modp->addStmtsp(declp);
|
|
|
|
|
nodep->addCoverincsp(
|
|
|
|
|
newCoverInc(nodep->fileline(), declp, m_beginHier + "_vlCoverageUserTrace"));
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
2006-08-26 13:35:28 +02:00
|
|
|
}
|
2022-09-16 12:22:11 +02:00
|
|
|
void visit(AstStop* nodep) override {
|
2025-05-23 02:29:32 +02:00
|
|
|
UINFO(4, " STOP: " << nodep);
|
2020-05-31 21:52:17 +02:00
|
|
|
m_state.m_on = false;
|
2006-08-26 13:35:28 +02:00
|
|
|
}
|
2025-10-22 17:04:15 +02:00
|
|
|
void visit(AstStmtPragma* nodep) override {
|
|
|
|
|
if (nodep->pragp()->pragType() == VPragmaType::COVERAGE_BLOCK_OFF) {
|
2019-05-19 22:13:13 +02:00
|
|
|
// Skip all NEXT nodes under this block, and skip this if/case branch
|
2025-05-23 02:29:32 +02:00
|
|
|
UINFO(4, " OFF: h" << m_state.m_handle << " " << nodep);
|
2020-05-31 21:52:17 +02:00
|
|
|
m_state.m_on = false;
|
2020-01-17 02:17:11 +01:00
|
|
|
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
2019-05-19 22:13:13 +02:00
|
|
|
} else {
|
2020-05-31 21:52:17 +02:00
|
|
|
if (m_state.m_on) iterateChildren(nodep);
|
|
|
|
|
lineTrack(nodep);
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
2008-11-05 16:23:03 +01:00
|
|
|
}
|
2025-09-23 20:49:01 +02:00
|
|
|
void visit(AstGenBlock* nodep) override {
|
|
|
|
|
// Similar to AstBegin
|
|
|
|
|
VL_RESTORER(m_beginHier);
|
|
|
|
|
if (nodep->name() != "") {
|
|
|
|
|
m_beginHier = m_beginHier + (m_beginHier != "" ? "__DOT__" : "") + nodep->name();
|
|
|
|
|
}
|
|
|
|
|
iterateChildren(nodep);
|
|
|
|
|
lineTrack(nodep);
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-16 12:22:11 +02:00
|
|
|
void visit(AstBegin* nodep) override {
|
2019-05-19 22:13:13 +02:00
|
|
|
// Record the hierarchy of any named begins, so we can apply to user
|
|
|
|
|
// coverage points. This is because there may be cov points inside
|
|
|
|
|
// generate blocks; each point should get separate consideration.
|
|
|
|
|
// (Currently ignored for line coverage, since any generate iteration
|
|
|
|
|
// covers the code in that line.)
|
2020-08-25 03:10:43 +02:00
|
|
|
VL_RESTORER(m_beginHier);
|
|
|
|
|
VL_RESTORER(m_inToggleOff);
|
2025-09-23 20:49:01 +02:00
|
|
|
m_inToggleOff = true;
|
2024-10-10 00:12:55 +02:00
|
|
|
if (nodep->name() != "") {
|
2025-05-16 14:24:57 +02:00
|
|
|
m_beginHier = m_beginHier + (m_beginHier != "" ? "__DOT__" : "") + nodep->name();
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
2024-10-10 00:12:55 +02:00
|
|
|
iterateChildren(nodep);
|
|
|
|
|
lineTrack(nodep);
|
2006-08-26 13:35:28 +02:00
|
|
|
}
|
2008-12-10 23:10:03 +01:00
|
|
|
|
2025-02-19 22:42:23 +01:00
|
|
|
void abortExprCoverage() {
|
|
|
|
|
// is possible to hit max while in NONE, see: exprReduce()
|
|
|
|
|
// if that happens we don't want to set ABORTED if it isn't already
|
|
|
|
|
// since that will bleed into other expressions
|
|
|
|
|
if (m_seeking != NONE) m_seeking = ABORTED;
|
|
|
|
|
m_exprs.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool checkMaxExprs(size_t additional = 0) {
|
|
|
|
|
if (m_seeking != ABORTED
|
|
|
|
|
&& static_cast<int>(m_exprs.size() + additional) <= v3Global.opt.coverageExprMax())
|
|
|
|
|
return false;
|
|
|
|
|
abortExprCoverage();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void addExprCoverInc(AstNodeExpr* nodep, int start = 0) {
|
|
|
|
|
FileLine* const fl = nodep->fileline();
|
|
|
|
|
int count = start;
|
|
|
|
|
for (CoverExpr& expr : m_exprs) {
|
|
|
|
|
const string name = "expr_" + std::to_string(count);
|
|
|
|
|
string comment = "(";
|
|
|
|
|
bool first = true;
|
|
|
|
|
AstNodeExpr* condp = nullptr;
|
|
|
|
|
for (CoverTerm& term : expr) {
|
|
|
|
|
comment += (first ? "" : " && ") + term.m_emitV
|
|
|
|
|
+ "==" + (term.m_objective ? "1" : "0");
|
2025-10-30 00:26:46 +01:00
|
|
|
AstNodeExpr* covExprp = nullptr;
|
2025-11-06 14:23:35 +01:00
|
|
|
if (VN_IS(term.m_exprp, FuncRef) || term.m_exprp->isSystemFunc()) {
|
|
|
|
|
AstNodeExpr* const frefp = term.m_exprp;
|
|
|
|
|
AstNodeDType* const dtypep = frefp->dtypep();
|
|
|
|
|
const auto pair = m_funcTemps.emplace(frefp, nullptr);
|
2025-10-30 00:26:46 +01:00
|
|
|
AstVar* varp = pair.first->second;
|
|
|
|
|
if (pair.second) {
|
|
|
|
|
varp = new AstVar{fl, VVarType::MODULETEMP, m_exprTempNames.get(frefp),
|
|
|
|
|
dtypep};
|
|
|
|
|
pair.first->second = varp;
|
|
|
|
|
if (m_ftaskp) {
|
|
|
|
|
varp->funcLocal(true);
|
|
|
|
|
varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
|
|
|
|
|
m_ftaskp->stmtsp()->addHereThisAsNext(varp);
|
|
|
|
|
} else {
|
|
|
|
|
m_modp->stmtsp()->addHereThisAsNext(varp);
|
|
|
|
|
}
|
|
|
|
|
VNRelinker relinkHandle;
|
|
|
|
|
frefp->unlinkFrBack(&relinkHandle);
|
|
|
|
|
relinkHandle.relink(new AstExprStmt{
|
|
|
|
|
fl, new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE}, frefp},
|
|
|
|
|
new AstVarRef{fl, varp, VAccess::READ}});
|
|
|
|
|
}
|
|
|
|
|
covExprp = new AstVarRef{fl, varp, VAccess::READ};
|
|
|
|
|
} else {
|
|
|
|
|
covExprp = term.m_exprp->cloneTree(false);
|
|
|
|
|
}
|
|
|
|
|
AstNodeExpr* const termp
|
|
|
|
|
= term.m_objective ? covExprp : new AstLogNot{fl, covExprp};
|
2025-02-19 22:42:23 +01:00
|
|
|
if (condp) {
|
|
|
|
|
condp = new AstLogAnd{fl, condp, termp};
|
|
|
|
|
} else {
|
|
|
|
|
condp = termp;
|
|
|
|
|
}
|
|
|
|
|
first = false;
|
|
|
|
|
}
|
|
|
|
|
comment += ") => ";
|
|
|
|
|
comment += (m_objective ? '1' : '0');
|
2025-08-04 14:29:56 +02:00
|
|
|
AstCoverOtherDecl* const declp = new AstCoverOtherDecl{
|
|
|
|
|
nodep->fileline(), "v_expr/" + m_modp->prettyName(), comment, "", 0};
|
|
|
|
|
m_modp->addStmtsp(declp);
|
|
|
|
|
AstNode* const newp = newCoverInc(fl, declp, traceNameForLine(nodep, name));
|
2025-02-19 22:42:23 +01:00
|
|
|
UASSERT_OBJ(condp, nodep, "No terms in expression coverage branch");
|
|
|
|
|
AstIf* const ifp = new AstIf{fl, condp, newp, nullptr};
|
|
|
|
|
ifp->user2(true);
|
|
|
|
|
insertProcStatement(m_exprStmtsp, ifp);
|
|
|
|
|
++count;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void coverExprs(AstNodeExpr* nodep) {
|
|
|
|
|
if (!m_state.exprCoverageOn(nodep) || nodep->dtypep()->width() != 1 || !m_exprStmtsp) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UASSERT_OBJ(m_seeking == NONE, nodep, "recursively covering expressions is not expected");
|
|
|
|
|
UASSERT_OBJ(m_exprs.empty(), nodep, "unexpected expression coverage garbage");
|
|
|
|
|
VL_RESTORER(m_seeking);
|
|
|
|
|
VL_RESTORER(m_objective);
|
|
|
|
|
VL_RESTORER(m_exprs);
|
|
|
|
|
|
|
|
|
|
m_seeking = SEEKING;
|
|
|
|
|
m_objective = false;
|
|
|
|
|
iterate(nodep);
|
|
|
|
|
CoverExprs falseExprs;
|
|
|
|
|
m_exprs.swap(falseExprs);
|
|
|
|
|
|
|
|
|
|
m_objective = true;
|
|
|
|
|
iterate(nodep);
|
|
|
|
|
if (checkMaxExprs(falseExprs.size())) return;
|
2025-03-20 00:01:31 +01:00
|
|
|
if (m_seeking == ABORTED) return;
|
2025-02-19 22:42:23 +01:00
|
|
|
|
|
|
|
|
addExprCoverInc(nodep);
|
|
|
|
|
const int start = m_exprs.size();
|
|
|
|
|
m_objective = false;
|
|
|
|
|
m_exprs.swap(falseExprs);
|
|
|
|
|
addExprCoverInc(nodep, start);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void exprEither(AstNodeBiop* nodep, bool overrideObjective = false, bool lObjective = false,
|
|
|
|
|
bool rObjective = false) {
|
|
|
|
|
VL_RESTORER(m_objective);
|
|
|
|
|
AstNodeExpr* const lhsp = nodep->lhsp();
|
|
|
|
|
AstNodeExpr* const rhsp = nodep->rhsp();
|
|
|
|
|
|
|
|
|
|
if (overrideObjective) m_objective = lObjective;
|
|
|
|
|
iterate(lhsp);
|
|
|
|
|
if (checkMaxExprs()) return;
|
|
|
|
|
CoverExprs lhsExprs;
|
|
|
|
|
m_exprs.swap(lhsExprs);
|
|
|
|
|
if (overrideObjective) m_objective = rObjective;
|
|
|
|
|
iterate(rhsp);
|
|
|
|
|
m_exprs.splice(m_exprs.end(), lhsExprs);
|
|
|
|
|
checkMaxExprs();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void exprBoth(AstNodeBiop* nodep, bool overrideObjective = false, bool lObjective = false,
|
|
|
|
|
bool rObjective = false) {
|
|
|
|
|
VL_RESTORER(m_objective);
|
|
|
|
|
AstNodeExpr* const lhsp = nodep->lhsp();
|
|
|
|
|
AstNodeExpr* const rhsp = nodep->rhsp();
|
|
|
|
|
|
|
|
|
|
if (overrideObjective) m_objective = lObjective;
|
|
|
|
|
iterate(lhsp);
|
|
|
|
|
if (checkMaxExprs()) return;
|
|
|
|
|
CoverExprs lhsExprs;
|
|
|
|
|
m_exprs.swap(lhsExprs);
|
|
|
|
|
if (overrideObjective) m_objective = rObjective;
|
|
|
|
|
iterate(rhsp);
|
|
|
|
|
if (checkMaxExprs()) return;
|
|
|
|
|
CoverExprs rhsExprs;
|
|
|
|
|
m_exprs.swap(rhsExprs);
|
|
|
|
|
|
|
|
|
|
for (CoverExpr& l : lhsExprs) {
|
|
|
|
|
for (CoverExpr& r : rhsExprs) {
|
|
|
|
|
// array size 2 -> (false, true)
|
|
|
|
|
std::array<std::set<AstVar*>, 2> varps;
|
|
|
|
|
std::array<std::set<std::string>, 2> strs;
|
|
|
|
|
|
|
|
|
|
UASSERT_OBJ(!l.empty() && !r.empty(), nodep, "Empty coverage expression branch");
|
|
|
|
|
CoverExpr expr;
|
|
|
|
|
|
|
|
|
|
// Compare Vars for simple VarRefs otherwise compare stringified terms
|
|
|
|
|
// remove redundant terms and remove entire expression branches when
|
|
|
|
|
// terms conflict
|
|
|
|
|
// Equivalent terms which don't match on either of these criteria will
|
|
|
|
|
// not be flagged as redundant or impossible, however the results will
|
|
|
|
|
// still be valid, albeit messier
|
|
|
|
|
for (CoverTerm& term : l) {
|
2025-08-19 23:02:10 +02:00
|
|
|
if (const AstVarRef* const refp = VN_CAST(term.m_exprp, VarRef)) {
|
2025-02-19 22:42:23 +01:00
|
|
|
varps[term.m_objective].insert(refp->varp());
|
|
|
|
|
} else {
|
|
|
|
|
strs[term.m_objective].insert(term.m_emitV);
|
|
|
|
|
}
|
|
|
|
|
expr.push_back(term);
|
|
|
|
|
}
|
|
|
|
|
bool impossible = false;
|
|
|
|
|
for (CoverTerm& term : r) {
|
|
|
|
|
bool redundant = false;
|
2025-08-19 23:02:10 +02:00
|
|
|
if (const AstNodeVarRef* const refp = VN_CAST(term.m_exprp, NodeVarRef)) {
|
2025-02-19 22:42:23 +01:00
|
|
|
if (varps[term.m_objective].find(refp->varp())
|
|
|
|
|
!= varps[term.m_objective].end())
|
|
|
|
|
redundant = true;
|
|
|
|
|
if (varps[!term.m_objective].find(refp->varp())
|
|
|
|
|
!= varps[!term.m_objective].end())
|
|
|
|
|
impossible = true;
|
|
|
|
|
} else {
|
|
|
|
|
if (strs[term.m_objective].find(term.m_emitV)
|
|
|
|
|
!= strs[term.m_objective].end())
|
|
|
|
|
redundant = true;
|
|
|
|
|
if (strs[!term.m_objective].find(term.m_emitV)
|
|
|
|
|
!= strs[!term.m_objective].end())
|
|
|
|
|
impossible = true;
|
|
|
|
|
}
|
|
|
|
|
if (!redundant) expr.push_back(term);
|
|
|
|
|
}
|
|
|
|
|
if (!impossible) m_exprs.push_back(std::move(expr));
|
|
|
|
|
if (checkMaxExprs()) return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void orExpr(AstNodeBiop* nodep) {
|
|
|
|
|
if (m_seeking == NONE) {
|
|
|
|
|
coverExprs(nodep);
|
|
|
|
|
} else if (m_objective) {
|
|
|
|
|
exprEither(nodep);
|
|
|
|
|
} else {
|
|
|
|
|
exprBoth(nodep);
|
|
|
|
|
}
|
|
|
|
|
lineTrack(nodep);
|
|
|
|
|
}
|
|
|
|
|
void visit(AstLogOr* nodep) override { orExpr(nodep); }
|
|
|
|
|
void visit(AstOr* nodep) override { orExpr(nodep); }
|
|
|
|
|
|
|
|
|
|
void andExpr(AstNodeBiop* nodep) {
|
|
|
|
|
if (m_seeking == NONE) {
|
|
|
|
|
coverExprs(nodep);
|
|
|
|
|
} else if (m_objective) {
|
|
|
|
|
exprBoth(nodep);
|
|
|
|
|
} else {
|
|
|
|
|
exprEither(nodep);
|
|
|
|
|
}
|
|
|
|
|
lineTrack(nodep);
|
|
|
|
|
}
|
|
|
|
|
void visit(AstLogAnd* nodep) override { andExpr(nodep); }
|
|
|
|
|
void visit(AstAnd* nodep) override { andExpr(nodep); }
|
|
|
|
|
|
|
|
|
|
void xorExpr(AstNodeBiop* nodep) {
|
|
|
|
|
if (m_seeking == NONE) {
|
|
|
|
|
coverExprs(nodep);
|
|
|
|
|
} else {
|
|
|
|
|
for (const bool lObjective : {false, true}) {
|
|
|
|
|
CoverExprs prevExprs;
|
|
|
|
|
m_exprs.swap(prevExprs);
|
|
|
|
|
const bool rObjective = lObjective ^ m_objective;
|
|
|
|
|
exprBoth(nodep, true, lObjective, rObjective);
|
|
|
|
|
m_exprs.splice(m_exprs.end(), prevExprs);
|
|
|
|
|
if (checkMaxExprs()) break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
lineTrack(nodep);
|
|
|
|
|
}
|
|
|
|
|
void visit(AstXor* nodep) override { xorExpr(nodep); }
|
|
|
|
|
|
|
|
|
|
void exprNot(AstNodeExpr* nodep) {
|
|
|
|
|
VL_RESTORER(m_objective);
|
|
|
|
|
if (m_seeking == NONE) {
|
|
|
|
|
coverExprs(nodep);
|
|
|
|
|
} else {
|
|
|
|
|
m_objective = !m_objective;
|
|
|
|
|
iterateChildren(nodep);
|
|
|
|
|
lineTrack(nodep);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
void visit(AstNot* nodep) override { exprNot(nodep); }
|
|
|
|
|
void visit(AstLogNot* nodep) override { exprNot(nodep); }
|
|
|
|
|
|
|
|
|
|
template <typename T_Oper>
|
|
|
|
|
void exprReduce(AstNodeUniop* nodep) {
|
|
|
|
|
if (m_seeking != ABORTED) {
|
|
|
|
|
FileLine* const fl = nodep->fileline();
|
|
|
|
|
AstNodeExpr* const lhsp = nodep->lhsp();
|
|
|
|
|
const int width = lhsp->dtypep()->width();
|
|
|
|
|
const size_t expected = std::is_same<T_Oper, AstXor>::value ? 0x1 << width : width + 1;
|
|
|
|
|
if (checkMaxExprs(expected)) return;
|
2025-06-24 17:59:09 +02:00
|
|
|
AstNodeExpr* unrolledp = new AstSel{
|
|
|
|
|
fl, lhsp->cloneTree(false), new AstConst{fl, static_cast<uint32_t>(width - 1)}, 1};
|
2025-02-19 22:42:23 +01:00
|
|
|
for (int bit = width - 2; bit >= 0; bit--) {
|
2025-05-23 23:38:34 +02:00
|
|
|
AstSel* const selp = new AstSel{fl, lhsp->cloneTree(false),
|
2025-06-24 17:59:09 +02:00
|
|
|
new AstConst{fl, static_cast<uint32_t>(bit)}, 1};
|
2025-02-19 22:42:23 +01:00
|
|
|
unrolledp = new T_Oper{fl, selp, unrolledp};
|
|
|
|
|
}
|
|
|
|
|
iterate(unrolledp);
|
|
|
|
|
pushDeletep(unrolledp);
|
|
|
|
|
} else {
|
|
|
|
|
iterateChildren(nodep);
|
|
|
|
|
lineTrack(nodep);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
void visit(AstRedOr* nodep) override { exprReduce<AstOr>(nodep); }
|
|
|
|
|
void visit(AstRedAnd* nodep) override { exprReduce<AstAnd>(nodep); }
|
|
|
|
|
void visit(AstRedXor* nodep) override { exprReduce<AstXor>(nodep); }
|
|
|
|
|
|
|
|
|
|
void visit(AstLogIf* nodep) override {
|
|
|
|
|
if (m_seeking == NONE) {
|
|
|
|
|
coverExprs(nodep);
|
|
|
|
|
} else if (m_objective) {
|
|
|
|
|
exprEither(nodep, true, false, true);
|
|
|
|
|
} else {
|
|
|
|
|
exprBoth(nodep, true, true, false);
|
|
|
|
|
}
|
|
|
|
|
lineTrack(nodep);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void visit(AstLogEq* nodep) override {
|
|
|
|
|
VL_RESTORER(m_objective);
|
|
|
|
|
if (m_seeking == NONE) {
|
|
|
|
|
coverExprs(nodep);
|
|
|
|
|
} else {
|
|
|
|
|
m_objective = !m_objective;
|
|
|
|
|
xorExpr(nodep);
|
|
|
|
|
lineTrack(nodep);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-20 00:01:31 +01:00
|
|
|
void visit(AstFuncRef* nodep) override {
|
|
|
|
|
if (nodep->taskp()->lifetime().isAutomatic()) {
|
|
|
|
|
visit(static_cast<AstNodeExpr*>(nodep));
|
|
|
|
|
} else {
|
|
|
|
|
exprUnsupported(nodep, "non-automatic function");
|
|
|
|
|
}
|
2025-02-19 22:42:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void visit(AstNodeExpr* nodep) override {
|
|
|
|
|
if (m_seeking != SEEKING) {
|
|
|
|
|
iterateChildren(nodep);
|
|
|
|
|
} else {
|
2025-03-20 00:01:31 +01:00
|
|
|
ExprCoverageEligibleVisitor elgibleVisitor(nodep);
|
|
|
|
|
if (elgibleVisitor.eligible()) {
|
|
|
|
|
std::stringstream emitV;
|
|
|
|
|
V3EmitV::verilogForTree(nodep, emitV);
|
|
|
|
|
// Add new expression with a single term
|
|
|
|
|
CoverExpr expr;
|
|
|
|
|
expr.emplace_back(nodep, m_objective, emitV.str());
|
|
|
|
|
m_exprs.push_back(std::move(expr));
|
|
|
|
|
checkMaxExprs();
|
|
|
|
|
} else {
|
|
|
|
|
exprUnsupported(nodep, "not coverage eligible");
|
|
|
|
|
}
|
2025-02-19 22:42:23 +01:00
|
|
|
}
|
|
|
|
|
lineTrack(nodep);
|
|
|
|
|
}
|
|
|
|
|
|
2008-12-10 23:10:03 +01:00
|
|
|
// VISITORS - BOTH
|
2022-09-16 12:22:11 +02:00
|
|
|
void visit(AstNode* nodep) override {
|
2020-05-31 21:52:17 +02:00
|
|
|
iterateChildren(nodep);
|
|
|
|
|
lineTrack(nodep);
|
2006-08-26 13:35:28 +02:00
|
|
|
}
|
|
|
|
|
|
2025-03-20 00:01:31 +01:00
|
|
|
void exprUnsupported(AstNode* nodep, const string& why) {
|
2025-05-23 02:29:32 +02:00
|
|
|
UINFO(9, "unsupported: " << why << " " << nodep);
|
2025-03-20 00:01:31 +01:00
|
|
|
bool wasSeeking = m_seeking == SEEKING;
|
|
|
|
|
Objective oldSeeking = m_seeking;
|
2025-03-20 03:55:11 +01:00
|
|
|
if (wasSeeking) abortExprCoverage();
|
2025-03-20 00:01:31 +01:00
|
|
|
m_seeking = ABORTED;
|
|
|
|
|
iterateChildren(nodep);
|
|
|
|
|
lineTrack(nodep);
|
2025-03-20 03:55:11 +01:00
|
|
|
if (!wasSeeking) m_seeking = oldSeeking;
|
2025-03-20 00:01:31 +01:00
|
|
|
}
|
|
|
|
|
|
2006-08-26 13:35:28 +02:00
|
|
|
public:
|
2019-09-12 13:22:22 +02:00
|
|
|
// CONSTRUCTORS
|
2025-10-30 00:26:46 +01:00
|
|
|
explicit CoverageVisitor(AstNetlist* rootp)
|
|
|
|
|
: m_exprTempNames{"__VExpr"} {
|
|
|
|
|
iterateChildren(rootp);
|
|
|
|
|
}
|
2022-09-16 12:22:11 +02:00
|
|
|
~CoverageVisitor() override = default;
|
2006-08-26 13:35:28 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//######################################################################
|
|
|
|
|
// Coverage class functions
|
|
|
|
|
|
|
|
|
|
void V3Coverage::coverage(AstNetlist* rootp) {
|
2025-05-23 02:29:32 +02:00
|
|
|
UINFO(2, __FUNCTION__ << ":");
|
2021-11-26 16:52:36 +01:00
|
|
|
{ CoverageVisitor{rootp}; } // Destruct before checking
|
2024-01-09 16:35:13 +01:00
|
|
|
V3Global::dumpCheckGlobalTree("coverage", 0, dumpTreeEitherLevel() >= 3);
|
2006-08-26 13:35:28 +02:00
|
|
|
}
|