diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index c603eebbf..3b693df94 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -66,6 +66,19 @@ enum ClassRandom : uint8_t { static constexpr const char* GLOBAL_CONSTRAINT_SEPARATOR = "__DT__"; static constexpr const char* BASIC_RANDOMIZE_FUNC_NAME = "__VBasicRand"; +// Walk extends chain to find __Vrandmode variable (stored in AstClass::user2p). +// user2p is only set on the root class where __Vrandmode was created, so +// derived classes need this chain walk. Accepts AstNodeModule* for flexibility. +static AstVar* getRandModeVarFromClass(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; +} + // ###################################################################### // Establishes the target of a rand_mode() call @@ -1085,7 +1098,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 = getRandModeVarFromClass(varClassp); if (effectiveRandModeVarp) { // Member's class has randmode, use it AstNodeExpr* parentAccess = membersel->fromp()->cloneTree(false); @@ -1346,7 +1359,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 = getRandModeVarFromClass(varClassp); if (subRandModeVarp) { AstNodeExpr* const parentAccess = membersel->fromp()->cloneTree(false); AstMemberSel* const randModeSel @@ -3003,13 +3016,6 @@ class RandomizeVisitor final : public VNVisitor { classp->user2p(randModeVarp); return randModeVarp; } - static AstVar* getRandModeVar(AstClass* const classp) { - if (classp->user2p()) return VN_AS(classp->user2p(), Var); - if (AstClassExtends* const extendsp = classp->extendsp()) { - return getRandModeVar(extendsp->classp()); - } - return nullptr; - } AstVar* getCreateConstraintModeVar(AstClass* const classp) { if (classp->user4p()) return VN_AS(classp->user4p(), Var); if (AstClassExtends* const extendsp = classp->extendsp()) { @@ -3251,7 +3257,7 @@ class RandomizeVisitor final : public VNVisitor { } static AstNodeStmt* wrapIfRandMode(AstClass* classp, AstVar* const varp, AstNodeStmt* stmtp) { const RandomizeMode rmode = {.asInt = varp->user1()}; - return VN_AS(wrapIfMode(rmode, getRandModeVar(classp), stmtp), NodeStmt); + return VN_AS(wrapIfMode(rmode, getRandModeVarFromClass(classp), stmtp), NodeStmt); } AstNode* wrapIfConstraintMode(AstClass* classp, AstConstraint* const constrp, AstNode* stmtp) { const RandomizeMode rmode = {.asInt = constrp->user1()}; @@ -3919,7 +3925,7 @@ class RandomizeVisitor final : public VNVisitor { if (commonPrefixp == exprp) break; AstVar* const randVarp = getVarFromRef(exprp); AstClass* const classp = VN_AS(randVarp->user2p(), Class); - AstVar* const randModeVarp = getRandModeVar(classp); + AstVar* const randModeVarp = getRandModeVarFromClass(classp); if (savedRandModeVarps.find(randModeVarp) == savedRandModeVarps.end()) { AstVar* const randModeTmpVarp = makeTmpRandModeVar(exprp, randModeVarp, storeStmtsp, restoreStmtsp); @@ -4166,7 +4172,7 @@ class RandomizeVisitor final : public VNVisitor { FileLine* fl = nodep->fileline(); AstFunc* const randomizep = V3Randomize::newRandomizeFunc(m_memberMap, nodep); AstVar* const fvarp = VN_AS(randomizep->fvarp(), Var); - AstVar* const randModeVarp = getRandModeVar(nodep); + AstVar* const randModeVarp = getRandModeVarFromClass(nodep); addPrePostCall(nodep, randomizep, "pre_randomize"); // Call nested pre_randomize on rand class-type members (IEEE 18.4.1) @@ -4549,7 +4555,7 @@ class RandomizeVisitor final : public VNVisitor { UASSERT_OBJ(randModeTarget.classp, nodep, "Should have checked in RandomizeMarkVisitor"); AstVar* const receiverp = randModeTarget.receiverp; - AstVar* const randModeVarp = getRandModeVar(randModeTarget.classp); + AstVar* const randModeVarp = getRandModeVarFromClass(randModeTarget.classp); AstNodeExpr* const lhsp = makeModeAssignLhs(nodep->fileline(), randModeTarget.classp, randModeTarget.fromp, randModeVarp); replaceWithModeAssign(nodep, @@ -4797,7 +4803,7 @@ class RandomizeVisitor final : public VNVisitor { } // Set rand mode if present (not needed if classGenp exists and was copied) - AstVar* const randModeVarp = getRandModeVar(classp); + AstVar* const randModeVarp = getRandModeVarFromClass(classp); if (!classGenp && randModeVarp) addSetRandMode(randomizeFuncp, localGenp, randModeVarp); // Generate constraint setup code and a hardcoded call to the solver 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 100755 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