functionality done, tiny test passed

This commit is contained in:
Yilou Wang 2026-03-01 12:30:31 +01:00
parent 78ee787bb1
commit da3d1d9584
3 changed files with 138 additions and 2 deletions

View File

@ -1244,7 +1244,7 @@ class AstDist final : public AstNodeExpr {
// @astgen op2 := itemsp : List[AstDistItem]
public:
AstDist(FileLine* fl, AstNodeExpr* exprp, AstDistItem* itemsp)
: ASTGEN_SUPER_Inside(fl) {
: ASTGEN_SUPER_Dist(fl) {
this->exprp(exprp);
addItemsp(itemsp);
dtypeSetBit();

View File

@ -2290,6 +2290,7 @@ class RandomizeVisitor final : public VNVisitor {
AstDynArrayDType* m_dynarrayDtp = nullptr; // Dynamic array type (for rand mode)
size_t m_enumValueTabCount = 0; // Number of tables with enum values created
int m_randCaseNum = 0; // Randcase number within a module for var naming
int m_distNum = 0; // Dist bucket variable counter within a module for var naming
std::map<std::string, AstCDType*> m_randcDtypes; // RandC data type deduplication
AstConstraint* m_constraintp = nullptr; // Current constraint
std::set<std::string> m_writtenVars; // Track write_var calls per class to avoid duplicates
@ -3328,6 +3329,132 @@ class RandomizeVisitor final : public VNVisitor {
}
}
// Lower dist constraints into weighted bucket selection + AstConstraintIf chains.
// Called for each constraint before ConstraintExprVisitor.
// taskp: constraint setup task (bucket var declarations + assignments go here)
// constrItemsp: first item in the constraint item list
void lowerDistConstraints(AstTask* taskp, AstNode* constrItemsp) {
for (AstNode *nextip, *itemp = constrItemsp; itemp; itemp = nextip) {
nextip = itemp->nextp();
AstConstraintExpr* const constrExprp = VN_CAST(itemp, ConstraintExpr);
if (!constrExprp) continue;
AstDist* const distp = VN_CAST(constrExprp->exprp(), Dist);
if (!distp) continue;
FileLine* const fl = distp->fileline();
// Collect buckets and compute effective weights
struct BucketInfo {
AstNodeExpr* rangep; // Points into distItem (will be cloned, not unlinked)
uint64_t effectiveWeight;
};
std::vector<BucketInfo> buckets;
uint64_t totalWeight = 0;
for (AstDistItem* ditemp = distp->itemsp(); ditemp;
ditemp = VN_AS(ditemp->nextp(), DistItem)) {
const AstConst* const weightp = VN_CAST(ditemp->weightp(), Const);
if (!weightp) continue; // Non-const weight: skip
const uint64_t w = weightp->toUQuad();
if (w == 0) continue; // weight=0 means never
uint64_t effectiveW = w;
if (!ditemp->isWhole()) { // := weight is per-value; multiply by range size
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);
if (lop && hip && hip->toUQuad() >= lop->toUQuad())
effectiveW = w * (hip->toUQuad() - lop->toUQuad() + 1);
}
// scalar: effectiveW stays w
}
// :/ weight: effectiveW stays w (weight for whole range, not per-value)
buckets.push_back({ditemp->rangep(), effectiveW});
totalWeight += effectiveW;
}
if (buckets.empty() || totalWeight == 0) continue;
// Create bucket selection variable: uint64_t __Vdist_bucketN
const std::string bucketName = "__Vdist_bucket" + cvtToStr(m_distNum++);
AstVar* const bucketVarp
= new AstVar{fl, VVarType::BLOCKTEMP, bucketName, taskp->findUInt64DType()};
bucketVarp->noSubst(true);
bucketVarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
bucketVarp->funcLocal(true);
taskp->addStmtsp(bucketVarp);
// Assign: bucketVar = (rand64() % totalWeight) + 1
// +1 ensures that weight=0 items are never selected
AstNodeExpr* randp = new AstRand{fl, nullptr, false};
randp->dtypeSetUInt64();
taskp->addStmtsp(new AstAssign{
fl, new AstVarRef{fl, bucketVarp, VAccess::WRITE},
new AstAdd{
fl, new AstConst{fl, AstConst::Unsized64{}, 1},
new AstModDiv{fl, randp,
new AstConst{fl, AstConst::Unsized64{}, totalWeight}}}});
// Build AstConstraintIf chain (last-to-first so nesting is natural):
// if (bucket <= w0) { exprp == range0 }
// else if (bucket <= w0+w1) { exprp in range1 }
// ...
AstNode* chainp = nullptr;
uint64_t cumWeight = totalWeight;
for (int i = static_cast<int>(buckets.size()) - 1; i >= 0; --i) {
cumWeight -= buckets[i].effectiveWeight;
const uint64_t thisCumWeight = cumWeight + buckets[i].effectiveWeight;
// Build range constraint expression (user1=true for rand-dependent nodes)
AstNodeExpr* constraintExprp;
if (const AstInsideRange* const irp = VN_CAST(buckets[i].rangep, InsideRange)) {
// Range bucket: exprp >= lo && exprp <= hi
AstNodeExpr* const exprCopy1p = distp->exprp()->cloneTreePure(false);
exprCopy1p->user1(true);
AstNodeExpr* const exprCopy2p = distp->exprp()->cloneTreePure(false);
exprCopy2p->user1(true);
AstGte* const gtep
= new AstGte{fl, exprCopy1p, irp->lhsp()->cloneTreePure(false)};
gtep->user1(true);
AstLte* const ltep
= new AstLte{fl, exprCopy2p, irp->rhsp()->cloneTreePure(false)};
ltep->user1(true);
constraintExprp = new AstLogAnd{fl, gtep, ltep};
constraintExprp->user1(true);
} else {
// Scalar bucket: exprp == value
AstNodeExpr* const exprCopyp = distp->exprp()->cloneTreePure(false);
exprCopyp->user1(true);
constraintExprp = new AstEq{fl, exprCopyp,
buckets[i].rangep->cloneTreePure(false)};
constraintExprp->user1(true);
}
AstConstraintExpr* const thenp = new AstConstraintExpr{fl, constraintExprp};
if (!chainp) {
// Last bucket: no condition needed (always selected when reached)
chainp = thenp;
} else {
// Bucket condition: bucketVar <= thisCumWeight (NOT rand-dependent)
AstNodeExpr* const condp = new AstLte{
fl, new AstVarRef{fl, bucketVarp, VAccess::READ},
new AstConst{fl, AstConst::Unsized64{}, thisCumWeight}};
chainp = new AstConstraintIf{fl, condp, thenp, chainp};
}
}
if (chainp) {
// Replace AstConstraintExpr(AstDist) with the weighted-bucket chain
constrExprp->replaceWith(chainp);
VL_DO_DANGLING(constrExprp->deleteTree(), constrExprp);
}
}
}
// VISITORS
void visit(AstNodeModule* nodep) override {
VL_RESTORER(m_modp);
@ -3346,6 +3473,7 @@ class RandomizeVisitor final : public VNVisitor {
VL_RESTORER(m_randCaseNum);
m_modp = nodep;
m_randCaseNum = 0;
m_distNum = 0;
m_writtenVars.clear(); // Each class has its own set of written variables
iterateChildren(nodep);
@ -3393,6 +3521,8 @@ class RandomizeVisitor final : public VNVisitor {
}
if (constrp->itemsp()) expandUniqueElementList(constrp->itemsp());
// Lower dist constraints into weighted bucket selection before SMT encoding
if (constrp->itemsp()) lowerDistConstraints(taskp, constrp->itemsp());
ConstraintExprVisitor{classp, m_memberMap, constrp->itemsp(), nullptr,
genp, randModeVarp, m_writtenVars};
if (constrp->itemsp()) {

View File

@ -3050,7 +3050,7 @@ class WidthVisitor final : public VNVisitor {
}
void visit(AstDist* nodep) override {
// x dist {a :/ p, b :/ q} --> (p > 0 && x == a) || (q > 0 && x == b)
nodep->v3warn(CONSTRAINTIGN, "Constraint expression ignored (imperfect distribution)");
// (only outside constraints; inside constraints V3Randomize handles weighted selection)
userIterateAndNext(nodep->exprp(), WidthVP{CONTEXT_DET, PRELIM}.p());
for (AstNode *nextip, *itemp = nodep->itemsp(); itemp; itemp = nextip) {
nextip = itemp->nextp(); // iterate may cause the node to get replaced
@ -3087,6 +3087,12 @@ class WidthVisitor final : public VNVisitor {
if (!VN_IS(itemp, InsideRange))
iterateCheck(nodep, "Dist Item", itemp, CONTEXT_DET, FINAL, subDTypep, EXTEND_EXP);
}
// Inside a constraint: keep AstDist alive for V3Randomize to do weighted bucket selection
if (m_constraintp) return;
// Outside constraint: lower to inside expressions (ignores weights - imperfect)
nodep->v3warn(CONSTRAINTIGN, "Constraint expression ignored (imperfect distribution)");
AstNodeExpr* newp = nullptr;
for (AstDistItem* itemp = nodep->itemsp(); itemp;
itemp = VN_AS(itemp->nextp(), DistItem)) {