parent
edb84f3776
commit
00988aed70
|
|
@ -148,10 +148,20 @@ class RandomizeMarkVisitor final : public VNVisitor {
|
||||||
bool m_inStdWith = false; // True when inside a 'with {}' clause
|
bool m_inStdWith = false; // True when inside a 'with {}' clause
|
||||||
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
|
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
|
std::unordered_set<const AstVar*> m_processedVars; // Track by variable instance, not class
|
||||||
|
|
||||||
// METHODS
|
// 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
|
// Check if a variable is listed in std::randomize() arguments
|
||||||
bool isVarInStdRandomizeArgs(const AstVar* varp) const {
|
bool isVarInStdRandomizeArgs(const AstVar* varp) const {
|
||||||
if (!m_inStdWith || !m_stdRandCallp) return false;
|
if (!m_inStdWith || !m_stdRandCallp) return false;
|
||||||
|
|
@ -187,7 +197,8 @@ class RandomizeMarkVisitor final : public VNVisitor {
|
||||||
if (classRefp) {
|
if (classRefp) {
|
||||||
AstClass* const rclassp = classRefp->classp();
|
AstClass* const rclassp = classRefp->classp();
|
||||||
if (!rclassp->user1()) {
|
if (!rclassp->user1()) {
|
||||||
rclassp->user1(IS_RANDOMIZED);
|
rclassp->user1(IS_RANDOMIZED_GLOBAL);
|
||||||
|
markGlobalConstrainedVars(rclassp);
|
||||||
markMembers(rclassp);
|
markMembers(rclassp);
|
||||||
markDerived(rclassp);
|
markDerived(rclassp);
|
||||||
}
|
}
|
||||||
|
|
@ -254,7 +265,7 @@ class RandomizeMarkVisitor final : public VNVisitor {
|
||||||
|
|
||||||
// Process a single constraint during nested constraint cloning
|
// Process a single constraint during nested constraint cloning
|
||||||
void processNestedConstraint(AstConstraint* const constrp, AstVarRef* rootVarRefp,
|
void processNestedConstraint(AstConstraint* const constrp, AstVarRef* rootVarRefp,
|
||||||
const std::vector<AstVar*>& newPath) {
|
const std::vector<AstVar*>& newPath, AstClass* targetClassp) {
|
||||||
std::string pathPrefix = rootVarRefp->name();
|
std::string pathPrefix = rootVarRefp->name();
|
||||||
for (AstVar* pathMemberVarp : newPath) {
|
for (AstVar* pathMemberVarp : newPath) {
|
||||||
pathPrefix += GLOBAL_CONSTRAINT_SEPARATOR + pathMemberVarp->name();
|
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();
|
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) {
|
if (existingConstrp->name() == newName) {
|
||||||
// Multiple paths lead to same constraint - unsupported pattern
|
// Multiple paths lead to same constraint - unsupported pattern
|
||||||
std::string fullPath = rootVarRefp->name();
|
std::string fullPath = rootVarRefp->name();
|
||||||
for (AstVar* pathVar : newPath) { fullPath += "." + pathVar->name(); }
|
for (AstVar* pathVar : newPath) { fullPath += "." + pathVar->name(); }
|
||||||
constrp->v3warn(E_UNSUPPORTED, "Unsupported: One variable '"
|
isDuplicate = true;
|
||||||
<< fullPath
|
|
||||||
<< "' cannot have multiple global constraints");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
if (isDuplicate) return;
|
||||||
|
|
||||||
AstConstraint* const cloneConstrp = constrp->cloneTree(false);
|
AstConstraint* const cloneConstrp = constrp->cloneTree(false);
|
||||||
cloneConstrp->name(newName);
|
cloneConstrp->name(newName);
|
||||||
|
|
@ -285,12 +296,16 @@ class RandomizeMarkVisitor final : public VNVisitor {
|
||||||
VL_DO_DANGLING(varRefp->deleteTree(), varRefp);
|
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
|
// Clone constraints from nested rand class members
|
||||||
void cloneNestedConstraintsRecurse(AstVarRef* rootVarRefp, AstClass* classp,
|
void cloneNestedConstraintsRecurse(AstVarRef* rootVarRefp, AstClass* classp,
|
||||||
const std::vector<AstVar*>& pathToClass) {
|
const std::vector<AstVar*>& pathToClass,
|
||||||
|
AstClass* targetClassp) {
|
||||||
for (AstNode* memberNodep = classp->membersp(); memberNodep;
|
for (AstNode* memberNodep = classp->membersp(); memberNodep;
|
||||||
memberNodep = memberNodep->nextp()) {
|
memberNodep = memberNodep->nextp()) {
|
||||||
AstVar* const memberVarp = VN_CAST(memberNodep, Var);
|
AstVar* const memberVarp = VN_CAST(memberNodep, Var);
|
||||||
|
|
@ -308,45 +323,9 @@ class RandomizeMarkVisitor final : public VNVisitor {
|
||||||
// member selections
|
// member selections
|
||||||
nestedClassp->foreachMember(
|
nestedClassp->foreachMember(
|
||||||
[&](AstClass* const containingClassp, AstConstraint* const constrp) {
|
[&](AstClass* const containingClassp, AstConstraint* const constrp) {
|
||||||
processNestedConstraint(constrp, rootVarRefp, newPath);
|
processNestedConstraint(constrp, rootVarRefp, newPath, targetClassp);
|
||||||
});
|
});
|
||||||
|
cloneNestedConstraintsRecurse(rootVarRefp, nestedClassp, 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -354,7 +333,6 @@ class RandomizeMarkVisitor final : public VNVisitor {
|
||||||
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()) {
|
||||||
|
|
@ -362,8 +340,6 @@ class RandomizeMarkVisitor final : public VNVisitor {
|
||||||
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);
|
||||||
|
|
@ -530,6 +506,26 @@ class RandomizeMarkVisitor final : public VNVisitor {
|
||||||
if (classp) {
|
if (classp) {
|
||||||
if (!classp->user1()) classp->user1(IS_RANDOMIZED);
|
if (!classp->user1()) classp->user1(IS_RANDOMIZED);
|
||||||
markMembers(classp);
|
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") {
|
if (nodep->classOrPackagep()->name() == "std") {
|
||||||
m_stdRandCallp = nullptr;
|
m_stdRandCallp = nullptr;
|
||||||
|
|
@ -649,36 +645,6 @@ class RandomizeMarkVisitor final : public VNVisitor {
|
||||||
} else {
|
} else {
|
||||||
nodep->user2p(m_modp);
|
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);
|
||||||
|
|
@ -743,6 +709,8 @@ class ConstraintExprVisitor final : public VNVisitor {
|
||||||
VMemberMap& m_memberMap; // Member names cached for fast lookup
|
VMemberMap& m_memberMap; // Member names cached for fast lookup
|
||||||
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)
|
||||||
|
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")
|
// Build full path for a MemberSel chain (e.g., "obj.l2.l3.l4")
|
||||||
std::string buildMemberPath(const AstMemberSel* const memberSelp) {
|
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}};
|
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 if (!membersel || !isGlobalConstrained) {
|
} else if (!isGlobalConstrained) {
|
||||||
// Only delete nodep here if it's not a global constraint
|
// Non-global constraints: delete nodep immediately
|
||||||
// Global constraints need nodep for write_var processing
|
|
||||||
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
||||||
}
|
}
|
||||||
|
// else: Global constraints keep nodep alive for write_var processing
|
||||||
relinker.relink(exprp);
|
relinker.relink(exprp);
|
||||||
|
|
||||||
// For global constraints: always call write_var with full path even if varp->user3() is
|
// For global constraints: check if this specific path has been written
|
||||||
// set For normal constraints: only call write_var if varp->user3() is not set
|
// For normal constraints: only call write_var if varp->user3() is not set
|
||||||
if (!varp->user3() || (membersel && nodep->varp()->globalConstrained())) {
|
const bool alreadyWritten
|
||||||
// For global constraints, delete nodep here after processing
|
= isGlobalConstrained ? m_writtenVars.count(smtName) > 0 : varp->user3();
|
||||||
if (membersel && isGlobalConstrained) VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
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{
|
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,
|
||||||
|
|
@ -981,6 +954,11 @@ class ConstraintExprVisitor final : public VNVisitor {
|
||||||
UASSERT_OBJ(initTaskp, classp, "No new() in class");
|
UASSERT_OBJ(initTaskp, classp, "No new() in class");
|
||||||
}
|
}
|
||||||
initTaskp->addStmtsp(methodp->makeStmt());
|
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 {
|
void visit(AstCountOnes* nodep) override {
|
||||||
|
|
@ -1171,10 +1149,16 @@ 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 {
|
||||||
|
// Check if rootVar is globalConstrained
|
||||||
if (nodep->varp()->rand().isRandomizable() && nodep->fromp()) {
|
if (nodep->varp()->rand().isRandomizable() && nodep->fromp()) {
|
||||||
AstNode* rootNode = nodep->fromp();
|
AstNode* rootNode = nodep->fromp();
|
||||||
while (const AstMemberSel* const selp = VN_CAST(rootNode, MemberSel))
|
while (const AstMemberSel* const selp = VN_CAST(rootNode, MemberSel))
|
||||||
rootNode = selp->fromp();
|
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
|
// Check if the root variable participates in global constraints
|
||||||
if (const AstVarRef* const varRefp = VN_CAST(rootNode, VarRef)) {
|
if (const AstVarRef* const varRefp = VN_CAST(rootNode, VarRef)) {
|
||||||
AstVar* const constrainedVar = varRefp->varp();
|
AstVar* const constrainedVar = varRefp->varp();
|
||||||
|
|
@ -1355,11 +1339,12 @@ public:
|
||||||
// CONSTRUCTORS
|
// CONSTRUCTORS
|
||||||
explicit ConstraintExprVisitor(VMemberMap& memberMap, AstNode* nodep,
|
explicit ConstraintExprVisitor(VMemberMap& memberMap, AstNode* nodep,
|
||||||
AstNodeFTask* inlineInitTaskp, AstVar* genp,
|
AstNodeFTask* inlineInitTaskp, AstVar* genp,
|
||||||
AstVar* randModeVarp)
|
AstVar* randModeVarp, std::set<std::string>& writtenVars)
|
||||||
: m_inlineInitTaskp{inlineInitTaskp}
|
: m_inlineInitTaskp{inlineInitTaskp}
|
||||||
, m_genp{genp}
|
, m_genp{genp}
|
||||||
, m_randModeVarp{randModeVarp}
|
, m_randModeVarp{randModeVarp}
|
||||||
, m_memberMap{memberMap} {
|
, m_memberMap{memberMap}
|
||||||
|
, m_writtenVars{writtenVars} {
|
||||||
iterateAndNextNull(nodep);
|
iterateAndNextNull(nodep);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -1643,6 +1628,7 @@ class RandomizeVisitor final : public VNVisitor {
|
||||||
int m_randCaseNum = 0; // Randcase number within a module for var naming
|
int m_randCaseNum = 0; // Randcase number within a module for var naming
|
||||||
std::map<std::string, AstCDType*> m_randcDtypes; // RandC data type deduplication
|
std::map<std::string, AstCDType*> m_randcDtypes; // RandC data type deduplication
|
||||||
AstConstraint* m_constraintp = nullptr; // Current constraint
|
AstConstraint* m_constraintp = nullptr; // Current constraint
|
||||||
|
std::set<std::string> m_writtenVars; // Track write_var calls per class to avoid duplicates
|
||||||
|
|
||||||
// METHODS
|
// METHODS
|
||||||
void createRandomGenerator(AstClass* const classp) {
|
void createRandomGenerator(AstClass* const classp) {
|
||||||
|
|
@ -2413,19 +2399,21 @@ class RandomizeVisitor final : public VNVisitor {
|
||||||
VL_RESTORER(m_randCaseNum);
|
VL_RESTORER(m_randCaseNum);
|
||||||
m_modp = nodep;
|
m_modp = nodep;
|
||||||
m_randCaseNum = 0;
|
m_randCaseNum = 0;
|
||||||
|
m_writtenVars.clear(); // Each class has its own set of written variables
|
||||||
|
|
||||||
iterateChildren(nodep);
|
iterateChildren(nodep);
|
||||||
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;
|
FileLine* fl = nodep->fileline();
|
||||||
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");
|
|
||||||
FileLine* fl = nodep->fileline();
|
|
||||||
|
|
||||||
AstVar* const randModeVarp = getRandModeVar(nodep);
|
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;
|
AstNodeExpr* beginValp = nullptr;
|
||||||
AstVar* genp = getRandomGenerator(nodep);
|
AstVar* genp = getRandomGenerator(nodep);
|
||||||
if (genp) {
|
if (genp) {
|
||||||
|
|
@ -2451,7 +2439,8 @@ class RandomizeVisitor final : public VNVisitor {
|
||||||
resizeAllTaskp->addStmtsp(resizeTaskRefp->makeStmt());
|
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()) {
|
if (constrp->itemsp()) {
|
||||||
taskp->addStmtsp(wrapIfConstraintMode(
|
taskp->addStmtsp(wrapIfConstraintMode(
|
||||||
nodep, constrp, constrp->itemsp()->unlinkFrBackWithNext()));
|
nodep, constrp, constrp->itemsp()->unlinkFrBackWithNext()));
|
||||||
|
|
@ -2481,18 +2470,7 @@ class RandomizeVisitor final : public VNVisitor {
|
||||||
}
|
}
|
||||||
|
|
||||||
AstVarRef* const fvarRefp = new AstVarRef{fl, fvarp, VAccess::WRITE};
|
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
|
if (AstTask* const resizeAllTaskp
|
||||||
= VN_AS(m_memberMap.findMember(nodep, "__Vresize_constrained_arrays"), Task)) {
|
= 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);
|
AstVarRef* const fvarRefReadp = fvarRefp->cloneTree(false);
|
||||||
fvarRefReadp->access(VAccess::READ);
|
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
|
AstFunc* const basicRandomizep
|
||||||
= V3Randomize::newRandomizeFunc(m_memberMap, nodep, BASIC_RANDOMIZE_FUNC_NAME);
|
= V3Randomize::newRandomizeFunc(m_memberMap, nodep, BASIC_RANDOMIZE_FUNC_NAME);
|
||||||
addBasicRandomizeBody(basicRandomizep, nodep, randModeVarp);
|
addBasicRandomizeBody(basicRandomizep, nodep, randModeVarp);
|
||||||
AstFuncRef* const basicRandomizeCallp = new AstFuncRef{fl, basicRandomizep, nullptr};
|
AstFuncRef* const basicRandomizeCallp = new AstFuncRef{fl, basicRandomizep, nullptr};
|
||||||
randomizep->addStmtsp(
|
randomizep->addStmtsp(new AstAssign{fl, fvarRefp->cloneTree(false),
|
||||||
new AstAssign{fl, fvarRefp->cloneTree(false),
|
|
||||||
new AstAnd{fl, fvarRefReadp, basicRandomizeCallp}});
|
new AstAnd{fl, fvarRefReadp, basicRandomizeCallp}});
|
||||||
}
|
|
||||||
addPrePostCall(nodep, randomizep, "post_randomize");
|
addPrePostCall(nodep, randomizep, "post_randomize");
|
||||||
nodep->user1(false);
|
nodep->user1(false);
|
||||||
}
|
}
|
||||||
|
|
@ -2649,8 +2619,8 @@ class RandomizeVisitor final : public VNVisitor {
|
||||||
AstNode* const capturedTreep = withp->exprp()->unlinkFrBackWithNext();
|
AstNode* const capturedTreep = withp->exprp()->unlinkFrBackWithNext();
|
||||||
randomizeFuncp->addStmtsp(capturedTreep);
|
randomizeFuncp->addStmtsp(capturedTreep);
|
||||||
{
|
{
|
||||||
ConstraintExprVisitor{m_memberMap, capturedTreep, randomizeFuncp, stdrand,
|
ConstraintExprVisitor{m_memberMap, capturedTreep, randomizeFuncp,
|
||||||
nullptr};
|
stdrand, nullptr, m_writtenVars};
|
||||||
}
|
}
|
||||||
AstCExpr* const solverCallp = new AstCExpr{fl};
|
AstCExpr* const solverCallp = new AstCExpr{fl};
|
||||||
solverCallp->dtypeSetBit();
|
solverCallp->dtypeSetBit();
|
||||||
|
|
@ -2793,8 +2763,8 @@ class RandomizeVisitor final : public VNVisitor {
|
||||||
AstNode* const capturedTreep = withp->exprp()->unlinkFrBackWithNext();
|
AstNode* const capturedTreep = withp->exprp()->unlinkFrBackWithNext();
|
||||||
randomizeFuncp->addStmtsp(capturedTreep);
|
randomizeFuncp->addStmtsp(capturedTreep);
|
||||||
{
|
{
|
||||||
ConstraintExprVisitor{m_memberMap, capturedTreep, randomizeFuncp, localGenp,
|
ConstraintExprVisitor{m_memberMap, capturedTreep, randomizeFuncp,
|
||||||
randModeVarp};
|
localGenp, randModeVarp, m_writtenVars};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call the solver and set return value
|
// Call the solver and set return value
|
||||||
|
|
@ -2883,6 +2853,18 @@ public:
|
||||||
explicit RandomizeVisitor(AstNetlist* nodep)
|
explicit RandomizeVisitor(AstNetlist* nodep)
|
||||||
: m_inlineUniqueNames{"__Vrandwith"} {
|
: m_inlineUniqueNames{"__Vrandwith"} {
|
||||||
createRandomizeClassVars(nodep);
|
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);
|
iterate(nodep);
|
||||||
nodep->foreach([&](AstConstraint* constrp) {
|
nodep->foreach([&](AstConstraint* constrp) {
|
||||||
VL_DO_DANGLING(pushDeletep(constrp->unlinkFrBack()), constrp);
|
VL_DO_DANGLING(pushDeletep(constrp->unlinkFrBack()), constrp);
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,26 @@
|
||||||
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:41:20: Unsupported: MEMBERSEL 'm_x' within a global constraint
|
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:41:20: Unsupported: Array element access in global constraint
|
||||||
: ... note: In instance 't_constraint_global_arr_unsup'
|
|
||||||
41 | m_mid.m_arr[0].m_x == 200;
|
41 | m_mid.m_arr[0].m_x == 200;
|
||||||
| ^~~
|
| ^~~
|
||||||
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
|
... 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
|
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:42:20: Unsupported: Array element access in global constraint
|
||||||
: ... note: In instance 't_constraint_global_arr_unsup'
|
|
||||||
42 | m_mid.m_arr[0].m_y == 201;
|
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
|
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:47:24: Unsupported: Array element access in 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:47:18: Unsupported: Array element access in global constraint
|
||||||
47 | m_mid_arr[0].m_obj.m_x == 300;
|
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
|
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:48:24: Unsupported: Array element access in 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:48:18: Unsupported: Array element access in global constraint
|
||||||
48 | m_mid_arr[0].m_obj.m_y == 301;
|
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
|
%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:53:27: Unsupported: Array element access in 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;
|
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
|
%Error: Exiting due to
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
# 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
|
# 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
|
# Lesser General Public License Version 3 or the Perl Artistic License
|
||||||
# Version 2.0.
|
# Version 2.0.
|
||||||
|
|
@ -9,8 +9,13 @@
|
||||||
|
|
||||||
import vltest_bootstrap
|
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()
|
test.passes()
|
||||||
|
|
@ -44,6 +44,10 @@ module t;
|
||||||
initial begin
|
initial begin
|
||||||
top = new();
|
top = new();
|
||||||
if (!top.randomize()) $stop;
|
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");
|
$write("*-* All Finished *-*\n");
|
||||||
$finish;
|
$finish;
|
||||||
end
|
end
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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,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
|
||||||
Loading…
Reference in New Issue