// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Break case statements up and add Unknown assigns // // Code available from: https://verilator.org // //************************************************************************* // // Copyright 2003-2021 by Wilson Snyder. This program is free software; you // can redistribute it and/or modify it under the terms of either the GNU // Lesser General Public License Version 3 or the Perl Artistic License // Version 2.0. // SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 // //************************************************************************* // 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 "config_build.h" #include "verilatedos.h" #include "V3Global.h" #include "V3Case.h" #include "V3Ast.h" #include "V3Stats.h" #include #define CASE_OVERLAP_WIDTH 16 // Maximum width we can check for overlaps in #define CASE_BARF 999999 // Magic width when non-constant #define CASE_ENCODER_GROUP_DEPTH 8 // Levels of priority to be ORed together in top IF tree //###################################################################### class CaseLintVisitor final : public AstNVisitor { private: AstNodeCase* m_caseExprp = nullptr; // Under a CASE value node, if so the relevant case statement // METHODS VL_DEBUG_FUNC; // Declare debug() virtual void visit(AstNodeCase* nodep) override { if (VN_IS(nodep, Case) && VN_CAST(nodep, Case)->casex()) { nodep->v3warn(CASEX, "Suggest casez (with ?'s) in place of casex (with X's)"); } // Detect multiple defaults bool hitDefault = false; for (AstCaseItem* itemp = nodep->itemsp(); itemp; itemp = VN_CAST(itemp->nextp(), CaseItem)) { if (itemp->isDefault()) { if (hitDefault) { itemp->v3error("Multiple default statements in case statement."); } hitDefault = true; } } // Check for X/Z in non-casex statements { m_caseExprp = nodep; iterate(nodep->exprp()); for (AstCaseItem* itemp = nodep->itemsp(); itemp; itemp = VN_CAST(itemp->nextp(), CaseItem)) { iterateAndNextNull(itemp->condsp()); } m_caseExprp = nullptr; } } virtual void visit(AstConst* nodep) override { // See also neverItem if (m_caseExprp && nodep->num().isFourState()) { if (VN_IS(m_caseExprp, GenCase)) { nodep->v3error("Use of x/? constant in generate case statement, " "(no such thing as 'generate casez')"); } else if (VN_IS(m_caseExprp, Case) && VN_CAST(m_caseExprp, Case)->casex()) { // Don't sweat it, we already complained about casex in general } else if (VN_IS(m_caseExprp, Case) && (VN_CAST(m_caseExprp, Case)->casez() || VN_CAST(m_caseExprp, Case)->caseInside())) { if (nodep->num().isAnyX()) { nodep->v3warn(CASEWITHX, "Use of x constant in casez statement, " "(perhaps intended ?/z in constant)"); } } else { nodep->v3warn(CASEWITHX, "Use of x/? constant in case statement, " "(perhaps intended casex/casez)"); } } } virtual void visit(AstNode* nodep) override { iterateChildren(nodep); } public: // CONSTRUCTORS explicit CaseLintVisitor(AstNodeCase* nodep) { iterate(nodep); } virtual ~CaseLintVisitor() override = default; }; //###################################################################### // Case state, as a visitor of each AstNode class CaseVisitor final : public AstNVisitor { private: // NODE STATE // Cleared each Case // AstIf::user3() -> bool. Set true to indicate clone not needed AstUser3InUse m_inuser3; // STATE VDouble0 m_statCaseFast; // Statistic tracking VDouble0 m_statCaseSlow; // Statistic tracking AstNode* m_alwaysp = nullptr; // Always in which case is located // Per-CASE int m_caseWidth = 0; // Width of valueItems int m_caseItems = 0; // Number of caseItem unique values bool m_caseNoOverlapsAllCovered = false; // Proven to be synopsys parallel_case compliant // For each possible value, the case branch we need std::array m_valueItem; // METHODS VL_DEBUG_FUNC; // Declare debug() bool isCaseTreeFast(AstCase* nodep) { int width = 0; bool opaque = false; m_caseItems = 0; m_caseNoOverlapsAllCovered = true; for (AstCaseItem* itemp = nodep->itemsp(); itemp; itemp = VN_CAST(itemp->nextp(), CaseItem)) { for (AstNode* icondp = itemp->condsp(); icondp; icondp = icondp->nextp()) { if (icondp->width() > width) width = icondp->width(); if (icondp->isDouble()) opaque = true; if (!VN_IS(icondp, Const)) width = CASE_BARF; // Can't parse; not a constant m_caseItems++; } } m_caseWidth = width; if (width == 0 || width > CASE_OVERLAP_WIDTH || opaque) { m_caseNoOverlapsAllCovered = false; return false; // Too wide for analysis } UINFO(8, "Simple case statement: " << nodep << endl); uint32_t numCases = 1UL << m_caseWidth; // Zero list of items for each value for (uint32_t i = 0; i < numCases; ++i) m_valueItem[i] = 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 reportedSubcase = false; for (AstCaseItem* itemp = nodep->itemsp(); itemp; itemp = VN_CAST(itemp->nextp(), CaseItem)) { for (AstNode* icondp = itemp->condsp(); icondp; icondp = icondp->nextp()) { // if (debug() >= 9) icondp->dumpTree(cout, " caseitem: "); AstConst* iconstp = VN_CAST(icondp, Const); UASSERT_OBJ(iconstp, nodep, "above 'can't parse' should have caught this"); if (neverItem(nodep, iconstp)) { // X in casez can't ever be executed } else { V3Number nummask(itemp, iconstp->width()); nummask.opBitsNonX(iconstp->num()); uint32_t mask = nummask.toUInt(); V3Number numval(itemp, iconstp->width()); numval.opBitsOne(iconstp->num()); uint32_t val = numval.toUInt(); uint32_t firstOverlap = 0; bool foundOverlap = false; bool foundHit = false; for (uint32_t i = 0; i < numCases; ++i) { if ((i & mask) == val) { if (!m_valueItem[i]) { m_valueItem[i] = itemp; foundHit = true; } else if (!foundOverlap && !itemp->ignoreOverlap()) { firstOverlap = i; foundOverlap = true; m_caseNoOverlapsAllCovered = false; } } } if (!nodep->priorityPragma()) { // If this case statement doesn't have the priority // keyword, we want to warn on any overlap. if (!reportedOverlap && foundOverlap) { icondp->v3warn(CASEOVERLAP, "Case values overlap (example pattern 0x" << std::hex << firstOverlap << ")"); reportedOverlap = true; } } else { // 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 foundHit is // false. if (!reportedSubcase && !foundHit) { icondp->v3warn(CASEOVERLAP, "Case item ignored: every matching value is covered " "by an earlier item"); reportedSubcase = true; } } } } // Defaults were moved to last in the caseitem list by V3LinkDot if (itemp->isDefault()) { // Case statement's default... Fill the table for (uint32_t i = 0; i < numCases; ++i) { if (!m_valueItem[i]) m_valueItem[i] = itemp; } } } for (uint32_t i = 0; i < numCases; ++i) { if (!m_valueItem[i]) { nodep->v3warn(CASEINCOMPLETE, "Case values incompletely covered " "(example pattern 0x" << std::hex << i << ")"); m_caseNoOverlapsAllCovered = false; return false; } } if (m_caseItems <= 3 // Avoid e.g. priority expanders from going crazy in expansion || (m_caseWidth >= 8 && (m_caseItems <= (m_caseWidth + 1)))) { return false; // Not worth simplifying } // Convert valueItem from AstCaseItem* to the expression // Not done earlier, as we may now have a nullptr because it's just a ";" NOP branch for (uint32_t i = 0; i < numCases; ++i) { m_valueItem[i] = VN_CAST(m_valueItem[i], CaseItem)->bodysp(); } return true; // All is fine } AstNode* replaceCaseFastRecurse(AstNode* cexprp, int msb, uint32_t upperValue) { if (msb < 0) { // There's no space for a IF. We know upperValue is thus down to a specific // exact value, so just return the tree value // Note can't clone here, as we're going to check for equivalence above return m_valueItem[upperValue]; } else { // Make left and right subtrees // cexpr[msb:lsb] == 1 AstNode* tree0p = replaceCaseFastRecurse(cexprp, msb - 1, upperValue | 0); AstNode* tree1p = replaceCaseFastRecurse(cexprp, msb - 1, upperValue | (1UL << msb)); if (tree0p == tree1p) { // Same logic on both sides, so we can just return one of 'em 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 = upperValue, b = (upperValue | (1UL << msb)); a < (upperValue | (1UL << msb)); a++, b++) { if (m_valueItem[a] != m_valueItem[b]) { same = false; break; } } if (same) { VL_DO_DANGLING(tree1p->deleteTree(), tree1p); return tree0p; } // Must have differing logic, so make a selection // Case expressions can't be linked twice, so clone them if (tree0p && !tree0p->user3()) tree0p = tree0p->cloneTree(true); if (tree1p && !tree1p->user3()) tree1p = tree1p->cloneTree(true); // Alternate scheme if we ever do multiple bits at a time: // V3Number nummask (cexprp, cexprp->width(), (1UL<fileline(), cexprp->cloneTree(false), // new AstConst(cexprp->fileline(), nummask)); AstNode* and1p = new AstSel(cexprp->fileline(), cexprp->cloneTree(false), msb, 1); AstNode* eqp = new AstNeq(cexprp->fileline(), new AstConst(cexprp->fileline(), 0), and1p); AstIf* ifp = new AstIf(cexprp->fileline(), eqp, tree1p, tree0p); ifp->user3(1); // So we don't bother to clone it return ifp; } } void replaceCaseFast(AstCase* nodep) { // CASEx(cexpr,.... // -> tree of IF(msb, IF(msb-1, 11, 10) // IF(msb-1, 01, 00)) AstNode* cexprp = nodep->exprp()->unlinkFrBack(); if (debug() >= 9) { // LCOV_EXCL_START for (uint32_t i = 0; i < (1UL << m_caseWidth); ++i) { if (AstNode* itemp = m_valueItem[i]) { UINFO(9, "Value " << std::hex << i << " " << itemp << endl); } } } // LCOV_EXCL_STOP // Handle any assertions replaceCaseParallel(nodep, m_caseNoOverlapsAllCovered); AstNode::user3ClearTree(); AstNode* ifrootp = replaceCaseFastRecurse(cexprp, m_caseWidth - 1, 0UL); // Case expressions can't be linked twice, so clone them if (ifrootp && !ifrootp->user3()) ifrootp = ifrootp->cloneTree(true); if (ifrootp) { nodep->replaceWith(ifrootp); } else { nodep->unlinkFrBack(); } VL_DO_DANGLING(nodep->deleteTree(), nodep); VL_DO_DANGLING(cexprp->deleteTree(), cexprp); if (debug() >= 9) ifrootp->dumpTree(cout, " _simp: "); } void replaceCaseComplicated(AstCase* nodep) { // 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 AstNode* cexprp = nodep->exprp()->unlinkFrBack(); // We'll do this in two stages. First stage, convert the conditions to // the appropriate IF AND terms. if (debug() >= 9) nodep->dumpTree(cout, " _comp_IN: "); bool hadDefault = false; for (AstCaseItem* itemp = nodep->itemsp(); itemp; itemp = VN_CAST(itemp->nextp(), CaseItem)) { if (!itemp->condsp()) { // Default clause. Just make true, we'll optimize it away later itemp->condsp(new AstConst(itemp->fileline(), AstConst::BitTrue())); hadDefault = true; } else { // Expressioned clause AstNode* icondNextp = nullptr; AstNode* ifexprp = nullptr; // If expression to test for (AstNode* icondp = itemp->condsp(); icondp; icondp = icondNextp) { icondNextp = icondp->nextp(); icondp->unlinkFrBack(); AstNode* condp = nullptr; // Default is to use and1p/and2p AstConst* iconstp = VN_CAST(icondp, Const); if (iconstp && neverItem(nodep, iconstp)) { // X in casez can't ever be executed VL_DO_DANGLING(icondp->deleteTree(), icondp); VL_DANGLING(iconstp); // For simplicity, make expression that is not equal, and let later // optimizations remove it condp = new AstConst(itemp->fileline(), AstConst::BitFalse()); } else if (AstInsideRange* irangep = VN_CAST(icondp, InsideRange)) { // Similar logic in V3Width::visit(AstInside) condp = irangep->newAndFromInside(cexprp, irangep->lhsp()->unlinkFrBack(), irangep->rhsp()->unlinkFrBack()); } else if (iconstp && iconstp->num().isFourState() && (nodep->casex() || nodep->casez() || nodep->caseInside())) { V3Number nummask(itemp, iconstp->width()); nummask.opBitsNonX(iconstp->num()); V3Number numval(itemp, iconstp->width()); numval.opBitsOne(iconstp->num()); AstNode* and1p = new AstAnd(itemp->fileline(), cexprp->cloneTree(false), new AstConst(itemp->fileline(), nummask)); AstNode* and2p = new AstAnd(itemp->fileline(), new AstConst(itemp->fileline(), numval), new AstConst(itemp->fileline(), nummask)); VL_DO_DANGLING(icondp->deleteTree(), icondp); VL_DANGLING(iconstp); condp = AstEq::newTyped(itemp->fileline(), and1p, and2p); } else { // Not a caseX mask, we can simply build CASEEQ(cexpr icond) AstNode* and1p = cexprp->cloneTree(false); AstNode* and2p = icondp; condp = AstEq::newTyped(itemp->fileline(), and1p, and2p); } if (!ifexprp) { ifexprp = condp; } else { ifexprp = new AstLogOr(itemp->fileline(), ifexprp, condp); } } // Replace expression in tree itemp->condsp(ifexprp); } } VL_DO_DANGLING(cexprp->deleteTree(), cexprp); if (!hadDefault) { // If there was no default, add a empty one, this greatly simplifies below code // and constant propagation will just eliminate it for us later. nodep->addItemsp(new AstCaseItem( nodep->fileline(), new AstConst(nodep->fileline(), AstConst::BitTrue()), nullptr)); } if (debug() >= 9) nodep->dumpTree(cout, " _comp_COND: "); // Now build the IF statement tree // The tree can be quite huge. Pull ever 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; AstNode* grouprootp = nullptr; AstIf* groupnextp = nullptr; AstIf* itemnextp = nullptr; for (AstCaseItem* itemp = nodep->itemsp(); itemp; itemp = VN_CAST(itemp->nextp(), CaseItem)) { AstNode* istmtsp = itemp->bodysp(); // Maybe null -- no action. if (istmtsp) istmtsp->unlinkFrBackWithNext(); // Expressioned clause AstNode* 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* newp = new AstIf(itemp->fileline(), ifexprp->cloneTree(true), nullptr, nullptr); if (groupnextp) { groupnextp->addElsesp(newp); } else { grouprootp = newp; } groupnextp = newp; } else { // Continue group, modify if condition to OR in this new condition AstNode* condp = groupnextp->condp()->unlinkFrBack(); groupnextp->condp( new AstOr(ifexprp->fileline(), condp, ifexprp->cloneTree(true))); } } { // Make the new lower IF and attach in the tree AstNode* 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* newp = new AstIf(itemp->fileline(), itemexprp, istmtsp, nullptr); if (itemnextp) { itemnextp->addElsesp(newp); } else { groupnextp->addIfsp(newp); // First in a new group } itemnextp = newp; } } if (debug() >= 9) nodep->dumpTree(cout, " _comp_TREE: "); // Handle any assertions replaceCaseParallel(nodep, false); // Replace the CASE... with IF... if (debug() >= 9 && grouprootp) grouprootp->dumpTree(cout, " _new: "); if (grouprootp) { nodep->replaceWith(grouprootp); } else { nodep->unlinkFrBack(); } VL_DO_DANGLING(nodep->deleteTree(), nodep); } void replaceCaseParallel(AstCase* nodep, bool noOverlapsAllCovered) { // Take the notParallelp tree under the case statement created by V3Assert // If the statement was proven to have no overlaps and all cases // covered, we're done with it. // Else, convert to a normal statement parallel with the case statement. if (nodep->notParallelp() && !noOverlapsAllCovered) { AstNode* parp = nodep->notParallelp()->unlinkFrBackWithNext(); nodep->addNextHere(parp); } } bool neverItem(AstCase* casep, AstConst* itemp) { // Xs in case or casez are impossible due to two state simulations if (casep->casex()) { } else if (casep->casez() || casep->caseInside()) { if (itemp->num().isAnyX()) return true; } else { if (itemp->num().isFourState()) return true; } return false; } // VISITORS virtual void visit(AstCase* nodep) override { V3Case::caseLint(nodep); iterateChildren(nodep); if (debug() >= 9) nodep->dumpTree(cout, " case_old: "); if (isCaseTreeFast(nodep) && v3Global.opt.oCase()) { // It's a simple priority encoder or complete statement // we can make a tree of statements to avoid extra comparisons ++m_statCaseFast; VL_DO_DANGLING(replaceCaseFast(nodep), nodep); } else { // If a case statement is whole, presume signals involved aren't forming a latch if (m_alwaysp) m_alwaysp->fileline()->warnOff(V3ErrorCode::LATCH, true); ++m_statCaseSlow; VL_DO_DANGLING(replaceCaseComplicated(nodep), nodep); } } //-------------------- virtual void visit(AstNode* nodep) override { if (VN_IS(nodep, Always)) m_alwaysp = nodep; iterateChildren(nodep); } public: // CONSTRUCTORS explicit CaseVisitor(AstNetlist* nodep) { for (auto& itr : m_valueItem) itr = nullptr; iterate(nodep); } virtual ~CaseVisitor() override { V3Stats::addStat("Optimizations, Cases parallelized", m_statCaseFast); V3Stats::addStat("Optimizations, Cases complex", m_statCaseSlow); } }; //###################################################################### // Case class functions void V3Case::caseAll(AstNetlist* nodep) { UINFO(2, __FUNCTION__ << ": " << endl); { CaseVisitor visitor(nodep); } // Destruct before checking V3Global::dumpCheckGlobalTree("case", 0, v3Global.opt.dumpTreeLevel(__FILE__) >= 3); } void V3Case::caseLint(AstNodeCase* nodep) { UINFO(4, __FUNCTION__ << ": " << endl); CaseLintVisitor visitor(nodep); }