diff --git a/src/dbSta/test/cpp/TestDbSta.cc b/src/dbSta/test/cpp/TestDbSta.cc index d51382780ea..87029681b15 100644 --- a/src/dbSta/test/cpp/TestDbSta.cc +++ b/src/dbSta/test/cpp/TestDbSta.cc @@ -264,7 +264,8 @@ TEST_F(TestDbSta, ReassociateModITermPin) db_network_->checkAxioms(); } -// Regression for #10210 (stale Path* dereference in rsz). +// Regression for #10210 (stale Path* dereference in rsz) and the rsz-side +// resetSearchAfterRepair() mitigation. // // Topology (TestDbSta_StalePrevPath.v): // clk -> b1(BUF) -> inv1(INV) -> nd1(NAND2) -> out1 @@ -277,6 +278,9 @@ TEST_F(TestDbSta, ReassociateModITermPin) // 4. Assert the captured Path's prev slot has been recycled: pin() // decodes to data that belongs to a different instance than nd1's // real input. +// 5. Apply the rsz mitigation (Resizer::resetSearchAfterRepair) and assert a +// fresh worst-path lookup is clean -- every node's vertex resolves, so no +// recycled slot survives for CheckCrpr::findCrpr to walk. TEST_F(TestDbSta, StalePrevPath) { const auto* test_info = testing::UnitTest::GetInstance()->current_test_info(); @@ -329,6 +333,21 @@ TEST_F(TestDbSta, StalePrevPath) EXPECT_NE(pre_pin_name, post_pin_name) << "but slot content should differ after free+reuse. before=" << pre_pin_name << " after=" << post_pin_name; + + // 5. rsz's #10210 mitigation: repair_timing runs resetSearchAfterRepair() at + // its end. Dropping the interned search state must make a fresh + // worst-path lookup clean again -- every node's vertex resolves, so no + // recycled slot survives for CheckCrpr::findCrpr to walk. + resizer_.resetSearchAfterRepair(); + Path* fixed_path = sta_->vertexWorstArrivalPath( + sta_->ensureGraph()->pinDrvrVertex(network->findPin(nd1, "ZN")), + MinMax::max()); + ASSERT_NE(fixed_path, nullptr); + EXPECT_EQ(network->pathName(fixed_path->pin(sta_.get())), "nd1/ZN"); + for (const Path* p = fixed_path; p != nullptr; p = p->prevPath()) { + EXPECT_NE(p->vertex(sta_.get()), nullptr) + << "reset left a stale path node for findCrpr to walk"; + } } } // namespace sta diff --git a/src/rsz/include/rsz/Resizer.hh b/src/rsz/include/rsz/Resizer.hh index d6830214029..1bec11da527 100644 --- a/src/rsz/include/rsz/Resizer.hh +++ b/src/rsz/include/rsz/Resizer.hh @@ -379,6 +379,11 @@ class Resizer : public sta::dbStaState, public sta::dbNetworkObserver int max_passes); int holdBufferCount() const; + // Drop interned STA search state and recompute timing so a stale CRPR clock + // path left by incremental repairs is not walked later (#10210 workaround; + // graph/parasitics/delays kept). See the definition. + void resetSearchAfterRepair(); + //////////////////////////////////////////////////////////////// bool recoverPower(float recover_power_percent, bool match_cell_footprint, diff --git a/src/rsz/src/Resizer.cc b/src/rsz/src/Resizer.cc index 3a168ba48dc..ba24721eb1f 100644 --- a/src/rsz/src/Resizer.cc +++ b/src/rsz/src/Resizer.cc @@ -5049,6 +5049,20 @@ int Resizer::holdBufferCount() const return repair_hold_->holdBufferCount(); } +void Resizer::resetSearchAfterRepair() +{ + // Workaround for #10210; the root fix belongs in OpenSTA. repair_timing's + // incremental updates recycle the per-vertex Path arena, but a CRPR clock + // path cached in an interned ClkInfo/Tag keeps a raw prev_path_ into the + // freed slot, so a later CheckCrpr::findCrpr (e.g. report_metrics) walks a + // dangling chain and aborts. Drop the interned search state so the next + // analysis rebuilds clean; graph/parasitics/delays are kept, only arrivals + // are recomputed. Proper fix: findCrpr should re-resolve clock-path nodes + // from stable graph ids, not raw prev_path_. TODO(#10210): remove when fixed. + search_->clear(); + sta_->updateTiming(/*full=*/true); +} + //////////////////////////////////////////////////////////////// bool Resizer::recoverPower(float recover_power_percent, bool match_cell_footprint, diff --git a/src/rsz/src/Resizer.i b/src/rsz/src/Resizer.i index b1c993189c3..753991c47c0 100644 --- a/src/rsz/src/Resizer.i +++ b/src/rsz/src/Resizer.i @@ -436,6 +436,14 @@ repair_hold_pin(Pin *end_pin, max_buffer_percent, max_passes); } +void +reset_search_after_repair() +{ + ensureLinked(); + Resizer *resizer = getResizer(); + resizer->resetSearchAfterRepair(); +} + int hold_buffer_count() { diff --git a/src/rsz/src/Resizer.tcl b/src/rsz/src/Resizer.tcl index 5ecf9fe282f..ca79f832cf1 100644 --- a/src/rsz/src/Resizer.tcl +++ b/src/rsz/src/Resizer.tcl @@ -395,7 +395,14 @@ proc repair_timing { args } { } } - return [expr $recovered_power || $repaired_setup || $repaired_hold] + set repaired [expr $recovered_power || $repaired_setup || $repaired_hold] + if { $repaired } { + # Workaround for #10210: incremental repairs can leave a stale CRPR clock + # path that crashes a later report; drop the search state so it rebuilds + # clean. Remove when fixed in OpenSTA. See Resizer::resetSearchAfterRepair. + rsz::reset_search_after_repair + } + return $repaired } ################################################################