8000 Fix crash when comparing queue to scalar (#5570) by hankhsu1996 · Pull Request #5950 · verilator/verilator · GitHub
[go: up one dir, main page]

Skip to content

Fix crash when comparing queue to scalar (#5570) #5950

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
May 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/CONTRIBUTORS
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ Sean Cross
Sebastien Van Cauwenberghe
Sergi Granell
Seth Pellegrino
Shou-Li Hsu
Srinivasan Venkataramanan
Stefan Wallentowitz
Stephen Henry
Expand Down
5 changes: 5 additions & 0 deletions src/V3AstNodeDType.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ class AstNodeDType VL_NOT_FINAL : public AstNode {
// 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(); }
Expand Down Expand Up @@ -366,6 +367,7 @@ class AstAssocArrayDType final : public AstNodeDType {
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); }
Expand Down Expand Up @@ -742,6 +744,7 @@ class AstDynArrayDType final : public AstNodeDType {
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); }
Expand Down Expand Up @@ -1102,6 +1105,7 @@ class AstQueueDType final : public AstNodeDType {
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; }
Expand Down Expand Up @@ -1398,6 +1402,7 @@ class AstUnpackArrayDType final : public AstNodeArrayDType {
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; }
Expand Down
95 changes: 42 additions & 53 deletions src/V3Slice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -310,69 +310,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); }
Expand Down
108 changes: 107 additions & 1 deletion src/V3Width.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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{}};
Expand Down
7 changes: 5 additions & 2 deletions test_regress/t/t_fuzz_eqne_bad.out
Original file line number Diff line number Diff line change
@@ -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
18 changes: 18 additions & 0 deletions test_regress/t/t_lint_dtype_compare.py
Original file line number Diff line number Diff line change
@@ -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()
78 changes: 78 additions & 0 deletions test_regress/t/t_lint_dtype_compare.v
Original file line number Diff line number Diff line change
@@ -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
Loading
0