Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/grt/include/grt/GlobalRouter.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<PinGridLocation> getPinGridPositions(odb::dbNet* db_net);

// Report wire resistance
Expand Down Expand Up @@ -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<Net*>& nets);
int computeNetInitialSteinerWirelength(Net* net);
std::vector<Pin*> getAllPorts();
void computeTrackConsumption(const Net* net,
int8_t& track_consumption,
Expand Down Expand Up @@ -532,6 +548,9 @@ class GlobalRouter
std::unique_ptr<AbstractGrouteRenderer> 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<odb::dbNet, int64_t> initial_steiner_wl_;

odb::PtrMap<odb::dbNet, Net*> db_net_map_;
Grid* grid_;
Expand Down
201 changes: 201 additions & 0 deletions src/grt/src/GlobalRouter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ void GlobalRouter::initGui(std::unique_ptr<AbstractRoutingCongestionDataSource>
void GlobalRouter::clear()
{
routes_.clear();
initial_steiner_wl_.clear();
for (auto [ignored, net] : db_net_map_) {
delete net;
}
Expand Down Expand Up @@ -410,6 +411,10 @@ void GlobalRouter::globalRoute(bool save_guides)
getMinMaxLayer(min_layer, max_layer);

std::vector<Net*> 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<odb::dbNet> clock_nets;
findClockNets(nets, clock_nets);
Expand Down Expand Up @@ -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<Pin>& pins = net->getPins();
const int pin_count = static_cast<int>(pins.size());
if (pin_count < 2) {
return 0;
}

std::vector<int> x;
std::vector<int> 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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To ensure that the initial Steiner wirelength is computed using the exact same parameters (such as net-specific alpha values) that FastRoute uses for each net, please use the makeSteinerTree overload that accepts the odb::dbNet* object.

stt::Tree tree = stt_builder_->makeSteinerTree(net->getDbNet(), x, y, driver_index);

return tree.length;
}

void GlobalRouter::computeInitialSteinerWirelengths(
const std::vector<Net*>& 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<double>(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";
}
}
Comment on lines +4189 to +4197

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If the specified detour report file cannot be opened, the tool currently fails silently. It is better to log a warning to inform the user that the report could not be written.

  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";
    } else {
      logger_->warn(GRT, 337, "Could not open detour report file {}.", file_name);
    }
  }

}

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<DetourEntry> 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<double>(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;
});
Comment on lines +4246 to +4250

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When sorting detour entries, if multiple nets have the same detour ratio, their relative order is non-deterministic. To ensure deterministic reporting (which is critical for regression testing), please add a tie-breaker using the unique net ID (net->getId()). This aligns with the repository's general rule of preferring unique integer IDs for tie-breaking in sort comparators.

  std::sort(entries.begin(),
            entries.end(),
            [](const DetourEntry& a, const DetourEntry& b) {
              if (a.ratio != b.ratio) {
                return a.ratio > b.ratio;
              }
              return a.net->getId() < b.net->getId();
            });
References
  1. For tie-breaking in sort comparators, prefer using unique integer IDs (e.g., cell1->id() < cell2->id()) instead of string comparisons (e.g., cell1->name() < cell2->name()), as string comparisons are highly expensive.


const double total_ratio
= (total_initial > 0) ? static_cast<double>(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<int>(entries.size())
: std::min(top_n, static_cast<int>(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";
}
}
}
Comment on lines +4285 to +4296

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If the specified detour report file cannot be opened, the tool currently fails silently. It is better to log a warning to inform the user that the report could not be written.

  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";
      }
    } else {
      logger_->warn(GRT, 338, "Could not open detour report file {}.", file_name);
    }
  }

}

void GlobalRouter::mergeSegments(const std::vector<Pin>& pins, GRoute& route)
{
if (route.empty()) {
Expand Down
15 changes: 15 additions & 0 deletions src/grt/src/GlobalRouter.i
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down
49 changes: 49 additions & 0 deletions src/grt/src/GlobalRouter.tcl
Original file line number Diff line number Diff line change
Expand Up @@ -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] \
Expand Down
1 change: 1 addition & 0 deletions src/grt/test/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ TESTS = [
"report_wire_length6",
"report_wire_length7",
"report_wire_length8",
"report_net_detours1",
"set_nets_to_route1",
"silence",
"single_row",
Expand Down
1 change: 1 addition & 0 deletions src/grt/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 18 additions & 0 deletions src/grt/test/report_net_detours1.ok
Original file line number Diff line number Diff line change
@@ -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_
10 changes: 10 additions & 0 deletions src/grt/test/report_net_detours1.tcl
Original file line number Diff line number Diff line change
@@ -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
Loading