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));
|
||||
}
|
||||
|
||||
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() {
|
||||
m_constraints.clear();
|
||||
m_constraints_line.clear();
|
||||
|
|
|
|||
|
|
@ -596,6 +596,7 @@ public:
|
|||
const char* source = "");
|
||||
void soft(std::string&& constraint, const char* filename = "", uint32_t linenum = 0,
|
||||
const char* source = "");
|
||||
void disable_soft(const std::string& varName);
|
||||
void clearConstraints();
|
||||
void clearAll(); // Clear both constraints and variables
|
||||
void markRandc(const char* name); // Mark variable as randc for cyclic tracking
|
||||
|
|
|
|||
|
|
@ -814,6 +814,7 @@ public:
|
|||
RANDOMIZER_BASIC_STD_RANDOMIZATION,
|
||||
RANDOMIZER_CLEARCONSTRAINTS,
|
||||
RANDOMIZER_CLEARALL,
|
||||
RANDOMIZER_DISABLE_SOFT,
|
||||
RANDOMIZER_HARD,
|
||||
RANDOMIZER_SOFT,
|
||||
RANDOMIZER_UNIQUE,
|
||||
|
|
@ -951,6 +952,7 @@ inline std::ostream& operator<<(std::ostream& os, const VCMethod& rhs) {
|
|||
{RANDOMIZER_BASIC_STD_RANDOMIZATION, "basicStdRandomization", false}, \
|
||||
{RANDOMIZER_CLEARCONSTRAINTS, "clearConstraints", false}, \
|
||||
{RANDOMIZER_CLEARALL, "clearAll", false}, \
|
||||
{RANDOMIZER_DISABLE_SOFT, "disable_soft", false}, \
|
||||
{RANDOMIZER_HARD, "hard", false}, \
|
||||
{RANDOMIZER_SOFT, "soft", false}, \
|
||||
{RANDOMIZER_UNIQUE, "rand_unique", false}, \
|
||||
|
|
|
|||
|
|
@ -1268,7 +1268,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();
|
||||
|
|
|
|||
|
|
@ -1785,6 +1785,7 @@ class ConstraintExprVisitor final : public VNVisitor {
|
|||
newp = new AstLogIf{fl, new AstNot{fl, nodep->condp()->unlinkFrBack()}, elsep};
|
||||
}
|
||||
if (newp) {
|
||||
newp->dtypeSetBit(); // Result is boolean (prevents bare-var != 0 wrapping)
|
||||
newp->user1(true); // Assume result-dependent
|
||||
nodep->replaceWith(new AstConstraintExpr{fl, newp});
|
||||
} else {
|
||||
|
|
@ -1963,6 +1964,45 @@ class ConstraintExprVisitor final : public VNVisitor {
|
|||
}
|
||||
|
||||
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);
|
||||
if (m_wantSingle) {
|
||||
nodep->replaceWith(nodep->exprp()->unlinkFrBack());
|
||||
|
|
@ -2506,6 +2546,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
|
||||
|
|
@ -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
|
||||
void visit(AstNodeModule* nodep) override {
|
||||
VL_RESTORER(m_modp);
|
||||
|
|
@ -3567,8 +3780,10 @@ class RandomizeVisitor final : public VNVisitor {
|
|||
void visit(AstClass* nodep) override {
|
||||
VL_RESTORER(m_modp);
|
||||
VL_RESTORER(m_randCaseNum);
|
||||
VL_RESTORER(m_distNum);
|
||||
m_modp = nodep;
|
||||
m_randCaseNum = 0;
|
||||
m_distNum = 0;
|
||||
m_writtenVars.clear(); // Each class has its own set of written variables
|
||||
|
||||
iterateChildren(nodep);
|
||||
|
|
@ -3616,6 +3831,7 @@ class RandomizeVisitor final : public VNVisitor {
|
|||
}
|
||||
|
||||
if (constrp->itemsp()) expandUniqueElementList(constrp->itemsp());
|
||||
if (constrp->itemsp()) lowerDistConstraints(taskp, constrp->itemsp());
|
||||
ConstraintExprVisitor{classp, m_memberMap, constrp->itemsp(), nullptr,
|
||||
genp, randModeVarp, m_writtenVars};
|
||||
if (constrp->itemsp()) {
|
||||
|
|
|
|||
|
|
@ -3126,7 +3126,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
|
||||
|
|
@ -3163,6 +3163,23 @@ class WidthVisitor final : public VNVisitor {
|
|||
if (!VN_IS(itemp, InsideRange))
|
||||
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;
|
||||
for (AstDistItem* itemp = nodep->itemsp(); itemp;
|
||||
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
|
||||
|
||||
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()
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ class Packet;
|
|||
rand bit if_4;
|
||||
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 {}
|
||||
|
||||
|
|
@ -58,7 +58,7 @@ module t;
|
|||
|
||||
automatic int v;
|
||||
automatic bit if_4 = '0;
|
||||
// TODO not testing constrained values
|
||||
p = new;
|
||||
v = p.randomize();
|
||||
if (v != 1) $stop;
|
||||
v = p.randomize() with {};
|
||||
|
|
|
|||
Loading…
Reference in New Issue