Skip to content
1 change: 1 addition & 0 deletions src/grt/src/GlobalRouter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,7 @@ void GlobalRouter::globalRoute(bool save_guides)
if (use_cugr_) {
std::set<odb::dbNet*> clock_nets;
findClockNets(nets, clock_nets);
cugr_->setCongestionIterations(congestion_iterations_);
cugr_->init(min_layer, max_layer, clock_nets);
if (verbose_) {
reportResources();
Expand Down
5 changes: 5 additions & 0 deletions src/grt/src/GlobalRouter.tcl
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,11 @@ proc global_route { args } {
set iterations $keys(-congestion_iterations)
sta::check_positive_integer "-congestion_iterations" $iterations
grt::set_congestion_iterations $iterations
} elseif { [info exists flags(-use_cugr)] } {
# CUGR's ripu and reroute loop saturates around iteration 5, and each
# iteration runs a full 3D maze pass, so the default budget is
# tighter than FastRoute's 50.
grt::set_congestion_iterations 5
} else {
grt::set_congestion_iterations 50
}
Expand Down
70 changes: 69 additions & 1 deletion src/grt/src/cugr/include/CUGR.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ class CUGR
{
critical_nets_percentage_ = percentage;
}
void setCongestionIterations(int iterations)
{
congestion_iterations_ = iterations;
}
void addDirtyNet(odb::dbNet* net);
void updateNet(odb::dbNet* net);
void routeIncremental();
Expand All @@ -110,15 +114,78 @@ class CUGR
float calculatePartialSlack();
float getNetSlack(odb::dbNet* net);
void setInitialNetSlacks();
void updateOverflowNets(std::vector<int>& net_indices);
/**
* @brief Builds the rip-up set of nets touching a congested edge.
*
* Populates `net_indices` with the indices of every net whose
* routing tree contains at least one edge whose
* `demand > capacity * threshold`. At threshold == 1.0 the result is
* the strict-overflow set (the default used by the pattern/maze
* stages); at threshold < 1.0 it widens to include near-overflow
* edges, which is how the iterative RRR loop catches the "many nets
* piled onto one layer, only a few overflow" failure mode.
*
* @param net_indices Output: cleared and refilled with the selected
* net indices.
* @param threshold Per-edge utilization cutoff in [0.0, 1.0]
* (default 1.0 = strict overflow).
*/
void updateCongestedNets(std::vector<int>& net_indices,
double threshold = 1.0);

void patternRoute(std::vector<int>& net_indices);
void patternRouteWithDetours(std::vector<int>& net_indices);
void mazeRoute(std::vector<int>& net_indices);

/**
* @brief Stage 4 — iterative rip-up and re-route.
*
* Wraps the maze stage in a loop that sharpens the logistic cost
* slope each pass (so `PatternRoute` and the maze cost surface
* penalise full edges more aggressively) and widens the rip-up set
* to nets sitting on near-full edges (not just strictly-overflowed
* ones). Designed for the per-layer over-concentration failure mode
* where many nets pile onto a single low layer while upper layers
* stay idle.
*
* Early-exits when the integer overflow metric (`totalOverflow()`)
* is already zero, so designs that finished stage 3 clean pay no
* cost. Emits `GRT-0117` per iteration and `GRT-0118` if overflow
* remains when the loop ends.
*
* See `src/grt/doc/01-iterative-rrr.md` for the cost-model audit
* and the rationale for the chosen defaults.
*
* @param net_indices Reused scratch buffer (cleared on entry by
* `updateCongestedNets`).
*/
void iterativeRRR(std::vector<int>& net_indices);
void sortNetIndices(std::vector<int>& net_indices) const;
void getGuides(const GRNet* net,
std::vector<std::pair<int, grt::BoxT>>& guides);
void printStatistics() const;

/**
* @brief Diagnoses whether residual overflow is spreadable.
*
* For each `(direction, x, y)` tile, sums capacity and demand across
* all same-direction routing layers and classifies the tile:
*
* - **2D-aggregate overflow** (`sum_demand > sum_capacity`): true
* planar congestion — no layer-assignment policy can avoid it.
* - **3D-only overflow** (per-layer overflow but the aggregate has
* slack): some same-direction layer at the same tile still has
* unused capacity. The router could in principle redistribute
* the demand there.
*
* Reports `3D overflow / 2D-aggregate / spreadable = 3D − 2D` (the
* gap is the upper bound on how much overflow a perfect layer
* assignment could clear). Gated on `debugPrint(GRT, "rrr_2d", 1)`,
* so default builds pay only the gate check. Enable via Tcl with
* `set_debug_level GRT rrr_2d 1`.
*/
void debugCongestion2D() const;

std::unique_ptr<Design> design_;
std::unique_ptr<GridGraph> grid_graph_;
std::vector<int> net_indices_;
Expand All @@ -138,6 +205,7 @@ class CUGR
int area_of_wire_patches_ = 0;

float critical_nets_percentage_ = 0;
int congestion_iterations_ = 5;

std::vector<int> nets_to_route_;
};
Expand Down
188 changes: 173 additions & 15 deletions src/grt/src/cugr/src/CUGR.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -151,16 +151,20 @@ void CUGR::setInitialNetSlacks()
}
}

void CUGR::updateOverflowNets(std::vector<int>& net_indices)
void CUGR::updateCongestedNets(std::vector<int>& net_indices,
const double threshold)
{
net_indices.clear();
for (const auto& net : gr_nets_) {
if (net->getRoutingTree()
&& grid_graph_->checkOverflow(net->getRoutingTree()) > 0) {
if (!net->getRoutingTree()) {
continue;
}
if (grid_graph_->checkCongestion(net->getRoutingTree(), threshold) > 0) {
net_indices.push_back(net->getIndex());
}
}
logger_->report("Nets with overflow: {}.", net_indices.size());
debugPrint(
logger_, GRT, "rrr", 1, "Nets with overflow: {}.", net_indices.size());
}

void CUGR::patternRoute(std::vector<int>& net_indices)
Expand All @@ -187,7 +191,7 @@ void CUGR::patternRoute(std::vector<int>& net_indices)
grid_graph_->addTreeUsage(gr_nets_[net_index]->getRoutingTree());
}

updateOverflowNets(net_indices);
updateCongestedNets(net_indices);
}

void CUGR::patternRouteWithDetours(std::vector<int>& net_indices)
Expand Down Expand Up @@ -221,15 +225,14 @@ void CUGR::patternRouteWithDetours(std::vector<int>& net_indices)
grid_graph_->addTreeUsage(net->getRoutingTree());
}

updateOverflowNets(net_indices);
updateCongestedNets(net_indices);
}

void CUGR::mazeRoute(std::vector<int>& net_indices)
{
if (net_indices.empty()) {
return;
}
logger_->report("Stage 3: Maze routing on sparsified graph.");

if (critical_nets_percentage_ != 0) {
calculatePartialSlack();
Expand Down Expand Up @@ -269,7 +272,7 @@ void CUGR::mazeRoute(std::vector<int>& net_indices)
grid.step();
}

updateOverflowNets(net_indices);
updateCongestedNets(net_indices);
}

void CUGR::route()
Expand All @@ -289,14 +292,163 @@ void CUGR::route()

patternRouteWithDetours(net_indices);

if (!net_indices.empty()) {
logger_->report("Stage 3: Maze routing on sparsified graph.");
}
mazeRoute(net_indices);

iterativeRRR(net_indices);

printStatistics();
debugCongestion2D();
if (constants_.write_heatmap) {
grid_graph_->write();
}
}

void CUGR::debugCongestion2D() const
{
if (!logger_->debugCheck(utl::GRT, "rrr_2d", 1)) {
return;
}

const int x_size = grid_graph_->getXSize();
const int y_size = grid_graph_->getYSize();
const int num_layers = grid_graph_->getNumLayers();

double total_3d_overflow = 0.0;
double total_2d_overflow = 0.0;
int tiles_3d_only = 0;
int tiles_2d = 0;

for (int direction = 0; direction < 2; ++direction) {
std::vector<int> same_dir_layers;
for (int l = constants_.min_routing_layer; l < num_layers; ++l) {
if (grid_graph_->getLayerDirection(l) == direction) {
same_dir_layers.push_back(l);
}
}
if (same_dir_layers.empty()) {
continue;
}

// For an H layer, an edge spans gcells (x, y)→(x+1, y), so the
// valid edge index range is x < x_size - 1. Mirror for V.
const int x_max = (direction == MetalLayer::H) ? x_size - 1 : x_size;
const int y_max = (direction == MetalLayer::H) ? y_size : y_size - 1;

for (int x = 0; x < x_max; ++x) {
for (int y = 0; y < y_max; ++y) {
double sum_cap = 0.0;
double sum_dem = 0.0;
double per_layer_overflow_sum = 0.0;
for (int l : same_dir_layers) {
const auto& edge = grid_graph_->getEdge(l, x, y);
sum_cap += std::max(edge.capacity, 0.0);
sum_dem += edge.demand;
const double ovf = edge.demand - edge.capacity;
if (ovf > 0.0) {
per_layer_overflow_sum += ovf;
}
}
const double tile_2d_overflow = std::max(0.0, sum_dem - sum_cap);
total_3d_overflow += per_layer_overflow_sum;
total_2d_overflow += tile_2d_overflow;
if (tile_2d_overflow > 0.0) {
++tiles_2d;
} else if (per_layer_overflow_sum > 0.0) {
++tiles_3d_only;
}
}
}
}

const auto rnd = [](double v) { return static_cast<int>(std::round(v)); };
const int spreadable = rnd(total_3d_overflow - total_2d_overflow);
debugPrint(logger_, GRT, "rrr_2d", 1, "2D-aggregate congestion check:");
debugPrint(logger_,
GRT,
"rrr_2d",
1,
" 3D overflow: {} units",
rnd(total_3d_overflow));
debugPrint(logger_,
GRT,
"rrr_2d",
1,
" 2D-aggregate overflow: {} units (unavoidable)",
rnd(total_2d_overflow));
debugPrint(
logger_,
GRT,
"rrr_2d",
1,
" Spreadable overflow: {} units (could move to other layers)",
spreadable);
debugPrint(logger_,
GRT,
"rrr_2d",
1,
" Tiles with 3D-only ovf: {}",
tiles_3d_only);
debugPrint(logger_,
GRT,
"rrr_2d",
1,
" Tiles with 2D ovf: {} (true planar congestion)",
tiles_2d);
}

// Iterative rip-up & re-route. Wraps the maze stage in a loop
// that sharpens the logistic cost slope each pass (so PatternRoute and
// the maze cost surface penalise full edges more aggressively) and
// widens the rip-up set to nets sitting on near-full layers (not just
// strictly-overflowed ones). Designed for the per-layer concentration
// failure mode where many nets pile onto a single layer and ripping up
// only the few overflowed ones cannot redistribute the load.
void CUGR::iterativeRRR(std::vector<int>& net_indices)
{
// Gate on the integer overflow metric (the one users see in
// printStatistics and GRT-0096). Sub-1 fractional overflow rounds to 0
// and cannot be driven lower by RRR, so don't waste iterations on it.
if (totalOverflow() == 0) {
Comment thread
eder-matheus marked this conversation as resolved.
return;
}

// Multiplier ramps up to saturate around slope=6 — beyond that the
// logistic cost surface degenerates into a step function with no
// gradient information, and the maze starts thrashing.
constexpr double kMultiplierStep = 1.0;
constexpr double kMultiplierCap = 6.0;
constexpr double kCongestionThreshold = 0.9;

double multiplier = 1.0;
for (int i = 1; i <= congestion_iterations_; ++i) {
updateCongestedNets(net_indices, kCongestionThreshold);
if (net_indices.empty()) {
break;
}
if (multiplier < kMultiplierCap) {
multiplier += kMultiplierStep;
}
grid_graph_->setCostMultiplier(multiplier);
logger_->info(
GRT, 117, "Start extra iteration {}/{}", i, congestion_iterations_);
mazeRoute(net_indices);
}
grid_graph_->setCostMultiplier(1.0);

// Final summary: the last mazeRoute already printed "Nets with
// overflow" via updateOverflowNets, so just warn (if anything remains)
// using the same metric without re-printing the count.
if (const int residual = totalOverflow(); residual > 0) {
logger_->warn(GRT,
118,
"Iterative RRR finished with overflow remaining ({}).",
residual);
}
}

void CUGR::write(const std::string& guide_file)
{
area_of_pin_patches_ = 0;
Expand Down Expand Up @@ -547,8 +699,8 @@ void CUGR::printStatistics() const
}

// Overflow is computed from edge.demand (which includes via-stub
// demand). This is the metric CUGR's own checkOverflow,
// updateOverflowNets, and extractCongestionView use
// demand). This is the same metric used by CUGR's checkCongestion,
// updateCongestedNets, and extractCongestionView.
CapacityT total_overflow = 0;
CapacityT min_resource = std::numeric_limits<CapacityT>::max();
GRPoint bottleneck(-1, -1, -1);
Expand Down Expand Up @@ -885,10 +1037,16 @@ void CUGR::saveCongestion()
}
const int cap_int = static_cast<int>(std::round(cap));
const int demand_int = static_cast<int>(std::round(demand));
const int overflow_int = demand_int - cap_int;
if (overflow_int <= 0) {
continue;
}
// Compute overflow on the unrounded values so a fractional
// demand > capacity excess (common after the floor-to-1
// adjustment when via-stub demand pushes an integer-capacity
// edge slightly over) still produces a marker. Display value
// is rounded up to the nearest integer so the comment never
// shows "overflow:0" on a tile we just flagged.
// TODO: update congestion report and ODB markers with double
// for capacities and usages.
const int overflow_int
= std::max(1, static_cast<int>(std::ceil(demand - cap)));
Comment thread
eder-matheus marked this conversation as resolved.

const auto wc_it = wire_count.find({l, x, y});
const int wires = wc_it != wire_count.end() ? wc_it->second : 0;
Expand Down Expand Up @@ -950,7 +1108,7 @@ void CUGR::routeIncremental()
route();

std::vector<int> overflow_nets;
updateOverflowNets(overflow_nets);
updateCongestedNets(overflow_nets);
std::vector<int> secondary_nets;
std::ranges::set_difference(
overflow_nets, initial_nets, std::back_inserter(secondary_nets));
Expand Down
Loading
Loading