verilator/src/V3Assert.cpp

893 lines
41 KiB
C++
Raw Normal View History

// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Collect and print statistics
//
2019-11-08 04:33:59 +01:00
// Code available from: https://verilator.org
//
//*************************************************************************
//
2025-01-01 14:30:25 +01:00
// Copyright 2005-2025 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
#include "V3Assert.h"
#include "V3Stats.h"
VL_DEFINE_DEBUG_FUNCTIONS;
//######################################################################
// AssertDeFuture
// If any AstFuture, then move all non-future varrefs to be one cycle behind,
// see IEEE 1800-2023 16.9.4.
class AssertDeFuture 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,
2025-10-14 02:30:47 +02:00
"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 AssertDeFuture(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");
}
~AssertDeFuture() = default;
};
//######################################################################
// 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
const VNUser1InUse m_inuser1;
// STATE
AstNodeModule* m_modp = nullptr; // Last module
const AstNode* m_beginp = nullptr; // Last AstBegin/AstGenBlock
2020-11-29 17:31:38 +01:00
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
2020-11-29 17:31:38 +01:00
unsigned m_modStrobeNum = 0; // Module $strobe numbering
const 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
2022-08-29 14:39:41 +02:00
bool m_inSampled = false; // True inside a sampled expression
bool m_inRestrict = false; // True inside restrict assertion
// METHODS
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()) {
Internals: Refactor text based Ast constructs (#6280) (#6571) Remove the large variety of ways raw "text" is represented in the Ast. Particularly, the only thing that represents a string to be emitted in the output is AstText. There are 5 AstNodes that can contain AstText, and V3Emit will throw an error if an AstText is encountered anywhere else: - AstCStmt: Internally generated procedural statements involving raw text. - AstCStmtUser: This is the old AstUCStmt, renamed so it sorts next to AstCStmt, as it's largely equivalent. We should never create this internally unless used to represent user input. It is used for $c, statements in the input, and for some 'systemc_* blocks. - AstCExpr: Internally generaged expression involving raw text. - AstCExprUser: This is the old AstUCFunc, renamed so it sorts next to AstCExpr. It is largely equivalent, but also has more optimizations disabled. This should never be created internally, it is only used for $c expressions in the input. - AstTextBlock: Use by V3ProtectLib only, to generate the hierarchical wrappers. Text "tracking" for indentation is always on for AstCStmt, AstCExpr, and AstTextBlock, as these are always generated by us, and should always be well formed. Tracking is always off for AstCStmtUser and AstCExprUser, as these contain arbitrary user input that might not be safe to parse for indentation. Remove subsequently redundant AstNodeSimpleText and AstNodeText types. This patch also fixes incorrect indentation in emitted waveform tracing functions, and makes the output more readable for hier block SV stubs. With that, all raw text nodes are handled as a proper AstNodeStmt or AstNodeExpr as required for #6280.
2025-10-21 13:41:29 +02:00
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()) {
Internals: Refactor text based Ast constructs (#6280) (#6571) Remove the large variety of ways raw "text" is represented in the Ast. Particularly, the only thing that represents a string to be emitted in the output is AstText. There are 5 AstNodes that can contain AstText, and V3Emit will throw an error if an AstText is encountered anywhere else: - AstCStmt: Internally generated procedural statements involving raw text. - AstCStmtUser: This is the old AstUCStmt, renamed so it sorts next to AstCStmt, as it's largely equivalent. We should never create this internally unless used to represent user input. It is used for $c, statements in the input, and for some 'systemc_* blocks. - AstCExpr: Internally generaged expression involving raw text. - AstCExprUser: This is the old AstUCFunc, renamed so it sorts next to AstCExpr. It is largely equivalent, but also has more optimizations disabled. This should never be created internally, it is only used for $c expressions in the input. - AstTextBlock: Use by V3ProtectLib only, to generate the hierarchical wrappers. Text "tracking" for indentation is always on for AstCStmt, AstCExpr, and AstTextBlock, as these are always generated by us, and should always be well formed. Tracking is always off for AstCStmtUser and AstCExprUser, as these contain arbitrary user input that might not be safe to parse for indentation. Remove subsequently redundant AstNodeSimpleText and AstNodeText types. This patch also fixes incorrect indentation in emitted waveform tracing functions, and makes the output more readable for hier block SV stubs. With that, all raw text nodes are handled as a proper AstNodeStmt or AstNodeExpr as required for #6280.
2025-10-21 13:41:29 +02:00
return new AstCExpr{fl, AstCExpr::Pure{},
"vlSymsp->_vm_contextp__->assertOnGet("s + std::to_string(type)
+ ", "s + std::to_string(directiveType) + ")"s,
1};
}
return new AstConst{fl, AstConst::BitFalse{}};
}
case VAssertDirectiveType::INTERNAL:
case VAssertDirectiveType::VIOLATION_IF:
case VAssertDirectiveType::RESTRICT: {
if (v3Global.opt.assertOn()) {
Internals: Refactor text based Ast constructs (#6280) (#6571) Remove the large variety of ways raw "text" is represented in the Ast. Particularly, the only thing that represents a string to be emitted in the output is AstText. There are 5 AstNodes that can contain AstText, and V3Emit will throw an error if an AstText is encountered anywhere else: - AstCStmt: Internally generated procedural statements involving raw text. - AstCStmtUser: This is the old AstUCStmt, renamed so it sorts next to AstCStmt, as it's largely equivalent. We should never create this internally unless used to represent user input. It is used for $c, statements in the input, and for some 'systemc_* blocks. - AstCExpr: Internally generaged expression involving raw text. - AstCExprUser: This is the old AstUCFunc, renamed so it sorts next to AstCExpr. It is largely equivalent, but also has more optimizations disabled. This should never be created internally, it is only used for $c expressions in the input. - AstTextBlock: Use by V3ProtectLib only, to generate the hierarchical wrappers. Text "tracking" for indentation is always on for AstCStmt, AstCExpr, and AstTextBlock, as these are always generated by us, and should always be well formed. Tracking is always off for AstCStmtUser and AstCExprUser, as these contain arbitrary user input that might not be safe to parse for indentation. Remove subsequently redundant AstNodeSimpleText and AstNodeText types. This patch also fixes incorrect indentation in emitted waveform tracing functions, and makes the output more readable for hier block SV stubs. With that, all raw text nodes are handled as a proper AstNodeStmt or AstNodeExpr as required for #6280.
2025-10-21 13:41:29 +02:00
return new AstCExpr{fl, AstCExpr::Pure{}, "vlSymsp->_vm_contextp__->assertOn()",
1};
}
return new AstConst{fl, AstConst::BitFalse{}};
}
}
VL_UNREACHABLE;
2024-02-23 15:05:53 +01:00
}
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");
} else {
return ("[%0t] "s + prefix + ": " + nodep->fileline()->filebasename() + ":"
+ cvtToStr(nodep->fileline()->lineno()) + ": %m"
+ ((message != "") ? ": " : "") + message + "\n");
}
}
static bool resolveAssertType(AstAssertCtl* nodep) {
if (!nodep->assertTypesp()) {
nodep->ctlAssertTypes(VAssertType{ALL_ASSERT_TYPES});
return true;
}
if (const AstConst* const assertTypesp = VN_CAST(nodep->assertTypesp(), Const)) {
nodep->ctlAssertTypes(VAssertType{assertTypesp->toSInt()});
return true;
}
return false;
}
static bool resolveControlType(AstAssertCtl* nodep) {
if (const AstConst* const constp = VN_CAST(nodep->controlTypep(), Const)) {
nodep->ctlType(constp->toSInt());
return true;
}
return false;
}
static bool resolveDirectiveType(AstAssertCtl* nodep) {
if (!nodep->directiveTypesp()) {
nodep->ctlDirectiveTypes(VAssertDirectiveType::ASSERT | VAssertDirectiveType::ASSUME
| VAssertDirectiveType::COVER);
return true;
}
if (const AstConst* const directiveTypesp = VN_CAST(nodep->directiveTypesp(), Const)) {
nodep->ctlDirectiveTypes(VAssertDirectiveType{directiveTypesp->toSInt()});
return true;
}
return false;
}
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) {
AstSampled* const sampledp = new AstSampled{nodep->fileline(), nodep};
2022-08-29 14:39:41 +02:00
sampledp->dtypeFrom(nodep);
return sampledp;
}
AstVarRef* newMonitorNumVarRefp(const AstNode* nodep, VAccess access) {
2020-11-29 17:31:38 +01:00
if (!m_monitorNumVarp) {
m_monitorNumVarp = new AstVar{nodep->fileline(), VVarType::MODULETEMP, "__VmonitorNum",
nodep->findUInt64DType()};
v3Global.rootp()->dollarUnitPkgAddp()->addStmtsp(m_monitorNumVarp);
2020-11-29 17:31:38 +01:00
}
AstVarRef* const varrefp = new AstVarRef{nodep->fileline(), m_monitorNumVarp, access};
2020-11-29 17:31:38 +01:00
varrefp->classOrPackagep(v3Global.rootp()->dollarUnitPkgAddp());
return varrefp;
}
AstVarRef* newMonitorOffVarRefp(const AstNode* nodep, VAccess access) {
2020-11-29 17:31:38 +01:00
if (!m_monitorOffVarp) {
m_monitorOffVarp = new AstVar{nodep->fileline(), VVarType::MODULETEMP, "__VmonitorOff",
nodep->findBitDType()};
v3Global.rootp()->dollarUnitPkgAddp()->addStmtsp(m_monitorOffVarp);
2020-11-29 17:31:38 +01:00
}
AstVarRef* const varrefp = new AstVarRef{nodep->fileline(), m_monitorOffVarp, access};
2020-11-29 17:31:38 +01:00
varrefp->classOrPackagep(v3Global.rootp()->dollarUnitPkgAddp());
return varrefp;
}
static AstNodeStmt* newIfAssertOn(AstNode* bodyp, VAssertDirectiveType directiveType,
VAssertType type = VAssertType::INTERNAL) {
// Add a internal if to check assertions are on.
// Don't make this a AND term, as it's unlikely to need to test this.
FileLine* const fl = bodyp->fileline();
AstNodeExpr* const condp = assertOnCond(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
return newp;
}
static AstIf* assertCond(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;
}
static AstNode* assertBody(AstNodeCoverOrAssert* nodep, AstNodeExpr* propp, AstNode* passsp,
AstNode* failsp) {
if (AstPExpr* const pExpr = VN_CAST(propp, PExpr)) {
AstNodeExpr* const condp = pExpr->condp();
UASSERT_OBJ(condp, pExpr, "Should have condition");
AstIf* const ifp = assertCond(nodep, condp->unlinkFrBack(), passsp, failsp);
AstNode* const precondps = pExpr->precondp();
UASSERT_OBJ(precondps, pExpr, "Should have precondition");
precondps->unlinkFrBackWithNext()->addNext(ifp);
AstNodeStmt* const aonp = newIfAssertOn(precondps, nodep->directive(), nodep->type());
FileLine* const flp = precondps->fileline();
AstFork* const forkp = new AstFork{flp, VJoinType::JOIN_NONE};
forkp->addForksp(new AstBegin{flp, "", aonp, true});
return forkp;
}
AstIf* const ifp = assertCond(nodep, propp, passsp, failsp);
return newIfAssertOn(ifp, nodep->directive(), nodep->type());
}
AstNodeStmt* newFireAssertUnchecked(const AstNodeStmt* nodep, const string& message,
AstNodeExpr* exprsp = nullptr) {
// Like newFireAssert() but omits the asserts-on check
AstDisplay* const dispp
2022-10-22 01:03:40 +02:00
= 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;
}
void visitAssertionIterate(AstNodeCoverOrAssert* nodep, AstNode* failsp) {
if (m_beginp && nodep->name() == "") nodep->name(m_beginp->name());
{ AssertDeFuture{nodep->propp(), m_modp, m_modPastNum++}; }
iterateChildren(nodep);
AstNodeExpr* const propp = VN_AS(nodep->propp()->unlinkFrBackWithNext(), NodeExpr);
AstSenTree* const sentreep = nodep->sentreep();
const string& message = nodep->name();
AstNode* passsp = nodep->passsp();
if (passsp) passsp->unlinkFrBackWithNext();
if (failsp) failsp->unlinkFrBackWithNext();
if (nodep->immediate()) {
2020-01-25 02:10:44 +01:00
UASSERT_OBJ(!sentreep, nodep, "Immediate assertions don't have sensitivity");
} else {
2020-01-25 02:10:44 +01:00
UASSERT_OBJ(sentreep, nodep, "Concurrent assertions must have sensitivity");
sentreep->unlinkFrBack();
if (m_procedurep) {
// To support this need queue of asserts to activate
2025-09-20 14:19:42 +02:00
nodep->v3warn(E_UNSUPPORTED,
"Unsupported: Procedural concurrent assertion with"
" clocking event inside always (IEEE 1800-2023 16.14.6)");
}
}
//
bool selfDestruct = false;
if (const AstCover* const snodep = VN_CAST(nodep, Cover)) {
++m_statCover;
if (!v3Global.opt.coverageUser()) {
selfDestruct = true;
} else {
// V3Coverage assigned us a bucket to increment.
AstCoverInc* const covincp = VN_AS(snodep->coverincsp(), CoverInc);
UASSERT_OBJ(covincp, snodep, "Missing AstCoverInc under assertion");
2019-10-27 14:27:18 +01:00
covincp->unlinkFrBackWithNext(); // next() might have AstAssign for trace
if (message != "") covincp->declp()->comment(message);
if (passsp) {
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, "'assert' failed.");
} else {
nodep->v3fatalSrc("Unknown node type");
}
AstNode* bodysp = assertBody(nodep, propp, passsp, failsp);
if (sentreep) {
bodysp = new AstAlways{nodep->fileline(), VAlwaysKwd::ALWAYS, sentreep, bodysp};
}
2025-09-09 23:39:44 +02:00
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);
}
2018-09-23 21:09:47 +02:00
// VISITORS
void visit(AstIf* nodep) override {
if (nodep->user1SetOnce()) return;
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());
2014-03-17 02:38:29 +01:00
// Recurse into the true case.
iterateAndNextNull(ifp->thensp());
2017-09-12 01:18:58 +02:00
// 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) {
2022-10-22 01:03:40 +02:00
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
2022-10-22 01:03:40 +02:00
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);
} else {
iterateChildren(nodep);
}
2014-03-17 02:38:29 +01:00
}
2018-09-23 21:09:47 +02:00
//========== Case assertions
void visit(AstCase* nodep) override {
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()) {
2022-03-31 02:17:59 +02:00
// Need to add a default if there isn't one already
++m_statAsFull;
if (!has_default) {
2022-10-22 01:03:40 +02:00
nodep->addItemsp(new AstCaseItem{
nodep->fileline(), nullptr /*DEFAULT*/,
newFireAssert(nodep, VAssertDirectiveType::VIOLATION_CASE, assertType,
2024-02-23 15:05:53 +01:00
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)) {
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));
}
if (propp) {
2022-10-22 01:03:40 +02:00
propp = new AstConcat{icondp->fileline(), onep, propp};
} else {
propp = onep;
}
}
}
// Empty case means no property
2022-10-22 01:03:40 +02:00
if (!propp) propp = new AstConst{nodep->fileline(), AstConst::BitFalse{}};
const bool allow_none = has_default || nodep->unique0Pragma();
// The following assertion lools 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,
2024-02-23 15:05:53 +01:00
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);
}
2018-09-23 21:09:47 +02:00
//========== Past
void visit(AstPast* nodep) override {
2018-09-23 21:09:47 +02:00
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();
2018-09-23 21:09:47 +02:00
}
UASSERT_OBJ(ticks >= 1, nodep, "0 tick should have been checked in V3Width");
AstNodeExpr* const exprp = nodep->exprp()->unlinkFrBack();
AstNodeExpr* inp = newSampledExpr(exprp);
AstVar* invarp = nullptr;
AstSenTree* const sentreep = nodep->sentreep()->unlinkFrBack();
AstAlways* const alwaysp
2022-10-22 01:03:40 +02:00
= new AstAlways{nodep->fileline(), VAlwaysKwd::ALWAYS, sentreep, nullptr};
m_modp->addStmtsp(alwaysp);
for (uint32_t i = 0; i < ticks; ++i) {
// TODO recognize AstVarRef is getting delayed and share variables between
// $pasts with same reference (or same expression). Saves downstream
// optimizations from identifying and removing duplication.
2022-10-22 01:03:40 +02:00
AstVar* const outvarp = new AstVar{
nodep->fileline(), VVarType::MODULETEMP,
2022-10-22 01:03:40 +02:00
"_Vpast_" + cvtToStr(m_modPastNum++) + "_" + cvtToStr(i), inp->dtypep()};
outvarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
++m_statPastVars;
m_modp->addStmtsp(outvarp);
2022-10-22 01:03:40 +02:00
AstNode* const assp = new AstAssignDly{
nodep->fileline(), new AstVarRef{nodep->fileline(), outvarp, VAccess::WRITE}, inp};
alwaysp->addStmtsp(assp);
// UINFOTREE(9, assp, "", "ass");
2018-09-23 21:09:47 +02:00
invarp = outvarp;
2022-10-22 01:03:40 +02:00
inp = new AstVarRef{nodep->fileline(), invarp, VAccess::READ};
2018-09-23 21:09:47 +02:00
}
nodep->replaceWith(inp);
2022-12-29 19:59:24 +01:00
VL_DO_DANGLING(pushDeletep(nodep), nodep);
2018-09-23 21:09:47 +02:00
}
2022-08-29 14:39:41 +02:00
//========== Move $sampled down to read-only variables
void visit(AstSampled* nodep) override {
2022-08-29 14:39:41 +02:00
if (nodep->user1()) return;
VL_RESTORER(m_inSampled);
{
m_inSampled = true;
iterateChildren(nodep);
}
2020-01-26 19:38:15 +01:00
nodep->replaceWith(nodep->exprp()->unlinkFrBack());
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
2022-09-16 17:15:10 +02:00
void visit(AstVarRef* nodep) override {
2022-08-29 14:39:41 +02:00
iterateChildren(nodep);
if (m_inSampled && !VString::startsWith(nodep->name(), "__VpropPrecond")) {
2022-08-29 14:39:41 +02:00
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();
2022-08-29 14:39:41 +02:00
}
}
}
// Don't sample sensitivities
2022-09-16 17:15:10 +02:00
void visit(AstSenItem* nodep) override {
2022-08-29 14:39:41 +02:00
VL_RESTORER(m_inSampled);
m_inSampled = false;
iterateChildren(nodep);
2022-08-29 14:39:41 +02:00
}
void visit(AstPExpr* nodep) override {
{
VL_RESTORER(m_inSampled);
m_inSampled = false;
iterateAndNextNull(nodep->precondp());
}
iterate(nodep->condp());
if (!m_inRestrict) {
VL_DO_DANGLING(pushDeletep(nodep), nodep);
} else {
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
}
}
2018-09-23 21:09:47 +02:00
//========== 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();
AstNode* monExprsp = nodep->fmtp()->exprsp();
AstSenItem* monSenItemsp = nullptr;
while (monExprsp) {
if (AstNodeVarRef* varrefp = VN_CAST(monExprsp, NodeVarRef)) {
AstSenItem* const senItemp
= new AstSenItem{fl, VEdgeType::ET_CHANGED,
// Clone so get VarRef or VarXRef as needed
varrefp->cloneTree(false)};
if (!monSenItemsp) {
monSenItemsp = senItemp;
} else {
monSenItemsp->addNext(senItemp);
}
}
monExprsp = monExprsp->nextp();
}
AstSenTree* const monSenTree = new AstSenTree{fl, monSenItemsp};
2020-11-29 17:31:38 +01:00
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}};
2020-11-29 17:31:38 +01:00
nodep->replaceWith(newsetp);
// Add "always_comb if (__VmonitorOn && __VmonitorNum==N) $display(...);"
AstNode* const stmtsp = nodep;
AstIf* const ifp = new AstIf{
2020-11-29 17:31:38 +01:00
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
2020-11-29 17:31:38 +01:00
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);
2020-11-29 17:31:38 +01:00
// Need one-shot
FileLine* const fl = nodep->fileline();
AstVar* const varp
= new AstVar{fl, VVarType::MODULETEMP, "__Vstrobe" + cvtToStr(m_modStrobeNum++),
2020-11-29 17:31:38 +01:00
nodep->findBitDType()};
m_modp->addStmtsp(varp);
2020-11-29 17:31:38 +01:00
// Where $strobe was we do "__Vstrobe = '1;"
AstAssign* const newsetp = new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE},
new AstConst{fl, AstConst::BitTrue{}}};
2020-11-29 17:31:38 +01:00
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
2020-11-29 17:31:38 +01:00
ifp->branchPred(VBranchPred::BP_UNLIKELY);
AstNode* const newp = new AstAlwaysPostponed{fl, ifp};
2020-11-29 17:31:38 +01:00
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
2022-10-22 01:03:40 +02:00
= new AstAssign{nodep->fileline(), newMonitorOffVarRefp(nodep, VAccess::WRITE),
new AstConst{nodep->fileline(), AstConst::BitTrue{}, nodep->off()}};
2020-11-29 17:31:38 +01:00
nodep->replaceWith(newp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstAssert* nodep) override { //
visitAssertionIterate(nodep, nodep->failsp());
}
void visit(AstAssertCtl* nodep) override {
if (VN_IS(m_modp, Class) || VN_IS(m_modp, Iface)) {
nodep->v3warn(E_UNSUPPORTED, "Unsupported: assertcontrols in classes or interfaces");
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
return;
}
iterateChildren(nodep);
if (!resolveAssertType(nodep)) {
nodep->v3warn(E_UNSUPPORTED,
"Unsupported: non-constant assert assertion-type expression");
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
return;
}
if (nodep->ctlAssertTypes() != ALL_ASSERT_TYPES
&& nodep->ctlAssertTypes().containsAny(VAssertType::EXPECT | VAssertType::UNIQUE
| VAssertType::UNIQUE0
| VAssertType::PRIORITY)) {
nodep->v3warn(E_UNSUPPORTED, "Unsupported: assert control assertion_type");
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
return;
}
if (!resolveControlType(nodep)) {
nodep->v3warn(E_UNSUPPORTED, "Unsupported: non-const assert control type expression");
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
return;
}
if (!resolveDirectiveType(nodep)) {
nodep->v3warn(E_UNSUPPORTED,
"Unsupported: non-const assert directive type expression");
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
return;
}
FileLine* const fl = nodep->fileline();
switch (nodep->ctlType()) {
case VAssertCtlType::ON:
UINFO(9, "Generating assertctl for a module: " << m_modp);
nodep->replaceWith(new AstCExpr{
fl,
"vlSymsp->_vm_contextp__->assertOnSet("s + std::to_string(nodep->ctlAssertTypes())
+ ", "s + std::to_string(nodep->ctlDirectiveTypes()) + ");\n"s,
1});
break;
case VAssertCtlType::OFF:
case VAssertCtlType::KILL: {
UINFO(9, "Generating assertctl for a module: " << m_modp);
nodep->replaceWith(new AstCExpr{fl,
"vlSymsp->_vm_contextp__->assertOnClear("s
+ std::to_string(nodep->ctlAssertTypes()) + " ,"s
+ std::to_string(nodep->ctlDirectiveTypes())
+ ");\n"s,
1});
break;
}
case VAssertCtlType::LOCK:
case VAssertCtlType::UNLOCK:
case VAssertCtlType::PASS_ON:
case VAssertCtlType::PASS_OFF:
case VAssertCtlType::FAIL_ON:
case VAssertCtlType::FAIL_OFF:
case VAssertCtlType::NONVACUOUS_ON:
case VAssertCtlType::VACUOUS_OFF: {
nodep->unlinkFrBack();
2025-10-14 02:30:47 +02:00
nodep->v3warn(E_UNSUPPORTED, "Unsupported: $assertcontrol control_type '" << cvtToStr(
static_cast<int>(nodep->ctlType())) << "'");
break;
}
default: {
nodep->unlinkFrBack();
2025-10-14 02:30:47 +02:00
nodep->v3warn(EC_ERROR, "Bad $assertcontrol control_type '"
<< cvtToStr(static_cast<int>(nodep->ctlType()))
<< "' (IEEE 1800-2023 Table 20-5)");
}
}
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);
2020-11-29 17:31:38 +01:00
VL_RESTORER(m_modStrobeNum);
m_modp = nodep;
m_modPastNum = 0;
m_modStrobeNum = 0;
iterateChildren(nodep);
}
void visit(AstNodeProcedure* nodep) override {
VL_RESTORER(m_procedurep);
m_procedurep = 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);
}
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);
}
};
//######################################################################
// Top Assert class
void V3Assert::assertAll(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ":");
{ AssertVisitor{nodep}; } // Destruct before checking
V3Global::dumpCheckGlobalTree("assert", 0, dumpTreeEitherLevel() >= 3);
}