Support unpacked struct stream (#7767)

This commit is contained in:
Nick Brereton 2026-06-12 17:32:01 -04:00 committed by GitHub
parent e03fa6c783
commit 87d2610674
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 691 additions and 19 deletions

View File

@ -170,6 +170,10 @@ public:
void generic(bool flag) { m_generic = flag; }
std::pair<uint32_t, uint32_t> dimensions(bool includeBasic) const;
uint32_t arrayUnpackedElements() const; // 1, or total multiplication of all dimensions
// Fixed aggregate streaming properties
bool isStreamableFixedAggregate() const;
bool containsUnpackedStruct() const;
int widthStream() const;
static int uniqueNumInc() { return ++s_uniqueNum; }
const char* charIQWN() const {
return (isString() ? "N" : isWide() ? "W" : isDouble() ? "D" : isQuad() ? "Q" : "I");

View File

@ -1259,6 +1259,47 @@ uint32_t AstNodeDType::arrayUnpackedElements() const {
return entries;
}
bool AstNodeDType::isStreamableFixedAggregate() const {
const AstNodeDType* const dtypep = skipRefp();
if (const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) {
return adtypep->subDTypep()->isStreamableFixedAggregate();
} else if (const AstNodeUOrStructDType* const sdtypep = VN_CAST(dtypep, NodeUOrStructDType)) {
if (sdtypep->packed()) return true;
if (!VN_IS(sdtypep, StructDType)) return false;
for (const AstMemberDType* itemp = sdtypep->membersp(); itemp;
itemp = VN_AS(itemp->nextp(), MemberDType)) {
if (!itemp->dtypep()->isStreamableFixedAggregate()) return false;
}
return true;
}
return dtypep->isIntegralOrPacked() || dtypep->isDouble();
}
bool AstNodeDType::containsUnpackedStruct() const {
const AstNodeDType* const dtypep = skipRefp();
if (const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) {
return adtypep->subDTypep()->containsUnpackedStruct();
}
const AstStructDType* const sdtypep = VN_CAST(dtypep, StructDType);
return sdtypep && !sdtypep->packed();
}
int AstNodeDType::widthStream() const {
const AstNodeDType* const dtypep = skipRefp();
if (const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) {
return adtypep->subDTypep()->widthStream() * adtypep->elementsConst();
} else if (const AstNodeUOrStructDType* const sdtypep = VN_CAST(dtypep, NodeUOrStructDType)) {
if (!VN_IS(sdtypep, StructDType) || sdtypep->packed()) return width();
int width = 0;
for (const AstMemberDType* itemp = sdtypep->membersp(); itemp;
itemp = VN_AS(itemp->nextp(), MemberDType)) {
width += itemp->dtypep()->widthStream();
}
return width;
}
return dtypep->width();
}
std::pair<uint32_t, uint32_t> AstNodeDType::dimensions(bool includeBasic) const {
// How many array dimensions (packed,unpacked) does this Var have?
uint32_t packed = 0;

View File

@ -973,6 +973,96 @@ class ConstVisitor final : public VNVisitor {
return !numv.isNumber() ? numv : V3Number{nodep, nodep->width(), numv};
}
static bool lowerAsFixedAggregate(const AstNodeDType* const dtypep) {
return dtypep->isStreamableFixedAggregate() && dtypep->containsUnpackedStruct();
}
AstStructSel* newStructSel(AstNodeExpr* const fromp, const AstMemberDType* const itemp) {
AstStructSel* const selp = new AstStructSel{fromp->fileline(), fromp, itemp->name()};
selp->dtypeFrom(itemp->dtypep());
return selp;
}
void collectFixedAggregateTerms(AstNodeExpr* const fromp, std::vector<AstNodeExpr*>& termps,
const bool packReal) {
const AstNodeDType* const dtypep = fromp->dtypep()->skipRefp();
if (const AstUnpackArrayDType* const unpackDtypep = VN_CAST(dtypep, UnpackArrayDType)) {
const int left = unpackDtypep->left();
const int right = unpackDtypep->right();
const int step = left <= right ? 1 : -1;
for (int idx = left;; idx += step) {
AstArraySel* const selp
= new AstArraySel{fromp->fileline(), fromp->cloneTreePure(false), idx};
collectFixedAggregateTerms(selp, termps, packReal);
if (idx == right) break;
}
VL_DO_DANGLING(pushDeletep(fromp), fromp);
} else if (const AstNodeUOrStructDType* const sdtypep
= VN_CAST(dtypep, NodeUOrStructDType)) {
if (sdtypep->packed()) {
termps.push_back(fromp);
return;
}
for (const AstMemberDType* itemp = sdtypep->membersp(); itemp;
itemp = VN_AS(itemp->nextp(), MemberDType)) {
collectFixedAggregateTerms(newStructSel(fromp->cloneTreePure(false), itemp),
termps, packReal);
}
VL_DO_DANGLING(pushDeletep(fromp), fromp);
} else if (packReal && dtypep->isDouble()) {
termps.push_back(new AstRealToBits{fromp->fileline(), fromp});
} else {
termps.push_back(fromp);
}
}
AstNodeExpr* packFixedAggregate(AstNodeExpr* const fromp) {
std::vector<AstNodeExpr*> termps;
collectFixedAggregateTerms(fromp, termps, true);
UASSERT(!termps.empty(), "No stream terms");
AstNodeExpr* resultp = termps[0];
for (size_t i = 1; i < termps.size(); ++i) {
resultp = new AstConcat{resultp->fileline(), resultp, termps[i]};
}
return resultp;
}
void replaceAssignToFixedAggregate(AstNodeAssign* const nodep, AstNodeExpr* const dstp,
AstNodeExpr* srcp) {
const int dstWidth = dstp->dtypep()->widthStream();
std::vector<AstNodeExpr*> termps;
collectFixedAggregateTerms(dstp, termps, false);
int srcWidth = srcp->width();
if (srcWidth < dstWidth) {
AstExtend* const extendp = new AstExtend{srcp->fileline(), srcp};
extendp->dtypeSetLogicSized(dstWidth, VSigning::UNSIGNED);
srcp = new AstShiftL{
extendp->fileline(), extendp,
new AstConst{extendp->fileline(), static_cast<uint32_t>(dstWidth - srcWidth)},
dstWidth};
srcWidth = dstWidth;
}
AstNodeAssign* newp = nullptr;
int offset = 0;
for (size_t i = 0; i < termps.size(); ++i) {
AstNodeExpr* const termp = termps[i];
const int width = termp->dtypep()->widthStream();
const int lsb = srcWidth - offset - width;
offset += width;
AstNodeExpr* rhsp
= new AstSel{srcp->fileline(), srcp->cloneTreePure(false), lsb, width};
if (termp->dtypep()->skipRefp()->isDouble()) {
rhsp = new AstBitsToRealD{rhsp->fileline(), rhsp};
}
AstNodeAssign* const assignp = nodep->cloneType(termp, rhsp);
assignp->dtypeFrom(termp);
newp = AstNode::addNext(newp, assignp);
}
nodep->addNextHere(newp);
VL_DO_DANGLING(pushDeletep(srcp), srcp);
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
}
bool operandConst(const AstNode* nodep) { return VN_IS(nodep, Const); }
bool operandAsvConst(const AstNode* nodep) {
// BIASV(CONST, BIASV(CONST,...)) -> BIASV( BIASV_CONSTED(a,b), ...)
@ -2393,6 +2483,25 @@ class ConstVisitor final : public VNVisitor {
VL_DO_DANGLING(pushDeletep(conp), conp);
// Further reduce, either node may have more reductions.
return true;
} else if (m_doV && VN_IS(nodep->rhsp(), CvtPackedToArray)
&& lowerAsFixedAggregate(nodep->lhsp()->dtypep())) {
AstCvtPackedToArray* const cvtp
= VN_AS(nodep->rhsp(), CvtPackedToArray)->unlinkFrBack();
AstNodeExpr* srcp = cvtp->fromp()->unlinkFrBack();
if (lowerAsFixedAggregate(srcp->dtypep())) {
srcp = packFixedAggregate(srcp);
} else if (AstNodeStream* const streamp = VN_CAST(srcp, NodeStream)) {
AstNodeExpr* const streamSrcp = streamp->lhsp();
if (lowerAsFixedAggregate(streamSrcp->dtypep())) {
AstNodeExpr* const packedp = packFixedAggregate(streamSrcp->unlinkFrBack());
streamp->lhsp(packedp);
streamp->dtypeSetLogicUnsized(packedp->width(), packedp->widthMin(),
VSigning::UNSIGNED);
}
}
VL_DO_DANGLING(pushDeletep(cvtp), cvtp);
replaceAssignToFixedAggregate(nodep, nodep->lhsp()->unlinkFrBack(), srcp);
return true;
} else if (m_doV && VN_IS(nodep->rhsp(), StreamR)
&& !VN_IS(nodep->lhsp()->dtypep()->skipRefp(), QueueDType)) {
// The right-streaming operator on rhs of assignment does not
@ -2402,7 +2511,9 @@ class ConstVisitor final : public VNVisitor {
AstNodeExpr* srcp = streamp->lhsp()->unlinkFrBack();
AstNodeDType* const srcDTypep = srcp->dtypep()->skipRefp();
const AstNodeDType* const dstDTypep = nodep->lhsp()->dtypep()->skipRefp();
if (VN_IS(srcDTypep, QueueDType) || VN_IS(srcDTypep, DynArrayDType)) {
if (lowerAsFixedAggregate(srcDTypep)) {
srcp = packFixedAggregate(srcp);
} else if (VN_IS(srcDTypep, QueueDType) || VN_IS(srcDTypep, DynArrayDType)) {
if (VN_IS(dstDTypep, QueueDType) || VN_IS(dstDTypep, DynArrayDType)) {
int srcElementBits = 0;
if (const AstNodeDType* const elemDtp = srcDTypep->subDTypep()) {
@ -2429,6 +2540,19 @@ class ConstVisitor final : public VNVisitor {
srcp = new AstShiftL{srcp->fileline(), srcp,
new AstConst{srcp->fileline(), offset}, packedBits};
}
if (!VN_IS(dstDTypep, UnpackArrayDType) && !VN_IS(dstDTypep, QueueDType)
&& !VN_IS(dstDTypep, DynArrayDType)) {
const int sWidth = srcp->width();
const int dWidth = nodep->lhsp()->width();
if (sWidth < dWidth) {
AstExtend* const extendp = new AstExtend{srcp->fileline(), srcp};
extendp->dtypeSetLogicSized(dWidth, VSigning::UNSIGNED);
srcp = new AstShiftL{
srcp->fileline(), extendp,
new AstConst{srcp->fileline(), static_cast<uint32_t>(dWidth - sWidth)},
dWidth};
}
}
nodep->rhsp(srcp);
VL_DO_DANGLING(pushDeletep(streamp), streamp);
// Further reduce, any of the nodes may have more reductions.
@ -2438,7 +2562,7 @@ class ConstVisitor final : public VNVisitor {
AstNodeExpr* streamp = nodep->lhsp()->unlinkFrBack();
AstNodeExpr* const dstp = VN_AS(streamp, StreamL)->lhsp()->unlinkFrBack();
AstNodeDType* const dstDTypep = dstp->dtypep()->skipRefp();
AstNodeExpr* const srcp = nodep->rhsp()->unlinkFrBack();
AstNodeExpr* srcp = nodep->rhsp()->unlinkFrBack();
const AstNodeDType* const srcDTypep = srcp->dtypep()->skipRefp();
// Handle unpacked/queue/dynarray source -> queue/dynarray dest via
// CvtArrayToArray (StreamL reverses, so reverse=true)
@ -2466,6 +2590,7 @@ class ConstVisitor final : public VNVisitor {
VL_DO_DANGLING(pushDeletep(streamp), streamp);
return true;
}
if (lowerAsFixedAggregate(srcDTypep)) srcp = packFixedAggregate(srcp);
const int sWidth = srcp->width();
const int dWidth = dstp->width();
// Connect the rhs to the stream operator and update its width
@ -2556,7 +2681,7 @@ class ConstVisitor final : public VNVisitor {
} else {
// Source narrower than destination: left-justify by shifting left.
// The right stream operator packs left-to-right, so remaining
// LSBs are zero-filled (IEEE 1800-2023 11.4.14.2).
// LSBs are zero-filled (IEEE 1800-2023 11.4.14.3).
if (!VN_IS(srcp->dtypep()->skipRefp(), QueueDType)) {
AstExtend* const extendp = new AstExtend{srcp->fileline(), srcp};
extendp->dtypeSetLogicSized(dWidth, VSigning::UNSIGNED);
@ -2580,6 +2705,13 @@ class ConstVisitor final : public VNVisitor {
AstNodeExpr* srcp = streamp->lhsp();
const AstNodeDType* const srcDTypep = srcp->dtypep()->skipRefp();
AstNodeDType* const dstDTypep = nodep->lhsp()->dtypep()->skipRefp();
if (lowerAsFixedAggregate(srcDTypep)) {
AstNodeExpr* const packedp = packFixedAggregate(srcp->unlinkFrBack());
streamp->lhsp(packedp);
streamp->dtypeSetLogicUnsized(packedp->width(), packedp->widthMin(),
VSigning::UNSIGNED);
srcp = packedp;
}
if ((VN_IS(srcDTypep, QueueDType) || VN_IS(srcDTypep, DynArrayDType)
|| VN_IS(srcDTypep, UnpackArrayDType))) {
if (VN_IS(dstDTypep, QueueDType) || VN_IS(dstDTypep, DynArrayDType)) {

View File

@ -255,13 +255,6 @@ class WidthVisitor final : public VNVisitor {
EXTEND_OFF // No extension
};
int widthUnpacked(const AstNodeDType* const dtypep) {
if (const AstUnpackArrayDType* const arrDtypep = VN_CAST(dtypep, UnpackArrayDType)) {
return arrDtypep->subDTypep()->width() * arrDtypep->arrayUnpackedElements();
}
return dtypep->width();
}
static void packIfUnpacked(AstNodeExpr* const nodep) {
if (AstUnpackArrayDType* const unpackDTypep = VN_CAST(nodep->dtypep(), UnpackArrayDType)) {
const int elementsNum = unpackDTypep->arrayUnpackedElements();
@ -274,6 +267,9 @@ class WidthVisitor final : public VNVisitor {
nodep->findLogicDType(unpackBits, unpackMinBits, VSigning::UNSIGNED)});
}
}
static bool lowerAsFixedAggregate(const AstNodeDType* const dtypep) {
return dtypep->isStreamableFixedAggregate() && dtypep->containsUnpackedStruct();
}
// When fromp() is a DType (e.g. unlinked RefDType), resolve through
// the ref chain; when it's an expression, dtypep() is already resolved.
static AstNodeDType* fromDTypep(AstNode* fromp) {
@ -979,7 +975,10 @@ class WidthVisitor final : public VNVisitor {
}
const AstNodeDType* const lhsDtypep = nodep->lhsp()->dtypep()->skipRefToEnump();
if (VN_IS(lhsDtypep, DynArrayDType) || VN_IS(lhsDtypep, QueueDType)
|| VN_IS(lhsDtypep, UnpackArrayDType)) {
|| (VN_IS(lhsDtypep, UnpackArrayDType) && lhsDtypep->isStreamableFixedAggregate())
|| (VN_IS(lhsDtypep, NodeUOrStructDType)
&& !VN_AS(lhsDtypep, NodeUOrStructDType)->packed()
&& lhsDtypep->isStreamableFixedAggregate())) {
nodep->dtypeSetStream();
} else if (lhsDtypep->isCompound()) {
nodep->v3warn(E_UNSUPPORTED,
@ -6295,14 +6294,14 @@ class WidthVisitor final : public VNVisitor {
userIterateAndNext(nodep->rhsp(), WidthVP{nodep->dtypep(), PRELIM}.p());
//
// UINFOTREE(1, nodep, "", "assign");
AstNodeDType* const lhsDTypep
AstNodeDType* lhsDTypep
= nodep->lhsp()->dtypep(); // Note we use rhsp for context determined
// Check width of stream and wrap if needed
if (AstNodeStream* const streamp = VN_CAST(nodep->rhsp(), NodeStream)) {
AstNodeDType* const lhsDTypeSkippedRefp = lhsDTypep->skipRefp();
const int lwidth = widthUnpacked(lhsDTypeSkippedRefp);
const int rwidth = widthUnpacked(streamp->lhsp()->dtypep()->skipRefp());
const int lwidth = lhsDTypeSkippedRefp->widthStream();
const int rwidth = streamp->lhsp()->dtypep()->skipRefp()->widthStream();
if (lwidth != 0 && lwidth < rwidth) {
nodep->v3widthWarn(lwidth, rwidth,
"Target fixed size variable ("
@ -6314,16 +6313,18 @@ class WidthVisitor final : public VNVisitor {
const int queueElementSize = streamp->lhsp()->dtypep()->subDTypep()->width();
UASSERT_OBJ(queueElementSize <= lwidth, nodep, "LHS < RHS");
}
if (VN_IS(lhsDTypeSkippedRefp, UnpackArrayDType)) {
if (VN_IS(lhsDTypeSkippedRefp, UnpackArrayDType)
|| lowerAsFixedAggregate(lhsDTypeSkippedRefp)) {
streamp->unlinkFrBack();
nodep->rhsp(new AstCvtPackedToArray{streamp->fileline(), streamp,
lhsDTypeSkippedRefp});
}
}
if (const AstNodeStream* const streamp = VN_CAST(nodep->lhsp(), NodeStream)) {
if (AstNodeStream* const streamp = VN_CAST(nodep->lhsp(), NodeStream)) {
const AstNodeDType* const rhsDTypep = nodep->rhsp()->dtypep()->skipRefp();
const int lwidth = widthUnpacked(streamp->lhsp()->dtypep()->skipRefp());
const int rwidth = widthUnpacked(rhsDTypep);
AstNodeDType* const lhsStreamDTypep = streamp->lhsp()->dtypep()->skipRefp();
const int lwidth = lhsStreamDTypep->widthStream();
const int rwidth = rhsDTypep->widthStream();
if (rwidth != 0 && rwidth < lwidth) {
nodep->v3widthWarn(lwidth, rwidth,
"Stream target requires "
@ -6331,7 +6332,27 @@ class WidthVisitor final : public VNVisitor {
<< " bits, but source expression only provides "
<< rwidth << " bits (IEEE 1800-2023 11.4.14.3)");
}
if (VN_IS(rhsDTypep, UnpackArrayDType)) {
if (lowerAsFixedAggregate(lhsStreamDTypep)) {
AstNodeExpr* const streamExprp = nodep->lhsp()->unlinkFrBack();
AstNodeExpr* const dstp = streamp->lhsp()->unlinkFrBack();
AstNodeExpr* srcp = nodep->rhsp()->unlinkFrBack();
if (VN_IS(streamp, StreamL)) {
streamp->lhsp(srcp);
streamp->dtypeSetLogicUnsized(srcp->width(), srcp->widthMin(),
VSigning::UNSIGNED);
srcp = streamExprp;
} else {
if (srcp->width() > lwidth) {
srcp = new AstSel{streamp->fileline(), srcp, srcp->width() - lwidth,
lwidth};
}
VL_DO_DANGLING(pushDeletep(streamExprp), streamExprp);
}
nodep->lhsp(dstp);
nodep->rhsp(new AstCvtPackedToArray{srcp->fileline(), srcp, lhsStreamDTypep});
nodep->dtypeFrom(dstp);
lhsDTypep = nodep->lhsp()->dtypep();
} else if (VN_IS(rhsDTypep, UnpackArrayDType)) {
AstNodeExpr* const rhsp = nodep->rhsp()->unlinkFrBack();
nodep->rhsp(
new AstCvtArrayToPacked{rhsp->fileline(), rhsp, streamp->dtypep()});

View File

@ -0,0 +1,18 @@
#!/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.compile()
test.execute()
test.passes()

View File

@ -0,0 +1,347 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// 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
// Ref. to IEEE 1800-2023 11.4.14, A.8.1
module t;
`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);
typedef struct packed {
logic [3:0] hi;
logic [3:0] lo;
} packed_pair_t;
typedef union packed {
logic [7:0] byte_data;
packed_pair_t pair;
} packed_union_t;
typedef struct {
byte a;
logic [3:0] b;
packed_pair_t p;
} simple_t;
typedef struct {
byte prefix;
byte data[2];
packed_pair_t p;
} array_t;
typedef struct {
simple_t inner;
byte tail[2];
} nested_t;
typedef struct {
byte prefix;
simple_t items[2];
byte suffix;
} struct_array_t;
typedef struct {
byte data[1:0];
} descending_array_t;
typedef struct {
byte matrix[2][3];
} matrix_array_t;
typedef struct {
logic [1:0][3:0] data[2];
} mixed_array_t;
typedef enum logic [2:0] {
E0 = 3'd0,
E5 = 3'd5
} enum_t;
typedef struct {
enum_t e;
logic [4:0] x;
} enum_struct_t;
typedef struct {
byte tag;
real r;
realtime rt;
real ra[2];
} real_struct_t;
typedef struct {
packed_union_t u;
byte tail;
} packed_union_struct_t;
simple_t simple;
simple_t simple_out;
simple_t simple_cont_out;
array_t arrayed;
array_t arrayed_out;
nested_t nested;
nested_t nested_out;
struct_array_t struct_array;
struct_array_t struct_array_out;
descending_array_t descending;
descending_array_t descending_out;
matrix_array_t matrix_array;
matrix_array_t matrix_array_out;
mixed_array_t mixed_array;
mixed_array_t mixed_array_out;
simple_t simple_array[2];
simple_t simple_array_out[2];
enum_struct_t enum_struct;
enum_struct_t enum_struct_out;
real_struct_t real_struct;
real_struct_t real_struct_out;
packed_union_struct_t packed_union_struct;
packed_union_struct_t packed_union_struct_out;
logic [19:0] simple_bits;
logic [31:0] wide_simple_bits;
logic [31:0] array_bits;
logic [35:0] nested_bits;
logic [55:0] struct_array_bits;
logic [15:0] descending_bits;
logic [47:0] matrix_array_bits;
logic [15:0] mixed_array_bits;
logic [39:0] simple_array_bits;
logic [31:0] byteswapped_bits;
logic [7:0] enum_bits;
logic [263:0] real_bits;
logic [15:0] packed_union_bits;
logic [11:0] narrow_bits;
logic [19:0] simple_streaml_src;
byte byte_array_out[2];
assign {>>{simple_cont_out}} = 20'habcde;
initial begin
byteswapped_bits = {<<8{32'h11223344}};
`checkh(byteswapped_bits, 32'h44332211);
byteswapped_bits = {<<byte{32'h11223344}};
`checkh(byteswapped_bits, 32'h44332211);
{>>{simple_bits}} = 20'h13579;
`checkh(simple_bits, 20'h13579);
{<<4{simple_bits}} = 20'h12345;
`checkh(simple_bits, 20'h54321);
simple = '{8'h12, 4'ha, '{4'hb, 4'hc}};
simple_bits = {>>{simple}};
`checkh(simple_bits, 20'h12abc);
/* verilator lint_off WIDTHEXPAND */
wide_simple_bits = {>>{simple}};
/* verilator lint_on WIDTHEXPAND */
`checkh(wide_simple_bits, 32'h12abc000);
simple_bits = {<<4{simple}};
`checkh(simple_bits, 20'hcba21);
{>>{simple_out}} = 20'h345de;
`checkh(simple_out.a, 8'h34);
`checkh(simple_out.b, 4'h5);
`checkh(simple_out.p, 8'hde);
`checkh(simple_cont_out.a, 8'hab);
`checkh(simple_cont_out.b, 4'hc);
`checkh(simple_cont_out.p, 8'hde);
{<<4{simple_out}} = 20'h789ab;
`checkh(simple_out.a, 8'hba);
`checkh(simple_out.b, 4'h9);
`checkh(simple_out.p, 8'h87);
simple_out = {>>{20'hfedcb}};
`checkh(simple_out.a, 8'hfe);
`checkh(simple_out.b, 4'hd);
`checkh(simple_out.p, 8'hcb);
simple_out = {>>{simple}};
`checkh(simple_out.a, 8'h12);
`checkh(simple_out.b, 4'ha);
`checkh(simple_out.p, 8'hbc);
{>>{simple_out}} = simple;
`checkh(simple_out.a, 8'h12);
`checkh(simple_out.b, 4'ha);
`checkh(simple_out.p, 8'hbc);
if ($test$plusargs("t_stream_unpacked_struct_alt")) begin
narrow_bits = 12'h123;
end else begin
narrow_bits = 12'habd;
end
/* verilator lint_off WIDTHEXPAND */
simple_bits = {>>{narrow_bits}};
/* verilator lint_on WIDTHEXPAND */
`checkh(simple_bits, {narrow_bits, 8'h00});
simple_out = {>>{narrow_bits}};
`checkh(simple_out.a, narrow_bits[11:4]);
`checkh(simple_out.b, narrow_bits[3:0]);
`checkh(simple_out.p, 8'h00);
{>>{simple_out}} = 24'habcdef;
`checkh(simple_out.a, 8'hab);
`checkh(simple_out.b, 4'hc);
`checkh(simple_out.p, 8'hde);
simple_out = {<<4{20'h13579}};
`checkh(simple_out.a, 8'h97);
`checkh(simple_out.b, 4'h5);
`checkh(simple_out.p, 8'h31);
simple_streaml_src = 20'h2468a;
simple_out = {<<4{simple_streaml_src}};
`checkh(simple_out.a, 8'ha8);
`checkh(simple_out.b, 4'h6);
`checkh(simple_out.p, 8'h42);
{<<4{simple_out}} = simple_streaml_src;
`checkh(simple_out.a, 8'ha8);
`checkh(simple_out.b, 4'h6);
`checkh(simple_out.p, 8'h42);
{<<8{byte_array_out}} = 16'h1234;
`checkh(byte_array_out[0], 8'h34);
`checkh(byte_array_out[1], 8'h12);
arrayed = '{8'h11, '{8'h22, 8'h33}, '{4'h4, 4'h5}};
array_bits = {>>{arrayed}};
`checkh(array_bits, 32'h11223345);
array_bits = {<<8{arrayed}};
`checkh(array_bits, 32'h45332211);
{>>{arrayed_out}} = 32'haabbccdd;
`checkh(arrayed_out.prefix, 8'haa);
`checkh(arrayed_out.data[0], 8'hbb);
`checkh(arrayed_out.data[1], 8'hcc);
`checkh(arrayed_out.p, 8'hdd);
nested = '{'{8'h12, 4'ha, '{4'hb, 4'hc}}, '{8'h34, 8'h56}};
nested_bits = {>>{nested}};
`checkh(nested_bits, 36'h12abc3456);
{>>{nested_out}} = 36'h6543210fe;
`checkh(nested_out.inner.a, 8'h65);
`checkh(nested_out.inner.b, 4'h4);
`checkh(nested_out.inner.p, 8'h32);
`checkh(nested_out.tail[0], 8'h10);
`checkh(nested_out.tail[1], 8'hfe);
struct_array.prefix = 8'haa;
struct_array.items[0] = '{8'h12, 4'h3, '{4'h4, 4'h5}};
struct_array.items[1] = '{8'h67, 4'h8, '{4'h9, 4'ha}};
struct_array.suffix = 8'hbb;
struct_array_bits = {>>{struct_array}};
`checkh(struct_array_bits, 56'haa123456789abb);
{>>{struct_array_out}} = 56'hccdef0123456dd;
`checkh(struct_array_out.prefix, 8'hcc);
`checkh(struct_array_out.items[0].a, 8'hde);
`checkh(struct_array_out.items[0].b, 4'hf);
`checkh(struct_array_out.items[0].p, 8'h01);
`checkh(struct_array_out.items[1].a, 8'h23);
`checkh(struct_array_out.items[1].b, 4'h4);
`checkh(struct_array_out.items[1].p, 8'h56);
`checkh(struct_array_out.suffix, 8'hdd);
descending = '{data: '{8'hcc, 8'hdd}};
descending_bits = {>>{descending}};
`checkh(descending_bits, 16'hccdd);
{>>{descending_out}} = 16'h1234;
`checkh(descending_out.data[1], 8'h12);
`checkh(descending_out.data[0], 8'h34);
matrix_array.matrix[0][0] = 8'h11;
matrix_array.matrix[0][1] = 8'h22;
matrix_array.matrix[0][2] = 8'h33;
matrix_array.matrix[1][0] = 8'h44;
matrix_array.matrix[1][1] = 8'h55;
matrix_array.matrix[1][2] = 8'h66;
matrix_array_bits = {>>{matrix_array}};
`checkh(matrix_array_bits, 48'h112233445566);
{>>{matrix_array_out}} = 48'haabbccddeeff;
`checkh(matrix_array_out.matrix[0][0], 8'haa);
`checkh(matrix_array_out.matrix[0][1], 8'hbb);
`checkh(matrix_array_out.matrix[0][2], 8'hcc);
`checkh(matrix_array_out.matrix[1][0], 8'hdd);
`checkh(matrix_array_out.matrix[1][1], 8'hee);
`checkh(matrix_array_out.matrix[1][2], 8'hff);
mixed_array.data[0] = 8'hab;
mixed_array.data[1] = 8'hcd;
mixed_array_bits = {>>{mixed_array}};
`checkh(mixed_array_bits, 16'habcd);
{>>{mixed_array_out}} = 16'h1234;
`checkh(mixed_array_out.data[0], 8'h12);
`checkh(mixed_array_out.data[0][1], 4'h1);
`checkh(mixed_array_out.data[0][0], 4'h2);
`checkh(mixed_array_out.data[1], 8'h34);
`checkh(mixed_array_out.data[1][1], 4'h3);
`checkh(mixed_array_out.data[1][0], 4'h4);
simple_array[0] = '{8'h12, 4'ha, '{4'hb, 4'hc}};
simple_array[1] = '{8'h34, 4'hd, '{4'he, 4'hf}};
simple_array_bits = {>>{simple_array}};
`checkh(simple_array_bits, 40'h12abc34def);
{>>{simple_array_out}} = 40'h567899abcd;
`checkh(simple_array_out[0].a, 8'h56);
`checkh(simple_array_out[0].b, 4'h7);
`checkh(simple_array_out[0].p, 8'h89);
`checkh(simple_array_out[1].a, 8'h9a);
`checkh(simple_array_out[1].b, 4'hb);
`checkh(simple_array_out[1].p, 8'hcd);
enum_struct = '{E5, 5'ha};
enum_bits = {>>{enum_struct}};
`checkh(enum_bits, 8'haa);
{>>{enum_struct_out}} = 8'h71;
`checkh(enum_struct_out.e, 3'h3);
`checkh(enum_struct_out.x, 5'h11);
real_struct.tag = 8'h42;
real_struct.r = 1.0;
real_struct.rt = 3.0;
real_struct.ra[0] = 4.0;
real_struct.ra[1] = 5.0;
real_bits = {>>{real_struct}};
`checkh(real_bits,
{8'h42, $realtobits(1.0), $realtobits(3.0), $realtobits(4.0), $realtobits(5.0)});
{>>{real_struct_out}}
= {8'h99, $realtobits(2.0), $realtobits(6.0), $realtobits(7.0), $realtobits(8.0)};
`checkh(real_struct_out.tag, 8'h99);
`checkh($realtobits(real_struct_out.r), $realtobits(2.0));
`checkh($realtobits(real_struct_out.rt), $realtobits(6.0));
`checkh($realtobits(real_struct_out.ra[0]), $realtobits(7.0));
`checkh($realtobits(real_struct_out.ra[1]), $realtobits(8.0));
packed_union_struct.u.byte_data = 8'hbe;
packed_union_struct.tail = 8'hef;
packed_union_bits = {>>{packed_union_struct}};
`checkh(packed_union_bits, 16'hbeef);
{>>{packed_union_struct_out}} = 16'hcafe;
`checkh(packed_union_struct_out.u.byte_data, 8'hca);
`checkh(packed_union_struct_out.tail, 8'hfe);
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,30 @@
%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:54:12: Unsupported: Stream operation on a variable of a type 'struct{}t.queue_struct_t'
: ... note: In instance 't'
54 | out = {>>{queue_struct}};
| ^~
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:55:12: Unsupported: Stream operation on a variable of a type 'struct{}t.dynamic_struct_t'
: ... note: In instance 't'
55 | out = {>>{dynamic_struct}};
| ^~
%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:56:12: Unsupported: Stream operation on a variable of a type 'struct{}t.associative_struct_t'
: ... note: In instance 't'
56 | out = {>>{associative_struct}};
| ^~
%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:57:12: Unsupported: Stream operation on a variable of a type 'struct{}t.union_struct_t'
: ... note: In instance 't'
57 | out = {>>{union_struct}};
| ^~
%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:58:12: Unsupported: Stream operation on a variable of a type 'struct{}t.string_struct_t'
: ... note: In instance 't'
58 | out = {>>{string_struct}};
| ^~
%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:59:12: Unsupported: Stream operation on a variable of a type 'struct{}t.chandle_struct_t'
: ... note: In instance 't'
59 | out = {>>{chandle_struct}};
| ^~
%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:60:12: Unsupported: Stream operation on a variable of a type 'struct{}t.event_struct_t'
: ... note: In instance 't'
60 | out = {>>{event_struct}};
| ^~
%Error: Exiting due to

View File

@ -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('linter')
test.lint(fails=True, expect_filename=test.golden_filename)
test.passes()

View File

@ -0,0 +1,63 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// 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
module t;
typedef struct {
byte bytes[$];
} queue_struct_t;
typedef struct {
byte bytes[];
} dynamic_struct_t;
typedef struct {
byte bytes[int];
} associative_struct_t;
typedef union {
byte a;
logic [15:0] b;
} unpacked_union_t;
typedef struct {
unpacked_union_t u;
} union_struct_t;
typedef struct {
string s;
} string_struct_t;
typedef struct {
chandle c;
} chandle_struct_t;
typedef struct {
event e;
} event_struct_t;
logic [255:0] out;
queue_struct_t queue_struct;
dynamic_struct_t dynamic_struct;
associative_struct_t associative_struct;
union_struct_t union_struct;
string_struct_t string_struct;
chandle_struct_t chandle_struct;
event_struct_t event_struct;
initial begin
out = {>>{queue_struct}};
out = {>>{dynamic_struct}};
out = {>>{associative_struct}};
out = {>>{union_struct}};
out = {>>{string_struct}};
out = {>>{chandle_struct}};
out = {>>{event_struct}};
end
endmodule