Fix wide enum names (#8344)

This commit is contained in:
Marco Bartoli
2026-09-14 16:29:42 -04:00
committed by GitHub
parent 377865e2a0
commit c65f312d6e
23 changed files with 827 additions and 156 deletions
+13 -8
View File
@@ -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<void>(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<std::string*>(enump);
} else if (fmt == 'p' && widthSet && width == 0) {
output += "'h";
+1 -1
View File
@@ -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
+2
View File
@@ -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<bool>(namep()));
return nullptr;
}
VFormatAttr formatAttr() const { return m_formatAttr; }
+5
View File
@@ -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());
+9 -2
View File
@@ -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
+5
View File
@@ -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<AstNodeDType*>(nodep));
}
void visit(AstEnumItemRef* nodep) override {
iterateChildren(nodep);
checkAll(nodep);
+13 -1
View File
@@ -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<const EData*>(");
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();
}
+35
View File
@@ -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<unsigned char>(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);
+5
View File
@@ -30,6 +30,8 @@
#include <limits>
#include <vector>
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(); }
+22 -1
View File
@@ -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);
+5 -2
View File
@@ -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;
+105 -46
View File
@@ -221,6 +221,7 @@ class WidthVisitor final : public VNVisitor {
using DTypeMap = std::map<const std::string, AstPatMember*>;
// 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<uint64_t, AstNodeExpr*> 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<uint32_t>(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) {
+9
View File
@@ -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;
+1
View File
@@ -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
+197 -88
View File
@@ -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");
+19
View File
@@ -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()
+21
View File
@@ -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()
+313 -3
View File
@@ -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");
@@ -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
+16
View File
@@ -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()
+17
View File
@@ -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
+3 -3
View File
@@ -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
+1 -1
View File
@@ -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