OpenSTA/power/Power.cc

1640 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::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 &macro,
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) {
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 (network_->isLoad(pin) && cell) {
if (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();
InstanceSet::Iterator reg_iter(regs);
while (reg_iter.hasNext()) {
const Instance *reg = reg_iter.next();
// 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);
for (Sequential *seq : cell->sequentials()) {
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 (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