diff --git a/src/database/data/design/IdbDesign.cpp b/src/database/data/design/IdbDesign.cpp index 50bc7a280..1bd8a8d8a 100644 --- a/src/database/data/design/IdbDesign.cpp +++ b/src/database/data/design/IdbDesign.cpp @@ -530,6 +530,36 @@ bool IdbDesign::disconnectPinFromNet(IdbPin* pin) return true; } +std::size_t IdbDesign::disconnectAllPinsFromNet(IdbNet* net) +{ + if (net == nullptr) { + return 0U; + } + + std::size_t disconnected_pin_count = 0U; + const auto disconnect_pin_refs = [&](IdbPins* pins) { + if (pins == nullptr) { + return; + } + for (auto* pin : pins->get_pin_list()) { + if (pin == nullptr || pin->get_net() != net) { + continue; + } + pin->remove_net(); + refreshPinNetName(pin); + ++disconnected_pin_count; + } + pins->clear_pin_refs(); + }; + + disconnect_pin_refs(net->get_io_pins()); + disconnect_pin_refs(net->get_instance_pin_list()); + if (net->get_instance_list() != nullptr) { + net->get_instance_list()->reset(false); + } + return disconnected_pin_count; +} + bool IdbDesign::connectPinToNet(IdbPin* pin, IdbNet* net) { if (pin == nullptr || net == nullptr) { @@ -595,14 +625,7 @@ bool IdbDesign::removeNetSafe(const std::string& net_name) return false; } - std::vector pin_list; - auto& io_pins = net->get_io_pins()->get_pin_list(); - auto& inst_pins = net->get_instance_pin_list()->get_pin_list(); - pin_list.insert(pin_list.end(), io_pins.begin(), io_pins.end()); - pin_list.insert(pin_list.end(), inst_pins.begin(), inst_pins.end()); - for (auto* pin : pin_list) { - disconnectPinFromNet(pin); - } + disconnectAllPinsFromNet(net); net->clear_wire_list(); return _net_list->remove_net_only(net_name); @@ -634,6 +657,7 @@ bool IdbDesign::mergeNetInto(const std::string& target_net_name, const std::stri auto& inst_pins = source_net->get_instance_pin_list()->get_pin_list(); pin_list.insert(pin_list.end(), io_pins.begin(), io_pins.end()); pin_list.insert(pin_list.end(), inst_pins.begin(), inst_pins.end()); + disconnectAllPinsFromNet(source_net); for (auto* pin : pin_list) { connectPinToNet(pin, target_net); } diff --git a/src/database/data/design/IdbDesign.h b/src/database/data/design/IdbDesign.h index 8d6b0a107..82154845e 100644 --- a/src/database/data/design/IdbDesign.h +++ b/src/database/data/design/IdbDesign.h @@ -32,6 +32,7 @@ //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #include +#include #include #include #include @@ -144,6 +145,7 @@ class IdbDesign bool connectIoPinToNet(const std::string& io_pin_name, const std::string& net_name); bool connectInstancePinToNet(const std::string& inst_name, const std::string& pin_name, const std::string& net_name); bool disconnectPinFromNet(IdbPin* pin); + std::size_t disconnectAllPinsFromNet(IdbNet* net); bool removeNetSafe(const std::string& net_name); bool renameNet(IdbNet* net, const std::string& new_name); bool mergeNetInto(const std::string& target_net_name, const std::string& source_net_name, bool move_wires = true); diff --git a/src/database/data/design/db_design/IdbInstance.cpp b/src/database/data/design/db_design/IdbInstance.cpp index 83a37e4e3..69ac62bd4 100644 --- a/src/database/data/design/db_design/IdbInstance.cpp +++ b/src/database/data/design/db_design/IdbInstance.cpp @@ -456,6 +456,14 @@ bool IdbInstanceList::contains(IdbInstance* instance) return false; } + const auto& name = instance->get_name(); + if (!name.empty()) { + const auto iter = _instance_map.find(name); + if (iter != _instance_map.end() && iter->second == instance) { + return true; + } + } + auto iter = std::find(_instance_list.begin(), _instance_list.end(), instance); return iter != _instance_list.end(); } diff --git a/src/database/data/design/db_design/IdbPins.cpp b/src/database/data/design/db_design/IdbPins.cpp index d21a73e64..6f27451d2 100644 --- a/src/database/data/design/db_design/IdbPins.cpp +++ b/src/database/data/design/db_design/IdbPins.cpp @@ -792,6 +792,12 @@ bool IdbPins::erase_pin_ref(IdbPin* pin_remove) return true; } +void IdbPins::clear_pin_refs() +{ + _pin_list.clear(); + _pin_ref_index.reset(); +} + bool IdbPins::delete_pin(IdbPin* pin_remove) { if (!erase_pin_ref(pin_remove)) { diff --git a/src/database/data/design/db_design/IdbPins.h b/src/database/data/design/db_design/IdbPins.h index 66e93cbd8..475f20a59 100644 --- a/src/database/data/design/db_design/IdbPins.h +++ b/src/database/data/design/db_design/IdbPins.h @@ -163,6 +163,7 @@ class IdbPins // Operate void remove_pin(IdbPin* pin_remove); bool erase_pin_ref(IdbPin* pin_remove); + void clear_pin_refs(); bool delete_pin(IdbPin* pin_remove); int32_t getIOPortWidth(); void checkPins(); diff --git a/src/interface/python/py_icts/py_icts.cpp b/src/interface/python/py_icts/py_icts.cpp index 989aa597d..6290f2127 100644 --- a/src/interface/python/py_icts/py_icts.cpp +++ b/src/interface/python/py_icts/py_icts.cpp @@ -31,6 +31,11 @@ bool CtsReport(const std::string& path) return CTS_API_INST.report(path).ok(); } +bool CtsDestroy() +{ + return CTS_API_INST.destroyCTS().ok(); +} + pybind11::dict CtsTimingFeature() { namespace py = pybind11; diff --git a/src/interface/python/py_icts/py_icts.h b/src/interface/python/py_icts/py_icts.h index f857d7522..3bd2fd069 100644 --- a/src/interface/python/py_icts/py_icts.h +++ b/src/interface/python/py_icts/py_icts.h @@ -22,5 +22,6 @@ namespace python_interface { bool CtsAutoRun(const std::string& cts_config, const std::string& cts_work_dir); bool CtsReport(const std::string& path); +bool CtsDestroy(); pybind11::dict CtsTimingFeature(); } // namespace python_interface diff --git a/src/interface/python/py_icts/py_register_icts.h b/src/interface/python/py_icts/py_register_icts.h index 73261d119..7e7732518 100644 --- a/src/interface/python/py_icts/py_register_icts.h +++ b/src/interface/python/py_icts/py_register_icts.h @@ -26,6 +26,7 @@ void register_icts(pybind11::module& m) { m.def("run_cts", CtsAutoRun, py::arg("cts_config"), py::arg("cts_work_dir")); m.def("cts_report", CtsReport, py::arg("path")); + m.def("destroy_cts", CtsDestroy); m.def("cts_timing_feature", CtsTimingFeature); } diff --git a/src/interface/tcl/tcl_icts/tcl_cts.cpp b/src/interface/tcl/tcl_icts/tcl_cts.cpp index 6e6c459c8..c3483edd1 100644 --- a/src/interface/tcl/tcl_icts/tcl_cts.cpp +++ b/src/interface/tcl/tcl_icts/tcl_cts.cpp @@ -98,4 +98,18 @@ unsigned CmdCTSReport::exec() return 0; } + +CmdCTSDestroy::CmdCTSDestroy(const char* cmd_name) : TclCmd(cmd_name) +{ +} + +unsigned CmdCTSDestroy::check() +{ + return 1U; +} + +unsigned CmdCTSDestroy::exec() +{ + return CTS_API_INST.destroyCTS().ok() ? 1U : 0U; +} } // namespace tcl diff --git a/src/interface/tcl/tcl_icts/tcl_cts.h b/src/interface/tcl/tcl_icts/tcl_cts.h index 2d6be5aef..6e53ae225 100644 --- a/src/interface/tcl/tcl_icts/tcl_cts.h +++ b/src/interface/tcl/tcl_icts/tcl_cts.h @@ -35,7 +35,8 @@ using ecc::TclStringOption; namespace tcl { -class CmdCTSAutoRun : public TclCmd { +class CmdCTSAutoRun : public TclCmd +{ public: explicit CmdCTSAutoRun(const char* cmd_name); ~CmdCTSAutoRun() override = default; @@ -48,7 +49,8 @@ class CmdCTSAutoRun : public TclCmd { // private data }; -class CmdCTSReport : public TclCmd { +class CmdCTSReport : public TclCmd +{ public: explicit CmdCTSReport(const char* cmd_name); ~CmdCTSReport() override = default; @@ -60,4 +62,14 @@ class CmdCTSReport : public TclCmd { // private function // private data }; + +class CmdCTSDestroy : public TclCmd +{ + public: + explicit CmdCTSDestroy(const char* cmd_name); + ~CmdCTSDestroy() override = default; + + unsigned check() override; + unsigned exec() override; +}; } // namespace tcl diff --git a/src/interface/tcl/tcl_icts/tcl_register_cts.h b/src/interface/tcl/tcl_icts/tcl_register_cts.h index 1986db58d..f06d812f4 100644 --- a/src/interface/tcl/tcl_icts/tcl_register_cts.h +++ b/src/interface/tcl/tcl_icts/tcl_register_cts.h @@ -31,10 +31,12 @@ using namespace ecc; namespace tcl { -int registerCmdCTS() { +int registerCmdCTS() +{ registerTclCmd(CmdCTSAutoRun, "run_cts"); registerTclCmd(CmdCTSReport, "cts_report"); registerTclCmd(CmdCTSConfig, "cts_config"); + registerTclCmd(CmdCTSDestroy, "destroy_cts"); return EXIT_SUCCESS; } diff --git a/src/operation/iCTS/interface/CTSAPI.cc b/src/operation/iCTS/interface/CTSAPI.cc index ff655b3a6..9b8c8eb8b 100644 --- a/src/operation/iCTS/interface/CTSAPI.cc +++ b/src/operation/iCTS/interface/CTSAPI.cc @@ -29,6 +29,7 @@ #include "LogTable.hh" #include "Logger.hh" #include "Monitor.hh" +#include "Utility.hh" #include "data_manager/DataManager.hh" #include "evaluation/Evaluation.hh" #include "evaluation/qor/QOREvaluation.hh" @@ -71,11 +72,7 @@ auto buildInputStatus(const DataManagerStatus& input_status) -> CTSStatus CTSAPI::CTSAPI() = default; -CTSAPI::~CTSAPI() -{ - DataManager::destroyInst(); - Logger::destroyInst(); -} +CTSAPI::~CTSAPI() = default; auto CTSAPI::setLastStatus(CTSStatus status) -> CTSStatus { @@ -152,18 +149,28 @@ auto CTSAPI::report(const std::string& save_dir) -> CTSStatus : CTSStatus{.code = CTSStatusCode::kReportError, .message = "CTS report generation failed.", .diagnostics = {}}); } -auto CTSAPI::resetAPI() -> void +auto CTSAPI::destroyCTS() -> CTSStatus { auto& api = getInst(); + Logger::initInst(); + CTSLOG.info(Loc::current(), "Starting CTS destruction..."); DataManager::destroyInst(); - Logger::destroyInst(); api._initialized = false; - api.setLastStatus(buildOkStatus("CTS API reset.")); + const auto memory_stats = Utility::releaseMemory(); + const auto status = api.setLastStatus(buildOkStatus("CTS destruction completed.")); + if (memory_stats.supported) { + CTSLOG.info(Loc::current(), "Completed CTS destruction; allocator release supported, RSS before=", Utility::formatFixed(memory_stats.rss_before_mb, 2), + " MiB, RSS after=", Utility::formatFixed(memory_stats.rss_after_mb, 2), " MiB."); + } else { + CTSLOG.info(Loc::current(), "Completed CTS destruction; allocator release unsupported."); + } + Logger::destroyInst(); + return status; } auto CTSAPI::init(const std::string& config_file, const std::string& work_dir) -> CTSStatus { - resetAPI(); + (void) destroyCTS(); auto& api = getInst(); Logger::initInst(); Monitor monitor; @@ -176,8 +183,7 @@ auto CTSAPI::init(const std::string& config_file, const std::string& work_dir) - auto status = buildInputStatus(input_status); if (!input_status.ok()) { CTSLOG.warn(Loc::current(), "CTS initialization failed: ", input_status.message, monitor.getStatsInfo()); - DataManager::destroyInst(); - Logger::destroyInst(); + (void) destroyCTS(); return api.setLastStatus(std::move(status)); } api._initialized = true; diff --git a/src/operation/iCTS/interface/CTSAPI.hh b/src/operation/iCTS/interface/CTSAPI.hh index db8a19663..19cc37948 100644 --- a/src/operation/iCTS/interface/CTSAPI.hh +++ b/src/operation/iCTS/interface/CTSAPI.hh @@ -63,7 +63,7 @@ class CTSAPI static auto report(const std::string& save_dir) -> CTSStatus; // Lifecycle API - static auto resetAPI() -> void; + static auto destroyCTS() -> CTSStatus; static auto init(const std::string& config_file, const std::string& work_dir = "") -> CTSStatus; static auto lastStatus() -> CTSStatus; diff --git a/src/operation/iCTS/source/data_manager/adapter/fast_sta/FastSTA.cc b/src/operation/iCTS/source/data_manager/adapter/fast_sta/FastSTA.cc index a767b8d8b..45a8d6b40 100644 --- a/src/operation/iCTS/source/data_manager/adapter/fast_sta/FastSTA.cc +++ b/src/operation/iCTS/source/data_manager/adapter/fast_sta/FastSTA.cc @@ -104,21 +104,31 @@ auto makeClockTreeTopology(const FastStaClockContext& context) -> FastStaClockTr topology.source_node_id = context.source_node_id; topology.parent_by_node.assign(context.nodes.size(), kInvalidFastStaNodeId); - std::unordered_map input_by_inst; - input_by_inst.reserve(context.nodes.size()); - for (FastStaNodeId node_id = 0U; node_id < context.nodes.size(); ++node_id) { - const auto& node = context.nodes.at(node_id); - if (node.kind == FastStaNodeKind::kBufferInput && !node.inst_name.empty()) { - input_by_inst[node.inst_name] = node_id; + const auto find_buffer_input = [&](const std::string& inst_name) -> FastStaNodeId { + if (const auto indexed = context.buffer_input_node_id_by_inst.find(inst_name); indexed != context.buffer_input_node_id_by_inst.end()) { + if (indexed->second < context.nodes.size()) { + const auto& node = context.nodes.at(indexed->second); + if (node.kind == FastStaNodeKind::kBufferInput && node.inst_name == inst_name) { + return indexed->second; + } + } + return kInvalidFastStaNodeId; } - } + for (FastStaNodeId node_id = 0U; node_id < context.nodes.size(); ++node_id) { + const auto& node = context.nodes.at(node_id); + if (node.kind == FastStaNodeKind::kBufferInput && node.inst_name == inst_name) { + return node_id; + } + } + return kInvalidFastStaNodeId; + }; for (FastStaNodeId node_id = 0U; node_id < context.nodes.size(); ++node_id) { const auto& node = context.nodes.at(node_id); if (node.kind == FastStaNodeKind::kBufferOutput) { - const auto input_iter = input_by_inst.find(node.inst_name); - if (input_iter != input_by_inst.end()) { - topology.parent_by_node.at(node_id) = input_iter->second; + const auto input_node_id = find_buffer_input(node.inst_name); + if (input_node_id != kInvalidFastStaNodeId) { + topology.parent_by_node.at(node_id) = input_node_id; } continue; } @@ -193,10 +203,12 @@ auto FastSTA::buildClockContext(const FastStaClockBuildInput& input) -> FastStaC auto FastSTA::eraseClockContext(FastStaClockId clock_id) -> bool { - if (clock_id >= _contexts->clock_context_valid.size() || !_contexts->clock_context_valid.at(clock_id)) { + if (clock_id >= _contexts->clock_contexts.size() || clock_id >= _contexts->clock_context_valid.size() || !_contexts->clock_context_valid.at(clock_id) + || _contexts->clock_contexts.at(clock_id) == nullptr) { return false; } _contexts->clock_context_valid.at(clock_id) = false; + _contexts->clock_contexts.at(clock_id).reset(); return true; } @@ -223,17 +235,19 @@ auto FastSTA::buildCharContext(const FastStaCharTopologySpec& spec) -> FastStaCh auto FastSTA::eraseCharContext(FastStaCharContextId char_context_id) -> bool { - if (char_context_id >= _contexts->char_context_valid.size() || !_contexts->char_context_valid.at(char_context_id)) { + if (char_context_id >= _contexts->char_contexts.size() || char_context_id >= _contexts->char_context_valid.size() + || !_contexts->char_context_valid.at(char_context_id) || _contexts->char_contexts.at(char_context_id) == nullptr) { return false; } _contexts->char_context_valid.at(char_context_id) = false; + _contexts->char_contexts.at(char_context_id).reset(); return true; } auto FastSTA::setCharLoad(FastStaCharContextId char_context_id, double effective_load_pf) -> bool { if (char_context_id >= _contexts->char_contexts.size() || char_context_id >= _contexts->char_context_valid.size() - || !_contexts->char_context_valid.at(char_context_id)) { + || !_contexts->char_context_valid.at(char_context_id) || _contexts->char_contexts.at(char_context_id) == nullptr) { CTSLOG.warn(Loc::current(), "FastSTA: characterization load update skipped because char context id is invalid."); return false; } @@ -243,7 +257,7 @@ auto FastSTA::setCharLoad(FastStaCharContextId char_context_id, double effective auto FastSTA::runCharSample(FastStaCharContextId char_context_id, double input_slew_ns) -> FastStaCharSampleResult { if (char_context_id >= _contexts->char_contexts.size() || char_context_id >= _contexts->char_context_valid.size() - || !_contexts->char_context_valid.at(char_context_id)) { + || !_contexts->char_context_valid.at(char_context_id) || _contexts->char_contexts.at(char_context_id) == nullptr) { CTSLOG.warn(Loc::current(), "FastSTA: characterization sample skipped because char context id is invalid."); return {}; } @@ -276,12 +290,14 @@ auto FastSTA::changeBufferMastersTimingOnly(FastStaClockId clock_id, const std:: if (changes.empty()) { return context->timing_valid; } - if (!FastStaIncremental::changeBufferMasters(*context, changes)) { + const auto dirty_region = FastStaIncremental::changeBufferMastersIncremental(*context, changes); + if (!dirty_region.has_value() || !FastStaTiming::updateRegion(*context, *dirty_region)) { + context->timing_valid = false; + context->power_valid = false; return false; } - const bool timing_updated = FastStaTiming::update(*context); context->power_valid = false; - return timing_updated; + return context->timing_valid; } auto FastSTA::updateTiming(FastStaClockId clock_id) -> bool @@ -444,7 +460,8 @@ auto FastSTA::queryPower(FastStaClockId clock_id) const -> std::optional const FastStaClockContext* { - if (clock_id >= _contexts->clock_contexts.size() || clock_id >= _contexts->clock_context_valid.size() || !_contexts->clock_context_valid.at(clock_id)) { + if (clock_id >= _contexts->clock_contexts.size() || clock_id >= _contexts->clock_context_valid.size() || !_contexts->clock_context_valid.at(clock_id) + || _contexts->clock_contexts.at(clock_id) == nullptr) { return nullptr; } return _contexts->clock_contexts.at(clock_id).get(); @@ -452,7 +469,8 @@ auto FastSTA::queryClockContext(FastStaClockId clock_id) const -> const FastStaC auto FastSTA::mutableClockContext(FastStaClockId clock_id) -> FastStaClockContext* { - if (clock_id >= _contexts->clock_contexts.size() || clock_id >= _contexts->clock_context_valid.size() || !_contexts->clock_context_valid.at(clock_id)) { + if (clock_id >= _contexts->clock_contexts.size() || clock_id >= _contexts->clock_context_valid.size() || !_contexts->clock_context_valid.at(clock_id) + || _contexts->clock_contexts.at(clock_id) == nullptr) { return nullptr; } return _contexts->clock_contexts.at(clock_id).get(); diff --git a/src/operation/iCTS/source/data_manager/adapter/fast_sta/clock_sizing/FastSTAIncremental.cc b/src/operation/iCTS/source/data_manager/adapter/fast_sta/clock_sizing/FastSTAIncremental.cc index d7ca9b4ea..c34005fda 100644 --- a/src/operation/iCTS/source/data_manager/adapter/fast_sta/clock_sizing/FastSTAIncremental.cc +++ b/src/operation/iCTS/source/data_manager/adapter/fast_sta/clock_sizing/FastSTAIncremental.cc @@ -23,10 +23,11 @@ #include "FastSTAIncremental.hh" +#include +#include #include #include -#include -#include +#include #include #include "FastSTAClockState.hh" @@ -50,6 +51,15 @@ auto normalizeBufferInputNodeId(const FastStaClockContext& context, FastStaNodeI if (node.kind != FastStaNodeKind::kBufferOutput || node.inst_name.empty()) { return kInvalidFastStaNodeId; } + if (const auto indexed = context.buffer_input_node_id_by_inst.find(node.inst_name); indexed != context.buffer_input_node_id_by_inst.end()) { + if (indexed->second < context.nodes.size()) { + const auto& input_node = context.nodes.at(indexed->second); + if (input_node.kind == FastStaNodeKind::kBufferInput && input_node.inst_name == node.inst_name) { + return indexed->second; + } + } + return kInvalidFastStaNodeId; + } for (FastStaNodeId candidate_id = 0U; candidate_id < context.nodes.size(); ++candidate_id) { const auto& candidate = context.nodes.at(candidate_id); if (candidate.kind == FastStaNodeKind::kBufferInput && candidate.inst_name == node.inst_name) { @@ -59,27 +69,54 @@ auto normalizeBufferInputNodeId(const FastStaClockContext& context, FastStaNodeI return kInvalidFastStaNodeId; } -auto markReachableFromNode(const FastStaClockContext& context, FastStaNodeId node_id, FastStaDirtyRegion& dirty_region, - std::unordered_set& node_seen, std::unordered_set& net_seen) -> void +auto normalizeBufferOutputNodeId(const FastStaClockContext& context, FastStaNodeId node_id) -> FastStaNodeId +{ + if (node_id >= context.nodes.size()) { + return kInvalidFastStaNodeId; + } + const auto& node = context.nodes.at(node_id); + if (node.kind == FastStaNodeKind::kBufferOutput) { + return node_id; + } + if (node.kind != FastStaNodeKind::kBufferInput || node.inst_name.empty()) { + return kInvalidFastStaNodeId; + } + if (const auto indexed = context.buffer_output_node_id_by_inst.find(node.inst_name); indexed != context.buffer_output_node_id_by_inst.end()) { + if (indexed->second < context.nodes.size()) { + const auto& output_node = context.nodes.at(indexed->second); + if (output_node.kind == FastStaNodeKind::kBufferOutput && output_node.inst_name == node.inst_name) { + return indexed->second; + } + } + return kInvalidFastStaNodeId; + } + for (FastStaNodeId candidate_id = 0U; candidate_id < context.nodes.size(); ++candidate_id) { + const auto& candidate = context.nodes.at(candidate_id); + if (candidate.kind == FastStaNodeKind::kBufferOutput && candidate.inst_name == node.inst_name) { + return candidate_id; + } + } + return kInvalidFastStaNodeId; +} + +auto markReachableFromNode(const FastStaClockContext& context, FastStaNodeId node_id, FastStaDirtyRegion& dirty_region, std::vector& node_seen, + std::vector& net_seen) -> void { std::vector pending_nodes{node_id}; while (!pending_nodes.empty()) { const auto current_node_id = pending_nodes.back(); pending_nodes.pop_back(); - if (current_node_id >= context.nodes.size() || node_seen.contains(current_node_id)) { + if (current_node_id >= context.nodes.size() || node_seen.at(current_node_id) != 0U) { continue; } - node_seen.insert(current_node_id); + node_seen.at(current_node_id) = 1U; dirty_region.node_ids.push_back(current_node_id); const auto& node = context.nodes.at(current_node_id); if (node.kind == FastStaNodeKind::kBufferInput) { - for (FastStaNodeId output_id = 0U; output_id < context.nodes.size(); ++output_id) { - const auto& output_node = context.nodes.at(output_id); - if (output_node.kind == FastStaNodeKind::kBufferOutput && output_node.inst_name == node.inst_name) { - pending_nodes.push_back(output_id); - break; - } + const auto output_id = normalizeBufferOutputNodeId(context, current_node_id); + if (output_id != kInvalidFastStaNodeId) { + pending_nodes.push_back(output_id); } continue; } @@ -88,8 +125,8 @@ auto markReachableFromNode(const FastStaClockContext& context, FastStaNodeId nod if (net_id >= context.nets.size()) { continue; } - if (!net_seen.contains(net_id)) { - net_seen.insert(net_id); + if (net_seen.at(net_id) == 0U) { + net_seen.at(net_id) = 1U; dirty_region.net_ids.push_back(net_id); } for (const auto load_node_id : context.nets.at(net_id).load_node_ids) { @@ -99,11 +136,10 @@ auto markReachableFromNode(const FastStaClockContext& context, FastStaNodeId nod } } -auto collectDirtyRegion(const FastStaClockContext& context, FastStaNodeId changed_input_node_id) -> FastStaDirtyRegion +auto dirtyRegionStartNode(const FastStaClockContext& context, FastStaNodeId changed_input_node_id) -> FastStaNodeId { - FastStaDirtyRegion dirty_region; if (changed_input_node_id >= context.nodes.size()) { - return dirty_region; + return kInvalidFastStaNodeId; } auto start_node_id = changed_input_node_id; @@ -119,15 +155,83 @@ auto collectDirtyRegion(const FastStaClockContext& context, FastStaNodeId change start_node_id = incoming_driver_id; } } + return start_node_id; +} + +auto parentNodeId(const FastStaClockContext& context, FastStaNodeId node_id) -> FastStaNodeId +{ + if (node_id >= context.nodes.size()) { + return kInvalidFastStaNodeId; + } + const auto& node = context.nodes.at(node_id); + if (node.kind == FastStaNodeKind::kBufferOutput) { + return normalizeBufferInputNodeId(context, node_id); + } + if (node.incoming_net_id < context.nets.size()) { + const auto parent_id = context.nets.at(node.incoming_net_id).driver_node_id; + return parent_id < context.nodes.size() ? parent_id : kInvalidFastStaNodeId; + } + return kInvalidFastStaNodeId; +} + +auto lowestCommonAncestor(const FastStaClockContext& context, FastStaNodeId lhs, FastStaNodeId rhs) -> FastStaNodeId +{ + if (lhs >= context.nodes.size() || rhs >= context.nodes.size()) { + return kInvalidFastStaNodeId; + } + std::vector lhs_ancestors(context.nodes.size(), 0U); + auto current = lhs; + for (std::size_t step = 0U; current < context.nodes.size() && step <= context.nodes.size(); ++step) { + if (lhs_ancestors.at(current) != 0U) { + return kInvalidFastStaNodeId; + } + lhs_ancestors.at(current) = 1U; + current = parentNodeId(context, current); + } + + std::vector rhs_seen(context.nodes.size(), 0U); + current = rhs; + for (std::size_t step = 0U; current < context.nodes.size() && step <= context.nodes.size(); ++step) { + if (lhs_ancestors.at(current) != 0U) { + return current; + } + if (rhs_seen.at(current) != 0U) { + return kInvalidFastStaNodeId; + } + rhs_seen.at(current) = 1U; + current = parentNodeId(context, current); + } + return kInvalidFastStaNodeId; +} + +auto collectDirtyRegionFromStart(const FastStaClockContext& context, FastStaNodeId start_node_id) -> FastStaDirtyRegion +{ + FastStaDirtyRegion dirty_region; + if (start_node_id >= context.nodes.size()) { + return dirty_region; + } + if (context.nodes.at(start_node_id).kind == FastStaNodeKind::kBufferOutput) { + start_node_id = normalizeBufferInputNodeId(context, start_node_id); + if (start_node_id == kInvalidFastStaNodeId) { + return dirty_region; + } + } dirty_region.valid = true; dirty_region.start_node_id = start_node_id; - std::unordered_set node_seen; - std::unordered_set net_seen; + std::vector node_seen(context.nodes.size(), 0U); + std::vector net_seen(context.nets.size(), 0U); markReachableFromNode(context, start_node_id, dirty_region, node_seen, net_seen); return dirty_region; } +auto collectDirtyRegion(const FastStaClockContext& context, FastStaNodeId changed_input_node_id) -> FastStaDirtyRegion +{ + return collectDirtyRegionFromStart(context, dirtyRegionStartNode(context, changed_input_node_id)); +} + +auto prepareBufferMasterChanges(FastStaClockContext& context, const std::vector& changes) -> bool; + auto applyBufferMasterChange(FastStaClockContext& context, FastStaNodeId node_id, std::string_view cell_master, bool invalidate_context) -> FastStaNodeId { if (node_id >= context.nodes.size()) { @@ -156,16 +260,17 @@ auto applyBufferMasterChange(FastStaClockContext& context, FastStaNodeId node_id } context.liberty_cell_by_master.emplace(target_master, *liberty_cell); } - const auto inst_name = context.nodes.at(input_node_id).inst_name; - for (auto& candidate : context.nodes) { - if (candidate.inst_name == inst_name && (candidate.kind == FastStaNodeKind::kBufferInput || candidate.kind == FastStaNodeKind::kBufferOutput)) { - candidate.cell_master = target_master; - if (candidate.kind == FastStaNodeKind::kBufferInput) { - candidate.input_cap_pf = context.liberty_cell_by_master.at(target_master).input_cap_pf; - candidate.max_slew_ns = context.liberty_cell_by_master.at(target_master).input_slew_limit_ns; - } - } + const auto output_node_id = normalizeBufferOutputNodeId(context, input_node_id); + if (output_node_id == kInvalidFastStaNodeId) { + CTSLOG.warn(Loc::current(), "FastStaIncremental: buffer master change skipped because buffer output node is unavailable for \"", node.name, "\"."); + return kInvalidFastStaNodeId; } + auto& input_node = context.nodes.at(input_node_id); + auto& output_node = context.nodes.at(output_node_id); + input_node.cell_master = target_master; + input_node.input_cap_pf = context.liberty_cell_by_master.at(target_master).input_cap_pf; + input_node.max_slew_ns = context.liberty_cell_by_master.at(target_master).input_slew_limit_ns; + output_node.cell_master = target_master; if (invalidate_context) { context.timing_valid = false; context.power_valid = false; @@ -188,6 +293,10 @@ auto validateBufferMasterChange(const FastStaClockContext& context, const FastSt CTSLOG.warn(Loc::current(), "FastStaIncremental: buffer master change skipped because buffer input node is unavailable for \"", node.name, "\"."); return false; } + if (normalizeBufferOutputNodeId(context, change.node_id) == kInvalidFastStaNodeId) { + CTSLOG.warn(Loc::current(), "FastStaIncremental: buffer master change skipped because buffer output node is unavailable for \"", node.name, "\"."); + return false; + } if (change.cell_master.empty()) { CTSLOG.warn(Loc::current(), "FastStaIncremental: buffer master change skipped because target master is empty for \"", node.name, "\"."); return false; @@ -195,6 +304,35 @@ auto validateBufferMasterChange(const FastStaClockContext& context, const FastSt return true; } +auto prepareBufferMasterChanges(FastStaClockContext& context, const std::vector& changes) -> bool +{ + if (!FastStaIncremental::validateBufferMasterChanges(context, changes)) { + return false; + } + std::vector> missing_cells; + for (const auto& change : changes) { + if (context.liberty_cell_by_master.contains(change.cell_master)) { + continue; + } + if (context.wrapper == nullptr) { + CTSLOG.error(Loc::current(), "FastStaIncremental: Wrapper is unavailable."); + } + if (std::ranges::any_of(missing_cells, [&](const auto& cell) -> bool { return cell.first == change.cell_master; })) { + continue; + } + const auto liberty_cell = FastStaLiberty::extractBufferCell(*context.wrapper, change.cell_master); + if (!liberty_cell.has_value()) { + CTSLOG.warn(Loc::current(), "FastStaIncremental: required Liberty data is unavailable for target master \"", change.cell_master, "\"."); + return false; + } + missing_cells.emplace_back(change.cell_master, *liberty_cell); + } + for (auto& [cell_master, liberty_cell] : missing_cells) { + context.liberty_cell_by_master.emplace(std::move(cell_master), std::move(liberty_cell)); + } + return true; +} + } // namespace auto FastStaIncremental::changeBufferMaster(FastStaClockContext& context, FastStaNodeId node_id, std::string_view cell_master) -> bool @@ -202,13 +340,21 @@ auto FastStaIncremental::changeBufferMaster(FastStaClockContext& context, FastSt return applyBufferMasterChange(context, node_id, cell_master, true) != kInvalidFastStaNodeId; } -auto FastStaIncremental::changeBufferMasters(FastStaClockContext& context, const std::vector& changes) -> bool +auto FastStaIncremental::validateBufferMasterChanges(const FastStaClockContext& context, const std::vector& changes) -> bool { for (const auto& change : changes) { if (!validateBufferMasterChange(context, change)) { return false; } } + return true; +} + +auto FastStaIncremental::changeBufferMasters(FastStaClockContext& context, const std::vector& changes) -> bool +{ + if (!prepareBufferMasterChanges(context, changes)) { + return false; + } for (const auto& change : changes) { if (applyBufferMasterChange(context, change.node_id, change.cell_master, false) == kInvalidFastStaNodeId) { context.timing_valid = false; @@ -221,6 +367,38 @@ auto FastStaIncremental::changeBufferMasters(FastStaClockContext& context, const return true; } +auto FastStaIncremental::changeBufferMastersIncremental(FastStaClockContext& context, const std::vector& changes) + -> std::optional +{ + if (changes.empty() || !prepareBufferMasterChanges(context, changes)) { + return std::nullopt; + } + auto common_start_node_id = kInvalidFastStaNodeId; + for (const auto& change : changes) { + const auto input_node_id = normalizeBufferInputNodeId(context, change.node_id); + const auto start_node_id = dirtyRegionStartNode(context, input_node_id); + if (start_node_id == kInvalidFastStaNodeId) { + return std::nullopt; + } + common_start_node_id = common_start_node_id == kInvalidFastStaNodeId ? start_node_id : lowestCommonAncestor(context, common_start_node_id, start_node_id); + if (common_start_node_id == kInvalidFastStaNodeId) { + return std::nullopt; + } + } + auto dirty_region = collectDirtyRegionFromStart(context, common_start_node_id); + if (!dirty_region.valid) { + return std::nullopt; + } + for (const auto& change : changes) { + if (applyBufferMasterChange(context, change.node_id, change.cell_master, false) == kInvalidFastStaNodeId) { + context.timing_valid = false; + context.power_valid = false; + return std::nullopt; + } + } + return dirty_region; +} + auto FastStaIncremental::changeBufferMasterIncremental(FastStaClockContext& context, FastStaNodeId node_id, std::string_view cell_master) -> std::optional { diff --git a/src/operation/iCTS/source/data_manager/adapter/fast_sta/clock_sizing/FastSTAIncremental.hh b/src/operation/iCTS/source/data_manager/adapter/fast_sta/clock_sizing/FastSTAIncremental.hh index 00afa64ca..e787ad488 100644 --- a/src/operation/iCTS/source/data_manager/adapter/fast_sta/clock_sizing/FastSTAIncremental.hh +++ b/src/operation/iCTS/source/data_manager/adapter/fast_sta/clock_sizing/FastSTAIncremental.hh @@ -40,7 +40,10 @@ class FastStaIncremental FastStaIncremental() = delete; static auto changeBufferMaster(FastStaClockContext& context, FastStaNodeId node_id, std::string_view cell_master) -> bool; + static auto validateBufferMasterChanges(const FastStaClockContext& context, const std::vector& changes) -> bool; static auto changeBufferMasters(FastStaClockContext& context, const std::vector& changes) -> bool; + static auto changeBufferMastersIncremental(FastStaClockContext& context, const std::vector& changes) + -> std::optional; static auto changeBufferMasterIncremental(FastStaClockContext& context, FastStaNodeId node_id, std::string_view cell_master) -> std::optional; }; diff --git a/src/operation/iCTS/source/data_manager/adapter/fast_sta/clock_state/FastSTAClockState.hh b/src/operation/iCTS/source/data_manager/adapter/fast_sta/clock_state/FastSTAClockState.hh index a86f85140..634856c0e 100644 --- a/src/operation/iCTS/source/data_manager/adapter/fast_sta/clock_state/FastSTAClockState.hh +++ b/src/operation/iCTS/source/data_manager/adapter/fast_sta/clock_state/FastSTAClockState.hh @@ -91,6 +91,8 @@ struct FastStaClockContext std::vector nodes; std::vector nets; std::unordered_map node_id_by_name; + std::unordered_map buffer_input_node_id_by_inst; + std::unordered_map buffer_output_node_id_by_inst; std::unordered_map, FastStaNodeId, FastStaPointKeyHash> node_id_by_location; std::unordered_map net_id_by_name; std::unordered_map liberty_cell_by_master; diff --git a/src/operation/iCTS/source/data_manager/adapter/fast_sta/clock_tree/FastSTAClockTree.cc b/src/operation/iCTS/source/data_manager/adapter/fast_sta/clock_tree/FastSTAClockTree.cc index ca64c7c6c..33facd8ff 100644 --- a/src/operation/iCTS/source/data_manager/adapter/fast_sta/clock_tree/FastSTAClockTree.cc +++ b/src/operation/iCTS/source/data_manager/adapter/fast_sta/clock_tree/FastSTAClockTree.cc @@ -81,16 +81,25 @@ auto appendPinNode(const Clock& clock, Pin* pin, FastStaClockContext& context) - const auto node_id = context.nodes.size(); context.node_id_by_name[node_name] = node_id; context.node_id_by_location.emplace(makeLocationKey(location), node_id); + const auto node_kind = makeNodeKind(clock, pin); + const auto inst_name = inst != nullptr ? inst->get_name() : std::string{}; context.nodes.push_back(FastStaNode{ - .kind = makeNodeKind(clock, pin), + .kind = node_kind, .name = node_name, - .inst_name = inst != nullptr ? inst->get_name() : std::string{}, + .inst_name = inst_name, .pin_name = pin->get_name(), .cell_master = inst != nullptr ? inst->get_cell_master() : std::string{}, .location = location, .output_net_ids = {}, .timing = {}, }); + if (!inst_name.empty()) { + if (node_kind == FastStaNodeKind::kBufferInput) { + context.buffer_input_node_id_by_inst[inst_name] = node_id; + } else if (node_kind == FastStaNodeKind::kBufferOutput) { + context.buffer_output_node_id_by_inst[inst_name] = node_id; + } + } return node_id; } diff --git a/src/operation/iCTS/source/data_manager/adapter/fast_sta/power/FastSTAPower.cc b/src/operation/iCTS/source/data_manager/adapter/fast_sta/power/FastSTAPower.cc index 8869bd7f1..a9a08d94f 100644 --- a/src/operation/iCTS/source/data_manager/adapter/fast_sta/power/FastSTAPower.cc +++ b/src/operation/iCTS/source/data_manager/adapter/fast_sta/power/FastSTAPower.cc @@ -73,6 +73,15 @@ auto resolveVoltage(const FastStaClockContext& context) -> std::optional auto findBufferInputNode(const FastStaClockContext& context, const FastStaNode& output_node) -> FastStaNodeId { + if (const auto indexed = context.buffer_input_node_id_by_inst.find(output_node.inst_name); indexed != context.buffer_input_node_id_by_inst.end()) { + if (indexed->second < context.nodes.size()) { + const auto& input_node = context.nodes.at(indexed->second); + if (input_node.kind == FastStaNodeKind::kBufferInput && input_node.inst_name == output_node.inst_name) { + return indexed->second; + } + } + return kInvalidFastStaNodeId; + } for (FastStaNodeId node_id = 0U; node_id < context.nodes.size(); ++node_id) { const auto& node = context.nodes.at(node_id); if (node.kind == FastStaNodeKind::kBufferInput && node.inst_name == output_node.inst_name) { diff --git a/src/operation/iCTS/source/data_manager/adapter/fast_sta/timing/FastSTATiming.cc b/src/operation/iCTS/source/data_manager/adapter/fast_sta/timing/FastSTATiming.cc index f4a47dd69..b3bc7768f 100644 --- a/src/operation/iCTS/source/data_manager/adapter/fast_sta/timing/FastSTATiming.cc +++ b/src/operation/iCTS/source/data_manager/adapter/fast_sta/timing/FastSTATiming.cc @@ -49,6 +49,15 @@ auto findBufferInputNode(const FastStaClockContext& context, const FastStaNode& if (output_node.inst_name.empty()) { return kInvalidFastStaNodeId; } + if (const auto indexed = context.buffer_input_node_id_by_inst.find(output_node.inst_name); indexed != context.buffer_input_node_id_by_inst.end()) { + if (indexed->second < context.nodes.size()) { + const auto& input_node = context.nodes.at(indexed->second); + if (input_node.kind == FastStaNodeKind::kBufferInput && input_node.inst_name == output_node.inst_name) { + return indexed->second; + } + } + return kInvalidFastStaNodeId; + } for (FastStaNodeId node_id = 0U; node_id < context.nodes.size(); ++node_id) { const auto& node = context.nodes.at(node_id); if (node.kind == FastStaNodeKind::kBufferInput && node.inst_name == output_node.inst_name) { @@ -207,7 +216,23 @@ auto hasCompleteSinkTiming(const FastStaClockContext& context) -> bool auto propagateReadyQueue(FastStaClockContext& context, std::queue& ready_nodes) -> void { - const auto output_by_inst = mapBufferOutputByInst(context); + std::unordered_map fallback_output_by_inst; + const auto find_buffer_output = [&](const FastStaNode& input_node) -> FastStaNodeId { + if (const auto indexed = context.buffer_output_node_id_by_inst.find(input_node.inst_name); indexed != context.buffer_output_node_id_by_inst.end()) { + if (indexed->second < context.nodes.size()) { + const auto& output_node = context.nodes.at(indexed->second); + if (output_node.kind == FastStaNodeKind::kBufferOutput && output_node.inst_name == input_node.inst_name) { + return indexed->second; + } + } + return kInvalidFastStaNodeId; + } + if (fallback_output_by_inst.empty()) { + fallback_output_by_inst = mapBufferOutputByInst(context); + } + const auto fallback = fallback_output_by_inst.find(input_node.inst_name); + return fallback == fallback_output_by_inst.end() ? kInvalidFastStaNodeId : fallback->second; + }; std::size_t visited_steps = 0U; const auto max_steps = std::max(1U, context.nodes.size() + context.nets.size() + 1U) * 4U; while (!ready_nodes.empty() && visited_steps < max_steps) { @@ -219,16 +244,16 @@ auto propagateReadyQueue(FastStaClockContext& context, std::queue } auto& node = context.nodes.at(node_id); if (node.kind == FastStaNodeKind::kBufferInput) { - const auto output_iter = output_by_inst.find(node.inst_name); - if (output_iter != output_by_inst.end() && output_iter->second < context.nodes.size()) { - auto& output_node = context.nodes.at(output_iter->second); + const auto output_node_id = find_buffer_output(node); + if (output_node_id < context.nodes.size()) { + auto& output_node = context.nodes.at(output_node_id); if (!output_node.output_net_ids.empty() && output_node.output_net_ids.front() < context.nets.size()) { - propagateBufferOutput(context, output_iter->second, context.nets.at(output_node.output_net_ids.front())); + propagateBufferOutput(context, output_node_id, context.nets.at(output_node.output_net_ids.front())); } else { output_node.timing = node.timing; } if (output_node.timing.valid) { - ready_nodes.push(output_iter->second); + ready_nodes.push(output_node_id); } } continue; diff --git a/src/operation/iCTS/source/data_manager/io/WrapperClockWriter.cc b/src/operation/iCTS/source/data_manager/io/WrapperClockWriter.cc index 7f25d07ca..954421e2c 100644 --- a/src/operation/iCTS/source/data_manager/io/WrapperClockWriter.cc +++ b/src/operation/iCTS/source/data_manager/io/WrapperClockWriter.cc @@ -83,18 +83,7 @@ auto DetachIdbNetPins(idb::IdbDesign* idb_design, idb::IdbNet* idb_net) -> void if (idb_design == nullptr || idb_net == nullptr) { return; } - std::vector pins; - if (idb_net->get_io_pins() != nullptr) { - const auto& io_pins = idb_net->get_io_pins()->get_pin_list(); - pins.insert(pins.end(), io_pins.begin(), io_pins.end()); - } - if (idb_net->get_instance_pin_list() != nullptr) { - const auto& inst_pins = idb_net->get_instance_pin_list()->get_pin_list(); - pins.insert(pins.end(), inst_pins.begin(), inst_pins.end()); - } - for (auto* pin : pins) { - idb_design->disconnectPinFromNet(pin); - } + idb_design->disconnectAllPinsFromNet(idb_net); } auto FindIdbInstPinByCtsPinName(idb::IdbInstance* idb_inst, const std::string& pin_name) -> idb::IdbPin* diff --git a/src/operation/iCTS/source/toolkit/utility/Utility.cc b/src/operation/iCTS/source/toolkit/utility/Utility.cc index 05b7d210b..48a10682e 100644 --- a/src/operation/iCTS/source/toolkit/utility/Utility.cc +++ b/src/operation/iCTS/source/toolkit/utility/Utility.cc @@ -23,6 +23,17 @@ #include "Utility.hh" +#include +#include + +#ifdef __GLIBC__ +#include +#endif + +#ifdef __linux__ +#include +#endif + namespace icts { auto Utility::getElapsedSeconds(std::chrono::steady_clock::time_point start_time) -> double @@ -30,4 +41,40 @@ auto Utility::getElapsedSeconds(std::chrono::steady_clock::time_point start_time return std::chrono::duration(std::chrono::steady_clock::now() - start_time).count(); } +auto Utility::currentRssMb() -> std::optional +{ +#ifdef __linux__ + std::ifstream statm("/proc/self/statm"); + std::size_t total_pages = 0U; + std::size_t resident_pages = 0U; + if (!(statm >> total_pages >> resident_pages)) { + return std::nullopt; + } + const auto page_size_bytes = ::sysconf(_SC_PAGESIZE); + if (page_size_bytes <= 0) { + return std::nullopt; + } + constexpr double bytes_per_mebibyte = 1024.0 * 1024.0; + return static_cast(resident_pages) * static_cast(page_size_bytes) / bytes_per_mebibyte; +#else + return std::nullopt; +#endif +} + +auto Utility::releaseMemory() -> MemoryReleaseStats +{ + MemoryReleaseStats stats; +#if defined(__linux__) && defined(__GLIBC__) + const auto rss_before_mb = currentRssMb(); + (void) ::malloc_trim(0); + const auto rss_after_mb = currentRssMb(); + if (rss_before_mb.has_value() && rss_after_mb.has_value()) { + stats.supported = true; + stats.rss_before_mb = *rss_before_mb; + stats.rss_after_mb = *rss_after_mb; + } +#endif + return stats; +} + } // namespace icts diff --git a/src/operation/iCTS/source/toolkit/utility/Utility.hh b/src/operation/iCTS/source/toolkit/utility/Utility.hh index 0912fdd13..7d0b7a325 100644 --- a/src/operation/iCTS/source/toolkit/utility/Utility.hh +++ b/src/operation/iCTS/source/toolkit/utility/Utility.hh @@ -25,18 +25,28 @@ #include #include +#include #include #include #include namespace icts { +struct MemoryReleaseStats +{ + bool supported = false; + double rss_before_mb = 0.0; + double rss_after_mb = 0.0; +}; + class Utility final { public: Utility() = delete; static auto getElapsedSeconds(std::chrono::steady_clock::time_point start_time) -> double; + static auto currentRssMb() -> std::optional; + static auto releaseMemory() -> MemoryReleaseStats; template static auto getString(Args&&... args) -> std::string diff --git a/src/operation/iCTS/test/data_manager/CMakeLists.txt b/src/operation/iCTS/test/data_manager/CMakeLists.txt index b54bd11af..0c67499e5 100644 --- a/src/operation/iCTS/test/data_manager/CMakeLists.txt +++ b/src/operation/iCTS/test/data_manager/CMakeLists.txt @@ -4,6 +4,7 @@ icts_add_test_executable( ${ICTS_TEST}/data_manager/DataManagerTest.cc ) +add_subdirectory(lifecycle) add_subdirectory(adapter) add_subdirectory(config) add_subdirectory(design) diff --git a/src/operation/iCTS/test/data_manager/adapter/fast_sta/FastSTATest.cc b/src/operation/iCTS/test/data_manager/adapter/fast_sta/FastSTATest.cc index 1d666c424..5802b7e19 100644 --- a/src/operation/iCTS/test/data_manager/adapter/fast_sta/FastSTATest.cc +++ b/src/operation/iCTS/test/data_manager/adapter/fast_sta/FastSTATest.cc @@ -24,6 +24,9 @@ #include #include +#include +#include +#include #include #include #include @@ -225,6 +228,8 @@ auto MakeTinyContext() -> icts::FastStaClockContext MakeNode(icts::FastStaNodeKind::kSink, "sink/CLK", "sink", "CLK", "", icts::FastStaPoint{.x_dbu = 2000, .y_dbu = 0}, 0.10, 1U, {}), }; context.node_id_by_name = {{"clk_src", 0U}, {"buf/A", 1U}, {"buf/Y", 2U}, {"sink/CLK", 3U}}; + context.buffer_input_node_id_by_inst = {{"buf", 1U}}; + context.buffer_output_node_id_by_inst = {{"buf", 2U}}; context.node_id_by_location = {{{0, 0}, 0U}, {{1000, 0}, 1U}, {{2000, 0}, 3U}}; context.nets = { MakeNet("clk_net", 0U, {1U}, 3.0, @@ -257,6 +262,8 @@ auto MakeTwoLevelContext() -> icts::FastStaClockContext MakeNode(icts::FastStaNodeKind::kSink, "sink/CLK", "sink", "CLK", "", {}, 0.10, 2U, {}), }; context.node_id_by_name = {{"clk_src", 0U}, {"buf1/A", 1U}, {"buf1/Y", 2U}, {"buf2/A", 3U}, {"buf2/Y", 4U}, {"sink/CLK", 5U}}; + context.buffer_input_node_id_by_inst = {{"buf1", 1U}, {"buf2", 3U}}; + context.buffer_output_node_id_by_inst = {{"buf1", 2U}, {"buf2", 4U}}; context.nets = { MakeNet("clk_net", 0U, {1U}, 3.0, MakeParasitic({MakeRcNode("clk_net@0", 0.0, 0.0, 0.0, 0.0, 0U), MakeRcNode("clk_net@1", 0.0, 0.20, 0.20, 0.0, 1U)}, @@ -272,6 +279,127 @@ auto MakeTwoLevelContext() -> icts::FastStaClockContext return context; } +auto MakeScaleContext(std::size_t node_count) -> icts::FastStaClockContext +{ + icts::FastStaClockContext context; + context.clock_name = "scale_clk"; + context.clock_net_name = "scale_source_net"; + context.clock_period_ns = 10.0; + context.root_input_slew_ns = 0.1; + context.liberty_cell_by_master["BUF_X1"] = MakeCell("BUF_X1", 0.20, 1.5, 0.01); + context.liberty_cell_by_master["BUF_X2"] = MakeCell("BUF_X2", 0.40, 2.5, 0.02); + + const auto buffer_count = (node_count - 2U) / 2U; + const auto buffer_input_id = [](std::size_t buffer_id) -> icts::FastStaNodeId { return 1U + 2U * buffer_id; }; + const auto buffer_output_id = [](std::size_t buffer_id) -> icts::FastStaNodeId { return 2U + 2U * buffer_id; }; + context.nodes.reserve(node_count); + context.nets.reserve(buffer_count + 1U); + context.node_id_by_name.reserve(node_count); + context.buffer_input_node_id_by_inst.reserve(buffer_count); + context.buffer_output_node_id_by_inst.reserve(buffer_count); + context.net_id_by_name.reserve(buffer_count + 1U); + + context.source_node_id = 0U; + context.nodes.push_back(MakeNode(icts::FastStaNodeKind::kSource, "scale_source", "", "scale_source", "", {}, 0.0, icts::kInvalidFastStaNetId, {0U})); + context.node_id_by_name.emplace("scale_source", 0U); + for (std::size_t buffer_id = 0U; buffer_id < buffer_count; ++buffer_id) { + const auto inst_name = "scale_buf_" + std::to_string(buffer_id); + const auto input_name = inst_name + "/A"; + const auto output_name = inst_name + "/Y"; + const auto input_id = buffer_input_id(buffer_id); + const auto output_id = buffer_output_id(buffer_id); + const auto incoming_net_id = buffer_id == 0U ? 0U : (buffer_id - 1U) / 2U + 1U; + context.nodes.push_back(MakeNode(icts::FastStaNodeKind::kBufferInput, input_name, inst_name, "A", "BUF_X1", {}, 0.20, incoming_net_id, {})); + context.nodes.push_back( + MakeNode(icts::FastStaNodeKind::kBufferOutput, output_name, inst_name, "Y", "BUF_X1", {}, 0.0, icts::kInvalidFastStaNetId, {buffer_id + 1U})); + context.node_id_by_name.emplace(input_name, input_id); + context.node_id_by_name.emplace(output_name, output_id); + context.buffer_input_node_id_by_inst.emplace(inst_name, input_id); + context.buffer_output_node_id_by_inst.emplace(inst_name, output_id); + } + + const auto sink_node_id = context.nodes.size(); + context.nodes.push_back(MakeNode(icts::FastStaNodeKind::kSink, "scale_sink/CLK", "scale_sink", "CLK", "", {}, 0.10, buffer_count, {})); + context.node_id_by_name.emplace("scale_sink/CLK", sink_node_id); + + context.nets.push_back(MakeNet("scale_source_net", 0U, {buffer_input_id(0U)}, 3.0)); + context.net_id_by_name.emplace("scale_source_net", 0U); + for (std::size_t buffer_id = 0U; buffer_id < buffer_count; ++buffer_id) { + std::vector load_node_ids; + const auto left_child = 2U * buffer_id + 1U; + const auto right_child = left_child + 1U; + if (left_child < buffer_count) { + load_node_ids.push_back(buffer_input_id(left_child)); + } + if (right_child < buffer_count) { + load_node_ids.push_back(buffer_input_id(right_child)); + } + if (buffer_id + 1U == buffer_count) { + load_node_ids.push_back(sink_node_id); + } + const auto net_name = "scale_net_" + std::to_string(buffer_id); + context.nets.push_back(MakeNet(net_name, buffer_output_id(buffer_id), std::move(load_node_ids), 3.0)); + context.net_id_by_name.emplace(net_name, buffer_id + 1U); + } + return context; +} + +auto MakeScaleChanges(std::size_t node_count) -> std::vector +{ + const auto buffer_count = (node_count - 2U) / 2U; + const auto parent_buffer_id = buffer_count / 2U - 1U; + const auto left_child = 2U * parent_buffer_id + 1U; + const auto right_child = left_child + 1U; + return { + {.node_id = 1U + 2U * left_child, .cell_master = "BUF_X2"}, + {.node_id = 1U + 2U * right_child, .cell_master = "BUF_X2"}, + }; +} + +auto TimingStatesMatch(const icts::FastStaClockContext& lhs, const icts::FastStaClockContext& rhs) -> bool +{ + if (lhs.nodes.size() != rhs.nodes.size() || lhs.timing_valid != rhs.timing_valid || lhs.power_valid != rhs.power_valid || lhs.skew.valid != rhs.skew.valid + || lhs.skew.min_sink_node_id != rhs.skew.min_sink_node_id || lhs.skew.max_sink_node_id != rhs.skew.max_sink_node_id + || std::abs(lhs.skew.min_arrival_ns - rhs.skew.min_arrival_ns) > 1e-12 || std::abs(lhs.skew.max_arrival_ns - rhs.skew.max_arrival_ns) > 1e-12 + || std::abs(lhs.skew.skew_ns - rhs.skew.skew_ns) > 1e-12) { + return false; + } + for (std::size_t node_id = 0U; node_id < lhs.nodes.size(); ++node_id) { + const auto& lhs_node = lhs.nodes.at(node_id); + const auto& rhs_node = rhs.nodes.at(node_id); + if (lhs_node.cell_master != rhs_node.cell_master || lhs_node.timing.valid != rhs_node.timing.valid + || std::abs(lhs_node.timing.arrival_ns - rhs_node.timing.arrival_ns) > 1e-12 || std::abs(lhs_node.timing.slew_ns - rhs_node.timing.slew_ns) > 1e-12) { + return false; + } + } + return true; +} + +auto MeasureScaleRoutes(const icts::FastStaClockContext& baseline_context, const std::vector& changes, double& full_replay_us, + double& incremental_replay_us) -> bool +{ + auto full_context = baseline_context; + const auto full_start = std::chrono::steady_clock::now(); + const auto full_ok = icts::FastStaIncremental::changeBufferMasters(full_context, changes) && icts::FastStaTiming::update(full_context); + const auto full_finish = std::chrono::steady_clock::now(); + + auto incremental_context = baseline_context; + const auto incremental_start = std::chrono::steady_clock::now(); + const auto dirty_region = icts::FastStaIncremental::changeBufferMastersIncremental(incremental_context, changes); + const auto incremental_ok = dirty_region.has_value() && icts::FastStaTiming::updateRegion(incremental_context, dirty_region.value()); + const auto incremental_finish = std::chrono::steady_clock::now(); + + full_replay_us = std::chrono::duration(full_finish - full_start).count(); + incremental_replay_us = std::chrono::duration(incremental_finish - incremental_start).count(); + return full_ok && incremental_ok && TimingStatesMatch(full_context, incremental_context); +} + +auto Median(std::vector samples) -> double +{ + std::ranges::sort(samples); + return samples.at(samples.size() / 2U); +} + auto MakeOpenStaAlignmentPathContext() -> icts::FastStaClockContext { icts::FastStaClockContext context; @@ -570,6 +698,17 @@ TEST(FastSTATest, CharacterizationSampleRejectsMissingSourceBoundaryNet) EXPECT_FALSE(sample.valid); } +TEST(FastSTATest, InvalidCharacterizationContextAccessIsSafe) +{ + icts::FastSTA fast_sta; + + EXPECT_FALSE(fast_sta.eraseCharContext(0U)); + EXPECT_FALSE(fast_sta.setCharLoad(0U, 0.25)); + EXPECT_FALSE(fast_sta.runCharSample(0U, 0.10).valid); + fast_sta.reset(); + EXPECT_FALSE(fast_sta.eraseCharContext(0U)); +} + TEST(FastSTATest, IncrementalMasterChangeMatchesFullRecompute) { auto incremental_context = MakeTwoLevelContext(); @@ -607,5 +746,133 @@ TEST(FastSTATest, IncrementalMasterChangeMatchesFullRecompute) EXPECT_NEAR(incremental_context.power.area_um2, full_context.power.area_um2, 1e-12); } +TEST(FastSTATest, BatchIncrementalMasterChangeAndRestoreMatchFullRecompute) +{ + auto original_context = MakeTwoLevelContext(); + ASSERT_TRUE(icts::FastStaTiming::update(original_context)); + auto incremental_context = original_context; + auto full_context = original_context; + const std::vector changes{ + {.node_id = 1U, .cell_master = "BUF_X2"}, + {.node_id = 3U, .cell_master = "BUF_X2"}, + }; + + ASSERT_TRUE(icts::FastStaIncremental::validateBufferMasterChanges(incremental_context, changes)); + const auto changed_region = icts::FastStaIncremental::changeBufferMastersIncremental(incremental_context, changes); + if (!changed_region.has_value()) { + ADD_FAILURE() << "Expected a dirty region for the validated buffer-master batch."; + return; + } + ASSERT_TRUE(icts::FastStaTiming::updateRegion(incremental_context, *changed_region)); + ASSERT_TRUE(icts::FastStaIncremental::changeBufferMasters(full_context, changes)); + ASSERT_TRUE(icts::FastStaTiming::update(full_context)); + EXPECT_TRUE(TimingStatesMatch(incremental_context, full_context)); + + const std::vector restore{ + {.node_id = 1U, .cell_master = "BUF_X1"}, + {.node_id = 3U, .cell_master = "BUF_X1"}, + }; + ASSERT_TRUE(icts::FastStaIncremental::validateBufferMasterChanges(incremental_context, restore)); + const auto restored_region = icts::FastStaIncremental::changeBufferMastersIncremental(incremental_context, restore); + if (!restored_region.has_value()) { + ADD_FAILURE() << "Expected a dirty region when restoring the original buffer masters."; + return; + } + ASSERT_TRUE(icts::FastStaTiming::updateRegion(incremental_context, *restored_region)); + EXPECT_TRUE(TimingStatesMatch(incremental_context, original_context)); +} + +TEST(FastSTATest, MissingBufferPairIndexesUseValidatedFallback) +{ + auto incremental_context = MakeTwoLevelContext(); + incremental_context.buffer_input_node_id_by_inst.clear(); + incremental_context.buffer_output_node_id_by_inst.clear(); + ASSERT_TRUE(icts::FastStaTiming::update(incremental_context)); + + auto full_context = incremental_context; + const std::vector changes{ + {.node_id = 2U, .cell_master = "BUF_X2"}, + {.node_id = 3U, .cell_master = "BUF_X2"}, + }; + + const auto dirty_region = icts::FastStaIncremental::changeBufferMastersIncremental(incremental_context, changes); + if (!dirty_region.has_value()) { + ADD_FAILURE() << "Expected missing buffer-pair indexes to use the validated fallback."; + return; + } + ASSERT_TRUE(icts::FastStaTiming::updateRegion(incremental_context, *dirty_region)); + ASSERT_TRUE(icts::FastStaIncremental::changeBufferMasters(full_context, changes)); + ASSERT_TRUE(icts::FastStaTiming::update(full_context)); + EXPECT_TRUE(TimingStatesMatch(incremental_context, full_context)); + + ASSERT_TRUE(icts::FastStaPower::update(incremental_context)); + ASSERT_TRUE(icts::FastStaPower::update(full_context)); + EXPECT_NEAR(incremental_context.power.total_power_w, full_context.power.total_power_w, 1e-18); + EXPECT_NEAR(incremental_context.power.area_um2, full_context.power.area_um2, 1e-12); +} + +TEST(FastSTATest, BatchPrevalidationRejectsWholeChangeWithoutMutation) +{ + auto context = MakeTwoLevelContext(); + ASSERT_TRUE(icts::FastStaTiming::update(context)); + const auto original_context = context; + const std::vector changes{ + {.node_id = 1U, .cell_master = "BUF_X2"}, + {.node_id = context.nodes.size(), .cell_master = "BUF_X2"}, + }; + + EXPECT_FALSE(icts::FastStaIncremental::validateBufferMasterChanges(context, changes)); + EXPECT_FALSE(icts::FastStaIncremental::changeBufferMastersIncremental(context, changes).has_value()); + EXPECT_TRUE(TimingStatesMatch(context, original_context)); +} + +TEST(FastSTATest, BatchIncrementalTimingScale) +{ + const auto run_scale = [](std::size_t node_count, std::size_t measured_rounds) -> std::pair { + auto baseline_context = MakeScaleContext(node_count); + EXPECT_EQ(baseline_context.nodes.size(), node_count); + EXPECT_TRUE(icts::FastStaTiming::update(baseline_context)); + const auto changes = MakeScaleChanges(node_count); + + double warmup_full_us = 0.0; + double warmup_incremental_us = 0.0; + EXPECT_TRUE(MeasureScaleRoutes(baseline_context, changes, warmup_full_us, warmup_incremental_us)); + + std::vector full_samples_us; + std::vector incremental_samples_us; + full_samples_us.reserve(measured_rounds); + incremental_samples_us.reserve(measured_rounds); + for (std::size_t round = 0U; round < measured_rounds; ++round) { + double full_replay_us = 0.0; + double incremental_replay_us = 0.0; + EXPECT_TRUE(MeasureScaleRoutes(baseline_context, changes, full_replay_us, incremental_replay_us)) << "node_count=" << node_count << " round=" << round; + full_samples_us.push_back(full_replay_us); + incremental_samples_us.push_back(incremental_replay_us); + } + + const auto full_median_us = Median(full_samples_us); + const auto incremental_median_us = Median(incremental_samples_us); + std::cout << "FASTSTA_SCALE node_count=" << node_count << " full_us="; + for (const auto sample : full_samples_us) { + std::cout << sample << ','; + } + std::cout << " incremental_us="; + for (const auto sample : incremental_samples_us) { + std::cout << sample << ','; + } + std::cout << " full_median_us=" << full_median_us << " incremental_median_us=" << incremental_median_us + << " ratio=" << incremental_median_us / full_median_us << '\n'; + return {full_median_us, incremental_median_us}; + }; + + const auto [full_10k_us, incremental_10k_us] = run_scale(10'000U, 5U); + EXPECT_GT(full_10k_us, 0.0); + EXPECT_GT(incremental_10k_us, 0.0); + const auto [full_100k_us, incremental_100k_us] = run_scale(100'000U, 3U); + EXPECT_GT(full_100k_us, 0.0); + EXPECT_GT(incremental_100k_us, 0.0); + EXPECT_LE(incremental_100k_us, full_100k_us * 0.80); +} + } // namespace } // namespace icts_test diff --git a/src/operation/iCTS/test/data_manager/io/CMakeLists.txt b/src/operation/iCTS/test/data_manager/io/CMakeLists.txt index 5c45a5f65..2a5a9b7be 100644 --- a/src/operation/iCTS/test/data_manager/io/CMakeLists.txt +++ b/src/operation/iCTS/test/data_manager/io/CMakeLists.txt @@ -1,5 +1,6 @@ icts_add_test_executable( icts_test_data_manager_io SOURCES + ${ICTS_TEST}/data_manager/io/IdbBulkDisconnectTest.cc ${ICTS_TEST}/data_manager/io/WrapperRCTest.cc ) diff --git a/src/operation/iCTS/test/data_manager/io/IdbBulkDisconnectTest.cc b/src/operation/iCTS/test/data_manager/io/IdbBulkDisconnectTest.cc new file mode 100644 index 000000000..dbe4fe24c --- /dev/null +++ b/src/operation/iCTS/test/data_manager/io/IdbBulkDisconnectTest.cc @@ -0,0 +1,422 @@ +// *************************************************************************************** +// Copyright (c) 2023-2025 Peng Cheng Laboratory +// Copyright (c) 2023-2025 Institute of Computing Technology, Chinese Academy of Sciences +// Copyright (c) 2023-2025 Beijing Institute of Open Source Chip +// +// iEDA is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// +// See the Mulan PSL v2 for more details. +// *************************************************************************************** +/** + * @file IdbBulkDisconnectTest.cc + * @author Dawn Li (dawnli619215645@gmail.com) + * @date 2026-08-11 + * @brief Regression and scale tests for iDB-owned bulk regular-net disconnection. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "IdbDesign.h" +#include "IdbInstance.h" +#include "IdbNet.h" +#include "IdbPins.h" + +namespace icts_test { +namespace { + +using SteadyClock = std::chrono::steady_clock; + +struct DisconnectObservation +{ + std::size_t disconnected_pin_count = 0U; + std::size_t net_pin_count = 0U; + std::size_t net_instance_count = 0U; + bool all_pins_disconnected = false; + bool all_net_names_empty = false; + + auto operator==(const DisconnectObservation&) const -> bool = default; +}; + +struct DisconnectSample +{ + double duration_us = 0.0; + DisconnectObservation observation; +}; + +struct ScaleSamples +{ + std::size_t pin_count = 0U; + std::vector legacy_us; + std::vector bulk_us; +}; + +class HighFanoutIoFixture +{ + public: + explicit HighFanoutIoFixture(std::size_t pin_count) : _owned_pins(pin_count) + { + _net = _design.createOrFindNet("high_fanout", idb::IdbConnectType::kClock); + _pin_refs.reserve(pin_count); + for (std::size_t index = 0U; index < pin_count; ++index) { + auto* pin_ref = &_owned_pins[index]; + pin_ref->set_as_io(); + pin_ref->set_pin_name("P" + std::to_string(index)); + _pin_refs.push_back(pin_ref); + if (!_design.connectPinToNet(pin_ref, _net)) { + _setup_ok = false; + break; + } + } + } + + auto setupOk() const -> bool { return _setup_ok && _net != nullptr && static_cast(_net->get_pin_number()) == _pin_refs.size(); } + auto design() -> idb::IdbDesign& { return _design; } + auto net() -> idb::IdbNet* { return _net; } + auto pinRefs() -> const std::vector& { return _pin_refs; } + + private: + std::vector _owned_pins; + idb::IdbDesign _design; + idb::IdbNet* _net = nullptr; + std::vector _pin_refs; + bool _setup_ok = true; +}; + +auto ObserveDisconnected(HighFanoutIoFixture& fixture, std::size_t disconnected_pin_count) -> DisconnectObservation +{ + auto* net = fixture.net(); + const auto& pins = fixture.pinRefs(); + + DisconnectObservation observation; + observation.disconnected_pin_count = disconnected_pin_count; + observation.net_pin_count = net == nullptr ? 0U : static_cast(net->get_pin_number()); + observation.net_instance_count = net == nullptr || net->get_instance_list() == nullptr ? 0U : net->get_instance_list()->get_instance_list().size(); + observation.all_pins_disconnected = std::ranges::all_of(pins, [](auto* pin) -> bool { return pin != nullptr && pin->get_net() == nullptr; }); + observation.all_net_names_empty = std::ranges::all_of(pins, [](auto* pin) -> bool { return pin != nullptr && pin->get_net_name().empty(); }); + return observation; +} + +auto RunLegacyDisconnect(std::size_t pin_count) -> DisconnectSample +{ + HighFanoutIoFixture fixture(pin_count); + EXPECT_TRUE(fixture.setupOk()); + + std::size_t disconnected_pin_count = 0U; + const auto start = SteadyClock::now(); + for (auto* pin : fixture.pinRefs()) { + disconnected_pin_count += fixture.design().disconnectPinFromNet(pin) ? 1U : 0U; + } + const auto stop = SteadyClock::now(); + + return DisconnectSample{.duration_us = std::chrono::duration(stop - start).count(), + .observation = ObserveDisconnected(fixture, disconnected_pin_count)}; +} + +auto RunBulkDisconnect(std::size_t pin_count) -> DisconnectSample +{ + HighFanoutIoFixture fixture(pin_count); + EXPECT_TRUE(fixture.setupOk()); + + const auto start = SteadyClock::now(); + const std::size_t disconnected_pin_count = fixture.design().disconnectAllPinsFromNet(fixture.net()); + const auto stop = SteadyClock::now(); + + return DisconnectSample{.duration_us = std::chrono::duration(stop - start).count(), + .observation = ObserveDisconnected(fixture, disconnected_pin_count)}; +} + +void ExpectCompleteDisconnect(const DisconnectObservation& observation, std::size_t pin_count) +{ + EXPECT_EQ(observation.disconnected_pin_count, pin_count); + EXPECT_EQ(observation.net_pin_count, 0U); + EXPECT_EQ(observation.net_instance_count, 0U); + EXPECT_TRUE(observation.all_pins_disconnected); + EXPECT_TRUE(observation.all_net_names_empty); +} + +void RecordMeasuredPair(ScaleSamples& samples, bool bulk_first) +{ + DisconnectSample legacy; + DisconnectSample bulk; + if (bulk_first) { + bulk = RunBulkDisconnect(samples.pin_count); + legacy = RunLegacyDisconnect(samples.pin_count); + } else { + legacy = RunLegacyDisconnect(samples.pin_count); + bulk = RunBulkDisconnect(samples.pin_count); + } + + ExpectCompleteDisconnect(legacy.observation, samples.pin_count); + ExpectCompleteDisconnect(bulk.observation, samples.pin_count); + EXPECT_EQ(legacy.observation, bulk.observation); + samples.legacy_us.push_back(legacy.duration_us); + samples.bulk_us.push_back(bulk.duration_us); +} + +auto Median(std::vector samples) -> double +{ + std::ranges::sort(samples); + const std::size_t middle = samples.size() / 2U; + if (samples.size() % 2U != 0U) { + return samples[middle]; + } + return (samples[middle - 1U] + samples[middle]) / 2.0; +} + +void PrintSamples(const ScaleSamples& samples) +{ + const auto print_route = [&](const char* route, const std::vector& durations) -> void { + std::cout << "IDB_BULK_PERF pin_count=" << samples.pin_count << " route=" << route << " samples_us=["; + for (std::size_t index = 0U; index < durations.size(); ++index) { + if (index != 0U) { + std::cout << ','; + } + std::cout << std::fixed << std::setprecision(3) << durations[index]; + } + std::cout << "] median_us=" << Median(durations) << '\n'; + }; + + print_route("legacy", samples.legacy_us); + print_route("bulk", samples.bulk_us); +} + +void WriteSamplesIfRequested(const std::vector& scale_samples) +{ + const char* output_value = std::getenv("ICTS_IDB_PERF_OUTPUT"); + if (output_value == nullptr || *output_value == '\0') { + return; + } + + const std::filesystem::path output_path(output_value); + std::error_code error; + if (!output_path.parent_path().empty()) { + std::filesystem::create_directories(output_path.parent_path(), error); + } + ASSERT_FALSE(error) << "Unable to create iDB performance evidence directory: " << error.message(); + + std::ofstream output(output_path, std::ios::trunc); + ASSERT_TRUE(output.is_open()) << "Unable to write iDB performance evidence: " << output_path; + output << "pin_count,route,round,duration_us\n"; + output << std::fixed << std::setprecision(3); + for (const auto& samples : scale_samples) { + for (std::size_t index = 0U; index < samples.legacy_us.size(); ++index) { + output << samples.pin_count << ",legacy," << (index + 1U) << ',' << samples.legacy_us[index] << '\n'; + } + for (std::size_t index = 0U; index < samples.bulk_us.size(); ++index) { + output << samples.pin_count << ",bulk," << (index + 1U) << ',' << samples.bulk_us[index] << '\n'; + } + } +} + +TEST(IdbPinsTest, ClearPinRefsPreservesBorrowedPinsAndResetsLazyIndex) +{ + idb::IdbPins refs; + std::vector> owned_pins; + owned_pins.reserve(40U); + for (std::size_t index = 0U; index < 40U; ++index) { + auto pin = std::make_unique(); + pin->set_pin_name("P" + std::to_string(index)); + auto* pin_ref = pin.get(); + owned_pins.emplace_back(std::move(pin)); + EXPECT_EQ(refs.add_pin_ref_unique(pin_ref), pin_ref); + } + ASSERT_EQ(refs.get_pin_num(), 40U); + + refs.clear_pin_refs(); + + EXPECT_EQ(refs.get_pin_num(), 0U); + EXPECT_EQ(owned_pins.front()->get_pin_name(), "P0"); + EXPECT_EQ(owned_pins.back()->get_pin_name(), "P39"); + for (const auto& pin : owned_pins) { + EXPECT_EQ(refs.add_pin_ref_unique(pin.get()), pin.get()); + } + EXPECT_EQ(refs.get_pin_num(), 40U); + EXPECT_EQ(refs.add_pin_ref_unique(owned_pins.back().get()), owned_pins.back().get()); + EXPECT_EQ(refs.get_pin_num(), 40U); +} + +TEST(IdbInstanceListTest, ContainsUsesNamedMapWithoutLosingPointerFallback) +{ + idb::IdbDesign owning_design; + auto* first = owning_design.get_instance_list()->add_instance("u_first"); + ASSERT_NE(first, nullptr); + idb::IdbNet borrowed_instance_owner; + auto* instances = borrowed_instance_owner.get_instance_list(); + ASSERT_NE(instances, nullptr); + ASSERT_TRUE(instances->add_instance_ref(first)); + EXPECT_TRUE(instances->contains(first)); + EXPECT_TRUE(instances->add_instance_ref(first)); + EXPECT_EQ(instances->get_instance_list().size(), 1U); + + idb::IdbInstance duplicate_name; + duplicate_name.set_name("u_first"); + EXPECT_FALSE(instances->contains(&duplicate_name)); + EXPECT_FALSE(instances->add_instance_ref(&duplicate_name)); + EXPECT_EQ(instances->get_instance_list().size(), 1U); + + first->set_name("u_renamed"); + EXPECT_TRUE(instances->contains(first)); + EXPECT_TRUE(instances->add_instance_ref(first)); + EXPECT_EQ(instances->get_instance_list().size(), 1U); +} + +TEST(IdbDesignBulkDisconnectTest, PreservesSpecialNameBorrowedObjectsAndRepeatUse) +{ + idb::IdbDesign design; + EXPECT_EQ(design.disconnectAllPinsFromNet(nullptr), 0U); + + auto* instance = design.get_instance_list()->add_instance("u_sink"); + ASSERT_NE(instance, nullptr); + auto* first_inst_pin = instance->get_pin_list()->add_pin_list("A"); + auto* second_inst_pin = instance->get_pin_list()->add_pin_list("B"); + ASSERT_NE(first_inst_pin, nullptr); + ASSERT_NE(second_inst_pin, nullptr); + first_inst_pin->set_instance(instance); + second_inst_pin->set_instance(instance); + + auto* io_pin = design.createOrFindIoPin("clk_in"); + auto* regular_net = design.createOrFindNet("clk", idb::IdbConnectType::kClock); + auto* special_net = design.createOrFindSpecialNet("VDD", idb::IdbConnectType::kPower); + ASSERT_NE(io_pin, nullptr); + ASSERT_NE(regular_net, nullptr); + ASSERT_NE(special_net, nullptr); + ASSERT_TRUE(design.connectPinToNet(io_pin, regular_net)); + ASSERT_TRUE(design.connectPinToNet(first_inst_pin, regular_net)); + ASSERT_TRUE(design.connectPinToNet(second_inst_pin, regular_net)); + ASSERT_TRUE(design.connectPinToSpecialNet(first_inst_pin, special_net)); + + EXPECT_EQ(design.disconnectAllPinsFromNet(regular_net), 3U); + EXPECT_TRUE(regular_net->get_io_pins()->get_pin_list().empty()); + EXPECT_TRUE(regular_net->get_instance_pin_list()->get_pin_list().empty()); + EXPECT_TRUE(regular_net->get_instance_list()->get_instance_list().empty()); + EXPECT_EQ(io_pin->get_net(), nullptr); + EXPECT_TRUE(io_pin->get_net_name().empty()); + EXPECT_EQ(first_inst_pin->get_net(), nullptr); + EXPECT_EQ(first_inst_pin->get_special_net(), special_net); + EXPECT_EQ(first_inst_pin->get_net_name(), "VDD"); + EXPECT_EQ(second_inst_pin->get_net(), nullptr); + EXPECT_TRUE(second_inst_pin->get_net_name().empty()); + EXPECT_EQ(design.get_instance_list()->find_instance("u_sink"), instance); + EXPECT_EQ(instance->get_pin("A"), first_inst_pin); + + ASSERT_TRUE(design.connectPinToNet(io_pin, regular_net)); + ASSERT_TRUE(design.connectPinToNet(first_inst_pin, regular_net)); + ASSERT_TRUE(design.connectPinToNet(second_inst_pin, regular_net)); + EXPECT_EQ(design.disconnectAllPinsFromNet(regular_net), 3U); + EXPECT_EQ(design.disconnectAllPinsFromNet(regular_net), 0U); + EXPECT_EQ(first_inst_pin->get_net_name(), "VDD"); + EXPECT_EQ(design.get_instance_list()->find_instance("u_sink"), instance); +} + +TEST(IdbDesignBulkDisconnectTest, RemoveNetSafePreservesPinAndSpecialNetName) +{ + idb::IdbDesign design; + auto* pin = design.createOrFindIoPin("clk_in"); + auto* regular_net = design.createOrFindNet("clk", idb::IdbConnectType::kClock); + auto* special_net = design.createOrFindSpecialNet("VDD", idb::IdbConnectType::kPower); + ASSERT_NE(pin, nullptr); + ASSERT_NE(regular_net, nullptr); + ASSERT_NE(special_net, nullptr); + ASSERT_TRUE(design.connectPinToNet(pin, regular_net)); + ASSERT_TRUE(design.connectPinToSpecialNet(pin, special_net)); + + EXPECT_TRUE(design.removeNetSafe("clk")); + EXPECT_EQ(design.get_net_list()->find_net("clk"), nullptr); + EXPECT_EQ(design.get_io_pin_list()->find_pin("clk_in"), pin); + EXPECT_EQ(pin->get_net(), nullptr); + EXPECT_EQ(pin->get_special_net(), special_net); + EXPECT_EQ(pin->get_net_name(), "VDD"); +} + +TEST(IdbDesignBulkDisconnectTest, MergeNetIntoMovesPinsAfterOneSourceDetach) +{ + idb::IdbDesign design; + auto* instance = design.get_instance_list()->add_instance("u_sink"); + ASSERT_NE(instance, nullptr); + auto* inst_pin = instance->get_pin_list()->add_pin_list("A"); + ASSERT_NE(inst_pin, nullptr); + inst_pin->set_instance(instance); + + auto* io_pin = design.createOrFindIoPin("clk_in"); + auto* target_net = design.createOrFindNet("target", idb::IdbConnectType::kClock); + auto* source_net = design.createOrFindNet("source", idb::IdbConnectType::kClock); + ASSERT_NE(io_pin, nullptr); + ASSERT_NE(target_net, nullptr); + ASSERT_NE(source_net, nullptr); + ASSERT_TRUE(design.connectPinToNet(io_pin, source_net)); + ASSERT_TRUE(design.connectPinToNet(inst_pin, source_net)); + + EXPECT_TRUE(design.mergeNetInto("target", "source", false)); + EXPECT_EQ(design.get_net_list()->find_net("source"), nullptr); + EXPECT_EQ(io_pin->get_net(), target_net); + EXPECT_EQ(io_pin->get_net_name(), "target"); + EXPECT_EQ(inst_pin->get_net(), target_net); + EXPECT_EQ(inst_pin->get_net_name(), "target"); + EXPECT_TRUE(target_net->has_io_pin(io_pin)); + EXPECT_TRUE(target_net->has_instance_pin(inst_pin)); + EXPECT_TRUE(target_net->has_instance(instance)); +} + +TEST(IdbDesignBulkDisconnectPerformanceTest, LegacyAndBulkPathsMatchAtTenAndHundredThousandPins) +{ + constexpr std::size_t ten_thousand_pin_count = 10000U; + constexpr std::size_t hundred_thousand_pin_count = 100000U; + constexpr std::size_t ten_thousand_round_count = 5U; + constexpr std::size_t hundred_thousand_round_count = 3U; + + const auto warmup_legacy = RunLegacyDisconnect(ten_thousand_pin_count); + const auto warmup_bulk = RunBulkDisconnect(ten_thousand_pin_count); + ExpectCompleteDisconnect(warmup_legacy.observation, ten_thousand_pin_count); + ExpectCompleteDisconnect(warmup_bulk.observation, ten_thousand_pin_count); + EXPECT_EQ(warmup_legacy.observation, warmup_bulk.observation); + + ScaleSamples ten_thousand{.pin_count = ten_thousand_pin_count, .legacy_us = {}, .bulk_us = {}}; + for (std::size_t round = 0U; round < ten_thousand_round_count; ++round) { + RecordMeasuredPair(ten_thousand, round % 2U != 0U); + } + + ScaleSamples hundred_thousand{.pin_count = hundred_thousand_pin_count, .legacy_us = {}, .bulk_us = {}}; + for (std::size_t round = 0U; round < hundred_thousand_round_count; ++round) { + RecordMeasuredPair(hundred_thousand, round % 2U == 0U); + } + + PrintSamples(ten_thousand); + PrintSamples(hundred_thousand); + WriteSamplesIfRequested({ten_thousand, hundred_thousand}); + + const double ten_thousand_legacy_median = Median(ten_thousand.legacy_us); + const double ten_thousand_bulk_median = Median(ten_thousand.bulk_us); + const double hundred_thousand_legacy_median = Median(hundred_thousand.legacy_us); + const double hundred_thousand_bulk_median = Median(hundred_thousand.bulk_us); + std::cout << "IDB_BULK_PERF_SUMMARY pin_count=10000 legacy_median_us=" << ten_thousand_legacy_median << " bulk_median_us=" << ten_thousand_bulk_median + << " speedup=" << (ten_thousand_legacy_median / ten_thousand_bulk_median) << '\n'; + std::cout << "IDB_BULK_PERF_SUMMARY pin_count=100000 legacy_median_us=" << hundred_thousand_legacy_median + << " bulk_median_us=" << hundred_thousand_bulk_median << " speedup=" << (hundred_thousand_legacy_median / hundred_thousand_bulk_median) + << " bulk_scaling=" << (hundred_thousand_bulk_median / ten_thousand_bulk_median) << '\n'; + + EXPECT_LE(hundred_thousand_bulk_median, hundred_thousand_legacy_median * 0.20); + EXPECT_LE(hundred_thousand_bulk_median, ten_thousand_bulk_median * 15.0); +} + +} // namespace +} // namespace icts_test diff --git a/src/operation/iCTS/test/data_manager/lifecycle/CMakeLists.txt b/src/operation/iCTS/test/data_manager/lifecycle/CMakeLists.txt new file mode 100644 index 000000000..ace9ee2c7 --- /dev/null +++ b/src/operation/iCTS/test/data_manager/lifecycle/CMakeLists.txt @@ -0,0 +1,21 @@ +icts_add_test_executable( + icts_test_data_manager_lifecycle + SOURCES + ${ICTS_TEST}/data_manager/lifecycle/CTSAPILifecycleTest.cc +) + +add_executable( + icts_test_data_manager_lifecycle_exit + ${ICTS_TEST}/data_manager/lifecycle/CTSProcessExitTest.cc +) + +target_link_libraries( + icts_test_data_manager_lifecycle_exit + PRIVATE + icts_test_base +) + +add_test( + NAME icts_test_data_manager_lifecycle_exit + COMMAND icts_test_data_manager_lifecycle_exit +) diff --git a/src/operation/iCTS/test/data_manager/lifecycle/CTSAPILifecycleTest.cc b/src/operation/iCTS/test/data_manager/lifecycle/CTSAPILifecycleTest.cc new file mode 100644 index 000000000..33d999366 --- /dev/null +++ b/src/operation/iCTS/test/data_manager/lifecycle/CTSAPILifecycleTest.cc @@ -0,0 +1,366 @@ +// *************************************************************************************** +// Copyright (c) 2023-2025 Peng Cheng Laboratory +// Copyright (c) 2023-2025 Institute of Computing Technology, Chinese Academy of Sciences +// Copyright (c) 2023-2025 Beijing Institute of Open Source Chip +// +// iEDA is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// +// See the Mulan PSL v2 for more details. +// *************************************************************************************** +/** + * @file CTSAPILifecycleTest.cc + * @author Dawn Li (dawnli619215645@gmail.com) + * @date 2026-08-10 + * @brief Public CTS destruction lifecycle and session-ownership tests. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "CTSAPI.hh" +#include "IdbDesign.h" +#include "Logger.hh" +#include "Utility.hh" +#include "data_manager/DataManager.hh" +#include "data_manager/io/Wrapper.hh" +#include "idm.h" + +namespace icts_test { +namespace { + +auto MakeUniqueOutputDir(const std::string& label) -> std::filesystem::path +{ + const auto suffix = std::chrono::steady_clock::now().time_since_epoch().count(); + return std::filesystem::temp_directory_path() / ("icts_lifecycle_" + label + "_" + std::to_string(suffix)); +} + +void RemoveOutputDir(const std::filesystem::path& output_dir) +{ + std::error_code error_code; + std::filesystem::remove_all(output_dir, error_code); +} + +auto WriteTextFile(const std::filesystem::path& path, const std::string& content) -> bool +{ + std::ofstream stream(path); + stream << content; + return stream.good(); +} + +class ScopedMinimalExternalIdb +{ + public: + explicit ScopedMinimalExternalIdb(const std::string& label) : _output_dir(MakeUniqueOutputDir(label)) + { + std::filesystem::create_directories(_output_dir); + _config_path = _output_dir / "cts_config.json"; + const auto tech_lef_path = _output_dir / "minimal.lef"; + const auto def_path = _output_dir / "minimal.def"; + _work_dir = _output_dir / "cts_work"; + + constexpr auto tech_lef = R"lef(VERSION 5.8 ; +BUSBITCHARS "[]" ; +DIVIDERCHAR "/" ; +UNITS + DATABASE MICRONS 1000 ; +END UNITS +MANUFACTURINGGRID 0.001 ; +LAYER M1 + TYPE ROUTING ; + DIRECTION HORIZONTAL ; + PITCH 0.10 ; + WIDTH 0.05 ; + RESISTANCE RPERSQ 0.10 ; + CAPACITANCE CPERSQDIST 0.001 ; +END M1 +END LIBRARY +)lef"; + constexpr auto design_def = R"def(VERSION 5.8 ; +DIVIDERCHAR "/" ; +BUSBITCHARS "[]" ; +DESIGN icts_lifecycle ; +UNITS DISTANCE MICRONS 1000 ; +DIEAREA ( 0 0 ) ( 1000 1000 ) ; +COMPONENTS 0 ; +END COMPONENTS +PINS 0 ; +END PINS +NETS 0 ; +END NETS +END DESIGN +)def"; + + dmInst->reset(); + _ready = WriteTextFile(_config_path, "{}") && WriteTextFile(tech_lef_path, tech_lef) && WriteTextFile(def_path, design_def) + && dmInst->readLef(std::vector{tech_lef_path.string()}, true) && dmInst->readDef(def_path.string()); + if (_ready) { + dmInst->get_config().set_sdc_path(""); + } + } + + ~ScopedMinimalExternalIdb() + { + (void) icts::CTSAPI::destroyCTS(); + dmInst->reset(); + RemoveOutputDir(_output_dir); + } + + ScopedMinimalExternalIdb(const ScopedMinimalExternalIdb& rhs) = delete; + ScopedMinimalExternalIdb(ScopedMinimalExternalIdb&& rhs) = delete; + auto operator=(const ScopedMinimalExternalIdb& rhs) -> ScopedMinimalExternalIdb& = delete; + auto operator=(ScopedMinimalExternalIdb&& rhs) -> ScopedMinimalExternalIdb& = delete; + + auto ready() const -> bool { return _ready; } + auto configPath() const -> const std::filesystem::path& { return _config_path; } + auto workDir() const -> const std::filesystem::path& { return _work_dir; } + + private: + std::filesystem::path _output_dir; + std::filesystem::path _config_path; + std::filesystem::path _work_dir; + bool _ready = false; +}; + +auto Median(std::array samples) -> double +{ + std::ranges::sort(samples); + return (samples.at(1U) + samples.at(2U)) / 2.0; +} + +struct ExternalDesignFingerprint +{ + std::vector insts; + std::vector nets; + std::vector pins; + + auto operator==(const ExternalDesignFingerprint& rhs) const -> bool = default; +}; + +auto Fingerprint(idb::IdbDesign& design) -> ExternalDesignFingerprint +{ + ExternalDesignFingerprint fingerprint; + for (auto* inst : design.get_instance_list()->get_instance_list()) { + if (inst == nullptr) { + continue; + } + auto* coordinate = inst->get_coordinate(); + fingerprint.insts.push_back(inst->get_name() + "@" + std::to_string(coordinate == nullptr ? 0 : coordinate->get_x()) + "," + + std::to_string(coordinate == nullptr ? 0 : coordinate->get_y())); + for (auto* pin : inst->get_pin_list()->get_pin_list()) { + if (pin == nullptr) { + continue; + } + auto* location = pin->get_location(); + fingerprint.pins.push_back(inst->get_name() + "/" + pin->get_pin_name() + "->" + pin->get_net_name() + "@" + + std::to_string(location == nullptr ? 0 : location->get_x()) + "," + + std::to_string(location == nullptr ? 0 : location->get_y())); + } + } + for (auto* net : design.get_net_list()->get_net_list()) { + if (net == nullptr) { + continue; + } + std::vector connections; + for (auto* pin : net->get_instance_pin_list()->get_pin_list()) { + if (pin != nullptr && pin->get_instance() != nullptr) { + connections.push_back(pin->get_instance()->get_name() + "/" + pin->get_pin_name()); + } + } + std::ranges::sort(connections); + std::string record = net->get_net_name(); + for (const auto& connection : connections) { + record.append("|").append(connection); + } + fingerprint.nets.push_back(std::move(record)); + } + std::ranges::sort(fingerprint.insts); + std::ranges::sort(fingerprint.nets); + std::ranges::sort(fingerprint.pins); + return fingerprint; +} + +void PopulateRepresentativeSession(std::size_t inst_count) +{ + for (std::size_t index = 0U; index < inst_count; ++index) { + ASSERT_NE(CTSDM.getDesign().makeInst("session_inst_" + std::to_string(index)), nullptr); + } +} + +TEST(CTSAPILifecycleTest, DestroyIsSuccessfulBeforeInitializationAndWhenRepeated) +{ + const auto first = icts::CTSAPI::destroyCTS(); + const auto second = icts::CTSAPI::destroyCTS(); + + EXPECT_EQ(first.code, icts::CTSStatusCode::kOk); + EXPECT_EQ(second.code, icts::CTSStatusCode::kOk); + EXPECT_TRUE(icts::CTSAPI::lastStatus().ok()); + EXPECT_TRUE(icts::CTSAPI::outputClockTiming().empty()); +} + +TEST(CTSAPILifecycleTest, FailedInitializationUsesCanonicalCleanupAndLeavesAPIUninitialized) +{ + const auto output_dir = MakeUniqueOutputDir("failed_init"); + const auto status = icts::CTSAPI::init((output_dir / "missing_config.json").string(), output_dir.string()); + + EXPECT_EQ(status.code, icts::CTSStatusCode::kConfigError); + EXPECT_EQ(icts::CTSAPI::runCTS().code, icts::CTSStatusCode::kNotInitialized); + EXPECT_EQ(icts::CTSAPI::report(output_dir.string()).code, icts::CTSStatusCode::kNotInitialized); + EXPECT_TRUE(icts::CTSAPI::outputClockTiming().empty()); + EXPECT_TRUE(icts::CTSAPI::destroyCTS().ok()); + EXPECT_TRUE(icts::CTSAPI::destroyCTS().ok()); + + RemoveOutputDir(output_dir); +} + +TEST(CTSAPILifecycleTest, DestroyReleasesRepresentativeSessionAndFreshOwnerStartsEmpty) +{ + icts::DataManager::initInst(); + PopulateRepresentativeSession(256U); + ASSERT_NE(CTSDM.getDesign().findInst("session_inst_255"), nullptr); + + EXPECT_TRUE(icts::CTSAPI::destroyCTS().ok()); + EXPECT_TRUE(icts::CTSAPI::outputClockTiming().empty()); + + icts::DataManager::initInst(); + EXPECT_EQ(CTSDM.getState(), icts::CTSRunState::kEmpty); + EXPECT_TRUE(CTSDM.getDesign().get_insts().empty()); + EXPECT_EQ(CTSDM.getDesign().findInst("session_inst_255"), nullptr); +} + +TEST(CTSAPILifecycleTest, PublicInitializedSessionCanDestroyAndReinitialize) +{ + const ScopedMinimalExternalIdb external_idb("public_reinit"); + ASSERT_TRUE(external_idb.ready()); + + const auto first_init = icts::CTSAPI::init(external_idb.configPath().string(), external_idb.workDir().string()); + ASSERT_EQ(first_init.code, icts::CTSStatusCode::kOk) << first_init.message; + EXPECT_TRUE(icts::CTSAPI::outputClockTiming().empty()); + EXPECT_EQ(icts::CTSAPI::runCTS().code, icts::CTSStatusCode::kNoOp); + + EXPECT_TRUE(icts::CTSAPI::destroyCTS().ok()); + EXPECT_EQ(icts::CTSAPI::runCTS().code, icts::CTSStatusCode::kNotInitialized); + EXPECT_EQ(icts::CTSAPI::report(external_idb.workDir().string()).code, icts::CTSStatusCode::kNotInitialized); + + const auto second_init = icts::CTSAPI::init(external_idb.configPath().string(), external_idb.workDir().string()); + ASSERT_EQ(second_init.code, icts::CTSStatusCode::kOk) << second_init.message; + EXPECT_EQ(icts::CTSAPI::runCTS().code, icts::CTSStatusCode::kNoOp); + EXPECT_TRUE(icts::CTSAPI::destroyCTS().ok()); + EXPECT_TRUE(icts::CTSAPI::destroyCTS().ok()); +} + +TEST(CTSAPILifecycleTest, DestroyPreservesBorrowedExternalIdbObjectsAndConnectivity) +{ + icts::DataManager::initInst(); + idb::IdbDesign external_design; + auto* driver_inst = external_design.get_instance_list()->add_instance("cts_driver"); + auto* load_inst = external_design.get_instance_list()->add_instance("cts_load"); + ASSERT_NE(driver_inst, nullptr); + ASSERT_NE(load_inst, nullptr); + driver_inst->set_coodinate(100, 200, false); + load_inst->set_coodinate(300, 400, false); + auto* driver_pin = driver_inst->addPin("Y"); + auto* load_pin = load_inst->addPin("A"); + ASSERT_NE(driver_pin, nullptr); + ASSERT_NE(load_pin, nullptr); + driver_pin->set_location(110, 210); + load_pin->set_location(310, 410); + + auto* clock_net = external_design.get_net_list()->add_net("cts_clock_net", idb::IdbConnectType::kClock); + ASSERT_NE(clock_net, nullptr); + clock_net->add_instance_pin(driver_pin); + clock_net->add_instance_pin(load_pin); + driver_pin->set_net(clock_net); + driver_pin->set_net_name(clock_net->get_net_name()); + load_pin->set_net(clock_net); + load_pin->set_net_name(clock_net->get_net_name()); + + CTSDM.getWrapper().set_idb_design(&external_design); + const auto before = Fingerprint(external_design); + + EXPECT_TRUE(icts::CTSAPI::destroyCTS().ok()); + + const auto after = Fingerprint(external_design); + EXPECT_EQ(after, before); + ASSERT_EQ(after.insts.size(), 2U); + ASSERT_EQ(after.nets.size(), 1U); + ASSERT_EQ(after.pins.size(), 2U); +} + +TEST(CTSAPILifecycleTest, CompletionIsLoggedBeforeLoggerDestruction) +{ + icts::Logger::initInst(); + icts::DataManager::initInst(); + const auto output_dir = MakeUniqueOutputDir("log_order"); + std::filesystem::create_directories(output_dir); + const auto log_path = output_dir / "cts.log"; + CTSLOG.openLogFileStream(log_path.string()); + PopulateRepresentativeSession(32U); + + ASSERT_TRUE(icts::CTSAPI::destroyCTS().ok()); + + std::ifstream log_file(log_path); + const std::string log_text((std::istreambuf_iterator(log_file)), std::istreambuf_iterator()); + const auto start_pos = log_text.find("Starting CTS destruction"); + const auto completion_pos = log_text.find("Completed CTS destruction"); + EXPECT_NE(start_pos, std::string::npos); + EXPECT_NE(completion_pos, std::string::npos); + EXPECT_LT(start_pos, completion_pos); + + RemoveOutputDir(output_dir); +} + +TEST(CTSAPILifecycleTest, TenCyclesRemainWithinApprovedPostDestroyRssThreshold) +{ +#ifdef __linux__ + const ScopedMinimalExternalIdb external_idb("ten_cycles"); + ASSERT_TRUE(external_idb.ready()); + std::array post_destroy_rss_mb{}; + for (std::size_t cycle = 0U; cycle < post_destroy_rss_mb.size(); ++cycle) { + const auto init_status = icts::CTSAPI::init(external_idb.configPath().string(), external_idb.workDir().string()); + ASSERT_EQ(init_status.code, icts::CTSStatusCode::kOk) << "cycle " << cycle << ": " << init_status.message; + EXPECT_TRUE(icts::CTSAPI::outputClockTiming().empty()); + EXPECT_EQ(icts::CTSAPI::runCTS().code, icts::CTSStatusCode::kNoOp); + PopulateRepresentativeSession(4096U); + ASSERT_TRUE(icts::CTSAPI::destroyCTS().ok()); + ASSERT_TRUE(icts::CTSAPI::destroyCTS().ok()); + const auto rss_mb = icts::Utility::currentRssMb(); + ASSERT_TRUE(rss_mb.has_value()); + post_destroy_rss_mb.at(cycle) = rss_mb.value_or(0.0); + } + + const std::array early_samples = {post_destroy_rss_mb.at(1U), post_destroy_rss_mb.at(2U), post_destroy_rss_mb.at(3U), post_destroy_rss_mb.at(4U)}; + const std::array late_samples = {post_destroy_rss_mb.at(6U), post_destroy_rss_mb.at(7U), post_destroy_rss_mb.at(8U), post_destroy_rss_mb.at(9U)}; + const double early_median_mb = Median(early_samples); + const double late_median_mb = Median(late_samples); + constexpr double absolute_tolerance_mb = 16.0; + const double allowed_growth_mb = std::max(absolute_tolerance_mb, early_median_mb * 0.05); + EXPECT_LE(late_median_mb, early_median_mb + allowed_growth_mb); + + bool sustained_growth = true; + for (std::size_t index = 2U; index < post_destroy_rss_mb.size(); ++index) { + sustained_growth = sustained_growth && post_destroy_rss_mb.at(index) > post_destroy_rss_mb.at(index - 1U); + } + EXPECT_FALSE(sustained_growth); +#else + GTEST_SKIP() << "Linux /proc RSS sampling is unavailable."; +#endif +} + +} // namespace +} // namespace icts_test diff --git a/src/operation/iCTS/test/data_manager/lifecycle/CTSProcessExitTest.cc b/src/operation/iCTS/test/data_manager/lifecycle/CTSProcessExitTest.cc new file mode 100644 index 000000000..0cad84c07 --- /dev/null +++ b/src/operation/iCTS/test/data_manager/lifecycle/CTSProcessExitTest.cc @@ -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 +// +// iEDA is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// +// See the Mulan PSL v2 for more details. +// *************************************************************************************** +/** + * @file CTSProcessExitTest.cc + * @author Dawn Li (dawnli619215645@gmail.com) + * @date 2026-08-10 + * @brief Process-exit fallback test for CTS static RAII owners. + */ + +#include "Logger.hh" +#include "data_manager/DataManager.hh" + +auto main() -> int +{ + icts::Logger::initInst(); + icts::DataManager::initInst(); + return CTSDM.getDesign().makeInst("process_exit_owned_inst") == nullptr ? 1 : 0; +} diff --git a/src/operation/iCTS/test/main.cc b/src/operation/iCTS/test/main.cc index 88eb46e16..1bf1c2ee7 100644 --- a/src/operation/iCTS/test/main.cc +++ b/src/operation/iCTS/test/main.cc @@ -24,6 +24,7 @@ #include #include +#include #include "Logger.hh" #include "data_manager/DataManager.hh" @@ -31,19 +32,30 @@ namespace icts_test { namespace { +void ResetTestState() +{ + icts::Logger::initInst(); + icts::DataManager::initInst(); + CTSLOG.closeLogFileStream(); + CTSDM.reset(); +} + +void DestroyTestState() +{ + icts::DataManager::destroyInst(); + icts::Logger::destroyInst(); +} + +auto IsLifecycleTest(const ::testing::TestInfo& test_info) -> bool +{ + return std::string_view(test_info.test_suite_name()) == "CTSAPILifecycleTest"; +} + class DataManagerTestListener final : public ::testing::EmptyTestEventListener { public: - void OnTestStart([[maybe_unused]] const ::testing::TestInfo& test_info) override - { - CTSLOG.closeLogFileStream(); - CTSDM.reset(); - } - void OnTestEnd([[maybe_unused]] const ::testing::TestInfo& test_info) override - { - CTSLOG.closeLogFileStream(); - CTSDM.reset(); - } + void OnTestStart(const ::testing::TestInfo& test_info) override { IsLifecycleTest(test_info) ? DestroyTestState() : ResetTestState(); } + void OnTestEnd(const ::testing::TestInfo& test_info) override { IsLifecycleTest(test_info) ? DestroyTestState() : ResetTestState(); } }; } // namespace @@ -52,8 +64,6 @@ class DataManagerTestListener final : public ::testing::EmptyTestEventListener auto main(int argc, char** argv) -> int { ::testing::InitGoogleTest(&argc, argv); - icts::Logger::initInst(); - icts::DataManager::initInst(); auto& listeners = ::testing::UnitTest::GetInstance()->listeners(); auto data_manager_listener = std::make_unique(); @@ -61,7 +71,6 @@ auto main(int argc, char** argv) -> int const int result = RUN_ALL_TESTS(); (void) listeners.Release(data_manager_listener.get()); - icts::DataManager::destroyInst(); - icts::Logger::destroyInst(); + icts_test::DestroyTestState(); return result; } diff --git a/src/operation/iCTS/test/toolkit/CMakeLists.txt b/src/operation/iCTS/test/toolkit/CMakeLists.txt index 01e5e9a11..3108410ec 100644 --- a/src/operation/iCTS/test/toolkit/CMakeLists.txt +++ b/src/operation/iCTS/test/toolkit/CMakeLists.txt @@ -1,4 +1,5 @@ add_subdirectory(logger) add_subdirectory(monitor) +add_subdirectory(utility) add_subdirectory(io) add_subdirectory(visualization) diff --git a/src/operation/iCTS/test/toolkit/utility/CMakeLists.txt b/src/operation/iCTS/test/toolkit/utility/CMakeLists.txt new file mode 100644 index 000000000..e3346e382 --- /dev/null +++ b/src/operation/iCTS/test/toolkit/utility/CMakeLists.txt @@ -0,0 +1,5 @@ +icts_add_test_executable( + icts_test_toolkit_utility + SOURCES + ${ICTS_TEST}/toolkit/utility/UtilityTest.cc +) diff --git a/src/operation/iCTS/test/toolkit/utility/UtilityTest.cc b/src/operation/iCTS/test/toolkit/utility/UtilityTest.cc new file mode 100644 index 000000000..8faadc659 --- /dev/null +++ b/src/operation/iCTS/test/toolkit/utility/UtilityTest.cc @@ -0,0 +1,57 @@ +// *************************************************************************************** +// Copyright (c) 2023-2025 Peng Cheng Laboratory +// Copyright (c) 2023-2025 Institute of Computing Technology, Chinese Academy of Sciences +// Copyright (c) 2023-2025 Beijing Institute of Open Source Chip +// +// iEDA is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// +// See the Mulan PSL v2 for more details. +// *************************************************************************************** +/** + * @file UtilityTest.cc + * @author Dawn Li (dawnli619215645@gmail.com) + * @date 2026-08-10 + * @brief Tests for CTS process-memory sampling and allocator release evidence. + */ + +#include + +#include "Utility.hh" + +namespace icts_test { +namespace { + +TEST(UtilityTest, CurrentRssReportsPlatformAvailability) +{ + const auto rss_mb = icts::Utility::currentRssMb(); +#ifdef __linux__ + ASSERT_TRUE(rss_mb.has_value()); + EXPECT_GT(rss_mb.value_or(0.0), 0.0); +#else + EXPECT_FALSE(rss_mb.has_value()); +#endif +} + +TEST(UtilityTest, ReleaseMemoryReportsEvidenceWithoutRequiringRssReduction) +{ + const auto stats = icts::Utility::releaseMemory(); +#if defined(__linux__) && defined(__GLIBC__) + EXPECT_TRUE(stats.supported); + EXPECT_GT(stats.rss_before_mb, 0.0); + EXPECT_GT(stats.rss_after_mb, 0.0); +#else + EXPECT_FALSE(stats.supported); + EXPECT_DOUBLE_EQ(stats.rss_before_mb, 0.0); + EXPECT_DOUBLE_EQ(stats.rss_after_mb, 0.0); +#endif +} + +} // namespace +} // namespace icts_test diff --git a/src/platform/data_manager/idm_design_net.cpp b/src/platform/data_manager/idm_design_net.cpp index 9e9cd7ab7..4680897a7 100644 --- a/src/platform/data_manager/idm_design_net.cpp +++ b/src/platform/data_manager/idm_design_net.cpp @@ -231,14 +231,7 @@ bool DataManager::disconnectNet(IdbNet* net) return false; } - std::vector pin_list; - auto& io_pins = net->get_io_pins()->get_pin_list(); - auto& inst_pins = net->get_instance_pin_list()->get_pin_list(); - pin_list.insert(pin_list.end(), io_pins.begin(), io_pins.end()); - pin_list.insert(pin_list.end(), inst_pins.begin(), inst_pins.end()); - for (auto* pin : pin_list) { - _design->disconnectPinFromNet(pin); - } + _design->disconnectAllPinsFromNet(net); return true; }