diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 911660103..52a750a6a 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -3105,6 +3105,8 @@ class RandomizeVisitor final : public VNVisitor { std::map m_staticConstraintModeVars; // Static constraint mode vars per class std::map m_staticRandModeVars; // Static rand mode vars per class + std::map> + m_prePostWrap; // Per-handle-type pre/post virtual wrapper presence // METHODS // Check if two nodes are semantically equivalent (not pointer equality): @@ -3797,12 +3799,75 @@ class RandomizeVisitor final : public VNVisitor { } return nullptr; } - void addPrePostCall(AstClass* const classp, AstFunc* const funcp, const string& name) { + void addPrePostCall(AstClass* const classp, AstNodeFTask* const funcp, const string& name) { if (AstTask* const userFuncp = findPrePostTask(classp, name)) { AstTaskRef* const callp = new AstTaskRef{userFuncp->fileline(), userFuncp}; funcp->addStmtsp(callp->makeStmt()); } } + // Per-class virtual wrapper that invokes the class's effective + // pre_randomize/post_randomize. IEEE 1800-2023 18.6.2: pre_randomize and + // post_randomize "appear to behave as virtual methods" because randomize() + // is virtual. The inline `randomize() with` path builds a non-virtual + // function on the static handle type, so it dispatches pre/post through + // this wrapper to reach the dynamic type's override. + AstTask* getCreatePrePostCallback(AstClass* const classp, const string& which) { + const string name = "__V" + which; + if (AstTask* const existingp = VN_CAST(m_memberMap.findMember(classp, name), Task)) { + return existingp; + } + AstTask* const taskp = new AstTask{classp->fileline(), name, nullptr}; + taskp->classMethod(true); + taskp->isVirtual(classp->isExtended()); + classp->addMembersp(taskp); + m_memberMap.insert(classp, taskp); + addPrePostCall(classp, taskp, which); + return taskp; + } + // Build the virtual pre/post wrappers across classp's whole hierarchy so a + // `randomize() with` through a base handle dispatches to a derived + // override. Returns whether a pre/post wrapper exists anywhere in the + // hierarchy (cached per static handle type). + std::pair buildPrePostVirtualWrappers(AstClass* const classp) { + const auto cachedIt = m_prePostWrap.find(classp); + if (cachedIt != m_prePostWrap.end()) return cachedIt->second; + std::vector hierp{classp}; + v3Global.rootp()->foreach([&](AstClass* subp) { + if (subp != classp && AstClass::isClassExtendedFrom(subp, classp)) + hierp.push_back(subp); + }); + bool hasPre = false; + bool hasPost = false; + for (AstClass* const cp : hierp) { + if (findPrePostTask(cp, "pre_randomize")) { + getCreatePrePostCallback(cp, "pre_randomize"); + hasPre = true; + } + if (findPrePostTask(cp, "post_randomize")) { + getCreatePrePostCallback(cp, "post_randomize"); + hasPost = true; + } + } + // Ensure the static handle type owns the slot whenever a subclass + // overrides, so the virtual call resolves on a base handle. + if (hasPre) getCreatePrePostCallback(classp, "pre_randomize"); + if (hasPost) getCreatePrePostCallback(classp, "post_randomize"); + const std::pair result{hasPre, hasPost}; + m_prePostWrap.emplace(classp, result); + return result; + } + void addVirtualPrePostCall(AstFunc* const randomizeFuncp, AstClass* const classp, + const string& which) { + FileLine* const fl = classp->fileline(); + AstTask* const wrapperp = getCreatePrePostCallback(classp, which); + AstClassRefDType* const refDTypep = new AstClassRefDType{fl, classp, nullptr}; + v3Global.rootp()->typeTablep()->addTypesp(refDTypep); + AstMethodCall* const callp + = new AstMethodCall{fl, new AstThisRef{fl, refDTypep}, wrapperp->name(), nullptr}; + callp->taskp(wrapperp); + callp->dtypeSetVoid(); + randomizeFuncp->addStmtsp(callp->makeStmt()); + } // Check if a class (including inherited members) has any rand class-type members bool classHasRandClassMembers(AstClass* classp) { return classp->existsMember([](const AstClass*, const AstVar* varp) { @@ -5228,7 +5293,17 @@ class RandomizeVisitor final : public VNVisitor { AstFunc* const randomizeFuncp = V3Randomize::newRandomizeFunc( m_memberMap, classp, m_inlineUniqueNames.get(nodep), false); - addPrePostCall(classp, randomizeFuncp, "pre_randomize"); + // A base-handle `randomize() with` must still reach a derived + // pre/post_randomize. Route them through per-class virtual wrappers + // when the static handle type participates in inheritance. + const std::pair prePostWrap = classp->isExtended() + ? buildPrePostVirtualWrappers(classp) + : std::pair{false, false}; + if (prePostWrap.first) { + addVirtualPrePostCall(randomizeFuncp, classp, "pre_randomize"); + } else { + addPrePostCall(classp, randomizeFuncp, "pre_randomize"); + } // Call nested pre_randomize on rand class-type members (IEEE 18.4.1) if (classHasRandClassMembers(classp)) { @@ -5390,7 +5465,11 @@ class RandomizeVisitor final : public VNVisitor { randomizeFuncp->addStmtsp((new AstTaskRef{nodep->fileline(), postTaskp})->makeStmt()); } - addPrePostCall(classp, randomizeFuncp, "post_randomize"); + if (prePostWrap.second) { + addVirtualPrePostCall(randomizeFuncp, classp, "post_randomize"); + } else { + addPrePostCall(classp, randomizeFuncp, "post_randomize"); + } // Replace the node with a call to that function nodep->name(randomizeFuncp->name()); diff --git a/test_regress/t/t_randomize_prepost_with_baseref.py b/test_regress/t/t_randomize_prepost_with_baseref.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_randomize_prepost_with_baseref.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_randomize_prepost_with_baseref.v b/test_regress/t/t_randomize_prepost_with_baseref.v new file mode 100644 index 000000000..1bfe4ca64 --- /dev/null +++ b/test_regress/t/t_randomize_prepost_with_baseref.v @@ -0,0 +1,94 @@ +// 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 +`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); +// verilog_format: on + +class Base; + rand bit [7:0] a; + bit [7:0] m_pre; + bit [7:0] m_post; +endclass + +class Derived extends Base; + function void pre_randomize; + `checkd(m_pre, 8'd0); + m_pre = 8'd10; + endfunction + function void post_randomize; + `checkd(m_pre, 8'd10); + m_post = a + 8'd1; + endfunction +endclass + +class Base2; + rand bit [7:0] b; + bit [7:0] bp; + bit [7:0] bq; + function void pre_randomize; + bp = 8'd1; + endfunction + function void post_randomize; + bq = b; + endfunction +endclass + +class Derived2 extends Base2; + bit [7:0] dp; + bit [7:0] dq; + function void pre_randomize; + dp = 8'd2; + super.pre_randomize(); + endfunction + function void post_randomize; + dq = b + 8'd1; + super.post_randomize(); + endfunction +endclass + +module t; + initial begin + Base b; + Derived d; + Base2 b2; + Derived2 d2; + int ok; + + // Plain randomize through a base handle already dispatches pre/post + d = new; + b = d; + ok = b.randomize(); + `checkd(ok, 1); + `checkd(d.m_pre, 8'd10); + `checkd(d.m_post, d.a + 8'd1); + + // randomize() with through a base handle whose static type lacks pre/post + d = new; + b = d; + ok = b.randomize() with {a == 8'h3c;}; + `checkd(ok, 1); + `checkd(b.a, 8'h3c); + `checkd(d.m_pre, 8'd10); + `checkd(d.m_post, 8'h3d); + + // randomize() with through a base handle that DOES define pre/post, + // overridden by the derived class with super chaining + d2 = new; + b2 = d2; + ok = b2.randomize() with {b == 8'h11;}; + `checkd(ok, 1); + `checkd(d2.b, 8'h11); + `checkd(d2.dp, 8'd2); + `checkd(d2.bp, 8'd1); + `checkd(d2.dq, 8'h12); + `checkd(d2.bq, 8'h11); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule