Compare commits

...

50 Commits

Author SHA1 Message Date
Yilou Wang 6c13257650
Merge d16f3988de into 574c69c092 2025-11-05 10:51:08 +00:00
github action 574c69c092 Apply 'make format' 2025-11-05 10:50:31 +00:00
Jens Yuechao Liu e2f5854088
Fix slice memory overflow on large output arrays (#6636) (#6638) 2025-11-05 05:48:22 -05:00
Yilou Wang d16f3988de add VL_RESTORER for m_clonedConstraints 2025-11-05 11:17:53 +01:00
Yilou Wang 2f3a7549cb update unsup.out 2025-11-01 08:57:53 +01:00
Yilou Wang fcc4da9d66 add an unsup test for case 'one var but in two different global constraints' 2025-11-01 07:46:06 +01:00
Yilou Wang 6ab926877a update test and clear m_constraints 2025-10-31 14:34:15 +01:00
Yilou Wang 12747ae18f reformat 2025-10-29 14:40:24 +01:00
Yilou Wang 04f7251975 rm restorer m_clonedConstraints, cause no modifications 2025-10-27 16:06:36 +01:00
Yilou Wang 8e7c4a11d4 rm cout 2025-10-27 15:52:29 +01:00
Yilou Wang 3bc1eff7ed add restorer 2025-10-27 15:06:38 +01:00
Yilou Wang 43cc217230 remove if branch 2025-10-26 20:57:32 +01:00
Yilou Wang 96857581d4 format 2025-10-26 20:04:30 +01:00
Yilou Wang 4960608221 coding style refining 2025-10-26 20:03:16 +01:00
Yilou Wang 606169fb57 add unsup warning for arraysel case 2025-10-26 12:44:43 +01:00
Yilou Wang 409a3cad0a refine 2025-10-24 10:57:21 +02:00
Yilou Wang 55c50f2984 revoke one Uassert 2025-10-23 17:13:20 +02:00
Yilou Wang a3e7fc89af final refining 2025-10-23 16:23:31 +02:00
Yilou Wang 8a85bf6c81 refine the code and improve the branch coverage 2025-10-22 22:15:06 +02:00
Yilou Wang 514a1c6050 rm IS_RANDOMIZED_INLINE_GLOBAL 2025-10-22 17:29:17 +02:00
github action a4d3ccacbf Apply 'make format' 2025-10-22 14:30:44 +00:00
Yilou Wang 3dddb90fc5 try to improve the coverage 2025-10-22 16:29:30 +02:00
Yilou Wang 3f017b4d8c Trigger CI 2025-10-22 10:23:57 +02:00
github action 6ae6b7817e Apply 'make format' 2025-10-22 08:19:15 +00:00
Yilou Wang 0b8a7171ef refine and fix 2025-10-22 10:18:02 +02:00
github action 6cd4d84c7e Apply 'make format' 2025-10-20 20:53:32 +00:00
Yilou Wang 69281ab33c var_naming and revert 2025-10-20 22:52:06 +02:00
Yilou Wang 64d48e4879
Merge branch 'master' into new_branch_for_global_rand 2025-10-19 11:59:03 +02:00
github action d5ec5a0eb0 Apply 'make format' 2025-10-19 09:57:28 +00:00
Yilou Wang 7c8d5a7468 naming adjustment and leak-error fixing 2025-10-19 11:55:59 +02:00
Yilou Wang 2679c8a235 leaked node fix 2025-10-12 15:53:28 +02:00
Yilou Wang 36a94a4fc5 fix leaked node 2025-10-11 23:24:14 +02:00
Yilou Wang 3eebebac2b update unsup output 2025-10-11 22:36:00 +02:00
github action 0adb467cea Apply 'make format' 2025-10-11 20:12:38 +00:00
Yilou Wang 87e1381ffe fix cpp style 2025-10-11 22:11:29 +02:00
github action feb8934185 Apply 'make format' 2025-10-11 15:57:28 +00:00
Yilou Wang 468d6fec0f remove debug cout messages 2025-10-11 17:52:47 +02:00
Yilou Wang c29e2601cb fix normal randomize case 2025-10-11 17:44:20 +02:00
Yilou Wang 76a60df6fd temp store 2025-10-11 17:34:48 +02:00
github action f9487374b5 Apply 'make format' 2025-10-09 18:32:39 +00:00
Yilou Wang 591d74c5d8 add tests 2025-10-09 20:31:04 +02:00
Yilou Wang b69e0569cf remove redundant debug info and modify comments 2025-10-09 16:02:59 +02:00
github action 809fba224b Apply 'make format' 2025-10-09 13:31:58 +00:00
Yilou Wang 9793737878 global rand traversal fix 2025-10-09 15:30:39 +02:00
github action 0c32d90b38 Apply 'make format' 2025-09-28 09:12:04 +00:00
Udaya Raj Subedi c5671e9e0b MInor bug fix for the global rand 2025-09-28 11:10:36 +02:00
github action 88e70eaed8 Apply 'make format' 2025-09-19 14:13:39 +00:00
Yilou Wang 77f6b85d1f 1 2025-09-19 16:11:58 +02:00
github action 358c5a7d72 Apply 'make format' 2025-09-19 09:05:12 +00:00
Yilou Wang 65142e7e7a merge and refine the code 2025-09-19 11:03:56 +02:00
22 changed files with 770 additions and 36 deletions

View File

@ -105,6 +105,7 @@ Jamey Hicks
Jamie Iles
Jan Van Winkel
Jean Berniolles
Jens Yuechao Liu
Jeremy Bennett
Jesse Taube
Jevin Sweval

View File

@ -719,6 +719,11 @@ Summary:
automatically. Variables explicitly annotated with
:option:`/*verilator&32;split_var*/` are still split.
.. option:: --fslice-element-limit
Rarely needed. Set the maximum array size (number of elements)
for slice optimization to avoid excessive memory usage.
.. option:: -future0 <option>
Rarely needed. Suppress an unknown Verilator option for an option that

View File

@ -941,6 +941,7 @@ public:
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
string name() const override VL_MT_STABLE { return m_name; } // * = Scope name
void name(const string& name) override { m_name = name; } // * = Scope name
bool isGateOptimizable() const override { return false; }
bool isPredictOptimizable() const override { return false; }
bool maybePointedTo() const override VL_MT_SAFE { return true; }
@ -1871,7 +1872,7 @@ class AstVar final : public AstNode {
bool m_ignorePostWrite : 1; // Ignore writes in 'Post' blocks during ordering
bool m_ignoreSchedWrite : 1; // Ignore writes in scheduling (for special optimizations)
bool m_dfgMultidriven : 1; // Singal is multidriven, used by DFG to avoid repeat processing
bool m_globalConstrained : 1; // Global constraint per IEEE 1800-2023 18.5.8
void init() {
m_ansi = false;
m_declTyped = false;
@ -1921,6 +1922,7 @@ class AstVar final : public AstNode {
m_ignorePostWrite = false;
m_ignoreSchedWrite = false;
m_dfgMultidriven = false;
m_globalConstrained = false;
}
public:
@ -2085,7 +2087,8 @@ public:
void setIgnoreSchedWrite() { m_ignoreSchedWrite = true; }
bool dfgMultidriven() const { return m_dfgMultidriven; }
void setDfgMultidriven() { m_dfgMultidriven = true; }
void globalConstrained(bool flag) { m_globalConstrained = flag; }
bool globalConstrained() const { return m_globalConstrained; }
// METHODS
void name(const string& name) override { m_name = name; }
void tag(const string& text) override { m_tag = text; }

View File

@ -1458,13 +1458,16 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc,
DECL_OPTION("-freloop", FOnOff, &m_fReloop);
DECL_OPTION("-freorder", FOnOff, &m_fReorder);
DECL_OPTION("-fslice", FOnOff, &m_fSlice);
DECL_OPTION("-fslice-element-limit", CbVal, [this, fl](const char* valp) {
m_fSliceElementLimit = std::atoi(valp);
if (m_fSliceElementLimit < 0) fl->v3fatal("--fslice-element-limit must be >= 0: " << valp);
});
DECL_OPTION("-fsplit", FOnOff, &m_fSplit);
DECL_OPTION("-fsubst", FOnOff, &m_fSubst);
DECL_OPTION("-fsubst-const", FOnOff, &m_fSubstConst);
DECL_OPTION("-ftable", FOnOff, &m_fTable);
DECL_OPTION("-ftaskify-all-forked", FOnOff, &m_fTaskifyAll).undocumented(); // Debug
DECL_OPTION("-fvar-split", FOnOff, &m_fVarSplit);
DECL_OPTION("-G", CbPartialMatch, [this](const char* optp) { addParameter(optp, false); });
DECL_OPTION("-gate-stmts", Set, &m_gateStmts);
DECL_OPTION("-gdb", CbCall, []() {}); // Processed only in bin/verilator shell

View File

@ -413,6 +413,7 @@ private:
bool m_fReloop; // main switch: -fno-reloop: reform loops
bool m_fReorder; // main switch: -fno-reorder: reorder assignments in blocks
bool m_fSlice = true; // main switch: -fno-slice: array assignment slicing
int m_fSliceElementLimit = 256; // main switch: --fslice-element-limit
bool m_fSplit; // main switch: -fno-split: always assignment splitting
bool m_fSubst; // main switch: -fno-subst: substitute expression temp values
bool m_fSubstConst; // main switch: -fno-subst-const: final constant substitution
@ -726,6 +727,7 @@ public:
bool fReloop() const { return m_fReloop; }
bool fReorder() const { return m_fReorder; }
bool fSlice() const { return m_fSlice; }
int fSliceElementLimit() const { return m_fSliceElementLimit; }
bool fSplit() const { return m_fSplit; }
bool fSubst() const { return m_fSubst; }
bool fSubstConst() const { return m_fSubstConst; }

View File

@ -54,10 +54,17 @@ VL_DEFINE_DEBUG_FUNCTIONS;
enum ClassRandom : uint8_t {
NONE, // randomize() is not called
IS_RANDOMIZED, // randomize() is called
IS_RANDOMIZED_GLOBAL, // randomize() is called with global constraints
IS_RANDOMIZED_INLINE, // randomize() with args is called
IS_STD_RANDOMIZED, // std::randomize() is called
};
// ######################################################################
// Constants for global constraint processing
static constexpr const char* GLOBAL_CONSTRAINT_SEPARATOR = "__DT__";
static constexpr const char* BASIC_RANDOMIZE_FUNC_NAME = "__VBasicRand";
// ######################################################################
// Establishes the target of a rand_mode() call
@ -138,6 +145,9 @@ class RandomizeMarkVisitor final : public VNVisitor {
AstNodeModule* m_modp; // Current module
AstNodeStmt* m_stmtp = nullptr; // Current statement
std::set<AstNodeVarRef*> m_staticRefs; // References to static variables under `with` clauses
AstWith* m_withp = nullptr; // Current 'with' constraint node
std::vector<AstConstraint*> m_clonedConstraints; // List of cloned global constraints
std::unordered_set<const AstVar*> m_processedVars; // Track by variable instance, not class
// METHODS
void markMembers(const AstClass* nodep) {
@ -196,18 +206,145 @@ class RandomizeMarkVisitor final : public VNVisitor {
staticRefp->classOrPackagep(VN_AS(staticRefp->varp()->user2p(), NodeModule));
}
}
void markNestedGlobalConstrainedRecurse(AstNode* nodep) {
if (const AstVarRef* const refp = VN_CAST(nodep, VarRef)) {
AstVar* const varp = refp->varp();
if (varp->globalConstrained()) return;
varp->globalConstrained(true);
} else if (const AstMemberSel* const memberSelp = VN_CAST(nodep, MemberSel)) {
if (memberSelp->varp()) {
AstVar* const varp = memberSelp->varp();
if (varp->globalConstrained()) return;
varp->globalConstrained(true);
}
markNestedGlobalConstrainedRecurse(memberSelp->fromp());
}
}
// Build MemberSel chain from variable path
AstNodeExpr* buildMemberSelChain(AstVarRef* rootVarRefp, const std::vector<AstVar*>& path) {
AstNodeExpr* exprp = rootVarRefp->cloneTree(false);
for (AstVar* memberVarp : path) {
AstMemberSel* memberSelp
= new AstMemberSel{rootVarRefp->fileline(), exprp, memberVarp};
memberSelp->user2p(m_classp);
exprp = memberSelp;
}
return exprp;
}
// Process a single constraint during nested constraint cloning
void processNestedConstraint(AstConstraint* const constrp, AstVarRef* rootVarRefp,
const std::vector<AstVar*>& newPath) {
std::string pathPrefix = rootVarRefp->name();
for (AstVar* pathMemberVarp : newPath) {
pathPrefix += GLOBAL_CONSTRAINT_SEPARATOR + pathMemberVarp->name();
}
const std::string newName = pathPrefix + GLOBAL_CONSTRAINT_SEPARATOR + constrp->name();
for (const AstConstraint* existingConstrp : m_clonedConstraints) {
if (existingConstrp->name() == newName) {
// Multiple paths lead to same constraint - unsupported pattern
std::string fullPath = rootVarRefp->name();
for (AstVar* pathVar : newPath) { fullPath += "." + pathVar->name(); }
constrp->v3warn(E_UNSUPPORTED, "Unsupported: One variable '"
<< fullPath
<< "' cannot have multiple global constraints");
return;
}
}
AstConstraint* const cloneConstrp = constrp->cloneTree(false);
cloneConstrp->name(newName);
cloneConstrp->foreach([&](AstVarRef* varRefp) {
AstNodeExpr* const chainp = buildMemberSelChain(rootVarRefp, newPath);
AstMemberSel* const finalSelp
= new AstMemberSel{varRefp->fileline(), chainp, varRefp->varp()};
finalSelp->user2p(m_classp);
varRefp->replaceWith(finalSelp);
VL_DO_DANGLING(varRefp->deleteTree(), varRefp);
});
m_clonedConstraints.push_back(cloneConstrp);
}
// Clone constraints from nested rand class members
void cloneNestedConstraintsRecurse(AstVarRef* rootVarRefp, AstClass* classp,
const std::vector<AstVar*>& pathToClass) {
for (AstNode* memberNodep = classp->membersp(); memberNodep;
memberNodep = memberNodep->nextp()) {
AstVar* const memberVarp = VN_CAST(memberNodep, Var);
if (!memberVarp) continue;
if (!memberVarp->rand().isRandomizable()) continue;
const AstClassRefDType* const memberClassRefp
= VN_CAST(memberVarp->dtypep()->skipRefp(), ClassRefDType);
if (!memberClassRefp || !memberClassRefp->classp()) continue;
AstClass* nestedClassp = memberClassRefp->classp();
std::vector<AstVar*> newPath = pathToClass;
newPath.push_back(memberVarp);
// Replace all variable references inside the cloned constraint with proper
// member selections
nestedClassp->foreachMember(
[&](AstClass* const containingClassp, AstConstraint* const constrp) {
processNestedConstraint(constrp, rootVarRefp, newPath);
});
cloneNestedConstraintsRecurse(rootVarRefp, nestedClassp, newPath);
}
}
void cloneNestedConstraints(AstVarRef* rootVarRefp, AstClass* rootClass) {
std::vector<AstVar*> emptyPath;
cloneNestedConstraintsRecurse(rootVarRefp, rootClass, emptyPath);
}
void nameManipulation(AstVarRef* fromp, AstConstraint* cloneCons) {
cloneCons->name(fromp->name() + GLOBAL_CONSTRAINT_SEPARATOR + cloneCons->name());
cloneCons->foreach([&](AstVarRef* varRefp) {
AstVarRef* const clonedFromp = fromp->cloneTree(false);
AstMemberSel* const varMemberp
= new AstMemberSel{cloneCons->fileline(), clonedFromp, varRefp->varp()};
varMemberp->user2p(m_classp);
varRefp->replaceWith(varMemberp);
VL_DO_DANGLING(varRefp->deleteTree(), varRefp);
});
}
// Process a globally constrained variable by cloning its constraints
void processGlobalConstraint(AstVarRef* varRefp, AstClass* gConsClass) {
AstVar* const objVar = varRefp->varp();
// Process per-variable (object instance), not per-class
// This allows multiple objects of the same class (e.g., obj1 and obj2 of type Sub)
if (m_processedVars.insert(objVar).second) {
// Clone constraints from the top-level class (e.g., Level1 for obj_a)
gConsClass->foreachMember([&](AstClass* const classp, AstConstraint* const constrp) {
AstConstraint* const cloneConstrp = constrp->cloneTree(false);
nameManipulation(varRefp, cloneConstrp);
m_clonedConstraints.push_back(cloneConstrp);
});
cloneNestedConstraints(varRefp, gConsClass);
}
}
// VISITORS
void visit(AstClass* nodep) override {
VL_RESTORER(m_classp);
VL_RESTORER(m_modp);
VL_RESTORER(m_clonedConstraints);
m_modp = m_classp = nodep;
iterateChildrenConst(nodep);
if (nodep->extendsp()) {
// Save pointer to derived class
// Record derived class for inheritance hierarchy tracking
const AstClass* const basep = nodep->extendsp()->classp();
m_baseToDerivedMap[basep].insert(nodep);
}
for (AstConstraint* const constrp : m_clonedConstraints) m_classp->addStmtsp(constrp);
m_clonedConstraints.clear();
}
void visit(AstNodeStmt* nodep) override {
VL_RESTORER(m_stmtp);
@ -464,7 +601,51 @@ class RandomizeMarkVisitor final : public VNVisitor {
// of type AstLambdaArgRef. They are randomized too.
const bool randObject = nodep->fromp()->user1() || VN_IS(nodep->fromp(), LambdaArgRef);
nodep->user1(randObject && nodep->varp()->rand().isRandomizable());
nodep->user2p(m_modp);
if (m_withp) {
AstNode* backp = m_withp;
while (backp->backp()) {
if (const AstMethodCall* const callp = VN_CAST(backp, MethodCall)) {
AstClassRefDType* classdtype
= VN_AS(callp->fromp()->dtypep()->skipRefp(), ClassRefDType);
nodep->user2p(classdtype->classp());
break;
}
backp = backp->backp();
}
} else {
nodep->user2p(m_modp);
}
if (randObject && nodep->varp()
&& nodep->varp()->rand().isRandomizable()) { // Process global constraints
if (m_classp && m_classp->user1() == IS_RANDOMIZED) {
m_classp->user1(IS_RANDOMIZED_GLOBAL);
}
// Mark the entire nested chain as participating in global constraints
if (VN_IS(nodep->fromp(), VarRef) || VN_IS(nodep->fromp(), MemberSel)) {
markNestedGlobalConstrainedRecurse(nodep->fromp());
} else if (VN_IS(nodep->fromp(), ArraySel)) {
nodep->v3warn(E_UNSUPPORTED, "Unsupported: " << nodep->prettyTypeName()
<< " within a global constraint");
}
// Global constraint processing algorithm:
// 1. Detect globally constrained object variables in randomized classes
// 2. Clone constraint trees from the constrained object's class
// 3. Rename cloned constraints with object prefix (obj.var format)
// 4. Insert cloned constraints into current class for solver processing
// 5. Use basic randomization for non-constrained variables to avoid recursion
// Extract and validate components early to avoid repeated type checks
AstVarRef* const varRefp = VN_CAST(nodep->fromp(), VarRef);
if (!varRefp) return;
const AstClassRefDType* const classRefp
= VN_AS(varRefp->dtypep()->skipRefp(), ClassRefDType);
if (nodep->user1() && varRefp->varp()->globalConstrained()) {
processGlobalConstraint(varRefp, classRefp->classp());
}
}
}
void visit(AstNodeModule* nodep) override {
VL_RESTORER(m_modp);
@ -479,6 +660,11 @@ class RandomizeMarkVisitor final : public VNVisitor {
nodep->user2p(m_modp);
iterateChildrenConst(nodep);
}
void visit(AstWith* nodep) override {
VL_RESTORER(m_withp);
m_withp = nodep;
iterateChildrenConst(nodep);
}
void visit(AstNodeExpr* nodep) override {
iterateChildrenConst(nodep);
@ -519,6 +705,20 @@ class ConstraintExprVisitor final : public VNVisitor {
bool m_structSel = false; // Marks when inside structSel
// (used to format "%@.%@" for struct arrays)
// Build full path for a MemberSel chain (e.g., "obj.l2.l3.l4")
std::string buildMemberPath(const AstMemberSel* const memberSelp) {
const AstNode* fromp = memberSelp->fromp();
if (const AstVarRef* const refp = VN_CAST(fromp, VarRef)) {
// Base case: reached root VarRef
return refp->name() + "." + memberSelp->name();
} else if (const AstMemberSel* const selp = VN_CAST(fromp, MemberSel)) {
// Recursive case: build path from outer levels
return buildMemberPath(selp) + "." + memberSelp->name();
}
memberSelp->v3fatalSrc("Unexpected node type in MemberSel chain");
return "";
}
AstSFormatF* getConstFormat(AstNodeExpr* nodep) {
return new AstSFormatF{nodep->fileline(), (nodep->width() & 3) ? "#b%b" : "#x%x", false,
nodep};
@ -641,19 +841,30 @@ class ConstraintExprVisitor final : public VNVisitor {
CONSTRAINTIGN,
"Size constraint combined with element constraint may not work correctly");
}
AstMemberSel* membersel = VN_IS(nodep->backp(), MemberSel)
? VN_AS(nodep->backp(), MemberSel)->cloneTree(false)
: nullptr;
// Check if this variable is marked as globally constrained
const bool isGlobalConstrained = nodep->varp()->globalConstrained();
AstMemberSel* membersel = nullptr;
std::string smtName;
if (isGlobalConstrained && VN_IS(nodep->backp(), MemberSel)) {
// For global constraints: build complete path from topmost MemberSel
AstNode* topMemberSel = nodep->backp();
while (VN_IS(topMemberSel->backp(), MemberSel)) {
topMemberSel = topMemberSel->backp();
}
membersel = VN_AS(topMemberSel, MemberSel)->cloneTree(false);
smtName = buildMemberPath(membersel);
} else {
// No MemberSel: just variable name
smtName = nodep->name();
}
if (membersel) varp = membersel->varp();
AstNodeModule* const classOrPackagep = nodep->classOrPackagep();
const RandomizeMode randMode = {.asInt = varp->user1()};
if (!randMode.usesMode && editFormat(nodep)) return;
// In SMT just variable name, but we also ensure write_var for the variable
const std::string smtName = membersel
? membersel->fromp()->name() + "." + membersel->name()
: nodep->name(); // Can be anything unique
VNRelinker relinker;
nodep->unlinkFrBack(&relinker);
AstNodeExpr* exprp = new AstSFormatF{nodep->fileline(), smtName, false, nullptr};
@ -666,12 +877,18 @@ class ConstraintExprVisitor final : public VNVisitor {
VCMethod::ARRAY_AT, new AstConst{nodep->fileline(), randMode.index}};
atp->dtypeSetUInt32();
exprp = new AstCond{varp->fileline(), atp, exprp, constFormatp};
} else {
} else if (!membersel || !isGlobalConstrained) {
// Only delete nodep here if it's not a global constraint
// Global constraints need nodep for write_var processing
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
relinker.relink(exprp);
if (!varp->user3()) {
// For global constraints: always call write_var with full path even if varp->user3() is
// set For normal constraints: only call write_var if varp->user3() is not set
if (!varp->user3() || (membersel && nodep->varp()->globalConstrained())) {
// For global constraints, delete nodep here after processing
if (membersel && isGlobalConstrained) VL_DO_DANGLING(pushDeletep(nodep), nodep);
AstCMethodHard* const methodp = new AstCMethodHard{
varp->fileline(),
new AstVarRef{varp->fileline(), VN_AS(m_genp->user2p(), NodeModule), m_genp,
@ -693,10 +910,14 @@ class ConstraintExprVisitor final : public VNVisitor {
methodp->dtypeSetVoid();
AstClass* const classp
= membersel ? VN_AS(membersel->user2p(), Class) : VN_AS(varp->user2p(), Class);
AstVarRef* const varRefp
= new AstVarRef{varp->fileline(), classp, varp, VAccess::WRITE};
varRefp->classOrPackagep(classOrPackagep);
membersel ? methodp->addPinsp(membersel) : methodp->addPinsp(varRefp);
if (membersel) {
methodp->addPinsp(membersel);
} else {
AstVarRef* const varRefp
= new AstVarRef{varp->fileline(), classp, varp, VAccess::WRITE};
varRefp->classOrPackagep(classOrPackagep);
methodp->addPinsp(varRefp);
}
AstNodeDType* tmpDtypep = varp->dtypep();
while (VN_IS(tmpDtypep, UnpackArrayDType) || VN_IS(tmpDtypep, DynArrayDType)
|| VN_IS(tmpDtypep, QueueDType) || VN_IS(tmpDtypep, AssocArrayDType))
@ -911,8 +1132,21 @@ class ConstraintExprVisitor final : public VNVisitor {
editSMT(nodep, nodep->fromp(), indexp);
}
void visit(AstMemberSel* nodep) override {
if (nodep->user1()) {
nodep->v3warn(CONSTRAINTIGN, "Global constraints ignored (unsupported)");
if (nodep->varp()->rand().isRandomizable() && nodep->fromp()) {
AstNode* rootNode = nodep->fromp();
while (const AstMemberSel* const selp = VN_CAST(rootNode, MemberSel))
rootNode = selp->fromp();
// Check if the root variable participates in global constraints
if (const AstVarRef* const varRefp = VN_CAST(rootNode, VarRef)) {
AstVar* const constrainedVar = varRefp->varp();
if (constrainedVar->globalConstrained()) {
// Global constraint - unwrap the MemberSel
iterateChildren(nodep);
nodep->replaceWith(nodep->fromp()->unlinkFrBack());
VL_DO_DANGLING(nodep->deleteTree(), nodep);
return;
}
}
}
// Handle MemberSel references created by captureRefByThis()
if (VN_IS(nodep->fromp(), VarRef)
@ -1943,10 +2177,19 @@ class RandomizeVisitor final : public VNVisitor {
return;
}
AstFunc* const memberFuncp
= V3Randomize::newRandomizeFunc(m_memberMap, classRefp->classp());
= memberVarp->globalConstrained()
? V3Randomize::newRandomizeFunc(m_memberMap, classRefp->classp(),
BASIC_RANDOMIZE_FUNC_NAME)
: V3Randomize::newRandomizeFunc(m_memberMap, classRefp->classp());
AstMethodCall* const callp
= new AstMethodCall{fl, new AstVarRef{fl, classp, memberVarp, VAccess::WRITE},
"randomize", nullptr};
= memberVarp->globalConstrained()
? new AstMethodCall{fl,
new AstVarRef{fl, classp, memberVarp,
VAccess::WRITE},
BASIC_RANDOMIZE_FUNC_NAME, nullptr}
: new AstMethodCall{
fl, new AstVarRef{fl, classp, memberVarp, VAccess::WRITE},
"randomize", nullptr};
callp->taskp(memberFuncp);
callp->dtypeFrom(memberFuncp);
AstVarRef* const basicFvarRefReadp = basicFvarRefp->cloneTree(false);
@ -2123,6 +2366,8 @@ class RandomizeVisitor final : public VNVisitor {
if (!nodep->user1()) return; // Doesn't need randomize, or already processed
UINFO(9, "Define randomize() for " << nodep);
nodep->baseMostClassp()->needRNG(true);
const bool globalConstrained = nodep->user1() == IS_RANDOMIZED_GLOBAL;
AstFunc* const randomizep = V3Randomize::newRandomizeFunc(m_memberMap, nodep);
AstVar* const fvarp = VN_AS(randomizep->fvarp(), Var);
addPrePostCall(nodep, randomizep, "pre_randomize");
@ -2184,7 +2429,18 @@ class RandomizeVisitor final : public VNVisitor {
}
AstVarRef* const fvarRefp = new AstVarRef{fl, fvarp, VAccess::WRITE};
randomizep->addStmtsp(new AstAssign{fl, fvarRefp, beginValp});
// For global constraints: call basic randomize first (without global constraints)
if (globalConstrained) {
AstFunc* const basicRandomizep
= V3Randomize::newRandomizeFunc(m_memberMap, nodep, BASIC_RANDOMIZE_FUNC_NAME);
addBasicRandomizeBody(basicRandomizep, nodep, randModeVarp);
AstFuncRef* const basicRandomizeCallp = new AstFuncRef{fl, basicRandomizep, nullptr};
randomizep->addStmtsp(new AstAssign{fl, fvarRefp, basicRandomizeCallp});
} else {
// For normal classes: use beginValp (standard flow)
randomizep->addStmtsp(new AstAssign{fl, fvarRefp, beginValp});
}
if (AstTask* const resizeAllTaskp
= VN_AS(m_memberMap.findMember(nodep, "__Vresize_constrained_arrays"), Task)) {
@ -2192,15 +2448,23 @@ class RandomizeVisitor final : public VNVisitor {
randomizep->addStmtsp(resizeTaskRefp->makeStmt());
}
AstFunc* const basicRandomizep
= V3Randomize::newRandomizeFunc(m_memberMap, nodep, "__Vbasic_randomize");
addBasicRandomizeBody(basicRandomizep, nodep, randModeVarp);
AstFuncRef* const basicRandomizeCallp = new AstFuncRef{fl, basicRandomizep, nullptr};
AstVarRef* const fvarRefReadp = fvarRefp->cloneTree(false);
fvarRefReadp->access(VAccess::READ);
randomizep->addStmtsp(new AstAssign{fl, fvarRefp->cloneTree(false),
new AstAnd{fl, fvarRefReadp, basicRandomizeCallp}});
// For global constraints: combine with solver result (beginValp)
// For normal classes: call basic randomize after resize
if (globalConstrained) {
randomizep->addStmtsp(new AstAssign{fl, fvarRefp->cloneTree(false),
new AstAnd{fl, fvarRefReadp, beginValp}});
} else {
AstFunc* const basicRandomizep
= V3Randomize::newRandomizeFunc(m_memberMap, nodep, BASIC_RANDOMIZE_FUNC_NAME);
addBasicRandomizeBody(basicRandomizep, nodep, randModeVarp);
AstFuncRef* const basicRandomizeCallp = new AstFuncRef{fl, basicRandomizep, nullptr};
randomizep->addStmtsp(
new AstAssign{fl, fvarRefp->cloneTree(false),
new AstAnd{fl, fvarRefReadp, basicRandomizeCallp}});
}
addPrePostCall(nodep, randomizep, "post_randomize");
nodep->user1(false);
}
@ -2417,7 +2681,7 @@ class RandomizeVisitor final : public VNVisitor {
randomizeFuncp->addStmtsp(localGenp);
AstFunc* const basicRandomizeFuncp
= V3Randomize::newRandomizeFunc(m_memberMap, classp, "__Vbasic_randomize");
= V3Randomize::newRandomizeFunc(m_memberMap, classp, BASIC_RANDOMIZE_FUNC_NAME);
AstFuncRef* const basicRandomizeFuncCallp
= new AstFuncRef{nodep->fileline(), basicRandomizeFuncp, nullptr};

View File

@ -58,6 +58,7 @@ class SliceVisitor final : public VNVisitor {
// STATE - across all visitors
VDouble0 m_statAssigns; // Statistic tracking
VDouble0 m_statSliceElementSkips; // Statistic tracking
// STATE - for current visit position (use VL_RESTORER)
AstNode* m_assignp = nullptr; // Assignment we are under
@ -248,6 +249,14 @@ class SliceVisitor final : public VNVisitor {
return false;
}
// Skip optimization if array is too large
const int elements = arrayp->rangep()->elementsConst();
const int elementLimit = v3Global.opt.fSliceElementLimit();
if (elements > elementLimit && elementLimit > 0) {
++m_statSliceElementSkips;
return false;
}
UINFO(4, "Slice optimizing " << nodep);
++m_statAssigns;
@ -256,7 +265,6 @@ class SliceVisitor final : public VNVisitor {
// Assign of an ascending range slice to a descending range one must reverse
// the elements
AstNodeAssign* newlistp = nullptr;
const int elements = arrayp->rangep()->elementsConst();
for (int elemIdx = 0; elemIdx < elements; ++elemIdx) {
// Original node is replaced, so it is safe to copy it one time even if it is impure.
AstNodeAssign* const newp
@ -383,6 +391,8 @@ public:
explicit SliceVisitor(AstNetlist* nodep) { iterate(nodep); }
~SliceVisitor() override {
V3Stats::addStat("Optimizations, Slice array assignments", m_statAssigns);
V3Stats::addStat("Optimizations, Slice array skips due to size limit",
m_statSliceElementSkips);
}
};

View File

@ -0,0 +1,26 @@
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:41:20: Unsupported: MEMBERSEL 'm_x' within a global constraint
: ... note: In instance 't_constraint_global_arr_unsup'
41 | m_mid.m_arr[0].m_x == 200;
| ^~~
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:42:20: Unsupported: MEMBERSEL 'm_y' within a global constraint
: ... note: In instance 't_constraint_global_arr_unsup'
42 | m_mid.m_arr[0].m_y == 201;
| ^~~
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:47:18: Unsupported: MEMBERSEL 'm_obj' within a global constraint
: ... note: In instance 't_constraint_global_arr_unsup'
47 | m_mid_arr[0].m_obj.m_x == 300;
| ^~~~~
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:48:18: Unsupported: MEMBERSEL 'm_obj' within a global constraint
: ... note: In instance 't_constraint_global_arr_unsup'
48 | m_mid_arr[0].m_obj.m_y == 301;
| ^~~~~
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:53:18: Unsupported: MEMBERSEL 'm_arr' within a global constraint
: ... note: In instance 't_constraint_global_arr_unsup'
53 | m_mid_arr[1].m_arr[2].m_y == 400;
| ^~~~~
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:53:27: Unsupported: MEMBERSEL 'm_y' within a global constraint
: ... note: In instance 't_constraint_global_arr_unsup'
53 | m_mid_arr[1].m_arr[2].m_y == 400;
| ^~~
%Error: Exiting due to

View File

@ -0,0 +1,16 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2025 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
test.lint(fails=test.vlt_all, expect_filename=test.golden_filename)
test.passes()

View File

@ -0,0 +1,86 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by PlanV GmbH.
// SPDX-License-Identifier: CC0-1.0
/* verilator lint_off WIDTHTRUNC */
class Inner;
rand int m_x;
rand int m_y;
endclass
class Middle;
rand Inner m_obj;
rand Inner m_arr[3];
endclass
class Outer;
rand Middle m_mid;
rand Middle m_mid_arr[2];
function new();
m_mid = new;
m_mid.m_obj = new;
foreach (m_mid.m_arr[i]) m_mid.m_arr[i] = new;
foreach (m_mid_arr[i]) begin
m_mid_arr[i] = new;
m_mid_arr[i].m_obj = new;
foreach (m_mid_arr[i].m_arr[j]) m_mid_arr[i].m_arr[j] = new;
end
endfunction
// Case 1: Simple nested member access (should work)
constraint c_simple {
m_mid.m_obj.m_x == 100;
m_mid.m_obj.m_y == 101;
}
// Case 2: Array indexing in the path (may not work)
constraint c_array_index {
m_mid.m_arr[0].m_x == 200;
m_mid.m_arr[0].m_y == 201;
}
// Case 3: Nested array indexing
constraint c_nested_array {
m_mid_arr[0].m_obj.m_x == 300;
m_mid_arr[0].m_obj.m_y == 301;
}
// Case 4: Multiple array indices
constraint c_multi_array {
m_mid_arr[1].m_arr[2].m_y == 400;
}
endclass
module t_constraint_global_arr_unsup;
initial begin
Outer o = new;
if (o.randomize()) begin
$display("Case 1 - Simple: mid.obj.x = %0d (expected 100)", o.m_mid.m_obj.m_x);
$display("Case 1 - Simple: mid.obj.y = %0d (expected 101)", o.m_mid.m_obj.m_y);
$display("Case 2 - Array[0]: mid.arr[0].x = %0d (expected 200)", o.m_mid.m_arr[0].m_x);
$display("Case 2 - Array[0]: mid.arr[0].y = %0d (expected 201)", o.m_mid.m_arr[0].m_y);
$display("Case 3 - Nested[0]: mid_arr[0].obj.x = %0d (expected 300)", o.m_mid_arr[0].m_obj.m_x);
$display("Case 3 - Nested[0]: mid_arr[0].obj.y = %0d (expected 301)", o.m_mid_arr[0].m_obj.m_y);
$display("Case 4 - Multi[1][2]: mid_arr[1].arr[2].y = %0d (expected 400)", o.m_mid_arr[1].m_arr[2].m_y);
// Check results
if (o.m_mid.m_obj.m_x == 100 && o.m_mid.m_obj.m_y == 101 &&
o.m_mid.m_arr[0].m_x == 200 && o.m_mid.m_arr[0].m_y == 201 &&
o.m_mid_arr[0].m_obj.m_x == 300 && o.m_mid_arr[0].m_obj.m_y == 301 &&
o.m_mid_arr[1].m_arr[2].m_y == 400) begin
$display("*-* All Finished *-*");
$finish;
end else begin
$display("*-* FAILED *-*");
$stop;
end
end else begin
$display("*-* FAILED: randomize() returned 0 *-*");
$stop;
end
end
endmodule
/* verilator lint_off WIDTHTRUNC */

View File

@ -0,0 +1,6 @@
%Error-UNSUPPORTED: t/t_constraint_global_nested_unsup.v:9:14: Unsupported: One variable 'm_mid.m_inner' cannot have multiple global constraints
: ... note: In instance 't'
9 | constraint c_inner { m_val inside {[1:10]}; }
| ^~~~~~~
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error: Exiting due to

View File

@ -0,0 +1,16 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2024 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
test.lint(fails=test.vlt_all, expect_filename=test.golden_filename)
test.passes()

View File

@ -0,0 +1,51 @@
// DESCRIPTION: Verilator: Test for unsupported multiple global constraints
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by PlanV GmbH.
// SPDX-License-Identifier: CC0-1.0
class Inner;
rand int m_val;
constraint c_inner { m_val inside {[1:10]}; }
function new(); m_val = 0; endfunction
endclass
class Mid;
rand Inner m_inner;
rand int m_x;
// Mid has global constraint on m_inner.m_val
constraint c_mid_global {
m_x > m_inner.m_val;
m_x inside {[5:15]};
}
function new();
m_inner = new();
m_x = 0;
endfunction
endclass
class Top;
rand Mid m_mid;
rand int m_y;
// Top also has global constraint on m_mid.m_inner.m_val
constraint c_top_global {
m_y < m_mid.m_inner.m_val;
m_y inside {[1:5]};
}
function new();
m_mid = new();
m_y = 0;
endfunction
endclass
module t;
Top top;
/* verilator lint_off WIDTHTRUNC */
initial begin
top = new();
if (!top.randomize()) $stop;
$write("*-* All Finished *-*\n");
$finish;
end
/* verilator lint_off WIDTHTRUNC */
endmodule

View File

@ -0,0 +1,21 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2025 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('simulator')
if not test.have_solver:
test.skip("No constraint solver installed")
test.compile()
test.execute()
test.passes()

View File

@ -0,0 +1,124 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by PlanV GmbH.
// SPDX-License-Identifier: CC0-1.0
class Inner;
rand int m_val;
constraint c_local { m_val inside {[1:5]}; }
class NestedInner;
rand int nested_val;
constraint c_nested { nested_val inside {[1:3]}; }
endclass
rand NestedInner nested_obj;
function new();
m_val = 0;
nested_obj = new();
endfunction
endclass
class Mid;
int m_limit;
rand int m_x;
rand Inner m_inner;
constraint c_mid { m_x == m_limit; }
function new(int lim);
m_limit = lim;
m_inner = new();
endfunction
endclass
class Top;
rand Mid m_m1;
rand Mid m_m2;
rand int m_y;
constraint c_global {
m_m1.m_inner.m_val < m_m2.m_inner.m_val;
m_y > m_m1.m_x;
m_y < m_m2.m_x;
m_m1.m_inner.m_val + m_m2.m_inner.m_val < 8;
// Global constraint on nested class variable (3-level deep)
m_m1.m_inner.nested_obj.nested_val == 1;
m_m2.m_inner.nested_obj.nested_val == 3;
}
function new();
m_m1 = new(3);
m_m2 = new(5);
m_y = 0;
endfunction
endclass
// Second independent class with global constraints
class AnotherTop;
rand Mid m_m3;
rand int m_z;
constraint c_another {
m_z < m_m3.m_x;
}
function new();
m_m3 = new(10);
m_z = 0;
endfunction
endclass
module t_constraint_global_random;
int success;
Top t;
AnotherTop t2;
initial begin
t = new();
// Test 1: Regular randomize() with global constraints
success = t.randomize();
if (success != 1) $stop;
if (t.m_m1.m_x != 3 || t.m_m2.m_x != 5) $stop;
if (t.m_m1.m_inner.m_val >= t.m_m2.m_inner.m_val) $stop;
if (t.m_y <= t.m_m1.m_x || t.m_y >= t.m_m2.m_x) $stop;
if (t.m_m1.m_inner.m_val + t.m_m2.m_inner.m_val >= 8) $stop;
if (t.m_m1.m_inner.m_val < 1 || t.m_m1.m_inner.m_val > 5 ||
t.m_m2.m_inner.m_val < 1 || t.m_m2.m_inner.m_val > 5) $stop;
// Verify nested class global constraints (3-level deep: Top -> Mid -> Inner -> NestedInner)
if (t.m_m1.m_inner.nested_obj.nested_val != 1) $stop;
if (t.m_m2.m_inner.nested_obj.nested_val != 3) $stop;
// Test 2: randomize() with inline constraint on global-constrained members
success = 0;
success = t.randomize() with {
m_m1.m_inner.m_val == 2;
m_m2.m_inner.m_val == 5;
};
if (success != 1) $stop;
// Verify inline constraints
if (t.m_m1.m_inner.m_val != 2) $stop;
if (t.m_m2.m_inner.m_val != 5) $stop;
// Verify global constraints still hold
if (t.m_m1.m_x != 3 || t.m_m2.m_x != 5) $stop;
if (t.m_m1.m_inner.m_val >= t.m_m2.m_inner.m_val) $stop;
if (t.m_y <= t.m_m1.m_x || t.m_y >= t.m_m2.m_x) $stop;
if (t.m_m1.m_inner.m_val + t.m_m2.m_inner.m_val >= 8) $stop;
// Test 3: Second independent class (tests m_clonedConstraints.clear() bug)
t2 = new();
success = t2.randomize();
if (success != 1) $stop;
if (t2.m_z >= t2.m_m3.m_x) $stop;
if (t2.m_m3.m_x != 10) $stop;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,20 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2024 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('simulator')
test.top_filename = "t/t_opt_slice_element_limit.v"
test.compile(verilator_flags2=['--stats', '--fslice-element-limit', '10'])
test.file_grep(test.stats, r'Optimizations, Slice array skips due to size limit\s+(\d+)', 4)
test.passes()

View File

@ -0,0 +1,20 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2024 by Wilson Snyder.
// SPDX-License-Identifier: CC0-1.0
module t (
input logic [7:0] i1 [8],
input logic [7:0] i2 [16],
input logic [7:0] i3 [512],
output logic [7:0] o1 [8],
output logic [7:0] o2 [16],
output logic [7:0] o3 [256]
);
initial begin
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,20 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2024 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('simulator')
test.top_filename = "t/t_opt_slice_element_limit.v"
test.compile(verilator_flags2=['--stats', '--fslice-element-limit', '0'])
test.file_grep(test.stats, r'Optimizations, Slice array skips due to size limit\s+(\d+)', 0)
test.passes()

View File

@ -0,0 +1,2 @@
%Error: --fslice-element-limit must be >= 0: -100
... See the manual at https://verilator.org/verilator_doc.html?v=5.043 for more assistance.

View File

@ -0,0 +1,21 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2024 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('linter')
test.top_filename = "t/t_opt_slice_element_limit.v"
test.golden_filename = "t/t_opt_slice_element_limit_bad.out"
test.lint(fails=True,
verilator_flags2=['--stats', '--fslice-element-limit', '-100'],
except_filename=test.golden_filename)
test.passes()

View File

@ -0,0 +1,20 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2024 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('simulator')
test.top_filename = "t/t_opt_slice_element_limit.v"
test.compile(verilator_flags2=['--stats'])
test.file_grep(test.stats, r'Optimizations, Slice array skips due to size limit\s+(\d+)', 1)
test.passes()

View File

@ -7,9 +7,6 @@
: ... note: In instance 't'
27 | q.size < 5;
| ^~~~
%Warning-CONSTRAINTIGN: t/t_randomize_method_types_unsup.v:31:10: Global constraints ignored (unsupported)
31 | foo.x < y;
| ^
%Error-UNSUPPORTED: t/t_randomize_method_types_unsup.v:15:13: Unsupported: random member variable with the type of the containing class
: ... note: In instance 't'
15 | rand Cls cls;