From 6cd43b1bb1163470b52b6fddeb9a03509f743ad9 Mon Sep 17 00:00:00 2001 From: Wolfgang Mayerwieser Date: Tue, 23 Jun 2026 15:54:11 +0200 Subject: [PATCH] Fix O(n^2) runtime in V3CUse for large package-scoped structs CUseVisitor::visit(AstNodeDType*) walked the full (recursively nested) member tree of every package-scoped struct each time the struct's dtype was reached. A large unpacked struct used as a module port produces one variable scope per leaf field, and the top-level struct dtype is reached once per such reference, so the member walk ran O(references x members) = O(n^2) times. The walk only feeds addNewUse(), which already de-duplicates by name, so the repeated traversals produced no additional output. Guard the walk with user1SetOnce() so each struct's members are traversed at most once per module, matching the process-once pattern already used by the other visitors in this file. This was found on a real, auto-generated PeakRDL-regblock register block that did not finish verilating before the fix; with the fix it completes. The original design is proprietary, so the included regression test and the scaling reproducer are self-contained stand-ins that mirror its structure. Reproducer scaling, V3CUse pass elapsed (doubling the size quadruples the unpatched time): registers leaf fields before after 200 400 ~5 s ~0.001 s 400 800 ~20 s ~0.002 s 800 1600 ~80 s ~0.004 s 2500 5000 ~804 s ~0.012 s At 2500 registers (5000 leaf fields, ~real-design scale) total verilation drops from ~806 s to ~1.3 s. Generated C++ output is byte-for-byte identical before and after the fix. Disclosure: investigated and drafted with the assistance of Claude (an AI tool); reviewed, built, and tested by the human contributor. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: Wolfgang Mayerwieser --- docs/CONTRIBUTORS | 1 + src/V3CUse.cpp | 1 + test_regress/t/t_cuse_struct_pkg.py | 18 +++++++ test_regress/t/t_cuse_struct_pkg.v | 76 +++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+) create mode 100644 test_regress/t/t_cuse_struct_pkg.py create mode 100644 test_regress/t/t_cuse_struct_pkg.v diff --git a/docs/CONTRIBUTORS b/docs/CONTRIBUTORS index a22db1f07..2acb737b0 100644 --- a/docs/CONTRIBUTORS +++ b/docs/CONTRIBUTORS @@ -300,6 +300,7 @@ Vito Gamberini Wei-Lun Chiu William D. Jones Wilson Snyder +Wolfgang Mayerwieser Xi Zhang Yan Xu Yangyu Chen diff --git a/src/V3CUse.cpp b/src/V3CUse.cpp index 2fddf7f82..51d6fd688 100644 --- a/src/V3CUse.cpp +++ b/src/V3CUse.cpp @@ -65,6 +65,7 @@ class CUseVisitor final : public VNVisitorConst { iterateConst(nodep->lhsp()->dtypep()); } void visit(AstNodeDType* nodep) override { + if (nodep->user1SetOnce()) return; // Process once if (nodep->virtRefDTypep()) iterateConst(nodep->virtRefDTypep()); if (nodep->virtRefDType2p()) iterateConst(nodep->virtRefDType2p()); diff --git a/test_regress/t/t_cuse_struct_pkg.py b/test_regress/t/t_cuse_struct_pkg.py new file mode 100644 index 000000000..8a938befd --- /dev/null +++ b/test_regress/t/t_cuse_struct_pkg.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_cuse_struct_pkg.v b/test_regress/t/t_cuse_struct_pkg.v new file mode 100644 index 000000000..197bbe62a --- /dev/null +++ b/test_regress/t/t_cuse_struct_pkg.v @@ -0,0 +1,76 @@ +// 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 + +package pkg; + typedef struct { + logic [7:0] value; + } field_t; + typedef struct { + field_t f0; + field_t f1; + } reg_t; + typedef struct { + reg_t R0; + reg_t R1; + reg_t R2; + } hwif_t; +endpackage + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + import pkg::*; + + hwif_t hwif_in; + hwif_t hwif_out; + hwif_t storage; + + integer cyc = 0; + + always_ff @(posedge clk) begin + storage.R0.f0.value <= hwif_in.R0.f0.value; + storage.R0.f1.value <= hwif_in.R0.f1.value; + storage.R1.f0.value <= hwif_in.R1.f0.value; + storage.R1.f1.value <= hwif_in.R1.f1.value; + storage.R2.f0.value <= hwif_in.R2.f0.value; + storage.R2.f1.value <= hwif_in.R2.f1.value; + end + + always_comb begin + hwif_out.R0.f0.value = storage.R0.f0.value; + hwif_out.R0.f1.value = storage.R0.f1.value; + hwif_out.R1.f0.value = storage.R1.f0.value; + hwif_out.R1.f1.value = storage.R1.f1.value; + hwif_out.R2.f0.value = storage.R2.f0.value; + hwif_out.R2.f1.value = storage.R2.f1.value; + end + + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 0) begin + hwif_in.R0.f0.value <= 8'h11; + hwif_in.R0.f1.value <= 8'h22; + hwif_in.R1.f0.value <= 8'h33; + hwif_in.R1.f1.value <= 8'h44; + hwif_in.R2.f0.value <= 8'h55; + hwif_in.R2.f1.value <= 8'h66; + end + else if (cyc == 3) begin + if (hwif_out.R0.f0.value !== 8'h11) $stop; + if (hwif_out.R0.f1.value !== 8'h22) $stop; + if (hwif_out.R1.f0.value !== 8'h33) $stop; + if (hwif_out.R1.f1.value !== 8'h44) $stop; + if (hwif_out.R2.f0.value !== 8'h55) $stop; + if (hwif_out.R2.f1.value !== 8'h66) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule