This commit is contained in:
Philip Axer 2026-07-14 08:30:24 -04:00 committed by GitHub
commit 82ba47cbcd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 291 additions and 26 deletions

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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);

View File

@ -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();

View File

@ -2204,11 +2204,13 @@ struct_union_memberList<memberDTypep>: // IEEE: { struct_union_member }
;
struct_union_member<memberDTypep>: // ==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<memberDTypep>: // 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<memberDTypep>: // Derived from IEEE: variable_decl_assi
? GRAMMARP->m_memDTypep->cloneTree(true) : nullptr),
$2, false),
$4};
$$->rand(GRAMMARP->m_memRand);
PARSEP->tagNodep($$);
}
| idSVKwd { $$ = nullptr; }

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,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

View File

@ -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;

View File

@ -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;