From 76c1b26e3bb6eaac24b4e51610a3f731741f7fa9 Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Tue, 28 Apr 2026 21:31:08 +0200 Subject: [PATCH] Support `obj.randomize(null)` (#7487) (#7509) --- include/verilated_random.cpp | 78 +++++- include/verilated_random.h | 7 + src/V3Randomize.cpp | 105 ++++++++ src/V3Width.cpp | 11 +- .../t/t_randomize_inline_var_ctl_unsup_1.out | 4 - test_regress/t/t_randomize_null.py | 21 ++ test_regress/t/t_randomize_null.v | 251 ++++++++++++++++++ test_regress/t/t_randomize_null_unsup.out | 26 ++ test_regress/t/t_randomize_null_unsup.py | 16 ++ test_regress/t/t_randomize_null_unsup.v | 52 ++++ 10 files changed, 547 insertions(+), 24 deletions(-) create mode 100755 test_regress/t/t_randomize_null.py create mode 100644 test_regress/t/t_randomize_null.v create mode 100644 test_regress/t/t_randomize_null_unsup.out create mode 100755 test_regress/t/t_randomize_null_unsup.py create mode 100644 test_regress/t/t_randomize_null_unsup.v diff --git a/include/verilated_random.cpp b/include/verilated_random.cpp index a9d519266..93aba10f9 100644 --- a/include/verilated_random.cpp +++ b/include/verilated_random.cpp @@ -23,6 +23,7 @@ #include "verilated_random.h" +#include #include #include #include @@ -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(dp) >> i) & 1; + } else if (w <= VL_SHORTSIZE) { + bit = (*static_cast(dp) >> i) & 1; + } else if (w <= VL_IDATASIZE) { + bit = (*static_cast(dp) >> i) & 1; + } else if (w <= VL_QUADSIZE) { + bit = (*static_cast(dp) >> i) & 1; + } else { + const EData* const wp = static_cast(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; diff --git a/include/verilated_random.h b/include/verilated_random.h index 0d82b5d78..6ab19a850 100644 --- a/include/verilated_random.h +++ b/include/verilated_random.h @@ -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 m_arrVarsRefp; void setArrayInfo(const std::shared_ptr& 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> 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 --- diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 67da65c05..e02b9e9fb 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -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. diff --git a/src/V3Width.cpp b/src/V3Width.cpp index c0ce22de3..713388321 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -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 diff --git a/test_regress/t/t_randomize_inline_var_ctl_unsup_1.out b/test_regress/t/t_randomize_inline_var_ctl_unsup_1.out index 1da1b0d5a..f2c0a7926 100644 --- a/test_regress/t/t_randomize_inline_var_ctl_unsup_1.out +++ b/test_regress/t/t_randomize_inline_var_ctl_unsup_1.out @@ -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 diff --git a/test_regress/t/t_randomize_null.py b/test_regress/t/t_randomize_null.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_randomize_null.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_null.v b/test_regress/t/t_randomize_null.v new file mode 100644 index 000000000..4c4955b1d --- /dev/null +++ b/test_regress/t/t_randomize_null.v @@ -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 diff --git a/test_regress/t/t_randomize_null_unsup.out b/test_regress/t/t_randomize_null_unsup.out new file mode 100644 index 000000000..7ffd08f0d --- /dev/null +++ b/test_regress/t/t_randomize_null_unsup.out @@ -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 diff --git a/test_regress/t/t_randomize_null_unsup.py b/test_regress/t/t_randomize_null_unsup.py new file mode 100755 index 000000000..02d2f5abe --- /dev/null +++ b/test_regress/t/t_randomize_null_unsup.py @@ -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() diff --git a/test_regress/t/t_randomize_null_unsup.v b/test_regress/t/t_randomize_null_unsup.v new file mode 100644 index 000000000..ad9e710a1 --- /dev/null +++ b/test_regress/t/t_randomize_null_unsup.v @@ -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