grt: dedup duplicate pin-via guides when reloading routes_ from ODB (#10474)#10714
grt: dedup duplicate pin-via guides when reloading routes_ from ODB (#10474)#10714saurav-fermions wants to merge 1 commit into
Conversation
…he-OpenROAD-Project#10474) Global routing produced different final results when repair_antennas started from an uninitialized state (routes_ reconstructed from ODB guides) versus a warm in-memory state. Root cause: saveGuides() writes two dbGuide objects per pin-covering via (one per direction, with swapped layer/via-layer). loadGuidesFromDB() read both back and created two via GSegments in routes_, whereas the in-memory FastRouteCore::getRoutes() represents each physical via with a single GSegment (it explicitly skips the layer-swapped inverse). The round-trip globalRoutingToBox -> ODB guide -> boxToGlobalRouting was therefore not an identity, leaving extra via segments and diverging the subsequent antenna-repair result. Fix: add GlobalRouter::dedupViaSegments() and call it in loadGuidesFromDB() before addImplicitVias()/mergeSegments(). A via and its layer-swapped inverse (same coords, same unordered layer pair) are treated as the same physical via; the first is kept and later duplicates dropped. Wire segments are untouched. Verification (gcd, sky130hs): reloaded routes_ had 51 extra via segments vs the in-memory routes_ before the fix; after the fix the two match exactly as a multiset (2016 vias, 1098 wires, 3114 records each). The repair_antennas_from_odb output DEF is byte-identical to the reference; only two duplicate via lines are removed from the guide golden, which is updated accordingly. All 131 grt regression tests pass. Signed-off-by: Saurav Singh <saurav.singh@fermions.co>
There was a problem hiding this comment.
Code Review
This pull request introduces a dedupViaSegments method to remove duplicate via segments created when reconstructing routes from ODB guides, preventing redundant via GSegments and their layer-swapped inverses. The review feedback suggests optimizing this method by replacing std::set with std::vector and std::find to avoid heap allocation overhead and improve cache locality, as the number of vias per net is typically small.
| std::set<std::tuple<int, int, int, int>> seen_vias; // x, y, lo_layer, hi | ||
| size_t write = 0; | ||
| for (size_t read = 0; read < route.size(); read++) { | ||
| const GSegment& seg = route[read]; | ||
| if (seg.isVia() && seg.init_layer != seg.final_layer) { | ||
| const int lo = std::min(seg.init_layer, seg.final_layer); | ||
| const int hi = std::max(seg.init_layer, seg.final_layer); | ||
| const std::tuple<int, int, int, int> key{seg.init_x, seg.init_y, lo, hi}; | ||
| if (!seen_vias.insert(key).second) { | ||
| // Duplicate via (or its inverse) already kept; skip it. | ||
| continue; | ||
| } | ||
| } | ||
| if (write != read) { | ||
| route[write] = route[read]; | ||
| } | ||
| write++; | ||
| } | ||
| route.resize(write); |
There was a problem hiding this comment.
Using std::set inside a function called for every net in the design can introduce significant performance overhead due to heap allocations for each unique via. Since the number of vias per net is typically very small, using a std::vector with std::find is much more efficient because it avoids node allocation overhead and benefits from cache locality.
std::vector<std::tuple<int, int, int, int>> seen_vias; // x, y, lo_layer, hi
size_t write = 0;
for (size_t read = 0; read < route.size(); read++) {
const GSegment& seg = route[read];
if (seg.isVia() && seg.init_layer != seg.final_layer) {
const int lo = std::min(seg.init_layer, seg.final_layer);
const int hi = std::max(seg.init_layer, seg.final_layer);
const std::tuple<int, int, int, int> key{seg.init_x, seg.init_y, lo, hi};
if (std::find(seen_vias.begin(), seen_vias.end(), key) != seen_vias.end()) {
// Duplicate via (or its inverse) already kept; skip it.
continue;
}
seen_vias.push_back(key);
}
if (write != read) {
route[write] = route[read];
}
write++;
}
route.resize(write);
eder-matheus
left a comment
There was a problem hiding this comment.
It would be nice to have a specific unit test that exercises the specific problem. This test could compare the guides generated post a fresh GRT run with the guides generated after loading them from ODB.
Summary
Global routing produced different final results when
repair_antennasstarted from ODB-reconstructed guides versus a warm in-memory state, because theglobalRoutingToBox -> ODB guide -> boxToGlobalRoutinground-trip was not an identity. This de-duplicates the double-written pin-via guides so reloadingroutes_from ODB is deterministic.Type of Change
Impact
Antenna-repair global routing is reproducible regardless of warm/cold start.
Verification
ctest -R '^grt\.'131/131.Related Issues
Fixes #10474
Developed with SAIGE, Fermions' autonomous RTL/EDA debugging agent; root-caused, tested, and signed off by the submitter (@saurav-fermions).