diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 7da70687b..a42397298 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -878,6 +878,26 @@ class ConstraintExprVisitor final : public VNVisitor { return selp; } + // Convert expression to target width using extend or truncate as needed + AstNodeExpr* adjustWidth(FileLine* fl, AstNodeExpr* exprp, int targetWidth, + VSigning targetSigning) { + const int exprWidth = exprp->width(); + if (targetWidth > exprWidth) { + // Extend to match target width + AstNodeExpr* const result = new AstExtend{fl, exprp, targetWidth}; + result->dtypeSetLogicSized(targetWidth, targetSigning); + return result; + } else if (targetWidth < exprWidth) { + // Truncate to match target width + AstNodeExpr* const result = new AstSel{fl, exprp, 0, targetWidth}; + result->dtypeSetLogicSized(targetWidth, targetSigning); + return result; + } else { + // Width already matches + return exprp; + } + } + // Extract return expression from a simple function body, or nullptr if too complex. // Uses foreach to walk all assigns regardless of body organization (JumpBlock nesting etc.). // Takes the last assignment to the return variable -- the first is the initializer. @@ -1284,13 +1304,8 @@ class ConstraintExprVisitor final : public VNVisitor { FileLine* const fl = nodep->fileline(); AstNodeExpr* const argp = nodep->lhsp()->unlinkFrBack(); AstNodeExpr* sump = buildCountOnesExpansion(fl, argp, nodep); - if (nodep->width() > sump->width()) { - sump = new AstExtend{fl, sump, nodep->width()}; - sump->user1(true); - } else if (nodep->width() < sump->width()) { - sump = new AstSel{fl, sump, 0, nodep->width()}; - sump->user1(true); - } + sump = adjustWidth(fl, sump, nodep->width(), nodep->dtypep()->numeric()); + sump->user1(true); nodep->replaceWith(sump); VL_DO_DANGLING(nodep->deleteTree(), nodep); iterate(sump); @@ -1407,13 +1422,8 @@ class ConstraintExprVisitor final : public VNVisitor { VL_DO_DANGLING(argp->deleteTree(), argp); } - if (nodep->width() > sump->width()) { - sump = new AstExtend{fl, sump, nodep->width()}; - sump->user1(true); - } else if (nodep->width() < sump->width()) { - sump = new AstSel{fl, sump, 0, nodep->width()}; - sump->user1(true); - } + sump = adjustWidth(fl, sump, nodep->width(), nodep->dtypep()->numeric()); + sump->user1(true); nodep->replaceWith(sump); VL_DO_DANGLING(nodep->deleteTree(), nodep); @@ -2114,20 +2124,6 @@ class ConstraintExprVisitor final : public VNVisitor { return; } } - - bool hasItemIndex = false; - withp->exprp()->foreach([&](AstLambdaArgRef* refp) { - if (refp->index()) hasItemIndex = true; - }); - if (hasItemIndex) { - nodep->v3warn(CONSTRAINTIGN, - "Unsupported: item.index in array reduction constraint 'with' " - "clause; treating as state"); - nodep->user1(false); - if (editFormat(nodep)) return; - nodep->v3fatalSrc("Method not handled in constraints? " << nodep); - return; - } } // For without 'with' clause: need random array for variable registration @@ -2210,20 +2206,47 @@ class ConstraintExprVisitor final : public VNVisitor { AstNodeExpr* const elemSelp = newSel(fl, nodep->fromp(), idxRefp); elemSelp->user1(randArr); + // Get the result width for the reduction + const int resultWidth = nodep->dtypep()->width(); + const VSigning resultSigning = nodep->dtypep()->numeric(); + AstNode* perElemExprp = withp->exprp()->cloneTreePure(false); if (AstLambdaArgRef* const rootRefp = VN_CAST(perElemExprp, LambdaArgRef)) { - perElemExprp = elemSelp->cloneTreePure(false); + if (rootRefp->index()) { + // item.index at root -> replace with loop variable, adjust width + AstVarRef* const loopRef = new AstVarRef{fl, loopVarp, VAccess::READ}; + perElemExprp = adjustWidth(fl, loopRef, resultWidth, resultSigning); + } else { + // item at root -> replace with array element + perElemExprp = elemSelp->cloneTreePure(false); + } VL_DO_DANGLING(rootRefp->deleteTree(), rootRefp); } else { perElemExprp->foreach([&](AstLambdaArgRef* refp) { - refp->replaceWith(elemSelp->cloneTreePure(false)); + if (refp->index()) { + // item.index -> replace with loop variable, adjust width + AstVarRef* loopRef = new AstVarRef{fl, loopVarp, VAccess::READ}; + AstNode* const replacement + = adjustWidth(fl, loopRef, resultWidth, resultSigning); + refp->replaceWith(replacement); + } else { + // item -> replace with array element + refp->replaceWith(elemSelp->cloneTreePure(false)); + } VL_DO_DANGLING(pushDeletep(refp), refp); }); } VL_DO_DANGLING(elemSelp->deleteTree(), elemSelp); - perElemExprp->foreach([](AstNode* nodep) { - if (!VN_IS(nodep, Const)) nodep->user1(true); + perElemExprp->foreach([&](AstNode* nodep) { + // Don't mark loop variable references as randomizable + if (!VN_IS(nodep, Const)) { + if (AstNodeVarRef* const vrefp = VN_CAST(nodep, NodeVarRef)) { + if (vrefp->varp() != loopVarp) nodep->user1(true); + } else { + nodep->user1(true); + } + } }); cstmtp->add("ret = \"(" + std::string(smtOp) + " \" + ret + \" \";\n"); diff --git a/test_regress/t/t_constraint_array_index_unsup.py b/test_regress/t/t_constraint_array_index.py similarity index 89% rename from test_regress/t/t_constraint_array_index_unsup.py rename to test_regress/t/t_constraint_array_index.py index d85bfeb37..ab048b5e8 100755 --- a/test_regress/t/t_constraint_array_index_unsup.py +++ b/test_regress/t/t_constraint_array_index.py @@ -14,6 +14,8 @@ test.scenarios('simulator') if not test.have_solver: test.skip("No constraint solver installed") -test.compile(fails=True, expect_filename=test.golden_filename) +test.compile() + +test.execute() test.passes() diff --git a/test_regress/t/t_constraint_array_index.v b/test_regress/t/t_constraint_array_index.v new file mode 100644 index 000000000..286276f77 --- /dev/null +++ b/test_regress/t/t_constraint_array_index.v @@ -0,0 +1,234 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2024 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +`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); + +// Test that item.index in array reduction constraints is supported +// Test 1: Basic item.index usage with sum +class Test1_BasicIndex; + rand bit [3:0] data[5]; + + constraint c { + // Sum of indices: 0+1+2+3+4 = 10 + // Cast to match result type (32-bit) + data.sum() with (item.index) <= 10; + } +endclass + +// Test 2: item.index with item in expression +class Test2_IndexPlusItem; + rand bit [3:0] data[5]; + + constraint c { + // Cast both to same width for expression + data.sum() with (int'(item) + item.index) <= 50; + } +endclass + +// Test 3: Conditional based on index +class Test3_ConditionalOnIndex; + rand bit [3:0] data[5]; + + constraint c { + // Only sum items at even indices + data.sum() with ((item.index % 2 == 0) ? int'(item) : 0) <= 20; + } +endclass + +// Test 4: item.index with product +class Test4_ProductWithIndex; + rand bit [2:0] data[4]; + + constraint c { + // Product of (item + index + 1) to avoid zero + data.product() with (int'(item) + item.index + 1) <= 1000; + } +endclass + +// Test 5: item.index with xor (using 32-bit result) +class Test5_XorWithIndex; + rand int data[4]; + + constraint c { + // Use int array so no casting needed + data.xor() with (item ^ item.index) >= 0; + } +endclass + +// Test 6: item.index with and (using 32-bit result) +class Test6_AndWithIndex; + rand int data[4]; + + constraint c { + data.and() with (item | item.index) != 0; + } +endclass + +// Test 7: item.index with or (using 32-bit result) +class Test7_OrWithIndex; + rand int data[4]; + + constraint c { + data.or() with (item & item.index) >= 0; + } +endclass + +// Test 8: Complex expression with multiple uses +class Test8_ComplexExpression; + rand bit [3:0] data[6]; + + constraint c { + // Use item.index multiple times in expression + data.sum() with (int'(item) * item.index + item.index) <= 100; + } +endclass + +// Test 9: Just item.index (no item reference) +class Test9_JustIndex; + rand bit [3:0] data[5]; + + constraint c { + // This tests sum of just indices + data.sum() with (item.index * 2) == 20; // 0+2+4+6+8 = 20 + } +endclass + +// Test 10: Dynamic array with item.index +class Test10_DynamicArray; + rand bit [3:0] dyn_data[]; + + function new(); + dyn_data = new[5]; + endfunction + + constraint c_size { + dyn_data.size() == 5; + } + + constraint c { + // item.index with dynamic array + dyn_data.sum() with (item.index) <= 10; + } +endclass + +// Test 11: Larger array (16 elements) +class Test11_LargerArray; + rand bit [3:0] data[16]; + + constraint c { + // Sum of indices: 0+1+2+...+15 = 120 + data.sum() with (item.index) <= 120; + } +endclass + +// Test 12: Large array with complex expression +class Test12_LargeComplex; + rand int data[20]; + + constraint c { + // Complex expression with larger array + data.sum() with ((item.index < 10) ? item : item * 2) <= 5000; + foreach (data[i]) data[i] inside {[1:100]}; + } +endclass + +// Test 13: Very large array (stress test) +class Test13_VeryLargeArray; + rand int data[50]; + + constraint c { + // Test scalability - sum of first 50 indices = 1225 + data.sum() with (item.index) == 1225; + } +endclass + +module t; + initial begin + Test1_BasicIndex t1; + Test2_IndexPlusItem t2; + Test3_ConditionalOnIndex t3; + Test4_ProductWithIndex t4; + Test5_XorWithIndex t5; + Test6_AndWithIndex t6; + Test7_OrWithIndex t7; + Test8_ComplexExpression t8; + Test9_JustIndex t9; + Test10_DynamicArray t10; + Test11_LargerArray t11; + Test12_LargeComplex t12; + Test13_VeryLargeArray t13; + int i; + + // Test 1: Basic index + t1 = new; + i = t1.randomize(); + `checkd(i, 1) + + // Test 2: Index plus item + t2 = new; + i = t2.randomize(); + `checkd(i, 1) + + // Test 3: Conditional on index + t3 = new; + i = t3.randomize(); + `checkd(i, 1) + + // Test 4: Product with index + t4 = new; + i = t4.randomize(); + `checkd(i, 1) + + // Test 5: XOR with index + t5 = new; + i = t5.randomize(); + `checkd(i, 1) + + // Test 6: AND with index + t6 = new; + i = t6.randomize(); + `checkd(i, 1) + + // Test 7: OR with index + t7 = new; + i = t7.randomize(); + `checkd(i, 1) + + // Test 8: Complex expression + t8 = new; + i = t8.randomize(); + `checkd(i, 1) + + // Test 9: Just index + t9 = new; + i = t9.randomize(); + `checkd(i, 1) + + // Test 10: Dynamic array + t10 = new; + i = t10.randomize(); + `checkd(i, 1) + + // Test 11: Larger array (16 elements) + t11 = new; + i = t11.randomize(); + `checkd(i, 1) + + // Test 12: Large complex + t12 = new; + i = t12.randomize(); + `checkd(i, 1) + + // Test 13: Very large array (50 elements) + t13 = new; + i = t13.randomize(); + `checkd(i, 1) + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_constraint_array_index_simple.py b/test_regress/t/t_constraint_array_index_simple.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_constraint_array_index_simple.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_array_index_simple.v b/test_regress/t/t_constraint_array_index_simple.v new file mode 100644 index 000000000..42ff3eba2 --- /dev/null +++ b/test_regress/t/t_constraint_array_index_simple.v @@ -0,0 +1,415 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +`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); + +// Comprehensive test that item.index in array reduction constraints is supported + +// Test 1: Basic item.index with sum +class Test1_BasicSum; + rand int data[5]; + + constraint c { + // Use item.index in sum - indices 0,1,2,3,4 sum to 10 + data.sum() with (item.index) <= 10; + } +endclass + +// Test 2: item.index with arithmetic +class Test2_IndexTimesTwo; + rand int data[5]; + + constraint c { + // Index * 2: 0,2,4,6,8 sum to 20 + data.sum() with (item.index * 2) == 20; + } +endclass + +// Test 3: item + item.index +class Test3_ItemPlusIndex; + rand int data[3]; + + constraint c { + // item + index for each element + data.sum() with (item + item.index) <= 50; + } +endclass + +// Test 4: item.index with product +class Test4_Product; + rand int data[4]; + + constraint c { + // Product with index+1 to avoid zeros + data.product() with (item.index + 1) <= 24; // 1*2*3*4 = 24 + } +endclass + +// Test 5: item.index with conditional +class Test5_Conditional; + rand int data[6]; + + constraint c { + // Only sum indices that are even + data.sum() with ((item.index % 2 == 0) ? item.index : 0) <= 6; // 0+2+4 = 6 + } +endclass + +// Test 6: item.index with XOR +class Test6_Xor; + rand int data[4]; + + constraint c { + data.xor() with (item.index) >= 0; + } +endclass + +// Test 7: item.index with AND +class Test7_And; + rand int data[4]; + + constraint c { + data.and() with (item + item.index) != 0; + } +endclass + +// Test 8: item.index with OR +class Test8_Or; + rand int data[4]; + + constraint c { + data.or() with (item - item.index) >= -10; + } +endclass + +// Test 9: Complex expression with item.index +class Test9_Complex; + rand int data[4]; + + constraint c { + // Multiple uses of item.index + data.sum() with ((item.index * item.index) + item) <= 100; + } +endclass + +// Test 10: Dynamic array +class Test10_Dynamic; + rand int data[]; + + function new(); + data = new[4]; + endfunction + + constraint c_size { + data.size() == 4; + } + + constraint c { + data.sum() with (item.index) <= 6; // 0+1+2+3 = 6 + } +endclass + +// Test 11: Larger array +class Test11_LargerArray; + rand int data[10]; + + constraint c { + data.sum() with (item.index) == 45; // 0+1+2+...+9 = 45 + } +endclass + +// Test 12: item.index with negative values +class Test12_Negative; + rand int data[5]; + + constraint c { + data.sum() with (item.index - 2) <= 5; // -2,-1,0,1,2 = 0 + } +endclass + +// Test 13: Just item (no index) - baseline test +class Test13_JustItem; + rand int data[3]; + + constraint c { + data.sum() with (item) <= 30; + } +endclass + +// Test 14: item.index only (no item reference) +class Test14_IndexOnly; + rand int data[5]; + + constraint c { + data.sum() with (item.index + 10) <= 60; // 10,11,12,13,14 = 60 + } +endclass + +// Test 15: Position-dependent weighting +class Test15_PositionWeight; + rand int data[6]; + + constraint c { + // Later elements weighted more heavily: item * index + data.sum() with (item * item.index) <= 200; + foreach (data[i]) data[i] inside {[1:10]}; + } +endclass + +// Test 16: Alternating pattern based on index +class Test16_AlternatingPattern; + rand int data[8]; + + constraint c { + // Alternating positive/negative based on even/odd index + data.sum() with ((item.index % 2 != 0) ? item : -item) >= -20; + data.sum() with ((item.index % 2 != 0) ? item : -item) <= 20; + foreach (data[i]) data[i] inside {[1:10]}; + } +endclass + +// Test 17: Index-based bounds +class Test17_IndexBounds; + rand int data[5]; + + constraint c { + // Each element should be within [0, index*2] + data.sum() with ((item >= 0 && item <= item.index*2) ? item : 0) <= 40; + foreach (data[i]) data[i] inside {[0:i*2]}; + } +endclass + +// Test 18: Progressive constraint +class Test18_Progressive; + rand int data[6]; + + constraint c { + // Count how many elements are greater than their index + data.sum() with ((item > item.index) ? 1 : 0) >= 3; + foreach (data[i]) data[i] inside {[0:10]}; + } +endclass + +// Test 19: Distance from middle +class Test19_DistanceFromMiddle; + rand int data[10]; + + constraint c { + // Weight by distance from middle index + data.sum() with (item * ((item.index < 5) ? item.index : (9 - item.index))) <= 500; + foreach (data[i]) data[i] inside {[1:10]}; + } +endclass + +// Test 20: First half vs second half +class Test20_HalfConstraint; + rand int data[8]; + + constraint c { + // First half sum should be less than second half sum + data.sum() with ((item.index < 4) ? item : 0) <= + data.sum() with ((item.index >= 4) ? item : 0); + foreach (data[i]) data[i] inside {[1:20]}; + } +endclass + +// Test 21: Modulo patterns +class Test21_ModuloPattern; + rand int data[12]; + + constraint c { + // Sum every third element (indices 0,3,6,9) + data.sum() with (((item.index % 3) == 0) ? item : 0) <= 40; + foreach (data[i]) data[i] inside {[1:15]}; + } +endclass + +// Test 22: Index power +class Test22_IndexPower; + rand int data[5]; + + constraint c { + // Use index squared as multiplier + data.sum() with (item * (item.index * item.index)) <= 1000; + foreach (data[i]) data[i] inside {[1:10]}; + } +endclass + +// Test 23: Boundary elements +class Test23_BoundaryElements; + rand int data[10]; + + constraint c { + // First and last elements should dominate + data.sum() with ((item.index == 0 || item.index == 9) ? item*3 : item) <= 150; + foreach (data[i]) data[i] inside {[1:20]}; + } +endclass + +// Test 24: Cascading constraint +class Test24_CascadingConstraint; + rand int data[7]; + + constraint c { + // Each position contributes: item * (index + 1) + data.sum() with (item * (item.index + 1)) <= 300; + foreach (data[i]) data[i] inside {[1:10]}; + } +endclass + +module t; + initial begin + Test1_BasicSum t1; + Test2_IndexTimesTwo t2; + Test3_ItemPlusIndex t3; + Test4_Product t4; + Test5_Conditional t5; + Test6_Xor t6; + Test7_And t7; + Test8_Or t8; + Test9_Complex t9; + Test10_Dynamic t10; + Test11_LargerArray t11; + Test12_Negative t12; + Test13_JustItem t13; + Test14_IndexOnly t14; + Test15_PositionWeight t15; + Test16_AlternatingPattern t16; + Test17_IndexBounds t17; + Test18_Progressive t18; + Test19_DistanceFromMiddle t19; + Test20_HalfConstraint t20; + Test21_ModuloPattern t21; + Test22_IndexPower t22; + Test23_BoundaryElements t23; + Test24_CascadingConstraint t24; + int i; + + // Test 1: Basic sum + t1 = new; + i = t1.randomize(); + `checkd(i, 1) + + // Test 2: Index times two + t2 = new; + i = t2.randomize(); + `checkd(i, 1) + + // Test 3: Item plus index + t3 = new; + i = t3.randomize(); + `checkd(i, 1) + + // Test 4: Product + t4 = new; + i = t4.randomize(); + `checkd(i, 1) + + // Test 5: Conditional + t5 = new; + i = t5.randomize(); + `checkd(i, 1) + + // Test 6: XOR + t6 = new; + i = t6.randomize(); + `checkd(i, 1) + + // Test 7: AND + t7 = new; + i = t7.randomize(); + `checkd(i, 1) + + // Test 8: OR + t8 = new; + i = t8.randomize(); + `checkd(i, 1) + + // Test 9: Complex + t9 = new; + i = t9.randomize(); + `checkd(i, 1) + + // Test 10: Dynamic array + t10 = new; + i = t10.randomize(); + `checkd(i, 1) + + // Test 11: Larger array + t11 = new; + i = t11.randomize(); + `checkd(i, 1) + + // Test 12: Negative + t12 = new; + i = t12.randomize(); + `checkd(i, 1) + + // Test 13: Just item + t13 = new; + i = t13.randomize(); + `checkd(i, 1) + + // Test 14: Index only + t14 = new; + i = t14.randomize(); + `checkd(i, 1) + + // Test 15: Position weight + t15 = new; + i = t15.randomize(); + `checkd(i, 1) + + // Test 16: Alternating pattern + t16 = new; + i = t16.randomize(); + `checkd(i, 1) + + // Test 17: Index bounds + t17 = new; + i = t17.randomize(); + `checkd(i, 1) + + // Test 18: Progressive + t18 = new; + i = t18.randomize(); + `checkd(i, 1) + + // Test 19: Distance from middle + t19 = new; + i = t19.randomize(); + `checkd(i, 1) + + // Test 20: Half constraint + t20 = new; + i = t20.randomize(); + `checkd(i, 1) + + // Test 21: Modulo pattern + t21 = new; + i = t21.randomize(); + `checkd(i, 1) + + // Test 22: Index power + t22 = new; + i = t22.randomize(); + `checkd(i, 1) + + // Test 23: Boundary elements + t23 = new; + i = t23.randomize(); + `checkd(i, 1) + + // Test 24: Cascading constraint + t24 = new; + i = t24.randomize(); + `checkd(i, 1) + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_constraint_array_index_unsup.out b/test_regress/t/t_constraint_array_index_unsup.out deleted file mode 100644 index 85ff611a6..000000000 --- a/test_regress/t/t_constraint_array_index_unsup.out +++ /dev/null @@ -1,6 +0,0 @@ -%Warning-CONSTRAINTIGN: t/t_constraint_array_index_unsup.v:13:10: Unsupported: item.index in array reduction constraint 'with' clause; treating as state - 13 | data.sum() with (item.index) <= 10; - | ^~~ - ... For warning description see https://verilator.org/warn/CONSTRAINTIGN?v=latest - ... Use "/* verilator lint_off CONSTRAINTIGN */" and lint_on around source to disable this message. -%Error: Exiting due to diff --git a/test_regress/t/t_constraint_array_index_unsup.v b/test_regress/t/t_constraint_array_index_unsup.v deleted file mode 100644 index d23621857..000000000 --- a/test_regress/t/t_constraint_array_index_unsup.v +++ /dev/null @@ -1,29 +0,0 @@ -// DESCRIPTION: Verilator: Verilog Test module -// -// This file ONLY is placed under the Creative Commons Public Domain. -// SPDX-FileCopyrightText: 2024 Wilson Snyder -// SPDX-License-Identifier: CC0-1.0 - -// Test that item.index in array reduction constraints is not yet supported -class Packet; - rand bit [3:0] data[5]; - - constraint c { - // This should trigger unsupported warning - data.sum() with (item.index) <= 10; - } -endclass - -module t; - initial begin - Packet p; - int i; - - p = new; - - i = p.randomize(); - - $write("*-* All Finished *-*\n"); - $finish; - end -endmodule