diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 2786ea305..c0c9e1707 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -4453,6 +4453,39 @@ class RandomizeVisitor final : public VNVisitor { 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( + new AstGteS{fl, distExprGtep, irp->lhsp()->cloneTreePure(false)}) + : static_cast( + new AstGte{fl, distExprGtep, irp->lhsp()->cloneTreePure(false)}); + AstNodeExpr* const lep + = isSigned ? static_cast( + new AstLteS{fl, distExprLtep, irp->rhsp()->cloneTreePure(false)}) + : static_cast( + 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. // Supports both constant and variable weight expressions. void lowerDistConstraints(AstTask* taskp, AstNode* constrItemsp, @@ -4577,25 +4610,7 @@ class RandomizeVisitor final : public VNVisitor { 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(new AstGteS{ - fl, distExprGtep, irp->lhsp()->cloneTreePure(false)}) - : static_cast(new AstGte{ - fl, distExprGtep, irp->lhsp()->cloneTreePure(false)}); - AstNodeExpr* const lep - = isSigned ? static_cast(new AstLteS{ - fl, distExprLtep, irp->rhsp()->cloneTreePure(false)}) - : static_cast(new AstLte{ - fl, distExprLtep, irp->rhsp()->cloneTreePure(false)}); - gep->user1(true); - lep->user1(true); - memberp = new AstLogAnd{fl, gep, lep}; + memberp = newDistRangeMembership(distp, irp); } else { // distExpr == val AstNodeExpr* const distExprCopyp = distp->exprp()->cloneTreePure(false); @@ -4673,7 +4688,13 @@ class RandomizeVisitor final : public VNVisitor { AstNode* chainp = nullptr; for (int i = static_cast(buckets.size()) - 1; i >= 0; --i) { 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 AstNodeExpr* const distExprCopyp = distp->exprp()->cloneTreePure(false); distExprCopyp->user1(true); diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 802fe155d..5eeaf2205 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -3464,9 +3464,11 @@ class WidthVisitor final : public VNVisitor { for (AstNode *nextip, *itemp = nodep->itemsp(); itemp; itemp = nextip) { nextip = itemp->nextp(); 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); + } } // Inside a constraint, V3Randomize handles dist lowering with proper weights, @@ -3541,10 +3543,12 @@ class WidthVisitor final : public VNVisitor { EXTEND_EXP); for (AstNode *nextip, *itemp = nodep->itemsp(); itemp; itemp = nextip) { 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) && !itemp->dtypep()->isNonPackedArray()) + if (VN_IS(itemp, InsideRange)) { + userIterate(itemp, WidthVP{expDTypep, FINAL}.p()); + } else if (!itemp->dtypep()->isNonPackedArray()) { iterateCheck(nodep, "Inside Item", itemp, CONTEXT_DET, FINAL, expDTypep, EXTEND_EXP); + } } AstNodeExpr* exprp; @@ -3636,8 +3640,25 @@ class WidthVisitor final : public VNVisitor { V3Const::constifyEdit(nodep->lhsp()); // lhsp may change V3Const::constifyEdit(nodep->rhsp()); // rhsp may change } else { - userIterateAndNext(nodep->lhsp(), m_vup); - userIterateAndNext(nodep->rhsp(), m_vup); + if (m_vup->prelim()) { + 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()); } diff --git a/test_regress/t/t_constraint_inside_impl_mixwidth.py b/test_regress/t/t_constraint_inside_impl_mixwidth.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_constraint_inside_impl_mixwidth.py @@ -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() diff --git a/test_regress/t/t_constraint_inside_impl_mixwidth.v b/test_regress/t/t_constraint_inside_impl_mixwidth.v new file mode 100644 index 000000000..25cd24ca9 --- /dev/null +++ b/test_regress/t/t_constraint_inside_impl_mixwidth.v @@ -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