OpenSTA/search/Search.cc

4200 lines
119 KiB
C++

// OpenSTA, Static Timing Analyzer
// Copyright (c) 2025, 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 <https://www.gnu.org/licenses/>.
//
// 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 "Search.hh"
#include <algorithm>
#include <cmath> // abs
#include "Mutex.hh"
#include "Report.hh"
#include "Debug.hh"
#include "Stats.hh"
#include "Fuzzy.hh"
#include "TimingRole.hh"
#include "FuncExpr.hh"
#include "TimingArc.hh"
#include "Sequential.hh"
#include "Units.hh"
#include "Liberty.hh"
#include "Network.hh"
#include "PortDirection.hh"
#include "Graph.hh"
#include "GraphCmp.hh"
#include "PortDelay.hh"
#include "Clock.hh"
#include "CycleAccting.hh"
#include "ExceptionPath.hh"
#include "DataCheck.hh"
#include "Sdc.hh"
#include "DcalcAnalysisPt.hh"
#include "SearchPred.hh"
#include "Levelize.hh"
#include "Bfs.hh"
#include "Corner.hh"
#include "Sim.hh"
#include "Path.hh"
#include "ClkInfo.hh"
#include "Tag.hh"
#include "TagGroup.hh"
#include "PathEnd.hh"
#include "PathGroup.hh"
#include "PathAnalysisPt.hh"
#include "VisitPathEnds.hh"
#include "GatedClk.hh"
#include "WorstSlack.hh"
#include "Latches.hh"
#include "Crpr.hh"
#include "Genclks.hh"
#include "Variables.hh"
namespace sta {
using std::min;
using std::max;
using std::abs;
////////////////////////////////////////////////////////////////
EvalPred::EvalPred(const StaState *sta) :
SearchPred0(sta),
search_thru_latches_(true)
{
}
void
EvalPred::setSearchThruLatches(bool thru_latches)
{
search_thru_latches_ = thru_latches;
}
bool
EvalPred::searchThru(Edge *edge)
{
const TimingRole *role = edge->role();
return SearchPred0::searchThru(edge)
&& (sta_->variables()->dynamicLoopBreaking()
|| !edge->isDisabledLoop())
&& !role->isTimingCheck()
&& (search_thru_latches_
|| role != TimingRole::latchDtoQ()
|| sta_->latches()->latchDtoQState(edge) == LatchEnableState::open);
}
bool
EvalPred::searchTo(const Vertex *to_vertex)
{
const Sdc *sdc = sta_->sdc();
const Pin *pin = to_vertex->pin();
return SearchPred0::searchTo(to_vertex)
&& !(sdc->isLeafPinClock(pin)
&& !sdc->isPathDelayInternalTo(pin));
}
////////////////////////////////////////////////////////////////
DynLoopSrchPred::DynLoopSrchPred(TagGroupBldr *tag_bldr) :
tag_bldr_(tag_bldr)
{
}
bool
DynLoopSrchPred::loopEnabled(Edge *edge,
bool dynamic_loop_breaking_enabled,
const Graph *graph,
Search *search)
{
return !edge->isDisabledLoop()
|| (dynamic_loop_breaking_enabled
&& hasPendingLoopPaths(edge, graph, search));
}
bool
DynLoopSrchPred::hasPendingLoopPaths(Edge *edge,
const Graph *graph,
Search *search)
{
if (tag_bldr_
&& tag_bldr_->hasLoopTag()) {
Corners *corners = search->corners();
Vertex *from_vertex = edge->from(graph);
TagGroup *prev_tag_group = search->tagGroup(from_vertex);
for (auto const [from_tag, path_index] : tag_bldr_->pathIndexMap()) {
if (from_tag->isLoop()) {
// Loop false path exceptions apply to rise/fall edges so to_rf
// does not matter.
PathAPIndex path_ap_index = from_tag->pathAPIndex();
PathAnalysisPt *path_ap = corners->findPathAnalysisPt(path_ap_index);
Tag *to_tag = search->thruTag(from_tag, edge, RiseFall::rise(),
path_ap->pathMinMax(), path_ap, nullptr);
if (to_tag
&& (prev_tag_group == nullptr
|| !prev_tag_group->hasTag(from_tag)))
return true;
}
}
}
return false;
}
// EvalPred unless
// latch D->Q edge
class SearchThru : public EvalPred, public DynLoopSrchPred
{
public:
SearchThru(TagGroupBldr *tag_bldr,
const StaState *sta);
virtual bool searchThru(Edge *edge);
};
SearchThru::SearchThru(TagGroupBldr *tag_bldr,
const StaState *sta) :
EvalPred(sta),
DynLoopSrchPred(tag_bldr)
{
}
bool
SearchThru::searchThru(Edge *edge)
{
const Graph *graph = sta_->graph();
Search *search = sta_->search();
return EvalPred::searchThru(edge)
// Only search thru latch D->Q if it is always open.
// Enqueue thru latches is handled explicitly by search.
&& (edge->role() != TimingRole::latchDtoQ()
|| sta_->latches()->latchDtoQState(edge) == LatchEnableState::open)
&& loopEnabled(edge, sta_->variables()->dynamicLoopBreaking(),
graph, search);
}
ClkArrivalSearchPred::ClkArrivalSearchPred(const StaState *sta) :
EvalPred(sta)
{
}
bool
ClkArrivalSearchPred::searchThru(Edge *edge)
{
const TimingRole *role = edge->role();
return (role->isWire()
|| role == TimingRole::combinational())
&& EvalPred::searchThru(edge);
}
////////////////////////////////////////////////////////////////
Search::Search(StaState *sta) :
StaState(sta)
{
init(sta);
}
void
Search::init(StaState *sta)
{
initVars();
search_adj_ = new SearchThru(nullptr, sta);
eval_pred_ = new EvalPred(sta);
check_crpr_ = new CheckCrpr(sta);
genclks_ = new Genclks(sta);
arrival_visitor_ = new ArrivalVisitor(sta);
clk_arrivals_valid_ = false;
arrivals_exist_ = false;
arrivals_at_endpoints_exist_ = false;
arrivals_seeded_ = false;
requireds_exist_ = false;
requireds_seeded_ = false;
invalid_arrivals_ = new VertexSet(graph_);
invalid_requireds_ = new VertexSet(graph_);
invalid_tns_ = new VertexSet(graph_);
tns_exists_ = false;
worst_slacks_ = nullptr;
arrival_iter_ = new BfsFwdIterator(BfsIndex::arrival, nullptr, sta);
required_iter_ = new BfsBkwdIterator(BfsIndex::required, search_adj_, sta);
tag_capacity_ = 128;
tag_set_ = new TagSet(tag_capacity_, TagHash(sta), TagEqual(sta));
clk_info_set_ = new ClkInfoSet(ClkInfoLess(sta));
tag_next_ = 0;
tags_ = new Tag*[tag_capacity_];
tag_group_capacity_ = tag_capacity_;
tag_groups_ = new TagGroup*[tag_group_capacity_];
tag_group_next_ = 0;
tag_group_set_ = new TagGroupSet(tag_group_capacity_);
pending_latch_outputs_ = new VertexSet(graph_);
visit_path_ends_ = new VisitPathEnds(this);
gated_clk_ = new GatedClk(this);
path_groups_ = nullptr;
endpoints_ = nullptr;
invalid_endpoints_ = nullptr;
filter_ = nullptr;
filter_from_ = nullptr;
filter_to_ = nullptr;
filtered_arrivals_ = new VertexSet(graph_);
found_downstream_clk_pins_ = false;
}
// Init "options".
void
Search::initVars()
{
unconstrained_paths_ = false;
crpr_path_pruning_enabled_ = true;
crpr_approx_missing_requireds_ = true;
}
Search::~Search()
{
deletePathGroups();
deletePaths();
deleteTags();
delete tag_set_;
delete clk_info_set_;
delete [] tags_;
delete [] tag_groups_;
delete tag_group_set_;
delete search_adj_;
delete eval_pred_;
delete arrival_visitor_;
delete arrival_iter_;
delete required_iter_;
delete endpoints_;
delete invalid_arrivals_;
delete invalid_requireds_;
delete invalid_tns_;
delete invalid_endpoints_;
delete pending_latch_outputs_;
delete visit_path_ends_;
delete gated_clk_;
delete worst_slacks_;
delete check_crpr_;
delete genclks_;
delete filtered_arrivals_;
deleteFilter();
}
void
Search::clear()
{
initVars();
clk_arrivals_valid_ = false;
arrivals_at_endpoints_exist_ = false;
arrivals_seeded_ = false;
requireds_exist_ = false;
requireds_seeded_ = false;
tns_exists_ = false;
clearWorstSlack();
invalid_arrivals_->clear();
arrival_iter_->clear();
invalid_requireds_->clear();
invalid_tns_->clear();
required_iter_->clear();
endpointsInvalid();
deletePathGroups();
deletePaths();
deleteTags();
clearPendingLatchOutputs();
deleteFilter();
genclks_->clear();
found_downstream_clk_pins_ = false;
}
bool
Search::crprPathPruningEnabled() const
{
return crpr_path_pruning_enabled_;
}
void
Search::setCrprpathPruningEnabled(bool enabled)
{
crpr_path_pruning_enabled_ = enabled;
}
bool
Search::crprApproxMissingRequireds() const
{
return crpr_approx_missing_requireds_;
}
void
Search::setCrprApproxMissingRequireds(bool enabled)
{
crpr_approx_missing_requireds_ = enabled;
}
void
Search::deleteTags()
{
for (TagGroupIndex i = 0; i < tag_group_next_; i++) {
TagGroup *group = tag_groups_[i];
delete group;
}
tag_group_next_ = 0;
tag_group_set_->clear();
tag_group_free_indices_.clear();
tag_next_ = 0;
tag_set_->deleteContentsClear();
tag_free_indices_.clear();
clk_info_set_->deleteContentsClear();
deleteTagsPrev();
}
void
Search::deleteFilter()
{
if (filter_) {
sdc_->deleteException(filter_);
filter_ = nullptr;
filter_from_ = nullptr;
}
else {
// Filter owns filter_from_ if it exists.
delete filter_from_;
filter_from_ = nullptr;
}
delete filter_to_;
filter_to_ = nullptr;
}
void
Search::copyState(const StaState *sta)
{
StaState::copyState(sta);
// Notify sub-components.
arrival_iter_->copyState(sta);
required_iter_->copyState(sta);
arrival_visitor_->copyState(sta);
visit_path_ends_->copyState(sta);
gated_clk_->copyState(sta);
check_crpr_->copyState(sta);
genclks_->copyState(sta);
}
////////////////////////////////////////////////////////////////
void
Search::deletePaths()
{
debugPrint(debug_, "search", 1, "delete paths");
if (arrivals_exist_) {
VertexIterator vertex_iter(graph_);
while (vertex_iter.hasNext()) {
Vertex *vertex = vertex_iter.next();
deletePaths(vertex);
}
filtered_arrivals_->clear();
arrivals_exist_ = false;
}
}
// Delete with incremental tns/wns update.
void
Search::deletePathsIncr(Vertex *vertex)
{
tnsNotifyBefore(vertex);
if (worst_slacks_)
worst_slacks_->worstSlackNotifyBefore(vertex);
deletePaths(vertex);
}
void
Search::deletePaths(Vertex *vertex)
{
debugPrint(debug_, "search", 4, "delete paths %s",
vertex->name(network_));
TagGroup *tag_group = tagGroup(vertex);
if (tag_group) {
graph_->deletePaths(vertex);
tag_group->decrRefCount();
}
}
////////////////////////////////////////////////////////////////
// from/thrus/to are owned and deleted by Search.
// Returned sequence is owned by the caller.
// PathEnds are owned by Search PathGroups and deleted on next call.
PathEndSeq
Search::findPathEnds(ExceptionFrom *from,
ExceptionThruSeq *thrus,
ExceptionTo *to,
bool unconstrained,
const Corner *corner,
const MinMaxAll *min_max,
size_t group_path_count,
size_t endpoint_path_count,
bool unique_pins,
bool unique_edges,
float slack_min,
float slack_max,
bool sort_by_slack,
PathGroupNameSet *group_names,
bool setup,
bool hold,
bool recovery,
bool removal,
bool clk_gating_setup,
bool clk_gating_hold)
{
findFilteredArrivals(from, thrus, to, unconstrained, true);
if (!variables_->recoveryRemovalChecksEnabled())
recovery = removal = false;
if (!variables_->gatedClkChecksEnabled())
clk_gating_setup = clk_gating_hold = false;
makePathGroups(group_path_count, endpoint_path_count,
unique_pins, unique_edges,
slack_min, slack_max,
group_names, setup, hold,
recovery, removal,
clk_gating_setup, clk_gating_hold);
ensureDownstreamClkPins();
PathEndSeq path_ends = path_groups_->makePathEnds(to, unconstrained_paths_,
corner, min_max,
sort_by_slack);
sdc_->reportClkToClkMaxCycleWarnings();
return path_ends;
}
void
Search::findFilteredArrivals(ExceptionFrom *from,
ExceptionThruSeq *thrus,
ExceptionTo *to,
bool unconstrained,
bool thru_latches)
{
unconstrained_paths_ = unconstrained;
checkFromThrusTo(from, thrus, to);
filter_from_ = from;
filter_to_ = to;
if ((from
&& (from->pins()
|| from->instances()))
|| thrus) {
filter_ = sdc_->makeFilterPath(from, thrus, nullptr);
findFilteredArrivals(thru_latches);
}
else
// These cases do not require filtered arrivals.
// -from clocks
// -to
findAllArrivals(thru_latches);
}
void
Search::makePathGroups(int group_path_count,
int endpoint_path_count,
bool unique_pins,
bool unique_edges,
float slack_min,
float slack_max,
PathGroupNameSet *group_names,
bool setup,
bool hold,
bool recovery,
bool removal,
bool clk_gating_setup,
bool clk_gating_hold)
{
path_groups_ = new PathGroups(group_path_count, endpoint_path_count,
unique_pins, unique_edges,
slack_min, slack_max,
group_names,
setup, hold,
recovery, removal,
clk_gating_setup, clk_gating_hold,
unconstrained_paths_,
this);
}
// From/thrus/to are used to make a filter exception. If the last
// search used a filter arrival/required times were only found for a
// subset of the paths. Delete the paths that have a filter
// exception state.
void
Search::deleteFilteredArrivals()
{
if (filter_) {
ExceptionFrom *from = filter_->from();
ExceptionThruSeq *thrus = filter_->thrus();
if ((from
&& (from->pins()
|| from->instances()))
|| thrus) {
for (Vertex *vertex : *filtered_arrivals_) {
if (isClock(vertex))
clk_arrivals_valid_ = false;
deletePathsIncr(vertex);
arrivalInvalid(vertex);
requiredInvalid(vertex);
}
bool check_filter_arrivals = false;
if (check_filter_arrivals) {
VertexIterator vertex_iter(graph_);
while (vertex_iter.hasNext()) {
Vertex *vertex = vertex_iter.next();
TagGroup *tag_group = tagGroup(vertex);
if (tag_group
&& tag_group->hasFilterTag())
filtered_arrivals_->erase(vertex);
}
if (!filtered_arrivals_->empty()) {
report_->reportLine("Filtered verticies mismatch");
for (Vertex *vertex : *filtered_arrivals_)
report_->reportLine(" %s", vertex->to_string(this).c_str());
}
}
filtered_arrivals_->clear();
deleteFilterTagGroups();
deleteFilterTags();
deleteFilterClkInfos();
}
deleteFilter();
}
}
void
Search::deleteFilterTagGroups()
{
for (TagGroupIndex i = 0; i < tag_group_next_; i++) {
TagGroup *group = tag_groups_[i];
if (group
&& group->hasFilterTag())
deleteTagGroup(group);
}
}
void
Search::deleteTagGroup(TagGroup *group)
{
tag_group_set_->erase(group);
tag_groups_[group->index()] = nullptr;
tag_group_free_indices_.push_back(group->index());
delete group;
}
void
Search::deleteFilterTags()
{
for (TagIndex i = 0; i < tag_next_; i++) {
Tag *tag = tags_[i];
if (tag
&& (tag->isFilter()
|| tag->clkInfo()->crprPathRefsFilter())) {
tags_[i] = nullptr;
tag_set_->erase(tag);
delete tag;
tag_free_indices_.push_back(i);
}
}
}
void
Search::deleteFilterClkInfos()
{
for (auto itr = clk_info_set_->cbegin(); itr != clk_info_set_->cend(); ) {
const ClkInfo *clk_info = *itr;
if (clk_info->crprPathRefsFilter()) {
itr = clk_info_set_->erase(itr);
delete clk_info;
}
else
itr++;
}
}
void
Search::findFilteredArrivals(bool thru_latches)
{
filtered_arrivals_->clear();
findArrivalsSeed();
seedFilterStarts();
Level max_level = levelize_->maxLevel();
// Search always_to_endpoint to search from exisiting arrivals at
// fanin startpoints to reach -thru/-to endpoints.
arrival_visitor_->init(true);
// Iterate until data arrivals at all latches stop changing.
for (int pass = 1; pass == 1 || (thru_latches && havePendingLatchOutputs()) ; pass++) {
if (thru_latches)
enqueuePendingLatchOutputs();
debugPrint(debug_, "search", 1, "find arrivals pass %d", pass);
int arrival_count = arrival_iter_->visitParallel(max_level, arrival_visitor_);
deleteTagsPrev();
debugPrint(debug_, "search", 1, "found %d arrivals", arrival_count);
}
arrivals_exist_ = true;
}
// Delete stale tag arrarys.
void
Search::deleteTagsPrev()
{
for (Tag** tags: tags_prev_)
delete [] tags;
tags_prev_.clear();
for (TagGroup** tag_groups: tag_groups_prev_)
delete [] tag_groups;
tag_groups_prev_.clear();
}
void
Search::deleteUnusedTagGroups()
{
for (TagGroupIndex i = 0; i < tag_group_next_; i++) {
TagGroup *group = tag_groups_[i];
if (group && group->refCount() == 0)
deleteTagGroup(group);
}
}
VertexSeq
Search::filteredEndpoints()
{
VertexSeq ends;
for (Vertex *vertex : *filtered_arrivals_) {
if (isEndpoint(vertex))
ends.push_back(vertex);
}
return ends;
}
class SeedFaninsThruHierPin : public HierPinThruVisitor
{
public:
SeedFaninsThruHierPin(Graph *graph,
Search *search);
protected:
virtual void visit(const Pin *drvr,
const Pin *load);
Graph *graph_;
Search *search_;
};
SeedFaninsThruHierPin::SeedFaninsThruHierPin(Graph *graph,
Search *search) :
HierPinThruVisitor(),
graph_(graph),
search_(search)
{
}
void
SeedFaninsThruHierPin::visit(const Pin *drvr,
const Pin *)
{
Vertex *vertex, *bidirect_drvr_vertex;
graph_->pinVertices(drvr, vertex, bidirect_drvr_vertex);
search_->seedArrival(vertex);
if (bidirect_drvr_vertex)
search_->seedArrival(bidirect_drvr_vertex);
}
void
Search::seedFilterStarts()
{
ExceptionPt *first_pt = filter_->firstPt();
if (first_pt) {
PinSet first_pins = first_pt->allPins(network_);
for (const Pin *pin : first_pins) {
if (network_->isHierarchical(pin)) {
SeedFaninsThruHierPin visitor(graph_, this);
visitDrvrLoadsThruHierPin(pin, network_, &visitor);
}
else {
Vertex *vertex, *bidirect_drvr_vertex;
graph_->pinVertices(pin, vertex, bidirect_drvr_vertex);
if (vertex)
seedArrival(vertex);
if (bidirect_drvr_vertex)
seedArrival(bidirect_drvr_vertex);
}
}
}
}
////////////////////////////////////////////////////////////////
void
Search::deleteVertexBefore(Vertex *vertex)
{
if (arrivals_exist_) {
deletePathsIncr(vertex);
arrival_iter_->deleteVertexBefore(vertex);
invalid_arrivals_->erase(vertex);
filtered_arrivals_->erase(vertex);
}
if (requireds_exist_) {
required_iter_->deleteVertexBefore(vertex);
invalid_requireds_->erase(vertex);
invalid_tns_->erase(vertex);
}
if (endpoints_)
endpoints_->erase(vertex);
if (invalid_endpoints_)
invalid_endpoints_->erase(vertex);
}
void
Search::deleteEdgeBefore(Edge *edge)
{
Vertex *from = edge->from(graph_);
Vertex *to = edge->to(graph_);
arrivalInvalid(to);
requiredInvalid(from);
VertexPathIterator path_iter(to, graph_);
while (path_iter.hasNext()) {
Path *path = path_iter.next();
path->clearPrevPath(this);
}
}
bool
Search::arrivalsValid()
{
return arrivals_exist_
&& invalid_arrivals_->empty();
}
void
Search::arrivalsInvalid()
{
if (arrivals_exist_) {
debugPrint(debug_, "search", 1, "arrivals invalid");
// Delete paths to make sure no state is left over.
// For example, set_disable_timing strands a vertex, which means
// the search won't revisit it to clear the previous arrival.
deletePathGroups();
deletePaths();
deleteTags();
genclks_->clear();
deleteFilter();
arrivals_at_endpoints_exist_ = false;
arrivals_seeded_ = false;
requireds_exist_ = false;
requireds_seeded_ = false;
clk_arrivals_valid_ = false;
arrival_iter_->clear();
required_iter_->clear();
// No need to keep track of incremental updates any more.
invalid_arrivals_->clear();
invalid_requireds_->clear();
tns_exists_ = false;
clearWorstSlack();
invalid_tns_->clear();
}
}
void
Search::requiredsInvalid()
{
debugPrint(debug_, "search", 1, "requireds invalid");
requireds_exist_ = false;
requireds_seeded_ = false;
invalid_requireds_->clear();
tns_exists_ = false;
clearWorstSlack();
invalid_tns_->clear();
}
void
Search::arrivalInvalid(Vertex *vertex)
{
if (arrivals_exist_) {
debugPrint(debug_, "search", 2, "arrival invalid %s",
vertex->to_string(this).c_str());
if (!arrival_iter_->inQueue(vertex)) {
// Lock for StaDelayCalcObserver called by delay calc threads.
LockGuard lock(invalid_arrivals_lock_);
invalid_arrivals_->insert(vertex);
}
tnsInvalid(vertex);
}
}
// Move any pending arrival/requireds to invalid before relevelization.
void
Search::levelsChangedBefore()
{
if (arrivals_exist_) {
while (arrival_iter_->hasNext()) {
Vertex *vertex = arrival_iter_->next();
arrivalInvalid(vertex);
}
while (required_iter_->hasNext()) {
Vertex *vertex = required_iter_->next();
requiredInvalid(vertex);
}
}
}
void
Search::levelChangedBefore(Vertex *vertex)
{
if (arrivals_exist_) {
arrival_iter_->remove(vertex);
required_iter_->remove(vertex);
arrivalInvalid(vertex);
requiredInvalid(vertex);
}
}
void
Search::arrivalInvalid(const Pin *pin)
{
if (graph_) {
Vertex *vertex, *bidirect_drvr_vertex;
graph_->pinVertices(pin, vertex, bidirect_drvr_vertex);
arrivalInvalid(vertex);
if (bidirect_drvr_vertex)
arrivalInvalid(bidirect_drvr_vertex);
}
}
void
Search::requiredInvalid(const Instance *inst)
{
if (graph_) {
InstancePinIterator *pin_iter = network_->pinIterator(inst);
while (pin_iter->hasNext()) {
Pin *pin = pin_iter->next();
requiredInvalid(pin);
}
delete pin_iter;
}
}
void
Search::requiredInvalid(const Pin *pin)
{
if (graph_) {
Vertex *vertex, *bidirect_drvr_vertex;
graph_->pinVertices(pin, vertex, bidirect_drvr_vertex);
requiredInvalid(vertex);
if (bidirect_drvr_vertex)
requiredInvalid(bidirect_drvr_vertex);
}
}
void
Search::requiredInvalid(Vertex *vertex)
{
if (requireds_exist_) {
debugPrint(debug_, "search", 2, "required invalid %s",
vertex->to_string(this).c_str());
if (!required_iter_->inQueue(vertex)) {
// Lock for StaDelayCalcObserver called by delay calc threads.
LockGuard lock(invalid_arrivals_lock_);
invalid_requireds_->insert(vertex);
}
tnsInvalid(vertex);
}
}
////////////////////////////////////////////////////////////////
void
Search::findClkArrivals()
{
if (!clk_arrivals_valid_) {
genclks_->ensureInsertionDelays();
Stats stats(debug_, report_);
debugPrint(debug_, "search", 1, "find clk arrivals");
arrival_iter_->clear();
seedClkVertexArrivals();
ClkArrivalSearchPred search_clk(this);
arrival_visitor_->init(false, &search_clk);
arrival_iter_->visitParallel(levelize_->maxLevel(), arrival_visitor_);
deleteTagsPrev();
arrivals_exist_ = true;
stats.report("Find clk arrivals");
}
clk_arrivals_valid_ = true;
}
void
Search::seedClkVertexArrivals()
{
PinSet clk_pins(network_);
findClkVertexPins(clk_pins);
for (const Pin *pin : clk_pins) {
Vertex *vertex, *bidirect_drvr_vertex;
graph_->pinVertices(pin, vertex, bidirect_drvr_vertex);
seedClkVertexArrivals(pin, vertex);
if (bidirect_drvr_vertex)
seedClkVertexArrivals(pin, bidirect_drvr_vertex);
}
}
void
Search::seedClkVertexArrivals(const Pin *pin,
Vertex *vertex)
{
TagGroupBldr tag_bldr(true, this);
tag_bldr.init(vertex);
genclks_->copyGenClkSrcPaths(vertex, &tag_bldr);
seedClkArrivals(pin, vertex, &tag_bldr);
setVertexArrivals(vertex, &tag_bldr);
}
Arrival
Search::clockInsertion(const Clock *clk,
const Pin *pin,
const RiseFall *rf,
const MinMax *min_max,
const EarlyLate *early_late,
const PathAnalysisPt *path_ap) const
{
float insert;
bool exists;
sdc_->clockInsertion(clk, pin, rf, min_max, early_late, insert, exists);
if (exists)
return insert;
else if (clk->isGeneratedWithPropagatedMaster())
return genclks_->insertionDelay(clk, pin, rf, early_late, path_ap);
else
return 0.0;
}
////////////////////////////////////////////////////////////////
void
Search::visitStartpoints(VertexVisitor *visitor)
{
Instance *top_inst = network_->topInstance();
InstancePinIterator *pin_iter = network_->pinIterator(top_inst);
while (pin_iter->hasNext()) {
Pin *pin = pin_iter->next();
if (network_->direction(pin)->isAnyInput()) {
Vertex *vertex = graph_->pinDrvrVertex(pin);
visitor->visit(vertex);
}
}
delete pin_iter;
for (const auto [pin, input_delays] : sdc_->inputDelayPinMap()) {
// Already hit these.
if (!network_->isTopLevelPort(pin)) {
Vertex *vertex = graph_->pinDrvrVertex(pin);
if (vertex)
visitor->visit(vertex);
}
}
for (const Clock *clk : sdc_->clks()) {
for (const Pin *pin : clk->leafPins()) {
// Already hit these.
if (!network_->isTopLevelPort(pin)) {
Vertex *vertex = graph_->pinDrvrVertex(pin);
visitor->visit(vertex);
}
}
}
// Register clk pins.
for (Vertex *vertex : *graph_->regClkVertices())
visitor->visit(vertex);
const PinSet &startpoints = sdc_->pathDelayInternalFrom();
for (const Pin *pin : startpoints) {
Vertex *vertex = graph_->pinDrvrVertex(pin);
visitor->visit(vertex);
}
}
void
Search::visitEndpoints(VertexVisitor *visitor)
{
for (Vertex *end : *endpoints()) {
Pin *pin = end->pin();
// Filter register clock pins (fails on set_max_delay -from clk_src).
if (!network_->isRegClkPin(pin)
|| sdc_->isPathDelayInternalTo(pin))
visitor->visit(end);
}
}
////////////////////////////////////////////////////////////////
void
Search::findAllArrivals()
{
findAllArrivals(true);
}
void
Search::findAllArrivals(bool thru_latches)
{
arrival_visitor_->init(false);
// Iterate until data arrivals at all latches stop changing.
for (int pass = 1; pass == 1 || (thru_latches && havePendingLatchOutputs()); pass++) {
enqueuePendingLatchOutputs();
debugPrint(debug_, "search", 1, "find arrivals pass %d", pass);
findArrivals1(levelize_->maxLevel());
}
}
bool
Search::havePendingLatchOutputs()
{
return !pending_latch_outputs_->empty();
}
void
Search::clearPendingLatchOutputs()
{
pending_latch_outputs_->clear();
}
void
Search::enqueuePendingLatchOutputs()
{
for (Vertex *latch_vertex : *pending_latch_outputs_)
arrival_iter_->enqueue(latch_vertex);
clearPendingLatchOutputs();
}
void
Search::findArrivals()
{
findArrivals(levelize_->maxLevel());
}
void
Search::findArrivals(Level level)
{
arrival_visitor_->init(false);
findArrivals1(level);
}
void
Search::findArrivals1(Level level)
{
debugPrint(debug_, "search", 1, "find arrivals to level %d", level);
findArrivalsSeed();
Stats stats(debug_, report_);
int arrival_count = arrival_iter_->visitParallel(level, arrival_visitor_);
deleteTagsPrev();
if (arrival_count > 0)
deleteUnusedTagGroups();
stats.report("Find arrivals");
if (arrival_iter_->empty()
&& invalid_arrivals_->empty()) {
clk_arrivals_valid_ = true;
arrivals_at_endpoints_exist_ = true;
}
arrivals_exist_ = true;
debugPrint(debug_, "search", 1, "found %d arrivals", arrival_count);
}
void
Search::findArrivalsSeed()
{
if (!arrivals_seeded_) {
genclks_->ensureInsertionDelays();
arrival_iter_->clear();
required_iter_->clear();
seedArrivals();
arrivals_seeded_ = true;
}
else {
arrival_iter_->ensureSize();
required_iter_->ensureSize();
}
seedInvalidArrivals();
}
////////////////////////////////////////////////////////////////
ArrivalVisitor::ArrivalVisitor(const StaState *sta) :
PathVisitor(nullptr, false, sta)
{
init0();
init(true);
}
// Copy constructor.
ArrivalVisitor::ArrivalVisitor(bool always_to_endpoints,
SearchPred *pred,
const StaState *sta) :
PathVisitor(pred, true, sta)
{
init0();
init(always_to_endpoints, pred);
}
void
ArrivalVisitor::init0()
{
tag_bldr_ = new TagGroupBldr(true, this);
tag_bldr_no_crpr_ = new TagGroupBldr(false, this);
adj_pred_ = new SearchThru(tag_bldr_, this);
}
void
ArrivalVisitor::init(bool always_to_endpoints)
{
init(always_to_endpoints, search_ ? search_->evalPred() : nullptr);
}
void
ArrivalVisitor::init(bool always_to_endpoints,
SearchPred *pred)
{
always_to_endpoints_ = always_to_endpoints;
pred_ = pred;
crpr_active_ = crprActive();
}
VertexVisitor *
ArrivalVisitor::copy() const
{
return new ArrivalVisitor(always_to_endpoints_, pred_, this);
}
ArrivalVisitor::~ArrivalVisitor()
{
delete tag_bldr_;
delete tag_bldr_no_crpr_;
delete adj_pred_;
}
void
ArrivalVisitor::setAlwaysToEndpoints(bool to_endpoints)
{
always_to_endpoints_ = to_endpoints;
}
void
ArrivalVisitor::visit(Vertex *vertex)
{
debugPrint(debug_, "search", 2, "find arrivals %s",
vertex->to_string(this).c_str());
Pin *pin = vertex->pin();
tag_bldr_->init(vertex);
has_fanin_one_ = graph_->hasFaninOne(vertex);
if (crpr_active_
&& !has_fanin_one_)
tag_bldr_no_crpr_->init(vertex);
// Fanin paths are broken by path delays internal pin startpoints.
if (!sdc_->isPathDelayInternalFromBreak(pin)) {
visitFaninPaths(vertex);
if (crpr_active_
&& search_->crprPathPruningEnabled()
// No crpr for ideal clocks.
&& tag_bldr_->hasPropagatedClk()
&& !has_fanin_one_)
pruneCrprArrivals();
}
// Insert paths that originate here.
if (!network_->isTopLevelPort(pin)
&& sdc_->hasInputDelay(pin))
// set_input_delay on internal pin.
search_->seedInputSegmentArrival(pin, vertex, tag_bldr_);
if (sdc_->isPathDelayInternalFrom(pin))
// set_min/max_delay -from internal pin.
search_->makeUnclkedPaths(vertex, false, true, tag_bldr_);
if (sdc_->isLeafPinClock(pin))
// set_min/max_delay -to internal pin also a clock src. Bizzaroland.
// Re-seed the clock arrivals on top of the propagated paths.
search_->seedClkArrivals(pin, vertex, tag_bldr_);
// Register/latch clock pin that is not connected to a declared clock.
// Seed with unclocked tag, zero arrival and allow search thru reg
// clk->q edges.
// These paths are required to report path delays from unclocked registers
// For example, "set_max_delay -to" from an unclocked source register.
bool is_clk = tag_bldr_->hasClkTag();
if (vertex->isRegClk() && !is_clk) {
debugPrint(debug_, "search", 2, "arrival seed unclked reg clk %s",
network_->pathName(pin));
search_->makeUnclkedPaths(vertex, true, false, tag_bldr_);
}
bool arrivals_changed = search_->arrivalsChanged(vertex, tag_bldr_);
// If vertex is a latch data input arrival that changed from the
// previous eval pass enqueue the latch outputs to be re-evaled on the
// next pass.
if (arrivals_changed
&& network_->isLatchData(pin))
search_->enqueueLatchDataOutputs(vertex);
if (!search_->arrivalsAtEndpointsExist()
|| always_to_endpoints_
|| arrivals_changed)
search_->arrivalIterator()->enqueueAdjacentVertices(vertex, adj_pred_);
if (arrivals_changed) {
debugPrint(debug_, "search", 4, "arrivals changed");
search_->setVertexArrivals(vertex, tag_bldr_);
search_->tnsInvalid(vertex);
constrainedRequiredsInvalid(vertex, is_clk);
}
enqueueRefPinInputDelays(pin);
}
// When a clock arrival changes, the required time changes for any
// timing checks, data checks or gated clock enables constrained
// by the clock pin.
void
ArrivalVisitor::constrainedRequiredsInvalid(Vertex *vertex,
bool is_clk)
{
Pin *pin = vertex->pin();
if (network_->isLoad(pin)
&& search_->requiredsExist()) {
if (is_clk && network_->isCheckClk(pin)) {
VertexOutEdgeIterator edge_iter(vertex, graph_);
while (edge_iter.hasNext()) {
Edge *edge = edge_iter.next();
if (edge->role()->isTimingCheck()) {
Vertex *to_vertex = edge->to(graph_);
search_->requiredInvalid(to_vertex);
}
}
}
// Data checks (vertex does not need to be a clk).
DataCheckSet *data_checks = sdc_->dataChecksFrom(pin);
if (data_checks) {
for (DataCheck *data_check : *data_checks) {
Pin *to = data_check->to();
search_->requiredInvalid(to);
}
}
// Gated clocks.
if (is_clk && variables_->gatedClkChecksEnabled()) {
PinSet enable_pins(network_);
search_->gatedClk()->gatedClkEnables(vertex, enable_pins);
for (const Pin *enable : enable_pins)
search_->requiredInvalid(enable);
}
}
}
bool
Search::arrivalsChanged(Vertex *vertex,
TagGroupBldr *tag_bldr)
{
Path *paths1 = graph_->paths(vertex);
if (paths1) {
TagGroup *tag_group = tagGroup(vertex);
if (tag_group == nullptr
|| tag_group->pathCount() != tag_bldr->pathCount())
return true;
for (auto const [tag1, path_index1] : *tag_group->pathIndexMap()) {
Path *path1 = &paths1[path_index1];
Path *path2 = tag_bldr->tagMatchPath(tag1);
if (path2 == nullptr
|| path1->tag(this) != path2->tag(this)
|| !delayEqual(path1->arrival(), path2->arrival())
|| path1->prevEdge(this) != path2->prevEdge(this)
|| path1->prevArc(this) != path2->prevArc(this)
|| path1->prevPath() != path2->prevPath())
return true;
}
return false;
}
else
return true;
}
bool
ArrivalVisitor::visitFromToPath(const Pin * /* from_pin */,
Vertex *from_vertex,
const RiseFall *from_rf,
Tag *from_tag,
Path *from_path,
const Arrival &from_arrival,
Edge *edge,
TimingArc *arc,
ArcDelay arc_delay,
Vertex * /* to_vertex */,
const RiseFall *to_rf,
Tag *to_tag,
Arrival &to_arrival,
const MinMax *min_max,
const PathAnalysisPt *)
{
debugPrint(debug_, "search", 3, " %s",
from_vertex->to_string(this).c_str());
debugPrint(debug_, "search", 3, " %s -> %s %s",
from_rf->to_string().c_str(),
to_rf->to_string().c_str(),
min_max->to_string().c_str());
debugPrint(debug_, "search", 3, " from tag: %s",
from_tag->to_string(this).c_str());
debugPrint(debug_, "search", 3, " to tag : %s",
to_tag->to_string(this).c_str());
const ClkInfo *to_clk_info = to_tag->clkInfo();
bool to_is_clk = to_tag->isClock();
Path *match;
size_t path_index;
tag_bldr_->tagMatchPath(to_tag, match, path_index);
if (match == nullptr
|| delayGreater(to_arrival, match->arrival(), min_max, this)) {
debugPrint(debug_, "search", 3, " %s + %s = %s %s %s",
delayAsString(from_arrival, this),
delayAsString(arc_delay, this),
delayAsString(to_arrival, this),
min_max == MinMax::max() ? ">" : "<",
match ? delayAsString(match->arrival(), this) : "MIA");
tag_bldr_->setMatchPath(match, path_index, to_tag, to_arrival, from_path, edge, arc);
if (crpr_active_
&& !has_fanin_one_
&& to_clk_info->hasCrprClkPin()
&& !to_is_clk) {
tag_bldr_no_crpr_->tagMatchPath(to_tag, match, path_index);
if (match == nullptr
|| delayGreater(to_arrival, match->arrival(), min_max, this)) {
tag_bldr_no_crpr_->setMatchPath(match, path_index, to_tag, to_arrival,
from_path, edge, arc);
}
}
}
return true;
}
void
ArrivalVisitor::pruneCrprArrivals()
{
CheckCrpr *crpr = search_->checkCrpr();
PathIndexMap &path_index_map = tag_bldr_->pathIndexMap();
for (auto path_itr = path_index_map.cbegin(); path_itr != path_index_map.cend(); ) {
Tag *tag = path_itr->first;
size_t path_index = path_itr->second;
const ClkInfo *clk_info = tag->clkInfo();
bool deleted_tag = false;
if (!tag->isClock()
&& clk_info->hasCrprClkPin()) {
PathAnalysisPt *path_ap = tag->pathAnalysisPt(this);
const MinMax *min_max = path_ap->pathMinMax();
Path *path_no_crpr = tag_bldr_no_crpr_->tagMatchPath(tag);
if (path_no_crpr) {
Arrival max_arrival = path_no_crpr->arrival();
const ClkInfo *clk_info_no_crpr = path_no_crpr->clkInfo(this);
Arrival max_crpr = crpr->maxCrpr(clk_info_no_crpr);
Arrival max_arrival_max_crpr = (min_max == MinMax::max())
? max_arrival - max_crpr
: max_arrival + max_crpr;
debugPrint(debug_, "search", 4, " cmp %s %s - %s = %s",
tag->to_string(this).c_str(),
delayAsString(max_arrival, this),
delayAsString(max_crpr, this),
delayAsString(max_arrival_max_crpr, this));
Arrival arrival = tag_bldr_->arrival(path_index);
// Latch D->Q path uses enable min so crpr clk path min/max
// does not match the path min/max.
if (delayGreater(max_arrival_max_crpr, arrival, min_max, this)
&& clk_info_no_crpr->crprClkPath(this)->minMax(this)
== clk_info->crprClkPath(this)->minMax(this)) {
debugPrint(debug_, "search", 3, " pruned %s",
tag->to_string(this).c_str());
path_itr = path_index_map.erase(path_itr);
deleted_tag = true;
}
}
}
if (!deleted_tag)
path_itr++;
}
}
// Enqueue pins with input delays that use ref_pin as the clock
// reference pin as if there is a timing arc from the reference pin to
// the input delay pin.
void
ArrivalVisitor::enqueueRefPinInputDelays(const Pin *ref_pin)
{
InputDelaySet *input_delays = sdc_->refPinInputDelays(ref_pin);
if (input_delays) {
for (InputDelay *input_delay : *input_delays) {
const Pin *pin = input_delay->pin();
Vertex *vertex, *bidirect_drvr_vertex;
graph_->pinVertices(pin, vertex, bidirect_drvr_vertex);
seedInputDelayArrival(pin, vertex, input_delay);
if (bidirect_drvr_vertex)
seedInputDelayArrival(pin, bidirect_drvr_vertex, input_delay);
}
}
}
void
ArrivalVisitor::seedInputDelayArrival(const Pin *pin,
Vertex *vertex,
InputDelay *input_delay)
{
TagGroupBldr tag_bldr(true, this);
tag_bldr.init(vertex);
search_->genclks()->copyGenClkSrcPaths(vertex, &tag_bldr);
search_->seedInputDelayArrival(pin, vertex, input_delay,
!network_->isTopLevelPort(pin), &tag_bldr);
search_->setVertexArrivals(vertex, &tag_bldr);
search_->arrivalIterator()->enqueueAdjacentVertices(vertex,
search_->searchAdj());
}
void
Search::enqueueLatchDataOutputs(Vertex *vertex)
{
VertexOutEdgeIterator out_edge_iter(vertex, graph_);
while (out_edge_iter.hasNext()) {
Edge *out_edge = out_edge_iter.next();
if (latches_->isLatchDtoQ(out_edge)) {
Vertex *out_vertex = out_edge->to(graph_);
LockGuard lock(pending_latch_outputs_lock_);
pending_latch_outputs_->insert(out_vertex);
}
}
}
void
Search::seedArrivals()
{
VertexSet vertices(graph_);
findClockVertices(vertices);
findRootVertices(vertices);
findInputDrvrVertices(vertices);
for (Vertex *vertex : vertices)
seedArrival(vertex);
}
void
Search::findClockVertices(VertexSet &vertices)
{
for (const Clock *clk : sdc_->clks()) {
for (const Pin *pin : clk->leafPins()) {
Vertex *vertex, *bidirect_drvr_vertex;
graph_->pinVertices(pin, vertex, bidirect_drvr_vertex);
vertices.insert(vertex);
if (bidirect_drvr_vertex)
vertices.insert(bidirect_drvr_vertex);
}
}
}
void
Search::seedInvalidArrivals()
{
for (Vertex *vertex : *invalid_arrivals_)
seedArrival(vertex);
invalid_arrivals_->clear();
}
void
Search::seedArrival(Vertex *vertex)
{
const Pin *pin = vertex->pin();
if (sdc_->isLeafPinClock(pin)) {
TagGroupBldr tag_bldr(true, this);
tag_bldr.init(vertex);
genclks_->copyGenClkSrcPaths(vertex, &tag_bldr);
seedClkArrivals(pin, vertex, &tag_bldr);
// Clock pin may also have input arrivals from other clocks.
seedInputArrival(pin, vertex, &tag_bldr);
setVertexArrivals(vertex, &tag_bldr);
}
else if (isInputArrivalSrchStart(vertex)) {
TagGroupBldr tag_bldr(true, this);
tag_bldr.init(vertex);
genclks_->copyGenClkSrcPaths(vertex, &tag_bldr);
seedInputArrival(pin, vertex, &tag_bldr);
setVertexArrivals(vertex, &tag_bldr);
if (!tag_bldr.empty())
// Only search downstream if there were non-false paths from here.
arrival_iter_->enqueueAdjacentVertices(vertex, search_adj_);
}
else if (levelize_->isRoot(vertex)) {
bool is_reg_clk = vertex->isRegClk();
if (is_reg_clk
// Internal roots isolated by disabled pins are seeded with no clock.
|| (unconstrained_paths_
&& !network_->isTopLevelPort(pin))) {
debugPrint(debug_, "search", 2, "arrival seed unclked root %s",
network_->pathName(pin));
TagGroupBldr tag_bldr(true, this);
tag_bldr.init(vertex);
genclks_->copyGenClkSrcPaths(vertex, &tag_bldr);
if (makeUnclkedPaths(vertex, is_reg_clk, false, &tag_bldr))
// Only search downstream if there are no false paths from here.
arrival_iter_->enqueueAdjacentVertices(vertex, search_adj_);
setVertexArrivals(vertex, &tag_bldr);
}
else {
deletePathsIncr(vertex);
if (search_adj_->searchFrom(vertex))
arrival_iter_->enqueueAdjacentVertices(vertex, search_adj_);
}
}
else {
debugPrint(debug_, "search", 4, "arrival enqueue %s %u",
network_->pathName(pin),
vertex->level());
arrival_iter_->enqueue(vertex);
}
}
// Find all of the clock leaf pins.
void
Search::findClkVertexPins(PinSet &clk_pins)
{
for (const Clock *clk : sdc_->clks()) {
for (const Pin *pin : clk->leafPins()) {
clk_pins.insert(pin);
}
}
}
void
Search::seedClkArrivals(const Pin *pin,
Vertex *vertex,
TagGroupBldr *tag_bldr)
{
for (const Clock *clk : *sdc_->findLeafPinClocks(pin)) {
debugPrint(debug_, "search", 2, "arrival seed clk %s pin %s",
clk->name(), network_->pathName(pin));
for (PathAnalysisPt *path_ap : corners_->pathAnalysisPts()) {
const MinMax *min_max = path_ap->pathMinMax();
for (const RiseFall *rf : RiseFall::range()) {
const ClockEdge *clk_edge = clk->edge(rf);
const EarlyLate *early_late = min_max;
if (clk->isGenerated()
&& clk->masterClk() == nullptr)
seedClkDataArrival(pin, rf, clk, clk_edge, min_max, path_ap,
0.0, tag_bldr);
else {
Arrival insertion = clockInsertion(clk, pin, rf, min_max,
early_late, path_ap);
seedClkArrival(pin, rf, clk, clk_edge, min_max, path_ap,
insertion, tag_bldr);
}
}
}
arrival_iter_->enqueueAdjacentVertices(vertex, search_adj_);
}
}
void
Search::seedClkArrival(const Pin *pin,
const RiseFall *rf,
const Clock *clk,
const ClockEdge *clk_edge,
const MinMax *min_max,
const PathAnalysisPt *path_ap,
Arrival insertion,
TagGroupBldr *tag_bldr)
{
bool is_propagated = false;
float latency = 0.0;
bool latency_exists;
// Check for clk pin latency.
sdc_->clockLatency(clk, pin, rf, min_max,
latency, latency_exists);
if (!latency_exists) {
// Check for clk latency (lower priority).
sdc_->clockLatency(clk, rf, min_max,
latency, latency_exists);
if (latency_exists) {
// Propagated pin overrides latency on clk.
if (sdc_->isPropagatedClock(pin)) {
latency = 0.0;
latency_exists = false;
is_propagated = true;
}
}
else
is_propagated = sdc_->isPropagatedClock(pin)
|| clk->isPropagated();
}
ClockUncertainties *uncertainties = sdc_->clockUncertainties(pin);
if (uncertainties == nullptr)
uncertainties = clk->uncertainties();
// Propagate liberty "pulse_clock" transition to transitive fanout.
LibertyPort *port = network_->libertyPort(pin);
const RiseFall *pulse_clk_sense = (port ? port->pulseClkSense() : nullptr);
const ClkInfo *clk_info = findClkInfo(clk_edge, pin, is_propagated, nullptr, false,
pulse_clk_sense, insertion, latency,
uncertainties, path_ap, nullptr);
// Only false_paths -from apply to clock tree pins.
ExceptionStateSet *states = nullptr;
sdc_->exceptionFromClkStates(pin,rf,clk,rf,min_max,states);
Tag *tag = findTag(rf, path_ap, clk_info, true, nullptr, false, states,
true, nullptr);
Arrival arrival(clk_edge->time() + insertion);
tag_bldr->setArrival(tag, arrival);
}
void
Search::seedClkDataArrival(const Pin *pin,
const RiseFall *rf,
const Clock *clk,
const ClockEdge *clk_edge,
const MinMax *min_max,
const PathAnalysisPt *path_ap,
Arrival insertion,
TagGroupBldr *tag_bldr)
{
Tag *tag = clkDataTag(pin, clk, rf, clk_edge, insertion, min_max, path_ap);
if (tag) {
// Data arrivals include insertion delay.
Arrival arrival(clk_edge->time() + insertion);
tag_bldr->setArrival(tag, arrival);
}
}
Tag *
Search::clkDataTag(const Pin *pin,
const Clock *clk,
const RiseFall *rf,
const ClockEdge *clk_edge,
Arrival insertion,
const MinMax *min_max,
const PathAnalysisPt *path_ap)
{
ExceptionStateSet *states = nullptr;
if (sdc_->exceptionFromStates(pin, rf, clk, rf, min_max, states)) {
bool is_propagated = (clk->isPropagated()
|| sdc_->isPropagatedClock(pin));
const ClkInfo *clk_info = findClkInfo(clk_edge, pin, is_propagated,
insertion, path_ap);
return findTag(rf, path_ap, clk_info, false, nullptr, false, states,
true, nullptr);
}
else
return nullptr;
}
////////////////////////////////////////////////////////////////
bool
Search::makeUnclkedPaths(Vertex *vertex,
bool is_segment_start,
bool require_exception,
TagGroupBldr *tag_bldr)
{
bool search_from = false;
const Pin *pin = vertex->pin();
for (PathAnalysisPt *path_ap : corners_->pathAnalysisPts()) {
const MinMax *min_max = path_ap->pathMinMax();
for (const RiseFall *rf : RiseFall::range()) {
Tag *tag = fromUnclkedInputTag(pin, rf, min_max, path_ap,
is_segment_start,
require_exception);
if (tag) {
tag_bldr->setArrival(tag, delay_zero);
search_from = true;
}
}
}
return search_from;
}
// Find graph roots and input ports that do NOT have arrivals.
void
Search::findRootVertices(VertexSet &vertices)
{
for (Vertex *vertex : levelize_->roots()) {
const Pin *pin = vertex->pin();
if (!sdc_->isLeafPinClock(pin)
&& !sdc_->hasInputDelay(pin)
&& !vertex->isConstant()) {
vertices.insert(vertex);
}
}
}
void
Search::findInputDrvrVertices(VertexSet &vertices)
{
Instance *top_inst = network_->topInstance();
InstancePinIterator *pin_iter = network_->pinIterator(top_inst);
while (pin_iter->hasNext()) {
Pin *pin = pin_iter->next();
if (network_->direction(pin)->isAnyInput())
vertices.insert(graph_->pinDrvrVertex(pin));
}
delete pin_iter;
}
bool
Search::isSegmentStart(const Pin *pin)
{
return sdc_->isInputDelayInternal(pin)
&& !sdc_->isLeafPinClock(pin);
}
bool
Search::isInputArrivalSrchStart(Vertex *vertex)
{
const Pin *pin = vertex->pin();
PortDirection *dir = network_->direction(pin);
bool is_top_level_port = network_->isTopLevelPort(pin);
return (is_top_level_port
&& (dir->isInput()
|| (dir->isBidirect() && vertex->isBidirectDriver()))) ;
}
// Seed input arrivals clocked by clks.
void
Search::seedInputArrivals(ClockSet *clks)
{
// Input arrivals can be on internal pins, so iterate over the pins
// that have input arrivals rather than the top level input pins.
for (const auto [pin, input_delays] : sdc_->inputDelayPinMap()) {
if (!sdc_->isLeafPinClock(pin)) {
Vertex *vertex = graph_->pinDrvrVertex(pin);
seedInputArrival(pin, vertex, clks);
}
}
}
void
Search::seedInputArrival(const Pin *pin,
Vertex *vertex,
ClockSet *wrt_clks)
{
bool has_arrival = false;
// There can be multiple arrivals for a pin with wrt different clocks.
TagGroupBldr tag_bldr(true, this);
tag_bldr.init(vertex);
genclks_->copyGenClkSrcPaths(vertex, &tag_bldr);
InputDelaySet *input_delays = sdc_->inputDelaysLeafPin(pin);
if (input_delays) {
for (InputDelay *input_delay : *input_delays) {
Clock *input_clk = input_delay->clock();
ClockSet *pin_clks = sdc_->findLeafPinClocks(pin);
if (input_clk && wrt_clks->hasKey(input_clk)
// Input arrivals wrt a clock source pin is the insertion
// delay (source latency), but arrivals wrt other clocks
// propagate.
&& (pin_clks == nullptr
|| !pin_clks->hasKey(input_clk))) {
seedInputDelayArrival(pin, vertex, input_delay, false, &tag_bldr);
has_arrival = true;
}
}
if (has_arrival)
setVertexArrivals(vertex, &tag_bldr);
}
}
void
Search::seedInputArrival(const Pin *pin,
Vertex *vertex,
TagGroupBldr *tag_bldr)
{
if (sdc_->hasInputDelay(pin))
seedInputArrival1(pin, vertex, false, tag_bldr);
else if (!sdc_->isLeafPinClock(pin))
// Seed inputs without set_input_delays.
seedInputDelayArrival(pin, vertex, nullptr, false, tag_bldr);
}
void
Search::seedInputSegmentArrival(const Pin *pin,
Vertex *vertex,
TagGroupBldr *tag_bldr)
{
seedInputArrival1(pin, vertex, true, tag_bldr);
}
void
Search::seedInputArrival1(const Pin *pin,
Vertex *vertex,
bool is_segment_start,
TagGroupBldr *tag_bldr)
{
// There can be multiple arrivals for a pin with wrt different clocks.
InputDelaySet *input_delays = sdc_->inputDelaysLeafPin(pin);
if (input_delays) {
for (InputDelay *input_delay : *input_delays) {
Clock *input_clk = input_delay->clock();
ClockSet *pin_clks = sdc_->findLeafPinClocks(pin);
// Input arrival wrt a clock source pin is the clock insertion
// delay (source latency), but arrivals wrt other clocks
// propagate.
if (pin_clks == nullptr
|| !pin_clks->hasKey(input_clk))
seedInputDelayArrival(pin, vertex, input_delay, is_segment_start,
tag_bldr);
}
}
}
void
Search::seedInputDelayArrival(const Pin *pin,
Vertex *vertex,
InputDelay *input_delay,
bool is_segment_start,
TagGroupBldr *tag_bldr)
{
debugPrint(debug_, "search", 2,
input_delay
? "arrival seed input arrival %s"
: "arrival seed input %s",
vertex->to_string(this).c_str());
const ClockEdge *clk_edge = nullptr;
const Pin *ref_pin = nullptr;
if (input_delay) {
clk_edge = input_delay->clkEdge();
if (clk_edge == nullptr
&& variables_->useDefaultArrivalClock())
clk_edge = sdc_->defaultArrivalClockEdge();
ref_pin = input_delay->refPin();
}
else if (variables_->useDefaultArrivalClock())
clk_edge = sdc_->defaultArrivalClockEdge();
if (ref_pin) {
Vertex *ref_vertex = graph_->pinLoadVertex(ref_pin);
for (PathAnalysisPt *path_ap : corners_->pathAnalysisPts()) {
const MinMax *min_max = path_ap->pathMinMax();
const RiseFall *ref_rf = input_delay->refTransition();
const Clock *clk = input_delay->clock();
VertexPathIterator ref_path_iter(ref_vertex, ref_rf, path_ap, this);
while (ref_path_iter.hasNext()) {
Path *ref_path = ref_path_iter.next();
if (ref_path->isClock(this)
&& (clk == nullptr
|| ref_path->clock(this) == clk)) {
float ref_arrival, ref_insertion, ref_latency;
inputDelayRefPinArrival(ref_path, ref_path->clkEdge(this), min_max,
ref_arrival, ref_insertion, ref_latency);
seedInputDelayArrival(pin, input_delay, ref_path->clkEdge(this),
ref_arrival, ref_insertion, ref_latency,
is_segment_start, min_max, path_ap, tag_bldr);
}
}
}
}
else {
for (PathAnalysisPt *path_ap : corners_->pathAnalysisPts()) {
const MinMax *min_max = path_ap->pathMinMax();
float clk_arrival, clk_insertion, clk_latency;
inputDelayClkArrival(input_delay, clk_edge, min_max, path_ap,
clk_arrival, clk_insertion, clk_latency);
seedInputDelayArrival(pin, input_delay, clk_edge,
clk_arrival, clk_insertion, clk_latency,
is_segment_start, min_max, path_ap, tag_bldr);
}
}
}
// Input delays with -reference_pin use the clock network latency
// from the clock source to the reference pin.
void
Search::inputDelayRefPinArrival(Path *ref_path,
const ClockEdge *clk_edge,
const MinMax *min_max,
// Return values.
float &ref_arrival,
float &ref_insertion,
float &ref_latency)
{
Clock *clk = clk_edge->clock();
if (clk->isPropagated()) {
const ClkInfo *clk_info = ref_path->clkInfo(this);
ref_arrival = delayAsFloat(ref_path->arrival());
ref_insertion = delayAsFloat(clk_info->insertion());
ref_latency = clk_info->latency();
}
else {
const RiseFall *clk_rf = clk_edge->transition();
const EarlyLate *early_late = min_max;
// Input delays from ideal clk reference pins include clock
// insertion delay but not latency.
ref_insertion = sdc_->clockInsertion(clk, clk_rf, min_max, early_late);
ref_arrival = clk_edge->time() + ref_insertion;
ref_latency = 0.0;
}
}
void
Search::seedInputDelayArrival(const Pin *pin,
InputDelay *input_delay,
const ClockEdge *clk_edge,
float clk_arrival,
float clk_insertion,
float clk_latency,
bool is_segment_start,
const MinMax *min_max,
PathAnalysisPt *path_ap,
TagGroupBldr *tag_bldr)
{
for (const RiseFall *rf : RiseFall::range()) {
if (input_delay) {
float delay;
bool exists;
input_delay->delays()->value(rf, min_max, delay, exists);
if (exists)
seedInputDelayArrival(pin, rf, clk_arrival + delay,
input_delay, clk_edge,
clk_insertion, clk_latency, is_segment_start,
min_max, path_ap, tag_bldr);
}
else
seedInputDelayArrival(pin, rf, 0.0, nullptr, clk_edge,
clk_insertion, clk_latency, is_segment_start,
min_max, path_ap, tag_bldr);
}
}
void
Search::seedInputDelayArrival(const Pin *pin,
const RiseFall *rf,
float arrival,
InputDelay *input_delay,
const ClockEdge *clk_edge,
float clk_insertion,
float clk_latency,
bool is_segment_start,
const MinMax *min_max,
PathAnalysisPt *path_ap,
TagGroupBldr *tag_bldr)
{
Tag *tag = inputDelayTag(pin, rf, clk_edge, clk_insertion, clk_latency,
input_delay, is_segment_start, min_max, path_ap);
if (tag)
tag_bldr->setArrival(tag, arrival);
}
void
Search::inputDelayClkArrival(InputDelay *input_delay,
const ClockEdge *clk_edge,
const MinMax *min_max,
const PathAnalysisPt *path_ap,
// Return values.
float &clk_arrival, float &clk_insertion,
float &clk_latency)
{
clk_arrival = 0.0;
clk_insertion = 0.0;
clk_latency = 0.0;
if (input_delay && clk_edge) {
clk_arrival = clk_edge->time();
Clock *clk = clk_edge->clock();
const RiseFall *clk_rf = clk_edge->transition();
if (!input_delay->sourceLatencyIncluded()) {
const EarlyLate *early_late = min_max;
clk_insertion = delayAsFloat(clockInsertion(clk, clk->defaultPin(),
clk_rf, min_max, early_late,
path_ap));
clk_arrival += clk_insertion;
}
if (!clk->isPropagated()
&& !input_delay->networkLatencyIncluded()) {
clk_latency = sdc_->clockLatency(clk, clk_rf, min_max);
clk_arrival += clk_latency;
}
}
}
Tag *
Search::inputDelayTag(const Pin *pin,
const RiseFall *rf,
const ClockEdge *clk_edge,
float clk_insertion,
float clk_latency,
InputDelay *input_delay,
bool is_segment_start,
const MinMax *min_max,
const PathAnalysisPt *path_ap)
{
Clock *clk = nullptr;
const Pin *clk_pin = nullptr;
const RiseFall *clk_rf = nullptr;
bool is_propagated = false;
ClockUncertainties *clk_uncertainties = nullptr;
if (clk_edge) {
clk = clk_edge->clock();
clk_rf = clk_edge->transition();
clk_pin = clk->defaultPin();
is_propagated = clk->isPropagated();
clk_uncertainties = clk->uncertainties();
}
ExceptionStateSet *states = nullptr;
Tag *tag = nullptr;
if (sdc_->exceptionFromStates(pin,rf,clk,clk_rf,min_max,states)) {
const ClkInfo *clk_info = findClkInfo(clk_edge, clk_pin, is_propagated, nullptr,
false, nullptr, clk_insertion, clk_latency,
clk_uncertainties, path_ap, nullptr);
tag = findTag(rf, path_ap, clk_info, false, input_delay, is_segment_start,
states, true, nullptr);
}
if (tag) {
const ClkInfo *clk_info = tag->clkInfo();
// Check for state changes on existing tag exceptions (pending -thru pins).
tag = mutateTag(tag, pin, rf, false, clk_info,
pin, rf, false, false, is_segment_start, clk_info,
input_delay, min_max, path_ap, nullptr);
}
return tag;
}
////////////////////////////////////////////////////////////////
PathVisitor::PathVisitor(const StaState *sta) :
StaState(sta),
pred_(sta->search()->evalPred()),
tag_cache_( nullptr)
{
}
PathVisitor::PathVisitor(SearchPred *pred,
bool make_tag_cache,
const StaState *sta) :
StaState(sta),
pred_(pred),
tag_cache_(make_tag_cache
? new TagSet(128, TagSet::hasher(sta), TagSet::key_equal(sta))
: nullptr)
{
}
PathVisitor::~PathVisitor()
{
delete tag_cache_;
}
void
PathVisitor::visitFaninPaths(Vertex *to_vertex)
{
if (pred_->searchTo(to_vertex)) {
VertexInEdgeIterator edge_iter(to_vertex, graph_);
while (edge_iter.hasNext()) {
Edge *edge = edge_iter.next();
Vertex *from_vertex = edge->from(graph_);
const Pin *from_pin = from_vertex->pin();
if (pred_->searchFrom(from_vertex)
&& pred_->searchThru(edge)) {
const Pin *to_pin = to_vertex->pin();
if (!visitEdge(from_pin, from_vertex, edge, to_pin, to_vertex))
break;
}
}
}
}
void
PathVisitor::visitFanoutPaths(Vertex *from_vertex)
{
const Pin *from_pin = from_vertex->pin();
if (pred_->searchFrom(from_vertex)) {
VertexOutEdgeIterator edge_iter(from_vertex, graph_);
while (edge_iter.hasNext()) {
Edge *edge = edge_iter.next();
Vertex *to_vertex = edge->to(graph_);
const Pin *to_pin = to_vertex->pin();
if (pred_->searchTo(to_vertex)
&& pred_->searchThru(edge)) {
debugPrint(debug_, "search", 3, " %s",
to_vertex->to_string(this).c_str());
if (!visitEdge(from_pin, from_vertex, edge, to_pin, to_vertex))
break;
}
}
}
}
bool
PathVisitor::visitEdge(const Pin *from_pin,
Vertex *from_vertex,
Edge *edge,
const Pin *to_pin,
Vertex *to_vertex)
{
TagGroup *from_tag_group = search_->tagGroup(from_vertex);
if (from_tag_group) {
TimingArcSet *arc_set = edge->timingArcSet();
VertexPathIterator from_iter(from_vertex, search_);
while (from_iter.hasNext()) {
Path *from_path = from_iter.next();
PathAnalysisPt *path_ap = from_path->pathAnalysisPt(this);
const MinMax *min_max = path_ap->pathMinMax();
const RiseFall *from_rf = from_path->transition(this);
TimingArc *arc1, *arc2;
arc_set->arcsFrom(from_rf, arc1, arc2);
if (!visitArc(from_pin, from_vertex, from_rf, from_path,
edge, arc1, to_pin, to_vertex,
min_max, path_ap))
return false;
if (!visitArc(from_pin, from_vertex, from_rf, from_path,
edge, arc2, to_pin, to_vertex,
min_max, path_ap))
return false;
}
}
return true;
}
bool
PathVisitor::visitArc(const Pin *from_pin,
Vertex *from_vertex,
const RiseFall *from_rf,
Path *from_path,
Edge *edge,
TimingArc *arc,
const Pin *to_pin,
Vertex *to_vertex,
const MinMax *min_max,
PathAnalysisPt *path_ap)
{
if (arc) {
const RiseFall *to_rf = arc->toEdge()->asRiseFall();
if (searchThru(from_vertex, from_rf, edge, to_vertex, to_rf))
return visitFromPath(from_pin, from_vertex, from_rf, from_path,
edge, arc, to_pin, to_vertex, to_rf,
min_max, path_ap);
}
return true;
}
bool
PathVisitor::visitFromPath(const Pin *from_pin,
Vertex *from_vertex,
const RiseFall *from_rf,
Path *from_path,
Edge *edge,
TimingArc *arc,
const Pin *to_pin,
Vertex *to_vertex,
const RiseFall *to_rf,
const MinMax *min_max,
const PathAnalysisPt *path_ap)
{
const TimingRole *role = edge->role();
Tag *from_tag = from_path->tag(this);
const ClkInfo *from_clk_info = from_tag->clkInfo();
Tag *to_tag = nullptr;
const ClockEdge *clk_edge = from_clk_info->clkEdge();
const Clock *clk = from_clk_info->clock();
Arrival from_arrival = from_path->arrival();
ArcDelay arc_delay = 0.0;
Arrival to_arrival;
if (from_clk_info->isGenClkSrcPath()) {
if (!sdc_->clkStopPropagation(clk,from_pin,from_rf,to_pin,to_rf)
&& (variables_->clkThruTristateEnabled()
|| !(role == TimingRole::tristateEnable()
|| role == TimingRole::tristateDisable()))) {
const Clock *gclk = from_tag->genClkSrcPathClk(this);
if (gclk) {
Genclks *genclks = search_->genclks();
VertexSet *fanins = genclks->fanins(gclk);
// Note: encountering a latch d->q edge means find the
// latch feedback edges, but they are referenced for
// other edges in the gen clk fanout.
if (role == TimingRole::latchDtoQ())
genclks->findLatchFdbkEdges(gclk);
EdgeSet *fdbk_edges = genclks->latchFdbkEdges(gclk);
if ((role == TimingRole::combinational()
|| role == TimingRole::wire()
|| !gclk->combinational())
&& fanins->hasKey(to_vertex)
&& !(fdbk_edges && fdbk_edges->hasKey(edge))) {
arc_delay = search_->deratedDelay(from_vertex, arc, edge,
true, path_ap);
const PathAnalysisPt *path_ap_opp =
path_ap->corner()->findPathAnalysisPt(min_max->opposite());
Delay arc_delay_opp = search_->deratedDelay(from_vertex, arc, edge,
true, path_ap_opp);
bool arc_delay_min_max_eq =
fuzzyEqual(delayAsFloat(arc_delay), delayAsFloat(arc_delay_opp));
to_tag = search_->thruClkTag(from_path, from_vertex, from_tag, true,
edge, to_rf, arc_delay_min_max_eq,
min_max, path_ap);
to_arrival = from_arrival + arc_delay;
}
}
}
}
else if (role->genericRole() == TimingRole::regClkToQ()) {
if (clk == nullptr
|| !sdc_->clkStopPropagation(from_pin, clk)) {
arc_delay = search_->deratedDelay(from_vertex, arc, edge, false, path_ap);
// Remove clock network delay for macros created with propagated
// clocks when used in a context with ideal clocks.
if (clk && clk->isIdeal()) {
const LibertyPort *clk_port = network_->libertyPort(from_pin);
const LibertyCell *inst_cell = clk_port->libertyCell();
if (inst_cell->isMacro()) {
float slew = delayAsFloat(from_path->slew(this));
arc_delay -= clk_port->clkTreeDelay(slew, from_rf, min_max);
}
}
// Propagate from unclocked reg/latch clk pins, which have no
// clk but are distinguished with a segment_start flag.
if ((clk_edge == nullptr
&& from_tag->isSegmentStart())
// Do not propagate paths from input ports with default
// input arrival clk thru CLK->Q edges.
|| (clk != sdc_->defaultArrivalClock()
// Only propagate paths from clocks that have not
// passed thru reg/latch D->Q edges.
&& from_tag->isClock())) {
const RiseFall *clk_rf = clk_edge ? clk_edge->transition() : nullptr;
const ClkInfo *to_clk_info = from_clk_info;
if (from_clk_info->crprClkPath(this) == nullptr
|| network_->direction(to_pin)->isInternal())
to_clk_info = search_->clkInfoWithCrprClkPath(from_clk_info,
from_path, path_ap);
to_tag = search_->fromRegClkTag(from_pin, from_rf, clk, clk_rf,
to_clk_info, to_pin, to_rf, min_max,
path_ap);
if (to_tag)
to_tag = search_->thruTag(to_tag, edge, to_rf, min_max, path_ap, tag_cache_);
from_arrival = search_->clkPathArrival(from_path, from_clk_info,
clk_edge, min_max, path_ap);
to_arrival = from_arrival + arc_delay;
}
else
to_tag = nullptr;
}
}
else if (edge->role() == TimingRole::latchDtoQ()) {
if (min_max == MinMax::max()
&& clk) {
arc_delay = search_->deratedDelay(from_vertex, arc, edge, false, path_ap);
latches_->latchOutArrival(from_path, arc, edge, path_ap,
to_tag, arc_delay, to_arrival);
if (to_tag)
to_tag = search_->thruTag(to_tag, edge, to_rf, min_max, path_ap, tag_cache_);
}
}
else if (from_tag->isClock()) {
ClockSet *clks = sdc_->findLeafPinClocks(from_pin);
// Disable edges from hierarchical clock source pins that do
// not go thru the hierarchical pin and edges from clock source pins
// that traverse a hierarchical source pin of a different clock.
// Clock arrivals used as data also need to be disabled.
if (!(role == TimingRole::wire()
&& sdc_->clkDisabledByHpinThru(clk, from_pin, to_pin))
// Generated clock source pins have arrivals for the source clock.
// Do not propagate them past the generated clock source pin.
&& !(clks
&& !clks->hasKey(const_cast<Clock*>(from_tag->clock())))) {
// Propagate arrival as non-clock at the end of the clock tree.
bool to_propagates_clk =
!sdc_->clkStopPropagation(clk,from_pin,from_rf,to_pin,to_rf)
&& (variables_->clkThruTristateEnabled()
|| !(role == TimingRole::tristateEnable()
|| role == TimingRole::tristateDisable()));
arc_delay = search_->deratedDelay(from_vertex, arc, edge,
to_propagates_clk, path_ap);
const PathAnalysisPt *path_ap_opp =
path_ap->corner()->findPathAnalysisPt(min_max->opposite());
Delay arc_delay_opp = search_->deratedDelay(from_vertex, arc, edge,
to_propagates_clk, path_ap_opp);
bool arc_delay_min_max_eq =
fuzzyEqual(delayAsFloat(arc_delay), delayAsFloat(arc_delay_opp));
to_tag = search_->thruClkTag(from_path, from_vertex, from_tag,
to_propagates_clk, edge, to_rf,
arc_delay_min_max_eq,
min_max, path_ap);
to_arrival = from_arrival + arc_delay;
}
}
else {
if (!(sdc_->isPathDelayInternalFromBreak(to_pin)
|| sdc_->isPathDelayInternalToBreak(from_pin))) {
to_tag = search_->thruTag(from_tag, edge, to_rf, min_max, path_ap, tag_cache_);
arc_delay = search_->deratedDelay(from_vertex, arc, edge, false, path_ap);
if (!delayInf(arc_delay))
to_arrival = from_arrival + arc_delay;
}
}
if (to_tag)
return visitFromToPath(from_pin, from_vertex, from_rf,
from_tag, from_path, from_arrival,
edge, arc, arc_delay,
to_vertex, to_rf, to_tag, to_arrival,
min_max, path_ap);
else
return true;
}
Arrival
Search::clkPathArrival(const Path *clk_path) const
{
const ClkInfo *clk_info = clk_path->clkInfo(this);
const ClockEdge *clk_edge = clk_info->clkEdge();
const PathAnalysisPt *path_ap = clk_path->pathAnalysisPt(this);
const MinMax *min_max = path_ap->pathMinMax();
return clkPathArrival(clk_path, clk_info, clk_edge, min_max, path_ap);
}
Arrival
Search::clkPathArrival(const Path *clk_path,
const ClkInfo *clk_info,
const ClockEdge *clk_edge,
const MinMax *min_max,
const PathAnalysisPt *path_ap) const
{
if (clk_path->vertex(this)->isRegClk()
&& clk_path->isClock(this)
&& clk_edge
&& !clk_info->isPropagated()) {
// Ideal clock, apply ideal insertion delay and latency.
const EarlyLate *early_late = min_max;
return clk_edge->time()
+ clockInsertion(clk_edge->clock(),
clk_info->clkSrc(),
clk_edge->transition(),
min_max, early_late, path_ap)
+ clk_info->latency();
}
else
return clk_path->arrival();
}
Arrival
Search::pathClkPathArrival(const Path *path) const
{
const ClkInfo *clk_info = path->clkInfo(this);
if (clk_info->isPropagated()) {
const Path *src_clk_path = pathClkPathArrival1(path);
if (src_clk_path)
return clkPathArrival(src_clk_path);
}
// Check for input arrival clock.
const ClockEdge *clk_edge = path->clkEdge(this);
if (clk_edge)
return clk_edge->time() + clk_info->latency();
return 0.0;
}
// PathExpanded::expand() and PathExpanded::clkPath().
const Path *
Search::pathClkPathArrival1(const Path *path) const
{
const Path *p = path;
while (p) {
Path *prev_path = p->prevPath();
Edge *prev_edge = p->prevEdge(this);
if (p->isClock(this))
return p;
if (prev_edge) {
const TimingRole *prev_role = prev_edge->role();
if (prev_role == TimingRole::regClkToQ()
|| prev_role == TimingRole::latchEnToQ()) {
return p->prevPath();
}
else if (prev_role == TimingRole::latchDtoQ()) {
Path *enable_path = latches_->latchEnablePath(p, prev_edge);
return enable_path;
}
}
p = prev_path;
}
return nullptr;
}
////////////////////////////////////////////////////////////////
// Find tag for a path starting with pin/clk_edge.
// Return nullptr if a false path starts at pin/clk_edge.
Tag *
Search::fromUnclkedInputTag(const Pin *pin,
const RiseFall *rf,
const MinMax *min_max,
const PathAnalysisPt *path_ap,
bool is_segment_start,
bool require_exception)
{
ExceptionStateSet *states = nullptr;
if (sdc_->exceptionFromStates(pin, rf, nullptr, nullptr, min_max, states)
&& (!require_exception || states)) {
const ClkInfo *clk_info = findClkInfo(nullptr, nullptr, false, 0.0, path_ap);
return findTag(rf, path_ap, clk_info, false, nullptr,
is_segment_start, states, true, nullptr);
}
return nullptr;
}
Tag *
Search::fromRegClkTag(const Pin *from_pin,
const RiseFall *from_rf,
const Clock *clk,
const RiseFall *clk_rf,
const ClkInfo *clk_info,
const Pin *to_pin,
const RiseFall *to_rf,
const MinMax *min_max,
const PathAnalysisPt *path_ap)
{
ExceptionStateSet *states = nullptr;
if (sdc_->exceptionFromStates(from_pin, from_rf, clk, clk_rf,
min_max, states)) {
// Hack for filter -from reg/Q.
sdc_->filterRegQStates(to_pin, to_rf, min_max, states);
return findTag(to_rf, path_ap, clk_info, false, nullptr, false, states,
true, nullptr);
}
else
return nullptr;
}
// Insert from_path as ClkInfo crpr_clk_path.
const ClkInfo *
Search::clkInfoWithCrprClkPath(const ClkInfo *from_clk_info,
Path *from_path,
const PathAnalysisPt *path_ap)
{
if (crprActive())
return findClkInfo(from_clk_info->clkEdge(),
from_clk_info->clkSrc(),
from_clk_info->isPropagated(),
from_clk_info->genClkSrc(),
from_clk_info->isGenClkSrcPath(),
from_clk_info->pulseClkSense(),
from_clk_info->insertion(),
from_clk_info->latency(),
from_clk_info->uncertainties(),
path_ap, from_path);
else
return from_clk_info;
}
// Find tag for a path starting with from_tag going thru edge.
// Return nullptr if the result tag completes a false path.
Tag *
Search::thruTag(Tag *from_tag,
Edge *edge,
const RiseFall *to_rf,
const MinMax *min_max,
const PathAnalysisPt *path_ap,
TagSet *tag_cache)
{
const Pin *from_pin = edge->from(graph_)->pin();
Vertex *to_vertex = edge->to(graph_);
const Pin *to_pin = to_vertex->pin();
const RiseFall *from_rf = from_tag->transition();
const ClkInfo *from_clk_info = from_tag->clkInfo();
bool to_is_reg_clk = to_vertex->isRegClk();
Tag *to_tag = mutateTag(from_tag, from_pin, from_rf, false, from_clk_info,
to_pin, to_rf, false, to_is_reg_clk, false,
// input delay is not propagated.
from_clk_info, nullptr, min_max, path_ap,
tag_cache);
return to_tag;
}
// thruTag for clocks.
Tag *
Search::thruClkTag(Path *from_path,
Vertex *from_vertex,
Tag *from_tag,
bool to_propagates_clk,
Edge *edge,
const RiseFall *to_rf,
bool arc_delay_min_max_eq,
const MinMax *min_max,
const PathAnalysisPt *path_ap)
{
const Pin *from_pin = edge->from(graph_)->pin();
Vertex *to_vertex = edge->to(graph_);
const Pin *to_pin = to_vertex->pin();
const RiseFall *from_rf = from_tag->transition();
const ClkInfo *from_clk_info = from_tag->clkInfo();
bool from_is_clk = from_tag->isClock();
bool to_is_reg_clk = to_vertex->isRegClk();
const TimingRole *role = edge->role();
bool to_is_clk = (from_is_clk
&& to_propagates_clk
&& (role->isWire()
|| role == TimingRole::combinational()));
const ClkInfo *to_clk_info = thruClkInfo(from_path, from_vertex,
from_clk_info, from_is_clk,
edge, to_vertex, to_pin, to_is_clk,
arc_delay_min_max_eq, min_max, path_ap);
Tag *to_tag = mutateTag(from_tag,from_pin,from_rf,from_is_clk,from_clk_info,
to_pin, to_rf, to_is_clk, to_is_reg_clk, false,
to_clk_info, nullptr, min_max, path_ap, nullptr);
return to_tag;
}
const ClkInfo *
Search::thruClkInfo(Path *from_path,
Vertex *from_vertex,
const ClkInfo *from_clk_info,
bool from_is_clk,
Edge *edge,
Vertex *to_vertex,
const Pin *to_pin,
bool to_is_clk,
bool arc_delay_min_max_eq,
const MinMax *min_max,
const PathAnalysisPt *path_ap)
{
bool changed = false;
const ClockEdge *from_clk_edge = from_clk_info->clkEdge();
const RiseFall *clk_rf = from_clk_edge->transition();
bool from_clk_prop = from_clk_info->isPropagated();
bool to_clk_prop = from_clk_prop;
if (!from_clk_prop
&& sdc_->isPropagatedClock(to_pin)) {
to_clk_prop = true;
changed = true;
}
// Distinguish gen clk src path ClkInfo at generated clock roots,
// so that generated clock crpr info can be (later) safely set on
// the clkinfo.
const Pin *gen_clk_src = nullptr;
if (from_clk_info->isGenClkSrcPath()
&& crprActive()
&& sdc_->isClock(to_pin)) {
// Don't care that it could be a regular clock root.
gen_clk_src = to_pin;
changed = true;
}
Path *to_crpr_clk_path = nullptr;
if (crprActive()
// Update crpr clk path for combinational paths leaving the clock
// network (ie, tristate en->out) and buffer driving reg clk.
&& ((from_is_clk
&& !to_is_clk
&& !from_vertex->isRegClk())
|| (to_vertex->isRegClk()
// If the wire delay to the reg clk pin is zero,
// leave the crpr_clk_path null to indicate that
// the reg clk path is the crpr clk path.
&& arc_delay_min_max_eq))) {
to_crpr_clk_path = from_path;
changed = true;
}
// Propagate liberty "pulse_clock" transition to transitive fanout.
const RiseFall *from_pulse_sense = from_clk_info->pulseClkSense();
const RiseFall *to_pulse_sense = from_pulse_sense;
LibertyPort *port = network_->libertyPort(to_pin);
if (port && port->pulseClkSense()) {
to_pulse_sense = port->pulseClkSense();
changed = true;
}
else if (from_pulse_sense &&
edge->timingArcSet()->sense() == TimingSense::negative_unate) {
to_pulse_sense = from_pulse_sense->opposite();
changed = true;
}
const Clock *from_clk = from_clk_info->clock();
Arrival to_insertion = from_clk_info->insertion();
float to_latency = from_clk_info->latency();
float latency;
bool exists;
sdc_->clockLatency(from_clk, to_pin, clk_rf, min_max,
latency, exists);
if (exists) {
// Latency on pin has precedence over fanin or hierarchical
// pin latency.
to_latency = latency;
to_clk_prop = false;
changed = true;
}
else {
// Check for hierarchical pin latency thru edge.
sdc_->clockLatency(edge, clk_rf, min_max,
latency, exists);
if (exists) {
to_latency = latency;
to_clk_prop = false;
changed = true;
}
}
ClockUncertainties *to_uncertainties = from_clk_info->uncertainties();
ClockUncertainties *uncertainties = sdc_->clockUncertainties(to_pin);
if (uncertainties) {
to_uncertainties = uncertainties;
changed = true;
}
if (changed) {
const ClkInfo *to_clk_info = findClkInfo(from_clk_edge,
from_clk_info->clkSrc(),
to_clk_prop, gen_clk_src,
from_clk_info->isGenClkSrcPath(),
to_pulse_sense, to_insertion, to_latency,
to_uncertainties, path_ap,
to_crpr_clk_path);
return to_clk_info;
}
return from_clk_info;
}
// Find the tag for a path going from from_tag thru edge to to_pin.
Tag *
Search::mutateTag(Tag *from_tag,
const Pin *from_pin,
const RiseFall *from_rf,
bool from_is_clk,
const ClkInfo *from_clk_info,
const Pin *to_pin,
const RiseFall *to_rf,
bool to_is_clk,
bool to_is_reg_clk,
bool to_is_segment_start,
const ClkInfo *to_clk_info,
InputDelay *to_input_delay,
const MinMax *min_max,
const PathAnalysisPt *path_ap,
TagSet *tag_cache)
{
ExceptionStateSet *new_states = nullptr;
ExceptionStateSet *from_states = from_tag->states();
if (from_states) {
// Check for state changes in from_tag (but postpone copying state set).
bool state_change = false;
for (ExceptionState *state : *from_states) {
ExceptionPath *exception = state->exception();
// One edge may traverse multiple hierarchical thru pins.
while (state->matchesNextThru(from_pin,to_pin,to_rf,min_max,network_)) {
// Found a -thru that we've been waiting for.
state = state->nextState();
state_change = true;
break;
}
if (state_change)
break;
// Don't propagate a completed false path -thru unless it is a
// clock. Clocks carry the completed false path to disable
// downstream paths that use the clock as data.
if ((state->isComplete()
&& exception->isFalse()
&& !from_is_clk)
// to_pin/edge completes a loop path.
|| (exception->isLoop()
&& state->isComplete()))
return nullptr;
// Kill path delay tags past the -to pin.
if ((exception->isPathDelay()
&& sdc_->isCompleteTo(state, to_pin, to_rf, min_max))
// Kill loop tags at register clock pins.
|| (exception->isLoop()
&& to_is_reg_clk)) {
state_change = true;
break;
}
}
// Get the set of -thru exceptions starting at to_pin/edge.
sdc_->exceptionThruStates(from_pin, to_pin, to_rf, min_max, new_states);
if (new_states || state_change) {
// Second pass to apply state changes and add updated existing
// states to new states.
if (new_states == nullptr)
new_states = new ExceptionStateSet();
for (auto state : *from_states) {
ExceptionPath *exception = state->exception();
// One edge may traverse multiple hierarchical thru pins.
while (state->matchesNextThru(from_pin,to_pin,to_rf,min_max,network_))
// Found a -thru that we've been waiting for.
state = state->nextState();
// Don't propagate a completed false path -thru unless it is a
// clock. Clocks carry the completed false path to disable
// downstream paths that use the clock as data.
if ((state->isComplete()
&& exception->isFalse()
&& !from_is_clk)
// to_pin/edge completes a loop path.
|| (exception->isLoop()
&& state->isComplete())) {
delete new_states;
return nullptr;
}
// Kill path delay tags past the -to pin.
if (!((exception->isPathDelay()
&& sdc_->isCompleteTo(state, from_pin, from_rf, min_max))
// Kill loop tags at register clock pins.
|| (to_is_reg_clk
&& exception->isLoop())))
new_states->insert(state);
}
}
}
else
// Get the set of -thru exceptions starting at to_pin/edge.
sdc_->exceptionThruStates(from_pin, to_pin, to_rf, min_max, new_states);
if (new_states)
return findTag(to_rf, path_ap, to_clk_info, to_is_clk,
from_tag->inputDelay(), to_is_segment_start, new_states,
true, tag_cache);
else {
// No state change.
if (to_clk_info == from_clk_info
&& to_rf == from_rf
&& to_is_clk == from_is_clk
&& from_tag->isSegmentStart() == to_is_segment_start
&& from_tag->inputDelay() == to_input_delay)
return from_tag;
else
return findTag(to_rf, path_ap, to_clk_info, to_is_clk, to_input_delay,
to_is_segment_start, from_states, false, tag_cache);
}
}
TagGroup *
Search::findTagGroup(TagGroupBldr *tag_bldr)
{
TagGroup probe(tag_bldr, this);
LockGuard lock(tag_group_lock_);
TagGroup *tag_group = tag_group_set_->findKey(&probe);
if (tag_group == nullptr) {
TagGroupIndex tag_group_index;
if (tag_group_free_indices_.empty())
tag_group_index = tag_group_next_++;
else {
tag_group_index = tag_group_free_indices_.back();
tag_group_free_indices_.pop_back();
}
tag_group = tag_bldr->makeTagGroup(tag_group_index, this);
tag_groups_[tag_group_index] = tag_group;
tag_group_set_->insert(tag_group);
// If tag_groups_ needs to grow make the new array and copy the
// contents into it before updating tags_groups_ so that other threads
// can use Search::tagGroup(TagGroupIndex) without returning gubbish.
if (tag_group_next_ == tag_group_capacity_) {
TagGroupIndex tag_capacity = tag_group_capacity_ * 2;
TagGroup **tag_groups = new TagGroup*[tag_capacity];
memcpy(tag_groups, tag_groups_,
tag_group_capacity_ * sizeof(TagGroup*));
tag_groups_prev_.push_back(tag_groups_);
tag_groups_ = tag_groups;
tag_group_capacity_ = tag_capacity;
tag_group_set_->reserve(tag_capacity);
}
if (tag_group_next_ > tag_group_index_max)
report_->critical(1510, "max tag group index exceeded");
}
return tag_group;
}
void
Search::setVertexArrivals(Vertex *vertex,
TagGroupBldr *tag_bldr)
{
if (tag_bldr->empty())
deletePathsIncr(vertex);
else {
TagGroup *prev_tag_group = tagGroup(vertex);
Path *prev_paths = graph_->paths(vertex);
TagGroup *tag_group = findTagGroup(tag_bldr);
if (tag_group == prev_tag_group) {
tag_bldr->copyPaths(tag_group, prev_paths);
requiredInvalid(vertex);
}
else {
if (prev_tag_group) {
graph_->deletePaths(vertex);
prev_tag_group->decrRefCount();
requiredInvalid(vertex);
}
size_t path_count = tag_group->pathCount();
Path *paths = graph_->makePaths(vertex, path_count);
tag_bldr->copyPaths(tag_group, paths);
vertex->setTagGroupIndex(tag_group->index());
tag_group->incrRefCount();
}
if (tag_group->hasFilterTag()) {
LockGuard lock(filtered_arrivals_lock_);
filtered_arrivals_->insert(vertex);
}
}
}
void
Search::checkPrevPaths() const
{
VertexIterator vertex_iter(graph_);
while (vertex_iter.hasNext()) {
Vertex *vertex = vertex_iter.next();
VertexPathIterator path_iter(vertex, graph_);
while (path_iter.hasNext()) {
Path *path = path_iter.next();
path->checkPrevPath(this);
}
}
}
class ReportPathLess
{
public:
ReportPathLess(const StaState *sta);
bool operator()(const Path *path1,
const Path *path2) const;
private:
const StaState *sta_;
};
ReportPathLess::ReportPathLess(const StaState *sta) :
sta_(sta)
{
}
bool
ReportPathLess::operator()(const Path *path1,
const Path *path2) const
{
return Tag::cmp(path1->tag(sta_), path2->tag(sta_), sta_) < 0;
}
void
Search::reportArrivals(Vertex *vertex,
bool report_tag_index) const
{
report_->reportLine("Vertex %s", vertex->to_string(this).c_str());
TagGroup *tag_group = tagGroup(vertex);
if (tag_group) {
if (report_tag_index)
report_->reportLine("Group %u", tag_group->index());
std::vector<const Path*> paths;
VertexPathIterator path_iter(vertex, this);
while (path_iter.hasNext()) {
const Path *path = path_iter.next();
paths.push_back(path);
}
sort(paths.begin(), paths.end(), ReportPathLess(this));
for (const Path *path : paths) {
const Tag *tag = path->tag(this);
const PathAnalysisPt *path_ap = tag->pathAnalysisPt(this);
const RiseFall *rf = tag->transition();
const char *req = delayAsString(path->required(), this);
std::string prev_str;
Path *prev_path = path->prevPath();
if (prev_path) {
prev_str += prev_path->to_string(this);
prev_str += " ";
const Edge *prev_edge = path->prevEdge(this);
TimingArc *arc = path->prevArc(this);
prev_str += prev_edge->from(graph_)->to_string(this);
prev_str += " ";
prev_str += arc->fromEdge()->to_string();
prev_str += " -> ";
prev_str += prev_edge->to(graph_)->to_string(this);
prev_str += " ";
prev_str += arc->toEdge()->to_string();
}
else
prev_str = "NULL";
report_->reportLine(" %s %s %s / %s %s prev %s",
rf->to_string().c_str(),
path_ap->pathMinMax()->to_string().c_str(),
delayAsString(path->arrival(), this),
req,
tag->to_string(report_tag_index, false, this).c_str(),
prev_str.c_str());
}
}
else
report_->reportLine(" no arrivals");
}
TagGroup *
Search::tagGroup(TagGroupIndex index) const
{
return tag_groups_[index];
}
TagGroup *
Search::tagGroup(const Vertex *vertex) const
{
TagGroupIndex index = vertex->tagGroupIndex();
if (index == tag_group_index_max)
return nullptr;
else
return tag_groups_[index];
}
TagGroupIndex
Search::tagGroupCount() const
{
return tag_group_set_->size();
}
void
Search::reportTagGroups() const
{
for (TagGroupIndex i = 0; i < tag_group_next_; i++) {
TagGroup *tag_group = tag_groups_[i];
if (tag_group) {
report_->reportLine("Group %4u hash = %4lu (%4lu)",
i,
tag_group->hash(),
tag_group->hash() % tag_group_set_->bucket_count());
tag_group->reportArrivalMap(this);
}
}
size_t long_hash = 0;
for (size_t i = 0; i < tag_group_set_->bucket_count(); i++) {
if (tag_group_set_->bucket_size(i) > long_hash)
long_hash = i;
}
report_->reportLine("Longest hash bucket length %zu hash=%zu",
tag_group_set_->bucket_size(long_hash),
long_hash);
}
void
Search::reportPathCountHistogram() const
{
Vector<int> vertex_counts(10);
VertexIterator vertex_iter(graph_);
while (vertex_iter.hasNext()) {
Vertex *vertex = vertex_iter.next();
TagGroup *tag_group = tagGroup(vertex);
if (tag_group) {
size_t path_count = tag_group->pathCount();
if (path_count >= vertex_counts.size())
vertex_counts.resize(path_count * 2);
vertex_counts[path_count]++;
}
}
for (size_t path_count = 0; path_count < vertex_counts.size(); path_count++) {
int vertex_count = vertex_counts[path_count];
if (vertex_count > 0)
report_->reportLine("%6lu %6d",path_count, vertex_count);
}
}
////////////////////////////////////////////////////////////////
Tag *
Search::tag(TagIndex index) const
{
return tags_[index];
}
TagIndex
Search::tagCount() const
{
return tag_set_->size();
}
Tag *
Search::findTag(const RiseFall *rf,
const PathAnalysisPt *path_ap,
const ClkInfo *clk_info,
bool is_clk,
InputDelay *input_delay,
bool is_segment_start,
ExceptionStateSet *states,
bool own_states,
TagSet *tag_cache)
{
Tag probe(0, rf->index(), path_ap->index(), clk_info, is_clk, input_delay,
is_segment_start, states, false, this);
if (tag_cache) {
Tag *tag = tag_cache->findKey(&probe);
if (tag)
return tag;
}
LockGuard lock(tag_lock_);
Tag *tag = tag_set_->findKey(&probe);
if (tag == nullptr) {
ExceptionStateSet *new_states = !own_states && states
? new ExceptionStateSet(*states) : states;
TagIndex tag_index;
if (tag_free_indices_.empty())
tag_index = tag_next_++;
else {
tag_index = tag_free_indices_.back();
tag_free_indices_.pop_back();
}
tag = new Tag(tag_index, rf->index(), path_ap->index(),
clk_info, is_clk, input_delay, is_segment_start,
new_states, true, this);
own_states = false;
// Make sure tag can be indexed in tags_ before it is visible to
// other threads via tag_set_.
tags_[tag_index] = tag;
tag_set_->insert(tag);
// If tags_ needs to grow make the new array and copy the
// contents into it before updating tags_ so that other threads
// can use Search::tag(TagIndex) without returning gubbish.
if (tag_next_ == tag_capacity_) {
TagIndex tag_capacity = tag_capacity_ * 2;
Tag **tags = new Tag*[tag_capacity];
memcpy(tags, tags_, tag_capacity_ * sizeof(Tag*));
tags_prev_.push_back(tags_);
tags_ = tags;
tag_capacity_ = tag_capacity;
tag_set_->reserve(tag_capacity);
}
if (tag_next_ == tag_index_max)
report_->critical(1511, "max tag index exceeded");
}
if (own_states)
delete states;
if (tag_cache)
tag_cache->insert(tag);
return tag;
}
void
Search::reportTags() const
{
for (TagIndex i = 0; i < tag_next_; i++) {
Tag *tag = tags_[i];
if (tag)
report_->reportLine("%s", tag->to_string(this).c_str()) ;
}
size_t long_hash = 0;
for (size_t i = 0; i < tag_set_->bucket_count(); i++) {
if (tag_set_->bucket_size(i) > long_hash)
long_hash = i;
}
report_->reportLine("Longest hash bucket length %zu hash=%zu",
tag_set_->bucket_size(long_hash),
long_hash);
}
void
Search::reportClkInfos() const
{
Vector<const ClkInfo*> clk_infos;
// set -> vector for sorting.
for (const ClkInfo *clk_info : *clk_info_set_)
clk_infos.push_back(clk_info);
sort(clk_infos, ClkInfoLess(this));
for (const ClkInfo *clk_info : clk_infos)
report_->reportLine("%s", clk_info->to_string(this).c_str());
report_->reportLine("%zu clk infos", clk_info_set_->size());
}
const ClkInfo *
Search::findClkInfo(const ClockEdge *clk_edge,
const Pin *clk_src,
bool is_propagated,
const Pin *gen_clk_src,
bool gen_clk_src_path,
const RiseFall *pulse_clk_sense,
Arrival insertion,
float latency,
ClockUncertainties *uncertainties,
const PathAnalysisPt *path_ap,
Path *crpr_clk_path)
{
ClkInfo probe(clk_edge, clk_src, is_propagated, gen_clk_src, gen_clk_src_path,
pulse_clk_sense, insertion, latency, uncertainties,
path_ap->index(), crpr_clk_path, this);
LockGuard lock(clk_info_lock_);
const ClkInfo *clk_info = clk_info_set_->findKey(&probe);
if (clk_info == nullptr) {
clk_info = new ClkInfo(clk_edge, clk_src,
is_propagated, gen_clk_src, gen_clk_src_path,
pulse_clk_sense, insertion, latency, uncertainties,
path_ap->index(), crpr_clk_path, this);
clk_info_set_->insert(clk_info);
}
return clk_info;
}
const ClkInfo *
Search::findClkInfo(const ClockEdge *clk_edge,
const Pin *clk_src,
bool is_propagated,
Arrival insertion,
const PathAnalysisPt *path_ap)
{
return findClkInfo(clk_edge, clk_src, is_propagated, nullptr, false, nullptr,
insertion, 0.0, nullptr, path_ap, nullptr);
}
int
Search::clkInfoCount() const
{
return clk_info_set_->size();
}
ArcDelay
Search::deratedDelay(const Vertex *from_vertex,
const TimingArc *arc,
const Edge *edge,
bool is_clk,
const PathAnalysisPt *path_ap)
{
const DcalcAnalysisPt *dcalc_ap = path_ap->dcalcAnalysisPt();
DcalcAPIndex ap_index = dcalc_ap->index();
float derate = timingDerate(from_vertex, arc, edge, is_clk, path_ap);
ArcDelay delay = graph_->arcDelay(edge, arc, ap_index);
return delay * derate;
}
float
Search::timingDerate(const Vertex *from_vertex,
const TimingArc *arc,
const Edge *edge,
bool is_clk,
const PathAnalysisPt *path_ap)
{
PathClkOrData derate_clk_data =
is_clk ? PathClkOrData::clk : PathClkOrData::data;
const TimingRole *role = edge->role();
const Pin *pin = from_vertex->pin();
if (role->isWire()) {
const RiseFall *rf = arc->toEdge()->asRiseFall();
return sdc_->timingDerateNet(pin, derate_clk_data, rf,
path_ap->pathMinMax());
}
else {
TimingDerateCellType derate_type;
const RiseFall *rf;
if (role->isTimingCheck()) {
derate_type = TimingDerateCellType::cell_check;
rf = arc->toEdge()->asRiseFall();
}
else {
derate_type = TimingDerateCellType::cell_delay;
rf = arc->fromEdge()->asRiseFall();
}
return sdc_->timingDerateInstance(pin, derate_type, derate_clk_data, rf,
path_ap->pathMinMax());
}
}
ClockSet
Search::clockDomains(const Vertex *vertex) const
{
ClockSet clks;
clockDomains(vertex, clks);
return clks;
}
void
Search::clockDomains(const Vertex *vertex,
// Return value.
ClockSet &clks) const
{
VertexPathIterator path_iter(const_cast<Vertex*>(vertex), this);
while (path_iter.hasNext()) {
Path *path = path_iter.next();
const Clock *clk = path->clock(this);
if (clk)
clks.insert(const_cast<Clock *>(clk));
}
}
ClockSet
Search::clockDomains(const Pin *pin) const
{
ClockSet clks;
Vertex *vertex;
Vertex *bidirect_drvr_vertex;
graph_->pinVertices(pin, vertex, bidirect_drvr_vertex);
if (vertex)
clockDomains(vertex, clks);
if (bidirect_drvr_vertex)
clockDomains(bidirect_drvr_vertex, clks);
return clks;
}
ClockSet
Search::clocks(const Vertex *vertex) const
{
ClockSet clks;
clocks(vertex, clks);
return clks;
}
void
Search::clocks(const Vertex *vertex,
// Return value.
ClockSet &clks) const
{
VertexPathIterator path_iter(const_cast<Vertex*>(vertex), this);
while (path_iter.hasNext()) {
Path *path = path_iter.next();
if (path->isClock(this))
clks.insert(const_cast<Clock *>(path->clock(this)));
}
}
ClockSet
Search::clocks(const Pin *pin) const
{
ClockSet clks;
Vertex *vertex;
Vertex *bidirect_drvr_vertex;
graph_->pinVertices(pin, vertex, bidirect_drvr_vertex);
if (vertex)
clocks(vertex, clks);
if (bidirect_drvr_vertex)
clocks(bidirect_drvr_vertex, clks);
return clks;
}
bool
Search::isClock(const Vertex *vertex) const
{
TagGroup *tag_group = tagGroup(vertex);
if (tag_group)
return tag_group->hasClkTag();
else
return false;
}
bool
Search::isGenClkSrc(const Vertex *vertex) const
{
TagGroup *tag_group = tagGroup(vertex);
if (tag_group)
return tag_group->hasGenClkSrcTag();
else
return false;
}
////////////////////////////////////////////////////////////////
void
Search::findRequireds()
{
findRequireds(0);
}
void
Search::findRequireds(Level level)
{
Stats stats(debug_, report_);
debugPrint(debug_, "search", 1, "find requireds to level %d", level);
RequiredVisitor req_visitor(this);
if (!requireds_seeded_)
seedRequireds();
seedInvalidRequireds();
int required_count = required_iter_->visitParallel(level, &req_visitor);
deleteTagsPrev();
requireds_exist_ = true;
debugPrint(debug_, "search", 1, "found %d requireds", required_count);
stats.report("Find requireds");
}
void
Search::seedRequireds()
{
ensureDownstreamClkPins();
for (Vertex *vertex : *endpoints())
seedRequired(vertex);
requireds_seeded_ = true;
requireds_exist_ = true;
}
VertexSet *
Search::endpoints()
{
if (endpoints_ == nullptr) {
endpoints_ = new VertexSet(graph_);
invalid_endpoints_ = new VertexSet(graph_);
VertexIterator vertex_iter(graph_);
while (vertex_iter.hasNext()) {
Vertex *vertex = vertex_iter.next();
if (isEndpoint(vertex)) {
debugPrint(debug_, "endpoint", 2, "insert %s",
vertex->to_string(this).c_str());
endpoints_->insert(vertex);
}
}
}
if (invalid_endpoints_) {
for (Vertex *vertex : *invalid_endpoints_) {
if (isEndpoint(vertex)) {
debugPrint(debug_, "endpoint", 2, "insert %s",
vertex->to_string(this).c_str());
endpoints_->insert(vertex);
}
else {
if (debug_->check("endpoint", 2)
&& endpoints_->hasKey(vertex))
report_->reportLine("endpoint: remove %s",
vertex->to_string(this).c_str());
endpoints_->erase(vertex);
}
}
invalid_endpoints_->clear();
}
return endpoints_;
}
void
Search::endpointInvalid(Vertex *vertex)
{
if (invalid_endpoints_) {
debugPrint(debug_, "endpoint", 2, "invalid %s",
vertex->to_string(this).c_str());
invalid_endpoints_->insert(vertex);
}
}
bool
Search::isEndpoint(Vertex *vertex) const
{
return isEndpoint(vertex, search_adj_);
}
bool
Search::isEndpoint(Vertex *vertex,
SearchPred *pred) const
{
Pin *pin = vertex->pin();
return hasFanin(vertex, pred, graph_)
&& ((vertex->hasChecks()
&& hasEnabledChecks(vertex))
|| (variables_->gatedClkChecksEnabled()
&& gated_clk_->isGatedClkEnable(vertex))
|| vertex->isConstrained()
|| sdc_->isPathDelayInternalTo(pin)
|| !hasFanout(vertex, pred, graph_)
// Unconstrained paths at register clk pins.
|| (unconstrained_paths_
&& vertex->isRegClk()));
}
bool
Search::hasEnabledChecks(Vertex *vertex) const
{
VertexInEdgeIterator edge_iter(vertex, graph_);
while (edge_iter.hasNext()) {
Edge *edge = edge_iter.next();
if (visit_path_ends_->checkEdgeEnabled(edge))
return true;
}
return false;
}
void
Search::endpointsInvalid()
{
delete endpoints_;
delete invalid_endpoints_;
endpoints_ = nullptr;
invalid_endpoints_ = nullptr;
}
void
Search::seedInvalidRequireds()
{
for (Vertex *vertex : *invalid_requireds_)
required_iter_->enqueue(vertex);
invalid_requireds_->clear();
}
////////////////////////////////////////////////////////////////
// Visitor used by visitPathEnds to seed end required time.
class FindEndRequiredVisitor : public PathEndVisitor
{
public:
FindEndRequiredVisitor(RequiredCmp *required_cmp,
const StaState *sta);
FindEndRequiredVisitor(const StaState *sta);
virtual ~FindEndRequiredVisitor();
virtual PathEndVisitor *copy() const;
virtual void visit(PathEnd *path_end);
protected:
const StaState *sta_;
RequiredCmp *required_cmp_;
bool own_required_cmp_;
};
FindEndRequiredVisitor::FindEndRequiredVisitor(RequiredCmp *required_cmp,
const StaState *sta) :
sta_(sta),
required_cmp_(required_cmp),
own_required_cmp_(false)
{
}
FindEndRequiredVisitor::FindEndRequiredVisitor(const StaState *sta) :
sta_(sta),
required_cmp_(new RequiredCmp),
own_required_cmp_(true)
{
}
FindEndRequiredVisitor::~FindEndRequiredVisitor()
{
if (own_required_cmp_)
delete required_cmp_;
}
PathEndVisitor *
FindEndRequiredVisitor::copy() const
{
return new FindEndRequiredVisitor(sta_);
}
void
FindEndRequiredVisitor::visit(PathEnd *path_end)
{
if (!path_end->isUnconstrained()) {
Path *path = path_end->path();
const MinMax *min_max = path->minMax(sta_)->opposite();
size_t path_index = path->pathIndex(sta_);
Required required = path_end->requiredTime(sta_);
required_cmp_->requiredSet(path_index, required, min_max, sta_);
}
}
void
Search::seedRequired(Vertex *vertex)
{
debugPrint(debug_, "search", 2, "required seed %s",
vertex->to_string(this).c_str());
RequiredCmp required_cmp;
FindEndRequiredVisitor seeder(&required_cmp, this);
required_cmp.requiredsInit(vertex, this);
visit_path_ends_->visitPathEnds(vertex, &seeder);
// Enqueue fanin vertices for back-propagating required times.
if (required_cmp.requiredsSave(vertex, this))
required_iter_->enqueueAdjacentVertices(vertex);
}
void
Search::seedRequiredEnqueueFanin(Vertex *vertex)
{
RequiredCmp required_cmp;
FindEndRequiredVisitor seeder(&required_cmp, this);
required_cmp.requiredsInit(vertex, this);
visit_path_ends_->visitPathEnds(vertex, &seeder);
// Enqueue fanin vertices for back-propagating required times.
required_cmp.requiredsSave(vertex, this);
required_iter_->enqueueAdjacentVertices(vertex);
}
////////////////////////////////////////////////////////////////
RequiredCmp::RequiredCmp() :
have_requireds_(false)
{
requireds_.reserve(10);
}
void
RequiredCmp::requiredsInit(Vertex *vertex,
const StaState *sta)
{
Search *search = sta->search();
TagGroup *tag_group = search->tagGroup(vertex);
if (tag_group) {
size_t path_count = tag_group->pathCount();
requireds_.resize(path_count);
for (auto const [tag, path_index] : *tag_group->pathIndexMap()) {
PathAnalysisPt *path_ap = tag->pathAnalysisPt(sta);
const MinMax *min_max = path_ap->pathMinMax();
requireds_[path_index] = delayInitValue(min_max->opposite());
}
}
else
requireds_.clear();
have_requireds_ = false;
}
void
RequiredCmp::requiredSet(size_t path_index,
Required &required,
const MinMax *min_max,
const StaState *sta)
{
if (delayGreater(required, requireds_[path_index], min_max, sta)) {
requireds_[path_index] = required;
have_requireds_ = true;
}
}
bool
RequiredCmp::requiredsSave(Vertex *vertex,
const StaState *sta)
{
bool requireds_changed = false;
Debug *debug = sta->debug();
VertexPathIterator path_iter(vertex, sta);
while (path_iter.hasNext()) {
Path *path = path_iter.next();
size_t path_index = path->pathIndex(sta);
Required req = requireds_[path_index];
Required &prev_req = path->required();
bool changed = !delayEqual(prev_req, req);
debugPrint(debug, "search", 3, "required %s save %s -> %s%s",
path->to_string(sta).c_str(),
delayAsString(prev_req, sta),
delayAsString(req, sta),
changed ? " changed" : "");
requireds_changed |= changed;
path->setRequired(req);
}
return requireds_changed;
}
Required
RequiredCmp::required(size_t path_index)
{
return requireds_[path_index];
}
////////////////////////////////////////////////////////////////
RequiredVisitor::RequiredVisitor(const StaState *sta) :
PathVisitor(sta),
required_cmp_(new RequiredCmp),
visit_path_ends_(new VisitPathEnds(sta))
{
}
RequiredVisitor::RequiredVisitor(bool make_tag_cache,
const StaState *sta) :
PathVisitor(sta->search()->evalPred(), make_tag_cache, sta),
required_cmp_(new RequiredCmp),
visit_path_ends_(new VisitPathEnds(sta))
{
}
RequiredVisitor::~RequiredVisitor()
{
delete required_cmp_;
delete visit_path_ends_;
}
VertexVisitor *
RequiredVisitor::copy() const
{
return new RequiredVisitor(true, this);
}
void
RequiredVisitor::visit(Vertex *vertex)
{
debugPrint(debug_, "search", 2, "find required %s",
vertex->to_string(this).c_str());
required_cmp_->requiredsInit(vertex, this);
// Back propagate requireds from fanout.
visitFanoutPaths(vertex);
// Check for constraints at endpoints that set required times.
if (search_->isEndpoint(vertex)) {
FindEndRequiredVisitor seeder(required_cmp_, this);
visit_path_ends_->visitPathEnds(vertex, &seeder);
}
bool changed = required_cmp_->requiredsSave(vertex, this);
search_->tnsInvalid(vertex);
if (changed)
search_->requiredIterator()->enqueueAdjacentVertices(vertex);
}
bool
RequiredVisitor::visitFromToPath(const Pin *,
Vertex * /* from_vertex */,
const RiseFall *from_rf,
Tag *from_tag,
Path *from_path,
const Arrival &,
Edge *edge,
TimingArc *,
ArcDelay arc_delay,
Vertex *to_vertex,
const RiseFall *to_rf,
Tag *to_tag,
Arrival &,
const MinMax *min_max,
const PathAnalysisPt *path_ap)
{
// Don't propagate required times through latch D->Q edges.
if (edge->role() != TimingRole::latchDtoQ()) {
debugPrint(debug_, "search", 3, " %s -> %s %s",
from_rf->to_string().c_str(),
to_rf->to_string().c_str(),
min_max->to_string().c_str());
debugPrint(debug_, "search", 3, " from tag %2u: %s",
from_tag->index(),
from_tag->to_string(this).c_str());
size_t path_index = from_path->pathIndex(this);
const MinMax *req_min = min_max->opposite();
TagGroup *to_tag_group = search_->tagGroup(to_vertex);
// Check to see if to_tag was pruned.
if (to_tag_group && to_tag_group->hasTag(to_tag)) {
size_t to_path_index = to_tag_group->pathIndex(to_tag);
Path &to_path = to_vertex->paths()[to_path_index];
Required &to_required = to_path.required();
Required from_required = to_required - arc_delay;
debugPrint(debug_, "search", 3, " to tag %2u: %s",
to_tag->index(),
to_tag->to_string(this).c_str());
debugPrint(debug_, "search", 3, " %s - %s = %s %s %s",
delayAsString(to_required, this),
delayAsString(arc_delay, this),
delayAsString(from_required, this),
min_max == MinMax::max() ? "<" : ">",
delayAsString(required_cmp_->required(path_index), this));
required_cmp_->requiredSet(path_index, from_required, req_min, this);
}
else {
if (search_->crprApproxMissingRequireds()) {
// Arrival on to_vertex that differs by crpr_pin was pruned.
// Find an arrival that matches everything but the crpr_pin
// as an appromate required.
VertexPathIterator to_iter(to_vertex, to_rf, path_ap, this);
while (to_iter.hasNext()) {
Path *to_path = to_iter.next();
Tag *to_path_tag = to_path->tag(this);
if (Tag::matchNoCrpr(to_path_tag, to_tag)) {
Required to_required = to_path->required();
Required from_required = to_required - arc_delay;
debugPrint(debug_, "search", 3, " to tag %2u: %s",
to_path_tag->index(),
to_path_tag->to_string(this).c_str());
debugPrint(debug_, "search", 3, " %s - %s = %s %s %s",
delayAsString(to_required, this),
delayAsString(arc_delay, this),
delayAsString(from_required, this),
min_max == MinMax::max() ? "<" : ">",
delayAsString(required_cmp_->required(path_index),
this));
required_cmp_->requiredSet(path_index, from_required, req_min, this);
break;
}
}
}
}
}
return true;
}
////////////////////////////////////////////////////////////////
void
Search::ensureDownstreamClkPins()
{
if (!found_downstream_clk_pins_) {
// Use backward BFS from register clk pins to mark upsteam pins
// as having downstream clk pins.
ClkTreeSearchPred pred(this);
BfsBkwdIterator iter(BfsIndex::other, &pred, this);
for (Vertex *vertex : *graph_->regClkVertices()) {
if (!vertex->isConstant())
iter.enqueue(vertex);
}
while (iter.hasNext()) {
Vertex *vertex = iter.next();
vertex->setHasDownstreamClkPin(true);
iter.enqueueAdjacentVertices(vertex);
}
}
found_downstream_clk_pins_ = true;
}
////////////////////////////////////////////////////////////////
bool
Search::matchesFilter(Path *path,
const ClockEdge *to_clk_edge)
{
if (filter_ == nullptr
&& filter_from_ == nullptr
&& filter_to_ == nullptr)
return true;
else if (filter_) {
// -from pins|inst
// -thru
// Path has to be tagged by traversing the filter exception points.
ExceptionStateSet *states = path->tag(this)->states();
if (states) {
for (auto state : *states) {
if (state->exception() == filter_
&& state->nextThru() == nullptr
&& matchesFilterTo(path, to_clk_edge))
return true;
}
}
return false;
}
else if (filter_from_
&& filter_from_->pins() == nullptr
&& filter_from_->instances() == nullptr
&& filter_from_->clks()) {
// -from clks
const ClockEdge *path_clk_edge = path->clkEdge(this);
const Clock *path_clk = path_clk_edge ? path_clk_edge->clock() : nullptr;
const RiseFall *path_clk_rf =
path_clk_edge ? path_clk_edge->transition() : nullptr;
return filter_from_->clks()->hasKey(const_cast<Clock*>(path_clk))
&& filter_from_->transition()->matches(path_clk_rf)
&& matchesFilterTo(path, to_clk_edge);
}
else if (filter_from_ == nullptr
&& filter_to_)
// -to
return matchesFilterTo(path, to_clk_edge);
else {
report_->critical(1512, "unexpected filter path");
return false;
}
}
// Similar to Constraints::exceptionMatchesTo.
bool
Search::matchesFilterTo(Path *path,
const ClockEdge *to_clk_edge) const
{
return (filter_to_ == nullptr
|| filter_to_->matchesFilter(path->pin(graph_), to_clk_edge,
path->transition(this), network_));
}
////////////////////////////////////////////////////////////////
// Find the exception that has the highest priority for an end path,
// including exceptions that start at the end pin or target clock.
ExceptionPath *
Search::exceptionTo(ExceptionPathType type,
const Path *path,
const Pin *pin,
const RiseFall *rf,
const ClockEdge *clk_edge,
const MinMax *min_max,
bool match_min_max_exactly,
bool require_to_pin) const
{
// Find the highest priority exception carried by the path's tag.
int hi_priority = -1;
ExceptionPath *hi_priority_exception = nullptr;
const ExceptionStateSet *states = path->tag(this)->states();
if (states) {
for (auto state : *states) {
ExceptionPath *exception = state->exception();
int priority = exception->priority(min_max);
if ((type == ExceptionPathType::any
|| exception->type() == type)
&& sdc_->isCompleteTo(state, pin, rf, clk_edge, min_max,
match_min_max_exactly, require_to_pin)
&& (hi_priority_exception == nullptr
|| priority > hi_priority
|| (priority == hi_priority
&& exception->tighterThan(hi_priority_exception)))) {
hi_priority = priority;
hi_priority_exception = exception;
}
}
}
// Check for -to exceptions originating at the end pin or target clock.
sdc_->exceptionTo(type, pin, rf, clk_edge, min_max,
match_min_max_exactly,
hi_priority_exception, hi_priority);
return hi_priority_exception;
}
////////////////////////////////////////////////////////////////
// Find group paths that end at the path.
ExceptionPathSeq
Search::groupPathsTo(const PathEnd *path_end) const
{
ExceptionPathSeq group_paths;
const Path *path = path_end->path();
const Pin *pin = path->pin(this);
const Tag *tag = path->tag(this);
const RiseFall *rf = tag->transition();
const ClockEdge *clk_edge = path_end->targetClkEdge(this);
const MinMax *min_max = tag->minMax(this);
const ExceptionStateSet *states = path->tag(this)->states();
if (states) {
for (auto state : *states) {
ExceptionPath *exception = state->exception();
if (exception->isGroupPath()
&& sdc_->exceptionMatchesTo(exception, pin, rf, clk_edge, min_max,
false, false))
group_paths.push_back(exception);
}
}
// Check for group_path -to exceptions originating at the end pin or target clock.
sdc_->groupPathsTo(pin, rf, clk_edge, min_max, group_paths);
return group_paths;
}
////////////////////////////////////////////////////////////////
Slack
Search::totalNegativeSlack(const MinMax *min_max)
{
tnsPreamble();
Slack tns = 0.0;
for (Corner *corner : *corners_) {
PathAPIndex path_ap_index = corner->findPathAnalysisPt(min_max)->index();
Slack tns1 = tns_[path_ap_index];
if (delayLess(tns1, tns, this))
tns = tns1;
}
return tns;
}
Slack
Search::totalNegativeSlack(const Corner *corner,
const MinMax *min_max)
{
tnsPreamble();
PathAPIndex path_ap_index = corner->findPathAnalysisPt(min_max)->index();
return tns_[path_ap_index];
}
void
Search::tnsPreamble()
{
wnsTnsPreamble();
PathAPIndex path_ap_count = corners_->pathAnalysisPtCount();
tns_.resize(path_ap_count);
tns_slacks_.resize(path_ap_count);
if (tns_exists_)
updateInvalidTns();
else
findTotalNegativeSlacks();
}
void
Search::tnsInvalid(Vertex *vertex)
{
if ((tns_exists_ || worst_slacks_)
&& isEndpoint(vertex)) {
debugPrint(debug_, "tns", 2, "tns invalid %s",
vertex->to_string(this).c_str());
LockGuard lock(tns_lock_);
invalid_tns_->insert(vertex);
}
}
void
Search::updateInvalidTns()
{
PathAPIndex path_ap_count = corners_->pathAnalysisPtCount();
for (Vertex *vertex : *invalid_tns_) {
// Network edits can change endpointedness since tnsInvalid was called.
if (isEndpoint(vertex)) {
debugPrint(debug_, "tns", 2, "update tns %s",
vertex->to_string(this).c_str());
SlackSeq slacks(path_ap_count);
wnsSlacks(vertex, slacks);
if (tns_exists_)
updateTns(vertex, slacks);
if (worst_slacks_)
worst_slacks_->updateWorstSlacks(vertex, slacks);
}
}
invalid_tns_->clear();
}
void
Search::findTotalNegativeSlacks()
{
PathAPIndex path_ap_count = corners_->pathAnalysisPtCount();
for (PathAPIndex i = 0; i < path_ap_count; i++) {
tns_[i] = 0.0;
tns_slacks_[i].clear();
}
for (Vertex *vertex : *endpoints()) {
// No locking required.
SlackSeq slacks(path_ap_count);
wnsSlacks(vertex, slacks);
for (PathAPIndex i = 0; i < path_ap_count; i++)
tnsIncr(vertex, slacks[i], i);
}
tns_exists_ = true;
}
void
Search::updateTns(Vertex *vertex,
SlackSeq &slacks)
{
PathAPIndex path_ap_count = corners_->pathAnalysisPtCount();
for (PathAPIndex i = 0; i < path_ap_count; i++) {
tnsDecr(vertex, i);
tnsIncr(vertex, slacks[i], i);
}
}
void
Search::tnsIncr(Vertex *vertex,
Slack slack,
PathAPIndex path_ap_index)
{
if (delayLess(slack, 0.0, this)) {
debugPrint(debug_, "tns", 3, "tns+ %s %s",
delayAsString(slack, this),
vertex->to_string(this).c_str());
tns_[path_ap_index] += slack;
if (tns_slacks_[path_ap_index].hasKey(vertex))
report_->critical(1513, "tns incr existing vertex");
tns_slacks_[path_ap_index][vertex] = slack;
}
}
void
Search::tnsDecr(Vertex *vertex,
PathAPIndex path_ap_index)
{
Slack slack;
bool found;
tns_slacks_[path_ap_index].findKey(vertex, slack, found);
if (found
&& delayLess(slack, 0.0, this)) {
debugPrint(debug_, "tns", 3, "tns- %s %s",
delayAsString(slack, this),
vertex->to_string(this).c_str());
tns_[path_ap_index] -= slack;
tns_slacks_[path_ap_index].erase(vertex);
}
}
// Notify tns before updating/deleting slack (arrival/required).
void
Search::tnsNotifyBefore(Vertex *vertex)
{
if (tns_exists_
&& isEndpoint(vertex)) {
int ap_count = corners_->pathAnalysisPtCount();
for (int i = 0; i < ap_count; i++) {
tnsDecr(vertex, i);
}
}
}
////////////////////////////////////////////////////////////////
void
Search::worstSlack(const MinMax *min_max,
// Return values.
Slack &worst_slack,
Vertex *&worst_vertex)
{
worstSlackPreamble();
worst_slacks_->worstSlack(min_max, worst_slack, worst_vertex);
}
void
Search::worstSlack(const Corner *corner,
const MinMax *min_max,
// Return values.
Slack &worst_slack,
Vertex *&worst_vertex)
{
worstSlackPreamble();
worst_slacks_->worstSlack(corner, min_max, worst_slack, worst_vertex);
}
void
Search::worstSlackPreamble()
{
wnsTnsPreamble();
if (worst_slacks_)
updateInvalidTns();
else
worst_slacks_ = new WorstSlacks(this);
}
void
Search::wnsTnsPreamble()
{
findAllArrivals();
// Required times are only needed at endpoints.
if (requireds_seeded_) {
for (auto itr = invalid_requireds_->begin(); itr != invalid_requireds_->end(); ) {
Vertex *vertex = *itr;
debugPrint(debug_, "search", 2, "tns update required %s",
vertex->to_string(this).c_str());
if (isEndpoint(vertex)) {
seedRequired(vertex);
// If the endpoint has fanout it's required time
// depends on downstream checks, so enqueue it to
// force required propagation to it's level if
// the required time is requested later.
if (hasFanout(vertex, search_adj_, graph_))
required_iter_->enqueue(vertex);
itr = invalid_requireds_->erase(itr);
}
else
itr++;
}
}
else
seedRequireds();
}
void
Search::clearWorstSlack()
{
if (worst_slacks_) {
// Don't maintain incremental worst slacks until there is a request.
delete worst_slacks_;
worst_slacks_ = nullptr;
}
}
////////////////////////////////////////////////////////////////
class FindEndSlackVisitor : public PathEndVisitor
{
public:
FindEndSlackVisitor(SlackSeq &slacks,
const StaState *sta);
FindEndSlackVisitor(const FindEndSlackVisitor &) = default;
virtual PathEndVisitor *copy() const;
virtual void visit(PathEnd *path_end);
protected:
SlackSeq &slacks_;
const StaState *sta_;
};
FindEndSlackVisitor::FindEndSlackVisitor(SlackSeq &slacks,
const StaState *sta) :
slacks_(slacks),
sta_(sta)
{
}
PathEndVisitor *
FindEndSlackVisitor::copy() const
{
return new FindEndSlackVisitor(*this);
}
void
FindEndSlackVisitor::visit(PathEnd *path_end)
{
if (!path_end->isUnconstrained()) {
Path *path = path_end->path();
PathAPIndex path_ap_index = path->pathAnalysisPtIndex(sta_);
Slack slack = path_end->slack(sta_);
if (delayLess(slack, slacks_[path_ap_index], sta_))
slacks_[path_ap_index] = slack;
}
}
void
Search::wnsSlacks(Vertex *vertex,
// Return values.
SlackSeq &slacks)
{
Slack slack_init = MinMax::min()->initValue();
PathAPIndex path_ap_count = corners_->pathAnalysisPtCount();
for (PathAPIndex i = 0; i < path_ap_count; i++)
slacks[i] = slack_init;
if (hasFanout(vertex, search_adj_, graph_)) {
// If the vertex has fanout the path slacks include downstream
// PathEnd slacks so find the endpoint slack directly.
FindEndSlackVisitor end_visitor(slacks, this);
visit_path_ends_->visitPathEnds(vertex, &end_visitor);
}
else {
VertexPathIterator path_iter(vertex, this);
while (path_iter.hasNext()) {
Path *path = path_iter.next();
PathAPIndex path_ap_index = path->pathAnalysisPtIndex(this);
const Slack path_slack = path->slack(this);
if (!path->tag(this)->isFilter()
&& delayLess(path_slack, slacks[path_ap_index], this))
slacks[path_ap_index] = path_slack;
}
}
}
Slack
Search::wnsSlack(Vertex *vertex,
PathAPIndex path_ap_index)
{
PathAPIndex path_ap_count = corners_->pathAnalysisPtCount();
SlackSeq slacks(path_ap_count);
wnsSlacks(vertex, slacks);
return slacks[path_ap_index];
}
////////////////////////////////////////////////////////////////
void
Search::deletePathGroups()
{
delete path_groups_;
path_groups_ = nullptr;
}
PathGroupSeq
Search::pathGroups(const PathEnd *path_end) const
{
if (path_groups_)
return path_groups_->pathGroups(path_end);
else
return PathGroupSeq();
}
bool
Search::havePathGroups() const
{
return path_groups_ != nullptr;
}
PathGroup *
Search::findPathGroup(const char *name,
const MinMax *min_max) const
{
if (path_groups_)
return path_groups_->findPathGroup(name, min_max);
else
return nullptr;
}
PathGroup *
Search::findPathGroup(const Clock *clk,
const MinMax *min_max) const
{
if (path_groups_)
return path_groups_->findPathGroup(clk, min_max);
else
return nullptr;
}
} // namespace