Support constraint_mode() on static constraints (#7027) (#7038)

This commit is contained in:
Yilou Wang 2026-02-10 19:58:35 +01:00 committed by GitHub
parent 6303eb45ce
commit 8791e6c5f2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 186 additions and 59 deletions

View File

@ -514,11 +514,6 @@ class RandomizeMarkVisitor final : public VNVisitor {
= VN_CAST(methodCallp->fromp(), ConstraintRef)) {
constrp = constrRefp->constrp();
if (constrRefp->fromp()) classp = VN_AS(constrRefp->classOrPackagep(), Class);
if (constrp->isStatic()) {
nodep->v3warn(E_UNSUPPORTED,
"Unsupported: 'constraint_mode()' on static constraint");
valid = false;
}
} else if (AstClassRefDType* classRefDtp
= VN_CAST(methodCallp->fromp()->dtypep()->skipRefp(), ClassRefDType)) {
classp = classRefDtp->classp();
@ -538,11 +533,6 @@ class RandomizeMarkVisitor final : public VNVisitor {
constrp->user1(constraintMode.asInt);
} else {
classp->foreachMember([=](AstClass*, AstConstraint* constrp) {
if (constrp->isStatic()) {
nodep->v3warn(E_UNSUPPORTED,
"Unsupported: 'constraint_mode()' on static constraint: "
<< constrp->prettyNameQ());
}
constrp->user1(constraintMode.asInt);
});
}
@ -1834,6 +1824,8 @@ class RandomizeVisitor final : public VNVisitor {
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
std::map<AstClass*, AstVar*>
m_staticConstraintModeVars; // Static constraint mode vars per class
// METHODS
// Check if two nodes are semantically equivalent (not pointer equality):
@ -1947,6 +1939,24 @@ class RandomizeVisitor final : public VNVisitor {
}
return nullptr;
}
AstVar* getCreateStaticConstraintModeVar(AstClass* const classp) {
auto it = m_staticConstraintModeVars.find(classp);
if (it != m_staticConstraintModeVars.end()) return it->second;
if (AstClassExtends* const extendsp = classp->extendsp()) {
return getCreateStaticConstraintModeVar(extendsp->classp());
}
AstVar* const staticModeVarp = createStaticModeVar(classp, "__Vstaticconstraintmode");
m_staticConstraintModeVars[classp] = staticModeVarp;
return staticModeVarp;
}
AstVar* getStaticConstraintModeVar(AstClass* const classp) {
auto it = m_staticConstraintModeVars.find(classp);
if (it != m_staticConstraintModeVars.end()) return it->second;
if (AstClassExtends* const extendsp = classp->extendsp()) {
return getStaticConstraintModeVar(extendsp->classp());
}
return nullptr;
}
AstVar* createModeVar(AstClass* const classp, const char* const name) {
FileLine* const fl = classp->fileline();
if (!m_dynarrayDtp) {
@ -1960,6 +1970,24 @@ class RandomizeVisitor final : public VNVisitor {
classp->addStmtsp(modeVarp);
return modeVarp;
}
AstVar* createStaticModeVar(AstClass* const classp, const char* const name) {
// Create a static variable that will be shared across all instances.
// By setting lifetime to STATIC_EXPLICIT, V3Class will move this to the class package.
FileLine* const fl = classp->fileline();
if (!m_dynarrayDtp) {
m_dynarrayDtp = new AstDynArrayDType{
fl, v3Global.rootp()->typeTablep()->findBitDType()->dtypep()};
m_dynarrayDtp->dtypep(m_dynarrayDtp);
v3Global.rootp()->typeTablep()->addTypesp(m_dynarrayDtp);
}
AstVar* const modeVarp = new AstVar{fl, VVarType::MODULETEMP, name, m_dynarrayDtp};
modeVarp->lifetime(VLifetime::STATIC_EXPLICIT);
// Note: user2p is set to classp here. V3Scope will later update varScopep
// to point to the package scope when the variable is moved by V3Class.
modeVarp->user2p(classp);
classp->addStmtsp(modeVarp);
return modeVarp;
}
static void addSetRandMode(AstNodeFTask* const ftaskp, AstVar* const genp,
AstVar* const randModeVarp) {
FileLine* const fl = ftaskp->fileline();
@ -1976,18 +2004,28 @@ class RandomizeVisitor final : public VNVisitor {
bool hasConstraints = false;
uint32_t randModeCount = 0;
uint32_t constraintModeCount = 0;
uint32_t staticConstraintModeCount = 0;
classp->foreachMember([&](AstClass*, AstNode* memberp) {
// SystemVerilog only allows single inheritance, so we don't need to worry about
// index overlap. If the index > 0, it's already been set.
if (VN_IS(memberp, Constraint)) {
if (AstConstraint* const constrp = VN_CAST(memberp, Constraint)) {
hasConstraints = true;
RandomizeMode constraintMode = {.asInt = memberp->user1()};
if (!constraintMode.usesMode) return;
if (constraintMode.index == 0) {
constraintMode.index = constraintModeCount++;
// Use separate index counters for static vs non-static constraints
if (constrp->isStatic()) {
constraintMode.index = staticConstraintModeCount++;
} else {
constraintMode.index = constraintModeCount++;
}
memberp->user1(constraintMode.asInt);
} else {
constraintModeCount = constraintMode.index + 1;
if (constrp->isStatic()) {
staticConstraintModeCount = constraintMode.index + 1;
} else {
constraintModeCount = constraintMode.index + 1;
}
}
} else if (VN_IS(memberp, Var)) {
RandomizeMode randMode = {.asInt = memberp->user1()};
@ -2009,6 +2047,10 @@ class RandomizeVisitor final : public VNVisitor {
AstVar* const constraintModeVarp = getCreateConstraintModeVar(classp);
makeModeInit(constraintModeVarp, classp, constraintModeCount);
}
if (staticConstraintModeCount > 0) {
AstVar* const staticConstraintModeVarp = getCreateStaticConstraintModeVar(classp);
makeStaticModeInit(staticConstraintModeVarp, classp, staticConstraintModeCount);
}
});
}
void makeModeInit(AstVar* modeVarp, AstClass* classp, uint32_t modeCount) {
@ -2025,6 +2067,37 @@ class RandomizeVisitor final : public VNVisitor {
new AstVarRef{fl, modeVarModp, modeVarp, VAccess::WRITE},
new AstConst{fl, 1}, true));
}
void makeStaticModeInit(AstVar* modeVarp, AstClass* classp, uint32_t modeCount) {
// For static constraint mode, we need lazy initialization since it's shared across
// instances. Generate: if (size() == 0) { resize(N); set all to 1; }
AstNodeModule* const modeVarModp = VN_AS(modeVarp->user2p(), NodeModule);
FileLine* fl = modeVarp->fileline();
// Build the condition: size() == 0
AstCMethodHard* const sizep
= new AstCMethodHard{fl, new AstVarRef{fl, modeVarModp, modeVarp, VAccess::READ},
VCMethod::DYN_SIZE, nullptr};
sizep->dtypeSetUInt32();
AstEq* const condp = new AstEq{fl, sizep, new AstConst{fl, 0}};
// Build the then-block: resize and set all to 1
AstCMethodHard* const dynarrayNewp
= new AstCMethodHard{fl, new AstVarRef{fl, modeVarModp, modeVarp, VAccess::WRITE},
VCMethod::DYN_RESIZE, new AstConst{fl, modeCount}};
dynarrayNewp->dtypeSetVoid();
AstNode* const thenStmtsp = dynarrayNewp->makeStmt();
thenStmtsp->addNext(
makeModeSetLoop(fl, new AstVarRef{fl, modeVarModp, modeVarp, VAccess::WRITE},
new AstConst{fl, 1}, true));
// Build the if statement
AstIf* const ifp = new AstIf{fl, condp, thenStmtsp};
// Add to new() constructor
AstNodeFTask* const newp = VN_AS(m_memberMap.findMember(classp, "new"), NodeFTask);
UASSERT_OBJ(newp, classp, "No new() in class");
newp->addStmtsp(ifp);
}
static AstNode* makeModeSetLoop(FileLine* const fl, AstNodeExpr* const lhsp,
AstNodeExpr* const rhsp, bool inTask) {
AstVar* const iterVarp = new AstVar{fl, VVarType::BLOCKTEMP, "i", lhsp->findUInt32DType()};
@ -2054,17 +2127,20 @@ class RandomizeVisitor final : public VNVisitor {
const RandomizeMode rmode = {.asInt = varp->user1()};
return VN_AS(wrapIfMode(rmode, getRandModeVar(classp), stmtp), NodeStmt);
}
static AstNode* wrapIfConstraintMode(AstClass* classp, AstConstraint* const constrp,
AstNode* stmtp) {
AstNode* wrapIfConstraintMode(AstClass* classp, AstConstraint* const constrp, AstNode* stmtp) {
const RandomizeMode rmode = {.asInt = constrp->user1()};
return wrapIfMode(rmode, getConstraintModeVar(classp), stmtp);
AstVar* const modeVarp = constrp->isStatic() ? getStaticConstraintModeVar(classp)
: getConstraintModeVar(classp);
return wrapIfMode(rmode, modeVarp, stmtp);
}
static AstNode* wrapIfMode(const RandomizeMode mode, AstVar* modeVarp, AstNode* stmtp) {
FileLine* const fl = stmtp->fileline();
if (mode.usesMode) {
AstCMethodHard* const atp = new AstCMethodHard{
fl, new AstVarRef{fl, VN_AS(modeVarp->user2p(), Class), modeVarp, VAccess::READ},
VCMethod::ARRAY_AT, new AstConst{fl, mode.index}};
// user2p can be either AstClass or AstClassPackage (for static constraints)
AstNodeModule* const modp = VN_AS(modeVarp->user2p(), NodeModule);
AstCMethodHard* const atp
= new AstCMethodHard{fl, new AstVarRef{fl, modp, modeVarp, VAccess::READ},
VCMethod::ARRAY_AT, new AstConst{fl, mode.index}};
atp->dtypeSetUInt32();
return new AstIf{fl, atp, stmtp};
}
@ -2492,7 +2568,15 @@ class RandomizeVisitor final : public VNVisitor {
// Creates a lvalue reference to the randomize mode var. Called by visit(AstNodeFTaskRef*)
AstNodeExpr* makeModeAssignLhs(FileLine* const fl, AstClass* const classp,
AstNodeExpr* const fromp, AstVar* const modeVarp) {
if (classp == m_modp) {
// For static constraint mode vars, always use VarRef (not MemberSel).
// At this point V3Class hasn't run yet, so user2p may be nullptr.
// Generate VarRef with classp as module; V3Scope will update varScopep later
// when the variable is moved to the class package.
if (modeVarp->lifetime().isStatic()) {
// Static mode var - generate VarRef that will be resolved by V3Scope
if (fromp) VL_DO_DANGLING(fromp->unlinkFrBack()->deleteTree(), fromp);
return new AstVarRef{fl, classp, modeVarp, VAccess::WRITE};
} else if (classp == m_modp) {
// Called on 'this' or a member of 'this'
return new AstVarRef{fl, VN_AS(modeVarp->user2p(), NodeModule), modeVarp,
VAccess::WRITE};
@ -2848,7 +2932,10 @@ class RandomizeVisitor final : public VNVisitor {
classp = VN_AS(fromp->dtypep()->skipRefp(), ClassRefDType)->classp();
}
UASSERT_OBJ(classp, nodep, "Failed to find class");
AstVar* const constraintModeVarp = getConstraintModeVar(classp);
// Use correct mode variable based on whether constraint is static
AstVar* const constraintModeVarp = (constrp && constrp->isStatic())
? getStaticConstraintModeVar(classp)
: getConstraintModeVar(classp);
AstNodeExpr* const lhsp
= makeModeAssignLhs(nodep->fileline(), classp, fromp, constraintModeVarp);
replaceWithModeAssign(nodep, constrp, lhsp);

View File

@ -4,13 +4,18 @@
# This program is free software; you can redistribute it and/or modify it
# under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0.
# SPDX-FileCopyrightText: 2024 Wilson Snyder
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
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

@ -0,0 +1,70 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2026 by PlanV GmbH.
// SPDX-License-Identifier: CC0-1.0
// Test static constraint_mode() support per IEEE 1800-2017 Section 18.4, 18.8
// Static constraint mode should be shared across all instances.
class StaticConstraintTest;
rand bit [7:0] value;
// Static constraint - shared across all instances
static constraint static_con {
value inside {[10:15]};
}
// Non-static constraint for comparison
constraint instance_con {
value > 5;
}
endclass
module t;
StaticConstraintTest obj1, obj2;
initial begin
obj1 = new();
obj2 = new();
// Test 1: Verify static constraint_mode getter works
if (obj1.static_con.constraint_mode() != 1) $stop;
if (obj2.static_con.constraint_mode() != 1) $stop;
// Test 2: Disable static constraint on one instance
obj1.static_con.constraint_mode(0);
// Verify the state is shared across all instances
if (obj1.static_con.constraint_mode() != 0) $stop;
if (obj2.static_con.constraint_mode() != 0) $stop;
// Test 3: Re-enable static constraint via different instance
obj2.static_con.constraint_mode(1);
// Verify state is updated for all instances
if (obj1.static_con.constraint_mode() != 1) $stop;
if (obj2.static_con.constraint_mode() != 1) $stop;
// Test 4: Verify randomization respects constraint mode when enabled
obj1.static_con.constraint_mode(1);
obj1.instance_con.constraint_mode(1);
for (int i = 0; i < 10; i++) begin
void'(obj1.randomize());
if (!(obj1.value inside {[10:15]})) $stop;
if (!(obj1.value > 5)) $stop;
end
// Test 5: Disable static constraint and verify randomization changes
obj1.static_con.constraint_mode(0);
// With static_con disabled, value only needs to be > 5
for (int i = 0; i < 10; i++) begin
obj1.value = 1; // Reset to low value
void'(obj1.randomize());
if (!(obj1.value > 5)) $stop;
end
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -1,14 +0,0 @@
%Error-UNSUPPORTED: t/t_constraint_mode_unsup.v:17:55: Unsupported: 'constraint_mode()' on static constraint
: ... note: In instance 't'
17 | $display("p.cons.constraint_mode()=%0d", p.cons.constraint_mode());
| ^~~~~~~~~~~~~~~
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error-UNSUPPORTED: t/t_constraint_mode_unsup.v:18:14: Unsupported: 'constraint_mode()' on static constraint
: ... note: In instance 't'
18 | p.cons.constraint_mode(0);
| ^~~~~~~~~~~~~~~
%Error-UNSUPPORTED: t/t_constraint_mode_unsup.v:19:9: Unsupported: 'constraint_mode()' on static constraint: 'cons'
: ... note: In instance 't'
19 | p.constraint_mode(0);
| ^~~~~~~~~~~~~~~
%Error: Exiting due to

View File

@ -1,21 +0,0 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain
// SPDX-FileCopyrightText: 2024 Antmicro
// SPDX-License-Identifier: CC0-1.0
class Packet;
int m_one;
static constraint cons { m_one > 0 && m_one < 2; }
endclass
module t;
Packet p;
initial begin
p = new;
$display("p.cons.constraint_mode()=%0d", p.cons.constraint_mode());
p.cons.constraint_mode(0);
p.constraint_mode(0);
end
endmodule