From e0c4c995b9ce0abf099b3ad0b43a39a3d7d289e7 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Fri, 12 Jun 2026 12:22:18 +0100 Subject: [PATCH] Fix crash on overlapping priority case --- src/V3Case.cpp | 66 ++++++++++++----------- test_regress/t/t_case_priority_overlap.py | 18 +++++++ test_regress/t/t_case_priority_overlap.v | 56 +++++++++++++++++++ 3 files changed, 108 insertions(+), 32 deletions(-) create mode 100755 test_regress/t/t_case_priority_overlap.py create mode 100644 test_regress/t/t_case_priority_overlap.v diff --git a/src/V3Case.cpp b/src/V3Case.cpp index 93612026f..9fc489c4d 100644 --- a/src/V3Case.cpp +++ b/src/V3Case.cpp @@ -290,9 +290,9 @@ class CaseVisitor final : public VNVisitor { const uint32_t mask = nummask.toUInt(); const uint32_t val = numval.toUInt(); - uint32_t firstOverlap = 0; - const AstConst* overlappedCondp = nullptr; - bool foundHit = false; + bool foundNewCase = false; + const AstConst* firstOverlapConstp = nullptr; + uint32_t firstOverlapValue = 0; for (uint32_t i = 0; i < numCases; ++i) { if ((i & mask) != val) continue; @@ -303,51 +303,53 @@ class CaseVisitor final : public VNVisitor { caseRecord.itemp = itemp; caseRecord.constp = iconstp; caseRecord.stmtsp = itemp->stmtsp(); - foundHit = true; + foundNewCase = true; continue; } - // Otherwise record the first overlapping case, - // but overlap within the same CaseItem is legal - if (!overlappedCondp && caseRecord.itemp != itemp) { - firstOverlap = i; - overlappedCondp = caseRecord.constp; + // There is an overlap. If it's within the same CaseItem, it is legal + if (caseRecord.itemp == itemp) continue; + + // Otherwise record the first overlapping case + if (!firstOverlapConstp) { + firstOverlapConstp = caseRecord.constp; + firstOverlapValue = i; m_caseNoOverlaps = false; } } - if (!nodep->priorityPragma()) { - // If this case statement doesn't have the priority - // keyword, we want to warn on any overlap. - if (!reportedOverlap && overlappedCondp) { + if (nodep->priorityPragma()) { + // If this is a priority case, we only want to complain if every possible value + // for this item is already hit by some other item. This is true if + // 'foundNewCase' is false. 'firstOverlapConstp' is null when the only covering + // item is this item itself, which is legal overlap within one item. + if (!reportedSubcase && !foundNewCase && firstOverlapConstp) { + iconstp->v3warn(CASEOVERLAP, + "Case item ignored: every matching value is covered " + "by an earlier condition\n" + << iconstp->warnContextPrimary() << '\n' + << firstOverlapConstp->warnOther() + << "... Location of previous condition\n" + << firstOverlapConstp->warnContextPrimary()); + reportedSubcase = true; + } + } else { + // If this case statement doesn't have the priority keyword, + // we want to warn on any overlap. + if (!reportedOverlap && firstOverlapConstp) { std::ostringstream examplePattern; if (iconstp->num().isAnyXZ()) { - examplePattern << " (example pattern 0x" << std::hex << firstOverlap - << ")"; + examplePattern << " (example pattern 0x" << std::hex + << firstOverlapValue << ")"; } iconstp->v3warn(CASEOVERLAP, "Case conditions overlap" << examplePattern.str() << "\n" << iconstp->warnContextPrimary() << '\n' - << overlappedCondp->warnOther() + << firstOverlapConstp->warnOther() << "... Location of overlapping condition\n" - << overlappedCondp->warnContextSecondary()); + << firstOverlapConstp->warnContextSecondary()); reportedOverlap = true; } - } else { - // If this is a priority case, we only want to complain - // if every possible value for this item is already hit - // by some other item. This is true if foundHit is - // false. - if (!reportedSubcase && !foundHit) { - iconstp->v3warn(CASEOVERLAP, - "Case item ignored: every matching value is covered " - "by an earlier condition\n" - << iconstp->warnContextPrimary() << '\n' - << overlappedCondp->warnOther() - << "... Location of previous condition\n" - << overlappedCondp->warnContextPrimary()); - reportedSubcase = true; - } } } } diff --git a/test_regress/t/t_case_priority_overlap.py b/test_regress/t/t_case_priority_overlap.py new file mode 100755 index 000000000..46d1fe4c0 --- /dev/null +++ b/test_regress/t/t_case_priority_overlap.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(verilator_flags2=['--binary']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_case_priority_overlap.v b/test_regress/t/t_case_priority_overlap.v new file mode 100644 index 000000000..2de27080a --- /dev/null +++ b/test_regress/t/t_case_priority_overlap.v @@ -0,0 +1,56 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// Regression: a `priority` case item whose later condition (INST_B) is fully +// subsumed by an earlier condition (INST_A) of the *same* item. Overlap within +// a single case item is legal, but this previously crashed V3Case with a null +// pointer dereference: the priority "case item ignored" CASEOVERLAP diagnostic +// dereferenced 'overlappedCondp', which is null when the only covering item is +// the item itself. Must compile cleanly and simulate correctly. + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module t; + + logic clk = 1'b0; + always #5 clk = ~clk; + + logic [1:0] in; + logic [1:0] out; + + always_comb begin + priority casez (in) + 2'b1?, // fully subsumes 2'b11 below on the same case clause + 2'b11: out = 2'b10; + 2'b0?: out = 2'b01; + endcase + end + + initial begin + in = 2'b00; + @(posedge clk); + `checkh(out, 2'b01); + + in = 2'b01; + @(posedge clk); + `checkh(out, 2'b01); + + in = 2'b10; + @(posedge clk); + `checkh(out, 2'b10); + + in = 2'b11; + @(posedge clk); + `checkh(out, 2'b10); + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule