diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index b0fbd17dd..e89340abc 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -3135,6 +3135,24 @@ class RandomizeVisitor final : public VNVisitor { } }; + // Wrap obj.randomize() with null guard: (obj != null) ? obj.randomize() : 0 + // IEEE 1800 requires randomize() on null handle to return 0. + // Uses user4() to prevent re-wrapping during iterateChildren. + void wrapRandomizeCallWithNullGuard(AstNodeFTaskRef* nodep) { + AstMethodCall* const callp = VN_CAST(nodep, MethodCall); + if (!callp) return; + if (callp->user4()) return; + callp->user4(true); + FileLine* const fl = callp->fileline(); + AstNodeExpr* const checkp + = new AstNeq{fl, callp->fromp()->cloneTree(false), new AstConst{fl, AstConst::Null{}}}; + VNRelinker relinker; + callp->unlinkFrBack(&relinker); + AstCond* const condp = new AstCond{fl, checkp, callp, new AstConst{fl, 0}}; + condp->dtypeFrom(callp); + relinker.relink(condp); + } + // Handle inline random variable control. After this, the randomize() call has no args void handleRandomizeArgs(AstNodeFTaskRef* const nodep) { if (!nodep->pinsp()) return; @@ -3593,6 +3611,7 @@ class RandomizeVisitor final : public VNVisitor { AstWith* const withp = VN_CAST(nodep->pinsp(), With); if (!withp) { iterateChildren(nodep); + wrapRandomizeCallWithNullGuard(nodep); return; } withp->unlinkFrBack(); @@ -3730,6 +3749,7 @@ class RandomizeVisitor final : public VNVisitor { nodep->dtypeFrom(randomizeFuncp->dtypep()); nodep->classOrPackagep(classp); UINFO(9, "Added `%s` randomization procedure"); + wrapRandomizeCallWithNullGuard(nodep); VL_DO_DANGLING(withp->deleteTree(), withp); } void visit(AstConstraint* nodep) override { diff --git a/test_regress/t/t_randomize_null_object.py b/test_regress/t/t_randomize_null_object.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_randomize_null_object.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_object.v b/test_regress/t/t_randomize_null_object.v new file mode 100644 index 000000000..8616fc2f2 --- /dev/null +++ b/test_regress/t/t_randomize_null_object.v @@ -0,0 +1,37 @@ +// 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 check_range(gotv,minv,maxv) do if ((gotv) < (minv) || (gotv) > (maxv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d-%0d\n", `__FILE__,`__LINE__, (gotv), (minv), (maxv)); `stop; end while(0); +// verilog_format: on + +class SimpleRandClass; + rand bit [7:0] value; + constraint value_con { value > 0 && value < 200; } + function new(); + endfunction +endclass + +module t; + SimpleRandClass obj; + int rand_result; + + initial begin + obj = null; + rand_result = obj.randomize(); + `checkd(rand_result, 0); + + obj = new(); + rand_result = obj.randomize(); + `checkd(rand_result, 1); + `check_range(obj.value, 1, 199); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule