diff --git a/src/operation/iDRC/interface/DRCInterface.cpp b/src/operation/iDRC/interface/DRCInterface.cpp index 2d64a183b..132e98af8 100644 --- a/src/operation/iDRC/interface/DRCInterface.cpp +++ b/src/operation/iDRC/interface/DRCInterface.cpp @@ -96,8 +96,10 @@ void DRCInterface::checkDef() bool origin_quiet = DRCLOG.isQuiet(); DRCLOG.disableQuiet(); + std::set obs_shape_idx_set; + std::vector env_shape_list = buildEnvShapeList(obs_shape_idx_set); std::map> type_violation_map; - for (ids::Violation& ids_violation : getViolationList(buildEnvShapeList(), buildResultShapeList(), {}, {})) { + for (ids::Violation& ids_violation : getViolationList(env_shape_list, buildResultShapeList(), {}, {}, obs_shape_idx_set)) { type_violation_map[ids_violation.violation_type].push_back(ids_violation); } printSummary(type_violation_map); @@ -150,11 +152,21 @@ std::vector DRCInterface::getViolationList(const std::vector& ids_result_shape_list, const std::set& ids_check_type_set, const std::vector& ids_check_region_list) +{ + return getViolationList(ids_env_shape_list, ids_result_shape_list, ids_check_type_set, ids_check_region_list, {}); +} + +std::vector DRCInterface::getViolationList(const std::vector& ids_env_shape_list, + const std::vector& ids_result_shape_list, + const std::set& ids_check_type_set, + const std::vector& ids_check_region_list, + const std::set& obs_shape_idx_set) { std::vector drc_env_shape_list; drc_env_shape_list.reserve(ids_env_shape_list.size()); - for (const ids::Shape& ids_env_shape : ids_env_shape_list) { - drc_env_shape_list.push_back(convertToDRCShape(ids_env_shape)); + for (size_t i = 0; i < ids_env_shape_list.size(); i++) { + drc_env_shape_list.push_back(convertToDRCShape(ids_env_shape_list[i])); + drc_env_shape_list.back().set_is_obs(obs_shape_idx_set.count(i) > 0); } std::vector drc_result_shape_list; drc_result_shape_list.reserve(ids_result_shape_list.size()); @@ -911,41 +923,39 @@ void DRCInterface::output() std::vector DRCInterface::buildEnvShapeList() { + std::set obs_shape_idx_set; + return buildEnvShapeList(obs_shape_idx_set); +} + +std::vector DRCInterface::buildEnvShapeList(std::set& obs_shape_idx_set) +{ + obs_shape_idx_set.clear(); std::vector env_shape_list; auto monitor = Monitor::create(); DRCLOG.info(Loc::current(), "Starting..."); - std::vector& idb_instance_list = dmInst->get_idb_def_service()->get_design()->get_instance_list()->get_instance_list(); - std::vector& idb_net_list = dmInst->get_idb_def_service()->get_design()->get_net_list()->get_net_list(); - std::vector& idb_special_net_list = dmInst->get_idb_def_service()->get_design()->get_special_net_list()->get_net_list(); - std::vector& idb_io_pin_list = dmInst->get_idb_def_service()->get_design()->get_io_pin_list()->get_pin_list(); - std::vector idb_blockage_list = dmInst->get_idb_def_service()->get_design()->get_blockage_list()->get_blockage_list(); - idb::IdbDesign* idb_design = dmInst->get_idb_def_service()->get_design(); - std::map special_pin_net_idx_map; + auto* idb_design = dmInst->get_idb_def_service()->get_design(); + std::vector& idb_instance_list = idb_design->get_instance_list()->get_instance_list(); + std::vector& idb_net_list = idb_design->get_net_list()->get_net_list(); + std::vector& idb_special_net_list = idb_design->get_special_net_list()->get_net_list(); + std::vector& idb_io_pin_list = idb_design->get_io_pin_list()->get_pin_list(); + std::vector idb_blockage_list = idb_design->get_blockage_list()->get_blockage_list(); std::map special_net_idx_map; int32_t regular_net_num = static_cast(idb_net_list.size()); for (size_t i = 0; i < idb_special_net_list.size(); ++i) { - int32_t special_net_id = regular_net_num + static_cast(i); - special_net_idx_map[idb_special_net_list[i]] = special_net_id; - for (idb::IdbPin* idb_pin : idb_special_net_list[i]->get_instance_pin_list()->get_pin_list()) { - special_pin_net_idx_map[idb_pin] = special_net_id; - } - for (idb::IdbPin* idb_pin : idb_special_net_list[i]->get_io_pins()->get_pin_list()) { - special_pin_net_idx_map[idb_pin] = special_net_id; - } + special_net_idx_map[idb_special_net_list[i]] = regular_net_num + static_cast(i); } auto get_pin_net_idx = [&](idb::IdbPin* idb_pin) { - auto it = special_pin_net_idx_map.find(idb_pin); - if (it != special_pin_net_idx_map.end()) { - return it->second; + if (idb_pin == nullptr) { + return -1; } - if (!idb_pin->is_io_pin()) { - idb::IdbSpecialNet* special_net = idb_design->findSpecialNetForInstancePin(idb_pin); - auto special_it = special_net_idx_map.find(special_net); - if (special_it != special_net_idx_map.end()) { - return special_it->second; - } + idb::IdbSpecialNet* special_net = idb_pin->is_io_pin() ? idb_pin->get_special_net() + : idb_design->findSpecialNetForInstancePin(idb_pin); + auto special_net_it = special_net_idx_map.find(special_net); + if (special_net_it != special_net_idx_map.end()) { + return special_net_it->second; } + if (!isSkipping(idb_pin->get_net())) { return static_cast(idb_pin->get_net()->get_id()); } @@ -1018,6 +1028,9 @@ std::vector DRCInterface::buildEnvShapeList() ids_shape.ur_y = rect->get_high_y(); ids_shape.layer_idx = obs_box->get_layer()->get_id(); ids_shape.is_routing = obs_box->get_layer()->is_routing(); + if (ids_shape.is_routing) { + obs_shape_idx_set.insert(env_shape_list.size()); + } env_shape_list.push_back(ids_shape); } } diff --git a/src/operation/iDRC/interface/DRCInterface.hpp b/src/operation/iDRC/interface/DRCInterface.hpp index 0a40a2574..357acfce3 100644 --- a/src/operation/iDRC/interface/DRCInterface.hpp +++ b/src/operation/iDRC/interface/DRCInterface.hpp @@ -118,6 +118,12 @@ class DRCInterface DRCInterface& operator=(const DRCInterface& other) = delete; DRCInterface& operator=(DRCInterface&& other) = delete; // function + std::vector buildEnvShapeList(std::set& obs_shape_idx_set); + std::vector getViolationList(const std::vector& ids_env_shape_list, + const std::vector& ids_result_shape_list, + const std::set& ids_check_type_set, + const std::vector& ids_check_region_list, + const std::set& obs_shape_idx_set); }; } // namespace idrc diff --git a/src/operation/iDRC/source/data_manager/advance/DRCShape.hpp b/src/operation/iDRC/source/data_manager/advance/DRCShape.hpp index 6b17b9c76..aadfa234e 100644 --- a/src/operation/iDRC/source/data_manager/advance/DRCShape.hpp +++ b/src/operation/iDRC/source/data_manager/advance/DRCShape.hpp @@ -35,14 +35,17 @@ class DRCShape : public LayerRect // getter int32_t get_net_idx() const { return _net_idx; } bool get_is_routing() const { return _is_routing; } + bool get_is_obs() const { return _is_obs; } // setter void set_net_idx(const int32_t net_idx) { _net_idx = net_idx; } void set_is_routing(const bool is_routing) { _is_routing = is_routing; } + void set_is_obs(const bool is_obs) { _is_obs = is_obs; } // function private: int32_t _net_idx = -1; bool _is_routing = true; + bool _is_obs = false; }; } // namespace idrc diff --git a/src/operation/iDRC/source/module/rule_validator/RuleValidator.cpp b/src/operation/iDRC/source/module/rule_validator/RuleValidator.cpp index 06e2780ae..5e10b6dd0 100644 --- a/src/operation/iDRC/source/module/rule_validator/RuleValidator.cpp +++ b/src/operation/iDRC/source/module/rule_validator/RuleValidator.cpp @@ -455,6 +455,14 @@ void RuleValidator::prepareRVCluster(RVCluster& rv_cluster) auto add_shape_to_layer_data = [&](DRCShape* drc_shape, bool is_env_shape) { GTLRectInt gtl_rect = DRCUTIL.convertToGTLRectInt(drc_shape->get_rect()); + if (drc_shape->get_is_routing()) { + RVLayerData& rv_layer_data = layer_data[drc_shape->get_layer_idx()]; + if (drc_shape->get_is_obs()) { + rv_layer_data.obs_rtree.insert(gtl_rect); + } else { + rv_layer_data.metal_rtree.insert({gtl_rect, drc_shape->get_net_idx()}); + } + } if (!drc_shape->get_is_routing()) { CutData cut_data; cut_data.rect = gtl_rect; diff --git a/src/operation/iDRC/source/module/rule_validator/rv_data_manager/RVLayerData.hpp b/src/operation/iDRC/source/module/rule_validator/rv_data_manager/RVLayerData.hpp index 7ea25500e..244358cf4 100644 --- a/src/operation/iDRC/source/module/rule_validator/rv_data_manager/RVLayerData.hpp +++ b/src/operation/iDRC/source/module/rule_validator/rv_data_manager/RVLayerData.hpp @@ -122,6 +122,8 @@ struct RVLayerData bgi::rtree, bgi::quadratic<16>> rect_rtrees; bgi::rtree, bgi::quadratic<16>> boundary_rtrees; bgi::rtree, CutDataIndexable> cut_rtrees; + bgi::rtree, bgi::quadratic<16>> metal_rtree; + bgi::rtree> obs_rtree; const CutData& getCut(int32_t cut_id) const { return cut_pool[cut_id]; } int32_t getCutId(const CutData& cut_data) const { return static_cast(&cut_data - cut_pool.data()); } @@ -182,6 +184,18 @@ struct RVLayerData rect_rtrees.query(bgi::intersects(query_rect), out); } + template + void queryMetalRects(const GTLRectInt& query_rect, OutputIt out) const + { + metal_rtree.query(bgi::intersects(query_rect), out); + } + + template + void queryObsRects(const GTLRectInt& query_rect, OutputIt out) const + { + obs_rtree.query(bgi::intersects(query_rect), out); + } + template void queryBoundaries(const GTLRectInt& query_rect, OutputIt out) const { diff --git a/src/operation/iDRC/source/module/rule_validator/rv_design_rule/MetalShort.cpp b/src/operation/iDRC/source/module/rule_validator/rv_design_rule/MetalShort.cpp index 3b710e88c..e540acca9 100644 --- a/src/operation/iDRC/source/module/rule_validator/rv_design_rule/MetalShort.cpp +++ b/src/operation/iDRC/source/module/rule_validator/rv_design_rule/MetalShort.cpp @@ -47,7 +47,8 @@ void RuleValidator::verifyMetalShort(RVCluster& rv_cluster) } const RVLayerData& rv_layer_data = layer_data_it->second; std::vector layer_violations; - std::vector> overlap_max_rects; + std::vector> overlap_metal_rects; + std::vector overlap_obs_rects; for (auto& [net_idx, polyset] : net_polyset) { std::vector rect_list; gtl::get_max_rectangles(rect_list, polyset); @@ -55,10 +56,9 @@ void RuleValidator::verifyMetalShort(RVCluster& rv_cluster) for (GTLRectInt& gtl_rect : rect_list) { PlanarRect rect = DRCUTIL.convertToPlanarRect(gtl_rect); - overlap_max_rects.clear(); - rv_layer_data.queryMaxRects(gtl_rect, std::back_inserter(overlap_max_rects)); - for (auto [env_gtl_rect, env_max_rect_id] : overlap_max_rects) { - int32_t env_net_idx = rv_layer_data.getNetIdxByMaxRectId(env_max_rect_id); + overlap_metal_rects.clear(); + rv_layer_data.queryMetalRects(gtl_rect, std::back_inserter(overlap_metal_rects)); + for (auto [env_gtl_rect, env_net_idx] : overlap_metal_rects) { if (net_idx == env_net_idx) { continue; } @@ -75,6 +75,23 @@ void RuleValidator::verifyMetalShort(RVCluster& rv_cluster) violation.set_required_size(0); layer_violations.push_back(std::move(violation)); } + + overlap_obs_rects.clear(); + rv_layer_data.queryObsRects(gtl_rect, std::back_inserter(overlap_obs_rects)); + for (const GTLRectInt& obs_gtl_rect : overlap_obs_rects) { + PlanarRect obs_rect = DRCUTIL.convertToPlanarRect(obs_gtl_rect); + if (!DRCUTIL.isOpenOverlap(rect, obs_rect)) { + continue; + } + Violation violation; + violation.set_violation_type(ViolationType::kMetalShort); + violation.set_is_routing(true); + violation.set_violation_net_set({net_idx, -1}); + violation.set_layer_idx(routing_layer_idx); + violation.set_rect(DRCUTIL.getOverlap(rect, obs_rect)); + violation.set_required_size(0); + layer_violations.push_back(std::move(violation)); + } } } diff --git a/src/operation/iRT/source/data_manager/advance/RoutingEdge.hpp b/src/operation/iRT/source/data_manager/advance/RoutingEdge.hpp index 99c5bd481..8d1d487b5 100644 --- a/src/operation/iRT/source/data_manager/advance/RoutingEdge.hpp +++ b/src/operation/iRT/source/data_manager/advance/RoutingEdge.hpp @@ -26,6 +26,7 @@ class RoutingEdge // getter int32_t get_supply() const { return _supply; } int32_t get_demand() const { return _demand; } + std::vector& get_demand_net_idx_list() { return _demand_net_idx_list; } std::set& get_ignore_net_set() { return _ignore_net_set; } double get_congestion_cost() const { return _congestion_cost; } int32_t get_overflow() const { return std::max(0, _demand - _supply); } @@ -38,6 +39,7 @@ class RoutingEdge private: int32_t _supply = 0; int32_t _demand = 0; + std::vector _demand_net_idx_list; std::set _ignore_net_set; double _congestion_cost = 0; }; diff --git a/src/operation/iRT/source/module/gds_plotter/GDSPlotter.cpp b/src/operation/iRT/source/module/gds_plotter/GDSPlotter.cpp index 6fc3c2982..653295f95 100644 --- a/src/operation/iRT/source/module/gds_plotter/GDSPlotter.cpp +++ b/src/operation/iRT/source/module/gds_plotter/GDSPlotter.cpp @@ -340,7 +340,8 @@ void GDSPlotter::buildGraphLypFile() {GPDataType::kKey, false}, {GPDataType::kGlobalPath, true}, {GPDataType::kDetailedPath, true}, {GPDataType::kPatch, true}, {GPDataType::kShape, true}, {GPDataType::kAccessPoint, false}, {GPDataType::kAxis, false}, {GPDataType::kOverflow, false}, {GPDataType::kRouteViolation, false}, - {GPDataType::kPatchViolation, false}}; + {GPDataType::kPatchViolation, false}, {GPDataType::kHEdgeAxis, false}, {GPDataType::kVEdgeAxis, false}, + {GPDataType::kHEdgeInfo, false}, {GPDataType::kVEdgeInfo, false}}; std::map cut_data_type_visible_map = {{GPDataType::kGlobalPath, true}, {GPDataType::kDetailedPath, true}, {GPDataType::kShape, true}}; // 0为base_region 最后一个为GCell 中间为cut+routing diff --git a/src/operation/iRT/source/module/gds_plotter/gp_data_manager/GPDataType.hpp b/src/operation/iRT/source/module/gds_plotter/gp_data_manager/GPDataType.hpp index fa85fe99e..283a85905 100644 --- a/src/operation/iRT/source/module/gds_plotter/gp_data_manager/GPDataType.hpp +++ b/src/operation/iRT/source/module/gds_plotter/gp_data_manager/GPDataType.hpp @@ -38,7 +38,11 @@ enum class GPDataType kAxis, kOverflow, kRouteViolation, - kPatchViolation + kPatchViolation, + kHEdgeAxis, + kVEdgeAxis, + kHEdgeInfo, + kVEdgeInfo }; struct GetGPDataTypeName @@ -95,6 +99,18 @@ struct GetGPDataTypeName case GPDataType::kPatchViolation: data_type_name = "patch_violation"; break; + case GPDataType::kHEdgeAxis: + data_type_name = "h_edge_axis"; + break; + case GPDataType::kVEdgeAxis: + data_type_name = "v_edge_axis"; + break; + case GPDataType::kHEdgeInfo: + data_type_name = "h_edge_info"; + break; + case GPDataType::kVEdgeInfo: + data_type_name = "v_edge_info"; + break; default: RTLOG.error(Loc::current(), "Unrecognized type!"); break; diff --git a/src/operation/iRT/source/module/layer_assigner/LayerAssigner.cpp b/src/operation/iRT/source/module/layer_assigner/LayerAssigner.cpp index fda41fe57..7c5684340 100644 --- a/src/operation/iRT/source/module/layer_assigner/LayerAssigner.cpp +++ b/src/operation/iRT/source/module/layer_assigner/LayerAssigner.cpp @@ -84,6 +84,7 @@ void LayerAssigner::clearRoutingEdgeDemand() for (int32_t x = 0; x < routing_edge_map.get_x_size(); x++) { for (int32_t y = 0; y < routing_edge_map.get_y_size(); y++) { routing_edge_map[x][y].set_demand(0); + routing_edge_map[x][y].get_demand_net_idx_list().clear(); } } } @@ -785,6 +786,16 @@ void LayerAssigner::updateRoutingTreeToGraph(LAModel& la_model, const RoutingSeg if (change_type == ChangeType::kDel && routing_edge.get_demand() <= 0) { RTLOG.error(Loc::current(), "The routing edge demand is error!"); } + std::vector& demand_net_idx_list = routing_edge.get_demand_net_idx_list(); + if (change_type == ChangeType::kAdd) { + demand_net_idx_list.push_back(curr_net_idx); + } else { + auto iter = std::find(demand_net_idx_list.begin(), demand_net_idx_list.end(), curr_net_idx); + if (iter == demand_net_idx_list.end()) { + RTLOG.error(Loc::current(), "The routing edge demand net is error!"); + } + demand_net_idx_list.erase(iter); + } routing_edge.set_demand(routing_edge.get_demand() + delta); } } diff --git a/src/operation/iRT/source/module/planar_router/PlanarRouter.cpp b/src/operation/iRT/source/module/planar_router/PlanarRouter.cpp index 6edcf4c98..09206e544 100644 --- a/src/operation/iRT/source/module/planar_router/PlanarRouter.cpp +++ b/src/operation/iRT/source/module/planar_router/PlanarRouter.cpp @@ -19,6 +19,8 @@ #include #include #include +#include +#include #include #include "GDSPlotter.hpp" @@ -30,6 +32,67 @@ namespace irt { +namespace { + +struct PRSegmentKey +{ + int32_t ll_x; + int32_t ll_y; + int32_t ur_x; + int32_t ur_y; + + bool operator==(const PRSegmentKey&) const = default; +}; + +struct PRSegmentKeyHash +{ + size_t operator()(const PRSegmentKey& key) const + { + size_t seed = 0; + for (int32_t value : {key.ll_x, key.ll_y, key.ur_x, key.ur_y}) { + seed ^= std::hash{}(value) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + } + return seed; + } +}; + +class PRTopologyCostCache +{ + public: + explicit PRTopologyCostCache(TBSegmentCostQuery cost_query) : _cost_query(std::move(cost_query)) {} + + double getCost(const PlanarCoord& first, const PlanarCoord& second) + { + if (first == second) { + return 0; + } + if (first.get_x() != second.get_x() && first.get_y() != second.get_y()) { + return std::numeric_limits::infinity(); + } + int64_t span = std::abs(static_cast(first.get_x()) - second.get_x()) + + std::abs(static_cast(first.get_y()) - second.get_y()); + if (span == 1) { + return _cost_query(first, second); + } + + PRSegmentKey key{std::min(first.get_x(), second.get_x()), std::min(first.get_y(), second.get_y()), + std::max(first.get_x(), second.get_x()), std::max(first.get_y(), second.get_y())}; + if (auto iter = _segment_cost_map.find(key); iter != _segment_cost_map.end()) { + return iter->second; + } + + double cost = _cost_query(first, second); + _segment_cost_map.emplace(key, cost); + return cost; + } + + private: + TBSegmentCostQuery _cost_query; + std::unordered_map _segment_cost_map; +}; + +} // namespace + // public void PlanarRouter::initInst() @@ -201,30 +264,20 @@ void PlanarRouter::buildPlanarRoutingEdgeMap() void PlanarRouter::initMacroGridRectList() { ScaleAxis& gcell_axis = RTDM.getDatabase().get_gcell_axis(); - GridMap& gcell_map = RTDM.getDatabase().get_gcell_map(); std::vector& macro_list = RTDM.getDatabase().get_macro_list(); _macro_grid_rect_list.clear(); - _macro_obs_rect_list.clear(); _macro_grid_rect_list.reserve(macro_list.size()); - _macro_obs_rect_list.reserve(macro_list.size()); for (Macro& macro : macro_list) { - PlanarRect macro_rect = RTUTIL.getClosedGCellGridRect(macro.get_body_rect(), gcell_axis); - _macro_grid_rect_list.push_back(macro_rect); - _macro_obs_rect_list.emplace_back(std::max(0, macro_rect.get_ll_x() - 1), std::max(0, macro_rect.get_ll_y() - 1), - std::min(gcell_map.get_x_size() - 1, macro_rect.get_ur_x() + 1), - std::min(gcell_map.get_y_size() - 1, macro_rect.get_ur_y() + 1)); + _macro_grid_rect_list.push_back(RTUTIL.getClosedGCellGridRect(macro.get_body_rect(), gcell_axis)); } } -PREdgeCost PlanarRouter::getRoutingEdgeCost(const RoutingEdge& routing_edge) +PREdgeCost PlanarRouter::getRoutingEdgeCost(int32_t supply, int32_t demand) { constexpr double saturation_start_ratio = 0.8; constexpr double hotspot_start_ratio = 0.9; - // Evaluate the edge after adding one unit of demand. Overflow uses a steep penalty; otherwise usage cost grows quartically. PREdgeCost edge_cost; - int32_t supply = routing_edge.get_supply(); - int32_t demand = routing_edge.get_demand() + 1; if (supply <= 0 || demand > supply) { double overflow_ratio = demand - std::max(supply, 0) + 1; double overflow_ratio_square = overflow_ratio * overflow_ratio; @@ -254,6 +307,64 @@ PREdgeCost PlanarRouter::getRoutingEdgeCost(const RoutingEdge& routing_edge) return edge_cost; } +PREdgeCost PlanarRouter::getRoutingEdgeCost(const RoutingEdge& routing_edge) +{ + return getRoutingEdgeCost(routing_edge.get_supply(), routing_edge.get_demand() + 1); +} + +double PlanarRouter::getTopologyEdgeCost(PRModel& pr_model, RoutingEdge& routing_edge) +{ + constexpr double wire_cost = 1; + PRNet* curr_net = pr_model.get_curr_pr_task(); + int32_t net_idx = curr_net->get_net_idx(); + if (routing_edge.get_ignore_net_set().contains(net_idx)) { + return wire_cost; + } + if (routing_edge.get_supply() <= 0) { + return std::numeric_limits::infinity(); + } + + int32_t effective_demand = routing_edge.get_demand() - curr_net->get_routing_edge_set().contains(&routing_edge); + effective_demand = std::max(0, effective_demand); + PREdgeCost edge_cost = getRoutingEdgeCost(routing_edge.get_supply(), effective_demand + 1); + return wire_cost + edge_cost.getTotalCost(pr_model.get_pr_com_param().get_overflow_unit(), routing_edge.get_congestion_cost()); +} + +double PlanarRouter::getTopologySegmentCost(PRModel& pr_model, const PlanarCoord& first_coord, const PlanarCoord& second_coord) +{ + if (first_coord == second_coord) { + return 0; + } + if (!RTUTIL.isRightAngled(first_coord, second_coord)) { + return std::numeric_limits::infinity(); + } + + GridMap& routing_h_edge_map = RTDM.getDatabase().get_planar_routing_h_edge_map(); + GridMap& routing_v_edge_map = RTDM.getDatabase().get_planar_routing_v_edge_map(); + bool is_horizontal = RTUTIL.isHorizontal(first_coord, second_coord); + int32_t first_x = std::min(first_coord.get_x(), second_coord.get_x()); + int32_t second_x = std::max(first_coord.get_x(), second_coord.get_x()); + int32_t first_y = std::min(first_coord.get_y(), second_coord.get_y()); + int32_t second_y = std::max(first_coord.get_y(), second_coord.get_y()); + double segment_cost = 0; + int32_t first_idx = is_horizontal ? first_x : first_y; + int32_t second_idx = is_horizontal ? second_x : second_y; + for (int32_t idx = first_idx; idx < second_idx; idx++) { + int32_t edge_x = is_horizontal ? idx : first_x; + int32_t edge_y = is_horizontal ? first_y : idx; + GridMap& edge_map = is_horizontal ? routing_h_edge_map : routing_v_edge_map; + if (!edge_map.isInside(edge_x, edge_y)) { + return std::numeric_limits::infinity(); + } + double edge_cost = getTopologyEdgeCost(pr_model, edge_map[edge_x][edge_y]); + if (!std::isfinite(edge_cost)) { + return edge_cost; + } + segment_cost += edge_cost; + } + return segment_cost; +} + void PlanarRouter::updateRoutingEdgeToGraph(RoutingEdge& routing_edge, PREdgeCost& edge_cost, int32_t curr_net_idx, ChangeType change_type, std::unordered_set& routing_edge_set) { @@ -275,6 +386,16 @@ void PlanarRouter::updateRoutingEdgeToGraph(RoutingEdge& routing_edge, PREdgeCos if (delta < 0 && routing_edge.get_demand() <= 0) { RTLOG.error(Loc::current(), "The planar routing edge demand is error!"); } + std::vector& demand_net_idx_list = routing_edge.get_demand_net_idx_list(); + if (delta > 0) { + demand_net_idx_list.push_back(curr_net_idx); + } else { + auto iter = std::find(demand_net_idx_list.begin(), demand_net_idx_list.end(), curr_net_idx); + if (iter == demand_net_idx_list.end()) { + RTLOG.error(Loc::current(), "The planar routing edge demand net is error!"); + } + demand_net_idx_list.erase(iter); + } routing_edge.set_demand(routing_edge.get_demand() + delta); edge_cost = getRoutingEdgeCost(routing_edge); } @@ -322,11 +443,11 @@ void PlanarRouter::runRouteFlow(PRModel& pr_model) constexpr int32_t partial_rip_up_guard = 1; std::vector& pr_task_list = pr_model.get_pr_task_list(); - routePRNetList(pr_model, pr_task_list, "initial LZ pattern", PRRouteMode::kLZPattern); + routePRNetList(pr_model, pr_task_list, "initial LZ pattern", PRRouteMode::kLZPattern, PRTopoMode::kNormal); updateCongestion(pr_model); - routePRNetList(pr_model, pr_task_list, "congestion LZ pattern", PRRouteMode::kLZPattern); + routePRNetList(pr_model, pr_task_list, "congestion LZ pattern", PRRouteMode::kLZPattern, PRTopoMode::kCongestion); updateCongestion(pr_model); - routePRNetList(pr_model, getOverflowPRNetList(pr_model), "overflow All pattern", PRRouteMode::kAllPattern); + routePRNetList(pr_model, getOverflowPRNetList(pr_model), "overflow All pattern", PRRouteMode::kAllPattern, PRTopoMode::kCongestion); updateCongestion(pr_model); for (int32_t iter = 0; iter < max_iter; iter++) { @@ -337,7 +458,7 @@ void PlanarRouter::runRouteFlow(PRModel& pr_model) bool is_partial_rip_up = enable_partial_rip_up && iter == 0; int32_t rip_up_guard = is_partial_rip_up ? partial_rip_up_guard : 0; - routePRNetList(pr_model, reroute_net_list, "overflow A*", PRRouteMode::kAStar, is_partial_rip_up, rip_up_guard); + routePRNetList(pr_model, reroute_net_list, "overflow A*", PRRouteMode::kAStar, PRTopoMode::kCongestion, is_partial_rip_up, rip_up_guard); updateCongestion(pr_model); auto& param = pr_model.get_pr_com_param(); param.set_astar_search_margin(param.get_astar_search_margin() * 2); @@ -350,13 +471,13 @@ void PlanarRouter::runRouteFlow(PRModel& pr_model) } void PlanarRouter::routePRNetList(PRModel& pr_model, const std::vector& pr_net_list, const char* route_mode, PRRouteMode pr_route_mode, - bool is_partial_rip_up, int32_t rip_up_guard) + PRTopoMode pr_topo_mode, bool is_partial_rip_up, int32_t rip_up_guard) { RTLOG.info(Loc::current(), "Mode: ", route_mode, ", net_num: ", pr_net_list.size()); size_t next_percent = 10; for (size_t i = 0; i < pr_net_list.size(); i++) { PRNet* pr_net = pr_net_list[i]; - routePRNet(pr_model, pr_net, pr_route_mode, is_partial_rip_up, rip_up_guard); + routePRNet(pr_model, pr_net, pr_route_mode, pr_topo_mode, is_partial_rip_up, rip_up_guard); size_t percent = ((i + 1) * 100) / pr_net_list.size(); if (percent >= next_percent || (i + 1) == pr_net_list.size()) { RTLOG.info(Loc::current(), "Mode: ", route_mode, ", progress: ", percent, "% (", (i + 1), "/", pr_net_list.size(), ")"); @@ -365,7 +486,8 @@ void PlanarRouter::routePRNetList(PRModel& pr_model, const std::vector& } } -void PlanarRouter::routePRNet(PRModel& pr_model, PRNet* pr_net, PRRouteMode pr_route_mode, bool is_partial_rip_up, int32_t rip_up_guard) +void PlanarRouter::routePRNet(PRModel& pr_model, PRNet* pr_net, PRRouteMode pr_route_mode, PRTopoMode pr_topo_mode, bool is_partial_rip_up, + int32_t rip_up_guard) { pr_model.set_curr_pr_task(pr_net); std::vector> old_routing_segment_list = pr_net->get_routing_segment_list(); @@ -389,7 +511,7 @@ void PlanarRouter::routePRNet(PRModel& pr_model, PRNet* pr_net, PRRouteMode pr_r // rip up all segments if (!is_partial_route) { - planar_topo_list = getPlanarTopoList(pr_model); + planar_topo_list = getPlanarTopoList(pr_model, pr_topo_mode); updateRoutingSegmentListToGraph(pr_model, old_routing_segment_list, ChangeType::kDel, pr_net->get_routing_edge_set()); } @@ -597,7 +719,6 @@ void PlanarRouter::splitLongPlanarTopoList(PRModel& pr_model, std::vector& gcell_map = RTDM.getDatabase().get_gcell_map(); std::vector> split_topo_list; split_topo_list.reserve(planar_topo_list.size() * 3); for (Segment& planar_topo : planar_topo_list) { @@ -617,43 +738,19 @@ void PlanarRouter::splitLongPlanarTopoList(PRModel& pr_model, std::vector(piece_num))), std::round(first_coord.get_y() + ((second_coord.get_y() - first_coord.get_y()) * i / static_cast(piece_num)))); - std::vector legal_candidate_list; - bool ideal_is_blocked = false; - for (const PlanarRect& obs_rect : _macro_obs_rect_list) { - if (!RTUTIL.isInside(obs_rect, ideal_coord)) { - continue; - } - ideal_is_blocked = true; - legal_candidate_list.emplace_back(obs_rect.get_ll_x() - 1, ideal_coord.get_y()); - legal_candidate_list.emplace_back(obs_rect.get_ur_x() + 1, ideal_coord.get_y()); - legal_candidate_list.emplace_back(ideal_coord.get_x(), obs_rect.get_ll_y() - 1); - legal_candidate_list.emplace_back(ideal_coord.get_x(), obs_rect.get_ur_y() + 1); - } - PlanarCoord legal_coord = ideal_coord; - int32_t min_distance = INT_MAX; - for (const PlanarCoord& candidate_coord : legal_candidate_list) { - if (candidate_coord.get_x() < 0 || gcell_map.get_x_size() <= candidate_coord.get_x() || candidate_coord.get_y() < 0 - || gcell_map.get_y_size() <= candidate_coord.get_y()) { - continue; - } - bool is_inside_obs = false; - for (const PlanarRect& obs_rect : _macro_obs_rect_list) { - if (RTUTIL.isInside(obs_rect, candidate_coord)) { - is_inside_obs = true; - break; - } - } - int32_t distance = RTUTIL.getManhattanDistance(ideal_coord, candidate_coord); - if (!is_inside_obs && distance < min_distance) { - legal_coord = candidate_coord; - min_distance = distance; + bool has_escape = false; + for (const PlanarCoord& neighbor : {PlanarCoord(ideal_coord.get_x() - 1, ideal_coord.get_y()), PlanarCoord(ideal_coord.get_x() + 1, ideal_coord.get_y()), + PlanarCoord(ideal_coord.get_x(), ideal_coord.get_y() - 1), PlanarCoord(ideal_coord.get_x(), ideal_coord.get_y() + 1)}) { + if (std::isfinite(getTopologySegmentCost(pr_model, ideal_coord, neighbor))) { + has_escape = true; + break; } } - if (ideal_is_blocked && min_distance == INT_MAX) { + if (!has_escape) { has_legal_split = false; break; } - legal_coord_list.push_back(legal_coord); + legal_coord_list.push_back(ideal_coord); } legal_coord_list.push_back(second_coord); @@ -774,7 +871,30 @@ std::vector PlanarRouter::getPRCandidateListByTopo(PRModel& pr_mode return pr_candidate_list; } -std::vector> PlanarRouter::getPlanarTopoList(PRModel& pr_model) +bool PlanarRouter::shouldUseCongestionFlute(PRModel& pr_model, size_t unique_pin_num) +{ + if (unique_pin_num < 3) { + return false; + } + PRNet* curr_net = pr_model.get_curr_pr_task(); + if (curr_net->get_routing_edge_set().empty()) { + return true; + } + double history_threshold = 0.64 * pr_model.get_pr_com_param().get_overflow_unit(); + for (RoutingEdge* routing_edge : curr_net->get_routing_edge_set()) { + if (routing_edge->get_ignore_net_set().contains(curr_net->get_net_idx())) { + continue; + } + int32_t supply = routing_edge->get_supply(); + if (supply <= 0 || routing_edge->get_demand() / static_cast(supply) >= 0.8 + || routing_edge->get_congestion_cost() >= history_threshold) { + return true; + } + } + return false; +} + +std::vector> PlanarRouter::getPlanarTopoList(PRModel& pr_model, PRTopoMode pr_topo_mode) { std::vector planar_coord_list; { @@ -787,9 +907,19 @@ std::vector> PlanarRouter::getPlanarTopoList(PRModel& pr_mo TBTask tb_task; tb_task.set_planar_coord_list(planar_coord_list); GridMap& gcell_map = RTDM.getDatabase().get_gcell_map(); - tb_task.set_planar_obs_list(_macro_obs_rect_list); tb_task.set_planar_search_region(PlanarRect(0, 0, gcell_map.get_x_size() - 1, gcell_map.get_y_size() - 1)); - + TBSegmentCostQuery segment_cost_query + = [this, &pr_model](const PlanarCoord& first, const PlanarCoord& second) { return getTopologySegmentCost(pr_model, first, second); }; + bool congestion_driven = pr_topo_mode == PRTopoMode::kCongestion && shouldUseCongestionFlute(pr_model, planar_coord_list.size()); + tb_task.set_congestion_driven(congestion_driven); + if (pr_topo_mode == PRTopoMode::kNormal) { + tb_task.set_segment_cost_query(std::move(segment_cost_query)); + return RTTB.getPlanarTopoList(tb_task); + } + + PRTopologyCostCache topology_cost_cache(std::move(segment_cost_query)); + tb_task.set_segment_cost_query( + [&topology_cost_cache](const PlanarCoord& first, const PlanarCoord& second) { return topology_cost_cache.getCost(first, second); }); return RTTB.getPlanarTopoList(tb_task); } @@ -1640,26 +1770,32 @@ void PlanarRouter::debugPlotPRModel(PRModel& pr_model, std::string flag) gp_gds.addStruct(base_region_struct); } - // gcell_axis + // edge_axis { - GPStruct gcell_axis_struct("gcell_axis"); - std::vector gcell_x_list = RTUTIL.getScaleList(die.get_real_ll_x(), die.get_real_ur_x(), gcell_axis.get_x_grid_list()); - std::vector gcell_y_list = RTUTIL.getScaleList(die.get_real_ll_y(), die.get_real_ur_y(), gcell_axis.get_y_grid_list()); - for (int32_t x : gcell_x_list) { - GPPath gp_path; - gp_path.set_layer_idx(0); - gp_path.set_data_type(1); - gp_path.set_segment(x, die.get_real_ll_y(), x, die.get_real_ur_y()); - gcell_axis_struct.push(gp_path); - } - for (int32_t y : gcell_y_list) { - GPPath gp_path; - gp_path.set_layer_idx(0); - gp_path.set_data_type(1); - gp_path.set_segment(die.get_real_ll_x(), y, die.get_real_ur_x(), y); - gcell_axis_struct.push(gp_path); - } - gp_gds.addStruct(gcell_axis_struct); + for (std::pair*, bool> edge_map_pair : {std::make_pair(&RTDM.getDatabase().get_planar_routing_h_edge_map(), true), + std::make_pair(&RTDM.getDatabase().get_planar_routing_v_edge_map(), false)}) { + GPStruct edge_axis_struct(edge_map_pair.second ? "h_edge_axis" : "v_edge_axis"); + GridMap& routing_edge_map = *edge_map_pair.first; + for (int32_t x = 0; x < routing_edge_map.get_x_size(); x++) { + for (int32_t y = 0; y < routing_edge_map.get_y_size(); y++) { + PlanarCoord first_grid_coord(x, y); + PlanarCoord second_grid_coord = edge_map_pair.second ? PlanarCoord(x + 1, y) : PlanarCoord(x, y + 1); + PlanarRect first_real_rect = RTUTIL.getRealRectByGCell(first_grid_coord, gcell_axis); + PlanarRect second_real_rect = RTUTIL.getRealRectByGCell(second_grid_coord, gcell_axis); + PlanarCoord first_coord = first_real_rect.getMidPoint(); + PlanarCoord second_coord = second_real_rect.getMidPoint(); + PlanarRect edge_rect = edge_map_pair.second ? PlanarRect(first_coord.get_x(), first_real_rect.get_ll_y(), second_coord.get_x(), first_real_rect.get_ur_y()) + : PlanarRect(first_real_rect.get_ll_x(), first_coord.get_y(), first_real_rect.get_ur_x(), second_coord.get_y()); + + GPBoundary gp_boundary; + gp_boundary.set_layer_idx(RTGP.getGDSIdxByRouting(0)); + gp_boundary.set_data_type(static_cast(edge_map_pair.second ? GPDataType::kHEdgeAxis : GPDataType::kVEdgeAxis)); + gp_boundary.set_rect(edge_rect); + edge_axis_struct.push(gp_boundary); + } + } + gp_gds.addStruct(edge_axis_struct); + } } // track_axis_struct @@ -1722,24 +1858,64 @@ void PlanarRouter::debugPlotPRModel(PRModel& pr_model, std::string flag) gp_gds.addStruct(access_point_struct); } - // routing result - for (auto& [net_idx, segment_set] : pr_model.get_net_global_result_map()) { - GPStruct global_result_struct(RTUTIL.getString("global_result(net_", net_idx, ")")); - for (Segment& segment_value : segment_set) { - Segment* segment = &segment_value; - for (NetShape& net_shape : RTDM.getNetGlobalShapeList(net_idx, *segment)) { + // routing_edge + { + for (std::pair*, bool> edge_map_pair : {std::make_pair(&RTDM.getDatabase().get_planar_routing_h_edge_map(), true), + std::make_pair(&RTDM.getDatabase().get_planar_routing_v_edge_map(), false)}) { + GPStruct routing_edge_struct(edge_map_pair.second ? "h_edge_info" : "v_edge_info"); + GridMap& routing_edge_map = *edge_map_pair.first; + for (int32_t x = 0; x < routing_edge_map.get_x_size(); x++) { + for (int32_t y = 0; y < routing_edge_map.get_y_size(); y++) { + RoutingEdge& routing_edge = routing_edge_map[x][y]; + PlanarCoord first_grid_coord(x, y); + PlanarCoord second_grid_coord = edge_map_pair.second ? PlanarCoord(x + 1, y) : PlanarCoord(x, y + 1); + PlanarRect first_real_rect = RTUTIL.getRealRectByGCell(first_grid_coord, gcell_axis); + PlanarRect second_real_rect = RTUTIL.getRealRectByGCell(second_grid_coord, gcell_axis); + PlanarCoord first_coord = first_real_rect.getMidPoint(); + PlanarCoord second_coord = second_real_rect.getMidPoint(); + PlanarRect edge_rect = edge_map_pair.second ? PlanarRect(first_coord.get_x(), first_real_rect.get_ll_y(), second_coord.get_x(), first_real_rect.get_ur_y()) + : PlanarRect(first_real_rect.get_ll_x(), first_coord.get_y(), first_real_rect.get_ur_x(), second_coord.get_y()); + + int32_t info_data_type = static_cast(edge_map_pair.second ? GPDataType::kHEdgeInfo : GPDataType::kVEdgeInfo); GPBoundary gp_boundary; - gp_boundary.set_data_type(static_cast(GPDataType::kGlobalPath)); - gp_boundary.set_rect(net_shape.get_rect()); - if (net_shape.get_is_routing()) { - gp_boundary.set_layer_idx(RTGP.getGDSIdxByRouting(net_shape.get_layer_idx())); - } else { - gp_boundary.set_layer_idx(RTGP.getGDSIdxByCut(net_shape.get_layer_idx())); - } - global_result_struct.push(gp_boundary); + gp_boundary.set_layer_idx(RTGP.getGDSIdxByRouting(0)); + gp_boundary.set_data_type(info_data_type); + gp_boundary.set_rect(edge_rect); + routing_edge_struct.push(gp_boundary); + + std::string ignore_net_message; + for (int32_t net_idx : routing_edge.get_ignore_net_set()) { + ignore_net_message += RTUTIL.getString(ignore_net_message.empty() ? "" : ",", net_idx); + } + std::string demand_net_message; + for (int32_t net_idx : routing_edge.get_demand_net_idx_list()) { + demand_net_message += RTUTIL.getString(demand_net_message.empty() ? "" : ",", net_idx); + } + std::vector message_list; + message_list.push_back(RTUTIL.getString(edge_map_pair.second ? "H" : "V", " (", first_grid_coord.get_x(), ",", first_grid_coord.get_y(), ")-(", + second_grid_coord.get_x(), ",", second_grid_coord.get_y(), ")")); + message_list.push_back(RTUTIL.getString("demand: ", routing_edge.get_demand())); + message_list.push_back(RTUTIL.getString("demand_net: [", demand_net_message, "]")); + message_list.push_back(RTUTIL.getString("supply: ", routing_edge.get_supply())); + message_list.push_back(RTUTIL.getString("ignore_net: [", ignore_net_message, "]")); + message_list.push_back(RTUTIL.getString("congestion_cost: ", routing_edge.get_congestion_cost())); + + int32_t text_y = edge_rect.get_ur_y(); + int32_t y_reduced_span = std::max(1, edge_rect.getYSpan() / 7); + for (std::string& message : message_list) { + text_y -= y_reduced_span; + GPText gp_text; + gp_text.set_coord(edge_rect.get_ll_x(), text_y); + gp_text.set_text_type(info_data_type); + gp_text.set_message(message); + gp_text.set_layer_idx(RTGP.getGDSIdxByRouting(0)); + gp_text.set_presentation(GPTextPresentation::kLeftMiddle); + routing_edge_struct.push(gp_text); + } } } - gp_gds.addStruct(global_result_struct); + gp_gds.addStruct(routing_edge_struct); + } } // routing result diff --git a/src/operation/iRT/source/module/planar_router/PlanarRouter.hpp b/src/operation/iRT/source/module/planar_router/PlanarRouter.hpp index da8cc6b75..4a4b722bc 100644 --- a/src/operation/iRT/source/module/planar_router/PlanarRouter.hpp +++ b/src/operation/iRT/source/module/planar_router/PlanarRouter.hpp @@ -46,6 +46,12 @@ enum class PRRouteMode kAStar }; +enum class PRTopoMode +{ + kNormal, + kCongestion +}; + struct PROverflowTask { std::vector> kept_segment_list; @@ -123,7 +129,10 @@ class PlanarRouter void initMacroGridRectList(); // routing edge + PREdgeCost getRoutingEdgeCost(int32_t supply, int32_t demand); PREdgeCost getRoutingEdgeCost(const RoutingEdge& routing_edge); + double getTopologyEdgeCost(PRModel& pr_model, RoutingEdge& routing_edge); + double getTopologySegmentCost(PRModel& pr_model, const PlanarCoord& first_coord, const PlanarCoord& second_coord); void updateRoutingEdgeToGraph(RoutingEdge& routing_edge, PREdgeCost& edge_cost, int32_t curr_net_idx, ChangeType change_type, std::unordered_set& routing_edge_set); void updateRoutingSegmentListToGraph(PRModel& pr_model, std::span> routing_segment_list, ChangeType change_type, @@ -132,8 +141,9 @@ class PlanarRouter // routing flow void runRouteFlow(PRModel& pr_model); void routePRNetList(PRModel& pr_model, const std::vector& pr_net_list, const char* route_mode, PRRouteMode pr_route_mode, - bool is_partial_rip_up = false, int32_t rip_up_guard = 0); - void routePRNet(PRModel& pr_model, PRNet* pr_net, PRRouteMode pr_route_mode, bool is_partial_rip_up, int32_t rip_up_guard); + PRTopoMode pr_topo_mode, bool is_partial_rip_up = false, int32_t rip_up_guard = 0); + void routePRNet(PRModel& pr_model, PRNet* pr_net, PRRouteMode pr_route_mode, PRTopoMode pr_topo_mode, bool is_partial_rip_up, + int32_t rip_up_guard); void splitLongPlanarTopoList(PRModel& pr_model, std::vector>& planar_topo_list); bool routePlanarTopoList(PRModel& pr_model, std::vector>& planar_topo_list, PRRouteMode pr_route_mode, std::vector>& routing_segment_list); @@ -142,7 +152,8 @@ class PlanarRouter PROverflowTask getOverflowTask(PRModel& pr_model, int32_t rip_up_guard); bool isBetterCandidate(PRModel& pr_model, const PRCandidate& candidate, const PRCandidate& best_candidate); std::vector getPRCandidateListByTopo(PRModel& pr_model, Segment& planar_topo, PRRouteMode pr_route_mode); - std::vector> getPlanarTopoList(PRModel& pr_model); + bool shouldUseCongestionFlute(PRModel& pr_model, size_t unique_pin_num); + std::vector> getPlanarTopoList(PRModel& pr_model, PRTopoMode pr_topo_mode); // A* route std::vector> getRoutingSegmentListByAStar(PRModel& pr_model, const Segment& planar_topo, @@ -187,7 +198,6 @@ class PlanarRouter GridMap _routing_h_edge_cost_map; GridMap _routing_v_edge_cost_map; std::vector _macro_grid_rect_list; - std::vector _macro_obs_rect_list; }; } // namespace irt diff --git a/src/operation/iRT/source/module/topo_builder/TOPOBuilder.cpp b/src/operation/iRT/source/module/topo_builder/TOPOBuilder.cpp index 18e57b429..d51c2e975 100644 --- a/src/operation/iRT/source/module/topo_builder/TOPOBuilder.cpp +++ b/src/operation/iRT/source/module/topo_builder/TOPOBuilder.cpp @@ -14,222 +14,912 @@ // // See the Mulan PSL v2 for more details. // *************************************************************************************** + #include "TOPOBuilder.hpp" -#include "Utility.hpp" +#include +#include +#include +#include +#include +#include +#include + +#include "Logger.hpp" +#include "Monitor.hpp" #include "flute3/flute.h" namespace irt { -// public +namespace { -void TOPOBuilder::initInst() +using PlanarTopo = std::vector>; +using NeighborList = std::vector>; + +constexpr double kCostEpsilon = 1e-9; +constexpr double kMaxWarpStretch = 8.0; +constexpr int64_t kWarpScale = 100; +constexpr int32_t kMaxAxisSampleNum = 64; +constexpr int32_t kMaxThreePinAxisNum = 32; +constexpr int32_t kMaxThreePinCandidateNum = 4096; +constexpr int32_t kThreePinExtraRadius = 2; +constexpr int32_t kMaxRefineAxisNum = 8; +constexpr int32_t kMaxRefinePassNum = 2; + +enum class TBAxis { - if (_tb_instance == nullptr) { - _tb_instance = new TOPOBuilder(); + kX, + kY +}; + +struct TBGapCostStat +{ + long double finite_cost_sum = 0; + int64_t edge_num = 0; + int64_t inf_edge_num = 0; +}; + +struct TBAxisCostStat +{ + std::vector gap_stat_list; + double min_positive_cost = std::numeric_limits::infinity(); +}; + +struct TBTopoCandidate +{ + PlanarTopo topo_list; + TBRefineStat refine_stat; + double cost = std::numeric_limits::infinity(); +}; + +struct TBSteinerShift +{ + int32_t first_idx = -1; + int32_t second_idx = -1; + PlanarCoord first_coord; + PlanarCoord second_coord; + double gain = -1; + + bool isValid() const { return first_idx >= 0; } +}; + +struct TBRefineScore +{ + int32_t inf_edge_num = 0; + double finite_cost = 0; + int64_t wire_length = 0; +}; + +int32_t getBranchNum(const Flute::Tree& tree) +{ + return std::max(0, 2 * tree.deg - 2); +} + +PlanarCoord getBranchCoord(const Flute::Tree& tree, int32_t branch_idx) +{ + return PlanarCoord(tree.branch[branch_idx].x, tree.branch[branch_idx].y); +} + +void setBranchCoord(Flute::Tree& tree, int32_t branch_idx, const PlanarCoord& coord) +{ + tree.branch[branch_idx].x = coord.get_x(); + tree.branch[branch_idx].y = coord.get_y(); +} + +double getSegmentCost(const TBTask& task, const PlanarCoord& first, const PlanarCoord& second) +{ + if (first == second) { + return 0; + } + if (first.get_x() != second.get_x() && first.get_y() != second.get_y()) { + return std::numeric_limits::infinity(); } + if (!task.has_segment_cost_query()) { + return std::abs(first.get_x() - second.get_x()) + std::abs(first.get_y() - second.get_y()); + } + double cost = task.get_segment_cost(first, second); + return std::isnan(cost) || cost < 0 ? std::numeric_limits::infinity() : cost; } -TOPOBuilder& TOPOBuilder::getInst() +double getBendCost(const TBTask& task, const PlanarCoord& first, const PlanarCoord& bend, const PlanarCoord& second) { - if (_tb_instance == nullptr) { - RTLOG.error(Loc::current(), "The instance not initialized!"); + double cost = getSegmentCost(task, first, bend); + if (!std::isfinite(cost)) { + return std::numeric_limits::infinity(); } - return *_tb_instance; + double second_cost = getSegmentCost(task, bend, second); + return std::isfinite(second_cost) ? cost + second_cost : std::numeric_limits::infinity(); } -void TOPOBuilder::destroyInst() +double getPatternCost(const TBTask& task, const PlanarCoord& first, const PlanarCoord& second) { - if (_tb_instance != nullptr) { - delete _tb_instance; - _tb_instance = nullptr; + if (first == second) { + return 0; } + if (first.get_x() == second.get_x() || first.get_y() == second.get_y()) { + return getSegmentCost(task, first, second); + } + PlanarCoord x_bend(second.get_x(), first.get_y()); + PlanarCoord y_bend(first.get_x(), second.get_y()); + return std::min(getBendCost(task, first, x_bend, second), getBendCost(task, first, y_bend, second)); } -// function +bool isInsideSearchRegion(const TBTask& task, const PlanarCoord& coord) +{ + if (!task.has_planar_search_region()) { + return true; + } + const PlanarRect& region = task.get_planar_search_region(); + return region.get_ll_x() <= coord.get_x() && coord.get_x() <= region.get_ur_x() && region.get_ll_y() <= coord.get_y() + && coord.get_y() <= region.get_ur_y(); +} -void TOPOBuilder::init() +NeighborList getNeighborList(const Flute::Tree& tree) { - Monitor monitor; - RTLOG.info(Loc::current(), "Starting..."); + int32_t branch_num = getBranchNum(tree); + NeighborList neighbor_list(branch_num); + for (int32_t i = 0; i < branch_num; i++) { + int32_t neighbor_idx = tree.branch[i].n; + if (neighbor_idx < 0 || branch_num <= neighbor_idx || neighbor_idx == i) { + continue; + } + neighbor_list[i].push_back(neighbor_idx); + neighbor_list[neighbor_idx].push_back(i); + } + return neighbor_list; +} - Flute::readLUT(); +double getIncidentEdgeCost(const TBTask& task, const Flute::Tree& tree, const NeighborList& neighbor_list, int32_t first_idx, int32_t second_idx, + const PlanarCoord& first_coord, const PlanarCoord& second_coord) +{ + double cost = getPatternCost(task, first_coord, second_coord); + if (!std::isfinite(cost)) { + return cost; + } + for (int32_t neighbor_idx : neighbor_list[first_idx]) { + if (neighbor_idx != second_idx) { + double pattern_cost = getPatternCost(task, first_coord, getBranchCoord(tree, neighbor_idx)); + if (!std::isfinite(pattern_cost)) { + return pattern_cost; + } + cost += pattern_cost; + } + } + for (int32_t neighbor_idx : neighbor_list[second_idx]) { + if (neighbor_idx != first_idx) { + double pattern_cost = getPatternCost(task, second_coord, getBranchCoord(tree, neighbor_idx)); + if (!std::isfinite(pattern_cost)) { + return pattern_cost; + } + cost += pattern_cost; + } + } + return cost; +} - RTLOG.info(Loc::current(), "Completed", monitor.getStatsInfo()); +bool isStrictlyBetterCost(double current_cost, double candidate_cost) +{ + return std::isfinite(candidate_cost) && (!std::isfinite(current_cost) || candidate_cost + kCostEpsilon < current_cost); } -std::vector> TOPOBuilder::getPlanarTopoList(const TBTask& tb_task) +std::pair getBranchShiftRange(const Flute::Tree& tree, const NeighborList& neighbor_list, int32_t branch_idx, + bool is_horizontal) { - TBSteinerRepairStat steiner_repair_stat; - return getPlanarTopoList(tb_task, steiner_repair_stat); + PlanarCoord branch_coord = getBranchCoord(tree, branch_idx); + int32_t lower = is_horizontal ? branch_coord.get_y() : branch_coord.get_x(); + int32_t upper = lower; + for (int32_t neighbor_idx : neighbor_list[branch_idx]) { + PlanarCoord neighbor_coord = getBranchCoord(tree, neighbor_idx); + int32_t value = is_horizontal ? neighbor_coord.get_y() : neighbor_coord.get_x(); + lower = std::min(lower, value); + upper = std::max(upper, value); + } + return {lower, upper}; } -std::vector> TOPOBuilder::getPlanarTopoList(const TBTask& tb_task, TBSteinerRepairStat& steiner_repair_stat) +void setShiftCoord(PlanarCoord& coord, bool is_horizontal, int32_t value) { - steiner_repair_stat = {}; - std::vector> raw_topo_list = getFlutePlanarTopoList(tb_task.get_planar_coord_list()); - return legalizePlanarTopo(tb_task, std::move(raw_topo_list), steiner_repair_stat); + if (is_horizontal) { + coord.set_y(value); + } else { + coord.set_x(value); + } } -void TOPOBuilder::destroy() +bool shiftBestSteinerEdge(const TBTask& task, Flute::Tree& tree) { - Monitor monitor; - RTLOG.info(Loc::current(), "Starting..."); + NeighborList neighbor_list = getNeighborList(tree); + TBSteinerShift best_shift; - Flute::deleteLUT(); + for (int32_t first_idx = tree.deg; first_idx < getBranchNum(tree); first_idx++) { + int32_t second_idx = tree.branch[first_idx].n; + if (second_idx < tree.deg || second_idx == first_idx) { + continue; + } + PlanarCoord first_coord = getBranchCoord(tree, first_idx); + PlanarCoord second_coord = getBranchCoord(tree, second_idx); + bool is_horizontal = first_coord.get_y() == second_coord.get_y() && first_coord.get_x() != second_coord.get_x(); + bool is_vertical = first_coord.get_x() == second_coord.get_x() && first_coord.get_y() != second_coord.get_y(); + if (!is_horizontal && !is_vertical) { + continue; + } - RTLOG.info(Loc::current(), "Completed", monitor.getStatsInfo()); + auto [first_lower, first_upper] = getBranchShiftRange(tree, neighbor_list, first_idx, is_horizontal); + auto [second_lower, second_upper] = getBranchShiftRange(tree, neighbor_list, second_idx, is_horizontal); + int32_t lower = std::max(first_lower, second_lower); + int32_t upper = std::min(first_upper, second_upper); + double current_cost = getIncidentEdgeCost(task, tree, neighbor_list, first_idx, second_idx, first_coord, second_coord); + for (int32_t value = lower; value <= upper; value++) { + PlanarCoord candidate_first = first_coord; + PlanarCoord candidate_second = second_coord; + setShiftCoord(candidate_first, is_horizontal, value); + setShiftCoord(candidate_second, is_horizontal, value); + if (candidate_first == first_coord || !isInsideSearchRegion(task, candidate_first) || !isInsideSearchRegion(task, candidate_second)) { + continue; + } + double candidate_cost = getIncidentEdgeCost(task, tree, neighbor_list, first_idx, second_idx, candidate_first, candidate_second); + if (!isStrictlyBetterCost(current_cost, candidate_cost)) { + continue; + } + double gain = std::isfinite(current_cost) ? current_cost - candidate_cost : std::numeric_limits::infinity(); + if (!best_shift.isValid() || gain > best_shift.gain + kCostEpsilon) { + best_shift = {first_idx, second_idx, candidate_first, candidate_second, gain}; + } + } + } + if (best_shift.isValid()) { + setBranchCoord(tree, best_shift.first_idx, best_shift.first_coord); + setBranchCoord(tree, best_shift.second_idx, best_shift.second_coord); + } + return best_shift.isValid(); } -// private +int32_t compareRefineScore(const TBRefineScore& first, const TBRefineScore& second) +{ + if (first.inf_edge_num != second.inf_edge_num) { + return first.inf_edge_num < second.inf_edge_num ? -1 : 1; + } + if (std::abs(first.finite_cost - second.finite_cost) > kCostEpsilon) { + return first.finite_cost < second.finite_cost ? -1 : 1; + } + if (first.wire_length != second.wire_length) { + return first.wire_length < second.wire_length ? -1 : 1; + } + return 0; +} -TOPOBuilder* TOPOBuilder::_tb_instance = nullptr; +std::vector getRefineAxisList(int32_t lower, int32_t upper, int32_t current, const std::vector& neighbor_axis_list) +{ + std::set axis_set{std::clamp(current, lower, upper)}; + for (int32_t value : neighbor_axis_list) { + if (lower <= value && value <= upper && axis_set.size() < kMaxRefineAxisNum) { + axis_set.insert(value); + } + } + int32_t sample_num = kMaxRefineAxisNum - static_cast(axis_set.size()); + int64_t span = static_cast(upper) - lower; + for (int32_t sample_idx = 0; sample_idx < sample_num; sample_idx++) { + int64_t offset = sample_num <= 1 ? span / 2 : span * sample_idx / (sample_num - 1); + axis_set.insert(static_cast(lower + offset)); + } + return {axis_set.begin(), axis_set.end()}; +} + +TBRefineScore getSteinerScore(const TBTask& task, const Flute::Tree& tree, const NeighborList& neighbor_list, + const std::vector& branch_idx_list, const PlanarCoord& candidate) +{ + std::set branch_idx_set(branch_idx_list.begin(), branch_idx_list.end()); + std::set> edge_set; + TBRefineScore score; + for (int32_t branch_idx : branch_idx_list) { + for (int32_t neighbor_idx : neighbor_list[branch_idx]) { + if (!branch_idx_set.contains(neighbor_idx)) { + edge_set.emplace(std::min(branch_idx, neighbor_idx), std::max(branch_idx, neighbor_idx)); + } + } + } + for (const auto& [first_idx, second_idx] : edge_set) { + int32_t neighbor_idx = branch_idx_set.contains(first_idx) ? second_idx : first_idx; + PlanarCoord neighbor = getBranchCoord(tree, neighbor_idx); + double cost = getPatternCost(task, candidate, neighbor); + if (std::isfinite(cost)) { + score.finite_cost += cost; + } else { + score.inf_edge_num++; + } + score.wire_length += std::abs(static_cast(candidate.get_x()) - neighbor.get_x()) + + std::abs(static_cast(candidate.get_y()) - neighbor.get_y()); + } + return score; +} + +std::vector getSteinerCandidateList(const TBTask& task, const Flute::Tree& tree, const NeighborList& neighbor_list, + const std::vector& branch_idx_list) +{ + PlanarCoord current = getBranchCoord(tree, branch_idx_list.front()); + int32_t ll_x = current.get_x(); + int32_t ur_x = current.get_x(); + int32_t ll_y = current.get_y(); + int32_t ur_y = current.get_y(); + for (const PlanarCoord& terminal : task.get_planar_coord_list()) { + ll_x = std::min(ll_x, terminal.get_x()); + ur_x = std::max(ur_x, terminal.get_x()); + ll_y = std::min(ll_y, terminal.get_y()); + ur_y = std::max(ur_y, terminal.get_y()); + } + if (task.has_planar_search_region()) { + const PlanarRect& region = task.get_planar_search_region(); + ll_x = std::max(ll_x, region.get_ll_x()); + ur_x = std::min(ur_x, region.get_ur_x()); + ll_y = std::max(ll_y, region.get_ll_y()); + ur_y = std::min(ur_y, region.get_ur_y()); + } + + std::vector neighbor_x_list; + std::vector neighbor_y_list; + for (int32_t branch_idx : branch_idx_list) { + for (int32_t neighbor_idx : neighbor_list[branch_idx]) { + PlanarCoord neighbor = getBranchCoord(tree, neighbor_idx); + neighbor_x_list.push_back(neighbor.get_x()); + neighbor_y_list.push_back(neighbor.get_y()); + } + } + std::vector x_list = getRefineAxisList(ll_x, ur_x, current.get_x(), neighbor_x_list); + std::vector y_list = getRefineAxisList(ll_y, ur_y, current.get_y(), neighbor_y_list); + std::vector candidate_list; + candidate_list.reserve(x_list.size() * y_list.size()); + for (int32_t x : x_list) { + for (int32_t y : y_list) { + PlanarCoord candidate(x, y); + if (candidate != current && isInsideSearchRegion(task, candidate)) { + candidate_list.push_back(candidate); + } + } + } + return candidate_list; +} + +void refineSteinerByCost(const TBTask& task, Flute::Tree& tree, TBRefineStat& stat) +{ + for (int32_t pass = 0; pass < kMaxRefinePassNum; pass++) { + NeighborList neighbor_list = getNeighborList(tree); + std::map, CmpPlanarCoordByXASC> coord_branch_map; + for (int32_t branch_idx = tree.deg; branch_idx < getBranchNum(tree); branch_idx++) { + coord_branch_map[getBranchCoord(tree, branch_idx)].push_back(branch_idx); + } + bool moved = false; + for (const auto& [current, branch_idx_list] : coord_branch_map) { + if (getBranchCoord(tree, branch_idx_list.front()) != current) { + continue; + } + TBRefineScore best_score = getSteinerScore(task, tree, neighbor_list, branch_idx_list, current); + PlanarCoord best_coord = current; + for (const PlanarCoord& candidate : getSteinerCandidateList(task, tree, neighbor_list, branch_idx_list)) { + TBRefineScore candidate_score = getSteinerScore(task, tree, neighbor_list, branch_idx_list, candidate); + int32_t score_cmp = compareRefineScore(candidate_score, best_score); + if (score_cmp < 0 || (score_cmp == 0 && best_coord != current && CmpPlanarCoordByXASC()(candidate, best_coord))) { + best_score = candidate_score; + best_coord = candidate; + } + } + if (best_coord == current) { + continue; + } + for (int32_t branch_idx : branch_idx_list) { + setBranchCoord(tree, branch_idx, best_coord); + } + stat.refined_steiner_num++; + moved = true; + } + if (!moved) { + break; + } + } +} + +void refineFluteTree(const TBTask& task, Flute::Tree& tree, TBRefineStat& stat, bool enable_steiner_refine) +{ + if (!task.has_segment_cost_query()) { + return; + } + int32_t max_shift_num = std::max(0, 2 * (tree.deg - 2)); + while (stat.shifted_edge_num < max_shift_num && shiftBestSteinerEdge(task, tree)) { + stat.shifted_edge_num++; + } + if (enable_steiner_refine) { + refineSteinerByCost(task, tree, stat); + } +} + +std::vector getUniqueAxisList(const std::vector& coord_list, TBAxis axis) +{ + std::vector axis_list; + axis_list.reserve(coord_list.size()); + for (const PlanarCoord& coord : coord_list) { + axis_list.push_back(axis == TBAxis::kX ? coord.get_x() : coord.get_y()); + } + std::ranges::sort(axis_list); + axis_list.erase(std::ranges::unique(axis_list).begin(), axis_list.end()); + return axis_list; +} + +std::vector getSampleCoordList(const std::vector& axis) +{ + int64_t span = static_cast(axis.back()) - axis.front(); + int32_t sample_num = static_cast(std::min(span + 1, kMaxAxisSampleNum)); + std::vector sample_list; + sample_list.reserve(sample_num); + for (int32_t sample_idx = 0; sample_idx < sample_num; sample_idx++) { + int64_t offset = sample_num == 1 ? 0 : span * sample_idx / (sample_num - 1); + sample_list.push_back(static_cast(axis.front() + offset)); + } + return sample_list; +} -std::vector> TOPOBuilder::getFlutePlanarTopoList(const std::vector& planar_coord_list) +TBAxisCostStat getAxisCostStat(const TBTask& task, const std::vector& axis, const std::vector& orth_axis, TBAxis direction) { - std::vector> planar_topo_list; - if (planar_coord_list.size() <= 1) { - return planar_topo_list; + TBAxisCostStat stat; + if (axis.size() <= 1 || orth_axis.empty()) { + return stat; } + size_t gap_num = axis.size() - 1; + stat.gap_stat_list.resize(gap_num); + std::vector sample_coord_list = getSampleCoordList(orth_axis); + bool is_horizontal = direction == TBAxis::kX; + for (size_t gap_idx = 0; gap_idx < gap_num; gap_idx++) { + TBGapCostStat& gap_stat = stat.gap_stat_list[gap_idx]; + for (int64_t axis_coord = axis[gap_idx]; axis_coord < axis[gap_idx + 1]; axis_coord++) { + for (int32_t orth_coord : sample_coord_list) { + PlanarCoord first = is_horizontal ? PlanarCoord(static_cast(axis_coord), orth_coord) + : PlanarCoord(orth_coord, static_cast(axis_coord)); + PlanarCoord second = is_horizontal ? PlanarCoord(static_cast(axis_coord + 1), orth_coord) + : PlanarCoord(orth_coord, static_cast(axis_coord + 1)); + double cost = getSegmentCost(task, first, second); + gap_stat.edge_num++; + if (!std::isfinite(cost)) { + gap_stat.inf_edge_num++; + continue; + } + gap_stat.finite_cost_sum += cost; + if (cost > kCostEpsilon) { + stat.min_positive_cost = std::min(stat.min_positive_cost, cost); + } + } + } + } + return stat; +} - int32_t point_num = static_cast(planar_coord_list.size()); - std::vector x_list(point_num); - std::vector y_list(point_num); - for (int32_t i = 0; i < point_num; i++) { - x_list[i] = planar_coord_list[i].get_x(); - y_list[i] = planar_coord_list[i].get_y(); +bool buildWarpedAxis(const std::vector& raw_axis, const TBAxisCostStat& cost_stat, double reference_cost, + std::vector& warped_axis) +{ + if (raw_axis.empty()) { + return false; } - Flute::Tree flute_tree = Flute::flute(point_num, x_list.data(), y_list.data(), FLUTE_ACCURACY); - for (int32_t i = 0; i < 2 * flute_tree.deg - 2; i++) { - int32_t neighbor_idx = flute_tree.branch[i].n; - PlanarCoord first_coord(flute_tree.branch[i].x, flute_tree.branch[i].y); - PlanarCoord second_coord(flute_tree.branch[neighbor_idx].x, flute_tree.branch[neighbor_idx].y); - if (first_coord != second_coord) { - planar_topo_list.emplace_back(first_coord, second_coord); + warped_axis.assign(raw_axis.size(), 0); + constexpr int64_t max_warp_coord = std::numeric_limits::max() / 4; + for (size_t gap_idx = 0; gap_idx + 1 < raw_axis.size(); gap_idx++) { + const TBGapCostStat& gap_stat = cost_stat.gap_stat_list[gap_idx]; + long double density = (gap_stat.finite_cost_sum + gap_stat.inf_edge_num * reference_cost * kMaxWarpStretch) / gap_stat.edge_num; + long double stretch = std::clamp(density / reference_cost, static_cast(1), static_cast(kMaxWarpStretch)); + int64_t axis_delta = static_cast(raw_axis[gap_idx + 1]) - raw_axis[gap_idx]; + long double raw_delta = static_cast(axis_delta) * kWarpScale * stretch; + if (!std::isfinite(raw_delta) || raw_delta > max_warp_coord - warped_axis[gap_idx]) { + return false; } + int64_t warped_delta = std::max(1, std::llround(raw_delta)); + warped_axis[gap_idx + 1] = static_cast(warped_axis[gap_idx] + warped_delta); } - Flute::free_tree(flute_tree); - return planar_topo_list; + return true; +} + +int32_t getAxisIndex(const std::vector& axis, int32_t value) +{ + auto iter = std::ranges::lower_bound(axis, value); + return iter != axis.end() && *iter == value ? static_cast(iter - axis.begin()) : -1; } -std::vector> TOPOBuilder::legalizePlanarTopo(const TBTask& tb_task, std::vector> raw_topo_list, - TBSteinerRepairStat& steiner_repair_stat) +bool restoreRawCoordinates(Flute::Tree& tree, const std::vector& raw_x_axis, const std::vector& raw_y_axis, + const std::vector& warped_x_axis, const std::vector& warped_y_axis) { - const std::vector& planar_obs_list = tb_task.get_planar_obs_list(); - if (planar_obs_list.empty()) { - return raw_topo_list; + for (int32_t branch_idx = 0; branch_idx < getBranchNum(tree); branch_idx++) { + int32_t x_idx = getAxisIndex(warped_x_axis, tree.branch[branch_idx].x); + int32_t y_idx = getAxisIndex(warped_y_axis, tree.branch[branch_idx].y); + if (x_idx < 0 || y_idx < 0 || tree.branch[branch_idx].n < 0 || getBranchNum(tree) <= tree.branch[branch_idx].n) { + return false; + } + setBranchCoord(tree, branch_idx, PlanarCoord(raw_x_axis[x_idx], raw_y_axis[y_idx])); } - if (!tb_task.has_planar_search_region() || tb_task.get_planar_search_region().isIncorrect()) { - RTLOG.error(Loc::current(), "The planar search region is invalid!"); - return raw_topo_list; + return true; +} + +PlanarTopo getTopoListByTree(const Flute::Tree& tree) +{ + PlanarTopo topo_list; + topo_list.reserve(getBranchNum(tree)); + for (int32_t branch_idx = 0; branch_idx < getBranchNum(tree); branch_idx++) { + PlanarCoord first = getBranchCoord(tree, branch_idx); + PlanarCoord second = getBranchCoord(tree, tree.branch[branch_idx].n); + if (first != second) { + topo_list.emplace_back(first, second); + } } - for (const PlanarRect& planar_obs : planar_obs_list) { - if (planar_obs.isIncorrect()) { - RTLOG.error(Loc::current(), "The planar obstacle is invalid!"); - return raw_topo_list; + return topo_list; +} + +double getTopoCost(const TBTask& task, const PlanarTopo& topo_list) +{ + double cost = 0; + for (const Segment& segment : topo_list) { + double pattern_cost = getPatternCost(task, segment.get_first(), segment.get_second()); + if (!std::isfinite(pattern_cost)) { + return pattern_cost; } + cost += pattern_cost; + } + return cost; +} + +PlanarTopo getThreePinTopo(const std::vector& terminal_list, const PlanarCoord& steiner) +{ + PlanarTopo topo_list; + topo_list.reserve(terminal_list.size()); + for (const PlanarCoord& terminal : terminal_list) { + if (terminal != steiner) { + topo_list.emplace_back(terminal, steiner); + } + } + return topo_list; +} + +std::vector getCandidateAxisList(int32_t lower, int32_t upper, std::vector mandatory_list) +{ + std::erase_if(mandatory_list, [&](int32_t coord) { return coord < lower || upper < coord; }); + std::ranges::sort(mandatory_list); + mandatory_list.erase(std::ranges::unique(mandatory_list).begin(), mandatory_list.end()); + + int64_t span = static_cast(upper) - lower; + if (span + 1 <= kMaxThreePinAxisNum) { + mandatory_list.clear(); + for (int64_t coord = lower; coord <= upper; coord++) { + mandatory_list.push_back(static_cast(coord)); + } + return mandatory_list; + } + + int32_t sample_num = std::max(0, kMaxThreePinAxisNum - static_cast(mandatory_list.size())); + for (int32_t sample_idx = 0; sample_idx < sample_num; sample_idx++) { + int64_t offset = sample_num <= 1 ? span / 2 : span * sample_idx / (sample_num - 1); + mandatory_list.push_back(static_cast(lower + offset)); + } + std::ranges::sort(mandatory_list); + mandatory_list.erase(std::ranges::unique(mandatory_list).begin(), mandatory_list.end()); + return mandatory_list; +} + +TBTopoCandidate finalizeCandidate(const TBTask& task, Flute::Tree& tree, bool enable_steiner_refine) +{ + TBTopoCandidate candidate; + refineFluteTree(task, tree, candidate.refine_stat, enable_steiner_refine); + candidate.topo_list = getTopoListByTree(tree); + candidate.cost = getTopoCost(task, candidate.topo_list); + return candidate; +} + +TBTopoCandidate buildBaselineCandidate(const TBTask& task, bool enable_steiner_refine) +{ + const std::vector& coord_list = task.get_planar_coord_list(); + std::vector x_list(coord_list.size()); + std::vector y_list(coord_list.size()); + for (size_t coord_idx = 0; coord_idx < coord_list.size(); coord_idx++) { + x_list[coord_idx] = coord_list[coord_idx].get_x(); + y_list[coord_idx] = coord_list[coord_idx].get_y(); } + Flute::Tree tree = Flute::flute(static_cast(coord_list.size()), x_list.data(), y_list.data(), FLUTE_ACCURACY); + TBTopoCandidate candidate = finalizeCandidate(task, tree, enable_steiner_refine); + Flute::free_tree(tree); + return candidate; +} - std::set terminal_coord_set(tb_task.get_planar_coord_list().begin(), tb_task.get_planar_coord_list().end()); - auto isInsideSearchRegion = [&](const PlanarCoord& coord) { - const PlanarRect& planar_search_region = tb_task.get_planar_search_region(); - return planar_search_region.get_ll_x() <= coord.get_x() && coord.get_x() <= planar_search_region.get_ur_x() - && planar_search_region.get_ll_y() <= coord.get_y() && coord.get_y() <= planar_search_region.get_ur_y(); +TBTopoCandidate buildTerminalMSTCandidate(const TBTask& task) +{ + struct Link + { + int32_t parent = -1; + double cost = std::numeric_limits::infinity(); + int64_t wire_length = std::numeric_limits::max(); }; - std::map steiner_legal_coord_map; - auto legalizeSteinerCoord = [&](const PlanarCoord& coord) { - if (terminal_coord_set.find(coord) != terminal_coord_set.end() || !isSteinerForbiddenCoord(planar_obs_list, coord)) { - return coord; - } - auto legal_iter = steiner_legal_coord_map.find(coord); - if (legal_iter != steiner_legal_coord_map.end()) { - return legal_iter->second; - } - - steiner_repair_stat.raw_steiner_in_macro++; - PlanarCoord legal_coord = getNearestLegalCoord(planar_obs_list, tb_task.get_planar_search_region(), coord); - steiner_legal_coord_map[coord] = legal_coord; - if (isSteinerForbiddenCoord(planar_obs_list, legal_coord)) { - steiner_repair_stat.failed_steiner_legalize_num++; - const PlanarRect& planar_search_region = tb_task.get_planar_search_region(); - RTLOG.warn(Loc::current(), "steiner_legalize_failed, coord: (", coord.get_x(), ",", coord.get_y(), - "), reason: ", isInsideSearchRegion(coord) ? "no_legal_coordinate_in_search_region" : "raw_steiner_outside_search_region", - ", search_region: (", planar_search_region.get_ll_x(), ",", planar_search_region.get_ll_y(), ")-(", planar_search_region.get_ur_x(), ",", - planar_search_region.get_ur_y(), "), obstacle_num: ", planar_obs_list.size(), ", terminal_num: ", terminal_coord_set.size()); - } else { - steiner_repair_stat.fixed_steiner_in_macro++; + auto is_better = [](const Link& first, const Link& second) { + bool first_finite = std::isfinite(first.cost); + bool second_finite = std::isfinite(second.cost); + if (first_finite != second_finite) { + return first_finite; + } + if (first_finite && std::abs(first.cost - second.cost) > kCostEpsilon) { + return first.cost < second.cost; + } + if (first.wire_length != second.wire_length) { + return first.wire_length < second.wire_length; } - return legal_coord; + return first.parent < second.parent; }; - std::vector> legal_topo_list; - legal_topo_list.reserve(raw_topo_list.size()); - for (Segment& raw_topo : raw_topo_list) { - PlanarCoord first_coord = legalizeSteinerCoord(raw_topo.get_first()); - PlanarCoord second_coord = legalizeSteinerCoord(raw_topo.get_second()); - if (first_coord != second_coord) { - legal_topo_list.emplace_back(first_coord, second_coord); + const std::vector& terminal_list = task.get_planar_coord_list(); + std::vector visited(terminal_list.size(), false); + std::vector link_list(terminal_list.size()); + TBTopoCandidate candidate; + visited.front() = true; + auto update_link = [&](int32_t parent_idx, int32_t child_idx) { + const PlanarCoord& parent = terminal_list[parent_idx]; + const PlanarCoord& child = terminal_list[child_idx]; + Link link{parent_idx, getPatternCost(task, parent, child), + std::abs(static_cast(parent.get_x()) - child.get_x()) + + std::abs(static_cast(parent.get_y()) - child.get_y())}; + if (link_list[child_idx].parent < 0 || is_better(link, link_list[child_idx])) { + link_list[child_idx] = link; + } + }; + for (int32_t child_idx = 1; child_idx < static_cast(terminal_list.size()); child_idx++) { + update_link(0, child_idx); + } + while (candidate.topo_list.size() + 1 < terminal_list.size()) { + int32_t best_idx = -1; + for (int32_t idx = 0; idx < static_cast(terminal_list.size()); idx++) { + if (!visited[idx] && (best_idx < 0 || is_better(link_list[idx], link_list[best_idx]))) { + best_idx = idx; + } + } + if (best_idx < 0) { + break; + } + visited[best_idx] = true; + candidate.topo_list.emplace_back(terminal_list[link_list[best_idx].parent], terminal_list[best_idx]); + for (int32_t idx = 0; idx < static_cast(terminal_list.size()); idx++) { + if (!visited[idx]) { + update_link(best_idx, idx); + } } } - return legal_topo_list; + candidate.cost = getTopoCost(task, candidate.topo_list); + return candidate; } -PlanarCoord TOPOBuilder::getNearestLegalCoord(const std::vector& planar_obs_list, const PlanarRect& planar_search_region, const PlanarCoord& coord) +std::optional buildThreePinCongestionCandidate(const TBTask& task) { - auto isInsideSearchRegion = [&](const PlanarCoord& candidate_coord) { - return planar_search_region.get_ll_x() <= candidate_coord.get_x() && candidate_coord.get_x() <= planar_search_region.get_ur_x() - && planar_search_region.get_ll_y() <= candidate_coord.get_y() && candidate_coord.get_y() <= planar_search_region.get_ur_y(); - }; - if (!isInsideSearchRegion(coord) || !isSteinerForbiddenCoord(planar_obs_list, coord)) { - return coord; - } - - int32_t max_radius = 0; - for (const PlanarCoord& corner : {planar_search_region.get_ll(), PlanarCoord(planar_search_region.get_ll_x(), planar_search_region.get_ur_y()), - PlanarCoord(planar_search_region.get_ur_x(), planar_search_region.get_ll_y()), planar_search_region.get_ur()}) { - max_radius = std::max(max_radius, RTUTIL.getManhattanDistance(coord, corner)); - } - for (int32_t radius = 1; radius <= max_radius; radius++) { - bool found_legal_coord = false; - bool best_is_axis_aligned = false; - PlanarCoord best_coord; - auto updateBest = [&](int32_t x, int32_t y) { - PlanarCoord candidate_coord(x, y); - if (!isInsideSearchRegion(candidate_coord) || isSteinerForbiddenCoord(planar_obs_list, candidate_coord)) { + const std::vector& terminal_list = task.get_planar_coord_list(); + int32_t ll_x = terminal_list.front().get_x(); + int32_t ur_x = ll_x; + int32_t ll_y = terminal_list.front().get_y(); + int32_t ur_y = ll_y; + + std::vector terminal_x_list; + std::vector terminal_y_list; + for (const PlanarCoord& terminal : terminal_list) { + ll_x = std::min(ll_x, terminal.get_x()); + ur_x = std::max(ur_x, terminal.get_x()); + ll_y = std::min(ll_y, terminal.get_y()); + ur_y = std::max(ur_y, terminal.get_y()); + terminal_x_list.push_back(terminal.get_x()); + terminal_y_list.push_back(terminal.get_y()); + } + std::vector candidate_x_list = getCandidateAxisList(ll_x, ur_x, terminal_x_list); + std::vector candidate_y_list = getCandidateAxisList(ll_y, ur_y, terminal_y_list); + + std::optional best_candidate; + PlanarCoord best_steiner; + int64_t best_wire_length = std::numeric_limits::max(); + int32_t candidate_num = 0; + std::unordered_set visited_set; + visited_set.reserve(kMaxThreePinCandidateNum); + auto evaluateCandidate = [&](const PlanarCoord& steiner) { + uint64_t coord_key = (static_cast(static_cast(steiner.get_x())) << 32) + | static_cast(steiner.get_y()); + if (candidate_num >= kMaxThreePinCandidateNum || !isInsideSearchRegion(task, steiner) || !visited_set.insert(coord_key).second) { + return; + } + candidate_num++; + double cost = 0; + int64_t wire_length = 0; + for (const PlanarCoord& terminal : terminal_list) { + if (terminal == steiner) { + continue; + } + double pattern_cost = getPatternCost(task, terminal, steiner); + if (!std::isfinite(pattern_cost)) { return; } - bool candidate_is_axis_aligned = candidate_coord.get_x() == coord.get_x() || candidate_coord.get_y() == coord.get_y(); - if (!found_legal_coord || (candidate_is_axis_aligned && !best_is_axis_aligned) - || (candidate_is_axis_aligned == best_is_axis_aligned && CmpPlanarCoordByXASC()(candidate_coord, best_coord))) { - best_coord = candidate_coord; - best_is_axis_aligned = candidate_is_axis_aligned; - found_legal_coord = true; + cost += pattern_cost; + wire_length += std::abs(static_cast(terminal.get_x()) - steiner.get_x()) + + std::abs(static_cast(terminal.get_y()) - steiner.get_y()); + if (best_candidate.has_value() && cost > best_candidate->cost + kCostEpsilon) { + return; } - }; - for (int32_t dx = -radius; dx <= radius; dx++) { - updateBest(coord.get_x() + dx, coord.get_y() - radius); - updateBest(coord.get_x() + dx, coord.get_y() + radius); } - for (int32_t dy = -radius + 1; dy <= radius - 1; dy++) { - updateBest(coord.get_x() - radius, coord.get_y() + dy); - updateBest(coord.get_x() + radius, coord.get_y() + dy); + bool has_equal_cost = best_candidate.has_value() && std::abs(cost - best_candidate->cost) <= kCostEpsilon; + bool is_better = !best_candidate.has_value() || isStrictlyBetterCost(best_candidate->cost, cost) + || (has_equal_cost && (wire_length < best_wire_length + || (wire_length == best_wire_length && CmpPlanarCoordByXASC()(steiner, best_steiner)))); + if (is_better) { + best_candidate = TBTopoCandidate{.topo_list = getThreePinTopo(terminal_list, steiner), .cost = cost}; + best_steiner = steiner; + best_wire_length = wire_length; + } + }; + + for (int32_t x : candidate_x_list) { + for (int32_t y : candidate_y_list) { + evaluateCandidate(PlanarCoord(x, y)); + } + } + if (best_candidate.has_value() || !task.has_planar_search_region()) { + return best_candidate; + } + + const PlanarRect& region = task.get_planar_search_region(); + int32_t max_radius = std::max({ll_x - region.get_ll_x(), region.get_ur_x() - ur_x, ll_y - region.get_ll_y(), region.get_ur_y() - ur_y}); + int32_t found_radius = -1; + for (int32_t radius = 1; radius <= max_radius && candidate_num < kMaxThreePinCandidateNum + && (found_radius == -1 || radius <= found_radius + kThreePinExtraRadius); + radius++) { + int32_t expanded_ll_x = std::max(region.get_ll_x(), ll_x - radius); + int32_t expanded_ur_x = std::min(region.get_ur_x(), ur_x + radius); + int32_t expanded_ll_y = std::max(region.get_ll_y(), ll_y - radius); + int32_t expanded_ur_y = std::min(region.get_ur_y(), ur_y + radius); + for (int64_t x = expanded_ll_x; x <= expanded_ur_x && candidate_num < kMaxThreePinCandidateNum; x++) { + evaluateCandidate(PlanarCoord(static_cast(x), expanded_ll_y)); + evaluateCandidate(PlanarCoord(static_cast(x), expanded_ur_y)); + } + for (int64_t y = static_cast(expanded_ll_y) + 1; y < expanded_ur_y && candidate_num < kMaxThreePinCandidateNum; y++) { + evaluateCandidate(PlanarCoord(expanded_ll_x, static_cast(y))); + evaluateCandidate(PlanarCoord(expanded_ur_x, static_cast(y))); } - if (found_legal_coord) { - return best_coord; + if (found_radius == -1 && best_candidate.has_value()) { + found_radius = radius; } } - return coord; + return best_candidate; +} + +std::optional buildWarpedCongestionCandidate(const TBTask& task) +{ + const std::vector& coord_list = task.get_planar_coord_list(); + std::vector raw_x_axis = getUniqueAxisList(coord_list, TBAxis::kX); + std::vector raw_y_axis = getUniqueAxisList(coord_list, TBAxis::kY); + constexpr int64_t max_warp_coord = std::numeric_limits::max() / 4; + auto is_axis_warpable = [](const std::vector& axis) { + int64_t span = static_cast(axis.back()) - axis.front(); + return span <= max_warp_coord / kWarpScale; + }; + if (!is_axis_warpable(raw_x_axis) || !is_axis_warpable(raw_y_axis)) { + return std::nullopt; + } + TBAxisCostStat x_cost_stat = getAxisCostStat(task, raw_x_axis, raw_y_axis, TBAxis::kX); + TBAxisCostStat y_cost_stat = getAxisCostStat(task, raw_y_axis, raw_x_axis, TBAxis::kY); + double reference_cost = std::min(x_cost_stat.min_positive_cost, y_cost_stat.min_positive_cost); + if (!std::isfinite(reference_cost)) { + return std::nullopt; + } + + std::vector warped_x_axis; + std::vector warped_y_axis; + if (!buildWarpedAxis(raw_x_axis, x_cost_stat, reference_cost, warped_x_axis) + || !buildWarpedAxis(raw_y_axis, y_cost_stat, reference_cost, warped_y_axis)) { + return std::nullopt; + } + + std::vector x_list(coord_list.size()); + std::vector y_list(coord_list.size()); + for (size_t coord_idx = 0; coord_idx < coord_list.size(); coord_idx++) { + x_list[coord_idx] = warped_x_axis[getAxisIndex(raw_x_axis, coord_list[coord_idx].get_x())]; + y_list[coord_idx] = warped_y_axis[getAxisIndex(raw_y_axis, coord_list[coord_idx].get_y())]; + } + + Flute::Tree tree = Flute::flute(static_cast(coord_list.size()), x_list.data(), y_list.data(), FLUTE_ACCURACY); + bool is_mapped = restoreRawCoordinates(tree, raw_x_axis, raw_y_axis, warped_x_axis, warped_y_axis); + TBTopoCandidate candidate; + if (is_mapped) { + candidate = finalizeCandidate(task, tree, false); + } + Flute::free_tree(tree); + return is_mapped ? std::optional(std::move(candidate)) : std::nullopt; +} + +std::optional buildCongestionCandidate(const TBTask& task) +{ + return task.get_planar_coord_list().size() == 3 ? buildThreePinCongestionCandidate(task) : buildWarpedCongestionCandidate(task); +} + +} // namespace + +// public + +void TOPOBuilder::initInst() +{ + if (_tb_instance == nullptr) { + _tb_instance = new TOPOBuilder(); + } +} + +TOPOBuilder& TOPOBuilder::getInst() +{ + if (_tb_instance == nullptr) { + RTLOG.error(Loc::current(), "The instance not initialized!"); + } + return *_tb_instance; +} + +void TOPOBuilder::destroyInst() +{ + if (_tb_instance != nullptr) { + delete _tb_instance; + _tb_instance = nullptr; + } +} + +void TOPOBuilder::init() +{ + Monitor monitor; + RTLOG.info(Loc::current(), "Starting..."); + Flute::readLUT(); + RTLOG.info(Loc::current(), "Completed", monitor.getStatsInfo()); +} + +std::vector> TOPOBuilder::getPlanarTopoList(const TBTask& task) +{ + TBRefineStat stat; + return getPlanarTopoList(task, stat); } -bool TOPOBuilder::isSteinerForbiddenCoord(const std::vector& planar_obs_list, const PlanarCoord& coord) +std::vector> TOPOBuilder::getPlanarTopoList(const TBTask& task, TBRefineStat& stat) { - for (const PlanarRect& planar_obs : planar_obs_list) { - if (planar_obs.get_ll_x() <= coord.get_x() && coord.get_x() <= planar_obs.get_ur_x() && planar_obs.get_ll_y() <= coord.get_y() - && coord.get_y() <= planar_obs.get_ur_y()) { - return true; + stat = {}; + const std::vector& coord_list = task.get_planar_coord_list(); + if (coord_list.size() <= 1) { + return {}; + } + + bool attempted_congestion_flute = task.is_congestion_driven() && coord_list.size() >= 3 && task.has_segment_cost_query(); + TBTopoCandidate selected_candidate = buildBaselineCandidate(task, false); + bool used_congestion_flute = false; + if (attempted_congestion_flute) { + std::optional congestion_candidate = buildCongestionCandidate(task); + if (congestion_candidate.has_value() && isStrictlyBetterCost(selected_candidate.cost, congestion_candidate->cost)) { + selected_candidate = std::move(*congestion_candidate); + used_congestion_flute = true; + } + } + bool attempted_steiner_refine = false; + bool used_steiner_refine = false; + if (attempted_congestion_flute && coord_list.size() > 3 && !std::isfinite(selected_candidate.cost)) { + attempted_steiner_refine = true; + TBTopoCandidate refined_candidate = buildBaselineCandidate(task, true); + if (std::isfinite(refined_candidate.cost)) { + selected_candidate = std::move(refined_candidate); + used_steiner_refine = true; } } - return false; + bool used_terminal_mst = attempted_congestion_flute && !std::isfinite(selected_candidate.cost); + if (used_terminal_mst) { + selected_candidate = buildTerminalMSTCandidate(task); + } + + stat = selected_candidate.refine_stat; + stat.attempted_congestion_flute = attempted_congestion_flute; + stat.used_congestion_flute = used_congestion_flute; + stat.attempted_steiner_refine = attempted_steiner_refine; + stat.used_steiner_refine = used_steiner_refine; + stat.used_terminal_mst = used_terminal_mst; + return std::move(selected_candidate.topo_list); +} + +void TOPOBuilder::destroy() +{ + Monitor monitor; + RTLOG.info(Loc::current(), "Starting..."); + Flute::deleteLUT(); + RTLOG.info(Loc::current(), "Completed", monitor.getStatsInfo()); } +TOPOBuilder* TOPOBuilder::_tb_instance = nullptr; + } // namespace irt diff --git a/src/operation/iRT/source/module/topo_builder/TOPOBuilder.hpp b/src/operation/iRT/source/module/topo_builder/TOPOBuilder.hpp index 9694a5645..39e763781 100644 --- a/src/operation/iRT/source/module/topo_builder/TOPOBuilder.hpp +++ b/src/operation/iRT/source/module/topo_builder/TOPOBuilder.hpp @@ -16,9 +16,6 @@ // *************************************************************************************** #pragma once -#include "Logger.hpp" -#include "Monitor.hpp" -#include "PlanarCoord.hpp" #include "Segment.hpp" #include "TBTask.hpp" @@ -26,11 +23,15 @@ namespace irt { #define RTTB (irt::TOPOBuilder::getInst()) -struct TBSteinerRepairStat +struct TBRefineStat { - int32_t raw_steiner_in_macro = 0; - int32_t fixed_steiner_in_macro = 0; - int32_t failed_steiner_legalize_num = 0; + int32_t shifted_edge_num = 0; + int32_t refined_steiner_num = 0; + bool attempted_congestion_flute = false; + bool used_congestion_flute = false; + bool attempted_steiner_refine = false; + bool used_steiner_refine = false; + bool used_terminal_mst = false; }; class TOPOBuilder @@ -42,7 +43,7 @@ class TOPOBuilder // function void init(); std::vector> getPlanarTopoList(const TBTask& tb_task); - std::vector> getPlanarTopoList(const TBTask& tb_task, TBSteinerRepairStat& steiner_repair_stat); + std::vector> getPlanarTopoList(const TBTask& tb_task, TBRefineStat& refine_stat); void destroy(); private: @@ -55,12 +56,6 @@ class TOPOBuilder ~TOPOBuilder() = default; TOPOBuilder& operator=(const TOPOBuilder& other) = delete; TOPOBuilder& operator=(TOPOBuilder&& other) = delete; - // function - std::vector> getFlutePlanarTopoList(const std::vector& planar_coord_list); - std::vector> legalizePlanarTopo(const TBTask& tb_task, std::vector> raw_topo_list, - TBSteinerRepairStat& steiner_repair_stat); - PlanarCoord getNearestLegalCoord(const std::vector& planar_obs_list, const PlanarRect& planar_search_region, const PlanarCoord& coord); - bool isSteinerForbiddenCoord(const std::vector& planar_obs_list, const PlanarCoord& coord); }; } // namespace irt diff --git a/src/operation/iRT/source/module/topo_builder/tb_data_manager/TBTask.hpp b/src/operation/iRT/source/module/topo_builder/tb_data_manager/TBTask.hpp index 9a512eab0..cb5280d3a 100644 --- a/src/operation/iRT/source/module/topo_builder/tb_data_manager/TBTask.hpp +++ b/src/operation/iRT/source/module/topo_builder/tb_data_manager/TBTask.hpp @@ -16,12 +16,16 @@ // *************************************************************************************** #pragma once +#include + #include "PlanarCoord.hpp" #include "PlanarRect.hpp" #include "RTHeader.hpp" namespace irt { +using TBSegmentCostQuery = std::function; + class TBTask { public: @@ -29,26 +33,29 @@ class TBTask ~TBTask() = default; // getter std::vector& get_planar_coord_list() { return _planar_coord_list; } - const std::vector& get_planar_obs_list() const { return _planar_obs_list; } + const std::vector& get_planar_coord_list() const { return _planar_coord_list; } const PlanarRect& get_planar_search_region() const { return _planar_search_region; } bool has_planar_search_region() const { return _has_planar_search_region; } - // const getter - const std::vector& get_planar_coord_list() const { return _planar_coord_list; } + bool has_segment_cost_query() const { return static_cast(_segment_cost_query); } + bool is_congestion_driven() const { return _is_congestion_driven; } // setter void set_planar_coord_list(std::vector planar_coord_list) { _planar_coord_list = std::move(planar_coord_list); } - void set_planar_obs_list(std::vector planar_obs_list) { _planar_obs_list = std::move(planar_obs_list); } + void set_segment_cost_query(TBSegmentCostQuery query) { _segment_cost_query = std::move(query); } + void set_congestion_driven(bool congestion_driven) { _is_congestion_driven = congestion_driven; } void set_planar_search_region(const PlanarRect& planar_search_region) { _planar_search_region = planar_search_region; _has_planar_search_region = true; } // function + double get_segment_cost(const PlanarCoord& first, const PlanarCoord& second) const { return _segment_cost_query(first, second); } + private: std::vector _planar_coord_list; - // GCell-coordinate obstacles with inclusive lower and upper bounds. - std::vector _planar_obs_list; + TBSegmentCostQuery _segment_cost_query; PlanarRect _planar_search_region; bool _has_planar_search_region = false; + bool _is_congestion_driven = false; }; } // namespace irt diff --git a/src/operation/iRT/test/test_topo_builder/CMakeLists.txt b/src/operation/iRT/test/test_topo_builder/CMakeLists.txt index e02951f3f..5c4645ad2 100644 --- a/src/operation/iRT/test/test_topo_builder/CMakeLists.txt +++ b/src/operation/iRT/test/test_topo_builder/CMakeLists.txt @@ -1,5 +1,4 @@ add_executable(test_topo_builder - TopoSvgPlotter.cpp test_topo_builder.cpp ) diff --git a/src/operation/iRT/test/test_topo_builder/test_topo_builder.cpp b/src/operation/iRT/test/test_topo_builder/test_topo_builder.cpp index d42028b34..206267dce 100644 --- a/src/operation/iRT/test/test_topo_builder/test_topo_builder.cpp +++ b/src/operation/iRT/test/test_topo_builder/test_topo_builder.cpp @@ -1,51 +1,91 @@ +// *************************************************************************************** +// Copyright (c) 2023-2025 Peng Cheng Laboratory +// Copyright (c) 2023-2025 Institute of Computing Technology, Chinese Academy of Sciences +// Copyright (c) 2023-2025 Beijing Institute of Open Source Chip +// +// iEDA is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// +// See the Mulan PSL v2 for more details. +// *************************************************************************************** + +#include +#include +#include #include #include +#include +#include #include -#include +#include +#include +#include +#include #include +#include #include -#include #include #include "Logger.hpp" -#include "PlanarRect.hpp" #include "TOPOBuilder.hpp" -#include "TopoSvgPlotter.hpp" #include "utility/logger/Logger.hpp" + namespace { using irt::PlanarCoord; using irt::PlanarRect; using irt::Segment; -struct PlotConfig -{ - std::filesystem::path output_directory; +constexpr double kInf = std::numeric_limits::infinity(); - bool isEnabled() const { return !output_directory.empty(); } +struct PlotOptions +{ + std::optional plot_dir; + bool show_help = false; }; -struct TopoRunResult +void printUsage(const char* program) { - std::vector> flute_topo_list; - std::vector> legal_topo_list; - irt::TBSteinerRepairStat steiner_repair_stat; -}; + std::cout << "Usage: " << program << " [--plot-dir ] [--help]\n"; +} -bool parsePlotConfig(int argc, char* argv[], PlotConfig& plot_config) +bool parseOptions(int argc, char* argv[], PlotOptions& options) { - if (argc == 1) { - return true; - } - if (argc == 3 && std::string(argv[1]) == "--plot-dir" && !std::string(argv[2]).empty()) { - plot_config.output_directory = argv[2]; - return true; + for (int arg_idx = 1; arg_idx < argc; arg_idx++) { + std::string arg = argv[arg_idx]; + if (arg == "--help") { + options.show_help = true; + } else if (arg == "--plot-dir") { + if (arg_idx + 1 >= argc) { + std::cerr << "Missing value for --plot-dir\n"; + return false; + } + options.plot_dir = argv[++arg_idx]; + } else { + std::cerr << "Unknown argument: " << arg << "\n"; + return false; + } } - ECCLOG.warn(ecc::Loc::current(), "Usage: test_topo_builder [--plot-dir ]"); - return false; + return true; } +bool preparePlotDirectory(const std::filesystem::path& plot_dir) +{ + std::error_code error; + std::filesystem::create_directories(plot_dir, error); + if (error) { + std::cerr << "Failed to create plot directory '" << plot_dir.string() << "': " << error.message() << "\n"; + return false; + } + return true; +} bool check(bool condition, const std::string& case_name) { if (!condition) { @@ -54,119 +94,372 @@ bool check(bool condition, const std::string& case_name) return condition; } -bool isSameTopo(const std::vector>& expected, const std::vector>& actual) +int32_t getDistance(const PlanarCoord& first, const PlanarCoord& second) { - if (expected.size() != actual.size()) { - return false; + return std::abs(first.get_x() - second.get_x()) + std::abs(first.get_y() - second.get_y()); +} + +double addCost(double first, double second) +{ + return std::isfinite(first) && std::isfinite(second) ? first + second : kInf; +} + +class GridCostMap +{ + public: + explicit GridCostMap(const PlanarRect& region, double default_cost = 1) + : _region(region), + _horizontal_cost((region.get_ur_x() - region.get_ll_x()) * (region.get_ur_y() - region.get_ll_y() + 1), default_cost), + _vertical_cost((region.get_ur_x() - region.get_ll_x() + 1) * (region.get_ur_y() - region.get_ll_y()), default_cost) + { } - for (size_t i = 0; i < expected.size(); ++i) { - if (expected[i].get_first() != actual[i].get_first() || expected[i].get_second() != actual[i].get_second()) { - return false; + + void setHorizontalCost(int32_t x, int32_t y, double cost) { _horizontal_cost[getHorizontalIndex(x, y)] = cost; } + void setVerticalCost(int32_t x, int32_t y, double cost) { _vertical_cost[getVerticalIndex(x, y)] = cost; } + + double getCost(const PlanarCoord& first, const PlanarCoord& second) const + { + if (first == second) { + return 0; + } + double cost = 0; + if (first.get_y() == second.get_y()) { + int32_t ll_x = std::min(first.get_x(), second.get_x()); + int32_t ur_x = std::max(first.get_x(), second.get_x()); + for (int32_t x = ll_x; x < ur_x; x++) { + if (!containsHorizontalEdge(x, first.get_y())) { + return kInf; + } + cost = addCost(cost, _horizontal_cost[getHorizontalIndex(x, first.get_y())]); + } + return cost; } + if (first.get_x() == second.get_x()) { + int32_t ll_y = std::min(first.get_y(), second.get_y()); + int32_t ur_y = std::max(first.get_y(), second.get_y()); + for (int32_t y = ll_y; y < ur_y; y++) { + if (!containsVerticalEdge(first.get_x(), y)) { + return kInf; + } + cost = addCost(cost, _vertical_cost[getVerticalIndex(first.get_x(), y)]); + } + return cost; + } + return kInf; } - return true; + + irt::TBSegmentCostQuery getQuery() const + { + return [cost_map = *this](const PlanarCoord& first, const PlanarCoord& second) { return cost_map.getCost(first, second); }; + } + + private: + bool containsHorizontalEdge(int32_t x, int32_t y) const + { + return _region.get_ll_x() <= x && x < _region.get_ur_x() && _region.get_ll_y() <= y && y <= _region.get_ur_y(); + } + + bool containsVerticalEdge(int32_t x, int32_t y) const + { + return _region.get_ll_x() <= x && x <= _region.get_ur_x() && _region.get_ll_y() <= y && y < _region.get_ur_y(); + } + + size_t getHorizontalIndex(int32_t x, int32_t y) const + { + int32_t width = _region.get_ur_x() - _region.get_ll_x(); + return static_cast(y - _region.get_ll_y()) * width + x - _region.get_ll_x(); + } + + size_t getVerticalIndex(int32_t x, int32_t y) const + { + int32_t height = _region.get_ur_y() - _region.get_ll_y(); + return static_cast(x - _region.get_ll_x()) * height + y - _region.get_ll_y(); + } + + PlanarRect _region; + std::vector _horizontal_cost; + std::vector _vertical_cost; +}; + +using CoordKey = std::pair; +using EdgeKey = std::pair; +using CanonicalTopo = std::vector>; + +CoordKey getCoordKey(const PlanarCoord& coord) +{ + return {coord.get_x(), coord.get_y()}; } -bool isInsideAnyObs(const PlanarCoord& coord, const std::vector& planar_obs_list) +CanonicalTopo canonicalizeTopo(const std::vector>& topo_list) { - for (const PlanarRect& planar_obs : planar_obs_list) { - if (planar_obs.get_ll_x() <= coord.get_x() && coord.get_x() <= planar_obs.get_ur_x() && planar_obs.get_ll_y() <= coord.get_y() - && coord.get_y() <= planar_obs.get_ur_y()) { - return true; + CanonicalTopo canonical_topo; + canonical_topo.reserve(topo_list.size()); + for (const Segment& topo : topo_list) { + CoordKey first = getCoordKey(topo.get_first()); + CoordKey second = getCoordKey(topo.get_second()); + if (second < first) { + std::swap(first, second); } + canonical_topo.push_back({first.first, first.second, second.first, second.second}); } - return false; + std::ranges::sort(canonical_topo); + return canonical_topo; } -std::vector getSteinerCoordList(const std::vector& terminal_list, const std::vector>& planar_topo_list) +bool isSameStat(const irt::TBRefineStat& first, const irt::TBRefineStat& second) { - std::set terminal_coord_set(terminal_list.begin(), terminal_list.end()); - std::set steiner_coord_set; - for (const Segment& planar_topo : planar_topo_list) { - for (const PlanarCoord& coord : {planar_topo.get_first(), planar_topo.get_second()}) { - if (terminal_coord_set.find(coord) == terminal_coord_set.end()) { - steiner_coord_set.insert(coord); - } + return first.shifted_edge_num == second.shifted_edge_num && first.refined_steiner_num == second.refined_steiner_num + && first.attempted_congestion_flute == second.attempted_congestion_flute + && first.used_congestion_flute == second.used_congestion_flute + && first.attempted_steiner_refine == second.attempted_steiner_refine && first.used_steiner_refine == second.used_steiner_refine + && first.used_terminal_mst == second.used_terminal_mst; +} + +bool isTopoValid(const std::vector& terminal_list, const std::vector>& topo_list, const PlanarRect& region) +{ + if (terminal_list.size() <= 1) { + return topo_list.empty(); + } + + std::set coord_set; + std::set edge_set; + auto is_inside = [&](const PlanarCoord& coord) { + return region.get_ll_x() <= coord.get_x() && coord.get_x() <= region.get_ur_x() && region.get_ll_y() <= coord.get_y() + && coord.get_y() <= region.get_ur_y(); + }; + for (const Segment& topo : topo_list) { + if (topo.get_first() == topo.get_second() || !is_inside(topo.get_first()) || !is_inside(topo.get_second())) { + return false; + } + CoordKey first = getCoordKey(topo.get_first()); + CoordKey second = getCoordKey(topo.get_second()); + if (second < first) { + std::swap(first, second); + } + if (!edge_set.emplace(first, second).second) { + return false; + } + coord_set.insert(first); + coord_set.insert(second); + } + for (const PlanarCoord& terminal : terminal_list) { + if (!coord_set.contains(getCoordKey(terminal))) { + return false; + } + } + + std::map coord_idx_map; + int32_t coord_idx = 0; + for (const CoordKey& coord : coord_set) { + coord_idx_map[coord] = coord_idx++; + } + std::vector parent(coord_set.size()); + std::iota(parent.begin(), parent.end(), 0); + std::function find_root = [&](int32_t idx) { + if (parent[idx] != idx) { + parent[idx] = find_root(parent[idx]); + } + return parent[idx]; + }; + for (const auto& [first, second] : edge_set) { + int32_t first_root = find_root(coord_idx_map[first]); + int32_t second_root = find_root(coord_idx_map[second]); + if (first_root == second_root) { + return false; } + parent[first_root] = second_root; } - return std::vector(steiner_coord_set.begin(), steiner_coord_set.end()); + return !coord_set.empty() && edge_set.size() + 1 == coord_set.size(); } -bool hasOnlyLegalSteiner(const std::vector& terminal_list, const std::vector>& planar_topo_list, - const std::vector& planar_obs_list) +double getPatternCost(const irt::TBSegmentCostQuery& query, const PlanarCoord& first, const PlanarCoord& second) { - for (const PlanarCoord& steiner_coord : getSteinerCoordList(terminal_list, planar_topo_list)) { - if (isInsideAnyObs(steiner_coord, planar_obs_list)) { + PlanarCoord x_bend(second.get_x(), first.get_y()); + PlanarCoord y_bend(first.get_x(), second.get_y()); + return std::min(addCost(query(first, x_bend), query(x_bend, second)), addCost(query(first, y_bend), query(y_bend, second))); +} + +double getTopoCost(const std::vector>& topo_list, const irt::TBSegmentCostQuery& query) +{ + double cost = 0; + for (const Segment& topo : topo_list) { + cost = addCost(cost, getPatternCost(query, topo.get_first(), topo.get_second())); + } + return cost; +} + +int32_t getWireLength(const std::vector>& topo_list) +{ + int32_t wire_length = 0; + for (const Segment& topo : topo_list) { + wire_length += getDistance(topo.get_first(), topo.get_second()); + } + return wire_length; +} + +bool containsCoord(const std::vector>& topo_list, const PlanarCoord& coord) +{ + return std::ranges::any_of(topo_list, [&](const Segment& topo) { return topo.get_first() == coord || topo.get_second() == coord; }); +} + +bool isInsideRect(const PlanarRect& rect, const PlanarCoord& coord) +{ + return rect.get_ll_x() <= coord.get_x() && coord.get_x() <= rect.get_ur_x() && rect.get_ll_y() <= coord.get_y() + && coord.get_y() <= rect.get_ur_y(); +} + +bool isSameTopo(const std::vector>& first, const std::vector>& second) +{ + if (first.size() != second.size()) { + return false; + } + for (size_t i = 0; i < first.size(); i++) { + if (first[i].get_first() != second[i].get_first() || first[i].get_second() != second[i].get_second()) { return false; } } return true; } -irt::TBTask makeTask(const std::vector& terminal_list, std::vector planar_obs_list = {}, - PlanarRect planar_search_region = PlanarRect(0, 0, 49, 49)) +irt::TBTask makeTask(const std::vector& terminal_list, irt::TBSegmentCostQuery query = {}, bool congestion_driven = false) { - irt::TBTask tb_task; - tb_task.set_planar_coord_list(terminal_list); - tb_task.set_planar_obs_list(std::move(planar_obs_list)); - tb_task.set_planar_search_region(planar_search_region); - return tb_task; + irt::TBTask task; + task.set_planar_coord_list(terminal_list); + task.set_planar_search_region(PlanarRect(0, 0, 49, 49)); + task.set_congestion_driven(congestion_driven); + if (query) { + task.set_segment_cost_query(std::move(query)); + } + return task; } -TopoRunResult runTopoCase(const std::vector& terminal_list, const std::vector& planar_obs_list, const PlanarRect& planar_search_region) +std::vector getBaseTerminalList() { - TopoRunResult result; - result.flute_topo_list = RTTB.getPlanarTopoList(makeTask(terminal_list, {}, planar_search_region)); - result.steiner_repair_stat = {1, 1, 1}; - result.legal_topo_list = RTTB.getPlanarTopoList(makeTask(terminal_list, planar_obs_list, planar_search_region), result.steiner_repair_stat); - return result; + return {PlanarCoord(0, 0), PlanarCoord(10, 30), PlanarCoord(30, 10), PlanarCoord(40, 40)}; } -bool checkRepairStat(const irt::TBSteinerRepairStat& stat, int32_t raw_steiner_num, int32_t fixed_steiner_num, int32_t failed_steiner_num, - const std::string& case_name) +std::vector getMultiHotspotTerminalList() { - bool passed = true; - passed = check(stat.raw_steiner_in_macro == raw_steiner_num, case_name + " raw Steiner statistic") && passed; - passed = check(stat.fixed_steiner_in_macro == fixed_steiner_num, case_name + " fixed Steiner statistic") && passed; - passed = check(stat.failed_steiner_legalize_num == failed_steiner_num, case_name + " failed Steiner statistic") && passed; - return passed; + return {PlanarCoord(0, 0), PlanarCoord(2, 4), PlanarCoord(4, 12), PlanarCoord(8, 7), + PlanarCoord(8, 34), PlanarCoord(10, 30), PlanarCoord(30, 10), PlanarCoord(40, 40)}; } -bool writeTopoPlot(const PlotConfig& plot_config, const std::string& case_id, const std::string& title, const PlanarRect& planar_search_region, - const std::vector& planar_obs_list, const std::vector& terminal_list, const TopoRunResult& topo_run_result) +GridCostMap getMultiHotspotCostMap() { - if (!plot_config.isEnabled()) { - return true; + PlanarRect region(0, 0, 49, 49); + GridCostMap cost_map(region); + for (int32_t y = region.get_ll_y(); y <= region.get_ur_y(); y++) { + if (y <= 10) { + for (int32_t x = region.get_ll_x(); x < region.get_ur_x(); x++) { + cost_map.setHorizontalCost(x, y, 50); + } + } else if (36 <= y) { + for (int32_t x = region.get_ll_x(); x < region.get_ur_x(); x++) { + cost_map.setHorizontalCost(x, y, 20); + } + } } - std::filesystem::path file_path = plot_config.output_directory / case_id / "topology.svg"; - irt::TopoSvgPlotRequest request{ - title, planar_search_region, planar_obs_list, terminal_list, topo_run_result.flute_topo_list, topo_run_result.legal_topo_list}; - std::string error_message; - if (!irt::writeTopoSvg(file_path.string(), request, error_message)) { - ECCLOG.warn(ecc::Loc::current(), "Failed to write ", file_path, ": ", error_message); - return false; + for (int32_t x = 18; x <= 24; x++) { + for (int32_t y = 18; y < 25; y++) { + cost_map.setVerticalCost(x, y, 12); + } } + return cost_map; +} - std::ifstream input_stream(file_path); - std::string svg_content((std::istreambuf_iterator(input_stream)), std::istreambuf_iterator()); - if (svg_content.find(" getCorridorTerminalList() +{ + return {PlanarCoord(2, 8), PlanarCoord(2, 40), PlanarCoord(24, 2), PlanarCoord(24, 46), PlanarCoord(46, 8), PlanarCoord(46, 40)}; +} + +GridCostMap getCorridorCostMap() +{ + PlanarRect region(0, 0, 49, 49); + GridCostMap cost_map(region, kInf); + for (int32_t y : {8, 24, 40}) { + for (int32_t x = region.get_ll_x(); x < region.get_ur_x(); x++) { + cost_map.setHorizontalCost(x, y, 1); + } } - return true; + for (int32_t x : {2, 24, 46}) { + for (int32_t y = region.get_ll_y(); y < region.get_ur_y(); y++) { + cost_map.setVerticalCost(x, y, 1); + } + } + return cost_map; } -const PlanarRect& getDefaultSearchRegion() +std::vector getHighDegreeTerminalList() { - static const PlanarRect search_region(0, 0, 49, 49); - return search_region; + std::vector terminal_list; + terminal_list.reserve(32); + for (int32_t row = 0; row < 4; row++) { + for (int32_t column = 0; column < 8; column++) { + terminal_list.emplace_back(2 + column * 6, 2 + row * 12 + ((column + row) % 3) * 2); + } + } + return terminal_list; } -std::vector getBaseTerminalList() +GridCostMap getHighDegreeCostMap() { - return {PlanarCoord(0, 0), PlanarCoord(10, 30), PlanarCoord(30, 10), PlanarCoord(40, 40)}; + PlanarRect region(0, 0, 49, 49); + GridCostMap cost_map(region); + for (int32_t y = region.get_ll_y(); y <= region.get_ur_y(); y++) { + for (int32_t x = 18; x < 23; x++) { + cost_map.setHorizontalCost(x, y, 8); + } + } + for (int32_t x = region.get_ll_x(); x <= region.get_ur_x(); x++) { + for (int32_t y = 20; y < 24; y++) { + cost_map.setVerticalCost(x, y, 12); + } + } + return cost_map; +} + +std::vector getSteinerCoordList(const std::vector& terminal_list, + const std::vector>& topo_list) +{ + std::set terminal_set; + for (const PlanarCoord& terminal : terminal_list) { + terminal_set.insert(getCoordKey(terminal)); + } + std::set steiner_set; + for (const Segment& topo : topo_list) { + for (const PlanarCoord& coord : {topo.get_first(), topo.get_second()}) { + if (!terminal_set.contains(getCoordKey(coord))) { + steiner_set.insert(getCoordKey(coord)); + } + } + } + std::vector steiner_list; + steiner_list.reserve(steiner_set.size()); + for (const auto& [x, y] : steiner_set) { + steiner_list.emplace_back(x, y); + } + return steiner_list; +} + +GridCostMap getSteinerBlockedCostMap(const std::vector& steiner_list) +{ + PlanarRect region(0, 0, 49, 49); + GridCostMap cost_map(region); + for (const PlanarCoord& steiner : steiner_list) { + if (region.get_ll_x() < steiner.get_x()) { + cost_map.setHorizontalCost(steiner.get_x() - 1, steiner.get_y(), kInf); + } + if (steiner.get_x() < region.get_ur_x()) { + cost_map.setHorizontalCost(steiner.get_x(), steiner.get_y(), kInf); + } + if (region.get_ll_y() < steiner.get_y()) { + cost_map.setVerticalCost(steiner.get_x(), steiner.get_y() - 1, kInf); + } + if (steiner.get_y() < region.get_ur_y()) { + cost_map.setVerticalCost(steiner.get_x(), steiner.get_y(), kInf); + } + } + return cost_map; } std::vector> getBaseFluteTopoList() @@ -177,196 +470,1049 @@ std::vector> getBaseFluteTopoList() {PlanarCoord(30, 10), PlanarCoord(30, 30)}}; } -bool checkCase1Baseline(const PlotConfig& plot_config) +irt::TBSegmentCostQuery getWireCostQuery() +{ + return [](const PlanarCoord& first, const PlanarCoord& second) { return static_cast(getDistance(first, second)); }; +} + +bool checkBaseline() { bool passed = true; - passed = check(RTTB.getPlanarTopoList(makeTask({})).empty(), "case1 empty FLUTE topology") && passed; - passed = check(RTTB.getPlanarTopoList(makeTask({PlanarCoord(10, 20)})).empty(), "case1 single-pin FLUTE topology") && passed; + passed = check(RTTB.getPlanarTopoList(makeTask({})).empty(), "empty topology") && passed; + passed = check(RTTB.getPlanarTopoList(makeTask({PlanarCoord(10, 20)})).empty(), "single-pin topology") && passed; + passed = check(isSameTopo(RTTB.getPlanarTopoList(makeTask(getBaseTerminalList())), getBaseFluteTopoList()), "default FLUTE topology") && passed; + + irt::TBRefineStat stat; + std::vector> topo_list = RTTB.getPlanarTopoList(makeTask(getBaseTerminalList(), getWireCostQuery()), stat); + passed = check(isSameTopo(topo_list, getBaseFluteTopoList()), "uniform finite cost keeps FLUTE topology") && passed; + passed = check(stat.shifted_edge_num == 0 && stat.refined_steiner_num == 0, "uniform cost does not refine topology") && passed; + return passed; +} - std::vector> two_pin_expected = {{PlanarCoord(10, 20), PlanarCoord(40, 20)}}; - passed = check(isSameTopo(two_pin_expected, RTTB.getPlanarTopoList(makeTask({PlanarCoord(10, 20), PlanarCoord(40, 20)}))), "case1 two-pin FLUTE topology") +bool checkCostDrivenShift() +{ + irt::TBSegmentCostQuery query = [](const PlanarCoord& first, const PlanarCoord& second) { + double cost = getDistance(first, second); + if (first.get_x() == second.get_x() && first.get_x() == 30) { + cost += 100 * getDistance(first, second); + } + return cost; + }; + std::vector> baseline = RTTB.getPlanarTopoList(makeTask(getBaseTerminalList())); + irt::TBRefineStat stat; + std::vector> refined = RTTB.getPlanarTopoList(makeTask(getBaseTerminalList(), query), stat); + + bool passed = true; + passed = check(stat.shifted_edge_num > 0, "congested Steiner edge is shifted") && passed; + passed = check(getTopoCost(refined, query) < getTopoCost(baseline, query), "edge shift lowers topology cost") && passed; + passed = check(getWireLength(refined) == getWireLength(baseline), "edge shift preserves wire length") && passed; + passed = check(isSameTopo(refined, RTTB.getPlanarTopoList(makeTask(getBaseTerminalList(), query))), "edge shift is deterministic") && passed; + return passed; +} + +bool checkCongestionFluteGuard() +{ + irt::TBRefineStat low_degree_stat; + std::vector> low_degree_topo + = RTTB.getPlanarTopoList(makeTask({PlanarCoord(0, 0), PlanarCoord(20, 20)}, getWireCostQuery(), true), low_degree_stat); + + irt::TBRefineStat uniform_stat; + std::vector> uniform_topo = RTTB.getPlanarTopoList(makeTask(getBaseTerminalList(), getWireCostQuery(), true), uniform_stat); + + bool passed = true; + passed = check(!low_degree_topo.empty() && !low_degree_stat.attempted_congestion_flute, "two-pin net skips congestion FLUTE") && passed; + passed = check(uniform_stat.attempted_congestion_flute && !uniform_stat.used_congestion_flute, + "uniform cost rejects equal congestion FLUTE candidate") && passed; + passed = check(isSameTopo(uniform_topo, getBaseFluteTopoList()), "uniform congestion FLUTE keeps baseline topology") && passed; + for (const PlanarCoord& terminal : getBaseTerminalList()) { + passed = check(containsCoord(uniform_topo, terminal), "congestion FLUTE keeps terminal coordinate") && passed; + } + return passed; +} - std::vector terminal_list = getBaseTerminalList(); - TopoRunResult result = runTopoCase(terminal_list, {}, getDefaultSearchRegion()); - passed = writeTopoPlot(plot_config, "case1", "Baseline without obstacles", getDefaultSearchRegion(), {}, terminal_list, result) && passed; - passed = check(isSameTopo(getBaseFluteTopoList(), result.flute_topo_list), "case1 multi-pin FLUTE topology") && passed; - passed = check(isSameTopo(result.flute_topo_list, result.legal_topo_list), "case1 no-obstacle topology remains unchanged") && passed; - passed = checkRepairStat(result.steiner_repair_stat, 0, 0, 0, "case1") && passed; +bool checkCongestionFluteCostGuard() +{ + irt::TBSegmentCostQuery query = [](const PlanarCoord& first, const PlanarCoord& second) { + double cost = getDistance(first, second); + if (first.get_y() == second.get_y() && first.get_y() <= 10) { + cost *= 50; + } + return cost; + }; + std::vector> normal = RTTB.getPlanarTopoList(makeTask(getBaseTerminalList(), query)); + irt::TBRefineStat stat; + std::vector> congestion = RTTB.getPlanarTopoList(makeTask(getBaseTerminalList(), query, true), stat); + + bool passed = true; + passed = check(stat.attempted_congestion_flute, "costed four-pin net attempts congestion FLUTE") && passed; + passed = check(stat.used_congestion_flute, "lower-cost congestion FLUTE candidate is selected") && passed; + passed = check(getTopoCost(congestion, query) < getTopoCost(normal, query), "selected congestion FLUTE lowers topology cost") && passed; + passed = check(isSameTopo(congestion, RTTB.getPlanarTopoList(makeTask(getBaseTerminalList(), query, true))), + "congestion FLUTE is deterministic") + && passed; return passed; } -bool checkCase2IrrelevantObstacle(const PlotConfig& plot_config) +bool checkCongestionFluteQueryBound() { - std::vector terminal_list = getBaseTerminalList(); - std::vector planar_obs_list = {PlanarRect(45, 0, 49, 4)}; - TopoRunResult result = runTopoCase(terminal_list, planar_obs_list, getDefaultSearchRegion()); + int64_t query_num = 0; + irt::TBSegmentCostQuery query = [&query_num](const PlanarCoord& first, const PlanarCoord& second) { + query_num++; + return static_cast(getDistance(first, second)); + }; + std::vector terminal_list + = {PlanarCoord(0, 0), PlanarCoord(0, 200), PlanarCoord(200, 0), PlanarCoord(200, 200)}; + RTTB.getPlanarTopoList(makeTask(terminal_list, query, true)); + return check(query_num <= 30000, "congestion FLUTE bounds bbox cost queries"); +} + +bool checkAlignedSegmentQueryOnce() +{ + int64_t query_num = 0; + irt::TBSegmentCostQuery query = [&query_num](const PlanarCoord& first, const PlanarCoord& second) { + query_num++; + return static_cast(getDistance(first, second)); + }; + std::vector> topo_list + = RTTB.getPlanarTopoList(makeTask({PlanarCoord(3, 7), PlanarCoord(31, 7)}, query)); + bool passed = true; - passed = writeTopoPlot(plot_config, "case2", "Irrelevant obstacle", getDefaultSearchRegion(), planar_obs_list, terminal_list, result) && passed; - passed = check(isSameTopo(result.flute_topo_list, result.legal_topo_list), "case2 topology remains unchanged") && passed; - passed = checkRepairStat(result.steiner_repair_stat, 0, 0, 0, "case2") && passed; + passed = check(topo_list.size() == 1, "aligned two-pin net keeps one topology edge") && passed; + passed = check(query_num == 1, "aligned topology edge queries segment cost once") && passed; return passed; } -bool checkCase3SingleCellObstacle(const PlotConfig& plot_config) +bool checkInfCostPruning() +{ + int64_t query_num = 0; + irt::TBSegmentCostQuery query = [&query_num](const PlanarCoord&, const PlanarCoord&) { + query_num++; + return kInf; + }; + RTTB.getPlanarTopoList(makeTask({PlanarCoord(3, 7), PlanarCoord(31, 29)}, query)); + return check(query_num == 2, "blocked L-patterns stop after their first segment"); +} + +irt::TBSegmentCostQuery getBlockedCoordQuery(const PlanarCoord& blocked_coord, bool block_all) +{ + return [blocked_coord, block_all](const PlanarCoord& first, const PlanarCoord& second) { + if (first == second) { + return 0.0; + } + if (block_all) { + return kInf; + } + if (first.get_y() == second.get_y() && first.get_y() == blocked_coord.get_y()) { + int32_t ll_x = std::min(first.get_x(), second.get_x()); + int32_t ur_x = std::max(first.get_x(), second.get_x()); + if ((ll_x <= blocked_coord.get_x() - 1 && blocked_coord.get_x() <= ur_x) + || (ll_x <= blocked_coord.get_x() && blocked_coord.get_x() + 1 <= ur_x)) { + return kInf; + } + } + if (first.get_x() == second.get_x() && first.get_x() == blocked_coord.get_x()) { + int32_t ll_y = std::min(first.get_y(), second.get_y()); + int32_t ur_y = std::max(first.get_y(), second.get_y()); + if ((ll_y <= blocked_coord.get_y() - 1 && blocked_coord.get_y() <= ur_y) + || (ll_y <= blocked_coord.get_y() && blocked_coord.get_y() + 1 <= ur_y)) { + return kInf; + } + } + return static_cast(getDistance(first, second)); + }; +} + +GridCostMap getMacroRingCostMap(const PlanarRect& macro, int32_t ring_width, double ring_cost) +{ + const PlanarRect region(0, 0, 49, 49); + const PlanarRect expanded_macro(macro.get_ll_x() - ring_width, macro.get_ll_y() - ring_width, macro.get_ur_x() + ring_width, + macro.get_ur_y() + ring_width); + GridCostMap cost_map(region); + auto getEdgeCost = [&](const PlanarCoord& first, const PlanarCoord& second) { + if (isInsideRect(macro, first) || isInsideRect(macro, second)) { + return kInf; + } + return isInsideRect(expanded_macro, first) || isInsideRect(expanded_macro, second) ? ring_cost : 1.0; + }; + for (int32_t y = region.get_ll_y(); y <= region.get_ur_y(); y++) { + for (int32_t x = region.get_ll_x(); x < region.get_ur_x(); x++) { + cost_map.setHorizontalCost(x, y, getEdgeCost(PlanarCoord(x, y), PlanarCoord(x + 1, y))); + } + } + for (int32_t x = region.get_ll_x(); x <= region.get_ur_x(); x++) { + for (int32_t y = region.get_ll_y(); y < region.get_ur_y(); y++) { + cost_map.setVerticalCost(x, y, getEdgeCost(PlanarCoord(x, y), PlanarCoord(x, y + 1))); + } + } + return cost_map; +} + +GridCostMap getBlockedMacroCostMap(const PlanarRect& macro) +{ + return getMacroRingCostMap(macro, 0, 1); +} + +bool checkCongestionFluteInfHandling() { std::vector terminal_list = getBaseTerminalList(); - std::vector planar_obs_list = {PlanarRect(30, 30, 30, 30)}; - TopoRunResult result = runTopoCase(terminal_list, planar_obs_list, getDefaultSearchRegion()); - std::vector> expected = {{PlanarCoord(0, 0), PlanarCoord(30, 10)}, - {PlanarCoord(10, 30), PlanarCoord(29, 30)}, - {PlanarCoord(40, 40), PlanarCoord(29, 30)}, - {PlanarCoord(30, 10), PlanarCoord(29, 30)}}; + irt::TBRefineStat partial_stat; + std::vector> partial_topo + = RTTB.getPlanarTopoList(makeTask(terminal_list, getBlockedCoordQuery(PlanarCoord(20, 20), false), true), partial_stat); + + irt::TBSegmentCostQuery fully_blocked_query = getBlockedCoordQuery({}, true); + irt::TBRefineStat fully_blocked_stat; + std::vector> fully_blocked_topo + = RTTB.getPlanarTopoList(makeTask(terminal_list, fully_blocked_query, true), fully_blocked_stat); + bool passed = true; - passed = writeTopoPlot(plot_config, "case3", "Single-cell Steiner legalization", getDefaultSearchRegion(), planar_obs_list, terminal_list, result) && passed; - passed = check(isSameTopo(expected, result.legal_topo_list), "case3 nearest legal Steiner point") && passed; - passed = checkRepairStat(result.steiner_repair_stat, 1, 1, 0, "case3") && passed; + passed = check(partial_stat.attempted_congestion_flute, "partial INF still attempts congestion FLUTE") && passed; + for (const PlanarCoord& terminal : terminal_list) { + passed = check(containsCoord(partial_topo, terminal), "partial INF congestion FLUTE keeps terminal") && passed; + } + passed = check(fully_blocked_stat.attempted_congestion_flute && !fully_blocked_stat.used_congestion_flute, + "fully blocked bbox rejects congestion FLUTE") + && passed; + passed = check(fully_blocked_stat.used_terminal_mst, "fully blocked bbox uses terminal MST fallback") && passed; + passed = check(fully_blocked_topo.size() == terminal_list.size() - 1, "fully blocked fallback builds a tree") && passed; + for (const Segment& segment : fully_blocked_topo) { + passed = check(std::ranges::find(terminal_list, segment.get_first()) != terminal_list.end() + && std::ranges::find(terminal_list, segment.get_second()) != terminal_list.end(), + "fully blocked fallback only uses terminals") + && passed; + } return passed; } -bool checkCase4RectangularObstacle(const PlotConfig& plot_config) +bool checkThreePinCongestionAvoidsMacro() { - std::vector terminal_list = getBaseTerminalList(); - std::vector planar_obs_list = {PlanarRect(30, 30, 31, 31)}; - TopoRunResult result = runTopoCase(terminal_list, planar_obs_list, getDefaultSearchRegion()); - std::vector> expected = {{PlanarCoord(0, 0), PlanarCoord(30, 10)}, - {PlanarCoord(10, 30), PlanarCoord(29, 30)}, - {PlanarCoord(40, 40), PlanarCoord(29, 30)}, - {PlanarCoord(30, 10), PlanarCoord(29, 30)}}; + const PlanarRect region(0, 0, 49, 49); + const PlanarRect macro(4, 0, 16, 8); + std::vector terminal_list = {PlanarCoord(0, 0), PlanarCoord(10, 20), PlanarCoord(20, 0)}; + PlanarCoord raw_steiner(10, 0); + std::vector> raw_topo = RTTB.getPlanarTopoList(makeTask(terminal_list)); + irt::TBSegmentCostQuery query = getBlockedMacroCostMap(macro).getQuery(); + irt::TBRefineStat stat; + std::vector> topo_list = RTTB.getPlanarTopoList(makeTask(terminal_list, query, true), stat); + std::vector> repeated_topo = RTTB.getPlanarTopoList(makeTask(terminal_list, query, true)); + bool passed = true; - passed = writeTopoPlot(plot_config, "case4", "Rectangular Steiner legalization", getDefaultSearchRegion(), planar_obs_list, terminal_list, result) && passed; - passed = check(isSameTopo(expected, result.legal_topo_list), "case4 rectangular obstacle legalization") && passed; - passed = checkRepairStat(result.steiner_repair_stat, 1, 1, 0, "case4") && passed; + passed = check(isInsideRect(macro, raw_steiner) && containsCoord(raw_topo, raw_steiner), "full-layer macro contains raw Steiner") && passed; + passed = check(std::ranges::none_of(terminal_list, [&](const PlanarCoord& terminal) { return isInsideRect(macro, terminal); }), + "full-layer macro excludes all terminals") + && passed; + passed = check(!std::isfinite(getTopoCost(raw_topo, query)), "full-layer macro blocks normal FLUTE topology") && passed; + passed = check(stat.attempted_congestion_flute && stat.used_congestion_flute, "three-pin congestion FLUTE is selected") && passed; + passed = check(stat.shifted_edge_num == 0 && !stat.attempted_steiner_refine && !stat.used_terminal_mst, + "three-pin congestion FLUTE avoids fallback") + && passed; + passed = check(std::isfinite(getTopoCost(topo_list, query)), "three-pin congestion topology has finite cost") && passed; + passed = check(!containsCoord(topo_list, raw_steiner), "three-pin congestion topology leaves blocked coordinate") && passed; + passed = check(std::ranges::none_of(getSteinerCoordList(terminal_list, topo_list), + [&](const PlanarCoord& steiner) { return isInsideRect(macro, steiner); }), + "three-pin congestion topology keeps Steiner outside full-layer macro") + && passed; + for (const PlanarCoord& terminal : terminal_list) { + passed = check(containsCoord(topo_list, terminal), "three-pin congestion topology keeps terminal") && passed; + } + passed = check(isTopoValid(terminal_list, topo_list, region), "three-pin congestion topology is valid") && passed; + passed = check(canonicalizeTopo(topo_list) == canonicalizeTopo(repeated_topo), "three-pin congestion topology is deterministic") && passed; return passed; } -bool checkCase5TerminalInsideObstacle(const PlotConfig& plot_config) +bool checkThreePinCongestionOutsidePinBBox() { - std::vector terminal_list = getBaseTerminalList(); - std::vector planar_obs_list = {PlanarRect(30, 10, 30, 10)}; - TopoRunResult result = runTopoCase(terminal_list, planar_obs_list, getDefaultSearchRegion()); + const PlanarRect region(0, 0, 49, 49); + std::vector terminal_list = {PlanarCoord(10, 10), PlanarCoord(15, 20), PlanarCoord(20, 10)}; + GridCostMap cost_map(region, kInf); + for (int32_t x = 10; x < 20; x++) { + cost_map.setHorizontalCost(x, 9, 1); + } + cost_map.setVerticalCost(10, 9, 1); + cost_map.setVerticalCost(20, 9, 1); + for (int32_t y = 9; y < 20; y++) { + cost_map.setVerticalCost(15, y, 1); + } + irt::TBSegmentCostQuery query = cost_map.getQuery(); + irt::TBRefineStat stat; + std::vector> topo_list = RTTB.getPlanarTopoList(makeTask(terminal_list, query, true), stat); + std::vector steiner_list = getSteinerCoordList(terminal_list, topo_list); + bool passed = true; - passed = writeTopoPlot(plot_config, "case5", "Terminal inside obstacle", getDefaultSearchRegion(), planar_obs_list, terminal_list, result) && passed; - passed = check(isInsideAnyObs(PlanarCoord(30, 10), planar_obs_list), "case5 terminal belongs to obstacle") && passed; - passed = check(isSameTopo(result.flute_topo_list, result.legal_topo_list), "case5 terminal remains unchanged") && passed; - passed = checkRepairStat(result.steiner_repair_stat, 0, 0, 0, "case5") && passed; + passed = check(stat.attempted_congestion_flute && stat.used_congestion_flute, "three-pin congestion searches outside pin bbox") && passed; + passed = check(!stat.attempted_steiner_refine && !stat.used_terminal_mst, "outside-bbox three-pin congestion avoids fallback") + && passed; + passed = check(std::isfinite(getTopoCost(topo_list, query)), "outside-bbox three-pin topology has finite cost") && passed; + passed = check(std::ranges::any_of(steiner_list, [](const PlanarCoord& steiner) { return steiner.get_y() < 10; }), + "three-pin congestion places Steiner outside pin bbox") + && passed; + passed = check(isTopoValid(terminal_list, topo_list, region), "outside-bbox three-pin topology is valid") && passed; return passed; } -bool checkCase6OverlappingObstacles(const PlotConfig& plot_config) +bool checkNormalDefersBlockedSteiner() { - std::vector terminal_list = getBaseTerminalList(); - std::vector planar_obs_list = {PlanarRect(30, 30, 31, 31), PlanarRect(31, 29, 32, 31)}; - TopoRunResult result = runTopoCase(terminal_list, planar_obs_list, getDefaultSearchRegion()); - std::vector> expected = {{PlanarCoord(0, 0), PlanarCoord(30, 10)}, - {PlanarCoord(10, 30), PlanarCoord(29, 30)}, - {PlanarCoord(40, 40), PlanarCoord(29, 30)}, - {PlanarCoord(30, 10), PlanarCoord(29, 30)}}; + const PlanarRect macro(4, 0, 16, 8); + std::vector terminal_list = {PlanarCoord(0, 0), PlanarCoord(10, 20), PlanarCoord(20, 0)}; + irt::TBRefineStat stat; + std::vector> topo_list = RTTB.getPlanarTopoList(makeTask(terminal_list, getBlockedMacroCostMap(macro).getQuery()), stat); bool passed = true; - passed = writeTopoPlot(plot_config, "case6", "Overlapping obstacles", getDefaultSearchRegion(), planar_obs_list, terminal_list, result) && passed; - passed = check(isSameTopo(expected, result.legal_topo_list), "case6 obstacle union legalization") && passed; - passed = checkRepairStat(result.steiner_repair_stat, 1, 1, 0, "case6") && passed; + passed = check(!stat.attempted_steiner_refine && !stat.used_terminal_mst, "normal mode skips congestion fallback") && passed; + passed = check(containsCoord(topo_list, PlanarCoord(10, 0)), "normal mode defers blocked Steiner handling") && passed; + passed = check(!std::isfinite(getTopoCost(topo_list, getBlockedMacroCostMap(macro).getQuery())), + "deferred normal topology remains blocked") + && passed; return passed; } -bool checkCase7SearchBoundary(const PlotConfig& plot_config) +bool checkMultiHotspotCompetition() { - std::vector terminal_list = getBaseTerminalList(); - PlanarRect planar_search_region(30, 30, 30, 30); - std::vector planar_obs_list = {PlanarRect(30, 30, 30, 30)}; - TopoRunResult result = runTopoCase(terminal_list, planar_obs_list, planar_search_region); + const PlanarRect region(0, 0, 49, 49); + std::vector terminal_list = getMultiHotspotTerminalList(); + irt::TBSegmentCostQuery query = getMultiHotspotCostMap().getQuery(); + std::vector> normal_topo = RTTB.getPlanarTopoList(makeTask(terminal_list, query)); + irt::TBRefineStat stat; + std::vector> congestion_topo = RTTB.getPlanarTopoList(makeTask(terminal_list, query, true), stat); + irt::TBRefineStat repeated_stat; + std::vector> repeated_topo = RTTB.getPlanarTopoList(makeTask(terminal_list, query, true), repeated_stat); + bool passed = true; - passed = writeTopoPlot(plot_config, "case7", "Search-region boundary", planar_search_region, planar_obs_list, terminal_list, result) && passed; - passed = check(isSameTopo(result.flute_topo_list, result.legal_topo_list), "case7 does not escape search region") && passed; - passed = checkRepairStat(result.steiner_repair_stat, 1, 0, 1, "case7") && passed; + passed = check(stat.attempted_congestion_flute && stat.used_congestion_flute, "multi-hotspot selects congestion FLUTE") && passed; + passed = check(getTopoCost(congestion_topo, query) < getTopoCost(normal_topo, query), "multi-hotspot topology lowers cost") && passed; + passed = check(isTopoValid(terminal_list, congestion_topo, region), "multi-hotspot topology is valid") && passed; + passed = check(canonicalizeTopo(congestion_topo) == canonicalizeTopo(repeated_topo), "multi-hotspot topology is deterministic") && passed; + passed = check(isSameStat(stat, repeated_stat), "multi-hotspot statistics are deterministic") && passed; return passed; } -bool checkCase8FullyBlocked(const PlotConfig& plot_config) +bool checkFiniteCorridor() { - std::vector terminal_list = getBaseTerminalList(); - std::vector planar_obs_list = {getDefaultSearchRegion()}; - TopoRunResult result = runTopoCase(terminal_list, planar_obs_list, getDefaultSearchRegion()); + const PlanarRect region(0, 0, 49, 49); + std::vector terminal_list = getCorridorTerminalList(); + irt::TBSegmentCostQuery query = getCorridorCostMap().getQuery(); + std::vector> normal_topo = RTTB.getPlanarTopoList(makeTask(terminal_list, query)); + irt::TBRefineStat stat; + std::vector> selected_topo = RTTB.getPlanarTopoList(makeTask(terminal_list, query, true), stat); + std::vector> repeated_topo = RTTB.getPlanarTopoList(makeTask(terminal_list, query, true)); + bool passed = true; - passed = writeTopoPlot(plot_config, "case8", "Fully blocked search region", getDefaultSearchRegion(), planar_obs_list, terminal_list, result) && passed; - passed = check(isSameTopo(result.flute_topo_list, result.legal_topo_list), "case8 keeps raw topology") && passed; - passed = checkRepairStat(result.steiner_repair_stat, 1, 0, 1, "case8") && passed; + passed = check(stat.attempted_congestion_flute, "finite corridor attempts congestion FLUTE") && passed; + passed = check(std::isfinite(getTopoCost(selected_topo, query)), "finite corridor produces finite topology cost") && passed; + passed = check(getTopoCost(selected_topo, query) <= getTopoCost(normal_topo, query), "finite corridor does not regress topology cost") && passed; + passed = check(isTopoValid(terminal_list, selected_topo, region), "finite corridor topology is valid") && passed; + passed = check(canonicalizeTopo(selected_topo) == canonicalizeTopo(repeated_topo), "finite corridor topology is deterministic") && passed; return passed; } -bool checkCase9MultipleSteiner(const PlotConfig& plot_config) +bool checkHighDegreeSteinerRefine() { + const PlanarRect region(0, 0, 49, 49); std::vector terminal_list = {PlanarCoord(0, 0), PlanarCoord(0, 40), PlanarCoord(10, 15), PlanarCoord(25, 30), PlanarCoord(40, 0), PlanarCoord(40, 40)}; - TopoRunResult baseline_result = runTopoCase(terminal_list, {}, getDefaultSearchRegion()); - std::vector raw_steiner_coord_list = getSteinerCoordList(terminal_list, baseline_result.flute_topo_list); - if (!check(raw_steiner_coord_list.size() >= 2, "case9 produces multiple raw Steiner points")) { - return false; - } + std::vector> raw_topo = RTTB.getPlanarTopoList(makeTask(terminal_list)); + std::vector raw_steiner_list = getSteinerCoordList(terminal_list, raw_topo); + irt::TBSegmentCostQuery query = getSteinerBlockedCostMap(raw_steiner_list).getQuery(); + int64_t query_num = 0; + irt::TBSegmentCostQuery counted_query = [&query_num, query](const PlanarCoord& first, const PlanarCoord& second) { + query_num++; + return query(first, second); + }; + irt::TBRefineStat stat; + std::vector> refined_topo = RTTB.getPlanarTopoList(makeTask(terminal_list, counted_query, true), stat); - std::vector planar_obs_list; - planar_obs_list.reserve(raw_steiner_coord_list.size()); - for (const PlanarCoord& raw_steiner_coord : raw_steiner_coord_list) { - planar_obs_list.emplace_back(raw_steiner_coord, raw_steiner_coord); - } - TopoRunResult result = runTopoCase(terminal_list, planar_obs_list, getDefaultSearchRegion()); - int32_t raw_steiner_num = static_cast(raw_steiner_coord_list.size()); bool passed = true; - passed = writeTopoPlot(plot_config, "case9", "Multiple Steiner legalizations", getDefaultSearchRegion(), planar_obs_list, terminal_list, result) && passed; - passed = check(isSameTopo(baseline_result.flute_topo_list, result.flute_topo_list), "case9 raw FLUTE topology is stable") && passed; - passed = check(hasOnlyLegalSteiner(terminal_list, result.legal_topo_list, planar_obs_list), "case9 all Steiner points are legal") && passed; - passed = checkRepairStat(result.steiner_repair_stat, raw_steiner_num, raw_steiner_num, 0, "case9") && passed; + passed = check(raw_steiner_list.size() >= 2, "high-degree case has multiple raw Steiner coordinates") && passed; + passed = check(stat.attempted_steiner_refine && stat.used_steiner_refine && stat.refined_steiner_num > 0, + "high-degree blocked topology uses Steiner refinement") + && passed; + passed = check(!stat.used_terminal_mst, "finite high-degree refinement avoids terminal MST") && passed; + for (const PlanarCoord& raw_steiner : raw_steiner_list) { + passed = check(!containsCoord(refined_topo, raw_steiner), "high-degree refinement leaves blocked Steiner coordinate") && passed; + } + passed = check(std::isfinite(getTopoCost(refined_topo, query)), "high-degree refinement produces finite topology") && passed; + passed = check(isTopoValid(terminal_list, refined_topo, region), "high-degree refined topology is valid") && passed; + passed = check(query_num <= 50000, "high-degree refinement bounds cost queries") && passed; return passed; } -bool checkCase10CollapsedSteinerEdge(const PlotConfig& plot_config) +bool checkHighDegreeStress() { - std::vector terminal_list = getBaseTerminalList(); - std::vector planar_obs_list = {PlanarRect(0, 0, 29, 49), PlanarRect(31, 0, 49, 49), PlanarRect(30, 0, 30, 9), PlanarRect(30, 11, 30, 49)}; - TopoRunResult result = runTopoCase(terminal_list, planar_obs_list, getDefaultSearchRegion()); - std::vector> expected - = {{PlanarCoord(0, 0), PlanarCoord(30, 10)}, {PlanarCoord(10, 30), PlanarCoord(30, 10)}, {PlanarCoord(40, 40), PlanarCoord(30, 10)}}; + const PlanarRect region(0, 0, 49, 49); + std::vector terminal_list = getHighDegreeTerminalList(); + irt::TBSegmentCostQuery base_query = getHighDegreeCostMap().getQuery(); + int64_t query_num = 0; + irt::TBSegmentCostQuery counted_query = [&query_num, base_query](const PlanarCoord& first, const PlanarCoord& second) { + query_num++; + return base_query(first, second); + }; + irt::TBRefineStat stat; + std::vector> topo_list = RTTB.getPlanarTopoList(makeTask(terminal_list, counted_query, true), stat); + irt::TBRefineStat repeated_stat; + std::vector> repeated_topo = RTTB.getPlanarTopoList(makeTask(terminal_list, base_query, true), repeated_stat); + bool passed = true; - passed = writeTopoPlot(plot_config, "case10", "Collapsed Steiner edge", getDefaultSearchRegion(), planar_obs_list, terminal_list, result) && passed; - passed = check(isSameTopo(expected, result.legal_topo_list), "case10 removes collapsed edge") && passed; - passed = check(result.legal_topo_list.size() + 1 == result.flute_topo_list.size(), "case10 removes exactly one edge") && passed; - passed = checkRepairStat(result.steiner_repair_stat, 1, 1, 0, "case10") && passed; + passed = check(stat.attempted_congestion_flute, "high-degree net attempts congestion FLUTE") && passed; + passed = check(std::isfinite(getTopoCost(topo_list, base_query)), "high-degree topology has finite cost") && passed; + passed = check(isTopoValid(terminal_list, topo_list, region), "high-degree topology is valid") && passed; + passed = check(canonicalizeTopo(topo_list) == canonicalizeTopo(repeated_topo), "high-degree topology is deterministic") && passed; + passed = check(isSameStat(stat, repeated_stat), "high-degree statistics are deterministic") && passed; + passed = check(query_num <= 500000, "high-degree topology bounds cost queries") && passed; return passed; } -bool checkInvalidObstacleRect() +bool checkPartialLayerMacroKeepsSteiner() { + const PlanarRect region(0, 0, 49, 49); + const PlanarRect macro(18, 20, 36, 36); + const PlanarCoord steiner(30, 30); std::vector terminal_list = getBaseTerminalList(); - std::vector planar_obs_list = {PlanarRect(31, 30, 30, 30)}; - TopoRunResult result = runTopoCase(terminal_list, planar_obs_list, getDefaultSearchRegion()); + irt::TBSegmentCostQuery query = GridCostMap(region).getQuery(); + std::vector> baseline_topo = RTTB.getPlanarTopoList(makeTask(terminal_list)); + irt::TBRefineStat stat; + std::vector> selected_topo = RTTB.getPlanarTopoList(makeTask(terminal_list, query), stat); + bool passed = true; - passed = check(isSameTopo(result.flute_topo_list, result.legal_topo_list), "invalid obstacle keeps raw topology") && passed; - passed = checkRepairStat(result.steiner_repair_stat, 0, 0, 0, "invalid obstacle") && passed; + passed = check(isInsideRect(macro, steiner) && containsCoord(baseline_topo, steiner), "partial-layer macro contains raw Steiner") && passed; + passed = check(std::ranges::none_of(terminal_list, [&](const PlanarCoord& terminal) { return isInsideRect(macro, terminal); }), + "partial-layer macro excludes all terminals") + && passed; + for (const PlanarCoord& neighbor : {PlanarCoord(steiner.get_x() - 1, steiner.get_y()), PlanarCoord(steiner.get_x() + 1, steiner.get_y()), + PlanarCoord(steiner.get_x(), steiner.get_y() - 1), PlanarCoord(steiner.get_x(), steiner.get_y() + 1)}) { + passed = check(std::isfinite(query(steiner, neighbor)), "partial-layer macro keeps finite Steiner escape") && passed; + } + passed = check(canonicalizeTopo(selected_topo) == canonicalizeTopo(baseline_topo), "partial-layer macro keeps FLUTE topology") && passed; + passed = check(containsCoord(selected_topo, steiner), "partial-layer macro keeps Steiner coordinate") && passed; + passed = check(stat.shifted_edge_num == 0 && stat.refined_steiner_num == 0 && !stat.used_terminal_mst, + "partial-layer macro does not refine Steiner") + && passed; + passed = check(std::isfinite(getTopoCost(selected_topo, query)), "partial-layer macro topology has finite cost") && passed; + passed = check(isTopoValid(terminal_list, selected_topo, region), "partial-layer macro topology is valid") && passed; return passed; } -bool checkMissingSearchRegion() +bool checkFullLayerMacroCongestionRing() { + const PlanarRect region(0, 0, 49, 49); + const PlanarRect macro(18, 20, 36, 36); + const PlanarRect expanded_macro(16, 18, 38, 38); + const PlanarCoord raw_steiner(30, 30); std::vector terminal_list = getBaseTerminalList(); - std::vector planar_obs_list = {PlanarRect(30, 30, 30, 30)}; - std::vector> flute_topo_list = RTTB.getPlanarTopoList(makeTask(terminal_list)); - irt::TBTask tb_task; - tb_task.set_planar_coord_list(terminal_list); - tb_task.set_planar_obs_list(planar_obs_list); - irt::TBSteinerRepairStat stat = {1, 1, 1}; - std::vector> legal_topo_list = RTTB.getPlanarTopoList(tb_task, stat); + irt::TBSegmentCostQuery query = getMacroRingCostMap(macro, 2, 50).getQuery(); + irt::TBSegmentCostQuery strict_avoid_query = getMacroRingCostMap(macro, 2, kInf).getQuery(); + std::vector> raw_topo = RTTB.getPlanarTopoList(makeTask(terminal_list)); + irt::TBRefineStat stat; + std::vector> selected_topo = RTTB.getPlanarTopoList(makeTask(terminal_list, query, true), stat); + std::vector> repeated_topo = RTTB.getPlanarTopoList(makeTask(terminal_list, query, true)); + bool passed = true; - passed = check(isSameTopo(flute_topo_list, legal_topo_list), "missing search region keeps raw topology") && passed; - passed = checkRepairStat(stat, 0, 0, 0, "missing search region") && passed; + passed = check(isInsideRect(macro, raw_steiner) && containsCoord(raw_topo, raw_steiner), "full-layer macro contains raw Steiner") && passed; + passed = check(std::ranges::none_of(terminal_list, [&](const PlanarCoord& terminal) { return isInsideRect(expanded_macro, terminal); }), + "full-layer macro ring excludes all terminals") + && passed; + passed = check(!std::isfinite(query(PlanarCoord(18, 20), PlanarCoord(19, 20))), "full-layer macro edges are INF") && passed; + passed = check(query(PlanarCoord(16, 18), PlanarCoord(17, 18)) == 50, "macro congestion ring has high finite cost") && passed; + passed = check(query(PlanarCoord(0, 0), PlanarCoord(1, 0)) == 1, "edges outside macro ring keep base cost") && passed; + passed = check(!std::isfinite(getTopoCost(raw_topo, query)), "full-layer macro blocks raw FLUTE topology") && passed; + passed = check(stat.attempted_congestion_flute, "full-layer macro ring attempts congestion FLUTE") && passed; + passed = check(stat.used_congestion_flute || stat.shifted_edge_num > 0, "full-layer macro ring selects a cost-driven topology") && passed; + passed = check(!stat.attempted_steiner_refine && !stat.used_terminal_mst, "full-layer macro ring avoids fallback") + && passed; + passed = check(std::isfinite(getTopoCost(selected_topo, query)), "full-layer macro ring topology has finite cost") && passed; + passed = check(canonicalizeTopo(selected_topo) != canonicalizeTopo(raw_topo), "full-layer macro ring changes raw topology") && passed; + passed = check(std::isfinite(getTopoCost(selected_topo, strict_avoid_query)), "selected topology can avoid macro congestion ring") && passed; + passed = check(std::ranges::none_of(getSteinerCoordList(terminal_list, selected_topo), + [&](const PlanarCoord& steiner) { return isInsideRect(expanded_macro, steiner); }), + "selected Steiner coordinates avoid macro congestion ring") + && passed; + passed = check(isTopoValid(terminal_list, selected_topo, region), "full-layer macro ring topology is valid") && passed; + passed = check(canonicalizeTopo(selected_topo) == canonicalizeTopo(repeated_topo), "full-layer macro ring topology is deterministic") && passed; return passed; } +struct PlotTopoLayer +{ + std::string name; + std::vector> topo_list; + std::string color; + bool dashed = false; +}; + +struct PlotMarker +{ + PlanarCoord coord; + std::string label; + std::string color; +}; + +struct PlotCase +{ + std::string file_name; + std::string title; + std::string summary; + PlanarRect region; + std::vector terminal_list; + std::vector macro_rect_list; + std::string macro_label; + std::vector topo_layer_list; + std::vector marker_list; + irt::TBSegmentCostQuery cost_query; +}; + +struct PlotSteinerUsage +{ + std::set used_set; + std::set unused_set; +}; + +PlotSteinerUsage getPlotSteinerUsage(const PlotCase& plot_case) +{ + std::set terminal_set; + for (const PlanarCoord& terminal : plot_case.terminal_list) { + terminal_set.insert(getCoordKey(terminal)); + } + + PlotSteinerUsage usage; + for (const PlotTopoLayer& layer : plot_case.topo_layer_list) { + std::set& target_set = layer.dashed ? usage.unused_set : usage.used_set; + for (const Segment& topo : layer.topo_list) { + for (const PlanarCoord& coord : {topo.get_first(), topo.get_second()}) { + if (!terminal_set.contains(getCoordKey(coord))) { + target_set.insert(getCoordKey(coord)); + } + } + } + } + for (const CoordKey& used_coord : usage.used_set) { + usage.unused_set.erase(used_coord); + } + return usage; +} + +bool checkSteinerUsageClassification() +{ + PlotCase plot_case; + plot_case.terminal_list = {PlanarCoord(0, 0), PlanarCoord(4, 4)}; + plot_case.topo_layer_list = {{"raw", + {{PlanarCoord(0, 0), PlanarCoord(2, 2)}, + {PlanarCoord(2, 2), PlanarCoord(3, 3)}, + {PlanarCoord(3, 3), PlanarCoord(4, 4)}}, + "", + true}, + {"selected", + {{PlanarCoord(0, 0), PlanarCoord(2, 2)}, + {PlanarCoord(2, 2), PlanarCoord(1, 1)}, + {PlanarCoord(1, 1), PlanarCoord(4, 4)}}, + "", + false}}; + PlotSteinerUsage usage = getPlotSteinerUsage(plot_case); + return check(usage.used_set == std::set{{1, 1}, {2, 2}} && usage.unused_set == std::set{{3, 3}}, + "plot distinguishes used and unused Steiner coordinates"); +} + +struct PlotTransform +{ + double scale = 1; + double origin_x = 0; + double origin_y = 0; + int32_t ll_x = 0; + int32_t ur_y = 0; + + double getX(int32_t x) const { return origin_x + ((x - ll_x) * scale); } + double getY(int32_t y) const { return origin_y + ((ur_y - y) * scale); } +}; + +struct PlotCostEdge +{ + PlanarCoord first; + PlanarCoord second; + double cost = 0; +}; + +std::string escapeXml(const std::string& text) +{ + std::string escaped; + escaped.reserve(text.size()); + for (char ch : text) { + switch (ch) { + case '&': escaped += "&"; break; + case '<': escaped += "<"; break; + case '>': escaped += ">"; break; + case '"': escaped += """; break; + case '\'': escaped += "'"; break; + default: escaped += ch; break; + } + } + return escaped; +} + +std::string formatCost(double cost) +{ + if (!std::isfinite(cost)) { + return "INF"; + } + std::ostringstream stream; + stream << std::fixed << std::setprecision(1) << cost; + return stream.str(); +} + +std::vector getPlotCostEdgeList(const PlotCase& plot_case, double& min_cost, double& max_cost) +{ + min_cost = kInf; + max_cost = 0; + std::vector edge_list; + const PlanarRect& region = plot_case.region; + for (int32_t y = region.get_ll_y(); y <= region.get_ur_y(); y++) { + for (int32_t x = region.get_ll_x(); x < region.get_ur_x(); x++) { + PlanarCoord first(x, y); + PlanarCoord second(x + 1, y); + double cost = plot_case.cost_query(first, second); + edge_list.push_back({first, second, cost}); + if (std::isfinite(cost)) { + min_cost = std::min(min_cost, cost); + max_cost = std::max(max_cost, cost); + } + } + } + for (int32_t x = region.get_ll_x(); x <= region.get_ur_x(); x++) { + for (int32_t y = region.get_ll_y(); y < region.get_ur_y(); y++) { + PlanarCoord first(x, y); + PlanarCoord second(x, y + 1); + double cost = plot_case.cost_query(first, second); + edge_list.push_back({first, second, cost}); + if (std::isfinite(cost)) { + min_cost = std::min(min_cost, cost); + max_cost = std::max(max_cost, cost); + } + } + } + if (!std::isfinite(min_cost)) { + min_cost = 0; + } + max_cost = std::max(max_cost, min_cost); + return edge_list; +} + +std::string getCostColor(double cost, double min_cost, double max_cost) +{ + if (!std::isfinite(cost)) { + return "rgb(185,28,28)"; + } + double range = max_cost - min_cost; + double ratio = range <= 0 ? 0 : std::log1p(std::max(0.0, cost - min_cost)) / std::log1p(range); + int32_t red = static_cast(59 + ((220 - 59) * ratio)); + int32_t green = static_cast(130 + ((38 - 130) * ratio)); + int32_t blue = static_cast(246 + ((38 - 246) * ratio)); + return "rgb(" + std::to_string(red) + "," + std::to_string(green) + "," + std::to_string(blue) + ")"; +} + +PlotTransform getPlotTransform(const PlanarRect& region) +{ + constexpr double canvas_width = 1000; + constexpr double canvas_height = 760; + constexpr double left = 80; + constexpr double right = 60; + constexpr double top = 90; + constexpr double bottom = 120; + double x_span = std::max(1, region.get_ur_x() - region.get_ll_x()); + double y_span = std::max(1, region.get_ur_y() - region.get_ll_y()); + double scale = std::min((canvas_width - left - right) / x_span, (canvas_height - top - bottom) / y_span); + double plot_width = x_span * scale; + double plot_height = y_span * scale; + return {.scale = scale, + .origin_x = left + ((canvas_width - left - right - plot_width) / 2), + .origin_y = top + ((canvas_height - top - bottom - plot_height) / 2), + .ll_x = region.get_ll_x(), + .ur_y = region.get_ur_y()}; +} + +void appendPolyline(std::ostream& stream, const std::vector& coord_list, const PlotTransform& transform, const std::string& color, + double width, bool dashed) +{ + if (coord_list.size() <= 1) { + return; + } + stream << "\n"; +} + +bool writePlotSvg(const std::filesystem::path& plot_dir, const PlotCase& plot_case) +{ + std::filesystem::path output_path = plot_dir / plot_case.file_name; + std::ofstream stream(output_path); + if (!stream) { + std::cerr << "Failed to open plot file '" << output_path.string() << "'\n"; + return false; + } + + PlotTransform transform = getPlotTransform(plot_case.region); + double min_cost = 0; + double max_cost = 0; + std::vector cost_edge_list = getPlotCostEdgeList(plot_case, min_cost, max_cost); + const PlanarRect& region = plot_case.region; + + stream << std::fixed << std::setprecision(2); + stream << "\n" + << "\n" + << "" + << escapeXml(plot_case.title) << "\n" + << "" + << escapeXml(plot_case.summary) << "\n"; + + if ((region.get_ur_x() - region.get_ll_x()) <= 100 && (region.get_ur_y() - region.get_ll_y()) <= 100) { + for (int32_t x = region.get_ll_x(); x <= region.get_ur_x(); x++) { + stream << "\n"; + } + for (int32_t y = region.get_ll_y(); y <= region.get_ur_y(); y++) { + stream << "\n"; + } + } + + for (const PlotCostEdge& edge : cost_edge_list) { + bool is_inf = !std::isfinite(edge.cost); + stream << "\n"; + } + + for (const PlanarRect& macro : plot_case.macro_rect_list) { + double x = transform.getX(macro.get_ll_x()); + double y = transform.getY(macro.get_ur_y()); + double width = transform.getX(macro.get_ur_x()) - x; + double height = transform.getY(macro.get_ll_y()) - y; + stream << "\n"; + } + + for (const PlotTopoLayer& layer : plot_case.topo_layer_list) { + for (const Segment& topo : layer.topo_list) { + appendPolyline(stream, {topo.get_first(), topo.get_second()}, transform, layer.color, layer.dashed ? 3.0 : 4.5, layer.dashed); + } + } + + PlotSteinerUsage steiner_usage = getPlotSteinerUsage(plot_case); + for (const auto& [x, y] : steiner_usage.unused_set) { + stream << "\n"; + } + for (const auto& [x, y] : steiner_usage.used_set) { + stream << "\n"; + } + for (const PlanarCoord& terminal : plot_case.terminal_list) { + stream << "\n"; + } + for (const PlotMarker& marker : plot_case.marker_list) { + double x = transform.getX(marker.coord.get_x()); + double y = transform.getY(marker.coord.get_y()); + stream << "\n" + << "\n" + << "" << escapeXml(marker.label) << "\n"; + } + + double legend_x = 80; + double legend_y = 690; + for (const PlotTopoLayer& layer : plot_case.topo_layer_list) { + stream << "\n" << escapeXml(layer.name) << "\n"; + legend_x += 180; + } + stream << "\n" + << "terminal\n"; + legend_x += 130; + stream << "\n" + << "used Steiner\n"; + legend_x += 160; + stream << "\n" + << "unused Steiner\n"; + if (!plot_case.macro_rect_list.empty()) { + legend_x = 80; + legend_y = 724; + stream << "\n" + << "" << escapeXml(plot_case.macro_label) << "\n"; + } + stream << "" + << "Edge heatmap: blue=low cost, red=high cost, dark red=INF. Lines connect abstract topology endpoints directly." + << "\n\n"; + if (!stream) { + std::cerr << "Failed to write plot file '" << output_path.string() << "'\n"; + return false; + } + return true; +} + +bool generatePlots(const std::filesystem::path& plot_dir) +{ + std::error_code error; + std::filesystem::remove(plot_dir / "index.html", error); + if (error) { + std::cerr << "Failed to remove legacy plot index: " << error.message() << "\n"; + return false; + } + + const PlanarRect region(0, 0, 49, 49); + const std::vector base_terminal_list = getBaseTerminalList(); + irt::TBSegmentCostQuery wire_query = getWireCostQuery(); + std::vector plot_case_list; + + std::vector> baseline = RTTB.getPlanarTopoList(makeTask(base_terminal_list)); + plot_case_list.push_back({.file_name = "01_baseline.svg", + .title = "Baseline FLUTE", + .summary = "wire_length=" + std::to_string(getWireLength(baseline)) + ", estimated_cost=" + + formatCost(getTopoCost(baseline, wire_query)), + .region = region, + .terminal_list = base_terminal_list, + .topo_layer_list = {{"baseline", baseline, "#6b7280", false}}, + .cost_query = wire_query}); + + irt::TBSegmentCostQuery shift_query = [](const PlanarCoord& first, const PlanarCoord& second) { + double cost = getDistance(first, second); + if (first.get_x() == second.get_x() && first.get_x() == 30) { + cost += 100 * getDistance(first, second); + } + return cost; + }; + irt::TBRefineStat shift_stat; + std::vector> shifted = RTTB.getPlanarTopoList(makeTask(base_terminal_list, shift_query), shift_stat); + plot_case_list.push_back({.file_name = "02_cost_shift.svg", + .title = "Cost-driven Steiner shift", + .summary = "baseline_cost=" + formatCost(getTopoCost(baseline, shift_query)) + ", refined_cost=" + + formatCost(getTopoCost(shifted, shift_query)) + ", shifted_edges=" + + std::to_string(shift_stat.shifted_edge_num), + .region = region, + .terminal_list = base_terminal_list, + .topo_layer_list = {{"baseline", baseline, "#6b7280", true}, {"refined", shifted, "#2563eb", false}}, + .cost_query = shift_query}); + + irt::TBSegmentCostQuery congestion_query = [](const PlanarCoord& first, const PlanarCoord& second) { + double cost = getDistance(first, second); + if (first.get_y() == second.get_y() && first.get_y() <= 10) { + cost *= 50; + } + return cost; + }; + std::vector> normal = RTTB.getPlanarTopoList(makeTask(base_terminal_list, congestion_query)); + irt::TBRefineStat congestion_stat; + std::vector> congestion + = RTTB.getPlanarTopoList(makeTask(base_terminal_list, congestion_query, true), congestion_stat); + plot_case_list.push_back({.file_name = "03_congestion_flute.svg", + .title = "Normal vs congestion FLUTE", + .summary = "normal_cost=" + formatCost(getTopoCost(normal, congestion_query)) + ", congestion_cost=" + + formatCost(getTopoCost(congestion, congestion_query)) + ", attempted=" + + std::to_string(congestion_stat.attempted_congestion_flute) + ", used=" + + std::to_string(congestion_stat.used_congestion_flute), + .region = region, + .terminal_list = base_terminal_list, + .topo_layer_list = {{"normal", normal, "#6b7280", true}, {"congestion", congestion, "#16a34a", false}}, + .cost_query = congestion_query}); + + PlanarCoord blocked_coord(20, 20); + irt::TBSegmentCostQuery blocked_query = getBlockedCoordQuery(blocked_coord, false); + std::vector> blocked_normal = RTTB.getPlanarTopoList(makeTask(base_terminal_list, blocked_query)); + irt::TBRefineStat blocked_stat; + std::vector> blocked_congestion + = RTTB.getPlanarTopoList(makeTask(base_terminal_list, blocked_query, true), blocked_stat); + plot_case_list.push_back({.file_name = "04_inf_handling.svg", + .title = "INF edge handling", + .summary = "normal_cost=" + formatCost(getTopoCost(blocked_normal, blocked_query)) + ", selected_cost=" + + formatCost(getTopoCost(blocked_congestion, blocked_query)) + ", congestion_used=" + + std::to_string(blocked_stat.used_congestion_flute), + .region = region, + .terminal_list = base_terminal_list, + .topo_layer_list = {{"normal", blocked_normal, "#6b7280", true}, + {"selected", blocked_congestion, "#16a34a", false}}, + .marker_list = {{blocked_coord, "blocked coordinate", "#dc2626"}}, + .cost_query = blocked_query}); + + std::vector three_pin_terminal_list = {PlanarCoord(0, 0), PlanarCoord(10, 20), PlanarCoord(20, 0)}; + PlanarCoord raw_steiner(10, 0); + PlanarRect three_pin_macro(4, 0, 16, 8); + std::vector> raw_three_pin_topo = RTTB.getPlanarTopoList(makeTask(three_pin_terminal_list)); + irt::TBSegmentCostQuery three_pin_query = getBlockedMacroCostMap(three_pin_macro).getQuery(); + irt::TBRefineStat three_pin_stat; + std::vector> congestion_topo + = RTTB.getPlanarTopoList(makeTask(three_pin_terminal_list, three_pin_query, true), three_pin_stat); + plot_case_list.push_back({.file_name = "05_three_pin_congestion.svg", + .title = "Three-pin congestion FLUTE", + .summary = "attempted=" + std::to_string(three_pin_stat.attempted_congestion_flute) + ", used=" + + std::to_string(three_pin_stat.used_congestion_flute) + ", cost=" + + formatCost(getTopoCost(congestion_topo, three_pin_query)) + ", mst=" + + std::to_string(three_pin_stat.used_terminal_mst), + .region = PlanarRect(0, 0, 24, 24), + .terminal_list = three_pin_terminal_list, + .macro_rect_list = {three_pin_macro}, + .macro_label = "full-layer macro", + .topo_layer_list = {{"raw", raw_three_pin_topo, "#6b7280", true}, {"congestion", congestion_topo, "#2563eb", false}}, + .marker_list = {{raw_steiner, "raw blocked Steiner", "#dc2626"}}, + .cost_query = three_pin_query}); + + std::vector hotspot_terminal_list = getMultiHotspotTerminalList(); + irt::TBSegmentCostQuery hotspot_query = getMultiHotspotCostMap().getQuery(); + std::vector> hotspot_normal = RTTB.getPlanarTopoList(makeTask(hotspot_terminal_list, hotspot_query)); + irt::TBRefineStat hotspot_stat; + std::vector> hotspot_congestion + = RTTB.getPlanarTopoList(makeTask(hotspot_terminal_list, hotspot_query, true), hotspot_stat); + plot_case_list.push_back({.file_name = "06_multi_hotspot.svg", + .title = "Multi-hotspot topology competition", + .summary = "normal_cost=" + formatCost(getTopoCost(hotspot_normal, hotspot_query)) + ", selected_cost=" + + formatCost(getTopoCost(hotspot_congestion, hotspot_query)) + ", congestion_used=" + + std::to_string(hotspot_stat.used_congestion_flute), + .region = region, + .terminal_list = hotspot_terminal_list, + .topo_layer_list = {{"normal", hotspot_normal, "#6b7280", true}, + {"congestion", hotspot_congestion, "#16a34a", false}}, + .cost_query = hotspot_query}); + + std::vector corridor_terminal_list = getCorridorTerminalList(); + irt::TBSegmentCostQuery corridor_query = getCorridorCostMap().getQuery(); + std::vector> corridor_normal = RTTB.getPlanarTopoList(makeTask(corridor_terminal_list, corridor_query)); + irt::TBRefineStat corridor_stat; + std::vector> corridor_selected + = RTTB.getPlanarTopoList(makeTask(corridor_terminal_list, corridor_query, true), corridor_stat); + plot_case_list.push_back({.file_name = "07_finite_corridor.svg", + .title = "Finite corridors through INF field", + .summary = "normal_cost=" + formatCost(getTopoCost(corridor_normal, corridor_query)) + ", selected_cost=" + + formatCost(getTopoCost(corridor_selected, corridor_query)) + ", congestion_used=" + + std::to_string(corridor_stat.used_congestion_flute), + .region = region, + .terminal_list = corridor_terminal_list, + .topo_layer_list = {{"normal", corridor_normal, "#6b7280", true}, + {"selected", corridor_selected, "#16a34a", false}}, + .cost_query = corridor_query}); + + std::vector refine_terminal_list + = {PlanarCoord(0, 0), PlanarCoord(0, 40), PlanarCoord(10, 15), PlanarCoord(25, 30), PlanarCoord(40, 0), PlanarCoord(40, 40)}; + std::vector> refine_raw = RTTB.getPlanarTopoList(makeTask(refine_terminal_list)); + std::vector multi_raw_steiner_list = getSteinerCoordList(refine_terminal_list, refine_raw); + irt::TBSegmentCostQuery refine_query = getSteinerBlockedCostMap(multi_raw_steiner_list).getQuery(); + irt::TBRefineStat refine_stat; + std::vector> refined_topo = RTTB.getPlanarTopoList(makeTask(refine_terminal_list, refine_query, true), refine_stat); + std::vector refine_marker_list; + for (const PlanarCoord& steiner : multi_raw_steiner_list) { + refine_marker_list.push_back({steiner, "blocked Steiner", "#dc2626"}); + } + plot_case_list.push_back({.file_name = "08_high_degree_refine.svg", + .title = "High-degree Steiner refinement", + .summary = "attempted=" + std::to_string(refine_stat.attempted_steiner_refine) + ", used=" + + std::to_string(refine_stat.used_steiner_refine) + ", refined=" + + std::to_string(refine_stat.refined_steiner_num) + ", cost=" + + formatCost(getTopoCost(refined_topo, refine_query)), + .region = region, + .terminal_list = refine_terminal_list, + .topo_layer_list = {{"raw", refine_raw, "#6b7280", true}, {"refined", refined_topo, "#2563eb", false}}, + .marker_list = std::move(refine_marker_list), + .cost_query = refine_query}); + + std::vector stress_terminal_list = getHighDegreeTerminalList(); + irt::TBSegmentCostQuery stress_query = getHighDegreeCostMap().getQuery(); + std::vector> stress_normal = RTTB.getPlanarTopoList(makeTask(stress_terminal_list, stress_query)); + irt::TBRefineStat stress_stat; + std::vector> stress_congestion + = RTTB.getPlanarTopoList(makeTask(stress_terminal_list, stress_query, true), stress_stat); + plot_case_list.push_back({.file_name = "09_high_degree_stress.svg", + .title = "High-degree deterministic stress", + .summary = "pins=" + std::to_string(stress_terminal_list.size()) + ", cost=" + + formatCost(getTopoCost(stress_congestion, stress_query)) + ", shifted=" + + std::to_string(stress_stat.shifted_edge_num) + ", congestion_used=" + + std::to_string(stress_stat.used_congestion_flute), + .region = region, + .terminal_list = stress_terminal_list, + .topo_layer_list = {{"normal", stress_normal, "#6b7280", true}, + {"selected", stress_congestion, "#7c3aed", false}}, + .cost_query = stress_query}); + + PlanarRect partial_macro(18, 20, 36, 36); + PlanarCoord macro_steiner(30, 30); + irt::TBSegmentCostQuery partial_macro_query = GridCostMap(region).getQuery(); + irt::TBRefineStat partial_macro_stat; + std::vector> partial_macro_topo + = RTTB.getPlanarTopoList(makeTask(base_terminal_list, partial_macro_query), partial_macro_stat); + plot_case_list.push_back({.file_name = "10_partial_layer_macro.svg", + .title = "Partial-layer macro keeps Steiner", + .summary = "steiner=(" + std::to_string(macro_steiner.get_x()) + "," + std::to_string(macro_steiner.get_y()) + + "), finite_escape=1, shifted=" + std::to_string(partial_macro_stat.shifted_edge_num) + ", refined=" + + std::to_string(partial_macro_stat.refined_steiner_num), + .region = region, + .terminal_list = base_terminal_list, + .macro_rect_list = {partial_macro}, + .macro_label = "partial-layer macro", + .topo_layer_list = {{"selected", partial_macro_topo, "#0f766e", false}}, + .cost_query = partial_macro_query}); + + PlanarRect full_layer_macro(18, 20, 36, 36); + PlanarCoord blocked_macro_steiner(30, 30); + irt::TBSegmentCostQuery macro_ring_query = getMacroRingCostMap(full_layer_macro, 2, 50).getQuery(); + irt::TBRefineStat macro_ring_stat; + std::vector> macro_ring_topo + = RTTB.getPlanarTopoList(makeTask(base_terminal_list, macro_ring_query, true), macro_ring_stat); + plot_case_list.push_back({.file_name = "11_full_layer_macro_ring.svg", + .title = "Full-layer macro with congestion ring", + .summary = "attempted=" + std::to_string(macro_ring_stat.attempted_congestion_flute) + ", used=" + + std::to_string(macro_ring_stat.used_congestion_flute) + ", shifted=" + + std::to_string(macro_ring_stat.shifted_edge_num) + ", cost=" + + formatCost(getTopoCost(macro_ring_topo, macro_ring_query)) + ", refined=" + + std::to_string(macro_ring_stat.refined_steiner_num), + .region = region, + .terminal_list = base_terminal_list, + .macro_rect_list = {full_layer_macro}, + .macro_label = "full-layer macro", + .topo_layer_list = {{"raw", baseline, "#6b7280", true}, {"selected", macro_ring_topo, "#0f766e", false}}, + .marker_list = {{blocked_macro_steiner, "raw blocked Steiner", "#dc2626"}}, + .cost_query = macro_ring_query}); + + bool generated = true; + for (const PlotCase& plot_case : plot_case_list) { + generated = writePlotSvg(plot_dir, plot_case) && generated; + } + if (generated) { + std::cout << "Generated TOPOBuilder plots in " << std::filesystem::absolute(plot_dir).string() << "\n"; + } + return generated; +} + } // namespace int main(int argc, char* argv[]) { - PlotConfig plot_config; - if (!parsePlotConfig(argc, argv, plot_config)) { + PlotOptions options; + if (!parseOptions(argc, argv, options)) { + printUsage(argv[0]); + return 2; + } + if (options.show_help) { + printUsage(argv[0]); + return 0; + } + if (options.plot_dir.has_value() && !preparePlotDirectory(*options.plot_dir)) { return 2; } @@ -375,18 +1521,27 @@ int main(int argc, char* argv[]) RTTB.init(); bool passed = true; - passed = checkCase1Baseline(plot_config) && passed; - passed = checkCase2IrrelevantObstacle(plot_config) && passed; - passed = checkCase3SingleCellObstacle(plot_config) && passed; - passed = checkCase4RectangularObstacle(plot_config) && passed; - passed = checkCase5TerminalInsideObstacle(plot_config) && passed; - passed = checkCase6OverlappingObstacles(plot_config) && passed; - passed = checkCase7SearchBoundary(plot_config) && passed; - passed = checkCase8FullyBlocked(plot_config) && passed; - passed = checkCase9MultipleSteiner(plot_config) && passed; - passed = checkCase10CollapsedSteinerEdge(plot_config) && passed; - passed = checkInvalidObstacleRect() && passed; - passed = checkMissingSearchRegion() && passed; + passed = checkBaseline() && passed; + passed = checkCostDrivenShift() && passed; + passed = checkCongestionFluteGuard() && passed; + passed = checkCongestionFluteCostGuard() && passed; + passed = checkCongestionFluteQueryBound() && passed; + passed = checkAlignedSegmentQueryOnce() && passed; + passed = checkInfCostPruning() && passed; + passed = checkCongestionFluteInfHandling() && passed; + passed = checkThreePinCongestionAvoidsMacro() && passed; + passed = checkThreePinCongestionOutsidePinBBox() && passed; + passed = checkNormalDefersBlockedSteiner() && passed; + passed = checkMultiHotspotCompetition() && passed; + passed = checkFiniteCorridor() && passed; + passed = checkHighDegreeSteinerRefine() && passed; + passed = checkHighDegreeStress() && passed; + passed = checkPartialLayerMacroKeepsSteiner() && passed; + passed = checkFullLayerMacroCongestionRing() && passed; + passed = checkSteinerUsageClassification() && passed; + if (options.plot_dir.has_value()) { + passed = generatePlots(*options.plot_dir) && passed; + } RTTB.destroy(); irt::TOPOBuilder::destroyInst();