// -*- 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-2023 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 "V3Case.h"

#include "V3Ast.h"
#include "V3Global.h"
#include "V3Stats.h"

#include <algorithm>

VL_DEFINE_DEBUG_FUNCTIONS;

#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 VNVisitor {
private:
    const AstNodeCase* m_caseExprp
        = nullptr;  // Under a CASE value node, if so the relevant case statement

    // METHODS

    void visit(AstNodeCase* nodep) override {
        if (VN_IS(nodep, Case) && VN_AS(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_AS(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_AS(itemp->nextp(), CaseItem)) {
                iterateAndNextNull(itemp->condsp());
            }
            m_caseExprp = nullptr;
        }
    }
    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_AS(m_caseExprp, Case)->casex()) {
                // Don't sweat it, we already complained about casex in general
            } else if (VN_IS(m_caseExprp, Case)
                       && (VN_AS(m_caseExprp, Case)->casez()
                           || VN_AS(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)");
            }
        }
    }
    void visit(AstNode* nodep) override { iterateChildren(nodep); }

public:
    // CONSTRUCTORS
    explicit CaseLintVisitor(AstNodeCase* nodep) { iterate(nodep); }
    ~CaseLintVisitor() override = default;
};

//######################################################################
// Case state, as a visitor of each AstNode

class CaseVisitor final : public VNVisitor {
private:
    // NODE STATE
    // Cleared each Case
    //  AstIf::user3()          -> bool.  Set true to indicate clone not needed
    const VNUser3InUse m_inuser3;

    // STATE
    VDouble0 m_statCaseFast;  // Statistic tracking
    VDouble0 m_statCaseSlow;  // Statistic tracking
    const 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<AstNode*, 1 << CASE_OVERLAP_WIDTH> m_valueItem;

    // METHODS
    bool caseIsEnumComplete(AstCase* nodep, uint32_t numCases) {
        // Return true if case is across an enum, and every value in the case
        // statement corresponds to one of the enum values
        if (!nodep->uniquePragma() && !nodep->unique0Pragma()) return false;
        AstEnumDType* const enumDtp
            = VN_CAST(nodep->exprp()->dtypep()->skipRefToEnump(), EnumDType);
        if (!enumDtp) return false;  // Case isn't enum
        AstBasicDType* const basicp = enumDtp->subDTypep()->basicp();
        if (!basicp) return false;  // Not simple type (perhaps IEEE illegal)
        if (basicp->width() > 32) return false;
        // Find all case values into a set
        std::set<uint32_t> caseSet;
        for (uint32_t i = 0; i < numCases; ++i) {  // All case items
            if (m_valueItem[i]) caseSet.emplace(i);
        }
        // Find all enum values into a set
        std::set<uint32_t> enumSet;
        for (AstEnumItem* itemp = enumDtp->itemsp(); itemp;
             itemp = VN_AS(itemp->nextp(), EnumItem)) {
            AstConst* const econstp = VN_AS(itemp->valuep(), Const);
            const uint32_t val = econstp->toUInt();
            // UINFO(9, "Complete enum item " << val << ": " << itemp << endl);
            enumSet.emplace(val);
        }
        // If sets match, all covered
        return (caseSet == enumSet);
    }
    bool isCaseTreeFast(AstCase* nodep) {
        int width = 0;
        bool opaque = false;
        m_caseItems = 0;
        m_caseNoOverlapsAllCovered = true;
        for (AstCaseItem* itemp = nodep->itemsp(); itemp;
             itemp = VN_AS(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);
        const 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_AS(itemp->nextp(), CaseItem)) {
            for (AstNode* icondp = itemp->condsp(); icondp; icondp = icondp->nextp()) {
                // if (debug() >= 9) icondp->dumpTree("-  caseitem: ");
                AstConst* const iconstp = VN_AS(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());
                    const uint32_t mask = nummask.toUInt();
                    V3Number numval{itemp, iconstp->width()};
                    numval.opBitsOne(iconstp->num());
                    const 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) {
                                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;
                }
            }
        }
        if (!caseIsEnumComplete(nodep, numCases)) {
            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) {
            if (AstCaseItem* const itemp = VN_AS(m_valueItem[i], CaseItem)) {
                m_valueItem[i] = itemp->stmtsp();
            }
        }
        return true;  // All is fine
    }

    AstNode* replaceCaseFastRecurse(AstNodeExpr* 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
            AstNode* const foundp = m_valueItem[upperValue];
            return foundp;
        } 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 << static_cast<uint32_t>(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<<msb));
            // AstNode* and1p = new AstAnd(cexprp->fileline(), cexprp->cloneTree(false),
            //                            new AstConst(cexprp->fileline(), nummask));
            AstNodeExpr* const and1p
                = new AstSel{cexprp->fileline(), cexprp->cloneTree(false), msb, 1};
            AstNodeExpr* const eqp
                = new AstNeq{cexprp->fileline(), new AstConst{cexprp->fileline(), 0}, and1p};
            AstIf* const 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))
        AstNodeExpr* const cexprp = nodep->exprp()->unlinkFrBack();

        if (debug() >= 9) {  // LCOV_EXCL_START
            for (uint32_t i = 0; i < (1UL << m_caseWidth); ++i) {
                if (const AstNode* const 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("-    _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
        AstNodeExpr* const 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("-    _comp_IN::: ");
        bool hadDefault = false;
        for (AstCaseItem* itemp = nodep->itemsp(); itemp;
             itemp = VN_AS(itemp->nextp(), CaseItem)) {
            if (!itemp->condsp()) {
                // Default clause.  Just make true, we'll optimize it away later
                itemp->addCondsp(new AstConst{itemp->fileline(), AstConst::BitTrue{}});
                hadDefault = true;
            } else {
                // Expressioned clause
                AstNodeExpr* icondNextp = nullptr;
                AstNodeExpr* ifexprp = nullptr;  // If expression to test
                for (AstNodeExpr* icondp = itemp->condsp(); icondp; icondp = icondNextp) {
                    icondNextp = VN_AS(icondp->nextp(), NodeExpr);
                    icondp->unlinkFrBack();

                    AstNodeExpr* condp = nullptr;  // Default is to use and1p/and2p
                    AstConst* const 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* const 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());
                        AstNodeExpr* const and1p
                            = new AstAnd{itemp->fileline(), cexprp->cloneTree(false),
                                         new AstConst{itemp->fileline(), nummask}};
                        AstNodeExpr* const 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 build CASEEQ(cexpr icond)
                        AstNodeExpr* const and1p = cexprp->cloneTree(false);
                        AstNodeExpr* const 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->addCondsp(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("-    _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_AS(itemp->nextp(), CaseItem)) {
            AstNode* const istmtsp = itemp->stmtsp();  // Maybe null -- no action.
            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->cloneTree(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->cloneTree(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;
            }
        }
        if (debug() >= 9) nodep->dumpTree("-    _comp_TREE: ");
        // Handle any assertions
        replaceCaseParallel(nodep, false);
        // Replace the CASE... with IF...
        if (debug() >= 9 && grouprootp) grouprootp->dumpTree("-     _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* const 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
    void visit(AstCase* nodep) override {
        V3Case::caseLint(nodep);
        iterateChildren(nodep);
        if (debug() >= 9) nodep->dumpTree("-  case_old: ");
        if (isCaseTreeFast(nodep) && v3Global.opt.fCase()) {
            // 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);
        }
    }
    //--------------------
    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);
    }
    ~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{nodep}; }  // Destruct before checking
    V3Global::dumpCheckGlobalTree("case", 0, dumpTree() >= 3);
}
void V3Case::caseLint(AstNodeCase* nodep) {
    UINFO(4, __FUNCTION__ << ": " << endl);
    { CaseLintVisitor{nodep}; }
}
