verilator/src/V3AssertProp.cpp

600 lines
25 KiB
C++
Raw Normal View History

// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Implementation of assertion properties
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// 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: 2005-2026 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
// Each sequence is translated into a decision tree in form of deterministic
// finite automaton (DFA) with bipartite structure. Each cycle delay is connected
// with an expression that depending on an evaluation result, proceeds to the next
// evaluation state. The structure is rooted with original sequence expression for
// simplifying further transformation back to AST.
//
// The graph consists of the following nodes:
//
// DfaStmtVertex: Statements to be executed to traverse from one state to another
// DfaExprVertex: Property expression that is checked and based on that a branch
// is taken.
// DfaConditionEdge: Branch edge that connects statements and expressions.
//
// Properties steps:
// Ensemble a property decision tree from sequence expressions.
// Transform property decision tree into AST, remove source sequence expression
// Property blocks are wrapped with AstPExpr that are transformed
// further by V3AssertPre and V3Assert.
//
//*************************************************************************
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
#include "V3AssertProp.h"
#include "V3Graph.h"
#include "V3UniqueNames.h"
VL_DEFINE_DEBUG_FUNCTIONS;
//######################################################################
// Data structures (graph types)
class DfaVertex VL_NOT_FINAL : public V3GraphVertex {
VL_RTTI_IMPL(DfaVertex, V3GraphVertex)
// STATE
AstNode* const m_nodep; // Underlying node
public:
// CONSTRUCTORS
explicit DfaVertex(V3Graph* graphp, AstNode* nodep) VL_MT_DISABLED : V3GraphVertex{graphp},
m_nodep{nodep} {}
AstNode* nodep() const { return m_nodep; }
string name() const override VL_MT_STABLE {
return cvtToHex(m_nodep) + "\\n " + cvtToStr(m_nodep->typeName()) + "\\n"s
+ m_nodep->fileline()->ascii();
};
string dotShape() const override {
if (inEmpty()) return "tripleoctagon";
if (outEmpty()) return "doubleoctagon";
return "oval";
}
bool isStart() const { return inEmpty(); }
};
class DfaStmtVertex final : public DfaVertex {
VL_RTTI_IMPL(DfaStmtVertex, V3GraphEdge)
public:
// CONSTRUCTORS
explicit DfaStmtVertex(V3Graph* graphp, AstNodeStmt* stmtp) VL_MT_DISABLED
: DfaVertex{graphp, stmtp} {}
string dotColor() const override { return "green"; }
};
class DfaExprVertex final : public DfaVertex {
VL_RTTI_IMPL(DfaExprVertex, V3GraphEdge)
public:
// CONSTRUCTORS
explicit DfaExprVertex(V3Graph* graphp, AstNodeExpr* exprp) VL_MT_DISABLED
: DfaVertex{graphp, exprp} {}
string dotColor() const override { return "blue"; }
};
class DfaConditionEdge final : public V3GraphEdge {
VL_RTTI_IMPL(DfaConditionEdge, V3GraphEdge)
// STATE
const bool m_ifBranch; // Whether this branch is taken for fulfilled condition
public:
// CONSTRUCTORS
explicit DfaConditionEdge(V3Graph* graphp, DfaVertex* fromp, DfaVertex* top,
bool ifBranch) VL_MT_DISABLED : V3GraphEdge{graphp, fromp, top, 1},
m_ifBranch{ifBranch} {}
~DfaConditionEdge() override = default;
bool ifBranch() const { return m_ifBranch; }
string dotColor() const override { return m_ifBranch ? "green" : "red"; }
};
// Check whether a subtree contains any AstSExpr (multi-cycle sequence)
static bool containsSExpr(const AstNode* nodep) {
return !nodep->forall([](const AstSExpr*) { return false; });
}
// A single step in a sequence timeline: delay cycles followed by an expression check
struct SeqStep final {
int delayCycles; // Cycle delay before this check (0 for first step)
AstNodeExpr* exprp; // Expression to evaluate at this step
};
// Extract a timeline of (delay, expression) pairs from a sequence expression.
// For a plain expression, returns a single step with delay 0.
// For AstSExpr chains like `a ##1 b ##2 c`, returns [{0,a}, {1,b}, {2,c}].
static std::vector<SeqStep> extractTimeline(AstNodeExpr* nodep) {
std::vector<SeqStep> timeline;
if (AstSExpr* const sexprp = VN_CAST(nodep, SExpr)) {
// Recursively extract from the inner (preExprp) chain first
if (sexprp->preExprp()) {
if (AstSExpr* const preSExprp = VN_CAST(sexprp->preExprp(), SExpr)) {
// preExprp is itself a sequence -- extract its timeline
timeline = extractTimeline(preSExprp);
} else {
// preExprp is a plain expression -- first step at cycle 0
timeline.push_back({0, sexprp->preExprp()});
}
}
// Get cycle delay from delayp
int cycles = 0;
if (AstDelay* const dlyp = VN_CAST(sexprp->delayp(), Delay)) {
if (AstConst* const constp = VN_CAST(dlyp->lhsp(), Const)) {
cycles = constp->toSInt();
} else {
dlyp->lhsp()->v3warn(E_UNSUPPORTED,
"Unsupported: non-constant cycle delay in sequence and/or");
}
}
// The expression after the delay
if (AstSExpr* const innerSExprp = VN_CAST(sexprp->exprp(), SExpr)) {
// Nested SExpr: extract its timeline and offset by current delay
std::vector<SeqStep> inner = extractTimeline(innerSExprp);
if (!inner.empty()) {
inner[0].delayCycles += cycles;
for (auto& step : inner) timeline.push_back(step);
}
} else {
timeline.push_back({cycles, sexprp->exprp()});
}
} else {
// Plain boolean expression -- single step, no delay
timeline.push_back({0, nodep});
}
return timeline;
}
// Lower sequence and/or to AST
class AssertPropLowerVisitor final : public VNVisitor {
// STATE
AstNodeModule* m_modp = nullptr; // Current module
V3UniqueNames m_seqBrNames{"__VseqBr"}; // Sequence branch dead-tracking name generator
// Lower a multi-cycle sequence 'and' to an AstPExpr with interleaved if/delay AST.
void lowerSeqAnd(AstNodeBiop* nodep) {
FileLine* const flp = nodep->fileline();
// Extract timelines from both operands
const std::vector<SeqStep> lhsTimeline = extractTimeline(nodep->lhsp());
const std::vector<SeqStep> rhsTimeline = extractTimeline(nodep->rhsp());
// Compute absolute cycle for each step
struct AbsStep final {
int cycle;
AstNodeExpr* exprp;
int branchId; // 0=lhs, 1=rhs
};
std::vector<AbsStep> allSteps;
int absCycle = 0;
for (const auto& step : lhsTimeline) {
absCycle += step.delayCycles;
allSteps.push_back({absCycle, step.exprp, 0});
}
const int lhsMaxCycle = absCycle;
absCycle = 0;
for (const auto& step : rhsTimeline) {
absCycle += step.delayCycles;
allSteps.push_back({absCycle, step.exprp, 1});
}
const int rhsMaxCycle = absCycle;
const int maxCycle = std::max(lhsMaxCycle, rhsMaxCycle);
// Sort by absolute cycle, then by branch id
std::stable_sort(allSteps.begin(), allSteps.end(), [](const AbsStep& a, const AbsStep& b) {
if (a.cycle != b.cycle) return a.cycle < b.cycle;
return a.branchId < b.branchId;
});
// Build AstPExprClause terminals
auto makePass = [&]() -> AstPExprClause* { return new AstPExprClause{flp, true}; };
auto makeFail = [&]() -> AstPExprClause* { return new AstPExprClause{flp, false}; };
{
// AND: all checks must pass. Generate nested if/delay chain.
// Group steps by cycle, combine same-cycle checks with LogAnd.
// Build from innermost (last cycle) outward.
// Group steps by cycle
std::map<int, std::vector<AstNodeExpr*>> cycleChecks;
for (const auto& step : allSteps) {
cycleChecks[step.cycle].push_back(step.exprp->cloneTree(false));
}
// Build from the last cycle inward
AstNode* innerp = makePass();
int prevCycle = maxCycle;
for (auto it = cycleChecks.rbegin(); it != cycleChecks.rend(); ++it) {
const int cycle = it->first;
auto& exprs = it->second;
// Combine all expressions at this cycle with LogAnd
AstNodeExpr* condp = new AstSampled{flp, exprs[0]};
condp->dtypeSetBit();
for (size_t i = 1; i < exprs.size(); ++i) {
AstNodeExpr* const rp = new AstSampled{flp, exprs[i]};
rp->dtypeSetBit();
condp = new AstLogAnd{flp, condp, rp};
condp->dtypeSetBit();
}
// Wrap in if: if (cond) { delay + inner } else { fail }
AstBegin* const thenp = new AstBegin{flp, "", nullptr, true};
// Add delay if needed (from this cycle to previous inner cycle)
if (prevCycle > cycle) {
const int delayCycles = prevCycle - cycle;
AstDelay* const dlyp = new AstDelay{
flp, new AstConst{flp, static_cast<uint32_t>(delayCycles)}, true};
thenp->addStmtsp(dlyp);
dlyp->addStmtsp(innerp);
} else {
thenp->addStmtsp(innerp);
}
AstIf* const ifp = new AstIf{flp, condp, thenp, makeFail()};
innerp = ifp;
prevCycle = cycle;
}
// Wrap in AstPExpr
AstBegin* const bodyp = new AstBegin{flp, "", nullptr, true};
bodyp->addStmtsp(innerp);
AstPExpr* const pexprp = new AstPExpr{flp, bodyp, nodep->dtypep()};
nodep->replaceWith(pexprp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}
}
// Lower a multi-cycle sequence 'or' to an AstPExpr with dead-tracking variables.
// IEEE 1800-2023 16.9.7: composite matches if at least one operand sequence matches.
// Both branches are evaluated independently from the same start cycle.
void lowerSeqOr(AstNodeBiop* nodep) {
UASSERT_OBJ(m_modp, nodep, "SOr not under a module");
FileLine* const flp = nodep->fileline();
// Extract timelines from both operands
const std::vector<SeqStep> lhsTimeline = extractTimeline(nodep->lhsp());
const std::vector<SeqStep> rhsTimeline = extractTimeline(nodep->rhsp());
// Compute absolute cycle for each step, mark which is last per branch
struct AbsStep final {
int cycle;
AstNodeExpr* exprp;
int branchId;
};
std::vector<AbsStep> allSteps;
int absCycle = 0;
for (const auto& step : lhsTimeline) {
absCycle += step.delayCycles;
allSteps.push_back({absCycle, step.exprp, 0});
}
const int br0MaxCycle = absCycle;
absCycle = 0;
for (const auto& step : rhsTimeline) {
absCycle += step.delayCycles;
allSteps.push_back({absCycle, step.exprp, 1});
}
const int br1MaxCycle = absCycle;
// Group by cycle, preserving branch info
struct CycleEntry final {
int branchId;
AstNodeExpr* exprp;
};
std::map<int, std::vector<CycleEntry>> cycleChecks;
for (const auto& step : allSteps) {
cycleChecks[step.cycle].push_back({step.branchId, step.exprp});
}
// Create dead-tracking variables at module level
AstVar* const br0Deadp = new AstVar{flp, VVarType::MODULETEMP, m_seqBrNames.get("0_dead"),
VFlagBitPacked{}, 1};
br0Deadp->lifetime(VLifetime::STATIC_EXPLICIT);
AstVar* const br1Deadp = new AstVar{flp, VVarType::MODULETEMP, m_seqBrNames.get("1_dead"),
VFlagBitPacked{}, 1};
br1Deadp->lifetime(VLifetime::STATIC_EXPLICIT);
m_modp->addStmtsp(br0Deadp);
m_modp->addStmtsp(br1Deadp);
auto makePass = [&]() -> AstPExprClause* { return new AstPExprClause{flp, true}; };
auto makeFail = [&]() -> AstPExprClause* { return new AstPExprClause{flp, false}; };
// Build from innermost (last cycle) outward, same nesting pattern as and
AstNode* innerp = nullptr;
int nextCycle = -1;
for (auto rit = cycleChecks.rbegin(); rit != cycleChecks.rend(); ++rit) {
const int cycle = rit->first;
AstBegin* const cycleBlock = new AstBegin{flp, "", nullptr, true};
// For each branch's check at this cycle
for (const auto& entry : rit->second) {
AstVar* const deadVarp = (entry.branchId == 0) ? br0Deadp : br1Deadp;
const int brMaxCycle = (entry.branchId == 0) ? br0MaxCycle : br1MaxCycle;
const bool isLast = (cycle == brMaxCycle);
AstNodeExpr* const sampledp = new AstSampled{flp, entry.exprp->cloneTree(false)};
sampledp->dtypeSetBit();
AstNodeExpr* const alivep
= new AstLogNot{flp, new AstVarRef{flp, deadVarp, VAccess::READ}};
alivep->dtypeSetBit();
if (isLast) {
// Last check: alive && passes -> pass
AstNodeExpr* const passCond = new AstLogAnd{flp, alivep, sampledp};
passCond->dtypeSetBit();
cycleBlock->addStmtsp(new AstIf{flp, passCond, makePass()});
// alive && fails -> dead
AstNodeExpr* const alive2p
= new AstLogNot{flp, new AstVarRef{flp, deadVarp, VAccess::READ}};
alive2p->dtypeSetBit();
AstNodeExpr* const failCond = new AstLogAnd{
flp, alive2p, new AstLogNot{flp, sampledp->cloneTree(false)}};
failCond->dtypeSetBit();
cycleBlock->addStmtsp(
new AstIf{flp, failCond,
new AstAssign{flp, new AstVarRef{flp, deadVarp, VAccess::WRITE},
new AstConst{flp, AstConst::BitTrue{}}}});
} else {
// Non-last: alive && fails -> dead
AstNodeExpr* const failCond
= new AstLogAnd{flp, alivep, new AstLogNot{flp, sampledp}};
failCond->dtypeSetBit();
cycleBlock->addStmtsp(
new AstIf{flp, failCond,
new AstAssign{flp, new AstVarRef{flp, deadVarp, VAccess::WRITE},
new AstConst{flp, AstConst::BitTrue{}}}});
}
}
// Both dead -> fail
AstNodeExpr* const allDeadp
= new AstLogAnd{flp, new AstVarRef{flp, br0Deadp, VAccess::READ},
new AstVarRef{flp, br1Deadp, VAccess::READ}};
allDeadp->dtypeSetBit();
cycleBlock->addStmtsp(new AstIf{flp, allDeadp, makeFail()});
// Nest delay + inner from later cycles
if (nextCycle > cycle && innerp) {
AstDelay* const dlyp = new AstDelay{
flp, new AstConst{flp, static_cast<uint32_t>(nextCycle - cycle)}, true};
cycleBlock->addStmtsp(dlyp);
dlyp->addStmtsp(innerp);
}
innerp = cycleBlock;
nextCycle = cycle;
}
// Wrap in AstPExpr with initialization
AstBegin* const bodyp = new AstBegin{flp, "", nullptr, true};
bodyp->addStmtsp(new AstAssign{flp, new AstVarRef{flp, br0Deadp, VAccess::WRITE},
new AstConst{flp, AstConst::BitFalse{}}});
bodyp->addStmtsp(new AstAssign{flp, new AstVarRef{flp, br1Deadp, VAccess::WRITE},
new AstConst{flp, AstConst::BitFalse{}}});
bodyp->addStmtsp(innerp);
AstPExpr* const pexprp = new AstPExpr{flp, bodyp, nodep->dtypep()};
nodep->replaceWith(pexprp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}
void visit(AstSAnd* nodep) override {
iterateChildren(nodep);
if (containsSExpr(nodep->lhsp()) || containsSExpr(nodep->rhsp())) {
lowerSeqAnd(nodep);
} else {
// Pure boolean operands: lower to LogAnd
AstLogAnd* const newp = new AstLogAnd{nodep->fileline(), nodep->lhsp()->unlinkFrBack(),
nodep->rhsp()->unlinkFrBack()};
newp->dtypeFrom(nodep);
nodep->replaceWith(newp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}
}
void visit(AstSOr* nodep) override {
iterateChildren(nodep);
if (containsSExpr(nodep->lhsp()) || containsSExpr(nodep->rhsp())) {
lowerSeqOr(nodep);
} else {
// Pure boolean operands: lower to LogOr
AstLogOr* const newp = new AstLogOr{nodep->fileline(), nodep->lhsp()->unlinkFrBack(),
nodep->rhsp()->unlinkFrBack()};
newp->dtypeFrom(nodep);
nodep->replaceWith(newp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}
}
void visit(AstNodeModule* nodep) override {
VL_RESTORER(m_modp);
m_modp = nodep;
iterateChildren(nodep);
}
void visit(AstNode* nodep) override { iterateChildren(nodep); }
void visit(AstConstPool* nodep) override {}
public:
explicit AssertPropLowerVisitor(AstNetlist* nodep) { iterate(nodep); }
~AssertPropLowerVisitor() override = default;
};
// Parse properties and ensemble a property tree graph
class AssertPropBuildVisitor final : public VNVisitorConst {
// STATE
V3Graph& m_graph; // Property tree
DfaVertex* m_lastVtxp = nullptr; // Last encountered vertex
bool m_underSExpr = false; // Is under sequence expression, for creating a start node
size_t m_underLogNots = 0; // Number of 'not' operators before sequence
DfaStmtVertex* makeClause(const AstSExpr* nodep, bool pass) {
return new DfaStmtVertex{
&m_graph,
new AstPExprClause{nodep->fileline(), m_underLogNots % 2 == 0 ? pass : !pass}};
}
// VISITORS
void visit(AstNodeCoverOrAssert* nodep) override { iterateChildrenConst(nodep); }
void visit(AstLogNot* nodep) override {
VL_RESTORER(m_underLogNots);
++m_underLogNots;
iterateChildrenConst(nodep);
}
void visit(AstSExpr* nodep) override {
if (VN_IS(nodep->exprp(), SExpr)) {
VL_RESTORER(m_underSExpr);
m_underSExpr = true;
iterateConst(nodep->exprp());
} else {
DfaExprVertex* const exprVtxp
= new DfaExprVertex{&m_graph, nodep->exprp()->unlinkFrBack()};
new DfaConditionEdge{&m_graph, exprVtxp, makeClause(nodep, true), true};
new DfaConditionEdge{&m_graph, exprVtxp, makeClause(nodep, false), false};
m_lastVtxp = exprVtxp;
}
DfaExprVertex* const startVtxp
= m_underSExpr ? nullptr : new DfaExprVertex{&m_graph, nodep};
DfaStmtVertex* const dlyVtxp
= new DfaStmtVertex{&m_graph, nodep->delayp()->unlinkFrBack()};
if (AstSExpr* const sexprp = VN_CAST(nodep->preExprp(), SExpr)) {
UASSERT_OBJ(!sexprp->preExprp() && !VN_IS(sexprp->exprp(), SExpr), sexprp,
"Incorrect sexpr tree");
DfaStmtVertex* const sdlyVtxp
= new DfaStmtVertex{&m_graph, sexprp->delayp()->unlinkFrBack()};
DfaExprVertex* const exprVtxp
= new DfaExprVertex{&m_graph, sexprp->exprp()->unlinkFrBack()};
if (startVtxp) new DfaConditionEdge{&m_graph, startVtxp, sdlyVtxp, true};
new DfaConditionEdge{&m_graph, sdlyVtxp, exprVtxp, true};
new DfaConditionEdge{&m_graph, exprVtxp, dlyVtxp, true};
new DfaConditionEdge{&m_graph, dlyVtxp, m_lastVtxp, true};
new DfaConditionEdge{&m_graph, exprVtxp, makeClause(nodep, false), false};
// This case only occurs when multi-delay sequence starts with an expression,
// don't set last as this is never a last expression.
} else if (nodep->preExprp()) {
DfaExprVertex* const preVtxp
= new DfaExprVertex{&m_graph, nodep->preExprp()->unlinkFrBack()};
if (startVtxp) new DfaConditionEdge{&m_graph, startVtxp, preVtxp, true};
new DfaConditionEdge{&m_graph, preVtxp, dlyVtxp, true};
new DfaConditionEdge{&m_graph, dlyVtxp, m_lastVtxp, true};
new DfaConditionEdge{&m_graph, preVtxp, makeClause(nodep, false), false};
m_lastVtxp = preVtxp;
} else {
if (startVtxp) new DfaConditionEdge{&m_graph, startVtxp, dlyVtxp, true};
new DfaConditionEdge{&m_graph, dlyVtxp, m_lastVtxp, true};
m_lastVtxp = dlyVtxp;
}
}
void visit(AstSOr* nodep) override {} // All SOr lowered by AssertPropLowerVisitor
void visit(AstNode* nodep) override { iterateChildrenConst(nodep); }
void visit(AstConstPool* nodep) override {}
public:
// CONSTRUCTORS
explicit AssertPropBuildVisitor(AstNetlist* nodep, V3Graph& graph)
: m_graph{graph} {
iterateConst(nodep);
if (dumpGraphLevel() >= 6) m_graph.dumpDotFilePrefixedAlways("properties", true);
}
~AssertPropBuildVisitor() override = default;
};
// Transform property graph into AST
class AssertPropTransformer final {
// STATE
V3Graph& m_graph; // Property tree
AstPExpr* m_pexprp = nullptr; // Currently built property sequence
AstBegin* m_current = nullptr; // Currently built block
V3GraphVertex* processVtx(V3GraphVertex* vtxp) {
if (DfaStmtVertex* const stmtp = vtxp->cast<DfaStmtVertex>()) return processVtx(stmtp);
if (DfaExprVertex* const exprp = vtxp->cast<DfaExprVertex>()) return processVtx(exprp);
// TODO use C++17 std::variant and std::visit
v3fatalSrc("Unexpected vertex type");
return nullptr;
}
V3GraphVertex* processVtx(DfaStmtVertex* vtxp) {
UASSERT_OBJ(!vtxp->isStart(), vtxp->nodep(),
"Starting node should be a property expression");
UASSERT_OBJ(m_current, vtxp->nodep(), "Should be under a block");
m_current->addStmtsp(vtxp->nodep());
return processEdge(vtxp->outEdges().frontp());
}
V3GraphVertex* processVtx(DfaExprVertex* vtxp) {
AstNode* const nodep = vtxp->nodep();
if (vtxp->isStart()) {
AstBegin* const bodyp = new AstBegin{nodep->fileline(), "", nullptr, true};
m_pexprp = new AstPExpr{nodep->fileline(), bodyp, nodep->dtypep()};
UASSERT_OBJ(vtxp->outSize1(), nodep, "Starting node must have one out edge");
m_current = m_pexprp->bodyp();
return processEdge(vtxp->outEdges().frontp());
}
UASSERT_OBJ(vtxp->outEdges().size() == 2, nodep, "Each expression must have two branches");
AstBegin* const passsp = new AstBegin{nodep->fileline(), "", nullptr, true};
AstNode* const failsp = vtxp->outEdges().backp()->top()->as<DfaStmtVertex>()->nodep();
AstSampled* const sampledp
= new AstSampled{nodep->fileline(), VN_AS(vtxp->nodep(), NodeExpr)};
sampledp->dtypeFrom(vtxp->nodep());
AstIf* const ifp = new AstIf{nodep->fileline(), sampledp, passsp, failsp};
m_current->addStmtsp(ifp);
m_current = passsp;
return processEdge(vtxp->outEdges().frontp());
}
V3GraphVertex* processEdge(const V3GraphEdge* edgep) {
if (edgep) return processVtx(edgep->top());
return nullptr;
}
public:
// CONSTRUCTORS
explicit AssertPropTransformer(V3Graph& graph)
: m_graph{graph} {
for (V3GraphVertex& vtx : m_graph.vertices()) {
if (DfaVertex* const dVtxp = vtx.cast<DfaExprVertex>()) {
if (dVtxp->isStart()) {
VL_RESTORER(m_pexprp);
processVtx(&vtx);
AstSExpr* const propp = VN_AS(dVtxp->nodep(), SExpr);
propp->replaceWith(m_pexprp);
VL_DO_DANGLING(propp->deleteTree(), propp);
}
}
}
}
};
//######################################################################
// Top AssertProp class
void V3AssertProp::assertPropAll(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ":");
{ AssertPropLowerVisitor{nodep}; }
{
V3Graph graph;
{ AssertPropBuildVisitor{nodep, graph}; }
AssertPropTransformer{graph};
}
V3Global::dumpCheckGlobalTree("assertproperties", 0, dumpTreeEitherLevel() >= 3);
}