diff --git a/src/V3Width.cpp b/src/V3Width.cpp index ef032d984..d88293db6 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -2875,6 +2875,7 @@ class WidthVisitor final : public VNVisitor { // Assign missing values V3Number num(nodep, nodep->width(), 0); const V3Number one{nodep, nodep->width(), 1}; + bool wrapAround = false; std::map inits; for (AstEnumItem* itemp = nodep->itemsp(); itemp; itemp = VN_AS(itemp->nextp(), EnumItem)) { @@ -2890,9 +2891,10 @@ class WidthVisitor final : public VNVisitor { continue; } // TODO IEEE says assigning sized number that is not same size as enum is illegal + wrapAround = false; // Explicit value resets wrap-around tracking } if (!itemp->valuep()) { - if (num.isEqZero() && itemp != nodep->itemsp()) { + if (wrapAround) { itemp->v3error("Enum value illegally wrapped around (IEEE 1800-2023 6.19)"); } if (num.isFourState()) { @@ -2918,7 +2920,16 @@ class WidthVisitor final : public VNVisitor { << otherp->warnOther() << "... Location of original declaration\n" << otherp->warnContextSecondary()); } + // Detect wrap-around after increment for the next auto-valued item. + // For signed types, overflow is positive-to-negative (e.g., INT_MAX+1). + // For unsigned types, overflow wraps to zero (e.g., UINT_MAX+1). + const bool prevNeg = constp->num().isNegative(); num.opAdd(one, constp->num()); + if (basicp->isSigned()) { + wrapAround = !prevNeg && num.isNegative(); + } else { + wrapAround = num.isEqZero(); + } } } void visit(AstEnumItem* nodep) override { diff --git a/test_regress/t/t_enum_signed_wrap.py b/test_regress/t/t_enum_signed_wrap.py new file mode 100755 index 000000000..8a938befd --- /dev/null +++ b/test_regress/t/t_enum_signed_wrap.py @@ -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() diff --git a/test_regress/t/t_enum_signed_wrap.v b/test_regress/t/t_enum_signed_wrap.v new file mode 100644 index 000000000..0e2d74c1c --- /dev/null +++ b/test_regress/t/t_enum_signed_wrap.v @@ -0,0 +1,93 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// Test that signed enum types auto-incrementing through zero are accepted. +// Previously Verilator falsely rejected these with "illegally wrapped around". + +module t; + + // Case 1: signed enum crossing zero (the original bug) + typedef enum int { + CROSS_NEG2 = -2, + CROSS_NEG1, // -1 + CROSS_ZERO, // 0 + CROSS_POS1 // 1 + } cross_zero_e; + + // Case 2: signed enum starting at zero + typedef enum int { + START_ZERO = 0, + START_ONE, // 1 + START_TWO // 2 + } start_zero_e; + + // Case 3: signed enum all negative + typedef enum int { + ALL_NEG3 = -3, + ALL_NEG2, // -2 + ALL_NEG1 // -1 + } all_neg_e; + + // Case 4: signed enum starting at large negative, crossing zero + typedef enum logic signed [7:0] { + WIDE_NEG3 = -8'sd3, + WIDE_NEG2, // -2 + WIDE_NEG1, // -1 + WIDE_ZERO, // 0 + WIDE_POS1, // 1 + WIDE_POS2 // 2 + } wide_cross_e; + + // Case 5: signed enum single value at zero + typedef enum int { + SINGLE_ZERO = 0 + } single_zero_e; + + // Case 6: signed enum starting at -1, crossing zero + typedef enum int { + FROM_NEG1 = -1, + FROM_ZERO, // 0 + FROM_POS1 // 1 + } from_neg1_e; + + initial begin + // Case 1: crossing zero + if (CROSS_NEG2 !== -2) $stop; + if (CROSS_NEG1 !== -1) $stop; + if (CROSS_ZERO !== 0) $stop; + if (CROSS_POS1 !== 1) $stop; + + // Case 2: starting at zero + if (START_ZERO !== 0) $stop; + if (START_ONE !== 1) $stop; + if (START_TWO !== 2) $stop; + + // Case 3: all negative + if (ALL_NEG3 !== -3) $stop; + if (ALL_NEG2 !== -2) $stop; + if (ALL_NEG1 !== -1) $stop; + + // Case 4: wider signed type crossing zero + if (WIDE_NEG3 !== -8'sd3) $stop; + if (WIDE_NEG2 !== -8'sd2) $stop; + if (WIDE_NEG1 !== -8'sd1) $stop; + if (WIDE_ZERO !== 8'sd0) $stop; + if (WIDE_POS1 !== 8'sd1) $stop; + if (WIDE_POS2 !== 8'sd2) $stop; + + // Case 5: single zero + if (SINGLE_ZERO !== 0) $stop; + + // Case 6: from -1 crossing zero + if (FROM_NEG1 !== -1) $stop; + if (FROM_ZERO !== 0) $stop; + if (FROM_POS1 !== 1) $stop; + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule