parent
5f3d475736
commit
3bc73cc768
|
|
@ -746,6 +746,14 @@ void VlRandomizer::soft(std::string&& constraint, const char* /*filename*/, uint
|
||||||
m_softConstraints.emplace_back(std::move(constraint));
|
m_softConstraints.emplace_back(std::move(constraint));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VlRandomizer::disable_soft(const std::string& varName) {
|
||||||
|
// IEEE 1800-2017 18.5.13: Remove all soft constraints referencing the variable
|
||||||
|
m_softConstraints.erase(
|
||||||
|
std::remove_if(m_softConstraints.begin(), m_softConstraints.end(),
|
||||||
|
[&](const std::string& c) { return c.find(varName) != std::string::npos; }),
|
||||||
|
m_softConstraints.end());
|
||||||
|
}
|
||||||
|
|
||||||
void VlRandomizer::clearConstraints() {
|
void VlRandomizer::clearConstraints() {
|
||||||
m_constraints.clear();
|
m_constraints.clear();
|
||||||
m_constraints_line.clear();
|
m_constraints_line.clear();
|
||||||
|
|
|
||||||
|
|
@ -596,6 +596,7 @@ public:
|
||||||
const char* source = "");
|
const char* source = "");
|
||||||
void soft(std::string&& constraint, const char* filename = "", uint32_t linenum = 0,
|
void soft(std::string&& constraint, const char* filename = "", uint32_t linenum = 0,
|
||||||
const char* source = "");
|
const char* source = "");
|
||||||
|
void disable_soft(const std::string& varName);
|
||||||
void clearConstraints();
|
void clearConstraints();
|
||||||
void clearAll(); // Clear both constraints and variables
|
void clearAll(); // Clear both constraints and variables
|
||||||
void markRandc(const char* name); // Mark variable as randc for cyclic tracking
|
void markRandc(const char* name); // Mark variable as randc for cyclic tracking
|
||||||
|
|
|
||||||
|
|
@ -814,6 +814,7 @@ public:
|
||||||
RANDOMIZER_BASIC_STD_RANDOMIZATION,
|
RANDOMIZER_BASIC_STD_RANDOMIZATION,
|
||||||
RANDOMIZER_CLEARCONSTRAINTS,
|
RANDOMIZER_CLEARCONSTRAINTS,
|
||||||
RANDOMIZER_CLEARALL,
|
RANDOMIZER_CLEARALL,
|
||||||
|
RANDOMIZER_DISABLE_SOFT,
|
||||||
RANDOMIZER_HARD,
|
RANDOMIZER_HARD,
|
||||||
RANDOMIZER_SOFT,
|
RANDOMIZER_SOFT,
|
||||||
RANDOMIZER_UNIQUE,
|
RANDOMIZER_UNIQUE,
|
||||||
|
|
@ -951,6 +952,7 @@ inline std::ostream& operator<<(std::ostream& os, const VCMethod& rhs) {
|
||||||
{RANDOMIZER_BASIC_STD_RANDOMIZATION, "basicStdRandomization", false}, \
|
{RANDOMIZER_BASIC_STD_RANDOMIZATION, "basicStdRandomization", false}, \
|
||||||
{RANDOMIZER_CLEARCONSTRAINTS, "clearConstraints", false}, \
|
{RANDOMIZER_CLEARCONSTRAINTS, "clearConstraints", false}, \
|
||||||
{RANDOMIZER_CLEARALL, "clearAll", false}, \
|
{RANDOMIZER_CLEARALL, "clearAll", false}, \
|
||||||
|
{RANDOMIZER_DISABLE_SOFT, "disable_soft", false}, \
|
||||||
{RANDOMIZER_HARD, "hard", false}, \
|
{RANDOMIZER_HARD, "hard", false}, \
|
||||||
{RANDOMIZER_SOFT, "soft", false}, \
|
{RANDOMIZER_SOFT, "soft", false}, \
|
||||||
{RANDOMIZER_UNIQUE, "rand_unique", false}, \
|
{RANDOMIZER_UNIQUE, "rand_unique", false}, \
|
||||||
|
|
|
||||||
|
|
@ -1268,7 +1268,7 @@ class AstDist final : public AstNodeExpr {
|
||||||
// @astgen op2 := itemsp : List[AstDistItem]
|
// @astgen op2 := itemsp : List[AstDistItem]
|
||||||
public:
|
public:
|
||||||
AstDist(FileLine* fl, AstNodeExpr* exprp, AstDistItem* itemsp)
|
AstDist(FileLine* fl, AstNodeExpr* exprp, AstDistItem* itemsp)
|
||||||
: ASTGEN_SUPER_Inside(fl) {
|
: ASTGEN_SUPER_Dist(fl) {
|
||||||
this->exprp(exprp);
|
this->exprp(exprp);
|
||||||
addItemsp(itemsp);
|
addItemsp(itemsp);
|
||||||
dtypeSetBit();
|
dtypeSetBit();
|
||||||
|
|
|
||||||
|
|
@ -1785,6 +1785,7 @@ class ConstraintExprVisitor final : public VNVisitor {
|
||||||
newp = new AstLogIf{fl, new AstNot{fl, nodep->condp()->unlinkFrBack()}, elsep};
|
newp = new AstLogIf{fl, new AstNot{fl, nodep->condp()->unlinkFrBack()}, elsep};
|
||||||
}
|
}
|
||||||
if (newp) {
|
if (newp) {
|
||||||
|
newp->dtypeSetBit(); // Result is boolean (prevents bare-var != 0 wrapping)
|
||||||
newp->user1(true); // Assume result-dependent
|
newp->user1(true); // Assume result-dependent
|
||||||
nodep->replaceWith(new AstConstraintExpr{fl, newp});
|
nodep->replaceWith(new AstConstraintExpr{fl, newp});
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1963,6 +1964,45 @@ class ConstraintExprVisitor final : public VNVisitor {
|
||||||
}
|
}
|
||||||
|
|
||||||
void visit(AstConstraintExpr* nodep) override {
|
void visit(AstConstraintExpr* nodep) override {
|
||||||
|
// IEEE 1800-2017 18.5.13: "disable soft" removes all soft constraints
|
||||||
|
// referencing the specified variable. Pass the variable name directly
|
||||||
|
// instead of going through SMT lowering.
|
||||||
|
if (nodep->isDisableSoft()) {
|
||||||
|
// Extract variable name from expression (VarRef or MemberSel)
|
||||||
|
std::string varName;
|
||||||
|
if (const AstNodeVarRef* const vrefp = VN_CAST(nodep->exprp(), NodeVarRef)) {
|
||||||
|
varName = vrefp->name();
|
||||||
|
} else if (const AstMemberSel* const mselp = VN_CAST(nodep->exprp(), MemberSel)) {
|
||||||
|
varName = mselp->name();
|
||||||
|
} else {
|
||||||
|
nodep->v3fatalSrc("Unexpected expression type in disable soft");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AstCMethodHard* const callp = new AstCMethodHard{
|
||||||
|
nodep->fileline(),
|
||||||
|
new AstVarRef{nodep->fileline(), VN_AS(m_genp->user2p(), NodeModule), m_genp,
|
||||||
|
VAccess::READWRITE},
|
||||||
|
VCMethod::RANDOMIZER_DISABLE_SOFT,
|
||||||
|
new AstConst{nodep->fileline(), AstConst::String{}, varName}};
|
||||||
|
callp->dtypeSetVoid();
|
||||||
|
nodep->replaceWith(callp->makeStmt());
|
||||||
|
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// IEEE 1800-2017 18.5.1: A bare expression used as a constraint is
|
||||||
|
// implicitly treated as "expr != 0" when wider than 1 bit.
|
||||||
|
// Must wrap before iterateChildren, which converts to SMT format.
|
||||||
|
{
|
||||||
|
AstNodeExpr* const exprp = nodep->exprp();
|
||||||
|
if (exprp->width() > 1) {
|
||||||
|
FileLine* const fl = exprp->fileline();
|
||||||
|
V3Number numZero{fl, exprp->width(), 0};
|
||||||
|
AstNodeExpr* const neqp
|
||||||
|
= new AstNeq{fl, exprp->unlinkFrBack(), new AstConst{fl, numZero}};
|
||||||
|
neqp->user1(true); // Mark as rand-dependent for SMT path
|
||||||
|
nodep->exprp(neqp);
|
||||||
|
}
|
||||||
|
}
|
||||||
iterateChildren(nodep);
|
iterateChildren(nodep);
|
||||||
if (m_wantSingle) {
|
if (m_wantSingle) {
|
||||||
nodep->replaceWith(nodep->exprp()->unlinkFrBack());
|
nodep->replaceWith(nodep->exprp()->unlinkFrBack());
|
||||||
|
|
@ -2506,6 +2546,7 @@ class RandomizeVisitor final : public VNVisitor {
|
||||||
AstDynArrayDType* m_dynarrayDtp = nullptr; // Dynamic array type (for rand mode)
|
AstDynArrayDType* m_dynarrayDtp = nullptr; // Dynamic array type (for rand mode)
|
||||||
size_t m_enumValueTabCount = 0; // Number of tables with enum values created
|
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_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
|
std::map<std::string, AstCDType*> m_randcDtypes; // RandC data type deduplication
|
||||||
AstConstraint* m_constraintp = nullptr; // Current constraint
|
AstConstraint* m_constraintp = nullptr; // Current constraint
|
||||||
std::set<std::string> m_writtenVars; // Track write_var calls per class to avoid duplicates
|
std::set<std::string> m_writtenVars; // Track write_var calls per class to avoid duplicates
|
||||||
|
|
@ -3551,6 +3592,178 @@ 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) {
|
||||||
|
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();
|
||||||
|
|
||||||
|
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});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buckets.empty()) {
|
||||||
|
// All weights are zero: dist is vacuously true (unconstrained)
|
||||||
|
AstConstraintExpr* const truep
|
||||||
|
= new AstConstraintExpr{fl, new AstConst{fl, AstConst::BitTrue{}}};
|
||||||
|
constrExprp->replaceWith(truep);
|
||||||
|
VL_DO_DANGLING(pushDeletep(constrExprp), constrExprp);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build totalWeight expression: w[0] + w[1] + ... + w[N-1]
|
||||||
|
AstNodeExpr* totalWeightExprp = nullptr;
|
||||||
|
for (auto& bucket : buckets) {
|
||||||
|
if (!totalWeightExprp) {
|
||||||
|
totalWeightExprp = bucket.weightExprp->cloneTreePure(false);
|
||||||
|
} else {
|
||||||
|
totalWeightExprp = new AstAdd{fl, totalWeightExprp,
|
||||||
|
bucket.weightExprp->cloneTreePure(false)};
|
||||||
|
totalWeightExprp->dtypeSetUInt64();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store totalWeight in temp var (evaluated once, used twice)
|
||||||
|
const int distId = m_distNum++;
|
||||||
|
const std::string totalName = "__Vdist_total" + cvtToStr(distId);
|
||||||
|
AstVar* const totalVarp
|
||||||
|
= new AstVar{fl, VVarType::BLOCKTEMP, totalName, taskp->findUInt64DType()};
|
||||||
|
totalVarp->noSubst(true);
|
||||||
|
totalVarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
|
||||||
|
totalVarp->funcLocal(true);
|
||||||
|
totalVarp->isInternal(true);
|
||||||
|
taskp->addStmtsp(totalVarp);
|
||||||
|
taskp->addStmtsp(
|
||||||
|
new AstAssign{fl, new AstVarRef{fl, totalVarp, VAccess::WRITE}, totalWeightExprp});
|
||||||
|
|
||||||
|
// bucketVar = (rand64() % totalWeight) + 1
|
||||||
|
const std::string bucketName = "__Vdist_bucket" + cvtToStr(distId);
|
||||||
|
AstVar* const bucketVarp
|
||||||
|
= new AstVar{fl, VVarType::BLOCKTEMP, bucketName, taskp->findUInt64DType()};
|
||||||
|
bucketVarp->noSubst(true);
|
||||||
|
bucketVarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
|
||||||
|
bucketVarp->funcLocal(true);
|
||||||
|
bucketVarp->isInternal(true);
|
||||||
|
taskp->addStmtsp(bucketVarp);
|
||||||
|
|
||||||
|
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 AstVarRef{fl, totalVarp, VAccess::READ}}}});
|
||||||
|
|
||||||
|
// Build cumulative sum expressions forward: cumSum[i] = w[0]+...+w[i]
|
||||||
|
std::vector<AstNodeExpr*> cumSums;
|
||||||
|
AstNodeExpr* runningSump = nullptr;
|
||||||
|
for (size_t i = 0; i < buckets.size(); ++i) {
|
||||||
|
if (!runningSump) {
|
||||||
|
runningSump = buckets[i].weightExprp->cloneTreePure(false);
|
||||||
|
} else {
|
||||||
|
runningSump = new AstAdd{fl, runningSump,
|
||||||
|
buckets[i].weightExprp->cloneTreePure(false)};
|
||||||
|
runningSump->dtypeSetUInt64();
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
if (const AstInsideRange* const irp = VN_CAST(buckets[i].rangep, InsideRange)) {
|
||||||
|
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 {
|
||||||
|
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) {
|
||||||
|
chainp = thenp;
|
||||||
|
} else {
|
||||||
|
AstNodeExpr* const condp
|
||||||
|
= new AstLte{fl, new AstVarRef{fl, bucketVarp, VAccess::READ}, cumSums[i]};
|
||||||
|
chainp = new AstConstraintIf{fl, condp, thenp, chainp};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chainp) {
|
||||||
|
constrExprp->replaceWith(chainp);
|
||||||
|
VL_DO_DANGLING(pushDeletep(constrExprp), constrExprp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up nodes used only as clone templates (never inserted into tree)
|
||||||
|
for (auto& bucket : buckets) {
|
||||||
|
VL_DO_DANGLING(pushDeletep(bucket.weightExprp), bucket.weightExprp);
|
||||||
|
}
|
||||||
|
VL_DO_DANGLING(pushDeletep(runningSump), runningSump);
|
||||||
|
// Last cumSum is unused (last bucket is unconditional default)
|
||||||
|
pushDeletep(cumSums.back());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// VISITORS
|
// VISITORS
|
||||||
void visit(AstNodeModule* nodep) override {
|
void visit(AstNodeModule* nodep) override {
|
||||||
VL_RESTORER(m_modp);
|
VL_RESTORER(m_modp);
|
||||||
|
|
@ -3567,8 +3780,10 @@ class RandomizeVisitor final : public VNVisitor {
|
||||||
void visit(AstClass* nodep) override {
|
void visit(AstClass* nodep) override {
|
||||||
VL_RESTORER(m_modp);
|
VL_RESTORER(m_modp);
|
||||||
VL_RESTORER(m_randCaseNum);
|
VL_RESTORER(m_randCaseNum);
|
||||||
|
VL_RESTORER(m_distNum);
|
||||||
m_modp = nodep;
|
m_modp = nodep;
|
||||||
m_randCaseNum = 0;
|
m_randCaseNum = 0;
|
||||||
|
m_distNum = 0;
|
||||||
m_writtenVars.clear(); // Each class has its own set of written variables
|
m_writtenVars.clear(); // Each class has its own set of written variables
|
||||||
|
|
||||||
iterateChildren(nodep);
|
iterateChildren(nodep);
|
||||||
|
|
@ -3616,6 +3831,7 @@ class RandomizeVisitor final : public VNVisitor {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (constrp->itemsp()) expandUniqueElementList(constrp->itemsp());
|
if (constrp->itemsp()) expandUniqueElementList(constrp->itemsp());
|
||||||
|
if (constrp->itemsp()) lowerDistConstraints(taskp, constrp->itemsp());
|
||||||
ConstraintExprVisitor{classp, m_memberMap, constrp->itemsp(), nullptr,
|
ConstraintExprVisitor{classp, m_memberMap, constrp->itemsp(), nullptr,
|
||||||
genp, randModeVarp, m_writtenVars};
|
genp, randModeVarp, m_writtenVars};
|
||||||
if (constrp->itemsp()) {
|
if (constrp->itemsp()) {
|
||||||
|
|
|
||||||
|
|
@ -3126,7 +3126,7 @@ class WidthVisitor final : public VNVisitor {
|
||||||
}
|
}
|
||||||
void visit(AstDist* nodep) override {
|
void visit(AstDist* nodep) override {
|
||||||
// x dist {a :/ p, b :/ q} --> (p > 0 && x == a) || (q > 0 && x == b)
|
// 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());
|
userIterateAndNext(nodep->exprp(), WidthVP{CONTEXT_DET, PRELIM}.p());
|
||||||
for (AstNode *nextip, *itemp = nodep->itemsp(); itemp; itemp = nextip) {
|
for (AstNode *nextip, *itemp = nodep->itemsp(); itemp; itemp = nextip) {
|
||||||
nextip = itemp->nextp(); // iterate may cause the node to get replaced
|
nextip = itemp->nextp(); // iterate may cause the node to get replaced
|
||||||
|
|
@ -3163,6 +3163,23 @@ class WidthVisitor final : public VNVisitor {
|
||||||
if (!VN_IS(itemp, InsideRange))
|
if (!VN_IS(itemp, InsideRange))
|
||||||
iterateCheck(nodep, "Dist Item", itemp, CONTEXT_DET, FINAL, subDTypep, EXTEND_EXP);
|
iterateCheck(nodep, "Dist Item", itemp, CONTEXT_DET, FINAL, subDTypep, EXTEND_EXP);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Inside a constraint, V3Randomize handles dist lowering with proper weights,
|
||||||
|
// but only for simple scalar/range items. Container-type items (queues, arrays)
|
||||||
|
// must be lowered here via insideItem() which knows how to expand them.
|
||||||
|
if (m_constraintp) {
|
||||||
|
bool canLower = true;
|
||||||
|
for (AstDistItem* ditemp = nodep->itemsp(); ditemp;
|
||||||
|
ditemp = VN_AS(ditemp->nextp(), DistItem)) {
|
||||||
|
if (!VN_IS(ditemp->rangep(), Const) && !VN_IS(ditemp->rangep(), InsideRange)) {
|
||||||
|
canLower = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (canLower) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Outside constraint: lower to inside (ignores weights)
|
||||||
AstNodeExpr* newp = nullptr;
|
AstNodeExpr* newp = nullptr;
|
||||||
for (AstDistItem* itemp = nodep->itemsp(); itemp;
|
for (AstDistItem* itemp = nodep->itemsp(); itemp;
|
||||||
itemp = VN_AS(itemp->nextp(), DistItem)) {
|
itemp = VN_AS(itemp->nextp(), DistItem)) {
|
||||||
|
|
|
||||||
|
|
@ -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,129 @@
|
||||||
|
// 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);
|
||||||
|
`define check_range(gotv,minv,maxv) do if ((gotv) < (minv) || (gotv) > (maxv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d-%0d\n", `__FILE__,`__LINE__, (gotv), (minv), (maxv)); `stop; end while(0);
|
||||||
|
// verilog_format: on
|
||||||
|
|
||||||
|
class DistScalar;
|
||||||
|
rand bit [7:0] x;
|
||||||
|
constraint c { x dist { 8'd0 := 1, 8'd255 := 3 }; }
|
||||||
|
endclass
|
||||||
|
|
||||||
|
class DistRange;
|
||||||
|
rand bit [7:0] x;
|
||||||
|
constraint c { x dist { [8'd0:8'd9] :/ 1, [8'd10:8'd19] :/ 3 }; }
|
||||||
|
endclass
|
||||||
|
|
||||||
|
class DistZeroWeight;
|
||||||
|
rand bit [7:0] x;
|
||||||
|
constraint c { x dist { 8'd0 := 0, 8'd1 := 1, 8'd2 := 1 }; }
|
||||||
|
endclass
|
||||||
|
|
||||||
|
class DistAllZeroWeight;
|
||||||
|
rand bit [7:0] x;
|
||||||
|
constraint c { x dist { 8'd0 := 0, 8'd1 := 0, 8'd2 := 0 }; }
|
||||||
|
endclass
|
||||||
|
|
||||||
|
class DistVarWeight;
|
||||||
|
rand bit [7:0] x;
|
||||||
|
int w1, w2;
|
||||||
|
constraint c { x dist { 8'd0 := w1, 8'd255 := w2 }; }
|
||||||
|
endclass
|
||||||
|
|
||||||
|
class DistVarWeightRange;
|
||||||
|
rand bit [7:0] x;
|
||||||
|
int w1, w2;
|
||||||
|
constraint c { x dist { [8'd0:8'd9] :/ w1, [8'd10:8'd19] :/ w2 }; }
|
||||||
|
endclass
|
||||||
|
|
||||||
|
module t;
|
||||||
|
initial begin
|
||||||
|
DistScalar sc;
|
||||||
|
DistRange rg;
|
||||||
|
DistZeroWeight zw;
|
||||||
|
DistAllZeroWeight azw;
|
||||||
|
DistVarWeight vw;
|
||||||
|
DistVarWeightRange vwr;
|
||||||
|
int count_high;
|
||||||
|
int count_range_high;
|
||||||
|
int total;
|
||||||
|
|
||||||
|
total = 2000;
|
||||||
|
|
||||||
|
// := scalar weights: expect ~75% for value 255
|
||||||
|
sc = new;
|
||||||
|
count_high = 0;
|
||||||
|
repeat (total) begin
|
||||||
|
`checkd(sc.randomize(), 1);
|
||||||
|
if (sc.x == 8'd255) count_high++;
|
||||||
|
else `checkd(sc.x, 0);
|
||||||
|
end
|
||||||
|
`check_range(count_high, total * 60 / 100, total * 90 / 100);
|
||||||
|
|
||||||
|
// :/ range weights: expect ~75% in [10:19]
|
||||||
|
rg = new;
|
||||||
|
count_range_high = 0;
|
||||||
|
repeat (total) begin
|
||||||
|
`checkd(rg.randomize(), 1);
|
||||||
|
if (rg.x >= 8'd10 && rg.x <= 8'd19) count_range_high++;
|
||||||
|
else if (rg.x > 8'd9) begin
|
||||||
|
$write("%%Error: x=%0d outside valid range [0:19]\n", rg.x);
|
||||||
|
`stop;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
`check_range(count_range_high, total * 60 / 100, total * 90 / 100);
|
||||||
|
|
||||||
|
// Zero weight: value 0 must never appear
|
||||||
|
zw = new;
|
||||||
|
repeat (total) begin
|
||||||
|
`checkd(zw.randomize(), 1);
|
||||||
|
if (zw.x == 8'd0) begin
|
||||||
|
$write("%%Error: zero-weight value 0 was selected\n");
|
||||||
|
`stop;
|
||||||
|
end
|
||||||
|
`check_range(zw.x, 1, 2);
|
||||||
|
end
|
||||||
|
|
||||||
|
// All-zero weights: dist constraint is effectively unconstrained, randomize succeeds
|
||||||
|
azw = new;
|
||||||
|
repeat (20) begin
|
||||||
|
`checkd(azw.randomize(), 1);
|
||||||
|
end
|
||||||
|
|
||||||
|
// Variable := scalar weights: w1=1, w2=3 => expect ~75% for value 255
|
||||||
|
vw = new;
|
||||||
|
vw.w1 = 1;
|
||||||
|
vw.w2 = 3;
|
||||||
|
count_high = 0;
|
||||||
|
repeat (total) begin
|
||||||
|
`checkd(vw.randomize(), 1);
|
||||||
|
if (vw.x == 8'd255) count_high++;
|
||||||
|
else `checkd(vw.x, 0);
|
||||||
|
end
|
||||||
|
`check_range(count_high, total * 60 / 100, total * 90 / 100);
|
||||||
|
|
||||||
|
// Variable :/ range weights: w1=1, w2=3 => expect ~75% in [10:19]
|
||||||
|
vwr = new;
|
||||||
|
vwr.w1 = 1;
|
||||||
|
vwr.w2 = 3;
|
||||||
|
count_range_high = 0;
|
||||||
|
repeat (total) begin
|
||||||
|
`checkd(vwr.randomize(), 1);
|
||||||
|
if (vwr.x >= 8'd10 && vwr.x <= 8'd19) count_range_high++;
|
||||||
|
else if (vwr.x > 8'd9) begin
|
||||||
|
$write("%%Error: x=%0d outside valid range [0:19]\n", vwr.x);
|
||||||
|
`stop;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
`check_range(count_range_high, total * 60 / 100, total * 90 / 100);
|
||||||
|
|
||||||
|
$write("*-* All Finished *-*\n");
|
||||||
|
$finish;
|
||||||
|
end
|
||||||
|
endmodule
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
%Warning-CONSTRAINTIGN: t/t_randomize.v:22:14: Constraint expression ignored (imperfect distribution)
|
|
||||||
: ... note: In instance 't'
|
|
||||||
22 | length dist { [0:1], [2:5] :/ 2, 6 := 6, 7 := 10, 1};
|
|
||||||
| ^~~~
|
|
||||||
... For warning description see https://verilator.org/warn/CONSTRAINTIGN?v=latest
|
|
||||||
... Use "/* verilator lint_off CONSTRAINTIGN */" and lint_on around source to disable this message.
|
|
||||||
%Error: Exiting due to
|
|
||||||
|
|
@ -9,8 +9,13 @@
|
||||||
|
|
||||||
import vltest_bootstrap
|
import vltest_bootstrap
|
||||||
|
|
||||||
test.scenarios('vlt')
|
test.scenarios('simulator')
|
||||||
|
|
||||||
test.lint(fails=True, expect_filename=test.golden_filename)
|
if not test.have_solver:
|
||||||
|
test.skip("No constraint solver installed")
|
||||||
|
|
||||||
|
test.compile()
|
||||||
|
|
||||||
|
test.execute()
|
||||||
|
|
||||||
test.passes()
|
test.passes()
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ class Packet;
|
||||||
rand bit if_4;
|
rand bit if_4;
|
||||||
rand bit iff_5_6;
|
rand bit iff_5_6;
|
||||||
|
|
||||||
/*rand*/ int array[2]; // 2,4,6 // TODO: add rand when supported
|
rand int array[2]; // 2,4,6
|
||||||
|
|
||||||
constraint empty {}
|
constraint empty {}
|
||||||
|
|
||||||
|
|
@ -58,7 +58,7 @@ module t;
|
||||||
|
|
||||||
automatic int v;
|
automatic int v;
|
||||||
automatic bit if_4 = '0;
|
automatic bit if_4 = '0;
|
||||||
// TODO not testing constrained values
|
p = new;
|
||||||
v = p.randomize();
|
v = p.randomize();
|
||||||
if (v != 1) $stop;
|
if (v != 1) $stop;
|
||||||
v = p.randomize() with {};
|
v = p.randomize() with {};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue