// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Constant folding // // Code available from: https://verilator.org // //************************************************************************* // // Copyright 2003-2024 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 // //************************************************************************* // CONST TRANSFORMATIONS: // Call on one node for PARAM values, or netlist for overall constant folding: // Bottom up traversal: // Attempt to convert operands to constants // If operands are constant, replace this node with constant. //************************************************************************* #include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT #include "config_build.h" #include "verilatedos.h" #include "V3Const.h" #include "V3Ast.h" #include "V3Global.h" #include "V3Simulate.h" #include "V3Stats.h" #include "V3String.h" #include "V3UniqueNames.h" #include "V3Width.h" #include #include #include VL_DEFINE_DEBUG_FUNCTIONS; #define TREE_SKIP_VISIT(...) #define TREEOP1(...) #define TREEOPA(...) #define TREEOP(...) #define TREEOPS(...) #define TREEOPC(...) #define TREEOPV(...) //###################################################################### // Utilities static bool isConst(const AstNode* nodep, uint64_t v) { const AstConst* const constp = VN_CAST(nodep, Const); return constp && constp->toUQuad() == v; } template static typename std::enable_if::value, bool>::type isPow2(T val) { return (val & (val - 1)) == 0; } static int countTrailingZeroes(uint64_t val) { UASSERT(val, "countTrailingZeroes argument must be non-zero"); #if defined(__GNUC__) && !defined(VL_NO_BUILTINS) return __builtin_ctzll(val); #else int bit = 0; val = ~val; while (val & 1) { ++bit; val >>= 1; } return bit; #endif } // This visitor can be used in the post-expanded Ast from V3Expand, where the Ast satisfies: // - Constants are 64 bit at most (because words are accessed via AstWordSel) // - Variables are scoped. class ConstBitOpTreeVisitor final : public VNVisitorConst { // NODE STATE // AstVarRef::user4u -> Base index of m_varInfos that points VarInfo // AstVarScope::user4u -> Same as AstVarRef::user4 const VNUser4InUse m_inuser4; // TYPES // Holds a node to be added as a term in the reduction tree, it's equivalent op count, and a // bool indicating if the term is clean (0/1 value, or if the top bits might be dirty) using ResultTerm = std::tuple; class LeafInfo final { // Leaf node (either AstConst or AstVarRef) // MEMBERS bool m_polarity = true; int m_lsb = 0; // LSB of actually used bit of m_refp->varp() int m_msb = 0; // MSB of actually used bit of m_refp->varp() int m_wordIdx = -1; // -1 means AstWordSel is not used. AstVarRef* m_refp = nullptr; const AstConst* m_constp = nullptr; public: // CONSTRUCTORS LeafInfo() = default; LeafInfo(const LeafInfo& other) = default; explicit LeafInfo(int lsb) : m_lsb{lsb} {} // METHODS void setLeaf(AstVarRef* refp) { UASSERT_OBJ(!m_refp && !m_constp, refp, "Must be called just once"); m_refp = refp; m_msb = refp->varp()->widthMin() - 1; } void setLeaf(const AstConst* constp) { UASSERT_OBJ(!m_refp && !m_constp, constp, "Must be called just once"); m_constp = constp; m_msb = constp->widthMin() - 1; } void updateBitRange(const AstCCast* castp) { m_msb = std::min(m_msb, m_lsb + castp->width() - 1); } void updateBitRange(const AstShiftR* shiftp) { m_lsb += VN_AS(shiftp->rhsp(), Const)->toUInt(); } int wordIdx() const { return m_wordIdx; } void wordIdx(int i) { m_wordIdx = i; } bool polarity() const { return m_polarity; } void polarity(bool p) { m_polarity = p; } AstVarRef* refp() const { return m_refp; } const AstConst* constp() const { return m_constp; } bool missingWordSel() const { // When V3Expand is skipped, WordSel is not inserted. return m_refp->isWide() && m_wordIdx == -1; } int lsb() const { return m_lsb; } int msb() const { return std::min(m_msb, varWidth() - 1); } int varWidth() const { UASSERT(m_refp, "m_refp should be set"); const int width = m_refp->varp()->widthMin(); if (!m_refp->isWide()) { UASSERT_OBJ(m_wordIdx == -1, m_refp, "Bad word index into non-wide"); return width; } else { if (missingWordSel()) return width; UASSERT_OBJ(m_wordIdx >= 0, m_refp, "Bad word index into wide"); const int bitsInMSW = VL_BITBIT_E(width) ? VL_BITBIT_E(width) : VL_EDATASIZE; return m_wordIdx == m_refp->widthWords() - 1 ? bitsInMSW : VL_EDATASIZE; } } }; struct BitPolarityEntry final { // Found bit polarity during iterate() LeafInfo m_info; bool m_polarity; int m_bit; BitPolarityEntry(const LeafInfo& info, bool pol, int bit) : m_info{info} , m_polarity{pol} , m_bit{bit} {} BitPolarityEntry() = default; }; struct FrozenNodeInfo final { // Context when a frozen node is found bool m_polarity; int m_lsb; bool operator<(const FrozenNodeInfo& other) const { if (m_lsb != other.m_lsb) return m_lsb < other.m_lsb; return m_polarity < other.m_polarity; } }; class Restorer final { // Restore the original state unless disableRestore() is called ConstBitOpTreeVisitor& m_visitor; const size_t m_polaritiesSize; const size_t m_frozenSize; const unsigned m_ops; const bool m_polarity; bool m_restore; public: explicit Restorer(ConstBitOpTreeVisitor& visitor) : m_visitor{visitor} , m_polaritiesSize{visitor.m_bitPolarities.size()} , m_frozenSize{visitor.m_frozenNodes.size()} , m_ops{visitor.m_ops} , m_polarity{visitor.m_polarity} , m_restore{true} {} ~Restorer() { UASSERT(m_visitor.m_bitPolarities.size() >= m_polaritiesSize, "m_bitPolarities must grow monotorilaclly"); UASSERT(m_visitor.m_frozenNodes.size() >= m_frozenSize, "m_frozenNodes must grow monotorilaclly"); if (m_restore) restoreNow(); } void disableRestore() { m_restore = false; } void restoreNow() { UASSERT(m_restore, "Can be called just once"); m_visitor.m_bitPolarities.resize(m_polaritiesSize); m_visitor.m_frozenNodes.resize(m_frozenSize); m_visitor.m_ops = m_ops; m_visitor.m_polarity = m_polarity; m_restore = false; } }; // Collect information for each Variable to transform as below class VarInfo final { // MEMBERS int m_knownResult = -1; // -1: result is not known, 0 or 1: result of this tree const ConstBitOpTreeVisitor* const m_parentp; // ConstBitOpTreeVisitor holding this VarInfo AstVarRef* const m_refp; // Points the variable that this VarInfo covers const int m_width; // Width of term this VarInfo refers to V3Number m_bitPolarity; // Coefficient of each bit public: // METHODS bool hasConstResult() const { return m_knownResult >= 0 || m_bitPolarity.isAllX(); } // The constant result. Only valid if hasConstResult() returned true. bool getConstResult() const { // Note that this condition covers m_knownResult == -1 but m_bitPolarity.isAllX(), // in which case the result is 0 return m_knownResult == 1; } const AstVarRef* refp() const { return m_refp; } bool sameVarAs(const AstNodeVarRef* otherp) const { return m_refp->sameNode(otherp); } void setPolarity(bool compBit, int bit) { // Ignore if already determined a known reduction if (m_knownResult >= 0) return; UASSERT_OBJ(bit < m_width, m_refp, "Bit index out of range: " << bit << " width: " << m_width); if (m_bitPolarity.bitIsX(bit)) { // The bit is not yet marked with either polarity m_bitPolarity.setBit(bit, compBit); } else { // The bit has already been marked with some polarity const bool sameFlag = m_bitPolarity.bitIs1(bit) == compBit; if (m_parentp->isXorTree()) { UASSERT_OBJ(compBit && sameFlag, m_refp, "Only true is set in Xor tree"); // a ^ a ^ b == b so we can ignore a m_bitPolarity.setBit(bit, 'x'); } else { // And, Or // Can ignore this nodep as the bit is already marked with the same polarity if (sameFlag) return; // a & a == a, b | b == b // Otherwise result is constant (a & ~a == 0) or (a | ~a == 1) m_knownResult = m_parentp->isAndTree() ? 0 : 1; m_bitPolarity.setAllBitsX(); // The variable is not referred anymore } } } // Return reduction term for this VarInfo, together with the number of ops in the term, // and a boolean indicating if the term is clean (1-bit vs multi-bit value) ResultTerm getResultTerm() const { UASSERT_OBJ(!hasConstResult(), m_refp, "getTerm on reduction that yields constant"); FileLine* const fl = m_refp->fileline(); // Get the term we are referencing (the WordSel, if wide, otherwise just the VarRef) AstNodeExpr* srcp = VN_CAST(m_refp->backp(), WordSel); if (!srcp) srcp = m_refp; srcp = srcp->cloneTree(false); // Signed variables might have redundant sign bits that need masking. const bool hasRedundantSignBits = m_refp->varp()->dtypep()->isSigned() && (m_refp->isWide() ? (m_width != VL_EDATASIZE) : (m_width < 8 || !isPow2(m_width))); // Get the mask that selects the bits that are relevant in this term V3Number maskNum{srcp, m_width, 0}; maskNum.opBitsNonX(m_bitPolarity); // 'x' -> 0, 0->1, 1->1 const uint64_t maskVal = maskNum.toUQuad(); UASSERT_OBJ(maskVal != 0, m_refp, "Should have been recognized as having const 0 result"); // Parts of the return value AstNodeExpr* resultp = srcp; // The tree for this term unsigned ops = 0; // Number of ops in this term bool clean = false; // Whether the term is clean (has value 0 or 1) if (isPow2(maskVal)) { // If we only want a single bit, shift it out instead of a masked compare. Shifts // don't go through the flags register on x86 and are hence faster. This is also // always fewer or same ops as mask and compare, but with shorter instructions on // x86. // Find the index of the bit we want. const int bit = countTrailingZeroes(maskVal); // If we want something other than the bottom bit, shift it out if (bit != 0) { resultp = new AstShiftR{fl, resultp, new AstConst{fl, static_cast(bit)}, m_width}; ++ops; } // Negate it if necessary const bool negate = m_bitPolarity.bitIs0(bit); if (negate) { resultp = new AstNot{fl, resultp}; ++ops; } // Clean if MSB of unsigned value, and not negated clean = (bit == m_width - 1) && !hasRedundantSignBits && !negate; } else { // We want multiple bits. Go ahead and extract them. // Check if masking is required, and if so apply it const bool needsMasking = maskVal != VL_MASK_Q(m_width) || hasRedundantSignBits; if (needsMasking) { resultp = new AstAnd{fl, new AstConst{fl, maskNum}, resultp}; ++ops; } // Create the sub-expression for this term if (m_parentp->isXorTree()) { if (needsMasking) { // Reduce the masked term to the minimum known width, // to use the smallest RedXor formula const int widthMin = maskNum.widthMin(); resultp->dtypeChgWidth(widthMin, widthMin); } resultp = new AstRedXor{fl, resultp}; ++ops; clean = false; // VL_REDXOR_* returns IData, set width accordingly to avoid unnecessary casts resultp->dtypeChgWidth(VL_IDATASIZE, 1); } else if (m_parentp->isAndTree()) { V3Number compNum{srcp, m_width, 0}; compNum.opBitsOne(m_bitPolarity); // 'x'->0, 0->0, 1->1 resultp = new AstEq{fl, new AstConst{fl, compNum}, resultp}; ++ops; clean = true; } else { // Or V3Number compNum{srcp, m_width, 0}; compNum.opBitsOne(m_bitPolarity); // 'x'->0, 0->0, 1->1 compNum.opXor(V3Number{compNum}, maskNum); resultp = new AstNeq{fl, new AstConst{fl, compNum}, resultp}; ++ops; clean = true; } } return ResultTerm{resultp, ops, clean}; } // CONSTRUCTORS VarInfo(ConstBitOpTreeVisitor* parent, AstVarRef* refp, int width) : m_parentp{parent} , m_refp{refp} , m_width{width} , m_bitPolarity{refp, m_width} { m_bitPolarity.setAllBitsX(); } }; // MEMBERS bool m_failed = false; bool m_polarity = true; // Flip when AstNot comes unsigned m_ops; // Number of operations such as And, Or, Xor, Sel... int m_lsb = 0; // Current LSB LeafInfo* m_leafp = nullptr; // AstConst or AstVarRef that currently looking for const AstNodeExpr* const m_rootp; // Root of this AST subtree std::vector> m_frozenNodes; // Nodes that cannot be optimized std::vector m_bitPolarities; // Polarity of bits found during iterate() std::vector> m_varInfos; // VarInfo for each variable, [0] is nullptr // METHODS bool isAndTree() const { return VN_IS(m_rootp, And); } bool isOrTree() const { return VN_IS(m_rootp, Or); } bool isXorTree() const { return VN_IS(m_rootp, Xor) || VN_IS(m_rootp, RedXor); } #define CONST_BITOP_RETURN_IF(cond, nodep) \ if (setFailed(cond, #cond, nodep, __LINE__)) return #define CONST_BITOP_SET_FAILED(reason, nodep) setFailed(true, reason, nodep, __LINE__) bool setFailed(bool fail, const char* reason, AstNode* nodep, int line) { if (fail && !m_failed) { UINFO(9, "cannot optimize " << m_rootp << " reason:" << reason << " called from line:" << line << " when checking:" << nodep << std::endl); // if (debug() >= 9) m_rootp->dumpTree("- root: "); m_failed = true; } return m_failed; } void incrOps(const AstNode* nodep, int line) { ++m_ops; UINFO(9, "Increment to " << m_ops << " " << nodep << " called from line " << line << "\n"); } VarInfo& getVarInfo(const LeafInfo& ref) { UASSERT_OBJ(ref.refp(), m_rootp, "null varref in And/Or/Xor optimization"); AstNode* nodep = ref.refp()->varScopep(); if (!nodep) nodep = ref.refp()->varp(); // Not scoped int baseIdx = nodep->user4(); if (baseIdx == 0) { // Not set yet baseIdx = m_varInfos.size(); const int numWords = ref.refp()->dtypep()->isWide() ? ref.refp()->dtypep()->widthWords() : 1; m_varInfos.resize(m_varInfos.size() + numWords); nodep->user4(baseIdx); } const size_t idx = baseIdx + std::max(0, ref.wordIdx()); VarInfo* varInfop = m_varInfos[idx].get(); if (!varInfop) { varInfop = new VarInfo{this, ref.refp(), ref.varWidth()}; m_varInfos[idx].reset(varInfop); if (ref.missingWordSel()) { // ConstBitOpTreeVisitor makes some constants for masks and its type is uint64_t. // That's why V3Expand, that inserts WordSel, is needed. CONST_BITOP_SET_FAILED("V3Expand is skipped", ref.refp()); } } else { if (!varInfop->sameVarAs(ref.refp())) CONST_BITOP_SET_FAILED("different var (scope?)", ref.refp()); } return *varInfop; } // Traverse down to see AstConst or AstVarRef LeafInfo findLeaf(AstNode* nodep, bool expectConst) { LeafInfo info{m_lsb}; { VL_RESTORER(m_leafp); m_leafp = &info; iterateConst(nodep); } bool ok = !m_failed; if (expectConst) { ok &= !info.refp() && info.constp(); } else { ok &= info.refp() && !info.constp(); } return ok ? info : LeafInfo{}; } // VISITORS void visit(AstNode* nodep) override { CONST_BITOP_SET_FAILED("Hit unexpected op", nodep); } void visit(AstCCast* nodep) override { iterateChildrenConst(nodep); if (m_leafp) m_leafp->updateBitRange(nodep); } void visit(AstShiftR* nodep) override { CONST_BITOP_RETURN_IF(!m_leafp, nodep); AstConst* const constp = VN_CAST(nodep->rhsp(), Const); CONST_BITOP_RETURN_IF(!constp, nodep->rhsp()); m_lsb += constp->toUInt(); incrOps(nodep, __LINE__); iterateConst(nodep->lhsp()); m_leafp->updateBitRange(nodep); m_lsb -= constp->toUInt(); } void visit(AstNot* nodep) override { CONST_BITOP_RETURN_IF(nodep->widthMin() != 1, nodep); AstNode* lhsp = nodep->lhsp(); AstCCast* const castp = VN_CAST(lhsp, CCast); if (castp) lhsp = castp->lhsp(); CONST_BITOP_RETURN_IF(!isXorTree() && !VN_IS(lhsp, VarRef) && !VN_IS(lhsp, ShiftR), lhsp); incrOps(nodep, __LINE__); m_polarity = !m_polarity; iterateChildrenConst(nodep); // Don't restore m_polarity for Xor as it counts parity of the entire tree if (!isXorTree()) m_polarity = !m_polarity; if (m_leafp && castp) m_leafp->updateBitRange(castp); } void visit(AstWordSel* nodep) override { CONST_BITOP_RETURN_IF(!m_leafp, nodep); AstConst* const constp = VN_CAST(nodep->bitp(), Const); CONST_BITOP_RETURN_IF(!constp, nodep->bitp()); UASSERT_OBJ(m_leafp->wordIdx() == -1, nodep, "Unexpected nested WordSel"); m_leafp->wordIdx(constp->toSInt()); iterateConst(nodep->fromp()); } void visit(AstVarRef* nodep) override { CONST_BITOP_RETURN_IF(!m_leafp, nodep); m_leafp->setLeaf(nodep); m_leafp->polarity(m_polarity); } void visit(AstConst* nodep) override { CONST_BITOP_RETURN_IF(!m_leafp, nodep); m_leafp->setLeaf(nodep); } void visit(AstRedXor* nodep) override { Restorer restorer{*this}; CONST_BITOP_RETURN_IF(!VN_IS(m_rootp, Xor), nodep); AstNode* lhsp = nodep->lhsp(); const AstCCast* const castp = VN_CAST(lhsp, CCast); if (castp) lhsp = castp->lhsp(); if (const AstAnd* const andp = VN_CAST(lhsp, And)) { // '^(mask & leaf)' CONST_BITOP_RETURN_IF(!andp, lhsp); const LeafInfo& mask = findLeaf(andp->lhsp(), true); CONST_BITOP_RETURN_IF(!mask.constp() || mask.lsb() != 0, andp->lhsp()); LeafInfo ref = findLeaf(andp->rhsp(), false); CONST_BITOP_RETURN_IF(!ref.refp(), andp->rhsp()); if (castp) ref.updateBitRange(castp); restorer.disableRestore(); // Now all subtree succeeded const V3Number& maskNum = mask.constp()->num(); incrOps(nodep, __LINE__); incrOps(andp, __LINE__); // Mark all bits checked in this reduction const int maxBitIdx = std::min(ref.lsb() + maskNum.width(), ref.msb() + 1); for (int bitIdx = ref.lsb(); bitIdx < maxBitIdx; ++bitIdx) { const int maskIdx = bitIdx - ref.lsb(); if (maskNum.bitIs0(maskIdx)) continue; // Set true, m_polarity takes care of the entire parity m_bitPolarities.emplace_back(ref, true, bitIdx); } } else { // '^leaf' LeafInfo ref = findLeaf(lhsp, false); CONST_BITOP_RETURN_IF(!ref.refp(), lhsp); if (castp) ref.updateBitRange(castp); restorer.disableRestore(); // Now all checks passed incrOps(nodep, __LINE__); // Mark all bits checked by this comparison for (int bitIdx = ref.lsb(); bitIdx <= ref.msb(); ++bitIdx) { m_bitPolarities.emplace_back(ref, true, bitIdx); } } } void visit(AstNodeBiop* nodep) override { if (VN_IS(nodep, And) && isConst(nodep->lhsp(), 1)) { // 1 & _ // Always reach past a plain making AND Restorer restorer{*this}; incrOps(nodep, __LINE__); iterateConst(nodep->rhsp()); CONST_BITOP_RETURN_IF(m_failed, nodep->rhsp()); restorer.disableRestore(); // Now all checks passed } else if (nodep->type() == m_rootp->type()) { // And, Or, Xor // subtree under NOT can be optimized only in XOR tree. CONST_BITOP_RETURN_IF(!m_polarity && !isXorTree(), nodep); incrOps(nodep, __LINE__); for (const bool right : {false, true}) { VL_RESTORER(m_leafp); Restorer restorer{*this}; LeafInfo leafInfo{m_lsb}; // cppcheck-suppress danglingLifetime m_leafp = &leafInfo; AstNodeExpr* opp = right ? nodep->rhsp() : nodep->lhsp(); const bool origFailed = m_failed; iterateConst(opp); if (leafInfo.constp() || m_failed) { // Revert changes in leaf restorer.restoreNow(); // Reach past a cast then add to frozen nodes to be added to final reduction if (const AstCCast* const castp = VN_CAST(opp, CCast)) opp = castp->lhsp(); const bool pol = isXorTree() || m_polarity; // Only AND/OR tree needs polarity UASSERT(pol, "AND/OR tree expects m_polarity==true"); m_frozenNodes.emplace_back(opp, FrozenNodeInfo{pol, m_lsb}); m_failed = origFailed; continue; } restorer.disableRestore(); // Now all checks passed if (leafInfo.refp()) { // The conditional on the lsb being in range is necessary for some degenerate // case, e.g.: (IData)((QData)wide[0] >> 32), or <1-bit-var> >> 1, which is // just zero if (leafInfo.lsb() <= leafInfo.msb()) { m_bitPolarities.emplace_back(leafInfo, isXorTree() || leafInfo.polarity(), leafInfo.lsb()); } else if ((isAndTree() && leafInfo.polarity()) || (isOrTree() && !leafInfo.polarity())) { // If there is a constant 0 term in an And tree or 1 term in an Or tree, we // must include it. Fudge this by adding a bit with both polarities, which // will simplify to zero or one respectively. // Note that Xor tree does not need this kind of care, polarity of Xor tree // is already cared when visitin AstNot. Taking xor with 1'b0 is nop. m_bitPolarities.emplace_back(leafInfo, true, 0); m_bitPolarities.emplace_back(leafInfo, false, 0); } } } } else if ((isAndTree() && VN_IS(nodep, Eq)) || (isOrTree() && VN_IS(nodep, Neq))) { Restorer restorer{*this}; CONST_BITOP_RETURN_IF(!m_polarity, nodep); CONST_BITOP_RETURN_IF(m_lsb, nodep); // the result of EQ/NE is 1 bit width const AstNode* lhsp = nodep->lhsp(); if (const AstCCast* const castp = VN_CAST(lhsp, CCast)) lhsp = castp->lhsp(); const AstConst* const constp = VN_CAST(lhsp, Const); CONST_BITOP_RETURN_IF(!constp, nodep->lhsp()); const V3Number& compNum = constp->num(); auto setPolarities = [this, &compNum](const LeafInfo& ref, const V3Number* maskp) { const bool maskFlip = isAndTree() ^ ref.polarity(); int constantWidth = compNum.width(); if (maskp) constantWidth = std::max(constantWidth, maskp->width()); const int maxBitIdx = std::max(ref.lsb() + constantWidth, ref.msb() + 1); // Mark all bits checked by this comparison for (int bitIdx = ref.lsb(); bitIdx < maxBitIdx; ++bitIdx) { const int maskIdx = bitIdx - ref.lsb(); const bool mask0 = maskp && maskp->bitIs0(maskIdx); const bool outOfRange = bitIdx > ref.msb(); if (mask0 || outOfRange) { // RHS is 0 if (compNum.bitIs1(maskIdx)) { // LHS is 1 // And tree: 1 == 0 => always false, set v && !v // Or tree : 1 != 0 => always true, set v || !v m_bitPolarities.emplace_back(ref, true, 0); m_bitPolarities.emplace_back(ref, false, 0); break; } else { // This bitIdx is irrelevant continue; } } const bool polarity = compNum.bitIs1(maskIdx) != maskFlip; m_bitPolarities.emplace_back(ref, polarity, bitIdx); } }; if (const AstAnd* const andp = VN_CAST(nodep->rhsp(), And)) { // comp == (mask & v) const LeafInfo& mask = findLeaf(andp->lhsp(), true); CONST_BITOP_RETURN_IF(!mask.constp() || mask.lsb() != 0, andp->lhsp()); const LeafInfo& ref = findLeaf(andp->rhsp(), false); CONST_BITOP_RETURN_IF(!ref.refp(), andp->rhsp()); restorer.disableRestore(); // Now all checks passed const V3Number& maskNum = mask.constp()->num(); incrOps(nodep, __LINE__); incrOps(andp, __LINE__); setPolarities(ref, &maskNum); } else { // comp == v const LeafInfo& ref = findLeaf(nodep->rhsp(), false); CONST_BITOP_RETURN_IF(!ref.refp(), nodep->rhsp()); restorer.disableRestore(); // Now all checks passed incrOps(nodep, __LINE__); setPolarities(ref, nullptr); } } else { CONST_BITOP_SET_FAILED("Mixture of different ops cannot be optimized", nodep); } } // CONSTRUCTORS ConstBitOpTreeVisitor(AstNodeExpr* nodep, unsigned externalOps) : m_ops{externalOps} , m_rootp{nodep} { // Fill nullptr at [0] because AstVarScope::user4 is 0 by default m_varInfos.push_back(nullptr); CONST_BITOP_RETURN_IF(!isAndTree() && !isOrTree() && !isXorTree(), nodep); if (AstNodeBiop* const biopp = VN_CAST(nodep, NodeBiop)) { iterateConst(biopp); } else { UASSERT_OBJ(VN_IS(nodep, RedXor), nodep, "Must be RedXor"); incrOps(nodep, __LINE__); iterateChildrenConst(nodep); } for (auto&& entry : m_bitPolarities) { getVarInfo(entry.m_info).setPolarity(entry.m_polarity, entry.m_bit); } UASSERT_OBJ(isXorTree() || m_polarity, nodep, "must be the original polarity"); } virtual ~ConstBitOpTreeVisitor() = default; #undef CONST_BITOP_RETURN_IF #undef CONST_BITOP_SET_FAILED public: // Transform as below. // v[0] & v[1] => 2'b11 == (2'b11 & v) // v[0] | v[1] => 2'b00 != (2'b11 & v) // v[0] ^ v[1] => ^{2'b11 & v} // (3'b011 == (3'b011 & v)) & v[2] => 3'b111 == (3'b111 & v) // (3'b000 != (3'b011 & v)) | v[2] => 3'b000 != (3'b111 & v) // Reduction ops are transformed in the same way. // &{v[0], v[1]} => 2'b11 == (2'b11 & v) static AstNodeExpr* simplify(AstNodeExpr* nodep, int resultWidth, unsigned externalOps, VDouble0& reduction) { UASSERT_OBJ(1 <= resultWidth && resultWidth <= 64, nodep, "resultWidth out of range"); // Walk tree, gathering all terms referenced in expression const ConstBitOpTreeVisitor visitor{nodep, externalOps}; // If failed on root node is not optimizable, or there are no variable terms, then done if (visitor.m_failed || visitor.m_varInfos.size() == 1) return nullptr; // FileLine used for constructing all new nodes in this function FileLine* const fl = nodep->fileline(); // Get partial result each term referenced, count total number of ops and keep track of // whether we have clean/dirty terms. visitor.m_varInfos appears in deterministic order, // so the optimized tree is deterministic as well. std::vector termps; termps.reserve(visitor.m_varInfos.size() - 1); unsigned resultOps = 0; bool hasCleanTerm = false; bool hasDirtyTerm = false; for (auto&& v : visitor.m_varInfos) { if (!v) continue; // Skip nullptr at m_varInfos[0] if (v->hasConstResult()) { // If a constant term is known, we can either drop it or the whole tree is constant AstNodeExpr* resultp = nullptr; if (v->getConstResult()) { UASSERT_OBJ(visitor.isOrTree(), nodep, "Only OR tree can yield known 1 result"); UINFO(9, "OR tree with const 1 term: " << v->refp() << endl); // Known 1 bit in OR tree, whole result is 1 resultp = new AstConst{fl, AstConst::BitTrue{}}; } else if (visitor.isAndTree()) { UINFO(9, "AND tree with const 0 term: " << v->refp() << endl); // Known 0 bit in AND tree, whole result is 0 resultp = new AstConst{fl, AstConst::BitFalse{}}; } else { // Known 0 bit in OR or XOR tree. Ignore it. continue; } // Set width and widthMin precisely resultp->dtypeChgWidth(resultWidth, 1); for (AstNode* const termp : termps) VL_DO_DANGLING(termp->deleteTree(), termp); return resultp; } const ResultTerm result = v->getResultTerm(); termps.push_back(std::get<0>(result)); resultOps += std::get<1>(result); if (std::get<2>(result)) { hasCleanTerm = true; UINFO(9, "Clean term: " << termps.back() << endl); } else { hasDirtyTerm = true; UINFO(9, "Dirty term: " << termps.back() << endl); } } // Group by FrozenNodeInfo std::map> frozenNodes; // Check if frozen terms are clean or not for (const auto& frozenInfo : visitor.m_frozenNodes) { AstNodeExpr* const termp = frozenInfo.first; // Comparison operators are clean if ((VN_IS(termp, Eq) || VN_IS(termp, Neq) || VN_IS(termp, Lt) || VN_IS(termp, Lte) || VN_IS(termp, Gt) || VN_IS(termp, Gte)) && frozenInfo.second.m_lsb == 0) { hasCleanTerm = true; } else { // Otherwise, conservatively assume the frozen term is dirty hasDirtyTerm = true; UINFO(9, "Dirty frozen term: " << termp << endl); } frozenNodes[frozenInfo.second].push_back(termp); } // Figure out if a final negation is required const bool needsFlip = visitor.isXorTree() && !visitor.m_polarity; // Figure out if the final tree needs cleaning const bool needsCleaning = visitor.isAndTree() ? !hasCleanTerm : hasDirtyTerm; // Add size of reduction tree to op count resultOps += termps.size() - 1; for (const auto& lsbAndNodes : frozenNodes) { if (lsbAndNodes.first.m_lsb > 0) ++resultOps; // Needs AstShiftR if (!lsbAndNodes.first.m_polarity) ++resultOps; // Needs AstNot resultOps += lsbAndNodes.second.size(); } // Add final polarity flip in Xor tree if (needsFlip) ++resultOps; // Add final cleaning AND if (needsCleaning) ++resultOps; if (debug() >= 9) { // LCOV_EXCL_START cout << "- Bitop tree considered:\n"; for (AstNodeExpr* const termp : termps) termp->dumpTree("- Reduced term: "); for (const std::pair& termp : visitor.m_frozenNodes) { termp.first->dumpTree("- Frozen term with lsb " + std::to_string(termp.second.m_lsb) + " polarity " + std::to_string(termp.second.m_polarity) + ": "); } cout << "- Needs flipping: " << needsFlip << "\n"; cout << "- Needs cleaning: " << needsCleaning << "\n"; cout << "- Size: " << resultOps << " input size: " << visitor.m_ops << "\n"; } // LCOV_EXCL_END // Sometimes we have no terms left after ignoring redundant terms // (all of which were zeroes) if (termps.empty() && visitor.m_frozenNodes.empty()) { reduction += visitor.m_ops; AstNodeExpr* const resultp = needsFlip ? new AstConst{fl, AstConst::BitTrue{}} : new AstConst{fl, AstConst::BitFalse{}}; resultp->dtypeChgWidth(resultWidth, 1); return resultp; } // Only substitute the result if beneficial as determined by operation count if (visitor.m_ops <= resultOps) { for (AstNode* const termp : termps) VL_DO_DANGLING(termp->deleteTree(), termp); return nullptr; } // Update statistics reduction += visitor.m_ops - resultOps; // Reduction op to combine terms const auto reduce = [&visitor, fl](AstNodeExpr* lhsp, AstNodeExpr* rhsp) -> AstNodeExpr* { if (!lhsp) return rhsp; if (visitor.isAndTree()) { return new AstAnd{fl, lhsp, rhsp}; } else if (visitor.isOrTree()) { return new AstOr{fl, lhsp, rhsp}; } else { return new AstXor{fl, lhsp, rhsp}; } }; // Compute result by reducing all terms AstNodeExpr* resultp = nullptr; for (AstNodeExpr* const termp : termps) { // resultp = reduce(resultp, termp); } // Add any frozen terms to the reduction for (auto&& nodes : frozenNodes) { // nodes.second has same lsb and polarity AstNodeExpr* termp = nullptr; for (AstNodeExpr* const itemp : nodes.second) { termp = reduce(termp, itemp->unlinkFrBack()); } if (nodes.first.m_lsb > 0) { // LSB is not 0, so shiftR AstNodeDType* const dtypep = termp->dtypep(); termp = new AstShiftR{termp->fileline(), termp, new AstConst(termp->fileline(), AstConst::WidthedValue{}, termp->width(), nodes.first.m_lsb)}; termp->dtypep(dtypep); } if (!nodes.first.m_polarity) { // Polarity is inverted, so append Not AstNodeDType* const dtypep = termp->dtypep(); termp = new AstNot{termp->fileline(), termp}; termp->dtypep(dtypep); } resultp = reduce(resultp, termp); } // Set width of masks to expected result width. This is required to prevent later removal // of the masking node e.g. by the "AND with all ones" rule. If the result width happens // to be 1, we still need to ensure the AstAnd is not dropped, so use a wider mask in this // special case. const int maskWidth = std::max(resultp->width(), resultWidth == 1 ? VL_IDATASIZE : resultWidth); // Apply final polarity flip if (needsFlip) { if (needsCleaning) { // Cleaning will be added below. Use a NOT which is a byte shorter on x86 resultp = new AstNot{fl, resultp}; } else { // Keep result clean by using XOR(1, _) AstConst* const maskp = new AstConst{fl, AstConst::WidthedValue{}, maskWidth, 1}; resultp = new AstXor{fl, maskp, resultp}; } } // Apply final cleaning if (needsCleaning) { AstConst* const maskp = new AstConst{fl, AstConst::WidthedValue{}, maskWidth, 1}; resultp = new AstAnd{fl, maskp, resultp}; } // Cast back to original size if required if (resultp->width() != resultWidth) { resultp = new AstCCast{fl, resultp, resultWidth, 1}; } // Set width and widthMin precisely resultp->dtypeChgWidth(resultWidth, 1); return resultp; } }; //###################################################################### // Const state, as a visitor of each AstNode class ConstVisitor final : public VNVisitor { // CONSTANTS static constexpr unsigned CONCAT_MERGABLE_MAX_DEPTH = 10; // Limit alg recursion // NODE STATE // ** only when m_warn/m_doExpensive is set. If state is needed other times, // ** must track down everywhere V3Const is called and make sure no overlaps. // AstVar::user4p -> Used by variable marking/finding // AstJumpLabel::user4 -> bool. Set when AstJumpGo uses this label // AstEnum::user4 -> bool. Recursing. // STATE static constexpr bool m_doShort = true; // Remove expressions that short circuit bool m_params = false; // If true, propagate parameterized and true numbers only bool m_required = false; // If true, must become a constant bool m_wremove = true; // Inside scope, no assignw removal bool m_warn = false; // Output warnings bool m_doExpensive = false; // Enable computationally expensive optimizations bool m_doCpp = false; // Enable late-stage C++ optimizations bool m_doNConst = false; // Enable non-constant-child simplifications bool m_doV = false; // Verilog, not C++ conversion bool m_doGenerate = false; // Postpone width checking inside generate bool m_convertLogicToBit = false; // Convert logical operators to bitwise bool m_hasJumpDelay = false; // JumpGo or Delay under this while bool m_underRecFunc = false; // Under a recursive function AstNodeModule* m_modp = nullptr; // Current module const AstArraySel* m_selp = nullptr; // Current select const AstNode* m_scopep = nullptr; // Current scope const AstAttrOf* m_attrp = nullptr; // Current attribute VDouble0 m_statBitOpReduction; // Ops reduced in ConstBitOpTreeVisitor const bool m_globalPass; // ConstVisitor invoked as a global pass static uint32_t s_globalPassNum; // Counts number of times ConstVisitor invoked as global pass V3UniqueNames m_concswapNames; // For generating unique temporary variable names std::map m_containsMemberAccess; // Caches results of matchBiopToBitwise // METHODS bool operandConst(AstNode* nodep) { return VN_IS(nodep, Const); } bool operandAsvConst(const AstNode* nodep) { // BIASV(CONST, BIASV(CONST,...)) -> BIASV( BIASV_CONSTED(a,b), ...) const AstNodeBiComAsv* const bnodep = VN_CAST(nodep, NodeBiComAsv); if (!bnodep) return false; if (!VN_IS(bnodep->lhsp(), Const)) return false; const AstNodeBiComAsv* const rnodep = VN_CAST(bnodep->rhsp(), NodeBiComAsv); if (!rnodep) return false; if (rnodep->type() != bnodep->type()) return false; if (rnodep->width() != bnodep->width()) return false; if (rnodep->lhsp()->width() != bnodep->lhsp()->width()) return false; if (!VN_IS(rnodep->lhsp(), Const)) return false; return true; } bool operandAsvSame(const AstNode* nodep) { // BIASV(SAMEa, BIASV(SAMEb,...)) -> BIASV( BIASV(SAMEa,SAMEb), ...) const AstNodeBiComAsv* const bnodep = VN_CAST(nodep, NodeBiComAsv); if (!bnodep) return false; const AstNodeBiComAsv* const rnodep = VN_CAST(bnodep->rhsp(), NodeBiComAsv); if (!rnodep) return false; if (rnodep->type() != bnodep->type()) return false; if (rnodep->width() != bnodep->width()) return false; return operandsSame(bnodep->lhsp(), rnodep->lhsp()); } bool operandAsvLUp(const AstNode* nodep) { // BIASV(BIASV(CONSTll,lr),r) -> BIASV(CONSTll,BIASV(lr,r)) ? // // Example of how this is useful: // BIASV(BIASV(CONSTa,b...),BIASV(CONSTc,d...)) // hits operandAsvUp // BIASV(CONSTa,BIASV(b...,BIASV(CONSTc,d...))) // hits operandAsvUp // BIASV(CONSTa,BIASV(CONSTc,BIASV(c...,d...))) // hits operandAsvConst // BIASV(BIASV(CONSTa,CONSTc),BIASV(c...,d...))) // hits normal constant propagation // BIASV(CONST_a_c,BIASV(c...,d...))) // // Idea for the future: All BiComAsvs could be lists, sorted by if they're constant const AstNodeBiComAsv* const bnodep = VN_CAST(nodep, NodeBiComAsv); if (!bnodep) return false; const AstNodeBiComAsv* const lnodep = VN_CAST(bnodep->lhsp(), NodeBiComAsv); if (!lnodep) return false; if (lnodep->type() != bnodep->type()) return false; if (lnodep->width() != bnodep->width()) return false; return VN_IS(lnodep->lhsp(), Const); } bool operandAsvRUp(const AstNode* nodep) { // BIASV(l,BIASV(CONSTrl,rr)) -> BIASV(CONSTrl,BIASV(l,rr)) ? const AstNodeBiComAsv* const bnodep = VN_CAST(nodep, NodeBiComAsv); if (!bnodep) return false; const AstNodeBiComAsv* const rnodep = VN_CAST(bnodep->rhsp(), NodeBiComAsv); if (!rnodep) return false; if (rnodep->type() != bnodep->type()) return false; if (rnodep->width() != bnodep->width()) return false; return VN_IS(rnodep->lhsp(), Const); } static bool operandSubAdd(const AstNode* nodep) { // SUB( ADD(CONSTx,y), CONSTz) -> ADD(SUB(CONSTx,CONSTz), y) const AstNodeBiop* const np = VN_CAST(nodep, NodeBiop); const AstNodeBiop* const lp = VN_CAST(np->lhsp(), NodeBiop); return (lp && VN_IS(lp->lhsp(), Const) && VN_IS(np->rhsp(), Const) && lp->width() == np->width()); } bool matchRedundantClean(AstAnd* andp) { // Remove And with constant one inserted by V3Clean // 1 & (a == b) -> (IData)(a == b) // When bool is casted to int, the value is either 0 or 1 AstConst* const constp = VN_AS(andp->lhsp(), Const); UASSERT_OBJ(constp && constp->isOne(), andp->lhsp(), "TRREEOPC must meet this condition"); AstNodeExpr* const rhsp = andp->rhsp(); AstCCast* ccastp = nullptr; const auto isEqOrNeq = [](AstNode* nodep) -> bool { return VN_IS(nodep, Eq) || VN_IS(nodep, Neq); }; if (isEqOrNeq(rhsp)) { ccastp = new AstCCast{andp->fileline(), rhsp->unlinkFrBack(), andp}; } else if (AstCCast* const tmpp = VN_CAST(rhsp, CCast)) { if (isEqOrNeq(tmpp->lhsp())) { if (tmpp->width() == andp->width()) { tmpp->unlinkFrBack(); ccastp = tmpp; } else { ccastp = new AstCCast{andp->fileline(), tmpp->lhsp()->unlinkFrBack(), andp}; } } } if (ccastp) { andp->replaceWith(ccastp); VL_DO_DANGLING(pushDeletep(andp), andp); return true; } return false; } static bool operandAndOrSame(const AstNode* nodep) { // OR( AND(VAL,x), AND(VAL,y)) -> AND(VAL,OR(x,y)) // OR( AND(x,VAL), AND(y,VAL)) -> AND(OR(x,y),VAL) const AstNodeBiop* const np = VN_CAST(nodep, NodeBiop); const AstNodeBiop* const lp = VN_CAST(np->lhsp(), NodeBiop); const AstNodeBiop* const rp = VN_CAST(np->rhsp(), NodeBiop); return (lp && rp && lp->width() == rp->width() && lp->type() == rp->type() && (operandsSame(lp->lhsp(), rp->lhsp()) || operandsSame(lp->rhsp(), rp->rhsp()))); } bool matchOrAndNot(AstNodeBiop* nodep) { // AstOr{$a, AstAnd{AstNot{$b}, $c}} if $a.width1, $a==$b => AstOr{$a,$c} // Someday we'll sort the biops completely and this can be simplified // This often results from our simplified clock generation: // if (rst) ... else if (enable)... -> OR(rst,AND(!rst,enable)) AstNodeExpr* ap; AstNodeBiop* andp; if (VN_IS(nodep->lhsp(), And)) { andp = VN_AS(nodep->lhsp(), And); ap = nodep->rhsp(); } else if (VN_IS(nodep->rhsp(), And)) { andp = VN_AS(nodep->rhsp(), And); ap = nodep->lhsp(); } else { return false; } const AstNodeUniop* notp; AstNodeExpr* cp; if (VN_IS(andp->lhsp(), Not)) { notp = VN_AS(andp->lhsp(), Not); cp = andp->rhsp(); } else if (VN_IS(andp->rhsp(), Not)) { notp = VN_AS(andp->rhsp(), Not); cp = andp->lhsp(); } else { return false; } AstNodeExpr* const bp = notp->lhsp(); if (!operandsSame(ap, bp)) return false; // Do it cp->unlinkFrBack(); VL_DO_DANGLING(pushDeletep(andp->unlinkFrBack()), andp); VL_DANGLING(notp); // Replace whichever branch is now dangling if (nodep->rhsp()) { nodep->lhsp(cp); } else { nodep->rhsp(cp); } return true; } bool matchAndCond(AstAnd* nodep) { // Push down a AND into conditional, when one side of conditional is constant // (otherwise we'd be trading one operation for two operations) // V3Clean often makes this pattern, as it postpones the AND until // as high as possible, which is usually the right choice, except for this. AstNodeCond* const condp = VN_CAST(nodep->rhsp(), NodeCond); if (!condp) return false; if (!VN_IS(condp->thenp(), Const) && !VN_IS(condp->elsep(), Const)) return false; AstConst* const maskp = VN_CAST(nodep->lhsp(), Const); if (!maskp) return false; UINFO(4, "AND(CONSTm, CONDcond(c, i, e))->CONDcond(c, AND(m,i), AND(m, e)) " << nodep << endl); AstNodeCond* const newp = static_cast(condp->cloneType( condp->condp()->unlinkFrBack(), new AstAnd{nodep->fileline(), maskp->cloneTree(false), condp->thenp()->unlinkFrBack()}, new AstAnd{nodep->fileline(), maskp->cloneTree(false), condp->elsep()->unlinkFrBack()})); newp->dtypeFrom(nodep); newp->thenp()->dtypeFrom(nodep); // As And might have been to change widths newp->elsep()->dtypeFrom(nodep); nodep->replaceWith(newp); VL_DO_DANGLING(pushDeletep(nodep), nodep); return true; } bool matchMaskedOr(AstAnd* nodep) { // Masking an OR with terms that have no bits set under the mask is replaced with masking // only the remaining terms. Canonical example as generated by V3Expand is: // 0xff & (a << 8 | b >> 24) --> 0xff & (b >> 24) // Compute how many significant bits are in the mask const AstConst* const constp = VN_AS(nodep->lhsp(), Const); const uint32_t significantBits = constp->num().widthMin(); AstOr* const orp = VN_AS(nodep->rhsp(), Or); // Predicate for checking whether the bottom 'significantBits' bits of the given expression // are all zeroes. const auto checkBottomClear = [=](const AstNode* nodep) -> bool { if (const AstShiftL* const shiftp = VN_CAST(nodep, ShiftL)) { if (const AstConst* const scp = VN_CAST(shiftp->rhsp(), Const)) { return scp->num().toUInt() >= significantBits; } } return false; }; const bool orLIsRedundant = checkBottomClear(orp->lhsp()); const bool orRIsRedundant = checkBottomClear(orp->rhsp()); if (orLIsRedundant && orRIsRedundant) { nodep->replaceWith( new AstConst{nodep->fileline(), AstConst::DTyped{}, nodep->dtypep()}); VL_DO_DANGLING(pushDeletep(nodep), nodep); return true; } else if (orLIsRedundant) { orp->replaceWith(orp->rhsp()->unlinkFrBack()); VL_DO_DANGLING(pushDeletep(orp), orp); return false; // input node is still valid, keep going } else if (orRIsRedundant) { orp->replaceWith(orp->lhsp()->unlinkFrBack()); VL_DO_DANGLING(pushDeletep(orp), orp); return false; // input node is still valid, keep going } else { return false; } } bool matchMaskedShift(AstAnd* nodep) { // Drop redundant masking of right shift result. E.g: 0xff & ((uint32_t)a >> 24). This // commonly appears after V3Expand and the simplification in matchMaskedOr. Similarly, // drop redundant masking of left shift result. E.g.: 0xff000000 & ((uint32_t)a << 24). const auto checkMask = [nodep, this](const V3Number& mask) -> bool { const AstConst* const constp = VN_AS(nodep->lhsp(), Const); if (constp->num().isCaseEq(mask)) { AstNode* const rhsp = nodep->rhsp(); rhsp->unlinkFrBack(); nodep->replaceWith(rhsp); rhsp->dtypeFrom(nodep); VL_DO_DANGLING(pushDeletep(nodep), nodep); return true; } return false; }; // Check if masking is redundant if (const AstShiftR* const shiftp = VN_CAST(nodep->rhsp(), ShiftR)) { if (const AstConst* const scp = VN_CAST(shiftp->rhsp(), Const)) { // Check if mask is full over the non-zero bits V3Number maskLo{nodep, nodep->width()}; maskLo.setMask(nodep->width() - scp->num().toUInt()); return checkMask(maskLo); } } else if (const AstShiftL* const shiftp = VN_CAST(nodep->rhsp(), ShiftL)) { if (const AstConst* const scp = VN_CAST(shiftp->rhsp(), Const)) { // Check if mask is full over the non-zero bits V3Number mask{nodep, nodep->width()}; const uint32_t shiftAmount = scp->num().toUInt(); mask.setMask(nodep->width() - shiftAmount, shiftAmount); return checkMask(mask); } } return false; } bool matchBitOpTree(AstNodeExpr* nodep) { if (nodep->widthMin() != 1) return false; if (!v3Global.opt.fConstBitOpTree()) return false; string debugPrefix; if (debug() >= 9) { // LCOV_EXCL_START static int c = 0; debugPrefix = "- matchBitOpTree["; debugPrefix += cvtToStr(++c); debugPrefix += "] "; nodep->dumpTree(debugPrefix + "INPUT: "); } // LCOV_EXCL_STOP AstNode* newp = nullptr; const AstAnd* const andp = VN_CAST(nodep, And); const int width = nodep->width(); if (andp && isConst(andp->lhsp(), 1)) { // 1 & BitOpTree newp = ConstBitOpTreeVisitor::simplify(andp->rhsp(), width, 1, m_statBitOpReduction); } else { // BitOpTree newp = ConstBitOpTreeVisitor::simplify(nodep, width, 0, m_statBitOpReduction); } if (newp) { UINFO(4, "Transformed leaf of bit tree to " << newp << std::endl); nodep->replaceWith(newp); VL_DO_DANGLING(pushDeletep(nodep), nodep); } if (debug() >= 9) { // LCOV_EXCL_START if (newp) { newp->dumpTree(debugPrefix + "RESULT: "); } else { cout << debugPrefix << "not replaced" << endl; } } // LCOV_EXCL_STOP return newp; } static bool operandShiftSame(const AstNode* nodep) { const AstNodeBiop* const np = VN_AS(nodep, NodeBiop); { const AstShiftL* const lp = VN_CAST(np->lhsp(), ShiftL); const AstShiftL* const rp = VN_CAST(np->rhsp(), ShiftL); if (lp && rp) { return (lp->width() == rp->width() && lp->lhsp()->width() == rp->lhsp()->width() && operandsSame(lp->rhsp(), rp->rhsp())); } } { const AstShiftR* const lp = VN_CAST(np->lhsp(), ShiftR); const AstShiftR* const rp = VN_CAST(np->rhsp(), ShiftR); if (lp && rp) { return (lp->width() == rp->width() && lp->lhsp()->width() == rp->lhsp()->width() && operandsSame(lp->rhsp(), rp->rhsp())); } } return false; } bool operandHugeShiftL(const AstNodeBiop* nodep) { return (VN_IS(nodep->rhsp(), Const) && !VN_AS(nodep->rhsp(), Const)->num().isFourState() && (!VN_AS(nodep->rhsp(), Const)->num().fitsInUInt() // > 2^32 shift || (VN_AS(nodep->rhsp(), Const)->toUInt() >= static_cast(nodep->width()))) && nodep->lhsp()->isPure()); } bool operandHugeShiftR(const AstNodeBiop* nodep) { return (VN_IS(nodep->rhsp(), Const) && !VN_AS(nodep->rhsp(), Const)->num().isFourState() && (!VN_AS(nodep->rhsp(), Const)->num().fitsInUInt() // > 2^32 shift || (VN_AS(nodep->rhsp(), Const)->toUInt() >= static_cast(nodep->lhsp()->width()))) && nodep->lhsp()->isPure()); } bool operandIsTwo(const AstNode* nodep) { return (VN_IS(nodep, Const) && !VN_AS(nodep, Const)->num().isFourState() && nodep->width() <= VL_QUADSIZE && VN_AS(nodep, Const)->toUQuad() == 2); } bool operandIsTwostate(const AstNode* nodep) { return (VN_IS(nodep, Const) && !VN_AS(nodep, Const)->num().isFourState()); } bool operandIsPowTwo(const AstNode* nodep) { if (!operandIsTwostate(nodep)) return false; return (1 == VN_AS(nodep, Const)->num().countOnes()); } bool operandShiftOp(const AstNodeBiop* nodep) { if (!VN_IS(nodep->rhsp(), Const)) return false; const AstNodeBiop* const lhsp = VN_CAST(nodep->lhsp(), NodeBiop); if (!lhsp || !(VN_IS(lhsp, And) || VN_IS(lhsp, Or) || VN_IS(lhsp, Xor))) return false; if (nodep->width() != lhsp->width()) return false; if (nodep->width() != lhsp->lhsp()->width()) return false; if (nodep->width() != lhsp->rhsp()->width()) return false; return true; } bool operandShiftShift(const AstNodeBiop* nodep) { // We could add a AND though. const AstNodeBiop* const lhsp = VN_CAST(nodep->lhsp(), NodeBiop); if (!lhsp || !(VN_IS(lhsp, ShiftL) || VN_IS(lhsp, ShiftR))) return false; // We can only get rid of a<>c or a<rhsp(), Const) && VN_IS(lhsp->rhsp(), Const))) return false; if (VN_AS(nodep->rhsp(), Const)->num().isFourState() || VN_AS(lhsp->rhsp(), Const)->num().isFourState()) return false; if (nodep->width() != lhsp->width()) return false; if (nodep->width() != lhsp->lhsp()->width()) return false; return true; } bool operandWordOOB(const AstWordSel* nodep) { // V3Expand may make a arraysel that exceeds the bounds of the array // It was an expression, then got constified. In reality, the WordSel // must be wrapped in a Cond, that will be false. return (VN_IS(nodep->bitp(), Const) && VN_IS(nodep->fromp(), NodeVarRef) && VN_AS(nodep->fromp(), NodeVarRef)->access().isReadOnly() && (static_cast(VN_AS(nodep->bitp(), Const)->toUInt()) >= VN_AS(nodep->fromp(), NodeVarRef)->varp()->widthWords())); } bool operandSelFull(const AstSel* nodep) { return (VN_IS(nodep->lsbp(), Const) && VN_IS(nodep->widthp(), Const) && nodep->lsbConst() == 0 && static_cast(nodep->widthConst()) == nodep->fromp()->width()); } bool operandSelExtend(AstSel* nodep) { // A pattern created by []'s after offsets have been removed // SEL(EXTEND(any,width,...),(width-1),0) -> ... // Since select's return unsigned, this is always an extend AstExtend* const extendp = VN_CAST(nodep->fromp(), Extend); if (!(m_doV && extendp && VN_IS(nodep->lsbp(), Const) && VN_IS(nodep->widthp(), Const) && nodep->lsbConst() == 0 && static_cast(nodep->widthConst()) == extendp->lhsp()->width())) return false; VL_DO_DANGLING(replaceWChild(nodep, extendp->lhsp()), nodep); return true; } bool operandSelBiLower(AstSel* nodep) { // SEL(ADD(a,b),(width-1),0) -> ADD(SEL(a),SEL(b)) // Add or any operation which doesn't care if we discard top bits AstNodeBiop* const bip = VN_CAST(nodep->fromp(), NodeBiop); if (!(m_doV && bip && VN_IS(nodep->lsbp(), Const) && VN_IS(nodep->widthp(), Const) && nodep->lsbConst() == 0)) return false; if (debug() >= 9) nodep->dumpTree("- SEL(BI)-in: "); AstNodeExpr* const bilhsp = bip->lhsp()->unlinkFrBack(); AstNodeExpr* const birhsp = bip->rhsp()->unlinkFrBack(); bip->lhsp(new AstSel{nodep->fileline(), bilhsp, 0, nodep->widthConst()}); bip->rhsp(new AstSel{nodep->fileline(), birhsp, 0, nodep->widthConst()}); if (debug() >= 9) bip->dumpTree("- SEL(BI)-ou: "); VL_DO_DANGLING(replaceWChild(nodep, bip), nodep); return true; } bool operandSelShiftLower(AstSel* nodep) { // AND({a}, SHIFTR({b}, {c})) is often shorthand in C for Verilog {b}[{c} :+ {a}] // becomes thought other optimizations // SEL(SHIFTR({a},{b}),{lsb},{width}) -> SEL({a},{lsb+b},{width}) AstShiftR* const shiftp = VN_CAST(nodep->fromp(), ShiftR); if (!(m_doV && shiftp && VN_IS(shiftp->rhsp(), Const) && VN_IS(nodep->lsbp(), Const) && VN_IS(nodep->widthp(), Const))) { return false; } AstNodeExpr* const ap = shiftp->lhsp(); AstConst* const bp = VN_AS(shiftp->rhsp(), Const); AstConst* const lp = VN_AS(nodep->lsbp(), Const); if (bp->isWide() || bp->num().isFourState() || bp->num().isNegative() || lp->isWide() || lp->num().isFourState() || lp->num().isNegative()) { return false; } const int newLsb = lp->toSInt() + bp->toSInt(); if (newLsb + nodep->widthConst() > ap->width()) return false; // UINFO(9, "SEL(SHIFTR(a,b),l,w) -> SEL(a,l+b,w)\n"); if (debug() >= 9) nodep->dumpTree("- SEL(SH)-in: "); AstSel* const newp = new AstSel{nodep->fileline(), ap->unlinkFrBack(), newLsb, nodep->widthConst()}; newp->dtypeFrom(nodep); if (debug() >= 9) newp->dumpTree("- SEL(SH)-ou: "); nodep->replaceWith(newp); VL_DO_DANGLING(pushDeletep(nodep), nodep); return true; } bool operandBiExtendConstShrink(AstNodeBiop* nodep) { // Loop unrolling favors standalone compares // EQ(const{width32}, EXTEND(xx{width3})) -> EQ(const{3}, xx{3}) // The constant must have zero bits (+ 1 if signed) or compare // would be incorrect. See also operandBiExtendConst AstExtend* const extendp = VN_CAST(nodep->rhsp(), Extend); if (!extendp) return false; AstNodeExpr* const smallerp = extendp->lhsp(); const int subsize = smallerp->width(); AstConst* const constp = VN_CAST(nodep->lhsp(), Const); if (!constp) return false; if (!constp->num().isBitsZero(constp->width() - 1, subsize)) return false; // if (debug() >= 9) nodep->dumpTree("- BI(EXTEND)-in: "); smallerp->unlinkFrBack(); VL_DO_DANGLING(pushDeletep(extendp->unlinkFrBack()), extendp); // aka nodep->lhsp. nodep->rhsp(smallerp); constp->unlinkFrBack(); V3Number num{constp, subsize}; num.opAssign(constp->num()); nodep->lhsp(new AstConst{constp->fileline(), num}); VL_DO_DANGLING(pushDeletep(constp), constp); if (debug() >= 9) nodep->dumpTree("- BI(EXTEND)-ou: "); return true; } bool operandBiExtendConstOver(const AstNodeBiop* nodep) { // EQ(const{width32}, EXTEND(xx{width3})) -> constant // When the constant has non-zero bits above the extend it's a constant. // Avoids compiler warning const AstExtend* const extendp = VN_CAST(nodep->rhsp(), Extend); if (!extendp) return false; AstNode* const smallerp = extendp->lhsp(); const int subsize = smallerp->width(); const AstConst* const constp = VN_CAST(nodep->lhsp(), Const); if (!constp) return false; if (constp->num().isBitsZero(constp->width() - 1, subsize)) return false; return true; } // Extraction checks bool warnSelect(AstSel* nodep) { if (m_doGenerate) { // Never checked yet V3Width::widthParamsEdit(nodep); iterateChildren(nodep); // May need "constifying" } // Find range of dtype we are selecting from // Similar code in V3Unknown::AstSel const bool doit = true; if (m_warn && VN_IS(nodep->lsbp(), Const) && VN_IS(nodep->widthp(), Const) && doit) { const int maxDeclBit = nodep->declRange().hiMaxSelect() * nodep->declElWidth() + (nodep->declElWidth() - 1); if (VN_AS(nodep->lsbp(), Const)->num().isFourState() || VN_AS(nodep->widthp(), Const)->num().isFourState()) { nodep->v3error("Selection index is constantly unknown or tristated: " "lsb=" << nodep->lsbp()->name() << " width=" << nodep->widthp()->name()); // Replacing nodep will make a mess above, so we replace the offender replaceZero(nodep->lsbp()); } else if (nodep->declRange().ranged() && (nodep->msbConst() > maxDeclBit || nodep->lsbConst() > maxDeclBit)) { // See also warning in V3Width // Must adjust by element width as declRange() is in number of elements string msbLsbProtected; if (nodep->declElWidth() == 0) { msbLsbProtected = "(nodep->declElWidth() == 0) " + std::to_string(nodep->msbConst()) + ":" + std::to_string(nodep->lsbConst()); } else { msbLsbProtected = std::to_string(nodep->msbConst() / nodep->declElWidth()) + ":" + std::to_string(nodep->lsbConst() / nodep->declElWidth()); } nodep->v3warn(SELRANGE, "Selection index out of range: " << msbLsbProtected << " outside " << nodep->declRange().hiMaxSelect() << ":0" << (nodep->declRange().lo() >= 0 ? "" : (" (adjusted +" + cvtToStr(-nodep->declRange().lo()) + " to account for negative lsb)"))); UINFO(1, " Related Raw index is " << nodep->msbConst() << ":" << nodep->lsbConst() << endl); // Don't replace with zero, we'll do it later } } return false; // Not a transform, so NOP } static bool operandsSame(AstNode* node1p, AstNode* node2p) { // For now we just detect constants & simple vars, though it could be more generic if (VN_IS(node1p, Const) && VN_IS(node2p, Const)) return node1p->sameGateTree(node2p); if (VN_IS(node1p, VarRef) && VN_IS(node2p, VarRef)) { // Avoid comparing widthMin's, which results in lost optimization attempts // If cleanup sameGateTree to be smarter, this can be restored. // return node1p->sameGateTree(node2p); return node1p->isSame(node2p); } // Pattern created by coverage-line; avoid compiler tautological-compare warning if (AstAnd* const and1p = VN_CAST(node1p, And)) { if (AstAnd* const and2p = VN_CAST(node2p, And)) { if (VN_IS(and1p->lhsp(), Const) && VN_IS(and1p->rhsp(), NodeVarRef) && VN_IS(and2p->lhsp(), Const) && VN_IS(and2p->rhsp(), NodeVarRef)) return node1p->sameGateTree(node2p); } } return false; } bool ifSameAssign(const AstNodeIf* nodep) { const AstNodeAssign* const thensp = VN_CAST(nodep->thensp(), NodeAssign); const AstNodeAssign* const elsesp = VN_CAST(nodep->elsesp(), NodeAssign); if (!thensp || thensp->nextp()) return false; // Must be SINGLE statement if (!elsesp || elsesp->nextp()) return false; if (thensp->type() != elsesp->type()) return false; // Can't mix an assigndly with assign if (!thensp->lhsp()->sameGateTree(elsesp->lhsp())) return false; if (!thensp->rhsp()->gateTree()) return false; if (!elsesp->rhsp()->gateTree()) return false; if (m_underRecFunc) return false; // This optimization may lead to infinite recursion return true; } bool operandIfIf(const AstNodeIf* nodep) { if (nodep->elsesp()) return false; const AstNodeIf* const lowerIfp = VN_CAST(nodep->thensp(), NodeIf); if (!lowerIfp || lowerIfp->nextp()) return false; if (nodep->type() != lowerIfp->type()) return false; if (AstNode::afterCommentp(lowerIfp->elsesp())) return false; return true; } bool ifConcatMergeableBiop(const AstNode* nodep) { return (VN_IS(nodep, And) || VN_IS(nodep, Or) || VN_IS(nodep, Xor)); } bool ifAdjacentSel(const AstSel* lhsp, const AstSel* rhsp) { if (!v3Global.opt.fAssemble()) return false; // opt disabled if (!lhsp || !rhsp) return false; const AstNode* const lfromp = lhsp->fromp(); const AstNode* const rfromp = rhsp->fromp(); if (!lfromp || !rfromp || !lfromp->sameGateTree(rfromp)) return false; const AstConst* const lstart = VN_CAST(lhsp->lsbp(), Const); const AstConst* const rstart = VN_CAST(rhsp->lsbp(), Const); const AstConst* const lwidth = VN_CAST(lhsp->widthp(), Const); const AstConst* const rwidth = VN_CAST(rhsp->widthp(), Const); if (!lstart || !rstart || !lwidth || !rwidth) return false; // too complicated const int rend = (rstart->toSInt() + rwidth->toSInt()); return (rend == lstart->toSInt()); } bool ifMergeAdjacent(AstNodeExpr* lhsp, AstNodeExpr* rhsp) { // called by concatmergeable to determine if {lhsp, rhsp} make sense if (!v3Global.opt.fAssemble()) return false; // opt disabled // two same varref if (operandsSame(lhsp, rhsp)) return true; const AstSel* lselp = VN_CAST(lhsp, Sel); const AstSel* rselp = VN_CAST(rhsp, Sel); // a[i:0] a if (lselp && !rselp && rhsp->sameGateTree(lselp->fromp())) rselp = new AstSel{rhsp->fileline(), rhsp->cloneTreePure(false), 0, rhsp->width()}; // a[i:j] {a[j-1:k], b} if (lselp && !rselp && VN_IS(rhsp, Concat)) return ifMergeAdjacent(lhsp, VN_CAST(rhsp, Concat)->lhsp()); // a a[msb:j] if (rselp && !lselp && lhsp->sameGateTree(rselp->fromp())) lselp = new AstSel{lhsp->fileline(), lhsp->cloneTreePure(false), 0, lhsp->width()}; // {b, a[j:k]} a[k-1:i] if (rselp && !lselp && VN_IS(lhsp, Concat)) return ifMergeAdjacent(VN_CAST(lhsp, Concat)->rhsp(), rhsp); if (!lselp || !rselp) return false; // a[a:b] a[b-1:c] are adjacent AstNode* const lfromp = lselp->fromp(); AstNode* const rfromp = rselp->fromp(); if (!lfromp || !rfromp || !lfromp->sameGateTree(rfromp)) return false; AstConst* const lstart = VN_CAST(lselp->lsbp(), Const); AstConst* const rstart = VN_CAST(rselp->lsbp(), Const); AstConst* const lwidth = VN_CAST(lselp->widthp(), Const); AstConst* const rwidth = VN_CAST(rselp->widthp(), Const); if (!lstart || !rstart || !lwidth || !rwidth) return false; // too complicated const int rend = (rstart->toSInt() + rwidth->toSInt()); // a[i:j] a[j-1:k] if (rend == lstart->toSInt()) return true; // a[i:0] a[msb:j] if (rend == rfromp->width() && lstart->toSInt() == 0) return true; return false; } bool concatMergeable(const AstNodeExpr* lhsp, const AstNodeExpr* rhsp, unsigned depth) { // determine if {a OP b, c OP d} => {a, c} OP {b, d} is advantageous if (!v3Global.opt.fAssemble()) return false; // opt disabled if (lhsp->type() != rhsp->type()) return false; if (!ifConcatMergeableBiop(lhsp)) return false; if (depth > CONCAT_MERGABLE_MAX_DEPTH) return false; // As worse case O(n^2) algorithm const AstNodeBiop* const lp = VN_CAST(lhsp, NodeBiop); const AstNodeBiop* const rp = VN_CAST(rhsp, NodeBiop); if (!lp || !rp) return false; // {a[]&b[], a[]&b[]} const bool lad = ifMergeAdjacent(lp->lhsp(), rp->lhsp()); const bool rad = ifMergeAdjacent(lp->rhsp(), rp->rhsp()); if (lad && rad) return true; // {a[] & b[]&c[], a[] & b[]&c[]} if (lad && concatMergeable(lp->rhsp(), rp->rhsp(), depth + 1)) return true; // {a[]&b[] & c[], a[]&b[] & c[]} if (rad && concatMergeable(lp->lhsp(), rp->lhsp(), depth + 1)) return true; // {(a[]&b[])&(c[]&d[]), (a[]&b[])&(c[]&d[])} if (concatMergeable(lp->lhsp(), rp->lhsp(), depth + 1) && concatMergeable(lp->rhsp(), rp->rhsp(), depth + 1)) { return true; } return false; } static bool operandsSameWidth(const AstNode* lhsp, const AstNode* rhsp) { return lhsp->width() == rhsp->width(); } //---------------------------------------- // Constant Replacement functions. // These all take a node, delete its tree, and replaces it with a constant void replaceNum(AstNode* oldp, const V3Number& num) { // Replace oldp node with a constant set to specified value UASSERT(oldp, "Null old"); UASSERT_OBJ(!(VN_IS(oldp, Const) && !VN_AS(oldp, Const)->num().isFourState()), oldp, "Already constant??"); AstNode* const newp = new AstConst{oldp->fileline(), num}; newp->dtypeFrom(oldp); if (debug() > 5) oldp->dumpTree("- const_old: "); if (debug() > 5) newp->dumpTree("- _new: "); oldp->replaceWith(newp); VL_DO_DANGLING(pushDeletep(oldp), oldp); } void replaceNum(AstNode* nodep, uint32_t val) { V3Number num{nodep, nodep->width(), val}; VL_DO_DANGLING(replaceNum(nodep, num), nodep); } void replaceNumSigned(AstNodeBiop* nodep, uint32_t val) { // We allow both sides to be constant, as one may have come from // parameter propagation, etc. if (m_warn && !(VN_IS(nodep->lhsp(), Const) && VN_IS(nodep->rhsp(), Const))) { nodep->v3warn(UNSIGNED, "Comparison is constant due to unsigned arithmetic"); } VL_DO_DANGLING(replaceNum(nodep, val), nodep); } void replaceNumLimited(AstNodeBiop* nodep, uint32_t val) { // Avoids gcc warning about same if (m_warn) nodep->v3warn(CMPCONST, "Comparison is constant due to limited range"); VL_DO_DANGLING(replaceNum(nodep, val), nodep); } void replaceZero(AstNode* nodep) { VL_DO_DANGLING(replaceNum(nodep, 0), nodep); } void replaceZeroChkPure(AstNode* nodep, AstNodeExpr* checkp) { // For example, "0 * n" -> 0 if n has no side effects // Else strength reduce it to 0 & n. // If ever change the operation note AstAnd rule specially ignores this created pattern if (checkp->isPure()) { VL_DO_DANGLING(replaceNum(nodep, 0), nodep); } else { AstNode* const newp = new AstAnd{nodep->fileline(), new AstConst{nodep->fileline(), 0}, checkp->unlinkFrBack()}; newp->dtypeFrom(nodep); nodep->replaceWith(newp); VL_DO_DANGLING(pushDeletep(nodep), nodep); } } void replaceAllOnes(AstNode* nodep) { V3Number ones{nodep, nodep->width(), 0}; ones.setMask(nodep->width()); VL_DO_DANGLING(replaceNum(nodep, ones), nodep); } void replaceConst(AstNodeUniop* nodep) { V3Number num{nodep, nodep->width()}; nodep->numberOperate(num, VN_AS(nodep->lhsp(), Const)->num()); UINFO(4, "UNICONST -> " << num << endl); VL_DO_DANGLING(replaceNum(nodep, num), nodep); } void replaceConst(AstNodeBiop* nodep) { V3Number num{nodep, nodep->width()}; nodep->numberOperate(num, VN_AS(nodep->lhsp(), Const)->num(), VN_AS(nodep->rhsp(), Const)->num()); UINFO(4, "BICONST -> " << num << endl); VL_DO_DANGLING(replaceNum(nodep, num), nodep); } void replaceConst(AstNodeTriop* nodep) { V3Number num{nodep, nodep->width()}; nodep->numberOperate(num, VN_AS(nodep->lhsp(), Const)->num(), VN_AS(nodep->rhsp(), Const)->num(), VN_AS(nodep->thsp(), Const)->num()); UINFO(4, "TRICONST -> " << num << endl); VL_DO_DANGLING(replaceNum(nodep, num), nodep); } void replaceConst(AstNodeQuadop* nodep) { V3Number num{nodep, nodep->width()}; nodep->numberOperate( num, VN_AS(nodep->lhsp(), Const)->num(), VN_AS(nodep->rhsp(), Const)->num(), VN_AS(nodep->thsp(), Const)->num(), VN_AS(nodep->fhsp(), Const)->num()); UINFO(4, "QUADCONST -> " << num << endl); VL_DO_DANGLING(replaceNum(nodep, num), nodep); } void replaceConstString(AstNode* oldp, const string& num) { // Replace oldp node with a constant set to specified value UASSERT(oldp, "Null old"); AstNode* const newp = new AstConst{oldp->fileline(), AstConst::String{}, num}; if (debug() > 5) oldp->dumpTree("- const_old: "); if (debug() > 5) newp->dumpTree("- _new: "); oldp->replaceWith(newp); VL_DO_DANGLING(pushDeletep(oldp), oldp); } //---------------------------------------- // Replacement functions. // These all take a node and replace it with something else void replaceWChild(AstNode* nodep, AstNodeExpr* childp) { // NODE(..., CHILD(...)) -> CHILD(...) childp->unlinkFrBackWithNext(); // If replacing a SEL for example, the data type comes from the parent (is less wide). // This may adversely affect the operation of the node being replaced. childp->dtypeFrom(nodep); nodep->replaceWith(childp); VL_DO_DANGLING(pushDeletep(nodep), nodep); } void replaceWChildBool(AstNode* nodep, AstNodeExpr* childp) { // NODE(..., CHILD(...)) -> REDOR(CHILD(...)) childp->unlinkFrBack(); if (childp->width1()) { nodep->replaceWith(childp); } else { nodep->replaceWith(new AstRedOr{childp->fileline(), childp}); } VL_DO_DANGLING(pushDeletep(nodep), nodep); } //! Replace a ternary node with its RHS after iterating //! Used with short-circuiting, where the RHS has not yet been iterated. void replaceWIteratedRhs(AstNodeTriop* nodep) { if (AstNode* const rhsp = nodep->rhsp()) iterateAndNextNull(rhsp); replaceWChild(nodep, nodep->rhsp()); // May have changed } //! Replace a ternary node with its THS after iterating //! Used with short-circuiting, where the THS has not yet been iterated. void replaceWIteratedThs(AstNodeTriop* nodep) { if (AstNode* const thsp = nodep->thsp()) iterateAndNextNull(thsp); replaceWChild(nodep, nodep->thsp()); // May have changed } void replaceWLhs(AstNodeUniop* nodep) { // Keep LHS, remove RHS replaceWChild(nodep, nodep->lhsp()); } void replaceWLhs(AstNodeBiop* nodep) { // Keep LHS, remove RHS replaceWChild(nodep, nodep->lhsp()); } void replaceWRhs(AstNodeBiop* nodep) { // Keep RHS, remove LHS replaceWChild(nodep, nodep->rhsp()); } void replaceWLhsBool(AstNodeBiop* nodep) { replaceWChildBool(nodep, nodep->lhsp()); } void replaceWRhsBool(AstNodeBiop* nodep) { replaceWChildBool(nodep, nodep->rhsp()); } void replaceAsv(AstNodeBiop* nodep) { // BIASV(CONSTa, BIASV(CONSTb, c)) -> BIASV( BIASV_CONSTED(a,b), c) // BIASV(SAMEa, BIASV(SAMEb, c)) -> BIASV( BIASV(SAMEa,SAMEb), c) // nodep->dumpTree("- repAsvConst_old: "); AstNodeExpr* const ap = nodep->lhsp(); AstNodeBiop* const rp = VN_AS(nodep->rhsp(), NodeBiop); AstNodeExpr* const bp = rp->lhsp(); AstNodeExpr* const cp = rp->rhsp(); ap->unlinkFrBack(); bp->unlinkFrBack(); cp->unlinkFrBack(); rp->unlinkFrBack(); nodep->lhsp(rp); nodep->rhsp(cp); rp->lhsp(ap); rp->rhsp(bp); if (VN_IS(rp->lhsp(), Const) && VN_IS(rp->rhsp(), Const)) replaceConst(rp); // nodep->dumpTree("- repAsvConst_new: "); } void replaceAsvLUp(AstNodeBiop* nodep) { // BIASV(BIASV(CONSTll,lr),r) -> BIASV(CONSTll,BIASV(lr,r)) AstNodeBiop* const lp = VN_AS(nodep->lhsp()->unlinkFrBack(), NodeBiop); AstNodeExpr* const llp = lp->lhsp()->unlinkFrBack(); AstNodeExpr* const lrp = lp->rhsp()->unlinkFrBack(); AstNodeExpr* const rp = nodep->rhsp()->unlinkFrBack(); nodep->lhsp(llp); nodep->rhsp(lp); lp->lhsp(lrp); lp->rhsp(rp); // nodep->dumpTree("- repAsvLUp_new: "); } void replaceAsvRUp(AstNodeBiop* nodep) { // BIASV(l,BIASV(CONSTrl,rr)) -> BIASV(CONSTrl,BIASV(l,rr)) AstNodeExpr* const lp = nodep->lhsp()->unlinkFrBack(); AstNodeBiop* const rp = VN_AS(nodep->rhsp()->unlinkFrBack(), NodeBiop); AstNodeExpr* const rlp = rp->lhsp()->unlinkFrBack(); AstNodeExpr* const rrp = rp->rhsp()->unlinkFrBack(); nodep->lhsp(rlp); nodep->rhsp(rp); rp->lhsp(lp); rp->rhsp(rrp); // nodep->dumpTree("- repAsvRUp_new: "); } void replaceAndOr(AstNodeBiop* nodep) { // OR (AND (CONSTll,lr), AND(CONSTrl==ll,rr)) -> AND (CONSTll, OR(lr,rr)) // OR (AND (CONSTll,lr), AND(CONSTrl, rr=lr)) -> AND (OR(ll,rl), rr) // nodep ^lp ^llp ^lrp ^rp ^rlp ^rrp // (Or/And may also be reversed) AstNodeBiop* const lp = VN_AS(nodep->lhsp()->unlinkFrBack(), NodeBiop); AstNodeExpr* const llp = lp->lhsp()->unlinkFrBack(); AstNodeExpr* const lrp = lp->rhsp()->unlinkFrBack(); AstNodeBiop* const rp = VN_AS(nodep->rhsp()->unlinkFrBack(), NodeBiop); AstNodeExpr* const rlp = rp->lhsp()->unlinkFrBack(); AstNodeExpr* const rrp = rp->rhsp()->unlinkFrBack(); nodep->replaceWith(lp); if (operandsSame(llp, rlp)) { lp->lhsp(llp); lp->rhsp(nodep); lp->dtypeFrom(nodep); nodep->lhsp(lrp); nodep->rhsp(rrp); VL_DO_DANGLING(pushDeletep(rp), rp); VL_DO_DANGLING(pushDeletep(rlp), rlp); } else if (operandsSame(lrp, rrp)) { lp->lhsp(nodep); lp->rhsp(rrp); lp->dtypeFrom(nodep); nodep->lhsp(llp); nodep->rhsp(rlp); VL_DO_DANGLING(pushDeletep(rp), rp); VL_DO_DANGLING(pushDeletep(lrp), lrp); } else { nodep->v3fatalSrc("replaceAndOr on something operandAndOrSame shouldn't have matched"); } // nodep->dumpTree("- repAndOr_new: "); } void replaceShiftSame(AstNodeBiop* nodep) { // Or(Shift(ll,CONSTlr),Shift(rl,CONSTrr==lr)) -> Shift(Or(ll,rl),CONSTlr) // (Or/And may also be reversed) AstNodeBiop* const lp = VN_AS(nodep->lhsp()->unlinkFrBack(), NodeBiop); AstNodeExpr* const llp = lp->lhsp()->unlinkFrBack(); AstNodeExpr* const lrp = lp->rhsp()->unlinkFrBack(); AstNodeBiop* const rp = VN_AS(nodep->rhsp()->unlinkFrBack(), NodeBiop); AstNodeExpr* const rlp = rp->lhsp()->unlinkFrBack(); AstNodeExpr* const rrp = rp->rhsp()->unlinkFrBack(); nodep->replaceWith(lp); lp->lhsp(nodep); lp->rhsp(lrp); nodep->lhsp(llp); nodep->rhsp(rlp); nodep->dtypep(llp->dtypep()); // dtype of Biop is before shift. VL_DO_DANGLING(pushDeletep(rp), rp); VL_DO_DANGLING(pushDeletep(rrp), rrp); // nodep->dumpTree("- repShiftSame_new: "); } void replaceConcatSel(AstConcat* nodep) { // {a[1], a[0]} -> a[1:0] AstSel* const lselp = VN_AS(nodep->lhsp()->unlinkFrBack(), Sel); AstSel* const rselp = VN_AS(nodep->rhsp()->unlinkFrBack(), Sel); const int lstart = lselp->lsbConst(); const int lwidth = lselp->widthConst(); const int rstart = rselp->lsbConst(); const int rwidth = rselp->widthConst(); UASSERT_OBJ((rstart + rwidth) == lstart, nodep, "tried to merge two selects which are not adjacent"); AstSel* const newselp = new AstSel{ lselp->fromp()->fileline(), rselp->fromp()->unlinkFrBack(), rstart, lwidth + rwidth}; UINFO(5, "merged two adjacent sel " << lselp << " and " << rselp << " to one " << newselp << endl); nodep->replaceWith(newselp); VL_DO_DANGLING(pushDeletep(lselp), lselp); VL_DO_DANGLING(pushDeletep(rselp), rselp); VL_DO_DANGLING(pushDeletep(nodep), nodep); } void replaceConcatMerge(AstConcat* nodep) { // {llp OP lrp, rlp OP rrp} => {llp, rlp} OP {lrp, rrp}, where OP = AND/OR/XOR AstNodeBiop* const lp = VN_AS(nodep->lhsp(), NodeBiop); AstNodeBiop* const rp = VN_AS(nodep->rhsp(), NodeBiop); if (concatMergeable(lp, rp, 0)) { AstNodeExpr* const llp = lp->lhsp(); AstNodeExpr* const lrp = lp->rhsp(); AstNodeExpr* const rlp = rp->lhsp(); AstNodeExpr* const rrp = rp->rhsp(); AstConcat* const newlp = new AstConcat{rlp->fileline(), llp->cloneTreePure(false), rlp->cloneTreePure(false)}; AstConcat* const newrp = new AstConcat{rrp->fileline(), lrp->cloneTreePure(false), rrp->cloneTreePure(false)}; // use the lhs to replace the parent concat llp->replaceWith(newlp); VL_DO_DANGLING(pushDeletep(llp), llp); lrp->replaceWith(newrp); VL_DO_DANGLING(pushDeletep(lrp), lrp); lp->dtypeChgWidthSigned(newlp->width(), newlp->width(), VSigning::UNSIGNED); UINFO(5, "merged " << nodep << endl); VL_DO_DANGLING(pushDeletep(rp->unlinkFrBack()), rp); nodep->replaceWith(lp->unlinkFrBack()); VL_DO_DANGLING(pushDeletep(nodep), nodep); iterate(lp->lhsp()); iterate(lp->rhsp()); } else { nodep->v3fatalSrc("tried to merge two Concat which are not adjacent"); } } void replaceExtend(AstNode* nodep, AstNodeExpr* arg0p) { // -> EXTEND(nodep) // like a AstExtend{$rhsp}, but we need to set the width correctly from base node arg0p->unlinkFrBack(); AstNodeExpr* const newp = (VN_IS(nodep, ExtendS) ? static_cast(new AstExtendS{nodep->fileline(), arg0p}) : static_cast(new AstExtend{nodep->fileline(), arg0p})); newp->dtypeFrom(nodep); nodep->replaceWith(newp); VL_DO_DANGLING(pushDeletep(nodep), nodep); } void replacePowShift(AstNodeBiop* nodep) { // Pow or PowS UINFO(5, "POW(2,b)->SHIFTL(1,b) " << nodep << endl); AstNodeExpr* const rhsp = nodep->rhsp()->unlinkFrBack(); AstShiftL* const newp = new AstShiftL{nodep->fileline(), new AstConst{nodep->fileline(), 1}, rhsp}; newp->dtypeFrom(nodep); newp->lhsp()->dtypeFrom(nodep); nodep->replaceWith(newp); VL_DO_DANGLING(pushDeletep(nodep), nodep); } void replaceMulShift(AstMul* nodep) { // Mul, but not MulS as not simple shift UINFO(5, "MUL(2^n,b)->SHIFTL(b,n) " << nodep << endl); const int amount = VN_AS(nodep->lhsp(), Const)->num().mostSetBitP1() - 1; // 2^n->n+1 AstNodeExpr* const opp = nodep->rhsp()->unlinkFrBack(); AstShiftL* const newp = new AstShiftL{nodep->fileline(), opp, new AstConst(nodep->fileline(), amount)}; newp->dtypeFrom(nodep); nodep->replaceWith(newp); VL_DO_DANGLING(pushDeletep(nodep), nodep); } void replaceDivShift(AstDiv* nodep) { // Mul, but not MulS as not simple shift UINFO(5, "DIV(b,2^n)->SHIFTR(b,n) " << nodep << endl); const int amount = VN_AS(nodep->rhsp(), Const)->num().mostSetBitP1() - 1; // 2^n->n+1 AstNodeExpr* const opp = nodep->lhsp()->unlinkFrBack(); AstShiftR* const newp = new AstShiftR{nodep->fileline(), opp, new AstConst(nodep->fileline(), amount)}; newp->dtypeFrom(nodep); nodep->replaceWith(newp); VL_DO_DANGLING(pushDeletep(nodep), nodep); } void replaceModAnd(AstModDiv* nodep) { // Mod, but not ModS as not simple shift UINFO(5, "MOD(b,2^n)->AND(b,2^n-1) " << nodep << endl); const int amount = VN_AS(nodep->rhsp(), Const)->num().mostSetBitP1() - 1; // 2^n->n+1 V3Number mask{nodep, nodep->width()}; mask.setMask(amount); AstNodeExpr* const opp = nodep->lhsp()->unlinkFrBack(); AstAnd* const newp = new AstAnd{nodep->fileline(), opp, new AstConst{nodep->fileline(), mask}}; newp->dtypeFrom(nodep); nodep->replaceWith(newp); VL_DO_DANGLING(pushDeletep(nodep), nodep); } void replaceShiftOp(AstNodeBiop* nodep) { UINFO(5, "SHIFT(AND(a,b),CONST)->AND(SHIFT(a,CONST),SHIFT(b,CONST)) " << nodep << endl); const int width = nodep->width(); const int widthMin = nodep->widthMin(); VNRelinker handle; nodep->unlinkFrBack(&handle); AstNodeBiop* const lhsp = VN_AS(nodep->lhsp(), NodeBiop); lhsp->unlinkFrBack(); AstNodeExpr* const shiftp = nodep->rhsp()->unlinkFrBack(); AstNodeExpr* const ap = lhsp->lhsp()->unlinkFrBack(); AstNodeExpr* const bp = lhsp->rhsp()->unlinkFrBack(); AstNodeBiop* const shift1p = nodep; AstNodeBiop* const shift2p = nodep->cloneTree(true); shift1p->lhsp(ap); shift1p->rhsp(shiftp->cloneTreePure(true)); shift2p->lhsp(bp); shift2p->rhsp(shiftp); AstNodeBiop* const newp = lhsp; newp->lhsp(shift1p); newp->rhsp(shift2p); newp->dtypeChgWidth(width, widthMin); // The new AND must have width of the original SHIFT handle.relink(newp); iterate(newp); // Further reduce, either node may have more reductions. } void replaceShiftShift(AstNodeBiop* nodep) { UINFO(4, "SHIFT(SHIFT(a,s1),s2)->SHIFT(a,ADD(s1,s2)) " << nodep << endl); if (debug() >= 9) nodep->dumpTree("- repShiftShift_old: "); AstNodeBiop* const lhsp = VN_AS(nodep->lhsp(), NodeBiop); lhsp->unlinkFrBack(); AstNodeExpr* const ap = lhsp->lhsp()->unlinkFrBack(); AstNodeExpr* const shift1p = lhsp->rhsp()->unlinkFrBack(); AstNodeExpr* const shift2p = nodep->rhsp()->unlinkFrBack(); // Shift1p and shift2p may have different sizes, both are // self-determined so sum with infinite width if (nodep->type() == lhsp->type()) { const int shift1 = VN_AS(shift1p, Const)->toUInt(); const int shift2 = VN_AS(shift2p, Const)->toUInt(); const int newshift = shift1 + shift2; VL_DO_DANGLING(pushDeletep(shift1p), shift1p); VL_DO_DANGLING(pushDeletep(shift2p), shift2p); nodep->lhsp(ap); nodep->rhsp(new AstConst(nodep->fileline(), newshift)); iterate(nodep); // Further reduce, either node may have more reductions. } else { // We know shift amounts are constant, but might be a mixed left/right shift int shift1 = VN_AS(shift1p, Const)->toUInt(); if (VN_IS(lhsp, ShiftR)) shift1 = -shift1; int shift2 = VN_AS(shift2p, Const)->toUInt(); if (VN_IS(nodep, ShiftR)) shift2 = -shift2; const int newshift = shift1 + shift2; VL_DO_DANGLING(pushDeletep(shift1p), shift1p); VL_DO_DANGLING(pushDeletep(shift2p), shift2p); AstNodeExpr* newp; V3Number mask1{nodep, nodep->width()}; V3Number ones{nodep, nodep->width()}; ones.setMask(nodep->width()); if (shift1 < 0) { mask1.opShiftR(ones, V3Number(nodep, VL_IDATASIZE, -shift1)); } else { mask1.opShiftL(ones, V3Number(nodep, VL_IDATASIZE, shift1)); } V3Number mask{nodep, nodep->width()}; if (shift2 < 0) { mask.opShiftR(mask1, V3Number(nodep, VL_IDATASIZE, -shift2)); } else { mask.opShiftL(mask1, V3Number(nodep, VL_IDATASIZE, shift2)); } if (newshift < 0) { newp = new AstShiftR{nodep->fileline(), ap, new AstConst(nodep->fileline(), -newshift)}; } else { newp = new AstShiftL{nodep->fileline(), ap, new AstConst(nodep->fileline(), newshift)}; } newp->dtypeFrom(nodep); newp = new AstAnd{nodep->fileline(), newp, new AstConst{nodep->fileline(), mask}}; newp->dtypeFrom(nodep); nodep->replaceWith(newp); VL_DO_DANGLING(pushDeletep(nodep), nodep); // newp->dumpTree("- repShiftShift_new: "); iterate(newp); // Further reduce, either node may have more reductions. } VL_DO_DANGLING(pushDeletep(lhsp), lhsp); } bool replaceAssignMultiSel(AstNodeAssign* nodep) { // Multiple assignments to sequential bits can be concated // ASSIGN(SEL(a),aq), ASSIGN(SEL(a+1),bq) -> ASSIGN(SEL(a:b),CONCAT(aq,bq) // ie. assign var[2]=a, assign var[3]=b -> assign var[3:2]={b,a} // Skip if we're not const'ing an entire module (IE doing only one assign, etc) if (!m_modp) return false; AstSel* const sel1p = VN_CAST(nodep->lhsp(), Sel); if (!sel1p) return false; AstNodeAssign* const nextp = VN_CAST(nodep->nextp(), NodeAssign); if (!nextp) return false; if (nodep->type() != nextp->type()) return false; AstSel* const sel2p = VN_CAST(nextp->lhsp(), Sel); if (!sel2p) return false; AstVarRef* const varref1p = VN_CAST(sel1p->fromp(), VarRef); if (!varref1p) return false; AstVarRef* const varref2p = VN_CAST(sel2p->fromp(), VarRef); if (!varref2p) return false; if (!varref1p->sameGateTree(varref2p)) return false; AstConst* const con1p = VN_CAST(sel1p->lsbp(), Const); if (!con1p) return false; AstConst* const con2p = VN_CAST(sel2p->lsbp(), Const); if (!con2p) return false; // We need to make sure there's no self-references involved in either // assignment. For speed, we only look 3 deep, then give up. if (!varNotReferenced(nodep->rhsp(), varref1p->varp())) return false; if (!varNotReferenced(nextp->rhsp(), varref2p->varp())) return false; // If a variable is marked split_var, access to the variable should not be merged. if (varref1p->varp()->attrSplitVar() || varref2p->varp()->attrSplitVar()) return false; // Swap? if ((con1p->toSInt() != con2p->toSInt() + sel2p->width()) && (con2p->toSInt() != con1p->toSInt() + sel1p->width())) { return false; } const bool lsbFirstAssign = (con1p->toUInt() < con2p->toUInt()); UINFO(4, "replaceAssignMultiSel " << nodep << endl); UINFO(4, " && " << nextp << endl); // nodep->dumpTree("- comb1: "); // nextp->dumpTree("- comb2: "); AstNodeExpr* const rhs1p = nodep->rhsp()->unlinkFrBack(); AstNodeExpr* const rhs2p = nextp->rhsp()->unlinkFrBack(); AstNodeAssign* newp; if (lsbFirstAssign) { newp = nodep->cloneType(new AstSel{sel1p->fileline(), varref1p->unlinkFrBack(), sel1p->lsbConst(), sel1p->width() + sel2p->width()}, new AstConcat{rhs1p->fileline(), rhs2p, rhs1p}); } else { newp = nodep->cloneType(new AstSel{sel1p->fileline(), varref1p->unlinkFrBack(), sel2p->lsbConst(), sel1p->width() + sel2p->width()}, new AstConcat{rhs1p->fileline(), rhs1p, rhs2p}); } // pnewp->dumpTree("- conew: "); nodep->replaceWith(newp); VL_DO_DANGLING(pushDeletep(nodep), nodep); VL_DO_DANGLING(pushDeletep(nextp->unlinkFrBack()), nextp); return true; } bool varNotReferenced(AstNode* nodep, AstVar* varp, int level = 0) { // Return true if varp never referenced under node. // Return false if referenced, or tree too deep to be worth it, or side effects if (!nodep) return true; if (level > 2) return false; if (!nodep->isPure()) return false; // For example a $fgetc can't be reordered if (VN_IS(nodep, NodeVarRef) && VN_AS(nodep, NodeVarRef)->varp() == varp) return false; return (varNotReferenced(nodep->nextp(), varp, level + 1) && varNotReferenced(nodep->op1p(), varp, level + 1) && varNotReferenced(nodep->op2p(), varp, level + 1) && varNotReferenced(nodep->op3p(), varp, level + 1) && varNotReferenced(nodep->op4p(), varp, level + 1)); } bool replaceNodeAssign(AstNodeAssign* nodep) { if (VN_IS(nodep->lhsp(), VarRef) && VN_IS(nodep->rhsp(), VarRef) && VN_AS(nodep->lhsp(), VarRef)->sameNoLvalue(VN_AS(nodep->rhsp(), VarRef)) && !VN_IS(nodep, AssignDly)) { // X = X. Quite pointless, though X <= X may override another earlier assignment if (VN_IS(nodep, AssignW)) { nodep->v3error("Wire inputs its own output, creating circular logic (wire x=x)"); return false; // Don't delete the assign, or V3Gate will freak out } else { VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); return true; } } else if (m_doV && VN_IS(nodep->lhsp(), Concat) && nodep->isPure()) { bool need_temp = false; if (m_warn && !VN_IS(nodep, AssignDly)) { // Is same var on LHS and RHS? // Note only do this (need user4) when m_warn, which is // done as unique visitor const VNUser4InUse m_inuser4; nodep->lhsp()->foreach([](const AstVarRef* nodep) { if (nodep->varp()) nodep->varp()->user4(1); }); nodep->rhsp()->foreach([&need_temp](const AstVarRef* nodep) { if (nodep->varp() && nodep->varp()->user4()) need_temp = true; }); } if (need_temp) { // The first time we constify, there may be the same variable on the LHS // and RHS. In that case, we must use temporaries, or {a,b}={b,a} will break. UINFO(4, " ASSITEMP " << nodep << endl); // ASSIGN(CONCAT(lc1,lc2),rhs) -> ASSIGN(temp1,SEL(rhs,{size})), // ASSIGN(temp2,SEL(newrhs,{size})) // ASSIGN(lc1,temp1), // ASSIGN(lc2,temp2) } else { UINFO(4, " ASSI " << nodep << endl); // ASSIGN(CONCAT(lc1,lc2),rhs) -> ASSIGN(lc1,SEL(rhs,{size})), // ASSIGN(lc2,SEL(newrhs,{size})) } if (debug() >= 9) nodep->dumpTree("- Ass_old: "); // Unlink the stuff AstNodeExpr* const lc1p = VN_AS(nodep->lhsp(), Concat)->lhsp()->unlinkFrBack(); AstNodeExpr* const lc2p = VN_AS(nodep->lhsp(), Concat)->rhsp()->unlinkFrBack(); AstNodeExpr* const conp = VN_AS(nodep->lhsp(), Concat)->unlinkFrBack(); AstNodeExpr* const rhsp = nodep->rhsp()->unlinkFrBack(); AstNodeExpr* const rhs2p = rhsp->cloneTreePure(false); // Calc widths const int lsb2 = 0; const int msb2 = lsb2 + lc2p->width() - 1; const int lsb1 = msb2 + 1; const int msb1 = lsb1 + lc1p->width() - 1; UASSERT_OBJ(msb1 == (conp->width() - 1), nodep, "Width calc mismatch"); // Form ranges AstSel* const sel1p = new AstSel{conp->fileline(), rhsp, lsb1, msb1 - lsb1 + 1}; AstSel* const sel2p = new AstSel{conp->fileline(), rhs2p, lsb2, msb2 - lsb2 + 1}; // Make new assigns of same flavor as old one //*** Not cloneTree; just one node. AstNodeAssign* newp = nullptr; if (!need_temp) { AstNodeAssign* const asn1ap = nodep->cloneType(lc1p, sel1p); AstNodeAssign* const asn2ap = nodep->cloneType(lc2p, sel2p); asn1ap->dtypeFrom(sel1p); asn2ap->dtypeFrom(sel2p); newp = AstNode::addNext(newp, asn1ap); newp = AstNode::addNext(newp, asn2ap); } else { UASSERT_OBJ(m_modp, nodep, "Not under module"); UASSERT_OBJ(m_globalPass, nodep, "Should not reach here when not invoked on whole AstNetlist"); // We could create just one temp variable, but we'll get better optimization // if we make one per term. AstVar* const temp1p = new AstVar{sel1p->fileline(), VVarType::BLOCKTEMP, m_concswapNames.get(sel1p), VFlagLogicPacked{}, msb1 - lsb1 + 1}; AstVar* const temp2p = new AstVar{sel2p->fileline(), VVarType::BLOCKTEMP, m_concswapNames.get(sel2p), VFlagLogicPacked{}, msb2 - lsb2 + 1}; m_modp->addStmtsp(temp1p); m_modp->addStmtsp(temp2p); AstNodeAssign* const asn1ap = nodep->cloneType( new AstVarRef{sel1p->fileline(), temp1p, VAccess::WRITE}, sel1p); AstNodeAssign* const asn2ap = nodep->cloneType( new AstVarRef{sel2p->fileline(), temp2p, VAccess::WRITE}, sel2p); AstNodeAssign* const asn1bp = nodep->cloneType( lc1p, new AstVarRef{sel1p->fileline(), temp1p, VAccess::READ}); AstNodeAssign* const asn2bp = nodep->cloneType( lc2p, new AstVarRef{sel2p->fileline(), temp2p, VAccess::READ}); asn1ap->dtypeFrom(temp1p); asn1bp->dtypeFrom(temp1p); asn2ap->dtypeFrom(temp2p); asn2bp->dtypeFrom(temp2p); // This order matters newp = AstNode::addNext(newp, asn1ap); newp = AstNode::addNext(newp, asn2ap); newp = AstNode::addNext(newp, asn1bp); newp = AstNode::addNext(newp, asn2bp); } if (debug() >= 9 && newp) newp->dumpTreeAndNext(cout, "- _new: "); nodep->addNextHere(newp); // Cleanup VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); VL_DO_DANGLING(pushDeletep(conp), conp); // Further reduce, either node may have more reductions. return true; } else if (m_doV && VN_IS(nodep->rhsp(), StreamR)) { // The right-streaming operator on rhs of assignment does not // change the order of bits. Eliminate stream but keep its lhsp. // Add a cast if needed. AstStreamR* const streamp = VN_AS(nodep->rhsp(), StreamR)->unlinkFrBack(); AstNodeExpr* srcp = streamp->lhsp()->unlinkFrBack(); AstNodeDType* const srcDTypep = srcp->dtypep(); if (VN_IS(srcDTypep, QueueDType) || VN_IS(srcDTypep, DynArrayDType)) { srcp = new AstCvtArrayToPacked{srcp->fileline(), srcp, nodep->dtypep()}; } else if (VN_IS(srcDTypep, UnpackArrayDType)) { srcp = new AstCvtArrayToPacked{srcp->fileline(), srcp, srcDTypep}; // Handling the case where lhs is wider than rhs by inserting zeros. StreamL does // not require this, since the left streaming operator implicitly handles this. const int packedBits = nodep->lhsp()->widthMin(); const int unpackBits = srcDTypep->arrayUnpackedElements() * srcDTypep->subDTypep()->widthMin(); const uint32_t offset = packedBits > unpackBits ? packedBits - unpackBits : 0; srcp = new AstShiftL{srcp->fileline(), srcp, new AstConst{srcp->fileline(), offset}, packedBits}; } nodep->rhsp(srcp); VL_DO_DANGLING(pushDeletep(streamp), streamp); // Further reduce, any of the nodes may have more reductions. return true; } else if (m_doV && VN_IS(nodep->lhsp(), StreamL)) { // Push the stream operator to the rhs of the assignment statement AstNodeExpr* streamp = nodep->lhsp()->unlinkFrBack(); AstNodeExpr* const dstp = VN_AS(streamp, StreamL)->lhsp()->unlinkFrBack(); AstNodeExpr* const srcp = nodep->rhsp()->unlinkFrBack(); const int sWidth = srcp->width(); const int dWidth = dstp->width(); // Connect the rhs to the stream operator and update its width VN_AS(streamp, StreamL)->lhsp(srcp); if (VN_IS(srcp->dtypep(), DynArrayDType) || VN_IS(srcp->dtypep(), QueueDType) || VN_IS(srcp->dtypep(), UnpackArrayDType)) { streamp->dtypeSetStream(); } else { streamp->dtypeSetLogicUnsized(srcp->width(), srcp->widthMin(), VSigning::UNSIGNED); } if (VN_IS(dstp->dtypep(), UnpackArrayDType)) { streamp = new AstCvtPackedToArray{nodep->fileline(), streamp, dstp->dtypep()}; } else { UASSERT(sWidth >= dWidth, "sWidth >= dWidth should have caused an error earlier"); if (dWidth == 0) { streamp = new AstCvtPackedToArray{nodep->fileline(), streamp, dstp->dtypep()}; } else if (sWidth >= dWidth) { streamp = new AstSel{streamp->fileline(), streamp, sWidth - dWidth, dWidth}; } } nodep->lhsp(dstp); nodep->rhsp(streamp); nodep->dtypep(dstp->dtypep()); return true; } else if (m_doV && VN_IS(nodep->lhsp(), StreamR)) { // The right stream operator on lhs of assignment statement does // not reorder bits. However, if the rhs is wider than the lhs, // then we select bits from the left-most, not the right-most. AstNodeExpr* const streamp = nodep->lhsp()->unlinkFrBack(); AstNodeExpr* const dstp = VN_AS(streamp, StreamR)->lhsp()->unlinkFrBack(); AstNodeExpr* srcp = nodep->rhsp()->unlinkFrBack(); const int sWidth = srcp->width(); const int dWidth = dstp->width(); if (VN_IS(dstp->dtypep(), UnpackArrayDType)) { const int dstBitWidth = dWidth * VN_AS(dstp->dtypep(), UnpackArrayDType)->arrayUnpackedElements(); // Handling the case where rhs is wider than lhs. StreamL does not require this // since the combination of the left streaming operation and the implicit // truncation in VL_ASSIGN_UNPACK automatically selects the left-most bits. if (sWidth > dstBitWidth) { srcp = new AstSel{streamp->fileline(), srcp, sWidth - dstBitWidth, dstBitWidth}; } srcp = new AstCvtPackedToArray{nodep->fileline(), srcp, dstp->dtypep()}; } else { UASSERT(sWidth >= dWidth, "sWidth >= dWidth should have caused an error earlier"); if (dWidth == 0) { srcp = new AstCvtPackedToArray{nodep->fileline(), srcp, dstp->dtypep()}; } else if (sWidth >= dWidth) { srcp = new AstSel{streamp->fileline(), srcp, sWidth - dWidth, dWidth}; } } nodep->lhsp(dstp); nodep->rhsp(srcp); nodep->dtypep(dstp->dtypep()); VL_DO_DANGLING(pushDeletep(streamp), streamp); // Further reduce, any of the nodes may have more reductions. return true; } else if (m_doV && VN_IS(nodep->rhsp(), StreamL)) { AstNodeDType* const lhsDtypep = nodep->lhsp()->dtypep(); AstStreamL* streamp = VN_AS(nodep->rhsp(), StreamL); AstNodeExpr* const srcp = streamp->lhsp(); const AstNodeDType* const srcDTypep = srcp->dtypep(); if (VN_IS(srcDTypep, QueueDType) || VN_IS(srcDTypep, DynArrayDType) || VN_IS(srcDTypep, UnpackArrayDType)) { streamp->lhsp(new AstCvtArrayToPacked{srcp->fileline(), srcp->unlinkFrBack(), nodep->dtypep()}); streamp->dtypeFrom(lhsDtypep); } } else if (m_doV && replaceAssignMultiSel(nodep)) { return true; } return false; } // Boolean replacements bool operandBoolShift(const AstNode* nodep) { // boolean test of AND(const,SHIFTR(x,const)) -> test of AND(SHIFTL(x,const), x) if (!VN_IS(nodep, And)) return false; if (!VN_IS(VN_AS(nodep, And)->lhsp(), Const)) return false; if (!VN_IS(VN_AS(nodep, And)->rhsp(), ShiftR)) return false; const AstShiftR* const shiftp = VN_AS(VN_AS(nodep, And)->rhsp(), ShiftR); if (!VN_IS(shiftp->rhsp(), Const)) return false; if (static_cast(nodep->width()) <= VN_AS(shiftp->rhsp(), Const)->toUInt()) { return false; } return true; } void replaceBoolShift(AstNode* nodep) { if (debug() >= 9) nodep->dumpTree("- bshft_old: "); AstConst* const andConstp = VN_AS(VN_AS(nodep, And)->lhsp(), Const); AstNodeExpr* const fromp = VN_AS(VN_AS(nodep, And)->rhsp(), ShiftR)->lhsp()->unlinkFrBack(); AstConst* const shiftConstp = VN_AS(VN_AS(VN_AS(nodep, And)->rhsp(), ShiftR)->rhsp(), Const); V3Number val{andConstp, andConstp->width()}; val.opShiftL(andConstp->num(), shiftConstp->num()); AstAnd* const newp = new AstAnd{nodep->fileline(), new AstConst{nodep->fileline(), val}, fromp}; // widthMin no longer applicable if different C-expanded width newp->dtypeSetLogicSized(nodep->width(), VSigning::UNSIGNED); nodep->replaceWith(newp); VL_DO_DANGLING(pushDeletep(nodep), nodep); if (debug() >= 9) newp->dumpTree("- _new: "); } void replaceWithSimulation(AstNode* nodep) { SimulateVisitor simvis; // Run it - may be unoptimizable due to large for loop, etc simvis.mainParamEmulate(nodep); if (!simvis.optimizable()) { const AstNode* errorp = simvis.whyNotNodep(); if (!errorp) errorp = nodep; nodep->v3error("Expecting expression to be constant, but can't determine constant for " << nodep->prettyTypeName() << '\n' << errorp->warnOther() << "... Location of non-constant " << errorp->prettyTypeName() << ": " << simvis.whyNotMessage()); VL_DO_DANGLING(replaceZero(nodep), nodep); } else { // Fetch the result AstNode* const valuep = simvis.fetchValueNull(nodep); // valuep is owned by Simulate UASSERT_OBJ(valuep, nodep, "No value returned from simulation"); // Replace it AstNode* const newp = valuep->cloneTree(false); newp->dtypeFrom(nodep); newp->fileline(nodep->fileline()); UINFO(4, "Simulate->" << newp << endl); nodep->replaceWith(newp); VL_DO_DANGLING(pushDeletep(nodep), nodep); } } //---------------------------------------- // VISITORS void visit(AstNetlist* nodep) override { // Iterate modules backwards, in bottom-up order. That's faster iterateChildrenBackwardsConst(nodep); } void visit(AstNodeModule* nodep) override { VL_RESTORER(m_modp); m_modp = nodep; m_concswapNames.reset(); iterateChildren(nodep); } void visit(AstCFunc* nodep) override { // No ASSIGNW removals under funcs, we've long eliminated INITIALs // (We should perhaps rename the assignw's to just assigns) VL_RESTORER(m_wremove); m_wremove = false; iterateChildren(nodep); } void visit(AstCLocalScope* nodep) override { iterateChildren(nodep); if (!nodep->stmtsp()) { nodep->unlinkFrBack(); VL_DO_DANGLING(pushDeletep(nodep), nodep); } } void visit(AstScope* nodep) override { // No ASSIGNW removals under scope, we've long eliminated INITIALs VL_RESTORER(m_wremove); VL_RESTORER(m_scopep); m_wremove = false; m_scopep = nodep; iterateChildren(nodep); } void swapSides(AstNodeBiCom* nodep) { // COMMUTATIVE({a},CONST) -> COMMUTATIVE(CONST,{a}) // This simplifies later optimizations AstNodeExpr* const lhsp = nodep->lhsp()->unlinkFrBackWithNext(); AstNodeExpr* const rhsp = nodep->rhsp()->unlinkFrBackWithNext(); nodep->lhsp(rhsp); nodep->rhsp(lhsp); iterate(nodep); // Again? } bool containsMemberAccessRecurse(const AstNode* const nodep) { if (!nodep) return false; const auto it = m_containsMemberAccess.lower_bound(nodep); if (it != m_containsMemberAccess.end() && it->first == nodep) return it->second; bool result = false; if (VN_IS(nodep, MemberSel) || VN_IS(nodep, MethodCall) || VN_IS(nodep, CMethodCall)) { result = true; } else if (const AstNodeFTaskRef* const funcRefp = VN_CAST(nodep, NodeFTaskRef)) { if (containsMemberAccessRecurse(funcRefp->taskp())) result = true; } else if (const AstNodeCCall* const funcRefp = VN_CAST(nodep, NodeCCall)) { if (containsMemberAccessRecurse(funcRefp->funcp())) result = true; } else if (const AstNodeFTask* const funcp = VN_CAST(nodep, NodeFTask)) { // Assume that it has a member access if (funcp->recursive()) result = true; } else if (const AstCFunc* const funcp = VN_CAST(nodep, CFunc)) { if (funcp->recursive()) result = true; } if (!result) { result = containsMemberAccessRecurse(nodep->op1p()) || containsMemberAccessRecurse(nodep->op2p()) || containsMemberAccessRecurse(nodep->op3p()) || containsMemberAccessRecurse(nodep->op4p()); } if (!result && !VN_IS(nodep, NodeFTask) && !VN_IS(nodep, CFunc) // don't enter into next function && containsMemberAccessRecurse(nodep->nextp())) { result = true; } m_containsMemberAccess.insert(it, std::make_pair(nodep, result)); return result; } bool matchBiopToBitwise(AstNodeBiop* const nodep) { if (!m_convertLogicToBit) return false; if (!nodep->lhsp()->width1()) return false; if (!nodep->rhsp()->width1()) return false; if (!nodep->isPure()) return false; if (containsMemberAccessRecurse(nodep)) return false; return true; } bool matchConcatRand(AstConcat* nodep) { // CONCAT(RAND, RAND) - created by Chisel code AstRand* const aRandp = VN_CAST(nodep->lhsp(), Rand); AstRand* const bRandp = VN_CAST(nodep->rhsp(), Rand); if (!aRandp || !bRandp) return false; if (!aRandp->combinable(bRandp)) return false; UINFO(4, "Concat(Rand,Rand) => Rand: " << nodep << endl); aRandp->dtypeFrom(nodep); // I.e. the total width nodep->replaceWith(aRandp->unlinkFrBack()); VL_DO_DANGLING(pushDeletep(nodep), nodep); return true; } bool matchSelRand(AstSel* nodep) { // SEL(RAND) - created by Chisel code AstRand* const aRandp = VN_CAST(nodep->fromp(), Rand); if (!aRandp) return false; if (aRandp->seedp()) return false; UINFO(4, "Sel(Rand) => Rand: " << nodep << endl); aRandp->dtypeFrom(nodep); // I.e. the total width nodep->replaceWith(aRandp->unlinkFrBack()); VL_DO_DANGLING(pushDeletep(nodep), nodep); return true; } int operandConcatMove(AstConcat* nodep) { // CONCAT under concat (See moveConcat) // Return value: true indicates to do it; 2 means move to LHS const AstConcat* const abConcp = VN_CAST(nodep->lhsp(), Concat); const AstConcat* const bcConcp = VN_CAST(nodep->rhsp(), Concat); if (!abConcp && !bcConcp) return 0; if (bcConcp) { AstNodeExpr* const ap = nodep->lhsp(); AstNodeExpr* const bp = bcConcp->lhsp(); // If a+b == 32,64,96 etc, then we want to have a+b together on LHS if (VL_BITBIT_I(ap->width() + bp->width()) == 0) return 2; // Transform 2: to abConc } else { // abConcp // Unless lhs is already 32 bits due to above, reorder it if (VL_BITBIT_I(nodep->lhsp()->width()) != 0) return 1; // Transform 1: to bcConc } return 0; // ok } void moveConcat(AstConcat* nodep) { // 1: CONCAT(CONCAT({a},{b}),{c}) -> CONCAT({a},CONCAT({b}, {c})) // or 2: CONCAT({a}, CONCAT({b},{c})) -> CONCAT(CONCAT({a},{b}),{c}) // Because the lhs of a concat needs a shift but the rhs doesn't, // putting additional CONCATs on the RHS leads to fewer assembler operations. // However, we'll end up with lots of wide moves if we make huge trees // like that, so on 32 bit boundaries, we'll do the opposite form. UINFO(4, "Move concat: " << nodep << endl); if (operandConcatMove(nodep) > 1) { AstNodeExpr* const ap = nodep->lhsp()->unlinkFrBack(); AstConcat* const bcConcp = VN_AS(nodep->rhsp(), Concat); bcConcp->unlinkFrBack(); AstNodeExpr* const bp = bcConcp->lhsp()->unlinkFrBack(); AstNodeExpr* const cp = bcConcp->rhsp()->unlinkFrBack(); AstConcat* const abConcp = new AstConcat{bcConcp->fileline(), ap, bp}; nodep->lhsp(abConcp); nodep->rhsp(cp); // If bp was a concat, then we have this exact same form again! // Recurse rather then calling node->iterate to prevent 2^n recursion! if (operandConcatMove(abConcp)) moveConcat(abConcp); VL_DO_DANGLING(pushDeletep(bcConcp), bcConcp); } else { AstConcat* const abConcp = VN_AS(nodep->lhsp(), Concat); abConcp->unlinkFrBack(); AstNodeExpr* const ap = abConcp->lhsp()->unlinkFrBack(); AstNodeExpr* const bp = abConcp->rhsp()->unlinkFrBack(); AstNodeExpr* const cp = nodep->rhsp()->unlinkFrBack(); AstConcat* const bcConcp = new AstConcat{abConcp->fileline(), bp, cp}; nodep->lhsp(ap); nodep->rhsp(bcConcp); if (operandConcatMove(bcConcp)) moveConcat(bcConcp); VL_DO_DANGLING(pushDeletep(abConcp), abConcp); } } // Special cases void visit(AstConst*) override {} // Already constant void visit(AstCell* nodep) override { if (m_params) { iterateAndNextNull(nodep->paramsp()); } else { iterateChildren(nodep); } } void visit(AstClassOrPackageRef* nodep) override { iterateChildren(nodep); } void visit(AstPin* nodep) override { iterateChildren(nodep); } void replaceLogEq(AstLogEq* nodep) { // LOGEQ(a,b) => AstLogAnd{AstLogOr{AstLogNot{a},b},AstLogOr{AstLogNot{b},a}} AstNodeExpr* const lhsp = nodep->lhsp()->unlinkFrBack(); AstNodeExpr* const rhsp = nodep->rhsp()->unlinkFrBack(); // Do exactly as IEEE says, might result in extra terms, so in future may do differently AstLogAnd* const newp = new AstLogAnd{ nodep->fileline(), new AstLogOr{nodep->fileline(), new AstLogNot{nodep->fileline(), lhsp}, rhsp}, new AstLogOr{nodep->fileline(), new AstLogNot{nodep->fileline(), rhsp->cloneTreePure(false)}, lhsp->cloneTreePure(false)}}; newp->dtypeFrom(nodep); nodep->replaceWith(newp); VL_DO_DANGLING(pushDeletep(nodep), nodep); } void replaceSelSel(AstSel* nodep) { // SEL(SEL({x},a,b),c,d) => SEL({x},a+c,d) AstSel* const belowp = VN_AS(nodep->fromp(), Sel); AstNodeExpr* const fromp = belowp->fromp()->unlinkFrBack(); AstNodeExpr* const widthp = nodep->widthp()->unlinkFrBack(); AstNodeExpr* const lsb1p = nodep->lsbp()->unlinkFrBack(); AstNodeExpr* const lsb2p = belowp->lsbp()->unlinkFrBack(); // Eliminate lower range UINFO(4, "Elim Lower range: " << nodep << endl); AstNodeExpr* newlsbp; if (VN_IS(lsb1p, Const) && VN_IS(lsb2p, Const)) { newlsbp = new AstConst{lsb1p->fileline(), VN_AS(lsb1p, Const)->toUInt() + VN_AS(lsb2p, Const)->toUInt()}; VL_DO_DANGLING(pushDeletep(lsb1p), lsb1p); VL_DO_DANGLING(pushDeletep(lsb2p), lsb2p); } else { // Width is important, we need the width of the fromp's expression, not the // potentially smaller lsb1p's width, but don't insert a redundant AstExtend. // Note that due to some sloppiness in earlier passes, lsb1p might actually be wider, // so extend to the wider type. AstNodeExpr* const widep = lsb1p->width() > lsb2p->width() ? lsb1p : lsb2p; AstNodeExpr* const lhsp = widep->width() > lsb2p->width() ? new AstExtend{lsb2p->fileline(), lsb2p} : lsb2p; AstNodeExpr* const rhsp = widep->width() > lsb1p->width() ? new AstExtend{lsb1p->fileline(), lsb1p} : lsb1p; lhsp->dtypeFrom(widep); rhsp->dtypeFrom(widep); newlsbp = new AstAdd{lsb1p->fileline(), lhsp, rhsp}; newlsbp->dtypeFrom(widep); } AstSel* const newp = new AstSel{nodep->fileline(), fromp, newlsbp, widthp}; nodep->replaceWith(newp); VL_DO_DANGLING(pushDeletep(nodep), nodep); } void replaceSelConcat(AstSel* nodep) { // SEL(CONCAT(a,b),c,d) => SEL(a or b, . .) AstConcat* const conp = VN_AS(nodep->fromp(), Concat); AstNodeExpr* const conLhsp = conp->lhsp(); AstNodeExpr* const conRhsp = conp->rhsp(); if (static_cast(nodep->lsbConst()) >= conRhsp->width()) { conLhsp->unlinkFrBack(); AstSel* const newp = new AstSel{nodep->fileline(), conLhsp, nodep->lsbConst() - conRhsp->width(), nodep->widthConst()}; nodep->replaceWith(newp); } else if (static_cast(nodep->msbConst()) < conRhsp->width()) { conRhsp->unlinkFrBack(); AstSel* const newp = new AstSel{nodep->fileline(), conRhsp, nodep->lsbConst(), nodep->widthConst()}; nodep->replaceWith(newp); } else { // Yuk, split between the two conRhsp->unlinkFrBack(); conLhsp->unlinkFrBack(); AstConcat* const newp = new AstConcat{nodep->fileline(), new AstSel{nodep->fileline(), conLhsp, 0, nodep->msbConst() - conRhsp->width() + 1}, new AstSel{nodep->fileline(), conRhsp, nodep->lsbConst(), conRhsp->width() - nodep->lsbConst()}}; nodep->replaceWith(newp); } VL_DO_DANGLING(pushDeletep(nodep), nodep); } bool operandSelReplicate(AstSel* nodep) { // SEL(REPLICATE(from,rep),lsb,width) => SEL(from,0,width) as long // as SEL's width <= b's width AstReplicate* const repp = VN_AS(nodep->fromp(), Replicate); AstNodeExpr* const fromp = repp->srcp(); AstConst* const lsbp = VN_CAST(nodep->lsbp(), Const); if (!lsbp) return false; AstNodeExpr* const widthp = nodep->widthp(); if (!VN_IS(widthp, Const)) return false; UASSERT_OBJ(fromp->width(), nodep, "Not widthed"); if ((lsbp->toUInt() / fromp->width()) != ((lsbp->toUInt() + nodep->width() - 1) / fromp->width())) { return false; } // fromp->unlinkFrBack(); widthp->unlinkFrBack(); AstSel* const newp = new AstSel{nodep->fileline(), fromp, new AstConst{lsbp->fileline(), lsbp->toUInt() % fromp->width()}, widthp}; newp->dtypeFrom(nodep); nodep->replaceWith(newp); VL_DO_DANGLING(pushDeletep(nodep), nodep); return true; } bool operandRepRep(AstReplicate* nodep) { // REPLICATE(REPLICATE2(from2,cnt2),cnt1) => REPLICATE(from2,(cnt1+cnt2)) AstReplicate* const rep2p = VN_AS(nodep->srcp(), Replicate); AstNodeExpr* const from2p = rep2p->srcp(); AstConst* const cnt1p = VN_CAST(nodep->countp(), Const); if (!cnt1p) return false; AstConst* const cnt2p = VN_CAST(rep2p->countp(), Const); if (!cnt2p) return false; // from2p->unlinkFrBack(); cnt1p->unlinkFrBack(); cnt2p->unlinkFrBack(); AstReplicate* const newp = new AstReplicate{nodep->fileline(), from2p, cnt1p->toUInt() * cnt2p->toUInt()}; newp->dtypeFrom(nodep); nodep->replaceWith(newp); VL_DO_DANGLING(pushDeletep(nodep), nodep); return true; } bool operandConcatSame(AstConcat* nodep) { // CONCAT(fromp,fromp) -> REPLICATE(fromp,1+1) // CONCAT(REP(fromp,cnt1),fromp) -> REPLICATE(fromp,cnt1+1) // CONCAT(fromp,REP(fromp,cnt1)) -> REPLICATE(fromp,1+cnt1) // CONCAT(REP(fromp,cnt1),REP(fromp,cnt2)) -> REPLICATE(fromp,cnt1+cnt2) AstNodeExpr* from1p = nodep->lhsp(); uint32_t cnt1 = 1; AstNodeExpr* from2p = nodep->rhsp(); uint32_t cnt2 = 1; if (VN_IS(from1p, Replicate)) { AstConst* const cnt1p = VN_CAST(VN_CAST(from1p, Replicate)->countp(), Const); if (!cnt1p) return false; from1p = VN_AS(from1p, Replicate)->srcp(); cnt1 = cnt1p->toUInt(); } if (VN_IS(from2p, Replicate)) { AstConst* const cnt2p = VN_CAST(VN_CAST(from2p, Replicate)->countp(), Const); if (!cnt2p) return false; from2p = VN_AS(from2p, Replicate)->srcp(); cnt2 = cnt2p->toUInt(); } if (!operandsSame(from1p, from2p)) return false; // from1p->unlinkFrBack(); AstReplicate* const newp = new AstReplicate{nodep->fileline(), from1p, cnt1 + cnt2}; newp->dtypeFrom(nodep); nodep->replaceWith(newp); VL_DO_DANGLING(pushDeletep(nodep), nodep); return true; } void replaceSelIntoBiop(AstSel* nodep) { // SEL(BUFIF1(a,b),1,bit) => BUFIF1(SEL(a,1,bit),SEL(b,1,bit)) AstNodeBiop* const fromp = VN_AS(nodep->fromp()->unlinkFrBack(), NodeBiop); UASSERT_OBJ(fromp, nodep, "Called on non biop"); AstNodeExpr* const lsbp = nodep->lsbp()->unlinkFrBack(); AstNodeExpr* const widthp = nodep->widthp()->unlinkFrBack(); // AstNodeExpr* const bilhsp = fromp->lhsp()->unlinkFrBack(); AstNodeExpr* const birhsp = fromp->rhsp()->unlinkFrBack(); // fromp->lhsp(new AstSel{nodep->fileline(), bilhsp, lsbp->cloneTreePure(true), widthp->cloneTreePure(true)}); fromp->rhsp(new AstSel{nodep->fileline(), birhsp, lsbp, widthp}); fromp->dtypeFrom(nodep); nodep->replaceWith(fromp); VL_DO_DANGLING(pushDeletep(nodep), nodep); } void replaceSelIntoUniop(AstSel* nodep) { // SEL(NOT(a),1,bit) => NOT(SEL(a,bit)) AstNodeUniop* const fromp = VN_AS(nodep->fromp()->unlinkFrBack(), NodeUniop); UASSERT_OBJ(fromp, nodep, "Called on non biop"); AstNodeExpr* const lsbp = nodep->lsbp()->unlinkFrBack(); AstNodeExpr* const widthp = nodep->widthp()->unlinkFrBack(); // AstNodeExpr* const bilhsp = fromp->lhsp()->unlinkFrBack(); // fromp->lhsp(new AstSel{nodep->fileline(), bilhsp, lsbp, widthp}); fromp->dtypeFrom(nodep); nodep->replaceWith(fromp); VL_DO_DANGLING(pushDeletep(nodep), nodep); } void visit(AstAttrOf* nodep) override { VL_RESTORER(m_attrp); m_attrp = nodep; iterateChildren(nodep); } void visit(AstArraySel* nodep) override { iterateAndNextNull(nodep->bitp()); if (VN_IS(nodep->bitp(), Const) && VN_IS(nodep->fromp(), VarRef) // Need to make sure it's an array object so don't mis-allow a constant (bug509.) && VN_AS(nodep->fromp(), VarRef)->varp() && VN_IS(VN_AS(nodep->fromp(), VarRef)->varp()->valuep(), InitArray)) { m_selp = nodep; // Ask visit(AstVarRef) to replace varref with const } iterateAndNextNull(nodep->fromp()); if (VN_IS(nodep->fromp(), Const)) { // It did. if (!m_selp) { nodep->v3error("Illegal assignment of constant to unpacked array"); } else { AstNode* const fromp = nodep->fromp()->unlinkFrBack(); nodep->replaceWith(fromp); if (VN_IS(fromp->dtypep()->skipRefp(), NodeArrayDType)) { // Strip off array to find what array references fromp->dtypeFrom( VN_AS(fromp->dtypep()->skipRefp(), NodeArrayDType)->subDTypep()); } VL_DO_DANGLING(pushDeletep(nodep), nodep); } } m_selp = nullptr; } void visit(AstCAwait* nodep) override { m_hasJumpDelay = true; iterateChildren(nodep); } void visit(AstNodeVarRef* nodep) override { iterateChildren(nodep); UASSERT_OBJ(nodep->varp(), nodep, "Not linked"); bool did = false; if (m_doV && nodep->varp()->valuep() && !m_attrp) { // if (debug()) valuep->dumpTree("- visitvaref: "); iterateAndNextNull(nodep->varp()->valuep()); // May change nodep->varp()->valuep() AstNode* const valuep = nodep->varp()->valuep(); if (nodep->access().isReadOnly() && ((!m_params // Can reduce constant wires into equations && m_doNConst && v3Global.opt.fConst() // Default value, not a "known" constant for this usage && !nodep->varp()->isClassMember() && !nodep->varp()->sensIfacep() && !(nodep->varp()->isFuncLocal() && nodep->varp()->isNonOutput()) && !nodep->varp()->noSubst() && !nodep->varp()->isSigPublic()) || nodep->varp()->isParam())) { if (operandConst(valuep)) { const V3Number& num = VN_AS(valuep, Const)->num(); // UINFO(2,"constVisit "<bitConst(); AstNode* const itemp = initarp->getIndexDefaultedValuep(bit); if (VN_IS(itemp, Const)) { const V3Number& num = VN_AS(itemp, Const)->num(); // UINFO(2,"constVisit "<cloneTree(false); nodep->replaceWith(newp); VL_DO_DANGLING(pushDeletep(nodep), nodep); did = true; } else if (nodep->varp()->isParam() && VN_IS(valuep, Unbounded)) { AstNode* const newp = valuep->cloneTree(false); nodep->replaceWith(newp); VL_DO_DANGLING(pushDeletep(nodep), nodep); did = true; } } } if (!did && m_required) { nodep->v3error("Expecting expression to be constant, but variable isn't const: " << nodep->varp()->prettyNameQ()); } } void visit(AstExprStmt* nodep) override { iterateChildren(nodep); if (!AstNode::afterCommentp(nodep->stmtsp())) { UINFO(8, "ExprStmt(...) " << nodep << " " << nodep->resultp() << endl); nodep->replaceWith(nodep->resultp()->unlinkFrBack()); VL_DO_DANGLING(pushDeletep(nodep), nodep); // Removing the ExprStmt might have made something impure above now pure } } void visit(AstEnumItemRef* nodep) override { iterateChildren(nodep); UASSERT_OBJ(nodep->itemp(), nodep, "Not linked"); bool did = false; if (nodep->itemp()->valuep()) { // if (debug()) nodep->itemp()->valuep()->dumpTree("- visitvaref: "); if (nodep->itemp()->user4()) { nodep->v3error("Recursive enum value: " << nodep->itemp()->prettyNameQ()); } else { nodep->itemp()->user4(true); iterateAndNextNull(nodep->itemp()->valuep()); nodep->itemp()->user4(false); } if (AstConst* const valuep = VN_CAST(nodep->itemp()->valuep(), Const)) { const V3Number& num = valuep->num(); VL_DO_DANGLING(replaceNum(nodep, num), nodep); did = true; } } if (!did && m_required) { nodep->v3error("Expecting expression to be constant, but enum value isn't const: " << nodep->itemp()->prettyNameQ()); } } // void visit(AstCvtPackString* nodep) override { // Not constant propagated (for today) because AstNodeExpr::isOpaque is set // Someday if lower is constant, convert to quoted "string". bool onlySenItemInSenTree(AstSenItem* nodep) { // Only one if it's not in a list return (!nodep->nextp() && nodep->backp()->nextp() != nodep); } void visit(AstSenItem* nodep) override { iterateChildren(nodep); if (m_doNConst && !v3Global.opt.timing().isSetTrue() // If --timing, V3Sched would turn this into an // infinite loop. See #5080 && (VN_IS(nodep->sensp(), Const) || VN_IS(nodep->sensp(), EnumItemRef) || (nodep->varrefp() && nodep->varrefp()->varp()->isParam()))) { // Constants in sensitivity lists may be removed (we'll simplify later) if (nodep->isClocked()) { // A constant can never get a pos/negedge if (onlySenItemInSenTree(nodep)) { if (nodep->edgeType() == VEdgeType::ET_CHANGED) { // TODO: This really is dodgy, as strictly compliant simulators will not // execute this block, but but t_func_check relies on it nodep->replaceWith( new AstSenItem{nodep->fileline(), AstSenItem::Initial{}}); } else { nodep->replaceWith(new AstSenItem{nodep->fileline(), AstSenItem::Never{}}); } VL_DO_DANGLING(pushDeletep(nodep), nodep); } else { VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); } } else { // Otherwise it may compute a result that needs to settle out nodep->replaceWith(new AstSenItem{nodep->fileline(), AstSenItem::Combo{}}); VL_DO_DANGLING(pushDeletep(nodep), nodep); } } else if (m_doNConst && VN_IS(nodep->sensp(), Not)) { // V3Gate may propagate NOTs into clocks... Just deal with it AstNode* const sensp = nodep->sensp(); AstNode* lastSensp = sensp; bool invert = false; while (VN_IS(lastSensp, Not)) { lastSensp = VN_AS(lastSensp, Not)->lhsp(); invert = !invert; } UINFO(8, "senItem(NOT...) " << nodep << " " << invert << endl); if (invert) nodep->edgeType(nodep->edgeType().invert()); sensp->replaceWith(lastSensp->unlinkFrBack()); VL_DO_DANGLING(pushDeletep(sensp), sensp); } } class SenItemCmp final { static int cmp(const AstNodeExpr* ap, const AstNodeExpr* bp) { const VNType aType = ap->type(); const VNType bType = bp->type(); if (aType != bType) return static_cast(bType) - static_cast(aType); if (const AstVarRef* const aRefp = VN_CAST(ap, VarRef)) { const AstVarRef* const bRefp = VN_AS(bp, VarRef); // Looks visually better if we keep sorted by name if (aRefp->name() < bRefp->name()) return -1; if (aRefp->name() > bRefp->name()) return 1; // But might be same name with different scopes if (aRefp->varScopep() < bRefp->varScopep()) return -1; if (aRefp->varScopep() > bRefp->varScopep()) return 1; // Or rarely, different data types if (aRefp->dtypep() < bRefp->dtypep()) return -1; if (aRefp->dtypep() > bRefp->dtypep()) return 1; return 0; } if (const AstConst* const aConstp = VN_CAST(ap, Const)) { const AstConst* const bConstp = VN_AS(bp, Const); if (aConstp->toUQuad() < bConstp->toUQuad()) return -1; if (aConstp->toUQuad() > bConstp->toUQuad()) return 1; return 0; } if (const AstNodeBiop* const aBiOpp = VN_CAST(ap, NodeBiop)) { const AstNodeBiop* const bBiOpp = VN_AS(bp, NodeBiop); // Compare RHSs first as LHS might be const, but the variable term should become // adjacent for optimization if identical. if (const int c = cmp(aBiOpp->rhsp(), bBiOpp->rhsp())) return c; return cmp(aBiOpp->lhsp(), bBiOpp->lhsp()); } if (const AstCMethodHard* const aCallp = VN_CAST(ap, CMethodHard)) { const AstCMethodHard* const bCallp = VN_AS(bp, CMethodHard); if (aCallp->name() < bCallp->name()) return -1; if (aCallp->name() > bCallp->name()) return 1; if (const int c = cmp(aCallp->fromp(), bCallp->fromp())) return c; AstNodeExpr* aPinsp = aCallp->pinsp(); AstNodeExpr* bPinsp = bCallp->pinsp(); while (aPinsp && bPinsp) { if (const int c = cmp(aPinsp, bPinsp)) return c; aPinsp = VN_AS(aPinsp->nextp(), NodeExpr); bPinsp = VN_AS(bPinsp->nextp(), NodeExpr); } return aPinsp ? -1 : bPinsp ? 1 : 0; } return 0; } public: bool operator()(const AstSenItem* lhsp, const AstSenItem* rhsp) const { AstNodeExpr* const lSensp = lhsp->sensp(); AstNodeExpr* const rSensp = rhsp->sensp(); if (lSensp && rSensp) { // If both terms have sensitivity expressions, recursively compare them if (const int c = cmp(lSensp, rSensp)) return c < 0; } else if (lSensp || rSensp) { // Terms with sensitivity expressions come after those without return rSensp; } // Finally sort by edge, AFTER variable, as we want multiple edges for same var // adjacent. note the SenTree optimizer requires this order (more general first, // less general last) return lhsp->edgeType() < rhsp->edgeType(); } }; void visit(AstSenTree* nodep) override { iterateChildren(nodep); if (m_doExpensive) { // cout<dumpTree("- ssin: "); // Optimize ideas for the future: // SENTREE(... SENGATE(x,a), SENGATE(SENITEM(x),b) ...) => SENGATE(x,OR(a,b)) // SENTREE(... SENITEM(x), SENGATE(SENITEM(x),*) ...) => SENITEM(x) // Do we need the SENITEM's to be identical? No because we're // ORing between them; we just need to ensure that the result is at // least as frequently activating. So we // SENGATE(SENITEM(x)) -> SENITEM(x), then let it collapse with the // other SENITEM(x). // Mark x in SENITEM(x) for (AstSenItem* senp = nodep->sensesp(); senp; senp = VN_AS(senp->nextp(), SenItem)) { if (senp->varrefp() && senp->varrefp()->varScopep()) { senp->varrefp()->varScopep()->user4(1); } } // Pass 1: Sort the sensitivity items so "posedge a or b" and "posedge b or a" and // similar, optimizable expressions end up next to each other. for (AstSenItem *nextp, *senp = nodep->sensesp(); senp; senp = nextp) { nextp = VN_AS(senp->nextp(), SenItem); // cppcheck-suppress unassignedVariable // cppcheck bug const SenItemCmp cmp; if (nextp && !cmp(senp, nextp)) { // Something's out of order, sort it senp = nullptr; std::vector vec; for (AstSenItem* senp = nodep->sensesp(); senp; senp = VN_AS(senp->nextp(), SenItem)) { vec.push_back(senp); } stable_sort(vec.begin(), vec.end(), SenItemCmp()); for (const auto& ip : vec) ip->unlinkFrBack(); for (const auto& ip : vec) nodep->addSensesp(ip); break; } } // Pass 2, remove duplicates and simplify adjacent terms if possible for (AstSenItem *senp = nodep->sensesp(), *nextp; senp; senp = nextp) { nextp = VN_AS(senp->nextp(), SenItem); if (!nextp) break; AstSenItem* const lItemp = senp; AstSenItem* const rItemp = nextp; AstNodeExpr* const lSenp = lItemp->sensp(); AstNodeExpr* const rSenp = rItemp->sensp(); if (!lSenp || !rSenp) continue; if (lSenp->sameGateTree(rSenp)) { // POSEDGE or NEGEDGE -> BOTHEDGE. (We've sorted POSEDGE, before NEGEDGE, so we // do not need to test for the opposite orders.) if (lItemp->edgeType() == VEdgeType::ET_POSEDGE && rItemp->edgeType() == VEdgeType::ET_NEGEDGE) { // Make both terms BOTHEDGE, the second will be removed below lItemp->edgeType(VEdgeType::ET_BOTHEDGE); rItemp->edgeType(VEdgeType::ET_BOTHEDGE); } // Remove identical expressions if (lItemp->edgeType() == rItemp->edgeType()) { VL_DO_DANGLING(pushDeletep(rItemp->unlinkFrBack()), rItemp); nextp = lItemp; } continue; } // Not identical terms, check if they can be combined if (lSenp->width() != rSenp->width()) continue; if (AstAnd* const lAndp = VN_CAST(lSenp, And)) { if (AstAnd* const rAndp = VN_CAST(rSenp, And)) { if (AstConst* const lConstp = VN_CAST(lAndp->lhsp(), Const)) { if (AstConst* const rConstp = VN_CAST(rAndp->lhsp(), Const)) { if (lAndp->rhsp()->sameTree(rAndp->rhsp())) { const V3Number lNum{lConstp->num()}; lConstp->num().opOr(lNum, rConstp->num()); // Remove redundant term VL_DO_DANGLING(pushDeletep(rItemp->unlinkFrBack()), rItemp); nextp = lItemp; } } } } } } } } //----- // Zero elimination void visit(AstNodeAssign* nodep) override { iterateChildren(nodep); if (nodep->timingControlp()) m_hasJumpDelay = true; if (m_doNConst && replaceNodeAssign(nodep)) return; } void visit(AstAssignAlias* nodep) override { // Don't perform any optimizations, keep the alias around } void visit(AstAssignVarScope* nodep) override { // Don't perform any optimizations, the node won't be linked yet } void visit(AstAssignW* nodep) override { iterateChildren(nodep); if (m_doNConst && replaceNodeAssign(nodep)) return; AstNodeVarRef* const varrefp = VN_CAST( nodep->lhsp(), VarRef); // Not VarXRef, as different refs may set different values to each hierarchy if (m_wremove && !m_params && m_doNConst && m_modp && operandConst(nodep->rhsp()) && !VN_AS(nodep->rhsp(), Const)->num().isFourState() && varrefp // Don't do messes with BITREFs/ARRAYREFs && !varrefp->varp()->hasStrengthAssignment() // Strengths are resolved in V3Tristate && !varrefp->varp()->valuep() // Not already constified && !varrefp->varScopep() // Not scoped (or each scope may have different initial val.) && !varrefp->varp()->isForced() // Not forced (not really a constant) ) { // ASSIGNW (VARREF, const) -> INITIAL ( ASSIGN (VARREF, const) ) UINFO(4, "constAssignW " << nodep << endl); // Make a initial assignment AstNodeExpr* const exprp = nodep->rhsp()->unlinkFrBack(); varrefp->unlinkFrBack(); AstInitial* const newinitp = new AstInitial{ nodep->fileline(), new AstAssign{nodep->fileline(), varrefp, exprp}}; m_modp->addStmtsp(newinitp); VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); // Set the initial value right in the variable so we can constant propagate AstNode* const initvaluep = exprp->cloneTree(false); varrefp->varp()->valuep(initvaluep); } } void visit(AstRelease* nodep) override { if (AstConcat* const concatp = VN_CAST(nodep->lhsp(), Concat)) { FileLine* const flp = nodep->fileline(); AstRelease* const newLp = new AstRelease{flp, concatp->lhsp()->unlinkFrBack()}; AstRelease* const newRp = new AstRelease{flp, concatp->rhsp()->unlinkFrBack()}; nodep->replaceWith(newLp); newLp->addNextHere(newRp); VL_DO_DANGLING(pushDeletep(nodep), nodep); visit(newLp); visit(newRp); } } void visit(AstNodeIf* nodep) override { iterateChildren(nodep); if (m_doNConst) { if (const AstConst* const constp = VN_CAST(nodep->condp(), Const)) { AstNode* keepp = nullptr; if (constp->isZero()) { UINFO(4, "IF(0,{any},{x}) => {x}: " << nodep << endl); keepp = nodep->elsesp(); } else if (!m_doV || constp->isNeqZero()) { // Might be X in Verilog UINFO(4, "IF(!0,{x},{any}) => {x}: " << nodep << endl); keepp = nodep->thensp(); } else { UINFO(4, "IF condition is X, retaining: " << nodep << endl); return; } if (keepp) { keepp->unlinkFrBackWithNext(); nodep->replaceWith(keepp); } else { nodep->unlinkFrBack(); } VL_DO_DANGLING(pushDeletep(nodep), nodep); } else if (!AstNode::afterCommentp(nodep->thensp()) && !AstNode::afterCommentp(nodep->elsesp())) { if (!nodep->condp()->isPure()) { // Condition has side effect - leave - perhaps in // future simplify to remove all but side effect terms } else { // Empty block, remove it VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); } } else if (!AstNode::afterCommentp(nodep->thensp())) { UINFO(4, "IF({x}) nullptr {...} => IF(NOT{x}}: " << nodep << endl); AstNodeExpr* const condp = nodep->condp(); AstNode* const elsesp = nodep->elsesp(); condp->unlinkFrBackWithNext(); elsesp->unlinkFrBackWithNext(); if (nodep->thensp()) { // Must have been comment pushDeletep(nodep->thensp()->unlinkFrBackWithNext()); } nodep->condp(new AstLogNot{condp->fileline(), condp}); // LogNot, as C++ optimization also possible nodep->addThensp(elsesp); } else if (((VN_IS(nodep->condp(), Not) && nodep->condp()->width() == 1) || VN_IS(nodep->condp(), LogNot)) && nodep->thensp() && nodep->elsesp()) { UINFO(4, "IF(NOT {x}) => IF(x) swapped if/else" << nodep << endl); AstNodeExpr* const condp = VN_AS(nodep->condp(), NodeUniop)->lhsp()->unlinkFrBackWithNext(); AstNode* const thensp = nodep->thensp()->unlinkFrBackWithNext(); AstNode* const elsesp = nodep->elsesp()->unlinkFrBackWithNext(); AstIf* const ifp = new AstIf{nodep->fileline(), condp, elsesp, thensp}; ifp->isBoundsCheck(nodep->isBoundsCheck()); // Copy bounds check info ifp->branchPred(nodep->branchPred().invert()); nodep->replaceWith(ifp); VL_DO_DANGLING(pushDeletep(nodep), nodep); } else if (ifSameAssign(nodep)) { UINFO( 4, "IF({a}) ASSIGN({b},{c}) else ASSIGN({b},{d}) => ASSIGN({b}, {a}?{c}:{d})\n"); AstNodeAssign* const thensp = VN_AS(nodep->thensp(), NodeAssign); AstNodeAssign* const elsesp = VN_AS(nodep->elsesp(), NodeAssign); thensp->unlinkFrBack(); AstNodeExpr* const condp = nodep->condp()->unlinkFrBack(); AstNodeExpr* const truep = thensp->rhsp()->unlinkFrBack(); AstNodeExpr* const falsep = elsesp->rhsp()->unlinkFrBack(); thensp->rhsp(new AstCond{truep->fileline(), condp, truep, falsep}); nodep->replaceWith(thensp); VL_DO_DANGLING(pushDeletep(nodep), nodep); } else if (false // Disabled, as vpm assertions are faster // without due to short-circuiting && operandIfIf(nodep)) { UINFO(9, "IF({a}) IF({b}) => IF({a} && {b})" << endl); AstNodeIf* const lowerIfp = VN_AS(nodep->thensp(), NodeIf); AstNodeExpr* const condp = nodep->condp()->unlinkFrBack(); AstNode* const lowerThensp = lowerIfp->thensp()->unlinkFrBackWithNext(); AstNodeExpr* const lowerCondp = lowerIfp->condp()->unlinkFrBackWithNext(); nodep->condp(new AstLogAnd{lowerIfp->fileline(), condp, lowerCondp}); lowerIfp->replaceWith(lowerThensp); VL_DO_DANGLING(pushDeletep(lowerIfp), lowerIfp); } else if (operandBoolShift(nodep->condp())) { replaceBoolShift(nodep->condp()); } } } void visit(AstDisplay* nodep) override { // DISPLAY(SFORMAT(text1)),DISPLAY(SFORMAT(text2)) -> DISPLAY(SFORMAT(text1+text2)) iterateChildren(nodep); if (stmtDisplayDisplay(nodep)) return; } bool stmtDisplayDisplay(AstDisplay* nodep) { // DISPLAY(SFORMAT(text1)),DISPLAY(SFORMAT(text2)) -> DISPLAY(SFORMAT(text1+text2)) if (!m_modp) return false; // Don't optimize under single statement AstDisplay* const prevp = VN_CAST(nodep->backp(), Display); if (!prevp) return false; if (!((prevp->displayType() == nodep->displayType()) || (prevp->displayType() == VDisplayType::DT_WRITE && nodep->displayType() == VDisplayType::DT_DISPLAY) || (prevp->displayType() == VDisplayType::DT_DISPLAY && nodep->displayType() == VDisplayType::DT_WRITE))) return false; if ((prevp->filep() && !nodep->filep()) || (!prevp->filep() && nodep->filep()) || (prevp->filep() && nodep->filep() && !prevp->filep()->sameTree(nodep->filep()))) return false; if (!prevp->fmtp() || prevp->fmtp()->nextp() || !nodep->fmtp() || nodep->fmtp()->nextp()) return false; AstSFormatF* const pformatp = prevp->fmtp(); if (!pformatp) return false; AstSFormatF* const nformatp = nodep->fmtp(); if (!nformatp) return false; // We don't merge scopeNames as can have only one and might be different scopes (late in // process) Also rare for real code to print %m multiple times in same message if (nformatp->scopeNamep() && pformatp->scopeNamep()) return false; // We don't early merge arguments as might need to later print warnings with // right line numbers, nor scopeNames as might be different scopes (late in process) if (!m_doCpp && pformatp->exprsp()) return false; if (!m_doCpp && nformatp->exprsp()) return false; if (pformatp->exprsp() && !pformatp->exprsp()->isPureAndNext()) return false; if (nformatp->exprsp() && !nformatp->exprsp()->isPureAndNext()) return false; // Avoid huge merges static constexpr int DISPLAY_MAX_MERGE_LENGTH = 500; if (pformatp->text().length() + nformatp->text().length() > DISPLAY_MAX_MERGE_LENGTH) return false; // UINFO(9, "DISPLAY(SF({a})) DISPLAY(SF({b})) -> DISPLAY(SF({a}+{b}))" << endl); // Convert DT_DISPLAY to DT_WRITE as may allow later optimizations if (prevp->displayType() == VDisplayType::DT_DISPLAY) { prevp->displayType(VDisplayType::DT_WRITE); pformatp->text(pformatp->text() + "\n"); } // We can't replace prev() as the edit tracking iterators will get confused. // So instead we edit the prev note itself. if (prevp->addNewline()) pformatp->text(pformatp->text() + "\n"); pformatp->text(pformatp->text() + nformatp->text()); if (!prevp->addNewline() && nodep->addNewline()) pformatp->text(pformatp->text() + "\n"); if (nformatp->exprsp()) pformatp->addExprsp(nformatp->exprsp()->unlinkFrBackWithNext()); if (AstScopeName* const scopeNamep = nformatp->scopeNamep()) { scopeNamep->unlinkFrBackWithNext(); pformatp->scopeNamep(scopeNamep); } VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); return true; } void visit(AstSFormatF* nodep) override { // Substitute constants into displays. The main point of this is to // simplify assertion methodologies which call functions with display's. // This eliminates a pile of wide temps, and makes the C a whole lot more readable. iterateChildren(nodep); bool anyconst = false; for (AstNode* argp = nodep->exprsp(); argp; argp = argp->nextp()) { if (VN_IS(argp, Const)) { anyconst = true; break; } } if (m_doNConst && anyconst) { // UINFO(9," Display in "<text()<exprsp(); const string text = nodep->text(); for (const char ch : text) { if (!inPct && ch == '%') { inPct = true; fmt = ch; } else if (inPct && (std::isdigit(ch) || ch == '.' || ch == '-')) { fmt += ch; } else if (inPct) { inPct = false; fmt += ch; switch (std::tolower(ch)) { case '%': break; // %% - just output a % case 'm': break; // %m - auto insert "name" case 'l': break; // %l - auto insert "library" case 't': // FALLTHRU case '^': // %t/%^ - don't know $timeformat so can't constify if (argp) argp = argp->nextp(); break; default: // Most operators, just move to next argument if (argp) { AstNode* const nextp = argp->nextp(); if (VN_IS(argp, Const)) { // Convert it const string out = VN_AS(argp, Const)->num().displayed(nodep, fmt); UINFO(9, " DispConst: " << fmt << " -> " << out << " for " << argp << endl); // fmt = out w/ replace % with %% as it must be literal. fmt = VString::quotePercent(out); VL_DO_DANGLING(pushDeletep(argp->unlinkFrBack()), argp); } argp = nextp; } break; } // switch newFormat += fmt; } else { newFormat += ch; } } if (newFormat != nodep->text()) { nodep->text(newFormat); UINFO(9, " Display out " << nodep << endl); } } if (!nodep->exprsp() && nodep->name().find('%') == string::npos && !nodep->hidden()) { // Just a simple constant string - the formatting is pointless VL_DO_DANGLING(replaceConstString(nodep, nodep->name()), nodep); } } void visit(AstNodeFTask* nodep) override { VL_RESTORER(m_underRecFunc); if (nodep->recursive()) m_underRecFunc = true; iterateChildren(nodep); } void visit(AstNodeCCall* nodep) override { iterateChildren(nodep); m_hasJumpDelay = true; // As don't analyze inside tasks for timing controls } void visit(AstNodeFTaskRef* nodep) override { // Note excludes AstFuncRef as other visitor below iterateChildren(nodep); m_hasJumpDelay = true; // As don't analyze inside tasks for timing controls } void visit(AstFuncRef* nodep) override { visit(static_cast(nodep)); if (m_params) { // Only parameters force us to do constant function call propagation replaceWithSimulation(nodep); } } void visit(AstArg* nodep) override { // replaceWithSimulation on the Arg's parent FuncRef replaces these iterateChildren(nodep); } void visit(AstWhile* nodep) override { const bool oldHasJumpDelay = m_hasJumpDelay; m_hasJumpDelay = false; { iterateChildren(nodep); } const bool thisWhileHasJumpDelay = m_hasJumpDelay; m_hasJumpDelay = thisWhileHasJumpDelay || oldHasJumpDelay; if (m_doNConst) { if (nodep->condp()->isZero()) { UINFO(4, "WHILE(0) => nop " << nodep << endl); if (nodep->precondsp()) { nodep->replaceWith(nodep->precondsp()); } else { nodep->v3warn(UNUSEDLOOP, "Loop condition is always false; body will never execute"); nodep->fileline()->modifyWarnOff(V3ErrorCode::UNUSEDLOOP, true); nodep->unlinkFrBack(); } VL_DO_DANGLING(pushDeletep(nodep), nodep); } else if (nodep->condp()->isNeqZero()) { if (!thisWhileHasJumpDelay) { nodep->v3warn(INFINITELOOP, "Infinite loop (condition always true)"); nodep->fileline()->modifyWarnOff(V3ErrorCode::INFINITELOOP, true); // Complain just once } } else if (operandBoolShift(nodep->condp())) { replaceBoolShift(nodep->condp()); } } } void visit(AstInitArray* nodep) override { iterateChildren(nodep); } void visit(AstInitItem* nodep) override { iterateChildren(nodep); } void visit(AstUnbounded* nodep) override { iterateChildren(nodep); } // These are converted by V3Param. Don't constify as we don't want the // from() VARREF to disappear, if any. // If output of a presel didn't get consted, chances are V3Param didn't visit properly void visit(AstNodePreSel*) override {} // Ignored, can eliminate early void visit(AstSysIgnore* nodep) override { iterateChildren(nodep); if (m_doNConst) VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); } void visit(AstStmtExpr* nodep) override { iterateChildren(nodep); if (!nodep->exprp() || VN_IS(nodep->exprp(), Const)) { VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); return; } // TODO if there's an ExprStmt underneath just keep lower statements // (No current test case needs this) } // Simplify void visit(AstBasicDType* nodep) override { iterateChildren(nodep); nodep->cvtRangeConst(); } //----- // Jump elimination void visit(AstJumpGo* nodep) override { iterateChildren(nodep); // Jump to label where label immediately follows this go is not useful if (nodep->labelp() == VN_CAST(nodep->nextp(), JumpLabel)) { VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); // Keep the label, might be other jumps pointing to it, gets cleaned later return; } if (m_doExpensive) { // Any non-label statements (at this statement level) can never execute while (nodep->nextp() && !VN_IS(nodep->nextp(), JumpLabel)) { pushDeletep(nodep->nextp()->unlinkFrBack()); } // If last statement in a jump label we have JumpLabel(...., JumpGo) // Often caused by "return" in a Verilog function. The Go is pointless, remove. if (!nodep->nextp()) { if (AstJumpBlock* const aboveBlockp = VN_CAST(nodep->abovep(), JumpBlock)) { if (aboveBlockp == nodep->labelp()->blockp()) { if (aboveBlockp->endStmtsp() == nodep->labelp()) { UINFO(4, "JUMPGO => last remove " << nodep << endl); VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); return; } } } } nodep->labelp()->blockp()->user4(true); } m_hasJumpDelay = true; } void visit(AstJumpBlock* nodep) override { // Because JumpLabels disable many optimizations, // remove JumpLabels that are not pointed to by any AstJumpGos // Note this assumes all AstJumpGos are underneath the given label; V3Broken asserts this iterateChildren(nodep); // AstJumpGo's below here that point to this node will set user4 if (m_doExpensive && !nodep->user4()) { UINFO(4, "JUMPLABEL => unused " << nodep << endl); AstNode* underp = nullptr; if (nodep->stmtsp()) underp = nodep->stmtsp()->unlinkFrBackWithNext(); if (underp) { nodep->replaceWith(underp); } else { nodep->unlinkFrBack(); } pushDeletep(nodep->labelp()->unlinkFrBack()); VL_DO_DANGLING(pushDeletep(nodep), nodep); } } //----- // Below lines are magic expressions processed by astgen // TREE_SKIP_VISIT("AstNODETYPE") # Rename normal visit to visitGen and don't iterate //----- // clang-format off TREE_SKIP_VISIT("ArraySel"); TREE_SKIP_VISIT("CAwait"); //----- // "AstNODETYPE { # bracket not paren // $accessor_name, ... // # .castFoo is the test VN_IS(object,Foo) // # ,, gets replaced with a , rather than && // }" # bracket not paren // ,"what to call" // // Where "what_to_call" is: // "function to call" // "AstREPLACEMENT_TYPE{ $accessor }" // "! # Print line number when matches, so can see operations // "NEVER" # Print error message // "DONE" # Process of matching did the transform already // In the future maybe support more complicated match & replace: // ("AstOr {%a, AstAnd{AstNot{%b}, %c}} if %a.width1 if %a==%b", "AstOr{%a,%c}; %b.delete"); // Lhs/rhs would be implied; for non math operations you'd need $lhsp etc. // v--- * * This op done on Verilog or C+++ mode, in all non-m_doConst stages // v--- *1* These ops are always first, as we warn before replacing // v--- *C* This op is a (C)++ op, only in m_doCpp mode // v--- *V* This op is a (V)erilog op, only in m_doV mode // v--- *A* This op works on (A)ll constant children, allowed in m_doConst mode // v--- *S* This op specifies a type should use (S)hort-circuiting of its lhs op TREEOP1("AstSel{warnSelect(nodep)}", "NEVER"); // Generic constants on both side. Do this first to avoid other replacements TREEOPA("AstNodeBiop {$lhsp.castConst, $rhsp.castConst, nodep->isPredictOptimizable()}", "replaceConst(nodep)"); TREEOPA("AstNodeUniop{$lhsp.castConst, !nodep->isOpaque(), nodep->isPredictOptimizable()}", "replaceConst(nodep)"); TREEOPA("AstNodeQuadop{$lhsp.castConst, $rhsp.castConst, $thsp.castConst, $fhsp.castConst}", "replaceConst(nodep)"); // Zero on one side or the other TREEOP ("AstAdd {$lhsp.isZero, $rhsp}", "replaceWRhs(nodep)"); TREEOP ("AstAnd {$lhsp.isZero, $rhsp, $rhsp.isPure}", "replaceZero(nodep)"); // Can't use replaceZeroChkPure as we make this pattern in ChkPure // This visit function here must allow for short-circuiting. TREEOPS("AstLogAnd {$lhsp.isZero}", "replaceZero(nodep)"); TREEOP ("AstLogAnd{$lhsp.isZero, $rhsp}", "replaceZero(nodep)"); // This visit function here must allow for short-circuiting. TREEOPS("AstLogOr {$lhsp.isOne}", "replaceNum(nodep, 1)"); TREEOP ("AstLogOr {$lhsp.isZero, $rhsp}", "replaceWRhsBool(nodep)"); TREEOP ("AstDiv {$lhsp.isZero, $rhsp}", "replaceZeroChkPure(nodep,$rhsp)"); TREEOP ("AstDivS {$lhsp.isZero, $rhsp}", "replaceZeroChkPure(nodep,$rhsp)"); TREEOP ("AstMul {$lhsp.isZero, $rhsp}", "replaceZeroChkPure(nodep,$rhsp)"); TREEOP ("AstMulS {$lhsp.isZero, $rhsp}", "replaceZeroChkPure(nodep,$rhsp)"); TREEOP ("AstPow {$rhsp.isZero}", "replaceNum(nodep, 1)"); // Overrides lhs zero rule TREEOP ("AstPowSS {$rhsp.isZero}", "replaceNum(nodep, 1)"); // Overrides lhs zero rule TREEOP ("AstPowSU {$rhsp.isZero}", "replaceNum(nodep, 1)"); // Overrides lhs zero rule TREEOP ("AstPowUS {$rhsp.isZero}", "replaceNum(nodep, 1)"); // Overrides lhs zero rule TREEOP ("AstOr {$lhsp.isZero, $rhsp}", "replaceWRhs(nodep)"); TREEOP ("AstShiftL {$lhsp.isZero, $rhsp}", "replaceZeroChkPure(nodep,$rhsp)"); TREEOP ("AstShiftLOvr {$lhsp.isZero, $rhsp}", "replaceZeroChkPure(nodep,$rhsp)"); TREEOP ("AstShiftR {$lhsp.isZero, $rhsp}", "replaceZeroChkPure(nodep,$rhsp)"); TREEOP ("AstShiftROvr {$lhsp.isZero, $rhsp}", "replaceZeroChkPure(nodep,$rhsp)"); TREEOP ("AstShiftRS {$lhsp.isZero, $rhsp}", "replaceZeroChkPure(nodep,$rhsp)"); TREEOP ("AstShiftRSOvr{$lhsp.isZero, $rhsp}", "replaceZeroChkPure(nodep,$rhsp)"); TREEOP ("AstXor {$lhsp.isZero, $rhsp}", "replaceWRhs(nodep)"); TREEOP ("AstSub {$lhsp.isZero, $rhsp}", "AstNegate{$rhsp}"); TREEOP ("AstAdd {$lhsp, $rhsp.isZero}", "replaceWLhs(nodep)"); TREEOP ("AstAnd {$lhsp, $rhsp.isZero}", "replaceZeroChkPure(nodep,$lhsp)"); TREEOP ("AstLogAnd{$lhsp, $rhsp.isZero}", "replaceZeroChkPure(nodep,$lhsp)"); TREEOP ("AstLogOr {$lhsp, $rhsp.isZero}", "replaceWLhsBool(nodep)"); TREEOP ("AstMul {$lhsp, $rhsp.isZero}", "replaceZeroChkPure(nodep,$lhsp)"); TREEOP ("AstMulS {$lhsp, $rhsp.isZero}", "replaceZeroChkPure(nodep,$lhsp)"); TREEOP ("AstOr {$lhsp, $rhsp.isZero}", "replaceWLhs(nodep)"); TREEOP ("AstShiftL {$lhsp, $rhsp.isZero}", "replaceWLhs(nodep)"); TREEOP ("AstShiftLOvr {$lhsp, $rhsp.isZero}", "replaceWLhs(nodep)"); TREEOP ("AstShiftR {$lhsp, $rhsp.isZero}", "replaceWLhs(nodep)"); TREEOP ("AstShiftROvr {$lhsp, $rhsp.isZero}", "replaceWLhs(nodep)"); TREEOP ("AstShiftRS {$lhsp, $rhsp.isZero}", "replaceWLhs(nodep)"); TREEOP ("AstShiftRSOvr{$lhsp, $rhsp.isZero}", "replaceWLhs(nodep)"); TREEOP ("AstSub {$lhsp, $rhsp.isZero}", "replaceWLhs(nodep)"); TREEOP ("AstXor {$lhsp, $rhsp.isZero}", "replaceWLhs(nodep)"); // Non-zero on one side or the other TREEOP ("AstAnd {$lhsp.isAllOnes, $rhsp}", "replaceWRhs(nodep)"); TREEOP ("AstLogAnd{$lhsp.isNeqZero, $rhsp}", "replaceWRhsBool(nodep)"); TREEOP ("AstOr {$lhsp.isAllOnes, $rhsp, $rhsp.isPure}", "replaceWLhs(nodep)"); // ->allOnes TREEOP ("AstLogOr {$lhsp.isNeqZero, $rhsp}", "replaceNum(nodep,1)"); TREEOP ("AstAnd {$lhsp, $rhsp.isAllOnes}", "replaceWLhs(nodep)"); TREEOP ("AstLogAnd{$lhsp, $rhsp.isNeqZero}", "replaceWLhsBool(nodep)"); TREEOP ("AstOr {$lhsp, $rhsp.isAllOnes, $lhsp.isPure}", "replaceWRhs(nodep)"); // ->allOnes TREEOP ("AstLogOr {$lhsp, $rhsp.isNeqZero, $lhsp.isPure, nodep->isPure()}", "replaceNum(nodep,1)"); TREEOP ("AstXor {$lhsp.isAllOnes, $rhsp}", "AstNot{$rhsp}"); TREEOP ("AstMul {$lhsp.isOne, $rhsp}", "replaceWRhs(nodep)"); TREEOP ("AstMulS {$lhsp.isOne, $rhsp}", "replaceWRhs(nodep)"); TREEOP ("AstDiv {$lhsp, $rhsp.isOne}", "replaceWLhs(nodep)"); TREEOP ("AstDivS {$lhsp, $rhsp.isOne}", "replaceWLhs(nodep)"); TREEOP ("AstMul {operandIsPowTwo($lhsp), operandsSameWidth($lhsp,,$rhsp)}", "replaceMulShift(nodep)"); // a*2^n -> a< a>>n TREEOP ("AstModDiv{$lhsp, operandIsPowTwo($rhsp)}", "replaceModAnd(nodep)"); // a % 2^n -> a&(2^n-1) TREEOP ("AstPow {operandIsTwo($lhsp), !$rhsp.isZero}", "replacePowShift(nodep)"); // 2**a == 1<castAdd()->lhsp(),$rhsp}, $lhsp->castAdd()->rhsp()}"); // ((a+x)-y) -> (a+(x-y)) TREEOPC("AstAnd {$lhsp.isOne, matchRedundantClean(nodep)}", "DONE") // 1 & (a == b) -> (IData)(a == b) // Trinary ops // Note V3Case::Sel requires Cond to always be conditionally executed in C to prevent core dump! TREEOP ("AstNodeCond{$condp.isZero, $thenp, $elsep}", "replaceWChild(nodep,$elsep)"); TREEOP ("AstNodeCond{$condp.isNeqZero, $thenp, $elsep}", "replaceWChild(nodep,$thenp)"); TREEOPA("AstNodeCond{$condp.isZero, $thenp.castConst, $elsep.castConst}", "replaceWChild(nodep,$elsep)"); TREEOPA("AstNodeCond{$condp.isNeqZero, $thenp.castConst, $elsep.castConst}", "replaceWChild(nodep,$thenp)"); TREEOP ("AstNodeCond{$condp, operandsSame($thenp,,$elsep)}","replaceWChild(nodep,$thenp)"); // This visit function here must allow for short-circuiting. TREEOPS("AstCond {$condp.isZero}", "replaceWIteratedThs(nodep)"); TREEOPS("AstCond {$condp.isNeqZero}", "replaceWIteratedRhs(nodep)"); TREEOP ("AstCond{$condp.castNot, $thenp, $elsep}", "AstCond{$condp->castNot()->lhsp(), $elsep, $thenp}"); TREEOP ("AstNodeCond{$condp.width1, $thenp.width1, $thenp.isAllOnes, $elsep}", "AstLogOr {$condp, $elsep}"); // a?1:b == a||b TREEOP ("AstNodeCond{$condp.width1, $thenp.width1, $thenp, $elsep.isZero, !$elsep.isClassHandleValue}", "AstLogAnd{$condp, $thenp}"); // a?b:0 == a&&b TREEOP ("AstNodeCond{$condp.width1, $thenp.width1, $thenp, $elsep.isAllOnes}", "AstLogOr {AstNot{$condp}, $thenp}"); // a?b:1 == ~a||b TREEOP ("AstNodeCond{$condp.width1, $thenp.width1, $thenp.isZero, !$thenp.isClassHandleValue, $elsep}", "AstLogAnd{AstNot{$condp}, $elsep}"); // a?0:b == ~a&&b TREEOP ("AstNodeCond{!$condp.width1, operandBoolShift(nodep->condp())}", "replaceBoolShift(nodep->condp())"); // Prefer constants on left, since that often needs a shift, it lets // constant red remove the shift TREEOP ("AstNodeBiCom{!$lhsp.castConst, $rhsp.castConst}", "swapSides(nodep)"); TREEOP ("AstNodeBiComAsv{operandAsvConst(nodep)}", "replaceAsv(nodep)"); TREEOP ("AstNodeBiComAsv{operandAsvSame(nodep)}", "replaceAsv(nodep)"); TREEOP ("AstNodeBiComAsv{operandAsvLUp(nodep)}", "replaceAsvLUp(nodep)"); TREEOP ("AstNodeBiComAsv{operandAsvRUp(nodep)}", "replaceAsvRUp(nodep)"); TREEOP ("AstLt {!$lhsp.castConst,$rhsp.castConst}", "AstGt {$rhsp,$lhsp}"); TREEOP ("AstLtS {!$lhsp.castConst,$rhsp.castConst}", "AstGtS {$rhsp,$lhsp}"); TREEOP ("AstLte {!$lhsp.castConst,$rhsp.castConst}", "AstGte {$rhsp,$lhsp}"); TREEOP ("AstLteS {!$lhsp.castConst,$rhsp.castConst}", "AstGteS{$rhsp,$lhsp}"); TREEOP ("AstGt {!$lhsp.castConst,$rhsp.castConst}", "AstLt {$rhsp,$lhsp}"); TREEOP ("AstGtS {!$lhsp.castConst,$rhsp.castConst}", "AstLtS {$rhsp,$lhsp}"); TREEOP ("AstGte {!$lhsp.castConst,$rhsp.castConst}", "AstLte {$rhsp,$lhsp}"); TREEOP ("AstGteS {!$lhsp.castConst,$rhsp.castConst}", "AstLteS{$rhsp,$lhsp}"); // v--- *1* as These ops are always first, as we warn before replacing TREEOP1("AstLt {$lhsp, $rhsp.isZero}", "replaceNumSigned(nodep,0)"); TREEOP1("AstGte {$lhsp, $rhsp.isZero}", "replaceNumSigned(nodep,1)"); TREEOP1("AstGt {$lhsp.isZero, $rhsp}", "replaceNumSigned(nodep,0)"); TREEOP1("AstLte {$lhsp.isZero, $rhsp}", "replaceNumSigned(nodep,1)"); TREEOP1("AstGt {$lhsp, $rhsp.isAllOnes, $lhsp->width()==$rhsp->width()}", "replaceNumLimited(nodep,0)"); TREEOP1("AstLte {$lhsp, $rhsp.isAllOnes, $lhsp->width()==$rhsp->width()}", "replaceNumLimited(nodep,1)"); TREEOP1("AstLt {$lhsp.isAllOnes, $rhsp, $lhsp->width()==$rhsp->width()}", "replaceNumLimited(nodep,0)"); TREEOP1("AstGte {$lhsp.isAllOnes, $rhsp, $lhsp->width()==$rhsp->width()}", "replaceNumLimited(nodep,1)"); // Two level bubble pushing TREEOP ("AstNot {$lhsp.castNot, $lhsp->width()==VN_AS($lhsp,,Not)->lhsp()->width()}", "replaceWChild(nodep, $lhsp->castNot()->lhsp())"); // NOT(NOT(x))->x TREEOP ("AstLogNot{$lhsp.castLogNot}", "replaceWChild(nodep, $lhsp->castLogNot()->lhsp())"); // LOGNOT(LOGNOT(x))->x TREEOPV("AstNot {$lhsp.castEqCase, $lhsp.width1}","AstNeqCase{$lhsp->castEqCase()->lhsp(),$lhsp->castEqCase()->rhsp()}"); TREEOP ("AstLogNot{$lhsp.castEqCase}", "AstNeqCase{$lhsp->castEqCase()->lhsp(),$lhsp->castEqCase()->rhsp()}"); TREEOPV("AstNot {$lhsp.castNeqCase, $lhsp.width1}","AstEqCase{$lhsp->castNeqCase()->lhsp(),$lhsp->castNeqCase()->rhsp()}"); TREEOP ("AstLogNot{$lhsp.castNeqCase}", "AstEqCase {$lhsp->castNeqCase()->lhsp(),$lhsp->castNeqCase()->rhsp()}"); TREEOPV("AstNot {$lhsp.castEqWild, $lhsp.width1}","AstNeqWild{$lhsp->castEqWild()->lhsp(),$lhsp->castEqWild()->rhsp()}"); TREEOP ("AstLogNot{$lhsp.castEqWild}", "AstNeqWild{$lhsp->castEqWild()->lhsp(),$lhsp->castEqWild()->rhsp()}"); TREEOPV("AstNot {$lhsp.castNeqWild, $lhsp.width1}","AstEqWild{$lhsp->castNeqWild()->lhsp(),$lhsp->castNeqWild()->rhsp()}"); TREEOP ("AstLogNot{$lhsp.castNeqWild}", "AstEqWild {$lhsp->castNeqWild()->lhsp(),$lhsp->castNeqWild()->rhsp()}"); TREEOPV("AstNot {$lhsp.castEq, $lhsp.width1}", "AstNeq {$lhsp->castEq()->lhsp(),$lhsp->castEq()->rhsp()}"); TREEOP ("AstLogNot{$lhsp.castEq}", "AstNeq {$lhsp->castEq()->lhsp(),$lhsp->castEq()->rhsp()}"); TREEOPV("AstNot {$lhsp.castNeq, $lhsp.width1}", "AstEq {$lhsp->castNeq()->lhsp(),$lhsp->castNeq()->rhsp()}"); TREEOP ("AstLogNot{$lhsp.castNeq}", "AstEq {$lhsp->castNeq()->lhsp(),$lhsp->castNeq()->rhsp()}"); TREEOPV("AstNot {$lhsp.castLt, $lhsp.width1}", "AstGte {$lhsp->castLt()->lhsp(),$lhsp->castLt()->rhsp()}"); TREEOP ("AstLogNot{$lhsp.castLt}", "AstGte {$lhsp->castLt()->lhsp(),$lhsp->castLt()->rhsp()}"); TREEOPV("AstNot {$lhsp.castLtS, $lhsp.width1}", "AstGteS{$lhsp->castLtS()->lhsp(),$lhsp->castLtS()->rhsp()}"); TREEOP ("AstLogNot{$lhsp.castLtS}", "AstGteS{$lhsp->castLtS()->lhsp(),$lhsp->castLtS()->rhsp()}"); TREEOPV("AstNot {$lhsp.castLte, $lhsp.width1}", "AstGt {$lhsp->castLte()->lhsp(),$lhsp->castLte()->rhsp()}"); TREEOP ("AstLogNot{$lhsp.castLte}", "AstGt {$lhsp->castLte()->lhsp(),$lhsp->castLte()->rhsp()}"); TREEOPV("AstNot {$lhsp.castLteS, $lhsp.width1}", "AstGtS {$lhsp->castLteS()->lhsp(),$lhsp->castLteS()->rhsp()}"); TREEOP ("AstLogNot{$lhsp.castLteS}", "AstGtS {$lhsp->castLteS()->lhsp(),$lhsp->castLteS()->rhsp()}"); TREEOPV("AstNot {$lhsp.castGt, $lhsp.width1}", "AstLte {$lhsp->castGt()->lhsp(),$lhsp->castGt()->rhsp()}"); TREEOP ("AstLogNot{$lhsp.castGt}", "AstLte {$lhsp->castGt()->lhsp(),$lhsp->castGt()->rhsp()}"); TREEOPV("AstNot {$lhsp.castGtS, $lhsp.width1}", "AstLteS{$lhsp->castGtS()->lhsp(),$lhsp->castGtS()->rhsp()}"); TREEOP ("AstLogNot{$lhsp.castGtS}", "AstLteS{$lhsp->castGtS()->lhsp(),$lhsp->castGtS()->rhsp()}"); TREEOPV("AstNot {$lhsp.castGte, $lhsp.width1}", "AstLt {$lhsp->castGte()->lhsp(),$lhsp->castGte()->rhsp()}"); TREEOP ("AstLogNot{$lhsp.castGte}", "AstLt {$lhsp->castGte()->lhsp(),$lhsp->castGte()->rhsp()}"); TREEOPV("AstNot {$lhsp.castGteS, $lhsp.width1}", "AstLtS {$lhsp->castGteS()->lhsp(),$lhsp->castGteS()->rhsp()}"); TREEOP ("AstLogNot{$lhsp.castGteS}", "AstLtS {$lhsp->castGteS()->lhsp(),$lhsp->castGteS()->rhsp()}"); // Not common, but avoids compiler warnings about over shifting TREEOP ("AstShiftL {operandHugeShiftL(nodep)}", "replaceZero(nodep)"); TREEOP ("AstShiftLOvr{operandHugeShiftL(nodep)}", "replaceZero(nodep)"); TREEOP ("AstShiftR {operandHugeShiftR(nodep)}", "replaceZero(nodep)"); TREEOP ("AstShiftROvr{operandHugeShiftR(nodep)}", "replaceZero(nodep)"); TREEOP ("AstShiftL{operandShiftOp(nodep)}", "replaceShiftOp(nodep)"); TREEOP ("AstShiftR{operandShiftOp(nodep)}", "replaceShiftOp(nodep)"); TREEOP ("AstShiftL{operandShiftShift(nodep)}", "replaceShiftShift(nodep)"); TREEOP ("AstShiftR{operandShiftShift(nodep)}", "replaceShiftShift(nodep)"); TREEOP ("AstWordSel{operandWordOOB(nodep)}", "replaceZero(nodep)"); // Compress out EXTENDs to appease loop unroller TREEOPV("AstEq {$rhsp.castExtend,operandBiExtendConstShrink(nodep)}", "DONE"); TREEOPV("AstNeq {$rhsp.castExtend,operandBiExtendConstShrink(nodep)}", "DONE"); TREEOPV("AstGt {$rhsp.castExtend,operandBiExtendConstShrink(nodep)}", "DONE"); TREEOPV("AstGte {$rhsp.castExtend,operandBiExtendConstShrink(nodep)}", "DONE"); TREEOPV("AstLt {$rhsp.castExtend,operandBiExtendConstShrink(nodep)}", "DONE"); TREEOPV("AstLte {$rhsp.castExtend,operandBiExtendConstShrink(nodep)}", "DONE"); TREEOPV("AstEq {$rhsp.castExtend,operandBiExtendConstOver(nodep)}", "replaceZero(nodep)"); TREEOPV("AstNeq {$rhsp.castExtend,operandBiExtendConstOver(nodep)}", "replaceNum(nodep,1)"); TREEOPV("AstGt {$rhsp.castExtend,operandBiExtendConstOver(nodep)}", "replaceNum(nodep,1)"); TREEOPV("AstGte {$rhsp.castExtend,operandBiExtendConstOver(nodep)}", "replaceNum(nodep,1)"); TREEOPV("AstLt {$rhsp.castExtend,operandBiExtendConstOver(nodep)}", "replaceZero(nodep)"); TREEOPV("AstLte {$rhsp.castExtend,operandBiExtendConstOver(nodep)}", "replaceZero(nodep)"); // Identical operands on both sides // AstLogAnd/AstLogOr already converted to AstAnd/AstOr for these rules // AstAdd->ShiftL(#,1) but uncommon TREEOP ("AstAnd {operandsSame($lhsp,,$rhsp)}", "replaceWLhs(nodep)"); TREEOP ("AstDiv {operandsSame($lhsp,,$rhsp)}", "replaceNum(nodep,1)"); TREEOP ("AstDivS {operandsSame($lhsp,,$rhsp)}", "replaceNum(nodep,1)"); TREEOP ("AstOr {operandsSame($lhsp,,$rhsp)}", "replaceWLhs(nodep)"); TREEOP ("AstSub {operandsSame($lhsp,,$rhsp)}", "replaceZero(nodep)"); TREEOP ("AstXor {operandsSame($lhsp,,$rhsp)}", "replaceZero(nodep)"); TREEOP ("AstEq {operandsSame($lhsp,,$rhsp)}", "replaceNum(nodep,1)"); // We let X==X -> 1, although in a true 4-state sim it's X. TREEOP ("AstEqD {operandsSame($lhsp,,$rhsp)}", "replaceNum(nodep,1)"); // We let X==X -> 1, although in a true 4-state sim it's X. TREEOP ("AstEqN {operandsSame($lhsp,,$rhsp)}", "replaceNum(nodep,1)"); // We let X==X -> 1, although in a true 4-state sim it's X. TREEOP ("AstEqCase {operandsSame($lhsp,,$rhsp)}", "replaceNum(nodep,1)"); TREEOP ("AstEqWild {operandsSame($lhsp,,$rhsp)}", "replaceNum(nodep,1)"); TREEOP ("AstGt {operandsSame($lhsp,,$rhsp)}", "replaceZero(nodep)"); TREEOP ("AstGtD {operandsSame($lhsp,,$rhsp)}", "replaceZero(nodep)"); TREEOP ("AstGtN {operandsSame($lhsp,,$rhsp)}", "replaceZero(nodep)"); TREEOP ("AstGtS {operandsSame($lhsp,,$rhsp)}", "replaceZero(nodep)"); TREEOP ("AstGte {operandsSame($lhsp,,$rhsp)}", "replaceNum(nodep,1)"); TREEOP ("AstGteD {operandsSame($lhsp,,$rhsp)}", "replaceNum(nodep,1)"); TREEOP ("AstGteN {operandsSame($lhsp,,$rhsp)}", "replaceNum(nodep,1)"); TREEOP ("AstGteS {operandsSame($lhsp,,$rhsp)}", "replaceNum(nodep,1)"); TREEOP ("AstLt {operandsSame($lhsp,,$rhsp)}", "replaceZero(nodep)"); TREEOP ("AstLtD {operandsSame($lhsp,,$rhsp)}", "replaceZero(nodep)"); TREEOP ("AstLtN {operandsSame($lhsp,,$rhsp)}", "replaceZero(nodep)"); TREEOP ("AstLtS {operandsSame($lhsp,,$rhsp)}", "replaceZero(nodep)"); TREEOP ("AstLte {operandsSame($lhsp,,$rhsp)}", "replaceNum(nodep,1)"); TREEOP ("AstLteD {operandsSame($lhsp,,$rhsp)}", "replaceNum(nodep,1)"); TREEOP ("AstLteN {operandsSame($lhsp,,$rhsp)}", "replaceNum(nodep,1)"); TREEOP ("AstLteS {operandsSame($lhsp,,$rhsp)}", "replaceNum(nodep,1)"); TREEOP ("AstNeq {operandsSame($lhsp,,$rhsp)}", "replaceZero(nodep)"); TREEOP ("AstNeqD {operandsSame($lhsp,,$rhsp)}", "replaceZero(nodep)"); TREEOP ("AstNeqN {operandsSame($lhsp,,$rhsp)}", "replaceZero(nodep)"); TREEOP ("AstNeqCase{operandsSame($lhsp,,$rhsp)}", "replaceZero(nodep)"); TREEOP ("AstNeqWild{operandsSame($lhsp,,$rhsp)}", "replaceZero(nodep)"); TREEOP ("AstLogAnd {operandsSame($lhsp,,$rhsp)}", "replaceWLhsBool(nodep)"); TREEOP ("AstLogOr {operandsSame($lhsp,,$rhsp)}", "replaceWLhsBool(nodep)"); ///=== Verilog operators // Comparison against 1'b0/1'b1; must be careful about widths. // These use Not, so must be Verilog only TREEOPV("AstEq {$rhsp.width1, $lhsp.isZero, $rhsp}", "AstNot{$rhsp}"); TREEOPV("AstEq {$lhsp.width1, $lhsp, $rhsp.isZero}", "AstNot{$lhsp}"); TREEOPV("AstEq {$rhsp.width1, $lhsp.isAllOnes, $rhsp}", "replaceWRhs(nodep)"); TREEOPV("AstEq {$lhsp.width1, $lhsp, $rhsp.isAllOnes}", "replaceWLhs(nodep)"); TREEOPV("AstNeq {$rhsp.width1, $lhsp.isZero, $rhsp}", "replaceWRhs(nodep)"); TREEOPV("AstNeq {$lhsp.width1, $lhsp, $rhsp.isZero}", "replaceWLhs(nodep)"); TREEOPV("AstNeq {$rhsp.width1, $lhsp.isAllOnes, $rhsp}", "AstNot{$rhsp}"); TREEOPV("AstNeq {$lhsp.width1, $lhsp, $rhsp.isAllOnes}", "AstNot{$lhsp}"); TREEOPV("AstLt {$rhsp.width1, $lhsp.isZero, $rhsp}", "replaceWRhs(nodep)"); // Because not signed #s TREEOPV("AstGt {$lhsp.width1, $lhsp, $rhsp.isZero}", "replaceWLhs(nodep)"); // Because not signed #s // Useful for CONDs added around ARRAYSEL's in V3Case step TREEOPV("AstLte {$lhsp->width()==$rhsp->width(), $rhsp.isAllOnes}", "replaceNum(nodep,1)"); // Simplify reduction operators // This also gets &{...,0,....} => const 0 (Common for unused_ok signals) TREEOPV("AstRedAnd{$lhsp, $lhsp.width1}", "replaceWLhs(nodep)"); TREEOPV("AstRedOr {$lhsp, $lhsp.width1}", "replaceWLhs(nodep)"); TREEOPV("AstRedXor{$lhsp, $lhsp.width1}", "replaceWLhs(nodep)"); TREEOPV("AstRedAnd{$lhsp.castConcat}", "AstAnd{AstRedAnd{$lhsp->castConcat()->lhsp()}, AstRedAnd{$lhsp->castConcat()->rhsp()}}"); // &{a,b} => {&a}&{&b} TREEOPV("AstRedOr {$lhsp.castConcat}", "AstOr {AstRedOr {$lhsp->castConcat()->lhsp()}, AstRedOr {$lhsp->castConcat()->rhsp()}}"); // |{a,b} => {|a}|{|b} TREEOPV("AstRedXor{$lhsp.castConcat}", "AstXor{AstRedXor{$lhsp->castConcat()->lhsp()}, AstRedXor{$lhsp->castConcat()->rhsp()}}"); // ^{a,b} => {^a}^{^b} TREEOPV("AstRedAnd{$lhsp.castExtend, $lhsp->width() > VN_AS($lhsp,,Extend)->lhsp()->width()}", "replaceZero(nodep)"); // &{0,...} => 0 Prevents compiler limited range error TREEOPV("AstRedOr {$lhsp.castExtend}", "AstRedOr {$lhsp->castExtend()->lhsp()}"); TREEOPV("AstRedXor{$lhsp.castExtend}", "AstRedXor{$lhsp->castExtend()->lhsp()}"); TREEOP ("AstRedXor{$lhsp.castXor, VN_IS(VN_AS($lhsp,,Xor)->lhsp(),,Const)}", "AstXor{AstRedXor{$lhsp->castXor()->lhsp()}, AstRedXor{$lhsp->castXor()->rhsp()}}"); // ^(const ^ a) => (^const)^(^a) TREEOPC("AstAnd {$lhsp.castConst, $rhsp.castRedXor, matchBitOpTree(nodep)}", "DONE"); TREEOPV("AstOneHot{$lhsp.width1}", "replaceWLhs(nodep)"); TREEOPV("AstOneHot0{$lhsp.width1}", "replaceNum(nodep,1)"); // Binary AND/OR is faster than logical and/or (usually) TREEOPV("AstLogAnd{matchBiopToBitwise(nodep)}", "AstAnd{$lhsp,$rhsp}"); TREEOPV("AstLogOr {matchBiopToBitwise(nodep)}", "AstOr{$lhsp,$rhsp}"); TREEOPV("AstLogNot{$lhsp.width1}", "AstNot{$lhsp}"); // CONCAT(CONCAT({a},{b}),{c}) -> CONCAT({a},CONCAT({b},{c})) // CONCAT({const},CONCAT({const},{c})) -> CONCAT((constifiedCONC{const|const},{c})) TREEOPV("AstConcat{matchConcatRand(nodep)}", "DONE"); TREEOPV("AstConcat{operandConcatMove(nodep)}", "moveConcat(nodep)"); TREEOPV("AstConcat{$lhsp.isZero, $rhsp}", "replaceExtend(nodep, nodep->rhsp())"); // CONCAT(a[1],a[0]) -> a[1:0] TREEOPV("AstConcat{$lhsp.castSel, $rhsp.castSel, ifAdjacentSel(VN_AS($lhsp,,Sel),,VN_AS($rhsp,,Sel))}", "replaceConcatSel(nodep)"); TREEOPV("AstConcat{ifConcatMergeableBiop($lhsp), concatMergeable($lhsp,,$rhsp,,0)}", "replaceConcatMerge(nodep)"); // Common two-level operations that can be simplified TREEOP ("AstAnd {$lhsp.castConst,matchAndCond(nodep)}", "DONE"); TREEOP ("AstAnd {$lhsp.castConst, $rhsp.castOr, matchMaskedOr(nodep)}", "DONE"); TREEOPC("AstAnd {$lhsp.castConst, matchMaskedShift(nodep)}", "DONE"); TREEOP ("AstAnd {$lhsp.castOr, $rhsp.castOr, operandAndOrSame(nodep)}", "replaceAndOr(nodep)"); TREEOP ("AstOr {$lhsp.castAnd,$rhsp.castAnd,operandAndOrSame(nodep)}", "replaceAndOr(nodep)"); TREEOP ("AstOr {matchOrAndNot(nodep)}", "DONE"); TREEOP ("AstAnd {operandShiftSame(nodep)}", "replaceShiftSame(nodep)"); TREEOP ("AstOr {operandShiftSame(nodep)}", "replaceShiftSame(nodep)"); TREEOP ("AstXor {operandShiftSame(nodep)}", "replaceShiftSame(nodep)"); TREEOPC("AstAnd {matchBitOpTree(nodep)}", "DONE"); TREEOPC("AstOr {matchBitOpTree(nodep)}", "DONE"); TREEOPC("AstXor {matchBitOpTree(nodep)}", "DONE"); // Note can't simplify a extend{extends}, extends{extend}, as the sign // bits end up in the wrong places TREEOPV("AstExtend{operandsSameWidth(nodep,,$lhsp)}", "replaceWLhs(nodep)"); TREEOPV("AstExtend{$lhsp.castExtend}", "replaceExtend(nodep, VN_AS(nodep->lhsp(), Extend)->lhsp())"); TREEOPV("AstExtendS{$lhsp.castExtendS}", "replaceExtend(nodep, VN_AS(nodep->lhsp(), ExtendS)->lhsp())"); TREEOPV("AstReplicate{$srcp, $countp.isOne, $srcp->width()==nodep->width()}", "replaceWLhs(nodep)"); // {1{lhs}}->lhs TREEOPV("AstReplicateN{$lhsp, $rhsp.isOne, $lhsp->width()==nodep->width()}", "replaceWLhs(nodep)"); // {1{lhs}}->lhs TREEOPV("AstReplicate{$srcp.castReplicate, operandRepRep(nodep)}", "DONE"); // {2{3{lhs}}}->{6{lhs}} TREEOPV("AstConcat{operandConcatSame(nodep)}", "DONE"); // {a,a}->{2{a}}, {a,2{a}}->{3{a}, etc // Next rule because AUTOINST puts the width of bits in // to pins, even when the widths are exactly the same across the hierarchy. TREEOPV("AstSel{matchSelRand(nodep)}", "DONE"); TREEOPV("AstSel{operandSelExtend(nodep)}", "DONE"); TREEOPV("AstSel{operandSelFull(nodep)}", "replaceWChild(nodep, nodep->fromp())"); TREEOPV("AstSel{$fromp.castSel}", "replaceSelSel(nodep)"); TREEOPV("AstSel{$fromp.castAdd, operandSelBiLower(nodep)}", "DONE"); TREEOPV("AstSel{$fromp.castAnd, operandSelBiLower(nodep)}", "DONE"); TREEOPV("AstSel{$fromp.castOr, operandSelBiLower(nodep)}", "DONE"); TREEOPV("AstSel{$fromp.castSub, operandSelBiLower(nodep)}", "DONE"); TREEOPV("AstSel{$fromp.castXor, operandSelBiLower(nodep)}", "DONE"); TREEOPV("AstSel{$fromp.castShiftR, operandSelShiftLower(nodep)}", "DONE"); TREEOPA("AstSel{$fromp.castConst, $lsbp.castConst, $widthp.castConst, }", "replaceConst(nodep)"); TREEOPV("AstSel{$fromp.castConcat, $lsbp.castConst, $widthp.castConst, }", "replaceSelConcat(nodep)"); TREEOPV("AstSel{$fromp.castReplicate, $lsbp.castConst, $widthp.castConst, operandSelReplicate(nodep) }", "DONE"); // V3Tristate requires selects below BufIf1. // Also do additional operators that are bit-independent, but only definite // win if bit select is a constant (otherwise we may need to compute bit index several times) TREEOPV("AstSel{$fromp.castBufIf1}", "replaceSelIntoBiop(nodep)"); TREEOPV("AstSel{$fromp.castNot}", "replaceSelIntoUniop(nodep)"); TREEOPV("AstSel{$fromp.castAnd,$lsbp.castConst}", "replaceSelIntoBiop(nodep)"); TREEOPV("AstSel{$fromp.castOr,$lsbp.castConst}", "replaceSelIntoBiop(nodep)"); TREEOPV("AstSel{$fromp.castXor,$lsbp.castConst}", "replaceSelIntoBiop(nodep)"); // This visit function here must allow for short-circuiting. TREEOPS("AstLogIf{$lhsp.isZero}", "replaceNum(nodep, 1)"); TREEOPV("AstLogIf{$lhsp, $rhsp}", "AstLogOr{AstLogNot{$lhsp},$rhsp}"); TREEOPV("AstLogEq{$lhsp, $rhsp}", "replaceLogEq(nodep)"); // Strings TREEOPA("AstPutcN{$lhsp.castConst, $rhsp.castConst, $thsp.castConst}", "replaceConst(nodep)"); TREEOPA("AstSubstrN{$lhsp.castConst, $rhsp.castConst, $thsp.castConst}", "replaceConst(nodep)"); TREEOPA("AstCvtPackString{$lhsp.castConst}", "replaceConstString(nodep, VN_AS(nodep->lhsp(), Const)->num().toString())"); // Custom // Implied by AstIsUnbounded::numberOperate: V("AstIsUnbounded{$lhsp.castConst}", "replaceNum(nodep, 0)"); TREEOPV("AstIsUnbounded{$lhsp.castUnbounded}", "replaceNum(nodep, 1)"); // clang-format on // Possible futures: // (a?(b?y:x):y) -> (a&&!b)?x:y // (a?(b?x:y):y) -> (a&&b)?x:y // (a?x:(b?x:y)) -> (a||b)?x:y // (a?x:(b?y:x)) -> (a||!b)?x:y // Note we can't convert EqCase/NeqCase to Eq/Neq here because that would break 3'b1x1==3'b101 //----- void visit(AstNode* nodep) override { // Default: Just iterate if (m_required) { if (VN_IS(nodep, NodeDType) || VN_IS(nodep, Range) || VN_IS(nodep, SliceSel)) { // Ignore dtypes for parameter type pins } else { nodep->v3error("Expecting expression to be constant, but can't convert a " << nodep->prettyTypeName() << " to constant."); } } else { if (nodep->isTimingControl()) m_hasJumpDelay = true; // Calculate the width of this operation if (m_params && !nodep->width()) nodep = V3Width::widthParamsEdit(nodep); iterateChildren(nodep); } } public: // Processing Mode Enum enum ProcMode : uint8_t { PROC_PARAMS_NOWARN, PROC_PARAMS, PROC_GENERATE, PROC_LIVE, PROC_V_WARN, PROC_V_NOWARN, PROC_V_EXPENSIVE, PROC_CPP }; // CONSTRUCTORS ConstVisitor(ProcMode pmode, bool globalPass) : m_globalPass{globalPass} , m_concswapNames{globalPass ? ("__Vconcswap_" + cvtToStr(s_globalPassNum++)) : ""} { // clang-format off switch (pmode) { case PROC_PARAMS_NOWARN: m_doV = true; m_doNConst = true; m_params = true; m_required = false; break; case PROC_PARAMS: m_doV = true; m_doNConst = true; m_params = true; m_required = true; break; case PROC_GENERATE: m_doV = true; m_doNConst = true; m_params = true; m_required = true; m_doGenerate = true; break; case PROC_LIVE: break; case PROC_V_WARN: m_doV = true; m_doNConst = true; m_warn = true; m_convertLogicToBit = true; break; case PROC_V_NOWARN: m_doV = true; m_doNConst = true; break; case PROC_V_EXPENSIVE: m_doV = true; m_doNConst = true; m_doExpensive = true; break; case PROC_CPP: m_doV = false; m_doNConst = true; m_doCpp = true; break; default: v3fatalSrc("Bad case"); break; } // clang-format on } ~ConstVisitor() override { if (m_doCpp) { if (m_globalPass) { V3Stats::addStat("Optimizations, Const bit op reduction", m_statBitOpReduction); } else { V3Stats::addStatSum("Optimizations, Const bit op reduction", m_statBitOpReduction); } } } AstNode* mainAcceptEdit(AstNode* nodep) { VIsCached::clearCacheTree(); // Avoid using any stale isPure // Operate starting at a random place return iterateSubtreeReturnEdits(nodep); } }; uint32_t ConstVisitor::s_globalPassNum = 0; //###################################################################### // Const class functions //! Force this cell node's parameter list to become a constant //! @return Pointer to the edited node. AstNode* V3Const::constifyParamsEdit(AstNode* nodep) { // if (debug() > 0) nodep->dumpTree("- forceConPRE : "); // Resize even if the node already has a width, because buried in the tree // we may have a node we just created with signing, etc, that isn't sized yet. // Make sure we've sized everything first nodep = V3Width::widthParamsEdit(nodep); ConstVisitor visitor{ConstVisitor::PROC_PARAMS, /* globalPass: */ false}; if (AstVar* const varp = VN_CAST(nodep, Var)) { // If a var wants to be constified, it's really a param, and // we want the value to be constant. We aren't passed just the // init value because we need widthing above to handle the var's type. if (varp->valuep()) visitor.mainAcceptEdit(varp->valuep()); } else { nodep = visitor.mainAcceptEdit(nodep); } // Because we do edits, nodep links may get trashed and core dump this. // if (debug() > 0) nodep->dumpTree("- forceConDONE: "); return nodep; } //! Constify this cell node's parameter list if possible //! @return Pointer to the edited node. AstNode* V3Const::constifyParamsNoWarnEdit(AstNode* nodep) { // if (debug() > 0) nodep->dumpTree("- forceConPRE : "); // Resize even if the node already has a width, because buried in the tree // we may have a node we just created with signing, etc, that isn't sized yet. // Make sure we've sized everything first nodep = V3Width::widthParamsEdit(nodep); ConstVisitor visitor{ConstVisitor::PROC_PARAMS_NOWARN, /* globalPass: */ false}; if (AstVar* const varp = VN_CAST(nodep, Var)) { // If a var wants to be constified, it's really a param, and // we want the value to be constant. We aren't passed just the // init value because we need widthing above to handle the var's type. if (varp->valuep()) visitor.mainAcceptEdit(varp->valuep()); } else { nodep = visitor.mainAcceptEdit(nodep); } // Because we do edits, nodep links may get trashed and core dump this. // if (debug() > 0) nodep->dumpTree("- forceConDONE: "); return nodep; } //! Force this cell node's parameter list to become a constant inside generate. //! If we are inside a generated "if", "case" or "for", we don't want to //! trigger warnings when we deal with the width. It is possible that these //! are spurious, existing within sub-expressions that will not actually be //! generated. Since such occurrences, must be constant, in order to be //! something a generate block can depend on, we can wait until later to do the //! width check. //! @return Pointer to the edited node. AstNode* V3Const::constifyGenerateParamsEdit(AstNode* nodep) { // if (debug() > 0) nodep->dumpTree("- forceConPRE:: "); // Resize even if the node already has a width, because buried in the tree // we may have a node we just created with signing, etc, that isn't sized // yet. // Make sure we've sized everything first nodep = V3Width::widthGenerateParamsEdit(nodep); ConstVisitor visitor{ConstVisitor::PROC_GENERATE, /* globalPass: */ false}; if (AstVar* const varp = VN_CAST(nodep, Var)) { // If a var wants to be constified, it's really a param, and // we want the value to be constant. We aren't passed just the // init value because we need widthing above to handle the var's type. if (varp->valuep()) visitor.mainAcceptEdit(varp->valuep()); } else { nodep = visitor.mainAcceptEdit(nodep); } // Because we do edits, nodep links may get trashed and core dump this. // if (debug() > 0) nodep->dumpTree("- forceConDONE: "); return nodep; } void V3Const::constifyAllLint(AstNetlist* nodep) { // Only call from Verilator.cpp, as it uses user#'s UINFO(2, __FUNCTION__ << ": " << endl); { ConstVisitor visitor{ConstVisitor::PROC_V_WARN, /* globalPass: */ true}; (void)visitor.mainAcceptEdit(nodep); } // Destruct before checking V3Global::dumpCheckGlobalTree("const", 0, dumpTreeEitherLevel() >= 3); } void V3Const::constifyCpp(AstNetlist* nodep) { UINFO(2, __FUNCTION__ << ": " << endl); { ConstVisitor visitor{ConstVisitor::PROC_CPP, /* globalPass: */ true}; (void)visitor.mainAcceptEdit(nodep); } // Destruct before checking V3Global::dumpCheckGlobalTree("const_cpp", 0, dumpTreeEitherLevel() >= 3); } AstNode* V3Const::constifyEdit(AstNode* nodep) { ConstVisitor visitor{ConstVisitor::PROC_V_NOWARN, /* globalPass: */ false}; nodep = visitor.mainAcceptEdit(nodep); return nodep; } AstNode* V3Const::constifyEditCpp(AstNode* nodep) { ConstVisitor visitor{ConstVisitor::PROC_CPP, /* globalPass: */ false}; nodep = visitor.mainAcceptEdit(nodep); return nodep; } void V3Const::constifyAllLive(AstNetlist* nodep) { // Only call from Verilator.cpp, as it uses user#'s // This only pushes constants up, doesn't make any other edits // IE doesn't prune dead statements, as we need to do some usability checks after this UINFO(2, __FUNCTION__ << ": " << endl); { ConstVisitor visitor{ConstVisitor::PROC_LIVE, /* globalPass: */ true}; (void)visitor.mainAcceptEdit(nodep); } // Destruct before checking V3Global::dumpCheckGlobalTree("const", 0, dumpTreeEitherLevel() >= 3); } void V3Const::constifyAll(AstNetlist* nodep) { // Only call from Verilator.cpp, as it uses user#'s UINFO(2, __FUNCTION__ << ": " << endl); { ConstVisitor visitor{ConstVisitor::PROC_V_EXPENSIVE, /* globalPass: */ true}; (void)visitor.mainAcceptEdit(nodep); } // Destruct before checking V3Global::dumpCheckGlobalTree("const", 0, dumpTreeEitherLevel() >= 3); } AstNode* V3Const::constifyExpensiveEdit(AstNode* nodep) { ConstVisitor visitor{ConstVisitor::PROC_V_EXPENSIVE, /* globalPass: */ false}; nodep = visitor.mainAcceptEdit(nodep); return nodep; }