Fix randomize() with skipping derived pre/post_randomize (#7799)

This commit is contained in:
Yilou Wang 2026-06-18 17:04:57 +02:00 committed by GitHub
parent 5712f9b614
commit 22b45f0fd7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 197 additions and 3 deletions

View File

@ -3105,6 +3105,8 @@ class RandomizeVisitor final : public VNVisitor {
std::map<AstClass*, AstVar*>
m_staticConstraintModeVars; // Static constraint mode vars per class
std::map<AstClass*, AstVar*> m_staticRandModeVars; // Static rand mode vars per class
std::map<AstClass*, std::pair<bool, bool>>
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<bool, bool> buildPrePostVirtualWrappers(AstClass* const classp) {
const auto cachedIt = m_prePostWrap.find(classp);
if (cachedIt != m_prePostWrap.end()) return cachedIt->second;
std::vector<AstClass*> 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<bool, bool> 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<bool, bool> prePostWrap = classp->isExtended()
? buildPrePostVirtualWrappers(classp)
: std::pair<bool, bool>{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());

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