Skip to content

Commit 5e16c53

Browse files
oharboeclaude
andcommitted
rsz: switch WNS-stagnation gate to initial-baseline comparison
The sliding-window check (window_best - window_oldest over 200 passes) mis-classified plateaus as futility: any real design whose WNS drops dramatically early then flat-lines at a topology-bound floor while TNS keeps improving (aes, clone_flat, repair_fanout*) tripped the gate and aborted repair_timing too early, leaving worse max_cap/max_slew slack and different .ok-file output. Replace with best_wns_ever vs initial_wns_: only fire when WNS has effectively never moved from its starting value, which is the real signature of an obviously-futile run. Also bump rel_tol 0.5% -> 5%. Empirically aes improves WNS by ~50% of initial, clone_flat by ~95%, repair_fanout by ~90%; the hopeless.v synthetic only moves WNS by ~2% because buffer insertion chips something off even a grossly-over-clocked design. 5% sits in the gap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
1 parent 6466ae9 commit 5e16c53

2 files changed

Lines changed: 61 additions & 71 deletions

File tree

src/rsz/src/RepairSetup.cc

Lines changed: 44 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -988,12 +988,11 @@ void RepairSetup::printProgressFooter() const
988988

989989
void RepairSetup::resetStagnationTracking()
990990
{
991-
wns_history_.assign(wns_stagnation_window_passes_, 0.0f);
992-
wns_history_head_ = 0;
993-
wns_history_count_ = 0;
991+
// Initialize best_wns_ to the most-negative finite float so the very first
992+
// worstSlack() sample always wins the max().
993+
best_wns_ = std::numeric_limits<float>::lowest();
994994
wns_stagnation_tripped_ = false;
995995
wns_stagnation_iteration_ = 0;
996-
wns_stagnation_last_wns_ = 0;
997996
}
998997

999998
std::string RepairSetup::wnsStagnationReport(const int iteration) const
@@ -1003,21 +1002,26 @@ std::string RepairSetup::wnsStagnationReport(const int iteration) const
10031002
}
10041003
const int digits = 3;
10051004
return fmt::format(
1006-
"repair_timing: obviously futile - WNS stuck at {} after {} passes "
1007-
"(initial {}). This is probably an exploration run, not a timing "
1008-
"closure run. Aborting with best-effort result.",
1009-
delayAsString(wns_stagnation_last_wns_, digits, sta_),
1005+
"repair_timing: obviously futile - WNS only reached {} after {} passes "
1006+
"(initial {}, improvement {}). This is probably an exploration run, "
1007+
"not a timing closure run. Aborting with best-effort result.",
1008+
delayAsString(best_wns_, digits, sta_),
10101009
wns_stagnation_iteration_,
1011-
delayAsString(initial_wns_, digits, sta_));
1010+
delayAsString(initial_wns_, digits, sta_),
1011+
delayAsString(best_wns_ - initial_wns_, digits, sta_));
10121012
}
10131013

10141014
// Terminate progress if incremental fix rate within an opto interval falls
10151015
// below the threshold. Bump up the threshold after each large opto
10161016
// interval.
1017-
// A second, independent "WNS-stagnation" gate also returns true when the
1018-
// best-so-far WNS has failed to improve by a deterministic threshold over
1019-
// the last wns_stagnation_window_passes_ passes. This catches
1020-
// "obviously futile" designs where TNS keeps twitching but WNS is pinned.
1017+
// A second, independent "WNS-stagnation" gate also returns true when, after
1018+
// the warmup, the best-so-far WNS has failed to improve by a deterministic
1019+
// threshold relative to the initial WNS. That detects designs where WNS
1020+
// essentially never moves from its starting value — the real signature of
1021+
// an "obviously futile" run. Plateaus on legitimate designs (WNS drops by
1022+
// orders of magnitude early, then flat-lines while TNS keeps improving) do
1023+
// not trip this gate, because best_wns_ has already moved far from
1024+
// initial_wns_ by the time the plateau starts.
10211025
bool RepairSetup::terminateProgress(const int iteration,
10221026
const float initial_tns,
10231027
float& prev_tns,
@@ -1028,48 +1032,33 @@ bool RepairSetup::terminateProgress(const int iteration,
10281032
const char* phase_name,
10291033
const char phase_marker)
10301034
{
1031-
// WNS-stagnation gate. Sampled every call so the ring buffer advances at
1032-
// the same rate as the caller's inner loop (deterministic).
1033-
const sta::Slack wns_now = sta_->worstSlack(max_);
1034-
if (!wns_history_.empty()) {
1035-
const size_t window = wns_history_.size();
1036-
wns_history_[wns_history_head_] = wns_now;
1037-
wns_history_head_ = (wns_history_head_ + 1) % window;
1038-
if (wns_history_count_ < window) {
1039-
wns_history_count_++;
1040-
} else if (iteration > wns_stagnation_warmup_iterations_) {
1041-
// Window is full; compare oldest sample (the one we are about to
1042-
// overwrite on the next call) to the best (largest, i.e. least
1043-
// negative) slack observed in the window.
1044-
sta::Slack window_best
1045-
= *std::max_element(wns_history_.begin(), wns_history_.end());
1046-
const sta::Slack window_oldest = wns_history_[wns_history_head_];
1047-
const float threshold
1048-
= std::max(wns_stagnation_abs_tol_,
1049-
wns_stagnation_rel_tol_ * std::fabs(initial_wns_));
1050-
if (window_best - window_oldest < threshold) {
1051-
debugPrint(
1052-
logger_,
1053-
RSZ,
1054-
"repair_setup",
1055-
1,
1056-
"{}{} Phase: Exiting at iteration {} because WNS best-in-window "
1057-
"{} only improved {} over last {} passes (threshold {}) "
1058-
"[endpoint {}/{}]",
1059-
phase_name,
1060-
phase_marker,
1061-
iteration,
1062-
delayAsString(window_best, 3, sta_),
1063-
delayAsString(window_best - window_oldest, 3, sta_),
1064-
window,
1065-
delayAsString(threshold, 3, sta_),
1066-
endpt_index,
1067-
num_endpts);
1068-
wns_stagnation_tripped_ = true;
1069-
wns_stagnation_iteration_ = iteration;
1070-
wns_stagnation_last_wns_ = wns_now;
1071-
return true;
1072-
}
1035+
// WNS-stagnation gate. Sampled every call so the "best so far" tracker
1036+
// advances at the same rate as the caller's inner loop (deterministic).
1037+
best_wns_ = std::max(best_wns_, sta_->worstSlack(max_));
1038+
if (iteration > wns_stagnation_warmup_iterations_) {
1039+
const float threshold
1040+
= std::max(wns_stagnation_abs_tol_,
1041+
wns_stagnation_rel_tol_ * std::fabs(initial_wns_));
1042+
if (best_wns_ - initial_wns_ < threshold) {
1043+
debugPrint(logger_,
1044+
RSZ,
1045+
"repair_setup",
1046+
1,
1047+
"{}{} Phase: Exiting at iteration {} because WNS best-so-far "
1048+
"{} only improved {} from initial {} (threshold {}) "
1049+
"[endpoint {}/{}]",
1050+
phase_name,
1051+
phase_marker,
1052+
iteration,
1053+
delayAsString(best_wns_, 3, sta_),
1054+
delayAsString(best_wns_ - initial_wns_, 3, sta_),
1055+
delayAsString(initial_wns_, 3, sta_),
1056+
delayAsString(threshold, 3, sta_),
1057+
endpt_index,
1058+
num_endpts);
1059+
wns_stagnation_tripped_ = true;
1060+
wns_stagnation_iteration_ = iteration;
1061+
return true;
10731062
}
10741063
}
10751064

src/rsz/src/RepairSetup.hh

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Copyright (c) 2022-2025, The OpenROAD Authors
33

44
#pragma once
5-
#include <cstddef>
65
#include <map>
76
#include <memory>
87
#include <set>
@@ -253,32 +252,34 @@ class RepairSetup : public sta::dbStaState
253252
= 0.0001; // default fix rate threshold = 0.01%
254253
static constexpr int max_last_gasp_passes_ = 10;
255254

256-
// WNS-stagnation gate. If best-so-far WNS has not improved by at least
257-
// max(wns_stagnation_abs_tol_, wns_stagnation_rel_tol_ * |initial_wns|)
258-
// over the last wns_stagnation_window_passes_ passes, terminateProgress()
259-
// returns true. Combined with the existing two-consecutive-termination rule
260-
// this aborts the phase after 2*window passes of no WNS movement.
261-
// Deterministic (iteration-count based, no wallclock). Conservative enough
262-
// that tape-out flows near closure are dominated by the TNS fix-rate gate
263-
// instead.
264-
static constexpr int wns_stagnation_window_passes_ = 200;
255+
// WNS-stagnation gate. Compares the best-so-far WNS against the WNS
256+
// captured at the start of repair_timing (initial_wns_). If after the
257+
// warmup the best WNS has improved by less than
258+
// max(wns_stagnation_abs_tol_, wns_stagnation_rel_tol_ * |initial_wns_|),
259+
// terminateProgress() returns true. "No improvement from initial" is the
260+
// real signature of an obviously-futile design; plateaus on legitimate
261+
// designs (WNS bottoms out at a topology limit while TNS keeps
262+
// improving) are *not* caught, because best_wns_ has already moved far
263+
// from initial_wns_. Deterministic (iteration-count based, no wallclock).
265264
static constexpr float wns_stagnation_abs_tol_ = 1.0e-12f; // 1 ps
266-
static constexpr float wns_stagnation_rel_tol_ = 0.005f; // 0.5% of |init|
265+
// 5% of |initial_wns|. Empirically: aes improves WNS by ~50%, clone_flat
266+
// by ~95%, repair_fanout by ~90% of initial; the hopeless.v synthetic only
267+
// moves WNS by ~2% because buffer insertion on a grossly-over-clocked
268+
// design still chips off a little. 5% sits safely in that gap.
269+
static constexpr float wns_stagnation_rel_tol_ = 0.05f;
267270
// Warmup: skip the gate until this many passes have elapsed, so normal
268271
// repair progress on ordinary designs is not short-circuited.
269272
static constexpr int wns_stagnation_warmup_iterations_ = 1000;
270273

271-
// Per-phase WNS history (ring buffer).
272-
std::vector<sta::Slack> wns_history_;
273-
size_t wns_history_head_ = 0;
274-
size_t wns_history_count_ = 0;
275274
sta::Slack initial_wns_ = 0;
275+
// Best (largest / least-negative) WNS observed since the most recent
276+
// resetStagnationTracking(). Reset at the start of each repair phase.
277+
sta::Slack best_wns_ = 0;
276278
// Set by terminateProgress() when the WNS-stagnation gate fires. Consumed
277279
// by the phase-level abort sites to emit a loud info log. Reset by
278280
// resetStagnationTracking().
279281
bool wns_stagnation_tripped_ = false;
280282
int wns_stagnation_iteration_ = 0;
281-
sta::Slack wns_stagnation_last_wns_ = 0;
282283
};
283284

284285
} // namespace rsz

0 commit comments

Comments
 (0)