| ///////////////////////////////////////////////////////////////////////////// |
| // |
| // Copyright (c) 2019, The Regents of the University of California |
| // All rights reserved. |
| // |
| // BSD 3-Clause License |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are met: |
| // |
| // * Redistributions of source code must retain the above copyright notice, this |
| // list of conditions and the following disclaimer. |
| // |
| // * Redistributions in binary form must reproduce the above copyright notice, |
| // this list of conditions and the following disclaimer in the documentation |
| // and/or other materials provided with the distribution. |
| // |
| // * Neither the name of the copyright holder nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
| // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| // POSSIBILITY OF SUCH DAMAGE. |
| // |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| #include "rsz/Resizer.hh" |
| |
| #include "rsz/SteinerTree.hh" |
| |
| #include "gui/gui.h" |
| #include "utl/Logger.h" |
| |
| #include "sta/Report.hh" |
| #include "sta/FuncExpr.hh" |
| #include "sta/PortDirection.hh" |
| #include "sta/TimingRole.hh" |
| #include "sta/Units.hh" |
| #include "sta/Liberty.hh" |
| #include "sta/TimingArc.hh" |
| #include "sta/TimingModel.hh" |
| #include "sta/Network.hh" |
| #include "sta/Graph.hh" |
| #include "sta/DcalcAnalysisPt.hh" |
| #include "sta/ArcDelayCalc.hh" |
| #include "sta/GraphDelayCalc.hh" |
| #include "sta/Parasitics.hh" |
| #include "sta/Sdc.hh" |
| #include "sta/InputDrive.hh" |
| #include "sta/Corner.hh" |
| #include "sta/PathVertex.hh" |
| #include "sta/SearchPred.hh" |
| #include "sta/Bfs.hh" |
| #include "sta/Search.hh" |
| #include "sta/PathRef.hh" |
| #include "sta/PathExpanded.hh" |
| #include "sta/StaMain.hh" |
| #include "sta/Fuzzy.hh" |
| |
| // http://vlsicad.eecs.umich.edu/BK/Slots/cache/dropzone.tamu.edu/~zhuoli/GSRC/fast_buffer_insertion.html |
| |
| namespace sta { |
| extern const char *rsz_tcl_inits[]; |
| } |
| |
| namespace rsz { |
| |
| using std::abs; |
| using std::min; |
| using std::max; |
| using std::string; |
| using std::to_string; |
| using std::vector; |
| using std::map; |
| using std::pair; |
| using std::sqrt; |
| |
| using utl::RSZ; |
| |
| using odb::dbInst; |
| using odb::dbPlacementStatus; |
| using odb::Rect; |
| using odb::dbOrientType; |
| using odb::dbMPin; |
| using odb::dbBox; |
| using odb::dbMasterType; |
| |
| using sta::evalTclInit; |
| using sta::makeBlockSta; |
| using sta::Level; |
| using sta::stringLess; |
| using sta::Network; |
| using sta::NetworkEdit; |
| using sta::NetPinIterator; |
| using sta::NetConnectedPinIterator; |
| using sta::InstancePinIterator; |
| using sta::LeafInstanceIterator; |
| using sta::LibertyLibraryIterator; |
| using sta::LibertyCellIterator; |
| using sta::LibertyCellTimingArcSetIterator; |
| using sta::TimingArcSet; |
| using sta::TimingArcSetArcIterator; |
| using sta::TimingArcSetSeq; |
| using sta::GateTimingModel; |
| using sta::TimingRole; |
| using sta::FuncExpr; |
| using sta::Term; |
| using sta::Port; |
| using sta::PinSeq; |
| using sta::NetIterator; |
| using sta::PinConnectedPinIterator; |
| using sta::FindNetDrvrLoads;; |
| using sta::VertexIterator; |
| using sta::VertexOutEdgeIterator; |
| using sta::Edge; |
| using sta::Search; |
| using sta::SearchPredNonReg2; |
| using sta::ClkArrivalSearchPred; |
| using sta::BfsBkwdIterator; |
| using sta::BfsFwdIterator; |
| using sta::BfsIndex; |
| using sta::Clock; |
| using sta::PathExpanded; |
| using sta::INF; |
| using sta::fuzzyEqual; |
| using sta::fuzzyLess; |
| using sta::fuzzyLessEqual; |
| using sta::fuzzyGreater; |
| using sta::fuzzyGreaterEqual; |
| using sta::delayInf; |
| using sta::stringPrint; |
| using sta::Unit; |
| using sta::ArcDelayCalc; |
| using sta::Corners; |
| using sta::InputDrive; |
| |
| extern "C" { |
| extern int Rsz_Init(Tcl_Interp *interp); |
| } |
| |
| Resizer::Resizer() : |
| StaState(), |
| wire_signal_res_(0.0), |
| wire_signal_cap_(0.0), |
| wire_clk_res_(0.0), |
| wire_clk_cap_(0.0), |
| max_area_(0.0), |
| openroad_(nullptr), |
| logger_(nullptr), |
| gui_(nullptr), |
| sta_(nullptr), |
| db_network_(nullptr), |
| db_(nullptr), |
| block_(nullptr), |
| core_exists_(false), |
| parasitics_src_(ParasiticsSrc::none), |
| design_area_(0.0), |
| max_(MinMax::max()), |
| buffer_lowest_drive_(nullptr), |
| buffer_med_drive_(nullptr), |
| buffer_highest_drive_(nullptr), |
| target_load_map_(nullptr), |
| level_drvr_vertices_valid_(false), |
| tgt_slews_{0.0, 0.0}, |
| tgt_slew_corner_(nullptr), |
| tgt_slew_dcalc_ap_(nullptr), |
| unique_net_index_(1), |
| unique_inst_index_(1), |
| resize_count_(0), |
| inserted_buffer_count_(0), |
| max_wire_length_(0), |
| steiner_renderer_(nullptr), |
| rebuffer_net_count_(0) |
| { |
| } |
| |
| void |
| Resizer::init(OpenRoad *openroad, |
| Tcl_Interp *interp, |
| Logger *logger, |
| Gui *gui, |
| dbDatabase *db, |
| dbSta *sta, |
| SteinerTreeBuilder *stt_builder, |
| GlobalRouter *global_router) |
| { |
| openroad_ = openroad; |
| logger_ = logger; |
| gui_ = gui; |
| db_ = db; |
| block_ = nullptr; |
| sta_ = sta; |
| stt_builder_ = stt_builder; |
| global_router_ = global_router; |
| incr_groute_ = nullptr; |
| db_network_ = sta->getDbNetwork(); |
| copyState(sta); |
| // Define swig TCL commands. |
| Rsz_Init(interp); |
| // Eval encoded sta TCL sources. |
| evalTclInit(interp, sta::rsz_tcl_inits); |
| } |
| |
| //////////////////////////////////////////////////////////////// |
| |
| double |
| Resizer::coreArea() const |
| { |
| return dbuToMeters(core_.dx()) * dbuToMeters(core_.dy()); |
| } |
| |
| double |
| Resizer::utilization() |
| { |
| ensureBlock(); |
| ensureDesignArea(); |
| double core_area = coreArea(); |
| if (core_area > 0.0) |
| return design_area_ / core_area; |
| else |
| return 1.0; |
| } |
| |
| double |
| Resizer::maxArea() const |
| { |
| return max_area_; |
| } |
| |
| //////////////////////////////////////////////////////////////// |
| |
| class VertexLevelLess |
| { |
| public: |
| VertexLevelLess(const Network *network); |
| bool operator()(const Vertex *vertex1, |
| const Vertex *vertex2) const; |
| |
| protected: |
| const Network *network_; |
| }; |
| |
| VertexLevelLess::VertexLevelLess(const Network *network) : |
| network_(network) |
| { |
| } |
| |
| bool |
| VertexLevelLess::operator()(const Vertex *vertex1, |
| const Vertex *vertex2) const |
| { |
| Level level1 = vertex1->level(); |
| Level level2 = vertex2->level(); |
| return (level1 < level2) |
| || (level1 == level2 |
| // Break ties for stable results. |
| && stringLess(network_->pathName(vertex1->pin()), |
| network_->pathName(vertex2->pin()))); |
| } |
| |
| |
| //////////////////////////////////////////////////////////////// |
| |
| // block_ indicates core_, design_area_, db_network_ etc valid. |
| void |
| Resizer::ensureBlock() |
| { |
| // block_ indicates core_, design_area_ |
| if (block_ == nullptr) { |
| block_ = db_->getChip()->getBlock(); |
| block_->getCoreArea(core_); |
| core_exists_ = !(core_.xMin() == 0 |
| && core_.xMax() == 0 |
| && core_.yMin() == 0 |
| && core_.yMax() == 0); |
| } |
| } |
| |
| void |
| Resizer::init() |
| { |
| // Abbreviated copyState |
| db_network_ = sta_->getDbNetwork(); |
| sta_->ensureLevelized(); |
| graph_ = sta_->graph(); |
| ensureBlock(); |
| ensureDesignArea(); |
| ensureLevelDrvrVertices(); |
| sta_->ensureClkNetwork(); |
| } |
| |
| void |
| Resizer::removeBuffers() |
| { |
| ensureBlock(); |
| db_network_ = sta_->getDbNetwork(); |
| // Disable incremental timing. |
| graph_delay_calc_->delaysInvalid(); |
| search_->arrivalsInvalid(); |
| |
| int remove_count = 0; |
| for (dbInst *inst : block_->getInsts()) { |
| LibertyCell *lib_cell = db_network_->libertyCell(inst); |
| if (lib_cell && lib_cell->isBuffer()) { |
| Instance *buffer = db_network_->dbToSta(inst); |
| // Do not remove buffers connected to input/output ports |
| // because verilog netlists use the net name for the port. |
| if (!bufferBetweenPorts(buffer)) { |
| removeBuffer(buffer); |
| remove_count++; |
| } |
| } |
| } |
| level_drvr_vertices_valid_ = false; |
| logger_->info(RSZ, 26, "Removed {} buffers.", remove_count); |
| } |
| |
| bool |
| Resizer::bufferBetweenPorts(Instance *buffer) |
| { |
| LibertyCell *lib_cell = network_->libertyCell(buffer); |
| LibertyPort *in_port, *out_port; |
| lib_cell->bufferPorts(in_port, out_port); |
| Pin *in_pin = db_network_->findPin(buffer, in_port); |
| Pin *out_pin = db_network_->findPin(buffer, out_port); |
| Net *in_net = db_network_->net(in_pin); |
| Net *out_net = db_network_->net(out_pin); |
| bool in_net_ports = hasPort(in_net); |
| bool out_net_ports = hasPort(out_net); |
| return in_net_ports && out_net_ports; |
| } |
| |
| void |
| Resizer::removeBuffer(Instance *buffer) |
| { |
| LibertyCell *lib_cell = network_->libertyCell(buffer); |
| LibertyPort *in_port, *out_port; |
| lib_cell->bufferPorts(in_port, out_port); |
| Pin *in_pin = db_network_->findPin(buffer, in_port); |
| Pin *out_pin = db_network_->findPin(buffer, out_port); |
| Net *in_net = db_network_->net(in_pin); |
| Net *out_net = db_network_->net(out_pin); |
| bool out_net_ports = hasPort(out_net); |
| Net *survivor, *removed; |
| if (out_net_ports) { |
| survivor = out_net; |
| removed = in_net; |
| } |
| else { |
| // default or out_net_ports |
| // Default to in_net surviving so drivers (cached in dbNetwork) |
| // do not change. |
| survivor = in_net; |
| removed = out_net; |
| } |
| |
| if (!sdc_->isConstrained(in_pin) |
| && !sdc_->isConstrained(out_pin) |
| && !sdc_->isConstrained(removed) |
| && !sdc_->isConstrained(buffer)) { |
| sta_->disconnectPin(in_pin); |
| sta_->disconnectPin(out_pin); |
| sta_->deleteInstance(buffer); |
| |
| NetPinIterator *pin_iter = db_network_->pinIterator(removed); |
| while (pin_iter->hasNext()) { |
| Pin *pin = pin_iter->next(); |
| Instance *pin_inst = db_network_->instance(pin); |
| if (pin_inst != buffer) { |
| Port *pin_port = db_network_->port(pin); |
| sta_->disconnectPin(pin); |
| sta_->connectPin(pin_inst, pin_port, survivor); |
| } |
| } |
| delete pin_iter; |
| sta_->deleteNet(removed); |
| parasitics_invalid_.erase(removed); |
| } |
| } |
| |
| void |
| Resizer::ensureLevelDrvrVertices() |
| { |
| if (!level_drvr_vertices_valid_) { |
| level_drvr_vertices_.clear(); |
| VertexIterator vertex_iter(graph_); |
| while (vertex_iter.hasNext()) { |
| Vertex *vertex = vertex_iter.next(); |
| if (vertex->isDriver(network_)) |
| level_drvr_vertices_.push_back(vertex); |
| } |
| sort(level_drvr_vertices_, VertexLevelLess(network_)); |
| level_drvr_vertices_valid_ = true; |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////// |
| |
| void |
| Resizer::resizePreamble() |
| { |
| init(); |
| makeEquivCells(); |
| findBuffers(); |
| findTargetLoads(); |
| } |
| |
| static float |
| bufferDrive(const LibertyCell *buffer) |
| { |
| LibertyPort *input, *output; |
| buffer->bufferPorts(input, output); |
| return output->driveResistance(); |
| } |
| |
| void |
| Resizer::findBuffers() |
| { |
| LibertyLibraryIterator *lib_iter = network_->libertyLibraryIterator(); |
| while (lib_iter->hasNext()) { |
| LibertyLibrary *lib = lib_iter->next(); |
| for (LibertyCell *buffer : *lib->buffers()) { |
| if (!dontUse(buffer) |
| && isLinkCell(buffer)) { |
| buffer_cells_.push_back(buffer); |
| } |
| } |
| } |
| delete lib_iter; |
| |
| if (buffer_cells_.empty()) |
| logger_->error(RSZ, 22, "no buffers found."); |
| |
| sort(buffer_cells_, [] (const LibertyCell *buffer1, |
| const LibertyCell *buffer2) { |
| return bufferDrive(buffer1) > bufferDrive(buffer2); |
| }); |
| buffer_lowest_drive_ = buffer_cells_[0]; |
| buffer_med_drive_ = buffer_cells_[buffer_cells_.size() / 2]; |
| buffer_highest_drive_ = buffer_cells_[buffer_cells_.size() - 1]; |
| } |
| |
| bool |
| Resizer::isLinkCell(LibertyCell *cell) |
| { |
| return network_->findLibertyCell(cell->name()) == cell; |
| } |
| |
| //////////////////////////////////////////////////////////////// |
| |
| void |
| Resizer::bufferInputs() |
| { |
| init(); |
| inserted_buffer_count_ = 0; |
| incrementalParasiticsBegin(); |
| InstancePinIterator *port_iter = network_->pinIterator(network_->topInstance()); |
| while (port_iter->hasNext()) { |
| Pin *pin = port_iter->next(); |
| Vertex *vertex = graph_->pinDrvrVertex(pin); |
| Net *net = network_->net(network_->term(pin)); |
| if (network_->direction(pin)->isInput() |
| && !vertex->isConstant() |
| && !sta_->isClock(pin) |
| // Hands off special nets. |
| && !db_network_->isSpecial(net) |
| && hasPins(net)) |
| // repair_design will resize to target slew. |
| bufferInput(pin, buffer_lowest_drive_); |
| } |
| delete port_iter; |
| updateParasitics(); |
| incrementalParasiticsEnd(); |
| |
| if (inserted_buffer_count_ > 0) { |
| logger_->info(RSZ, 27, "Inserted {} input buffers.", inserted_buffer_count_); |
| level_drvr_vertices_valid_ = false; |
| } |
| } |
| |
| bool |
| Resizer::hasPins(Net *net) |
| { |
| NetPinIterator *pin_iter = db_network_->pinIterator(net); |
| bool has_pins = pin_iter->hasNext(); |
| delete pin_iter; |
| return has_pins; |
| } |
| |
| Instance * |
| Resizer::bufferInput(const Pin *top_pin, |
| LibertyCell *buffer_cell) |
| { |
| Term *term = db_network_->term(top_pin); |
| Net *input_net = db_network_->net(term); |
| LibertyPort *input, *output; |
| buffer_cell->bufferPorts(input, output); |
| string buffer_name = makeUniqueInstName("input"); |
| Instance *parent = db_network_->topInstance(); |
| Net *buffer_out = makeUniqueNet(); |
| Instance *buffer = makeInstance(buffer_cell, |
| buffer_name.c_str(), |
| parent); |
| if (buffer) { |
| journalMakeBuffer(buffer); |
| Point pin_loc = db_network_->location(top_pin); |
| Point buf_loc = core_exists_ ? core_.closestPtInside(pin_loc) : pin_loc; |
| setLocation(buffer, buf_loc); |
| designAreaIncr(area(db_network_->cell(buffer_cell))); |
| inserted_buffer_count_++; |
| |
| NetPinIterator *pin_iter = db_network_->pinIterator(input_net); |
| while (pin_iter->hasNext()) { |
| Pin *pin = pin_iter->next(); |
| // Leave input port pin connected to input_net. |
| if (pin != top_pin) { |
| sta_->disconnectPin(pin); |
| Port *pin_port = db_network_->port(pin); |
| sta_->connectPin(db_network_->instance(pin), pin_port, buffer_out); |
| } |
| } |
| delete pin_iter; |
| sta_->connectPin(buffer, input, input_net); |
| sta_->connectPin(buffer, output, buffer_out); |
| |
| parasiticsInvalid(input_net); |
| parasiticsInvalid(buffer_out); |
| } |
| return buffer; |
| } |
| |
| void |
| Resizer::setLocation(Instance *inst, |
| Point pt) |
| { |
| // Stay inside the lines. |
| if (core_exists_) |
| pt = core_.closestPtInside(pt); |
| |
| dbInst *dinst = db_network_->staToDb(inst); |
| dinst->setPlacementStatus(dbPlacementStatus::PLACED); |
| dinst->setLocation(pt.getX(), pt.getY()); |
| } |
| |
| void |
| Resizer::bufferOutputs() |
| { |
| init(); |
| inserted_buffer_count_ = 0; |
| incrementalParasiticsBegin(); |
| InstancePinIterator *port_iter = network_->pinIterator(network_->topInstance()); |
| while (port_iter->hasNext()) { |
| Pin *pin = port_iter->next(); |
| Vertex *vertex = graph_->pinLoadVertex(pin); |
| Net *net = network_->net(network_->term(pin)); |
| if (network_->direction(pin)->isOutput() |
| && net |
| // DEF does not have tristate output types so we have look at the drivers. |
| && !hasTristateDriver(net) |
| && !vertex->isConstant() |
| // Hands off special nets. |
| && !db_network_->isSpecial(net) |
| && hasPins(net)) |
| bufferOutput(pin, buffer_lowest_drive_); |
| } |
| delete port_iter; |
| updateParasitics(); |
| incrementalParasiticsEnd(); |
| |
| if (inserted_buffer_count_ > 0) { |
| logger_->info(RSZ, 28, "Inserted {} output buffers.", inserted_buffer_count_); |
| level_drvr_vertices_valid_ = false; |
| } |
| } |
| |
| bool |
| Resizer::hasTristateDriver(const Net *net) |
| { |
| PinSet *drivers = network_->drivers(net); |
| if (drivers) { |
| for (Pin *pin : *drivers) { |
| if (isTristateDriver(pin)) |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool |
| Resizer::isTristateDriver(const Pin *pin) |
| { |
| // Note LEF macro PINs do not have a clue about tristates. |
| LibertyPort *port = network_->libertyPort(pin); |
| return port && port->direction()->isAnyTristate(); |
| } |
| |
| void |
| Resizer::bufferOutput(Pin *top_pin, |
| LibertyCell *buffer_cell) |
| { |
| NetworkEdit *network = networkEdit(); |
| Term *term = network_->term(top_pin); |
| Net *output_net = network_->net(term); |
| LibertyPort *input, *output; |
| buffer_cell->bufferPorts(input, output); |
| string buffer_name = makeUniqueInstName("output"); |
| Instance *parent = network->topInstance(); |
| Net *buffer_in = makeUniqueNet(); |
| Instance *buffer = makeInstance(buffer_cell, |
| buffer_name.c_str(), |
| parent); |
| if (buffer) { |
| journalMakeBuffer(buffer); |
| setLocation(buffer, db_network_->location(top_pin)); |
| designAreaIncr(area(db_network_->cell(buffer_cell))); |
| inserted_buffer_count_++; |
| |
| NetPinIterator *pin_iter = network->pinIterator(output_net); |
| while (pin_iter->hasNext()) { |
| Pin *pin = pin_iter->next(); |
| if (pin != top_pin) { |
| // Leave output port pin connected to output_net. |
| sta_->disconnectPin(pin); |
| Port *pin_port = network->port(pin); |
| sta_->connectPin(network->instance(pin), pin_port, buffer_in); |
| } |
| } |
| delete pin_iter; |
| sta_->connectPin(buffer, input, buffer_in); |
| sta_->connectPin(buffer, output, output_net); |
| |
| parasiticsInvalid(buffer_in); |
| parasiticsInvalid(output_net); |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////// |
| |
| // Repair long wires, max slew, max capacitance, max fanout violations |
| // The whole enchilada. |
| // max_wire_length zero for none (meters) |
| void |
| Resizer::repairDesign(double max_wire_length, |
| double slew_margin, |
| double max_cap_margin) |
| { |
| int repair_count, slew_violations, cap_violations; |
| int fanout_violations, length_violations; |
| repairDesign(max_wire_length, slew_margin, max_cap_margin, |
| repair_count, slew_violations, cap_violations, |
| fanout_violations, length_violations); |
| |
| if (slew_violations > 0) |
| logger_->info(RSZ, 34, "Found {} slew violations.", slew_violations); |
| if (fanout_violations > 0) |
| logger_->info(RSZ, 35, "Found {} fanout violations.", fanout_violations); |
| if (cap_violations > 0) |
| logger_->info(RSZ, 36, "Found {} capacitance violations.", cap_violations); |
| if (length_violations > 0) |
| logger_->info(RSZ, 37, "Found {} long wires.", length_violations); |
| if (inserted_buffer_count_ > 0) |
| logger_->info(RSZ, 38, "Inserted {} buffers in {} nets.", |
| inserted_buffer_count_, |
| repair_count); |
| if (resize_count_ > 0) |
| logger_->info(RSZ, 39, "Resized {} instances.", resize_count_); |
| } |
| |
| void |
| Resizer::repairDesign(double max_wire_length, // zero for none (meters) |
| double slew_margin, |
| double max_cap_margin, |
| int &repair_count, |
| int &slew_violations, |
| int &cap_violations, |
| int &fanout_violations, |
| int &length_violations) |
| { |
| repair_count = 0; |
| slew_violations = 0; |
| cap_violations = 0; |
| fanout_violations = 0; |
| length_violations = 0; |
| inserted_buffer_count_ = 0; |
| resize_count_ = 0; |
| |
| sta_->checkSlewLimitPreamble(); |
| sta_->checkCapacitanceLimitPreamble(); |
| sta_->checkFanoutLimitPreamble(); |
| |
| incrementalParasiticsBegin(); |
| int max_length = metersToDbu(max_wire_length); |
| for (int i = level_drvr_vertices_.size() - 1; i >= 0; i--) { |
| Vertex *drvr = level_drvr_vertices_[i]; |
| Pin *drvr_pin = drvr->pin(); |
| Net *net = network_->isTopLevelPort(drvr_pin) |
| ? network_->net(network_->term(drvr_pin)) |
| : network_->net(drvr_pin); |
| if (net |
| && !sta_->isClock(drvr_pin) |
| // Exclude tie hi/low cells and supply nets. |
| && !drvr->isConstant()) |
| repairNet(net, drvr_pin, drvr, slew_margin, max_cap_margin, |
| true, true, true, max_length, true, |
| repair_count, slew_violations, cap_violations, |
| fanout_violations, length_violations); |
| } |
| updateParasitics(); |
| incrementalParasiticsEnd(); |
| |
| if (inserted_buffer_count_ > 0) |
| level_drvr_vertices_valid_ = false; |
| } |
| |
| // repairDesign but restricted to clock network and |
| // no max_fanout/max_cap checks. |
| void |
| Resizer::repairClkNets(double max_wire_length) // max_wire_length zero for none (meters) |
| { |
| init(); |
| // Need slews to resize inserted buffers. |
| sta_->findDelays(); |
| |
| inserted_buffer_count_ = 0; |
| resize_count_ = 0; |
| |
| int repair_count = 0; |
| int slew_violations = 0; |
| int cap_violations = 0; |
| int fanout_violations = 0; |
| int length_violations = 0; |
| int max_length = metersToDbu(max_wire_length); |
| incrementalParasiticsBegin(); |
| for (Clock *clk : sdc_->clks()) { |
| for (const Pin *clk_pin : *sta_->pins(clk)) { |
| Net *net = network_->isTopLevelPort(clk_pin) |
| ? network_->net(network_->term(clk_pin)) |
| : network_->net(clk_pin); |
| if (network_->isDriver(clk_pin)) { |
| Vertex *drvr = graph_->pinDrvrVertex(clk_pin); |
| // Do not resize clock tree gates. |
| repairNet(net, clk_pin, drvr, 0.0, 0.0, |
| false, false, false, max_length, false, |
| repair_count, slew_violations, cap_violations, |
| fanout_violations, length_violations); |
| } |
| } |
| } |
| updateParasitics(); |
| incrementalParasiticsEnd(); |
| |
| if (length_violations > 0) |
| logger_->info(RSZ, 47, "Found {} long wires.", length_violations); |
| if (inserted_buffer_count_ > 0) { |
| logger_->info(RSZ, 48, "Inserted {} buffers in {} nets.", |
| inserted_buffer_count_, |
| repair_count); |
| level_drvr_vertices_valid_ = false; |
| } |
| } |
| |
| // Repair one net (for debugging) |
| void |
| Resizer::repairNet(Net *net, |
| double max_wire_length, // meters |
| double slew_margin, |
| double max_cap_margin) |
| { |
| init(); |
| |
| sta_->checkSlewLimitPreamble(); |
| sta_->checkCapacitanceLimitPreamble(); |
| sta_->checkFanoutLimitPreamble(); |
| |
| inserted_buffer_count_ = 0; |
| resize_count_ = 0; |
| resized_multi_output_insts_.clear(); |
| int repair_count = 0; |
| int slew_violations = 0; |
| int cap_violations = 0; |
| int fanout_violations = 0; |
| int length_violations = 0; |
| int max_length = metersToDbu(max_wire_length); |
| PinSet *drivers = network_->drivers(net); |
| if (drivers && !drivers->empty()) { |
| PinSet::Iterator drvr_iter(drivers); |
| Pin *drvr_pin = drvr_iter.next(); |
| Vertex *drvr = graph_->pinDrvrVertex(drvr_pin); |
| repairNet(net, drvr_pin, drvr, slew_margin, max_cap_margin, |
| true, true, true, max_length, true, |
| repair_count, slew_violations, cap_violations, |
| fanout_violations, length_violations); |
| } |
| |
| if (slew_violations > 0) |
| logger_->info(RSZ, 51, "Found {} slew violations.", slew_violations); |
| if (fanout_violations > 0) |
| logger_->info(RSZ, 52, "Found {} fanout violations.", fanout_violations); |
| if (cap_violations > 0) |
| logger_->info(RSZ, 53, "Found {} capacitance violations.", cap_violations); |
| if (length_violations > 0) |
| logger_->info(RSZ, 54, "Found {} long wires.", length_violations); |
| if (inserted_buffer_count_ > 0) { |
| logger_->info(RSZ, 55, "Inserted {} buffers in {} nets.", |
| inserted_buffer_count_, |
| repair_count); |
| level_drvr_vertices_valid_ = false; |
| } |
| if (resize_count_ > 0) |
| logger_->info(RSZ, 56, "Resized {} instances.", resize_count_); |
| if (resize_count_ > 0) |
| logger_->info(RSZ, 57, "Resized {} instances.", resize_count_); |
| } |
| |
| void |
| Resizer::repairNet(Net *net, |
| const Pin *drvr_pin, |
| Vertex *drvr, |
| double slew_margin, |
| double max_cap_margin, |
| bool check_slew, |
| bool check_cap, |
| bool check_fanout, |
| int max_length, // dbu |
| bool resize_drvr, |
| int &repair_count, |
| int &slew_violations, |
| int &cap_violations, |
| int &fanout_violations, |
| int &length_violations) |
| { |
| // Hands off special nets. |
| if (!db_network_->isSpecial(net)) { |
| SteinerTree *tree = makeSteinerTree(drvr_pin, true, max_steiner_pin_count_, |
| stt_builder_, db_network_, logger_); |
| if (tree) { |
| debugPrint(logger_, RSZ, "repair_net", 1, "repair net {}", |
| sdc_network_->pathName(drvr_pin)); |
| // Resize the driver to normalize slews before repairing limit violations. |
| if (resize_drvr) |
| resizeToTargetSlew(drvr_pin, true); |
| // For tristate nets all we can do is resize the driver. |
| if (!isTristateDriver(drvr_pin)) { |
| ensureWireParasitic(drvr_pin, net); |
| graph_delay_calc_->findDelays(drvr); |
| |
| float max_load_slew = INF; |
| float max_cap = INF; |
| float max_fanout = INF; |
| bool repair_slew = false; |
| bool repair_cap = false; |
| bool repair_fanout = false; |
| bool repair_wire = false; |
| const Corner *corner = sta_->cmdCorner(); |
| if (check_cap) { |
| float cap1, max_cap1, cap_slack1; |
| const Corner *corner1; |
| const RiseFall *tr1; |
| sta_->checkCapacitance(drvr_pin, nullptr, max_, |
| corner1, tr1, cap1, max_cap1, cap_slack1); |
| if (max_cap1 > 0.0 && corner1) { |
| max_cap1 *= (1.0 - max_cap_margin / 100.0); |
| max_cap = max_cap1; |
| if (cap1 > max_cap1) { |
| corner = corner1; |
| cap_violations++; |
| repair_cap = true; |
| } |
| } |
| } |
| if (check_fanout) { |
| float fanout, fanout_slack; |
| sta_->checkFanout(drvr_pin, max_, |
| fanout, max_fanout, fanout_slack); |
| if (max_fanout > 0.0 && fanout_slack < 0.0) { |
| fanout_violations++; |
| repair_fanout = true; |
| } |
| } |
| int wire_length = findMaxSteinerDist(tree); |
| if (max_length |
| && wire_length > max_length) { |
| length_violations++; |
| repair_wire = true; |
| } |
| if (check_slew) { |
| float slew1, slew_slack1, max_slew1; |
| const Corner *corner1; |
| // Check slew at the driver. |
| checkSlew(drvr_pin, slew_margin, slew1, max_slew1, slew_slack1, corner1); |
| // Max slew violations at the driver pin are repaired by reducing the |
| // load capacitance. Wire resistance may shield capacitance from the |
| // driver but so this is conservative. |
| // Find max load cap that corresponds to max_slew. |
| LibertyPort *drvr_port = network_->libertyPort(drvr_pin); |
| if (corner1 |
| && max_slew1 > 0.0 && drvr_port) { |
| float max_cap1 = findSlewLoadCap(drvr_port, max_slew1, corner1); |
| max_cap = min(max_cap, max_cap1); |
| corner = corner1; |
| debugPrint(logger_, RSZ, "repair_net", 2, "drvr_slew={} max_slew={} max_cap={} corner={}", |
| delayAsString(slew1, this, 3), |
| delayAsString(max_slew1, this, 3), |
| units_->capacitanceUnit()->asString(max_cap1, 3), |
| corner1->name()); |
| if (slew_slack1 < 0.0) |
| repair_slew = true; |
| } |
| if (slew_slack1 < 0.0) |
| slew_violations++; |
| |
| // Check slew at the loads. |
| // Note that many liberty libraries do not have max_transition attributes on |
| // input pins. |
| // Max slew violations at the load pins are repaired by reducing the |
| // wire length. |
| checkLoadSlews(drvr_pin, slew_margin, slew1, max_slew1, slew_slack1, corner1); |
| // Even when there are no load violations we need max_load_slew for |
| // sizing inserted buffers. |
| if (max_slew1 > 0.0) { |
| max_load_slew = max_slew1; |
| debugPrint(logger_, RSZ, "repair_net", 2, "load_slew={} max_load_slew={}", |
| delayAsString(slew1, this, 3), |
| delayAsString(max_load_slew, this, 3)); |
| if (slew_slack1 < 0.0) { |
| // Don't double count violations on the same net. |
| if (!repair_slew) |
| slew_violations++; |
| corner = corner1; |
| repair_slew = true; |
| } |
| } |
| } |
| |
| if (repair_slew |
| || repair_cap |
| || repair_fanout |
| || repair_wire) { |
| Point drvr_loc = db_network_->location(drvr->pin()); |
| debugPrint(logger_, RSZ, "repair_net", 1, "driver {} ({} {}) l={}", |
| sdc_network_->pathName(drvr_pin), |
| units_->distanceUnit()->asString(dbuToMeters(drvr_loc.getX()), 1), |
| units_->distanceUnit()->asString(dbuToMeters(drvr_loc.getY()), 1), |
| units_->distanceUnit()->asString(dbuToMeters(wire_length), 1)); |
| SteinerPt drvr_pt = tree->drvrPt(network_); |
| int wire_length; |
| float pin_cap, fanout; |
| PinSeq load_pins; |
| if (drvr_pt != SteinerTree::null_pt) |
| repairNet(tree, drvr_pt, SteinerTree::null_pt, net, drvr_pin, |
| max_load_slew, max_cap, max_fanout, max_length, corner, 0, |
| wire_length, pin_cap, fanout, load_pins); |
| repair_count++; |
| |
| if (resize_drvr) |
| resizeToTargetSlew(drvr_pin, true); |
| } |
| } |
| delete tree; |
| } |
| } |
| } |
| |
| bool |
| Resizer::checkLimits(const Pin *drvr_pin, |
| double slew_margin, |
| double max_cap_margin, |
| bool check_slew, |
| bool check_cap, |
| bool check_fanout) |
| { |
| if (check_cap) { |
| float cap1, max_cap1, cap_slack1; |
| const Corner *corner1; |
| const RiseFall *tr1; |
| sta_->checkCapacitance(drvr_pin, nullptr, max_, |
| corner1, tr1, cap1, max_cap1, cap_slack1); |
| max_cap1 *= (1.0 - max_cap_margin / 100.0); |
| if (cap1 < max_cap1) |
| return true; |
| } |
| if (check_fanout) { |
| float fanout, fanout_slack, max_fanout; |
| sta_->checkFanout(drvr_pin, max_, |
| fanout, max_fanout, fanout_slack); |
| if (fanout_slack < 0.0) |
| return true; |
| |
| } |
| if (check_slew) { |
| float slew1, slew_slack1, max_slew1; |
| const Corner *corner1; |
| checkSlew(drvr_pin, slew_margin, slew1, max_slew1, slew_slack1, corner1); |
| if (slew_slack1 < 0.0) |
| return true; |
| checkLoadSlews(drvr_pin, slew_margin, slew1, max_slew1, slew_slack1, corner1); |
| if (slew_slack1 < 0.0) |
| return true; |
| } |
| return false; |
| } |
| |
| void |
| Resizer::checkSlew(const Pin *drvr_pin, |
| double slew_margin, |
| // Return values. |
| Slew &slew, |
| float &limit, |
| float &slack, |
| const Corner *&corner) |
| { |
| slack = INF; |
| limit = INF; |
| corner = nullptr; |
| |
| const Corner *corner1; |
| const RiseFall *tr1; |
| Slew slew1; |
| float limit1, slack1; |
| sta_->checkSlew(drvr_pin, nullptr, max_, false, |
| corner1, tr1, slew1, limit1, slack1); |
| if (corner1) { |
| limit1 *= (1.0 - slew_margin / 100.0); |
| slack1 = limit1 - slew1; |
| if (slack1 < slack) { |
| slew = slew1; |
| limit = limit1; |
| slack = slack1; |
| corner = corner1; |
| } |
| } |
| } |
| |
| void |
| Resizer::checkLoadSlews(const Pin *drvr_pin, |
| double slew_margin, |
| // Return values. |
| Slew &slew, |
| float &limit, |
| float &slack, |
| const Corner *&corner) |
| { |
| slack = INF; |
| limit = INF; |
| PinConnectedPinIterator *pin_iter = network_->connectedPinIterator(drvr_pin); |
| while (pin_iter->hasNext()) { |
| Pin *pin = pin_iter->next(); |
| if (pin != drvr_pin) { |
| const Corner *corner1; |
| const RiseFall *tr1; |
| Slew slew1; |
| float limit1, slack1; |
| sta_->checkSlew(pin, nullptr, max_, false, |
| corner1, tr1, slew1, limit1, slack1); |
| if (corner1) { |
| limit1 *= (1.0 - slew_margin / 100.0); |
| limit = min(limit, limit1); |
| slack1 = limit1 - slew1; |
| if (slack1 < slack) { |
| slew = slew1; |
| slack = slack1; |
| corner = corner1; |
| } |
| } |
| } |
| } |
| delete pin_iter; |
| } |
| |
| // Find the output port load capacitance that results in slew. |
| double |
| Resizer::findSlewLoadCap(LibertyPort *drvr_port, |
| double slew, |
| const Corner *corner) |
| { |
| const DcalcAnalysisPt *dcalc_ap = corner->findDcalcAnalysisPt(max_); |
| // cap1 lower bound |
| // cap2 upper bound |
| double cap1 = 0.0; |
| double cap2 = slew / drvr_port->driveResistance() * 2; |
| double tol = .01; // 1% |
| double diff1 = gateSlewDiff(drvr_port, cap2, slew, dcalc_ap); |
| // binary search for diff = 0. |
| while (abs(cap1 - cap2) > max(cap1, cap2) * tol) { |
| if (diff1 < 0.0) { |
| cap1 = cap2; |
| cap2 *= 2; |
| diff1 = gateSlewDiff(drvr_port, cap2, slew, dcalc_ap); |
| } |
| else { |
| double cap3 = (cap1 + cap2) / 2.0; |
| double diff2 = gateSlewDiff(drvr_port, cap3, slew, dcalc_ap); |
| if (diff2 < 0.0) { |
| cap1 = cap3; |
| } |
| else { |
| cap2 = cap3; |
| diff1 = diff2; |
| } |
| } |
| } |
| return cap1; |
| } |
| |
| // objective function |
| double |
| Resizer::gateSlewDiff(LibertyPort *drvr_port, |
| double load_cap, |
| double slew, |
| const DcalcAnalysisPt *dcalc_ap) |
| { |
| ArcDelay delays[RiseFall::index_count]; |
| Slew slews[RiseFall::index_count]; |
| gateDelays(drvr_port, load_cap, dcalc_ap, delays, slews); |
| Slew gate_slew = max(slews[RiseFall::riseIndex()], slews[RiseFall::fallIndex()]); |
| return gate_slew - slew; |
| } |
| |
| void |
| Resizer::repairNet(SteinerTree *tree, |
| SteinerPt pt, |
| SteinerPt prev_pt, |
| Net *net, |
| const Pin *drvr_pin, |
| float max_load_slew, |
| float max_cap, |
| float max_fanout, |
| int max_length, // dbu |
| const Corner *corner, |
| int level, |
| // Return values. |
| // Remaining parasiics after repeater insertion. |
| int &wire_length, // dbu |
| float &pin_cap, |
| float &fanout, |
| PinSeq &load_pins) |
| { |
| Point pt_loc = tree->location(pt); |
| int pt_x = pt_loc.getX(); |
| int pt_y = pt_loc.getY(); |
| debugPrint(logger_, RSZ, "repair_net", 2, "{:{}s}pt ({} {})", |
| "", level, |
| units_->distanceUnit()->asString(dbuToMeters(pt_x), 1), |
| units_->distanceUnit()->asString(dbuToMeters(pt_y), 1)); |
| double wire_cap = wireSignalCapacitance(corner); |
| double wire_res = wireSignalResistance(corner); |
| SteinerPt left = tree->left(pt); |
| int wire_length_left = 0; |
| float pin_cap_left = 0.0; |
| float fanout_left = 0.0; |
| PinSeq loads_left; |
| if (left != SteinerTree::null_pt) |
| repairNet(tree, left, pt, net, drvr_pin, max_load_slew, max_cap, max_fanout, max_length, |
| corner, level+1, |
| wire_length_left, pin_cap_left, fanout_left, loads_left); |
| SteinerPt right = tree->right(pt); |
| int wire_length_right = 0; |
| float pin_cap_right = 0.0; |
| float fanout_right = 0.0; |
| PinSeq loads_right; |
| if (right != SteinerTree::null_pt) |
| repairNet(tree, right, pt, net, drvr_pin, max_load_slew, max_cap, max_fanout, max_length, |
| corner, level+1, |
| wire_length_right, pin_cap_right, fanout_right, loads_right); |
| debugPrint(logger_, RSZ, "repair_net", 3, "{:{}s}left l={} pin_cap={} fanout={}, right l={} pin_cap={} fanout={}", |
| "", level, |
| units_->distanceUnit()->asString(dbuToMeters(wire_length_left), 1), |
| units_->capacitanceUnit()->asString(pin_cap_left, 3), |
| fanout_left, |
| units_->distanceUnit()->asString(dbuToMeters(wire_length_right), 1), |
| units_->capacitanceUnit()->asString(pin_cap_right, 3), |
| fanout_right); |
| // Add a buffer to left or right branch to stay under the max cap/length/fanout. |
| bool repeater_left = false; |
| bool repeater_right = false; |
| double cap_left = pin_cap_left + dbuToMeters(wire_length_left) * wire_cap; |
| double cap_right = pin_cap_right + dbuToMeters(wire_length_right) * wire_cap; |
| debugPrint(logger_, RSZ, "repair_net", 3, "{:{}s}cap_left={}, right_cap={}", |
| "", level, |
| units_->capacitanceUnit()->asString(cap_left, 3), |
| units_->capacitanceUnit()->asString(cap_right, 3)); |
| |
| double wire_length1 = dbuToMeters(wire_length_left + wire_length_right); |
| float load_cap = cap_left + cap_right; |
| |
| LibertyCell *buffer_cell = findTargetCell(buffer_lowest_drive_, load_cap, false); |
| LibertyPort *input, *buffer_output; |
| buffer_cell->bufferPorts(input, buffer_output); |
| float r_buffer = buffer_output->driveResistance(); |
| float r_drv = driveResistance(drvr_pin); |
| float r_drvr = max(r_drv, r_buffer); |
| // Elmore factor for 20-80% slew thresholds. |
| float k_threshold = 1.39; |
| Slew load_slew = (r_drvr + wire_length1 * wire_res) * load_cap * k_threshold; |
| debugPrint(logger_, RSZ, "repair_net", 3, "{:{}s}load_slew={} r_drvr={} r_buffer={}", |
| "", level, |
| delayAsString(load_slew, this, 3), |
| units_->resistanceUnit()->asString(r_drv, 3), |
| units_->resistanceUnit()->asString(r_buffer, 3)); |
| |
| bool slew_violation = load_slew > max_load_slew; |
| if (slew_violation) { |
| debugPrint(logger_, RSZ, "repair_net", 3, "{:{}s}slew violation", "", level); |
| if (cap_left > cap_right) |
| repeater_left = true; |
| else |
| repeater_right = true; |
| } |
| |
| bool cap_violation = (cap_left + cap_right) > max_cap; |
| if (cap_violation) { |
| debugPrint(logger_, RSZ, "repair_net", 3, "{:{}s}cap violation", "", level); |
| if (cap_left > cap_right) |
| repeater_left = true; |
| else |
| repeater_right = true; |
| } |
| bool length_violation = max_length > 0 |
| && (wire_length_left + wire_length_right) > max_length; |
| if (length_violation) { |
| debugPrint(logger_, RSZ, "repair_net", 3, "{:{}s}length violation", "", level); |
| if (wire_length_left > wire_length_right) |
| repeater_left = true; |
| else |
| repeater_right = true; |
| } |
| bool fanout_violation = max_fanout > 0 |
| // Note that if both fanout_left==max_fanout and fanout_right==max_fanout |
| // there is no way repair the violation (adding a buffer to either branch |
| // results in max_fanout+1, which is a violation). |
| // Leave room for one buffer on the other branch by using >= to avoid |
| // this situation. |
| && (fanout_left + fanout_right) >= max_fanout; |
| if (fanout_violation) { |
| debugPrint(logger_, RSZ, "repair_net", 3, "{:{}s}fanout violation", "", level); |
| if (fanout_left > fanout_right) |
| repeater_left = true; |
| else |
| repeater_right = true; |
| } |
| |
| if (repeater_left) |
| makeRepeater("left", tree, pt, buffer_cell, level, |
| wire_length_left, pin_cap_left, fanout_left, loads_left); |
| if (repeater_right) |
| makeRepeater("right", tree, pt, buffer_cell, level, |
| wire_length_right, pin_cap_right, fanout_right, loads_right); |
| |
| // Update after left/right repeaters are inserted. |
| wire_length = wire_length_left + wire_length_right; |
| pin_cap = pin_cap_left + pin_cap_right; |
| fanout = fanout_left + fanout_right; |
| |
| // Union left/right load pins. |
| for (Pin *load_pin : loads_left) |
| load_pins.push_back(load_pin); |
| for (Pin *load_pin : loads_right) |
| load_pins.push_back(load_pin); |
| |
| // Steiner pt pin is the net driver if prev_pt is null. |
| if (prev_pt != SteinerTree::null_pt) { |
| const PinSeq *pt_pins = tree->pins(pt); |
| if (pt_pins) { |
| for (Pin *load_pin : *pt_pins) { |
| Point load_loc = db_network_->location(load_pin); |
| int load_dist = Point::manhattanDistance(load_loc, pt_loc); |
| debugPrint(logger_, RSZ, "repair_net", 2, "{:{}s}load {} ({} {}) dist={}", |
| "", level, |
| sdc_network_->pathName(load_pin), |
| units_->distanceUnit()->asString(dbuToMeters(load_loc.getX()), 1), |
| units_->distanceUnit()->asString(dbuToMeters(load_loc.getY()), 1), |
| units_->distanceUnit()->asString(dbuToMeters(load_dist), 1)); |
| LibertyPort *load_port = network_->libertyPort(load_pin); |
| if (load_port) { |
| pin_cap += load_port->capacitance(); |
| fanout += portFanoutLoad(load_port); |
| } |
| else |
| fanout += 1; |
| load_pins.push_back(load_pin); |
| } |
| } |
| |
| Point prev_loc = tree->location(prev_pt); |
| int length = Point::manhattanDistance(prev_loc, pt_loc); |
| wire_length += length; |
| // Back up from pt to prev_pt adding repeaters every max_length. |
| int prev_x = prev_loc.getX(); |
| int prev_y = prev_loc.getY(); |
| debugPrint(logger_, RSZ, "repair_net", 3, "{:{}s}wl={} l={}", |
| "", level, |
| units_->distanceUnit()->asString(dbuToMeters(wire_length), 1), |
| units_->distanceUnit()->asString(dbuToMeters(length), 1)); |
| wire_length1 = dbuToMeters(wire_length); |
| load_cap = pin_cap + wire_length1 * wire_cap; |
| |
| buffer_cell = findTargetCell(buffer_lowest_drive_, load_cap, false); |
| buffer_cell->bufferPorts(input, buffer_output); |
| r_buffer = buffer_output->driveResistance(); |
| r_drv = driveResistance(drvr_pin); |
| r_drvr = max(r_drv, r_buffer); |
| load_slew = (r_drvr + wire_length1 * wire_res) * load_cap * k_threshold; |
| debugPrint(logger_, RSZ, "repair_net", 3, "{:{}s}load_slew={} r_drvr={} r_buffer={}", |
| "", level, |
| delayAsString(load_slew, this, 3), |
| units_->resistanceUnit()->asString(r_drv, 3), |
| units_->resistanceUnit()->asString(r_buffer, 3)); |
| |
| while ((max_length > 0 && wire_length > max_length) |
| || (wire_cap > 0.0 |
| // Cannot fix max cap violations from pin cap by shortening wire. |
| && pin_cap < max_cap |
| && load_cap > max_cap) |
| || load_slew > max_load_slew) { |
| // Make the wire a bit shorter than necessary to allow for |
| // offset from instance origin to pin and detailed placement movement. |
| static double length_margin = .05; |
| bool split_wire = false; |
| int split_length = std::numeric_limits<int>::max(); |
| if (max_length > 0 && wire_length > max_length) { |
| split_length = min(split_length, max_length); |
| split_wire = true; |
| } |
| if (wire_cap > 0.0 |
| && pin_cap < max_cap |
| && load_cap > max_cap) { |
| split_length = min(split_length, metersToDbu((max_cap - pin_cap) / wire_cap)); |
| split_wire = true; |
| } |
| if (load_slew > max_load_slew |
| // Check that zero length wire meets max slew. |
| && r_drvr*pin_cap*k_threshold < max_load_slew) { |
| // Using elmore delay to approximate wire |
| // load_slew = (Rdrvr + L*Rwire) * (L*Cwire + Cpin) * k_threshold |
| // Setting this to max_slew is a quadratic in L |
| // L^2*Rwire*Cwire + L*(Rdrvr*Cwire + Rwire*Cpin) + Rdrvr*Cpin - max_slew/k_threshold |
| // Solve using quadradic eqn for L. |
| float a = wire_res * wire_cap; |
| float b = r_drvr * wire_cap + wire_res * pin_cap; |
| float c = r_drvr * pin_cap - max_load_slew / k_threshold; |
| float l = (-b + sqrt(b*b - 4 * a * c)) / (2 * a); |
| if (l > 0.0) { |
| split_length = min(split_length, metersToDbu(l)); |
| split_wire = true; |
| } |
| } |
| if (split_wire) { |
| // Distance from pt to repeater backward toward prev_pt. |
| double buf_dist = length - (wire_length - split_length * (1.0 - length_margin)); |
| double dx = prev_x - pt_x; |
| double dy = prev_y - pt_y; |
| double d = (length == 0) ? 0.0 : buf_dist / length; |
| int buf_x = pt_x + d * dx; |
| int buf_y = pt_y + d * dy; |
| makeRepeater("wire", buf_x, buf_y, buffer_lowest_drive_, level, |
| wire_length, pin_cap, fanout, load_pins); |
| // Update for the next round. |
| length -= buf_dist; |
| wire_length = length; |
| pt_x = buf_x; |
| pt_y = buf_y; |
| |
| wire_length1 = dbuToMeters(wire_length); |
| load_cap = pin_cap + wire_length1 * wire_cap; |
| load_slew = (r_drvr + wire_length1 * wire_res) * load_cap * k_threshold; |
| debugPrint(logger_, RSZ, "repair_net", 3, "{:{}s}load_slew={}", |
| "", level, |
| delayAsString(load_slew, this, 3)); |
| debugPrint(logger_, RSZ, "repair_net", 3, "{:{}s}wl={} l={}", |
| "", level, |
| units_->distanceUnit()->asString(dbuToMeters(wire_length), 1), |
| units_->distanceUnit()->asString(dbuToMeters(length), 1)); |
| } |
| else |
| break; |
| } |
| } |
| } |
| |
| void |
| Resizer::makeRepeater(const char *where, |
| SteinerTree *tree, |
| SteinerPt pt, |
| LibertyCell *buffer_cell, |
| int level, |
| int &wire_length, |
| float &pin_cap, |
| float &fanout, |
| PinSeq &load_pins) |
| { |
| Point pt_loc = tree->location(pt); |
| makeRepeater(where, pt_loc.getX(), pt_loc.getY(), buffer_cell, level, |
| wire_length, pin_cap, fanout, load_pins); |
| } |
| |
| void |
| Resizer::makeRepeater(const char *where, |
| int x, |
| int y, |
| LibertyCell *buffer_cell, |
| int level, |
| int &wire_length, |
| float &pin_cap, |
| float &fanout, |
| PinSeq &load_pins) |
| { |
| LibertyPort *buffer_input_port, *buffer_output_port; |
| buffer_cell->bufferPorts(buffer_input_port, buffer_output_port); |
| |
| string buffer_name = makeUniqueInstName("repeater"); |
| debugPrint(logger_, RSZ, "repair_net", 2, "{:{}s}{} {} ({} {})", |
| "", level, |
| where, |
| buffer_name.c_str(), |
| units_->distanceUnit()->asString(dbuToMeters(x), 1), |
| units_->distanceUnit()->asString(dbuToMeters(y), 1)); |
| |
| // Inserting a buffer is complicated by the fact that verilog netlists |
| // use the net name for input and output ports. This means the ports |
| // cannot be moved to a different net. |
| |
| // This cannot depend on the net in caller because the buffer may be inserted |
| // between the driver and the loads changing the net as the repair works its |
| // way from the loads to the driver. |
| |
| Net *net = nullptr, *in_net, *out_net; |
| bool have_output_port_load = false; |
| for (Pin *pin : load_pins) { |
| if (network_->isTopLevelPort(pin)) { |
| net = network_->net(network_->term(pin)); |
| if (network_->direction(pin)->isAnyOutput()) { |
| have_output_port_load = true; |
| break; |
| } |
| } |
| else |
| net = network_->net(pin); |
| } |
| Instance *parent = db_network_->topInstance(); |
| |
| // If the net is driven by an input port, |
| // use the net as the repeater input net so the port stays connected to it. |
| if (hasInputPort(net) |
| || !have_output_port_load) { |
| in_net = net; |
| out_net = makeUniqueNet(); |
| // Copy signal type to new net. |
| dbNet *out_net_db = db_network_->staToDb(out_net); |
| dbNet *in_net_db = db_network_->staToDb(in_net); |
| out_net_db->setSigType(in_net_db->getSigType()); |
| |
| // Move load pins to out_net. |
| for (Pin *pin : load_pins) { |
| Port *port = network_->port(pin); |
| Instance *inst = network_->instance(pin); |
| sta_->disconnectPin(pin); |
| sta_->connectPin(inst, port, out_net); |
| } |
| } |
| else { |
| // One of the loads is an output port. |
| // Use the net as the repeater output net so the port stays connected to it. |
| in_net = makeUniqueNet(); |
| out_net = net; |
| // Copy signal type to new net. |
| dbNet *out_net_db = db_network_->staToDb(out_net); |
| dbNet *in_net_db = db_network_->staToDb(in_net); |
| in_net_db->setSigType(out_net_db->getSigType()); |
| |
| // Move non-repeater load pins to in_net. |
| PinSet load_pins1; |
| for (Pin *pin : load_pins) |
| load_pins1.insert(pin); |
| |
| NetPinIterator *pin_iter = network_->pinIterator(out_net); |
| while (pin_iter->hasNext()) { |
| Pin *pin = pin_iter->next(); |
| if (!load_pins1.hasKey(pin)) { |
| Port *port = network_->port(pin); |
| Instance *inst = network_->instance(pin); |
| sta_->disconnectPin(pin); |
| sta_->connectPin(inst, port, in_net); |
| } |
| } |
| } |
| |
| Instance *buffer = makeInstance(buffer_cell, |
| buffer_name.c_str(), |
| parent); |
| journalMakeBuffer(buffer); |
| Point buf_loc(x, y); |
| setLocation(buffer, buf_loc); |
| designAreaIncr(area(db_network_->cell(buffer_cell))); |
| inserted_buffer_count_++; |
| |
| sta_->connectPin(buffer, buffer_input_port, in_net); |
| sta_->connectPin(buffer, buffer_output_port, out_net); |
| |
| parasiticsInvalid(in_net); |
| parasiticsInvalid(out_net); |
| |
| // Resize repeater as we back up by levels. |
| Pin *drvr_pin = network_->findPin(buffer, buffer_output_port); |
| resizeToTargetSlew(drvr_pin, false); |
| buffer_cell = network_->libertyCell(buffer); |
| buffer_cell->bufferPorts(buffer_input_port, buffer_output_port); |
| |
| Pin *buf_in_pin = network_->findPin(buffer, buffer_input_port); |
| load_pins.clear(); |
| load_pins.push_back(buf_in_pin); |
| wire_length = 0; |
| pin_cap = buffer_input_port->capacitance(); |
| fanout = portFanoutLoad(buffer_input_port); |
| } |
| |
| bool |
| Resizer::hasInputPort(const Net *net) |
| { |
| bool has_top_level_port = false; |
| NetConnectedPinIterator *pin_iter = network_->connectedPinIterator(net); |
| while (pin_iter->hasNext()) { |
| Pin *pin = pin_iter->next(); |
| if (network_->isTopLevelPort(pin) |
| && network_->direction(pin)->isAnyInput()) { |
| has_top_level_port = true; |
| break; |
| } |
| } |
| delete pin_iter; |
| return has_top_level_port; |
| } |
| |
| bool |
| Resizer::hasOutputPort(const Net *net) |
| { |
| bool has_top_level_port = false; |
| NetConnectedPinIterator *pin_iter = network_->connectedPinIterator(net); |
| while (pin_iter->hasNext()) { |
| Pin *pin = pin_iter->next(); |
| if (network_->isTopLevelPort(pin) |
| && network_->direction(pin)->isAnyOutput()) { |
| has_top_level_port = true; |
| break; |
| } |
| } |
| delete pin_iter; |
| return has_top_level_port; |
| } |
| |
| bool |
| Resizer::hasPort(const Net *net) |
| { |
| bool has_top_level_port = false; |
| NetConnectedPinIterator *pin_iter = network_->connectedPinIterator(net); |
| while (pin_iter->hasNext()) { |
| Pin *pin = pin_iter->next(); |
| if (network_->isTopLevelPort(pin)) { |
| has_top_level_port = true; |
| break; |
| } |
| } |
| delete pin_iter; |
| return has_top_level_port; |
| } |
| |
| float |
| Resizer::driveResistance(const Pin *drvr_pin) |
| { |
| if (network_->isTopLevelPort(drvr_pin)) { |
| InputDrive *drive = sdc_->findInputDrive(network_->port(drvr_pin)); |
| if (drive) { |
| float max_res = 0; |
| for (auto min_max : MinMax::range()) { |
| for (auto rf : RiseFall::range()) { |
| LibertyCell *cell; |
| LibertyPort *from_port; |
| float *from_slews; |
| LibertyPort *to_port; |
| drive->driveCell(rf, min_max, cell, from_port, from_slews, to_port); |
| if (to_port) |
| max_res = max(max_res, to_port->driveResistance()); |
| else { |
| float res; |
| bool exists; |
| drive->driveResistance(rf, min_max, res, exists); |
| max_res = max(max_res, res); |
| } |
| } |
| } |
| return max_res; |
| } |
| } |
| else { |
| LibertyPort *drvr_port = network_->libertyPort(drvr_pin); |
| if (drvr_port) |
| return drvr_port->driveResistance(); |
| } |
| return 0.0; |
| } |
| |
| //////////////////////////////////////////////////////////////// |
| |
| void |
| Resizer::resizeToTargetSlew() |
| { |
| resize_count_ = 0; |
| resized_multi_output_insts_.clear(); |
| incrementalParasiticsBegin(); |
| // Resize in reverse level order. |
| for (int i = level_drvr_vertices_.size() - 1; i >= 0; i--) { |
| Vertex *drvr = level_drvr_vertices_[i]; |
| Pin *drvr_pin = drvr->pin(); |
| Net *net = network_->net(drvr_pin); |
| if (net |
| && !drvr->isConstant() |
| && hasFanout(drvr) |
| // Hands off the clock nets. |
| && !sta_->isClock(drvr_pin) |
| // Hands off special nets. |
| && !db_network_->isSpecial(net)) { |
| resizeToTargetSlew(drvr_pin, true); |
| if (overMaxArea()) { |
| logger_->error(RSZ, 24, "Max utilization reached."); |
| break; |
| } |
| } |
| } |
| updateParasitics(); |
| incrementalParasiticsEnd(); |
| |
| if (resize_count_ > 0) |
| logger_->info(RSZ, 29, "Resized {} instances.", resize_count_); |
| } |
| |
| bool |
| Resizer::hasFanout(Vertex *drvr) |
| { |
| VertexOutEdgeIterator edge_iter(drvr, graph_); |
| return edge_iter.hasNext(); |
| } |
| |
| void |
| Resizer::makeEquivCells() |
| { |
| LibertyLibrarySeq libs; |
| LibertyLibraryIterator *lib_iter = network_->libertyLibraryIterator(); |
| while (lib_iter->hasNext()) { |
| LibertyLibrary *lib = lib_iter->next(); |
| // massive kludge until makeEquivCells is fixed to only incldue link cells |
| LibertyCellIterator cell_iter(lib); |
| if (cell_iter.hasNext()) { |
| LibertyCell *cell = cell_iter.next(); |
| if (isLinkCell(cell)) |
| libs.push_back(lib); |
| } |
| } |
| delete lib_iter; |
| sta_->makeEquivCells(&libs, nullptr); |
| } |
| |
| static float |
| targetLoadDist(float load_cap, |
| float target_load) |
| { |
| return abs(load_cap - target_load); |
| } |
| |
| bool |
| Resizer::resizeToTargetSlew(const Pin *drvr_pin, |
| bool update_count) |
| { |
| Instance *inst = network_->instance(drvr_pin); |
| LibertyCell *cell = network_->libertyCell(inst); |
| if (cell) { |
| bool revisiting_inst = false; |
| if (hasMultipleOutputs(inst)) { |
| revisiting_inst = resized_multi_output_insts_.hasKey(inst); |
| debugPrint(logger_, RSZ, "resize", 2, "multiple outputs{}", |
| revisiting_inst ? " - revisit" : ""); |
| resized_multi_output_insts_.insert(inst); |
| } |
| ensureWireParasitic(drvr_pin); |
| // Includes net parasitic capacitance. |
| float load_cap = graph_delay_calc_->loadCap(drvr_pin, tgt_slew_dcalc_ap_); |
| // DINESH-A: delay cells resize disabled |
| if (load_cap > 0.00 && (strncmp(cell->name(),"sky130_fd_sc_hd__clkdlybuf4s15_2",26) != 0)) { |
| LibertyCell *target_cell = findTargetCell(cell, load_cap, revisiting_inst); |
| if (target_cell != cell) { |
| //printf("Dinesh-A: Resizing : %s => %s %s Load_cap: %f \n",sdc_network_->pathName(drvr_pin),cell->name(),target_cell->name(),load_cap); |
| debugPrint(logger_, RSZ, "resize", 2, "{} {} -> {}", |
| sdc_network_->pathName(drvr_pin), |
| cell->name(), |
| target_cell->name()); |
| if (replaceCell(inst, target_cell, true) |
| && !revisiting_inst |
| && update_count) |
| resize_count_++; |
| } |
| } |
| } |
| return false; |
| } |
| |
| LibertyCell * |
| Resizer::findTargetCell(LibertyCell *cell, |
| float load_cap, |
| bool revisiting_inst) |
| { |
| LibertyCell *best_cell = cell; |
| LibertyCellSeq *equiv_cells = sta_->equivCells(cell); |
| if (equiv_cells) { |
| bool is_buf_inv = cell->isBuffer() || cell->isInverter(); |
| float target_load = (*target_load_map_)[cell]; |
| float best_load = target_load; |
| float best_dist = targetLoadDist(load_cap, target_load); |
| float best_delay = is_buf_inv |
| ? bufferDelay(cell, load_cap, tgt_slew_dcalc_ap_) |
| : 0.0; |
| debugPrint(logger_, RSZ, "resize", 3, "{} load cap {} dist={:.2e} delay={}", |
| cell->name(), |
| units_->capacitanceUnit()->asString(load_cap), |
| best_dist, |
| delayAsString(best_delay, sta_, 3)); |
| for (LibertyCell *target_cell : *equiv_cells) { |
| if (!dontUse(target_cell) |
| && isLinkCell(target_cell)) { |
| float target_load = (*target_load_map_)[target_cell]; |
| float delay = is_buf_inv |
| ? bufferDelay(target_cell, load_cap, tgt_slew_dcalc_ap_) |
| : 0.0; |
| float dist = targetLoadDist(load_cap, target_load); |
| debugPrint(logger_, RSZ, "resize", 3, " {} dist={:.2e} delay={}", |
| target_cell->name(), |
| dist, |
| delayAsString(delay, sta_, 3)); |
| if (is_buf_inv |
| // Library may have "delay" buffers/inverters that are |
| // functionally buffers/inverters but have additional |
| // intrinsic delay. Accept worse target load matching if |
| // delay is reduced to avoid using them. |
| ? ((delay < best_delay |
| && dist < best_dist * 1.1) |
| || (dist < best_dist |
| && delay < best_delay * 1.1)) |
| : dist < best_dist |
| // If the instance has multiple outputs (generally a register Q/QN) |
| // only allow upsizing after the first pin is visited. |
| && (!revisiting_inst |
| || target_load > best_load)) { |
| best_cell = target_cell; |
| best_dist = dist; |
| best_load = target_load; |
| best_delay = delay; |
| } |
| } |
| } |
| } |
| return best_cell; |
| } |
| |
| // Replace LEF with LEF so ports stay aligned in instance. |
| bool |
| Resizer::replaceCell(Instance *inst, |
| LibertyCell *replacement, |
| bool journal) |
| { |
| const char *replacement_name = replacement->name(); |
| dbMaster *replacement_master = db_->findMaster(replacement_name); |
| if (replacement_master) { |
| dbInst *dinst = db_network_->staToDb(inst); |
| dbMaster *master = dinst->getMaster(); |
| designAreaIncr(-area(master)); |
| Cell *replacement_cell1 = db_network_->dbToSta(replacement_master); |
| if (journal) |
| journalInstReplaceCellBefore(inst); |
| sta_->replaceCell(inst, replacement_cell1); |
| designAreaIncr(area(replacement_master)); |
| |
| // Invalidate estimated parasitics on all instance pins. |
| // Input nets change pin cap, outputs change location (slightly). |
| if (haveEstimatedParasitics()) { |
| InstancePinIterator *pin_iter = network_->pinIterator(inst); |
| while (pin_iter->hasNext()) { |
| const Pin *pin = pin_iter->next(); |
| const Net *net = network_->net(pin); |
| if (net) |
| parasiticsInvalid(net); |
| } |
| delete pin_iter; |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| bool |
| Resizer::hasMultipleOutputs(const Instance *inst) |
| { |
| int output_count = 0; |
| InstancePinIterator *pin_iter = network_->pinIterator(inst); |
| while (pin_iter->hasNext()) { |
| const Pin *pin = pin_iter->next(); |
| if (network_->direction(pin)->isAnyOutput() |
| && network_->net(pin)) { |
| output_count++; |
| if (output_count > 1) |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| //////////////////////////////////////////////////////////////// |
| |
| void |
| Resizer::resizeSlackPreamble() |
| { |
| resizePreamble(); |
| // Save max_wire_length for multiple repairDesign calls. |
| max_wire_length_ = findMaxWireLength(); |
| net_slack_map_.clear(); |
| } |
| |
| // Run repair design to find the slacks but save/restore all changes to the netlist. |
| void |
| Resizer::findResizeSlacks() |
| { |
| journalBegin(); |
| estimateWireParasitics(); |
| int repair_count, slew_violations, cap_violations; |
| int fanout_violations, length_violations; |
| repairDesign(max_wire_length_, 0.0, 0.0, |
| repair_count, slew_violations, cap_violations, |
| fanout_violations, length_violations); |
| findResizeSlacks1(); |
| journalRestore(); |
| } |
| |
| void |
| Resizer::findResizeSlacks1() |
| { |
| // Use driver pin slacks rather than Sta::netSlack to save visiting |
| // the net pins and min'ing the slack. |
| NetSeq nets; |
| for (int i = level_drvr_vertices_.size() - 1; i >= 0; i--) { |
| Vertex *drvr = level_drvr_vertices_[i]; |
| Pin *drvr_pin = drvr->pin(); |
| Net *net = network_->isTopLevelPort(drvr_pin) |
| ? network_->net(network_->term(drvr_pin)) |
| : network_->net(drvr_pin); |
| if (net |
| && !drvr->isConstant() |
| // Hands off special nets. |
| && !db_network_->isSpecial(net) |
| && !sta_->isClock(drvr_pin)) { |
| net_slack_map_[net] = sta_->vertexSlack(drvr, max_); |
| nets.push_back(net); |
| } |
| } |
| |
| // Find the nets with the worst slack. |
| double worst_percent = .1; |
| // sort(nets.begin(), nets.end(). [&](const Net *net1, |
| sort(nets, [this](const Net *net1, |
| const Net *net2) |
| { return resizeNetSlack(net1) < resizeNetSlack(net2); }); |
| worst_slack_nets_.clear(); |
| for (int i = 0; i < nets.size() * worst_percent; i++) |
| worst_slack_nets_.push_back(nets[i]); |
| } |
| |
| NetSeq & |
| Resizer::resizeWorstSlackNets() |
| { |
| return worst_slack_nets_; |
| } |
| |
| vector<dbNet*> |
| Resizer::resizeWorstSlackDbNets() |
| { |
| vector<dbNet*> nets; |
| for (Net* net : worst_slack_nets_) |
| nets.push_back(db_network_->staToDb(net)); |
| return nets; |
| } |
| |
| Slack |
| Resizer::resizeNetSlack(const Net *net) |
| { |
| return net_slack_map_[net]; |
| } |
| |
| Slack |
| Resizer::resizeNetSlack(const dbNet *db_net) |
| { |
| const Net *net = db_network_->dbToSta(db_net); |
| return net_slack_map_[net]; |
| } |
| |
| //////////////////////////////////////////////////////////////// |
| |
| double |
| Resizer::area(Cell *cell) |
| { |
| return area(db_network_->staToDb(cell)); |
| } |
| |
| double |
| Resizer::area(dbMaster *master) |
| { |
| if (!master->isCoreAutoPlaceable()) { |
| return 0; |
| } |
| return dbuToMeters(master->getWidth()) * dbuToMeters(master->getHeight()); |
| } |
| |
| double |
| Resizer::dbuToMeters(int dist) const |
| { |
| int dbu = db_->getTech()->getDbUnitsPerMicron(); |
| return dist / (dbu * 1e+6); |
| } |
| |
| int |
| Resizer::metersToDbu(double dist) const |
| { |
| int dbu = db_->getTech()->getDbUnitsPerMicron(); |
| return dist * dbu * 1e+6; |
| } |
| |
| void |
| Resizer::setMaxUtilization(double max_utilization) |
| { |
| max_area_ = coreArea() * max_utilization; |
| } |
| |
| bool |
| Resizer::overMaxArea() |
| { |
| return max_area_ |
| && fuzzyGreaterEqual(design_area_, max_area_); |
| } |
| |
| void |
| Resizer::setDontUse(LibertyCellSeq *dont_use) |
| { |
| if (dont_use) { |
| for (LibertyCell *cell : *dont_use) |
| dont_use_.insert(cell); |
| } |
| } |
| |
| bool |
| Resizer::dontUse(LibertyCell *cell) |
| { |
| return cell->dontUse() |
| || dont_use_.hasKey(cell); |
| } |
| |
| //////////////////////////////////////////////////////////////// |
| |
| // Find a target slew for the libraries and then |
| // a target load for each cell that gives the target slew. |
| void |
| Resizer::findTargetLoads() |
| { |
| // Find target slew across all buffers in the libraries. |
| findBufferTargetSlews(); |
| if (target_load_map_ == nullptr) |
| target_load_map_ = new CellTargetLoadMap; |
| target_load_map_->clear(); |
| |
| // Find target loads at the tgt_slew_corner. |
| int lib_ap_index = tgt_slew_corner_->libertyIndex(max_); |
| LibertyLibraryIterator *lib_iter = network_->libertyLibraryIterator(); |
| while (lib_iter->hasNext()) { |
| LibertyLibrary *lib = lib_iter->next(); |
| LibertyCellIterator cell_iter(lib); |
| while (cell_iter.hasNext()) { |
| LibertyCell *cell = cell_iter.next(); |
| if (isLinkCell(cell)) { |
| LibertyCell *corner_cell = cell->cornerCell(lib_ap_index); |
| float tgt_load; |
| bool exists; |
| target_load_map_->findKey(corner_cell, tgt_load, exists); |
| if (!exists) { |
| tgt_load = findTargetLoad(corner_cell); |
| (*target_load_map_)[corner_cell] = tgt_load; |
| } |
| // Map link cell to corner cell target load. |
| if (cell != corner_cell) |
| (*target_load_map_)[cell] = tgt_load; |
| } |
| } |
| } |
| delete lib_iter; |
| } |
| |
| float |
| Resizer::targetLoadCap(LibertyCell *cell) |
| { |
| float load_cap = 0.0; |
| bool exists; |
| target_load_map_->findKey(cell, load_cap, exists); |
| if (!exists) |
| logger_->error(RSZ, 68, "missing target load cap."); |
| return load_cap; |
| } |
| |
| float |
| Resizer::findTargetLoad(LibertyCell *cell) |
| { |
| LibertyCellTimingArcSetIterator arc_set_iter(cell); |
| float target_load_sum = 0.0; |
| int arc_count = 0; |
| while (arc_set_iter.hasNext()) { |
| TimingArcSet *arc_set = arc_set_iter.next(); |
| TimingRole *role = arc_set->role(); |
| if (!role->isTimingCheck() |
| && role != TimingRole::tristateDisable() |
| && role != TimingRole::tristateEnable()) { |
| TimingArcSetArcIterator arc_iter(arc_set); |
| while (arc_iter.hasNext()) { |
| TimingArc *arc = arc_iter.next(); |
| int in_rf_index = arc->fromTrans()->asRiseFall()->index(); |
| int out_rf_index = arc->toTrans()->asRiseFall()->index(); |
| float arc_target_load = findTargetLoad(cell, arc, |
| tgt_slews_[in_rf_index], |
| tgt_slews_[out_rf_index]); |
| debugPrint(logger_, RSZ, "target_load", 3, "{} {} -> {} {} target_load = {:.2e}", |
| cell->name(), |
| arc->from()->name(), |
| arc->to()->name(), |
| arc->toTrans()->asString(), |
| arc_target_load); |
| target_load_sum += arc_target_load; |
| arc_count++; |
| } |
| } |
| } |
| float target_load = arc_count ? target_load_sum / arc_count : 0.0; |
| debugPrint(logger_, RSZ, "target_load", 2, "{} target_load = {:.2e}", |
| cell->name(), |
| target_load); |
| return target_load; |
| } |
| |
| // Find the load capacitance that will cause the output slew |
| // to be equal to out_slew. |
| float |
| Resizer::findTargetLoad(LibertyCell *cell, |
| TimingArc *arc, |
| Slew in_slew, |
| Slew out_slew) |
| { |
| GateTimingModel *model = dynamic_cast<GateTimingModel*>(arc->model()); |
| if (model) { |
| // load_cap1 lower bound |
| // load_cap2 upper bound |
| double load_cap1 = 0.0; |
| double load_cap2 = 1.0e-12; // 1pF |
| double tol = .01; // 1% |
| double diff1 = gateSlewDiff(cell, arc, model, in_slew, load_cap1, out_slew); |
| if (diff1 > 0.0) |
| // Zero load cap out_slew is higher than the target. |
| return 0.0; |
| double diff2 = gateSlewDiff(cell, arc, model, in_slew, load_cap2, out_slew); |
| // binary search for diff = 0. |
| while (abs(load_cap1 - load_cap2) > max(load_cap1, load_cap2) * tol) { |
| if (diff2 < 0.0) { |
| load_cap1 = load_cap2; |
| diff1 = diff2; |
| load_cap2 *= 2; |
| diff2 = gateSlewDiff(cell, arc, model, in_slew, load_cap2, out_slew); |
| } |
| else { |
| double load_cap3 = (load_cap1 + load_cap2) / 2.0; |
| double diff3 = gateSlewDiff(cell, arc, model, in_slew, load_cap3, out_slew); |
| if (diff3 < 0.0) { |
| load_cap1 = load_cap3; |
| diff1 = diff3; |
| } |
| else { |
| load_cap2 = load_cap3; |
| diff2 = diff3; |
| } |
| } |
| } |
| return load_cap1; |
| } |
| return 0.0; |
| } |
| |
| // objective function |
| Slew |
| Resizer::gateSlewDiff(LibertyCell *cell, |
| TimingArc *arc, |
| GateTimingModel *model, |
| Slew in_slew, |
| float load_cap, |
| Slew out_slew) |
| |
| { |
| const Pvt *pvt = tgt_slew_dcalc_ap_->operatingConditions(); |
| ArcDelay arc_delay; |
| Slew arc_slew; |
| model->gateDelay(cell, pvt, in_slew, load_cap, 0.0, false, |
| arc_delay, arc_slew); |
| return arc_slew - out_slew; |
| } |
| |
| //////////////////////////////////////////////////////////////// |
| |
| Slew |
| Resizer::targetSlew(const RiseFall *rf) |
| { |
| return tgt_slews_[rf->index()]; |
| } |
| |
| // Find target slew across all buffers in the libraries. |
| void |
| Resizer::findBufferTargetSlews() |
| { |
| tgt_slews_ = {0.0}; |
| tgt_slew_corner_ = nullptr; |
| |
| for (Corner *corner : *sta_->corners()) { |
| int lib_ap_index = corner->libertyIndex(max_); |
| const DcalcAnalysisPt *dcalc_ap = corner->findDcalcAnalysisPt(max_); |
| const Pvt *pvt = dcalc_ap->operatingConditions(); |
| // Average slews across buffers at corner. |
| Slew slews[RiseFall::index_count]{0.0}; |
| int counts[RiseFall::index_count]{0}; |
| for (LibertyCell *buffer : buffer_cells_) { |
| LibertyCell *corner_buffer = buffer->cornerCell(lib_ap_index); |
| findBufferTargetSlews(corner_buffer, pvt, slews, counts); |
| } |
| Slew slew_rise = slews[RiseFall::riseIndex()] / counts[RiseFall::riseIndex()]; |
| Slew slew_fall = slews[RiseFall::fallIndex()] / counts[RiseFall::fallIndex()]; |
| // Use the target slews from the slowest corner, |
| // and resize using that corner. |
| if (slew_rise > tgt_slews_[RiseFall::riseIndex()]) { |
| tgt_slews_[RiseFall::riseIndex()] = slew_rise; |
| tgt_slews_[RiseFall::fallIndex()] = slew_fall; |
| tgt_slew_corner_ = corner; |
| tgt_slew_dcalc_ap_ = corner->findDcalcAnalysisPt(max_); |
| } |
| } |
| |
| debugPrint(logger_, RSZ, "target_load", 1, "target slew corner {} = {}/{}", |
| tgt_slew_corner_->name(), |
| delayAsString(tgt_slews_[RiseFall::riseIndex()], sta_, 3), |
| delayAsString(tgt_slews_[RiseFall::fallIndex()], sta_, 3)); |
| } |
| |
| void |
| Resizer::findBufferTargetSlews(LibertyCell *buffer, |
| const Pvt *pvt, |
| // Return values. |
| Slew slews[], |
| int counts[]) |
| { |
| LibertyPort *input, *output; |
| buffer->bufferPorts(input, output); |
| TimingArcSetSeq *arc_sets = buffer->timingArcSets(input, output); |
| if (arc_sets) { |
| for (TimingArcSet *arc_set : *arc_sets) { |
| TimingArcSetArcIterator arc_iter(arc_set); |
| while (arc_iter.hasNext()) { |
| TimingArc *arc = arc_iter.next(); |
| GateTimingModel *model = dynamic_cast<GateTimingModel*>(arc->model()); |
| RiseFall *in_rf = arc->fromTrans()->asRiseFall(); |
| RiseFall *out_rf = arc->toTrans()->asRiseFall(); |
| float in_cap = input->capacitance(in_rf, max_); |
| float load_cap = in_cap * tgt_slew_load_cap_factor; |
| ArcDelay arc_delay; |
| Slew arc_slew; |
| model->gateDelay(buffer, pvt, 0.0, load_cap, 0.0, false, |
| arc_delay, arc_slew); |
| model->gateDelay(buffer, pvt, arc_slew, load_cap, 0.0, false, |
| arc_delay, arc_slew); |
| slews[out_rf->index()] += arc_slew; |
| counts[out_rf->index()]++; |
| } |
| } |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////// |
| |
| // Repair tie hi/low net driver fanout by duplicating the |
| // tie hi/low instances for every pin connected to tie hi/low instances. |
| void |
| Resizer::repairTieFanout(LibertyPort *tie_port, |
| double separation, // meters |
| bool verbose) |
| { |
| ensureBlock(); |
| ensureDesignArea(); |
| Instance *top_inst = network_->topInstance(); |
| LibertyCell *tie_cell = tie_port->libertyCell(); |
| InstanceSeq insts; |
| findCellInstances(tie_cell, insts); |
| int tie_count = 0; |
| int separation_dbu = metersToDbu(separation); |
| for (Instance *inst : insts) { |
| Pin *drvr_pin = network_->findPin(inst, tie_port); |
| if (drvr_pin) { |
| const char *inst_name = network_->name(inst); |
| Net *net = network_->net(drvr_pin); |
| if (net) { |
| NetConnectedPinIterator *pin_iter = network_->connectedPinIterator(net); |
| while (pin_iter->hasNext()) { |
| Pin *load = pin_iter->next(); |
| if (load != drvr_pin) { |
| // Make tie inst. |
| Point tie_loc = tieLocation(load, separation_dbu); |
| Instance *load_inst = network_->instance(load); |
| string tie_name = makeUniqueInstName(inst_name, true); |
| Instance *tie = makeInstance(tie_cell, tie_name.c_str(), |
| top_inst); |
| setLocation(tie, tie_loc); |
| |
| // Make tie output net. |
| Net *load_net = makeUniqueNet(); |
| |
| // Connect tie inst output. |
| sta_->connectPin(tie, tie_port, load_net); |
| |
| // Connect load to tie output net. |
| sta_->disconnectPin(load); |
| Port *load_port = network_->port(load); |
| sta_->connectPin(load_inst, load_port, load_net); |
| |
| designAreaIncr(area(db_network_->cell(tie_cell))); |
| tie_count++; |
| } |
| } |
| delete pin_iter; |
| |
| // Delete inst output net. |
| Pin *tie_pin = network_->findPin(inst, tie_port); |
| Net *tie_net = network_->net(tie_pin); |
| sta_->deleteNet(tie_net); |
| parasitics_invalid_.erase(tie_net); |
| // Delete the tie instance. |
| sta_->deleteInstance(inst); |
| } |
| } |
| } |
| |
| if (tie_count > 0) { |
| logger_->info(RSZ, 42, "Inserted {} tie {} instances.", |
| tie_count, |
| tie_cell->name()); |
| level_drvr_vertices_valid_ = false; |
| } |
| } |
| |
| void |
| Resizer::findCellInstances(LibertyCell *cell, |
| // Return value. |
| InstanceSeq &insts) |
| { |
| LeafInstanceIterator *inst_iter = network_->leafInstanceIterator(); |
| while (inst_iter->hasNext()) { |
| Instance *inst = inst_iter->next(); |
| if (network_->libertyCell(inst) == cell) |
| insts.push_back(inst); |
| } |
| delete inst_iter; |
| } |
| |
| // Place the tie instance on the side of the load pin. |
| Point |
| Resizer::tieLocation(Pin *load, |
| int separation) |
| { |
| Point load_loc = db_network_->location(load); |
| int load_x = load_loc.getX(); |
| int load_y = load_loc.getY(); |
| int tie_x = load_x; |
| int tie_y = load_y; |
| if (!network_->isTopLevelPort(load)) { |
| dbInst *db_inst = db_network_->staToDb(network_->instance(load)); |
| dbBox *bbox = db_inst->getBBox(); |
| int left_dist = abs(load_x - bbox->xMin()); |
| int right_dist = abs(load_x - bbox->xMax()); |
| int bot_dist = abs(load_y - bbox->yMin()); |
| int top_dist = abs(load_y - bbox->yMax()); |
| if (left_dist < right_dist |
| && left_dist < bot_dist |
| && left_dist < top_dist) |
| // left |
| tie_x -= separation; |
| if (right_dist < left_dist |
| && right_dist < bot_dist |
| && right_dist < top_dist) |
| // right |
| tie_x += separation; |
| if (bot_dist < left_dist |
| && bot_dist < right_dist |
| && bot_dist < top_dist) |
| // bot |
| tie_y -= separation; |
| if (top_dist < left_dist |
| && top_dist < right_dist |
| && top_dist < bot_dist) |
| // top |
| tie_y += separation; |
| } |
| return Point(tie_x, tie_y); |
| } |
| |
| //////////////////////////////////////////////////////////////// |
| |
| void |
| Resizer::repairSetup(float slack_margin, |
| int max_passes) |
| { |
| inserted_buffer_count_ = 0; |
| resize_count_ = 0; |
| Slack worst_slack; |
| Vertex *worst_vertex; |
| sta_->worstSlack(max_, worst_slack, worst_vertex); |
| debugPrint(logger_, RSZ, "repair_setup", 1, "worst_slack = {}", |
| delayAsString(worst_slack, sta_, 3)); |
| Slack prev_worst_slack = -INF; |
| int pass = 1; |
| int decreasing_slack_passes = 0; |
| incrementalParasiticsBegin(); |
| while (fuzzyLess(worst_slack, slack_margin) |
| && pass <= max_passes) { |
| PathRef worst_path = sta_->vertexWorstSlackPath(worst_vertex, max_); |
| bool changed = repairSetup(worst_path, worst_slack); |
| updateParasitics(); |
| sta_->findRequireds(); |
| sta_->worstSlack(max_, worst_slack, worst_vertex); |
| bool decreasing_slack = fuzzyLessEqual(worst_slack, prev_worst_slack); |
| debugPrint(logger_, RSZ, "repair_setup", 1, "pass {} worst_slack = {} {}", |
| pass, |
| delayAsString(worst_slack, sta_, 3), |
| decreasing_slack ? "v" : "^"); |
| if (decreasing_slack) { |
| // Allow slack to increase to get out of local minima. |
| // Do not update prev_worst_slack so it saves the high water mark. |
| decreasing_slack_passes++; |
| if (!changed |
| || decreasing_slack_passes > repair_setup_decreasing_slack_passes_allowed_) { |
| // Undo changes that reduced slack. |
| journalRestore(); |
| debugPrint(logger_, RSZ, "repair_setup", 1, |
| "decreasing slack for {} passes. Restoring best slack {}", |
| decreasing_slack_passes, |
| delayAsString(prev_worst_slack, sta_, 3)); |
| break; |
| } |
| } |
| else { |
| prev_worst_slack = worst_slack; |
| decreasing_slack_passes = 0; |
| // Progress, start journal so we can back up to here. |
| journalBegin(); |
| } |
| if (overMaxArea()) |
| break; |
| pass++; |
| } |
| // Leave the parasitics up to date. |
| updateParasitics(); |
| incrementalParasiticsEnd(); |
| |
| if (inserted_buffer_count_ > 0) |
| logger_->info(RSZ, 40, "Inserted {} buffers.", inserted_buffer_count_); |
| if (resize_count_ > 0) |
| logger_->info(RSZ, 41, "Resized {} instances.", resize_count_); |
| if (fuzzyLess(worst_slack, slack_margin)) |
| logger_->warn(RSZ, 62, "Unable to repair all setup violations."); |
| if (overMaxArea()) |
| logger_->error(RSZ, 25, "max utilization reached."); |
| } |
| |
| // For testing. |
| void |
| Resizer::repairSetup(Pin *end_pin) |
| { |
| inserted_buffer_count_ = 0; |
| resize_count_ = 0; |
| Vertex *vertex = graph_->pinLoadVertex(end_pin); |
| Slack slack = sta_->vertexSlack(vertex, max_); |
| PathRef path = sta_->vertexWorstSlackPath(vertex, max_); |
| incrementalParasiticsBegin(); |
| repairSetup(path, slack); |
| // Leave the parasitices up to date. |
| updateParasitics(); |
| incrementalParasiticsEnd(); |
| |
| if (inserted_buffer_count_ > 0) |
| logger_->info(RSZ, 30, "Inserted {} buffers.", inserted_buffer_count_); |
| if (resize_count_ > 0) |
| logger_->info(RSZ, 31, "Resized {} instances.", resize_count_); |
| } |
| |
| bool |
| Resizer::repairSetup(PathRef &path, |
| Slack path_slack) |
| { |
| PathExpanded expanded(&path, sta_); |
| bool changed = false; |
| if (expanded.size() > 1) { |
| int path_length = expanded.size(); |
| vector<pair<int, Delay>> load_delays; |
| int start_index = expanded.startIndex(); |
| const DcalcAnalysisPt *dcalc_ap = path.dcalcAnalysisPt(sta_); |
| int lib_ap = dcalc_ap->libertyIndex(); |
| // Find load delay for each gate in the path. |
| for (int i = start_index; i < path_length; i++) { |
| PathRef *path = expanded.path(i); |
| Vertex *path_vertex = path->vertex(sta_); |
| const Pin *path_pin = path->pin(sta_); |
| if (network_->isDriver(path_pin) |
| && !network_->isTopLevelPort(path_pin)) { |
| TimingArc *prev_arc = expanded.prevArc(i); |
| TimingArc *corner_arc = prev_arc->cornerArc(lib_ap); |
| Edge *prev_edge = path->prevEdge(prev_arc, sta_); |
| Delay load_delay = graph_->arcDelay(prev_edge, prev_arc, dcalc_ap->index()) |
| // Remove intrinsic delay to find load dependent delay. |
| - corner_arc->intrinsicDelay(); |
| load_delays.push_back(pair(i, load_delay)); |
| debugPrint(logger_, RSZ, "repair_setup", 3, "{} load_delay = {}", |
| path_vertex->name(network_), |
| delayAsString(load_delay, sta_, 3)); |
| } |
| } |
| |
| sort(load_delays.begin(), load_delays.end(), |
| [](pair<int, Delay> pair1, |
| pair<int, Delay> pair2) { |
| return pair1.second > pair2.second; |
| }); |
| // Attack gates with largest load delays first. |
| for (auto index_delay : load_delays) { |
| int drvr_index = index_delay.first; |
| PathRef *drvr_path = expanded.path(drvr_index); |
| Vertex *drvr_vertex = drvr_path->vertex(sta_); |
| const Pin *drvr_pin = drvr_vertex->pin(); |
| LibertyPort *drvr_port = network_->libertyPort(drvr_pin); |
| LibertyCell *drvr_cell = drvr_port ? drvr_port->libertyCell() : nullptr; |
| int fanout = this->fanout(drvr_vertex); |
| debugPrint(logger_, RSZ, "repair_setup", 2, "{} {} fanout = {}", |
| network_->pathName(drvr_pin), |
| drvr_cell ? drvr_cell->name() : "none", |
| fanout); |
| |
| if (upsizeDrvr(drvr_path, drvr_index, &expanded)) { |
| changed = true; |
| break; |
| } |
| |
| // For tristate nets all we can do is resize the driver. |
| bool tristate_drvr = isTristateDriver(drvr_pin); |
| if (fanout > 1 |
| // Rebuffer blows up on large fanout nets. |
| && fanout < rebuffer_max_fanout_ |
| && !tristate_drvr) { |
| int rebuffer_count = rebuffer(drvr_pin); |
| if (rebuffer_count > 0) { |
| debugPrint(logger_, RSZ, "repair_setup", 2, "rebuffer {} inserted {}", |
| network_->pathName(drvr_pin), |
| rebuffer_count); |
| changed = true; |
| break; |
| } |
| } |
| |
| // Don't split loads on low fanout nets. |
| if (fanout > split_load_min_fanout_ |
| && !tristate_drvr) { |
| splitLoads(drvr_path, drvr_index, path_slack, &expanded); |
| changed = true; |
| break; |
| } |
| } |
| } |
| return changed; |
| } |
| |
| bool |
| Resizer::upsizeDrvr(PathRef *drvr_path, |
| int drvr_index, |
| PathExpanded *expanded) |
| { |
| Pin *drvr_pin = drvr_path->pin(this); |
| const DcalcAnalysisPt *dcalc_ap = drvr_path->dcalcAnalysisPt(sta_); |
| float load_cap = graph_delay_calc_->loadCap(drvr_pin, dcalc_ap); |
| int in_index = drvr_index - 1; |
| PathRef *in_path = expanded->path(in_index); |
| Pin *in_pin = in_path->pin(sta_); |
| LibertyPort *in_port = network_->libertyPort(in_pin); |
| |
| float prev_drive; |
| if (drvr_index >= 2) { |
| int prev_drvr_index = drvr_index - 2; |
| PathRef *prev_drvr_path = expanded->path(prev_drvr_index); |
| Pin *prev_drvr_pin = prev_drvr_path->pin(sta_); |
| prev_drive = 0.0; |
| LibertyPort *prev_drvr_port = network_->libertyPort(prev_drvr_pin); |
| if (prev_drvr_port) { |
| prev_drive = prev_drvr_port->driveResistance(); |
| } |
| } |
| else |
| prev_drive = 0.0; |
| LibertyPort *drvr_port = network_->libertyPort(drvr_pin); |
| LibertyCell *upsize = upsizeCell(in_port, drvr_port, load_cap, |
| prev_drive, dcalc_ap); |
| if (upsize) { |
| Instance *drvr = network_->instance(drvr_pin); |
| debugPrint(logger_, RSZ, "repair_setup", 2, "resize {} {} -> {}", |
| network_->pathName(drvr_pin), |
| drvr_port->libertyCell()->name(), |
| upsize->name()); |
| if (replaceCell(drvr, upsize, true)) { |
| resize_count_++; |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| LibertyCell * |
| Resizer::upsizeCell(LibertyPort *in_port, |
| LibertyPort *drvr_port, |
| float load_cap, |
| float prev_drive, |
| const DcalcAnalysisPt *dcalc_ap) |
| { |
| int lib_ap = dcalc_ap->libertyIndex(); |
| LibertyCell *cell = drvr_port->libertyCell(); |
| LibertyCellSeq *equiv_cells = sta_->equivCells(cell); |
| if (equiv_cells) { |
| const char *in_port_name = in_port->name(); |
| const char *drvr_port_name = drvr_port->name(); |
| sort(equiv_cells, |
| [=] (const LibertyCell *cell1, |
| const LibertyCell *cell2) { |
| LibertyPort *port1=cell1->findLibertyPort(drvr_port_name)->cornerPort(lib_ap); |
| LibertyPort *port2=cell2->findLibertyPort(drvr_port_name)->cornerPort(lib_ap); |
| return port1->driveResistance() > port2->driveResistance(); |
| }); |
| float drive = drvr_port->cornerPort(lib_ap)->driveResistance(); |
| float delay = gateDelay(drvr_port, load_cap, tgt_slew_dcalc_ap_) |
| + prev_drive * in_port->cornerPort(lib_ap)->capacitance(); |
| for (LibertyCell *equiv : *equiv_cells) { |
| LibertyCell *equiv_corner = equiv->cornerCell(lib_ap); |
| LibertyPort *equiv_drvr = equiv_corner->findLibertyPort(drvr_port_name); |
| LibertyPort *equiv_input = equiv_corner->findLibertyPort(in_port_name); |
| float equiv_drive = equiv_drvr->driveResistance(); |
| // Include delay of previous driver into equiv gate. |
| float equiv_delay = gateDelay(equiv_drvr, load_cap, dcalc_ap) |
| + prev_drive * equiv_input->capacitance(); |
| if (!dontUse(equiv) |
| && equiv_drive < drive |
| && equiv_delay < delay) |
| return equiv; |
| } |
| } |
| return nullptr; |
| } |
| |
| void |
| Resizer::splitLoads(PathRef *drvr_path, |
| int drvr_index, |
| Slack drvr_slack, |
| PathExpanded *expanded) |
| { |
| Pin *drvr_pin = drvr_path->pin(this); |
| PathRef *load_path = expanded->path(drvr_index + 1); |
| Vertex *load_vertex = load_path->vertex(sta_); |
| Pin *load_pin = load_vertex->pin(); |
| // Divide and conquer. |
| debugPrint(logger_, RSZ, "repair_setup", 2, "split loads {} -> {}", |
| network_->pathName(drvr_pin), |
| network_->pathName(load_pin)); |
| |
| Vertex *drvr_vertex = drvr_path->vertex(sta_); |
| const RiseFall *rf = drvr_path->transition(sta_); |
| // Sort fanouts of the drvr on the critical path by slack margin |
| // wrt the critical path slack. |
| vector<pair<Vertex*, Slack>> fanout_slacks; |
| VertexOutEdgeIterator edge_iter(drvr_vertex, graph_); |
| while (edge_iter.hasNext()) { |
| Edge *edge = edge_iter.next(); |
| Vertex *fanout_vertex = edge->to(graph_); |
| Slack fanout_slack = sta_->vertexSlack(fanout_vertex, rf, max_); |
| Slack slack_margin = fanout_slack - drvr_slack; |
| debugPrint(logger_, RSZ, "repair_setup", 3, " fanin {} slack_margin = {}", |
| network_->pathName(fanout_vertex->pin()), |
| delayAsString(slack_margin, sta_, 3)); |
| fanout_slacks.push_back(pair<Vertex*, Slack>(fanout_vertex, slack_margin)); |
| } |
| |
| sort(fanout_slacks.begin(), fanout_slacks.end(), |
| [](pair<Vertex*, Slack> pair1, |
| pair<Vertex*, Slack> pair2) { |
| return pair1.second > pair2.second; |
| }); |
| |
| Net *net = network_->net(drvr_pin); |
| |
| string buffer_name = makeUniqueInstName("split"); |
| Instance *parent = db_network_->topInstance(); |
| LibertyCell *buffer_cell = buffer_lowest_drive_; |
| Instance *buffer = makeInstance(buffer_cell, |
| buffer_name.c_str(), |
| parent); |
| journalMakeBuffer(buffer); |
| inserted_buffer_count_++; |
| designAreaIncr(area(db_network_->cell(buffer_cell))); |
| |
| Net *out_net = makeUniqueNet(); |
| LibertyPort *input, *output; |
| buffer_cell->bufferPorts(input, output); |
| Point drvr_loc = db_network_->location(drvr_pin); |
| setLocation(buffer, drvr_loc); |
| |
| // Split the loads with extra slack to an inserted buffer. |
| // before |
| // drvr_pin -> net -> load_pins |
| // after |
| // drvr_pin -> net -> load_pins with low slack |
| // -> buffer_in -> net -> rest of loads |
| sta_->connectPin(buffer, input, net); |
| parasiticsInvalid(net); |
| sta_->connectPin(buffer, output, out_net); |
| int split_index = fanout_slacks.size() / 2; |
| for (int i = 0; i < split_index; i++) { |
| pair<Vertex*, Slack> fanout_slack = fanout_slacks[i]; |
| Vertex *load_vertex = fanout_slack.first; |
| Pin *load_pin = load_vertex->pin(); |
| // Leave ports connected to original net so verilog port names are preserved. |
| if (!network_->isTopLevelPort(load_pin)) { |
| LibertyPort *load_port = network_->libertyPort(load_pin); |
| Instance *load = network_->instance(load_pin); |
| |
| sta_->disconnectPin(load_pin); |
| sta_->connectPin(load, load_port, out_net); |
| } |
| } |
| Pin *buffer_out_pin = network_->findPin(buffer, output); |
| resizeToTargetSlew(buffer_out_pin, false); |
| } |
| |
| //////////////////////////////////////////////////////////////// |
| |
| void |
| Resizer::repairHold(float slack_margin, |
| bool allow_setup_violations, |
| // Max buffer count as percent of design instance count. |
| float max_buffer_percent) |
| { |
| init(); |
| LibertyCell *buffer_cell = findHoldBuffer(); |
| sta_->findRequireds(); |
| VertexSet *ends = sta_->search()->endpoints(); |
| int max_buffer_count = max_buffer_percent * network_->instanceCount(); |
| incrementalParasiticsBegin(); |
| repairHold(ends, buffer_cell, slack_margin, |
| allow_setup_violations, max_buffer_count); |
| |
| // Leave the parasitices up to date. |
| updateParasitics(); |
| incrementalParasiticsEnd(); |
| } |
| |
| // For testing/debug. |
| void |
| Resizer::repairHold(Pin *end_pin, |
| LibertyCell *buffer_cell, |
| float slack_margin, |
| bool allow_setup_violations, |
| float max_buffer_percent) |
| { |
| Vertex *end = graph_->pinLoadVertex(end_pin); |
| VertexSet ends; |
| ends.insert(end); |
| |
| init(); |
| sta_->findRequireds(); |
| int max_buffer_count = max_buffer_percent * network_->instanceCount(); |
| incrementalParasiticsBegin(); |
| repairHold(&ends, buffer_cell, slack_margin, allow_setup_violations, |
| max_buffer_count); |
| // Leave the parasitices up to date. |
| updateParasitics(); |
| incrementalParasiticsEnd(); |
| } |
| |
| // Find the buffer with the most delay in the fastest corner. |
| LibertyCell * |
| Resizer::findHoldBuffer() |
| { |
| LibertyCell *max_buffer = nullptr; |
| float max_delay = 0.0; |
| for (LibertyCell *buffer : buffer_cells_) { |
| float buffer_min_delay = bufferHoldDelay(buffer); |
| if (max_buffer == nullptr |
| || buffer_min_delay > max_delay) { |
| max_buffer = buffer; |
| max_delay = buffer_min_delay; |
| } |
| } |
| return max_buffer; |
| } |
| |
| float |
| Resizer::bufferHoldDelay(LibertyCell *buffer) |
| { |
| Delay delays[RiseFall::index_count]; |
| bufferHoldDelays(buffer, delays); |
| return min(delays[RiseFall::riseIndex()], |
| delays[RiseFall::fallIndex()]); |
| } |
| |
| // Min self delay across corners; buffer -> buffer |
| void |
| Resizer::bufferHoldDelays(LibertyCell *buffer, |
| // Return values. |
| Delay delays[RiseFall::index_count]) |
| { |
| LibertyPort *input, *output; |
| buffer->bufferPorts(input, output); |
| |
| for (int rf_index : RiseFall::rangeIndex()) |
| delays[rf_index] = MinMax::min()->initValue(); |
| for (Corner *corner : *sta_->corners()) { |
| LibertyPort *corner_port = input->cornerPort(corner->libertyIndex(max_)); |
| const DcalcAnalysisPt *dcalc_ap = corner->findDcalcAnalysisPt(max_); |
| float load_cap = corner_port->capacitance(); |
| ArcDelay gate_delays[RiseFall::index_count]; |
| Slew slews[RiseFall::index_count]; |
| gateDelays(output, load_cap, dcalc_ap, gate_delays, slews); |
| for (int rf_index : RiseFall::rangeIndex()) |
| delays[rf_index] = min(delays[rf_index], gate_delays[rf_index]); |
| } |
| } |
| |
| void |
| Resizer::repairHold(VertexSet *ends, |
| LibertyCell *buffer_cell, |
| float slack_margin, |
| bool allow_setup_violations, |
| int max_buffer_count) |
| { |
| // Find endpoints with hold violation. |
| VertexSet hold_failures; |
| Slack worst_slack; |
| findHoldViolations(ends, slack_margin, worst_slack, hold_failures); |
| if (!hold_failures.empty()) { |
| logger_->info(RSZ, 46, "Found {} endpoints with hold violations.", |
| hold_failures.size()); |
| inserted_buffer_count_ = 0; |
| int repair_count = 1; |
| int pass = 1; |
| while (!hold_failures.empty() |
| // Make sure we are making progress. |
| && repair_count > 0 |
| && !overMaxArea() |
| && inserted_buffer_count_ <= max_buffer_count) { |
| repair_count = repairHoldPass(hold_failures, buffer_cell, slack_margin, |
| allow_setup_violations, max_buffer_count); |
| debugPrint(logger_, RSZ, "repair_hold", 1, |
| "pass {} worst slack {} failures {} inserted {}", |
| pass, |
| delayAsString(worst_slack, sta_, 3), |
| hold_failures .size(), |
| repair_count); |
| sta_->findRequireds(); |
| findHoldViolations(ends, slack_margin, worst_slack, hold_failures); |
| pass++; |
| } |
| if (slack_margin == 0.0 && fuzzyLess(worst_slack, 0.0)) |
| logger_->warn(RSZ, 66, "Unable to repair all hold violations."); |
| else if (fuzzyLess(worst_slack, slack_margin)) |
| logger_->warn(RSZ, 64, "Unable to repair all hold checks within margin."); |
| |
| if (inserted_buffer_count_ > 0) { |
| logger_->info(RSZ, 32, "Inserted {} hold buffers.", inserted_buffer_count_); |
| level_drvr_vertices_valid_ = false; |
| } |
| if (inserted_buffer_count_ > max_buffer_count) |
| logger_->error(RSZ, 60, "Max buffer count reached."); |
| if (overMaxArea()) |
| logger_->error(RSZ, 50, "Max utilization reached."); |
| } |
| else |
| logger_->info(RSZ, 33, "No hold violations found."); |
| } |
| |
| void |
| Resizer::findHoldViolations(VertexSet *ends, |
| float slack_margin, |
| // Return values. |
| Slack &worst_slack, |
| VertexSet &hold_violations) |
| { |
| worst_slack = INF; |
| hold_violations.clear(); |
| debugPrint(logger_, RSZ, "repair_hold", 3, "Hold violations"); |
| for (Vertex *end : *ends) { |
| Slack slack = sta_->vertexSlack(end, MinMax::min()); |
| if (!sta_->isClock(end->pin()) |
| && fuzzyLess(slack, slack_margin)) { |
| debugPrint(logger_, RSZ, "repair_hold", 3, " {}", |
| end->name(sdc_network_)); |
| if (slack < worst_slack) |
| worst_slack = slack; |
| hold_violations.insert(end); |
| } |
| } |
| } |
| |
| int |
| Resizer::repairHoldPass(VertexSet &hold_failures, |
| LibertyCell *buffer_cell, |
| float slack_margin, |
| bool allow_setup_violations, |
| int max_buffer_count) |
| { |
| VertexSet fanins = findHoldFanins(hold_failures); |
| VertexSeq sorted_fanins = sortHoldFanins(fanins); |
| |
| int repair_count = 0; |
| int max_repair_count = max(static_cast<int>(hold_failures.size() * .2), 10); |
| for(int i = 0; i < sorted_fanins.size() && repair_count < max_repair_count ; i++) { |
| Vertex *vertex = sorted_fanins[i]; |
| Pin *drvr_pin = vertex->pin(); |
| Net *net = network_->isTopLevelPort(drvr_pin) |
| ? network_->net(network_->term(drvr_pin)) |
| : network_->net(drvr_pin); |
| updateParasitics(); |
| Slack drvr_slacks[RiseFall::index_count][MinMax::index_count]; |
| sta_->vertexSlacks(vertex, drvr_slacks); |
| int min_index = MinMax::minIndex(); |
| int max_index = MinMax::maxIndex(); |
| const RiseFall *drvr_rf = (drvr_slacks[RiseFall::riseIndex()][min_index] < |
| drvr_slacks[RiseFall::fallIndex()][min_index]) |
| ? RiseFall::rise() |
| : RiseFall::fall(); |
| Slack drvr_hold_slack = drvr_slacks[drvr_rf->index()][min_index] - slack_margin; |
| Slack drvr_setup_slack = drvr_slacks[drvr_rf->index()][max_index]; |
| if (!vertex->isConstant() |
| && fuzzyLess(drvr_hold_slack, 0.0) |
| // Hands off special nets. |
| && !db_network_->isSpecial(net) |
| && !isTristateDriver(drvr_pin) |
| // Have to have enough setup slack to add delay to repair the hold violation. |
| && (allow_setup_violations |
| || fuzzyLess(drvr_hold_slack, drvr_setup_slack))) { |
| debugPrint(logger_, RSZ, "repair_hold", 2, "driver {}", |
| vertex->name(sdc_network_)); |
| // Only add delay to loads with hold violations. |
| PinSeq load_pins; |
| float load_cap = 0.0; |
| bool loads_have_out_port = false; |
| VertexOutEdgeIterator edge_iter(vertex, graph_); |
| while (edge_iter.hasNext()) { |
| Edge *edge = edge_iter.next(); |
| Vertex *fanout = edge->to(graph_); |
| Slack fanout_slack = sta_->vertexSlack(fanout, MinMax::min()); |
| if (fanout_slack < slack_margin) { |
| Pin *fanout_pin = fanout->pin(); |
| load_pins.push_back(fanout_pin); |
| if (network_->direction(fanout_pin)->isAnyOutput() |
| && network_->isTopLevelPort(fanout_pin)) |
| loads_have_out_port = true; |
| else { |
| LibertyPort *fanout_port = network_->libertyPort(fanout_pin); |
| if (fanout_port) |
| load_cap += fanout_port->capacitance(); |
| } |
| } |
| } |
| if (!load_pins.empty()) { |
| // multi-corner support MIA |
| const DcalcAnalysisPt *dcalc_ap = sta_->cmdCorner()->findDcalcAnalysisPt(max_); |
| Delay buffer_delay1 = bufferDelay(buffer_cell, drvr_rf, load_cap, dcalc_ap); |
| // Need enough slack to at least insert the last buffer with loads. |
| if (allow_setup_violations |
| || (buffer_delay1 < drvr_slacks[RiseFall::riseIndex()][max_index] |
| && buffer_delay1 < drvr_slacks[RiseFall::fallIndex()][max_index])) { |
| Delay buffer_delay = bufferHoldDelay(buffer_cell); |
| Delay max_insert = min(drvr_slacks[RiseFall::riseIndex()][max_index], |
| drvr_slacks[RiseFall::fallIndex()][max_index]); |
| int max_insert_count = delayInf(max_insert) |
| ? 1 |
| : (max_insert - buffer_delay1) / buffer_delay + 1; |
| int hold_buffer_count = (-drvr_hold_slack > buffer_delay1) |
| ? std::ceil((-drvr_hold_slack-buffer_delay1)/buffer_delay)+1 |
| : 1; |
| int buffer_count = allow_setup_violations |
| ? hold_buffer_count |
| : min(max_insert_count, hold_buffer_count); |
| debugPrint(logger_, RSZ, "repair_hold", 2, |
| " {} hold={} inserted {} for {}/{} loads", |
| vertex->name(sdc_network_), |
| delayAsString(drvr_hold_slack, this, 3), |
| buffer_count, |
| load_pins.size(), |
| fanout(vertex)); |
| makeHoldDelay(vertex, buffer_count, load_pins, |
| loads_have_out_port, buffer_cell); |
| repair_count += buffer_count; |
| if (logger_->debugCheck(RSZ, "repair_hold", 4)) { |
| // Check that no setup violations are introduced. |
| updateParasitics(); |
| Slack drvr_setup_slack1 = sta_->vertexSlack(vertex, max_); |
| if (drvr_setup_slack1 < 0 |
| && drvr_setup_slack1 < drvr_setup_slack) |
| printf("%s %s -> %s\n", vertex->name(network_), |
| delayAsString(drvr_setup_slack, sta_, 3), |
| delayAsString(drvr_setup_slack1, sta_, 3)); |
| } |
| if (inserted_buffer_count_ > max_buffer_count |
| || overMaxArea()) |
| return repair_count; |
| } |
| } |
| } |
| } |
| return repair_count; |
| } |
| |
| VertexSet |
| Resizer::findHoldFanins(VertexSet &ends) |
| { |
| SearchPredNonReg2 pred(sta_); |
| BfsBkwdIterator iter(BfsIndex::other, &pred, this); |
| for (Vertex *vertex : ends) |
| iter.enqueueAdjacentVertices(vertex); |
| |
| VertexSet fanins; |
| while (iter.hasNext()) { |
| Vertex *fanin = iter.next(); |
| if (!sta_->isClock(fanin->pin())) { |
| if (fanin->isDriver(network_)) |
| fanins.insert(fanin); |
| iter.enqueueAdjacentVertices(fanin); |
| } |
| } |
| return fanins; |
| } |
| |
| VertexSeq |
| Resizer::sortHoldFanins(VertexSet &fanins) |
| { |
| VertexSeq sorted_fanins; |
| for(Vertex *vertex : fanins) |
| sorted_fanins.push_back(vertex); |
| |
| sort(sorted_fanins, [&](Vertex *v1, Vertex *v2) |
| { float s1 = sta_->vertexSlack(v1, MinMax::min()); |
| float s2 = sta_->vertexSlack(v2, MinMax::min()); |
| if (fuzzyEqual(s1, s2)) { |
| float gap1 = slackGap(v1); |
| float gap2 = slackGap(v2); |
| // Break ties based on the hold/setup gap. |
| if (fuzzyEqual(gap1, gap2)) |
| return v1->level() > v2->level(); |
| else |
| return gap1 > gap2; |
| } |
| else |
| return s1 < s2;}); |
| if (logger_->debugCheck(RSZ, "repair_hold", 4)) { |
| printf("Sorted fanins"); |
| printf(" hold_slack slack_gap level"); |
| for(Vertex *vertex : sorted_fanins) |
| printf("%s %s %s %d", |
| vertex->name(network_), |
| delayAsString(sta_->vertexSlack(vertex, MinMax::min()), sta_, 3), |
| delayAsString(slackGap(vertex), sta_, 3), |
| vertex->level()); |
| } |
| return sorted_fanins; |
| } |
| |
| void |
| Resizer::makeHoldDelay(Vertex *drvr, |
| int buffer_count, |
| PinSeq &load_pins, |
| bool loads_have_out_port, |
| LibertyCell *buffer_cell) |
| { |
| Pin *drvr_pin = drvr->pin(); |
| Instance *parent = db_network_->topInstance(); |
| Net *drvr_net = network_->isTopLevelPort(drvr_pin) |
| ? db_network_->net(db_network_->term(drvr_pin)) |
| : db_network_->net(drvr_pin); |
| Net *in_net, *out_net; |
| if (loads_have_out_port) { |
| // Verilog uses nets as ports, so the net connected to an output port has |
| // to be preserved. |
| // Move the driver pin over to gensym'd net. |
| in_net = makeUniqueNet(); |
| Port *drvr_port = network_->port(drvr_pin); |
| Instance *drvr_inst = network_->instance(drvr_pin); |
| sta_->disconnectPin(drvr_pin); |
| sta_->connectPin(drvr_inst, drvr_port, in_net); |
| out_net = drvr_net; |
| } |
| else { |
| in_net = drvr_net; |
| out_net = makeUniqueNet(); |
| } |
| |
| parasiticsInvalid(in_net); |
| // Spread buffers between driver and load center. |
| Point drvr_loc = db_network_->location(drvr_pin); |
| Point load_center = findCenter(load_pins); |
| int dx = (load_center.x() - drvr_loc.x()) / (buffer_count + 1); |
| int dy = (load_center.y() - drvr_loc.y()) / (buffer_count + 1); |
| |
| Net *buf_in_net = in_net; |
| Instance *buffer = nullptr; |
| LibertyPort *input, *output; |
| buffer_cell->bufferPorts(input, output); |
| // drvr_pin->in_net->hold_buffer1->net2->hold_buffer2->out_net...->load_pins |
| for (int i = 0; i < buffer_count; i++) { |
| Net *buf_out_net = (i == buffer_count - 1) ? out_net : makeUniqueNet(); |
| // drvr_pin->drvr_net->hold_buffer->net2->load_pins |
| string buffer_name = makeUniqueInstName("hold"); |
| buffer = makeInstance(buffer_cell, buffer_name.c_str(), |
| parent); |
| journalMakeBuffer(buffer); |
| inserted_buffer_count_++; |
| designAreaIncr(area(db_network_->cell(buffer_cell))); |
| |
| sta_->connectPin(buffer, input, buf_in_net); |
| sta_->connectPin(buffer, output, buf_out_net); |
| Point buffer_loc(drvr_loc.x() + dx * i, |
| drvr_loc.y() + dy * i); |
| setLocation(buffer, buffer_loc); |
| buf_in_net = buf_out_net; |
| parasiticsInvalid(buf_out_net); |
| } |
| |
| for (Pin *load_pin : load_pins) { |
| Net *load_net = network_->isTopLevelPort(load_pin) |
| ? network_->net(network_->term(load_pin)) |
| : network_->net(load_pin); |
| if (load_net != out_net) { |
| Instance *load = db_network_->instance(load_pin); |
| Port *load_port = db_network_->port(load_pin); |
| sta_->disconnectPin(load_pin); |
| sta_->connectPin(load, load_port, out_net); |
| } |
| } |
| Pin *buffer_out_pin = network_->findPin(buffer, output); |
| resizeToTargetSlew(buffer_out_pin, false); |
| } |
| |
| Point |
| Resizer::findCenter(PinSeq &pins) |
| { |
| long sum_x = 0; |
| long sum_y = 0; |
| for (Pin *pin : pins) { |
| Point loc = db_network_->location(pin); |
| sum_x += loc.x(); |
| sum_y += loc.y(); |
| } |
| return Point(sum_x / pins.size(), sum_y / pins.size()); |
| } |
| |
| // Gap between min setup and hold slacks. |
| // This says how much head room there is for adding delay to fix a |
| // hold violation before violating a setup check. |
| Slack |
| Resizer::slackGap(Slacks &slacks) |
| { |
| return min(slacks[RiseFall::riseIndex()][MinMax::maxIndex()] |
| - slacks[RiseFall::riseIndex()][MinMax::minIndex()], |
| slacks[RiseFall::fallIndex()][MinMax::maxIndex()] |
| - slacks[RiseFall::fallIndex()][MinMax::minIndex()]); |
| } |
| |
| Slack |
| Resizer::slackGap(Vertex *vertex) |
| { |
| Slacks slacks; |
| sta_->vertexSlacks(vertex, slacks); |
| return slackGap(slacks); |
| } |
| |
| int |
| Resizer::fanout(Vertex *vertex) |
| { |
| int fanout = 0; |
| VertexOutEdgeIterator edge_iter(vertex, graph_); |
| while (edge_iter.hasNext()) { |
| edge_iter.next(); |
| fanout++; |
| } |
| return fanout; |
| } |
| |
| //////////////////////////////////////////////////////////////// |
| |
| void |
| Resizer::reportLongWires(int count, |
| int digits) |
| { |
| graph_ = sta_->ensureGraph(); |
| sta_->ensureClkNetwork(); |
| VertexSeq drvrs; |
| findLongWires(drvrs); |
| report_->reportLine("Driver length delay"); |
| const Corner *corner = sta_->cmdCorner(); |
| double wire_res = wireSignalResistance(corner); |
| double wire_cap = wireSignalCapacitance(corner); |
| int i = 0; |
| for (Vertex *drvr : drvrs) { |
| Pin *drvr_pin = drvr->pin(); |
| double wire_length = dbuToMeters(maxLoadManhattenDistance(drvr)); |
| double steiner_length = dbuToMeters(findMaxSteinerDist(drvr)); |
| double delay = (wire_length * wire_res) * (wire_length * wire_cap) * 0.5; |
| report_->reportLine("%s manhtn %s steiner %s %s", |
| sdc_network_->pathName(drvr_pin), |
| units_->distanceUnit()->asString(wire_length, 1), |
| units_->distanceUnit()->asString(steiner_length, 1), |
| delayAsString(delay, sta_, digits)); |
| if (i == count) |
| break; |
| i++; |
| } |
| } |
| |
| typedef std::pair<Vertex*, int> DrvrDist; |
| |
| void |
| Resizer::findLongWires(VertexSeq &drvrs) |
| { |
| Vector<DrvrDist> drvr_dists; |
| VertexIterator vertex_iter(graph_); |
| while (vertex_iter.hasNext()) { |
| Vertex *vertex = vertex_iter.next(); |
| if (vertex->isDriver(network_)) { |
| Pin *pin = vertex->pin(); |
| // Hands off the clock nets. |
| if (!sta_->isClock(pin) |
| && !vertex->isConstant() |
| && !vertex->isDisabledConstraint()) |
| drvr_dists.push_back(DrvrDist(vertex, maxLoadManhattenDistance(vertex))); |
| } |
| } |
| sort(drvr_dists, [](const DrvrDist &drvr_dist1, |
| const DrvrDist &drvr_dist2) { |
| return drvr_dist1.second > drvr_dist2.second; |
| }); |
| drvrs.reserve(drvr_dists.size()); |
| for (DrvrDist &drvr_dist : drvr_dists) |
| drvrs.push_back(drvr_dist.first); |
| } |
| |
| void |
| Resizer::findLongWiresSteiner(VertexSeq &drvrs) |
| { |
| Vector<DrvrDist> drvr_dists; |
| VertexIterator vertex_iter(graph_); |
| while (vertex_iter.hasNext()) { |
| Vertex *vertex = vertex_iter.next(); |
| if (vertex->isDriver(network_)) { |
| Pin *pin = vertex->pin(); |
| // Hands off the clock nets. |
| if (!sta_->isClock(pin) |
| && !vertex->isConstant()) |
| drvr_dists.push_back(DrvrDist(vertex, findMaxSteinerDist(vertex))); |
| } |
| } |
| sort(drvr_dists, [](const DrvrDist &drvr_dist1, |
| const DrvrDist &drvr_dist2) { |
| return drvr_dist1.second > drvr_dist2.second; |
| }); |
| drvrs.reserve(drvr_dists.size()); |
| for (DrvrDist &drvr_dist : drvr_dists) |
| drvrs.push_back(drvr_dist.first); |
| } |
| |
| // Find the maximum distance along steiner tree branches from |
| // the driver to loads (in dbu). |
| int |
| Resizer::findMaxSteinerDist(Vertex *drvr) |
| { |
| Pin *drvr_pin = drvr->pin(); |
| SteinerTree *tree = makeSteinerTree(drvr_pin, true, max_steiner_pin_count_, |
| stt_builder_, db_network_, logger_); |
| if (tree) { |
| int dist = findMaxSteinerDist(tree); |
| delete tree; |
| return dist; |
| } |
| return 0; |
| } |
| |
| int |
| Resizer::findMaxSteinerDist(SteinerTree *tree) |
| { |
| SteinerPt drvr_pt = tree->drvrPt(network_); |
| if (drvr_pt == SteinerTree::null_pt) |
| return 0; |
| else |
| return findMaxSteinerDist(tree, drvr_pt, 0); |
| } |
| |
| // DFS of steiner tree. |
| int |
| Resizer::findMaxSteinerDist(SteinerTree *tree, |
| SteinerPt pt, |
| int dist_from_drvr) |
| { |
| const PinSeq *pins = tree->pins(pt); |
| if (pins) { |
| for (const Pin *pin : *pins) { |
| if (db_network_->isLoad(pin)) |
| return dist_from_drvr; |
| } |
| } |
| Point loc = tree->location(pt); |
| SteinerPt left = tree->left(pt); |
| int left_max = 0; |
| if (left != SteinerTree::null_pt) { |
| int left_dist = Point::manhattanDistance(loc, tree->location(left)); |
| left_max = findMaxSteinerDist(tree, left, dist_from_drvr + left_dist); |
| } |
| SteinerPt right = tree->right(pt); |
| int right_max = 0; |
| if (right != SteinerTree::null_pt) { |
| int right_dist = Point::manhattanDistance(loc, tree->location(right)); |
| right_max = findMaxSteinerDist(tree, right, dist_from_drvr + right_dist); |
| } |
| return max(left_max, right_max); |
| } |
| |
| double |
| Resizer::maxLoadManhattenDistance(const Net *net) |
| { |
| NetPinIterator *pin_iter = network_->pinIterator(net); |
| int max_dist = 0; |
| while (pin_iter->hasNext()) { |
| Pin *pin = pin_iter->next(); |
| if (network_->isDriver(pin)) { |
| Vertex *drvr = graph_->pinDrvrVertex(pin); |
| if (drvr) { |
| int dist = maxLoadManhattenDistance(drvr); |
| max_dist = max(max_dist, dist); |
| } |
| } |
| } |
| delete pin_iter; |
| return dbuToMeters(max_dist); |
| } |
| |
| int |
| Resizer::maxLoadManhattenDistance(Vertex *drvr) |
| { |
| int max_dist = 0; |
| Point drvr_loc = db_network_->location(drvr->pin()); |
| VertexOutEdgeIterator edge_iter(drvr, graph_); |
| while (edge_iter.hasNext()) { |
| Edge *edge = edge_iter.next(); |
| Vertex *load = edge->to(graph_); |
| Point load_loc = db_network_->location(load->pin()); |
| int dist = Point::manhattanDistance(drvr_loc, load_loc); |
| max_dist = max(max_dist, dist); |
| } |
| return max_dist; |
| } |
| |
| //////////////////////////////////////////////////////////////// |
| |
| NetSeq * |
| Resizer::findFloatingNets() |
| { |
| NetSeq *floating_nets = new NetSeq; |
| NetIterator *net_iter = network_->netIterator(network_->topInstance()); |
| while (net_iter->hasNext()) { |
| Net *net = net_iter->next(); |
| PinSeq loads; |
| PinSeq drvrs; |
| PinSet visited_drvrs; |
| FindNetDrvrLoads visitor(nullptr, visited_drvrs, loads, drvrs, network_); |
| network_->visitConnectedPins(net, visitor); |
| if (drvrs.size() == 0 && loads.size() > 0) |
| floating_nets->push_back(net); |
| } |
| delete net_iter; |
| sort(floating_nets, sta::NetPathNameLess(network_)); |
| return floating_nets; |
| } |
| |
| //////////////////////////////////////////////////////////////// |
| |
| string |
| Resizer::makeUniqueNetName() |
| { |
| string node_name; |
| Instance *top_inst = network_->topInstance(); |
| do |
| stringPrint(node_name, "net%d", unique_net_index_++); |
| while (network_->findNet(top_inst, node_name.c_str())); |
| return node_name; |
| } |
| |
| Net * |
| Resizer::makeUniqueNet() |
| { |
| string net_name = makeUniqueNetName(); |
| Instance *parent = db_network_->topInstance(); |
| return db_network_->makeNet(net_name.c_str(), parent); |
| } |
| |
| string |
| Resizer::makeUniqueInstName(const char *base_name) |
| { |
| return makeUniqueInstName(base_name, false); |
| } |
| |
| string |
| Resizer::makeUniqueInstName(const char *base_name, |
| bool underscore) |
| { |
| string inst_name; |
| do |
| stringPrint(inst_name, underscore ? "%s_%d" : "%s%d", |
| base_name, unique_inst_index_++); |
| while (network_->findInstance(inst_name.c_str())); |
| return inst_name; |
| } |
| |
| float |
| Resizer::portFanoutLoad(LibertyPort *port) |
| { |
| float fanout_load; |
| bool exists; |
| port->fanoutLoad(fanout_load, exists); |
| if (!exists) { |
| LibertyLibrary *lib = port->libertyLibrary(); |
| lib->defaultFanoutLoad(fanout_load, exists); |
| } |
| if (exists) |
| return fanout_load; |
| else |
| return 0.0; |
| } |
| |
| float |
| Resizer::bufferDelay(LibertyCell *buffer_cell, |
| const RiseFall *rf, |
| float load_cap, |
| const DcalcAnalysisPt *dcalc_ap) |
| { |
| LibertyPort *input, *output; |
| buffer_cell->bufferPorts(input, output); |
| ArcDelay gate_delays[RiseFall::index_count]; |
| Slew slews[RiseFall::index_count]; |
| gateDelays(output, load_cap, dcalc_ap, gate_delays, slews); |
| return gate_delays[rf->index()]; |
| } |
| |
| float |
| Resizer::bufferDelay(LibertyCell *buffer_cell, |
| float load_cap, |
| const DcalcAnalysisPt *dcalc_ap) |
| { |
| LibertyPort *input, *output; |
| buffer_cell->bufferPorts(input, output); |
| ArcDelay gate_delays[RiseFall::index_count]; |
| Slew slews[RiseFall::index_count]; |
| gateDelays(output, load_cap, dcalc_ap, gate_delays, slews); |
| return max(gate_delays[RiseFall::riseIndex()], |
| gate_delays[RiseFall::fallIndex()]); |
| } |
| |
| // Self delay; buffer -> buffer |
| float |
| Resizer::bufferSelfDelay(LibertyCell *buffer_cell) |
| { |
| const DcalcAnalysisPt *dcalc_ap = sta_->cmdCorner()->findDcalcAnalysisPt(max_); |
| LibertyPort *input, *output; |
| buffer_cell->bufferPorts(input, output); |
| ArcDelay gate_delays[RiseFall::index_count]; |
| Slew slews[RiseFall::index_count]; |
| float load_cap = input->capacitance(); |
| gateDelays(output, load_cap, dcalc_ap, gate_delays, slews); |
| return max(gate_delays[RiseFall::riseIndex()], |
| gate_delays[RiseFall::fallIndex()]); |
| } |
| |
| float |
| Resizer::bufferSelfDelay(LibertyCell *buffer_cell, |
| const RiseFall *rf) |
| { |
| const DcalcAnalysisPt *dcalc_ap = sta_->cmdCorner()->findDcalcAnalysisPt(max_); |
| LibertyPort *input, *output; |
| buffer_cell->bufferPorts(input, output); |
| ArcDelay gate_delays[RiseFall::index_count]; |
| Slew slews[RiseFall::index_count]; |
| float load_cap = input->capacitance(); |
| gateDelays(output, load_cap, dcalc_ap, gate_delays, slews); |
| return gate_delays[rf->index()]; |
| } |
| |
| // Rise/fall delays across all timing arcs into drvr_port. |
| // Uses target slew for input slew. |
| void |
| Resizer::gateDelays(LibertyPort *drvr_port, |
| float load_cap, |
| const DcalcAnalysisPt *dcalc_ap, |
| // Return values. |
| ArcDelay delays[RiseFall::index_count], |
| Slew slews[RiseFall::index_count]) |
| { |
| for (int rf_index : RiseFall::rangeIndex()) { |
| delays[rf_index] = -INF; |
| slews[rf_index] = -INF; |
| } |
| const Pvt *pvt = dcalc_ap->operatingConditions(); |
| LibertyCell *cell = drvr_port->libertyCell(); |
| LibertyCellTimingArcSetIterator set_iter(cell); |
| while (set_iter.hasNext()) { |
| TimingArcSet *arc_set = set_iter.next(); |
| if (arc_set->to() == drvr_port |
| && !arc_set->role()->isTimingCheck()) { |
| TimingArcSetArcIterator arc_iter(arc_set); |
| while (arc_iter.hasNext()) { |
| TimingArc *arc = arc_iter.next(); |
| RiseFall *in_rf = arc->fromTrans()->asRiseFall(); |
| int out_rf_index = arc->toTrans()->asRiseFall()->index(); |
| float in_slew = tgt_slews_[in_rf->index()]; |
| ArcDelay gate_delay; |
| Slew drvr_slew; |
| arc_delay_calc_->gateDelay(cell, arc, in_slew, load_cap, |
| nullptr, 0.0, pvt, dcalc_ap, |
| gate_delay, |
| drvr_slew); |
| delays[out_rf_index] = max(delays[out_rf_index], gate_delay); |
| slews[out_rf_index] = max(slews[out_rf_index], drvr_slew); |
| } |
| } |
| } |
| } |
| |
| ArcDelay |
| Resizer::gateDelay(LibertyPort *drvr_port, |
| const RiseFall *rf, |
| float load_cap, |
| const DcalcAnalysisPt *dcalc_ap) |
| { |
| ArcDelay delays[RiseFall::index_count]; |
| Slew slews[RiseFall::index_count]; |
| gateDelays(drvr_port, load_cap, dcalc_ap, delays, slews); |
| return delays[rf->index()]; |
| } |
| |
| ArcDelay |
| Resizer::gateDelay(LibertyPort *drvr_port, |
| float load_cap, |
| const DcalcAnalysisPt *dcalc_ap) |
| { |
| ArcDelay delays[RiseFall::index_count]; |
| Slew slews[RiseFall::index_count]; |
| gateDelays(drvr_port, load_cap, dcalc_ap, delays, slews); |
| return max(delays[RiseFall::riseIndex()], delays[RiseFall::fallIndex()]); |
| } |
| |
| //////////////////////////////////////////////////////////////// |
| |
| double |
| Resizer::findMaxWireLength() |
| { |
| double max_length = INF; |
| for (const Corner *corner : *sta_->corners()) { |
| if (wireSignalResistance(corner) > 0.0) { |
| for (LibertyCell *buffer_cell : buffer_cells_) { |
| double buffer_length = findMaxWireLength(buffer_cell, corner); |
| max_length = min(max_length, buffer_length); |
| } |
| } |
| } |
| return max_length; |
| } |
| |
| // Find the max wire length before it is faster to split the wire |
| // in half with a buffer (in meters). |
| double |
| Resizer::findMaxWireLength(LibertyCell *buffer_cell, |
| const Corner *corner) |
| { |
| ensureBlock(); |
| LibertyPort *load_port, *drvr_port; |
| buffer_cell->bufferPorts(load_port, drvr_port); |
| return findMaxWireLength(drvr_port, corner); |
| } |
| |
| double |
| Resizer::findMaxWireLength(LibertyPort *drvr_port, |
| const Corner *corner) |
| { |
| LibertyCell *cell = drvr_port->libertyCell(); |
| if (db_network_->staToDb(cell) == nullptr) |
| logger_->error(RSZ, 70, "no LEF cell for {}.", cell->name()); |
| double drvr_r = drvr_port->driveResistance(); |
| // wire_length1 lower bound |
| // wire_length2 upper bound |
| double wire_length1 = 0.0; |
| // Initial guess with wire resistance same as driver resistance. |
| double wire_length2 = drvr_r / wireSignalResistance(corner); |
| double tol = .01; // 1% |
| double diff1 = splitWireDelayDiff(wire_length2, cell); |
| // binary search for diff = 0. |
| while (abs(wire_length1 - wire_length2) > max(wire_length1, wire_length2) * tol) { |
| if (diff1 < 0.0) { |
| wire_length1 = wire_length2; |
| wire_length2 *= 2; |
| diff1 = splitWireDelayDiff(wire_length2, cell); |
| } |
| else { |
| double wire_length3 = (wire_length1 + wire_length2) / 2.0; |
| double diff2 = splitWireDelayDiff(wire_length3, cell); |
| if (diff2 < 0.0) { |
| wire_length1 = wire_length3; |
| } |
| else { |
| wire_length2 = wire_length3; |
| diff1 = diff2; |
| } |
| } |
| } |
| return wire_length1; |
| } |
| |
| // objective function |
| double |
| Resizer::splitWireDelayDiff(double wire_length, |
| LibertyCell *buffer_cell) |
| { |
| Delay delay1, delay2; |
| Slew slew1, slew2; |
| bufferWireDelay(buffer_cell, wire_length, delay1, slew1); |
| bufferWireDelay(buffer_cell, wire_length / 2, delay2, slew2); |
| return delay1 - delay2 * 2; |
| } |
| |
| void |
| Resizer::bufferWireDelay(LibertyCell *buffer_cell, |
| double wire_length, // meters |
| // Return values. |
| Delay &delay, |
| Slew &slew) |
| { |
| LibertyPort *load_port, *drvr_port; |
| buffer_cell->bufferPorts(load_port, drvr_port); |
| return cellWireDelay(drvr_port, load_port, wire_length, delay, slew); |
| } |
| |
| // Cell delay plus wire delay. |
| // Use target slew for input slew. |
| // drvr_port and load_port do not have to be the same liberty cell. |
| void |
| Resizer::cellWireDelay(LibertyPort *drvr_port, |
| LibertyPort *load_port, |
| double wire_length, // meters |
| // Return values. |
| Delay &delay, |
| Slew &slew) |
| { |
| // Make a (hierarchical) block to use as a scratchpad. |
| dbBlock *block = dbBlock::create(block_, "wire_delay", '/'); |
| dbSta *sta = makeBlockSta(openroad_, block); |
| Parasitics *parasitics = sta->parasitics(); |
| Network *network = sta->network(); |
| ArcDelayCalc *arc_delay_calc = sta->arcDelayCalc(); |
| Corners *corners = sta->corners(); |
| corners->copy(sta_->corners()); |
| |
| Instance *top_inst = network->topInstance(); |
| // Tmp net for parasitics to live on. |
| Net *net = sta->makeNet("wire", top_inst); |
| LibertyCell *drvr_cell = drvr_port->libertyCell(); |
| LibertyCell *load_cell = load_port->libertyCell(); |
| Instance *drvr = sta->makeInstance("drvr", drvr_cell, top_inst); |
| Instance *load = sta->makeInstance("load", load_cell, top_inst); |
| sta->connectPin(drvr, drvr_port, net); |
| sta->connectPin(load, load_port, net); |
| Pin *drvr_pin = network->findPin(drvr, drvr_port); |
| Pin *load_pin = network->findPin(load, load_port); |
| |
| // Max rise/fall delays. |
| delay = -INF; |
| slew = -INF; |
| |
| for (Corner *corner : *corners) { |
| const DcalcAnalysisPt *dcalc_ap = corner->findDcalcAnalysisPt(max_); |
| const Pvt *pvt = dcalc_ap->operatingConditions(); |
| |
| makeWireParasitic(net, drvr_pin, load_pin, wire_length, corner, parasitics); |
| // Let delay calc reduce parasitic network as it sees fit. |
| Parasitic *drvr_parasitic = arc_delay_calc->findParasitic(drvr_pin, |
| RiseFall::rise(), |
| dcalc_ap); |
| LibertyCellTimingArcSetIterator set_iter(drvr_cell); |
| while (set_iter.hasNext()) { |
| TimingArcSet *arc_set = set_iter.next(); |
| if (arc_set->to() == drvr_port) { |
| TimingArcSetArcIterator arc_iter(arc_set); |
| while (arc_iter.hasNext()) { |
| TimingArc *arc = arc_iter.next(); |
| RiseFall *in_rf = arc->fromTrans()->asRiseFall(); |
| double in_slew = tgt_slews_[in_rf->index()]; |
| ArcDelay gate_delay; |
| Slew drvr_slew; |
| arc_delay_calc->gateDelay(drvr_cell, arc, in_slew, 0.0, |
| drvr_parasitic, 0.0, pvt, dcalc_ap, |
| gate_delay, |
| drvr_slew); |
| ArcDelay wire_delay; |
| Slew load_slew; |
| arc_delay_calc->loadDelay(load_pin, wire_delay, load_slew); |
| delay = max(delay, gate_delay + wire_delay); |
| slew = max(slew, load_slew); |
| } |
| } |
| } |
| arc_delay_calc->finishDrvrPin(); |
| parasitics->deleteParasitics(net, dcalc_ap->parasiticAnalysisPt()); |
| } |
| |
| // Cleanup the turds. |
| sta->deleteInstance(drvr); |
| sta->deleteInstance(load); |
| sta->deleteNet(net); |
| delete sta; |
| dbBlock::destroy(block); |
| } |
| |
| void |
| Resizer::makeWireParasitic(Net *net, |
| Pin *drvr_pin, |
| Pin *load_pin, |
| double wire_length, // meters |
| const Corner *corner, |
| Parasitics *parasitics) |
| { |
| const ParasiticAnalysisPt *parasitics_ap = |
| corner->findParasiticAnalysisPt(max_); |
| Parasitic *parasitic = parasitics->makeParasiticNetwork(net, false, |
| parasitics_ap); |
| ParasiticNode *n1 = parasitics->ensureParasiticNode(parasitic, drvr_pin); |
| ParasiticNode *n2 = parasitics->ensureParasiticNode(parasitic, load_pin); |
| double wire_cap = wire_length * wireSignalCapacitance(corner); |
| double wire_res = wire_length * wireSignalResistance(corner); |
| parasitics->incrCap(n1, wire_cap / 2.0, parasitics_ap); |
| parasitics->makeResistor(nullptr, n1, n2, wire_res, parasitics_ap); |
| parasitics->incrCap(n2, wire_cap / 2.0, parasitics_ap); |
| } |
| |
| //////////////////////////////////////////////////////////////// |
| |
| double |
| Resizer::findMaxSlewWireLength(LibertyPort *drvr_port, |
| LibertyPort *load_port, |
| double max_slew, |
| const Corner *corner) |
| { |
| ensureBlock(); |
| // wire_length1 lower bound |
| // wire_length2 upper bound |
| double wire_length1 = 0.0; |
| double wire_length2 = std::sqrt(max_slew /(wireSignalResistance(corner) |
| * wireSignalCapacitance(corner))); |
| double tol = .01; // 1% |
| double diff1 = maxSlewWireDiff(drvr_port, load_port, wire_length2, max_slew); |
| // binary search for diff = 0. |
| while (abs(wire_length1 - wire_length2) > max(wire_length1, wire_length2) * tol) { |
| if (diff1 < 0.0) { |
| wire_length1 = wire_length2; |
| wire_length2 *= 2; |
| diff1 = maxSlewWireDiff(drvr_port, load_port, wire_length2, max_slew); |
| } |
| else { |
| double wire_length3 = (wire_length1 + wire_length2) / 2.0; |
| double diff2 = maxSlewWireDiff(drvr_port, load_port, wire_length3, max_slew); |
| if (diff2 < 0.0) { |
| wire_length1 = wire_length3; |
| } |
| else { |
| wire_length2 = wire_length3; |
| diff1 = diff2; |
| } |
| } |
| } |
| return wire_length1; |
| } |
| |
| // objective function |
| double |
| Resizer::maxSlewWireDiff(LibertyPort *drvr_port, |
| LibertyPort *load_port, |
| double wire_length, |
| double max_slew) |
| { |
| Delay delay; |
| Slew slew; |
| cellWireDelay(drvr_port, load_port, wire_length, delay, slew); |
| return slew - max_slew; |
| } |
| |
| //////////////////////////////////////////////////////////////// |
| |
| double |
| Resizer::designArea() |
| { |
| ensureBlock(); |
| ensureDesignArea(); |
| return design_area_; |
| } |
| |
| void |
| Resizer::designAreaIncr(float delta) |
| { |
| design_area_ += delta; |
| } |
| |
| void |
| Resizer::ensureDesignArea() |
| { |
| if (block_) { |
| design_area_ = 0.0; |
| for (dbInst *inst : block_->getInsts()) { |
| dbMaster *master = inst->getMaster(); |
| // Don't count fillers otherwise you'll always get 100% utilization |
| if (!master->isFiller()) { |
| design_area_ += area(master); |
| } |
| } |
| } |
| } |
| |
| int |
| Resizer::fanout(Pin *drvr_pin) |
| { |
| int fanout = 0; |
| NetConnectedPinIterator *pin_iter = network_->connectedPinIterator(drvr_pin); |
| while (pin_iter->hasNext()) { |
| Pin *pin = pin_iter->next(); |
| if (pin != drvr_pin) |
| fanout++; |
| } |
| delete pin_iter; |
| return fanout; |
| } |
| |
| bool |
| Resizer::isFuncOneZero(const Pin *drvr_pin) |
| { |
| LibertyPort *port = network_->libertyPort(drvr_pin); |
| if (port) { |
| FuncExpr *func = port->function(); |
| return func && (func->op() == FuncExpr::op_zero |
| || func->op() == FuncExpr::op_one); |
| } |
| return false; |
| } |
| |
| //////////////////////////////////////////////////////////////// |
| |
| void |
| Resizer::repairClkInverters() |
| { |
| // Abbreviated copyState |
| db_network_ = sta_->getDbNetwork(); |
| sta_->ensureLevelized(); |
| graph_ = sta_->graph(); |
| ensureBlock(); |
| ensureDesignArea(); |
| for (Instance *inv : findClkInverters()) |
| cloneClkInverter(inv); |
| } |
| |
| InstanceSeq |
| Resizer::findClkInverters() |
| { |
| InstanceSeq clk_inverters; |
| ClkArrivalSearchPred srch_pred(this); |
| BfsFwdIterator bfs(BfsIndex::other, &srch_pred, this); |
| for (Clock *clk : sdc_->clks()) { |
| for (Pin *pin : clk->leafPins()) { |
| Vertex *vertex = graph_->pinDrvrVertex(pin); |
| bfs.enqueue(vertex); |
| } |
| } |
| while (bfs.hasNext()) { |
| Vertex *vertex = bfs.next(); |
| const Pin *pin = vertex->pin(); |
| Instance *inst = network_->instance(pin); |
| LibertyCell *lib_cell = network_->libertyCell(inst); |
| if (vertex->isDriver(network_) |
| && lib_cell |
| && lib_cell->isInverter()) { |
| clk_inverters.push_back(inst); |
| debugPrint(logger_, RSZ, "repair_clk_inverters", 2, "inverter {}", |
| network_->pathName(inst)); |
| } |
| if (!vertex->isRegClk()) |
| bfs.enqueueAdjacentVertices(vertex); |
| } |
| return clk_inverters; |
| } |
| |
| void |
| Resizer::cloneClkInverter(Instance *inv) |
| { |
| LibertyCell *inv_cell = network_->libertyCell(inv); |
| LibertyPort *in_port, *out_port; |
| inv_cell->bufferPorts(in_port, out_port); |
| Pin *in_pin = network_->findPin(inv, in_port); |
| Pin *out_pin = network_->findPin(inv, out_port); |
| Net *in_net = network_->net(in_pin); |
| dbNet *in_net_db = db_network_->staToDb(in_net); |
| Net *out_net = network_->isTopLevelPort(out_pin) |
| ? network_->net(network_->term(out_pin)) |
| : network_->net(out_pin); |
| if (out_net) { |
| const char *inv_name = network_->name(inv); |
| Instance *top_inst = network_->topInstance(); |
| NetConnectedPinIterator *load_iter = network_->pinIterator(out_net); |
| while (load_iter->hasNext()) { |
| Pin *load_pin = load_iter->next(); |
| if (load_pin != out_pin) { |
| string clone_name = makeUniqueInstName(inv_name, true); |
| Instance *clone = makeInstance(inv_cell, clone_name.c_str(), |
| top_inst); |
| Point clone_loc = db_network_->location(load_pin); |
| journalMakeBuffer(clone); |
| setLocation(clone, clone_loc); |
| |
| Net *clone_out_net = makeUniqueNet(); |
| dbNet *clone_out_net_db = db_network_->staToDb(clone_out_net); |
| clone_out_net_db->setSigType(in_net_db->getSigType()); |
| |
| Instance *load = network_->instance(load_pin); |
| sta_->connectPin(clone, in_port, in_net); |
| sta_->connectPin(clone, out_port, clone_out_net); |
| |
| // Connect load to clone |
| sta_->disconnectPin(load_pin); |
| Port *load_port = network_->port(load_pin); |
| sta_->connectPin(load, load_port, clone_out_net); |
| } |
| } |
| delete load_iter; |
| |
| // Delete inv |
| sta_->disconnectPin(in_pin); |
| sta_->disconnectPin(out_pin); |
| sta_->deleteNet(out_net); |
| parasitics_invalid_.erase(out_net); |
| sta_->deleteInstance(inv); |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////// |
| |
| // Journal to roll back changes (OpenDB not up to the task). |
| void |
| Resizer::journalBegin() |
| { |
| debugPrint(logger_, RSZ, "journal", 1, "journal begin"); |
| resized_inst_map_.clear(); |
| inserted_buffers_.clear(); |
| } |
| |
| void |
| Resizer::journalInstReplaceCellBefore(Instance *inst) |
| { |
| LibertyCell *lib_cell = network_->libertyCell(inst); |
| debugPrint(logger_, RSZ, "journal", 1, "journal replace {} ({})", |
| network_->pathName(inst), |
| lib_cell->name()); |
| // Do not clobber an existing checkpoint cell. |
| if (!resized_inst_map_.hasKey(inst)) |
| resized_inst_map_[inst] = lib_cell; |
| } |
| |
| void |
| Resizer::journalMakeBuffer(Instance *buffer) |
| { |
| debugPrint(logger_, RSZ, "journal", 1, "journal make_buffer {}", |
| network_->pathName(buffer)); |
| inserted_buffers_.insert(buffer); |
| } |
| |
| void |
| Resizer::journalRestore() |
| { |
| for (auto inst_cell : resized_inst_map_) { |
| Instance *inst = inst_cell.first; |
| if (!inserted_buffers_.hasKey(inst)) { |
| LibertyCell *lib_cell = inst_cell.second; |
| debugPrint(logger_, RSZ, "journal", 1, "journal restore {} ({})", |
| network_->pathName(inst), |
| lib_cell->name()); |
| replaceCell(inst, lib_cell, false); |
| resize_count_--; |
| } |
| } |
| for (Instance *buffer : inserted_buffers_) { |
| debugPrint(logger_, RSZ, "journal", 1, "journal remove {}", |
| network_->pathName(buffer)); |
| removeBuffer(buffer); |
| inserted_buffer_count_--; |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////// |
| |
| class SteinerRenderer : public gui::Renderer |
| { |
| public: |
| SteinerRenderer(); |
| void highlight(SteinerTree *tree); |
| virtual void drawObjects(gui::Painter& /* painter */) override; |
| |
| private: |
| SteinerTree *tree_; |
| }; |
| |
| void |
| Resizer::highlightSteiner(const Pin *drvr) |
| { |
| if (gui::Gui::enabled()) { |
| if (steiner_renderer_ == nullptr) { |
| steiner_renderer_ = new SteinerRenderer(); |
| gui_->registerRenderer(steiner_renderer_); |
| } |
| SteinerTree *tree = nullptr; |
| if (drvr) |
| tree = makeSteinerTree(drvr, false, max_steiner_pin_count_, |
| stt_builder_, db_network_, logger_); |
| steiner_renderer_->highlight(tree); |
| } |
| } |
| |
| SteinerRenderer::SteinerRenderer() : |
| tree_(nullptr) |
| { |
| } |
| |
| void |
| SteinerRenderer::highlight(SteinerTree *tree) |
| { |
| tree_ = tree; |
| } |
| |
| void |
| SteinerRenderer::drawObjects(gui::Painter &painter) |
| { |
| if (tree_) { |
| painter.setPen(gui::Painter::red, true); |
| for (int i = 0 ; i < tree_->branchCount(); ++i) { |
| Point pt1, pt2; |
| int steiner_pt1, steiner_pt2; |
| int wire_length; |
| tree_->branch(i, pt1, steiner_pt1, pt2, steiner_pt2, wire_length); |
| painter.drawLine(pt1, pt2); |
| } |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////// |
| |
| PinSet |
| Resizer::findFaninFanouts(PinSet *end_pins) |
| { |
| // Abbreviated copyState |
| db_network_ = sta_->getDbNetwork(); |
| sta_->ensureLevelized(); |
| graph_ = sta_->graph(); |
| |
| VertexSet ends; |
| for (Pin *pin : *end_pins) { |
| Vertex *end = graph_->pinLoadVertex(pin); |
| ends.insert(end); |
| } |
| PinSet fanin_fanout_pins; |
| VertexSet fanin_fanouts = findFaninFanouts(ends); |
| for (Vertex *vertex : fanin_fanouts) |
| fanin_fanout_pins.insert(vertex->pin()); |
| return fanin_fanout_pins; |
| } |
| |
| VertexSet |
| Resizer::findFaninFanouts(VertexSet &ends) |
| { |
| // Search backwards from ends to fanin register outputs and input ports. |
| VertexSet fanin_roots = findFaninRoots(ends); |
| // Search forward from register outputs. |
| VertexSet fanouts = findFanouts(fanin_roots); |
| return fanouts; |
| } |
| |
| // Find source pins for logic fanin of ends. |
| PinSet |
| Resizer::findFanins(PinSet *end_pins) |
| { |
| // Abbreviated copyState |
| db_network_ = sta_->getDbNetwork(); |
| sta_->ensureLevelized(); |
| graph_ = sta_->graph(); |
| |
| VertexSet ends; |
| for (Pin *pin : *end_pins) { |
| Vertex *end = graph_->pinLoadVertex(pin); |
| ends.insert(end); |
| } |
| |
| SearchPredNonReg2 pred(sta_); |
| BfsBkwdIterator iter(BfsIndex::other, &pred, this); |
| for (Vertex *vertex : ends) |
| iter.enqueueAdjacentVertices(vertex); |
| |
| PinSet fanins; |
| while (iter.hasNext()) { |
| Vertex *vertex = iter.next(); |
| if (isRegOutput(vertex) |
| || network_->isTopLevelPort(vertex->pin())) |
| continue; |
| else { |
| iter.enqueueAdjacentVertices(vertex); |
| fanins.insert(vertex->pin()); |
| } |
| } |
| return fanins; |
| } |
| |
| // Find roots for logic fanin of ends. |
| VertexSet |
| Resizer::findFaninRoots(VertexSet &ends) |
| { |
| SearchPredNonReg2 pred(sta_); |
| BfsBkwdIterator iter(BfsIndex::other, &pred, this); |
| for (Vertex *vertex : ends) |
| iter.enqueueAdjacentVertices(vertex); |
| |
| VertexSet roots; |
| while (iter.hasNext()) { |
| Vertex *vertex = iter.next(); |
| if (isRegOutput(vertex) |
| || network_->isTopLevelPort(vertex->pin())) |
| roots.insert(vertex); |
| else |
| iter.enqueueAdjacentVertices(vertex); |
| } |
| return roots; |
| } |
| |
| bool |
| Resizer::isRegOutput(Vertex *vertex) |
| { |
| LibertyPort *port = network_->libertyPort(vertex->pin()); |
| if (port) { |
| LibertyCell *cell = port->libertyCell(); |
| LibertyCellTimingArcSetIterator arc_set_iter(cell, nullptr, port); |
| while (arc_set_iter.hasNext()) { |
| TimingArcSet *arc_set = arc_set_iter.next(); |
| if (arc_set->role()->genericRole() == TimingRole::regClkToQ()) |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| VertexSet |
| Resizer::findFanouts(VertexSet ®_outs) |
| { |
| VertexSet fanouts; |
| sta::SearchPredNonLatch2 pred(sta_); |
| BfsFwdIterator iter(BfsIndex::other, &pred, this); |
| for (Vertex *reg_out : reg_outs) |
| iter.enqueueAdjacentVertices(reg_out); |
| |
| while (iter.hasNext()) { |
| Vertex *vertex = iter.next(); |
| if (!isRegister(vertex)) { |
| fanouts.insert(vertex); |
| iter.enqueueAdjacentVertices(vertex); |
| } |
| } |
| return fanouts; |
| } |
| |
| bool |
| Resizer::isRegister(Vertex *vertex) |
| { |
| LibertyPort *port = network_->libertyPort(vertex->pin()); |
| if (port) { |
| LibertyCell *cell = port->libertyCell(); |
| return cell && cell->hasSequentials(); |
| } |
| return false; |
| } |
| |
| Instance *Resizer::makeInstance(LibertyCell *cell, |
| const char *name, |
| Instance *parent) |
| { |
| debugPrint(logger_, RSZ, "make_instance", 1, "make instance {}", name); |
| Instance *inst = db_network_->makeInstance(cell, name, parent); |
| dbInst *db_inst = db_network_->staToDb(inst); |
| db_inst->setSourceType(odb::dbSourceType::TIMING); |
| return inst; |
| } |
| |
| } // namespace |