Fix skewed dist operator for arrays (#7802)
This commit is contained in:
parent
6fbc7042a5
commit
d5c040d8e6
|
|
@ -2144,10 +2144,15 @@ class ConstraintExprVisitor final : public VNVisitor {
|
|||
nodep->replaceWith(new AstSFormatF{fl, "%s", false, cexprp});
|
||||
} else {
|
||||
iterateAndNextNull(nodep->bodyp());
|
||||
nodep->replaceWith(new AstBegin{fl, "",
|
||||
new AstForeach{fl, nodep->headerp()->unlinkFrBack(),
|
||||
nodep->bodyp()->unlinkFrBackWithNext()},
|
||||
true});
|
||||
AstNode* bodyp = nodep->bodyp()->unlinkFrBackWithNext();
|
||||
// Prepend bucket preamble stmts stored by lowerDistConstraints (foreach case)
|
||||
if (AstNode* const preamblep = nodep->user3p()) {
|
||||
preamblep->addNext(bodyp);
|
||||
bodyp = preamblep;
|
||||
nodep->user3p(nullptr);
|
||||
}
|
||||
nodep->replaceWith(new AstBegin{
|
||||
fl, "", new AstForeach{fl, nodep->headerp()->unlinkFrBack(), bodyp}, true});
|
||||
}
|
||||
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
||||
}
|
||||
|
|
@ -3076,6 +3081,7 @@ class RandomizeVisitor final : public VNVisitor {
|
|||
// AstVar::user3() -> bool. Handled in constraints
|
||||
// AstClass::user3p() -> AstVar*. Constrained randomizer variable
|
||||
// AstConstraint::user3p() -> AstTask*. Pointer to resize procedure
|
||||
// AstConstraintForeach::user3p() -> AstNode*. Dist bucket preamble stmts (foreach case)
|
||||
// AstClass::user4p() -> AstVar*. Constraint mode state variable
|
||||
// AstVar::user4p() -> AstVar*. Size variable for constrained queues
|
||||
// AstMemberSel::user2p() -> AstNodeModule*. Pointer to containing module
|
||||
|
|
@ -4431,20 +4437,41 @@ 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) {
|
||||
void lowerDistConstraints(AstTask* taskp, AstNode* constrItemsp,
|
||||
AstConstraintForeach* foreachp = nullptr) {
|
||||
// When inside a foreach, bucket preamble stmts are stored in foreachp->user3p()
|
||||
// (as a linked list) so visit(AstConstraintForeach*) can inject them into the
|
||||
// real AstForeach body. Outside a foreach, they go directly into taskp.
|
||||
AstNode* foreachTailp = nullptr;
|
||||
auto addStmt = [&](AstNode* nodep) {
|
||||
if (foreachp) {
|
||||
if (!foreachTailp) {
|
||||
foreachp->user3p(nodep);
|
||||
foreachTailp = nodep;
|
||||
} else {
|
||||
foreachTailp->addNext(nodep);
|
||||
foreachTailp = nodep;
|
||||
}
|
||||
} else {
|
||||
taskp->addStmtsp(nodep);
|
||||
}
|
||||
};
|
||||
|
||||
for (AstNode *nextip, *itemp = constrItemsp; itemp; itemp = nextip) {
|
||||
nextip = itemp->nextp();
|
||||
|
||||
// Recursively handle ConstraintIf nodes (dist can be inside if/else)
|
||||
if (AstConstraintIf* const cifp = VN_CAST(itemp, ConstraintIf)) {
|
||||
if (cifp->thensp()) lowerDistConstraints(taskp, cifp->thensp());
|
||||
if (cifp->elsesp()) lowerDistConstraints(taskp, cifp->elsesp());
|
||||
if (cifp->thensp()) // LCOV_EXCL_LINE
|
||||
lowerDistConstraints(taskp, cifp->thensp(), foreachp); // LCOV_EXCL_LINE
|
||||
if (cifp->elsesp()) lowerDistConstraints(taskp, cifp->elsesp(), foreachp);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Recursively handle ConstraintForeach nodes (dist can be inside foreach)
|
||||
if (AstConstraintForeach* const cfep = VN_CAST(itemp, ConstraintForeach)) {
|
||||
if (cfep->bodyp()) lowerDistConstraints(taskp, cfep->bodyp());
|
||||
if (cfep->bodyp()) // LCOV_EXCL_LINE
|
||||
lowerDistConstraints(taskp, cfep->bodyp(), cfep); // LCOV_EXCL_LINE
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -4464,7 +4491,7 @@ class RandomizeVisitor final : public VNVisitor {
|
|||
AstConstraintIf* const liftedp = liftLogIfChainToConstraintIf(topLogIfp);
|
||||
constrExprp->replaceWith(liftedp);
|
||||
VL_DO_DANGLING(pushDeletep(constrExprp), constrExprp);
|
||||
lowerDistConstraints(taskp, liftedp->thensp());
|
||||
lowerDistConstraints(taskp, liftedp->thensp(), foreachp);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
|
@ -4526,6 +4553,47 @@ class RandomizeVisitor final : public VNVisitor {
|
|||
continue;
|
||||
}
|
||||
|
||||
// IEEE 1800-2023 18.5.3: values not in the distribution must never appear.
|
||||
// Build the union of all non-zero-weight ranges as a single hard ConstraintExpr
|
||||
AstNodeExpr* unionExprp = nullptr;
|
||||
for (const auto& bucket : buckets) {
|
||||
AstNodeExpr* memberp;
|
||||
if (const AstInsideRange* const irp = VN_CAST(bucket.rangep, InsideRange)) {
|
||||
// (distExpr >= lo) && (distExpr <= hi); signed comparisons for signed vars
|
||||
const bool isSigned = distp->exprp()->isSigned();
|
||||
AstNodeExpr* const distExprGtep = distp->exprp()->cloneTreePure(false);
|
||||
AstNodeExpr* const distExprLtep = distp->exprp()->cloneTreePure(false);
|
||||
distExprGtep->user1(true);
|
||||
distExprLtep->user1(true);
|
||||
AstNodeExpr* const gep
|
||||
= isSigned ? static_cast<AstNodeExpr*>(new AstGteS{
|
||||
fl, distExprGtep, irp->lhsp()->cloneTreePure(false)})
|
||||
: static_cast<AstNodeExpr*>(new AstGte{
|
||||
fl, distExprGtep, irp->lhsp()->cloneTreePure(false)});
|
||||
AstNodeExpr* const lep
|
||||
= isSigned ? static_cast<AstNodeExpr*>(new AstLteS{
|
||||
fl, distExprLtep, irp->rhsp()->cloneTreePure(false)})
|
||||
: static_cast<AstNodeExpr*>(new AstLte{
|
||||
fl, distExprLtep, irp->rhsp()->cloneTreePure(false)});
|
||||
gep->user1(true);
|
||||
lep->user1(true);
|
||||
memberp = new AstLogAnd{fl, gep, lep};
|
||||
} else {
|
||||
// distExpr == val
|
||||
AstNodeExpr* const distExprCopyp = distp->exprp()->cloneTreePure(false);
|
||||
distExprCopyp->user1(true);
|
||||
memberp = new AstEq{fl, distExprCopyp, bucket.rangep->cloneTreePure(false)};
|
||||
}
|
||||
memberp->user1(true);
|
||||
if (!unionExprp) {
|
||||
unionExprp = memberp;
|
||||
} else {
|
||||
unionExprp = new AstLogOr{fl, memberp, unionExprp};
|
||||
unionExprp->user1(true);
|
||||
}
|
||||
}
|
||||
AstConstraintExpr* const membershipp = new AstConstraintExpr{fl, unionExprp};
|
||||
|
||||
// Build totalWeight expression: w[0] + w[1] + ... + w[N-1]
|
||||
AstNodeExpr* totalWeightExprp = nullptr;
|
||||
for (auto& bucket : buckets) {
|
||||
|
|
@ -4547,8 +4615,8 @@ class RandomizeVisitor final : public VNVisitor {
|
|||
totalVarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
|
||||
totalVarp->funcLocal(true);
|
||||
totalVarp->isInternal(true);
|
||||
taskp->addStmtsp(totalVarp);
|
||||
taskp->addStmtsp(
|
||||
addStmt(totalVarp);
|
||||
addStmt(
|
||||
new AstAssign{fl, new AstVarRef{fl, totalVarp, VAccess::WRITE}, totalWeightExprp});
|
||||
|
||||
// bucketVar = (rand64() % totalWeight) + 1
|
||||
|
|
@ -4559,11 +4627,11 @@ class RandomizeVisitor final : public VNVisitor {
|
|||
bucketVarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
|
||||
bucketVarp->funcLocal(true);
|
||||
bucketVarp->isInternal(true);
|
||||
taskp->addStmtsp(bucketVarp);
|
||||
addStmt(bucketVarp);
|
||||
|
||||
AstNodeExpr* randp = new AstRand{fl, nullptr, false};
|
||||
AstNodeExpr* const randp = new AstRand{fl, nullptr, false};
|
||||
randp->dtypeSetUInt64();
|
||||
taskp->addStmtsp(new AstAssign{
|
||||
addStmt(new AstAssign{
|
||||
fl, new AstVarRef{fl, bucketVarp, VAccess::WRITE},
|
||||
new AstAdd{
|
||||
fl, new AstConst{fl, AstConst::Unsized64{}, 1},
|
||||
|
|
@ -4588,27 +4656,59 @@ class RandomizeVisitor final : public VNVisitor {
|
|||
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};
|
||||
// Pick distExpr = lo + rand64() % (hi - lo + 1) for a uniform value in range
|
||||
AstNodeExpr* const distExprCopyp = distp->exprp()->cloneTreePure(false);
|
||||
distExprCopyp->user1(true);
|
||||
const int distWidth = distp->exprp()->width();
|
||||
// Compute range size in 64-bit to avoid overflow
|
||||
const AstConst* const lopC = VN_CAST(irp->lhsp(), Const);
|
||||
const AstConst* const hipC = VN_CAST(irp->rhsp(), Const);
|
||||
AstNodeExpr* rangeSzp;
|
||||
if (lopC && hipC) {
|
||||
const uint64_t rsz = hipC->toUQuad() - lopC->toUQuad() + 1;
|
||||
rangeSzp = new AstConst{fl, AstConst::Unsized64{}, rsz};
|
||||
} else {
|
||||
const bool isSigned = irp->lhsp()->isSigned();
|
||||
AstNodeExpr* const lo64p
|
||||
= isSigned
|
||||
? static_cast<AstNodeExpr*>(
|
||||
new AstExtendS{fl, irp->lhsp()->cloneTreePure(false), 64})
|
||||
: static_cast<AstNodeExpr*>(
|
||||
new AstExtend{fl, irp->lhsp()->cloneTreePure(false), 64});
|
||||
lo64p->dtypeSetUInt64();
|
||||
AstNodeExpr* const hi64p
|
||||
= isSigned
|
||||
? static_cast<AstNodeExpr*>(
|
||||
new AstExtendS{fl, irp->rhsp()->cloneTreePure(false), 64})
|
||||
: static_cast<AstNodeExpr*>(
|
||||
new AstExtend{fl, irp->rhsp()->cloneTreePure(false), 64});
|
||||
hi64p->dtypeSetUInt64();
|
||||
rangeSzp = new AstAdd{fl, new AstConst{fl, AstConst::Unsized64{}, 1ULL},
|
||||
new AstSub{fl, hi64p, lo64p}};
|
||||
}
|
||||
AstNodeExpr* const rand64p = new AstRand{fl, nullptr, false};
|
||||
rand64p->dtypeSetUInt64();
|
||||
// offset = rand64() % rangeSize (result in [0, rangeSize-1])
|
||||
AstNodeExpr* const offset64p = new AstModDiv{fl, rand64p, rangeSzp};
|
||||
// Truncate offset to dist expression width, then add lo
|
||||
AstNodeExpr* const offsetp = new AstCCast{fl, offset64p, distWidth};
|
||||
AstNodeExpr* const lop = irp->lhsp()->cloneTreePure(false);
|
||||
AstNodeExpr* const valuep = new AstAdd{fl, lop, offsetp};
|
||||
valuep->dtypeFrom(distp->exprp());
|
||||
constraintExprp = new AstEq{fl, distExprCopyp, valuep};
|
||||
constraintExprp->user1(true);
|
||||
} else {
|
||||
AstNodeExpr* const exprCopyp = distp->exprp()->cloneTreePure(false);
|
||||
exprCopyp->user1(true);
|
||||
AstNodeExpr* const distExprCopyp = distp->exprp()->cloneTreePure(false);
|
||||
distExprCopyp->user1(true);
|
||||
constraintExprp
|
||||
= new AstEq{fl, exprCopyp, buckets[i].rangep->cloneTreePure(false)};
|
||||
= new AstEq{fl, distExprCopyp, buckets[i].rangep->cloneTreePure(false)};
|
||||
constraintExprp->user1(true);
|
||||
}
|
||||
|
||||
AstConstraintExpr* const thenp = new AstConstraintExpr{fl, constraintExprp};
|
||||
// Per IEEE 18.5.3: weights are a preference, not a hard constraint.
|
||||
// The solver may discard this when it conflicts with other constraints.
|
||||
thenp->isSoft(true);
|
||||
|
||||
if (!chainp) {
|
||||
chainp = thenp;
|
||||
|
|
@ -4622,6 +4722,8 @@ class RandomizeVisitor final : public VNVisitor {
|
|||
if (chainp) {
|
||||
constrExprp->replaceWith(chainp);
|
||||
VL_DO_DANGLING(pushDeletep(constrExprp), constrExprp);
|
||||
// Hard membership precedes the soft bucket chain in the constraint list.
|
||||
chainp->addHereThisAsNext(membershipp);
|
||||
}
|
||||
|
||||
// Clean up nodes used only as clone templates (never inserted into tree)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2024 Antmicro Ltd
|
||||
// SPDX-FileCopyrightText: 2026 Antmicro
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
`define check_rand(cl, field, cond) \
|
||||
|
|
@ -11,7 +11,7 @@ begin \
|
|||
if (!bit'(cl.randomize())) $stop; \
|
||||
prev_result = longint'(field); \
|
||||
if (!(cond)) $stop; \
|
||||
repeat(9) begin \
|
||||
repeat(100) begin \
|
||||
longint result; \
|
||||
if (!bit'(cl.randomize())) $stop; \
|
||||
result = longint'(field); \
|
||||
|
|
@ -33,8 +33,12 @@ class C;
|
|||
};
|
||||
constraint distinside {
|
||||
z dist {que};
|
||||
w dist {arr};
|
||||
};
|
||||
w dist {arr}; }; endclass
|
||||
|
||||
class DistNarrow;
|
||||
rand bit [3:0] x;
|
||||
constraint c1 { x dist {[4'd1:4'd9] := 1}; }
|
||||
constraint c2 { x > 4'd5; }
|
||||
endclass
|
||||
|
||||
module t;
|
||||
|
|
@ -45,6 +49,11 @@ module t;
|
|||
`check_rand(c, c.y, 5 <= c.y && c.y <= 6);
|
||||
`check_rand(c, c.z, 3 <= c.z && c.z <= 5);
|
||||
`check_rand(c, c.w, 5 <= c.w && c.w <= 7);
|
||||
begin
|
||||
DistNarrow dn;
|
||||
dn = new;
|
||||
`check_rand(dn, dn.x, 6 <= dn.x && dn.x <= 9);
|
||||
end
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
|
|
|
|||
|
|
@ -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,133 @@
|
|||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Antmicro
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
// Test that dist constraints nested inside if / -> inside foreach produce
|
||||
// values only within the declared distribution and cover all buckets.
|
||||
|
||||
// verilog_format: off
|
||||
`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);
|
||||
// verilog_format: on
|
||||
|
||||
// foreach (a[i]) if (gate) a[i] dist {...}
|
||||
class ClsIf;
|
||||
rand bit [3:0] a [4];
|
||||
bit gate;
|
||||
constraint c {
|
||||
foreach (a[i]) {
|
||||
if (gate == 1'b1) {
|
||||
a[i] dist { 4'd0 := 3, [4'd1:4'd4] := 1 };
|
||||
}
|
||||
}
|
||||
}
|
||||
endclass
|
||||
|
||||
// foreach (a[i]) gate -> a[i] dist {...}
|
||||
class ClsImpl;
|
||||
rand bit [3:0] a [4];
|
||||
bit gate;
|
||||
constraint c {
|
||||
foreach (a[i]) {
|
||||
gate -> (a[i] dist { 4'd0 := 3, [4'd1:4'd4] := 1 });
|
||||
}
|
||||
}
|
||||
endclass
|
||||
|
||||
// foreach (a[i]) gateA -> (gateB -> a[i] dist {...}) -- doubly-nested implication
|
||||
class ClsImplChained;
|
||||
rand bit [3:0] a [4];
|
||||
bit gateA, gateB;
|
||||
constraint c {
|
||||
foreach (a[i]) {
|
||||
gateA -> (gateB -> (a[i] dist { 4'd0 := 3, [4'd1:4'd4] := 1 }));
|
||||
}
|
||||
}
|
||||
endclass
|
||||
|
||||
module t;
|
||||
initial begin
|
||||
// Test if form
|
||||
begin
|
||||
static ClsIf obj = new();
|
||||
int seen_zero, seen_nonzero;
|
||||
obj.gate = 1'b1;
|
||||
seen_zero = 0;
|
||||
seen_nonzero = 0;
|
||||
repeat (100) begin
|
||||
`checkd(obj.randomize(), 1)
|
||||
foreach (obj.a[i]) begin
|
||||
if (obj.a[i] > 4) begin
|
||||
$write("%%Error: %s:%0d: if: value out of dist range: %0d\n",
|
||||
`__FILE__, `__LINE__, obj.a[i]);
|
||||
$stop;
|
||||
end
|
||||
if (obj.a[i] == 0) seen_zero++;
|
||||
else seen_nonzero++;
|
||||
end
|
||||
end
|
||||
if (seen_zero == 0 || seen_nonzero == 0) begin
|
||||
$write("%%Error: %s:%0d: dist inside foreach+if: not all buckets hit (zero=%0d nonzero=%0d)\n",
|
||||
`__FILE__, `__LINE__, seen_zero, seen_nonzero);
|
||||
$stop;
|
||||
end
|
||||
end
|
||||
|
||||
// Test -> (implication) form
|
||||
begin
|
||||
static ClsImpl obj = new();
|
||||
int seen_zero, seen_nonzero;
|
||||
obj.gate = 1'b1;
|
||||
seen_zero = 0;
|
||||
seen_nonzero = 0;
|
||||
repeat (100) begin
|
||||
`checkd(obj.randomize(), 1)
|
||||
foreach (obj.a[i]) begin
|
||||
if (obj.a[i] > 4) begin
|
||||
$write("%%Error: %s:%0d: ->: value out of dist range: %0d\n",
|
||||
`__FILE__, `__LINE__, obj.a[i]);
|
||||
$stop;
|
||||
end
|
||||
if (obj.a[i] == 0) seen_zero++;
|
||||
else seen_nonzero++;
|
||||
end
|
||||
end
|
||||
if (seen_zero == 0 || seen_nonzero == 0) begin
|
||||
$write("%%Error: %s:%0d: dist inside foreach+->: not all buckets hit (zero=%0d nonzero=%0d)\n",
|
||||
`__FILE__, `__LINE__, seen_zero, seen_nonzero);
|
||||
$stop;
|
||||
end
|
||||
end
|
||||
|
||||
// Test doubly-nested -> (chained implication) form
|
||||
begin
|
||||
static ClsImplChained obj = new();
|
||||
int seen_zero, seen_nonzero;
|
||||
obj.gateA = 1'b1;
|
||||
obj.gateB = 1'b1;
|
||||
seen_zero = 0;
|
||||
seen_nonzero = 0;
|
||||
repeat (100) begin
|
||||
`checkd(obj.randomize(), 1)
|
||||
foreach (obj.a[i]) begin
|
||||
if (obj.a[i] > 4) begin
|
||||
$write("%%Error: %s:%0d: ->->: value out of dist range: %0d\n",
|
||||
`__FILE__, `__LINE__, obj.a[i]);
|
||||
$stop;
|
||||
end
|
||||
if (obj.a[i] == 0) seen_zero++;
|
||||
else seen_nonzero++;
|
||||
end
|
||||
end
|
||||
if (seen_zero == 0 || seen_nonzero == 0) begin
|
||||
$write("%%Error: %s:%0d: dist inside foreach+->->: not all buckets hit (zero=%0d nonzero=%0d)\n",
|
||||
`__FILE__, `__LINE__, seen_zero, seen_nonzero);
|
||||
$stop;
|
||||
end
|
||||
end
|
||||
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
|
|
@ -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,179 @@
|
|||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Antmicro
|
||||
// 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);
|
||||
`define check_tol(gotv,expv) `check_range((gotv), (expv)*(100-TOL_PCT)/100, (expv)*(100+TOL_PCT)/100)
|
||||
// verilog_format: on
|
||||
|
||||
// Scalar: uniform over [0:9] (10% each)
|
||||
class DistScalarRange;
|
||||
rand bit [3:0] x;
|
||||
constraint c { x dist {[4'd0:4'd9] := 1}; }
|
||||
endclass
|
||||
|
||||
// Array foreach: uniform over [0:4] (20% each per element)
|
||||
class DistForeachUniform;
|
||||
rand bit [2:0] a[5];
|
||||
constraint c { foreach (a[i]) a[i] dist {[3'd0:3'd4] := 1}; }
|
||||
endclass
|
||||
|
||||
// Array foreach: mixed single + range
|
||||
// a[i] dist {0 := 5, [1:9] := 1} total weight = 5+9 = 14
|
||||
// 0: 5/14 ~= 35.7%, 1..9: 1/14 ~= 7.1% each
|
||||
class DistForeachMixed;
|
||||
rand bit [3:0] a[5];
|
||||
constraint c { foreach (a[i]) a[i] dist {4'd0 := 5, [4'd1:4'd9] := 1}; }
|
||||
endclass
|
||||
|
||||
// Scalar signed int: uniform over negative range [-9:0] (10% each)
|
||||
class DistNegRange;
|
||||
rand int x;
|
||||
constraint c { x dist {[-9:0] := 1}; }
|
||||
endclass
|
||||
|
||||
// Non-constant unsigned range bounds: uniform over [lo_val:hi_val] = [2:7] (6 values)
|
||||
class DistVarRangeUnsigned;
|
||||
rand bit [3:0] x;
|
||||
bit [3:0] lo_val = 4'd2, hi_val = 4'd7;
|
||||
constraint c { x dist {[lo_val:hi_val] := 1}; }
|
||||
endclass
|
||||
|
||||
// Mixed const/non-const bounds: lo is constant, hi is a variable [1:hi_val] = [1:7]
|
||||
class DistMixedBounds;
|
||||
rand bit [3:0] x;
|
||||
bit [3:0] hi_val = 4'd7;
|
||||
constraint c { x dist {[4'd1:hi_val] := 1}; }
|
||||
endclass
|
||||
|
||||
module t;
|
||||
parameter int N = 2000; // randomize() calls per test
|
||||
parameter int TOL_PCT = 30; // +-% tolerance on expected counts
|
||||
|
||||
initial begin
|
||||
|
||||
// --- T1: scalar uniform [0:9] ---
|
||||
// 10 values, expected N/10 each
|
||||
begin
|
||||
automatic DistScalarRange obj = new();
|
||||
int cnt[10];
|
||||
foreach (cnt[v]) cnt[v] = 0;
|
||||
repeat (N) begin
|
||||
`checkd(obj.randomize(), 1)
|
||||
if (obj.x > 4'd9) begin
|
||||
$write("%%Error: x=%0d outside valid range [0:9]\n", obj.x);
|
||||
`stop;
|
||||
end
|
||||
cnt[obj.x]++;
|
||||
end
|
||||
foreach (cnt[v])
|
||||
`check_tol(cnt[v], N/10)
|
||||
end
|
||||
|
||||
// --- T2: array foreach uniform [0:4], 5 elements * N calls ---
|
||||
// total element samples = N*5; 5 values -> expected N each
|
||||
begin
|
||||
automatic DistForeachUniform obj = new();
|
||||
int cnt[5];
|
||||
foreach (cnt[v]) cnt[v] = 0;
|
||||
repeat (N) begin
|
||||
`checkd(obj.randomize(), 1)
|
||||
foreach (obj.a[i]) begin
|
||||
if (obj.a[i] > 3'd4) begin
|
||||
$write("%%Error: a[%0d]=%0d outside valid range [0:4]\n", i, obj.a[i]);
|
||||
`stop;
|
||||
end
|
||||
cnt[obj.a[i]]++;
|
||||
end
|
||||
end
|
||||
foreach (cnt[v])
|
||||
`check_tol(cnt[v], N)
|
||||
end
|
||||
|
||||
// --- T3: array foreach mixed {0:=5, [1:9]:=1}, 5 elements * N calls ---
|
||||
// total element samples = N*5; total weight = 5+9 = 14
|
||||
// v=0: expected N*5*5/14
|
||||
// v=1..9: expected N*5*1/14
|
||||
begin
|
||||
automatic DistForeachMixed obj = new();
|
||||
int cnt[10];
|
||||
foreach (cnt[v]) cnt[v] = 0;
|
||||
repeat (N) begin
|
||||
`checkd(obj.randomize(), 1)
|
||||
foreach (obj.a[i]) begin
|
||||
if (obj.a[i] > 4'd9) begin
|
||||
$write("%%Error: a[%0d]=%0d outside valid range [0:9]\n", i, obj.a[i]);
|
||||
`stop;
|
||||
end
|
||||
cnt[obj.a[i]]++;
|
||||
end
|
||||
end
|
||||
`check_tol(cnt[0], N*5*5/14)
|
||||
for (int v = 1; v <= 9; v++)
|
||||
`check_tol(cnt[v], N*5*1/14)
|
||||
end
|
||||
|
||||
// --- T4: signed int, uniform over negative range [-9:0] ---
|
||||
// 10 values, expected N/10 each
|
||||
// cnt[v] = count of (x == v-9), so v=0 -> x=-9, v=9 -> x=0
|
||||
begin
|
||||
automatic DistNegRange obj = new();
|
||||
int cnt[10];
|
||||
foreach (cnt[v]) cnt[v] = 0;
|
||||
repeat (N) begin
|
||||
`checkd(obj.randomize(), 1)
|
||||
if (obj.x < -9 || obj.x > 0) begin
|
||||
$write("%%Error: x=%0d outside valid range [-9:0]\n", obj.x);
|
||||
`stop;
|
||||
end
|
||||
cnt[obj.x + 9]++;
|
||||
end
|
||||
foreach (cnt[v])
|
||||
`check_tol(cnt[v], N/10)
|
||||
end
|
||||
|
||||
// --- T5: non-constant unsigned range bounds [lo_val:hi_val] = [2:7] ---
|
||||
// 6 values, expected N/6 each
|
||||
begin
|
||||
automatic DistVarRangeUnsigned obj = new();
|
||||
int cnt[16];
|
||||
foreach (cnt[v]) cnt[v] = 0;
|
||||
repeat (N) begin
|
||||
`checkd(obj.randomize(), 1)
|
||||
if (obj.x < 4'd2 || obj.x > 4'd7) begin
|
||||
$write("%%Error: x=%0d outside valid range [2:7]\n", obj.x);
|
||||
`stop;
|
||||
end
|
||||
cnt[obj.x]++;
|
||||
end
|
||||
for (int v = 2; v <= 7; v++)
|
||||
`check_tol(cnt[v], N/6)
|
||||
end
|
||||
|
||||
// --- T6: mixed const/non-const bounds [4'd1:hi_val] = [1:7] ---
|
||||
// 7 values, expected N/7 each
|
||||
begin
|
||||
automatic DistMixedBounds obj = new();
|
||||
int cnt[16];
|
||||
foreach (cnt[v]) cnt[v] = 0;
|
||||
repeat (N) begin
|
||||
`checkd(obj.randomize(), 1)
|
||||
if (obj.x < 4'd1 || obj.x > 4'd7) begin
|
||||
$write("%%Error: x=%0d outside valid range [1:7]\n", obj.x);
|
||||
`stop;
|
||||
end
|
||||
cnt[obj.x]++;
|
||||
end
|
||||
for (int v = 1; v <= 7; v++)
|
||||
`check_tol(cnt[v], N/7)
|
||||
end
|
||||
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
Loading…
Reference in New Issue