diff --git a/doc/ChangeLog.txt b/doc/ChangeLog.txt
index add039d0..4b803497 100644
--- a/doc/ChangeLog.txt
+++ b/doc/ChangeLog.txt
@@ -4,6 +4,20 @@ OpenSTA Timing Analyzer Release Notes
This file summarizes user visible changes for each release.
See ApiChangeLog.txt for changes to the STA api.
+2026/08/02
+----------
+
+The set_path_margin command applies a signed slack adjustment to the
+capture-clock side of matching timing paths. A positive margin
+tightens the path and a negative margin loosens it.
+
+ set_path_margin [-setup] [-hold] [-rise] [-fall]
+ [-from from_list|-rise_from from_list|-fall_from from_list]
+ [-through through_list|-rise_through through_list|-fall_through through_list]
+ [-to to_list|-rise_to to_list|-fall_to to_list]
+ [-comment comment]
+ margin
+
2026/07/20
----------
diff --git a/doc/OpenSTA.fodt b/doc/OpenSTA.fodt
index 4e32f507..4ea0eeba 100644
--- a/doc/OpenSTA.fodt
+++ b/doc/OpenSTA.fodt
@@ -12579,6 +12579,91 @@
The set_output_delay command is used to specify the external delay to a setup/hold check on an output port or internal pin that is clocked by clock. Unless the -add_delay option is specified any existing output delays are replaced.
The –reference_pin option is used to specify a timing check with respect to the arrival on a pin in the clock network. For propagated clocks, the timing check is relative to the clock arrival time at the reference pin (the clock source latency and network latency from the clock source to the reference pin). For ideal clocks, the timing check is relative to the reference pin clock source latency. With the -clock_fall flag the timing check is relative to the falling edge of the reference pin. If no clocks arrive at the reference pin the set_output_delay command is ignored. If no -clock is specified the timing check is with respect to all clocks that arrive at the reference pin. The -source_latency_included and -network_latency_included options cannot be used with -reference_pin.
+
+
+
+
+
+ set_path_margin
+
+
+ [-setup][-hold][-rise][-fall][-from from_list][-rise_from from_list][-fall_from from_list][-through through_list][-rise_through through_list][-fall_through through_list][-to to_list][-rise_to to_list][-fall_to to_list][-comment comment]margin
+
+
+
+
+ -setup
+
+
+ Apply the margin to setup checks.
+
+
+
+
+ -hold
+
+
+ Apply the margin to hold checks.
+
+
+
+
+ -rise
+
+
+ Apply the margin to rising path edges.
+
+
+
+
+ -fall
+
+
+ Apply the margin to falling path edges.
+
+
+
+
+ -from from_list
+
+
+ A list of clocks, instances, ports or pins.
+
+
+
+
+ -through through_list
+
+
+ A list of instances, pins or nets.
+
+
+
+
+ -to to_list
+
+
+ A list of clocks, instances, ports or pins.
+
+
+
+
+ -comment comment
+
+
+ Optional comment written by write_sdc.
+
+
+
+
+ margin
+
+
+ Signed slack adjustment applied on the capture clock. A positive margin tightens the path and a negative margin loosens it.
+
+
+
+ The set_path_margin command applies a signed slack adjustment to matching timing paths on the capture-clock side. A positive margin makes the path harder to meet and a negative margin makes it easier. If neither -setup nor -hold is specified the margin applies to both. See set_false_path for a description of allowed from_list, through_list and to_list objects. At least one of -from, -through, or -to is required. Matching exceptions are removed with unset_path_exceptions.
@@ -13724,7 +13809,7 @@
- The unset_path_exceptions command removes any matching set_false_path, set_multicycle_path, set_max_delay, and set_min_delay exceptions.
+ The unset_path_exceptions command removes any matching set_false_path, set_multicycle_path, set_max_delay, set_min_delay, and set_path_margin exceptions.
@@ -14587,6 +14672,7 @@
set_multicycle_path67
set_operating_conditions68
set_output_delay69
+ set_path_margin70
set_port_fanout_number70
set_power_activity70
set_propagated_clock71
diff --git a/doc/OpenSTA.pdf b/doc/OpenSTA.pdf
index 971a4c0c..b261ba45 100644
Binary files a/doc/OpenSTA.pdf and b/doc/OpenSTA.pdf differ
diff --git a/include/sta/ExceptionPath.hh b/include/sta/ExceptionPath.hh
index c67270ae..67f5a2bd 100644
--- a/include/sta/ExceptionPath.hh
+++ b/include/sta/ExceptionPath.hh
@@ -66,6 +66,7 @@ public:
virtual bool isLoop() const { return false; }
virtual bool isMultiCycle() const { return false; }
virtual bool isPathDelay() const { return false; }
+ virtual bool isPathMargin() const { return false; }
virtual bool isGroupPath() const { return false; }
virtual bool isFilter() const { return false; }
virtual ExceptionPathType type() const = 0;
@@ -101,6 +102,7 @@ public:
static int pathDelayPriority() { return 3000; }
static int multiCyclePathPriority() { return 2000; }
static int filterPathPriority() { return 1000; }
+ static int pathMarginPriority() { return 500; }
static int groupPathPriority() { return 0; }
// Compare the value (path delay or cycle count) to another exception
// of the same priority. Because the exception "values" are floats,
@@ -130,6 +132,7 @@ public:
virtual bool useEndClk() const { return false; }
virtual int pathMultiplier() const { return 0; }
virtual float delay() const { return 0.0; }
+ virtual float margin() const { return 0.0; }
virtual std::string_view name() const { return {}; }
virtual bool isDefault() const { return false; }
virtual bool ignoreClkLatency() const { return false; }
@@ -227,6 +230,35 @@ protected:
float delay_;
};
+// set_path_margin
+class PathMargin : public ExceptionPath
+{
+public:
+ PathMargin(ExceptionFrom *from,
+ ExceptionThruSeq *thrus,
+ ExceptionTo *to,
+ const MinMaxAll *min_max,
+ float margin,
+ bool own_pts,
+ std::string_view comment);
+ ExceptionPath *clone(ExceptionFrom *from,
+ ExceptionThruSeq *thrus,
+ ExceptionTo *to,
+ bool own_pts) override;
+ bool isPathMargin() const override { return true; }
+ ExceptionPathType type() const override
+ { return ExceptionPathType::path_margin; }
+ std::string_view typeString() const override;
+ bool mergeable(ExceptionPath *exception) const override;
+ bool overrides(ExceptionPath *exception) const override;
+ float margin() const override { return margin_; }
+ int typePriority() const override;
+ bool tighterThan(ExceptionPath *exception) const override;
+
+protected:
+ float margin_;
+};
+
// set_multicycle_path
class MultiCyclePath : public ExceptionPath
{
diff --git a/include/sta/PathEnd.hh b/include/sta/PathEnd.hh
index be54fa73..a496af76 100644
--- a/include/sta/PathEnd.hh
+++ b/include/sta/PathEnd.hh
@@ -135,6 +135,10 @@ public:
// Target clock uncertainty + inter-clk uncertainty.
virtual float targetClkUncertainty(const StaState *sta) const;
virtual float targetClkMcpAdjustment(const StaState *sta) const;
+ // Target clock path margin (set_path_margin).
+ virtual float targetClkPathMargin(const StaState *sta) const;
+ virtual bool hasPathMargin() const { return false; }
+ virtual PathMargin *pathMargin() const { return nullptr; }
virtual const TimingRole *checkRole(const StaState *sta) const;
const TimingRole *checkGenericRole(const StaState *sta) const;
virtual bool pathDelayMarginIsExternal() const;
@@ -260,6 +264,9 @@ public:
float targetNonInterClkUncertainty(const StaState *sta) const override;
float interClkUncertainty(const StaState *sta) const override;
float targetClkUncertainty(const StaState *sta) const override;
+ float targetClkPathMargin(const StaState *sta) const override;
+ bool hasPathMargin() const override { return path_margin_ != nullptr; }
+ PathMargin *pathMargin() const override { return path_margin_; }
Crpr crpr(const StaState *sta) const override;
Required requiredTime(const StaState *sta) const override;
Slack slack(const StaState *sta) const override;
@@ -270,7 +277,8 @@ public:
protected:
PathEndClkConstrained(Path *path,
- Path *clk_path);
+ Path *clk_path,
+ PathMargin *path_margin);
float sourceClkOffset(const ClockEdge *src_clk_edge,
const ClockEdge *tgt_clk_edge,
const TimingRole *check_role,
@@ -280,6 +288,7 @@ protected:
virtual Required requiredTimeNoCrpr(const StaState *sta) const;
Path *clk_path_;
+ PathMargin *path_margin_;
mutable Crpr crpr_;
mutable bool crpr_valid_{false};
};
@@ -295,7 +304,8 @@ public:
protected:
PathEndClkConstrainedMcp(Path *path,
Path *clk_path,
- MultiCyclePath *mcp);
+ MultiCyclePath *mcp,
+ PathMargin *path_margin);
float checkMcpAdjustment(const Path *path,
const ClockEdge *tgt_clk_edge,
const StaState *sta) const;
@@ -316,6 +326,7 @@ public:
Edge *check_edge,
Path *clk_path,
MultiCyclePath *mcp,
+ PathMargin *path_margin,
const StaState *sta);
PathEnd *copy() const override;
Type type() const override;
@@ -348,6 +359,7 @@ public:
Edge *check_edge,
Path *disable_path,
MultiCyclePath *mcp,
+ PathMargin *path_margin,
PathDelay *path_delay,
const StaState *sta);
Type type() const override;
@@ -404,6 +416,7 @@ public:
Path *path,
Path *clk_path,
MultiCyclePath *mcp,
+ PathMargin *path_margin,
const StaState *sta);
PathEnd *copy() const override;
Type type() const override;
@@ -443,6 +456,7 @@ public:
Path *clk_path,
const TimingRole *check_role,
MultiCyclePath *mcp,
+ PathMargin *path_margin,
ArcDelay margin,
const StaState *sta);
PathEnd *copy() const override;
@@ -468,6 +482,7 @@ public:
Path *data_path,
Path *data_clk_path,
MultiCyclePath *mcp,
+ PathMargin *path_margin,
const StaState *sta);
PathEnd *copy() const override;
Type type() const override;
diff --git a/include/sta/Sdc.hh b/include/sta/Sdc.hh
index 948efe10..f3802efb 100644
--- a/include/sta/Sdc.hh
+++ b/include/sta/Sdc.hh
@@ -757,6 +757,12 @@ public:
bool break_path,
float delay,
std::string_view comment);
+ void makePathMargin(ExceptionFrom *from,
+ ExceptionThruSeq *thrus,
+ ExceptionTo *to,
+ const MinMaxAll *min_max,
+ float margin,
+ std::string_view comment);
bool pathDelaysWithoutTo() const { return path_delays_without_to_; }
// Delete matching false/multicycle/path_delay exceptions.
// Caller owns from, thrus, to exception points (and must delete them).
diff --git a/include/sta/SdcClass.hh b/include/sta/SdcClass.hh
index 62c24efe..b1fe7360 100644
--- a/include/sta/SdcClass.hh
+++ b/include/sta/SdcClass.hh
@@ -44,6 +44,7 @@ class InputDelay;
class OutputDelay;
class FalsePath;
class PathDelay;
+class PathMargin;
class MultiCyclePath;
class FilterPath;
class GroupPath;
@@ -69,6 +70,7 @@ class PortDelay;
enum class AnalysisType { single, bc_wc, ocv };
enum class ExceptionPathType { false_path, loop, multi_cycle, path_delay,
+ path_margin,
group_path, filter, any};
enum class ClockSense { positive, negative, stop };
diff --git a/include/sta/Sta.hh b/include/sta/Sta.hh
index e9b668e5..84e70be1 100644
--- a/include/sta/Sta.hh
+++ b/include/sta/Sta.hh
@@ -654,6 +654,13 @@ public:
float delay,
std::string_view comment,
Sdc *sdc);
+ void makePathMargin(ExceptionFrom *from,
+ ExceptionThruSeq *thrus,
+ ExceptionTo *to,
+ const MinMaxAll *min_max,
+ float margin,
+ std::string_view comment,
+ Sdc *sdc);
void makeGroupPath(std::string_view name,
bool is_default,
ExceptionFrom *from,
diff --git a/include/sta/VisitPathEnds.hh b/include/sta/VisitPathEnds.hh
index 8ac723f5..93081cbb 100644
--- a/include/sta/VisitPathEnds.hh
+++ b/include/sta/VisitPathEnds.hh
@@ -131,6 +131,11 @@ protected:
const Pin *pin,
const RiseFall *rf,
const MinMax *min_max);
+ PathMargin *pathMarginTo(const Path *path,
+ const Pin *pin,
+ const RiseFall *rf,
+ const ClockEdge *clk_edge,
+ const MinMax *min_max) const;
ExceptionPath *exceptionTo(const Path *path,
const Pin *pin,
const RiseFall *rf,
diff --git a/sdc/ExceptionPath.cc b/sdc/ExceptionPath.cc
index 151c98ad..34aab4bc 100644
--- a/sdc/ExceptionPath.cc
+++ b/sdc/ExceptionPath.cc
@@ -559,6 +559,64 @@ PathDelay::overrides(ExceptionPath *exception) const
////////////////////////////////////////////////////////////////
+PathMargin::PathMargin(ExceptionFrom *from,
+ ExceptionThruSeq *thrus,
+ ExceptionTo *to,
+ const MinMaxAll *min_max,
+ float margin,
+ bool own_pts,
+ std::string_view comment) :
+ ExceptionPath(from, thrus, to, min_max, own_pts,
+ pathMarginPriority() + fromThruToPriority(from, thrus, to),
+ comment),
+ margin_(margin)
+{
+}
+
+ExceptionPath *
+PathMargin::clone(ExceptionFrom *from,
+ ExceptionThruSeq *thrus,
+ ExceptionTo *to,
+ bool own_pts)
+{
+ return new PathMargin(from, thrus, to, min_max_, margin_, own_pts, comment_);
+}
+
+int
+PathMargin::typePriority() const
+{
+ return pathMarginPriority();
+}
+
+bool
+PathMargin::tighterThan(ExceptionPath *) const
+{
+ return false;
+}
+
+std::string_view
+PathMargin::typeString() const
+{
+ return "Margin";
+}
+
+bool
+PathMargin::mergeable(ExceptionPath *) const
+{
+ return false;
+}
+
+bool
+PathMargin::overrides(ExceptionPath *exception) const
+{
+ // A later set_path_margin with the same scope replaces the earlier one.
+ return exception->isPathMargin()
+ && exception->priority() == priority_
+ && exception->minMax() == min_max_;
+}
+
+////////////////////////////////////////////////////////////////
+
FalsePath::FalsePath(ExceptionFrom *from,
ExceptionThruSeq *thrus,
ExceptionTo *to,
diff --git a/sdc/Sdc.cc b/sdc/Sdc.cc
index 77f3ba89..f538cb50 100644
--- a/sdc/Sdc.cc
+++ b/sdc/Sdc.cc
@@ -3958,6 +3958,20 @@ Sdc::makePathDelay(ExceptionFrom *from,
addException(exception);
}
+void
+Sdc::makePathMargin(ExceptionFrom *from,
+ ExceptionThruSeq *thrus,
+ ExceptionTo *to,
+ const MinMaxAll *min_max,
+ float margin,
+ std::string_view comment)
+{
+ checkFromThrusTo(from, thrus, to);
+ PathMargin *exception = new PathMargin(from, thrus, to, min_max,
+ margin, true, comment);
+ addException(exception);
+}
+
void
Sdc::recordPathDelayInternalFrom(ExceptionPath *exception)
{
@@ -4365,7 +4379,8 @@ Sdc::addException1(ExceptionPath *exception)
void
Sdc::addException2(ExceptionPath *exception)
{
- if (exception->isMultiCycle() || exception->isPathDelay())
+ if (exception->isMultiCycle() || exception->isPathDelay()
+ || exception->isPathMargin())
deleteMatchingExceptions(exception);
recordException(exception);
mergeException(exception);
diff --git a/sdc/Sdc.i b/sdc/Sdc.i
index d326512e..d08c6e9f 100644
--- a/sdc/Sdc.i
+++ b/sdc/Sdc.i
@@ -838,6 +838,19 @@ make_path_delay(ExceptionFrom *from,
delay, std::move(comment), sdc);
}
+void
+make_path_margin(ExceptionFrom *from,
+ ExceptionThruSeq *thrus,
+ ExceptionTo *to,
+ const MinMaxAll *min_max,
+ float margin,
+ std::string_view comment)
+{
+ Sta *sta = Sta::sta();
+ Sdc *sdc = sta->cmdSdc();
+ sta->makePathMargin(from, thrus, to, min_max, margin, comment, sdc);
+}
+
void
reset_path_cmd(ExceptionFrom *
from, ExceptionThruSeq *thrus,
diff --git a/sdc/Sdc.tcl b/sdc/Sdc.tcl
index 6059f9ae..64fdbfc3 100644
--- a/sdc/Sdc.tcl
+++ b/sdc/Sdc.tcl
@@ -2430,6 +2430,61 @@ proc set_min_delay { args } {
################################################################
+define_cmd_args "set_path_margin" \
+ {[-setup] [-hold] [-rise] [-fall] [-comment comment]\
+ [-from from_list] [-rise_from from_list] [-fall_from from_list]\
+ [-through|-thr|-th through_list] [-rise_through|-rise_thr|-rise_th through_list]\
+ [-fall_through|-fall_thr|-fall_th through_list]\
+ [-to to_list] [-rise_to to_list] [-fall_to to_list] margin}
+
+proc set_path_margin { args } {
+ parse_key_args "set_path_margin" args \
+ keys {-from -rise_from -fall_from -to -rise_to -fall_to -comment} \
+ flags {-rise -fall -setup -hold} 0
+
+ # Applies to setup, hold, or both.
+ set min_max "min_max"
+ if { [info exists flags(-setup)] && ![info exists flags(-hold)] } {
+ set min_max "max"
+ } elseif { [info exists flags(-hold)] && ![info exists flags(-setup)] } {
+ set min_max "min"
+ }
+
+ # Validate arguments.
+ set cmd "set_path_margin"
+ set arg_error 0
+ set from [parse_from_arg keys arg_error]
+ set thrus [parse_thrus_arg args arg_error]
+ set to [parse_to_arg keys flags arg_error]
+ check_exception_pins $from $to
+ if { $arg_error } {
+ delete_from_thrus_to $from $thrus $to
+ return
+ }
+
+ # Validate margin value count and argument type.
+ check_for_key_args $cmd args
+ if { [llength $args] == 0 } {
+ delete_from_thrus_to $from $thrus $to
+ sta_error 1800 "missing margin argument."
+ } elseif { [llength $args] > 1 } {
+ sta_warn 1801 "'$args' ignored."
+ }
+ if { $from == "NULL" && $thrus == "" && $to == "NULL" } {
+ delete_from_thrus_to $from $thrus $to
+ sta_error 1802 "-from, -through or -to required."
+ }
+
+ # Parse margin value.
+ set margin [lindex $args 0]
+ check_float "set_path_margin margin" $margin
+ set margin [time_ui_sta $margin]
+ set comment [parse_comment_key keys]
+ make_path_margin $from $thrus $to $min_max $margin $comment
+}
+
+################################################################
+
define_cmd_args "set_min_pulse_width" {[-low] [-high] value [objects]}
proc set_min_pulse_width { args } {
diff --git a/sdc/WriteSdc.cc b/sdc/WriteSdc.cc
index 4fa34656..f979d9eb 100644
--- a/sdc/WriteSdc.cc
+++ b/sdc/WriteSdc.cc
@@ -1231,6 +1231,10 @@ WriteSdc::writeExceptionCmd(ExceptionPath *exception) const
if (exception->ignoreClkLatency())
sta::print(stream_, " -ignore_clock_latency");
}
+ else if (exception->isPathMargin()) {
+ sta::print(stream_, "set_path_margin");
+ writeSetupHoldFlag(exception->minMax());
+ }
else if (exception->isGroupPath()) {
if (exception->isDefault())
sta::print(stream_, "group_path -default");
@@ -1251,6 +1255,10 @@ WriteSdc::writeExceptionValue(ExceptionPath *exception) const
sta::print(stream_, " ");
writeTime(exception->delay());
}
+ else if (exception->isPathMargin()) {
+ sta::print(stream_, " ");
+ writeTime(exception->margin());
+ }
}
void
diff --git a/search/PathEnd.cc b/search/PathEnd.cc
index e917d89f..b4d6c036 100644
--- a/search/PathEnd.cc
+++ b/search/PathEnd.cc
@@ -222,6 +222,12 @@ PathEnd::targetClkMcpAdjustment(const StaState *) const
return 0.0;
}
+float
+PathEnd::targetClkPathMargin(const StaState *) const
+{
+ return 0.0;
+}
+
const TimingRole *
PathEnd::checkRole(const StaState *) const
{
@@ -511,9 +517,11 @@ PathEndUnconstrained::typeName() const
////////////////////////////////////////////////////////////////
PathEndClkConstrained::PathEndClkConstrained(Path *path,
- Path *clk_path) :
+ Path *clk_path,
+ PathMargin *path_margin) :
PathEnd(path),
clk_path_(clk_path),
+ path_margin_(path_margin),
crpr_(0.0)
{
}
@@ -630,8 +638,9 @@ PathEndClkConstrained::targetClkArrivalNoCrpr(const StaState *sta) const
targetClkPath(),
checkRole(sta),
sdc);
- return delaySum(delaySum(clk_arrival, uncertainty, sta),
- targetClkMcpAdjustment(sta), sta);
+ return delaySum(delaySum(delaySum(clk_arrival, uncertainty, sta),
+ targetClkMcpAdjustment(sta), sta),
+ targetClkPathMargin(sta), sta);
}
Delay
@@ -692,6 +701,17 @@ PathEndClkConstrained::targetClkUncertainty(const StaState *sta) const
targetClkPath(), checkRole(sta), sdc);
}
+float
+PathEndClkConstrained::targetClkPathMargin(const StaState *sta) const
+{
+ if (!path_margin_)
+ return 0.0;
+ float margin = path_margin_->margin();
+ if (checkRole(sta)->genericRole() == TimingRole::setup())
+ margin = -margin;
+ return margin;
+}
+
Crpr
PathEndClkConstrained::crpr(const StaState *sta) const
{
@@ -752,8 +772,9 @@ PathEndClkConstrained::exceptPathCmp(const PathEnd *path_end,
PathEndClkConstrainedMcp::PathEndClkConstrainedMcp(Path *path,
Path *clk_path,
- MultiCyclePath *mcp) :
- PathEndClkConstrained(path, clk_path),
+ MultiCyclePath *mcp,
+ PathMargin *path_margin) :
+ PathEndClkConstrained(path, clk_path, path_margin),
mcp_(mcp)
{
}
@@ -921,8 +942,9 @@ PathEndCheck::PathEndCheck(Path *path,
Edge *check_edge,
Path *clk_path,
MultiCyclePath *mcp,
+ PathMargin *path_margin,
const StaState *) :
- PathEndClkConstrainedMcp(path, clk_path, mcp),
+ PathEndClkConstrainedMcp(path, clk_path, mcp, path_margin),
check_arc_(check_arc),
check_edge_(check_edge)
{
@@ -1082,9 +1104,10 @@ PathEndLatchCheck::PathEndLatchCheck(Path *path,
Edge *check_edge,
Path *disable_path,
MultiCyclePath *mcp,
+ PathMargin *path_margin,
PathDelay *path_delay,
const StaState *sta) :
- PathEndCheck(path, check_arc, check_edge, nullptr, mcp, sta),
+ PathEndCheck(path, check_arc, check_edge, nullptr, mcp, path_margin, sta),
disable_path_(disable_path),
path_delay_(path_delay),
src_clk_arrival_(0.0)
@@ -1300,9 +1323,10 @@ PathEndOutputDelay::PathEndOutputDelay(OutputDelay *output_delay,
Path *path,
Path *clk_path,
MultiCyclePath *mcp,
+ PathMargin *path_margin,
const StaState *) :
// No target clk_path_ for output delays.
- PathEndClkConstrainedMcp(path, clk_path, mcp),
+ PathEndClkConstrainedMcp(path, clk_path, mcp, path_margin),
output_delay_(output_delay)
{
}
@@ -1494,9 +1518,10 @@ PathEndGatedClock::PathEndGatedClock(Path *gating_ref,
Path *clk_path,
const TimingRole *check_role,
MultiCyclePath *mcp,
+ PathMargin *path_margin,
ArcDelay margin,
const StaState *) :
- PathEndClkConstrainedMcp(gating_ref, clk_path, mcp),
+ PathEndClkConstrainedMcp(gating_ref, clk_path, mcp, path_margin),
check_role_(check_role),
margin_(margin)
{
@@ -1564,8 +1589,9 @@ PathEndDataCheck::PathEndDataCheck(DataCheck *check,
Path *data_path,
Path *data_clk_path,
MultiCyclePath *mcp,
+ PathMargin *path_margin,
const StaState *sta) :
- PathEndClkConstrainedMcp(data_path, nullptr, mcp),
+ PathEndClkConstrainedMcp(data_path, nullptr, mcp, path_margin),
data_clk_path_(data_clk_path),
check_(check)
{
@@ -1711,7 +1737,7 @@ PathEndDataCheck::exceptPathCmp(const PathEnd *path_end,
PathEndPathDelay::PathEndPathDelay(PathDelay *path_delay,
Path *path,
const StaState *sta):
- PathEndClkConstrained(path, nullptr),
+ PathEndClkConstrained(path, nullptr, nullptr),
path_delay_(path_delay),
check_arc_(nullptr),
check_edge_(nullptr),
@@ -1724,7 +1750,7 @@ PathEndPathDelay::PathEndPathDelay(PathDelay *path_delay,
Path *path,
OutputDelay *output_delay,
const StaState *sta):
- PathEndClkConstrained(path, nullptr),
+ PathEndClkConstrained(path, nullptr, nullptr),
path_delay_(path_delay),
check_arc_(nullptr),
check_edge_(nullptr),
@@ -1739,7 +1765,7 @@ PathEndPathDelay::PathEndPathDelay(PathDelay *path_delay,
TimingArc *check_arc,
Edge *check_edge,
const StaState *sta) :
- PathEndClkConstrained(path, clk_path),
+ PathEndClkConstrained(path, clk_path, nullptr),
path_delay_(path_delay),
check_arc_(check_arc),
check_edge_(check_edge),
diff --git a/search/ReportPath.cc b/search/ReportPath.cc
index 1bfcdfc6..12e3b2af 100644
--- a/search/ReportPath.cc
+++ b/search/ReportPath.cc
@@ -1134,6 +1134,9 @@ ReportPath::reportJson(const PathEnd *end,
delayAsFloat(end->checkCrpr(this)));
result += sta::format(" \"margin\": {:.3e},\n",
delayAsFloat(end->margin(this)));
+ if (end->hasPathMargin())
+ result += sta::format(" \"path_margin\": {:.3e},\n",
+ end->targetClkPathMargin(this));
result += sta::format(" \"required_time\": {:.3e},\n",
delayAsFloat(end->requiredTimeOffset(this)));
result += sta::format(" \"slack\": {:.3e}\n",
@@ -2569,6 +2572,11 @@ ReportPath::reportClkUncertainty(const PathEnd *end,
if (inter_uncertainty != 0.0)
reportLine("inter-clock uncertainty", inter_uncertainty,
clk_arrival, early_late);
+ if (end->hasPathMargin()) {
+ float margin = end->targetClkPathMargin(this);
+ clk_arrival = delaySum(clk_arrival, margin, this);
+ reportLine("path margin", margin, clk_arrival, early_late);
+ }
}
////////////////////////////////////////////////////////////////
diff --git a/search/Sta.cc b/search/Sta.cc
index fc7136de..7ece680a 100644
--- a/search/Sta.cc
+++ b/search/Sta.cc
@@ -2070,6 +2070,19 @@ Sta::makePathDelay(ExceptionFrom *from,
search_->arrivalsInvalid();
}
+void
+Sta::makePathMargin(ExceptionFrom *from,
+ ExceptionThruSeq *thrus,
+ ExceptionTo *to,
+ const MinMaxAll *min_max,
+ float margin,
+ std::string_view comment,
+ Sdc *sdc)
+{
+ sdc->makePathMargin(from, thrus, to, min_max, margin, comment);
+ search_->arrivalsInvalid();
+}
+
void
Sta::resetPath(ExceptionFrom *from,
ExceptionThruSeq *thrus,
diff --git a/search/VisitPathEnds.cc b/search/VisitPathEnds.cc
index 3a420aad..98b05d1c 100644
--- a/search/VisitPathEnds.cc
+++ b/search/VisitPathEnds.cc
@@ -185,19 +185,22 @@ VisitPathEnds::visitCheckEnd(const Pin *pin,
&& (exception == nullptr
|| exception->isFilter()
|| exception->isGroupPath()
- || exception->isMultiCycle())) {
+ || exception->isMultiCycle()
+ || exception->isPathMargin())) {
MultiCyclePath *mcp=dynamic_cast(exception);
+ PathMargin *path_margin = pathMarginTo(path, pin, end_rf,
+ tgt_clk_edge, min_max);
if (network_->isLatchData(pin)
&& check_role == TimingRole::setup()) {
PathEndLatchCheck path_end(path, check_arc, edge,
- tgt_clk_path, mcp, nullptr,
- this);
+ tgt_clk_path, mcp, path_margin,
+ nullptr, this);
visitor->visit(&path_end);
is_constrained = true;
}
else {
PathEndCheck path_end(path, check_arc, edge,
- tgt_clk_path, mcp, this);
+ tgt_clk_path, mcp, path_margin, this);
visitor->visit(&path_end);
is_constrained = true;
}
@@ -210,7 +213,7 @@ VisitPathEnds::visitCheckEnd(const Pin *pin,
if (network_->isLatchData(pin)
&& check_role == TimingRole::setup()) {
PathEndLatchCheck path_end(path, check_arc, edge,
- tgt_clk_path, nullptr,
+ tgt_clk_path, nullptr, nullptr,
path_delay, this);
visitor->visit(&path_end);
}
@@ -369,9 +372,13 @@ VisitPathEnds::visitOutputDelayEnd1(OutputDelay *output_delay,
&& (exception == nullptr
|| exception->isFilter()
|| exception->isGroupPath()
- || exception->isMultiCycle())) {
+ || exception->isMultiCycle()
+ || exception->isPathMargin())) {
MultiCyclePath *mcp = dynamic_cast(exception);
- PathEndOutputDelay path_end(output_delay, path, ref_path, mcp, this);
+ PathMargin *path_margin = pathMarginTo(path, pin, end_rf,
+ tgt_clk_edge, min_max);
+ PathEndOutputDelay path_end(output_delay, path, ref_path, mcp,
+ path_margin, this);
visitor->visit(&path_end);
is_constrained = true;
}
@@ -442,13 +449,16 @@ VisitPathEnds::visitGatedClkEnd(const Pin *pin,
&& (exception == nullptr
|| exception->isFilter()
|| exception->isGroupPath()
- || exception->isMultiCycle())
+ || exception->isMultiCycle()
+ || exception->isPathMargin())
&& (!filtered
|| search_->matchesFilter(path, clk_edge))) {
MultiCyclePath *mcp =
dynamic_cast(exception);
+ PathMargin *path_margin = pathMarginTo(path, pin, end_rf,
+ clk_edge, min_max);
PathEndGatedClock path_end(path, clk_path, check_role,
- mcp, margin, this);
+ mcp, path_margin, margin, this);
visitor->visit(&path_end);
is_constrained = true;
}
@@ -565,11 +575,15 @@ VisitPathEnds::visitDataCheckEnd1(DataCheck *check,
&& (exception == nullptr
|| exception->isFilter()
|| exception->isGroupPath()
- || exception->isMultiCycle())
+ || exception->isMultiCycle()
+ || exception->isPathMargin())
&& (!filtered
|| search_->matchesFilter(path, tgt_clk_edge))) {
MultiCyclePath *mcp=dynamic_cast(exception);
- PathEndDataCheck path_end(check, path, tgt_clk_path, mcp, this);
+ PathMargin *path_margin = pathMarginTo(path, pin, end_rf,
+ tgt_clk_edge, min_max);
+ PathEndDataCheck path_end(check, path, tgt_clk_path, mcp,
+ path_margin, this);
visitor->visit(&path_end);
is_constrained = true;
}
@@ -639,6 +653,19 @@ VisitPathEnds::pathDelayTo(Path *path,
return dynamic_cast(exception);
}
+PathMargin *
+VisitPathEnds::pathMarginTo(const Path *path,
+ const Pin *pin,
+ const RiseFall *rf,
+ const ClockEdge *clk_edge,
+ const MinMax *min_max) const
+{
+ ExceptionPath *exception =
+ search_->exceptionTo(ExceptionPathType::path_margin, path, pin, rf,
+ clk_edge, min_max, false, false, path->sdc(this));
+ return dynamic_cast(exception);
+}
+
ExceptionPath *
VisitPathEnds::exceptionTo(const Path *path,
const Pin *pin,
diff --git a/test/regression_vars.tcl b/test/regression_vars.tcl
index 7cc0a2aa..ec03cdc3 100644
--- a/test/regression_vars.tcl
+++ b/test/regression_vars.tcl
@@ -168,6 +168,12 @@ record_public_tests {
report_checks_src_attr
report_json1
report_json2
+ set_path_margin1
+ set_path_margin2
+ set_path_margin3
+ set_path_margin4
+ set_path_margin5
+ set_path_margin6
slash_port_test
suppress_msg
user_properties
diff --git a/test/set_path_margin1.ok b/test/set_path_margin1.ok
new file mode 100644
index 00000000..c0967df7
--- /dev/null
+++ b/test/set_path_margin1.ok
@@ -0,0 +1,122 @@
+Startpoint: r2 (rising edge-triggered flip-flop clocked by clk)
+Endpoint: r3 (rising edge-triggered flip-flop clocked by clk)
+Path Group: clk
+Path Type: max
+
+ Delay Time Description
+-------------------------------------------------------------
+ 0.0000 0.0000 clock clk (rise edge)
+ 0.0000 0.0000 clock network delay (ideal)
+ 0.0000 0.0000 ^ r2/CK (DFF_X1)
+ 0.0772 0.0772 v r2/Q (DFF_X1)
+ 0.0233 0.1006 v u1/Z (BUF_X1)
+ 0.0273 0.1278 v u2/ZN (AND2_X1)
+ 0.0000 0.1278 v r3/D (DFF_X1)
+ 0.1278 data arrival time
+
+ 10.0000 10.0000 clock clk (rise edge)
+ 0.0000 10.0000 clock network delay (ideal)
+ -0.5000 9.5000 path margin
+ 0.0000 9.5000 clock reconvergence pessimism
+ 9.5000 ^ r3/CK (DFF_X1)
+ -0.0390 9.4610 library setup time
+ 9.4610 data required time
+-------------------------------------------------------------
+ 9.4610 data required time
+ -0.1278 data arrival time
+-------------------------------------------------------------
+ 9.3332 slack (MET)
+
+
+setup_to_tighten -5.000e-10
+Startpoint: r1 (rising edge-triggered flip-flop clocked by clk)
+Endpoint: r3 (rising edge-triggered flip-flop clocked by clk)
+Path Group: clk
+Path Type: min
+
+ Delay Time Description
+-------------------------------------------------------------
+ 0.0000 0.0000 clock clk (rise edge)
+ 0.0000 0.0000 clock network delay (ideal)
+ 0.0000 0.0000 ^ r1/CK (DFF_X1)
+ 0.0772 0.0772 v r1/Q (DFF_X1)
+ 0.0254 0.1026 v u2/ZN (AND2_X1)
+ 0.0000 0.1026 v r3/D (DFF_X1)
+ 0.1026 data arrival time
+
+ 0.0000 0.0000 clock clk (rise edge)
+ 0.0000 0.0000 clock network delay (ideal)
+ 0.5000 0.5000 path margin
+ 0.0000 0.5000 clock reconvergence pessimism
+ 0.5000 ^ r3/CK (DFF_X1)
+ 0.0016 0.5016 library hold time
+ 0.5016 data required time
+-------------------------------------------------------------
+ 0.5016 data required time
+ -0.1026 data arrival time
+-------------------------------------------------------------
+ -0.3990 slack (VIOLATED)
+
+
+hold_to_tighten 5.000e-10
+Startpoint: r2 (rising edge-triggered flip-flop clocked by clk)
+Endpoint: r3 (rising edge-triggered flip-flop clocked by clk)
+Path Group: clk
+Path Type: max
+
+ Delay Time Description
+-------------------------------------------------------------
+ 0.0000 0.0000 clock clk (rise edge)
+ 0.0000 0.0000 clock network delay (ideal)
+ 0.0000 0.0000 ^ r2/CK (DFF_X1)
+ 0.0772 0.0772 v r2/Q (DFF_X1)
+ 0.0233 0.1006 v u1/Z (BUF_X1)
+ 0.0273 0.1278 v u2/ZN (AND2_X1)
+ 0.0000 0.1278 v r3/D (DFF_X1)
+ 0.1278 data arrival time
+
+ 10.0000 10.0000 clock clk (rise edge)
+ 0.0000 10.0000 clock network delay (ideal)
+ 67.0000 77.0000 path margin
+ 0.0000 77.0000 clock reconvergence pessimism
+ 77.0000 ^ r3/CK (DFF_X1)
+ -0.0390 76.9610 library setup time
+ 76.9610 data required time
+-------------------------------------------------------------
+ 76.9610 data required time
+ -0.1278 data arrival time
+-------------------------------------------------------------
+ 76.8332 slack (MET)
+
+
+setup_to_loosen 6.700e-08
+Startpoint: r1 (rising edge-triggered flip-flop clocked by clk)
+Endpoint: r3 (rising edge-triggered flip-flop clocked by clk)
+Path Group: clk
+Path Type: min
+
+ Delay Time Description
+-------------------------------------------------------------
+ 0.0000 0.0000 clock clk (rise edge)
+ 0.0000 0.0000 clock network delay (ideal)
+ 0.0000 0.0000 ^ r1/CK (DFF_X1)
+ 0.0772 0.0772 v r1/Q (DFF_X1)
+ 0.0254 0.1026 v u2/ZN (AND2_X1)
+ 0.0000 0.1026 v r3/D (DFF_X1)
+ 0.1026 data arrival time
+
+ 0.0000 0.0000 clock clk (rise edge)
+ 0.0000 0.0000 clock network delay (ideal)
+ -0.5000 -0.5000 path margin
+ 0.0000 -0.5000 clock reconvergence pessimism
+ -0.5000 ^ r3/CK (DFF_X1)
+ 0.0016 -0.4984 library hold time
+ -0.4984 data required time
+-------------------------------------------------------------
+ -0.4984 data required time
+ -0.1026 data arrival time
+-------------------------------------------------------------
+ 0.6010 slack (MET)
+
+
+hold_to_loosen -5.000e-10
diff --git a/test/set_path_margin1.tcl b/test/set_path_margin1.tcl
new file mode 100644
index 00000000..41793261
--- /dev/null
+++ b/test/set_path_margin1.tcl
@@ -0,0 +1,37 @@
+# set_path_margin -to with -setup/-hold
+
+read_liberty ../examples/nangate45_typ.lib.gz
+read_verilog ../examples/example1.v
+link_design top
+create_clock -name clk -period 10 {clk1 clk2 clk3}
+set_input_delay -clock clk 0 {in1 in2}
+
+proc setup_at { args } {
+ report_checks {*}$args -path_delay max -digits 4 -fields {} -group_path_count 1
+}
+proc hold_at { args } {
+ report_checks {*}$args -path_delay min -digits 4 -fields {} -group_path_count 1
+}
+proc report_json_path_margin { label path_delay args } {
+ set cmd [concat [list report_checks] $args \
+ [list -path_delay $path_delay -format json -group_path_count 1]]
+ with_output_to_variable json $cmd
+ if { [regexp {"path_margin": ([^,\n]+)} $json match margin] } {
+ puts "$label $margin"
+ } else {
+ puts "$label none"
+ }
+}
+
+set_path_margin -setup 0.50 -comment {tighten setup time} -to [get_pins r3/D]
+setup_at -to [get_pins r3/D]
+report_json_path_margin setup_to_tighten max -to [get_pins r3/D]
+set_path_margin -hold 0.50 -comment {tighten hold time} -to [get_pins r3/D]
+hold_at -to [get_pins r3/D]
+report_json_path_margin hold_to_tighten min -to [get_pins r3/D]
+set_path_margin -setup -67 -comment {loosen setup time} -to [get_pins r3/D]
+setup_at -to [get_pins r3/D]
+report_json_path_margin setup_to_loosen max -to [get_pins r3/D]
+set_path_margin -hold -0.50 -comment {loosen hold time} -to [get_pins r3/D]
+hold_at -to [get_pins r3/D]
+report_json_path_margin hold_to_loosen min -to [get_pins r3/D]
diff --git a/test/set_path_margin2.ok b/test/set_path_margin2.ok
new file mode 100644
index 00000000..2c1eaf37
--- /dev/null
+++ b/test/set_path_margin2.ok
@@ -0,0 +1,60 @@
+Startpoint: r1 (rising edge-triggered flip-flop clocked by clk)
+Endpoint: r3 (rising edge-triggered flip-flop clocked by clk)
+Path Group: clk
+Path Type: max
+
+ Delay Time Description
+-------------------------------------------------------------
+ 0.0000 0.0000 clock clk (rise edge)
+ 0.0000 0.0000 clock network delay (ideal)
+ 0.0000 0.0000 ^ r1/CK (DFF_X1)
+ 0.0836 0.0836 ^ r1/Q (DFF_X1)
+ 0.0277 0.1113 ^ u2/ZN (AND2_X1)
+ 0.0000 0.1113 ^ r3/D (DFF_X1)
+ 0.1113 data arrival time
+
+ 10.0000 10.0000 clock clk (rise edge)
+ 0.0000 10.0000 clock network delay (ideal)
+ -2.0000 8.0000 path margin
+ 0.0000 8.0000 clock reconvergence pessimism
+ 8.0000 ^ r3/CK (DFF_X1)
+ -0.0311 7.9689 library setup time
+ 7.9689 data required time
+-------------------------------------------------------------
+ 7.9689 data required time
+ -0.1113 data arrival time
+-------------------------------------------------------------
+ 7.8576 slack (MET)
+
+
+setup_from_r1 -2.000e-09
+Startpoint: r2 (rising edge-triggered flip-flop clocked by clk)
+Endpoint: r3 (rising edge-triggered flip-flop clocked by clk)
+Path Group: clk
+Path Type: max
+
+ Delay Time Description
+-------------------------------------------------------------
+ 0.0000 0.0000 clock clk (rise edge)
+ 0.0000 0.0000 clock network delay (ideal)
+ 0.0000 0.0000 ^ r2/CK (DFF_X1)
+ 0.0772 0.0772 v r2/Q (DFF_X1)
+ 0.0233 0.1006 v u1/Z (BUF_X1)
+ 0.0273 0.1278 v u2/ZN (AND2_X1)
+ 0.0000 0.1278 v r3/D (DFF_X1)
+ 0.1278 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
+ 10.0000 ^ r3/CK (DFF_X1)
+ -0.0390 9.9610 library setup time
+ 9.9610 data required time
+-------------------------------------------------------------
+ 9.9610 data required time
+ -0.1278 data arrival time
+-------------------------------------------------------------
+ 9.8332 slack (MET)
+
+
+setup_from_r2 none
diff --git a/test/set_path_margin2.tcl b/test/set_path_margin2.tcl
new file mode 100644
index 00000000..ebc1de59
--- /dev/null
+++ b/test/set_path_margin2.tcl
@@ -0,0 +1,29 @@
+# set_path_margin -from
+
+read_liberty ../examples/nangate45_typ.lib.gz
+read_verilog ../examples/example1.v
+link_design top
+create_clock -name clk -period 10 {clk1 clk2 clk3}
+set_input_delay -clock clk 0 {in1 in2}
+
+proc setup_at { args } {
+ report_checks {*}$args -path_delay max -digits 4 -fields {} -group_path_count 1
+}
+proc report_json_path_margin { label path_delay args } {
+ set cmd [concat [list report_checks] $args \
+ [list -path_delay $path_delay -format json -group_path_count 1]]
+ with_output_to_variable json $cmd
+ if { [regexp {"path_margin": ([^,\n]+)} $json match margin] } {
+ puts "$label $margin"
+ } else {
+ puts "$label none"
+ }
+}
+
+set_path_margin -setup 2.0 -from [get_pins r1/CK]
+# Should see path margin.
+setup_at -from [get_pins r1/CK] -to [get_pins r3/D]
+report_json_path_margin setup_from_r1 max -from [get_pins r1/CK] -to [get_pins r3/D]
+# Should not see path margin.
+setup_at -from [get_pins r2/CK] -to [get_pins r3/D]
+report_json_path_margin setup_from_r2 max -from [get_pins r2/CK] -to [get_pins r3/D]
diff --git a/test/set_path_margin3.ok b/test/set_path_margin3.ok
new file mode 100644
index 00000000..f18e9b7a
--- /dev/null
+++ b/test/set_path_margin3.ok
@@ -0,0 +1,60 @@
+Startpoint: r1 (rising edge-triggered flip-flop clocked by clk)
+Endpoint: r3 (rising edge-triggered flip-flop clocked by clk)
+Path Group: clk
+Path Type: max
+
+ Delay Time Description
+-------------------------------------------------------------
+ 0.0000 0.0000 clock clk (rise edge)
+ 0.0000 0.0000 clock network delay (ideal)
+ 0.0000 0.0000 ^ r1/CK (DFF_X1)
+ 0.0836 0.0836 ^ r1/Q (DFF_X1)
+ 0.0277 0.1113 ^ u2/ZN (AND2_X1)
+ 0.0000 0.1113 ^ r3/D (DFF_X1)
+ 0.1113 data arrival time
+
+ 10.0000 10.0000 clock clk (rise edge)
+ 0.0000 10.0000 clock network delay (ideal)
+ -5.0000 5.0000 path margin
+ 0.0000 5.0000 clock reconvergence pessimism
+ 5.0000 ^ r3/CK (DFF_X1)
+ -0.0311 4.9689 library setup time
+ 4.9689 data required time
+-------------------------------------------------------------
+ 4.9689 data required time
+ -0.1113 data arrival time
+-------------------------------------------------------------
+ 4.8576 slack (MET)
+
+
+setup_from_to_r1 -5.000e-09
+Startpoint: r2 (rising edge-triggered flip-flop clocked by clk)
+Endpoint: r3 (rising edge-triggered flip-flop clocked by clk)
+Path Group: clk
+Path Type: max
+
+ Delay Time Description
+-------------------------------------------------------------
+ 0.0000 0.0000 clock clk (rise edge)
+ 0.0000 0.0000 clock network delay (ideal)
+ 0.0000 0.0000 ^ r2/CK (DFF_X1)
+ 0.0772 0.0772 v r2/Q (DFF_X1)
+ 0.0233 0.1006 v u1/Z (BUF_X1)
+ 0.0273 0.1278 v u2/ZN (AND2_X1)
+ 0.0000 0.1278 v r3/D (DFF_X1)
+ 0.1278 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
+ 10.0000 ^ r3/CK (DFF_X1)
+ -0.0390 9.9610 library setup time
+ 9.9610 data required time
+-------------------------------------------------------------
+ 9.9610 data required time
+ -0.1278 data arrival time
+-------------------------------------------------------------
+ 9.8332 slack (MET)
+
+
+setup_from_to_r2 none
diff --git a/test/set_path_margin3.tcl b/test/set_path_margin3.tcl
new file mode 100644
index 00000000..ad4cc813
--- /dev/null
+++ b/test/set_path_margin3.tcl
@@ -0,0 +1,29 @@
+# set_path_margin -from and -to
+
+read_liberty ../examples/nangate45_typ.lib.gz
+read_verilog ../examples/example1.v
+link_design top
+create_clock -name clk -period 10 {clk1 clk2 clk3}
+set_input_delay -clock clk 0 {in1 in2}
+
+proc setup_at { args } {
+ report_checks {*}$args -path_delay max -digits 4 -fields {} -group_path_count 1
+}
+proc report_json_path_margin { label path_delay args } {
+ set cmd [concat [list report_checks] $args \
+ [list -path_delay $path_delay -format json -group_path_count 1]]
+ with_output_to_variable json $cmd
+ if { [regexp {"path_margin": ([^,\n]+)} $json match margin] } {
+ puts "$label $margin"
+ } else {
+ puts "$label none"
+ }
+}
+
+set_path_margin -setup 5.0 -from [get_pins r1/CK] -to [get_pins r3/D]
+# Should see path margin.
+setup_at -from [get_pins r1/CK] -to [get_pins r3/D]
+report_json_path_margin setup_from_to_r1 max -from [get_pins r1/CK] -to [get_pins r3/D]
+# Should not see path margin.
+setup_at -from [get_pins r2/CK] -to [get_pins r3/D]
+report_json_path_margin setup_from_to_r2 max -from [get_pins r2/CK] -to [get_pins r3/D]
diff --git a/test/set_path_margin4.ok b/test/set_path_margin4.ok
new file mode 100644
index 00000000..d24d46cd
--- /dev/null
+++ b/test/set_path_margin4.ok
@@ -0,0 +1,60 @@
+Startpoint: r1 (rising edge-triggered flip-flop clocked by clk)
+Endpoint: r3 (rising edge-triggered flip-flop clocked by clk)
+Path Group: clk
+Path Type: max
+
+ Delay Time Description
+-------------------------------------------------------------
+ 0.0000 0.0000 clock clk (rise edge)
+ 0.0000 0.0000 clock network delay (ideal)
+ 0.0000 0.0000 ^ r1/CK (DFF_X1)
+ 0.0836 0.0836 ^ r1/Q (DFF_X1)
+ 0.0277 0.1113 ^ u2/ZN (AND2_X1)
+ 0.0000 0.1113 ^ r3/D (DFF_X1)
+ 0.1113 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
+ 10.0000 ^ r3/CK (DFF_X1)
+ -0.0311 9.9689 library setup time
+ 9.9689 data required time
+-------------------------------------------------------------
+ 9.9689 data required time
+ -0.1113 data arrival time
+-------------------------------------------------------------
+ 9.8576 slack (MET)
+
+
+setup_through_r1 none
+Startpoint: r2 (rising edge-triggered flip-flop clocked by clk)
+Endpoint: r3 (rising edge-triggered flip-flop clocked by clk)
+Path Group: clk
+Path Type: max
+
+ Delay Time Description
+-------------------------------------------------------------
+ 0.0000 0.0000 clock clk (rise edge)
+ 0.0000 0.0000 clock network delay (ideal)
+ 0.0000 0.0000 ^ r2/CK (DFF_X1)
+ 0.0772 0.0772 v r2/Q (DFF_X1)
+ 0.0233 0.1006 v u1/Z (BUF_X1)
+ 0.0273 0.1278 v u2/ZN (AND2_X1)
+ 0.0000 0.1278 v r3/D (DFF_X1)
+ 0.1278 data arrival time
+
+ 10.0000 10.0000 clock clk (rise edge)
+ 0.0000 10.0000 clock network delay (ideal)
+ -3.0000 7.0000 path margin
+ 0.0000 7.0000 clock reconvergence pessimism
+ 7.0000 ^ r3/CK (DFF_X1)
+ -0.0390 6.9610 library setup time
+ 6.9610 data required time
+-------------------------------------------------------------
+ 6.9610 data required time
+ -0.1278 data arrival time
+-------------------------------------------------------------
+ 6.8332 slack (MET)
+
+
+setup_through_r2 -3.000e-09
diff --git a/test/set_path_margin4.tcl b/test/set_path_margin4.tcl
new file mode 100644
index 00000000..bf1f2e2e
--- /dev/null
+++ b/test/set_path_margin4.tcl
@@ -0,0 +1,29 @@
+# set_path_margin -through
+
+read_liberty ../examples/nangate45_typ.lib.gz
+read_verilog ../examples/example1.v
+link_design top
+create_clock -name clk -period 10 {clk1 clk2 clk3}
+set_input_delay -clock clk 0 {in1 in2}
+
+proc setup_at { args } {
+ report_checks {*}$args -path_delay max -digits 4 -fields {} -group_path_count 1
+}
+proc report_json_path_margin { label path_delay args } {
+ set cmd [concat [list report_checks] $args \
+ [list -path_delay $path_delay -format json -group_path_count 1]]
+ with_output_to_variable json $cmd
+ if { [regexp {"path_margin": ([^,\n]+)} $json match margin] } {
+ puts "$label $margin"
+ } else {
+ puts "$label none"
+ }
+}
+
+set_path_margin -setup 3.0 -through [get_pins u1/Z]
+# Should not see path margin.
+setup_at -from [get_pins r1/CK] -to [get_pins r3/D]
+report_json_path_margin setup_through_r1 max -from [get_pins r1/CK] -to [get_pins r3/D]
+# Should see path margin.
+setup_at -from [get_pins r2/CK] -to [get_pins r3/D]
+report_json_path_margin setup_through_r2 max -from [get_pins r2/CK] -to [get_pins r3/D]
diff --git a/test/set_path_margin5.ok b/test/set_path_margin5.ok
new file mode 100644
index 00000000..0c83dfdd
--- /dev/null
+++ b/test/set_path_margin5.ok
@@ -0,0 +1,61 @@
+Startpoint: r1 (rising edge-triggered flip-flop clocked by clk)
+Endpoint: r3 (rising edge-triggered flip-flop clocked by clk)
+Path Group: clk
+Path Type: max
+
+ Delay Time Description
+-------------------------------------------------------------
+ 0.0000 0.0000 clock clk (rise edge)
+ 0.0000 0.0000 clock network delay (ideal)
+ 0.0000 0.0000 ^ r1/CK (DFF_X1)
+ 0.0836 0.0836 ^ r1/Q (DFF_X1)
+ 0.0277 0.1113 ^ u2/ZN (AND2_X1)
+ 0.0000 0.1113 ^ r3/D (DFF_X1)
+ 0.1113 data arrival time
+
+ 10.0000 10.0000 clock clk (rise edge)
+ 0.0000 10.0000 clock network delay (ideal)
+ -4.0000 6.0000 path margin
+ 0.0000 6.0000 clock reconvergence pessimism
+ 6.0000 ^ r3/CK (DFF_X1)
+ -0.0311 5.9689 library setup time
+ 5.9689 data required time
+-------------------------------------------------------------
+ 5.9689 data required time
+ -0.1113 data arrival time
+-------------------------------------------------------------
+ 5.8576 slack (MET)
+
+
+setup_from_clk_r1 -4.000e-09
+Startpoint: r2 (rising edge-triggered flip-flop clocked by clk)
+Endpoint: r3 (rising edge-triggered flip-flop clocked by clk)
+Path Group: clk
+Path Type: max
+
+ Delay Time Description
+-------------------------------------------------------------
+ 0.0000 0.0000 clock clk (rise edge)
+ 0.0000 0.0000 clock network delay (ideal)
+ 0.0000 0.0000 ^ r2/CK (DFF_X1)
+ 0.0772 0.0772 v r2/Q (DFF_X1)
+ 0.0233 0.1006 v u1/Z (BUF_X1)
+ 0.0273 0.1278 v u2/ZN (AND2_X1)
+ 0.0000 0.1278 v r3/D (DFF_X1)
+ 0.1278 data arrival time
+
+ 10.0000 10.0000 clock clk (rise edge)
+ 0.0000 10.0000 clock network delay (ideal)
+ -4.0000 6.0000 path margin
+ 0.0000 6.0000 clock reconvergence pessimism
+ 6.0000 ^ r3/CK (DFF_X1)
+ -0.0390 5.9610 library setup time
+ 5.9610 data required time
+-------------------------------------------------------------
+ 5.9610 data required time
+ -0.1278 data arrival time
+-------------------------------------------------------------
+ 5.8332 slack (MET)
+
+
+setup_from_clk_r2 -4.000e-09
diff --git a/test/set_path_margin5.tcl b/test/set_path_margin5.tcl
new file mode 100644
index 00000000..797ffa8e
--- /dev/null
+++ b/test/set_path_margin5.tcl
@@ -0,0 +1,28 @@
+# set_path_margin -from clock
+
+read_liberty ../examples/nangate45_typ.lib.gz
+read_verilog ../examples/example1.v
+link_design top
+create_clock -name clk -period 10 {clk1 clk2 clk3}
+set_input_delay -clock clk 0 {in1 in2}
+
+proc setup_at { args } {
+ report_checks {*}$args -path_delay max -digits 4 -fields {} -group_path_count 1
+}
+proc report_json_path_margin { label path_delay args } {
+ set cmd [concat [list report_checks] $args \
+ [list -path_delay $path_delay -format json -group_path_count 1]]
+ with_output_to_variable json $cmd
+ if { [regexp {"path_margin": ([^,\n]+)} $json match margin] } {
+ puts "$label $margin"
+ } else {
+ puts "$label none"
+ }
+}
+
+set_path_margin -setup 4.0 -from [get_clocks clk]
+# Should see path margin on the clock.
+setup_at -from [get_pins r1/CK] -to [get_pins r3/D]
+report_json_path_margin setup_from_clk_r1 max -from [get_pins r1/CK] -to [get_pins r3/D]
+setup_at -from [get_pins r2/CK] -to [get_pins r3/D]
+report_json_path_margin setup_from_clk_r2 max -from [get_pins r2/CK] -to [get_pins r3/D]
diff --git a/test/set_path_margin6.ok b/test/set_path_margin6.ok
new file mode 100644
index 00000000..3f3ce897
--- /dev/null
+++ b/test/set_path_margin6.ok
@@ -0,0 +1,60 @@
+Startpoint: r1 (rising edge-triggered flip-flop clocked by clk)
+Endpoint: r3 (rising edge-triggered flip-flop clocked by clk)
+Path Group: clk
+Path Type: max
+
+ Delay Time Description
+-------------------------------------------------------------
+ 0.0000 0.0000 clock clk (rise edge)
+ 0.0000 0.0000 clock network delay (ideal)
+ 0.0000 0.0000 ^ r1/CK (DFF_X1)
+ 0.0836 0.0836 ^ r1/Q (DFF_X1)
+ 0.0277 0.1113 ^ u2/ZN (AND2_X1)
+ 0.0000 0.1113 ^ r3/D (DFF_X1)
+ 0.1113 data arrival time
+
+ 10.0000 10.0000 clock clk (rise edge)
+ 0.0000 10.0000 clock network delay (ideal)
+ -6.0000 4.0000 path margin
+ 0.0000 4.0000 clock reconvergence pessimism
+ 4.0000 ^ r3/CK (DFF_X1)
+ -0.0311 3.9689 library setup time
+ 3.9689 data required time
+-------------------------------------------------------------
+ 3.9689 data required time
+ -0.1113 data arrival time
+-------------------------------------------------------------
+ 3.8576 slack (MET)
+
+
+setup_combo_r1 -6.000e-09
+Startpoint: r2 (rising edge-triggered flip-flop clocked by clk)
+Endpoint: r3 (rising edge-triggered flip-flop clocked by clk)
+Path Group: clk
+Path Type: max
+
+ Delay Time Description
+-------------------------------------------------------------
+ 0.0000 0.0000 clock clk (rise edge)
+ 0.0000 0.0000 clock network delay (ideal)
+ 0.0000 0.0000 ^ r2/CK (DFF_X1)
+ 0.0772 0.0772 v r2/Q (DFF_X1)
+ 0.0233 0.1006 v u1/Z (BUF_X1)
+ 0.0273 0.1278 v u2/ZN (AND2_X1)
+ 0.0000 0.1278 v r3/D (DFF_X1)
+ 0.1278 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
+ 10.0000 ^ r3/CK (DFF_X1)
+ -0.0390 9.9610 library setup time
+ 9.9610 data required time
+-------------------------------------------------------------
+ 9.9610 data required time
+ -0.1278 data arrival time
+-------------------------------------------------------------
+ 9.8332 slack (MET)
+
+
+setup_combo_r2 none
diff --git a/test/set_path_margin6.tcl b/test/set_path_margin6.tcl
new file mode 100644
index 00000000..dcd33e67
--- /dev/null
+++ b/test/set_path_margin6.tcl
@@ -0,0 +1,31 @@
+# set_path_margin -from/-through/-to
+
+read_liberty ../examples/nangate45_typ.lib.gz
+read_verilog ../examples/example1.v
+link_design top
+create_clock -name clk -period 10 {clk1 clk2 clk3}
+set_input_delay -clock clk 0 {in1 in2}
+
+proc setup_at { args } {
+ report_checks {*}$args -path_delay max -digits 4 -fields {} -group_path_count 1
+}
+proc report_json_path_margin { label path_delay args } {
+ set cmd [concat [list report_checks] $args \
+ [list -path_delay $path_delay -format json -group_path_count 1]]
+ with_output_to_variable json $cmd
+ if { [regexp {"path_margin": ([^,\n]+)} $json match margin] } {
+ puts "$label $margin"
+ } else {
+ puts "$label none"
+ }
+}
+
+set_path_margin -setup 6.0 -from [get_pins r1/CK] \
+ -through [get_pins u2/A1] \
+ -to [get_pins r3/D]
+# Should see path margin.
+setup_at -from [get_pins r1/CK] -to [get_pins r3/D]
+report_json_path_margin setup_combo_r1 max -from [get_pins r1/CK] -to [get_pins r3/D]
+# Should not see path margin.
+setup_at -from [get_pins r2/CK] -to [get_pins r3/D]
+report_json_path_margin setup_combo_r2 max -from [get_pins r2/CK] -to [get_pins r3/D]