diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index ef0723b2a..a2ab48398 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -3397,7 +3397,8 @@ class RandomizeVisitor final : public VNVisitor { m_dynarrayDtp->dtypep(m_dynarrayDtp); v3Global.rootp()->typeTablep()->addTypesp(m_dynarrayDtp); } - AstVar* const modeVarp = new AstVar{fl, VVarType::MODULETEMP, name, m_dynarrayDtp}; + // MEMBER, not MODULETEMP, so V3Localize does not localize it into new(). + AstVar* const modeVarp = new AstVar{fl, VVarType::MEMBER, name, m_dynarrayDtp}; modeVarp->user2p(classp); classp->addStmtsp(modeVarp); return modeVarp; @@ -3418,6 +3419,9 @@ class RandomizeVisitor final : public VNVisitor { // to point to the package scope when the variable is moved by V3Class. modeVarp->user2p(classp); classp->addStmtsp(modeVarp); + // Register so findStaticRandModeVarMember sees it through the already + // scanned member cache + m_memberMap.insert(classp, modeVarp); return modeVarp; } static void addSetRandMode(AstNodeFTask* const ftaskp, AstVar* const genp, @@ -4536,6 +4540,46 @@ class RandomizeVisitor final : public VNVisitor { return new AstConstraintIf{fl, condp, thenBodyp, nullptr}; } + struct DistBucket final { + AstNodeExpr* rangep; // A single value or an InsideRange + AstNodeExpr* weightExprp; // Effective 64-bit weight (range weight scaled by size) + }; + + // Non-zero-weight buckets with each weight extended to 64 bits. + std::vector collectDistBuckets(AstDist* distp) { + FileLine* const fl = distp->fileline(); + std::vector buckets; + for (AstDistItem* ditemp = distp->itemsp(); ditemp; + ditemp = VN_AS(ditemp->nextp(), DistItem)) { + if (const AstConst* const constp = VN_CAST(ditemp->weightp(), Const)) { + if (constp->toUQuad() == 0) continue; + } + AstNodeExpr* weightExprp + = new AstExtend{fl, ditemp->weightp()->cloneTreePure(false), 64}; + // := on a range weights every element, so scale by the range size. + if (!ditemp->isWhole()) { + if (const AstInsideRange* const irp = VN_CAST(ditemp->rangep(), InsideRange)) { + const AstConst* const lop = VN_CAST(irp->lhsp(), Const); + const AstConst* const hip = VN_CAST(irp->rhsp(), Const); + AstNodeExpr* rangeSizep; + if (lop && hip) { + const uint64_t rangeSize = hip->toUQuad() - lop->toUQuad() + 1; + rangeSizep = new AstConst{fl, AstConst::Unsized64{}, rangeSize}; + } else { + rangeSizep = new AstAdd{ + fl, new AstConst{fl, AstConst::Unsized64{}, 1}, + new AstSub{fl, + new AstExtend{fl, irp->rhsp()->cloneTreePure(false), 64}, + new AstExtend{fl, irp->lhsp()->cloneTreePure(false), 64}}}; + } + weightExprp = new AstMul{fl, weightExprp, rangeSizep}; + } + } + buckets.push_back({ditemp->rangep(), weightExprp}); + } + return buckets; + } + static bool distBoundRefsRandVar(const AstNode* boundp) { bool found = false; boundp->foreach([&](const AstVarRef* vrefp) { @@ -4569,9 +4613,200 @@ class RandomizeVisitor final : public VNVisitor { return andp; } + // Membership test for one bucket: a range comparison or an equality. + static AstNodeExpr* newDistMembershipTerm(AstDist* distp, AstNodeExpr* rangep) { + if (const AstInsideRange* const irp = VN_CAST(rangep, InsideRange)) { + return newDistRangeMembership(distp, irp); + } + FileLine* const fl = distp->fileline(); + AstNodeExpr* const eqExprp = distp->exprp()->cloneTreePure(false); + eqExprp->user1(true); + AstNodeExpr* const eqp = new AstEq{fl, eqExprp, rangep->cloneTreePure(false)}; + eqp->user1(true); + return eqp; + } + + // Hard constraint that the dist value stays inside the union of its ranges + // (IEEE 1800-2023 18.5.3: values outside the set must never appear). + AstConstraintExpr* buildDistMembership(AstDist* distp, + const std::vector& buckets) { + FileLine* const fl = distp->fileline(); + AstNodeExpr* unionExprp = nullptr; + for (const auto& bucket : buckets) { + AstNodeExpr* const memberp = newDistMembershipTerm(distp, bucket.rangep); + if (!unionExprp) { + unionExprp = memberp; + } else { + unionExprp = new AstLogOr{fl, memberp, unionExprp}; + unionExprp->user1(true); + } + } + return new AstConstraintExpr{fl, unionExprp}; + } + + // Weighted chain while any value-source variable is active, membership once + // all are frozen. + AstNode* gateFrozenDist(AstNode* chainp, AstDist* distp, + const std::vector& buckets, AstVar* randModeVarp) { + FileLine* const fl = distp->fileline(); + AstNodeExpr* gatep = nullptr; + std::unordered_set gatedVars; + // Handles are never re-pointed by randomize(), only read through. + std::unordered_set handleVars; + distp->exprp()->foreach([&](const AstMemberSel* mselp) { + mselp->fromp()->foreach( + [&](const AstNodeVarRef* hrefp) { handleVars.insert(hrefp->varp()); }); + }); + const auto addModeGate = [&](AstNodeExpr* modeArrayp, uint32_t index) { + AstCMethodHard* const atp + = new AstCMethodHard{fl, modeArrayp, VCMethod::ARRAY_AT, new AstConst{fl, index}}; + atp->dtypeSetUInt32(); + if (gatep) { + gatep = new AstLogOr{fl, gatep, atp}; + gatep->dtypeSetBit(); + } else { + gatep = atp; + } + }; + const auto staticModeRead = [&](AstVar* varp) -> AstNodeExpr* { + AstClass* const ownerp = VN_CAST(varp->user2p(), Class); + AstVar* const smodep = ownerp ? getStaticRandModeVar(ownerp) : nullptr; + if (!smodep) return nullptr; + return new AstVarRef{fl, VN_AS(smodep->user2p(), NodeModule), smodep, VAccess::READ}; + }; + // Each direct, static, or sub-object member reference carries a mode bit. + // A rand variable without a mode bit can never freeze, so no gate is needed. + bool alwaysActive = false; + distp->exprp()->foreach([&](const AstNode* nodep) { + const AstNodeVarRef* const refp = VN_CAST(nodep, NodeVarRef); + const AstMemberSel* const mselp = VN_CAST(nodep, MemberSel); + if (!refp && !mselp) return; + AstVar* const varp = refp ? refp->varp() : mselp->varp(); + if (refp && handleVars.count(varp)) return; + if (!varp->rand().isRandomizable()) return; + const RandomizeMode rmode = {.asInt = varp->user1()}; + if (!rmode.usesMode) { + alwaysActive = true; + return; + } + if (!gatedVars.insert(varp).second) return; + if (varp->lifetime().isStatic()) { + if (AstNodeExpr* const readp = staticModeRead(varp)) { + addModeGate(readp, rmode.index); + } + } else if (mselp) { + AstVar* const memberModep + = getRandModeVarFromClass(VN_AS(varp->user2p(), NodeModule)); + if (!memberModep) return; + AstMemberSel* const modeSelp + = new AstMemberSel{fl, mselp->fromp()->cloneTreePure(false), memberModep}; + addModeGate(modeSelp, rmode.index); + } else if (randModeVarp) { + addModeGate(new AstVarRef{fl, VN_AS(randModeVarp->user2p(), NodeModule), + randModeVarp, VAccess::READ}, + rmode.index); + } + }); + if (alwaysActive || !gatep) { + if (gatep) VL_DO_DANGLING(pushDeletep(gatep), gatep); + return chainp; + } + + AstNodeExpr* unionExprp = nullptr; + for (auto& bucket : buckets) { + AstNodeExpr* termp = newDistMembershipTerm(distp, bucket.rangep); + // A weight that is zero only at runtime excludes the bucket's values too. + const bool runtimeWeight + = bucket.weightExprp->exists([](const AstNodeVarRef*) { return true; }); + if (runtimeWeight) { + AstNeq* const nzp = new AstNeq{fl, bucket.weightExprp->cloneTreePure(false), + new AstConst{fl, AstConst::Unsized64{}, 0}}; + nzp->user1(true); + termp = new AstLogAnd{fl, termp, nzp}; + termp->user1(true); + } + if (unionExprp) { + unionExprp = new AstLogOr{fl, unionExprp, termp}; + unionExprp->user1(true); + } else { + unionExprp = termp; + } + } + return new AstConstraintIf{fl, gatep, chainp, new AstConstraintExpr{fl, unionExprp}}; + } + + AstNodeExpr* newUniformRangePick(AstDist* distp, const AstInsideRange* irp) { + FileLine* const fl = distp->fileline(); + AstNodeExpr* const distExprCopyp = distp->exprp()->cloneTreePure(false); + distExprCopyp->user1(true); + const int distWidth = distp->exprp()->width(); + const AstConst* const lopC = VN_CAST(irp->lhsp(), Const); + const AstConst* const hipC = VN_CAST(irp->rhsp(), Const); + AstNodeExpr* rangeSzp; + if (lopC && hipC) { + const uint64_t rsz = hipC->toUQuad() - lopC->toUQuad() + 1; + rangeSzp = new AstConst{fl, AstConst::Unsized64{}, rsz}; + } else { + const bool isSigned = irp->lhsp()->isSigned(); + AstNodeExpr* const lo64p + = isSigned ? static_cast( + new AstExtendS{fl, irp->lhsp()->cloneTreePure(false), 64}) + : static_cast( + new AstExtend{fl, irp->lhsp()->cloneTreePure(false), 64}); + AstNodeExpr* const hi64p + = isSigned ? static_cast( + new AstExtendS{fl, irp->rhsp()->cloneTreePure(false), 64}) + : static_cast( + new AstExtend{fl, irp->rhsp()->cloneTreePure(false), 64}); + rangeSzp = new AstAdd{fl, new AstConst{fl, AstConst::Unsized64{}, 1ULL}, + new AstSub{fl, hi64p, lo64p}}; + } + AstNodeExpr* const rand64p = new AstRand{fl, nullptr, false}; + rand64p->dtypeSetUInt64(); + AstNodeExpr* const offsetp + = new AstCCast{fl, new AstModDiv{fl, rand64p, rangeSzp}, distWidth}; + AstNodeExpr* const valuep = new AstAdd{fl, irp->lhsp()->cloneTreePure(false), offsetp}; + AstNodeExpr* const eqp = new AstEq{fl, distExprCopyp, valuep}; + eqp->user1(true); + return eqp; + } + + // Soft weighted bucket chain: select a bucket by bucketVar against cumulative weights. + AstNode* buildWeightedBucketChain(AstDist* distp, const std::vector& buckets, + AstVar* bucketVarp, + const std::vector& cumSums) { + FileLine* const fl = distp->fileline(); + AstNode* chainp = nullptr; + for (int i = static_cast(buckets.size()) - 1; i >= 0; --i) { + AstNodeExpr* constraintExprp; + const AstInsideRange* const irp = VN_CAST(buckets[i].rangep, InsideRange); + if (irp && (distBoundRefsRandVar(irp->lhsp()) || distBoundRefsRandVar(irp->rhsp()))) { + constraintExprp = newDistRangeMembership(distp, irp); + } else if (irp) { + constraintExprp = newUniformRangePick(distp, irp); + } else { + AstNodeExpr* const distExprCopyp = distp->exprp()->cloneTreePure(false); + distExprCopyp->user1(true); + constraintExprp + = new AstEq{fl, distExprCopyp, buckets[i].rangep->cloneTreePure(false)}; + constraintExprp->user1(true); + } + AstConstraintExpr* const thenp = new AstConstraintExpr{fl, constraintExprp}; + thenp->isSoft(true); + if (!chainp) { + chainp = thenp; + } else { + AstNodeExpr* const condp + = new AstLte{fl, new AstVarRef{fl, bucketVarp, VAccess::READ}, cumSums[i]}; + chainp = new AstConstraintIf{fl, condp, thenp, chainp}; + } + } + return chainp; + } + // Replace AstDist with weighted bucket selection via AstConstraintIf chain. // Supports both constant and variable weight expressions. - void lowerDistConstraints(AstTask* taskp, AstNode* constrItemsp, + void lowerDistConstraints(AstTask* taskp, AstNode* constrItemsp, AstVar* randModeVarp, AstConstraintForeach* foreachp = nullptr) { // When inside a foreach, bucket preamble stmts are stored in foreachp->user3p() // (as a linked list) so visit(AstConstraintForeach*) can inject them into the @@ -4594,18 +4829,18 @@ class RandomizeVisitor final : public VNVisitor { for (AstNode *nextip, *itemp = constrItemsp; itemp; itemp = nextip) { nextip = itemp->nextp(); - // Recursively handle ConstraintIf nodes (dist can be inside if/else) + // dist can appear inside an if/else or foreach constraint. if (AstConstraintIf* const cifp = VN_CAST(itemp, ConstraintIf)) { - if (cifp->thensp()) // LCOV_EXCL_LINE - lowerDistConstraints(taskp, cifp->thensp(), foreachp); // LCOV_EXCL_LINE - if (cifp->elsesp()) lowerDistConstraints(taskp, cifp->elsesp(), foreachp); + if (cifp->thensp()) { + lowerDistConstraints(taskp, cifp->thensp(), randModeVarp, foreachp); + } + if (cifp->elsesp()) { + lowerDistConstraints(taskp, cifp->elsesp(), randModeVarp, foreachp); + } continue; } - - // Recursively handle ConstraintForeach nodes (dist can be inside foreach) if (AstConstraintForeach* const cfep = VN_CAST(itemp, ConstraintForeach)) { - if (cfep->bodyp()) // LCOV_EXCL_LINE - lowerDistConstraints(taskp, cfep->bodyp(), cfep); // LCOV_EXCL_LINE + if (cfep->bodyp()) lowerDistConstraints(taskp, cfep->bodyp(), randModeVarp, cfep); continue; } @@ -4625,7 +4860,7 @@ class RandomizeVisitor final : public VNVisitor { AstConstraintIf* const liftedp = liftLogIfChainToConstraintIf(topLogIfp); constrExprp->replaceWith(liftedp); VL_DO_DANGLING(pushDeletep(constrExprp), constrExprp); - lowerDistConstraints(taskp, liftedp->thensp(), foreachp); + lowerDistConstraints(taskp, liftedp->thensp(), randModeVarp, foreachp); continue; } } @@ -4635,48 +4870,7 @@ class RandomizeVisitor final : public VNVisitor { FileLine* const fl = distp->fileline(); - struct BucketInfo final { - AstNodeExpr* rangep; - AstNodeExpr* weightExprp; // Effective weight as AST expression - }; - std::vector buckets; - - for (AstDistItem* ditemp = distp->itemsp(); ditemp; - ditemp = VN_AS(ditemp->nextp(), DistItem)) { - // Skip compile-time zero weights - if (const AstConst* const constp = VN_CAST(ditemp->weightp(), Const)) { - if (constp->toUQuad() == 0) continue; - } - - // Clone and extend weight to 64-bit - AstNodeExpr* weightExprp - = new AstExtend{fl, ditemp->weightp()->cloneTreePure(false), 64}; - - // := is per-value weight; for ranges multiply by range size - if (!ditemp->isWhole()) { - if (const AstInsideRange* const irp = VN_CAST(ditemp->rangep(), InsideRange)) { - const AstConst* const lop = VN_CAST(irp->lhsp(), Const); - const AstConst* const hip = VN_CAST(irp->rhsp(), Const); - AstNodeExpr* rangeSizep; - if (lop && hip) { - const uint64_t rangeSize = hip->toUQuad() - lop->toUQuad() + 1; - rangeSizep = new AstConst{fl, AstConst::Unsized64{}, rangeSize}; - } else { - // Variable range bounds: (hi - lo + 1) at runtime - rangeSizep = new AstAdd{ - fl, new AstConst{fl, AstConst::Unsized64{}, 1}, - new AstSub{ - fl, new AstExtend{fl, irp->rhsp()->cloneTreePure(false), 64}, - new AstExtend{fl, irp->lhsp()->cloneTreePure(false), 64}}}; - rangeSizep->dtypeSetUInt64(); - } - weightExprp = new AstMul{fl, weightExprp, rangeSizep}; - weightExprp->dtypeSetUInt64(); - } - } - - buckets.push_back({ditemp->rangep(), weightExprp}); - } + std::vector buckets = collectDistBuckets(distp); if (buckets.empty()) { // All weights are zero: dist is vacuously true (unconstrained) @@ -4687,28 +4881,7 @@ class RandomizeVisitor final : public VNVisitor { continue; } - // IEEE 1800-2023 18.5.3: values not in the distribution must never appear. - // Build the union of all non-zero-weight ranges as a single hard ConstraintExpr - AstNodeExpr* unionExprp = nullptr; - for (const auto& bucket : buckets) { - AstNodeExpr* memberp; - if (const AstInsideRange* const irp = VN_CAST(bucket.rangep, InsideRange)) { - memberp = newDistRangeMembership(distp, irp); - } else { - // distExpr == val - AstNodeExpr* const distExprCopyp = distp->exprp()->cloneTreePure(false); - distExprCopyp->user1(true); - memberp = new AstEq{fl, distExprCopyp, bucket.rangep->cloneTreePure(false)}; - } - memberp->user1(true); - if (!unionExprp) { - unionExprp = memberp; - } else { - unionExprp = new AstLogOr{fl, memberp, unionExprp}; - unionExprp->user1(true); - } - } - AstConstraintExpr* const membershipp = new AstConstraintExpr{fl, unionExprp}; + AstConstraintExpr* const membershipp = buildDistMembership(distp, buckets); // Build totalWeight expression: w[0] + w[1] + ... + w[N-1] AstNodeExpr* totalWeightExprp = nullptr; @@ -4767,79 +4940,9 @@ class RandomizeVisitor final : public VNVisitor { cumSums.push_back(runningSump->cloneTreePure(true)); } - // Build ConstraintIf chain backward (last bucket is unconditional default) - AstNode* chainp = nullptr; - for (int i = static_cast(buckets.size()) - 1; i >= 0; --i) { - AstNodeExpr* constraintExprp; - const AstInsideRange* const irp = VN_CAST(buckets[i].rangep, InsideRange); - if (irp - && (distBoundRefsRandVar(irp->lhsp()) || distBoundRefsRandVar(irp->rhsp()))) { - // Bounds solved concurrently cannot pin a pre-solve value; softly - // prefer the symbolic range so the hard membership stays satisfiable - constraintExprp = newDistRangeMembership(distp, irp); - } else if (irp) { - // Pick distExpr = lo + rand64() % (hi - lo + 1) for a uniform value in range - AstNodeExpr* const distExprCopyp = distp->exprp()->cloneTreePure(false); - distExprCopyp->user1(true); - const int distWidth = distp->exprp()->width(); - // Compute range size in 64-bit to avoid overflow - const AstConst* const lopC = VN_CAST(irp->lhsp(), Const); - const AstConst* const hipC = VN_CAST(irp->rhsp(), Const); - AstNodeExpr* rangeSzp; - if (lopC && hipC) { - const uint64_t rsz = hipC->toUQuad() - lopC->toUQuad() + 1; - rangeSzp = new AstConst{fl, AstConst::Unsized64{}, rsz}; - } else { - const bool isSigned = irp->lhsp()->isSigned(); - AstNodeExpr* const lo64p - = isSigned - ? static_cast( - new AstExtendS{fl, irp->lhsp()->cloneTreePure(false), 64}) - : static_cast( - new AstExtend{fl, irp->lhsp()->cloneTreePure(false), 64}); - lo64p->dtypeSetUInt64(); - AstNodeExpr* const hi64p - = isSigned - ? static_cast( - new AstExtendS{fl, irp->rhsp()->cloneTreePure(false), 64}) - : static_cast( - new AstExtend{fl, irp->rhsp()->cloneTreePure(false), 64}); - hi64p->dtypeSetUInt64(); - rangeSzp = new AstAdd{fl, new AstConst{fl, AstConst::Unsized64{}, 1ULL}, - new AstSub{fl, hi64p, lo64p}}; - } - AstNodeExpr* const rand64p = new AstRand{fl, nullptr, false}; - rand64p->dtypeSetUInt64(); - // offset = rand64() % rangeSize (result in [0, rangeSize-1]) - AstNodeExpr* const offset64p = new AstModDiv{fl, rand64p, rangeSzp}; - // Truncate offset to dist expression width, then add lo - AstNodeExpr* const offsetp = new AstCCast{fl, offset64p, distWidth}; - AstNodeExpr* const lop = irp->lhsp()->cloneTreePure(false); - AstNodeExpr* const valuep = new AstAdd{fl, lop, offsetp}; - valuep->dtypeFrom(distp->exprp()); - constraintExprp = new AstEq{fl, distExprCopyp, valuep}; - constraintExprp->user1(true); - } else { - AstNodeExpr* const distExprCopyp = distp->exprp()->cloneTreePure(false); - distExprCopyp->user1(true); - constraintExprp - = new AstEq{fl, distExprCopyp, buckets[i].rangep->cloneTreePure(false)}; - constraintExprp->user1(true); - } + AstNode* chainp = buildWeightedBucketChain(distp, buckets, bucketVarp, cumSums); - AstConstraintExpr* const thenp = new AstConstraintExpr{fl, constraintExprp}; - // Per IEEE 18.5.3: weights are a preference, not a hard constraint. - // The solver may discard this when it conflicts with other constraints. - thenp->isSoft(true); - - if (!chainp) { - chainp = thenp; - } else { - AstNodeExpr* const condp - = new AstLte{fl, new AstVarRef{fl, bucketVarp, VAccess::READ}, cumSums[i]}; - chainp = new AstConstraintIf{fl, condp, thenp, chainp}; - } - } + chainp = gateFrozenDist(chainp, distp, buckets, randModeVarp); if (chainp) { constrExprp->replaceWith(chainp); @@ -4935,7 +5038,9 @@ class RandomizeVisitor final : public VNVisitor { } if (constrp->itemsp()) expandUniqueElementList(constrp->itemsp()); - if (constrp->itemsp()) lowerDistConstraints(taskp, constrp->itemsp()); + if (constrp->itemsp()) { + lowerDistConstraints(taskp, constrp->itemsp(), randModeVarp); + } std::set& sizeArrays = m_sizeConstrainedArrays[classp]; ConstraintExprVisitor{classp, m_memberMap, constrp->itemsp(), nullptr, genp, randModeVarp, diff --git a/test_regress/t/t_constraint_dist_randmode_frozen.py b/test_regress/t/t_constraint_dist_randmode_frozen.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_constraint_dist_randmode_frozen.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_constraint_dist_randmode_frozen.v b/test_regress/t/t_constraint_dist_randmode_frozen.v new file mode 100644 index 000000000..7971a2672 --- /dev/null +++ b/test_regress/t/t_constraint_dist_randmode_frozen.v @@ -0,0 +1,473 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// 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); +// verilog_format: on + +// A frozen dist variable degrades to a membership test (IEEE 1800-2023 18.5.3). +typedef int unsigned uint; + +class Base; + rand uint delay; + rand uint x; + constraint delay_c {delay inside {[1 : 100]};} + function int rd(); + return this.randomize(delay); + endfunction +endclass + +class SingleVals extends Base; + constraint c { + x dist { + 5 := 99, + 1000000 := 1 + }; + } +endclass + +class RangeVals extends Base; + constraint c { + x dist { + [1 : 10] := 1, + [1000 : 2000] := 9 + }; + } +endclass + +class MixVals extends Base; + constraint c { + x dist { + 0 := 1, + [100 : 200] := 5, + 9999 := 3 + }; + } +endclass + +class DistInIf extends Base; + rand uint sel; + constraint c { + sel inside {0, 1}; + if (sel == 1) + x dist { + 5 := 9, + 70 := 1 + }; + else + x == 4242; + } +endclass + +class DistInForeach; + rand uint arr[4]; + constraint c { + foreach (arr[i]) + arr[i] dist { + 10 := 9, + 20 := 1 + }; + } +endclass + +class StaticVar; + rand uint delay; + static rand uint sx; + constraint delay_c {delay inside {[1 : 100]};} + constraint c { + sx dist { + 5 := 9, + 70 := 1 + }; + } + function int rd(); + return this.randomize(delay); + endfunction +endclass + +class StaticPlain; // non-dist constraint on a frozen static var + rand uint d; + static rand uint sy; + constraint c {sy inside {[10 : 20]};} +endclass + +class Sub; + rand uint x; +endclass + +class Holder; + rand Sub s; + rand uint delay; + constraint delay_c {delay inside {[1 : 100]};} + constraint c { + s.x dist { + 5 := 9, + 70 := 1 + }; + } + function new(); + s = new; + endfunction +endclass + +class MultiVar; + rand uint x, y; + constraint c { + (x + y) dist { + 10 := 9, + 1000 := 1 + }; + } +endclass + +class DupVar; + rand uint x; + constraint c { + (x + x) dist { + 10 := 9, + 20 := 1 + }; + } +endclass + +class DupMember; + rand Sub s; + constraint c { + (s.x + s.x) dist { + 10 := 9, + 20 := 1 + }; + } + function new(); + s = new; + endfunction +endclass + +class RtWeight; + rand uint x; + uint w; + constraint c { + x dist { + 5 := 9, + 70 := w + }; + } +endclass + +class IdxSel; + rand uint a[4]; + rand uint idx; + constraint ci {idx inside {[0 : 3]};} + constraint c { + a[idx] dist { + 10 := 9, + 20 := 1 + }; + } +endclass + +module t; + int ok; + initial begin + SingleVals sv; + RangeVals rv; + MixVals mv; + StaticVar st; + Holder h; + MultiVar m; + RtWeight r; + DistInIf di; + DistInForeach df; + IdxSel ix; + + // Frozen in the low-weight bucket -> succeeds, value untouched. + sv = new; + ok = sv.randomize(); + `checkd(ok, 1); + sv.x.rand_mode(0); + sv.x = 1000000; + for (int i = 0; i < 8; ++i) begin + ok = sv.rd(); + `checkd(ok, 1); + `checkd(sv.x, 1000000); + if (sv.delay < 1 || sv.delay > 100) `checkd(sv.delay, 0); + end + + // Frozen in the high-weight bucket -> succeeds. + sv = new; + ok = sv.randomize(); + `checkd(ok, 1); + sv.x.rand_mode(0); + sv.x = 5; + for (int i = 0; i < 8; ++i) begin + ok = sv.rd(); + `checkd(ok, 1); + `checkd(sv.x, 5); + end + + // Frozen OUTSIDE the set -> membership violated, randomize fails every call. + sv = new; + ok = sv.randomize(); + `checkd(ok, 1); + sv.x.rand_mode(0); + sv.x = 7; + for (int i = 0; i < 8; ++i) begin + ok = sv.rd(); + `checkd(ok, 0); + `checkd(sv.x, 7); + end + + // Frozen inside a range bucket -> succeeds. + rv = new; + ok = rv.randomize(); + `checkd(ok, 1); + rv.x.rand_mode(0); + rv.x = 5; + for (int i = 0; i < 8; ++i) begin + ok = rv.rd(); + `checkd(ok, 1); + `checkd(rv.x, 5); + end + + // Frozen inside a mixed single+range set -> succeeds. + mv = new; + ok = mv.randomize(); + `checkd(ok, 1); + mv.x.rand_mode(0); + mv.x = 150; + for (int i = 0; i < 8; ++i) begin + ok = mv.rd(); + `checkd(ok, 1); + `checkd(mv.x, 150); + end + + // Active dist var still solves (weighted, never fails on a valid set). + sv = new; + for (int i = 0; i < 8; ++i) begin + ok = sv.randomize(); + `checkd(ok, 1); + if (sv.x != 5 && sv.x != 1000000) `checkd(0, 1); + end + + // Static rand var frozen in the low-weight bucket -> succeeds. + st = new; + ok = st.randomize(); + `checkd(ok, 1); + st.sx.rand_mode(0); + st.sx = 70; + for (int i = 0; i < 8; ++i) begin + ok = st.rd(); + `checkd(ok, 1); + `checkd(st.sx, 70); + ok = st.randomize(); + `checkd(ok, 1); + `checkd(st.sx, 70); + end + + // Static rand var frozen outside the set -> fails every call. + st.sx = 7; + for (int i = 0; i < 8; ++i) begin + ok = st.randomize(); + `checkd(ok, 0); + `checkd(st.sx, 7); + end + + // Frozen static var under a plain constraint: current value decides. + begin + StaticPlain sp; + sp = new; + ok = sp.randomize(); + `checkd(ok, 1); + sp.sy.rand_mode(0); + sp.sy = 15; + for (int i = 0; i < 8; ++i) begin + ok = sp.randomize(); + `checkd(ok, 1); + `checkd(sp.sy, 15); + end + sp.sy = 99; + for (int i = 0; i < 8; ++i) begin + ok = sp.randomize(); + `checkd(ok, 0); + `checkd(sp.sy, 99); + end + end + + // Sub-object member frozen in the low-weight bucket -> succeeds. + h = new; + ok = h.randomize(); + `checkd(ok, 1); + h.s.x.rand_mode(0); + h.s.x = 70; + for (int i = 0; i < 8; ++i) begin + ok = h.randomize(); + `checkd(ok, 1); + `checkd(h.s.x, 70); + end + + // Sub-object member frozen outside the set -> fails every call. + h.s.x = 7; + for (int i = 0; i < 8; ++i) begin + ok = h.randomize(); + `checkd(ok, 0); + `checkd(h.s.x, 7); + end + + // Both the handle and the member frozen. + h = new; + ok = h.randomize(); + `checkd(ok, 1); + h.s.rand_mode(0); + h.s.x.rand_mode(0); + h.s.x = 5; + for (int i = 0; i < 8; ++i) begin + ok = h.randomize(); + `checkd(ok, 1); + `checkd(h.s.x, 5); + end + h.s.x = 7; + for (int i = 0; i < 8; ++i) begin + ok = h.randomize(); + `checkd(ok, 0); + `checkd(h.s.x, 7); + end + + // Multi-var dist expression, one var frozen: the active var must still + // satisfy membership (sum wraps mod 2**32, so both values stay reachable). + m = new; + ok = m.randomize(); + `checkd(ok, 1); + m.x.rand_mode(0); + m.x = 600; + for (int i = 0; i < 8; ++i) begin + ok = m.randomize(); + `checkd(ok, 1); + `checkd(m.x, 600); + if (m.x + m.y != 10 && m.x + m.y != 1000) `checkd(0, 1); + end + + // Both vars frozen: membership of the frozen sum decides. + m.y.rand_mode(0); + m.y = 400; + ok = m.randomize(); + `checkd(ok, 1); + m.y = 500; + for (int i = 0; i < 8; ++i) begin + ok = m.randomize(); + `checkd(ok, 0); + `checkd(m.y, 500); + end + + // Same var twice in the dist expression, frozen: membership of 2*x decides. + begin + DupVar d; + d = new; + ok = d.randomize(); + `checkd(ok, 1); + d.x.rand_mode(0); + d.x = 5; + for (int i = 0; i < 8; ++i) begin + ok = d.randomize(); + `checkd(ok, 1); + `checkd(d.x, 5); + end + d.x = 7; + for (int i = 0; i < 8; ++i) begin + ok = d.randomize(); + `checkd(ok, 0); + `checkd(d.x, 7); + end + end + + // Same sub-object member twice in the dist expression, frozen. + begin + DupMember dm; + dm = new; + ok = dm.randomize(); + `checkd(ok, 1); + dm.s.x.rand_mode(0); + dm.s.x = 5; + for (int i = 0; i < 8; ++i) begin + ok = dm.randomize(); + `checkd(ok, 1); + `checkd(dm.s.x, 5); + end + dm.s.x = 7; + for (int i = 0; i < 8; ++i) begin + ok = dm.randomize(); + `checkd(ok, 0); + `checkd(dm.s.x, 7); + end + end + + // Runtime weight of zero excludes the bucket's values (18.5.3): frozen + // there must fail; nonzero weight on the same frozen value succeeds. + r = new; + r.w = 1; + ok = r.randomize(); + `checkd(ok, 1); + r.x.rand_mode(0); + r.x = 70; + for (int i = 0; i < 8; ++i) begin + ok = r.randomize(); + `checkd(ok, 1); + `checkd(r.x, 70); + end + r.w = 0; + for (int i = 0; i < 8; ++i) begin + ok = r.randomize(); + `checkd(ok, 0); + `checkd(r.x, 70); + end + + // Active var with a runtime-zero weight never draws that bucket. + r = new; + r.w = 0; + for (int i = 0; i < 8; ++i) begin + ok = r.randomize(); + `checkd(ok, 1); + `checkd(r.x, 5); + end + + // dist inside a constraint if: taken branch follows the dist, else pins 4242. + di = new; + for (int i = 0; i < 16; ++i) begin + ok = di.randomize(); + `checkd(ok, 1); + if (di.sel == 1) begin + if (di.x != 5 && di.x != 70) `checkd(0, 1); + end + else begin + `checkd(di.x, 4242); + end + end + + // dist inside a constraint foreach: every element honors the set. + df = new; + for (int i = 0; i < 16; ++i) begin + ok = df.randomize(); + `checkd(ok, 1); + foreach (df.arr[j]) if (df.arr[j] != 10 && df.arr[j] != 20) `checkd(0, 1); + end + + // Frozen index into an active array: a[idx] is still freshly drawn. + ix = new; + ok = ix.randomize(); + `checkd(ok, 1); + ix.idx.rand_mode(0); + ix.idx = 2; + for (int i = 0; i < 16; ++i) begin + ok = ix.randomize(); + `checkd(ok, 1); + `checkd(ix.idx, 2); + if (ix.a[2] != 10 && ix.a[2] != 20) `checkd(ix.a[2], 10); + end + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule