From da3d1d958457e2057334a2b80054da2a598b0f6d Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Sun, 1 Mar 2026 12:30:31 +0100 Subject: [PATCH] functionality done, tiny test passed --- src/V3AstNodeExpr.h | 2 +- src/V3Randomize.cpp | 130 ++++++++++++++++++++++++++++++++++++++++++++ src/V3Width.cpp | 8 ++- 3 files changed, 138 insertions(+), 2 deletions(-) diff --git a/src/V3AstNodeExpr.h b/src/V3AstNodeExpr.h index 165f29933..6b210a069 100644 --- a/src/V3AstNodeExpr.h +++ b/src/V3AstNodeExpr.h @@ -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(); diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 6d886da5d..91db63ba4 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -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 m_randcDtypes; // RandC data type deduplication AstConstraint* m_constraintp = nullptr; // Current constraint std::set 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 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(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()) { diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 529e42f86..e87b843f8 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -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)) {