Merge 8e6fab3075 into 3b43b304a3
This commit is contained in:
commit
cfd5d5ac00
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,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
|
||||
Loading…
Reference in New Issue