diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 8464367bc..83309b453 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -2691,12 +2691,95 @@ class RandomizeVisitor final : public VNVisitor { return new AstRandRNG{fl, dtypep}; } + // Find pre_randomize/post_randomize task in class hierarchy (walks extendsp chain) + AstTask* findPrePostTask(AstClass* classp, const string& name) { + for (AstClass* cp = classp; cp; cp = cp->extendsp() ? cp->extendsp()->classp() : nullptr) { + if (AstTask* const taskp = VN_CAST(m_memberMap.findMember(cp, name), Task)) { + return taskp; + } + } + return nullptr; + } void addPrePostCall(AstClass* const classp, AstFunc* const funcp, const string& name) { - if (AstTask* userFuncp = VN_CAST(m_memberMap.findMember(classp, name), Task)) { + if (AstTask* const userFuncp = findPrePostTask(classp, name)) { AstTaskRef* const callp = new AstTaskRef{userFuncp->fileline(), userFuncp, nullptr}; funcp->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) { + if (!varp->rand().isRandomizable()) return false; + const AstNodeDType* const dtypep = varp->dtypep()->skipRefp(); + return VN_IS(dtypep, ClassRefDType); + }); + } + // Get or create __VrandCb_pre/__VrandCb_post task for nested callbacks + AstTask* getCreateNestedCallbackTask(AstClass* classp, const string& suffix) { + const string name = "__VrandCb_" + suffix; + AstTask* taskp = VN_CAST(m_memberMap.findMember(classp, name), Task); + if (taskp) return taskp; + taskp = new AstTask{classp->fileline(), name, nullptr}; + taskp->classMethod(true); + classp->addMembersp(taskp); + m_memberMap.insert(classp, taskp); + return taskp; + } + // Populate nested callback task body: calls pre/post_randomize on nested rand class members + void populateNestedCallbackTask(AstTask* const callbackTaskp, AstClass* const classp, + const string& cbName) { + FileLine* const fl = classp->fileline(); + classp->foreachMember([&](AstClass* ownerClassp, AstVar* memberVarp) { + if (!memberVarp->rand().isRandomizable()) return; + const AstNodeDType* const dtypep = memberVarp->dtypep()->skipRefp(); + const AstClassRefDType* const classRefp = VN_CAST(dtypep, ClassRefDType); + if (!classRefp) return; + AstClass* const memberClassp = classRefp->classp(); + if (memberClassp == classp) return; // Avoid self-reference + + // Force-visit member class if not yet processed + if (memberClassp->user1()) { + iterate(memberClassp); + m_writtenVars.clear(); + } + + AstNode* stmtsp = nullptr; + + // 1. Call member.pre/post_randomize() if exists in hierarchy + if (AstTask* const userFuncp = findPrePostTask(memberClassp, cbName)) { + AstMethodCall* const callp = new AstMethodCall{ + fl, new AstVarRef{fl, ownerClassp, memberVarp, VAccess::WRITE}, cbName, + nullptr}; + callp->taskp(userFuncp); + callp->dtypeSetVoid(); + stmtsp = AstNode::addNext(stmtsp, callp->makeStmt()); + } + + // 2. Call member.__VrandCb_pre/post() for deeper recursion + if (classHasRandClassMembers(memberClassp)) { + const string suffix = (cbName == "pre_randomize") ? "pre" : "post"; + AstTask* const nestedTaskp = getCreateNestedCallbackTask(memberClassp, suffix); + AstMethodCall* const recurseCallp = new AstMethodCall{ + fl, new AstVarRef{fl, ownerClassp, memberVarp, VAccess::WRITE}, + nestedTaskp->name(), nullptr}; + recurseCallp->taskp(nestedTaskp); + recurseCallp->dtypeSetVoid(); + stmtsp = AstNode::addNext(stmtsp, recurseCallp->makeStmt()); + } + + if (!stmtsp) return; + + // Wrap in null check + AstIf* const nullCheckp = new AstIf{ + fl, + new AstNeq{fl, new AstVarRef{fl, ownerClassp, memberVarp, VAccess::READ}, + new AstConst{fl, AstConst::Null{}}}, + stmtsp}; + + // Wrap in rand_mode check + callbackTaskp->addStmtsp(wrapIfRandMode(classp, memberVarp, nullCheckp)); + }); + } AstTask* newSetupConstraintTask(AstClass* const nodep, const std::string& name) { AstTask* const taskp = new AstTask{nodep->fileline(), name + "_setup_constraint", nullptr}; taskp->classMethod(true); @@ -3058,6 +3141,13 @@ class RandomizeVisitor final : public VNVisitor { AstVar* const randModeVarp = getRandModeVar(nodep); addPrePostCall(nodep, randomizep, "pre_randomize"); + // Call nested pre_randomize on rand class-type members (IEEE 18.4.1) + if (classHasRandClassMembers(nodep)) { + AstTask* const preTaskp = getCreateNestedCallbackTask(nodep, "pre"); + populateNestedCallbackTask(preTaskp, nodep, "pre_randomize"); + randomizep->addStmtsp((new AstTaskRef{fl, preTaskp, nullptr})->makeStmt()); + } + // Both IS_RANDOMIZED and IS_RANDOMIZED_GLOBAL classes need full constraint support // IS_RANDOMIZED_GLOBAL classes can be randomized independently AstNodeExpr* beginValp = nullptr; @@ -3159,6 +3249,14 @@ class RandomizeVisitor final : public VNVisitor { AstFuncRef* const basicRandomizeCallp = new AstFuncRef{fl, basicRandomizep, nullptr}; randomizep->addStmtsp(new AstAssign{fl, fvarRefp->cloneTree(false), new AstAnd{fl, fvarRefReadp, basicRandomizeCallp}}); + + // Call nested post_randomize on rand class-type members (IEEE 18.4.1) + if (classHasRandClassMembers(nodep)) { + AstTask* const postTaskp = getCreateNestedCallbackTask(nodep, "post"); + populateNestedCallbackTask(postTaskp, nodep, "post_randomize"); + randomizep->addStmtsp((new AstTaskRef{fl, postTaskp, nullptr})->makeStmt()); + } + addPrePostCall(nodep, randomizep, "post_randomize"); nodep->user1(false); } @@ -3422,6 +3520,16 @@ class RandomizeVisitor final : public VNVisitor { addPrePostCall(classp, randomizeFuncp, "pre_randomize"); + // Call nested pre_randomize on rand class-type members (IEEE 18.4.1) + if (classHasRandClassMembers(classp)) { + AstTask* const preTaskp = getCreateNestedCallbackTask(classp, "pre"); + if (!preTaskp->stmtsp()) { + populateNestedCallbackTask(preTaskp, classp, "pre_randomize"); + } + randomizeFuncp->addStmtsp( + (new AstTaskRef{nodep->fileline(), preTaskp, nullptr})->makeStmt()); + } + // Detach the expression and prepare variable copies const CaptureVisitor captured{withp->exprp(), m_modp, classp}; // Add function arguments @@ -3489,6 +3597,16 @@ class RandomizeVisitor final : public VNVisitor { new AstVarRef{nodep->fileline(), VN_AS(randomizeFuncp->fvarp(), Var), VAccess::WRITE}, new AstAnd{nodep->fileline(), basicRandomizeFuncCallp, solverCallp}}); + // Call nested post_randomize on rand class-type members (IEEE 18.4.1) + if (classHasRandClassMembers(classp)) { + AstTask* const postTaskp = getCreateNestedCallbackTask(classp, "post"); + if (!postTaskp->stmtsp()) { + populateNestedCallbackTask(postTaskp, classp, "post_randomize"); + } + randomizeFuncp->addStmtsp( + (new AstTaskRef{nodep->fileline(), postTaskp, nullptr})->makeStmt()); + } + addPrePostCall(classp, randomizeFuncp, "post_randomize"); // Replace the node with a call to that function diff --git a/test_regress/t/t_randomize_prepost_nested.py b/test_regress/t/t_randomize_prepost_nested.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_randomize_prepost_nested.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_nested.v b/test_regress/t/t_randomize_prepost_nested.v new file mode 100644 index 000000000..e616eb542 --- /dev/null +++ b/test_regress/t/t_randomize_prepost_nested.v @@ -0,0 +1,242 @@ +// 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 + +// Test: pre/post_randomize callbacks on nested rand class objects and inherited methods +// Covers: IEEE 1800-2017 Section 18.4.1 recursive callback invocation + +`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); + +// --- Inherited callbacks (no override) --- + +class BaseInherit; + rand int x; + int pre_count; + int post_count; + + function new(); + pre_count = 0; + post_count = 0; + endfunction + + function void pre_randomize; + pre_count++; + endfunction + + function void post_randomize; + post_count++; + endfunction +endclass + +class DerivedNoOverride extends BaseInherit; + rand int y; + + function new(); + super.new(); + endfunction + // Does NOT override pre/post_randomize +endclass + +class DerivedPartialOverride extends BaseInherit; + rand int z; + int derived_pre_count; + + function new(); + super.new(); + derived_pre_count = 0; + endfunction + + function void pre_randomize; + derived_pre_count++; + super.pre_randomize(); + endfunction + // Does NOT override post_randomize +endclass + +// Override both without calling super +class DerivedOverrideBoth extends BaseInherit; + rand int w; + int derived_pre_count; + int derived_post_count; + + function new(); + super.new(); + derived_pre_count = 0; + derived_post_count = 0; + endfunction + + function void pre_randomize; + derived_pre_count++; + endfunction + + function void post_randomize; + derived_post_count++; + endfunction +endclass + +// Override only post_randomize without calling super +class DerivedOverridePostOnly extends BaseInherit; + rand int v; + int derived_post_count; + + function new(); + super.new(); + derived_post_count = 0; + endfunction + + // Does NOT override pre_randomize -> should inherit BaseInherit's + function void post_randomize; + derived_post_count++; + endfunction +endclass + +// --- Nested rand class callbacks (3-level) --- + +class Level3; + rand bit [7:0] val; + int pre_count; + int post_count; + + constraint c_val { val inside {[10:200]}; } + + function new(); + pre_count = 0; + post_count = 0; + endfunction + + function void pre_randomize; + pre_count++; + endfunction + + function void post_randomize; + post_count++; + endfunction +endclass + +class Level2; + rand Level3 l3; + rand bit [7:0] val; + int pre_count; + int post_count; + + constraint c_val { val inside {[1:100]}; } + + function new(); + l3 = new(); + pre_count = 0; + post_count = 0; + endfunction + + function void pre_randomize; + pre_count++; + endfunction + + function void post_randomize; + post_count++; + endfunction +endclass + +class Level1; + rand Level2 l2; + rand bit [7:0] val; + int pre_count; + int post_count; + + constraint c_val { val inside {[50:150]}; } + + function new(); + l2 = new(); + pre_count = 0; + post_count = 0; + endfunction + + function void pre_randomize; + pre_count++; + endfunction + + function void post_randomize; + post_count++; + endfunction +endclass + +module t; + + initial begin + automatic int r; + + // Test 1: Inherited callbacks (no override) + begin + automatic DerivedNoOverride obj = new; + r = obj.randomize(); + `checkd(r, 1); + `checkd(obj.pre_count, 1); + `checkd(obj.post_count, 1); + end + + // Test 2: Partial override (pre overridden, post inherited) + begin + automatic DerivedPartialOverride obj = new; + r = obj.randomize(); + `checkd(r, 1); + `checkd(obj.derived_pre_count, 1); + `checkd(obj.pre_count, 1); // super.pre_randomize called + `checkd(obj.post_count, 1); // inherited post_randomize + end + + // Test 3: Override both without super - base counts stay 0 + begin + automatic DerivedOverrideBoth obj = new; + r = obj.randomize(); + `checkd(r, 1); + `checkd(obj.derived_pre_count, 1); + `checkd(obj.derived_post_count, 1); + `checkd(obj.pre_count, 0); // base NOT called (no super) + `checkd(obj.post_count, 0); // base NOT called (no super) + end + + // Test 4: Override only post, inherit pre + begin + automatic DerivedOverridePostOnly obj = new; + r = obj.randomize(); + `checkd(r, 1); + `checkd(obj.pre_count, 1); // inherited pre_randomize called + `checkd(obj.derived_post_count, 1); // overridden post called + `checkd(obj.post_count, 0); // base post NOT called (no super) + end + + // Test 5: Nested callbacks (3-level) + begin + automatic Level1 l1 = new; + r = l1.randomize(); + `checkd(r, 1); + `checkd(l1.pre_count, 1); + `checkd(l1.post_count, 1); + `checkd(l1.l2.pre_count, 1); + `checkd(l1.l2.post_count, 1); + `checkd(l1.l2.l3.pre_count, 1); + `checkd(l1.l2.l3.post_count, 1); + end + + // Test 6: Multiple randomizations + begin + automatic Level1 l1 = new; + repeat(5) begin + r = l1.randomize(); + `checkd(r, 1); + end + `checkd(l1.pre_count, 5); + `checkd(l1.post_count, 5); + `checkd(l1.l2.pre_count, 5); + `checkd(l1.l2.post_count, 5); + `checkd(l1.l2.l3.pre_count, 5); + `checkd(l1.l2.l3.post_count, 5); + end + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule