1208 lines
57 KiB
C++
1208 lines
57 KiB
C++
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
//*************************************************************************
|
|
// DESCRIPTION: Verilator: Break case statements up and add Unknown assigns
|
|
//
|
|
// 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;
|
|
|
|
// 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); }
|
|
|
|
// CONSTRUCTORS
|
|
explicit CaseLintVisitor(AstCase* nodep) { iterateConst(nodep); }
|
|
explicit CaseLintVisitor(AstGenCase* nodep) { iterateConst(nodep); }
|
|
~CaseLintVisitor() override = default;
|
|
|
|
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;
|
|
// 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
|
|
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)
|
|
};
|
|
|
|
// 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 caseDecoder; // Cases using decoder method
|
|
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;
|
|
} 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;
|
|
}
|
|
|
|
// 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) {
|
|
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;
|
|
}
|
|
|
|
// 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();
|
|
bool fullyCovered = true;
|
|
string missingItems;
|
|
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();
|
|
|
|
// 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
|
|
if (!missingItems.empty()) missingItems += ", ";
|
|
missingItems += eip->prettyNameQ();
|
|
fullyCovered = false;
|
|
break;
|
|
}
|
|
}
|
|
if (!missingItems.empty() && !nodep->unique0Pragma()) {
|
|
nodep->v3warn(CASEINCOMPLETE, "Enum item(s) not covered by case: " << missingItems);
|
|
}
|
|
return fullyCovered; // enum is fully covered
|
|
}
|
|
|
|
// 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
|
|
if (!nodep->unique0Pragma()) {
|
|
nodep->v3warn(CASEINCOMPLETE,
|
|
"Case values incompletely covered (example pattern 0x" << std::hex
|
|
<< i << ")");
|
|
}
|
|
// TODO: warn for more than one uncovered case, not just the first
|
|
return false;
|
|
}
|
|
|
|
// It's an exhaustive case statement
|
|
return true;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
// 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;
|
|
bool hasDefault = false;
|
|
m_caseDetails.noOverlaps = true;
|
|
for (AstCaseItem* itemp = nodep->itemsp(); itemp;
|
|
itemp = VN_AS(itemp->nextp(), CaseItem)) {
|
|
|
|
// 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];
|
|
if (!caseRecord.itemp) {
|
|
caseRecord.itemp = itemp;
|
|
caseRecord.stmtsp = itemp->stmtsp();
|
|
}
|
|
}
|
|
hasDefault = true;
|
|
continue;
|
|
}
|
|
|
|
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();
|
|
|
|
bool foundNewCase = false;
|
|
const AstConst* firstOverlapConstp = nullptr;
|
|
uint32_t firstOverlapValue = 0;
|
|
for (uint32_t i = 0; i < numValues; ++i) {
|
|
if ((i & mask) != bits) continue;
|
|
|
|
CaseRecord& caseRecord = m_caseDetails.records[i];
|
|
|
|
// 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();
|
|
foundNewCase = true;
|
|
continue;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
// Only report first overlap
|
|
if (reportedOverlap || !firstOverlapConstp) continue;
|
|
|
|
// Report first overlap
|
|
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) {
|
|
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;
|
|
}
|
|
} 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
|
|
<< ")";
|
|
}
|
|
iconstp->v3warn(CASEOVERLAP,
|
|
"Case conditions overlap"
|
|
<< examplePattern.str() << "\n"
|
|
<< iconstp->warnContextPrimary() << '\n'
|
|
<< firstOverlapConstp->warnOther()
|
|
<< "... Location of overlapping condition\n"
|
|
<< firstOverlapConstp->warnContextSecondary());
|
|
reportedOverlap = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If there was no default, check exhaustiveness
|
|
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;
|
|
}
|
|
// Check the table fits max size
|
|
const size_t alignedEntryWidth = VL_WORDS_I(m_caseDecoderEntryWidth) * VL_EDATASIZE;
|
|
if (fitsLimit(alignedEntryWidth, CASE_TABLE_MAX_BITS)) {
|
|
m_caseTableWidth = alignedEntryWidth << caseWidth; // Can optimize
|
|
return;
|
|
}
|
|
|
|
// Can optimize as AstMatchMasked, no other info needed
|
|
}
|
|
|
|
// 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();
|
|
|
|
// Mark opaque if not a packed value - TODO: can this be a class?
|
|
if (caseExprp->isDouble() || caseExprp->isString()) m_caseOpaque = true;
|
|
|
|
// 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 that can actually match.
|
|
if (!neverItem(nodep, VN_AS(condp, NodeExpr))) ++m_caseNConditions;
|
|
// Mark opaque if non-constant condition
|
|
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
|
|
if (m_caseOpaque) return;
|
|
|
|
// 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* connectDecoderOutputs(AstCase* nodep, AstNodeExpr* exprp,
|
|
const char* tmpPrefixp) {
|
|
FileLine* const flp = nodep->fileline();
|
|
|
|
// 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
|
|
= exprp->width() == width ? exprp : new AstSel{flp, exprp, 0, 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 = tmpPrefixp + std::to_string(m_nTmps++);
|
|
AstVarScope* const tempVscp = m_scopep->createTemp(name, m_caseDecoderEntryWidth);
|
|
AstNodeExpr* const tempWritep = new AstVarRef{flp, tempVscp, VAccess::WRITE};
|
|
AstNodeStmt* const resultp = new AstAssign{flp, tempWritep, exprp};
|
|
|
|
// 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;
|
|
}
|
|
|
|
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();
|
|
|
|
const bool isTinyTable = m_caseTableWidth <= CASE_TABLE_TINY_BITS;
|
|
if (isTinyTable) {
|
|
++m_stats.caseTableTiny;
|
|
} else {
|
|
++m_stats.caseTableNormal;
|
|
}
|
|
|
|
// Populate the table. Align entries to a word boundary to avoid bit swizzling at runtime.
|
|
const uint32_t entryWidth = isTinyTable
|
|
? m_caseDecoderEntryWidth
|
|
: VL_WORDS_I(m_caseDecoderEntryWidth) * VL_EDATASIZE;
|
|
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 * entryWidth + 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 * entryWidth + 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 * entryWidth + 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 (isTinyTable) return nullptr;
|
|
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, entryWidth};
|
|
AstNodeExpr* const tableLsbp = new AstMul{flp, scalep, caseExprp};
|
|
AstNodeExpr* const tableSelp
|
|
= new AstSel{flp, tableRefp, tableLsbp, static_cast<int>(m_caseDecoderEntryWidth)};
|
|
|
|
// Connect outputs
|
|
return connectDecoderOutputs(nodep, tableSelp, "__VcaseTableOut");
|
|
}
|
|
|
|
AstNodeStmt* convertCaseDecoder(AstCase* nodep) {
|
|
++m_stats.caseDecoder;
|
|
|
|
FileLine* const flp = nodep->fileline();
|
|
|
|
// Gather all the case conditions, paird with their statements. A 'nullptr' condition
|
|
// matches anything (the default case, or the catch-all added below).
|
|
std::vector<std::pair<AstConst*, AstNode*>> clauses; // (condition, item statements)
|
|
clauses.reserve(m_caseNConditions + 1);
|
|
for (AstCaseItem* cip = nodep->itemsp(); cip; cip = VN_AS(cip->nextp(), CaseItem)) {
|
|
if (cip->isDefault()) {
|
|
clauses.emplace_back(nullptr, cip->stmtsp());
|
|
continue;
|
|
}
|
|
for (AstNode* condp = cip->condsp(); condp; condp = condp->nextp()) {
|
|
AstConst* const iconstp = VN_AS(condp, Const);
|
|
// Skip items that can never match in 2-state simulation (e.g. X in casez)
|
|
if (neverItem(nodep, iconstp)) continue;
|
|
clauses.emplace_back(iconstp, cip->stmtsp());
|
|
}
|
|
}
|
|
// If the case has no default item and is not provably exhaustive, unmatched selector
|
|
// values fall back to the pre-defaults. Represent that with a catch-all clause (null
|
|
// condition and no statements, so every LHS uses its pre-default). 'analyzeDecoderPattern'
|
|
// guarantees every LHS has a pre-default in this case.
|
|
const bool provenExhaustive = m_caseDetailsValid && m_caseDetails.exhaustive
|
|
&& !m_caseDetails.exhaustiveOverEnumOnly;
|
|
if (clauses.back().first && !provenExhaustive) clauses.emplace_back(nullptr, nullptr);
|
|
|
|
// Number of entries in decoder table
|
|
const int decoderEnries = clauses.size();
|
|
|
|
// Build the match table: a {matchBits, matchMask} packed pair per clause, shared by all
|
|
// LHSs. Each field is rounded up to a whole EDATA word boundary to avoid bit swizzling
|
|
// at runtime. We use a packed value so the runtime function can take a VlWide pointer
|
|
// without templating on the array size.
|
|
const int condWidth = nodep->exprp()->width();
|
|
const int matchWidth = 2 * VL_WORDS_I(condWidth) * VL_EDATASIZE;
|
|
AstConst* const matchp
|
|
= new AstConst{flp, AstConst::WidthedValue{}, decoderEnries * matchWidth, 0};
|
|
for (int i = 0; i < decoderEnries; ++i) {
|
|
const int entryLsb = i * matchWidth;
|
|
|
|
// If the entry has a condition, use it's match bits and mask
|
|
if (AstConst* const condp = clauses[i].first) {
|
|
const auto& match = matchPattern(nodep, condp);
|
|
matchp->num().opSelInto(match.first, entryLsb, condWidth);
|
|
matchp->num().opSelInto(match.second, entryLsb + matchWidth / 2, condWidth);
|
|
continue;
|
|
}
|
|
|
|
// Otherwise use zero for mask and bits, which matches anything
|
|
V3Number numZero{flp, condWidth, 0};
|
|
matchp->num().opSelInto(numZero, entryLsb, condWidth);
|
|
matchp->num().opSelInto(numZero, entryLsb + matchWidth / 2, condWidth);
|
|
}
|
|
|
|
// Create the table initializer
|
|
AstRange* const rangep = new AstRange{flp, decoderEnries - 1, 0};
|
|
AstNodeDType* const entryDtypep = nodep->findBitDType(
|
|
m_caseDecoderEntryWidth, m_caseDecoderEntryWidth, VSigning::UNSIGNED);
|
|
AstNodeDType* const tableDtypep = new AstUnpackArrayDType{flp, entryDtypep, rangep};
|
|
v3Global.rootp()->typeTablep()->addTypesp(tableDtypep);
|
|
AstInitArray* const tablep = new AstInitArray{flp, tableDtypep, nullptr};
|
|
|
|
// Build a single value table for all LHSs: one entry per clause, packing each LHS's value
|
|
// at its offset. The entry width is the table packing computed by 'analyzeDecoderPattern'
|
|
// Rounded up to a whole EDATA word boundary to avoid bit swizzling at runtime.
|
|
for (int i = 0; i < decoderEnries; ++i) {
|
|
AstNode* const stmtsp = clauses[i].second;
|
|
AstConst* const entryp = new AstConst{flp, AstConst::WidthedValue{},
|
|
static_cast<int>(m_caseDecoderEntryWidth), 0};
|
|
for (const LhsRecord& lhsRecord : m_caseDecoderRecords) {
|
|
AstNodeExpr* const lhsp = lhsRecord.lhsp;
|
|
// Find the value assigned to this LHS in the clause's statements
|
|
AstConst* valConstp = nullptr;
|
|
for (AstNode* stmtp = stmtsp; stmtp; stmtp = stmtp->nextp()) {
|
|
AstNodeAssign* const assignp = VN_AS(stmtp, NodeAssign);
|
|
if (!lhsp->sameTree(assignp->lhsp())) continue;
|
|
valConstp = VN_AS(assignp->rhsp(), Const);
|
|
break;
|
|
}
|
|
// Not assigned in this clause, so use the pre-assigned default
|
|
if (!valConstp) {
|
|
UASSERT_OBJ(lhsRecord.preDefaultp, nodep,
|
|
"Decoder LHS unassigned in case item without a pre-default");
|
|
valConstp = VN_AS(lhsRecord.preDefaultp->rhsp(), Const);
|
|
}
|
|
entryp->num().opSelInto(valConstp->num(), lhsRecord.offset, lhsp->width());
|
|
}
|
|
tablep->addIndexValuep(i, entryp);
|
|
}
|
|
|
|
// Create the tables
|
|
AstVarScope* const matchVscp = v3Global.rootp()->constPoolp()->findConst(matchp, true);
|
|
AstVarScope* const tableVscp = v3Global.rootp()->constPoolp()->findTable(tablep);
|
|
VL_DO_DANGLING(matchp->deleteTree(), matchp);
|
|
VL_DO_DANGLING(tablep->deleteTree(), tablep);
|
|
|
|
// AstMatchMasked produces the index of the matching entry
|
|
AstNodeExpr* const tableRefp = new AstVarRef{flp, tableVscp, VAccess::READ};
|
|
AstNodeExpr* const caseExprp = nodep->exprp()->cloneTreePure(false);
|
|
AstMatchMasked* const indexp = new AstMatchMasked{flp, caseExprp, matchVscp};
|
|
AstNodeExpr* const entryp = new AstArraySel{flp, tableRefp, indexp};
|
|
|
|
return connectDecoderOutputs(nodep, entryp, "__VcaseDecoderOut");
|
|
}
|
|
|
|
// TODO: should return AstNodeStmt after #6280
|
|
AstNode* convertCaseFastRecurse(AstNodeExpr* cexprp, int msb, uint32_t upperValue) const {
|
|
// 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;
|
|
|
|
// 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);
|
|
|
|
// 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;
|
|
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;
|
|
}
|
|
}
|
|
|
|
// 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))
|
|
// TODO: should return AstNodeStmt after #6280
|
|
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;
|
|
}
|
|
|
|
// 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
|
|
// TODO: should return AstNodeStmt after #6280
|
|
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;
|
|
for (AstCaseItem* itemp = nodep->itemsp(); itemp;
|
|
itemp = VN_AS(itemp->nextp(), CaseItem)) {
|
|
|
|
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;
|
|
}
|
|
|
|
// 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;
|
|
VL_DO_DANGLING2(itemExprp->deleteTree(), itemExprp, itemConstp);
|
|
return AstEq::newTyped(
|
|
flp, //
|
|
new AstConst{flp, matchBits},
|
|
new AstAnd{flp, caseExprp, new AstConst{flp, matchMask}});
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
// 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);
|
|
}
|
|
|
|
// 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) {
|
|
nodep->addItemsp(new AstCaseItem{
|
|
nodep->fileline(), new AstConst{nodep->fileline(), AstConst::BitTrue{}}, nullptr});
|
|
}
|
|
|
|
// Now build the IF statement tree
|
|
// 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)) {
|
|
|
|
// Grab the statements from this item. May be empty.
|
|
AstNode* const istmtsp = itemp->stmtsp();
|
|
if (istmtsp) istmtsp->unlinkFrBackWithNext();
|
|
|
|
// 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);
|
|
itemexprp = new AstConst{itemp->fileline(), AstConst::BitTrue{}};
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
return grouprootp;
|
|
}
|
|
|
|
// Convert the given case statement to a representation not using AstCase
|
|
// TODO: should return AstNodeStmt after #6280
|
|
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 decoder method.
|
|
const bool useDecoder = [&]() {
|
|
// Not if disabled
|
|
if (!v3Global.opt.fCaseDecoder()) return false;
|
|
// Not if not a decoder pattern
|
|
if (m_caseDecoderRecords.empty()) return false;
|
|
// Only worth it once the branch lowering it would replace is deep enough (see
|
|
// useTable)
|
|
const size_t branches = std::min<size_t>(nodep->exprp()->width(), m_caseNConditions);
|
|
if (branches < CASE_TABLE_MIN_BRANCHES) return false;
|
|
return true;
|
|
}();
|
|
if (useDecoder) return convertCaseDecoder(nodep);
|
|
|
|
// Determine if we should use the fast bitwise branching tree method
|
|
const bool useFastBitTree = [&]() {
|
|
// Not if disabled
|
|
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
|
|
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) return convertCaseFast(nodep);
|
|
|
|
// Convert using the generic if/else tree method
|
|
// 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");
|
|
|
|
CaseLintVisitor::apply(nodep);
|
|
|
|
// Convert any children first
|
|
iterateChildren(nodep);
|
|
|
|
// Analyze this case statement
|
|
analyzeCase(nodep);
|
|
|
|
// Take the 'notParallelp' statements under the case statement created by V3Assert.
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
// Convert the case statement and replace the original
|
|
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;
|
|
iterateChildren(nodep);
|
|
}
|
|
void visit(AstNode* nodep) override { iterateChildren(nodep); }
|
|
|
|
public:
|
|
// CONSTRUCTORS
|
|
explicit CaseVisitor(AstNetlist* nodep) { iterate(nodep); }
|
|
~CaseVisitor() override {
|
|
V3Stats::addStat("Optimizations, Cases decoder", m_stats.caseDecoder);
|
|
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
|
|
|
|
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__ << ": ");
|
|
CaseLintVisitor::apply(nodep);
|
|
}
|