Fix enums inside randomize() with (#8517)

Signed-off-by: Kornel Uriasz <[email protected]>
This commit is contained in:
Kornel Uriasz
2026-09-29 10:01:43 -04:00
committed by GitHub
parent 58fc34e6df
commit 38f011ed2c
3 changed files with 153 additions and 76 deletions
+55 -76
View File
@@ -89,6 +89,20 @@ static AstVar* getRandomGenerator(AstClass* const classp) {
return nullptr;
}
// Create __Vsetup_constraints function if non existant inside memberMap,
// if existant return one already present
static AstTask* getCreateConstraintSetupFunc(AstClass* classp, VMemberMap& memberMap) {
static const char* const name = "__Vsetup_constraints";
AstTask* setupAllTaskp = VN_AS(memberMap.findMember(classp, name), Task);
if (setupAllTaskp) return setupAllTaskp;
setupAllTaskp = new AstTask{classp->fileline(), "__Vsetup_constraints", nullptr};
setupAllTaskp->classMethod(true);
setupAllTaskp->isVirtual(true);
classp->addMembersp(setupAllTaskp);
memberMap.insert(classp, setupAllTaskp);
return setupAllTaskp;
}
// ######################################################################
// Establishes the target of a rand_mode() call
@@ -792,6 +806,7 @@ class ConstraintExprVisitor final : public VNVisitor {
AstNodeFTask* m_prepareConstrainedArraysp = nullptr; // Grow arrays for indexed struct access
uint32_t* m_uniqueConstraintId = nullptr; // Current ID of unique call
V3UniqueNames& m_uniqueNames; // Unique names of temporaries, and of blocks holding them
std::vector<AstVar*> m_rangeConstrainedEnums; // Enums that are already range-constrained
AstNode* m_firstExpressionInsideIndexp = nullptr;
class NestedAccessPath final {
@@ -879,6 +894,26 @@ class ConstraintExprVisitor final : public VNVisitor {
};
NestedAccessPath* m_nestedAccess = nullptr; // Indicates state of nested access
// Emit enum range hard constraint for a single variable
AstNodeExpr* createEnumConstraint(FileLine* const fl, const std::string& smtName,
AstEnumDType* const enumDtp, AstVar* const genVarp) {
AstNodeModule* const genModp = VN_AS(genVarp->user2p(), NodeModule);
const int width = enumDtp->width();
std::string constraint = "(__Vbv (or";
for (AstEnumItem* itemp = enumDtp->itemsp(); itemp;
itemp = VN_AS(itemp->nextp(), EnumItem)) {
const AstConst* const vconstp = VN_AS(itemp->valuep(), Const);
constraint += " (= " + smtName + " (_ bv" + cvtToStr(vconstp->toUInt()) + " "
+ cvtToStr(width) + "))";
}
constraint += "))";
AstCMethodHard* const callp = new AstCMethodHard{
fl, new AstVarRef{fl, genModp, genVarp, VAccess::READWRITE}, VCMethod::RANDOMIZER_HARD,
new AstCExpr{fl, AstCExpr::Pure{}, "\"" + constraint + "\""}};
callp->dtypeSetVoid();
return callp;
};
// Routes nested sub-objects with static rand vars when the outer class has none.
AstVar* findStaticRandModeVarMember(AstClass* classp) const {
while (true) {
@@ -1745,6 +1780,22 @@ class ConstraintExprVisitor final : public VNVisitor {
const RandomizeMode randMode = {.asUQuad = varp->user1()};
if (!randMode.usesMode && editFormat(nodep)) return;
AstEnumDType* enumDtp = VN_CAST(nodep->dtypep()->skipRefToEnump(), EnumDType);
if (enumDtp
&& (std::find(m_rangeConstrainedEnums.begin(), m_rangeConstrainedEnums.end(), varp)
== m_rangeConstrainedEnums.end())) {
AstVar* genVarp = m_genp;
if (m_classp && m_classp->user3p()) genVarp = VN_AS(m_classp->user3p(), Var);
UASSERT_OBJ(genVarp, nodep, "No 'randomize' variable in m_genp or m_classp");
AstNodeExpr* enumRangeExprp
= createEnumConstraint(nodep->fileline(), varp->name(), enumDtp, genVarp);
AstNodeFTask* targetTaskp = m_inlineInitTaskp;
if (!targetTaskp) targetTaskp = getCreateConstraintSetupFunc(m_classp, m_memberMap);
UASSERT_OBJ(targetTaskp, nodep, "No function to inline enum range constraint into");
targetTaskp->addStmtsp(enumRangeExprp->makeStmt());
m_rangeConstrainedEnums.push_back(varp);
}
VNRelinker relinker;
nodep->unlinkFrBack(&relinker);
AstNodeExpr* const exprp
@@ -3862,17 +3913,6 @@ class RandomizeVisitor final : public VNVisitor {
}
return it->second;
}
AstTask* getCreateConstraintSetupFunc(AstClass* classp) {
static constexpr const char* const name = "__Vsetup_constraints";
AstTask* setupAllTaskp = VN_AS(m_memberMap.findMember(classp, name), Task);
if (setupAllTaskp) return setupAllTaskp;
setupAllTaskp = new AstTask{classp->fileline(), "__Vsetup_constraints", nullptr};
setupAllTaskp->classMethod(true);
setupAllTaskp->isVirtual(true);
classp->addMembersp(setupAllTaskp);
m_memberMap.insert(classp, setupAllTaskp);
return setupAllTaskp;
}
AstTask* createPrepareConstrainedArraysTask(AstClass* const classp) {
static const char* const name = "__Vprepare_constrained_arrays";
AstTask* const taskp = new AstTask{classp->fileline(), name, nullptr};
@@ -5142,7 +5182,7 @@ class RandomizeVisitor final : public VNVisitor {
} else {
AstNodeModule* const genModp = VN_AS(classGenp->user2p(), NodeModule);
funcp->addStmtsp(implementConstraintsClear(fl, classGenp));
AstTask* const setupAllTaskp = getCreateConstraintSetupFunc(classp);
AstTask* const setupAllTaskp = getCreateConstraintSetupFunc(classp, m_memberMap);
funcp->addStmtsp((new AstTaskRef{fl, setupAllTaskp})->makeStmt());
AstCExpr* const solverCallp = new AstCExpr{fl};
solverCallp->dtypeSetBit();
@@ -5888,7 +5928,7 @@ class RandomizeVisitor final : public VNVisitor {
nodep->foreachMember([&](AstClass* const, AstConstraint* const constrp) {
maxDepth = std::max(maxDepth, constraintDepth(constrp));
});
AstTask* const setupAllTaskp = getCreateConstraintSetupFunc(nodep);
AstTask* const setupAllTaskp = getCreateConstraintSetupFunc(nodep, m_memberMap);
for (int d = maxDepth; d >= 0; --d) {
nodep->foreachMember(
[&](AstClass* const classp, AstConstraint* const constrp) {
@@ -5939,72 +5979,11 @@ class RandomizeVisitor final : public VNVisitor {
}
randomizep->addStmtsp(implementConstraintsClear(fl, genp));
// Restrict enum variables in solver to valid members only
{
AstNodeModule* const genModp = VN_AS(genp->user2p(), NodeModule);
// Emit enum range hard constraint for a single variable
const auto emitEnumConstraint = [&](const std::string& smtName,
AstEnumDType* const enumDtp) {
const int width = enumDtp->width();
std::string constraint = "(__Vbv (or";
for (AstEnumItem* itemp = enumDtp->itemsp(); itemp;
itemp = VN_AS(itemp->nextp(), EnumItem)) {
const AstConst* const vconstp = VN_AS(itemp->valuep(), Const);
constraint += " (= " + smtName + " (_ bv" + cvtToStr(vconstp->toUInt())
+ " " + cvtToStr(width) + "))";
}
constraint += "))";
AstCMethodHard* const callp = new AstCMethodHard{
fl, new AstVarRef{fl, genModp, genp, VAccess::READWRITE},
VCMethod::RANDOMIZER_HARD,
new AstCExpr{fl, AstCExpr::Pure{}, "\"" + constraint + "\""}};
callp->dtypeSetVoid();
randomizep->addStmtsp(callp->makeStmt());
};
// Recursively emit enum constraints for sub-object members
std::function<void(AstClass*, const std::string&)> addSubObjEnumConstraints
= [&](AstClass* classp, const std::string& pathPrefix) {
classp->foreachMember([&](AstClass*, AstVar* subVarp) {
if (!subVarp->rand().isRandomizable()) return;
const std::string smtName = pathPrefix + "." + subVarp->name();
AstEnumDType* const enumDtp
= VN_CAST(subVarp->dtypep()->skipRefToEnump(), EnumDType);
if (enumDtp) {
// Do not emit when enum isn't constrained
if (!subVarp->user3()) return;
emitEnumConstraint(smtName, enumDtp);
return;
}
if (!subVarp->globalConstrained()) return;
const AstNodeDType* const subDtypep = subVarp->dtypep()->skipRefp();
const AstClassRefDType* const subClassRefp
= VN_CAST(subDtypep, ClassRefDType);
if (!subClassRefp) return;
addSubObjEnumConstraints(subClassRefp->classp(), smtName);
});
};
nodep->foreachMember([&](AstClass*, AstVar* memberVarp) {
// Direct enum members
if (memberVarp->user3()) {
AstEnumDType* const enumDtp
= VN_CAST(memberVarp->dtypep()->skipRefToEnump(), EnumDType);
if (enumDtp) emitEnumConstraint(memberVarp->name(), enumDtp);
}
// Enum members inside globalConstrained sub-objects
if (memberVarp->globalConstrained()) {
const AstNodeDType* const dtypep = memberVarp->dtypep()->skipRefp();
const AstClassRefDType* const classRefp = VN_CAST(dtypep, ClassRefDType);
if (!classRefp) return;
addSubObjEnumConstraints(classRefp->classp(), memberVarp->name());
}
});
}
const auto sizeArraysIt = m_sizeConstrainedArrays.find(nodep);
if (sizeArraysIt != m_sizeConstrainedArrays.end()) {
for (AstVar* const arrVarp : sizeArraysIt->second) sizeArrayVars.insert(arrVarp);
}
AstTask* setupAllTaskp = getCreateConstraintSetupFunc(nodep);
AstTask* setupAllTaskp = getCreateConstraintSetupFunc(nodep, m_memberMap);
if (!sizeArrayVars.empty() && prepareArraysTaskp->stmtsp()) {
AstNodeStmt* const preparep = (new AstTaskRef{fl, prepareArraysTaskp})->makeStmt();
// A size-constrained array always has a preceding write_var registration.
@@ -6488,7 +6467,7 @@ class RandomizeVisitor final : public VNVisitor {
// Copy (derive) class constraints if present
if (classGenp) {
AstTask* const constrSetupFuncp = getCreateConstraintSetupFunc(classp);
AstTask* const constrSetupFuncp = getCreateConstraintSetupFunc(classp, m_memberMap);
AstTaskRef* const callp = new AstTaskRef{nodep->fileline(), constrSetupFuncp};
randomizeFuncp->addStmtsp(callp->makeStmt());
randomizeFuncp->addStmtsp(new AstAssign{
+21
View File
@@ -0,0 +1,21 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# 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: 2026 Wilson Snyder
# 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()
+77
View File
@@ -0,0 +1,77 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain
// SPDX-FileCopyrightText: 2026 Antmicro
// SPDX-License-Identifier: CC0-1.0
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0);
`define checkinrange(gotv,minv,maxv) do if (((minv) > (gotv)) || ((maxv) < (gotv))) begin $write("%%Error: %s:%0d: got=%0d min=%0d max=%0d\n", `__FILE__,`__LINE__, (gotv), (minv), (maxv)); `stop; end while(0);
// verilog_format: on
typedef enum bit[3:0] {
ONE,
TWO,
THREE,
FOUR,
FIVE
} enum_t;
class withConstraint;
rand enum_t e;
constraint c{
e != ONE;
};
endclass
class withoutConstraint;
rand enum_t e;
rand enum_t f;
endclass
module t;
withConstraint wc;
withoutConstraint woc;
initial begin
int rand_result;
wc = new;
woc = new;
repeat (20) begin
rand_result = wc.randomize();
`checkd(rand_result, 1);
`checkinrange(int'(wc.e), int'(TWO), int'(FIVE));
rand_result = woc.randomize();
`checkd(rand_result, 1);
`checkinrange(int'(woc.e), int'(ONE), int'(FIVE));
rand_result = (wc.randomize() with {
e != TWO;
});
`checkd(rand_result, 1);
`checkinrange(int'(wc.e), int'(THREE), int'(FIVE));
rand_result = (woc.randomize() with {
e != ONE;
});
`checkd(rand_result, 1);
`checkinrange(int'(woc.e), int'(TWO), int'(FIVE));
`checkinrange(int'(woc.f), int'(ONE), int'(FIVE));
rand_result = (woc.randomize() with {
e != ONE;
f != ONE;
});
`checkd(rand_result, 1);
`checkinrange(int'(woc.e), int'(TWO), int'(FIVE));
`checkinrange(int'(woc.f), int'(TWO), int'(FIVE));
end
$write("*-* all finished *-*\n");
$finish;
end
endmodule