Gate dist by whether the expression value can still be drawn, fixing frozen-index weights

This commit is contained in:
Yilou Wang 2026-07-14 15:02:47 +02:00
parent 50c1059374
commit 71334a9234
2 changed files with 45 additions and 6 deletions

View File

@ -4561,19 +4561,25 @@ class RandomizeVisitor final : public VNVisitor {
return new AstConstraintExpr{fl, unionExprp};
}
// Gate the weighted chain on the rand_mode bits of the dist's variables, so a
// frozen variable requires only membership, not a freshly drawn bucket.
// 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 AstLogAnd{fl, gatep, atp};
gatep = new AstLogOr{fl, gatep, atp};
gatep->dtypeSetBit();
} else {
gatep = atp;
@ -4586,13 +4592,21 @@ class RandomizeVisitor final : public VNVisitor {
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 || !gatedVars.insert(varp).second) return;
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);
@ -4603,7 +4617,6 @@ class RandomizeVisitor final : public VNVisitor {
if (!memberModep) return;
AstMemberSel* const modeSelp
= new AstMemberSel{fl, mselp->fromp()->cloneTreePure(false), memberModep};
modeSelp->dtypep(memberModep->dtypep());
addModeGate(modeSelp, rmode.index);
} else if (randModeVarp) {
addModeGate(new AstVarRef{fl, VN_AS(randModeVarp->user2p(), NodeModule),
@ -4611,7 +4624,7 @@ class RandomizeVisitor final : public VNVisitor {
rmode.index);
}
});
if (!gatep) return chainp;
if (alwaysActive || !gatep) return chainp;
AstNodeExpr* unionExprp = nullptr;
for (auto& bucket : buckets) {

View File

@ -158,6 +158,18 @@ class RtWeight;
}
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
@ -170,6 +182,7 @@ module t;
RtWeight r;
DistInIf di;
DistInForeach df;
IdxSel ix;
// Frozen in the low-weight bucket -> succeeds, value untouched.
sv = new;
@ -441,6 +454,19 @@ module t;
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