Support inherited and nested pre/post_randomize callbacks (#7049) (#7053)

This commit is contained in:
Yilou Wang 2026-02-11 18:33:57 +01:00 committed by GitHub
parent 554fcef627
commit e41436bd4a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 382 additions and 1 deletions

View File

@ -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

View File

@ -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()

View File

@ -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