Support `obj.randomize(null)` (#7487) (#7509)

This commit is contained in:
Yilou Wang 2026-04-28 21:31:08 +02:00 committed by GitHub
parent 93c594e18a
commit 76c1b26e3b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 547 additions and 24 deletions

View File

@ -23,6 +23,7 @@
#include "verilated_random.h"
#include <cassert>
#include <fstream>
#include <iomanip>
#include <iostream>
@ -330,6 +331,30 @@ void VlRandomVar::emitExtract(std::ostream& s, int i) const {
s << " ((_ extract " << i << ' ' << i << ") " << m_name << ')';
}
void VlRandomVar::emitType(std::ostream& s) const { s << "(_ BitVec " << width() << ')'; }
// Serialize the current runtime value as an SMT-LIB binary literal. Used by
// randomize(null) to pin a var via `(assert (= var #b...))`. Binary (#b)
// rather than hex (#x) sidesteps SMT-LIB's hex-width-multiple-of-4 rule.
void VlRandomVar::emitConcreteValue(std::ostream& s) const {
const int w = width();
const void* const dp = datap(0);
s << "#b";
for (int i = w - 1; i >= 0; --i) {
int bit = 0;
if (w <= VL_BYTESIZE) {
bit = (*static_cast<const CData*>(dp) >> i) & 1;
} else if (w <= VL_SHORTSIZE) {
bit = (*static_cast<const SData*>(dp) >> i) & 1;
} else if (w <= VL_IDATASIZE) {
bit = (*static_cast<const IData*>(dp) >> i) & 1;
} else if (w <= VL_QUADSIZE) {
bit = (*static_cast<const QData*>(dp) >> i) & 1;
} else {
const EData* const wp = static_cast<const EData*>(dp);
bit = (wp[VL_BITWORD_E(i)] >> VL_BITBIT_E(i)) & 1;
}
s << (bit ? '1' : '0');
}
}
int VlRandomVar::totalWidth() const { return m_width; }
static bool parseSMTNum(int obits, WDataOutP owp, const std::string& val) {
int i;
@ -441,8 +466,16 @@ void VlRandomizer::recordRandcValues() {
}
}
bool VlRandomizer::next_check_only(VlRNG& rngr) {
m_checkOnly = true;
const bool result = next(rngr);
m_checkOnly = false;
return result;
}
bool VlRandomizer::next(VlRNG& rngr) {
if (m_vars.empty() && m_unique_arrays.empty()) return true;
if (!m_checkOnly && m_vars.empty() && m_unique_arrays.empty()) return true;
if (m_checkOnly && m_vars.empty()) return true; // No rand members: trivially SAT
for (const std::string& baseName : m_unique_arrays) {
const auto it = m_vars.find(baseName);
const uint32_t size = m_unique_array_sizes.at(baseName);
@ -470,8 +503,8 @@ bool VlRandomizer::next(VlRNG& rngr) {
}
}
// If solve-before constraints are present, use phased solving
if (!m_solveBefore.empty()) return nextPhased(rngr);
// Pinned vars make phase ordering moot; skip phased path in check-only.
if (!m_checkOnly && !m_solveBefore.empty()) return nextPhased(rngr);
// Randc retry: if unsat due to randc exhaustion, clear history and retry once
const bool hasRandc = !m_randcVarNames.empty();
@ -494,14 +527,24 @@ bool VlRandomizer::next(VlRNG& rngr) {
os << "(declare-fun " << var.first << " () ";
var.second->emitType(os);
os << ")\n";
// Pin each var to its current value: SAT iff the current values
// satisfy the constraints. V3Randomize rejects non-scalar rand
// members upstream, hence the assert.
if (m_checkOnly) {
assert(var.second->dimension() == 0);
os << "(assert (= " << var.first << ' ';
var.second->emitConcreteValue(os);
os << "))\n";
}
}
for (const std::string& constraint : m_constraints) {
os << "(assert (= #b1 " << constraint << "))\n";
}
// Randc: exclude previously used values to enforce cyclic non-repetition
emitRandcExclusions(os);
// randc exclusions vs. a pinned current value would make every check
// trivially UNSAT after the first cycle.
if (!m_checkOnly) emitRandcExclusions(os);
const size_t nSoft = m_softConstraints.size();
bool sat = false;
@ -544,6 +587,10 @@ bool VlRandomizer::next(VlRNG& rngr) {
m_randcUsedValues.clear();
continue; // Retry without exclusions
}
// Skip the unsat-core path in check-only: it re-declares vars
// without pinning, so parseSolution would clobber user state with
// the solver's free assignment.
if (m_checkOnly) return false;
// Genuine unsat: report via unsat-core
os << "(set-option :produce-unsat-cores true)\n";
os << "(set-logic QF_ABV)\n";
@ -569,17 +616,20 @@ bool VlRandomizer::next(VlRNG& rngr) {
return false;
}
for (int i = 0; i < _VL_SOLVER_HASH_LEN_TOTAL && sat; ++i) {
os << "(assert ";
randomConstraint(os, rngr, _VL_SOLVER_HASH_LEN);
os << ")\n";
os << "\n(check-sat)\n";
sat = parseSolution(os, false);
(void)sat;
// Pinned vars: salting cannot diversify, only burn solver calls.
if (!m_checkOnly) {
for (int i = 0; i < _VL_SOLVER_HASH_LEN_TOTAL && sat; ++i) {
os << "(assert ";
randomConstraint(os, rngr, _VL_SOLVER_HASH_LEN);
os << ")\n";
os << "\n(check-sat)\n";
sat = parseSolution(os, false);
(void)sat;
}
}
// Record solved randc values for future exclusion
recordRandcValues();
// Check-only must not advance randc cycle state.
if (!m_checkOnly) recordRandcValues();
os << "(reset)\n";
return true;

View File

@ -82,6 +82,9 @@ public:
virtual void emitGetValue(std::ostream& s) const;
virtual void emitExtract(std::ostream& s, int i) const;
virtual void emitType(std::ostream& s) const;
// Emit the current runtime value as an SMT bit-vector literal (#b...).
// Used by randomize(null) to pin a var to its existing value.
virtual void emitConcreteValue(std::ostream& s) const;
virtual int totalWidth() const;
mutable std::shared_ptr<const ArrayInfoMap> m_arrVarsRefp;
void setArrayInfo(const std::shared_ptr<const ArrayInfoMap>& arrVarsRefp) const {
@ -223,6 +226,7 @@ class VlRandomizer VL_NOT_FINAL {
size_t m_randcConstraintHash = 0; // Hash of constraints when history was valid
std::vector<std::pair<std::string, std::string>>
m_solveBefore; // Solve-before ordering pairs (beforeVar, afterVar)
bool m_checkOnly = false; // Set for randomize(null)
// PRIVATE METHODS
void randomConstraint(std::ostream& os, VlRNG& rngr, int bits);
@ -241,6 +245,9 @@ public:
// METHODS
// Finds the next solution satisfying the constraints
bool next(VlRNG& rngr);
// Validate the constraints against the current runtime values of every
// registered rand variable without picking new ones.
bool next_check_only(VlRNG& rngr);
// --- Process the key for associative array ---

View File

@ -607,6 +607,28 @@ class RandomizeMarkVisitor final : public VNVisitor {
return;
}
for (AstArg* argp = nodep->argsp(); argp; argp = VN_AS(argp->nextp(), Arg)) {
// randomize(null): handle before IS_RANDOMIZED_INLINE so the
// check-only path does not allocate unused __Vrandmode slots.
if (const AstConst* const constp = VN_CAST(argp->exprp(), Const)) {
UASSERT_OBJ(constp->num().isNull(), constp,
"Non-null AstConst arg to randomize() should have been "
"rejected by V3Width");
// SMT pin only handles scalars; nested-class constraints don't cascade.
const bool hasUnsupportedMember
= classp->existsMember([](const AstClass*, const AstVar* memberVarp) {
if (!memberVarp->rand().isRandomizable()) return false;
const AstNodeDType* const dtp = memberVarp->dtypep()->skipRefp();
return VN_IS(dtp, UnpackArrayDType) || VN_IS(dtp, DynArrayDType)
|| VN_IS(dtp, QueueDType) || VN_IS(dtp, AssocArrayDType)
|| VN_IS(dtp, WildcardArrayDType) || VN_IS(dtp, ClassRefDType);
});
if (hasUnsupportedMember) {
nodep->v3warn(E_UNSUPPORTED,
"Unsupported: 'randomize(null)' on class with rand "
"container or class member");
}
continue;
}
classp->user1(IS_RANDOMIZED_INLINE);
AstVar* fromVarp = nullptr; // If nodep is a method call, this is its receiver
if (AstMethodCall* methodCallp = VN_CAST(nodep, MethodCall)) {
@ -4027,6 +4049,19 @@ class RandomizeVisitor final : public VNVisitor {
// Handle inline random variable control. After this, the randomize() call has no args
void handleRandomizeArgs(AstNodeFTaskRef* const nodep) {
if (!nodep->argsp()) return;
// Strip the null literal arg. V3Width already rejected mixed/non-null
// AstConst args.
bool hasNullArg = false;
for (AstArg *argp = nodep->argsp(), *nextp = nullptr; argp; argp = nextp) {
nextp = VN_AS(argp->nextp(), Arg);
if (const AstConst* const constp = VN_CAST(argp->exprp(), Const)) {
UASSERT_OBJ(constp->num().isNull(), constp,
"Non-null AstConst arg to randomize() should have been "
"rejected by V3Width");
hasNullArg = true;
VL_DO_DANGLING(argp->unlinkFrBack()->deleteTree(), argp);
}
}
// This assumes arguments to always be a member sel from nodep->fromp(), if applicable
// e.g. LinkDot transformed a.randomize(b, a.c) -> a.randomize(a.b, a.c)
// Merge pins with common prefixes so that setting their rand mode doesn't interfere
@ -4089,6 +4124,24 @@ class RandomizeVisitor final : public VNVisitor {
}
argp->unlinkFrBack()->deleteTree();
}
if (hasNullArg) { // Re-point to the per-class __Vrandomize_null wrapper
AstClass* targetClassp = nullptr;
AstMethodCall* const methodCallp = VN_CAST(nodep, MethodCall);
if (methodCallp) {
const AstNodeDType* const fromDTypep = methodCallp->fromp()->dtypep();
const AstClassRefDType* const crdtp
= VN_CAST(fromDTypep->skipRefp(), ClassRefDType);
UASSERT_OBJ(crdtp, nodep, "randomize(null) receiver is not a class type");
targetClassp = crdtp->classp();
} else {
targetClassp = VN_CAST(m_modp, Class);
}
UASSERT_OBJ(targetClassp, nodep, "randomize(null) target class unresolved");
AstFunc* const checkOnlyFuncp = getCreateRandomizeNullFunc(targetClassp);
nodep->name(checkOnlyFuncp->name());
nodep->taskp(checkOnlyFuncp);
nodep->dtypeFrom(checkOnlyFuncp->dtypep());
}
if (tmpVarps) {
UASSERT_OBJ(storeStmtsp && setStmtsp && restoreStmtsp, nodep, "Should have stmts");
VNRelinker relinker;
@ -4102,6 +4155,58 @@ class RandomizeVisitor final : public VNVisitor {
}
}
// Create a class method `__Vrandomize_null` that implements the IEEE
// 1800-2023 18.11 semantic: validate all declared constraints against the
// current runtime values without assigning new ones. Body is:
// 1. pre_randomize() -- always (IEEE 1800-2023 18.6.2; 18.11 has no
// carve-out for the null case).
// 2. Compute result:
// - no constraints: `fvar = 1` (trivially satisfied; IEEE 18.11.1).
// - has constraints: clear solver constraints, re-run
// `__Vsetup_constraints`, then `fvar = gen.next_check_only(rng)`.
// 3. if (fvar) post_randomize() -- IEEE 1800-2023 18.6.3 says
// post_randomize is not called when randomize() fails.
AstFunc* getCreateRandomizeNullFunc(AstClass* const classp) {
static const char* const name = "__Vrandomize_null";
if (AstFunc* const existingp = VN_AS(m_memberMap.findMember(classp, name), Func)) {
return existingp;
}
FileLine* const fl = classp->fileline();
AstFunc* const funcp = V3Randomize::newRandomizeFunc(m_memberMap, classp, name);
AstVar* const fvarp = VN_AS(funcp->fvarp(), Var);
// 1. pre_randomize -- always
addPrePostCall(classp, funcp, "pre_randomize");
// 2. Compute result
AstVar* const classGenp = getRandomGenerator(classp);
if (!classGenp) {
funcp->addStmtsp(new AstAssign{fl, new AstVarRef{fl, fvarp, VAccess::WRITE},
new AstConst{fl, AstConst::WidthedValue{}, 32, 1}});
} else {
AstNodeModule* const genModp = VN_AS(classGenp->user2p(), NodeModule);
funcp->addStmtsp(implementConstraintsClear(fl, classGenp));
AstTask* const setupAllTaskp = getCreateConstraintSetupFunc(classp);
funcp->addStmtsp((new AstTaskRef{fl, setupAllTaskp})->makeStmt());
AstCExpr* const solverCallp = new AstCExpr{fl};
solverCallp->dtypeSetBit();
solverCallp->add(new AstVarRef{fl, genModp, classGenp, VAccess::READWRITE});
solverCallp->add(".next_check_only(__Vm_rng)");
funcp->addStmtsp(
new AstAssign{fl, new AstVarRef{fl, fvarp, VAccess::WRITE}, solverCallp});
classp->needRNG(true);
}
// 3. post_randomize -- only if result is non-zero
if (AstTask* const userPostp = findPrePostTask(classp, "post_randomize")) {
AstTaskRef* const callp = new AstTaskRef{userPostp->fileline(), userPostp};
funcp->addStmtsp(
new AstIf{fl, new AstVarRef{fl, fvarp, VAccess::READ}, callp->makeStmt()});
}
return funcp;
}
// Rewrite a LogIf-of-Dist chain into nested AstConstraintIf. The outermost
// AstLogIf shell is left for the caller's AstConstraintExpr to free; inner
// shells are deleted here once their children are transplanted.

View File

@ -4740,13 +4740,12 @@ class WidthVisitor final : public VNVisitor {
VL_DO_DANGLING(argp->unlinkFrBack()->deleteTree(), argp);
}
}
if (nullp) {
if (hasNonNullArgs) {
nullp->v3error("Cannot pass more arguments to 'randomize(null)'");
} else {
nullp->v3warn(E_UNSUPPORTED, "Unsupported: 'randomize(null)'");
}
if (nullp && hasNonNullArgs) {
nullp->v3error("Cannot pass more arguments to 'randomize(null)'");
}
// A solo 'null' arg is left in place; V3Randomize lowers it into a
// check-only solve so constraints are validated against the current
// values of every rand member (IEEE 1800-2023 18.11).
}
void methodCallClass(AstMethodCall* nodep, AstClassRefDType* adtypep) {
// No need to width-resolve the class, as it was done when we did the child

View File

@ -7,8 +7,4 @@
: ... note: In instance 't'
21 | void'(foo.randomize(foos[0].x));
| ^
%Error-UNSUPPORTED: t/t_randomize_inline_var_ctl_unsup_1.v:22:25: Unsupported: 'randomize(null)'
: ... note: In instance 't'
22 | void'(foo.randomize(null));
| ^~~~
%Error: Exiting due to

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,251 @@
// 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);
`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0);
// verilog_format: on
class A;
rand int x;
int v;
constraint c_lt {x < v;}
endclass
class Multi;
rand int x;
rand int y;
int lo;
int hi;
constraint c_x {x >= lo; x <= hi;}
constraint c_y {y > x;}
function int self_check;
return this.randomize(null);
endfunction
endclass
class Trivial;
rand int p;
rand bit [3:0] q;
endclass
class Base;
rand int x;
int v;
constraint c_base {x <= v;}
endclass
class Derived extends Base;
rand int y;
constraint c_derived {y > x;}
endclass
class Wide;
rand bit [64:0] w65;
rand bit [95:0] w96;
bit [64:0] lo65;
bit [95:0] lo96;
constraint c_wide {w65 >= lo65; w96 >= lo96;}
endclass
// Cover the 16-bit (SData) and 64-bit (QData) tiers of
// VlRandomVar::emitConcreteValue's bit-extraction ladder, exercised when
// pinned current values are serialized to the SMT solver.
class Widths;
rand shortint s16;
rand longint l64;
shortint s_lo;
longint l_lo;
constraint c_widths {s16 >= s_lo; l64 >= l_lo;}
endclass
class Cyc;
randc bit [1:0] c;
bit [1:0] lo;
constraint c_range {c >= lo;}
endclass
// IEEE 1800-2023 18.6.2 / 18.6.3: pre_randomize is always called; post_randomize
// is called iff randomize() returned 1.
class Cb;
rand int x;
int v;
int pre_count;
int post_count;
constraint c_lt {x < v;}
function void pre_randomize; pre_count = pre_count + 1; endfunction
function void post_randomize; post_count = post_count + 1; endfunction
endclass
module t;
A a;
Multi m;
Trivial triv;
Derived d;
Wide w;
Widths wd;
Cyc cyc;
Cb cb;
int i;
int ok0;
int ok1;
initial begin
// 1. Original issue reproducer: unsat keeps values, sat preserves them.
a = new;
a.x = 2; a.v = 1;
i = a.randomize(null);
`checkd(i, 0);
`checkd(a.x, 2);
`checkd(a.v, 1);
a.x = 1; a.v = 2;
i = a.randomize(null);
`checkd(i, 1);
`checkd(a.x, 1);
`checkd(a.v, 2);
// 2. Multiple rand members, multiple constraints, plus implicit-this path.
m = new;
m.x = 5; m.y = 7; m.lo = 0; m.hi = 10;
i = m.randomize(null);
`checkd(i, 1);
`checkd(m.x, 5);
`checkd(m.y, 7);
m.x = -1; m.y = 7; m.lo = 0; m.hi = 10;
i = m.randomize(null);
`checkd(i, 0);
`checkd(m.x, -1);
m.x = 5; m.y = 5; m.lo = 0; m.hi = 10;
i = m.randomize(null);
`checkd(i, 0);
`checkd(m.y, 5);
m.x = 3; m.y = 9; m.lo = 0; m.hi = 10;
i = m.self_check();
`checkd(i, 1);
// 3. Class with rand vars and no constraints: always sat, values untouched.
triv = new;
triv.p = 42;
triv.q = 4'h5;
i = triv.randomize(null);
`checkd(i, 1);
`checkd(triv.p, 42);
`checkh(triv.q, 4'h5);
// 4. Inheritance: base and derived constraints validated together.
d = new;
d.x = 2; d.y = 5; d.v = 10;
i = d.randomize(null);
`checkd(i, 1);
`checkd(d.x, 2);
`checkd(d.y, 5);
d.x = 11; d.y = 20; d.v = 10;
i = d.randomize(null);
`checkd(i, 0);
`checkd(d.x, 11);
d.x = 3; d.y = 1; d.v = 10;
i = d.randomize(null);
`checkd(i, 0);
`checkd(d.y, 1);
// 5. Wide (65-bit, 96-bit) rand vars: current-value pin must be bit-exact.
w = new;
w.w65 = 65'h1_0000_0000_0000_0000;
w.w96 = 96'hDEAD_BEEF_CAFE_0000_0000_0001;
w.lo65 = 65'h0_FFFF_FFFF_FFFF_FFFF;
w.lo96 = 96'h0;
i = w.randomize(null);
`checkd(i, 1);
`checkh(w.w65, 65'h1_0000_0000_0000_0000);
`checkh(w.w96, 96'hDEAD_BEEF_CAFE_0000_0000_0001);
w.w65 = 65'h0_1234_5678_9ABC_DEF0;
w.lo65 = 65'h1_FFFF_FFFF_FFFF_FFFF;
i = w.randomize(null);
`checkd(i, 0);
`checkh(w.w65, 65'h0_1234_5678_9ABC_DEF0);
// 5b. shortint (16-bit, SData) and longint (64-bit, QData) widths --
// covers the middle tiers of emitConcreteValue's bit-extraction ladder.
wd = new;
wd.s16 = 16'sh1234;
wd.l64 = 64'sh0123_4567_89AB_CDEF;
wd.s_lo = 16'sh0;
wd.l_lo = 64'sh0;
i = wd.randomize(null);
`checkd(i, 1);
`checkh(wd.s16, 16'sh1234);
`checkh(wd.l64, 64'sh0123_4567_89AB_CDEF);
wd.s16 = 16'sh0001;
wd.s_lo = 16'sh7FFF;
i = wd.randomize(null);
`checkd(i, 0);
`checkh(wd.s16, 16'sh0001);
// 6. randc: null-call must NOT be poisoned by the exclusion history nor
// record values itself; a subsequent real randomize() must still cycle.
cyc = new;
cyc.lo = 2'd0;
repeat (4) begin
i = cyc.randomize();
`checkd(i, 1);
end
cyc.c = 2'd0; cyc.lo = 2'd0;
i = cyc.randomize(null);
`checkd(i, 1);
`checkd(cyc.c, 2'd0);
cyc.c = 2'd3; cyc.lo = 2'd0;
i = cyc.randomize(null);
`checkd(i, 1);
`checkd(cyc.c, 2'd3);
cyc.c = 2'd0; cyc.lo = 2'd1;
i = cyc.randomize(null);
`checkd(i, 0);
`checkd(cyc.c, 2'd0);
cyc.lo = 2'd0;
ok0 = 0; ok1 = 0;
repeat (20) begin
i = cyc.randomize();
`checkd(i, 1);
if (cyc.c == 2'd0) ok0 = 1;
if (cyc.c == 2'd1) ok1 = 1;
end
`checkd(ok0, 1);
`checkd(ok1, 1);
// 7. pre_randomize / post_randomize observable behavior.
// IEEE 1800-2023 18.6.2: pre is always called.
// IEEE 1800-2023 18.6.3: post is called iff randomize() returned 1.
cb = new;
cb.x = 1; cb.v = 2; cb.pre_count = 0; cb.post_count = 0;
i = cb.randomize(null); // sat: pre + post
`checkd(i, 1);
`checkd(cb.pre_count, 1);
`checkd(cb.post_count, 1);
cb.x = 5; cb.v = 1;
i = cb.randomize(null); // unsat: pre only, no post
`checkd(i, 0);
`checkd(cb.pre_count, 2);
`checkd(cb.post_count, 1);
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,26 @@
%Error-UNSUPPORTED: t/t_randomize_null_unsup.v:45:14: Unsupported: 'randomize(null)' on class with rand container or class member
: ... note: In instance 't'
45 | void'(fq.randomize(null));
| ^~~~~~~~~
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error-UNSUPPORTED: t/t_randomize_null_unsup.v:46:14: Unsupported: 'randomize(null)' on class with rand container or class member
: ... note: In instance 't'
46 | void'(fu.randomize(null));
| ^~~~~~~~~
%Error-UNSUPPORTED: t/t_randomize_null_unsup.v:47:14: Unsupported: 'randomize(null)' on class with rand container or class member
: ... note: In instance 't'
47 | void'(fd.randomize(null));
| ^~~~~~~~~
%Error-UNSUPPORTED: t/t_randomize_null_unsup.v:48:14: Unsupported: 'randomize(null)' on class with rand container or class member
: ... note: In instance 't'
48 | void'(fa.randomize(null));
| ^~~~~~~~~
%Error-UNSUPPORTED: t/t_randomize_null_unsup.v:49:14: Unsupported: 'randomize(null)' on class with rand container or class member
: ... note: In instance 't'
49 | void'(fw.randomize(null));
| ^~~~~~~~~
%Error-UNSUPPORTED: t/t_randomize_null_unsup.v:50:13: Unsupported: 'randomize(null)' on class with rand container or class member
: ... note: In instance 't'
50 | void'(o.randomize(null));
| ^~~~~~~~~
%Error: Exiting due to

View File

@ -0,0 +1,16 @@
#!/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')
test.lint(fails=test.vlt_all, expect_filename=test.golden_filename)
test.passes()

View File

@ -0,0 +1,52 @@
// 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
class FooQ;
rand int q[$];
endclass
class FooU;
rand int u[3];
endclass
class FooD;
rand int d[];
endclass
class FooA;
rand int a[string];
endclass
class FooW;
rand int w[*];
endclass
class Inner;
rand int x;
constraint c_in {x > 0;}
endclass
class Outer;
rand Inner inner;
endclass
module t;
initial begin
automatic FooQ fq = new;
automatic FooU fu = new;
automatic FooD fd = new;
automatic FooA fa = new;
automatic FooW fw = new;
automatic Outer o = new;
o.inner = new;
void'(fq.randomize(null));
void'(fu.randomize(null));
void'(fd.randomize(null));
void'(fa.randomize(null));
void'(fw.randomize(null));
void'(o.randomize(null));
end
endmodule