diff --git a/.gitignore b/.gitignore index 73cda30806..8ee3bbcfd7 100755 --- a/.gitignore +++ b/.gitignore @@ -61,6 +61,7 @@ test/** !test/data/test_python_io.py !test/data/test_pyplacedb_rows.py !test/operations/ +!test/operations/test_antenna_benchmark.py !test/operations/test_python_operations.py !test/operations/test_placement_map.py !test/fixtures/ diff --git a/CMakeLists.txt b/CMakeLists.txt index b5ca4f6d75..5237cb6bf7 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,7 @@ # *************************************************************************************** cmake_minimum_required(VERSION 3.11) +cmake_policy(SET CMP0079 NEW) project(ecc) include(CTest) diff --git a/src/database/data/design/db_layout/IdbCellMaster.cpp b/src/database/data/design/db_layout/IdbCellMaster.cpp index 33d8da8c49..bdf484d146 100644 --- a/src/database/data/design/db_layout/IdbCellMaster.cpp +++ b/src/database/data/design/db_layout/IdbCellMaster.cpp @@ -149,6 +149,11 @@ bool IdbCellMaster::is_spacer() return false; } +bool IdbCellMaster::is_antenna_cell() +{ + return _type == CellMasterType::kCoreAntenaCell; +} + bool IdbCellMaster::is_endcap() { if (_type >= CellMasterType::kEndcap && _type <= CellMasterType::kEndcapBottomRight) diff --git a/src/database/data/design/db_layout/IdbCellMaster.h b/src/database/data/design/db_layout/IdbCellMaster.h index d74408f3c7..bd24fa2b1b 100644 --- a/src/database/data/design/db_layout/IdbCellMaster.h +++ b/src/database/data/design/db_layout/IdbCellMaster.h @@ -68,6 +68,7 @@ class IdbCellMaster bool is_io_cell() { return is_pad() || is_pad_filler(); } bool is_logic(); bool is_spacer(); + bool is_antenna_cell(); string& get_name() { return _name; } const bool is_symmetry_x() const { return _symmetry_x; } const bool is_symmetry_y() const { return _symmetry_y; } diff --git a/src/interface/python/py_irt/py_irt.cpp b/src/interface/python/py_irt/py_irt.cpp index 6d8955d968..ed8ebf07a5 100644 --- a/src/interface/python/py_irt/py_irt.cpp +++ b/src/interface/python/py_irt/py_irt.cpp @@ -40,6 +40,9 @@ bool runERT(std::string& config, std::map& config_dict if (!pass) { return false; } + for (auto& [key, value] : config_dict) { + config_map[key] = value; + } RTI.runERT(config_map); return true; } diff --git a/src/interface/tcl/tcl_irt/src/tcl_run_ert.cpp b/src/interface/tcl/tcl_irt/src/tcl_run_ert.cpp index 5f8f40b9d9..ec1ae39692 100644 --- a/src/interface/tcl/tcl_irt/src/tcl_run_ert.cpp +++ b/src/interface/tcl/tcl_irt/src/tcl_run_ert.cpp @@ -26,6 +26,12 @@ TclRunERT::TclRunERT(const char* cmd_name) : TclCmd(cmd_name) { _config_list.push_back(std::make_pair("-stage", ValueType::kString)); _config_list.push_back(std::make_pair("-resolve_congestion", ValueType::kString)); + _config_list.push_back(std::make_pair("-enable_antenna_fix", ValueType::kInt)); + _config_list.push_back(std::make_pair("-antenna_max_iter", ValueType::kInt)); + _config_list.push_back(std::make_pair("-antenna_diode_cells", ValueType::kString)); + _config_list.push_back(std::make_pair("-antenna_report_dir", ValueType::kString)); + _config_list.push_back(std::make_pair("-antenna_search_radius", ValueType::kInt)); + _config_list.push_back(std::make_pair("-antenna_max_jog", ValueType::kInt)); TclUtil::addOption(this, _config_list); } diff --git a/src/operation/CMakeLists.txt b/src/operation/CMakeLists.txt index 6473ed1eab..d0d98ccbca 100644 --- a/src/operation/CMakeLists.txt +++ b/src/operation/CMakeLists.txt @@ -7,3 +7,7 @@ add_subdirectory(iRCX) add_subdirectory(iSTA) add_subdirectory(iCTS) add_subdirectory(iZH) + +if(TARGET irt_early_router AND TARGET izh_interface) + target_link_libraries(irt_early_router PUBLIC izh_interface) +endif() diff --git a/src/operation/iRT/interface/RTInterface.cpp b/src/operation/iRT/interface/RTInterface.cpp index f104903063..a6c5ceecb1 100644 --- a/src/operation/iRT/interface/RTInterface.cpp +++ b/src/operation/iRT/interface/RTInterface.cpp @@ -30,6 +30,10 @@ #include "TOPOBuilder.hpp" #include "TrackAssigner.hpp" #include "ViolationReporter.hpp" +#include "IdbNet.h" +#include "IdbRegularWire.h" +#include "IdbViaMaster.h" +#include "Utility.hpp" #include "feature_irt.h" #include "feature_manager.h" #include "idm.h" @@ -1373,6 +1377,11 @@ void RTInterface::outputGCellGrid() } void RTInterface::outputNetList() +{ + materializeDetailedResult(); +} + +void RTInterface::materializeDetailedResult() { Die& die = RTDM.getDatabase().get_die(); std::vector& net_list = RTDM.getDatabase().get_net_list(); @@ -1392,9 +1401,6 @@ void RTInterface::outputNetList() if (idb_net_list == nullptr) { RTLOG.error(Loc::current(), "The idb net list is empty!"); } - for (idb::IdbNet* idb_net : idb_net_list->get_net_list()) { - idb_net->clear_wire_list(); - } for (auto& [net_idx, idb_segment_list] : net_idb_segment_map) { std::string net_name = net_list[net_idx].get_net_name(); idb::IdbNet* idb_net = idb_net_list->find_net(net_name); @@ -1421,6 +1427,127 @@ void RTInterface::outputNetList() } } +void RTInterface::importDetailedResultFromIdb() +{ + std::vector& net_list = RTDM.getDatabase().get_net_list(); + std::vector& routing_layer_list = RTDM.getDatabase().get_routing_layer_list(); + std::map& routing_layer_name_to_idx_map = RTDM.getDatabase().get_routing_layer_name_to_idx_map(); + std::vector>& layer_via_master_list = RTDM.getDatabase().get_layer_via_master_list(); + ScaleAxis& gcell_axis = RTDM.getDatabase().get_gcell_axis(); + + std::map net_name_to_idx; + for (int32_t i = 0; i < static_cast(net_list.size()); ++i) { + net_name_to_idx[net_list[i].get_net_name()] = i; + } + + std::map>> net_detailed_result_map; + std::map> net_detailed_patch_map; + + idb::IdbNetList* idb_net_list = dmInst->get_idb_def_service()->get_design()->get_net_list(); + if (idb_net_list == nullptr) { + RTLOG.error(Loc::current(), "The idb net list is empty!"); + } + + auto layerIdxByName = [&](const std::string& layer_name) -> int32_t { + auto it = routing_layer_name_to_idx_map.find(layer_name); + if (it != routing_layer_name_to_idx_map.end()) { + return it->second; + } + return -1; + }; + + for (idb::IdbNet* idb_net : idb_net_list->get_net_list()) { + if (idb_net == nullptr || idb_net->get_wire_list() == nullptr) { + continue; + } + auto net_it = net_name_to_idx.find(idb_net->get_net_name()); + if (net_it == net_name_to_idx.end()) { + continue; + } + int32_t net_idx = net_it->second; + for (idb::IdbRegularWire* wire : idb_net->get_wire_list()->get_wire_list()) { + if (wire == nullptr) { + continue; + } + for (idb::IdbRegularWireSegment* seg : wire->get_segment_list()) { + if (seg == nullptr) { + continue; + } + if (seg->is_via() && !seg->get_via_list().empty()) { + idb::IdbVia* via = seg->get_via_list().front(); + if (via == nullptr || via->get_instance() == nullptr) { + continue; + } + idb::IdbLayerShape* bottom = via->get_instance()->get_bottom_layer_shape(); + idb::IdbLayerShape* top = via->get_instance()->get_top_layer_shape(); + if (bottom == nullptr || top == nullptr || bottom->get_layer() == nullptr || top->get_layer() == nullptr) { + continue; + } + int32_t bottom_idx = layerIdxByName(bottom->get_layer()->get_name()); + int32_t top_idx = layerIdxByName(top->get_layer()->get_name()); + if (bottom_idx < 0 || top_idx < 0) { + continue; + } + int32_t x = via->get_coordinate() != nullptr ? via->get_coordinate()->get_x() : 0; + int32_t y = via->get_coordinate() != nullptr ? via->get_coordinate()->get_y() : 0; + if (seg->get_point_start() != nullptr) { + x = seg->get_point_start()->get_x(); + y = seg->get_point_start()->get_y(); + } + Segment segment(LayerCoord(x, y, bottom_idx), LayerCoord(x, y, top_idx)); + int32_t below_idx = std::min(bottom_idx, top_idx); + if (below_idx >= 0 && below_idx < static_cast(layer_via_master_list.size())) { + std::string via_name = via->get_name(); + if (via_name.empty() && via->get_instance() != nullptr) { + via_name = via->get_instance()->get_name(); + } + bool matched = false; + for (int32_t via_idx = 0; via_idx < static_cast(layer_via_master_list[below_idx].size()); ++via_idx) { + if (layer_via_master_list[below_idx][via_idx].get_via_name() == via_name) { + segment.set_via_master_idx(ViaMasterIdx(below_idx, via_idx)); + matched = true; + break; + } + } + if (!matched && !layer_via_master_list[below_idx].empty()) { + segment.set_via_master_idx(layer_via_master_list[below_idx].front().get_via_master_idx()); + } + } + net_detailed_result_map[net_idx].push_back(segment); + } else if (seg->is_rect() && seg->get_layer() != nullptr && seg->get_point_start() != nullptr && seg->get_delta_rect() != nullptr) { + int32_t layer_idx = layerIdxByName(seg->get_layer()->get_name()); + if (layer_idx < 0) { + continue; + } + int32_t x = seg->get_point_start()->get_x(); + int32_t y = seg->get_point_start()->get_y(); + int32_t ll_x = x + seg->get_delta_rect()->get_low_x(); + int32_t ll_y = y + seg->get_delta_rect()->get_low_y(); + int32_t ur_x = x + seg->get_delta_rect()->get_high_x(); + int32_t ur_y = y + seg->get_delta_rect()->get_high_y(); + EXTLayerRect patch; + patch.set_real_rect(PlanarRect(ll_x, ll_y, ur_x, ur_y)); + patch.set_grid_rect(RTUTIL.getClosedGCellGridRect(patch.get_real_rect(), gcell_axis)); + patch.set_layer_idx(layer_idx); + net_detailed_patch_map[net_idx].push_back(patch); + } else if (seg->is_wire() && seg->get_layer() != nullptr && seg->get_point_start() != nullptr && seg->get_point_second() != nullptr) { + int32_t layer_idx = layerIdxByName(seg->get_layer()->get_name()); + if (layer_idx < 0) { + continue; + } + LayerCoord first(seg->get_point_start()->get_x(), seg->get_point_start()->get_y(), layer_idx); + LayerCoord second(seg->get_point_second()->get_x(), seg->get_point_second()->get_y(), layer_idx); + net_detailed_result_map[net_idx].emplace_back(first, second); + } + } + } + } + + (void) routing_layer_list; + RTDM.getDatabase().get_net_detailed_result_map() = std::move(net_detailed_result_map); + RTDM.getDatabase().get_net_detailed_patch_map() = std::move(net_detailed_patch_map); +} + void RTInterface::outputSummary() { ecc_feature::RTSummary& top_rt_summary = featureInst->get_summary()->get_summary_irt(); diff --git a/src/operation/iRT/interface/RTInterface.hpp b/src/operation/iRT/interface/RTInterface.hpp index f73f7853e5..1f7d844871 100644 --- a/src/operation/iRT/interface/RTInterface.hpp +++ b/src/operation/iRT/interface/RTInterface.hpp @@ -124,6 +124,8 @@ class RTInterface void outputGCellGrid(); void outputNetList(); void outputSummary(); + void materializeDetailedResult(); + void importDetailedResultFromIdb(); #endif #if 1 // convert idb diff --git a/src/operation/iRT/source/module/early_router/EarlyRouter.cpp b/src/operation/iRT/source/module/early_router/EarlyRouter.cpp index 9e24c7625b..79fcaf9ad9 100644 --- a/src/operation/iRT/source/module/early_router/EarlyRouter.cpp +++ b/src/operation/iRT/source/module/early_router/EarlyRouter.cpp @@ -23,6 +23,7 @@ #include "TBTask.hpp" #include "TOPOBuilder.hpp" #include "Utility.hpp" +#include "ZHInterface.hpp" namespace irt { @@ -166,6 +167,9 @@ void EarlyRouter::route(std::map config_map) printDetailedSummary(er_model); updateNetResult(er_model); updateNetPatch(er_model); + if (er_model.get_er_com_param().get_enable_antenna_fix()) { + checkAndFixAntenna(er_model); + } // debugPlotERModel(er_model, "dr"); } else { cleanTempResult(er_model); @@ -256,6 +260,56 @@ void EarlyRouter::setERComParam(ERModel& er_model, std::map int32_t { + auto it = config_map.find(key); + if (it == config_map.end()) { + return default_value; + } + if (const int32_t* value = std::any_cast(&it->second)) { + return *value; + } + if (const int* value = std::any_cast(&it->second)) { + return *value; + } + if (const std::string* value = std::any_cast(&it->second)) { + try { + return std::stoi(*value); + } catch (...) { + return default_value; + } + } + return default_value; + }; + auto getAnyString = [&](const std::string& key, const std::string& default_value) -> std::string { + auto it = config_map.find(key); + if (it == config_map.end()) { + return default_value; + } + if (const std::string* value = std::any_cast(&it->second)) { + return *value; + } + return default_value; + }; + bool enable_antenna_fix = (er_com_param.get_stage() >= ERStage::kEdr); + if (config_map.find("-enable_antenna_fix") != config_map.end()) { + enable_antenna_fix = getAnyInt("-enable_antenna_fix", 1) != 0; + } + er_com_param.set_enable_antenna_fix(enable_antenna_fix); + if (config_map.find("-antenna_max_iter") != config_map.end()) { + er_com_param.set_antenna_max_iter(getAnyInt("-antenna_max_iter", 3)); + } + if (config_map.find("-antenna_diode_cells") != config_map.end()) { + er_com_param.set_antenna_diode_cells(getAnyString("-antenna_diode_cells", "")); + } + if (config_map.find("-antenna_report_dir") != config_map.end()) { + er_com_param.set_antenna_report_dir(getAnyString("-antenna_report_dir", "")); + } + if (config_map.find("-antenna_search_radius") != config_map.end()) { + er_com_param.set_antenna_search_radius(getAnyInt("-antenna_search_radius", 0)); + } + if (config_map.find("-antenna_max_jog") != config_map.end()) { + er_com_param.set_antenna_max_jog(getAnyInt("-antenna_max_jog", 0)); + } RTLOG.info(Loc::current(), "stage: ", GetERStageName()(er_com_param.get_stage())); RTLOG.info(Loc::current(), "resolve_congestion: ", er_com_param.get_resolve_congestion()); RTLOG.info(Loc::current(), "max_candidate_point_num: ", er_com_param.get_max_candidate_point_num()); @@ -265,6 +319,7 @@ void EarlyRouter::setERComParam(ERModel& er_model, std::map(detailed_result_list.size()); net_idx++) { std::vector>& routing_segment_list = detailed_result_list[net_idx]; + std::vector> via_segment_list; + for (Segment& segment : routing_segment_list) { + if (segment.get_first().get_planar_coord() == segment.get_second().get_planar_coord() + && std::abs(segment.get_first().get_layer_idx() - segment.get_second().get_layer_idx()) == 1 && segment.hasValidViaMaster()) { + via_segment_list.push_back(segment); + } + } std::vector candidate_root_coord_list; std::map, CmpLayerCoordByXASC> key_coord_pin_map; std::vector& er_pin_list = er_net_list[net_idx].get_er_pin_list(); @@ -2693,7 +2755,26 @@ void EarlyRouter::updateNetResult(ERModel& er_model) } MTree coord_tree = RTUTIL.getTreeByFullFlow(candidate_root_coord_list, routing_segment_list, key_coord_pin_map); for (Segment*>& coord_segment : RTUTIL.getSegListByTree(coord_tree)) { - new_detailed_result_list[net_idx].emplace_back(coord_segment.get_first()->value(), coord_segment.get_second()->value()); + Segment new_segment(coord_segment.get_first()->value(), coord_segment.get_second()->value()); + if (new_segment.get_first().get_planar_coord() == new_segment.get_second().get_planar_coord() + && std::abs(new_segment.get_first().get_layer_idx() - new_segment.get_second().get_layer_idx()) == 1) { + for (Segment& via_segment : via_segment_list) { + if ((new_segment.get_first() == via_segment.get_first() && new_segment.get_second() == via_segment.get_second()) + || (new_segment.get_first() == via_segment.get_second() && new_segment.get_second() == via_segment.get_first())) { + new_segment.set_via_master_idx(via_segment.get_via_master_idx()); + break; + } + } + if (!new_segment.hasValidViaMaster()) { + int32_t below_layer_idx = std::min(new_segment.get_first().get_layer_idx(), new_segment.get_second().get_layer_idx()); + std::vector>& layer_via_master_list = RTDM.getDatabase().get_layer_via_master_list(); + if (0 <= below_layer_idx && below_layer_idx < static_cast(layer_via_master_list.size()) + && !layer_via_master_list[below_layer_idx].empty()) { + new_segment.set_via_master_idx(layer_via_master_list[below_layer_idx].front().get_via_master_idx()); + } + } + } + new_detailed_result_list[net_idx].push_back(new_segment); } } for (int32_t net_idx = 0; net_idx < static_cast(new_detailed_result_list.size()); net_idx++) { @@ -2716,6 +2797,43 @@ void EarlyRouter::updateNetPatch(ERModel& er_model) RTLOG.info(Loc::current(), "Completed", monitor.getStatsInfo()); } +void EarlyRouter::checkAndFixAntenna(ERModel& er_model) +{ + Monitor monitor; + RTLOG.info(Loc::current(), "Starting..."); + + RTDM.getDatabase().get_net_detailed_result_map() = er_model.get_net_detailed_result_map(); + RTDM.getDatabase().get_net_detailed_patch_map() = er_model.get_net_detailed_patch_map(); + RTI.materializeDetailedResult(); + + const ERComParam& param = er_model.get_er_com_param(); + std::map config_map; + config_map["-enable_antenna_fix"] = true; + config_map["-antenna_max_iter"] = param.get_antenna_max_iter(); + if (!param.get_antenna_diode_cells().empty()) { + config_map["-antenna_diode_cells"] = param.get_antenna_diode_cells(); + } + std::string report_dir = param.get_antenna_report_dir(); + if (report_dir.empty()) { + report_dir = RTDM.getConfig().er_temp_directory_path; + } + config_map["report_dir"] = report_dir; + config_map["-antenna_report_dir"] = report_dir; + if (param.get_antenna_search_radius() > 0) { + config_map["-antenna_search_radius"] = param.get_antenna_search_radius(); + } + if (param.get_antenna_max_jog() > 0) { + config_map["-antenna_max_jog"] = param.get_antenna_max_jog(); + } + + ZHI.checkAndFixAntenna(config_map); + RTI.importDetailedResultFromIdb(); + er_model.get_net_detailed_result_map() = RTDM.getDatabase().get_net_detailed_result_map(); + er_model.get_net_detailed_patch_map() = RTDM.getDatabase().get_net_detailed_patch_map(); + + RTLOG.info(Loc::current(), "Completed", monitor.getStatsInfo()); +} + void EarlyRouter::cleanTempResult(ERModel& er_model) { er_model.get_net_detailed_result_map().clear(); diff --git a/src/operation/iRT/source/module/early_router/EarlyRouter.hpp b/src/operation/iRT/source/module/early_router/EarlyRouter.hpp index eaac831332..510a0e4237 100644 --- a/src/operation/iRT/source/module/early_router/EarlyRouter.hpp +++ b/src/operation/iRT/source/module/early_router/EarlyRouter.hpp @@ -123,6 +123,7 @@ class EarlyRouter void routeERBox(ERModel& er_model, ERBox& er_box); void updateNetResult(ERModel& er_model); void updateNetPatch(ERModel& er_model); + void checkAndFixAntenna(ERModel& er_model); void cleanTempResult(ERModel& er_model); void uploadERModel(ERModel& er_model); diff --git a/src/operation/iRT/source/module/early_router/er_data_manager/ERComParam.hpp b/src/operation/iRT/source/module/early_router/er_data_manager/ERComParam.hpp index 492ed88d5c..eaaadcd140 100644 --- a/src/operation/iRT/source/module/early_router/er_data_manager/ERComParam.hpp +++ b/src/operation/iRT/source/module/early_router/er_data_manager/ERComParam.hpp @@ -49,6 +49,12 @@ class ERComParam double get_via_unit() const { return _via_unit; } double get_overflow_unit() const { return _overflow_unit; } int32_t get_schedule_interval() const { return _schedule_interval; } + bool get_enable_antenna_fix() const { return _enable_antenna_fix; } + int32_t get_antenna_max_iter() const { return _antenna_max_iter; } + const std::string& get_antenna_diode_cells() const { return _antenna_diode_cells; } + const std::string& get_antenna_report_dir() const { return _antenna_report_dir; } + int32_t get_antenna_search_radius() const { return _antenna_search_radius; } + int32_t get_antenna_max_jog() const { return _antenna_max_jog; } // setter void set_stage(const ERStage stage) { _stage = stage; } void set_resolve_congestion(std::string& resolve_congestion) { _resolve_congestion = resolve_congestion; } @@ -59,6 +65,12 @@ class ERComParam void set_via_unit(const double via_unit) { _via_unit = via_unit; } void set_overflow_unit(const double overflow_unit) { _overflow_unit = overflow_unit; } void set_schedule_interval(const int32_t schedule_interval) { _schedule_interval = schedule_interval; } + void set_enable_antenna_fix(const bool enable_antenna_fix) { _enable_antenna_fix = enable_antenna_fix; } + void set_antenna_max_iter(const int32_t antenna_max_iter) { _antenna_max_iter = antenna_max_iter; } + void set_antenna_diode_cells(const std::string& antenna_diode_cells) { _antenna_diode_cells = antenna_diode_cells; } + void set_antenna_report_dir(const std::string& antenna_report_dir) { _antenna_report_dir = antenna_report_dir; } + void set_antenna_search_radius(const int32_t antenna_search_radius) { _antenna_search_radius = antenna_search_radius; } + void set_antenna_max_jog(const int32_t antenna_max_jog) { _antenna_max_jog = antenna_max_jog; } private: ERStage _stage = ERStage::kNone; @@ -70,6 +82,12 @@ class ERComParam double _via_unit = 0; double _overflow_unit = 0; int32_t _schedule_interval = 0; + bool _enable_antenna_fix = false; + int32_t _antenna_max_iter = 3; + std::string _antenna_diode_cells; + std::string _antenna_report_dir; + int32_t _antenna_search_radius = 0; + int32_t _antenna_max_jog = 0; }; } // namespace irt diff --git a/src/operation/iZH/CMakeLists.txt b/src/operation/iZH/CMakeLists.txt index 1a0df91767..15612635d9 100644 --- a/src/operation/iZH/CMakeLists.txt +++ b/src/operation/iZH/CMakeLists.txt @@ -16,6 +16,7 @@ set(CMAKE_CXX_STANDARD 20) ############################ debug source module ############################ # set(DEBUG_IZH_ANTENNA_CHECKER ON) +# set(DEBUG_IZH_ANTENNA_FIXER ON) # set(DEBUG_IZH_FILLER_INSERTER ON) # set(DEBUG_IZH_METAL_INSERTER ON) diff --git a/src/operation/iZH/interface/ZHInterface.cpp b/src/operation/iZH/interface/ZHInterface.cpp index 1e52862a84..6c054e8d7e 100644 --- a/src/operation/iZH/interface/ZHInterface.cpp +++ b/src/operation/iZH/interface/ZHInterface.cpp @@ -17,6 +17,7 @@ #include "ZHInterface.hpp" #include "AntennaChecker.hpp" +#include "AntennaEngine.hpp" #include "FillerInserter.hpp" #include "MetalInserter.hpp" @@ -57,6 +58,13 @@ void ZHInterface::checkAntenna(std::map config_map) AntennaChecker::destroyInst(); } +void ZHInterface::checkAndFixAntenna(std::map config_map) +{ + AntennaEngine::initInst(); + ZHAE.checkAndFix(config_map); + AntennaEngine::destroyInst(); +} + #endif #endif diff --git a/src/operation/iZH/interface/ZHInterface.hpp b/src/operation/iZH/interface/ZHInterface.hpp index c6e58d0ce0..56016b8713 100644 --- a/src/operation/iZH/interface/ZHInterface.hpp +++ b/src/operation/iZH/interface/ZHInterface.hpp @@ -81,6 +81,7 @@ class ZHInterface void insertFiller(std::map config_map); void insertMetal(std::map config_map); void checkAntenna(std::map config_map); + void checkAndFixAntenna(std::map config_map); #endif #endif diff --git a/src/operation/iZH/source/module/antenna_checker/AntennaChecker.cpp b/src/operation/iZH/source/module/antenna_checker/AntennaChecker.cpp index 2fc95a34a4..e23ade7499 100644 --- a/src/operation/iZH/source/module/antenna_checker/AntennaChecker.cpp +++ b/src/operation/iZH/source/module/antenna_checker/AntennaChecker.cpp @@ -17,31 +17,19 @@ #include "AntennaChecker.hpp" -#include "ACEdge.hpp" -#include "ACGraphNode.hpp" -#include "ACNormRect.hpp" -#include "ACPinShape.hpp" -#include "ACSegmentTree.hpp" -#include "ACSweepEvent.hpp" -#include "ACUFNode.hpp" -#include "ACUnionFind.hpp" +#include "AntennaPathAnalyzer.hpp" +#include "AntennaResult.hpp" +#include "AntennaRuleEvaluator.hpp" #include "Utility.hpp" #include "IdbDesign.h" -#include "IdbLayer.h" #include "IdbLayout.h" #include "IdbNet.h" -#include "IdbPins.h" -#include "IdbRegularWire.h" -#include "IdbTerm.h" #include "IdbUnits.h" -#include "IdbVias.h" #include "idm.h" namespace izh { -// public - void AntennaChecker::initInst() { if (_ac_instance == nullptr) { @@ -67,8 +55,6 @@ void AntennaChecker::destroyInst() } } -// function - void AntennaChecker::check(std::map config_map) { Monitor monitor; @@ -82,7 +68,71 @@ void AntennaChecker::check(std::map config_map) ZHLOG.info(Loc::current(), "Completed", monitor.getStatsInfo()); } -// private +AntennaResult AntennaChecker::checkToResult(std::map config_map, bool write_report) +{ + ACModel ac_model = initACModel(config_map); + runACModel(ac_model); + if (write_report) { + reportACModel(ac_model); + } + AntennaResult result; + result.get_violation_list() = ac_model.get_violation_list(); + return result; +} + +void AntennaChecker::checkNets(ACModel& ac_model, const std::vector& net_list) +{ + idb::IdbDesign* design = nullptr; + if (dmInst != nullptr) { + auto* def_service = dmInst->get_idb_def_service(); + if (def_service != nullptr) { + design = def_service->get_design(); + } + } + if (!design || !design->get_layout()) { + return; + } + + if (ac_model.get_rule_list().empty()) { + AntennaRuleEvaluator::initLayers(ac_model, design); + } + + std::vector signal_nets; + signal_nets.reserve(net_list.size()); + for (idb::IdbNet* net : net_list) { + if (net && net->is_signal()) { + signal_nets.push_back(net); + } + } + + const int nthreads = std::max(1u, std::thread::hardware_concurrency()); + std::vector> local_violations(nthreads); + +#pragma omp parallel for schedule(dynamic, 16) num_threads(nthreads) + for (long long i = 0; i < static_cast(signal_nets.size()); ++i) { + const int tid = omp_get_thread_num(); + AntennaPathAnalyzer::checkNet(ac_model, design, signal_nets[static_cast(i)], local_violations[tid]); + } + + std::vector& violation_list = ac_model.get_violation_list(); + std::unordered_set touched; + for (idb::IdbNet* net : signal_nets) { + touched.insert(net->get_net_name()); + } + violation_list.erase(std::remove_if(violation_list.begin(), violation_list.end(), + [&](const ACViolation& v) { return touched.count(v.net_name) > 0; }), + violation_list.end()); + + for (auto& v : local_violations) { + violation_list.insert(violation_list.end(), std::make_move_iterator(v.begin()), std::make_move_iterator(v.end())); + } + + std::sort(violation_list.begin(), violation_list.end(), [](const ACViolation& a, const ACViolation& b) { + return std::tie(a.net_name, a.layer_name, a.type, a.ratio, a.threshold, a.lx, a.ly, a.hx, a.hy) + < std::tie(b.net_name, b.layer_name, b.type, b.ratio, b.threshold, b.lx, b.ly, b.hx, b.hy); + }); + ac_model.set_violation_num(static_cast(violation_list.size())); +} ACModel AntennaChecker::initACModel(std::map& config_map) { @@ -137,7 +187,7 @@ void AntennaChecker::runACModel(ACModel& ac_model) return; } - initLayers(ac_model, design); + AntennaRuleEvaluator::initLayers(ac_model, design); if (ac_model.get_rule_list().empty()) { ZHLOG.info(Loc::current(), "No antenna rules defined in technology; skipping antenna check"); @@ -164,7 +214,7 @@ void AntennaChecker::runACModel(ACModel& ac_model) #pragma omp parallel for schedule(dynamic, 16) num_threads(nthreads) for (long long i = 0; i < static_cast(signal_nets.size()); ++i) { const int tid = omp_get_thread_num(); - checkNet(ac_model, design, signal_nets[static_cast(i)], local_violations[tid]); + AntennaPathAnalyzer::checkNet(ac_model, design, signal_nets[static_cast(i)], local_violations[tid]); } for (auto& v : local_violations) { @@ -172,8 +222,8 @@ void AntennaChecker::runACModel(ACModel& ac_model) } std::sort(violation_list.begin(), violation_list.end(), [](const ACViolation& a, const ACViolation& b) { - return std::tie(a.net_name, a.layer_name, a.type, a.ratio, a.threshold, a.lx, a.ly, a.hx, a.hy) < - std::tie(b.net_name, b.layer_name, b.type, b.ratio, b.threshold, b.lx, b.ly, b.hx, b.hy); + return std::tie(a.net_name, a.layer_name, a.type, a.ratio, a.threshold, a.lx, a.ly, a.hx, a.hy) + < std::tie(b.net_name, b.layer_name, b.type, b.ratio, b.threshold, b.lx, b.ly, b.hx, b.hy); }); ac_model.set_violation_num(static_cast(violation_list.size())); @@ -237,1452 +287,8 @@ void AntennaChecker::initDatabaseInfo(ACModel& ac_model) } } -void AntennaChecker::initLayers(ACModel& ac_model, idb::IdbDesign* design) -{ - std::vector& rules = ac_model.get_rule_list(); - std::vector>& rule_map = ac_model.get_rule_map(); - std::vector& thickness_by_order = ac_model.get_thickness_by_order(); - std::vector& conductive_order = ac_model.get_conductive_order(); - int& max_layer_order = ac_model.get_max_layer_order(); - if (!design || !design->get_layout()) { - return; - } - - idb::IdbLayers* layers = design->get_layout()->get_layers(); - if (!layers) { - return; - } - - rules.clear(); - rule_map.clear(); - thickness_by_order.clear(); - - max_layer_order = 0; - - for (idb::IdbLayer* layer : layers->get_layers()) { - if (layer) { - max_layer_order = std::max(max_layer_order, static_cast(layer->get_order())); - } - } - - // Routing layers - for (idb::IdbLayer* layer : layers->get_routing_layers()) { - idb::IdbLayerRouting* routing = dynamic_cast(layer); - if (routing == nullptr) { - continue; - } - - bool has_any_rule = false; - - has_any_rule |= routing->has_antenna_area_ratio(); - has_any_rule |= routing->has_antenna_cum_area_ratio(); - has_any_rule |= routing->has_antenna_area_factor(); - has_any_rule |= routing->has_antenna_side_area_ratio(); - has_any_rule |= routing->has_antenna_cum_side_area_ratio(); - has_any_rule |= routing->has_antenna_side_area_factor(); - has_any_rule |= routing->has_antenna_gate_plus_diff(); - has_any_rule |= routing->has_antenna_area_minus_diff(); - has_any_rule |= routing->has_antenna_diff_area_ratio(); - has_any_rule |= routing->has_antenna_cum_diff_area_ratio(); - has_any_rule |= routing->has_antenna_diff_side_area_ratio(); - has_any_rule |= routing->has_antenna_cum_diff_side_area_ratio(); - has_any_rule |= routing->has_antenna_diff_area_ratio_pwl(); - has_any_rule |= routing->has_antenna_cum_diff_area_ratio_pwl(); - has_any_rule |= routing->has_antenna_diff_side_area_ratio_pwl(); - has_any_rule |= routing->has_antenna_cum_diff_side_area_ratio_pwl(); - has_any_rule |= routing->has_antenna_area_diff_reduce_pwl(); - has_any_rule |= routing->get_antenna_cum_routing_plus_cut(); - - if (!has_any_rule) { - continue; - } - - ACAntennaRule rule; - rule.layer_name = routing->get_name(); - rule.layer_order = static_cast(routing->get_order()); - rule.is_routing = true; - rule.thickness_um = Utility::getRatio(static_cast(routing->get_thickness()), ac_model.get_micron_dbu()); - - if (routing->has_antenna_area_ratio()) { - rule.area_ratio = routing->get_antenna_area_ratio(); - } - if (routing->has_antenna_cum_area_ratio()) { - rule.cum_area_ratio = routing->get_antenna_cum_area_ratio(); - } - if (routing->has_antenna_area_factor()) { - rule.area_factor = routing->get_antenna_area_factor(); - } - - rule.area_factor_diffuse_only = routing->get_antenna_area_factor_diffuse_only(); - - if (routing->has_antenna_side_area_ratio()) { - rule.side_area_ratio = routing->get_antenna_side_area_ratio(); - } - if (routing->has_antenna_cum_side_area_ratio()) { - rule.cum_side_area_ratio = routing->get_antenna_cum_side_area_ratio(); - } - if (routing->has_antenna_side_area_factor()) { - rule.side_area_factor = routing->get_antenna_side_area_factor(); - } - - rule.side_area_factor_diffuse_only = routing->get_antenna_side_area_factor_diffuse_only(); - - if (routing->has_antenna_gate_plus_diff()) { - rule.gate_plus_diff = routing->get_antenna_gate_plus_diff(); - } - if (routing->has_antenna_area_minus_diff()) { - rule.area_minus_diff = routing->get_antenna_area_minus_diff(); - } - if (routing->has_antenna_diff_area_ratio()) { - rule.diff_area_ratio = routing->get_antenna_diff_area_ratio(); - } - if (routing->has_antenna_cum_diff_area_ratio()) { - rule.cum_diff_area_ratio = routing->get_antenna_cum_diff_area_ratio(); - } - if (routing->has_antenna_diff_side_area_ratio()) { - rule.diff_side_area_ratio = routing->get_antenna_diff_side_area_ratio(); - } - if (routing->has_antenna_cum_diff_side_area_ratio()) { - rule.cum_diff_side_area_ratio = routing->get_antenna_cum_diff_side_area_ratio(); - } - - rule.cum_routing_plus_cut = routing->get_antenna_cum_routing_plus_cut(); - - if (routing->has_antenna_diff_area_ratio_pwl()) { - rule.diff_area_ratio_pwl = routing->get_antenna_diff_area_ratio_pwl(); - } - if (routing->has_antenna_cum_diff_area_ratio_pwl()) { - rule.cum_diff_area_ratio_pwl = routing->get_antenna_cum_diff_area_ratio_pwl(); - } - if (routing->has_antenna_diff_side_area_ratio_pwl()) { - rule.diff_side_area_ratio_pwl = routing->get_antenna_diff_side_area_ratio_pwl(); - } - if (routing->has_antenna_cum_diff_side_area_ratio_pwl()) { - rule.cum_diff_side_area_ratio_pwl = routing->get_antenna_cum_diff_side_area_ratio_pwl(); - } - if (routing->has_antenna_area_diff_reduce_pwl()) { - rule.area_diff_reduce_pwl = routing->get_antenna_area_diff_reduce_pwl(); - } - - rules.push_back(rule); - } - - // Cut layers - for (idb::IdbLayer* layer : layers->get_cut_layers()) { - idb::IdbLayerCut* cut = dynamic_cast(layer); - if (cut == nullptr) { - continue; - } - - bool has_any_rule = false; - - has_any_rule |= cut->has_antenna_area_ratio(); - has_any_rule |= cut->has_antenna_cum_area_ratio(); - has_any_rule |= cut->has_antenna_area_factor(); - has_any_rule |= cut->has_antenna_diff_area_ratio(); - has_any_rule |= cut->has_antenna_cum_diff_area_ratio(); - has_any_rule |= cut->has_antenna_gate_plus_diff(); - has_any_rule |= cut->has_antenna_area_minus_diff(); - has_any_rule |= cut->has_antenna_diff_area_ratio_pwl(); - has_any_rule |= cut->has_antenna_cum_diff_area_ratio_pwl(); - has_any_rule |= cut->has_antenna_area_diff_reduce_pwl(); - has_any_rule |= cut->get_antenna_cum_routing_plus_cut(); - - if (!has_any_rule) { - continue; - } - - ACAntennaRule rule; - rule.layer_name = cut->get_name(); - rule.layer_order = static_cast(cut->get_order()); - rule.is_routing = false; - - if (cut->has_antenna_area_factor()) { - rule.area_factor = cut->get_antenna_area_factor(); - } - rule.area_factor_diffuse_only = cut->get_antenna_area_factor_diffuse_only(); - - if (cut->has_antenna_area_ratio()) { - rule.area_ratio = cut->get_antenna_area_ratio(); - } - if (cut->has_antenna_cum_area_ratio()) { - rule.cum_area_ratio = cut->get_antenna_cum_area_ratio(); - } - if (cut->has_antenna_diff_area_ratio()) { - rule.diff_area_ratio = cut->get_antenna_diff_area_ratio(); - } - if (cut->has_antenna_cum_diff_area_ratio()) { - rule.cum_diff_area_ratio = cut->get_antenna_cum_diff_area_ratio(); - } - if (cut->has_antenna_gate_plus_diff()) { - rule.gate_plus_diff = cut->get_antenna_gate_plus_diff(); - } - if (cut->has_antenna_area_minus_diff()) { - rule.area_minus_diff = cut->get_antenna_area_minus_diff(); - } - - rule.cum_routing_plus_cut = cut->get_antenna_cum_routing_plus_cut(); - - if (cut->has_antenna_diff_area_ratio_pwl()) { - rule.diff_area_ratio_pwl = cut->get_antenna_diff_area_ratio_pwl(); - } - if (cut->has_antenna_cum_diff_area_ratio_pwl()) { - rule.cum_diff_area_ratio_pwl = cut->get_antenna_cum_diff_area_ratio_pwl(); - } - if (cut->has_antenna_area_diff_reduce_pwl()) { - rule.area_diff_reduce_pwl = cut->get_antenna_area_diff_reduce_pwl(); - } - - rules.push_back(rule); - } - - // Masterslice (POLY) layers - for (idb::IdbLayer* layer : layers->get_layers()) { - idb::IdbLayerMasterslice* ms = dynamic_cast(layer); - if (ms == nullptr) { - continue; - } - - bool has_any_rule = false; - - has_any_rule |= ms->has_antenna_area_ratio(); - has_any_rule |= ms->has_antenna_cum_area_ratio(); - has_any_rule |= ms->has_antenna_area_factor(); - has_any_rule |= ms->has_antenna_side_area_ratio(); - has_any_rule |= ms->has_antenna_cum_side_area_ratio(); - has_any_rule |= ms->has_antenna_side_area_factor(); - has_any_rule |= ms->has_antenna_gate_plus_diff(); - has_any_rule |= ms->has_antenna_area_minus_diff(); - has_any_rule |= ms->has_antenna_diff_area_ratio(); - has_any_rule |= ms->has_antenna_cum_diff_area_ratio(); - has_any_rule |= ms->has_antenna_diff_side_area_ratio(); - has_any_rule |= ms->has_antenna_cum_diff_side_area_ratio(); - has_any_rule |= ms->has_antenna_diff_area_ratio_pwl(); - has_any_rule |= ms->has_antenna_cum_diff_area_ratio_pwl(); - has_any_rule |= ms->has_antenna_diff_side_area_ratio_pwl(); - has_any_rule |= ms->has_antenna_cum_diff_side_area_ratio_pwl(); - has_any_rule |= ms->has_antenna_area_diff_reduce_pwl(); - has_any_rule |= ms->get_antenna_cum_routing_plus_cut(); - - if (!has_any_rule) { - continue; - } - - ACAntennaRule rule; - rule.layer_name = ms->get_name(); - rule.layer_order = static_cast(ms->get_order()); - rule.is_routing = true; - rule.thickness_um = Utility::getRatio(static_cast(ms->get_thickness()), ac_model.get_micron_dbu()); - - if (ms->has_antenna_area_ratio()) { - rule.area_ratio = ms->get_antenna_area_ratio(); - } - if (ms->has_antenna_cum_area_ratio()) { - rule.cum_area_ratio = ms->get_antenna_cum_area_ratio(); - } - if (ms->has_antenna_area_factor()) { - rule.area_factor = ms->get_antenna_area_factor(); - } - - rule.area_factor_diffuse_only = ms->get_antenna_area_factor_diffuse_only(); - - if (ms->has_antenna_side_area_ratio()) { - rule.side_area_ratio = ms->get_antenna_side_area_ratio(); - } - if (ms->has_antenna_cum_side_area_ratio()) { - rule.cum_side_area_ratio = ms->get_antenna_cum_side_area_ratio(); - } - if (ms->has_antenna_side_area_factor()) { - rule.side_area_factor = ms->get_antenna_side_area_factor(); - } - - rule.side_area_factor_diffuse_only = ms->get_antenna_side_area_factor_diffuse_only(); - - if (ms->has_antenna_gate_plus_diff()) { - rule.gate_plus_diff = ms->get_antenna_gate_plus_diff(); - } - if (ms->has_antenna_area_minus_diff()) { - rule.area_minus_diff = ms->get_antenna_area_minus_diff(); - } - if (ms->has_antenna_diff_area_ratio()) { - rule.diff_area_ratio = ms->get_antenna_diff_area_ratio(); - } - if (ms->has_antenna_cum_diff_area_ratio()) { - rule.cum_diff_area_ratio = ms->get_antenna_cum_diff_area_ratio(); - } - if (ms->has_antenna_diff_side_area_ratio()) { - rule.diff_side_area_ratio = ms->get_antenna_diff_side_area_ratio(); - } - if (ms->has_antenna_cum_diff_side_area_ratio()) { - rule.cum_diff_side_area_ratio = ms->get_antenna_cum_diff_side_area_ratio(); - } - - rule.cum_routing_plus_cut = ms->get_antenna_cum_routing_plus_cut(); - - if (ms->has_antenna_diff_area_ratio_pwl()) { - rule.diff_area_ratio_pwl = ms->get_antenna_diff_area_ratio_pwl(); - } - if (ms->has_antenna_cum_diff_area_ratio_pwl()) { - rule.cum_diff_area_ratio_pwl = ms->get_antenna_cum_diff_area_ratio_pwl(); - } - if (ms->has_antenna_diff_side_area_ratio_pwl()) { - rule.diff_side_area_ratio_pwl = ms->get_antenna_diff_side_area_ratio_pwl(); - } - if (ms->has_antenna_cum_diff_side_area_ratio_pwl()) { - rule.cum_diff_side_area_ratio_pwl = ms->get_antenna_cum_diff_side_area_ratio_pwl(); - } - if (ms->has_antenna_area_diff_reduce_pwl()) { - rule.area_diff_reduce_pwl = ms->get_antenna_area_diff_reduce_pwl(); - } - - rules.push_back(rule); - } - - if (max_layer_order < 0) { - max_layer_order = 0; - } - - rule_map.resize(static_cast(max_layer_order) + 1); - thickness_by_order.assign(static_cast(max_layer_order) + 1, 0.0); - conductive_order.assign(static_cast(max_layer_order) + 1, false); - - for (const auto& rule : rules) { - if (rule.layer_order >= 0 && static_cast(rule.layer_order) < rule_map.size()) { - rule_map[rule.layer_order].push_back(&rule); - conductive_order[rule.layer_order] = true; - - if (rule.is_routing && rule.thickness_um > 0.0 && thickness_by_order[rule.layer_order] <= 0.0) { - thickness_by_order[rule.layer_order] = rule.thickness_um; - } - } - } -} - -void AntennaChecker::checkNet(ACModel& ac_model, idb::IdbDesign* design, idb::IdbNet* net, - std::vector& out_violations) +void AntennaChecker::writeReport(const ACModel& ac_model) const { - const int micron_dbu = ac_model.get_micron_dbu(); - const std::vector& thickness_by_order = ac_model.get_thickness_by_order(); - const std::vector& conductive_order = ac_model.get_conductive_order(); - const int max_layer_order = ac_model.get_max_layer_order(); - std::atomic& comps_without_gate = ac_model.get_comps_without_gate(); - std::atomic& skipped_segments = ac_model.get_skipped_segments(); - std::atomic& conductors_out_of_range = ac_model.get_conductors_out_of_range(); - std::atomic& partial_areas_dropped = ac_model.get_partial_areas_dropped(); - if (net == nullptr) { - return; - } - - const std::string& net_name = net->get_net_name(); - - std::vector nodes; - std::vector all_edges; - - size_t estimated_nodes = 0; - - if (net->get_instance_pin_list() != nullptr) { - estimated_nodes += net->get_instance_pin_list()->get_pin_list().size() * 2; - } - - if (net->get_io_pins() != nullptr) { - estimated_nodes += net->get_io_pins()->get_pin_list().size() * 2; - } - - if (net->get_wire_list() != nullptr) { - estimated_nodes += net->get_wire_list()->get_wire_list().size() * 4; - } - - nodes.reserve(estimated_nodes); - all_edges.reserve(estimated_nodes * 2); - - int max_time = 0; - int max_shape_layer = 0; - - auto addEdge = [&](int a, int b, int t) { - if (a < 0 || b < 0 || a == b) { - return; - } - if (a > b) { - std::swap(a, b); - } - if (t < 0) { - t = 0; - } - if (t > max_layer_order) { - return; - } - - max_time = std::max(max_time, t); - all_edges.push_back({a, b, t}); - }; - - auto createGateNode = [&](double gate_area, double diff_area, bool provides_diff) -> int { - if (Utility::equalDoubleByError(gate_area, 0.0, ZH_ERROR) && - Utility::equalDoubleByError(diff_area, 0.0, ZH_ERROR) && !provides_diff) { - return -1; - } - - ACGraphNode n; - n.id = static_cast(nodes.size()); - n.time = 0; - n.is_conductor = false; - - n.gate_area = gate_area; - n.diff_area = diff_area; - n.provides_diff = provides_diff || !Utility::equalDoubleByError(diff_area, 0.0, ZH_ERROR); - - nodes.push_back(std::move(n)); - return static_cast(nodes.size()) - 1; - }; - - auto createConductorNode = [&](int order, bool is_routing, bool is_cut, const std::vector& rects) -> int { - if (order < 0 || order > max_layer_order || rects.empty()) { - return -1; - } - - ACGraphNode n; - n.id = static_cast(nodes.size()); - n.time = order; - n.is_conductor = true; - n.is_routing = is_routing; - n.is_cut = is_cut; - - n.shapes.reserve(rects.size()); - for (const auto& r : rects) { - n.shapes.push_back({order, r}); - } - - max_time = std::max(max_time, order); - max_shape_layer = std::max(max_shape_layer, order); - - nodes.push_back(std::move(n)); - return static_cast(nodes.size()) - 1; - }; - - auto isRoutingLike = [&](idb::IdbLayer* layer, int order) -> bool { - if (layer == nullptr) { - return false; - } - if (layer->is_routing()) { - return true; - } - if (layer->get_type() == idb::IdbLayerType::kLayerMasterslice && order >= 0 && - static_cast(order) < conductive_order.size() && conductive_order[order]) { - return true; - } - return false; - }; - - auto createVirtualConductorNode = [&](int order, double area, bool is_routing, bool is_side, bool is_cut) -> int { - ACGraphNode n; - n.id = static_cast(nodes.size()); - n.time = order; - n.is_conductor = true; - n.is_routing = is_routing; - n.is_side = is_side; - n.is_cut = is_cut; - n.declared_area = area; - - max_time = std::max(max_time, order); - nodes.push_back(std::move(n)); - return static_cast(nodes.size()) - 1; - }; - - auto processPin = [&](idb::IdbPin* pin, bool instance_pin) { - if (pin == nullptr) { - return; - } - - double gate_area = 0.0; - double diff_area = 0.0; - bool provides_diff = false; - - readPinAntennaInfo(ac_model, pin, instance_pin, gate_area, diff_area, provides_diff); - int gate_node = createGateNode(gate_area, diff_area, provides_diff); - - std::vector pin_shapes; - - for (idb::IdbLayerShape* shape : pin->get_port_box_list()) { - if (shape == nullptr || shape->get_layer() == nullptr) { - continue; - } - - idb::IdbLayer* layer = shape->get_layer(); - int order = static_cast(layer->get_order()); - - if (order < 0 || order > max_layer_order) { - conductors_out_of_range.fetch_add(1, std::memory_order_relaxed); - continue; - } - - bool routing = isRoutingLike(layer, order); - bool cut = layer->is_cut(); - - for (idb::IdbRect* r : shape->get_rect_list()) { - if (r != nullptr) { - pin_shapes.push_back({order, routing, cut, *r}); - } - } - } - - int first_shape_node = -1; - - if (!pin_shapes.empty()) { - std::sort(pin_shapes.begin(), pin_shapes.end(), [](const ACPinShape& a, const ACPinShape& b) { - return a.order < b.order; - }); - - size_t i = 0; - while (i < pin_shapes.size()) { - int order = pin_shapes[i].order; - - bool routing = false; - bool cut = false; - std::vector rects; - - while (i < pin_shapes.size() && pin_shapes[i].order == order) { - routing = routing || pin_shapes[i].routing; - cut = cut || pin_shapes[i].cut; - rects.push_back(pin_shapes[i].rect); - ++i; - } - - int shape_node = createConductorNode(order, routing, cut, rects); - - if (shape_node >= 0 && first_shape_node < 0) { - first_shape_node = shape_node; - } - - if (gate_node >= 0 && shape_node >= 0) { - addEdge(gate_node, shape_node, order); - } - } - } - - idb::IdbTerm* term = pin->get_term(); - if (term != nullptr && (term->has_antenna_partial_metal_area() || - term->has_antenna_partial_metal_side_area() || - term->has_antenna_partial_cut_area())) { - int anchor = (gate_node >= 0) ? gate_node : first_shape_node; - - idb::IdbLayers* layers = (design && design->get_layout()) ? design->get_layout()->get_layers() : nullptr; - - auto addPartialArea = [&](const std::string& layer_name, double area, bool is_routing, bool is_side, bool is_cut) { - if (anchor < 0 || layers == nullptr) { - partial_areas_dropped.fetch_add(1, std::memory_order_relaxed); - return; - } - - int order = layers->get_layer_order(layer_name); - if (order < 0 || order > max_layer_order) { - partial_areas_dropped.fetch_add(1, std::memory_order_relaxed); - return; - } - - int vnode = createVirtualConductorNode(order, area, is_routing, is_side, is_cut); - addEdge(anchor, vnode, order); - }; - - for (const auto& [layer_name, area] : term->get_antenna_partial_metal_area()) { - addPartialArea(layer_name, area, true, false, false); - } - for (const auto& [layer_name, area] : term->get_antenna_partial_metal_side_area()) { - addPartialArea(layer_name, area, false, true, false); - } - for (const auto& [layer_name, area] : term->get_antenna_partial_cut_area()) { - addPartialArea(layer_name, area, false, false, true); - } - } - }; - - if (net->get_instance_pin_list() != nullptr) { - for (idb::IdbPin* pin : net->get_instance_pin_list()->get_pin_list()) { - processPin(pin, true); - } - } - - if (net->get_io_pins() != nullptr) { - for (idb::IdbPin* pin : net->get_io_pins()->get_pin_list()) { - processPin(pin, false); - } - } - - if (net->get_wire_list() != nullptr) { - for (idb::IdbRegularWire* wire : net->get_wire_list()->get_wire_list()) { - if (wire == nullptr) { - continue; - } - - for (idb::IdbRegularWireSegment* seg : wire->get_segment_list()) { - if (seg == nullptr) { - continue; - } - - if (seg->is_wire()) { - idb::IdbLayer* layer = seg->get_layer(); - - if (layer != nullptr) { - int order = static_cast(layer->get_order()); - - if (isRoutingLike(layer, order)) { - if (order < 0 || order > max_layer_order) { - conductors_out_of_range.fetch_add(1, std::memory_order_relaxed); - } else { - std::vector rects; - rects.push_back(seg->get_segment_rect()); - createConductorNode(order, true, false, rects); - } - } else { - skipped_segments.fetch_add(1, std::memory_order_relaxed); - } - } else { - skipped_segments.fetch_add(1, std::memory_order_relaxed); - } - } - - if (seg->is_via()) { - for (idb::IdbVia* via : seg->get_via_list()) { - if (via == nullptr) { - continue; - } - - auto collectShapeRects = [&](idb::IdbLayerShape& shape, int& order, bool& is_routing, - bool& is_cut) -> std::vector { - std::vector rects; - idb::IdbLayer* layer = shape.get_layer(); - if (layer == nullptr) { - return rects; - } - - order = static_cast(layer->get_order()); - is_routing = layer->is_routing(); - is_cut = layer->is_cut(); - - if (order < 0 || order > max_layer_order) { - return rects; - } - - for (idb::IdbRect* r : shape.get_rect_list()) { - if (r != nullptr) { - rects.push_back(*r); - } - } - - return rects; - }; - - idb::IdbLayerShape bottom_shape = via->get_bottom_layer_shape(); - idb::IdbLayerShape cut_shape = via->get_cut_layer_shape(); - idb::IdbLayerShape top_shape = via->get_top_layer_shape(); - - int bottom_order = -1; - bool bottom_routing = false; - bool bottom_cut = false; - - int cut_order = -1; - bool cut_routing = false; - bool cut_is_cut = false; - - int top_order = -1; - bool top_routing = false; - bool top_cut = false; - - auto bottom_rects = collectShapeRects(bottom_shape, bottom_order, bottom_routing, bottom_cut); - auto cut_rects = collectShapeRects(cut_shape, cut_order, cut_routing, cut_is_cut); - auto top_rects = collectShapeRects(top_shape, top_order, top_routing, top_cut); - - int b_id = createConductorNode(bottom_order, bottom_routing, bottom_cut, bottom_rects); - int c_id = createConductorNode(cut_order, cut_routing, cut_is_cut, cut_rects); - int t_id = createConductorNode(top_order, top_routing, top_cut, top_rects); - - if (b_id >= 0 && c_id >= 0) { - addEdge(b_id, c_id, nodes[c_id].time); - } - if (c_id >= 0 && t_id >= 0) { - addEdge(c_id, t_id, nodes[t_id].time); - } - if (b_id >= 0 && t_id >= 0 && c_id < 0) { - addEdge(b_id, t_id, nodes[t_id].time); - } - } - } - - if (!seg->is_wire() && !seg->is_via()) { - skipped_segments.fetch_add(1, std::memory_order_relaxed); - } - } - } - } - - if (nodes.empty()) { - return; - } - - int max_layer_idx = std::max(max_time, max_shape_layer); - if (max_layer_idx < 0) { - return; - } - - // 4. Same-layer geometric overlap edges - std::vector>> items_by_layer(static_cast(max_layer_idx) + 1); - - for (size_t i = 0; i < nodes.size(); ++i) { - if (!nodes[i].is_conductor) { - continue; - } - - for (const auto& s : nodes[i].shapes) { - if (s.layer_order < 0 || s.layer_order > max_layer_idx) { - continue; - } - - BGPointInt p_min(s.rect.get_low_x(), s.rect.get_low_y()); - BGPointInt p_max(s.rect.get_high_x(), s.rect.get_high_y()); - BGRectInt b_rect(p_min, p_max); - - items_by_layer[s.layer_order].emplace_back(b_rect, static_cast(i)); - } - } - - constexpr size_t kBruteForceThreshold = 8; - using RTree = bgi::rtree, bgi::quadratic<16>>; - - std::vector> rtrees(static_cast(max_layer_idx) + 1); - - for (int layer = 0; layer <= max_layer_idx; ++layer) { - auto& items = items_by_layer[layer]; - if (items.empty()) { - continue; - } - - if (items.size() < kBruteForceThreshold) { - for (size_t a = 0; a < items.size(); ++a) { - for (size_t b = a + 1; b < items.size(); ++b) { - if (items[a].second == items[b].second) { - continue; - } - - if (bg::intersects(items[a].first, items[b].first)) { - addEdge(items[a].second, items[b].second, layer); - } - } - } - } else { - rtrees[layer].emplace(items.begin(), items.end()); - } - } - - for (size_t i = 0; i < nodes.size(); ++i) { - if (!nodes[i].is_conductor) { - continue; - } - - for (const auto& s : nodes[i].shapes) { - if (s.layer_order < 0 || s.layer_order > max_layer_idx) { - continue; - } - - auto& items = items_by_layer[s.layer_order]; - if (items.size() < kBruteForceThreshold) { - continue; - } - - BGPointInt p_min(s.rect.get_low_x(), s.rect.get_low_y()); - BGPointInt p_max(s.rect.get_high_x(), s.rect.get_high_y()); - BGRectInt b_rect(p_min, p_max); - - std::vector> results; - if (rtrees[s.layer_order].has_value()) { - rtrees[s.layer_order]->query(bgi::intersects(b_rect), std::back_inserter(results)); - } - - for (const auto& res : results) { - if (res.second > static_cast(i)) { - addEdge(static_cast(i), res.second, s.layer_order); - } - } - } - } - - // 5. Bucket edges by time - std::vector> edges_by_time(static_cast(max_time) + 1); - for (auto& e : all_edges) { - if (e.time >= 0 && e.time <= max_time) { - edges_by_time[e.time].push_back(std::move(e)); - } - } - std::vector().swap(all_edges); - - std::vector> nodes_by_time(static_cast(max_time) + 1); - for (size_t i = 0; i < nodes.size(); ++i) { - if (!nodes[i].is_conductor) { - continue; - } - - int t = nodes[i].time; - if (t >= 0 && t <= max_time) { - nodes_by_time[t].push_back(static_cast(i)); - } - } - - ACUnionFind uf(static_cast(nodes.size()), nodes); - - std::vector> comps_by_root(nodes.size()); - std::vector active_roots; - - std::vector routing_rects; - std::vector cut_rects; - - auto edgeCmp = [](const ACEdge& a, const ACEdge& b) { - return std::tie(a.time, a.a, a.b) < std::tie(b.time, b.a, b.b); - }; - - auto edgeEq = [](const ACEdge& a, const ACEdge& b) { - return a.time == b.time && a.a == b.a && a.b == b.b; - }; - - // 6. Layer-by-layer antenna calculation - for (int T = 0; T <= max_time; ++T) { - auto& edges = edges_by_time[T]; - - if (!edges.empty()) { - std::sort(edges.begin(), edges.end(), edgeCmp); - edges.erase(std::unique(edges.begin(), edges.end(), edgeEq), edges.end()); - - for (const auto& e : edges) { - uf.merge(e.a, e.b); - } - } - - if (nodes_by_time[T].empty()) { - continue; - } - - const ACAntennaRule* routing_rule = pickRule(ac_model, T, true); - const ACAntennaRule* cut_rule = pickRule(ac_model, T, false); - - active_roots.clear(); - - for (int u : nodes_by_time[T]) { - int root = uf.find(u); - - if (comps_by_root[root].empty()) { - active_roots.push_back(root); - } - - comps_by_root[root].push_back(u); - } - - if (active_roots.empty()) { - continue; - } - - if (routing_rule == nullptr && cut_rule == nullptr) { - for (int root : active_roots) { - ACUFNode& root_data = uf.d[root]; - for (int u : comps_by_root[root]) { - if (nodes[u].is_conductor && nodes[u].declared_area > 0.0) { - if (nodes[u].is_cut) { - root_data.cum_cut_num += nodes[u].declared_area; - } else if (nodes[u].is_side) { - root_data.cum_side_num += nodes[u].declared_area; - } else if (nodes[u].is_routing) { - root_data.cum_area_num += nodes[u].declared_area; - } - } - } - comps_by_root[root].clear(); - } - continue; - } - - for (int root : active_roots) { - auto& comp_nodes = comps_by_root[root]; - ACUFNode& root_data = uf.d[root]; - - routing_rects.clear(); - cut_rects.clear(); - - bool has_bbox = false; - int64_t bbox_lx = std::numeric_limits::max(); - int64_t bbox_ly = std::numeric_limits::max(); - int64_t bbox_hx = std::numeric_limits::min(); - int64_t bbox_hy = std::numeric_limits::min(); - - auto addRectToBBox = [&](const idb::IdbRect& r) { - int64_t lx = std::min(r.get_low_x(), r.get_high_x()); - int64_t ly = std::min(r.get_low_y(), r.get_high_y()); - int64_t hx = std::max(r.get_low_x(), r.get_high_x()); - int64_t hy = std::max(r.get_low_y(), r.get_high_y()); - - bbox_lx = std::min(bbox_lx, lx); - bbox_ly = std::min(bbox_ly, ly); - bbox_hx = std::max(bbox_hx, hx); - bbox_hy = std::max(bbox_hy, hy); - has_bbox = true; - }; - - double declared_routing_area = 0.0; - double declared_side_area = 0.0; - double declared_cut_area = 0.0; - - for (int u : comp_nodes) { - if (!nodes[u].is_conductor) { - continue; - } - - if (nodes[u].declared_area > 0.0) { - if (nodes[u].is_cut) { - declared_cut_area += nodes[u].declared_area; - } else if (nodes[u].is_side) { - declared_side_area += nodes[u].declared_area; - } else if (nodes[u].is_routing) { - declared_routing_area += nodes[u].declared_area; - } - } - - for (const auto& s : nodes[u].shapes) { - if (s.layer_order != T) { - continue; - } - - if (nodes[u].is_routing) { - routing_rects.push_back(s.rect); - addRectToBBox(s.rect); - } else if (nodes[u].is_cut) { - cut_rects.push_back(s.rect); - addRectToBBox(s.rect); - } - } - } - - if (routing_rule == nullptr) { - root_data.cum_area_num += declared_routing_area; - root_data.cum_side_num += declared_side_area; - } - if (cut_rule == nullptr) { - root_data.cum_cut_num += declared_cut_area; - } - - double cut_area_um = declared_cut_area; - if (!cut_rects.empty()) { - double physical_cut_area_um = 0.0; - unionArea(cut_rects, micron_dbu, physical_cut_area_um); - cut_area_um += physical_cut_area_um; - } - - double metal_area_um = declared_routing_area; - double side_area_um = declared_side_area; - if (!routing_rects.empty()) { - double physical_metal_area_um = 0.0; - double physical_metal_perimeter_um = 0.0; - unionAreaPerimeter(routing_rects, micron_dbu, physical_metal_area_um, physical_metal_perimeter_um); - metal_area_um += physical_metal_area_um; - - double thickness_um = routing_rule ? routing_rule->thickness_um : 0.0; - if (thickness_um <= 0.0 && static_cast(T) < thickness_by_order.size()) { - thickness_um = thickness_by_order[T]; - } - side_area_um += physical_metal_perimeter_um * thickness_um; - } - - const bool has_routing = !Utility::equalDoubleByError(metal_area_um, 0.0, ZH_ERROR) || - !Utility::equalDoubleByError(side_area_um, 0.0, ZH_ERROR); - const bool has_cut = !Utility::equalDoubleByError(cut_area_um, 0.0, ZH_ERROR); - - if (!has_routing && !has_cut) { - comps_by_root[root].clear(); - continue; - } - - if (Utility::equalDoubleByError(root_data.gate_area, 0.0, ZH_ERROR) && - Utility::equalDoubleByError(root_data.diff_area, 0.0, ZH_ERROR) && !root_data.diff_connected) { - comps_without_gate.fetch_add(1, std::memory_order_relaxed); - comps_by_root[root].clear(); - continue; - } - - const bool diff_active = root_data.diff_connected || - !Utility::equalDoubleByError(root_data.diff_area, 0.0, ZH_ERROR); - - if (!has_bbox) { - for (int u : comp_nodes) { - if (has_bbox) break; - for (const auto& s : nodes[u].shapes) { - addRectToBBox(s.rect); - } - } - } - - auto emit = [&](const std::string& layer_name, ACViolationType type, double ratio, double threshold) { - if (ratio > threshold) { - ACViolation v; - v.net_name = net_name; - v.layer_name = layer_name; - v.type = type; - v.ratio = ratio; - v.threshold = threshold; - - if (has_bbox) { - v.lx = Utility::getRatio(bbox_lx, micron_dbu); - v.ly = Utility::getRatio(bbox_ly, micron_dbu); - v.hx = Utility::getRatio(bbox_hx, micron_dbu); - v.hy = Utility::getRatio(bbox_hy, micron_dbu); - } - - out_violations.push_back(v); - } - }; - - // Cut layer PAR/CAR - if (has_cut && cut_rule != nullptr) { - if (!Utility::equalDoubleByError(cut_area_um, 0.0, ZH_ERROR)) { - const double gate_plus_diff_factor = (cut_rule->gate_plus_diff >= 0.0) ? cut_rule->gate_plus_diff : 0.0; - const double eff_gate = root_data.gate_area + gate_plus_diff_factor * root_data.diff_area; - - if (!Utility::equalDoubleByError(eff_gate, 0.0, ZH_ERROR)) { - auto factor = [&](double f, bool diffuse_only) { - if (f >= 0.0 && (!diffuse_only || diff_active)) { - return f; - } - return 1.0; - }; - - const double cut_scale = factor(cut_rule->area_factor, cut_rule->area_factor_diffuse_only); - const double reduce_factor = Utility::getPWLValue(cut_rule->area_diff_reduce_pwl, root_data.diff_area, 1.0); - const double minus_diff = (cut_rule->area_minus_diff >= 0.0) ? cut_rule->area_minus_diff * root_data.diff_area : 0.0; - - const double par_cut_num = (cut_scale * cut_area_um) * reduce_factor - minus_diff; - const double par_cut_check = std::max(0.0, Utility::getRatio(par_cut_num, eff_gate)); - - const double prev_cut_num = cut_rule->cum_routing_plus_cut ? root_data.cum_area_num : root_data.cum_cut_num; - root_data.cum_cut_num = std::max(0.0, prev_cut_num + par_cut_num); - const double cut_car = Utility::getRatio(root_data.cum_cut_num, eff_gate); - - ACThresholdPick p = pickThreshold(cut_rule->area_ratio, cut_rule->diff_area_ratio, cut_rule->diff_area_ratio_pwl, - root_data.diff_area, root_data.diff_connected); - - if (p.available) { - emit(cut_rule->layer_name, p.is_diff ? ACViolationType::kAntennaDiffCutPar : ACViolationType::kAntennaCutPar, par_cut_check, - p.threshold); - } - - p = pickThreshold(cut_rule->cum_area_ratio, cut_rule->cum_diff_area_ratio, cut_rule->cum_diff_area_ratio_pwl, - root_data.diff_area, root_data.diff_connected); - - if (p.available) { - emit(cut_rule->layer_name, p.is_diff ? ACViolationType::kAntennaDiffCutCar : ACViolationType::kAntennaCutCar, cut_car, - p.threshold); - } - } else { - comps_without_gate.fetch_add(1, std::memory_order_relaxed); - } - } - } - - // Routing layer PAR/CAR/PSR/CSR - if (has_routing && routing_rule != nullptr) { - if (!Utility::equalDoubleByError(metal_area_um, 0.0, ZH_ERROR) || - !Utility::equalDoubleByError(side_area_um, 0.0, ZH_ERROR)) { - const double gate_plus_diff_factor = (routing_rule->gate_plus_diff >= 0.0) ? routing_rule->gate_plus_diff : 0.0; - const double eff_gate = root_data.gate_area + gate_plus_diff_factor * root_data.diff_area; - - if (!Utility::equalDoubleByError(eff_gate, 0.0, ZH_ERROR)) { - auto factor = [&](double f, bool diffuse_only) { - if (f >= 0.0 && (!diffuse_only || diff_active)) { - return f; - } - return 1.0; - }; - - const double area_scale = factor(routing_rule->area_factor, routing_rule->area_factor_diffuse_only); - const double side_scale = factor(routing_rule->side_area_factor, routing_rule->side_area_factor_diffuse_only); - - const double area_reduce = Utility::getPWLValue(routing_rule->area_diff_reduce_pwl, root_data.diff_area, 1.0); - - const double minus_diff = (routing_rule->area_minus_diff >= 0.0) ? routing_rule->area_minus_diff * root_data.diff_area : 0.0; - - const double par_area_num = (area_scale * metal_area_um) * area_reduce - minus_diff; - const double par_side_num = side_scale * side_area_um; - - const double par_area_check = std::max(0.0, Utility::getRatio(par_area_num, eff_gate)); - const double par_side_check = std::max(0.0, Utility::getRatio(par_side_num, eff_gate)); - - const double prev_area_num = routing_rule->cum_routing_plus_cut ? root_data.cum_cut_num : root_data.cum_area_num; - root_data.cum_area_num = std::max(0.0, prev_area_num + par_area_num); - root_data.cum_side_num = std::max(0.0, root_data.cum_side_num + par_side_num); - - const double car = Utility::getRatio(root_data.cum_area_num, eff_gate); - const double csr = Utility::getRatio(root_data.cum_side_num, eff_gate); - - ACThresholdPick p = pickThreshold(routing_rule->area_ratio, routing_rule->diff_area_ratio, routing_rule->diff_area_ratio_pwl, - root_data.diff_area, root_data.diff_connected); - - if (p.available) { - emit(routing_rule->layer_name, p.is_diff ? ACViolationType::kAntennaDiffPar : ACViolationType::kAntennaPar, par_area_check, - p.threshold); - } - - p = pickThreshold(routing_rule->cum_area_ratio, routing_rule->cum_diff_area_ratio, routing_rule->cum_diff_area_ratio_pwl, - root_data.diff_area, root_data.diff_connected); - - if (p.available) { - emit(routing_rule->layer_name, p.is_diff ? ACViolationType::kAntennaDiffCar : ACViolationType::kAntennaCar, car, p.threshold); - } - - p = pickThreshold(routing_rule->side_area_ratio, routing_rule->diff_side_area_ratio, routing_rule->diff_side_area_ratio_pwl, - root_data.diff_area, root_data.diff_connected); - - if (p.available) { - emit(routing_rule->layer_name, p.is_diff ? ACViolationType::kAntennaDiffPsr : ACViolationType::kAntennaPsr, par_side_check, - p.threshold); - } - - p = pickThreshold(routing_rule->cum_side_area_ratio, routing_rule->cum_diff_side_area_ratio, - routing_rule->cum_diff_side_area_ratio_pwl, root_data.diff_area, root_data.diff_connected); - - if (p.available) { - emit(routing_rule->layer_name, p.is_diff ? ACViolationType::kAntennaDiffCsr : ACViolationType::kAntennaCsr, csr, p.threshold); - } - } else { - comps_without_gate.fetch_add(1, std::memory_order_relaxed); - } - } - } - - comps_by_root[root].clear(); - } - } -} - -void AntennaChecker::readPinAntennaInfo(ACModel& ac_model, idb::IdbPin* pin, const bool instance_pin, double& gate_area, - double& diff_area, bool& provides_diff) -{ - std::atomic& pins_missing_antenna_info = ac_model.get_pins_missing_antenna_info(); - std::atomic& pins_with_gate_area = ac_model.get_pins_with_gate_area(); - gate_area = 0.0; - diff_area = 0.0; - provides_diff = false; - - if (pin == nullptr) { - return; - } - - idb::IdbTerm* term = pin->get_term(); - if (term == nullptr) { - if (instance_pin) { - pins_missing_antenna_info.fetch_add(1, std::memory_order_relaxed); - } - return; - } - - if (term->has_antenna_gate_area()) { - gate_area = term->get_antenna_gate_area(); - if (instance_pin) { - pins_with_gate_area.fetch_add(1, std::memory_order_relaxed); - } - } - - if (term->has_antenna_diff_area()) { - diff_area = term->get_antenna_diff_area(); - provides_diff = true; - } - - if (!Utility::equalDoubleByError(diff_area, 0.0, ZH_ERROR)) { - provides_diff = true; - } - - idb::IdbConnectDirection dir = term->get_direction(); - if (dir == idb::IdbConnectDirection::kOutput || dir == idb::IdbConnectDirection::kOutputTriState || - dir == idb::IdbConnectDirection::kInOut) { - provides_diff = true; - } - - if (instance_pin && !term->has_antenna_gate_area() && !term->has_antenna_diff_area()) { - pins_missing_antenna_info.fetch_add(1, std::memory_order_relaxed); - } -} - -const ACAntennaRule* AntennaChecker::pickRule(const ACModel& ac_model, const int layer_order, const bool routing) const -{ - const std::vector>& rule_map = ac_model.get_rule_map(); - if (layer_order < 0 || static_cast(layer_order) >= rule_map.size()) { - return nullptr; - } - - for (const ACAntennaRule* rule : rule_map[layer_order]) { - if (rule != nullptr && rule->is_routing == routing) { - return rule; - } - } - - return nullptr; -} - -void AntennaChecker::unionArea(const std::vector& rects, int micron_dbu, double& area_um) -{ - double unused_perimeter_um = 0.0; - unionAreaPerimeter(rects, micron_dbu, area_um, unused_perimeter_um); -} - -void AntennaChecker::unionAreaPerimeter(const std::vector& rects, int micron_dbu, double& area_um, double& perimeter_um) -{ - area_um = 0.0; - perimeter_um = 0.0; - - if (rects.empty() || micron_dbu <= 0) { - return; - } - - std::vector rs; - std::vector ys; - - rs.reserve(rects.size()); - ys.reserve(rects.size() * 2); - - for (const auto& r : rects) { - int64_t x1 = std::min(r.get_low_x(), r.get_high_x()); - int64_t x2 = std::max(r.get_low_x(), r.get_high_x()); - int64_t y1 = std::min(r.get_low_y(), r.get_high_y()); - int64_t y2 = std::max(r.get_low_y(), r.get_high_y()); - - if (x2 <= x1 || y2 <= y1) { - continue; - } - - rs.push_back({x1, x2, y1, y2}); - ys.push_back(y1); - ys.push_back(y2); - } - - if (rs.empty()) { - return; - } - - const long double dbu = static_cast(micron_dbu); - - auto finalize = [&](long double area_dbu, long double perimeter_dbu) { - if (area_dbu < 0.0L) { - area_dbu = 0.0L; - } - if (perimeter_dbu < 0.0L) { - perimeter_dbu = 0.0L; - } - area_um = static_cast(area_dbu / (dbu * dbu)); - perimeter_um = static_cast(perimeter_dbu / dbu); - }; - - if (rs.size() == 1) { - long double w = static_cast(rs[0].x2 - rs[0].x1); - long double h = static_cast(rs[0].y2 - rs[0].y1); - finalize(w * h, 2.0L * (w + h)); - return; - } - - if (rs.size() <= 64) { - bool need_union = false; - - for (size_t i = 0; i < rs.size() && !need_union; ++i) { - for (size_t j = i + 1; j < rs.size(); ++j) { - if (!(rs[i].x2 < rs[j].x1 || rs[j].x2 < rs[i].x1 || rs[i].y2 < rs[j].y1 || rs[j].y2 < rs[i].y1)) { - need_union = true; - break; - } - } - } - - if (!need_union) { - long double area = 0.0L; - long double per = 0.0L; - - for (const auto& r : rs) { - long double w = static_cast(r.x2 - r.x1); - long double h = static_cast(r.y2 - r.y1); - area += w * h; - per += 2.0L * (w + h); - } - - finalize(area, per); - return; - } - } - - std::sort(ys.begin(), ys.end()); - ys.erase(std::unique(ys.begin(), ys.end()), ys.end()); - - const int y_interval_count = static_cast(ys.size()) - 1; - if (y_interval_count <= 0) { - return; - } - - std::vector events; - events.reserve(rs.size() * 2); - - auto yIndex = [&](int64_t v) -> int { - return static_cast(std::lower_bound(ys.begin(), ys.end(), v) - ys.begin()); - }; - - for (const auto& r : rs) { - int y1 = yIndex(r.y1); - int y2 = yIndex(r.y2) - 1; - - if (y1 > y2) { - continue; - } - - events.push_back({r.x1, y1, y2, +1}); - events.push_back({r.x2, y1, y2, -1}); - } - - if (events.empty()) { - return; - } - - std::sort(events.begin(), events.end(), [](const ACSweepEvent& a, const ACSweepEvent& b) { - return a.x < b.x; - }); - - ACSegmentTree tree; - tree.init(ys); - - long double area = 0.0L; - long double perimeter = 0.0L; - - bool has_prev_x = false; - int64_t prev_x = 0; - - std::vector> left_ranges; - std::vector> right_ranges; - - auto addVerticalContribution = [&](std::vector>& ranges) { - if (ranges.empty()) { - return; - } - - std::sort(ranges.begin(), ranges.end()); - - size_t i = 0; - while (i < ranges.size()) { - int a = ranges[i].first; - int b = ranges[i].second; - - size_t j = i + 1; - while (j < ranges.size() && ranges[j].first <= b + 1) { - b = std::max(b, ranges[j].second); - ++j; - } - - long double range_len = static_cast(ys[b + 1] - ys[a]); - long double covered = tree.queryCovered(a, b); - long double uncovered = range_len - covered; - - if (uncovered > 0.0L) { - perimeter += uncovered; - } - - i = j; - } - }; - - size_t i = 0; - while (i < events.size()) { - int64_t x = events[i].x; - - if (has_prev_x) { - long double dx = static_cast(x - prev_x); - if (dx > 0.0L) { - area += tree.coveredLength() * dx; - perimeter += 2.0L * static_cast(tree.intervalCount()) * dx; - } - } - - size_t j = i; - left_ranges.clear(); - right_ranges.clear(); - - while (j < events.size() && events[j].x == x) { - if (events[j].delta > 0) { - left_ranges.emplace_back(events[j].y1, events[j].y2); - } else { - right_ranges.emplace_back(events[j].y1, events[j].y2); - } - ++j; - } - - addVerticalContribution(left_ranges); - - for (size_t k = i; k < j; ++k) { - tree.update(events[k].y1, events[k].y2, events[k].delta); - } - - addVerticalContribution(right_ranges); - - has_prev_x = true; - prev_x = x; - i = j; - } - - finalize(area, perimeter); -} - -ACThresholdPick AntennaChecker::pickThreshold(const double plain_ratio, const double diff_ratio, - const std::vector>& diff_pwl, const double diff_area, - const bool diff_connected) -{ - const bool has_diff = (diff_ratio >= 0.0) || (!diff_pwl.empty()); - const bool has_plain = (plain_ratio >= 0.0); - const bool diff_active = diff_connected || !Utility::equalDoubleByError(diff_area, 0.0, ZH_ERROR); - bool use_diff = has_diff && (diff_active || !has_plain); - - if (use_diff && !diff_pwl.empty() && Utility::equalDoubleByError(diff_area, 0.0, ZH_ERROR) && has_plain) { - use_diff = false; - } - - if (use_diff) { - const double threshold = !diff_pwl.empty() ? Utility::getPWLValue(diff_pwl, diff_area, diff_ratio) : diff_ratio; - return {true, threshold, true}; - } - - if (has_plain) { - return {true, plain_ratio, false}; - } - - return {}; -} - -void AntennaChecker::writeReport(const ACModel& ac_model) const -{ - auto type_to_string = [](ACViolationType t) -> const char* { - switch (t) { - case ACViolationType::kAntennaPar: - return "PAR"; - case ACViolationType::kAntennaDiffPar: - return "DiffPAR"; - case ACViolationType::kAntennaCar: - return "CAR"; - case ACViolationType::kAntennaDiffCar: - return "DiffCAR"; - case ACViolationType::kAntennaPsr: - return "PSR"; - case ACViolationType::kAntennaDiffPsr: - return "DiffPSR"; - case ACViolationType::kAntennaCsr: - return "CSR"; - case ACViolationType::kAntennaDiffCsr: - return "DiffCSR"; - case ACViolationType::kAntennaCutPar: - return "CutPAR"; - case ACViolationType::kAntennaCutCar: - return "CutCAR"; - case ACViolationType::kAntennaDiffCutPar: - return "DiffCutPAR"; - case ACViolationType::kAntennaDiffCutCar: - return "DiffCutCAR"; - default: - return "UNKNOWN"; - } - }; - if (!ac_model.get_report_dir().empty()) { const std::string report_path = ac_model.get_report_dir() + "/antenna_check.rpt"; std::ofstream rpt(report_path); @@ -1698,7 +304,7 @@ void AntennaChecker::writeReport(const ACModel& ac_model) const rpt << "------------------------------------------------------------------------\n"; for (const auto& v : ac_model.get_violation_list()) { - rpt << "Net: " << v.net_name << " | Layer: " << v.layer_name << " | Type: " << type_to_string(v.type) << "\n" + rpt << "Net: " << v.net_name << " | Layer: " << v.layer_name << " | Type: " << acViolationTypeToString(v.type) << "\n" << " Ratio: " << v.ratio << " (Threshold: " << v.threshold << ")\n" << " Location: (" << v.lx << ", " << v.ly << ") to (" << v.hx << ", " << v.hy << ")\n\n"; } diff --git a/src/operation/iZH/source/module/antenna_checker/AntennaChecker.hpp b/src/operation/iZH/source/module/antenna_checker/AntennaChecker.hpp index a7f564dec2..0beeb82b6b 100644 --- a/src/operation/iZH/source/module/antenna_checker/AntennaChecker.hpp +++ b/src/operation/iZH/source/module/antenna_checker/AntennaChecker.hpp @@ -17,11 +17,9 @@ #pragma once #include "ACModel.hpp" -#include "ACThresholdPick.hpp" +#include "AntennaResult.hpp" #include "IdbDesign.h" -#include "IdbGeometry.h" #include "IdbNet.h" -#include "IdbPins.h" #include "Logger.hpp" #include "Monitor.hpp" @@ -36,11 +34,11 @@ class AntennaChecker static AntennaChecker& getInst(); static void destroyInst(); - // function void check(std::map config_map); + void checkNets(ACModel& ac_model, const std::vector& net_list); + AntennaResult checkToResult(std::map config_map, bool write_report = false); private: - // self static AntennaChecker* _ac_instance; AntennaChecker() = default; @@ -51,20 +49,10 @@ class AntennaChecker AntennaChecker& operator=(const AntennaChecker& other) = delete; AntennaChecker& operator=(AntennaChecker&& other) = delete; - // function ACModel initACModel(std::map& config_map); void runACModel(ACModel& ac_model); void reportACModel(const ACModel& ac_model); void initDatabaseInfo(ACModel& ac_model); - void initLayers(ACModel& ac_model, idb::IdbDesign* design); - void checkNet(ACModel& ac_model, idb::IdbDesign* design, idb::IdbNet* net, std::vector& out_violations); - void readPinAntennaInfo(ACModel& ac_model, idb::IdbPin* pin, bool instance_pin, double& gate_area, double& diff_area, - bool& provides_diff); - const ACAntennaRule* pickRule(const ACModel& ac_model, int layer_order, bool routing) const; - static void unionArea(const std::vector& rects, int micron_dbu, double& area_um); - static void unionAreaPerimeter(const std::vector& rects, int micron_dbu, double& area_um, double& perimeter_um); - static ACThresholdPick pickThreshold(double plain_ratio, double diff_ratio, const std::vector>& diff_pwl, - double diff_area, bool diff_connected); void writeReport(const ACModel& ac_model) const; }; diff --git a/src/operation/iZH/source/module/antenna_checker/AntennaEngine.cpp b/src/operation/iZH/source/module/antenna_checker/AntennaEngine.cpp new file mode 100644 index 0000000000..b331bd2b14 --- /dev/null +++ b/src/operation/iZH/source/module/antenna_checker/AntennaEngine.cpp @@ -0,0 +1,301 @@ +// *************************************************************************************** +// 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 +// *************************************************************************************** + +#include "AntennaEngine.hpp" + +#include + +#include "ACModel.hpp" +#include "AntennaChecker.hpp" +#include "AntennaFixer.hpp" +#include "AntennaRuleEvaluator.hpp" +#include "Logger.hpp" +#include "Monitor.hpp" +#include "RoutingContext.hpp" +#include "Utility.hpp" +#include "WireEnvIndex.hpp" + +#include "IdbDesign.h" +#include "IdbNet.h" +#include "idm.h" + +namespace izh { + +void AntennaEngine::initInst() +{ + if (_ae_instance == nullptr) { + _ae_instance = new AntennaEngine(); + } +} + +AntennaEngine& AntennaEngine::getInst() +{ + if (_ae_instance == nullptr) { + ZHLOG.error(Loc::current(), "The instance not initialized!"); + abort(); + } + return *_ae_instance; +} + +void AntennaEngine::destroyInst() +{ + if (_ae_instance != nullptr) { + delete _ae_instance; + _ae_instance = nullptr; + } +} + +AFComParam AntennaEngine::initParam(std::map& config_map) +{ + AFComParam param; + auto consumeString = [&](const std::string& key, std::string& out) { + auto it = config_map.find(key); + if (it == config_map.end()) { + return; + } + if (const std::string* value = std::any_cast(&it->second)) { + out = *value; + } + config_map.erase(it); + }; + auto consumeInt = [&](const std::string& key, int32_t& out) { + auto it = config_map.find(key); + if (it == config_map.end()) { + return; + } + if (const int32_t* value = std::any_cast(&it->second)) { + out = *value; + } else if (const int* value = std::any_cast(&it->second)) { + out = *value; + } else if (const std::string* value = std::any_cast(&it->second)) { + try { + out = std::stoi(*value); + } catch (...) { + } + } + config_map.erase(it); + }; + auto consumeBool = [&](const std::string& key, bool& out) { + auto it = config_map.find(key); + if (it == config_map.end()) { + return; + } + if (const bool* value = std::any_cast(&it->second)) { + out = *value; + } else if (const int32_t* value = std::any_cast(&it->second)) { + out = (*value != 0); + } else if (const int* value = std::any_cast(&it->second)) { + out = (*value != 0); + } else if (const std::string* value = std::any_cast(&it->second)) { + out = (*value != "0" && *value != "false" && *value != "False"); + } + config_map.erase(it); + }; + + std::string report_dir; + consumeString("report_dir", report_dir); + consumeString("-antenna_report_dir", report_dir); + param.set_report_dir(report_dir); + + bool enable_fix = true; + consumeBool("-enable_antenna_fix", enable_fix); + param.set_enable_fix(enable_fix); + + int32_t max_iter = 3; + consumeInt("-antenna_max_iter", max_iter); + if (max_iter <= 0) { + max_iter = 1; + } + param.set_max_iter(max_iter); + + int32_t search_radius = 0; + consumeInt("-antenna_search_radius", search_radius); + param.set_search_radius(search_radius); + + int32_t max_jog = 0; + consumeInt("-antenna_max_jog", max_jog); + param.set_max_jog(max_jog); + + std::string diode_cells; + consumeString("-antenna_diode_cells", diode_cells); + if (!diode_cells.empty()) { + std::string normalized = diode_cells; + for (char& ch : normalized) { + if (std::string(" \t\r\n,;").find(ch) != std::string::npos) { + ch = ' '; + } + } + std::stringstream stream(normalized); + std::string name; + while (stream >> name) { + param.get_diode_name_list().push_back(name); + } + } + + bool enable_drc = false; + consumeBool("-antenna_enable_drc", enable_drc); + param.set_enable_drc(enable_drc); + + config_map.erase("-stage"); + config_map.erase("-resolve_congestion"); + if (!config_map.empty()) { + ZHLOG.warn(Loc::current(), "The checkAndFixAntenna config has not been consumed yet!"); + } + return param; +} + +AntennaResult AntennaEngine::checkAndFix(std::map config_map) +{ + Monitor monitor; + ZHLOG.info(Loc::current(), "Starting..."); + + AFComParam param = initParam(config_map); + AntennaChecker::initInst(); + + std::map check_map; + check_map["report_dir"] = param.get_report_dir(); + AntennaResult result = ZHAC.checkToResult(check_map, true); + AFIterStat first; + first.iter = 0; + first.violation_num = result.get_violation_num(); + result.get_iter_stat_list().push_back(first); + + if (!param.get_enable_fix() || result.get_violation_num() == 0) { + writeFixReport(result, param); + AntennaChecker::destroyInst(); + ZHLOG.info(Loc::current(), "Completed", monitor.getStatsInfo()); + return result; + } + + idb::IdbDesign* design = nullptr; + if (dmInst != nullptr && dmInst->get_idb_def_service() != nullptr) { + design = dmInst->get_idb_def_service()->get_design(); + } + if (design == nullptr || design->get_net_list() == nullptr) { + writeFixReport(result, param); + AntennaChecker::destroyInst(); + return result; + } + + RoutingContext ctx = RoutingContext::build(design, param); + WireEnvIndex index; + index.rebuild(design, ctx); + AntennaFixer fixer; + ACModel ac_model; + ac_model.get_report_dir() = param.get_report_dir(); + ac_model.get_micron_dbu() = ctx.get_micron_dbu(); + ac_model.get_violation_list() = result.get_violation_list(); + ac_model.set_violation_num(result.get_violation_num()); + AntennaRuleEvaluator::initLayers(ac_model, design); + + int32_t prev_violation_count = result.get_violation_num(); + int64_t total_signal_nets = static_cast( + design->get_net_list()->get_net_list().size()); + + for (int32_t iter = 1; iter <= param.get_max_iter(); ++iter) { + auto iter_start = std::chrono::steady_clock::now(); + AFIterStat stat; + stat.iter = iter; + std::vector current = result.get_violation_list(); + if (current.empty()) { + break; + } + + std::unordered_set touched; + std::map> by_net; + for (const ACViolation& v : current) { + by_net[v.net_name].push_back(v); + } + + for (auto& [net_name, violations] : by_net) { + idb::IdbNet* net = design->get_net_list()->find_net(net_name); + if (net == nullptr) { + continue; + } + std::unordered_set fixed_pins; + for (const ACViolation& v : violations) { + std::string pin_key = v.inst_name + "/" + v.pin_name; + if (!v.pin_name.empty() && fixed_pins.count(pin_key) > 0) { + continue; + } + bool applied = fixer.applySelected(design, net, v, ctx, index, stat, result.get_logged_no_antenna_cell()); + if (applied) { + touched.insert(net_name); + if (!v.pin_name.empty()) { + fixed_pins.insert(pin_key); + } + } + } + } + + if (touched.empty()) { + stat.violation_num = result.get_violation_num(); + result.get_iter_stat_list().push_back(stat); + break; + } + + index.rebuild(design, ctx); + std::vector nets; + for (const std::string& name : touched) { + idb::IdbNet* net = design->get_net_list()->find_net(name); + if (net != nullptr) { + nets.push_back(net); + } + } + ZHAC.checkNets(ac_model, nets); + result.get_violation_list() = ac_model.get_violation_list(); + stat.violation_num = result.get_violation_num(); + result.get_iter_stat_list().push_back(stat); + + auto iter_elapsed = std::chrono::duration_cast( + std::chrono::steady_clock::now() - iter_start).count(); + ZHLOG.info(Loc::current(), "antenna fix iter ", iter, + " remaining violations: ", stat.violation_num, + " touched_nets: ", static_cast(touched.size()), + " total_signal_nets: ", total_signal_nets, + " elapsed_ms: ", iter_elapsed); + if (stat.violation_num == 0) { + break; + } + if (stat.violation_num >= prev_violation_count) { + ZHLOG.info(Loc::current(), "antenna fix iter ", iter, " no progress, stopping"); + break; + } + prev_violation_count = stat.violation_num; + } + + writeFixReport(result, param); + AntennaChecker::destroyInst(); + ZHLOG.info(Loc::current(), "Completed", monitor.getStatsInfo()); + return result; +} + +void AntennaEngine::writeFixReport(const AntennaResult& result, const AFComParam& param) const +{ + if (param.get_report_dir().empty()) { + return; + } + const std::string report_path = param.get_report_dir() + "/antenna_check.rpt"; + std::ofstream rpt(report_path, std::ios::app); + if (!rpt.is_open()) { + ZHLOG.warn(Loc::current(), "Cannot open antenna report file: ", report_path); + return; + } + rpt << "========================================================================\n"; + rpt << " Antenna Fix Summary \n"; + rpt << "========================================================================\n"; + for (const AFIterStat& stat : result.get_iter_stat_list()) { + rpt << "Iter " << stat.iter << ": violations=" << stat.violation_num << " hop_up=" << stat.hop_up_applied << "/" + << (stat.hop_up_applied + stat.hop_up_rejected) << " jog=" << stat.jog_applied << "/" << (stat.jog_applied + stat.jog_rejected) + << " diode=" << stat.diode_applied << "/" << (stat.diode_applied + stat.diode_rejected) << "\n"; + } + rpt << "Remaining violations: " << result.get_violation_num() << "\n"; + rpt.close(); +} + +AntennaEngine* AntennaEngine::_ae_instance = nullptr; + +} // namespace izh diff --git a/src/operation/iZH/source/module/antenna_checker/AntennaEngine.hpp b/src/operation/iZH/source/module/antenna_checker/AntennaEngine.hpp new file mode 100644 index 0000000000..98cdd0ad28 --- /dev/null +++ b/src/operation/iZH/source/module/antenna_checker/AntennaEngine.hpp @@ -0,0 +1,39 @@ +// *************************************************************************************** +// 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 +// *************************************************************************************** +#pragma once + +#include "AFComParam.hpp" +#include "AntennaResult.hpp" +#include "ZHHeader.hpp" + +namespace izh { + +#define ZHAE (izh::AntennaEngine::getInst()) + +class AntennaEngine +{ + public: + static void initInst(); + static AntennaEngine& getInst(); + static void destroyInst(); + + AntennaResult checkAndFix(std::map config_map); + + private: + static AntennaEngine* _ae_instance; + + AntennaEngine() = default; + AntennaEngine(const AntennaEngine& other) = delete; + AntennaEngine(AntennaEngine&& other) = delete; + ~AntennaEngine() = default; + AntennaEngine& operator=(const AntennaEngine& other) = delete; + AntennaEngine& operator=(AntennaEngine&& other) = delete; + + AFComParam initParam(std::map& config_map); + void writeFixReport(const AntennaResult& result, const AFComParam& param) const; +}; + +} // namespace izh diff --git a/src/operation/iZH/source/module/antenna_checker/AntennaFixer.cpp b/src/operation/iZH/source/module/antenna_checker/AntennaFixer.cpp new file mode 100644 index 0000000000..cd2ab15434 --- /dev/null +++ b/src/operation/iZH/source/module/antenna_checker/AntennaFixer.cpp @@ -0,0 +1,434 @@ +// *************************************************************************************** +// 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 +// *************************************************************************************** + +#include "AntennaFixer.hpp" + +#include "AntennaResult.hpp" +#include "Logger.hpp" +#include "Utility.hpp" + +#include "IdbEnum.h" +#include "IdbInstance.h" +#include "IdbLayer.h" +#include "IdbRegularWire.h" +#include "IdbTerm.h" +#include "IdbVias.h" + +namespace izh { + +AFFixKind AntennaFixer::classify(const ACViolation& violation, const RoutingContext& ctx) const +{ + if (acViolationIsCut(violation.type)) { + return AFFixKind::kDiode; + } + if (violation.layer_order < 0 || violation.layer_order >= ctx.get_top_routing_order()) { + return AFFixKind::kDiode; + } + const RCRoutingLayer* next = ctx.findNextRouting(violation.layer_order); + if (next == nullptr || ctx.findViaBetween(violation.layer_order, next->order) == nullptr) { + return AFFixKind::kDiode; + } + return AFFixKind::kBoth; +} + +bool AntennaFixer::hopUpPermitted(const ACViolation& violation, const RoutingContext& ctx) const +{ + if (acViolationIsCut(violation.type)) { + return false; + } + if (violation.layer_order < 0 || violation.layer_order >= ctx.get_top_routing_order()) { + return false; + } + const RCRoutingLayer* next = ctx.findNextRouting(violation.layer_order); + return next != nullptr && ctx.findViaBetween(violation.layer_order, next->order) != nullptr; +} + +bool AntennaFixer::applySelected(idb::IdbDesign* design, idb::IdbNet* net, const ACViolation& violation, const RoutingContext& ctx, + WireEnvIndex& index, AFIterStat& stat, bool& logged_no_cell) +{ + AFFixKind kind = classify(violation, ctx); + bool hop_ok = hopUpPermitted(violation, ctx); + bool applied = false; + + if (kind == AFFixKind::kHopUp || kind == AFFixKind::kBoth) { + applied = applyHopUp(design, net, violation, ctx, index, true, stat); + if (applied) { + return true; + } + } + + if (kind == AFFixKind::kDiode || kind == AFFixKind::kBoth) { + applied = applyDiode(design, net, violation, ctx, index, stat, logged_no_cell); + if (applied) { + return true; + } + } + + if (kind == AFFixKind::kHopUp && !applied && hop_ok) { + applied = applyDiode(design, net, violation, ctx, index, stat, logged_no_cell); + if (applied) { + return true; + } + } + + if (kind == AFFixKind::kDiode && !applied && hop_ok && !ctx.get_diode_masters().empty()) { + applied = applyHopUp(design, net, violation, ctx, index, true, stat); + if (applied) { + return true; + } + } + + if (!applied) { + ZHLOG.warn(Loc::current(), "antenna violation unresolved: net=", violation.net_name, " pin=", violation.pin_name, + " layer_order=", violation.layer_order); + } + return applied; +} + +idb::IdbPin* AntennaFixer::findVictimPin(idb::IdbNet* net, const ACViolation& violation) const +{ + if (net == nullptr) { + return nullptr; + } + auto match = [&](idb::IdbPin* pin, bool instance_pin) -> bool { + if (pin == nullptr) { + return false; + } + if (!violation.pin_name.empty() && pin->get_pin_name() != violation.pin_name) { + return false; + } + if (instance_pin && !violation.inst_name.empty()) { + if (pin->get_instance() == nullptr || pin->get_instance()->get_name() != violation.inst_name) { + return false; + } + } + return true; + }; + + if (net->get_instance_pin_list() != nullptr) { + for (idb::IdbPin* pin : net->get_instance_pin_list()->get_pin_list()) { + if (match(pin, true)) { + return pin; + } + } + } + if (net->get_io_pins() != nullptr) { + for (idb::IdbPin* pin : net->get_io_pins()->get_pin_list()) { + if (match(pin, false)) { + return pin; + } + } + } + if (net->get_instance_pin_list() != nullptr && !net->get_instance_pin_list()->get_pin_list().empty()) { + return net->get_instance_pin_list()->get_pin_list().front(); + } + if (net->get_io_pins() != nullptr && !net->get_io_pins()->get_pin_list().empty()) { + return net->get_io_pins()->get_pin_list().front(); + } + return nullptr; +} + +bool AntennaFixer::pinCoord(idb::IdbPin* pin, int32_t& x, int32_t& y) const +{ + x = 0; + y = 0; + if (pin == nullptr) { + return false; + } + if (pin->get_location() != nullptr && pin->get_location()->is_init()) { + x = pin->get_location()->get_x(); + y = pin->get_location()->get_y(); + return true; + } + if (pin->get_average_coordinate() != nullptr) { + x = pin->get_average_coordinate()->get_x(); + y = pin->get_average_coordinate()->get_y(); + return true; + } + return false; +} + +const RCRoutingLayer* AntennaFixer::resolveStubLayer(idb::IdbPin* pin, const RoutingContext& ctx) const +{ + if (pin != nullptr) { + for (idb::IdbLayerShape* shape : pin->get_port_box_list()) { + if (shape == nullptr || shape->get_layer() == nullptr) { + continue; + } + idb::IdbLayer* layer = shape->get_layer(); + for (const auto& rl : ctx.get_routing_layers()) { + if (rl.layer == layer) { + return &rl; + } + } + } + } + return ctx.get_routing_layers().empty() ? nullptr : &ctx.get_routing_layers().front(); +} + +bool AntennaFixer::addViaAndStub(idb::IdbNet* net, int32_t x, int32_t y, const RCRoutingLayer& lower, const RCRoutingLayer& upper, + idb::IdbVia* via, WireEnvIndex& index) +{ + if (net == nullptr || net->get_wire_list() == nullptr || via == nullptr || lower.layer == nullptr || upper.layer == nullptr) { + return false; + } + idb::IdbRegularWire* wire = net->get_wire_list()->add_wire(); + wire->set_wire_state(idb::IdbWiringStatement::kRouted); + + idb::IdbRegularWireSegment* via_seg = new idb::IdbRegularWireSegment(); + via_seg->set_layer(upper.layer); + via_seg->set_is_via(true); + via_seg->add_point(x, y); + via_seg->copy_via(via); + if (!via_seg->get_via_list().empty() && via_seg->get_via_list().front() != nullptr) { + via_seg->get_via_list().front()->set_coordinate(x, y); + index.insertVia(via_seg->get_via_list().front(), net); + } + via_seg->set_layer_as_new(); + wire->add_segment(via_seg); + + int32_t stub = std::max(upper.width, 1); + if (upper.min_area > 0 && stub > 0) { + stub = std::max(stub, (upper.min_area + stub - 1) / stub); + } + int32_t x2 = x; + int32_t y2 = y; + if (upper.horizontal) { + x2 = x + stub; + } else { + y2 = y + stub; + } + + idb::IdbRegularWireSegment* stub_seg = new idb::IdbRegularWireSegment(); + stub_seg->set_layer(upper.layer); + stub_seg->add_point(x, y); + stub_seg->add_point(x2, y2); + wire->add_segment(stub_seg); + int32_t hw = std::max(upper.width, 1) / 2; + index.insertWire(upper.order, std::min(x, x2) - hw, std::min(y, y2) - hw, std::max(x, x2) + hw, std::max(y, y2) + hw, net); + return true; +} + +bool AntennaFixer::tryPlaceVia(idb::IdbDesign* design, idb::IdbNet* net, int32_t x, int32_t y, const RCRoutingLayer& lower, + const RCRoutingLayer& upper, idb::IdbVia* via, const RoutingContext& ctx, WireEnvIndex& index) +{ + (void) design; + if (!index.inDie(ctx, x, y)) { + return false; + } + int32_t hw = std::max(lower.width, 1) / 2; + int32_t pad = std::max(lower.spacing, hw); + if (index.hasOverlap(lower.order, x - pad, y - pad, x + pad, y + pad, net)) { + return false; + } + int32_t uhw = std::max(upper.width, 1) / 2; + int32_t upad = std::max(upper.spacing, uhw); + if (index.hasOverlap(upper.order, x - upad, y - upad, x + upad, y + upad, net)) { + return false; + } + const RCCutLayer* cut = ctx.findCutBetween(lower.order, upper.order); + int cut_order = cut != nullptr ? cut->order : (lower.order + 1); + int32_t cut_spacing = cut != nullptr ? cut->spacing : pad; + if (index.hasViaOverlap(x, y, cut_order, cut_spacing, net)) { + return false; + } + return addViaAndStub(net, x, y, lower, upper, via, index); +} + +bool AntennaFixer::applyHopUp(idb::IdbDesign* design, idb::IdbNet* net, const ACViolation& violation, const RoutingContext& ctx, + WireEnvIndex& index, bool allow_jog, AFIterStat& stat) +{ + const RCRoutingLayer* lower = ctx.findRoutingByOrder(violation.layer_order); + const RCRoutingLayer* upper = ctx.findNextRouting(violation.layer_order); + if (lower == nullptr || upper == nullptr) { + ++stat.hop_up_rejected; + return false; + } + idb::IdbVia* via = ctx.findViaBetween(lower->order, upper->order); + if (via == nullptr) { + ++stat.hop_up_rejected; + return false; + } + idb::IdbPin* pin = findVictimPin(net, violation); + int32_t x = 0; + int32_t y = 0; + if (!pinCoord(pin, x, y)) { + x = static_cast((violation.lx + violation.hx) * 0.5 * ctx.get_micron_dbu()); + y = static_cast((violation.ly + violation.hy) * 0.5 * ctx.get_micron_dbu()); + } + + if (tryPlaceVia(design, net, x, y, *lower, *upper, via, ctx, index)) { + ++stat.hop_up_applied; + return true; + } + if (!allow_jog) { + ++stat.hop_up_rejected; + return false; + } + + int32_t pitch = std::max(lower->pitch, 1); + int32_t max_jog = ctx.get_max_jog(); + bool prefer_h = lower->horizontal; + for (int32_t step = pitch; step <= max_jog; step += pitch) { + std::vector> cands; + if (prefer_h) { + cands.emplace_back(x + step, y); + cands.emplace_back(x - step, y); + } else { + cands.emplace_back(x, y + step); + cands.emplace_back(x, y - step); + } + for (auto [cx, cy] : cands) { + if (tryPlaceVia(design, net, cx, cy, *lower, *upper, via, ctx, index)) { + int32_t hw = std::max(lower->width, 1) / 2; + int32_t pad = std::max(lower->spacing, hw); + if (!index.hasOverlap(lower->order, std::min(x, cx) - pad, std::min(y, cy) - pad, std::max(x, cx) + pad, std::max(y, cy) + pad, + net)) { + idb::IdbRegularWire* wire = net->get_wire_list()->add_wire(); + wire->set_wire_state(idb::IdbWiringStatement::kRouted); + idb::IdbRegularWireSegment* jog = new idb::IdbRegularWireSegment(); + jog->set_layer(lower->layer); + jog->add_point(x, y); + jog->add_point(cx, cy); + wire->add_segment(jog); + index.insertWire(lower->order, std::min(x, cx) - hw, std::min(y, cy) - hw, std::max(x, cx) + hw, std::max(y, cy) + hw, net); + ++stat.jog_applied; + ++stat.hop_up_applied; + return true; + } + } + } + } + ++stat.jog_rejected; + ++stat.hop_up_rejected; + return false; +} + +idb::IdbTerm* AntennaFixer::pickDiodeTerm(idb::IdbCellMaster* master) const +{ + if (master == nullptr) { + return nullptr; + } + idb::IdbTerm* fallback = nullptr; + for (idb::IdbTerm* term : master->get_term_list()) { + if (term == nullptr || term->is_pdn()) { + continue; + } + if (term->has_antenna_diff_area()) { + return term; + } + if (fallback == nullptr) { + fallback = term; + } + } + return fallback; +} + +bool AntennaFixer::applyDiode(idb::IdbDesign* design, idb::IdbNet* net, const ACViolation& violation, const RoutingContext& ctx, + WireEnvIndex& index, AFIterStat& stat, bool& logged_no_cell) +{ + if (ctx.get_diode_masters().empty()) { + if (!logged_no_cell) { + ZHLOG.warn(Loc::current(), "No CORE ANTENNACELL found; leftover antenna violations will remain"); + logged_no_cell = true; + } + ++stat.diode_rejected; + return false; + } + idb::IdbPin* pin = findVictimPin(net, violation); + int32_t pin_x = 0; + int32_t pin_y = 0; + if (!pinCoord(pin, pin_x, pin_y)) { + pin_x = static_cast((violation.lx + violation.hx) * 0.5 * ctx.get_micron_dbu()); + pin_y = static_cast((violation.ly + violation.hy) * 0.5 * ctx.get_micron_dbu()); + } + + for (idb::IdbCellMaster* master : ctx.get_diode_masters()) { + idb::IdbTerm* term = pickDiodeTerm(master); + if (term == nullptr) { + continue; + } + int32_t x = 0; + int32_t y = 0; + idb::IdbOrient orient = idb::IdbOrient::kN_R0; + if (!index.findFreeDiodeSite(ctx, pin_x, pin_y, static_cast(master->get_width()), static_cast(master->get_height()), x, + y, orient)) { + continue; + } + std::string inst_name = design->makeUniqueInstanceName("ANTENNA_DIODE"); + idb::IdbInstance* inst + = design->createInstance(inst_name, master->get_name(), idb::IdbInstanceType::kDist, idb::IdbPlacementStatus::kPlaced, orient, x, y, + idb::IdbCreatePolicy::kErrorIfExists); + if (inst == nullptr) { + continue; + } + if (!design->connectInstancePinToNet(inst_name, term->get_name(), net->get_net_name())) { + design->removeInstanceSafe(inst_name); + continue; + } + index.insertInstance(inst); + + idb::IdbPin* diode_pin = inst->get_pin_by_term(term->get_name()); + int32_t dx = x; + int32_t dy = y; + pinCoord(diode_pin, dx, dy); + + const RCRoutingLayer* stub_layer = resolveStubLayer(pin, ctx); + if (stub_layer != nullptr && stub_layer->layer != nullptr && net->get_wire_list() != nullptr) { + idb::IdbRegularWire* wire = net->get_wire_list()->add_wire(); + wire->set_wire_state(idb::IdbWiringStatement::kRouted); + + int32_t stub_x1 = dx; + int32_t stub_y1 = dy; + int32_t stub_x2 = pin_x; + int32_t stub_y2 = pin_y; + + if (std::abs(dx - pin_x) > std::abs(dy - pin_y)) { + stub_y2 = stub_y1; + } else { + stub_x2 = stub_x1; + } + if (stub_x1 != stub_x2 || stub_y1 != stub_y2) { + idb::IdbRegularWireSegment* seg1 = new idb::IdbRegularWireSegment(); + seg1->set_layer(stub_layer->layer); + seg1->add_point(dx, dy); + seg1->add_point(stub_x2, stub_y2); + seg1->set_layer_as_new(); + wire->add_segment(seg1); + int32_t hw = std::max(stub_layer->width, 1) / 2; + index.insertWire(stub_layer->order, std::min(dx, stub_x2) - hw, std::min(dy, stub_y2) - hw, std::max(dx, stub_x2) + hw, + std::max(dy, stub_y2) + hw, net); + + if (stub_x2 != pin_x || stub_y2 != pin_y) { + idb::IdbRegularWireSegment* seg2 = new idb::IdbRegularWireSegment(); + seg2->set_layer(stub_layer->layer); + seg2->add_point(stub_x2, stub_y2); + seg2->add_point(pin_x, pin_y); + seg2->set_layer_as_new(); + wire->add_segment(seg2); + index.insertWire(stub_layer->order, std::min(stub_x2, pin_x) - hw, std::min(stub_y2, pin_y) - hw, + std::max(stub_x2, pin_x) + hw, std::max(stub_y2, pin_y) + hw, net); + } + } else { + idb::IdbRegularWireSegment* stub = new idb::IdbRegularWireSegment(); + stub->set_layer(stub_layer->layer); + stub->add_point(dx, dy); + stub->add_point(pin_x, pin_y); + stub->set_layer_as_new(); + wire->add_segment(stub); + int32_t hw = std::max(stub_layer->width, 1) / 2; + index.insertWire(stub_layer->order, std::min(dx, pin_x) - hw, std::min(dy, pin_y) - hw, std::max(dx, pin_x) + hw, + std::max(dy, pin_y) + hw, net); + } + } + ++stat.diode_applied; + return true; + } + ++stat.diode_rejected; + return false; +} + +} // namespace izh diff --git a/src/operation/iZH/source/module/antenna_checker/AntennaFixer.hpp b/src/operation/iZH/source/module/antenna_checker/AntennaFixer.hpp new file mode 100644 index 0000000000..0a164cf8c9 --- /dev/null +++ b/src/operation/iZH/source/module/antenna_checker/AntennaFixer.hpp @@ -0,0 +1,53 @@ +// *************************************************************************************** +// 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 +// *************************************************************************************** +#pragma once + +#include "AFComParam.hpp" +#include "ACViolation.hpp" +#include "AntennaResult.hpp" +#include "IdbDesign.h" +#include "IdbInstance.h" +#include "IdbNet.h" +#include "IdbPins.h" +#include "RoutingContext.hpp" +#include "WireEnvIndex.hpp" + +namespace izh { + +enum class AFFixKind +{ + kNone, + kHopUp, + kJog, + kDiode, + kBoth, + kSkip +}; + +class AntennaFixer +{ + public: + AFFixKind classify(const ACViolation& violation, const RoutingContext& ctx) const; + bool hopUpPermitted(const ACViolation& violation, const RoutingContext& ctx) const; + bool applySelected(idb::IdbDesign* design, idb::IdbNet* net, const ACViolation& violation, const RoutingContext& ctx, WireEnvIndex& index, + AFIterStat& stat, bool& logged_no_cell); + bool applyHopUp(idb::IdbDesign* design, idb::IdbNet* net, const ACViolation& violation, const RoutingContext& ctx, WireEnvIndex& index, + bool allow_jog, AFIterStat& stat); + bool applyDiode(idb::IdbDesign* design, idb::IdbNet* net, const ACViolation& violation, const RoutingContext& ctx, WireEnvIndex& index, + AFIterStat& stat, bool& logged_no_cell); + + private: + idb::IdbPin* findVictimPin(idb::IdbNet* net, const ACViolation& violation) const; + bool pinCoord(idb::IdbPin* pin, int32_t& x, int32_t& y) const; + const RCRoutingLayer* resolveStubLayer(idb::IdbPin* pin, const RoutingContext& ctx) const; + bool tryPlaceVia(idb::IdbDesign* design, idb::IdbNet* net, int32_t x, int32_t y, const RCRoutingLayer& lower, + const RCRoutingLayer& upper, idb::IdbVia* via, const RoutingContext& ctx, WireEnvIndex& index); + bool addViaAndStub(idb::IdbNet* net, int32_t x, int32_t y, const RCRoutingLayer& lower, const RCRoutingLayer& upper, idb::IdbVia* via, + WireEnvIndex& index); + idb::IdbTerm* pickDiodeTerm(idb::IdbCellMaster* master) const; +}; + +} // namespace izh diff --git a/src/operation/iZH/source/module/antenna_checker/AntennaGeometry.cpp b/src/operation/iZH/source/module/antenna_checker/AntennaGeometry.cpp new file mode 100644 index 0000000000..0e8e14a9f7 --- /dev/null +++ b/src/operation/iZH/source/module/antenna_checker/AntennaGeometry.cpp @@ -0,0 +1,386 @@ +// *************************************************************************************** +// 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 +// *************************************************************************************** + +#include "AntennaGeometry.hpp" + +#include "ACNormRect.hpp" +#include "ACSegmentTree.hpp" +#include "ACSweepEvent.hpp" +#include "AntennaRuleEvaluator.hpp" +#include "Utility.hpp" + +namespace izh { + +void AntennaGeometry::unionArea(const std::vector& rects, int micron_dbu, double& area_um) +{ + double unused_perimeter_um = 0.0; + unionAreaPerimeter(rects, micron_dbu, area_um, unused_perimeter_um); +} + +void AntennaGeometry::unionAreaPerimeter(const std::vector& rects, int micron_dbu, double& area_um, double& perimeter_um) +{ + area_um = 0.0; + perimeter_um = 0.0; + + if (rects.empty() || micron_dbu <= 0) { + return; + } + + std::vector rs; + std::vector ys; + + rs.reserve(rects.size()); + ys.reserve(rects.size() * 2); + + for (const auto& r : rects) { + int64_t x1 = std::min(r.get_low_x(), r.get_high_x()); + int64_t x2 = std::max(r.get_low_x(), r.get_high_x()); + int64_t y1 = std::min(r.get_low_y(), r.get_high_y()); + int64_t y2 = std::max(r.get_low_y(), r.get_high_y()); + + if (x2 <= x1 || y2 <= y1) { + continue; + } + + rs.push_back({x1, x2, y1, y2}); + ys.push_back(y1); + ys.push_back(y2); + } + + if (rs.empty()) { + return; + } + + const long double dbu = static_cast(micron_dbu); + + auto finalize = [&](long double area_dbu, long double perimeter_dbu) { + if (area_dbu < 0.0L) { + area_dbu = 0.0L; + } + if (perimeter_dbu < 0.0L) { + perimeter_dbu = 0.0L; + } + area_um = static_cast(area_dbu / (dbu * dbu)); + perimeter_um = static_cast(perimeter_dbu / dbu); + }; + + if (rs.size() == 1) { + long double w = static_cast(rs[0].x2 - rs[0].x1); + long double h = static_cast(rs[0].y2 - rs[0].y1); + finalize(w * h, 2.0L * (w + h)); + return; + } + + if (rs.size() <= 64) { + bool need_union = false; + + for (size_t i = 0; i < rs.size() && !need_union; ++i) { + for (size_t j = i + 1; j < rs.size(); ++j) { + if (!(rs[i].x2 < rs[j].x1 || rs[j].x2 < rs[i].x1 || rs[i].y2 < rs[j].y1 || rs[j].y2 < rs[i].y1)) { + need_union = true; + break; + } + } + } + + if (!need_union) { + long double area = 0.0L; + long double per = 0.0L; + + for (const auto& r : rs) { + long double w = static_cast(r.x2 - r.x1); + long double h = static_cast(r.y2 - r.y1); + area += w * h; + per += 2.0L * (w + h); + } + + finalize(area, per); + return; + } + } + + std::sort(ys.begin(), ys.end()); + ys.erase(std::unique(ys.begin(), ys.end()), ys.end()); + + const int y_interval_count = static_cast(ys.size()) - 1; + if (y_interval_count <= 0) { + return; + } + + std::vector events; + events.reserve(rs.size() * 2); + + auto yIndex = [&](int64_t v) -> int { + return static_cast(std::lower_bound(ys.begin(), ys.end(), v) - ys.begin()); + }; + + for (const auto& r : rs) { + int y1 = yIndex(r.y1); + int y2 = yIndex(r.y2) - 1; + + if (y1 > y2) { + continue; + } + + events.push_back({r.x1, y1, y2, +1}); + events.push_back({r.x2, y1, y2, -1}); + } + + if (events.empty()) { + return; + } + + std::sort(events.begin(), events.end(), [](const ACSweepEvent& a, const ACSweepEvent& b) { + return a.x < b.x; + }); + + ACSegmentTree tree; + tree.init(ys); + + long double area = 0.0L; + long double perimeter = 0.0L; + + bool has_prev_x = false; + int64_t prev_x = 0; + + std::vector> left_ranges; + std::vector> right_ranges; + + auto addVerticalContribution = [&](std::vector>& ranges) { + if (ranges.empty()) { + return; + } + + std::sort(ranges.begin(), ranges.end()); + + size_t i = 0; + while (i < ranges.size()) { + int a = ranges[i].first; + int b = ranges[i].second; + + size_t j = i + 1; + while (j < ranges.size() && ranges[j].first <= b + 1) { + b = std::max(b, ranges[j].second); + ++j; + } + + long double range_len = static_cast(ys[b + 1] - ys[a]); + long double covered = tree.queryCovered(a, b); + long double uncovered = range_len - covered; + + if (uncovered > 0.0L) { + perimeter += uncovered; + } + + i = j; + } + }; + + size_t i = 0; + while (i < events.size()) { + int64_t x = events[i].x; + + if (has_prev_x) { + long double dx = static_cast(x - prev_x); + if (dx > 0.0L) { + area += tree.coveredLength() * dx; + perimeter += 2.0L * static_cast(tree.intervalCount()) * dx; + } + } + + size_t j = i; + left_ranges.clear(); + right_ranges.clear(); + + while (j < events.size() && events[j].x == x) { + if (events[j].delta > 0) { + left_ranges.emplace_back(events[j].y1, events[j].y2); + } else { + right_ranges.emplace_back(events[j].y1, events[j].y2); + } + ++j; + } + + addVerticalContribution(left_ranges); + + for (size_t k = i; k < j; ++k) { + tree.update(events[k].y1, events[k].y2, events[k].delta); + } + + addVerticalContribution(right_ranges); + + has_prev_x = true; + prev_x = x; + i = j; + } + + finalize(area, perimeter); +} + +void AntennaGeometry::evaluateCut(const ACAntennaRule& cut_rule, const ACUFNode& root_data, double cut_area_um, bool diff_active, + int micron_dbu, bool has_bbox, int64_t bbox_lx, int64_t bbox_ly, int64_t bbox_hx, int64_t bbox_hy, + const std::string& net_name, const std::string& pin_name, const std::string& inst_name, int layer_order, + double gate_area, double diff_area, double metal_area, std::vector& out_violations, + double& cum_cut_num) +{ + if (Utility::equalDoubleByError(cut_area_um, 0.0, ZH_ERROR)) { + return; + } + + const double gate_plus_diff_factor = (cut_rule.gate_plus_diff >= 0.0) ? cut_rule.gate_plus_diff : 0.0; + const double eff_gate = root_data.gate_area + gate_plus_diff_factor * root_data.diff_area; + if (Utility::equalDoubleByError(eff_gate, 0.0, ZH_ERROR)) { + return; + } + + auto factor = [&](double f, bool diffuse_only) { + if (f >= 0.0 && (!diffuse_only || diff_active)) { + return f; + } + return 1.0; + }; + + const double cut_scale = factor(cut_rule.area_factor, cut_rule.area_factor_diffuse_only); + const double reduce_factor = Utility::getPWLValue(cut_rule.area_diff_reduce_pwl, root_data.diff_area, 1.0); + const double minus_diff = (cut_rule.area_minus_diff >= 0.0) ? cut_rule.area_minus_diff * root_data.diff_area : 0.0; + + const double par_cut_num = (cut_scale * cut_area_um) * reduce_factor - minus_diff; + const double par_cut_check = std::max(0.0, Utility::getRatio(par_cut_num, eff_gate)); + + const double prev_cut_num = cut_rule.cum_routing_plus_cut ? root_data.cum_area_num : root_data.cum_cut_num; + cum_cut_num = std::max(0.0, prev_cut_num + par_cut_num); + const double cut_car = Utility::getRatio(cum_cut_num, eff_gate); + + auto emit = [&](ACViolationType type, double ratio, double threshold) { + if (ratio > threshold) { + ACViolation v; + v.net_name = net_name; + v.layer_name = cut_rule.layer_name; + v.type = type; + v.ratio = ratio; + v.threshold = threshold; + v.pin_name = pin_name; + v.inst_name = inst_name; + v.layer_order = layer_order; + v.gate_area = gate_area; + v.diff_area = diff_area; + v.metal_area = metal_area; + v.cut_area = cut_area_um; + if (has_bbox) { + v.lx = Utility::getRatio(bbox_lx, micron_dbu); + v.ly = Utility::getRatio(bbox_ly, micron_dbu); + v.hx = Utility::getRatio(bbox_hx, micron_dbu); + v.hy = Utility::getRatio(bbox_hy, micron_dbu); + } + out_violations.push_back(v); + } + }; + + ACThresholdPick p = AntennaRuleEvaluator::pickThreshold(cut_rule.area_ratio, cut_rule.diff_area_ratio, cut_rule.diff_area_ratio_pwl, + root_data.diff_area, root_data.diff_connected); + if (p.available) { + emit(p.is_diff ? ACViolationType::kAntennaDiffCutPar : ACViolationType::kAntennaCutPar, par_cut_check, p.threshold); + } + + p = AntennaRuleEvaluator::pickThreshold(cut_rule.cum_area_ratio, cut_rule.cum_diff_area_ratio, cut_rule.cum_diff_area_ratio_pwl, + root_data.diff_area, root_data.diff_connected); + if (p.available) { + emit(p.is_diff ? ACViolationType::kAntennaDiffCutCar : ACViolationType::kAntennaCutCar, cut_car, p.threshold); + } +} + +void AntennaGeometry::evaluateRouting(const ACAntennaRule& routing_rule, const ACUFNode& root_data, double metal_area_um, + double side_area_um, bool diff_active, int micron_dbu, bool has_bbox, int64_t bbox_lx, + int64_t bbox_ly, int64_t bbox_hx, int64_t bbox_hy, const std::string& net_name, + const std::string& pin_name, const std::string& inst_name, int layer_order, double gate_area, + double diff_area, double cut_area, std::vector& out_violations, double& cum_area_num, + double& cum_side_num) +{ + if (Utility::equalDoubleByError(metal_area_um, 0.0, ZH_ERROR) && Utility::equalDoubleByError(side_area_um, 0.0, ZH_ERROR)) { + return; + } + + const double gate_plus_diff_factor = (routing_rule.gate_plus_diff >= 0.0) ? routing_rule.gate_plus_diff : 0.0; + const double eff_gate = root_data.gate_area + gate_plus_diff_factor * root_data.diff_area; + if (Utility::equalDoubleByError(eff_gate, 0.0, ZH_ERROR)) { + return; + } + + auto factor = [&](double f, bool diffuse_only) { + if (f >= 0.0 && (!diffuse_only || diff_active)) { + return f; + } + return 1.0; + }; + + const double area_scale = factor(routing_rule.area_factor, routing_rule.area_factor_diffuse_only); + const double side_scale = factor(routing_rule.side_area_factor, routing_rule.side_area_factor_diffuse_only); + const double area_reduce = Utility::getPWLValue(routing_rule.area_diff_reduce_pwl, root_data.diff_area, 1.0); + const double minus_diff = (routing_rule.area_minus_diff >= 0.0) ? routing_rule.area_minus_diff * root_data.diff_area : 0.0; + + const double par_area_num = (area_scale * metal_area_um) * area_reduce - minus_diff; + const double par_side_num = side_scale * side_area_um; + const double par_area_check = std::max(0.0, Utility::getRatio(par_area_num, eff_gate)); + const double par_side_check = std::max(0.0, Utility::getRatio(par_side_num, eff_gate)); + + const double prev_area_num = routing_rule.cum_routing_plus_cut ? root_data.cum_cut_num : root_data.cum_area_num; + cum_area_num = std::max(0.0, prev_area_num + par_area_num); + cum_side_num = std::max(0.0, root_data.cum_side_num + par_side_num); + + const double car = Utility::getRatio(cum_area_num, eff_gate); + const double csr = Utility::getRatio(cum_side_num, eff_gate); + + auto emit = [&](ACViolationType type, double ratio, double threshold) { + if (ratio > threshold) { + ACViolation v; + v.net_name = net_name; + v.layer_name = routing_rule.layer_name; + v.type = type; + v.ratio = ratio; + v.threshold = threshold; + v.pin_name = pin_name; + v.inst_name = inst_name; + v.layer_order = layer_order; + v.gate_area = gate_area; + v.diff_area = diff_area; + v.metal_area = metal_area_um; + v.cut_area = cut_area; + if (has_bbox) { + v.lx = Utility::getRatio(bbox_lx, micron_dbu); + v.ly = Utility::getRatio(bbox_ly, micron_dbu); + v.hx = Utility::getRatio(bbox_hx, micron_dbu); + v.hy = Utility::getRatio(bbox_hy, micron_dbu); + } + out_violations.push_back(v); + } + }; + + ACThresholdPick p = AntennaRuleEvaluator::pickThreshold(routing_rule.area_ratio, routing_rule.diff_area_ratio, + routing_rule.diff_area_ratio_pwl, root_data.diff_area, root_data.diff_connected); + if (p.available) { + emit(p.is_diff ? ACViolationType::kAntennaDiffPar : ACViolationType::kAntennaPar, par_area_check, p.threshold); + } + + p = AntennaRuleEvaluator::pickThreshold(routing_rule.cum_area_ratio, routing_rule.cum_diff_area_ratio, routing_rule.cum_diff_area_ratio_pwl, + root_data.diff_area, root_data.diff_connected); + if (p.available) { + emit(p.is_diff ? ACViolationType::kAntennaDiffCar : ACViolationType::kAntennaCar, car, p.threshold); + } + + p = AntennaRuleEvaluator::pickThreshold(routing_rule.side_area_ratio, routing_rule.diff_side_area_ratio, + routing_rule.diff_side_area_ratio_pwl, root_data.diff_area, root_data.diff_connected); + if (p.available) { + emit(p.is_diff ? ACViolationType::kAntennaDiffPsr : ACViolationType::kAntennaPsr, par_side_check, p.threshold); + } + + p = AntennaRuleEvaluator::pickThreshold(routing_rule.cum_side_area_ratio, routing_rule.cum_diff_side_area_ratio, + routing_rule.cum_diff_side_area_ratio_pwl, root_data.diff_area, root_data.diff_connected); + if (p.available) { + emit(p.is_diff ? ACViolationType::kAntennaDiffCsr : ACViolationType::kAntennaCsr, csr, p.threshold); + } +} + +} // namespace izh diff --git a/src/operation/iZH/source/module/antenna_checker/AntennaGeometry.hpp b/src/operation/iZH/source/module/antenna_checker/AntennaGeometry.hpp new file mode 100644 index 0000000000..ede5cdaf20 --- /dev/null +++ b/src/operation/iZH/source/module/antenna_checker/AntennaGeometry.hpp @@ -0,0 +1,32 @@ +// *************************************************************************************** +// 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 +// *************************************************************************************** +#pragma once + +#include "ACAntennaRule.hpp" +#include "ACGraphNode.hpp" +#include "ACUFNode.hpp" +#include "ACViolation.hpp" +#include "IdbGeometry.h" + +namespace izh { + +class AntennaGeometry +{ + public: + static void unionArea(const std::vector& rects, int micron_dbu, double& area_um); + static void unionAreaPerimeter(const std::vector& rects, int micron_dbu, double& area_um, double& perimeter_um); + static void evaluateCut(const ACAntennaRule& cut_rule, const ACUFNode& root_data, double cut_area_um, bool diff_active, int micron_dbu, + bool has_bbox, int64_t bbox_lx, int64_t bbox_ly, int64_t bbox_hx, int64_t bbox_hy, const std::string& net_name, + const std::string& pin_name, const std::string& inst_name, int layer_order, double gate_area, double diff_area, + double metal_area, std::vector& out_violations, double& cum_cut_num); + static void evaluateRouting(const ACAntennaRule& routing_rule, const ACUFNode& root_data, double metal_area_um, double side_area_um, + bool diff_active, int micron_dbu, bool has_bbox, int64_t bbox_lx, int64_t bbox_ly, int64_t bbox_hx, + int64_t bbox_hy, const std::string& net_name, const std::string& pin_name, const std::string& inst_name, + int layer_order, double gate_area, double diff_area, double cut_area, std::vector& out_violations, + double& cum_area_num, double& cum_side_num); +}; + +} // namespace izh diff --git a/src/operation/iZH/source/module/antenna_checker/AntennaPathAnalyzer.cpp b/src/operation/iZH/source/module/antenna_checker/AntennaPathAnalyzer.cpp new file mode 100644 index 0000000000..ace88b66b1 --- /dev/null +++ b/src/operation/iZH/source/module/antenna_checker/AntennaPathAnalyzer.cpp @@ -0,0 +1,786 @@ +// *************************************************************************************** +// 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 +// *************************************************************************************** + +#include "AntennaPathAnalyzer.hpp" + +#include "ACEdge.hpp" +#include "ACGraphNode.hpp" +#include "ACPinShape.hpp" +#include "ACUFNode.hpp" +#include "ACUnionFind.hpp" +#include "AntennaGeometry.hpp" +#include "AntennaRuleEvaluator.hpp" +#include "Utility.hpp" + +#include "IdbDesign.h" +#include "IdbInstance.h" +#include "IdbLayer.h" +#include "IdbLayout.h" +#include "IdbNet.h" +#include "IdbPins.h" +#include "IdbRegularWire.h" +#include "IdbTerm.h" +#include "IdbVias.h" + +namespace izh { + +void AntennaPathAnalyzer::readPinAntennaInfo(ACModel& ac_model, idb::IdbPin* pin, const bool instance_pin, double& gate_area, + double& diff_area, bool& provides_diff) +{ + std::atomic& pins_missing_antenna_info = ac_model.get_pins_missing_antenna_info(); + std::atomic& pins_with_gate_area = ac_model.get_pins_with_gate_area(); + gate_area = 0.0; + diff_area = 0.0; + provides_diff = false; + + if (pin == nullptr) { + return; + } + + idb::IdbTerm* term = pin->get_term(); + if (term == nullptr) { + if (instance_pin) { + pins_missing_antenna_info.fetch_add(1, std::memory_order_relaxed); + } + return; + } + + if (term->has_antenna_gate_area()) { + gate_area = term->get_antenna_gate_area(); + if (instance_pin) { + pins_with_gate_area.fetch_add(1, std::memory_order_relaxed); + } + } + + if (term->has_antenna_diff_area()) { + diff_area = term->get_antenna_diff_area(); + provides_diff = true; + } + + if (!Utility::equalDoubleByError(diff_area, 0.0, ZH_ERROR)) { + provides_diff = true; + } + + idb::IdbConnectDirection dir = term->get_direction(); + if (dir == idb::IdbConnectDirection::kOutput || dir == idb::IdbConnectDirection::kOutputTriState + || dir == idb::IdbConnectDirection::kInOut) { + provides_diff = true; + } + + if (instance_pin && !term->has_antenna_gate_area() && !term->has_antenna_diff_area()) { + pins_missing_antenna_info.fetch_add(1, std::memory_order_relaxed); + } +} + +void AntennaPathAnalyzer::checkNet(ACModel& ac_model, idb::IdbDesign* design, idb::IdbNet* net, std::vector& out_violations) +{ + const int micron_dbu = ac_model.get_micron_dbu(); + const std::vector& thickness_by_order = ac_model.get_thickness_by_order(); + const std::vector& conductive_order = ac_model.get_conductive_order(); + const int max_layer_order = ac_model.get_max_layer_order(); + std::atomic& comps_without_gate = ac_model.get_comps_without_gate(); + std::atomic& skipped_segments = ac_model.get_skipped_segments(); + std::atomic& conductors_out_of_range = ac_model.get_conductors_out_of_range(); + std::atomic& partial_areas_dropped = ac_model.get_partial_areas_dropped(); + if (net == nullptr) { + return; + } + + const std::string& net_name = net->get_net_name(); + + std::vector nodes; + std::vector all_edges; + + size_t estimated_nodes = 0; + + if (net->get_instance_pin_list() != nullptr) { + estimated_nodes += net->get_instance_pin_list()->get_pin_list().size() * 2; + } + + if (net->get_io_pins() != nullptr) { + estimated_nodes += net->get_io_pins()->get_pin_list().size() * 2; + } + + if (net->get_wire_list() != nullptr) { + estimated_nodes += net->get_wire_list()->get_wire_list().size() * 4; + } + + nodes.reserve(estimated_nodes); + all_edges.reserve(estimated_nodes * 2); + + int max_time = 0; + int max_shape_layer = 0; + + auto addEdge = [&](int a, int b, int t) { + if (a < 0 || b < 0 || a == b) { + return; + } + if (a > b) { + std::swap(a, b); + } + if (t < 0) { + t = 0; + } + if (t > max_layer_order) { + return; + } + + max_time = std::max(max_time, t); + all_edges.push_back({a, b, t}); + }; + + auto createGateNode = [&](double gate_area, double diff_area, bool provides_diff, const std::string& pin_name, + const std::string& inst_name) -> int { + if (Utility::equalDoubleByError(gate_area, 0.0, ZH_ERROR) && Utility::equalDoubleByError(diff_area, 0.0, ZH_ERROR) && !provides_diff) { + return -1; + } + + ACGraphNode n; + n.id = static_cast(nodes.size()); + n.time = 0; + n.is_conductor = false; + + n.gate_area = gate_area; + n.diff_area = diff_area; + n.provides_diff = provides_diff || !Utility::equalDoubleByError(diff_area, 0.0, ZH_ERROR); + n.pin_name = pin_name; + n.inst_name = inst_name; + + nodes.push_back(std::move(n)); + return static_cast(nodes.size()) - 1; + }; + + auto createConductorNode = [&](int order, bool is_routing, bool is_cut, const std::vector& rects) -> int { + if (order < 0 || order > max_layer_order || rects.empty()) { + return -1; + } + + ACGraphNode n; + n.id = static_cast(nodes.size()); + n.time = order; + n.is_conductor = true; + n.is_routing = is_routing; + n.is_cut = is_cut; + + n.shapes.reserve(rects.size()); + for (const auto& r : rects) { + n.shapes.push_back({order, r}); + } + + max_time = std::max(max_time, order); + max_shape_layer = std::max(max_shape_layer, order); + + nodes.push_back(std::move(n)); + return static_cast(nodes.size()) - 1; + }; + + auto isRoutingLike = [&](idb::IdbLayer* layer, int order) -> bool { + if (layer == nullptr) { + return false; + } + if (layer->is_routing()) { + return true; + } + if (layer->get_type() == idb::IdbLayerType::kLayerMasterslice && order >= 0 && static_cast(order) < conductive_order.size() + && conductive_order[order]) { + return true; + } + return false; + }; + + auto createVirtualConductorNode = [&](int order, double area, bool is_routing, bool is_side, bool is_cut) -> int { + ACGraphNode n; + n.id = static_cast(nodes.size()); + n.time = order; + n.is_conductor = true; + n.is_routing = is_routing; + n.is_side = is_side; + n.is_cut = is_cut; + n.declared_area = area; + + max_time = std::max(max_time, order); + nodes.push_back(std::move(n)); + return static_cast(nodes.size()) - 1; + }; + + auto processPin = [&](idb::IdbPin* pin, bool instance_pin) { + if (pin == nullptr) { + return; + } + + double gate_area = 0.0; + double diff_area = 0.0; + bool provides_diff = false; + + readPinAntennaInfo(ac_model, pin, instance_pin, gate_area, diff_area, provides_diff); + std::string pin_name = pin->get_pin_name(); + std::string inst_name; + if (instance_pin && pin->get_instance() != nullptr) { + inst_name = pin->get_instance()->get_name(); + } + int gate_node = createGateNode(gate_area, diff_area, provides_diff, pin_name, inst_name); + + std::vector pin_shapes; + + for (idb::IdbLayerShape* shape : pin->get_port_box_list()) { + if (shape == nullptr || shape->get_layer() == nullptr) { + continue; + } + + idb::IdbLayer* layer = shape->get_layer(); + int order = static_cast(layer->get_order()); + + if (order < 0 || order > max_layer_order) { + conductors_out_of_range.fetch_add(1, std::memory_order_relaxed); + continue; + } + + bool routing = isRoutingLike(layer, order); + bool cut = layer->is_cut(); + + for (idb::IdbRect* r : shape->get_rect_list()) { + if (r != nullptr) { + pin_shapes.push_back({order, routing, cut, *r}); + } + } + } + + int first_shape_node = -1; + + if (!pin_shapes.empty()) { + std::sort(pin_shapes.begin(), pin_shapes.end(), [](const ACPinShape& a, const ACPinShape& b) { return a.order < b.order; }); + + size_t i = 0; + while (i < pin_shapes.size()) { + int order = pin_shapes[i].order; + + bool routing = false; + bool cut = false; + std::vector rects; + + while (i < pin_shapes.size() && pin_shapes[i].order == order) { + routing = routing || pin_shapes[i].routing; + cut = cut || pin_shapes[i].cut; + rects.push_back(pin_shapes[i].rect); + ++i; + } + + int shape_node = createConductorNode(order, routing, cut, rects); + + if (shape_node >= 0 && first_shape_node < 0) { + first_shape_node = shape_node; + } + + if (gate_node >= 0 && shape_node >= 0) { + addEdge(gate_node, shape_node, order); + } + } + } + + idb::IdbTerm* term = pin->get_term(); + if (term != nullptr + && (term->has_antenna_partial_metal_area() || term->has_antenna_partial_metal_side_area() || term->has_antenna_partial_cut_area())) { + int anchor = (gate_node >= 0) ? gate_node : first_shape_node; + + idb::IdbLayers* layers = (design && design->get_layout()) ? design->get_layout()->get_layers() : nullptr; + + auto addPartialArea = [&](const std::string& layer_name, double area, bool is_routing, bool is_side, bool is_cut) { + if (anchor < 0 || layers == nullptr) { + partial_areas_dropped.fetch_add(1, std::memory_order_relaxed); + return; + } + + int order = layers->get_layer_order(layer_name); + if (order < 0 || order > max_layer_order) { + partial_areas_dropped.fetch_add(1, std::memory_order_relaxed); + return; + } + + int vnode = createVirtualConductorNode(order, area, is_routing, is_side, is_cut); + addEdge(anchor, vnode, order); + }; + + for (const auto& [layer_name, area] : term->get_antenna_partial_metal_area()) { + addPartialArea(layer_name, area, true, false, false); + } + for (const auto& [layer_name, area] : term->get_antenna_partial_metal_side_area()) { + addPartialArea(layer_name, area, false, true, false); + } + for (const auto& [layer_name, area] : term->get_antenna_partial_cut_area()) { + addPartialArea(layer_name, area, false, false, true); + } + } + }; + + if (net->get_instance_pin_list() != nullptr) { + for (idb::IdbPin* pin : net->get_instance_pin_list()->get_pin_list()) { + processPin(pin, true); + } + } + + if (net->get_io_pins() != nullptr) { + for (idb::IdbPin* pin : net->get_io_pins()->get_pin_list()) { + processPin(pin, false); + } + } + + if (net->get_wire_list() != nullptr) { + for (idb::IdbRegularWire* wire : net->get_wire_list()->get_wire_list()) { + if (wire == nullptr) { + continue; + } + + for (idb::IdbRegularWireSegment* seg : wire->get_segment_list()) { + if (seg == nullptr) { + continue; + } + + if (seg->is_wire()) { + idb::IdbLayer* layer = seg->get_layer(); + + if (layer != nullptr) { + int order = static_cast(layer->get_order()); + + if (isRoutingLike(layer, order)) { + if (order < 0 || order > max_layer_order) { + conductors_out_of_range.fetch_add(1, std::memory_order_relaxed); + } else { + std::vector rects; + rects.push_back(seg->get_segment_rect()); + createConductorNode(order, true, false, rects); + } + } else { + skipped_segments.fetch_add(1, std::memory_order_relaxed); + } + } else { + skipped_segments.fetch_add(1, std::memory_order_relaxed); + } + } + + if (seg->is_via()) { + for (idb::IdbVia* via : seg->get_via_list()) { + if (via == nullptr) { + continue; + } + + auto collectShapeRects = [&](idb::IdbLayerShape& shape, int& order, bool& is_routing, bool& is_cut) -> std::vector { + std::vector rects; + idb::IdbLayer* layer = shape.get_layer(); + if (layer == nullptr) { + return rects; + } + + order = static_cast(layer->get_order()); + is_routing = layer->is_routing(); + is_cut = layer->is_cut(); + + if (order < 0 || order > max_layer_order) { + return rects; + } + + for (idb::IdbRect* r : shape.get_rect_list()) { + if (r != nullptr) { + rects.push_back(*r); + } + } + + return rects; + }; + + idb::IdbLayerShape bottom_shape = via->get_bottom_layer_shape(); + idb::IdbLayerShape cut_shape = via->get_cut_layer_shape(); + idb::IdbLayerShape top_shape = via->get_top_layer_shape(); + + int bottom_order = -1; + bool bottom_routing = false; + bool bottom_cut = false; + + int cut_order = -1; + bool cut_routing = false; + bool cut_is_cut = false; + + int top_order = -1; + bool top_routing = false; + bool top_cut = false; + + auto bottom_rects = collectShapeRects(bottom_shape, bottom_order, bottom_routing, bottom_cut); + auto cut_rects = collectShapeRects(cut_shape, cut_order, cut_routing, cut_is_cut); + auto top_rects = collectShapeRects(top_shape, top_order, top_routing, top_cut); + + int b_id = createConductorNode(bottom_order, bottom_routing, bottom_cut, bottom_rects); + int c_id = createConductorNode(cut_order, cut_routing, cut_is_cut, cut_rects); + int t_id = createConductorNode(top_order, top_routing, top_cut, top_rects); + + if (b_id >= 0 && c_id >= 0) { + addEdge(b_id, c_id, nodes[c_id].time); + } + if (c_id >= 0 && t_id >= 0) { + addEdge(c_id, t_id, nodes[t_id].time); + } + if (b_id >= 0 && t_id >= 0 && c_id < 0) { + addEdge(b_id, t_id, nodes[t_id].time); + } + } + } + + if (!seg->is_wire() && !seg->is_via()) { + skipped_segments.fetch_add(1, std::memory_order_relaxed); + } + } + } + } + + if (nodes.empty()) { + return; + } + + int max_layer_idx = std::max(max_time, max_shape_layer); + if (max_layer_idx < 0) { + return; + } + + std::vector>> items_by_layer(static_cast(max_layer_idx) + 1); + + for (size_t i = 0; i < nodes.size(); ++i) { + if (!nodes[i].is_conductor) { + continue; + } + + for (const auto& s : nodes[i].shapes) { + if (s.layer_order < 0 || s.layer_order > max_layer_idx) { + continue; + } + + BGPointInt p_min(s.rect.get_low_x(), s.rect.get_low_y()); + BGPointInt p_max(s.rect.get_high_x(), s.rect.get_high_y()); + BGRectInt b_rect(p_min, p_max); + + items_by_layer[s.layer_order].emplace_back(b_rect, static_cast(i)); + } + } + + constexpr size_t kBruteForceThreshold = 8; + using RTree = bgi::rtree, bgi::quadratic<16>>; + + std::vector> rtrees(static_cast(max_layer_idx) + 1); + + for (int layer = 0; layer <= max_layer_idx; ++layer) { + auto& items = items_by_layer[layer]; + if (items.empty()) { + continue; + } + + if (items.size() < kBruteForceThreshold) { + for (size_t a = 0; a < items.size(); ++a) { + for (size_t b = a + 1; b < items.size(); ++b) { + if (items[a].second == items[b].second) { + continue; + } + + if (bg::intersects(items[a].first, items[b].first)) { + addEdge(items[a].second, items[b].second, layer); + } + } + } + } else { + rtrees[layer].emplace(items.begin(), items.end()); + } + } + + for (size_t i = 0; i < nodes.size(); ++i) { + if (!nodes[i].is_conductor) { + continue; + } + + for (const auto& s : nodes[i].shapes) { + if (s.layer_order < 0 || s.layer_order > max_layer_idx) { + continue; + } + + auto& items = items_by_layer[s.layer_order]; + if (items.size() < kBruteForceThreshold) { + continue; + } + + BGPointInt p_min(s.rect.get_low_x(), s.rect.get_low_y()); + BGPointInt p_max(s.rect.get_high_x(), s.rect.get_high_y()); + BGRectInt b_rect(p_min, p_max); + + std::vector> results; + if (rtrees[s.layer_order].has_value()) { + rtrees[s.layer_order]->query(bgi::intersects(b_rect), std::back_inserter(results)); + } + + for (const auto& res : results) { + if (res.second > static_cast(i)) { + addEdge(static_cast(i), res.second, s.layer_order); + } + } + } + } + + std::vector> edges_by_time(static_cast(max_time) + 1); + for (auto& e : all_edges) { + if (e.time >= 0 && e.time <= max_time) { + edges_by_time[e.time].push_back(std::move(e)); + } + } + std::vector().swap(all_edges); + + std::vector> nodes_by_time(static_cast(max_time) + 1); + for (size_t i = 0; i < nodes.size(); ++i) { + if (!nodes[i].is_conductor) { + continue; + } + + int t = nodes[i].time; + if (t >= 0 && t <= max_time) { + nodes_by_time[t].push_back(static_cast(i)); + } + } + + ACUnionFind uf(static_cast(nodes.size()), nodes); + + std::vector> comps_by_root(nodes.size()); + std::vector active_roots; + + std::vector routing_rects; + std::vector cut_rects; + + auto edgeCmp = [](const ACEdge& a, const ACEdge& b) { return std::tie(a.time, a.a, a.b) < std::tie(b.time, b.a, b.b); }; + + auto edgeEq = [](const ACEdge& a, const ACEdge& b) { return a.time == b.time && a.a == b.a && a.b == b.b; }; + + for (int T = 0; T <= max_time; ++T) { + auto& edges = edges_by_time[T]; + + if (!edges.empty()) { + std::sort(edges.begin(), edges.end(), edgeCmp); + edges.erase(std::unique(edges.begin(), edges.end(), edgeEq), edges.end()); + + for (const auto& e : edges) { + uf.merge(e.a, e.b); + } + } + + if (nodes_by_time[T].empty()) { + continue; + } + + const ACAntennaRule* routing_rule = AntennaRuleEvaluator::pickRule(ac_model, T, true); + const ACAntennaRule* cut_rule = AntennaRuleEvaluator::pickRule(ac_model, T, false); + + active_roots.clear(); + + for (int u : nodes_by_time[T]) { + int root = uf.find(u); + + if (comps_by_root[root].empty()) { + active_roots.push_back(root); + } + + comps_by_root[root].push_back(u); + } + + if (active_roots.empty()) { + continue; + } + + if (routing_rule == nullptr && cut_rule == nullptr) { + for (int root : active_roots) { + ACUFNode& root_data = uf.d[root]; + for (int u : comps_by_root[root]) { + if (nodes[u].is_conductor && nodes[u].declared_area > 0.0) { + if (nodes[u].is_cut) { + root_data.cum_cut_num += nodes[u].declared_area; + } else if (nodes[u].is_side) { + root_data.cum_side_num += nodes[u].declared_area; + } else if (nodes[u].is_routing) { + root_data.cum_area_num += nodes[u].declared_area; + } + } + } + comps_by_root[root].clear(); + } + continue; + } + + for (int root : active_roots) { + auto& comp_nodes = comps_by_root[root]; + ACUFNode& root_data = uf.d[root]; + + routing_rects.clear(); + cut_rects.clear(); + + bool has_bbox = false; + int64_t bbox_lx = std::numeric_limits::max(); + int64_t bbox_ly = std::numeric_limits::max(); + int64_t bbox_hx = std::numeric_limits::min(); + int64_t bbox_hy = std::numeric_limits::min(); + + auto addRectToBBox = [&](const idb::IdbRect& r) { + int64_t lx = std::min(r.get_low_x(), r.get_high_x()); + int64_t ly = std::min(r.get_low_y(), r.get_high_y()); + int64_t hx = std::max(r.get_low_x(), r.get_high_x()); + int64_t hy = std::max(r.get_low_y(), r.get_high_y()); + + bbox_lx = std::min(bbox_lx, lx); + bbox_ly = std::min(bbox_ly, ly); + bbox_hx = std::max(bbox_hx, hx); + bbox_hy = std::max(bbox_hy, hy); + has_bbox = true; + }; + + double declared_routing_area = 0.0; + double declared_side_area = 0.0; + double declared_cut_area = 0.0; + std::string victim_pin; + std::string victim_inst; + + auto collectVictim = [&](int node_idx) { + int gate_root = uf.find(node_idx); + if (gate_root < 0 || gate_root >= static_cast(nodes.size())) { + return; + } + if (!victim_pin.empty()) { + return; + } + for (size_t gi = 0; gi < nodes.size(); ++gi) { + if (nodes[gi].is_conductor) { + continue; + } + if (uf.find(static_cast(gi)) != gate_root) { + continue; + } + if (!nodes[gi].pin_name.empty()) { + victim_pin = nodes[gi].pin_name; + victim_inst = nodes[gi].inst_name; + return; + } + } + }; + + for (int u : comp_nodes) { + if (!nodes[u].is_conductor) { + continue; + } + + if (nodes[u].declared_area > 0.0) { + if (nodes[u].is_cut) { + declared_cut_area += nodes[u].declared_area; + } else if (nodes[u].is_side) { + declared_side_area += nodes[u].declared_area; + } else if (nodes[u].is_routing) { + declared_routing_area += nodes[u].declared_area; + } + } + + for (const auto& s : nodes[u].shapes) { + if (s.layer_order != T) { + continue; + } + + if (nodes[u].is_routing) { + routing_rects.push_back(s.rect); + addRectToBBox(s.rect); + } else if (nodes[u].is_cut) { + cut_rects.push_back(s.rect); + addRectToBBox(s.rect); + } + } + } + + collectVictim(root); + + if (routing_rule == nullptr) { + root_data.cum_area_num += declared_routing_area; + root_data.cum_side_num += declared_side_area; + } + if (cut_rule == nullptr) { + root_data.cum_cut_num += declared_cut_area; + } + + double cut_area_um = declared_cut_area; + if (!cut_rects.empty()) { + double physical_cut_area_um = 0.0; + AntennaGeometry::unionArea(cut_rects, micron_dbu, physical_cut_area_um); + cut_area_um += physical_cut_area_um; + } + + double metal_area_um = declared_routing_area; + double side_area_um = declared_side_area; + if (!routing_rects.empty()) { + double physical_metal_area_um = 0.0; + double physical_metal_perimeter_um = 0.0; + AntennaGeometry::unionAreaPerimeter(routing_rects, micron_dbu, physical_metal_area_um, physical_metal_perimeter_um); + metal_area_um += physical_metal_area_um; + + double thickness_um = routing_rule ? routing_rule->thickness_um : 0.0; + if (thickness_um <= 0.0 && static_cast(T) < thickness_by_order.size()) { + thickness_um = thickness_by_order[T]; + } + side_area_um += physical_metal_perimeter_um * thickness_um; + } + + const bool has_routing + = !Utility::equalDoubleByError(metal_area_um, 0.0, ZH_ERROR) || !Utility::equalDoubleByError(side_area_um, 0.0, ZH_ERROR); + const bool has_cut = !Utility::equalDoubleByError(cut_area_um, 0.0, ZH_ERROR); + + if (!has_routing && !has_cut) { + comps_by_root[root].clear(); + continue; + } + + if (Utility::equalDoubleByError(root_data.gate_area, 0.0, ZH_ERROR) && Utility::equalDoubleByError(root_data.diff_area, 0.0, ZH_ERROR) + && !root_data.diff_connected) { + comps_without_gate.fetch_add(1, std::memory_order_relaxed); + comps_by_root[root].clear(); + continue; + } + + const bool diff_active = root_data.diff_connected || !Utility::equalDoubleByError(root_data.diff_area, 0.0, ZH_ERROR); + + if (!has_bbox) { + for (int u : comp_nodes) { + if (has_bbox) { + break; + } + for (const auto& s : nodes[u].shapes) { + addRectToBBox(s.rect); + } + } + } + + if (has_cut && cut_rule != nullptr) { + const double gate_plus_diff_factor = (cut_rule->gate_plus_diff >= 0.0) ? cut_rule->gate_plus_diff : 0.0; + const double eff_gate = root_data.gate_area + gate_plus_diff_factor * root_data.diff_area; + if (Utility::equalDoubleByError(eff_gate, 0.0, ZH_ERROR)) { + comps_without_gate.fetch_add(1, std::memory_order_relaxed); + } else { + AntennaGeometry::evaluateCut(*cut_rule, root_data, cut_area_um, diff_active, micron_dbu, has_bbox, bbox_lx, bbox_ly, bbox_hx, + bbox_hy, net_name, victim_pin, victim_inst, T, root_data.gate_area, root_data.diff_area, + metal_area_um, out_violations, root_data.cum_cut_num); + } + } + + if (has_routing && routing_rule != nullptr) { + const double gate_plus_diff_factor = (routing_rule->gate_plus_diff >= 0.0) ? routing_rule->gate_plus_diff : 0.0; + const double eff_gate = root_data.gate_area + gate_plus_diff_factor * root_data.diff_area; + if (Utility::equalDoubleByError(eff_gate, 0.0, ZH_ERROR)) { + comps_without_gate.fetch_add(1, std::memory_order_relaxed); + } else { + AntennaGeometry::evaluateRouting(*routing_rule, root_data, metal_area_um, side_area_um, diff_active, micron_dbu, has_bbox, bbox_lx, + bbox_ly, bbox_hx, bbox_hy, net_name, victim_pin, victim_inst, T, root_data.gate_area, + root_data.diff_area, cut_area_um, out_violations, root_data.cum_area_num, root_data.cum_side_num); + } + } + + comps_by_root[root].clear(); + } + } +} + +} // namespace izh diff --git a/src/operation/iZH/source/module/antenna_checker/AntennaPathAnalyzer.hpp b/src/operation/iZH/source/module/antenna_checker/AntennaPathAnalyzer.hpp new file mode 100644 index 0000000000..67d229dc78 --- /dev/null +++ b/src/operation/iZH/source/module/antenna_checker/AntennaPathAnalyzer.hpp @@ -0,0 +1,24 @@ +// *************************************************************************************** +// 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 +// *************************************************************************************** +#pragma once + +#include "ACModel.hpp" +#include "ACViolation.hpp" +#include "IdbDesign.h" +#include "IdbNet.h" +#include "IdbPins.h" + +namespace izh { + +class AntennaPathAnalyzer +{ + public: + static void checkNet(ACModel& ac_model, idb::IdbDesign* design, idb::IdbNet* net, std::vector& out_violations); + static void readPinAntennaInfo(ACModel& ac_model, idb::IdbPin* pin, bool instance_pin, double& gate_area, double& diff_area, + bool& provides_diff); +}; + +} // namespace izh diff --git a/src/operation/iZH/source/module/antenna_checker/AntennaRuleEvaluator.cpp b/src/operation/iZH/source/module/antenna_checker/AntennaRuleEvaluator.cpp new file mode 100644 index 0000000000..4ac079b15a --- /dev/null +++ b/src/operation/iZH/source/module/antenna_checker/AntennaRuleEvaluator.cpp @@ -0,0 +1,375 @@ +// *************************************************************************************** +// 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 +// *************************************************************************************** + +#include "AntennaRuleEvaluator.hpp" + +#include "Utility.hpp" + +#include "IdbLayer.h" +#include "IdbLayout.h" + +namespace izh { + +void AntennaRuleEvaluator::initLayers(ACModel& ac_model, idb::IdbDesign* design) +{ + std::vector& rules = ac_model.get_rule_list(); + std::vector>& rule_map = ac_model.get_rule_map(); + std::vector& thickness_by_order = ac_model.get_thickness_by_order(); + std::vector& conductive_order = ac_model.get_conductive_order(); + int& max_layer_order = ac_model.get_max_layer_order(); + if (!design || !design->get_layout()) { + return; + } + + idb::IdbLayers* layers = design->get_layout()->get_layers(); + if (!layers) { + return; + } + + rules.clear(); + rule_map.clear(); + thickness_by_order.clear(); + + max_layer_order = 0; + + for (idb::IdbLayer* layer : layers->get_layers()) { + if (layer) { + max_layer_order = std::max(max_layer_order, static_cast(layer->get_order())); + } + } + + for (idb::IdbLayer* layer : layers->get_routing_layers()) { + idb::IdbLayerRouting* routing = dynamic_cast(layer); + if (routing == nullptr) { + continue; + } + + bool has_any_rule = false; + + has_any_rule |= routing->has_antenna_area_ratio(); + has_any_rule |= routing->has_antenna_cum_area_ratio(); + has_any_rule |= routing->has_antenna_area_factor(); + has_any_rule |= routing->has_antenna_side_area_ratio(); + has_any_rule |= routing->has_antenna_cum_side_area_ratio(); + has_any_rule |= routing->has_antenna_side_area_factor(); + has_any_rule |= routing->has_antenna_gate_plus_diff(); + has_any_rule |= routing->has_antenna_area_minus_diff(); + has_any_rule |= routing->has_antenna_diff_area_ratio(); + has_any_rule |= routing->has_antenna_cum_diff_area_ratio(); + has_any_rule |= routing->has_antenna_diff_side_area_ratio(); + has_any_rule |= routing->has_antenna_cum_diff_side_area_ratio(); + has_any_rule |= routing->has_antenna_diff_area_ratio_pwl(); + has_any_rule |= routing->has_antenna_cum_diff_area_ratio_pwl(); + has_any_rule |= routing->has_antenna_diff_side_area_ratio_pwl(); + has_any_rule |= routing->has_antenna_cum_diff_side_area_ratio_pwl(); + has_any_rule |= routing->has_antenna_area_diff_reduce_pwl(); + has_any_rule |= routing->get_antenna_cum_routing_plus_cut(); + + if (!has_any_rule) { + continue; + } + + ACAntennaRule rule; + rule.layer_name = routing->get_name(); + rule.layer_order = static_cast(routing->get_order()); + rule.is_routing = true; + rule.thickness_um = Utility::getRatio(static_cast(routing->get_thickness()), ac_model.get_micron_dbu()); + + if (routing->has_antenna_area_ratio()) { + rule.area_ratio = routing->get_antenna_area_ratio(); + } + if (routing->has_antenna_cum_area_ratio()) { + rule.cum_area_ratio = routing->get_antenna_cum_area_ratio(); + } + if (routing->has_antenna_area_factor()) { + rule.area_factor = routing->get_antenna_area_factor(); + } + + rule.area_factor_diffuse_only = routing->get_antenna_area_factor_diffuse_only(); + + if (routing->has_antenna_side_area_ratio()) { + rule.side_area_ratio = routing->get_antenna_side_area_ratio(); + } + if (routing->has_antenna_cum_side_area_ratio()) { + rule.cum_side_area_ratio = routing->get_antenna_cum_side_area_ratio(); + } + if (routing->has_antenna_side_area_factor()) { + rule.side_area_factor = routing->get_antenna_side_area_factor(); + } + + rule.side_area_factor_diffuse_only = routing->get_antenna_side_area_factor_diffuse_only(); + + if (routing->has_antenna_gate_plus_diff()) { + rule.gate_plus_diff = routing->get_antenna_gate_plus_diff(); + } + if (routing->has_antenna_area_minus_diff()) { + rule.area_minus_diff = routing->get_antenna_area_minus_diff(); + } + if (routing->has_antenna_diff_area_ratio()) { + rule.diff_area_ratio = routing->get_antenna_diff_area_ratio(); + } + if (routing->has_antenna_cum_diff_area_ratio()) { + rule.cum_diff_area_ratio = routing->get_antenna_cum_diff_area_ratio(); + } + if (routing->has_antenna_diff_side_area_ratio()) { + rule.diff_side_area_ratio = routing->get_antenna_diff_side_area_ratio(); + } + if (routing->has_antenna_cum_diff_side_area_ratio()) { + rule.cum_diff_side_area_ratio = routing->get_antenna_cum_diff_side_area_ratio(); + } + + rule.cum_routing_plus_cut = routing->get_antenna_cum_routing_plus_cut(); + + if (routing->has_antenna_diff_area_ratio_pwl()) { + rule.diff_area_ratio_pwl = routing->get_antenna_diff_area_ratio_pwl(); + } + if (routing->has_antenna_cum_diff_area_ratio_pwl()) { + rule.cum_diff_area_ratio_pwl = routing->get_antenna_cum_diff_area_ratio_pwl(); + } + if (routing->has_antenna_diff_side_area_ratio_pwl()) { + rule.diff_side_area_ratio_pwl = routing->get_antenna_diff_side_area_ratio_pwl(); + } + if (routing->has_antenna_cum_diff_side_area_ratio_pwl()) { + rule.cum_diff_side_area_ratio_pwl = routing->get_antenna_cum_diff_side_area_ratio_pwl(); + } + if (routing->has_antenna_area_diff_reduce_pwl()) { + rule.area_diff_reduce_pwl = routing->get_antenna_area_diff_reduce_pwl(); + } + + rules.push_back(rule); + } + + for (idb::IdbLayer* layer : layers->get_cut_layers()) { + idb::IdbLayerCut* cut = dynamic_cast(layer); + if (cut == nullptr) { + continue; + } + + bool has_any_rule = false; + + has_any_rule |= cut->has_antenna_area_ratio(); + has_any_rule |= cut->has_antenna_cum_area_ratio(); + has_any_rule |= cut->has_antenna_area_factor(); + has_any_rule |= cut->has_antenna_diff_area_ratio(); + has_any_rule |= cut->has_antenna_cum_diff_area_ratio(); + has_any_rule |= cut->has_antenna_gate_plus_diff(); + has_any_rule |= cut->has_antenna_area_minus_diff(); + has_any_rule |= cut->has_antenna_diff_area_ratio_pwl(); + has_any_rule |= cut->has_antenna_cum_diff_area_ratio_pwl(); + has_any_rule |= cut->has_antenna_area_diff_reduce_pwl(); + has_any_rule |= cut->get_antenna_cum_routing_plus_cut(); + + if (!has_any_rule) { + continue; + } + + ACAntennaRule rule; + rule.layer_name = cut->get_name(); + rule.layer_order = static_cast(cut->get_order()); + rule.is_routing = false; + + if (cut->has_antenna_area_factor()) { + rule.area_factor = cut->get_antenna_area_factor(); + } + rule.area_factor_diffuse_only = cut->get_antenna_area_factor_diffuse_only(); + + if (cut->has_antenna_area_ratio()) { + rule.area_ratio = cut->get_antenna_area_ratio(); + } + if (cut->has_antenna_cum_area_ratio()) { + rule.cum_area_ratio = cut->get_antenna_cum_area_ratio(); + } + if (cut->has_antenna_diff_area_ratio()) { + rule.diff_area_ratio = cut->get_antenna_diff_area_ratio(); + } + if (cut->has_antenna_cum_diff_area_ratio()) { + rule.cum_diff_area_ratio = cut->get_antenna_cum_diff_area_ratio(); + } + if (cut->has_antenna_gate_plus_diff()) { + rule.gate_plus_diff = cut->get_antenna_gate_plus_diff(); + } + if (cut->has_antenna_area_minus_diff()) { + rule.area_minus_diff = cut->get_antenna_area_minus_diff(); + } + + rule.cum_routing_plus_cut = cut->get_antenna_cum_routing_plus_cut(); + + if (cut->has_antenna_diff_area_ratio_pwl()) { + rule.diff_area_ratio_pwl = cut->get_antenna_diff_area_ratio_pwl(); + } + if (cut->has_antenna_cum_diff_area_ratio_pwl()) { + rule.cum_diff_area_ratio_pwl = cut->get_antenna_cum_diff_area_ratio_pwl(); + } + if (cut->has_antenna_area_diff_reduce_pwl()) { + rule.area_diff_reduce_pwl = cut->get_antenna_area_diff_reduce_pwl(); + } + + rules.push_back(rule); + } + + for (idb::IdbLayer* layer : layers->get_layers()) { + idb::IdbLayerMasterslice* ms = dynamic_cast(layer); + if (ms == nullptr) { + continue; + } + + bool has_any_rule = false; + + has_any_rule |= ms->has_antenna_area_ratio(); + has_any_rule |= ms->has_antenna_cum_area_ratio(); + has_any_rule |= ms->has_antenna_area_factor(); + has_any_rule |= ms->has_antenna_side_area_ratio(); + has_any_rule |= ms->has_antenna_cum_side_area_ratio(); + has_any_rule |= ms->has_antenna_side_area_factor(); + has_any_rule |= ms->has_antenna_gate_plus_diff(); + has_any_rule |= ms->has_antenna_area_minus_diff(); + has_any_rule |= ms->has_antenna_diff_area_ratio(); + has_any_rule |= ms->has_antenna_cum_diff_area_ratio(); + has_any_rule |= ms->has_antenna_diff_side_area_ratio(); + has_any_rule |= ms->has_antenna_cum_diff_side_area_ratio(); + has_any_rule |= ms->has_antenna_diff_area_ratio_pwl(); + has_any_rule |= ms->has_antenna_cum_diff_area_ratio_pwl(); + has_any_rule |= ms->has_antenna_diff_side_area_ratio_pwl(); + has_any_rule |= ms->has_antenna_cum_diff_side_area_ratio_pwl(); + has_any_rule |= ms->has_antenna_area_diff_reduce_pwl(); + has_any_rule |= ms->get_antenna_cum_routing_plus_cut(); + + if (!has_any_rule) { + continue; + } + + ACAntennaRule rule; + rule.layer_name = ms->get_name(); + rule.layer_order = static_cast(ms->get_order()); + rule.is_routing = true; + rule.thickness_um = Utility::getRatio(static_cast(ms->get_thickness()), ac_model.get_micron_dbu()); + + if (ms->has_antenna_area_ratio()) { + rule.area_ratio = ms->get_antenna_area_ratio(); + } + if (ms->has_antenna_cum_area_ratio()) { + rule.cum_area_ratio = ms->get_antenna_cum_area_ratio(); + } + if (ms->has_antenna_area_factor()) { + rule.area_factor = ms->get_antenna_area_factor(); + } + + rule.area_factor_diffuse_only = ms->get_antenna_area_factor_diffuse_only(); + + if (ms->has_antenna_side_area_ratio()) { + rule.side_area_ratio = ms->get_antenna_side_area_ratio(); + } + if (ms->has_antenna_cum_side_area_ratio()) { + rule.cum_side_area_ratio = ms->get_antenna_cum_side_area_ratio(); + } + if (ms->has_antenna_side_area_factor()) { + rule.side_area_factor = ms->get_antenna_side_area_factor(); + } + + rule.side_area_factor_diffuse_only = ms->get_antenna_side_area_factor_diffuse_only(); + + if (ms->has_antenna_gate_plus_diff()) { + rule.gate_plus_diff = ms->get_antenna_gate_plus_diff(); + } + if (ms->has_antenna_area_minus_diff()) { + rule.area_minus_diff = ms->get_antenna_area_minus_diff(); + } + if (ms->has_antenna_diff_area_ratio()) { + rule.diff_area_ratio = ms->get_antenna_diff_area_ratio(); + } + if (ms->has_antenna_cum_diff_area_ratio()) { + rule.cum_diff_area_ratio = ms->get_antenna_cum_diff_area_ratio(); + } + if (ms->has_antenna_diff_side_area_ratio()) { + rule.diff_side_area_ratio = ms->get_antenna_diff_side_area_ratio(); + } + if (ms->has_antenna_cum_diff_side_area_ratio()) { + rule.cum_diff_side_area_ratio = ms->get_antenna_cum_diff_side_area_ratio(); + } + + rule.cum_routing_plus_cut = ms->get_antenna_cum_routing_plus_cut(); + + if (ms->has_antenna_diff_area_ratio_pwl()) { + rule.diff_area_ratio_pwl = ms->get_antenna_diff_area_ratio_pwl(); + } + if (ms->has_antenna_cum_diff_area_ratio_pwl()) { + rule.cum_diff_area_ratio_pwl = ms->get_antenna_cum_diff_area_ratio_pwl(); + } + if (ms->has_antenna_diff_side_area_ratio_pwl()) { + rule.diff_side_area_ratio_pwl = ms->get_antenna_diff_side_area_ratio_pwl(); + } + if (ms->has_antenna_cum_diff_side_area_ratio_pwl()) { + rule.cum_diff_side_area_ratio_pwl = ms->get_antenna_cum_diff_side_area_ratio_pwl(); + } + if (ms->has_antenna_area_diff_reduce_pwl()) { + rule.area_diff_reduce_pwl = ms->get_antenna_area_diff_reduce_pwl(); + } + + rules.push_back(rule); + } + + if (max_layer_order < 0) { + max_layer_order = 0; + } + + rule_map.resize(static_cast(max_layer_order) + 1); + thickness_by_order.assign(static_cast(max_layer_order) + 1, 0.0); + conductive_order.assign(static_cast(max_layer_order) + 1, false); + + for (const auto& rule : rules) { + if (rule.layer_order >= 0 && static_cast(rule.layer_order) < rule_map.size()) { + rule_map[rule.layer_order].push_back(&rule); + conductive_order[rule.layer_order] = true; + + if (rule.is_routing && rule.thickness_um > 0.0 && thickness_by_order[rule.layer_order] <= 0.0) { + thickness_by_order[rule.layer_order] = rule.thickness_um; + } + } + } +} + +const ACAntennaRule* AntennaRuleEvaluator::pickRule(const ACModel& ac_model, const int layer_order, const bool routing) +{ + const std::vector>& rule_map = ac_model.get_rule_map(); + if (layer_order < 0 || static_cast(layer_order) >= rule_map.size()) { + return nullptr; + } + + for (const ACAntennaRule* rule : rule_map[layer_order]) { + if (rule != nullptr && rule->is_routing == routing) { + return rule; + } + } + + return nullptr; +} + +ACThresholdPick AntennaRuleEvaluator::pickThreshold(const double plain_ratio, const double diff_ratio, + const std::vector>& diff_pwl, const double diff_area, + const bool diff_connected) +{ + const bool has_diff = (diff_ratio >= 0.0) || (!diff_pwl.empty()); + const bool has_plain = (plain_ratio >= 0.0); + const bool diff_active = diff_connected || !Utility::equalDoubleByError(diff_area, 0.0, ZH_ERROR); + bool use_diff = has_diff && (diff_active || !has_plain); + + if (use_diff && !diff_pwl.empty() && Utility::equalDoubleByError(diff_area, 0.0, ZH_ERROR) && has_plain) { + use_diff = false; + } + + if (use_diff) { + const double threshold = !diff_pwl.empty() ? Utility::getPWLValue(diff_pwl, diff_area, diff_ratio) : diff_ratio; + return {true, threshold, true}; + } + + if (has_plain) { + return {true, plain_ratio, false}; + } + + return {}; +} + +} // namespace izh diff --git a/src/operation/iZH/source/module/antenna_checker/AntennaRuleEvaluator.hpp b/src/operation/iZH/source/module/antenna_checker/AntennaRuleEvaluator.hpp new file mode 100644 index 0000000000..b6b7bb2262 --- /dev/null +++ b/src/operation/iZH/source/module/antenna_checker/AntennaRuleEvaluator.hpp @@ -0,0 +1,23 @@ +// *************************************************************************************** +// 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 +// *************************************************************************************** +#pragma once + +#include "ACModel.hpp" +#include "ACThresholdPick.hpp" +#include "IdbDesign.h" + +namespace izh { + +class AntennaRuleEvaluator +{ + public: + static void initLayers(ACModel& ac_model, idb::IdbDesign* design); + static const ACAntennaRule* pickRule(const ACModel& ac_model, int layer_order, bool routing); + static ACThresholdPick pickThreshold(double plain_ratio, double diff_ratio, const std::vector>& diff_pwl, + double diff_area, bool diff_connected); +}; + +} // namespace izh diff --git a/src/operation/iZH/source/module/antenna_checker/CMakeLists.txt b/src/operation/iZH/source/module/antenna_checker/CMakeLists.txt index 1dc0d4af36..ac0092aa96 100644 --- a/src/operation/iZH/source/module/antenna_checker/CMakeLists.txt +++ b/src/operation/iZH/source/module/antenna_checker/CMakeLists.txt @@ -1,4 +1,4 @@ -if(DEBUG_IZH_ANTENNA_CHECKER) +if(DEBUG_IZH_ANTENNA_CHECKER OR DEBUG_IZH_ANTENNA_FIXER) message(STATUS "ZH: DEBUG_IZH_ANTENNA_CHECKER") set(CMAKE_BUILD_TYPE "Debug") else() @@ -9,6 +9,13 @@ endif() add_library(izh_antenna_checker ## module antenna_checker ${IZH_MODULE}/antenna_checker/AntennaChecker.cpp + ${IZH_MODULE}/antenna_checker/AntennaRuleEvaluator.cpp + ${IZH_MODULE}/antenna_checker/AntennaPathAnalyzer.cpp + ${IZH_MODULE}/antenna_checker/AntennaGeometry.cpp + ${IZH_MODULE}/antenna_checker/AntennaEngine.cpp + ${IZH_MODULE}/antenna_checker/AntennaFixer.cpp + ${IZH_MODULE}/antenna_checker/RoutingContext.cpp + ${IZH_MODULE}/antenna_checker/WireEnvIndex.cpp ) target_link_libraries(izh_antenna_checker diff --git a/src/operation/iZH/source/module/antenna_checker/RoutingContext.cpp b/src/operation/iZH/source/module/antenna_checker/RoutingContext.cpp new file mode 100644 index 0000000000..7995d29e4f --- /dev/null +++ b/src/operation/iZH/source/module/antenna_checker/RoutingContext.cpp @@ -0,0 +1,219 @@ +// *************************************************************************************** +// 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 +// *************************************************************************************** + +#include "RoutingContext.hpp" + +#include "Logger.hpp" +#include "Utility.hpp" + +#include "IdbDie.h" +#include "IdbLayout.h" +#include "IdbRow.h" +#include "IdbSite.h" +#include "IdbUnits.h" +#include "IdbVias.h" + +namespace izh { + +RoutingContext RoutingContext::forTest(std::vector routing_layers, std::vector cut_layers, + std::vector diode_masters) +{ + RoutingContext ctx; + ctx._routing_layers = std::move(routing_layers); + ctx._cut_layers = std::move(cut_layers); + ctx._diode_masters = std::move(diode_masters); + ctx._top_routing_order = -1; + for (const auto& rl : ctx._routing_layers) { + ctx._top_routing_order = std::max(ctx._top_routing_order, rl.order); + } + ctx._die_llx = 0; + ctx._die_lly = 0; + ctx._die_urx = 100000; + ctx._die_ury = 100000; + ctx._max_jog = 500; + ctx._search_radius = 5000; + ctx._micron_dbu = 1000; + return ctx; +} + +const RCRoutingLayer* RoutingContext::findRoutingByOrder(int order) const +{ + for (const auto& layer : _routing_layers) { + if (layer.order == order) { + return &layer; + } + } + return nullptr; +} + +const RCRoutingLayer* RoutingContext::findNextRouting(int order) const +{ + const RCRoutingLayer* best = nullptr; + for (const auto& layer : _routing_layers) { + if (layer.order > order && (best == nullptr || layer.order < best->order)) { + best = &layer; + } + } + return best; +} + +const RCCutLayer* RoutingContext::findCutBetween(int below_order, int above_order) const +{ + const RCCutLayer* best = nullptr; + for (const auto& cut : _cut_layers) { + if (cut.order > below_order && cut.order < above_order) { + if (best == nullptr || cut.order < best->order) { + best = &cut; + } + } + } + return best; +} + +idb::IdbVia* RoutingContext::findViaBetween(int below_order, int above_order) const +{ + const RCCutLayer* cut = findCutBetween(below_order, above_order); + return cut != nullptr ? cut->via : nullptr; +} + +RoutingContext RoutingContext::build(idb::IdbDesign* design, const AFComParam& param) +{ + RoutingContext ctx; + ctx._design = design; + ctx._enable_drc = param.get_enable_drc(); + if (design == nullptr || design->get_layout() == nullptr) { + return ctx; + } + + idb::IdbLayout* layout = design->get_layout(); + if (layout->get_units() != nullptr) { + const int dbu = layout->get_units()->get_micron_dbu(); + if (dbu > 0) { + ctx._micron_dbu = dbu; + } + } + + if (layout->get_die() != nullptr) { + ctx._die_llx = layout->get_die()->get_llx(); + ctx._die_lly = layout->get_die()->get_lly(); + ctx._die_urx = layout->get_die()->get_urx(); + ctx._die_ury = layout->get_die()->get_ury(); + } + + idb::IdbLayers* layers = layout->get_layers(); + if (layers != nullptr) { + for (idb::IdbLayer* layer : layers->get_routing_layers()) { + idb::IdbLayerRouting* routing = dynamic_cast(layer); + if (routing == nullptr) { + continue; + } + RCRoutingLayer info; + info.layer = routing; + info.order = static_cast(routing->get_order()); + info.pitch = routing->get_pitch_prefer() > 0 ? routing->get_pitch_prefer() : routing->get_pitch_x(); + info.width = routing->get_width(); + info.min_area = routing->get_area(); + info.spacing = routing->get_spacing(info.width); + info.horizontal = routing->is_horizontal(); + if (info.pitch <= 0) { + info.pitch = info.width > 0 ? info.width * 2 : 1; + } + if (info.spacing <= 0) { + info.spacing = info.width; + } + ctx._routing_layers.push_back(info); + ctx._top_routing_order = std::max(ctx._top_routing_order, info.order); + } + + for (idb::IdbLayer* layer : layers->get_cut_layers()) { + idb::IdbLayerCut* cut = dynamic_cast(layer); + if (cut == nullptr) { + continue; + } + RCCutLayer info; + info.layer = cut; + info.order = static_cast(cut->get_order()); + if (!cut->get_spacings().empty() && cut->get_spacings().front() != nullptr) { + info.spacing = cut->get_spacings().front()->get_spacing(); + } + ctx._cut_layers.push_back(info); + } + } + + idb::IdbVias* lef_vias = layout->get_via_list(); + if (lef_vias != nullptr) { + for (RCCutLayer& cut : ctx._cut_layers) { + for (idb::IdbVia* via : lef_vias->get_via_list()) { + if (via == nullptr || via->get_instance() == nullptr) { + continue; + } + idb::IdbLayerShape* cut_shape = via->get_instance()->get_cut_layer_shape(); + if (cut_shape == nullptr || cut_shape->get_layer() != cut.layer) { + continue; + } + cut.via = via; + break; + } + } + } + + if (layout->get_rows() != nullptr) { + for (idb::IdbRow* row : layout->get_rows()->get_row_list()) { + if (row == nullptr || row->get_site() == nullptr || row->get_original_coordinate() == nullptr) { + continue; + } + if (!row->is_horizontal()) { + continue; + } + RCRow rc_row; + rc_row.origin_x = row->get_original_coordinate()->get_x(); + rc_row.origin_y = row->get_original_coordinate()->get_y(); + rc_row.site_width = row->get_step_x() > 0 ? row->get_step_x() : row->get_site()->get_width(); + rc_row.row_height = row->get_site()->get_height(); + rc_row.site_count = row->get_row_num_x(); + rc_row.orient = row->get_orient() == idb::IdbOrient::kNone ? idb::IdbOrient::kN_R0 : row->get_orient(); + if (rc_row.site_width > 0 && rc_row.row_height > 0 && rc_row.site_count > 0) { + ctx._rows.push_back(rc_row); + } + } + } + + int32_t site_width = ctx._rows.empty() ? 0 : ctx._rows.front().site_width; + int32_t pitch = ctx._routing_layers.empty() ? site_width : ctx._routing_layers.front().pitch; + ctx._search_radius = param.get_search_radius(); + ctx._max_jog = param.get_max_jog(); + if (ctx._search_radius <= 0) { + ctx._search_radius = site_width > 0 ? site_width * 10 : (pitch > 0 ? pitch * 10 : 1000); + } + if (ctx._max_jog <= 0) { + ctx._max_jog = pitch > 0 ? pitch * 20 : ctx._search_radius; + } + + if (layout->get_cell_master_list() != nullptr) { + if (!param.get_diode_name_list().empty()) { + for (const std::string& name : param.get_diode_name_list()) { + idb::IdbCellMaster* master = layout->get_cell_master_list()->find_cell_master(name); + if (master != nullptr) { + ctx._diode_masters.push_back(master); + } else { + ZHLOG.warn(Loc::current(), "Cannot find antenna diode cell: ", name); + } + } + } else { + for (idb::IdbCellMaster* master : layout->get_cell_master_list()->get_cell_master()) { + if (master != nullptr && master->is_antenna_cell()) { + ctx._diode_masters.push_back(master); + } + } + } + std::sort(ctx._diode_masters.begin(), ctx._diode_masters.end(), + [](idb::IdbCellMaster* a, idb::IdbCellMaster* b) { return a->get_width() < b->get_width(); }); + } + + return ctx; +} + +} // namespace izh diff --git a/src/operation/iZH/source/module/antenna_checker/RoutingContext.hpp b/src/operation/iZH/source/module/antenna_checker/RoutingContext.hpp new file mode 100644 index 0000000000..6ad7ba4e69 --- /dev/null +++ b/src/operation/iZH/source/module/antenna_checker/RoutingContext.hpp @@ -0,0 +1,95 @@ +// *************************************************************************************** +// 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 +// *************************************************************************************** +#pragma once + +#include "AFComParam.hpp" +#include "IdbCellMaster.h" +#include "IdbDesign.h" +#include "IdbDie.h" +#include "IdbLayer.h" +#include "IdbViaMaster.h" +#include "IdbVias.h" +#include "ZHHeader.hpp" + +namespace izh { + +class RCRoutingLayer +{ + public: + idb::IdbLayerRouting* layer = nullptr; + int order = -1; + int32_t pitch = 0; + int32_t width = 0; + int32_t min_area = 0; + int32_t spacing = 0; + bool horizontal = true; +}; + +class RCCutLayer +{ + public: + idb::IdbLayerCut* layer = nullptr; + int order = -1; + int32_t spacing = 0; + idb::IdbVia* via = nullptr; +}; + +class RCRow +{ + public: + int32_t origin_x = 0; + int32_t origin_y = 0; + int32_t site_width = 0; + int32_t row_height = 0; + int32_t site_count = 0; + idb::IdbOrient orient = idb::IdbOrient::kN_R0; +}; + +class RoutingContext +{ + public: + static RoutingContext build(idb::IdbDesign* design, const AFComParam& param); + static RoutingContext forTest(std::vector routing_layers, std::vector cut_layers, + std::vector diode_masters = {}); + + idb::IdbDesign* get_design() const { return _design; } + int get_micron_dbu() const { return _micron_dbu; } + int32_t get_die_llx() const { return _die_llx; } + int32_t get_die_lly() const { return _die_lly; } + int32_t get_die_urx() const { return _die_urx; } + int32_t get_die_ury() const { return _die_ury; } + const std::vector& get_routing_layers() const { return _routing_layers; } + const std::vector& get_cut_layers() const { return _cut_layers; } + const std::vector& get_rows() const { return _rows; } + const std::vector& get_diode_masters() const { return _diode_masters; } + int32_t get_search_radius() const { return _search_radius; } + int32_t get_max_jog() const { return _max_jog; } + bool get_enable_drc() const { return _enable_drc; } + int get_top_routing_order() const { return _top_routing_order; } + + const RCRoutingLayer* findRoutingByOrder(int order) const; + const RCRoutingLayer* findNextRouting(int order) const; + const RCCutLayer* findCutBetween(int below_order, int above_order) const; + idb::IdbVia* findViaBetween(int below_order, int above_order) const; + + private: + idb::IdbDesign* _design = nullptr; + int _micron_dbu = 1000; + int32_t _die_llx = 0; + int32_t _die_lly = 0; + int32_t _die_urx = 0; + int32_t _die_ury = 0; + std::vector _routing_layers; + std::vector _cut_layers; + std::vector _rows; + std::vector _diode_masters; + int32_t _search_radius = 0; + int32_t _max_jog = 0; + bool _enable_drc = false; + int _top_routing_order = -1; +}; + +} // namespace izh diff --git a/src/operation/iZH/source/module/antenna_checker/WireEnvIndex.cpp b/src/operation/iZH/source/module/antenna_checker/WireEnvIndex.cpp new file mode 100644 index 0000000000..87b4f84f30 --- /dev/null +++ b/src/operation/iZH/source/module/antenna_checker/WireEnvIndex.cpp @@ -0,0 +1,266 @@ +// *************************************************************************************** +// 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 +// *************************************************************************************** + +#include "WireEnvIndex.hpp" + +#include "IdbBlockages.h" +#include "IdbInstance.h" +#include "IdbLayer.h" +#include "IdbNet.h" +#include "IdbRegularWire.h" +#include "IdbVias.h" + +namespace izh { + +void WireEnvIndex::insertRect(std::map& tree_map, int order, int32_t lx, int32_t ly, int32_t hx, int32_t hy, idb::IdbNet* net) +{ + if (hx < lx) { + std::swap(hx, lx); + } + if (hy < ly) { + std::swap(hy, ly); + } + BGRectInt box(BGPointInt(lx, ly), BGPointInt(hx, hy)); + tree_map[order].insert(std::make_pair(box, net)); +} + +void WireEnvIndex::insertWire(int layer_order, int32_t lx, int32_t ly, int32_t hx, int32_t hy, idb::IdbNet* net) +{ + insertRect(_wire_rtree, layer_order, lx, ly, hx, hy, net); +} + +void WireEnvIndex::insertVia(idb::IdbVia* via, idb::IdbNet* net) +{ + if (via == nullptr) { + return; + } + idb::IdbLayerShape cut_shape = via->get_cut_layer_shape(); + if (cut_shape.get_layer() != nullptr) { + int cut_order = static_cast(cut_shape.get_layer()->get_order()); + for (idb::IdbRect* r : cut_shape.get_rect_list()) { + if (r != nullptr) { + insertRect(_via_rtree, cut_order, r->get_low_x(), r->get_low_y(), r->get_high_x(), r->get_high_y(), net); + } + } + } + idb::IdbLayerShape bottom_shape = via->get_bottom_layer_shape(); + if (bottom_shape.get_layer() != nullptr) { + int order = static_cast(bottom_shape.get_layer()->get_order()); + for (idb::IdbRect* r : bottom_shape.get_rect_list()) { + if (r != nullptr) { + insertRect(_wire_rtree, order, r->get_low_x(), r->get_low_y(), r->get_high_x(), r->get_high_y(), net); + } + } + } + idb::IdbLayerShape top_shape = via->get_top_layer_shape(); + if (top_shape.get_layer() != nullptr) { + int order = static_cast(top_shape.get_layer()->get_order()); + for (idb::IdbRect* r : top_shape.get_rect_list()) { + if (r != nullptr) { + insertRect(_wire_rtree, order, r->get_low_x(), r->get_low_y(), r->get_high_x(), r->get_high_y(), net); + } + } + } +} + +void WireEnvIndex::insertInstance(idb::IdbInstance* inst) +{ + if (inst == nullptr || inst->get_bounding_box() == nullptr) { + return; + } + idb::IdbRect* box = inst->get_bounding_box(); + _inst_rtree.insert(std::make_pair(BGRectInt(BGPointInt(box->get_low_x(), box->get_low_y()), BGPointInt(box->get_high_x(), box->get_high_y())), 0)); +} + +void WireEnvIndex::rebuild(idb::IdbDesign* design, const RoutingContext& ctx) +{ + _wire_rtree.clear(); + _via_rtree.clear(); + _inst_rtree = InstRTree(); + if (design == nullptr) { + return; + } + + if (design->get_net_list() != nullptr) { + for (idb::IdbNet* net : design->get_net_list()->get_net_list()) { + if (net == nullptr || net->get_wire_list() == nullptr) { + continue; + } + for (idb::IdbRegularWire* wire : net->get_wire_list()->get_wire_list()) { + if (wire == nullptr) { + continue; + } + for (idb::IdbRegularWireSegment* seg : wire->get_segment_list()) { + if (seg == nullptr) { + continue; + } + if (seg->is_wire() && seg->get_layer() != nullptr) { + idb::IdbRect rect = seg->get_segment_rect(); + insertRect(_wire_rtree, static_cast(seg->get_layer()->get_order()), rect.get_low_x(), rect.get_low_y(), rect.get_high_x(), + rect.get_high_y(), net); + } + if (seg->is_rect() && seg->get_layer() != nullptr && seg->get_point_start() != nullptr && seg->get_delta_rect() != nullptr) { + int32_t x = seg->get_point_start()->get_x(); + int32_t y = seg->get_point_start()->get_y(); + insertRect(_wire_rtree, static_cast(seg->get_layer()->get_order()), x + seg->get_delta_rect()->get_low_x(), + y + seg->get_delta_rect()->get_low_y(), x + seg->get_delta_rect()->get_high_x(), + y + seg->get_delta_rect()->get_high_y(), net); + } + if (seg->is_via()) { + for (idb::IdbVia* via : seg->get_via_list()) { + insertVia(via, net); + } + } + } + } + } + } + + std::vector inst_items; + if (design->get_instance_list() != nullptr) { + for (idb::IdbInstance* inst : design->get_instance_list()->get_instance_list()) { + if (inst == nullptr || inst->get_bounding_box() == nullptr) { + continue; + } + idb::IdbRect* box = inst->get_bounding_box(); + inst_items.emplace_back(BGRectInt(BGPointInt(box->get_low_x(), box->get_low_y()), BGPointInt(box->get_high_x(), box->get_high_y())), + 0); + } + } + if (design->get_blockage_list() != nullptr) { + for (idb::IdbBlockage* blk : design->get_blockage_list()->get_blockage_list()) { + if (blk == nullptr || !blk->is_palcement_blockage()) { + continue; + } + for (idb::IdbRect* r : blk->get_rect_list()) { + if (r != nullptr) { + inst_items.emplace_back(BGRectInt(BGPointInt(r->get_low_x(), r->get_low_y()), BGPointInt(r->get_high_x(), r->get_high_y())), 1); + } + } + } + } + if (!inst_items.empty()) { + _inst_rtree = InstRTree(inst_items.begin(), inst_items.end()); + } + + (void) ctx; +} + +bool WireEnvIndex::hasOverlap(int layer_order, int32_t lx, int32_t ly, int32_t hx, int32_t hy, idb::IdbNet* skip_net) const +{ + auto it = _wire_rtree.find(layer_order); + if (it == _wire_rtree.end()) { + return false; + } + if (hx < lx) { + std::swap(hx, lx); + } + if (hy < ly) { + std::swap(hy, ly); + } + BGRectInt query(BGPointInt(lx, ly), BGPointInt(hx, hy)); + std::vector hits; + it->second.query(bgi::intersects(query), std::back_inserter(hits)); + for (const auto& hit : hits) { + if (hit.second != skip_net) { + return true; + } + } + return false; +} + +bool WireEnvIndex::hasViaOverlap(int32_t x, int32_t y, int cut_order, int32_t spacing, idb::IdbNet* skip_net) const +{ + auto it = _via_rtree.find(cut_order); + if (it == _via_rtree.end()) { + return false; + } + int32_t pad = std::max(spacing, 1); + BGRectInt query(BGPointInt(x - pad, y - pad), BGPointInt(x + pad, y + pad)); + std::vector hits; + it->second.query(bgi::intersects(query), std::back_inserter(hits)); + for (const auto& hit : hits) { + if (hit.second != skip_net) { + return true; + } + } + return false; +} + +bool WireEnvIndex::inDie(const RoutingContext& ctx, int32_t x, int32_t y) const +{ + return x >= ctx.get_die_llx() && x <= ctx.get_die_urx() && y >= ctx.get_die_lly() && y <= ctx.get_die_ury(); +} + +bool WireEnvIndex::hasInstanceOverlap(int32_t lx, int32_t ly, int32_t hx, int32_t hy) const +{ + if (hx < lx) { + std::swap(hx, lx); + } + if (hy < ly) { + std::swap(hy, ly); + } + BGRectInt query(BGPointInt(lx, ly), BGPointInt(hx, hy)); + std::vector hits; + _inst_rtree.query(bgi::intersects(query), std::back_inserter(hits)); + return !hits.empty(); +} + +bool WireEnvIndex::findFreeDiodeSite(const RoutingContext& ctx, int32_t pin_x, int32_t pin_y, int32_t width, int32_t height, int32_t& out_x, + int32_t& out_y, idb::IdbOrient& out_orient) const +{ + if (ctx.get_rows().empty() || width <= 0 || height <= 0) { + return false; + } + + std::vector sorted_rows; + sorted_rows.reserve(ctx.get_rows().size()); + for (const RCRow& row : ctx.get_rows()) { + sorted_rows.push_back(&row); + } + std::sort(sorted_rows.begin(), sorted_rows.end(), [pin_y](const RCRow* a, const RCRow* b) { + return std::abs(a->origin_y - pin_y) < std::abs(b->origin_y - pin_y); + }); + + int32_t max_row_attempts = std::min(static_cast(sorted_rows.size()), 3); + + for (int32_t ri = 0; ri < max_row_attempts; ++ri) { + const RCRow* row = sorted_rows[ri]; + int32_t site_need = (width + row->site_width - 1) / row->site_width; + if (site_need <= 0) { + site_need = 1; + } + int32_t pin_site = (pin_x - row->origin_x) / row->site_width; + int32_t max_sites = ctx.get_search_radius() / std::max(row->site_width, 1); + + for (int32_t d = 0; d <= max_sites; ++d) { + for (int32_t sign : {0, 1, -1}) { + if (d == 0 && sign != 0) { + continue; + } + int32_t site = pin_site + sign * d; + if (site < 0 || site + site_need > row->site_count) { + continue; + } + int32_t x = row->origin_x + site * row->site_width; + int32_t y = row->origin_y; + if (hasInstanceOverlap(x, y, x + width, y + height)) { + continue; + } + if (!inDie(ctx, x, y) || !inDie(ctx, x + width, y + height)) { + continue; + } + out_x = x; + out_y = y; + out_orient = row->orient; + return true; + } + } + } + return false; +} + +} // namespace izh diff --git a/src/operation/iZH/source/module/antenna_checker/WireEnvIndex.hpp b/src/operation/iZH/source/module/antenna_checker/WireEnvIndex.hpp new file mode 100644 index 0000000000..ab774f2351 --- /dev/null +++ b/src/operation/iZH/source/module/antenna_checker/WireEnvIndex.hpp @@ -0,0 +1,45 @@ +// *************************************************************************************** +// 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 +// *************************************************************************************** +#pragma once + +#include "IdbDesign.h" +#include "IdbGeometry.h" +#include "IdbInstance.h" +#include "IdbNet.h" +#include "IdbVias.h" +#include "RoutingContext.hpp" +#include "ZHHeader.hpp" + +namespace izh { + +class WireEnvIndex +{ + public: + void rebuild(idb::IdbDesign* design, const RoutingContext& ctx); + void insertWire(int layer_order, int32_t lx, int32_t ly, int32_t hx, int32_t hy, idb::IdbNet* net); + void insertVia(idb::IdbVia* via, idb::IdbNet* net); + void insertInstance(idb::IdbInstance* inst); + bool hasOverlap(int layer_order, int32_t lx, int32_t ly, int32_t hx, int32_t hy, idb::IdbNet* skip_net = nullptr) const; + bool hasViaOverlap(int32_t x, int32_t y, int cut_order, int32_t spacing, idb::IdbNet* skip_net = nullptr) const; + bool inDie(const RoutingContext& ctx, int32_t x, int32_t y) const; + bool hasInstanceOverlap(int32_t lx, int32_t ly, int32_t hx, int32_t hy) const; + bool findFreeDiodeSite(const RoutingContext& ctx, int32_t pin_x, int32_t pin_y, int32_t width, int32_t height, int32_t& out_x, + int32_t& out_y, idb::IdbOrient& out_orient) const; + + private: + using Item = std::pair; + using RTree = bgi::rtree>; + using InstItem = std::pair; + using InstRTree = bgi::rtree>; + + void insertRect(std::map& tree_map, int order, int32_t lx, int32_t ly, int32_t hx, int32_t hy, idb::IdbNet* net); + + std::map _wire_rtree; + std::map _via_rtree; + InstRTree _inst_rtree; +}; + +} // namespace izh diff --git a/src/operation/iZH/source/module/antenna_checker/ac_data_manager/ACGraphNode.hpp b/src/operation/iZH/source/module/antenna_checker/ac_data_manager/ACGraphNode.hpp index 51be730369..2d8a9c03c8 100644 --- a/src/operation/iZH/source/module/antenna_checker/ac_data_manager/ACGraphNode.hpp +++ b/src/operation/iZH/source/module/antenna_checker/ac_data_manager/ACGraphNode.hpp @@ -23,6 +23,8 @@ class ACGraphNode double gate_area = 0.0; double diff_area = 0.0; bool provides_diff = false; + std::string pin_name; + std::string inst_name; std::vector shapes; }; diff --git a/src/operation/iZH/source/module/antenna_checker/ac_data_manager/ACViolation.hpp b/src/operation/iZH/source/module/antenna_checker/ac_data_manager/ACViolation.hpp index 6e6c974bab..4705e4504e 100644 --- a/src/operation/iZH/source/module/antenna_checker/ac_data_manager/ACViolation.hpp +++ b/src/operation/iZH/source/module/antenna_checker/ac_data_manager/ACViolation.hpp @@ -22,6 +22,13 @@ class ACViolation double ly = 0.0; double hx = 0.0; double hy = 0.0; + std::string pin_name; + std::string inst_name; + int layer_order = -1; + double gate_area = 0.0; + double diff_area = 0.0; + double metal_area = 0.0; + double cut_area = 0.0; }; } // namespace izh diff --git a/src/operation/iZH/source/module/antenna_checker/ac_data_manager/AFComParam.hpp b/src/operation/iZH/source/module/antenna_checker/ac_data_manager/AFComParam.hpp new file mode 100644 index 0000000000..a94efe5dff --- /dev/null +++ b/src/operation/iZH/source/module/antenna_checker/ac_data_manager/AFComParam.hpp @@ -0,0 +1,42 @@ +// *************************************************************************************** +// 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 +// *************************************************************************************** +#pragma once + +#include "ZHHeader.hpp" + +namespace izh { + +class AFComParam +{ + public: + bool get_enable_fix() const { return _enable_fix; } + int32_t get_max_iter() const { return _max_iter; } + const std::vector& get_diode_name_list() const { return _diode_name_list; } + std::vector& get_diode_name_list() { return _diode_name_list; } + const std::string& get_report_dir() const { return _report_dir; } + std::string& get_report_dir() { return _report_dir; } + int32_t get_search_radius() const { return _search_radius; } + int32_t get_max_jog() const { return _max_jog; } + bool get_enable_drc() const { return _enable_drc; } + + void set_enable_fix(const bool enable_fix) { _enable_fix = enable_fix; } + void set_max_iter(const int32_t max_iter) { _max_iter = max_iter; } + void set_report_dir(const std::string& report_dir) { _report_dir = report_dir; } + void set_search_radius(const int32_t search_radius) { _search_radius = search_radius; } + void set_max_jog(const int32_t max_jog) { _max_jog = max_jog; } + void set_enable_drc(const bool enable_drc) { _enable_drc = enable_drc; } + + private: + bool _enable_fix = true; + int32_t _max_iter = 3; + std::vector _diode_name_list; + std::string _report_dir; + int32_t _search_radius = 0; + int32_t _max_jog = 0; + bool _enable_drc = false; +}; + +} // namespace izh diff --git a/src/operation/iZH/source/module/antenna_checker/ac_data_manager/AntennaResult.hpp b/src/operation/iZH/source/module/antenna_checker/ac_data_manager/AntennaResult.hpp new file mode 100644 index 0000000000..4f9a946ff9 --- /dev/null +++ b/src/operation/iZH/source/module/antenna_checker/ac_data_manager/AntennaResult.hpp @@ -0,0 +1,89 @@ +// *************************************************************************************** +// 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 +// *************************************************************************************** +#pragma once + +#include "ACViolation.hpp" +#include "ZHHeader.hpp" + +namespace izh { + +enum class AFRepairType +{ + kNone, + kHopUp, + kJog, + kDiode +}; + +class AFIterStat +{ + public: + int32_t iter = 0; + int32_t violation_num = 0; + int32_t hop_up_applied = 0; + int32_t hop_up_rejected = 0; + int32_t jog_applied = 0; + int32_t jog_rejected = 0; + int32_t diode_applied = 0; + int32_t diode_rejected = 0; +}; + +class AntennaResult +{ + public: + std::vector& get_violation_list() { return _violation_list; } + const std::vector& get_violation_list() const { return _violation_list; } + int32_t get_violation_num() const { return static_cast(_violation_list.size()); } + std::vector& get_iter_stat_list() { return _iter_stat_list; } + const std::vector& get_iter_stat_list() const { return _iter_stat_list; } + bool& get_logged_no_antenna_cell() { return _logged_no_antenna_cell; } + bool get_logged_no_antenna_cell() const { return _logged_no_antenna_cell; } + + private: + std::vector _violation_list; + std::vector _iter_stat_list; + bool _logged_no_antenna_cell = false; +}; + +inline const char* acViolationTypeToString(ACViolationType type) +{ + switch (type) { + case ACViolationType::kAntennaPar: + return "PAR"; + case ACViolationType::kAntennaDiffPar: + return "DiffPAR"; + case ACViolationType::kAntennaCar: + return "CAR"; + case ACViolationType::kAntennaDiffCar: + return "DiffCAR"; + case ACViolationType::kAntennaPsr: + return "PSR"; + case ACViolationType::kAntennaDiffPsr: + return "DiffPSR"; + case ACViolationType::kAntennaCsr: + return "CSR"; + case ACViolationType::kAntennaDiffCsr: + return "DiffCSR"; + case ACViolationType::kAntennaCutPar: + return "CutPAR"; + case ACViolationType::kAntennaCutCar: + return "CutCAR"; + case ACViolationType::kAntennaDiffCutPar: + return "DiffCutPAR"; + case ACViolationType::kAntennaDiffCutCar: + return "DiffCutCAR"; + default: + return "UNKNOWN"; + } +} + +inline bool acViolationIsCut(ACViolationType type) +{ + return type == ACViolationType::kAntennaCutPar || type == ACViolationType::kAntennaCutCar + || type == ACViolationType::kAntennaDiffCutPar || type == ACViolationType::kAntennaDiffCutCar; +} + +} // namespace izh diff --git a/src/operation/iZH/test/CMakeLists.txt b/src/operation/iZH/test/CMakeLists.txt index a1f7a07228..e0dc968539 100644 --- a/src/operation/iZH/test/CMakeLists.txt +++ b/src/operation/iZH/test/CMakeLists.txt @@ -1,5 +1,6 @@ if(BUILD_TESTING) add_subdirectory(${IZH_TEST}/test_antenna_checker) + add_subdirectory(${IZH_TEST}/test_antenna_fixer) add_subdirectory(${IZH_TEST}/test_filler_inserter) add_subdirectory(${IZH_TEST}/test_metal_inserter) endif() diff --git a/src/operation/iZH/test/test_antenna_checker/test_antenna_checker.cpp b/src/operation/iZH/test/test_antenna_checker/test_antenna_checker.cpp index 174a36eb52..22ad83b22e 100644 --- a/src/operation/iZH/test/test_antenna_checker/test_antenna_checker.cpp +++ b/src/operation/iZH/test/test_antenna_checker/test_antenna_checker.cpp @@ -15,6 +15,7 @@ // *************************************************************************************** #include "ACModel.hpp" #include "AntennaChecker.hpp" +#include "AntennaRuleEvaluator.hpp" #include "Utility.hpp" int main() @@ -37,6 +38,11 @@ int main() return 1; } + izh::ACThresholdPick pick = izh::AntennaRuleEvaluator::pickThreshold(-1.0, -1.0, pwl, 1.0, true); + if (!pick.available || !izh::Utility::equalDoubleByError(pick.threshold, 5.0, ZH_ERROR)) { + return 1; + } + izh::AntennaChecker::initInst(); izh::AntennaChecker* ac_instance = &ZHAC; izh::AntennaChecker::initInst(); diff --git a/src/operation/iZH/test/test_antenna_fixer/CMakeLists.txt b/src/operation/iZH/test/test_antenna_fixer/CMakeLists.txt new file mode 100644 index 0000000000..f6d342a3fa --- /dev/null +++ b/src/operation/iZH/test/test_antenna_fixer/CMakeLists.txt @@ -0,0 +1,10 @@ +add_executable(test_izh_antenna_fixer + ${IZH_TEST}/test_antenna_fixer/test_antenna_fixer.cpp +) + +target_link_libraries(test_izh_antenna_fixer + PRIVATE + izh_antenna_checker +) + +add_test(NAME test_izh_antenna_fixer COMMAND test_izh_antenna_fixer) diff --git a/src/operation/iZH/test/test_antenna_fixer/test_antenna_fixer.cpp b/src/operation/iZH/test/test_antenna_fixer/test_antenna_fixer.cpp new file mode 100644 index 0000000000..ecc335876e --- /dev/null +++ b/src/operation/iZH/test/test_antenna_fixer/test_antenna_fixer.cpp @@ -0,0 +1,377 @@ +// *************************************************************************************** +// 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 +// *************************************************************************************** +#include "ACViolation.hpp" +#include "AntennaEngine.hpp" +#include "AntennaFixer.hpp" +#include "AntennaResult.hpp" +#include "AntennaRuleEvaluator.hpp" +#include "IdbCellMaster.h" +#include "IdbEnum.h" +#include "IdbLayer.h" +#include "IdbNet.h" +#include "IdbVias.h" +#include "RoutingContext.hpp" +#include "Utility.hpp" +#include "WireEnvIndex.hpp" + +static izh::ACViolation makeCutViolation() +{ + izh::ACViolation v; + v.net_name = "n1"; + v.pin_name = "A"; + v.inst_name = "u1"; + v.layer_order = 2; + v.type = izh::ACViolationType::kAntennaCutPar; + return v; +} + +static izh::ACViolation makeMetalViolation() +{ + izh::ACViolation v; + v.net_name = "n1"; + v.pin_name = "A"; + v.inst_name = "u1"; + v.layer_order = 0; + v.type = izh::ACViolationType::kAntennaPar; + return v; +} + +static izh::RoutingContext makeContextWithNextLayer() +{ + static idb::IdbLayerRouting s_layer1; + s_layer1.set_direction("HORIZONTAL"); + static idb::IdbLayerRouting s_layer2; + s_layer2.set_direction("VERTICAL"); + static idb::IdbLayerCut s_cut_layer; + static idb::IdbVia s_via_dummy; + + izh::RCRoutingLayer rl1; + rl1.layer = &s_layer1; + rl1.order = 0; + rl1.pitch = 100; + rl1.width = 20; + rl1.min_area = 0; + rl1.spacing = 20; + rl1.horizontal = true; + + izh::RCRoutingLayer rl2; + rl2.layer = &s_layer2; + rl2.order = 2; + rl2.pitch = 100; + rl2.width = 20; + rl2.min_area = 0; + rl2.spacing = 20; + rl2.horizontal = false; + + izh::RCCutLayer cl; + cl.layer = &s_cut_layer; + cl.order = 1; + cl.spacing = 10; + cl.via = &s_via_dummy; + + return izh::RoutingContext::forTest({rl1, rl2}, {cl}); +} + +static izh::RoutingContext makeContextNoVia() +{ + static idb::IdbLayerRouting s_layer1; + s_layer1.set_direction("HORIZONTAL"); + static idb::IdbLayerRouting s_layer2; + s_layer2.set_direction("VERTICAL"); + + izh::RCRoutingLayer rl1; + rl1.layer = &s_layer1; + rl1.order = 0; + rl1.pitch = 100; + rl1.width = 20; + rl1.min_area = 0; + rl1.spacing = 20; + rl1.horizontal = true; + + izh::RCRoutingLayer rl2; + rl2.layer = &s_layer2; + rl2.order = 2; + rl2.pitch = 100; + rl2.width = 20; + rl2.min_area = 0; + rl2.spacing = 20; + rl2.horizontal = false; + + return izh::RoutingContext::forTest({rl1, rl2}, {}); +} + +static izh::RoutingContext makeContextTopLayer() +{ + static idb::IdbLayerRouting s_layer1; + s_layer1.set_direction("HORIZONTAL"); + + izh::RCRoutingLayer rl1; + rl1.layer = &s_layer1; + rl1.order = 1; + rl1.pitch = 100; + rl1.width = 20; + rl1.min_area = 0; + rl1.spacing = 20; + rl1.horizontal = true; + + return izh::RoutingContext::forTest({rl1}, {}); +} + +static izh::RoutingContext makeContextWithDiode() +{ + static idb::IdbLayerRouting s_layer1; + s_layer1.set_direction("HORIZONTAL"); + static idb::IdbLayerRouting s_layer2; + s_layer2.set_direction("VERTICAL"); + static idb::IdbLayerCut s_cut_layer; + static idb::IdbVia s_via_dummy; + static idb::IdbCellMaster s_diode_master; + s_diode_master.set_type(idb::CellMasterType::kCoreAntenaCell); + + izh::RCRoutingLayer rl1; + rl1.layer = &s_layer1; + rl1.order = 0; + rl1.pitch = 100; + rl1.width = 20; + rl1.min_area = 0; + rl1.spacing = 20; + rl1.horizontal = true; + + izh::RCRoutingLayer rl2; + rl2.layer = &s_layer2; + rl2.order = 2; + rl2.pitch = 100; + rl2.width = 20; + rl2.min_area = 0; + rl2.spacing = 20; + rl2.horizontal = false; + + izh::RCCutLayer cl; + cl.layer = &s_cut_layer; + cl.order = 1; + cl.spacing = 10; + cl.via = &s_via_dummy; + + return izh::RoutingContext::forTest({rl1, rl2}, {cl}, {&s_diode_master}); +} + +int main() +{ + const std::vector> pwl{{0.0, 0.0}, {2.0, 10.0}}; + izh::ACThresholdPick pick = izh::AntennaRuleEvaluator::pickThreshold(5.0, 8.0, pwl, 1.0, true); + if (!pick.available || !pick.is_diff) { + return 1; + } + if (!izh::Utility::equalDoubleByError(pick.threshold, 5.0, ZH_ERROR)) { + return 1; + } + + izh::ACThresholdPick plain = izh::AntennaRuleEvaluator::pickThreshold(4.0, -1.0, {}, 0.0, false); + if (!plain.available || plain.is_diff || !izh::Utility::equalDoubleByError(plain.threshold, 4.0, ZH_ERROR)) { + return 1; + } + + izh::ACThresholdPick zero_diff = izh::AntennaRuleEvaluator::pickThreshold(4.0, 8.0, pwl, 0.0, false); + if (!zero_diff.available || zero_diff.is_diff) { + return 1; + } + if (!izh::Utility::equalDoubleByError(zero_diff.threshold, 4.0, ZH_ERROR)) { + return 1; + } + + izh::ACViolation v; + v.net_name = "n1"; + v.pin_name = "A"; + v.inst_name = "u1"; + v.layer_order = 2; + v.type = izh::ACViolationType::kAntennaCutPar; + if (v.pin_name != "A" || !izh::acViolationIsCut(v.type)) { + return 1; + } + + izh::RoutingContext ctx; + izh::AntennaFixer fixer; + if (fixer.classify(v, ctx) != izh::AFFixKind::kDiode) { + return 1; + } + v.type = izh::ACViolationType::kAntennaDiffCutCar; + if (fixer.classify(v, ctx) != izh::AFFixKind::kDiode) { + return 1; + } + v.type = izh::ACViolationType::kAntennaPar; + v.layer_order = 100000; + if (fixer.classify(v, ctx) != izh::AFFixKind::kDiode) { + return 1; + } + v.layer_order = -1; + if (fixer.classify(v, ctx) != izh::AFFixKind::kDiode) { + return 1; + } + v.layer_order = 2; + if (fixer.classify(v, ctx) != izh::AFFixKind::kDiode) { + return 1; + } + + idb::IdbCellMaster master; + master.set_type(idb::CellMasterType::kCoreAntenaCell); + if (!master.is_antenna_cell()) { + return 1; + } + master.set_type(idb::CellMasterType::kCore); + if (master.is_antenna_cell()) { + return 1; + } + + if (std::string(izh::acViolationTypeToString(izh::ACViolationType::kAntennaPar)) != "PAR") { + return 1; + } + if (std::string(izh::acViolationTypeToString(izh::ACViolationType::kAntennaCutPar)) != "CutPAR") { + return 1; + } + if (izh::acViolationIsCut(izh::ACViolationType::kAntennaPar)) { + return 1; + } + + izh::AntennaResult result; + if (result.get_violation_num() != 0) { + return 1; + } + result.get_violation_list().push_back(v); + if (result.get_violation_num() != 1) { + return 1; + } + izh::AFIterStat stat; + stat.iter = 1; + stat.violation_num = 1; + result.get_iter_stat_list().push_back(stat); + if (result.get_iter_stat_list().size() != 1) { + return 1; + } + + idb::IdbNet net_a; + idb::IdbNet net_b; + izh::WireEnvIndex index; + index.insertWire(1, 0, 0, 100, 20, &net_a); + if (!index.hasOverlap(1, 10, 0, 30, 10, &net_b)) { + return 1; + } + if (index.hasOverlap(1, 10, 0, 30, 10, &net_a)) { + return 1; + } + if (index.hasOverlap(2, 10, 0, 30, 10, &net_b)) { + return 1; + } + if (index.hasViaOverlap(50, 10, 3, 5, &net_a)) { + return 1; + } + + izh::AntennaEngine::initInst(); + izh::AntennaEngine* inst = &ZHAE; + izh::AntennaEngine::initInst(); + if (inst != &ZHAE) { + return 1; + } + izh::AntennaEngine::destroyInst(); + + // ================================================================ + // Strategy selector tests + // ================================================================ + + // test_rule_selects_diode_when_diode_available + // CutPAR violation -> strategy is kDiode regardless of routing geometry + { + izh::AntennaFixer fixer2; + izh::ACViolation cut_v = makeCutViolation(); + izh::RoutingContext ctx_with_layers = makeContextWithNextLayer(); + izh::AFFixKind k1 = fixer2.classify(cut_v, ctx_with_layers); + if (k1 != izh::AFFixKind::kDiode) { + return 10; + } + bool h1 = fixer2.hopUpPermitted(cut_v, ctx_with_layers); + if (h1) { + return 11; + } + } + + // test_rule_selects_hopup_even_when_diode_available + // Metal PAR, next layer + via exist -> kBoth (hop-up preferred), not kDiode + { + izh::AntennaFixer fixer2; + izh::ACViolation metal_v = makeMetalViolation(); + izh::RoutingContext ctx_with_layers = makeContextWithNextLayer(); + izh::AFFixKind kind = fixer2.classify(metal_v, ctx_with_layers); + if (kind != izh::AFFixKind::kBoth) { + return 20; + } + if (!fixer2.hopUpPermitted(metal_v, ctx_with_layers)) { + return 21; + } + if (kind == izh::AFFixKind::kDiode) { + return 22; + } + } + + // test_no_diode_uses_valid_hopup_fallback + { + izh::AntennaFixer fixer2; + izh::ACViolation metal_v = makeMetalViolation(); + izh::RoutingContext ctx_no_diode = makeContextWithNextLayer(); + if (fixer2.classify(metal_v, ctx_no_diode) != izh::AFFixKind::kBoth) { + return 30; + } + if (!fixer2.hopUpPermitted(metal_v, ctx_no_diode)) { + return 31; + } + if (ctx_no_diode.get_diode_masters().size() != 0) { + return 32; + } + } + + // test_invalid_diode_does_not_override_hopup_selection + { + izh::AntennaFixer fixer2; + izh::RoutingContext ctx_with_diode = makeContextWithDiode(); + if (ctx_with_diode.get_diode_masters().size() != 1) { + return 40; + } + if (!ctx_with_diode.get_diode_masters().front()->is_antenna_cell()) { + return 41; + } + izh::ACViolation metal_v = makeMetalViolation(); + izh::AFFixKind kind = fixer2.classify(metal_v, ctx_with_diode); + if (kind != izh::AFFixKind::kBoth) { + return 42; + } + } + + // test_failed_diode_rolls_back_before_fallback + { + izh::AntennaFixer fixer2; + izh::ACViolation metal_v = makeMetalViolation(); + izh::RoutingContext ctx_no_via = makeContextNoVia(); + if (fixer2.classify(metal_v, ctx_no_via) != izh::AFFixKind::kDiode) { + return 50; + } + if (fixer2.hopUpPermitted(metal_v, ctx_no_via)) { + return 51; + } + } + + // test_unrepairable_strategy_reports_unresolved + { + izh::AntennaFixer fixer2; + izh::ACViolation metal_v = makeMetalViolation(); + izh::RoutingContext ctx_top = makeContextTopLayer(); + if (fixer2.classify(metal_v, ctx_top) != izh::AFFixKind::kDiode) { + return 60; + } + if (fixer2.hopUpPermitted(metal_v, ctx_top)) { + return 61; + } + } + + return 0; +} diff --git a/test/native_scenarios.py b/test/native_scenarios.py index 70b45b2163..9ed3708e78 100644 --- a/test/native_scenarios.py +++ b/test/native_scenarios.py @@ -56,6 +56,36 @@ def antenna(manifest: dict[str, Any]) -> dict[str, Path]: return {"report": report_file} +def antenna_fix(manifest: dict[str, Any]) -> dict[str, Path]: + _setup(manifest) + _read_design(manifest) + output_dir = Path(manifest["output_dir"]) + report_file = output_dir / "antenna_check.rpt" + _require(ecc_py.init_rt(manifest["config"]["route_ecc"]), "init_rt") + try: + _require( + ecc_py.run_ert( + manifest["config"]["route_ecc"], + { + "-stage": "edr", + "-resolve_congestion": "low", + "-enable_antenna_fix": "1", + "-antenna_report_dir": str(output_dir), + }, + ), + "run_ert", + ) + finally: + ecc_py.destroy_rt() + _require_file(report_file) + _require( + ecc_py.check_antenna("", str(output_dir)), + "check_antenna", + ) + _require_file(report_file) + return {"report": report_file} + + def def_round_trip(manifest: dict[str, Any]) -> dict[str, Path]: _setup(manifest) _read_design(manifest) @@ -344,6 +374,7 @@ def _require_file(path: Path) -> None: SCENARIOS = { "antenna": antenna, + "antenna_fix": antenna_fix, "combined_io": combined_io, "cts": cts, "def_round_trip": def_round_trip, diff --git a/test/operations/test_antenna_benchmark.py b/test/operations/test_antenna_benchmark.py new file mode 100644 index 0000000000..751e713f95 --- /dev/null +++ b/test/operations/test_antenna_benchmark.py @@ -0,0 +1,131 @@ +"""Antenna repair benchmark: violations, nets, and timing per iteration. + +Supports two modes: + - ``antenna_fix``: runs router with antenna fix, then re-checks (requires route config) + - ``antenna``: check-only, reports initial violation counts (no fix iterations) + +Requires: + - ECC_TOOLS_TEST_REPO_ROOT, ECC_TOOLS_TEST_PDK_ROOT, ECC_TOOLS_TEST_ARTIFACT_ROOT + +Run with:: + + pytest test/operations/test_antenna_benchmark.py -v --tb=short -s +""" + +from __future__ import annotations + +import os +import re +import sys +from pathlib import Path + +import pytest + +sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "test")) +from support import ScenarioResult, TestRoots, assert_nonempty_file, run_scenario + + +# --------------------------------------------------------------------------- +# Fixtures +# --------------------------------------------------------------------------- + +@pytest.fixture(scope="module") +def roots() -> TestRoots: + return TestRoots.from_environment() + + +@pytest.fixture(scope="module") +def antenna_result(roots: TestRoots) -> ScenarioResult: + """Run antenna check-only scenario (always succeeds if PDK is present).""" + result = run_scenario(roots, "antenna", timeout=120) + assert_nonempty_file(result.output_path("report")) + return result + + +# --------------------------------------------------------------------------- +# Parsers +# --------------------------------------------------------------------------- + +_ITER_LINE_RE = re.compile( + r"Iter (\d+): violations=(\d+) hop_up=(\d+)/(\d+) jog=(\d+)/(\d+) diode=(\d+)/(\d+)" +) +_REMAINING_RE = re.compile(r"Remaining violations: (\d+)") + + +def _parse_report(report_path: Path) -> dict: + """Parse antenna_check.rpt into structured data.""" + text = report_path.read_text(encoding="utf-8", errors="replace") + iterations = [] + for m in _ITER_LINE_RE.finditer(text): + iterations.append({ + "iter": int(m.group(1)), + "violations": int(m.group(2)), + "hop_up_applied": int(m.group(3)), + "hop_up_total": int(m.group(3)) + int(m.group(4)), + "jog_applied": int(m.group(5)), + "jog_total": int(m.group(5)) + int(m.group(6)), + "diode_applied": int(m.group(7)), + "diode_total": int(m.group(7)) + int(m.group(8)), + }) + remaining_m = _REMAINING_RE.search(text) + remaining = int(remaining_m.group(1)) if remaining_m else None + + # Also extract signal net count from report if present + signal_nets_m = re.search(r"signal nets:\s*(\d+)", text) + signal_nets = int(signal_nets_m.group(1)) if signal_nets_m else None + + return { + "iterations": iterations, + "remaining": remaining, + "signal_nets": signal_nets, + "raw_report": text, + } + + +# --------------------------------------------------------------------------- +# Tests +# --------------------------------------------------------------------------- + +class TestAntennaBenchmark: + """Parse and report antenna metrics from the antenna scenario.""" + + def test_report_has_content(self, antenna_result: ScenarioResult): + report = _parse_report(antenna_result.output_path("report")) + assert len(report["raw_report"]) > 0, "Report file is empty" + + def test_report_summary(self, antenna_result: ScenarioResult): + report = _parse_report(antenna_result.output_path("report")) + print("\n" + "=" * 70) + print("ANTENNA CHECK BENCHMARK — REPORT FILE") + print("=" * 70) + + if report["iterations"]: + for it in report["iterations"]: + print( + f" Iter {it['iter']:>2d}: violations={it['violations']:>4d} " + f"hop_up={it['hop_up_applied']}/{it['hop_up_total']} " + f"jog={it['jog_applied']}/{it['jog_total']} " + f"diode={it['diode_applied']}/{it['diode_total']}" + ) + print(f" Remaining violations: {report['remaining']}") + else: + # Check-only mode: just show the report + print(" (check-only mode — no fix iterations)") + violation_count_m = re.search(r"violation[s]?\s*[:=]\s*(\d+)", report["raw_report"], re.I) + if violation_count_m: + print(f" Total violations found: {violation_count_m.group(1)}") + + print("=" * 70) + + def test_log_has_antenna_output(self, antenna_result: ScenarioResult): + log_path = antenna_result.work_dir / "native.log" + if not log_path.is_file(): + pytest.skip("native.log not found") + log_text = log_path.read_text(encoding="utf-8", errors="replace") + assert "antenna" in log_text.lower(), "No antenna output in log" + + def test_violation_count_non_negative(self, antenna_result: ScenarioResult): + report = _parse_report(antenna_result.output_path("report")) + if report["iterations"]: + for it in report["iterations"]: + assert it["violations"] >= 0, f"Iter {it['iter']}: negative violations" diff --git a/test/support.py b/test/support.py index 051e4127e6..2402e4b6c7 100644 --- a/test/support.py +++ b/test/support.py @@ -10,6 +10,7 @@ INPUTS = { "antenna": ("route_in.def.gz", "route_in.v.gz"), + "antenna_fix": ("route_in.def.gz",), "combined_io": ("route_in.def.gz", "route_in.v.gz"), "cts": ("cts_in.def.gz", "cts_in.v.gz"), "def_round_trip": ("route_in.def.gz",),