3386 lines
79 KiB
C++
3386 lines
79 KiB
C++
// OpenSTA, Static Timing Analyzer
|
|
// Copyright (c) 2025, Parallax Software, Inc.
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
//
|
|
// The origin of this software must not be misrepresented; you must not
|
|
// claim that you wrote the original software.
|
|
//
|
|
// Altered source versions must be plainly marked as such, and must not be
|
|
// misrepresented as being the original software.
|
|
//
|
|
// This notice may not be removed or altered from any source distribution.
|
|
|
|
#include "Liberty.hh"
|
|
|
|
#include "Mutex.hh"
|
|
#include "EnumNameMap.hh"
|
|
#include "Report.hh"
|
|
#include "Debug.hh"
|
|
#include "Error.hh"
|
|
#include "StringUtil.hh"
|
|
#include "StringSet.hh"
|
|
#include "PatternMatch.hh"
|
|
#include "Units.hh"
|
|
#include "Transition.hh"
|
|
#include "TimingRole.hh"
|
|
#include "FuncExpr.hh"
|
|
#include "TableModel.hh"
|
|
#include "TimingArc.hh"
|
|
#include "InternalPower.hh"
|
|
#include "LeakagePower.hh"
|
|
#include "Sequential.hh"
|
|
#include "Wireload.hh"
|
|
#include "EquivCells.hh"
|
|
#include "Network.hh"
|
|
#include "PortDirection.hh"
|
|
#include "Corner.hh"
|
|
#include "DcalcAnalysisPt.hh"
|
|
|
|
namespace sta {
|
|
|
|
using std::string;
|
|
|
|
typedef Set<LatchEnable*> LatchEnableSet;
|
|
|
|
void
|
|
initLiberty()
|
|
{
|
|
TimingArcSet::init();
|
|
}
|
|
|
|
void
|
|
deleteLiberty()
|
|
{
|
|
TimingArcSet::destroy();
|
|
}
|
|
|
|
LibertyLibrary::LibertyLibrary(const char *name,
|
|
const char *filename) :
|
|
ConcreteLibrary(name, filename, true),
|
|
units_(new Units()),
|
|
delay_model_type_(DelayModelType::table), // default
|
|
nominal_process_(0.0),
|
|
nominal_voltage_(0.0),
|
|
nominal_temperature_(0.0),
|
|
scale_factors_(nullptr),
|
|
default_input_pin_cap_(0.0),
|
|
default_output_pin_cap_(0.0),
|
|
default_bidirect_pin_cap_(0.0),
|
|
default_fanout_load_(0.0),
|
|
default_fanout_load_exists_(false),
|
|
default_max_cap_(0.0),
|
|
default_max_cap_exists_(false),
|
|
default_max_fanout_(0.0),
|
|
default_max_fanout_exists_(false),
|
|
default_max_slew_(0.0),
|
|
default_max_slew_exists_(false),
|
|
slew_derate_from_library_(1.0),
|
|
default_wire_load_(nullptr),
|
|
default_wire_load_mode_(WireloadMode::unknown),
|
|
default_wire_load_selection_(nullptr),
|
|
default_operating_conditions_(nullptr),
|
|
ocv_arc_depth_(0.0),
|
|
default_ocv_derate_(nullptr),
|
|
buffers_(nullptr),
|
|
inverters_(nullptr),
|
|
driver_waveform_default_(nullptr)
|
|
{
|
|
// Scalar templates are builtin.
|
|
for (int i = 0; i != table_template_type_count; i++) {
|
|
TableTemplateType type = static_cast<TableTemplateType>(i);
|
|
TableTemplate *scalar_template = new TableTemplate("scalar", nullptr,
|
|
nullptr, nullptr);
|
|
addTableTemplate(scalar_template, type);
|
|
}
|
|
|
|
for (auto rf_index : RiseFall::rangeIndex()) {
|
|
wire_slew_degradation_tbls_[rf_index] = nullptr;
|
|
input_threshold_[rf_index] = input_threshold_default_;
|
|
output_threshold_[rf_index] = output_threshold_default_;
|
|
slew_lower_threshold_[rf_index] = slew_lower_threshold_default_;
|
|
slew_upper_threshold_[rf_index] = slew_upper_threshold_default_;
|
|
}
|
|
}
|
|
|
|
LibertyLibrary::~LibertyLibrary()
|
|
{
|
|
bus_dcls_.deleteContents();
|
|
for (int i = 0; i < table_template_type_count; i++)
|
|
template_maps_[i].deleteContents();
|
|
scale_factors_map_.deleteContents();
|
|
delete scale_factors_;
|
|
|
|
for (auto rf_index : RiseFall::rangeIndex()) {
|
|
TableModel *model = wire_slew_degradation_tbls_[rf_index];
|
|
delete model;
|
|
}
|
|
operating_conditions_.deleteContents();
|
|
wireloads_.deleteContents();
|
|
wire_load_selections_.deleteContents();
|
|
delete units_;
|
|
// Also deletes default_ocv_derate_
|
|
ocv_derate_map_.deleteContents();
|
|
|
|
delete buffers_;
|
|
delete inverters_;
|
|
driver_waveform_map_.deleteContents();
|
|
delete driver_waveform_default_;
|
|
}
|
|
|
|
LibertyCell *
|
|
LibertyLibrary::findLibertyCell(const char *name) const
|
|
{
|
|
return static_cast<LibertyCell*>(findCell(name));
|
|
}
|
|
|
|
LibertyCellSeq
|
|
LibertyLibrary::findLibertyCellsMatching(PatternMatch *pattern)
|
|
{
|
|
LibertyCellSeq matches;
|
|
LibertyCellIterator cell_iter(this);
|
|
while (cell_iter.hasNext()) {
|
|
LibertyCell *cell = cell_iter.next();
|
|
if (pattern->match(cell->name()))
|
|
matches.push_back(cell);
|
|
}
|
|
return matches;
|
|
}
|
|
|
|
LibertyCellSeq *
|
|
LibertyLibrary::inverters()
|
|
{
|
|
if (inverters_ == nullptr) {
|
|
inverters_ = new LibertyCellSeq;
|
|
LibertyCellIterator cell_iter(this);
|
|
while (cell_iter.hasNext()) {
|
|
LibertyCell *cell = cell_iter.next();
|
|
if (!cell->dontUse()
|
|
&& cell->isInverter())
|
|
inverters_->push_back(cell);
|
|
}
|
|
}
|
|
return inverters_;
|
|
}
|
|
|
|
LibertyCellSeq *
|
|
LibertyLibrary::buffers()
|
|
{
|
|
if (buffers_ == nullptr) {
|
|
buffers_ = new LibertyCellSeq;
|
|
LibertyCellIterator cell_iter(this);
|
|
while (cell_iter.hasNext()) {
|
|
LibertyCell *cell = cell_iter.next();
|
|
if (!cell->dontUse()
|
|
&& cell->isBuffer())
|
|
buffers_->push_back(cell);
|
|
}
|
|
}
|
|
return buffers_;
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::setDelayModelType(DelayModelType type)
|
|
{
|
|
delay_model_type_ = type;
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::addBusDcl(BusDcl *bus_dcl)
|
|
{
|
|
bus_dcls_[bus_dcl->name()] = bus_dcl;
|
|
}
|
|
|
|
BusDcl *
|
|
LibertyLibrary::findBusDcl(const char *name) const
|
|
{
|
|
return bus_dcls_.findKey(name);
|
|
}
|
|
|
|
BusDclSeq
|
|
LibertyLibrary::busDcls() const
|
|
{
|
|
BusDclSeq dcls;
|
|
for (auto [name, dcl] : bus_dcls_)
|
|
dcls.push_back(dcl);
|
|
return dcls;
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::addTableTemplate(TableTemplate *tbl_template,
|
|
TableTemplateType type)
|
|
{
|
|
template_maps_[int(type)][tbl_template->name()] = tbl_template;
|
|
}
|
|
|
|
TableTemplate *
|
|
LibertyLibrary::findTableTemplate(const char *name,
|
|
TableTemplateType type)
|
|
{
|
|
return template_maps_[int(type)].findKey(name);
|
|
}
|
|
|
|
TableTemplateSeq
|
|
LibertyLibrary::tableTemplates() const
|
|
{
|
|
TableTemplateSeq tbl_templates;
|
|
for (int type = 0; type < table_template_type_count; type++) {
|
|
for (auto [name, tbl_template] : template_maps_[type])
|
|
tbl_templates.push_back(tbl_template);
|
|
}
|
|
return tbl_templates;
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::setNominalProcess(float process)
|
|
{
|
|
nominal_process_ = process;
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::setNominalVoltage(float voltage)
|
|
{
|
|
nominal_voltage_ = voltage;
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::setNominalTemperature(float temperature)
|
|
{
|
|
nominal_temperature_ = temperature;
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::setScaleFactors(ScaleFactors *scales)
|
|
{
|
|
scale_factors_ = scales;
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::addScaleFactors(ScaleFactors *scales)
|
|
{
|
|
scale_factors_map_[scales->name()] = scales;
|
|
}
|
|
|
|
ScaleFactors *
|
|
LibertyLibrary::findScaleFactors(const char *name)
|
|
{
|
|
return scale_factors_map_[name];
|
|
}
|
|
|
|
float
|
|
LibertyLibrary::scaleFactor(ScaleFactorType type,
|
|
const Pvt *pvt) const
|
|
{
|
|
return scaleFactor(type, 0, nullptr, pvt);
|
|
}
|
|
|
|
float
|
|
LibertyLibrary::scaleFactor(ScaleFactorType type,
|
|
const LibertyCell *cell,
|
|
const Pvt *pvt) const
|
|
{
|
|
return scaleFactor(type, 0, cell, pvt);
|
|
}
|
|
|
|
float
|
|
LibertyLibrary::scaleFactor(ScaleFactorType type,
|
|
int rf_index,
|
|
const LibertyCell *cell,
|
|
const Pvt *pvt) const
|
|
{
|
|
if (pvt == nullptr)
|
|
pvt = default_operating_conditions_;
|
|
// If there is no operating condition, nominal pvt values are used.
|
|
// All scale factors are unity for nominal pvt.
|
|
if (pvt) {
|
|
ScaleFactors *scale_factors = nullptr;
|
|
// Cell level scale factors have precedence over library scale factors.
|
|
if (cell)
|
|
scale_factors = cell->scaleFactors();
|
|
if (scale_factors == nullptr)
|
|
scale_factors = scale_factors_;
|
|
if (scale_factors) {
|
|
float process_scale = 1.0F + (pvt->process() - nominal_process_)
|
|
* scale_factors->scale(type, ScaleFactorPvt::process, rf_index);
|
|
float temp_scale = 1.0F + (pvt->temperature() - nominal_temperature_)
|
|
* scale_factors->scale(type, ScaleFactorPvt::temp, rf_index);
|
|
float volt_scale = 1.0F + (pvt->voltage() - nominal_voltage_)
|
|
* scale_factors->scale(type, ScaleFactorPvt::volt, rf_index);
|
|
float scale = process_scale * temp_scale * volt_scale;
|
|
return scale;
|
|
}
|
|
}
|
|
return 1.0F;
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::setWireSlewDegradationTable(TableModel *model,
|
|
const RiseFall *rf)
|
|
{
|
|
int rf_index = rf->index();
|
|
if (wire_slew_degradation_tbls_[rf_index])
|
|
delete wire_slew_degradation_tbls_[rf_index];
|
|
wire_slew_degradation_tbls_[rf_index] = model;
|
|
}
|
|
|
|
TableModel *
|
|
LibertyLibrary::wireSlewDegradationTable(const RiseFall *rf) const
|
|
{
|
|
return wire_slew_degradation_tbls_[rf->index()];
|
|
}
|
|
|
|
float
|
|
LibertyLibrary::degradeWireSlew(const RiseFall *rf,
|
|
float in_slew,
|
|
float wire_delay) const
|
|
{
|
|
const TableModel *model = wireSlewDegradationTable(rf);
|
|
if (model)
|
|
return degradeWireSlew(model, in_slew, wire_delay);
|
|
else
|
|
return in_slew;
|
|
}
|
|
|
|
float
|
|
LibertyLibrary::degradeWireSlew(const TableModel *model,
|
|
float in_slew,
|
|
float wire_delay) const
|
|
{
|
|
switch (model->order()) {
|
|
case 0:
|
|
return model->findValue(0.0, 0.0, 0.0);
|
|
case 1: {
|
|
const TableAxis *axis1 = model->axis1();
|
|
TableAxisVariable var1 = axis1->variable();
|
|
if (var1 == TableAxisVariable::output_pin_transition)
|
|
return model->findValue(in_slew, 0.0, 0.0);
|
|
else if (var1 == TableAxisVariable::connect_delay)
|
|
return model->findValue(wire_delay, 0.0, 0.0);
|
|
else {
|
|
criticalError(1116, "unsupported slew degradation table axes");
|
|
return 0.0;
|
|
}
|
|
}
|
|
case 2: {
|
|
const TableAxis *axis1 = model->axis1();
|
|
const TableAxis * axis2 = model->axis2();
|
|
TableAxisVariable var1 = axis1->variable();
|
|
TableAxisVariable var2 = axis2->variable();
|
|
if (var1 == TableAxisVariable::output_pin_transition
|
|
&& var2 == TableAxisVariable::connect_delay)
|
|
return model->findValue(in_slew, wire_delay, 0.0);
|
|
else if (var1 == TableAxisVariable::connect_delay
|
|
&& var2 == TableAxisVariable::output_pin_transition)
|
|
return model->findValue(wire_delay, in_slew, 0.0);
|
|
else {
|
|
criticalError(1117, "unsupported slew degradation table axes");
|
|
return 0.0;
|
|
}
|
|
}
|
|
default:
|
|
criticalError(1118, "unsupported slew degradation table order");
|
|
return 0.0;
|
|
}
|
|
}
|
|
|
|
// Check for supported axis variables.
|
|
// Return true if axes are supported.
|
|
bool
|
|
LibertyLibrary::checkSlewDegradationAxes(const TablePtr &table)
|
|
{
|
|
switch (table->order()) {
|
|
case 0:
|
|
return true;
|
|
case 1: {
|
|
const TableAxis *axis1 = table->axis1();
|
|
TableAxisVariable var1 = axis1->variable();
|
|
return var1 == TableAxisVariable::output_pin_transition
|
|
|| var1 == TableAxisVariable::connect_delay;
|
|
}
|
|
case 2: {
|
|
const TableAxis *axis1 = table->axis1();
|
|
const TableAxis *axis2 = table->axis2();
|
|
TableAxisVariable var1 = axis1->variable();
|
|
TableAxisVariable var2 = axis2->variable();
|
|
return (var1 == TableAxisVariable::output_pin_transition
|
|
&& var2 == TableAxisVariable::connect_delay)
|
|
|| (var1 == TableAxisVariable::connect_delay
|
|
&& var2 == TableAxisVariable::output_pin_transition);
|
|
}
|
|
default:
|
|
criticalError(1119, "unsupported slew degradation table axes");
|
|
return 0.0;
|
|
}
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::defaultMaxFanout(float &fanout,
|
|
bool &exists) const
|
|
{
|
|
fanout = default_max_fanout_;
|
|
exists = default_max_fanout_exists_;
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::setDefaultMaxFanout(float fanout)
|
|
{
|
|
default_max_fanout_ = fanout;
|
|
default_max_fanout_exists_ = true;
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::defaultMaxSlew(float &slew,
|
|
bool &exists) const
|
|
{
|
|
slew = default_max_slew_;
|
|
exists = default_max_slew_exists_;
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::setDefaultMaxSlew(float slew)
|
|
{
|
|
default_max_slew_ = slew;
|
|
default_max_slew_exists_ = true;
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::defaultMaxCapacitance(float &cap,
|
|
bool &exists) const
|
|
{
|
|
cap = default_max_cap_;
|
|
exists = default_max_cap_exists_;
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::setDefaultMaxCapacitance(float cap)
|
|
{
|
|
default_max_cap_ = cap;
|
|
default_max_cap_exists_ = true;
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::defaultFanoutLoad(// Return values.
|
|
float &fanout,
|
|
bool &exists) const
|
|
{
|
|
fanout = default_fanout_load_;
|
|
exists = default_fanout_load_exists_;
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::setDefaultFanoutLoad(float load)
|
|
{
|
|
default_fanout_load_ = load;
|
|
default_fanout_load_exists_ = true;
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::setDefaultBidirectPinCap(float cap)
|
|
{
|
|
default_bidirect_pin_cap_ = cap;
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::setDefaultInputPinCap(float cap)
|
|
{
|
|
default_input_pin_cap_ = cap;
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::setDefaultOutputPinCap(float cap)
|
|
{
|
|
default_output_pin_cap_ = cap;
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::defaultIntrinsic(const RiseFall *rf,
|
|
// Return values.
|
|
float &intrinsic,
|
|
bool &exists) const
|
|
{
|
|
default_intrinsic_.value(rf, intrinsic, exists);
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::setDefaultIntrinsic(const RiseFall *rf,
|
|
float value)
|
|
{
|
|
default_intrinsic_.setValue(rf, value);
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::defaultPinResistance(const RiseFall *rf,
|
|
const PortDirection *dir,
|
|
// Return values.
|
|
float &res,
|
|
bool &exists) const
|
|
{
|
|
if (dir->isAnyTristate())
|
|
defaultBidirectPinRes(rf, res, exists);
|
|
else
|
|
defaultOutputPinRes(rf, res, exists);
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::defaultBidirectPinRes(const RiseFall *rf,
|
|
// Return values.
|
|
float &res,
|
|
bool &exists) const
|
|
{
|
|
return default_inout_pin_res_.value(rf, res, exists);
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::setDefaultBidirectPinRes(const RiseFall *rf,
|
|
float value)
|
|
{
|
|
default_inout_pin_res_.setValue(rf, value);
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::defaultOutputPinRes(const RiseFall *rf,
|
|
// Return values.
|
|
float &res,
|
|
bool &exists) const
|
|
{
|
|
default_output_pin_res_.value(rf, res, exists);
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::setDefaultOutputPinRes(const RiseFall *rf,
|
|
float value)
|
|
{
|
|
default_output_pin_res_.setValue(rf, value);
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::addWireload(Wireload *wireload)
|
|
{
|
|
wireloads_[wireload->name()] = wireload;
|
|
}
|
|
|
|
Wireload *
|
|
LibertyLibrary::findWireload(const char *name) const
|
|
{
|
|
return wireloads_.findKey(name);
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::setDefaultWireload(Wireload *wireload)
|
|
{
|
|
default_wire_load_ = wireload;
|
|
}
|
|
|
|
Wireload *
|
|
LibertyLibrary::defaultWireload() const
|
|
{
|
|
return default_wire_load_;
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::addWireloadSelection(WireloadSelection *selection)
|
|
{
|
|
wire_load_selections_[selection->name()] = selection;
|
|
}
|
|
|
|
WireloadSelection *
|
|
LibertyLibrary::findWireloadSelection(const char *name) const
|
|
{
|
|
return wire_load_selections_.findKey(name);
|
|
}
|
|
|
|
WireloadSelection *
|
|
LibertyLibrary::defaultWireloadSelection() const
|
|
{
|
|
return default_wire_load_selection_;
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::setDefaultWireloadSelection(WireloadSelection *selection)
|
|
{
|
|
default_wire_load_selection_ = selection;
|
|
}
|
|
|
|
WireloadMode
|
|
LibertyLibrary::defaultWireloadMode() const
|
|
{
|
|
return default_wire_load_mode_;
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::setDefaultWireloadMode(WireloadMode mode)
|
|
{
|
|
default_wire_load_mode_ = mode;
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::addOperatingConditions(OperatingConditions *op_cond)
|
|
{
|
|
operating_conditions_[op_cond->name()] = op_cond;
|
|
}
|
|
|
|
OperatingConditions *
|
|
LibertyLibrary::findOperatingConditions(const char *name)
|
|
{
|
|
return operating_conditions_.findKey(name);
|
|
}
|
|
|
|
OperatingConditions *
|
|
LibertyLibrary::defaultOperatingConditions() const
|
|
{
|
|
return default_operating_conditions_;
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::setDefaultOperatingConditions(OperatingConditions *op_cond)
|
|
{
|
|
default_operating_conditions_ = op_cond;
|
|
}
|
|
|
|
float
|
|
LibertyLibrary::inputThreshold(const RiseFall *rf) const
|
|
{
|
|
return input_threshold_[rf->index()];
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::setInputThreshold(const RiseFall *rf,
|
|
float th)
|
|
{
|
|
input_threshold_[rf->index()] = th;
|
|
}
|
|
|
|
float
|
|
LibertyLibrary::outputThreshold(const RiseFall *rf) const
|
|
{
|
|
return output_threshold_[rf->index()];
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::setOutputThreshold(const RiseFall *rf,
|
|
float th)
|
|
{
|
|
output_threshold_[rf->index()] = th;
|
|
}
|
|
|
|
float
|
|
LibertyLibrary::slewLowerThreshold(const RiseFall *rf) const
|
|
{
|
|
return slew_lower_threshold_[rf->index()];
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::setSlewLowerThreshold(const RiseFall *rf,
|
|
float th)
|
|
{
|
|
slew_lower_threshold_[rf->index()] = th;
|
|
}
|
|
|
|
float
|
|
LibertyLibrary::slewUpperThreshold(const RiseFall *rf) const
|
|
{
|
|
return slew_upper_threshold_[rf->index()];
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::setSlewUpperThreshold(const RiseFall *rf,
|
|
float th)
|
|
{
|
|
slew_upper_threshold_[rf->index()] = th;
|
|
}
|
|
|
|
float
|
|
LibertyLibrary::slewDerateFromLibrary() const
|
|
{
|
|
return slew_derate_from_library_;
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::setSlewDerateFromLibrary(float derate)
|
|
{
|
|
slew_derate_from_library_ = derate;
|
|
}
|
|
|
|
LibertyCell *
|
|
LibertyLibrary::makeScaledCell(const char *name,
|
|
const char *filename)
|
|
{
|
|
LibertyCell *cell = new LibertyCell(this, name, filename);
|
|
return cell;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
void
|
|
LibertyLibrary::makeCornerMap(LibertyLibrary *lib,
|
|
int ap_index,
|
|
Network *network,
|
|
Report *report)
|
|
{
|
|
LibertyCellIterator cell_iter(lib);
|
|
while (cell_iter.hasNext()) {
|
|
LibertyCell *cell = cell_iter.next();
|
|
const char *name = cell->name();
|
|
LibertyCell *link_cell = network->findLibertyCell(name);
|
|
if (link_cell)
|
|
makeCornerMap(link_cell, cell, ap_index, report);
|
|
}
|
|
}
|
|
|
|
// Map a cell linked in the network to the corresponding liberty cell
|
|
// to use for delay calculation at a corner.
|
|
void
|
|
LibertyLibrary::makeCornerMap(LibertyCell *link_cell,
|
|
LibertyCell *corner_cell,
|
|
int ap_index,
|
|
Report *report)
|
|
{
|
|
link_cell->setCornerCell(corner_cell, ap_index);
|
|
makeCornerMap(link_cell, corner_cell, true, ap_index, report);
|
|
// Check for brain damage in the other direction.
|
|
makeCornerMap(corner_cell, link_cell, false, ap_index, report);
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::makeCornerMap(LibertyCell *cell1,
|
|
LibertyCell *cell2,
|
|
bool link,
|
|
int ap_index,
|
|
Report *report)
|
|
{
|
|
LibertyCellPortBitIterator port_iter1(cell1);
|
|
while (port_iter1.hasNext()) {
|
|
LibertyPort *port1 = port_iter1.next();
|
|
const char *port_name = port1->name();
|
|
LibertyPort *port2 = cell2->findLibertyPort(port_name);
|
|
if (port2) {
|
|
if (link)
|
|
port1->setCornerPort(port2, ap_index);
|
|
}
|
|
else
|
|
report->warn(1110, "cell %s/%s port %s not found in cell %s/%s.",
|
|
cell1->library()->name(),
|
|
cell1->name(),
|
|
port_name,
|
|
cell2->library()->name(),
|
|
cell2->name());
|
|
}
|
|
|
|
for (TimingArcSet *arc_set1 : cell1->timing_arc_sets_) {
|
|
TimingArcSet *arc_set2 = cell2->findTimingArcSet(arc_set1);
|
|
if (arc_set2) {
|
|
if (link) {
|
|
const TimingArcSeq &arcs1 = arc_set1->arcs();
|
|
const TimingArcSeq &arcs2 = arc_set2->arcs();
|
|
auto arc_itr1 = arcs1.begin(), arc_itr2 = arcs2.begin();
|
|
for (;
|
|
arc_itr1 != arcs1.end() && arc_itr2 != arcs2.end();
|
|
arc_itr1++, arc_itr2++) {
|
|
TimingArc *arc1 = *arc_itr1;
|
|
TimingArc *arc2 = *arc_itr2;
|
|
if (TimingArc::equiv(arc1, arc2))
|
|
arc1->setCornerArc(arc2, ap_index);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
report->warn(1111, "cell %s/%s %s -> %s timing group %s not found in cell %s/%s.",
|
|
cell1->library()->name(),
|
|
cell1->name(),
|
|
arc_set1->from() ? arc_set1->from()->name() : "",
|
|
arc_set1->to()->name(),
|
|
arc_set1->role()->to_string().c_str(),
|
|
cell2->library()->name(),
|
|
cell2->name());
|
|
}
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::checkCorners(LibertyCell *cell,
|
|
Corners *corners,
|
|
Report *report)
|
|
{
|
|
for (const Corner *corner : *corners) {
|
|
for (auto min_max : MinMax::range()) {
|
|
if (!cell->checkCornerCell(corner, min_max))
|
|
report->error(1112, "Liberty cell %s/%s for corner %s/%s not found.",
|
|
cell->libertyLibrary()->name(),
|
|
cell->name(),
|
|
corner->name(),
|
|
min_max->to_string().c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
float
|
|
LibertyLibrary::ocvArcDepth() const
|
|
{
|
|
return ocv_arc_depth_;
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::setOcvArcDepth(float depth)
|
|
{
|
|
ocv_arc_depth_ = depth;
|
|
}
|
|
|
|
OcvDerate *
|
|
LibertyLibrary::defaultOcvDerate() const
|
|
{
|
|
return default_ocv_derate_;
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::setDefaultOcvDerate(OcvDerate *derate)
|
|
{
|
|
default_ocv_derate_ = derate;
|
|
}
|
|
|
|
OcvDerate *
|
|
LibertyLibrary::findOcvDerate(const char *derate_name)
|
|
{
|
|
return ocv_derate_map_.findKey(derate_name);
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::addOcvDerate(OcvDerate *derate)
|
|
{
|
|
ocv_derate_map_[derate->name()] = derate;
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::addSupplyVoltage(const char *supply_name,
|
|
float voltage)
|
|
{
|
|
supply_voltage_map_[supply_name] = voltage;
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::supplyVoltage(const char *supply_name,
|
|
// Return value.
|
|
float &voltage,
|
|
bool &exists) const
|
|
{
|
|
supply_voltage_map_.findKey(supply_name, voltage, exists);
|
|
}
|
|
|
|
bool
|
|
LibertyLibrary::supplyExists(const char *supply_name) const
|
|
{
|
|
return supply_voltage_map_.hasKey(supply_name);
|
|
}
|
|
|
|
DriverWaveform *
|
|
LibertyLibrary::findDriverWaveform(const char *name)
|
|
{
|
|
return driver_waveform_map_[name];
|
|
}
|
|
|
|
void
|
|
LibertyLibrary::addDriverWaveform(DriverWaveform *driver_waveform)
|
|
{
|
|
if (driver_waveform->name())
|
|
driver_waveform_map_[driver_waveform->name()] = driver_waveform;
|
|
else {
|
|
delete driver_waveform_default_;
|
|
driver_waveform_default_ = driver_waveform;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
LibertyCellIterator::LibertyCellIterator(const LibertyLibrary *library) :
|
|
iter_(library->cell_map_)
|
|
{
|
|
}
|
|
|
|
bool
|
|
LibertyCellIterator::hasNext()
|
|
{
|
|
return iter_.hasNext();
|
|
}
|
|
|
|
LibertyCell *
|
|
LibertyCellIterator::next()
|
|
{
|
|
return static_cast<LibertyCell*>(iter_.next());
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
LibertyCell::LibertyCell(LibertyLibrary *library,
|
|
const char *name,
|
|
const char *filename) :
|
|
ConcreteCell(name, filename, true, library),
|
|
liberty_library_(library),
|
|
area_(0.0),
|
|
dont_use_(false),
|
|
is_macro_(false),
|
|
is_memory_(false),
|
|
is_pad_(false),
|
|
is_clock_cell_(false),
|
|
is_level_shifter_(false),
|
|
level_shifter_type_(LevelShifterType::HL_LH),
|
|
is_isolation_cell_(false),
|
|
always_on_(false),
|
|
switch_cell_type_(SwitchCellType::fine_grain),
|
|
interface_timing_(false),
|
|
clock_gate_type_(ClockGateType::none),
|
|
has_infered_reg_timing_arcs_(false),
|
|
statetable_(nullptr),
|
|
scale_factors_(nullptr),
|
|
test_cell_(nullptr),
|
|
ocv_arc_depth_(0.0),
|
|
ocv_derate_(nullptr),
|
|
is_disabled_constraint_(false),
|
|
leakage_power_(0.0),
|
|
leakage_power_exists_(false),
|
|
has_internal_ports_(false),
|
|
have_voltage_waveforms_(false)
|
|
{
|
|
liberty_cell_ = this;
|
|
}
|
|
|
|
LibertyCell::~LibertyCell()
|
|
{
|
|
mode_defs_.deleteContents();
|
|
latch_enables_.deleteContents();
|
|
|
|
timing_arc_sets_.deleteContents();
|
|
port_timing_arc_set_map_.deleteContents();
|
|
timing_arc_set_from_map_.deleteContents();
|
|
timing_arc_set_to_map_.deleteContents();
|
|
|
|
deleteInternalPowerAttrs();
|
|
internal_powers_.deleteContents();
|
|
leakage_powers_.deleteContents();
|
|
|
|
sequentials_.deleteContents();
|
|
delete statetable_;
|
|
bus_dcls_.deleteContents();
|
|
scaled_cells_.deleteContents();
|
|
|
|
delete test_cell_;
|
|
ocv_derate_map_.deleteContents();
|
|
|
|
pg_port_map_.deleteContents();
|
|
}
|
|
|
|
LibertyPort *
|
|
LibertyCell::findLibertyPort(const char *name) const
|
|
{
|
|
return static_cast<LibertyPort*>(findPort(name));
|
|
}
|
|
|
|
LibertyPortSeq
|
|
LibertyCell::findLibertyPortsMatching(PatternMatch *pattern) const
|
|
{
|
|
LibertyPortSeq matches;
|
|
LibertyCellPortIterator port_iter(this);
|
|
while (port_iter.hasNext()) {
|
|
LibertyPort *port = port_iter.next();
|
|
if (pattern->match(port->name()))
|
|
matches.push_back(port);
|
|
if (port->hasMembers()) {
|
|
LibertyPortMemberIterator port_iter2(port);
|
|
while (port_iter2.hasNext()) {
|
|
LibertyPort *port2 = port_iter2.next();
|
|
if (pattern->match(port2->name()))
|
|
matches.push_back(port2);
|
|
}
|
|
}
|
|
}
|
|
return matches;
|
|
}
|
|
|
|
void
|
|
LibertyCell::addPort(ConcretePort *port)
|
|
{
|
|
ConcreteCell::addPort(port);
|
|
if (port->direction()->isInternal())
|
|
has_internal_ports_ = true;
|
|
}
|
|
|
|
|
|
void
|
|
LibertyCell::setHasInternalPorts(bool has_internal)
|
|
{
|
|
has_internal_ports_ = has_internal;
|
|
}
|
|
|
|
void
|
|
LibertyCell::addPgPort(LibertyPgPort *pg_port)
|
|
{
|
|
pg_port_map_[pg_port->name()] = pg_port;
|
|
}
|
|
|
|
LibertyPgPort *
|
|
LibertyCell::findPgPort(const char *name) const
|
|
{
|
|
return pg_port_map_.findKey(name);
|
|
}
|
|
|
|
ModeDef *
|
|
LibertyCell::makeModeDef(const char *name)
|
|
{
|
|
ModeDef *mode = new ModeDef(name);
|
|
mode_defs_[mode->name()] = mode;
|
|
return mode;
|
|
}
|
|
|
|
ModeDef *
|
|
LibertyCell::findModeDef(const char *name)
|
|
{
|
|
return mode_defs_.findKey(name);
|
|
}
|
|
|
|
void
|
|
LibertyCell::setScaleFactors(ScaleFactors *scale_factors)
|
|
{
|
|
scale_factors_ = scale_factors;
|
|
}
|
|
|
|
void
|
|
LibertyCell::addBusDcl(BusDcl *bus_dcl)
|
|
{
|
|
bus_dcls_[bus_dcl->name()] = bus_dcl;
|
|
}
|
|
|
|
BusDcl *
|
|
LibertyCell::findBusDcl(const char *name) const
|
|
{
|
|
return bus_dcls_.findKey(name);
|
|
}
|
|
|
|
void
|
|
LibertyCell::setArea(float area)
|
|
{
|
|
area_ = area;
|
|
}
|
|
|
|
void
|
|
LibertyCell::setDontUse(bool dont_use)
|
|
{
|
|
dont_use_ = dont_use;
|
|
}
|
|
|
|
void
|
|
LibertyCell::setIsMacro(bool is_macro)
|
|
{
|
|
is_macro_ = is_macro;
|
|
}
|
|
|
|
void
|
|
LibertyCell::setIsMemory(bool is_memory)
|
|
{
|
|
is_memory_ = is_memory;
|
|
}
|
|
|
|
void
|
|
LibertyCell::setIsPad(bool is_pad)
|
|
{
|
|
is_pad_ = is_pad;
|
|
}
|
|
|
|
void
|
|
LibertyCell::setIsClockCell(bool is_clock_cell)
|
|
{
|
|
is_clock_cell_ = is_clock_cell;
|
|
}
|
|
|
|
void
|
|
LibertyCell::setIsLevelShifter(bool is_level_shifter)
|
|
{
|
|
is_level_shifter_ = is_level_shifter;
|
|
}
|
|
|
|
void
|
|
LibertyCell::setLevelShifterType(LevelShifterType level_shifter_type)
|
|
{
|
|
level_shifter_type_ = level_shifter_type;
|
|
}
|
|
|
|
void
|
|
LibertyCell::setIsIsolationCell(bool is_isolation_cell)
|
|
{
|
|
is_isolation_cell_ = is_isolation_cell;
|
|
}
|
|
|
|
void
|
|
LibertyCell::setAlwaysOn(bool always_on)
|
|
{
|
|
always_on_ = always_on;
|
|
}
|
|
|
|
void
|
|
LibertyCell::setSwitchCellType(SwitchCellType switch_cell_type)
|
|
{
|
|
switch_cell_type_ = switch_cell_type;
|
|
}
|
|
|
|
void
|
|
LibertyCell::setInterfaceTiming(bool value)
|
|
{
|
|
interface_timing_ = value;
|
|
}
|
|
|
|
bool
|
|
LibertyCell::isClockGateLatchPosedge() const
|
|
{
|
|
return clock_gate_type_ == ClockGateType::latch_posedge;
|
|
}
|
|
|
|
bool
|
|
LibertyCell::isClockGateLatchNegedge() const
|
|
{
|
|
return clock_gate_type_ == ClockGateType::latch_posedge;
|
|
}
|
|
|
|
bool
|
|
LibertyCell::isClockGateOther() const
|
|
{
|
|
return clock_gate_type_ == ClockGateType::other;
|
|
}
|
|
|
|
bool
|
|
LibertyCell::isClockGate() const
|
|
{
|
|
return clock_gate_type_ != ClockGateType::none;
|
|
}
|
|
|
|
void
|
|
LibertyCell::setClockGateType(ClockGateType type)
|
|
{
|
|
clock_gate_type_ = type;
|
|
}
|
|
|
|
bool
|
|
LibertyCell::isBuffer() const
|
|
{
|
|
LibertyPort *input;
|
|
LibertyPort *output;
|
|
bufferPorts(input, output);
|
|
return input && output
|
|
&& hasBufferFunc(input, output)
|
|
&& !is_level_shifter_
|
|
&& !is_pad_;
|
|
}
|
|
|
|
bool
|
|
LibertyCell::hasBufferFunc(const LibertyPort *input,
|
|
const LibertyPort *output) const
|
|
{
|
|
FuncExpr *func = output->function();
|
|
return func
|
|
&& func->op() == FuncExpr::op_port
|
|
&& func->port() == input;
|
|
}
|
|
|
|
bool
|
|
LibertyCell::isInverter() const
|
|
{
|
|
LibertyPort *input;
|
|
LibertyPort *output;
|
|
bufferPorts(input, output);
|
|
return input && output
|
|
&& hasInverterFunc(input, output)
|
|
&& !is_level_shifter_
|
|
&& !is_pad_;
|
|
}
|
|
|
|
bool
|
|
LibertyCell::hasInverterFunc(const LibertyPort *input,
|
|
const LibertyPort *output) const
|
|
{
|
|
FuncExpr *func = output->function();
|
|
return func
|
|
&& func->op() == FuncExpr::op_not
|
|
&& func->left()->op() == FuncExpr::op_port
|
|
&& func->left()->port() == input;
|
|
}
|
|
|
|
void
|
|
LibertyCell::bufferPorts(// Return values.
|
|
LibertyPort *&input,
|
|
LibertyPort *&output) const
|
|
{
|
|
input = nullptr;
|
|
output = nullptr;
|
|
for (ConcretePort *cport : ports_) {
|
|
LibertyPort *port = static_cast<LibertyPort*>(cport);
|
|
PortDirection *dir = port->direction();
|
|
if (dir->isInput()) {
|
|
if (input) {
|
|
// More than one input.
|
|
input = nullptr;
|
|
output = nullptr;
|
|
break;
|
|
}
|
|
input = port;
|
|
}
|
|
else if (dir->isOutput()) {
|
|
if (output) {
|
|
// More than one output.
|
|
input = nullptr;
|
|
output = nullptr;
|
|
break;
|
|
}
|
|
output = port;
|
|
}
|
|
else if (!dir->isPowerGround()) {
|
|
input = nullptr;
|
|
output = nullptr;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
unsigned
|
|
LibertyCell::addTimingArcSet(TimingArcSet *arc_set)
|
|
{
|
|
int set_index = timing_arc_sets_.size();
|
|
timing_arc_sets_.push_back(arc_set);
|
|
|
|
LibertyPort *from = arc_set->from();
|
|
LibertyPort *to = arc_set->to();
|
|
const TimingRole *role = arc_set->role();
|
|
if (role == TimingRole::regClkToQ()
|
|
|| role == TimingRole::latchEnToQ()) {
|
|
from->setIsRegClk(true);
|
|
to->setIsRegOutput(true);
|
|
}
|
|
if (role->isTimingCheck())
|
|
from->setIsCheckClk(true);
|
|
return set_index;
|
|
}
|
|
|
|
void
|
|
LibertyCell::addInternalPower(InternalPower *power)
|
|
{
|
|
internal_powers_.push_back(power);
|
|
port_internal_powers_[power->port()].push_back(power);
|
|
}
|
|
|
|
const InternalPowerSeq &
|
|
LibertyCell::internalPowers(const LibertyPort *port)
|
|
{
|
|
return port_internal_powers_[port];
|
|
}
|
|
|
|
void
|
|
LibertyCell::addInternalPowerAttrs(InternalPowerAttrs *attrs)
|
|
{
|
|
internal_power_attrs_.push_back(attrs);
|
|
}
|
|
|
|
void
|
|
LibertyCell::deleteInternalPowerAttrs()
|
|
{
|
|
for (auto attrs : internal_power_attrs_) {
|
|
attrs->deleteContents();
|
|
delete attrs;
|
|
}
|
|
}
|
|
|
|
void
|
|
LibertyCell::addLeakagePower(LeakagePower *power)
|
|
{
|
|
leakage_powers_.push_back(power);
|
|
}
|
|
|
|
void
|
|
LibertyCell::setLeakagePower(float leakage)
|
|
{
|
|
leakage_power_ = leakage;
|
|
leakage_power_exists_ = true;
|
|
}
|
|
|
|
void
|
|
LibertyCell::leakagePower(// Return values.
|
|
float &leakage,
|
|
bool &exists) const
|
|
{
|
|
leakage = leakage_power_;
|
|
exists = leakage_power_exists_;
|
|
}
|
|
|
|
void
|
|
LibertyCell::finish(bool infer_latches,
|
|
Report *report,
|
|
Debug *debug)
|
|
{
|
|
translatePresetClrCheckRoles();
|
|
makeTimingArcMap(report);
|
|
makeTimingArcPortMaps();
|
|
findDefaultCondArcs();
|
|
makeLatchEnables(report, debug);
|
|
if (infer_latches)
|
|
inferLatchRoles(report, debug);
|
|
}
|
|
|
|
void
|
|
LibertyCell::findDefaultCondArcs()
|
|
{
|
|
for (auto [port_pair, sets] : port_timing_arc_set_map_) {
|
|
bool has_cond_arcs = false;
|
|
for (auto set : *sets) {
|
|
if (set->cond()) {
|
|
has_cond_arcs = true;
|
|
break;
|
|
}
|
|
}
|
|
if (has_cond_arcs) {
|
|
for (auto set : *sets) {
|
|
if (!set->cond())
|
|
set->setIsCondDefault(true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Timing checks for set/clear pins use setup/hold times. This
|
|
// changes their roles to recovery/removal by finding the set/clear
|
|
// pins and then translating the timing check roles.
|
|
void
|
|
LibertyCell::translatePresetClrCheckRoles()
|
|
{
|
|
LibertyPortSet pre_clr_ports;
|
|
for (auto arc_set : timing_arc_sets_) {
|
|
if (arc_set->role() == TimingRole::regSetClr())
|
|
pre_clr_ports.insert(arc_set->from());
|
|
}
|
|
|
|
if (!pre_clr_ports.empty()) {
|
|
for (auto arc_set : timing_arc_sets_) {
|
|
if (pre_clr_ports.findKey(arc_set->to())) {
|
|
if (arc_set->role() == TimingRole::setup())
|
|
arc_set->setRole(TimingRole::recovery());
|
|
else if (arc_set->role() == TimingRole::hold())
|
|
arc_set->setRole(TimingRole::removal());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
LibertyCell::makeTimingArcMap(Report *)
|
|
{
|
|
// Filter duplicate timing arcs, keeping the later definition.
|
|
for (auto arc_set : timing_arc_sets_)
|
|
// The last definition will be left in the set.
|
|
timing_arc_set_map_.insert(arc_set);
|
|
|
|
// Prune the arc sets not in the map.
|
|
int j = 0;
|
|
for (size_t i = 0; i < timing_arc_sets_.size(); i++) {
|
|
TimingArcSet *arc_set = timing_arc_sets_[i];
|
|
TimingArcSet *match = timing_arc_set_map_.findKey(arc_set);
|
|
if (match != arc_set) {
|
|
// Unfortunately these errors are common in some brain damaged
|
|
// libraries.
|
|
// report->warn("cell %s/%s has duplicate %s -> %s %s timing groups.",
|
|
// library_->name(),
|
|
// name_,
|
|
// match->from()->name(),
|
|
// match->to()->name(),
|
|
// match->role()->asString());
|
|
delete arc_set;
|
|
}
|
|
else
|
|
// Shift arc sets down to fill holes left by removed duplicates.
|
|
timing_arc_sets_[j++] = arc_set;
|
|
}
|
|
timing_arc_sets_.resize(j);
|
|
|
|
if (timing_arc_set_map_.size() != timing_arc_sets_.size())
|
|
criticalError(1121, "timing arc count mismatch");
|
|
}
|
|
|
|
void
|
|
LibertyCell::makeTimingArcPortMaps()
|
|
{
|
|
for (auto arc_set : timing_arc_sets_) {
|
|
LibertyPort *from = arc_set->from();
|
|
LibertyPort *to = arc_set->to();
|
|
LibertyPortPair port_pair(from, to);
|
|
TimingArcSetSeq *sets = port_timing_arc_set_map_.findKey(port_pair);
|
|
if (sets == nullptr) {
|
|
// First arc set for from/to ports.
|
|
sets = new TimingArcSetSeq;
|
|
port_timing_arc_set_map_[port_pair] = sets;
|
|
}
|
|
sets->push_back(arc_set);
|
|
|
|
sets = timing_arc_set_from_map_.findKey(from);
|
|
if (sets == nullptr) {
|
|
sets = new TimingArcSetSeq;
|
|
timing_arc_set_from_map_[from] = sets;
|
|
}
|
|
sets->push_back(arc_set);
|
|
|
|
sets = timing_arc_set_to_map_.findKey(to);
|
|
if (sets == nullptr) {
|
|
sets = new TimingArcSetSeq;
|
|
timing_arc_set_to_map_[to] = sets;
|
|
}
|
|
sets->push_back(arc_set);
|
|
}
|
|
}
|
|
|
|
const TimingArcSetSeq &
|
|
LibertyCell::timingArcSets(const LibertyPort *from,
|
|
const LibertyPort *to) const
|
|
{
|
|
TimingArcSetSeq *arc_sets = nullptr;
|
|
if (from && to) {
|
|
LibertyPortPair port_pair(from, to);
|
|
arc_sets = port_timing_arc_set_map_.findKey(port_pair);
|
|
}
|
|
else if (from)
|
|
arc_sets = timing_arc_set_from_map_.findKey(from);
|
|
else if (to)
|
|
arc_sets = timing_arc_set_to_map_.findKey(to);
|
|
|
|
if (arc_sets)
|
|
return *arc_sets;
|
|
else {
|
|
static TimingArcSetSeq null_set;
|
|
return null_set;
|
|
}
|
|
}
|
|
|
|
TimingArcSet *
|
|
LibertyCell::findTimingArcSet(TimingArcSet *key) const
|
|
{
|
|
return timing_arc_set_map_.findKey(key);
|
|
}
|
|
|
|
TimingArcSet *
|
|
LibertyCell::findTimingArcSet(unsigned arc_set_index) const
|
|
{
|
|
return timing_arc_sets_[arc_set_index];
|
|
}
|
|
|
|
size_t
|
|
LibertyCell::timingArcSetCount() const
|
|
{
|
|
return timing_arc_sets_.size();
|
|
}
|
|
|
|
bool
|
|
LibertyCell::hasTimingArcs(LibertyPort *port) const
|
|
{
|
|
return timing_arc_set_from_map_.findKey(port)
|
|
|| timing_arc_set_to_map_.findKey(port);
|
|
}
|
|
|
|
void
|
|
LibertyCell::makeSequential(int size,
|
|
bool is_register,
|
|
FuncExpr *clk,
|
|
FuncExpr *data,
|
|
FuncExpr *clear,
|
|
FuncExpr *preset,
|
|
LogicValue clr_preset_out,
|
|
LogicValue clr_preset_out_inv,
|
|
LibertyPort *output,
|
|
LibertyPort *output_inv)
|
|
{
|
|
for (int bit = 0; bit < size; bit++) {
|
|
FuncExpr *clk_bit = nullptr;
|
|
if (clk)
|
|
clk_bit = clk->bitSubExpr(bit);
|
|
FuncExpr *data_bit = nullptr;
|
|
if (data)
|
|
data_bit = data->bitSubExpr(bit);
|
|
FuncExpr *clear_bit = nullptr;
|
|
if (clear)
|
|
clear_bit = clear->bitSubExpr(bit);
|
|
FuncExpr *preset_bit = nullptr;
|
|
if (preset)
|
|
preset_bit = preset->bitSubExpr(bit);
|
|
LibertyPort *out_bit = output;
|
|
if (output && output->hasMembers())
|
|
out_bit = output->findLibertyMember(bit);
|
|
LibertyPort *out_inv_bit = output_inv;
|
|
if (output_inv && output_inv->hasMembers())
|
|
out_inv_bit = output_inv->findLibertyMember(bit);
|
|
Sequential *seq = new Sequential(is_register, clk_bit, data_bit,
|
|
clear_bit,preset_bit,
|
|
clr_preset_out, clr_preset_out_inv,
|
|
out_bit, out_inv_bit);
|
|
sequentials_.push_back(seq);
|
|
port_to_seq_map_[seq->output()] = seq;
|
|
port_to_seq_map_[seq->outputInv()] = seq;
|
|
}
|
|
}
|
|
|
|
Sequential *
|
|
LibertyCell::outputPortSequential(LibertyPort *port)
|
|
{
|
|
return port_to_seq_map_.findKey(port);
|
|
}
|
|
|
|
bool
|
|
LibertyCell::hasSequentials() const
|
|
{
|
|
return !sequentials_.empty()
|
|
|| statetable_ != nullptr;
|
|
}
|
|
|
|
void
|
|
LibertyCell::makeStatetable(LibertyPortSeq &input_ports,
|
|
LibertyPortSeq &internal_ports,
|
|
StatetableRows &table)
|
|
{
|
|
statetable_ = new Statetable(input_ports, internal_ports, table);
|
|
}
|
|
|
|
void
|
|
LibertyCell::addScaledCell(OperatingConditions *op_cond,
|
|
LibertyCell *scaled_cell)
|
|
{
|
|
scaled_cells_[op_cond] = scaled_cell;
|
|
|
|
LibertyCellPortBitIterator port_iter1(this);
|
|
LibertyCellPortBitIterator port_iter2(scaled_cell);
|
|
while (port_iter1.hasNext() && port_iter2.hasNext()) {
|
|
LibertyPort *port = port_iter1.next();
|
|
LibertyPort *scaled_port = port_iter2.next();
|
|
port->addScaledPort(op_cond, scaled_port);
|
|
}
|
|
|
|
const TimingArcSetSeq &arc_sets1 = this->timingArcSets();
|
|
const TimingArcSetSeq &arc_sets2 = scaled_cell->timingArcSets();
|
|
for (auto set_itr1 = arc_sets1.begin(), set_itr2 = arc_sets2.begin();
|
|
set_itr1 != arc_sets1.end() && set_itr2 != arc_sets2.end();
|
|
set_itr1++, set_itr2++) {
|
|
TimingArcSet *arc_set1 = *set_itr1;
|
|
TimingArcSet *arc_set2 = *set_itr2;
|
|
const TimingArcSeq &arcs1 = arc_set1->arcs();
|
|
const TimingArcSeq &arcs2 = arc_set2->arcs();
|
|
auto arc_itr1 = arcs1.begin(), arc_itr2 = arcs2.begin();
|
|
for (;
|
|
arc_itr1 != arcs1.end() && arc_itr2 != arcs2.end();
|
|
arc_itr1++, arc_itr2++) {
|
|
TimingArc *arc = *arc_itr1;
|
|
const TimingArc *scaled_arc = *arc_itr2;
|
|
|
|
if (TimingArc::equiv(arc, scaled_arc)) {
|
|
TimingModel *model = scaled_arc->model();
|
|
model->setIsScaled(true);
|
|
arc->addScaledModel(op_cond, model);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
LibertyCell::setLibertyLibrary(LibertyLibrary *library)
|
|
{
|
|
liberty_library_ = library;
|
|
library_ = library;
|
|
}
|
|
|
|
void
|
|
LibertyCell::setHasInferedRegTimingArcs(bool infered)
|
|
{
|
|
has_infered_reg_timing_arcs_ = infered;
|
|
}
|
|
|
|
void
|
|
LibertyCell::setTestCell(TestCell *test)
|
|
{
|
|
test_cell_ = test;
|
|
}
|
|
|
|
void
|
|
LibertyCell::setIsDisabledConstraint(bool is_disabled)
|
|
{
|
|
is_disabled_constraint_ = is_disabled;
|
|
}
|
|
|
|
LibertyCell *
|
|
LibertyCell::cornerCell(const Corner *corner,
|
|
const MinMax *min_max)
|
|
{
|
|
return cornerCell(corner->libertyIndex(min_max));
|
|
}
|
|
|
|
LibertyCell *
|
|
LibertyCell::cornerCell(const DcalcAnalysisPt *dcalc_ap)
|
|
{
|
|
return cornerCell(dcalc_ap->libertyIndex());
|
|
}
|
|
|
|
LibertyCell *
|
|
LibertyCell::cornerCell(int ap_index)
|
|
{
|
|
if (corner_cells_.empty())
|
|
return this;
|
|
else if (ap_index < static_cast<int>(corner_cells_.size()))
|
|
return corner_cells_[ap_index];
|
|
else
|
|
return nullptr;
|
|
}
|
|
|
|
bool
|
|
LibertyCell::checkCornerCell(const Corner *corner,
|
|
const MinMax *min_max) const
|
|
{
|
|
unsigned lib_index = corner->libertyIndex(min_max);
|
|
return corner_cells_.empty()
|
|
|| (lib_index < corner_cells_.size()
|
|
&& corner_cells_[lib_index]);
|
|
}
|
|
|
|
void
|
|
LibertyCell::setCornerCell(LibertyCell *corner_cell,
|
|
int ap_index)
|
|
{
|
|
if (ap_index >= static_cast<int>(corner_cells_.size()))
|
|
corner_cells_.resize(ap_index + 1);
|
|
corner_cells_[ap_index] = corner_cell;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
float
|
|
LibertyCell::ocvArcDepth() const
|
|
{
|
|
return ocv_arc_depth_;
|
|
}
|
|
|
|
void
|
|
LibertyCell::setOcvArcDepth(float depth)
|
|
{
|
|
ocv_arc_depth_ = depth;
|
|
}
|
|
|
|
OcvDerate *
|
|
LibertyCell::ocvDerate() const
|
|
{
|
|
if (ocv_derate_)
|
|
return ocv_derate_;
|
|
else
|
|
return liberty_library_->defaultOcvDerate();
|
|
}
|
|
|
|
void
|
|
LibertyCell::setOcvDerate(OcvDerate *derate)
|
|
{
|
|
ocv_derate_ = derate;
|
|
}
|
|
|
|
OcvDerate *
|
|
LibertyCell::findOcvDerate(const char *derate_name)
|
|
{
|
|
return ocv_derate_map_.findKey(derate_name);
|
|
}
|
|
|
|
void
|
|
LibertyCell::addOcvDerate(OcvDerate *derate)
|
|
{
|
|
ocv_derate_map_[derate->name()] = derate;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
// Latch enable port/function for a latch D->Q timing arc set.
|
|
class LatchEnable
|
|
{
|
|
public:
|
|
LatchEnable(LibertyPort *data,
|
|
LibertyPort *enable,
|
|
const RiseFall *enable_edge,
|
|
FuncExpr *enable_func,
|
|
LibertyPort *output,
|
|
TimingArcSet *d_to_q,
|
|
TimingArcSet *en_to_q,
|
|
TimingArcSet *setup_check);
|
|
LibertyPort *data() const { return data_; }
|
|
LibertyPort *output() const { return output_; }
|
|
LibertyPort *enable() const { return enable_; }
|
|
FuncExpr *enableFunc() const { return enable_func_; }
|
|
const RiseFall *enableEdge() const { return enable_edge_; }
|
|
TimingArcSet *dToQ() const { return d_to_q_; }
|
|
TimingArcSet *enToQ() const { return en_to_q_; }
|
|
TimingArcSet *setupCheck() const { return setup_check_; }
|
|
|
|
private:
|
|
LibertyPort *data_;
|
|
LibertyPort *enable_;
|
|
const RiseFall *enable_edge_;
|
|
FuncExpr *enable_func_;
|
|
LibertyPort *output_;
|
|
TimingArcSet *d_to_q_;
|
|
TimingArcSet *en_to_q_;
|
|
TimingArcSet *setup_check_;
|
|
};
|
|
|
|
LatchEnable::LatchEnable(LibertyPort *data,
|
|
LibertyPort *enable,
|
|
const RiseFall *enable_edge,
|
|
FuncExpr *enable_func,
|
|
LibertyPort *output,
|
|
TimingArcSet *d_to_q,
|
|
TimingArcSet *en_to_q,
|
|
TimingArcSet *setup_check) :
|
|
data_(data),
|
|
enable_(enable),
|
|
enable_edge_(enable_edge),
|
|
enable_func_(enable_func),
|
|
output_(output),
|
|
d_to_q_(d_to_q),
|
|
en_to_q_(en_to_q),
|
|
setup_check_(setup_check)
|
|
{
|
|
}
|
|
|
|
// Latch enable port/function for a latch D->Q timing arc set.
|
|
// This augments cell timing data by linking enables to D->Q arcs.
|
|
// Use timing arcs rather than sequentials (because they are optional).
|
|
void
|
|
LibertyCell::makeLatchEnables(Report *report,
|
|
Debug *debug)
|
|
{
|
|
if (hasSequentials()
|
|
|| hasInferedRegTimingArcs()) {
|
|
for (auto en_to_q : timing_arc_sets_) {
|
|
if (en_to_q->role() == TimingRole::latchEnToQ()) {
|
|
LibertyPort *en = en_to_q->from();
|
|
LibertyPort *q = en_to_q->to();
|
|
for (TimingArcSet *d_to_q : timingArcSets(nullptr, q)) {
|
|
if (d_to_q->role() == TimingRole::latchDtoQ()
|
|
&& condMatch(en_to_q, d_to_q)) {
|
|
LibertyPort *d = d_to_q->from();
|
|
const RiseFall *en_rf = en_to_q->isRisingFallingEdge();
|
|
if (en_rf) {
|
|
TimingArcSet *setup_check = findLatchSetup(d, en, en_rf, q, d_to_q,
|
|
report);
|
|
LatchEnable *latch_enable = makeLatchEnable(d, en, en_rf, q, d_to_q,
|
|
en_to_q,
|
|
setup_check,
|
|
debug);
|
|
FuncExpr *en_func = latch_enable->enableFunc();
|
|
if (en_func) {
|
|
TimingSense en_sense = en_func->portTimingSense(en);
|
|
if (en_sense == TimingSense::positive_unate
|
|
&& en_rf != RiseFall::rise())
|
|
report->warn(1114, "cell %s/%s %s -> %s latch enable %s_edge is inconsistent with latch group enable function positive sense.",
|
|
library_->name(),
|
|
name(),
|
|
en->name(),
|
|
q->name(),
|
|
en_rf == RiseFall::rise()?"rising":"falling");
|
|
else if (en_sense == TimingSense::negative_unate
|
|
&& en_rf != RiseFall::fall())
|
|
report->warn(1115, "cell %s/%s %s -> %s latch enable %s_edge is inconsistent with latch group enable function negative sense.",
|
|
library_->name(),
|
|
name(),
|
|
en->name(),
|
|
q->name(),
|
|
en_rf == RiseFall::rise()?"rising":"falling");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
LibertyCell::condMatch(const TimingArcSet *arc_set1,
|
|
const TimingArcSet *arc_set2)
|
|
{
|
|
FuncExpr *cond1 = arc_set1->cond();
|
|
FuncExpr *cond2 = arc_set2->cond();
|
|
return (cond1 == nullptr && cond2 == nullptr)
|
|
|| FuncExpr::equiv(cond1, cond2);
|
|
}
|
|
|
|
TimingArcSet *
|
|
LibertyCell::findLatchSetup(const LibertyPort *d,
|
|
const LibertyPort *en,
|
|
const RiseFall *en_rf,
|
|
const LibertyPort *q,
|
|
const TimingArcSet *en_to_q,
|
|
Report *report)
|
|
{
|
|
TimingArcSetSeq en_d_arcs = timingArcSets(en, d);
|
|
// First search for setup checks with the correct clock edge.
|
|
for (TimingArcSet *arc_set : en_d_arcs) {
|
|
if (arc_set->role() == TimingRole::setup()) {
|
|
for (TimingArc *arc : arc_set->arcs()) {
|
|
const RiseFall *from_rf = arc->fromEdge()->asRiseFall();
|
|
if (from_rf == en_rf->opposite()
|
|
&& condMatch(arc_set, en_to_q))
|
|
return arc_set;
|
|
}
|
|
}
|
|
}
|
|
// Then search for setup checks with the opposite clock edge.
|
|
for (TimingArcSet *arc_set : en_d_arcs) {
|
|
if (arc_set->role() == TimingRole::setup()) {
|
|
for (TimingArc *arc : arc_set->arcs()) {
|
|
const RiseFall *from_rf = arc->fromEdge()->asRiseFall();
|
|
if (from_rf == en_rf) {
|
|
report->warn(1113, "cell %s/%s %s -> %s latch enable %s_edge is inconsistent with %s -> %s setup_%s check.",
|
|
library_->name(),
|
|
name(),
|
|
en->name(),
|
|
q->name(),
|
|
en_rf == RiseFall::rise() ? "rising" : "falling",
|
|
en->name(),
|
|
d->name(),
|
|
from_rf == RiseFall::rise() ? "rising" : "falling");
|
|
return arc_set;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
FuncExpr *
|
|
LibertyCell::findLatchEnableFunc(const LibertyPort *d,
|
|
const LibertyPort *en,
|
|
const RiseFall *en_rf) const
|
|
{
|
|
for (auto seq : sequentials_) {
|
|
if (seq->isLatch()
|
|
&& seq->data()
|
|
&& seq->data()->hasPort(d)
|
|
&& seq->clock()
|
|
&& seq->clock()->hasPort(en)) {
|
|
FuncExpr *en_func = seq->clock();
|
|
TimingSense en_sense = en_func->portTimingSense(en);
|
|
if ((en_sense == TimingSense::positive_unate
|
|
&& en_rf == RiseFall::rise())
|
|
|| (en_sense == TimingSense::negative_unate
|
|
&& en_rf == RiseFall::fall()))
|
|
return seq->clock();
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
LatchEnable *
|
|
LibertyCell::makeLatchEnable(LibertyPort *d,
|
|
LibertyPort *en,
|
|
const RiseFall *en_rf,
|
|
LibertyPort *q,
|
|
TimingArcSet *d_to_q,
|
|
TimingArcSet *en_to_q,
|
|
TimingArcSet *setup_check,
|
|
Debug *debug)
|
|
{
|
|
FuncExpr *en_func = findLatchEnableFunc(d, en, en_rf);
|
|
LatchEnable *latch_enable = new LatchEnable(d, en, en_rf, en_func, q,
|
|
d_to_q, en_to_q, setup_check);
|
|
latch_enables_.push_back(latch_enable);
|
|
latch_d_to_q_map_[d_to_q] = latch_enable;
|
|
latch_check_map_[setup_check] = latch_enable;
|
|
d->setIsLatchData(true);
|
|
debugPrint(debug, "liberty_latch", 1,
|
|
"latch %s -> %s | %s %s -> %s | %s %s -> %s setup",
|
|
d->name(),
|
|
q->name(),
|
|
en->name(),
|
|
en_rf->shortName(),
|
|
q->name(),
|
|
en->name(),
|
|
setup_check->arcs()[0]->fromEdge()->asRiseFall()->shortName(),
|
|
q->name());
|
|
return latch_enable;
|
|
}
|
|
|
|
void
|
|
LibertyCell::inferLatchRoles(Report *report,
|
|
Debug *debug)
|
|
{
|
|
if (hasInferedRegTimingArcs()) {
|
|
// Hunt down potential latch D/EN/Q triples.
|
|
LatchEnableSet latch_enables;
|
|
for (TimingArcSet *en_to_q : timingArcSets()) {
|
|
// Locate potential d->q arcs from reg clk->q arcs.
|
|
if (en_to_q->role() == TimingRole::regClkToQ()) {
|
|
LibertyPort *en = en_to_q->from();
|
|
LibertyPort *q = en_to_q->to();
|
|
for (TimingArcSet *d_to_q : timingArcSets(nullptr, q)) {
|
|
// Look for combinational d->q arcs.
|
|
const TimingRole *d_to_q_role = d_to_q->role();
|
|
if (((d_to_q_role == TimingRole::combinational()
|
|
&& d_to_q->arcCount() == 2
|
|
&& (d_to_q->sense() == TimingSense::positive_unate
|
|
|| d_to_q->sense() == TimingSense::negative_unate))
|
|
// Previously identified as D->Q arc.
|
|
|| d_to_q_role == TimingRole::latchDtoQ())
|
|
&& condMatch(en_to_q, d_to_q)) {
|
|
LibertyPort *d = d_to_q->from();
|
|
const RiseFall *en_rf = en_to_q->isRisingFallingEdge();
|
|
if (en_rf) {
|
|
TimingArcSet *setup_check = findLatchSetup(d, en, en_rf, q, en_to_q,
|
|
report);
|
|
makeLatchEnable(d, en, en_rf, q, d_to_q, en_to_q, setup_check, debug);
|
|
d_to_q->setRole(TimingRole::latchDtoQ());
|
|
en_to_q->setRole(TimingRole::latchEnToQ());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
LibertyCell::latchEnable(const TimingArcSet *d_to_q_set,
|
|
// Return values.
|
|
const LibertyPort *&enable_port,
|
|
const FuncExpr *&enable_func,
|
|
const RiseFall *&enable_edge) const
|
|
{
|
|
LatchEnable *latch_enable = latch_d_to_q_map_.findKey(d_to_q_set);
|
|
if (latch_enable) {
|
|
enable_port = latch_enable->enable();
|
|
enable_func = latch_enable->enableFunc();
|
|
enable_edge = latch_enable->enableEdge();
|
|
}
|
|
else {
|
|
enable_port = nullptr;
|
|
enable_func = nullptr;
|
|
enable_edge = nullptr;
|
|
}
|
|
}
|
|
|
|
const RiseFall *
|
|
LibertyCell::latchCheckEnableEdge(TimingArcSet *check_set)
|
|
{
|
|
LatchEnable *latch_enable = latch_check_map_.findKey(check_set);
|
|
if (latch_enable)
|
|
return latch_enable->enableEdge();
|
|
else
|
|
return nullptr;
|
|
}
|
|
|
|
void
|
|
LibertyCell::ensureVoltageWaveforms(const DcalcAnalysisPtSeq &dcalc_aps)
|
|
{
|
|
if (!have_voltage_waveforms_) {
|
|
LockGuard lock(waveform_lock_);
|
|
// Recheck with lock.
|
|
if (!have_voltage_waveforms_) {
|
|
float vdd = 0.0; // shutup gcc
|
|
bool vdd_exists;
|
|
liberty_library_->supplyVoltage("VDD", vdd, vdd_exists);
|
|
if (!vdd_exists || vdd == 0.0)
|
|
criticalError(1120, "library missing vdd");
|
|
for (TimingArcSet *arc_set : timingArcSets()) {
|
|
for (TimingArc *arc : arc_set->arcs()) {
|
|
for (const DcalcAnalysisPt *dcalc_ap : dcalc_aps) {
|
|
GateTableModel *model = arc->gateTableModel(dcalc_ap);
|
|
if (model) {
|
|
OutputWaveforms *output_waveforms = model->outputWaveforms();
|
|
if (output_waveforms)
|
|
output_waveforms->ensureVoltageWaveforms(vdd);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
have_voltage_waveforms_ = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
const char *
|
|
LibertyCell::footprint() const
|
|
{
|
|
if (footprint_.empty())
|
|
return nullptr;
|
|
else
|
|
return footprint_.c_str();
|
|
}
|
|
|
|
|
|
void
|
|
LibertyCell::setFootprint(const char *footprint)
|
|
{
|
|
footprint_ = footprint;
|
|
}
|
|
|
|
const char *
|
|
LibertyCell::userFunctionClass() const
|
|
{
|
|
if (user_function_class_.empty())
|
|
return nullptr;
|
|
else
|
|
return user_function_class_.c_str();
|
|
}
|
|
|
|
void
|
|
LibertyCell::setUserFunctionClass(const char *user_function_class)
|
|
{
|
|
user_function_class_ = user_function_class;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
LibertyCellPortIterator::LibertyCellPortIterator(const LibertyCell *cell) :
|
|
iter_(cell->ports_)
|
|
{
|
|
}
|
|
|
|
bool
|
|
LibertyCellPortIterator::hasNext()
|
|
{
|
|
return iter_.hasNext();
|
|
}
|
|
|
|
LibertyPort *
|
|
LibertyCellPortIterator::next()
|
|
{
|
|
return static_cast<LibertyPort*>(iter_.next());
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
LibertyCellPortBitIterator::LibertyCellPortBitIterator(const LibertyCell *cell):
|
|
iter_(cell->portBitIterator())
|
|
{
|
|
}
|
|
|
|
LibertyCellPortBitIterator::~LibertyCellPortBitIterator()
|
|
{
|
|
delete iter_;
|
|
}
|
|
|
|
bool
|
|
LibertyCellPortBitIterator::hasNext()
|
|
{
|
|
return iter_->hasNext();
|
|
}
|
|
|
|
LibertyPort *
|
|
LibertyCellPortBitIterator::next()
|
|
{
|
|
return static_cast<LibertyPort*>(iter_->next());
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
LibertyPort::LibertyPort(LibertyCell *cell,
|
|
const char *name,
|
|
bool is_bus,
|
|
BusDcl *bus_dcl,
|
|
int from_index,
|
|
int to_index,
|
|
bool is_bundle,
|
|
ConcretePortSeq *members) :
|
|
ConcretePort(name, is_bus, from_index, to_index, is_bundle, members, cell),
|
|
liberty_cell_(cell),
|
|
bus_dcl_(bus_dcl),
|
|
function_(nullptr),
|
|
scan_signal_type_(ScanSignalType::none),
|
|
tristate_enable_(nullptr),
|
|
scaled_ports_(nullptr),
|
|
fanout_load_(0.0),
|
|
fanout_load_exists_(false),
|
|
min_period_(0.0),
|
|
pulse_clk_trigger_(nullptr),
|
|
pulse_clk_sense_(nullptr),
|
|
receiver_model_(nullptr),
|
|
driver_waveform_{nullptr, nullptr},
|
|
min_pulse_width_exists_(false),
|
|
min_period_exists_(false),
|
|
is_clk_(false),
|
|
is_reg_clk_(false),
|
|
is_reg_output_(false),
|
|
is_latch_data_(false),
|
|
is_check_clk_(false),
|
|
is_clk_gate_clk_(false),
|
|
is_clk_gate_enable_(false),
|
|
is_clk_gate_out_(false),
|
|
is_pll_feedback_(false),
|
|
isolation_cell_data_(false),
|
|
isolation_cell_enable_(false),
|
|
level_shifter_data_(false),
|
|
is_switch_(false),
|
|
is_disabled_constraint_(false),
|
|
is_pad_(false)
|
|
{
|
|
liberty_port_ = this;
|
|
min_pulse_width_[RiseFall::riseIndex()] = 0.0;
|
|
min_pulse_width_[RiseFall::fallIndex()] = 0.0;
|
|
for (auto from_rf_index : RiseFall::rangeIndex()) {
|
|
for (auto to_rf_index : RiseFall::rangeIndex()) {
|
|
for (auto mm_index : MinMax::rangeIndex())
|
|
clk_tree_delay_[from_rf_index][to_rf_index][mm_index] = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
LibertyPort::~LibertyPort()
|
|
{
|
|
if (function_)
|
|
function_->deleteSubexprs();
|
|
if (tristate_enable_)
|
|
tristate_enable_->deleteSubexprs();
|
|
delete scaled_ports_;
|
|
}
|
|
|
|
void
|
|
LibertyPort::setDirection(PortDirection *dir)
|
|
{
|
|
ConcretePort::setDirection(dir);
|
|
if (dir->isInternal())
|
|
liberty_cell_->setHasInternalPorts(true);
|
|
}
|
|
|
|
void
|
|
LibertyPort::setScanSignalType(ScanSignalType type)
|
|
{
|
|
scan_signal_type_ = type;
|
|
}
|
|
|
|
static EnumNameMap<ScanSignalType> scan_signal_type_map =
|
|
{{ScanSignalType::enable, "enable"},
|
|
{ScanSignalType::enable_inverted, "enable_inverted"},
|
|
{ScanSignalType::clock, "clock"},
|
|
{ScanSignalType::clock_a, "clock_a"},
|
|
{ScanSignalType::clock_b, "clock_b"},
|
|
{ScanSignalType::input, "input"},
|
|
{ScanSignalType::input_inverted, "input_inverted"},
|
|
{ScanSignalType::output, "output"},
|
|
{ScanSignalType::output_inverted, "output_inverted"},
|
|
{ScanSignalType::none, "none"}};
|
|
|
|
|
|
const char *
|
|
scanSignalTypeName(ScanSignalType scan_type)
|
|
{
|
|
return scan_signal_type_map.find(scan_type);
|
|
}
|
|
|
|
LibertyPort *
|
|
LibertyPort::findLibertyMember(int index) const
|
|
{
|
|
return static_cast<LibertyPort*>(findMember(index));
|
|
}
|
|
|
|
LibertyPort *
|
|
LibertyPort::findLibertyBusBit(int index) const
|
|
{
|
|
return static_cast<LibertyPort*>(findBusBit(index));
|
|
}
|
|
|
|
LibertyPort *
|
|
LibertyPort::bundlePort() const
|
|
{
|
|
return static_cast<LibertyPort*>(bundle_port_);
|
|
}
|
|
|
|
void
|
|
LibertyPort::setCapacitance(float cap)
|
|
{
|
|
setCapacitance(RiseFall::rise(), MinMax::min(), cap);
|
|
setCapacitance(RiseFall::fall(), MinMax::min(), cap);
|
|
setCapacitance(RiseFall::rise(), MinMax::max(), cap);
|
|
setCapacitance(RiseFall::fall(), MinMax::max(), cap);
|
|
}
|
|
|
|
void
|
|
LibertyPort::setCapacitance(const RiseFall *rf,
|
|
const MinMax *min_max,
|
|
float cap)
|
|
{
|
|
capacitance_.setValue(rf, min_max, cap);
|
|
if (hasMembers()) {
|
|
LibertyPortMemberIterator member_iter(this);
|
|
while (member_iter.hasNext()) {
|
|
LibertyPort *port_bit = member_iter.next();
|
|
port_bit->setCapacitance(rf, min_max, cap);
|
|
}
|
|
}
|
|
}
|
|
|
|
float
|
|
LibertyPort::capacitance() const
|
|
{
|
|
float cap;
|
|
bool exists;
|
|
capacitance_.maxValue(cap, exists);
|
|
if (exists)
|
|
return cap;
|
|
else
|
|
return 0.0;
|
|
}
|
|
|
|
float
|
|
LibertyPort::capacitance(const MinMax *min_max) const
|
|
{
|
|
return capacitance_.value(min_max);
|
|
}
|
|
|
|
float
|
|
LibertyPort::capacitance(const RiseFall *rf,
|
|
const MinMax *min_max) const
|
|
{
|
|
float cap;
|
|
bool exists;
|
|
capacitance_.value(rf, min_max, cap, exists);
|
|
if (exists)
|
|
return cap;
|
|
else
|
|
return 0.0;
|
|
}
|
|
|
|
void
|
|
LibertyPort::capacitance(const RiseFall *rf,
|
|
const MinMax *min_max,
|
|
// Return values.
|
|
float &cap,
|
|
bool &exists) const
|
|
{
|
|
capacitance_.value(rf, min_max, cap, exists);
|
|
}
|
|
|
|
float
|
|
LibertyPort::capacitance(const RiseFall *rf,
|
|
const MinMax *min_max,
|
|
const OperatingConditions *op_cond,
|
|
const Pvt *pvt) const
|
|
{
|
|
if (scaled_ports_) {
|
|
LibertyPort *scaled_port = (*scaled_ports_)[op_cond];
|
|
// Scaled capacitance is not derated because scale factors are wrt
|
|
// nominal pvt.
|
|
if (scaled_port)
|
|
return scaled_port->capacitance(rf, min_max);
|
|
}
|
|
LibertyLibrary *lib = liberty_cell_->libertyLibrary();
|
|
float cap = capacitance(rf, min_max);
|
|
return cap * lib->scaleFactor(ScaleFactorType::pin_cap, liberty_cell_, pvt);
|
|
}
|
|
|
|
bool
|
|
LibertyPort::capacitanceIsOneValue() const
|
|
{
|
|
return capacitance_.isOneValue();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
float
|
|
LibertyPort::driveResistance() const
|
|
{
|
|
return driveResistance(nullptr, MinMax::max());
|
|
}
|
|
|
|
// Min/max "drive" for all cell timing arcs.
|
|
float
|
|
LibertyPort::driveResistance(const RiseFall *rf,
|
|
const MinMax *min_max) const
|
|
{
|
|
float max_drive = min_max->initValue();
|
|
bool found_drive = false;
|
|
for (TimingArcSet *arc_set : liberty_cell_->timingArcSets(nullptr, this)) {
|
|
if (!arc_set->role()->isTimingCheck()) {
|
|
for (TimingArc *arc : arc_set->arcs()) {
|
|
if (rf == nullptr
|
|
|| arc->toEdge()->asRiseFall() == rf) {
|
|
float drive = arc->driveResistance();
|
|
if (drive > 0.0) {
|
|
if (min_max->compare(drive, max_drive))
|
|
max_drive = drive;
|
|
found_drive = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (found_drive)
|
|
return max_drive;
|
|
else
|
|
return 0.0;
|
|
}
|
|
|
|
ArcDelay
|
|
LibertyPort::intrinsicDelay(const StaState *sta) const
|
|
{
|
|
return intrinsicDelay(nullptr, MinMax::max(), sta);
|
|
}
|
|
|
|
ArcDelay
|
|
LibertyPort::intrinsicDelay(const RiseFall *rf,
|
|
const MinMax *min_max,
|
|
const StaState *sta) const
|
|
{
|
|
ArcDelay max_delay = min_max->initValue();
|
|
bool found_delay = false;
|
|
for (TimingArcSet *arc_set : liberty_cell_->timingArcSets(nullptr, this)) {
|
|
if (!arc_set->role()->isTimingCheck()) {
|
|
for (TimingArc *arc : arc_set->arcs()) {
|
|
if (rf == nullptr
|
|
|| arc->toEdge()->asRiseFall() == rf) {
|
|
ArcDelay delay = arc->intrinsicDelay();
|
|
if (delayGreater(delay, 0.0, sta)) {
|
|
if (delayGreater(delay, max_delay, min_max, sta))
|
|
max_delay = delay;
|
|
found_delay = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (found_delay)
|
|
return max_delay;
|
|
else
|
|
return 0.0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
void
|
|
LibertyPort::setFunction(FuncExpr *func)
|
|
{
|
|
function_ = func;
|
|
if (hasMembers()) {
|
|
LibertyPortMemberIterator member_iter(this);
|
|
int bit_offset = 0;
|
|
while (member_iter.hasNext()) {
|
|
LibertyPort *port_bit = member_iter.next();
|
|
FuncExpr *sub_expr = (func) ? func->bitSubExpr(bit_offset) : nullptr;
|
|
port_bit->setFunction(sub_expr);
|
|
bit_offset++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
LibertyPort::setTristateEnable(FuncExpr *enable)
|
|
{
|
|
tristate_enable_ = enable;
|
|
if (hasMembers()) {
|
|
LibertyPortMemberIterator member_iter(this);
|
|
while (member_iter.hasNext()) {
|
|
LibertyPort *port_bit = member_iter.next();
|
|
FuncExpr *sub_expr =
|
|
(enable) ? enable->bitSubExpr(port_bit->busBitIndex()) : nullptr;
|
|
port_bit->setTristateEnable(sub_expr);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
LibertyPort::slewLimit(const MinMax *min_max,
|
|
// Return values.
|
|
float &limit,
|
|
bool &exists) const
|
|
{
|
|
slew_limit_.value(min_max, limit, exists);
|
|
}
|
|
|
|
void
|
|
LibertyPort::setSlewLimit(float slew,
|
|
const MinMax *min_max)
|
|
{
|
|
slew_limit_.setValue(min_max, slew);
|
|
setMemberMinMaxFloat(slew, min_max, &LibertyPort::setSlewLimit);
|
|
}
|
|
|
|
void
|
|
LibertyPort::capacitanceLimit(const MinMax *min_max,
|
|
// Return values.
|
|
float &limit,
|
|
bool &exists) const
|
|
{
|
|
return cap_limit_.value(min_max, limit, exists);
|
|
}
|
|
|
|
void
|
|
LibertyPort::setCapacitanceLimit(float cap,
|
|
const MinMax *min_max)
|
|
{
|
|
cap_limit_.setValue(min_max, cap);
|
|
setMemberMinMaxFloat(cap, min_max, &LibertyPort::setCapacitanceLimit);
|
|
}
|
|
|
|
void
|
|
LibertyPort::fanoutLoad(// Return values.
|
|
float &fanout_load,
|
|
bool &exists) const
|
|
{
|
|
fanout_load = fanout_load_;
|
|
exists = fanout_load_exists_;
|
|
}
|
|
|
|
void
|
|
LibertyPort::setFanoutLoad(float fanout_load)
|
|
{
|
|
fanout_load_ = fanout_load;
|
|
fanout_load_exists_ = true;
|
|
setMemberFloat(fanout_load, &LibertyPort::setFanoutLoad);
|
|
}
|
|
|
|
void
|
|
LibertyPort::fanoutLimit(const MinMax *min_max,
|
|
// Return values.
|
|
float &limit,
|
|
bool &exists) const
|
|
{
|
|
return fanout_limit_.value(min_max, limit, exists);
|
|
}
|
|
|
|
void
|
|
LibertyPort::setFanoutLimit(float fanout,
|
|
const MinMax *min_max)
|
|
{
|
|
fanout_limit_.setValue(min_max, fanout);
|
|
}
|
|
|
|
void
|
|
LibertyPort::minPeriod(const OperatingConditions *op_cond,
|
|
const Pvt *pvt,
|
|
float &min_period,
|
|
bool &exists) const
|
|
{
|
|
if (scaled_ports_) {
|
|
LibertyPort *scaled_port = (*scaled_ports_)[op_cond];
|
|
if (scaled_port) {
|
|
scaled_port->minPeriod(min_period, exists);
|
|
return;
|
|
}
|
|
}
|
|
LibertyLibrary *lib = liberty_cell_->libertyLibrary();
|
|
min_period = min_period_ * lib->scaleFactor(ScaleFactorType::min_period,
|
|
liberty_cell_, pvt);
|
|
exists = min_period_exists_;
|
|
}
|
|
|
|
void
|
|
LibertyPort::minPeriod(float &min_period,
|
|
bool &exists) const
|
|
{
|
|
min_period = min_period_;
|
|
exists = min_period_exists_;
|
|
}
|
|
|
|
void
|
|
LibertyPort::setMinPeriod(float min_period)
|
|
{
|
|
min_period_ = min_period;
|
|
min_period_exists_ = true;
|
|
if (hasMembers()) {
|
|
LibertyPortMemberIterator member_iter(this);
|
|
while (member_iter.hasNext()) {
|
|
LibertyPort *port_bit = member_iter.next();
|
|
port_bit->setMinPeriod(min_period);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
LibertyPort::minPulseWidth(const RiseFall *hi_low,
|
|
float &min_width,
|
|
bool &exists) const
|
|
{
|
|
int hi_low_index = hi_low->index();
|
|
min_width = min_pulse_width_[hi_low_index];
|
|
exists = min_pulse_width_exists_ & (1 << hi_low_index);
|
|
}
|
|
|
|
void
|
|
LibertyPort::setMinPulseWidth(const RiseFall *hi_low,
|
|
float min_width)
|
|
{
|
|
int hi_low_index = hi_low->index();
|
|
min_pulse_width_[hi_low_index] = min_width;
|
|
min_pulse_width_exists_ |= (1 << hi_low_index);
|
|
if (hasMembers()) {
|
|
LibertyPortMemberIterator member_iter(this);
|
|
while (member_iter.hasNext()) {
|
|
LibertyPort *port_bit = member_iter.next();
|
|
port_bit->setMinPulseWidth(hi_low, min_width);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
LibertyPort::equiv(const LibertyPort *port1,
|
|
const LibertyPort *port2)
|
|
{
|
|
return (port1 == nullptr && port2 == nullptr)
|
|
|| (port1 != nullptr && port2 != nullptr
|
|
&& stringEq(port1->name(), port2->name())
|
|
&& port1->direction() == port2->direction());
|
|
}
|
|
|
|
bool
|
|
LibertyPort::less(const LibertyPort *port1,
|
|
const LibertyPort *port2)
|
|
{
|
|
if (port1 == nullptr && port2 != nullptr)
|
|
return true;
|
|
if (port1 != nullptr && port2 == nullptr)
|
|
return false;
|
|
const char *name1 = port1->name();
|
|
const char *name2 = port2->name();
|
|
if (stringEq(name1, name2)) {
|
|
PortDirection *dir1 = port1->direction();
|
|
PortDirection *dir2 = port2->direction();
|
|
return dir1->index() < dir2->index();
|
|
}
|
|
return stringLess(name1, name2);
|
|
}
|
|
|
|
void
|
|
LibertyPort::addScaledPort(OperatingConditions *op_cond,
|
|
LibertyPort *scaled_port)
|
|
{
|
|
if (scaled_ports_ == nullptr)
|
|
scaled_ports_ = new ScaledPortMap;
|
|
(*scaled_ports_)[op_cond] = scaled_port;
|
|
}
|
|
|
|
bool
|
|
LibertyPort::isClock() const
|
|
{
|
|
return is_clk_;
|
|
}
|
|
|
|
void
|
|
LibertyPort::setIsClock(bool is_clk)
|
|
{
|
|
is_clk_ = is_clk;
|
|
setMemberFlag(is_clk, &LibertyPort::setIsClock);
|
|
}
|
|
|
|
void
|
|
LibertyPort::setIsRegClk(bool is_clk)
|
|
{
|
|
is_reg_clk_ = is_clk;
|
|
setMemberFlag(is_clk, &LibertyPort::setIsRegClk);
|
|
}
|
|
|
|
void
|
|
LibertyPort::setIsRegOutput(bool is_reg_out)
|
|
{
|
|
is_reg_output_ = is_reg_out;
|
|
setMemberFlag(is_reg_out, &LibertyPort::setIsRegOutput);
|
|
}
|
|
|
|
void
|
|
LibertyPort::setIsLatchData(bool is_latch_data)
|
|
{
|
|
is_latch_data_ = is_latch_data;
|
|
setMemberFlag(is_latch_data, &LibertyPort::setIsLatchData);
|
|
}
|
|
|
|
void
|
|
LibertyPort::setIsCheckClk(bool is_clk)
|
|
{
|
|
is_check_clk_ = is_clk;
|
|
}
|
|
|
|
void
|
|
LibertyPort::setIsClockGateClock(bool is_clk_gate_clk)
|
|
{
|
|
is_clk_gate_clk_ = is_clk_gate_clk;
|
|
}
|
|
|
|
void
|
|
LibertyPort::setIsClockGateEnable(bool is_clk_gate_enable)
|
|
{
|
|
is_clk_gate_enable_ = is_clk_gate_enable;
|
|
}
|
|
|
|
void
|
|
LibertyPort::setIsClockGateOut(bool is_clk_gate_out)
|
|
{
|
|
is_clk_gate_out_ = is_clk_gate_out;
|
|
}
|
|
|
|
void
|
|
LibertyPort::setIsPllFeedback(bool is_pll_feedback)
|
|
{
|
|
is_pll_feedback_ = is_pll_feedback;
|
|
}
|
|
|
|
void
|
|
LibertyPort::setIsolationCellData(bool isolation_cell_data)
|
|
{
|
|
isolation_cell_data_ = isolation_cell_data;
|
|
setMemberFlag(isolation_cell_data, &LibertyPort::setIsolationCellData);
|
|
}
|
|
|
|
void
|
|
LibertyPort::setIsolationCellEnable(bool isolation_cell_enable)
|
|
{
|
|
isolation_cell_enable_ = isolation_cell_enable;
|
|
setMemberFlag(isolation_cell_enable, &LibertyPort::setIsolationCellEnable);
|
|
}
|
|
|
|
void
|
|
LibertyPort::setLevelShifterData(bool level_shifter_data)
|
|
{
|
|
level_shifter_data_ = level_shifter_data;
|
|
setMemberFlag(level_shifter_data, &LibertyPort::setLevelShifterData);
|
|
}
|
|
|
|
void
|
|
LibertyPort::setIsSwitch(bool is_switch)
|
|
{
|
|
is_switch_ = is_switch;
|
|
setMemberFlag(is_switch, &LibertyPort::setIsSwitch);
|
|
}
|
|
|
|
void
|
|
LibertyPort::setPulseClk(const RiseFall *trigger,
|
|
const RiseFall *sense)
|
|
{
|
|
pulse_clk_trigger_ = trigger;
|
|
pulse_clk_sense_ = sense;
|
|
}
|
|
|
|
void
|
|
LibertyPort::setIsDisabledConstraint(bool is_disabled)
|
|
{
|
|
is_disabled_constraint_ = is_disabled;
|
|
}
|
|
|
|
void
|
|
LibertyPort::setIsPad(bool is_pad)
|
|
{
|
|
is_pad_ = is_pad;
|
|
}
|
|
|
|
LibertyPort *
|
|
LibertyPort::cornerPort(const Corner *corner,
|
|
const MinMax *min_max)
|
|
{
|
|
return cornerPort(corner->libertyIndex(min_max));
|
|
}
|
|
|
|
const LibertyPort *
|
|
LibertyPort::cornerPort(const Corner *corner,
|
|
const MinMax *min_max) const
|
|
{
|
|
return cornerPort(corner->libertyIndex(min_max));
|
|
}
|
|
|
|
LibertyPort *
|
|
LibertyPort::cornerPort(const DcalcAnalysisPt *dcalc_ap)
|
|
{
|
|
return cornerPort(dcalc_ap->libertyIndex());
|
|
}
|
|
|
|
const LibertyPort *
|
|
LibertyPort::cornerPort(const DcalcAnalysisPt *dcalc_ap) const
|
|
{
|
|
return cornerPort(dcalc_ap->libertyIndex());
|
|
}
|
|
|
|
LibertyPort *
|
|
LibertyPort::cornerPort(int ap_index)
|
|
{
|
|
if (corner_ports_.empty())
|
|
return this;
|
|
else if (ap_index < static_cast<int>(corner_ports_.size()))
|
|
return corner_ports_[ap_index];
|
|
else
|
|
return nullptr;
|
|
}
|
|
|
|
const LibertyPort *
|
|
LibertyPort::cornerPort(int ap_index) const
|
|
{
|
|
if (corner_ports_.empty())
|
|
return this;
|
|
else if (ap_index < static_cast<int>(corner_ports_.size()))
|
|
return corner_ports_[ap_index];
|
|
else
|
|
return nullptr;
|
|
}
|
|
|
|
void
|
|
LibertyPort::setCornerPort(LibertyPort *corner_port,
|
|
int ap_index)
|
|
{
|
|
if (ap_index >= static_cast<int>(corner_ports_.size()))
|
|
corner_ports_.resize(ap_index + 1);
|
|
corner_ports_[ap_index] = corner_port;
|
|
}
|
|
|
|
const char *
|
|
LibertyPort::relatedGroundPin() const
|
|
{
|
|
if (related_ground_pin_.empty())
|
|
return nullptr;
|
|
else
|
|
return related_ground_pin_.c_str();
|
|
}
|
|
|
|
void
|
|
LibertyPort::setRelatedGroundPin(const char *related_ground_pin)
|
|
{
|
|
related_ground_pin_ = related_ground_pin;
|
|
}
|
|
|
|
const char *
|
|
LibertyPort::relatedPowerPin() const
|
|
{
|
|
if (related_power_pin_.empty())
|
|
return nullptr;
|
|
else
|
|
return related_power_pin_.c_str();
|
|
}
|
|
|
|
void
|
|
LibertyPort::setRelatedPowerPin(const char *related_power_pin)
|
|
{
|
|
related_power_pin_ = related_power_pin;
|
|
}
|
|
|
|
void
|
|
LibertyPort::setReceiverModel(ReceiverModelPtr receiver_model)
|
|
{
|
|
receiver_model_ = receiver_model;
|
|
}
|
|
|
|
string
|
|
portLibertyToSta(const char *port_name)
|
|
{
|
|
constexpr char bus_brkt_left = '[';
|
|
constexpr char bus_brkt_right = ']';
|
|
size_t name_length = strlen(port_name);
|
|
string sta_name;
|
|
for (size_t i = 0; i < name_length; i++) {
|
|
char ch = port_name[i];
|
|
if (ch == bus_brkt_left
|
|
|| ch == bus_brkt_right)
|
|
sta_name += '\\';
|
|
sta_name += ch;
|
|
}
|
|
return sta_name;
|
|
}
|
|
|
|
DriverWaveform *
|
|
LibertyPort::driverWaveform(const RiseFall *rf) const
|
|
{
|
|
return driver_waveform_[rf->index()];
|
|
}
|
|
|
|
void
|
|
LibertyPort::setDriverWaveform(DriverWaveform *driver_waveform,
|
|
const RiseFall *rf)
|
|
{
|
|
driver_waveform_[rf->index()] = driver_waveform;
|
|
}
|
|
|
|
RiseFallMinMax
|
|
LibertyPort::clockTreePathDelays() const
|
|
{
|
|
return clkTreeDelays1();
|
|
}
|
|
|
|
RiseFallMinMax
|
|
LibertyPort::clkTreeDelays() const
|
|
{
|
|
return clkTreeDelays1();
|
|
}
|
|
|
|
RiseFallMinMax
|
|
LibertyPort::clkTreeDelays1() const
|
|
{
|
|
RiseFallMinMax delays;
|
|
for (const RiseFall *from_rf : RiseFall::range()) {
|
|
for (const RiseFall *to_rf : RiseFall::range()) {
|
|
for (const MinMax *min_max : MinMax::range()) {
|
|
const TableModel *model =
|
|
clk_tree_delay_[from_rf->index()][to_rf->index()][min_max->index()];
|
|
if (model) {
|
|
float delay = model->findValue(0.0, 0.0, 0.0);
|
|
delays.setValue(from_rf, min_max, delay);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return delays;
|
|
}
|
|
|
|
float
|
|
LibertyPort::clkTreeDelay(float in_slew,
|
|
const RiseFall *rf,
|
|
const MinMax *min_max) const
|
|
{
|
|
const TableModel *model = clk_tree_delay_[rf->index()][rf->index()][min_max->index()];
|
|
if (model)
|
|
return model->findValue(in_slew, 0.0, 0.0);
|
|
else
|
|
return 0.0;
|
|
}
|
|
|
|
float
|
|
LibertyPort::clkTreeDelay(float in_slew,
|
|
const RiseFall *from_rf,
|
|
const RiseFall *to_rf,
|
|
const MinMax *min_max) const
|
|
{
|
|
const TableModel *model =
|
|
clk_tree_delay_[from_rf->index()][to_rf->index()][min_max->index()];
|
|
if (model)
|
|
return model->findValue(in_slew, 0.0, 0.0);
|
|
else
|
|
return 0.0;
|
|
}
|
|
|
|
void
|
|
LibertyPort::setClkTreeDelay(const TableModel *model,
|
|
const RiseFall *from_rf,
|
|
const RiseFall *to_rf,
|
|
const MinMax *min_max)
|
|
{
|
|
clk_tree_delay_[from_rf->index()][to_rf->index()][min_max->index()] = model;
|
|
}
|
|
|
|
void
|
|
LibertyPort::setMemberFlag(bool value,
|
|
const std::function<void(LibertyPort*,
|
|
bool)> &setter)
|
|
{
|
|
if (hasMembers()) {
|
|
LibertyPortMemberIterator member_iter(this);
|
|
while (member_iter.hasNext()) {
|
|
LibertyPort *port_bit = member_iter.next();
|
|
setter(port_bit, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
LibertyPort::setMemberFloat(float value,
|
|
const std::function<void(LibertyPort*,
|
|
float)> &setter)
|
|
{
|
|
if (hasMembers()) {
|
|
LibertyPortMemberIterator member_iter(this);
|
|
while (member_iter.hasNext()) {
|
|
LibertyPort *port_bit = member_iter.next();
|
|
setter(port_bit, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
LibertyPort::setMemberMinMaxFloat(float value,
|
|
const MinMax *min_max,
|
|
const std::function<void(LibertyPort*,
|
|
float,
|
|
const MinMax *)> &setter)
|
|
{
|
|
if (hasMembers()) {
|
|
LibertyPortMemberIterator member_iter(this);
|
|
while (member_iter.hasNext()) {
|
|
LibertyPort *port_bit = member_iter.next();
|
|
setter(port_bit, value, min_max);
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
LibertyPortSeq
|
|
sortByName(const LibertyPortSet *set)
|
|
{
|
|
LibertyPortSeq ports;
|
|
for (LibertyPort *port : *set)
|
|
ports.push_back(port);
|
|
sort(ports, LibertyPortNameLess());
|
|
return ports;
|
|
}
|
|
|
|
bool
|
|
LibertyPortLess::operator()(const LibertyPort *port1,
|
|
const LibertyPort *port2) const
|
|
{
|
|
return LibertyPort::less(port1, port2);
|
|
}
|
|
|
|
bool
|
|
LibertyPortNameLess::operator()(const LibertyPort *port1,
|
|
const LibertyPort *port2) const
|
|
{
|
|
return stringLess(port1->name(), port2->name());
|
|
}
|
|
|
|
bool
|
|
LibertyPortPairLess::operator()(const LibertyPortPair &pair1,
|
|
const LibertyPortPair &pair2) const
|
|
{
|
|
ObjectId id1 = pair1.first ? pair1.first->id() : 0;
|
|
ObjectId id2 = pair2.first ? pair2.first->id() : 0;
|
|
return id1 < id2
|
|
|| (id1 == id2
|
|
&& pair1.second->id() < pair2.second->id());
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
LibertyPortMemberIterator::LibertyPortMemberIterator(const LibertyPort *port) :
|
|
iter_(port->memberIterator())
|
|
{
|
|
}
|
|
|
|
LibertyPortMemberIterator::~LibertyPortMemberIterator()
|
|
{
|
|
delete iter_;
|
|
}
|
|
|
|
bool
|
|
LibertyPortMemberIterator::hasNext()
|
|
{
|
|
return iter_->hasNext();
|
|
}
|
|
|
|
LibertyPort *
|
|
LibertyPortMemberIterator::next()
|
|
{
|
|
return static_cast<LibertyPort*>(iter_->next());
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
BusDcl::BusDcl(const char *name,
|
|
int from,
|
|
int to) :
|
|
name_(name),
|
|
from_(from),
|
|
to_(to)
|
|
{
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
ModeDef::ModeDef(const char *name) :
|
|
name_(name)
|
|
{
|
|
}
|
|
|
|
ModeDef::~ModeDef()
|
|
{
|
|
values_.deleteContents();
|
|
}
|
|
|
|
ModeValueDef *
|
|
ModeDef::defineValue(const char *value,
|
|
FuncExpr *cond,
|
|
const char *sdf_cond)
|
|
{
|
|
ModeValueDef *val_def = new ModeValueDef(value, cond, sdf_cond);
|
|
values_[val_def->value()] = val_def;
|
|
return val_def;
|
|
}
|
|
|
|
ModeValueDef *
|
|
ModeDef::findValueDef(const char *value)
|
|
{
|
|
return values_[value];
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
ModeValueDef::ModeValueDef(const char *value,
|
|
FuncExpr *cond,
|
|
const char *sdf_cond) :
|
|
value_(value),
|
|
cond_(cond),
|
|
sdf_cond_(sdf_cond ? sdf_cond : "")
|
|
{
|
|
}
|
|
|
|
ModeValueDef::~ModeValueDef()
|
|
{
|
|
if (cond_)
|
|
cond_->deleteSubexprs();
|
|
}
|
|
|
|
void
|
|
ModeValueDef::setCond(FuncExpr *cond)
|
|
{
|
|
cond_ = cond;
|
|
}
|
|
|
|
void
|
|
ModeValueDef::setSdfCond(const char *sdf_cond)
|
|
{
|
|
sdf_cond_ = sdf_cond;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
TableTemplate::TableTemplate(const char *name) :
|
|
name_(name),
|
|
axis1_(nullptr),
|
|
axis2_(nullptr),
|
|
axis3_(nullptr)
|
|
{
|
|
}
|
|
|
|
TableTemplate::TableTemplate(const char *name,
|
|
TableAxisPtr axis1,
|
|
TableAxisPtr axis2,
|
|
TableAxisPtr axis3) :
|
|
name_(name),
|
|
axis1_(axis1),
|
|
axis2_(axis2),
|
|
axis3_(axis3)
|
|
{
|
|
}
|
|
|
|
void
|
|
TableTemplate::setName(const char *name)
|
|
{
|
|
name_ = name;
|
|
}
|
|
|
|
void
|
|
TableTemplate::setAxis1(TableAxisPtr axis)
|
|
{
|
|
axis1_ = axis;
|
|
}
|
|
|
|
void
|
|
TableTemplate::setAxis2(TableAxisPtr axis)
|
|
{
|
|
axis2_ = axis;
|
|
}
|
|
|
|
void
|
|
TableTemplate::setAxis3(TableAxisPtr axis)
|
|
{
|
|
axis3_ = axis;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
Pvt::Pvt(float process,
|
|
float voltage,
|
|
float temperature) :
|
|
process_(process),
|
|
voltage_(voltage),
|
|
temperature_(temperature)
|
|
{
|
|
}
|
|
|
|
void
|
|
Pvt::setProcess(float process)
|
|
{
|
|
process_ = process;
|
|
}
|
|
|
|
void
|
|
Pvt::setVoltage(float voltage)
|
|
{
|
|
voltage_ = voltage;
|
|
}
|
|
|
|
void
|
|
Pvt::setTemperature(float temp)
|
|
{
|
|
temperature_ = temp;
|
|
}
|
|
|
|
OperatingConditions::OperatingConditions(const char *name) :
|
|
Pvt(0.0, 0.0, 0.0),
|
|
name_(name),
|
|
// Default wireload tree.
|
|
wire_load_tree_(WireloadTree::balanced)
|
|
{
|
|
}
|
|
|
|
OperatingConditions::OperatingConditions(const char *name,
|
|
float process,
|
|
float voltage,
|
|
float temperature,
|
|
WireloadTree wire_load_tree) :
|
|
Pvt(process, voltage, temperature),
|
|
name_(name),
|
|
wire_load_tree_(wire_load_tree)
|
|
{
|
|
}
|
|
|
|
void
|
|
OperatingConditions::setWireloadTree(WireloadTree tree)
|
|
{
|
|
wire_load_tree_ = tree;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
static EnumNameMap<ScaleFactorType> scale_factor_type_map =
|
|
{{ScaleFactorType::pin_cap, "pin_cap"},
|
|
{ScaleFactorType::wire_cap, "wire_res"},
|
|
{ScaleFactorType::min_period, "min_period"},
|
|
{ScaleFactorType::cell, "cell"},
|
|
{ScaleFactorType::hold, "hold"},
|
|
{ScaleFactorType::setup, "setup"},
|
|
{ScaleFactorType::recovery, "recovery"},
|
|
{ScaleFactorType::removal, "removal"},
|
|
{ScaleFactorType::nochange, "nochange"},
|
|
{ScaleFactorType::skew, "skew"},
|
|
{ScaleFactorType::leakage_power, "leakage_power"},
|
|
{ScaleFactorType::internal_power, "internal_power"},
|
|
{ScaleFactorType::transition, "transition"},
|
|
{ScaleFactorType::min_pulse_width, "min_pulse_width"},
|
|
{ScaleFactorType::unknown, "unknown"}
|
|
};
|
|
|
|
const char *
|
|
scaleFactorTypeName(ScaleFactorType type)
|
|
{
|
|
return scale_factor_type_map.find(type);
|
|
}
|
|
|
|
ScaleFactorType
|
|
findScaleFactorType(const char *name)
|
|
{
|
|
return scale_factor_type_map.find(name, ScaleFactorType::unknown);
|
|
}
|
|
|
|
bool
|
|
scaleFactorTypeRiseFallSuffix(ScaleFactorType type)
|
|
{
|
|
return type == ScaleFactorType::cell
|
|
|| type == ScaleFactorType::hold
|
|
|| type == ScaleFactorType::setup
|
|
|| type == ScaleFactorType::recovery
|
|
|| type == ScaleFactorType::removal
|
|
|| type == ScaleFactorType::nochange
|
|
|| type == ScaleFactorType::skew;
|
|
}
|
|
|
|
bool
|
|
scaleFactorTypeRiseFallPrefix(ScaleFactorType type)
|
|
{
|
|
return type == ScaleFactorType::transition;
|
|
}
|
|
|
|
bool
|
|
scaleFactorTypeLowHighSuffix(ScaleFactorType type)
|
|
{
|
|
return type == ScaleFactorType::min_pulse_width;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
EnumNameMap<ScaleFactorPvt> scale_factor_pvt_names =
|
|
{{ScaleFactorPvt::process, "process"},
|
|
{ScaleFactorPvt::volt, "volt"},
|
|
{ScaleFactorPvt::temp, "temp"}
|
|
};
|
|
|
|
ScaleFactorPvt
|
|
findScaleFactorPvt(const char *name)
|
|
{
|
|
return scale_factor_pvt_names.find(name, ScaleFactorPvt::unknown);
|
|
}
|
|
|
|
const char *
|
|
scaleFactorPvtName(ScaleFactorPvt pvt)
|
|
{
|
|
return scale_factor_pvt_names.find(pvt);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
ScaleFactors::ScaleFactors(const char *name) :
|
|
name_(name)
|
|
{
|
|
for (int type = 0; type < scale_factor_type_count; type++) {
|
|
for (int pvt = 0; pvt < scale_factor_pvt_count; pvt++) {
|
|
for (auto rf_index : RiseFall::rangeIndex()) {
|
|
scales_[type][pvt][rf_index] = 0.0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
ScaleFactors::setScale(ScaleFactorType type,
|
|
ScaleFactorPvt pvt,
|
|
const RiseFall *rf,
|
|
float scale)
|
|
{
|
|
scales_[int(type)][int(pvt)][rf->index()] = scale;
|
|
}
|
|
|
|
void
|
|
ScaleFactors::setScale(ScaleFactorType type,
|
|
ScaleFactorPvt pvt,
|
|
float scale)
|
|
{
|
|
scales_[int(type)][int(pvt)][0] = scale;
|
|
}
|
|
|
|
float
|
|
ScaleFactors::scale(ScaleFactorType type,
|
|
ScaleFactorPvt pvt,
|
|
const RiseFall *rf)
|
|
{
|
|
return scales_[int(type)][int(pvt)][rf->index()];
|
|
}
|
|
|
|
float
|
|
ScaleFactors::scale(ScaleFactorType type,
|
|
ScaleFactorPvt pvt,
|
|
int rf_index)
|
|
{
|
|
return scales_[int(type)][int(pvt)][rf_index];
|
|
}
|
|
|
|
float
|
|
ScaleFactors::scale(ScaleFactorType type,
|
|
ScaleFactorPvt pvt)
|
|
{
|
|
return scales_[int(type)][int(pvt)][0];
|
|
}
|
|
|
|
void
|
|
ScaleFactors::print()
|
|
{
|
|
printf("%10s", " ");
|
|
for (int pvt_index = 0; pvt_index < scale_factor_pvt_count; pvt_index++) {
|
|
ScaleFactorPvt pvt = (ScaleFactorPvt) pvt_index;
|
|
printf("%10s", scaleFactorPvtName(pvt));
|
|
}
|
|
printf("\n");
|
|
for (int type_index = 0; type_index < scale_factor_type_count; type_index++) {
|
|
ScaleFactorType type = (ScaleFactorType) type_index;
|
|
printf("%10s ", scaleFactorTypeName(type));
|
|
for (int pvt_index = 0; pvt_index < scale_factor_pvt_count; pvt_index++) {
|
|
if (scaleFactorTypeRiseFallSuffix(type)
|
|
|| scaleFactorTypeRiseFallPrefix(type)
|
|
|| scaleFactorTypeLowHighSuffix(type)) {
|
|
printf(" %.3f,%.3f",
|
|
scales_[type_index][pvt_index][RiseFall::riseIndex()],
|
|
scales_[type_index][pvt_index][RiseFall::fallIndex()]);
|
|
}
|
|
else {
|
|
printf(" %.3f",
|
|
scales_[type_index][pvt_index][0]);
|
|
}
|
|
}
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
TestCell::TestCell(LibertyLibrary *library,
|
|
const char *name,
|
|
const char *filename) :
|
|
LibertyCell(library, name, filename)
|
|
{
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
OcvDerate::OcvDerate(const char *name) :
|
|
name_(name)
|
|
{
|
|
for (auto el_index : EarlyLate::rangeIndex()) {
|
|
for (auto rf_index : RiseFall::rangeIndex()) {
|
|
derate_[rf_index][el_index][int(PathType::clk)] = nullptr;
|
|
derate_[rf_index][el_index][int(PathType::data)] = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
OcvDerate::~OcvDerate()
|
|
{
|
|
stringDelete(name_);
|
|
}
|
|
|
|
const Table *
|
|
OcvDerate::derateTable(const RiseFall *rf,
|
|
const EarlyLate *early_late,
|
|
PathType path_type)
|
|
{
|
|
return derate_[rf->index()][early_late->index()][int(path_type)].get();
|
|
}
|
|
|
|
void
|
|
OcvDerate::setDerateTable(const RiseFall *rf,
|
|
const EarlyLate *early_late,
|
|
const PathType path_type,
|
|
TablePtr derate)
|
|
{
|
|
derate_[rf->index()][early_late->index()][int(path_type)] = derate;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
LibertyPgPort::LibertyPgPort(const char *name,
|
|
LibertyCell *cell) :
|
|
name_(name),
|
|
pg_type_(unknown),
|
|
cell_(cell)
|
|
{
|
|
}
|
|
|
|
void
|
|
LibertyPgPort::setPgType(PgType type)
|
|
{
|
|
pg_type_ = type;
|
|
}
|
|
|
|
void
|
|
LibertyPgPort::setVoltageName(const char *voltage_name)
|
|
{
|
|
voltage_name_ = voltage_name;
|
|
}
|
|
|
|
bool
|
|
LibertyPgPort::equiv(const LibertyPgPort *port1,
|
|
const LibertyPgPort *port2)
|
|
{
|
|
return port1->name_ == port2->name_
|
|
&& port1->pg_type_ == port2->pg_type_;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
LibertyCellPgPortIterator::LibertyCellPgPortIterator(const LibertyCell *cell) :
|
|
iter_(const_cast<LibertyCell*>(cell)->pg_port_map_)
|
|
{
|
|
}
|
|
|
|
bool
|
|
LibertyCellPgPortIterator::hasNext()
|
|
{
|
|
return iter_.hasNext();
|
|
}
|
|
|
|
LibertyPgPort *
|
|
LibertyCellPgPortIterator::next()
|
|
{
|
|
string name;
|
|
LibertyPgPort *port;
|
|
iter_.next(name, port);
|
|
return port;
|
|
}
|
|
|
|
} // namespace
|