This commit is contained in:
Yilou Wang 2026-07-14 13:30:43 +00:00 committed by GitHub
commit cfd5d5ac00
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 747 additions and 148 deletions

View File

@ -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<DistBucket> collectDistBuckets(AstDist* distp) {
FileLine* const fl = distp->fileline();
std::vector<DistBucket> 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<DistBucket>& 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<DistBucket>& buckets, AstVar* randModeVarp) {
FileLine* const fl = distp->fileline();
AstNodeExpr* gatep = nullptr;
std::unordered_set<const AstVar*> gatedVars;
// Handles are never re-pointed by randomize(), only read through.
std::unordered_set<const AstVar*> 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<AstNodeExpr*>(
new AstExtendS{fl, irp->lhsp()->cloneTreePure(false), 64})
: static_cast<AstNodeExpr*>(
new AstExtend{fl, irp->lhsp()->cloneTreePure(false), 64});
AstNodeExpr* const hi64p
= isSigned ? static_cast<AstNodeExpr*>(
new AstExtendS{fl, irp->rhsp()->cloneTreePure(false), 64})
: static_cast<AstNodeExpr*>(
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<DistBucket>& buckets,
AstVar* bucketVarp,
const std::vector<AstNodeExpr*>& cumSums) {
FileLine* const fl = distp->fileline();
AstNode* chainp = nullptr;
for (int i = static_cast<int>(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<BucketInfo> 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<DistBucket> 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<int>(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<AstNodeExpr*>(
new AstExtendS{fl, irp->lhsp()->cloneTreePure(false), 64})
: static_cast<AstNodeExpr*>(
new AstExtend{fl, irp->lhsp()->cloneTreePure(false), 64});
lo64p->dtypeSetUInt64();
AstNodeExpr* const hi64p
= isSigned
? static_cast<AstNodeExpr*>(
new AstExtendS{fl, irp->rhsp()->cloneTreePure(false), 64})
: static_cast<AstNodeExpr*>(
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<AstVar*>& sizeArrays = m_sizeConstrainedArrays[classp];
ConstraintExprVisitor{classp, m_memberMap, constrp->itemsp(),
nullptr, genp, randModeVarp,

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()

View File

@ -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