diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index b9f175fe1..0fdb99843 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -170,13 +170,21 @@ class RandomizeMarkVisitor final : public VNVisitor { if (VN_IS(pinp, With)) continue; const AstArg* const argp = VN_CAST(pinp, Arg); if (!argp) continue; - const AstNodeExpr* const exprp = argp->exprp(); - if (const AstNodeVarRef* const varrefp = VN_CAST(exprp, NodeVarRef)) { - if (varrefp->varp() == varp) return true; - } else if (const AstMemberSel* const memberselp = VN_CAST(exprp, MemberSel)) { - if (memberselp->varp() == varp) return true; - } else if (const AstArraySel* const arrselp = VN_CAST(exprp, ArraySel)) { - if (VN_AS(arrselp->fromp(), VarRef)->varp() == varp) return true; + const AstNodeExpr* exprp = argp->exprp(); + // Traverse through ArraySel and MemberSel to find the base variable + while (exprp) { + if (const AstNodeVarRef* const varrefp = VN_CAST(exprp, NodeVarRef)) { + if (varrefp->varp() == varp) return true; + break; + } + if (const AstMemberSel* const memberselp = VN_CAST(exprp, MemberSel)) { + if (memberselp->varp() == varp) return true; + exprp = memberselp->fromp(); + } else if (const AstArraySel* const arrselp = VN_CAST(exprp, ArraySel)) { + exprp = arrselp->fromp(); + } else { + break; + } } } return false; @@ -535,22 +543,31 @@ class RandomizeMarkVisitor final : public VNVisitor { AstArg* const argp = VN_CAST(pinp, Arg); if (!argp) continue; AstNodeExpr* exprp = argp->exprp(); + // IEEE 1800-2023 18.11: "Arguments are limited to the names of properties + // of the calling object; expressions are not allowed." + // However, for compatibility with other simulators, we support complex + // expressions like obj.member[idx] in std::randomize(). while (exprp) { AstVar* randVarp = nullptr; AstVarRef* varrefp = nullptr; if (AstMemberSel* const memberSelp = VN_CAST(exprp, MemberSel)) { randVarp = memberSelp->varp(); exprp = memberSelp->fromp(); + } else if (AstArraySel* const arraySelp = VN_CAST(exprp, ArraySel)) { + // Check if child is VarRef and mark it + if (AstVarRef* const childVarRefp = VN_CAST(arraySelp->fromp(), VarRef)) { + childVarRefp->access(VAccess::READWRITE); + } + exprp = arraySelp->fromp(); + continue; // Skip ArraySel, continue traversing } else if ((varrefp = VN_CAST(exprp, VarRef))) { randVarp = varrefp->varp(); varrefp->user1(true); exprp = nullptr; } else { - varrefp = VN_AS(VN_CAST(exprp, ArraySel)->fromp(), VarRef); - randVarp = varrefp->varp(); - varrefp->user1(true); - varrefp->access(VAccess::READWRITE); - exprp = nullptr; + // All invalid and unsupported expressions should be caught in V3Width + nodep->v3fatalSrc( + "Unexpected expression type in std::randomize() argument"); } UASSERT_OBJ(randVarp, nodep, "No rand variable found"); AstNode* backp = randVarp; @@ -578,15 +595,28 @@ class RandomizeMarkVisitor final : public VNVisitor { fromVarp = varrefp->varp(); } } + // IEEE 1800-2023 18.11: "Arguments are limited to the names of properties + // of the calling object; expressions are not allowed." + // However, for compatibility with other simulators, we support complex + // expressions like obj.member[idx].field in inline randomize(). while (exprp) { AstVar* randVarp = nullptr; if (AstMemberSel* const memberSelp = VN_CAST(exprp, MemberSel)) { randVarp = memberSelp->varp(); exprp = memberSelp->fromp(); - } else { - AstVarRef* const varrefp = VN_AS(exprp, VarRef); + } else if (AstArraySel* const arraySelp = VN_CAST(exprp, ArraySel)) { + // Check if child is VarRef and mark it + if (AstVarRef* const childVarRefp = VN_CAST(arraySelp->fromp(), VarRef)) { + childVarRefp->access(VAccess::READWRITE); + } + exprp = arraySelp->fromp(); + continue; // Skip ArraySel, continue traversing + } else if (AstVarRef* const varrefp = VN_CAST(exprp, VarRef)) { randVarp = varrefp->varp(); exprp = nullptr; + } else { + // All invalid and unsupported expressions should be caught in V3Width + nodep->v3fatalSrc("Unexpected expression type in randomize() argument"); } if (randVarp == fromVarp) break; UASSERT_OBJ(randVarp, nodep, "No rand variable found"); diff --git a/test_regress/t/t_randomize_arraysel_membersel.py b/test_regress/t/t_randomize_arraysel_membersel.py new file mode 100755 index 000000000..466368b3d --- /dev/null +++ b/test_regress/t/t_randomize_arraysel_membersel.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2025 by Wilson Snyder. 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-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_randomize_arraysel_membersel.v b/test_regress/t/t_randomize_arraysel_membersel.v new file mode 100644 index 000000000..b2d90668b --- /dev/null +++ b/test_regress/t/t_randomize_arraysel_membersel.v @@ -0,0 +1,107 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2025 by Antmicro. +// SPDX-License-Identifier: CC0-1.0 + +class inner_class; + logic [3:0] c; + logic [3:0] d[2]; +endclass + +class test_class; + logic [3:0] member[5]; + logic [3:0] member_2d[3][4]; + inner_class b; +endclass + +module t; + initial begin + test_class example; + example = new; + example.b = new; + + // Simple array element access + repeat(5) begin + if (std::randomize(example.member[1]) with {example.member[1] inside {[0 : 3]};} == 0) begin + $stop; + end + if (example.member[1] > 3) begin + $stop; + end + end + + // Different array indices + repeat(5) begin + if (std::randomize(example.member[0]) with {example.member[0] inside {[5 : 7]};} == 0) begin + $stop; + end + if (example.member[0] < 5 || example.member[0] > 7) begin + $stop; + end + end + + // Last element + repeat(5) begin + if (std::randomize(example.member[4]) with {example.member[4] inside {[10 : 15]};} == 0) begin + $stop; + end + if (example.member[4] < 10 || example.member[4] > 15) begin + $stop; + end + end + + // 2D array access + repeat(5) begin + if (std::randomize(example.member_2d[1][2]) with {example.member_2d[1][2] inside {[8 : 12]};} == 0) begin + $stop; + end + if (example.member_2d[1][2] < 8 || example.member_2d[1][2] > 12) begin + $stop; + end + end + + // 2D array different indices + repeat(5) begin + if (std::randomize(example.member_2d[0][0]) with {example.member_2d[0][0] inside {[1 : 4]};} == 0) begin + $stop; + end + if (example.member_2d[0][0] < 1 || example.member_2d[0][0] > 4) begin + $stop; + end + end + + // Nested object: obj.b.c + repeat(5) begin + if (std::randomize(example.b.c) with {example.b.c inside {[5 : 9]};} == 0) begin + $stop; + end + if (example.b.c < 5 || example.b.c > 9) begin + $stop; + end + end + + // Nested object with array: obj.b.d[0] + repeat(5) begin + if (std::randomize(example.b.d[0]) with {example.b.d[0] inside {[11 : 14]};} == 0) begin + $stop; + end + if (example.b.d[0] < 11 || example.b.d[0] > 14) begin + $stop; + end + end + + // Nested object with array: obj.b.d[1] (different index) + repeat(5) begin + if (std::randomize(example.b.d[1]) with {example.b.d[1] inside {[2 : 6]};} == 0) begin + $stop; + end + if (example.b.d[1] < 2 || example.b.d[1] > 6) begin + $stop; + end + end + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule