mirror of
https://github.com/verilator/verilator.git
synced 2026-10-02 16:13:34 +02:00
Fix wide enum names (#8344)
This commit is contained in:
+13
-8
@@ -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";
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
Executable
+19
@@ -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()
|
||||
Executable
+21
@@ -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()
|
||||
@@ -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
|
||||
Executable
+16
@@ -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()
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user