diff --git a/src/V3AstNodeDType.h b/src/V3AstNodeDType.h index fcb2183ba..6c791c2f2 100644 --- a/src/V3AstNodeDType.h +++ b/src/V3AstNodeDType.h @@ -968,7 +968,7 @@ class AstMemberDType final : public AstNodeDType { string m_tag; // Holds the string of the verilator tag -- used in JSON output. int m_lsb = -1; // Within this level's packed struct, the LSB of the first bit of the member bool m_constrainedRand = false; - // UNSUP: int m_randType; // Randomization type (IEEE) + VRandAttr m_rand; // Randomizability of this member (rand, randc, etc) public: AstMemberDType(FileLine* fl, const string& name, VFlagChildDType, AstNodeDType* dtp, AstNode* valuep) @@ -1025,6 +1025,8 @@ public: } bool isConstrainedRand() const { return m_constrainedRand; } void markConstrainedRand(bool flag) { m_constrainedRand = flag; } + VRandAttr rand() const { return m_rand; } + void rand(const VRandAttr flag) { m_rand = flag; } }; class AstNBACommitQueueDType final : public AstNodeDType { // @astgen ptr := m_subDTypep : AstNodeDType // Type of the corresponding variable diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 9b94d4e89..830c97999 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -2499,12 +2499,14 @@ const char* AstLoopTest::broken() const { void AstMemberDType::dump(std::ostream& str) const { this->AstNodeDType::dump(str); if (isConstrainedRand()) str << " [CONSTRAINEDRAND]"; + if (rand().isRandomizable()) str << " [" << rand() << "]"; if (name() != "") str << " name=" << name(); if (tag() != "") str << " tag=" << tag(); } void AstMemberDType::dumpJson(std::ostream& str) const { dumpJsonBoolFuncIf(str, isConstrainedRand); + if (rand().isRandomizable()) dumpJsonStr(str, "rand", rand().ascii()); dumpJsonStrFunc(str, name); dumpJsonStrFunc(str, tag); dumpJsonGen(str); diff --git a/src/V3ParseGrammar.h b/src/V3ParseGrammar.h index c081d599a..ad46d9a05 100644 --- a/src/V3ParseGrammar.h +++ b/src/V3ParseGrammar.h @@ -31,6 +31,7 @@ public: AstCase* m_caseAttrp = nullptr; // Current case statement for attribute adding AstNodeDType* m_varDTypep = nullptr; // Pointer to data type for next signal declaration AstNodeDType* m_memDTypep = nullptr; // Pointer to data type for next member declaration + VRandAttr m_memRand; // 'rand'/'randc' qualifier for next member declaration AstDelay* m_netDelayp = nullptr; // Pointer to delay for next signal declaration AstStrengthSpec* m_netStrengthp = nullptr; // Pointer to strength for next net declaration FileLine* m_instModuleFl = nullptr; // Fileline of module referenced for instantiations diff --git a/src/V3ParseImp.h b/src/V3ParseImp.h index 882b0e817..ee1a836a9 100644 --- a/src/V3ParseImp.h +++ b/src/V3ParseImp.h @@ -69,6 +69,11 @@ struct VMemberQualifiers final { q.m_flags = a.m_flags | b.m_flags; return q; } + VRandAttr randAttr() const { + if (m_randc) return VRandAttr::RAND_CYCLIC; + if (m_rand) return VRandAttr::RAND; + return VRandAttr::NONE; + } void applyToNodes(AstNodeFTask* nodesp) const { for (AstNodeFTask* nodep = nodesp; nodep; nodep = VN_AS(nodep->nextp(), NodeFTask)) { if (m_local) nodep->isHideLocal(true); diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index ef0723b2a..405026788 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -783,6 +783,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(); @@ -1345,7 +1374,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(); @@ -1852,34 +1881,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/src/verilog.y b/src/verilog.y index 378c4f402..ad804dba3 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -2204,11 +2204,13 @@ struct_union_memberList: // IEEE: { struct_union_member } ; struct_union_member: // ==IEEE: struct_union_member - // // UNSUP random_qualifer not propagated until have randomize support random_qualifierE data_type_or_void - /*mid*/ { GRAMMARP->m_memDTypep = $2; } // As a list follows, need to attach this dtype to each member. + /*mid*/ { GRAMMARP->m_memDTypep = $2; // As a list follows, need to attach this dtype to each member. + GRAMMARP->m_memRand = $1.randAttr(); } /*cont*/ list_of_member_decl_assignments ';' - { $$ = $4; DEL(GRAMMARP->m_memDTypep); GRAMMARP->m_memDTypep = nullptr; } + { $$ = $4; + DEL(GRAMMARP->m_memDTypep); GRAMMARP->m_memDTypep = nullptr; + GRAMMARP->m_memRand = VRandAttr::NONE; } | vlTag { $$ = nullptr; } ; @@ -2226,6 +2228,7 @@ member_decl_assignment: // Derived from IEEE: variable_decl_assi ? GRAMMARP->m_memDTypep->cloneTree(true) : nullptr), $2, false), nullptr}; + $$->rand(GRAMMARP->m_memRand); PARSEP->tagNodep($$); } | idAny variable_dimensionListE '=' variable_declExpr @@ -2234,6 +2237,7 @@ member_decl_assignment: // Derived from IEEE: variable_decl_assi ? GRAMMARP->m_memDTypep->cloneTree(true) : nullptr), $2, false), $4}; + $$->rand(GRAMMARP->m_memRand); PARSEP->tagNodep($$); } | idSVKwd { $$ = nullptr; } 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 diff --git a/test_regress/t/t_debug_emitv.out b/test_regress/t/t_debug_emitv.out index 5fcf5c7d7..2574d2c56 100644 --- a/test_regress/t/t_debug_emitv.out +++ b/test_regress/t/t_debug_emitv.out @@ -10,6 +10,7 @@ module Vt_debug_emitv_t; } ps_t; typedef struct { logic signed [2:0] a; + logic [1:0] b; } us_t; typedef union { logic a; @@ -19,6 +20,7 @@ module Vt_debug_emitv_t; } ps[0:2]; struct { logic signed [2:0] a; + logic [1:0] b; } us; union { logic a; diff --git a/test_regress/t/t_debug_emitv.v b/test_regress/t/t_debug_emitv.v index 7e474c305..1255b5c3c 100644 --- a/test_regress/t/t_debug_emitv.v +++ b/test_regress/t/t_debug_emitv.v @@ -64,6 +64,7 @@ module t (/*AUTOARG*/ } ps_t; typedef struct { logic signed [2:0] a; + rand logic [1:0] b; } us_t; typedef union { logic a;