diff --git a/include/verilated.cpp b/include/verilated.cpp index 09f3c4d03..db9b32a01 100644 --- a/include/verilated.cpp +++ b/include/verilated.cpp @@ -1180,23 +1180,28 @@ void _vl_vsformat(std::string& output, const std::string& format, int argc, if (fmt != 'p' && fmt != 'x') fmt = 's'; // Override } else if (formatAttr == VL_VFORMATATTR_ENUM || formatAttr == VL_VFORMATATTR_ENUM_SIGNED) { - // Always <= VL_QUADSIZE; emit uses non-ENUM format for wider enums const int numericAttr = formatAttr == VL_VFORMATATTR_ENUM_SIGNED ? VL_VFORMATATTR_SIGNED : VL_VFORMATATTR_UNSIGNED; lbits = va_arg(ap, int); - ld = VL_VA_ARG_Q_(ap, lbits); - strwide.resize(2); - WDataOutP strwidep = WDataOutP::external(strwide.data()); - VL_SET_WQ(strwidep, ld); - lwp = strwidep; + if (lbits <= VL_QUADSIZE) { + ld = VL_VA_ARG_Q_(ap, lbits); + strwide.resize(2); + WDataOutP strwidep = WDataOutP::external(strwide.data()); + VL_SET_WQ(strwidep, ld); + lwp = strwidep; + } else { + lwp = WDataInP::external(va_arg(ap, const EData*)); + ld = VL_SET_QW(lwp); + } lsb = lbits - 1; ++argn; // Enum value is followed by the generated name string argument static_cast(va_arg(ap, int)); // VL_VFORMATATTR_STRING enump = va_arg(ap, std::string*); - if (enump && !enump->empty()) { + if (fmt != 'p' && fmt != 's') { + formatAttr = numericAttr; + } else if (enump && !enump->empty()) { formatAttr = (fmt == 'p') ? VL_VFORMATATTR_COMPLEX : VL_VFORMATATTR_STRING; - if (fmt == 'd') formatAttr = numericAttr; thingp = const_cast(enump); } else if (fmt == 'p' && widthSet && width == 0) { output += "'h"; diff --git a/include/verilatedos.h b/include/verilatedos.h index d720fdc32..69ac918e4 100644 --- a/include/verilatedos.h +++ b/include/verilatedos.h @@ -461,7 +461,7 @@ using ssize_t = uint32_t; ///< signed size_t; returned from read() #define VL_VFORMATATTR_SIGNED '~' // (int widthMin, IData/VlWide/etc) Signed number; for %d showing sign #define VL_VFORMATATTR_COMPLEX '!' // (std::string*); for non-POD; e.g. struct, requires %p typically #define VL_VFORMATATTR_DOUBLE 'D' // (double); promote %p to %f -#define VL_VFORMATATTR_ENUM 'E' // (width, IData/QData, std::string* name); <= 64 bit enum with runtime %p/%s +#define VL_VFORMATATTR_ENUM 'E' // (width, IData/QData/const EData*, STRING, std::string* name) #define VL_VFORMATATTR_ENUM_SIGNED 'F' // Same arguments as ENUM, with a signed numeric value #define VL_VFORMATATTR_SCOPE 'M' // (char* name, char* scope); for scopes #define VL_VFORMATATTR_STRING 'S' // (char* name, char* scope); for scopes // (std::string*); for %p/%s diff --git a/src/V3AstNodeExpr.h b/src/V3AstNodeExpr.h index ac931b6c0..057eb243e 100644 --- a/src/V3AstNodeExpr.h +++ b/src/V3AstNodeExpr.h @@ -2404,6 +2404,7 @@ class AstSFormatArg final : public AstNodeExpr { // used to pass to (potentially) runtime decoding of format arguments // PARENT: SFormatF (or next list of expressions) // @astgen op1 := exprp : AstNodeExpr + // @astgen op2 := namep : Optional[AstNodeExpr] // Runtime enum name lookup VFormatAttr m_formatAttr; // How to format expression public: @@ -2426,6 +2427,7 @@ public: bool cleanOut() const override { return true; } const char* broken() const override { BROKEN_RTN(!VN_IS(backp(), SFormatF) && firstAbovep()); // In list under SFormatF + BROKEN_RTN(formatAttr().isEnum() != static_cast(namep())); return nullptr; } VFormatAttr formatAttr() const { return m_formatAttr; } diff --git a/src/V3Clean.cpp b/src/V3Clean.cpp index afd7d3f14..d6423b447 100644 --- a/src/V3Clean.cpp +++ b/src/V3Clean.cpp @@ -282,6 +282,11 @@ class CleanVisitor final : public VNVisitor { iterateChildren(nodep); ensureClean(nodep->condp()); } + void visit(AstSFormatArg* nodep) override { + iterateChildren(nodep); + ensureCleanAndNext(nodep->exprp()); + setClean(nodep, true); + } void visit(AstSFormatF* nodep) override { iterateChildren(nodep); ensureCleanAndNext(nodep->exprsp()); diff --git a/src/V3Const.cpp b/src/V3Const.cpp index e800a221f..9d63f93a7 100644 --- a/src/V3Const.cpp +++ b/src/V3Const.cpp @@ -3969,7 +3969,12 @@ class ConstVisitor final : public VNVisitor { VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); return true; } - void visit(AstSFormatArg* nodep) override { iterateChildren(nodep); } + void visit(AstSFormatArg* nodep) override { + // Skip namep(): requiring its runtime lookup to be constant can reject valid + // enum-valued constant-function calls in parameters. displayedEnum() resolves + // the name from the folded exprp() value and enum dtype instead. + iterateAndNextNull(nodep->exprp()); + } void visit(AstSFormatF* nodep) override { // Substitute constants into displays. The main point of this is to // simplify assertion methodologies which call functions with display's. @@ -4033,7 +4038,9 @@ class ConstVisitor final : public VNVisitor { : VFormatAttr{}; if (VN_IS(subargp, Const)) { // Convert it const string out - = constNumV(subargp).displayed(nodep, fmt, formatAttr); + = formatAttr.isEnum() + ? constNumV(subargp).displayedEnum(fargp, fmt) + : constNumV(subargp).displayed(nodep, fmt, formatAttr); UINFO(9, " DispConst: " << fmt << " -> " << out << " for " << subargp); // fmt = out w/ replace % with %% as it must later when diff --git a/src/V3Dead.cpp b/src/V3Dead.cpp index a203a04bf..52cdbfe6c 100644 --- a/src/V3Dead.cpp +++ b/src/V3Dead.cpp @@ -447,6 +447,11 @@ class DeadVisitor final : public VNVisitor { checkDType(nodep); checkAll(nodep); } + void visit(AstEnumDType* nodep) override { + // Keep cached lookup tables until V3WidthCommit releases the cache. + for (const auto& entry : nodep->tableMap()) entry.second->user1Inc(); + visit(static_cast(nodep)); + } void visit(AstEnumItemRef* nodep) override { iterateChildren(nodep); checkAll(nodep); diff --git a/src/V3EmitCFunc.cpp b/src/V3EmitCFunc.cpp index 1dfc033db..cf936bf8e 100644 --- a/src/V3EmitCFunc.cpp +++ b/src/V3EmitCFunc.cpp @@ -304,7 +304,11 @@ void EmitCFunc::displayNode(AstNode* nodep, AstSFormatF* fmtp, // fmtp is nullp int argc = 0; if (needsScope) ++argc; if (needsTimescale) ++argc; - for (AstNode* argp = exprsp; argp; argp = argp->nextp()) ++argc; + for (AstNode* argp = exprsp; argp; argp = argp->nextp()) { + ++argc; + const AstSFormatArg* const fargp = VN_CAST(argp, SFormatArg); + if (fargp && fargp->formatAttr().isEnum()) ++argc; // Additional name argument + } ofp()->puts("," + std::to_string(argc)); if (needsScope) { @@ -345,7 +349,9 @@ void EmitCFunc::displayNode(AstNode* nodep, AstSFormatF* fmtp, // fmtp is nullp if (formatAttr.isSigned() || formatAttr.isUnsigned() || formatAttr.isEnum()) puts("," + cvtToStr(subargp->widthMin())); const bool addrof = isScan || formatAttr.isString() || formatAttr.isComplex(); + const bool wideEnum = formatAttr.isEnum() && subargp->isWide(); puts(","); + if (wideEnum) puts("static_cast("); if (addrof) puts("&("); if (VN_IS(subargp, StreamR)) emitStreamR( @@ -354,6 +360,12 @@ void EmitCFunc::displayNode(AstNode* nodep, AstSFormatF* fmtp, // fmtp is nullp else { iterateConst(subargp); } if (addrof) puts(")"); if (!addrof) emitDatap(argp); + if (wideEnum) puts(")"); + if (formatAttr.isEnum()) { + puts(", '"s + VFormatAttr{VFormatAttr::STRING}.ascii() + "', &("); + iterateConst(fargp->namep()); + puts(")"); + } ofp()->indentDec(); } diff --git a/src/V3Number.cpp b/src/V3Number.cpp index 096609363..7fff42aed 100644 --- a/src/V3Number.cpp +++ b/src/V3Number.cpp @@ -645,6 +645,41 @@ string V3Number::displayPad(size_t fmtsize, char pad, bool left, const string& i return left ? (in + padding) : (padding + in); } +string V3Number::displayedEnumName(const AstEnumItem* itemp) { + const string name = VIdProtect::protect(itemp->prettyName()); + return VString::isIdentifier(name) && !std::isdigit(static_cast(name[0])) + ? name + : "\\" + name + " "; +} + +string V3Number::displayedEnumName(const AstEnumDType* dtypep) const VL_MT_STABLE { + V3Number value{this, dtypep->width()}; + value.opAssign(*this); + for (const AstEnumItem* itemp = dtypep->itemsp(); itemp; + itemp = VN_AS(itemp->nextp(), EnumItem)) { + const AstConst* const constp = VN_AS(itemp->valuep(), Const); + if (!constp->num().isAnyXZ() && value.isCaseEq(constp->num())) + return displayedEnumName(itemp); + } + return ""; +} + +string V3Number::displayedEnum(const AstSFormatArg* argp, + const string& vformat) const VL_MT_STABLE { + const AstEnumDType* const dtypep = VN_AS(argp->dtypep()->skipRefToEnump(), EnumDType); + V3Number value{this, dtypep->width()}; + value.opAssign(*this); + const VFormatAttr numericAttr + = dtypep->isSigned() ? VFormatAttr::SIGNED : VFormatAttr::UNSIGNED; + const char fmt = std::tolower(vformat.back()); + if (fmt != 'p' && fmt != 's') return value.displayed(argp, vformat, numericAttr); + string name = value.displayedEnumName(dtypep); + if (name.empty()) name = value.displayed(argp, fmt == 'p' ? vformat : "%0d", numericAttr); + V3Number nameNum{this}; + nameNum.setString(name); + return nameNum.displayed(argp, vformat, VFormatAttr::COMPLEX); +} + string V3Number::displayed(const AstNode* nodep, const string& vformat, const VFormatAttr& formatAttr) const VL_MT_STABLE { return displayed(nodep->fileline(), vformat, formatAttr); diff --git a/src/V3Number.h b/src/V3Number.h index 404add9e7..23e5308f3 100644 --- a/src/V3Number.h +++ b/src/V3Number.h @@ -30,6 +30,8 @@ #include #include +class AstEnumDType; +class AstEnumItem; class AstNode; class AstNodeDType; class AstSFormatArg; @@ -652,6 +654,9 @@ public: const VFormatAttr& formatAttr = VFormatAttr::UNSIGNED) const VL_MT_STABLE; string displayed(FileLine* fl, const string& vformat, const VFormatAttr& formatAttr = VFormatAttr::UNSIGNED) const VL_MT_STABLE; + string displayedEnum(const AstSFormatArg* argp, const string& vformat) const VL_MT_STABLE; + string displayedEnumName(const AstEnumDType* dtypep) const VL_MT_STABLE; + static string displayedEnumName(const AstEnumItem* itemp); static bool displayedFmtHasArg(char format, bool isScan); string emitC() const VL_MT_STABLE; int width() const VL_MT_SAFE { return m_data.width(); } diff --git a/src/V3Premit.cpp b/src/V3Premit.cpp index 82a3b0864..bec4538b5 100644 --- a/src/V3Premit.cpp +++ b/src/V3Premit.cpp @@ -415,12 +415,33 @@ class PremitVisitor final : public VNVisitor { // Any strings sent to a display must be var of string data type, // to avoid passing a pointer to a temporary. AstNodeExpr* exprsp = nodep->exprsp(); - if (nodep->exprFormat()) exprsp = VN_AS(exprsp->nextp(), NodeExpr); + if (nodep->exprFormat()) { + exprsp = VN_AS(exprsp->nextp(), NodeExpr); + for (AstNode* argp = exprsp; argp; argp = argp->nextp()) { + const AstSFormatArg* const fargp = VN_CAST(argp, SFormatArg); + if (fargp && fargp->formatAttr().isEnum()) { + // Evaluate the format before materializing enum arguments. + AstVar* const varp = createTemp(nodep->exprsp()); + varp->noSubst(true); + break; + } + } + } for (AstNodeExpr *argp = exprsp, *nextp; argp; argp = nextp) { nextp = VN_AS(argp->nextp(), NodeExpr); AstSFormatArg* const fargp = VN_CAST(argp, SFormatArg); AstNodeExpr* const subargp = fargp ? fargp->exprp() : argp; + if (fargp && fargp->formatAttr().isEnum()) { + // The name lookup must see the same value as the numeric argument. + AstVar* const valueVarp = createTemp(subargp); + valueVarp->noSubst(true); + if (!VN_IS(fargp->namep(), VarRef)) { + AstVar* const nameVarp = createTemp(fargp->namep()); + nameVarp->noSubst(true); + } + continue; + } // Must avoid taking address of rvalue, so even Const needs a temp if (subargp->isString() && !VN_IS(subargp, VarRef)) { AstVar* const varp = createTemp(subargp); diff --git a/src/V3Simulate.h b/src/V3Simulate.h index d4b7fde4d..f15cb174d 100644 --- a/src/V3Simulate.h +++ b/src/V3Simulate.h @@ -1261,7 +1261,8 @@ private: void visit(AstSFormatArg* nodep) override { checkNodeInfo(nodep); - iterateChildrenConst(nodep); + // Constant enum names come from the dtype, not the runtime lookup. + iterateAndNextConstNull(nodep->exprp()); } void visit(AstSFormatF* nodep) override { if (jumpingOver()) return; @@ -1305,7 +1306,9 @@ private: break; } const string pformat = "%"s + width + pos[0]; - result += constp->num().displayed(nodep, pformat, formatAttr); + result += formatAttr.isEnum() + ? constp->num().displayedEnum(fargp, pformat) + : constp->num().displayed(nodep, pformat, formatAttr); } else { switch (std::tolower(pos[0])) { case '%': result += "%"; break; diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 9954767b2..922758e82 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -221,6 +221,7 @@ class WidthVisitor final : public VNVisitor { using DTypeMap = std::map; // STATE + V3UniqueNames m_enumTempNames; // Captured enum expressions V3UniqueNames m_insideTempNames; // For generating unique temporary variable names for // `inside` expressions VMemberMap m_memberMap; // Member names cached for fast lookup @@ -6727,6 +6728,10 @@ class WidthVisitor final : public VNVisitor { while (AstNodeExpr* argp = oldExprsp) { oldExprsp = VN_AS(oldExprsp->nextp(), NodeExpr); if (oldExprsp) oldExprsp->unlinkFrBackWithNext(); + if (VN_IS(argp, SFormatArg)) { + nodep->addExprsp(argp); + continue; + } // Need to record formatAttr's at elaboration time, as later optimizations // may change an argument's data type. Plus need them for runtime formats VFormatAttr formatAttr = VFormatAttr::UNSIGNED; @@ -6767,21 +6772,14 @@ class WidthVisitor final : public VNVisitor { argp = newp; } else if (nodep->exprFormat()) { if (AstEnumDType* const enumDtp = formatEnumDType(argp)) { - const VFormatAttr attr - = enumDtp->isSigned() ? VFormatAttr::ENUM_SIGNED : VFormatAttr::ENUM; - nodep->addExprsp(new AstSFormatArg{argp->fileline(), attr, argp}); - AstNodeExpr* const namep - = enumSelect(argp->cloneTreePure(false), enumDtp, VAttrType::ENUM_NAME); - nodep->addExprsp( - new AstSFormatArg{namep->fileline(), VFormatAttr::STRING, namep}); + nodep->addExprsp(newFormatEnumArg(argp, enumDtp)); continue; } } if (formatAttr.isUnsigned() && dtypep->isSigned()) { formatAttr = VFormatAttr::SIGNED; } - if (VN_IS(argp, SFormatArg) // Already done - || formatAttr.isUnsigned()) { // Save Ast space and imply the AstSFormatArg + if (formatAttr.isUnsigned()) { // Save Ast space and imply the AstSFormatArg nodep->addExprsp(argp); } else { nodep->addExprsp(new AstSFormatArg{argp->fileline(), formatAttr, argp}); @@ -8024,6 +8022,7 @@ class WidthVisitor final : public VNVisitor { } void visit(AstNodeModule* nodep) override { assertAtStatement(nodep); + VL_RESTORER_COPY(m_enumTempNames); VL_RESTORER_COPY(m_insideTempNames); if (AstClass* const classp = VN_CAST(nodep, Class)) { visitClass(classp); @@ -8708,16 +8707,13 @@ class WidthVisitor final : public VNVisitor { // For sformatf's with constant format, iterate/check arguments UASSERT_OBJ(!nodep->exprFormat(), nodep, "Assumes constant format"); bool inPct = false; - string fmtMods; AstNodeExpr* argp = nodep->exprsp(); string newFormat; for (char ch : nodep->text()) { if (!inPct && ch == '%') { inPct = true; - fmtMods = ""; newFormat += ch; } else if (inPct && (std::isdigit(ch) || ch == '.' || ch == '-')) { - fmtMods += ch; newFormat += ch; } else if (!inPct) { // Normal text newFormat += ch; @@ -8784,33 +8780,11 @@ class WidthVisitor final : public VNVisitor { case 'p': // FALLTHRU case 's': // As with enum.name(): valid values print the mnemonic, else numeric - if (subargp) { + if (subargp && !fargp) { if (AstEnumDType* const enumDtp = formatEnumDType(subargp)) { - string fallbackFormat = "%0d"; - if (ch == 'p') { - bool widthSet = false; - size_t width = 0; - for (const char mod : fmtMods) { - if (!std::isdigit(mod)) continue; - widthSet = true; - width = width * 10 + (mod - '0'); - } - if (widthSet && width == 0) fallbackFormat = "'h%0h"; - } - AstNodeExpr* fallbackp = subargp->cloneTreePure(false); - if (enumDtp->isSigned()) { - fallbackp = new AstSFormatArg{subargp->fileline(), - VFormatAttr::SIGNED, fallbackp}; - } - AstNodeExpr* const newp - = new AstCond{subargp->fileline(), enumTestValid(subargp, enumDtp), - enumSelect(subargp->cloneTreePure(false), enumDtp, - VAttrType::ENUM_NAME), - new AstSFormatF{subargp->fileline(), fallbackFormat, - true, fallbackp}}; - subargp->replaceWith(new AstSFormatArg{subargp->fileline(), - VFormatAttr::COMPLEX, newp}); - VL_DO_DANGLING(pushDeletep(subargp), subargp); + VNRelinker relinker; + subargp->unlinkFrBack(&relinker); + relinker.relink(newFormatEnumArg(subargp, enumDtp)); } } argp = nextp; @@ -8832,11 +8806,47 @@ class WidthVisitor final : public VNVisitor { enumDtp = VN_CAST(varrefp->varp()->dtypep()->skipRefToEnump(), EnumDType); } } - // Enums > 64 bits have no name table (see enumMaxValue); format as plain numbers - if (enumDtp && enumDtp->width() > VL_QUADSIZE) return nullptr; return enumDtp; } + AstExprStmt* newEnumCapture(AstNodeExpr* valuep, AstEnumDType* dtypep) { + FileLine* const flp = valuep->fileline(); + AstVar* const varp = new AstVar{flp, VVarType::XTEMP, m_enumTempNames.get(valuep), dtypep}; + varp->noSubst(true); + if (m_ftaskp) { + varp->funcLocal(true); + varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); + m_ftaskp->addStmtsp(varp); + } else { + UASSERT_OBJ(m_modep, valuep, "Enum expression without a containing module"); + m_modep->addStmtsp(varp); + } + iterate(varp); + AstExprStmt* const exprp = new AstExprStmt{ + flp, new AstAssign{flp, new AstVarRef{flp, varp, VAccess::WRITE}, valuep}, + new AstVarRef{flp, varp, VAccess::READ}}; + iterate(exprp); + return exprp; + } + + AstSFormatArg* newFormatEnumArg(AstNodeExpr* valuep, AstEnumDType* dtypep) { + if (valuep->isPure()) valuep = V3Const::constifyEdit(valuep); + AstNodeExpr* keyp; + if (VN_IS(valuep, Const) || VN_IS(valuep, VarRef) || (!m_modep && !m_ftaskp)) { + // Standalone parameter expressions are checked for constness by V3Const. + keyp = valuep->cloneTree(false); + } else { + AstExprStmt* const capturep = newEnumCapture(valuep, dtypep); + valuep = capturep; + keyp = capturep->resultp()->cloneTreePure(false); + } + const VFormatAttr attr = dtypep->isSigned() ? VFormatAttr::ENUM_SIGNED : VFormatAttr::ENUM; + AstSFormatArg* const argp = new AstSFormatArg{valuep->fileline(), attr, valuep}; + argp->dtypep(dtypep); + argp->namep(enumSelect(keyp, dtypep, VAttrType::ENUM_NAME)); + return argp; + } + //---------------------------------------------------------------------- // LOWER LEVEL WIDTH METHODS (none iterate) @@ -9979,7 +9989,7 @@ class WidthVisitor final : public VNVisitor { } if (adtypep->itemsp()->width() > 64) { errNodep->v3warn(E_UNSUPPORTED, - "Unsupported: enum next/prev/name method on enum with > 64 bits"); + "Unsupported: enum next/prev method on enum with > 64 bits"); return 64; } return maxval; @@ -10039,6 +10049,7 @@ class WidthVisitor final : public VNVisitor { UASSERT_OBJ(nodep->itemsp(), nodep, "enum without items"); std::map values; { + uint64_t ordinal = 0; AstEnumItem* const firstp = nodep->itemsp(); const AstEnumItem* prevp = firstp; // Prev must start with last item while (prevp->nextp()) prevp = VN_AS(prevp->nextp(), EnumItem); @@ -10047,10 +10058,10 @@ class WidthVisitor final : public VNVisitor { const AstConst* const vconstp = VN_AS(itemp->valuep(), Const); UASSERT_OBJ(vconstp, nodep, "Enum item without constified value"); if (!vconstp->num().isAnyXZ()) { // Can 2-state runtime decode - const uint64_t i = vconstp->toUQuad(); + const uint64_t i = nodep->isWide() ? ++ordinal : vconstp->toUQuad(); if (attrType == VAttrType::ENUM_NAME) { values[i] = new AstConst{nodep->fileline(), AstConst::String{}, - itemp->name()}; + V3Number::displayedEnumName(itemp)}; } else if (attrType == VAttrType::ENUM_NEXT) { values[i] = (nextp ? nextp : firstp)->valuep()->cloneTree(false); // A const @@ -10080,9 +10091,44 @@ class WidthVisitor final : public VNVisitor { return pair.first->second; } - static AstNodeExpr* enumSelect(AstNodeExpr* nodep, AstEnumDType* adtypep, VAttrType attrType) { + AstNodeExpr* enumSelect(AstNodeExpr* nodep, AstEnumDType* adtypep, VAttrType attrType) { // Return expression to get given attrType information from a enum's value (nodep) - // Need a runtime lookup table. Yuk. + if (attrType == VAttrType::ENUM_NAME) { + if (nodep->isPure()) nodep = V3Const::constifyEdit(nodep); + if (const AstConst* const constp = VN_CAST(nodep, Const)) { + AstNodeExpr* const newp = new AstConst{nodep->fileline(), AstConst::String{}, + constp->num().displayedEnumName(adtypep)}; + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return newp; + } + } + if (attrType == VAttrType::ENUM_NAME && !nodep->isPure() && (m_modep || m_ftaskp)) { + AstExprStmt* const capturep = newEnumCapture(nodep, adtypep); + AstNodeExpr* const valuep = capturep->resultp()->unlinkFrBack(); + capturep->resultp(enumSelect(valuep, adtypep, attrType)); + capturep->dtypeSetString(); + return capturep; + } + if (attrType == VAttrType::ENUM_NAME && adtypep->isWide()) { + // Wide values select an ordinal in the shared name table. + AstNodeExpr* indexp = new AstConst{nodep->fileline(), 0}; + uint32_t ordinal = 0; + for (const AstEnumItem* itemp = adtypep->itemsp(); itemp; + itemp = VN_AS(itemp->nextp(), EnumItem)) { + const AstConst* const constp = VN_AS(itemp->valuep(), Const); + if (constp->num().isAnyXZ()) continue; + indexp = new AstCond{nodep->fileline(), + new AstEq{nodep->fileline(), nodep->cloneTree(false), + itemp->valuep()->cloneTree(false)}, + new AstConst{nodep->fileline(), ++ordinal}, indexp}; + } + AstVar* const varp = enumVarp(adtypep, attrType, false, ordinal); + AstNodeExpr* const newp + = new AstArraySel{nodep->fileline(), newVarRefDollarUnit(varp), indexp}; + newp->dtypeSetString(); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return newp; + } const uint64_t msbdim = enumMaxValue(nodep, adtypep); const bool assoc = msbdim > ENUM_LOOKUP_BITS; AstNodeExpr* newp; @@ -10092,12 +10138,24 @@ class WidthVisitor final : public VNVisitor { } else { const int selwidth = V3Number::log2b(msbdim) + 1; // Width to address a bit AstVar* const varp = enumVarp(adtypep, attrType, false, (1ULL << selwidth) - 1); + AstNodeExpr* const boundp + = attrType == VAttrType::ENUM_NAME + && (adtypep->width() > selwidth || msbdim != (1ULL << selwidth) - 1) + ? new AstGt{nodep->fileline(), nodep->cloneTree(false), + new AstConst{nodep->fileline(), + V3Number{nodep, adtypep->width(), + static_cast(msbdim)}}} + : nullptr; newp = new AstArraySel{ nodep->fileline(), newVarRefDollarUnit(varp), // Select in case widths are off due to msblen!=width // We return "random" values if outside the range, which is fine // as next/previous on illegal values just need something good out new AstSel{nodep->fileline(), nodep, 0, selwidth}}; + if (boundp) { + newp = new AstCond{nodep->fileline(), boundp, + new AstConst{nodep->fileline(), AstConst::String{}, ""}, newp}; + } } if (attrType == VAttrType::ENUM_NAME) { newp->dtypeSetString(); @@ -10391,7 +10449,8 @@ public: WidthVisitor(bool paramsOnly, // [in] TRUE if we are considering parameters only. bool doGenerate) // [in] TRUE if we are inside a generate statement and // // don't wish to trigger errors - : m_insideTempNames{"__VInside"} + : m_enumTempNames{"__Venum"} + , m_insideTempNames{"__VInside"} , m_paramsOnly{paramsOnly} , m_doGenerate{doGenerate} {} AstNode* mainAcceptEdit(AstNode* nodep) { diff --git a/test_regress/t/t_debug_emitv.out b/test_regress/t/t_debug_emitv.out index 013bce73a..0a8e08bd9 100644 --- a/test_regress/t/t_debug_emitv.out +++ b/test_regress/t/t_debug_emitv.out @@ -52,6 +52,10 @@ module Vt_debug_emitv_t; if ((ZERO == 'sh0)) begin $write(""); end + $display("%p", $_EXPRSTMT( + __Venum_h4d326b97__0 = (in); + , __Venum_h4d326b97__0); + ); if ($value$plusargs("TEST=%d", i1)) begin $display("value was %d", i1); end @@ -1023,6 +1027,10 @@ module Vt_debug_emitv_t; cg_trans_inst = new(); Vt_debug_emitv_cg_cross cg_cross_instVt_debug_emitv_cg_cross; cg_cross_inst = new(); + enum logic [2:0] { + ZERO = 3'h0, + ONE = 3'h1 + } __Venum_h4d326b97__0; endmodule package Vt_debug_emitv___024unit; class Vt_debug_emitv_Cls; @@ -1154,6 +1162,7 @@ package Vt_debug_emitv___024unit; disable label3; end endfunction + string __Venumtab_enum_name17[1:0]; endpackage package Vt_debug_emitv_std; class Vt_debug_emitv_semaphore; diff --git a/test_regress/t/t_debug_emitv.v b/test_regress/t/t_debug_emitv.v index 0fe96dbbf..b138c2e9c 100644 --- a/test_regress/t/t_debug_emitv.v +++ b/test_regress/t/t_debug_emitv.v @@ -96,6 +96,7 @@ module t (/*AUTOARG*/ if ($test$plusargs("HELLO")) $display("Hello argument found."); if (Pkg::FOO == 0) $write(""); if (ZERO == 0) $write(""); + $display("%p", e_t'(in)); if ($value$plusargs("TEST=%d", i1)) $display("value was %d", i1); else diff --git a/test_regress/t/t_display_enum_format.v b/test_regress/t/t_display_enum_format.v index 4281a9380..b956ae9da 100644 --- a/test_regress/t/t_display_enum_format.v +++ b/test_regress/t/t_display_enum_format.v @@ -12,7 +12,7 @@ // verilog_format: on module t ( - input string empty_no_opt + input no_opt ); typedef enum logic [1:0] { E0 = 0, @@ -24,13 +24,28 @@ module t ( W64A = 64'h1, W64B = 64'h0000_0001_0000_0001 } wide64_e; - // Enums > 64 bits are beyond enum.name() support, so %p/%s format numerically typedef enum logic [95:0] { W96A = 96'h1, W96B = 96'hA_0000_0000_0000_0001 } wide96_e; typedef logic signed [4095:0] uvm_bitstream_t; + function automatic wide64_e const_enum(input bit high); + wide64_e value; + if (high) value = W64B; + else value = W64A; + return value; + endfunction + + localparam string CONST_ENUM_TEXT = $sformatf("%p/%s", const_enum(1'b1), const_enum(1'b1)); + + // IEEE 1800-2023 21.2.1.6 permits implementation-specific %0p output. +`ifdef QUESTA + localparam string COMPACT_NUM_PREFIX = ""; +`else + localparam string COMPACT_NUM_PREFIX = "'h"; +`endif + my_e e; wide64_e e64; wide96_e e96; @@ -39,6 +54,11 @@ module t ( initial begin string fmt; + string formatted; + string empty_no_opt; + // Keep formats nonconstant using an input supported by the generated testbench. + empty_no_opt = (no_opt === 1'b1) ? "unexpected" : ""; + `checks(CONST_ENUM_TEXT, "W64B/W64B"); begin my_e it; string names_p; @@ -53,134 +73,222 @@ module t ( names_s = {names_s, ","}; vals_d = {vals_d, ","}; end - names_p = {names_p, $sformatf("%p", it)}; - names_s = {names_s, $sformatf("%s", it)}; - vals_d = {vals_d, $sformatf("%0d", it)}; + names_p = {names_p, $sformatf("%p/%p", it, it)}; + names_s = {names_s, $sformatf("%s/%s", it, it)}; + vals_d = {vals_d, $sformatf("%0d/%0d", it, it)}; if (it == it.last) break; end - `checks(names_p, "E0,E1,E2"); - `checks(names_s, "E0,E1,E2"); - `checks(vals_d, "0,1,2"); + `checks(names_p, "E0/E0,E1/E1,E2/E2"); + `checks(names_s, "E0/E0,E1/E1,E2/E2"); + `checks(vals_d, "0/0,1/1,2/2"); end // Valid enum values print mnemonic for %p/%s. e = E0; - `checks($sformatf("%p", e), "E0"); - `checks($sformatf("%s", e), "E0"); + formatted = $sformatf("%p/%p", e, e); + `checks(formatted, "E0/E0"); + formatted = $sformatf("%s/%s", e, e); + `checks(formatted, "E0/E0"); e = E1; - `checks($sformatf("%p", e), "E1"); - `checks($sformatf("%P", e), "E1"); - `checks($sformatf("%0p", e), "E1"); - `checks($sformatf("%s", e), "E1"); - `checks($sformatf("%S", e), "E1"); - `checks($sformatf("%d", e), "1"); - `checks($sformatf("%0d", e), "1"); - `checks($sformatf("%h", e), "1"); - `checks($sformatf("%0h", e), "1"); - `checks($sformatf("%b", e), "01"); - `checks($sformatf("%0b", e), "1"); - `checks($sformatf("%o", e), "1"); - `checks($sformatf("%0o", e), "1"); - `checks($sformatf("%x", e), "1"); - `checks($sformatf("%0x", e), "1"); + formatted = $sformatf("%p/%p", e, e); + `checks(formatted, "E1/E1"); + formatted = $sformatf("%P/%P", e, e); + `checks(formatted, "E1/E1"); + formatted = $sformatf("%0p/%0p", e, e); + `checks(formatted, "E1/E1"); + formatted = $sformatf("%s/%s", e, e); + `checks(formatted, "E1/E1"); + formatted = $sformatf("%S/%S", e, e); + `checks(formatted, "E1/E1"); + formatted = $sformatf("%d/%d", e, e); + `checks(formatted, "1/1"); + formatted = $sformatf("%0d/%0d", e, e); + `checks(formatted, "1/1"); + formatted = $sformatf("%h/%h", e, e); + `checks(formatted, "1/1"); + formatted = $sformatf("%0h/%0h", e, e); + `checks(formatted, "1/1"); + formatted = $sformatf("%b/%b", e, e); + `checks(formatted, "01/01"); + formatted = $sformatf("%0b/%0b", e, e); + `checks(formatted, "1/1"); + formatted = $sformatf("%o/%o", e, e); + `checks(formatted, "1/1"); + formatted = $sformatf("%0o/%0o", e, e); + `checks(formatted, "1/1"); + formatted = $sformatf("%x/%x", e, e); + `checks(formatted, "1/1"); + formatted = $sformatf("%0x/%0x", e, e); + `checks(formatted, "1/1"); e = E2; - `checks($sformatf("%p", e), "E2"); - `checks($sformatf("%s", e), "E2"); + formatted = $sformatf("%p/%p", e, e); + `checks(formatted, "E2/E2"); + formatted = $sformatf("%s/%s", e, e); + `checks(formatted, "E2/E2"); `checks($sformatf("%s|%p", e, e), "E2|E2"); - `checks($sformatf("%4p", e), "E2"); - `checks($sformatf("%-4p", e), "E2"); - `checks($sformatf("%d", e), "2"); - `checks($sformatf("%h", e), "2"); - `checks($sformatf("%b", e), "10"); - `checks($sformatf("%0b", e), "10"); - `checks($sformatf("%o", e), "2"); - `checks($sformatf("%x", e), "2"); - `checks($sformatf("%4d", e), " 2"); - `checks($sformatf("%04d", e), "0002"); - `checks($sformatf("%4h", e), "0002"); - `checks($sformatf("%-4s", e), "E2 "); - `checks($sformatf("%4s", e), " E2"); + // IEEE 1800-2023 21.2.1.6 specifies %p/%0p, not nonzero %p field widths. + formatted = $sformatf("%4p/%-4p", e, e); +`ifdef QUESTA + `checks(formatted, "00E2/E2 "); +`else + `checks(formatted, "E2/E2"); +`endif + formatted = $sformatf("%d/%d", e, e); + `checks(formatted, "2/2"); + formatted = $sformatf("%h/%h", e, e); + `checks(formatted, "2/2"); + formatted = $sformatf("%b/%b", e, e); + `checks(formatted, "10/10"); + formatted = $sformatf("%0b/%0b", e, e); + `checks(formatted, "10/10"); + formatted = $sformatf("%o/%o", e, e); + `checks(formatted, "2/2"); + formatted = $sformatf("%x/%x", e, e); + `checks(formatted, "2/2"); + formatted = $sformatf("%4d/%4d", e, e); + `checks(formatted, " 2/ 2"); + formatted = $sformatf("%04d/%04d", e, e); +`ifdef QUESTA + `checks(formatted, " 2/ 2"); +`else + `checks(formatted, "0002/0002"); +`endif + formatted = $sformatf("%4h/%4h", e, e); + `checks(formatted, "0002/0002"); + formatted = $sformatf("%-4s/%-4s", e, e); + `checks(formatted, "E2 /E2 "); + formatted = $sformatf("%4s/%4s", e, e); + `checks(formatted, " E2/ E2"); // `%p`/`%s` in non-terminal positions with mixed formatters. `checks($sformatf("%0d:%s:%0d", 9, e, 7), "9:E2:7"); `checks($sformatf("%s %h %p", e, 4'hA, e), "E2 a E2"); - `checks($sformatf("pre %% %s post", e), "pre % E2 post"); + formatted = $sformatf("pre %% %s/%s post", e, e); + `checks(formatted, "pre % E2/E2 post"); // Complex enum expressions (non-var-ref) in format args. - `checks($sformatf("%s", (1'b1 ? E2 : E0)), "E2"); + formatted = $sformatf("%s/%s", (1'b1 ? E2 : E0), (1'b1 ? E2 : E0)); + `checks(formatted, "E2/E2"); // 64-bit enums should preserve bits above 32 in both named and numeric cases. e64 = W64B; - `checks($sformatf("%p", e64), "W64B"); - `checks($sformatf("%s", e64), "W64B"); + formatted = $sformatf("%p/%p", e64, e64); + `checks(formatted, "W64B/W64B"); + formatted = $sformatf("%s/%s", e64, e64); + `checks(formatted, "W64B/W64B"); e64 = wide64_e'(64'h0000_0002_0000_0001); - `checks($sformatf("%p", e64), "8589934593"); - `checks($sformatf("%s", e64), "8589934593"); + formatted = $sformatf("%p/%p", e64, e64); + `checks(formatted, "8589934593/8589934593"); + formatted = $sformatf("%s/%s", e64, e64); + `checks(formatted, "8589934593/8589934593"); n64 = 64'h0000_0000_0000_0001; - `checks($sformatf("%0p", n64), "'h1"); - // > 64-bit enums print numerically for %p (no name table support) + formatted = $sformatf("%0p/%0p", n64, n64); + `checks(formatted, {COMPACT_NUM_PREFIX, "1/", COMPACT_NUM_PREFIX, "1"}); + // Wide enums use names for %p/%s without changing explicit numeric formats. e96 = W96B; // 10 * 2**64 + 1 if (empty_no_opt != "") e96 = W96A; // Defeat constant folding - `checks($sformatf("%p", e96), "184467440737095516161"); - `checks($sformatf("%0p", e96), "'ha0000000000000001"); - `checks($sformatf("%0d", e96), "184467440737095516161"); - `checks($sformatf("%0h", e96), "a0000000000000001"); + formatted = $sformatf("%p/%p", e96, e96); + `checks(formatted, "W96B/W96B"); + formatted = $sformatf("%s/%s", e96, e96); + `checks(formatted, "W96B/W96B"); + formatted = $sformatf("%0p/%0p", e96, e96); + `checks(formatted, "W96B/W96B"); + formatted = $sformatf("%0d/%0d", e96, e96); + `checks(formatted, "184467440737095516161/184467440737095516161"); + formatted = $sformatf("%0h/%0h", e96, e96); + `checks(formatted, "a0000000000000001/a0000000000000001"); // Exercise display/write-family formatting path in addition to $sformatf checks. $display("display-valid:%s:%0d:%p", e, 7, e); $write("write-valid:%s:%0d:%p\n", e, 8, e); // Invalid enum values fall back to numeric formatting for %p/%s. e = my_e'(3); - `checks($sformatf("%p", e), "3"); - `checks($sformatf("%P", e), "3"); - `checks($sformatf("%0p", e), "'h3"); - `checks($sformatf("%s", e), "3"); - `checks($sformatf("%S", e), "3"); - `checks($sformatf("%4p", e), "3"); - `checks($sformatf("%4s", e), " 3"); - `checks($sformatf("%d", e), "3"); - `checks($sformatf("%0d", e), "3"); - `checks($sformatf("%h", e), "3"); - `checks($sformatf("%0h", e), "3"); - `checks($sformatf("%b", e), "11"); - `checks($sformatf("%0b", e), "11"); - `checks($sformatf("%o", e), "3"); - `checks($sformatf("%x", e), "3"); + formatted = $sformatf("%p/%p", e, e); + `checks(formatted, "3/3"); + formatted = $sformatf("%P/%P", e, e); + `checks(formatted, "3/3"); + formatted = $sformatf("%0p/%0p", e, e); + `checks(formatted, {COMPACT_NUM_PREFIX, "3/", COMPACT_NUM_PREFIX, "3"}); + formatted = $sformatf("%s/%s", e, e); + `checks(formatted, "3/3"); + formatted = $sformatf("%S/%S", e, e); + `checks(formatted, "3/3"); + formatted = $sformatf("%4p/%4p", e, e); +`ifdef QUESTA + `checks(formatted, "0003/0003"); +`else + `checks(formatted, "3/3"); +`endif + formatted = $sformatf("%4s/%4s", e, e); + `checks(formatted, " 3/ 3"); + formatted = $sformatf("%d/%d", e, e); + `checks(formatted, "3/3"); + formatted = $sformatf("%0d/%0d", e, e); + `checks(formatted, "3/3"); + formatted = $sformatf("%h/%h", e, e); + `checks(formatted, "3/3"); + formatted = $sformatf("%0h/%0h", e, e); + `checks(formatted, "3/3"); + formatted = $sformatf("%b/%b", e, e); + `checks(formatted, "11/11"); + formatted = $sformatf("%0b/%0b", e, e); + `checks(formatted, "11/11"); + formatted = $sformatf("%o/%o", e, e); + `checks(formatted, "3/3"); + formatted = $sformatf("%x/%x", e, e); + `checks(formatted, "3/3"); // Non-terminal invalid-value fallback with mixed formatters. `checks($sformatf("%0d:%p:%0d", 9, e, 7), "9:3:7"); `checks($sformatf("%s %h %p", e, 4'hA, e), "3 a 3"); - `checks($sformatf("pre %% %s post", e), "pre % 3 post"); + formatted = $sformatf("pre %% %s/%s post", e, e); + `checks(formatted, "pre % 3/3 post"); `checks($sformatf("%s|%p", e, e), "3|3"); - `checks($sformatf("%s", (1'b1 ? my_e'(3) : E0)), "3"); - `checks($sformatf("%p", (1'b0 ? E0 : my_e'(3))), "3"); + formatted = $sformatf("%s/%s", (1'b1 ? my_e'(3) : E0), (1'b1 ? my_e'(3) : E0)); + `checks(formatted, "3/3"); + formatted = $sformatf("%p/%p", (1'b0 ? E0 : my_e'(3)), (1'b0 ? E0 : my_e'(3))); + `checks(formatted, "3/3"); $display("display-invalid:%s:%0d:%p", e, 7, e); $write("write-invalid:%s:%0d:%p\n", e, 8, e); // Runtime-computed $sformatf formats should preserve enum mnemonic/fallback behavior. e = E2; - fmt = {"%", "s", empty_no_opt}; - `checks($sformatf(fmt, e), "E2"); - fmt = {"%", "p", empty_no_opt}; - `checks($sformatf(fmt, e), "E2"); + fmt = {"%", "s/%s", empty_no_opt}; + formatted = $sformatf(fmt, e, e); + `checks(formatted, "E2/E2"); + fmt = {"%", "p/%p", empty_no_opt}; + formatted = $sformatf(fmt, e, e); + `checks(formatted, "E2/E2"); + fmt = {"%0h/%0h", empty_no_opt}; + formatted = $sformatf(fmt, e, e); + `checks(formatted, "2/2"); fmt = {"%0d:%", "s", ":%0d", empty_no_opt}; `checks($sformatf(fmt, 9, e, 7), "9:E2:7"); fmt = {"%", "s", " %h %", "p", empty_no_opt}; `checks($sformatf(fmt, e, 4'hA, e), "E2 a E2"); e = my_e'(3); - fmt = {"%", "s", empty_no_opt}; - `checks($sformatf(fmt, e), "3"); - fmt = {"%", "p", empty_no_opt}; - `checks($sformatf(fmt, e), "3"); - fmt = {"%0", "p", empty_no_opt}; - `checks($sformatf(fmt, e), "'h3"); + fmt = {"%0b/%0b", empty_no_opt}; + formatted = $sformatf(fmt, e, e); + `checks(formatted, "11/11"); + fmt = {"%", "s/%s", empty_no_opt}; + formatted = $sformatf(fmt, e, e); + `checks(formatted, "3/3"); + fmt = {"%", "p/%p", empty_no_opt}; + formatted = $sformatf(fmt, e, e); + `checks(formatted, "3/3"); + fmt = {"%0", "p/%0p", empty_no_opt}; + formatted = $sformatf(fmt, e, e); + `checks(formatted, {COMPACT_NUM_PREFIX, "3/", COMPACT_NUM_PREFIX, "3"}); fmt = {"%0d:%", "s", ":%0d", empty_no_opt}; `checks($sformatf(fmt, 9, e, 7), "9:3:7"); fmt = {"%", "s", " %h %", "p", empty_no_opt}; `checks($sformatf(fmt, e, 4'hA, e), "3 a 3"); - fmt = {"%", "p", empty_no_opt}; - `checks($sformatf(fmt, e64), "8589934593"); - // > 64-bit enums use the non-ENUM format in runtime formats too - fmt = {"%", "p", empty_no_opt}; - `checks($sformatf(fmt, e96), "184467440737095516161"); - fmt = {"%0d", empty_no_opt}; - `checks($sformatf(fmt, e96), "184467440737095516161"); + fmt = {"%", "p/%p", empty_no_opt}; + formatted = $sformatf(fmt, e64, e64); + `checks(formatted, "8589934593/8589934593"); + // Runtime formats must also preserve the wide enum's name. + fmt = {"%", "p/%p", empty_no_opt}; + formatted = $sformatf(fmt, e96, e96); + `checks(formatted, "W96B/W96B"); + fmt = {"%0d/%0d", empty_no_opt}; + formatted = $sformatf(fmt, e96, e96); + `checks(formatted, "184467440737095516161/184467440737095516161"); bitstream_value = 30; `checks($sformatf("%0s%0t", "", bitstream_value), "30"); bitstream_value = '0; @@ -191,7 +299,8 @@ module t ( `checks($sformatf("%0s%0t", "", bitstream_value), "4294967297"); bitstream_value[7:0] = "A"; // verilator lint_off WIDTHTRUNC - `checks($sformatf("%c", bitstream_value), "A"); + formatted = $sformatf("%c/%c", bitstream_value, bitstream_value); + `checks(formatted, "A/A"); // verilator lint_on WIDTHTRUNC $write("*-* All Finished *-*\n"); diff --git a/test_regress/t/t_display_enum_nolift.py b/test_regress/t/t_display_enum_nolift.py new file mode 100755 index 000000000..e311e226e --- /dev/null +++ b/test_regress/t/t_display_enum_nolift.py @@ -0,0 +1,19 @@ +#!/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('vlt') +test.top_filename = 't/t_display_pattern_format.v' + +test.compile(verilator_flags2=['--binary', '-fno-lift-expr']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_display_enum_protect_ids.py b/test_regress/t/t_display_enum_protect_ids.py new file mode 100755 index 000000000..9ef6f9db5 --- /dev/null +++ b/test_regress/t/t_display_enum_protect_ids.py @@ -0,0 +1,21 @@ +#!/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('vlt') +test.top_filename = 't/t_display_pattern_format.v' + +test.compile(verilator_flags2=[ + '--binary', '--protect-ids', '--protect-key ENUM_KEY', '-Wno-INSECURE', '+define+TEST_PROTECT' +]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_display_pattern_format.v b/test_regress/t/t_display_pattern_format.v index b93d7ed16..f0398c2d1 100644 --- a/test_regress/t/t_display_pattern_format.v +++ b/test_regress/t/t_display_pattern_format.v @@ -6,6 +6,7 @@ // verilog_format: off `define stop $stop +`define checkd(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); `define checks(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got='%s' exp='%s'\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); // verilog_format: on @@ -21,6 +22,10 @@ module t; SECOND = 7'd65 } enum_t; typedef enum_t enum_alias_t; + typedef enum logic [6:0] { + SMALL_FIRST = 7'd0, + SMALL_SECOND = 7'd1 + } small_t; typedef enum logic signed [6:0] { SIGNED7_NEG = -7'sd3, SIGNED7_POS = 7'sd7 @@ -34,6 +39,54 @@ module t; SIGNED65_NEG = -65'sd18446744073709551615, SIGNED65_POS = 65'sd7 } signed65_t; + typedef enum logic signed [94:0] { + SIGNED95_NEG = -95'sd19807040628566084398385987583, + SIGNED95_POS = 95'sd1 + } signed95_t; + typedef enum logic signed [128:0] { + SIGNED129_LOW = 129'sd1, + SIGNED129_HIGH = 129'sd18446744073709551617, + SIGNED129_NEG = -129'sd340282366920938463463374607431768211455 + } signed129_t; + typedef signed129_t signed129_alias_t; + typedef enum logic [64:0] { + UNSIGNED65_LOW = 65'h1, + UNSIGNED65_HIGH = 65'h10000000000000001, + \escaped.wide = 65'h3, + UNSIGNED65_X = 65'bx + } unsigned65_t; + typedef unsigned65_t unsigned65_alias_t; + + int enum_calls = 0; + int format_calls = 0; + unsigned65_t format_value; + function automatic unsigned65_t next_enum(input bit high); + enum_calls++; + return high ? UNSIGNED65_HIGH : UNSIGNED65_LOW; + endfunction + + function automatic string format_enum(input unsigned65_t value); + return $sformatf("%p/%s", value, value); + endfunction + + function automatic string format_next_enum(input bit high); + return $sformatf("%p", next_enum(high)); + endfunction + + function automatic enum_t next_narrow_enum(input bit high); + enum_calls++; + return high ? SECOND : FIRST; + endfunction + + function automatic string format_narrow_enum(input enum_t value); + return $sformatf("%p/%s", value, value); + endfunction + + function automatic string select_format(input bit high, input bit string_format); + format_calls++; + format_value = high ? UNSIGNED65_HIGH : UNSIGNED65_LOW; + return string_format ? "%s" : "%p"; + endfunction localparam text_t TEXT_PARAM = "quote=\" slash=\\ bell=\a form=\f vert=\v ctrl=\001"; localparam string ESCAPED_PARAM_STRING = $sformatf("%p", TEXT_PARAM); @@ -47,8 +100,78 @@ module t; localparam string WIDE_SECOND_TEXT = $sformatf("%p", WIDE_SECOND); localparam string REAL_FIRST_TEXT = $sformatf("%p", 1.25); localparam string REAL_SECOND_TEXT = $sformatf("%p", 0.5); + localparam string ENUM_FIRST_TEXT = $sformatf("%p", FIRST); + localparam string ENUM_SECOND_TEXT = $sformatf("%s", SECOND); + localparam string ENUM7_NEG_TEXT = $sformatf("%p", SIGNED7_NEG); + localparam string ENUM33_NEG_TEXT = $sformatf("%p", SIGNED33_NEG); + localparam string NARROW_FUNC_TEXT = format_narrow_enum(FIRST); + localparam string NARROW_EXPR_TEXT = $sformatf("%p", enum_t'(7'd1 + 7'd2)); + localparam string SMALL_INVALID_TEXT = $sformatf("%p", small_t'(7'd2)); + localparam string ENUM_LOW_TEXT = $sformatf("%p", UNSIGNED65_LOW); + localparam string ENUM_HIGH_TEXT = $sformatf("%s", UNSIGNED65_HIGH); + localparam unsigned65_t ENUM_METHOD_VALUE = UNSIGNED65_HIGH; + localparam string ENUM_METHOD_TEXT = ENUM_METHOD_VALUE.name(); + localparam string WIDE_EXPR_TEXT = $sformatf("%p", unsigned65_t'((65'd1 << 64) | 65'd1)); + localparam string ENUM_NEG_TEXT = $sformatf("%p", SIGNED65_NEG); + localparam string ENUM95_NEG_TEXT = $sformatf("%p", SIGNED95_NEG); + localparam string ENUM95_POS_TEXT = $sformatf("%s", SIGNED95_POS); + localparam string ENUM129_LOW_TEXT = $sformatf("%p", SIGNED129_LOW); + localparam string ENUM129_HIGH_TEXT = $sformatf("%s", SIGNED129_HIGH); + localparam string ENUM129_NEG_TEXT = $sformatf("%p", SIGNED129_NEG); + localparam string ENUM_ESCAPED_TEXT = $sformatf("%p", \escaped.wide ); + localparam string ENUM_FUNC_TEXT = format_enum(UNSIGNED65_HIGH); + localparam unsigned65_t ENUM_UNKNOWN = unsigned65_t'(65'h10000000000000002); + localparam string ENUM_UNKNOWN_TEXT = $sformatf("%p", ENUM_UNKNOWN); + localparam string ENUM_UNKNOWN_STRING = $sformatf("%s", ENUM_UNKNOWN); + localparam string ENUM_UNKNOWN_COMPACT = $sformatf("%0p", ENUM_UNKNOWN); + localparam string ENUM_UNKNOWN_FUNC_TEXT = format_enum(ENUM_UNKNOWN); + localparam string ENUM_SIGNED_UNKNOWN_TEXT = $sformatf("%p", signed65_t'(-65'sd2)); initial begin + string formatted; + string fmt; +`ifdef TEST_PROTECT + formatted = ENUM_HIGH_TEXT.substr(0, 1); + `checks(formatted, "PS"); + formatted = ENUM129_NEG_TEXT.substr(0, 1); + `checks(formatted, "PS"); +`else + `checks(ENUM_FIRST_TEXT, "FIRST"); + `checks(ENUM_SECOND_TEXT, "SECOND"); + `checks(ENUM7_NEG_TEXT, "SIGNED7_NEG"); + `checks(ENUM33_NEG_TEXT, "SIGNED33_NEG"); + `checks(NARROW_FUNC_TEXT, "FIRST/FIRST"); + `checks(NARROW_EXPR_TEXT, "FIRST"); + `checks(ENUM_LOW_TEXT, "UNSIGNED65_LOW"); + `checks(ENUM_HIGH_TEXT, "UNSIGNED65_HIGH"); + `checks(WIDE_EXPR_TEXT, "UNSIGNED65_HIGH"); + `checks(ENUM_NEG_TEXT, "SIGNED65_NEG"); + `checks(ENUM95_NEG_TEXT, "SIGNED95_NEG"); + `checks(ENUM95_POS_TEXT, "SIGNED95_POS"); + `checks(ENUM129_LOW_TEXT, "SIGNED129_LOW"); + `checks(ENUM129_HIGH_TEXT, "SIGNED129_HIGH"); + `checks(ENUM129_NEG_TEXT, "SIGNED129_NEG"); + `checks(ENUM_ESCAPED_TEXT, "\\escaped.wide "); + `checks(ENUM_FUNC_TEXT, "UNSIGNED65_HIGH/UNSIGNED65_HIGH"); +`endif + `checks(SMALL_INVALID_TEXT, "2"); + `checks(ENUM_UNKNOWN_STRING, ENUM_UNKNOWN_TEXT); + `checks(ENUM_METHOD_TEXT, ENUM_HIGH_TEXT); + `checks(ENUM_UNKNOWN_FUNC_TEXT, {ENUM_UNKNOWN_TEXT, "/", ENUM_UNKNOWN_TEXT}); + `checks(ENUM_SIGNED_UNKNOWN_TEXT, "-2"); +`ifdef QUESTA + `checks(ENUM_UNKNOWN_TEXT, "2"); + `checks(ENUM_UNKNOWN_COMPACT, "2"); +`else + `checks(ENUM_UNKNOWN_TEXT, "18446744073709551618"); + `checks(ENUM_UNKNOWN_COMPACT, "'h10000000000000002"); +`endif + fmt = "%0h"; + formatted = $sformatf(fmt, UNSIGNED65_HIGH); + `checks(formatted, "10000000000000001"); + fmt = "%p"; + formatted = $sformatf(fmt, FIRST); + `checks(formatted, ENUM_FIRST_TEXT); `ifdef QUESTA // Questa 2025.2 does not escape strings as required by IEEE 1800-2012 21.2.1.7. `checks(ESCAPED_PARAM_STRING, {"\"", TEXT_PARAM, "\""}); @@ -64,6 +187,7 @@ module t; string escaped_expected; string fmt; enum_alias_t enum_value; + small_t small_value; narrow_array_t narrow; wide_array_t wide; real real_value; @@ -74,7 +198,12 @@ module t; signed7_alias_t signed7_value; signed33_t signed33_value; signed65_t signed65_value; + signed95_t signed95_value; + signed129_alias_t signed129_value; + unsigned65_alias_t unsigned65_value; + logic [64:0] unsigned65_bits; string signed_expected; + string unsigned_expected; plain = $sformatf("round %0d", cyc); escaped = {"quote=\" slash=\\ line=\n cr=\r tab=\t bell=\a form=\f vert=\v ctrl=\001 ", plain}; @@ -122,11 +251,23 @@ module t; if (cyc[0]) enum_text = $sformatf(fmt, enum_value); else enum_text = $sformatf(fmt, enum_value); - `checks(enum_text, cyc[0] ? "SECOND" : "FIRST"); + `checks(enum_text, cyc[0] ? ENUM_SECOND_TEXT : ENUM_FIRST_TEXT); formatted = $sformatf("%p", enum_value); `checks(formatted, enum_text); formatted = $sformatf("%s", enum_value); `checks(formatted, enum_text); + formatted = enum_value.name(); + `checks(formatted, enum_text); + formatted = format_narrow_enum(enum_value); + `checks(formatted, {enum_text, "/", enum_text}); + small_value = small_t'(cyc + 2); + formatted = small_value.name(); + `checks(formatted, ""); + unsigned_expected = $sformatf("%0d", cyc + 2); + formatted = $sformatf("%p", small_value); + `checks(formatted, unsigned_expected); + formatted = $sformatf(fmt, small_value); + `checks(formatted, unsigned_expected); real_value = cyc[0] ? 0.5 : 1.25; real_expected = cyc[0] ? REAL_SECOND_TEXT : REAL_FIRST_TEXT; @@ -153,7 +294,7 @@ module t; formatted = $sformatf(fmt, signed65_value); `checks(formatted, signed_expected); - signed_expected = cyc[0] ? "SIGNED7_NEG" : "-2"; + signed_expected = cyc[0] ? ENUM7_NEG_TEXT : "-2"; `ifdef QUESTA // Questa 2025.2 zero-extends unnamed enums narrower than 32 bits for %p/%s. if (!cyc[0]) signed_expected = "126"; @@ -166,7 +307,7 @@ module t; formatted = $sformatf(fmt, signed7_value); `checks(formatted, signed_expected); - signed_expected = cyc[0] ? "SIGNED33_NEG" : "-2"; + signed_expected = cyc[0] ? ENUM33_NEG_TEXT : "-2"; formatted = $sformatf("%p", signed33_value); `checks(formatted, signed_expected); formatted = $sformatf("%s", signed33_value); @@ -174,6 +315,175 @@ module t; formatted = $sformatf(fmt, signed33_value); `checks(formatted, signed_expected); + // The named values differ only above bit 63. + unsigned65_value = cyc[0] ? UNSIGNED65_HIGH : UNSIGNED65_LOW; + unsigned_expected = cyc[0] ? ENUM_HIGH_TEXT : ENUM_LOW_TEXT; + formatted = $sformatf("%p", unsigned65_value); + `checks(formatted, unsigned_expected); + formatted = $sformatf("%s", unsigned65_value); + `checks(formatted, unsigned_expected); + formatted = unsigned65_value.name(); + `checks(formatted, unsigned_expected); + formatted = $sformatf("%0p", unsigned65_value); + `checks(formatted, unsigned_expected); + formatted = $sformatf(fmt, unsigned65_value); + `checks(formatted, unsigned_expected); + if (!$value$plusargs("enum_complement=%h", unsigned65_bits)) + unsigned65_bits = cyc[0] ? 65'h0fffffffffffffffe : 65'h1fffffffffffffffe; + formatted = $sformatf("%p", unsigned65_t'(~unsigned65_bits)); + `checks(formatted, unsigned_expected); + formatted = $sformatf(fmt, unsigned65_t'(~unsigned65_bits)); + `checks(formatted, unsigned_expected); + formatted = format_enum(unsigned65_value); + `checks(formatted, {unsigned_expected, "/", unsigned_expected}); + formatted = $sformatf("%0d:%p:%s:%0d", 9, unsigned65_value, signed65_value, 7); + signed_expected = cyc[0] ? "-2" : ENUM_NEG_TEXT; + `checks(formatted, {"9:", unsigned_expected, ":", signed_expected, ":7"}); + formatted = $sformatf("%p", signed65_value); + `checks(formatted, signed_expected); + formatted = $sformatf("%s", signed65_value); + `checks(formatted, signed_expected); + formatted = signed65_value.name(); + `checks(formatted, cyc[0] ? "" : ENUM_NEG_TEXT); + formatted = $sformatf(fmt, signed65_value); + `checks(formatted, signed_expected); + + fmt = cyc[0] ? "%0d:%p:%s:%0d" : "%0d:%P:%S:%0d"; + formatted = $sformatf(fmt, 9, unsigned65_value, signed65_value, 7); + `checks(formatted, {"9:", unsigned_expected, ":", signed_expected, ":7"}); + fmt = cyc[1] ? "%p" : "%s"; + enum_calls = 0; + formatted = $sformatf("%p", next_enum(cyc[0])); + `checks(formatted, unsigned_expected); + `checkd(enum_calls, 1); + formatted = $sformatf("%s", next_enum(cyc[0])); + `checks(formatted, unsigned_expected); + `checkd(enum_calls, 2); + formatted = $sformatf(fmt, next_enum(cyc[0])); + `checks(formatted, unsigned_expected); + `checkd(enum_calls, 3); + $sformat(formatted, "%p", next_enum(cyc[0])); + `checks(formatted, unsigned_expected); + `checkd(enum_calls, 4); + $display("wide-enum: %p", next_enum(cyc[0])); + `checkd(enum_calls, 5); + formatted = $sformatf("%p", next_narrow_enum(cyc[0])); + `checks(formatted, enum_text); + `checkd(enum_calls, 6); + formatted = $sformatf(fmt, next_narrow_enum(cyc[0])); + `checks(formatted, enum_text); + `checkd(enum_calls, 7); + formatted = next_enum(cyc[0]).name(); + `checks(formatted, unsigned_expected); + `checkd(enum_calls, 8); + formatted = next_narrow_enum(cyc[0]).name(); + `checks(formatted, enum_text); + `checkd(enum_calls, 9); + formatted = $sformatf(cyc[1] ? "%p/%p" : "%s/%s", next_enum(cyc[0]), next_enum(!cyc[0])); + `checks(formatted, {unsigned_expected, "/", cyc[0] ? ENUM_LOW_TEXT : ENUM_HIGH_TEXT}); + `checkd(enum_calls, 11); + formatted = format_next_enum(cyc[0]); + `checks(formatted, unsigned_expected); + `checkd(enum_calls, 12); + + format_value = cyc[0] ? UNSIGNED65_LOW : UNSIGNED65_HIGH; + formatted = $sformatf(select_format(cyc[0], cyc[1]), format_value); + `checks(formatted, unsigned_expected); + `checkd(format_calls, 2 * cyc + 1); + format_value = cyc[0] ? UNSIGNED65_LOW : UNSIGNED65_HIGH; + $sformat(formatted, select_format(cyc[0], !cyc[1]), format_value); + `checks(formatted, unsigned_expected); + `checkd(format_calls, 2 * cyc + 2); + + unsigned65_value = cyc[0] ? UNSIGNED65_HIGH : \escaped.wide ; + unsigned_expected = cyc[0] ? ENUM_HIGH_TEXT : ENUM_ESCAPED_TEXT; + formatted = $sformatf("%p", unsigned65_value); + `checks(formatted, unsigned_expected); + formatted = $sformatf("%s", unsigned65_value); + `checks(formatted, unsigned_expected); + formatted = unsigned65_value.name(); + `checks(formatted, unsigned_expected); + formatted = $sformatf(fmt, unsigned65_value); + `checks(formatted, unsigned_expected); + fmt = cyc[0] ? "%20s" : "%-20s"; + enum_text = $sformatf(fmt, unsigned_expected); + formatted = $sformatf(fmt, unsigned65_value); + `checks(formatted, enum_text); + + unsigned65_bits = unsigned65_value; + unsigned_expected = $sformatf("%0h", unsigned65_bits); + fmt = cyc[0] ? "%0h" : "%0H"; + formatted = $sformatf(fmt, unsigned65_value); + `checks(formatted, unsigned_expected); + unsigned_expected = $sformatf("%0d", unsigned65_bits); + fmt = cyc[0] ? "%0d" : "%0D"; + formatted = $sformatf(fmt, unsigned65_value); + `checks(formatted, unsigned_expected); + + unsigned65_value = unsigned65_t'(65'h10000000000000002 + 65'(cyc)); + unsigned65_bits = unsigned65_value; + unsigned_expected = $sformatf("%0d", unsigned65_bits); +`ifdef QUESTA + // Questa 2025.2 truncates unnamed wide enums to 64 bits for %p/%s. + unsigned_expected = $sformatf("%0d", unsigned65_bits[63:0]); +`endif + formatted = $sformatf("%p", unsigned65_value); + `checks(formatted, unsigned_expected); + formatted = $sformatf("%s", unsigned65_value); + `checks(formatted, unsigned_expected); + fmt = cyc[0] ? "%p" : "%s"; + formatted = $sformatf(fmt, unsigned65_value); + `checks(formatted, unsigned_expected); + unsigned_expected = $sformatf("%0h", unsigned65_bits); + fmt = cyc[0] ? "%0h" : "%0H"; + formatted = $sformatf(fmt, unsigned65_value); + `checks(formatted, unsigned_expected); + formatted = $sformatf("%0p", unsigned65_value); +`ifdef QUESTA + unsigned_expected = $sformatf("%0d", unsigned65_bits[63:0]); + `checks(formatted, unsigned_expected); +`else + `checks(formatted, {"'h", unsigned_expected}); +`endif + + // Signed values sharing their low 64 bits must retain distinct names. + signed95_value = cyc[0] ? SIGNED95_NEG : SIGNED95_POS; + signed_expected = cyc[0] ? ENUM95_NEG_TEXT : ENUM95_POS_TEXT; + formatted = $sformatf("%p", signed95_value); + `checks(formatted, signed_expected); + formatted = $sformatf("%s", signed95_value); + `checks(formatted, signed_expected); + formatted = signed95_value.name(); + `checks(formatted, signed_expected); + fmt = cyc[0] ? "%p" : "%s"; + formatted = $sformatf(fmt, signed95_value); + `checks(formatted, signed_expected); + signed_expected = cyc[0] ? "-19807040628566084398385987583" : "1"; + formatted = $sformatf("%0d", signed95_value); + `checks(formatted, signed_expected); + fmt = cyc[0] ? "%0d" : "%0D"; + formatted = $sformatf(fmt, signed95_value); + `checks(formatted, signed_expected); + + signed129_value = cyc[1] ? SIGNED129_NEG : (cyc[0] ? SIGNED129_HIGH : SIGNED129_LOW); + signed_expected = cyc[1] ? ENUM129_NEG_TEXT : (cyc[0] ? ENUM129_HIGH_TEXT : ENUM129_LOW_TEXT); + formatted = $sformatf("%p", signed129_value); + `checks(formatted, signed_expected); + formatted = $sformatf("%s", signed129_value); + `checks(formatted, signed_expected); + formatted = signed129_value.name(); + `checks(formatted, signed_expected); + fmt = cyc[0] ? "%p" : "%s"; + formatted = $sformatf(fmt, signed129_value); + `checks(formatted, signed_expected); + signed_expected = cyc[1] ? "-340282366920938463463374607431768211455" : + (cyc[0] ? "18446744073709551617" : "1"); + formatted = $sformatf("%0d", signed129_value); + `checks(formatted, signed_expected); + fmt = cyc[0] ? "%0d" : "%0D"; + formatted = $sformatf(fmt, signed129_value); + `checks(formatted, signed_expected); + cyc <= cyc + 1; if (cyc == 3) begin $write("*-* All Finished *-*\n"); diff --git a/test_regress/t/t_enum_format_param_bad.out b/test_regress/t/t_enum_format_param_bad.out new file mode 100644 index 000000000..d376e6fb3 --- /dev/null +++ b/test_regress/t/t_enum_format_param_bad.out @@ -0,0 +1,10 @@ +%Error: t/t_enum_format_param_bad.v:13:50: Expecting expression to be constant, but variable isn't const: 'small_value' + : ... note: In instance 't' + 13 | localparam string SMALL_TEXT = $sformatf("%p", small_value); + | ^~~~~~~~~~~ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: t/t_enum_format_param_bad.v:14:50: Expecting expression to be constant, but variable isn't const: 'large_value' + : ... note: In instance 't' + 14 | localparam string LARGE_TEXT = $sformatf("%p", large_value); + | ^~~~~~~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_enum_format_param_bad.py b/test_regress/t/t_enum_format_param_bad.py new file mode 100755 index 000000000..344a4e20a --- /dev/null +++ b/test_regress/t/t_enum_format_param_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('vlt') + +test.lint(fails=True, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_enum_format_param_bad.v b/test_regress/t/t_enum_format_param_bad.v new file mode 100644 index 000000000..0ba6c59e3 --- /dev/null +++ b/test_regress/t/t_enum_format_param_bad.v @@ -0,0 +1,17 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +module t; + typedef enum logic [6:0] {SMALL = 7'd1} small_t; + typedef enum logic signed [94:0] {LARGE = -95'sd3} large_t; + small_t small_value; + large_t large_value; + + localparam string SMALL_TEXT = $sformatf("%p", small_value); + localparam string LARGE_TEXT = $sformatf("%p", large_value); + + initial $display("%s:%s", SMALL_TEXT, LARGE_TEXT); +endmodule diff --git a/test_regress/t/t_enum_huge_methods_bad.out b/test_regress/t/t_enum_huge_methods_bad.out index e3a0d576c..a21de182d 100644 --- a/test_regress/t/t_enum_huge_methods_bad.out +++ b/test_regress/t/t_enum_huge_methods_bad.out @@ -2,8 +2,8 @@ 13 | ELARGE = 160'h1234_4567_abcd_1234_4567_abcd | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. -%Error-UNSUPPORTED: t/t_enum_huge_methods_bad.v:28:16: Unsupported: enum next/prev/name method on enum with > 64 bits - 28 | $display(e.name); - | ^ +%Error-UNSUPPORTED: t/t_enum_huge_methods_bad.v:28:12: Unsupported: enum next/prev method on enum with > 64 bits + 28 | e <= e.next(); + | ^ ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest %Error: Exiting due to diff --git a/test_regress/t/t_enum_huge_methods_bad.v b/test_regress/t/t_enum_huge_methods_bad.v index 898bc8e92..551cd54bc 100644 --- a/test_regress/t/t_enum_huge_methods_bad.v +++ b/test_regress/t/t_enum_huge_methods_bad.v @@ -25,7 +25,7 @@ module t ( e <= E01; end else if (cyc == 1) begin - $display(e.name); + e <= e.next(); e <= ELARGE; end else if (cyc == 99) begin