Merge branch 'verilator:master' into u26
This commit is contained in:
commit
e06158b404
|
|
@ -170,6 +170,10 @@ public:
|
|||
void generic(bool flag) { m_generic = flag; }
|
||||
std::pair<uint32_t, uint32_t> 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");
|
||||
|
|
|
|||
|
|
@ -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<uint32_t, uint32_t> AstNodeDType::dimensions(bool includeBasic) const {
|
||||
// How many array dimensions (packed,unpacked) does this Var have?
|
||||
uint32_t packed = 0;
|
||||
|
|
|
|||
289
src/V3Case.cpp
289
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<CaseRecord, CASE_MAX_VALUES> 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<CaseRecord, 1U << CASE_DETAILS_MAX_WIDTH> records;
|
||||
} m_caseDetails;
|
||||
|
||||
// METHODS
|
||||
|
||||
// Xs in case or casez are impossible due to two state simulations.
|
||||
// Returns true if the item is never possible
|
||||
static bool neverItem(const AstCase* casep, const AstNodeExpr* itemExprp) {
|
||||
const AstConst* const constp = VN_CAST(itemExprp, Const);
|
||||
if (!constp) return false;
|
||||
if (casep->casex() || casep->caseInside()) return false;
|
||||
if (casep->casez()) return constp->num().isAnyX();
|
||||
return constp->num().isFourState();
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
138
src/V3Const.cpp
138
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<AstNodeExpr*>& 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<AstNodeExpr*> 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<AstNodeExpr*> 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<uint32_t>(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<uint32_t>(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)) {
|
||||
|
|
|
|||
|
|
@ -1885,10 +1885,24 @@ class V3DfgPeephole final : public DfgVisitor {
|
|||
if (!idxp) return;
|
||||
DfgVarArray* const varp = vtxp->fromp()->cast<DfgVarArray>();
|
||||
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<DfgSpliceArray>()) {
|
||||
DfgVertex* const driverp = splicep->driverAt(idxp->toSizeT());
|
||||
|
|
|
|||
|
|
@ -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) \
|
||||
|
|
|
|||
|
|
@ -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()});
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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 = {<<byte{32'h11223344}};
|
||||
`checkh(byteswapped_bits, 32'h44332211);
|
||||
|
||||
{>>{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
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue