mirror of
https://github.com/verilator/verilator.git
synced 2026-10-02 16:13:34 +02:00
Fix enums inside randomize() with (#8517)
Signed-off-by: Kornel Uriasz <[email protected]>
This commit is contained in:
+55
-76
@@ -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{
|
||||
|
||||
Executable
+21
@@ -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()
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user