diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 4a782d821..be99bb632 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -742,6 +742,17 @@ class ConstraintExprVisitor final : public VNVisitor { // (shared across all constraints) std::set* m_sizeConstrainedArraysp = nullptr; // Arrays with size+element constraints + // Walk extends chain to find __Vrandmode variable (user2p on AstClass) + static AstVar* findRandModeVar(AstNodeModule* classp) { + while (classp) { + if (classp->user2p()) return VN_AS(classp->user2p(), Var); + AstClass* const cp = VN_CAST(classp, Class); + if (!cp || !cp->extendsp()) return nullptr; + classp = cp->extendsp()->classp(); + } + return nullptr; + } + // Build full path for a MemberSel chain (e.g., "obj.l2.l3.l4") std::string buildMemberPath(const AstMemberSel* const memberSelp) { const AstNode* fromp = memberSelp->fromp(); @@ -1085,7 +1096,7 @@ class ConstraintExprVisitor final : public VNVisitor { AstNodeExpr* randModeAccess; if (membersel) { AstNodeModule* const varClassp = VN_AS(varp->user2p(), NodeModule); - AstVar* const effectiveRandModeVarp = VN_AS(varClassp->user2p(), Var); + AstVar* const effectiveRandModeVarp = findRandModeVar(varClassp); if (effectiveRandModeVarp) { // Member's class has randmode, use it AstNodeExpr* parentAccess = membersel->fromp()->cloneTree(false); @@ -1353,7 +1364,7 @@ class ConstraintExprVisitor final : public VNVisitor { initTaskp->addStmtsp(methodp->makeStmt()); if (isGlobalConstrained && membersel && randMode.usesMode) { AstNodeModule* const varClassp = VN_AS(varp->user2p(), NodeModule); - AstVar* const subRandModeVarp = VN_AS(varClassp->user2p(), Var); + AstVar* const subRandModeVarp = findRandModeVar(varClassp); if (subRandModeVarp) { AstNodeExpr* const parentAccess = membersel->fromp()->cloneTree(false); AstMemberSel* const randModeSel diff --git a/test_regress/t/t_constraint_global_randmode_subobj.py b/test_regress/t/t_constraint_global_randmode_subobj.py new file mode 100644 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_constraint_global_randmode_subobj.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_global_randmode_subobj.v b/test_regress/t/t_constraint_global_randmode_subobj.v new file mode 100644 index 000000000..bdf1016af --- /dev/null +++ b/test_regress/t/t_constraint_global_randmode_subobj.v @@ -0,0 +1,116 @@ +// 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 + +typedef logic [31:0] uvm_reg_data_t; + +class uvm_object; +endclass + +class uvm_reg_field extends uvm_object; + rand uvm_reg_data_t value; + int unsigned m_size; + + function uvm_reg_data_t get; + return value; + endfunction + + function void set_rand_mode(bit rand_mode); + value.rand_mode(rand_mode); + uvm_reg_field_valid.constraint_mode(rand_mode); + endfunction + + function bit get_rand_mode(); + return bit'(value.rand_mode()); + endfunction + + constraint uvm_reg_field_valid { + if (64'd64 > {32'd0, m_size}) { + {32'd0, value} < (64'd1 << m_size); + } + } + + function void configure(int unsigned size); + m_size = size; + endfunction +endclass + +class uvm_reg extends uvm_object; + virtual function void build(); + endfunction +endclass + +class regA extends uvm_reg; + rand uvm_reg_field fA1; + rand uvm_reg_field fA2; + + virtual function void build(); + this.fA1 = new; + this.fA2 = new; + this.fA1.configure(16); + this.fA2.configure(16); + endfunction +endclass + +class test extends uvm_object; + regA rg; + + function new; + rg = new; + rg.build(); + endfunction + + task run_test; + uvm_reg_data_t pre_fA1; + uvm_reg_data_t pre_fA2; + int rand_ok; + + // Disable fA1, enable fA2 + rg.fA1.set_rand_mode(0); + rg.fA2.set_rand_mode(1); + + if (rg.fA1.get_rand_mode() != 0) $stop; + if (rg.fA2.get_rand_mode() != 1) $stop; + + pre_fA1 = rg.fA1.value; + + rand_ok = rg.randomize(); + if (rand_ok != 0) begin + // fA1 should be unchanged + if (rg.fA1.get() !== pre_fA1) begin + $display("%%Error: fA1 changed: got=%0h exp=%0h", rg.fA1.get(), pre_fA1); + $stop; + end + end + + // Re-enable fA1, disable fA2 + rg.fA1.set_rand_mode(1); + rg.fA2.set_rand_mode(0); + + pre_fA2 = rg.fA2.value; + + rand_ok = rg.randomize(); + if (rand_ok != 0) begin + // fA2 should be unchanged + if (rg.fA2.get() !== pre_fA2) begin + $display("%%Error: fA2 changed: got=%0h exp=%0h", rg.fA2.get(), pre_fA2); + $stop; + end + end + + $write("*-* All Finished *-*\n"); + $finish; + endtask + +endclass + +module top; + initial begin + test t; + t = new; + t.run_test(); + end +endmodule