sta: very crude static timing analysis pass
authorLofty <dan.ravensloft@gmail.com>
Wed, 24 Nov 2021 21:21:08 +0000 (21:21 +0000)
committerMarcelina Koƛcielnicka <mwk@0x04.net>
Thu, 25 Nov 2021 16:20:27 +0000 (17:20 +0100)
Co-authored-by: Eddie Hung <eddie@fpgeh.com>
backends/aiger/xaiger.cc
kernel/constids.inc
kernel/rtlil.cc
kernel/rtlil.h
kernel/timinginfo.h
passes/cmds/Makefile.inc
passes/cmds/sta.cc [new file with mode: 0644]
passes/techmap/abc9_ops.cc
tests/various/sta.ys [new file with mode: 0644]

index 66955d88e9d854f73c827257742bbd72a44e01f0..e223f185e5e40c52cca3847cda995c85d00a0a39 100644 (file)
@@ -261,26 +261,27 @@ struct XAigerWriter
 
                                if (!timing.count(inst_module->name))
                                        timing.setup_module(inst_module);
-                               auto &t = timing.at(inst_module->name).arrival;
-                               for (const auto &conn : cell->connections()) {
-                                       auto port_wire = inst_module->wire(conn.first);
-                                       if (!port_wire->port_output)
+
+                               for (auto &i : timing.at(inst_module->name).arrival) {
+                                       if (!cell->hasPort(i.first.name))
                                                continue;
 
-                                       for (int i = 0; i < GetSize(conn.second); i++) {
-                                               auto d = t.at(TimingInfo::NameBit(conn.first,i), 0);
-                                               if (d == 0)
-                                                       continue;
+                                       auto port_wire = inst_module->wire(i.first.name);
+                                       log_assert(port_wire->port_output);
+
+                                       auto d = i.second.first;
+                                       if (d == 0)
+                                               continue;
+                                       auto offset = i.first.offset;
 
 #ifndef NDEBUG
-                                               if (ys_debug(1)) {
-                                                       static std::set<std::tuple<IdString,IdString,int>> seen;
-                                                       if (seen.emplace(inst_module->name, conn.first, i).second) log("%s.%s[%d] abc9_arrival = %d\n",
-                                                                       log_id(cell->type), log_id(conn.first), i, d);
-                                               }
-#endif
-                                               arrival_times[conn.second[i]] = d;
+                                       if (ys_debug(1)) {
+                                               static pool<std::pair<IdString,TimingInfo::NameBit>> seen;
+                                               if (seen.emplace(inst_module->name, i.first).second) log("%s.%s[%d] abc9_arrival = %d\n",
+                                                               log_id(cell->type), log_id(i.first.name), offset, d);
                                        }
+#endif
+                                       arrival_times[cell->getPort(i.first.name)[offset]] = d;
                                }
 
                                if (abc9_flop)
index 8ccb6008904ebe9a2c2d010553eb48215610ec81..566b76217737c7bdea2a6e083fb8141a2658a298 100644 (file)
@@ -179,6 +179,7 @@ X(SRC_WIDTH)
 X(SRST)
 X(SRST_POLARITY)
 X(SRST_VALUE)
+X(sta_arrival)
 X(STATE_BITS)
 X(STATE_NUM)
 X(STATE_NUM_LOG2)
index 88153a380569b4852750667f721a210b48ee7c03..cd0f5ab1246c8cd6aed4a57cfe500326f45c82f1 100644 (file)
@@ -480,6 +480,35 @@ vector<string> RTLIL::AttrObject::get_hdlname_attribute() const
        return split_tokens(get_string_attribute(ID::hdlname), " ");
 }
 
+void RTLIL::AttrObject::set_intvec_attribute(RTLIL::IdString id, const vector<int> &data)
+{
+       std::stringstream attrval;
+       for (auto &i : data) {
+               if (attrval.tellp() > 0)
+                       attrval << " ";
+               attrval << i;
+       }
+       attributes[id] = RTLIL::Const(attrval.str());
+}
+
+vector<int> RTLIL::AttrObject::get_intvec_attribute(RTLIL::IdString id) const
+{
+       vector<int> data;
+       auto it = attributes.find(id);
+       if (it != attributes.end())
+               for (const auto &s : split_tokens(attributes.at(id).decode_string())) {
+                       char *end = nullptr;
+                       errno = 0;
+                       long value = strtol(s.c_str(), &end, 10);
+                       if (end != s.c_str() + s.size())
+                               log_cmd_error("Literal for intvec attribute has invalid format");
+                       if (errno == ERANGE || value < INT_MIN || value > INT_MAX)
+                               log_cmd_error("Literal for intvec attribute is out of range");
+                       data.push_back(value);
+               }
+       return data;
+}
+
 bool RTLIL::Selection::selected_module(RTLIL::IdString mod_name) const
 {
        if (full_selection)
index 68481b81ccd764273ae5487745ac60e597bc4694..073110f16104ae44d3d5c150ea33431ee4387ae6 100644 (file)
@@ -718,6 +718,9 @@ struct RTLIL::AttrObject
 
        void set_hdlname_attribute(const vector<string> &hierarchy);
        vector<string> get_hdlname_attribute() const;
+
+       void set_intvec_attribute(RTLIL::IdString id, const vector<int> &data);
+       vector<int> get_intvec_attribute(RTLIL::IdString id) const;
 };
 
 struct RTLIL::SigChunk
index 9d88ac027cc7267701fc6a95ef6548c09a7d9150..e7e4eab6e2e4df489529a3f5b28dc23270cb5803 100644 (file)
@@ -49,9 +49,9 @@ struct TimingInfo
 
        struct ModuleTiming
        {
-               RTLIL::IdString type;
                dict<BitBit, int> comb;
-               dict<NameBit, int> arrival, required;
+               dict<NameBit, std::pair<int,NameBit>> arrival, required;
+               bool has_inputs;
        };
 
        dict<RTLIL::IdString, ModuleTiming> data;
@@ -120,11 +120,10 @@ struct TimingInfo
                                }
                        }
                        else if (cell->type == ID($specify3)) {
-                               auto src = cell->getPort(ID::SRC);
+                               auto src = cell->getPort(ID::SRC).as_bit();
                                auto dst = cell->getPort(ID::DST);
-                               for (const auto &c : src.chunks())
-                                       if (!c.wire->port_input)
-                                               log_error("Module '%s' contains specify cell '%s' where SRC '%s' is not a module input.\n", log_id(module), log_id(cell), log_signal(src));
+                               if (!src.wire || !src.wire->port_input)
+                                       log_error("Module '%s' contains specify cell '%s' where SRC '%s' is not a module input.\n", log_id(module), log_id(cell), log_signal(src));
                                for (const auto &c : dst.chunks())
                                        if (!c.wire->port_output)
                                                log_error("Module '%s' contains specify cell '%s' where DST '%s' is not a module output.\n", log_id(module), log_id(cell), log_signal(dst));
@@ -136,34 +135,49 @@ struct TimingInfo
                                        max = 0;
                                }
                                for (const auto &d : dst) {
-                                       auto &v = t.arrival[NameBit(d)];
-                                       v = std::max(v, max);
+                                       auto r = t.arrival.insert(NameBit(d));
+                                       auto &v = r.first->second;
+                                       if (r.second || v.first < max) {
+                                               v.first = max;
+                                               v.second = NameBit(src);
+                                       }
                                }
                        }
                        else if (cell->type == ID($specrule)) {
-                               auto type = cell->getParam(ID::TYPE).decode_string();
-                               if (type != "$setup" && type != "$setuphold")
+                               IdString type = cell->getParam(ID::TYPE).decode_string();
+                               if (type != ID($setup) && type != ID($setuphold))
                                        continue;
                                auto src = cell->getPort(ID::SRC);
-                               auto dst = cell->getPort(ID::DST);
+                               auto dst = cell->getPort(ID::DST).as_bit();
                                for (const auto &c : src.chunks())
-                                       if (!c.wire->port_input)
+                                       if (!c.wire || !c.wire->port_input)
                                                log_error("Module '%s' contains specify cell '%s' where SRC '%s' is not a module input.\n", log_id(module), log_id(cell), log_signal(src));
-                               for (const auto &c : dst.chunks())
-                                       if (!c.wire->port_input)
-                                               log_error("Module '%s' contains specify cell '%s' where DST '%s' is not a module input.\n", log_id(module), log_id(cell), log_signal(dst));
+                               if (!dst.wire || !dst.wire->port_input)
+                                       log_error("Module '%s' contains specify cell '%s' where DST '%s' is not a module input.\n", log_id(module), log_id(cell), log_signal(dst));
                                int max = cell->getParam(ID::T_LIMIT_MAX).as_int();
                                if (max < 0) {
                                        log_warning("Module '%s' contains specify cell '%s' with T_LIMIT_MAX < 0 which is currently unsupported. Clamping to 0.\n", log_id(module), log_id(cell));
                                        max = 0;
                                }
                                for (const auto &s : src) {
-                                       auto &v = t.required[NameBit(s)];
-                                       v = std::max(v, max);
+                                       auto r = t.required.insert(NameBit(s));
+                                       auto &v = r.first->second;
+                                       if (r.second || v.first < max) {
+                                               v.first = max;
+                                               v.second = NameBit(dst);
+                                       }
                                }
                        }
                }
 
+               for (auto port_name : module->ports) {
+                       auto wire = module->wire(port_name);
+                       if (wire->port_input) {
+                               t.has_inputs = true;
+                               break;
+                       }
+               }
+
                return t;
        }
 
index 53bfd40c64ad709e63e189b8e46ae6ddee1d5302..01e052fdf8f6d0142dd274d7c82e7f03ef60122d 100644 (file)
@@ -40,3 +40,4 @@ endif
 OBJS += passes/cmds/scratchpad.o
 OBJS += passes/cmds/logger.o
 OBJS += passes/cmds/printattrs.o
+OBJS += passes/cmds/sta.o
\ No newline at end of file
diff --git a/passes/cmds/sta.cc b/passes/cmds/sta.cc
new file mode 100644 (file)
index 0000000..13e1ee1
--- /dev/null
@@ -0,0 +1,312 @@
+/*
+ *  yosys -- Yosys Open SYnthesis Suite
+ *
+ *  Copyright (C) 2012  Clifford Wolf <clifford@clifford.at>
+ *            (C) 2019  Eddie Hung <eddie@fpgeh.com>
+ *
+ *  Permission to use, copy, modify, and/or distribute this software for any
+ *  purpose with or without fee is hereby granted, provided that the above
+ *  copyright notice and this permission notice appear in all copies.
+ *
+ *  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ *  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ *  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ *  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ *  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ *  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ *  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ */
+
+#include "kernel/yosys.h"
+#include "kernel/sigtools.h"
+#include "kernel/timinginfo.h"
+#include <deque>
+
+USING_YOSYS_NAMESPACE
+PRIVATE_NAMESPACE_BEGIN
+
+struct StaWorker
+{
+       Design *design;
+       Module *module;
+       SigMap sigmap;
+
+       struct t_data {
+               Cell* driver;
+               IdString dst_port, src_port;
+               vector<tuple<SigBit,int,IdString>> fanouts;
+               SigBit backtrack;
+               t_data() : driver(nullptr) {}
+       };
+       dict<SigBit, t_data> data;
+       std::deque<SigBit> queue;
+       struct t_endpoint {
+               Cell *sink;
+               IdString port;
+               int required;
+               t_endpoint() : sink(nullptr), required(0) {}
+       };
+       dict<SigBit, t_endpoint> endpoints;
+
+       int maxarrival;
+       SigBit maxbit;
+
+       pool<SigBit> driven;
+
+       StaWorker(RTLIL::Module *module) : design(module->design), module(module), sigmap(module), maxarrival(0)
+       {
+               TimingInfo timing;
+
+               for (auto cell : module->cells())
+               {
+                       Module *inst_module = design->module(cell->type);
+                       if (!inst_module) {
+                               log_warning("Cell type '%s' not recognised! Ignoring.\n", log_id(cell->type));
+                               continue;
+                       }
+
+                       if (!inst_module->get_blackbox_attribute()) {
+                               log_warning("Cell type '%s' is not a black- nor white-box! Ignoring.\n", log_id(cell->type));
+                               continue;
+                       }
+
+                       IdString derived_type = inst_module->derive(design, cell->parameters);
+                       inst_module = design->module(derived_type);
+                       log_assert(inst_module);
+
+                       if (!timing.count(derived_type)) {
+                               auto &t = timing.setup_module(inst_module);
+                               if (t.has_inputs && t.comb.empty() && t.arrival.empty() && t.required.empty())
+                                       log_warning("Module '%s' has no timing arcs!\n", log_id(cell->type));
+                       }
+
+                       auto &t = timing.at(derived_type);
+                       if (t.comb.empty() && t.arrival.empty() && t.required.empty())
+                               continue;
+
+                       pool<std::pair<SigBit,TimingInfo::NameBit>> src_bits, dst_bits;
+
+                       for (auto &conn : cell->connections()) {
+                               auto rhs = sigmap(conn.second);
+                               for (auto i = 0; i < GetSize(rhs); i++) {
+                                       const auto &bit = rhs[i];
+                                       if (!bit.wire)
+                                               continue;
+                                       TimingInfo::NameBit namebit(conn.first,i);
+                                       if (cell->input(conn.first)) {
+                                               src_bits.insert(std::make_pair(bit,namebit));
+
+                                               auto it = t.required.find(namebit);
+                                               if (it == t.required.end())
+                                                       continue;
+                                               auto r = endpoints.insert(bit);
+                                               if (r.second || r.first->second.required < it->second.first) {
+                                                       r.first->second.sink = cell;
+                                                       r.first->second.port = conn.first;
+                                                       r.first->second.required = it->second.first;
+                                               }
+                                       }
+                                       if (cell->output(conn.first)) {
+                                               dst_bits.insert(std::make_pair(bit,namebit));
+                                               auto &d = data[bit];
+                                               d.driver = cell;
+                                               d.dst_port = conn.first;
+                                               driven.insert(bit);
+
+                                               auto it = t.arrival.find(namebit);
+                                               if (it == t.arrival.end())
+                                                       continue;
+                                               const auto &s = it->second.second;
+                                               if (cell->hasPort(s.name)) {
+                                                       auto s_bit = sigmap(cell->getPort(s.name)[s.offset]);
+                                                       if (s_bit.wire)
+                                                               data[s_bit].fanouts.emplace_back(bit,it->second.first,s.name);
+                                               }
+                                       }
+                               }
+                       }
+
+                       for (const auto &s : src_bits)
+                               for (const auto &d : dst_bits) {
+                                       auto it = t.comb.find(TimingInfo::BitBit(s.second,d.second));
+                                       if (it == t.comb.end())
+                                               continue;
+                                       data[s.first].fanouts.emplace_back(d.first,it->second,s.second.name);
+                               }
+               }
+
+               for (auto port_name : module->ports) {
+                       auto wire = module->wire(port_name);
+                       if (wire->port_input) {
+                               for (const auto &b : sigmap(wire)) {
+                                       queue.emplace_back(b);
+                                       driven.insert(b);
+                               }
+                               // All primary inputs to arrive at time zero
+                               wire->set_intvec_attribute(ID::sta_arrival, std::vector<int>(GetSize(wire), 0));
+                       }
+                       if (wire->port_output)
+                               for (const auto &b : sigmap(wire))
+                                       if (b.wire)
+                                               endpoints.insert(b);
+               }
+       }
+
+       void run()
+       {
+               while (!queue.empty()) {
+                       auto b = queue.front();
+                       queue.pop_front();
+                       auto it = data.find(b);
+                       if (it == data.end())
+                               continue;
+                       const auto& src_arrivals = b.wire->get_intvec_attribute(ID::sta_arrival);
+                       log_assert(GetSize(src_arrivals) == GetSize(b.wire));
+                       auto src_arrival = src_arrivals[b.offset];
+                       for (const auto &d : it->second.fanouts) {
+                               const auto &dst_bit = std::get<0>(d);
+                               auto dst_arrivals = dst_bit.wire->get_intvec_attribute(ID::sta_arrival);
+                               if (dst_arrivals.empty())
+                                       dst_arrivals = std::vector<int>(GetSize(dst_bit.wire), -1);
+                               else
+                                       log_assert(GetSize(dst_arrivals) == GetSize(dst_bit.wire));
+                               auto &dst_arrival = dst_arrivals[dst_bit.offset];
+                               auto new_arrival = src_arrival + std::get<1>(d);
+                               if (dst_arrival < new_arrival) {
+                                       auto dst_wire = dst_bit.wire;
+                                       dst_arrival = std::max(dst_arrival, new_arrival);
+                                       dst_wire->set_intvec_attribute(ID::sta_arrival, dst_arrivals);
+                                       queue.emplace_back(dst_bit);
+
+                                       data[dst_bit].backtrack = b;
+                                       data[dst_bit].src_port = std::get<2>(d);
+
+                                       auto it = endpoints.find(dst_bit);
+                                       if (it != endpoints.end())
+                                               new_arrival += it->second.required;
+                                       if (new_arrival > maxarrival && driven.count(b)) {
+                                               maxarrival = new_arrival;
+                                               maxbit = dst_bit;
+                                       }
+                               }
+                       }
+               }
+
+               auto b = maxbit;
+               if (b == SigBit()) {
+                       log("No timing paths found.\n");
+                       return;
+               }
+
+               log("Latest arrival time in '%s' is %d:\n", log_id(module), maxarrival);
+               auto it = endpoints.find(maxbit);
+               if (it != endpoints.end() && it->second.sink)
+                       log("  %6d %s (%s.%s)\n", maxarrival, log_id(it->second.sink), log_id(it->second.sink->type), log_id(it->second.port));
+               else {
+                       log("  %6d (%s)\n", maxarrival, b.wire->port_output ? "<primary output>" : "<unknown>");
+                       if (!b.wire->port_output)
+                               log_warning("Critical-path does not terminate in a recognised endpoint.\n");
+               }
+               auto jt = data.find(b);
+               while (jt != data.end()) {
+                       int arrival = b.wire->get_intvec_attribute(ID::sta_arrival)[b.offset];
+                       if (jt->second.driver) {
+                               log("           %s\n", log_signal(b));
+                               log("  %6d %s (%s.%s->%s)\n", arrival, log_id(jt->second.driver), log_id(jt->second.driver->type), log_id(jt->second.src_port), log_id(jt->second.dst_port));
+                       }
+                       else if (b.wire->port_input)
+                               log("  %6d   %s (%s)\n", arrival, log_signal(b), "<primary input>");
+                       else
+                               log_abort();
+                       b = jt->second.backtrack;
+                       jt = data.find(b);
+               }
+
+               std::map<int, unsigned> arrival_histogram;
+               for (const auto &i : endpoints) {
+                       const auto &b = i.first;
+                       if (!driven.count(b))
+                               continue;
+
+                       if (!b.wire->attributes.count(ID::sta_arrival)) {
+                               log_warning("Endpoint %s.%s has no (* sta_arrival *) value.\n", log_id(module), log_signal(b));
+                               continue;
+                       }
+
+                       auto arrival = b.wire->get_intvec_attribute(ID::sta_arrival)[b.offset];
+                       if (arrival < 0) {
+                               log_warning("Endpoint %s.%s has no (* sta_arrival *) value.\n", log_id(module), log_signal(b));
+                               continue;
+                       }
+                       arrival += i.second.required;
+                       arrival_histogram[arrival]++;
+               }
+               // Adapted from https://github.com/YosysHQ/nextpnr/blob/affb12cc27ebf409eade062c4c59bb98569d8147/common/timing.cc#L946-L969
+               if (arrival_histogram.size() > 0) {
+                       unsigned num_bins = 20;
+                       unsigned bar_width = 60;
+                       auto min_arrival = arrival_histogram.begin()->first;
+                       auto max_arrival = arrival_histogram.rbegin()->first;
+                       auto bin_size = std::max<unsigned>(1, ceil((max_arrival - min_arrival + 1) / float(num_bins)));
+                       std::vector<unsigned> bins(num_bins);
+                       unsigned max_freq = 0;
+                       for (const auto &i : arrival_histogram) {
+                               auto &bin = bins[(i.first - min_arrival) / bin_size];
+                               bin += i.second;
+                               max_freq = std::max(max_freq, bin);
+                       }
+                       bar_width = std::min(bar_width, max_freq);
+
+                       log("\n");
+                       log("Arrival histogram:\n");
+                       log(" legend: * represents %d endpoint(s)\n", max_freq / bar_width);
+                       log("         + represents [1,%d) endpoint(s)\n", max_freq / bar_width);
+                       for (int i = num_bins-1; i >= 0; --i)
+                               log("(%6d, %6d] |%s%c\n", min_arrival + bin_size * (i + 1), min_arrival + bin_size * i,
+                                               std::string(bins[i] * bar_width / max_freq, '*').c_str(),
+                                               (bins[i] * bar_width) % max_freq > 0 ? '+' : ' ');
+               }
+       }
+};
+
+struct StaPass : public Pass {
+       StaPass() : Pass("sta", "perform static timing analysis") { }
+       void help() override
+       {
+               //   |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
+               log("\n");
+               log("    sta [options] [selection]\n");
+               log("\n");
+               log("This command performs static timing analysis on the design. (Only considers\n");
+               log("paths within a single module, so the design must be flattened.)\n");
+               log("\n");
+       }
+       void execute(std::vector<std::string> args, RTLIL::Design *design) override
+       {
+               log_header(design, "Executing STA pass (static timing analysis).\n");
+
+               /*
+               size_t argidx;
+               for (argidx = 1; argidx < args.size(); argidx++) {
+                       if (args[argidx] == "-TODO") {
+                               continue;
+                       }
+                       break;
+               }
+               */
+
+               extra_args(args, 1, design);
+
+               for (Module *module : design->selected_modules())
+               {
+                       if (module->has_processes_warn())
+                               continue;
+
+                       StaWorker worker(module);
+                       worker.run();
+               }
+       }
+} StaPass;
+
+PRIVATE_NAMESPACE_END
index 7a6959971980faa4d136acf5266974697516fae5..29fe74ec740e013f46e2ae048204df79f004948d 100644 (file)
@@ -648,40 +648,38 @@ void prep_delays(RTLIL::Design *design, bool dff_mode)
                auto inst_module = design->module(cell->type);
                log_assert(inst_module);
 
-               auto &t = timing.at(cell->type).required;
-               for (auto &conn : cell->connections_) {
-                       auto port_wire = inst_module->wire(conn.first);
+               for (auto &i : timing.at(cell->type).required) {
+                       auto port_wire = inst_module->wire(i.first.name);
                        if (!port_wire)
                                log_error("Port %s in cell %s (type %s) from module %s does not actually exist",
-                                               log_id(conn.first), log_id(cell), log_id(cell->type), log_id(module));
-                       if (!port_wire->port_input)
-                               continue;
-                       if (conn.second.is_fully_const())
+                                               log_id(i.first.name), log_id(cell), log_id(cell->type), log_id(module));
+                       log_assert(port_wire->port_input);
+
+                       auto d = i.second.first;
+                       if (d == 0)
                                continue;
 
-                       SigSpec O = module->addWire(NEW_ID, GetSize(conn.second));
-                       for (int i = 0; i < GetSize(conn.second); i++) {
-                               auto d = t.at(TimingInfo::NameBit(conn.first,i), 0);
-                               if (d == 0)
-                                       continue;
+                       auto offset = i.first.offset;
+                       auto O = module->addWire(NEW_ID);
+                       auto rhs = cell->getPort(i.first.name);
 
 #ifndef NDEBUG
-                               if (ys_debug(1)) {
-                                       static std::set<std::tuple<IdString,IdString,int>> seen;
-                                       if (seen.emplace(cell->type, conn.first, i).second) log("%s.%s[%d] abc9_required = %d\n",
-                                                       log_id(cell->type), log_id(conn.first), i, d);
-                               }
+                       if (ys_debug(1)) {
+                               static pool<std::pair<IdString,TimingInfo::NameBit>> seen;
+                               if (seen.emplace(cell->type, i.first).second) log("%s.%s[%d] abc9_required = %d\n",
+                                               log_id(cell->type), log_id(i.first.name), offset, d);
+                       }
 #endif
-                               auto r = box_cache.insert(d);
-                               if (r.second) {
-                                       r.first->second = delay_module->derive(design, {{ID::DELAY, d}});
-                                       log_assert(r.first->second.begins_with("$paramod$__ABC9_DELAY\\DELAY="));
-                               }
-                               auto box = module->addCell(NEW_ID, r.first->second);
-                               box->setPort(ID::I, conn.second[i]);
-                               box->setPort(ID::O, O[i]);
-                               conn.second[i] = O[i];
+                       auto r = box_cache.insert(d);
+                       if (r.second) {
+                               r.first->second = delay_module->derive(design, {{ID::DELAY, d}});
+                               log_assert(r.first->second.begins_with("$paramod$__ABC9_DELAY\\DELAY="));
                        }
+                       auto box = module->addCell(NEW_ID, r.first->second);
+                       box->setPort(ID::I, rhs[offset]);
+                       box->setPort(ID::O, O);
+                       rhs[offset] = O;
+                       cell->setPort(i.first.name, rhs);
                }
        }
 }
@@ -1006,16 +1004,16 @@ void prep_box(RTLIL::Design *design)
                                log_assert(GetSize(wire) == 1);
                                auto it = t.find(TimingInfo::NameBit(port_name,0));
                                if (it == t.end())
-                                       // Assume no connectivity if no setup time
-                                       ss << "-";
+                                       // Assume that no setup time means zero
+                                       ss << 0;
                                else {
-                                       ss << it->second;
+                                       ss << it->second.first;
 
 #ifndef NDEBUG
                                        if (ys_debug(1)) {
                                                static std::set<std::pair<IdString,IdString>> seen;
                                                if (seen.emplace(module->name, port_name).second) log("%s.%s abc9_required = %d\n", log_id(module),
-                                                               log_id(port_name), it->second);
+                                                               log_id(port_name), it->second.first);
                                        }
 #endif
                                }
diff --git a/tests/various/sta.ys b/tests/various/sta.ys
new file mode 100644 (file)
index 0000000..156c31c
--- /dev/null
@@ -0,0 +1,81 @@
+read_verilog -specify <<EOT
+module buffer(input i, output o);
+specify
+(i => o) = 10;
+endspecify
+endmodule
+
+module top(input i);
+wire w;
+buffer b(.i(i), .o(w));
+endmodule
+EOT
+
+logger -expect warning "Critical-path does not terminate in a recognised endpoint\." 1
+sta
+
+
+design -reset
+read_verilog -specify <<EOT
+module top(input i, output o, p);
+assign o = i;
+endmodule
+EOT
+
+logger -expect log "No timing paths found\." 1
+sta
+
+
+design -reset
+read_verilog -specify <<EOT
+module buffer(input i, output o);
+specify
+(i => o) = 10;
+endspecify
+endmodule
+
+module top(input i, output o, p);
+buffer b(.i(i), .o(o));
+endmodule
+EOT
+
+sta
+
+
+design -reset
+read_verilog -specify <<EOT
+module buffer(input i, output o);
+specify
+(i => o) = 10;
+endspecify
+endmodule
+
+module top(input i, output o, p);
+buffer b(.i(i), .o(o));
+const0 c(.o(p));
+endmodule
+EOT
+
+logger -expect warning "Cell type 'const0' not recognised! Ignoring\." 1
+sta
+
+
+design -reset
+read_verilog -specify <<EOT
+module buffer(input i, output o);
+specify
+(i => o) = 10;
+endspecify
+endmodule
+module const0(output o);
+endmodule
+
+module top(input i, output o, p);
+buffer b(.i(i), .o(o));
+const0 c(.o(p));
+endmodule
+EOT
+
+sta
+
+logger -expect-no-warnings