This commit is contained in:
Nick Brereton 2026-07-14 19:05:39 -04:00 committed by GitHub
commit 3b7640a174
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 216 additions and 69 deletions

View File

@ -26,6 +26,7 @@
#include "V3Inst.h"
#include "V3Const.h"
#include "V3Width.h"
VL_DEFINE_DEBUG_FUNCTIONS;
@ -909,9 +910,14 @@ public:
<< pinexprp->width());
rhsp = extendOrSel(pinp->fileline(), rhsp, pinVarp);
pinp->exprp(new AstVarRef{newvarp->fileline(), newvarp, VAccess::WRITE});
AstNodeExpr* const rhsSelp = extendOrSel(pinp->fileline(), rhsp, pinexprp);
markContinuousLhs(pinexprp);
assignp = new AstAssignW{pinp->fileline(), pinexprp, rhsSelp};
if (VN_IS(pinexprp, NodeStream)) {
assignp = new AstAssignW{pinp->fileline(), pinexprp, rhsp};
V3Width::streamAssignLowerEdit(assignp);
} else {
AstNodeExpr* const rhsSelp = extendOrSel(pinp->fileline(), rhsp, pinexprp);
assignp = new AstAssignW{pinp->fileline(), pinexprp, rhsSelp};
}
} else {
// V3 width should have range/extended to make the widths correct
newvarp->isContinuously(true);

View File

@ -284,9 +284,6 @@ 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) {
@ -6402,70 +6399,8 @@ class WidthVisitor final : public VNVisitor {
userIterateAndNext(nodep->rhsp(), WidthVP{nodep->dtypep(), PRELIM}.p());
//
// UINFOTREE(1, nodep, "", "assign");
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 = lhsDTypeSkippedRefp->widthStream();
const int rwidth = streamp->lhsp()->dtypep()->skipRefp()->widthStream();
if (lwidth != 0 && lwidth < rwidth) {
nodep->v3widthWarn(lwidth, rwidth,
"Target fixed size variable ("
<< lwidth << " bits) is narrower than the stream ("
<< rwidth << " bits) (IEEE 1800-2023 11.4.14)");
}
if (VN_IS(streamp->lhsp()->dtypep()->skipRefp(), QueueDType)
&& !VN_IS(nodep->lhsp()->dtypep()->skipRefp(), QueueDType)) {
const int queueElementSize = streamp->lhsp()->dtypep()->subDTypep()->width();
UASSERT_OBJ(queueElementSize <= lwidth, nodep, "LHS < RHS");
}
if (VN_IS(lhsDTypeSkippedRefp, UnpackArrayDType)
|| lowerAsFixedAggregate(lhsDTypeSkippedRefp)) {
streamp->unlinkFrBack();
nodep->rhsp(new AstCvtPackedToArray{streamp->fileline(), streamp,
lhsDTypeSkippedRefp});
}
}
if (AstNodeStream* const streamp = VN_CAST(nodep->lhsp(), NodeStream)) {
const AstNodeDType* const rhsDTypep = nodep->rhsp()->dtypep()->skipRefp();
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 "
<< lwidth
<< " bits, but source expression only provides "
<< rwidth << " bits (IEEE 1800-2023 11.4.14.3)");
}
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()});
}
}
streamAssignLower(nodep);
AstNodeDType* const lhsDTypep = nodep->lhsp()->dtypep();
// IEEE 1800-2023 7.6: For unpacked arrays to be assignment compatible,
// the element types shall be equivalent (IEEE 1800-2023 6.22.2).
@ -10283,6 +10218,70 @@ public:
AstNode* mainAcceptEdit(AstNode* nodep) {
return userIterateSubtreeReturnEdits(nodep, WidthVP{SELF, BOTH}.p());
}
static bool lowerAsFixedAggregate(const AstNodeDType* const dtypep) {
return dtypep->isStreamableFixedAggregate() && dtypep->containsUnpackedStruct();
}
void streamAssignLower(AstNodeAssign* nodep) {
AstNodeDType* lhsDTypep = nodep->lhsp()->dtypep();
// 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 = lhsDTypeSkippedRefp->widthStream();
const int rwidth = streamp->lhsp()->dtypep()->skipRefp()->widthStream();
if (lwidth != 0 && lwidth < rwidth) {
nodep->v3widthWarn(lwidth, rwidth,
"Target fixed size variable ("
<< lwidth << " bits) is narrower than the stream ("
<< rwidth << " bits) (IEEE 1800-2023 11.4.14)");
}
if (VN_IS(streamp->lhsp()->dtypep()->skipRefp(), QueueDType)
&& !VN_IS(nodep->lhsp()->dtypep()->skipRefp(), QueueDType)) {
const int queueElementSize = streamp->lhsp()->dtypep()->subDTypep()->width();
UASSERT_OBJ(queueElementSize <= lwidth, nodep, "LHS < RHS");
}
if (VN_IS(lhsDTypeSkippedRefp, UnpackArrayDType)
|| lowerAsFixedAggregate(lhsDTypeSkippedRefp)) {
streamp->unlinkFrBack();
nodep->rhsp(
new AstCvtPackedToArray{streamp->fileline(), streamp, lhsDTypeSkippedRefp});
}
}
if (AstNodeStream* const streamp = VN_CAST(nodep->lhsp(), NodeStream)) {
const AstNodeDType* const rhsDTypep = nodep->rhsp()->dtypep()->skipRefp();
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 "
<< lwidth << " bits, but source expression only provides "
<< rwidth << " bits (IEEE 1800-2023 11.4.14.3)");
}
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);
} else if (VN_IS(rhsDTypep, UnpackArrayDType)) {
AstNodeExpr* const rhsp = nodep->rhsp()->unlinkFrBack();
nodep->rhsp(new AstCvtArrayToPacked{rhsp->fileline(), rhsp, streamp->dtypep()});
}
}
}
~WidthVisitor() override = default;
};
@ -10312,6 +10311,12 @@ AstNode* V3Width::widthParamsEdit(AstNode* nodep) {
return nodep;
}
void V3Width::streamAssignLowerEdit(AstNodeAssign* nodep) {
UINFO(4, __FUNCTION__ << ": " << nodep);
WidthVisitor visitor{false, false};
visitor.streamAssignLower(nodep);
}
//! Single node parameter propagation for generate blocks.
//! Smaller step... Only do a single node for parameter propagation
//! If we are inside a generated "if", "case" or "for", we don't want to

View File

@ -22,6 +22,7 @@
class AstNetlist;
class AstNode;
class AstNodeAssign;
class AstNodeDType;
//============================================================================
@ -32,6 +33,7 @@ public:
static AstNode* widthParamsEdit(AstNode* nodep) VL_MT_DISABLED;
static AstNode* widthGenerateParamsEdit(AstNode* nodep) VL_MT_DISABLED;
static AstNode* selectNonConstantRecurse(AstNode* nodep, bool inSel = false) VL_MT_DISABLED;
static void streamAssignLowerEdit(AstNodeAssign* nodep) VL_MT_DISABLED;
// For use only in WidthVisitor
// Replace AstSelBit, etc with AstSel/AstArraySel

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,116 @@
// 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 streaming concatenation used as the lvalue of a module output-port
// connection, targeting an unpacked struct. This form is lowered by
// V3Inst::pinReconnectSimple (after V3Width), which must apply the same
// fixed-aggregate stream lowering as a normal streaming assignment. Without
// that lowering the port assign const-folds to a zero-width, driverless
// assign and the target reads as a constant 0 (and generates invalid C++).
module t( /*AUTOARG*/
// Inputs
clk
);
input clk;
`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 [7:0] a;
logic [7:0] b;
} inner_t;
// Unpacked struct, $bits == 40
typedef struct {
inner_t x;
logic [15:0] c;
logic [7:0] d;
} order_t;
// Unpacked array, also $bits == 40 (so producer width OW is shared).
typedef byte arr_t[5];
localparam int OW = $bits(order_t);
integer cyc = 0;
reg [63:0] crc = 64'h5aef0c8d_d70a4497;
wire [OW-1:0] src = crc[OW-1:0];
// Path under test: streaming concat as an output-port lvalue, into an
// unpacked struct (needs CvtPackedToArray) and an unpacked array (does not).
order_t out_r; // right-stream {>>{}}
order_t out_l; // left-stream {<<8{}}
arr_t arr_r;
arr_t arr_l;
producer #(.W(OW)) u_r (.din(src), .dout({>>{out_r}}));
producer #(.W(OW)) u_l (.din(src), .dout({<<8{out_l}}));
producer #(.W(OW)) u_ar (.din(src), .dout({>>{arr_r}}));
producer #(.W(OW)) u_al (.din(src), .dout({<<8{arr_l}}));
// Reference: plain packed port + separate streaming unpack assign.
wire [OW-1:0] pack_r;
wire [OW-1:0] pack_l;
order_t ref_r, ref_l;
producer #(.W(OW)) u_pr (.din(src), .dout(pack_r));
producer #(.W(OW)) u_pl (.din(src), .dout(pack_l));
assign ref_r = {>>{pack_r}};
assign ref_l = {<<8{pack_l}};
// Packed views (streaming into a packed wire is the legal direction) so the
// checks compare plain vectors rather than unpacked structs/arrays.
wire [OW-1:0] out_r_bits = {>>{out_r}};
wire [OW-1:0] out_l_bits = {<<8{out_l}};
wire [OW-1:0] arr_r_bits = {>>{arr_r}};
wire [OW-1:0] arr_l_bits = {<<8{arr_l}};
wire [OW-1:0] ref_r_bits = {>>{ref_r}};
wire [OW-1:0] ref_l_bits = {<<8{ref_l}};
always @(posedge clk) begin
cyc <= cyc + 1;
crc <= {crc[62:0], crc[63] ^ crc[2] ^ crc[0]};
if (cyc > 1) begin
// The target has a real driver (not folded to 0) and round-trips.
`checkh(out_r_bits, src);
`checkh(out_l_bits, src);
`checkh(arr_r_bits, src);
`checkh(arr_l_bits, src);
// Output-port streaming lvalue matches the reference unpack.
`checkh(out_r_bits, ref_r_bits);
`checkh(out_l_bits, ref_l_bits);
if (out_r !== ref_r) begin
$write("%%Error: %s:%0d: out_r struct mismatch\n", `__FILE__, `__LINE__);
$stop;
end
if (out_l !== ref_l) begin
$write("%%Error: %s:%0d: out_l struct mismatch\n", `__FILE__, `__LINE__);
$stop;
end
end
if (cyc == 20) begin
$write("*-* All Finished *-*\n");
$finish;
end
end
endmodule
module producer #(
parameter int W = 1
) (
input logic [W-1:0] din,
output logic [W-1:0] dout
);
assign dout = din;
endmodule