Files
verilator/src/V3Assert.cpp
T
Geza Lore 02546791da Internals: Remove V3LinkDot::linkDotArrayed (#8495)
Follow up to #8476 and #8493, now that elaboration fully expands
instance arrays, linkDotArrayed's only remaining purpose was to name
AstBegin blocks created after V3Width.

Fix up earlier passes to name all Begin blocks they create, or avoid
creating AstBegin in the first place if possible.

One warning about assigning to a modport input of a generic interface
port had to move into LinkLValue (previously this only used to be
reported by linkDotArrayed, as the type/modportness only resolved in
V3Param, and the lvalueness is subsequently only known in LinkLValue).

Then remove linkDotArrayed and simplify V3LinkDot.cpp
2026-09-24 22:41:24 +01:00

1413 lines
66 KiB
C++

// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Collect and print statistics
//
// 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
//
//*************************************************************************
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
#include "V3Assert.h"
#include "V3AstUserAllocator.h"
#include "V3Stats.h"
#include "V3UniqueNames.h"
VL_DEFINE_DEBUG_FUNCTIONS;
namespace {
class DefaultDisableLocalVisitor final : public VNVisitor {
// STATE
AstNode* m_scopep = nullptr;
// VISITORS
void visit(AstNodeModule* nodep) override {
VL_RESTORER(m_scopep);
m_scopep = nodep;
nodep->defaultDisablep(nullptr);
iterateChildren(nodep);
}
void visit(AstGenBlock* nodep) override {
VL_RESTORER(m_scopep);
m_scopep = nodep;
nodep->defaultDisablep(nullptr);
iterateChildren(nodep);
}
void visit(AstDefaultDisable* nodep) override {
UASSERT_OBJ(nodep, m_scopep,
"default disable iff must be inside a module or generate block");
AstDefaultDisable* defaultp = nullptr;
if (const AstNodeModule* const modp = VN_CAST(m_scopep, NodeModule)) {
defaultp = modp->defaultDisablep();
} else {
defaultp = VN_AS(m_scopep, GenBlock)->defaultDisablep();
}
if (VL_UNLIKELY(defaultp)) {
nodep->v3error("Only one 'default disable iff' allowed per "
<< (VN_IS(m_scopep, NodeModule) ? "module" : "generate block")
<< " (IEEE 1800-2023 16.15)");
} else if (AstNodeModule* const modp = VN_CAST(m_scopep, NodeModule)) {
modp->defaultDisablep(nodep);
} else {
VN_AS(m_scopep, GenBlock)->defaultDisablep(nodep);
}
}
void visit(AstNode* nodep) override { iterateChildren(nodep); }
public:
explicit DefaultDisableLocalVisitor(AstNetlist* nodep) { iterate(nodep); }
};
class DefaultDisablePropagateVisitor final : public VNVisitor {
// STATE
AstDefaultDisable* m_defaultDisablep = nullptr;
// VISITORS
void visit(AstNodeModule* nodep) override {
VL_RESTORER(m_defaultDisablep);
m_defaultDisablep = nodep->defaultDisablep();
iterateChildren(nodep);
}
void visit(AstGenBlock* nodep) override {
VL_RESTORER(m_defaultDisablep);
if (!nodep->defaultDisablep()) nodep->defaultDisablep(m_defaultDisablep);
m_defaultDisablep = nodep->defaultDisablep();
iterateChildren(nodep);
}
void visit(AstNode* nodep) override { iterateChildren(nodep); }
public:
explicit DefaultDisablePropagateVisitor(AstNetlist* nodep) { iterate(nodep); }
};
// Lower a sequence used as an event control ('@seq', IEEE 1800-2023 9.4.2.4) into a
// synthesized event fired by an internal 'cover sequence' on each end-of-match
class SeqEventLowerVisitor final : public VNVisitor {
// STATE
AstNodeModule* m_modp = nullptr; // Current module
V3UniqueNames m_names{"__VseqEvent"}; // Synthesized event names
// VISITORS
void visit(AstNodeModule* nodep) override {
VL_RESTORER(m_modp);
m_modp = nodep;
iterateChildren(nodep);
}
void visit(AstSenItem* nodep) override {
AstFuncRef* const funcrefp = VN_CAST(nodep->sensp(), FuncRef);
if (funcrefp && VN_IS(funcrefp->taskp(), Sequence)) {
FileLine* const flp = nodep->fileline();
AstVar* const eventp = new AstVar{flp, VVarType::MODULETEMP, m_names.get(nodep),
m_modp->findBasicDType(VBasicDTypeKwd::EVENT)};
eventp->lifetime(VLifetime::STATIC_EXPLICIT);
m_modp->addStmtsp(eventp);
v3Global.setHasEvents();
funcrefp->unlinkFrBack();
nodep->sensp(new AstVarRef{flp, eventp, VAccess::READ});
const bool automaticActual = funcrefp->exists([](const AstNodeVarRef* refp) {
return refp->varp() && refp->varp()->lifetime().isAutomatic();
});
if (automaticActual) {
nodep->v3error("Arguments to a sequence used as an event control must be"
" static (IEEE 1800-2023 9.4.2.4)");
VN_AS(funcrefp->taskp(), Sequence)->isReferenced(false);
VL_DO_DANGLING(pushDeletep(funcrefp), funcrefp);
return;
}
AstFireEvent* const firep
= new AstFireEvent{flp, new AstVarRef{flp, eventp, VAccess::WRITE}, false};
AstCover* const coverp
= new AstCover{flp, new AstPropSpec{flp, nullptr, nullptr, funcrefp}, firep,
VAssertType::CONCURRENT};
coverp->isCoverSeq(true);
coverp->isSeqEvent(true);
m_modp->addStmtsp(coverp);
return;
}
iterateChildren(nodep);
}
void visit(AstNode* nodep) override { iterateChildren(nodep); }
public:
explicit SeqEventLowerVisitor(AstNetlist* nodep) { iterate(nodep); }
};
} // namespace
void V3AssertCommon::collectDefaultDisable(AstNetlist* nodep) {
{ DefaultDisableLocalVisitor{nodep}; }
{ DefaultDisablePropagateVisitor{nodep}; }
}
void V3AssertCommon::lowerSequenceEvents(AstNetlist* nodep) {
{ SeqEventLowerVisitor{nodep}; }
V3Global::dumpCheckGlobalTree("assertseqevent", 0, dumpTreeEitherLevel() >= 3);
}
//######################################################################
// AssertDeFutureVisitor
// If any AstFuture, then move all non-future varrefs to be one cycle behind,
// see IEEE 1800-2023 16.9.4.
class AssertDeFutureVisitor final : public VNVisitor {
// STATE - across all visitors
AstNodeModule* const m_modp; // Module future is underneath
const AstFuture* m_futurep; // First AstFuture found
const unsigned m_pastNum; // Prefix unique number for this module
std::map<AstVar*, AstVar*> m_delayedVars; // Old to delayed variable mapping
// STATE - for current visit position (use VL_RESTORER)
bool m_inFuture = false; // Inside a future
bool m_unsupported = false; // Printed unsupported
// METHODS
void unsupported(AstNode* nodep) {
if (m_unsupported) return;
m_unsupported = true;
nodep->v3warn(E_UNSUPPORTED,
"Unsupported/illegal: Future value function used with expression with "
<< nodep->prettyOperatorName());
}
// VISITORS
void visit(AstFuture* nodep) override {
VL_RESTORER(m_inFuture);
m_inFuture = true;
iterateChildren(nodep);
// Done with the future, this subexpression is current-time
nodep->replaceWith(nodep->exprp()->unlinkFrBack());
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstNodeVarRef* nodep) override {
if (nodep->user1SetOnce()) return;
if (m_inFuture || m_unsupported)
return; // Need user1 set above, don't process when Future is removed
if (nodep->access().isWriteOrRW()) {
unsupported(nodep);
return;
}
auto it = m_delayedVars.find(nodep->varp());
AstVar* outvarp;
if (it == m_delayedVars.end()) {
AstSenTree* const sentreep = m_futurep->sentreep();
AstAlways* const alwaysp = new AstAlways{nodep->fileline(), VAlwaysKwd::ALWAYS,
sentreep->cloneTree(false), nullptr};
m_modp->addStmtsp(alwaysp);
outvarp = new AstVar{nodep->fileline(), VVarType::MODULETEMP,
"__Vnotfuture" + cvtToStr(m_pastNum) + "_" + nodep->name(),
nodep->dtypep()};
m_modp->addStmtsp(outvarp);
AstVarRef* varRefAWritep = new AstVarRef{nodep->fileline(), outvarp, VAccess::WRITE};
varRefAWritep->user1(true);
AstNodeVarRef* varRefAReadp = nodep->cloneTree(false);
varRefAReadp->user1(true);
AstNode* const assp = new AstAssignDly{nodep->fileline(), varRefAWritep, varRefAReadp};
alwaysp->addStmtsp(assp);
m_delayedVars.emplace(nodep->varp(), outvarp);
} else {
outvarp = it->second;
}
AstVarRef* newp = new AstVarRef{nodep->fileline(), outvarp, VAccess::READ};
newp->user1(true);
UINFO(9, "DeFuture " << nodep << " becomes " << newp);
nodep->replaceWith(newp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstNodeFTaskRef* nodep) override { unsupported(nodep); }
void visit(AstMethodCall* nodep) override { unsupported(nodep); }
void visit(AstNode* nodep) override {
if (!nodep->isPure()) unsupported(nodep);
iterateChildren(nodep);
}
public:
// CONSTRUCTORS
explicit AssertDeFutureVisitor(AstNode* nodep, AstNodeModule* modp, unsigned pastNum)
: m_modp{modp}
, m_pastNum{pastNum} {
// See if any Future before we process
if (nodep->forall([&](const AstFuture* futurep) -> bool {
m_futurep = futurep;
return false;
}))
return;
// UINFOTREE(9, nodep, "", "defuture-in");
visit(nodep); // Nodep may get deleted
// UINFOTREE(9, nodep, "", "defuture-ou");
}
~AssertDeFutureVisitor() = default;
};
// Prepare one default sampled expression.
class DefaultSampledValueVisitor final : public VNVisitor {
// METHODS
static AstConst* newTypeDefault(AstNodeExpr* nodep) {
AstConst* const constp
= new AstConst{nodep->fileline(), AstConst::DTyped{}, nodep->dtypep()};
if (nodep->dtypep()->isFourstate()) constp->num().setAllBitsX();
constp->dtypeFrom(nodep);
return constp;
}
// VISITORS
void visit(AstCMethodHard* nodep) override {
if (nodep->method() == VCMethod::EVENT_IS_TRIGGERED) {
nodep->replaceWith(new AstConst{nodep->fileline(), AstConst::BitFalse{}});
VL_DO_DANGLING(pushDeletep(nodep), nodep);
} else {
iterateChildren(nodep);
}
}
void visit(AstNodeVarRef* nodep) override {
AstVar* const varp = nodep->varp();
if (varp->isParam()
|| (!varp->isNet() && varp->lifetime().isStatic() && varp->hasUserInit())) {
return;
}
nodep->replaceWith(newTypeDefault(nodep));
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstNode* nodep) override { iterateChildren(nodep); }
public:
explicit DefaultSampledValueVisitor(AstNode* nodep) { iterate(nodep); }
};
//######################################################################
// AssertVisitor
class AssertVisitor final : public VNVisitor {
// CONSTANTS
static constexpr uint8_t ALL_ASSERT_TYPES
= std::numeric_limits<std::underlying_type<VAssertType::en>::type>::max();
// NODE STATE/TYPES
// Cleared on netlist
// AstNode::user1() -> bool. True if processed
// AstAlways::user2p() -> std::vector<AstVar*>. Delayed variables via 'm_delayed'
// AstNodeVarRef::user2() -> bool. True if shouldn't be sampled
// AstIf::user2() -> bool. True for assertOn() checks inserted in this pass
const VNUser1InUse m_user1InUse;
const VNUser2InUse m_user2InUse;
AstUser2Allocator<AstAlways, std::vector<AstVar*>> m_delayed;
// STATE
AstNodeModule* m_modp = nullptr; // Last module
const AstNode* m_beginp = nullptr; // Last AstBegin/AstGenBlock
unsigned m_monitorNum = 0; // Global $monitor numbering (not per module)
AstVar* m_monitorNumVarp = nullptr; // $monitor number variable
AstVar* m_monitorOffVarp = nullptr; // $monitoroff variable
unsigned m_modPastNum = 0; // Module past numbering
unsigned m_modStrobeNum = 0; // Module $strobe numbering
AstNodeProcedure* m_procedurep = nullptr; // Current procedure
VDouble0 m_statCover; // Statistic tracking
VDouble0 m_statAsNotImm; // Statistic tracking
VDouble0 m_statAsImm; // Statistic tracking
VDouble0 m_statAsFull; // Statistic tracking
VDouble0 m_statPastVars; // Statistic tracking
VDouble0 m_statAssertOnCombined; // Statistic tracking
VDouble0 m_statAssertOnHoisted; // Statistic tracking
bool m_inSampled = false; // True inside a sampled expression
bool m_inRestrict = false; // True inside restrict assertion
AstNode* m_passsp = nullptr; // Current pass statement
AstNode* m_failsp = nullptr; // Current fail statement
AstNodeCoverOrAssert* m_assertp = nullptr; // Current assertion
AstFinal* m_finalp = nullptr; // Current final block
VDouble0 m_statLiftedCaseExprs; // Count of purified case expressions
AstNodeFTask* m_ftaskp = nullptr; // Current function/task
V3UniqueNames m_caseTempNames{"__VCase"};
V3UniqueNames m_matchCountNames{"__VnfaRemainingMatchCount"}; // Match replay counter names
// Maps from (expression, senTree) to the AstAlways that computes its delayed values.
std::unordered_map<VNRef<AstNodeExpr>, std::unordered_map<VNRef<AstSenTree>, AstAlways*>>
m_modExpr2Sen2DelayedAlwaysp;
// Source sampled-value history is initialized, unlike internal property-lowering history.
std::unordered_map<VNRef<AstNodeExpr>, std::unordered_map<VNRef<AstSenTree>, AstAlways*>>
m_modExpr2Sen2DefaultDelayedAlwaysp;
// Map from delayed-value AstAlways to its last-sampling-tick time variable
std::unordered_map<const AstAlways*, AstVar*> m_delayedAlways2TickTimep;
// METHODS
static string assertCtlGetCall(const char* query, VAssertType type,
VAssertDirectiveType directiveType) {
return "vlSymsp->_vm_contextp__->assertCtlGet(VerilatedAssertCtlQuery::"s + query + ", "s
+ std::to_string(type) + ", "s + std::to_string(directiveType) + ")"s;
}
static const char* assertPassOnQuery(bool vacuous) {
static constexpr const char* queries[2]
= {"ASSERT_CTL_PASS_ON_NONVACUOUS", "ASSERT_CTL_PASS_ON_VACUOUS"};
return queries[vacuous];
}
static AstNodeExpr* assertOnCond(FileLine* fl, VAssertType type,
VAssertDirectiveType directiveType) {
// cppcheck-suppress missingReturn
switch (directiveType) {
case VAssertDirectiveType::INTRINSIC: return new AstConst{fl, AstConst::BitTrue{}};
case VAssertDirectiveType::VIOLATION_CASE: {
if (v3Global.opt.assertCase()) {
return new AstCExpr{fl, AstCExpr::Pure{}, "vlSymsp->_vm_contextp__->assertOn()",
1};
}
// If assertions are off, have constant propagation rip them out later
// This allows syntax errors and such to be detected normally.
return new AstConst{fl, AstConst::BitFalse{}};
}
case VAssertDirectiveType::ASSERT:
case VAssertDirectiveType::COVER:
case VAssertDirectiveType::ASSUME: {
if (v3Global.opt.assertOn()) {
return new AstCExpr{fl, AstCExpr::Pure{},
assertCtlGetCall("ASSERT_CTL_ON", type, directiveType), 1};
}
return new AstConst{fl, AstConst::BitFalse{}};
}
case VAssertDirectiveType::INTERNAL:
case VAssertDirectiveType::VIOLATION_IF:
case VAssertDirectiveType::RESTRICT: {
if (v3Global.opt.assertOn()) {
return new AstCExpr{fl, AstCExpr::Pure{}, "vlSymsp->_vm_contextp__->assertOn()",
1};
}
return new AstConst{fl, AstConst::BitFalse{}};
}
}
VL_UNREACHABLE;
}
static bool isControlled(VAssertDirectiveType directiveType) {
return (static_cast<int>(directiveType)
& (static_cast<int>(VAssertDirectiveType::ASSERT)
| static_cast<int>(VAssertDirectiveType::COVER)
| static_cast<int>(VAssertDirectiveType::ASSUME)));
}
static AstNodeExpr* assertPassOnCond(FileLine* fl, VAssertType type,
VAssertDirectiveType directiveType, bool vacuous) {
if (!isControlled(directiveType)) return new AstConst{fl, AstConst::BitTrue{}};
if (!v3Global.opt.assertOn()) return new AstConst{fl, AstConst::BitFalse{}};
return new AstCExpr{fl, AstCExpr::Pure{},
assertCtlGetCall(assertPassOnQuery(vacuous), type, directiveType), 1};
}
static AstNodeExpr* assertFailOnCond(FileLine* fl, VAssertType type,
VAssertDirectiveType directiveType) {
if (!isControlled(directiveType)) return new AstConst{fl, AstConst::BitTrue{}};
if (!v3Global.opt.assertOn()) return new AstConst{fl, AstConst::BitFalse{}};
return new AstCExpr{fl, AstCExpr::Pure{},
assertCtlGetCall("ASSERT_CTL_FAIL_ON", type, directiveType), 1};
}
string assertDisplayMessage(const AstNode* nodep, const string& prefix, const string& message,
VDisplayType severity) {
if (severity == VDisplayType::DT_ERROR || severity == VDisplayType::DT_FATAL) {
return ("[%0t] "s + prefix + ": " + nodep->fileline()->filebasename() + ":"
+ cvtToStr(nodep->fileline()->lineno()) + ": Assertion failed in %m"
+ ((message != "") ? ": " : "") + message + "\n");
}
return ("[%0t] "s + prefix + ": " + nodep->fileline()->filebasename() + ":"
+ cvtToStr(nodep->fileline()->lineno()) + ": %m" + ((message != "") ? ": " : "")
+ message + "\n");
}
void replaceDisplay(AstDisplay* nodep, const string& prefix) {
nodep->fmtp()->text(
assertDisplayMessage(nodep, prefix, nodep->fmtp()->text(), nodep->displayType()));
nodep->displayType(VDisplayType::DT_WRITE);
// cppcheck-suppress nullPointer
AstNodeExpr* const timenewp = new AstTime{nodep->fileline(), m_modp->timeunit()};
if (AstNodeExpr* const timesp = nodep->fmtp()->exprsp()) {
timesp->unlinkFrBackWithNext();
timenewp->addNext(timesp);
}
nodep->fmtp()->addExprsp(timenewp);
if (!nodep->fmtp()->scopeNamep() && nodep->fmtp()->formatScopeTracking()) {
nodep->fmtp()->scopeNamep(new AstScopeName{nodep->fileline(), true});
}
}
AstSampled* newSampledExpr(AstNodeExpr* nodep) {
return new AstSampled{nodep->fileline(), nodep, nodep->dtypep(), true};
}
AstVarRef* newMonitorNumVarRefp(const AstNode* nodep, VAccess access) {
if (!m_monitorNumVarp) {
m_monitorNumVarp = new AstVar{nodep->fileline(), VVarType::MODULETEMP, "__VmonitorNum",
nodep->findUInt64DType()};
v3Global.rootp()->dollarUnitPkgp()->addStmtsp(m_monitorNumVarp);
}
AstVarRef* const varrefp = new AstVarRef{nodep->fileline(), m_monitorNumVarp, access};
varrefp->classOrPackagep(v3Global.rootp()->dollarUnitPkgp());
return varrefp;
}
AstVarRef* newMonitorOffVarRefp(const AstNode* nodep, VAccess access) {
if (!m_monitorOffVarp) {
m_monitorOffVarp = new AstVar{nodep->fileline(), VVarType::MODULETEMP, "__VmonitorOff",
nodep->findBitDType()};
v3Global.rootp()->dollarUnitPkgp()->addStmtsp(m_monitorOffVarp);
}
AstVarRef* const varrefp = new AstVarRef{nodep->fileline(), m_monitorOffVarp, access};
varrefp->classOrPackagep(v3Global.rootp()->dollarUnitPkgp());
return varrefp;
}
static AstIf* newIfAssertOn(AstNode* bodyp, VAssertDirectiveType directiveType,
VAssertType type = VAssertType::INTERNAL) {
// Add an internal if to check assertions are on.
// Don't make this an AND term, as it's unlikely to need to test this.
FileLine* const fl = bodyp->fileline();
AstNodeExpr* const condp = assertOnCond(fl, type, directiveType);
AstIf* const newp = new AstIf{fl, condp, bodyp};
newp->isBoundsCheck(true); // To avoid LATCH warning
newp->user2(true); // Mark as an assertOn() check
return newp;
}
static AstNodeStmt* newIfAssertPassOn(AstNode* bodyp, VAssertDirectiveType directiveType,
VAssertType type, bool vacuous) {
// Add an internal if to check assertion passOn is enabled.
// Don't make this an AND term, as it's unlikely to need to test this.
FileLine* const fl = bodyp->fileline();
AstNodeExpr* const condp = assertPassOnCond(fl, type, directiveType, vacuous);
AstNodeIf* const newp = new AstIf{fl, condp, bodyp};
newp->isBoundsCheck(true); // To avoid LATCH warning
newp->user1(true); // Don't assert/cover this if
newp->user2(true); // Mark as an assertOn() check
return newp;
}
static AstNodeStmt* newIfAssertFailOn(AstNode* bodyp, VAssertDirectiveType directiveType,
VAssertType type) {
// Add an internal if to check assertion failOn is enabled.
// Don't make this an AND term, as it's unlikely to need to test this.
FileLine* const fl = bodyp->fileline();
AstNodeExpr* const condp = assertFailOnCond(fl, type, directiveType);
AstNodeIf* const newp = new AstIf{fl, condp, bodyp};
newp->isBoundsCheck(true); // To avoid LATCH warning
newp->user1(true); // Don't assert/cover this if
newp->user2(true); // Mark as an assertOn() check
return newp;
}
static AstIf* assertCond(const AstNodeCoverOrAssert* nodep, AstNodeExpr* propp,
AstNode* passsp, AstNode* failsp) {
AstIf* const ifp = new AstIf{nodep->fileline(), propp, passsp, failsp};
// It's more LIKELY that we'll take the nullptr if clause
// than the sim-killing else clause:
ifp->branchPred(VBranchPred::BP_LIKELY);
ifp->isBoundsCheck(true); // To avoid LATCH warning
return ifp;
}
AstNodeStmt* assertBody(const AstNodeCoverOrAssert* nodep, AstNode* propp, AstNode* passsp,
AstNode* failsp) {
if (AstPExpr* const pexprp = VN_CAST(propp, PExpr)) {
AstFork* const forkp = new AstFork{nodep->fileline(), VJoinType::JOIN_NONE};
forkp->addForksp(pexprp->bodyp()->unlinkFrBack());
if (AstNodeStmt* const finalp = pexprp->finalp()) {
if (!m_finalp) {
m_finalp = new AstFinal{m_modp->fileline(), finalp->unlinkFrBack()};
m_modp->addStmtsp(m_finalp);
} else {
m_finalp->addStmtsp(finalp->unlinkFrBack());
}
}
VL_DO_DANGLING2(pushDeletep(pexprp), pexprp, propp);
return forkp;
}
return assertCond(nodep, VN_AS(propp, NodeExpr), passsp, failsp);
}
AstNodeStmt* newFireAssertUnchecked(const AstNodeStmt* nodep, const string& message,
AstNodeExpr* exprsp = nullptr) {
// Like newFireAssert() but omits the asserts-on check
AstDisplay* const dispp
= new AstDisplay{nodep->fileline(), VDisplayType::DT_ERROR, message, nullptr, nullptr};
dispp->fmtp()->timeunit(m_modp->timeunit());
AstNodeStmt* const bodysp = dispp;
replaceDisplay(dispp, "%%Error"); // Convert to standard DISPLAY format
if (exprsp) dispp->fmtp()->exprsp()->addNext(exprsp);
if (v3Global.opt.stopFail()) bodysp->addNext(new AstStop{nodep->fileline(), false});
return bodysp;
}
AstNodeStmt* newFireAssert(const AstNodeStmt* nodep, VAssertDirectiveType directiveType,
VAssertType assertType, const string& message,
AstNodeExpr* exprsp = nullptr) {
AstNodeStmt* bodysp = newFireAssertUnchecked(nodep, message, exprsp);
bodysp = newIfAssertOn(bodysp, directiveType, assertType);
return bodysp;
}
AstVar* createDelayedVar(const std::string& name, AstAlways* alwaysp, AstNodeExpr* exprp,
AstNodeExpr* initp) {
FileLine* const flp = exprp->fileline();
AstVar* const varp = new AstVar{flp, VVarType::MODULETEMP, name, exprp->dtypep()};
// TODO: this lifetime seems nonsene (can't have NBAs to automatics), but is as before
varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
m_modp->addStmtsp(varp);
// Before enough clock ticks, $past returns the expression's default sampled value.
if (initp) {
m_modp->addStmtsp(new AstInitialStatic{
flp, new AstAssign{flp, new AstVarRef{flp, varp, VAccess::WRITE}, initp}});
}
++m_statPastVars;
// Actually set the delayed value
AstNodeExpr* const lhsp = new AstVarRef{flp, varp, VAccess::WRITE};
AstAssignDly* const assignp = new AstAssignDly{flp, lhsp, exprp};
if (!alwaysp->stmtsp()) {
alwaysp->addStmtsp(assignp);
} else {
alwaysp->stmtsp()->addHereThisAsNext(assignp);
}
return varp;
}
AstAlways* getDelayedAlways(AstNodeExpr* exprp, AstNodeExpr* initp, AstSenTree* senTreep) {
auto& expr2Sen2Alwaysr
= initp ? m_modExpr2Sen2DefaultDelayedAlwaysp : m_modExpr2Sen2DelayedAlwaysp;
AstAlways*& alwayspr = expr2Sen2Alwaysr[*exprp][*senTreep];
if (!alwayspr) {
FileLine* const flp = exprp->fileline();
// Create the always block that computes the delayed values
alwayspr = new AstAlways{flp, VAlwaysKwd::ALWAYS, senTreep, nullptr};
m_modp->addStmtsp(alwayspr);
// Create the once-delayed variable
const std::string name = "_Vpast_" + cvtToStr(m_modPastNum++) + "_1";
AstVar* const varp = createDelayedVar(name, alwayspr, exprp, initp);
// Add it to delayed variable vector
m_delayed(alwayspr).emplace_back(varp);
} else {
// Reusing exiting, not needed
VL_DO_DANGLING(pushDeletep(exprp), exprp);
if (initp) VL_DO_DANGLING(pushDeletep(initp), initp);
VL_DO_DANGLING(pushDeletep(senTreep), senTreep);
}
return alwayspr;
}
AstNodeExpr* getPastValue(AstNodeExpr* exprp, AstNodeExpr* initp, AstSenTree* senTreep,
uint32_t ticks) {
UASSERT_OBJ(ticks > 0, exprp, "Delay must be > 0");
FileLine* const flp = exprp->fileline();
AstAlways* const alwaysp
= getDelayedAlways(exprp, initp ? initp->cloneTreePure(false) : nullptr, senTreep);
AstNodeExpr* const resultp = pastValueRef(alwaysp, flp, ticks, initp);
if (initp) VL_DO_DANGLING(pushDeletep(initp), initp);
return resultp;
}
AstNodeExpr* pastValueRef(AstAlways* alwaysp, FileLine* flp, uint32_t ticks,
AstNodeExpr* initp) {
std::vector<AstVar*>& delayedr = m_delayed(alwaysp);
// Ensure the required delay exists
while (delayedr.size() < ticks) {
AstVar* const firstp = delayedr.front();
FileLine* const varFlp = firstp->fileline();
// Create once more delayed value
std::string name = firstp->name();
name.resize(name.size() - 1);
name += std::to_string(delayedr.size() + 1);
AstNodeExpr* const prevp = new AstVarRef{varFlp, delayedr.back(), VAccess::READ};
AstVar* const varp = createDelayedVar(name, alwaysp, prevp,
initp ? initp->cloneTreePure(false) : nullptr);
// Add it to delayed variable vector
delayedr.emplace_back(varp);
}
// Return a reference to the appropriately delayed variable
return new AstVarRef{flp, delayedr.at(ticks - 1), VAccess::READ};
}
// Time of the pipeline's last sampling tick, for end-of-simulation readers
AstVar* getPastTickTimeVar(AstAlways* alwaysp) {
AstVar*& varpr = m_delayedAlways2TickTimep[alwaysp];
if (!varpr) {
FileLine* const flp = alwaysp->fileline();
varpr = new AstVar{flp, VVarType::MODULETEMP,
"_Vpast_" + cvtToStr(m_modPastNum++) + "_t",
m_modp->findUInt64DType()};
m_modp->addStmtsp(varpr);
AstNodeExpr* const timep = new AstCExpr{flp, "vlSymsp->_vm_contextp__->time()", 64};
alwaysp->addStmtsp(
new AstAssign{flp, new AstVarRef{flp, varpr, VAccess::WRITE}, timep});
}
return varpr;
}
void visitAssertionIterate(AstNodeCoverOrAssert* nodep, AstNode* failsp) {
if (m_beginp && nodep->name() == "") nodep->name(m_beginp->name());
{ AssertDeFutureVisitor{nodep->propp(), m_modp, m_modPastNum++}; }
iterateAndNextNull(nodep->sentreep());
if (AstAssert* const assertp = VN_CAST(nodep, Assert)) {
iterateAndNextNull(assertp->failsp());
} else if (AstAssertIntrinsic* const assertp = VN_CAST(nodep, AssertIntrinsic)) {
iterateAndNextNull(assertp->failsp());
} else if (AstCover* const coverp = VN_CAST(nodep, Cover)) {
iterateAndNextNull(coverp->coverincsp());
} else if (!VN_IS(nodep, Restrict)) {
nodep->v3fatalSrc("Unhandled assert type");
}
iterateAndNextNull(nodep->passsp());
AstSenTree* sentreep = nodep->sentreep();
if (nodep->immediate()) {
UASSERT_OBJ(!sentreep, nodep, "Immediate assertions don't have sensitivity");
} else {
UASSERT_OBJ(sentreep, nodep, "Concurrent assertions must have sensitivity");
// Explicit inline clock differs from the enclosing always: hoist
// and warn. To support this need queue of asserts to activate.
if (m_procedurep && !nodep->senFromAlways()) {
nodep->v3warn(E_UNSUPPORTED,
"Unsupported: Procedural concurrent assertion with"
" clocking event inside always (IEEE 1800-2023 16.14.6)");
static_cast<AstNode*>(m_procedurep)->addNext(nodep->unlinkFrBack());
return; // Later iterate will pick up
}
sentreep->unlinkFrBack();
if (m_procedurep) { VL_DO_DANGLING(pushDeletep(sentreep), sentreep); }
}
//
const string& message = nodep->name();
AstNode* passsp = nodep->passsp();
if (passsp) passsp->unlinkFrBackWithNext();
if (AstAssert* const assertp = VN_CAST(nodep, Assert)) {
failsp = assertp->failsp();
} else if (AstAssertIntrinsic* const assertp = VN_CAST(nodep, AssertIntrinsic)) {
failsp = assertp->failsp();
}
if (failsp) failsp->unlinkFrBackWithNext();
bool selfDestruct = false;
bool passspGated = false;
const AstCover* const coverp = VN_CAST(nodep, Cover);
// A sequence event control is not an assertion directive; no assertion control
const bool seqEvent = coverp && coverp->isSeqEvent();
if (coverp) {
++m_statCover;
if (seqEvent) {
// Keep the event-fire action, with no coverage bucket
} else if (!v3Global.opt.coverageUser()) {
selfDestruct = true;
} else {
// V3Coverage assigned us a bucket to increment.
AstCoverInc* const covincp = VN_AS(coverp->coverincsp(), CoverInc);
UASSERT_OBJ(covincp, coverp, "Missing AstCoverInc under assertion");
covincp->unlinkFrBackWithNext(); // next() might have AstAssign for trace
if (message != "") covincp->declp()->comment(message);
if (passsp) {
passsp = newIfAssertPassOn(passsp, nodep->directive(), nodep->userType(),
/*vacuous=*/false);
passspGated = true;
passsp = AstNode::addNext<AstNode, AstNode>(covincp, passsp);
} else {
passsp = covincp;
}
}
} else if (VN_IS(nodep, Assert) || VN_IS(nodep, AssertIntrinsic)) {
if (nodep->immediate()) {
++m_statAsImm;
} else {
++m_statAsNotImm;
}
if (!passsp && !failsp)
failsp = newFireAssertUnchecked(
nodep, VN_IS(nodep, AssertIntrinsic) ? "'$cast' failed." : "'assert' failed.");
} else {
nodep->v3fatalSrc("Unknown node type");
}
VL_RESTORER(m_passsp);
VL_RESTORER(m_failsp);
VL_RESTORER(m_assertp);
m_passsp = passsp;
m_failsp = failsp;
m_assertp = nodep;
iterate(nodep->propp());
AstNode* propExprp;
AstNodeExpr* disablep = nullptr;
AstNodeExpr* matchCountp = nullptr;
if (AstPropSpec* const specp = VN_CAST(nodep->propp(), PropSpec)) {
propExprp = specp->propp()->unlinkFrBack();
if (specp->disablep()) disablep = specp->disablep()->unlinkFrBack();
matchCountp = specp->matchCountp();
} else {
propExprp = nodep->propp()->unlinkFrBack();
}
FileLine* const flp = nodep->fileline();
bool passspAlreadyGated = false;
if (passsp && VN_IS(passsp, If)) passspAlreadyGated = VN_AS(passsp, If)->user1();
if (passsp && !passspGated && !passspAlreadyGated && !VN_IS(propExprp, PExpr)
&& !seqEvent) {
passsp = newIfAssertPassOn(passsp, nodep->directive(), nodep->userType(),
/*vacuous=*/false);
}
if (failsp && !VN_IS(propExprp, PExpr)) {
failsp = newIfAssertFailOn(failsp, nodep->directive(), nodep->userType());
}
if (coverp && matchCountp && passsp) {
// Convert the match count into a loop that decrements a temporary variable until it
// reaches zero.
matchCountp->unlinkFrBack();
AstVar* const remainingp = new AstVar{
flp, VVarType::MODULETEMP, m_matchCountNames.get(""), matchCountp->dtypep()};
remainingp->lifetime(VLifetime::STATIC_EXPLICIT);
m_modp->addStmtsp(remainingp);
AstNode* const replaysp
= new AstAssign{flp, new AstVarRef{flp, remainingp, VAccess::WRITE}, matchCountp};
AstLoop* const loopp = new AstLoop{flp};
loopp->addStmtsp(
new AstLoopTest{flp, loopp, new AstVarRef{flp, remainingp, VAccess::READ}});
loopp->addStmtsp(passsp);
loopp->addStmtsp(
new AstAssign{flp, new AstVarRef{flp, remainingp, VAccess::WRITE},
new AstSub{flp, new AstVarRef{flp, remainingp, VAccess::READ},
new AstConst{flp, AstConst::WidthedValue{},
remainingp->dtypep()->width(), 1}}});
replaysp->addNext(loopp);
passsp = replaysp;
}
AstNode* bodysp = assertBody(nodep, propExprp, passsp, failsp);
if (disablep) bodysp = new AstIf{flp, new AstLogNot{flp, disablep}, bodysp};
// Add assertOn check last, for better combining
if (!seqEvent) bodysp = newIfAssertOn(bodysp, nodep->directive(), nodep->userType());
if (sentreep) bodysp = new AstAlways{flp, VAlwaysKwd::ALWAYS, sentreep, bodysp};
if (passsp && !passsp->backp()) VL_DO_DANGLING(pushDeletep(passsp), passsp);
if (failsp && !failsp->backp()) VL_DO_DANGLING(pushDeletep(failsp), failsp);
// Install it
if (selfDestruct) {
// Delete it after making the tree. This way we can tell the user
// if it wasn't constructed nicely or has other errors without needing --coverage.
VL_DO_DANGLING(bodysp->deleteTree(), bodysp);
nodep->unlinkFrBack();
} else {
nodep->replaceWith(bodysp);
}
// Bye
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
bool isEmptyStmt(AstNode* nodep) {
if (!nodep) return true;
if (AstBegin* const beginp = VN_CAST(nodep, Begin)) {
if (beginp->declsp()) return false;
for (AstNode* stmtp = beginp->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
if (!isEmptyStmt(stmtp)) return false;
}
return true;
}
return false;
}
// VISITORS
void visit(AstIf* nodep) override {
if (!nodep->user1SetOnce()) {
if (nodep->uniquePragma() || nodep->unique0Pragma()) {
const AstNodeIf* ifp = nodep;
AstNodeExpr* propp = nullptr;
bool hasDefaultElse = false;
do {
// If this statement ends with 'else if', then nextIf will point to the
// nextIf statement. Otherwise it will be null.
const AstNodeIf* const nextifp = dynamic_cast<AstNodeIf*>(ifp->elsesp());
iterateAndNextNull(ifp->condp());
// Recurse into the true case.
iterateAndNextNull(ifp->thensp());
// If the last else is not an else if, recurse into that too.
if (ifp->elsesp() && !nextifp) { //
iterateAndNextNull(ifp->elsesp());
}
// Build a bitmask of the true predicates
AstNodeExpr* const predp = ifp->condp()->cloneTreePure(false);
if (propp) {
propp = new AstConcat{nodep->fileline(), predp, propp};
} else {
propp = predp;
}
// Record if this ends with an 'else' that does not have an if
if (ifp->elsesp() && !nextifp) hasDefaultElse = true;
ifp = nextifp;
} while (ifp);
AstIf* const newifp = nodep->cloneTree(false);
const bool allow_none = nodep->unique0Pragma();
// Empty case means no property
if (!propp) propp = new AstConst{nodep->fileline(), AstConst::BitFalse{}};
// Note: if this ends with an 'else', then we don't need to validate that one of
// the predicates evaluates to true.
AstNodeExpr* const ohot
= ((allow_none || hasDefaultElse)
? static_cast<AstNodeExpr*>(new AstOneHot0{nodep->fileline(), propp})
: static_cast<AstNodeExpr*>(new AstOneHot{nodep->fileline(), propp}));
const VAssertType assertType
= nodep->uniquePragma() ? VAssertType::UNIQUE : VAssertType::UNIQUE0;
AstIf* const checkifp
= new AstIf{nodep->fileline(), new AstLogNot{nodep->fileline(), ohot},
newFireAssert(nodep, VAssertDirectiveType::VIOLATION_IF,
assertType, "'unique if' statement violated"),
newifp};
checkifp->isBoundsCheck(true); // To avoid LATCH warning
checkifp->branchPred(VBranchPred::BP_UNLIKELY);
nodep->replaceWith(checkifp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
return;
}
}
iterateChildren(nodep);
if (nodep->user2()) {
// Combine consecutive assertOn checks if possible
if (AstIf* const backp = VN_CAST(nodep->backp(), If)) {
if (backp->nextp() == nodep //
&& backp->user2() //
&& backp->condp()->sameTree(nodep->condp())) {
++m_statAssertOnCombined;
backp->addThensp(nodep->thensp()->unlinkFrBackWithNext());
nodep->unlinkFrBack();
VL_DO_DANGLING(pushDeletep(nodep), nodep);
return;
}
}
// Combine nested assertOn checks if possible
if (nodep->thensp() && !nodep->thensp()->nextp() && isEmptyStmt(nodep->elsesp())) {
AstIf* const checkp = VN_CAST(nodep->thensp(), If);
if (checkp //
&& checkp->user2() //
&& checkp->condp()->sameTree(nodep->condp())) {
++m_statAssertOnCombined;
nodep->addThensp(checkp->thensp()->unlinkFrBackWithNext());
VL_DO_DANGLING(pushDeletep(checkp->unlinkFrBack()), checkp);
return;
}
}
return;
}
// Swap assertOn check with single statement 'if' statement to bubble up for combining
// Note we can't just swap the conditions as they two Ifs have different flags,
// so swapping the Ifs themeselves then swapping back the bodies.
if (nodep->condp()->isPure()) {
if (nodep->thensp() && !nodep->thensp()->nextp() && isEmptyStmt(nodep->elsesp())) {
AstIf* const checkp = VN_CAST(nodep->thensp(), If);
if (checkp && checkp->user2()) {
++m_statAssertOnHoisted;
nodep->replaceWith(checkp->unlinkFrBack());
nodep->addThensp(checkp->thensp()->unlinkFrBackWithNext());
checkp->addThensp(nodep);
return;
}
}
if (nodep->elsesp() && !nodep->elsesp()->nextp() && isEmptyStmt(nodep->thensp())) {
AstIf* const checkp = VN_CAST(nodep->elsesp(), If);
if (checkp && checkp->user2()) {
++m_statAssertOnHoisted;
nodep->replaceWith(checkp->unlinkFrBack());
nodep->addElsesp(checkp->thensp()->unlinkFrBackWithNext());
checkp->addThensp(nodep);
return;
}
}
}
}
//========== Case assertions
void visit(AstCase* nodep) override {
// Introduce temporary variable for AstCase if needed - it is done here and not in V3Case
// because this phase is before V3Scope and V3Case is not. Doing it before V3Scope ensures
// that V3Scope will take care of a scope creation
// We also need to do it before V3Begin, co that pragmas like `unique0` also work correctly
if (!nodep->exprp()->isPure()) {
++m_statLiftedCaseExprs;
FileLine* const fl = nodep->exprp()->fileline();
AstVar* const varp = new AstVar{fl, VVarType::BLOCKTEMP, m_caseTempNames.get(nodep),
nodep->exprp()->dtypep()};
AstNodeExpr* const origp = nodep->exprp()->unlinkFrBack();
nodep->addHereThisAsNext(
new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE}, origp});
nodep->exprp(new AstVarRef{fl, varp, VAccess::READ});
if (m_ftaskp) {
varp->funcLocal(true);
varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
m_ftaskp->stmtsp()->addHereThisAsNext(varp);
} else {
m_modp->stmtsp()->addHereThisAsNext(varp);
}
VIsCached::clearCacheTree();
}
iterateChildren(nodep);
if (!nodep->user1SetOnce()) {
bool has_default = false;
for (AstCaseItem* itemp = nodep->itemsp(); itemp;
itemp = VN_AS(itemp->nextp(), CaseItem)) {
if (itemp->isDefault()) has_default = true;
}
const AstNodeDType* exprDtypep = nodep->exprp()->dtypep()->skipRefp();
VAssertType assertType = VAssertType::INTERNAL;
if (nodep->priorityPragma()) {
assertType = VAssertType::PRIORITY;
} else if (nodep->uniquePragma()) {
assertType = VAssertType::UNIQUE;
} else if (nodep->unique0Pragma()) {
assertType = VAssertType::UNIQUE0;
}
string valFmt;
if (exprDtypep->isIntegralOrPacked())
valFmt = " for '" + cvtToStr(exprDtypep->widthMin()) + "'h%X'";
if (nodep->fullPragma() || nodep->priorityPragma()) {
// Need to add a default if there isn't one already
++m_statAsFull;
if (!has_default) {
nodep->addItemsp(new AstCaseItem{
nodep->fileline(), nullptr /*DEFAULT*/,
newFireAssert(nodep, VAssertDirectiveType::VIOLATION_CASE, assertType,
nodep->pragmaString() + ", but non-match found" + valFmt,
valFmt.empty() ? nullptr
: nodep->exprp()->cloneTreePure(false))});
}
}
if (nodep->parallelPragma() || nodep->uniquePragma() || nodep->unique0Pragma()) {
// Need to check that one, and only one of the case items match at any moment
// If there's a default, we allow none to match, else exactly one must match
++m_statAsFull;
if (!has_default && !nodep->itemsp()) {
// Not parallel, but harmlessly so.
} else {
AstNodeExpr* propp = nullptr;
for (AstCaseItem* itemp = nodep->itemsp(); itemp;
itemp = VN_AS(itemp->nextp(), CaseItem)) {
AstNodeExpr* itembitp = nullptr;
for (AstNodeExpr* icondp = itemp->condsp(); icondp;
icondp = VN_AS(icondp->nextp(), NodeExpr)) {
AstNodeExpr* onep;
if (AstInsideRange* const rcondp = VN_CAST(icondp, InsideRange)) {
onep = rcondp->newAndFromInside(
nodep->exprp()->cloneTreePure(true),
rcondp->lhsp()->cloneTreePure(true),
rcondp->rhsp()->cloneTreePure(true));
} else if (nodep->casex() || nodep->casez() || nodep->caseInside()) {
onep = AstEqWild::newTyped(itemp->fileline(),
nodep->exprp()->cloneTreePure(false),
icondp->cloneTreePure(false));
} else {
onep = AstEq::newTyped(icondp->fileline(),
nodep->exprp()->cloneTreePure(false),
icondp->cloneTreePure(false));
}
// OR together all conditions within the same case item
if (onep) {
if (itembitp) {
itembitp = new AstOr{icondp->fileline(), onep, itembitp};
} else {
itembitp = onep;
}
}
}
if (itembitp) {
if (propp) {
propp = new AstConcat{itemp->fileline(), itembitp, propp};
} else {
propp = itembitp;
}
}
}
// Empty case means no property
if (!propp) propp = new AstConst{nodep->fileline(), AstConst::BitFalse{}};
const bool allow_none = has_default || nodep->unique0Pragma();
// The following assertion looks as below.
// if (!$onehot(propp)) begin
// if (propp == '0) begin if (!allow_none) $error("none match"); end
// else $error("multiple match");
// end
AstNodeExpr* const ohot = new AstOneHot{nodep->fileline(), propp};
AstConst* const zero = new AstConst{
nodep->fileline(), AstConst::WidthedValue{}, propp->width(), 0};
AstIf* const ohotIfp
= new AstIf{nodep->fileline(), new AstLogNot{nodep->fileline(), ohot}};
AstIf* const zeroIfp = new AstIf{
nodep->fileline(),
new AstEq{nodep->fileline(), propp->cloneTreePure(false), zero}};
AstNodeExpr* const exprp = nodep->exprp();
const string pragmaStr = nodep->pragmaString();
if (!allow_none)
zeroIfp->addThensp(
newFireAssert(nodep, VAssertDirectiveType::VIOLATION_CASE, assertType,
pragmaStr + ", but none matched" + valFmt,
valFmt.empty() ? nullptr : exprp->cloneTreePure(false)));
zeroIfp->addElsesp(
newFireAssert(nodep, VAssertDirectiveType::VIOLATION_CASE, assertType,
pragmaStr + ", but multiple matches found" + valFmt,
valFmt.empty() ? nullptr : exprp->cloneTreePure(false)));
ohotIfp->addThensp(zeroIfp);
ohotIfp->isBoundsCheck(true); // To avoid LATCH warning
ohotIfp->branchPred(VBranchPred::BP_UNLIKELY);
nodep->addNotParallelp(ohotIfp);
}
}
}
}
void visit(AstFuture* nodep) override {
nodep->v3error("Future sampled value function called outside property or sequence "
"expression (IEEE 16.9.4)");
nodep->replaceWith(new AstConst{nodep->fileline(), 0});
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
//========== Past
void visit(AstPast* nodep) override {
if (!nodep->propertyTiming()) {
nodep->initp(nodep->exprp()->cloneTreePure(false));
{ DefaultSampledValueVisitor{nodep->initp()}; }
}
AstNodeExpr* const initp = nodep->initp() ? nodep->initp()->unlinkFrBack() : nullptr;
iterateChildren(nodep);
uint32_t ticks = 1;
if (nodep->ticksp()) {
UASSERT_OBJ(VN_IS(nodep->ticksp(), Const), nodep,
"Expected constant ticks, checked in V3Width");
ticks = VN_AS(nodep->ticksp(), Const)->toUInt();
}
UASSERT_OBJ(ticks >= 1, nodep, "0 tick should have been checked in V3Width");
AstNodeExpr* const exprp = newSampledExpr(nodep->exprp()->unlinkFrBack());
AstSenTree* const senTreep = nodep->sentreep()->unlinkFrBack();
AstNodeExpr* inp = nullptr;
// Deliberately emitted for every ftask $past; calls outside final take the normal stage
if (VN_IS(m_procedurep, Final) || m_ftaskp) {
// A sampling-tick finish reads one stage deeper (IEEE 1800-2023 16.9.3).
FileLine* const flp = nodep->fileline();
AstAlways* const alwaysp
= getDelayedAlways(exprp, initp ? initp->cloneTreePure(false) : nullptr, senTreep);
AstNodeExpr* const betweenTicksp = pastValueRef(alwaysp, flp, ticks, initp);
AstNodeExpr* const onTickp = pastValueRef(alwaysp, flp, ticks + 1, initp);
if (initp) VL_DO_DANGLING(pushDeletep(initp), initp);
AstNodeExpr* const onTickCondp = new AstLogAnd{
flp, new AstCExpr{flp, "vlSymsp->_vm_contextp__->executingFinal()", 1},
new AstEq{flp, new AstVarRef{flp, getPastTickTimeVar(alwaysp), VAccess::READ},
new AstCExpr{flp, "vlSymsp->_vm_contextp__->finishPendingTime()", 64}}};
AstCond* const condp = new AstCond{flp, onTickCondp, onTickp, betweenTicksp};
condp->dtypeFrom(onTickp);
inp = condp;
} else {
inp = getPastValue(exprp, initp, senTreep, ticks);
}
nodep->replaceWith(inp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
//========== Move $sampled down to read-only variables
void visit(AstSampled* nodep) override {
if (nodep->user1()) return;
VL_RESTORER(m_inSampled);
{
m_inSampled = true;
iterateChildren(nodep);
}
if (nodep->exprp()) {
nodep->replaceWith(nodep->exprp()->unlinkFrBack());
} else {
nodep->unlinkFrBack();
}
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstNodeVarRef* nodep) override {
if (m_inSampled && !nodep->user2() && !nodep->varp()->isTemp()) {
if (!nodep->access().isReadOnly()) {
nodep->v3warn(E_UNSUPPORTED,
"Unsupported: Write to variable in sampled expression");
} else {
VNRelinker relinkHandle;
nodep->unlinkFrBack(&relinkHandle);
AstSampled* const newp = newSampledExpr(nodep);
relinkHandle.relink(newp);
newp->user1(1);
v3Global.setHasSampled();
}
}
}
// Don't sample sensitivities
void visit(AstSenItem* nodep) override {
VL_RESTORER(m_inSampled);
m_inSampled = false;
iterateChildren(nodep);
}
void visit(AstPExprClause* nodep) override {
AstNode* stmtsp = nullptr;
if (nodep->pass() && m_passsp) {
// Cover adds COVERINC by AstNode::addNext, thus need to clone next too.
stmtsp = m_passsp->cloneTree(true);
stmtsp = newIfAssertPassOn(stmtsp, m_assertp->directive(), m_assertp->userType(),
nodep->vacuous());
} else if (!nodep->pass() && m_failsp) {
stmtsp = m_failsp->cloneTree(true);
stmtsp = newIfAssertFailOn(stmtsp, m_assertp->directive(), m_assertp->userType());
}
if (stmtsp) {
stmtsp->foreachAndNext([](AstNodeVarRef* const refp) {
// References inside action blocks shouldn't be implicitly sampled
// m_passsp/m_failsp have been already visited once and refs explicitly sampled
// are handled already
refp->user2(1);
});
nodep->replaceWith(stmtsp);
} else {
nodep->unlinkFrBack();
}
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstPExpr* nodep) override {
if (m_inRestrict) {
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
} else {
iterateChildren(nodep);
}
}
//========== Statements
void visit(AstDisplay* nodep) override {
iterateChildren(nodep);
// Replace the special types with standard text
if (nodep->displayType() == VDisplayType::DT_INFO) {
replaceDisplay(nodep, "-Info");
} else if (nodep->displayType() == VDisplayType::DT_WARNING) {
replaceDisplay(nodep, "%%Warning");
} else if (nodep->displayType() == VDisplayType::DT_ERROR) {
replaceDisplay(nodep, "%%Error");
} else if (nodep->displayType() == VDisplayType::DT_FATAL) {
replaceDisplay(nodep, "%%Fatal");
} else if (nodep->displayType() == VDisplayType::DT_MONITOR) {
nodep->displayType(VDisplayType::DT_DISPLAY);
FileLine* const fl = nodep->fileline();
AstSenItem* monSenItemsp = nullptr;
if (AstNode* const monExprsp = nodep->fmtp()->exprsp()) {
monExprsp->foreachAndNext([&](AstVarRef* varrefp) {
AstSenItem* const senItemp
= new AstSenItem{fl, VEdgeType::ET_CHANGED,
// Clone so get VarRef or VarXRef as needed
varrefp->cloneTree(false)};
monSenItemsp = AstNode::addNextNull(monSenItemsp, senItemp);
});
}
AstSenTree* const monSenTree = new AstSenTree{fl, monSenItemsp};
const auto monNum = ++m_monitorNum;
// Where $monitor was we do "__VmonitorNum = N;"
AstAssign* const newsetp = new AstAssign{
fl, newMonitorNumVarRefp(nodep, VAccess::WRITE), new AstConst{fl, monNum}};
nodep->replaceWith(newsetp);
// Add "always_comb if (__VmonitorOn && __VmonitorNum==N) $display(...);"
AstNode* const stmtsp = nodep;
AstIf* const ifp = new AstIf{
fl,
new AstLogAnd{fl, new AstLogNot{fl, newMonitorOffVarRefp(nodep, VAccess::READ)},
new AstEq{fl, new AstConst{fl, monNum},
newMonitorNumVarRefp(nodep, VAccess::READ)}},
stmtsp};
ifp->isBoundsCheck(true); // To avoid LATCH warning
ifp->branchPred(VBranchPred::BP_UNLIKELY);
AstNode* const newp = new AstAlways{fl, VAlwaysKwd::ALWAYS, monSenTree, ifp};
m_modp->addStmtsp(newp);
} else if (nodep->displayType() == VDisplayType::DT_STROBE) {
nodep->displayType(VDisplayType::DT_DISPLAY);
// Need one-shot
FileLine* const fl = nodep->fileline();
AstVar* const varp
= new AstVar{fl, VVarType::MODULETEMP, "__Vstrobe" + cvtToStr(m_modStrobeNum++),
nodep->findBitDType()};
m_modp->addStmtsp(varp);
// Where $strobe was we do "__Vstrobe = '1;"
AstAssign* const newsetp = new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE},
new AstConst{fl, AstConst::BitTrue{}}};
nodep->replaceWith(newsetp);
// Add "always_comb if (__Vstrobe) begin $display(...); __Vstrobe = '0; end"
AstNode* const stmtsp = nodep;
AstIf* const ifp = new AstIf{fl, new AstVarRef{fl, varp, VAccess::READ}, stmtsp};
ifp->isBoundsCheck(true); // To avoid LATCH warning
ifp->branchPred(VBranchPred::BP_UNLIKELY);
AstNode* const newp = new AstAlwaysPostponed{fl, ifp};
stmtsp->addNext(new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE},
new AstConst{fl, AstConst::BitFalse{}}});
m_modp->addStmtsp(newp);
}
}
void visit(AstMonitorOff* nodep) override {
AstAssign* const newp
= new AstAssign{nodep->fileline(), newMonitorOffVarRefp(nodep, VAccess::WRITE),
new AstConst{nodep->fileline(), AstConst::BitTrue{}, nodep->off()}};
nodep->replaceWith(newp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstAssert* nodep) override { //
visitAssertionIterate(nodep, nodep->failsp());
}
void visit(AstAssertCtl* nodep) override {
iterateChildren(nodep);
bool assertTypeConst = true;
if (!nodep->assertTypesp()) {
nodep->assertTypes(VAssertType{ALL_ASSERT_TYPES});
} else if (const AstConst* const assertTypesp = VN_CAST(nodep->assertTypesp(), Const)) {
nodep->assertTypes(VAssertType{assertTypesp->toSInt()});
} else {
assertTypeConst = false;
}
bool controlTypeConst = false;
if (const AstConst* const constp = VN_CAST(nodep->controlTypep(), Const)) {
nodep->ctlType(constp->toSInt());
controlTypeConst = true;
}
if (controlTypeConst
&& (nodep->ctlType() < VAssertCtlType::LOCK
|| nodep->ctlType() > VAssertCtlType::VACUOUS_OFF)) {
nodep->unlinkFrBack();
nodep->v3error("Bad $assertcontrol control_type '"
<< cvtToStr(static_cast<int>(nodep->ctlType()))
<< "' (IEEE 1800-2023 Table 20-5)");
VL_DO_DANGLING(pushDeletep(nodep), nodep);
return;
}
if (assertTypeConst && nodep->assertTypes() != ALL_ASSERT_TYPES
&& nodep->assertTypes().containsAny(VAssertType::UNIQUE | VAssertType::UNIQUE0
| VAssertType::PRIORITY)) {
nodep->v3warn(E_UNSUPPORTED, "Unsupported: assert control assertion_type");
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
return;
}
bool directiveTypeConst = true;
if (!nodep->directiveTypesp()) {
nodep->directiveTypes(VAssertDirectiveType::ASSERT | VAssertDirectiveType::ASSUME
| VAssertDirectiveType::COVER);
} else if (const AstConst* const directiveTypesp
= VN_CAST(nodep->directiveTypesp(), Const)) {
nodep->directiveTypes(VAssertDirectiveType{directiveTypesp->toSInt()});
} else {
directiveTypeConst = false;
}
if (!directiveTypeConst) {
nodep->v3warn(E_UNSUPPORTED,
"Unsupported: non-const assert directive type expression");
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
return;
}
FileLine* const fl = nodep->fileline();
UINFO(9, "Generating assertctl for a module: " << m_modp);
AstCStmt* const newp = new AstCStmt{fl};
newp->add("vlSymsp->_vm_contextp__->assertCtl(");
newp->add(nodep->controlTypep()->unlinkFrBack());
newp->add(", ");
if (nodep->assertTypesp()) {
newp->add(nodep->assertTypesp()->unlinkFrBack());
} else {
newp->add(std::to_string(ALL_ASSERT_TYPES));
}
newp->add(", " + std::to_string(nodep->directiveTypes()) + ");\n");
nodep->replaceWith(newp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstAssertIntrinsic* nodep) override { //
visitAssertionIterate(nodep, nodep->failsp());
}
void visit(AstCover* nodep) override { //
visitAssertionIterate(nodep, nullptr);
}
void visit(AstRestrict* nodep) override {
VL_RESTORER(m_inRestrict);
m_inRestrict = true;
iterateChildren(nodep);
// IEEE says simulator ignores these
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
}
void visit(AstNodeModule* nodep) override {
VL_RESTORER(m_modp);
VL_RESTORER(m_modPastNum);
VL_RESTORER(m_modStrobeNum);
VL_RESTORER(m_finalp);
VL_RESTORER_CLEAR(m_modExpr2Sen2DelayedAlwaysp);
VL_RESTORER_CLEAR(m_modExpr2Sen2DefaultDelayedAlwaysp);
VL_RESTORER_CLEAR(m_delayedAlways2TickTimep);
m_modp = nodep;
m_modPastNum = 0;
m_modStrobeNum = 0;
m_finalp = nullptr;
iterateChildren(nodep);
}
void visit(AstNodeProcedure* nodep) override {
VL_RESTORER(m_procedurep);
m_procedurep = nodep;
iterateChildren(nodep);
}
void visit(AstNodeFTask* nodep) override {
VL_RESTORER(m_ftaskp);
m_ftaskp = nodep;
iterateChildren(nodep);
}
void visit(AstGenBlock* nodep) override {
// This code is needed rather than a visitor in V3Begin,
// because V3Assert is called before V3Begin
VL_RESTORER(m_beginp);
m_beginp = nodep;
iterateChildren(nodep);
}
void visit(AstBegin* nodep) override {
// This code is needed rather than a visitor in V3Begin,
// because V3Assert is called before V3Begin
VL_RESTORER(m_beginp);
m_beginp = nodep;
iterateChildren(nodep);
// If the body is a single assertOn check, bubble it up for combining
if (!nodep->declsp()) {
if (AstIf* const ifp = VN_CAST(nodep->stmtsp(), If)) {
if (ifp->user2() && !ifp->nextp()) {
++m_statAssertOnHoisted;
nodep->replaceWith(ifp->unlinkFrBack());
nodep->addStmtsp(ifp->thensp()->unlinkFrBackWithNext());
ifp->addThensp(nodep);
}
}
}
}
void visit(AstNode* nodep) override { iterateChildren(nodep); }
public:
// CONSTRUCTORS
explicit AssertVisitor(AstNetlist* nodep) { iterate(nodep); }
~AssertVisitor() override {
V3Stats::addStat("Assertions, assert non-immediate statements", m_statAsNotImm);
V3Stats::addStat("Assertions, assert immediate statements", m_statAsImm);
V3Stats::addStat("Assertions, cover statements", m_statCover);
V3Stats::addStat("Assertions, full/parallel case", m_statAsFull);
V3Stats::addStat("Assertions, $past variables", m_statPastVars);
V3Stats::addStat("Assertions, assertOn checks combined", m_statAssertOnCombined);
V3Stats::addStat("Assertions, assertOn checks hoisted", m_statAssertOnHoisted);
V3Stats::addStat("Assertions, lifted impure case expressions", m_statLiftedCaseExprs);
// Rewrites can change purity, e.g. by compiling out assertion statements with --no-assert
VIsCached::clearCacheTree();
}
};
//######################################################################
// Top Assert class
void V3Assert::assertAll(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ":");
{ AssertVisitor{nodep}; } // Destruct before checking
V3Global::dumpCheckGlobalTree("assert", 0, dumpTreeEitherLevel() >= 3);
}