diff --git a/BUILD.bazel b/BUILD.bazel index f2fcf1639..945df266c 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -135,6 +135,8 @@ tcl_srcs = [ "sdf/Sdf.tcl", "verilog/Verilog.tcl", "spice/WriteSpice.tcl", + # OpenROAD fork: analysis_corner support (must follow tcl/Sta.tcl). + "tcl/AnalysisCorner.tcl", ] exports_files([ @@ -197,6 +199,8 @@ filegroup( "power/Power.i", "sdc/Sdc.i", "sdf/Sdf.i", + # OpenROAD fork: analysis_corner support. + "search/AnalysisCorner.i", "search/Property.i", "search/Search.i", "spice/WriteSpice.i", diff --git a/CMakeLists.txt b/CMakeLists.txt index f8ddda840..7c66118c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -201,6 +201,8 @@ set(STA_SOURCE search/ReportPath.cc search/Search.cc search/SearchPred.cc + # OpenROAD fork: analysis_corner support. + search/AnalysisCorner.cc search/Scene.cc search/Sim.cc search/Sta.cc @@ -271,6 +273,8 @@ set(STA_TCL_FILES search/Search.tcl spice/WriteSpice.tcl verilog/Verilog.tcl + # OpenROAD fork: analysis_corner support (must follow tcl/Sta.tcl). + tcl/AnalysisCorner.tcl ) ################################################################ @@ -491,6 +495,8 @@ set(SWIG_FILES ${STA_HOME}/tcl/StaTclTypes.i ${STA_HOME}/util/Util.i ${STA_HOME}/verilog/Verilog.i + # OpenROAD fork: analysis_corner support. + ${STA_HOME}/search/AnalysisCorner.i ) set_property(SOURCE ${STA_SWIG_FILE} diff --git a/app/StaApp.i b/app/StaApp.i index bce8cdbb9..30224815d 100644 --- a/app/StaApp.i +++ b/app/StaApp.i @@ -37,6 +37,8 @@ %include "sdf/Sdf.i" %include "search/Search.i" %include "search/Property.i" +// OpenROAD fork: analysis_corner support. +%include "search/AnalysisCorner.i" %include "util/Util.i" %include "spice/WriteSpice.i" %include "verilog/Verilog.i" diff --git a/include/sta/AnalysisCorner.hh b/include/sta/AnalysisCorner.hh new file mode 100644 index 000000000..c21dff736 --- /dev/null +++ b/include/sta/AnalysisCorner.hh @@ -0,0 +1,208 @@ +// OpenSTA, Static Timing Analyzer +// Copyright (c) 2026, Parallax Software, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. +// +// Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// +// This notice may not be removed or altered from any source distribution. + +// OpenROAD fork: analysis_corner support. + +#pragma once + +#include +#include +#include +#include + +#include "SdcClass.hh" +#include "StringUtil.hh" + +namespace sta { + +class AnalysisCorner; +class Edge; +class Mode; +class Scene; +class Sta; + +// Same aliases as Sdc.hh (alias redeclaration to the same type is legal); +// avoids including Sdc.hh here. +using InputDelaySet = std::set; +using OutputDelaySet = std::set; + +using AnalysisCornerSeq = std::vector; +using AnalysisCornerNameMap = std::map>; + +// Register the "analysis_corner" property on scene objects. +// Called once from Sta::makeComponents. +void +defineAnalysisCornerProperties(Sta *sta); + +// Corner-overlay IO delay lookups (defined in search/AnalysisCorner.cc). +// Override semantics are wholesale per pin: when a scene's corner overlay +// defines any input (output) delays on a pin, they replace the mode's +// delays on that pin for that scene; otherwise the mode's delays apply. + +// The overlay's delay set for the pin, or nullptr when the scene has no +// corner, no overlay, or no overlay delays on the pin. +InputDelaySet *cornerInputDelaysLeafPin(const Scene *scene, + const Pin *pin); +OutputDelaySet *cornerOutputDelaysLeafPin(const Scene *scene, + const Pin *pin); +// True when input_delay applies to this scene under the override rule. +// A nullptr input_delay stands for the default (unconstrained) arrival +// seed, which applies only to scenes with no effective delays on the pin. +bool sceneSeesInputDelay(const Scene *scene, + const Pin *pin, + const InputDelay *input_delay); +// Distinct corner-overlay delay sets on the pin across the mode's scenes. +std::vector cornerInputDelaySets(const Mode *mode, + const Pin *pin); +std::vector cornerOutputDelaySets(const Mode *mode, + const Pin *pin); +bool modeHasCornerInputDelay(const Mode *mode, + const Pin *pin); +bool modeHasCornerOutputDelay(const Mode *mode, + const Pin *pin); + +// Corner-overlay clock uncertainty lookups (defined in +// search/AnalysisCorner.cc). Selection order at the read sites mirrors +// upstream specificity (pin beats clock) with the corner beating the mode +// at equal specificity. + +// Overlay's set_clock_uncertainty on the pin, or nullptr. +const ClockUncertainties *cornerPinClockUncertainties(const Scene *scene, + const Pin *pin); +// Corner's set_clock_uncertainty on the clock, or nullptr. +const ClockUncertainties *cornerClkUncertainties(const Scene *scene, + const Clock *clk); +// Corner-overlay clock latency/insertion lookups (defined in +// search/AnalysisCorner.cc). Per-lookup fallback: exists=false when the +// scene has no corner, no overlay, or the overlay defines no value; the +// read sites then keep the mode's value. Upstream pin-beats-clock +// precedence is replicated at the sites. +void cornerClockLatencyPin(const Scene *scene, + const Clock *clk, + const Pin *pin, + const RiseFall *rf, + const MinMax *min_max, + // Return values. + float &latency, + bool &exists); +void cornerClockLatencyClk(const Scene *scene, + const Clock *clk, + const RiseFall *rf, + const MinMax *min_max, + // Return values. + float &latency, + bool &exists); +void cornerClockLatencyEdge(const Scene *scene, + Edge *edge, + const RiseFall *rf, + const MinMax *min_max, + // Return values. + float &latency, + bool &exists); +// pin may be nullptr for clock-level insertion. +void cornerClockInsertion(const Scene *scene, + const Clock *clk, + const Pin *pin, + const RiseFall *rf, + const MinMax *min_max, + const EarlyLate *early_late, + // Return values. + float &insertion, + bool &exists); + +// Overlay's inter-clock uncertainty; exists=false when the overlay does +// not define a value for this transition/min_max combination. +void cornerInterClockUncertainty(const Scene *scene, + const Clock *src_clk, + const RiseFall *src_rf, + const Clock *tgt_clk, + const RiseFall *tgt_rf, + const MinMax *min_max, + // Return values. + float &uncertainty, + bool &exists); + +// PVT/RC analysis corner. Groups the scenes that share an operating +// point across modes and bundles the liberty/spef authoring data +// define_scene composes from. Liberty maps, parasitics bindings and +// SDC remain on Scene/Mode. +class AnalysisCorner +{ +public: + AnalysisCorner(std::string_view name, + size_t index); + const std::string &name() const { return name_; } + size_t index() const { return index_; } + const StringSeq &libertyMinFiles() const { return liberty_min_files_; } + const StringSeq &libertyMaxFiles() const { return liberty_max_files_; } + const std::string &spefMinName() const { return spef_min_name_; } + const std::string &spefMaxName() const { return spef_max_name_; } + const StringSeq &sdcFiles() const { return sdc_files_; } + void setLiberty(const StringSeq &min_files, + const StringSeq &max_files) + { + liberty_min_files_ = min_files; + liberty_max_files_ = max_files; + } + void setSpef(const std::string &min_name, + const std::string &max_name) + { + spef_min_name_ = min_name; + spef_max_name_ = max_name; + } + void setSdc(const StringSeq &sdc_files) { sdc_files_ = sdc_files; } + + // Corner-scoped set_clock_uncertainty on a clock. Stored here keyed by + // the mode's Clock object because upstream stores clock uncertainty on + // the (corner-shared) Clock itself, not in the Sdc. Wholesale override: + // a corner entry replaces the clock's own uncertainties entirely. + const ClockUncertainties *clockUncertainty(const Clock *clk) const; + void setClockUncertainty(const Clock *clk, + const MinMaxAll *setup_hold, + float uncertainty); + void removeClockUncertainty(const Clock *clk, + const MinMaxAll *setup_hold); + // Lifecycle: the map keys are the mode's Clock objects, which die on + // remove_clock / Sta::clear. + void removeClockUncertainties(const Clock *clk) { clk_uncertainties_.erase(clk); } + void clearClockUncertainties() { clk_uncertainties_.clear(); } + +private: + std::string name_; + size_t index_; + // Liberty names/filenames and read_spef -name keys, composed into + // define_scene when the scene names this corner. + StringSeq liberty_min_files_; + StringSeq liberty_max_files_; + std::string spef_min_name_; + std::string spef_max_name_; + // Corner-scoped SDC files, applied to the (mode, corner) overlay Sdc + // when the first scene naming this corner is defined + // (see tcl/AnalysisCorner.tcl). + StringSeq sdc_files_; + // Corner-scoped clock uncertainties keyed by the mode's Clock objects. + std::map clk_uncertainties_; +}; + +} // namespace sta diff --git a/include/sta/Mode.hh b/include/sta/Mode.hh index b3a2b9c04..e6eba3548 100644 --- a/include/sta/Mode.hh +++ b/include/sta/Mode.hh @@ -24,12 +24,16 @@ #pragma once +// OpenROAD fork: analysis_corner support. +#include #include #include "StaState.hh" namespace sta { +// OpenROAD fork: analysis_corner support. +class AnalysisCorner; class Sdc; class Sim; class ClkNetwork; @@ -82,6 +86,18 @@ public: bool unconstrained_paths); void deletePathGroups(); + // ---- OpenROAD fork: analysis_corner support (begin) ---- + // Sparse overlay Sdc per analysis corner holding corner-scoped + // constraints (currently timing derates). Lazily created; nullptr + // until a corner-scoped SDC command runs. Definitions live in + // search/AnalysisCorner.cc. + Sdc *cornerSdc(const AnalysisCorner *corner) const; + Sdc *makeCornerSdc(const AnalysisCorner *corner); + void clearCornerSdcs(); + const std::map &cornerSdcs() const + { return corner_sdcs_; } + // ---- OpenROAD fork: analysis_corner support (end) ---- + private: std::string name_; size_t mode_index_; @@ -91,6 +107,9 @@ private: ClkNetwork *clk_network_; Genclks *genclks_; PathGroups *path_groups_{nullptr}; + // ---- OpenROAD fork: analysis_corner support (begin) ---- + std::map corner_sdcs_; + // ---- OpenROAD fork: analysis_corner support (end) ---- StaState *sta_; }; diff --git a/include/sta/PathEnd.hh b/include/sta/PathEnd.hh index be54fa735..9f8876ab2 100644 --- a/include/sta/PathEnd.hh +++ b/include/sta/PathEnd.hh @@ -188,12 +188,16 @@ public: const ClockEdge *tgt_clk_edge, const Path *tgt_clk_path, const TimingRole *check_role, - const Sdc *sdc); + const Sdc *sdc, + // OpenROAD fork: analysis_corner support. + const Scene *scene = nullptr); // Non inter-clock uncertainty. static float checkTgtClkUncertainty(const Path *tgt_clk_path, const ClockEdge *tgt_clk_edge, const TimingRole *check_role, - const StaState *sta); + const StaState *sta, + // OpenROAD fork: analysis_corner support. + const Scene *scene = nullptr); static float checkSetupMcpAdjustment(const ClockEdge *src_clk_edge, const ClockEdge *tgt_clk_edge, const MultiCyclePath *mcp, @@ -207,7 +211,9 @@ protected: const TimingRole *check_role, const Sdc *sdc, float &uncertainty, - bool &exists); + bool &exists, + // OpenROAD fork: analysis_corner support. + const Scene *scene = nullptr); static float outputDelayMargin(OutputDelay *output_delay, const Path *path, const StaState *sta); diff --git a/include/sta/Scene.hh b/include/sta/Scene.hh index 2e2041d39..ba7aecbfb 100644 --- a/include/sta/Scene.hh +++ b/include/sta/Scene.hh @@ -33,6 +33,8 @@ namespace sta { +// OpenROAD fork: analysis_corner support. +class AnalysisCorner; class Mode; class Sdc; class MinMax; @@ -85,12 +87,23 @@ public: static ModeSet modeSet(const SceneSeq &scenes); static ModeSeq modesSorted(const SceneSeq &scenes); + // ---- OpenROAD fork: analysis_corner support (begin) ---- + AnalysisCorner *analysisCorner() const { return analysis_corner_; } + void setAnalysisCorner(AnalysisCorner *corner) { analysis_corner_ = corner; } + // This scene's corner overlay Sdc when it defines timing derates, + // else nullptr. Defined in search/AnalysisCorner.cc. + const Sdc *sdcOverlayForDerate() const; + // ---- OpenROAD fork: analysis_corner support (end) ---- + protected: std::string name_; size_t index_; Mode *mode_; std::array liberty_; std::array parasitics_; + // ---- OpenROAD fork: analysis_corner support (begin) ---- + AnalysisCorner *analysis_corner_{nullptr}; + // ---- OpenROAD fork: analysis_corner support (end) ---- friend class Scenes; }; diff --git a/include/sta/Sdc.hh b/include/sta/Sdc.hh index e585e4402..ef0990b5c 100644 --- a/include/sta/Sdc.hh +++ b/include/sta/Sdc.hh @@ -1428,6 +1428,19 @@ protected: WireloadMode wireload_mode_; const WireloadSelection *wireload_selection_[MinMax::index_count]; + // ---- OpenROAD fork: analysis_corner support (begin) ---- +public: + // True when any timing derates are set. Used to decide whether an + // analysis corner overlay Sdc overrides the mode Sdc for derate queries. + bool hasDeratingFactors() const + { + return derating_factors_ != nullptr + || !net_derating_factors_.empty() + || !inst_derating_factors_.empty() + || !cell_derating_factors_.empty(); + } + // ---- OpenROAD fork: analysis_corner support (end) ---- + private: friend class WriteSdc; friend class FindNetCaps; diff --git a/include/sta/Search.hh b/include/sta/Search.hh index 5661a387c..390ea4cd6 100644 --- a/include/sta/Search.hh +++ b/include/sta/Search.hh @@ -304,7 +304,9 @@ public: const RiseFall *rf, const MinMax *min_max, const EarlyLate *early_late, - const Mode *mode) const; + const Mode *mode, + // OpenROAD fork: analysis_corner support. + const Scene *scene = nullptr) const; [[nodiscard]] bool propagateClkSense(const Pin *from_pin, Path *from_path, const RiseFall *to_rf); diff --git a/include/sta/Sta.hh b/include/sta/Sta.hh index 274fac6b1..41dc61298 100644 --- a/include/sta/Sta.hh +++ b/include/sta/Sta.hh @@ -29,6 +29,8 @@ #include #include +// OpenROAD fork: analysis_corner support. +#include "AnalysisCorner.hh" #include "ArcDelayCalc.hh" #include "CircuitSim.hh" #include "GraphClass.hh" @@ -148,6 +150,38 @@ public: ModeSeq findModes(const std::string &mode_name) const; Sdc *cmdSdc() const; + // ---- OpenROAD fork: analysis_corner support (begin) ---- + // Definitions live in search/AnalysisCorner.cc. + AnalysisCorner *makeAnalysisCorner(std::string_view name); + AnalysisCorner *findAnalysisCorner(std::string_view name) const; + // Pattern match name. + AnalysisCornerSeq findAnalysisCorners(const std::string &pattern) const; + void setSceneAnalysisCorner(Scene *scene, + AnalysisCorner *corner); + void deleteAnalysisCorners(); + // Scope SDC commands to a corner overlay Sdc of the command mode + // (nullptr restores mode scope). Mirrors setCmdMode. + void setCmdAnalysisCorner(AnalysisCorner *corner) { cmd_analysis_corner_ = corner; } + AnalysisCorner *cmdAnalysisCorner() const { return cmd_analysis_corner_; } + // Sdc that corner-scoped SDC *write* commands target: the corner overlay + // when a corner scope is active, else the command mode Sdc. Read/query + // commands keep using cmdSdc() so names (clocks, ...) resolve against + // the mode Sdc. Defined in search/AnalysisCorner.cc. + Sdc *cmdCornerSdc() const; + // Corner-scoped set_clock_uncertainty on a clock (stored on the corner, + // not the shared Clock object). + void setAnalysisCornerClockUncertainty(AnalysisCorner *corner, + const Clock *clk, + const MinMaxAll *setup_hold, + float uncertainty); + void removeAnalysisCornerClockUncertainty(AnalysisCorner *corner, + const Clock *clk, + const MinMaxAll *setup_hold); + // Purge corner clock uncertainties keyed by dying Clock objects + // (nullptr purges all clocks). + void purgeAnalysisCornerClockUncertainties(const Clock *clk); + // ---- OpenROAD fork: analysis_corner support (end) ---- + virtual LibertyLibrary *readLiberty(std::string_view filename, Scene *scene, const MinMaxAll *min_max, @@ -1639,6 +1673,11 @@ protected: SceneNameMap scene_name_map_; ModeNameMap mode_name_map_; ParasiticsNameMap parasitics_name_map_; + // ---- OpenROAD fork: analysis_corner support (begin) ---- + AnalysisCornerSeq analysis_corners_; + AnalysisCornerNameMap analysis_corner_name_map_; + AnalysisCorner *cmd_analysis_corner_{nullptr}; + // ---- OpenROAD fork: analysis_corner support (end) ---- VerilogReader *verilog_reader_{nullptr}; CheckTiming *check_timing_{nullptr}; CheckSlews *check_slews_{nullptr}; diff --git a/sdc/Sdc.i b/sdc/Sdc.i index 3ac13d812..3c3f96c9c 100644 --- a/sdc/Sdc.i +++ b/sdc/Sdc.i @@ -394,7 +394,7 @@ set_clock_latency_cmd(Clock *clk, MinMaxAll *min_max, float delay) { Sta *sta = Sta::sta(); - Sdc *sdc = sta->cmdSdc(); + Sdc *sdc = sta->cmdCornerSdc(); // OpenROAD fork: analysis_corner write scope. sta->setClockLatency(clk, pin, rf, min_max, delay, sdc); } @@ -407,7 +407,7 @@ set_clock_insertion_cmd(Clock *clk, float delay) { Sta *sta = Sta::sta(); - Sdc *sdc = sta->cmdSdc(); + Sdc *sdc = sta->cmdCornerSdc(); // OpenROAD fork: analysis_corner write scope. sta->setClockInsertion(clk, pin, rf, min_max, early_late, delay, sdc); } @@ -416,7 +416,7 @@ unset_clock_latency_cmd(Clock *clk, Pin *pin) { Sta *sta = Sta::sta(); - Sdc *sdc = sta->cmdSdc(); + Sdc *sdc = sta->cmdCornerSdc(); // OpenROAD fork: analysis_corner write scope. sta->removeClockLatency(clk, pin, sdc); } @@ -425,7 +425,7 @@ unset_clock_insertion_cmd(Clock *clk, Pin *pin) { Sta *sta = Sta::sta(); - Sdc *sdc = sta->cmdSdc(); + Sdc *sdc = sta->cmdCornerSdc(); // OpenROAD fork: analysis_corner write scope. sta->removeClockInsertion(clk, pin, sdc); } @@ -435,6 +435,15 @@ set_clock_uncertainty_clk(Clock *clk, float uncertainty) { Sta *sta = Sta::sta(); + // ---- OpenROAD fork: analysis_corner support (begin) ---- + // Upstream stores clock uncertainty on the (corner-shared) Clock + // object; in a corner scope it goes to the corner instead. + if (sta->cmdAnalysisCorner()) { + sta->setAnalysisCornerClockUncertainty(sta->cmdAnalysisCorner(), clk, + setup_hold, uncertainty); + return; + } + // ---- OpenROAD fork: analysis_corner support (end) ---- sta->setClockUncertainty(clk, setup_hold, uncertainty); } @@ -443,6 +452,13 @@ unset_clock_uncertainty_clk(Clock *clk, const SetupHoldAll *setup_hold) { Sta *sta = Sta::sta(); + // ---- OpenROAD fork: analysis_corner support (begin) ---- + if (sta->cmdAnalysisCorner()) { + sta->removeAnalysisCornerClockUncertainty(sta->cmdAnalysisCorner(), clk, + setup_hold); + return; + } + // ---- OpenROAD fork: analysis_corner support (end) ---- sta->removeClockUncertainty(clk, setup_hold); } @@ -452,7 +468,7 @@ set_clock_uncertainty_pin(Pin *pin, float uncertainty) { Sta *sta = Sta::sta(); - Sdc *sdc = sta->cmdSdc(); + Sdc *sdc = sta->cmdCornerSdc(); // OpenROAD fork: analysis_corner write scope. sta->setClockUncertainty(pin, min_max, uncertainty, sdc); } @@ -461,7 +477,7 @@ unset_clock_uncertainty_pin(Pin *pin, const MinMaxAll *min_max) { Sta *sta = Sta::sta(); - Sdc *sdc = sta->cmdSdc(); + Sdc *sdc = sta->cmdCornerSdc(); // OpenROAD fork: analysis_corner write scope. sta->removeClockUncertainty(pin, min_max, sdc); } @@ -474,7 +490,7 @@ set_inter_clock_uncertainty(Clock *from_clk, float uncertainty) { Sta *sta = Sta::sta(); - Sdc *sdc = sta->cmdSdc(); + Sdc *sdc = sta->cmdCornerSdc(); // OpenROAD fork: analysis_corner write scope. sta->setClockUncertainty(from_clk, from_tr, to_clk, to_tr, min_max, uncertainty, sdc); } @@ -487,7 +503,7 @@ unset_inter_clock_uncertainty(Clock *from_clk, const MinMaxAll *min_max) { Sta *sta = Sta::sta(); - Sdc *sdc = sta->cmdSdc(); + Sdc *sdc = sta->cmdCornerSdc(); // OpenROAD fork: analysis_corner write scope. sta->removeClockUncertainty(from_clk, from_tr, to_clk, to_tr, min_max, sdc); } @@ -576,7 +592,7 @@ set_input_delay_cmd(Pin *pin, float delay) { Sta *sta = Sta::sta(); - Sdc *sdc = sta->cmdSdc(); + Sdc *sdc = sta->cmdCornerSdc(); // OpenROAD fork: analysis_corner write scope. sta->setInputDelay(pin, rf, clk, clk_rf, ref_pin, source_latency_included, network_latency_included, min_max, add, delay, sdc); @@ -590,7 +606,7 @@ unset_input_delay_cmd(Pin *pin, MinMaxAll *min_max) { Sta *sta = Sta::sta(); - Sdc *sdc = sta->cmdSdc(); + Sdc *sdc = sta->cmdCornerSdc(); // OpenROAD fork: analysis_corner write scope. sta->removeInputDelay(pin, rf, clk, clk_rf, min_max, sdc); } @@ -607,7 +623,7 @@ set_output_delay_cmd(Pin *pin, float delay) { Sta *sta = Sta::sta(); - Sdc *sdc = sta->cmdSdc(); + Sdc *sdc = sta->cmdCornerSdc(); // OpenROAD fork: analysis_corner write scope. sta->setOutputDelay(pin, rf, clk, clk_rf, ref_pin, source_latency_included, network_latency_included, min_max, add, delay, sdc); @@ -621,7 +637,7 @@ unset_output_delay_cmd(Pin *pin, MinMaxAll *min_max) { Sta *sta = Sta::sta(); - Sdc *sdc = sta->cmdSdc(); + Sdc *sdc = sta->cmdCornerSdc(); // OpenROAD fork: analysis_corner write scope. sta->removeOutputDelay(pin, rf, clk, clk_rf, min_max, sdc); } @@ -1283,7 +1299,7 @@ set_timing_derate_cmd(TimingDerateType type, float derate) { Sta *sta = Sta::sta(); - Sdc *sdc = sta->cmdSdc(); + Sdc *sdc = sta->cmdCornerSdc(); // OpenROAD fork: analysis_corner write scope. sta->setTimingDerate(type, clk_data, rf, early_late, derate, sdc); } @@ -1295,7 +1311,7 @@ set_timing_derate_net_cmd(const Net *net, float derate) { Sta *sta = Sta::sta(); - Sdc *sdc = sta->cmdSdc(); + Sdc *sdc = sta->cmdCornerSdc(); // OpenROAD fork: analysis_corner write scope. sta->setTimingDerate(net, clk_data, rf, early_late, derate, sdc); } @@ -1308,7 +1324,7 @@ set_timing_derate_inst_cmd(const Instance *inst, float derate) { Sta *sta = Sta::sta(); - Sdc *sdc = sta->cmdSdc(); + Sdc *sdc = sta->cmdCornerSdc(); // OpenROAD fork: analysis_corner write scope. sta->setTimingDerate(inst, type, clk_data, rf, early_late, derate, sdc); } @@ -1321,7 +1337,7 @@ set_timing_derate_cell_cmd(const LibertyCell *cell, float derate) { Sta *sta = Sta::sta(); - Sdc *sdc = sta->cmdSdc(); + Sdc *sdc = sta->cmdCornerSdc(); // OpenROAD fork: analysis_corner write scope. sta->setTimingDerate(cell, type, clk_data, rf, early_late, derate, sdc); } @@ -1329,7 +1345,7 @@ void unset_timing_derate_cmd() { Sta *sta = Sta::sta(); - Sdc *sdc = sta->cmdSdc(); + Sdc *sdc = sta->cmdCornerSdc(); // OpenROAD fork: analysis_corner write scope. sta->unsetTimingDerate(sdc); } diff --git a/search/AnalysisCorner.cc b/search/AnalysisCorner.cc new file mode 100644 index 000000000..fa53c13b3 --- /dev/null +++ b/search/AnalysisCorner.cc @@ -0,0 +1,446 @@ +// OpenSTA, Static Timing Analyzer +// Copyright (c) 2026, Parallax Software, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. +// +// Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// +// This notice may not be removed or altered from any source distribution. + +// OpenROAD fork: analysis_corner support. + +#include "AnalysisCorner.hh" + +#include + +#include "ContainerHelpers.hh" +#include "Mode.hh" +#include "PatternMatch.hh" +#include "Property.hh" +#include "Scene.hh" +#include "Sdc.hh" +#include "Search.hh" +#include "Sta.hh" + +namespace sta { + +AnalysisCorner::AnalysisCorner(std::string_view name, + size_t index) : + name_(name), + index_(index) +{ +} + +//////////////////////////////////////////////////////////////// + +// Register the "analysis_corner" property on scene objects so +// get_property and get_scenes -filter resolve it natively. +void +defineAnalysisCornerProperties(Sta *sta) +{ + sta->properties().defineProperty("analysis_corner", + [] (const Scene *scene, + Sta *) -> PropertyValue { + AnalysisCorner *corner = scene->analysisCorner(); + if (corner) + return PropertyValue(corner->name()); + return PropertyValue(); + }); +} + +AnalysisCorner * +Sta::makeAnalysisCorner(std::string_view name) +{ + AnalysisCorner *corner = findAnalysisCorner(name); + if (corner == nullptr) { + corner = new AnalysisCorner(name, analysis_corners_.size()); + analysis_corner_name_map_[std::string(name)] = corner; + analysis_corners_.push_back(corner); + } + return corner; +} + +AnalysisCorner * +Sta::findAnalysisCorner(std::string_view name) const +{ + return findStringKey(analysis_corner_name_map_, name); +} + +AnalysisCornerSeq +Sta::findAnalysisCorners(const std::string &pattern) const +{ + AnalysisCornerSeq matches; + PatternMatch pattern_match(pattern); + for (AnalysisCorner *corner : analysis_corners_) { + if (pattern_match.match(corner->name())) + matches.push_back(corner); + } + return matches; +} + +void +Sta::setSceneAnalysisCorner(Scene *scene, + AnalysisCorner *corner) +{ + scene->setAnalysisCorner(corner); +} + +Sdc * +Sta::cmdCornerSdc() const +{ + if (cmd_analysis_corner_) + return cmd_mode_->makeCornerSdc(cmd_analysis_corner_); + return cmd_mode_->sdc(); +} + +void +Sta::deleteAnalysisCorners() +{ + deleteContents(analysis_corners_); + analysis_corners_.clear(); + analysis_corner_name_map_.clear(); + cmd_analysis_corner_ = nullptr; +} + +//////////////////////////////////////////////////////////////// + +// Mode overlay Sdc definitions (declared in Mode.hh). + +Sdc * +Mode::cornerSdc(const AnalysisCorner *corner) const +{ + return findKey(corner_sdcs_, corner); +} + +Sdc * +Mode::makeCornerSdc(const AnalysisCorner *corner) +{ + Sdc *&sdc = corner_sdcs_[corner]; + if (sdc == nullptr) + sdc = new Sdc(this, sta_); + return sdc; +} + +void +Mode::clearCornerSdcs() +{ + for (const auto [corner, sdc] : corner_sdcs_) + sdc->clear(); +} + +//////////////////////////////////////////////////////////////// + +// Corner-overlay lookups (declared in AnalysisCorner.hh / Scene.hh). + +static Sdc * +sceneCornerSdc(const Scene *scene) +{ + AnalysisCorner *corner = scene->analysisCorner(); + if (corner) + return scene->mode()->cornerSdc(corner); + return nullptr; +} + +// Corner overlay Sdc for derate queries (declared in Scene.hh). +// The overlay overrides the mode Sdc wholesale iff it defines derates. +const Sdc * +Scene::sdcOverlayForDerate() const +{ + const Sdc *overlay = sceneCornerSdc(this); + if (overlay && overlay->hasDeratingFactors()) + return overlay; + return nullptr; +} + +template +static DelaySet * +cornerDelaysLeafPin(const Scene *scene, + const Pin *pin, + DelaySet *(Sdc::*delays_leaf_pin)(const Pin*) const) +{ + const Sdc *overlay = sceneCornerSdc(scene); + if (overlay) { + DelaySet *delays = (overlay->*delays_leaf_pin)(pin); + if (delays && !delays->empty()) + return delays; + } + return nullptr; +} + +InputDelaySet * +cornerInputDelaysLeafPin(const Scene *scene, + const Pin *pin) +{ + return cornerDelaysLeafPin(scene, pin, &Sdc::inputDelaysLeafPin); +} + +OutputDelaySet * +cornerOutputDelaysLeafPin(const Scene *scene, + const Pin *pin) +{ + return cornerDelaysLeafPin(scene, pin, &Sdc::outputDelaysLeafPin); +} + +bool +sceneSeesInputDelay(const Scene *scene, + const Pin *pin, + const InputDelay *input_delay) +{ + const InputDelaySet *overlay_delays = cornerInputDelaysLeafPin(scene, pin); + const InputDelaySet *mode_delays = + scene->mode()->sdc()->inputDelaysLeafPin(pin); + if (input_delay == nullptr) + // Default (unconstrained) arrival seed: only for scenes with no + // effective input delays on the pin. + return overlay_delays == nullptr + && (mode_delays == nullptr || mode_delays->empty()); + const InputDelaySet *effective = + overlay_delays ? overlay_delays : mode_delays; + return effective + && effective->contains(const_cast(input_delay)); +} + +template +static std::vector +cornerDelaySets(const Mode *mode, + const Pin *pin, + DelaySet *(Sdc::*delays_leaf_pin)(const Pin*) const) +{ + std::vector delay_sets; + for (const Scene *scene : mode->scenes()) { + DelaySet *delays = cornerDelaysLeafPin(scene, pin, delays_leaf_pin); + if (delays + && std::find(delay_sets.begin(), delay_sets.end(), delays) + == delay_sets.end()) + delay_sets.push_back(delays); + } + return delay_sets; +} + +std::vector +cornerInputDelaySets(const Mode *mode, + const Pin *pin) +{ + return cornerDelaySets(mode, pin, &Sdc::inputDelaysLeafPin); +} + +std::vector +cornerOutputDelaySets(const Mode *mode, + const Pin *pin) +{ + return cornerDelaySets(mode, pin, &Sdc::outputDelaysLeafPin); +} + +template +static bool +modeHasCornerDelay(const Mode *mode, + const Pin *pin, + DelaySet *(Sdc::*delays_leaf_pin)(const Pin*) const) +{ + if (mode->cornerSdcs().empty()) + return false; + for (const Scene *scene : mode->scenes()) { + if (cornerDelaysLeafPin(scene, pin, delays_leaf_pin)) + return true; + } + return false; +} + +bool +modeHasCornerInputDelay(const Mode *mode, + const Pin *pin) +{ + return modeHasCornerDelay(mode, pin, &Sdc::inputDelaysLeafPin); +} + +bool +modeHasCornerOutputDelay(const Mode *mode, + const Pin *pin) +{ + return modeHasCornerDelay(mode, pin, &Sdc::outputDelaysLeafPin); +} + +//////////////////////////////////////////////////////////////// + +// Corner-scoped clock uncertainties (declared in AnalysisCorner.hh). + +const ClockUncertainties * +AnalysisCorner::clockUncertainty(const Clock *clk) const +{ + auto iter = clk_uncertainties_.find(clk); + if (iter != clk_uncertainties_.end()) + return &iter->second; + return nullptr; +} + +void +AnalysisCorner::setClockUncertainty(const Clock *clk, + const MinMaxAll *setup_hold, + float uncertainty) +{ + clk_uncertainties_[clk].setValue(setup_hold, uncertainty); +} + +void +AnalysisCorner::removeClockUncertainty(const Clock *clk, + const MinMaxAll *setup_hold) +{ + auto iter = clk_uncertainties_.find(clk); + if (iter != clk_uncertainties_.end()) { + iter->second.removeValue(setup_hold); + if (iter->second.empty()) + clk_uncertainties_.erase(iter); + } +} + +void +Sta::setAnalysisCornerClockUncertainty(AnalysisCorner *corner, + const Clock *clk, + const MinMaxAll *setup_hold, + float uncertainty) +{ + corner->setClockUncertainty(clk, setup_hold, uncertainty); + search_->arrivalsInvalid(); +} + +void +Sta::removeAnalysisCornerClockUncertainty(AnalysisCorner *corner, + const Clock *clk, + const MinMaxAll *setup_hold) +{ + corner->removeClockUncertainty(clk, setup_hold); + search_->arrivalsInvalid(); +} + +// Corner clock uncertainties are keyed by Clock objects; purge them when +// clocks die (remove_clock, Sta::clear) so recycled Clock addresses cannot +// inherit stale corner data. +void +Sta::purgeAnalysisCornerClockUncertainties(const Clock *clk) +{ + for (AnalysisCorner *corner : analysis_corners_) { + if (clk) + corner->removeClockUncertainties(clk); + else + corner->clearClockUncertainties(); + } +} + +const ClockUncertainties * +cornerPinClockUncertainties(const Scene *scene, + const Pin *pin) +{ + const Sdc *overlay = sceneCornerSdc(scene); + if (overlay) + return overlay->clockUncertainties(pin); + return nullptr; +} + +const ClockUncertainties * +cornerClkUncertainties(const Scene *scene, + const Clock *clk) +{ + AnalysisCorner *corner = scene->analysisCorner(); + if (corner) + return corner->clockUncertainty(clk); + return nullptr; +} + +void +cornerClockLatencyPin(const Scene *scene, + const Clock *clk, + const Pin *pin, + const RiseFall *rf, + const MinMax *min_max, + // Return values. + float &latency, + bool &exists) +{ + exists = false; + const Sdc *overlay = sceneCornerSdc(scene); + if (overlay) + overlay->clockLatency(clk, pin, rf, min_max, latency, exists); +} + +void +cornerClockLatencyClk(const Scene *scene, + const Clock *clk, + const RiseFall *rf, + const MinMax *min_max, + // Return values. + float &latency, + bool &exists) +{ + exists = false; + const Sdc *overlay = sceneCornerSdc(scene); + if (overlay) + overlay->clockLatency(clk, rf, min_max, latency, exists); +} + +void +cornerClockLatencyEdge(const Scene *scene, + Edge *edge, + const RiseFall *rf, + const MinMax *min_max, + // Return values. + float &latency, + bool &exists) +{ + exists = false; + const Sdc *overlay = sceneCornerSdc(scene); + if (overlay) + overlay->clockLatency(edge, rf, min_max, latency, exists); +} + +void +cornerClockInsertion(const Scene *scene, + const Clock *clk, + const Pin *pin, + const RiseFall *rf, + const MinMax *min_max, + const EarlyLate *early_late, + // Return values. + float &insertion, + bool &exists) +{ + exists = false; + const Sdc *overlay = sceneCornerSdc(scene); + if (overlay) + overlay->clockInsertion(clk, pin, rf, min_max, early_late, + insertion, exists); +} + +void +cornerInterClockUncertainty(const Scene *scene, + const Clock *src_clk, + const RiseFall *src_rf, + const Clock *tgt_clk, + const RiseFall *tgt_rf, + const MinMax *min_max, + // Return values. + float &uncertainty, + bool &exists) +{ + exists = false; + const Sdc *overlay = sceneCornerSdc(scene); + if (overlay) + overlay->clockUncertainty(src_clk, src_rf, tgt_clk, tgt_rf, min_max, + uncertainty, exists); +} + +} // namespace sta diff --git a/search/AnalysisCorner.i b/search/AnalysisCorner.i new file mode 100644 index 000000000..0cb4fa3f0 --- /dev/null +++ b/search/AnalysisCorner.i @@ -0,0 +1,157 @@ +// OpenSTA, Static Timing Analyzer +// Copyright (c) 2026, Parallax Software, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. +// +// Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// +// This notice may not be removed or altered from any source distribution. + +// OpenROAD fork: analysis_corner support. + +%{ + +#include "AnalysisCorner.hh" +#include "Scene.hh" +#include "Sta.hh" +#include "TclTypeHelpers.hh" + +using namespace sta; + +%} + +%typemap(in) AnalysisCorner* { + Tcl_Size length; + const char *arg = Tcl_GetStringFromObj($input, &length); + if (stringEqual(arg, "NULL")) + $1 = nullptr; + else { + void *obj; + if (SWIG_ConvertPtr($input, &obj, SWIGTYPE_p_AnalysisCorner, false) != TCL_OK) { + tclArgError(interp, 3701, "{} is not an analysis_corner object.", arg); + return TCL_ERROR; + } + $1 = reinterpret_cast(obj); + } +} + +%typemap(out) AnalysisCorner* { + AnalysisCorner *corner = $1; + if (corner) { + Tcl_Obj *obj = SWIG_NewInstanceObj(corner, SWIGTYPE_p_AnalysisCorner, false); + Tcl_SetObjResult(interp, obj); + } + else + Tcl_SetResult(interp, const_cast("NULL"), TCL_STATIC); +} + +%typemap(out) AnalysisCornerSeq { + seqTclList($1, SWIGTYPE_p_AnalysisCorner, interp); +} + +%inline %{ + +void +define_analysis_corner_cmd(const char *name, + StringSeq liberty_min_files, + StringSeq liberty_max_files, + const char *spef_min_name, + const char *spef_max_name, + StringSeq sdc_files) +{ + AnalysisCorner *corner = Sta::sta()->makeAnalysisCorner(name); + // Redefining with any data replaces the whole bundle; a bare redefine + // preserves it (makeAnalysisCorner is find-or-create). Min/max always + // arrive as a pair, so checking min suffices. + if (!liberty_min_files.empty() || spef_min_name[0] != '\0' + || !sdc_files.empty()) { + corner->setLiberty(liberty_min_files, liberty_max_files); + corner->setSpef(spef_min_name, spef_max_name); + corner->setSdc(sdc_files); + } +} + +StringSeq +analysis_corner_liberty_min(AnalysisCorner *corner) +{ + return corner->libertyMinFiles(); +} + +StringSeq +analysis_corner_liberty_max(AnalysisCorner *corner) +{ + return corner->libertyMaxFiles(); +} + +const char * +analysis_corner_spef_min(AnalysisCorner *corner) +{ + return corner->spefMinName().c_str(); +} + +const char * +analysis_corner_spef_max(AnalysisCorner *corner) +{ + return corner->spefMaxName().c_str(); +} + +StringSeq +analysis_corner_sdc(AnalysisCorner *corner) +{ + return corner->sdcFiles(); +} + +AnalysisCorner * +find_analysis_corner(const char *name) +{ + return Sta::sta()->findAnalysisCorner(name); +} + +AnalysisCornerSeq +find_analysis_corners_matching(const char *pattern) +{ + return Sta::sta()->findAnalysisCorners(pattern); +} + +const char * +analysis_corner_name(AnalysisCorner *corner) +{ + return corner->name().c_str(); +} + +void +set_scene_analysis_corner_cmd(Scene *scene, + AnalysisCorner *corner) +{ + Sta::sta()->setSceneAnalysisCorner(scene, corner); +} + +// Scope SDC commands to a corner overlay Sdc (NULL restores mode scope). +void +set_cmd_analysis_corner_cmd(AnalysisCorner *corner) +{ + Sta::sta()->setCmdAnalysisCorner(corner); +} + +AnalysisCorner * +cmd_analysis_corner() +{ + return Sta::sta()->cmdAnalysisCorner(); +} + +%} // inline diff --git a/search/CheckTiming.cc b/search/CheckTiming.cc index f64975e5a..2e639104a 100644 --- a/search/CheckTiming.cc +++ b/search/CheckTiming.cc @@ -24,6 +24,8 @@ #include "CheckTiming.hh" +// OpenROAD fork: analysis_corner support. +#include "AnalysisCorner.hh" #include "ClkNetwork.hh" #include "ExceptionPath.hh" #include "Format.hh" @@ -115,6 +117,8 @@ CheckTiming::checkNoInputDelay() PortDirection *dir = network_->direction(pin); if (dir->isAnyInput() && !sdc_->hasInputDelay(pin) + // OpenROAD fork: analysis_corner support. + && !modeHasCornerInputDelay(mode_, pin) && !sim_->isConstant(pin)) no_arrival.insert(pin); } @@ -142,6 +146,8 @@ CheckTiming::checkNoOutputDelay(PinSet &no_departure) PortDirection *dir = network_->direction(pin); if (dir->isAnyOutput() && !sdc_->hasOutputDelay(pin) + // OpenROAD fork: analysis_corner support. + && !modeHasCornerOutputDelay(mode_, pin) && !sim_->isConstant(pin)) no_departure.insert(pin); } @@ -274,6 +280,15 @@ CheckTiming::hasClkedDepature(Pin *pin) return true; } } + // ---- OpenROAD fork: analysis_corner support (begin) ---- + for (OutputDelaySet *corner_delays : cornerOutputDelaySets(mode_, pin)) { + for (OutputDelay *output_delay : *corner_delays) { + if (output_delay->clkEdge() != nullptr + || output_delay->refPin() != nullptr) + return true; + } + } + // ---- OpenROAD fork: analysis_corner support (end) ---- return false; } diff --git a/search/ClkLatency.cc b/search/ClkLatency.cc index 1e972abc4..3f10ca2ad 100644 --- a/search/ClkLatency.cc +++ b/search/ClkLatency.cc @@ -295,7 +295,9 @@ ClkDelays::insertionDelay(Path *clk_path, const Pin *src_pin = clk_info->clkSrc(); const MinMax *min_max = clk_path->minMax(sta); const Mode *mode = clk_path->mode(sta); - return sta->search()->clockInsertion(clk, src_pin, clk_rf, min_max, min_max, mode); + return sta->search()->clockInsertion(clk, src_pin, clk_rf, min_max, min_max, mode, + // OpenROAD fork: analysis_corner support. + clk_path->scene(sta)); } float diff --git a/search/ClkSkew.cc b/search/ClkSkew.cc index f44086695..97b5b855f 100644 --- a/search/ClkSkew.cc +++ b/search/ClkSkew.cc @@ -469,7 +469,8 @@ ClkSkew::uncertainty(const StaState *sta) : TimingRole::hold(); // Uncertainty decreases slack, but increases skew. return -PathEnd::checkTgtClkUncertainty(tgt_path_, tgt_path_->clkEdge(sta), - check_role, sta); + check_role, sta, + tgt_path_->scene(sta)); } bool diff --git a/search/Genclks.cc b/search/Genclks.cc index 2aad70741..1bfa4903a 100644 --- a/search/Genclks.cc +++ b/search/Genclks.cc @@ -603,7 +603,9 @@ Genclks::seedSrcPins(Clock *gclk, const EarlyLate *early_late = min_max; for (const RiseFall *rf : RiseFall::range()) { Arrival insert = search_->clockInsertion(master_clk, master_pin, rf, - min_max, early_late, mode_); + min_max, early_late, mode_, + // OpenROAD fork: analysis_corner support. + scene); Tag *tag = makeTag(gclk, master_clk, master_pin, rf, src_filter, insert, scene, min_max); tag_bldr.setArrival(tag, insert); diff --git a/search/Latches.cc b/search/Latches.cc index 5dcf69576..4904a76ca 100644 --- a/search/Latches.cc +++ b/search/Latches.cc @@ -187,7 +187,8 @@ Latches::latchBorrowInfo(const Path *data_path, nom_pulse_width = is_pulse_clk ? 0.0 : enable_clk_edge->pulseWidth(); open_uncertainty = PathEnd::checkClkUncertainty(data_clk_edge, enable_clk_edge, enable_path, - TimingRole::latchSetup(), sdc); + TimingRole::latchSetup(), sdc, + data_path->scene(this)); if (ignore_clk_latency) { open_latency = 0.0; latency_diff = 0.0; diff --git a/search/Mode.cc b/search/Mode.cc index f822ff924..7d962dcf8 100644 --- a/search/Mode.cc +++ b/search/Mode.cc @@ -54,6 +54,10 @@ Mode::~Mode() delete clk_network_; delete genclks_; delete path_groups_; + // ---- OpenROAD fork: analysis_corner support (begin) ---- + for (const auto [corner, sdc] : corner_sdcs_) + delete sdc; + // ---- OpenROAD fork: analysis_corner support (end) ---- } void @@ -64,6 +68,10 @@ Mode::copyState(const StaState *sta) sim_->copyState(sta); clk_network_->copyState(sta); genclks_->copyState(sta); + // ---- OpenROAD fork: analysis_corner support (begin) ---- + for (const auto [corner, sdc] : corner_sdcs_) + sdc->copyState(sta); + // ---- OpenROAD fork: analysis_corner support (end) ---- } void diff --git a/search/PathEnd.cc b/search/PathEnd.cc index e917d89f6..6f3464b2f 100644 --- a/search/PathEnd.cc +++ b/search/PathEnd.cc @@ -24,6 +24,8 @@ #include "PathEnd.hh" +// OpenROAD fork: analysis_corner support. +#include "AnalysisCorner.hh" #include "ClkInfo.hh" #include "Clock.hh" #include "DataCheck.hh" @@ -327,7 +329,8 @@ PathEnd::checkTgtClkDelay(const Path *tgt_clk_path, const RiseFall *tgt_clk_rf = tgt_clk_edge->transition(); const Mode *mode = tgt_clk_path->mode(sta); insertion = search->clockInsertion(tgt_clk, tgt_src_pin, tgt_clk_rf, - min_max, early_late, mode); + min_max, early_late, mode, + tgt_clk_path->scene(sta)); if (clk_info->isPropagated() // Data check target clock is always propagated. || check_role->isDataCheck()) { @@ -336,7 +339,8 @@ PathEnd::checkTgtClkDelay(const Path *tgt_clk_path, Arrival clk_arrival = tgt_clk_path->arrival(); Delay path_insertion = search->clockInsertion(tgt_clk, tgt_src_pin, tgt_clk_rf, min_max, - min_max, mode); + min_max, mode, + tgt_clk_path->scene(sta)); latency = delayRemove(delayDiff(clk_arrival, tgt_clk_edge->time(), sta), path_insertion); } @@ -355,31 +359,45 @@ PathEnd::checkClkUncertainty(const ClockEdge *src_clk_edge, const ClockEdge *tgt_clk_edge, const Path *tgt_clk_path, const TimingRole *check_role, - const Sdc *sdc) + const Sdc *sdc, + const Scene *scene) { float inter_clk; bool inter_exists; checkInterClkUncertainty(src_clk_edge, tgt_clk_edge, check_role, sdc, - inter_clk, inter_exists); + inter_clk, inter_exists, scene); if (inter_exists) return inter_clk; else - return checkTgtClkUncertainty(tgt_clk_path, tgt_clk_edge, check_role, sdc); + return checkTgtClkUncertainty(tgt_clk_path, tgt_clk_edge, check_role, sdc, + scene); } float PathEnd::checkTgtClkUncertainty(const Path *tgt_clk_path, const ClockEdge *tgt_clk_edge, const TimingRole *check_role, - const StaState *sta) + const StaState *sta, + const Scene *scene) { const MinMax *min_max = check_role->pathMinMax(); const ClockUncertainties *uncertainties = nullptr; if (tgt_clk_path && tgt_clk_path->isClock(sta)) uncertainties = tgt_clk_path->clkInfo(sta)->uncertainties(); - else if (tgt_clk_edge) + else if (tgt_clk_edge) { uncertainties = &tgt_clk_edge->clock()->uncertainties(); + // ---- OpenROAD fork: analysis_corner support (begin) ---- + // The clock-path branch above is corner-aware via ClkInfo; only the + // clock-object fallback needs the corner override. + if (scene) { + const ClockUncertainties *corner_unc = + cornerClkUncertainties(scene, tgt_clk_edge->clock()); + if (corner_unc) + uncertainties = corner_unc; + } + // ---- OpenROAD fork: analysis_corner support (end) ---- + } float uncertainty = 0.0; if (uncertainties) { bool exists; @@ -399,7 +417,8 @@ PathEnd::checkInterClkUncertainty(const ClockEdge *src_clk_edge, const TimingRole *check_role, const Sdc *sdc, float &uncertainty, - bool &exists) + bool &exists, + const Scene *scene) { if (src_clk_edge && src_clk_edge != sdc->defaultArrivalClockEdge() @@ -410,6 +429,23 @@ PathEnd::checkInterClkUncertainty(const ClockEdge *src_clk_edge, tgt_clk_edge->transition(), check_role->pathMinMax(), uncertainty, exists); + // ---- OpenROAD fork: analysis_corner support (begin) ---- + if (scene) { + float corner_uncertainty; + bool corner_exists; + cornerInterClockUncertainty(scene, + src_clk_edge->clock(), + src_clk_edge->transition(), + tgt_clk_edge->clock(), + tgt_clk_edge->transition(), + check_role->pathMinMax(), + corner_uncertainty, corner_exists); + if (corner_exists) { + uncertainty = corner_uncertainty; + exists = true; + } + } + // ---- OpenROAD fork: analysis_corner support (end) ---- if (exists && check_role->genericRole() == TimingRole::setup()) uncertainty = -uncertainty; @@ -629,7 +665,8 @@ PathEndClkConstrained::targetClkArrivalNoCrpr(const StaState *sta) const targetClkEdge(sta), targetClkPath(), checkRole(sta), - sdc); + sdc, + path_->scene(sta)); return delaySum(delaySum(clk_arrival, uncertainty, sta), targetClkMcpAdjustment(sta), sta); } @@ -661,12 +698,13 @@ PathEndClkConstrained::targetNonInterClkUncertainty(const StaState *sta) const float inter_clk; bool inter_exists; checkInterClkUncertainty(src_clk_edge, tgt_clk_edge, check_role, - sdc, inter_clk, inter_exists); + sdc, inter_clk, inter_exists, path_->scene(sta)); if (inter_exists) // This returns non inter-clock uncertainty. return 0.0; else - return checkTgtClkUncertainty(targetClkPath(), tgt_clk_edge, check_role, sta); + return checkTgtClkUncertainty(targetClkPath(), tgt_clk_edge, check_role, sta, + path_->scene(sta)); } float @@ -677,7 +715,7 @@ PathEndClkConstrained::interClkUncertainty(const StaState *sta) const bool exists; checkInterClkUncertainty(sourceClkEdge(sta), targetClkEdge(sta), checkRole(sta), sdc, - uncertainty, exists); + uncertainty, exists, path_->scene(sta)); if (exists) return uncertainty; else @@ -689,7 +727,8 @@ PathEndClkConstrained::targetClkUncertainty(const StaState *sta) const { Sdc *sdc = path_->sdc(sta); return checkClkUncertainty(sourceClkEdge(sta), targetClkEdge(sta), - targetClkPath(), checkRole(sta), sdc); + targetClkPath(), checkRole(sta), sdc, + path_->scene(sta)); } Crpr @@ -1005,7 +1044,8 @@ PathEndCheck::clkSkew(const StaState *sta) checkTgtClkUncertainty(clk_path_, clk_path_->clkEdge(sta), checkRole(sta), - sta), + sta, + path_->scene(sta)), sta); return skew; } @@ -1445,12 +1485,22 @@ PathEndOutputDelay::tgtClkDelay(const ClockEdge *tgt_clk_edge, tgt_clk->defaultPin(), tgt_clk_rf, latency_min_max, - early_late, mode); + early_late, mode, + path_->scene(sta)); else insertion = 0.0; if (!tgt_clk->isPropagated() - && !output_delay_->networkLatencyIncluded()) + && !output_delay_->networkLatencyIncluded()) { latency = mode->sdc()->clockLatency(tgt_clk, tgt_clk_rf, latency_min_max); + // ---- OpenROAD fork: analysis_corner support (begin) ---- + float corner_latency; + bool corner_exists; + cornerClockLatencyClk(path_->scene(sta), tgt_clk, tgt_clk_rf, + latency_min_max, corner_latency, corner_exists); + if (corner_exists) + latency = corner_latency; + // ---- OpenROAD fork: analysis_corner support (end) ---- + } else latency = 0.0; } diff --git a/search/ReportPath.cc b/search/ReportPath.cc index 1bfcdfc6b..7307b2b1c 100644 --- a/search/ReportPath.cc +++ b/search/ReportPath.cc @@ -2263,9 +2263,11 @@ ReportPath::tgtClkInsertionOffet(const Path *clk_path, const MinMax *min_max = clk_path->minMax(this); const Mode *mode = clk_path->mode(this); Arrival path_insertion = search_->clockInsertion(clk, src_pin, clk_rf, - min_max, min_max, mode); + min_max, min_max, mode, + clk_path->scene(this)); Arrival tgt_insertion = search_->clockInsertion(clk, src_pin, clk_rf, - min_max, early_late, mode); + min_max, early_late, mode, + clk_path->scene(this)); return delayAsFloat(delayDiff(tgt_insertion, path_insertion, this)); } @@ -2417,7 +2419,8 @@ ReportPath::reportGenClkSrcPath1(const Clock *clk, const Arrival insertion = search_->clockInsertion(src_clk, src_clk_pin, src_clk_rf, min_max, - early_late, mode); + early_late, mode, + src_path->scene(this)); reportClkSrcLatency(insertion, gclk_time, early_late); } PathExpanded src_expanded(src_path, this); diff --git a/search/Search.cc b/search/Search.cc index 35270944e..04f54d17a 100644 --- a/search/Search.cc +++ b/search/Search.cc @@ -28,6 +28,9 @@ #include #include +// OpenROAD fork: analysis_corner support. +#include "AnalysisCorner.hh" + #include "Bfs.hh" #include "ClkInfo.hh" #include "ClkNetwork.hh" @@ -997,10 +1000,21 @@ Search::clockInsertion(const Clock *clk, const RiseFall *rf, const MinMax *min_max, const EarlyLate *early_late, - const Mode *mode) const + const Mode *mode, + const Scene *scene) const { float insert; bool exists; + // ---- OpenROAD fork: analysis_corner support (begin) ---- + // Corner overlay insertion (set_clock_latency -source) wins when the + // caller has scene context and the overlay defines a value. + if (scene) { + cornerClockInsertion(scene, clk, pin, rf, min_max, early_late, + insert, exists); + if (exists) + return insert; + } + // ---- OpenROAD fork: analysis_corner support (end) ---- mode->sdc()->clockInsertion(clk, pin, rf, min_max, early_late, insert, exists); if (exists) return insert; @@ -1524,7 +1538,8 @@ Search::seedClkArrivals(const Pin *pin, tag_bldr); else { Arrival insertion = - clockInsertion(clk, pin, rf, min_max, early_late, mode); + clockInsertion(clk, pin, rf, min_max, early_late, mode, + scene); seedClkArrival(pin, rf, clk, clk_edge, min_max, insertion, scene, tag_bldr); } @@ -1564,8 +1579,55 @@ Search::seedClkArrival(const Pin *pin, else is_propagated = sdc->isPropagatedClock(pin) || clk->isPropagated(); } + // ---- OpenROAD fork: analysis_corner support (begin) ---- + // Corner overlay latency: pin beats clock as upstream, and the corner + // beats the mode at equal specificity (propagated remains mode-level). + if (scene->analysisCorner()) { + float corner_latency; + bool corner_exists; + cornerClockLatencyPin(scene, clk, pin, rf, min_max, + corner_latency, corner_exists); + if (corner_exists) { + latency = corner_latency; + latency_exists = true; + is_propagated = false; + } + else { + float pin_latency; + bool pin_exists; + sdc->clockLatency(clk, pin, rf, min_max, pin_latency, pin_exists); + if (!pin_exists) { + cornerClockLatencyClk(scene, clk, rf, min_max, + corner_latency, corner_exists); + if (corner_exists) { + if (sdc->isPropagatedClock(pin)) { + latency = 0.0; + is_propagated = true; + } + else { + latency = corner_latency; + is_propagated = false; + } + latency_exists = true; + } + } + } + } + // ---- OpenROAD fork: analysis_corner support (end) ---- const ClockUncertainties *uncertainties = sdc->clockUncertainties(pin); + // ---- OpenROAD fork: analysis_corner support (begin) ---- + // Corner-scoped uncertainty beats the mode's at equal specificity; + // pin beats clock as upstream. + if (scene->analysisCorner()) { + const ClockUncertainties *corner_pin_unc = + cornerPinClockUncertainties(scene, pin); + if (corner_pin_unc) + uncertainties = corner_pin_unc; + if (uncertainties == nullptr) + uncertainties = cornerClkUncertainties(scene, clk); + } + // ---- OpenROAD fork: analysis_corner support (end) ---- if (uncertainties == nullptr) uncertainties = &clk->uncertainties(); // Propagate liberty "pulse_clock" transition to transitive fanout. @@ -1714,6 +1776,18 @@ Search::seedInputArrival(const Pin *pin, TagGroupBldr *tag_bldr) { const Sdc *sdc = mode->sdc(); + // ---- OpenROAD fork: analysis_corner support (begin) ---- + if (modeHasCornerInputDelay(mode, pin)) { + // Some scene's corner overlay defines input delays on the pin. + // Seed the default arrival for scenes with no effective delays and + // enumerate mode + overlay delays; both fan-outs route per scene + // (sceneSeesInputDelay) in seedInputDelayArrival. + if (!sdc->isLeafPinClock(pin)) + seedInputDelayArrival(pin, vertex, nullptr, false, mode, tag_bldr); + seedInputArrival1(pin, vertex, false, mode, tag_bldr); + return; + } + // ---- OpenROAD fork: analysis_corner support (end) ---- if (sdc->hasInputDelay(pin)) seedInputArrival1(pin, vertex, false, mode, tag_bldr); else if (!sdc->isLeafPinClock(pin)) @@ -1752,6 +1826,19 @@ Search::seedInputArrival1(const Pin *pin, tag_bldr); } } + // ---- OpenROAD fork: analysis_corner support (begin) ---- + // Also enumerate corner-overlay input delays; the per-scene fan-out in + // seedInputDelayArrival routes each delay to the scenes it applies to. + for (InputDelaySet *corner_delays : cornerInputDelaySets(mode, pin)) { + for (InputDelay *input_delay : *corner_delays) { + Clock *input_clk = input_delay->clock(); + ClockSet *pin_clks = sdc->findLeafPinClocks(pin); + if (pin_clks == nullptr || !pin_clks->contains(input_clk)) + seedInputDelayArrival(pin, vertex, input_delay, is_segment_start, mode, + tag_bldr); + } + } + // ---- OpenROAD fork: analysis_corner support (end) ---- } void @@ -1779,6 +1866,11 @@ Search::seedInputDelayArrival(const Pin *pin, if (ref_pin) { Vertex *ref_vertex = graph_->pinLoadVertex(ref_pin); for (Scene *scene : mode->scenes()) { + // ---- OpenROAD fork: analysis_corner support (begin) ---- + if (!mode->cornerSdcs().empty() + && !sceneSeesInputDelay(scene, pin, input_delay)) + continue; + // ---- OpenROAD fork: analysis_corner support (end) ---- for (const MinMax *min_max : MinMax::range()) { const RiseFall *ref_rf = input_delay->refTransition(); const Clock *clk = input_delay->clock(); @@ -1804,6 +1896,11 @@ Search::seedInputDelayArrival(const Pin *pin, inputDelayClkArrival(input_delay, clk_edge, min_max, mode, clk_arrival, clk_insertion, clk_latency); for (Scene *scene : mode->scenes()) { + // ---- OpenROAD fork: analysis_corner support (begin) ---- + if (!mode->cornerSdcs().empty() + && !sceneSeesInputDelay(scene, pin, input_delay)) + continue; + // ---- OpenROAD fork: analysis_corner support (end) ---- seedInputDelayArrival(pin, input_delay, clk_edge, clk_arrival, clk_insertion, clk_latency, is_segment_start, min_max, scene, tag_bldr); @@ -1941,6 +2038,13 @@ Search::inputDelayTag(const Pin *pin, clk_pin = clk->defaultPin(); is_propagated = clk->isPropagated(); clk_uncertainties = &clk->uncertainties(); + // ---- OpenROAD fork: analysis_corner support (begin) ---- + if (scene->analysisCorner()) { + const ClockUncertainties *corner_unc = cornerClkUncertainties(scene, clk); + if (corner_unc) + clk_uncertainties = corner_unc; + } + // ---- OpenROAD fork: analysis_corner support (end) ---- } Sdc *sdc = scene->sdc(); @@ -2268,7 +2372,7 @@ Search::clkPathArrival(const Path *clk_path, const EarlyLate *early_late = min_max; Arrival insertion = clockInsertion(clk_edge->clock(), clk_info->clkSrc(), clk_edge->transition(), min_max, - early_late, scene->mode()); + early_late, scene->mode(), scene); return delaySum(delaySum(insertion, clk_edge->time(), this), clk_info->latency(), this); } @@ -2522,9 +2626,41 @@ Search::thruClkInfo(Path *from_path, changed = true; } } + // ---- OpenROAD fork: analysis_corner support (begin) ---- + // Corner overlay latency overrides at matching specificity (pin, then + // hierarchical edge). + if (scene->analysisCorner()) { + float corner_latency; + bool corner_exists; + cornerClockLatencyPin(scene, from_clk, to_pin, clk_rf, min_max, + corner_latency, corner_exists); + if (!corner_exists) { + float pin_latency; + bool pin_exists; + sdc->clockLatency(from_clk, to_pin, clk_rf, min_max, + pin_latency, pin_exists); + if (!pin_exists) + cornerClockLatencyEdge(scene, edge, clk_rf, min_max, + corner_latency, corner_exists); + } + if (corner_exists) { + to_latency = corner_latency; + to_clk_prop = false; + changed = true; + } + } + // ---- OpenROAD fork: analysis_corner support (end) ---- const ClockUncertainties *to_uncertainties = from_clk_info->uncertainties(); const ClockUncertainties *uncertainties = sdc->clockUncertainties(to_pin); + // ---- OpenROAD fork: analysis_corner support (begin) ---- + if (scene->analysisCorner()) { + const ClockUncertainties *corner_uncertainties = + cornerPinClockUncertainties(scene, to_pin); + if (corner_uncertainties) + uncertainties = corner_uncertainties; + } + // ---- OpenROAD fork: analysis_corner support (end) ---- if (uncertainties) { to_uncertainties = uncertainties; changed = true; @@ -3034,6 +3170,15 @@ Search::deratedDelay(const Vertex *from_vertex, DcalcAPIndex dcalc_ap, const Sdc *sdc) { + // ---- OpenROAD fork: analysis_corner support (begin) ---- + // dcalc_ap encodes the scene (scene index * MinMax::index_count + min/max + // index), so the scene's analysis corner overlay Sdc (when it defines + // derates) is resolved here without touching any caller. + const Sdc *overlay_sdc = + scenes_[dcalc_ap / MinMax::index_count]->sdcOverlayForDerate(); + if (overlay_sdc) + sdc = overlay_sdc; + // ---- OpenROAD fork: analysis_corner support (end) ---- float derate = timingDerate(from_vertex, arc, edge, is_clk, sdc, min_max); const ArcDelay &delay = graph_->arcDelay(edge, arc, dcalc_ap); return delayProduct(delay, derate, this);; diff --git a/search/Sta.cc b/search/Sta.cc index 8288a76a9..9308e2cb8 100644 --- a/search/Sta.cc +++ b/search/Sta.cc @@ -292,6 +292,10 @@ Sta::makeComponents() updateComponentsState(); makeObservers(); + + // ---- OpenROAD fork: analysis_corner support (begin) ---- + defineAnalysisCornerProperties(this); + // ---- OpenROAD fork: analysis_corner support (end) ---- } void @@ -516,6 +520,9 @@ Sta::~Sta() deleteContents(parasitics_name_map_); deleteContents(modes_); deleteContents(scenes_); + // ---- OpenROAD fork: analysis_corner support (begin) ---- + deleteAnalysisCorners(); + // ---- OpenROAD fork: analysis_corner support (end) ---- } void @@ -524,6 +531,12 @@ Sta::clear() clearNonSdc(); for (Mode *mode : modes_) mode->sdc()->clear(); + // ---- OpenROAD fork: analysis_corner support (begin) ---- + for (Mode *mode : modes_) + mode->clearCornerSdcs(); + // The mode Sdc clear above deleted all Clock objects. + purgeAnalysisCornerClockUncertainties(nullptr); + // ---- OpenROAD fork: analysis_corner support (end) ---- } void @@ -546,6 +559,10 @@ Sta::clearNonSdc() mode->sim()->clear(); // ref_pin edges are owned by the graph deleted below; force a rebuild. mode->sdc()->inputDelayRefPinEdgesInvalid(); + // ---- OpenROAD fork: analysis_corner support (begin) ---- + for (const auto [corner, corner_sdc] : mode->cornerSdcs()) + corner_sdc->inputDelayRefPinEdgesInvalid(); + // ---- OpenROAD fork: analysis_corner support (end) ---- } search_->clear(); @@ -1193,6 +1210,9 @@ void Sta::removeClock(Clock *clk, Sdc *sdc) { + // ---- OpenROAD fork: analysis_corner support (begin) ---- + purgeAnalysisCornerClockUncertainties(clk); + // ---- OpenROAD fork: analysis_corner support (end) ---- sdc->removeClock(clk); search_->arrivalsInvalid(); power_->activitiesInvalid(); diff --git a/search/VisitPathEnds.cc b/search/VisitPathEnds.cc index 3a420aada..255b8e2c5 100644 --- a/search/VisitPathEnds.cc +++ b/search/VisitPathEnds.cc @@ -24,6 +24,8 @@ #include "VisitPathEnds.hh" +// OpenROAD fork: analysis_corner support. +#include "AnalysisCorner.hh" #include "ClkInfo.hh" #include "Debug.hh" #include "ExceptionPath.hh" @@ -302,6 +304,11 @@ VisitPathEnds::visitOutputDelayEnd(const Pin *pin, const Sdc *sdc = scene->sdc(); const MinMax *min_max = path->minMax(this); OutputDelaySet *output_delays = sdc->outputDelaysLeafPin(pin); + // ---- OpenROAD fork: analysis_corner support (begin) ---- + OutputDelaySet *corner_delays = cornerOutputDelaysLeafPin(scene, pin); + if (corner_delays) + output_delays = corner_delays; + // ---- OpenROAD fork: analysis_corner support (end) ---- if (output_delays) { for (OutputDelay *output_delay : *output_delays) { float margin; diff --git a/search/test/CMakeLists.txt b/search/test/CMakeLists.txt index d3739b5a3..50ce46197 100644 --- a/search/test/CMakeLists.txt +++ b/search/test/CMakeLists.txt @@ -1,12 +1,15 @@ sta_module_tests("search" TESTS analysis + analysis_corner annotated_write_verilog assigned_delays check_timing check_types_deep clk_skew_interclk clk_skew_multiclock + corner_sdc_bundle + corner_sdc_derate corner_skew crpr crpr_data_checks diff --git a/search/test/search_analysis_corner.ok b/search/test/search_analysis_corner.ok new file mode 100644 index 000000000..1b31a1629 --- /dev/null +++ b/search/test/search_analysis_corner.ok @@ -0,0 +1,134 @@ +corner count: 2 +corner: slow +corner: fast +match f*: 1 +corner count after dup: 2 +ss corner: slow +ff corner: fast +ss2 corner: slow_b +ff2 corner: slow_b +filter slow: 1 ss +filter not fast: 3 +filter pattern: 3 +filter and name: 1 +filter none: 0 +after reassoc filter fast: 2 +after restore filter fast: 1 +1 +Error 3703: search_analysis_corner.tcl line 1, no_such_corner is not the name of an analysis corner. +1 +Error 3702: search_analysis_corner.tcl line 1, no_such_scene is not the name of a scene. +1 +Error 3704: search_analysis_corner.tcl line 1, no_such_corner is not the name of an analysis corner. +1 +Error 3707: search_analysis_corner.tcl line 1, analysis corner fast defines no liberty files; use -liberty or define_analysis_corner -liberty. +1 +Error 3705: search_analysis_corner.tcl line 1, -liberty_min and -liberty_max are required arguments. +Startpoint: reg2 (rising edge-triggered flip-flop clocked by clk) +Endpoint: out1 (output port clocked by clk) +Path Group: clk +Path Type: max +Corner: ss + + Delay Time Description +--------------------------------------------------------- + 0.00 0.00 clock clk (rise edge) + 0.00 0.00 clock network delay (ideal) + 0.00 0.00 ^ reg2/CK (DFF_X1) + 0.29 0.29 ^ reg2/Q (DFF_X1) + 0.00 0.29 ^ out1 (out) + 0.29 data arrival time + + 10.00 10.00 clock clk (rise edge) + 0.00 10.00 clock network delay (ideal) + 0.00 10.00 clock reconvergence pessimism + -2.00 8.00 output external delay + 8.00 data required time +--------------------------------------------------------- + 8.00 data required time + -0.29 data arrival time +--------------------------------------------------------- + 7.71 slack (MET) + + +Startpoint: reg2 (rising edge-triggered flip-flop clocked by clk) +Endpoint: out1 (output port clocked by clk) +Path Group: clk +Path Type: max +Corner: ff + + Delay Time Description +--------------------------------------------------------- + 0.00 0.00 clock clk (rise edge) + 0.00 0.00 clock network delay (ideal) + 0.00 0.00 ^ reg2/CK (DFF_X1) + 0.05 0.05 v reg2/Q (DFF_X1) + 0.00 0.05 v out1 (out) + 0.05 data arrival time + + 10.00 10.00 clock clk (rise edge) + 0.00 10.00 clock network delay (ideal) + 0.00 10.00 clock reconvergence pessimism + -2.00 8.00 output external delay + 8.00 data required time +--------------------------------------------------------- + 8.00 data required time + -0.05 data arrival time +--------------------------------------------------------- + 7.95 slack (MET) + + +Startpoint: reg2 (rising edge-triggered flip-flop clocked by clk) +Endpoint: out1 (output port clocked by clk) +Path Group: clk +Path Type: max +Corner: ss2 + + Delay Time Description +--------------------------------------------------------- + 0.00 0.00 clock clk (rise edge) + 0.00 0.00 clock network delay (ideal) + 0.00 0.00 ^ reg2/CK (DFF_X1) + 0.29 0.29 ^ reg2/Q (DFF_X1) + 0.00 0.29 ^ out1 (out) + 0.29 data arrival time + + 10.00 10.00 clock clk (rise edge) + 0.00 10.00 clock network delay (ideal) + 0.00 10.00 clock reconvergence pessimism + -2.00 8.00 output external delay + 8.00 data required time +--------------------------------------------------------- + 8.00 data required time + -0.29 data arrival time +--------------------------------------------------------- + 7.71 slack (MET) + + +Startpoint: reg2 (rising edge-triggered flip-flop clocked by clk) +Endpoint: out1 (output port clocked by clk) +Path Group: clk +Path Type: max +Corner: ff2 + + Delay Time Description +--------------------------------------------------------- + 0.00 0.00 clock clk (rise edge) + 0.00 0.00 clock network delay (ideal) + 0.00 0.00 ^ reg2/CK (DFF_X1) + 0.05 0.05 v reg2/Q (DFF_X1) + 0.00 0.05 v out1 (out) + 0.05 data arrival time + + 10.00 10.00 clock clk (rise edge) + 0.00 10.00 clock network delay (ideal) + 0.00 10.00 clock reconvergence pessimism + -2.00 8.00 output external delay + 8.00 data required time +--------------------------------------------------------- + 8.00 data required time + -0.05 data arrival time +--------------------------------------------------------- + 7.95 slack (MET) + + diff --git a/search/test/search_analysis_corner.tcl b/search/test/search_analysis_corner.tcl new file mode 100644 index 000000000..875f67604 --- /dev/null +++ b/search/test/search_analysis_corner.tcl @@ -0,0 +1,73 @@ +# analysis_corner: define/get/pattern, liberty/spef bundles, +# define_scene -analysis_corner composition, get_scenes -filter, +# set_scene_analysis_corner, error cases +read_liberty ../../test/nangate45/Nangate45_slow.lib +read_liberty ../../test/nangate45/Nangate45_fast.lib +read_verilog search_crpr.v +link_design search_crpr + +create_clock -name clk -period 10 [get_ports clk] +set_input_delay -clock clk 1.0 [get_ports in1] +set_input_delay -clock clk 1.0 [get_ports in2] +set_output_delay -clock clk 2.0 [get_ports out1] + +define_analysis_corner slow +define_analysis_corner fast +puts "corner count: [llength [get_analysis_corners]]" +foreach corner [get_analysis_corners] { + puts "corner: [sta::analysis_corner_name $corner]" +} +puts "match f*: [llength [get_analysis_corners f*]]" + +# Duplicate define is idempotent. +define_analysis_corner slow +puts "corner count after dup: [llength [get_analysis_corners]]" + +# Corner with a liberty bundle; define_scene composes from it. +define_analysis_corner slow_b -liberty NangateOpenCellLibrary_slow + +# Redefine without data preserves the bundle (ss2 below composes from it). +define_analysis_corner slow_b + +# All scenes defined before timing runs. +define_scene ss -liberty NangateOpenCellLibrary_slow -analysis_corner slow +define_scene ff -liberty NangateOpenCellLibrary_fast -analysis_corner fast +define_scene ss2 -analysis_corner slow_b +# Explicit -liberty overrides the corner bundle. +define_scene ff2 -analysis_corner slow_b -liberty NangateOpenCellLibrary_fast + +foreach scene [get_scenes] { + puts "[get_name $scene] corner: [get_property $scene analysis_corner]" +} + +set slow_scenes [get_scenes -filter {analysis_corner == slow}] +puts "filter slow: [llength $slow_scenes] [get_name [lindex $slow_scenes 0]]" +puts "filter not fast: [llength [get_scenes -filter {analysis_corner != fast}]]" +puts "filter pattern: [llength [get_scenes -filter {analysis_corner =~ s*}]]" +puts "filter and name: [llength [get_scenes -filter {analysis_corner == fast && name == ff}]]" +puts "filter none: [llength [get_scenes -filter {analysis_corner == typ}]]" + +# Reassociate a scene with a different corner. +set_scene_analysis_corner ss fast +puts "after reassoc filter fast: [llength [get_scenes -filter {analysis_corner == fast}]]" +set_scene_analysis_corner ss slow +puts "after restore filter fast: [llength [get_scenes -filter {analysis_corner == fast}]]" + +# Error cases. +puts [catch { set_scene_analysis_corner ss no_such_corner } msg] +puts $msg +puts [catch { set_scene_analysis_corner no_such_scene slow } msg] +puts $msg +puts [catch { define_scene bad -liberty NangateOpenCellLibrary_slow -analysis_corner no_such_corner } msg] +puts $msg +# Corner with no liberty bundle and no -liberty errors before scene creation. +puts [catch { define_scene bad2 -analysis_corner fast } msg] +puts $msg +puts [catch { define_analysis_corner bad3 -liberty_min NangateOpenCellLibrary_slow } msg] +puts $msg + +# Timing per scene; composed ss2 must match explicit ss. +report_checks -scenes ss +report_checks -scenes ff +report_checks -scenes ss2 +report_checks -scenes ff2 diff --git a/search/test/search_corner_sdc_bundle.ok b/search/test/search_corner_sdc_bundle.ok new file mode 100644 index 000000000..46264e63f --- /dev/null +++ b/search/test/search_corner_sdc_bundle.ok @@ -0,0 +1,75 @@ +Warning 1556: multiple scenes reference mode func +Warning 1556: multiple scenes reference mode func +modes: func msyn +s_ss corner: ss +s_ff corner: ff +s_manual corner: +filter ss: 1 +=== corner-composed scene (Mode: func) === +Startpoint: reg2 (rising edge-triggered flip-flop clocked by clk) +Endpoint: out1 (output port clocked by clk) +Path Group: clk +Path Type: max +Mode: func +Corner: s_ss + + Delay Time Description +------------------------------------------------------------- + 0.0000 0.0000 clock clk (rise edge) + 1.8000 1.8000 clock network delay (ideal) + 0.0000 1.8000 ^ reg2/CK (DFF_X1) + 0.3463 2.1463 ^ reg2/Q (DFF_X1) + 0.0000 2.1463 ^ out1 (out) + 2.1463 data arrival time + + 10.0000 10.0000 clock clk (rise edge) + 1.8000 11.8000 clock network delay (ideal) + -0.4000 11.4000 clock uncertainty + 0.0000 11.4000 clock reconvergence pessimism + -3.5000 7.9000 output external delay + 7.9000 data required time +------------------------------------------------------------- + 7.9000 data required time + -2.1463 data arrival time +------------------------------------------------------------- + 5.7537 slack (MET) + + +=== plain corner scene (mode constraints only) === +Startpoint: reg2 (rising edge-triggered flip-flop clocked by clk) +Endpoint: out1 (output port clocked by clk) +Path Group: clk +Path Type: max +Mode: func +Corner: s_ff + + Delay Time Description +------------------------------------------------------------- + 0.0000 0.0000 clock clk (rise edge) + 0.5000 0.5000 clock network delay (ideal) + 0.0000 0.5000 ^ reg2/CK (DFF_X1) + 0.0478 0.5478 v reg2/Q (DFF_X1) + 0.0000 0.5478 v out1 (out) + 0.5478 data arrival time + + 10.0000 10.0000 clock clk (rise edge) + 0.5000 10.5000 clock network delay (ideal) + -0.1000 10.4000 clock uncertainty + 0.0000 10.4000 clock reconvergence pessimism + -2.0000 8.4000 output external delay + 8.4000 data required time +------------------------------------------------------------- + 8.4000 data required time + -0.5478 data arrival time +------------------------------------------------------------- + 7.8522 slack (MET) + + +equiv vs manual: 1 +ff isolated: 1 +Warning 1556: multiple scenes reference mode func +Warning 1556: multiple scenes reference mode func +1 +Error 3710: corner_bundle_bad.sdc line 1, create_clock is not allowed in an analysis corner scope; it is a mode-level constraint. +bundle after bare redefine: 1 +bundle after data redefine: 0 diff --git a/search/test/search_corner_sdc_bundle.tcl b/search/test/search_corner_sdc_bundle.tcl new file mode 100644 index 000000000..5afa34fe3 --- /dev/null +++ b/search/test/search_corner_sdc_bundle.tcl @@ -0,0 +1,85 @@ +# Corner SDC bundles: define_analysis_corner -sdc files are applied to +# the (mode, corner) overlay Sdc when the first scene naming the corner +# is defined. No synthetic modes: one mode, N corners, full whitelist +# per corner (derates, IO delays, clock uncertainty, clock latency). +# Checks: equivalence vs a manually composed mode, mode list stays clean, +# per-pin IO override + corner-only IO constraint, clock-object +# uncertainty isolation between corners, structural commands in a corner +# bundle error, whole-bundle redefine rules, property/filter. +source ../../test/helpers.tcl + +read_liberty ../../test/nangate45/Nangate45_slow.lib +read_liberty ../../test/nangate45/Nangate45_fast.lib +read_verilog search_crpr.v +link_design search_crpr + +set base_sdc [make_result_file corner_bundle_base.sdc] +set stream [open $base_sdc "w"] +puts $stream {create_clock -name clk -period 10 [get_ports clk]} +puts $stream {set_input_delay -clock clk 1.0 [get_ports in1]} +puts $stream {set_output_delay -clock clk 2.0 [get_ports out1]} +puts $stream {set_clock_uncertainty 0.1 [get_clocks clk]} +puts $stream {set_clock_latency 0.5 [get_clocks clk]} +close $stream + +# Full whitelist in the corner SDC: derate, IO override, corner-only IO +# constraint (in2 is unconstrained in the mode), clock uncertainty, +# clock latency, source latency. +set corner_ss_sdc [make_result_file corner_bundle_ss.sdc] +set stream [open $corner_ss_sdc "w"] +puts $stream {set_timing_derate -late 1.2} +puts $stream {set_input_delay -clock clk 2.5 [get_ports in1]} +puts $stream {set_input_delay -clock clk 3.0 [get_ports in2]} +puts $stream {set_output_delay -clock clk 3.5 [get_ports out1]} +puts $stream {set_clock_uncertainty 0.4 [get_clocks clk]} +puts $stream {set_clock_latency 1.5 [get_clocks clk]} +puts $stream {set_clock_latency -source 0.3 [get_clocks clk]} +close $stream + +read_sdc -mode func $base_sdc + +define_analysis_corner ss -liberty NangateOpenCellLibrary_slow -sdc [list $corner_ss_sdc] +define_analysis_corner ff -liberty NangateOpenCellLibrary_fast + +define_scene s_ss -mode func -analysis_corner ss +define_scene s_ff -mode func -analysis_corner ff + +# Manual composition in a separate mode for the equivalence check. +read_sdc -mode msyn $base_sdc +read_sdc -mode msyn $corner_ss_sdc +define_scene s_manual -mode msyn -liberty NangateOpenCellLibrary_slow + +# No synthetic modes are minted. +set mode_names {} +foreach mode [get_modes] { lappend mode_names [get_name $mode] } +puts "modes: [lsort $mode_names]" +foreach scene [get_scenes] { + puts "[get_name $scene] corner: [get_property $scene analysis_corner]" +} +puts "filter ss: [llength [get_scenes -filter {analysis_corner == ss}]]" + +puts "=== corner-composed scene (Mode: func) ===" +report_checks -scenes s_ss -digits 4 +puts "=== plain corner scene (mode constraints only) ===" +report_checks -scenes s_ff -digits 4 +puts "equiv vs manual: [expr {[worst_slack -scene s_ss -max] == [worst_slack -scene s_manual -max]}]" +# ff keeps the mode's 0.1 uncertainty: the corner's clock uncertainty is +# stored on the corner, not the shared Clock object. +puts "ff isolated: [expr {[worst_slack -scene s_ff -max] != [worst_slack -scene s_ss -max]}]" + +# Structural commands in a corner bundle error out (guard active while +# the bundle is applied in corner scope). +set bad_sdc [make_result_file corner_bundle_bad.sdc] +set stream [open $bad_sdc "w"] +puts $stream {create_clock -name clk2 -period 5} +close $stream +define_analysis_corner bad -liberty NangateOpenCellLibrary_slow -sdc [list $bad_sdc] +puts [catch { define_scene s_bad -mode func -analysis_corner bad } msg] +puts $msg + +# Bare corner redefine preserves the sdc bundle; redefine with data +# replaces the whole bundle. +define_analysis_corner ss +puts "bundle after bare redefine: [llength [sta::analysis_corner_sdc [sta::find_analysis_corner ss]]]" +define_analysis_corner ss -liberty NangateOpenCellLibrary_slow +puts "bundle after data redefine: [llength [sta::analysis_corner_sdc [sta::find_analysis_corner ss]]]" diff --git a/search/test/search_corner_sdc_derate.ok b/search/test/search_corner_sdc_derate.ok new file mode 100644 index 000000000..539c8d172 --- /dev/null +++ b/search/test/search_corner_sdc_derate.ok @@ -0,0 +1,226 @@ +=== baseline, no derates === +Startpoint: reg2 (rising edge-triggered flip-flop clocked by clk) +Endpoint: out1 (output port clocked by clk) +Path Group: clk +Path Type: max +Mode: func +Corner: ss + + Delay Time Description +------------------------------------------------------------- + 0.0000 0.0000 clock clk (rise edge) + 0.0000 0.0000 clock network delay (ideal) + 0.0000 0.0000 ^ reg2/CK (DFF_X1) + 0.2886 0.2886 ^ reg2/Q (DFF_X1) + 0.0000 0.2886 ^ out1 (out) + 0.2886 data arrival time + + 10.0000 10.0000 clock clk (rise edge) + 0.0000 10.0000 clock network delay (ideal) + 0.0000 10.0000 clock reconvergence pessimism + -2.0000 8.0000 output external delay + 8.0000 data required time +------------------------------------------------------------- + 8.0000 data required time + -0.2886 data arrival time +------------------------------------------------------------- + 7.7114 slack (MET) + + +Startpoint: reg2 (rising edge-triggered flip-flop clocked by clk) +Endpoint: out1 (output port clocked by clk) +Path Group: clk +Path Type: max +Mode: func +Corner: ff + + Delay Time Description +------------------------------------------------------------- + 0.0000 0.0000 clock clk (rise edge) + 0.0000 0.0000 clock network delay (ideal) + 0.0000 0.0000 ^ reg2/CK (DFF_X1) + 0.0478 0.0478 v reg2/Q (DFF_X1) + 0.0000 0.0478 v out1 (out) + 0.0478 data arrival time + + 10.0000 10.0000 clock clk (rise edge) + 0.0000 10.0000 clock network delay (ideal) + 0.0000 10.0000 clock reconvergence pessimism + -2.0000 8.0000 output external delay + 8.0000 data required time +------------------------------------------------------------- + 8.0000 data required time + -0.0478 data arrival time +------------------------------------------------------------- + 7.9522 slack (MET) + + +Warning 1556: multiple scenes reference mode func +Warning 1556: multiple scenes reference mode func +Warning 1556: multiple scenes reference mode func +Warning 1556: multiple scenes reference mode func +Warning 1556: multiple scenes reference mode func +Warning 1556: multiple scenes reference mode func +=== corner-scoped derates === +Startpoint: reg2 (rising edge-triggered flip-flop clocked by clk) +Endpoint: out1 (output port clocked by clk) +Path Group: clk +Path Type: max +Mode: func +Corner: ss + + Delay Time Description +------------------------------------------------------------- + 0.0000 0.0000 clock clk (rise edge) + 0.0000 0.0000 clock network delay (ideal) + 0.0000 0.0000 ^ reg2/CK (DFF_X1) + 0.3463 0.3463 ^ reg2/Q (DFF_X1) + 0.0000 0.3463 ^ out1 (out) + 0.3463 data arrival time + + 10.0000 10.0000 clock clk (rise edge) + 0.0000 10.0000 clock network delay (ideal) + 0.0000 10.0000 clock reconvergence pessimism + -2.0000 8.0000 output external delay + 8.0000 data required time +------------------------------------------------------------- + 8.0000 data required time + -0.3463 data arrival time +------------------------------------------------------------- + 7.6537 slack (MET) + + +Startpoint: reg2 (rising edge-triggered flip-flop clocked by clk) +Endpoint: out1 (output port clocked by clk) +Path Group: clk +Path Type: max +Mode: func +Corner: ff + + Delay Time Description +------------------------------------------------------------- + 0.0000 0.0000 clock clk (rise edge) + 0.0000 0.0000 clock network delay (ideal) + 0.0000 0.0000 ^ reg2/CK (DFF_X1) + 0.0502 0.0502 v reg2/Q (DFF_X1) + 0.0000 0.0502 v out1 (out) + 0.0502 data arrival time + + 10.0000 10.0000 clock clk (rise edge) + 0.0000 10.0000 clock network delay (ideal) + 0.0000 10.0000 clock reconvergence pessimism + -2.0000 8.0000 output external delay + 8.0000 data required time +------------------------------------------------------------- + 8.0000 data required time + -0.0502 data arrival time +------------------------------------------------------------- + 7.9498 slack (MET) + + +=== synthetic-mode equivalents === +Startpoint: reg2 (rising edge-triggered flip-flop clocked by clk) +Endpoint: out1 (output port clocked by clk) +Path Group: clk +Path Type: max +Mode: ms +Corner: ss_syn + + Delay Time Description +------------------------------------------------------------- + 0.0000 0.0000 clock clk (rise edge) + 0.0000 0.0000 clock network delay (ideal) + 0.0000 0.0000 ^ reg2/CK (DFF_X1) + 0.3463 0.3463 ^ reg2/Q (DFF_X1) + 0.0000 0.3463 ^ out1 (out) + 0.3463 data arrival time + + 10.0000 10.0000 clock clk (rise edge) + 0.0000 10.0000 clock network delay (ideal) + 0.0000 10.0000 clock reconvergence pessimism + -2.0000 8.0000 output external delay + 8.0000 data required time +------------------------------------------------------------- + 8.0000 data required time + -0.3463 data arrival time +------------------------------------------------------------- + 7.6537 slack (MET) + + +Startpoint: reg2 (rising edge-triggered flip-flop clocked by clk) +Endpoint: out1 (output port clocked by clk) +Path Group: clk +Path Type: max +Mode: mf +Corner: ff_syn + + Delay Time Description +------------------------------------------------------------- + 0.0000 0.0000 clock clk (rise edge) + 0.0000 0.0000 clock network delay (ideal) + 0.0000 0.0000 ^ reg2/CK (DFF_X1) + 0.0502 0.0502 v reg2/Q (DFF_X1) + 0.0000 0.0502 v out1 (out) + 0.0502 data arrival time + + 10.0000 10.0000 clock clk (rise edge) + 0.0000 10.0000 clock network delay (ideal) + 0.0000 10.0000 clock reconvergence pessimism + -2.0000 8.0000 output external delay + 8.0000 data required time +------------------------------------------------------------- + 8.0000 data required time + -0.0502 data arrival time +------------------------------------------------------------- + 7.9498 slack (MET) + + +equiv ss: 1 +equiv ff: 1 +Warning 1556: multiple scenes reference mode func +Warning 1556: multiple scenes reference mode func +=== mode derate 1.5: ss/ff unchanged, typ corner derated === +ss still corner-derated: 1 +ff still corner-derated: 1 +Startpoint: reg2 (rising edge-triggered flip-flop clocked by clk) +Endpoint: out1 (output port clocked by clk) +Path Group: clk +Path Type: max +Mode: func +Corner: st + + Delay Time Description +------------------------------------------------------------- + 0.0000 0.0000 clock clk (rise edge) + 0.0000 0.0000 clock network delay (ideal) + 0.0000 0.0000 ^ reg2/CK (DFF_X1) + 0.4328 0.4328 ^ reg2/Q (DFF_X1) + 0.0000 0.4328 ^ out1 (out) + 0.4328 data arrival time + + 10.0000 10.0000 clock clk (rise edge) + 0.0000 10.0000 clock network delay (ideal) + 0.0000 10.0000 clock reconvergence pessimism + -2.0000 8.0000 output external delay + 8.0000 data required time +------------------------------------------------------------- + 8.0000 data required time + -0.4328 data arrival time +------------------------------------------------------------- + 7.5672 slack (MET) + + +=== slow overlay emptied: falls back to mode derate === +ss matches typ: 1 +1 +Error 3710: search_corner_sdc_derate.tcl line 1, create_clock is not allowed in an analysis corner scope; it is a mode-level constraint. +1 +Error 3710: search_corner_sdc_derate.tcl line 1, set_false_path is not allowed in an analysis corner scope; it is a mode-level constraint. +1 +Error 3711: search_corner_sdc_derate.tcl line 1, set_input_transition is not yet supported in an analysis corner scope. +0 + +1 +Error 3708: search_corner_sdc_derate.tcl line 1, no_such_corner is not the name of an analysis corner. +1 +Error 3709: search_corner_sdc_derate.tcl line 1, no_such_corner is not the name of an analysis corner. diff --git a/search/test/search_corner_sdc_derate.tcl b/search/test/search_corner_sdc_derate.tcl new file mode 100644 index 000000000..86b89f283 --- /dev/null +++ b/search/test/search_corner_sdc_derate.tcl @@ -0,0 +1,107 @@ +# Corner-scoped SDC: per-corner timing derates. +# 1 mode x 2 corners with different set_timing_derate must match the +# equivalent 2-synthetic-mode setup numerically. Also checks wholesale +# override of mode derates, fallback for corners without derates, the +# corner-scope command guard, and scope save/restore. +source ../../test/helpers.tcl + +read_liberty ../../test/nangate45/Nangate45_slow.lib +read_liberty ../../test/nangate45/Nangate45_fast.lib +read_verilog search_crpr.v +link_design search_crpr + +# Base SDC shared by the corner-scoped mode and both synthetic modes. +set base_sdc [make_result_file corner_sdc_base.sdc] +set stream [open $base_sdc "w"] +puts $stream {create_clock -name clk -period 10 [get_ports clk]} +puts $stream {set_input_delay -clock clk 1.0 [get_ports in1]} +puts $stream {set_input_delay -clock clk 1.0 [get_ports in2]} +puts $stream {set_output_delay -clock clk 2.0 [get_ports out1]} +close $stream + +set derate_slow_sdc [make_result_file corner_sdc_derate_slow.sdc] +set stream [open $derate_slow_sdc "w"] +puts $stream {set_timing_derate -late 1.2} +close $stream + +set derate_fast_sdc [make_result_file corner_sdc_derate_fast.sdc] +set stream [open $derate_fast_sdc "w"] +puts $stream {set_timing_derate -late 1.05} +close $stream + +read_sdc -mode func $base_sdc +read_sdc -mode ms $base_sdc +read_sdc -mode mf $base_sdc + +define_analysis_corner slow +define_analysis_corner fast +define_analysis_corner typ + +# All scenes defined before timing queries. +# 1 mode x 3 corners. +define_scene ss -mode func -liberty NangateOpenCellLibrary_slow -analysis_corner slow +define_scene ff -mode func -liberty NangateOpenCellLibrary_fast -analysis_corner fast +define_scene st -mode func -liberty NangateOpenCellLibrary_slow -analysis_corner typ +# Equivalent synthetic modes. +define_scene ss_syn -mode ms -liberty NangateOpenCellLibrary_slow +define_scene ff_syn -mode mf -liberty NangateOpenCellLibrary_fast + +puts "=== baseline, no derates ===" +report_checks -scenes ss -digits 4 +report_checks -scenes ff -digits 4 + +# Corner-scoped derates on mode func; mode-level derates on the synthetic +# modes with the same values. +read_sdc -mode func -analysis_corner slow $derate_slow_sdc +read_sdc -mode func -analysis_corner fast $derate_fast_sdc +read_sdc -mode ms $derate_slow_sdc +read_sdc -mode mf $derate_fast_sdc + +puts "=== corner-scoped derates ===" +report_checks -scenes ss -digits 4 +report_checks -scenes ff -digits 4 +puts "=== synthetic-mode equivalents ===" +report_checks -scenes ss_syn -digits 4 +report_checks -scenes ff_syn -digits 4 +puts "equiv ss: [expr {[worst_slack -scene ss -max] == [worst_slack -scene ss_syn -max]}]" +puts "equiv ff: [expr {[worst_slack -scene ff -max] == [worst_slack -scene ff_syn -max]}]" + +# Mode-level derate on func: corners with derates override it wholesale; +# the typ corner (no derates) falls back to it. +set derate_base_sdc [make_result_file corner_sdc_derate_base.sdc] +set stream [open $derate_base_sdc "w"] +puts $stream {set_timing_derate -late 1.5} +close $stream +read_sdc -mode func $derate_base_sdc + +puts "=== mode derate 1.5: ss/ff unchanged, typ corner derated ===" +puts "ss still corner-derated: [expr {[worst_slack -scene ss -max] == [worst_slack -scene ss_syn -max]}]" +puts "ff still corner-derated: [expr {[worst_slack -scene ff -max] == [worst_slack -scene ff_syn -max]}]" +report_checks -scenes st -digits 4 + +# Corner unset_timing_derate empties the overlay; the corner then falls +# back to the mode derate (1.5). +set_cmd_analysis_corner slow +unset_timing_derate +unset_cmd_analysis_corner +puts "=== slow overlay emptied: falls back to mode derate ===" +puts "ss matches typ: [expr {[worst_slack -scene ss -max] == [worst_slack -scene st -max]}]" + +# Guard: structural and not-yet-supported commands error in corner scope. +set_cmd_analysis_corner slow +puts [catch { create_clock -name clk2 -period 5 } msg] +puts $msg +puts [catch { set_false_path -from [get_ports in1] } msg] +puts $msg +puts [catch { set_input_transition 0.5 [get_ports in1] } msg] +puts $msg +unset_cmd_analysis_corner +# Out of corner scope the guarded commands work again. +puts [catch { set_input_transition 0.5 [get_ports in1] } msg] +puts $msg + +# Error cases. +puts [catch { set_cmd_analysis_corner no_such_corner } msg] +puts $msg +puts [catch { read_sdc -analysis_corner no_such_corner $derate_slow_sdc } msg] +puts $msg diff --git a/tcl/AnalysisCorner.tcl b/tcl/AnalysisCorner.tcl new file mode 100644 index 000000000..c51a35aa2 --- /dev/null +++ b/tcl/AnalysisCorner.tcl @@ -0,0 +1,317 @@ +# OpenSTA, Static Timing Analyzer +# Copyright (c) 2026, Parallax Software, Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# The origin of this software must not be misrepresented; you must not +# claim that you wrote the original software. +# +# Altered source versions must be plainly marked as such, and must not be +# misrepresented as being the original software. +# +# This notice may not be removed or altered from any source distribution. + +# OpenROAD fork: analysis_corner support. +# Sourced after Sta.tcl (list order in CMakeLists.txt/BUILD matters). + +namespace eval sta { + +define_cmd_args "define_analysis_corner" {name\ + [-liberty liberty_files \ + | -liberty_min liberty_min_files -liberty_max liberty_max_files]\ + [-spef spef_name | -spef_min spef_min_name -spef_max spef_max_name]\ + [-sdc sdc_files]} + +proc define_analysis_corner { args } { + parse_key_args "define_analysis_corner" args \ + keys {-liberty -liberty_min -liberty_max -spef -spef_min -spef_max -sdc} \ + flags {} + check_argc_eq1 "define_analysis_corner" $args + set name [lindex $args 0] + + set liberty_min_files {} + set liberty_max_files {} + if { [info exists keys(-liberty)] } { + set liberty_min_files $keys(-liberty) + set liberty_max_files $keys(-liberty) + } elseif { [info exists keys(-liberty_min)] && [info exists keys(-liberty_max)] } { + set liberty_min_files $keys(-liberty_min) + set liberty_max_files $keys(-liberty_max) + } elseif { [info exists keys(-liberty_min)] || [info exists keys(-liberty_max)] } { + sta_error 3705 "-liberty_min and -liberty_max are required arguments." + } + + set spef_min_name "" + set spef_max_name "" + if { [info exists keys(-spef)] } { + set spef_min_name $keys(-spef) + set spef_max_name $keys(-spef) + } elseif { [info exists keys(-spef_min)] && [info exists keys(-spef_max)] } { + set spef_min_name $keys(-spef_min) + set spef_max_name $keys(-spef_max) + } elseif { [info exists keys(-spef_min)] || [info exists keys(-spef_max)] } { + sta_error 3706 "-spef_min and -spef_max are required arguments." + } + + # Corner-scoped SDC files, applied to the (mode, corner) overlay Sdc + # when the first scene naming this corner is defined. + set sdc_files {} + if { [info exists keys(-sdc)] } { + set sdc_files $keys(-sdc) + } + + define_analysis_corner_cmd $name $liberty_min_files $liberty_max_files \ + $spef_min_name $spef_max_name $sdc_files + + # A redefine with data replaces the whole bundle: forget which + # (mode, corner) pairs the old SDC bundle was applied to so the new + # bundle applies to scenes defined from now on. + if { $liberty_min_files != {} || $spef_min_name != "" || $sdc_files != {} } { + variable corner_bundle_applied + foreach pair [dict keys $corner_bundle_applied] { + if { [lindex $pair 1] == $name } { + dict unset corner_bundle_applied $pair + } + } + } +} + +define_cmd_args "get_analysis_corners" {[corner_name]} + +proc get_analysis_corners { args } { + check_argc_eq0or1 "get_analysis_corners" $args + if { [llength $args] == 0 } { + set pattern "*" + } else { + set pattern [lindex $args 0] + } + return [find_analysis_corners_matching $pattern] +} + +define_cmd_args "set_scene_analysis_corner" {scene_name corner_name} + +proc set_scene_analysis_corner { args } { + check_argc_eq2 "set_scene_analysis_corner" $args + set scene_name [lindex $args 0] + set corner_name [lindex $args 1] + set scene [find_scene $scene_name] + if { $scene == "NULL" } { + sta_error 3702 "$scene_name is not the name of a scene." + } + set corner [find_analysis_corner $corner_name] + if { $corner == "NULL" } { + sta_error 3703 "$corner_name is not the name of an analysis corner." + } + set_scene_analysis_corner_cmd $scene $corner +} + +# (mode, corner) pairs whose corner SDC bundle has been applied to the +# corner's overlay Sdc; a bundle is applied once per pair, when the first +# scene naming that mode and corner is defined. Redefining a corner with +# new bundle data forgets its pairs (see define_analysis_corner). +variable corner_bundle_applied [dict create] + +# Extend define_scene with -analysis_corner by wrapping the proc at +# runtime instead of editing Sta.tcl (upstream merge hygiene). +if { [info procs define_scene_base] == "" } { + rename define_scene define_scene_base +} + +proc define_scene { args } { + variable corner_bundle_applied + # Passthrough parse: extract our key plus the base mode/liberty/spef keys + # so the corner bundle is only injected when the user supplied none of + # them (explicit args win over the bundle). Unparsed args stay for the + # base cmd. + parse_key_args "define_scene" args \ + keys {-analysis_corner -mode -liberty -liberty_min -liberty_max \ + -spef -spef_min -spef_max} \ + flags {} 0 + set corner "NULL" + if { [info exists keys(-analysis_corner)] } { + set corner [find_analysis_corner $keys(-analysis_corner)] + if { $corner == "NULL" } { + sta_error 3704 "$keys(-analysis_corner) is not the name of an analysis corner." + } + } + + # When the corner carries an SDC bundle, apply it to the (mode, corner) + # overlay Sdc, once per pair. The mode itself is untouched: the overlay + # holds only the corner-scoped constraints and the corner-scope guard + # rejects commands that cannot be corner-scoped. + set mode_name [cmd_mode_name] + if { [info exists keys(-mode)] } { + set mode_name $keys(-mode) + } + if { $corner != "NULL" } { + set corner_sdc [analysis_corner_sdc $corner] + set pair [list $mode_name [analysis_corner_name $corner]] + if { $corner_sdc != {} && ![dict exists $corner_bundle_applied $pair] } { + dict set corner_bundle_applied $pair 1 + foreach f $corner_sdc { + read_sdc -mode $mode_name -analysis_corner [analysis_corner_name $corner] $f + } + } + } + if { [info exists keys(-mode)] } { + lappend args -mode $mode_name + } + # Reinject the user's liberty/spef keys verbatim. + set user_liberty 0 + foreach key {-liberty -liberty_min -liberty_max} { + if { [info exists keys($key)] } { + set user_liberty 1 + lappend args $key $keys($key) + } + } + set user_spef 0 + foreach key {-spef -spef_min -spef_max} { + if { [info exists keys($key)] } { + set user_spef 1 + lappend args $key $keys($key) + } + } + if { $corner != "NULL" } { + # Compose the corner's bundle for anything the user left out. + # Min/max are always set as a pair, so checking min suffices. + if { !$user_liberty } { + set liberty_min [analysis_corner_liberty_min $corner] + if { $liberty_min == {} } { + sta_error 3707 "analysis corner $keys(-analysis_corner) defines no liberty files; use -liberty or define_analysis_corner -liberty." + } + lappend args -liberty_min $liberty_min \ + -liberty_max [analysis_corner_liberty_max $corner] + } + if { !$user_spef } { + set spef_min [analysis_corner_spef_min $corner] + if { $spef_min != "" } { + lappend args -spef_min $spef_min \ + -spef_max [analysis_corner_spef_max $corner] + } + } + } + define_scene_base {*}$args + if { $corner != "NULL" } { + # Sta::makeScene leaves the new scene as cmd_scene_. + set_scene_analysis_corner_cmd [cmd_scene] $corner + } +} + +################################################################ +# +# Corner-scoped SDC: whitelisted commands write into the (mode, corner) +# overlay Sdc (timing derates, IO delays, clock uncertainty, clock +# latency/insertion). +# +################################################################ + +define_cmd_args "set_cmd_analysis_corner" {corner_name} + +proc set_cmd_analysis_corner { args } { + check_argc_eq1 "set_cmd_analysis_corner" $args + set corner_name [lindex $args 0] + set corner [find_analysis_corner $corner_name] + if { $corner == "NULL" } { + sta_error 3708 "$corner_name is not the name of an analysis corner." + } + set_cmd_analysis_corner_cmd $corner +} + +define_cmd_args "unset_cmd_analysis_corner" {} + +proc unset_cmd_analysis_corner { args } { + check_argc_eq0 "unset_cmd_analysis_corner" $args + set_cmd_analysis_corner_cmd NULL +} + +# Extend read_sdc with -analysis_corner by wrapping the proc at runtime +# (same technique as define_scene above). Unknown keys (-mode, -echo, +# redirection) pass through parse_key_args verbatim to the base command. +if { [info procs read_sdc_base] == "" } { + rename read_sdc read_sdc_base +} + +proc read_sdc { args } { + parse_key_args "read_sdc" args keys {-analysis_corner} flags {} 0 + if { [info exists keys(-analysis_corner)] } { + set corner [find_analysis_corner $keys(-analysis_corner)] + if { $corner == "NULL" } { + sta_error 3709 "$keys(-analysis_corner) is not the name of an analysis corner." + } + set prev_corner [cmd_analysis_corner] + set_cmd_analysis_corner_cmd $corner + try { + read_sdc_base {*}$args + } finally { + set_cmd_analysis_corner_cmd $prev_corner + } + } else { + read_sdc_base {*}$args + } +} + +# Commands that may not run in an analysis corner scope: every SDC write +# command that is not corner-scoped writes to the MODE Sdc, so it must +# error instead of silently escaping the corner scope. Covers structural +# constraints (clocks, exceptions, logic state) and mode-level +# environment/limits. NOTE: a new upstream SDC write command is unsafe in +# corner scope until added here or to the corner whitelist (Sdc.i +# cmdCornerSdc swaps). +variable corner_scope_disallowed_cmds { + create_clock create_generated_clock set_clock_groups set_propagated_clock + set_false_path set_multicycle_path set_max_delay set_min_delay + group_path set_case_analysis set_disable_timing + create_voltage_area set_clock_gating_check set_clock_sense set_sense + set_clock_transition set_data_check set_drive set_driving_cell + set_fanout_load set_ideal_latency set_ideal_network set_ideal_transition + set_level_shifter_strategy set_level_shifter_threshold + set_logic_dc set_logic_one set_logic_zero + set_max_area set_max_capacitance set_max_dynamic_power set_max_fanout + set_max_leakage_power set_max_time_borrow set_max_transition + set_min_capacitance set_min_pulse_width set_operating_conditions + set_port_fanout_number set_pvt set_resistance set_voltage + unset_case_analysis unset_clock_groups unset_clock_transition + unset_data_check unset_disable_timing unset_propagated_clock + unset_path_exceptions +} +# Corner-scoped by design but not implemented yet. +variable corner_scope_pending_cmds { + set_input_transition set_load +} + +proc check_corner_scope { cmd } { + variable corner_scope_pending_cmds + if { [cmd_analysis_corner] != "NULL" } { + if { [lsearch -exact $corner_scope_pending_cmds $cmd] >= 0 } { + sta_error 3711 "$cmd is not yet supported in an analysis corner scope." + } else { + sta_error 3710 "$cmd is not allowed in an analysis corner scope; it is a mode-level constraint." + } + } +} + +# Guard the disallowed commands with a corner-scope check (runtime +# wrappers; the base commands are untouched). The base is invoked with +# uplevel/linsert rather than {*}$args because sdc_file_line list-parses +# the command text of every stack frame (Util.tcl). +foreach cmd [concat $corner_scope_disallowed_cmds $corner_scope_pending_cmds] { + if { [info procs ${cmd}_corner_scope_base] == "" } { + rename $cmd ${cmd}_corner_scope_base + proc $cmd { args } \ + "check_corner_scope $cmd ; uplevel 1 \[linsert \$args 0 ::sta::${cmd}_corner_scope_base\]" + } +} + +# sta namespace end. +}