Skip to content

Commit 260f401

Browse files
committed
rsz: Refine latch borrow effective slack
Compute latch endpoint effective slack from uncovered time borrow instead of subtracting the full borrowed amount. This prevents repair_timing from over-optimizing latch D input paths when the latch output path has enough positive slack to absorb the borrow. Find latch output pins through both direct sequential output pins and output functions that reference internal latch state ports. Add a covered-borrow regression to ensure repair_timing leaves fully covered latch borrow unchanged. Signed-off-by: Jaehyun Kim <jhkim@precisioninno.com>
1 parent ce2d2f6 commit 260f401

5 files changed

Lines changed: 223 additions & 37 deletions

File tree

src/rsz/src/RepairTargetCollector.cc

Lines changed: 152 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "rsz/Resizer.hh"
2424
#include "sta/Delay.hh"
2525
#include "sta/ExceptionPath.hh"
26+
#include "sta/FuncExpr.hh"
2627
#include "sta/Fuzzy.hh"
2728
#include "sta/Graph.hh"
2829
#include "sta/GraphClass.hh"
@@ -38,10 +39,12 @@
3839
#include "sta/Sdc.hh"
3940
#include "sta/Search.hh"
4041
#include "sta/SearchClass.hh"
42+
#include "sta/Sequential.hh"
4143
#include "sta/Sta.hh"
4244
#include "sta/StringUtil.hh"
4345
#include "sta/TimingArc.hh"
4446
#include "sta/Transition.hh"
47+
#include "sta/VisitPathEnds.hh"
4548
#include "utl/Logger.h"
4649

4750
namespace rsz {
@@ -832,6 +835,44 @@ void RepairTargetCollector::collectViolatingEndpoints()
832835
* 100));
833836
}
834837

838+
namespace {
839+
840+
// Track the worst-slack constrained max path end at a vertex and record its
841+
// latch time borrow. Non-latch path ends report zero borrow.
842+
class WorstPathEndBorrowVisitor : public sta::PathEndVisitor
843+
{
844+
public:
845+
WorstPathEndBorrowVisitor(const sta::MinMax* min_max,
846+
const sta::StaState* sta)
847+
: min_max_(min_max), sta_(sta)
848+
{
849+
}
850+
sta::PathEndVisitor* copy() const override
851+
{
852+
return new WorstPathEndBorrowVisitor(*this);
853+
}
854+
void visit(sta::PathEnd* path_end) override
855+
{
856+
if (path_end->isUnconstrained() || path_end->minMax(sta_) != min_max_) {
857+
return;
858+
}
859+
const sta::Slack end_slack = path_end->slack(sta_);
860+
if (sta::delayLess(end_slack, worst_slack_, sta_)) {
861+
worst_slack_ = end_slack;
862+
borrow_ = path_end->borrow(sta_);
863+
}
864+
}
865+
sta::Arrival borrow() const { return borrow_; }
866+
867+
private:
868+
const sta::MinMax* min_max_;
869+
const sta::StaState* sta_;
870+
sta::Slack worst_slack_ = sta::INF;
871+
sta::Arrival borrow_ = 0.0;
872+
};
873+
874+
} // namespace
875+
835876
sta::Slack RepairTargetCollector::getEndpointEffectiveSlack(
836877
sta::Vertex* endpoint) const
837878
{
@@ -840,45 +881,121 @@ sta::Slack RepairTargetCollector::getEndpointEffectiveSlack(
840881
return reported_slack;
841882
}
842883

843-
sta::PinSet* to_pins = new sta::PinSet(network_);
844-
to_pins->insert(endpoint->pin());
845-
sta::ExceptionTo* to = sdc_->makeExceptionTo(to_pins,
846-
nullptr,
847-
nullptr,
848-
sta::RiseFallBoth::riseFall(),
849-
sta::RiseFallBoth::riseFall());
884+
// Visit the path ends stored at this endpoint and read the time borrow of
885+
// the worst-slack one. This avoids the heavyweight findPathEnds() machinery
886+
// (exception filter, path group construction, sorting) per endpoint.
887+
sta::VisitPathEnds visit_ends(sta_);
888+
WorstPathEndBorrowVisitor borrow_visitor(max_, sta_);
889+
visit_ends.visitPathEnds(endpoint, &borrow_visitor);
850890

851-
sta::StringSeq group_names;
852-
sta::PathEndSeq path_ends
853-
= search_->findPathEnds(nullptr, // from
854-
nullptr, // thrus
855-
to, // to
856-
false, // unconstrained
857-
sta_->scenes(), // scene
858-
sta::MinMaxAll::max(), // min_max
859-
1, // group_path_count
860-
1, // endpoint_path_count
861-
false, // unique_pins
862-
false, // unique_edges
863-
-sta::INF, // slack_min
864-
sta::INF, // slack_max
865-
true, // sort_by_slack
866-
group_names, // group_names
867-
true,
868-
false,
869-
true,
870-
true,
871-
true,
872-
true); // checks
873-
874-
if (path_ends.empty()) {
891+
const sta::Arrival borrow = borrow_visitor.borrow();
892+
if (sta::fuzzyLessEqual(borrow, 0.0f)) {
875893
return reported_slack;
876894
}
877895

878-
// Treat consumed latch transparency as hidden setup debt for repair_timing.
879-
// Only positive borrow adds debt; flop endpoints report zero borrow.
880-
const sta::Arrival borrow = path_ends[0]->borrow(search_);
881-
return reported_slack - std::max<float>(borrow, 0.0f);
896+
const std::vector<const sta::Pin*> output_pins = latchOutputPins(endpoint);
897+
const sta::Slack output_slack = latchOutputWorstSlack(output_pins);
898+
const sta::Slack uncovered_borrow
899+
= std::max<float>(borrow - std::max<float>(output_slack, 0.0f), 0.0f);
900+
return reported_slack - uncovered_borrow;
901+
}
902+
903+
std::vector<const sta::Pin*> RepairTargetCollector::latchOutputPins(
904+
sta::Vertex* endpoint) const
905+
{
906+
std::vector<const sta::Pin*> output_pins;
907+
const std::function<void(
908+
sta::Instance*, sta::LibertyCell*, sta::LibertyPort*)>
909+
add_output_pin = [this, &output_pins](sta::Instance* inst,
910+
sta::LibertyCell* cell,
911+
sta::LibertyPort* state_port) {
912+
const sta::Pin* output_pin = network_->findPin(inst, state_port);
913+
if (output_pin != nullptr) {
914+
output_pins.push_back(output_pin);
915+
return;
916+
}
917+
918+
// Some libraries declare latch state as an internal port and expose it
919+
// through an output pin function, for example Q = IQ.
920+
sta::LibertyCellPortIterator port_iter(cell);
921+
while (port_iter.hasNext()) {
922+
sta::LibertyPort* port = port_iter.next();
923+
sta::FuncExpr* func = port->function();
924+
if (!port->direction()->isAnyOutput() || func == nullptr
925+
|| !func->hasPort(state_port)) {
926+
continue;
927+
}
928+
929+
output_pin = network_->findPin(inst, port);
930+
if (output_pin != nullptr
931+
&& std::find(output_pins.begin(), output_pins.end(), output_pin)
932+
== output_pins.end()) {
933+
output_pins.push_back(output_pin);
934+
}
935+
}
936+
};
937+
938+
const sta::Pin* endpoint_pin = endpoint->pin();
939+
sta::LibertyPort* data_port = network_->libertyPort(endpoint_pin);
940+
sta::LibertyCell* cell = data_port->libertyCell();
941+
sta::Instance* inst = network_->instance(endpoint_pin);
942+
943+
for (const sta::Sequential& seq : cell->sequentials()) {
944+
if (!seq.isLatch() || seq.data() == nullptr
945+
|| !seq.data()->hasPort(data_port)) {
946+
continue;
947+
}
948+
949+
if (seq.output() != nullptr) {
950+
add_output_pin(inst, cell, seq.output());
951+
}
952+
if (seq.outputInv() != nullptr) {
953+
add_output_pin(inst, cell, seq.outputInv());
954+
}
955+
}
956+
957+
return output_pins;
958+
}
959+
960+
sta::Slack RepairTargetCollector::latchOutputWorstSlack(
961+
const std::vector<const sta::Pin*>& output_pins) const
962+
{
963+
sta::Slack worst_slack = sta::INF;
964+
965+
for (const sta::Pin* output_pin : output_pins) {
966+
sta::PinSet* from_pins = new sta::PinSet(network_);
967+
from_pins->insert(output_pin);
968+
sta::ExceptionFrom* from = sdc_->makeExceptionFrom(
969+
from_pins, nullptr, nullptr, sta::RiseFallBoth::riseFall());
970+
971+
sta::StringSeq group_names;
972+
sta::PathEndSeq path_ends = search_->findPathEnds(from,
973+
nullptr,
974+
nullptr,
975+
false,
976+
sta_->scenes(),
977+
sta::MinMaxAll::max(),
978+
1,
979+
1,
980+
false,
981+
false,
982+
-sta::INF,
983+
sta::INF,
984+
true,
985+
group_names,
986+
true,
987+
false,
988+
true,
989+
true,
990+
true,
991+
true);
992+
993+
for (sta::PathEnd* path_end : path_ends) {
994+
worst_slack = std::min(worst_slack, path_end->slack(search_));
995+
}
996+
}
997+
998+
return worst_slack;
882999
}
8831000

8841001
void RepairTargetCollector::collectViolatingStartpoints()

src/rsz/src/RepairTargetCollector.hh

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,8 @@ class RepairTargetCollector
170170
// Public endpoint collection for TNS Phase
171171
void collectViolatingEndpoints();
172172
// Effective slack includes hidden setup debt from latch time borrowing.
173-
// A latch endpoint with reported slack of zero but positive borrow is treated
174-
// as a setup violation by subtracting the borrow from the reported slack.
173+
// A latch endpoint only pays the part of its borrow that is not covered by
174+
// slack on the latch output path.
175175
sta::Slack getEndpointEffectiveSlack(sta::Vertex* endpoint) const;
176176
sta::Slack getPathSlackByIndex(const sta::Pin* endpoint_pin, int path_index);
177177
const vector<std::pair<const sta::Pin*, sta::Slack>>& getViolatingEndpoints()
@@ -290,6 +290,9 @@ class RepairTargetCollector
290290
void sortByHeuristic(float load_delay_threshold = 0.0);
291291
std::map<const sta::Pin*, sta::Delay> getLocalTns() const;
292292
sta::Delay getLocalPinTns(const sta::Pin* pin) const;
293+
std::vector<const sta::Pin*> latchOutputPins(sta::Vertex* endpoint) const;
294+
sta::Slack latchOutputWorstSlack(
295+
const std::vector<const sta::Pin*>& output_pins) const;
293296

294297
// === Cone traversal helpers ==============================================
295298
// Helper functions for cone-based collection

src/rsz/test/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ PASSFAIL_TESTS = [
258258
# build directory structure not compatible with Bazel
259259
# "cpp_tests",
260260
"inferred_clock_gator_time_borrow",
261+
"inferred_clock_gator_time_borrow_covered",
261262
"repair_setup_legacy_mt",
262263
"repair_setup_mt1",
263264
]

src/rsz/test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ or_integration_tests(
240240
global_sizing_presize
241241
PASSFAIL_TESTS
242242
inferred_clock_gator_time_borrow
243+
inferred_clock_gator_time_borrow_covered
243244
repair_setup_legacy_mt
244245
repair_setup_mt1
245246
cpp_tests
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
source "helpers.tcl"
2+
3+
set test_name inferred_clock_gator_time_borrow_covered
4+
5+
source Nangate45/Nangate45.vars
6+
read_liberty Nangate45/Nangate45_typ.lib
7+
read_lef Nangate45/Nangate45.lef
8+
read_def inferred_clock_gator_time_borrow.def
9+
10+
create_clock -name clk -period 1.0 clk
11+
create_clock -name vclk -period 1.0
12+
set_input_delay -clock vclk 0.90 [get_ports en_in]
13+
14+
source Nangate45/Nangate45.rc
15+
source $layer_rc_file
16+
set_wire_rc -signal -layer $wire_rc_layer
17+
set_wire_rc -clock -layer $wire_rc_layer_clk
18+
19+
clock_tree_synthesis -root_buf CLKBUF_X3 \
20+
-buf_list CLKBUF_X3 \
21+
-wire_unit 20 \
22+
-sink_clustering_enable \
23+
-distance_between_buffers 80 \
24+
-num_static_layers 1 \
25+
-repair_clock_nets
26+
27+
set_propagated_clock [all_clocks]
28+
estimate_parasitics -placement
29+
30+
proc extract_borrow { report_text } {
31+
if { ![regexp {actual time borrow[ ]+([0-9.]+)} $report_text -> borrow] } {
32+
utl::error RSZ 1009 "Missing actual time borrow in report_checks output."
33+
}
34+
return $borrow
35+
}
36+
37+
proc borrow_report { test_name label } {
38+
set report_file [make_result_file "${test_name}_${label}.rpt"]
39+
report_checks -path_delay max -to enable_latch/D \
40+
-group_path_count 1 -format full_clock_expanded -digits 4 > $report_file
41+
set stream [open $report_file r]
42+
set report_text [read $stream]
43+
close $stream
44+
return $report_text
45+
}
46+
47+
set before_report [borrow_report $test_name "before"]
48+
set before_borrow [extract_borrow $before_report]
49+
puts [format "BEFORE_BORROW %.4f" $before_borrow]
50+
51+
repair_timing -setup -skip_last_gasp -skip_pin_swap -skip_gate_cloning \
52+
-max_passes 20
53+
54+
set after_report [borrow_report $test_name "after"]
55+
set after_borrow [extract_borrow $after_report]
56+
puts [format "AFTER_BORROW %.4f" $after_borrow]
57+
58+
if { abs($after_borrow - $before_borrow) > 0.001 } {
59+
utl::error RSZ 1010 [format \
60+
"repair_timing changed covered latch borrow: before %.4f after %.4f" \
61+
$before_borrow $after_borrow]
62+
}
63+
64+
puts "pass inferred_clock_gator_time_borrow_covered"

0 commit comments

Comments
 (0)