diff --git a/src/grt/include/grt/GlobalRouter.h b/src/grt/include/grt/GlobalRouter.h index c605e9b8223..4dfc289c24f 100644 --- a/src/grt/include/grt/GlobalRouter.h +++ b/src/grt/include/grt/GlobalRouter.h @@ -311,6 +311,17 @@ class GlobalRouter const char* file_name); void reportNetDetailedRouteWL(odb::dbWire* wire, std::ofstream& out); void createWLReportFile(const char* file_name, bool verbose); + + // Detour reporting. The detour metric compares a net's final routed + // wirelength against its initial Steiner-tree wirelength captured before + // routing. ratio = final_wl / initial_steiner_wl; delta = final - initial. + // Read-only reporting; does not alter routing. + void reportNetDetour(odb::dbNet* net, const char* file_name); + void reportNetDetours(int top_n, const char* file_name); + bool hasInitialSteinerWirelengths() const + { + return !initial_steiner_wl_.empty(); + } std::vector getPinGridPositions(odb::dbNet* db_net); // Report wire resistance @@ -402,6 +413,11 @@ class GlobalRouter bool isPinReachable(const Pin& pin, const odb::Point& pos_on_grid); int computeNetWirelength(odb::dbNet* db_net); void computeWirelength(); + // Detour metric: capture the initial Steiner-tree wirelength (from the STT + // builder) for each net before overflow-removal routing runs. Read-only, + // does not affect routing. + void computeInitialSteinerWirelengths(const std::vector& nets); + int computeNetInitialSteinerWirelength(Net* net); std::vector getAllPorts(); void computeTrackConsumption(const Net* net, int8_t& track_consumption, @@ -532,6 +548,9 @@ class GlobalRouter std::unique_ptr groute_renderer_; NetRouteMap routes_; NetRouteMap partial_routes_; + // Initial Steiner-tree wirelength per net (dbu), captured before + // overflow-removal routing. Used for the detour metric. Read-only data. + odb::PtrMap initial_steiner_wl_; odb::PtrMap db_net_map_; Grid* grid_; diff --git a/src/grt/src/GlobalRouter.cpp b/src/grt/src/GlobalRouter.cpp index 4b29f48737b..38b41a06828 100644 --- a/src/grt/src/GlobalRouter.cpp +++ b/src/grt/src/GlobalRouter.cpp @@ -148,6 +148,7 @@ void GlobalRouter::initGui(std::unique_ptr void GlobalRouter::clear() { routes_.clear(); + initial_steiner_wl_.clear(); for (auto [ignored, net] : db_net_map_) { delete net; } @@ -410,6 +411,10 @@ void GlobalRouter::globalRoute(bool save_guides) getMinMaxLayer(min_layer, max_layer); std::vector nets = initFastRoute(min_layer, max_layer); + // Capture the initial Steiner-tree wirelength per net for the detour + // metric, before any overflow-removal routing alters the topology. + // Read-only: does not feed the routing engine. + computeInitialSteinerWirelengths(nets); if (use_cugr_) { odb::PtrSet clock_nets; findClockNets(nets, clock_nets); @@ -4095,6 +4100,202 @@ void GlobalRouter::computeWirelength() } } +// Build the initial Steiner topology for a net using the same STT builder that +// FastRoute uses, and return its total wirelength in dbu. Pin positions are the +// on-grid (track-snapped) positions in dbu, so the result is directly +// comparable with the final routed wirelength from computeNetWirelength(). +// Read-only: this does not feed the routing engine. +int GlobalRouter::computeNetInitialSteinerWirelength(Net* net) +{ + std::vector& pins = net->getPins(); + const int pin_count = static_cast(pins.size()); + if (pin_count < 2) { + return 0; + } + + std::vector x; + std::vector y; + x.reserve(pin_count); + y.reserve(pin_count); + int driver_index = 0; + for (int i = 0; i < pin_count; i++) { + const odb::Point& pos = pins[i].getOnGridPosition(); + x.push_back(pos.getX()); + y.push_back(pos.getY()); + if (pins[i].isDriver()) { + driver_index = i; + } + } + + // A two-pin net's Steiner WL is just the HPWL of its pins. + if (pin_count == 2) { + return std::abs(x[0] - x[1]) + std::abs(y[0] - y[1]); + } + + stt::Tree tree = stt_builder_->makeSteinerTree(x, y, driver_index); + return tree.length; +} + +void GlobalRouter::computeInitialSteinerWirelengths( + const std::vector& nets) +{ + initial_steiner_wl_.clear(); + for (Net* net : nets) { + if (net == nullptr) { + continue; + } + odb::dbNet* db_net = net->getDbNet(); + const int wl = computeNetInitialSteinerWirelength(net); + initial_steiner_wl_[db_net] = wl; + } +} + +void GlobalRouter::reportNetDetour(odb::dbNet* net, const char* file_name) +{ + block_ = db_->getChip()->getBlock(); + + auto init_iter = initial_steiner_wl_.find(net); + if (init_iter == initial_steiner_wl_.end()) { + logger_->warn(GRT, + 330, + "Net {} does not have an initial Steiner wirelength. Run " + "global_route first.", + net->getName()); + return; + } + if (!routes_.contains(net)) { + logger_->warn( + GRT, 331, "Net {} does not have a global route.", net->getName()); + return; + } + + const int64_t initial_wl = init_iter->second; + const int64_t final_wl = computeNetWirelength(net); + const int64_t delta = final_wl - initial_wl; + const double ratio + = (initial_wl > 0) ? static_cast(final_wl) / initial_wl : 1.0; + + logger_->info(GRT, + 332, + "Net {} detour: initial {:.2f}um, final {:.2f}um, delta " + "{:.2f}um, ratio {:.3f}", + net->getName(), + block_->dbuToMicrons(initial_wl), + block_->dbuToMicrons(final_wl), + block_->dbuToMicrons(delta), + ratio); + + std::string file(file_name); + if (!file.empty()) { + std::ofstream out(file, std::ios::app); + if (out.is_open()) { + out << "grt_detour: " << net->getName() << " " + << block_->dbuToMicrons(initial_wl) << " " + << block_->dbuToMicrons(final_wl) << " " + << block_->dbuToMicrons(delta) << " " << ratio << "\n"; + } + } +} + +void GlobalRouter::reportNetDetours(int top_n, const char* file_name) +{ + block_ = db_->getChip()->getBlock(); + + if (initial_steiner_wl_.empty()) { + logger_->warn(GRT, + 333, + "No initial Steiner wirelengths captured. Run global_route " + "first."); + return; + } + + struct DetourEntry + { + odb::dbNet* net; + int64_t initial_wl; + int64_t final_wl; + int64_t delta; + double ratio; + }; + + std::vector entries; + int64_t total_initial = 0; + int64_t total_final = 0; + for (const auto& [net, initial_wl] : initial_steiner_wl_) { + if (!routes_.contains(net)) { + continue; + } + const int64_t final_wl = computeNetWirelength(net); + // Only nets with a meaningful (multi-pin) initial topology are useful. + if (initial_wl <= 0) { + continue; + } + const int64_t delta = final_wl - initial_wl; + const double ratio = static_cast(final_wl) / initial_wl; + entries.push_back({net, initial_wl, final_wl, delta, ratio}); + total_initial += initial_wl; + total_final += final_wl; + } + + if (entries.empty()) { + logger_->warn( + GRT, 334, "No routed multi-pin nets available for detour reporting."); + return; + } + + std::sort(entries.begin(), + entries.end(), + [](const DetourEntry& a, const DetourEntry& b) { + return a.ratio > b.ratio; + }); + + const double total_ratio + = (total_initial > 0) ? static_cast(total_final) / total_initial + : 1.0; + + logger_->report("Detour metric (final routed WL vs initial Steiner WL):"); + logger_->report(" Nets analyzed: {}", entries.size()); + logger_->report(" Total initial Steiner WL: {:.2f}um", + block_->dbuToMicrons(total_initial)); + logger_->report(" Total final routed WL: {:.2f}um", + block_->dbuToMicrons(total_final)); + logger_->report(" Total detour: {:.2f}um (ratio {:.3f})", + block_->dbuToMicrons(total_final - total_initial), + total_ratio); + + const int n = (top_n <= 0) + ? static_cast(entries.size()) + : std::min(top_n, static_cast(entries.size())); + logger_->report(" Worst {} nets by detour ratio:", n); + logger_->report(" {:>10} {:>10} {:>10} {:>7} net", + "initial_um", + "final_um", + "delta_um", + "ratio"); + for (int i = 0; i < n; i++) { + const DetourEntry& e = entries[i]; + logger_->report(" {:>10.2f} {:>10.2f} {:>10.2f} {:>7.3f} {}", + block_->dbuToMicrons(e.initial_wl), + block_->dbuToMicrons(e.final_wl), + block_->dbuToMicrons(e.delta), + e.ratio, + e.net->getName()); + } + + std::string file(file_name); + if (!file.empty()) { + std::ofstream out(file, std::ios::app); + if (out.is_open()) { + for (const DetourEntry& e : entries) { + out << "grt_detour: " << e.net->getName() << " " + << block_->dbuToMicrons(e.initial_wl) << " " + << block_->dbuToMicrons(e.final_wl) << " " + << block_->dbuToMicrons(e.delta) << " " << e.ratio << "\n"; + } + } + } +} + void GlobalRouter::mergeSegments(const std::vector& pins, GRoute& route) { if (route.empty()) { diff --git a/src/grt/src/GlobalRouter.i b/src/grt/src/GlobalRouter.i index 02ddda16a40..3609fa1c50a 100644 --- a/src/grt/src/GlobalRouter.i +++ b/src/grt/src/GlobalRouter.i @@ -293,6 +293,21 @@ void report_net_wire_length(odb::dbNet* net, net, global_route, detailed_route, verbose, file_name); } +bool have_initial_steiner_wirelengths() +{ + return getGlobalRouter()->hasInitialSteinerWirelengths(); +} + +void report_net_detour(odb::dbNet* net, const char* file_name) +{ + getGlobalRouter()->reportNetDetour(net, file_name); +} + +void report_net_detours(int top_n, const char* file_name) +{ + getGlobalRouter()->reportNetDetours(top_n, file_name); +} + void clear_route_guides() { diff --git a/src/grt/src/GlobalRouter.tcl b/src/grt/src/GlobalRouter.tcl index 42cee8f91fd..e27562a7919 100644 --- a/src/grt/src/GlobalRouter.tcl +++ b/src/grt/src/GlobalRouter.tcl @@ -503,6 +503,55 @@ proc report_wire_length { args } { } } +sta::define_cmd_args "report_net_detours" { [-net net_list] \ + [-file file] \ + [-top_n count] +} + +# Reports the routing detour metric: the ratio (and delta) of each net's final +# routed wirelength to its initial Steiner-tree wirelength captured before +# overflow-removal routing. This is a read-only analysis command and does not +# change routing. +proc report_net_detours { args } { + sta::parse_key_args "report_net_detours" args \ + keys {-net -file -top_n} \ + flags {} + + set block [ord::get_db_block] + if { $block == "NULL" } { + utl::error GRT 335 "Missing dbBlock." + } + + if { ![grt::have_initial_steiner_wirelengths] } { + utl::error GRT 336 "No detour data available. Run global_route first." + } + + set file "" + if { [info exists keys(-file)] } { + set file $keys(-file) + } + + if { [info exists keys(-net)] } { + foreach net [get_nets $keys(-net)] { + set db_net [sta::sta_to_db_net $net] + if { + [$db_net getSigType] != "POWER" && + [$db_net getSigType] != "GROUND" && + ![$db_net isSpecial] + } { + grt::report_net_detour $db_net $file + } + } + } else { + set top_n 10 + if { [info exists keys(-top_n)] } { + set top_n $keys(-top_n) + sta::check_positive_integer "-top_n" $top_n + } + grt::report_net_detours $top_n $file + } +} + sta::define_cmd_args "estimate_path_resistance" { pin1_name pin2_name \ [-layer1 layer1] \ [-layer2 layer2] \ diff --git a/src/grt/test/BUILD b/src/grt/test/BUILD index 2ee847d906a..8dc23b53521 100644 --- a/src/grt/test/BUILD +++ b/src/grt/test/BUILD @@ -95,6 +95,7 @@ TESTS = [ "report_wire_length6", "report_wire_length7", "report_wire_length8", + "report_net_detours1", "set_nets_to_route1", "silence", "single_row", diff --git a/src/grt/test/CMakeLists.txt b/src/grt/test/CMakeLists.txt index bddf8cc267a..e682f3d4150 100644 --- a/src/grt/test/CMakeLists.txt +++ b/src/grt/test/CMakeLists.txt @@ -92,6 +92,7 @@ or_integration_tests( report_wire_length6 report_wire_length7 report_wire_length8 + report_net_detours1 set_nets_to_route1 silence single_row diff --git a/src/grt/test/report_net_detours1.ok b/src/grt/test/report_net_detours1.ok new file mode 100644 index 00000000000..71f0d560fb5 --- /dev/null +++ b/src/grt/test/report_net_detours1.ok @@ -0,0 +1,18 @@ +[INFO ODB-0227] LEF file: Nangate45/Nangate45.lef, created 22 layers, 27 vias, 135 library cells +[INFO ODB-0128] Design: gcd +[INFO ODB-0130] Created 54 pins. +[INFO ODB-0131] Created 676 components and 2850 component-terminals. +[INFO ODB-0133] Created 579 nets and 1498 connections. +[WARNING GRT-0300] Timing is not available, setting critical nets percentage to 0. +Detour metric (final routed WL vs initial Steiner WL): + Nets analyzed: 495 + Total initial Steiner WL: 7187.70um + Total final routed WL: 10767.30um + Total detour: 3579.60um (ratio 1.498) + Worst 5 nets by detour ratio: + initial_um final_um delta_um ratio net + 8.55 22.80 14.25 2.667 _283_ + 5.70 11.40 5.70 2.000 _233_ + 2.85 5.70 2.85 2.000 _256_ + 2.85 5.70 2.85 2.000 _254_ + 2.85 5.70 2.85 2.000 _247_ diff --git a/src/grt/test/report_net_detours1.tcl b/src/grt/test/report_net_detours1.tcl new file mode 100644 index 00000000000..ae8e400ef94 --- /dev/null +++ b/src/grt/test/report_net_detours1.tcl @@ -0,0 +1,10 @@ +# Test the read-only routing detour metric: ratio of final routed wirelength +# to the initial Steiner-tree wirelength captured before overflow removal. +# Uses gcd_nangate45 (same design as gcd.tcl) so routing is unchanged. +source "helpers.tcl" +read_lef "Nangate45/Nangate45.lef" +read_def "gcd.def" + +global_route + +report_net_detours -top_n 5