// 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 . // // 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 "sdf/SdfWriter.hh" #include #include #include "Zlib.hh" #include "StaConfig.hh" // STA_VERSION #include "Fuzzy.hh" #include "StringUtil.hh" #include "Units.hh" #include "TimingRole.hh" #include "TimingArc.hh" #include "Liberty.hh" #include "Sdc.hh" #include "MinMaxValues.hh" #include "Network.hh" #include "Graph.hh" #include "DcalcAnalysisPt.hh" #include "GraphDelayCalc.hh" #include "StaState.hh" #include "Corner.hh" #include "PathAnalysisPt.hh" namespace sta { using std::string; class SdfWriter : public StaState { public: SdfWriter(StaState *sta); ~SdfWriter(); void write(const char *filename, const Corner *corner, char sdf_divider, bool include_typ, int digits, bool gzip, bool no_timestamp, bool no_version); protected: void writeHeader(LibertyLibrary *default_lib, bool no_timestamp, bool no_version); void writeTrailer(); void writeInterconnects(); void writeInstInterconnects(Instance *inst); void writeInterconnectFromPin(Pin *drvr_pin); void writeInstances(); void writeInstHeader(const Instance *inst); void writeInstTrailer(); void writeIopaths(const Instance *inst, bool &inst_header); void writeIopathHeader(); void writeIopathTrailer(); void writeTimingChecks(const Instance *inst, bool &inst_header); void ensureTimingCheckheaders(bool &check_header, const Instance *inst, bool &inst_header); void writeCheck(Edge *edge, const char *sdf_check); void writeCheck(Edge *edge, TimingArc *arc, const char *sdf_check, bool use_data_edge, bool use_clk_edge); void writeEdgeCheck(Edge *edge, const char *sdf_check, int clk_rf_index, TimingArc *arcs[RiseFall::index_count][RiseFall::index_count]); void writeTimingCheckHeader(); void writeTimingCheckTrailer(); void writeWidthCheck(const Pin *pin, const RiseFall *hi_low, float min_width, float max_width); void writePeriodCheck(const Pin *pin, float min_period); const char *sdfEdge(const Transition *tr); void writeArcDelays(Edge *edge); void writeSdfTriple(RiseFallMinMax &delays, const RiseFall *rf); void writeSdfTriple(float min, float max); void writeSdfDelay(double delay); string sdfPortName(const Pin *pin); string sdfPathName(const Pin *pin); string sdfPathName(const Instance *inst); string sdfName(const Instance *inst); private: char sdf_divider_; bool include_typ_; float timescale_; char sdf_escape_; char network_escape_; char *delay_format_; gzFile stream_; const Corner *corner_; int arc_delay_min_index_; int arc_delay_max_index_; }; void writeSdf(const char *filename, const Corner *corner, char sdf_divider, bool include_typ, int digits, bool gzip, bool no_timestamp, bool no_version, StaState *sta) { SdfWriter writer(sta); writer.write(filename, corner, sdf_divider, include_typ, digits, gzip, no_timestamp, no_version); } SdfWriter::SdfWriter(StaState *sta) : StaState(sta), sdf_escape_('\\'), network_escape_(network_->pathEscape()), delay_format_(nullptr) { } SdfWriter::~SdfWriter() { stringDelete(delay_format_); } void SdfWriter::write(const char *filename, const Corner *corner, char sdf_divider, bool include_typ, int digits, bool gzip, bool no_timestamp, bool no_version) { sdf_divider_ = sdf_divider; include_typ_ = include_typ; if (delay_format_ == nullptr) delay_format_ = stringPrint("%%.%df", digits); LibertyLibrary *default_lib = network_->defaultLibertyLibrary(); timescale_ = default_lib->units()->timeUnit()->scale(); corner_ = corner; const MinMax *min_max; const DcalcAnalysisPt *dcalc_ap; min_max = MinMax::min(); dcalc_ap = corner_->findDcalcAnalysisPt(min_max); arc_delay_min_index_ = dcalc_ap->index(); min_max = MinMax::max(); dcalc_ap = corner_->findDcalcAnalysisPt(min_max); arc_delay_max_index_ = dcalc_ap->index(); stream_ = gzopen(filename, gzip ? "wb" : "wT"); if (stream_ == nullptr) throw FileNotWritable(filename); writeHeader(default_lib, no_timestamp, no_version); writeInterconnects(); writeInstances(); writeTrailer(); gzclose(stream_); stream_ = nullptr; } void SdfWriter::writeHeader(LibertyLibrary *default_lib, bool no_timestamp, bool no_version) { gzprintf(stream_, "(DELAYFILE\n"); gzprintf(stream_, " (SDFVERSION \"3.0\")\n"); gzprintf(stream_, " (DESIGN \"%s\")\n", network_->cellName(network_->topInstance())); if (!no_timestamp) { time_t now; time(&now); char *time_str = ctime(&now); // Remove trailing \n. time_str[strlen(time_str) - 1] = '\0'; gzprintf(stream_, " (DATE \"%s\")\n", time_str); } gzprintf(stream_, " (VENDOR \"Parallax\")\n"); gzprintf(stream_, " (PROGRAM \"STA\")\n"); if (!no_version) gzprintf(stream_, " (VERSION \"%s\")\n", STA_VERSION); gzprintf(stream_, " (DIVIDER %c)\n", sdf_divider_); LibertyLibrary *lib_min = default_lib; const LibertySeq &libs_min = corner_->libertyLibraries(MinMax::min()); if (!libs_min.empty()) lib_min = libs_min[0]; LibertyLibrary *lib_max = default_lib; const LibertySeq &libs_max = corner_->libertyLibraries(MinMax::max()); if (!libs_max.empty()) lib_max = libs_max[0]; OperatingConditions *cond_min = lib_min->defaultOperatingConditions(); OperatingConditions *cond_max = lib_max->defaultOperatingConditions(); if (cond_min && cond_max) { gzprintf(stream_, " (VOLTAGE %.3f::%.3f)\n", cond_min->voltage(), cond_max->voltage()); gzprintf(stream_, " (PROCESS \"%.3f::%.3f\")\n", cond_min->process(), cond_max->process()); gzprintf(stream_, " (TEMPERATURE %.3f::%.3f)\n", cond_min->temperature(), cond_max->temperature()); } const char *sdf_timescale = nullptr; if (fuzzyEqual(timescale_, 1e-6)) sdf_timescale = "1us"; else if (fuzzyEqual(timescale_, 10e-6)) sdf_timescale = "10us"; else if (fuzzyEqual(timescale_, 100e-6)) sdf_timescale = "100us"; else if (fuzzyEqual(timescale_, 1e-9)) sdf_timescale = "1ns"; else if (fuzzyEqual(timescale_, 10e-9)) sdf_timescale = "10ns"; else if (fuzzyEqual(timescale_, 100e-9)) sdf_timescale = "100ns"; else if (fuzzyEqual(timescale_, 1e-12)) sdf_timescale = "1ps"; else if (fuzzyEqual(timescale_, 10e-12)) sdf_timescale = "10ps"; else if (fuzzyEqual(timescale_, 100e-12)) sdf_timescale = "100ps"; if (sdf_timescale) gzprintf(stream_, " (TIMESCALE %s)\n", sdf_timescale); } void SdfWriter::writeTrailer() { gzprintf(stream_, ")\n"); } void SdfWriter::writeInterconnects() { gzprintf(stream_, " (CELL\n"); gzprintf(stream_, " (CELLTYPE \"%s\")\n", network_->cellName(network_->topInstance())); gzprintf(stream_, " (INSTANCE)\n"); gzprintf(stream_, " (DELAY\n"); gzprintf(stream_, " (ABSOLUTE\n"); writeInstInterconnects(network_->topInstance()); LeafInstanceIterator *inst_iter = network_->leafInstanceIterator(); while (inst_iter->hasNext()) { Instance *inst = inst_iter->next(); writeInstInterconnects(inst); } delete inst_iter; gzprintf(stream_, " )\n"); gzprintf(stream_, " )\n"); gzprintf(stream_, " )\n"); } void SdfWriter::writeInstInterconnects(Instance *inst) { InstancePinIterator *pin_iter = network_->pinIterator(inst); while (pin_iter->hasNext()) { Pin *pin = pin_iter->next(); if (network_->isDriver(pin)) writeInterconnectFromPin(pin); } delete pin_iter; } void SdfWriter::writeInterconnectFromPin(Pin *drvr_pin) { Vertex *drvr_vertex = graph_->pinDrvrVertex(drvr_pin); if (drvr_vertex) { VertexOutEdgeIterator edge_iter(drvr_vertex, graph_); while (edge_iter.hasNext()) { Edge *edge = edge_iter.next(); if (edge->isWire()) { Pin *load_pin = edge->to(graph_)->pin(); string drvr_pin_name = sdfPathName(drvr_pin); string load_pin_name = sdfPathName(load_pin); gzprintf(stream_, " (INTERCONNECT %s %s ", drvr_pin_name.c_str(), load_pin_name.c_str()); writeArcDelays(edge); gzprintf(stream_, ")\n"); } } } } void SdfWriter::writeInstances() { LeafInstanceIterator *leaf_iter = network_->leafInstanceIterator(); while (leaf_iter->hasNext()) { const Instance *inst = leaf_iter->next(); bool inst_header = false; writeIopaths(inst, inst_header); writeTimingChecks(inst, inst_header); if (inst_header) writeInstTrailer(); } delete leaf_iter; } void SdfWriter::writeInstHeader(const Instance *inst) { gzprintf(stream_, " (CELL\n"); gzprintf(stream_, " (CELLTYPE \"%s\")\n", network_->cellName(inst)); string inst_name = sdfPathName(inst); gzprintf(stream_, " (INSTANCE %s)\n", inst_name.c_str()); } void SdfWriter::writeInstTrailer() { gzprintf(stream_, " )\n"); } void SdfWriter::writeIopaths(const Instance *inst, bool &inst_header) { bool iopath_header = false; InstancePinIterator *pin_iter = network_->pinIterator(inst); while (pin_iter->hasNext()) { Pin *from_pin = pin_iter->next(); if (network_->isLoad(from_pin)) { Vertex *from_vertex = graph_->pinLoadVertex(from_pin); VertexOutEdgeIterator edge_iter(from_vertex, graph_); while (edge_iter.hasNext()) { Edge *edge = edge_iter.next(); const TimingRole *role = edge->role(); if (role == TimingRole::combinational() || role == TimingRole::tristateEnable() || role == TimingRole::regClkToQ() || role == TimingRole::regSetClr() || role == TimingRole::latchEnToQ() || role == TimingRole::latchDtoQ()) { Vertex *to_vertex = edge->to(graph_); Pin *to_pin = to_vertex->pin(); if (!inst_header) { writeInstHeader(inst); inst_header = true; } if (!iopath_header) { writeIopathHeader(); iopath_header = true; } const char *sdf_cond = edge->timingArcSet()->sdfCond(); if (sdf_cond) { gzprintf(stream_, " (COND %s\n", sdf_cond); gzprintf(stream_, " "); } string from_pin_name = sdfPortName(from_pin); string to_pin_name = sdfPortName(to_pin); gzprintf(stream_, " (IOPATH %s %s ", from_pin_name.c_str(), to_pin_name.c_str()); writeArcDelays(edge); if (sdf_cond) gzprintf(stream_, ")"); gzprintf(stream_, ")\n"); } } } } delete pin_iter; if (iopath_header) writeIopathTrailer(); } void SdfWriter::writeIopathHeader() { gzprintf(stream_, " (DELAY\n"); gzprintf(stream_, " (ABSOLUTE\n"); } void SdfWriter::writeIopathTrailer() { gzprintf(stream_, " )\n"); gzprintf(stream_, " )\n"); } void SdfWriter::writeArcDelays(Edge *edge) { RiseFallMinMax delays; TimingArcSet *arc_set = edge->timingArcSet(); for (TimingArc *arc : arc_set->arcs()) { const RiseFall *rf = arc->toEdge()->asRiseFall(); ArcDelay min_delay = graph_->arcDelay(edge, arc, arc_delay_min_index_); delays.setValue(rf, MinMax::min(), delayAsFloat(min_delay)); ArcDelay max_delay = graph_->arcDelay(edge, arc, arc_delay_max_index_); delays.setValue(rf, MinMax::max(), delayAsFloat(max_delay)); } if (delays.hasValue(RiseFall::rise(), MinMax::min()) && delays.hasValue(RiseFall::fall(), MinMax::min())) { // Rise and fall. writeSdfTriple(delays, RiseFall::rise()); // Merge rise/fall values if they are the same. if (!(fuzzyEqual(delays.value(RiseFall::rise(), MinMax::min()), delays.value(RiseFall::fall(), MinMax::min())) && fuzzyEqual(delays.value(RiseFall::rise(), MinMax::max()), delays.value(RiseFall::fall(),MinMax::max())))) { gzprintf(stream_, " "); writeSdfTriple(delays, RiseFall::fall()); } } else if (delays.hasValue(RiseFall::rise(), MinMax::min())) // Rise only. writeSdfTriple(delays, RiseFall::rise()); else if (delays.hasValue(RiseFall::fall(), MinMax::min())) { // Fall only. gzprintf(stream_, "() "); writeSdfTriple(delays, RiseFall::fall()); } } void SdfWriter::writeSdfTriple(RiseFallMinMax &delays, const RiseFall *rf) { float min = delays.value(rf, MinMax::min()); float max = delays.value(rf, MinMax::max()); writeSdfTriple(min, max); } void SdfWriter::writeSdfTriple(float min, float max) { gzprintf(stream_, "("); writeSdfDelay(min); if (include_typ_) { gzprintf(stream_, ":"); writeSdfDelay((min + max) / 2.0); gzprintf(stream_, ":"); } else gzprintf(stream_, "::"); writeSdfDelay(max); gzprintf(stream_, ")"); } void SdfWriter::writeSdfDelay(double delay) { gzprintf(stream_, delay_format_, delay / timescale_); } void SdfWriter::writeTimingChecks(const Instance *inst, bool &inst_header) { bool check_header = false; InstancePinIterator *pin_iter = network_->pinIterator(inst); while (pin_iter->hasNext()) { Pin *pin = pin_iter->next(); Vertex *vertex = graph_->pinLoadVertex(pin); VertexOutEdgeIterator edge_iter(vertex, graph_); while (edge_iter.hasNext()) { Edge *edge = edge_iter.next(); const TimingRole *role = edge->role(); const char *sdf_check = nullptr; if (role == TimingRole::setup()) sdf_check = "SETUP"; else if (role == TimingRole::hold()) sdf_check = "HOLD"; else if (role == TimingRole::recovery()) sdf_check = "RECOVERY"; else if (role == TimingRole::removal()) sdf_check = "REMOVAL"; if (sdf_check) { ensureTimingCheckheaders(check_header, inst, inst_header); writeCheck(edge, sdf_check); } } for (auto hi_low : RiseFall::range()) { float min_width, max_width; Edge *edge; TimingArc *arc; graph_->minPulseWidthArc(vertex, hi_low, edge, arc); if (edge) { min_width = delayAsFloat(graph_->arcDelay(edge, arc, arc_delay_min_index_)); max_width = delayAsFloat(graph_->arcDelay(edge, arc, arc_delay_max_index_)); ensureTimingCheckheaders(check_header, inst, inst_header); writeWidthCheck(pin, hi_low, min_width, max_width); } } float min_period; bool exists; graph_delay_calc_->minPeriod(pin, corner_, min_period, exists); if (exists) { ensureTimingCheckheaders(check_header, inst, inst_header); writePeriodCheck(pin, min_period); } } delete pin_iter; if (check_header) writeTimingCheckTrailer(); } void SdfWriter::ensureTimingCheckheaders(bool &check_header, const Instance *inst, bool &inst_header) { if (!inst_header) { writeInstHeader(inst); inst_header = true; } if (!check_header) { writeTimingCheckHeader(); check_header = true; } } void SdfWriter::writeTimingCheckHeader() { gzprintf(stream_, " (TIMINGCHECK\n"); } void SdfWriter::writeTimingCheckTrailer() { gzprintf(stream_, " )\n"); } void SdfWriter::writeCheck(Edge *edge, const char *sdf_check) { TimingArcSet *arc_set = edge->timingArcSet(); // Examine the arcs to see if the check requires clk or data edge specifiers. TimingArc *arcs[RiseFall::index_count][RiseFall::index_count] = {{nullptr, nullptr}, {nullptr, nullptr}}; for (TimingArc *arc : arc_set->arcs()) { const RiseFall *clk_rf = arc->fromEdge()->asRiseFall(); const RiseFall *data_rf = arc->toEdge()->asRiseFall();; arcs[clk_rf->index()][data_rf->index()] = arc; } if (arcs[RiseFall::fallIndex()][RiseFall::riseIndex()] == nullptr && arcs[RiseFall::fallIndex()][RiseFall::fallIndex()] == nullptr) writeEdgeCheck(edge, sdf_check, RiseFall::riseIndex(), arcs); else if (arcs[RiseFall::riseIndex()][RiseFall::riseIndex()] == nullptr && arcs[RiseFall::riseIndex()][RiseFall::fallIndex()] == nullptr) writeEdgeCheck(edge, sdf_check, RiseFall::fallIndex(), arcs); else { // No special case; write all the checks with data and clock edge specifiers. for (TimingArc *arc : arc_set->arcs()) writeCheck(edge, arc, sdf_check, true, true); } } void SdfWriter::writeEdgeCheck(Edge *edge, const char *sdf_check, int clk_rf_index, TimingArc *arcs[RiseFall::index_count][RiseFall::index_count]) { // SDF requires edge specifiers on the data port to define separate // rise/fall check values. // Check the rise/fall margins to see if they are the same to avoid adding // data port edge specifiers if they aren't necessary. if (arcs[clk_rf_index][RiseFall::riseIndex()] && arcs[clk_rf_index][RiseFall::fallIndex()] && arcs[clk_rf_index][RiseFall::riseIndex()] && arcs[clk_rf_index][RiseFall::fallIndex()] && delayEqual(graph_->arcDelay(edge, arcs[clk_rf_index][RiseFall::riseIndex()], arc_delay_min_index_), graph_->arcDelay(edge, arcs[clk_rf_index][RiseFall::fallIndex()], arc_delay_min_index_)) && delayEqual(graph_->arcDelay(edge, arcs[clk_rf_index][RiseFall::riseIndex()], arc_delay_max_index_), graph_->arcDelay(edge, arcs[clk_rf_index][RiseFall::fallIndex()], arc_delay_max_index_))) // Rise/fall margins are the same, so no data edge specifier is required. writeCheck(edge, arcs[clk_rf_index][RiseFall::riseIndex()], sdf_check, false, true); else { if (arcs[clk_rf_index][RiseFall::riseIndex()]) writeCheck(edge, arcs[clk_rf_index][RiseFall::riseIndex()], sdf_check, true, true); if (arcs[clk_rf_index][RiseFall::fallIndex()]) writeCheck(edge, arcs[clk_rf_index][RiseFall::fallIndex()], sdf_check, true, true); } } void SdfWriter::writeCheck(Edge *edge, TimingArc *arc, const char *sdf_check, bool use_data_edge, bool use_clk_edge) { TimingArcSet *arc_set = edge->timingArcSet(); Pin *from_pin = edge->from(graph_)->pin(); Pin *to_pin = edge->to(graph_)->pin(); const char *sdf_cond_start = arc_set->sdfCondStart(); const char *sdf_cond_end = arc_set->sdfCondEnd(); gzprintf(stream_, " (%s ", sdf_check); if (sdf_cond_start) gzprintf(stream_, "(COND %s ", sdf_cond_start); string to_pin_name = sdfPortName(to_pin); if (use_data_edge) { gzprintf(stream_, "(%s %s)", sdfEdge(arc->toEdge()), to_pin_name.c_str()); } else gzprintf(stream_, "%s", to_pin_name.c_str()); if (sdf_cond_start) gzprintf(stream_, ")"); gzprintf(stream_, " "); if (sdf_cond_end) gzprintf(stream_, "(COND %s ", sdf_cond_end); string from_pin_name = sdfPortName(from_pin); if (use_clk_edge) gzprintf(stream_, "(%s %s)", sdfEdge(arc->fromEdge()), from_pin_name.c_str()); else gzprintf(stream_, "%s", from_pin_name.c_str()); if (sdf_cond_end) gzprintf(stream_, ")"); gzprintf(stream_, " "); ArcDelay min_delay = graph_->arcDelay(edge, arc, arc_delay_min_index_); ArcDelay max_delay = graph_->arcDelay(edge, arc, arc_delay_max_index_); writeSdfTriple(delayAsFloat(min_delay), delayAsFloat(max_delay)); gzprintf(stream_, ")\n"); } void SdfWriter::writeWidthCheck(const Pin *pin, const RiseFall *hi_low, float min_width, float max_width) { string pin_name = sdfPortName(pin); gzprintf(stream_, " (WIDTH (%s %s) ", sdfEdge(hi_low->asTransition()), pin_name.c_str()); writeSdfTriple(min_width, max_width); gzprintf(stream_, ")\n"); } void SdfWriter::writePeriodCheck(const Pin *pin, float min_period) { string pin_name = sdfPortName(pin); gzprintf(stream_, " (PERIOD %s ", pin_name.c_str()); writeSdfTriple(min_period, min_period); gzprintf(stream_, ")\n"); } const char * SdfWriter::sdfEdge(const Transition *tr) { if (tr == Transition::rise()) return "posedge"; else if (tr == Transition::fall()) return "negedge"; return nullptr; } //////////////////////////////////////////////////////////////// string SdfWriter::sdfPathName(const Pin *pin) { Instance *inst = network_->instance(pin); if (network_->isTopInstance(inst)) return sdfPortName(pin); else { string inst_path = sdfPathName(inst); string port_name = sdfPortName(pin); string sdf_name = inst_path; sdf_name += sdf_divider_; sdf_name += port_name; return sdf_name; } } // Based on Network::pathName. string SdfWriter::sdfPathName(const Instance *instance) { InstanceSeq inst_path; network_->path(instance, inst_path); InstanceSeq::Iterator path_iter1(inst_path); string path_name; while (!inst_path.empty()) { const Instance *inst = inst_path.back(); string inst_name = sdfName(inst); path_name += inst_name; inst_path.pop_back(); if (!inst_path.empty()) path_name += sdf_divider_; } return path_name; } // Escape for non-alpha numeric characters. string SdfWriter::sdfName(const Instance *inst) { const char *name = network_->name(inst); string sdf_name; const char *p = name; while (*p) { char ch = *p; // Ignore sta escapes. if (ch != network_escape_) { if (!(isalnum(ch) || ch == '_')) // Insert escape. sdf_name += sdf_escape_; sdf_name += ch; } p++; } return sdf_name; } string SdfWriter::sdfPortName(const Pin *pin) { const char *name = network_->portName(pin); size_t name_length = strlen(name); string sdf_name; constexpr char bus_brkt_left = '['; constexpr char bus_brkt_right = ']'; size_t bus_index = name_length; if (name_length >= 4 && name[name_length - 1] == bus_brkt_right) { const char *left = strrchr(name, bus_brkt_left); if (left) bus_index = left - name; } for (size_t i = 0; i < name_length; i++) { char ch = name[i]; if (ch == network_escape_) { // Copy escape and escaped char. sdf_name += sdf_escape_; sdf_name += name[++i]; } else { if (!(isalnum(ch) || ch == '_') && !(i >= bus_index && (ch == bus_brkt_right || ch == bus_brkt_left))) // Insert escape. sdf_name += sdf_escape_; sdf_name += ch; } } return sdf_name; } } // namespace