diff --git a/src/V3AstNodeDType.h b/src/V3AstNodeDType.h index 1253c8527..4600911d2 100644 --- a/src/V3AstNodeDType.h +++ b/src/V3AstNodeDType.h @@ -170,6 +170,10 @@ public: void generic(bool flag) { m_generic = flag; } std::pair dimensions(bool includeBasic) const; uint32_t arrayUnpackedElements() const; // 1, or total multiplication of all dimensions + // Fixed aggregate streaming properties + bool isStreamableFixedAggregate() const; + bool containsUnpackedStruct() const; + int widthStream() const; static int uniqueNumInc() { return ++s_uniqueNum; } const char* charIQWN() const { return (isString() ? "N" : isWide() ? "W" : isDouble() ? "D" : isQuad() ? "Q" : "I"); diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index e98c9bb89..746bd4595 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -1259,6 +1259,47 @@ uint32_t AstNodeDType::arrayUnpackedElements() const { return entries; } +bool AstNodeDType::isStreamableFixedAggregate() const { + const AstNodeDType* const dtypep = skipRefp(); + if (const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) { + return adtypep->subDTypep()->isStreamableFixedAggregate(); + } else if (const AstNodeUOrStructDType* const sdtypep = VN_CAST(dtypep, NodeUOrStructDType)) { + if (sdtypep->packed()) return true; + if (!VN_IS(sdtypep, StructDType)) return false; + for (const AstMemberDType* itemp = sdtypep->membersp(); itemp; + itemp = VN_AS(itemp->nextp(), MemberDType)) { + if (!itemp->dtypep()->isStreamableFixedAggregate()) return false; + } + return true; + } + return dtypep->isIntegralOrPacked() || dtypep->isDouble(); +} + +bool AstNodeDType::containsUnpackedStruct() const { + const AstNodeDType* const dtypep = skipRefp(); + if (const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) { + return adtypep->subDTypep()->containsUnpackedStruct(); + } + const AstStructDType* const sdtypep = VN_CAST(dtypep, StructDType); + return sdtypep && !sdtypep->packed(); +} + +int AstNodeDType::widthStream() const { + const AstNodeDType* const dtypep = skipRefp(); + if (const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) { + return adtypep->subDTypep()->widthStream() * adtypep->elementsConst(); + } else if (const AstNodeUOrStructDType* const sdtypep = VN_CAST(dtypep, NodeUOrStructDType)) { + if (!VN_IS(sdtypep, StructDType) || sdtypep->packed()) return width(); + int width = 0; + for (const AstMemberDType* itemp = sdtypep->membersp(); itemp; + itemp = VN_AS(itemp->nextp(), MemberDType)) { + width += itemp->dtypep()->widthStream(); + } + return width; + } + return dtypep->width(); +} + std::pair AstNodeDType::dimensions(bool includeBasic) const { // How many array dimensions (packed,unpacked) does this Var have? uint32_t packed = 0; diff --git a/src/V3Case.cpp b/src/V3Case.cpp index 9fc489c4d..65386df2a 100644 --- a/src/V3Case.cpp +++ b/src/V3Case.cpp @@ -129,10 +129,8 @@ public: // Case state, as a visitor of each AstNode class CaseVisitor final : public VNVisitor { - // Maximum width we can check for overlaps/exhaustiveness - constexpr static int CASE_OVERLAP_WIDTH = 16; - // Maximum number of case values for exhaustive analysis/optimization - constexpr static int CASE_MAX_VALUES = 1 << CASE_OVERLAP_WIDTH; + // Maximum width for detailed analysis + constexpr static int CASE_DETAILS_MAX_WIDTH = 16; // Levels of priority to be ORed together in top IF tree constexpr static int CASE_ENCODER_GROUP_DEPTH = 8; @@ -145,17 +143,37 @@ class CaseVisitor final : public VNVisitor { }; // STATE - VDouble0 m_statCaseFast; // Statistic tracking - VDouble0 m_statCaseSlow; // Statistic tracking + // Statistics tracking, as a struct so can be passed to 'const' methods + struct Stats final { + VDouble0 caseFast; // Cases using fast bit tree method + VDouble0 caseGeneric; // Cases using generic if/else tree method + VDouble0 provenAssertions; // Assertions proven to hold + } m_stats; const AstNode* m_alwaysp = nullptr; // Always in which case is located - // Per-CASE - bool m_caseExhaustive = false; // Proven exhaustive - bool m_caseNoOverlaps = false; // Proven no overlaps between cases - // Map from value (index) to the CaseRecord that covers this value - std::array m_value2CaseRecord; + // STATE - per AstCase. Update by 'analyzeCase', treat 'const' otherwise + bool m_caseOpaque = false; // Case statement is opaque (non-packed, or non-const conditions) + size_t m_caseNConditions = 0; // Number of conditions in the case statement + bool m_caseDetailsValid = false; // Indicates m_caseDetails is valid + struct final { + bool exhaustive = false; // Proven exhaustive + bool noOverlaps = false; // Proven no overlaps between cases + // Map from value (index) to the CaseRecord that covers this value + std::array records; + } m_caseDetails; // METHODS + + // Xs in case or casez are impossible due to two state simulations. + // Returns true if the item is never possible + static bool neverItem(const AstCase* casep, const AstNodeExpr* itemExprp) { + const AstConst* const constp = VN_CAST(itemExprp, Const); + if (!constp) return false; + if (casep->casex() || casep->caseInside()) return false; + if (casep->casez()) return constp->num().isAnyX(); + return constp->num().isFourState(); + } + // Determine whether we should check case items are complete // Returns enum's dtype if should check, nullptr if shouldn't static const AstEnumDType* getEnumCompletionCheckDType(const AstCase* const nodep) { @@ -185,7 +203,7 @@ class CaseVisitor final : public VNVisitor { // Check all cases to see if they cover this enum value/pattern for (uint32_t i = 0; i < numCases; ++i) { if ((i & mask) != val) continue; // This case is not for this enum value - if (m_value2CaseRecord[i].itemp) continue; // Covered case + if (m_caseDetails.records[i].itemp) continue; // Covered case // Warn unless unique0 case which allows no-match if (!nodep->unique0Pragma()) { nodep->v3warn(CASEINCOMPLETE, @@ -203,7 +221,7 @@ class CaseVisitor final : public VNVisitor { bool checkExhaustivePacked(AstCase* nodep) { const uint32_t numCases = 1UL << nodep->exprp()->width(); for (uint32_t i = 0; i < numCases; ++i) { - if (m_value2CaseRecord[i].itemp) continue; // Covered case + if (m_caseDetails.records[i].itemp) continue; // Covered case if (!nodep->unique0Pragma()) { nodep->v3warn(CASEINCOMPLETE, "Case values incompletely covered (example pattern 0x" << std::hex @@ -224,50 +242,28 @@ class CaseVisitor final : public VNVisitor { return checkExhaustivePacked(nodep); } - bool isCaseTreeFast(AstCase* nodep) { - m_caseExhaustive = true; // TODO: we haven't proven this yet, but is as was before - m_caseNoOverlaps = false; - - AstNode* const caseExprp = nodep->exprp(); - if (caseExprp->isDouble() || caseExprp->isString()) return false; - - const int caseWidth = caseExprp->width(); - if (!caseWidth) return false; - if (caseWidth > CASE_OVERLAP_WIDTH) return false; - - int caseConditions = 0; - - for (AstCaseItem* cip = nodep->itemsp(); cip; cip = VN_AS(cip->nextp(), CaseItem)) { - for (AstNode* condp = cip->condsp(); condp; condp = condp->nextp()) { - // Can't do anything with non-constants - if (!VN_IS(condp, Const)) return false; - // Count conditions - ++caseConditions; - } - } - - UINFO(8, "Simple case statement: " << nodep); - const uint32_t numCases = 1UL << caseWidth; - // Zero list of items for each value - for (uint32_t i = 0; i < numCases; ++i) { - m_value2CaseRecord[i].itemp = nullptr; - m_value2CaseRecord[i].constp = nullptr; - m_value2CaseRecord[i].stmtsp = nullptr; + // Analyze each value in the case statement. Updates 'm_caseDetails' and issues warnings. + void analyzeCaseDetails(AstCase* nodep) { + const uint32_t numValues = 1UL << nodep->exprp()->width(); + // Clear case records + for (uint32_t i = 0; i < numValues; ++i) { + m_caseDetails.records[i].itemp = nullptr; + m_caseDetails.records[i].constp = nullptr; + m_caseDetails.records[i].stmtsp = nullptr; } // Now pick up the values for each assignment // We can cheat and use uint32_t's because we only support narrow case's bool reportedOverlap = false; - bool reportedSubcase = false; bool hasDefault = false; - m_caseNoOverlaps = true; + m_caseDetails.noOverlaps = true; for (AstCaseItem* itemp = nodep->itemsp(); itemp; itemp = VN_AS(itemp->nextp(), CaseItem)) { // Default case if (itemp->isDefault()) { // Default was moved to be the last item by V3LinkDot. Fill remaining cases - for (uint32_t i = 0; i < numCases; ++i) { - CaseRecord& caseRecord = m_value2CaseRecord[i]; + for (uint32_t i = 0; i < numValues; ++i) { + CaseRecord& caseRecord = m_caseDetails.records[i]; if (!caseRecord.itemp) { caseRecord.itemp = itemp; caseRecord.stmtsp = itemp->stmtsp(); @@ -293,10 +289,10 @@ class CaseVisitor final : public VNVisitor { bool foundNewCase = false; const AstConst* firstOverlapConstp = nullptr; uint32_t firstOverlapValue = 0; - for (uint32_t i = 0; i < numCases; ++i) { + for (uint32_t i = 0; i < numValues; ++i) { if ((i & mask) != val) continue; - CaseRecord& caseRecord = m_value2CaseRecord[i]; + CaseRecord& caseRecord = m_caseDetails.records[i]; // If this is the first case that covers this value, record it if (!caseRecord.itemp) { @@ -314,15 +310,21 @@ class CaseVisitor final : public VNVisitor { if (!firstOverlapConstp) { firstOverlapConstp = caseRecord.constp; firstOverlapValue = i; - m_caseNoOverlaps = false; + m_caseDetails.noOverlaps = false; } } + + // Only report first overlap + if (reportedOverlap || !firstOverlapConstp) continue; + + // Report first overlap if (nodep->priorityPragma()) { - // If this is a priority case, we only want to complain if every possible value - // for this item is already hit by some other item. This is true if - // 'foundNewCase' is false. 'firstOverlapConstp' is null when the only covering - // item is this item itself, which is legal overlap within one item. - if (!reportedSubcase && !foundNewCase && firstOverlapConstp) { + // If this is a priority case, we only want to complain if every possible + // value for this item is already hit by some other item. This is true if + // 'foundNewCase' is false. 'firstOverlapConstp' is null when the only + // covering item is this item itself, which is legal overlap within one + // item. + if (!foundNewCase) { iconstp->v3warn(CASEOVERLAP, "Case item ignored: every matching value is covered " "by an earlier condition\n" @@ -330,59 +332,76 @@ class CaseVisitor final : public VNVisitor { << firstOverlapConstp->warnOther() << "... Location of previous condition\n" << firstOverlapConstp->warnContextPrimary()); - reportedSubcase = true; + reportedOverlap = true; } } else { // If this case statement doesn't have the priority keyword, // we want to warn on any overlap. - if (!reportedOverlap && firstOverlapConstp) { - std::ostringstream examplePattern; - if (iconstp->num().isAnyXZ()) { - examplePattern << " (example pattern 0x" << std::hex - << firstOverlapValue << ")"; - } - iconstp->v3warn(CASEOVERLAP, - "Case conditions overlap" - << examplePattern.str() << "\n" - << iconstp->warnContextPrimary() << '\n' - << firstOverlapConstp->warnOther() - << "... Location of overlapping condition\n" - << firstOverlapConstp->warnContextSecondary()); - reportedOverlap = true; + std::ostringstream examplePattern; + if (iconstp->num().isAnyXZ()) { + examplePattern << " (example pattern 0x" << std::hex << firstOverlapValue + << ")"; } + iconstp->v3warn(CASEOVERLAP, + "Case conditions overlap" + << examplePattern.str() << "\n" + << iconstp->warnContextPrimary() << '\n' + << firstOverlapConstp->warnOther() + << "... Location of overlapping condition\n" + << firstOverlapConstp->warnContextSecondary()); + reportedOverlap = true; } } } // If there was no default, check exhaustiveness - m_caseExhaustive = hasDefault || checkExhaustive(nodep); - if (!m_caseExhaustive) { - m_caseNoOverlaps = false; - return false; + m_caseDetails.exhaustive = hasDefault || checkExhaustive(nodep); + // Records now valid + m_caseDetailsValid = true; + } + + // Analyze case statement. Updates 'm_case*' members. Reports warnings. + void analyzeCase(AstCase* nodep) { + // Reset all analysis results + m_caseOpaque = false; + m_caseNConditions = 0; + m_caseDetailsValid = false; + + AstNode* const caseExprp = nodep->exprp(); + + // Mark opaque if not a packed value - TODO: can this be a class? + if (caseExprp->isDouble() || caseExprp->isString()) m_caseOpaque = true; + + // Check each condition expression + for (AstCaseItem* cip = nodep->itemsp(); cip; cip = VN_AS(cip->nextp(), CaseItem)) { + for (AstNode* condp = cip->condsp(); condp; condp = condp->nextp()) { + // Count conditions + ++m_caseNConditions; + // Mark opaque if non-constant condition + if (!VN_IS(condp, Const)) m_caseOpaque = true; + } } - if (caseConditions <= 3 - // Avoid e.g. priority expanders from going crazy in expansion - || (caseWidth >= 8 && (caseConditions <= (caseWidth + 1)))) { - return false; // Not worth simplifying - } + // Nothing else to do if not a packed type, or non-const conditions + if (m_caseOpaque) return; - return true; // All is fine + // If small enough, analyse details + if (caseExprp->width() <= CASE_DETAILS_MAX_WIDTH) analyzeCaseDetails(nodep); } // TODO: should return AstNodeStmt after #6280 - AstNode* replaceCaseFastRecurse(AstNodeExpr* cexprp, int msb, uint32_t upperValue) { + AstNode* convertCaseFastRecurse(AstNodeExpr* cexprp, int msb, uint32_t upperValue) const { // Base case: If reached the last bit, upperValue equals an exact value, just return // the statements from that CaseItem. Note: Not cloning here as the caller will do // an identity check. - if (msb < 0) return m_value2CaseRecord[upperValue].stmtsp; + if (msb < 0) return m_caseDetails.records[upperValue].stmtsp; // Recursive case: // Make left and right subtrees assuming cexpr[msb] is 0 and 1 respectively const uint32_t upperValue0 = upperValue; const uint32_t upperValue1 = upperValue | (1UL << msb); - AstNode* tree0p = replaceCaseFastRecurse(cexprp, msb - 1, upperValue0); - AstNode* tree1p = replaceCaseFastRecurse(cexprp, msb - 1, upperValue1); + AstNode* tree0p = convertCaseFastRecurse(cexprp, msb - 1, upperValue0); + AstNode* tree1p = convertCaseFastRecurse(cexprp, msb - 1, upperValue1); // If same logic on both sides, we can just return one of them if (tree0p == tree1p) return tree0p; @@ -391,7 +410,7 @@ class CaseVisitor final : public VNVisitor { { bool same = true; for (uint32_t a = upperValue0, b = upperValue1; a < upperValue1; ++a, ++b) { - if (m_value2CaseRecord[a].stmtsp != m_value2CaseRecord[b].stmtsp) { + if (m_caseDetails.records[a].stmtsp != m_caseDetails.records[b].stmtsp) { same = false; break; } @@ -414,23 +433,23 @@ class CaseVisitor final : public VNVisitor { return ifp; } + // CASEx(cexpr,.... + // -> tree of IF(msb, IF(msb-1, 11, 10) + // IF(msb-1, 01, 00)) // TODO: should return AstNodeStmt after #6280 - AstNode* replaceCaseFast(AstCase* nodep) { - // CASEx(cexpr,.... - // -> tree of IF(msb, IF(msb-1, 11, 10) - // IF(msb-1, 01, 00)) + AstNode* convertCaseFast(AstCase* nodep) const { const int caseWidth = nodep->exprp()->width(); - AstNode* const ifrootp = replaceCaseFastRecurse(nodep->exprp(), caseWidth - 1, 0UL); + AstNode* const ifrootp = convertCaseFastRecurse(nodep->exprp(), caseWidth - 1, 0UL); return ifrootp && ifrootp->backp() ? ifrootp->cloneTree(true) : ifrootp; } + // Convet case statement using generic if/else tree + // CASEx(cexpr,ITEM(icond1,istmts1),ITEM(icond2,istmts2),ITEM(default,istmts3)) + // -> IF((cexpr==icond1),istmts1, + // IF((EQ (AND MASK cexpr) (AND MASK icond1) + // ,istmts2, istmts3 // TODO: should return AstNodeStmt after #6280 - AstNode* replaceCaseComplicated(AstCase* nodep) { - // CASEx(cexpr,ITEM(icond1,istmts1),ITEM(icond2,istmts2),ITEM(default,istmts3)) - // -> IF((cexpr==icond1),istmts1, - // IF((EQ (AND MASK cexpr) (AND MASK icond1) - // ,istmts2, istmts3 - + AstNode* convertCaseGeneric(AstCase* nodep) const { // We'll do this in two stages. // First stage, convert the conditions to the appropriate IF AND terms. bool hasDefault = false; @@ -567,13 +586,38 @@ class CaseVisitor final : public VNVisitor { return grouprootp; } - bool neverItem(const AstCase* casep, const AstNodeExpr* itemExprp) { - const AstConst* const constp = VN_CAST(itemExprp, Const); - if (!constp) return false; - // Xs in case or casez are impossible due to two state simulations - if (casep->casex() || casep->caseInside()) return false; - if (casep->casez()) return constp->num().isAnyX(); - return constp->num().isFourState(); + // Convert the given case statement to a representation not using AstCase + // TODO: should return AstNodeStmt after #6280 + AstNode* convertCase(AstCase* nodep, Stats& stats) const { + // Determine if we should use the fast bitwise branching tree method + const bool useFastBitTree = [&]() { + // Not if disabled + if (!v3Global.opt.fCase()) return false; + // Can't do it without the detailed analysis + if (!m_caseDetailsValid) return false; + // Can't do it if not exhaustive + if (!m_caseDetails.exhaustive) return false; + // Not worth doing if there are few conditions + if (m_caseNConditions <= 3) return false; + // Avoid e.g. priority expanders from going crazy in expansion + const size_t caseWidth = nodep->exprp()->width(); + if (caseWidth >= 8 && m_caseNConditions <= (caseWidth + 1)) return false; + // Otherwise use the bit tree + return true; + }(); + if (useFastBitTree) { + ++stats.caseFast; + return convertCaseFast(nodep); + } + + // Convert using the generic if/else tree method + ++stats.caseGeneric; + // If a case statement is exhaustive, presume signals involved aren't forming a latch + // TODO: this is broken, but it is as was before + if (m_alwaysp && (!m_caseDetailsValid || m_caseDetails.exhaustive)) { + m_alwaysp->fileline()->warnOff(V3ErrorCode::LATCH, true); + } + return convertCaseGeneric(nodep); } // VISITORS @@ -586,34 +630,24 @@ class CaseVisitor final : public VNVisitor { // Convert any children first iterateChildren(nodep); - // Convert the case statement - AstNode* replacementp = nullptr; - if (isCaseTreeFast(nodep) && v3Global.opt.fCase()) { - // It's a simple priority encoder or complete statement - // we can make a tree of statements to avoid extra comparisons - ++m_statCaseFast; - replacementp = replaceCaseFast(nodep); - } else { - // If a case statement is exhaustive, presume signals involved aren't forming a latch - // TODO: this is broken, but it is as was before - if (m_alwaysp && m_caseExhaustive) { - m_alwaysp->fileline()->warnOff(V3ErrorCode::LATCH, true); - } - ++m_statCaseSlow; - m_caseExhaustive = false; - m_caseNoOverlaps = false; - replacementp = replaceCaseComplicated(nodep); - } + // Analyze this case statement + analyzeCase(nodep); - // Take the notParallelp tree under the case statement created by V3Assert + // Take the 'notParallelp' statements under the case statement created by V3Assert. // If the statement was proven to have no overlaps and all cases covered, // it can be removed. Otherwise insert the assertion after the case statement. - if (nodep->notParallelp() && (!m_caseExhaustive || !m_caseNoOverlaps)) { - nodep->addNextHere(nodep->notParallelp()->unlinkFrBackWithNext()); + if (AstNode* const stmtp = nodep->notParallelp()) { + stmtp->unlinkFrBackWithNext(); + if (m_caseDetailsValid && m_caseDetails.exhaustive && m_caseDetails.noOverlaps) { + ++m_stats.provenAssertions; + VL_DO_DANGLING(stmtp->deleteTree(), stmtp); + } else { + nodep->addNextHere(stmtp); + } } - // Replace/remove the case statement - if (replacementp) { + // Convert the case statement and replace the original + if (AstNode* const replacementp = convertCase(nodep, m_stats)) { nodep->replaceWith(replacementp); } else { nodep->unlinkFrBack(); @@ -632,8 +666,9 @@ public: // CONSTRUCTORS explicit CaseVisitor(AstNetlist* nodep) { iterate(nodep); } ~CaseVisitor() override { - V3Stats::addStat("Optimizations, Cases parallelized", m_statCaseFast); - V3Stats::addStat("Optimizations, Cases complex", m_statCaseSlow); + V3Stats::addStat("Optimizations, Cases parallelized", m_stats.caseFast); + V3Stats::addStat("Optimizations, Cases complex", m_stats.caseGeneric); + V3Stats::addStat("Optimizations, Cases proven assertions", m_stats.provenAssertions); } }; diff --git a/src/V3Const.cpp b/src/V3Const.cpp index de2e1d64f..2ff6a0a80 100644 --- a/src/V3Const.cpp +++ b/src/V3Const.cpp @@ -973,6 +973,96 @@ class ConstVisitor final : public VNVisitor { return !numv.isNumber() ? numv : V3Number{nodep, nodep->width(), numv}; } + static bool lowerAsFixedAggregate(const AstNodeDType* const dtypep) { + return dtypep->isStreamableFixedAggregate() && dtypep->containsUnpackedStruct(); + } + + AstStructSel* newStructSel(AstNodeExpr* const fromp, const AstMemberDType* const itemp) { + AstStructSel* const selp = new AstStructSel{fromp->fileline(), fromp, itemp->name()}; + selp->dtypeFrom(itemp->dtypep()); + return selp; + } + + void collectFixedAggregateTerms(AstNodeExpr* const fromp, std::vector& termps, + const bool packReal) { + const AstNodeDType* const dtypep = fromp->dtypep()->skipRefp(); + if (const AstUnpackArrayDType* const unpackDtypep = VN_CAST(dtypep, UnpackArrayDType)) { + const int left = unpackDtypep->left(); + const int right = unpackDtypep->right(); + const int step = left <= right ? 1 : -1; + for (int idx = left;; idx += step) { + AstArraySel* const selp + = new AstArraySel{fromp->fileline(), fromp->cloneTreePure(false), idx}; + collectFixedAggregateTerms(selp, termps, packReal); + if (idx == right) break; + } + VL_DO_DANGLING(pushDeletep(fromp), fromp); + } else if (const AstNodeUOrStructDType* const sdtypep + = VN_CAST(dtypep, NodeUOrStructDType)) { + if (sdtypep->packed()) { + termps.push_back(fromp); + return; + } + for (const AstMemberDType* itemp = sdtypep->membersp(); itemp; + itemp = VN_AS(itemp->nextp(), MemberDType)) { + collectFixedAggregateTerms(newStructSel(fromp->cloneTreePure(false), itemp), + termps, packReal); + } + VL_DO_DANGLING(pushDeletep(fromp), fromp); + } else if (packReal && dtypep->isDouble()) { + termps.push_back(new AstRealToBits{fromp->fileline(), fromp}); + } else { + termps.push_back(fromp); + } + } + + AstNodeExpr* packFixedAggregate(AstNodeExpr* const fromp) { + std::vector termps; + collectFixedAggregateTerms(fromp, termps, true); + UASSERT(!termps.empty(), "No stream terms"); + AstNodeExpr* resultp = termps[0]; + for (size_t i = 1; i < termps.size(); ++i) { + resultp = new AstConcat{resultp->fileline(), resultp, termps[i]}; + } + return resultp; + } + + void replaceAssignToFixedAggregate(AstNodeAssign* const nodep, AstNodeExpr* const dstp, + AstNodeExpr* srcp) { + const int dstWidth = dstp->dtypep()->widthStream(); + std::vector termps; + collectFixedAggregateTerms(dstp, termps, false); + int srcWidth = srcp->width(); + if (srcWidth < dstWidth) { + AstExtend* const extendp = new AstExtend{srcp->fileline(), srcp}; + extendp->dtypeSetLogicSized(dstWidth, VSigning::UNSIGNED); + srcp = new AstShiftL{ + extendp->fileline(), extendp, + new AstConst{extendp->fileline(), static_cast(dstWidth - srcWidth)}, + dstWidth}; + srcWidth = dstWidth; + } + AstNodeAssign* newp = nullptr; + int offset = 0; + for (size_t i = 0; i < termps.size(); ++i) { + AstNodeExpr* const termp = termps[i]; + const int width = termp->dtypep()->widthStream(); + const int lsb = srcWidth - offset - width; + offset += width; + AstNodeExpr* rhsp + = new AstSel{srcp->fileline(), srcp->cloneTreePure(false), lsb, width}; + if (termp->dtypep()->skipRefp()->isDouble()) { + rhsp = new AstBitsToRealD{rhsp->fileline(), rhsp}; + } + AstNodeAssign* const assignp = nodep->cloneType(termp, rhsp); + assignp->dtypeFrom(termp); + newp = AstNode::addNext(newp, assignp); + } + nodep->addNextHere(newp); + VL_DO_DANGLING(pushDeletep(srcp), srcp); + VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); + } + bool operandConst(const AstNode* nodep) { return VN_IS(nodep, Const); } bool operandAsvConst(const AstNode* nodep) { // BIASV(CONST, BIASV(CONST,...)) -> BIASV( BIASV_CONSTED(a,b), ...) @@ -2393,6 +2483,25 @@ class ConstVisitor final : public VNVisitor { VL_DO_DANGLING(pushDeletep(conp), conp); // Further reduce, either node may have more reductions. return true; + } else if (m_doV && VN_IS(nodep->rhsp(), CvtPackedToArray) + && lowerAsFixedAggregate(nodep->lhsp()->dtypep())) { + AstCvtPackedToArray* const cvtp + = VN_AS(nodep->rhsp(), CvtPackedToArray)->unlinkFrBack(); + AstNodeExpr* srcp = cvtp->fromp()->unlinkFrBack(); + if (lowerAsFixedAggregate(srcp->dtypep())) { + srcp = packFixedAggregate(srcp); + } else if (AstNodeStream* const streamp = VN_CAST(srcp, NodeStream)) { + AstNodeExpr* const streamSrcp = streamp->lhsp(); + if (lowerAsFixedAggregate(streamSrcp->dtypep())) { + AstNodeExpr* const packedp = packFixedAggregate(streamSrcp->unlinkFrBack()); + streamp->lhsp(packedp); + streamp->dtypeSetLogicUnsized(packedp->width(), packedp->widthMin(), + VSigning::UNSIGNED); + } + } + VL_DO_DANGLING(pushDeletep(cvtp), cvtp); + replaceAssignToFixedAggregate(nodep, nodep->lhsp()->unlinkFrBack(), srcp); + return true; } else if (m_doV && VN_IS(nodep->rhsp(), StreamR) && !VN_IS(nodep->lhsp()->dtypep()->skipRefp(), QueueDType)) { // The right-streaming operator on rhs of assignment does not @@ -2402,7 +2511,9 @@ class ConstVisitor final : public VNVisitor { AstNodeExpr* srcp = streamp->lhsp()->unlinkFrBack(); AstNodeDType* const srcDTypep = srcp->dtypep()->skipRefp(); const AstNodeDType* const dstDTypep = nodep->lhsp()->dtypep()->skipRefp(); - if (VN_IS(srcDTypep, QueueDType) || VN_IS(srcDTypep, DynArrayDType)) { + if (lowerAsFixedAggregate(srcDTypep)) { + srcp = packFixedAggregate(srcp); + } else if (VN_IS(srcDTypep, QueueDType) || VN_IS(srcDTypep, DynArrayDType)) { if (VN_IS(dstDTypep, QueueDType) || VN_IS(dstDTypep, DynArrayDType)) { int srcElementBits = 0; if (const AstNodeDType* const elemDtp = srcDTypep->subDTypep()) { @@ -2429,6 +2540,19 @@ class ConstVisitor final : public VNVisitor { srcp = new AstShiftL{srcp->fileline(), srcp, new AstConst{srcp->fileline(), offset}, packedBits}; } + if (!VN_IS(dstDTypep, UnpackArrayDType) && !VN_IS(dstDTypep, QueueDType) + && !VN_IS(dstDTypep, DynArrayDType)) { + const int sWidth = srcp->width(); + const int dWidth = nodep->lhsp()->width(); + if (sWidth < dWidth) { + AstExtend* const extendp = new AstExtend{srcp->fileline(), srcp}; + extendp->dtypeSetLogicSized(dWidth, VSigning::UNSIGNED); + srcp = new AstShiftL{ + srcp->fileline(), extendp, + new AstConst{srcp->fileline(), static_cast(dWidth - sWidth)}, + dWidth}; + } + } nodep->rhsp(srcp); VL_DO_DANGLING(pushDeletep(streamp), streamp); // Further reduce, any of the nodes may have more reductions. @@ -2438,7 +2562,7 @@ class ConstVisitor final : public VNVisitor { AstNodeExpr* streamp = nodep->lhsp()->unlinkFrBack(); AstNodeExpr* const dstp = VN_AS(streamp, StreamL)->lhsp()->unlinkFrBack(); AstNodeDType* const dstDTypep = dstp->dtypep()->skipRefp(); - AstNodeExpr* const srcp = nodep->rhsp()->unlinkFrBack(); + AstNodeExpr* srcp = nodep->rhsp()->unlinkFrBack(); const AstNodeDType* const srcDTypep = srcp->dtypep()->skipRefp(); // Handle unpacked/queue/dynarray source -> queue/dynarray dest via // CvtArrayToArray (StreamL reverses, so reverse=true) @@ -2466,6 +2590,7 @@ class ConstVisitor final : public VNVisitor { VL_DO_DANGLING(pushDeletep(streamp), streamp); return true; } + if (lowerAsFixedAggregate(srcDTypep)) srcp = packFixedAggregate(srcp); const int sWidth = srcp->width(); const int dWidth = dstp->width(); // Connect the rhs to the stream operator and update its width @@ -2556,7 +2681,7 @@ class ConstVisitor final : public VNVisitor { } else { // Source narrower than destination: left-justify by shifting left. // The right stream operator packs left-to-right, so remaining - // LSBs are zero-filled (IEEE 1800-2023 11.4.14.2). + // LSBs are zero-filled (IEEE 1800-2023 11.4.14.3). if (!VN_IS(srcp->dtypep()->skipRefp(), QueueDType)) { AstExtend* const extendp = new AstExtend{srcp->fileline(), srcp}; extendp->dtypeSetLogicSized(dWidth, VSigning::UNSIGNED); @@ -2580,6 +2705,13 @@ class ConstVisitor final : public VNVisitor { AstNodeExpr* srcp = streamp->lhsp(); const AstNodeDType* const srcDTypep = srcp->dtypep()->skipRefp(); AstNodeDType* const dstDTypep = nodep->lhsp()->dtypep()->skipRefp(); + if (lowerAsFixedAggregate(srcDTypep)) { + AstNodeExpr* const packedp = packFixedAggregate(srcp->unlinkFrBack()); + streamp->lhsp(packedp); + streamp->dtypeSetLogicUnsized(packedp->width(), packedp->widthMin(), + VSigning::UNSIGNED); + srcp = packedp; + } if ((VN_IS(srcDTypep, QueueDType) || VN_IS(srcDTypep, DynArrayDType) || VN_IS(srcDTypep, UnpackArrayDType))) { if (VN_IS(dstDTypep, QueueDType) || VN_IS(dstDTypep, DynArrayDType)) { diff --git a/src/V3DfgPeephole.cpp b/src/V3DfgPeephole.cpp index 34c1ac5a6..3f08f490f 100644 --- a/src/V3DfgPeephole.cpp +++ b/src/V3DfgPeephole.cpp @@ -1885,10 +1885,24 @@ class V3DfgPeephole final : public DfgVisitor { if (!idxp) return; DfgVarArray* const varp = vtxp->fromp()->cast(); if (!varp) return; - if (varp->vscp()->varp()->isForced()) return; - if (varp->vscp()->varp()->isSigUserRWPublic()) return; + AstVar* const astVarp = varp->vscp()->varp(); + if (astVarp->isForced()) return; + if (astVarp->isSigUserRWPublic()) return; DfgVertex* const srcp = varp->srcp(); - if (!srcp) return; + if (!srcp) { + if (vtxp->isPacked()) { + if (AstInitArray* const iap = VN_CAST(astVarp->valuep(), InitArray)) { + if (AstConst* const valp + = VN_CAST(iap->getIndexDefaultedValuep(idxp->toSizeT()), Const)) { + APPLYING(FOLD_ARRAYSEL_TABLE) { + replace(new DfgConst{m_dfg, valp->fileline(), valp->num()}); + return; + } + } + } + } + return; + } if (DfgSpliceArray* const splicep = srcp->cast()) { DfgVertex* const driverp = splicep->driverAt(idxp->toSizeT()); diff --git a/src/V3DfgPeepholePatterns.h b/src/V3DfgPeepholePatterns.h index 4da947664..52d5717fe 100644 --- a/src/V3DfgPeepholePatterns.h +++ b/src/V3DfgPeepholePatterns.h @@ -27,6 +27,7 @@ // Enumeration of each peephole optimization. Must be kept in sorted order (enforced by tests). // clang-format off #define FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION(macro) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_ARRAYSEL_TABLE) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_ASSOC_BINARY_LHS_OF_RHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_ASSOC_BINARY_RHS_OF_LHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_BINARY) \ diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 861714e34..a323f9336 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -255,13 +255,6 @@ class WidthVisitor final : public VNVisitor { EXTEND_OFF // No extension }; - int widthUnpacked(const AstNodeDType* const dtypep) { - if (const AstUnpackArrayDType* const arrDtypep = VN_CAST(dtypep, UnpackArrayDType)) { - return arrDtypep->subDTypep()->width() * arrDtypep->arrayUnpackedElements(); - } - return dtypep->width(); - } - static void packIfUnpacked(AstNodeExpr* const nodep) { if (AstUnpackArrayDType* const unpackDTypep = VN_CAST(nodep->dtypep(), UnpackArrayDType)) { const int elementsNum = unpackDTypep->arrayUnpackedElements(); @@ -274,6 +267,9 @@ class WidthVisitor final : public VNVisitor { nodep->findLogicDType(unpackBits, unpackMinBits, VSigning::UNSIGNED)}); } } + static bool lowerAsFixedAggregate(const AstNodeDType* const dtypep) { + return dtypep->isStreamableFixedAggregate() && dtypep->containsUnpackedStruct(); + } // When fromp() is a DType (e.g. unlinked RefDType), resolve through // the ref chain; when it's an expression, dtypep() is already resolved. static AstNodeDType* fromDTypep(AstNode* fromp) { @@ -979,7 +975,10 @@ class WidthVisitor final : public VNVisitor { } const AstNodeDType* const lhsDtypep = nodep->lhsp()->dtypep()->skipRefToEnump(); if (VN_IS(lhsDtypep, DynArrayDType) || VN_IS(lhsDtypep, QueueDType) - || VN_IS(lhsDtypep, UnpackArrayDType)) { + || (VN_IS(lhsDtypep, UnpackArrayDType) && lhsDtypep->isStreamableFixedAggregate()) + || (VN_IS(lhsDtypep, NodeUOrStructDType) + && !VN_AS(lhsDtypep, NodeUOrStructDType)->packed() + && lhsDtypep->isStreamableFixedAggregate())) { nodep->dtypeSetStream(); } else if (lhsDtypep->isCompound()) { nodep->v3warn(E_UNSUPPORTED, @@ -6295,14 +6294,14 @@ class WidthVisitor final : public VNVisitor { userIterateAndNext(nodep->rhsp(), WidthVP{nodep->dtypep(), PRELIM}.p()); // // UINFOTREE(1, nodep, "", "assign"); - AstNodeDType* const lhsDTypep + AstNodeDType* lhsDTypep = nodep->lhsp()->dtypep(); // Note we use rhsp for context determined // Check width of stream and wrap if needed if (AstNodeStream* const streamp = VN_CAST(nodep->rhsp(), NodeStream)) { AstNodeDType* const lhsDTypeSkippedRefp = lhsDTypep->skipRefp(); - const int lwidth = widthUnpacked(lhsDTypeSkippedRefp); - const int rwidth = widthUnpacked(streamp->lhsp()->dtypep()->skipRefp()); + const int lwidth = lhsDTypeSkippedRefp->widthStream(); + const int rwidth = streamp->lhsp()->dtypep()->skipRefp()->widthStream(); if (lwidth != 0 && lwidth < rwidth) { nodep->v3widthWarn(lwidth, rwidth, "Target fixed size variable (" @@ -6314,16 +6313,18 @@ class WidthVisitor final : public VNVisitor { const int queueElementSize = streamp->lhsp()->dtypep()->subDTypep()->width(); UASSERT_OBJ(queueElementSize <= lwidth, nodep, "LHS < RHS"); } - if (VN_IS(lhsDTypeSkippedRefp, UnpackArrayDType)) { + if (VN_IS(lhsDTypeSkippedRefp, UnpackArrayDType) + || lowerAsFixedAggregate(lhsDTypeSkippedRefp)) { streamp->unlinkFrBack(); nodep->rhsp(new AstCvtPackedToArray{streamp->fileline(), streamp, lhsDTypeSkippedRefp}); } } - if (const AstNodeStream* const streamp = VN_CAST(nodep->lhsp(), NodeStream)) { + if (AstNodeStream* const streamp = VN_CAST(nodep->lhsp(), NodeStream)) { const AstNodeDType* const rhsDTypep = nodep->rhsp()->dtypep()->skipRefp(); - const int lwidth = widthUnpacked(streamp->lhsp()->dtypep()->skipRefp()); - const int rwidth = widthUnpacked(rhsDTypep); + AstNodeDType* const lhsStreamDTypep = streamp->lhsp()->dtypep()->skipRefp(); + const int lwidth = lhsStreamDTypep->widthStream(); + const int rwidth = rhsDTypep->widthStream(); if (rwidth != 0 && rwidth < lwidth) { nodep->v3widthWarn(lwidth, rwidth, "Stream target requires " @@ -6331,7 +6332,27 @@ class WidthVisitor final : public VNVisitor { << " bits, but source expression only provides " << rwidth << " bits (IEEE 1800-2023 11.4.14.3)"); } - if (VN_IS(rhsDTypep, UnpackArrayDType)) { + if (lowerAsFixedAggregate(lhsStreamDTypep)) { + AstNodeExpr* const streamExprp = nodep->lhsp()->unlinkFrBack(); + AstNodeExpr* const dstp = streamp->lhsp()->unlinkFrBack(); + AstNodeExpr* srcp = nodep->rhsp()->unlinkFrBack(); + if (VN_IS(streamp, StreamL)) { + streamp->lhsp(srcp); + streamp->dtypeSetLogicUnsized(srcp->width(), srcp->widthMin(), + VSigning::UNSIGNED); + srcp = streamExprp; + } else { + if (srcp->width() > lwidth) { + srcp = new AstSel{streamp->fileline(), srcp, srcp->width() - lwidth, + lwidth}; + } + VL_DO_DANGLING(pushDeletep(streamExprp), streamExprp); + } + nodep->lhsp(dstp); + nodep->rhsp(new AstCvtPackedToArray{srcp->fileline(), srcp, lhsStreamDTypep}); + nodep->dtypeFrom(dstp); + lhsDTypep = nodep->lhsp()->dtypep(); + } else if (VN_IS(rhsDTypep, UnpackArrayDType)) { AstNodeExpr* const rhsp = nodep->rhsp()->unlinkFrBack(); nodep->rhsp( new AstCvtArrayToPacked{rhsp->fileline(), rhsp, streamp->dtypep()}); diff --git a/test_regress/t/t_case_unique_overlap.py b/test_regress/t/t_case_unique_overlap.py index 8a938befd..f3945fcdd 100755 --- a/test_regress/t/t_case_unique_overlap.py +++ b/test_regress/t/t_case_unique_overlap.py @@ -11,8 +11,10 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile() +test.compile(verilator_flags2=['--stats']) test.execute() +test.file_grep(test.stats, r'Optimizations, Cases proven assertions\s+(\d+)', 1) + test.passes() diff --git a/test_regress/t/t_dfg_peephole.v b/test_regress/t/t_dfg_peephole.v index 2d679a8de..98d9146be 100644 --- a/test_regress/t/t_dfg_peephole.v +++ b/test_regress/t/t_dfg_peephole.v @@ -50,6 +50,9 @@ module t ( assign unitArrayParts[0][1] = rand_a[1]; assign unitArrayParts[0][9] = rand_a[9]; + // Complicated way to write constant 0 that only Dfg can decipher + wire [63:0] convoluted_zero = (({64{rand_a[0]}} & ~{64{rand_a[0]}})); + `signal(FOLD_UNARY_LogNot, !const_a[0]); `signal(FOLD_UNARY_Negate, -const_a); `signal(FOLD_UNARY_Not, ~const_a); @@ -133,6 +136,13 @@ module t ( `signal(FOLD_SEL, const_a[3:1]); + int fold_arraysel_table_one; + ffs ffs_a(convoluted_zero[0] ? 8'hff: 8'd2, fold_arraysel_table_one); + int fold_arraysel_table_two; + ffs ffs_b(convoluted_zero[1] ? 8'hff: 8'd7, fold_arraysel_table_two); + `signal(FOLD_ARRAYSEL_TABLE_ONE, fold_arraysel_table_one); + `signal(FOLD_ARRAYSEL_TABLE_TWO, fold_arraysel_table_two); + `signal(SWAP_CONST_IN_COMMUTATIVE_BINARY, rand_a + const_a); `signal(SWAP_NOT_IN_COMMUTATIVE_BINARY, rand_a + ~rand_a); `signal(SWAP_VAR_IN_COMMUTATIVE_BINARY, rand_b + rand_a); @@ -427,3 +437,25 @@ module t ( assign zero = '0; assign ones = '1; endmodule + +module ffs( + input logic [7:0] i, + output int o +); + // V3Table will convert this + always_comb begin + // verilator lint_off CASEOVERLAP + casez (i) + 8'b1???????: o = 7; + 8'b?1??????: o = 6; + 8'b??1?????: o = 5; + 8'b???1????: o = 4; + 8'b????1???: o = 3; + 8'b?????1??: o = 2; + 8'b??????1?: o = 1; + 8'b???????1: o = 0; + 8'b00000000: o = -1; + endcase + // verilator lint_on CASEOVERLAP + end +endmodule diff --git a/test_regress/t/t_stream_unpacked_struct.py b/test_regress/t/t_stream_unpacked_struct.py new file mode 100755 index 000000000..8a938befd --- /dev/null +++ b/test_regress/t/t_stream_unpacked_struct.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_stream_unpacked_struct.v b/test_regress/t/t_stream_unpacked_struct.v new file mode 100644 index 000000000..509d8a035 --- /dev/null +++ b/test_regress/t/t_stream_unpacked_struct.v @@ -0,0 +1,347 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +// Ref. to IEEE 1800-2023 11.4.14, A.8.1 + +module t; + + `define checkh(gotv, expv) \ + do if ((gotv) !== (expv)) begin \ + $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__, `__LINE__, (gotv), (expv)); \ + $stop; \ + end while (0); + + typedef struct packed { + logic [3:0] hi; + logic [3:0] lo; + } packed_pair_t; + + typedef union packed { + logic [7:0] byte_data; + packed_pair_t pair; + } packed_union_t; + + typedef struct { + byte a; + logic [3:0] b; + packed_pair_t p; + } simple_t; + + typedef struct { + byte prefix; + byte data[2]; + packed_pair_t p; + } array_t; + + typedef struct { + simple_t inner; + byte tail[2]; + } nested_t; + + typedef struct { + byte prefix; + simple_t items[2]; + byte suffix; + } struct_array_t; + + typedef struct { + byte data[1:0]; + } descending_array_t; + + typedef struct { + byte matrix[2][3]; + } matrix_array_t; + + typedef struct { + logic [1:0][3:0] data[2]; + } mixed_array_t; + + typedef enum logic [2:0] { + E0 = 3'd0, + E5 = 3'd5 + } enum_t; + + typedef struct { + enum_t e; + logic [4:0] x; + } enum_struct_t; + + typedef struct { + byte tag; + real r; + realtime rt; + real ra[2]; + } real_struct_t; + + typedef struct { + packed_union_t u; + byte tail; + } packed_union_struct_t; + + simple_t simple; + simple_t simple_out; + simple_t simple_cont_out; + array_t arrayed; + array_t arrayed_out; + nested_t nested; + nested_t nested_out; + struct_array_t struct_array; + struct_array_t struct_array_out; + descending_array_t descending; + descending_array_t descending_out; + matrix_array_t matrix_array; + matrix_array_t matrix_array_out; + mixed_array_t mixed_array; + mixed_array_t mixed_array_out; + simple_t simple_array[2]; + simple_t simple_array_out[2]; + enum_struct_t enum_struct; + enum_struct_t enum_struct_out; + real_struct_t real_struct; + real_struct_t real_struct_out; + packed_union_struct_t packed_union_struct; + packed_union_struct_t packed_union_struct_out; + + logic [19:0] simple_bits; + logic [31:0] wide_simple_bits; + logic [31:0] array_bits; + logic [35:0] nested_bits; + logic [55:0] struct_array_bits; + logic [15:0] descending_bits; + logic [47:0] matrix_array_bits; + logic [15:0] mixed_array_bits; + logic [39:0] simple_array_bits; + logic [31:0] byteswapped_bits; + logic [7:0] enum_bits; + logic [263:0] real_bits; + logic [15:0] packed_union_bits; + logic [11:0] narrow_bits; + logic [19:0] simple_streaml_src; + byte byte_array_out[2]; + + assign {>>{simple_cont_out}} = 20'habcde; + + initial begin + byteswapped_bits = {<<8{32'h11223344}}; + `checkh(byteswapped_bits, 32'h44332211); + byteswapped_bits = {<>{simple_bits}} = 20'h13579; + `checkh(simple_bits, 20'h13579); + {<<4{simple_bits}} = 20'h12345; + `checkh(simple_bits, 20'h54321); + + simple = '{8'h12, 4'ha, '{4'hb, 4'hc}}; + simple_bits = {>>{simple}}; + `checkh(simple_bits, 20'h12abc); + /* verilator lint_off WIDTHEXPAND */ + wide_simple_bits = {>>{simple}}; + /* verilator lint_on WIDTHEXPAND */ + `checkh(wide_simple_bits, 32'h12abc000); + simple_bits = {<<4{simple}}; + `checkh(simple_bits, 20'hcba21); + + {>>{simple_out}} = 20'h345de; + `checkh(simple_out.a, 8'h34); + `checkh(simple_out.b, 4'h5); + `checkh(simple_out.p, 8'hde); + `checkh(simple_cont_out.a, 8'hab); + `checkh(simple_cont_out.b, 4'hc); + `checkh(simple_cont_out.p, 8'hde); + + {<<4{simple_out}} = 20'h789ab; + `checkh(simple_out.a, 8'hba); + `checkh(simple_out.b, 4'h9); + `checkh(simple_out.p, 8'h87); + + simple_out = {>>{20'hfedcb}}; + `checkh(simple_out.a, 8'hfe); + `checkh(simple_out.b, 4'hd); + `checkh(simple_out.p, 8'hcb); + + simple_out = {>>{simple}}; + `checkh(simple_out.a, 8'h12); + `checkh(simple_out.b, 4'ha); + `checkh(simple_out.p, 8'hbc); + + {>>{simple_out}} = simple; + `checkh(simple_out.a, 8'h12); + `checkh(simple_out.b, 4'ha); + `checkh(simple_out.p, 8'hbc); + + if ($test$plusargs("t_stream_unpacked_struct_alt")) begin + narrow_bits = 12'h123; + end else begin + narrow_bits = 12'habd; + end + /* verilator lint_off WIDTHEXPAND */ + simple_bits = {>>{narrow_bits}}; + /* verilator lint_on WIDTHEXPAND */ + `checkh(simple_bits, {narrow_bits, 8'h00}); + + simple_out = {>>{narrow_bits}}; + `checkh(simple_out.a, narrow_bits[11:4]); + `checkh(simple_out.b, narrow_bits[3:0]); + `checkh(simple_out.p, 8'h00); + + {>>{simple_out}} = 24'habcdef; + `checkh(simple_out.a, 8'hab); + `checkh(simple_out.b, 4'hc); + `checkh(simple_out.p, 8'hde); + + simple_out = {<<4{20'h13579}}; + `checkh(simple_out.a, 8'h97); + `checkh(simple_out.b, 4'h5); + `checkh(simple_out.p, 8'h31); + + simple_streaml_src = 20'h2468a; + simple_out = {<<4{simple_streaml_src}}; + `checkh(simple_out.a, 8'ha8); + `checkh(simple_out.b, 4'h6); + `checkh(simple_out.p, 8'h42); + + {<<4{simple_out}} = simple_streaml_src; + `checkh(simple_out.a, 8'ha8); + `checkh(simple_out.b, 4'h6); + `checkh(simple_out.p, 8'h42); + + {<<8{byte_array_out}} = 16'h1234; + `checkh(byte_array_out[0], 8'h34); + `checkh(byte_array_out[1], 8'h12); + + arrayed = '{8'h11, '{8'h22, 8'h33}, '{4'h4, 4'h5}}; + array_bits = {>>{arrayed}}; + `checkh(array_bits, 32'h11223345); + array_bits = {<<8{arrayed}}; + `checkh(array_bits, 32'h45332211); + + {>>{arrayed_out}} = 32'haabbccdd; + `checkh(arrayed_out.prefix, 8'haa); + `checkh(arrayed_out.data[0], 8'hbb); + `checkh(arrayed_out.data[1], 8'hcc); + `checkh(arrayed_out.p, 8'hdd); + + nested = '{'{8'h12, 4'ha, '{4'hb, 4'hc}}, '{8'h34, 8'h56}}; + nested_bits = {>>{nested}}; + `checkh(nested_bits, 36'h12abc3456); + + {>>{nested_out}} = 36'h6543210fe; + `checkh(nested_out.inner.a, 8'h65); + `checkh(nested_out.inner.b, 4'h4); + `checkh(nested_out.inner.p, 8'h32); + `checkh(nested_out.tail[0], 8'h10); + `checkh(nested_out.tail[1], 8'hfe); + + struct_array.prefix = 8'haa; + struct_array.items[0] = '{8'h12, 4'h3, '{4'h4, 4'h5}}; + struct_array.items[1] = '{8'h67, 4'h8, '{4'h9, 4'ha}}; + struct_array.suffix = 8'hbb; + struct_array_bits = {>>{struct_array}}; + `checkh(struct_array_bits, 56'haa123456789abb); + + {>>{struct_array_out}} = 56'hccdef0123456dd; + `checkh(struct_array_out.prefix, 8'hcc); + `checkh(struct_array_out.items[0].a, 8'hde); + `checkh(struct_array_out.items[0].b, 4'hf); + `checkh(struct_array_out.items[0].p, 8'h01); + `checkh(struct_array_out.items[1].a, 8'h23); + `checkh(struct_array_out.items[1].b, 4'h4); + `checkh(struct_array_out.items[1].p, 8'h56); + `checkh(struct_array_out.suffix, 8'hdd); + + descending = '{data: '{8'hcc, 8'hdd}}; + descending_bits = {>>{descending}}; + `checkh(descending_bits, 16'hccdd); + + {>>{descending_out}} = 16'h1234; + `checkh(descending_out.data[1], 8'h12); + `checkh(descending_out.data[0], 8'h34); + + matrix_array.matrix[0][0] = 8'h11; + matrix_array.matrix[0][1] = 8'h22; + matrix_array.matrix[0][2] = 8'h33; + matrix_array.matrix[1][0] = 8'h44; + matrix_array.matrix[1][1] = 8'h55; + matrix_array.matrix[1][2] = 8'h66; + matrix_array_bits = {>>{matrix_array}}; + `checkh(matrix_array_bits, 48'h112233445566); + + {>>{matrix_array_out}} = 48'haabbccddeeff; + `checkh(matrix_array_out.matrix[0][0], 8'haa); + `checkh(matrix_array_out.matrix[0][1], 8'hbb); + `checkh(matrix_array_out.matrix[0][2], 8'hcc); + `checkh(matrix_array_out.matrix[1][0], 8'hdd); + `checkh(matrix_array_out.matrix[1][1], 8'hee); + `checkh(matrix_array_out.matrix[1][2], 8'hff); + + mixed_array.data[0] = 8'hab; + mixed_array.data[1] = 8'hcd; + mixed_array_bits = {>>{mixed_array}}; + `checkh(mixed_array_bits, 16'habcd); + + {>>{mixed_array_out}} = 16'h1234; + `checkh(mixed_array_out.data[0], 8'h12); + `checkh(mixed_array_out.data[0][1], 4'h1); + `checkh(mixed_array_out.data[0][0], 4'h2); + `checkh(mixed_array_out.data[1], 8'h34); + `checkh(mixed_array_out.data[1][1], 4'h3); + `checkh(mixed_array_out.data[1][0], 4'h4); + + simple_array[0] = '{8'h12, 4'ha, '{4'hb, 4'hc}}; + simple_array[1] = '{8'h34, 4'hd, '{4'he, 4'hf}}; + simple_array_bits = {>>{simple_array}}; + `checkh(simple_array_bits, 40'h12abc34def); + + {>>{simple_array_out}} = 40'h567899abcd; + `checkh(simple_array_out[0].a, 8'h56); + `checkh(simple_array_out[0].b, 4'h7); + `checkh(simple_array_out[0].p, 8'h89); + `checkh(simple_array_out[1].a, 8'h9a); + `checkh(simple_array_out[1].b, 4'hb); + `checkh(simple_array_out[1].p, 8'hcd); + + enum_struct = '{E5, 5'ha}; + enum_bits = {>>{enum_struct}}; + `checkh(enum_bits, 8'haa); + + {>>{enum_struct_out}} = 8'h71; + `checkh(enum_struct_out.e, 3'h3); + `checkh(enum_struct_out.x, 5'h11); + + real_struct.tag = 8'h42; + real_struct.r = 1.0; + real_struct.rt = 3.0; + real_struct.ra[0] = 4.0; + real_struct.ra[1] = 5.0; + real_bits = {>>{real_struct}}; + `checkh(real_bits, + {8'h42, $realtobits(1.0), $realtobits(3.0), $realtobits(4.0), $realtobits(5.0)}); + + {>>{real_struct_out}} + = {8'h99, $realtobits(2.0), $realtobits(6.0), $realtobits(7.0), $realtobits(8.0)}; + `checkh(real_struct_out.tag, 8'h99); + `checkh($realtobits(real_struct_out.r), $realtobits(2.0)); + `checkh($realtobits(real_struct_out.rt), $realtobits(6.0)); + `checkh($realtobits(real_struct_out.ra[0]), $realtobits(7.0)); + `checkh($realtobits(real_struct_out.ra[1]), $realtobits(8.0)); + + packed_union_struct.u.byte_data = 8'hbe; + packed_union_struct.tail = 8'hef; + packed_union_bits = {>>{packed_union_struct}}; + `checkh(packed_union_bits, 16'hbeef); + + {>>{packed_union_struct_out}} = 16'hcafe; + `checkh(packed_union_struct_out.u.byte_data, 8'hca); + `checkh(packed_union_struct_out.tail, 8'hfe); + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_stream_unpacked_struct_bad.out b/test_regress/t/t_stream_unpacked_struct_bad.out new file mode 100644 index 000000000..9955e9208 --- /dev/null +++ b/test_regress/t/t_stream_unpacked_struct_bad.out @@ -0,0 +1,30 @@ +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:54:12: Unsupported: Stream operation on a variable of a type 'struct{}t.queue_struct_t' + : ... note: In instance 't' + 54 | out = {>>{queue_struct}}; + | ^~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:55:12: Unsupported: Stream operation on a variable of a type 'struct{}t.dynamic_struct_t' + : ... note: In instance 't' + 55 | out = {>>{dynamic_struct}}; + | ^~ +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:56:12: Unsupported: Stream operation on a variable of a type 'struct{}t.associative_struct_t' + : ... note: In instance 't' + 56 | out = {>>{associative_struct}}; + | ^~ +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:57:12: Unsupported: Stream operation on a variable of a type 'struct{}t.union_struct_t' + : ... note: In instance 't' + 57 | out = {>>{union_struct}}; + | ^~ +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:58:12: Unsupported: Stream operation on a variable of a type 'struct{}t.string_struct_t' + : ... note: In instance 't' + 58 | out = {>>{string_struct}}; + | ^~ +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:59:12: Unsupported: Stream operation on a variable of a type 'struct{}t.chandle_struct_t' + : ... note: In instance 't' + 59 | out = {>>{chandle_struct}}; + | ^~ +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:60:12: Unsupported: Stream operation on a variable of a type 'struct{}t.event_struct_t' + : ... note: In instance 't' + 60 | out = {>>{event_struct}}; + | ^~ +%Error: Exiting due to diff --git a/test_regress/t/t_stream_unpacked_struct_bad.py b/test_regress/t/t_stream_unpacked_struct_bad.py new file mode 100755 index 000000000..38cf36b43 --- /dev/null +++ b/test_regress/t/t_stream_unpacked_struct_bad.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('linter') + +test.lint(fails=True, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_stream_unpacked_struct_bad.v b/test_regress/t/t_stream_unpacked_struct_bad.v new file mode 100644 index 000000000..2c1e00b97 --- /dev/null +++ b/test_regress/t/t_stream_unpacked_struct_bad.v @@ -0,0 +1,63 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of either the GNU Lesser General Public License Version 3 +// or the Perl Artistic License Version 2.0. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +module t; + + typedef struct { + byte bytes[$]; + } queue_struct_t; + + typedef struct { + byte bytes[]; + } dynamic_struct_t; + + typedef struct { + byte bytes[int]; + } associative_struct_t; + + typedef union { + byte a; + logic [15:0] b; + } unpacked_union_t; + + typedef struct { + unpacked_union_t u; + } union_struct_t; + + typedef struct { + string s; + } string_struct_t; + + typedef struct { + chandle c; + } chandle_struct_t; + + typedef struct { + event e; + } event_struct_t; + + logic [255:0] out; + queue_struct_t queue_struct; + dynamic_struct_t dynamic_struct; + associative_struct_t associative_struct; + union_struct_t union_struct; + string_struct_t string_struct; + chandle_struct_t chandle_struct; + event_struct_t event_struct; + + initial begin + out = {>>{queue_struct}}; + out = {>>{dynamic_struct}}; + out = {>>{associative_struct}}; + out = {>>{union_struct}}; + out = {>>{string_struct}}; + out = {>>{chandle_struct}}; + out = {>>{event_struct}}; + end + +endmodule