parent
574c69c092
commit
0853aa7515
|
|
@ -941,6 +941,7 @@ public:
|
||||||
void dump(std::ostream& str) const override;
|
void dump(std::ostream& str) const override;
|
||||||
void dumpJson(std::ostream& str) const override;
|
void dumpJson(std::ostream& str) const override;
|
||||||
string name() const override VL_MT_STABLE { return m_name; } // * = Scope name
|
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 isGateOptimizable() const override { return false; }
|
||||||
bool isPredictOptimizable() const override { return false; }
|
bool isPredictOptimizable() const override { return false; }
|
||||||
bool maybePointedTo() const override VL_MT_SAFE { return true; }
|
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_ignorePostWrite : 1; // Ignore writes in 'Post' blocks during ordering
|
||||||
bool m_ignoreSchedWrite : 1; // Ignore writes in scheduling (for special optimizations)
|
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_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() {
|
void init() {
|
||||||
m_ansi = false;
|
m_ansi = false;
|
||||||
m_declTyped = false;
|
m_declTyped = false;
|
||||||
|
|
@ -1921,6 +1922,7 @@ class AstVar final : public AstNode {
|
||||||
m_ignorePostWrite = false;
|
m_ignorePostWrite = false;
|
||||||
m_ignoreSchedWrite = false;
|
m_ignoreSchedWrite = false;
|
||||||
m_dfgMultidriven = false;
|
m_dfgMultidriven = false;
|
||||||
|
m_globalConstrained = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
@ -2085,7 +2087,8 @@ public:
|
||||||
void setIgnoreSchedWrite() { m_ignoreSchedWrite = true; }
|
void setIgnoreSchedWrite() { m_ignoreSchedWrite = true; }
|
||||||
bool dfgMultidriven() const { return m_dfgMultidriven; }
|
bool dfgMultidriven() const { return m_dfgMultidriven; }
|
||||||
void setDfgMultidriven() { m_dfgMultidriven = true; }
|
void setDfgMultidriven() { m_dfgMultidriven = true; }
|
||||||
|
void globalConstrained(bool flag) { m_globalConstrained = flag; }
|
||||||
|
bool globalConstrained() const { return m_globalConstrained; }
|
||||||
// METHODS
|
// METHODS
|
||||||
void name(const string& name) override { m_name = name; }
|
void name(const string& name) override { m_name = name; }
|
||||||
void tag(const string& text) override { m_tag = text; }
|
void tag(const string& text) override { m_tag = text; }
|
||||||
|
|
|
||||||
|
|
@ -54,10 +54,17 @@ VL_DEFINE_DEBUG_FUNCTIONS;
|
||||||
enum ClassRandom : uint8_t {
|
enum ClassRandom : uint8_t {
|
||||||
NONE, // randomize() is not called
|
NONE, // randomize() is not called
|
||||||
IS_RANDOMIZED, // randomize() is called
|
IS_RANDOMIZED, // randomize() is called
|
||||||
|
IS_RANDOMIZED_GLOBAL, // randomize() is called with global constraints
|
||||||
IS_RANDOMIZED_INLINE, // randomize() with args is called
|
IS_RANDOMIZED_INLINE, // randomize() with args is called
|
||||||
IS_STD_RANDOMIZED, // std::randomize() 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
|
// Establishes the target of a rand_mode() call
|
||||||
|
|
||||||
|
|
@ -138,6 +145,9 @@ class RandomizeMarkVisitor final : public VNVisitor {
|
||||||
AstNodeModule* m_modp; // Current module
|
AstNodeModule* m_modp; // Current module
|
||||||
AstNodeStmt* m_stmtp = nullptr; // Current statement
|
AstNodeStmt* m_stmtp = nullptr; // Current statement
|
||||||
std::set<AstNodeVarRef*> m_staticRefs; // References to static variables under `with` clauses
|
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
|
// METHODS
|
||||||
void markMembers(const AstClass* nodep) {
|
void markMembers(const AstClass* nodep) {
|
||||||
|
|
@ -196,18 +206,145 @@ class RandomizeMarkVisitor final : public VNVisitor {
|
||||||
staticRefp->classOrPackagep(VN_AS(staticRefp->varp()->user2p(), NodeModule));
|
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
|
// VISITORS
|
||||||
void visit(AstClass* nodep) override {
|
void visit(AstClass* nodep) override {
|
||||||
VL_RESTORER(m_classp);
|
VL_RESTORER(m_classp);
|
||||||
VL_RESTORER(m_modp);
|
VL_RESTORER(m_modp);
|
||||||
|
VL_RESTORER(m_clonedConstraints);
|
||||||
m_modp = m_classp = nodep;
|
m_modp = m_classp = nodep;
|
||||||
iterateChildrenConst(nodep);
|
iterateChildrenConst(nodep);
|
||||||
if (nodep->extendsp()) {
|
if (nodep->extendsp()) {
|
||||||
// Save pointer to derived class
|
// Record derived class for inheritance hierarchy tracking
|
||||||
const AstClass* const basep = nodep->extendsp()->classp();
|
const AstClass* const basep = nodep->extendsp()->classp();
|
||||||
m_baseToDerivedMap[basep].insert(nodep);
|
m_baseToDerivedMap[basep].insert(nodep);
|
||||||
}
|
}
|
||||||
|
for (AstConstraint* const constrp : m_clonedConstraints) m_classp->addStmtsp(constrp);
|
||||||
|
m_clonedConstraints.clear();
|
||||||
}
|
}
|
||||||
void visit(AstNodeStmt* nodep) override {
|
void visit(AstNodeStmt* nodep) override {
|
||||||
VL_RESTORER(m_stmtp);
|
VL_RESTORER(m_stmtp);
|
||||||
|
|
@ -464,7 +601,51 @@ class RandomizeMarkVisitor final : public VNVisitor {
|
||||||
// of type AstLambdaArgRef. They are randomized too.
|
// of type AstLambdaArgRef. They are randomized too.
|
||||||
const bool randObject = nodep->fromp()->user1() || VN_IS(nodep->fromp(), LambdaArgRef);
|
const bool randObject = nodep->fromp()->user1() || VN_IS(nodep->fromp(), LambdaArgRef);
|
||||||
nodep->user1(randObject && nodep->varp()->rand().isRandomizable());
|
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 {
|
void visit(AstNodeModule* nodep) override {
|
||||||
VL_RESTORER(m_modp);
|
VL_RESTORER(m_modp);
|
||||||
|
|
@ -479,6 +660,11 @@ class RandomizeMarkVisitor final : public VNVisitor {
|
||||||
nodep->user2p(m_modp);
|
nodep->user2p(m_modp);
|
||||||
iterateChildrenConst(nodep);
|
iterateChildrenConst(nodep);
|
||||||
}
|
}
|
||||||
|
void visit(AstWith* nodep) override {
|
||||||
|
VL_RESTORER(m_withp);
|
||||||
|
m_withp = nodep;
|
||||||
|
iterateChildrenConst(nodep);
|
||||||
|
}
|
||||||
|
|
||||||
void visit(AstNodeExpr* nodep) override {
|
void visit(AstNodeExpr* nodep) override {
|
||||||
iterateChildrenConst(nodep);
|
iterateChildrenConst(nodep);
|
||||||
|
|
@ -519,6 +705,20 @@ class ConstraintExprVisitor final : public VNVisitor {
|
||||||
bool m_structSel = false; // Marks when inside structSel
|
bool m_structSel = false; // Marks when inside structSel
|
||||||
// (used to format "%@.%@" for struct arrays)
|
// (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) {
|
AstSFormatF* getConstFormat(AstNodeExpr* nodep) {
|
||||||
return new AstSFormatF{nodep->fileline(), (nodep->width() & 3) ? "#b%b" : "#x%x", false,
|
return new AstSFormatF{nodep->fileline(), (nodep->width() & 3) ? "#b%b" : "#x%x", false,
|
||||||
nodep};
|
nodep};
|
||||||
|
|
@ -641,19 +841,30 @@ class ConstraintExprVisitor final : public VNVisitor {
|
||||||
CONSTRAINTIGN,
|
CONSTRAINTIGN,
|
||||||
"Size constraint combined with element constraint may not work correctly");
|
"Size constraint combined with element constraint may not work correctly");
|
||||||
}
|
}
|
||||||
AstMemberSel* membersel = VN_IS(nodep->backp(), MemberSel)
|
|
||||||
? VN_AS(nodep->backp(), MemberSel)->cloneTree(false)
|
// Check if this variable is marked as globally constrained
|
||||||
: nullptr;
|
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();
|
if (membersel) varp = membersel->varp();
|
||||||
AstNodeModule* const classOrPackagep = nodep->classOrPackagep();
|
AstNodeModule* const classOrPackagep = nodep->classOrPackagep();
|
||||||
const RandomizeMode randMode = {.asInt = varp->user1()};
|
const RandomizeMode randMode = {.asInt = varp->user1()};
|
||||||
if (!randMode.usesMode && editFormat(nodep)) return;
|
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;
|
VNRelinker relinker;
|
||||||
nodep->unlinkFrBack(&relinker);
|
nodep->unlinkFrBack(&relinker);
|
||||||
AstNodeExpr* exprp = new AstSFormatF{nodep->fileline(), smtName, false, nullptr};
|
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}};
|
VCMethod::ARRAY_AT, new AstConst{nodep->fileline(), randMode.index}};
|
||||||
atp->dtypeSetUInt32();
|
atp->dtypeSetUInt32();
|
||||||
exprp = new AstCond{varp->fileline(), atp, exprp, constFormatp};
|
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);
|
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
||||||
}
|
}
|
||||||
relinker.relink(exprp);
|
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{
|
AstCMethodHard* const methodp = new AstCMethodHard{
|
||||||
varp->fileline(),
|
varp->fileline(),
|
||||||
new AstVarRef{varp->fileline(), VN_AS(m_genp->user2p(), NodeModule), m_genp,
|
new AstVarRef{varp->fileline(), VN_AS(m_genp->user2p(), NodeModule), m_genp,
|
||||||
|
|
@ -693,10 +910,14 @@ class ConstraintExprVisitor final : public VNVisitor {
|
||||||
methodp->dtypeSetVoid();
|
methodp->dtypeSetVoid();
|
||||||
AstClass* const classp
|
AstClass* const classp
|
||||||
= membersel ? VN_AS(membersel->user2p(), Class) : VN_AS(varp->user2p(), Class);
|
= membersel ? VN_AS(membersel->user2p(), Class) : VN_AS(varp->user2p(), Class);
|
||||||
AstVarRef* const varRefp
|
if (membersel) {
|
||||||
= new AstVarRef{varp->fileline(), classp, varp, VAccess::WRITE};
|
methodp->addPinsp(membersel);
|
||||||
varRefp->classOrPackagep(classOrPackagep);
|
} else {
|
||||||
membersel ? methodp->addPinsp(membersel) : methodp->addPinsp(varRefp);
|
AstVarRef* const varRefp
|
||||||
|
= new AstVarRef{varp->fileline(), classp, varp, VAccess::WRITE};
|
||||||
|
varRefp->classOrPackagep(classOrPackagep);
|
||||||
|
methodp->addPinsp(varRefp);
|
||||||
|
}
|
||||||
AstNodeDType* tmpDtypep = varp->dtypep();
|
AstNodeDType* tmpDtypep = varp->dtypep();
|
||||||
while (VN_IS(tmpDtypep, UnpackArrayDType) || VN_IS(tmpDtypep, DynArrayDType)
|
while (VN_IS(tmpDtypep, UnpackArrayDType) || VN_IS(tmpDtypep, DynArrayDType)
|
||||||
|| VN_IS(tmpDtypep, QueueDType) || VN_IS(tmpDtypep, AssocArrayDType))
|
|| VN_IS(tmpDtypep, QueueDType) || VN_IS(tmpDtypep, AssocArrayDType))
|
||||||
|
|
@ -911,8 +1132,21 @@ class ConstraintExprVisitor final : public VNVisitor {
|
||||||
editSMT(nodep, nodep->fromp(), indexp);
|
editSMT(nodep, nodep->fromp(), indexp);
|
||||||
}
|
}
|
||||||
void visit(AstMemberSel* nodep) override {
|
void visit(AstMemberSel* nodep) override {
|
||||||
if (nodep->user1()) {
|
if (nodep->varp()->rand().isRandomizable() && nodep->fromp()) {
|
||||||
nodep->v3warn(CONSTRAINTIGN, "Global constraints ignored (unsupported)");
|
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()
|
// Handle MemberSel references created by captureRefByThis()
|
||||||
if (VN_IS(nodep->fromp(), VarRef)
|
if (VN_IS(nodep->fromp(), VarRef)
|
||||||
|
|
@ -1943,10 +2177,19 @@ class RandomizeVisitor final : public VNVisitor {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
AstFunc* const memberFuncp
|
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
|
AstMethodCall* const callp
|
||||||
= new AstMethodCall{fl, new AstVarRef{fl, classp, memberVarp, VAccess::WRITE},
|
= memberVarp->globalConstrained()
|
||||||
"randomize", nullptr};
|
? 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->taskp(memberFuncp);
|
||||||
callp->dtypeFrom(memberFuncp);
|
callp->dtypeFrom(memberFuncp);
|
||||||
AstVarRef* const basicFvarRefReadp = basicFvarRefp->cloneTree(false);
|
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
|
if (!nodep->user1()) return; // Doesn't need randomize, or already processed
|
||||||
UINFO(9, "Define randomize() for " << nodep);
|
UINFO(9, "Define randomize() for " << nodep);
|
||||||
nodep->baseMostClassp()->needRNG(true);
|
nodep->baseMostClassp()->needRNG(true);
|
||||||
|
|
||||||
|
const bool globalConstrained = nodep->user1() == IS_RANDOMIZED_GLOBAL;
|
||||||
AstFunc* const randomizep = V3Randomize::newRandomizeFunc(m_memberMap, nodep);
|
AstFunc* const randomizep = V3Randomize::newRandomizeFunc(m_memberMap, nodep);
|
||||||
AstVar* const fvarp = VN_AS(randomizep->fvarp(), Var);
|
AstVar* const fvarp = VN_AS(randomizep->fvarp(), Var);
|
||||||
addPrePostCall(nodep, randomizep, "pre_randomize");
|
addPrePostCall(nodep, randomizep, "pre_randomize");
|
||||||
|
|
@ -2184,7 +2429,18 @@ class RandomizeVisitor final : public VNVisitor {
|
||||||
}
|
}
|
||||||
|
|
||||||
AstVarRef* const fvarRefp = new AstVarRef{fl, fvarp, VAccess::WRITE};
|
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
|
if (AstTask* const resizeAllTaskp
|
||||||
= VN_AS(m_memberMap.findMember(nodep, "__Vresize_constrained_arrays"), Task)) {
|
= VN_AS(m_memberMap.findMember(nodep, "__Vresize_constrained_arrays"), Task)) {
|
||||||
|
|
@ -2192,15 +2448,23 @@ class RandomizeVisitor final : public VNVisitor {
|
||||||
randomizep->addStmtsp(resizeTaskRefp->makeStmt());
|
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);
|
AstVarRef* const fvarRefReadp = fvarRefp->cloneTree(false);
|
||||||
fvarRefReadp->access(VAccess::READ);
|
fvarRefReadp->access(VAccess::READ);
|
||||||
|
|
||||||
randomizep->addStmtsp(new AstAssign{fl, fvarRefp->cloneTree(false),
|
// For global constraints: combine with solver result (beginValp)
|
||||||
new AstAnd{fl, fvarRefReadp, basicRandomizeCallp}});
|
// 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");
|
addPrePostCall(nodep, randomizep, "post_randomize");
|
||||||
nodep->user1(false);
|
nodep->user1(false);
|
||||||
}
|
}
|
||||||
|
|
@ -2417,7 +2681,7 @@ class RandomizeVisitor final : public VNVisitor {
|
||||||
randomizeFuncp->addStmtsp(localGenp);
|
randomizeFuncp->addStmtsp(localGenp);
|
||||||
|
|
||||||
AstFunc* const basicRandomizeFuncp
|
AstFunc* const basicRandomizeFuncp
|
||||||
= V3Randomize::newRandomizeFunc(m_memberMap, classp, "__Vbasic_randomize");
|
= V3Randomize::newRandomizeFunc(m_memberMap, classp, BASIC_RANDOMIZE_FUNC_NAME);
|
||||||
AstFuncRef* const basicRandomizeFuncCallp
|
AstFuncRef* const basicRandomizeFuncCallp
|
||||||
= new AstFuncRef{nodep->fileline(), basicRandomizeFuncp, nullptr};
|
= new AstFuncRef{nodep->fileline(), basicRandomizeFuncp, nullptr};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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()
|
||||||
|
|
@ -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 */
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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()
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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()
|
||||||
|
|
@ -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
|
||||||
|
|
@ -7,9 +7,6 @@
|
||||||
: ... note: In instance 't'
|
: ... note: In instance 't'
|
||||||
27 | q.size < 5;
|
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
|
%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'
|
: ... note: In instance 't'
|
||||||
15 | rand Cls cls;
|
15 | rand Cls cls;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue