// 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.
#include "CheckSlews.hh"
#include "Fuzzy.hh"
#include "Liberty.hh"
#include "Network.hh"
#include "Sdc.hh"
#include "Mode.hh"
#include "InputDrive.hh"
#include "Graph.hh"
#include "GraphDelayCalc.hh"
#include "StaState.hh"
#include "Scene.hh"
#include "Path.hh"
#include "PortDirection.hh"
#include "Sim.hh"
#include "Search.hh"
#include "ClkNetwork.hh"
namespace sta {
CheckSlews::CheckSlews(const StaState *sta) :
heap_(0, SlewCheckSlackLess(sta)),
sta_(sta)
{
}
void
CheckSlews::clear()
{
checks_.clear();
heap_.clear();
}
SlewCheckSeq &
CheckSlews::check(const Net *net,
size_t max_count,
bool violators,
const SceneSeq &scenes,
const MinMax *min_max)
{
clear();
if (!violators)
heap_.setMaxSize(max_count);
if (net)
checkNet(net, violators, scenes, min_max);
else
checkAll(violators, scenes, min_max);
if (violators)
sort(checks_, SlewCheckSlackLess(sta_));
else
checks_ = heap_.extract();
return checks_;
}
void
CheckSlews::checkNet(const Net *net,
bool violators,
const SceneSeq &scenes,
const MinMax *min_max)
{
const Network *network = sta_->network();
NetPinIterator *pin_iter = network->pinIterator(net);
while (pin_iter->hasNext()) {
const Pin *pin = pin_iter->next();
checkPin(pin, violators, scenes, min_max);
}
delete pin_iter;
}
void
CheckSlews::checkAll(bool violators,
const SceneSeq &scenes,
const MinMax *min_max)
{
const Network *network = sta_->network();
LeafInstanceIterator *inst_iter = network->leafInstanceIterator();
while (inst_iter->hasNext()) {
const Instance *inst = inst_iter->next();
checkInst(inst, violators, scenes, min_max);
}
delete inst_iter;
// Check top level ports.
checkInst(network->topInstance(), violators, scenes, min_max);
}
void
CheckSlews::checkInst(const Instance *inst,
bool violators,
const SceneSeq &scenes,
const MinMax *min_max)
{
const Network *network = sta_->network();
InstancePinIterator *pin_iter = network->pinIterator(inst);
while (pin_iter->hasNext()) {
Pin *pin = pin_iter->next();
checkPin(pin, violators, scenes, min_max);
}
delete pin_iter;
}
void
CheckSlews::checkPin(const Pin *pin,
bool violators,
const SceneSeq &scenes,
const MinMax *min_max)
{
const Scene *scene;
const RiseFall *rf;
Slew slew;
float limit, slack;
check(pin, scenes, min_max, true, slew, limit, slack, rf, scene);
if (scene) {
if (violators) {
if (slack < 0.0)
checks_.emplace_back(pin, rf, slew, limit, slack, scene);
}
else
heap_.insert(SlewCheck(pin, rf, slew, limit, slack, scene));
}
}
void
CheckSlews::check(const Pin *pin,
const SceneSeq &scenes,
const MinMax *min_max,
bool check_clks,
// Return values.
Slew &slew,
float &limit,
float &slack,
const RiseFall *&rf,
const Scene *&scene) const
{
scene = nullptr;
rf = nullptr;
slew = 0.0;
limit = 0.0;
slack = MinMax::min()->initValue();
for (const Scene *scene1 : scenes) {
Vertex *vertex, *bidirect_drvr_vertex;
sta_->graph()->pinVertices(pin, vertex, bidirect_drvr_vertex);
if (vertex)
check2(vertex, scene1, min_max, check_clks,
scene, rf, slew, limit, slack);
if (bidirect_drvr_vertex)
check2(bidirect_drvr_vertex, scene1, min_max, check_clks,
scene, rf, slew, limit, slack);
}
}
void
CheckSlews::check2(const Vertex *vertex,
const Scene *scene,
const MinMax *min_max,
bool check_clks,
// Return values.
const Scene *&scene1,
const RiseFall *&rf1,
Slew &slew1,
float &limit1,
float &slack1) const
{
const Mode *mode = scene->mode();
const Sdc *sdc = mode->sdc();
const ClkNetwork *clk_network = mode->clkNetwork();
const Pin *pin = vertex->pin();
if (!sdc->isDisabledConstraint(pin)
&& !clk_network->isIdealClock(pin)) {
ConstClockSet clks;
if (check_clks)
clks = clockDomains(vertex, scene);
for (const RiseFall *rf : RiseFall::range()) {
float limit;
bool exists;
findLimit(pin, scene, rf, min_max, clks,
limit, exists);
if (exists) {
check3(vertex, scene, rf, min_max, limit,
scene1, rf1, slew1, slack1, limit1);
}
}
}
}
void
CheckSlews::check3(const Vertex *vertex,
const Scene *scene,
const RiseFall *rf,
const MinMax *min_max,
float limit,
// Return values.
const Scene *&scene1,
const RiseFall *&rf1,
Slew &slew1,
float &slack1,
float &limit1) const
{
DcalcAPIndex ap_index = scene->dcalcAnalysisPtIndex(min_max);
Slew slew = sta_->graph()->slew(vertex, rf, ap_index);
float slew2 = delayAsFloat(slew);
float slack = (min_max == MinMax::max())
? limit - slew2 : slew2 - limit;
if (scene1 == nullptr
|| (slack < slack1
// Break ties for the sake of regression stability.
|| (fuzzyEqual(slack, slack1)
&& rf->index() < rf1->index()))) {
scene1 = scene;
rf1 = rf;
slew1 = slew;
slack1 = slack;
limit1 = limit;
}
}
// Return the tightest limit.
void
CheckSlews::findLimit(const Pin *pin,
const Scene *scene,
const RiseFall *rf,
const MinMax *min_max,
const ConstClockSet &clks,
// Return values.
float &limit,
bool &exists) const
{
const Network *network = sta_->network();
const Sdc *sdc = scene->sdc();
LibertyPort *port = network->libertyPort(pin);
findLimit(port, scene, min_max,
limit, exists);
float limit1;
bool exists1;
if (!clks.empty()) {
// Look for clock slew limits.
const ClkNetwork *clk_network = scene->mode()->clkNetwork();
bool is_clk = clk_network->isIdealClock(pin);
for (const Clock *clk : clks) {
PathClkOrData clk_data = is_clk ? PathClkOrData::clk : PathClkOrData::data;
sdc->slewLimit(clk, rf, clk_data, min_max,
limit1, exists1);
if (exists1
&& (!exists
|| min_max->compare(limit, limit1))) {
limit = limit1;
exists = true;
}
}
}
if (network->isTopLevelPort(pin)) {
Port *port = network->port(pin);
sdc->slewLimit(port, min_max, limit1, exists1);
if (exists1
&& (!exists
|| min_max->compare(limit, limit1))) {
limit = limit1;
exists = true;
}
InputDrive *drive = sdc->findInputDrive(port);
if (drive) {
for (auto rf : RiseFall::range()) {
const LibertyCell *cell;
const LibertyPort *from_port;
float *from_slews;
const LibertyPort *to_port;
drive->driveCell(rf, min_max, cell, from_port, from_slews, to_port);
if (to_port) {
const LibertyPort *scene_port = to_port->scenePort(scene, min_max);
scene_port->slewLimit(min_max, limit1, exists1);
if (!exists1
&& scene_port->direction()->isAnyOutput()
&& min_max == MinMax::max())
scene_port->libertyLibrary()->defaultMaxSlew(limit1, exists1);
if (exists1
&& (!exists
|| min_max->compare(limit, limit1))) {
limit = limit1;
exists = true;
}
}
}
}
}
}
void
CheckSlews::findLimit(const LibertyPort *port,
const Scene *scene,
const MinMax *min_max,
// Return values.
float &limit,
bool &exists) const
{
limit = INF;
exists = false;
const Network *network = sta_->network();
const Sdc *sdc = scene->sdc();
float limit1;
bool exists1;
// Default to top ("design") limit.
Cell *top_cell = network->cell(network->topInstance());
sdc->slewLimit(top_cell, min_max,
limit1, exists1);
if (exists1) {
limit = limit1;
exists = true;
}
if (port) {
const LibertyPort *scene_port = port->scenePort(scene, min_max);
scene_port->slewLimit(min_max, limit1, exists1);
if (!exists1
// default_max_transition only applies to outputs.
&& scene_port->direction()->isAnyOutput()
&& min_max == MinMax::max())
scene_port->libertyLibrary()->defaultMaxSlew(limit1, exists1);
if (exists1
&& (!exists
|| min_max->compare(limit, limit1))) {
limit = limit1;
exists = true;
}
}
}
ConstClockSet
CheckSlews::clockDomains(const Vertex *vertex,
const Scene *scene) const
{
ConstClockSet clks;
VertexPathIterator path_iter(const_cast(vertex), sta_);
while (path_iter.hasNext()) {
Path *path = path_iter.next();
if (path->scene(sta_) == scene) {
const Clock *clk = path->clock(sta_);
if (clk)
clks.insert(clk);
}
}
return clks;
}
////////////////////////////////////////////////////////////////
SlewCheck::SlewCheck() :
pin_(nullptr),
rf_(nullptr),
slew_(0.0),
limit_(0.0),
slack_(0.0),
scene_(nullptr)
{
}
SlewCheck::SlewCheck(const Pin *pin,
const RiseFall *rf,
Slew &slew,
float limit,
float slack,
const Scene *scene) :
pin_(pin),
rf_(rf),
slew_(slew),
limit_(limit),
slack_(slack),
scene_(scene)
{
}
////////////////////////////////////////////////////////////////
SlewCheckSlackLess::SlewCheckSlackLess(const StaState *sta) :
sta_(sta)
{
}
bool
SlewCheckSlackLess::operator()(const SlewCheck &check1,
const SlewCheck &check2) const
{
float slack1 = check1.slack();
float slack2 = check2.slack();
return fuzzyLess(slack1, slack2)
|| (fuzzyEqual(slack1, slack2)
// Break ties for the sake of regression stability.
&& sta_->network()->pinLess(check1.pin(), check2.pin()));
}
} // namespace