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) <noreply@anthropic.com> Signed-off-by: Wolfgang Mayerwieser <wolfgang@mayerwieser.com>
This commit is contained in:
parent
6fbc7042a5
commit
6cd43b1bb1
|
|
@ -300,6 +300,7 @@ Vito Gamberini
|
||||||
Wei-Lun Chiu
|
Wei-Lun Chiu
|
||||||
William D. Jones
|
William D. Jones
|
||||||
Wilson Snyder
|
Wilson Snyder
|
||||||
|
Wolfgang Mayerwieser
|
||||||
Xi Zhang
|
Xi Zhang
|
||||||
Yan Xu
|
Yan Xu
|
||||||
Yangyu Chen
|
Yangyu Chen
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@ class CUseVisitor final : public VNVisitorConst {
|
||||||
iterateConst(nodep->lhsp()->dtypep());
|
iterateConst(nodep->lhsp()->dtypep());
|
||||||
}
|
}
|
||||||
void visit(AstNodeDType* nodep) override {
|
void visit(AstNodeDType* nodep) override {
|
||||||
|
if (nodep->user1SetOnce()) return; // Process once
|
||||||
if (nodep->virtRefDTypep()) iterateConst(nodep->virtRefDTypep());
|
if (nodep->virtRefDTypep()) iterateConst(nodep->virtRefDTypep());
|
||||||
if (nodep->virtRefDType2p()) iterateConst(nodep->virtRefDType2p());
|
if (nodep->virtRefDType2p()) iterateConst(nodep->virtRefDType2p());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
@ -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
|
||||||
Loading…
Reference in New Issue