From 3f890f0f758792c4a158ea6766f547752836d6ef Mon Sep 17 00:00:00 2001 From: Philip Axer Date: Fri, 10 Jul 2026 16:31:52 +0200 Subject: [PATCH] Fix randomization of unreferenced rand members of unpacked structs (#7910) A rand member of a rand unpacked struct that no constraint references stopped being randomized (frozen at 0), while randomize() still returned 1. When a constraint referenced any member, the whole struct variable was marked "handled in constraints" (skipped by basic randomization) but only the explicitly-referenced members were handed to the solver, so unmentioned rand members were neither solved nor basic-randomized. Per IEEE 1800-2023 18.4 all random members of a rand unpacked struct are solved concurrently. When a constrained unpacked struct is seen, mark all of its rand members -- looking through unpacked-array dimensions and recursing into nested unpacked structs -- so the solver randomizes them too. Non-rand members keep their value; a directly-referenced member is still registered even when it has no rand qualifier. --- src/V3Randomize.cpp | 58 ++++-- test_regress/t/t_constraint_struct_unref.py | 21 ++ test_regress/t/t_constraint_struct_unref.v | 213 ++++++++++++++++++++ 3 files changed, 270 insertions(+), 22 deletions(-) create mode 100755 test_regress/t/t_constraint_struct_unref.py create mode 100644 test_regress/t/t_constraint_struct_unref.v diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index c0c9e1707..2bbae6d44 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -781,6 +781,35 @@ class ConstraintExprVisitor final : public VNVisitor { } } + // Mark the one directly-referenced member; it must reach the solver even when it + // carries no rand qualifier (the constraint refers to it). + void markConstrainedRandMember(AstStructDType* const structp, const std::string& name) { + AstMemberDType* const memberp + = VN_CAST(m_memberMap.findMember(structp, name), MemberDType); + UASSERT_OBJ(memberp, structp, "Constraint references unknown struct member " << name); + memberp->markConstrainedRand(true); + } + + // IEEE 1800-2023 18.4: all rand members of a rand unpacked struct are solved + // concurrently, so unreferenced rand members must still reach the solver. + static void markStructMembersConstrainedRand(AstNodeDType* const dtypep) { + // Look through unpacked array dimensions to the element type + AstNodeDType* elemp = dtypep->skipRefp(); + while (elemp->isNonPackedArray()) elemp = elemp->subDTypep()->skipRefp(); + AstStructDType* const structp = VN_CAST(elemp, StructDType); + if (!structp || structp->packed()) return; + if (structp->isConstrainedRand()) return; // Already processed + structp->markConstrainedRand(true); + for (AstMemberDType* memberp = structp->membersp(); memberp; + memberp = VN_AS(memberp->nextp(), MemberDType)) { + // Non-rand members keep their value. TODO: randc members are solved as + // plain rand (uniform); per-member cyclic state is not generated for structs. + if (!memberp->rand().isRandomizable()) continue; + memberp->markConstrainedRand(true); + markStructMembersConstrainedRand(memberp->subDTypep()); + } + } + // Build full path for a MemberSel chain (e.g., "obj.l2.l3.l4") std::string buildMemberPath(const AstMemberSel* const memberSelp) { const AstNode* fromp = memberSelp->fromp(); @@ -1342,7 +1371,7 @@ class ConstraintExprVisitor final : public VNVisitor { } if (VN_IS(varp->dtypeSkipRefp(), StructDType) && !VN_AS(varp->dtypeSkipRefp(), StructDType)->packed()) { - VN_AS(varp->dtypeSkipRefp(), StructDType)->markConstrainedRand(true); + markStructMembersConstrainedRand(varp->dtypeSkipRefp()); dimension = 1; } methodp->dtypeSetVoid(); @@ -1849,34 +1878,19 @@ class ConstraintExprVisitor final : public VNVisitor { m_structSel = true; if (VN_IS(nodep->fromp()->dtypep()->skipRefp(), StructDType)) { AstNodeExpr* const fromp = nodep->fromp(); - if (VN_IS(fromp, StructSel)) { - VN_AS(fromp->dtypep()->skipRefp(), StructDType)->markConstrainedRand(true); - } - AstMemberDType* memberp = VN_AS(fromp->dtypep()->skipRefp(), StructDType)->membersp(); - while (memberp) { - if (memberp->name() == nodep->name()) { - memberp->markConstrainedRand(true); - break; - } else - memberp = VN_CAST(memberp->nextp(), MemberDType); - } + AstStructDType* const structp = VN_AS(fromp->dtypep()->skipRefp(), StructDType); + markConstrainedRandMember(structp, nodep->name()); + markStructMembersConstrainedRand(fromp->dtypep()); } // Mark Random for structArray if (VN_IS(nodep->fromp(), ArraySel) || VN_IS(nodep->fromp(), CMethodHard)) { AstNodeExpr* const fromp = VN_IS(nodep->fromp(), ArraySel) ? VN_AS(nodep->fromp(), ArraySel)->fromp() : VN_AS(nodep->fromp(), CMethodHard)->fromp(); - AstStructDType* const dtypep + AstStructDType* const structp = VN_AS(fromp->dtypep()->skipRefp()->subDTypep()->skipRefp(), StructDType); - dtypep->markConstrainedRand(true); - AstMemberDType* memberp = dtypep->membersp(); - while (memberp) { - if (memberp->name() == nodep->name()) { - memberp->markConstrainedRand(true); - break; - } else - memberp = VN_CAST(memberp->nextp(), MemberDType); - } + markConstrainedRandMember(structp, nodep->name()); + markStructMembersConstrainedRand(fromp->dtypep()->skipRefp()->subDTypep()); } iterateChildren(nodep); FileLine* const fl = nodep->fileline(); diff --git a/test_regress/t/t_constraint_struct_unref.py b/test_regress/t/t_constraint_struct_unref.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_constraint_struct_unref.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_constraint_struct_unref.v b/test_regress/t/t_constraint_struct_unref.v new file mode 100644 index 000000000..82c915b58 --- /dev/null +++ b/test_regress/t/t_constraint_struct_unref.v @@ -0,0 +1,213 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// Regression for: a random member of a rand unpacked struct that no constraint +// references must still be randomized (IEEE 1800-2023 18.4 - all random +// members of a rand unpacked struct are solved concurrently). + +// 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 checkgt(gotv,minv) do if (!((gotv) > (minv))) begin \ + $write("%%Error: %s:%0d: got=%0d expected > %0d\n", `__FILE__, `__LINE__, (gotv), (minv)); `stop; end while(0) +// verilog_format: on + +typedef struct { + rand bit [3:0] inner_id; // UNCONSTRAINED nested random member +} inner_t; + +typedef struct { + rand bit [3:0] id; // UNCONSTRAINED random member + rand bit [2:0] size; // constrained random member + rand inner_t inner; // UNCONSTRAINED nested rand struct member + bit [3:0] fixed; // non-rand member: must keep its value +} unpacked_t; + +typedef struct packed { + bit [3:0] id; + bit [2:0] size; +} packed_t; + +class C; + rand unpacked_t u; + rand packed_t p; + // Reference ONLY .size on each; .id, .inner.inner_id are random members left + // unmentioned, and .fixed is a non-rand member. + constraint c { + u.size inside {[0 : 2]}; + p.size inside {[0 : 2]}; + } + function new(); + u.fixed = 4'hA; + endfunction +endclass + +// Shared struct type as a non-rand member of a different randomized class. +class D; + unpacked_t m; + rand bit [3:0] z; + constraint dz {z < 5;} +endclass + +// Struct-in-struct: constraint reaches into the nested struct. +typedef struct { + rand bit [3:0] a; // constrained nested member + rand bit [3:0] b; // UNCONSTRAINED nested member +} inner2_t; + +typedef struct { + rand inner2_t inr; + rand bit [3:0] x; // UNCONSTRAINED outer member +} outer_t; + +class E; + rand outer_t o; + constraint eo {o.inr.a < 3;} +endclass + +// A rand member with an initializer, and a randc member, both unreferenced. +typedef struct { + rand bit [3:0] wi = 4'h1; // rand member with initializer + randc bit [3:0] cyc; // randc member + rand bit [3:0] r; // constrained member +} extra_t; + +class F; + rand extra_t s; + constraint fc {s.r < 5;} +endclass + +// Unpacked array of struct as a rand member, left unreferenced by the constraint. +typedef struct {rand bit [3:0] f;} aelem_t; + +typedef struct { + rand bit [3:0] g; + rand aelem_t es[2]; +} arr_t; + +class G; + rand arr_t o; + constraint gc {o.g < 5;} +endclass + +module t_constraint_struct_unref; + initial begin + C c; + D d; + E e; + F f; + G g; + int uid[16]; + int pid[16]; + int iid[16]; + int zid[16]; + int bid[16]; + int xid[16]; + int wid[16]; + int cid[16]; + int e0id[16]; + int e1id[16]; + int un; + int pn; + int inn; + int zn; + int bn; + int xn; + int wn; + int cn; + int e0n; + int e1n; + // Non-randomized use of the shared type: must stay untouched. + unpacked_t plain; + outer_t plain2; + c = new(); + d = new(); + e = new(); + f = new(); + g = new(); + un = 0; + pn = 0; + inn = 0; + zn = 0; + bn = 0; + xn = 0; + wn = 0; + cn = 0; + e0n = 0; + e1n = 0; + plain.id = 4'h5; + plain.size = 3'h6; + plain.inner.inner_id = 4'h7; + plain.fixed = 4'h8; + d.m.id = 4'h1; + d.m.size = 3'h2; + d.m.inner.inner_id = 4'h3; + d.m.fixed = 4'h4; + plain2.inr.a = 4'h9; + plain2.inr.b = 4'hA; + plain2.x = 4'hB; + for (int i = 0; i < 300; i++) begin + `checkd(c.randomize(), 1); + `checkd((c.u.size inside {[0 : 2]}), 1'b1); + `checkd((c.p.size inside {[0 : 2]}), 1'b1); + `checkd(c.u.fixed, 4'hA); + uid[c.u.id]++; + pid[c.p.id]++; + iid[c.u.inner.inner_id]++; + `checkd(d.randomize(), 1); + `checkd((d.z < 5), 1'b1); + `checkd(d.m.id, 4'h1); + `checkd(d.m.size, 3'h2); + `checkd(d.m.inner.inner_id, 4'h3); + `checkd(d.m.fixed, 4'h4); + zid[d.z]++; + `checkd(e.randomize(), 1); + `checkd((e.o.inr.a < 3), 1'b1); + bid[e.o.inr.b]++; // unreferenced nested member + xid[e.o.x]++; // unreferenced outer member + `checkd(f.randomize(), 1); + `checkd((f.s.r < 5), 1'b1); + wid[f.s.wi]++; // unreferenced rand member with initializer + cid[f.s.cyc]++; // unreferenced randc member + `checkd(g.randomize(), 1); + `checkd((g.o.g < 5), 1'b1); + e0id[g.o.es[0].f]++; // unreferenced array-of-struct element + e1id[g.o.es[1].f]++; + end + foreach (uid[k]) if (uid[k] > 0) un++; + foreach (pid[k]) if (pid[k] > 0) pn++; + foreach (iid[k]) if (iid[k] > 0) inn++; + foreach (zid[k]) if (zid[k] > 0) zn++; + foreach (bid[k]) if (bid[k] > 0) bn++; + foreach (xid[k]) if (xid[k] > 0) xn++; + foreach (wid[k]) if (wid[k] > 0) wn++; + foreach (cid[k]) if (cid[k] > 0) cn++; + foreach (e0id[k]) if (e0id[k] > 0) e0n++; + foreach (e1id[k]) if (e1id[k] > 0) e1n++; + // Unreferenced rand members must vary; packed struct is the control case. + `checkgt(un, 7); + `checkgt(pn, 7); + `checkgt(inn, 7); + `checkgt(zn, 3); + `checkgt(bn, 7); + `checkgt(xn, 7); + `checkgt(wn, 7); + `checkgt(cn, 7); + `checkgt(e0n, 7); + `checkgt(e1n, 7); + `checkd(plain.id, 4'h5); + `checkd(plain.size, 3'h6); + `checkd(plain.inner.inner_id, 4'h7); + `checkd(plain.fixed, 4'h8); + `checkd(plain2.inr.a, 4'h9); + `checkd(plain2.inr.b, 4'hA); + `checkd(plain2.x, 4'hB); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule