Support General Global Constraints (#6709) (#6711)

This commit is contained in:
Yilou Wang 2025-11-19 17:08:42 +01:00 committed by GitHub
parent edb84f3776
commit 00988aed70
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 264 additions and 150 deletions

View File

@ -148,10 +148,20 @@ class RandomizeMarkVisitor final : public VNVisitor {
bool m_inStdWith = false; // True when inside a 'with {}' clause
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
// Mark all rand variables in IS_RANDOMIZED_GLOBAL classes as globalConstrained
void markGlobalConstrainedVars(AstClass* classp) {
for (const AstClass* cp = classp; cp;
cp = cp->extendsp() ? cp->extendsp()->classp() : nullptr) {
for (AstNode* memberp = cp->stmtsp(); memberp; memberp = memberp->nextp()) {
AstVar* const varp = VN_CAST(memberp, Var);
if (!varp) continue;
if (varp->rand().isRandomizable()) varp->globalConstrained(true);
}
}
}
// Check if a variable is listed in std::randomize() arguments
bool isVarInStdRandomizeArgs(const AstVar* varp) const {
if (!m_inStdWith || !m_stdRandCallp) return false;
@ -187,7 +197,8 @@ class RandomizeMarkVisitor final : public VNVisitor {
if (classRefp) {
AstClass* const rclassp = classRefp->classp();
if (!rclassp->user1()) {
rclassp->user1(IS_RANDOMIZED);
rclassp->user1(IS_RANDOMIZED_GLOBAL);
markGlobalConstrainedVars(rclassp);
markMembers(rclassp);
markDerived(rclassp);
}
@ -254,7 +265,7 @@ class RandomizeMarkVisitor final : public VNVisitor {
// Process a single constraint during nested constraint cloning
void processNestedConstraint(AstConstraint* const constrp, AstVarRef* rootVarRefp,
const std::vector<AstVar*>& newPath) {
const std::vector<AstVar*>& newPath, AstClass* targetClassp) {
std::string pathPrefix = rootVarRefp->name();
for (AstVar* pathMemberVarp : newPath) {
pathPrefix += GLOBAL_CONSTRAINT_SEPARATOR + pathMemberVarp->name();
@ -262,17 +273,17 @@ class RandomizeMarkVisitor final : public VNVisitor {
const std::string newName = pathPrefix + GLOBAL_CONSTRAINT_SEPARATOR + constrp->name();
for (const AstConstraint* existingConstrp : m_clonedConstraints) {
// Check if this constraint already exists in the target class
bool isDuplicate = false;
targetClassp->foreachMember([&](AstClass* const, AstConstraint* const existingConstrp) {
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;
isDuplicate = true;
}
}
});
if (isDuplicate) return;
AstConstraint* const cloneConstrp = constrp->cloneTree(false);
cloneConstrp->name(newName);
@ -285,12 +296,16 @@ class RandomizeMarkVisitor final : public VNVisitor {
VL_DO_DANGLING(varRefp->deleteTree(), varRefp);
});
m_clonedConstraints.push_back(cloneConstrp);
// Add constraint directly to the target class
targetClassp->addStmtsp(cloneConstrp);
// Immediately iterate to set user1 marks
iterateConst(cloneConstrp);
}
// Clone constraints from nested rand class members
void cloneNestedConstraintsRecurse(AstVarRef* rootVarRefp, AstClass* classp,
const std::vector<AstVar*>& pathToClass) {
const std::vector<AstVar*>& pathToClass,
AstClass* targetClassp) {
for (AstNode* memberNodep = classp->membersp(); memberNodep;
memberNodep = memberNodep->nextp()) {
AstVar* const memberVarp = VN_CAST(memberNodep, Var);
@ -308,45 +323,9 @@ class RandomizeMarkVisitor final : public VNVisitor {
// member selections
nestedClassp->foreachMember(
[&](AstClass* const containingClassp, AstConstraint* const constrp) {
processNestedConstraint(constrp, rootVarRefp, newPath);
processNestedConstraint(constrp, rootVarRefp, newPath, targetClassp);
});
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);
cloneNestedConstraintsRecurse(rootVarRefp, nestedClassp, newPath, targetClassp);
}
}
@ -354,7 +333,6 @@ class RandomizeMarkVisitor final : public VNVisitor {
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()) {
@ -362,8 +340,6 @@ class RandomizeMarkVisitor final : public VNVisitor {
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);
@ -530,6 +506,26 @@ class RandomizeMarkVisitor final : public VNVisitor {
if (classp) {
if (!classp->user1()) classp->user1(IS_RANDOMIZED);
markMembers(classp);
// Clone constraints from all IS_RANDOMIZED_GLOBAL members
classp->foreachMember([&](AstClass* const, AstVar* const memberVarp) {
if (!memberVarp->rand().isRandomizable()) return;
const AstNodeDType* const dtypep = memberVarp->dtypep()->skipRefp();
const AstClassRefDType* const classRefp = VN_CAST(dtypep, ClassRefDType);
if (!classRefp || !classRefp->classp()) return;
AstClass* const memberClassp = classRefp->classp();
if (memberClassp->user1() != IS_RANDOMIZED_GLOBAL) return;
memberVarp->globalConstrained(true);
// Clone constraints from this IS_RANDOMIZED_GLOBAL member class
AstVarRef* rootVarRefp
= new AstVarRef{nodep->fileline(), classp, memberVarp, VAccess::READ};
std::vector<AstVar*> emptyPath;
memberClassp->foreachMember([&](AstClass* const, AstConstraint* const constrp) {
processNestedConstraint(constrp, rootVarRefp, emptyPath, classp);
});
cloneNestedConstraintsRecurse(rootVarRefp, memberClassp, emptyPath, classp);
// Delete the temporary VarRef created for constraint cloning
VL_DO_DANGLING(rootVarRefp->deleteTree(), rootVarRefp);
});
}
if (nodep->classOrPackagep()->name() == "std") {
m_stdRandCallp = nullptr;
@ -649,36 +645,6 @@ class RandomizeMarkVisitor final : public VNVisitor {
} 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);
@ -743,6 +709,8 @@ class ConstraintExprVisitor final : public VNVisitor {
VMemberMap& m_memberMap; // Member names cached for fast lookup
bool m_structSel = false; // Marks when inside structSel
// (used to format "%@.%@" for struct arrays)
std::set<std::string>& m_writtenVars; // Track which variable paths have write_var generated
// (shared across all constraints)
// Build full path for a MemberSel chain (e.g., "obj.l2.l3.l4")
std::string buildMemberPath(const AstMemberSel* const memberSelp) {
@ -916,18 +884,23 @@ 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 if (!membersel || !isGlobalConstrained) {
// Only delete nodep here if it's not a global constraint
// Global constraints need nodep for write_var processing
} else if (!isGlobalConstrained) {
// Non-global constraints: delete nodep immediately
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
// else: Global constraints keep nodep alive for write_var processing
relinker.relink(exprp);
// 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);
// For global constraints: check if this specific path has been written
// For normal constraints: only call write_var if varp->user3() is not set
const bool alreadyWritten
= isGlobalConstrained ? m_writtenVars.count(smtName) > 0 : varp->user3();
const bool shouldWriteVar = !alreadyWritten;
if (shouldWriteVar) {
// Track this variable path as written
if (isGlobalConstrained) m_writtenVars.insert(smtName);
// For global constraints, delete nodep after processing
if (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,
@ -981,6 +954,11 @@ class ConstraintExprVisitor final : public VNVisitor {
UASSERT_OBJ(initTaskp, classp, "No new() in class");
}
initTaskp->addStmtsp(methodp->makeStmt());
} else {
// Variable already written, clean up cloned membersel if any
if (membersel) VL_DO_DANGLING(membersel->deleteTree(), membersel);
// Delete nodep if it's a global constraint (not deleted yet)
if (isGlobalConstrained) VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
}
void visit(AstCountOnes* nodep) override {
@ -1171,10 +1149,16 @@ class ConstraintExprVisitor final : public VNVisitor {
editSMT(nodep, nodep->fromp(), indexp);
}
void visit(AstMemberSel* nodep) override {
// Check if rootVar is globalConstrained
if (nodep->varp()->rand().isRandomizable() && nodep->fromp()) {
AstNode* rootNode = nodep->fromp();
while (const AstMemberSel* const selp = VN_CAST(rootNode, MemberSel))
rootNode = selp->fromp();
// Detect array/assoc array access in global constraints
if (VN_IS(rootNode, ArraySel) || VN_IS(rootNode, AssocSel)) {
nodep->v3warn(E_UNSUPPORTED,
"Unsupported: Array element access in global constraint ");
}
// Check if the root variable participates in global constraints
if (const AstVarRef* const varRefp = VN_CAST(rootNode, VarRef)) {
AstVar* const constrainedVar = varRefp->varp();
@ -1355,11 +1339,12 @@ public:
// CONSTRUCTORS
explicit ConstraintExprVisitor(VMemberMap& memberMap, AstNode* nodep,
AstNodeFTask* inlineInitTaskp, AstVar* genp,
AstVar* randModeVarp)
AstVar* randModeVarp, std::set<std::string>& writtenVars)
: m_inlineInitTaskp{inlineInitTaskp}
, m_genp{genp}
, m_randModeVarp{randModeVarp}
, m_memberMap{memberMap} {
, m_memberMap{memberMap}
, m_writtenVars{writtenVars} {
iterateAndNextNull(nodep);
}
};
@ -1643,6 +1628,7 @@ class RandomizeVisitor final : public VNVisitor {
int m_randCaseNum = 0; // Randcase number within a module for var naming
std::map<std::string, AstCDType*> m_randcDtypes; // RandC data type deduplication
AstConstraint* m_constraintp = nullptr; // Current constraint
std::set<std::string> m_writtenVars; // Track write_var calls per class to avoid duplicates
// METHODS
void createRandomGenerator(AstClass* const classp) {
@ -2413,19 +2399,21 @@ class RandomizeVisitor final : public VNVisitor {
VL_RESTORER(m_randCaseNum);
m_modp = nodep;
m_randCaseNum = 0;
m_writtenVars.clear(); // Each class has its own set of written variables
iterateChildren(nodep);
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;
FileLine* fl = nodep->fileline();
AstFunc* const randomizep = V3Randomize::newRandomizeFunc(m_memberMap, nodep);
AstVar* const fvarp = VN_AS(randomizep->fvarp(), Var);
addPrePostCall(nodep, randomizep, "pre_randomize");
FileLine* fl = nodep->fileline();
AstVar* const randModeVarp = getRandModeVar(nodep);
addPrePostCall(nodep, randomizep, "pre_randomize");
// Both IS_RANDOMIZED and IS_RANDOMIZED_GLOBAL classes need full constraint support
// IS_RANDOMIZED_GLOBAL classes can be randomized independently
AstNodeExpr* beginValp = nullptr;
AstVar* genp = getRandomGenerator(nodep);
if (genp) {
@ -2451,7 +2439,8 @@ class RandomizeVisitor final : public VNVisitor {
resizeAllTaskp->addStmtsp(resizeTaskRefp->makeStmt());
}
ConstraintExprVisitor{m_memberMap, constrp->itemsp(), nullptr, genp, randModeVarp};
ConstraintExprVisitor{m_memberMap, constrp->itemsp(), nullptr,
genp, randModeVarp, m_writtenVars};
if (constrp->itemsp()) {
taskp->addStmtsp(wrapIfConstraintMode(
nodep, constrp, constrp->itemsp()->unlinkFrBackWithNext()));
@ -2481,18 +2470,7 @@ class RandomizeVisitor final : public VNVisitor {
}
AstVarRef* const fvarRefp = new AstVarRef{fl, fvarp, VAccess::WRITE};
// 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});
}
randomizep->addStmtsp(new AstAssign{fl, fvarRefp, beginValp});
if (AstTask* const resizeAllTaskp
= VN_AS(m_memberMap.findMember(nodep, "__Vresize_constrained_arrays"), Task)) {
@ -2503,20 +2481,12 @@ class RandomizeVisitor final : public VNVisitor {
AstVarRef* const fvarRefReadp = fvarRefp->cloneTree(false);
fvarRefReadp->access(VAccess::READ);
// 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}});
}
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);
}
@ -2649,8 +2619,8 @@ class RandomizeVisitor final : public VNVisitor {
AstNode* const capturedTreep = withp->exprp()->unlinkFrBackWithNext();
randomizeFuncp->addStmtsp(capturedTreep);
{
ConstraintExprVisitor{m_memberMap, capturedTreep, randomizeFuncp, stdrand,
nullptr};
ConstraintExprVisitor{m_memberMap, capturedTreep, randomizeFuncp,
stdrand, nullptr, m_writtenVars};
}
AstCExpr* const solverCallp = new AstCExpr{fl};
solverCallp->dtypeSetBit();
@ -2793,8 +2763,8 @@ class RandomizeVisitor final : public VNVisitor {
AstNode* const capturedTreep = withp->exprp()->unlinkFrBackWithNext();
randomizeFuncp->addStmtsp(capturedTreep);
{
ConstraintExprVisitor{m_memberMap, capturedTreep, randomizeFuncp, localGenp,
randModeVarp};
ConstraintExprVisitor{m_memberMap, capturedTreep, randomizeFuncp,
localGenp, randModeVarp, m_writtenVars};
}
// Call the solver and set return value
@ -2883,6 +2853,18 @@ public:
explicit RandomizeVisitor(AstNetlist* nodep)
: m_inlineUniqueNames{"__Vrandwith"} {
createRandomizeClassVars(nodep);
// Mark variables in global constraints
// These should not be randomized in nested class's __VBasicRand
nodep->foreach([&](AstConstraint* constrp) {
constrp->foreach([&](AstMemberSel* memberSelp) {
// Only mark if this MemberSel was created during constraint cloning
if (memberSelp->user2p()) {
AstVar* const varp = memberSelp->varp();
if (!varp->user3()) varp->user3(true);
}
});
});
iterate(nodep);
nodep->foreach([&](AstConstraint* constrp) {
VL_DO_DANGLING(pushDeletep(constrp->unlinkFrBack()), constrp);

View File

@ -1,26 +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'
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:41:20: Unsupported: Array element access in global constraint
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'
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:42:20: Unsupported: Array element access in global constraint
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'
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:47:24: Unsupported: Array element access in global constraint
47 | m_mid_arr[0].m_obj.m_x == 300;
| ^~~
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:47:18: Unsupported: Array element access in global constraint
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'
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:48:24: Unsupported: Array element access in global constraint
48 | m_mid_arr[0].m_obj.m_y == 301;
| ^~~
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:48:18: Unsupported: Array element access in global constraint
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'
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:53:27: Unsupported: Array element access in global constraint
53 | m_mid_arr[1].m_arr[2].m_y == 400;
| ^~~
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:53:18: Unsupported: Array element access in global constraint
53 | m_mid_arr[1].m_arr[2].m_y == 400;
| ^~~~~
%Error: Exiting due to

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2024 by Wilson Snyder. This program is free software; you
# 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.
@ -9,8 +9,13 @@
import vltest_bootstrap
test.scenarios('vlt')
test.scenarios('simulator')
test.lint(fails=test.vlt_all, expect_filename=test.golden_filename)
if not test.have_solver:
test.skip("No constraint solver installed")
test.compile()
test.execute()
test.passes()

View File

@ -44,6 +44,10 @@ module t;
initial begin
top = new();
if (!top.randomize()) $stop;
$display("After randomization:");
$display(" top.m_y = %0d", top.m_y);
$display(" top.m_mid.m_x = %0d", top.m_mid.m_x);
$display(" top.m_mid.m_inner.m_val = %0d", top.m_mid.m_inner.m_val);
$write("*-* All Finished *-*\n");
$finish;
end

View File

@ -1,6 +0,0 @@
%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,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,108 @@
// 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
// Simple test for global constraints with 2-level nesting: Top -> Mid -> Inner
class Inner;
rand int m_val;
rand int m_rand_in_val;
rand int m_only_topConstrained_val;
constraint c_inner { m_val inside {[1:5]}; }
function new();
m_val = 0;
m_rand_in_val = 0;
m_only_topConstrained_val = 0;
endfunction
endclass
class Mid;
int m_limit;
rand int m_x;
rand Inner m_inner;
rand int m_rand_mid_val;
constraint c_mid { m_x == m_limit; }
function new(int lim);
m_limit = lim;
m_x = 0;
m_rand_mid_val = 0;
m_inner = new();
endfunction
endclass
class Top;
rand Mid m_mid;
rand int m_y;
constraint c_top {
m_y < m_mid.m_x; // 1-level reference
m_mid.m_inner.m_val < m_y; // 2-level reference
m_mid.m_inner.m_only_topConstrained_val == 5; // Only constrained at top level
}
function new();
m_mid = new(10);
m_y = 0;
endfunction
endclass
module t_constraint_global_random_simple;
int success;
Top t;
initial begin
t = new();
// Test: Regular randomize() with global constraints
success = t.randomize();
if (success != 1) $stop;
$display("After randomization:");
$display(" t.m_y = %0d", t.m_y);
$display(" t.m_mid.m_x = %0d", t.m_mid.m_x);
$display(" t.m_mid.m_inner.m_val = %0d", t.m_mid.m_inner.m_val);
$display(" t.m_mid.m_inner.m_only_topConstrained_val = %0d", t.m_mid.m_inner.m_only_topConstrained_val);
$display(" t.m_mid.m_rand_mid_val = %0d", t.m_mid.m_rand_mid_val);
$display(" t.m_mid.m_inner.m_rand_in_val = %0d", t.m_mid.m_inner.m_rand_in_val);
// Verify constraints
// 1. c_mid: m_x == m_limit
if (t.m_mid.m_x != 10) begin
$display("ERROR: m_mid.m_x should be 10, got %0d", t.m_mid.m_x);
$stop;
end
// 2. c_inner: m_val in [1:5]
if (t.m_mid.m_inner.m_val < 1 || t.m_mid.m_inner.m_val > 5) begin
$display("ERROR: m_inner.m_val should be in [1:5], got %0d", t.m_mid.m_inner.m_val);
$stop;
end
// 3. c_top: m_y < m_mid.m_x (m_y < 10)
if (t.m_y >= t.m_mid.m_x) begin
$display("ERROR: m_y should be < m_mid.m_x, got m_y=%0d, m_x=%0d", t.m_y, t.m_mid.m_x);
$stop;
end
// 4. c_top: m_mid.m_inner.m_val < m_y
if (t.m_mid.m_inner.m_val >= t.m_y) begin
$display("ERROR: m_inner.m_val should be < m_y, got m_val=%0d, m_y=%0d",
t.m_mid.m_inner.m_val, t.m_y);
$stop;
end
// 5. c_top: m_mid.m_inner.m_only_topConstrained_val == 5
if (t.m_mid.m_inner.m_only_topConstrained_val != 5) begin
$display("ERROR: m_only_topConstrained_val should be 5, got %0d",
t.m_mid.m_inner.m_only_topConstrained_val);
$stop;
end
$write("*-* All Finished *-*\n");
$finish;
end
endmodule