// OpenSTA, Static Timing Analyzer
// Copyright (c) 2026, Parallax Software, Inc.
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <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 "PathEnd.hh"

#include "ClkInfo.hh"
#include "Clock.hh"
#include "DataCheck.hh"
#include "Debug.hh"
#include "ExceptionPath.hh"
#include "Graph.hh"
#include "Latches.hh"
#include "Liberty.hh"
#include "Mode.hh"
#include "Network.hh"
#include "PathExpanded.hh"
#include "PortDelay.hh"
#include "ReportPath.hh"
#include "Sdc.hh"
#include "Search.hh"
#include "Sim.hh"
#include "StaState.hh"
#include "Tag.hh"
#include "TimingArc.hh"
#include "TimingRole.hh"
#include "search/Crpr.hh"

namespace sta {

PathEnd::PathEnd(Path *path) :
  path_(path)
{
}

void
PathEnd::setPath(Path *path)
{
  path_ = path;
}

void
PathEnd::setPathGroup(PathGroup *path_group)
{
  path_group_ = path_group;
}

Vertex *
PathEnd::vertex(const StaState *sta) const
{
  return path_->vertex(sta);
}

const MinMax *
PathEnd::minMax(const StaState *sta) const
{
  return path_->minMax(sta);
}

const EarlyLate *
PathEnd::pathEarlyLate(const StaState *sta) const
{
  return path_->minMax(sta);
}

const EarlyLate *
PathEnd::clkEarlyLate(const StaState *sta) const
{
  return checkRole(sta)->tgtClkEarlyLate();
}

const RiseFall *
PathEnd::transition(const StaState *sta) const
{
  return path_->transition(sta);
}

const ClockEdge *
PathEnd::sourceClkEdge(const StaState *sta) const
{
  return path_->clkEdge(sta);
}

const Arrival &
PathEnd::dataArrivalTime(const StaState *) const
{
  return path_->arrival();
}

Arrival
PathEnd::dataArrivalTimeOffset(const StaState *sta) const
{
  return delaySum(dataArrivalTime(sta),
                  sourceClkOffset(sta),
                  sta);
}

Required
PathEnd::requiredTimeOffset(const StaState *sta) const
{
  return delaySum(requiredTime(sta),
                  sourceClkOffset(sta),
                  sta);
}

const RiseFall *
PathEnd::targetClkEndTrans(const StaState *sta) const
{
  const Path *clk_path = targetClkPath();
  if (clk_path)
    return clk_path->transition(sta);
  else {
    const ClockEdge *clk_edge = targetClkEdge(sta);
    if (clk_edge)
      return clk_edge->transition();
    else
      return nullptr;
  }
}

const TimingRole *
PathEnd::checkGenericRole(const StaState *sta) const
{
  return checkRole(sta)->genericRole();
}

Delay
PathEnd::sourceClkLatency(const StaState *) const
{
  return delay_zero;
}

Delay
PathEnd::sourceClkInsertionDelay(const StaState *) const
{
  return delay_zero;
}

const Clock *
PathEnd::targetClk(const StaState *) const
{
  return nullptr;
}

const ClockEdge *
PathEnd::targetClkEdge(const StaState *) const
{
  return nullptr;
}

float
PathEnd::targetClkTime(const StaState *) const
{
  return 0.0;
}

float
PathEnd::targetClkOffset(const StaState *) const
{
  return 0.0;
}

Arrival
PathEnd::targetClkArrival(const StaState *) const
{
  return delay_zero;
}

Delay
PathEnd::targetClkDelay(const StaState *) const
{
  return delay_zero;
}

Delay
PathEnd::targetClkInsertionDelay(const StaState *) const
{
  return delay_zero;
}

float
PathEnd::targetNonInterClkUncertainty(const StaState *) const
{
  return 0.0;
}

float
PathEnd::interClkUncertainty(const StaState *) const
{
  return 0.0;
}

float
PathEnd::targetClkUncertainty(const StaState *) const
{
  return 0.0;
}

float
PathEnd::targetClkMcpAdjustment(const StaState *) const
{
  return 0.0;
}

const TimingRole *
PathEnd::checkRole(const StaState *) const
{
  return nullptr;
}

Path *
PathEnd::targetClkPath()
{
  return nullptr;
}

const Path *
PathEnd::targetClkPath() const
{
  return nullptr;
}

bool
PathEnd::pathDelayMarginIsExternal() const
{
  return false;
}

PathDelay *
PathEnd::pathDelay() const
{
  return nullptr;
}

Arrival
PathEnd::borrow(const StaState *) const
{
  return 0.0;
}

Crpr
PathEnd::checkCrpr(const StaState *sta) const
{
  if (checkRole(sta)->genericRole() == TimingRole::hold())
    return delayDiff(delay_zero, crpr(sta), sta);
  else
    return crpr(sta);
}

Crpr
PathEnd::crpr(const StaState *) const
{
  return 0.0;
}

MultiCyclePath *
PathEnd::multiCyclePath() const
{
  return nullptr;
}

int
PathEnd::exceptPathCmp(const PathEnd *path_end,
                       const StaState *) const
{
  Type type1 = type();
  Type type2 = path_end->type();
  if (type1 == type2)
    return 0;
  else if (type1 < type2)
    return -1;
  else
    return 1;
}

////////////////////////////////////////////////////////////////

Delay
PathEnd::checkTgtClkDelay(const Path *tgt_clk_path,
                          const ClockEdge *tgt_clk_edge,
                          const TimingRole *check_role,
                          const StaState *sta)
{
  Delay insertion, latency;
  checkTgtClkDelay(tgt_clk_path, tgt_clk_edge, check_role, sta,
                   insertion, latency);
  return delaySum(insertion, latency, sta);
}

void
PathEnd::checkTgtClkDelay(const Path *tgt_clk_path,
                          const ClockEdge *tgt_clk_edge,
                          const TimingRole *check_role,
                          const StaState *sta,
                          // Return values.
                          Delay &insertion,
                          Delay &latency)
{
  if (tgt_clk_path) {
    Search *search = sta->search();
    // If propagated clk, adjust required time for target clk network delay.
    const MinMax *min_max = tgt_clk_path->minMax(sta);
    const EarlyLate *early_late = check_role->tgtClkEarlyLate();
    const ClkInfo *clk_info = tgt_clk_path->clkInfo(sta);
    const Pin *tgt_src_pin = clk_info->clkSrc();
    const Clock *tgt_clk = tgt_clk_edge->clock();
    const RiseFall *tgt_clk_rf = tgt_clk_edge->transition();
    const Mode *mode = tgt_clk_path->mode(sta);
    insertion = search->clockInsertion(tgt_clk, tgt_src_pin, tgt_clk_rf,
                                       min_max, early_late, mode);
    if (clk_info->isPropagated()
        // Data check target clock is always propagated.
        || check_role->isDataCheck()) {
      // Propagated clock.  Propagated arrival is seeded with
      // early_late==path_min_max insertion delay.
      Arrival clk_arrival = tgt_clk_path->arrival();
      Delay path_insertion = search->clockInsertion(tgt_clk, tgt_src_pin,
                                                    tgt_clk_rf, min_max,
                                                    min_max, mode);
      latency = delayRemove(delayDiff(clk_arrival, tgt_clk_edge->time(), sta),
                            path_insertion);
    }
    else
      // Ideal clock.
      latency = clk_info->latency();
  }
  else {
    insertion = 0.0F;
    latency = 0.0F;
  }
}

float
PathEnd::checkClkUncertainty(const ClockEdge *src_clk_edge,
                             const ClockEdge *tgt_clk_edge,
                             const Path *tgt_clk_path,
                             const TimingRole *check_role,
                             const Sdc *sdc)
{

  float inter_clk;
  bool inter_exists;
  checkInterClkUncertainty(src_clk_edge, tgt_clk_edge, check_role, sdc,
                           inter_clk, inter_exists);
  if (inter_exists)
    return inter_clk;
  else
    return checkTgtClkUncertainty(tgt_clk_path, tgt_clk_edge, check_role, sdc);
}

float
PathEnd::checkTgtClkUncertainty(const Path *tgt_clk_path,
                                const ClockEdge *tgt_clk_edge,
                                const TimingRole *check_role,
                                const StaState *sta)
{
  const MinMax *min_max = check_role->pathMinMax();
  const ClockUncertainties *uncertainties = nullptr;
  if (tgt_clk_path && tgt_clk_path->isClock(sta))
    uncertainties = tgt_clk_path->clkInfo(sta)->uncertainties();
  else if (tgt_clk_edge)
    uncertainties = &tgt_clk_edge->clock()->uncertainties();
  float uncertainty = 0.0;
  if (uncertainties) {
    bool exists;
    float uncertainty1;
    uncertainties->value(min_max, uncertainty1, exists);
    if (exists)
      uncertainty = uncertainty1;
  }
  if (check_role->genericRole() == TimingRole::setup())
    uncertainty = -uncertainty;
  return uncertainty;
}

void
PathEnd::checkInterClkUncertainty(const ClockEdge *src_clk_edge,
                                  const ClockEdge *tgt_clk_edge,
                                  const TimingRole *check_role,
                                  const Sdc *sdc,
                                  float &uncertainty,
                                  bool &exists)
{
  if (src_clk_edge
      && src_clk_edge != sdc->defaultArrivalClockEdge()
      && tgt_clk_edge) {
    sdc->clockUncertainty(src_clk_edge->clock(),
                          src_clk_edge->transition(),
                          tgt_clk_edge->clock(),
                          tgt_clk_edge->transition(),
                          check_role->pathMinMax(),
                          uncertainty, exists);
    if (exists
        && check_role->genericRole() == TimingRole::setup())
      uncertainty = -uncertainty;
  }
  else
    exists = false;
}

bool
PathEnd::ignoreClkLatency(const Path *path,
                          PathDelay *path_delay,
                          const StaState *sta)
{
  return path_delay->ignoreClkLatency() && !path->isClock(sta);
}

////////////////////////////////////////////////////////////////

void
PathEndUnconstrained::reportFull(const ReportPath *report) const
{
  report->reportFull(this);
}

Slack
PathEndUnconstrained::slackNoCrpr(const StaState *) const
{
  return INF;
}

void
PathEndUnconstrained::reportShort(const ReportPath *report) const
{
  report->reportShort(this);
}

////////////////////////////////////////////////////////////////

PathEndUnconstrained::PathEndUnconstrained(Path *path) :
  PathEnd(path)
{
}

PathEnd *
PathEndUnconstrained::copy() const
{
  return new PathEndUnconstrained(*this);
}

bool
PathEndUnconstrained::isUnconstrained() const
{
  return true;
}

Required
PathEndUnconstrained::requiredTime(const StaState *sta) const
{
  return delayInitValue(minMax(sta)->opposite());
}

Required
PathEndUnconstrained::requiredTimeOffset(const StaState *sta) const
{
  return delayInitValue(minMax(sta)->opposite());
}

ArcDelay
PathEndUnconstrained::margin(const StaState *) const
{
  return delay_zero;
}

Slack
PathEndUnconstrained::slack(const StaState *) const
{
  return INF;
}


float
PathEndUnconstrained::sourceClkOffset(const StaState *) const
{
  return 0.0;
}

PathEnd::Type
PathEndUnconstrained::type() const
{
  return Type::unconstrained;
}

const char *
PathEndUnconstrained::typeName() const
{
  return "unconstrained";
}

////////////////////////////////////////////////////////////////

PathEndClkConstrained::PathEndClkConstrained(Path *path,
                                             Path *clk_path) :
  PathEnd(path),
  clk_path_(clk_path),
  crpr_(0.0)
{
}

void
PathEndClkConstrained::setPath(Path *path)
{
  path_ = path;
  crpr_valid_ = false;
}

float
PathEndClkConstrained::sourceClkOffset(const StaState *sta) const
{
  return sourceClkOffset(sourceClkEdge(sta),
                         targetClkEdge(sta),
                         checkRole(sta),
                         sta);
}

float
PathEndClkConstrained::sourceClkOffset(const ClockEdge *src_clk_edge,
                                       const ClockEdge *tgt_clk_edge,
                                       const TimingRole *check_role,
                                       const StaState *sta) const
{
  Sdc *sdc = path_->sdc(sta);
  CycleAccting *acct = sdc->cycleAccting(src_clk_edge, tgt_clk_edge);
  return acct->sourceTimeOffset(check_role);
}

Arrival
PathEndClkConstrained::sourceClkLatency(const StaState *sta) const
{
  const ClkInfo *clk_info = path_->clkInfo(sta);
  return clk_info->latency();
}

Arrival
PathEndClkConstrained::sourceClkInsertionDelay(const StaState *sta) const
{
  const ClkInfo *clk_info = path_->clkInfo(sta);
  return clk_info->insertion();
}

Path *
PathEndClkConstrained::targetClkPath()
{
  return clk_path_;
}

const Path *
PathEndClkConstrained::targetClkPath() const
{
  return clk_path_;
}

float
PathEndClkConstrained::targetClkOffset(const StaState *sta) const
{
  const ClockEdge *src_clk_edge = sourceClkEdge(sta);
  const ClockEdge *tgt_clk_edge = targetClkEdge(sta);
  const TimingRole *check_role = checkRole(sta);
  Sdc *sdc = path_->sdc(sta);
  CycleAccting *acct = sdc->cycleAccting(src_clk_edge, tgt_clk_edge);
  return acct->targetTimeOffset(check_role);
}

const ClockEdge *
PathEndClkConstrained::targetClkEdge(const StaState *sta) const
{
  if (clk_path_)
    return clk_path_->clkEdge(sta);
  else
    return nullptr;
}

const Clock *
PathEndClkConstrained::targetClk(const StaState *sta) const
{
  const ClockEdge *clk_edge = targetClkEdge(sta);
  if (clk_edge)
    return clk_edge->clock();
  else
    return nullptr;
}

float
PathEndClkConstrained::targetClkTime(const StaState *sta) const
{
  const ClockEdge *src_clk_edge = sourceClkEdge(sta);
  const ClockEdge *tgt_clk_edge = targetClkEdge(sta);
  const TimingRole *check_role = checkRole(sta);
  Sdc *sdc = path_->sdc(sta);
  CycleAccting *acct = sdc->cycleAccting(src_clk_edge, tgt_clk_edge);
  return acct->requiredTime(check_role);
}

Arrival
PathEndClkConstrained::targetClkArrival(const StaState *sta) const
{
  return delaySum(targetClkArrivalNoCrpr(sta), checkCrpr(sta), sta);
}
 
Arrival
PathEndClkConstrained::targetClkArrivalNoCrpr(const StaState *sta) const
{
  Sdc *sdc = path_->sdc(sta);
  Arrival clk_arrival = delaySum(targetClkDelay(sta),
                                 targetClkTime(sta),
                                 sta);
  float uncertainty = checkClkUncertainty(sourceClkEdge(sta),
                                          targetClkEdge(sta),
                                          targetClkPath(),
                                          checkRole(sta),
                                          sdc);
  return delaySum(delaySum(clk_arrival, uncertainty, sta),
                  targetClkMcpAdjustment(sta), sta);
}

Delay
PathEndClkConstrained::targetClkDelay(const StaState *sta) const
{
  return checkTgtClkDelay(targetClkPath(), targetClkEdge(sta), checkRole(sta), sta);
}

Delay
PathEndClkConstrained::targetClkInsertionDelay(const StaState *sta) const
{
  Arrival insertion, latency;
  checkTgtClkDelay(targetClkPath(), targetClkEdge(sta),
                   checkRole(sta), sta,
                   insertion, latency);
  return insertion;
}

float
PathEndClkConstrained::targetNonInterClkUncertainty(const StaState *sta) const
{
  const ClockEdge *src_clk_edge = sourceClkEdge(sta);
  const ClockEdge *tgt_clk_edge = targetClkEdge(sta);
  const TimingRole *check_role = checkRole(sta);

  Sdc *sdc = path_->sdc(sta);
  float inter_clk;
  bool inter_exists;
  checkInterClkUncertainty(src_clk_edge, tgt_clk_edge, check_role,
                           sdc, inter_clk, inter_exists);
  if (inter_exists)
    // This returns non inter-clock uncertainty.
    return 0.0;
  else
    return checkTgtClkUncertainty(targetClkPath(), tgt_clk_edge, check_role, sta);
}

float
PathEndClkConstrained::interClkUncertainty(const StaState *sta) const
{
  Sdc *sdc = path_->sdc(sta);
  float uncertainty;
  bool exists;
  checkInterClkUncertainty(sourceClkEdge(sta), targetClkEdge(sta),
                           checkRole(sta), sdc,
                           uncertainty, exists);
  if (exists)
    return uncertainty;
  else
    return 0.0;
}

float
PathEndClkConstrained::targetClkUncertainty(const StaState *sta) const
{
  Sdc *sdc = path_->sdc(sta);
  return checkClkUncertainty(sourceClkEdge(sta), targetClkEdge(sta),
                             targetClkPath(), checkRole(sta), sdc);
}

Crpr
PathEndClkConstrained::crpr(const StaState *sta) const
{
  if (!crpr_valid_) {
    CheckCrpr *check_crpr = sta->search()->checkCrpr();
    crpr_ = check_crpr->checkCrpr(path_, targetClkPath());
    crpr_valid_ = true;
  }
  return crpr_;
}

Required
PathEndClkConstrained::requiredTime(const StaState *sta) const
{
  return delaySum(requiredTimeNoCrpr(sta),
                  checkCrpr(sta),
                  sta);
}

Required
PathEndClkConstrained::requiredTimeNoCrpr(const StaState *sta) const
{
  Arrival tgt_clk_arrival = targetClkArrivalNoCrpr(sta);
  ArcDelay check_margin = margin(sta);
  if (checkGenericRole(sta) == TimingRole::setup())
    return delayDiff(tgt_clk_arrival, check_margin, sta);
  else
    return delaySum(tgt_clk_arrival, check_margin, sta);
}

Slack
PathEndClkConstrained::slack(const StaState *sta) const
{
  Arrival arrival = dataArrivalTime(sta);
  Required required = requiredTime(sta);
  if (checkGenericRole(sta) == TimingRole::setup())
    return delayDiff(required, arrival, sta);
  else
    return delayDiff(arrival, required, sta);
}

int
PathEndClkConstrained::exceptPathCmp(const PathEnd *path_end,
                                     const StaState *sta) const
{
  int cmp = PathEnd::exceptPathCmp(path_end, sta);
  if (cmp == 0) {
    const PathEndClkConstrained *path_end2 = 
      dynamic_cast<const PathEndClkConstrained*>(path_end);
    const Path *clk_path2 = path_end2->targetClkPath();
    return Path::cmp(targetClkPath(), clk_path2, sta);
  }
  else
    return cmp;
}

////////////////////////////////////////////////////////////////

PathEndClkConstrainedMcp::PathEndClkConstrainedMcp(Path *path,
                                                   Path *clk_path,
                                                   MultiCyclePath *mcp) :
  PathEndClkConstrained(path, clk_path),
  mcp_(mcp)
{
}

float
PathEndClkConstrainedMcp::targetClkMcpAdjustment(const StaState *sta) const
{
  return checkMcpAdjustment(path_, targetClkEdge(sta), sta);
}

float
PathEndClkConstrainedMcp::checkMcpAdjustment(const Path *path,
                                             const ClockEdge *tgt_clk_edge,
                                             const StaState *sta) const
{
  if (mcp_) {
    const TimingRole *check_role = checkRole(sta);
    const MinMax *min_max = check_role->pathMinMax();
    const ClockEdge *src_clk_edge = path->clkEdge(sta);
    Sdc *sdc = path_->sdc(sta);
    if (min_max == MinMax::max())
      return PathEnd::checkSetupMcpAdjustment(src_clk_edge, tgt_clk_edge,
                                              mcp_, setupDefaultCycles(), sdc);
    else {
      // Hold check.
      // Default arrival clock is a proxy for the target clock.
      if (src_clk_edge == nullptr)
        src_clk_edge = tgt_clk_edge;
      else if (src_clk_edge->clock() == sdc->defaultArrivalClock())
        src_clk_edge = tgt_clk_edge->clock()->edge(src_clk_edge->transition());

      const MultiCyclePath *setup_mcp;
      const MultiCyclePath *hold_mcp;
      // Hold checks also need the setup mcp for cycle accounting.
      findHoldMcps(tgt_clk_edge, setup_mcp, hold_mcp, sta);
      if (setup_mcp && hold_mcp) {
        int setup_mult = setup_mcp->pathMultiplier(MinMax::max());
        int hold_mult = hold_mcp->pathMultiplier(MinMax::min());
        const ClockEdge *setup_clk_edge =
          setup_mcp->useEndClk() ? tgt_clk_edge : src_clk_edge;
        float setup_period = setup_clk_edge->clock()->period();
        const ClockEdge *hold_clk_edge =
          hold_mcp->useEndClk() ? tgt_clk_edge : src_clk_edge;
        float hold_period = hold_clk_edge->clock()->period();
        return (setup_mult - 1) * setup_period - hold_mult * hold_period;
      }
      else if (hold_mcp) {
        int mult = hold_mcp->pathMultiplier(min_max);
        const ClockEdge *clk_edge =
          hold_mcp->useEndClk() ? tgt_clk_edge : src_clk_edge;
        float period = clk_edge->clock()->period();
        return -mult * period;
      }
      else if (setup_mcp) {
        int mult = setup_mcp->pathMultiplier(min_max);
        const ClockEdge *clk_edge =
          setup_mcp->useEndClk() ? tgt_clk_edge : src_clk_edge;
        float period = clk_edge->clock()->period();
        return (mult - 1) * period;
      }
      else
        return 0.0;
    }
  }
  else
    return 0.0;
}

float
PathEnd::checkSetupMcpAdjustment(const ClockEdge *src_clk_edge,
                                 const ClockEdge *tgt_clk_edge,
                                 const MultiCyclePath *mcp,
                                 int default_cycles,
                                 Sdc *sdc)
{
  if (mcp) {
    // Default arrival clock is a proxy for the target clock.
    if (src_clk_edge == nullptr)
      src_clk_edge = tgt_clk_edge;
    else if (src_clk_edge->clock() == sdc->defaultArrivalClock())
      src_clk_edge = tgt_clk_edge->clock()->edge(src_clk_edge->transition());
    if (mcp->minMax()->matches(MinMax::max())) {
      int mult = mcp->pathMultiplier(MinMax::max());
      const ClockEdge *clk_edge =
        mcp->useEndClk() ? tgt_clk_edge : src_clk_edge;
      float period = clk_edge->clock()->period();
      return (mult - default_cycles) * period;
    }
    else
      return 0.0;
  }
  else
    return 0.0;
}

Slack
PathEndClkConstrained::slackNoCrpr(const StaState *sta) const
{
  Arrival arrival = dataArrivalTime(sta);
  Required required = requiredTimeNoCrpr(sta);
  if (checkGenericRole(sta) == TimingRole::setup())
    return delayDiff(required, arrival, sta);
  else
    return delayDiff(arrival, required, sta);
}

void
PathEndClkConstrainedMcp::findHoldMcps(const ClockEdge *tgt_clk_edge,
                                       const MultiCyclePath *&setup_mcp,
                                       const MultiCyclePath *&hold_mcp,
                                       const StaState *sta) const

{
  Pin *pin = path_->pin(sta);
  const RiseFall *rf = path_->transition(sta);
  // Mcp may be setup, hold or setup_hold, since all match min paths.
  const MinMaxAll *mcp_min_max = mcp_->minMax();
  Search *search = sta->search();
  if (mcp_min_max->matches(MinMax::min())) {
    hold_mcp = mcp_;
    setup_mcp =
      dynamic_cast<MultiCyclePath*>(search->exceptionTo(ExceptionPathType::multi_cycle,
                                                        path_, pin, rf,
                                                        tgt_clk_edge,
                                                        MinMax::max(), true,
                                                        false,
                                                        path_->sdc(sta)));
  }
  else {
    setup_mcp = mcp_;
    hold_mcp =
      dynamic_cast<MultiCyclePath*>(search->exceptionTo(ExceptionPathType::multi_cycle,
                                                        path_, pin, rf,
                                                        tgt_clk_edge,
                                                        MinMax::min(), true,
                                                        false,
                                                        path_->sdc(sta)));
  }
}

int
PathEndClkConstrainedMcp::exceptPathCmp(const PathEnd *path_end,
                                        const StaState *sta) const
{
  int cmp = PathEndClkConstrained::exceptPathCmp(path_end, sta);
  if (cmp == 0) {
    const PathEndClkConstrainedMcp *path_end2 = 
      dynamic_cast<const PathEndClkConstrainedMcp*>(path_end);
    const MultiCyclePath *mcp2 = path_end2->mcp_;
    if (mcp_ == mcp2)
      return 0;
    else if (mcp_ < mcp2)
      return -1;
    else
      return 1;
  }
  else
    return cmp;
}

////////////////////////////////////////////////////////////////

PathEndCheck::PathEndCheck(Path *path,
                           TimingArc *check_arc,
                           Edge *check_edge,
                           Path *clk_path,
                           MultiCyclePath *mcp,
                           const StaState *) :
  PathEndClkConstrainedMcp(path, clk_path, mcp),
  check_arc_(check_arc),
  check_edge_(check_edge)
{
}

PathEnd *
PathEndCheck::copy() const
{
  return new PathEndCheck(*this);
}

PathEnd::Type
PathEndCheck::type() const
{
  return Type::check;
}

const char *
PathEndCheck::typeName() const
{
  return "check";
}

void
PathEndCheck::reportFull(const ReportPath *report) const
{
  report->reportFull(this);
}

void
PathEndCheck::reportShort(const ReportPath *report) const
{
  report->reportShort(this);
}

const TimingRole *
PathEndCheck::checkRole(const StaState *) const
{
  return check_edge_->role();
}

ArcDelay
PathEndCheck::margin(const StaState *sta) const
{
  return sta->search()->deratedDelay(clk_path_->vertex(sta),
                                     check_arc_, check_edge_, false,
                                     path_->minMax(sta),
                                     path_->dcalcAnalysisPtIndex(sta),
                                     path_->sdc(sta));
}

int
PathEndCheck::exceptPathCmp(const PathEnd *path_end,
                            const StaState *sta) const
{
  int cmp = PathEndClkConstrainedMcp::exceptPathCmp(path_end, sta);
  if (cmp == 0) {
    const PathEndCheck *path_end2=dynamic_cast<const PathEndCheck*>(path_end);
    const TimingArc *check_arc2 = path_end2->check_arc_;
    if (check_arc_ == check_arc2)
      return 0;
    else if (check_arc_ < check_arc2)
      return -1;
    else
      return 1;
  }
  else
    return cmp;
}

Delay
PathEndCheck::clkSkew(const StaState *sta)
{
  Delay skew = delayDiff(sourceClkDelay(sta),
                         targetClkDelay(sta),
                         sta);
  skew = delayDiff(skew, crpr(sta), sta);
  // Uncertainty decreases slack, but increases skew.
  skew = delayDiff(skew,
                   checkTgtClkUncertainty(clk_path_,
                                                    clk_path_->clkEdge(sta),
                                                    checkRole(sta),
                                                    sta),
                   sta);
  return skew;
}

Delay
PathEndCheck::sourceClkDelay(const StaState *sta) const
{
  PathExpanded expanded(path_, sta);
  const Path *src_clk_path = expanded.clkPath();
  if (src_clk_path) {
    const ClkInfo *src_clk_info = path_->tag(sta)->clkInfo();
    if (src_clk_info->isPropagated()) {
      // Propagated clock.  Propagated arrival is seeded with insertion delay.
      Arrival clk_arrival = src_clk_path->arrival();
      const ClockEdge *src_clk_edge = src_clk_info->clkEdge();
      Delay insertion = sourceClkInsertionDelay(sta);
      return delayRemove(delayDiff(clk_arrival, src_clk_edge->time(), sta),
                         insertion);
    }
    else
      // Ideal clock.
      return sourceClkLatency(sta);
  }
  else
    return 0.0;
}

Required
PathEndCheck::requiredTimeNoCrpr(const StaState *sta) const
{
  Arrival tgt_clk_arrival = targetClkArrivalNoCrpr(sta);
  ArcDelay check_margin = margin(sta);
  float macro_clk_tree_delay = macroClkTreeDelay(sta);
  if (checkGenericRole(sta) == TimingRole::setup())
    return delayDiff(tgt_clk_arrival,
                     delaySum(check_margin,
                              macro_clk_tree_delay,
                              sta),
                     sta);
  else
    return delaySum(tgt_clk_arrival,
                    delayDiff(check_margin,
                              macro_clk_tree_delay,
                              sta),
                    sta);
}

float
PathEndCheck::macroClkTreeDelay(const StaState *sta) const
{
  const ClockEdge *tgt_clk_edge = targetClkEdge(sta);
  const Clock *tgt_clk = tgt_clk_edge->clock();
  const Network *network = sta->network();
  const Pin *clk_pin = clk_path_->pin(sta);
  const Instance *inst = network->instance(clk_pin);
  const LibertyCell *inst_cell = network->libertyCell(inst);
  if (tgt_clk->isIdeal()
      && inst_cell && inst_cell->isMacro()) {
    LibertyPort *clk_port = network->libertyPort(clk_pin);
    if (clk_port) {
      const MinMax *min_max = clk_path_->minMax(sta);
      const RiseFall *rf = clk_path_->transition(sta);
      float slew = delayAsFloat(clk_path_->slew(sta), min_max, sta);
      return clk_port->clkTreeDelay(slew, rf, min_max);
    }
  }
  return 0.0;
}

////////////////////////////////////////////////////////////////

PathEndLatchCheck::PathEndLatchCheck(Path *path,
                                     TimingArc *check_arc,
                                     Edge *check_edge,
                                     Path *disable_path,
                                     MultiCyclePath *mcp,
                                     PathDelay *path_delay,
                                     const StaState *sta) :
  PathEndCheck(path, check_arc, check_edge, nullptr, mcp, sta),
  disable_path_(disable_path),
  path_delay_(path_delay),
  src_clk_arrival_(0.0)
{
  Latches *latches = sta->latches();
  Path *enable_path = latches->latchEnableOtherPath(disable_path);
  clk_path_ = enable_path;
  Search *search = sta->search();
  // Same as PathEndPathDelay::findRequired.
  if (path_delay_ && PathEnd::ignoreClkLatency(path_, path_delay_, sta))
    src_clk_arrival_ = search->pathClkPathArrival(path_);
}

PathEnd *
PathEndLatchCheck::copy() const
{
  return new PathEndLatchCheck(*this);
}

PathEnd::Type
PathEndLatchCheck::type() const
{
  return Type::latch_check;
}

const char *
PathEndLatchCheck::typeName() const
{
  return "latch_check";
}

Path *
PathEndLatchCheck::latchDisable()
{
  return disable_path_;
}

const Path *
PathEndLatchCheck::latchDisable() const
{
  return disable_path_;
}

void
PathEndLatchCheck::reportFull(const ReportPath *report) const
{
  report->reportFull(this);
}

void
PathEndLatchCheck::reportShort(const ReportPath *report) const
{
  report->reportShort(this);
}

float
PathEndLatchCheck::sourceClkOffset(const StaState *sta) const
{
  if (path_delay_)
    return pathDelaySrcClkOffset(path_, path_delay_, src_clk_arrival_, sta);
  else
    return PathEndClkConstrained::sourceClkOffset(sourceClkEdge(sta),
                                                  disable_path_->clkEdge(sta),
                                                  TimingRole::setup(),
                                                  sta);
}

const TimingRole *
PathEndLatchCheck::checkRole(const StaState *sta) const
{
  if (clk_path_ && clk_path_->clkInfo(sta)->isPulseClk())
    // Pulse latches use register cycle accounting.
    return TimingRole::setup();
  else
    // Setup cycle accting is slightly different because it is wrt
    // the enable opening edge, not the disable (setup check) edge.
    return TimingRole::latchSetup();
}

float
PathEndLatchCheck::targetClkTime(const StaState *sta) const
{
  if (path_delay_)
    return 0.0;
  else
    return PathEndClkConstrained::targetClkTime(sta);
}

float
PathEndLatchCheck::targetClkOffset(const StaState *sta) const
{
  if (path_delay_)
    return -targetClkEdge(sta)->time();
  else
    return PathEndClkConstrained::targetClkOffset(sta);
}

Required
PathEndLatchCheck::requiredTime(const StaState *sta) const
{
  Required required;
  Arrival borrow, adjusted_data_arrival, time_given_to_startpoint;
  Latches *latches = sta->latches();
  latches->latchRequired(path_, targetClkPath(), latchDisable(),
                         mcp_, path_delay_, src_clk_arrival_, margin(sta),
                         required, borrow, adjusted_data_arrival,
                         time_given_to_startpoint);
  return required;
}

Arrival
PathEndLatchCheck::borrow(const StaState *sta) const
{
  Latches *latches = sta->latches();
  Required required;
  Arrival  borrow, adjusted_data_arrival, time_given_to_startpoint;
  latches->latchRequired(path_, targetClkPath(), latchDisable(),
                         mcp_, path_delay_, src_clk_arrival_, margin(sta),
                         required, borrow, adjusted_data_arrival,
                         time_given_to_startpoint);
  return borrow;
}

void 
PathEndLatchCheck::latchRequired(const StaState *sta,
                                 // Return values.
                                 Required &required,
                                 Delay &borrow,
                                 Arrival &adjusted_data_arrival,
                                 Delay &time_given_to_startpoint) const
{
  Latches *latches = sta->latches();
  latches->latchRequired(path_, targetClkPath(), latchDisable(),
                         mcp_, path_delay_, src_clk_arrival_, margin(sta),
                         required, borrow, adjusted_data_arrival,
                         time_given_to_startpoint);
}

void 
PathEndLatchCheck::latchBorrowInfo(const StaState *sta,
                                   // Return values.
                                   float &nom_pulse_width,
                                   Delay &open_latency,
                                   Delay &latency_diff,
                                   float &open_uncertainty,
                                   Crpr &open_crpr,
                                   Crpr &crpr_diff,
                                   Delay &max_borrow,
                                   bool &borrow_limit_exists) const
{
  Latches *latches = sta->latches();
  latches->latchBorrowInfo(path_, targetClkPath(), latchDisable(),
                           margin(sta),
                           path_delay_ && ignoreClkLatency(sta),
                           nom_pulse_width, open_latency,
                           latency_diff, open_uncertainty,
                           open_crpr, crpr_diff, max_borrow,
                           borrow_limit_exists);
}

Arrival
PathEndLatchCheck::targetClkWidth(const StaState *sta) const
{
  const Search *search = sta->search();
  Arrival disable_arrival = search->clkPathArrival(disable_path_);
  Arrival enable_arrival = search->clkPathArrival(clk_path_);
  const ClkInfo *enable_clk_info = clk_path_->clkInfo(sta);
  if (enable_clk_info->isPulseClk())
    return delayDiff(disable_arrival, enable_arrival, sta);
  else {
    if (delayGreater(enable_arrival, disable_arrival, sta)) {
      const Clock *disable_clk = enable_clk_info->clock();
      if (disable_clk)
        disable_arrival = delaySum(disable_arrival,
                                   disable_clk->period(),
                                   sta);
    }
    return delayDiff(disable_arrival, enable_arrival, sta);
  }
}

int
PathEndLatchCheck::exceptPathCmp(const PathEnd *path_end,
                                 const StaState *sta) const
{
  int cmp = PathEndCheck::exceptPathCmp(path_end, sta);
  if (cmp == 0) {
    const PathEndLatchCheck *path_end2 =
      dynamic_cast<const PathEndLatchCheck*>(path_end);
    const TimingArc *check_arc2 = path_end2->check_arc_;
    if (check_arc_ == check_arc2) {
      const Path *disable_path2 = path_end2->disable_path_;
      return Path::cmp(disable_path_, disable_path2, sta);
    }
    else if (check_arc_ < check_arc2)
      return -1;
    else
      return 1;
  }
  else
    return cmp;
}

bool
PathEndLatchCheck::ignoreClkLatency(const StaState *sta) const
{
  return PathEnd::ignoreClkLatency(path_, path_delay_, sta);
}

///////////////////////////////////////////////////////////////

PathEndOutputDelay::PathEndOutputDelay(OutputDelay *output_delay,
                                       Path *path,
                                       Path *clk_path,
                                       MultiCyclePath *mcp,
                                       const StaState *) :
  // No target clk_path_ for output delays.
  PathEndClkConstrainedMcp(path, clk_path, mcp),
  output_delay_(output_delay)
{
}

PathEnd *
PathEndOutputDelay::copy() const
{
  return new PathEndOutputDelay(*this);
}

PathEnd::Type
PathEndOutputDelay::type() const
{
  return Type::output_delay;
}

const char *
PathEndOutputDelay::typeName() const
{
  return "output_delay";
}

void
PathEndOutputDelay::reportFull(const ReportPath *report) const
{
  report->reportFull(this);
}

void
PathEndOutputDelay::reportShort(const ReportPath *report) const
{
  report->reportShort(this);
}

ArcDelay
PathEndOutputDelay::margin(const StaState *sta) const
{
  return outputDelayMargin(output_delay_, path_, sta);
}

float
PathEnd::outputDelayMargin(OutputDelay *output_delay,
                           const Path *path,
                           const StaState *sta)
{
  const RiseFall *rf = path->transition(sta);
  const MinMax *min_max = path->minMax(sta);
  float margin = output_delay->delays()->value(rf, min_max);
  if (min_max == MinMax::max())
    return margin;
  else
    return -margin;
}

const TimingRole *
PathEndOutputDelay::checkRole(const StaState *sta) const
{
  if (path_->minMax(sta) == MinMax::max())
    return TimingRole::outputSetup();
  else
    return TimingRole::outputHold();
}

const ClockEdge *
PathEndOutputDelay::targetClkEdge(const StaState *sta) const
{
  if (clk_path_)
    return clk_path_->clkEdge(sta);
  else
    return output_delay_->clkEdge();
}

Arrival
PathEndOutputDelay::targetClkArrivalNoCrpr(const StaState *sta) const
{
  if (clk_path_)
    return PathEndClkConstrained::targetClkArrivalNoCrpr(sta);
  else {
    const ClockEdge *tgt_clk_edge = targetClkEdge(sta);
    const TimingRole *check_role = checkRole(sta);
    Arrival base = delaySum(targetClkTime(sta),
                            tgtClkDelay(tgt_clk_edge, check_role, sta),
                            sta);
    return delaySum(delaySum(base,
                             targetClkUncertainty(sta),
                             sta),
                    checkMcpAdjustment(path_, tgt_clk_edge, sta),
                    sta);
  }
}

Crpr
PathEndOutputDelay::crpr(const StaState *sta) const
{
  if (!crpr_valid_) {
    CheckCrpr *check_crpr = sta->search()->checkCrpr();
    crpr_ = check_crpr->outputDelayCrpr(path_, targetClkEdge(sta));
    crpr_valid_ = true;
  }
  return crpr_;
}

Delay
PathEndOutputDelay::targetClkDelay(const StaState *sta) const
{
  if (clk_path_)
    return PathEndClkConstrained::targetClkDelay(sta);
  else
    return tgtClkDelay(targetClkEdge(sta), checkRole(sta), sta);
}

Arrival
PathEndOutputDelay::tgtClkDelay(const ClockEdge *tgt_clk_edge,
                                const TimingRole *check_role,
                                const StaState *sta) const
{
  Arrival insertion, latency;
  tgtClkDelay(tgt_clk_edge, check_role, sta,
              insertion, latency);
  return delaySum(insertion, latency, sta);
}

void
PathEndOutputDelay::tgtClkDelay(const ClockEdge *tgt_clk_edge,
                                const TimingRole *check_role,
                                const StaState *sta,
                                // Return values.
                                Arrival &insertion,
                                Arrival &latency) const
{
  // Early late: setup early, hold late.
  const EarlyLate *early_late = check_role->tgtClkEarlyLate();
  // Latency min_max depends on bc_wc or ocv.
  const MinMax *latency_min_max = path_->tgtClkMinMax(sta);
  Clock *tgt_clk = tgt_clk_edge->clock();
  const RiseFall *tgt_clk_rf = tgt_clk_edge->transition();
  const Mode *mode = path_->mode(sta);
  if (!output_delay_->sourceLatencyIncluded())
    insertion = sta->search()->clockInsertion(tgt_clk,
                                              tgt_clk->defaultPin(),
                                              tgt_clk_rf,
                                              latency_min_max,
                                              early_late, mode);
  else
    insertion = 0.0;
  if (!tgt_clk->isPropagated()
      && !output_delay_->networkLatencyIncluded())
    latency = mode->sdc()->clockLatency(tgt_clk, tgt_clk_rf, latency_min_max);
  else
    latency = 0.0;
}

Delay
PathEndOutputDelay::targetClkInsertionDelay(const StaState *sta) const
{
  if (clk_path_)
    return PathEndClkConstrained::targetClkInsertionDelay(sta);
  else {
    Arrival insertion, latency;
    tgtClkDelay(targetClkEdge(sta), checkRole(sta), sta,
                insertion, latency);
    return insertion;
  }
}

int
PathEndOutputDelay::exceptPathCmp(const PathEnd *path_end,
                                  const StaState *sta) const
{
  int cmp = PathEndClkConstrainedMcp::exceptPathCmp(path_end, sta);
  if (cmp == 0) {
    const PathEndOutputDelay *path_end2 =
      dynamic_cast<const PathEndOutputDelay*>(path_end);
    OutputDelay *output_delay2 = path_end2->output_delay_;
    if (output_delay_ == output_delay2)
      return 0;
    else if (output_delay_ < output_delay2)
      return -1;
    else
      return 1;
  }
  else
    return cmp;
}

////////////////////////////////////////////////////////////////

PathEndGatedClock::PathEndGatedClock(Path *gating_ref,
                                     Path *clk_path,
                                     const TimingRole *check_role,
                                     MultiCyclePath *mcp,
                                     ArcDelay margin,
                                     const StaState *) :
  PathEndClkConstrainedMcp(gating_ref, clk_path, mcp),
  check_role_(check_role),
  margin_(margin)
{
}

PathEnd *
PathEndGatedClock::copy() const
{
  return new PathEndGatedClock(*this);
}

PathEnd::Type
PathEndGatedClock::type() const
{
  return Type::gated_clk;
}

const char *
PathEndGatedClock::typeName() const
{
  return "gated_clk";
}

const TimingRole *
PathEndGatedClock::checkRole(const StaState *) const
{
  return check_role_;
}

void
PathEndGatedClock::reportFull(const ReportPath *report) const
{
  report->reportFull(this);
}

void
PathEndGatedClock::reportShort(const ReportPath *report) const
{
  report->reportShort(this);
}

int
PathEndGatedClock::exceptPathCmp(const PathEnd *path_end,
                                 const StaState *sta) const
{
  int cmp = PathEndClkConstrainedMcp::exceptPathCmp(path_end, sta);
  if (cmp == 0) {
    const PathEndGatedClock *path_end2 =
      dynamic_cast<const PathEndGatedClock*>(path_end);
    const TimingRole *check_role2 = path_end2->check_role_;
    if (check_role_ == check_role2)
      return 0;
    else if (check_role_ < check_role2)
      return -1;
    else
      return 1;
  }
  else
    return cmp;
}

////////////////////////////////////////////////////////////////

PathEndDataCheck::PathEndDataCheck(DataCheck *check,
                                   Path *data_path,
                                   Path *data_clk_path,
                                   MultiCyclePath *mcp,
                                   const StaState *sta) :
  PathEndClkConstrainedMcp(data_path, nullptr, mcp),
  data_clk_path_(data_clk_path),
  check_(check)
{
  clk_path_ = clkPath(data_clk_path, sta);
}


// PathExpanded::expand() and PathExpanded::clkPath().
Path *
PathEndDataCheck::clkPath(Path *path,
                          const StaState *sta)
{
  Path *p = path;
  while (p) {
    Path *prev_path = p->prevPath();
    TimingArc *prev_arc = p->prevArc(sta);

    if (p->isClock(sta))
      return p;
    if (prev_arc) {
      const TimingRole *prev_role = prev_arc->role();
      if (prev_role == TimingRole::regClkToQ()
          || prev_role == TimingRole::latchEnToQ()) {
        prev_path = p->prevPath();
        return prev_path;
      }
      else if (prev_role == TimingRole::latchDtoQ()) {
        const Latches *latches = sta->latches();
        Edge *prev_edge = p->prevEdge(sta);
        Path *enable_path = latches->latchEnablePath(p, prev_edge);
        return enable_path;
      }
    }
    p = prev_path;
  }
  return nullptr;
}

PathEnd *
PathEndDataCheck::copy() const
{
  return new PathEndDataCheck(*this);
}

PathEnd::Type
PathEndDataCheck::type() const
{
  return Type::data_check;
}

const char *
PathEndDataCheck::typeName() const
{
  return "data_check";
}

const ClockEdge *
PathEndDataCheck::targetClkEdge(const StaState *sta) const
{
  // clk_path_ can be null if data_clk_path is from an input port.
  return data_clk_path_->clkEdge(sta);
}

Arrival
PathEndDataCheck::requiredTimeNoCrpr(const StaState *sta) const
{
  Arrival data_clk_arrival = data_clk_path_->arrival();
  float data_clk_time = data_clk_path_->clkEdge(sta)->time();
  Arrival data_clk_delay = delayDiff(data_clk_arrival,
                                     data_clk_time,
                                     sta);
  Arrival tgt_clk_arrival =
    delaySum(delaySum(targetClkTime(sta),
                      data_clk_delay,
                      sta),
             delaySum(targetClkUncertainty(sta),
                      targetClkMcpAdjustment(sta),
                      sta),
             sta);

  ArcDelay check_margin = margin(sta);
  if (checkGenericRole(sta) == TimingRole::setup())
    return delayDiff(tgt_clk_arrival, check_margin, sta);
  else
    return delaySum(tgt_clk_arrival, check_margin, sta);
}

ArcDelay
PathEndDataCheck::margin(const StaState *sta) const
{
  float margin;
  bool margin_exists;
  check_->margin(data_clk_path_->transition(sta),
                 path_->transition(sta),
                 path_->minMax(sta),
                 margin, margin_exists);
  return margin;
}

const TimingRole *
PathEndDataCheck::checkRole(const StaState *sta) const
{
  if (path_->minMax(sta) == MinMax::max())
    return TimingRole::dataCheckSetup();
  else
    return TimingRole::dataCheckHold();
}

void
PathEndDataCheck::reportFull(const ReportPath *report) const
{
  report->reportFull(this);
}

void
PathEndDataCheck::reportShort(const ReportPath *report) const
{
  report->reportShort(this);
}

int
PathEndDataCheck::exceptPathCmp(const PathEnd *path_end,
                                const StaState *sta) const
{
  int cmp = PathEndClkConstrainedMcp::exceptPathCmp(path_end, sta);
  if (cmp == 0) {
    const PathEndDataCheck *path_end2 = 
      dynamic_cast<const PathEndDataCheck*>(path_end);
    const DataCheck *check2 = path_end2->check_;
    if (check_ == check2)
      return 0;
    else if (check_ < check2)
      return -1;
    else
      return 1;
  }
  else
    return cmp;
}

////////////////////////////////////////////////////////////////

PathEndPathDelay::PathEndPathDelay(PathDelay *path_delay,
                                   Path *path,
                                   const StaState *sta):
  PathEndClkConstrained(path, nullptr),
  path_delay_(path_delay),
  check_arc_(nullptr),
  check_edge_(nullptr),
  output_delay_(nullptr)
{
  findSrcClkArrival(sta);
}

PathEndPathDelay::PathEndPathDelay(PathDelay *path_delay,
                                   Path *path,
                                   OutputDelay *output_delay,
                                   const StaState *sta):
  PathEndClkConstrained(path, nullptr),
  path_delay_(path_delay),
  check_arc_(nullptr),
  check_edge_(nullptr),
  output_delay_(output_delay)
{
  findSrcClkArrival(sta);
}

PathEndPathDelay::PathEndPathDelay(PathDelay *path_delay,
                                   Path *path,
                                   Path *clk_path,
                                   TimingArc *check_arc,
                                   Edge *check_edge,
                                   const StaState *sta) :
  PathEndClkConstrained(path, clk_path),
  path_delay_(path_delay),
  check_arc_(check_arc),
  check_edge_(check_edge),
  output_delay_(nullptr)
{
  findSrcClkArrival(sta);
}

PathEnd *
PathEndPathDelay::copy() const
{
  return new PathEndPathDelay(*this);
}

PathEnd::Type
PathEndPathDelay::type() const
{
  return Type::path_delay;
}

const char *
PathEndPathDelay::typeName() const
{
  return "path_delay";
}

void
PathEndPathDelay::findSrcClkArrival(const StaState *sta)
{
  if (PathEnd::ignoreClkLatency(path_, path_delay_, sta)) {
    Search *search = sta->search();
    src_clk_arrival_ = search->pathClkPathArrival(path_);
  }
  else
    src_clk_arrival_ = 0.0;

}

void
PathEndPathDelay::reportFull(const ReportPath *report) const
{
  report->reportFull(this);
}

void
PathEndPathDelay::reportShort(const ReportPath *report) const
{
  report->reportShort(this);
}

bool
PathEndPathDelay::pathDelayMarginIsExternal() const
{
  return check_arc_ == nullptr;
}

const TimingRole *
PathEndPathDelay::checkRole(const StaState *sta) const
{
  if (check_edge_)
    return check_edge_->role();
  else if (minMax(sta) == MinMax::max())
    return TimingRole::setup();
  else
    return TimingRole::hold();
}

ArcDelay
PathEndPathDelay::margin(const StaState *sta) const
{
  if (check_arc_) {
    return sta->search()->deratedDelay(check_edge_->from(sta->graph()),
                                       check_arc_, check_edge_, false,
                                       path_->minMax(sta),
                                       path_->dcalcAnalysisPtIndex(sta),
                                       path_->sdc(sta));
  }
  else if (output_delay_)
    return outputDelayMargin(output_delay_, path_, sta);
  else
    return delay_zero;
}

float
PathEndPathDelay::sourceClkOffset(const StaState *sta) const
{
  return pathDelaySrcClkOffset(path_, path_delay_, src_clk_arrival_, sta);
}

Delay
PathEnd::clkSkew(const StaState *)
{
  return 0.0;
}

// Helper shared by PathEndLatchCheck.
float
PathEnd::pathDelaySrcClkOffset(const Path *path,
                               PathDelay *path_delay,
                               Arrival src_clk_arrival,
                               const StaState *sta)
{
  float offset = 0.0;
  const ClockEdge *clk_edge = path->clkEdge(sta);
  if (clk_edge) {
    if (ignoreClkLatency(path, path_delay, sta))
      offset = -delayAsFloat(src_clk_arrival, path->minMax(sta), sta);
    else
      // Arrival includes src clock edge time that is not counted in the
      // path delay.
      offset = -clk_edge->time();
  }
  return offset;
}

const ClockEdge *
PathEndPathDelay::targetClkEdge(const StaState *sta) const
{
  if (clk_path_)
    return clk_path_->clkEdge(sta);
  else if (output_delay_)
    return output_delay_->clkEdge();
  else
    return nullptr;
}

float 
PathEndPathDelay::targetClkTime(const StaState *sta) const
{
  const ClockEdge *tgt_clk_edge = targetClkEdge(sta);
  if (tgt_clk_edge)
    return tgt_clk_edge->time();
  else
    return 0.0;
}

Arrival
PathEndPathDelay::targetClkArrivalNoCrpr(const StaState *sta) const
{
  const ClockEdge *tgt_clk_edge = targetClkEdge(sta);
  if (tgt_clk_edge)
    return delaySum(targetClkDelay(sta),
                    targetClkUncertainty(sta),
                    sta);
  else if (clk_path_)
    return clk_path_->arrival();
  else
    return 0.0;
}

float 
PathEndPathDelay::targetClkOffset(const StaState *) const
{
  return 0.0;
}

Required
PathEndPathDelay::requiredTime(const StaState *sta) const
{
  float delay = path_delay_->delay();
  if (path_delay_->ignoreClkLatency()) {
    Delay with_delay = path_->isClock(sta)
      ? Delay(path_->clkEdge(sta)->time() + delay)
      : delaySum(src_clk_arrival_, delay, sta);
    ArcDelay m = margin(sta);
    return (minMax(sta) == MinMax::max())
      ? delayDiff(with_delay, m, sta)
      : delaySum(with_delay, m, sta);
  }
  else {
    Arrival tgt_clk_arrival = targetClkArrival(sta);
    float src_clk_offset = sourceClkOffset(sta);
    // Path delay includes target clk latency and timing check setup/hold 
    // margin or external departure at target.
    Delay base = delaySum(tgt_clk_arrival,
                          delay,
                          sta);
    Delay with_src = delayDiff(base,
                               src_clk_offset,
                               sta);
    ArcDelay m = margin(sta);
    return (minMax(sta) == MinMax::max())
      ? delayDiff(with_src, m, sta)
      : delaySum(with_src, m, sta);
  }
}

bool
PathEndPathDelay::ignoreClkLatency(const StaState *sta) const
{
  return PathEnd::ignoreClkLatency(path_, path_delay_, sta);
}

int
PathEndPathDelay::exceptPathCmp(const PathEnd *path_end,
                                const StaState *sta) const
{
  int cmp = PathEndClkConstrained::exceptPathCmp(path_end, sta);
  if (cmp == 0) {
    const PathEndPathDelay *path_end2 =
      dynamic_cast<const PathEndPathDelay*>(path_end);
    PathDelay *path_delay2 = path_end2->path_delay_;
    if (path_delay_ == path_delay2) {
      const TimingArc *check_arc2 = path_end2->check_arc_;
      if (check_arc_ == check_arc2)
        return 0;
      else if (check_arc_ < check_arc2)
        return -1;
      else
        return 1;
    }
    else if (path_delay_ < path_delay2)
      return -1;
    else
      return 1;
  }
  else
    return cmp;
}

////////////////////////////////////////////////////////////////

bool
PathEnd::less(const PathEnd *path_end1,
              const PathEnd *path_end2,
              bool cmp_slack,
              const StaState *sta)
{
  return cmp(path_end1, path_end2, cmp_slack, sta) < 0;
}

int
PathEnd::cmp(const PathEnd *path_end1,
             const PathEnd *path_end2,
             bool cmp_slack,
             const StaState *sta)
{
  int cmp = !cmp_slack || path_end1->isUnconstrained()
    ? -cmpArrival(path_end1, path_end2, sta)
    : cmpSlack(path_end1, path_end2, sta);
  if (cmp == 0) {
    const Path *path1 = path_end1->path();
    const Path *path2 = path_end2->path();
    cmp = Path::cmpPinTrClk(path1, path2, sta);
    if (cmp == 0) {
      const Path *clk_path1 = path_end1->targetClkPath();
      const Path *clk_path2 = path_end2->targetClkPath();
      cmp = Path::cmpPinTrClk(clk_path1, clk_path2, sta);
      if (cmp == 0) {
        cmp = Path::cmpAll(path1, path2, sta);
        if (cmp == 0)
          cmp = Path::cmpAll(clk_path1, clk_path2, sta);
      }
    }
  }
  return cmp;
}

int
PathEnd::cmpSlack(const PathEnd *path_end1,
                  const PathEnd *path_end2,
                  const StaState *sta)
{
  Slack slack1 = path_end1->slack(sta);
  Slack slack2 = path_end2->slack(sta);
  if (delayZero(slack1, sta)
      && delayZero(slack2, sta)
      && path_end1->isLatchCheck()
      && path_end2->isLatchCheck()) {
    Arrival borrow1 = path_end1->borrow(sta);
    Arrival borrow2 = path_end2->borrow(sta);
    // Latch slack is zero if there is borrowing so break ties
    // based on borrow time.
    if (delayEqual(borrow1, borrow2, sta))
      return 0;
    else if (delayGreater(borrow1, borrow2, sta))
      return -1;
    else
      return 1;
  }
  else if (delayEqual(slack1, slack2, sta))
    return 0;
  else if (delayLess(slack1, slack2, sta))
    return -1;
  else
    return 1;
}

int
PathEnd::cmpArrival(const PathEnd *path_end1,
                    const PathEnd *path_end2,
                    const StaState *sta)
{
  Arrival arrival1 = path_end1->dataArrivalTime(sta);
  Arrival arrival2 = path_end2->dataArrivalTime(sta);
  const MinMax *min_max = path_end1->minMax(sta);
  if (delayEqual(arrival1, arrival2, sta))
    return 0;
  else if (delayLess(arrival1, arrival2, min_max, sta))
    return -1;
  else
    return 1;
}

int
PathEnd::cmpNoCrpr(const PathEnd *path_end1,
                   const PathEnd *path_end2,
                   const StaState *sta)
{
  int cmp = path_end1->exceptPathCmp(path_end2, sta);
  if (cmp == 0) {
    const Path *path1 = path_end1->path();
    const Path *path2 = path_end2->path();
    return Path::cmpNoCrpr(path1, path2, sta);
  }
  else
    return cmp;
}

////////////////////////////////////////////////////////////////

PathEndLess::PathEndLess(bool cmp_slack,
                         const StaState *sta) :
  cmp_slack_(cmp_slack),
  sta_(sta)
{
}

bool
PathEndLess::operator()(const PathEnd *path_end1,
                        const PathEnd *path_end2) const
{
  return PathEnd::less(path_end1, path_end2, cmp_slack_, sta_);
}

////////////////////////////////////////////////////////////////

PathEndSlackLess::PathEndSlackLess(bool cmp_slack,
                                   const StaState *sta) :
  cmp_slack_(cmp_slack),
  sta_(sta)
{
}

bool
PathEndSlackLess::operator()(const PathEnd *path_end1,
                             const PathEnd *path_end2) const
{
  int cmp = path_end1->isUnconstrained()
    ? -PathEnd::cmpArrival(path_end1, path_end2, sta_)
    : PathEnd::cmpSlack(path_end1, path_end2, sta_);
  return cmp < 0;
}

////////////////////////////////////////////////////////////////

PathEndNoCrprLess::PathEndNoCrprLess(const StaState *sta) :
  sta_(sta)
{
}

bool
PathEndNoCrprLess::operator()(const PathEnd *path_end1,
                              const PathEnd *path_end2) const
{
  int cmp = path_end1->exceptPathCmp(path_end2, sta_);
  if (cmp == 0) {
    const Path *path1 = path_end1->path();
    const Path *path2 = path_end2->path();
    return Path::cmpNoCrpr(path1, path2, sta_) < 0;
  }
  else
    return cmp < 0;
}

} // namespace sta
