// 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 "CcsCeffDelayCalc.hh"

#include <algorithm>
#include <cmath>

#include "Debug.hh"
#include "DmpDelayCalc.hh"
#include "FindRoot.hh"
#include "Graph.hh"
#include "GraphDelayCalc.hh"
#include "Liberty.hh"
#include "Network.hh"
#include "Parasitics.hh"
#include "Scene.hh"
#include "TimingArc.hh"
#include "Units.hh"

namespace sta {

// Implementaion based on:
// "Gate Delay Estimation with Library Compatible Current Source Models
// and Effective Capacitance", D. Garyfallou et al,
// IEEE Transactions on Very Large Scale Integration (VLSI) Systems, March 2021

ArcDelayCalc *
makeCcsCeffDelayCalc(StaState *sta)
{
  return new CcsCeffDelayCalc(sta);
}

CcsCeffDelayCalc::CcsCeffDelayCalc(StaState *sta) :
  LumpedCapDelayCalc(sta),
  watch_pin_values_(network_),
  capacitance_unit_(units_->capacitanceUnit()),
  table_dcalc_(makeDmpCeffElmoreDelayCalc(sta))
{
}

CcsCeffDelayCalc::CcsCeffDelayCalc(const CcsCeffDelayCalc &dcalc) :
  LumpedCapDelayCalc(dcalc),
  watch_pin_values_(dcalc.network_),
  capacitance_unit_(dcalc.capacitance_unit_),
  table_dcalc_(makeDmpCeffElmoreDelayCalc(this))
{
}

CcsCeffDelayCalc::~CcsCeffDelayCalc()
{
  delete table_dcalc_;
}

ArcDelayCalc *
CcsCeffDelayCalc::copy()
{
  return new CcsCeffDelayCalc(*this);
}

ArcDcalcResult
CcsCeffDelayCalc::gateDelay(const Pin *drvr_pin,
                            const TimingArc *arc,
                            const Slew &in_slew,
                            float load_cap,
                            const Parasitic *parasitic,
                            const LoadPinIndexMap &load_pin_index_map,
                            const Scene *scene,
                            const MinMax *min_max)
{
  in_slew_ = delayAsFloat(in_slew);
  load_cap_ = load_cap;
  parasitics_ = scene->parasitics(min_max);
  parasitic_ = parasitic;
  output_waveforms_ = nullptr;

  const GateTableModel *table_model = arc->gateTableModel(scene, min_max);
  if (table_model && parasitic) {
    OutputWaveforms *output_waveforms = table_model->outputWaveforms();
    Parasitics *parasitics = scene->parasitics(min_max);
    parasitics->piModel(parasitic, c2_, rpi_, c1_);
    if (output_waveforms && rpi_ > 0.0
        && c1_ > 0.0
        // Bounds check because extrapolating waveforms does not work for shit.
        && output_waveforms->slewAxis()->inBounds(in_slew_)
        && output_waveforms->capAxis()->inBounds(c2_)
        && output_waveforms->capAxis()->inBounds(load_cap_)) {
      bool vdd_exists;
      LibertyCell *drvr_cell = arc->to()->libertyCell();
      const LibertyLibrary *drvr_library = drvr_cell->libertyLibrary();
      drvr_rf_ = arc->toEdge()->asRiseFall();
      drvr_library->supplyVoltage("VDD", vdd_, vdd_exists);
      if (!vdd_exists)
        report_->error(1700, "VDD not defined in library {}", drvr_library->name());
      vth_ = drvr_library->outputThreshold(drvr_rf_) * vdd_;
      vl_ = drvr_library->slewLowerThreshold(drvr_rf_) * vdd_;
      vh_ = drvr_library->slewUpperThreshold(drvr_rf_) * vdd_;

      drvr_cell->ensureVoltageWaveforms(scenes_);
      output_waveforms_ = output_waveforms;
      ref_time_ = output_waveforms_->referenceTime(in_slew_);
      debugPrint(debug_, "ccs_dcalc", 1, "{} {}",
                 drvr_cell->name(),
                 drvr_rf_->shortName());

      double gate_delay, drvr_slew;
      gateDelaySlew(drvr_library, gate_delay, drvr_slew);
      debugPrint(debug_, "ccs_dcalc", 2, "gate_delay {} drvr_slew {}",
                 delayAsString(gate_delay, this), delayAsString(drvr_slew, this));

      // Fill in pocv parameters.
      ArcDelay gate_delay2(gate_delay);
      Slew drvr_slew2(drvr_slew);
      if (variables_->pocvEnabled()) {
        double ceff = region_ceff_[0];
        const Pvt *pvt = pinPvt(drvr_pin_, scene, min_max);
        table_model->gateDelayPocv(pvt, in_slew_, ceff, min_max,
                                   variables_->pocvMode(),
                                   gate_delay2, drvr_slew2);
      }
      ArcDcalcResult dcalc_result(load_pin_index_map.size());
      dcalc_result.setGateDelay(gate_delay2);
      dcalc_result.setDrvrSlew(drvr_slew2);

      for (const auto &[load_pin, load_idx] : load_pin_index_map) {
        double wire_delay, load_slew;
        loadDelaySlew(load_pin, drvr_library, drvr_slew, wire_delay, load_slew);
        dcalc_result.setWireDelay(load_idx, wire_delay);
        dcalc_result.setLoadSlew(load_idx, load_slew);
      }
      return dcalc_result;
    }
  }
  return table_dcalc_->gateDelay(drvr_pin, arc, in_slew, load_cap, parasitic,
                                 load_pin_index_map, scene, min_max);
}

void
CcsCeffDelayCalc::gateDelaySlew(const LibertyLibrary *drvr_library,
                                // Return values.
                                double &gate_delay,
                                double &drvr_slew)
{    
  initRegions(drvr_library, drvr_rf_);
  findCsmWaveform();
  ref_time_ = output_waveforms_->referenceTime(in_slew_);
  gate_delay = region_times_[region_vth_idx_] - ref_time_;
  drvr_slew = std::abs(region_times_[region_vh_idx_] - region_times_[region_vl_idx_]);
  debugPrint(debug_, "ccs_dcalc", 2,
             "gate_delay {} drvr_slew {} (initial)",
             delayAsString(gate_delay, this),
             delayAsString(drvr_slew, this));
  float prev_drvr_slew = drvr_slew;
  constexpr int max_iterations = 5;
  for (int iter = 0; iter < max_iterations; iter++) {
    debugPrint(debug_, "ccs_dcalc", 2, "iteration {}", iter);
    // Init drvr ramp model for vl.
    for (size_t i = 0; i <= region_count_; i++) {
      region_ramp_times_[i] = region_times_[i];
      if (i < region_count_)
        region_ramp_slopes_[i] = (region_volts_[i + 1] - region_volts_[i])
            / (region_times_[i + 1] - region_times_[i]);
    }

    for (size_t i = 0; i < region_count_; i++) {
      double seg_v1 = region_volts_[i];
      double seg_v2 = region_volts_[i + 1];
      double t1 = region_times_[i];
      double t2 = region_times_[i + 1];

      // Receiver cap Cp(l) assumed constant and included in c1.
      // Note that eqn 8 in the ref'd paper does not properly account
      // for the charge on c1 from previous segments so it does not
      // work well.
      double c1_v1, c1_v2, ignore;
      vLoad(t1, rpi_ * c1_, c1_v1, ignore);
      vLoad(t2, rpi_ * c1_, c1_v2, ignore);
      double q1 = seg_v1 * c2_ + c1_v1 * c1_;
      double q2 = seg_v2 * c2_ + c1_v2 * c1_;
      double ceff = (q2 - q1) / (seg_v2 - seg_v1);

      debugPrint(debug_, "ccs_dcalc", 2, "ceff {}",
                 capacitance_unit_->asString(ceff));
      region_ceff_[i] = ceff;
    }
    findCsmWaveform();
    gate_delay = region_times_[region_vth_idx_] - ref_time_;
    drvr_slew = std::abs(region_times_[region_vh_idx_] - region_times_[region_vl_idx_]);
    debugPrint(debug_, "ccs_dcalc", 2, "gate_delay {} drvr_slew {}",
               delayAsString(gate_delay, this),
               delayAsString(drvr_slew, this));
    if (std::abs(drvr_slew - prev_drvr_slew) < .01 * prev_drvr_slew)
      break;
    prev_drvr_slew = drvr_slew;
  }
}

void
CcsCeffDelayCalc::initRegions(const LibertyLibrary *drvr_library,
                              const RiseFall *rf)
{
  // Falling waveforms are treated as rising to simplify the conditionals.
  if (rf == RiseFall::fall()) {
    vl_ = (1.0 - drvr_library->slewUpperThreshold(rf)) * vdd_;
    vh_ = (1.0 - drvr_library->slewLowerThreshold(rf)) * vdd_;
  }

  // Includes the Vh:Vdd region.
  region_count_ = 7;
  region_volts_.resize(region_count_ + 1);
  region_ceff_.resize(region_count_ + 1);
  region_times_.resize(region_count_ + 1);
  region_begin_times_.resize(region_count_ + 1);
  region_end_times_.resize(region_count_ + 1);
  region_time_offsets_.resize(region_count_ + 1);
  region_ramp_times_.resize(region_count_ + 1);
  region_ramp_slopes_.resize(region_count_ + 1);

  region_vl_idx_ = 1;
  region_vh_idx_ = region_count_ - 1;

  double vth_vh = (vh_ - vth_);
  switch (region_count_) {
    case 4:
      region_vth_idx_ = 2;
      region_volts_ = {0.0, vl_, vth_, vh_, vdd_};
      break;
    case 5: {
      region_vth_idx_ = 2;
      double v1 = vth_ + .7 * vth_vh;
      region_volts_ = {0.0, vl_, vth_, v1, vh_, vdd_};
      break;
    }
    case 6: {
      region_vth_idx_ = 2;
      double v1 = vth_ + .3 * vth_vh;
      double v2 = vth_ + .6 * vth_vh;
      region_volts_ = {0.0, vl_, vth_, v1, v2, vh_, vdd_};
      break;
    }
    case 7: {
      region_vth_idx_ = 2;
      region_vh_idx_ = 5;
      double v1 = vth_ + .3 * vth_vh;
      double v2 = vth_ + .6 * vth_vh;
      double v3 = vh_ + .5 * (vdd_ - vh_);
      region_volts_ = {0.0, vl_, vth_, v1, v2, vh_, v3, vdd_};
      break;
    }
    case 8: {
      region_vth_idx_ = 2;
      region_vh_idx_ = 6;
      double v1 = vth_ + .25 * vth_vh;
      double v2 = vth_ + .50 * vth_vh;
      double v3 = vth_ + .75 * vth_vh;
      double v4 = vh_ + .5 * (vdd_ - vh_);
      region_volts_ = {0.0, vl_, vth_, v1, v2, v3, vh_, v4, vdd_};
      break;
    }
    case 9: {
      region_vth_idx_ = 2;
      region_vh_idx_ = 7;
      double v1 = vth_ + .2 * vth_vh;
      double v2 = vth_ + .4 * vth_vh;
      double v3 = vth_ + .6 * vth_vh;
      double v4 = vth_ + .8 * vth_vh;
      double v5 = vh_ + .5 * (vdd_ - vh_);
      region_volts_ = {0.0, vl_, vth_, v1, v2, v3, v4, vh_, v5, vdd_};
      break;
    }
    case 10: {
      region_vth_idx_ = 2;
      region_vh_idx_ = 7;
      double v1 = vth_ + .2 * vth_vh;
      double v2 = vth_ + .4 * vth_vh;
      double v3 = vth_ + .6 * vth_vh;
      double v4 = vth_ + .8 * vth_vh;
      double v5 = vh_ + .3 * (vdd_ - vh_);
      double v6 = vh_ + .6 * (vdd_ - vh_);
      region_volts_ = {0.0, vl_, vth_, v1, v2, v3, v4, vh_, v5, v6, vdd_};
      break;
    }
    default:
      report_->error(1701, "unsupported ccs region count.");
      break;
  }
  std::ranges::fill(region_ceff_, c2_ + c1_);
}

void
CcsCeffDelayCalc::findCsmWaveform()
{
  for (size_t i = 0; i < region_count_; i++) {
    double t1 =
        output_waveforms_->voltageTime(in_slew_, region_ceff_[i], region_volts_[i]);
    double t2 = output_waveforms_->voltageTime(in_slew_, region_ceff_[i],
                                               region_volts_[i + 1]);
    region_begin_times_[i] = t1;
    region_end_times_[i] = t2;
    double time_offset = (i == 0)
        ? 0.0
        : t1 - (region_end_times_[i - 1] - region_time_offsets_[i - 1]);
    region_time_offsets_[i] = time_offset;

    if (i == 0)
      region_times_[i] = t1 - time_offset;
    region_times_[i + 1] = t2 - time_offset;
  }
}

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

void
CcsCeffDelayCalc::loadDelaySlew(const Pin *load_pin,
                                const LibertyLibrary *drvr_library,
                                double drvr_slew,
                                // Return values.
                                double &wire_delay,
                                double &load_slew)
{
  wire_delay = 0.0;
  load_slew = drvr_slew;

  bool elmore_exists = false;
  float elmore = 0.0;
  if (parasitic_ && parasitics_->isPiElmore(parasitic_))
    parasitics_->findElmore(parasitic_, load_pin, elmore, elmore_exists);

  if (elmore_exists &&
      (elmore == 0.0
       // Elmore delay is small compared to driver slew.
       || elmore < drvr_slew * 1e-3)) {
    wire_delay = elmore;
    load_slew = drvr_slew;
  }
  else
    loadDelaySlew(load_pin, drvr_slew, elmore, wire_delay, load_slew);

  thresholdAdjust(load_pin, drvr_library, drvr_rf_, wire_delay, load_slew);
}

void
CcsCeffDelayCalc::loadDelaySlew(const Pin *load_pin,
                                double &drvr_slew,
                                float elmore,
                                // Return values.
                                double &delay,
                                double &slew)
{
  for (size_t i = 0; i <= region_count_; i++) {
    region_ramp_times_[i] = region_times_[i];
    if (i < region_count_)
      region_ramp_slopes_[i] = (region_volts_[i + 1] - region_volts_[i])
          / (region_times_[i + 1] - region_times_[i]);
  }

  vl_fail_ = false;
  double t_vl = findVlTime(vl_, elmore);
  double t_vth = findVlTime(vth_, elmore);
  double t_vh = findVlTime(vh_, elmore);
  if (!vl_fail_) {
    delay = t_vth - region_times_[region_vth_idx_];
    slew = t_vh - t_vl;
  }
  else {
    delay = elmore;
    slew = drvr_slew;
    fail("load delay threshold crossing");
  }
  debugPrint(debug_, "ccs_dcalc", 2, "load {} delay {} slew {}",
             network_->pathName(load_pin), delayAsString(delay, this),
             delayAsString(slew, this));
}

// Elmore (one pole) response to ramp with slope slew.
static void
rampElmoreV(double t,
            double slew,
            double elmore,
            // Return values.
            double &v,
            double &dv_dt)
{
  double exp_t = 1.0 - exp2(-t / elmore);
  v = slew * (t - elmore * exp_t);
  // First derivative of loadVl dv/dt.
  dv_dt = slew * exp_t;
}

// Elmore (one pole) response to 2 segment ramps [0, vth] slew1, [vth, vdd] slew2.
void
CcsCeffDelayCalc::vLoad(double t,
                        double elmore,
                        // Return values.
                        double &vl,
                        double &dvl_dt)
{
  vl = 0.0;
  dvl_dt = 0.0;
  for (size_t i = 0; i < region_count_; i++) {
    double t_begin = region_ramp_times_[i];
    double t_end = region_ramp_times_[i + 1];
    double ramp_slope = region_ramp_slopes_[i];
    if (t >= t_begin) {
      double v, dv_dt;
      rampElmoreV(t - t_begin, ramp_slope, elmore, v, dv_dt);
      vl += v;
      dvl_dt += dv_dt;
    }
    if (t > t_end) {
      double v, dv_dt;
      rampElmoreV(t - t_end, ramp_slope, elmore, v, dv_dt);
      vl -= v;
      dvl_dt -= dv_dt;
    }
  }
}

// for debugging
double
CcsCeffDelayCalc::vLoad(double t,
                        double elmore)
{
  double vl1, dvl_dt;
  vLoad(t, elmore, vl1, dvl_dt);
  return vl1;
}

double
CcsCeffDelayCalc::findVlTime(double v,
                             double elmore)
{
  double t_init = region_ramp_times_[0];
  double t_final = region_ramp_times_[region_count_];
  auto [time, failed] =
    findRoot([&](double t, double &y, double &dy) {
      vLoad(t, elmore, y, dy);
      y -= v;
    },
      t_init, t_final + elmore * 3.0, .001, 20);
  vl_fail_ |= failed;
  return time;
}

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

// Waveform accessors for swig/tcl.

void
CcsCeffDelayCalc::watchPin(const Pin *pin)
{
  watch_pin_values_[pin] = FloatSeq();
}

void
CcsCeffDelayCalc::clearWatchPins()
{
  watch_pin_values_.clear();
}

PinSeq
CcsCeffDelayCalc::watchPins() const
{
  PinSeq pins;
  for (const auto &[pin, values] : watch_pin_values_)
    pins.push_back(pin);
  return pins;
}

Waveform
CcsCeffDelayCalc::watchWaveform(const Pin *pin)
{
  if (pin == drvr_pin_)
    return drvrWaveform();
  else
    return loadWaveform(pin);
}

Waveform
CcsCeffDelayCalc::drvrWaveform()
{
  if (output_waveforms_) {
    // Stitch together the ccs waveforms for each region.
    FloatSeq *drvr_times = new FloatSeq;
    FloatSeq *drvr_volts = new FloatSeq;
    for (size_t i = 0; i < region_count_; i++) {
      double t1 = region_begin_times_[i];
      double t2 = region_end_times_[i];
      size_t time_steps = 10;
      double time_step = (t2 - t1) / time_steps;
      double time_offset = region_time_offsets_[i];
      for (size_t s = 0; s <= time_steps; s++) {
        double t = t1 + s * time_step;
        drvr_times->push_back(t - time_offset);
        double v = output_waveforms_->timeVoltage(in_slew_, region_ceff_[i], t);
        if (drvr_rf_ == RiseFall::fall())
          v = vdd_ - v;
        drvr_volts->push_back(v);
      }
    }
    TableAxisPtr drvr_time_axis =
        std::make_shared<TableAxis>(TableAxisVariable::time, std::move(*drvr_times));
    delete drvr_times;
    Table drvr_table(drvr_volts, drvr_time_axis);
    return drvr_table;
  }
  else
    return Table();
}

Waveform
CcsCeffDelayCalc::loadWaveform(const Pin *load_pin)
{
  if (output_waveforms_) {
    bool elmore_exists = false;
    float elmore = 0.0;
    parasitics_->findElmore(parasitic_, load_pin, elmore, elmore_exists);
    if (elmore_exists) {
      FloatSeq *load_times = new FloatSeq;
      FloatSeq *load_volts = new FloatSeq;
      double t_vh = findVlTime(vh_, elmore);
      double dt = t_vh / 20.0;
      double v_final = vh_ + (vdd_ - vh_) * .8;
      double v = 0.0;
      for (double t = 0; v < v_final; t += dt) {
        load_times->push_back(t);

        double ignore;
        vLoad(t, elmore, v, ignore);
        double v1 = (drvr_rf_ == RiseFall::rise()) ? v : vdd_ - v;
        load_volts->push_back(v1);
      }
      TableAxisPtr load_time_axis = std::make_shared<TableAxis>(
          TableAxisVariable::time, std::move(*load_times));
      delete load_times;
      Table load_table(load_volts, load_time_axis);
      return load_table;
    }
  }
  return Table();
}

Waveform
CcsCeffDelayCalc::drvrRampWaveform(const Pin *in_pin,
                                   const RiseFall *in_rf,
                                   const Pin *drvr_pin,
                                   const RiseFall *drvr_rf,
                                   const Pin *load_pin,
                                   const Scene *scene,
                                   const MinMax *min_max)
{
  bool elmore_exists = false;
  float elmore = 0.0;
  if (parasitic_) {
    parasitics_->findElmore(parasitic_, load_pin, elmore, elmore_exists);
    bool dcalc_success =
        makeWaveformPreamble(in_pin, in_rf, drvr_pin, drvr_rf, scene, min_max);
    if (dcalc_success && elmore_exists) {
      FloatSeq *load_times = new FloatSeq;
      FloatSeq *load_volts = new FloatSeq;
      for (size_t j = 0; j <= region_count_; j++) {
        double t = region_ramp_times_[j];
        load_times->push_back(t);
        double v = 0.0;
        for (size_t i = 0; i < region_count_; i++) {
          double t_begin = region_ramp_times_[i];
          double t_end = region_ramp_times_[i + 1];
          double ramp_slope = region_ramp_slopes_[i];
          if (t >= t_begin)
            v += (t - t_begin) * ramp_slope;
          if (t > t_end)
            v -= (t - t_end) * ramp_slope;
        }
        double v1 = (drvr_rf == RiseFall::rise()) ? v : vdd_ - v;
        load_volts->push_back(v1);
      }
      TableAxisPtr load_time_axis = std::make_shared<TableAxis>(
          TableAxisVariable::time, std::move(*load_times));
      delete load_times;
      Table load_table(load_volts, load_time_axis);
      return load_table;
    }
  }
  return Table();
}

bool
CcsCeffDelayCalc::makeWaveformPreamble(const Pin *in_pin,
                                       const RiseFall *in_rf,
                                       const Pin *drvr_pin,
                                       const RiseFall *drvr_rf,
                                       const Scene *scene,
                                       const MinMax *min_max)
{
  Vertex *in_vertex = graph_->pinLoadVertex(in_pin);
  Vertex *drvr_vertex = graph_->pinDrvrVertex(drvr_pin);
  Edge *edge = nullptr;
  VertexInEdgeIterator edge_iter(drvr_vertex, graph_);
  while (edge_iter.hasNext()) {
    edge = edge_iter.next();
    Vertex *from_vertex = edge->from(graph_);
    const Pin *from_pin = from_vertex->pin();
    if (from_pin == in_pin)
      break;
  }
  if (edge) {
    TimingArc *arc = nullptr;
    for (TimingArc *arc1 : edge->timingArcSet()->arcs()) {
      if (arc1->fromEdge()->asRiseFall() == in_rf
          && arc1->toEdge()->asRiseFall() == drvr_rf) {
        arc = arc1;
        break;
      }
    }
    if (arc) {
      DcalcAPIndex slew_index = scene->dcalcAnalysisPtIndex(min_max);
      const Slew &in_slew = graph_->slew(in_vertex, in_rf, slew_index);
      parasitic_ = arc_delay_calc_->findParasitic(drvr_pin, drvr_rf, scene, min_max);
      if (parasitic_) {
        parasitics_->piModel(parasitic_, c2_, rpi_, c1_);
        LoadPinIndexMap load_pin_index_map =
            graph_delay_calc_->makeLoadPinIndexMap(drvr_vertex);
        gateDelay(drvr_pin, arc, in_slew, load_cap_, parasitic_, load_pin_index_map,
                  scene, min_max);
        return true;
      }
    }
  }
  return false;
}

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

std::string
CcsCeffDelayCalc::reportGateDelay(const Pin *drvr_pin,
                                  const TimingArc *arc,
                                  const Slew &in_slew,
                                  float load_cap,
                                  const Parasitic *parasitic,
                                  const LoadPinIndexMap &load_pin_index_map,
                                  const Scene *scene,
                                  const MinMax *min_max,
                                  int digits)
{
  Parasitic *pi_elmore = nullptr;
  const RiseFall *rf = arc->toEdge()->asRiseFall();
  if (parasitic && !parasitics_->isPiElmore(parasitic)) {
    pi_elmore =
        parasitics_->reduceToPiElmore(parasitic, drvr_pin_, rf, scene, min_max);
  }
  std::string report =
      table_dcalc_->reportGateDelay(drvr_pin, arc, in_slew, load_cap, pi_elmore,
                                    load_pin_index_map, scene, min_max, digits);
  parasitics_->deleteDrvrReducedParasitics(drvr_pin);
  return report;
}

void
CcsCeffDelayCalc::fail(std::string_view reason)
{
  // Report failures with a unique debug flag.
  if (debug_->check("ccs_dcalc", 1) || debug_->check("dcalc_error", 1))
    report_->report("delay_calc: CCS failed - {}", reason);
}

}  // namespace sta
