1664 lines
46 KiB
C++
1664 lines
46 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 "Power.hh"
|
|
|
|
#include <algorithm> // max
|
|
#include <cmath> // abs
|
|
|
|
#include "cudd.h"
|
|
#include "Stats.hh"
|
|
#include "Debug.hh"
|
|
#include "EnumNameMap.hh"
|
|
#include "Hash.hh"
|
|
#include "MinMax.hh"
|
|
#include "Units.hh"
|
|
#include "Transition.hh"
|
|
#include "TimingRole.hh"
|
|
#include "Liberty.hh"
|
|
#include "InternalPower.hh"
|
|
#include "LeakagePower.hh"
|
|
#include "Sequential.hh"
|
|
#include "TimingArc.hh"
|
|
#include "FuncExpr.hh"
|
|
#include "PortDirection.hh"
|
|
#include "Network.hh"
|
|
#include "Clock.hh"
|
|
#include "Sdc.hh"
|
|
#include "Graph.hh"
|
|
#include "DcalcAnalysisPt.hh"
|
|
#include "GraphDelayCalc.hh"
|
|
#include "Corner.hh"
|
|
#include "Path.hh"
|
|
#include "search/Levelize.hh"
|
|
#include "search/Sim.hh"
|
|
#include "Search.hh"
|
|
#include "Bfs.hh"
|
|
#include "ClkNetwork.hh"
|
|
|
|
// Related liberty not supported:
|
|
// library
|
|
// default_cell_leakage_power : 0;
|
|
// output_voltage (default_VDD_VSS_output) {
|
|
// leakage_power
|
|
// related_pg_pin : VDD;
|
|
// internal_power
|
|
// input_voltage : default_VDD_VSS_input;
|
|
// pin
|
|
// output_voltage : default_VDD_VSS_output;
|
|
|
|
namespace sta {
|
|
|
|
using std::abs;
|
|
using std::max;
|
|
using std::min;
|
|
using std::isnormal;
|
|
using std::vector;
|
|
using std::map;
|
|
|
|
static bool
|
|
isPositiveUnate(const LibertyCell *cell,
|
|
const LibertyPort *from,
|
|
const LibertyPort *to);
|
|
|
|
static EnumNameMap<PwrActivityOrigin> pwr_activity_origin_map =
|
|
{{PwrActivityOrigin::global, "global"},
|
|
{PwrActivityOrigin::input, "input"},
|
|
{PwrActivityOrigin::user, "user"},
|
|
{PwrActivityOrigin::vcd, "vcd"},
|
|
{PwrActivityOrigin::saif, "saif"},
|
|
{PwrActivityOrigin::propagated, "propagated"},
|
|
{PwrActivityOrigin::clock, "clock"},
|
|
{PwrActivityOrigin::constant, "constant"},
|
|
{PwrActivityOrigin::defaulted, "defaulted"},
|
|
{PwrActivityOrigin::unknown, "unknown"}};
|
|
|
|
Power::Power(StaState *sta) :
|
|
StaState(sta),
|
|
global_activity_(),
|
|
input_activity_(), // default set in ensureActivities()
|
|
seq_activity_map_(100, SeqPinHash(network_), SeqPinEqual()),
|
|
activities_valid_(false),
|
|
bdd_(sta)
|
|
{
|
|
}
|
|
|
|
void
|
|
Power::clear()
|
|
{
|
|
global_activity_.init();
|
|
input_activity_.init();
|
|
user_activity_map_.clear();
|
|
seq_activity_map_.clear();
|
|
activity_map_.clear();
|
|
activities_valid_ = false;
|
|
}
|
|
|
|
void
|
|
Power::setGlobalActivity(float density,
|
|
float duty)
|
|
{
|
|
global_activity_.set(density, duty, PwrActivityOrigin::global);
|
|
activities_valid_ = false;
|
|
}
|
|
|
|
void
|
|
Power::unsetGlobalActivity()
|
|
{
|
|
global_activity_.init();
|
|
activities_valid_ = false;
|
|
}
|
|
|
|
void
|
|
Power::setInputActivity(float density,
|
|
float duty)
|
|
{
|
|
input_activity_.set(density, duty, PwrActivityOrigin::input);
|
|
activities_valid_ = false;
|
|
}
|
|
|
|
void
|
|
Power::unsetInputActivity()
|
|
{
|
|
input_activity_.init();
|
|
activities_valid_ = false;
|
|
}
|
|
|
|
void
|
|
Power::setInputPortActivity(const Port *input_port,
|
|
float density,
|
|
float duty)
|
|
{
|
|
Instance *top_inst = network_->topInstance();
|
|
const Pin *pin = network_->findPin(top_inst, input_port);
|
|
if (pin) {
|
|
user_activity_map_[pin] = {density, duty, PwrActivityOrigin::user};
|
|
activities_valid_ = false;
|
|
}
|
|
}
|
|
|
|
void
|
|
Power::unsetInputPortActivity(const Port *input_port)
|
|
{
|
|
Instance *top_inst = network_->topInstance();
|
|
const Pin *pin = network_->findPin(top_inst, input_port);
|
|
if (pin) {
|
|
user_activity_map_.erase(pin);
|
|
activities_valid_ = false;
|
|
}
|
|
}
|
|
|
|
void
|
|
Power::setUserActivity(const Pin *pin,
|
|
float density,
|
|
float duty,
|
|
PwrActivityOrigin origin)
|
|
{
|
|
user_activity_map_[pin] = {density, duty, origin};
|
|
activities_valid_ = false;
|
|
}
|
|
|
|
void
|
|
Power::unsetUserActivity(const Pin *pin)
|
|
{
|
|
user_activity_map_.erase(pin);
|
|
activities_valid_ = false;
|
|
}
|
|
|
|
PwrActivity &
|
|
Power::userActivity(const Pin *pin)
|
|
{
|
|
return user_activity_map_[pin];
|
|
}
|
|
|
|
bool
|
|
Power::hasUserActivity(const Pin *pin)
|
|
{
|
|
return user_activity_map_.hasKey(pin);
|
|
}
|
|
|
|
void
|
|
Power::setActivity(const Pin *pin,
|
|
PwrActivity &activity)
|
|
{
|
|
debugPrint(debug_, "power_activity", 3, "set %s %.2e %.2f %s",
|
|
network_->pathName(pin),
|
|
activity.density(),
|
|
activity.duty(),
|
|
pwr_activity_origin_map.find(activity.origin()));
|
|
activity_map_[pin] = activity;
|
|
}
|
|
|
|
PwrActivity &
|
|
Power::activity(const Pin *pin)
|
|
{
|
|
return activity_map_[pin];
|
|
}
|
|
|
|
bool
|
|
Power::hasActivity(const Pin *pin)
|
|
{
|
|
return activity_map_.hasKey(pin);
|
|
}
|
|
|
|
// Sequential internal pins may not be in the netlist so their
|
|
// activities are stored by instance/liberty_port pairs.
|
|
void
|
|
Power::setSeqActivity(const Instance *reg,
|
|
LibertyPort *output,
|
|
PwrActivity &activity)
|
|
{
|
|
seq_activity_map_[SeqPin(reg, output)] = activity;
|
|
activities_valid_ = false;
|
|
}
|
|
|
|
bool
|
|
Power::hasSeqActivity(const Instance *reg,
|
|
LibertyPort *output)
|
|
{
|
|
return seq_activity_map_.hasKey(SeqPin(reg, output));
|
|
}
|
|
|
|
PwrActivity &
|
|
Power::seqActivity(const Instance *reg,
|
|
LibertyPort *output)
|
|
{
|
|
return seq_activity_map_[SeqPin(reg, output)];
|
|
}
|
|
|
|
SeqPinHash::SeqPinHash(const Network *network) :
|
|
network_(network)
|
|
{
|
|
}
|
|
|
|
size_t
|
|
SeqPinHash::operator()(const SeqPin &pin) const
|
|
{
|
|
return hashSum(network_->id(pin.first), pin.second->id());
|
|
}
|
|
|
|
bool
|
|
SeqPinEqual::operator()(const SeqPin &pin1,
|
|
const SeqPin &pin2) const
|
|
{
|
|
return pin1.first == pin2.first
|
|
&& pin1.second == pin2.second;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
void
|
|
Power::power(const Corner *corner,
|
|
// Return values.
|
|
PowerResult &total,
|
|
PowerResult &sequential,
|
|
PowerResult &combinational,
|
|
PowerResult &clock,
|
|
PowerResult ¯o,
|
|
PowerResult &pad)
|
|
{
|
|
total.clear();
|
|
sequential.clear();
|
|
combinational.clear();
|
|
clock.clear();
|
|
macro.clear();
|
|
pad.clear();
|
|
|
|
ensureActivities();
|
|
Stats stats(debug_, report_);
|
|
LeafInstanceIterator *inst_iter = network_->leafInstanceIterator();
|
|
while (inst_iter->hasNext()) {
|
|
Instance *inst = inst_iter->next();
|
|
LibertyCell *cell = network_->libertyCell(inst);
|
|
if (cell) {
|
|
PowerResult inst_power = power(inst, cell, corner);
|
|
if (cell->isMacro()
|
|
|| cell->isMemory()
|
|
|| cell->interfaceTiming())
|
|
macro.incr(inst_power);
|
|
else if (cell->isPad())
|
|
pad.incr(inst_power);
|
|
else if (inClockNetwork(inst))
|
|
clock.incr(inst_power);
|
|
else if (cell->hasSequentials())
|
|
sequential.incr(inst_power);
|
|
else
|
|
combinational.incr(inst_power);
|
|
total.incr(inst_power);
|
|
}
|
|
}
|
|
delete inst_iter;
|
|
stats.report("Find power");
|
|
}
|
|
|
|
bool
|
|
Power::inClockNetwork(const Instance *inst)
|
|
{
|
|
InstancePinIterator *pin_iter = network_->pinIterator(inst);
|
|
while (pin_iter->hasNext()) {
|
|
const Pin *pin = pin_iter->next();
|
|
if (network_->direction(pin)->isAnyOutput()
|
|
&& !clk_network_->isClock(pin)) {
|
|
delete pin_iter;
|
|
return false;
|
|
}
|
|
}
|
|
delete pin_iter;
|
|
return true;
|
|
}
|
|
|
|
PowerResult
|
|
Power::power(const Instance *inst,
|
|
const Corner *corner)
|
|
{
|
|
if (network_->isHierarchical(inst)) {
|
|
PowerResult result;
|
|
powerInside(inst, corner, result);
|
|
return result;
|
|
}
|
|
LibertyCell *cell = network_->libertyCell(inst);
|
|
if (cell) {
|
|
ensureActivities();
|
|
return power(inst, cell, corner);
|
|
}
|
|
return PowerResult();
|
|
}
|
|
|
|
void
|
|
Power::powerInside(const Instance *hinst,
|
|
const Corner *corner,
|
|
PowerResult &result)
|
|
{
|
|
InstanceChildIterator *child_iter = network_->childIterator(hinst);
|
|
while (child_iter->hasNext()) {
|
|
Instance *child = child_iter->next();
|
|
if (network_->isHierarchical(child))
|
|
powerInside(child, corner, result);
|
|
else {
|
|
LibertyCell *cell = network_->libertyCell(child);
|
|
if (cell) {
|
|
PowerResult inst_power = power(child, cell, corner);
|
|
result.incr(inst_power);
|
|
}
|
|
}
|
|
}
|
|
delete child_iter;
|
|
}
|
|
|
|
typedef std::pair<Instance*, float> InstPower;
|
|
|
|
InstanceSeq
|
|
Power::highestPowerInstances(size_t count,
|
|
const Corner *corner)
|
|
{
|
|
vector<InstPower> inst_pwrs;
|
|
LeafInstanceIterator *inst_iter = network_->leafInstanceIterator();
|
|
while (inst_iter->hasNext()) {
|
|
Instance *inst = inst_iter->next();
|
|
PowerResult pwr = power(inst, corner);
|
|
inst_pwrs.push_back({inst, pwr.total()});
|
|
}
|
|
delete inst_iter;
|
|
|
|
sort(inst_pwrs.begin(), inst_pwrs.end(), [](InstPower &inst_pwr1,
|
|
InstPower &inst_pwr2) {
|
|
return inst_pwr1.second > inst_pwr2.second;
|
|
});
|
|
|
|
InstanceSeq insts;
|
|
for (size_t i = 0; i < count; i++)
|
|
insts.push_back(inst_pwrs[i].first);
|
|
return insts;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
class ActivitySrchPred : public SearchPredNonLatch2
|
|
{
|
|
public:
|
|
explicit ActivitySrchPred(const StaState *sta);
|
|
virtual bool searchThru(Edge *edge);
|
|
};
|
|
|
|
ActivitySrchPred::ActivitySrchPred(const StaState *sta) :
|
|
SearchPredNonLatch2(sta)
|
|
{
|
|
}
|
|
|
|
bool
|
|
ActivitySrchPred::searchThru(Edge *edge)
|
|
{
|
|
const TimingRole *role = edge->role();
|
|
return SearchPredNonLatch2::searchThru(edge)
|
|
&& role != TimingRole::regClkToQ();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
class PropActivityVisitor : public VertexVisitor, StaState
|
|
{
|
|
public:
|
|
PropActivityVisitor(Power *power,
|
|
BfsFwdIterator *bfs);
|
|
virtual VertexVisitor *copy() const;
|
|
virtual void visit(Vertex *vertex);
|
|
InstanceSet &visitedRegs() { return visited_regs_; }
|
|
void init();
|
|
float maxChange() const { return max_change_; }
|
|
|
|
private:
|
|
bool setActivityCheck(const Pin *pin,
|
|
PwrActivity &activity);
|
|
|
|
static constexpr float change_tolerance_ = .001;
|
|
InstanceSet visited_regs_;
|
|
float max_change_;
|
|
Power *power_;
|
|
BfsFwdIterator *bfs_;
|
|
};
|
|
|
|
PropActivityVisitor::PropActivityVisitor(Power *power,
|
|
BfsFwdIterator *bfs) :
|
|
StaState(power),
|
|
visited_regs_(network_),
|
|
max_change_(0.0),
|
|
power_(power),
|
|
bfs_(bfs)
|
|
{
|
|
}
|
|
|
|
VertexVisitor *
|
|
PropActivityVisitor::copy() const
|
|
{
|
|
return new PropActivityVisitor(power_, bfs_);
|
|
}
|
|
|
|
void
|
|
PropActivityVisitor::init()
|
|
{
|
|
max_change_ = 0.0;
|
|
}
|
|
|
|
void
|
|
PropActivityVisitor::visit(Vertex *vertex)
|
|
{
|
|
Pin *pin = vertex->pin();
|
|
Instance *inst = network_->instance(pin);
|
|
debugPrint(debug_, "power_activity", 3, "visit %s",
|
|
vertex->to_string(this).c_str());
|
|
bool changed = false;
|
|
if (power_->hasUserActivity(pin)) {
|
|
PwrActivity &activity = power_->userActivity(pin);
|
|
changed = setActivityCheck(pin, activity);
|
|
}
|
|
else {
|
|
if (network_->isLoad(pin)) {
|
|
VertexInEdgeIterator edge_iter(vertex, graph_);
|
|
if (edge_iter.hasNext()) {
|
|
Edge *edge = edge_iter.next();
|
|
if (edge->isWire()) {
|
|
Vertex *from_vertex = edge->from(graph_);
|
|
const Pin *from_pin = from_vertex->pin();
|
|
PwrActivity &from_activity = power_->activity(from_pin);
|
|
PwrActivity to_activity(from_activity.density(),
|
|
from_activity.duty(),
|
|
PwrActivityOrigin::propagated);
|
|
changed = setActivityCheck(pin, to_activity);
|
|
}
|
|
}
|
|
}
|
|
if (network_->isDriver(pin)) {
|
|
LibertyPort *port = network_->libertyPort(pin);
|
|
if (port) {
|
|
LibertyCell *test_cell = port->libertyCell()->testCell();
|
|
if (test_cell)
|
|
port = test_cell->findLibertyPort(port->name());
|
|
}
|
|
if (port) {
|
|
FuncExpr *func = port->function();
|
|
if (func) {
|
|
PwrActivity activity = power_->evalActivity(func, inst);
|
|
changed = setActivityCheck(pin, activity);
|
|
}
|
|
if (port->isClockGateOut()) {
|
|
const Pin *enable, *clk, *gclk;
|
|
power_->clockGatePins(inst, enable, clk, gclk);
|
|
if (enable && clk && gclk) {
|
|
PwrActivity activity1 = power_->findActivity(clk);
|
|
PwrActivity activity2 = power_->findActivity(enable);
|
|
float p1 = activity1.duty();
|
|
float p2 = activity2.duty();
|
|
PwrActivity activity(activity1.density() * p2 + activity2.density() * p1,
|
|
p1 * p2,
|
|
PwrActivityOrigin::propagated);
|
|
changed = setActivityCheck(gclk, activity);
|
|
debugPrint(debug_, "power_activity", 3, "gated_clk %s %.2e %.2f",
|
|
network_->pathName(gclk),
|
|
activity.density(),
|
|
activity.duty());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (changed) {
|
|
LibertyCell *cell = network_->libertyCell(inst);
|
|
if (cell) {
|
|
LibertyCell *test_cell = cell->libertyCell()->testCell();
|
|
if (network_->isLoad(pin)) {
|
|
if (cell->hasSequentials()
|
|
|| (test_cell
|
|
&& test_cell->hasSequentials())) {
|
|
debugPrint(debug_, "power_activity", 3, "pending seq %s",
|
|
network_->pathName(inst));
|
|
visited_regs_.insert(inst);
|
|
}
|
|
// Gated clock cells latch the enable so there is no EN->GCLK timing arc.
|
|
if (cell->isClockGate()) {
|
|
const Pin *enable, *clk, *gclk;
|
|
power_->clockGatePins(inst, enable, clk, gclk);
|
|
if (gclk) {
|
|
Vertex *gclk_vertex = graph_->pinDrvrVertex(gclk);
|
|
bfs_->enqueue(gclk_vertex);
|
|
}
|
|
}
|
|
}
|
|
bfs_->enqueueAdjacentVertices(vertex);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return true if the activity changed.
|
|
bool
|
|
PropActivityVisitor::setActivityCheck(const Pin *pin,
|
|
PwrActivity &activity)
|
|
{
|
|
float min_rf_slew = power_->getMinRfSlew(pin);
|
|
float max_density = (min_rf_slew > 0.0) ? 1.0 / min_rf_slew : INF;
|
|
if (activity.density() > max_density)
|
|
activity.setDensity(max_density);
|
|
PwrActivity &prev_activity = power_->activity(pin);
|
|
float density_delta = abs(activity.density() - prev_activity.density());
|
|
float duty_delta = abs(activity.duty() - prev_activity.duty());
|
|
if (density_delta > change_tolerance_
|
|
|| duty_delta > change_tolerance_
|
|
|| activity.origin() != prev_activity.origin()) {
|
|
max_change_ = max(max_change_, density_delta);
|
|
max_change_ = max(max_change_, duty_delta);
|
|
power_->setActivity(pin, activity);
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
void
|
|
Power::clockGatePins(const Instance *inst,
|
|
// Return values.
|
|
const Pin *&enable,
|
|
const Pin *&clk,
|
|
const Pin *&gclk) const
|
|
{
|
|
enable = nullptr;
|
|
clk = nullptr;
|
|
gclk = nullptr;
|
|
InstancePinIterator *pin_iter = network_->pinIterator(inst);
|
|
while (pin_iter->hasNext()) {
|
|
const Pin *pin = pin_iter->next();
|
|
const LibertyPort *port = network_->libertyPort(pin);
|
|
if (port->isClockGateEnable())
|
|
enable = pin;
|
|
if (port->isClockGateClock())
|
|
clk = pin;
|
|
if (port->isClockGateOut())
|
|
gclk = pin;
|
|
}
|
|
delete pin_iter;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
PwrActivity
|
|
Power::evalActivity(FuncExpr *expr,
|
|
const Instance *inst)
|
|
{
|
|
LibertyPort *func_port = expr->port();
|
|
if (func_port && func_port->direction()->isInternal())
|
|
return findSeqActivity(inst, func_port);
|
|
else {
|
|
DdNode *bdd = bdd_.funcBdd(expr);
|
|
float duty = evalBddDuty(bdd, inst);
|
|
float density = evalBddActivity(bdd, inst);
|
|
|
|
Cudd_RecursiveDeref(bdd_.cuddMgr(), bdd);
|
|
bdd_.clearVarMap();
|
|
return PwrActivity(density, duty, PwrActivityOrigin::propagated);
|
|
}
|
|
}
|
|
|
|
// Find duty when from_port is sensitized.
|
|
float
|
|
Power::evalDiffDuty(FuncExpr *expr,
|
|
LibertyPort *from_port,
|
|
const Instance *inst)
|
|
{
|
|
DdNode *bdd = bdd_.funcBdd(expr);
|
|
DdNode *var_node = bdd_.findNode(from_port);
|
|
unsigned var_index = Cudd_NodeReadIndex(var_node);
|
|
DdNode *diff = Cudd_bddBooleanDiff(bdd_.cuddMgr(), bdd, var_index);
|
|
Cudd_Ref(diff);
|
|
float duty = evalBddDuty(diff, inst);
|
|
|
|
Cudd_RecursiveDeref(bdd_.cuddMgr(), diff);
|
|
Cudd_RecursiveDeref(bdd_.cuddMgr(), bdd);
|
|
bdd_.clearVarMap();
|
|
return duty;
|
|
}
|
|
|
|
// As suggested by
|
|
// https://stackoverflow.com/questions/63326728/cudd-printminterm-accessing-the-individual-minterms-in-the-sum-of-products
|
|
float
|
|
Power::evalBddDuty(DdNode *bdd,
|
|
const Instance *inst)
|
|
{
|
|
if (Cudd_IsConstant(bdd)) {
|
|
if (bdd == Cudd_ReadOne(bdd_.cuddMgr()))
|
|
return 1.0;
|
|
else if (bdd == Cudd_ReadLogicZero(bdd_.cuddMgr()))
|
|
return 0.0;
|
|
else
|
|
criticalError(1100, "unknown cudd constant");
|
|
}
|
|
else {
|
|
float duty0 = evalBddDuty(Cudd_E(bdd), inst);
|
|
float duty1 = evalBddDuty(Cudd_T(bdd), inst);
|
|
unsigned int index = Cudd_NodeReadIndex(bdd);
|
|
int var_index = Cudd_ReadPerm(bdd_.cuddMgr(), index);
|
|
const LibertyPort *port = bdd_.varIndexPort(var_index);
|
|
if (port->direction()->isInternal())
|
|
return findSeqActivity(inst, const_cast<LibertyPort*>(port)).duty();
|
|
else {
|
|
const Pin *pin = findLinkPin(inst, port);
|
|
if (pin) {
|
|
PwrActivity var_activity = findActivity(pin);
|
|
float var_duty = var_activity.duty();
|
|
float duty = duty0 * (1.0 - var_duty) + duty1 * var_duty;
|
|
if (Cudd_IsComplement(bdd))
|
|
duty = 1.0 - duty;
|
|
return duty;
|
|
}
|
|
}
|
|
}
|
|
return 0.0;
|
|
}
|
|
|
|
// https://www.brown.edu/Departments/Engineering/Courses/engn2912/Lectures/LP-02-logic-power-est.pdf
|
|
// F(x0, x1, .. ) is sensitized when F(Xi=1) xor F(Xi=0)
|
|
// F(Xi=1), F(Xi=0) are the cofactors of F wrt Xi.
|
|
float
|
|
Power::evalBddActivity(DdNode *bdd,
|
|
const Instance *inst)
|
|
{
|
|
float density = 0.0;
|
|
for (const auto [port, var_node] : bdd_.portVarMap()) {
|
|
const Pin *pin = findLinkPin(inst, port);
|
|
if (pin) {
|
|
PwrActivity var_activity = findActivity(pin);
|
|
unsigned int var_index = Cudd_NodeReadIndex(var_node);
|
|
DdNode *diff = Cudd_bddBooleanDiff(bdd_.cuddMgr(), bdd, var_index);
|
|
Cudd_Ref(diff);
|
|
float diff_duty = evalBddDuty(diff, inst);
|
|
Cudd_RecursiveDeref(bdd_.cuddMgr(), diff);
|
|
float var_density = var_activity.density() * diff_duty;
|
|
density += var_density;
|
|
debugPrint(debug_, "power_activity", 3, "var %s %.3e * %.3f = %.3e",
|
|
port->name(),
|
|
var_activity.density(),
|
|
diff_duty,
|
|
var_density);
|
|
}
|
|
}
|
|
return density;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
void
|
|
Power::ensureActivities()
|
|
{
|
|
// No need to propagate activites if global activity is set.
|
|
if (!global_activity_.isSet()) {
|
|
if (!activities_valid_) {
|
|
Stats stats(debug_, report_);
|
|
// Clear existing activities.
|
|
activity_map_.clear();
|
|
seq_activity_map_.clear();
|
|
|
|
// Initialize default input activity (after sdc is defined)
|
|
// unless it has been set by command.
|
|
if (input_activity_.origin() == PwrActivityOrigin::unknown) {
|
|
float min_period = clockMinPeriod();
|
|
float density = 0.1 / (min_period != 0.0
|
|
? min_period
|
|
: units_->timeUnit()->scale());
|
|
input_activity_.set(density, 0.5, PwrActivityOrigin::input);
|
|
}
|
|
ActivitySrchPred activity_srch_pred(this);
|
|
BfsFwdIterator bfs(BfsIndex::other, &activity_srch_pred, this);
|
|
seedActivities(bfs);
|
|
PropActivityVisitor visitor(this, &bfs);
|
|
// Propagate activities through combinational logic.
|
|
bfs.visit(levelize_->maxLevel(), &visitor);
|
|
// Propagate activiities through registers.
|
|
InstanceSet regs = std::move(visitor.visitedRegs());
|
|
int pass = 1;
|
|
while (!regs.empty() && pass < max_activity_passes_) {
|
|
visitor.init();
|
|
for (const Instance *reg : regs)
|
|
// Propagate activiities across register D->Q.
|
|
seedRegOutputActivities(reg, bfs);
|
|
// Propagate register output activities through
|
|
// combinational logic.
|
|
bfs.visit(levelize_->maxLevel(), &visitor);
|
|
regs = std::move(visitor.visitedRegs());
|
|
debugPrint(debug_, "power_activity", 1, "Pass %d change %.2f",
|
|
pass, visitor.maxChange());
|
|
pass++;
|
|
}
|
|
stats.report("Find power activities");
|
|
activities_valid_ = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
Power::seedActivities(BfsFwdIterator &bfs)
|
|
{
|
|
for (Vertex *vertex : levelize_->roots()) {
|
|
const Pin *pin = vertex->pin();
|
|
// Clock activities are baked in.
|
|
if (!sdc_->isLeafPinClock(pin)
|
|
&& !network_->direction(pin)->isInternal()) {
|
|
debugPrint(debug_, "power_activity", 3, "seed %s",
|
|
vertex->to_string(this).c_str());
|
|
if (hasUserActivity(pin))
|
|
setActivity(pin, userActivity(pin));
|
|
else
|
|
// Default inputs without explicit activities to the input default.
|
|
setActivity(pin, input_activity_);
|
|
Vertex *vertex = graph_->pinDrvrVertex(pin);
|
|
bfs.enqueueAdjacentVertices(vertex);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
Power::seedRegOutputActivities(const Instance *inst,
|
|
BfsFwdIterator &bfs)
|
|
{
|
|
LibertyCell *cell = network_->libertyCell(inst);
|
|
LibertyCell *test_cell = cell->testCell();
|
|
const SequentialSeq &seqs = test_cell
|
|
? test_cell->sequentials()
|
|
: cell->sequentials();
|
|
for (Sequential *seq : seqs) {
|
|
seedRegOutputActivities(inst, seq, seq->output(), false);
|
|
seedRegOutputActivities(inst, seq, seq->outputInv(), true);
|
|
// Enqueue register output pins with functions that reference
|
|
// the sequential internal pins (IQ, IQN).
|
|
InstancePinIterator *pin_iter = network_->pinIterator(inst);
|
|
while (pin_iter->hasNext()) {
|
|
Pin *pin = pin_iter->next();
|
|
LibertyPort *port = network_->libertyPort(pin);
|
|
if (test_cell)
|
|
port = test_cell->findLibertyPort(port->name());
|
|
if (port) {
|
|
FuncExpr *func = port->function();
|
|
Vertex *vertex = graph_->pinDrvrVertex(pin);
|
|
if (vertex
|
|
&& func
|
|
&& (func->port() == seq->output()
|
|
|| func->port() == seq->outputInv())) {
|
|
debugPrint(debug_, "power_reg", 1, "enqueue reg output %s",
|
|
vertex->to_string(this).c_str());
|
|
bfs.enqueue(vertex);
|
|
}
|
|
}
|
|
}
|
|
delete pin_iter;
|
|
}
|
|
}
|
|
|
|
void
|
|
Power::seedRegOutputActivities(const Instance *reg,
|
|
Sequential *seq,
|
|
LibertyPort *output,
|
|
bool invert)
|
|
{
|
|
const Pin *out_pin = network_->findPin(reg, output);
|
|
if (!hasUserActivity(out_pin)) {
|
|
PwrActivity activity = evalActivity(seq->data(), reg);
|
|
// Register output activity cannnot exceed one transition per clock cycle,
|
|
// but latch output can.
|
|
if (seq->isRegister()) {
|
|
FuncExpr *clk_func = seq->clock();
|
|
if (clk_func->port()) {
|
|
const Pin *pin = network_->findPin(reg, clk_func->port());
|
|
const Clock *clk = findClk(pin);
|
|
if (clk) {
|
|
if (activity.density() > 1.0 / clk->period())
|
|
activity.setDensity(1.0 / clk->period());
|
|
}
|
|
}
|
|
}
|
|
if (invert)
|
|
activity.setDuty(1.0 - activity.duty());
|
|
activity.setOrigin(PwrActivityOrigin::propagated);
|
|
setSeqActivity(reg, output, activity);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
PowerResult
|
|
Power::power(const Instance *inst,
|
|
LibertyCell *cell,
|
|
const Corner *corner)
|
|
{
|
|
PowerResult result;
|
|
findInternalPower(inst, cell, corner, result);
|
|
findSwitchingPower(inst, cell, corner, result);
|
|
findLeakagePower(inst, cell, corner, result);
|
|
return result;
|
|
}
|
|
|
|
const Clock *
|
|
Power::findInstClk(const Instance *inst)
|
|
{
|
|
const Clock *inst_clk = nullptr;
|
|
InstancePinIterator *pin_iter = network_->pinIterator(inst);
|
|
while (pin_iter->hasNext()) {
|
|
const Pin *pin = pin_iter->next();
|
|
const Clock *clk = findClk(pin);
|
|
if (clk) {
|
|
inst_clk = clk;
|
|
break;
|
|
}
|
|
}
|
|
delete pin_iter;
|
|
return inst_clk;
|
|
}
|
|
|
|
void
|
|
Power::findInternalPower(const Instance *inst,
|
|
LibertyCell *cell,
|
|
const Corner *corner,
|
|
// Return values.
|
|
PowerResult &result)
|
|
{
|
|
const DcalcAnalysisPt *dcalc_ap = corner->findDcalcAnalysisPt(MinMax::max());
|
|
InstancePinIterator *pin_iter = network_->pinIterator(inst);
|
|
while (pin_iter->hasNext()) {
|
|
const Pin *to_pin = pin_iter->next();
|
|
LibertyPort *to_port = network_->libertyPort(to_pin);
|
|
if (to_port) {
|
|
float load_cap = to_port->direction()->isAnyOutput()
|
|
? graph_delay_calc_->loadCap(to_pin, dcalc_ap)
|
|
: 0.0;
|
|
PwrActivity activity = findActivity(to_pin);
|
|
if (to_port->direction()->isAnyOutput())
|
|
findOutputInternalPower(to_port, inst, cell, activity,
|
|
load_cap, corner, result);
|
|
if (to_port->direction()->isAnyInput())
|
|
findInputInternalPower(to_pin, to_port, inst, cell, activity,
|
|
load_cap, corner, result);
|
|
}
|
|
}
|
|
delete pin_iter;
|
|
}
|
|
|
|
void
|
|
Power::findInputInternalPower(const Pin *pin,
|
|
LibertyPort *port,
|
|
const Instance *inst,
|
|
LibertyCell *cell,
|
|
PwrActivity &activity,
|
|
float load_cap,
|
|
const Corner *corner,
|
|
// Return values.
|
|
PowerResult &result)
|
|
{
|
|
const MinMax *min_max = MinMax::max();
|
|
LibertyCell *corner_cell = cell->cornerCell(corner, min_max);
|
|
const LibertyPort *corner_port = port->cornerPort(corner, min_max);
|
|
if (corner_cell && corner_port) {
|
|
const InternalPowerSeq &internal_pwrs = corner_cell->internalPowers(corner_port);
|
|
if (!internal_pwrs.empty()) {
|
|
debugPrint(debug_, "power", 2, "internal input %s/%s cap %s",
|
|
network_->pathName(inst),
|
|
port->name(),
|
|
units_->capacitanceUnit()->asString(load_cap));
|
|
debugPrint(debug_, "power", 2, " when act/ns duty energy power");
|
|
const DcalcAnalysisPt *dcalc_ap = corner->findDcalcAnalysisPt(MinMax::max());
|
|
const Pvt *pvt = dcalc_ap->operatingConditions();
|
|
Vertex *vertex = graph_->pinLoadVertex(pin);
|
|
float internal = 0.0;
|
|
for (InternalPower *pwr : internal_pwrs) {
|
|
const char *related_pg_pin = pwr->relatedPgPin();
|
|
float energy = 0.0;
|
|
int rf_count = 0;
|
|
for (const RiseFall *rf : RiseFall::range()) {
|
|
float slew = getSlew(vertex, rf, corner);
|
|
if (!delayInf(slew)) {
|
|
float table_energy = pwr->power(rf, pvt, slew, load_cap);
|
|
energy += table_energy;
|
|
rf_count++;
|
|
}
|
|
}
|
|
if (rf_count)
|
|
energy /= rf_count; // average non-inf energies
|
|
float duty = 1.0; // fallback default
|
|
FuncExpr *when = pwr->when();
|
|
if (when) {
|
|
const LibertyPort *out_corner_port = findExprOutPort(when);
|
|
if (out_corner_port) {
|
|
LibertyPort *out_port = findLinkPort(cell, out_corner_port);
|
|
if (out_port) {
|
|
FuncExpr *func = out_port->function();
|
|
if (func && func->hasPort(port))
|
|
duty = evalDiffDuty(func, port, inst);
|
|
else
|
|
duty = evalActivity(when, inst).duty();
|
|
}
|
|
}
|
|
else
|
|
duty = evalActivity(when, inst).duty();
|
|
}
|
|
float port_internal = energy * duty * activity.density();
|
|
debugPrint(debug_, "power", 2, " %3s %6s %.2f %.2f %9.2e %9.2e %s",
|
|
port->name(),
|
|
when ? when->to_string().c_str() : "",
|
|
activity.density() * 1e-9,
|
|
duty,
|
|
energy,
|
|
port_internal,
|
|
related_pg_pin ? related_pg_pin : "no pg_pin");
|
|
internal += port_internal;
|
|
}
|
|
result.incrInternal(internal);
|
|
}
|
|
}
|
|
}
|
|
|
|
float
|
|
Power::getSlew(Vertex *vertex,
|
|
const RiseFall *rf,
|
|
const Corner *corner)
|
|
{
|
|
const DcalcAnalysisPt *dcalc_ap = corner->findDcalcAnalysisPt(MinMax::max());
|
|
const Pin *pin = vertex->pin();
|
|
if (clk_network_->isIdealClock(pin))
|
|
return clk_network_->idealClkSlew(pin, rf, MinMax::max());
|
|
else
|
|
return delayAsFloat(graph_->slew(vertex, rf, dcalc_ap->index()));
|
|
}
|
|
|
|
float
|
|
Power::getMinRfSlew(const Pin *pin)
|
|
{
|
|
Vertex *vertex, *bidir_vertex;
|
|
graph_->pinVertices(pin, vertex, bidir_vertex);
|
|
if (vertex) {
|
|
const MinMax *min_max = MinMax::min();
|
|
Slew mm_slew = min_max->initValue();
|
|
for (const DcalcAnalysisPt *dcalc_ap : corners_->dcalcAnalysisPts()) {
|
|
DcalcAPIndex ap_index = dcalc_ap->index();
|
|
const Slew &slew1 = graph_->slew(vertex, RiseFall::rise(), ap_index);
|
|
const Slew &slew2 = graph_->slew(vertex, RiseFall::fall(), ap_index);
|
|
Slew slew = delayAsFloat(slew1 + slew2) / 2.0;
|
|
if (delayGreater(slew, mm_slew, min_max, this))
|
|
mm_slew = slew;
|
|
}
|
|
return delayAsFloat(mm_slew);
|
|
}
|
|
return 0.0;
|
|
}
|
|
|
|
LibertyPort *
|
|
Power::findExprOutPort(FuncExpr *expr)
|
|
{
|
|
LibertyPort *port;
|
|
switch (expr->op()) {
|
|
case FuncExpr::op_port:
|
|
port = expr->port();
|
|
if (port && port->direction()->isAnyOutput())
|
|
return expr->port();
|
|
return nullptr;
|
|
case FuncExpr::op_not:
|
|
port = findExprOutPort(expr->left());
|
|
if (port)
|
|
return port;
|
|
return nullptr;
|
|
case FuncExpr::op_or:
|
|
case FuncExpr::op_and:
|
|
case FuncExpr::op_xor:
|
|
port = findExprOutPort(expr->left());
|
|
if (port)
|
|
return port;
|
|
port = findExprOutPort(expr->right());
|
|
if (port)
|
|
return port;
|
|
return nullptr;
|
|
case FuncExpr::op_one:
|
|
case FuncExpr::op_zero:
|
|
return nullptr;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void
|
|
Power::findOutputInternalPower(const LibertyPort *to_port,
|
|
const Instance *inst,
|
|
LibertyCell *cell,
|
|
PwrActivity &to_activity,
|
|
float load_cap,
|
|
const Corner *corner,
|
|
// Return values.
|
|
PowerResult &result)
|
|
{
|
|
debugPrint(debug_, "power", 2, "internal output %s/%s cap %s",
|
|
network_->pathName(inst),
|
|
to_port->name(),
|
|
units_->capacitanceUnit()->asString(load_cap));
|
|
const DcalcAnalysisPt *dcalc_ap = corner->findDcalcAnalysisPt(MinMax::max());
|
|
const Pvt *pvt = dcalc_ap->operatingConditions();
|
|
LibertyCell *corner_cell = cell->cornerCell(dcalc_ap);
|
|
const LibertyPort *to_corner_port = to_port->cornerPort(dcalc_ap);
|
|
FuncExpr *func = to_port->function();
|
|
|
|
map<const char*, float, StringLessIf> pg_duty_sum;
|
|
for (InternalPower *pwr : corner_cell->internalPowers(to_corner_port)) {
|
|
const LibertyPort *from_corner_port = pwr->relatedPort();
|
|
if (from_corner_port) {
|
|
const Pin *from_pin = findLinkPin(inst, from_corner_port);
|
|
float from_density = findActivity(from_pin).density();
|
|
float duty = findInputDuty(inst, func, pwr);
|
|
const char *related_pg_pin = pwr->relatedPgPin();
|
|
// Note related_pg_pin may be null.
|
|
pg_duty_sum[related_pg_pin] += from_density * duty;
|
|
}
|
|
}
|
|
|
|
debugPrint(debug_, "power", 2,
|
|
" when act/ns duty wgt energy power");
|
|
float internal = 0.0;
|
|
for (InternalPower *pwr : corner_cell->internalPowers(to_corner_port)) {
|
|
FuncExpr *when = pwr->when();
|
|
const char *related_pg_pin = pwr->relatedPgPin();
|
|
float duty = findInputDuty(inst, func, pwr);
|
|
Vertex *from_vertex = nullptr;
|
|
bool positive_unate = true;
|
|
const LibertyPort *from_corner_port = pwr->relatedPort();
|
|
const Pin *from_pin = nullptr;
|
|
if (from_corner_port) {
|
|
positive_unate = isPositiveUnate(corner_cell, from_corner_port, to_corner_port);
|
|
from_pin = findLinkPin(inst, from_corner_port);
|
|
if (from_pin)
|
|
from_vertex = graph_->pinLoadVertex(from_pin);
|
|
}
|
|
float energy = 0.0;
|
|
int rf_count = 0;
|
|
for (const RiseFall *to_rf : RiseFall::range()) {
|
|
// Use unateness to find from_rf.
|
|
const RiseFall *from_rf = positive_unate ? to_rf : to_rf->opposite();
|
|
float slew = from_vertex
|
|
? getSlew(from_vertex, from_rf, corner)
|
|
: 0.0;
|
|
if (!delayInf(slew)) {
|
|
float table_energy = pwr->power(to_rf, pvt, slew, load_cap);
|
|
energy += table_energy;
|
|
rf_count++;
|
|
}
|
|
}
|
|
if (rf_count)
|
|
energy /= rf_count; // average non-inf energies
|
|
auto duty_sum_iter = pg_duty_sum.find(related_pg_pin);
|
|
float weight = 0.0;
|
|
if (duty_sum_iter != pg_duty_sum.end()) {
|
|
float duty_sum = duty_sum_iter->second;
|
|
if (duty_sum != 0.0 && from_pin) {
|
|
float from_density = findActivity(from_pin).density();
|
|
weight = from_density * duty / duty_sum;
|
|
}
|
|
}
|
|
float port_internal = weight * energy * to_activity.density();
|
|
debugPrint(debug_, "power", 2, "%3s -> %-3s %6s %.3f %.3f %.3f %9.2e %9.2e %s",
|
|
from_corner_port ? from_corner_port->name() : "-" ,
|
|
to_port->name(),
|
|
when ? when->to_string().c_str() : "",
|
|
to_activity.density() * 1e-9,
|
|
duty,
|
|
weight,
|
|
energy,
|
|
port_internal,
|
|
related_pg_pin ? related_pg_pin : "no pg_pin");
|
|
internal += port_internal;
|
|
}
|
|
result.incrInternal(internal);
|
|
}
|
|
|
|
float
|
|
Power::findInputDuty(const Instance *inst,
|
|
FuncExpr *func,
|
|
InternalPower *pwr)
|
|
|
|
{
|
|
const LibertyPort *from_corner_port = pwr->relatedPort();
|
|
if (from_corner_port) {
|
|
LibertyPort *from_port = findLinkPort(network_->libertyCell(inst),
|
|
from_corner_port);
|
|
const Pin *from_pin = network_->findPin(inst, from_port);
|
|
if (from_pin) {
|
|
FuncExpr *when = pwr->when();
|
|
Vertex *from_vertex = graph_->pinLoadVertex(from_pin);
|
|
if (func && func->hasPort(from_port)) {
|
|
float duty = evalDiffDuty(func, from_port, inst);
|
|
return duty;
|
|
}
|
|
else if (when)
|
|
return evalActivity(when, inst).duty();
|
|
else if (search_->isClock(from_vertex))
|
|
return 0.5;
|
|
return 0.5;
|
|
}
|
|
}
|
|
return 0.0;
|
|
}
|
|
|
|
// Hack to find cell port that corresponds to corner_port.
|
|
LibertyPort *
|
|
Power::findLinkPort(const LibertyCell *cell,
|
|
const LibertyPort *corner_port)
|
|
{
|
|
return cell->findLibertyPort(corner_port->name());
|
|
}
|
|
|
|
Pin *
|
|
Power::findLinkPin(const Instance *inst,
|
|
const LibertyPort *corner_port)
|
|
{
|
|
const LibertyCell *cell = network_->libertyCell(inst);
|
|
LibertyPort *port = findLinkPort(cell, corner_port);
|
|
return network_->findPin(inst, port);
|
|
}
|
|
|
|
static bool
|
|
isPositiveUnate(const LibertyCell *cell,
|
|
const LibertyPort *from,
|
|
const LibertyPort *to)
|
|
{
|
|
const TimingArcSetSeq &arc_sets = cell->timingArcSets(from, to);
|
|
if (!arc_sets.empty()) {
|
|
TimingSense sense = arc_sets[0]->sense();
|
|
return sense == TimingSense::positive_unate
|
|
|| sense == TimingSense::non_unate;
|
|
}
|
|
// default
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
void
|
|
Power::findSwitchingPower(const Instance *inst,
|
|
LibertyCell *cell,
|
|
const Corner *corner,
|
|
// Return values.
|
|
PowerResult &result)
|
|
{
|
|
const DcalcAnalysisPt *dcalc_ap = corner->findDcalcAnalysisPt(MinMax::max());
|
|
LibertyCell *corner_cell = cell->cornerCell(dcalc_ap);
|
|
InstancePinIterator *pin_iter = network_->pinIterator(inst);
|
|
while (pin_iter->hasNext()) {
|
|
const Pin *to_pin = pin_iter->next();
|
|
const LibertyPort *to_port = network_->libertyPort(to_pin);
|
|
if (to_port) {
|
|
float load_cap = to_port->direction()->isAnyOutput()
|
|
? graph_delay_calc_->loadCap(to_pin, dcalc_ap)
|
|
: 0.0;
|
|
PwrActivity activity = findActivity(to_pin);
|
|
if (to_port->direction()->isAnyOutput()) {
|
|
float volt = portVoltage(corner_cell, to_port, dcalc_ap);
|
|
float switching = .5 * load_cap * volt * volt * activity.density();
|
|
debugPrint(debug_, "power", 2, "switching %s/%s activity = %.2e volt = %.2f %.3e",
|
|
cell->name(),
|
|
to_port->name(),
|
|
activity.density(),
|
|
volt,
|
|
switching);
|
|
result.incrSwitching(switching);
|
|
}
|
|
}
|
|
}
|
|
delete pin_iter;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
|
|
void
|
|
Power::findLeakagePower(const Instance *inst,
|
|
LibertyCell *cell,
|
|
const Corner *corner,
|
|
// Return values.
|
|
PowerResult &result)
|
|
{
|
|
LibertyCell *corner_cell = cell->cornerCell(corner, MinMax::max());
|
|
float cond_leakage = 0.0;
|
|
bool found_cond = false;
|
|
float uncond_leakage = 0.0;
|
|
bool found_uncond = false;
|
|
float cond_duty_sum = 0.0;
|
|
for (LeakagePower *leak : *corner_cell->leakagePowers()) {
|
|
FuncExpr *when = leak->when();
|
|
if (when) {
|
|
PwrActivity cond_activity = evalActivity(when, inst);
|
|
float cond_duty = cond_activity.duty();
|
|
debugPrint(debug_, "power", 2, "leakage %s %s %.3e * %.2f",
|
|
cell->name(),
|
|
when->to_string().c_str(),
|
|
leak->power(),
|
|
cond_duty);
|
|
cond_leakage += leak->power() * cond_duty;
|
|
if (leak->power() > 0.0)
|
|
cond_duty_sum += cond_duty;
|
|
found_cond = true;
|
|
}
|
|
else {
|
|
debugPrint(debug_, "power", 2, "leakage -- %s %.3e",
|
|
cell->name(),
|
|
leak->power());
|
|
uncond_leakage += leak->power();
|
|
found_uncond = true;
|
|
}
|
|
}
|
|
float leakage = 0.0;
|
|
float cell_leakage;
|
|
bool cell_leakage_exists;
|
|
cell->leakagePower(cell_leakage, cell_leakage_exists);
|
|
if (cell_leakage_exists) {
|
|
float duty = 1.0 - cond_duty_sum;
|
|
debugPrint(debug_, "power", 2, "leakage cell %s %.3e * %.2f",
|
|
cell->name(),
|
|
cell_leakage,
|
|
duty);
|
|
cell_leakage *= duty;
|
|
}
|
|
// Ignore unconditional leakage unless there are no conditional leakage groups.
|
|
if (found_cond)
|
|
leakage = cond_leakage;
|
|
else if (found_uncond)
|
|
leakage = uncond_leakage;
|
|
if (cell_leakage_exists)
|
|
leakage += cell_leakage;
|
|
debugPrint(debug_, "power", 2, "leakage %s %.3e",
|
|
cell->name(),
|
|
leakage);
|
|
result.incrLeakage(leakage);
|
|
}
|
|
|
|
// External.
|
|
PwrActivity
|
|
Power::pinActivity(const Pin *pin)
|
|
{
|
|
ensureActivities();
|
|
return findActivity(pin);
|
|
}
|
|
|
|
PwrActivity
|
|
Power::findActivity(const Pin *pin)
|
|
{
|
|
Vertex *vertex = graph_->pinLoadVertex(pin);
|
|
if (vertex && vertex->isConstant())
|
|
return PwrActivity(0.0, 0.0, PwrActivityOrigin::constant);
|
|
else if (vertex && search_->isClock(vertex)) {
|
|
if (activity_map_.hasKey(pin)) {
|
|
PwrActivity &activity = activity_map_[pin];
|
|
if (activity.origin() != PwrActivityOrigin::unknown)
|
|
return activity;
|
|
}
|
|
const Clock *clk = findClk(pin);
|
|
float duty = clockDuty(clk);
|
|
return PwrActivity(2.0 / clk->period(), duty, PwrActivityOrigin::clock);
|
|
}
|
|
else if (global_activity_.isSet())
|
|
return global_activity_;
|
|
else if (activity_map_.hasKey(pin)) {
|
|
PwrActivity &activity = activity_map_[pin];
|
|
if (activity.origin() != PwrActivityOrigin::unknown)
|
|
return activity;
|
|
}
|
|
return PwrActivity(0.0, 0.0, PwrActivityOrigin::unknown);
|
|
}
|
|
|
|
float
|
|
Power::clockDuty(const Clock *clk)
|
|
{
|
|
if (clk->isGenerated()) {
|
|
const Clock *master = clk->masterClk();
|
|
if (master == nullptr)
|
|
return 0.5; // punt
|
|
else
|
|
return clockDuty(master);
|
|
}
|
|
else {
|
|
const FloatSeq *waveform = clk->waveform();
|
|
float rise_time = (*waveform)[0];
|
|
float fall_time = (*waveform)[1];
|
|
float duty = (fall_time - rise_time) / clk->period();
|
|
return duty;
|
|
}
|
|
}
|
|
|
|
PwrActivity
|
|
Power::findSeqActivity(const Instance *inst,
|
|
LibertyPort *port)
|
|
{
|
|
if (global_activity_.isSet())
|
|
return global_activity_;
|
|
else if (hasSeqActivity(inst, port)) {
|
|
PwrActivity &activity = seqActivity(inst, port);
|
|
return activity;
|
|
}
|
|
return PwrActivity();
|
|
}
|
|
|
|
float
|
|
Power::portVoltage(LibertyCell *cell,
|
|
const LibertyPort *port,
|
|
const DcalcAnalysisPt *dcalc_ap)
|
|
{
|
|
return pgNameVoltage(cell, port->relatedPowerPin(), dcalc_ap);
|
|
}
|
|
|
|
float
|
|
Power::pgNameVoltage(LibertyCell *cell,
|
|
const char *pg_port_name,
|
|
const DcalcAnalysisPt *dcalc_ap)
|
|
{
|
|
if (pg_port_name) {
|
|
LibertyPgPort *pg_port = cell->findPgPort(pg_port_name);
|
|
if (pg_port) {
|
|
const char *volt_name = pg_port->voltageName();
|
|
LibertyLibrary *library = cell->libertyLibrary();
|
|
float voltage;
|
|
bool exists;
|
|
library->supplyVoltage(volt_name, voltage, exists);
|
|
if (exists)
|
|
return voltage;
|
|
}
|
|
}
|
|
|
|
const Pvt *pvt = dcalc_ap->operatingConditions();
|
|
if (pvt == nullptr)
|
|
pvt = cell->libertyLibrary()->defaultOperatingConditions();
|
|
if (pvt)
|
|
return pvt->voltage();
|
|
else
|
|
return 0.0;
|
|
}
|
|
|
|
const Clock *
|
|
Power::findClk(const Pin *to_pin)
|
|
{
|
|
const Clock *clk = nullptr;
|
|
Vertex *to_vertex = graph_->pinDrvrVertex(to_pin);
|
|
if (to_vertex) {
|
|
VertexPathIterator path_iter(to_vertex, this);
|
|
while (path_iter.hasNext()) {
|
|
Path *path = path_iter.next();
|
|
const Clock *path_clk = path->clock(this);
|
|
if (path_clk
|
|
&& (clk == nullptr
|
|
|| path_clk->period() < clk->period()))
|
|
clk = path_clk;
|
|
}
|
|
}
|
|
return clk;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
void
|
|
Power::reportActivityAnnotation(bool report_unannotated,
|
|
bool report_annotated)
|
|
{
|
|
size_t vcd_count = 0;
|
|
size_t saif_count = 0;
|
|
size_t input_count = 0;
|
|
for (auto const& [pin, activity] : user_activity_map_) {
|
|
PwrActivityOrigin origin = activity.origin();
|
|
switch (origin) {
|
|
case PwrActivityOrigin::vcd:
|
|
vcd_count++;
|
|
break;
|
|
case PwrActivityOrigin::saif:
|
|
saif_count++;
|
|
break;
|
|
case PwrActivityOrigin::user:
|
|
input_count++;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if (vcd_count > 0)
|
|
report_->reportLine("vcd %5zu", vcd_count);
|
|
if (saif_count > 0)
|
|
report_->reportLine("saif %5zu", saif_count);
|
|
if (input_count > 0)
|
|
report_->reportLine("input %5zu", input_count);
|
|
size_t pin_count = pinCount();
|
|
size_t unannotated_count = pin_count - vcd_count - saif_count - input_count;
|
|
report_->reportLine("unannotated %5zu", unannotated_count);
|
|
|
|
if (report_annotated) {
|
|
PinSeq annotated_pins;
|
|
for (auto const& [pin, activity] : user_activity_map_)
|
|
annotated_pins.push_back(pin);
|
|
sort(annotated_pins.begin(), annotated_pins.end(), PinPathNameLess(sdc_network_));
|
|
report_->reportLine("Annotated pins:");
|
|
for (const Pin *pin : annotated_pins) {
|
|
const PwrActivity &activity = user_activity_map_[pin];
|
|
PwrActivityOrigin origin = activity.origin();
|
|
const char *origin_name = pwr_activity_origin_map.find(origin);
|
|
report_->reportLine("%5s %s",
|
|
origin_name,
|
|
sdc_network_->pathName(pin));
|
|
}
|
|
}
|
|
if (report_unannotated) {
|
|
PinSeq unannotated_pins;
|
|
findUnannotatedPins(network_->topInstance(), unannotated_pins);
|
|
LeafInstanceIterator *inst_iter = network_->leafInstanceIterator();
|
|
while (inst_iter->hasNext()) {
|
|
const Instance *inst = inst_iter->next();
|
|
findUnannotatedPins(inst, unannotated_pins);
|
|
}
|
|
delete inst_iter;
|
|
|
|
sort(unannotated_pins.begin(), unannotated_pins.end(),
|
|
PinPathNameLess(sdc_network_));
|
|
report_->reportLine("Unannotated pins:");
|
|
for (const Pin *pin : unannotated_pins) {
|
|
report_->reportLine(" %s", sdc_network_->pathName(pin));
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
Power::findUnannotatedPins(const Instance *inst,
|
|
PinSeq &unannotated_pins)
|
|
{
|
|
InstancePinIterator *pin_iter = network_->pinIterator(inst);
|
|
while (pin_iter->hasNext()) {
|
|
const Pin *pin = pin_iter->next();
|
|
if (!network_->direction(pin)->isInternal()
|
|
&& user_activity_map_.find(pin) == user_activity_map_.end())
|
|
unannotated_pins.push_back(pin);
|
|
}
|
|
delete pin_iter;
|
|
}
|
|
|
|
// leaf pins - internal pins + top instance pins
|
|
size_t
|
|
Power::pinCount()
|
|
{
|
|
size_t count = 0;
|
|
LeafInstanceIterator *leaf_iter = network_->leafInstanceIterator();
|
|
while (leaf_iter->hasNext()) {
|
|
Instance *leaf = leaf_iter->next();
|
|
InstancePinIterator *pin_iter = network_->pinIterator(leaf);
|
|
while (pin_iter->hasNext()) {
|
|
const Pin *pin = pin_iter->next();
|
|
if (!network_->direction(pin)->isInternal())
|
|
count++;
|
|
}
|
|
delete pin_iter;
|
|
}
|
|
delete leaf_iter;
|
|
|
|
InstancePinIterator *pin_iter = network_->pinIterator(network_->topInstance());
|
|
while (pin_iter->hasNext()) {
|
|
pin_iter->next();
|
|
count++;
|
|
}
|
|
delete pin_iter;
|
|
|
|
return count;
|
|
}
|
|
|
|
float
|
|
Power::clockMinPeriod()
|
|
{
|
|
ClockSeq *clks = sdc_->clocks();
|
|
if (clks && !clks->empty()) {
|
|
float min_period = INF;
|
|
for (const Clock *clk : *clks)
|
|
min_period = min(min_period, clk->period());
|
|
return min_period;
|
|
}
|
|
else
|
|
return 0.0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
PowerResult::PowerResult() :
|
|
internal_(0.0),
|
|
switching_(0.0),
|
|
leakage_(0.0)
|
|
{
|
|
}
|
|
|
|
void
|
|
PowerResult::clear()
|
|
{
|
|
internal_ = 0.0;
|
|
switching_ = 0.0;
|
|
leakage_ = 0.0;
|
|
}
|
|
|
|
float
|
|
PowerResult::total() const
|
|
{
|
|
return internal_ + switching_ + leakage_;
|
|
}
|
|
|
|
void
|
|
PowerResult::incrInternal(float pwr)
|
|
{
|
|
internal_ += pwr;
|
|
}
|
|
|
|
void
|
|
PowerResult::incrSwitching(float pwr)
|
|
{
|
|
switching_ += pwr;
|
|
}
|
|
|
|
void
|
|
PowerResult::incrLeakage(float pwr)
|
|
{
|
|
leakage_ += pwr;
|
|
}
|
|
|
|
void
|
|
PowerResult::incr(PowerResult &result)
|
|
{
|
|
internal_ += result.internal_;
|
|
switching_ += result.switching_;
|
|
leakage_ += result.leakage_;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
PwrActivity::PwrActivity(float density,
|
|
float duty,
|
|
PwrActivityOrigin origin) :
|
|
density_(density),
|
|
duty_(duty),
|
|
origin_(origin)
|
|
{
|
|
check();
|
|
}
|
|
|
|
PwrActivity::PwrActivity() :
|
|
density_(0.0),
|
|
duty_(0.0),
|
|
origin_(PwrActivityOrigin::unknown)
|
|
{
|
|
}
|
|
|
|
void
|
|
PwrActivity::setDensity(float density)
|
|
{
|
|
density_ = density;
|
|
}
|
|
|
|
void
|
|
PwrActivity::setDuty(float duty)
|
|
{
|
|
duty_ = duty;
|
|
}
|
|
|
|
void
|
|
PwrActivity::setOrigin(PwrActivityOrigin origin)
|
|
{
|
|
origin_ = origin;
|
|
}
|
|
|
|
void
|
|
PwrActivity::init()
|
|
{
|
|
density_ = 0.0;
|
|
duty_ = 0.0;
|
|
origin_ = PwrActivityOrigin::unknown;
|
|
}
|
|
|
|
void
|
|
PwrActivity::set(float density,
|
|
float duty,
|
|
PwrActivityOrigin origin)
|
|
{
|
|
density_ = density;
|
|
duty_ = duty;
|
|
origin_ = origin;
|
|
check();
|
|
}
|
|
|
|
void
|
|
PwrActivity::check()
|
|
{
|
|
// Densities can get very small from multiplying probabilities
|
|
// through deep chains of logic. Clip them to prevent floating
|
|
// point anomalies.
|
|
if (abs(density_) < min_density)
|
|
density_ = 0.0;
|
|
}
|
|
|
|
bool
|
|
PwrActivity::isSet() const
|
|
{
|
|
return origin_ != PwrActivityOrigin::unknown;
|
|
}
|
|
|
|
const char *
|
|
PwrActivity::originName() const
|
|
{
|
|
return pwr_activity_origin_map.find(origin_);
|
|
}
|
|
|
|
} // namespace
|