Add aggregate type error checks (#5570) (#5950)

This commit is contained in:
Shou-Li Hsu 2025-05-20 06:10:22 -07:00 committed by GitHub
parent bed0456eca
commit 25cb31c38b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 440 additions and 60 deletions

View File

@ -205,6 +205,7 @@ Sean Cross
Sebastien Van Cauwenberghe
Sergi Granell
Seth Pellegrino
Shou-Li Hsu
Srinivasan Venkataramanan
Stefan Wallentowitz
Stephen Henry

View File

@ -124,6 +124,7 @@ public:
// Iff has a non-null subDTypep(), as generic node function
virtual AstNodeDType* subDTypep() const VL_MT_STABLE { return nullptr; }
virtual AstNodeDType* subDType2p() const VL_MT_STABLE { return nullptr; }
virtual bool isAggregateType() const { return false; }
virtual bool isFourstate() const;
// Ideally an IEEE $typename
virtual string prettyDTypeName(bool) const { return prettyTypeName(); }
@ -366,6 +367,7 @@ public:
AstNodeDType* subDType2p() const override VL_MT_STABLE {
return m_keyDTypep ? m_keyDTypep : keyChildDTypep();
}
bool isAggregateType() const override { return true; }
void refDTypep(AstNodeDType* nodep) { m_refDTypep = nodep; }
AstNodeDType* virtRefDTypep() const override { return m_refDTypep; }
void virtRefDTypep(AstNodeDType* nodep) override { refDTypep(nodep); }
@ -742,6 +744,7 @@ public:
AstNodeDType* subDTypep() const override VL_MT_STABLE {
return m_refDTypep ? m_refDTypep : childDTypep();
}
bool isAggregateType() const override { return true; }
void refDTypep(AstNodeDType* nodep) { m_refDTypep = nodep; }
AstNodeDType* virtRefDTypep() const override { return m_refDTypep; }
void virtRefDTypep(AstNodeDType* nodep) override { refDTypep(nodep); }
@ -1108,6 +1111,7 @@ public:
AstNodeDType* subDTypep() const override VL_MT_STABLE {
return m_refDTypep ? m_refDTypep : childDTypep();
}
bool isAggregateType() const override { return true; }
void refDTypep(AstNodeDType* nodep) { m_refDTypep = nodep; }
inline int boundConst() const VL_MT_STABLE;
AstNodeDType* virtRefDTypep() const override { return m_refDTypep; }
@ -1404,6 +1408,7 @@ public:
const AstUnpackArrayDType* const sp = VN_DBG_AS(samep, UnpackArrayDType);
return m_isCompound == sp->m_isCompound;
}
bool isAggregateType() const override { return true; }
// Outer dimension comes first. The first element is this node.
std::vector<AstUnpackArrayDType*> unpackDimensions();
void isCompound(bool flag) { m_isCompound = flag; }

View File

@ -319,69 +319,58 @@ class SliceVisitor final : public VNVisitor {
void expandBiOp(AstNodeBiop* nodep) {
if (nodep->user1SetOnce()) return; // Process once
// If it's an unpacked array, blow it up into comparing each element
AstNodeDType* const fromDtp = nodep->lhsp()->dtypep()->skipRefp();
UINFO(9, " Bi-Eq/Neq expansion " << nodep << endl);
// Only expand if lhs is an unpacked array (we assume type checks already passed)
const AstNodeDType* const fromDtp = nodep->lhsp()->dtypep()->skipRefp();
if (const AstUnpackArrayDType* const adtypep = VN_CAST(fromDtp, UnpackArrayDType)) {
AstNodeBiop* logp = nullptr;
if (!VN_IS(nodep->lhsp()->dtypep()->skipRefp(), NodeArrayDType)) {
nodep->lhsp()->v3error(
"Slice operator "
<< nodep->lhsp()->prettyTypeName()
<< " on non-slicable (e.g. non-vector) left-hand-side operand");
} else if (!VN_IS(nodep->rhsp()->dtypep()->skipRefp(), NodeArrayDType)) {
nodep->rhsp()->v3error(
"Slice operator "
<< nodep->rhsp()->prettyTypeName()
<< " on non-slicable (e.g. non-vector) right-hand-side operand");
} else {
const int elements = adtypep->rangep()->elementsConst();
for (int elemIdx = 0; elemIdx < elements; ++elemIdx) {
// EQ(a,b) -> LOGAND(EQ(ARRAYSEL(a,0), ARRAYSEL(b,0)), ...[1])
// Original node is replaced, so it is safe to copy it one time even if it is
// impure.
AstNodeBiop* const clonep
= VN_AS(nodep->cloneType(
cloneAndSel(nodep->lhsp(), elements, elemIdx, elemIdx != 0),
cloneAndSel(nodep->rhsp(), elements, elemIdx, elemIdx != 0)),
NodeBiop);
if (elemIdx == 0) {
nodep->foreach([this](AstExprStmt* const exprp) {
// Result expression is always evaluated to the same value, so the
// statements can be removed once they were included in the expression
// created for the 1st element.
AstNodeExpr* const resultp = exprp->resultp()->unlinkFrBack();
exprp->replaceWith(resultp);
VL_DO_DANGLING(pushDeletep(exprp), exprp);
});
}
if (!logp) {
logp = clonep;
} else {
switch (nodep->type()) {
case VNType::atEq: // FALLTHRU
case VNType::atEqCase:
logp = new AstLogAnd{nodep->fileline(), logp, clonep};
break;
case VNType::atNeq: // FALLTHRU
case VNType::atNeqCase:
logp = new AstLogOr{nodep->fileline(), logp, clonep};
break;
default:
nodep->v3fatalSrc("Unknown node type processing array slice");
break;
}
const int elements = adtypep->rangep()->elementsConst();
for (int elemIdx = 0; elemIdx < elements; ++elemIdx) {
// EQ(a,b) -> LOGAND(EQ(ARRAYSEL(a,0), ARRAYSEL(b,0)), ...[1])
// Original node is replaced, so it is safe to copy it one time even if it is
// impure.
AstNodeBiop* const clonep = VN_AS(
nodep->cloneType(cloneAndSel(nodep->lhsp(), elements, elemIdx, elemIdx != 0),
cloneAndSel(nodep->rhsp(), elements, elemIdx, elemIdx != 0)),
NodeBiop);
if (elemIdx == 0) {
nodep->foreach([this](AstExprStmt* const exprp) {
// Result expression is always evaluated to the same value, so the
// statements can be removed once they were included in the expression
// created for the 1st element.
AstNodeExpr* const resultp = exprp->resultp()->unlinkFrBack();
exprp->replaceWith(resultp);
VL_DO_DANGLING(pushDeletep(exprp), exprp);
});
}
if (!logp) {
logp = clonep;
} else {
switch (nodep->type()) {
case VNType::atEq: // FALLTHRU
case VNType::atEqCase:
logp = new AstLogAnd{nodep->fileline(), logp, clonep};
break;
case VNType::atNeq: // FALLTHRU
case VNType::atNeqCase:
logp = new AstLogOr{nodep->fileline(), logp, clonep};
break;
default: nodep->v3fatalSrc("Unknown node type processing array slice"); break;
}
}
UASSERT_OBJ(logp, nodep, "Unpacked array with empty indices range");
nodep->replaceWith(logp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
nodep = logp;
}
UASSERT_OBJ(logp, nodep, "Unpacked array with empty indices range");
nodep->replaceWith(logp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
nodep = logp;
}
iterateChildren(nodep);
}
void visit(AstEq* nodep) override { expandBiOp(nodep); }
void visit(AstNeq* nodep) override { expandBiOp(nodep); }
void visit(AstEqCase* nodep) override { expandBiOp(nodep); }

View File

@ -6551,6 +6551,93 @@ class WidthVisitor final : public VNVisitor {
}
}
// LRM 6.22.2 Equivalent types
bool isEquivalentDType(const AstNodeDType* lhs, const AstNodeDType* rhs) {
// a) If two types match, they are equivalent.
if (!lhs || !rhs) return false;
lhs = lhs->skipRefp();
rhs = rhs->skipRefp();
if (lhs == rhs) return true;
// If both are basic types, check if they are the same type
if (VN_IS(lhs, BasicDType) && VN_IS(rhs, BasicDType)) {
const auto* lb = VN_CAST(lhs, BasicDType);
const auto* rb = VN_CAST(rhs, BasicDType);
if (lb->isString() != rb->isString()) return false;
}
// d) Unpacked fixed-size array types are equivalent if they have equivalent element types
// and equal size; the actual range bounds may differ. Note that the element type of a
// multidimensional array is itself an array type.
const bool lhsIsUnpackArray = VN_IS(lhs, UnpackArrayDType);
const bool rhsIsUnpackArray = VN_IS(rhs, UnpackArrayDType);
if (lhsIsUnpackArray || rhsIsUnpackArray) {
if (VN_IS(lhs, UnpackArrayDType) && VN_IS(rhs, UnpackArrayDType)) {
const AstUnpackArrayDType* const lhsp = VN_CAST(lhs, UnpackArrayDType);
const AstUnpackArrayDType* const rhsp = VN_CAST(rhs, UnpackArrayDType);
const int lsz = lhsp->elementsConst();
const int rsz = rhsp->elementsConst();
if (lsz >= 0 && rsz >= 0 && lsz != rsz) return false;
return isEquivalentDType(lhsp->subDTypep(), rhsp->subDTypep());
}
return false;
}
// e) Dynamic array, associative array, and queue types are equivalent if they are the same
// kind of array (dynamic, associative, or queue), have equivalent index types (for
// associative arrays), and have equivalent element types.
const bool lhsIsDynArray = VN_IS(lhs, DynArrayDType);
const bool rhsIsDynArray = VN_IS(rhs, DynArrayDType);
const bool lhsIsQueue = VN_IS(lhs, QueueDType);
const bool rhsIsQueue = VN_IS(rhs, QueueDType);
const bool lhsIsAssocArray = VN_IS(lhs, AssocArrayDType);
const bool rhsIsAssocArray = VN_IS(rhs, AssocArrayDType);
if (lhsIsDynArray || rhsIsDynArray || lhsIsQueue || rhsIsQueue || lhsIsAssocArray
|| rhsIsAssocArray) {
if (const AstDynArrayDType* const lhsp = VN_CAST(lhs, DynArrayDType)) {
if (const AstDynArrayDType* const rhsp = VN_CAST(rhs, DynArrayDType)) {
return isEquivalentDType(lhsp->subDTypep(), rhsp->subDTypep());
}
}
if (const AstQueueDType* const lhsp = VN_CAST(lhs, QueueDType)) {
if (const AstQueueDType* const rhsp = VN_CAST(rhs, QueueDType)) {
return isEquivalentDType(lhsp->subDTypep(), rhsp->subDTypep());
}
}
if (const AstAssocArrayDType* const lhsp = VN_CAST(lhs, AssocArrayDType)) {
if (const AstAssocArrayDType* const rhsp = VN_CAST(rhs, AssocArrayDType)) {
return isEquivalentDType(lhsp->subDTypep(), rhsp->subDTypep())
&& isEquivalentDType(lhsp->keyDTypep(), rhsp->keyDTypep());
}
}
return false;
}
// c) Packed arrays, packed structures, packed unions, and built-in integral
// types are equivalent if they contain the same number of total bits, are either all
// 2-state or all 4-state, and are either all signed or all unsigned.
if (lhs->isIntegralOrPacked() && rhs->isIntegralOrPacked()) {
if (lhs->width() != rhs->width()) return false;
if (lhs->isFourstate() != rhs->isFourstate()) return false;
if (lhs->isSigned() != rhs->isSigned()) return false;
return true;
}
return true;
}
static bool isAggregateType(const AstNode* nodep) {
if (!nodep) return false;
const AstNodeDType* dtypep = nodep->dtypep();
if (!dtypep) return false;
dtypep = dtypep->skipRefp();
if (!dtypep) return false;
return dtypep->isAggregateType();
}
void visit_cmp_eq_gt(AstNodeBiop* nodep, bool realok) {
// CALLER: AstEq, AstGt, ..., AstLtS
// Real allowed if and only if real_lhs set
@ -6567,7 +6654,26 @@ class WidthVisitor final : public VNVisitor {
if (m_vup->prelim()) {
userIterateAndNext(nodep->lhsp(), WidthVP{CONTEXT_DET, PRELIM}.p());
userIterateAndNext(nodep->rhsp(), WidthVP{CONTEXT_DET, PRELIM}.p());
if (nodep->lhsp()->isDouble() || nodep->rhsp()->isDouble()) {
const bool isAggrLhs = isAggregateType(nodep->lhsp());
const bool isAggrRhs = isAggregateType(nodep->rhsp());
if ((isAggrLhs || isAggrRhs) && nodep->lhsp() && nodep->rhsp()) {
const AstNodeDType* const lhsDType = nodep->lhsp()->dtypep();
const AstNodeDType* const rhsDType = nodep->rhsp()->dtypep();
if (lhsDType && rhsDType && !isEquivalentDType(lhsDType, rhsDType)) {
nodep->v3error("Comparison requires matching data types\n"
<< nodep->warnMore() << "... Left-hand data type: "
<< lhsDType->prettyDTypeNameQ() << "\n"
<< nodep->warnMore() << "... Right-hand data type: "
<< rhsDType->prettyDTypeNameQ());
AstNode* const newp = new AstConst{nodep->fileline(), AstConst::BitFalse{}};
nodep->replaceWith(newp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
return;
}
} else if (nodep->lhsp()->isDouble() || nodep->rhsp()->isDouble()) {
if (!realok) {
nodep->v3error("Real is illegal operand to ?== operator");
AstNode* const newp = new AstConst{nodep->fileline(), AstConst::BitFalse{}};

View File

@ -1,5 +1,8 @@
%Error: t/t_fuzz_eqne_bad.v:12:23: Slice operator VARREF 't.b' on non-slicable (e.g. non-vector) right-hand-side operand
%Error: t/t_fuzz_eqne_bad.v:12:19: Comparison requires matching data types
: ... note: In instance 't'
: ... Left-hand data type: 'logic$[0:-1]'
: ... Right-hand data type: 'logic'
12 | initial c = (a != &b);
| ^
| ^~
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: Exiting due to

View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2024 by Wilson Snyder. 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-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,78 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by Wilson Snyder.
// SPDX-License-Identifier: CC0-1.0
module t;
// Typedefs
typedef int myint_t;
typedef int myint2_t;
typedef int myq_t[$];
typedef int myval_t;
typedef string mykey_t;
initial begin
// Scalar
int a = 1, b = 1;
// Unpacked array
int u1[2] = '{1, 2};
int u2[2] = '{1, 2};
int m1[2][2] = '{{1, 2}, {3, 4}};
int m2[2][2] = '{{1, 2}, {3, 4}};
// Dynamic array
int d1[] = new[2];
int d2[] = new[2];
// Queue
int q1[$] = '{10, 20};
int q2[$] = '{10, 20};
// Associative array
int aa1[string];
int aa2[string];
// Typedef array
myint_t t1[2] = '{1, 2};
myint2_t t2[2] = '{1, 2};
// Typedef queue
myq_t tq1 = '{1, 2};
int tq2[$] = '{1, 2};
// Typedef associative array
myval_t aa_typedef1[mykey_t];
int aa_typedef2[string];
// Typedef scalar
bit signed [31:0] b1 = 1;
int i1 = 1;
d1[0] = 5; d1[1] = 6;
d2[0] = 5; d2[1] = 6;
aa1["a"] = 1; aa2["a"] = 1;
aa1["b"] = 2; aa2["b"] = 2;
aa_typedef1["foo"] = 123;
aa_typedef2["foo"] = 123;
if (a != b) $fatal(0, "Scalar comparison failed");
if (u1 != u2) $fatal(0, "Unpacked 1D array comparison failed");
if (m1 != m2) $fatal(0, "Unpacked multi-dimensional array comparison failed");
if (d1 != d2) $fatal(0, "Dynamic array comparison failed");
if (q1 != q2) $fatal(0, "Queue comparison failed");
if (aa1 != aa2) $fatal(0, "Associative array comparison failed");
if (t1 != t2) $fatal(0, "Typedef unpacked array comparison failed");
if (tq1 != tq2) $fatal(0, "Typedef queue comparison failed");
if (aa_typedef1 != aa_typedef2)
$fatal(0, "Typedef associative array comparison failed");
if (b1 != i1) $fatal(0, "bit[31:0] vs int comparison failed");
$display("*-* All Finished *-*");
$finish;
end
endmodule

View File

@ -0,0 +1,68 @@
%Error: t/t_lint_dtype_compare_bad.v:52:19: Comparison requires matching data types
: ... note: In instance 't'
: ... Left-hand data type: 'int$[$]'
: ... Right-hand data type: 'logic[31:0]'
52 | if (queue_var == 1) begin end
| ^~
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: t/t_lint_dtype_compare_bad.v:55:11: Comparison requires matching data types
: ... note: In instance 't'
: ... Left-hand data type: 'logic[31:0]'
: ... Right-hand data type: 'int$[$]'
55 | if (1 == queue_var) begin end
| ^~
%Error: t/t_lint_dtype_compare_bad.v:58:12: Comparison requires matching data types
: ... note: In instance 't'
: ... Left-hand data type: 'int$[$]'
: ... Right-hand data type: 'bit$[$]'
58 | if (q1 == q2) begin end
| ^~
%Error: t/t_lint_dtype_compare_bad.v:61:12: Comparison requires matching data types
: ... note: In instance 't'
: ... Left-hand data type: 'int$[]'
: ... Right-hand data type: 'bit$[]'
61 | if (d1 == d2) begin end
| ^~
%Error: t/t_lint_dtype_compare_bad.v:64:12: Comparison requires matching data types
: ... note: In instance 't'
: ... Left-hand data type: 'int$[0:1]'
: ... Right-hand data type: 'int$[0:1][0:0]'
64 | if (u1 == u2) begin end
| ^~
%Error: t/t_lint_dtype_compare_bad.v:67:12: Comparison requires matching data types
: ... note: In instance 't'
: ... Left-hand data type: 'int$[0:1]'
: ... Right-hand data type: 'int$[0:2]'
67 | if (a1 == a2) begin end
| ^~
%Error: t/t_lint_dtype_compare_bad.v:70:13: Comparison requires matching data types
: ... note: In instance 't'
: ... Left-hand data type: 'int$[string]'
: ... Right-hand data type: 'int$[int]'
70 | if (aa1 == aa2) begin end
| ^~
%Error: t/t_lint_dtype_compare_bad.v:73:13: Comparison requires matching data types
: ... note: In instance 't'
: ... Left-hand data type: 'int$[string]'
: ... Right-hand data type: 'logic[3:0]$[string]'
73 | if (aa3 == aa4) begin end
| ^~
%Error: t/t_lint_dtype_compare_bad.v:76:14: Comparison requires matching data types
: ... note: In instance 't'
: ... Left-hand data type: 'int$[0:1]'
: ... Right-hand data type: 'bit$[0:1]'
76 | if (bad1 == bad2) begin end
| ^~
%Error: t/t_lint_dtype_compare_bad.v:79:14: Comparison requires matching data types
: ... note: In instance 't'
: ... Left-hand data type: 'int$[string]'
: ... Right-hand data type: 'logic[31:0]$[string]'
79 | if (val1 == val2) begin end
| ^~
%Error: t/t_lint_dtype_compare_bad.v:82:13: Comparison requires matching data types
: ... note: In instance 't'
: ... Left-hand data type: 'int$[string]'
: ... Right-hand data type: 'int$[int]'
82 | if (aa5 == aa6) begin end
| ^~
%Error: Exiting due to

View File

@ -0,0 +1,16 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2025 by Wilson Snyder. 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-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,84 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by Wilson Snyder.
// SPDX-License-Identifier: CC0-1.0
// DESCRIPTION: Verilator: Invalid aggregate dtype comparisons
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by Shou-Li Hsu.
// SPDX-License-Identifier: CC0-1.0
module t;
typedef int myint_t;
typedef bit mybit_t;
typedef string mystr_t;
typedef int myval_t;
typedef logic [31:0] mylogic_t;
initial begin
int queue_var[$] = '{1, 2, 3};
int q1[$] = '{1, 2};
bit q2[$] = '{1'b1, 1'b0};
int d1[] = new[2];
bit d2[] = new[2];
int u1[2] = '{1, 2};
int u2[2][1] = '{{1}, {2}};
int a1[2] = '{1, 2};
int a2[3] = '{1, 2, 3};
int aa1[string];
int aa2[int];
int aa3[string];
logic [3:0] aa4[string];
myint_t bad1[2] = '{1, 2};
mybit_t bad2[2] = '{1, 0};
myval_t val1[mystr_t] = '{"foo": 123};
mylogic_t val2[string] = '{"foo": 32'h12345678};
myint_t aa5[string];
myint_t aa6[int];
aa5["a"] = 1;
aa6[1] = 1;
// queue vs scalar
if (queue_var == 1) begin end
// scalar vs queue
if (1 == queue_var) begin end
// queue with diff type
if (q1 == q2) begin end
// dyn array with diff type
if (d1 == d2) begin end
// unpacked diff dim
if (u1 == u2) begin end
// unpacked diff size
if (a1 == a2) begin end
// assoc array diff key type
if (aa1 == aa2) begin end
// assoc array diff value type
if (aa3 == aa4) begin end
// typedef mismatch in unpacked array
if (bad1 == bad2) begin end
// typedef mismatch in assoc array value
if (val1 == val2) begin end
// typedef mismatch in assoc array key
if (aa5 == aa6) begin end
end
endmodule

View File

@ -18,7 +18,7 @@ module t;
initial begin
begin // integers
int q1[$];
bit[31:0] q2[$];
bit signed [31:0] q2[$];
q1.push_back(1);
q2.push_back(1);
q1.push_back(-2);

View File

@ -3,8 +3,20 @@
113 | if (unpacked_siz_dout != '{8'h01, 8'h23, 8'h45, 8'h67}) $stop;
| ^~
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error: Internal Error: t/t_stream_unpack_lhs.v:113:35: ../V3Width.cpp:#: Node has no type
%Error-UNSUPPORTED: t/t_stream_unpack_lhs.v:114:38: Unsupported/Illegal: Assignment pattern member not underneath a supported construct: NEQ
: ... note: In instance 't'
114 | if (unpacked_asc_dout != '{8'h01, 8'h23, 8'h45, 8'h67}) $stop;
| ^~
%Error-UNSUPPORTED: t/t_stream_unpack_lhs.v:115:38: Unsupported/Illegal: Assignment pattern member not underneath a supported construct: NEQ
: ... note: In instance 't'
115 | if (unpacked_des_dout != '{8'h76, 8'h54, 8'h32, 8'h10}) $stop;
| ^~
%Error-UNSUPPORTED: t/t_stream_unpack_lhs.v:117:36: Unsupported/Illegal: Assignment pattern member not underneath a supported construct: NEQ
: ... note: In instance 't'
117 | if (packed_siz_dout != '{8'h01, 8'h23, 8'h45, 8'h67}) $stop;
| ^~
%Error: Internal Error: t/t_stream_unpack_lhs.v:117:33: ../V3Width.cpp:#: Node has no type
: ... note: In instance 't'
113 | if (unpacked_siz_dout != '{8'h01, 8'h23, 8'h45, 8'h67}) $stop;
| ^~
117 | if (packed_siz_dout != '{8'h01, 8'h23, 8'h45, 8'h67}) $stop;
| ^~
... This fatal error may be caused by the earlier error(s); resolve those first.