From dce9d2056c9bd8e2a8266ea1e990cde497846293 Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Sun, 1 Mar 2026 15:26:15 +0100 Subject: [PATCH] test pass, and reformat --- src/V3Randomize.cpp | 20 +++--- src/V3Width.cpp | 17 ++++- test_regress/t/t_constraint_dist_weight.py | 21 ++++++ test_regress/t/t_constraint_dist_weight.v | 83 ++++++++++++++++++++++ 4 files changed, 128 insertions(+), 13 deletions(-) create mode 100644 test_regress/t/t_constraint_dist_weight.py create mode 100644 test_regress/t/t_constraint_dist_weight.v diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 91db63ba4..94f251c20 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -3360,8 +3360,7 @@ class RandomizeVisitor final : public VNVisitor { uint64_t effectiveW = w; if (!ditemp->isWhole()) { // := weight is per-value; multiply by range size - if (const AstInsideRange* const irp - = VN_CAST(ditemp->rangep(), InsideRange)) { + 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); if (lop && hip && hip->toUQuad() >= lop->toUQuad()) @@ -3392,10 +3391,9 @@ class RandomizeVisitor final : public VNVisitor { 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 AstConst{fl, AstConst::Unsized64{}, totalWeight}}}}); + new AstAdd{fl, new AstConst{fl, AstConst::Unsized64{}, 1}, + new AstModDiv{fl, randp, + new AstConst{fl, AstConst::Unsized64{}, totalWeight}}}}); // Build AstConstraintIf chain (last-to-first so nesting is natural): // if (bucket <= w0) { exprp == range0 } @@ -3428,8 +3426,8 @@ class RandomizeVisitor final : public VNVisitor { // Scalar bucket: exprp == value AstNodeExpr* const exprCopyp = distp->exprp()->cloneTreePure(false); exprCopyp->user1(true); - constraintExprp = new AstEq{fl, exprCopyp, - buckets[i].rangep->cloneTreePure(false)}; + constraintExprp + = new AstEq{fl, exprCopyp, buckets[i].rangep->cloneTreePure(false)}; constraintExprp->user1(true); } @@ -3440,9 +3438,9 @@ class RandomizeVisitor final : public VNVisitor { chainp = thenp; } else { // Bucket condition: bucketVar <= thisCumWeight (NOT rand-dependent) - AstNodeExpr* const condp = new AstLte{ - fl, new AstVarRef{fl, bucketVarp, VAccess::READ}, - new AstConst{fl, AstConst::Unsized64{}, thisCumWeight}}; + AstNodeExpr* const condp + = new AstLte{fl, new AstVarRef{fl, bucketVarp, VAccess::READ}, + new AstConst{fl, AstConst::Unsized64{}, thisCumWeight}}; chainp = new AstConstraintIf{fl, condp, thenp, chainp}; } } diff --git a/src/V3Width.cpp b/src/V3Width.cpp index e87b843f8..54b61192e 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -3088,8 +3088,21 @@ class WidthVisitor final : public VNVisitor { iterateCheck(nodep, "Dist Item", itemp, CONTEXT_DET, FINAL, subDTypep, EXTEND_EXP); } - // Inside a constraint: keep AstDist alive for V3Randomize to do weighted bucket selection - if (m_constraintp) return; + // Inside a constraint: keep AstDist alive for V3Randomize to do weighted bucket + // selection, but only if all items have const weights and simple ranges (scalar or + // InsideRange). Array/queue-based dist items are not supported by lowerDistConstraints. + if (m_constraintp) { + bool canLower = true; + for (const AstDistItem* itemp = nodep->itemsp(); itemp; + itemp = VN_AS(itemp->nextp(), DistItem)) { + if (!VN_IS(itemp->weightp(), Const) + || (!VN_IS(itemp->rangep(), Const) && !VN_IS(itemp->rangep(), InsideRange))) { + canLower = false; + break; + } + } + if (canLower) return; + } // Outside constraint: lower to inside expressions (ignores weights - imperfect) nodep->v3warn(CONSTRAINTIGN, "Constraint expression ignored (imperfect distribution)"); diff --git a/test_regress/t/t_constraint_dist_weight.py b/test_regress/t/t_constraint_dist_weight.py new file mode 100644 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_constraint_dist_weight.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_dist_weight.v b/test_regress/t/t_constraint_dist_weight.v new file mode 100644 index 000000000..e1dbc03fb --- /dev/null +++ b/test_regress/t/t_constraint_dist_weight.v @@ -0,0 +1,83 @@ +// 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 + +// Test that dist constraint weights (:= and :/) produce correct distributions. +// IEEE 1800-2017 18.5.4 + +class DistScalar; + rand bit [7:0] x; + // := weight is per-value: 0 has weight 1, 255 has weight 3 => ~75% should be 255 + constraint c { x dist { 8'd0 := 1, 8'd255 := 3 }; } +endclass + +class DistRange; + rand bit [7:0] x; + // :/ weight is per-range: [0:9] has total weight 1, [10:19] has total weight 3 + constraint c { x dist { [8'd0:8'd9] :/ 1, [8'd10:8'd19] :/ 3 }; } +endclass + +class DistZeroWeight; + rand bit [7:0] x; + // Weight 0 means never selected + constraint c { x dist { 8'd0 := 0, 8'd1 := 1, 8'd2 := 1 }; } +endclass + +module t; + initial begin + DistScalar sc; + DistRange rg; + DistZeroWeight zw; + int count_high; + int count_range_high; + int total; + + total = 2000; + + // Test 1: := 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); // Only 0 or 255 should appear + end + // 75% of 2000 = 1500, allow wide range for statistical test + `check_range(count_high, 1200, 1800); + + // Test 2: :/ 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, 1200, 1800); + + // Test 3: Zero weight exclusion (value 0 should 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 + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule