From 368f59d5f26d6349b95e4db9ba8ca9fdac0c387c Mon Sep 17 00:00:00 2001 From: Nick Brereton <85175726+nbstrike@users.noreply.github.com> Date: Wed, 30 Sep 2026 20:17:00 -0400 Subject: [PATCH] Optimize exhaustive case if-chains with an unconditional else (#8554) --- src/V3Case.cpp | 17 ++- test_regress/t/t_case_exhaustive_nocase.py | 21 +++ test_regress/t/t_case_exhaustive_nocase.v | 143 +++++++++++++++++++++ 3 files changed, 178 insertions(+), 3 deletions(-) create mode 100755 test_regress/t/t_case_exhaustive_nocase.py create mode 100644 test_regress/t/t_case_exhaustive_nocase.v diff --git a/src/V3Case.cpp b/src/V3Case.cpp index ef24d8a56..26a40cd58 100644 --- a/src/V3Case.cpp +++ b/src/V3Case.cpp @@ -1001,11 +1001,22 @@ class CaseVisitor final : public VNVisitor { itemp->addCondsp(newCondp); } - // If there was no default, add a empty one, this greatly simplifies below code + // If there was no default, make one up, this greatly simplifies below code // and constant propagation will just eliminate it for us later. if (!hasDefault) { - nodep->addItemsp(new AstCaseItem{ - nodep->fileline(), new AstConst{nodep->fileline(), AstConst::BitTrue{}}, nullptr}); + if (m_caseDetailsValid && m_caseDetails.exhaustive + && !m_caseDetails.exhaustiveOverEnumOnly) { + // Case is proven exhaustive, so the last is reached only when its test holds, + // make it unconditional + AstCaseItem* const lastp = VN_AS(nodep->itemsp()->lastp(), CaseItem); + pushDeletep(lastp->condsp()->unlinkFrBackWithNext()); + lastp->addCondsp(new AstConst{lastp->fileline(), AstConst::BitTrue{}}); + } else { + // Otherwise needs an explicit empty default + nodep->addItemsp(new AstCaseItem{ + nodep->fileline(), new AstConst{nodep->fileline(), AstConst::BitTrue{}}, + nullptr}); + } } // Now build the IF statement tree diff --git a/test_regress/t/t_case_exhaustive_nocase.py b/test_regress/t/t_case_exhaustive_nocase.py new file mode 100755 index 000000000..2a3286a5d --- /dev/null +++ b/test_regress/t/t_case_exhaustive_nocase.py @@ -0,0 +1,21 @@ +#!/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=["--stats -fno-case"]) + +test.execute() + +test.file_grep(test.stats, r'Optimizations, Cases complex\s+(\d+)', 9) +test.file_grep(test.stats, r'Optimizations, Tables created\s+(\d+)', 1) + +test.passes() diff --git a/test_regress/t/t_case_exhaustive_nocase.v b/test_regress/t/t_case_exhaustive_nocase.v new file mode 100644 index 000000000..0f52dc978 --- /dev/null +++ b/test_regress/t/t_case_exhaustive_nocase.v @@ -0,0 +1,143 @@ +// 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 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +// Exhaustive cases lowered as if/else chains, checked against reference expressions. + +// verilator lint_off CASEINCOMPLETE +// verilator lint_off CASEOVERLAP +// verilator lint_off CASEWITHX +// verilator lint_off CASEX + +module t ( + input clk +); + + typedef enum logic [1:0] { + E0, + E1, + E2 + } e_t; + + integer cyc = 0; + + wire [1:0] sel_plain = cyc[1:0]; + wire [2:0] s3 = cyc[2:0]; + e_t e; + assign e = e_t'(cyc[1:0]); + + logic [3:0] y_plain, y_casez, y_casex, y_never, y_inside, y_overlap, y_unique, y_enum, y_incompl; + + always_comb begin + case (sel_plain) + 2'd0: y_plain = 4'h1; + 2'd1: y_plain = 4'h2; + 2'd2: y_plain = 4'h4; + 2'd3: y_plain = 4'h8; + endcase + end + + always_comb begin + casez (s3) + 3'b1??: y_casez = 4'h1; + 3'b01?: y_casez = 4'h2; + 3'b001: y_casez = 4'h3; + 3'b000: y_casez = 4'h4; + endcase + end + + always_comb begin + casex (s3) + 3'bxx1: y_casex = 4'h1; + 3'bx10: y_casex = 4'h2; + 3'b100: y_casex = 4'h3; + 3'b000: y_casex = 4'h4; + endcase + end + + // Items with X never match in a plain case: the last item is unreachable + always_comb begin + case (s3[1:0]) + 2'b0x: y_never = 4'hf; + 2'd0: y_never = 4'h1; + 2'd1: y_never = 4'h2; + 2'd2: y_never = 4'h3; + 2'd3: y_never = 4'h4; + 2'b1x: y_never = 4'he; + endcase + end + + always_comb begin + case (s3) inside + 3'b1??: y_inside = 4'h1; + 3'b0?1: y_inside = 4'h2; + 3'b0?0: y_inside = 4'h3; + endcase + end + + // First match wins; the last item is reached only for values it covers + always_comb begin + casez (s3) + 3'b??1: y_overlap = 4'h1; + 3'b1??: y_overlap = 4'h2; + 3'b0?0: y_overlap = 4'h3; + 3'b1?0: y_overlap = 4'h4; + endcase + end + + always_comb begin + unique case (sel_plain) + 2'd3: y_unique = 4'h1; + 2'd2: y_unique = 4'h2; + 2'd1: y_unique = 4'h3; + 2'd0: y_unique = 4'h4; + endcase + end + + // Covers every enum value but not every bit pattern: value 3 matches no item + always_comb begin + y_enum = 4'h0; + unique0 case (e) + E0: y_enum = 4'h1; + E1: y_enum = 4'h2; + E2: y_enum = 4'h3; + endcase + end + + always_comb begin + y_incompl = 4'h0; + case (sel_plain) + 2'd0: y_incompl = 4'h1; + 2'd1: y_incompl = 4'h2; + 2'd2: y_incompl = 4'h3; + endcase + end + + always @(posedge clk) begin +`ifdef TEST_VERBOSE + $write("[%0t] cyc=%0d s3=%b %h %h %h %h %h %h %h %h %h\n", $time, cyc, s3, y_plain, y_casez, + y_casex, y_never, y_inside, y_overlap, y_unique, y_enum, y_incompl); +`endif + `checkd(y_plain, 4'h1 << sel_plain); + `checkd(y_casez, (s3[2] ? 4'h1 : s3[1] ? 4'h2 : s3[0] ? 4'h3 : 4'h4)); + `checkd(y_casex, (s3[0] ? 4'h1 : s3[1] ? 4'h2 : s3[2] ? 4'h3 : 4'h4)); + `checkd(y_never, 4'(s3[1:0]) + 4'h1); + `checkd(y_inside, (s3[2] ? 4'h1 : s3[0] ? 4'h2 : 4'h3)); + `checkd(y_overlap, (s3[0] ? 4'h1 : s3[2] ? 4'h2 : 4'h3)); + `checkd(y_unique, 4'h4 - 4'(sel_plain)); + `checkd(y_enum, (sel_plain == 2'd3 ? 4'h0 : 4'(sel_plain) + 4'h1)); + `checkd(y_incompl, (sel_plain == 2'd3 ? 4'h0 : 4'(sel_plain) + 4'h1)); + cyc <= cyc + 1; + if (cyc == 20) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule