Fix dist constraint on a frozen variable failing randomization (#7878)
This commit is contained in:
parent
f22676eea9
commit
b34f685f70
|
|
@ -3487,7 +3487,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;
|
||||
|
|
@ -3508,6 +3509,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,
|
||||
|
|
@ -4627,11 +4631,16 @@ class RandomizeVisitor final : public VNVisitor {
|
|||
}
|
||||
|
||||
static bool distBoundRefsRandVar(const AstNode* boundp) {
|
||||
bool found = false;
|
||||
boundp->foreach([&](const AstVarRef* vrefp) {
|
||||
if (vrefp->varp()->rand().isRandomizable()) found = true;
|
||||
return boundp->exists(
|
||||
[](const AstVarRef* vrefp) { return vrefp->varp()->rand().isRandomizable(); });
|
||||
}
|
||||
|
||||
static bool distBoundRefsModeVar(const AstNode* boundp) {
|
||||
return boundp->exists([](const AstVarRef* vrefp) {
|
||||
if (!vrefp->varp()->rand().isRandomizable()) return false;
|
||||
const RandomizeMode rmode = {.asInt = vrefp->varp()->user1()};
|
||||
return rmode.usesMode;
|
||||
});
|
||||
return found;
|
||||
}
|
||||
|
||||
// (distExpr >= lo) && (distExpr <= hi); signed comparisons for signed vars
|
||||
|
|
@ -4730,6 +4739,175 @@ class RandomizeVisitor final : public VNVisitor {
|
|||
return new AstConstraintExpr{fl, unionExprp};
|
||||
}
|
||||
|
||||
// Gate junction with constants folded and identical terms merged.
|
||||
AstNodeExpr* newGateJoin(FileLine* fl, AstNodeExpr* ap, AstNodeExpr* bp, bool isAnd) {
|
||||
if (ap->sameTree(bp)) {
|
||||
VL_DO_DANGLING(pushDeletep(bp), bp);
|
||||
return ap;
|
||||
}
|
||||
if (VN_IS(bp, Const)) std::swap(ap, bp);
|
||||
if (VN_IS(ap, Const)) {
|
||||
const bool constWins = VN_AS(ap, Const)->isZero() == isAnd;
|
||||
if (!constWins) std::swap(ap, bp);
|
||||
VL_DO_DANGLING(pushDeletep(bp), bp);
|
||||
return ap;
|
||||
}
|
||||
if (isAnd) return new AstLogAnd{fl, ap, bp};
|
||||
return new AstLogOr{fl, ap, bp};
|
||||
}
|
||||
AstNodeExpr* newGateOr(FileLine* fl, AstNodeExpr* ap, AstNodeExpr* bp) {
|
||||
return newGateJoin(fl, ap, bp, false);
|
||||
}
|
||||
AstNodeExpr* newGateAnd(FileLine* fl, AstNodeExpr* ap, AstNodeExpr* bp) {
|
||||
return newGateJoin(fl, ap, bp, true);
|
||||
}
|
||||
|
||||
// The rand_mode bit of varp: static class var, owner object's array via the
|
||||
// member select, or this object's array.
|
||||
AstNodeExpr* newModeBitRead(AstVar* varp, const AstMemberSel* mselp, AstVar* randModeVarp,
|
||||
FileLine* fl) {
|
||||
AstNodeExpr* arrayp = nullptr;
|
||||
if (varp->lifetime().isStatic()) {
|
||||
AstClass* const ownerp = VN_CAST(varp->user2p(), Class);
|
||||
UASSERT_OBJ(ownerp, varp, "usesMode static var without an owning class");
|
||||
AstVar* const smodep = getStaticRandModeVar(ownerp);
|
||||
UASSERT_OBJ(smodep, varp, "usesMode static var without a static rand-mode array");
|
||||
arrayp = new AstVarRef{fl, VN_AS(smodep->user2p(), NodeModule), smodep, VAccess::READ};
|
||||
} else if (mselp) {
|
||||
AstVar* const memberModep = getRandModeVarFromClass(VN_AS(varp->user2p(), NodeModule));
|
||||
UASSERT_OBJ(memberModep, varp, "usesMode member without a class rand-mode array");
|
||||
arrayp = new AstMemberSel{fl, mselp->fromp()->cloneTreePure(false), memberModep};
|
||||
} else {
|
||||
UASSERT_OBJ(randModeVarp, varp, "rand_mode variable without a class mode array");
|
||||
arrayp = new AstVarRef{fl, VN_AS(randModeVarp->user2p(), NodeModule), randModeVarp,
|
||||
VAccess::READ};
|
||||
}
|
||||
const RandomizeMode rmode = {.asInt = varp->user1()};
|
||||
AstCMethodHard* const atp
|
||||
= new AstCMethodHard{fl, arrayp, VCMethod::ARRAY_AT, new AstConst{fl, rmode.index}};
|
||||
atp->dtypeSetUInt32();
|
||||
return atp;
|
||||
}
|
||||
|
||||
// The rand_mode bit of one variable access, or a constant when it has none.
|
||||
// A non-rand value can never be re-drawn; a non-rand owner level is skipped.
|
||||
AstNodeExpr* newVarGate(AstVar* varp, const AstMemberSel* mselp, bool ownerLevel,
|
||||
AstVar* randModeVarp, FileLine* fl) {
|
||||
if (!varp->rand().isRandomizable()) {
|
||||
return new AstConst{fl, AstConst::BitTrue{}, ownerLevel};
|
||||
}
|
||||
const RandomizeMode rmode = {.asInt = varp->user1()};
|
||||
if (!rmode.usesMode) return new AstConst{fl, AstConst::BitTrue{}};
|
||||
return newModeBitRead(varp, mselp, randModeVarp, fl);
|
||||
}
|
||||
|
||||
// Change bit of one variable access, ANDed level by level down the
|
||||
// member-select owner path.
|
||||
AstNodeExpr* newAccessGate(const AstNode* nodep, bool ownerLevel, AstVar* randModeVarp,
|
||||
FileLine* fl) {
|
||||
if (const AstMemberSel* const mselp = VN_CAST(nodep, MemberSel)) {
|
||||
return newGateAnd(fl, newVarGate(mselp->varp(), mselp, ownerLevel, randModeVarp, fl),
|
||||
newAccessGate(mselp->fromp(), true, randModeVarp, fl));
|
||||
}
|
||||
if (const AstNodeVarRef* const refp = VN_CAST(nodep, NodeVarRef)) {
|
||||
return newVarGate(refp->varp(), nullptr, ownerLevel, randModeVarp, fl);
|
||||
}
|
||||
if (const AstNodeSel* const selp = VN_CAST(nodep, NodeSel)) {
|
||||
return newAccessGate(selp->fromp(), true, randModeVarp, fl);
|
||||
}
|
||||
if (distBoundRefsModeVar(nodep)) {
|
||||
nodep->v3warn(E_UNSUPPORTED,
|
||||
"Unsupported: 'rand_mode' on a variable used inside this form of"
|
||||
" 'dist' expression");
|
||||
}
|
||||
return new AstConst{fl, AstConst::BitTrue{}};
|
||||
}
|
||||
|
||||
// Gate of one dist expression node: nonzero while its value can still be
|
||||
// freshly drawn. Constant when no runtime rand_mode bit is involved.
|
||||
AstNodeExpr* newDistGate(const AstNode* nodep, AstVar* randModeVarp, FileLine* fl) {
|
||||
if (VN_IS(nodep, NodeVarRef) || VN_IS(nodep, MemberSel)) {
|
||||
return newAccessGate(nodep, false, randModeVarp, fl);
|
||||
}
|
||||
// A selection is drawn through its base; the index never gates.
|
||||
if (const AstNodeSel* const selp = VN_CAST(nodep, NodeSel)) {
|
||||
return newDistGate(selp->fromp(), randModeVarp, fl);
|
||||
}
|
||||
if (const AstSel* const selp = VN_CAST(nodep, Sel)) {
|
||||
return newDistGate(selp->fromp(), randModeVarp, fl);
|
||||
}
|
||||
// A conditional re-draws through an active selector or the selected arm.
|
||||
if (const AstCond* const condp = VN_CAST(nodep, Cond)) {
|
||||
AstNodeExpr* const thenGatep = newDistGate(condp->thenp(), randModeVarp, fl);
|
||||
AstNodeExpr* elseGatep = newDistGate(condp->elsep(), randModeVarp, fl);
|
||||
AstNodeExpr* armGatep = thenGatep;
|
||||
if (thenGatep->sameTree(elseGatep)) {
|
||||
VL_DO_DANGLING(pushDeletep(elseGatep), elseGatep);
|
||||
} else {
|
||||
armGatep
|
||||
= new AstCond{fl, condp->condp()->cloneTreePure(false), thenGatep, elseGatep};
|
||||
}
|
||||
return newGateOr(fl, newDistGate(condp->condp(), randModeVarp, fl), armGatep);
|
||||
}
|
||||
if (const AstNodeUniop* const uopp = VN_CAST(nodep, NodeUniop)) {
|
||||
return newDistGate(uopp->lhsp(), randModeVarp, fl);
|
||||
}
|
||||
if (const AstNodeBiop* const bopp = VN_CAST(nodep, NodeBiop)) {
|
||||
return newGateOr(fl, newDistGate(bopp->lhsp(), randModeVarp, fl),
|
||||
newDistGate(bopp->rhsp(), randModeVarp, fl));
|
||||
}
|
||||
if (distBoundRefsModeVar(nodep)) {
|
||||
nodep->v3warn(E_UNSUPPORTED,
|
||||
"Unsupported: 'rand_mode' on a variable used inside this form of"
|
||||
" 'dist' expression");
|
||||
}
|
||||
// Constants and remaining forms re-draw only if a rand var is inside.
|
||||
return new AstConst{fl, AstConst::BitTrue{}, distBoundRefsRandVar(nodep)};
|
||||
}
|
||||
|
||||
// Membership union of the dist set, with runtime-zero weights excluded.
|
||||
AstConstraintExpr* buildFrozenMembership(AstDist* distp,
|
||||
const std::vector<DistBucket>& buckets) {
|
||||
FileLine* const fl = distp->fileline();
|
||||
AstNodeExpr* unionExprp = nullptr;
|
||||
for (const 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 AstConstraintExpr{fl, unionExprp};
|
||||
}
|
||||
|
||||
// Weighted chain while the dist value can still be freshly drawn, membership
|
||||
// once it is frozen; membership only when it can never be re-drawn.
|
||||
AstNode* gateFrozenDist(AstNode* chainp, AstDist* distp,
|
||||
const std::vector<DistBucket>& buckets, AstVar* randModeVarp) {
|
||||
FileLine* const fl = distp->fileline();
|
||||
AstNodeExpr* gatep = newDistGate(distp->exprp(), randModeVarp, fl);
|
||||
if (VN_IS(gatep, Const)) {
|
||||
const bool drawable = !VN_AS(gatep, Const)->isZero();
|
||||
VL_DO_DANGLING(pushDeletep(gatep), gatep);
|
||||
if (drawable) return chainp;
|
||||
VL_DO_DANGLING(pushDeletep(chainp), chainp);
|
||||
return buildFrozenMembership(distp, buckets);
|
||||
}
|
||||
return new AstConstraintIf{fl, gatep, chainp, buildFrozenMembership(distp, buckets)};
|
||||
}
|
||||
|
||||
AstNodeExpr* newUniformRangePick(AstDist* distp, const AstInsideRange* irp) {
|
||||
FileLine* const fl = distp->fileline();
|
||||
AstNodeExpr* const distExprCopyp = distp->exprp()->cloneTreePure(false);
|
||||
|
|
@ -4801,7 +4979,7 @@ class RandomizeVisitor final : public VNVisitor {
|
|||
|
||||
// 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
|
||||
|
|
@ -4827,13 +5005,15 @@ class RandomizeVisitor final : public VNVisitor {
|
|||
// dist can appear inside an if/else or foreach constraint.
|
||||
if (AstConstraintIf* const cifp = VN_CAST(itemp, ConstraintIf)) {
|
||||
UASSERT_OBJ(cifp->thensp(), cifp, "constraint if without a then body");
|
||||
lowerDistConstraints(taskp, cifp->thensp(), foreachp);
|
||||
if (cifp->elsesp()) { lowerDistConstraints(taskp, cifp->elsesp(), foreachp); }
|
||||
lowerDistConstraints(taskp, cifp->thensp(), randModeVarp, foreachp);
|
||||
if (cifp->elsesp()) {
|
||||
lowerDistConstraints(taskp, cifp->elsesp(), randModeVarp, foreachp);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (AstConstraintForeach* const cfep = VN_CAST(itemp, ConstraintForeach)) {
|
||||
UASSERT_OBJ(cfep->bodyp(), cfep, "constraint foreach without a body");
|
||||
lowerDistConstraints(taskp, cfep->bodyp(), cfep);
|
||||
lowerDistConstraints(taskp, cfep->bodyp(), randModeVarp, cfep);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -4853,7 +5033,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;
|
||||
}
|
||||
}
|
||||
|
|
@ -4933,7 +5113,10 @@ class RandomizeVisitor final : public VNVisitor {
|
|||
cumSums.push_back(runningSump->cloneTreePure(true));
|
||||
}
|
||||
|
||||
AstNode* const chainp = buildWeightedBucketChain(distp, buckets, bucketVarp, cumSums);
|
||||
AstNode* chainp = buildWeightedBucketChain(distp, buckets, bucketVarp, cumSums);
|
||||
|
||||
chainp = gateFrozenDist(chainp, distp, buckets, randModeVarp);
|
||||
|
||||
constrExprp->replaceWith(chainp);
|
||||
VL_DO_DANGLING(pushDeletep(constrExprp), constrExprp);
|
||||
// Hard membership precedes the soft bucket chain in the constraint list.
|
||||
|
|
@ -5026,7 +5209,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,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
#!/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')
|
||||
|
||||
# Emitted C++ for call-owner constraints does not yet compile; verilate only
|
||||
test.compile(verilator_make_gmake=False)
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
// 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
|
||||
|
||||
class Inner;
|
||||
rand int x;
|
||||
endclass
|
||||
|
||||
class Outer;
|
||||
rand Inner m;
|
||||
function Inner get();
|
||||
return m;
|
||||
endfunction
|
||||
constraint c {
|
||||
get().x dist {
|
||||
5 := 9,
|
||||
10 := 1
|
||||
};
|
||||
}
|
||||
function new();
|
||||
m = new;
|
||||
endfunction
|
||||
endclass
|
||||
|
||||
module t;
|
||||
initial begin
|
||||
automatic Outer o = new;
|
||||
automatic int ok = o.randomize();
|
||||
$display("ok=%0d x=%0d", ok, o.m.x);
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
|
|
@ -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,949 @@
|
|||
// 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
|
||||
|
||||
class ConstOp;
|
||||
rand uint x;
|
||||
constraint c {
|
||||
(x + 1) dist {
|
||||
6 := 9,
|
||||
71 := 1
|
||||
};
|
||||
}
|
||||
endclass
|
||||
|
||||
class StateMix;
|
||||
rand uint x;
|
||||
uint nw;
|
||||
constraint c {
|
||||
(x + nw) dist {
|
||||
5 := 9,
|
||||
70 := 1
|
||||
};
|
||||
}
|
||||
endclass
|
||||
|
||||
class MixedFreeze;
|
||||
rand uint x, z;
|
||||
constraint c {
|
||||
(x + z) dist {
|
||||
10 := 9,
|
||||
1000 := 1
|
||||
};
|
||||
}
|
||||
endclass
|
||||
|
||||
class NoModeHandle;
|
||||
rand Sub s2;
|
||||
constraint c {
|
||||
s2.x dist {
|
||||
5 := 9,
|
||||
70 := 1
|
||||
};
|
||||
}
|
||||
function new();
|
||||
s2 = new;
|
||||
endfunction
|
||||
endclass
|
||||
|
||||
class SelOp;
|
||||
rand uint x;
|
||||
constraint c {
|
||||
x[15:0] dist {
|
||||
16'd5 := 9,
|
||||
16'd70 := 1
|
||||
};
|
||||
}
|
||||
endclass
|
||||
|
||||
class NotOp;
|
||||
rand uint x;
|
||||
constraint c {
|
||||
(~x) dist {
|
||||
32'hFFFF_FFFA := 9,
|
||||
32'hFFFF_FFB9 := 1
|
||||
};
|
||||
}
|
||||
endclass
|
||||
|
||||
class CondSel;
|
||||
rand uint x, y;
|
||||
rand bit b;
|
||||
constraint c {
|
||||
(b ? x : y) dist {
|
||||
5 := 9,
|
||||
70 := 1
|
||||
};
|
||||
}
|
||||
endclass
|
||||
|
||||
class ObjArr;
|
||||
rand Sub arr2[2];
|
||||
constraint c {
|
||||
arr2[0].x dist {
|
||||
5 := 9,
|
||||
70 := 1
|
||||
};
|
||||
}
|
||||
function new();
|
||||
arr2[0] = new;
|
||||
arr2[1] = new;
|
||||
endfunction
|
||||
endclass
|
||||
|
||||
// Same SV shape as CondSel, but x2/y2 never use rand_mode anywhere in this
|
||||
// test: the arm gates fold to a constant at verilation instead of runtime bits.
|
||||
class CondSame;
|
||||
rand uint x2, y2;
|
||||
rand bit b2;
|
||||
constraint c {
|
||||
(b2 ? x2 : y2) dist {
|
||||
5 := 9,
|
||||
70 := 1
|
||||
};
|
||||
}
|
||||
endclass
|
||||
|
||||
typedef struct packed {
|
||||
bit [15:0] a;
|
||||
bit [15:0] b;
|
||||
} ab_t;
|
||||
|
||||
class PackedStruct;
|
||||
rand ab_t st;
|
||||
constraint c {
|
||||
st.a dist {
|
||||
16'd5 := 9,
|
||||
16'd70 := 1
|
||||
};
|
||||
}
|
||||
endclass
|
||||
|
||||
class Mixed;
|
||||
rand uint d;
|
||||
uint nf;
|
||||
endclass
|
||||
|
||||
class NonRandMember;
|
||||
rand Mixed m;
|
||||
constraint c {
|
||||
m.nf dist {
|
||||
5 := 9,
|
||||
70 := 1
|
||||
};
|
||||
}
|
||||
function new();
|
||||
m = new;
|
||||
endfunction
|
||||
endclass
|
||||
|
||||
class QSel;
|
||||
rand Sub q[$];
|
||||
int pick;
|
||||
constraint c {
|
||||
q[pick].x dist {
|
||||
30 := 9,
|
||||
90 := 1
|
||||
};
|
||||
}
|
||||
function new();
|
||||
Sub tmp = new;
|
||||
q.push_back(tmp);
|
||||
endfunction
|
||||
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) $stop;
|
||||
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
|
||||
|
||||
// Setting rand_mode back to 1 restores drawing.
|
||||
sv.x.rand_mode(1);
|
||||
for (int i = 0; i < 8; ++i) begin
|
||||
ok = sv.randomize();
|
||||
`checkd(ok, 1);
|
||||
if (sv.x != 5 && sv.x != 1000000) $stop;
|
||||
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 toward the heavy bucket.
|
||||
sv = new;
|
||||
begin
|
||||
automatic int n5 = 0, nbig = 0;
|
||||
for (int i = 0; i < 64; ++i) begin
|
||||
ok = sv.randomize();
|
||||
`checkd(ok, 1);
|
||||
if (sv.x == 5) n5++;
|
||||
else if (sv.x == 1000000) nbig++;
|
||||
else $stop;
|
||||
end
|
||||
if (n5 <= nbig) $stop;
|
||||
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
|
||||
|
||||
// Unfreezing the static var restores drawing.
|
||||
st.sx.rand_mode(1);
|
||||
for (int i = 0; i < 8; ++i) begin
|
||||
ok = st.randomize();
|
||||
`checkd(ok, 1);
|
||||
if (st.sx != 5 && st.sx != 70) $stop;
|
||||
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
|
||||
|
||||
// Unfreezing the sub-object member restores drawing.
|
||||
h.s.x.rand_mode(1);
|
||||
for (int i = 0; i < 8; ++i) begin
|
||||
ok = h.randomize();
|
||||
`checkd(ok, 1);
|
||||
if (h.s.x != 5 && h.s.x != 70) $stop;
|
||||
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) $stop;
|
||||
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) $stop;
|
||||
end
|
||||
else begin
|
||||
`checkd(di.x, 4242);
|
||||
end
|
||||
end
|
||||
|
||||
// Frozen x forces the solver to whichever branch its value satisfies.
|
||||
di.x.rand_mode(0);
|
||||
di.x = 70;
|
||||
for (int i = 0; i < 8; ++i) begin
|
||||
ok = di.randomize();
|
||||
`checkd(ok, 1);
|
||||
`checkd(di.sel, 1);
|
||||
`checkd(di.x, 70);
|
||||
end
|
||||
di.x = 4242;
|
||||
for (int i = 0; i < 8; ++i) begin
|
||||
ok = di.randomize();
|
||||
`checkd(ok, 1);
|
||||
`checkd(di.sel, 0);
|
||||
`checkd(di.x, 4242);
|
||||
end
|
||||
di.x = 7;
|
||||
for (int i = 0; i < 8; ++i) begin
|
||||
ok = di.randomize();
|
||||
`checkd(ok, 0);
|
||||
`checkd(di.x, 7);
|
||||
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) $stop;
|
||||
end
|
||||
|
||||
// Frozen index into an active array: a[idx] still draws BOTH values,
|
||||
// biased toward the heavy bucket.
|
||||
ix = new;
|
||||
ok = ix.randomize();
|
||||
`checkd(ok, 1);
|
||||
ix.idx.rand_mode(0);
|
||||
ix.idx = 2;
|
||||
begin
|
||||
automatic int n10 = 0, n20 = 0;
|
||||
for (int i = 0; i < 64; ++i) begin
|
||||
ok = ix.randomize();
|
||||
`checkd(ok, 1);
|
||||
`checkd(ix.idx, 2);
|
||||
if (ix.a[2] == 10) n10++;
|
||||
else if (ix.a[2] == 20) n20++;
|
||||
else $stop;
|
||||
end
|
||||
if (n10 == 0 || n20 == 0) $stop;
|
||||
if (n10 <= n20) $stop;
|
||||
end
|
||||
|
||||
// Constant operand: frozen (x + 1) follows membership of the sum.
|
||||
begin
|
||||
ConstOp cp;
|
||||
cp = new;
|
||||
ok = cp.randomize();
|
||||
`checkd(ok, 1);
|
||||
if (cp.x != 5 && cp.x != 70) $stop;
|
||||
cp.x.rand_mode(0);
|
||||
cp.x = 70;
|
||||
for (int i = 0; i < 8; ++i) begin
|
||||
ok = cp.randomize();
|
||||
`checkd(ok, 1);
|
||||
`checkd(cp.x, 70);
|
||||
end
|
||||
cp.x = 7;
|
||||
for (int i = 0; i < 8; ++i) begin
|
||||
ok = cp.randomize();
|
||||
`checkd(ok, 0);
|
||||
`checkd(cp.x, 7);
|
||||
end
|
||||
end
|
||||
|
||||
// Non-rand term in the dist expression: only the rand var gates, and the
|
||||
// state value participates in the frozen membership.
|
||||
begin
|
||||
StateMix sm;
|
||||
sm = new;
|
||||
sm.nw = 0;
|
||||
ok = sm.randomize();
|
||||
`checkd(ok, 1);
|
||||
if (sm.x != 5 && sm.x != 70) $stop;
|
||||
sm.x.rand_mode(0);
|
||||
sm.x = 70;
|
||||
for (int i = 0; i < 8; ++i) begin
|
||||
ok = sm.randomize();
|
||||
`checkd(ok, 1);
|
||||
`checkd(sm.x, 70);
|
||||
end
|
||||
sm.x = 7;
|
||||
for (int i = 0; i < 8; ++i) begin
|
||||
ok = sm.randomize();
|
||||
`checkd(ok, 0);
|
||||
`checkd(sm.x, 7);
|
||||
end
|
||||
sm.nw = 65;
|
||||
sm.x = 5;
|
||||
ok = sm.randomize();
|
||||
`checkd(ok, 1);
|
||||
`checkd(sm.x, 5);
|
||||
sm.nw = 66;
|
||||
ok = sm.randomize();
|
||||
`checkd(ok, 0);
|
||||
`checkd(sm.x, 5);
|
||||
end
|
||||
|
||||
// Partner var never uses rand_mode: freezing x leaves the dist weighted.
|
||||
begin
|
||||
MixedFreeze mf;
|
||||
mf = new;
|
||||
ok = mf.randomize();
|
||||
`checkd(ok, 1);
|
||||
mf.x.rand_mode(0);
|
||||
mf.x = 600;
|
||||
begin
|
||||
automatic int n10 = 0, n1000 = 0;
|
||||
for (int i = 0; i < 64; ++i) begin
|
||||
ok = mf.randomize();
|
||||
`checkd(ok, 1);
|
||||
`checkd(mf.x, 600);
|
||||
if (mf.x + mf.z == 10) n10++;
|
||||
else if (mf.x + mf.z == 1000) n1000++;
|
||||
else $stop;
|
||||
end
|
||||
if (n10 == 0 || n1000 == 0) $stop;
|
||||
if (n10 <= n1000) $stop;
|
||||
end
|
||||
end
|
||||
|
||||
// Handle without rand_mode in the chain: the member's own bit still gates.
|
||||
begin
|
||||
NoModeHandle nm;
|
||||
nm = new;
|
||||
ok = nm.randomize();
|
||||
`checkd(ok, 1);
|
||||
nm.s2.x.rand_mode(0);
|
||||
nm.s2.x = 70;
|
||||
for (int i = 0; i < 8; ++i) begin
|
||||
ok = nm.randomize();
|
||||
`checkd(ok, 1);
|
||||
`checkd(nm.s2.x, 70);
|
||||
end
|
||||
nm.s2.x = 7;
|
||||
for (int i = 0; i < 8; ++i) begin
|
||||
ok = nm.randomize();
|
||||
`checkd(ok, 0);
|
||||
`checkd(nm.s2.x, 7);
|
||||
end
|
||||
end
|
||||
|
||||
// Bit-select: frozen x degrades to membership of the selection.
|
||||
begin
|
||||
SelOp se;
|
||||
se = new;
|
||||
ok = se.randomize();
|
||||
`checkd(ok, 1);
|
||||
if (se.x[15:0] != 5 && se.x[15:0] != 70) $stop;
|
||||
se.x.rand_mode(0);
|
||||
se.x = 70;
|
||||
for (int i = 0; i < 8; ++i) begin
|
||||
ok = se.randomize();
|
||||
`checkd(ok, 1);
|
||||
`checkd(se.x, 70);
|
||||
end
|
||||
se.x = 7;
|
||||
for (int i = 0; i < 8; ++i) begin
|
||||
ok = se.randomize();
|
||||
`checkd(ok, 0);
|
||||
`checkd(se.x, 7);
|
||||
end
|
||||
end
|
||||
|
||||
// Unary operand: frozen (~x) follows membership of the inverted value.
|
||||
begin
|
||||
NotOp nt;
|
||||
nt = new;
|
||||
ok = nt.randomize();
|
||||
`checkd(ok, 1);
|
||||
if (nt.x != 5 && nt.x != 70) $stop;
|
||||
nt.x.rand_mode(0);
|
||||
nt.x = 5;
|
||||
for (int i = 0; i < 8; ++i) begin
|
||||
ok = nt.randomize();
|
||||
`checkd(ok, 1);
|
||||
`checkd(nt.x, 5);
|
||||
end
|
||||
nt.x = 7;
|
||||
for (int i = 0; i < 8; ++i) begin
|
||||
ok = nt.randomize();
|
||||
`checkd(ok, 0);
|
||||
`checkd(nt.x, 7);
|
||||
end
|
||||
end
|
||||
|
||||
// Conditional: frozen selector picks the live arm; a frozen selected arm
|
||||
// degrades to membership of the selected value.
|
||||
begin
|
||||
CondSel cs;
|
||||
cs = new;
|
||||
for (int i = 0; i < 8; ++i) begin
|
||||
ok = cs.randomize();
|
||||
`checkd(ok, 1);
|
||||
if ((cs.b ? cs.x : cs.y) != 5 && (cs.b ? cs.x : cs.y) != 70) $stop;
|
||||
end
|
||||
cs.b.rand_mode(0);
|
||||
cs.b = 1'b1;
|
||||
begin
|
||||
automatic int n5 = 0, n70 = 0;
|
||||
uint y0;
|
||||
automatic bit yvar = 0;
|
||||
ok = cs.randomize();
|
||||
`checkd(ok, 1);
|
||||
y0 = cs.y;
|
||||
for (int i = 0; i < 64; ++i) begin
|
||||
ok = cs.randomize();
|
||||
`checkd(ok, 1);
|
||||
`checkd(cs.b, 1'b1);
|
||||
if (cs.x == 5) n5++;
|
||||
else if (cs.x == 70) n70++;
|
||||
else $stop;
|
||||
if (cs.y != y0) yvar = 1;
|
||||
end
|
||||
if (n5 == 0 || n70 == 0) $stop;
|
||||
if (n5 <= n70) $stop;
|
||||
if (!yvar) $stop;
|
||||
end
|
||||
cs.x.rand_mode(0);
|
||||
cs.x = 70;
|
||||
for (int i = 0; i < 8; ++i) begin
|
||||
ok = cs.randomize();
|
||||
`checkd(ok, 1);
|
||||
`checkd(cs.x, 70);
|
||||
end
|
||||
cs.x = 7;
|
||||
for (int i = 0; i < 8; ++i) begin
|
||||
ok = cs.randomize();
|
||||
`checkd(ok, 0);
|
||||
`checkd(cs.x, 7);
|
||||
end
|
||||
end
|
||||
|
||||
// Conditional with mode-less arms: a frozen selector alone stays weighted.
|
||||
begin
|
||||
CondSame cq;
|
||||
cq = new;
|
||||
ok = cq.randomize();
|
||||
`checkd(ok, 1);
|
||||
cq.b2.rand_mode(0);
|
||||
for (int i = 0; i < 8; ++i) begin
|
||||
ok = cq.randomize();
|
||||
`checkd(ok, 1);
|
||||
if ((cq.b2 ? cq.x2 : cq.y2) != 5 && (cq.b2 ? cq.x2 : cq.y2) != 70) $stop;
|
||||
end
|
||||
end
|
||||
|
||||
// Object-array element dist honors the set.
|
||||
begin
|
||||
ObjArr oa;
|
||||
oa = new;
|
||||
for (int i = 0; i < 8; ++i) begin
|
||||
ok = oa.randomize();
|
||||
`checkd(ok, 1);
|
||||
if (oa.arr2[0].x != 5 && oa.arr2[0].x != 70) $stop;
|
||||
end
|
||||
end
|
||||
|
||||
// Packed struct member: frozen struct follows membership of the field.
|
||||
begin
|
||||
PackedStruct ps;
|
||||
ps = new;
|
||||
ok = ps.randomize();
|
||||
`checkd(ok, 1);
|
||||
if (ps.st.a != 5 && ps.st.a != 70) $stop;
|
||||
ps.st.rand_mode(0);
|
||||
ps.st = '{a: 16'd70, b: 16'd0};
|
||||
for (int i = 0; i < 8; ++i) begin
|
||||
ok = ps.randomize();
|
||||
`checkd(ok, 1);
|
||||
`checkd(ps.st.a, 70);
|
||||
end
|
||||
ps.st = '{a: 16'd7, b: 16'd0};
|
||||
for (int i = 0; i < 8; ++i) begin
|
||||
ok = ps.randomize();
|
||||
`checkd(ok, 0);
|
||||
`checkd(ps.st.a, 7);
|
||||
end
|
||||
end
|
||||
|
||||
// Dist on a non-rand member of a rand object: in-set value succeeds.
|
||||
begin
|
||||
NonRandMember nr;
|
||||
nr = new;
|
||||
nr.m.nf = 5;
|
||||
ok = nr.randomize();
|
||||
`checkd(ok, 1);
|
||||
`checkd(nr.m.nf, 5);
|
||||
end
|
||||
|
||||
// Queue element member: draws both values, biased toward the heavy bucket.
|
||||
begin
|
||||
QSel qs;
|
||||
qs = new;
|
||||
begin
|
||||
automatic int n30 = 0, n90 = 0;
|
||||
for (int i = 0; i < 64; ++i) begin
|
||||
ok = qs.randomize();
|
||||
`checkd(ok, 1);
|
||||
if (qs.q[0].x == 30) n30++;
|
||||
else if (qs.q[0].x == 90) n90++;
|
||||
else $stop;
|
||||
end
|
||||
if (n30 == 0 || n90 == 0) $stop;
|
||||
if (n30 <= n90) $stop;
|
||||
end
|
||||
end
|
||||
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
%Error-UNSUPPORTED: t/t_constraint_dist_randmode_unsup.v:14:6: Unsupported: 'rand_mode' on a variable used inside this form of 'dist' expression
|
||||
: ... note: In instance 't'
|
||||
14 | q[0].x dist {
|
||||
| ^
|
||||
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
|
||||
%Error-UNSUPPORTED: t/t_constraint_dist_randmode_unsup.v:28:7: Unsupported: 'rand_mode' on a variable used inside this form of 'dist' expression
|
||||
: ... note: In instance 't'
|
||||
28 | iq[0] dist {
|
||||
| ^
|
||||
%Error: Exiting due to
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
#!/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')
|
||||
|
||||
test.compile(fails=True, expect_filename=test.golden_filename)
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
// 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
|
||||
|
||||
class Inner;
|
||||
rand int x;
|
||||
endclass
|
||||
|
||||
class QOwner;
|
||||
rand Inner q[$];
|
||||
constraint c {
|
||||
q[0].x dist {
|
||||
5 := 9,
|
||||
10 := 1
|
||||
};
|
||||
}
|
||||
function new();
|
||||
Inner tmp = new;
|
||||
q.push_back(tmp);
|
||||
endfunction
|
||||
endclass
|
||||
|
||||
class QValue;
|
||||
rand int iq[$];
|
||||
constraint c {
|
||||
iq[0] dist {
|
||||
5 := 9,
|
||||
10 := 1
|
||||
};
|
||||
}
|
||||
function new();
|
||||
iq.push_back(1);
|
||||
endfunction
|
||||
endclass
|
||||
|
||||
module t;
|
||||
initial begin
|
||||
automatic QOwner qo = new;
|
||||
automatic QValue qv = new;
|
||||
automatic int ok;
|
||||
qo.q.rand_mode(0);
|
||||
qv.iq.rand_mode(0);
|
||||
ok = qo.randomize();
|
||||
ok = qv.randomize();
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
Loading…
Reference in New Issue