diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 52a750a6a..36699526d 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -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(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}; + } 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(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( + new AstExtendS{fl, irp->lhsp()->cloneTreePure(false), 64}) + : static_cast( + new AstExtend{fl, irp->lhsp()->cloneTreePure(false), 64}); + lo64p->dtypeSetUInt64(); + AstNodeExpr* const hi64p + = isSigned + ? static_cast( + new AstExtendS{fl, irp->rhsp()->cloneTreePure(false), 64}) + : static_cast( + 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) diff --git a/test_regress/t/t_constraint_dist.v b/test_regress/t/t_constraint_dist.v index 7d27d8748..3b62845a3 100644 --- a/test_regress/t/t_constraint_dist.v +++ b/test_regress/t/t_constraint_dist.v @@ -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 diff --git a/test_regress/t/t_constraint_dist_foreach_if.py b/test_regress/t/t_constraint_dist_foreach_if.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_constraint_dist_foreach_if.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_foreach_if.v b/test_regress/t/t_constraint_dist_foreach_if.v new file mode 100644 index 000000000..3ae315711 --- /dev/null +++ b/test_regress/t/t_constraint_dist_foreach_if.v @@ -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 diff --git a/test_regress/t/t_constraint_dist_range.py b/test_regress/t/t_constraint_dist_range.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_constraint_dist_range.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_range.v b/test_regress/t/t_constraint_dist_range.v new file mode 100644 index 000000000..fbddaf0a1 --- /dev/null +++ b/test_regress/t/t_constraint_dist_range.v @@ -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