Merge pull request #345 from YosysHQ/dave/sdf

Improve handling of top level IO and add SDF support
diff --git a/common/command.cc b/common/command.cc
index ad5b6c5..fd31078 100644
--- a/common/command.cc
+++ b/common/command.cc
@@ -149,6 +149,9 @@
     general.add_options()("freq", po::value<double>(), "set target frequency for design in MHz");
     general.add_options()("timing-allow-fail", "allow timing to fail in design");
     general.add_options()("no-tmdriv", "disable timing-driven placement");
+    general.add_options()("sdf", po::value<std::string>(), "SDF delay back-annotation file to write");
+    general.add_options()("sdf-cvc", "enable tweaks for SDF file compatibility with the CVC simulator");
+
     return general;
 }
 
@@ -336,6 +339,14 @@
             log_error("Saving design failed.\n");
     }
 
+    if (vm.count("sdf")) {
+        std::string filename = vm["sdf"].as<std::string>();
+        std::ofstream f(filename);
+        if (!f)
+            log_error("Failed to open SDF file '%s' for writing.\n", filename.c_str());
+        ctx->writeSDF(f, vm.count("sdf-cvc"));
+    }
+
 #ifndef NO_PYTHON
     deinit_python();
 #endif
diff --git a/common/design_utils.cc b/common/design_utils.cc
index bdf5ca5..10212a0 100644
--- a/common/design_utils.cc
+++ b/common/design_utils.cc
@@ -88,7 +88,7 @@
         NPNR_ASSERT(net->driver.cell == nullptr);
         net->driver.cell = cell;
         net->driver.port = port_name;
-    } else if (port.type == PORT_IN) {
+    } else if (port.type == PORT_IN || port.type == PORT_INOUT) {
         PortRef user;
         user.cell = cell;
         user.port = port_name;
@@ -146,4 +146,14 @@
     cell->ports[new_name] = pi;
 }
 
+void rename_net(Context *ctx, NetInfo *net, IdString new_name)
+{
+    if (net == nullptr)
+        return;
+    NPNR_ASSERT(!ctx->nets.count(new_name));
+    std::swap(ctx->nets[net->name], ctx->nets[new_name]);
+    ctx->nets.erase(net->name);
+    net->name = new_name;
+}
+
 NEXTPNR_NAMESPACE_END
diff --git a/common/design_utils.h b/common/design_utils.h
index 3eb9024..1ae1d64 100644
--- a/common/design_utils.h
+++ b/common/design_utils.h
@@ -94,6 +94,9 @@
 // Rename a port if it exists on a cell
 void rename_port(Context *ctx, CellInfo *cell, IdString old_name, IdString new_name);
 
+// Rename a net without invalidating pointers to it
+void rename_net(Context *ctx, NetInfo *net, IdString new_name);
+
 void print_utilisation(const Context *ctx);
 
 NEXTPNR_NAMESPACE_END
diff --git a/common/nextpnr.h b/common/nextpnr.h
index bae828f..24f6948 100644
--- a/common/nextpnr.h
+++ b/common/nextpnr.h
@@ -808,6 +808,11 @@
 
     // --------------------------------------------------------------
 
+    // provided by sdf.cc
+    void writeSDF(std::ostream &out, bool cvc_mode = false) const;
+
+    // --------------------------------------------------------------
+
     uint32_t checksum() const;
 
     void check() const;
diff --git a/common/sdf.cc b/common/sdf.cc
new file mode 100644
index 0000000..b960690
--- /dev/null
+++ b/common/sdf.cc
@@ -0,0 +1,334 @@
+/*
+ *  nextpnr -- Next Generation Place and Route
+ *
+ *  Copyright (C) 2019  David Shah <dave@ds0.me>
+ *
+ *  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 "nextpnr.h"
+#include "util.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+namespace SDF {
+
+struct MinMaxTyp
+{
+    double min, typ, max;
+};
+
+struct RiseFallDelay
+{
+    MinMaxTyp rise, fall;
+};
+
+struct PortAndEdge
+{
+    std::string port;
+    ClockEdge edge;
+};
+
+struct IOPath
+{
+    std::string from, to;
+    RiseFallDelay delay;
+};
+
+struct TimingCheck
+{
+    enum CheckType
+    {
+        SETUPHOLD,
+        PERIOD,
+        WIDTH
+    } type;
+    PortAndEdge from, to;
+    RiseFallDelay delay;
+};
+
+struct Cell
+{
+    std::string celltype, instance;
+    std::vector<IOPath> iopaths;
+    std::vector<TimingCheck> checks;
+};
+
+struct CellPort
+{
+    std::string cell, port;
+};
+
+struct Interconnect
+{
+    CellPort from, to;
+    RiseFallDelay delay;
+};
+
+struct SDFWriter
+{
+    bool cvc_mode = false;
+    std::vector<Cell> cells;
+    std::vector<Interconnect> conn;
+    std::string sdfversion, design, vendor, program;
+
+    std::string format_name(const std::string &name)
+    {
+        std::string fmt = "\"";
+        for (char c : name) {
+            if (c == '\\' || c == '\"')
+                fmt += "\"";
+            fmt += c;
+        }
+        fmt += "\"";
+        return fmt;
+    }
+
+    std::string escape_name(const std::string &name)
+    {
+        std::string esc;
+        for (char c : name) {
+            if (c == '$' || c == '\\' || c == '[' || c == ']' || c == ':' || (cvc_mode && c == '.'))
+                esc += '\\';
+            esc += c;
+        }
+        return esc;
+    }
+
+    std::string timing_check_name(TimingCheck::CheckType type)
+    {
+        switch (type) {
+        case TimingCheck::SETUPHOLD:
+            return "SETUPHOLD";
+        case TimingCheck::PERIOD:
+            return "PERIOD";
+        case TimingCheck::WIDTH:
+            return "WIDTH";
+        default:
+            NPNR_ASSERT_FALSE("unknown timing check type");
+        }
+    }
+
+    void write_delay(std::ostream &out, const RiseFallDelay &delay)
+    {
+        write_delay(out, delay.rise);
+        out << " ";
+        write_delay(out, delay.fall);
+    }
+
+    void write_delay(std::ostream &out, const MinMaxTyp &delay)
+    {
+        if (cvc_mode)
+            out << "(" << int(delay.min) << ":" << int(delay.typ) << ":" << int(delay.max) << ")";
+        else
+            out << "(" << delay.min << ":" << delay.typ << ":" << delay.max << ")";
+    }
+
+    void write_port(std::ostream &out, const CellPort &port)
+    {
+        if (cvc_mode)
+            out << escape_name(port.cell) + "." + escape_name(port.port);
+        else
+            out << escape_name(port.cell + "/" + port.port);
+    }
+
+    void write_portedge(std::ostream &out, const PortAndEdge &pe)
+    {
+        out << "(" << (pe.edge == RISING_EDGE ? "posedge" : "negedge") << " " << escape_name(pe.port) << ")";
+    }
+
+    void write(std::ostream &out)
+    {
+        out << "(DELAYFILE" << std::endl;
+        // Headers and  metadata
+        out << "  (SDFVERSION " << format_name(sdfversion) << ")" << std::endl;
+        out << "  (DESIGN " << format_name(design) << ")" << std::endl;
+        out << "  (VENDOR " << format_name(vendor) << ")" << std::endl;
+        out << "  (PROGRAM " << format_name(program) << ")" << std::endl;
+        out << "  (DIVIDER " << (cvc_mode ? "." : "/") << ")" << std::endl;
+        out << "  (TIMESCALE 1ps)" << std::endl;
+        // Write interconnect delays, with the main design begin a "cell"
+        out << "  (CELL" << std::endl;
+        out << "    (CELLTYPE " << format_name(design) << ")" << std::endl;
+        out << "    (INSTANCE )" << std::endl;
+        out << "    (DELAY" << std::endl;
+        out << "      (ABSOLUTE" << std::endl;
+        for (auto &ic : conn) {
+            out << "        (INTERCONNECT ";
+            write_port(out, ic.from);
+            out << " ";
+            write_port(out, ic.to);
+            out << " ";
+            write_delay(out, ic.delay);
+            out << ")" << std::endl;
+        }
+        out << "      )" << std::endl;
+        out << "    )" << std::endl;
+        out << "  )" << std::endl;
+        // Write cells
+        for (auto &cell : cells) {
+            out << "  (CELL" << std::endl;
+            out << "    (CELLTYPE " << format_name(cell.celltype) << ")" << std::endl;
+            out << "    (INSTANCE " << escape_name(cell.instance) << ")" << std::endl;
+            // IOPATHs (combinational delay and clock-to-q)
+            if (!cell.iopaths.empty()) {
+                out << "    (DELAY" << std::endl;
+                out << "      (ABSOLUTE" << std::endl;
+                for (auto &path : cell.iopaths) {
+                    out << "        (IOPATH " << escape_name(path.from) << " " << escape_name(path.to) << " ";
+                    write_delay(out, path.delay);
+                    out << ")" << std::endl;
+                }
+                out << "      )" << std::endl;
+                out << "    )" << std::endl;
+            }
+            // Timing Checks (setup/hold, period, width)
+            if (!cell.checks.empty()) {
+                out << "    (TIMINGCHECK" << std::endl;
+                for (auto &check : cell.checks) {
+                    out << "      (" << timing_check_name(check.type) << " ";
+                    write_portedge(out, check.from);
+                    out << " ";
+                    if (check.type == TimingCheck::SETUPHOLD) {
+                        write_portedge(out, check.to);
+                        out << " ";
+                    }
+                    if (check.type == TimingCheck::SETUPHOLD)
+                        write_delay(out, check.delay);
+                    else
+                        write_delay(out, check.delay.rise);
+                    out << ")" << std::endl;
+                }
+                out << "    )" << std::endl;
+            }
+            out << "    )" << std::endl;
+        }
+        out << ")" << std::endl;
+    }
+};
+
+} // namespace SDF
+
+void Context::writeSDF(std::ostream &out, bool cvc_mode) const
+{
+    using namespace SDF;
+    SDFWriter wr;
+    wr.cvc_mode = cvc_mode;
+    wr.design = str_or_default(attrs, id("module"), "top");
+    wr.sdfversion = "3.0";
+    wr.vendor = "nextpnr";
+    wr.program = "nextpnr";
+
+    const double delay_scale = 1000;
+    // Convert from DelayInfo to SDF-friendly RiseFallDelay
+    auto convert_delay = [&](const DelayInfo &dly) {
+        RiseFallDelay rf;
+        rf.rise.min = getDelayNS(dly.minRaiseDelay()) * delay_scale;
+        rf.rise.typ = getDelayNS((dly.minRaiseDelay() + dly.maxRaiseDelay()) / 2) * delay_scale; // fixme: typ delays?
+        rf.rise.max = getDelayNS(dly.maxRaiseDelay()) * delay_scale;
+        rf.fall.min = getDelayNS(dly.minFallDelay()) * delay_scale;
+        rf.fall.typ = getDelayNS((dly.minFallDelay() + dly.maxFallDelay()) / 2) * delay_scale; // fixme: typ delays?
+        rf.fall.max = getDelayNS(dly.maxFallDelay()) * delay_scale;
+        return rf;
+    };
+
+    auto convert_setuphold = [&](const DelayInfo &setup, const DelayInfo &hold) {
+        RiseFallDelay rf;
+        rf.rise.min = getDelayNS(setup.minDelay()) * delay_scale;
+        rf.rise.typ = getDelayNS((setup.minDelay() + setup.maxDelay()) / 2) * delay_scale; // fixme: typ delays?
+        rf.rise.max = getDelayNS(setup.maxDelay()) * delay_scale;
+        rf.fall.min = getDelayNS(hold.minDelay()) * delay_scale;
+        rf.fall.typ = getDelayNS((hold.minDelay() + hold.maxDelay()) / 2) * delay_scale; // fixme: typ delays?
+        rf.fall.max = getDelayNS(hold.maxDelay()) * delay_scale;
+        return rf;
+    };
+
+    for (auto cell : sorted(cells)) {
+        Cell sc;
+        const CellInfo *ci = cell.second;
+        sc.instance = ci->name.str(this);
+        sc.celltype = ci->type.str(this);
+        for (auto port : ci->ports) {
+            int clockCount = 0;
+            TimingPortClass cls = getPortTimingClass(ci, port.first, clockCount);
+            if (cls == TMG_IGNORE)
+                continue;
+            if (port.second.net == nullptr)
+                continue; // Ignore disconnected ports
+            if (port.second.type != PORT_IN) {
+                // Add combinational paths to this output (or inout)
+                for (auto other : ci->ports) {
+                    if (other.second.net == nullptr)
+                        continue;
+                    if (other.second.type == PORT_OUT)
+                        continue;
+                    DelayInfo dly;
+                    if (!getCellDelay(ci, other.first, port.first, dly))
+                        continue;
+                    IOPath iop;
+                    iop.from = other.first.str(this);
+                    iop.to = port.first.str(this);
+                    iop.delay = convert_delay(dly);
+                    sc.iopaths.push_back(iop);
+                }
+                // Add clock-to-output delays, also as IOPaths
+                if (cls == TMG_REGISTER_OUTPUT)
+                    for (int i = 0; i < clockCount; i++) {
+                        auto clkInfo = getPortClockingInfo(ci, port.first, i);
+                        IOPath cqp;
+                        cqp.from = clkInfo.clock_port.str(this);
+                        cqp.to = port.first.str(this);
+                        cqp.delay = convert_delay(clkInfo.clockToQ);
+                        sc.iopaths.push_back(cqp);
+                    }
+            }
+            if (port.second.type != PORT_OUT && cls == TMG_REGISTER_INPUT) {
+                // Add setup/hold checks
+                for (int i = 0; i < clockCount; i++) {
+                    auto clkInfo = getPortClockingInfo(ci, port.first, i);
+                    TimingCheck chk;
+                    chk.from.edge = RISING_EDGE; // Add setup/hold checks equally for rising and falling edges
+                    chk.from.port = port.first.str(this);
+                    chk.to.edge = clkInfo.edge;
+                    chk.to.port = clkInfo.clock_port.str(this);
+                    chk.type = TimingCheck::SETUPHOLD;
+                    chk.delay = convert_setuphold(clkInfo.setup, clkInfo.hold);
+                    sc.checks.push_back(chk);
+                    chk.from.edge = FALLING_EDGE;
+                    sc.checks.push_back(chk);
+                }
+            }
+        }
+        wr.cells.push_back(sc);
+    }
+
+    for (auto net : sorted(nets)) {
+        NetInfo *ni = net.second;
+        if (ni->driver.cell == nullptr)
+            continue;
+        for (auto &usr : ni->users) {
+            Interconnect ic;
+            ic.from.cell = ni->driver.cell->name.str(this);
+            ic.from.port = ni->driver.port.str(this);
+            ic.to.cell = usr.cell->name.str(this);
+            ic.to.port = usr.port.str(this);
+            // FIXME: min/max routing delay - or at least constructing DelayInfo here
+            ic.delay = convert_delay(getDelayFromNS(getDelayNS(getNetinfoRouteDelay(ni, usr))));
+            wr.conn.push_back(ic);
+        }
+    }
+    wr.write(out);
+}
+
+NEXTPNR_NAMESPACE_END
\ No newline at end of file
diff --git a/ecp5/cells.cc b/ecp5/cells.cc
index 37b6ac8..9accdd5 100644
--- a/ecp5/cells.cc
+++ b/ecp5/cells.cc
@@ -416,7 +416,25 @@
     } else {
         NPNR_ASSERT(false);
     }
-    NetInfo *donet = trio->ports.at(ctx->id("I")).net;
+    NetInfo *donet = trio->ports.at(ctx->id("I")).net, *dinet = trio->ports.at(ctx->id("O")).net;
+
+    // Rename I/O nets to avoid conflicts
+    if (donet != nullptr && donet->name == nxio->name)
+        rename_net(ctx, donet, ctx->id(donet->name.str(ctx) + "$TRELLIS_IO_OUT"));
+    if (dinet != nullptr && dinet->name == nxio->name)
+        rename_net(ctx, dinet, ctx->id(dinet->name.str(ctx) + "$TRELLIS_IO_IN"));
+
+    // Create a new top port net for accurate IO timing analysis and simulation netlists
+    if (ctx->ports.count(nxio->name)) {
+        IdString tn_netname = nxio->name;
+        NPNR_ASSERT(!ctx->nets.count(tn_netname));
+        std::unique_ptr<NetInfo> toplevel_net{new NetInfo};
+        toplevel_net->name = tn_netname;
+        connect_port(ctx, toplevel_net.get(), trio, ctx->id("B"));
+        ctx->ports[nxio->name].net = toplevel_net.get();
+        ctx->nets[tn_netname] = std::move(toplevel_net);
+    }
+
     CellInfo *tbuf = net_driven_by(
             ctx, donet, [](const Context *ctx, const CellInfo *cell) { return cell->type == ctx->id("$_TBUF_"); },
             ctx->id("Y"));
diff --git a/ecp5/pack.cc b/ecp5/pack.cc
index 166ed18..c498d20 100644
--- a/ecp5/pack.cc
+++ b/ecp5/pack.cc
@@ -362,8 +362,7 @@
                     for (auto &port : ci->ports)
                         disconnect_port(ctx, ci, port.first);
                 } else if (trio != nullptr) {
-                    // Trivial case, TRELLIS_IO used. Just destroy the net and the
-                    // iobuf
+                    // Trivial case, TRELLIS_IO used. Just remove the IOBUF
                     log_info("%s feeds TRELLIS_IO %s, removing %s %s.\n", ci->name.c_str(ctx), trio->name.c_str(ctx),
                              ci->type.c_str(ctx), ci->name.c_str(ctx));
 
@@ -384,14 +383,6 @@
                                 std::swap(net->clkconstr, onet->clkconstr);
                             }
                         }
-                        ctx->nets.erase(net->name);
-                        trio->ports.at(ctx->id("B")).net = nullptr;
-                    }
-                    if (ci->type == ctx->id("$nextpnr_iobuf")) {
-                        NetInfo *net2 = ci->ports.at(ctx->id("I")).net;
-                        if (net2 != nullptr) {
-                            ctx->nets.erase(net2->name);
-                        }
                     }
                 } else if (drives_top_port(ionet, tp)) {
                     log_info("%s feeds %s %s.%s, removing %s %s.\n", ci->name.c_str(ctx), tp.cell->type.c_str(ctx),
@@ -414,7 +405,8 @@
                     new_cells.push_back(std::move(tr_cell));
                     trio = new_cells.back().get();
                 }
-
+                for (auto port : ci->ports)
+                    disconnect_port(ctx, ci, port.first);
                 packed_cells.insert(ci->name);
                 if (trio != nullptr) {
                     for (const auto &attr : ci->attrs)
diff --git a/ice40/cells.cc b/ice40/cells.cc
index c4e93d5..3def82b 100644
--- a/ice40/cells.cc
+++ b/ice40/cells.cc
@@ -69,7 +69,7 @@
         new_cell->params[ctx->id("PIN_TYPE")] = Property(0, 6);
         new_cell->params[ctx->id("PULLUP")] = Property::State::S0;
         new_cell->params[ctx->id("NEG_TRIGGER")] = Property::State::S0;
-        new_cell->params[ctx->id("IOSTANDARD")] = Property("SB_LVCMOS");
+        new_cell->params[ctx->id("IO_STANDARD")] = Property("SB_LVCMOS");
 
         add_port(ctx, new_cell.get(), "PACKAGE_PIN", PORT_INOUT);
 
@@ -426,7 +426,25 @@
     } else {
         NPNR_ASSERT(false);
     }
-    NetInfo *donet = sbio->ports.at(ctx->id("D_OUT_0")).net;
+    NetInfo *donet = sbio->ports.at(ctx->id("D_OUT_0")).net, *dinet = sbio->ports.at(ctx->id("D_IN_0")).net;
+
+    // Rename I/O nets to avoid conflicts
+    if (donet != nullptr && donet->name == nxio->name)
+        rename_net(ctx, donet, ctx->id(donet->name.str(ctx) + "$SB_IO_OUT"));
+    if (dinet != nullptr && dinet->name == nxio->name)
+        rename_net(ctx, dinet, ctx->id(dinet->name.str(ctx) + "$SB_IO_IN"));
+
+    // Create a new top port net for accurate IO timing analysis and simulation netlists
+    if (ctx->ports.count(nxio->name)) {
+        IdString tn_netname = nxio->name;
+        NPNR_ASSERT(!ctx->nets.count(tn_netname));
+        std::unique_ptr<NetInfo> toplevel_net{new NetInfo};
+        toplevel_net->name = tn_netname;
+        connect_port(ctx, toplevel_net.get(), sbio, ctx->id("PACKAGE_PIN"));
+        ctx->ports[nxio->name].net = toplevel_net.get();
+        ctx->nets[tn_netname] = std::move(toplevel_net);
+    }
+
     CellInfo *tbuf = net_driven_by(
             ctx, donet, [](const Context *ctx, const CellInfo *cell) { return cell->type == ctx->id("$_TBUF_"); },
             ctx->id("Y"));
diff --git a/ice40/pack.cc b/ice40/pack.cc
index a1eba41..90c6de3 100644
--- a/ice40/pack.cc
+++ b/ice40/pack.cc
@@ -459,7 +459,6 @@
 {
     std::unordered_set<IdString> packed_cells;
     std::unordered_set<IdString> delete_nets;
-
     std::vector<std::unique_ptr<CellInfo>> new_cells;
     log_info("Packing IOs..\n");
 
@@ -478,8 +477,7 @@
                     rgb = net->driver.cell;
             }
             if (sb != nullptr) {
-                // Trivial case, SB_IO used. Just destroy the net and the
-                // iobuf
+                // Trivial case, SB_IO used. Just destroy the iobuf
                 log_info("%s feeds SB_IO %s, removing %s %s.\n", ci->name.c_str(ctx), sb->name.c_str(ctx),
                          ci->type.c_str(ctx), ci->name.c_str(ctx));
                 NetInfo *net = sb->ports.at(ctx->id("PACKAGE_PIN")).net;
@@ -490,7 +488,6 @@
                               sb->type.c_str(ctx), sb->name.c_str(ctx));
 
                 if (net != nullptr) {
-
                     if (net->clkconstr != nullptr) {
                         if (sb->ports.count(id_D_IN_0)) {
                             NetInfo *din0_net = sb->ports.at(id_D_IN_0).net;
@@ -509,15 +506,6 @@
                             }
                         }
                     }
-
-                    delete_nets.insert(net->name);
-                    sb->ports.at(ctx->id("PACKAGE_PIN")).net = nullptr;
-                }
-                if (ci->type == ctx->id("$nextpnr_iobuf")) {
-                    NetInfo *net2 = ci->ports.at(ctx->id("I")).net;
-                    if (net2 != nullptr) {
-                        delete_nets.insert(net2->name);
-                    }
                 }
             } else if (rgb != nullptr) {
                 log_info("%s use by SB_RGBA_DRV/SB_RGB_DRV %s, not creating SB_IO\n", ci->name.c_str(ctx),
@@ -533,6 +521,8 @@
                 new_cells.push_back(std::move(ice_cell));
                 sb = new_cells.back().get();
             }
+            for (auto port : ci->ports)
+                disconnect_port(ctx, ci, port.first);
             packed_cells.insert(ci->name);
             std::copy(ci->attrs.begin(), ci->attrs.end(), std::inserter(sb->attrs, sb->attrs.begin()));
         } else if (is_sb_io(ctx, ci) || is_sb_gb_io(ctx, ci)) {