Files
verilator/src/V3Active.cpp
T

800 lines
32 KiB
C++
Raw Normal View History

// -*- mode: C++; c-file-style: "cc-mode" -*-
2006-08-26 11:35:28 +00:00
//*************************************************************************
// DESCRIPTION: Verilator: Break always into sensitivity active domains
//
2019-11-07 22:33:59 -05:00
// Code available from: https://verilator.org
2006-08-26 11:35:28 +00:00
//
//*************************************************************************
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of either the GNU Lesser General Public License Version 3
// or the Perl Artistic License Version 2.0.
// SPDX-FileCopyrightText: 2003-2026 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
2006-08-26 11:35:28 +00:00
//
//*************************************************************************
// V3Active's Transformations:
2008-06-09 21:25:10 -04:00
//
2006-08-26 11:35:28 +00:00
// Note this can be called multiple times.
// Create a IACTIVE(initial), SACTIVE(combo)
// ALWAYS: Remove any-edges from sense list
// If no POS/NEG in senselist, Fold into SACTIVE(combo)
// Else fold into SACTIVE(sequent).
// OPTIMIZE: When support async clocks, fold into that active if possible
// INITIAL: Move into IACTIVE
// WIRE: Move into SACTIVE(combo)
2006-08-26 11:35:28 +00:00
//
//*************************************************************************
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
2006-08-26 11:35:28 +00:00
#include "V3Active.h"
2022-08-05 10:56:57 +01:00
#include "V3Const.h"
#include "V3Graph.h"
#include <unordered_map>
#include <unordered_set>
2006-08-26 11:35:28 +00:00
2022-09-18 20:53:42 +01:00
VL_DEFINE_DEBUG_FUNCTIONS;
2006-08-26 11:35:28 +00:00
//***** See below for main transformation engine
//######################################################################
// Extend V3GraphVertex class for use in latch detection graph
class LatchDetectGraphVertex final : public V3GraphVertex {
VL_RTTI_IMPL(LatchDetectGraphVertex, V3GraphVertex)
public:
enum VertexType : uint8_t { VT_BLOCK, VT_BRANCH, VT_OUTPUT };
private:
const string m_name; // Only used for .dot file generation
const VertexType m_type; // Vertex type (BLOCK/BRANCH/OUTPUT)
string typestr() const VL_MT_SAFE { // "
switch (m_type) {
case VT_BLOCK: return "(||)"; // basic block node
case VT_BRANCH: return "(&&)"; // if/else branch mode
case VT_OUTPUT: return "(out)"; // var assignment
default: return "??"; // unknown
}
}
public:
LatchDetectGraphVertex(V3Graph* graphp, const string& name, VertexType type = VT_BLOCK)
: V3GraphVertex{graphp}
, m_name{name}
, m_type{type} {}
string name() const override VL_MT_STABLE { return m_name + " " + typestr(); }
string dotColor() const override { return user() ? "green" : "black"; }
virtual int type() const { return m_type; }
};
//######################################################################
// Extend V3Graph class for use as a latch detection graph
class LatchDetectGraph final : public V3Graph {
protected:
LatchDetectGraphVertex* m_curVertexp = nullptr; // Current latch detection graph vertex
std::vector<AstVarRef*> m_outputs; // Vector of lvalues encountered on this pass
static LatchDetectGraphVertex* castVertexp(void* vertexp) {
return reinterpret_cast<LatchDetectGraphVertex*>(vertexp);
}
// Recursively traverse the graph to determine whether every control 'BLOCK' has an assignment
2022-12-23 11:32:38 -05:00
// to the output we are currently analyzing (the output whose 'user() is set), if so return
// true. Where a BLOCK contains a BRANCH, both the if and else sides of the branch must return
// true for the BRANCH to evaluate to true. A BLOCK however needs only a single one of its
// siblings to evaluate true in order to evaluate true itself. On output vertex only evaluates
// true if it is the vertex we are analyzing on this check
bool latchCheckInternal(LatchDetectGraphVertex* vertexp) {
bool result = false;
switch (vertexp->type()) {
case LatchDetectGraphVertex::VT_OUTPUT: // Base case
result = vertexp->user();
break;
case LatchDetectGraphVertex::VT_BLOCK: // (OR of potentially many siblings)
// cppcheck-suppress constVariableReference
2024-03-25 23:06:25 +00:00
for (V3GraphEdge& edge : vertexp->outEdges()) {
if (latchCheckInternal(castVertexp(edge.top()))) {
result = true;
break;
}
}
break;
case LatchDetectGraphVertex::VT_BRANCH: // (AND of both sibling)
// A BRANCH vertex always has exactly 2 siblings
2024-03-25 23:06:25 +00:00
LatchDetectGraphVertex* const ifp = castVertexp(vertexp->outEdges().frontp()->top());
LatchDetectGraphVertex* const elsp = castVertexp(vertexp->outEdges().backp()->top());
result = latchCheckInternal(ifp) && latchCheckInternal(elsp);
break;
}
vertexp->user(result);
return result;
}
public:
LatchDetectGraph() { clear(); }
~LatchDetectGraph() override { clear(); }
// ACCESSORS
LatchDetectGraphVertex* currentp() { return m_curVertexp; }
void currentp(LatchDetectGraphVertex* vertex) { m_curVertexp = vertex; }
// METHODS
void begin() {
// Start a new if/else tracking graph
2022-10-21 19:03:40 -04:00
m_curVertexp = new LatchDetectGraphVertex{this, "ROOT"};
}
// Clear out userp field of referenced outputs on destruction
// (occurs at the end of each combinational always block)
// cppcheck-suppress duplInheritedMember
void clear() {
m_outputs.clear();
// Calling base class clear will unlink & delete all edges & vertices
V3Graph::clear();
m_curVertexp = nullptr;
}
// Add a new control path and connect it to its parent
LatchDetectGraphVertex* addPathVertex(LatchDetectGraphVertex* parent, const string& name,
bool branch = false) {
m_curVertexp = new LatchDetectGraphVertex{this, name,
branch ? LatchDetectGraphVertex::VT_BRANCH
: LatchDetectGraphVertex::VT_BLOCK};
new V3GraphEdge{this, parent, m_curVertexp, 1};
return m_curVertexp;
}
// Add a new output variable vertex and store a pointer to it in the user1 field of the
// variables AstNode
LatchDetectGraphVertex* addOutputVertex(AstVarRef* nodep) {
LatchDetectGraphVertex* const outVertexp
= new LatchDetectGraphVertex{this, nodep->name(), LatchDetectGraphVertex::VT_OUTPUT};
nodep->varp()->user1p(outVertexp);
m_outputs.push_back(nodep);
return outVertexp;
}
// Connect an output assignment to its parent control block
void addAssignment(AstVarRef* nodep) {
LatchDetectGraphVertex* outVertexp;
if (!nodep->varp()->user1p()) { // Not seen this output before
outVertexp = addOutputVertex(nodep);
} else {
outVertexp = castVertexp(nodep->varp()->user1p());
}
2022-10-21 19:03:40 -04:00
new V3GraphEdge{this, m_curVertexp, outVertexp, 1};
}
// Run latchCheckInternal on each variable assigned by the always block to see if all control
// paths make an assignment. Detected latches are flagged in the variables AstVar
void latchCheck(AstNode* nodep, bool latch_expected) {
bool latch_detected = false;
for (const AstVarRef* const vrp : m_outputs) {
LatchDetectGraphVertex* const vertp = castVertexp(vrp->varp()->user1p());
vertp->user(true); // Identify the output vertex we are checking paths _to_
2024-03-25 23:06:25 +00:00
if (!latchCheckInternal(castVertexp(vertices().frontp()))) latch_detected = true;
if (latch_detected && !latch_expected) {
nodep->v3warn(
LATCH,
"Latch inferred for signal "
<< vrp->prettyNameQ()
<< " (not all control paths of combinational always assign a value)\n"
<< nodep->warnMore()
<< "... Suggest use of always_latch for intentional latches");
if (dumpGraphLevel() >= 9) dumpDotFilePrefixed("latch_" + vrp->name());
}
vertp->user(false); // Clear again (see above)
vrp->varp()->isLatched(latch_detected);
}
// Should _all_ variables assigned in always_latch be latches? Probably, but this only
// warns if none of them are
if (latch_expected && !latch_detected)
nodep->v3warn(NOLATCH, "No latches detected in always_latch block");
}
};
2006-08-26 11:35:28 +00:00
//######################################################################
// Collect existing active names
2022-09-18 20:53:42 +01:00
class ActiveNamer final : public VNVisitor {
2006-08-26 11:35:28 +00:00
// STATE
AstScope* m_scopep = nullptr; // Current scope to add statement to
2022-05-15 16:03:32 +01:00
AstActive* m_sActivep = nullptr; // For current scope, the Static active we're building
AstActive* m_iActivep = nullptr; // For current scope, the Initial active we're building
AstActive* m_fActivep = nullptr; // For current scope, the Final active we're building
AstActive* m_cActivep = nullptr; // For current scope, the Combo active we're building
2018-06-11 22:05:45 -04:00
2022-02-05 16:04:48 +00:00
// Map from AstSenTree (equivalence) to the corresponding AstActive created.
std::unordered_map<VNRef<AstSenTree>, AstActive*> m_activeMap;
2018-06-11 22:05:45 -04:00
2006-08-26 11:35:28 +00:00
// METHODS
void addActive(AstActive* nodep) {
2020-08-15 10:12:55 -04:00
UASSERT_OBJ(m_scopep, nodep, "nullptr scope");
2022-09-15 19:43:56 +01:00
m_scopep->addBlocksp(nodep);
2006-08-26 11:35:28 +00:00
}
2022-05-15 16:03:32 +01:00
2006-08-26 11:35:28 +00:00
// VISITORS
void visit(AstScope* nodep) override {
m_scopep = nodep;
2022-05-15 16:03:32 +01:00
m_sActivep = nullptr;
2020-08-15 10:12:55 -04:00
m_iActivep = nullptr;
2022-05-15 16:03:32 +01:00
m_fActivep = nullptr;
2020-08-15 10:12:55 -04:00
m_cActivep = nullptr;
2018-06-11 22:05:45 -04:00
m_activeMap.clear();
iterateChildren(nodep);
// Don't clear scopep, the namer persists beyond this visit
2006-08-26 11:35:28 +00:00
}
void visit(AstSenTree* nodep) override {
// Simplify sensitivity list
VL_DO_DANGLING(V3Const::constifyExpensiveEdit(nodep), nodep);
2006-08-26 11:35:28 +00:00
}
//--------------------
void visit(AstNodeStmt*) override {} // Accelerate
void visit(AstNode* nodep) override { iterateChildren(nodep); }
2022-05-15 16:03:32 +01:00
// Specialized below for the special sensitivity classes
template <typename T_SenItemKind>
2022-08-05 12:15:59 +01:00
AstActive*& getSpecialActive();
2022-05-15 16:03:32 +01:00
2006-08-26 11:35:28 +00:00
public:
// METHODS
2006-08-26 11:35:28 +00:00
AstScope* scopep() { return m_scopep; }
2022-05-15 16:03:32 +01:00
2022-12-23 12:34:49 +00:00
// Make a new AstActive sensitive to the given sentree and return it
AstActive* makeActive(FileLine* const fl, AstSenTree* const senTreep) {
AstActive* const activep = new AstActive{fl, "", senTreep};
activep->senTreeStorep(activep->sentreep());
addActive(activep);
return activep;
}
2022-12-23 12:34:49 +00:00
// Make a new AstActive sensitive to the given special sensitivity class and return it
template <typename T_SenItemKind>
2022-12-23 12:34:49 +00:00
AstActive* makeSpecialActive(FileLine* const fl) {
AstSenTree* const senTreep = new AstSenTree{fl, new AstSenItem{fl, T_SenItemKind{}}};
2022-12-23 12:34:49 +00:00
return makeActive(fl, senTreep);
}
// Return an AstActive sensitive to the given special sensitivity class (possibly pre-created)
template <typename T_SenItemKind>
2022-08-05 12:15:59 +01:00
AstActive* getSpecialActive(FileLine* fl) {
AstActive*& cachep = getSpecialActive<T_SenItemKind>();
if (!cachep) cachep = makeSpecialActive<T_SenItemKind>(fl);
2022-05-15 16:03:32 +01:00
return cachep;
2006-08-26 11:35:28 +00:00
}
2022-02-05 16:04:48 +00:00
// Return an AstActive that is sensitive to a SenTree equivalent to the given sentreep.
AstActive* getActive(FileLine* fl, AstSenTree* sentreep) {
UASSERT(sentreep, "Must be non-null");
2018-06-11 22:05:45 -04:00
auto it = m_activeMap.find(*sentreep);
2022-02-05 16:04:48 +00:00
// If found matching AstActive, return it
if (it != m_activeMap.end()) return it->second;
2018-06-11 22:05:45 -04:00
2022-02-05 16:04:48 +00:00
// No such AstActive yet, creat it, and add to map.
AstSenTree* const newsenp = sentreep->cloneTree(false);
2022-10-21 19:03:40 -04:00
AstActive* const activep = new AstActive{fl, "sequent", newsenp};
activep->senTreeStorep(activep->sentreep());
2022-02-05 16:04:48 +00:00
addActive(activep);
m_activeMap.emplace(*newsenp, activep);
return activep;
2006-08-26 11:35:28 +00:00
}
2019-09-12 07:22:22 -04:00
// CONSTRUCTORS
ActiveNamer() = default;
~ActiveNamer() override = default;
void main(AstScope* nodep) { iterate(nodep); }
2006-08-26 11:35:28 +00:00
};
2022-08-05 12:15:59 +01:00
template <>
AstActive*& ActiveNamer::getSpecialActive<AstSenItem::Static>() {
return m_sActivep;
}
template <>
AstActive*& ActiveNamer::getSpecialActive<AstSenItem::Initial>() {
return m_iActivep;
}
template <>
AstActive*& ActiveNamer::getSpecialActive<AstSenItem::Final>() {
return m_fActivep;
}
template <>
AstActive*& ActiveNamer::getSpecialActive<AstSenItem::Combo>() {
return m_cActivep;
}
2022-05-15 16:03:32 +01:00
//######################################################################
// Latch checking visitor
class ActiveLatchCheckVisitor final : public VNVisitorConst {
// NODE STATE
// Input:
// AstVar::user1p // V2LatchGraphVertex* The vertex handling this node
const VNUser1InUse m_inuser1;
// STATE
LatchDetectGraph m_graph; // Graph used to detect latches in combo always
// VISITORS
void visit(AstVarRef* nodep) override {
const AstVar* const varp = nodep->varp();
if (nodep->access().isWriteOrRW() && varp->isSignal() && !varp->isUsedLoopIdx()
&& !varp->isFuncLocalSticky() && !varp->lifetime().isAutomatic()) {
m_graph.addAssignment(nodep);
}
}
void visit(AstNodeIf* nodep) override {
if (!nodep->isBoundsCheck()) {
LatchDetectGraphVertex* const parentp = m_graph.currentp();
LatchDetectGraphVertex* const branchp = m_graph.addPathVertex(parentp, "BRANCH", true);
m_graph.addPathVertex(branchp, "IF");
iterateAndNextConstNull(nodep->thensp());
m_graph.addPathVertex(branchp, "ELSE");
iterateAndNextConstNull(nodep->elsesp());
m_graph.currentp(parentp);
} else {
iterateChildrenConst(nodep);
}
}
//--------------------
void visit(AstNode* nodep) override { iterateChildrenConst(nodep); }
public:
// CONSTRUCTORS
2022-05-15 16:03:32 +01:00
ActiveLatchCheckVisitor(AstNode* nodep, bool expectLatch) {
m_graph.begin();
iterateConst(nodep);
2022-05-15 16:03:32 +01:00
m_graph.latchCheck(nodep, expectLatch);
}
~ActiveLatchCheckVisitor() override = default;
};
2006-08-26 11:35:28 +00:00
//######################################################################
// Replace unsupported non-blocking assignments with blocking assignments
2006-08-26 11:35:28 +00:00
2022-09-18 20:53:42 +01:00
class ActiveDlyVisitor final : public VNVisitor {
public:
2026-06-20 23:23:05 +02:00
enum CheckType : uint8_t { CT_COMB, CT_FINAL };
2006-08-26 11:35:28 +00:00
private:
// MEMBERS
const CheckType m_check; // Process type we are checking
2006-08-26 11:35:28 +00:00
// VISITORS
void visit(AstAssignDly* nodep) override {
// Issue appropriate warning
2026-06-20 23:23:05 +02:00
if (m_check == CT_FINAL) {
nodep->v3warn(FINALDLY, "Non-blocking assignment '<=' in final block");
} else {
nodep->v3warn(COMBDLY,
"Non-blocking assignment '<=' in combinational logic process\n"
<< nodep->warnMore()
<< "... This will be executed as a blocking assignment '='!");
}
// Convert to blocking assignment
2022-08-22 14:26:32 +02:00
nodep->replaceWith(new AstAssign{
nodep->fileline(), //
nodep->lhsp()->unlinkFrBack(), //
nodep->rhsp()->unlinkFrBack(), //
nodep->timingControlp() ? nodep->timingControlp()->unlinkFrBack() : nullptr});
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}
2006-08-26 11:35:28 +00:00
//--------------------
void visit(AstNode* nodep) override { iterateChildren(nodep); }
2006-08-26 11:35:28 +00:00
public:
2019-09-12 07:22:22 -04:00
// CONSTRUCTORS
ActiveDlyVisitor(AstNode* nodep, CheckType check)
: m_check{check} {
iterate(nodep);
2006-08-26 11:35:28 +00:00
}
~ActiveDlyVisitor() override = default;
2006-08-26 11:35:28 +00:00
};
//######################################################################
// Active class functions
2022-09-18 20:53:42 +01:00
class ActiveVisitor final : public VNVisitor {
2006-08-26 11:35:28 +00:00
// NODE STATE
// Each call to V3Const::constify
2022-05-15 16:03:32 +01:00
// AstVarScope::user1() bool: This VarScope is referenced in the sensitivity list
// AstVarScope::user2() bool: This VarScope is written in the current process
// AstNode::user4() Used by V3Const::constify, called below
2006-08-26 11:35:28 +00:00
// STATE
ActiveNamer m_namer; // Tracking of active names
2022-05-15 16:03:32 +01:00
bool m_clockedProcess = false; // Whether current process is a clocked process
bool m_allChanged = false; // Whether all SenItem in the SenTree are ET_CHANGED
bool m_walkingBody = false; // Walking body of a process
bool m_canBeComb = false; // Whether current clocked process can be turned into a comb process
2006-08-26 11:35:28 +00:00
2022-05-15 16:03:32 +01:00
// METHODS
2022-08-05 12:15:59 +01:00
template <typename T>
void moveUnderSpecial(AstNode* nodep) {
2022-05-15 16:03:32 +01:00
AstActive* const wantactivep = m_namer.getSpecialActive<T>(nodep->fileline());
nodep->unlinkFrBack();
wantactivep->addStmtsp(nodep);
2008-12-12 15:34:02 -05:00
}
2006-08-26 11:35:28 +00:00
void visitAlways(AstNode* nodep, AstSenTree* oldsentreep, VAlwaysKwd kwd) {
// Move always to appropriate ACTIVE based on its sense list
if (oldsentreep && oldsentreep->sensesp() && oldsentreep->sensesp()->isNever()) {
// Never executing. Kill it.
UASSERT_OBJ(!oldsentreep->sensesp()->nextp(), nodep,
"Never senitem should be alone, else the never should be eliminated.");
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
return;
}
2006-08-26 11:35:28 +00:00
2022-05-15 16:03:32 +01:00
{
const VNUser1InUse user1InUse;
2006-08-26 11:35:28 +00:00
2022-05-15 16:03:32 +01:00
// Walk sensitivity list
m_clockedProcess = false;
m_allChanged = true;
if (oldsentreep) {
oldsentreep->unlinkFrBack();
iterateChildrenConst(oldsentreep);
2022-05-15 16:03:32 +01:00
}
// If all SenItems are ET_CHANGE, then walk the body to determine if this process
// could be turned into a combinational process instead.
if (m_allChanged) {
const VNUser2InUse user2InUse;
m_walkingBody = true;
m_canBeComb = true;
iterateChildrenConst(nodep);
m_walkingBody = false;
if (m_canBeComb) m_clockedProcess = false;
}
}
2006-08-26 11:35:28 +00:00
2022-05-15 16:03:32 +01:00
AstActive* const wantactivep
2022-08-22 14:26:32 +02:00
= !m_clockedProcess ? m_namer.getSpecialActive<AstSenItem::Combo>(nodep->fileline())
: oldsentreep ? m_namer.getActive(nodep->fileline(), oldsentreep)
// Clocked, no sensitivity lists, it's a suspendable, put it in initial
: m_namer.getSpecialActive<AstSenItem::Initial>(nodep->fileline());
2006-08-26 11:35:28 +00:00
// Move node to new active
nodep->unlinkFrBack();
wantactivep->addStmtsp(nodep);
2006-08-26 11:35:28 +00:00
2022-05-15 16:03:32 +01:00
// Warn and convert any delayed assignments
2026-06-20 23:23:05 +02:00
if (!m_clockedProcess) ActiveDlyVisitor{nodep, ActiveDlyVisitor::CT_COMB};
2022-05-15 16:03:32 +01:00
2025-01-09 14:40:32 +01:00
// Delete sensitivity list
if (oldsentreep) VL_DO_DANGLING(oldsentreep->deleteTree(), oldsentreep);
2025-01-09 14:40:32 +01:00
2022-05-15 16:03:32 +01:00
// check combinational processes for latches
if (!m_clockedProcess || kwd == VAlwaysKwd::ALWAYS_LATCH) {
const ActiveLatchCheckVisitor latchvisitor{nodep, kwd == VAlwaysKwd::ALWAYS_LATCH};
}
2006-08-26 11:35:28 +00:00
}
2022-08-22 14:26:32 +02:00
void visitSenItems(AstNode* nodep) {
if (v3Global.opt.timing().isSetTrue()) {
nodep->foreach([this](AstSenItem* senItemp) { visit(senItemp); });
2022-08-22 14:26:32 +02:00
}
}
2026-05-07 17:02:41 -04:00
static void markEventEdges(AstSenTree* sentreep) {
for (AstSenItem* senip = sentreep->sensesp(); senip;
senip = VN_AS(senip->nextp(), SenItem)) {
if (!senip->sensp()) continue;
if (const AstNodeDType* const dtypep = senip->sensp()->dtypep()) {
if (const AstBasicDType* const basicp = dtypep->basicp()) {
if (basicp->isEvent()) senip->edgeType(VEdgeType::ET_EVENT);
}
}
}
}
2022-05-15 16:03:32 +01:00
// VISITORS
2022-09-16 16:15:10 +01:00
void visit(AstScope* nodep) override {
2022-05-15 16:03:32 +01:00
m_namer.main(nodep); // Clear last scope's names, and collect this scope's existing names
iterateChildren(nodep);
}
2022-09-16 16:15:10 +01:00
void visit(AstActive* nodep) override {
2022-05-15 16:03:32 +01:00
// Actives are being formed, so we can ignore any already made
}
2022-09-16 16:15:10 +01:00
void visit(AstInitialStatic* nodep) override { moveUnderSpecial<AstSenItem::Static>(nodep); }
void visit(AstInitial* nodep) override {
2022-08-22 14:26:32 +02:00
visitSenItems(nodep);
2022-05-15 16:03:32 +01:00
moveUnderSpecial<AstSenItem::Initial>(nodep);
}
2022-09-16 16:15:10 +01:00
void visit(AstFinal* nodep) override {
2026-06-20 23:23:05 +02:00
const ActiveDlyVisitor dlyvisitor{nodep, ActiveDlyVisitor::CT_FINAL};
2022-05-15 16:03:32 +01:00
moveUnderSpecial<AstSenItem::Final>(nodep);
}
2022-09-16 16:15:10 +01:00
void visit(AstCoverToggle* nodep) override { moveUnderSpecial<AstSenItem::Combo>(nodep); }
void visit(AstAlways* nodep) override {
2022-09-15 19:43:56 +01:00
if (!nodep->stmtsp()) { // Empty always. Remove it now.
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
return;
}
if (nodep->keyword() == VAlwaysKwd::CONT_ASSIGN) {
moveUnderSpecial<AstSenItem::Combo>(nodep);
return;
}
2022-08-22 14:26:32 +02:00
visitSenItems(nodep);
visitAlways(nodep, nodep->sentreep(), nodep->keyword());
}
void visit(AstAlwaysPostponed* nodep) override {
2022-05-16 20:02:49 +01:00
// Might be empty with later optimizations, so this assertion can be removed,
// but for now it is guaranteed to be not empty.
2022-09-15 19:43:56 +01:00
UASSERT_OBJ(nodep->stmtsp(), nodep, "Should not be empty");
// Make a new active for it, needs to be the only item under the active for V3Sched
AstActive* const activep = m_namer.makeSpecialActive<AstSenItem::Combo>(nodep->fileline());
activep->addStmtsp(nodep->unlinkFrBack());
2020-11-29 11:31:38 -05:00
}
2022-12-23 12:34:49 +00:00
void visit(AstAlwaysObserved* nodep) override {
UASSERT_OBJ(nodep->sentreep(), nodep, "Should have a sentree");
2026-05-07 17:02:41 -04:00
markEventEdges(nodep->sentreep());
AstSenTree* const sentreep = nodep->sentreep();
sentreep->unlinkFrBack();
2022-12-23 12:34:49 +00:00
// Make a new active for it, needs to be the only item under the active for V3Sched
AstActive* const activep = m_namer.makeActive(nodep->fileline(), sentreep);
2022-12-23 12:34:49 +00:00
activep->addStmtsp(nodep->unlinkFrBack());
}
void visit(AstAlwaysReactive* nodep) override {
UASSERT_OBJ(nodep->sentreep(), nodep, "Should have a sentree");
2026-05-07 17:02:41 -04:00
markEventEdges(nodep->sentreep());
AstSenTree* const sentreep = nodep->sentreep();
sentreep->unlinkFrBack();
2022-12-23 12:34:49 +00:00
// Make a new active for it, needs to be the only item under the active for V3Sched
AstActive* const activep = m_namer.makeActive(nodep->fileline(), sentreep);
2022-12-23 12:34:49 +00:00
activep->addStmtsp(nodep->unlinkFrBack());
}
2022-09-16 16:15:10 +01:00
void visit(AstCFunc* nodep) override { visitSenItems(nodep); }
void visit(AstSenItem* nodep) override {
2022-08-22 14:26:32 +02:00
UASSERT_OBJ(!m_walkingBody, nodep,
"Should not reach here when walking body without --timing");
2022-05-15 16:03:32 +01:00
if (!nodep->sensp()) return; // Ignore sequential items (e.g.: initial, comb, etc.)
m_clockedProcess = true;
if (nodep->edgeType() != VEdgeType::ET_CHANGED) m_allChanged = false;
if (const AstNodeDType* const dtypep = nodep->sensp()->dtypep()) {
if (const AstBasicDType* const basicp = dtypep->basicp()) {
2022-05-15 16:03:32 +01:00
if (basicp->isEvent()) nodep->edgeType(VEdgeType::ET_EVENT);
}
}
2022-05-15 16:03:32 +01:00
nodep->sensp()->foreach([](const AstVarRef* refp) { refp->varScopep()->user1(true); });
2022-05-15 16:03:32 +01:00
}
2022-09-16 16:15:10 +01:00
void visit(AstVarRef* nodep) override {
2022-05-15 16:03:32 +01:00
AstVarScope* const vscp = nodep->varScopep();
if (nodep->access().isWriteOnly()) {
vscp->user2(true);
} else {
// If the variable is read before it is written (and is not a never-changing value),
// and is not in the sensitivity list, then this cannot be optimized into a
// combinational process
2022-05-15 16:03:32 +01:00
// TODO: live variable analysis would be more precise
if (!vscp->user2() && !vscp->varp()->valuep() && !vscp->user1()) m_canBeComb = false;
}
}
2022-09-16 16:15:10 +01:00
void visit(AstAssignDly* nodep) override {
2022-05-15 16:03:32 +01:00
m_canBeComb = false;
2022-08-22 14:26:32 +02:00
if (nodep->isTimingControl()) m_clockedProcess = true;
2022-05-15 16:03:32 +01:00
iterateChildrenConst(nodep);
}
2022-09-16 16:15:10 +01:00
void visit(AstFireEvent* nodep) override {
2022-05-15 16:03:32 +01:00
m_canBeComb = false;
iterateChildrenConst(nodep);
}
2022-09-16 16:15:10 +01:00
void visit(AstAssignForce* nodep) override {
2022-05-15 16:03:32 +01:00
m_canBeComb = false;
iterateChildrenConst(nodep);
}
2022-09-16 16:15:10 +01:00
void visit(AstRelease* nodep) override {
2022-05-15 16:03:32 +01:00
m_canBeComb = false;
iterateChildrenConst(nodep);
}
2022-09-16 16:15:10 +01:00
void visit(AstFork* nodep) override {
2022-08-22 14:26:32 +02:00
if (nodep->isTimingControl()) {
m_canBeComb = false;
m_clockedProcess = true;
}
// Do not iterate children, technically not part of this process
}
2006-08-26 11:35:28 +00:00
//--------------------
2022-09-16 16:15:10 +01:00
void visit(AstVar*) override {} // Accelerate
void visit(AstVarScope*) override {} // Accelerate
2022-09-16 16:15:10 +01:00
void visit(AstNode* nodep) override {
2022-08-22 14:26:32 +02:00
if (!v3Global.opt.timing().isSetTrue() && m_walkingBody && !m_canBeComb) {
return; // Accelerate
}
2022-05-15 16:03:32 +01:00
if (!nodep->isPure()) m_canBeComb = false;
2022-08-22 14:26:32 +02:00
if (nodep->isTimingControl()) {
m_canBeComb = false;
m_clockedProcess = true;
return;
}
2022-05-15 16:03:32 +01:00
iterateChildren(nodep);
}
2006-08-26 11:35:28 +00:00
public:
2019-09-12 07:22:22 -04:00
// CONSTRUCTORS
explicit ActiveVisitor(AstNetlist* nodep) { iterate(nodep); }
~ActiveVisitor() override = default;
2006-08-26 11:35:28 +00:00
};
//######################################################################
// Pass 1: collect sample CFuncs and sampling events from covergroup class scopes
class CovergroupCollectVisitor final : public VNVisitor {
// NODE STATE
// Netlist:
// AstClass::user1p() -> AstCFunc*. The sample() CFunc for this covergroup class
// AstClass::user2p() -> AstSenTree*. Owned sampling event template (if any)
// STATE
AstClass* m_classp = nullptr; // Current covergroup class context, or nullptr
// VISITORS
void visit(AstClass* nodep) override {
if (!nodep->isCovergroup()) return;
VL_RESTORER(m_classp);
m_classp = nodep;
iterateChildren(nodep);
}
void visit(AstScope* nodep) override { iterateChildren(nodep); }
void visit(AstCFunc* nodep) override {
if (!m_classp) return;
if (nodep->isCovergroupSample()) m_classp->user1p(nodep);
}
void visit(AstCovergroup* nodep) override {
// V3Covergroup guarantees: only supported-event covergroups survive to V3Active,
// and they are always inside a covergroup class (so m_classp is set).
// Unlink eventp from cgp so it survives cgp's deletion,
// then store it in user2p for use during the second pass.
m_classp->user2p(nodep->eventp()->unlinkFrBack());
nodep->unlinkFrBack();
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstNode* nodep) override { iterateChildren(nodep); }
public:
// CONSTRUCTORS
explicit CovergroupCollectVisitor(AstNetlist* nodep) { iterate(nodep); }
~CovergroupCollectVisitor() override = default;
};
//######################################################################
// Pass 2: inject automatic sample() calls for covergroup instances
class CovergroupEventBindVisitor final : public VNVisitor {
AstVarScope* const m_instancep; // Variable scope for the covergroup instance being sampled
std::unordered_set<const AstVar*> m_memberps; // Non-static variables in the covergroup class
void visit(AstVarRef* nodep) override {
if (!m_memberps.count(nodep->varp())) return;
FileLine* const fl = nodep->fileline();
AstMemberSel* const selp
= new AstMemberSel{fl, new AstVarRef{fl, m_instancep, VAccess::READ}, nodep->varp()};
selp->access(nodep->access());
nodep->replaceWith(selp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstNode* nodep) override { iterateChildren(nodep); }
public:
CovergroupEventBindVisitor(AstSenTree* eventp, AstClass* classp, AstVarScope* instancep)
: m_instancep{instancep} {
classp->foreachMember([&](AstClass* const, AstVar* const varp) {
if (!varp->isStatic()) m_memberps.emplace(varp);
});
iterate(eventp);
}
~CovergroupEventBindVisitor() override = default;
};
class CovergroupInjectVisitor final : public VNVisitor {
// NODE STATE (set by CovergroupCollectVisitor, consumed here)
// AstClass::user1p() -> AstCFunc*. The sample() CFunc for this covergroup class
// AstClass::user2p() -> AstSenTree*. Owned sampling event template (if any)
// STATE
ActiveNamer m_namer; // Reuse active naming infrastructure
// VISITORS
void visit(AstScope* nodep) override {
m_namer.main(nodep); // Initialize active naming for this scope
iterateChildren(nodep);
}
void visit(AstVarScope* nodep) override {
// Get the underlying var
AstVar* const varp = nodep->varp();
UASSERT_OBJ(varp, nodep, "AstVarScope must have non-null varp");
// Check if the variable is of covergroup class type
const AstNodeDType* const dtypep = varp->dtypep();
UASSERT_OBJ(dtypep, nodep, "AstVar must have non-null dtypep after V3Width");
const AstClassRefDType* const classRefp = VN_CAST(dtypep, ClassRefDType);
if (!classRefp) return;
AstClass* const classp = classRefp->classp();
// Check if this covergroup has an automatic sampling event
AstSenTree* const eventp = VN_CAST(classp->user2p(), SenTree);
if (!eventp) return; // No automatic sampling for this covergroup
// V3Covergroup guarantees every supported-event covergroup has a registered sample CFunc
AstCFunc* const sampleCFuncp = VN_AS(classp->user1p(), CFunc);
UASSERT_OBJ(sampleCFuncp, nodep,
"No sample() CFunc found for covergroup " << classp->name());
// Create a VarRef to the covergroup instance for the method call
FileLine* const fl = nodep->fileline();
AstVarRef* const varrefp = new AstVarRef{fl, nodep, VAccess::READ};
// Create the CMethodCall to sample()
// Note: We don't pass arguments in argsp since vlSymsp is passed via argTypes
AstCMethodCall* const cmethodCallp
= new AstCMethodCall{fl, varrefp, sampleCFuncp, nullptr};
cmethodCallp->dtypeSetVoid();
cmethodCallp->argTypes("vlSymsp");
// Clone the sensitivity for this active block. References to covergroup members need
// to select through this particular instance; all other VarRefs retain the VarScopes
// resolved by V3Scope.
AstSenTree* senTreep = eventp->cloneTree(false);
CovergroupEventBindVisitor{senTreep, classp, nodep};
// Get or create the AstActive node for this sensitivity
// senTreep is a template used by getActive() which clones it into the AstActive;
// delete it afterwards as it is not added to the AST directly.
AstActive* const activep = m_namer.getActive(fl, senTreep);
VL_DO_DANGLING(pushDeletep(senTreep), senTreep);
// Wrap the sample() call in an AstAlways so SchedPartition handles it
// via visit(AstNodeProcedure*) like any other clocked always block.
activep->addStmtsp(
new AstAlways{fl, VAlwaysKwd::ALWAYS_FF, nullptr, cmethodCallp->makeStmt()});
}
void visit(AstClass* nodep) override {
iterateChildren(nodep);
// Delete the owned sampling event template stored during collection
if (AstSenTree* const eventp = VN_CAST(nodep->user2p(), SenTree)) {
VL_DO_DANGLING(pushDeletep(eventp), eventp);
}
}
void visit(AstActive*) override {} // Don't iterate into actives
void visit(AstNode* nodep) override { iterateChildren(nodep); }
public:
// CONSTRUCTORS
explicit CovergroupInjectVisitor(AstNetlist* nodep) { iterate(nodep); }
~CovergroupInjectVisitor() override = default;
};
2006-08-26 11:35:28 +00:00
//######################################################################
// Active class functions
void V3Active::activeAll(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ":");
{ ActiveVisitor{nodep}; } // Destruct before checking
if (v3Global.useCovergroup()) {
// Add automatic covergroup sampling in two focused passes.
// user1p/user2p on AstClass span both passes; guards must outlive both visitors.
const VNUser1InUse user1InUse;
const VNUser2InUse user2InUse;
CovergroupCollectVisitor{nodep}; // Pass 1: collect CFuncs and events into user#p
CovergroupInjectVisitor{nodep}; // Pass 2: inject sample() calls, delete user2p events
}
V3Global::dumpCheckGlobalTree("active", 0, dumpTreeEitherLevel() >= 3);
2006-08-26 11:35:28 +00:00
}