From 6ad182464bcf04a28d59f32e9344da93d6a48600 Mon Sep 17 00:00:00 2001 From: Matthew Guthaus Date: Sat, 6 Jun 2026 09:05:12 -0700 Subject: [PATCH 1/3] grt: skip cross-side pairs in checkAdjacentLayersDirection checkAdjacentLayersDirection walks tech->findRoutingLayer(l) and findRoutingLayer(l+1) and errors GRT-126 when the two share a preferred direction. dbTech::findRoutingLayer indexes both frontside and backside layers by routing level, so the level-l / level-l+1 pair can straddle the backside / frontside boundary (e.g. BPR(H) at level k and M0(H) at level k+1 in a BSPDN tech). Those layers are not physical neighbors; they sit on opposite sides of the substrate, and direction agreement across the boundary is not a misconfiguration. Skip the pair when the two layers disagree on isBackside(). Does not trip in flows that set MIN_ROUTING_LAYER above the boundary (as the gt2n ORFS platform does today with M2), but the check would fire as a hard error if MIN_ROUTING_LAYER were lowered to M0 or below. Signed-off-by: Matthew Guthaus --- src/grt/src/GlobalRouter.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/grt/src/GlobalRouter.cpp b/src/grt/src/GlobalRouter.cpp index 9e3b4b59906..cdf8ffd7ca8 100644 --- a/src/grt/src/GlobalRouter.cpp +++ b/src/grt/src/GlobalRouter.cpp @@ -796,6 +796,13 @@ void GlobalRouter::checkAdjacentLayersDirection(int min_routing_layer, for (int l = min_routing_layer; l < max_routing_layer; l++) { odb::dbTechLayer* layer_a = tech->findRoutingLayer(l); odb::dbTechLayer* layer_b = tech->findRoutingLayer(l + 1); + // Backside and frontside routing layers sit on opposite sides of the + // substrate, so "adjacent routing level" across the side boundary is + // not a physical neighbor relationship and direction agreement there + // is not a misconfiguration. + if (layer_a->isBackside() != layer_b->isBackside()) { + continue; + } if (layer_a->getDirection() == layer_b->getDirection()) { logger_->error( GRT, From 0522a7dfef7934455edf61a4028dfd1e7bdbf7f9 Mon Sep 17 00:00:00 2001 From: Matthew Guthaus Date: Sat, 6 Jun 2026 09:05:21 -0700 Subject: [PATCH 2/3] cts: skip backside layers in writeClockNDRsToDb writeClockNDRsToDb walks tech->findRoutingLayer(1..count) and creates a dbTechLayerRule under the clock NDR for every routing layer in the tech, including backside layers (BPR, BM*, BRDL on a BSPDN tech). Clock trees are routed on the frontside; the backside NDR rules pick up backside-specific widths and pitches that would never be applied to a clock net but still live in the dbTechNonDefaultRule and could leak through downstream lookups by layer name. Guard the loop with isBackside() so the NDR only covers layers that clock routing can actually use. Signed-off-by: Matthew Guthaus --- src/cts/src/TritonCTS.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/cts/src/TritonCTS.cpp b/src/cts/src/TritonCTS.cpp index b0389c407b7..90cbc3dcf87 100644 --- a/src/cts/src/TritonCTS.cpp +++ b/src/cts/src/TritonCTS.cpp @@ -1931,6 +1931,13 @@ void TritonCTS::writeClockNDRsToDb(TreeBuilder* builder) odb::dbTech* tech = db_->getTech(); for (int i = 1; i <= tech->getRoutingLayerCount(); i++) { odb::dbTechLayer* layer = tech->findRoutingLayer(i); + // Backside routing layers (BPR, BM*, BRDL) are not clock-routing + // targets; clock trees live on the frontside. Skip them so we do + // not create NDR rules whose widths/spacings are derived from + // backside design rules and would never apply to a clock net. + if (layer->isBackside()) { + continue; + } odb::dbTechLayerRule* layerRule = clockNDR->getLayerRule(layer); if (!layerRule) { layerRule = odb::dbTechLayerRule::create(clockNDR, layer); From 778e7e9a2b0a4840fe1eaad00cb244618c582936 Mon Sep 17 00:00:00 2001 From: Matthew Guthaus Date: Sat, 6 Jun 2026 09:05:30 -0700 Subject: [PATCH 3/3] est: keep cut-resistance fallback midpoint on the frontside stack computeAverageCutResistance falls back to getRoutingLayerCount() / 2 when the block has no explicit max routing layer set. On a BSPDN tech the count includes backside routing layers (BPR, BM*, BRDL) sitting at low routing levels, so the midpoint lands on the backside stack and the loop that averages cut-layer resistance walks an inactive part of the via stack instead of the signal-routing layers it is meant to characterize. Use firstFrontsideRoutingLayer() to anchor the fallback at the first frontside level and halve the frontside-only count from there. Legacy techs without backside layers (first frontside == level 1) keep exactly the previous count / 2 behavior. Signed-off-by: Matthew Guthaus --- src/est/src/EstimateParasitics.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/est/src/EstimateParasitics.cpp b/src/est/src/EstimateParasitics.cpp index 5a800cf2815..294c51133f6 100644 --- a/src/est/src/EstimateParasitics.cpp +++ b/src/est/src/EstimateParasitics.cpp @@ -914,7 +914,22 @@ double EstimateParasitics::computeAverageCutResistance(sta::Scene* scene) int max_layer = block_->getMaxRoutingLayer(); if (max_layer < 0) { - max_layer = db_->getTech()->getRoutingLayerCount() / 2; + // Fall back to roughly the middle of the frontside routing stack. + // Halving the total routing-layer count picks the wrong stack when + // backside layers (BPR, BM*, BRDL) push the midpoint below the + // frontside boundary; counting only frontside levels keeps the + // heuristic on the side that hosts the signal routes whose cut + // resistance this function averages. + odb::dbTech* tech = db_->getTech(); + const int total_levels = tech->getRoutingLayerCount(); + odb::dbTechLayer* first_front = tech->firstFrontsideRoutingLayer(); + if (first_front != nullptr) { + const int first_level = first_front->getRoutingLevel(); + const int frontside_count = total_levels - first_level + 1; + max_layer = first_level - 1 + frontside_count / 2; + } else { + max_layer = total_levels / 2; + } } odb::dbTechLayer* min_tech_layer