From 24cc6ebd0b83d668def2032705db77b642a8b81b Mon Sep 17 00:00:00 2001 From: xuyipei Date: Mon, 14 Sep 2026 19:55:45 +0800 Subject: [PATCH] feat(iSTA): support generated clocks and common SDC constraints --- src/database/manager/parser/liberty/Lib.hh | 3 + .../manager/parser/liberty/LibParserCpp.cc | 4 + src/operation/iSTA/interface/STAInterface.cpp | 13 ++ .../iSTA/source/data_manager/DataManager.cpp | 5 + .../source/data_manager/advance/Database.hpp | 3 + .../data_manager/advance/TimingCellPort.hpp | 6 + .../data_manager/advance/TimingClock.hpp | 11 ++ .../data_manager/advance/TimingConstraint.hpp | 9 + .../data_manager/advance/TimingException.hpp | 65 ++++++ .../advance/TimingFanoutCheck.hpp | 48 +++++ .../data_manager/advance/TimingPath.hpp | 6 + .../clock_propagator/ClockPropagator.cpp | 29 ++- .../source/module/sdc_command/CMakeLists.txt | 9 + .../source/module/sdc_command/SdcCommand.cpp | 6 + .../sdc_command/sdc_commands/AllPorts.cpp | 64 ++++++ .../sdc_commands/CreateGeneratedClock.cpp | 186 ++++++++++++++++++ .../sdc_commands/CurrentDesign.cpp | 40 ++++ .../sdc_command/sdc_commands/GetClocks.cpp | 34 ++-- .../sdc_command/sdc_commands/GetPins.cpp | 45 +++++ .../sdc_command/sdc_commands/GetPorts.cpp | 35 ++-- .../sdc_commands/RemoveFromCollection.cpp | 73 +++++++ .../sdc_commands/SdcCommandUtils.cpp | 98 +++++++++ .../sdc_commands/SdcCommandUtils.hpp | 12 ++ .../sdc_command/sdc_commands/SdcCommands.hpp | 80 ++++++++ .../sdc_command/sdc_commands/SdcTclCmd.cpp | 19 +- .../sdc_command/sdc_commands/SdcTclCmd.hpp | 10 +- .../sdc_commands/SetClockGroups.cpp | 76 +++++++ .../sdc_commands/SetClockTransition.cpp | 69 +++++++ .../sdc_commands/SetClockUncertainty.cpp | 29 ++- .../sdc_command/sdc_commands/SetFalsePath.cpp | 50 +++++ .../sdc_command/sdc_commands/SetMaxFanout.cpp | 79 ++++++++ .../module/timing_analyzer/TimingAnalyzer.cpp | 133 +++++++++++-- .../module/timing_analyzer/TimingAnalyzer.hpp | 3 + .../timing_propagator/TimingPropagator.cpp | 23 ++- .../module/timing_reporter/TimingReporter.cpp | 92 ++++++--- .../module/timing_reporter/TimingReporter.hpp | 2 + .../iSTA/source/toolkit/utility/Utility.cpp | 121 ++++++++++++ .../iSTA/source/toolkit/utility/Utility.hpp | 12 ++ 38 files changed, 1481 insertions(+), 121 deletions(-) create mode 100644 src/operation/iSTA/source/data_manager/advance/TimingException.hpp create mode 100644 src/operation/iSTA/source/data_manager/advance/TimingFanoutCheck.hpp create mode 100644 src/operation/iSTA/source/module/sdc_command/sdc_commands/AllPorts.cpp create mode 100644 src/operation/iSTA/source/module/sdc_command/sdc_commands/CreateGeneratedClock.cpp create mode 100644 src/operation/iSTA/source/module/sdc_command/sdc_commands/CurrentDesign.cpp create mode 100644 src/operation/iSTA/source/module/sdc_command/sdc_commands/GetPins.cpp create mode 100644 src/operation/iSTA/source/module/sdc_command/sdc_commands/RemoveFromCollection.cpp create mode 100644 src/operation/iSTA/source/module/sdc_command/sdc_commands/SetClockGroups.cpp create mode 100644 src/operation/iSTA/source/module/sdc_command/sdc_commands/SetClockTransition.cpp create mode 100644 src/operation/iSTA/source/module/sdc_command/sdc_commands/SetFalsePath.cpp create mode 100644 src/operation/iSTA/source/module/sdc_command/sdc_commands/SetMaxFanout.cpp diff --git a/src/database/manager/parser/liberty/Lib.hh b/src/database/manager/parser/liberty/Lib.hh index 9ad5aa0dbf..e5bc5384dd 100644 --- a/src/database/manager/parser/liberty/Lib.hh +++ b/src/database/manager/parser/liberty/Lib.hh @@ -606,6 +606,8 @@ class LibPort : public LibObject void set_fanout_load(double fanout_load_val) { _fanout_load = fanout_load_val; } auto& get_fanout_load() { return _fanout_load; } + void set_max_fanout(double max_fanout) { _max_fanout = max_fanout; } + auto& get_max_fanout() { return _max_fanout; } double driveResistance(); @@ -635,6 +637,7 @@ class LibPort : public LibObject std::array, MODE_SPLIT> _slew_limits{}; std::optional _fanout_load; + std::optional _max_fanout; absl::InlinedVector, 64> _internal_powers; //!< The internal power information. diff --git a/src/database/manager/parser/liberty/LibParserCpp.cc b/src/database/manager/parser/liberty/LibParserCpp.cc index 55ea731a87..035f2323f2 100644 --- a/src/database/manager/parser/liberty/LibParserCpp.cc +++ b/src/database/manager/parser/liberty/LibParserCpp.cc @@ -379,6 +379,10 @@ unsigned LibertyReader::visitSimpleAttri(LibertySimpleAttrStmt* attri) { const char* default_wire_load = attri_value_handle->value; current_lib->set_default_wire_load(default_wire_load); liberty_free_string_value(attri_value_handle); + } else if (is_attri("max_fanout")) { + auto* attri_value_handle = liberty_convert_float_value(attri_value); + lib_port->set_max_fanout(attri_value_handle->value); + liberty_free_float_value(attri_value_handle); } else if (is_attri("fanout_load")) { auto* attri_value_handle = liberty_convert_float_value(attri_value); double fanout_load_val = attri_value_handle->value; diff --git a/src/operation/iSTA/interface/STAInterface.cpp b/src/operation/iSTA/interface/STAInterface.cpp index de80631704..3d116b3f80 100644 --- a/src/operation/iSTA/interface/STAInterface.cpp +++ b/src/operation/iSTA/interface/STAInterface.cpp @@ -91,6 +91,10 @@ void STAInterface::initSTA(std::map config_map) STADM.input(config_map); DelayCalculator::initInst(); SdcCommand::initInst({ + {"current_design", sdc::executeTclCommand}, + {"remove_from_collection", sdc::executeTclCommand}, + {"set_clock_transition", sdc::executeTclCommand}, + {"set_max_fanout", sdc::executeTclCommand}, {"set_case_analysis", sdc::executeTclCommand}, {"set_input_delay", sdc::executeTclCommand}, {"set_output_delay", sdc::executeTclCommand}, @@ -102,6 +106,12 @@ void STAInterface::initSTA(std::map config_map) {"get_port", sdc::executeTclCommand}, {"get_ports", sdc::executeTclCommand}, {"create_clock", sdc::executeTclCommand}, + {"create_generated_clock", sdc::executeTclCommand}, + {"set_clock_groups", sdc::executeTclCommand}, + {"set_false_path", sdc::executeTclCommand}, + {"get_pins", sdc::executeTclCommand}, + {"all_inputs", sdc::executeTclCommand}, + {"all_outputs", sdc::executeTclCommand}, {"set_propagated_clock", sdc::executeTclCommand}, }); STADM.readConstraint(); @@ -698,6 +708,9 @@ void STAInterface::wrapTimingCellPort(TimingCell& timing_cell, idb::LibPort* lib timing_cell_port.set_port_name(lib_port->get_port_name()); timing_cell_port.set_capacitance(lib_port->get_port_cap()); timing_cell_port.set_drive_resistance(lib_port->driveResistance()); + idb::LibLibrary* library = lib_port->get_ower_cell()->get_owner_lib(); + timing_cell_port.set_fanout_load(lib_port->get_fanout_load().value_or(library->get_default_fanout_load().value_or(0.0))); + timing_cell_port.set_max_fanout(lib_port->get_max_fanout() ? lib_port->get_max_fanout() : library->get_default_max_fanout()); for (idb::AnalysisMode analysis_mode : {idb::AnalysisMode::kMax, idb::AnalysisMode::kMin}) { for (idb::TransType trans_type : {idb::TransType::kRise, idb::TransType::kFall}) { std::optional port_cap = lib_port->get_port_cap(analysis_mode, trans_type); diff --git a/src/operation/iSTA/source/data_manager/DataManager.cpp b/src/operation/iSTA/source/data_manager/DataManager.cpp index a9a24bedff..656280b6d1 100644 --- a/src/operation/iSTA/source/data_manager/DataManager.cpp +++ b/src/operation/iSTA/source/data_manager/DataManager.cpp @@ -309,6 +309,10 @@ void DataManager::readConstraint() database.get_timing_constraint().get_clock_map().clear(); database.get_timing_constraint().get_port_constraint_map().clear(); database.get_timing_constraint().get_case_analysis_map().clear(); + database.get_timing_constraint().get_false_path_list().clear(); + database.get_timing_constraint().get_clock_group_list().clear(); + database.get_timing_constraint().get_max_fanout().reset(); + database.get_timing_constraint().get_port_max_fanout_map().clear(); if (sdc_file_path.empty()) { return; } @@ -318,6 +322,7 @@ void DataManager::readConstraint() for (const SdcError& error : sdc_command.getErrors()) { STALOG.warn(Loc::current(), "SDC command failed in '", sdc_file_path, "' at line ", error.line_number, ": ", error.message); } + STALOG.error(Loc::current(), "SDC contains invalid or unsupported constraints; timing analysis stopped"); } } diff --git a/src/operation/iSTA/source/data_manager/advance/Database.hpp b/src/operation/iSTA/source/data_manager/advance/Database.hpp index 4f85aad3bc..00311c9cee 100644 --- a/src/operation/iSTA/source/data_manager/advance/Database.hpp +++ b/src/operation/iSTA/source/data_manager/advance/Database.hpp @@ -27,6 +27,7 @@ #include "STAHeader.hpp" #include "Summary.hpp" #include "TimingConstraint.hpp" +#include "TimingFanoutCheck.hpp" #include "TimingLibrary.hpp" #include "TimingPathGroup.hpp" #include "TimingPoint.hpp" @@ -59,6 +60,7 @@ class Database ParasiticLibrary& get_parasitic_library() { return _parasitic_library; } TimingConstraint& get_timing_constraint() { return _timing_constraint; } Summary& get_summary() { return _summary; } + std::vector& get_fanout_check_list() { return _fanout_check_list; } // setter void set_design_name(const std::string& design_name) { _design_name = design_name; } // function @@ -76,6 +78,7 @@ class Database std::vector _timing_order_list; std::map _timing_point_map; std::vector _timing_path_group_list; + std::vector _fanout_check_list; std::map _vcd_activity_map; std::map _power_activity_map; std::map _instance_power_map; diff --git a/src/operation/iSTA/source/data_manager/advance/TimingCellPort.hpp b/src/operation/iSTA/source/data_manager/advance/TimingCellPort.hpp index c0b83bb03a..2df9c1fb13 100644 --- a/src/operation/iSTA/source/data_manager/advance/TimingCellPort.hpp +++ b/src/operation/iSTA/source/data_manager/advance/TimingCellPort.hpp @@ -37,6 +37,8 @@ class TimingCellPort bool get_is_input() const { return _is_input; } bool get_is_output() const { return _is_output; } bool get_is_clock() const { return _is_clock; } + double get_fanout_load() const { return _fanout_load; } + std::optional& get_max_fanout() { return _max_fanout; } // setter void set_port_name(const std::string& port_name) { _port_name = port_name; } void set_capacitance(const double capacitance) { _capacitance = capacitance; } @@ -49,6 +51,8 @@ class TimingCellPort void set_is_input(const bool is_input) { _is_input = is_input; } void set_is_output(const bool is_output) { _is_output = is_output; } void set_is_clock(const bool is_clock) { _is_clock = is_clock; } + void set_fanout_load(double fanout_load) { _fanout_load = fanout_load; } + void set_max_fanout(const std::optional& max_fanout) { _max_fanout = max_fanout; } // function private: @@ -60,6 +64,8 @@ class TimingCellPort bool _is_input = false; bool _is_output = false; bool _is_clock = false; + double _fanout_load = 0.0; + std::optional _max_fanout; }; } // namespace ista diff --git a/src/operation/iSTA/source/data_manager/advance/TimingClock.hpp b/src/operation/iSTA/source/data_manager/advance/TimingClock.hpp index d74c622c71..cc4fecf280 100644 --- a/src/operation/iSTA/source/data_manager/advance/TimingClock.hpp +++ b/src/operation/iSTA/source/data_manager/advance/TimingClock.hpp @@ -16,7 +16,9 @@ // *************************************************************************************** #pragma once +#include "AnalysisType.hpp" #include "STAHeader.hpp" +#include "TransType.hpp" namespace ista { @@ -34,6 +36,10 @@ class TimingClock double get_setup_uncertainty() const { return _setup_uncertainty; } double get_hold_uncertainty() const { return _hold_uncertainty; } bool get_is_propagated() const { return _is_propagated; } + const std::string& get_master_clock_name() const { return _master_clock_name; } + const std::string& get_master_source() const { return _master_source; } + bool get_is_generated() const { return !_master_clock_name.empty(); } + std::map>& get_transition_map() { return _transition_map; } // setter void set_clock_name(const std::string& clock_name) { _clock_name = clock_name; } void set_source_list(const std::vector& source_list) { _source_list = source_list; } @@ -43,9 +49,13 @@ class TimingClock void set_setup_uncertainty(const double uncertainty) { _setup_uncertainty = uncertainty; } void set_hold_uncertainty(const double uncertainty) { _hold_uncertainty = uncertainty; } void set_is_propagated(const bool is_propagated) { _is_propagated = is_propagated; } + void set_master_clock_name(const std::string& name) { _master_clock_name = name; } + void set_master_source(const std::string& source) { _master_source = source; } // function private: + std::string _master_clock_name; + std::string _master_source; std::string _clock_name; std::vector _source_list; double _period = 0.0; @@ -54,6 +64,7 @@ class TimingClock double _setup_uncertainty = 0.0; double _hold_uncertainty = 0.0; bool _is_propagated = false; + std::map> _transition_map; }; } // namespace ista diff --git a/src/operation/iSTA/source/data_manager/advance/TimingConstraint.hpp b/src/operation/iSTA/source/data_manager/advance/TimingConstraint.hpp index 3518e23b32..011f09755b 100644 --- a/src/operation/iSTA/source/data_manager/advance/TimingConstraint.hpp +++ b/src/operation/iSTA/source/data_manager/advance/TimingConstraint.hpp @@ -18,6 +18,7 @@ #include "STAHeader.hpp" #include "TimingClock.hpp" +#include "TimingException.hpp" #include "TimingPortConstraint.hpp" namespace ista { @@ -32,6 +33,10 @@ class TimingConstraint std::map& get_clock_map() { return _clock_map; } std::map& get_port_constraint_map() { return _port_constraint_map; } std::map& get_case_analysis_map() { return _case_analysis_map; } + std::vector& get_false_path_list() { return _false_path_list; } + std::vector& get_clock_group_list() { return _clock_group_list; } + std::optional& get_max_fanout() { return _max_fanout; } + std::map& get_port_max_fanout_map() { return _port_max_fanout_map; } // setter void set_sdc_file_path(const std::string& sdc_file_path) { _sdc_file_path = sdc_file_path; } void set_clock_map(const std::map& clock_map) { _clock_map = clock_map; } @@ -40,10 +45,14 @@ class TimingConstraint // function private: + std::vector _false_path_list; + std::vector _clock_group_list; std::string _sdc_file_path; std::map _clock_map; std::map _port_constraint_map; std::map _case_analysis_map; + std::optional _max_fanout; + std::map _port_max_fanout_map; }; } // namespace ista diff --git a/src/operation/iSTA/source/data_manager/advance/TimingException.hpp b/src/operation/iSTA/source/data_manager/advance/TimingException.hpp new file mode 100644 index 0000000000..d8f69e55c3 --- /dev/null +++ b/src/operation/iSTA/source/data_manager/advance/TimingException.hpp @@ -0,0 +1,65 @@ +// *************************************************************************************** +// 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. +// *************************************************************************************** +#pragma once + +#include "STAHeader.hpp" + +namespace ista { + +class TimingException +{ + public: + TimingException() = default; + ~TimingException() = default; + // getter + const std::set& get_from_objects() const { return _from_objects; } + const std::set& get_to_objects() const { return _to_objects; } + bool get_setup() const { return _setup; } + bool get_hold() const { return _hold; } + // setter + void set_from_objects(const std::set& objects) { _from_objects = objects; } + void set_to_objects(const std::set& objects) { _to_objects = objects; } + void set_setup(bool value) { _setup = value; } + void set_hold(bool value) { _hold = value; } + // function + + private: + std::set _from_objects; + std::set _to_objects; + bool _setup = true; + bool _hold = true; +}; + +class TimingClockGroup +{ + public: + TimingClockGroup() = default; + ~TimingClockGroup() = default; + // getter + const std::vector>& get_groups() const { return _groups; } + bool get_allow_paths() const { return _allow_paths; } + // setter + void set_groups(const std::vector>& groups) { _groups = groups; } + void set_allow_paths(bool value) { _allow_paths = value; } + // function + + private: + std::vector> _groups; + bool _allow_paths = false; +}; + +} // namespace ista diff --git a/src/operation/iSTA/source/data_manager/advance/TimingFanoutCheck.hpp b/src/operation/iSTA/source/data_manager/advance/TimingFanoutCheck.hpp new file mode 100644 index 0000000000..62549126e2 --- /dev/null +++ b/src/operation/iSTA/source/data_manager/advance/TimingFanoutCheck.hpp @@ -0,0 +1,48 @@ +// *************************************************************************************** +// 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. +// *************************************************************************************** +#pragma once + +#include "STAHeader.hpp" + +namespace ista { + +class TimingFanoutCheck +{ + public: + TimingFanoutCheck() = default; + ~TimingFanoutCheck() = default; + // getter + const std::string& get_net_name() const { return _net_name; } + const std::string& get_driver_pin() const { return _driver_pin; } + double get_fanout_load() const { return _fanout_load; } + double get_limit() const { return _limit; } + double get_slack() const { return _limit - _fanout_load; } + // setter + void set_net_name(const std::string& net_name) { _net_name = net_name; } + void set_driver_pin(const std::string& driver_pin) { _driver_pin = driver_pin; } + void set_fanout_load(double fanout_load) { _fanout_load = fanout_load; } + void set_limit(double limit) { _limit = limit; } + // function + + private: + std::string _net_name; + std::string _driver_pin; + double _fanout_load = 0.0; + double _limit = 0.0; +}; + +} // namespace ista diff --git a/src/operation/iSTA/source/data_manager/advance/TimingPath.hpp b/src/operation/iSTA/source/data_manager/advance/TimingPath.hpp index 557e169db7..bd0801e1d2 100644 --- a/src/operation/iSTA/source/data_manager/advance/TimingPath.hpp +++ b/src/operation/iSTA/source/data_manager/advance/TimingPath.hpp @@ -51,6 +51,8 @@ class TimingPath TimingCheckType get_check_type() const { return _check_type; } TransType get_trans_type() const { return _trans_type; } std::string& get_clock_name() { return _clock_name; } + std::string& get_capture_clock_name() { return _capture_clock_name; } + TransType get_capture_clock_transition() const { return _capture_clock_transition; } std::string& get_capture_clock_pin() { return _capture_clock_pin; } std::string& get_last_common_pin() { return _last_common_pin; } std::vector& get_point_list() { return _point_list; } @@ -75,6 +77,8 @@ class TimingPath void set_check_type(const TimingCheckType& check_type) { _check_type = check_type; } void set_trans_type(const TransType& trans_type) { _trans_type = trans_type; } void set_clock_name(const std::string& clock_name) { _clock_name = clock_name; } + void set_capture_clock_name(const std::string_view name) { _capture_clock_name = name; } + void set_capture_clock_transition(TransType transition) { _capture_clock_transition = transition; } void set_capture_clock_pin(const std::string& capture_clock_pin) { _capture_clock_pin = capture_clock_pin; } void set_last_common_pin(const std::string& last_common_pin) { _last_common_pin = last_common_pin; } void set_point_list(const std::vector& point_list) { _point_list = point_list; } @@ -101,6 +105,8 @@ class TimingPath TimingCheckType _check_type = TimingCheckType::kNone; TransType _trans_type = TransType::kNone; std::string _clock_name; + std::string _capture_clock_name; + TransType _capture_clock_transition = TransType::kRise; std::string _capture_clock_pin; std::string _last_common_pin; std::vector _point_list; diff --git a/src/operation/iSTA/source/module/clock_propagator/ClockPropagator.cpp b/src/operation/iSTA/source/module/clock_propagator/ClockPropagator.cpp index 45795c5735..d6c16b15ab 100644 --- a/src/operation/iSTA/source/module/clock_propagator/ClockPropagator.cpp +++ b/src/operation/iSTA/source/module/clock_propagator/ClockPropagator.cpp @@ -58,6 +58,13 @@ void ClockPropagator::propagate() CPModel cp_model = initCPModel(); buildClockSourceList(cp_model); markClockPointList(cp_model); + for (CPClock& clock : cp_model.clock_list) { + TimingClock& definition = STADM.getDatabase().get_timing_constraint().get_clock_map().at(std::string(clock.get_clock_name())); + if (definition.get_is_generated() && clock.get_is_propagated() && !clock.get_clock_point_list().empty()) { + STALOG.error(Loc::current(), "propagated generated clock '", clock.get_clock_name(), + "' requires master-to-target insertion delay support; this analysis currently supports ideal generated clocks"); + } + } propagateClockArrival(cp_model); STALOG.info(Loc::current(), "Completed", monitor.getStatsInfo()); } @@ -135,6 +142,23 @@ void ClockPropagator::markClockPoint(CPClock& clock) std::string pin_name = pin_queue.front(); pin_queue.pop(); + Pin& pin = database.get_pin_map().at(pin_name); + // Multiple exported clock modes have no internal data fanout to analyze. + if (pin.get_is_port() && pin.get_direction() == PinDirection::kOutput && database.get_outgoing_arc_list_map()[pin_name].empty()) { + continue; + } + bool another_root = false; + for (auto& [name, definition] : database.get_timing_constraint().get_clock_map()) { + if (name != clock.get_clock_name() + && std::find(definition.get_source_list().begin(), definition.get_source_list().end(), pin_name) != definition.get_source_list().end() + && std::find(clock.get_source_list().begin(), clock.get_source_list().end(), pin_name) == clock.get_source_list().end()) { + another_root = true; + break; + } + } + if (another_root) { + continue; + } TimingPoint& timing_point = database.get_timing_point_map()[pin_name]; if (is_clock_tree_overlap(timing_point, clock)) { STALOG.error(Loc::current(), "clock trees overlap at pin '", pin_name, "': '", timing_point.get_clock_name(), "' and '", clock.get_clock_name(), "'"); @@ -205,6 +229,7 @@ void ClockPropagator::seedPhysicalClockState(CPClock& clock) void ClockPropagator::updateEffectiveClockState(CPClock& clock) { Database& database = STADM.getDatabase(); + TimingClock& definition = database.get_timing_constraint().get_clock_map().at(std::string(clock.get_clock_name())); for (const auto& pin_name : clock.get_clock_point_list()) { TimingPoint& timing_point = database.get_timing_point_map()[pin_name]; if (clock.get_is_propagated()) { @@ -219,7 +244,9 @@ void ClockPropagator::updateEffectiveClockState(CPClock& clock) for (AnalysisType analysis_type : {AnalysisType::kMax, AnalysisType::kMin}) { for (TransType trans_type : {TransType::kRise, TransType::kFall}) { timing_point.get_clock_arrival_map()[analysis_type][trans_type] = 0.0; - timing_point.get_clock_slew_map()[analysis_type][trans_type] = 0.0; + const auto mode = definition.get_transition_map().find(analysis_type); + timing_point.get_clock_slew_map()[analysis_type][trans_type] + = mode != definition.get_transition_map().end() && mode->second.contains(trans_type) ? mode->second.at(trans_type) : 0.0; } } } diff --git a/src/operation/iSTA/source/module/sdc_command/CMakeLists.txt b/src/operation/iSTA/source/module/sdc_command/CMakeLists.txt index 048fd13b19..44d731c4df 100644 --- a/src/operation/iSTA/source/module/sdc_command/CMakeLists.txt +++ b/src/operation/iSTA/source/module/sdc_command/CMakeLists.txt @@ -1,4 +1,13 @@ add_library(ista_sdc_command + ${ISTA_MODULE}/sdc_command/sdc_commands/CurrentDesign.cpp + ${ISTA_MODULE}/sdc_command/sdc_commands/RemoveFromCollection.cpp + ${ISTA_MODULE}/sdc_command/sdc_commands/SetClockTransition.cpp + ${ISTA_MODULE}/sdc_command/sdc_commands/SetMaxFanout.cpp + ${ISTA_MODULE}/sdc_command/sdc_commands/CreateGeneratedClock.cpp + ${ISTA_MODULE}/sdc_command/sdc_commands/GetPins.cpp + ${ISTA_MODULE}/sdc_command/sdc_commands/AllPorts.cpp + ${ISTA_MODULE}/sdc_command/sdc_commands/SetClockGroups.cpp + ${ISTA_MODULE}/sdc_command/sdc_commands/SetFalsePath.cpp ${ISTA_MODULE}/sdc_command/SdcCommand.cpp ${ISTA_MODULE}/sdc_command/sdc_commands/SdcTclCmd.cpp ${ISTA_MODULE}/sdc_command/sdc_commands/SdcCommandUtils.cpp diff --git a/src/operation/iSTA/source/module/sdc_command/SdcCommand.cpp b/src/operation/iSTA/source/module/sdc_command/SdcCommand.cpp index f767f26d0f..10f8f73d06 100644 --- a/src/operation/iSTA/source/module/sdc_command/SdcCommand.cpp +++ b/src/operation/iSTA/source/module/sdc_command/SdcCommand.cpp @@ -43,6 +43,12 @@ void SdcCommand::destroyInst() SdcCommand::SdcCommand() { _interp = Tcl_CreateInterp(); + if (Tcl_Init(_interp) != TCL_OK) { + const std::string message = Tcl_GetStringResult(_interp); + Tcl_DeleteInterp(_interp); + _interp = nullptr; + throw std::runtime_error("failed to initialize SDC Tcl interpreter: " + message); + } } SdcCommand::~SdcCommand() diff --git a/src/operation/iSTA/source/module/sdc_command/sdc_commands/AllPorts.cpp b/src/operation/iSTA/source/module/sdc_command/sdc_commands/AllPorts.cpp new file mode 100644 index 0000000000..96f7b4afc1 --- /dev/null +++ b/src/operation/iSTA/source/module/sdc_command/sdc_commands/AllPorts.cpp @@ -0,0 +1,64 @@ +// *************************************************************************************** +// Copyright (c) 2023-2025 Peng Cheng Laboratory +// Copyright (c) 2023-2025 Institute of Computing Technology, Chinese Academy of Sciences +// Copyright (c) 2023-2025 Beijing Institute of Open Source Chip +// +// iEDA is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// +// See the Mulan PSL v2 for more details. +// *************************************************************************************** +#include "DataManager.hpp" +#include "SdcCommands.hpp" + +namespace ista::sdc { + +TclAllInputs::TclAllInputs(const char* cmd_name, ClientData client_data) : SdcTclCmd(cmd_name, client_data) +{ + addOption(new ecc::TclSwitchOption("-no_clocks")); +} + +unsigned TclAllInputs::exec() +{ + Database& database = STADM.getDatabase(); + std::set sources; + if (getOptionOrArg("-no_clocks")->is_set_val()) { + for (auto& [name, clock] : database.get_timing_constraint().get_clock_map()) { + sources.insert(clock.get_source_list().begin(), clock.get_source_list().end()); + } + } + std::vector ports; + for (auto& [name, pin] : database.get_pin_map()) { + if (pin.get_is_port() && !sources.contains(name) && (pin.get_direction() == PinDirection::kInput || pin.get_direction() == PinDirection::kInout)) { + ports.push_back(name); + } + } + std::sort(ports.begin(), ports.end()); + setResult(std::move(ports)); + return 1; +} + +TclAllOutputs::TclAllOutputs(const char* cmd_name, ClientData client_data) : SdcTclCmd(cmd_name, client_data) +{ +} + +unsigned TclAllOutputs::exec() +{ + std::vector ports; + for (auto& [name, pin] : STADM.getDatabase().get_pin_map()) { + if (pin.get_is_port() && (pin.get_direction() == PinDirection::kOutput || pin.get_direction() == PinDirection::kInout)) { + ports.push_back(name); + } + } + std::sort(ports.begin(), ports.end()); + setResult(std::move(ports)); + return 1; +} + +} // namespace ista::sdc diff --git a/src/operation/iSTA/source/module/sdc_command/sdc_commands/CreateGeneratedClock.cpp b/src/operation/iSTA/source/module/sdc_command/sdc_commands/CreateGeneratedClock.cpp new file mode 100644 index 0000000000..3796106aa0 --- /dev/null +++ b/src/operation/iSTA/source/module/sdc_command/sdc_commands/CreateGeneratedClock.cpp @@ -0,0 +1,186 @@ +// *************************************************************************************** +// Copyright (c) 2023-2025 Peng Cheng Laboratory +// Copyright (c) 2023-2025 Institute of Computing Technology, Chinese Academy of Sciences +// Copyright (c) 2023-2025 Beijing Institute of Open Source Chip +// +// iEDA is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// +// See the Mulan PSL v2 for more details. +// *************************************************************************************** +#include "DataManager.hpp" +#include "SdcCommandUtils.hpp" +#include "SdcCommands.hpp" + +namespace ista::sdc { + +TclCreateGeneratedClock::TclCreateGeneratedClock(const char* cmd_name, ClientData client_data) : SdcTclCmd(cmd_name, client_data) +{ + for (const char* option : {"-name", "-master_clock", "-comment"}) { + addOption(new ecc::TclStringOption(option, 0)); + } + addOption(new ecc::TclStringListOption("-source", 0)); + addOption(new ecc::TclStringListOption("objects", 1)); + for (const char* option : {"-divide_by", "-multiply_by", "-duty_cycle"}) { + addOption(new ecc::TclDoubleOption(option, 0)); + } + for (const char* option : {"-edges", "-edge_shift"}) { + addOption(new ecc::TclDoubleListOption(option, 0)); + } + for (const char* option : {"-add", "-invert", "-combinational"}) { + addOption(new ecc::TclSwitchOption(option)); + } +} + +unsigned TclCreateGeneratedClock::exec() +{ + Database& database = STADM.getDatabase(); + auto& clocks = database.get_timing_constraint().get_clock_map(); + if (!getOptionOrArg("-source")->is_set_val() || !getOptionOrArg("objects")->is_set_val()) { + setTclError("create_generated_clock requires -source and target pins/ports"); + return 0; + } + const std::vector sources = resolveObjectList(database, getOptionOrArg("-source")->getStringList()); + const std::vector targets = resolveObjectList(database, getOptionOrArg("objects")->getStringList()); + if (sources.size() != 1 || targets.empty()) { + setTclError("invalid generated clock source or targets"); + return 0; + } + if (sources.size() != getOptionOrArg("-source")->getStringList().size() || targets.size() != getOptionOrArg("objects")->getStringList().size()) { + setTclError("generated clock collection contains an unknown pin or port"); + return 0; + } + const std::string name = getOptionOrArg("-name")->is_set_val() ? getOptionOrArg("-name")->getStringVal() : targets.front(); + std::string master_name; + if (getOptionOrArg("-master_clock")->is_set_val()) { + master_name = getOptionOrArg("-master_clock")->getStringVal(); + } else { + for (auto& [candidate, clock] : clocks) { + if (std::find(clock.get_source_list().begin(), clock.get_source_list().end(), sources.front()) != clock.get_source_list().end()) { + if (!master_name.empty()) { + setTclError("ambiguous master; specify -master_clock"); + return 0; + } + master_name = candidate; + } + } + if (master_name.empty() && clocks.size() == 1) { + master_name = clocks.begin()->first; + } + } + if (name.empty() || !clocks.contains(master_name)) { + setTclError("generated clock master does not exist"); + return 0; + } + std::set ancestors{name}; + for (std::string ancestor = master_name; !ancestor.empty();) { + if (!ancestors.insert(ancestor).second) { + setTclError("generated clock dependency cycle"); + return 0; + } + ancestor = clocks.at(ancestor).get_master_clock_name(); + } + TimingClock& master = clocks.at(master_name); + int transformations = 0; + for (const char* option : {"-divide_by", "-multiply_by", "-edges", "-combinational"}) { + transformations += getOptionOrArg(option)->is_set_val(); + } + if (transformations > 1) { + setTclError("generated clock transformations are mutually exclusive"); + return 0; + } + double period = master.get_period(); + double rise = master.get_rise_edge(); + double fall = master.get_fall_edge(); + for (const char* option : {"-divide_by", "-multiply_by"}) { + if (!getOptionOrArg(option)->is_set_val()) { + continue; + } + const double factor = getOptionOrArg(option)->getDoubleVal(); + if (!std::isfinite(factor) || factor < 1 || factor != std::floor(factor)) { + setTclError("generated clock factor must be a positive integer"); + return 0; + } + if (std::string_view(option) == "-multiply_by") { + period /= factor; + rise /= factor; + fall /= factor; + } else { + period *= factor; + const bool power_of_two = std::floor(std::log2(factor)) == std::log2(factor); + if (factor > 1 && power_of_two) { + fall = rise + period / 2; + } else { + rise *= factor; + fall *= factor; + } + } + } + if (getOptionOrArg("-edges")->is_set_val()) { + const std::vector edges = getOptionOrArg("-edges")->getDoubleList(); + const std::vector shifts + = getOptionOrArg("-edge_shift")->is_set_val() ? getOptionOrArg("-edge_shift")->getDoubleList() : std::vector(3, 0.0); + if (edges.size() != 3 || shifts.size() != 3) { + setTclError("-edges and -edge_shift require three values"); + return 0; + } + std::vector times; + for (std::size_t i = 0; i < 3; ++i) { + if (!std::isfinite(edges[i]) || edges[i] < 1 || edges[i] != std::floor(edges[i]) || (i && edges[i] <= edges[i - 1])) { + setTclError("-edges requires three increasing positive integers"); + return 0; + } + const double index = edges[i] - 1; + times.push_back(std::floor(index / 2) * master.get_period() + (std::fmod(index, 2) == 0 ? master.get_rise_edge() : master.get_fall_edge()) + shifts[i]); + } + period = times[2] - times[0]; + rise = times[0]; + fall = times[1]; + } else if (getOptionOrArg("-edge_shift")->is_set_val()) { + setTclError("-edge_shift requires -edges"); + return 0; + } + if (getOptionOrArg("-duty_cycle")->is_set_val()) { + const double duty = getOptionOrArg("-duty_cycle")->getDoubleVal(); + if (!getOptionOrArg("-multiply_by")->is_set_val() || !std::isfinite(duty) || duty <= 0 || duty >= 100) { + setTclError("-duty_cycle requires -multiply_by and a percentage between 0 and 100"); + return 0; + } + fall = rise + period * duty / 100; + } + if (getOptionOrArg("-invert")->is_set_val()) { + const double old_rise = rise; + rise = fall; + fall = old_rise + period; + } + if (!std::isfinite(period) || !std::isfinite(rise) || !std::isfinite(fall) || period <= 0 || fall <= rise || fall >= rise + period) { + setTclError("invalid generated clock waveform"); + return 0; + } + TimingClock generated; + generated.set_clock_name(name); + generated.set_master_clock_name(master_name); + generated.set_master_source(sources.front()); + generated.set_source_list(targets); + generated.set_period(period); + generated.set_rise_edge(rise); + generated.set_fall_edge(fall); + if (!getOptionOrArg("-add")->is_set_val()) { + for (auto& [other_name, other] : clocks) { + std::vector& roots = other.get_source_list(); + roots.erase( + std::remove_if(roots.begin(), roots.end(), [&](const std::string& pin) { return std::find(targets.begin(), targets.end(), pin) != targets.end(); }), + roots.end()); + } + } + clocks[name] = std::move(generated); + return 1; +} + +} // namespace ista::sdc diff --git a/src/operation/iSTA/source/module/sdc_command/sdc_commands/CurrentDesign.cpp b/src/operation/iSTA/source/module/sdc_command/sdc_commands/CurrentDesign.cpp new file mode 100644 index 0000000000..74935b2ddc --- /dev/null +++ b/src/operation/iSTA/source/module/sdc_command/sdc_commands/CurrentDesign.cpp @@ -0,0 +1,40 @@ +// *************************************************************************************** +// Copyright (c) 2023-2025 Peng Cheng Laboratory +// Copyright (c) 2023-2025 Institute of Computing Technology, Chinese Academy of Sciences +// Copyright (c) 2023-2025 Beijing Institute of Open Source Chip +// +// iEDA is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// +// See the Mulan PSL v2 for more details. +// *************************************************************************************** +#include "DataManager.hpp" +#include "SdcCommandUtils.hpp" +#include "SdcCommands.hpp" + +namespace ista::sdc { + +TclCurrentDesign::TclCurrentDesign(const char* cmd_name, ClientData client_data) : SdcTclCmd(cmd_name, client_data) +{ + addOption(new ecc::TclStringOption("design", 1)); +} + +unsigned TclCurrentDesign::exec() +{ + const std::string& design = STADM.getDatabase().get_design_name(); + ecc::TclOption* option = getOptionOrArg("design"); + if (option->is_set_val() && (design.empty() || design != option->getStringVal())) { + setTclError("current_design can only select the loaded design '" + design + "'"); + return 0; + } + setResult(design.empty() ? std::vector{} : std::vector{design}); + return 1; +} + +} // namespace ista::sdc diff --git a/src/operation/iSTA/source/module/sdc_command/sdc_commands/GetClocks.cpp b/src/operation/iSTA/source/module/sdc_command/sdc_commands/GetClocks.cpp index 3cceb702c8..b48d133074 100644 --- a/src/operation/iSTA/source/module/sdc_command/sdc_commands/GetClocks.cpp +++ b/src/operation/iSTA/source/module/sdc_command/sdc_commands/GetClocks.cpp @@ -15,41 +15,29 @@ // See the Mulan PSL v2 for more details. // *************************************************************************************** #include "DataManager.hpp" -#include "Logger.hpp" +#include "SdcCommandUtils.hpp" #include "SdcCommands.hpp" namespace ista::sdc { TclGetClocks::TclGetClocks(const char* cmd_name, ClientData client_data) : SdcTclCmd(cmd_name, client_data) { - addOption(new ecc::TclStringListOption("clocks", 1)); + addOption(new ecc::TclStringOption("clocks", 1)); + addOption(new ecc::TclSwitchOption("-quiet")); + addOption(new ecc::TclSwitchOption("-regexp")); } unsigned TclGetClocks::exec() { - ecc::TclOption* clock_option = getOptionOrArg("clocks"); - if (!clock_option->is_set_val()) { - setTclError("get_clocks requires a clock list"); + ecc::TclOption* objects = getOptionOrArg("clocks"); + const bool regexp = getOptionOrArg("-regexp")->is_set_val(); + const std::string patterns = objects->is_set_val() ? objects->getStringVal() : "*"; + std::vector result = queryObjects(STADM.getDatabase(), queryPatterns(patterns, regexp), QueryObjectType::kClock, regexp); + if (result.empty() && !getOptionOrArg("-quiet")->is_set_val()) { + setTclError("no clocks matched: " + patterns); return 0; } - const std::vector clock_name_list = clock_option->getStringList(); - if (clock_name_list.empty()) { - setTclError("get_clocks requires at least one clock name"); - return 0; - } - - auto& clock_map = STADM.getDatabase().get_timing_constraint().get_clock_map(); - std::vector resolved_clocks; - resolved_clocks.reserve(clock_name_list.size()); - for (const std::string& clock_name : clock_name_list) { - if (!clock_map.contains(clock_name)) { - STALOG.error(Loc::current(), "clock '", clock_name, "' does not exist"); - setTclError("clock does not exist"); - return 0; - } - resolved_clocks.push_back(clock_name); - } - setResult(std::move(resolved_clocks)); + setResult(std::move(result)); return 1; } diff --git a/src/operation/iSTA/source/module/sdc_command/sdc_commands/GetPins.cpp b/src/operation/iSTA/source/module/sdc_command/sdc_commands/GetPins.cpp new file mode 100644 index 0000000000..c10b0d9d0a --- /dev/null +++ b/src/operation/iSTA/source/module/sdc_command/sdc_commands/GetPins.cpp @@ -0,0 +1,45 @@ +// *************************************************************************************** +// Copyright (c) 2023-2025 Peng Cheng Laboratory +// Copyright (c) 2023-2025 Institute of Computing Technology, Chinese Academy of Sciences +// Copyright (c) 2023-2025 Beijing Institute of Open Source Chip +// +// iEDA is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// +// See the Mulan PSL v2 for more details. +// *************************************************************************************** +#include "DataManager.hpp" +#include "SdcCommandUtils.hpp" +#include "SdcCommands.hpp" + +namespace ista::sdc { + +TclGetPins::TclGetPins(const char* cmd_name, ClientData client_data) : SdcTclCmd(cmd_name, client_data) +{ + addOption(new ecc::TclStringOption("pins", 1)); + addOption(new ecc::TclSwitchOption("-quiet")); + addOption(new ecc::TclSwitchOption("-regexp")); + addOption(new ecc::TclSwitchOption("-hierarchical")); +} + +unsigned TclGetPins::exec() +{ + ecc::TclOption* objects = getOptionOrArg("pins"); + const bool regexp = getOptionOrArg("-regexp")->is_set_val(); + const std::string patterns = objects->is_set_val() ? objects->getStringVal() : "*"; + std::vector result = queryObjects(STADM.getDatabase(), queryPatterns(patterns, regexp), QueryObjectType::kPin, regexp); + if (result.empty() && !getOptionOrArg("-quiet")->is_set_val()) { + setTclError("no pins matched: " + patterns); + return 0; + } + setResult(std::move(result)); + return 1; +} + +} // namespace ista::sdc diff --git a/src/operation/iSTA/source/module/sdc_command/sdc_commands/GetPorts.cpp b/src/operation/iSTA/source/module/sdc_command/sdc_commands/GetPorts.cpp index 1ca45c4347..637d5b7360 100644 --- a/src/operation/iSTA/source/module/sdc_command/sdc_commands/GetPorts.cpp +++ b/src/operation/iSTA/source/module/sdc_command/sdc_commands/GetPorts.cpp @@ -15,42 +15,29 @@ // See the Mulan PSL v2 for more details. // *************************************************************************************** #include "DataManager.hpp" -#include "Logger.hpp" +#include "SdcCommandUtils.hpp" #include "SdcCommands.hpp" namespace ista::sdc { TclGetPorts::TclGetPorts(const char* cmd_name, ClientData client_data) : SdcTclCmd(cmd_name, client_data) { - addOption(new ecc::TclStringListOption("ports", 1)); + addOption(new ecc::TclStringOption("ports", 1)); + addOption(new ecc::TclSwitchOption("-quiet")); + addOption(new ecc::TclSwitchOption("-regexp")); } unsigned TclGetPorts::exec() { - ecc::TclOption* port_option = getOptionOrArg("ports"); - if (!port_option->is_set_val()) { - setTclError("get_ports requires a port list"); + ecc::TclOption* objects = getOptionOrArg("ports"); + const bool regexp = getOptionOrArg("-regexp")->is_set_val(); + const std::string patterns = objects->is_set_val() ? objects->getStringVal() : "*"; + std::vector result = queryObjects(STADM.getDatabase(), queryPatterns(patterns, regexp), QueryObjectType::kPort, regexp); + if (result.empty() && !getOptionOrArg("-quiet")->is_set_val()) { + setTclError("no ports matched: " + patterns); return 0; } - const std::vector port_name_list = port_option->getStringList(); - if (port_name_list.empty()) { - setTclError("get_ports requires at least one port name"); - return 0; - } - - Database& database = STADM.getDatabase(); - std::vector resolved_ports; - resolved_ports.reserve(port_name_list.size()); - for (const std::string& port_name : port_name_list) { - const auto pin_it = database.get_pin_map().find(port_name); - if (pin_it == database.get_pin_map().end() || !pin_it->second.get_is_port()) { - STALOG.warn(Loc::current(), "port '", port_name, "' does not exist"); - setTclError("port does not exist"); - return 0; - } - resolved_ports.push_back(port_name); - } - setResult(std::move(resolved_ports)); + setResult(std::move(result)); return 1; } diff --git a/src/operation/iSTA/source/module/sdc_command/sdc_commands/RemoveFromCollection.cpp b/src/operation/iSTA/source/module/sdc_command/sdc_commands/RemoveFromCollection.cpp new file mode 100644 index 0000000000..816698be81 --- /dev/null +++ b/src/operation/iSTA/source/module/sdc_command/sdc_commands/RemoveFromCollection.cpp @@ -0,0 +1,73 @@ +// *************************************************************************************** +// Copyright (c) 2023-2025 Peng Cheng Laboratory +// Copyright (c) 2023-2025 Institute of Computing Technology, Chinese Academy of Sciences +// Copyright (c) 2023-2025 Beijing Institute of Open Source Chip +// +// iEDA is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// +// See the Mulan PSL v2 for more details. +// *************************************************************************************** +#include "DataManager.hpp" +#include "SdcCommandUtils.hpp" +#include "SdcCommands.hpp" + +namespace ista::sdc { + +TclRemoveFromCollection::TclRemoveFromCollection(const char* cmd_name, ClientData client_data) : SdcTclCmd(cmd_name, client_data) +{ + addOption(new ecc::TclSwitchOption("-intersect")); + addOption(new ecc::TclStringOption("collection", 1)); + addOption(new ecc::TclStringOption("objects", 1)); +} + +unsigned TclRemoveFromCollection::exec() +{ + ecc::TclOption* collection_option = getOptionOrArg("collection"); + ecc::TclOption* object_option = getOptionOrArg("objects"); + if (!collection_option->is_set_val() || !object_option->is_set_val()) { + setTclError("remove_from_collection requires a base collection and an object specification"); + return 0; + } + Database& database = STADM.getDatabase(); + const std::vector collection = queryPatterns(collection_option->getStringVal(), false); + const std::vector objects = queryPatterns(object_option->getStringVal(), false); + std::optional type; + bool homogeneous = true; + for (const std::string& name : collection) { + QueryObjectType object_type = QueryObjectType::kAny; + const auto pin = database.get_pin_map().find(name); + if (pin != database.get_pin_map().end()) { + object_type = pin->second.get_is_port() ? QueryObjectType::kPort : QueryObjectType::kPin; + } else if (database.get_timing_constraint().get_clock_map().contains(name)) { + object_type = QueryObjectType::kClock; + } else if (name != database.get_design_name() && !database.get_instance_map().contains(name)) { + setTclError("invalid collection object '" + name + "'"); + return 0; + } + homogeneous = homogeneous && (!type || *type == object_type); + type = object_type; + } + std::set selected(objects.begin(), objects.end()); + if (homogeneous && type && *type != QueryObjectType::kAny) { + const std::vector matches = queryObjects(database, objects, *type); + selected.insert(matches.begin(), matches.end()); + } + const bool intersect = getOptionOrArg("-intersect")->is_set_val(); + std::vector result; + for (const std::string& name : collection) { + if (selected.contains(name) == intersect) { + result.push_back(name); + } + } + setResult(std::move(result)); + return 1; +} + +} // namespace ista::sdc diff --git a/src/operation/iSTA/source/module/sdc_command/sdc_commands/SdcCommandUtils.cpp b/src/operation/iSTA/source/module/sdc_command/sdc_commands/SdcCommandUtils.cpp index bb09f77b1f..e289602c54 100644 --- a/src/operation/iSTA/source/module/sdc_command/sdc_commands/SdcCommandUtils.cpp +++ b/src/operation/iSTA/source/module/sdc_command/sdc_commands/SdcCommandUtils.cpp @@ -17,9 +17,107 @@ #include "SdcCommandUtils.hpp" #include "STAHeader.hpp" +#include "SdcCommand.hpp" namespace ista::sdc { +std::vector queryPatterns(const std::string& text, bool regexp) +{ + // OpenSTA SDC commonly passes one raw regexp; PT writers pass a Tcl list. + if (regexp && !text.empty() && text.front() != '{' && text.find('\\') != std::string::npos) { + return {text}; + } + int count = 0; + const char** items = nullptr; + if (Tcl_SplitList(SdcCommand::getInst().getInterp(), text.c_str(), &count, &items) != TCL_OK) { + throw std::invalid_argument("invalid object pattern list"); + } + std::vector result(items, items + count); + Tcl_Free(reinterpret_cast(items)); + return result; +} + +std::vector queryObjects(Database& database, const std::vector& patterns, QueryObjectType type, bool regexp) +{ + std::map names; + if (type == QueryObjectType::kClock || type == QueryObjectType::kAny) { + for (const auto& [name, clock] : database.get_timing_constraint().get_clock_map()) { + names[name] = name; + } + } + if (type != QueryObjectType::kClock) { + for (auto& [name, pin] : database.get_pin_map()) { + if ((type == QueryObjectType::kPort && !pin.get_is_port()) || (type == QueryObjectType::kPin && pin.get_is_port())) { + continue; + } + names[name] = name; + if (!pin.get_is_port()) { + names[pin.get_instance_name() + "/" + pin.get_pin_name()] = name; + } + } + } + if (type == QueryObjectType::kAny) { + for (const auto& [name, instance] : database.get_instance_map()) { + names[name] = name; + } + } + std::set found; + for (const std::string& pattern : patterns) { + if (!regexp && names.contains(pattern)) { + found.insert(names.at(pattern)); + continue; + } + const std::string expression = "^(?:" + pattern + ")$"; + if (regexp && Tcl_RegExpMatch(SdcCommand::getInst().getInterp(), "", expression.c_str()) < 0) { + throw std::invalid_argument("invalid regular expression: " + pattern); + } + for (const auto& [name, canonical] : names) { + if (regexp ? Tcl_RegExpMatch(SdcCommand::getInst().getInterp(), name.c_str(), expression.c_str()) == 1 + : Tcl_StringMatch(name.c_str(), pattern.c_str()) != 0) { + found.insert(canonical); + } + } + } + return {found.begin(), found.end()}; +} + +std::set resolveClockObjects(Database& database, const std::vector& objects) +{ + std::set clocks; + for (const std::string& pattern : objects) { + const std::vector matches = queryObjects(database, {pattern}, QueryObjectType::kClock); + if (matches.empty()) { + throw std::invalid_argument("clock '" + pattern + "' does not exist"); + } + clocks.insert(matches.begin(), matches.end()); + } + if (clocks.empty()) { + throw std::invalid_argument("a non-empty clock collection is required"); + } + return clocks; +} + +std::set resolveExceptionObjects(Database& database, const std::vector& objects) +{ + std::set result; + for (const std::string& object : objects) { + if (database.get_pin_map().contains(object) || database.get_instance_map().contains(object) + || database.get_timing_constraint().get_clock_map().contains(object)) { + result.insert(object); + continue; + } + const std::vector matches = queryObjects(database, {object}, QueryObjectType::kAny); + if (matches.empty()) { + throw std::invalid_argument("exception object not found: " + object); + } + result.insert(matches.begin(), matches.end()); + } + if (result.empty()) { + throw std::invalid_argument("empty timing exception collection"); + } + return result; +} + std::vector resolveObjectList(Database& database, const std::vector& object_list) { std::vector resolved_object_list; diff --git a/src/operation/iSTA/source/module/sdc_command/sdc_commands/SdcCommandUtils.hpp b/src/operation/iSTA/source/module/sdc_command/sdc_commands/SdcCommandUtils.hpp index e57ad924de..b8539d5a40 100644 --- a/src/operation/iSTA/source/module/sdc_command/sdc_commands/SdcCommandUtils.hpp +++ b/src/operation/iSTA/source/module/sdc_command/sdc_commands/SdcCommandUtils.hpp @@ -20,6 +20,18 @@ #include "STAHeader.hpp" namespace ista::sdc { +enum class QueryObjectType +{ + kPort, + kPin, + kClock, + kAny +}; +std::vector queryPatterns(const std::string& text, bool regexp); +std::vector queryObjects(Database& database, const std::vector& patterns, QueryObjectType type, bool regexp = false); +std::set resolveClockObjects(Database& database, const std::vector& objects); +std::set resolveExceptionObjects(Database& database, const std::vector& objects); + std::vector resolveObjectList(Database& database, const std::vector& object_list); TimingPortConstraint& getPortConstraint(Database& database, const std::string& port_name); diff --git a/src/operation/iSTA/source/module/sdc_command/sdc_commands/SdcCommands.hpp b/src/operation/iSTA/source/module/sdc_command/sdc_commands/SdcCommands.hpp index 2fff392410..1446ade86b 100644 --- a/src/operation/iSTA/source/module/sdc_command/sdc_commands/SdcCommands.hpp +++ b/src/operation/iSTA/source/module/sdc_command/sdc_commands/SdcCommands.hpp @@ -20,6 +20,86 @@ namespace ista::sdc { +class TclCurrentDesign : public SdcTclCmd +{ + public: + TclCurrentDesign(const char* cmd_name, ClientData client_data); + unsigned check() override { return 1; } + unsigned exec() override; +}; + +class TclRemoveFromCollection : public SdcTclCmd +{ + public: + TclRemoveFromCollection(const char* cmd_name, ClientData client_data); + unsigned check() override { return 1; } + unsigned exec() override; +}; + +class TclSetClockTransition : public SdcTclCmd +{ + public: + TclSetClockTransition(const char* cmd_name, ClientData client_data); + unsigned check() override { return 1; } + unsigned exec() override; +}; + +class TclSetMaxFanout : public SdcTclCmd +{ + public: + TclSetMaxFanout(const char* cmd_name, ClientData client_data); + unsigned check() override { return 1; } + unsigned exec() override; +}; + +class TclGetPins : public SdcTclCmd +{ + public: + TclGetPins(const char* cmd_name, ClientData client_data); + unsigned check() override { return 1; } + unsigned exec() override; +}; + +class TclAllInputs : public SdcTclCmd +{ + public: + TclAllInputs(const char* cmd_name, ClientData client_data); + unsigned check() override { return 1; } + unsigned exec() override; +}; + +class TclAllOutputs : public SdcTclCmd +{ + public: + TclAllOutputs(const char* cmd_name, ClientData client_data); + unsigned check() override { return 1; } + unsigned exec() override; +}; + +class TclSetFalsePath : public SdcTclCmd +{ + public: + TclSetFalsePath(const char* cmd_name, ClientData client_data); + unsigned check() override { return 1; } + unsigned exec() override; +}; + +class TclSetClockGroups : public SdcTclCmd +{ + public: + TclSetClockGroups(const char* cmd_name, ClientData client_data); + unsigned check() override { return 1; } + unsigned exec() override; +}; + +class TclCreateGeneratedClock : public SdcTclCmd +{ + public: + TclCreateGeneratedClock(const char* cmd_name, ClientData client_data); + unsigned check() override { return 1; } + unsigned exec() override; +}; + class TclSetCaseAnalysis : public SdcTclCmd { public: diff --git a/src/operation/iSTA/source/module/sdc_command/sdc_commands/SdcTclCmd.cpp b/src/operation/iSTA/source/module/sdc_command/sdc_commands/SdcTclCmd.cpp index 4e3f4f866f..d9e8783f85 100644 --- a/src/operation/iSTA/source/module/sdc_command/sdc_commands/SdcTclCmd.cpp +++ b/src/operation/iSTA/source/module/sdc_command/sdc_commands/SdcTclCmd.cpp @@ -32,7 +32,7 @@ int SdcTclCmd::execute(Tcl_Interp* interp, int objc, Tcl_Obj* const objv[]) for (int index = 1; index < objc; ++index) { const char* value = Tcl_GetString(objv[index]); if (next_is_option_value) { - current_option->setVal(value); + setOptionValue(current_option, value); next_is_option_value = false; continue; } @@ -54,7 +54,7 @@ int SdcTclCmd::execute(Tcl_Interp* interp, int objc, Tcl_Obj* const objv[]) setInterpreterError(interp); return TCL_ERROR; } - argument->setVal(value); + setOptionValue(argument, value); } if (next_is_option_value) { @@ -91,6 +91,21 @@ int SdcTclCmd::execute(Tcl_Interp* interp, int objc, Tcl_Obj* const objv[]) return TCL_OK; } +void SdcTclCmd::setOptionValue(ecc::TclOption* option, const char* value) +{ + if (option->isDoubleOption()) { + double number = 0.0; + if (Tcl_GetDouble(nullptr, value, &number) != TCL_OK || !std::isfinite(number)) { + throw std::invalid_argument(std::string(option->get_option_name()) + " requires a finite number: " + value); + } + std::ostringstream number_stream; + number_stream << std::setprecision(std::numeric_limits::max_digits10) << number; + option->setVal(number_stream.str().c_str()); + return; + } + option->setVal(value); +} + void SdcTclCmd::setResult(std::string result) { _result = std::move(result); diff --git a/src/operation/iSTA/source/module/sdc_command/sdc_commands/SdcTclCmd.hpp b/src/operation/iSTA/source/module/sdc_command/sdc_commands/SdcTclCmd.hpp index 9491060baa..247d8f9b34 100644 --- a/src/operation/iSTA/source/module/sdc_command/sdc_commands/SdcTclCmd.hpp +++ b/src/operation/iSTA/source/module/sdc_command/sdc_commands/SdcTclCmd.hpp @@ -30,6 +30,7 @@ class SdcTclCmd : public ecc::TclCmd int execute(Tcl_Interp* interp, int objc, Tcl_Obj* const objv[]); protected: + void setOptionValue(ecc::TclOption* option, const char* value); void setTclError(std::string error_message) { _error_message = std::move(error_message); } void setResult(std::string result); void setResult(std::vector result); @@ -56,8 +57,13 @@ int executeTclCommand(ClientData client_data, Tcl_Interp* interp, int objc, Tcl_ return TCL_ERROR; } - Command command(Tcl_GetString(objv[0]), client_data); - return command.execute(interp, objc, objv); + try { + Command command(Tcl_GetString(objv[0]), client_data); + return command.execute(interp, objc, objv); + } catch (const std::exception& error) { + Tcl_SetObjResult(interp, Tcl_NewStringObj(error.what(), -1)); + return TCL_ERROR; + } } } // namespace ista::sdc diff --git a/src/operation/iSTA/source/module/sdc_command/sdc_commands/SetClockGroups.cpp b/src/operation/iSTA/source/module/sdc_command/sdc_commands/SetClockGroups.cpp new file mode 100644 index 0000000000..dc56a28310 --- /dev/null +++ b/src/operation/iSTA/source/module/sdc_command/sdc_commands/SetClockGroups.cpp @@ -0,0 +1,76 @@ +// *************************************************************************************** +// Copyright (c) 2023-2025 Peng Cheng Laboratory +// Copyright (c) 2023-2025 Institute of Computing Technology, Chinese Academy of Sciences +// Copyright (c) 2023-2025 Beijing Institute of Open Source Chip +// +// iEDA is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// +// See the Mulan PSL v2 for more details. +// *************************************************************************************** +#include "DataManager.hpp" +#include "SdcCommands.hpp" + +namespace ista::sdc { + +TclSetClockGroups::TclSetClockGroups(const char* cmd_name, ClientData client_data) : SdcTclCmd(cmd_name, client_data) +{ + addOption(new ecc::TclStringListListOption("-group", 0)); + addOption(new ecc::TclStringOption("-name", 0)); + addOption(new ecc::TclStringOption("-comment", 0)); + for (const char* option : {"-asynchronous", "-logically_exclusive", "-physically_exclusive", "-exclusive", "-allow_paths"}) { + addOption(new ecc::TclSwitchOption(option)); + } +} + +unsigned TclSetClockGroups::exec() +{ + int mode_count = 0; + for (const char* option : {"-asynchronous", "-logically_exclusive", "-physically_exclusive", "-exclusive"}) { + mode_count += getOptionOrArg(option)->is_set_val(); + } + if (mode_count != 1 || !getOptionOrArg("-group")->is_set_val()) { + setTclError("set_clock_groups requires one relationship mode and at least one -group"); + return 0; + } + const bool allow_paths = getOptionOrArg("-allow_paths")->is_set_val(); + if (allow_paths && !getOptionOrArg("-asynchronous")->is_set_val()) { + setTclError("-allow_paths requires -asynchronous"); + return 0; + } + std::vector> groups; + std::set seen; + for (const std::vector& names : getOptionOrArg("-group")->getStringListList()) { + if (names.empty()) { + setTclError("empty clock group"); + return 0; + } + std::set group; + for (const std::string& name : names) { + if (!STADM.getDatabase().get_timing_constraint().get_clock_map().contains(name)) { + setTclError("unknown clock in group: " + name); + return 0; + } + if (seen.contains(name) && !group.contains(name)) { + setTclError("clock belongs to multiple groups: " + name); + return 0; + } + group.insert(name); + seen.insert(name); + } + groups.push_back(std::move(group)); + } + TimingClockGroup relation; + relation.set_groups(groups); + relation.set_allow_paths(allow_paths); + STADM.getDatabase().get_timing_constraint().get_clock_group_list().push_back(std::move(relation)); + return 1; +} + +} // namespace ista::sdc diff --git a/src/operation/iSTA/source/module/sdc_command/sdc_commands/SetClockTransition.cpp b/src/operation/iSTA/source/module/sdc_command/sdc_commands/SetClockTransition.cpp new file mode 100644 index 0000000000..d6cfdba75b --- /dev/null +++ b/src/operation/iSTA/source/module/sdc_command/sdc_commands/SetClockTransition.cpp @@ -0,0 +1,69 @@ +// *************************************************************************************** +// Copyright (c) 2023-2025 Peng Cheng Laboratory +// Copyright (c) 2023-2025 Institute of Computing Technology, Chinese Academy of Sciences +// Copyright (c) 2023-2025 Beijing Institute of Open Source Chip +// +// iEDA is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// +// See the Mulan PSL v2 for more details. +// *************************************************************************************** +#include "DataManager.hpp" +#include "SdcCommandUtils.hpp" +#include "SdcCommands.hpp" + +namespace ista::sdc { + +TclSetClockTransition::TclSetClockTransition(const char* cmd_name, ClientData client_data) : SdcTclCmd(cmd_name, client_data) +{ + addOption(new ecc::TclSwitchOption("-rise")); + addOption(new ecc::TclSwitchOption("-fall")); + addOption(new ecc::TclSwitchOption("-min")); + addOption(new ecc::TclSwitchOption("-max")); + addOption(new ecc::TclDoubleOption("transition", 1)); + addOption(new ecc::TclStringOption("clocks", 1)); +} + +unsigned TclSetClockTransition::exec() +{ + ecc::TclOption* transition_option = getOptionOrArg("transition"); + ecc::TclOption* clock_option = getOptionOrArg("clocks"); + if (!transition_option->is_set_val() || !clock_option->is_set_val()) { + setTclError("set_clock_transition requires a transition and a clock collection"); + return 0; + } + const double transition = transition_option->getDoubleVal(); + if (!std::isfinite(transition) || transition < 0.0) { + setTclError("set_clock_transition must be finite and non-negative"); + return 0; + } + Database& database = STADM.getDatabase(); + const std::set clocks = resolveClockObjects(database, queryPatterns(clock_option->getStringVal(), false)); + const bool rise = getOptionOrArg("-rise")->is_set_val(); + const bool fall = getOptionOrArg("-fall")->is_set_val(); + const bool min = getOptionOrArg("-min")->is_set_val(); + const bool max = getOptionOrArg("-max")->is_set_val(); + for (const std::string& name : clocks) { + TimingClock& clock = database.get_timing_constraint().get_clock_map().at(name); + for (AnalysisType analysis_type : {AnalysisType::kMin, AnalysisType::kMax}) { + if ((analysis_type == AnalysisType::kMin && max && !min) || (analysis_type == AnalysisType::kMax && min && !max)) { + continue; + } + if (!fall || rise) { + clock.get_transition_map()[analysis_type][TransType::kRise] = transition; + } + if (!rise || fall) { + clock.get_transition_map()[analysis_type][TransType::kFall] = transition; + } + } + } + return 1; +} + +} // namespace ista::sdc diff --git a/src/operation/iSTA/source/module/sdc_command/sdc_commands/SetClockUncertainty.cpp b/src/operation/iSTA/source/module/sdc_command/sdc_commands/SetClockUncertainty.cpp index e882bcfa3f..b1a966993d 100644 --- a/src/operation/iSTA/source/module/sdc_command/sdc_commands/SetClockUncertainty.cpp +++ b/src/operation/iSTA/source/module/sdc_command/sdc_commands/SetClockUncertainty.cpp @@ -26,7 +26,7 @@ TclSetClockUncertainty::TclSetClockUncertainty(const char* cmd_name, ClientData addOption(new ecc::TclSwitchOption("-setup")); addOption(new ecc::TclSwitchOption("-hold")); addOption(new ecc::TclDoubleOption("uncertainty", 1)); - addOption(new ecc::TclStringListOption("clocks", 1)); + addOption(new ecc::TclStringOption("clocks", 1)); } unsigned TclSetClockUncertainty::exec() @@ -46,25 +46,18 @@ unsigned TclSetClockUncertainty::exec() return 0; } - const std::vector clock_list = clock_option->getStringList(); - if (clock_list.empty()) { - setTclError("set_clock_uncertainty requires a clock collection"); - return 0; - } - auto& clock_map = data_manager.getDatabase().get_timing_constraint().get_clock_map(); - const auto clock_it = clock_map.find(clock_list.front()); - if (clock_it == clock_map.end()) { - setTclError("clock '" + clock_list.front() + "' does not exist"); - return 0; - } - + Database& database = data_manager.getDatabase(); + const std::set clocks = resolveClockObjects(database, queryPatterns(clock_option->getStringVal(), false)); const bool setup = getOptionOrArg("-setup")->is_set_val(); const bool hold = getOptionOrArg("-hold")->is_set_val(); - if (!hold || setup) { - clock_it->second.set_setup_uncertainty(uncertainty); - } - if (!setup || hold) { - clock_it->second.set_hold_uncertainty(uncertainty); + for (const std::string& name : clocks) { + TimingClock& clock = database.get_timing_constraint().get_clock_map().at(name); + if (!hold || setup) { + clock.set_setup_uncertainty(uncertainty); + } + if (!setup || hold) { + clock.set_hold_uncertainty(uncertainty); + } } return 1; } diff --git a/src/operation/iSTA/source/module/sdc_command/sdc_commands/SetFalsePath.cpp b/src/operation/iSTA/source/module/sdc_command/sdc_commands/SetFalsePath.cpp new file mode 100644 index 0000000000..127071ce00 --- /dev/null +++ b/src/operation/iSTA/source/module/sdc_command/sdc_commands/SetFalsePath.cpp @@ -0,0 +1,50 @@ +// *************************************************************************************** +// Copyright (c) 2023-2025 Peng Cheng Laboratory +// Copyright (c) 2023-2025 Institute of Computing Technology, Chinese Academy of Sciences +// Copyright (c) 2023-2025 Beijing Institute of Open Source Chip +// +// iEDA is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// +// See the Mulan PSL v2 for more details. +// *************************************************************************************** +#include "DataManager.hpp" +#include "SdcCommandUtils.hpp" +#include "SdcCommands.hpp" + +namespace ista::sdc { + +TclSetFalsePath::TclSetFalsePath(const char* cmd_name, ClientData client_data) : SdcTclCmd(cmd_name, client_data) +{ + addOption(new ecc::TclStringListOption("-from", 0)); + addOption(new ecc::TclStringListOption("-to", 0)); + addOption(new ecc::TclSwitchOption("-setup")); + addOption(new ecc::TclSwitchOption("-hold")); + addOption(new ecc::TclStringOption("-comment", 0)); +} + +unsigned TclSetFalsePath::exec() +{ + Database& database = STADM.getDatabase(); + TimingException exception; + const bool setup = getOptionOrArg("-setup")->is_set_val(); + const bool hold = getOptionOrArg("-hold")->is_set_val(); + exception.set_setup(setup || !hold); + exception.set_hold(hold || !setup); + if (getOptionOrArg("-from")->is_set_val()) { + exception.set_from_objects(resolveExceptionObjects(database, getOptionOrArg("-from")->getStringList())); + } + if (getOptionOrArg("-to")->is_set_val()) { + exception.set_to_objects(resolveExceptionObjects(database, getOptionOrArg("-to")->getStringList())); + } + database.get_timing_constraint().get_false_path_list().push_back(std::move(exception)); + return 1; +} + +} // namespace ista::sdc diff --git a/src/operation/iSTA/source/module/sdc_command/sdc_commands/SetMaxFanout.cpp b/src/operation/iSTA/source/module/sdc_command/sdc_commands/SetMaxFanout.cpp new file mode 100644 index 0000000000..57bb39b41d --- /dev/null +++ b/src/operation/iSTA/source/module/sdc_command/sdc_commands/SetMaxFanout.cpp @@ -0,0 +1,79 @@ +// *************************************************************************************** +// Copyright (c) 2023-2025 Peng Cheng Laboratory +// Copyright (c) 2023-2025 Institute of Computing Technology, Chinese Academy of Sciences +// Copyright (c) 2023-2025 Beijing Institute of Open Source Chip +// +// iEDA is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// +// See the Mulan PSL v2 for more details. +// *************************************************************************************** +#include "DataManager.hpp" +#include "SdcCommandUtils.hpp" +#include "SdcCommands.hpp" + +namespace ista::sdc { + +TclSetMaxFanout::TclSetMaxFanout(const char* cmd_name, ClientData client_data) : SdcTclCmd(cmd_name, client_data) +{ + addOption(new ecc::TclDoubleOption("fanout", 1)); + addOption(new ecc::TclStringOption("objects", 1)); +} + +unsigned TclSetMaxFanout::exec() +{ + ecc::TclOption* fanout_option = getOptionOrArg("fanout"); + ecc::TclOption* object_option = getOptionOrArg("objects"); + if (!fanout_option->is_set_val() || !object_option->is_set_val()) { + setTclError("set_max_fanout requires a limit and an input port or design collection"); + return 0; + } + const double fanout = fanout_option->getDoubleVal(); + if (!std::isfinite(fanout) || fanout < 0.0) { + setTclError("set_max_fanout must be finite and non-negative"); + return 0; + } + Database& database = STADM.getDatabase(); + const std::vector objects = queryPatterns(object_option->getStringVal(), false); + if (objects.empty()) { + setTclError("set_max_fanout requires a non-empty collection"); + return 0; + } + bool design = false; + std::set ports; + for (const std::string& object : objects) { + if (!database.get_design_name().empty() && object == database.get_design_name()) { + design = true; + continue; + } + const std::vector matches = queryObjects(database, {object}, QueryObjectType::kPort); + if (matches.empty()) { + setTclError("set_max_fanout object not found: " + object); + return 0; + } + for (const std::string& name : matches) { + const PinDirection direction = database.get_pin_map().at(name).get_direction(); + if (direction != PinDirection::kInput && direction != PinDirection::kInout) { + setTclError("set_max_fanout requires input ports; '" + name + "' is not an input port"); + return 0; + } + ports.insert(name); + } + } + TimingConstraint& constraint = database.get_timing_constraint(); + if (design) { + constraint.get_max_fanout() = fanout; + } + for (const std::string& port : ports) { + constraint.get_port_max_fanout_map()[port] = fanout; + } + return 1; +} + +} // namespace ista::sdc diff --git a/src/operation/iSTA/source/module/timing_analyzer/TimingAnalyzer.cpp b/src/operation/iSTA/source/module/timing_analyzer/TimingAnalyzer.cpp index e55dbbfc7b..8db5fc723a 100644 --- a/src/operation/iSTA/source/module/timing_analyzer/TimingAnalyzer.cpp +++ b/src/operation/iSTA/source/module/timing_analyzer/TimingAnalyzer.cpp @@ -20,6 +20,7 @@ #include "DelayCalculator.hpp" #include "Logger.hpp" #include "Monitor.hpp" +#include "Utility.hpp" namespace ista { @@ -59,6 +60,7 @@ void TimingAnalyzer::analyze() analyzeEndPointList(ta_model); uploadTimingPathGroupList(ta_model); updateSummary(ta_model); + analyzeFanoutConstraints(); STALOG.info(Loc::current(), "Completed", monitor.getStatsInfo()); } @@ -66,6 +68,77 @@ void TimingAnalyzer::analyze() TimingAnalyzer* TimingAnalyzer::_ta_instance = nullptr; +void TimingAnalyzer::analyzeFanoutConstraints() +{ + Database& database = STADM.getDatabase(); + TimingConstraint& constraint = database.get_timing_constraint(); + std::vector& checks = database.get_fanout_check_list(); + checks.clear(); + for (auto& [net_name, net] : database.get_net_map()) { + double fanout_load = 0.0; + const std::set loads(net.get_load_pin_list().begin(), net.get_load_pin_list().end()); + for (const std::string& load : loads) { + Pin& pin = database.get_pin_map().at(load); + // External port fanout defaults to zero. Capacitance is a separate quantity. + if (!pin.get_is_port()) { + TimingCellPort* port = getFanoutCellPort(pin); + fanout_load += port == nullptr ? 1.0 : port->get_fanout_load(); + } + } + std::set drivers(net.get_driver_pin_list().begin(), net.get_driver_pin_list().end()); + if (!net.get_driver_pin().empty()) { + drivers.insert(net.get_driver_pin()); + } + for (const std::string& driver : drivers) { + Pin& pin = database.get_pin_map().at(driver); + std::optional pin_limit; + if (pin.get_is_port()) { + const auto port_limit = constraint.get_port_max_fanout_map().find(driver); + if (port_limit != constraint.get_port_max_fanout_map().end()) { + pin_limit = port_limit->second; + } + } else { + TimingCellPort* port = getFanoutCellPort(pin); + if (port != nullptr) { + pin_limit = port->get_max_fanout(); + } + } + std::optional limit = constraint.get_max_fanout(); + if (pin_limit) { + limit = limit ? std::min(*limit, *pin_limit) : *pin_limit; + } + if (!limit) { + continue; + } + TimingFanoutCheck check; + check.set_net_name(net_name); + check.set_driver_pin(driver); + check.set_fanout_load(fanout_load); + check.set_limit(*limit); + checks.push_back(check); + } + } + std::sort(checks.begin(), checks.end(), [](const TimingFanoutCheck& lhs, const TimingFanoutCheck& rhs) { + return std::make_tuple(lhs.get_slack(), lhs.get_driver_pin(), lhs.get_net_name()) + < std::make_tuple(rhs.get_slack(), rhs.get_driver_pin(), rhs.get_net_name()); + }); +} + +TimingCellPort* TimingAnalyzer::getFanoutCellPort(Pin& pin) +{ + Database& database = STADM.getDatabase(); + const auto instance = database.get_instance_map().find(pin.get_instance_name()); + if (instance == database.get_instance_map().end()) { + return nullptr; + } + const auto cell = database.get_timing_library().get_cell_map().find(instance->second.get_cell_name()); + if (cell == database.get_timing_library().get_cell_map().end()) { + return nullptr; + } + const auto port = cell->second.get_port_map().find(pin.get_pin_name()); + return port == cell->second.get_port_map().end() ? nullptr : &port->second; +} + TAModel TimingAnalyzer::initTAModel() { TAModel ta_model; @@ -190,7 +263,7 @@ std::map& TimingAnalyzer::getPathStateMap(TimingPo TimingPathState& TimingAnalyzer::getPathState(TimingPoint& timing_point, AnalysisType analysis_type, PathSourceType source_type, TransType trans_type, std::string& start_point) { - std::string path_state_tag{getClockName(start_point)}; + std::string path_state_tag = STAUTIL.getPathStateTag(STADM.getDatabase(), start_point, getClockName(start_point)); return timing_point.get_path_state_map()[analysis_type][source_type][trans_type][path_state_tag]; } @@ -333,10 +406,10 @@ double TimingAnalyzer::getEndPointRequired(std::string& start_point, std::string if (pin.get_is_port()) { std::map& port_constraint_map = database.get_timing_constraint().get_port_constraint_map(); if (analysis_type == AnalysisType::kMin && port_constraint_map.count(end_point) > 0 && port_constraint_map[end_point].get_has_output_delay_min()) { - return getEndPointCaptureTime(end_point, analysis_type) - port_constraint_map[end_point].get_output_delay_min(); + return getEndPointCaptureTime(start_point, end_point, analysis_type) - port_constraint_map[end_point].get_output_delay_min(); } if (port_constraint_map.count(end_point) > 0 && port_constraint_map[end_point].get_has_output_delay_max()) { - return getEndPointCaptureTime(end_point, analysis_type) - port_constraint_map[end_point].get_output_delay_max(); + return getEndPointCaptureTime(start_point, end_point, analysis_type) - port_constraint_map[end_point].get_output_delay_max(); } return default_required_time; } @@ -350,9 +423,9 @@ double TimingAnalyzer::getEndPointRequired(std::string& start_point, std::string std::string common_pin_name; double cppr = getClockReconvergencePessimism(start_point, end_point, analysis_type, common_pin_name); if (analysis_type == AnalysisType::kMin) { - return roundTime(getEndPointCaptureTime(end_point, analysis_type) + check_time - cppr + getClockUncertainty(end_point, analysis_type)); + return roundTime(getEndPointCaptureTime(start_point, end_point, analysis_type) + check_time - cppr + getClockUncertainty(end_point, analysis_type)); } - return roundTime(getEndPointCaptureTime(end_point, analysis_type) - check_time + cppr - getClockUncertainty(end_point, analysis_type)); + return roundTime(getEndPointCaptureTime(start_point, end_point, analysis_type) - check_time + cppr - getClockUncertainty(end_point, analysis_type)); } return default_required_time; } @@ -435,9 +508,11 @@ double TimingAnalyzer::getEndPointRequired(TimingPathState& end_path_state, std: std::string common_pin_name; double cppr = getClockReconvergencePessimism(end_path_state, end_point, analysis_type, common_pin_name); if (analysis_type == AnalysisType::kMin) { - return roundTime(getEndPointCaptureTime(end_point, analysis_type) + check_time - cppr + getClockUncertainty(end_point, analysis_type)); + return roundTime(getEndPointCaptureTime(end_path_state.get_start_point(), end_point, analysis_type) + check_time - cppr + + getClockUncertainty(end_point, analysis_type)); } - return roundTime(getEndPointCaptureTime(end_point, analysis_type) - check_time + cppr - getClockUncertainty(end_point, analysis_type)); + return roundTime(getEndPointCaptureTime(end_path_state.get_start_point(), end_point, analysis_type) - check_time + cppr + - getClockUncertainty(end_point, analysis_type)); } return default_required_time; } @@ -500,14 +575,25 @@ TransType TimingAnalyzer::getClockTransType(TimingCheckArc& timing_check_arc) double TimingAnalyzer::getEndPointCaptureTime(std::string& end_point, AnalysisType analysis_type) { - AnalysisType capture_analysis_type = getCaptureAnalysisType(analysis_type); - TimingCheckArc* timing_check_arc = getEndPointCheckArc(end_point, analysis_type); - TransType clock_trans_type = timing_check_arc == nullptr ? TransType::kRise : getClockTransType(*timing_check_arc); - if (analysis_type == AnalysisType::kMax) { - const auto& clock_name = getClockName(end_point); - return getClockPeriod(clock_name) + getEndPointClockArrival(end_point, capture_analysis_type, clock_trans_type); + return getEndPointCaptureTime(end_point, end_point, analysis_type); +} + +double TimingAnalyzer::getEndPointCaptureTime(std::string& start_point, std::string& end_point, AnalysisType analysis_type) +{ + Database& database = STADM.getDatabase(); + TimingCheckArc* check_arc = getEndPointCheckArc(end_point, analysis_type); + TransType capture_transition = check_arc == nullptr ? TransType::kRise : getClockTransType(*check_arc); + const std::string launch_clock(getClockName(start_point)); + const std::string capture_clock(getClockName(end_point)); + auto& clocks = database.get_timing_constraint().get_clock_map(); + if (!clocks.contains(launch_clock) || !clocks.contains(capture_clock)) { + return 0.0; } - return getEndPointClockArrival(end_point, capture_analysis_type, clock_trans_type); + TimingClock& capture = clocks.at(capture_clock); + const double launch_edge = STAUTIL.getLaunchClockEdge(database, start_point, launch_clock); + const double capture_edge = capture_transition == TransType::kFall ? capture.get_fall_edge() : capture.get_rise_edge(); + return launch_edge + STAUTIL.getClockEdgeSeparation(clocks.at(launch_clock).get_period(), capture.get_period(), launch_edge, capture_edge, analysis_type) + + getEndPointClockArrival(end_point, getCaptureAnalysisType(analysis_type), capture_transition); } double TimingAnalyzer::getEndPointClockArrival(std::string& end_point, AnalysisType analysis_type) @@ -980,8 +1066,8 @@ std::string TimingAnalyzer::getTimingPathGroupName(TimingPath& timing_path) if (timing_path.get_check_type() == TimingCheckType::kRecovery || timing_path.get_check_type() == TimingCheckType::kRemoval) { return "**async_default**"; } - if (!timing_path.get_clock_name().empty()) { - return timing_path.get_clock_name(); + if (!timing_path.get_capture_clock_name().empty()) { + return timing_path.get_capture_clock_name(); } if (!database.get_timing_constraint().get_clock_map().empty()) { return database.get_timing_constraint().get_clock_map().begin()->first; @@ -1139,7 +1225,7 @@ bool TimingAnalyzer::updateDiversionPathState(std::string& pin_name, AnalysisTyp { Database& database = STADM.getDatabase(); TimingPoint& timing_point = database.get_timing_point_map()[pin_name]; - const std::string& path_state_tag = source_path_state.get_clock_name(); + std::string path_state_tag = STAUTIL.getPathStateTag(database, source_path_state.get_start_point(), source_path_state.get_clock_name()); std::map& path_state_map = getPathStateMap(timing_point, analysis_type, source_type, trans_type); if (path_state_map.count(path_state_tag) > 0 && !isBetterArrival(arrival, path_state_map[path_state_tag].get_arrival(), analysis_type)) { return false; @@ -1188,6 +1274,9 @@ TimingPathState* TimingAnalyzer::getWorstSlackPathState(std::string& end_point, if (timing_check_arc != nullptr && !isMatchCheckTransType(*timing_check_arc, path_state.get_trans_type())) { continue; } + if (STAUTIL.isFalsePath(database, path_state.get_start_point(), path_state.get_clock_name(), end_point, getClockName(end_point), analysis_type)) { + continue; + } double required_time = calcPathRequiredTime(end_point, path_state, analysis_type); double slack = calcPathSlack(path_state, required_time, analysis_type); if (worst_path_state == nullptr || slack < worst_slack - STA_ERROR) { @@ -1221,6 +1310,9 @@ bool TimingAnalyzer::isOutputEndPoint(std::string& end_point) { Database& database = STADM.getDatabase(); Pin& pin = database.get_pin_map()[end_point]; + if (pin.get_is_port() && getStartPointClock(end_point) != nullptr && database.get_outgoing_arc_list_map()[end_point].empty()) { + return false; + } return pin.get_is_port() && (pin.get_direction() == PinDirection::kOutput || pin.get_direction() == PinDirection::kInout) && hasOutputDelay(end_point); } @@ -1363,16 +1455,19 @@ void TimingAnalyzer::updateClockInfo(TimingPath& timing_path, AnalysisType analy = getPathState(database.get_timing_point_map()[timing_path.get_end_point()], analysis_type, source_type, trans_type, start_point); timing_path.set_launch_time(end_path_state.get_launch_time()); timing_path.set_clock_name(end_path_state.get_clock_name()); + timing_path.set_capture_clock_name(getClockName(timing_path.get_end_point())); std::string common_pin_name; double cppr = getClockReconvergencePessimism(end_path_state, timing_path.get_end_point(), analysis_type, common_pin_name); if (analysis_type == AnalysisType::kMin) { cppr = -cppr; } timing_path.set_last_common_pin(common_pin_name); - timing_path.set_capture_time(getEndPointCaptureTime(timing_path.get_end_point(), analysis_type) + cppr); - timing_path.set_launch_clock_network_delay(end_path_state.get_launch_time()); + timing_path.set_capture_time(getEndPointCaptureTime(start_point, timing_path.get_end_point(), analysis_type) + cppr); + timing_path.set_launch_clock_network_delay(end_path_state.get_launch_time() + - STAUTIL.getLaunchClockEdge(database, start_point, end_path_state.get_clock_name())); TimingCheckArc* timing_check_arc = getEndPointCheckArc(timing_path.get_end_point(), analysis_type); TransType capture_trans_type = timing_check_arc == nullptr ? TransType::kRise : getClockTransType(*timing_check_arc); + timing_path.set_capture_clock_transition(capture_trans_type); timing_path.set_capture_clock_network_delay(getEndPointClockArrival(timing_path.get_end_point(), getCaptureAnalysisType(analysis_type), capture_trans_type)); timing_path.set_clock_reconvergence_pessimism(cppr); diff --git a/src/operation/iSTA/source/module/timing_analyzer/TimingAnalyzer.hpp b/src/operation/iSTA/source/module/timing_analyzer/TimingAnalyzer.hpp index ea13f92473..22b5b7ac3f 100644 --- a/src/operation/iSTA/source/module/timing_analyzer/TimingAnalyzer.hpp +++ b/src/operation/iSTA/source/module/timing_analyzer/TimingAnalyzer.hpp @@ -42,6 +42,8 @@ class TimingAnalyzer TimingAnalyzer& operator=(const TimingAnalyzer& other) = delete; TimingAnalyzer& operator=(TimingAnalyzer&& other) = delete; // function + void analyzeFanoutConstraints(); + TimingCellPort* getFanoutCellPort(Pin& pin); TAModel initTAModel(); bool isDisableArc(Arc& arc); bool shouldStopDataPropagation(Arc& arc); @@ -84,6 +86,7 @@ class TimingAnalyzer AnalysisType getCaptureAnalysisType(AnalysisType analysis_type); TransType getClockTransType(TimingCheckArc& timing_check_arc); double getEndPointCaptureTime(std::string& end_point, AnalysisType analysis_type); + double getEndPointCaptureTime(std::string& start_point, std::string& end_point, AnalysisType analysis_type); double getEndPointClockArrival(std::string& end_point, AnalysisType analysis_type); double getEndPointClockArrival(std::string& end_point, AnalysisType analysis_type, TransType trans_type); double getClockReconvergencePessimism(TimingPathState& end_path_state, std::string& end_point, AnalysisType analysis_type, std::string& common_pin_name); diff --git a/src/operation/iSTA/source/module/timing_propagator/TimingPropagator.cpp b/src/operation/iSTA/source/module/timing_propagator/TimingPropagator.cpp index a713d36004..9a9e43525c 100644 --- a/src/operation/iSTA/source/module/timing_propagator/TimingPropagator.cpp +++ b/src/operation/iSTA/source/module/timing_propagator/TimingPropagator.cpp @@ -20,6 +20,7 @@ #include "DelayCalculator.hpp" #include "Logger.hpp" #include "Monitor.hpp" +#include "Utility.hpp" namespace ista { @@ -342,10 +343,10 @@ double TimingPropagator::getStartPointArrival(std::string& start_point, Analysis if (pin.get_is_port()) { std::map& port_constraint_map = database.get_timing_constraint().get_port_constraint_map(); if (analysis_type == AnalysisType::kMin && port_constraint_map.count(start_point) > 0 && port_constraint_map[start_point].get_has_input_delay_min()) { - return port_constraint_map[start_point].get_input_delay_min(); + return STAUTIL.getLaunchClockEdge(database, start_point, getClockName(start_point)) + port_constraint_map[start_point].get_input_delay_min(); } if (port_constraint_map.count(start_point) > 0 && port_constraint_map[start_point].get_has_input_delay_max()) { - return port_constraint_map[start_point].get_input_delay_max(); + return STAUTIL.getLaunchClockEdge(database, start_point, getClockName(start_point)) + port_constraint_map[start_point].get_input_delay_max(); } return 0.0; } @@ -355,7 +356,8 @@ double TimingPropagator::getStartPointArrival(std::string& start_point, Analysis Instance& instance = database.get_instance_map()[pin.get_instance_name()]; if (instance.get_is_sequential() && start_point == instance.get_clock_pin_name()) { TransType clock_trans_type = getClockTransType(instance.get_clock_to_q_arc()); - return getClockArrival(instance.get_clock_pin_name(), analysis_type, clock_trans_type); + return STAUTIL.getLaunchClockEdge(database, start_point, getClockName(start_point)) + + getClockArrival(instance.get_clock_pin_name(), analysis_type, clock_trans_type); } if (instance.get_is_sequential() && start_point == instance.get_output_pin_name()) { TransType clock_trans_type = getClockTransType(instance.get_clock_to_q_arc()); @@ -370,7 +372,8 @@ double TimingPropagator::getStartPointArrival(std::string& start_point, Analysis dc_task.set_input_slew(clock_slew); STADC.calculate(dc_task); if (dc_task.get_is_valid()) { - return getClockArrival(instance.get_clock_pin_name(), analysis_type, clock_trans_type) + dc_task.get_timing_result().get_delay(); + return STAUTIL.getLaunchClockEdge(database, start_point, getClockName(start_point)) + + getClockArrival(instance.get_clock_pin_name(), analysis_type, clock_trans_type) + dc_task.get_timing_result().get_delay(); } } return 0.0; @@ -459,13 +462,14 @@ double TimingPropagator::getStartPointLaunchTime(std::string& start_point, Analy } Pin& pin = database.get_pin_map()[start_point]; if (pin.get_is_port() || database.get_instance_map().count(pin.get_instance_name()) == 0) { - return 0.0; + return STAUTIL.getLaunchClockEdge(database, start_point, getClockName(start_point)); } Instance& instance = database.get_instance_map()[pin.get_instance_name()]; if (!instance.get_is_sequential() || (start_point != instance.get_output_pin_name() && start_point != instance.get_clock_pin_name())) { return 0.0; } - return getClockArrival(instance.get_clock_pin_name(), analysis_type, getClockTransType(instance.get_clock_to_q_arc())); + return STAUTIL.getLaunchClockEdge(database, start_point, getClockName(start_point)) + + getClockArrival(instance.get_clock_pin_name(), analysis_type, getClockTransType(instance.get_clock_to_q_arc())); } std::string TimingPropagator::getStartPointCrprClockPin(std::string& start_point) @@ -567,7 +571,8 @@ void TimingPropagator::seedPathState(std::string& start_point, AnalysisType anal return; } std::string path_state_start_point = getPathStateStartPoint(start_point); - std::string path_state_tag{getClockName(start_point)}; + std::string clock_name{getClockName(start_point)}; + std::string path_state_tag = STAUTIL.getPathStateTag(database, path_state_start_point, clock_name); TimingPoint& timing_point = database.get_timing_point_map()[start_point]; for (TransType trans_type : {TransType::kRise, TransType::kFall}) { double arrival = getStartPointArrival(start_point, analysis_type, trans_type); @@ -581,7 +586,7 @@ void TimingPropagator::seedPathState(std::string& start_point, AnalysisType anal path_state.set_slew(getStartPointSlew(start_point, analysis_type, trans_type)); path_state.set_launch_time(getStartPointLaunchTime(start_point, analysis_type, trans_type)); path_state.set_start_point(path_state_start_point); - path_state.set_clock_name(path_state_tag); + path_state.set_clock_name(clock_name); path_state.set_crpr_clock_pin(getStartPointCrprClockPin(start_point)); path_state.get_predecessor().clear(); path_state.set_predecessor_arc_idx(std::numeric_limits::max()); @@ -710,7 +715,7 @@ void TimingPropagator::propagatePathStateArc(std::size_t arc_idx, AnalysisType a } double arc_delay = getArcDelay(arc, analysis_type, input_trans_type, output_trans_type); double candidate_arrival = roundTime(source_path_state.get_arrival() + arc_delay); - const std::string& path_state_tag = source_path_state.get_clock_name(); + const std::string& path_state_tag = source_path_state_pair.first; std::map& sink_path_state_map = getPathStateMap(sink_point, analysis_type, source_type, output_trans_type); if (sink_path_state_map.count(path_state_tag) == 0 || isBetterArrival(candidate_arrival, sink_path_state_map[path_state_tag].get_arrival(), analysis_type)) { diff --git a/src/operation/iSTA/source/module/timing_reporter/TimingReporter.cpp b/src/operation/iSTA/source/module/timing_reporter/TimingReporter.cpp index 47639f8b60..c62d4f166b 100644 --- a/src/operation/iSTA/source/module/timing_reporter/TimingReporter.cpp +++ b/src/operation/iSTA/source/module/timing_reporter/TimingReporter.cpp @@ -168,6 +168,7 @@ void TimingReporter::outputTimingReportList() } } if (output_reports || output_features) { + outputFanoutReport(); outputQorSummaryReport(); } if (output_features) { @@ -343,6 +344,47 @@ bool TimingReporter::isMatchReportSlack(TimingPath& timing_path) return true; } +void TimingReporter::outputFanoutReport() +{ + const std::vector& checks = STADM.getDatabase().get_fanout_check_list(); + const std::string directory = STADM.getConfig().tr_temp_directory_path; + if (STADM.getConfig().output_timing_reports != 0) { + std::ofstream* report = STAUTIL.getOutputFileStream(directory + "max_fanout.rpt"); + (*report) << "Maximum fanout checks (library fanout units)\n" + << "Driver Net Fanout_load Limit Slack Status\n"; + for (const TimingFanoutCheck& check : checks) { + (*report) << check.get_driver_pin() << " " << check.get_net_name() << " " << std::fixed << std::setprecision(6) << check.get_fanout_load() << " " + << check.get_limit() << " " << check.get_slack() << " " << (check.get_slack() < -STA_ERROR ? "VIOLATED" : "MET") << "\n"; + } + (*report) << "Checked drivers: " << checks.size() << "; violations: " << getFanoutViolationNum() << "\n"; + STAUTIL.closeFileStream(report); + } + if (STADM.getConfig().output_timing_features != 0) { + std::ofstream* json = STAUTIL.getOutputFileStream(directory + "max_fanout.json"); + (*json) << "{\"violation_count\":" << getFanoutViolationNum() << ",\"checks\":["; + bool first = true; + for (const TimingFanoutCheck& check : checks) { + (*json) << (first ? "" : ",") << "{\"driver\":\"" << escapeJsonString(check.get_driver_pin()) << "\",\"net\":\"" << escapeJsonString(check.get_net_name()) + << "\",\"fanout_load\":"; + outputJsonNumber(json, check.get_fanout_load()); + (*json) << ",\"limit\":"; + outputJsonNumber(json, check.get_limit()); + (*json) << ",\"slack\":"; + outputJsonNumber(json, check.get_slack()); + (*json) << "}"; + first = false; + } + (*json) << "]}\n"; + STAUTIL.closeFileStream(json); + } +} + +int32_t TimingReporter::getFanoutViolationNum() +{ + const std::vector& checks = STADM.getDatabase().get_fanout_check_list(); + return static_cast(std::count_if(checks.begin(), checks.end(), [](const TimingFanoutCheck& check) { return check.get_slack() < -STA_ERROR; })); +} + void TimingReporter::outputQorSummaryReport() { Database& database = STADM.getDatabase(); @@ -433,6 +475,7 @@ void TimingReporter::outputQorSummaryReport() worst_frequency = getQorFrequencyString(setup_frequency_map[setup_group_list.front()]); } + const int32_t fanout_violations = getFanoutViolationNum(); const int32_t cell_area = getQorCellArea(); const int32_t leaf_cell_k = getQorLeafCellK(); auto output_qor_summary_json = [&]() { @@ -529,8 +572,8 @@ void TimingReporter::outputQorSummaryReport() output_double(total_hold_tns, 1); (*json_file) << ",\"nvp\":" << total_hold_nvp << "}"; } - (*json_file) << "},\n \"design_statistics\": {\"cap\":0,\"fanout\":0,\"tran\":0,\"tdrc\":0,\"cella\":" << cell_area - << ",\"bufs\":null,\"leafs_k\":" << leaf_cell_k << ",\"tnets_k\":null,\"ctbuf\":null,\"regs\":null}\n}\n"; + (*json_file) << "},\n \"design_statistics\": {\"cap\":0,\"fanout\":" << fanout_violations << ",\"tran\":0,\"tdrc\":" << fanout_violations + << ",\"cella\":" << cell_area << ",\"bufs\":null,\"leafs_k\":" << leaf_cell_k << ",\"tnets_k\":null,\"ctbuf\":null,\"regs\":null}\n}\n"; STAUTIL.closeFileStream(json_file); }; @@ -543,13 +586,6 @@ void TimingReporter::outputQorSummaryReport() std::string report_file_path = getQorSummaryReportFilePath(); std::ofstream* report_file = STAUTIL.getOutputFileStream(report_file_path); - if (group_set.empty()) { - STAUTIL.closeFileStream(report_file); - if (STADM.getConfig().output_timing_features != 0) { - output_qor_summary_json(); - } - return; - } (*report_file) << std::left << std::setw(max_group_length) << "Path Group" << std::right << std::setw(11) << "WNS" << std::setw(11) << "TNS" << std::setw(8) << "NVP" << std::setw(10) << "FREQ" << " " << std::setw(8) << "WNS(H)" << std::setw(11) << "TNS(H)" << std::setw(8) << "NVP(H)" << "\n"; (*report_file) << bar << "\n"; @@ -588,9 +624,9 @@ void TimingReporter::outputQorSummaryReport() << "CELLA" << std::setw(8) << "BUFS" << std::setw(10) << "LEAFS" << std::setw(12) << "TNETS" << std::setw(11) << "CTBUF" << std::setw(8) << "REGS" << "\n"; (*report_file) << bar << "\n"; - (*report_file) << std::setw(7) << 0 << std::setw(8) << 0 << std::setw(8) << 0 << std::setw(drc_column_width + 1) << 0 << std::setw(11) << cell_area - << getQorNilString(7) << "K" << getQorKString(leaf_cell_k, 10) << getQorNilString(11) << "K" << getQorNilString(11) << getQorNilString(8) - << "\n"; + (*report_file) << std::setw(7) << 0 << std::setw(8) << fanout_violations << std::setw(8) << 0 << std::setw(drc_column_width + 1) << fanout_violations + << std::setw(11) << cell_area << getQorNilString(7) << "K" << getQorKString(leaf_cell_k, 10) << getQorNilString(11) << "K" + << getQorNilString(11) << getQorNilString(8) << "\n"; (*report_file) << bar << "\n"; (*report_file) << "\n"; (*report_file) << "NVP - No. of Violating Paths\n"; @@ -678,7 +714,8 @@ void TimingReporter::outputTimingPathJson(std::ofstream* json_file, TimingPath& (*json_file) << "{\"path_id\":\"" << escapeJsonString(getTimingPathId(timing_path, path_group_name, delay_type)) << "\",\"analysis_type\":\"" << analysis_type << "\",\"path_group\":\"" << escapeJsonString(path_group_name) << "\",\"start_point\":\"" << escapeJsonString(timing_path.get_start_point()) << "\",\"end_point\":\"" << escapeJsonString(timing_path.get_end_point()) << "\",\"launch_clock\":\"" << escapeJsonString(clock_name) - << "\",\"capture_clock\":\"" << escapeJsonString(clock_name) << "\",\"check_type\":\"" << escapeJsonString(check_type) << "\",\"slack_ns\":"; + << "\",\"capture_clock\":\"" << escapeJsonString(timing_path.get_capture_clock_name()) << "\",\"check_type\":\"" << escapeJsonString(check_type) + << "\",\"slack_ns\":"; outputJsonNumber(json_file, timing_path.get_slack()); (*json_file) << ",\"arrival_ns\":"; outputJsonNumber(json_file, timing_path.get_path_delay()); @@ -720,7 +757,7 @@ std::string TimingReporter::getTimingPathId(TimingPath& timing_path, std::string const std::string corner = STADM.getConfig().timing_corner.empty() ? "unknown" : STADM.getConfig().timing_corner; const std::string clock_name = getClockName(timing_path); return stableTimingPathId(STAUTIL.getString(corner, "|", analysis_type, "|", path_group_name, "|", timing_path.get_start_point(), "|", - timing_path.get_end_point(), "|", clock_name, "|", clock_name)); + timing_path.get_end_point(), "|", clock_name, "|", timing_path.get_capture_clock_name())); } std::vector TimingReporter::getQorTimingPathList(TimingPathGroup& timing_path_group, DelayType delay_type) @@ -985,7 +1022,10 @@ std::string TimingReporter::getStartPointText(TimingPath& timing_path) if (isPort(timing_path.get_start_point())) { return STAUTIL.getString(start_point, " (input port clocked by ", clock_name, ")"); } - return STAUTIL.getString(start_point, " (rising edge-triggered flip-flop clocked by ", clock_name, ")"); + return STAUTIL.getString(start_point, + STAUTIL.getLaunchClockTransition(database, start_point) == TransType::kFall ? " (falling edge-triggered flip-flop clocked by " + : " (rising edge-triggered flip-flop clocked by ", + clock_name, ")"); } bool TimingReporter::isInternalStartPoint(TimingPath& timing_path) @@ -1049,7 +1089,7 @@ std::string TimingReporter::getEndPointText(TimingPath& timing_path) { Database& database = STADM.getDatabase(); Pin& end_pin = database.get_pin_map()[timing_path.get_end_point()]; - std::string clock_name = getClockName(timing_path); + std::string clock_name = timing_path.get_capture_clock_name(); std::string end_point = getPTPinName(timing_path.get_end_point()); if (!end_pin.get_is_port()) { end_point = end_pin.get_instance_name(); @@ -1068,7 +1108,10 @@ std::string TimingReporter::getEndPointCheckText(std::string& end_point, std::st if (timing_path.get_check_type() == TimingCheckType::kRemoval) { return STAUTIL.getString(end_point, " (removal check against rising-edge clock ", clock_name, ")"); } - return STAUTIL.getString(end_point, " (rising edge-triggered flip-flop clocked by ", clock_name, ")"); + return STAUTIL.getString(end_point, + timing_path.get_capture_clock_transition() == TransType::kFall ? " (falling edge-triggered flip-flop clocked by " + : " (rising edge-triggered flip-flop clocked by ", + clock_name, ")"); } std::size_t TimingReporter::outputTimingPointList(std::ofstream* report_file, TimingPath& timing_path, DelayType delay_type) @@ -1164,7 +1207,7 @@ void TimingReporter::outputLaunchClockInfo(std::ofstream* report_file, TimingPat std::string clock_name = getClockName(timing_path); double launch_time = timing_path.get_launch_time(); double launch_clock_network_delay = timing_path.get_launch_clock_network_delay(); - double launch_clock_edge = isClockSourceStartPoint(timing_path.get_start_point()) ? launch_time : 0.0; + double launch_clock_edge = launch_time - launch_clock_network_delay; outputTimingLine(report_file, STAUTIL.getString("clock ", clock_name, " (", getLaunchClockEdgeText(timing_path, delay_type), " edge)"), launch_clock_edge, launch_clock_edge, true, "", label_width); if (isClockSourceStartPoint(timing_path.get_start_point())) { @@ -1193,7 +1236,7 @@ std::string TimingReporter::getLaunchClockEdgeText(TimingPath& timing_path, Dela if (isClockSourceStartPoint(timing_path.get_start_point()) && delay_type == DelayType::kMax && timing_path.get_trans_type() == TransType::kFall) { return GetTransTypeName()(TransType::kFall); } - return GetTransTypeName()(TransType::kRise); + return GetTransTypeName()(STAUTIL.getLaunchClockTransition(STADM.getDatabase(), timing_path.get_start_point())); } void TimingReporter::outputTimingLine(std::ofstream* report_file, std::string_view label, double incr, double path, bool has_incr, std::string transition, @@ -1349,16 +1392,19 @@ std::string TimingReporter::getPTCellName(TimingPathPoint& path_point) void TimingReporter::outputRequiredClockInfo(std::ofstream* report_file, TimingPath& timing_path, DelayType delay_type, std::size_t label_width) { Database& database = STADM.getDatabase(); - std::string clock_name = getClockName(timing_path); + std::string clock_name = timing_path.get_capture_clock_name(); double capture_time = timing_path.get_capture_time(); - double clock_edge = delay_type == DelayType::kMin ? 0.0 : getClockPeriod(clock_name); + double clock_edge = capture_time - timing_path.get_capture_clock_network_delay() - timing_path.get_clock_reconvergence_pessimism(); double capture_clock_network_delay = timing_path.get_capture_clock_network_delay(); - outputTimingLine(report_file, STAUTIL.getString("clock ", clock_name, " (rise edge)"), clock_edge, clock_edge, true, "", label_width); + outputTimingLine(report_file, + STAUTIL.getString("clock ", clock_name, timing_path.get_capture_clock_transition() == TransType::kFall ? " (fall edge)" : " (rise edge)"), + clock_edge, clock_edge, true, "", label_width); outputTimingLine(report_file, getClockNetworkDelayLabel(timing_path), capture_clock_network_delay, clock_edge + capture_clock_network_delay, true, "", label_width); outputTimingLine(report_file, "clock reconvergence pessimism", timing_path.get_clock_reconvergence_pessimism(), capture_time, true, "", label_width); if (!timing_path.get_capture_clock_pin().empty()) { - outputTimingLine(report_file, getPinLabel(timing_path.get_capture_clock_pin()), 0.0, capture_time, false, "r", label_width); + outputTimingLine(report_file, getPinLabel(timing_path.get_capture_clock_pin()), 0.0, capture_time, false, + GetTransTypeInitial()(timing_path.get_capture_clock_transition()), label_width); } double uncertainty = getClockUncertainty(clock_name, delay_type); if (uncertainty > STA_ERROR) { diff --git a/src/operation/iSTA/source/module/timing_reporter/TimingReporter.hpp b/src/operation/iSTA/source/module/timing_reporter/TimingReporter.hpp index 1a065847c5..a5f372a1eb 100644 --- a/src/operation/iSTA/source/module/timing_reporter/TimingReporter.hpp +++ b/src/operation/iSTA/source/module/timing_reporter/TimingReporter.hpp @@ -56,6 +56,8 @@ class TimingReporter std::vector getSortedTimingPathList(TimingPathGroup& timing_path_group, DelayType delay_type, StartEndType start_end_type); std::vector getEndpointWorstTimingPathList(std::vector& timing_path_list); bool isMatchReportSlack(TimingPath& timing_path); + void outputFanoutReport(); + int32_t getFanoutViolationNum(); void outputQorSummaryReport(); std::string getQorSummaryReportFilePath(); std::string getQorSummaryJsonFilePath(); diff --git a/src/operation/iSTA/source/toolkit/utility/Utility.cpp b/src/operation/iSTA/source/toolkit/utility/Utility.cpp index 4e8309c955..834529d374 100644 --- a/src/operation/iSTA/source/toolkit/utility/Utility.cpp +++ b/src/operation/iSTA/source/toolkit/utility/Utility.cpp @@ -16,6 +16,8 @@ // *************************************************************************************** #include "Utility.hpp" +#include "Database.hpp" + namespace ista { // public @@ -43,6 +45,125 @@ void Utility::destroyInst() } } +// Keep candidates with different false-path applicability in separate arrival tags. +// A false worst path must not hide a slower/faster valid path from the same clock. +std::string Utility::getPathStateTag(Database& database, std::string_view start, std::string_view clock) +{ + std::string tag(clock); + if (getLaunchClockTransition(database, start) == TransType::kFall) { + tag += "\x1e"; + } + std::size_t index = 0; + for (const TimingException& exception : database.get_timing_constraint().get_false_path_list()) { + if (!exception.get_from_objects().empty() && matchesTimingObjects(database, exception.get_from_objects(), start, clock, true)) { + tag += "\x1f" + std::to_string(index); + } + ++index; + } + return tag; +} + +TransType Utility::getLaunchClockTransition(Database& database, std::string_view start) +{ + const auto pin = database.get_pin_map().find(std::string(start)); + if (pin != database.get_pin_map().end() && !pin->second.get_is_port()) { + const auto instance = database.get_instance_map().find(pin->second.get_instance_name()); + if (instance != database.get_instance_map().end() && instance->second.get_is_sequential()) { + std::vector& arcs = instance->second.get_clock_to_q_arc().get_timing_arc_list(); + if (!arcs.empty() && arcs.front().get_trigger_trans_type() == TransType::kFall) { + return TransType::kFall; + } + } + } + return TransType::kRise; +} + +double Utility::getLaunchClockEdge(Database& database, std::string_view start, std::string_view clock) +{ + const auto definition = database.get_timing_constraint().get_clock_map().find(std::string(clock)); + if (definition == database.get_timing_constraint().get_clock_map().end()) { + return 0.0; + } + return getLaunchClockTransition(database, start) == TransType::kFall ? definition->second.get_fall_edge() : definition->second.get_rise_edge(); +} + +double Utility::getClockEdgeSeparation(double launch_period, double capture_period, double launch_edge, double capture_edge, AnalysisType type) +{ + // All relative edge positions repeat modulo the greatest common period. + // Use a floating Euclidean algorithm to retain rational divide/multiply ratios. + double interval = std::max(launch_period, capture_period); + double remainder = std::min(launch_period, capture_period); + const double tolerance = interval * 1e-10; + if (!std::isfinite(interval) || remainder <= 0) { + throw std::invalid_argument("invalid clock period"); + } + for (int iteration = 0; remainder > tolerance && iteration < 128; ++iteration) { + const double next = std::fmod(interval, remainder); + interval = remainder; + remainder = std::min(next, remainder - next); + } + double separation = std::fmod(capture_edge - launch_edge, interval); + if (separation < 0) { + separation += interval; + } + if (separation < tolerance || interval - separation < tolerance) { + separation = 0; + } + if (type == AnalysisType::kMax) { + return separation == 0 ? interval : separation; + } + return separation == 0 ? 0 : separation - interval; +} + +bool Utility::isFalsePath(Database& database, std::string_view start, std::string_view launch_clock, std::string_view end, std::string_view capture_clock, + AnalysisType type) +{ + for (const TimingClockGroup& relation : database.get_timing_constraint().get_clock_group_list()) { + if (relation.get_allow_paths() || launch_clock.empty() || capture_clock.empty()) { + continue; + } + int launch_group = -1; + int capture_group = -1; + const std::vector>& groups = relation.get_groups(); + for (std::size_t i = 0; i < groups.size(); ++i) { + if (groups[i].contains(std::string(launch_clock))) { + launch_group = static_cast(i); + } + if (groups[i].contains(std::string(capture_clock))) { + capture_group = static_cast(i); + } + } + if (launch_group != capture_group && ((launch_group >= 0 && capture_group >= 0) || groups.size() == 1)) { + return true; + } + } + for (const TimingException& exception : database.get_timing_constraint().get_false_path_list()) { + if ((type == AnalysisType::kMax && exception.get_setup()) || (type == AnalysisType::kMin && exception.get_hold())) { + if (matchesTimingObjects(database, exception.get_from_objects(), start, launch_clock, true) + && matchesTimingObjects(database, exception.get_to_objects(), end, capture_clock, false)) { + return true; + } + } + } + return false; +} + +bool Utility::matchesTimingObjects(Database& database, const std::set& objects, std::string_view pin_name, std::string_view clock_name, bool start) +{ + if (objects.empty() || objects.contains(std::string(pin_name)) || objects.contains(std::string(clock_name))) { + return true; + } + const auto pin = database.get_pin_map().find(std::string(pin_name)); + if (pin == database.get_pin_map().end() || pin->second.get_is_port()) { + return false; + } + const auto instance = database.get_instance_map().find(pin->second.get_instance_name()); + if (instance == database.get_instance_map().end()) { + return false; + } + return objects.contains(instance->first) || (start && objects.contains(instance->second.get_clock_pin_name())); +} + // private Utility* Utility::_util_instance = nullptr; diff --git a/src/operation/iSTA/source/toolkit/utility/Utility.hpp b/src/operation/iSTA/source/toolkit/utility/Utility.hpp index 8f0b18eda9..1d9cf77a18 100644 --- a/src/operation/iSTA/source/toolkit/utility/Utility.hpp +++ b/src/operation/iSTA/source/toolkit/utility/Utility.hpp @@ -16,11 +16,15 @@ // *************************************************************************************** #pragma once +#include "AnalysisType.hpp" #include "Logger.hpp" #include "STAHeader.hpp" +#include "TransType.hpp" namespace ista { +class Database; + #define STAUTIL (ista::Utility::getInst()) class Utility @@ -30,6 +34,14 @@ class Utility static Utility& getInst(); static void destroyInst(); // function + static std::string getPathStateTag(Database& database, std::string_view start, std::string_view clock); + static TransType getLaunchClockTransition(Database& database, std::string_view start); + static double getLaunchClockEdge(Database& database, std::string_view start, std::string_view clock); + static double getClockEdgeSeparation(double launch_period, double capture_period, double launch_edge, double capture_edge, AnalysisType type); + static bool isFalsePath(Database& database, std::string_view start, std::string_view launch_clock, std::string_view end, std::string_view capture_clock, + AnalysisType type); + static bool matchesTimingObjects(Database& database, const std::set& objects, std::string_view pin_name, std::string_view clock_name, + bool start); #if 1 // std数据结构工具函数