Sta variant corner - #394
Conversation
Signed-off-by: dsengupta0628 <dsengupta@precisioninno.com>
Signed-off-by: dsengupta0628 <dsengupta@precisioninno.com>
Signed-off-by: dsengupta0628 <dsengupta@precisioninno.com>
…pt set_ip_trans and set_load Signed-off-by: dsengupta0628 <dsengupta@precisioninno.com>
There was a problem hiding this comment.
Code Review
This pull request introduces support for analysis corners (PVT/RC corners) to group scenes sharing operating points across modes and manage corner-scoped SDC constraints like timing derates, IO delays, clock uncertainty, and clock latency. The feedback suggests optimizing string lookups in AnalysisCornerNameMap by accepting std::string_view in makeAnalysisCorner and findAnalysisCorner to avoid unnecessary allocations, and improving the time complexity of unique delay set collection in cornerDelaySets from O(N^2) to O(N log N) using std::set.
| AnalysisCorner *makeAnalysisCorner(const std::string &name); | ||
| AnalysisCorner *findAnalysisCorner(const std::string &name) const; |
There was a problem hiding this comment.
You're using std::less<> for AnalysisCornerNameMap in AnalysisCorner.hh, which enables heterogeneous lookups (e.g., with std::string_view) to avoid std::string allocations on lookups. To fully leverage this, consider changing the signatures of makeAnalysisCorner and findAnalysisCorner to accept std::string_view instead of const std::string &.
AnalysisCorner *makeAnalysisCorner(std::string_view name);
AnalysisCorner *findAnalysisCorner(std::string_view name) const;There was a problem hiding this comment.
Applied. makeAnalysisCorner/findAnalysisCorner now take std::string_view; find goes through findStringKey (the exact idiom upstream's Sta::findMode(std::string_view) uses
| std::vector<DelaySet*> 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; |
There was a problem hiding this comment.
The current implementation to find unique DelaySet* uses std::find within a loop over scenes, which has a time complexity of O(N^2) where N is the number of scenes. For better performance, especially if a mode could have a large number of scenes, consider using std::set to collect unique pointers, which would have a complexity of O(N log N).
std::set<DelaySet*> unique_delay_sets;
for (const Scene *scene : mode->scenes()) {
if (auto* delays = cornerDelaysLeafPin(scene, pin, delays_leaf_pin)) {
unique_delay_sets.insert(delays);
}
}
return {unique_delay_sets.begin(), unique_delay_sets.end()};There was a problem hiding this comment.
declined: N <= scenes-per-mode (~10), distinct sets bounded by corner count, so linear dedup beats std::set's allocations at this scale, and std::set's pointer-order iteration would make enumeration order nondeterministic across runs, which we avoid in the timing engine.
|
The following work currently: The corner itself isn't in the property dispatch: no PropertyRegistry<const AnalysisCorner*>, so the following don't work yet: |
Signed-off-by: dsengupta0628 <dsengupta@precisioninno.com>
* Fir for write_verilog issue 3826 Signed-off-by: dsengupta0628 <dsengupta@precisioninno.com> * staToVerilog2 remove escaped_name+=ch Signed-off-by: dsengupta0628 <dsengupta@precisioninno.com> * updated regression to remove \ from module name Signed-off-by: dsengupta0628 <dsengupta@precisioninno.com> * Using helpers.tcl function to redirect results Signed-off-by: dsengupta0628 <dsengupta@precisioninno.com> * add std::string and remove trailing space, update regression name Signed-off-by: dsengupta0628 <dsengupta@precisioninno.com> * update regression to reflect correct output verilog name Signed-off-by: dsengupta0628 <dsengupta@precisioninno.com> --------- Signed-off-by: dsengupta0628 <dsengupta@precisioninno.com>
gadfort
left a comment
There was a problem hiding this comment.
I don't see how the write_sdc -corner / -scene would be handled?
| @@ -0,0 +1,208 @@ | |||
| // OpenSTA, Static Timing Analyzer | |||
| // Copyright (c) 2026, Parallax Software, Inc. | |||
There was a problem hiding this comment.
this copyright should be OpenSTA authors or something right?
| StringSeq liberty_min_files_; | ||
| StringSeq liberty_max_files_; | ||
| std::string spef_min_name_; | ||
| std::string spef_max_name_; |
There was a problem hiding this comment.
this is duplicated with the scene information. Can a scene now define additional liberty files from the corner?
There was a problem hiding this comment.
These members aren't a duplicate of scene state - they're at a different stage of the pipeline. The corner holds authoring inputs (file paths and spef names, plain strings, nothing loaded); the Scene holds engine state (the loaded LibertyLibrary objects and the per-scene liberty mapping, which LibertyLibrary::makeSceneMap requires to be keyed by scene->libertyIndex() - that storage can't move). It's the same relationship as read_spef -name, which parks a parasitics under a name before any scene binds it.
The point of keeping the strings on the corner is reuse across modes - a corner is authored once and expanded into N scenes:
define_analysis_corner ss_cmax -liberty {ss.lib} -spef spef_ss
define_scene func_ss -mode func -analysis_corner ss_cmax
define_scene test_ss -mode test -analysis_corner ss_cmax
define_scene scan_ss -mode scan -analysis_corner ss_cmax
Without the bundle, those file lists get re-typed per scene - the duplication just moves into user scripts, which is what this feature removes.
And no, a scene cannot add liberty files on top of the corner's. Composition is all-or-nothing per family: if define_scene has any explicit -liberty* key, the corner's liberty bundle is ignored entirely for that scene (explicit wins, no union); same independently for -spef*. A corner with no liberty bundle and no explicit -liberty errors out before the scene is created. So for any scene there is exactly one unambiguous source per family - never a merge whose provenance is unclear.
| 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_; |
There was a problem hiding this comment.
why are all these strings? seems like that could be very brittle.
There was a problem hiding this comment.
These are strings because they're just the arguments define_scene would have received - the corner saves them and replays them when a scene is defined. Nothing can be resolved earlier than that: liberty files are loaded per scene, a spef name is looked up when the scene is created (same as upstream's define_scene -spef_min , which is already a string name), and the SDC files can't be applied until a scene tells us which mode to pair the corner with.
A bad path or unknown name fails at scene creation with the same error as if the user had typed it directly - no new failure mode.
Storing pointers instead would be worse: re-reading a spef or deleting a library would leave corners holding dangling pointers, and it would force read_spef to happen before define_analysis_corner. Strings avoid both.
| 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(); | ||
| } |
There was a problem hiding this comment.
could this be upstreamed?
| { | ||
| Sta *sta = Sta::sta(); | ||
| Sdc *sdc = sta->cmdSdc(); | ||
| Sdc *sdc = sta->cmdCornerSdc(); // OpenROAD fork: analysis_corner write scope. |
There was a problem hiding this comment.
so these items are only stored in the corner?
There was a problem hiding this comment.
No. This stores to the corner only when a corner scope is explicitly active. cmdCornerSdc() is:
Sdc *
Sta::cmdCornerSdc() const
{
if (cmd_analysis_corner_)
return cmd_mode_->makeCornerSdc(cmd_analysis_corner_);
return cmd_mode_->sdc();
}
cmd_analysis_corner_ is only set inside read_sdc -analysis_corner or after set_cmd_analysis_corner. Outside that, this returns the mode's Sdc - identical behavior to cmdSdc(), and the per-corner storage is never even allocated.
So
- A plain set_clock_latency --> stored on the mode, all scenes see it, exactly as before.
- Same set_clock_latency inside a corner-scoped SDC file --> stored in that (mode, corner) overlay, and at lookup time the overlay value overrides the mode's one for scenes using that corner, with fallback to the mode value otherwise.
| @@ -0,0 +1,446 @@ | |||
| // OpenSTA, Static Timing Analyzer | |||
| // Copyright (c) 2026, Parallax Software, Inc. | |||
There was a problem hiding this comment.
Will check with Matt. Even though I wrote every line here, it still interacts with rest of OpenSTA. Will need to check how the copyright will look like (also in light of the acquisition now! This PR predates the closing date)
| AnalysisCorner * | ||
| Sta::makeAnalysisCorner(std::string_view name) | ||
| { | ||
| AnalysisCorner *corner = findAnalysisCorner(name); |
There was a problem hiding this comment.
should this error out if you try to make one with the same name?
There was a problem hiding this comment.
No, and on purpose. If the name already exists you get the existing corner back, so running the same setup script twice doesn't error. Modes work the same way (reusing a mode name is silent), and define_scene doesn't check for duplicate names at all.
Defining it again with new files replaces the old files completely - there's no partial merge, so nothing stale is left behind.
One gap: scenes that were already built from the old definition keep it; they aren't updated. I can add a warning for that case if you think that would be useful.
Summary
Adds an
analysis_cornerobject to the OpenROAD fork of OpenSTA: a namedbundle of per-corner setup data (liberty, parasitics, corner-scoped SDC)
that scenes reference. One logical mode can now be analyzed across N
corners with different derates, IO delays, clock uncertainty, and clock
latency — without defining one composed mode per (mode x corner) pair.
Scenes remain the only unit of analysis and results; the corner is a
setup-side object that computes nothing. The timing engine's data model
is unchanged.
Motivation
Per-corner data (derates, IO budgets from external blocks, uncertainty)
lives in SDC, but SDC state is per-mode. Without this feature, MCMM
flows must compose mode+corner SDC files into synthetic modes
(
func_ss,func_ff, ...): each is a fullModeinstance (ownSdc,Sim,ClkNetwork,Genclks,PathGroups) duplicated per corner, andthe generated mode names surface in
report_checksheaders,get_modes,write_sdc -mode, and every downstream script. This PR removes the needfor synthetic modes entirely: reports show the real mode, and the mode
list stays clean.
User-facing commands
All existing commands and arguments are unchanged; flows that do not use
corners are unaffected.
Design
(mode, corner), lazily created. The overlay reuses the existing Sdc
class unmodified; whitelisted SDC write commands target it when a
corner scope is active, and engine read points consult it with the
mode Sdc as fallback.
set_output_delay, set_clock_uncertainty (all forms),
set_clock_latency / insertion, and their unset forms. Everything
else — clocks, exceptions, case analysis, and all other mode-level SDC
writes — errors out in corner scope, so structural constraints can
never exist in two places.
replaces the mode's wholesale for that family's key — whole derate
table, per pin for IO delays, per clock for uncertainty, per lookup
for latency) or it doesn't (mode value applies untouched). No merging.
The only ordering is the pin-beats-clock specificity the code already
has; a corner value wins only at equal specificity.
the mode's Clock* (upstream stores it on the Clock object, which is
shared across corners); purged on remove_clock / sta::clear.
Tcl-side filtering.
Implementation / merge hygiene
search/AnalysisCorner.cc, search/AnalysisCorner.i,
tcl/AnalysisCorner.tcl.
(// ---- OpenROAD fork: analysis_corner support ----) intended to be
re-applied on upstream merges: read-point overrides in
search/Search.cc, PathEnd.cc, VisitPathEnds.cc, CheckTiming.cc,
ReportPath.cc, one-line cmdSdc() -> cmdCornerSdc() swaps in the
whitelisted write commands in sdc/Sdc.i, and one inline accessor in
Sdc.hh. Sdc.cc is untouched.
inline null/empty checks; no overlays are allocated unless a
corner-scoped SDC command runs.
Testing
liberty-spef bundles, composition), search_corner_sdc_derate
(1 mode x N corners vs equivalent synthetic-mode setup — exact
numeric equivalence asserted in-test), search_corner_sdc_bundle
(full whitelist per corner vs manual composition — exact equivalence;
corner isolation; guard errors; redefine semantics).
Known limitations (documented)
3711); per-corner RC is expected via per-corner SPEF.
computation or the clock arrival implied by corner IO delays'
-clock reference (mode values used).
wholesale per pin, no tombstones).
found in upstream behavior, not introduced here).