diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index ec149a650..7c7f94186 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -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 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 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{ diff --git a/test_regress/t/t_randomize_with_enum.py b/test_regress/t/t_randomize_with_enum.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_randomize_with_enum.py @@ -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() diff --git a/test_regress/t/t_randomize_with_enum.v b/test_regress/t/t_randomize_with_enum.v new file mode 100644 index 000000000..f37e21188 --- /dev/null +++ b/test_regress/t/t_randomize_with_enum.v @@ -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