Optimize decoder case statements into lookup tables (#7795)

Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.

While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.

Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.

Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.

Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
This commit is contained in:
Geza Lore
2026-06-18 09:30:50 +01:00
committed by GitHub
parent 3a4377d39e
commit 5712f9b614
27 changed files with 1287 additions and 44 deletions
+401 -22
View File
@@ -133,6 +133,12 @@ class CaseVisitor final : public VNVisitor {
constexpr static int CASE_DETAILS_MAX_WIDTH = 16;
// Levels of priority to be ORed together in top IF tree
constexpr static int CASE_ENCODER_GROUP_DEPTH = 8;
// Maximum size for tiny lookup tables - materialized in code
constexpr static size_t CASE_TABLE_TINY_BITS = 32; // Up to 2 instructions to materialize
// Maximum size for normal lookup tables - stored in constant pool
constexpr static size_t CASE_TABLE_MAX_BITS = 1ULL << 16; // 64Kbits / 8KBytes
// Minimum number of the branches a table must replace to be worth a load
constexpr static size_t CASE_TABLE_MIN_BRANCHES = 3;
// TYPES
// Record for each case value
@@ -142,21 +148,49 @@ class CaseVisitor final : public VNVisitor {
AstNode* stmtsp; // Statements of 'itemp' (might be nullptr if case is empty)
};
// Record for each LHS of a decoder pattern
struct LhsRecord final {
AstNodeExpr* lhsp = nullptr; // LHS of the assignment
AstNodeAssign* preDefaultp = nullptr; // Default assignment *before the case statement*
size_t nCaseAssigns = 0; // Number of AstAssigns to this LHS in case clauses
size_t nCaseAssignDlys = 0; // Number of AstAssignDlys to this LHS in case clauses
size_t offset = 0; // Offset in the table for this LHS
static size_t s_nextId; // Static unique Id counter
size_t id = ++s_nextId; // Unique Id for sorting
};
// NODE STATE:
// AstVarScope::user1() -> bool: true if written to, only in parts of analysis phase
// STATE
// Statistics tracking, as a struct so can be passed to 'const' methods
struct Stats final {
VDouble0 caseTableNormal; // Cases using table method with normal table
VDouble0 caseTableTiny; // Cases using table method with tiny table
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
size_t m_nTmps = 0; // Sequence numbers for temporary variables
AstScope* m_scopep = nullptr; // Current scope
// STATE - per AstCase. Update by 'analyzeCase', treat 'const' otherwise
bool m_caseOpaque = false; // Case statement is opaque (non-packed, or non-const conditions)
bool m_caseHasDefault = false; // Indicates the case statement has a default case
size_t m_caseNCaseItems = 0; // Number of AstCaseItems in the case statement
size_t m_caseNConditions = 0; // Number of conditions in the case statement
// Map from LHSs of decoder pattern to corresponding LhsRecord.
std::unordered_map<VNRef<AstNodeExpr>, LhsRecord> m_caseLhsRecords;
// Values of 'm_caseLhsRecords' in sorted order, if case statement is a decoder pattern
std::vector<LhsRecord> m_caseDecoderRecords;
size_t m_caseDecoderEntryWidth = 0; // Width of each entry in the decoder table
size_t m_caseTableWidth = 0; // Total width of the case table - 0 means can't optimize
bool m_caseDetailsValid = false; // Indicates m_caseDetails is valid
struct final {
bool exhaustive = false; // Proven exhaustive
bool exhaustiveOverEnumOnly = false; // Exhaustive over enum values only
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;
@@ -189,6 +223,50 @@ class CaseVisitor final : public VNVisitor {
return pairMaskBits;
}
// If the given statement is an assignment that fits the decoder pattern,
// return it, otherwise return nullptr
static AstNodeAssign* checkDecoderAssign(AstNode* stmtp) {
// Only Assign and AssignDly are supported
if (!VN_IS(stmtp, Assign) && !VN_IS(stmtp, AssignDly)) return nullptr;
AstNodeAssign* const assp = VN_AS(stmtp, NodeAssign);
// Only if no timing control
if (assp->timingControlp()) return nullptr;
// Only if assigning a constant
if (!VN_IS(assp->rhsp(), Const)) return nullptr;
// Only if it's a packed value
AstNodeDType* const dtypep = assp->rhsp()->dtypep();
if (dtypep->isString() || dtypep->isDouble()) return nullptr;
// Only if the LHS has no reads (can be relaxed, but need to prove there is no r/w hazard)
if (assp->lhsp()->exists([](AstVarRef* refp) { return refp->access().isReadOrRW(); })) {
return nullptr;
}
// This is an assignment that fits the decoder pattern
return assp;
}
// Analyze if the given case item fits the decoder pattern, return true iff so.
// Updates 'm_caseLhsRecords'.
bool analyzeDecoderCaseItem(AstCaseItem* cip) {
// AstVarScope::user1() -> bool: true if written to
const VNUser1InUse user1InUse;
for (AstNode* stmtp = cip->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
// Must be an assignment that fits the decoder pattern
AstNodeAssign* const assp = checkDecoderAssign(stmtp);
if (!assp) return false;
// Must assign each LHS exactly once - RHS is Const
const bool multipleAssignments = assp->lhsp()->exists([](AstVarRef* refp) { //
return refp->varScopep()->user1SetOnce();
});
if (multipleAssignments) return false;
// Update LhsRecord
LhsRecord& lhsRecord = m_caseLhsRecords[*assp->lhsp()];
if (!lhsRecord.lhsp) lhsRecord.lhsp = assp->lhsp();
lhsRecord.nCaseAssigns += VN_IS(assp, Assign);
lhsRecord.nCaseAssignDlys += VN_IS(assp, AssignDly);
}
return true;
}
// 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) {
@@ -245,13 +323,6 @@ class CaseVisitor final : public VNVisitor {
return true;
}
bool checkExhaustive(AstCase* nodep) {
if (const AstEnumDType* const enump = getEnumCompletionCheckDType(nodep)) {
return checkExhaustiveEnum(nodep, enump);
}
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();
@@ -361,16 +432,136 @@ class CaseVisitor final : public VNVisitor {
}
// If there was no default, check exhaustiveness
m_caseDetails.exhaustive = hasDefault || checkExhaustive(nodep);
m_caseDetails.exhaustiveOverEnumOnly = false;
m_caseDetails.exhaustive = hasDefault;
if (!hasDefault) {
if (const AstEnumDType* const enump = getEnumCompletionCheckDType(nodep)) {
// Only checks enum values are covered, not all bit patterns of the case expression
const bool exhaustiveOverEnum = checkExhaustiveEnum(nodep, enump);
m_caseDetails.exhaustiveOverEnumOnly = exhaustiveOverEnum;
m_caseDetails.exhaustive = exhaustiveOverEnum;
} else {
m_caseDetails.exhaustive = checkExhaustivePacked(nodep);
}
}
// Records now valid
m_caseDetailsValid = true;
}
void analyzeDecoderPattern(AstCase* nodep) {
// Check each LHS record
for (auto it = m_caseLhsRecords.cbegin(); it != m_caseLhsRecords.cend();) {
const LhsRecord& lhsRecord = it->second;
// Delete records that have no assignments in any case item (only pre-defaults)
if (!lhsRecord.nCaseAssigns && !lhsRecord.nCaseAssignDlys) {
it = m_caseLhsRecords.erase(it);
continue;
}
++it;
// If mixed assignments, it's not a decoder pattern
if (lhsRecord.nCaseAssigns && lhsRecord.nCaseAssignDlys) return;
// If assigned in all branches, it's good - but only if every table entry will be
// covered, i.e. the case has a default, or is exhaustive over all bit patterns.
// Enum-only exhaustiveness is not enough: out-of-enum values leave entries
// uncovered.
if (m_caseHasDefault
|| (m_caseDetailsValid && m_caseDetails.exhaustive
&& !m_caseDetails.exhaustiveOverEnumOnly)) {
if (lhsRecord.nCaseAssigns == m_caseNCaseItems) continue;
if (lhsRecord.nCaseAssignDlys == m_caseNCaseItems) continue;
}
// Otherwise it needs to have a pre-default assignment
AstNode* const preDefaultp = lhsRecord.preDefaultp;
if (!preDefaultp) return;
// And the pre-default needs to be the same type
if (lhsRecord.nCaseAssigns && !VN_IS(preDefaultp, Assign)) return;
if (lhsRecord.nCaseAssignDlys && !VN_IS(preDefaultp, AssignDly)) return;
}
// All cases check out, can optimize if there are some entries left
if (m_caseLhsRecords.empty()) return;
// Gather all the LhsRecords and sort them - there is a copy here, it's ok, won't be many
m_caseDecoderRecords.reserve(m_caseLhsRecords.size());
for (const auto& item : m_caseLhsRecords) m_caseDecoderRecords.emplace_back(item.second);
std::sort(m_caseDecoderRecords.begin(), m_caseDecoderRecords.end(),
[](const LhsRecord& a, const LhsRecord& b) {
// Sort by width, then id
const int aWidth = a.lhsp->width();
const int bWidth = b.lhsp->width();
if (aWidth != bWidth) return aWidth < bWidth;
return a.id < b.id;
});
// We can either create a single lookup table for all LHSs, or one for each LHS.
// With a single table, we need to select out of the lookup via a temporary variable.
// With one table per LHS, we need to do multiple loads. The table is likely to incur a
// D-cache miss on large designs, so we choose single table.
const int caseWidth = nodep->exprp()->width();
// Safely check if table with 'entryWidth' entries would fit within 'maxWidth' bits
const auto fitsLimit = [&](size_t entryWidth, size_t maxWidth) -> bool {
size_t totalWidth = entryWidth;
// Multiply cases - iterative to avoid overflow
for (int i = 0; i < caseWidth; ++i) {
totalWidth <<= 1;
if (totalWidth > maxWidth) return false;
}
return true;
};
// Check if the whole table would fit in a tiny table packed tightly
m_caseDecoderEntryWidth = 0;
for (LhsRecord& lhsRecord : m_caseDecoderRecords) {
lhsRecord.offset = m_caseDecoderEntryWidth;
m_caseDecoderEntryWidth += lhsRecord.lhsp->width();
}
// If it fits, we will pack it tightly
if (fitsLimit(m_caseDecoderEntryWidth, CASE_TABLE_TINY_BITS)) {
m_caseTableWidth = m_caseDecoderEntryWidth << caseWidth; // Can optimize
return;
}
// Tabel will be bigish. To avoid expensive bit swizzling, align each entry to a
// word boundary if it would cross a word boundary.
m_caseDecoderEntryWidth = 0;
for (LhsRecord& lhsRecord : m_caseDecoderRecords) {
const size_t width = lhsRecord.lhsp->width();
const size_t lsbWord = VL_BITWORD_E(m_caseDecoderEntryWidth);
const size_t msbWord = VL_BITWORD_E(m_caseDecoderEntryWidth + width - 1);
if (lsbWord != msbWord) {
m_caseDecoderEntryWidth = VL_WORDS_I(m_caseDecoderEntryWidth) * VL_EDATASIZE;
}
lhsRecord.offset = m_caseDecoderEntryWidth;
m_caseDecoderEntryWidth += width;
}
// Also align the whole entry width to a word boundary
m_caseDecoderEntryWidth = VL_WORDS_I(m_caseDecoderEntryWidth) * VL_EDATASIZE;
// Check the table fits max size
if (fitsLimit(m_caseDecoderEntryWidth, CASE_TABLE_MAX_BITS)) {
m_caseTableWidth = m_caseDecoderEntryWidth << caseWidth; // Can optimize
return;
}
// Can't optimize - yet ...
}
// Analyze case statement. Updates 'm_case*' members. Reports warnings.
void analyzeCase(AstCase* nodep) {
// Reset all analysis results
m_caseOpaque = false;
m_caseHasDefault = false;
m_caseNCaseItems = 0;
m_caseNConditions = 0;
m_caseDecoderRecords.clear();
m_caseDecoderEntryWidth = 0;
m_caseTableWidth = 0;
m_caseLhsRecords.clear();
m_caseDetailsValid = false;
AstNode* const caseExprp = nodep->exprp();
@@ -378,14 +569,44 @@ class CaseVisitor final : public VNVisitor {
// 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
// Gather pre-default assignments of decoder pattern
{
// AstVarScope::user1() -> bool: true if written to
const VNUser1InUse user1InUse;
for (AstNode* prevp = nodep->prevp(); prevp; prevp = prevp->prevp()) {
AstNodeAssign* const assp = checkDecoderAssign(prevp);
if (!assp) break; // Stop if not a decoder assignment
// Stop if multiple assignments
const bool multipleAssignments = assp->lhsp()->exists([&](AstVarRef* refp) { //
return refp->varScopep()->user1SetOnce();
});
if (multipleAssignments) break;
// Store pre-default assignment
LhsRecord& lhsRecord = m_caseLhsRecords[*assp->lhsp()];
lhsRecord.lhsp = assp->lhsp();
lhsRecord.preDefaultp = assp;
}
}
// Check each case item
bool canBeDecoder = true;
for (AstCaseItem* cip = nodep->itemsp(); cip; cip = VN_AS(cip->nextp(), CaseItem)) {
// Check conditions
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;
if (!VN_IS(condp, Const)) {
m_caseOpaque = true;
canBeDecoder = false; // Can't be a decoder if opaque
}
}
// Check if it has a default case
if (cip->isDefault()) m_caseHasDefault = true;
// Count case items
++m_caseNCaseItems;
// Check if it fits the decoder pattern, if still possible
if (canBeDecoder) canBeDecoder = analyzeDecoderCaseItem(cip);
}
// Nothing else to do if not a packed type, or non-const conditions
@@ -393,6 +614,135 @@ class CaseVisitor final : public VNVisitor {
// If small enough, analyse details
if (caseExprp->width() <= CASE_DETAILS_MAX_WIDTH) analyzeCaseDetails(nodep);
// Check if it actually fits a full decoder pattern
if (canBeDecoder) analyzeDecoderPattern(nodep);
}
AstNodeStmt* convertCaseTable(AstCase* nodep) {
// Create the table constant
FileLine* const flp = nodep->fileline();
AstConst* const tablep
= new AstConst{flp, AstConst::WidthedValue{}, static_cast<int>(m_caseTableWidth), 0};
const uint32_t tableEntries = 1U << nodep->exprp()->width();
// Populate the table
for (const LhsRecord& lhsRecord : m_caseDecoderRecords) {
const int lhsWidth = lhsRecord.lhsp->width();
const int lhsOffset = lhsRecord.offset;
// Broadcast the pre-default assignment
if (lhsRecord.preDefaultp) {
AstConst* const rhsp = VN_AS(lhsRecord.preDefaultp->rhsp(), Const);
for (uint32_t index = 0; index < tableEntries; ++index) {
const uint32_t tableOffset = index * m_caseDecoderEntryWidth + lhsOffset;
tablep->num().opSelInto(rhsp->num(), tableOffset, lhsWidth);
}
}
// Populate table based on each case item. In reverse order so earlier items win
for (AstCaseItem* cip = VN_AS(nodep->itemsp()->lastp(), CaseItem); cip;
cip = VN_AS(cip->prevp(), CaseItem)) {
// Find the RHS in this case
AstConst* const rhsp = [&]() -> AstConst* {
for (AstNode* stmtp = cip->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
AstNodeAssign* const ap = VN_AS(stmtp, NodeAssign);
if (lhsRecord.lhsp->sameTree(ap->lhsp())) return VN_AS(ap->rhsp(), Const);
}
// Not assigned in this case, use the pre-assigned default
return VN_AS(lhsRecord.preDefaultp->rhsp(), Const);
}();
// If default, broadcast it
if (cip->isDefault()) {
for (uint32_t index = 0; index < tableEntries; ++index) {
const uint32_t tableOffset = index * m_caseDecoderEntryWidth + lhsOffset;
tablep->num().opSelInto(rhsp->num(), tableOffset, lhsWidth);
}
continue;
}
// Iterate case conditions in reverse order
for (AstConst* condp = VN_AS(cip->condsp()->lastp(), Const); condp;
condp = VN_AS(condp->prevp(), Const)) {
if (neverItem(nodep, condp)) continue; // If item never matches, ignore it
const auto& match = matchPattern(nodep, condp);
const uint32_t matchMask = match.first.toUInt();
const uint32_t matchBits = match.second.toUInt();
const uint32_t inverseMask = ~matchMask & ((1U << condp->width()) - 1);
// This iterates through all integers that are a subset of the inverse mask,
// i.e.: all don't care values masked out
for (uint32_t i = inverseMask; true; i = (i - 1) & inverseMask) {
const uint32_t index = i | matchBits;
const uint32_t tableOffset = index * m_caseDecoderEntryWidth + lhsOffset;
tablep->num().opSelInto(rhsp->num(), tableOffset, lhsWidth);
if (!i) break;
}
}
}
}
// Create the table in the constant pool, unless using an inline table
AstVarScope* const tableVscp = [&]() -> AstVarScope* {
if (m_caseTableWidth <= CASE_TABLE_TINY_BITS) {
++m_stats.caseTableTiny;
return nullptr;
}
++m_stats.caseTableNormal;
AstVarScope* vscp = v3Global.rootp()->constPoolp()->findConst(tablep, true);
VL_DO_DANGLING(tablep->deleteTree(), tablep); // findConst clones
return vscp;
}();
// Create the lookup table reference and index
AstNodeExpr* const tableRefp
= tableVscp ? static_cast<AstNodeExpr*>(new AstVarRef{flp, tableVscp, VAccess::READ})
: static_cast<AstNodeExpr*>(tablep);
AstNodeExpr* const caseExprp
= new AstExtend{flp, nodep->exprp()->cloneTreePure(false), 32};
AstNodeExpr* const scalep
= new AstConst{flp, static_cast<uint32_t>(m_caseDecoderEntryWidth)};
AstNodeExpr* const tableLsbp = new AstMul{flp, scalep, caseExprp};
// If there is only one LHS, just use the result
if (m_caseDecoderRecords.size() == 1) {
const LhsRecord& lhsRecord = m_caseDecoderRecords[0];
const int width = lhsRecord.lhsp->width();
AstNodeExpr* const rhsp = new AstSel{flp, tableRefp, tableLsbp, width};
AstNodeExpr* const lhsp = lhsRecord.lhsp->cloneTreePure(false);
if (lhsRecord.nCaseAssigns) {
return new AstAssign{flp, lhsp, rhsp};
} else if (lhsRecord.nCaseAssignDlys) {
return new AstAssignDly{flp, lhsp, rhsp};
} else {
nodep->v3fatalSrc("Unknown assignment type");
}
}
// There are multiple LHSs, store the lookup result in a temporary
const std::string name = "__VcaseTableOut" + std::to_string(m_nTmps++);
AstVarScope* const tempVscp = m_scopep->createTemp(name, m_caseDecoderEntryWidth);
AstNodeExpr* const tempWritep = new AstVarRef{flp, tempVscp, VAccess::WRITE};
AstNodeExpr* const tableSelp
= new AstSel{flp, tableRefp, tableLsbp, static_cast<int>(m_caseDecoderEntryWidth)};
AstNodeStmt* const resultp = new AstAssign{flp, tempWritep, tableSelp};
// For each LHS, select out the result
for (const LhsRecord& lhsRecord : m_caseDecoderRecords) {
const int width = lhsRecord.lhsp->width();
const int lsb = lhsRecord.offset;
AstNodeExpr* const tempReadp = new AstVarRef{flp, tempVscp, VAccess::READ};
AstNodeExpr* const rhsp = new AstSel{flp, tempReadp, lsb, width};
AstNodeExpr* const lhsp = lhsRecord.lhsp->cloneTreePure(false);
if (lhsRecord.nCaseAssigns) {
resultp->addNext(new AstAssign{flp, lhsp, rhsp});
} else if (lhsRecord.nCaseAssignDlys) {
resultp->addNext(new AstAssignDly{flp, lhsp, rhsp});
} else {
nodep->v3fatalSrc("Unknown assignment type");
}
}
return resultp;
}
// TODO: should return AstNodeStmt after #6280
@@ -443,7 +793,8 @@ class CaseVisitor final : public VNVisitor {
// -> tree of IF(msb, IF(msb-1, 11, 10)
// IF(msb-1, 01, 00))
// TODO: should return AstNodeStmt after #6280
AstNode* convertCaseFast(AstCase* nodep) const {
AstNode* convertCaseFast(AstCase* nodep) {
++m_stats.caseFast;
const int caseWidth = nodep->exprp()->width();
AstNode* const ifrootp = convertCaseFastRecurse(nodep->exprp(), caseWidth - 1, 0UL);
return ifrootp && ifrootp->backp() ? ifrootp->cloneTree(true) : ifrootp;
@@ -455,7 +806,8 @@ class CaseVisitor final : public VNVisitor {
// IF((EQ (AND MASK cexpr) (AND MASK icond1)
// ,istmts2, istmts3
// TODO: should return AstNodeStmt after #6280
AstNode* convertCaseGeneric(AstCase* nodep) const {
AstNode* convertCaseGeneric(AstCase* nodep) {
++m_stats.caseGeneric;
// We'll do this in two stages.
// First stage, convert the conditions to the appropriate IF AND terms.
bool hasDefault = false;
@@ -522,7 +874,8 @@ class CaseVisitor final : public VNVisitor {
// 'Or' new term with previous terms
newCondp = newCondp ? new AstLogOr{flp, newCondp, termp} : termp;
}
// Replace expression in tree. Needs to be non-null, so add a constant false if needed
// 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);
}
@@ -591,11 +944,31 @@ class CaseVisitor final : public VNVisitor {
// Convert the given case statement to a representation not using AstCase
// TODO: should return AstNodeStmt after #6280
AstNode* convertCase(AstCase* nodep, Stats& stats) const {
AstNode* convertCase(AstCase* nodep) {
// Determine if we should use the lookup table method
const bool useTable = [&]() {
// Not if disabled
if (!v3Global.opt.fCaseTable()) return false;
// Not if analysis tells us we can't
if (!m_caseTableWidth) return false;
// Always if tiny - it is materialized inline, so there is no load to amortize
if (m_caseTableWidth <= CASE_TABLE_TINY_BITS) return true;
// For a normal (constant-pool) table, weigh the indexed load against the branch
// lowering it would replace. That lowering's depth is bounded by the selector
// width (a balanced bit tree tests ~one bit per level) and by the number of
// distinct values (a generic if/else does ~one compare per value). A few compares
// are cheaper than a load that is likely to be a cache miss, so only table once that
// depth is exceeded.
const size_t branches = std::min<size_t>(nodep->exprp()->width(), m_caseNConditions);
if (branches < CASE_TABLE_MIN_BRANCHES) return false;
return true;
}();
if (useTable) return convertCaseTable(nodep);
// Determine if we should use the fast bitwise branching tree method
const bool useFastBitTree = [&]() {
// Not if disabled
if (!v3Global.opt.fCase()) return false;
if (!v3Global.opt.fCaseTree()) return false;
// Can't do it without the detailed analysis
if (!m_caseDetailsValid) return false;
// Can't do it if not exhaustive
@@ -608,13 +981,9 @@ class CaseVisitor final : public VNVisitor {
// Otherwise use the bit tree
return true;
}();
if (useFastBitTree) {
++stats.caseFast;
return convertCaseFast(nodep);
}
if (useFastBitTree) 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)) {
@@ -650,14 +1019,20 @@ class CaseVisitor final : public VNVisitor {
}
// Convert the case statement and replace the original
if (AstNode* const replacementp = convertCase(nodep, m_stats)) {
if (AstNode* const replacementp = convertCase(nodep)) {
nodep->replaceWith(replacementp);
} else {
nodep->unlinkFrBack();
}
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}
//--------------------
void visit(AstScope* nodep) override {
VL_RESTORER(m_scopep);
m_scopep = nodep;
iterateChildren(nodep);
}
void visit(AstAlways* nodep) override {
VL_RESTORER(m_alwaysp);
m_alwaysp = nodep;
@@ -669,12 +1044,16 @@ public:
// CONSTRUCTORS
explicit CaseVisitor(AstNetlist* nodep) { iterate(nodep); }
~CaseVisitor() override {
V3Stats::addStat("Optimizations, Cases table normal", m_stats.caseTableNormal);
V3Stats::addStat("Optimizations, Cases table tiny", m_stats.caseTableTiny);
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);
}
};
size_t CaseVisitor::LhsRecord::s_nextId = 0;
//######################################################################
// Case class functions