verilator/src/V3Case.cpp

690 lines
31 KiB
C++
Raw Normal View History

// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Break case statements up and add Unknown assigns
//
2019-11-08 04:33:59 +01:00
// 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: 2003-2026 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
// V3Case's Transformations:
//
// Each module:
// TBD: Eliminate tristates by adding __in, __inen, __en wires in parallel
// Need __en in changed list if a signal is on the LHS of a assign
// Cases:
// CASE(v) CASEITEM(items,body) -> IF (OR (EQ (AND v mask)
// (AND item1 mask))
// (other items))
// body
// Or, converts to a if/else tree.
// FUTURES:
// Large 16+ bit tables with constants and no masking (address muxes)
// Enter all into std::multimap, sort by value and use a tree of < and == compares.
// "Diagonal" find of {rightmost,leftmost} bit {set,clear}
// Ignoring mask, check each value is unique (using std::multimap as above?)
// Each branch is then mask-and-compare operation (IE
// <000000001_000000000 at midpoint.)
//
//*************************************************************************
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
#include "V3Case.h"
#include "V3Stats.h"
VL_DEFINE_DEBUG_FUNCTIONS;
//######################################################################
class CaseLintVisitor final : public VNVisitorConst {
// Under a CASE value node, if so the relevant case statement
const AstNode* m_casep = nullptr;
2009-01-21 22:56:50 +01:00
// METHODS
template <typename CaseItem>
static void detectMultipleDefaults(CaseItem* itemsp) {
bool hitDefault = false;
for (CaseItem* itemp = itemsp; itemp; itemp = AstNode::as<CaseItem>(itemp->nextp())) {
if (!itemp->isDefault()) continue;
if (hitDefault) itemp->v3error("Multiple default statements in case statement.");
hitDefault = true;
}
}
template <typename CaseItem>
void checkXZinNonCaseX(AstNode* casep, AstNodeExpr* exprp, CaseItem* itemsp) {
VL_RESTORER(m_casep);
m_casep = casep;
iterateConst(exprp);
for (CaseItem* itemp = itemsp; itemp; itemp = AstNode::as<CaseItem>(itemp->nextp())) {
iterateAndNextConstNull(itemp->condsp());
}
}
// VISITORS
void visit(AstGenCase* nodep) override {
// Detect multiple defaults
detectMultipleDefaults(nodep->itemsp());
// Check for X/Z in non-casex statements
checkXZinNonCaseX(nodep, nodep->exprp(), nodep->itemsp());
}
void visit(AstCase* nodep) override {
if (nodep->casex()) {
nodep->v3warn(CASEX, "Suggest casez (with ?'s) in place of casex (with X's)");
}
// Detect multiple defaults
detectMultipleDefaults(nodep->itemsp());
// Check for X/Z in non-casex statements
checkXZinNonCaseX(nodep, nodep->exprp(), nodep->itemsp());
}
void visit(AstConst* nodep) override {
if (!nodep->num().isFourState()) return;
// Error if generate case
if (VN_IS(m_casep, GenCase)) {
nodep->v3error("Use of x/? constant in generate case statement, "
"(no such thing as 'generate casez')");
return;
}
// Otherwise must be a case statement
const AstCase* const casep = VN_AS(m_casep, Case);
// Don't sweat it, we already complained about casex in general
if (casep->casex()) return;
if (casep->casez() || casep->caseInside()) {
if (nodep->num().isAnyX()) {
nodep->v3warn(CASEWITHX, "Use of x constant in casez statement, "
"(perhaps intended ?/z in constant)");
}
return;
}
nodep->v3warn(CASEWITHX, "Use of x/? constant in case statement, "
"(perhaps intended casex/casez)");
}
void visit(AstNode* nodep) override { iterateChildrenConst(nodep); }
2019-09-12 13:22:22 +02:00
// CONSTRUCTORS
explicit CaseLintVisitor(AstCase* nodep) { iterateConst(nodep); }
explicit CaseLintVisitor(AstGenCase* nodep) { iterateConst(nodep); }
~CaseLintVisitor() override = default;
2026-06-12 15:15:41 +02:00
public:
static void apply(AstCase* nodep) { CaseLintVisitor{nodep}; }
static void apply(AstGenCase* nodep) { CaseLintVisitor{nodep}; }
};
//######################################################################
// Case state, as a visitor of each AstNode
class CaseVisitor final : public VNVisitor {
// Maximum width for detailed analysis
constexpr static int CASE_DETAILS_MAX_WIDTH = 16;
2026-06-12 15:15:41 +02:00
// Levels of priority to be ORed together in top IF tree
constexpr static int CASE_ENCODER_GROUP_DEPTH = 8;
// TYPES
// Record for each case value
struct CaseRecord final {
AstCaseItem* itemp; // Case item that covers value
AstConst* constp; // Expression within 'itemp' that covers value (nullptr for default)
AstNode* stmtsp; // Statements of 'itemp' (might be nullptr if case is empty)
};
// STATE
// Statistics tracking, as a struct so can be passed to 'const' methods
struct Stats final {
VDouble0 caseFast; // Cases using fast bit tree method
VDouble0 caseGeneric; // Cases using generic if/else tree method
VDouble0 provenAssertions; // Assertions proven to hold
} m_stats;
const AstNode* m_alwaysp = nullptr; // Always in which case is located
// STATE - per AstCase. Update by 'analyzeCase', treat 'const' otherwise
bool m_caseOpaque = false; // Case statement is opaque (non-packed, or non-const conditions)
size_t m_caseNConditions = 0; // Number of conditions in the case statement
bool m_caseDetailsValid = false; // Indicates m_caseDetails is valid
struct final {
bool exhaustive = false; // Proven exhaustive
bool noOverlaps = false; // Proven no overlaps between cases
// Map from value (index) to the CaseRecord that covers this value
std::array<CaseRecord, 1U << CASE_DETAILS_MAX_WIDTH> records;
} m_caseDetails;
// METHODS
// Xs in case or casez are impossible due to two state simulations.
// Returns true if the item is never possible
static bool neverItem(const AstCase* casep, const AstNodeExpr* itemExprp) {
const AstConst* const constp = VN_CAST(itemExprp, Const);
if (!constp) return false;
if (casep->casex() || casep->caseInside()) return false;
if (casep->casez()) return constp->num().isAnyX();
return constp->num().isFourState();
}
// Returns the matching mask and bits for a case item constant condition
// TODO: with 'neverItem' checked, this is alway the same for all types of cases
// 4-state will have to fix this up
static std::pair<V3Number, V3Number> matchPattern(const AstCase* /*casep*/,
const AstConst* condp) {
// As one to encourage NRVO/move
std::pair<V3Number, V3Number> pairMaskBits{
std::piecewise_construct, //
std::forward_as_tuple(condp->fileline(), condp->width(), 0),
std::forward_as_tuple(condp->fileline(), condp->width(), 0)};
pairMaskBits.first.opBitsNonXZ(condp->num());
pairMaskBits.second.opBitsOne(condp->num());
return pairMaskBits;
}
2026-06-12 15:15:41 +02:00
// Determine whether we should check case items are complete
// Returns enum's dtype if should check, nullptr if shouldn't
static const AstEnumDType* getEnumCompletionCheckDType(const AstCase* const nodep) {
if (!nodep->uniquePragma() && !nodep->unique0Pragma()) return nullptr;
const AstEnumDType* const enumDtp
= VN_CAST(nodep->exprp()->dtypep()->skipRefToEnump(), EnumDType);
if (!enumDtp) return nullptr; // Case isn't enum
const AstBasicDType* const basicp = enumDtp->subDTypep()->basicp();
if (!basicp) return nullptr; // Not simple type (perhaps IEEE illegal)
return enumDtp;
}
2026-06-12 15:15:41 +02:00
// Check and warn if case items are not complete over the given enum type.
// Returns true iff the case items cover all enum values/patterns.
bool checkExhaustiveEnum(const AstCase* const nodep, const AstEnumDType* const enump) {
const uint32_t numCases = 1UL << nodep->exprp()->width();
for (AstEnumItem* eip = enump->itemsp(); eip; eip = VN_AS(eip->nextp(), EnumItem)) {
const auto& match = matchPattern(nodep, VN_AS(eip->valuep(), Const));
const uint32_t mask = match.first.toUInt();
const uint32_t bits = match.second.toUInt();
2026-06-12 15:15:41 +02:00
// Check all cases to see if they cover this enum value/pattern
for (uint32_t i = 0; i < numCases; ++i) {
if ((i & mask) != bits) continue; // This case is not for this enum value
if (m_caseDetails.records[i].itemp) continue; // Covered case
2026-06-12 15:15:41 +02:00
// Warn unless unique0 case which allows no-match
if (!nodep->unique0Pragma()) {
nodep->v3warn(CASEINCOMPLETE,
"Enum item " << eip->prettyNameQ() << " not covered by case");
}
2026-06-12 15:15:41 +02:00
// TODO: warn for all uncovered enum values, not just the first
return false; // enum has uncovered value by case items
}
}
return true; // enum is fully covered
}
2026-06-12 15:15:41 +02:00
// Check and warn if case items are not complete over all possible values.
// Returns true iff the case items cover all values of the case expression.
bool checkExhaustivePacked(AstCase* nodep) {
const uint32_t numCases = 1UL << nodep->exprp()->width();
for (uint32_t i = 0; i < numCases; ++i) {
if (m_caseDetails.records[i].itemp) continue; // Covered case
2026-06-12 15:15:41 +02:00
if (!nodep->unique0Pragma()) {
nodep->v3warn(CASEINCOMPLETE,
"Case values incompletely covered (example pattern 0x" << std::hex
<< i << ")");
}
2026-06-12 15:15:41 +02:00
// TODO: warn for more than one uncovered case, not just the first
return false;
}
// It's an exhaustive case statement
return true;
}
bool checkExhaustive(AstCase* nodep) {
if (const AstEnumDType* const enump = getEnumCompletionCheckDType(nodep)) {
return checkExhaustiveEnum(nodep, enump);
}
2026-06-12 15:15:41 +02:00
return checkExhaustivePacked(nodep);
}
// Analyze each value in the case statement. Updates 'm_caseDetails' and issues warnings.
void analyzeCaseDetails(AstCase* nodep) {
const uint32_t numValues = 1UL << nodep->exprp()->width();
// Clear case records
for (uint32_t i = 0; i < numValues; ++i) {
m_caseDetails.records[i].itemp = nullptr;
m_caseDetails.records[i].constp = nullptr;
m_caseDetails.records[i].stmtsp = nullptr;
2026-06-12 15:15:41 +02:00
}
// Now pick up the values for each assignment
// We can cheat and use uint32_t's because we only support narrow case's
bool reportedOverlap = false;
2026-06-12 15:15:41 +02:00
bool hasDefault = false;
m_caseDetails.noOverlaps = true;
for (AstCaseItem* itemp = nodep->itemsp(); itemp;
itemp = VN_AS(itemp->nextp(), CaseItem)) {
2026-06-12 15:15:41 +02:00
// Default case
if (itemp->isDefault()) {
// Default was moved to be the last item by V3LinkDot. Fill remaining cases
for (uint32_t i = 0; i < numValues; ++i) {
CaseRecord& caseRecord = m_caseDetails.records[i];
2026-06-12 15:15:41 +02:00
if (!caseRecord.itemp) {
caseRecord.itemp = itemp;
caseRecord.stmtsp = itemp->stmtsp();
}
}
2026-06-12 15:15:41 +02:00
hasDefault = true;
continue;
}
2026-06-12 15:15:41 +02:00
for (AstConst* iconstp = VN_AS(itemp->condsp(), Const); iconstp;
iconstp = VN_AS(iconstp->nextp(), Const)) {
// Some items can never match due to 2-state simulation
if (neverItem(nodep, iconstp)) continue;
const auto& match = matchPattern(nodep, iconstp);
const uint32_t mask = match.first.toUInt();
const uint32_t bits = match.second.toUInt();
2026-06-12 15:15:41 +02:00
2026-06-12 13:22:18 +02:00
bool foundNewCase = false;
const AstConst* firstOverlapConstp = nullptr;
uint32_t firstOverlapValue = 0;
for (uint32_t i = 0; i < numValues; ++i) {
if ((i & mask) != bits) continue;
2026-06-12 15:15:41 +02:00
CaseRecord& caseRecord = m_caseDetails.records[i];
2026-06-12 15:15:41 +02:00
// If this is the first case that covers this value, record it
if (!caseRecord.itemp) {
caseRecord.itemp = itemp;
caseRecord.constp = iconstp;
caseRecord.stmtsp = itemp->stmtsp();
2026-06-12 13:22:18 +02:00
foundNewCase = true;
2026-06-12 15:15:41 +02:00
continue;
}
2026-06-12 13:22:18 +02:00
// There is an overlap. If it's within the same CaseItem, it is legal
if (caseRecord.itemp == itemp) continue;
// Otherwise record the first overlapping case
if (!firstOverlapConstp) {
firstOverlapConstp = caseRecord.constp;
firstOverlapValue = i;
m_caseDetails.noOverlaps = false;
2026-06-12 15:15:41 +02:00
}
}
// Only report first overlap
if (reportedOverlap || !firstOverlapConstp) continue;
// Report first overlap
2026-06-12 13:22:18 +02:00
if (nodep->priorityPragma()) {
// If this is a priority case, we only want to complain if every possible
// value for this item is already hit by some other item. This is true if
// 'foundNewCase' is false. 'firstOverlapConstp' is null when the only
// covering item is this item itself, which is legal overlap within one
// item.
if (!foundNewCase) {
2026-06-12 13:22:18 +02:00
iconstp->v3warn(CASEOVERLAP,
"Case item ignored: every matching value is covered "
"by an earlier condition\n"
<< iconstp->warnContextPrimary() << '\n'
<< firstOverlapConstp->warnOther()
<< "... Location of previous condition\n"
<< firstOverlapConstp->warnContextPrimary());
reportedOverlap = true;
2026-06-12 13:22:18 +02:00
}
} else {
// If this case statement doesn't have the priority keyword,
// we want to warn on any overlap.
std::ostringstream examplePattern;
if (iconstp->num().isAnyXZ()) {
examplePattern << " (example pattern 0x" << std::hex << firstOverlapValue
<< ")";
2026-06-12 15:15:41 +02:00
}
iconstp->v3warn(CASEOVERLAP,
"Case conditions overlap"
<< examplePattern.str() << "\n"
<< iconstp->warnContextPrimary() << '\n'
<< firstOverlapConstp->warnOther()
<< "... Location of overlapping condition\n"
<< firstOverlapConstp->warnContextSecondary());
reportedOverlap = true;
}
}
}
2026-06-12 15:15:41 +02:00
// If there was no default, check exhaustiveness
m_caseDetails.exhaustive = hasDefault || checkExhaustive(nodep);
// Records now valid
m_caseDetailsValid = true;
}
2026-06-12 15:15:41 +02:00
// Analyze case statement. Updates 'm_case*' members. Reports warnings.
void analyzeCase(AstCase* nodep) {
// Reset all analysis results
m_caseOpaque = false;
m_caseNConditions = 0;
m_caseDetailsValid = false;
AstNode* const caseExprp = nodep->exprp();
// Mark opaque if not a packed value - TODO: can this be a class?
if (caseExprp->isDouble() || caseExprp->isString()) m_caseOpaque = true;
// Check each condition expression
for (AstCaseItem* cip = nodep->itemsp(); cip; cip = VN_AS(cip->nextp(), CaseItem)) {
for (AstNode* condp = cip->condsp(); condp; condp = condp->nextp()) {
// Count conditions
++m_caseNConditions;
// Mark opaque if non-constant condition
if (!VN_IS(condp, Const)) m_caseOpaque = true;
}
}
// Nothing else to do if not a packed type, or non-const conditions
if (m_caseOpaque) return;
// If small enough, analyse details
if (caseExprp->width() <= CASE_DETAILS_MAX_WIDTH) analyzeCaseDetails(nodep);
}
2026-06-12 15:15:41 +02:00
// TODO: should return AstNodeStmt after #6280
AstNode* convertCaseFastRecurse(AstNodeExpr* cexprp, int msb, uint32_t upperValue) const {
2026-06-12 15:15:41 +02:00
// Base case: If reached the last bit, upperValue equals an exact value, just return
// the statements from that CaseItem. Note: Not cloning here as the caller will do
// an identity check.
if (msb < 0) return m_caseDetails.records[upperValue].stmtsp;
2026-06-12 15:15:41 +02:00
// Recursive case:
// Make left and right subtrees assuming cexpr[msb] is 0 and 1 respectively
const uint32_t upperValue0 = upperValue;
const uint32_t upperValue1 = upperValue | (1UL << msb);
AstNode* tree0p = convertCaseFastRecurse(cexprp, msb - 1, upperValue0);
AstNode* tree1p = convertCaseFastRecurse(cexprp, msb - 1, upperValue1);
2026-06-12 15:15:41 +02:00
// If same logic on both sides, we can just return one of them
if (tree0p == tree1p) return tree0p;
// We could have a "checkerboard" with A B A B, we can use the same IF on both edges
{
bool same = true;
2026-06-12 15:15:41 +02:00
for (uint32_t a = upperValue0, b = upperValue1; a < upperValue1; ++a, ++b) {
if (m_caseDetails.records[a].stmtsp != m_caseDetails.records[b].stmtsp) {
same = false;
break;
}
}
if (same) {
VL_DO_DANGLING(tree1p->deleteTree(), tree1p);
return tree0p;
}
}
2026-06-12 15:15:41 +02:00
// Must have differing logic. Test the bit and convert to an If.
// Clone if needed
if (tree0p && tree0p->backp()) tree0p = tree0p->cloneTree(true);
if (tree1p && tree1p->backp()) tree1p = tree1p->cloneTree(true);
// Create the If statement
FileLine* const flp = cexprp->fileline();
AstNodeExpr* const condp = new AstSel{flp, cexprp->cloneTreePure(false), msb, 1};
AstIf* const ifp = new AstIf{flp, condp, tree1p, tree0p};
return ifp;
}
// CASEx(cexpr,....
// -> tree of IF(msb, IF(msb-1, 11, 10)
// IF(msb-1, 01, 00))
2026-06-12 15:15:41 +02:00
// TODO: should return AstNodeStmt after #6280
AstNode* convertCaseFast(AstCase* nodep) const {
2026-06-12 15:15:41 +02:00
const int caseWidth = nodep->exprp()->width();
AstNode* const ifrootp = convertCaseFastRecurse(nodep->exprp(), caseWidth - 1, 0UL);
2026-06-12 15:15:41 +02:00
return ifrootp && ifrootp->backp() ? ifrootp->cloneTree(true) : ifrootp;
}
// Convet case statement using generic if/else tree
// CASEx(cexpr,ITEM(icond1,istmts1),ITEM(icond2,istmts2),ITEM(default,istmts3))
// -> IF((cexpr==icond1),istmts1,
// IF((EQ (AND MASK cexpr) (AND MASK icond1)
// ,istmts2, istmts3
2026-06-12 15:15:41 +02:00
// TODO: should return AstNodeStmt after #6280
AstNode* convertCaseGeneric(AstCase* nodep) const {
2026-06-12 15:15:41 +02:00
// We'll do this in two stages.
// First stage, convert the conditions to the appropriate IF AND terms.
bool hasDefault = false;
for (AstCaseItem* itemp = nodep->itemsp(); itemp;
itemp = VN_AS(itemp->nextp(), CaseItem)) {
2026-06-12 15:15:41 +02:00
FileLine* const flp = itemp->fileline();
// Default clause. Just make true, we'll optimize it away later
if (itemp->isDefault()) {
itemp->addCondsp(new AstConst{flp, AstConst::BitTrue{}});
hasDefault = true;
continue;
}
// Regular clause. Construct the condition expression.
AstNodeExpr* newCondp = nullptr;
for (AstNodeExpr *itemExprp = itemp->condsp(), *nextp; itemExprp; itemExprp = nextp) {
nextp = VN_AS(itemExprp->nextp(), NodeExpr);
itemExprp->unlinkFrBack();
// If case never matches, ignore it
if (neverItem(nodep, itemExprp)) {
VL_DO_DANGLING(itemExprp->deleteTree(), itemExprp);
continue;
}
// Compute the term to add to the condition expression
AstNodeExpr* const termp = [&]() -> AstNodeExpr* {
// Will need a copy of the caseExpr regardless
AstNodeExpr* const caseExprp = nodep->exprp()->cloneTreePure(false);
// InsideRange: Similar logic in V3Width::visit(AstInside)
if (AstInsideRange* const itemRangep = VN_CAST(itemExprp, InsideRange)) {
AstNodeExpr* const resultp = itemRangep->newAndFromInside( //
caseExprp, //
itemRangep->lhsp()->unlinkFrBack(),
itemRangep->rhsp()->unlinkFrBack());
VL_DO_DANGLING2(itemExprp->deleteTree(), itemExprp, itemRangep);
return resultp;
}
2026-06-12 15:15:41 +02:00
// Check if we need to perform a wildcard match, this needs masking
if (AstConst* const itemConstp = VN_CAST(itemExprp, Const)) {
// TODO: 4-state will need to fix this
if (itemConstp->num().isFourState()
&& (nodep->casex() || nodep->casez() || nodep->caseInside())) {
// Wildcard match, make 'caseExpr' & 'mask' == 'itemExpr' & 'mask'
const auto& match = matchPattern(nodep, itemConstp);
const V3Number& matchMask = match.first;
const V3Number& matchBits = match.second;
2026-06-12 15:15:41 +02:00
VL_DO_DANGLING2(itemExprp->deleteTree(), itemExprp, itemConstp);
return AstEq::newTyped(
flp, //
new AstConst{flp, matchBits},
new AstAnd{flp, caseExprp, new AstConst{flp, matchMask}});
2026-06-12 15:15:41 +02:00
}
}
2026-06-12 15:15:41 +02:00
// Regular case, use simple equality comparison
return AstEq::newTyped(flp, caseExprp, itemExprp);
}();
// 'Or' new term with previous terms
newCondp = newCondp ? new AstLogOr{flp, newCondp, termp} : termp;
}
2026-06-12 15:15:41 +02:00
// Replace expression in tree. Needs to be non-null, so add a constant false if needed
if (!newCondp) newCondp = new AstConst{flp, AstConst::BitFalse{}};
itemp->addCondsp(newCondp);
}
2026-06-12 15:15:41 +02:00
// If there was no default, add a empty one, this greatly simplifies below code
// and constant propagation will just eliminate it for us later.
if (!hasDefault) {
2022-11-20 23:40:38 +01:00
nodep->addItemsp(new AstCaseItem{
nodep->fileline(), new AstConst{nodep->fileline(), AstConst::BitTrue{}}, nullptr});
}
2026-06-12 15:15:41 +02:00
// Now build the IF statement tree
2026-06-12 15:15:41 +02:00
// The tree can be quite huge. Pull every group of 8 out, and make a OR tree.
// This reduces the depth for the bottom elements, at the cost of
// some of the top elements. If we ever have profiling data, we
// should pull out the most common item from here and instead make
// it the first IF branch.
int depth = 0;
AstIf* grouprootp = nullptr;
AstIf* groupnextp = nullptr;
AstIf* itemnextp = nullptr;
for (AstCaseItem* itemp = nodep->itemsp(); itemp;
itemp = VN_AS(itemp->nextp(), CaseItem)) {
2026-06-12 15:15:41 +02:00
// Grab the statements from this item. May be empty.
AstNode* const istmtsp = itemp->stmtsp();
if (istmtsp) istmtsp->unlinkFrBackWithNext();
2026-06-12 15:15:41 +02:00
// Expressioned clause
AstNodeExpr* const ifexprp = itemp->condsp()->unlinkFrBack();
{ // Prepare for next group
if (++depth > CASE_ENCODER_GROUP_DEPTH) depth = 1;
if (depth == 1) { // First group or starting new group
itemnextp = nullptr;
AstIf* const newp = new AstIf{itemp->fileline(), ifexprp->cloneTreePure(true)};
if (groupnextp) {
groupnextp->addElsesp(newp);
} else {
grouprootp = newp;
}
groupnextp = newp;
} else { // Continue group, modify if condition to OR in this new condition
AstNodeExpr* const condp = groupnextp->condp()->unlinkFrBack();
groupnextp->condp(
new AstOr{ifexprp->fileline(), condp, ifexprp->cloneTreePure(true)});
}
}
{ // Make the new lower IF and attach in the tree
AstNodeExpr* itemexprp = ifexprp;
VL_DANGLING(ifexprp);
if (depth == CASE_ENCODER_GROUP_DEPTH) { // End of group - can skip the condition
VL_DO_DANGLING(itemexprp->deleteTree(), itemexprp);
2022-11-20 23:40:38 +01:00
itemexprp = new AstConst{itemp->fileline(), AstConst::BitTrue{}};
}
2022-11-20 23:40:38 +01:00
AstIf* const newp = new AstIf{itemp->fileline(), itemexprp, istmtsp};
if (itemnextp) {
itemnextp->addElsesp(newp);
} else {
groupnextp->addThensp(newp); // First in a new group
}
itemnextp = newp;
}
}
2026-06-12 15:15:41 +02:00
return grouprootp;
}
// Convert the given case statement to a representation not using AstCase
// TODO: should return AstNodeStmt after #6280
AstNode* convertCase(AstCase* nodep, Stats& stats) const {
// Determine if we should use the fast bitwise branching tree method
const bool useFastBitTree = [&]() {
// Not if disabled
if (!v3Global.opt.fCase()) return false;
// Can't do it without the detailed analysis
if (!m_caseDetailsValid) return false;
// Can't do it if not exhaustive
if (!m_caseDetails.exhaustive) return false;
// Not worth doing if there are few conditions
if (m_caseNConditions <= 3) return false;
// Avoid e.g. priority expanders from going crazy in expansion
const size_t caseWidth = nodep->exprp()->width();
if (caseWidth >= 8 && m_caseNConditions <= (caseWidth + 1)) return false;
// Otherwise use the bit tree
return true;
}();
if (useFastBitTree) {
++stats.caseFast;
return convertCaseFast(nodep);
}
// Convert using the generic if/else tree method
++stats.caseGeneric;
// If a case statement is exhaustive, presume signals involved aren't forming a latch
// TODO: this is broken, but it is as was before
if (m_alwaysp && (!m_caseDetailsValid || m_caseDetails.exhaustive)) {
m_alwaysp->fileline()->warnOff(V3ErrorCode::LATCH, true);
}
return convertCaseGeneric(nodep);
}
// VISITORS
void visit(AstCase* nodep) override {
UASSERT_OBJ(nodep->exprp()->isPure(), nodep,
"Impure case expression should have been removed by V3LiftExpr");
2026-06-12 15:15:41 +02:00
CaseLintVisitor::apply(nodep);
// Convert any children first
iterateChildren(nodep);
// Analyze this case statement
analyzeCase(nodep);
2026-06-12 15:15:41 +02:00
// Take the 'notParallelp' statements under the case statement created by V3Assert.
2026-06-12 15:15:41 +02:00
// If the statement was proven to have no overlaps and all cases covered,
// it can be removed. Otherwise insert the assertion after the case statement.
if (AstNode* const stmtp = nodep->notParallelp()) {
stmtp->unlinkFrBackWithNext();
if (m_caseDetailsValid && m_caseDetails.exhaustive && m_caseDetails.noOverlaps) {
++m_stats.provenAssertions;
VL_DO_DANGLING(stmtp->deleteTree(), stmtp);
} else {
nodep->addNextHere(stmtp);
}
}
2026-06-12 15:15:41 +02:00
// Convert the case statement and replace the original
if (AstNode* const replacementp = convertCase(nodep, m_stats)) {
2026-06-12 15:15:41 +02:00
nodep->replaceWith(replacementp);
} else {
nodep->unlinkFrBack();
}
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}
//--------------------
void visit(AstAlways* nodep) override {
VL_RESTORER(m_alwaysp);
m_alwaysp = nodep;
iterateChildren(nodep);
}
void visit(AstNode* nodep) override { iterateChildren(nodep); }
public:
2019-09-12 13:22:22 +02:00
// CONSTRUCTORS
2026-06-12 15:15:41 +02:00
explicit CaseVisitor(AstNetlist* nodep) { iterate(nodep); }
~CaseVisitor() override {
V3Stats::addStat("Optimizations, Cases parallelized", m_stats.caseFast);
V3Stats::addStat("Optimizations, Cases complex", m_stats.caseGeneric);
V3Stats::addStat("Optimizations, Cases proven assertions", m_stats.provenAssertions);
}
};
//######################################################################
// Case class functions
void V3Case::caseAll(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ":");
{ CaseVisitor{nodep}; } // Destruct before checking
V3Global::dumpCheckGlobalTree("case", 0, dumpTreeEitherLevel() >= 3);
}
void V3Case::caseLint(AstGenCase* nodep) {
UINFO(4, __FUNCTION__ << ": ");
2026-06-12 15:15:41 +02:00
CaseLintVisitor::apply(nodep);
}