From da94fbe6ef6dd011583864d69cd36dd0c4f1d6df Mon Sep 17 00:00:00 2001 From: James Cherry Date: Tue, 19 May 2020 13:07:52 -0700 Subject: [PATCH 01/51] sdc non-incr graph annotation --- sdc/Sdc.cc | 464 +----------------------------------------------- sdc/SdcGraph.cc | 443 +++++++++++++++++++++++++++++++++++++++++++++ search/Sta.cc | 14 ++ 3 files changed, 461 insertions(+), 460 deletions(-) create mode 100644 sdc/SdcGraph.cc diff --git a/sdc/Sdc.cc b/sdc/Sdc.cc index 1219b86a..eac66166 100644 --- a/sdc/Sdc.cc +++ b/sdc/Sdc.cc @@ -46,7 +46,6 @@ #include "ClockGatingCheck.hh" #include "ClockGroups.hh" #include "DeratingFactors.hh" -#include "Graph.hh" #include "search/Levelize.hh" #include "Corner.hh" @@ -77,11 +76,6 @@ typedef Set PvtSet; static ExceptionThruSeq * clone(ExceptionThruSeq *thrus, Network *network); -static void -annotateGraphDisabledWireEdge(Pin *from_pin, - Pin *to_pin, - bool annotate, - Graph *graph); //////////////////////////////////////////////////////////////// @@ -409,15 +403,6 @@ Sdc::initInstancePvtMaps() //////////////////////////////////////////////////////////////// -void -Sdc::searchPreamble() -{ - ensureClkHpinDisables(); - ensureClkGroupExclusions(); -} - -//////////////////////////////////////////////////////////////// - bool Sdc::isConstrained(const Pin *pin) const { @@ -1444,49 +1429,6 @@ Sdc::makeClkHpinDisable(Clock *clk, } } -void -Sdc::ensureClkHpinDisables() -{ - if (!clk_hpin_disables_valid_) { - clk_hpin_disables_.deleteContentsClear(); - for (auto clk : clocks_) { - for (Pin *src : clk->pins()) { - if (network_->isHierarchical(src)) { - FindClkHpinDisables visitor(clk, network_, this); - visitHpinDrvrLoads(src, network_, &visitor); - // Disable fanouts from the src driver pins that do - // not go thru the hierarchical src pin. - for (Pin *lpin : clk->leafPins()) { - Vertex *vertex, *bidirect_drvr_vertex; - graph_->pinVertices(lpin, vertex, bidirect_drvr_vertex); - makeVertexClkHpinDisables(clk, vertex, visitor); - if (bidirect_drvr_vertex) - makeVertexClkHpinDisables(clk, bidirect_drvr_vertex, visitor); - } - } - } - } - clk_hpin_disables_valid_ = true; - } -} - -void -Sdc::makeVertexClkHpinDisables(Clock *clk, - Vertex *vertex, - FindClkHpinDisables &visitor) -{ - VertexOutEdgeIterator edge_iter(vertex, graph_); - while (edge_iter.hasNext()) { - Edge *edge = edge_iter.next(); - if (edge->isWire()) { - Pin *drvr = edge->from(graph_)->pin(); - Pin *load = edge->to(graph_)->pin(); - if (!visitor.drvrLoadExists(drvr, load)) - makeClkHpinDisable(clk, drvr, load); - } - } -} - void Sdc::clkHpinDisablesInvalid() { @@ -1573,8 +1515,6 @@ Sdc::setClockLatency(Clock *clk, clk_latencies_.insert(latency); } latency->setDelay(rf, min_max, delay); - if (pin && graph_ && network_->isHierarchical(pin)) - annotateHierClkLatency(pin, latency); // set_clock_latency removes set_propagated_clock on the same object. if (clk && pin == nullptr) @@ -1597,8 +1537,6 @@ void Sdc::deleteClockLatency(ClockLatency *latency) { const Pin *pin = latency->pin(); - if (pin && graph_ && network_->isHierarchical(pin)) - deannotateHierClkLatency(pin); clk_latencies_.erase(latency); delete latency; } @@ -2564,9 +2502,6 @@ Sdc::setDataCheck(Pin *from, data_checks_to_map_[to] = checks; } checks->insert(check); - - if (graph_) - annotateGraphConstrained(to, true); } void @@ -3000,8 +2935,6 @@ Sdc::makeOutputDelay(Pin *pin, output_delay_leaf_pin_map_[lpin] = leaf_outputs; } leaf_outputs->insert(output_delay); - if (graph_) - annotateGraphConstrained(lpin, true); } return output_delay; } @@ -3625,19 +3558,11 @@ void Sdc::disable(Port *port) { disabled_ports_.insert(port); - if (graph_) { - Pin *pin = network_->findPin(network_->topInstance(), port); - annotateGraphDisabled(pin, true); - } } void Sdc::removeDisable(Port *port) { - if (graph_) { - Pin *pin = network_->findPin(network_->topInstance(), port); - annotateGraphDisabled(pin, false); - } disabled_ports_.erase(port); } @@ -3659,9 +3584,6 @@ Sdc::disable(Instance *inst, disabled_inst->setDisabledTo(to); else disabled_inst->setDisabledAll(); - - if (graph_) - setEdgeDisabledInstPorts(disabled_inst, true); } void @@ -3671,8 +3593,6 @@ Sdc::removeDisable(Instance *inst, { DisabledInstancePorts *disabled_inst = disabled_inst_ports_.findKey(inst); if (disabled_inst) { - if (graph_) - setEdgeDisabledInstPorts(disabled_inst, false); if (from && to) disabled_inst->removeDisabledFromTo(from, to); else if (from) @@ -3692,8 +3612,6 @@ Sdc::disable(Pin *from, if (!disabled_wire_edges_.hasKey(&probe)) { PinPair *pair = new PinPair(from, to); disabled_wire_edges_.insert(pair); - if (graph_) - annotateGraphDisabledWireEdge(from, to, true, graph_); } } @@ -3701,7 +3619,6 @@ void Sdc::removeDisable(Pin *from, Pin *to) { - annotateGraphDisabledWireEdge(from, to, false, graph_); PinPair probe(from, to); disabled_wire_edges_.erase(&probe); } @@ -3759,8 +3676,6 @@ DisableEdgesThruHierPin::visit(Pin *drvr, if (!pairs_->hasKey(&probe)) { PinPair *pair = new PinPair(drvr, load); pairs_->insert(pair); - if (graph_) - annotateGraphDisabledWireEdge(drvr, load, true, graph_); } } @@ -3772,17 +3687,15 @@ Sdc::disable(Pin *pin) DisableEdgesThruHierPin visitor(&disabled_wire_edges_, graph_); visitDrvrLoadsThruHierPin(pin, network_, &visitor); } - else { + else disabled_pins_.insert(pin); - if (graph_) - annotateGraphDisabled(pin, true); - } } class RemoveDisableEdgesThruHierPin : public HierPinThruVisitor { public: - RemoveDisableEdgesThruHierPin(PinPairSet *pairs, Graph *graph); + RemoveDisableEdgesThruHierPin(PinPairSet *pairs, + Graph *graph); protected: virtual void visit(Pin *drvr, Pin *load); @@ -3806,8 +3719,6 @@ void RemoveDisableEdgesThruHierPin::visit(Pin *drvr, Pin *load) { - if (graph_) - annotateGraphDisabledWireEdge(drvr, load, false, graph_); PinPair probe(drvr, load); PinPair *pair = pairs_->findKey(&probe); if (pair) { @@ -3824,11 +3735,8 @@ Sdc::removeDisable(Pin *pin) RemoveDisableEdgesThruHierPin visitor(&disabled_wire_edges_, graph_); visitDrvrLoadsThruHierPin(pin, network_, &visitor); } - else { - if (graph_) - annotateGraphDisabled(pin, false); + else disabled_pins_.erase(pin); - } } bool @@ -6090,370 +5998,6 @@ Sdc::clkHpinDisablesChanged(Pin *pin) //////////////////////////////////////////////////////////////// -// Annotate constraints to the timing graph. -void -Sdc::annotateGraph(bool annotate) -{ - Stats stats(debug_); - // All output pins are considered constrained because - // they may be downstream from a set_min/max_delay -from that - // does not have a set_output_delay. - annotateGraphConstrainOutputs(); - annotateDisables(annotate); - annotateGraphOutputDelays(annotate); - annotateGraphDataChecks(annotate); - annotateHierClkLatency(annotate); - stats.report("Annotate constraints to graph"); -} - -void -Sdc::annotateGraphConstrainOutputs() -{ - 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)->isAnyOutput()) - annotateGraphConstrained(pin, true); - } - delete pin_iter; -} - -void -Sdc::annotateDisables(bool annotate) -{ - PinSet::Iterator pin_iter(disabled_pins_); - while (pin_iter.hasNext()) { - Pin *pin = pin_iter.next(); - annotateGraphDisabled(pin, annotate); - } - - if (!disabled_lib_ports_.empty()) { - VertexIterator vertex_iter(graph_); - while (vertex_iter.hasNext()) { - Vertex *vertex = vertex_iter.next(); - Pin *pin = vertex->pin(); - LibertyPort *port = network_->libertyPort(pin); - if (disabled_lib_ports_.hasKey(port)) - annotateGraphDisabled(pin, annotate); - } - } - - Instance *top_inst = network_->topInstance(); - PortSet::Iterator port_iter(disabled_ports_); - while (port_iter.hasNext()) { - Port *port = port_iter.next(); - Pin *pin = network_->findPin(top_inst, port); - annotateGraphDisabled(pin, annotate); - } - - PinPairSet::Iterator pair_iter(disabled_wire_edges_); - while (pair_iter.hasNext()) { - PinPair *pair = pair_iter.next(); - annotateGraphDisabledWireEdge(pair->first, pair->second, annotate, graph_); - } - - EdgeSet::Iterator edge_iter(disabled_edges_); - while (edge_iter.hasNext()) { - Edge *edge = edge_iter.next(); - edge->setIsDisabledConstraint(annotate); - } - - DisabledInstancePortsMap::Iterator disable_inst_iter(disabled_inst_ports_); - while (disable_inst_iter.hasNext()) { - DisabledInstancePorts *disabled_inst = disable_inst_iter.next(); - setEdgeDisabledInstPorts(disabled_inst, annotate); - } -} - -class DisableHpinEdgeVisitor : public HierPinThruVisitor -{ -public: - DisableHpinEdgeVisitor(bool annotate, Graph *graph); - virtual void visit(Pin *from_pin, - Pin *to_pin); - -protected: - bool annotate_; - Graph *graph_; - -private: - DISALLOW_COPY_AND_ASSIGN(DisableHpinEdgeVisitor); -}; - -DisableHpinEdgeVisitor::DisableHpinEdgeVisitor(bool annotate, - Graph *graph) : - HierPinThruVisitor(), - annotate_(annotate), - graph_(graph) -{ -} - -void -DisableHpinEdgeVisitor::visit(Pin *from_pin, - Pin *to_pin) -{ - annotateGraphDisabledWireEdge(from_pin, to_pin, annotate_, graph_); -} - -static void -annotateGraphDisabledWireEdge(Pin *from_pin, - Pin *to_pin, - bool annotate, - Graph *graph) -{ - Vertex *from_vertex = graph->pinDrvrVertex(from_pin); - Vertex *to_vertex = graph->pinLoadVertex(to_pin); - if (from_vertex && to_vertex) { - VertexOutEdgeIterator edge_iter(from_vertex, graph); - while (edge_iter.hasNext()) { - Edge *edge = edge_iter.next(); - if (edge->isWire() - && edge->to(graph) == to_vertex) - edge->setIsDisabledConstraint(annotate); - } - } -} - -void -Sdc::annotateGraphDisabled(const Pin *pin, - bool annotate) -{ - Vertex *vertex, *bidirect_drvr_vertex; - graph_->pinVertices(pin, vertex, bidirect_drvr_vertex); - vertex->setIsDisabledConstraint(annotate); - if (bidirect_drvr_vertex) - bidirect_drvr_vertex->setIsDisabledConstraint(annotate); -} - -void -Sdc::setEdgeDisabledInstPorts(DisabledInstancePorts *disabled_inst, - bool annotate) -{ - setEdgeDisabledInstPorts(disabled_inst, disabled_inst->instance(), annotate); -} - -void -Sdc::setEdgeDisabledInstPorts(DisabledPorts *disabled_port, - Instance *inst, - bool annotate) -{ - if (disabled_port->all()) { - InstancePinIterator *pin_iter = network_->pinIterator(inst); - while (pin_iter->hasNext()) { - Pin *pin = pin_iter->next(); - // set_disable_timing instance does not disable timing checks. - setEdgeDisabledInstFrom(pin, false, annotate); - } - delete pin_iter; - } - - // Disable from pins. - LibertyPortSet::Iterator from_iter(disabled_port->from()); - while (from_iter.hasNext()) { - LibertyPort *from_port = from_iter.next(); - Pin *from_pin = network_->findPin(inst, from_port); - if (from_pin) - setEdgeDisabledInstFrom(from_pin, true, annotate); - } - - // Disable to pins. - LibertyPortSet::Iterator to_iter(disabled_port->to()); - while (to_iter.hasNext()) { - LibertyPort *to_port = to_iter.next(); - Pin *to_pin = network_->findPin(inst, to_port); - if (to_pin) { - if (network_->direction(to_pin)->isAnyOutput()) { - Vertex *vertex = graph_->pinDrvrVertex(to_pin); - if (vertex) { - VertexInEdgeIterator edge_iter(vertex, graph_); - while (edge_iter.hasNext()) { - Edge *edge = edge_iter.next(); - edge->setIsDisabledConstraint(annotate); - } - } - } - } - } - - // Disable from/to pins. - LibertyPortPairSet::Iterator from_to_iter(disabled_port->fromTo()); - while (from_to_iter.hasNext()) { - LibertyPortPair *pair = from_to_iter.next(); - const LibertyPort *from_port = pair->first; - const LibertyPort *to_port = pair->second; - Pin *from_pin = network_->findPin(inst, from_port); - Pin *to_pin = network_->findPin(inst, to_port); - if (from_pin && network_->direction(from_pin)->isAnyInput() - && to_pin) { - Vertex *from_vertex = graph_->pinLoadVertex(from_pin); - Vertex *to_vertex = graph_->pinDrvrVertex(to_pin); - if (from_vertex && to_vertex) { - VertexOutEdgeIterator edge_iter(from_vertex, graph_); - while (edge_iter.hasNext()) { - Edge *edge = edge_iter.next(); - if (edge->to(graph_) == to_vertex) - edge->setIsDisabledConstraint(annotate); - } - } - } - } -} - -void -Sdc::setEdgeDisabledInstFrom(Pin *from_pin, - bool disable_checks, - bool annotate) -{ - if (network_->direction(from_pin)->isAnyInput()) { - Vertex *from_vertex = graph_->pinLoadVertex(from_pin); - if (from_vertex) { - VertexOutEdgeIterator edge_iter(from_vertex, graph_); - while (edge_iter.hasNext()) { - Edge *edge = edge_iter.next(); - if (disable_checks - || !edge->role()->isTimingCheck()) - edge->setIsDisabledConstraint(annotate); - } - } - } -} - -void -Sdc::annotateGraphOutputDelays(bool annotate) -{ - for (OutputDelay *output_delay : output_delays_) { - for (Pin *lpin : output_delay->leafPins()) - annotateGraphConstrained(lpin, annotate); - } -} - -void -Sdc::annotateGraphDataChecks(bool annotate) -{ - DataChecksMap::Iterator data_checks_iter(data_checks_to_map_); - while (data_checks_iter.hasNext()) { - DataCheckSet *checks = data_checks_iter.next(); - DataCheckSet::Iterator check_iter(checks); - // There may be multiple data checks on a single pin, - // but we only need to mark it as constrained once. - if (check_iter.hasNext()) { - DataCheck *check = check_iter.next(); - annotateGraphConstrained(check->to(), annotate); - } - } -} - -void -Sdc::annotateGraphConstrained(const PinSet *pins, - bool annotate) -{ - PinSet::ConstIterator pin_iter(pins); - while (pin_iter.hasNext()) { - const Pin *pin = pin_iter.next(); - annotateGraphConstrained(pin, annotate); - } -} - -void -Sdc::annotateGraphConstrained(const InstanceSet *insts, - bool annotate) -{ - InstanceSet::ConstIterator inst_iter(insts); - while (inst_iter.hasNext()) { - const Instance *inst = inst_iter.next(); - annotateGraphConstrained(inst, annotate); - } -} - -void -Sdc::annotateGraphConstrained(const Instance *inst, - bool annotate) -{ - InstancePinIterator *pin_iter = network_->pinIterator(inst); - while (pin_iter->hasNext()) { - Pin *pin = pin_iter->next(); - if (network_->direction(pin)->isAnyInput()) - annotateGraphConstrained(pin, annotate); - } - delete pin_iter; -} - -void -Sdc::annotateGraphConstrained(const Pin *pin, - bool annotate) -{ - Vertex *vertex, *bidirect_drvr_vertex; - graph_->pinVertices(pin, vertex, bidirect_drvr_vertex); - // Pin may be hierarchical and have no vertex. - if (vertex) - vertex->setIsConstrained(annotate); - if (bidirect_drvr_vertex) - bidirect_drvr_vertex->setIsConstrained(annotate); -} - -void -Sdc::annotateHierClkLatency(bool annotate) -{ - if (annotate) { - ClockLatencies::Iterator latency_iter(clk_latencies_); - while (latency_iter.hasNext()) { - ClockLatency *latency = latency_iter.next(); - const Pin *pin = latency->pin(); - if (pin && network_->isHierarchical(pin)) - annotateHierClkLatency(pin, latency); - } - } - else - edge_clk_latency_.clear(); -} - -void -Sdc::annotateHierClkLatency(const Pin *hpin, - ClockLatency *latency) -{ - EdgesThruHierPinIterator edge_iter(hpin, network_, graph_); - while (edge_iter.hasNext()) { - Edge *edge = edge_iter.next(); - edge_clk_latency_[edge] = latency; - } -} - -void -Sdc::deannotateHierClkLatency(const Pin *hpin) -{ - EdgesThruHierPinIterator edge_iter(hpin, network_, graph_); - while (edge_iter.hasNext()) { - Edge *edge = edge_iter.next(); - edge_clk_latency_.erase(edge); - } -} - -ClockLatency * -Sdc::clockLatency(Edge *edge) const -{ - return edge_clk_latency_.findKey(edge); -} - -void -Sdc::clockLatency(Edge *edge, - const RiseFall *rf, - const MinMax *min_max, - // Return values. - float &latency, - bool &exists) const -{ - ClockLatency *latencies = edge_clk_latency_.findKey(edge); - if (latencies) - latencies->delay(rf, min_max, latency, exists); - else { - latency = 0.0; - exists = false; - } -} - -//////////////////////////////////////////////////////////////// - // Find the leaf load pins corresponding to pin. // If the pin is hierarchical, the leaf pins are: // hierarchical input - load pins inside the hierarchical instance diff --git a/sdc/SdcGraph.cc b/sdc/SdcGraph.cc new file mode 100644 index 00000000..2237f6e7 --- /dev/null +++ b/sdc/SdcGraph.cc @@ -0,0 +1,443 @@ +// OpenSTA, Static Timing Analyzer +// Copyright (c) 2020, 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 . + +#include "SdcGraph.hh" + +#include "Graph.hh" + +namespace sta { + +static void +annotateGraphDisabledWireEdge(Pin *from_pin, + Pin *to_pin, + bool annotate, + Graph *graph); + +// Annotate constraints to the timing graph. +void +Sdc::annotateGraph(bool annotate) +{ + Stats stats(debug_); + // All output pins are considered constrained because + // they may be downstream from a set_min/max_delay -from that + // does not have a set_output_delay. + annotateGraphConstrainOutputs(); + annotateDisables(annotate); + annotateGraphOutputDelays(annotate); + annotateGraphDataChecks(annotate); + annotateHierClkLatency(annotate); + stats.report("Annotate constraints to graph"); +} + +void +Sdc::annotateGraphConstrainOutputs() +{ + 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)->isAnyOutput()) + annotateGraphConstrained(pin, true); + } + delete pin_iter; +} + +void +Sdc::annotateDisables(bool annotate) +{ + PinSet::Iterator pin_iter(disabled_pins_); + while (pin_iter.hasNext()) { + Pin *pin = pin_iter.next(); + annotateGraphDisabled(pin, annotate); + } + + if (!disabled_lib_ports_.empty()) { + VertexIterator vertex_iter(graph_); + while (vertex_iter.hasNext()) { + Vertex *vertex = vertex_iter.next(); + Pin *pin = vertex->pin(); + LibertyPort *port = network_->libertyPort(pin); + if (disabled_lib_ports_.hasKey(port)) + annotateGraphDisabled(pin, annotate); + } + } + + Instance *top_inst = network_->topInstance(); + PortSet::Iterator port_iter(disabled_ports_); + while (port_iter.hasNext()) { + Port *port = port_iter.next(); + Pin *pin = network_->findPin(top_inst, port); + annotateGraphDisabled(pin, annotate); + } + + PinPairSet::Iterator pair_iter(disabled_wire_edges_); + while (pair_iter.hasNext()) { + PinPair *pair = pair_iter.next(); + annotateGraphDisabledWireEdge(pair->first, pair->second, annotate, graph_); + } + + EdgeSet::Iterator edge_iter(disabled_edges_); + while (edge_iter.hasNext()) { + Edge *edge = edge_iter.next(); + edge->setIsDisabledConstraint(annotate); + } + + DisabledInstancePortsMap::Iterator disable_inst_iter(disabled_inst_ports_); + while (disable_inst_iter.hasNext()) { + DisabledInstancePorts *disabled_inst = disable_inst_iter.next(); + setEdgeDisabledInstPorts(disabled_inst, annotate); + } +} + +class DisableHpinEdgeVisitor : public HierPinThruVisitor +{ +public: + DisableHpinEdgeVisitor(bool annotate, Graph *graph); + virtual void visit(Pin *from_pin, + Pin *to_pin); + +protected: + bool annotate_; + Graph *graph_; + +private: + DISALLOW_COPY_AND_ASSIGN(DisableHpinEdgeVisitor); +}; + +DisableHpinEdgeVisitor::DisableHpinEdgeVisitor(bool annotate, + Graph *graph) : + HierPinThruVisitor(), + annotate_(annotate), + graph_(graph) +{ +} + +void +DisableHpinEdgeVisitor::visit(Pin *from_pin, + Pin *to_pin) +{ + annotateGraphDisabledWireEdge(from_pin, to_pin, annotate_, graph_); +} + +static void +annotateGraphDisabledWireEdge(Pin *from_pin, + Pin *to_pin, + bool annotate, + Graph *graph) +{ + Vertex *from_vertex = graph->pinDrvrVertex(from_pin); + Vertex *to_vertex = graph->pinLoadVertex(to_pin); + if (from_vertex && to_vertex) { + VertexOutEdgeIterator edge_iter(from_vertex, graph); + while (edge_iter.hasNext()) { + Edge *edge = edge_iter.next(); + if (edge->isWire() + && edge->to(graph) == to_vertex) + edge->setIsDisabledConstraint(annotate); + } + } +} + +void +Sdc::annotateGraphDisabled(const Pin *pin, + bool annotate) +{ + Vertex *vertex, *bidirect_drvr_vertex; + graph_->pinVertices(pin, vertex, bidirect_drvr_vertex); + vertex->setIsDisabledConstraint(annotate); + if (bidirect_drvr_vertex) + bidirect_drvr_vertex->setIsDisabledConstraint(annotate); +} + +void +Sdc::setEdgeDisabledInstPorts(DisabledInstancePorts *disabled_inst, + bool annotate) +{ + setEdgeDisabledInstPorts(disabled_inst, disabled_inst->instance(), annotate); +} + +void +Sdc::setEdgeDisabledInstPorts(DisabledPorts *disabled_port, + Instance *inst, + bool annotate) +{ + if (disabled_port->all()) { + InstancePinIterator *pin_iter = network_->pinIterator(inst); + while (pin_iter->hasNext()) { + Pin *pin = pin_iter->next(); + // set_disable_timing instance does not disable timing checks. + setEdgeDisabledInstFrom(pin, false, annotate); + } + delete pin_iter; + } + + // Disable from pins. + LibertyPortSet::Iterator from_iter(disabled_port->from()); + while (from_iter.hasNext()) { + LibertyPort *from_port = from_iter.next(); + Pin *from_pin = network_->findPin(inst, from_port); + if (from_pin) + setEdgeDisabledInstFrom(from_pin, true, annotate); + } + + // Disable to pins. + LibertyPortSet::Iterator to_iter(disabled_port->to()); + while (to_iter.hasNext()) { + LibertyPort *to_port = to_iter.next(); + Pin *to_pin = network_->findPin(inst, to_port); + if (to_pin) { + if (network_->direction(to_pin)->isAnyOutput()) { + Vertex *vertex = graph_->pinDrvrVertex(to_pin); + if (vertex) { + VertexInEdgeIterator edge_iter(vertex, graph_); + while (edge_iter.hasNext()) { + Edge *edge = edge_iter.next(); + edge->setIsDisabledConstraint(annotate); + } + } + } + } + } + + // Disable from/to pins. + LibertyPortPairSet::Iterator from_to_iter(disabled_port->fromTo()); + while (from_to_iter.hasNext()) { + LibertyPortPair *pair = from_to_iter.next(); + const LibertyPort *from_port = pair->first; + const LibertyPort *to_port = pair->second; + Pin *from_pin = network_->findPin(inst, from_port); + Pin *to_pin = network_->findPin(inst, to_port); + if (from_pin && network_->direction(from_pin)->isAnyInput() + && to_pin) { + Vertex *from_vertex = graph_->pinLoadVertex(from_pin); + Vertex *to_vertex = graph_->pinDrvrVertex(to_pin); + if (from_vertex && to_vertex) { + VertexOutEdgeIterator edge_iter(from_vertex, graph_); + while (edge_iter.hasNext()) { + Edge *edge = edge_iter.next(); + if (edge->to(graph_) == to_vertex) + edge->setIsDisabledConstraint(annotate); + } + } + } + } +} + +void +Sdc::setEdgeDisabledInstFrom(Pin *from_pin, + bool disable_checks, + bool annotate) +{ + if (network_->direction(from_pin)->isAnyInput()) { + Vertex *from_vertex = graph_->pinLoadVertex(from_pin); + if (from_vertex) { + VertexOutEdgeIterator edge_iter(from_vertex, graph_); + while (edge_iter.hasNext()) { + Edge *edge = edge_iter.next(); + if (disable_checks + || !edge->role()->isTimingCheck()) + edge->setIsDisabledConstraint(annotate); + } + } + } +} + +void +Sdc::annotateGraphOutputDelays(bool annotate) +{ + for (OutputDelay *output_delay : output_delays_) { + for (Pin *lpin : output_delay->leafPins()) + annotateGraphConstrained(lpin, annotate); + } +} + +void +Sdc::annotateGraphDataChecks(bool annotate) +{ + DataChecksMap::Iterator data_checks_iter(data_checks_to_map_); + while (data_checks_iter.hasNext()) { + DataCheckSet *checks = data_checks_iter.next(); + DataCheckSet::Iterator check_iter(checks); + // There may be multiple data checks on a single pin, + // but we only need to mark it as constrained once. + if (check_iter.hasNext()) { + DataCheck *check = check_iter.next(); + annotateGraphConstrained(check->to(), annotate); + } + } +} + +void +Sdc::annotateGraphConstrained(const PinSet *pins, + bool annotate) +{ + PinSet::ConstIterator pin_iter(pins); + while (pin_iter.hasNext()) { + const Pin *pin = pin_iter.next(); + annotateGraphConstrained(pin, annotate); + } +} + +void +Sdc::annotateGraphConstrained(const InstanceSet *insts, + bool annotate) +{ + InstanceSet::ConstIterator inst_iter(insts); + while (inst_iter.hasNext()) { + const Instance *inst = inst_iter.next(); + annotateGraphConstrained(inst, annotate); + } +} + +void +Sdc::annotateGraphConstrained(const Instance *inst, + bool annotate) +{ + InstancePinIterator *pin_iter = network_->pinIterator(inst); + while (pin_iter->hasNext()) { + Pin *pin = pin_iter->next(); + if (network_->direction(pin)->isAnyInput()) + annotateGraphConstrained(pin, annotate); + } + delete pin_iter; +} + +void +Sdc::annotateGraphConstrained(const Pin *pin, + bool annotate) +{ + Vertex *vertex, *bidirect_drvr_vertex; + graph_->pinVertices(pin, vertex, bidirect_drvr_vertex); + // Pin may be hierarchical and have no vertex. + if (vertex) + vertex->setIsConstrained(annotate); + if (bidirect_drvr_vertex) + bidirect_drvr_vertex->setIsConstrained(annotate); +} + +void +Sdc::annotateHierClkLatency(bool annotate) +{ + if (annotate) { + ClockLatencies::Iterator latency_iter(clk_latencies_); + while (latency_iter.hasNext()) { + ClockLatency *latency = latency_iter.next(); + const Pin *pin = latency->pin(); + if (pin && network_->isHierarchical(pin)) + annotateHierClkLatency(pin, latency); + } + } + else + edge_clk_latency_.clear(); +} + +void +Sdc::annotateHierClkLatency(const Pin *hpin, + ClockLatency *latency) +{ + EdgesThruHierPinIterator edge_iter(hpin, network_, graph_); + while (edge_iter.hasNext()) { + Edge *edge = edge_iter.next(); + edge_clk_latency_[edge] = latency; + } +} + +void +Sdc::deannotateHierClkLatency(const Pin *hpin) +{ + EdgesThruHierPinIterator edge_iter(hpin, network_, graph_); + while (edge_iter.hasNext()) { + Edge *edge = edge_iter.next(); + edge_clk_latency_.erase(edge); + } +} + +ClockLatency * +Sdc::clockLatency(Edge *edge) const +{ + return edge_clk_latency_.findKey(edge); +} + +void +Sdc::clockLatency(Edge *edge, + const RiseFall *rf, + const MinMax *min_max, + // Return values. + float &latency, + bool &exists) const +{ + ClockLatency *latencies = edge_clk_latency_.findKey(edge); + if (latencies) + latencies->delay(rf, min_max, latency, exists); + else { + latency = 0.0; + exists = false; + } +} + +void +Sdc::ensureClkHpinDisables() +{ + if (!clk_hpin_disables_valid_) { + clk_hpin_disables_.deleteContentsClear(); + for (auto clk : clocks_) { + for (Pin *src : clk->pins()) { + if (network_->isHierarchical(src)) { + FindClkHpinDisables visitor(clk, network_, this); + visitHpinDrvrLoads(src, network_, &visitor); + // Disable fanouts from the src driver pins that do + // not go thru the hierarchical src pin. + for (Pin *lpin : clk->leafPins()) { + Vertex *vertex, *bidirect_drvr_vertex; + graph_->pinVertices(lpin, vertex, bidirect_drvr_vertex); + makeVertexClkHpinDisables(clk, vertex, visitor); + if (bidirect_drvr_vertex) + makeVertexClkHpinDisables(clk, bidirect_drvr_vertex, visitor); + } + } + } + } + clk_hpin_disables_valid_ = true; + } +} + +void +Sdc::makeVertexClkHpinDisables(Clock *clk, + Vertex *vertex, + FindClkHpinDisables &visitor) +{ + VertexOutEdgeIterator edge_iter(vertex, graph_); + while (edge_iter.hasNext()) { + Edge *edge = edge_iter.next(); + if (edge->isWire()) { + Pin *drvr = edge->from(graph_)->pin(); + Pin *load = edge->to(graph_)->pin(); + if (!visitor.drvrLoadExists(drvr, load)) + makeClkHpinDisable(clk, drvr, load); + } + } +} + +//////////////////////////////////////////////////////////////// + +void +Sdc::searchPreamble() +{ + ensureClkHpinDisables(); + ensureClkGroupExclusions(); +} + +} // namespace diff --git a/search/Sta.cc b/search/Sta.cc index f0851495..109e9efd 100644 --- a/search/Sta.cc +++ b/search/Sta.cc @@ -1186,14 +1186,21 @@ Sta::setClockLatency(Clock *clk, const MinMaxAll *min_max, float delay) { + sdcChangedGraph(); sdc_->setClockLatency(clk, pin, rf, min_max, delay); search_->arrivalsInvalid(); } +void +Sta::sdcChangedGraph() +{ +} + void Sta::removeClockLatency(const Clock *clk, const Pin *pin) { + sdcChangedGraph(); sdc_->removeClockLatency(clk, pin); search_->arrivalsInvalid(); } @@ -1384,6 +1391,7 @@ Sta::setDataCheck(Pin *from, const SetupHoldAll *setup_hold, float margin) { + sdcChangedGraph(); sdc_->setDataCheck(from, from_rf, to, to_rf, clk, setup_hold,margin); search_->requiredInvalid(to); } @@ -1428,6 +1436,7 @@ Sta::disable(Instance *inst, LibertyPort *from, LibertyPort *to) { + sdcChangedGraph(); sdc_->disable(inst, from, to); if (from) { @@ -1456,6 +1465,7 @@ Sta::removeDisable(Instance *inst, LibertyPort *from, LibertyPort *to) { + sdcChangedGraph(); sdc_->removeDisable(inst, from, to); if (from) { @@ -1500,6 +1510,7 @@ Sta::removeDisable(LibertyCell *cell, void Sta::disable(LibertyPort *port) { + sdcChangedGraph(); sdc_->disable(port); disableAfter(); } @@ -1507,6 +1518,7 @@ Sta::disable(LibertyPort *port) void Sta::removeDisable(LibertyPort *port) { + sdcChangedGraph(); sdc_->removeDisable(port); disableAfter(); } @@ -1868,6 +1880,7 @@ Sta::setOutputDelay(Pin *pin, sdc_->setOutputDelay(pin, rf, clk, clk_rf, ref_pin, source_latency_included,network_latency_included, min_max, add, delay); + sdcChangedGraph(); search_->requiredInvalid(pin); } @@ -1879,6 +1892,7 @@ Sta::removeOutputDelay(Pin *pin, MinMaxAll *min_max) { sdc_->removeOutputDelay(pin, rf, clk, clk_rf, min_max); + sdcChangedGraph(); search_->arrivalInvalid(pin); } From 9075688077c186ec0620c791b26728de500832bf Mon Sep 17 00:00:00 2001 From: James Cherry Date: Thu, 25 Jun 2020 13:38:47 -0700 Subject: [PATCH 02/51] set_max_delay set_output_delay set_clock_uncertainty --- include/sta/PathEnd.hh | 7 +++++-- search/PathEnd.cc | 29 ++++++++++++++++++----------- search/ReportPath.cc | 34 ++++++++++++++++++++++------------ search/ReportPath.hh | 2 ++ 4 files changed, 47 insertions(+), 25 deletions(-) diff --git a/include/sta/PathEnd.hh b/include/sta/PathEnd.hh index c60d8fdc..e5f983d4 100644 --- a/include/sta/PathEnd.hh +++ b/include/sta/PathEnd.hh @@ -425,6 +425,7 @@ private: // Path constrained by an output delay. // If there is a reference pin, clk_path_ is the reference pin clock. +// If there is a path delay PathEndPathDelay is used instead of this. class PathEndOutputDelay : public PathEndClkConstrainedMcp { public: @@ -555,6 +556,7 @@ private: // Path constrained by set_min/max_delay. // "Clocked" when path delay ends at timing check pin. +// May end at output with set_output_delay. class PathEndPathDelay : public PathEndClkConstrained { public: @@ -587,6 +589,7 @@ public: virtual PathDelay *pathDelay() const { return path_delay_; } virtual ArcDelay margin(const StaState *sta) const; virtual float sourceClkOffset(const StaState *sta) const; + virtual ClockEdge *targetClkEdge(const StaState *sta) const; virtual float targetClkTime(const StaState *sta) const; virtual Arrival targetClkArrivalNoCrpr(const StaState *sta) const; virtual float targetClkOffset(const StaState *sta) const; @@ -594,6 +597,7 @@ public: virtual Required requiredTime(const StaState *sta) const; virtual int exceptPathCmp(const PathEnd *path_end, const StaState *sta) const; + bool hasOutputDelay() const { return output_delay_ != nullptr; } protected: PathEndPathDelay(PathDelay *path_delay, @@ -610,8 +614,7 @@ protected: PathDelay *path_delay_; TimingArc *check_arc_; Edge *check_edge_; - // Output delay is nullptr when there is no timing check or output - // delay at the endpoint. + // Output delay is nullptr when there is no output delay at the endpoint. OutputDelay *output_delay_; // Source clk arrival for set_min/max_delay -ignore_clk_latency. Arrival src_clk_arrival_; diff --git a/search/PathEnd.cc b/search/PathEnd.cc index 57a014c1..786a8af0 100644 --- a/search/PathEnd.cc +++ b/search/PathEnd.cc @@ -1845,6 +1845,17 @@ PathEnd::pathDelaySrcClkOffset(const PathRef &path, return offset; } +ClockEdge * +PathEndPathDelay::targetClkEdge(const StaState *sta) const +{ + if (!clk_path_.isNull()) + return clk_path_.clkEdge(sta); + else if (output_delay_) + return output_delay_->clkEdge(); + else + return nullptr; +} + float PathEndPathDelay::targetClkTime(const StaState *sta) const { @@ -1858,14 +1869,12 @@ PathEndPathDelay::targetClkTime(const StaState *sta) const Arrival PathEndPathDelay::targetClkArrivalNoCrpr(const StaState *sta) const { - if (!clk_path_.isNull()) { - ClockEdge *tgt_clk_edge = targetClkEdge(sta); - if (tgt_clk_edge) - return targetClkDelay(sta) - + targetClkUncertainty(sta); - else - return clk_path_.arrival(sta); - } + ClockEdge *tgt_clk_edge = targetClkEdge(sta); + if (tgt_clk_edge) + return targetClkDelay(sta) + + targetClkUncertainty(sta); + else if (!clk_path_.isNull()) + return clk_path_.arrival(sta); else return 0.0; } @@ -1887,9 +1896,7 @@ PathEndPathDelay::requiredTime(const StaState *sta) const return src_clk_arrival_ + delay + margin(sta); } else { - Arrival tgt_clk_arrival = 0.0; - if (!clk_path_.isNull()) - tgt_clk_arrival = targetClkArrival(sta); + 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. diff --git a/search/ReportPath.cc b/search/ReportPath.cc index 07b6e3d8..456cb2fd 100644 --- a/search/ReportPath.cc +++ b/search/ReportPath.cc @@ -705,12 +705,16 @@ void ReportPath::reportEndpoint(const PathEndPathDelay *end, string &result) { - Instance *inst = network_->instance(end->vertex(this)->pin()); - const char *inst_name = cmd_network_->pathName(inst); - string clk_name = tgtClkName(end); - const char *reg_desc = clkRegLatchDesc(end); - auto reason = stdstrPrint("%s clocked by %s", reg_desc, clk_name.c_str()); - reportEndpoint(inst_name, reason, result); + if (end->hasOutputDelay()) + reportEndpointOutputDelay(end, result); + else { + Instance *inst = network_->instance(end->vertex(this)->pin()); + const char *inst_name = cmd_network_->pathName(inst); + string clk_name = tgtClkName(end); + const char *reg_desc = clkRegLatchDesc(end); + auto reason = stdstrPrint("%s clocked by %s", reg_desc, clk_name.c_str()); + reportEndpoint(inst_name, reason, result); + } } void @@ -746,13 +750,11 @@ ReportPath::reportFull(const PathEndPathDelay *end, float delay = path_delay->delay(); reportLine(delay_msg.c_str(), delay, delay, early_late, result); if (!path_delay->ignoreClkLatency()) { - const Path *tgt_clk_path = end->targetClkPath(); - if (tgt_clk_path) { - float delay = 0.0; - if (path_delay) - delay = path_delay->delay(); + Clock *tgt_clk = end->targetClk(this); + if (tgt_clk) { + const Path *tgt_clk_path = end->targetClkPath(); if (reportClkPath() - && isPropagated(tgt_clk_path)) + && isPropagated(tgt_clk_path, tgt_clk)) reportTgtClk(end, delay, result); else { Arrival tgt_clk_delay = end->targetClkDelay(this); @@ -832,6 +834,13 @@ ReportPath::reportFull(const PathEndOutputDelay *end, void ReportPath::reportEndpoint(const PathEndOutputDelay *end, string &result) +{ + reportEndpointOutputDelay(end, result); +} + +void +ReportPath::reportEndpointOutputDelay(const PathEndClkConstrained *end, + string &result) { Vertex *vertex = end->vertex(this); Pin *pin = vertex->pin(); @@ -851,6 +860,7 @@ ReportPath::reportEndpoint(const PathEndOutputDelay *end, if (tgt_clk) { string clk_name = tgtClkName(end); auto reason = stdstrPrint("internal path endpoint clocked by %s", clk_name.c_str()); + reportEndpoint(pin_name, reason, result); } else diff --git a/search/ReportPath.hh b/search/ReportPath.hh index 8f24cc9d..c7ae2e75 100644 --- a/search/ReportPath.hh +++ b/search/ReportPath.hh @@ -203,6 +203,8 @@ protected: string &result); void reportEndpoint(const PathEndOutputDelay *end, string &result); + void reportEndpointOutputDelay(const PathEndClkConstrained *end, + string &result); void reportEndpoint(const PathEndPathDelay *end, string &result); void reportEndpoint(const PathEndGatedClock *end, From 1f0d7ffddeb55edc068f24bdf55ae58dba0bbb4b Mon Sep 17 00:00:00 2001 From: James Cherry Date: Sat, 27 Jun 2020 16:24:17 -0700 Subject: [PATCH 03/51] report_net pin locations --- tcl/Network.tcl | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tcl/Network.tcl b/tcl/Network.tcl index 352207a2..b35daed8 100644 --- a/tcl/Network.tcl +++ b/tcl/Network.tcl @@ -382,7 +382,7 @@ proc report_net_pin { pin verbose corner digits } { puts -nonewline [port_capacitance_str $liberty_port $digits] } } - puts "" + puts "[pin_location_str $pin]" } elseif [$pin is_top_level_port] { puts -nonewline " [get_full_name $pin] [pin_direction $pin] port" if { $verbose } { @@ -404,12 +404,17 @@ proc report_net_pin { pin verbose corner digits } { puts -nonewline " pin [capacitances_str $cap_r_min $cap_r_max $cap_f_min $cap_f_max $digits]" } } - puts "" + puts "[pin_location_str $pin]" } elseif [$pin is_hierarchical] { puts " [get_full_name $pin] [pin_direction $pin]" } } +# default handler +proc pin_location_str { pin } { + return "" +} + ################################################################ proc report_pin_ { pin } { From eefd98482ab1ebfc2b75f648c2d9318471a13449 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Tue, 30 Jun 2020 19:24:30 -0700 Subject: [PATCH 04/51] report_checks -max_fanout non-liberty ports --- search/CheckFanoutLimits.cc | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/search/CheckFanoutLimits.cc b/search/CheckFanoutLimits.cc index 352482f2..fcb7ea3e 100644 --- a/search/CheckFanoutLimits.cc +++ b/search/CheckFanoutLimits.cc @@ -182,15 +182,19 @@ CheckFanoutLimits::fanoutLoad(const Pin *pin) const Pin *pin = pin_iter->next(); if (network->isLoad(pin)) { LibertyPort *port = network->libertyPort(pin); - float fanout_load; - bool exists; - port->fanoutLoad(fanout_load, exists); - if (!exists) { - LibertyLibrary *lib = port->libertyLibrary(); - lib->defaultFanoutLoad(fanout_load, exists); + if (port) { + float fanout_load; + bool exists; + port->fanoutLoad(fanout_load, exists); + if (!exists) { + LibertyLibrary *lib = port->libertyLibrary(); + lib->defaultFanoutLoad(fanout_load, exists); + } + if (exists) + fanout += fanout_load; } - if (exists) - fanout += fanout_load; + else + fanout += 1; } } delete pin_iter; From e8639dfafdaa2bdf62f89b17b15bf2376f281766 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Thu, 2 Jul 2020 06:28:14 -0700 Subject: [PATCH 05/51] get_property clock is_generated --- search/Property.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/search/Property.cc b/search/Property.cc index 2877fc86..386d551f 100644 --- a/search/Property.cc +++ b/search/Property.cc @@ -916,7 +916,9 @@ getProperty(Clock *clk, else if (stringEqual(property, "sources")) return PropertyValue(&clk->pins()); else if (stringEqual(property, "propagated")) - return PropertyValue(clk->isPropagated() ? "1" : "0"); + return PropertyValue(clk->isPropagated()); + else if (stringEqual(property, "is_generated")) + return PropertyValue(clk->isGenerated()); else throw PropertyUnknown("clock", property); } From d35e38a450c9229927cf179c7d6619b9a9817acc Mon Sep 17 00:00:00 2001 From: James Cherry Date: Thu, 2 Jul 2020 08:45:33 -0700 Subject: [PATCH 06/51] Sta::checkFanout, checkCapacitance no preamble --- search/Sta.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/search/Sta.cc b/search/Sta.cc index 42e42b14..c18a9d9f 100644 --- a/search/Sta.cc +++ b/search/Sta.cc @@ -4962,7 +4962,6 @@ Sta::checkFanout(const Pin *pin, float &limit, float &slack) { - checkFanoutLimitPreamble(); check_fanout_limits_->checkFanout(pin, min_max, fanout, limit, slack); } @@ -5042,7 +5041,6 @@ Sta::checkCapacitance(const Pin *pin, float &limit, float &slack) { - checkCapacitanceLimitPreamble(); check_capacitance_limits_->checkCapacitance(pin, corner, min_max, corner1, rf, capacitance, limit, slack); From 338a82add442c69b208da77e9ea5633774063ffb Mon Sep 17 00:00:00 2001 From: James Cherry Date: Fri, 3 Jul 2020 10:16:10 -0700 Subject: [PATCH 07/51] pocv comparisons use +/- sigma --- graph/DelayNormal1.cc | 63 ++++++++++++------- graph/DelayNormal2.cc | 142 +++++++++++++++++++++++------------------- 2 files changed, 119 insertions(+), 86 deletions(-) diff --git a/graph/DelayNormal1.cc b/graph/DelayNormal1.cc index f0d58659..344a3c1f 100644 --- a/graph/DelayNormal1.cc +++ b/graph/DelayNormal1.cc @@ -24,6 +24,8 @@ #include "Fuzzy.hh" #include "Units.hh" #include "StaState.hh" +// temporary hack +#include "Sta.hh" // SSTA compilation. #if (SSTA == 1) @@ -162,32 +164,31 @@ Delay::operator-=(const Delay &delay) bool Delay::operator==(const Delay &delay) const { - return mean_ == delay.mean_ - && sigma2_ == delay.sigma2_; + return fuzzyEqual(*this, delay); } bool Delay::operator>(const Delay &delay) const { - return mean_ > delay.mean_; + return fuzzyGreater(*this, delay); } bool Delay::operator>=(const Delay &delay) const { - return mean_ >= delay.mean_; + return fuzzyGreaterEqual(*this, delay); } bool Delay::operator<(const Delay &delay) const { - return mean_ < delay.mean_; + return fuzzyLess(*this, delay); } bool Delay::operator<=(const Delay &delay) const { - return mean_ <= delay.mean_; + return fuzzyLessEqual(*this, delay); } //////////////////////////////////////////////////////////////// @@ -241,28 +242,36 @@ bool fuzzyLess(const Delay &delay1, const Delay &delay2) { - return fuzzyLess(delay1.mean(), delay2.mean()); + Sta *sta = Sta::sta(); + return fuzzyLess(delayAsFloat(delay1, EarlyLate::early(), sta), + delayAsFloat(delay2, EarlyLate::early(), sta)); } bool fuzzyLess(const Delay &delay1, float delay2) { - return fuzzyLess(delay1.mean(), delay2); + Sta *sta = Sta::sta(); + return fuzzyLess(delayAsFloat(delay1, EarlyLate::early(), sta), + delay2); } bool fuzzyLessEqual(const Delay &delay1, const Delay &delay2) { - return fuzzyLessEqual(delay1.mean(), delay2.mean()); + Sta *sta = Sta::sta(); + return fuzzyLessEqual(delayAsFloat(delay1, EarlyLate::early(), sta), + delayAsFloat(delay2, EarlyLate::early(), sta)); } bool fuzzyLessEqual(const Delay &delay1, - float delay2) + float delay2) { - return fuzzyLessEqual(delay1.mean(), delay2); + Sta *sta = Sta::sta(); + return fuzzyLessEqual(delayAsFloat(delay1, EarlyLate::early(), sta), + delay2); } bool @@ -271,37 +280,45 @@ fuzzyLessEqual(const Delay &delay1, const MinMax *min_max) { if (min_max == MinMax::max()) - return fuzzyLessEqual(delay1.mean(), delay2.mean()); + return fuzzyLessEqual(delay1, delay2); else - return fuzzyGreaterEqual(delay1.mean(), delay2.mean()); + return fuzzyGreaterEqual(delay1, delay2); } bool fuzzyGreater(const Delay &delay1, const Delay &delay2) { - return fuzzyGreater(delay1.mean(), delay2.mean()); + Sta *sta = Sta::sta(); + return fuzzyGreater(delayAsFloat(delay1, EarlyLate::late(), sta), + delayAsFloat(delay2, EarlyLate::late(), sta)); } bool fuzzyGreater(const Delay &delay1, float delay2) { - return fuzzyGreater(delay1.mean(), delay2); + Sta *sta = Sta::sta(); + return fuzzyGreater(delayAsFloat(delay1, EarlyLate::late(), sta), + delay2); } bool fuzzyGreaterEqual(const Delay &delay1, const Delay &delay2) { - return fuzzyGreaterEqual(delay1.mean(), delay2.mean()); + Sta *sta = Sta::sta(); + return fuzzyGreaterEqual(delayAsFloat(delay1, EarlyLate::late(), sta), + delayAsFloat(delay2, EarlyLate::late(), sta)); } bool fuzzyGreaterEqual(const Delay &delay1, float delay2) { - return fuzzyGreaterEqual(delay1.mean(), delay2); + Sta *sta = Sta::sta(); + return fuzzyGreaterEqual(delayAsFloat(delay1, EarlyLate::late(), sta), + delay2); } bool @@ -310,9 +327,9 @@ fuzzyGreater(const Delay &delay1, const MinMax *min_max) { if (min_max == MinMax::max()) - return fuzzyGreater(delay1.mean(), delay2.mean()); + return fuzzyGreater(delay1, delay2); else - return fuzzyLess(delay1.mean(), delay2.mean()); + return fuzzyLess(delay1, delay2); } bool @@ -321,9 +338,9 @@ fuzzyGreaterEqual(const Delay &delay1, const MinMax *min_max) { if (min_max == MinMax::max()) - return fuzzyGreaterEqual(delay1.mean(), delay2.mean()); + return fuzzyGreaterEqual(delay1, delay2); else - return fuzzyLessEqual(delay1.mean(), delay2.mean()); + return fuzzyLessEqual(delay1, delay2); } bool @@ -332,9 +349,9 @@ fuzzyLess(const Delay &delay1, const MinMax *min_max) { if (min_max == MinMax::max()) - return fuzzyLess(delay1.mean(), delay2.mean()); + return fuzzyLess(delay1, delay2); else - return fuzzyGreater(delay1.mean(), delay2.mean()); + return fuzzyGreater(delay1, delay2); } float diff --git a/graph/DelayNormal2.cc b/graph/DelayNormal2.cc index db5a7eca..8b6f02ba 100644 --- a/graph/DelayNormal2.cc +++ b/graph/DelayNormal2.cc @@ -190,25 +190,25 @@ Delay::operator==(const Delay &delay) const bool Delay::operator>(const Delay &delay) const { - return mean_ > delay.mean_; + return mean_ + sqrt(sigma2_late_) > delay.mean_ + sqrt(delay.sigma2_late_); } bool Delay::operator>=(const Delay &delay) const { - return mean_ >= delay.mean_; + return mean_ + sqrt(sigma2_late_) >= delay.mean_ + sqrt(delay.sigma2_late_); } bool Delay::operator<(const Delay &delay) const { - return mean_ < delay.mean_; + return mean_ - sqrt(sigma2_early_) < delay.mean_ - sqrt(delay.sigma2_early_); } bool Delay::operator<=(const Delay &delay) const { - return mean_ <= delay.mean_; + return mean_ - sqrt(sigma2_early_) <= delay.mean_ - sqrt(delay.sigma2_early_); } //////////////////////////////////////////////////////////////// @@ -265,28 +265,36 @@ bool fuzzyLess(const Delay &delay1, const Delay &delay2) { - return fuzzyLess(delay1.mean(), delay2.mean()); + Sta *sta = Sta::sta(); + return fuzzyLess(delayAsFloat(delay1, EarlyLate::early(), sta), + delayAsFloat(delay2, EarlyLate::early(), sta)); } bool fuzzyLess(const Delay &delay1, float delay2) { - return fuzzyLess(delay1.mean(), delay2); + Sta *sta = Sta::sta(); + return fuzzyLess(delayAsFloat(delay1, EarlyLate::early(), sta), + delay2); } bool fuzzyLessEqual(const Delay &delay1, const Delay &delay2) { - return fuzzyLessEqual(delay1.mean(), delay2.mean()); + Sta *sta = Sta::sta(); + return fuzzyLessEqual(delayAsFloat(delay1, EarlyLate::early(), sta), + delayAsFloat(delay2, EarlyLate::early(), sta)); } bool fuzzyLessEqual(const Delay &delay1, float delay2) { - return fuzzyLessEqual(delay1.mean(), delay2); + Sta *sta = Sta::sta(); + return fuzzyLessEqual(delayAsFloat(delay1, EarlyLate::early(), sta), + delay2); } bool @@ -295,37 +303,45 @@ fuzzyLessEqual(const Delay &delay1, const MinMax *min_max) { if (min_max == MinMax::max()) - return fuzzyLessEqual(delay1.mean(), delay2.mean()); + return fuzzyLessEqual(delay1, delay2); else - return fuzzyGreaterEqual(delay1.mean(), delay2.mean()); + return fuzzyGreaterEqual(delay1, delay2); } bool fuzzyGreater(const Delay &delay1, const Delay &delay2) { - return fuzzyGreater(delay1.mean(), delay2.mean()); + Sta *sta = Sta::sta(); + return fuzzyGreater(delayAsFloat(delay1, EarlyLate::late(), sta), + delayAsFloat(delay2, EarlyLate::late(), sta)); } bool fuzzyGreater(const Delay &delay1, float delay2) { - return fuzzyGreater(delay1.mean(), delay2); + Sta *sta = Sta::sta(); + return fuzzyGreaterEqual(delayAsFloat(delay1, EarlyLate::late(), sta), + delayAsFloat(delay2, EarlyLate::late(), sta)); } bool fuzzyGreaterEqual(const Delay &delay1, const Delay &delay2) { - return fuzzyGreaterEqual(delay1.mean(), delay2.mean()); + Sta *sta = Sta::sta(); + return fuzzyGreaterEqual(delayAsFloat(delay1, EarlyLate::late(), sta), + delay2); } bool fuzzyGreaterEqual(const Delay &delay1, float delay2) { - return fuzzyGreaterEqual(delay1.mean(), delay2); + Sta *sta = Sta::sta(); + return fuzzyGreaterEqual(delayAsFloat(delay1, EarlyLate::late(), sta), + delay2); } bool @@ -334,9 +350,9 @@ fuzzyGreater(const Delay &delay1, const MinMax *min_max) { if (min_max == MinMax::max()) - return fuzzyGreater(delay1.mean(), delay2.mean()); + return fuzzyGreater(delay1, delay2); else - return fuzzyLess(delay1.mean(), delay2.mean()); + return fuzzyLess(delay1, delay2); } bool @@ -345,9 +361,9 @@ fuzzyGreaterEqual(const Delay &delay1, const MinMax *min_max) { if (min_max == MinMax::max()) - return fuzzyGreaterEqual(delay1.mean(), delay2.mean()); + return fuzzyGreaterEqual(delay1, delay2); else - return fuzzyLessEqual(delay1.mean(), delay2.mean()); + return fuzzyLessEqual(delay1, delay2); } bool @@ -356,52 +372,9 @@ fuzzyLess(const Delay &delay1, const MinMax *min_max) { if (min_max == MinMax::max()) - return fuzzyLess(delay1.mean(), delay2.mean()); + return fuzzyLess(delay1, delay2); else - return fuzzyGreater(delay1.mean(), delay2.mean()); -} - -Delay -operator+(float delay1, - const Delay &delay2) -{ - return Delay(delay1 + delay2.mean(), - delay2.sigma2Early(), - delay2.sigma2Late()); -} - -Delay -operator/(float delay1, - const Delay &delay2) -{ - return Delay(delay1 / delay2.mean(), - delay2.sigma2Early(), - delay2.sigma2Late()); -} - -Delay -operator*(const Delay &delay1, - float delay2) -{ - return Delay(delay1.mean() * delay2, - delay1.sigma2Early() * delay2 * delay2, - delay1.sigma2Late() * delay2 * delay2); -} - -Delay -delayRemove(const Delay &delay1, - const Delay &delay2) -{ - return Delay(delay1.mean() - delay2.mean(), - delay1.sigma2Early() - delay2.sigma2Early(), - delay1.sigma2Late() - delay2.sigma2Late()); -} - -float -delayRatio(const Delay &delay1, - const Delay &delay2) -{ - return delay1.mean() / delay2.mean(); + return fuzzyGreater(delay1, delay2); } float @@ -463,6 +436,49 @@ delayAsString(const Delay &delay, return sta->units()->timeUnit()->asString(mean_sigma, digits); } +Delay +delayRemove(const Delay &delay1, + const Delay &delay2) +{ + return Delay(delay1.mean() - delay2.mean(), + delay1.sigma2Early() - delay2.sigma2Early(), + delay1.sigma2Late() - delay2.sigma2Late()); +} + +float +delayRatio(const Delay &delay1, + const Delay &delay2) +{ + return delay1.mean() / delay2.mean(); +} + +Delay +operator+(float delay1, + const Delay &delay2) +{ + return Delay(delay1 + delay2.mean(), + delay2.sigma2Early(), + delay2.sigma2Late()); +} + +Delay +operator/(float delay1, + const Delay &delay2) +{ + return Delay(delay1 / delay2.mean(), + delay2.sigma2Early(), + delay2.sigma2Late()); +} + +Delay +operator*(const Delay &delay1, + float delay2) +{ + return Delay(delay1.mean() * delay2, + delay1.sigma2Early() * delay2 * delay2, + delay1.sigma2Late() * delay2 * delay2); +} + } // namespace #endif // (SSTA == 2) From 220f280c0224aff4d1f5e3a97a1524b7f637f839 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Fri, 3 Jul 2020 12:11:23 -0700 Subject: [PATCH 08/51] Delay*.hh protects --- graph/DelayNormal1.cc | 6 +++--- graph/DelayNormal2.cc | 6 +++--- include/sta/Delay.hh | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/graph/DelayNormal1.cc b/graph/DelayNormal1.cc index 344a3c1f..1042bbdb 100644 --- a/graph/DelayNormal1.cc +++ b/graph/DelayNormal1.cc @@ -14,6 +14,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +// SSTA compilation. +#if (SSTA == 1) + #include "Delay.hh" #include // sqrt @@ -27,9 +30,6 @@ // temporary hack #include "Sta.hh" -// SSTA compilation. -#if (SSTA == 1) - namespace sta { inline float diff --git a/graph/DelayNormal2.cc b/graph/DelayNormal2.cc index 8b6f02ba..9ab1432a 100644 --- a/graph/DelayNormal2.cc +++ b/graph/DelayNormal2.cc @@ -14,6 +14,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +// SSTA compilation. +#if (SSTA == 2) + #include "Delay.hh" #include // sqrt @@ -25,9 +28,6 @@ #include "Units.hh" #include "StaState.hh" -// SSTA compilation. -#if (SSTA == 2) - namespace sta { inline float diff --git a/include/sta/Delay.hh b/include/sta/Delay.hh index 06ca2929..5b68ae8c 100644 --- a/include/sta/Delay.hh +++ b/include/sta/Delay.hh @@ -14,10 +14,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "StaConfig.hh" - #pragma once +#include "StaConfig.hh" + #if (SSTA == 1) // Delays are Normal PDFs. #include "DelayNormal1.hh" From e7ed3170f32c8a52961a3ee2290afec5294b90f8 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Fri, 3 Jul 2020 16:56:15 -0700 Subject: [PATCH 09/51] write_verilog power/ground port dcls --- verilog/VerilogWriter.cc | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/verilog/VerilogWriter.cc b/verilog/VerilogWriter.cc index 52ffc0f4..ed475971 100644 --- a/verilog/VerilogWriter.cc +++ b/verilog/VerilogWriter.cc @@ -165,12 +165,20 @@ VerilogWriter::verilogPortDir(PortDirection *dir) return "input"; else if (dir == PortDirection::output()) return "output"; - else if (dir == PortDirection::bidirect()) - return "inout"; else if (dir == PortDirection::tristate()) return "output"; - else + else if (dir == PortDirection::bidirect()) + return "inout"; + else if (dir == PortDirection::power()) + return "input"; + else if (dir == PortDirection::ground()) + return "input"; + else if (dir == PortDirection::internal()) + return nullptr; + else { + internalError("unknown port direction"); return nullptr; + } } void From 2b30d90555298b268f047f792f04c55498c16ac1 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Fri, 3 Jul 2020 18:19:26 -0700 Subject: [PATCH 10/51] DelayNormal.cc condition --- graph/DelayNormal1.cc | 6 +++--- graph/DelayNormal2.cc | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/graph/DelayNormal1.cc b/graph/DelayNormal1.cc index 1042bbdb..344a3c1f 100644 --- a/graph/DelayNormal1.cc +++ b/graph/DelayNormal1.cc @@ -14,9 +14,6 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -// SSTA compilation. -#if (SSTA == 1) - #include "Delay.hh" #include // sqrt @@ -30,6 +27,9 @@ // temporary hack #include "Sta.hh" +// SSTA compilation. +#if (SSTA == 1) + namespace sta { inline float diff --git a/graph/DelayNormal2.cc b/graph/DelayNormal2.cc index 9ab1432a..8b6f02ba 100644 --- a/graph/DelayNormal2.cc +++ b/graph/DelayNormal2.cc @@ -14,9 +14,6 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -// SSTA compilation. -#if (SSTA == 2) - #include "Delay.hh" #include // sqrt @@ -28,6 +25,9 @@ #include "Units.hh" #include "StaState.hh" +// SSTA compilation. +#if (SSTA == 2) + namespace sta { inline float From 535a09edcc4e0e2d0bc363938bd72f8d7cd5db59 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Fri, 3 Jul 2020 18:19:39 -0700 Subject: [PATCH 11/51] get_cells -of_objects ports --- tcl/Sdc.tcl | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tcl/Sdc.tcl b/tcl/Sdc.tcl index 1839abc3..9566572f 100644 --- a/tcl/Sdc.tcl +++ b/tcl/Sdc.tcl @@ -502,9 +502,16 @@ proc get_cells { args } { if { $args != {} } { sta_warn "patterns argument not supported with -of_objects." } - parse_pin_net_args $keys(-of_objects) pins nets + parse_port_pin_net_arg $keys(-of_objects) pins nets foreach pin $pins { - lappend insts [$pin instance] + if { [$pin is_top_level_port] } { + set net [get_nets [get_name $pin]] + if { $net != "NULL" } { + lappend nets $net + } + } else { + lappend insts [$pin instance] + } } foreach net $nets { set pin_iter [$net pin_iterator] From 3d492eddeec17fc6c2b19b6609ae41edb16a8c56 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Sat, 4 Jul 2020 08:26:11 -0700 Subject: [PATCH 12/51] get -filter spaces around op not required --- tcl/Sdc.tcl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tcl/Sdc.tcl b/tcl/Sdc.tcl index 9566572f..8f2e42c0 100644 --- a/tcl/Sdc.tcl +++ b/tcl/Sdc.tcl @@ -1004,7 +1004,7 @@ proc get_ports { args } { return $ports } -variable filter_regexp1 {@?([a-zA-Z_]+) +(==|=~) +([0-9a-zA-Z_\*]+)} +variable filter_regexp1 {@?([a-zA-Z_]+) *(==|=~) *([0-9a-zA-Z_\*]+)} variable filter_or_regexp "($filter_regexp1) +\\|\\| +($filter_regexp1)" variable filter_and_regexp "($filter_regexp1) +&& +($filter_regexp1)" From 2627ed3812e6d99fdd348dbdb3ed28d5df86dcb4 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Sun, 5 Jul 2020 08:08:11 -0700 Subject: [PATCH 13/51] doc --- doc/OpenSTA.odt | Bin 76613 -> 76617 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/doc/OpenSTA.odt b/doc/OpenSTA.odt index 1eb3cf7b93cd14ffec2e4e2e2987be2122f31235..a6045afa559e5b349c99f12619a7ff0e4178411d 100644 GIT binary patch delta 31386 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 delta 31353 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 From 3d4b0cf1a321ec1103b87a2be4b0535ecbfd94ac Mon Sep 17 00:00:00 2001 From: James Cherry Date: Sun, 5 Jul 2020 08:08:18 -0700 Subject: [PATCH 14/51] no crpr for ideal clks --- search/Crpr.cc | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/search/Crpr.cc b/search/Crpr.cc index 31d032cf..64496ffb 100644 --- a/search/Crpr.cc +++ b/search/Crpr.cc @@ -165,6 +165,8 @@ CheckCrpr::checkCrpr1(const Path *src_path, const MinMax *src_clk_min_max = src_clk_path ? src_clk_path->minMax(this) : src_path->minMax(this); if (crprPossible(src_clk, tgt_clk) + && src_clk_info->isPropagated() + && tgt_clk_info->isPropagated() // Note that crpr clk min/max is NOT the same as the path min max. // For path from latches that are borrowing the enable path // is from the opposite min/max of the data. @@ -372,9 +374,12 @@ CheckCrpr::outputDelayCrpr1(const Path *src_path, { crpr = 0.0; crpr_pin = nullptr; + ClkInfo *src_clk_info = src_path->tag(this)->clkInfo(); Clock *tgt_clk = tgt_clk_edge->clock(); Clock *src_clk = src_path->clock(this); - if (tgt_clk->isGenerated() + if (src_clk_info->isPropagated() + && tgt_clk->isGenerated() + && tgt_clk->isPropagated() && crprPossible(src_clk, tgt_clk)) { PathVertex tgt_genclk_path; portClkPath(tgt_clk_edge, tgt_clk_edge->clock()->defaultPin(), tgt_path_ap, @@ -393,7 +398,7 @@ CheckCrpr::crprPossible(Clock *clk1, return clk1 && clk2 && !clk1->isVirtual() && !clk2->isVirtual() - // Generated clock can have crpr in the source path. + // Generated clocks can have crpr in the source path. && (clk1 == clk2 || clk1->isGenerated() || clk2->isGenerated() From 6b448fe2c90cf4a50fe2ec7c3834bc4be0f0c89c Mon Sep 17 00:00:00 2001 From: James Cherry Date: Sun, 5 Jul 2020 16:37:50 -0700 Subject: [PATCH 15/51] write_sdc set_load net --- sdc/WriteSdc.cc | 42 ++++++++++++++++++++++++++++++++++++++++-- sdc/WriteSdcPvt.hh | 6 +++++- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/sdc/WriteSdc.cc b/sdc/WriteSdc.cc index 71515f65..15b27e19 100644 --- a/sdc/WriteSdc.cc +++ b/sdc/WriteSdc.cc @@ -1578,7 +1578,8 @@ WriteSdc::writeEnvironment() const writeCommentSection("Environment"); writeOperatingConditions(); writeWireload(); - writePinLoads(); + writePortLoads(); + writeNetLoads(); writeDriveResistances(); writeDrivingCells(); writeInputTransitions(); @@ -1606,7 +1607,44 @@ WriteSdc::writeWireload() const } void -WriteSdc::writePinLoads() const +WriteSdc::writeNetLoads() const +{ + if (sdc_->net_wire_cap_map_) { + for (auto net_cap : *sdc_->net_wire_cap_map_) { + Net *net = net_cap.first; + MinMaxFloatValues &caps = net_cap.second; + float min_cap, max_cap; + bool min_exists, max_exists; + caps.value(MinMax::min(), min_cap, min_exists); + caps.value(MinMax::max(), max_cap, max_exists); + if (min_exists && max_exists + && min_cap == max_cap) + writeNetLoad(net, MinMaxAll::all(), min_cap); + else { + if (min_exists) + writeNetLoad(net, MinMaxAll::min(), min_cap); + if (max_exists) + writeNetLoad(net, MinMaxAll::max(), max_cap); + } + } + } +} + +void +WriteSdc::writeNetLoad(Net *net, + const MinMaxAll *min_max, + float cap) const +{ + fprintf(stream_, "set_load "); + fprintf(stream_, "%s ", minMaxFlag(min_max)); + writeCapacitance(cap); + fprintf(stream_, " "); + writeGetNet(net); + fprintf(stream_, "\n"); +} + +void +WriteSdc::writePortLoads() const { CellPortBitIterator *port_iter = sdc_network_->portBitIterator(cell_); while (port_iter->hasNext()) { diff --git a/sdc/WriteSdcPvt.hh b/sdc/WriteSdcPvt.hh index 83ee4d2e..4cc69ddd 100644 --- a/sdc/WriteSdcPvt.hh +++ b/sdc/WriteSdcPvt.hh @@ -117,7 +117,11 @@ public: void writeEnvironment() const; void writeOperatingConditions() const; void writeWireload() const; - void writePinLoads() const; + void writeNetLoads() const; + void writeNetLoad(Net *net, + const MinMaxAll *min_max, + float cap) const; + void writePortLoads() const; void writePortLoads(Port *port) const; void writePortFanout(Port *port) const; void writeDriveResistances() const; From 17b48a681ba6e55974e4a587a0cd642c98c6a75e Mon Sep 17 00:00:00 2001 From: James Cherry Date: Mon, 6 Jul 2020 09:45:45 -0700 Subject: [PATCH 16/51] get_property pin name --- search/Property.cc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/search/Property.cc b/search/Property.cc index 386d551f..0a840dfa 100644 --- a/search/Property.cc +++ b/search/Property.cc @@ -732,13 +732,13 @@ getProperty(const Pin *pin, Sta *sta) { auto network = sta->cmdNetwork(); - if (stringEqual(property, "direction")) - return PropertyValue(network->direction(pin)->name()); - else if (stringEqual(property, "name") - || stringEqual(property, "full_name")) - return PropertyValue(network->pathName(pin)); - else if (stringEqual(property, "lib_pin_name")) + if (stringEqual(property, "name") + || stringEqual(property, "lib_pin_name")) return PropertyValue(network->portName(pin)); + else if (stringEqual(property, "full_name")) + return PropertyValue(network->pathName(pin)); + else if (stringEqual(property, "direction")) + return PropertyValue(network->direction(pin)->name()); else if (stringEqual(property, "clocks")) { ClockSet clks; sta->clocks(pin, clks); From 27cc8f1614fc94b744d509acf9516880c10fab98 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Mon, 6 Jul 2020 15:18:13 -0700 Subject: [PATCH 17/51] report_path -format json --- include/sta/Network.hh | 7 +++++ include/sta/SearchClass.hh | 3 +- network/Network.cc | 11 +++++++ search/ReportPath.cc | 62 ++++++++++++++++++++++++++++++++++++++ search/ReportPath.hh | 5 +++ tcl/Search.tcl | 2 +- tcl/StaTcl.i | 2 ++ 7 files changed, 90 insertions(+), 2 deletions(-) diff --git a/include/sta/Network.hh b/include/sta/Network.hh index 4a6e092e..879ad2c5 100644 --- a/include/sta/Network.hh +++ b/include/sta/Network.hh @@ -323,6 +323,13 @@ public: virtual VertexId vertexId(const Pin *pin) const = 0; virtual void setVertexId(Pin *pin, VertexId id) = 0; + // Return the physical X/Y coordinates of the pin. + virtual void location(const Pin *pin, + // Return values. + double x, + double y, + bool exists) const; + int pinCount(); int pinCount(Instance *inst); int leafPinCount(); diff --git a/include/sta/SearchClass.hh b/include/sta/SearchClass.hh index 2eaae23c..c6f49dd3 100644 --- a/include/sta/SearchClass.hh +++ b/include/sta/SearchClass.hh @@ -119,7 +119,8 @@ enum class ReportPathFormat { full, shorter, endpoint, summary, - slack_only + slack_only, + json }; static const int tag_index_bits = 24; diff --git a/network/Network.cc b/network/Network.cc index 8ac29711..535566f9 100644 --- a/network/Network.cc +++ b/network/Network.cc @@ -935,6 +935,17 @@ Network::findInstPinsMatching(const Instance *instance, } } +void +Network::location(const Pin *pin, + // Return values. + double x, + double y, + bool exists) const +{ + x = y = 0.0; + exists = false; +} + int Network::instanceCount(Instance *inst) { diff --git a/search/ReportPath.cc b/search/ReportPath.cc index 456cb2fd..09c2443c 100644 --- a/search/ReportPath.cc +++ b/search/ReportPath.cc @@ -2503,6 +2503,27 @@ ReportPath::reportPath(const PathEnd *end, void ReportPath::reportPath(const Path *path, string &result) +{ + switch (format_) { + case ReportPathFormat::full: + case ReportPathFormat::full_clock: + case ReportPathFormat::full_clock_expanded: + reportPathFull(path, result); + break; + case ReportPathFormat::json: + reportPathJson(path, result); + break; + case ReportPathFormat::summary: + case ReportPathFormat::slack_only: + default: + internalError("unsupported path type"); + break; + } +} + +void +ReportPath::reportPathFull(const Path *path, + string &result) { reportPathHeader(result); PathExpanded expanded(path, this); @@ -2510,6 +2531,47 @@ ReportPath::reportPath(const Path *path, false, result); } +void +ReportPath::reportPathJson(const Path *path, + string &result) +{ + result += "{ \"path\": [\n"; + PathExpanded expanded(path, this); + for (auto i = expanded.startIndex(); i < expanded.size(); i++) { + PathRef *path = expanded.path(i); + const Pin *pin = path->vertex(this)->pin(); + result += " {\n"; + result += " \"pin\": \""; + result += network_->pathName(pin); + result += "\",\n"; + + double x, y; + bool exists; + string tmp; + network_->location(pin, x, y, exists); + if (exists) { + result += " \"x\": "; + stringPrint(tmp, "%.3f", x); + result += tmp + ",\n"; + result += " \"y\": "; + stringPrint(tmp, "%.3f", y); + result += tmp + "\n"; + } + + result += " \"arrival\": "; + stringPrint(tmp, "%.3e", path->arrival(this)); + result += tmp + ",\n"; + + result += " \"slew\": "; + stringPrint(tmp, "%.3e", path->slew(this)); + result += tmp + ",\n"; + + result += " }\n"; + } + result += " ]\n"; + result += "}\n"; +} + void ReportPath::reportPath1(const Path *path, PathExpanded &expanded, diff --git a/search/ReportPath.hh b/search/ReportPath.hh index c7ae2e75..3fda967c 100644 --- a/search/ReportPath.hh +++ b/search/ReportPath.hh @@ -63,6 +63,7 @@ public: void reportPathEnd(PathEnd *end, PathEnd *prev_end); void reportPathEnds(PathEndSeq *ends); + // for debugging void reportPath(const Path *path); void reportShort(const PathEndUnconstrained *end, @@ -341,6 +342,10 @@ protected: string &result); void reportPath(const Path *path, string &result); + void reportPathFull(const Path *path, + string &result); + void reportPathJson(const Path *path, + string &result); void reportPathHeader(string &result); void reportPath1(const Path *path, PathExpanded &expanded, diff --git a/tcl/Search.tcl b/tcl/Search.tcl index 23a06b0e..41491223 100644 --- a/tcl/Search.tcl +++ b/tcl/Search.tcl @@ -227,7 +227,7 @@ proc parse_report_path_options { cmd args_var default_format if [info exists path_options(-format)] { set format $path_options(-format) set formats {full full_clock full_clock_expanded short \ - end slack_only summary} + end slack_only summary json} if { [lsearch $formats $format] == -1 } { sta_error "-format $format not recognized." } diff --git a/tcl/StaTcl.i b/tcl/StaTcl.i index 8e6ff2e1..4500e3a6 100644 --- a/tcl/StaTcl.i +++ b/tcl/StaTcl.i @@ -1065,6 +1065,8 @@ using namespace sta; $1 = ReportPathFormat::summary; else if (stringEq(arg, "slack_only")) $1 = ReportPathFormat::slack_only; + else if (stringEq(arg, "json")) + $1 = ReportPathFormat::json; else { tclError(interp, "Error: unknown path type %s.", arg); return TCL_ERROR; From 9cb7222f56b08d1502063ec75fb3ea5b2b7dd066 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Mon, 6 Jul 2020 15:35:03 -0700 Subject: [PATCH 18/51] pin_location --- tcl/StaTcl.i | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tcl/StaTcl.i b/tcl/StaTcl.i index 4500e3a6..1a8bb558 100644 --- a/tcl/StaTcl.i +++ b/tcl/StaTcl.i @@ -5311,6 +5311,20 @@ delays_invalid() sta->delaysInvalid(); } +const char * +pin_location(Pin *pin) +{ + Network *network = cmdNetwork(); + double x, y; + bool exists; + network->location(pin, x, y, exists); + // return x/y as tcl list + if (exists) + return sta::stringPrintTmp("%f %f", x, y); + else + return ""; +} + %} // inline //////////////////////////////////////////////////////////////// From ccff78468b2d59f76c6ae2d4b1e32d3c8b34e0ad Mon Sep 17 00:00:00 2001 From: James Cherry Date: Mon, 6 Jul 2020 15:42:53 -0700 Subject: [PATCH 19/51] pin_location_str --- tcl/Network.tcl | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tcl/Network.tcl b/tcl/Network.tcl index b35daed8..4dbcb7da 100644 --- a/tcl/Network.tcl +++ b/tcl/Network.tcl @@ -410,9 +410,15 @@ proc report_net_pin { pin verbose corner digits } { } } -# default handler +# Used by report_net proc pin_location_str { pin } { - return "" + set loc [pin_location $pin] + if { $loc != "" } { + lassign $loc x y + return " ([format_distance $x 0], [format_distance $y 0])" + } else { + return "" + } } ################################################################ From 2cab7b18e559b9cd609201ab85c1fa60612a85d2 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Mon, 6 Jul 2020 15:59:16 -0700 Subject: [PATCH 20/51] Network::location(pin) --- include/sta/Network.hh | 6 +++--- network/Network.cc | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/sta/Network.hh b/include/sta/Network.hh index 879ad2c5..eab973ad 100644 --- a/include/sta/Network.hh +++ b/include/sta/Network.hh @@ -326,9 +326,9 @@ public: // Return the physical X/Y coordinates of the pin. virtual void location(const Pin *pin, // Return values. - double x, - double y, - bool exists) const; + double &x, + double &y, + bool &exists) const; int pinCount(); int pinCount(Instance *inst); diff --git a/network/Network.cc b/network/Network.cc index 535566f9..412afcfb 100644 --- a/network/Network.cc +++ b/network/Network.cc @@ -938,9 +938,9 @@ Network::findInstPinsMatching(const Instance *instance, void Network::location(const Pin *pin, // Return values. - double x, - double y, - bool exists) const + double &x, + double &y, + bool &exists) const { x = y = 0.0; exists = false; From 6d95ef44e5447c42d5cd70ca7ee9b37ac67911e7 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Mon, 6 Jul 2020 16:28:58 -0700 Subject: [PATCH 21/51] SdcNetwork::location(pin) --- include/sta/SdcNetwork.hh | 6 +++++- network/SdcNetwork.cc | 10 ++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/include/sta/SdcNetwork.hh b/include/sta/SdcNetwork.hh index 6172a17d..d5aece26 100644 --- a/include/sta/SdcNetwork.hh +++ b/include/sta/SdcNetwork.hh @@ -103,6 +103,11 @@ public: virtual VertexId vertexId(const Pin *pin) const; virtual void setVertexId(Pin *pin, VertexId id); + virtual void location(const Pin *pin, + // Return values. + double &x, + double &y, + bool &exists) const; virtual Net *net(const Term *term) const; virtual Pin *pin(const Term *term) const; @@ -120,7 +125,6 @@ public: virtual char pathEscape() const; virtual void setPathEscape(char escape); - virtual bool isEditable() const; virtual LibertyLibrary *makeLibertyLibrary(const char *name, const char *filename); diff --git a/network/SdcNetwork.cc b/network/SdcNetwork.cc index f8b4de73..707687f3 100644 --- a/network/SdcNetwork.cc +++ b/network/SdcNetwork.cc @@ -223,6 +223,16 @@ NetworkNameAdapter::setVertexId(Pin *pin, network_->setVertexId(pin, id); } +void +NetworkNameAdapter::location(const Pin *pin, + // Return values. + double &x, + double &y, + bool &exists) const +{ + network_->location(pin, x, y, exists); +} + bool NetworkNameAdapter::isBundle(const Port *port) const { From 875778b25a0f7f33150f7e2e650cb1fe6a7285af Mon Sep 17 00:00:00 2001 From: James Cherry Date: Mon, 6 Jul 2020 16:49:25 -0700 Subject: [PATCH 22/51] json xy digits --- search/ReportPath.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/search/ReportPath.cc b/search/ReportPath.cc index 09c2443c..293554ac 100644 --- a/search/ReportPath.cc +++ b/search/ReportPath.cc @@ -2551,10 +2551,10 @@ ReportPath::reportPathJson(const Path *path, network_->location(pin, x, y, exists); if (exists) { result += " \"x\": "; - stringPrint(tmp, "%.3f", x); + stringPrint(tmp, "%.6f", x); result += tmp + ",\n"; result += " \"y\": "; - stringPrint(tmp, "%.3f", y); + stringPrint(tmp, "%.6f", y); result += tmp + "\n"; } From 7f037334bf239bcf940849b76a08ba4e98e7c69a Mon Sep 17 00:00:00 2001 From: James Cherry Date: Mon, 6 Jul 2020 18:35:36 -0700 Subject: [PATCH 23/51] compiler warning --- liberty/LibertyExpr.cc | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/liberty/LibertyExpr.cc b/liberty/LibertyExpr.cc index 60ce4f55..4933cad9 100644 --- a/liberty/LibertyExpr.cc +++ b/liberty/LibertyExpr.cc @@ -16,6 +16,7 @@ #include "FuncExpr.hh" +#include // min #include "Report.hh" #include "StringUtil.hh" #include "Liberty.hh" @@ -129,14 +130,9 @@ LibExprParser::copyInput(char *buf, { size_t length = strlen(func_); if (length == 0) - return 0; + return 0; else { - size_t count; - - if (length < max_size) - count = length; - else - count = max_size; + size_t count = std::min(length, max_size); strncpy(buf, func_, count); func_ += count; return count; From bf161cc759bee7dc75e9bde5a95528475fabece4 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Tue, 7 Jul 2020 17:07:50 -0700 Subject: [PATCH 24/51] compiler warning --- liberty/LibertyExpr.cc | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/liberty/LibertyExpr.cc b/liberty/LibertyExpr.cc index 60ce4f55..4933cad9 100644 --- a/liberty/LibertyExpr.cc +++ b/liberty/LibertyExpr.cc @@ -16,6 +16,7 @@ #include "FuncExpr.hh" +#include // min #include "Report.hh" #include "StringUtil.hh" #include "Liberty.hh" @@ -129,14 +130,9 @@ LibExprParser::copyInput(char *buf, { size_t length = strlen(func_); if (length == 0) - return 0; + return 0; else { - size_t count; - - if (length < max_size) - count = length; - else - count = max_size; + size_t count = std::min(length, max_size); strncpy(buf, func_, count); func_ += count; return count; From 64a196da75f08da7370d7c535d82bcbdb8e997b5 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Tue, 7 Jul 2020 17:08:17 -0700 Subject: [PATCH 25/51] report json syntax --- search/ReportPath.cc | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/search/ReportPath.cc b/search/ReportPath.cc index 293554ac..b16fda52 100644 --- a/search/ReportPath.cc +++ b/search/ReportPath.cc @@ -2551,11 +2551,11 @@ ReportPath::reportPathJson(const Path *path, network_->location(pin, x, y, exists); if (exists) { result += " \"x\": "; - stringPrint(tmp, "%.6f", x); + stringPrint(tmp, "%.9f", x); result += tmp + ",\n"; result += " \"y\": "; - stringPrint(tmp, "%.6f", y); - result += tmp + "\n"; + stringPrint(tmp, "%.9f", y); + result += tmp + ",\n"; } result += " \"arrival\": "; @@ -2564,9 +2564,12 @@ ReportPath::reportPathJson(const Path *path, result += " \"slew\": "; stringPrint(tmp, "%.3e", path->slew(this)); - result += tmp + ",\n"; + result += tmp + "\n"; - result += " }\n"; + result += " }"; + if (i < expanded.size() - 1) + result += ","; + result += "\n"; } result += " ]\n"; result += "}\n"; From b3b46773411b0824a34ccc7d9e774fad85896cd8 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Wed, 8 Jul 2020 08:01:43 -0700 Subject: [PATCH 26/51] worst slack empty check --- search/WorstSlack.cc | 70 +++++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/search/WorstSlack.cc b/search/WorstSlack.cc index bce83e93..f3278e1d 100644 --- a/search/WorstSlack.cc +++ b/search/WorstSlack.cc @@ -178,41 +178,43 @@ void WorstSlack::sortQueue(PathAPIndex path_ap_index, const StaState *sta) { - Search *search = sta->search(); - const Debug *debug = sta->debug(); - debugPrint0(debug, "wns", 3, "sort queue\n"); - - VertexSeq vertices; - vertices.reserve(queue_.size()); - VertexSet::Iterator queue_iter(queue_); - while (queue_iter.hasNext()) { - Vertex *vertex = queue_iter.next(); - vertices.push_back(vertex); - } - WnsSlackLess slack_less(path_ap_index, sta); - sort(vertices, slack_less); - - int vertex_count = vertices.size(); - int threshold_index = min(min_queue_size_, vertex_count - 1); - Vertex *threshold_vertex = vertices[threshold_index]; - slack_threshold_ = search->wnsSlack(threshold_vertex, path_ap_index); - debugPrint1(debug, "wns", 3, "threshold %s\n", - delayAsString(slack_threshold_, sta)); - - // Reinsert vertices with slack < threshold. - queue_.clear(); - VertexSeq::Iterator queue_iter2(vertices); - while (queue_iter2.hasNext()) { - Vertex *vertex = queue_iter2.next(); - Slack slack = search->wnsSlack(vertex, path_ap_index); - if (fuzzyGreater(slack, slack_threshold_)) - break; - queue_.insert(vertex); + if (queue_.size() > 0) { + Search *search = sta->search(); + const Debug *debug = sta->debug(); + debugPrint0(debug, "wns", 3, "sort queue\n"); + + VertexSeq vertices; + vertices.reserve(queue_.size()); + VertexSet::Iterator queue_iter(queue_); + while (queue_iter.hasNext()) { + Vertex *vertex = queue_iter.next(); + vertices.push_back(vertex); + } + WnsSlackLess slack_less(path_ap_index, sta); + sort(vertices, slack_less); + + int vertex_count = vertices.size(); + int threshold_index = min(min_queue_size_, vertex_count - 1); + Vertex *threshold_vertex = vertices[threshold_index]; + slack_threshold_ = search->wnsSlack(threshold_vertex, path_ap_index); + debugPrint1(debug, "wns", 3, "threshold %s\n", + delayAsString(slack_threshold_, sta)); + + // Reinsert vertices with slack < threshold. + queue_.clear(); + VertexSeq::Iterator queue_iter2(vertices); + while (queue_iter2.hasNext()) { + Vertex *vertex = queue_iter2.next(); + Slack slack = search->wnsSlack(vertex, path_ap_index); + if (fuzzyGreater(slack, slack_threshold_)) + break; + queue_.insert(vertex); + } + max_queue_size_ = queue_.size() * 2; + Vertex *worst_slack_vertex = vertices[0]; + Slack worst_slack_slack = search->wnsSlack(worst_slack_vertex, path_ap_index); + setWorstSlack(worst_slack_vertex, worst_slack_slack, sta); } - max_queue_size_ = queue_.size() * 2; - Vertex *worst_slack_vertex = vertices[0]; - Slack worst_slack_slack = search->wnsSlack(worst_slack_vertex, path_ap_index); - setWorstSlack(worst_slack_vertex, worst_slack_slack, sta); } void From 46d2446f88904a2a4c47ddb814fa1950cb89b726 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Thu, 9 Jul 2020 08:42:52 -0700 Subject: [PATCH 27/51] LibertyCell::isInverter --- include/sta/Liberty.hh | 3 +++ liberty/Liberty.cc | 21 +++++++++++++++++++++ search/Property.cc | 2 ++ 3 files changed, 26 insertions(+) diff --git a/include/sta/Liberty.hh b/include/sta/Liberty.hh index 5818e4bb..c7f374b4 100644 --- a/include/sta/Liberty.hh +++ b/include/sta/Liberty.hh @@ -493,6 +493,7 @@ public: Report *report, Debug *debug); bool isBuffer() const; + bool isInverter() const; // Only valid when isBuffer() returns true. void bufferPorts(// Return values. LibertyPort *&input, @@ -522,6 +523,8 @@ protected: void makeTimingArcPortMaps(); bool hasBufferFunc(const LibertyPort *input, const LibertyPort *output) const; + bool hasInverterFunc(const LibertyPort *input, + const LibertyPort *output) const; LibertyLibrary *liberty_library_; float area_; diff --git a/liberty/Liberty.cc b/liberty/Liberty.cc index 38a64706..efb7919d 100644 --- a/liberty/Liberty.cc +++ b/liberty/Liberty.cc @@ -1064,6 +1064,27 @@ LibertyCell::hasBufferFunc(const LibertyPort *input, && func->port() == input; } +bool +LibertyCell::isInverter() const +{ + LibertyPort *input; + LibertyPort *output; + bufferPorts(input, output); + return input && output + && hasInverterFunc(input, output); +} + +bool +LibertyCell::hasInverterFunc(const LibertyPort *input, + const LibertyPort *output) const +{ + FuncExpr *func = output->function(); + return func + && func->op() == FuncExpr::op_not + && func->left()->op() == FuncExpr::op_port + && func->left()->port() == input; +} + void LibertyCell::bufferPorts(// Return values. LibertyPort *&input, diff --git a/search/Property.cc b/search/Property.cc index 0a840dfa..d69bd3f1 100644 --- a/search/Property.cc +++ b/search/Property.cc @@ -566,6 +566,8 @@ getProperty(const LibertyCell *cell, return PropertyValue(cell->libertyLibrary()); else if (stringEqual(property, "is_buffer")) return PropertyValue(cell->isBuffer()); + else if (stringEqual(property, "is_inverter")) + return PropertyValue(cell->isInverter()); else if (stringEqual(property, "dont_use")) return PropertyValue(cell->dontUse()); else From b54125a1ae6c41018dd2b618267594049d4f28e6 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Thu, 9 Jul 2020 11:26:06 -0700 Subject: [PATCH 28/51] get_property pin is_register_clock --- search/Property.cc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/search/Property.cc b/search/Property.cc index d69bd3f1..177cb858 100644 --- a/search/Property.cc +++ b/search/Property.cc @@ -688,6 +688,8 @@ getProperty(const LibertyPort *port, float cap = port->capacitance(RiseFall::rise(), MinMax::max()); return PropertyValue(sta->units()->capacitanceUnit()->asString(cap, 6)); } + else if (stringEqual(property, "is_register_clock")) + return PropertyValue(port->isRegClk()); else if (stringEqual(property, "drive_resistance_rise_min")) return PropertyValue(port->driveResistance(RiseFall::rise(), MinMax::min())); @@ -741,6 +743,10 @@ getProperty(const Pin *pin, return PropertyValue(network->pathName(pin)); else if (stringEqual(property, "direction")) return PropertyValue(network->direction(pin)->name()); + else if (stringEqual(property, "is_register_clock")) { + const LibertyPort *port = network->libertyPort(pin); + return PropertyValue(port && port->isRegClk()); + } else if (stringEqual(property, "clocks")) { ClockSet clks; sta->clocks(pin, clks); From b7a572cfe2e6b46ab3ed3b348fd74bdb417845f3 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Thu, 9 Jul 2020 16:10:21 -0700 Subject: [PATCH 29/51] LibertyPort::capacitance() --- include/sta/Liberty.hh | 1 + include/sta/RiseFallMinMax.hh | 3 +++ liberty/Liberty.cc | 12 ++++++++++++ sdc/RiseFallMinMax.cc | 17 +++++++++++++++++ 4 files changed, 33 insertions(+) diff --git a/include/sta/Liberty.hh b/include/sta/Liberty.hh index c7f374b4..ec898916 100644 --- a/include/sta/Liberty.hh +++ b/include/sta/Liberty.hh @@ -636,6 +636,7 @@ public: float &fanout_load, bool &exists) const; void setFanoutLoad(float fanout_load); + float capacitance() const; float capacitance(const RiseFall *rf, const MinMax *min_max) const; void capacitance(const RiseFall *rf, diff --git a/include/sta/RiseFallMinMax.hh b/include/sta/RiseFallMinMax.hh index 57f2900d..69711fe4 100644 --- a/include/sta/RiseFallMinMax.hh +++ b/include/sta/RiseFallMinMax.hh @@ -36,6 +36,9 @@ public: float &value, bool &exists) const; bool hasValue() const; + void maxValue(// Return values + float &max_value, + bool &exists) const; bool empty() const; bool hasValue(const RiseFall *rf, const MinMax *min_max) const; diff --git a/liberty/Liberty.cc b/liberty/Liberty.cc index efb7919d..11c9c317 100644 --- a/liberty/Liberty.cc +++ b/liberty/Liberty.cc @@ -1928,6 +1928,18 @@ LibertyPort::setCapacitance(const RiseFall *rf, } } +float +LibertyPort::capacitance() const +{ + float cap; + bool exists; + capacitance_.maxValue(cap, exists); + if (exists) + return cap; + else + return 0.0; +} + float LibertyPort::capacitance(const RiseFall *rf, const MinMax *min_max) const diff --git a/sdc/RiseFallMinMax.cc b/sdc/RiseFallMinMax.cc index 47e66a28..35c95b23 100644 --- a/sdc/RiseFallMinMax.cc +++ b/sdc/RiseFallMinMax.cc @@ -163,6 +163,23 @@ RiseFallMinMax::hasValue() const return !empty(); } +void +RiseFallMinMax::maxValue(// Return values + float &max_value, + bool &exists) const +{ + max_value = MinMax::max()->initValue(); + exists = false; + for (int rf_index=0;rf_index Date: Fri, 10 Jul 2020 17:08:44 -0700 Subject: [PATCH 30/51] report clk used as data respect -format --- search/ReportPath.cc | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/search/ReportPath.cc b/search/ReportPath.cc index b16fda52..8a50e8cc 100644 --- a/search/ReportPath.cc +++ b/search/ReportPath.cc @@ -2052,7 +2052,18 @@ ReportPath::reportSrcClkAndPath(const Path *path, early_late, result); if (clk_insertion > 0.0) reportClkSrcLatency(clk_insertion, clk_time, early_late, result); - reportPath1(path, expanded, true, time_offset, result); + if (reportClkPath()) + reportPath1(path, expanded, true, time_offset, result); + else { + Arrival clk_arrival = clk_end_time; + Arrival end_arrival = path->arrival(this) + time_offset; + Delay clk_delay = end_arrival - clk_arrival; + reportLine("clock network delay", clk_delay, + end_arrival, early_late, result); + Vertex *end_vertex = path->vertex(this); + reportLine(descriptionField(end_vertex).c_str(), + end_arrival, early_late, clk_end_rf, result); + } } else { if (is_path_delay) { From 9468da1ae8bd2fa9dadaa25366d542e179d79988 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Sat, 11 Jul 2020 16:24:48 -0700 Subject: [PATCH 31/51] Delay compare ops --- dcalc/DmpDelayCalc.cc | 2 +- dcalc/GraphDelayCalc1.cc | 6 +- graph/DelayFloat.cc | 108 ++++++++++++++++++++++------------ graph/DelayNormal1.cc | 88 ++++++++++----------------- graph/DelayNormal2.cc | 98 ++++++++++++------------------ include/sta/DelayFloat.hh | 81 ++++++++++++++++--------- include/sta/DelayNormal1.hh | 56 +++++++++--------- include/sta/DelayNormal2.hh | 26 ++++---- sdf/SdfReader.cc | 2 +- sdf/SdfWriter.cc | 24 ++++---- search/CheckMaxSkews.cc | 6 +- search/CheckMinPeriods.cc | 6 +- search/CheckMinPulseWidths.cc | 6 +- search/ClkInfo.cc | 4 +- search/Genclks.cc | 6 +- search/Latches.cc | 6 +- search/PathEnd.cc | 18 +++--- search/PathEnum.cc | 4 +- search/PathGroup.cc | 12 ++-- search/PathVertex.cc | 2 +- search/Power.cc | 4 +- search/Property.cc | 8 +-- search/ReportPath.cc | 24 ++++---- search/Search.cc | 24 ++++---- search/Sta.cc | 45 ++++++++------ search/WorstSlack.cc | 26 ++++---- 26 files changed, 354 insertions(+), 338 deletions(-) diff --git a/dcalc/DmpDelayCalc.cc b/dcalc/DmpDelayCalc.cc index 77ea8c11..44823aa7 100644 --- a/dcalc/DmpDelayCalc.cc +++ b/dcalc/DmpDelayCalc.cc @@ -347,7 +347,7 @@ DmpCeffTwoPoleDelayCalc::loadDelay(Parasitic *pole_residue, { ComplexFloat pole2, residue2; parasitics_->poleResidue(pole_residue, 1, pole2, residue2); - if (!fuzzyZero(drvr_slew_) + if (!delayZero(drvr_slew_) && pole2.imag() == 0.0 && residue2.imag() == 0.0) { double p2 = pole2.real(); diff --git a/dcalc/GraphDelayCalc1.cc b/dcalc/GraphDelayCalc1.cc index 33f4bdbd..7f3ab44e 100644 --- a/dcalc/GraphDelayCalc1.cc +++ b/dcalc/GraphDelayCalc1.cc @@ -1246,7 +1246,7 @@ GraphDelayCalc1::findArcDelay(LibertyCell *drvr_cell, // Merge slews. const Slew &drvr_slew = graph_->slew(drvr_vertex, drvr_rf, ap_index); const MinMax *slew_min_max = dcalc_ap->slewMinMax(); - if (fuzzyGreater(gate_slew, drvr_slew, dcalc_ap->slewMinMax()) + if (delayGreater(gate_slew, drvr_slew, dcalc_ap->slewMinMax()) && !drvr_vertex->slewAnnotated(drvr_rf, slew_min_max)) graph_->setSlew(drvr_vertex, drvr_rf, ap_index, gate_slew); if (!graph_->arcDelayAnnotated(edge, arc, ap_index)) { @@ -1446,7 +1446,7 @@ GraphDelayCalc1::annotateLoadDelays(Vertex *drvr_vertex, else { const Slew &slew = graph_->slew(load_vertex, drvr_rf, ap_index); if (!merge - || fuzzyGreater(load_slew, slew, slew_min_max)) + || delayGreater(load_slew, slew, slew_min_max)) graph_->setSlew(load_vertex, drvr_rf, ap_index, load_slew); } } @@ -1459,7 +1459,7 @@ GraphDelayCalc1::annotateLoadDelays(Vertex *drvr_vertex, Delay wire_delay_extra = extra_delay + wire_delay; const MinMax *delay_min_max = dcalc_ap->delayMinMax(); if (!merge - || fuzzyGreater(wire_delay_extra, delay, delay_min_max)) { + || delayGreater(wire_delay_extra, delay, delay_min_max)) { graph_->setWireArcDelay(wire_edge, drvr_rf, ap_index, wire_delay_extra); if (observer_) diff --git a/graph/DelayFloat.cc b/graph/DelayFloat.cc index 2b06819a..d4372543 100644 --- a/graph/DelayFloat.cc +++ b/graph/DelayFloat.cc @@ -35,19 +35,6 @@ initDelayConstants() delay_init_values[MinMax::maxIndex()] = MinMax::max()->initValue(); } -const Delay & -delayInitValue(const MinMax *min_max) -{ - return delay_init_values[min_max->index()]; -} - -bool -delayIsInitValue(const Delay &delay, - const MinMax *min_max) -{ - return fuzzyEqual(delay, min_max->initValue()); -} - const char * delayAsString(const Delay &delay, const StaState *sta) @@ -73,45 +60,47 @@ delayAsString(const Delay &delay, return unit->asString(delay, digits); } -float -delayAsFloat(const Delay &delay, - const EarlyLate *, - const StaState *) +const Delay & +delayInitValue(const MinMax *min_max) { - return delay; + return delay_init_values[min_max->index()]; } -float -delaySigma2(const Delay &, - const EarlyLate *) +bool +delayIsInitValue(const Delay &delay, + const MinMax *min_max) { - return 0.0; + return fuzzyEqual(delay, min_max->initValue()); } bool -fuzzyGreater(const Delay &delay1, - const Delay &delay2, - const MinMax *min_max) +delayZero(const Delay &delay) { - if (min_max == MinMax::max()) - return fuzzyGreater(delay1, delay2); - else - return fuzzyLess(delay1, delay2); + return fuzzyZero(delay); } bool -fuzzyGreaterEqual(const Delay &delay1, - const Delay &delay2, - const MinMax *min_max) +delayInf(const Delay &delay) { - if (min_max == MinMax::max()) - return fuzzyGreaterEqual(delay1, delay2); - else - return fuzzyLessEqual(delay1, delay2); + return fuzzyInf(delay); } bool -fuzzyLess(const Delay &delay1, +delayEqual(const Delay &delay1, + const Delay &delay2) +{ + return fuzzyEqual(delay1, delay2); +} + +bool +delayLess(const Delay &delay1, + const Delay &delay2) +{ + return fuzzyLess(delay1, delay2); +} + +bool +delayLess(const Delay &delay1, const Delay &delay2, const MinMax *min_max) { @@ -122,7 +111,14 @@ fuzzyLess(const Delay &delay1, } bool -fuzzyLessEqual(const Delay &delay1, +delayLessEqual(const Delay &delay1, + const Delay &delay2) +{ + return fuzzyLessEqual(delay1, delay2); +} + +bool +delayLessEqual(const Delay &delay1, const Delay &delay2, const MinMax *min_max) { @@ -132,6 +128,42 @@ fuzzyLessEqual(const Delay &delay1, return fuzzyGreaterEqual(delay1, delay2); } +bool +delayGreater(const Delay &delay1, + const Delay &delay2) +{ + return fuzzyGreater(delay1, delay2); +} + +bool +delayGreater(const Delay &delay1, + const Delay &delay2, + const MinMax *min_max) +{ + if (min_max == MinMax::max()) + return fuzzyGreater(delay1, delay2); + else + return fuzzyLess(delay1, delay2); +} + +bool +delayGreaterEqual(const Delay &delay1, + const Delay &delay2) +{ + return fuzzyGreaterEqual(delay1, delay2); +} + +bool +delayGreaterEqual(const Delay &delay1, + const Delay &delay2, + const MinMax *min_max) +{ + if (min_max == MinMax::max()) + return fuzzyGreaterEqual(delay1, delay2); + else + return fuzzyLessEqual(delay1, delay2); +} + Delay delayRemove(const Delay &delay1, const Delay &delay2) diff --git a/graph/DelayNormal1.cc b/graph/DelayNormal1.cc index 344a3c1f..fc70e885 100644 --- a/graph/DelayNormal1.cc +++ b/graph/DelayNormal1.cc @@ -164,31 +164,7 @@ Delay::operator-=(const Delay &delay) bool Delay::operator==(const Delay &delay) const { - return fuzzyEqual(*this, delay); -} - -bool -Delay::operator>(const Delay &delay) const -{ - return fuzzyGreater(*this, delay); -} - -bool -Delay::operator>=(const Delay &delay) const -{ - return fuzzyGreaterEqual(*this, delay); -} - -bool -Delay::operator<(const Delay &delay) const -{ - return fuzzyLess(*this, delay); -} - -bool -Delay::operator<=(const Delay &delay) const -{ - return fuzzyLessEqual(*this, delay); + return delayEqual(*this, delay); } //////////////////////////////////////////////////////////////// @@ -218,20 +194,20 @@ delayIsInitValue(const Delay &delay, } bool -fuzzyZero(const Delay &delay) +delayZero(const Delay &delay) { return fuzzyZero(delay.mean()) && fuzzyZero(delay.sigma2()); } bool -fuzzyInf(const Delay &delay) +delayInf(const Delay &delay) { return fuzzyInf(delay.mean()); } bool -fuzzyEqual(const Delay &delay1, +delayEqual(const Delay &delay1, const Delay &delay2) { return fuzzyEqual(delay1.mean(), delay2.mean()) @@ -239,7 +215,7 @@ fuzzyEqual(const Delay &delay1, } bool -fuzzyLess(const Delay &delay1, +delayLess(const Delay &delay1, const Delay &delay2) { Sta *sta = Sta::sta(); @@ -248,7 +224,7 @@ fuzzyLess(const Delay &delay1, } bool -fuzzyLess(const Delay &delay1, +delayLess(const Delay &delay1, float delay2) { Sta *sta = Sta::sta(); @@ -257,7 +233,18 @@ fuzzyLess(const Delay &delay1, } bool -fuzzyLessEqual(const Delay &delay1, +delayLess(const Delay &delay1, + const Delay &delay2, + const MinMax *min_max) +{ + if (min_max == MinMax::max()) + return delayLess(delay1, delay2); + else + return delayGreater(delay1, delay2); +} + +bool +delayLessEqual(const Delay &delay1, const Delay &delay2) { Sta *sta = Sta::sta(); @@ -266,7 +253,7 @@ fuzzyLessEqual(const Delay &delay1, } bool -fuzzyLessEqual(const Delay &delay1, +delayLessEqual(const Delay &delay1, float delay2) { Sta *sta = Sta::sta(); @@ -275,18 +262,18 @@ fuzzyLessEqual(const Delay &delay1, } bool -fuzzyLessEqual(const Delay &delay1, +delayLessEqual(const Delay &delay1, const Delay &delay2, const MinMax *min_max) { if (min_max == MinMax::max()) - return fuzzyLessEqual(delay1, delay2); + return delayLessEqual(delay1, delay2); else - return fuzzyGreaterEqual(delay1, delay2); + return delayGreaterEqual(delay1, delay2); } bool -fuzzyGreater(const Delay &delay1, +delayGreater(const Delay &delay1, const Delay &delay2) { Sta *sta = Sta::sta(); @@ -295,7 +282,7 @@ fuzzyGreater(const Delay &delay1, } bool -fuzzyGreater(const Delay &delay1, +delayGreater(const Delay &delay1, float delay2) { Sta *sta = Sta::sta(); @@ -304,7 +291,7 @@ fuzzyGreater(const Delay &delay1, } bool -fuzzyGreaterEqual(const Delay &delay1, +delayGreaterEqual(const Delay &delay1, const Delay &delay2) { Sta *sta = Sta::sta(); @@ -313,7 +300,7 @@ fuzzyGreaterEqual(const Delay &delay1, } bool -fuzzyGreaterEqual(const Delay &delay1, +delayGreaterEqual(const Delay &delay1, float delay2) { Sta *sta = Sta::sta(); @@ -322,36 +309,25 @@ fuzzyGreaterEqual(const Delay &delay1, } bool -fuzzyGreater(const Delay &delay1, +delayGreater(const Delay &delay1, const Delay &delay2, const MinMax *min_max) { if (min_max == MinMax::max()) - return fuzzyGreater(delay1, delay2); + return delayGreater(delay1, delay2); else - return fuzzyLess(delay1, delay2); + return delayLess(delay1, delay2); } bool -fuzzyGreaterEqual(const Delay &delay1, +delayGreaterEqual(const Delay &delay1, const Delay &delay2, const MinMax *min_max) { if (min_max == MinMax::max()) - return fuzzyGreaterEqual(delay1, delay2); - else - return fuzzyLessEqual(delay1, delay2); -} - -bool -fuzzyLess(const Delay &delay1, - const Delay &delay2, - const MinMax *min_max) -{ - if (min_max == MinMax::max()) - return fuzzyLess(delay1, delay2); + return delayGreaterEqual(delay1, delay2); else - return fuzzyGreater(delay1, delay2); + return delayLessEqual(delay1, delay2); } float diff --git a/graph/DelayNormal2.cc b/graph/DelayNormal2.cc index 8b6f02ba..739676b8 100644 --- a/graph/DelayNormal2.cc +++ b/graph/DelayNormal2.cc @@ -24,6 +24,8 @@ #include "Fuzzy.hh" #include "Units.hh" #include "StaState.hh" +// temporary hack +#include "Sta.hh" // SSTA compilation. #if (SSTA == 2) @@ -187,30 +189,6 @@ Delay::operator==(const Delay &delay) const && sigma2_[late_index] == delay.sigma2_[early_index]; } -bool -Delay::operator>(const Delay &delay) const -{ - return mean_ + sqrt(sigma2_late_) > delay.mean_ + sqrt(delay.sigma2_late_); -} - -bool -Delay::operator>=(const Delay &delay) const -{ - return mean_ + sqrt(sigma2_late_) >= delay.mean_ + sqrt(delay.sigma2_late_); -} - -bool -Delay::operator<(const Delay &delay) const -{ - return mean_ - sqrt(sigma2_early_) < delay.mean_ - sqrt(delay.sigma2_early_); -} - -bool -Delay::operator<=(const Delay &delay) const -{ - return mean_ - sqrt(sigma2_early_) <= delay.mean_ - sqrt(delay.sigma2_early_); -} - //////////////////////////////////////////////////////////////// Delay @@ -234,12 +212,12 @@ delayIsInitValue(const Delay &delay, const MinMax *min_max) { return fuzzyEqual(delay.mean(), min_max->initValue()) - && delay.sigma2Early() == 0.0 - && delay.sigma2Late() == 0.0; + && fuzzyZero(delay.sigma2Early()) + && fuzzyZero(delay.sigma2Late()); } bool -fuzzyZero(const Delay &delay) +delayZero(const Delay &delay) { return fuzzyZero(delay.mean()) && fuzzyZero(delay.sigma2Early()) @@ -247,13 +225,13 @@ fuzzyZero(const Delay &delay) } bool -fuzzyInf(const Delay &delay) +delayInf(const Delay &delay) { return fuzzyInf(delay.mean()); } bool -fuzzyEqual(const Delay &delay1, +delayEqual(const Delay &delay1, const Delay &delay2) { return fuzzyEqual(delay1.mean(), delay2.mean()) @@ -262,7 +240,7 @@ fuzzyEqual(const Delay &delay1, } bool -fuzzyLess(const Delay &delay1, +delayLess(const Delay &delay1, const Delay &delay2) { Sta *sta = Sta::sta(); @@ -271,7 +249,7 @@ fuzzyLess(const Delay &delay1, } bool -fuzzyLess(const Delay &delay1, +delayLess(const Delay &delay1, float delay2) { Sta *sta = Sta::sta(); @@ -280,7 +258,18 @@ fuzzyLess(const Delay &delay1, } bool -fuzzyLessEqual(const Delay &delay1, +delayLess(const Delay &delay1, + const Delay &delay2, + const MinMax *min_max) +{ + if (min_max == MinMax::max()) + return delayLess(delay1, delay2); + else + return delayGreater(delay1, delay2); +} + +bool +delayLessEqual(const Delay &delay1, const Delay &delay2) { Sta *sta = Sta::sta(); @@ -289,8 +278,8 @@ fuzzyLessEqual(const Delay &delay1, } bool -fuzzyLessEqual(const Delay &delay1, - float delay2) +delayLessEqual(const Delay &delay1, + float delay2) { Sta *sta = Sta::sta(); return fuzzyLessEqual(delayAsFloat(delay1, EarlyLate::early(), sta), @@ -298,18 +287,18 @@ fuzzyLessEqual(const Delay &delay1, } bool -fuzzyLessEqual(const Delay &delay1, +delayLessEqual(const Delay &delay1, const Delay &delay2, const MinMax *min_max) { if (min_max == MinMax::max()) - return fuzzyLessEqual(delay1, delay2); + return delayLessEqual(delay1, delay2); else - return fuzzyGreaterEqual(delay1, delay2); + return delayGreaterEqual(delay1, delay2); } bool -fuzzyGreater(const Delay &delay1, +delayGreater(const Delay &delay1, const Delay &delay2) { Sta *sta = Sta::sta(); @@ -318,7 +307,7 @@ fuzzyGreater(const Delay &delay1, } bool -fuzzyGreater(const Delay &delay1, +delayGreater(const Delay &delay1, float delay2) { Sta *sta = Sta::sta(); @@ -327,54 +316,43 @@ fuzzyGreater(const Delay &delay1, } bool -fuzzyGreaterEqual(const Delay &delay1, +delayGreaterEqual(const Delay &delay1, const Delay &delay2) { Sta *sta = Sta::sta(); return fuzzyGreaterEqual(delayAsFloat(delay1, EarlyLate::late(), sta), - delay2); + delayAsFloat(delay2, EarlyLate::late(), sta)); } bool -fuzzyGreaterEqual(const Delay &delay1, +delayGreaterEqual(const Delay &delay1, float delay2) { Sta *sta = Sta::sta(); - return fuzzyGreaterEqual(delayAsFloat(delay1, EarlyLate::late(), sta), + return delayGreaterEqual(delayAsFloat(delay1, EarlyLate::late(), sta), delay2); } bool -fuzzyGreater(const Delay &delay1, +delayGreater(const Delay &delay1, const Delay &delay2, const MinMax *min_max) { if (min_max == MinMax::max()) - return fuzzyGreater(delay1, delay2); + return delayGreater(delay1, delay2); else - return fuzzyLess(delay1, delay2); + return delayLess(delay1, delay2); } bool -fuzzyGreaterEqual(const Delay &delay1, +delayGreaterEqual(const Delay &delay1, const Delay &delay2, const MinMax *min_max) { if (min_max == MinMax::max()) - return fuzzyGreaterEqual(delay1, delay2); - else - return fuzzyLessEqual(delay1, delay2); -} - -bool -fuzzyLess(const Delay &delay1, - const Delay &delay2, - const MinMax *min_max) -{ - if (min_max == MinMax::max()) - return fuzzyLess(delay1, delay2); + return delayGreaterEqual(delay1, delay2); else - return fuzzyGreater(delay1, delay2); + return delayLessEqual(delay1, delay2); } float diff --git a/include/sta/DelayFloat.hh b/include/sta/DelayFloat.hh index 70bd51f1..e895eae1 100644 --- a/include/sta/DelayFloat.hh +++ b/include/sta/DelayFloat.hh @@ -17,7 +17,6 @@ #pragma once #include "MinMax.hh" -#include "Fuzzy.hh" // Delay values defined as floats. @@ -32,6 +31,19 @@ const Delay delay_zero = 0.0; void initDelayConstants(); +const char * +delayAsString(const Delay &delay, + const StaState *sta); +const char * +delayAsString(const Delay &delay, + const StaState *sta, + int digits); +const char * +delayAsString(const Delay &delay, + const EarlyLate *early_late, + const StaState *sta, + int digits); + inline Delay makeDelay(float delay, float, @@ -55,46 +67,61 @@ delayAsFloat(const Delay &delay) } // mean late+/early- sigma -float +inline float delayAsFloat(const Delay &delay, - const EarlyLate *early_late, - const StaState *sta); -float -delaySigma2(const Delay &delay, - const EarlyLate *early_late); -const char * -delayAsString(const Delay &delay, - const StaState *sta); -const char * -delayAsString(const Delay &delay, - const StaState *sta, - int digits); -const char * -delayAsString(const Delay &delay, - const EarlyLate *early_late, - const StaState *sta, - int digits); + const EarlyLate *, + const StaState *) +{ + return delay; +} + +inline float +delaySigma2(const Delay &, + const EarlyLate *) +{ + return 0.0; +} + const Delay & delayInitValue(const MinMax *min_max); bool delayIsInitValue(const Delay &delay, const MinMax *min_max); bool -fuzzyGreater(const Delay &delay1, - const Delay &delay2, - const MinMax *min_max); +delayZero(const Delay &delay); bool -fuzzyGreaterEqual(const Delay &delay1, - const Delay &delay2, - const MinMax *min_max); +delayInf(const Delay &delay); +bool +delayEqual(const Delay &delay1, + const Delay &delay2); +bool +delayLess(const Delay &delay1, + const Delay &delay2); bool -fuzzyLess(const Delay &delay1, +delayLess(const Delay &delay1, const Delay &delay2, const MinMax *min_max); bool -fuzzyLessEqual(const Delay &delay1, +delayLessEqual(const Delay &delay1, + const Delay &delay2); +bool +delayLessEqual(const Delay &delay1, const Delay &delay2, const MinMax *min_max); +bool +delayGreater(const Delay &delay1, + const Delay &delay2); +bool +delayGreater(const Delay &delay1, + const Delay &delay2, + const MinMax *min_max); +bool +delayGreaterEqual(const Delay &delay1, + const Delay &delay2); +bool +delayGreaterEqual(const Delay &delay1, + const Delay &delay2, + const MinMax *min_max); Delay delayRemove(const Delay &delay1, diff --git a/include/sta/DelayNormal1.hh b/include/sta/DelayNormal1.hh index 1b99f139..6a3bfb86 100644 --- a/include/sta/DelayNormal1.hh +++ b/include/sta/DelayNormal1.hh @@ -47,10 +47,6 @@ public: void operator-=(float delay); void operator-=(const Delay &delay); bool operator==(const Delay &delay) const; - bool operator>(const Delay &delay) const; - bool operator>=(const Delay &delay) const; - bool operator<(const Delay &delay) const; - bool operator<=(const Delay &delay) const; private: float mean_; @@ -63,6 +59,19 @@ const Delay delay_zero(0.0); void initDelayConstants(); +const char * +delayAsString(const Delay &delay, + const StaState *sta); +const char * +delayAsString(const Delay &delay, + const StaState *sta, + int digits); +const char * +delayAsString(const Delay &delay, + const EarlyLate *early_late, + const StaState *sta, + int digits); + Delay makeDelay(float delay, float sigma_early, @@ -75,7 +84,10 @@ makeDelay2(float delay, float sigma_late); inline float -delayAsFloat(const Delay &delay) { return delay.mean(); } +delayAsFloat(const Delay &delay) +{ + return delay.mean(); +} // mean late+/early- sigma float @@ -85,56 +97,44 @@ delayAsFloat(const Delay &delay, float delaySigma2(const Delay &delay, const EarlyLate *early_late); -const char * -delayAsString(const Delay &delay, - const StaState *sta); -const char * -delayAsString(const Delay &delay, - const StaState *sta, - int digits); -const char * -delayAsString(const Delay &delay, - const EarlyLate *early_late, - const StaState *sta, - int digits); const Delay & delayInitValue(const MinMax *min_max); bool delayIsInitValue(const Delay &delay, const MinMax *min_max); bool -fuzzyZero(const Delay &delay); +delayZero(const Delay &delay); bool -fuzzyInf(const Delay &delay); +delayInf(const Delay &delay); bool -fuzzyEqual(const Delay &delay1, +delayEqual(const Delay &delay1, const Delay &delay2); bool -fuzzyLess(const Delay &delay1, +delayLess(const Delay &delay1, const Delay &delay2); bool -fuzzyLess(const Delay &delay1, +delayLess(const Delay &delay1, const Delay &delay2, const MinMax *min_max); bool -fuzzyLessEqual(const Delay &delay1, +delayLessEqual(const Delay &delay1, const Delay &delay2); bool -fuzzyLessEqual(const Delay &delay1, +delayLessEqual(const Delay &delay1, const Delay &delay2, const MinMax *min_max); bool -fuzzyGreater(const Delay &delay1, +delayGreater(const Delay &delay1, const Delay &delay2); bool -fuzzyGreaterEqual(const Delay &delay1, +delayGreaterEqual(const Delay &delay1, const Delay &delay2); bool -fuzzyGreaterEqual(const Delay &delay1, +delayGreaterEqual(const Delay &delay1, const Delay &delay2, const MinMax *min_max); bool -fuzzyGreater(const Delay &delay1, +delayGreater(const Delay &delay1, const Delay &delay2, const MinMax *min_max); // delay1-delay2 subtracting sigma instead of addiing. diff --git a/include/sta/DelayNormal2.hh b/include/sta/DelayNormal2.hh index d39a5ec3..ab1c58c6 100644 --- a/include/sta/DelayNormal2.hh +++ b/include/sta/DelayNormal2.hh @@ -50,10 +50,6 @@ public: void operator-=(float delay); void operator-=(const Delay &delay); bool operator==(const Delay &delay) const; - bool operator>(const Delay &delay) const; - bool operator>=(const Delay &delay) const; - bool operator<(const Delay &delay) const; - bool operator<=(const Delay &delay) const; protected: static const int early_index = 0; @@ -110,38 +106,38 @@ bool delayIsInitValue(const Delay &delay, const MinMax *min_max); bool -fuzzyZero(const Delay &delay); +delayZero(const Delay &delay); bool -fuzzyInf(const Delay &delay); +delayInf(const Delay &delay); bool -fuzzyEqual(const Delay &delay1, +delayEqual(const Delay &delay1, const Delay &delay2); bool -fuzzyLess(const Delay &delay1, +delayLess(const Delay &delay1, const Delay &delay2); bool -fuzzyLess(const Delay &delay1, +delayLess(const Delay &delay1, const Delay &delay2, const MinMax *min_max); bool -fuzzyLessEqual(const Delay &delay1, +delayLessEqual(const Delay &delay1, const Delay &delay2); bool -fuzzyLessEqual(const Delay &delay1, +delayLessEqual(const Delay &delay1, const Delay &delay2, const MinMax *min_max); bool -fuzzyGreater(const Delay &delay1, +delayGreater(const Delay &delay1, const Delay &delay2); bool -fuzzyGreaterEqual(const Delay &delay1, +delayGreaterEqual(const Delay &delay1, const Delay &delay2); bool -fuzzyGreaterEqual(const Delay &delay1, +delayGreaterEqual(const Delay &delay1, const Delay &delay2, const MinMax *min_max); bool -fuzzyGreater(const Delay &delay1, +delayGreater(const Delay &delay1, const Delay &delay2, const MinMax *min_max); // delay1-delay2 subtracting sigma instead of addiing. diff --git a/sdf/SdfReader.cc b/sdf/SdfReader.cc index 98f7dff6..34502129 100644 --- a/sdf/SdfReader.cc +++ b/sdf/SdfReader.cc @@ -809,7 +809,7 @@ SdfReader::setEdgeArcDelaysCondUse(Edge *edge, delay = graph_->arcDelay(edge, arc, arc_delay_index) + *value; else if (graph_->arcDelayAnnotated(edge, arc, arc_delay_index)) { ArcDelay prev_value = graph_->arcDelay(edge, arc, arc_delay_index); - if (fuzzyGreater(prev_value, delay, min_max)) + if (delayGreater(prev_value, delay, min_max)) delay = prev_value; } graph_->setArcDelay(edge, arc, arc_delay_index, delay); diff --git a/sdf/SdfWriter.cc b/sdf/SdfWriter.cc index 4287776c..09b408ad 100644 --- a/sdf/SdfWriter.cc +++ b/sdf/SdfWriter.cc @@ -605,18 +605,18 @@ SdfWriter::writeEdgeCheck(Edge *edge, && arcs[clk_rf_index][RiseFall::fallIndex()] && arcs[clk_rf_index][RiseFall::riseIndex()] && arcs[clk_rf_index][RiseFall::fallIndex()] - && fuzzyEqual(graph_->arcDelay(edge, - arcs[clk_rf_index][RiseFall::riseIndex()], - arc_delay_min_index_), - graph_->arcDelay(edge, - arcs[clk_rf_index][RiseFall::fallIndex()], - arc_delay_min_index_)) - && fuzzyEqual(graph_->arcDelay(edge, - arcs[clk_rf_index][RiseFall::riseIndex()], - arc_delay_max_index_), - graph_->arcDelay(edge, - arcs[clk_rf_index][RiseFall::fallIndex()], - arc_delay_max_index_))) + && delayEqual(graph_->arcDelay(edge, + arcs[clk_rf_index][RiseFall::riseIndex()], + arc_delay_min_index_), + graph_->arcDelay(edge, + arcs[clk_rf_index][RiseFall::fallIndex()], + arc_delay_min_index_)) + && delayEqual(graph_->arcDelay(edge, + arcs[clk_rf_index][RiseFall::riseIndex()], + arc_delay_max_index_), + graph_->arcDelay(edge, + arcs[clk_rf_index][RiseFall::fallIndex()], + arc_delay_max_index_))) // Rise/fall margins are the same, so no data edge specifier is required. writeCheck(edge, arcs[clk_rf_index][RiseFall::riseIndex()], sdf_check, false, true); diff --git a/search/CheckMaxSkews.cc b/search/CheckMaxSkews.cc index d7020cc5..a3081697 100644 --- a/search/CheckMaxSkews.cc +++ b/search/CheckMaxSkews.cc @@ -107,7 +107,7 @@ void MaxSkewViolatorsVisititor::visit(MaxSkewCheck &check, const StaState *sta) { - if (fuzzyLess(check.slack(sta), 0.0)) + if (delayLess(check.slack(sta), 0.0)) checks_.push_back(new MaxSkewCheck(check)); } @@ -280,8 +280,8 @@ MaxSkewSlackLess::operator()(const MaxSkewCheck *check1, { Slack slack1 = check1->slack(sta_); Slack slack2 = check2->slack(sta_); - return slack1 < slack2 - || (fuzzyEqual(slack1, slack2) + return delayLess(slack1, slack2) + || (delayEqual(slack1, slack2) // Break ties based on constrained pin names. && sta_->network()->pinLess(check1->clkPin(sta_),check2->clkPin(sta_))); } diff --git a/search/CheckMinPeriods.cc b/search/CheckMinPeriods.cc index dd21636d..7e797c53 100644 --- a/search/CheckMinPeriods.cc +++ b/search/CheckMinPeriods.cc @@ -79,7 +79,7 @@ void MinPeriodViolatorsVisitor::visit(MinPeriodCheck &check, StaState *sta) { - if (fuzzyLess(check.slack(sta), 0.0)) + if (delayLess(check.slack(sta), 0.0)) checks_.push_back(check.copy()); } @@ -231,9 +231,9 @@ MinPeriodSlackLess::operator()(const MinPeriodCheck *check1, Slack slack2 = check2->slack(sta_); const Pin *pin1 = check1->pin(); const Pin *pin2 = check2->pin(); - return fuzzyLess(slack1, slack2) + return delayLess(slack1, slack2) // Break ties based on pin and clock names. - || (fuzzyEqual(slack1, slack2) + || (delayEqual(slack1, slack2) && (sta_->network()->pinLess(pin1, pin2) || (pin1 == pin2 && ClockNameLess()(check1->clk(), diff --git a/search/CheckMinPulseWidths.cc b/search/CheckMinPulseWidths.cc index 385662c0..78d1dfa9 100644 --- a/search/CheckMinPulseWidths.cc +++ b/search/CheckMinPulseWidths.cc @@ -167,7 +167,7 @@ void MinPulseWidthViolatorsVisitor::visit(MinPulseWidthCheck &check, const StaState *sta) { - if (fuzzyLess(check.slack(sta), 0.0) + if (delayLess(check.slack(sta), 0.0) && (corner_ == nullptr || check.corner(sta) == corner_)) { MinPulseWidthCheck *copy = new MinPulseWidthCheck(check.openPath()); @@ -499,8 +499,8 @@ MinPulseWidthSlackLess::operator()(const MinPulseWidthCheck *check1, Slack slack2 = check2->slack(sta_); const Pin *pin1 = check1->pin(sta_); const Pin *pin2 = check2->pin(sta_); - return slack1 < slack2 - || (fuzzyEqual(slack1, slack2) + return delayLess(slack1, slack2) + || (delayEqual(slack1, slack2) // Break ties for the sake of regression stability. && (sta_->network()->pinLess(pin1, pin2) || (pin1 == pin2 diff --git a/search/ClkInfo.cc b/search/ClkInfo.cc index 8638eda3..d1d3388f 100644 --- a/search/ClkInfo.cc +++ b/search/ClkInfo.cc @@ -294,9 +294,9 @@ clkInfoCmp(const ClkInfo *clk_info1, const Arrival &insert1 = clk_info1->insertion(); const Arrival &insert2 = clk_info2->insertion(); - if (insert1 < insert2) + if (delayLess(insert1, insert2)) return -1; - if (insert1 > insert2) + if (delayGreater(insert1, insert2)) return 1; float latency1 = clk_info1->latency(); diff --git a/search/Genclks.cc b/search/Genclks.cc index 78da5608..5abdd31a 100644 --- a/search/Genclks.cc +++ b/search/Genclks.cc @@ -968,9 +968,9 @@ Genclks::recordSrcPaths(Clock *gclk) && (!has_edges || src_clk_rf == gclk->masterClkEdgeTr(rf)) && (src_path.isNull() - || fuzzyGreater(path->arrival(this), - src_path.arrival(this), - early_late))) { + || delayGreater(path->arrival(this), + src_path.arrival(this), + early_late))) { debugPrint4(debug_, "genclk", 2, " %s insertion %s %s %s\n", network_->pathName(gclk_pin), early_late->asString(), diff --git a/search/Latches.cc b/search/Latches.cc index d47b16a1..1a127567 100644 --- a/search/Latches.cc +++ b/search/Latches.cc @@ -98,7 +98,7 @@ Latches::latchRequired(const Path *data_path, network_->pathName(data_path->pin(this)), delayAsString(data_arrival, this), delayAsString(enable_arrival, this)); - if (data_arrival <= enable_arrival) { + if (delayLessEqual(data_arrival, enable_arrival)) { // Data arrives before latch opens. required = enable_arrival; borrow = 0.0; @@ -108,7 +108,7 @@ Latches::latchRequired(const Path *data_path, else { // Data arrives while latch is transparent. borrow = data_arrival - enable_arrival; - if (borrow <= max_borrow) + if (delayLessEqual(borrow, max_borrow)) required = data_arrival; else { borrow = max_borrow; @@ -332,7 +332,7 @@ Latches::latchOutArrival(Path *data_path, latchRequired(data_path, enable_path, &disable_path, path_ap, required, borrow, adjusted_data_arrival, time_given_to_startpoint); - if (borrow > 0.0) { + if (delayGreater(borrow, 0.0)) { // Latch is transparent when data arrives. arc_delay = search_->deratedDelay(data_vertex, d_q_arc, d_q_edge, false, path_ap); diff --git a/search/PathEnd.cc b/search/PathEnd.cc index 786a8af0..18d11cc7 100644 --- a/search/PathEnd.cc +++ b/search/PathEnd.cc @@ -1244,7 +1244,7 @@ PathEndLatchCheck::targetClkWidth(const StaState *sta) const if (enable_clk_info->isPulseClk()) return disable_arrival - enable_arrival; else { - if (enable_arrival > disable_arrival) { + if (delayGreater(enable_arrival, disable_arrival)) { float period = enable_clk_info->clock()->period(); disable_arrival += period; } @@ -1989,24 +1989,24 @@ PathEnd::cmpSlack(const PathEnd *path_end1, { Slack slack1 = path_end1->slack(sta); Slack slack2 = path_end2->slack(sta); - if (fuzzyZero(slack1) - && fuzzyZero(slack2) + if (delayZero(slack1) + && delayZero(slack2) && 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 (fuzzyEqual(borrow1, borrow2)) + if (delayEqual(borrow1, borrow2)) return 0; - else if (borrow1 > borrow2) + else if (delayGreater(borrow1, borrow2)) return -1; else return 1; } - else if (fuzzyEqual(slack1, slack2)) + else if (delayEqual(slack1, slack2)) return 0; - else if (slack1 < slack2) + else if (delayLess(slack1, slack2)) return -1; else return 1; @@ -2020,9 +2020,9 @@ PathEnd::cmpArrival(const PathEnd *path_end1, Arrival arrival1 = path_end1->dataArrivalTime(sta); Arrival arrival2 = path_end2->dataArrivalTime(sta); const MinMax *min_max = path_end1->minMax(sta); - if (fuzzyEqual(arrival1, arrival2)) + if (delayEqual(arrival1, arrival2)) return 0; - else if (fuzzyLess(arrival1, arrival2, min_max)) + else if (delayLess(arrival1, arrival2, min_max)) return -1; else return 1; diff --git a/search/PathEnum.cc b/search/PathEnum.cc index 0d650fa1..ce7abd00 100644 --- a/search/PathEnum.cc +++ b/search/PathEnum.cc @@ -348,7 +348,7 @@ PathEnumFaninVisitor::visitFromToPath(const Pin *, // Make the diverted path end to check slack with from_path crpr. makeDivertedPathEnd(from_path, arc, div_end, after_div_copy); // Only enumerate paths with greater slack. - if (fuzzyGreaterEqual(div_end->slack(sta_), path_end_slack_)) { + if (delayGreaterEqual(div_end->slack(sta_), path_end_slack_)) { reportDiversion(arc, from_path); path_enum_->makeDiversion(div_end, after_div_copy); } @@ -356,7 +356,7 @@ PathEnumFaninVisitor::visitFromToPath(const Pin *, delete div_end; } // Only enumerate slower/faster paths. - else if (fuzzyLessEqual(to_arrival, before_div_arrival_, min_max)) { + else if (delayLessEqual(to_arrival, before_div_arrival_, min_max)) { PathEnd *div_end; PathEnumed *after_div_copy; makeDivertedPathEnd(from_path, arc, div_end, after_div_copy); diff --git a/search/PathGroup.cc b/search/PathGroup.cc index 20422f8d..d2856083 100644 --- a/search/PathGroup.cc +++ b/search/PathGroup.cc @@ -101,19 +101,19 @@ PathGroup::savable(PathEnd *path_end) // without crpr first because it is expensive to find. Slack slack = path_end->slackNoCrpr(sta_); if (!delayIsInitValue(slack, min_max_) - && fuzzyLessEqual(slack, threshold_) - && fuzzyLessEqual(slack, slack_max_)) { + && delayLessEqual(slack, threshold_) + && delayLessEqual(slack, slack_max_)) { // Now check with crpr. slack = path_end->slack(sta_); - savable = fuzzyLessEqual(slack, threshold_) - && fuzzyLessEqual(slack, slack_max_) - && fuzzyGreaterEqual(slack, slack_min_); + savable = delayLessEqual(slack, threshold_) + && delayLessEqual(slack, slack_max_) + && delayGreaterEqual(slack, slack_min_); } } else { const Arrival &arrival = path_end->dataArrivalTime(sta_); savable = !delayIsInitValue(arrival, min_max_) - && fuzzyGreaterEqual(arrival, threshold_, min_max_); + && delayGreaterEqual(arrival, threshold_, min_max_); } return savable; } diff --git a/search/PathVertex.cc b/search/PathVertex.cc index e870f659..d26b32ab 100644 --- a/search/PathVertex.cc +++ b/search/PathVertex.cc @@ -403,7 +403,7 @@ PrevPathVisitor::visitFromToPath(const Pin *, && path_ap_index == path_ap_index_ && (dcalc_tol_ > 0.0 ? std::abs(delayAsFloat(to_arrival - path_arrival_)) < dcalc_tol_ - : fuzzyEqual(to_arrival, path_arrival_)) + : delayEqual(to_arrival, path_arrival_)) && (tagMatch(to_tag, path_tag_, sta_) // If the filter exception became active searching from // from_path to to_path the tag includes the filter, but diff --git a/search/Power.cc b/search/Power.cc index ba5ecdd5..525aecde 100644 --- a/search/Power.cc +++ b/search/Power.cc @@ -597,7 +597,7 @@ Power::findInputInternalPower(const Pin *pin, for (auto rf : RiseFall::range()) { float slew = delayAsFloat(graph_->slew(vertex, rf, dcalc_ap->index())); - if (!fuzzyInf(slew)) { + if (!delayInf(slew)) { float table_energy = pwr->power(rf, pvt, slew, load_cap); energy += table_energy; tr_count++; @@ -735,7 +735,7 @@ Power::findOutputInternalPower(const Pin *to_pin, ? delayAsFloat(graph_->slew(from_vertex, from_rf, dcalc_ap->index())) : 0.0; - if (!fuzzyInf(slew)) { + if (!delayInf(slew)) { float table_energy = pwr->power(to_rf, pvt, slew, load_cap); energy += table_energy; tr_count++; diff --git a/search/Property.cc b/search/Property.cc index 177cb858..c06c0fe7 100644 --- a/search/Property.cc +++ b/search/Property.cc @@ -801,12 +801,12 @@ pinSlewProperty(const Pin *pin, Slew slew = min_max->initValue(); if (vertex) { Slew vertex_slew = sta->vertexSlew(vertex, rf, min_max); - if (fuzzyGreater(vertex_slew, slew, min_max)) + if (delayGreater(vertex_slew, slew, min_max)) slew = vertex_slew; } if (bidirect_drvr_vertex) { Slew vertex_slew = sta->vertexSlew(bidirect_drvr_vertex, rf, min_max); - if (fuzzyGreater(vertex_slew, slew, min_max)) + if (delayGreater(vertex_slew, slew, min_max)) slew = vertex_slew; } return PropertyValue(delayPropertyValue(slew, sta)); @@ -879,9 +879,9 @@ edgeDelayProperty(Edge *edge, ArcDelay arc_delay = sta->arcDelay(edge, arc, dcalc_ap); if (!delay_exists || ((min_max == MinMax::max() - && arc_delay > delay) + && delayGreater(arc_delay, delay)) || (min_max == MinMax::min() - && arc_delay < delay))) + && delayLess(arc_delay, delay)))) delay = arc_delay; } } diff --git a/search/ReportPath.cc b/search/ReportPath.cc index 8a50e8cc..e5998078 100644 --- a/search/ReportPath.cc +++ b/search/ReportPath.cc @@ -578,7 +578,7 @@ ReportPath::reportFull(const PathEndLatchCheck *end, else reportTgtClk(end, result); - if (borrow >= 0.0) + if (delayGreaterEqual(borrow, 0.0)) reportLine("time borrowed from endpoint", borrow, req_time, early_late, result); else @@ -644,7 +644,7 @@ ReportPath::reportBorrowing(const PathEndLatchCheck *end, if (tgt_clk_path->clkInfo(search_)->isPropagated()) { auto width_msg = stdstrPrint("%s nominal pulse width", tgt_clk_name.c_str()); reportLineTotal(width_msg.c_str(), nom_pulse_width, early_late, result); - if (!fuzzyZero(latency_diff)) + if (!delayZero(latency_diff)) reportLineTotalMinus("clock latency difference", latency_diff, early_late, result); } @@ -655,19 +655,19 @@ ReportPath::reportBorrowing(const PathEndLatchCheck *end, ArcDelay margin = end->margin(this); reportLineTotalMinus("library setup time", margin, early_late, result); reportDashLineTotal(result); - if (!fuzzyZero(crpr_diff)) + if (!delayZero(crpr_diff)) reportLineTotalMinus("CRPR difference", crpr_diff, early_late, result); reportLineTotal("max time borrow", max_borrow, early_late, result); } - if (fuzzyGreater(borrow, delay_zero) + if (delayGreater(borrow, delay_zero) && (!fuzzyZero(open_uncertainty) - || !fuzzyZero(open_crpr))) { + || !delayZero(open_crpr))) { reportDashLineTotal(result); reportLineTotal("actual time borrow", borrow, early_late, result); if (!fuzzyZero(open_uncertainty)) reportLineTotal("open edge uncertainty", open_uncertainty, early_late, result); - if (!fuzzyZero(open_crpr)) + if (!delayZero(open_crpr)) reportLineTotal("open edge CRPR", open_crpr, early_late, result); reportDashLineTotal(result); reportLineTotal("time given to startpoint", time_given_to_startpoint, @@ -759,7 +759,7 @@ ReportPath::reportFull(const PathEndPathDelay *end, else { Arrival tgt_clk_delay = end->targetClkDelay(this); Arrival tgt_clk_arrival = delay + tgt_clk_delay; - if (!fuzzyZero(tgt_clk_delay)) + if (!delayZero(tgt_clk_delay)) reportLine(clkNetworkDelayIdealProp(isPropagated(tgt_clk_path)), tgt_clk_delay, tgt_clk_arrival, early_late, result); reportClkUncertainty(end, tgt_clk_arrival, result); @@ -2050,7 +2050,7 @@ ReportPath::reportSrcClkAndPath(const Path *path, else if (clk_used_as_data) { reportClkLine(clk, clk_name.c_str(), clk_end_rf, clk_time, early_late, result); - if (clk_insertion > 0.0) + if (delayGreater(clk_insertion, 0.0)) reportClkSrcLatency(clk_insertion, clk_time, early_late, result); if (reportClkPath()) reportPath1(path, expanded, true, time_offset, result); @@ -2067,7 +2067,7 @@ ReportPath::reportSrcClkAndPath(const Path *path, } else { if (is_path_delay) { - if (clk_delay > 0.0) + if (delayGreater(clk_delay, 0.0)) reportLine(clkNetworkDelayIdealProp(is_prop), clk_delay, clk_end_time, early_late, result); } @@ -2570,11 +2570,11 @@ ReportPath::reportPathJson(const Path *path, } result += " \"arrival\": "; - stringPrint(tmp, "%.3e", path->arrival(this)); + stringPrint(tmp, "%.3e", delayAsFloat(path->arrival(this))); result += tmp + ",\n"; result += " \"slew\": "; - stringPrint(tmp, "%.3e", path->slew(this)); + stringPrint(tmp, "%.3e", delayAsFloat(path->slew(this))); result += tmp + "\n"; result += " }"; @@ -2613,7 +2613,7 @@ ReportPath::reportPath1(const Path *path, } Arrival time = latch_enable_time + latch_time_given; Arrival incr = latch_time_given; - if (incr >= 0.0) + if (delayGreaterEqual(incr, 0.0)) reportLine("time given to startpoint", incr, time, early_late, result); else reportLine("time borrowed from startpoint", incr, time, diff --git a/search/Search.cc b/search/Search.cc index e585456b..0808f18c 100644 --- a/search/Search.cc +++ b/search/Search.cc @@ -1194,7 +1194,7 @@ Search::arrivalsChanged(Vertex *vertex, bool arrival_exists2; tag_bldr->tagArrival(tag1, arrival2, arrival_exists2); if (!arrival_exists2 - || !fuzzyEqual(arrival1, arrival2)) + || !delayEqual(arrival1, arrival2)) return true; } return false; @@ -1238,7 +1238,7 @@ ArrivalVisitor::visitFromToPath(const Pin *, Tag *tag_match; tag_bldr_->tagMatchArrival(to_tag, tag_match, arrival, arrival_index); if (tag_match == nullptr - || fuzzyGreater(to_arrival, arrival, min_max)) { + || delayGreater(to_arrival, arrival, min_max)) { debugPrint5(debug, "search", 3, " %s + %s = %s %s %s\n", delayAsString(from_path->arrival(sta_), sta_), delayAsString(arc_delay, sta_), @@ -1258,7 +1258,7 @@ ArrivalVisitor::visitFromToPath(const Pin *, tag_bldr_no_crpr_->tagMatchArrival(to_tag, tag_match, arrival, arrival_index); if (tag_match == nullptr - || fuzzyGreater(to_arrival, arrival, min_max)) { + || delayGreater(to_arrival, arrival, min_max)) { tag_bldr_no_crpr_->setMatchArrival(to_tag, tag_match, to_arrival, arrival_index, &prev_path); @@ -1300,7 +1300,7 @@ ArrivalVisitor::pruneCrprArrivals() delayAsString(max_crpr, sta_), delayAsString(max_arrival_max_crpr, sta_)); Arrival arrival = tag_bldr_->arrival(arrival_index); - if (fuzzyGreater(max_arrival_max_crpr, arrival, min_max)) { + if (delayGreater(max_arrival_max_crpr, arrival, min_max)) { debugPrint1(debug, "search", 3, " pruned %s\n", tag->asString(sta_)); tag_bldr_->deleteArrival(tag); @@ -2207,7 +2207,7 @@ PathVisitor::visitFromPath(const Pin *from_pin, } else { arc_delay = search->deratedDelay(from_vertex, arc, edge, false, path_ap); - if (!fuzzyEqual(arc_delay, min_max->initValue())) { + if (!delayEqual(arc_delay, min_max->initValue())) { to_arrival = from_arrival + arc_delay; to_tag = search->thruTag(from_tag, edge, to_rf, min_max, path_ap); } @@ -3321,7 +3321,7 @@ RequiredCmp::requiredSet(int arrival_index, Required required, const MinMax *min_max) { - if (fuzzyGreater(required, requireds_[arrival_index], min_max)) { + if (delayGreater(required, requireds_[arrival_index], min_max)) { requireds_[arrival_index] = required; have_requireds_ = true; } @@ -3346,7 +3346,7 @@ RequiredCmp::requiredsSave(Vertex *vertex, Required req = requireds_[arrival_index]; if (prev_reqs) { Required prev_req = path->required(sta); - if (!fuzzyEqual(prev_req, req)) { + if (!delayEqual(prev_req, req)) { debugPrint2(debug, "search", 3, "required save %s -> %s\n", delayAsString(prev_req, sta), delayAsString(req, sta)); @@ -3640,7 +3640,7 @@ Search::totalNegativeSlack(const MinMax *min_max) for (Corner *corner : *corners_) { PathAPIndex path_ap_index = corner->findPathAnalysisPt(min_max)->index(); Slack tns1 = tns_[path_ap_index]; - if (tns1 < tns) + if (delayLess(tns1, tns)) tns = tns1; } return tns; @@ -3735,7 +3735,7 @@ Search::tnsIncr(Vertex *vertex, Slack slack, PathAPIndex path_ap_index) { - if (fuzzyLess(slack, 0.0)) { + if (delayLess(slack, 0.0)) { debugPrint2(debug_, "tns", 3, "tns+ %s %s\n", delayAsString(slack, this), vertex->name(sdc_network_)); @@ -3754,7 +3754,7 @@ Search::tnsDecr(Vertex *vertex, bool found; tns_slacks_[path_ap_index].findKey(vertex, slack, found); if (found - && fuzzyLess(slack, 0.0)) { + && delayLess(slack, 0.0)) { debugPrint2(debug_, "tns", 3, "tns- %s %s\n", delayAsString(slack, this), vertex->name(sdc_network_)); @@ -3880,7 +3880,7 @@ FindEndSlackVisitor::visit(PathEnd *path_end) PathRef &path = path_end->pathRef(); PathAPIndex path_ap_index = path.pathAnalysisPtIndex(sta_); Slack slack = path_end->slack(sta_); - if (fuzzyLess(slack, slacks_[path_ap_index])) + if (delayLess(slack, slacks_[path_ap_index])) slacks_[path_ap_index] = slack; } } @@ -3907,7 +3907,7 @@ Search::wnsSlacks(Vertex *vertex, PathAPIndex path_ap_index = path->pathAnalysisPtIndex(this); const Slack path_slack = path->slack(this); if (!path->tag(this)->isFilter() - && fuzzyLess(path_slack, slacks[path_ap_index])) + && delayLess(path_slack, slacks[path_ap_index])) slacks[path_ap_index] = path_slack; } } diff --git a/search/Sta.cc b/search/Sta.cc index c18a9d9f..e2680e73 100644 --- a/search/Sta.cc +++ b/search/Sta.cc @@ -2669,7 +2669,7 @@ Sta::vertexWorstArrivalPath(Vertex *vertex, PathVertex *path = path_iter.next(); Arrival arrival = path->arrival(this); if (!path->tag(this)->isGenClkSrcPath() - && fuzzyGreater(arrival, worst_arrival, min_max)) { + && delayGreater(arrival, worst_arrival, min_max)) { worst_arrival = arrival; worst_path.init(path); } @@ -2689,7 +2689,7 @@ Sta::vertexWorstArrivalPath(Vertex *vertex, Arrival arrival = path->arrival(this); if (path->minMax(this) == min_max && !path->tag(this)->isGenClkSrcPath() - && fuzzyGreater(arrival, worst_arrival, min_max)) { + && delayGreater(arrival, worst_arrival, min_max)) { worst_arrival = arrival; worst_path.init(path); } @@ -2709,7 +2709,7 @@ Sta::vertexWorstSlackPath(Vertex *vertex, PathVertex *path = path_iter.next(); Slack slack = path->slack(this); if (!path->tag(this)->isGenClkSrcPath() - && slack < min_slack) { + && delayLess(slack, min_slack)) { min_slack = slack; worst_path.init(path); } @@ -2730,7 +2730,7 @@ Sta::vertexWorstSlackPath(Vertex *vertex, if (path->minMax(this) == min_max && !path->tag(this)->isGenClkSrcPath()) { Slack slack = path->slack(this); - if (fuzzyLess(slack, min_slack)) { + if (delayLess(slack, min_slack)) { min_slack = slack; worst_path.init(path); } @@ -2764,7 +2764,7 @@ Sta::vertexArrival(Vertex *vertex, if ((clk_edge == clk_edge_wildcard || clk_info->clkEdge() == clk_edge) && !clk_info->isGenClkSrcPath() - && fuzzyGreater(path->arrival(this), arrival, min_max)) + && delayGreater(path->arrival(this), arrival, min_max)) arrival = path_arrival; } return arrival; @@ -2782,7 +2782,7 @@ Sta::vertexRequired(Vertex *vertex, const Path *path = path_iter.next(); if (path->minMax(this) == min_max) { const Required path_required = path->required(this); - if (fuzzyGreater(path_required, required, req_min_max)) + if (delayGreater(path_required, required, req_min_max)) required = path_required; } } @@ -2812,7 +2812,7 @@ Sta::vertexRequired(Vertex *vertex, const Required path_required = path->required(this); if ((clk_edge == clk_edge_wildcard || path->clkEdge(search_) == clk_edge) - && fuzzyGreater(path_required, required, min_max)) + && delayGreater(path_required, required, min_max)) required = path_required; } return required; @@ -2830,7 +2830,8 @@ Sta::netSlack(const Net *net, if (network_->isLoad(pin)) { Vertex *vertex = graph_->pinLoadVertex(pin); Slack pin_slack = vertexSlack(vertex, min_max); - slack = min(slack, pin_slack); + if (delayLess(pin_slack, slack)) + slack = pin_slack; } } return slack; @@ -2846,8 +2847,11 @@ Sta::pinSlack(const Pin *pin, Slack slack = MinMax::min()->initValue(); if (vertex) slack = vertexSlack(vertex, min_max); - if (bidirect_drvr_vertex) - slack = min(slack, vertexSlack(bidirect_drvr_vertex, min_max)); + if (bidirect_drvr_vertex) { + Slack slack1 = vertexSlack(bidirect_drvr_vertex, min_max); + if (delayLess(slack1, slack)) + slack = slack1; + } return slack; } @@ -2862,8 +2866,11 @@ Sta::pinSlack(const Pin *pin, Slack slack = MinMax::min()->initValue(); if (vertex) slack = vertexSlack(vertex, rf, min_max); - if (bidirect_drvr_vertex) - slack = min(slack, vertexSlack(bidirect_drvr_vertex, rf, min_max)); + if (bidirect_drvr_vertex) { + Slack slack1 = vertexSlack(bidirect_drvr_vertex, rf, min_max); + if (delayLess(slack1, slack)) + slack = slack1; + } return slack; } @@ -2879,7 +2886,7 @@ Sta::vertexSlack(Vertex *vertex, Path *path = path_iter.next(); if (path->minMax(this) == min_max) { Slack path_slack = path->slack(this); - if (path_slack < slack) + if (delayLess(path_slack, slack)) slack = path_slack; } } @@ -2897,7 +2904,7 @@ Sta::vertexSlack(Vertex *vertex, while (path_iter.hasNext()) { Path *path = path_iter.next(); Slack path_slack = path->slack(this); - if (path_slack < slack) + if (delayLess(path_slack, slack)) slack = path_slack; } return slack; @@ -2936,7 +2943,7 @@ Sta::vertexSlack1(Vertex *vertex, Slack path_slack = path->slack(this); if ((clk_edge == clk_edge_wildcard || path->clkEdge(search_) == clk_edge) - && path_slack < slack) + && delayLess(path_slack, slack)) slack = path_slack; } return slack; @@ -2958,7 +2965,7 @@ Sta::vertexSlacks(Vertex *vertex, Slack path_slack = path->slack(this); int rf_index = path->rfIndex(this); int mm_index = path->minMax(this)->index(); - if (path_slack < slacks[rf_index][mm_index]) + if (delayLess(path_slack, slacks[rf_index][mm_index])) slacks[rf_index][mm_index] = path_slack; } } @@ -3153,7 +3160,7 @@ Sta::vertexSlew(Vertex *vertex, Slew mm_slew = min_max->initValue(); for (DcalcAnalysisPt *dcalc_ap : corners_->dcalcAnalysisPts()) { Slew slew = graph_->slew(vertex, rf, dcalc_ap->index()); - if (fuzzyGreater(slew, mm_slew, min_max)) + if (delayGreater(slew, mm_slew, min_max)) mm_slew = slew; } return mm_slew; @@ -4775,7 +4782,7 @@ bool InstanceMaxSlewGreater::operator()(const Instance *inst1, const Instance *inst2) const { - return instMaxSlew(inst1) > instMaxSlew(inst2); + return delayGreater(instMaxSlew(inst1), instMaxSlew(inst2)); } Slew @@ -4792,7 +4799,7 @@ InstanceMaxSlewGreater::instMaxSlew(const Instance *inst) const for (RiseFall *rf : RiseFall::range()) { for (DcalcAnalysisPt *dcalc_ap : sta_->corners()->dcalcAnalysisPts()) { Slew slew = graph->slew(vertex, rf, dcalc_ap->index()); - if (slew > max_slew) + if (delayGreater(slew, max_slew)) max_slew = slew; } } diff --git a/search/WorstSlack.cc b/search/WorstSlack.cc index f3278e1d..704a6a14 100644 --- a/search/WorstSlack.cc +++ b/search/WorstSlack.cc @@ -48,7 +48,7 @@ WorstSlacks::worstSlack(const MinMax *min_max, Vertex *worst_vertex1; worst_slacks_[path_ap_index].worstSlack(path_ap_index, sta_, worst_slack1, worst_vertex1); - if (fuzzyLess(worst_slack1, worst_slack)) { + if (delayLess(worst_slack1, worst_slack)) { worst_slack = worst_slack1; worst_vertex = worst_vertex1; } @@ -159,10 +159,10 @@ WorstSlack::initQueue(PathAPIndex path_ap_index, while (end_iter.hasNext()) { Vertex *vertex = end_iter.next(); Slack slack = search->wnsSlack(vertex, path_ap_index); - if (!fuzzyEqual(slack, slack_init_)) { - if (fuzzyLess(slack, worst_slack_)) + if (!delayEqual(slack, slack_init_)) { + if (delayLess(slack, worst_slack_)) setWorstSlack(vertex, slack, sta); - if (fuzzyLessEqual(slack, slack_threshold_)) + if (delayLessEqual(slack, slack_threshold_)) queue_.insert(vertex); int queue_size = queue_.size(); if (queue_size >= max_queue_size_) @@ -206,7 +206,7 @@ WorstSlack::sortQueue(PathAPIndex path_ap_index, while (queue_iter2.hasNext()) { Vertex *vertex = queue_iter2.next(); Slack slack = search->wnsSlack(vertex, path_ap_index); - if (fuzzyGreater(slack, slack_threshold_)) + if (delayGreater(slack, slack_threshold_)) break; queue_.insert(vertex); } @@ -231,7 +231,7 @@ WorstSlack::findWorstInQueue(PathAPIndex path_ap_index, while (queue_iter.hasNext()) { Vertex *vertex = queue_iter.next(); Slack slack = search->wnsSlack(vertex, path_ap_index); - if (slack < worst_slack_) + if (delayLess(slack, worst_slack_)) setWorstSlack(vertex, slack, sta); } } @@ -248,7 +248,7 @@ WorstSlack::checkQueue(PathAPIndex path_ap_index, VertexSet::Iterator end_iter(search->endpoints()); while (end_iter.hasNext()) { Vertex *end = end_iter.next(); - if (fuzzyLessEqual(search->wnsSlack(end, path_ap_index), + if (delayLessEqual(search->wnsSlack(end, path_ap_index), slack_threshold_)) ends.push_back(end); } @@ -261,7 +261,7 @@ WorstSlack::checkQueue(PathAPIndex path_ap_index, Vertex *end = end_iter2.next(); end_set.insert(end); if (!queue_.hasKey(end) - && fuzzyLessEqual(search->wnsSlack(end, path_ap_index), + && delayLessEqual(search->wnsSlack(end, path_ap_index), slack_threshold_)) report->print("WorstSlack queue missing %s %s < %s\n", end->name(network), @@ -294,14 +294,14 @@ WorstSlack::updateWorstSlack(Vertex *vertex, // threads. UniqueLock lock(lock_); if (worst_vertex_ - && fuzzyLess(slack, worst_slack_)) + && delayLess(slack, worst_slack_)) setWorstSlack(vertex, slack, sta); else if (vertex == worst_vertex_) // Mark worst slack as unknown (updated by findWorstSlack(). worst_vertex_ = nullptr; - if (!fuzzyEqual(slack, slack_init_) - && fuzzyLessEqual(slack, slack_threshold_)) { + if (!delayEqual(slack, slack_init_) + && delayLessEqual(slack, slack_threshold_)) { debugPrint2(debug, "wns", 3, "insert %s %s\n", vertex->name(network), delayAsString(slack, sta)); @@ -341,8 +341,8 @@ bool WnsSlackLess::operator()(Vertex *vertex1, Vertex *vertex2) { - return fuzzyLess(search_->wnsSlack(vertex1, path_ap_index_), - search_->wnsSlack(vertex2, path_ap_index_)); + return delayLess(search_->wnsSlack(vertex1, path_ap_index_), + search_->wnsSlack(vertex2, path_ap_index_)); } } // namespace From 7653096fe69937a524118c4930c4fe1c50a8c617 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Sat, 11 Jul 2020 17:43:30 -0700 Subject: [PATCH 32/51] Delay compare ops round2 --- dcalc/GraphDelayCalc1.cc | 6 +- graph/DelayFloat.cc | 24 +++-- graph/DelayNormal1.cc | 177 +++++++++++++++++----------------- graph/DelayNormal2.cc | 64 ++++++------ include/sta/DelayFloat.hh | 29 ++++-- include/sta/DelayNormal1.hh | 25 +++-- include/sta/DelayNormal2.hh | 55 +++++++---- include/sta/Search.hh | 3 +- sdf/SdfReader.cc | 2 +- search/CheckMaxSkews.cc | 4 +- search/CheckMinPeriods.cc | 4 +- search/CheckMinPulseWidths.cc | 4 +- search/ClkInfo.cc | 4 +- search/Genclks.cc | 3 +- search/Latches.cc | 6 +- search/PathEnd.cc | 8 +- search/PathEnum.cc | 4 +- search/PathGroup.cc | 12 +-- search/Property.cc | 8 +- search/ReportPath.cc | 10 +- search/Search.cc | 27 +++--- search/Sta.cc | 34 +++---- search/WorstSlack.cc | 21 ++-- 23 files changed, 291 insertions(+), 243 deletions(-) diff --git a/dcalc/GraphDelayCalc1.cc b/dcalc/GraphDelayCalc1.cc index 7f3ab44e..99d97673 100644 --- a/dcalc/GraphDelayCalc1.cc +++ b/dcalc/GraphDelayCalc1.cc @@ -1246,7 +1246,7 @@ GraphDelayCalc1::findArcDelay(LibertyCell *drvr_cell, // Merge slews. const Slew &drvr_slew = graph_->slew(drvr_vertex, drvr_rf, ap_index); const MinMax *slew_min_max = dcalc_ap->slewMinMax(); - if (delayGreater(gate_slew, drvr_slew, dcalc_ap->slewMinMax()) + if (delayGreater(gate_slew, drvr_slew, dcalc_ap->slewMinMax(), this) && !drvr_vertex->slewAnnotated(drvr_rf, slew_min_max)) graph_->setSlew(drvr_vertex, drvr_rf, ap_index, gate_slew); if (!graph_->arcDelayAnnotated(edge, arc, ap_index)) { @@ -1446,7 +1446,7 @@ GraphDelayCalc1::annotateLoadDelays(Vertex *drvr_vertex, else { const Slew &slew = graph_->slew(load_vertex, drvr_rf, ap_index); if (!merge - || delayGreater(load_slew, slew, slew_min_max)) + || delayGreater(load_slew, slew, slew_min_max, this)) graph_->setSlew(load_vertex, drvr_rf, ap_index, load_slew); } } @@ -1459,7 +1459,7 @@ GraphDelayCalc1::annotateLoadDelays(Vertex *drvr_vertex, Delay wire_delay_extra = extra_delay + wire_delay; const MinMax *delay_min_max = dcalc_ap->delayMinMax(); if (!merge - || delayGreater(wire_delay_extra, delay, delay_min_max)) { + || delayGreater(wire_delay_extra, delay, delay_min_max, this)) { graph_->setWireArcDelay(wire_edge, drvr_rf, ap_index, wire_delay_extra); if (observer_) diff --git a/graph/DelayFloat.cc b/graph/DelayFloat.cc index d4372543..9fda2611 100644 --- a/graph/DelayFloat.cc +++ b/graph/DelayFloat.cc @@ -94,7 +94,8 @@ delayEqual(const Delay &delay1, bool delayLess(const Delay &delay1, - const Delay &delay2) + const Delay &delay2, + const StaState *) { return fuzzyLess(delay1, delay2); } @@ -102,7 +103,8 @@ delayLess(const Delay &delay1, bool delayLess(const Delay &delay1, const Delay &delay2, - const MinMax *min_max) + const MinMax *min_max, + const StaState *) { if (min_max == MinMax::max()) return fuzzyLess(delay1, delay2); @@ -112,7 +114,8 @@ delayLess(const Delay &delay1, bool delayLessEqual(const Delay &delay1, - const Delay &delay2) + const Delay &delay2, + const StaState *) { return fuzzyLessEqual(delay1, delay2); } @@ -120,7 +123,8 @@ delayLessEqual(const Delay &delay1, bool delayLessEqual(const Delay &delay1, const Delay &delay2, - const MinMax *min_max) + const MinMax *min_max, + const StaState *) { if (min_max == MinMax::max()) return fuzzyLessEqual(delay1, delay2); @@ -130,7 +134,8 @@ delayLessEqual(const Delay &delay1, bool delayGreater(const Delay &delay1, - const Delay &delay2) + const Delay &delay2, + const StaState *) { return fuzzyGreater(delay1, delay2); } @@ -138,7 +143,8 @@ delayGreater(const Delay &delay1, bool delayGreater(const Delay &delay1, const Delay &delay2, - const MinMax *min_max) + const MinMax *min_max, + const StaState *) { if (min_max == MinMax::max()) return fuzzyGreater(delay1, delay2); @@ -148,7 +154,8 @@ delayGreater(const Delay &delay1, bool delayGreaterEqual(const Delay &delay1, - const Delay &delay2) + const Delay &delay2, + const StaState *) { return fuzzyGreaterEqual(delay1, delay2); } @@ -156,7 +163,8 @@ delayGreaterEqual(const Delay &delay1, bool delayGreaterEqual(const Delay &delay1, const Delay &delay2, - const MinMax *min_max) + const MinMax *min_max, + const StaState *) { if (min_max == MinMax::max()) return fuzzyGreaterEqual(delay1, delay2); diff --git a/graph/DelayNormal1.cc b/graph/DelayNormal1.cc index fc70e885..3b2f5edb 100644 --- a/graph/DelayNormal1.cc +++ b/graph/DelayNormal1.cc @@ -24,8 +24,6 @@ #include "Fuzzy.hh" #include "Units.hh" #include "StaState.hh" -// temporary hack -#include "Sta.hh" // SSTA compilation. #if (SSTA == 1) @@ -185,6 +183,63 @@ makeDelay2(float delay, return Delay(delay, sigma2); } +float +delayAsFloat(const Delay &delay, + const EarlyLate *early_late, + const StaState *sta) +{ + if (sta->pocvEnabled()) { + if (early_late == EarlyLate::early()) + return delay.mean() - delay.sigma() * sta->sigmaFactor(); + else if (early_late == EarlyLate::late()) + return delay.mean() + delay.sigma() * sta->sigmaFactor(); + else + internalError("unknown early/late value."); + } + else + return delay.mean(); +} + +float +delaySigma2(const Delay &delay, + const EarlyLate *) +{ + return delay.sigma2(); +} + +const char * +delayAsString(const Delay &delay, + const StaState *sta) +{ + return delayAsString(delay, sta, sta->units()->timeUnit()->digits()); +} + +const char * +delayAsString(const Delay &delay, + const StaState *sta, + int digits) +{ + const Unit *unit = sta->units()->timeUnit(); + if (sta->pocvEnabled()) { + float sigma = delay.sigma(); + return stringPrintTmp("%s[%s]", + unit->asString(delay.mean(), digits), + unit->asString(sigma, digits)); + } + else + return unit->asString(delay.mean(), digits); +} + +const char * +delayAsString(const Delay &delay, + const EarlyLate *early_late, + const StaState *sta, + int digits) +{ + float mean_sigma = delayAsFloat(delay, early_late, sta); + return sta->units()->timeUnit()->asString(mean_sigma, digits); +} + bool delayIsInitValue(const Delay &delay, const MinMax *min_max) @@ -216,18 +271,18 @@ delayEqual(const Delay &delay1, bool delayLess(const Delay &delay1, - const Delay &delay2) + const Delay &delay2, + const StaState *sta) { - Sta *sta = Sta::sta(); return fuzzyLess(delayAsFloat(delay1, EarlyLate::early(), sta), delayAsFloat(delay2, EarlyLate::early(), sta)); } bool delayLess(const Delay &delay1, - float delay2) + float delay2, + const StaState *sta) { - Sta *sta = Sta::sta(); return fuzzyLess(delayAsFloat(delay1, EarlyLate::early(), sta), delay2); } @@ -235,28 +290,29 @@ delayLess(const Delay &delay1, bool delayLess(const Delay &delay1, const Delay &delay2, - const MinMax *min_max) + const MinMax *min_max, + const StaState *sta) { if (min_max == MinMax::max()) - return delayLess(delay1, delay2); + return delayLess(delay1, delay2, sta); else - return delayGreater(delay1, delay2); + return delayGreater(delay1, delay2, sta); } bool delayLessEqual(const Delay &delay1, - const Delay &delay2) + const Delay &delay2, + const StaState *sta) { - Sta *sta = Sta::sta(); return fuzzyLessEqual(delayAsFloat(delay1, EarlyLate::early(), sta), delayAsFloat(delay2, EarlyLate::early(), sta)); } bool delayLessEqual(const Delay &delay1, - float delay2) + float delay2, + const StaState *sta) { - Sta *sta = Sta::sta(); return fuzzyLessEqual(delayAsFloat(delay1, EarlyLate::early(), sta), delay2); } @@ -264,46 +320,48 @@ delayLessEqual(const Delay &delay1, bool delayLessEqual(const Delay &delay1, const Delay &delay2, - const MinMax *min_max) + const MinMax *min_max, + const StaState *sta) { if (min_max == MinMax::max()) - return delayLessEqual(delay1, delay2); + return delayLessEqual(delay1, delay2, sta); else - return delayGreaterEqual(delay1, delay2); + return delayGreaterEqual(delay1, delay2, sta); } bool delayGreater(const Delay &delay1, - const Delay &delay2) + const Delay &delay2, + const StaState *sta) + { - Sta *sta = Sta::sta(); return fuzzyGreater(delayAsFloat(delay1, EarlyLate::late(), sta), delayAsFloat(delay2, EarlyLate::late(), sta)); } bool delayGreater(const Delay &delay1, - float delay2) + float delay2, + const StaState *sta) { - Sta *sta = Sta::sta(); return fuzzyGreater(delayAsFloat(delay1, EarlyLate::late(), sta), delay2); } bool delayGreaterEqual(const Delay &delay1, - const Delay &delay2) + const Delay &delay2, + const StaState *sta) { - Sta *sta = Sta::sta(); return fuzzyGreaterEqual(delayAsFloat(delay1, EarlyLate::late(), sta), delayAsFloat(delay2, EarlyLate::late(), sta)); } bool delayGreaterEqual(const Delay &delay1, - float delay2) + float delay2, + const StaState *sta) { - Sta *sta = Sta::sta(); return fuzzyGreaterEqual(delayAsFloat(delay1, EarlyLate::late(), sta), delay2); } @@ -311,80 +369,25 @@ delayGreaterEqual(const Delay &delay1, bool delayGreater(const Delay &delay1, const Delay &delay2, - const MinMax *min_max) + const MinMax *min_max, + const StaState *sta) { if (min_max == MinMax::max()) - return delayGreater(delay1, delay2); + return delayGreater(delay1, delay2, sta); else - return delayLess(delay1, delay2); + return delayLess(delay1, delay2, sta); } bool delayGreaterEqual(const Delay &delay1, const Delay &delay2, - const MinMax *min_max) + const MinMax *min_max, + const StaState *sta) { if (min_max == MinMax::max()) - return delayGreaterEqual(delay1, delay2); - else - return delayLessEqual(delay1, delay2); -} - -float -delayAsFloat(const Delay &delay, - const EarlyLate *early_late, - const StaState *sta) -{ - if (sta->pocvEnabled()) { - if (early_late == EarlyLate::early()) - return delay.mean() - delay.sigma() * sta->sigmaFactor(); - else if (early_late == EarlyLate::late()) - return delay.mean() + delay.sigma() * sta->sigmaFactor(); - else - internalError("unknown early/late value."); - } - else - return delay.mean(); -} - -float -delaySigma2(const Delay &delay, - const EarlyLate *) -{ - return delay.sigma2(); -} - -const char * -delayAsString(const Delay &delay, - const StaState *sta) -{ - return delayAsString(delay, sta, sta->units()->timeUnit()->digits()); -} - -const char * -delayAsString(const Delay &delay, - const StaState *sta, - int digits) -{ - const Unit *unit = sta->units()->timeUnit(); - if (sta->pocvEnabled()) { - float sigma = delay.sigma(); - return stringPrintTmp("%s[%s]", - unit->asString(delay.mean(), digits), - unit->asString(sigma, digits)); - } + return delayGreaterEqual(delay1, delay2, sta); else - return unit->asString(delay.mean(), digits); -} - -const char * -delayAsString(const Delay &delay, - const EarlyLate *early_late, - const StaState *sta, - int digits) -{ - float mean_sigma = delayAsFloat(delay, early_late, sta); - return sta->units()->timeUnit()->asString(mean_sigma, digits); + return delayLessEqual(delay1, delay2, sta); } Delay diff --git a/graph/DelayNormal2.cc b/graph/DelayNormal2.cc index 739676b8..883aa306 100644 --- a/graph/DelayNormal2.cc +++ b/graph/DelayNormal2.cc @@ -24,8 +24,6 @@ #include "Fuzzy.hh" #include "Units.hh" #include "StaState.hh" -// temporary hack -#include "Sta.hh" // SSTA compilation. #if (SSTA == 2) @@ -241,18 +239,18 @@ delayEqual(const Delay &delay1, bool delayLess(const Delay &delay1, - const Delay &delay2) + const Delay &delay2, + const StaState *sta) { - Sta *sta = Sta::sta(); return fuzzyLess(delayAsFloat(delay1, EarlyLate::early(), sta), delayAsFloat(delay2, EarlyLate::early(), sta)); } bool delayLess(const Delay &delay1, - float delay2) + float delay2, + const StaState *sta) { - Sta *sta = Sta::sta(); return fuzzyLess(delayAsFloat(delay1, EarlyLate::early(), sta), delay2); } @@ -260,28 +258,29 @@ delayLess(const Delay &delay1, bool delayLess(const Delay &delay1, const Delay &delay2, - const MinMax *min_max) + const MinMax *min_max, + const StaState *sta) { if (min_max == MinMax::max()) - return delayLess(delay1, delay2); + return delayLess(delay1, delay2, sta); else - return delayGreater(delay1, delay2); + return delayGreater(delay1, delay2, sta); } bool delayLessEqual(const Delay &delay1, - const Delay &delay2) + const Delay &delay2, + const StaState *sta) { - Sta *sta = Sta::sta(); return fuzzyLessEqual(delayAsFloat(delay1, EarlyLate::early(), sta), delayAsFloat(delay2, EarlyLate::early(), sta)); } bool delayLessEqual(const Delay &delay1, - float delay2) + float delay2, + const StaState *sta) { - Sta *sta = Sta::sta(); return fuzzyLessEqual(delayAsFloat(delay1, EarlyLate::early(), sta), delay2); } @@ -289,70 +288,73 @@ delayLessEqual(const Delay &delay1, bool delayLessEqual(const Delay &delay1, const Delay &delay2, - const MinMax *min_max) + const MinMax *min_max, + const StaState *sta) { if (min_max == MinMax::max()) - return delayLessEqual(delay1, delay2); + return delayLessEqual(delay1, delay2, sta); else - return delayGreaterEqual(delay1, delay2); + return delayGreaterEqual(delay1, delay2, sta); } bool delayGreater(const Delay &delay1, - const Delay &delay2) + const Delay &delay2, + const StaState *sta) { - Sta *sta = Sta::sta(); return fuzzyGreater(delayAsFloat(delay1, EarlyLate::late(), sta), delayAsFloat(delay2, EarlyLate::late(), sta)); } bool delayGreater(const Delay &delay1, - float delay2) + float delay2, + const StaState *sta) { - Sta *sta = Sta::sta(); return fuzzyGreaterEqual(delayAsFloat(delay1, EarlyLate::late(), sta), delayAsFloat(delay2, EarlyLate::late(), sta)); } bool delayGreaterEqual(const Delay &delay1, - const Delay &delay2) + const Delay &delay2, + const StaState *sta) { - Sta *sta = Sta::sta(); return fuzzyGreaterEqual(delayAsFloat(delay1, EarlyLate::late(), sta), delayAsFloat(delay2, EarlyLate::late(), sta)); } bool delayGreaterEqual(const Delay &delay1, - float delay2) + float delay2, + const StaState *sta) { - Sta *sta = Sta::sta(); - return delayGreaterEqual(delayAsFloat(delay1, EarlyLate::late(), sta), + return fuzzyGreaterEqual(delayAsFloat(delay1, EarlyLate::late(), sta), delay2); } bool delayGreater(const Delay &delay1, const Delay &delay2, - const MinMax *min_max) + const MinMax *min_max, + const StaState *sta) { if (min_max == MinMax::max()) - return delayGreater(delay1, delay2); + return delayGreater(delay1, delay2, sta); else - return delayLess(delay1, delay2); + return delayLess(delay1, delay2, sta); } bool delayGreaterEqual(const Delay &delay1, const Delay &delay2, - const MinMax *min_max) + const MinMax *min_max, + const StaState *sta) { if (min_max == MinMax::max()) - return delayGreaterEqual(delay1, delay2); + return delayGreaterEqual(delay1, delay2, sta); else - return delayLessEqual(delay1, delay2); + return delayLessEqual(delay1, delay2, sta); } float diff --git a/include/sta/DelayFloat.hh b/include/sta/DelayFloat.hh index e895eae1..58987103 100644 --- a/include/sta/DelayFloat.hh +++ b/include/sta/DelayFloat.hh @@ -96,33 +96,42 @@ delayEqual(const Delay &delay1, const Delay &delay2); bool delayLess(const Delay &delay1, - const Delay &delay2); + const Delay &delay2, + const StaState *sta); bool delayLess(const Delay &delay1, const Delay &delay2, - const MinMax *min_max); + const MinMax *min_max, + const StaState *sta); bool delayLessEqual(const Delay &delay1, - const Delay &delay2); + const Delay &delay2, + const StaState *sta); bool delayLessEqual(const Delay &delay1, const Delay &delay2, - const MinMax *min_max); -bool -delayGreater(const Delay &delay1, - const Delay &delay2); + const MinMax *min_max, + const StaState *sta); bool delayGreater(const Delay &delay1, const Delay &delay2, - const MinMax *min_max); + const StaState *sta); bool delayGreaterEqual(const Delay &delay1, - const Delay &delay2); + const Delay &delay2, + const StaState *sta); bool delayGreaterEqual(const Delay &delay1, const Delay &delay2, - const MinMax *min_max); + const MinMax *min_max, + const StaState *sta); +bool +delayGreater(const Delay &delay1, + const Delay &delay2, + const MinMax *min_max, + const StaState *sta); +// delay1-delay2 subtracting sigma instead of addiing. Delay delayRemove(const Delay &delay1, const Delay &delay2); diff --git a/include/sta/DelayNormal1.hh b/include/sta/DelayNormal1.hh index 6a3bfb86..b25529c7 100644 --- a/include/sta/DelayNormal1.hh +++ b/include/sta/DelayNormal1.hh @@ -111,32 +111,41 @@ delayEqual(const Delay &delay1, const Delay &delay2); bool delayLess(const Delay &delay1, - const Delay &delay2); + const Delay &delay2, + const StaState *sta); bool delayLess(const Delay &delay1, const Delay &delay2, - const MinMax *min_max); + const MinMax *min_max, + const StaState *sta); bool delayLessEqual(const Delay &delay1, - const Delay &delay2); + const Delay &delay2, + const StaState *sta); bool delayLessEqual(const Delay &delay1, const Delay &delay2, - const MinMax *min_max); + const MinMax *min_max, + const StaState *sta); bool delayGreater(const Delay &delay1, - const Delay &delay2); + const Delay &delay2, + const StaState *sta); bool delayGreaterEqual(const Delay &delay1, - const Delay &delay2); + const Delay &delay2, + const StaState *sta); bool delayGreaterEqual(const Delay &delay1, const Delay &delay2, - const MinMax *min_max); + const MinMax *min_max, + const StaState *sta); bool delayGreater(const Delay &delay1, const Delay &delay2, - const MinMax *min_max); + const MinMax *min_max, + const StaState *sta); + // delay1-delay2 subtracting sigma instead of addiing. Delay delayRemove(const Delay &delay1, const Delay &delay2); diff --git a/include/sta/DelayNormal2.hh b/include/sta/DelayNormal2.hh index ab1c58c6..44f3d278 100644 --- a/include/sta/DelayNormal2.hh +++ b/include/sta/DelayNormal2.hh @@ -66,6 +66,19 @@ const Delay delay_zero(0.0); void initDelayConstants(); +const char * +delayAsString(const Delay &delay, + const StaState *sta); +const char * +delayAsString(const Delay &delay, + const StaState *sta, + int digits); +const char * +delayAsString(const Delay &delay, + const EarlyLate *early_late, + const StaState *sta, + int digits); + Delay makeDelay(float delay, float sigma_early, @@ -78,7 +91,10 @@ makeDelay2(float delay, float sigma_late); inline float -delayAsFloat(const Delay &delay) { return delay.mean(); } +delayAsFloat(const Delay &delay) +{ + return delay.mean(); +} // mean late+/early- sigma float @@ -88,18 +104,6 @@ delayAsFloat(const Delay &delay, float delaySigma2(const Delay &delay, const EarlyLate *early_late); -const char * -delayAsString(const Delay &delay, - const StaState *sta); -const char * -delayAsString(const Delay &delay, - const StaState *sta, - int digits); -const char * -delayAsString(const Delay &delay, - const EarlyLate *early_late, - const StaState *sta, - int digits); const Delay & delayInitValue(const MinMax *min_max); bool @@ -114,32 +118,41 @@ delayEqual(const Delay &delay1, const Delay &delay2); bool delayLess(const Delay &delay1, - const Delay &delay2); + const Delay &delay2, + const StaState *sta); bool delayLess(const Delay &delay1, const Delay &delay2, - const MinMax *min_max); + const MinMax *min_max, + const StaState *sta); bool delayLessEqual(const Delay &delay1, - const Delay &delay2); + const Delay &delay2, + const StaState *sta); bool delayLessEqual(const Delay &delay1, const Delay &delay2, - const MinMax *min_max); + const MinMax *min_max, + const StaState *sta); bool delayGreater(const Delay &delay1, - const Delay &delay2); + const Delay &delay2, + const StaState *sta); bool delayGreaterEqual(const Delay &delay1, - const Delay &delay2); + const Delay &delay2, + const StaState *sta); bool delayGreaterEqual(const Delay &delay1, const Delay &delay2, - const MinMax *min_max); + const MinMax *min_max, + const StaState *sta); bool delayGreater(const Delay &delay1, const Delay &delay2, - const MinMax *min_max); + const MinMax *min_max, + const StaState *sta); + // delay1-delay2 subtracting sigma instead of addiing. Delay delayRemove(const Delay &delay1, const Delay &delay2); diff --git a/include/sta/Search.hh b/include/sta/Search.hh index 09929e3c..18efcf20 100644 --- a/include/sta/Search.hh +++ b/include/sta/Search.hh @@ -752,7 +752,8 @@ public: const StaState *sta); void requiredSet(int arrival_index, Required required, - const MinMax *min_max); + const MinMax *min_max, + const StaState *sta); // Return true if the requireds changed. bool requiredsSave(Vertex *vertex, const StaState *sta); diff --git a/sdf/SdfReader.cc b/sdf/SdfReader.cc index 34502129..e0f91d77 100644 --- a/sdf/SdfReader.cc +++ b/sdf/SdfReader.cc @@ -809,7 +809,7 @@ SdfReader::setEdgeArcDelaysCondUse(Edge *edge, delay = graph_->arcDelay(edge, arc, arc_delay_index) + *value; else if (graph_->arcDelayAnnotated(edge, arc, arc_delay_index)) { ArcDelay prev_value = graph_->arcDelay(edge, arc, arc_delay_index); - if (delayGreater(prev_value, delay, min_max)) + if (delayGreater(prev_value, delay, min_max, this)) delay = prev_value; } graph_->setArcDelay(edge, arc, arc_delay_index, delay); diff --git a/search/CheckMaxSkews.cc b/search/CheckMaxSkews.cc index a3081697..19583141 100644 --- a/search/CheckMaxSkews.cc +++ b/search/CheckMaxSkews.cc @@ -107,7 +107,7 @@ void MaxSkewViolatorsVisititor::visit(MaxSkewCheck &check, const StaState *sta) { - if (delayLess(check.slack(sta), 0.0)) + if (delayLess(check.slack(sta), 0.0, sta)) checks_.push_back(new MaxSkewCheck(check)); } @@ -280,7 +280,7 @@ MaxSkewSlackLess::operator()(const MaxSkewCheck *check1, { Slack slack1 = check1->slack(sta_); Slack slack2 = check2->slack(sta_); - return delayLess(slack1, slack2) + return delayLess(slack1, slack2, sta_) || (delayEqual(slack1, slack2) // Break ties based on constrained pin names. && sta_->network()->pinLess(check1->clkPin(sta_),check2->clkPin(sta_))); diff --git a/search/CheckMinPeriods.cc b/search/CheckMinPeriods.cc index 7e797c53..e4a47c70 100644 --- a/search/CheckMinPeriods.cc +++ b/search/CheckMinPeriods.cc @@ -79,7 +79,7 @@ void MinPeriodViolatorsVisitor::visit(MinPeriodCheck &check, StaState *sta) { - if (delayLess(check.slack(sta), 0.0)) + if (delayLess(check.slack(sta), 0.0, sta)) checks_.push_back(check.copy()); } @@ -231,7 +231,7 @@ MinPeriodSlackLess::operator()(const MinPeriodCheck *check1, Slack slack2 = check2->slack(sta_); const Pin *pin1 = check1->pin(); const Pin *pin2 = check2->pin(); - return delayLess(slack1, slack2) + return delayLess(slack1, slack2, sta_) // Break ties based on pin and clock names. || (delayEqual(slack1, slack2) && (sta_->network()->pinLess(pin1, pin2) diff --git a/search/CheckMinPulseWidths.cc b/search/CheckMinPulseWidths.cc index 78d1dfa9..28fe6dec 100644 --- a/search/CheckMinPulseWidths.cc +++ b/search/CheckMinPulseWidths.cc @@ -167,7 +167,7 @@ void MinPulseWidthViolatorsVisitor::visit(MinPulseWidthCheck &check, const StaState *sta) { - if (delayLess(check.slack(sta), 0.0) + if (delayLess(check.slack(sta), 0.0, sta) && (corner_ == nullptr || check.corner(sta) == corner_)) { MinPulseWidthCheck *copy = new MinPulseWidthCheck(check.openPath()); @@ -499,7 +499,7 @@ MinPulseWidthSlackLess::operator()(const MinPulseWidthCheck *check1, Slack slack2 = check2->slack(sta_); const Pin *pin1 = check1->pin(sta_); const Pin *pin2 = check2->pin(sta_); - return delayLess(slack1, slack2) + return delayLess(slack1, slack2, sta_) || (delayEqual(slack1, slack2) // Break ties for the sake of regression stability. && (sta_->network()->pinLess(pin1, pin2) diff --git a/search/ClkInfo.cc b/search/ClkInfo.cc index d1d3388f..f8b0c5e0 100644 --- a/search/ClkInfo.cc +++ b/search/ClkInfo.cc @@ -294,9 +294,9 @@ clkInfoCmp(const ClkInfo *clk_info1, const Arrival &insert1 = clk_info1->insertion(); const Arrival &insert2 = clk_info2->insertion(); - if (delayLess(insert1, insert2)) + if (delayLess(insert1, insert2, sta)) return -1; - if (delayGreater(insert1, insert2)) + if (delayGreater(insert1, insert2, sta)) return 1; float latency1 = clk_info1->latency(); diff --git a/search/Genclks.cc b/search/Genclks.cc index 5abdd31a..9e89fd98 100644 --- a/search/Genclks.cc +++ b/search/Genclks.cc @@ -970,7 +970,8 @@ Genclks::recordSrcPaths(Clock *gclk) && (src_path.isNull() || delayGreater(path->arrival(this), src_path.arrival(this), - early_late))) { + early_late, + this))) { debugPrint4(debug_, "genclk", 2, " %s insertion %s %s %s\n", network_->pathName(gclk_pin), early_late->asString(), diff --git a/search/Latches.cc b/search/Latches.cc index 1a127567..687ecfec 100644 --- a/search/Latches.cc +++ b/search/Latches.cc @@ -98,7 +98,7 @@ Latches::latchRequired(const Path *data_path, network_->pathName(data_path->pin(this)), delayAsString(data_arrival, this), delayAsString(enable_arrival, this)); - if (delayLessEqual(data_arrival, enable_arrival)) { + if (delayLessEqual(data_arrival, enable_arrival, this)) { // Data arrives before latch opens. required = enable_arrival; borrow = 0.0; @@ -108,7 +108,7 @@ Latches::latchRequired(const Path *data_path, else { // Data arrives while latch is transparent. borrow = data_arrival - enable_arrival; - if (delayLessEqual(borrow, max_borrow)) + if (delayLessEqual(borrow, max_borrow, this)) required = data_arrival; else { borrow = max_borrow; @@ -332,7 +332,7 @@ Latches::latchOutArrival(Path *data_path, latchRequired(data_path, enable_path, &disable_path, path_ap, required, borrow, adjusted_data_arrival, time_given_to_startpoint); - if (delayGreater(borrow, 0.0)) { + if (delayGreater(borrow, 0.0, this)) { // Latch is transparent when data arrives. arc_delay = search_->deratedDelay(data_vertex, d_q_arc, d_q_edge, false, path_ap); diff --git a/search/PathEnd.cc b/search/PathEnd.cc index 18d11cc7..ff7a9b32 100644 --- a/search/PathEnd.cc +++ b/search/PathEnd.cc @@ -1244,7 +1244,7 @@ PathEndLatchCheck::targetClkWidth(const StaState *sta) const if (enable_clk_info->isPulseClk()) return disable_arrival - enable_arrival; else { - if (delayGreater(enable_arrival, disable_arrival)) { + if (delayGreater(enable_arrival, disable_arrival, sta)) { float period = enable_clk_info->clock()->period(); disable_arrival += period; } @@ -1999,14 +1999,14 @@ PathEnd::cmpSlack(const PathEnd *path_end1, // based on borrow time. if (delayEqual(borrow1, borrow2)) return 0; - else if (delayGreater(borrow1, borrow2)) + else if (delayGreater(borrow1, borrow2, sta)) return -1; else return 1; } else if (delayEqual(slack1, slack2)) return 0; - else if (delayLess(slack1, slack2)) + else if (delayLess(slack1, slack2, sta)) return -1; else return 1; @@ -2022,7 +2022,7 @@ PathEnd::cmpArrival(const PathEnd *path_end1, const MinMax *min_max = path_end1->minMax(sta); if (delayEqual(arrival1, arrival2)) return 0; - else if (delayLess(arrival1, arrival2, min_max)) + else if (delayLess(arrival1, arrival2, min_max, sta)) return -1; else return 1; diff --git a/search/PathEnum.cc b/search/PathEnum.cc index ce7abd00..7ff1f781 100644 --- a/search/PathEnum.cc +++ b/search/PathEnum.cc @@ -348,7 +348,7 @@ PathEnumFaninVisitor::visitFromToPath(const Pin *, // Make the diverted path end to check slack with from_path crpr. makeDivertedPathEnd(from_path, arc, div_end, after_div_copy); // Only enumerate paths with greater slack. - if (delayGreaterEqual(div_end->slack(sta_), path_end_slack_)) { + if (delayGreaterEqual(div_end->slack(sta_), path_end_slack_, sta_)) { reportDiversion(arc, from_path); path_enum_->makeDiversion(div_end, after_div_copy); } @@ -356,7 +356,7 @@ PathEnumFaninVisitor::visitFromToPath(const Pin *, delete div_end; } // Only enumerate slower/faster paths. - else if (delayLessEqual(to_arrival, before_div_arrival_, min_max)) { + else if (delayLessEqual(to_arrival, before_div_arrival_, min_max, sta_)) { PathEnd *div_end; PathEnumed *after_div_copy; makeDivertedPathEnd(from_path, arc, div_end, after_div_copy); diff --git a/search/PathGroup.cc b/search/PathGroup.cc index d2856083..0afe3ab2 100644 --- a/search/PathGroup.cc +++ b/search/PathGroup.cc @@ -101,19 +101,19 @@ PathGroup::savable(PathEnd *path_end) // without crpr first because it is expensive to find. Slack slack = path_end->slackNoCrpr(sta_); if (!delayIsInitValue(slack, min_max_) - && delayLessEqual(slack, threshold_) - && delayLessEqual(slack, slack_max_)) { + && delayLessEqual(slack, threshold_, sta_) + && delayLessEqual(slack, slack_max_, sta_)) { // Now check with crpr. slack = path_end->slack(sta_); - savable = delayLessEqual(slack, threshold_) - && delayLessEqual(slack, slack_max_) - && delayGreaterEqual(slack, slack_min_); + savable = delayLessEqual(slack, threshold_, sta_) + && delayLessEqual(slack, slack_max_, sta_) + && delayGreaterEqual(slack, slack_min_, sta_); } } else { const Arrival &arrival = path_end->dataArrivalTime(sta_); savable = !delayIsInitValue(arrival, min_max_) - && delayGreaterEqual(arrival, threshold_, min_max_); + && delayGreaterEqual(arrival, threshold_, min_max_, sta_); } return savable; } diff --git a/search/Property.cc b/search/Property.cc index c06c0fe7..f77e0889 100644 --- a/search/Property.cc +++ b/search/Property.cc @@ -801,12 +801,12 @@ pinSlewProperty(const Pin *pin, Slew slew = min_max->initValue(); if (vertex) { Slew vertex_slew = sta->vertexSlew(vertex, rf, min_max); - if (delayGreater(vertex_slew, slew, min_max)) + if (delayGreater(vertex_slew, slew, min_max, sta)) slew = vertex_slew; } if (bidirect_drvr_vertex) { Slew vertex_slew = sta->vertexSlew(bidirect_drvr_vertex, rf, min_max); - if (delayGreater(vertex_slew, slew, min_max)) + if (delayGreater(vertex_slew, slew, min_max, sta)) slew = vertex_slew; } return PropertyValue(delayPropertyValue(slew, sta)); @@ -879,9 +879,9 @@ edgeDelayProperty(Edge *edge, ArcDelay arc_delay = sta->arcDelay(edge, arc, dcalc_ap); if (!delay_exists || ((min_max == MinMax::max() - && delayGreater(arc_delay, delay)) + && delayGreater(arc_delay, delay, sta)) || (min_max == MinMax::min() - && delayLess(arc_delay, delay)))) + && delayLess(arc_delay, delay, sta)))) delay = arc_delay; } } diff --git a/search/ReportPath.cc b/search/ReportPath.cc index e5998078..2a9187d9 100644 --- a/search/ReportPath.cc +++ b/search/ReportPath.cc @@ -578,7 +578,7 @@ ReportPath::reportFull(const PathEndLatchCheck *end, else reportTgtClk(end, result); - if (delayGreaterEqual(borrow, 0.0)) + if (delayGreaterEqual(borrow, 0.0, this)) reportLine("time borrowed from endpoint", borrow, req_time, early_late, result); else @@ -659,7 +659,7 @@ ReportPath::reportBorrowing(const PathEndLatchCheck *end, reportLineTotalMinus("CRPR difference", crpr_diff, early_late, result); reportLineTotal("max time borrow", max_borrow, early_late, result); } - if (delayGreater(borrow, delay_zero) + if (delayGreater(borrow, delay_zero, this) && (!fuzzyZero(open_uncertainty) || !delayZero(open_crpr))) { reportDashLineTotal(result); @@ -2050,7 +2050,7 @@ ReportPath::reportSrcClkAndPath(const Path *path, else if (clk_used_as_data) { reportClkLine(clk, clk_name.c_str(), clk_end_rf, clk_time, early_late, result); - if (delayGreater(clk_insertion, 0.0)) + if (delayGreater(clk_insertion, 0.0, this)) reportClkSrcLatency(clk_insertion, clk_time, early_late, result); if (reportClkPath()) reportPath1(path, expanded, true, time_offset, result); @@ -2067,7 +2067,7 @@ ReportPath::reportSrcClkAndPath(const Path *path, } else { if (is_path_delay) { - if (delayGreater(clk_delay, 0.0)) + if (delayGreater(clk_delay, 0.0, this)) reportLine(clkNetworkDelayIdealProp(is_prop), clk_delay, clk_end_time, early_late, result); } @@ -2613,7 +2613,7 @@ ReportPath::reportPath1(const Path *path, } Arrival time = latch_enable_time + latch_time_given; Arrival incr = latch_time_given; - if (delayGreaterEqual(incr, 0.0)) + if (delayGreaterEqual(incr, 0.0, this)) reportLine("time given to startpoint", incr, time, early_late, result); else reportLine("time borrowed from startpoint", incr, time, diff --git a/search/Search.cc b/search/Search.cc index 0808f18c..48c7afe1 100644 --- a/search/Search.cc +++ b/search/Search.cc @@ -1238,7 +1238,7 @@ ArrivalVisitor::visitFromToPath(const Pin *, Tag *tag_match; tag_bldr_->tagMatchArrival(to_tag, tag_match, arrival, arrival_index); if (tag_match == nullptr - || delayGreater(to_arrival, arrival, min_max)) { + || delayGreater(to_arrival, arrival, min_max, sta_)) { debugPrint5(debug, "search", 3, " %s + %s = %s %s %s\n", delayAsString(from_path->arrival(sta_), sta_), delayAsString(arc_delay, sta_), @@ -1258,7 +1258,7 @@ ArrivalVisitor::visitFromToPath(const Pin *, tag_bldr_no_crpr_->tagMatchArrival(to_tag, tag_match, arrival, arrival_index); if (tag_match == nullptr - || delayGreater(to_arrival, arrival, min_max)) { + || delayGreater(to_arrival, arrival, min_max, sta_)) { tag_bldr_no_crpr_->setMatchArrival(to_tag, tag_match, to_arrival, arrival_index, &prev_path); @@ -1300,7 +1300,7 @@ ArrivalVisitor::pruneCrprArrivals() delayAsString(max_crpr, sta_), delayAsString(max_arrival_max_crpr, sta_)); Arrival arrival = tag_bldr_->arrival(arrival_index); - if (delayGreater(max_arrival_max_crpr, arrival, min_max)) { + if (delayGreater(max_arrival_max_crpr, arrival, min_max, sta_)) { debugPrint1(debug, "search", 3, " pruned %s\n", tag->asString(sta_)); tag_bldr_->deleteArrival(tag); @@ -3254,7 +3254,7 @@ FindEndRequiredVisitor::visit(PathEnd *path_end) bool arrival_exists; path.arrivalIndex(arrival_index, arrival_exists); Required required = path_end->requiredTime(sta_); - required_cmp_->requiredSet(arrival_index, required, req_min); + required_cmp_->requiredSet(arrival_index, required, req_min, sta_); } } @@ -3319,9 +3319,10 @@ RequiredCmp::requiredsInit(Vertex *vertex, void RequiredCmp::requiredSet(int arrival_index, Required required, - const MinMax *min_max) + const MinMax *min_max, + const StaState *sta) { - if (delayGreater(required, requireds_[arrival_index], min_max)) { + if (delayGreater(required, requireds_[arrival_index], min_max, sta)) { requireds_[arrival_index] = required; have_requireds_ = true; } @@ -3463,7 +3464,7 @@ RequiredVisitor::visitFromToPath(const Pin *, delayAsString(from_required, sta_), min_max == MinMax::max() ? "<" : ">", delayAsString(required_cmp_->required(arrival_index), sta_)); - required_cmp_->requiredSet(arrival_index, from_required, req_min); + required_cmp_->requiredSet(arrival_index, from_required, req_min, sta_); } else { if (sta_->search()->crprApproxMissingRequireds()) { @@ -3487,7 +3488,7 @@ RequiredVisitor::visitFromToPath(const Pin *, min_max == MinMax::max() ? "<" : ">", delayAsString(required_cmp_->required(arrival_index), sta_)); - required_cmp_->requiredSet(arrival_index, from_required, req_min); + required_cmp_->requiredSet(arrival_index, from_required, req_min, sta_); break; } } @@ -3640,7 +3641,7 @@ Search::totalNegativeSlack(const MinMax *min_max) for (Corner *corner : *corners_) { PathAPIndex path_ap_index = corner->findPathAnalysisPt(min_max)->index(); Slack tns1 = tns_[path_ap_index]; - if (delayLess(tns1, tns)) + if (delayLess(tns1, tns, this)) tns = tns1; } return tns; @@ -3735,7 +3736,7 @@ Search::tnsIncr(Vertex *vertex, Slack slack, PathAPIndex path_ap_index) { - if (delayLess(slack, 0.0)) { + if (delayLess(slack, 0.0, this)) { debugPrint2(debug_, "tns", 3, "tns+ %s %s\n", delayAsString(slack, this), vertex->name(sdc_network_)); @@ -3754,7 +3755,7 @@ Search::tnsDecr(Vertex *vertex, bool found; tns_slacks_[path_ap_index].findKey(vertex, slack, found); if (found - && delayLess(slack, 0.0)) { + && delayLess(slack, 0.0, this)) { debugPrint2(debug_, "tns", 3, "tns- %s %s\n", delayAsString(slack, this), vertex->name(sdc_network_)); @@ -3880,7 +3881,7 @@ FindEndSlackVisitor::visit(PathEnd *path_end) PathRef &path = path_end->pathRef(); PathAPIndex path_ap_index = path.pathAnalysisPtIndex(sta_); Slack slack = path_end->slack(sta_); - if (delayLess(slack, slacks_[path_ap_index])) + if (delayLess(slack, slacks_[path_ap_index], sta_)) slacks_[path_ap_index] = slack; } } @@ -3907,7 +3908,7 @@ Search::wnsSlacks(Vertex *vertex, 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])) + && delayLess(path_slack, slacks[path_ap_index], this)) slacks[path_ap_index] = path_slack; } } diff --git a/search/Sta.cc b/search/Sta.cc index e2680e73..cbd8f649 100644 --- a/search/Sta.cc +++ b/search/Sta.cc @@ -2669,7 +2669,7 @@ Sta::vertexWorstArrivalPath(Vertex *vertex, PathVertex *path = path_iter.next(); Arrival arrival = path->arrival(this); if (!path->tag(this)->isGenClkSrcPath() - && delayGreater(arrival, worst_arrival, min_max)) { + && delayGreater(arrival, worst_arrival, min_max, this)) { worst_arrival = arrival; worst_path.init(path); } @@ -2689,7 +2689,7 @@ Sta::vertexWorstArrivalPath(Vertex *vertex, Arrival arrival = path->arrival(this); if (path->minMax(this) == min_max && !path->tag(this)->isGenClkSrcPath() - && delayGreater(arrival, worst_arrival, min_max)) { + && delayGreater(arrival, worst_arrival, min_max, this)) { worst_arrival = arrival; worst_path.init(path); } @@ -2709,7 +2709,7 @@ Sta::vertexWorstSlackPath(Vertex *vertex, PathVertex *path = path_iter.next(); Slack slack = path->slack(this); if (!path->tag(this)->isGenClkSrcPath() - && delayLess(slack, min_slack)) { + && delayLess(slack, min_slack, this)) { min_slack = slack; worst_path.init(path); } @@ -2730,7 +2730,7 @@ Sta::vertexWorstSlackPath(Vertex *vertex, if (path->minMax(this) == min_max && !path->tag(this)->isGenClkSrcPath()) { Slack slack = path->slack(this); - if (delayLess(slack, min_slack)) { + if (delayLess(slack, min_slack, this)) { min_slack = slack; worst_path.init(path); } @@ -2764,7 +2764,7 @@ Sta::vertexArrival(Vertex *vertex, if ((clk_edge == clk_edge_wildcard || clk_info->clkEdge() == clk_edge) && !clk_info->isGenClkSrcPath() - && delayGreater(path->arrival(this), arrival, min_max)) + && delayGreater(path->arrival(this), arrival, min_max, this)) arrival = path_arrival; } return arrival; @@ -2782,7 +2782,7 @@ Sta::vertexRequired(Vertex *vertex, const Path *path = path_iter.next(); if (path->minMax(this) == min_max) { const Required path_required = path->required(this); - if (delayGreater(path_required, required, req_min_max)) + if (delayGreater(path_required, required, req_min_max, this)) required = path_required; } } @@ -2812,7 +2812,7 @@ Sta::vertexRequired(Vertex *vertex, const Required path_required = path->required(this); if ((clk_edge == clk_edge_wildcard || path->clkEdge(search_) == clk_edge) - && delayGreater(path_required, required, min_max)) + && delayGreater(path_required, required, min_max, this)) required = path_required; } return required; @@ -2830,7 +2830,7 @@ Sta::netSlack(const Net *net, if (network_->isLoad(pin)) { Vertex *vertex = graph_->pinLoadVertex(pin); Slack pin_slack = vertexSlack(vertex, min_max); - if (delayLess(pin_slack, slack)) + if (delayLess(pin_slack, slack, this)) slack = pin_slack; } } @@ -2849,7 +2849,7 @@ Sta::pinSlack(const Pin *pin, slack = vertexSlack(vertex, min_max); if (bidirect_drvr_vertex) { Slack slack1 = vertexSlack(bidirect_drvr_vertex, min_max); - if (delayLess(slack1, slack)) + if (delayLess(slack1, slack, this)) slack = slack1; } return slack; @@ -2868,7 +2868,7 @@ Sta::pinSlack(const Pin *pin, slack = vertexSlack(vertex, rf, min_max); if (bidirect_drvr_vertex) { Slack slack1 = vertexSlack(bidirect_drvr_vertex, rf, min_max); - if (delayLess(slack1, slack)) + if (delayLess(slack1, slack, this)) slack = slack1; } return slack; @@ -2886,7 +2886,7 @@ Sta::vertexSlack(Vertex *vertex, Path *path = path_iter.next(); if (path->minMax(this) == min_max) { Slack path_slack = path->slack(this); - if (delayLess(path_slack, slack)) + if (delayLess(path_slack, slack, this)) slack = path_slack; } } @@ -2904,7 +2904,7 @@ Sta::vertexSlack(Vertex *vertex, while (path_iter.hasNext()) { Path *path = path_iter.next(); Slack path_slack = path->slack(this); - if (delayLess(path_slack, slack)) + if (delayLess(path_slack, slack, this)) slack = path_slack; } return slack; @@ -2943,7 +2943,7 @@ Sta::vertexSlack1(Vertex *vertex, Slack path_slack = path->slack(this); if ((clk_edge == clk_edge_wildcard || path->clkEdge(search_) == clk_edge) - && delayLess(path_slack, slack)) + && delayLess(path_slack, slack, this)) slack = path_slack; } return slack; @@ -2965,7 +2965,7 @@ Sta::vertexSlacks(Vertex *vertex, Slack path_slack = path->slack(this); int rf_index = path->rfIndex(this); int mm_index = path->minMax(this)->index(); - if (delayLess(path_slack, slacks[rf_index][mm_index])) + if (delayLess(path_slack, slacks[rf_index][mm_index], this)) slacks[rf_index][mm_index] = path_slack; } } @@ -3160,7 +3160,7 @@ Sta::vertexSlew(Vertex *vertex, Slew mm_slew = min_max->initValue(); for (DcalcAnalysisPt *dcalc_ap : corners_->dcalcAnalysisPts()) { Slew slew = graph_->slew(vertex, rf, dcalc_ap->index()); - if (delayGreater(slew, mm_slew, min_max)) + if (delayGreater(slew, mm_slew, min_max, this)) mm_slew = slew; } return mm_slew; @@ -4782,7 +4782,7 @@ bool InstanceMaxSlewGreater::operator()(const Instance *inst1, const Instance *inst2) const { - return delayGreater(instMaxSlew(inst1), instMaxSlew(inst2)); + return delayGreater(instMaxSlew(inst1), instMaxSlew(inst2), sta_); } Slew @@ -4799,7 +4799,7 @@ InstanceMaxSlewGreater::instMaxSlew(const Instance *inst) const for (RiseFall *rf : RiseFall::range()) { for (DcalcAnalysisPt *dcalc_ap : sta_->corners()->dcalcAnalysisPts()) { Slew slew = graph->slew(vertex, rf, dcalc_ap->index()); - if (delayGreater(slew, max_slew)) + if (delayGreater(slew, max_slew, sta_)) max_slew = slew; } } diff --git a/search/WorstSlack.cc b/search/WorstSlack.cc index 704a6a14..f9c0893a 100644 --- a/search/WorstSlack.cc +++ b/search/WorstSlack.cc @@ -48,7 +48,7 @@ WorstSlacks::worstSlack(const MinMax *min_max, Vertex *worst_vertex1; worst_slacks_[path_ap_index].worstSlack(path_ap_index, sta_, worst_slack1, worst_vertex1); - if (delayLess(worst_slack1, worst_slack)) { + if (delayLess(worst_slack1, worst_slack, sta_)) { worst_slack = worst_slack1; worst_vertex = worst_vertex1; } @@ -160,9 +160,9 @@ WorstSlack::initQueue(PathAPIndex path_ap_index, Vertex *vertex = end_iter.next(); Slack slack = search->wnsSlack(vertex, path_ap_index); if (!delayEqual(slack, slack_init_)) { - if (delayLess(slack, worst_slack_)) + if (delayLess(slack, worst_slack_, sta)) setWorstSlack(vertex, slack, sta); - if (delayLessEqual(slack, slack_threshold_)) + if (delayLessEqual(slack, slack_threshold_, sta)) queue_.insert(vertex); int queue_size = queue_.size(); if (queue_size >= max_queue_size_) @@ -206,7 +206,7 @@ WorstSlack::sortQueue(PathAPIndex path_ap_index, while (queue_iter2.hasNext()) { Vertex *vertex = queue_iter2.next(); Slack slack = search->wnsSlack(vertex, path_ap_index); - if (delayGreater(slack, slack_threshold_)) + if (delayGreater(slack, slack_threshold_, sta)) break; queue_.insert(vertex); } @@ -231,7 +231,7 @@ WorstSlack::findWorstInQueue(PathAPIndex path_ap_index, while (queue_iter.hasNext()) { Vertex *vertex = queue_iter.next(); Slack slack = search->wnsSlack(vertex, path_ap_index); - if (delayLess(slack, worst_slack_)) + if (delayLess(slack, worst_slack_, sta)) setWorstSlack(vertex, slack, sta); } } @@ -249,7 +249,7 @@ WorstSlack::checkQueue(PathAPIndex path_ap_index, while (end_iter.hasNext()) { Vertex *end = end_iter.next(); if (delayLessEqual(search->wnsSlack(end, path_ap_index), - slack_threshold_)) + slack_threshold_, sta)) ends.push_back(end); } WnsSlackLess slack_less(path_ap_index, sta); @@ -262,7 +262,7 @@ WorstSlack::checkQueue(PathAPIndex path_ap_index, end_set.insert(end); if (!queue_.hasKey(end) && delayLessEqual(search->wnsSlack(end, path_ap_index), - slack_threshold_)) + slack_threshold_, sta)) report->print("WorstSlack queue missing %s %s < %s\n", end->name(network), delayAsString(search->wnsSlack(end, path_ap_index), sta), @@ -294,14 +294,14 @@ WorstSlack::updateWorstSlack(Vertex *vertex, // threads. UniqueLock lock(lock_); if (worst_vertex_ - && delayLess(slack, worst_slack_)) + && delayLess(slack, worst_slack_, sta)) setWorstSlack(vertex, slack, sta); else if (vertex == worst_vertex_) // Mark worst slack as unknown (updated by findWorstSlack(). worst_vertex_ = nullptr; if (!delayEqual(slack, slack_init_) - && delayLessEqual(slack, slack_threshold_)) { + && delayLessEqual(slack, slack_threshold_, sta)) { debugPrint2(debug, "wns", 3, "insert %s %s\n", vertex->name(network), delayAsString(slack, sta)); @@ -342,7 +342,8 @@ WnsSlackLess::operator()(Vertex *vertex1, Vertex *vertex2) { return delayLess(search_->wnsSlack(vertex1, path_ap_index_), - search_->wnsSlack(vertex2, path_ap_index_)); + search_->wnsSlack(vertex2, path_ap_index_), + search_); } } // namespace From a49dd870dfc3e842a32a0f95feec6891204056e6 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Sat, 11 Jul 2020 23:56:39 -0700 Subject: [PATCH 33/51] refactor sdc graph annotation --- CMakeLists.txt | 1 + include/sta/Sta.hh | 3 ++ sdc/Sdc.cc | 84 +----------------------------------------- sdc/SdcGraph.cc | 92 +++++++++++++++++++++++++++++++++++++++++++++- search/Sta.cc | 22 ++++++++++- 5 files changed, 115 insertions(+), 87 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 07ccac05..bf2fe384 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -141,6 +141,7 @@ set(STA_SOURCE sdc/RiseFallMinMax.cc sdc/RiseFallValues.cc sdc/Sdc.cc + sdc/SdcGraph.cc sdc/SdcCmdComment.cc sdc/WriteSdc.cc diff --git a/include/sta/Sta.hh b/include/sta/Sta.hh index e4e911a1..b7e78efe 100644 --- a/include/sta/Sta.hh +++ b/include/sta/Sta.hh @@ -1367,6 +1367,8 @@ protected: void replaceCell(Instance *inst, Cell *to_cell, LibertyCell *to_lib_cell); + void sdcChangedGraph(); + void ensureGraphSdcAnnotated(); CmdNamespace cmd_namespace_; Instance *current_instance_; @@ -1385,6 +1387,7 @@ protected: bool link_make_black_boxes_; bool update_genclks_; EquivCells *equiv_cells_; + bool graph_sdc_annotated_; // Singleton sta used by tcl command interpreter. static Sta *sta_; diff --git a/sdc/Sdc.cc b/sdc/Sdc.cc index eb68f39f..50c25ca8 100644 --- a/sdc/Sdc.cc +++ b/sdc/Sdc.cc @@ -31,7 +31,6 @@ #include "Transition.hh" #include "PortDirection.hh" #include "Network.hh" -#include "HpinDrvrLoad.hh" #include "RiseFallMinMax.hh" #include "Clock.hh" #include "ClockLatency.hh" @@ -48,6 +47,7 @@ #include "DeratingFactors.hh" #include "search/Levelize.hh" #include "Corner.hh" +#include "Graph.hh" namespace sta { @@ -1301,88 +1301,6 @@ ClkHpinDisableLess::operator()(const ClkHpinDisable *disable1, return clk_index1 < clk_index2; } -class FindClkHpinDisables : public HpinDrvrLoadVisitor -{ -public: - FindClkHpinDisables(Clock *clk, - const Network *network, - Sdc *sdc); - ~FindClkHpinDisables(); - bool drvrLoadExists(Pin *drvr, - Pin *load); - -protected: - virtual void visit(HpinDrvrLoad *drvr_load); - void makeClkHpinDisables(Pin *clk_src, - Pin *drvr, - Pin *load); - - Clock *clk_; - PinPairSet drvr_loads_; - const Network *network_; - Sdc *sdc_; - -private: - DISALLOW_COPY_AND_ASSIGN(FindClkHpinDisables); -}; - -FindClkHpinDisables::FindClkHpinDisables(Clock *clk, - const Network *network, - Sdc *sdc) : - HpinDrvrLoadVisitor(), - clk_(clk), - network_(network), - sdc_(sdc) -{ -} - -FindClkHpinDisables::~FindClkHpinDisables() -{ - drvr_loads_.deleteContents(); -} - -void -FindClkHpinDisables::visit(HpinDrvrLoad *drvr_load) -{ - Pin *drvr = drvr_load->drvr(); - Pin *load = drvr_load->load(); - - makeClkHpinDisables(drvr, drvr, load); - - PinSet *hpins_from_drvr = drvr_load->hpinsFromDrvr(); - PinSet::Iterator hpin_iter(hpins_from_drvr); - while (hpin_iter.hasNext()) { - Pin *hpin = hpin_iter.next(); - makeClkHpinDisables(hpin, drvr, load); - } - drvr_loads_.insert(new PinPair(drvr, load)); -} - -void -FindClkHpinDisables::makeClkHpinDisables(Pin *clk_src, - Pin *drvr, - Pin *load) -{ - ClockSet *clks = sdc_->findClocks(clk_src); - ClockSet::Iterator clk_iter(clks); - while (clk_iter.hasNext()) { - Clock *clk = clk_iter.next(); - if (clk != clk_) - // Do not propagate clock from source pin if another - // clock is defined on a hierarchical pin between the - // driver and load. - sdc_->makeClkHpinDisable(clk, drvr, load); - } -} - -bool -FindClkHpinDisables::drvrLoadExists(Pin *drvr, - Pin *load) -{ - PinPair probe(drvr, load); - return drvr_loads_.hasKey(&probe); -} - void Sdc::makeClkHpinDisable(Clock *clk, Pin *drvr, diff --git a/sdc/SdcGraph.cc b/sdc/SdcGraph.cc index 2237f6e7..b2a15fb8 100644 --- a/sdc/SdcGraph.cc +++ b/sdc/SdcGraph.cc @@ -14,9 +14,15 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#include "SdcGraph.hh" - +#include "Stats.hh" +#include "PortDirection.hh" +#include "Network.hh" #include "Graph.hh" +#include "DisabledPorts.hh" +#include "PortDelay.hh" +#include "ClockLatency.hh" +#include "HpinDrvrLoad.hh" +#include "Sdc.hh" namespace sta { @@ -388,6 +394,88 @@ Sdc::clockLatency(Edge *edge, } } +class FindClkHpinDisables : public HpinDrvrLoadVisitor +{ +public: + FindClkHpinDisables(Clock *clk, + const Network *network, + Sdc *sdc); + ~FindClkHpinDisables(); + bool drvrLoadExists(Pin *drvr, + Pin *load); + +protected: + virtual void visit(HpinDrvrLoad *drvr_load); + void makeClkHpinDisables(Pin *clk_src, + Pin *drvr, + Pin *load); + + Clock *clk_; + PinPairSet drvr_loads_; + const Network *network_; + Sdc *sdc_; + +private: + DISALLOW_COPY_AND_ASSIGN(FindClkHpinDisables); +}; + +FindClkHpinDisables::FindClkHpinDisables(Clock *clk, + const Network *network, + Sdc *sdc) : + HpinDrvrLoadVisitor(), + clk_(clk), + network_(network), + sdc_(sdc) +{ +} + +FindClkHpinDisables::~FindClkHpinDisables() +{ + drvr_loads_.deleteContents(); +} + +void +FindClkHpinDisables::visit(HpinDrvrLoad *drvr_load) +{ + Pin *drvr = drvr_load->drvr(); + Pin *load = drvr_load->load(); + + makeClkHpinDisables(drvr, drvr, load); + + PinSet *hpins_from_drvr = drvr_load->hpinsFromDrvr(); + PinSet::Iterator hpin_iter(hpins_from_drvr); + while (hpin_iter.hasNext()) { + Pin *hpin = hpin_iter.next(); + makeClkHpinDisables(hpin, drvr, load); + } + drvr_loads_.insert(new PinPair(drvr, load)); +} + +void +FindClkHpinDisables::makeClkHpinDisables(Pin *clk_src, + Pin *drvr, + Pin *load) +{ + ClockSet *clks = sdc_->findClocks(clk_src); + ClockSet::Iterator clk_iter(clks); + while (clk_iter.hasNext()) { + Clock *clk = clk_iter.next(); + if (clk != clk_) + // Do not propagate clock from source pin if another + // clock is defined on a hierarchical pin between the + // driver and load. + sdc_->makeClkHpinDisable(clk, drvr, load); + } +} + +bool +FindClkHpinDisables::drvrLoadExists(Pin *drvr, + Pin *load) +{ + PinPair probe(drvr, load); + return drvr_loads_.hasKey(&probe); +} + void Sdc::ensureClkHpinDisables() { diff --git a/search/Sta.cc b/search/Sta.cc index f5984228..52c84af5 100644 --- a/search/Sta.cc +++ b/search/Sta.cc @@ -268,7 +268,8 @@ Sta::Sta() : power_(nullptr), link_make_black_boxes_(true), update_genclks_(false), - equiv_cells_(nullptr) + equiv_cells_(nullptr), + graph_sdc_annotated_(false) { } @@ -535,6 +536,7 @@ Sta::clear() // Constraints reference search filter, so clear search first. search_->clear(); sdc_->clear(); + graph_sdc_annotated_ = false; // corners are NOT cleared because they are used to index liberty files. levelize_->clear(); if (parasitics_) @@ -1202,6 +1204,19 @@ Sta::setClockLatency(Clock *clk, void Sta::sdcChangedGraph() { + if (graph_sdc_annotated_) + // Remove graph constraint annotations. + sdc_->annotateGraph(false); + graph_sdc_annotated_ = false; +} + +void +Sta::ensureGraphSdcAnnotated() +{ + if (!graph_sdc_annotated_) { + sdc_->annotateGraph(true); + graph_sdc_annotated_ = true; + } } void @@ -1421,6 +1436,7 @@ Sta::removeDataCheck(Pin *from, void Sta::disable(Pin *pin) { + sdcChangedGraph(); sdc_->disable(pin); // Levelization respects disabled edges. levelize_->invalid(); @@ -1431,6 +1447,7 @@ Sta::disable(Pin *pin) void Sta::removeDisable(Pin *pin) { + sdcChangedGraph(); sdc_->removeDisable(pin); disableAfter(); // Levelization respects disabled edges. @@ -3189,7 +3206,6 @@ Sta::ensureGraph() makeGraph(); // Update pointers to graph. updateComponentsState(); - sdc_->annotateGraph(true); } return graph_; } @@ -3205,6 +3221,7 @@ void Sta::ensureLevelized() { ensureGraph(); + ensureGraphSdcAnnotated(); // Need constant propagation before levelization to know edges that // are disabled by constants. sim_->ensureConstantsPropagated(); @@ -4403,6 +4420,7 @@ void Sta::findRegisterPreamble() { ensureGraph(); + ensureGraphSdcAnnotated(); sim_->ensureConstantsPropagated(); } From 7e71edecf27902f3ead66cea5609877babc70cf0 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Sun, 12 Jul 2020 08:55:44 -0700 Subject: [PATCH 34/51] separate sdc annotate/remove functions --- include/sta/Sdc.hh | 40 +++---- sdc/Sdc.cc | 126 ++++++++++++++++++++++ sdc/SdcGraph.cc | 256 +++++++++++---------------------------------- search/Sta.cc | 8 +- 4 files changed, 204 insertions(+), 226 deletions(-) diff --git a/include/sta/Sdc.hh b/include/sta/Sdc.hh index 560fddbe..e4692993 100644 --- a/include/sta/Sdc.hh +++ b/include/sta/Sdc.hh @@ -990,11 +990,8 @@ public: void deleteException(ExceptionPath *exception); void recordException(ExceptionPath *exception); void unrecordException(ExceptionPath *exception); - // Annotate graph from constraints. If the graph exists when the - // constraints are defined it is annotated incrementally. This is - // called after building the graph to annotate any constraints that - // were defined before the graph is built. - void annotateGraph(bool annotate); + void annotateGraph(); + void removeGraphAnnotations(); // Network edit before/after methods. void disconnectPinBefore(Pin *pin); @@ -1180,34 +1177,25 @@ protected: // Liberty library to look for defaults. LibertyLibrary *defaultLibertyLibrary(); void annotateGraphConstrainOutputs(); - void annotateDisables(bool annotate); - void annotateGraphDisabled(const Pin *pin, - bool annotate); - void setEdgeDisabledInstPorts(DisabledInstancePorts *disabled_inst, - bool annotate); + void annotateDisables(); + void annotateGraphDisabled(const Pin *pin); + void setEdgeDisabledInstPorts(DisabledInstancePorts *disabled_inst); void setEdgeDisabledInstFrom(Pin *from_pin, - bool disable_checks, - bool annotate); + bool disable_checks); void setEdgeDisabledInstPorts(DisabledPorts *disabled_port, - Instance *inst, - bool annotate); + Instance *inst); void deleteClockLatenciesReferencing(Clock *clk); void deleteClockLatency(ClockLatency *latency); void deleteDeratingFactors(); - void annotateGraphOutputDelays(bool annotate); - void annotateGraphDataChecks(bool annotate); - void annotateGraphConstrained(const PinSet *pins, - bool annotate); - void annotateGraphConstrained(const InstanceSet *insts, - bool annotate); - void annotateGraphConstrained(const Instance *inst, - bool annotate); - void annotateGraphConstrained(const Pin *pin, - bool annotate); - void annotateHierClkLatency(bool annotate); + void annotateGraphOutputDelays(); + void annotateGraphDataChecks(); + void annotateGraphConstrained(const PinSet *pins); + void annotateGraphConstrained(const InstanceSet *insts); + void annotateGraphConstrained(const Instance *inst); + void annotateGraphConstrained(const Pin *pin); + void annotateHierClkLatency(); void annotateHierClkLatency(const Pin *hpin, ClockLatency *latency); - void deannotateHierClkLatency(const Pin *hpin); void initInstancePvtMaps(); void pinCaps(const Pin *pin, const RiseFall *rf, diff --git a/sdc/Sdc.cc b/sdc/Sdc.cc index 50c25ca8..ab0656e1 100644 --- a/sdc/Sdc.cc +++ b/sdc/Sdc.cc @@ -45,6 +45,7 @@ #include "ClockGatingCheck.hh" #include "ClockGroups.hh" #include "DeratingFactors.hh" +#include "HpinDrvrLoad.hh" #include "search/Levelize.hh" #include "Corner.hh" #include "Graph.hh" @@ -1301,6 +1302,131 @@ ClkHpinDisableLess::operator()(const ClkHpinDisable *disable1, return clk_index1 < clk_index2; } +class FindClkHpinDisables : public HpinDrvrLoadVisitor +{ +public: + FindClkHpinDisables(Clock *clk, + const Network *network, + Sdc *sdc); + ~FindClkHpinDisables(); + bool drvrLoadExists(Pin *drvr, + Pin *load); + +protected: + virtual void visit(HpinDrvrLoad *drvr_load); + void makeClkHpinDisables(Pin *clk_src, + Pin *drvr, + Pin *load); + + Clock *clk_; + PinPairSet drvr_loads_; + const Network *network_; + Sdc *sdc_; + +private: + DISALLOW_COPY_AND_ASSIGN(FindClkHpinDisables); +}; + +FindClkHpinDisables::FindClkHpinDisables(Clock *clk, + const Network *network, + Sdc *sdc) : + HpinDrvrLoadVisitor(), + clk_(clk), + network_(network), + sdc_(sdc) +{ +} + +FindClkHpinDisables::~FindClkHpinDisables() +{ + drvr_loads_.deleteContents(); +} + +void +FindClkHpinDisables::visit(HpinDrvrLoad *drvr_load) +{ + Pin *drvr = drvr_load->drvr(); + Pin *load = drvr_load->load(); + + makeClkHpinDisables(drvr, drvr, load); + + PinSet *hpins_from_drvr = drvr_load->hpinsFromDrvr(); + PinSet::Iterator hpin_iter(hpins_from_drvr); + while (hpin_iter.hasNext()) { + Pin *hpin = hpin_iter.next(); + makeClkHpinDisables(hpin, drvr, load); + } + drvr_loads_.insert(new PinPair(drvr, load)); +} + +void +FindClkHpinDisables::makeClkHpinDisables(Pin *clk_src, + Pin *drvr, + Pin *load) +{ + ClockSet *clks = sdc_->findClocks(clk_src); + ClockSet::Iterator clk_iter(clks); + while (clk_iter.hasNext()) { + Clock *clk = clk_iter.next(); + if (clk != clk_) + // Do not propagate clock from source pin if another + // clock is defined on a hierarchical pin between the + // driver and load. + sdc_->makeClkHpinDisable(clk, drvr, load); + } +} + +bool +FindClkHpinDisables::drvrLoadExists(Pin *drvr, + Pin *load) +{ + PinPair probe(drvr, load); + return drvr_loads_.hasKey(&probe); +} + +void +Sdc::ensureClkHpinDisables() +{ + if (!clk_hpin_disables_valid_) { + clk_hpin_disables_.deleteContentsClear(); + for (auto clk : clocks_) { + for (Pin *src : clk->pins()) { + if (network_->isHierarchical(src)) { + FindClkHpinDisables visitor(clk, network_, this); + visitHpinDrvrLoads(src, network_, &visitor); + // Disable fanouts from the src driver pins that do + // not go thru the hierarchical src pin. + for (Pin *lpin : clk->leafPins()) { + Vertex *vertex, *bidirect_drvr_vertex; + graph_->pinVertices(lpin, vertex, bidirect_drvr_vertex); + makeVertexClkHpinDisables(clk, vertex, visitor); + if (bidirect_drvr_vertex) + makeVertexClkHpinDisables(clk, bidirect_drvr_vertex, visitor); + } + } + } + } + clk_hpin_disables_valid_ = true; + } +} + +void +Sdc::makeVertexClkHpinDisables(Clock *clk, + Vertex *vertex, + FindClkHpinDisables &visitor) +{ + VertexOutEdgeIterator edge_iter(vertex, graph_); + while (edge_iter.hasNext()) { + Edge *edge = edge_iter.next(); + if (edge->isWire()) { + Pin *drvr = edge->from(graph_)->pin(); + Pin *load = edge->to(graph_)->pin(); + if (!visitor.drvrLoadExists(drvr, load)) + makeClkHpinDisable(clk, drvr, load); + } + } +} + void Sdc::makeClkHpinDisable(Clock *clk, Pin *drvr, diff --git a/sdc/SdcGraph.cc b/sdc/SdcGraph.cc index b2a15fb8..5fca8d79 100644 --- a/sdc/SdcGraph.cc +++ b/sdc/SdcGraph.cc @@ -21,7 +21,6 @@ #include "DisabledPorts.hh" #include "PortDelay.hh" #include "ClockLatency.hh" -#include "HpinDrvrLoad.hh" #include "Sdc.hh" namespace sta { @@ -29,22 +28,21 @@ namespace sta { static void annotateGraphDisabledWireEdge(Pin *from_pin, Pin *to_pin, - bool annotate, Graph *graph); // Annotate constraints to the timing graph. void -Sdc::annotateGraph(bool annotate) +Sdc::annotateGraph() { Stats stats(debug_); // All output pins are considered constrained because // they may be downstream from a set_min/max_delay -from that // does not have a set_output_delay. annotateGraphConstrainOutputs(); - annotateDisables(annotate); - annotateGraphOutputDelays(annotate); - annotateGraphDataChecks(annotate); - annotateHierClkLatency(annotate); + annotateDisables(); + annotateGraphOutputDelays(); + annotateGraphDataChecks(); + annotateHierClkLatency(); stats.report("Annotate constraints to graph"); } @@ -56,18 +54,18 @@ Sdc::annotateGraphConstrainOutputs() while (pin_iter->hasNext()) { Pin *pin = pin_iter->next(); if (network_->direction(pin)->isAnyOutput()) - annotateGraphConstrained(pin, true); + annotateGraphConstrained(pin); } delete pin_iter; } void -Sdc::annotateDisables(bool annotate) +Sdc::annotateDisables() { PinSet::Iterator pin_iter(disabled_pins_); while (pin_iter.hasNext()) { Pin *pin = pin_iter.next(); - annotateGraphDisabled(pin, annotate); + annotateGraphDisabled(pin); } if (!disabled_lib_ports_.empty()) { @@ -77,7 +75,7 @@ Sdc::annotateDisables(bool annotate) Pin *pin = vertex->pin(); LibertyPort *port = network_->libertyPort(pin); if (disabled_lib_ports_.hasKey(port)) - annotateGraphDisabled(pin, annotate); + annotateGraphDisabled(pin); } } @@ -86,32 +84,32 @@ Sdc::annotateDisables(bool annotate) while (port_iter.hasNext()) { Port *port = port_iter.next(); Pin *pin = network_->findPin(top_inst, port); - annotateGraphDisabled(pin, annotate); + annotateGraphDisabled(pin); } PinPairSet::Iterator pair_iter(disabled_wire_edges_); while (pair_iter.hasNext()) { PinPair *pair = pair_iter.next(); - annotateGraphDisabledWireEdge(pair->first, pair->second, annotate, graph_); + annotateGraphDisabledWireEdge(pair->first, pair->second, graph_); } EdgeSet::Iterator edge_iter(disabled_edges_); while (edge_iter.hasNext()) { Edge *edge = edge_iter.next(); - edge->setIsDisabledConstraint(annotate); + edge->setIsDisabledConstraint(true); } DisabledInstancePortsMap::Iterator disable_inst_iter(disabled_inst_ports_); while (disable_inst_iter.hasNext()) { DisabledInstancePorts *disabled_inst = disable_inst_iter.next(); - setEdgeDisabledInstPorts(disabled_inst, annotate); + setEdgeDisabledInstPorts(disabled_inst); } } class DisableHpinEdgeVisitor : public HierPinThruVisitor { public: - DisableHpinEdgeVisitor(bool annotate, Graph *graph); + DisableHpinEdgeVisitor(Graph *graph); virtual void visit(Pin *from_pin, Pin *to_pin); @@ -123,10 +121,8 @@ class DisableHpinEdgeVisitor : public HierPinThruVisitor DISALLOW_COPY_AND_ASSIGN(DisableHpinEdgeVisitor); }; -DisableHpinEdgeVisitor::DisableHpinEdgeVisitor(bool annotate, - Graph *graph) : +DisableHpinEdgeVisitor::DisableHpinEdgeVisitor(Graph *graph) : HierPinThruVisitor(), - annotate_(annotate), graph_(graph) { } @@ -135,13 +131,12 @@ void DisableHpinEdgeVisitor::visit(Pin *from_pin, Pin *to_pin) { - annotateGraphDisabledWireEdge(from_pin, to_pin, annotate_, graph_); + annotateGraphDisabledWireEdge(from_pin, to_pin, graph_); } static void annotateGraphDisabledWireEdge(Pin *from_pin, Pin *to_pin, - bool annotate, Graph *graph) { Vertex *from_vertex = graph->pinDrvrVertex(from_pin); @@ -152,40 +147,37 @@ annotateGraphDisabledWireEdge(Pin *from_pin, Edge *edge = edge_iter.next(); if (edge->isWire() && edge->to(graph) == to_vertex) - edge->setIsDisabledConstraint(annotate); + edge->setIsDisabledConstraint(true); } } } void -Sdc::annotateGraphDisabled(const Pin *pin, - bool annotate) +Sdc::annotateGraphDisabled(const Pin *pin) { Vertex *vertex, *bidirect_drvr_vertex; graph_->pinVertices(pin, vertex, bidirect_drvr_vertex); - vertex->setIsDisabledConstraint(annotate); + vertex->setIsDisabledConstraint(true); if (bidirect_drvr_vertex) - bidirect_drvr_vertex->setIsDisabledConstraint(annotate); + bidirect_drvr_vertex->setIsDisabledConstraint(true); } void -Sdc::setEdgeDisabledInstPorts(DisabledInstancePorts *disabled_inst, - bool annotate) +Sdc::setEdgeDisabledInstPorts(DisabledInstancePorts *disabled_inst) { - setEdgeDisabledInstPorts(disabled_inst, disabled_inst->instance(), annotate); + setEdgeDisabledInstPorts(disabled_inst, disabled_inst->instance()); } void Sdc::setEdgeDisabledInstPorts(DisabledPorts *disabled_port, - Instance *inst, - bool annotate) + Instance *inst) { if (disabled_port->all()) { InstancePinIterator *pin_iter = network_->pinIterator(inst); while (pin_iter->hasNext()) { Pin *pin = pin_iter->next(); // set_disable_timing instance does not disable timing checks. - setEdgeDisabledInstFrom(pin, false, annotate); + setEdgeDisabledInstFrom(pin, false); } delete pin_iter; } @@ -196,7 +188,7 @@ Sdc::setEdgeDisabledInstPorts(DisabledPorts *disabled_port, LibertyPort *from_port = from_iter.next(); Pin *from_pin = network_->findPin(inst, from_port); if (from_pin) - setEdgeDisabledInstFrom(from_pin, true, annotate); + setEdgeDisabledInstFrom(from_pin, true); } // Disable to pins. @@ -211,7 +203,7 @@ Sdc::setEdgeDisabledInstPorts(DisabledPorts *disabled_port, VertexInEdgeIterator edge_iter(vertex, graph_); while (edge_iter.hasNext()) { Edge *edge = edge_iter.next(); - edge->setIsDisabledConstraint(annotate); + edge->setIsDisabledConstraint(true); } } } @@ -235,7 +227,7 @@ Sdc::setEdgeDisabledInstPorts(DisabledPorts *disabled_port, while (edge_iter.hasNext()) { Edge *edge = edge_iter.next(); if (edge->to(graph_) == to_vertex) - edge->setIsDisabledConstraint(annotate); + edge->setIsDisabledConstraint(true); } } } @@ -244,8 +236,7 @@ Sdc::setEdgeDisabledInstPorts(DisabledPorts *disabled_port, void Sdc::setEdgeDisabledInstFrom(Pin *from_pin, - bool disable_checks, - bool annotate) + bool disable_checks) { if (network_->direction(from_pin)->isAnyInput()) { Vertex *from_vertex = graph_->pinLoadVertex(from_pin); @@ -255,23 +246,23 @@ Sdc::setEdgeDisabledInstFrom(Pin *from_pin, Edge *edge = edge_iter.next(); if (disable_checks || !edge->role()->isTimingCheck()) - edge->setIsDisabledConstraint(annotate); + edge->setIsDisabledConstraint(true); } } } } void -Sdc::annotateGraphOutputDelays(bool annotate) +Sdc::annotateGraphOutputDelays() { for (OutputDelay *output_delay : output_delays_) { for (Pin *lpin : output_delay->leafPins()) - annotateGraphConstrained(lpin, annotate); + annotateGraphConstrained(lpin); } } void -Sdc::annotateGraphDataChecks(bool annotate) +Sdc::annotateGraphDataChecks() { DataChecksMap::Iterator data_checks_iter(data_checks_to_map_); while (data_checks_iter.hasNext()) { @@ -281,73 +272,65 @@ Sdc::annotateGraphDataChecks(bool annotate) // but we only need to mark it as constrained once. if (check_iter.hasNext()) { DataCheck *check = check_iter.next(); - annotateGraphConstrained(check->to(), annotate); + annotateGraphConstrained(check->to()); } } } void -Sdc::annotateGraphConstrained(const PinSet *pins, - bool annotate) +Sdc::annotateGraphConstrained(const PinSet *pins) { PinSet::ConstIterator pin_iter(pins); while (pin_iter.hasNext()) { const Pin *pin = pin_iter.next(); - annotateGraphConstrained(pin, annotate); + annotateGraphConstrained(pin); } } void -Sdc::annotateGraphConstrained(const InstanceSet *insts, - bool annotate) +Sdc::annotateGraphConstrained(const InstanceSet *insts) { InstanceSet::ConstIterator inst_iter(insts); while (inst_iter.hasNext()) { const Instance *inst = inst_iter.next(); - annotateGraphConstrained(inst, annotate); + annotateGraphConstrained(inst); } } void -Sdc::annotateGraphConstrained(const Instance *inst, - bool annotate) +Sdc::annotateGraphConstrained(const Instance *inst) { InstancePinIterator *pin_iter = network_->pinIterator(inst); while (pin_iter->hasNext()) { Pin *pin = pin_iter->next(); if (network_->direction(pin)->isAnyInput()) - annotateGraphConstrained(pin, annotate); + annotateGraphConstrained(pin); } delete pin_iter; } void -Sdc::annotateGraphConstrained(const Pin *pin, - bool annotate) +Sdc::annotateGraphConstrained(const Pin *pin) { Vertex *vertex, *bidirect_drvr_vertex; graph_->pinVertices(pin, vertex, bidirect_drvr_vertex); // Pin may be hierarchical and have no vertex. if (vertex) - vertex->setIsConstrained(annotate); + vertex->setIsConstrained(true); if (bidirect_drvr_vertex) - bidirect_drvr_vertex->setIsConstrained(annotate); + bidirect_drvr_vertex->setIsConstrained(true); } void -Sdc::annotateHierClkLatency(bool annotate) +Sdc::annotateHierClkLatency() { - if (annotate) { - ClockLatencies::Iterator latency_iter(clk_latencies_); - while (latency_iter.hasNext()) { - ClockLatency *latency = latency_iter.next(); - const Pin *pin = latency->pin(); - if (pin && network_->isHierarchical(pin)) - annotateHierClkLatency(pin, latency); - } + ClockLatencies::Iterator latency_iter(clk_latencies_); + while (latency_iter.hasNext()) { + ClockLatency *latency = latency_iter.next(); + const Pin *pin = latency->pin(); + if (pin && network_->isHierarchical(pin)) + annotateHierClkLatency(pin, latency); } - else - edge_clk_latency_.clear(); } void @@ -361,16 +344,6 @@ Sdc::annotateHierClkLatency(const Pin *hpin, } } -void -Sdc::deannotateHierClkLatency(const Pin *hpin) -{ - EdgesThruHierPinIterator edge_iter(hpin, network_, graph_); - while (edge_iter.hasNext()) { - Edge *edge = edge_iter.next(); - edge_clk_latency_.erase(edge); - } -} - ClockLatency * Sdc::clockLatency(Edge *edge) const { @@ -394,133 +367,26 @@ Sdc::clockLatency(Edge *edge, } } -class FindClkHpinDisables : public HpinDrvrLoadVisitor -{ -public: - FindClkHpinDisables(Clock *clk, - const Network *network, - Sdc *sdc); - ~FindClkHpinDisables(); - bool drvrLoadExists(Pin *drvr, - Pin *load); - -protected: - virtual void visit(HpinDrvrLoad *drvr_load); - void makeClkHpinDisables(Pin *clk_src, - Pin *drvr, - Pin *load); - - Clock *clk_; - PinPairSet drvr_loads_; - const Network *network_; - Sdc *sdc_; - -private: - DISALLOW_COPY_AND_ASSIGN(FindClkHpinDisables); -}; - -FindClkHpinDisables::FindClkHpinDisables(Clock *clk, - const Network *network, - Sdc *sdc) : - HpinDrvrLoadVisitor(), - clk_(clk), - network_(network), - sdc_(sdc) -{ -} - -FindClkHpinDisables::~FindClkHpinDisables() -{ - drvr_loads_.deleteContents(); -} - -void -FindClkHpinDisables::visit(HpinDrvrLoad *drvr_load) -{ - Pin *drvr = drvr_load->drvr(); - Pin *load = drvr_load->load(); - - makeClkHpinDisables(drvr, drvr, load); - - PinSet *hpins_from_drvr = drvr_load->hpinsFromDrvr(); - PinSet::Iterator hpin_iter(hpins_from_drvr); - while (hpin_iter.hasNext()) { - Pin *hpin = hpin_iter.next(); - makeClkHpinDisables(hpin, drvr, load); - } - drvr_loads_.insert(new PinPair(drvr, load)); -} - -void -FindClkHpinDisables::makeClkHpinDisables(Pin *clk_src, - Pin *drvr, - Pin *load) -{ - ClockSet *clks = sdc_->findClocks(clk_src); - ClockSet::Iterator clk_iter(clks); - while (clk_iter.hasNext()) { - Clock *clk = clk_iter.next(); - if (clk != clk_) - // Do not propagate clock from source pin if another - // clock is defined on a hierarchical pin between the - // driver and load. - sdc_->makeClkHpinDisable(clk, drvr, load); - } -} - -bool -FindClkHpinDisables::drvrLoadExists(Pin *drvr, - Pin *load) -{ - PinPair probe(drvr, load); - return drvr_loads_.hasKey(&probe); -} +//////////////////////////////////////////////////////////////// void -Sdc::ensureClkHpinDisables() +Sdc::removeGraphAnnotations() { - if (!clk_hpin_disables_valid_) { - clk_hpin_disables_.deleteContentsClear(); - for (auto clk : clocks_) { - for (Pin *src : clk->pins()) { - if (network_->isHierarchical(src)) { - FindClkHpinDisables visitor(clk, network_, this); - visitHpinDrvrLoads(src, network_, &visitor); - // Disable fanouts from the src driver pins that do - // not go thru the hierarchical src pin. - for (Pin *lpin : clk->leafPins()) { - Vertex *vertex, *bidirect_drvr_vertex; - graph_->pinVertices(lpin, vertex, bidirect_drvr_vertex); - makeVertexClkHpinDisables(clk, vertex, visitor); - if (bidirect_drvr_vertex) - makeVertexClkHpinDisables(clk, bidirect_drvr_vertex, visitor); - } - } - } - } - clk_hpin_disables_valid_ = true; - } -} + VertexIterator vertex_iter(graph_); + while (vertex_iter.hasNext()) { + Vertex *vertex = vertex_iter.next(); + vertex->setIsDisabledConstraint(false); + vertex->setIsConstrained(false); -void -Sdc::makeVertexClkHpinDisables(Clock *clk, - Vertex *vertex, - FindClkHpinDisables &visitor) -{ - VertexOutEdgeIterator edge_iter(vertex, graph_); - while (edge_iter.hasNext()) { - Edge *edge = edge_iter.next(); - if (edge->isWire()) { - Pin *drvr = edge->from(graph_)->pin(); - Pin *load = edge->to(graph_)->pin(); - if (!visitor.drvrLoadExists(drvr, load)) - makeClkHpinDisable(clk, drvr, load); + VertexOutEdgeIterator edge_iter(vertex, graph_); + while (edge_iter.hasNext()) { + Edge *edge = edge_iter.next(); + edge->setIsDisabledConstraint(false); } } + edge_clk_latency_.clear(); } -//////////////////////////////////////////////////////////////// - void Sdc::searchPreamble() { diff --git a/search/Sta.cc b/search/Sta.cc index 52c84af5..4b5890fd 100644 --- a/search/Sta.cc +++ b/search/Sta.cc @@ -1205,8 +1205,7 @@ void Sta::sdcChangedGraph() { if (graph_sdc_annotated_) - // Remove graph constraint annotations. - sdc_->annotateGraph(false); + sdc_->removeGraphAnnotations(); graph_sdc_annotated_ = false; } @@ -1214,7 +1213,7 @@ void Sta::ensureGraphSdcAnnotated() { if (!graph_sdc_annotated_) { - sdc_->annotateGraph(true); + sdc_->annotateGraph(); graph_sdc_annotated_ = true; } } @@ -2108,8 +2107,7 @@ Sta::removeConstraints() search_->clear(); sim_->constantsInvalid(); if (graph_) - // Remove graph constraint annotations. - sdc_->annotateGraph(false); + sdc_->removeGraphAnnotations(); sdc_->clear(); } From f60b82fee54973c6162a45c8d0486f16c0d9af7c Mon Sep 17 00:00:00 2001 From: James Cherry Date: Sun, 12 Jul 2020 10:29:06 -0700 Subject: [PATCH 35/51] hpin clk disables without graph --- include/sta/Sdc.hh | 3 --- sdc/Sdc.cc | 37 ++++++++++++------------------------- 2 files changed, 12 insertions(+), 28 deletions(-) diff --git a/include/sta/Sdc.hh b/include/sta/Sdc.hh index e4692993..5bea0c9f 100644 --- a/include/sta/Sdc.hh +++ b/include/sta/Sdc.hh @@ -1245,9 +1245,6 @@ protected: void disconnectPinBefore(Pin *pin, ExceptionPathSet *exceptions); void clockGroupsDeleteClkRefs(Clock *clk); - void makeVertexClkHpinDisables(Clock *clk, - Vertex *vertex, - FindClkHpinDisables &visitor); void clearGroupPathMap(); AnalysisType analysis_type_; diff --git a/sdc/Sdc.cc b/sdc/Sdc.cc index ab0656e1..ea1de829 100644 --- a/sdc/Sdc.cc +++ b/sdc/Sdc.cc @@ -1392,16 +1392,20 @@ Sdc::ensureClkHpinDisables() for (auto clk : clocks_) { for (Pin *src : clk->pins()) { if (network_->isHierarchical(src)) { - FindClkHpinDisables visitor(clk, network_, this); - visitHpinDrvrLoads(src, network_, &visitor); + FindClkHpinDisables visitor1(clk, network_, this); + visitHpinDrvrLoads(src, network_, &visitor1); + PinSeq loads, drvrs; + PinSet visited_drvrs; + FindNetDrvrLoads visitor2(nullptr, visited_drvrs, loads, drvrs, network_); + network_->visitConnectedPins(src, visitor2); + // Disable fanouts from the src driver pins that do // not go thru the hierarchical src pin. - for (Pin *lpin : clk->leafPins()) { - Vertex *vertex, *bidirect_drvr_vertex; - graph_->pinVertices(lpin, vertex, bidirect_drvr_vertex); - makeVertexClkHpinDisables(clk, vertex, visitor); - if (bidirect_drvr_vertex) - makeVertexClkHpinDisables(clk, bidirect_drvr_vertex, visitor); + for (Pin *drvr : drvrs) { + for (Pin *load : loads) { + if (!visitor1.drvrLoadExists(drvr, load)) + makeClkHpinDisable(clk, drvr, load); + } } } } @@ -1410,23 +1414,6 @@ Sdc::ensureClkHpinDisables() } } -void -Sdc::makeVertexClkHpinDisables(Clock *clk, - Vertex *vertex, - FindClkHpinDisables &visitor) -{ - VertexOutEdgeIterator edge_iter(vertex, graph_); - while (edge_iter.hasNext()) { - Edge *edge = edge_iter.next(); - if (edge->isWire()) { - Pin *drvr = edge->from(graph_)->pin(); - Pin *load = edge->to(graph_)->pin(); - if (!visitor.drvrLoadExists(drvr, load)) - makeClkHpinDisable(clk, drvr, load); - } - } -} - void Sdc::makeClkHpinDisable(Clock *clk, Pin *drvr, From f3a50663d8e103038074f5c219f677dfba556b20 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Mon, 13 Jul 2020 07:38:39 -0700 Subject: [PATCH 36/51] set_data_check support mcp, 1/2 cycle path reporting --- include/sta/PathEnd.hh | 4 ++++ search/Latches.cc | 2 +- search/PathEnd.cc | 9 ++++++--- search/ReportPath.cc | 10 ++++++---- search/VisitPathEnds.cc | 4 ++-- 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/include/sta/PathEnd.hh b/include/sta/PathEnd.hh index e5f983d4..82e16883 100644 --- a/include/sta/PathEnd.hh +++ b/include/sta/PathEnd.hh @@ -142,6 +142,7 @@ public: virtual TimingArc *checkArc() const { return nullptr; } // PathEndDataCheck data clock path. virtual const PathVertex *dataClkPath() const { return nullptr; } + virtual int setupDefaultCycles() const { return 1; } static bool less(const PathEnd *path_end1, const PathEnd *path_end2, @@ -181,6 +182,7 @@ public: static float checkSetupMcpAdjustment(const ClockEdge *src_clk_edge, const ClockEdge *tgt_clk_edge, const MultiCyclePath *mcp, + int default_cycles, Sdc *sdc); protected: @@ -545,6 +547,8 @@ protected: Crpr crpr, bool crpr_valid); Arrival requiredTimeNoCrpr(const StaState *sta) const; + // setup uses zero cycle default + virtual int setupDefaultCycles() const { return 0; } private: PathVertex data_clk_path_; diff --git a/search/Latches.cc b/search/Latches.cc index 687ecfec..629ce6fe 100644 --- a/search/Latches.cc +++ b/search/Latches.cc @@ -92,7 +92,7 @@ Latches::latchRequired(const Path *data_path, + open_latency + open_uncertainty + PathEnd::checkSetupMcpAdjustment(data_clk_edge, enable_clk_edge, mcp, - sdc_) + 1, sdc_) + open_crpr; debugPrint3(debug_, "latch", 1, "latch data %s %s enable %s\n", network_->pathName(data_path->pin(this)), diff --git a/search/PathEnd.cc b/search/PathEnd.cc index ff7a9b32..8e1ab6be 100644 --- a/search/PathEnd.cc +++ b/search/PathEnd.cc @@ -358,7 +358,9 @@ PathEnd::checkTgtClkDelay(const PathVertex *tgt_clk_path, const RiseFall *tgt_clk_rf = tgt_clk_edge->transition(); insertion = search->clockInsertion(tgt_clk, tgt_src_pin, tgt_clk_rf, min_max, early_late, tgt_path_ap); - if (clk_info->isPropagated()) { + 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(sta); @@ -824,7 +826,7 @@ PathEndClkConstrainedMcp::checkMcpAdjustment(const Path *path, Sdc *sdc = sta->sdc(); if (min_max == MinMax::max()) return PathEnd::checkSetupMcpAdjustment(src_clk_edge, tgt_clk_edge, - mcp_, sdc); + mcp_, setupDefaultCycles(), sdc); else { // Hold check. // Default arrival clock is a proxy for the target clock. @@ -874,6 +876,7 @@ float PathEnd::checkSetupMcpAdjustment(const ClockEdge *src_clk_edge, const ClockEdge *tgt_clk_edge, const MultiCyclePath *mcp, + int default_cycles, Sdc *sdc) { if (mcp) { @@ -887,7 +890,7 @@ PathEnd::checkSetupMcpAdjustment(const ClockEdge *src_clk_edge, const ClockEdge *clk_edge = mcp->useEndClk() ? tgt_clk_edge : src_clk_edge; float period = clk_edge->clock()->period(); - return (mult - 1) * period; + return (mult - default_cycles) * period; } else return 0.0; diff --git a/search/ReportPath.cc b/search/ReportPath.cc index 2a9187d9..367d5c27 100644 --- a/search/ReportPath.cc +++ b/search/ReportPath.cc @@ -947,8 +947,8 @@ ReportPath::reportFull(const PathEndDataCheck *end, reportShort(end, expanded, result); reportSrcPathArrival(end, expanded, result); - // Capture/target clock path reporting resembles both source (reportSrcPath) - // and target (reportTgtClk) clocks. + // Data check target clock path reporting resembles + // both source (reportSrcPath) and target (reportTgtClk) clocks. // It is like a source because it can be a non-clock path. // It is like a target because crpr and uncertainty are reported. // It is always propagated, even if the clock is ideal. @@ -960,12 +960,14 @@ ReportPath::reportFull(const PathEndDataCheck *end, float src_offset = end->sourceClkOffset(this); Delay clk_delay = end->targetClkDelay(this); Arrival clk_arrival = end->targetClkArrival(this); - float offset = delayAsFloat(clk_arrival - clk_delay + src_offset); + ClockEdge *tgt_clk_edge = end->targetClkEdge(this); + float prev = delayAsFloat(clk_arrival) + src_offset; + float offset = prev - delayAsFloat(clk_delay) - tgt_clk_edge->time(); reportPath5(data_clk_path, clk_expanded, clk_expanded.startIndex(), clk_expanded.size() - 1, data_clk_path->clkInfo(search_)->isPropagated(), false, // Delay to startpoint is already included. - clk_arrival + src_offset, offset, result); + prev, offset, result); } reportRequired(end, checkRoleReason(end), result); reportSlack(end, result); diff --git a/search/VisitPathEnds.cc b/search/VisitPathEnds.cc index d3bd3da7..99e1152a 100644 --- a/search/VisitPathEnds.cc +++ b/search/VisitPathEnds.cc @@ -556,8 +556,8 @@ VisitPathEnds::visitDataCheckEnd1(DataCheck *check, || exception->isMultiCycle()) && (!filtered || search_->matchesFilter(path, tgt_clk_edge))) { - // No mcp for data checks. - PathEndDataCheck path_end(check, path, tgt_clk_path, nullptr, this); + MultiCyclePath *mcp=dynamic_cast(exception); + PathEndDataCheck path_end(check, path, tgt_clk_path, mcp, this); visitor->visit(&path_end); is_constrained = true; } From 00d0da6d5a627e9821d2986869395dd3a05367b4 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Mon, 13 Jul 2020 11:11:39 -0700 Subject: [PATCH 37/51] Sta::networkChanged() invalidate sdc graph annotations --- search/Sta.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/search/Sta.cc b/search/Sta.cc index 4b5890fd..f2798042 100644 --- a/search/Sta.cc +++ b/search/Sta.cc @@ -570,6 +570,7 @@ Sta::networkChanged() check_min_periods_->clear(); delete graph_; graph_ = nullptr; + graph_sdc_annotated_ = false; current_instance_ = nullptr; updateComponentsState(); } From d296a3405d7ffd072088ce4070e3ede0d5c052de Mon Sep 17 00:00:00 2001 From: James Cherry Date: Mon, 13 Jul 2020 11:30:26 -0700 Subject: [PATCH 38/51] rm swig_link_libraries --- CMakeLists.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bf2fe384..b679aaf6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -329,11 +329,6 @@ swig_add_library(sta_swig SOURCES ${STA_SWIG_FILE} ) -swig_link_libraries(sta_swig - PUBLIC - OpenSTA -) - # result build/CMakeFiles/sta_swig.dir/StaAppTCL_wrap.cxx target_include_directories(sta_swig PUBLIC From 4fa9e46235fd71df16ff52b2d003f21047f52c96 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Wed, 15 Jul 2020 07:56:34 -0700 Subject: [PATCH 39/51] write_verilog -remove_cells --- doc/OpenSTA.odt | Bin 76617 -> 77029 bytes include/sta/VerilogWriter.hh | 3 +++ tcl/Cmds.tcl | 6 +++++ tcl/StaTcl.i | 4 ++++ verilog/Verilog.i | 6 +++-- verilog/Verilog.tcl | 10 ++++++--- verilog/VerilogWriter.cc | 42 +++++++++++++++++++++-------------- 7 files changed, 49 insertions(+), 22 deletions(-) diff --git a/doc/OpenSTA.odt b/doc/OpenSTA.odt index a6045afa559e5b349c99f12619a7ff0e4178411d..6c52b0606bd5831e9054d4cedf76361e812338fb 100644 GIT binary patch delta 47442 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

Emx!NK7T)Se?-p3hV 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 delta 46978 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

k#q-?OOU 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 o{nh;bAD4(iNFYWStKXsJ)L;wH) diff --git a/include/sta/VerilogWriter.hh b/include/sta/VerilogWriter.hh index 0c877195..0684655e 100644 --- a/include/sta/VerilogWriter.hh +++ b/include/sta/VerilogWriter.hh @@ -16,6 +16,8 @@ #pragma once +#include "LibertyClass.hh" + namespace sta { class Network; @@ -23,6 +25,7 @@ class Network; void writeVerilog(const char *filename, bool sort, + LibertyCellSeq *remove_cells, Network *network); } // namespace diff --git a/tcl/Cmds.tcl b/tcl/Cmds.tcl index 7a541783..c4beb0b2 100644 --- a/tcl/Cmds.tcl +++ b/tcl/Cmds.tcl @@ -1145,6 +1145,12 @@ proc parse_libcell_libport_inst_port_pin_edge_timing_arc_set_arg { objects \ edges timing_arc_sets } +proc parse_libcell_arg { objects } { + set libcells {} + get_object_args $objects {} libcells {} {} {} {} {} {} {} {} + return $libcells +} + proc parse_libcell_inst_arg { objects libcells_var insts_var } { upvar 1 $libcells_var libcells upvar 1 $insts_var insts diff --git a/tcl/StaTcl.i b/tcl/StaTcl.i index 1a8bb558..e2d61edc 100644 --- a/tcl/StaTcl.i +++ b/tcl/StaTcl.i @@ -429,6 +429,10 @@ using namespace sta; Tcl_SetObjResult(interp, list); } +%typemap(in) LibertyCellSeq* { + $1 = tclListSeqLibertyCell($input, interp); +} + %typemap(out) TmpCellSeq* { Tcl_Obj *list = Tcl_NewListObj(0, nullptr); CellSeq *cells = $1; diff --git a/verilog/Verilog.i b/verilog/Verilog.i index 34f7609a..accbb207 100644 --- a/verilog/Verilog.i +++ b/verilog/Verilog.i @@ -25,6 +25,7 @@ using sta::Sta; using sta::NetworkReader; using sta::readVerilogFile; +using sta::LibertyCellSeq; %} @@ -51,13 +52,14 @@ delete_verilog_reader() void write_verilog_cmd(const char *filename, - bool sort) + bool sort, + LibertyCellSeq *remove_cells) { // This does NOT want the SDC (cmd) network because it wants // to see the sta internal names. Sta *sta = Sta::sta(); Network *network = sta->network(); - writeVerilog(filename, sort, network); + writeVerilog(filename, sort, remove_cells, network); } %} // inline diff --git a/verilog/Verilog.tcl b/verilog/Verilog.tcl index 3f46461a..59c5f0a8 100644 --- a/verilog/Verilog.tcl +++ b/verilog/Verilog.tcl @@ -19,15 +19,19 @@ namespace eval sta { # Defined by SWIG interface Verilog.i. define_cmd_args "read_verilog" {filename} -define_cmd_args "write_verilog" {[-sort] filename} +define_cmd_args "write_verilog" {[-sort] [-remove_cells cells] filename} proc write_verilog { args } { - parse_key_args "write_verilog" args keys {} flags {-sort} + parse_key_args "write_verilog" args keys {-remove_cells} flags {-sort} + set remove_cells {} + if { [info exists keys(-remove_cells)] } { + set remove_cells [sta::parse_libcell_arg $keys(-remove_cells)] + } set sort [info exists flags(-sort)] check_argc_eq1 "write_verilog" $args set filename $args - write_verilog_cmd $filename $sort + write_verilog_cmd $filename $sort $remove_cells } # sta namespace end diff --git a/verilog/VerilogWriter.cc b/verilog/VerilogWriter.cc index ed475971..eecabb7a 100644 --- a/verilog/VerilogWriter.cc +++ b/verilog/VerilogWriter.cc @@ -32,6 +32,7 @@ class VerilogWriter public: VerilogWriter(const char *filename, bool sort, + LibertyCellSeq *remove_cells, FILE *stream, Network *network); void writeModule(Instance *inst); @@ -54,6 +55,7 @@ class VerilogWriter const char *filename_; bool sort_; + CellSet remove_cells_; FILE *stream_; Network *network_; @@ -65,12 +67,13 @@ class VerilogWriter void writeVerilog(const char *filename, bool sort, + LibertyCellSeq *remove_cells, Network *network) { if (network->topInstance()) { FILE *stream = fopen(filename, "w"); if (stream) { - VerilogWriter writer(filename, sort, stream, network); + VerilogWriter writer(filename, sort, remove_cells, stream, network); writer.writeModule(network->topInstance()); fclose(stream); } @@ -81,6 +84,7 @@ writeVerilog(const char *filename, VerilogWriter::VerilogWriter(const char *filename, bool sort, + LibertyCellSeq *remove_cells, FILE *stream, Network *network) : filename_(filename), @@ -89,6 +93,8 @@ VerilogWriter::VerilogWriter(const char *filename, network_(network), unconnected_net_index_(1) { + for(LibertyCell *lib_cell : *remove_cells) + remove_cells_.insert(network->cell(lib_cell)); } void @@ -205,23 +211,25 @@ void VerilogWriter::writeChild(Instance *child) { Cell *child_cell = network_->cell(child); - const char *child_name = network_->name(child); - const char *child_vname = instanceVerilogName(child_name, - network_->pathEscape()); - fprintf(stream_, " %s %s (", - network_->name(child_cell), - child_vname); - bool first_port = true; - CellPortIterator *port_iter = network_->portIterator(child_cell); - while (port_iter->hasNext()) { - Port *port = port_iter->next(); - if (network_->hasMembers(port)) - writeInstBusPin(child, port, first_port); - else - writeInstPin(child, port, first_port); + if (!remove_cells_.hasKey(child_cell)) { + const char *child_name = network_->name(child); + const char *child_vname = instanceVerilogName(child_name, + network_->pathEscape()); + fprintf(stream_, " %s %s (", + network_->name(child_cell), + child_vname); + bool first_port = true; + CellPortIterator *port_iter = network_->portIterator(child_cell); + while (port_iter->hasNext()) { + Port *port = port_iter->next(); + if (network_->hasMembers(port)) + writeInstBusPin(child, port, first_port); + else + writeInstPin(child, port, first_port); + } + delete port_iter; + fprintf(stream_, ");\n"); } - delete port_iter; - fprintf(stream_, ");\n"); } void From a5722ae63c80d21e08a420059493319c86ce5a14 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Wed, 15 Jul 2020 11:56:11 -0700 Subject: [PATCH 40/51] write_verilog remove_cells use std::vector --- include/sta/VerilogWriter.hh | 4 +++- tcl/StaTcl.i | 38 ++++++++++++++++++++++++++++++------ verilog/Verilog.i | 2 +- verilog/VerilogWriter.cc | 12 +++++++----- 4 files changed, 43 insertions(+), 13 deletions(-) diff --git a/include/sta/VerilogWriter.hh b/include/sta/VerilogWriter.hh index 0684655e..7daa2f83 100644 --- a/include/sta/VerilogWriter.hh +++ b/include/sta/VerilogWriter.hh @@ -16,16 +16,18 @@ #pragma once +#include #include "LibertyClass.hh" namespace sta { +using std::vector; class Network; void writeVerilog(const char *filename, bool sort, - LibertyCellSeq *remove_cells, + vector *remove_cells, Network *network); } // namespace diff --git a/tcl/StaTcl.i b/tcl/StaTcl.i index e2d61edc..45318431 100644 --- a/tcl/StaTcl.i +++ b/tcl/StaTcl.i @@ -106,6 +106,8 @@ typedef MinMaxAll MinMaxAllNull; typedef ClockSet TmpClockSet; typedef StringSeq TmpStringSeq; +using std::vector; + class CmdErrorNetworkNotLinked : public Exception { public: @@ -188,6 +190,30 @@ tclListSeq(Tcl_Obj *const source, return nullptr; } +template +vector * +tclListStdSeq(Tcl_Obj *const source, + swig_type_info *swig_type, + Tcl_Interp *interp) +{ + int argc; + Tcl_Obj **argv; + + if (Tcl_ListObjGetElements(interp, source, &argc, &argv) == TCL_OK + && argc > 0) { + vector *seq = new vector; + for (int i = 0; i < argc; i++) { + void *obj; + // Ignore returned TCL_ERROR because can't get swig_type_info. + SWIG_ConvertPtr(argv[i], &obj, swig_type, false); + seq->push_back(reinterpret_cast(obj)); + } + return seq; + } + else + return nullptr; +} + LibertyLibrarySeq * tclListSeqLibertyLibrary(Tcl_Obj *const source, Tcl_Interp *interp) @@ -195,11 +221,11 @@ tclListSeqLibertyLibrary(Tcl_Obj *const source, return tclListSeq(source, SWIGTYPE_p_LibertyLibrary, interp); } -LibertyCellSeq * +vector * tclListSeqLibertyCell(Tcl_Obj *const source, Tcl_Interp *interp) { - return tclListSeq(source, SWIGTYPE_p_LibertyCell, interp); + return tclListStdSeq(source, SWIGTYPE_p_LibertyCell, interp); } template @@ -429,10 +455,6 @@ using namespace sta; Tcl_SetObjResult(interp, list); } -%typemap(in) LibertyCellSeq* { - $1 = tclListSeqLibertyCell($input, interp); -} - %typemap(out) TmpCellSeq* { Tcl_Obj *list = Tcl_NewListObj(0, nullptr); CellSeq *cells = $1; @@ -446,6 +468,10 @@ using namespace sta; delete cells; } +%typemap(in) vector * { + $1 = tclListSeqLibertyCell($input, interp); +} + %typemap(out) LibertyCellSeq* { Tcl_Obj *list = Tcl_NewListObj(0, nullptr); LibertyCellSeq *cells = $1; diff --git a/verilog/Verilog.i b/verilog/Verilog.i index accbb207..a0a2b368 100644 --- a/verilog/Verilog.i +++ b/verilog/Verilog.i @@ -53,7 +53,7 @@ delete_verilog_reader() void write_verilog_cmd(const char *filename, bool sort, - LibertyCellSeq *remove_cells) + vector *remove_cells) { // This does NOT want the SDC (cmd) network because it wants // to see the sta internal names. diff --git a/verilog/VerilogWriter.cc b/verilog/VerilogWriter.cc index eecabb7a..36ed2093 100644 --- a/verilog/VerilogWriter.cc +++ b/verilog/VerilogWriter.cc @@ -32,7 +32,7 @@ class VerilogWriter public: VerilogWriter(const char *filename, bool sort, - LibertyCellSeq *remove_cells, + vector *remove_cells, FILE *stream, Network *network); void writeModule(Instance *inst); @@ -67,7 +67,7 @@ class VerilogWriter void writeVerilog(const char *filename, bool sort, - LibertyCellSeq *remove_cells, + vector *remove_cells, Network *network) { if (network->topInstance()) { @@ -84,7 +84,7 @@ writeVerilog(const char *filename, VerilogWriter::VerilogWriter(const char *filename, bool sort, - LibertyCellSeq *remove_cells, + vector *remove_cells, FILE *stream, Network *network) : filename_(filename), @@ -93,8 +93,10 @@ VerilogWriter::VerilogWriter(const char *filename, network_(network), unconnected_net_index_(1) { - for(LibertyCell *lib_cell : *remove_cells) - remove_cells_.insert(network->cell(lib_cell)); + if (remove_cells) { + for(LibertyCell *lib_cell : *remove_cells) + remove_cells_.insert(network->cell(lib_cell)); + } } void From 9882f9d938b15c2bac2051c9b98fabf824342457 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Wed, 15 Jul 2020 15:12:14 -0700 Subject: [PATCH 41/51] swig_link_libraries back in --- CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index b679aaf6..bf2fe384 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -329,6 +329,11 @@ swig_add_library(sta_swig SOURCES ${STA_SWIG_FILE} ) +swig_link_libraries(sta_swig + PUBLIC + OpenSTA +) + # result build/CMakeFiles/sta_swig.dir/StaAppTCL_wrap.cxx target_include_directories(sta_swig PUBLIC From 1c8f1ec9fcd163c2bbad415ddaf2e0f344ff6afb Mon Sep 17 00:00:00 2001 From: James Cherry Date: Sat, 18 Jul 2020 09:12:38 -0700 Subject: [PATCH 42/51] VerilogWriter using instead of include for LibertyCell --- include/sta/VerilogWriter.hh | 3 ++- verilog/Verilog.i | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/sta/VerilogWriter.hh b/include/sta/VerilogWriter.hh index 7daa2f83..163e5f38 100644 --- a/include/sta/VerilogWriter.hh +++ b/include/sta/VerilogWriter.hh @@ -17,12 +17,13 @@ #pragma once #include -#include "LibertyClass.hh" namespace sta { using std::vector; + class Network; +class LibertyCell; void writeVerilog(const char *filename, diff --git a/verilog/Verilog.i b/verilog/Verilog.i index a0a2b368..2d740479 100644 --- a/verilog/Verilog.i +++ b/verilog/Verilog.i @@ -25,7 +25,6 @@ using sta::Sta; using sta::NetworkReader; using sta::readVerilogFile; -using sta::LibertyCellSeq; %} From 305a9bbf7053539e651db3fa8a6be6e1b74151bd Mon Sep 17 00:00:00 2001 From: James Cherry Date: Sat, 18 Jul 2020 09:13:17 -0700 Subject: [PATCH 43/51] rm deprecated code --- CMakeLists.txt | 2 +- include/sta/Clock.hh | 8 -------- include/sta/Sdc.hh | 1 - include/sta/Sta.hh | 46 -------------------------------------------- sdc/Clock.cc | 5 ----- sdc/Sdc.cc | 6 ------ search/Sta.cc | 30 ----------------------------- tcl/Cmds.tcl | 9 --------- 8 files changed, 1 insertion(+), 106 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bf2fe384..7115c790 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,7 +24,7 @@ if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.14) cmake_policy(SET CMP0086 NEW) endif() -project(STA VERSION 2.1.0 +project(STA VERSION 2.2.0 LANGUAGES CXX ) diff --git a/include/sta/Clock.hh b/include/sta/Clock.hh index d38c65ee..33d48898 100644 --- a/include/sta/Clock.hh +++ b/include/sta/Clock.hh @@ -294,12 +294,4 @@ void sortClockSet(ClockSet * set, ClockSeq &clks); -// Clock source pins. -class ClockPinIterator : public PinSet::Iterator -{ -public: - // Use range iterator on Clock::pins(). - ClockPinIterator(Clock *clk) __attribute__ ((deprecated)); -}; - } // namespace diff --git a/include/sta/Sdc.hh b/include/sta/Sdc.hh index 5bea0c9f..03dd6513 100644 --- a/include/sta/Sdc.hh +++ b/include/sta/Sdc.hh @@ -824,7 +824,6 @@ public: // Find the clocks defined for pin. ClockSet *findClocks(const Pin *pin) const; ClockSet *findLeafPinClocks(const Pin *pin) const; - ClockIterator *clockIterator() __attribute__ ((deprecated)); void sortedClocks(ClockSeq &clks); ClockSeq *clocks() { return &clocks_; } ClockSeq &clks() { return clocks_; } diff --git a/include/sta/Sta.hh b/include/sta/Sta.hh index b7e78efe..3c0a3377 100644 --- a/include/sta/Sta.hh +++ b/include/sta/Sta.hh @@ -292,17 +292,8 @@ public: void removeClock(Clock *clk); // Update period/waveform for generated clocks from source pin clock. void updateGeneratedClks(); - // Use Sdc::findClock - Clock *findClock(const char *name) const __attribute__ ((deprecated)); - // Use findClocksMatching. - void findClocksMatching(PatternMatch *pattern, - ClockSeq *clks) const __attribute__ ((deprecated)); - // Use Sdc::clockIterator. - ClockIterator *clockIterator() const __attribute__ ((deprecated)); // True if pin is defined as a clock source (pin may be hierarchical). bool isClockSrc(const Pin *pin) const; - // Use Sdc::defaultArrivalClock. - Clock *defaultArrivalClock() const __attribute__ ((deprecated)); // Propagated (non-ideal) clocks. void setPropagatedClock(Clock *clk); void removePropagatedClock(Clock *clk); @@ -873,42 +864,6 @@ public: bool removal, bool clk_gating_setup, bool clk_gating_hold); - PathEndSeq *reportTiming(ExceptionFrom *from, - ExceptionThruSeq *thrus, - ExceptionTo *to, - // Use corner nullptr to report timing - // for all corners. - const Corner *corner, - // max for setup checks. - // min for hold checks. - // min_max for setup and hold checks. - const MinMaxAll *min_max, - // Number of path ends to report in - // each group. - int group_count, - // Number of paths to report for - // each endpoint. - int endpoint_count, - // endpoint_count paths report unique pins - // without rise/fall variations. - bool unique_pins, - // Min/max bounds for slack of - // returned path ends. - float slack_min, - float slack_max, - // Sort path ends by slack ignoring path groups. - bool sort_by_slack, - // Path groups to report. - // Null or empty list reports all groups. - PathGroupNameSet *group_names, - // Predicates to filter the type of path - // ends returned. - bool setup, - bool hold, - bool recovery, - bool removal, - bool clk_gating_setup, - bool clk_gating_hold) __attribute__ ((deprecated)); void setReportPathFormat(ReportPathFormat format); void setReportPathFieldOrder(StringSeq *field_names); void setReportPathFields(bool report_input_pin, @@ -947,7 +902,6 @@ public: void delaysInvalid(); // Invalidate all arrival and required times. void arrivalsInvalid(); - void setPathMinMax(const MinMaxAll *min_max) __attribute__ ((deprecated)); void visitStartpoints(VertexVisitor *visitor); void visitEndpoints(VertexVisitor *visitor); // Find the fanin vertices for a group path. diff --git a/sdc/Clock.cc b/sdc/Clock.cc index 51a991a5..04cf3bbb 100644 --- a/sdc/Clock.cc +++ b/sdc/Clock.cc @@ -699,9 +699,4 @@ sortClockSet(ClockSet *set, sort(clks, ClockNameLess()); } -ClockPinIterator::ClockPinIterator(Clock *clk) : - PinSet::Iterator(clk->pins()) -{ -} - } // namespace diff --git a/sdc/Sdc.cc b/sdc/Sdc.cc index ea1de829..9d2839b1 100644 --- a/sdc/Sdc.cc +++ b/sdc/Sdc.cc @@ -1240,12 +1240,6 @@ Sdc::findClocksMatching(PatternMatch *pattern, } } -ClockIterator * -Sdc::clockIterator() -{ - return new ClockIterator(clocks_); -} - void Sdc::sortedClocks(ClockSeq &clks) { diff --git a/search/Sta.cc b/search/Sta.cc index f2798042..10a76a1b 100644 --- a/search/Sta.cc +++ b/search/Sta.cc @@ -1102,37 +1102,12 @@ Sta::removeClock(Clock *clk) search_->arrivalsInvalid(); } -Clock * -Sta::findClock(const char *name) const -{ - return sdc_->findClock(name); -} - -void -Sta::findClocksMatching(PatternMatch *pattern, - ClockSeq *clks) const -{ - sdc_->findClocksMatching(pattern, clks); -} - -ClockIterator * -Sta::clockIterator() const -{ - return new ClockIterator(sdc_); -} - bool Sta::isClockSrc(const Pin *pin) const { return sdc_->isClock(pin); } -Clock * -Sta::defaultArrivalClock() const -{ - return sdc_->defaultArrivalClock(); -} - void Sta::setPropagatedClock(Clock *clk) { @@ -2427,11 +2402,6 @@ Sta::setCmdCorner(Corner *corner) cmd_corner_ = corner; } -void -Sta::setPathMinMax(const MinMaxAll *) -{ -} - //////////////////////////////////////////////////////////////// // from/thrus/to are owned and deleted by Search. diff --git a/tcl/Cmds.tcl b/tcl/Cmds.tcl index c4beb0b2..91548b66 100644 --- a/tcl/Cmds.tcl +++ b/tcl/Cmds.tcl @@ -45,15 +45,6 @@ proc report_clock1 { clk } { } } -proc_redirect read_parasitics { - variable native - - if { $native } { - sta_warn "The read_parasitics command is deprecated. Use read_spef." - } - eval [concat read_spef $args] -} - proc check_setup_cmd { cmd cmd_args } { parse_key_args $cmd cmd_args keys {} flags {-verbose} 0 # When nothing is everything. From 2ce82bd1876dac7622e2a1383580c336b89d0255 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Sat, 18 Jul 2020 19:54:10 -0700 Subject: [PATCH 44/51] include Machine.hh in headers that use __attribute__ --- app/StaMain.cc | 1 - include/sta/Debug.hh | 1 + {util => include/sta}/Machine.hh | 0 include/sta/Report.hh | 1 + include/sta/Sta.hh | 1 + include/sta/StringUtil.hh | 1 + include/sta/TimingArc.hh | 1 + liberty/LibertyReaderPvt.hh | 1 + parasitics/SpefReaderPvt.hh | 1 + search/Sta.cc | 1 - tcl/StaTcl.i | 1 - util/Report.cc | 1 - util/Stats.cc | 2 +- util/StringUtil.cc | 2 +- 14 files changed, 9 insertions(+), 6 deletions(-) rename {util => include/sta}/Machine.hh (100%) diff --git a/app/StaMain.cc b/app/StaMain.cc index 88d5821d..aaed624a 100644 --- a/app/StaMain.cc +++ b/app/StaMain.cc @@ -19,7 +19,6 @@ #include #include -#include "util/Machine.hh" #include "StringUtil.hh" #include "Vector.hh" #include "Sta.hh" diff --git a/include/sta/Debug.hh b/include/sta/Debug.hh index eb70468a..22c57794 100644 --- a/include/sta/Debug.hh +++ b/include/sta/Debug.hh @@ -17,6 +17,7 @@ #pragma once #include +#include "Machine.hh" #include "DisallowCopyAssign.hh" #include "Map.hh" #include "StringUtil.hh" diff --git a/util/Machine.hh b/include/sta/Machine.hh similarity index 100% rename from util/Machine.hh rename to include/sta/Machine.hh diff --git a/include/sta/Report.hh b/include/sta/Report.hh index 6dd0ca47..df45765d 100644 --- a/include/sta/Report.hh +++ b/include/sta/Report.hh @@ -20,6 +20,7 @@ #include #include #include +#include "Machine.hh" #include "DisallowCopyAssign.hh" struct Tcl_Interp; diff --git a/include/sta/Sta.hh b/include/sta/Sta.hh index 3c0a3377..e8b23756 100644 --- a/include/sta/Sta.hh +++ b/include/sta/Sta.hh @@ -18,6 +18,7 @@ #include +#include "Machine.hh" #include "DisallowCopyAssign.hh" #include "StringSeq.hh" #include "LibertyClass.hh" diff --git a/include/sta/StringUtil.hh b/include/sta/StringUtil.hh index 5cfc650a..d06eef34 100644 --- a/include/sta/StringUtil.hh +++ b/include/sta/StringUtil.hh @@ -19,6 +19,7 @@ #include #include #include +#include "Machine.hh" #include "Vector.hh" namespace sta { diff --git a/include/sta/TimingArc.hh b/include/sta/TimingArc.hh index ce1a2fc9..00cea66b 100644 --- a/include/sta/TimingArc.hh +++ b/include/sta/TimingArc.hh @@ -16,6 +16,7 @@ #pragma once +#include "Machine.hh" #include "DisallowCopyAssign.hh" #include "Vector.hh" #include "Transition.hh" diff --git a/liberty/LibertyReaderPvt.hh b/liberty/LibertyReaderPvt.hh index 2a356a61..26dffdcc 100644 --- a/liberty/LibertyReaderPvt.hh +++ b/liberty/LibertyReaderPvt.hh @@ -18,6 +18,7 @@ #include +#include "Machine.hh" #include "DisallowCopyAssign.hh" #include "Vector.hh" #include "Map.hh" diff --git a/parasitics/SpefReaderPvt.hh b/parasitics/SpefReaderPvt.hh index 3008230a..41a9cfd6 100644 --- a/parasitics/SpefReaderPvt.hh +++ b/parasitics/SpefReaderPvt.hh @@ -16,6 +16,7 @@ #pragma once +#include "Machine.hh" #include "Zlib.hh" #include "Map.hh" #include "StringSeq.hh" diff --git a/search/Sta.cc b/search/Sta.cc index 10a76a1b..dbba27fc 100644 --- a/search/Sta.cc +++ b/search/Sta.cc @@ -16,7 +16,6 @@ #include "Sta.hh" -#include "util/Machine.hh" #include "DispatchQueue.hh" #include "ReportTcl.hh" #include "Debug.hh" diff --git a/tcl/StaTcl.i b/tcl/StaTcl.i index 45318431..00b46b43 100644 --- a/tcl/StaTcl.i +++ b/tcl/StaTcl.i @@ -73,7 +73,6 @@ #include "WritePathSpice.hh" #include "Search.hh" #include "Sta.hh" -#include "util/Machine.hh" #include "search/Tag.hh" #include "search/CheckTiming.hh" #include "search/CheckMinPulseWidths.hh" diff --git a/util/Report.cc b/util/Report.cc index 0d8d07eb..fea3d287 100644 --- a/util/Report.cc +++ b/util/Report.cc @@ -19,7 +19,6 @@ #include // min #include "Error.hh" -#include "Machine.hh" namespace sta { diff --git a/util/Stats.cc b/util/Stats.cc index 5b647f63..d27f89f0 100644 --- a/util/Stats.cc +++ b/util/Stats.cc @@ -16,9 +16,9 @@ #include "Stats.hh" +#include "Machine.hh" #include "StringUtil.hh" #include "Debug.hh" -#include "Machine.hh" namespace sta { diff --git a/util/StringUtil.cc b/util/StringUtil.cc index 151b9a08..95f8d13e 100644 --- a/util/StringUtil.cc +++ b/util/StringUtil.cc @@ -20,8 +20,8 @@ #include #include -#include "Mutex.hh" #include "Machine.hh" +#include "Mutex.hh" namespace sta { From 157ef75a1041879275a80d477a3723ee38e30b3c Mon Sep 17 00:00:00 2001 From: James Cherry Date: Sat, 25 Jul 2020 15:29:46 -0700 Subject: [PATCH 45/51] Sta::makeInstanceAfter internal pins may be missing --- search/Sta.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/search/Sta.cc b/search/Sta.cc index dbba27fc..7cd254ed 100644 --- a/search/Sta.cc +++ b/search/Sta.cc @@ -3806,7 +3806,9 @@ Sta::makeInstanceAfter(Instance *inst) while (port_iter.hasNext()) { LibertyPort *lib_port = port_iter.next(); Pin *pin = network_->findPin(inst, lib_port); - connectPinAfter(pin); + // Internal pins may not exist. + if (pin) + connectPinAfter(pin); } } } From 3970a1b31469ca0fe7e878b80000075a5f9c0895 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Sat, 25 Jul 2020 17:02:56 -0700 Subject: [PATCH 46/51] swig cmake --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7115c790..97d0a982 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -329,7 +329,7 @@ swig_add_library(sta_swig SOURCES ${STA_SWIG_FILE} ) -swig_link_libraries(sta_swig +target_link_libraries(sta_swig PUBLIC OpenSTA ) @@ -477,8 +477,8 @@ target_compile_options(sta ) target_link_libraries(sta - OpenSTA sta_swig + OpenSTA ${TCL_LIBRARY} ) From 746ed77193c7fea31c823262bde0b244d1c68f99 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Sun, 26 Jul 2020 21:41:19 -0700 Subject: [PATCH 47/51] get_property liberty_port drive_resistance --- search/Property.cc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/search/Property.cc b/search/Property.cc index f77e0889..d56a1447 100644 --- a/search/Property.cc +++ b/search/Property.cc @@ -36,6 +36,7 @@ namespace sta { using std::string; +using std::max; static PropertyValue pinSlewProperty(const Pin *pin, @@ -690,6 +691,11 @@ getProperty(const LibertyPort *port, } else if (stringEqual(property, "is_register_clock")) return PropertyValue(port->isRegClk()); + else if (stringEqual(property, "drive_resistance")) { + float drive = max(port->driveResistance(RiseFall::rise(), MinMax::max()), + port->driveResistance(RiseFall::fall(), MinMax::max())); + return PropertyValue(drive); + } else if (stringEqual(property, "drive_resistance_rise_min")) return PropertyValue(port->driveResistance(RiseFall::rise(), MinMax::min())); From 6cdb9d3cfe3ebc4c23647b92a705abe99538856a Mon Sep 17 00:00:00 2001 From: James Cherry Date: Wed, 29 Jul 2020 18:14:56 -0700 Subject: [PATCH 48/51] power --- search/Power.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/search/Power.cc b/search/Power.cc index 525aecde..4a3caadb 100644 --- a/search/Power.cc +++ b/search/Power.cc @@ -153,7 +153,7 @@ Power::power(const Corner *corner, LibertyCell *cell = network_->libertyCell(inst); if (cell) { PowerResult inst_power; - power(inst, corner, inst_power); + power(inst, cell, corner, inst_power); if (cell->isMacro() || cell->isMemory()) macro.incr(inst_power); @@ -358,7 +358,7 @@ Power::evalActivity(FuncExpr *expr, else return PwrActivity(0.0, 0.0, PwrActivityOrigin::constant); } - Pin *pin = network_->findPin(inst, port->name()); + Pin *pin = network_->findPin(inst, port); if (pin) return findActivity(pin); else From a23ba7b88e26b603a942db42be48d7ec7acd06cf Mon Sep 17 00:00:00 2001 From: James Cherry Date: Thu, 30 Jul 2020 07:55:48 -0700 Subject: [PATCH 49/51] power activity for internal pins --- include/sta/Power.hh | 27 ++++++++++++ search/Power.cc | 102 +++++++++++++++++++++++++++++++++---------- 2 files changed, 105 insertions(+), 24 deletions(-) diff --git a/include/sta/Power.hh b/include/sta/Power.hh index 12625a5e..c35c16f0 100644 --- a/include/sta/Power.hh +++ b/include/sta/Power.hh @@ -25,7 +25,24 @@ class PwrActivity; class PropActivityVisitor; class BfsFwdIterator; +typedef std::pair SeqPin; + +class SeqPinHash +{ +public: + size_t operator()(const SeqPin &pin) const; +}; + +class SeqPinEqual +{ +public: + bool operator()(const SeqPin &pin1, + const SeqPin &pin2) const; +}; + typedef UnorderedMap PwrActivityMap; +typedef UnorderedMap PwrSeqActivityMap; enum class PwrActivityOrigin { @@ -94,6 +111,13 @@ public: float activity, float duty, PwrActivityOrigin origin); + void setSeqActivity(const Instance *reg, + LibertyPort *output, + PwrActivity &activity); + bool hasSeqActivity(const Instance *reg, + LibertyPort *output); + PwrActivity seqActivity(const Instance *reg, + LibertyPort *output); // Activity is toggles per second. PwrActivity findClkedActivity(const Pin *pin); @@ -140,6 +164,8 @@ protected: PwrActivity findClkedActivity(const Pin *pin, const Clock *inst_clk); PwrActivity findActivity(const Pin *pin); + PwrActivity findSeqActivity(const Instance *inst, + LibertyPort *port); float portVoltage(LibertyCell *cell, const LibertyPort *port, const DcalcAnalysisPt *dcalc_ap); @@ -172,6 +198,7 @@ private: PwrActivity global_activity_; PwrActivity input_activity_; PwrActivityMap activity_map_; + PwrSeqActivityMap seq_activity_map_; bool activities_valid_; friend class PropActivityVisitor; diff --git a/search/Power.cc b/search/Power.cc index 4a3caadb..506b10e6 100644 --- a/search/Power.cc +++ b/search/Power.cc @@ -20,6 +20,7 @@ #include "Debug.hh" #include "EnumNameMap.hh" +#include "Hash.hh" #include "MinMax.hh" #include "Units.hh" #include "Transition.hh" @@ -131,6 +132,47 @@ Power::setPinActivity(const Pin *pin, activities_valid_ = false; } +// Sequential internal pins may not be in the netlist so their +// activities are stored by instance/liberty_port pairs. +void +Power::setSeqActivity(const Instance *reg, + LibertyPort *output, + PwrActivity &activity) +{ + seq_activity_map_[SeqPin(reg, output)] = activity; + activities_valid_ = false; +} + +bool +Power::hasSeqActivity(const Instance *reg, + LibertyPort *output) +{ + return seq_activity_map_.hasKey(SeqPin(reg, output)); +} + +PwrActivity +Power::seqActivity(const Instance *reg, + LibertyPort *output) +{ + return seq_activity_map_[SeqPin(reg, output)]; +} + +size_t +SeqPinHash::operator()(const SeqPin &pin) const +{ + return hashSum(hashPtr(pin.first), hashPtr(pin.second)); +} + +bool +SeqPinEqual::operator()(const SeqPin &pin1, + const SeqPin &pin2) const +{ + return pin1.first == pin2.first + && pin1.second == pin2.second; +} + +//////////////////////////////////////////////////////////////// + void Power::power(const Corner *corner, // Return values. @@ -358,11 +400,14 @@ Power::evalActivity(FuncExpr *expr, else return PwrActivity(0.0, 0.0, PwrActivityOrigin::constant); } - Pin *pin = network_->findPin(inst, port); - if (pin) - return findActivity(pin); - else - return PwrActivity(0.0, 0.0, PwrActivityOrigin::constant); + if (port->direction()->isInternal()) + return findSeqActivity(inst, port); + else { + Pin *pin = network_->findPin(inst, port); + if (pin) + return findActivity(pin); + } + return PwrActivity(0.0, 0.0, PwrActivityOrigin::constant); } case FuncExpr::op_not: { PwrActivity activity1 = evalActivity(expr->left(), inst, @@ -456,7 +501,7 @@ Power::seedActivities(BfsFwdIterator &bfs) const Pin *pin = vertex->pin(); // Clock activities are baked in. if (!sdc_->isLeafPinClock(pin) - && network_->direction(pin) != PortDirection::internal()) { + && !network_->direction(pin)->isInternal()) { debugPrint1(debug_, "power_activity", 3, "seed %s\n", vertex->name(network_)); PwrActivity &activity = pinActivity(pin); @@ -484,9 +529,9 @@ Power::seedRegOutputActivities(const Instance *inst, // the sequential internal pins (IQ, IQN). InstancePinIterator *pin_iter = network_->pinIterator(inst); while (pin_iter->hasNext()) { - auto pin = pin_iter->next(); - auto port = network_->libertyPort(pin); - auto func = port->function(); + Pin *pin = pin_iter->next(); + LibertyPort *port = network_->libertyPort(pin); + FuncExpr *func = port->function(); if (func) { Vertex *vertex = graph_->pinDrvrVertex(pin); if (func->port() == seq->output() @@ -507,15 +552,12 @@ Power::seedRegOutputActivities(const Instance *reg, LibertyPort *output, bool invert) { - const Pin *pin = network_->findPin(reg, output); - if (pin) { - PwrActivity activity = evalActivity(seq->data(), reg); - if (invert) - activity.set(activity.activity(), - 1.0 - activity.duty(), - activity.origin()); - setPinActivity(pin, activity); - } + PwrActivity activity = evalActivity(seq->data(), reg); + if (invert) + activity.set(activity.activity(), + 1.0 - activity.duty(), + activity.origin()); + setSeqActivity(reg, output, activity); } //////////////////////////////////////////////////////////////// @@ -951,12 +993,24 @@ Power::findActivity(const Pin *pin) return PwrActivity(2.0, 0.5, PwrActivityOrigin::clock); else if (global_activity_.isSet()) return global_activity_; - else { - if (activity_map_.hasKey(pin)) { - PwrActivity &activity = activity_map_[pin]; - if (activity.origin() != PwrActivityOrigin::unknown) - return activity; - } + else if (activity_map_.hasKey(pin)) { + PwrActivity &activity = activity_map_[pin]; + if (activity.origin() != PwrActivityOrigin::unknown) + return activity; + } + return input_activity_; +} + +PwrActivity +Power::findSeqActivity(const Instance *inst, + LibertyPort *port) +{ + if (global_activity_.isSet()) + return global_activity_; + else if (hasSeqActivity(inst, port)) { + PwrActivity activity = seqActivity(inst, port); + if (activity.origin() != PwrActivityOrigin::unknown) + return activity; } return input_activity_; } From cafb7b9152efac0ef7fdaf99193b3f0a73d1d043 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Fri, 31 Jul 2020 09:42:24 -0700 Subject: [PATCH 50/51] reorg power headers --- include/sta/PowerClass.hh | 73 ++++++++++++++++++++++++++++++++ include/sta/Property.hh | 2 +- include/sta/Sta.hh | 1 + search/Power.cc | 2 +- {include/sta => search}/Power.hh | 67 +++++------------------------ tcl/StaTcl.i | 2 +- 6 files changed, 88 insertions(+), 59 deletions(-) create mode 100644 include/sta/PowerClass.hh rename {include/sta => search}/Power.hh (83%) diff --git a/include/sta/PowerClass.hh b/include/sta/PowerClass.hh new file mode 100644 index 00000000..706e1f45 --- /dev/null +++ b/include/sta/PowerClass.hh @@ -0,0 +1,73 @@ +// OpenSTA, Static Timing Analyzer +// Copyright (c) 2020, 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 . + +#pragma once + +namespace sta { + +enum class PwrActivityOrigin +{ + global, + input, + user, + propagated, + clock, + constant, + defaulted, + unknown +}; + +class PwrActivity +{ +public: + PwrActivity(); + PwrActivity(float activity, + float duty, + PwrActivityOrigin origin); + float activity() const { return activity_; } + float duty() const { return duty_; } + PwrActivityOrigin origin() { return origin_; } + const char *originName() const; + void set(float activity, + float duty, + PwrActivityOrigin origin); + bool isSet() const; + +private: + // In general activity is per clock cycle, NOT per second. + float activity_; + float duty_; + PwrActivityOrigin origin_; +}; + +class PowerResult +{ +public: + PowerResult(); + void clear(); + float &internal() { return internal_; } + float &switching() { return switching_; } + float &leakage() { return leakage_; } + float total() const; + void incr(PowerResult &result); + +private: + float internal_; + float switching_; + float leakage_; +}; + +} // namespace diff --git a/include/sta/Property.hh b/include/sta/Property.hh index c83f1af1..d4b35bee 100644 --- a/include/sta/Property.hh +++ b/include/sta/Property.hh @@ -22,7 +22,7 @@ #include "NetworkClass.hh" #include "SearchClass.hh" #include "SdcClass.hh" -#include "Power.hh" +#include "PowerClass.hh" namespace sta { diff --git a/include/sta/Sta.hh b/include/sta/Sta.hh index e8b23756..fb3b6172 100644 --- a/include/sta/Sta.hh +++ b/include/sta/Sta.hh @@ -29,6 +29,7 @@ #include "StaState.hh" #include "VertexVisitor.hh" #include "SearchClass.hh" +#include "PowerClass.hh" struct Tcl_Interp; diff --git a/search/Power.cc b/search/Power.cc index 506b10e6..367a8dc3 100644 --- a/search/Power.cc +++ b/search/Power.cc @@ -65,7 +65,7 @@ isPositiveUnate(const LibertyCell *cell, const LibertyPort *from, const LibertyPort *to); -Power::Power(Sta *sta) : +Power::Power(StaState *sta) : StaState(sta), global_activity_{0.0, 0.0, PwrActivityOrigin::unknown}, input_activity_{0.1, 0.5, PwrActivityOrigin::input}, diff --git a/include/sta/Power.hh b/search/Power.hh similarity index 83% rename from include/sta/Power.hh rename to search/Power.hh index c35c16f0..d83a5ddc 100644 --- a/include/sta/Power.hh +++ b/search/Power.hh @@ -16,12 +16,19 @@ #pragma once -#include "Sta.hh" +#include + +#include "UnorderedMap.hh" +#include "Network.hh" +#include "SdcClass.hh" +#include "PowerClass.hh" +#include "StaState.hh" namespace sta { -class PowerResult; -class PwrActivity; +class Sta; +class Corner; +class DcalcAnalysisPt; class PropActivityVisitor; class BfsFwdIterator; @@ -44,47 +51,12 @@ typedef UnorderedMap PwrActivityMap; typedef UnorderedMap PwrSeqActivityMap; -enum class PwrActivityOrigin -{ - global, - input, - user, - propagated, - clock, - constant, - defaulted, - unknown -}; - -class PwrActivity -{ -public: - PwrActivity(); - PwrActivity(float activity, - float duty, - PwrActivityOrigin origin); - float activity() const { return activity_; } - float duty() const { return duty_; } - PwrActivityOrigin origin() { return origin_; } - const char *originName() const; - void set(float activity, - float duty, - PwrActivityOrigin origin); - bool isSet() const; - -private: - // In general activity is per clock cycle, NOT per second. - float activity_; - float duty_; - PwrActivityOrigin origin_; -}; - // The Power class has access to Sta components directly for // convenience but also requires access to the Sta class member functions. class Power : public StaState { public: - Power(Sta *sta); + Power(StaState *sta); void power(const Corner *corner, // Return values. PowerResult &total, @@ -204,21 +176,4 @@ private: friend class PropActivityVisitor; }; -class PowerResult -{ -public: - PowerResult(); - void clear(); - float &internal() { return internal_; } - float &switching() { return switching_; } - float &leakage() { return leakage_; } - float total() const; - void incr(PowerResult &result); - -private: - float internal_; - float switching_; - float leakage_; -}; - } // namespace diff --git a/tcl/StaTcl.i b/tcl/StaTcl.i index 00b46b43..65cd5cce 100644 --- a/tcl/StaTcl.i +++ b/tcl/StaTcl.i @@ -68,7 +68,6 @@ #include "PathEnd.hh" #include "PathGroup.hh" #include "PathAnalysisPt.hh" -#include "Power.hh" #include "Property.hh" #include "WritePathSpice.hh" #include "Search.hh" @@ -78,6 +77,7 @@ #include "search/CheckMinPulseWidths.hh" #include "search/Levelize.hh" #include "search/ReportPath.hh" +#include "search/Power.hh" namespace sta { From ae3da179e2bd5243a88702a906340196614e7472 Mon Sep 17 00:00:00 2001 From: James Cherry Date: Wed, 5 Aug 2020 07:23:38 -0700 Subject: [PATCH 51/51] set_power_activity -pins --- search/Power.cc | 137 ++++++++++++++++++++++++++++-------------------- search/Power.hh | 36 ++++++++----- tcl/Power.tcl | 2 +- tcl/StaTcl.i | 4 +- 4 files changed, 106 insertions(+), 73 deletions(-) diff --git a/search/Power.cc b/search/Power.cc index 367a8dc3..3e274280 100644 --- a/search/Power.cc +++ b/search/Power.cc @@ -97,39 +97,50 @@ Power::setInputPortActivity(const Port *input_port, Instance *top_inst = network_->topInstance(); const Pin *pin = network_->findPin(top_inst, input_port); if (pin) { - activity_map_[pin] = {activity, duty, PwrActivityOrigin::user}; + user_activity_map_[pin] = {activity, duty, PwrActivityOrigin::user}; activities_valid_ = false; } } +void +Power::setUserActivity(const Pin *pin, + float activity, + float duty, + PwrActivityOrigin origin) +{ + user_activity_map_[pin] = {activity, duty, origin}; + activities_valid_ = false; +} + PwrActivity & -Power::pinActivity(const Pin *pin) +Power::userActivity(const Pin *pin) { - return activity_map_[pin]; + return user_activity_map_[pin]; } bool -Power::hasPinActivity(const Pin *pin) +Power::hasUserActivity(const Pin *pin) { - return activity_map_.hasKey(pin); + return user_activity_map_.hasKey(pin); } void -Power::setPinActivity(const Pin *pin, - PwrActivity &activity) +Power::setActivity(const Pin *pin, + PwrActivity &activity) { activity_map_[pin] = activity; - activities_valid_ = false; } -void -Power::setPinActivity(const Pin *pin, - float activity, - float duty, - PwrActivityOrigin origin) +PwrActivity & +Power::activity(const Pin *pin) { - activity_map_[pin] = {activity, duty, origin}; - activities_valid_ = false; + return activity_map_[pin]; +} + +bool +Power::hasActivity(const Pin *pin) +{ + return activity_map_.hasKey(pin); } // Sequential internal pins may not be in the netlist so their @@ -332,44 +343,48 @@ PropActivityVisitor::visit(Vertex *vertex) auto pin = vertex->pin(); debugPrint1(debug_, "power_activity", 3, "visit %s\n", vertex->name(network_)); - bool input_without_activity = false; - if (network_->isLoad(pin)) { - VertexInEdgeIterator edge_iter(vertex, graph_); - if (edge_iter.hasNext()) { - Edge *edge = edge_iter.next(); - if (edge->isWire()) { - Vertex *from_vertex = edge->from(graph_); - const Pin *from_pin = from_vertex->pin(); - PwrActivity &from_activity = power_->pinActivity(from_pin); - PwrActivity to_activity(from_activity.activity(), - from_activity.duty(), - PwrActivityOrigin::propagated); - if (!power_->hasPinActivity(pin)) - input_without_activity = true; - power_->setPinActivity(pin, to_activity); + if (power_->hasUserActivity(pin)) + power_->setActivity(pin, power_->userActivity(pin)); + else { + bool input_without_activity = false; + if (network_->isLoad(pin)) { + VertexInEdgeIterator edge_iter(vertex, graph_); + if (edge_iter.hasNext()) { + Edge *edge = edge_iter.next(); + if (edge->isWire()) { + Vertex *from_vertex = edge->from(graph_); + const Pin *from_pin = from_vertex->pin(); + PwrActivity &from_activity = power_->activity(from_pin); + PwrActivity to_activity(from_activity.activity(), + from_activity.duty(), + PwrActivityOrigin::propagated); + if (!power_->hasActivity(pin)) + input_without_activity = true; + power_->setActivity(pin, to_activity); + } + } + Instance *inst = network_->instance(pin); + auto cell = network_->libertyCell(inst); + if (cell && cell->hasSequentials()) { + debugPrint1(debug_, "power_activity", 3, "pending reg %s\n", + network_->pathName(inst)); + visited_regs_->insert(inst); + found_reg_without_activity_ = input_without_activity; } } - Instance *inst = network_->instance(pin); - auto cell = network_->libertyCell(inst); - if (cell && cell->hasSequentials()) { - debugPrint1(debug_, "power_activity", 3, "pending reg %s\n", - network_->pathName(inst)); - visited_regs_->insert(inst); - found_reg_without_activity_ = input_without_activity; - } - } - if (network_->isDriver(pin)) { - LibertyPort *port = network_->libertyPort(pin); - if (port) { - FuncExpr *func = port->function(); - if (func) { - Instance *inst = network_->instance(pin); - PwrActivity activity = power_->evalActivity(func, inst); - power_->setPinActivity(pin, activity); - debugPrint3(debug_, "power_activity", 3, "set %s %.2e %.2f\n", - vertex->name(network_), - activity.activity(), - activity.duty()); + if (network_->isDriver(pin)) { + LibertyPort *port = network_->libertyPort(pin); + if (port) { + FuncExpr *func = port->function(); + if (func) { + Instance *inst = network_->instance(pin); + PwrActivity activity = power_->evalActivity(func, inst); + power_->setActivity(pin, activity); + debugPrint3(debug_, "power_activity", 3, "set %s %.2e %.2f\n", + vertex->name(network_), + activity.activity(), + activity.duty()); + } } } } @@ -469,14 +484,21 @@ Power::preamble() void Power::ensureActivities() { + // No need to propagate activites if global activity is set. if (!global_activity_.isSet()) { if (!activities_valid_) { + // Clear existing activities. + activity_map_.clear(); + seq_activity_map_.clear(); + ActivitySrchPred activity_srch_pred(this); BfsFwdIterator bfs(BfsIndex::other, &activity_srch_pred, this); seedActivities(bfs); PropActivityVisitor visitor(this, &bfs); visitor.init(); + // Propagate activities through combinational logic. bfs.visit(levelize_->maxLevel(), &visitor); + // Propagate activiities through registers. while (visitor.foundRegWithoutActivity()) { InstanceSet *regs = visitor.stealVisitedRegs(); InstanceSet::Iterator reg_iter(regs); @@ -486,7 +508,10 @@ Power::ensureActivities() seedRegOutputActivities(reg, bfs); } delete regs; + visitor.init(); + // Propagate register output activities through + // combinational logic. bfs.visit(levelize_->maxLevel(), &visitor); } activities_valid_ = true; @@ -504,11 +529,11 @@ Power::seedActivities(BfsFwdIterator &bfs) && !network_->direction(pin)->isInternal()) { debugPrint1(debug_, "power_activity", 3, "seed %s\n", vertex->name(network_)); - PwrActivity &activity = pinActivity(pin); - PwrActivityOrigin origin = activity.origin(); - // Default inputs without explicit activities to the input default. - if (origin != PwrActivityOrigin::user) - setPinActivity(pin, input_activity_); + if (hasUserActivity(pin)) + setActivity(pin, userActivity(pin)); + else + // Default inputs without explicit activities to the input default. + setActivity(pin, input_activity_); Vertex *vertex = graph_->pinDrvrVertex(pin); bfs.enqueueAdjacentVertices(vertex); } diff --git a/search/Power.hh b/search/Power.hh index d83a5ddc..ec51b5d8 100644 --- a/search/Power.hh +++ b/search/Power.hh @@ -75,14 +75,19 @@ public: void setInputPortActivity(const Port *input_port, float activity, float duty); - PwrActivity &pinActivity(const Pin *pin); - bool hasPinActivity(const Pin *pin); - void setPinActivity(const Pin *pin, - PwrActivity &activity); - void setPinActivity(const Pin *pin, - float activity, - float duty, - PwrActivityOrigin origin); + PwrActivity &activity(const Pin *pin); + void setUserActivity(const Pin *pin, + float activity, + float duty, + PwrActivityOrigin origin); + // Activity is toggles per second. + PwrActivity findClkedActivity(const Pin *pin); + +protected: + void preamble(); + void ensureActivities(); + bool hasUserActivity(const Pin *pin); + PwrActivity &userActivity(const Pin *pin); void setSeqActivity(const Instance *reg, LibertyPort *output, PwrActivity &activity); @@ -90,12 +95,9 @@ public: LibertyPort *output); PwrActivity seqActivity(const Instance *reg, LibertyPort *output); - // Activity is toggles per second. - PwrActivity findClkedActivity(const Pin *pin); - -protected: - void preamble(); - void ensureActivities(); + bool hasActivity(const Pin *pin); + void setActivity(const Pin *pin, + PwrActivity &activity); void power(const Instance *inst, LibertyCell *cell, @@ -167,8 +169,14 @@ protected: const LibertyPort *cofactor_port); private: + // Port/pin activities set by set_pin_activity. + // set_pin_activity -global PwrActivity global_activity_; + // set_pin_activity -input PwrActivity input_activity_; + // set_pin_activity -input_ports -pins + PwrActivityMap user_activity_map_; + // Propagated activities. PwrActivityMap activity_map_; PwrSeqActivityMap seq_activity_map_; bool activities_valid_; diff --git a/tcl/Power.tcl b/tcl/Power.tcl index 840801ba..d8bfaaa3 100644 --- a/tcl/Power.tcl +++ b/tcl/Power.tcl @@ -247,7 +247,7 @@ proc set_power_activity { args } { } } if { [info exists keys(-pins)] } { - set ports [get_pins_error "pins" $keys(-pins)] + set pins [get_pins $keys(-pins)] foreach pin $pins { if { [get_property $pin "direction"] == "input" } { set_power_pin_activity $pin $activity $duty diff --git a/tcl/StaTcl.i b/tcl/StaTcl.i index 65cd5cce..0e4c5f31 100644 --- a/tcl/StaTcl.i +++ b/tcl/StaTcl.i @@ -4830,8 +4830,8 @@ set_power_pin_activity(const Pin *pin, float activity, float duty) { - return Sta::sta()->power()->setPinActivity(pin, activity, duty, - PwrActivityOrigin::user); + return Sta::sta()->power()->setUserActivity(pin, activity, duty, + PwrActivityOrigin::user); } ////////////////////////////////////////////////////////////////