Merge remote-tracking branch 'upstream/master' into sta_update_0729
This commit is contained in:
commit
8541d77bc7
|
|
@ -45,8 +45,9 @@ ArcDcalcWaveforms::inputWaveform(ArcDcalcArg &dcalc_arg,
|
|||
Graph *graph = sta->graph();
|
||||
Report *report = sta->report();
|
||||
const Pin *in_pin = dcalc_arg.inPin();
|
||||
LibertyPort *port = network->libertyPort(in_pin);
|
||||
if (port) {
|
||||
LibertyPort *link_port = network->libertyPort(in_pin);
|
||||
if (link_port) {
|
||||
LibertyPort *port = link_port->scenePort(scene, min_max);
|
||||
const RiseFall *in_rf = dcalc_arg.inEdge();
|
||||
DriverWaveform *driver_waveform = port->driverWaveform(in_rf);
|
||||
if (driver_waveform) {
|
||||
|
|
|
|||
|
|
@ -186,7 +186,7 @@ ArcDcalcArg::drvrCell() const
|
|||
return arc_->to()->libertyCell();
|
||||
}
|
||||
|
||||
const LibertyLibrary *
|
||||
LibertyLibrary *
|
||||
ArcDcalcArg::drvrLibrary() const
|
||||
{
|
||||
return arc_->to()->libertyLibrary();
|
||||
|
|
@ -198,6 +198,13 @@ ArcDcalcArg::drvrEdge() const
|
|||
return arc_->toEdge()->asRiseFall();
|
||||
}
|
||||
|
||||
void
|
||||
ArcDcalcArg::setSceneArc(const Scene *scene,
|
||||
const MinMax *min_max)
|
||||
{
|
||||
arc_ = arc_->sceneArc(scene->libertyIndex(min_max));
|
||||
}
|
||||
|
||||
const Net *
|
||||
ArcDcalcArg::drvrNet(const Network *network) const
|
||||
{
|
||||
|
|
|
|||
|
|
@ -227,6 +227,7 @@ DelayCalcBase::setDcalcArgParasiticSlew(ArcDcalcArg &gate,
|
|||
gate.edge(),
|
||||
scene, min_max);
|
||||
gate.setInSlew(in_slew);
|
||||
gate.setSceneArc(scene, min_max);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -48,6 +48,13 @@ namespace sta {
|
|||
// Lawrence Pillage - “Electronic Circuit & System Simulation Methods” 1998
|
||||
// McGraw-Hill, Inc. New York, NY.
|
||||
|
||||
// "PRIMA: Passive Reduced-order Interconnect Macromodeling Algorithm",
|
||||
// Altan Odabasioglu, Mustafa Celik, and Lawrence T. Pileggi
|
||||
// IEEE Transactions on Computer-Aided Design of Integrated Circuits and Systems,
|
||||
// vol. 17, no. 8, August 1998
|
||||
|
||||
using ParasiticSet = std::set<const Parasitic*>;
|
||||
|
||||
ArcDelayCalc *
|
||||
makePrimaDelayCalc(StaState *sta)
|
||||
{
|
||||
|
|
@ -176,6 +183,7 @@ PrimaDelayCalc::gateDelay(const Pin *drvr_pin,
|
|||
ArcDcalcArgSeq dcalc_args;
|
||||
dcalc_args.emplace_back(nullptr, drvr_pin, nullptr, arc, in_slew, load_cap,
|
||||
parasitic);
|
||||
dcalc_args[0].setSceneArc(scene, min_max);
|
||||
ArcDcalcResultSeq dcalc_results = gateDelays(dcalc_args, load_pin_index_map,
|
||||
scene, min_max);
|
||||
return dcalc_results[0];
|
||||
|
|
@ -249,9 +257,10 @@ PrimaDelayCalc::checkArgs(ArcDcalcArgSeq &dcalc_args,
|
|||
if (output_waveforms->slewAxis()->inBounds(in_slew)) {
|
||||
if (output_waveforms->capAxis()->inBounds(dcalc_arg.loadCap())) {
|
||||
output_waveforms_[drvr_idx] = output_waveforms;
|
||||
debugPrint(debug_, "prima", 1, "{} {}",
|
||||
debugPrint(debug_, "prima", 1, "{} {} {}",
|
||||
dcalc_arg.drvrCell()->name(),
|
||||
dcalc_arg.drvrEdge()->to_string().c_str());
|
||||
dcalc_arg.drvrEdge()->to_string().c_str(),
|
||||
scene->name());
|
||||
LibertyCell *drvr_cell = dcalc_arg.drvrCell();
|
||||
drvr_cell->ensureVoltageWaveforms(scenes_);
|
||||
}
|
||||
|
|
@ -355,7 +364,7 @@ PrimaDelayCalc::simulate1(const MatrixSd &G,
|
|||
v_ = v_prev_ = x_to_v * x_init;
|
||||
|
||||
time_step_ = time_step_prev_ = timeStep();
|
||||
debugPrint(debug_, "ccs_dcalc", 1, "time step {}",
|
||||
debugPrint(debug_, "prima", 1, "time step {}",
|
||||
delayAsString(time_step_, this));
|
||||
|
||||
MatrixSd A(order, order);
|
||||
|
|
@ -395,7 +404,7 @@ PrimaDelayCalc::simulate1(const MatrixSd &G,
|
|||
v_ = x_to_v * x;
|
||||
|
||||
const ArcDcalcArg &dcalc_arg = (*dcalc_args_)[0];
|
||||
debugPrint(debug_, "ccs_dcalc", 3, "{} ceff {} VDrvr {:.4f} Idrvr {}",
|
||||
debugPrint(debug_, "prima", 3, "{} ceff {} VDrvr {:.4f} Idrvr {}",
|
||||
delayAsString(time, this),
|
||||
units_->capacitanceUnit()->asString(ceff_[0]),
|
||||
voltage(dcalc_arg.drvrPin()),
|
||||
|
|
@ -420,7 +429,7 @@ PrimaDelayCalc::simulate1(const MatrixSd &G,
|
|||
double
|
||||
PrimaDelayCalc::timeStep()
|
||||
{
|
||||
// Needs to use LTE for time step dynamic control.
|
||||
// Should use LTE for dynamic time step control.
|
||||
return driverResistance() * load_cap_ * .02;
|
||||
}
|
||||
|
||||
|
|
@ -436,7 +445,8 @@ PrimaDelayCalc::driverResistance()
|
|||
{
|
||||
const Pin *drvr_pin = (*dcalc_args_)[0].drvrPin();
|
||||
LibertyPort *drvr_port = network_->libertyPort(drvr_pin);
|
||||
return drvr_port->driveResistance(drvr_rf_, min_max_);
|
||||
LibertyPort *scene_port = drvr_port->scenePort(scene_, min_max_);
|
||||
return scene_port->driveResistance(drvr_rf_, min_max_);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -460,61 +470,68 @@ PrimaDelayCalc::initSim()
|
|||
void
|
||||
PrimaDelayCalc::findNodeCount()
|
||||
{
|
||||
includes_pin_caps_ = parasitics_->includesPinCaps(parasitic_network_);
|
||||
coupling_cap_multiplier_ = 1.0;
|
||||
|
||||
node_capacitances_.clear();
|
||||
pin_node_map_.clear();
|
||||
node_index_map_.clear();
|
||||
node_count_ = 0;
|
||||
|
||||
for (ParasiticNode *node : parasitics_->nodes(parasitic_network_)) {
|
||||
if (!parasitics_->isExternal(node)) {
|
||||
size_t node_idx = node_index_map_.size();
|
||||
node_index_map_[node] = node_idx;
|
||||
const Pin *pin = parasitics_->pin(node);
|
||||
if (pin) {
|
||||
pin_node_map_[pin] = node_idx;
|
||||
debugPrint(debug_, "ccs_dcalc", 1, "pin {} node {}",
|
||||
network_->pathName(pin), node_idx);
|
||||
// Collect the nodes that enter G by walking out from the drivers through
|
||||
// resistors. G is conductance-only, so a node with no resistive path to a
|
||||
// driver has an all-zero row which is dropped to prevent singularity.
|
||||
ParasiticSet visited_parasitics;
|
||||
for (const ArcDcalcArg &dcalc_arg : *dcalc_args_) {
|
||||
const Parasitic *parasitic = dcalc_arg.parasitic();
|
||||
if (!visited_parasitics.contains(parasitic)) {
|
||||
ParasiticNodeResistorMap resistor_map =
|
||||
parasitics_->parasiticNodeResistorMap(parasitic);
|
||||
std::vector<ParasiticNode *> queue;
|
||||
for (size_t drvr_idx = 0; drvr_idx < drvr_count_; drvr_idx++) {
|
||||
const Pin *drvr_pin = (*dcalc_args_)[drvr_idx].drvrPin();
|
||||
ParasiticNode *drvr_node =
|
||||
parasitics_->findParasiticNode(parasitic, drvr_pin);
|
||||
if (drvr_node && !parasitics_->isExternal(drvr_node)
|
||||
&& !node_index_map_.contains(drvr_node)) {
|
||||
placeNode(drvr_node, node_count_++);
|
||||
queue.push_back(drvr_node);
|
||||
}
|
||||
}
|
||||
double cap = parasitics_->nodeGndCap(node) + pinCapacitance(node);
|
||||
node_capacitances_.push_back(cap);
|
||||
while (!queue.empty()) {
|
||||
ParasiticNode *node = queue.back();
|
||||
queue.pop_back();
|
||||
size_t node_index = node_index_map_[node];
|
||||
auto resistor_itr = resistor_map.find(node);
|
||||
if (resistor_itr != resistor_map.end()) {
|
||||
for (ParasiticResistor *resistor : resistor_itr->second) {
|
||||
ParasiticNode *next_node = parasitics_->otherNode(resistor, node);
|
||||
if (next_node
|
||||
&& !parasitics_->isExternal(next_node)
|
||||
&& !node_index_map_.contains(next_node)) {
|
||||
bool shorted = parasitics_->value(resistor) == 0;
|
||||
placeNode(next_node, shorted ? node_index : node_count_++);
|
||||
queue.push_back(next_node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
visited_parasitics.insert(parasitic);
|
||||
}
|
||||
}
|
||||
|
||||
for (ParasiticCapacitor *capacitor : parasitics_->capacitors(parasitic_network_)) {
|
||||
float cap = parasitics_->value(capacitor) * coupling_cap_multiplier_;
|
||||
ParasiticNode *node1 = parasitics_->node1(capacitor);
|
||||
if (node1 && !parasitics_->isExternal(node1)) {
|
||||
size_t node_idx = node_index_map_[node1];
|
||||
node_capacitances_[node_idx] += cap;
|
||||
}
|
||||
ParasiticNode *node2 = parasitics_->node2(capacitor);
|
||||
if (node2 && !parasitics_->isExternal(node2)) {
|
||||
size_t node_idx = node_index_map_[node2];
|
||||
node_capacitances_[node_idx] += cap;
|
||||
}
|
||||
}
|
||||
node_count_ = node_index_map_.size();
|
||||
}
|
||||
|
||||
float
|
||||
PrimaDelayCalc::pinCapacitance(ParasiticNode *node)
|
||||
// Add node to network at index (shared by drivers and by the
|
||||
// resistor walk). A merged short reuses the near node's index.
|
||||
void
|
||||
PrimaDelayCalc::placeNode(ParasiticNode *node,
|
||||
size_t index)
|
||||
{
|
||||
node_index_map_[node] = index;
|
||||
const Pin *pin = parasitics_->pin(node);
|
||||
float pin_cap = 0.0;
|
||||
const Sdc *sdc = scene_->sdc();
|
||||
if (pin) {
|
||||
Port *port = network_->port(pin);
|
||||
LibertyPort *lib_port = network_->libertyPort(port);
|
||||
if (lib_port) {
|
||||
if (!includes_pin_caps_)
|
||||
pin_cap = sdc->pinCapacitance(pin, drvr_rf_, scene_, min_max_);
|
||||
}
|
||||
else if (network_->isTopLevelPort(pin))
|
||||
pin_cap = sdc->portExtCap(port, drvr_rf_, min_max_);
|
||||
pin_node_map_[pin] = index;
|
||||
debugPrint(debug_, "prima", 1, "pin {} node {}",
|
||||
network_->pathName(pin), index);
|
||||
}
|
||||
return pin_cap;
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -556,6 +573,16 @@ PrimaDelayCalc::setXinit()
|
|||
x_init_[node_count_ + p] = drvr_init_volt;
|
||||
}
|
||||
|
||||
std::pair<size_t, bool>
|
||||
PrimaDelayCalc::nodeIndex(const ParasiticNode *node)
|
||||
{
|
||||
auto node_index = node_index_map_.find(node);
|
||||
if (node_index != node_index_map_.end())
|
||||
return {node_index->second, true};
|
||||
else
|
||||
return {0, false};
|
||||
}
|
||||
|
||||
void
|
||||
PrimaDelayCalc::stampEqns()
|
||||
{
|
||||
|
|
@ -563,40 +590,127 @@ PrimaDelayCalc::stampEqns()
|
|||
C_.setZero();
|
||||
B_.setZero();
|
||||
|
||||
for (size_t node_idx = 0; node_idx < node_count_; node_idx++)
|
||||
stampCapacitance(node_idx, node_capacitances_[node_idx]);
|
||||
NetSet drvr_nets(network_);
|
||||
for (ArcDcalcArg &dcalc_arg : *dcalc_args_) {
|
||||
const Net *net = dcalc_arg.drvrNet(network_);
|
||||
drvr_nets.insert(net);
|
||||
}
|
||||
|
||||
resistance_sum_ = 0.0;
|
||||
for (ParasiticResistor *resistor : parasitics_->resistors(parasitic_network_)) {
|
||||
ParasiticNode *node1 = parasitics_->node1(resistor);
|
||||
ParasiticNode *node2 = parasitics_->node2(resistor);
|
||||
// One commercial extractor creates resistors with identical from/to nodes.
|
||||
if (node1 != node2) {
|
||||
size_t node_idx1 = node_index_map_[node1];
|
||||
size_t node_idx2 = node_index_map_[node2];
|
||||
float resistance = parasitics_->value(resistor);
|
||||
stampConductance(node_idx1, node_idx2, 1.0 / resistance);
|
||||
resistance_sum_ += resistance;
|
||||
ParasiticSet visited_parasitics;
|
||||
for (size_t drvr_idx = 0; drvr_idx < drvr_count_; drvr_idx++) {
|
||||
const ArcDcalcArg &dcalc_arg = (*dcalc_args_)[drvr_idx];
|
||||
stampDriver(dcalc_arg, drvr_idx);
|
||||
const Parasitic *parasitic = dcalc_arg.parasitic();
|
||||
if (!visited_parasitics.contains(parasitic)) {
|
||||
stampResistors(parasitic);
|
||||
stampCapacitors(parasitic, dcalc_arg, drvr_nets);
|
||||
visited_parasitics.insert(parasitic);
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t drvr_idx = 0; drvr_idx < drvr_count_; drvr_idx++) {
|
||||
const ArcDcalcArg &dcalc_arg = (*dcalc_args_)[drvr_idx];
|
||||
if (debug_->check("prima", 3)) {
|
||||
reportMatrix("G", G_);
|
||||
reportMatrix("C", C_);
|
||||
reportMatrix("B", B_);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
PrimaDelayCalc::stampDriver(const ArcDcalcArg &dcalc_arg,
|
||||
size_t drvr_idx)
|
||||
{
|
||||
size_t drvr_node = pin_node_map_[dcalc_arg.drvrPin()];
|
||||
G_.coeffRef(node_count_ + drvr_idx, drvr_node) = 1.0;
|
||||
G_.coeffRef(node_count_ + drvr_idx, node_count_ + drvr_idx) = -1.0;
|
||||
// special sauce
|
||||
G_.coeffRef(drvr_node, drvr_node) += 1e-6;
|
||||
B_.coeffRef(drvr_node, drvr_idx) = 1.0;
|
||||
}
|
||||
|
||||
void
|
||||
PrimaDelayCalc::stampResistors(const Parasitic *parasitic)
|
||||
{
|
||||
for (ParasiticResistor *resistor : parasitics_->resistors(parasitic)) {
|
||||
auto [node_idx1, exsits1] = nodeIndex(parasitics_->node1(resistor));
|
||||
auto [node_idx2, exsits2] = nodeIndex(parasitics_->node2(resistor));
|
||||
// Skip a resistor with a node left out of the network.
|
||||
if (exsits1 && exsits2) {
|
||||
float resistance = parasitics_->value(resistor);
|
||||
// Skip a self loop / merged short (same index) or a non-positive (short)
|
||||
// resistance; stamping 1/resistance would be infinite.
|
||||
if (node_idx1 != node_idx2 && resistance > 0.0) {
|
||||
stampConductance(node_idx1, node_idx2, 1.0 / resistance);
|
||||
resistance_sum_ += resistance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
PrimaDelayCalc::stampCapacitors(const Parasitic *parasitic,
|
||||
const ArcDcalcArg &dcalc_arg,
|
||||
NetSet &drvr_nets)
|
||||
{
|
||||
const RiseFall *drvr_rf = dcalc_arg.drvrEdge();
|
||||
bool includes_pin_caps = parasitics_->includesPinCaps(parasitic);
|
||||
// Grounded capacitors.
|
||||
for (ParasiticNode *node : parasitics_->nodes(parasitic)) {
|
||||
if (!parasitics_->isExternal(node)) {
|
||||
auto [node_idx, exists] = nodeIndex(node);
|
||||
if (exists) {
|
||||
double cap = parasitics_->nodeGndCap(node);
|
||||
const Pin *pin = parasitics_->pin(node);
|
||||
if (pin)
|
||||
cap += pinCapacitance(pin, drvr_rf, includes_pin_caps);
|
||||
stampCapacitance(node_idx, cap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (debug_->check("ccs_dcalc", 3)) {
|
||||
reportMatrix("G", G_);
|
||||
reportMatrix("C", C_);
|
||||
reportMatrix("B", B_);
|
||||
// Coupling capcacitors.
|
||||
const Net *drvr_net = dcalc_arg.drvrNet(network_);
|
||||
for (ParasiticCapacitor *capacitor : parasitics_->capacitors(parasitic)) {
|
||||
ParasiticNode *node1 = parasitics_->node1(capacitor);
|
||||
ParasiticNode *node2 = parasitics_->node2(capacitor);
|
||||
float cap = parasitics_->value(capacitor);
|
||||
const Net *net1 = node1 ? parasitics_->net(node1, network_) : nullptr;
|
||||
const Net *net2 = node2 ? parasitics_->net(node2, network_) : nullptr;
|
||||
if (net2 == drvr_net) {
|
||||
std::swap(net1, net2);
|
||||
std::swap(node1, node2);
|
||||
}
|
||||
auto [node_idx1, exists1] = nodeIndex(node1);
|
||||
if (exists1) {
|
||||
if (net2 && drvr_nets.contains(net2)) {
|
||||
auto [node_idx2, exists2] = nodeIndex(node2);
|
||||
if (exists2)
|
||||
// Stamp half the capacitance because the coupled net will do the same.
|
||||
stampCapacitance(node_idx1, node_idx2, cap * .5);
|
||||
}
|
||||
else
|
||||
stampCapacitance(node_idx1, cap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float
|
||||
PrimaDelayCalc::pinCapacitance(const Pin *pin,
|
||||
const RiseFall *rf,
|
||||
bool includes_pin_caps)
|
||||
{
|
||||
Port *port = network_->port(pin);
|
||||
LibertyPort *lib_port = network_->libertyPort(port);
|
||||
const Sdc *sdc = scene_->sdc();
|
||||
if (lib_port) {
|
||||
if (!includes_pin_caps)
|
||||
return sdc->pinCapacitance(pin, rf, scene_, min_max_);
|
||||
}
|
||||
else if (network_->isTopLevelPort(pin))
|
||||
return sdc->portExtCap(port, rf, min_max_);
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// Grounded resistor.
|
||||
void
|
||||
PrimaDelayCalc::stampConductance(size_t n1,
|
||||
|
|
@ -723,7 +837,8 @@ PrimaDelayCalc::measureThresholds(double time)
|
|||
if ((v_prev < th && th <= v) || (v_prev > th && th >= v)) {
|
||||
double t_cross =
|
||||
time - time_step_ + (th - v_prev) * time_step_ / (v - v_prev);
|
||||
debugPrint(debug_, "ccs_measure", 1, "node {} cross {:.2f} {}", node_idx, th,
|
||||
debugPrint(debug_, "prima_measure", 1, "node {} cross {:.2f} {}",
|
||||
node_idx, th,
|
||||
delayAsString(t_cross, this));
|
||||
threshold_times_[node_idx][m] = t_cross;
|
||||
}
|
||||
|
|
@ -771,7 +886,7 @@ PrimaDelayCalc::dcalcResults()
|
|||
dcalc_result.setGateDelay(gate_delay2);
|
||||
dcalc_result.setDrvrSlew(drvr_slew2);
|
||||
|
||||
debugPrint(debug_, "ccs_dcalc", 2, "{} gate delay {} slew {}",
|
||||
debugPrint(debug_, "prima", 2, "{} gate delay {} slew {}",
|
||||
network_->pathName(drvr_pin), delayAsString(gate_delay, this),
|
||||
delayAsString(drvr_slew, this));
|
||||
|
||||
|
|
@ -784,7 +899,7 @@ PrimaDelayCalc::dcalcResults()
|
|||
ThresholdTimes &drvr_times = threshold_times_[drvr_node];
|
||||
double wire_delay = wire_times[threshold_vth] - drvr_times[threshold_vth];
|
||||
double load_slew = std::abs(wire_times[threshold_vh] - wire_times[threshold_vl]);
|
||||
debugPrint(debug_, "ccs_dcalc", 2, "load {} {} delay {} slew {}",
|
||||
debugPrint(debug_, "prima", 2, "load {} {} delay {} slew {}",
|
||||
network_->pathName(load_pin),
|
||||
drvr_rf_->shortName(),
|
||||
delayAsString(wire_delay, this),
|
||||
|
|
@ -876,7 +991,7 @@ PrimaDelayCalc::primaReduce()
|
|||
// solve x_init = Vq * x~_init for x~_init
|
||||
xq_init_ = Vq_.colPivHouseholderQr().solve(x_init_);
|
||||
|
||||
if (debug_->check("ccs_dcalc", 3)) {
|
||||
if (debug_->check("prima", 3)) {
|
||||
reportMatrix("Vq", Vq_);
|
||||
reportMatrix("G~", Gq_);
|
||||
reportMatrix("C~", Cq_);
|
||||
|
|
@ -939,7 +1054,7 @@ PrimaDelayCalc::primaReduce2()
|
|||
// solve x_init = Vq * x~_init for x~_init
|
||||
xq_init_ = Vq_.colPivHouseholderQr().solve(x_init_);
|
||||
|
||||
if (debug_->check("ccs_dcalc", 3)) {
|
||||
if (debug_->check("prima", 3)) {
|
||||
reportMatrix("Vq", Vq_);
|
||||
reportMatrix("G~", Gq_);
|
||||
reportMatrix("C~", Cq_);
|
||||
|
|
|
|||
|
|
@ -132,10 +132,22 @@ protected:
|
|||
void initSim();
|
||||
void findLoads();
|
||||
void findNodeCount();
|
||||
void placeNode(ParasiticNode *node,
|
||||
size_t index);
|
||||
void setOrder();
|
||||
void initCeffIdrvr();
|
||||
void setXinit();
|
||||
std::pair<size_t, bool> nodeIndex(const ParasiticNode *node);
|
||||
void stampEqns();
|
||||
void stampDriver(const ArcDcalcArg &dcalc_arg,
|
||||
size_t drvr_idx);
|
||||
void stampResistors(const Parasitic *parasitic);
|
||||
void stampCapacitors(const Parasitic *parasitic,
|
||||
const ArcDcalcArg &dcalc_arg,
|
||||
NetSet &drvr_nets);
|
||||
float pinCapacitance(const Pin *pin,
|
||||
const RiseFall *rf,
|
||||
bool includes_pin_caps);
|
||||
void stampConductance(size_t n1,
|
||||
double g);
|
||||
void stampConductance(size_t n1,
|
||||
|
|
@ -146,7 +158,6 @@ protected:
|
|||
void stampCapacitance(size_t n1,
|
||||
size_t n2,
|
||||
double cap);
|
||||
float pinCapacitance(ParasiticNode *node);
|
||||
void setPortCurrents();
|
||||
void measureThresholds(double time);
|
||||
double voltage(const Pin *pin);
|
||||
|
|
@ -194,8 +205,6 @@ protected:
|
|||
std::vector<OutputWaveforms*> output_waveforms_;
|
||||
double resistance_sum_;
|
||||
|
||||
std::vector<double> node_capacitances_;
|
||||
bool includes_pin_caps_;
|
||||
float coupling_cap_multiplier_;
|
||||
|
||||
size_t node_count_; // Parasitic network node count
|
||||
|
|
|
|||
|
|
@ -4,6 +4,15 @@ OpenSTA Timing Analyzer Release Notes
|
|||
This file summarizes user visible changes for each release.
|
||||
See ApiChangeLog.txt for changes to the STA api.
|
||||
|
||||
2026/07/20
|
||||
----------
|
||||
|
||||
The read_vcd command supports -begin_time / -end_time to limit
|
||||
activity annotation to a VCD time window.
|
||||
|
||||
read_vcd [-scope scope] [-mode mode_name]
|
||||
[-begin_time begin_time] [-end_time end_time] filename
|
||||
|
||||
2026/05/01
|
||||
----------
|
||||
|
||||
|
|
|
|||
|
|
@ -76,11 +76,13 @@ public:
|
|||
const Pin *drvrPin() const { return drvr_pin_; }
|
||||
Vertex *drvrVertex(const Graph *graph) const;
|
||||
LibertyCell *drvrCell() const;
|
||||
const LibertyLibrary *drvrLibrary() const;
|
||||
LibertyLibrary *drvrLibrary() const;
|
||||
const RiseFall *drvrEdge() const;
|
||||
const Net *drvrNet(const Network *network) const;
|
||||
Edge *edge() const { return edge_; }
|
||||
const TimingArc *arc() const { return arc_; }
|
||||
void setSceneArc(const Scene *scene,
|
||||
const MinMax *min_max);
|
||||
const Slew &inSlew() const { return in_slew_; }
|
||||
float inSlewFlt() const;
|
||||
void setInSlew(Slew in_slew);
|
||||
|
|
|
|||
|
|
@ -284,6 +284,10 @@ protected:
|
|||
PropertyValue::Type propertyType(std::string_view type);
|
||||
PropertyValue coercePropertyValue(PropertyValue::Type type,
|
||||
std::string_view value);
|
||||
// True if a user-defined property of this name was declared (via
|
||||
// defineProperty) on this object type.
|
||||
bool isUserProperty(std::string_view object_type,
|
||||
std::string_view property);
|
||||
|
||||
PropertyRegistry<const Library*> registry_library_;
|
||||
PropertyRegistry<const LibertyLibrary*> registry_liberty_library_;
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@
|
|||
//
|
||||
// This notice may not be removed or altered from any source distribution.
|
||||
|
||||
%include "stdint.i"
|
||||
|
||||
%{
|
||||
#include "power/Power.hh"
|
||||
|
||||
|
|
@ -29,12 +31,16 @@
|
|||
#include "Sdc.hh"
|
||||
#include "Sta.hh"
|
||||
#include "power/SaifReader.hh"
|
||||
#include "power/VcdParse.hh"
|
||||
#include "power/VcdReader.hh"
|
||||
|
||||
using namespace sta;
|
||||
|
||||
%}
|
||||
|
||||
// Match power/VcdParse.hh vcd_null_time for Tcl defaults.
|
||||
%constant int64_t vcd_null_time = -1;
|
||||
|
||||
%inline %{
|
||||
|
||||
void
|
||||
|
|
@ -201,11 +207,13 @@ clock_min_period(const char *mode_name)
|
|||
void
|
||||
read_vcd_file(const char *filename,
|
||||
const char *scope,
|
||||
const char *mode_name)
|
||||
const char *mode_name,
|
||||
int64_t begin_time,
|
||||
int64_t end_time)
|
||||
{
|
||||
Sta *sta = Sta::sta();
|
||||
sta->ensureLibLinked();
|
||||
readVcdActivities(filename, scope, mode_name, sta);
|
||||
readVcdActivities(filename, scope, mode_name, begin_time, end_time, sta);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
|
@ -228,4 +236,12 @@ report_activity_annotation_cmd(bool report_unannotated,
|
|||
report_annotated);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
clear_power()
|
||||
{
|
||||
Power *power = Sta::sta()->power();
|
||||
power->clear();
|
||||
}
|
||||
|
||||
%} // inline
|
||||
|
|
|
|||
|
|
@ -238,16 +238,18 @@ proc read_power_activities { args } {
|
|||
set scope $keys(-scope)
|
||||
}
|
||||
sta_warn 305 "read_power_activities is deprecated. Use read_vcd."
|
||||
read_vcd_file $filename $scope
|
||||
read_vcd_file $filename $scope [cmd_mode_name] \
|
||||
$sta::vcd_null_time $sta::vcd_null_time
|
||||
}
|
||||
|
||||
################################################################
|
||||
|
||||
define_cmd_args "read_vcd" { [-scope scope] [-mode mode_name] filename }
|
||||
define_cmd_args "read_vcd" \
|
||||
{[-scope scope] [-mode mode_name] [-begin_time begin_time] [-end_time end_time] filename}
|
||||
|
||||
proc read_vcd { args } {
|
||||
parse_key_args "read_vcd" args \
|
||||
keys {-scope -mode_name} flags {}
|
||||
keys {-scope -mode -begin_time -end_time} flags {}
|
||||
|
||||
check_argc_eq1 "read_vcd" $args
|
||||
set filename [file nativename [lindex $args 0]]
|
||||
|
|
@ -259,7 +261,15 @@ proc read_vcd { args } {
|
|||
if { [info exists keys(-mode)] } {
|
||||
set mode_name $keys(-mode)
|
||||
}
|
||||
read_vcd_file $filename $scope $mode_name
|
||||
set begin_time $sta::vcd_null_time
|
||||
if { [info exists keys(-begin_time)] } {
|
||||
set begin_time $keys(-begin_time)
|
||||
}
|
||||
set end_time $sta::vcd_null_time
|
||||
if { [info exists keys(-end_time)] } {
|
||||
set end_time $keys(-end_time)
|
||||
}
|
||||
read_vcd_file $filename $scope $mode_name $begin_time $end_time
|
||||
}
|
||||
|
||||
################################################################
|
||||
|
|
|
|||
|
|
@ -42,8 +42,13 @@ namespace sta {
|
|||
|
||||
void
|
||||
VcdParse::read(const char *filename,
|
||||
VcdReader *reader)
|
||||
VcdReader *reader,
|
||||
VcdTime begin_time,
|
||||
VcdTime end_time)
|
||||
{
|
||||
begin_time_ = begin_time;
|
||||
end_time_ = end_time;
|
||||
|
||||
stream_ = gzopen(filename, "r");
|
||||
if (stream_) {
|
||||
Stats stats(debug_, report_);
|
||||
|
|
@ -51,6 +56,11 @@ VcdParse::read(const char *filename,
|
|||
reader_ = reader;
|
||||
file_line_ = 0;
|
||||
stmt_line_ = 0;
|
||||
|
||||
// If user specified a start time, set it now.
|
||||
if (begin_time != vcd_null_time) {
|
||||
reader_->setTimeMin(begin_time);
|
||||
}
|
||||
std::string token = getToken();
|
||||
while (!token.empty()) {
|
||||
if (token == "$date")
|
||||
|
|
@ -87,7 +97,10 @@ VcdParse::read(const char *filename,
|
|||
report_->fileError(806, filename_, file_line_, "time out of range {}",
|
||||
token.substr(1));
|
||||
}
|
||||
reader_->setTimeMin(time_);
|
||||
// Set time min to start time if it is not set at beginning
|
||||
if (begin_time == vcd_null_time) {
|
||||
reader_->setTimeMin(time_);
|
||||
}
|
||||
prev_time_ = time_;
|
||||
}
|
||||
else if (token[0] == '$')
|
||||
|
|
@ -238,7 +251,13 @@ VcdParse::parseVarValues()
|
|||
}
|
||||
token = getToken();
|
||||
}
|
||||
reader_->setTimeMax(time_);
|
||||
|
||||
// Set time_max to end_time if specified, otherwise use actual parsed time
|
||||
if (end_time_ != vcd_null_time) {
|
||||
reader_->setTimeMax(end_time_);
|
||||
} else {
|
||||
reader_->setTimeMax(time_);
|
||||
}
|
||||
}
|
||||
|
||||
std::string
|
||||
|
|
|
|||
|
|
@ -36,6 +36,9 @@ namespace sta {
|
|||
using VcdTime = int64_t;
|
||||
using VcdScope = std::vector<std::string>;
|
||||
|
||||
// Sentinel for an unset begin/end time window bound.
|
||||
constexpr VcdTime vcd_null_time = -1;
|
||||
|
||||
enum class VcdVarType {
|
||||
wire,
|
||||
reg,
|
||||
|
|
@ -64,7 +67,9 @@ public:
|
|||
VcdParse(Report *report,
|
||||
Debug *debug);
|
||||
void read(const char *filename,
|
||||
VcdReader *reader);
|
||||
VcdReader *reader,
|
||||
VcdTime begin_time,
|
||||
VcdTime end_time);
|
||||
|
||||
private:
|
||||
void parseTimescale();
|
||||
|
|
@ -87,6 +92,11 @@ private:
|
|||
|
||||
VcdTime time_ = 0;
|
||||
VcdTime prev_time_ = 0;
|
||||
|
||||
// Arguments to VcdParse
|
||||
VcdTime begin_time_ = vcd_null_time;
|
||||
VcdTime end_time_ = vcd_null_time;
|
||||
|
||||
VcdScope scope_;
|
||||
|
||||
Report *report_;
|
||||
|
|
|
|||
|
|
@ -52,48 +52,89 @@ public:
|
|||
VcdTime highTime(VcdTime time_max) const;
|
||||
void incrCounts(VcdTime time,
|
||||
char value);
|
||||
void incrCounts(VcdTime time,
|
||||
int64_t value);
|
||||
void addPin(const Pin *pin);
|
||||
const PinSeq &pins() const { return pins_; }
|
||||
|
||||
static void setFilter(VcdTime begin,
|
||||
VcdTime end);
|
||||
|
||||
private:
|
||||
VcdTime clippedIntervalStart() const;
|
||||
PinSeq pins_;
|
||||
VcdTime prev_time_ = -1;
|
||||
VcdTime prev_time_ = vcd_null_time;
|
||||
char prev_value_ = '\0';
|
||||
VcdTime high_time_ = 0;
|
||||
double transition_count_ = 0;
|
||||
|
||||
static VcdTime begin_time_;
|
||||
static VcdTime end_time_;
|
||||
};
|
||||
|
||||
// Define static members
|
||||
VcdTime VcdCount::begin_time_ = vcd_null_time;
|
||||
VcdTime VcdCount::end_time_ = vcd_null_time;
|
||||
|
||||
void
|
||||
VcdCount::addPin(const Pin *pin)
|
||||
{
|
||||
pins_.push_back(pin);
|
||||
}
|
||||
|
||||
VcdTime
|
||||
VcdCount::clippedIntervalStart() const
|
||||
{
|
||||
// Clip prev_time_ to begin_time if signal went high before the window.
|
||||
return (begin_time_ != vcd_null_time && prev_time_ < begin_time_)
|
||||
? begin_time_ : prev_time_;
|
||||
}
|
||||
|
||||
void
|
||||
VcdCount::setFilter(VcdTime begin,
|
||||
VcdTime end)
|
||||
{
|
||||
begin_time_ = begin;
|
||||
end_time_ = end;
|
||||
}
|
||||
|
||||
void
|
||||
VcdCount::incrCounts(VcdTime time,
|
||||
char value)
|
||||
{
|
||||
// Initial value does not coontribute to transitions or high time.
|
||||
if (prev_time_ != -1) {
|
||||
if (prev_value_ == '1')
|
||||
high_time_ += time - prev_time_;
|
||||
// Determine if this time point is within the filter window
|
||||
bool in_window = (begin_time_ == vcd_null_time || time >= begin_time_)
|
||||
&& (end_time_ == vcd_null_time || time <= end_time_);
|
||||
|
||||
// Initial value does not contribute to transitions or high time.
|
||||
if (prev_time_ != vcd_null_time && in_window) {
|
||||
if (prev_value_ == '1') {
|
||||
VcdTime interval_start = clippedIntervalStart();
|
||||
if (time > interval_start)
|
||||
high_time_ += time - interval_start;
|
||||
}
|
||||
if (value != prev_value_)
|
||||
transition_count_ +=
|
||||
(value == 'X' || value == 'Z' || prev_value_ == 'X' || prev_value_ == 'Z')
|
||||
? .5
|
||||
: 1.0;
|
||||
}
|
||||
prev_time_ = time;
|
||||
prev_value_ = value;
|
||||
// Update state for transitions before or within the window.
|
||||
// This prevents values after window boundaries corrupting high time.
|
||||
if (end_time_ == vcd_null_time || time <= end_time_) {
|
||||
prev_time_ = time;
|
||||
prev_value_ = value;
|
||||
}
|
||||
}
|
||||
|
||||
VcdTime
|
||||
VcdCount::highTime(VcdTime time_max) const
|
||||
{
|
||||
if (prev_value_ == '1')
|
||||
return high_time_ + time_max - prev_time_;
|
||||
if (prev_value_ == '1') {
|
||||
VcdTime interval_start = clippedIntervalStart();
|
||||
if (time_max > interval_start)
|
||||
return high_time_ + time_max - interval_start;
|
||||
else
|
||||
return high_time_;
|
||||
}
|
||||
else
|
||||
return high_time_;
|
||||
}
|
||||
|
|
@ -180,12 +221,14 @@ VcdCountReader::setTimeUnit(std::string_view ,
|
|||
void
|
||||
VcdCountReader::setTimeMin(VcdTime time)
|
||||
{
|
||||
debugPrint(debug_, "read_vcd", 1, "setTimeMin called with time {}", time);
|
||||
time_min_ = time;
|
||||
}
|
||||
|
||||
void
|
||||
VcdCountReader::setTimeMax(VcdTime time)
|
||||
{
|
||||
debugPrint(debug_, "read_vcd", 1, "setTimeMax called with time {}", time);
|
||||
time_max_ = time;
|
||||
}
|
||||
|
||||
|
|
@ -335,6 +378,8 @@ class ReadVcdActivities : public StaState
|
|||
public:
|
||||
ReadVcdActivities(std::string_view filename,
|
||||
std::string_view scope,
|
||||
VcdTime begin_time,
|
||||
VcdTime end_time,
|
||||
const Sdc *sdc,
|
||||
Sta *sta);
|
||||
void readActivities();
|
||||
|
|
@ -345,6 +390,8 @@ private:
|
|||
double transition_count);
|
||||
|
||||
const std::string filename_;
|
||||
VcdTime begin_time_;
|
||||
VcdTime end_time_;
|
||||
|
||||
std::set<const Pin *> annotated_pins_;
|
||||
VcdCountReader vcd_reader_;
|
||||
|
|
@ -359,20 +406,26 @@ void
|
|||
readVcdActivities(std::string_view filename,
|
||||
std::string_view scope,
|
||||
std::string_view mode_name,
|
||||
VcdTime begin_time,
|
||||
VcdTime end_time,
|
||||
Sta *sta)
|
||||
{
|
||||
const Mode *mode = sta->findMode(mode_name);
|
||||
const Sdc *sdc = mode->sdc();
|
||||
ReadVcdActivities reader(filename, scope, sdc, sta);
|
||||
ReadVcdActivities reader(filename, scope, begin_time, end_time, sdc, sta);
|
||||
reader.readActivities();
|
||||
}
|
||||
|
||||
ReadVcdActivities::ReadVcdActivities(std::string_view filename,
|
||||
std::string_view scope,
|
||||
VcdTime begin_time,
|
||||
VcdTime end_time,
|
||||
const Sdc *sdc,
|
||||
Sta *sta) :
|
||||
StaState(sta),
|
||||
filename_(filename),
|
||||
begin_time_(begin_time),
|
||||
end_time_(end_time),
|
||||
vcd_reader_(scope,
|
||||
sdc_network_,
|
||||
report_,
|
||||
|
|
@ -391,7 +444,9 @@ ReadVcdActivities::readActivities()
|
|||
if (clks.empty())
|
||||
report_->error(820, "No clocks have been defined.");
|
||||
|
||||
vcd_parse_.read(filename_.c_str(), &vcd_reader_);
|
||||
// Set the time window filter once globally
|
||||
VcdCount::setFilter(begin_time_, end_time_);
|
||||
vcd_parse_.read(filename_.c_str(), &vcd_reader_, begin_time_, end_time_);
|
||||
|
||||
if (vcd_reader_.timeMax() > 0)
|
||||
setActivities();
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@
|
|||
|
||||
#include <string>
|
||||
|
||||
#include "VcdParse.hh"
|
||||
|
||||
namespace sta {
|
||||
|
||||
class Sta;
|
||||
|
|
@ -34,6 +36,8 @@ void
|
|||
readVcdActivities(std::string_view filename,
|
||||
std::string_view scope,
|
||||
std::string_view mode_name,
|
||||
VcdTime begin_time,
|
||||
VcdTime end_time,
|
||||
Sta *sta);
|
||||
|
||||
} // namespace sta
|
||||
|
|
|
|||
|
|
@ -643,6 +643,8 @@ Properties::getProperty(const Library *lib,
|
|||
"library", sta_);
|
||||
if (value.type() != PropertyValue::Type::none)
|
||||
return value;
|
||||
else if (isUserProperty("library", property))
|
||||
return value;
|
||||
else
|
||||
throw PropertyUnknown("library", property);
|
||||
}
|
||||
|
|
@ -665,6 +667,8 @@ Properties::getProperty(const LibertyLibrary *lib,
|
|||
sta_);
|
||||
if (value.type() != PropertyValue::Type::none)
|
||||
return value;
|
||||
else if (isUserProperty("liberty_library", property))
|
||||
return value;
|
||||
else
|
||||
throw PropertyUnknown("liberty library", property);
|
||||
}
|
||||
|
|
@ -696,6 +700,8 @@ Properties::getProperty(const Cell *cell,
|
|||
"cell", sta_);
|
||||
if (value.type() != PropertyValue::Type::none)
|
||||
return value;
|
||||
else if (isUserProperty("cell", property))
|
||||
return value;
|
||||
else
|
||||
throw PropertyUnknown("cell", property);
|
||||
}
|
||||
|
|
@ -737,6 +743,8 @@ Properties::getProperty(const LibertyCell *cell,
|
|||
"liberty_cell", sta_);
|
||||
if (value.type() != PropertyValue::Type::none)
|
||||
return value;
|
||||
else if (isUserProperty("liberty_cell", property))
|
||||
return value;
|
||||
else
|
||||
throw PropertyUnknown("liberty cell", property);
|
||||
}
|
||||
|
|
@ -797,6 +805,8 @@ Properties::getProperty(const Port *port,
|
|||
"port", sta_);
|
||||
if (value.type() != PropertyValue::Type::none)
|
||||
return value;
|
||||
else if (isUserProperty("port", property))
|
||||
return value;
|
||||
else
|
||||
throw PropertyUnknown("port", property);
|
||||
}
|
||||
|
|
@ -898,6 +908,8 @@ Properties::getProperty(const LibertyPort *port,
|
|||
"liberty_port", sta_);
|
||||
if (value.type() != PropertyValue::Type::none)
|
||||
return value;
|
||||
else if (isUserProperty("liberty_port", property))
|
||||
return value;
|
||||
else
|
||||
throw PropertyUnknown("liberty port", property);
|
||||
}
|
||||
|
|
@ -938,6 +950,8 @@ Properties::getProperty(const Instance *inst,
|
|||
"instance", sta_);
|
||||
if (value.type() != PropertyValue::Type::none)
|
||||
return value;
|
||||
else if (isUserProperty("instance", property))
|
||||
return value;
|
||||
else
|
||||
throw PropertyUnknown("instance", property);
|
||||
}
|
||||
|
|
@ -1025,6 +1039,8 @@ Properties::getProperty(const Pin *pin,
|
|||
PropertyValue value = registry_pin_.getProperty(pin, property, "pin", sta_);
|
||||
if (value.type() != PropertyValue::Type::none)
|
||||
return value;
|
||||
else if (isUserProperty("pin", property))
|
||||
return value;
|
||||
else
|
||||
throw PropertyUnknown("pin", property);
|
||||
}
|
||||
|
|
@ -1086,6 +1102,8 @@ Properties::getProperty(const Net *net,
|
|||
PropertyValue value = registry_net_.getProperty(net, property, "net", sta_);
|
||||
if (value.type() != PropertyValue::Type::none)
|
||||
return value;
|
||||
else if (isUserProperty("net", property))
|
||||
return value;
|
||||
else
|
||||
throw PropertyUnknown("net", property);
|
||||
}
|
||||
|
|
@ -1191,6 +1209,8 @@ Properties::getProperty(const Clock *clk,
|
|||
"clock", sta_);
|
||||
if (value.type() != PropertyValue::Type::none)
|
||||
return value;
|
||||
else if (isUserProperty("clock", property))
|
||||
return value;
|
||||
else
|
||||
throw PropertyUnknown("clock", property);
|
||||
}
|
||||
|
|
@ -1420,6 +1440,13 @@ Properties::coercePropertyValue(PropertyValue::Type type,
|
|||
}
|
||||
}
|
||||
|
||||
bool
|
||||
Properties::isUserProperty(std::string_view object_type,
|
||||
std::string_view property)
|
||||
{
|
||||
return prop_types_.contains({std::string(object_type), std::string(property)});
|
||||
}
|
||||
|
||||
PropertyKey::PropertyKey(const void *object,
|
||||
std::string_view property) :
|
||||
object_(object),
|
||||
|
|
@ -1462,6 +1489,36 @@ template void Properties::defineProperty<Scene>(std::string_view,
|
|||
template void Properties::defineProperty<Mode>(std::string_view,
|
||||
std::string_view,
|
||||
std::string_view);
|
||||
template void Properties::defineProperty<Library>(std::string_view,
|
||||
std::string_view,
|
||||
std::string_view);
|
||||
template void Properties::defineProperty<LibertyLibrary>(std::string_view,
|
||||
std::string_view,
|
||||
std::string_view);
|
||||
template void Properties::defineProperty<Cell>(std::string_view,
|
||||
std::string_view,
|
||||
std::string_view);
|
||||
template void Properties::defineProperty<LibertyCell>(std::string_view,
|
||||
std::string_view,
|
||||
std::string_view);
|
||||
template void Properties::defineProperty<Port>(std::string_view,
|
||||
std::string_view,
|
||||
std::string_view);
|
||||
template void Properties::defineProperty<LibertyPort>(std::string_view,
|
||||
std::string_view,
|
||||
std::string_view);
|
||||
template void Properties::defineProperty<Instance>(std::string_view,
|
||||
std::string_view,
|
||||
std::string_view);
|
||||
template void Properties::defineProperty<Pin>(std::string_view,
|
||||
std::string_view,
|
||||
std::string_view);
|
||||
template void Properties::defineProperty<Net>(std::string_view,
|
||||
std::string_view,
|
||||
std::string_view);
|
||||
template void Properties::defineProperty<Clock>(std::string_view,
|
||||
std::string_view,
|
||||
std::string_view);
|
||||
|
||||
void
|
||||
Properties::setProperty(const void *object,
|
||||
|
|
|
|||
|
|
@ -149,6 +149,26 @@ define_property_cmd(const char *object_type,
|
|||
properties.defineProperty<Scene>(object_type, property, type);
|
||||
else if (object_type_view == "mode")
|
||||
properties.defineProperty<Mode>(object_type, property, type);
|
||||
else if (object_type_view == "library")
|
||||
properties.defineProperty<Library>(object_type, property, type);
|
||||
else if (object_type_view == "liberty_library")
|
||||
properties.defineProperty<LibertyLibrary>(object_type, property, type);
|
||||
else if (object_type_view == "cell")
|
||||
properties.defineProperty<Cell>(object_type, property, type);
|
||||
else if (object_type_view == "liberty_cell")
|
||||
properties.defineProperty<LibertyCell>(object_type, property, type);
|
||||
else if (object_type_view == "port")
|
||||
properties.defineProperty<Port>(object_type, property, type);
|
||||
else if (object_type_view == "liberty_port")
|
||||
properties.defineProperty<LibertyPort>(object_type, property, type);
|
||||
else if (object_type_view == "instance")
|
||||
properties.defineProperty<Instance>(object_type, property, type);
|
||||
else if (object_type_view == "pin")
|
||||
properties.defineProperty<Pin>(object_type, property, type);
|
||||
else if (object_type_view == "net")
|
||||
properties.defineProperty<Net>(object_type, property, type);
|
||||
else if (object_type_view == "clock")
|
||||
properties.defineProperty<Clock>(object_type, property, type);
|
||||
else
|
||||
Sta::sta()->report()->error(2209, "define_property -object_type {} not supported.",
|
||||
object_type);
|
||||
|
|
@ -166,6 +186,26 @@ set_property_cmd(void *object,
|
|||
properties.setProperty(object, "scene", property, value);
|
||||
else if (object_type_view == "Mode")
|
||||
properties.setProperty(object, "mode", property, value);
|
||||
else if (object_type_view == "Library")
|
||||
properties.setProperty(object, "library", property, value);
|
||||
else if (object_type_view == "LibertyLibrary")
|
||||
properties.setProperty(object, "liberty_library", property, value);
|
||||
else if (object_type_view == "Cell")
|
||||
properties.setProperty(object, "cell", property, value);
|
||||
else if (object_type_view == "LibertyCell")
|
||||
properties.setProperty(object, "liberty_cell", property, value);
|
||||
else if (object_type_view == "Port")
|
||||
properties.setProperty(object, "port", property, value);
|
||||
else if (object_type_view == "LibertyPort")
|
||||
properties.setProperty(object, "liberty_port", property, value);
|
||||
else if (object_type_view == "Instance")
|
||||
properties.setProperty(object, "instance", property, value);
|
||||
else if (object_type_view == "Pin")
|
||||
properties.setProperty(object, "pin", property, value);
|
||||
else if (object_type_view == "Net")
|
||||
properties.setProperty(object, "net", property, value);
|
||||
else if (object_type_view == "Clock")
|
||||
properties.setProperty(object, "clock", property, value);
|
||||
else
|
||||
Sta::sta()->report()->error(2214, "set_property unsupported object type {}.",
|
||||
object_type);
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ public:
|
|||
void writeSpice();
|
||||
|
||||
private:
|
||||
void initPowerGnd();
|
||||
void writeHeader();
|
||||
void writePrintStmt();
|
||||
void writeStageInstances();
|
||||
|
|
@ -150,6 +151,7 @@ private:
|
|||
using WriteSpice::writeMeasureDelayStmt;
|
||||
using WriteSpice::writeMeasureSlewStmt;
|
||||
using WriteSpice::findSlew;
|
||||
using WriteSpice::initPowerGnd;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
|
@ -187,7 +189,6 @@ WritePathSpice::WritePathSpice(const Path *path,
|
|||
path_expanded_(sta),
|
||||
written_insts_(network_)
|
||||
{
|
||||
initPowerGnd();
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -196,6 +197,8 @@ WritePathSpice::writeSpice()
|
|||
spice_stream_.open(spice_filename_);
|
||||
if (spice_stream_.is_open()) {
|
||||
path_expanded_.expand(path_, true);
|
||||
|
||||
initPowerGnd();
|
||||
// Find subckt port names as a side-effect of writeSubckts.
|
||||
writeSubckts();
|
||||
writeHeader();
|
||||
|
|
@ -212,6 +215,27 @@ WritePathSpice::writeSpice()
|
|||
throw FileNotWritable(spice_filename_);
|
||||
}
|
||||
|
||||
void
|
||||
WritePathSpice::initPowerGnd()
|
||||
{
|
||||
Scene *scene = path_->scene(this);
|
||||
const MinMax *min_max = path_->minMax(this);
|
||||
LibertyLibrary *threshold_lib = nullptr;
|
||||
for (size_t i = 0; i < path_expanded_.size(); i++) {
|
||||
const Path *path = path_expanded_.path(i);
|
||||
const Pin *pin = path->pin(this);
|
||||
const LibertyPort *port = network_->libertyPort(pin);
|
||||
if (port) {
|
||||
threshold_lib = port->scenePort(scene, min_max)->libertyLibrary();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (threshold_lib)
|
||||
initPowerGnd(threshold_lib);
|
||||
else
|
||||
report_->error(1606, "No instance with Liberty cell found in path.");
|
||||
}
|
||||
|
||||
void
|
||||
WritePathSpice::writeHeader()
|
||||
{
|
||||
|
|
@ -330,7 +354,7 @@ WritePathSpice::writeInputWaveform()
|
|||
const TimingArc *next_arc = stageGateArc(input_stage + 1);
|
||||
float slew0 = findSlew(input_path, rf, next_arc);
|
||||
|
||||
float threshold = default_library_->inputThreshold(rf);
|
||||
float threshold = threshold_library_->inputThreshold(rf);
|
||||
float dt = railToRailSlew(slew0, rf);
|
||||
float time0 = dt * threshold;
|
||||
|
||||
|
|
@ -514,11 +538,13 @@ WritePathSpice::writeGateStage(Stage stage)
|
|||
|
||||
const Path *drvr_path = stageDrvrPath(stage);
|
||||
const RiseFall *drvr_rf = drvr_path->transition(this);
|
||||
const Path *gate_input_path = stageGateInputPath(stage);
|
||||
const RiseFall *input_rf = gate_input_path->transition(this);
|
||||
const Edge *gate_edge = stageGateEdge(stage);
|
||||
|
||||
LibertyPortLogicValues port_values;
|
||||
PortLogicValues port_values;
|
||||
bool is_clked;
|
||||
gatePortValues(input_pin, drvr_pin, drvr_rf, gate_edge,
|
||||
gatePortValues(input_pin, drvr_pin, input_rf, drvr_rf, gate_edge,
|
||||
port_values, is_clked);
|
||||
|
||||
PinSet inputs(network_);
|
||||
|
|
|
|||
|
|
@ -76,17 +76,18 @@ WriteSpice::WriteSpice(std::string_view spice_filename,
|
|||
ckt_sim_(ckt_sim),
|
||||
scene_(scene),
|
||||
min_max_(min_max),
|
||||
default_library_(network_->defaultLibertyLibrary()),
|
||||
threshold_library_(nullptr),
|
||||
bdd_(sta),
|
||||
parasitics_(scene->parasitics(min_max))
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
WriteSpice::initPowerGnd()
|
||||
WriteSpice::initPowerGnd(LibertyLibrary *threshold_library)
|
||||
{
|
||||
threshold_library_ = threshold_library;
|
||||
bool exists = false;
|
||||
default_library_->supplyVoltage(power_name_, power_voltage_, exists);
|
||||
threshold_library_->supplyVoltage(power_name_, power_voltage_, exists);
|
||||
if (!exists) {
|
||||
const OperatingConditions *op_cond =
|
||||
scene_->sdc()->operatingConditions(min_max_);
|
||||
|
|
@ -94,7 +95,7 @@ WriteSpice::initPowerGnd()
|
|||
op_cond = network_->defaultLibertyLibrary()->defaultOperatingConditions();
|
||||
power_voltage_ = op_cond->voltage();
|
||||
}
|
||||
default_library_->supplyVoltage(gnd_name_, gnd_voltage_, exists);
|
||||
threshold_library_->supplyVoltage(gnd_name_, gnd_voltage_, exists);
|
||||
if (!exists)
|
||||
gnd_voltage_ = 0.0;
|
||||
}
|
||||
|
|
@ -309,10 +310,11 @@ WriteSpice::writeSubcktInst(const Instance *inst)
|
|||
// Power/ground and input voltage sources.
|
||||
void
|
||||
WriteSpice::writeSubcktInstVoltSrcs(const Instance *inst,
|
||||
LibertyPortLogicValues &port_values,
|
||||
PortLogicValues &port_values,
|
||||
const PinSet &excluded_input_pins)
|
||||
{
|
||||
LibertyCell *cell = network_->libertyCell(inst);
|
||||
LibertyCell *link_cell = network_->libertyCell(inst);
|
||||
LibertyCell *cell = link_cell->sceneCell(scene_, min_max_);
|
||||
const std::string &cell_name = cell->name();
|
||||
StringSeq &spice_port_names = cell_spice_port_names_[cell_name];
|
||||
std::string inst_name = network_->pathName(inst);
|
||||
|
|
@ -339,7 +341,7 @@ WriteSpice::writeSubcktInstVoltSrcs(const Instance *inst,
|
|||
if (port_value == LogicValue::unknown) {
|
||||
bool has_value;
|
||||
LogicValue value;
|
||||
findKeyValue(port_values, port, value, has_value);
|
||||
findKeyValue(port_values, port->name(), value, has_value);
|
||||
if (has_value)
|
||||
port_value = value;
|
||||
}
|
||||
|
|
@ -732,7 +734,7 @@ WriteSpice::writeWaveformEdge(const RiseFall *rf,
|
|||
volt0 = power_voltage_;
|
||||
volt1 = gnd_voltage_;
|
||||
}
|
||||
float threshold = default_library_->inputThreshold(rf);
|
||||
float threshold = threshold_library_->inputThreshold(rf);
|
||||
float dt = railToRailSlew(slew, rf);
|
||||
float time0 = time - dt * threshold;
|
||||
float time1 = time0 + dt;
|
||||
|
|
@ -745,8 +747,8 @@ float
|
|||
WriteSpice::railToRailSlew(float slew,
|
||||
const RiseFall *rf)
|
||||
{
|
||||
float lower = default_library_->slewLowerThreshold(rf);
|
||||
float upper = default_library_->slewUpperThreshold(rf);
|
||||
float lower = threshold_library_->slewLowerThreshold(rf);
|
||||
float upper = threshold_library_->slewUpperThreshold(rf);
|
||||
return slew / (upper - lower);
|
||||
}
|
||||
|
||||
|
|
@ -756,14 +758,14 @@ WriteSpice::railToRailSlew(float slew,
|
|||
void
|
||||
WriteSpice::gatePortValues(const Pin *input_pin,
|
||||
const Pin *drvr_pin,
|
||||
const RiseFall *input_rf,
|
||||
const RiseFall *drvr_rf,
|
||||
const Edge *gate_edge,
|
||||
// Return values.
|
||||
LibertyPortLogicValues &port_values,
|
||||
PortLogicValues &port_values,
|
||||
bool &is_clked)
|
||||
{
|
||||
is_clked = false;
|
||||
const Instance *inst = network_->instance(input_pin);
|
||||
const LibertyPort *input_port = network_->libertyPort(input_pin);
|
||||
const LibertyPort *drvr_port = network_->libertyPort(drvr_pin);
|
||||
const FuncExpr *drvr_func = drvr_port->function();
|
||||
|
|
@ -771,49 +773,68 @@ WriteSpice::gatePortValues(const Pin *input_pin,
|
|||
if (gate_edge && gate_edge->role()->genericRole() == TimingRole::regClkToQ())
|
||||
regPortValues(input_pin, drvr_rf, drvr_port, drvr_func, port_values, is_clked);
|
||||
else
|
||||
gatePortValues(inst, drvr_func, input_port, port_values);
|
||||
gatePortValues(drvr_func, input_port, input_rf, drvr_rf, port_values);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
WriteSpice::gatePortValues(const Instance *,
|
||||
const FuncExpr *expr,
|
||||
WriteSpice::gatePortValues(const FuncExpr *expr,
|
||||
const LibertyPort *input_port,
|
||||
const RiseFall *input_rf,
|
||||
const RiseFall *drvr_rf,
|
||||
// Return values.
|
||||
LibertyPortLogicValues &port_values)
|
||||
PortLogicValues &port_values)
|
||||
{
|
||||
DdManager *cudd_mgr = bdd_.cuddMgr();
|
||||
DdNode *bdd = bdd_.funcBdd(expr);
|
||||
DdNode *input_node = bdd_.findNode(input_port);
|
||||
unsigned input_node_index = Cudd_NodeReadIndex(input_node);
|
||||
DdManager *cudd_mgr = bdd_.cuddMgr();
|
||||
DdNode *diff = Cudd_bddBooleanDiff(cudd_mgr, bdd, input_node_index);
|
||||
// Cofactors of the driver function wrt the switching (path) input.
|
||||
DdNode *f1 = Cudd_Cofactor(cudd_mgr, bdd, input_node);
|
||||
Cudd_Ref(f1);
|
||||
DdNode *f0 = Cudd_Cofactor(cudd_mgr, bdd, Cudd_Not(input_node));
|
||||
Cudd_Ref(f0);
|
||||
// The side inputs must sensitize the path with the polarity of this
|
||||
// arc, not just any sensitization: for non-unate gates (xor/xnor, mux
|
||||
// select arcs) the side values decide whether the gate inverts, so a
|
||||
// cube of the plain Boolean difference (f1 XOR f0) can put the gate on
|
||||
// the arc opposite to the one the path used.
|
||||
// input and driver edges agree (non-inverting): f1 & ~f0
|
||||
// input and driver edges differ (inverting): f0 & ~f1
|
||||
DdNode *care = (input_rf == drvr_rf)
|
||||
? Cudd_bddAnd(cudd_mgr, f1, Cudd_Not(f0))
|
||||
: Cudd_bddAnd(cudd_mgr, f0, Cudd_Not(f1));
|
||||
Cudd_Ref(care);
|
||||
|
||||
int *cube;
|
||||
CUDD_VALUE_TYPE value;
|
||||
DdGen *cube_gen = Cudd_FirstCube(cudd_mgr, diff, &cube, &value);
|
||||
|
||||
LibertyPortSet ports = expr->ports();
|
||||
for (const LibertyPort *port : ports) {
|
||||
if (port != input_port) {
|
||||
DdNode *port_node = bdd_.findNode(port);
|
||||
int var_index = Cudd_NodeReadIndex(port_node);
|
||||
LogicValue value;
|
||||
switch (cube[var_index]) {
|
||||
case 0:
|
||||
value = LogicValue::zero;
|
||||
break;
|
||||
case 1:
|
||||
value = LogicValue::one;
|
||||
break;
|
||||
case 2:
|
||||
default:
|
||||
value = LogicValue::unknown;
|
||||
break;
|
||||
DdGen *cube_gen = Cudd_FirstCube(cudd_mgr, care, &cube, &value);
|
||||
if (!Cudd_IsGenEmpty(cube_gen)) {
|
||||
LibertyPortSet ports = expr->ports();
|
||||
for (const LibertyPort *port : ports) {
|
||||
if (port != input_port) {
|
||||
DdNode *port_node = bdd_.findNode(port);
|
||||
int var_index = Cudd_NodeReadIndex(port_node);
|
||||
LogicValue port_value;
|
||||
switch (cube[var_index]) {
|
||||
case 0:
|
||||
port_value = LogicValue::zero;
|
||||
break;
|
||||
case 1:
|
||||
port_value = LogicValue::one;
|
||||
break;
|
||||
case 2:
|
||||
default:
|
||||
port_value = LogicValue::unknown;
|
||||
break;
|
||||
}
|
||||
port_values[port->name()] = port_value;
|
||||
}
|
||||
port_values[port] = value;
|
||||
}
|
||||
}
|
||||
Cudd_GenFree(cube_gen);
|
||||
Cudd_Ref(diff);
|
||||
Cudd_RecursiveDeref(cudd_mgr, care);
|
||||
Cudd_RecursiveDeref(cudd_mgr, f0);
|
||||
Cudd_RecursiveDeref(cudd_mgr, f1);
|
||||
bdd_.clearVarMap();
|
||||
}
|
||||
|
||||
|
|
@ -823,7 +844,7 @@ WriteSpice::regPortValues(const Pin *input_pin,
|
|||
const LibertyPort *drvr_port,
|
||||
const FuncExpr *drvr_func,
|
||||
// Return values.
|
||||
LibertyPortLogicValues &port_values,
|
||||
PortLogicValues &port_values,
|
||||
bool &is_clked)
|
||||
{
|
||||
is_clked = false;
|
||||
|
|
@ -849,7 +870,7 @@ void
|
|||
WriteSpice::seqPortValues(Sequential *seq,
|
||||
const RiseFall *rf,
|
||||
// Return values.
|
||||
LibertyPortLogicValues &port_values)
|
||||
PortLogicValues &port_values)
|
||||
{
|
||||
FuncExpr *data = seq->data();
|
||||
// SHOULD choose values for all ports of data to make output rise/fall
|
||||
|
|
@ -857,18 +878,19 @@ WriteSpice::seqPortValues(Sequential *seq,
|
|||
LibertyPort *port = onePort(data);
|
||||
if (port) {
|
||||
TimingSense sense = data->portTimingSense(port);
|
||||
const std::string &port_name = port->name();
|
||||
switch (sense) {
|
||||
case TimingSense::positive_unate:
|
||||
if (rf == RiseFall::rise())
|
||||
port_values[port] = LogicValue::one;
|
||||
port_values[port_name] = LogicValue::one;
|
||||
else
|
||||
port_values[port] = LogicValue::zero;
|
||||
port_values[port_name] = LogicValue::zero;
|
||||
break;
|
||||
case TimingSense::negative_unate:
|
||||
if (rf == RiseFall::rise())
|
||||
port_values[port] = LogicValue::zero;
|
||||
port_values[port_name] = LogicValue::zero;
|
||||
else
|
||||
port_values[port] = LogicValue::one;
|
||||
port_values[port_name] = LogicValue::one;
|
||||
break;
|
||||
case TimingSense::non_unate:
|
||||
case TimingSense::none:
|
||||
|
|
@ -933,7 +955,7 @@ WriteSpice::writeSubcktInstLoads(const Pin *drvr_pin,
|
|||
sta::print(spice_stream_, "* Load pins\n");
|
||||
PinSeq drvr_loads = drvrLoads(drvr_pin);
|
||||
// Do not sensitize side load gates.
|
||||
LibertyPortLogicValues port_values;
|
||||
PortLogicValues port_values;
|
||||
for (const Pin *load_pin : drvr_loads) {
|
||||
const Instance *load_inst = network_->instance(load_pin);
|
||||
if (load_pin != path_load && network_->direction(load_pin)->isAnyInput()
|
||||
|
|
@ -957,9 +979,9 @@ WriteSpice::writeMeasureDelayStmt(const Pin *from_pin,
|
|||
std::string_view prefix)
|
||||
{
|
||||
std::string from_pin_name = network_->pathName(from_pin);
|
||||
float from_threshold = power_voltage_ * default_library_->inputThreshold(from_rf);
|
||||
float from_threshold = power_voltage_ * threshold_library_->inputThreshold(from_rf);
|
||||
std::string to_pin_name = network_->pathName(to_pin);
|
||||
float to_threshold = power_voltage_ * default_library_->inputThreshold(to_rf);
|
||||
float to_threshold = power_voltage_ * threshold_library_->inputThreshold(to_rf);
|
||||
sta::print(spice_stream_, ".measure tran {}_{}_delay_{}\n", prefix,
|
||||
from_pin_name, to_pin_name);
|
||||
sta::print(spice_stream_, "+trig v({}) val={:.3f} {}=last\n", from_pin_name,
|
||||
|
|
@ -975,8 +997,8 @@ WriteSpice::writeMeasureSlewStmt(const Pin *pin,
|
|||
{
|
||||
std::string pin_name = network_->pathName(pin);
|
||||
std::string_view spice_rf = spiceTrans(rf);
|
||||
float lower = power_voltage_ * default_library_->slewLowerThreshold(rf);
|
||||
float upper = power_voltage_ * default_library_->slewUpperThreshold(rf);
|
||||
float lower = power_voltage_ * threshold_library_->slewLowerThreshold(rf);
|
||||
float upper = power_voltage_ * threshold_library_->slewUpperThreshold(rf);
|
||||
float threshold1, threshold2;
|
||||
if (rf == RiseFall::rise()) {
|
||||
threshold1 = lower;
|
||||
|
|
|
|||
|
|
@ -43,7 +43,8 @@ namespace sta {
|
|||
|
||||
using ParasiticNodeMap = std::map<const ParasiticNode*, int>;
|
||||
using CellSpicePortNames = std::map<std::string, StringSeq, std::less<>>;
|
||||
using LibertyPortLogicValues = std::map<const LibertyPort*, LogicValue>;
|
||||
// Use port name so lookup works across scenes.
|
||||
using PortLogicValues = std::map<std::string, LogicValue>;
|
||||
|
||||
// Utilities for writing a spice deck.
|
||||
class WriteSpice : public StaState
|
||||
|
|
@ -61,7 +62,7 @@ public:
|
|||
const StaState *sta);
|
||||
|
||||
protected:
|
||||
void initPowerGnd();
|
||||
void initPowerGnd(LibertyLibrary *threshold_library);
|
||||
void writeHeader(std::string &title,
|
||||
float max_time,
|
||||
float time_step);
|
||||
|
|
@ -73,7 +74,7 @@ protected:
|
|||
StringSeq &tokens);
|
||||
void writeSubcktInst(const Instance *inst);
|
||||
void writeSubcktInstVoltSrcs(const Instance *inst,
|
||||
LibertyPortLogicValues &port_values,
|
||||
PortLogicValues &port_values,
|
||||
const PinSet &excluded_input_pins);
|
||||
float pgPortVoltage(const LibertyPort *pg_port);
|
||||
void writeVoltageSource(std::string_view inst_name,
|
||||
|
|
@ -120,7 +121,7 @@ protected:
|
|||
void seqPortValues(Sequential *seq,
|
||||
const RiseFall *rf,
|
||||
// Return values.
|
||||
LibertyPortLogicValues &port_values);
|
||||
PortLogicValues &port_values);
|
||||
LibertyPort *onePort(FuncExpr *expr);
|
||||
void writeMeasureDelayStmt(const Pin *from_pin,
|
||||
const RiseFall *from_rf,
|
||||
|
|
@ -139,23 +140,25 @@ protected:
|
|||
|
||||
void gatePortValues(const Pin *input_pin,
|
||||
const Pin *drvr_pin,
|
||||
const RiseFall *input_rf,
|
||||
const RiseFall *drvr_rf,
|
||||
const Edge *gate_edge,
|
||||
// Return values.
|
||||
LibertyPortLogicValues &port_values,
|
||||
PortLogicValues &port_values,
|
||||
bool &is_clked);
|
||||
void regPortValues(const Pin *input_pin,
|
||||
const RiseFall *drvr_rf,
|
||||
const LibertyPort *drvr_port,
|
||||
const FuncExpr *drvr_func,
|
||||
// Return values.
|
||||
LibertyPortLogicValues &port_values,
|
||||
PortLogicValues &port_values,
|
||||
bool &is_clked);
|
||||
void gatePortValues(const Instance *inst,
|
||||
const FuncExpr *expr,
|
||||
void gatePortValues(const FuncExpr *expr,
|
||||
const LibertyPort *input_port,
|
||||
const RiseFall *input_rf,
|
||||
const RiseFall *drvr_rf,
|
||||
// Return values.
|
||||
LibertyPortLogicValues &port_values);
|
||||
PortLogicValues &port_values);
|
||||
void writeSubcktInstLoads(const Pin *drvr_pin,
|
||||
const Pin *path_load,
|
||||
const PinSet &excluded_input_pins,
|
||||
|
|
@ -176,7 +179,7 @@ protected:
|
|||
const MinMax *min_max_;
|
||||
|
||||
std::ofstream spice_stream_;
|
||||
LibertyLibrary *default_library_;
|
||||
LibertyLibrary *threshold_library_;
|
||||
float power_voltage_;
|
||||
float gnd_voltage_;
|
||||
float max_time_;
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ proc get_property_object_type { object_type object_name quiet } {
|
|||
}
|
||||
|
||||
define_cmd_args "define_property" \
|
||||
{-object_type scene|mode -type bool|float|string property}
|
||||
{-object_type scene|mode|library|liberty_library|cell|liberty_cell|port|liberty_port|instance|pin|net|clock -type bool|float|string property}
|
||||
|
||||
proc define_property { args } {
|
||||
parse_key_args "define_property" args keys {-object_type -type} flags {}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
Startpoint: r0 (rising edge-triggered flip-flop clocked by clk)
|
||||
Endpoint: t0 (rising edge-triggered flip-flop clocked by clk)
|
||||
Path Group: clk
|
||||
Path Type: max
|
||||
|
||||
Delay Time Description
|
||||
---------------------------------------------------------
|
||||
0.00 0.00 clock clk (rise edge)
|
||||
0.00 0.00 clock network delay (propagated)
|
||||
0.00 0.00 ^ r0/CLK (DFFHQx4_ASAP7_75t_R)
|
||||
68.24 68.24 ^ r0/Q (DFFHQx4_ASAP7_75t_R)
|
||||
52.16 120.40 ^ u0/Y (BUFx2_ASAP7_75t_R)
|
||||
18.40 138.80 ^ t0/D (DFFHQx4_ASAP7_75t_R)
|
||||
138.80 data arrival time
|
||||
|
||||
500.00 500.00 clock clk (rise edge)
|
||||
0.00 500.00 clock network delay (propagated)
|
||||
0.00 500.00 clock reconvergence pessimism
|
||||
500.00 ^ t0/CLK (DFFHQx4_ASAP7_75t_R)
|
||||
-22.18 477.82 library setup time
|
||||
477.82 data required time
|
||||
---------------------------------------------------------
|
||||
477.82 data required time
|
||||
-138.80 data arrival time
|
||||
---------------------------------------------------------
|
||||
339.02 slack (MET)
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
*SPEF "IEEE 1481-1998"
|
||||
*DESIGN "top"
|
||||
*DATE "2026"
|
||||
*VENDOR "OpenSTA test"
|
||||
*PROGRAM "hand written"
|
||||
*VERSION "1.0.1c"
|
||||
*DESIGN_FLOW "MISSING_NETS"
|
||||
*DIVIDER /
|
||||
*DELIMITER :
|
||||
*BUS_DELIMITER [ ]
|
||||
*T_UNIT 1.0 PS
|
||||
*C_UNIT 1.0 FF
|
||||
*R_UNIT 1.0 KOHM
|
||||
*L_UNIT 1.0 UH
|
||||
|
||||
// Each buffer output net z<i> is degenerate two ways:
|
||||
// - z<i>:2 z<i>:3 have ground cap but NO resistor (floating islands)
|
||||
// - u<i>:Y -- z<i>:1 is a 0 KOHM resistor (an ideal short)
|
||||
// Both used to make PrimaDelayCalc::primaReduce() factorize a singular G
|
||||
// (STA-1752). node_count_ = 5 > prima_order_ (default 3) selects the
|
||||
// primaReduce() path that factorizes the pure G matrix.
|
||||
|
||||
*D_NET z0 40.2
|
||||
*CONN
|
||||
*I u0:Y O
|
||||
*I t0:D I *L .0086
|
||||
*CAP
|
||||
1 u0:Y 6.7
|
||||
2 t0:D 6.7
|
||||
3 z0:1 6.7
|
||||
4 z0:2 6.7
|
||||
5 z0:3 6.7
|
||||
*RES
|
||||
6 u0:Y z0:1 0
|
||||
7 z0:1 t0:D 2.42
|
||||
*END
|
||||
|
||||
*D_NET z1 40.2
|
||||
*CONN
|
||||
*I u1:Y O
|
||||
*I t1:D I *L .0086
|
||||
*CAP
|
||||
1 u1:Y 6.7
|
||||
2 t1:D 6.7
|
||||
3 z1:1 6.7
|
||||
4 z1:2 6.7
|
||||
5 z1:3 6.7
|
||||
*RES
|
||||
6 u1:Y z1:1 0
|
||||
7 z1:1 t1:D 2.42
|
||||
*END
|
||||
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
# Prima delay calc on degenerate parasitic networks (STA-1752 regression).
|
||||
#
|
||||
# Each buffer output net has both a floating (resistor-less) node and a
|
||||
# zero-resistance short. Either one used to make PrimaDelayCalc's conductance
|
||||
# matrix G singular, raising STA-1752 "G matrix is singular". Single threaded
|
||||
# this surfaced as a Tcl error; multi threaded the error was thrown from a
|
||||
# DispatchQueue worker and aborted with SIGABRT. findNodeCount() now drops
|
||||
# isolated nodes and merges shorted nodes, so the delay is computed correctly.
|
||||
#
|
||||
# This test runs single threaded and checks the reported path. To exercise the
|
||||
# historical multi-threaded crash path set STA_TEST_THREADS to the number of
|
||||
# parallel buffers (2); the run must still complete without aborting. (A BFS
|
||||
# level is dispatched to workers only when its vertex count >= the thread count,
|
||||
# so more threads than buffers runs inline on the main thread.)
|
||||
read_liberty asap7_small.lib.gz
|
||||
read_verilog prima_singular.v
|
||||
link_design top
|
||||
create_clock -name clk -period 500 clk
|
||||
set_input_delay -clock clk 1 [list in0 in1]
|
||||
set_input_transition 10 [list clk in0 in1]
|
||||
set_propagated_clock clk
|
||||
read_spef prima_singular.spef
|
||||
sta::set_delay_calculator prima
|
||||
if { [info exists ::env(STA_TEST_THREADS)] } {
|
||||
sta::set_thread_count $::env(STA_TEST_THREADS)
|
||||
} else {
|
||||
sta::set_thread_count 1
|
||||
}
|
||||
report_checks -group_path_count 1
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
module top (clk, in0, in1, out0, out1);
|
||||
input clk, in0, in1;
|
||||
output out0, out1;
|
||||
wire q0, q1, z0, z1;
|
||||
DFFHQx4_ASAP7_75t_R r0 (.D(in0), .CLK(clk), .Q(q0));
|
||||
BUFx2_ASAP7_75t_R u0 (.A(q0), .Y(z0));
|
||||
DFFHQx4_ASAP7_75t_R t0 (.D(z0), .CLK(clk), .Q(out0));
|
||||
DFFHQx4_ASAP7_75t_R r1 (.D(in1), .CLK(clk), .Q(q1));
|
||||
BUFx2_ASAP7_75t_R u1 (.A(q1), .Y(z1));
|
||||
DFFHQx4_ASAP7_75t_R t1 (.D(z1), .CLK(clk), .Q(out1));
|
||||
endmodule
|
||||
|
|
@ -161,18 +161,22 @@ record_public_tests {
|
|||
path_group_names
|
||||
power_json
|
||||
prima3
|
||||
prima_singular
|
||||
read_saif_null_instance
|
||||
report_checks_sorted
|
||||
report_checks_src_attr
|
||||
report_json1
|
||||
report_json2
|
||||
suppress_msg
|
||||
user_properties
|
||||
vcd_begin_end_time
|
||||
verilog_attribute
|
||||
verilog_well_supplies
|
||||
verilog_specify
|
||||
verilog_write_escape
|
||||
verilog_write_gzip
|
||||
verilog_unconnected_hpin
|
||||
write_path_spice_arc_sense
|
||||
}
|
||||
|
||||
define_test_group fast [group_tests all]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
[get_property u1/Z owner]
|
||||
alice
|
||||
[get_property u2/ZN owner] (unset)
|
||||
<>
|
||||
[get_property r1q weight]
|
||||
3.500000
|
||||
[get_property u1z weight] (unset)
|
||||
<>
|
||||
[get_property u2 crit]
|
||||
1
|
||||
[get_property u1 crit] (unset)
|
||||
<>
|
||||
[get_property clk1 grp]
|
||||
main
|
||||
[get_pins -filter {owner == alice} *]
|
||||
Z
|
||||
Error: pin objects do not have a no_such_prop property.
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
# User-defined properties on pin/net/instance/clock object types.
|
||||
read_liberty ../examples/nangate45_typ.lib.gz
|
||||
read_verilog ../examples/example1.v
|
||||
link_design top
|
||||
create_clock -name clk1 -period 10 {clk1}
|
||||
|
||||
# pin: string property, set on one pin, left unset on another.
|
||||
define_property -object_type pin -type string owner
|
||||
set_property [get_pins u1/Z] owner alice
|
||||
puts {[get_property u1/Z owner]}
|
||||
puts [get_property [get_pins u1/Z] owner]
|
||||
puts {[get_property u2/ZN owner] (unset)}
|
||||
puts "<[get_property [get_pins u2/ZN] owner]>"
|
||||
|
||||
# net: float property.
|
||||
define_property -object_type net -type float weight
|
||||
set_property [get_nets r1q] weight 3.5
|
||||
puts {[get_property r1q weight]}
|
||||
puts [get_property [get_nets r1q] weight]
|
||||
puts {[get_property u1z weight] (unset)}
|
||||
puts "<[get_property [get_nets u1z] weight]>"
|
||||
|
||||
# instance: bool property.
|
||||
define_property -object_type instance -type bool crit
|
||||
set_property [get_cells u2] crit true
|
||||
puts {[get_property u2 crit]}
|
||||
puts [get_property [get_cells u2] crit]
|
||||
puts {[get_property u1 crit] (unset)}
|
||||
puts "<[get_property [get_cells u1] crit]>"
|
||||
|
||||
# clock: string property.
|
||||
define_property -object_type clock -type string grp
|
||||
set_property [get_clocks clk1] grp main
|
||||
puts {[get_property clk1 grp]}
|
||||
puts [get_property [get_clocks clk1] grp]
|
||||
|
||||
# -filter skips objects the property was never set on (no error).
|
||||
puts {[get_pins -filter {owner == alice} *]}
|
||||
report_object_names [get_pins -filter {owner == alice} *]
|
||||
|
||||
# An undefined property still errors.
|
||||
if {[catch {get_property [get_pins u1/Z] no_such_prop} msg]} {
|
||||
puts $msg
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
Annotated 2 pin activities.
|
||||
Pin Name Activity Duty Cycle
|
||||
--------------------------------------------------------
|
||||
u_inv/Y 0.0666665 0.667
|
||||
u_inv/A 0.0666665 0.333
|
||||
|
||||
Annotated 2 pin activities.
|
||||
Pin Name Activity Duty Cycle
|
||||
--------------------------------------------------------
|
||||
u_inv/Y 0.1 1.000
|
||||
u_inv/A 0.1 0.000
|
||||
|
||||
Annotated 2 pin activities.
|
||||
Pin Name Activity Duty Cycle
|
||||
--------------------------------------------------------
|
||||
u_inv/Y 0.2 0.000
|
||||
u_inv/A 0.2 1.000
|
||||
|
||||
Annotated 2 pin activities.
|
||||
Pin Name Activity Duty Cycle
|
||||
--------------------------------------------------------
|
||||
u_inv/Y 0.1 1.000
|
||||
u_inv/A 0.1 0.000
|
||||
|
||||
Annotated 2 pin activities.
|
||||
Pin Name Activity Duty Cycle
|
||||
--------------------------------------------------------
|
||||
u_inv/Y 0.25 0.500
|
||||
u_inv/A 0.25 0.500
|
||||
|
||||
Annotated 2 pin activities.
|
||||
Pin Name Activity Duty Cycle
|
||||
--------------------------------------------------------
|
||||
u_inv/Y 0.125 0.750
|
||||
u_inv/A 0.125 0.250
|
||||
|
||||
Annotated 2 pin activities.
|
||||
Pin Name Activity Duty Cycle
|
||||
--------------------------------------------------------
|
||||
u_inv/Y 0.125 0.250
|
||||
u_inv/A 0.125 0.750
|
||||
|
||||
Annotated 2 pin activities.
|
||||
Pin Name Activity Duty Cycle
|
||||
--------------------------------------------------------
|
||||
u_inv/Y 0.25 0.500
|
||||
u_inv/A 0.25 0.500
|
||||
|
||||
Annotated 2 pin activities.
|
||||
Pin Name Activity Duty Cycle
|
||||
--------------------------------------------------------
|
||||
u_inv/Y 0.125 0.250
|
||||
u_inv/A 0.125 0.750
|
||||
|
||||
Annotated 2 pin activities.
|
||||
Pin Name Activity Duty Cycle
|
||||
--------------------------------------------------------
|
||||
u_inv/Y 0.125 0.750
|
||||
u_inv/A 0.125 0.250
|
||||
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
# Report pin activities
|
||||
proc report_activities { } {
|
||||
set pins [get_pins -hierarchical *]
|
||||
set clk_freq [expr 1.0 / (10 * 1e-12)]
|
||||
puts "Pin Name Activity Duty Cycle"
|
||||
puts "--------------------------------------------------------"
|
||||
foreach pin $pins {
|
||||
set prop [get_property $pin activity]
|
||||
set transitions_per_sec [lindex $prop 0]
|
||||
set duty [lindex $prop 1]
|
||||
set activity [expr double($transitions_per_sec) / [expr $clk_freq * 2]]
|
||||
puts "[get_full_name $pin] $activity $duty"
|
||||
}
|
||||
puts ""
|
||||
}
|
||||
|
||||
# Setup
|
||||
read_liberty asap7_invbuf.lib.gz
|
||||
read_verilog vcd_begin_end_time.v
|
||||
link_design top
|
||||
|
||||
# Define clock period in ps
|
||||
create_clock -name vclk -period 10
|
||||
|
||||
# Full VCD reading works (normal behavior)
|
||||
# VCD changes at time 50 and 100 (inverter)
|
||||
sta::clear_power
|
||||
read_vcd vcd_begin_end_time.vcd -scope top
|
||||
report_activities
|
||||
|
||||
# Read VCD from start to first transition point
|
||||
sta::clear_power
|
||||
read_vcd vcd_begin_end_time.vcd -scope top -end_time 50
|
||||
report_activities
|
||||
|
||||
# Read VCD from first transition point to second transition point
|
||||
sta::clear_power
|
||||
read_vcd vcd_begin_end_time.vcd -scope top -begin_time 50 -end_time 100
|
||||
report_activities
|
||||
|
||||
# Read VCD from second transition point to end
|
||||
sta::clear_power
|
||||
read_vcd vcd_begin_end_time.vcd -scope top -begin_time 100
|
||||
report_activities
|
||||
|
||||
# Read VCD around the first transition point
|
||||
sta::clear_power
|
||||
read_vcd vcd_begin_end_time.vcd -scope top -begin_time 40 -end_time 60
|
||||
report_activities
|
||||
|
||||
sta::clear_power
|
||||
read_vcd vcd_begin_end_time.vcd -scope top -begin_time 20 -end_time 60
|
||||
report_activities
|
||||
|
||||
sta::clear_power
|
||||
read_vcd vcd_begin_end_time.vcd -scope top -begin_time 40 -end_time 80
|
||||
report_activities
|
||||
|
||||
# Read VCD around the second transition point (should mirror the first)
|
||||
sta::clear_power
|
||||
read_vcd vcd_begin_end_time.vcd -scope top -begin_time 90 -end_time 110
|
||||
report_activities
|
||||
|
||||
sta::clear_power
|
||||
read_vcd vcd_begin_end_time.vcd -scope top -begin_time 70 -end_time 110
|
||||
report_activities
|
||||
|
||||
sta::clear_power
|
||||
read_vcd vcd_begin_end_time.vcd -scope top -begin_time 90 -end_time 130
|
||||
report_activities
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
`timescale 1ps/1ps
|
||||
|
||||
module top (
|
||||
input wire A,
|
||||
input wire clk,
|
||||
output wire Y
|
||||
);
|
||||
|
||||
INVx2_ASAP7_75t_R u_inv (
|
||||
.A(A),
|
||||
.Y(Y)
|
||||
);
|
||||
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
$date
|
||||
Mon Mar 16 2026
|
||||
$end
|
||||
$version
|
||||
VCD Test File
|
||||
$end
|
||||
$timescale
|
||||
1ps
|
||||
$end
|
||||
$scope module top $end
|
||||
$var wire 1 ! A $end
|
||||
$var wire 1 " Y $end
|
||||
$upscope $end
|
||||
$enddefinitions $end
|
||||
#0
|
||||
$dumpvars
|
||||
0!
|
||||
1"
|
||||
$end
|
||||
#50
|
||||
1!
|
||||
0"
|
||||
#100
|
||||
0!
|
||||
1"
|
||||
#150
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
* Copyright 2020 The SkyWater PDK Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
|
||||
.subckt sky130_fd_sc_hd__xor2_1 A B VGND VNB VPB VPWR X
|
||||
X0 a_35_297# A VGND VNB sky130_fd_pr__nfet_01v8 w=650000u l=150000u
|
||||
X1 VGND B a_35_297# VNB sky130_fd_pr__nfet_01v8 w=650000u l=150000u
|
||||
X2 X a_35_297# VGND VNB sky130_fd_pr__nfet_01v8 w=650000u l=150000u
|
||||
X3 a_285_297# B VPWR VPB sky130_fd_pr__pfet_01v8_hvt w=1e+06u l=150000u
|
||||
X4 VPWR A a_285_297# VPB sky130_fd_pr__pfet_01v8_hvt w=1e+06u l=150000u
|
||||
X5 a_35_297# B a_117_297# VPB sky130_fd_pr__pfet_01v8_hvt w=1e+06u l=150000u
|
||||
X6 a_117_297# A VPWR VPB sky130_fd_pr__pfet_01v8_hvt w=1e+06u l=150000u
|
||||
X7 a_285_47# B X VNB sky130_fd_pr__nfet_01v8 w=650000u l=150000u
|
||||
X8 a_285_297# a_35_297# X VPB sky130_fd_pr__pfet_01v8_hvt w=1e+06u l=150000u
|
||||
X9 VGND A a_285_47# VNB sky130_fd_pr__nfet_01v8 w=650000u l=150000u
|
||||
.ends
|
||||
|
||||
* Absorber stubs: write_path_spice's lib_subckt reader (findCellSubckts) treats
|
||||
* the last token of every device line as a subckt-call name, so a flat
|
||||
* transistor netlist makes it look for the parameter "l=150000u" and the
|
||||
* closing ".ends" as if they were cells. These empty subckts satisfy that
|
||||
* lookup; only the sky130_fd_sc_hd__xor2_1 subckt above is real. (Unrelated to
|
||||
* the arc-sense fix under test.)
|
||||
.subckt l=150000u
|
||||
.ends
|
||||
.subckt .ends
|
||||
.ends
|
||||
Binary file not shown.
|
|
@ -0,0 +1,6 @@
|
|||
* Placeholder SPICE model file for the write_path_spice_arc_sense regression.
|
||||
*
|
||||
* write_path_spice requires a -model_file and emits it as a ".include" line in
|
||||
* the generated deck, but does not read its contents. The regression only
|
||||
* diffs the written deck, so no transistor models are needed here. Supply the
|
||||
* real SKY130 sky130_fd_pr models if you want to simulate the deck in ngspice.
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
Warning 1171: write_path_spice_arc_sense.lib.gz line 23, default_fanout_load is 0.0.
|
||||
* Path from a v to x ^
|
||||
.include "write_path_spice_arc_sense.models.spice"
|
||||
.include "write_path_spice_arc_sense.sp_1.subckt"
|
||||
.tran 1e-13 3.33e-09
|
||||
|
||||
.print tran v(a) v(x0/B) v(x0/X) v(x)
|
||||
|
||||
**************
|
||||
* Input source
|
||||
**************
|
||||
|
||||
v1 a 0 pwl(
|
||||
+0.000e+00 1.800e+00
|
||||
+1.667e-11 0.000e+00
|
||||
+3.333e-09 0.000e+00
|
||||
+)
|
||||
|
||||
*****************
|
||||
* Stage instances
|
||||
*****************
|
||||
|
||||
xstage1 a x0/B stage1
|
||||
xstage2 x0/B x0/X x stage2
|
||||
|
||||
***************
|
||||
* Stage subckts
|
||||
***************
|
||||
|
||||
.subckt stage1 a x0/B
|
||||
* Net a
|
||||
* Net has no parasitics.
|
||||
R1 a x0/B 1.000e-04
|
||||
.ends
|
||||
|
||||
.subckt stage2 x0/B x0/X x
|
||||
* Gate x0 B -> X
|
||||
xx0 x0/A x0/B x0/VGND x0/VNB x0/VPB x0/VPWR x0/X sky130_fd_sc_hd__xor2_1
|
||||
v1 x0/A 0 1.800
|
||||
v2 x0/VGND 0 0.000
|
||||
v3 x0/VNB 0 0.000
|
||||
v4 x0/VPB 0 1.800
|
||||
v5 x0/VPWR 0 1.800
|
||||
|
||||
* Load pins
|
||||
* Net x
|
||||
* Net has no parasitics.
|
||||
R1 x0/X x 1.000e-04
|
||||
.ends
|
||||
|
||||
.end
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
# write_path_spice ties non-unate side inputs to the sensitized arc (issue #474)
|
||||
source helpers.tcl
|
||||
read_liberty write_path_spice_arc_sense.lib.gz
|
||||
read_verilog write_path_spice_arc_sense.v
|
||||
link_design repro
|
||||
create_clock -name vclk -period 10
|
||||
set_input_delay -clock vclk 0 [all_inputs]
|
||||
set_output_delay -clock vclk 0 [all_outputs]
|
||||
# Force the inverting arc B(fall) -> X(rise), which the liberty defines only
|
||||
# under "when A" (A=1). The xor2 side input A is the unconstrained port s, so
|
||||
# a correct deck must tie x0/A high (v1 x0/A 0 1.800). Before the fix the
|
||||
# Boolean-difference cube ignored the arc direction and tied it low (0.000).
|
||||
set spice_file [make_result_file "write_path_spice_arc_sense.sp"]
|
||||
write_path_spice -path_args {-path_delay max -fall_from [get_ports a] -rise_to [get_ports x]} \
|
||||
-spice_file $spice_file \
|
||||
-lib_subckt_file write_path_spice_arc_sense.cells.spice \
|
||||
-model_file write_path_spice_arc_sense.models.spice \
|
||||
-power VPWR -ground VGND \
|
||||
-simulator ngspice
|
||||
report_file ${spice_file}_1.sp
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
// Minimal repro for the write_path_spice non-unate side-input tie bug.
|
||||
// One xor2: the timed path enters pin B, the side input A comes from an
|
||||
// unconstrained port, so STA knows no constant for it and write_path_spice
|
||||
// must pick a tie that matches the arc it sensitized.
|
||||
module repro (input a, input s, output x);
|
||||
sky130_fd_sc_hd__xor2_1 x0 (.A(s), .B(a), .X(x));
|
||||
endmodule
|
||||
Loading…
Reference in New Issue