Fix mixed-width inside and dist range bounds failing randomization (#7875)
This commit is contained in:
parent
87d77f0133
commit
f1a8192a36
|
|
@ -4453,6 +4453,39 @@ class RandomizeVisitor final : public VNVisitor {
|
||||||
return new AstConstraintIf{fl, condp, thenBodyp, nullptr};
|
return new AstConstraintIf{fl, condp, thenBodyp, nullptr};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool distBoundRefsRandVar(const AstNode* boundp) {
|
||||||
|
bool found = false;
|
||||||
|
boundp->foreach([&](const AstVarRef* vrefp) {
|
||||||
|
if (vrefp->varp()->rand().isRandomizable()) found = true;
|
||||||
|
});
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
// (distExpr >= lo) && (distExpr <= hi); signed comparisons for signed vars
|
||||||
|
static AstNodeExpr* newDistRangeMembership(AstDist* distp, const AstInsideRange* irp) {
|
||||||
|
FileLine* const fl = distp->fileline();
|
||||||
|
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);
|
||||||
|
AstNodeExpr* const andp = new AstLogAnd{fl, gep, lep};
|
||||||
|
andp->user1(true);
|
||||||
|
return andp;
|
||||||
|
}
|
||||||
|
|
||||||
// Replace AstDist with weighted bucket selection via AstConstraintIf chain.
|
// Replace AstDist with weighted bucket selection via AstConstraintIf chain.
|
||||||
// Supports both constant and variable weight expressions.
|
// Supports both constant and variable weight expressions.
|
||||||
void lowerDistConstraints(AstTask* taskp, AstNode* constrItemsp,
|
void lowerDistConstraints(AstTask* taskp, AstNode* constrItemsp,
|
||||||
|
|
@ -4577,25 +4610,7 @@ class RandomizeVisitor final : public VNVisitor {
|
||||||
for (const auto& bucket : buckets) {
|
for (const auto& bucket : buckets) {
|
||||||
AstNodeExpr* memberp;
|
AstNodeExpr* memberp;
|
||||||
if (const AstInsideRange* const irp = VN_CAST(bucket.rangep, InsideRange)) {
|
if (const AstInsideRange* const irp = VN_CAST(bucket.rangep, InsideRange)) {
|
||||||
// (distExpr >= lo) && (distExpr <= hi); signed comparisons for signed vars
|
memberp = newDistRangeMembership(distp, irp);
|
||||||
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 {
|
} else {
|
||||||
// distExpr == val
|
// distExpr == val
|
||||||
AstNodeExpr* const distExprCopyp = distp->exprp()->cloneTreePure(false);
|
AstNodeExpr* const distExprCopyp = distp->exprp()->cloneTreePure(false);
|
||||||
|
|
@ -4673,7 +4688,13 @@ class RandomizeVisitor final : public VNVisitor {
|
||||||
AstNode* chainp = nullptr;
|
AstNode* chainp = nullptr;
|
||||||
for (int i = static_cast<int>(buckets.size()) - 1; i >= 0; --i) {
|
for (int i = static_cast<int>(buckets.size()) - 1; i >= 0; --i) {
|
||||||
AstNodeExpr* constraintExprp;
|
AstNodeExpr* constraintExprp;
|
||||||
if (const AstInsideRange* const irp = VN_CAST(buckets[i].rangep, InsideRange)) {
|
const AstInsideRange* const irp = VN_CAST(buckets[i].rangep, InsideRange);
|
||||||
|
if (irp
|
||||||
|
&& (distBoundRefsRandVar(irp->lhsp()) || distBoundRefsRandVar(irp->rhsp()))) {
|
||||||
|
// Bounds solved concurrently cannot pin a pre-solve value; softly
|
||||||
|
// prefer the symbolic range so the hard membership stays satisfiable
|
||||||
|
constraintExprp = newDistRangeMembership(distp, irp);
|
||||||
|
} else if (irp) {
|
||||||
// Pick distExpr = lo + rand64() % (hi - lo + 1) for a uniform value in range
|
// Pick distExpr = lo + rand64() % (hi - lo + 1) for a uniform value in range
|
||||||
AstNodeExpr* const distExprCopyp = distp->exprp()->cloneTreePure(false);
|
AstNodeExpr* const distExprCopyp = distp->exprp()->cloneTreePure(false);
|
||||||
distExprCopyp->user1(true);
|
distExprCopyp->user1(true);
|
||||||
|
|
|
||||||
|
|
@ -3464,9 +3464,11 @@ class WidthVisitor final : public VNVisitor {
|
||||||
for (AstNode *nextip, *itemp = nodep->itemsp(); itemp; itemp = nextip) {
|
for (AstNode *nextip, *itemp = nodep->itemsp(); itemp; itemp = nextip) {
|
||||||
nextip = itemp->nextp();
|
nextip = itemp->nextp();
|
||||||
itemp = VN_AS(itemp, DistItem)->rangep();
|
itemp = VN_AS(itemp, DistItem)->rangep();
|
||||||
// InsideRange will get replaced with Lte&Gte and finalized later
|
if (VN_IS(itemp, InsideRange)) {
|
||||||
if (!VN_IS(itemp, InsideRange))
|
userIterate(itemp, WidthVP{subDTypep, FINAL}.p());
|
||||||
|
} else {
|
||||||
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,
|
// Inside a constraint, V3Randomize handles dist lowering with proper weights,
|
||||||
|
|
@ -3541,10 +3543,12 @@ class WidthVisitor final : public VNVisitor {
|
||||||
EXTEND_EXP);
|
EXTEND_EXP);
|
||||||
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
|
||||||
// InsideRange will get replaced with Lte&Gte and finalized later
|
if (VN_IS(itemp, InsideRange)) {
|
||||||
if (!VN_IS(itemp, InsideRange) && !itemp->dtypep()->isNonPackedArray())
|
userIterate(itemp, WidthVP{expDTypep, FINAL}.p());
|
||||||
|
} else if (!itemp->dtypep()->isNonPackedArray()) {
|
||||||
iterateCheck(nodep, "Inside Item", itemp, CONTEXT_DET, FINAL, expDTypep,
|
iterateCheck(nodep, "Inside Item", itemp, CONTEXT_DET, FINAL, expDTypep,
|
||||||
EXTEND_EXP);
|
EXTEND_EXP);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AstNodeExpr* exprp;
|
AstNodeExpr* exprp;
|
||||||
|
|
@ -3636,8 +3640,25 @@ class WidthVisitor final : public VNVisitor {
|
||||||
V3Const::constifyEdit(nodep->lhsp()); // lhsp may change
|
V3Const::constifyEdit(nodep->lhsp()); // lhsp may change
|
||||||
V3Const::constifyEdit(nodep->rhsp()); // rhsp may change
|
V3Const::constifyEdit(nodep->rhsp()); // rhsp may change
|
||||||
} else {
|
} else {
|
||||||
userIterateAndNext(nodep->lhsp(), m_vup);
|
if (m_vup->prelim()) {
|
||||||
userIterateAndNext(nodep->rhsp(), m_vup);
|
userIterateAndNext(nodep->lhsp(), m_vup);
|
||||||
|
userIterateAndNext(nodep->rhsp(), m_vup);
|
||||||
|
}
|
||||||
|
if (m_vup->final()) {
|
||||||
|
AstNodeDType* const expDTypep = m_vup->dtypeOverridep(nodep->dtypep());
|
||||||
|
// Warning waivers match visit_cmp_eq_gt on the lowered Gte/Lte
|
||||||
|
const int expWidth = expDTypep->width();
|
||||||
|
const bool waiveLhs = expWidth == 32
|
||||||
|
&& !(expDTypep->isSigned() && nodep->lhsp()->isSigned())
|
||||||
|
&& expDTypep->widthMin() >= nodep->lhsp()->width();
|
||||||
|
const bool waiveRhs = expWidth == 32
|
||||||
|
&& !(expDTypep->isSigned() && nodep->rhsp()->isSigned())
|
||||||
|
&& expWidth >= nodep->rhsp()->widthMin();
|
||||||
|
iterateCheck(nodep, "Range LHS", nodep->lhsp(), CONTEXT_DET, FINAL, expDTypep,
|
||||||
|
EXTEND_EXP, !waiveLhs);
|
||||||
|
iterateCheck(nodep, "Range RHS", nodep->rhsp(), CONTEXT_DET, FINAL, expDTypep,
|
||||||
|
EXTEND_EXP, !waiveRhs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
nodep->dtypeFrom(nodep->lhsp());
|
nodep->dtypeFrom(nodep->lhsp());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,155 @@
|
||||||
|
// 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);
|
||||||
|
// verilog_format: on
|
||||||
|
|
||||||
|
// verilator lint_off WIDTHEXPAND
|
||||||
|
class Impl;
|
||||||
|
rand bit [63:0] x, y;
|
||||||
|
rand bit [31:0] g;
|
||||||
|
constraint c {
|
||||||
|
g inside {[1 : 10]};
|
||||||
|
y == 64'h100;
|
||||||
|
y != 0 -> x inside {[y - g : y]};
|
||||||
|
}
|
||||||
|
endclass
|
||||||
|
|
||||||
|
class Neg; // inside under logical-not
|
||||||
|
rand bit [63:0] x, y;
|
||||||
|
rand bit [31:0] g;
|
||||||
|
constraint c {
|
||||||
|
g inside {[1 : 10]};
|
||||||
|
y == 64'h100;
|
||||||
|
y != 0 -> !(x inside {[y - g : y - 1]});
|
||||||
|
}
|
||||||
|
endclass
|
||||||
|
|
||||||
|
class LAnd; // inside as a logical-and operand
|
||||||
|
rand bit [63:0] x, y;
|
||||||
|
rand bit [31:0] g;
|
||||||
|
constraint c {
|
||||||
|
g inside {[1 : 10]};
|
||||||
|
y == 64'h100;
|
||||||
|
(x inside {[y - g : y]}) && (x[0] == 1'b0);
|
||||||
|
}
|
||||||
|
endclass
|
||||||
|
|
||||||
|
class Nest; // nested implication a -> (b -> inside)
|
||||||
|
rand bit [63:0] x, y;
|
||||||
|
rand bit [31:0] g;
|
||||||
|
rand bit a, b;
|
||||||
|
constraint c {
|
||||||
|
g inside {[1 : 10]};
|
||||||
|
y == 64'h100;
|
||||||
|
a == 1;
|
||||||
|
b == 1;
|
||||||
|
a -> (b -> x inside {[y - g : y]});
|
||||||
|
}
|
||||||
|
endclass
|
||||||
|
|
||||||
|
class CondCtx; // inside as a ?: condition
|
||||||
|
rand bit [63:0] x, y;
|
||||||
|
rand bit [31:0] g;
|
||||||
|
rand bit s;
|
||||||
|
constraint c {
|
||||||
|
g inside {[1 : 10]};
|
||||||
|
y == 64'h100;
|
||||||
|
(x inside {[y - g : y]}) ? (s == 1'b1) : (s == 1'b0);
|
||||||
|
}
|
||||||
|
endclass
|
||||||
|
|
||||||
|
class Ctl; // all-32-bit control
|
||||||
|
rand bit [31:0] x, y, g;
|
||||||
|
constraint c {
|
||||||
|
g inside {[1 : 10]};
|
||||||
|
y == 32'h100;
|
||||||
|
y != 0 -> x inside {[y - g : y]};
|
||||||
|
}
|
||||||
|
endclass
|
||||||
|
|
||||||
|
class DistRange; // mixed-width dist range bound
|
||||||
|
rand bit [63:0] x, y;
|
||||||
|
rand bit [31:0] g;
|
||||||
|
constraint c {
|
||||||
|
g inside {[1 : 10]};
|
||||||
|
y == 64'h100;
|
||||||
|
x dist {
|
||||||
|
[y - g : y] := 1,
|
||||||
|
5 := 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
endclass
|
||||||
|
|
||||||
|
class Bare; // bare narrow variable as a bound
|
||||||
|
rand bit [63:0] x, y;
|
||||||
|
rand bit [31:0] g;
|
||||||
|
constraint c {
|
||||||
|
g inside {[1 : 10]};
|
||||||
|
y == 64'h100;
|
||||||
|
y != 0 -> x inside {[g : y]};
|
||||||
|
}
|
||||||
|
endclass
|
||||||
|
|
||||||
|
module t;
|
||||||
|
Impl im;
|
||||||
|
Neg ng;
|
||||||
|
LAnd la;
|
||||||
|
Nest ne;
|
||||||
|
CondCtx cx;
|
||||||
|
Ctl ct;
|
||||||
|
DistRange dr;
|
||||||
|
Bare br;
|
||||||
|
int ok;
|
||||||
|
initial begin
|
||||||
|
im = new;
|
||||||
|
ng = new;
|
||||||
|
la = new;
|
||||||
|
ne = new;
|
||||||
|
cx = new;
|
||||||
|
ct = new;
|
||||||
|
dr = new;
|
||||||
|
br = new;
|
||||||
|
for (int i = 0; i < 20; ++i) begin
|
||||||
|
ok = im.randomize();
|
||||||
|
`checkd(ok, 1);
|
||||||
|
if (im.x < (64'h100 - im.g) || im.x > 64'h100) `checkd(0, 1);
|
||||||
|
|
||||||
|
ok = ng.randomize();
|
||||||
|
`checkd(ok, 1);
|
||||||
|
if (ng.x >= (64'h100 - ng.g) && ng.x <= 64'hFF) `checkd(0, 1);
|
||||||
|
|
||||||
|
ok = la.randomize();
|
||||||
|
`checkd(ok, 1);
|
||||||
|
if (la.x < (64'h100 - la.g) || la.x > 64'h100 || la.x[0] !== 1'b0) `checkd(0, 1);
|
||||||
|
|
||||||
|
ok = ne.randomize();
|
||||||
|
`checkd(ok, 1);
|
||||||
|
if (ne.x < (64'h100 - ne.g) || ne.x > 64'h100) `checkd(0, 1);
|
||||||
|
|
||||||
|
ok = cx.randomize();
|
||||||
|
`checkd(ok, 1);
|
||||||
|
if (cx.s !== ((cx.x >= (64'h100 - cx.g)) && (cx.x <= 64'h100))) `checkd(0, 1);
|
||||||
|
|
||||||
|
ok = ct.randomize();
|
||||||
|
`checkd(ok, 1);
|
||||||
|
if (ct.x < (32'h100 - ct.g) || ct.x > 32'h100) `checkd(0, 1);
|
||||||
|
|
||||||
|
ok = dr.randomize();
|
||||||
|
`checkd(ok, 1);
|
||||||
|
if (dr.x != 5 && (dr.x < (64'h100 - dr.g) || dr.x > 64'h100)) `checkd(0, 1);
|
||||||
|
|
||||||
|
ok = br.randomize();
|
||||||
|
`checkd(ok, 1);
|
||||||
|
if (br.x < {32'h0, br.g} || br.x > 64'h100) `checkd(0, 1);
|
||||||
|
end
|
||||||
|
$write("*-* All Finished *-*\n");
|
||||||
|
$finish;
|
||||||
|
end
|
||||||
|
endmodule
|
||||||
|
// verilator lint_on WIDTHEXPAND
|
||||||
Loading…
Reference in New Issue