diff options
37 files changed, 1490 insertions, 152 deletions
diff --git a/common/chain_utils.h b/common/chain_utils.h new file mode 100644 index 00000000..b783e30b --- /dev/null +++ b/common/chain_utils.h @@ -0,0 +1,68 @@ +/* + *  nextpnr -- Next Generation Place and Route + * + *  Copyright (C) 2018  David Shah <david@symbioticeda.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. + * + */ + +#ifndef CHAIN_UTILS_H +#define CHAIN_UTILS_H + +#include "nextpnr.h" +#include "util.h" + +NEXTPNR_NAMESPACE_BEGIN + +struct CellChain +{ +    std::vector<CellInfo *> cells; +}; + +// Generic chain finder +template <typename F1, typename F2, typename F3> +std::vector<CellChain> find_chains(const Context *ctx, F1 cell_type_predicate, F2 get_previous, F3 get_next, +                                   size_t min_length = 2) +{ +    std::set<IdString> chained; +    std::vector<CellChain> chains; +    for (auto cell : sorted(ctx->cells)) { +        if (chained.find(cell.first) != chained.end()) +            continue; +        CellInfo *ci = cell.second; +        if (cell_type_predicate(ctx, ci)) { +            CellInfo *start = ci; +            CellInfo *prev_start = ci; +            while (prev_start != nullptr) { +                start = prev_start; +                prev_start = get_previous(ctx, start); +            } +            CellChain chain; +            CellInfo *end = start; +            while (end != nullptr) { +                chain.cells.push_back(end); +                end = get_next(ctx, end); +            } +            if (chain.cells.size() >= min_length) { +                chains.push_back(chain); +                for (auto c : chain.cells) +                    chained.insert(c->name); +            } +        } +    } +    return chains; +} + +NEXTPNR_NAMESPACE_END +#endif diff --git a/common/design_utils.cc b/common/design_utils.cc index 21c9dcc4..a0b87764 100644 --- a/common/design_utils.cc +++ b/common/design_utils.cc @@ -19,6 +19,7 @@   */  #include "design_utils.h" +#include <algorithm>  #include <map>  #include "log.h"  #include "util.h" @@ -73,4 +74,55 @@ void print_utilisation(const Context *ctx)      log_break();  } +// Connect a net to a port +void connect_port(const Context *ctx, NetInfo *net, CellInfo *cell, IdString port_name) +{ +    if (net == nullptr) +        return; +    PortInfo &port = cell->ports.at(port_name); +    NPNR_ASSERT(port.net == nullptr); +    port.net = net; +    if (port.type == PORT_OUT) { +        NPNR_ASSERT(net->driver.cell == nullptr); +        net->driver.cell = cell; +        net->driver.port = port_name; +    } else if (port.type == PORT_IN) { +        PortRef user; +        user.cell = cell; +        user.port = port_name; +        net->users.push_back(user); +    } else { +        NPNR_ASSERT_FALSE("invalid port type for connect_port"); +    } +} + +void disconnect_port(const Context *ctx, CellInfo *cell, IdString port_name) +{ +    if (!cell->ports.count(port_name)) +        return; +    PortInfo &port = cell->ports.at(port_name); +    if (port.net != nullptr) { +        port.net->users.erase(std::remove_if(port.net->users.begin(), port.net->users.end(), +                                             [cell, port_name](const PortRef &user) { +                                                 return user.cell == cell && user.port == port_name; +                                             }), +                              port.net->users.end()); +    } +} + +void connect_ports(Context *ctx, CellInfo *cell1, IdString port1_name, CellInfo *cell2, IdString port2_name) +{ +    PortInfo &port1 = cell1->ports.at(port1_name); +    if (port1.net == nullptr) { +        // No net on port1; need to create one +        std::unique_ptr<NetInfo> p1net(new NetInfo()); +        p1net->name = ctx->id(cell1->name.str(ctx) + "$conn$" + port1_name.str(ctx)); +        connect_port(ctx, p1net.get(), cell1, port1_name); +        IdString p1name = p1net->name; +        NPNR_ASSERT(!ctx->cells.count(p1name)); +        ctx->nets[p1name] = std::move(p1net); +    } +    connect_port(ctx, port1.net, cell2, port2_name); +} +  NEXTPNR_NAMESPACE_END diff --git a/common/design_utils.h b/common/design_utils.h index 95975179..8a42d21f 100644 --- a/common/design_utils.h +++ b/common/design_utils.h @@ -82,6 +82,15 @@ template <typename F1> CellInfo *net_driven_by(const Context *ctx, const NetInfo      }  } +// Connect a net to a port +void connect_port(const Context *ctx, NetInfo *net, CellInfo *cell, IdString port_name); + +// Disconnect a net from a port +void disconnect_port(const Context *ctx, CellInfo *cell, IdString port_name); + +// Connect two ports together +void connect_ports(Context *ctx, CellInfo *cell1, IdString port1_name, CellInfo *cell2, IdString port2_name); +  void print_utilisation(const Context *ctx);  NEXTPNR_NAMESPACE_END diff --git a/common/log.cc b/common/log.cc index e30449ad..6b2d6065 100644 --- a/common/log.cc +++ b/common/log.cc @@ -177,7 +177,8 @@ void log_always(const char *format, ...)  void log(const char *format, ...)  { -    if (log_quiet_warnings) return; +    if (log_quiet_warnings) +        return;      va_list ap;      va_start(ap, format);      logv(format, ap); @@ -186,7 +187,8 @@ void log(const char *format, ...)  void log_info(const char *format, ...)  { -    if (log_quiet_warnings) return; +    if (log_quiet_warnings) +        return;      va_list ap;      va_start(ap, format);      logv_info(format, ap); @@ -195,7 +197,6 @@ void log_info(const char *format, ...)  void log_warning(const char *format, ...)  { -    if (log_quiet_warnings) return;      va_list ap;      va_start(ap, format);      logv_warning(format, ap); @@ -204,7 +205,6 @@ void log_warning(const char *format, ...)  void log_warning_noprefix(const char *format, ...)  { -    if (log_quiet_warnings) return;      va_list ap;      va_start(ap, format);      logv_warning_noprefix(format, ap); @@ -235,7 +235,8 @@ void log_cmd_error(const char *format, ...)  void log_break()  { -    if (log_quiet_warnings) return; +    if (log_quiet_warnings) +        return;      if (log_newline_count < 2)          log_always("\n");      if (log_newline_count < 2) diff --git a/common/nextpnr.cc b/common/nextpnr.cc index b04679ad..4e6407b2 100644 --- a/common/nextpnr.cc +++ b/common/nextpnr.cc @@ -89,6 +89,11 @@ WireId Context::getNetinfoSinkWire(const NetInfo *net_info, const PortRef &user_  delay_t Context::getNetinfoRouteDelay(const NetInfo *net_info, const PortRef &user_info) const  { +#ifdef ARCH_ECP5 +    if (net_info->is_global) +        return 0; +#endif +      WireId src_wire = getNetinfoSourceWire(net_info);      if (src_wire == WireId())          return 0; @@ -99,8 +104,10 @@ delay_t Context::getNetinfoRouteDelay(const NetInfo *net_info, const PortRef &us      while (cursor != WireId() && cursor != src_wire) {          auto it = net_info->wires.find(cursor); +          if (it == net_info->wires.end())              break; +          PipId pip = it->second.pip;          delay += getPipDelay(pip).maxDelay();          delay += getWireDelay(cursor).maxDelay(); @@ -238,6 +245,11 @@ void Context::check() const                  NPNR_ASSERT(ni == getBoundPipNet(w.second.pip));              }          } +        if (ni->driver.cell != nullptr) +            NPNR_ASSERT(ni->driver.cell->ports.at(ni->driver.port).net == ni); +        for (auto user : ni->users) { +            NPNR_ASSERT(user.cell->ports.at(user.port).net == ni); +        }      }      for (auto w : getWires()) { diff --git a/common/place_common.cc b/common/place_common.cc index 5cdb96ef..120e5e00 100644 --- a/common/place_common.cc +++ b/common/place_common.cc @@ -329,7 +329,8 @@ class ConstraintLegaliseWorker                  yRootSearch = IncreasingDiameterSearch(cell->constr_y);              if (cell->constr_z == cell->UNCONSTR) -                zRootSearch = IncreasingDiameterSearch(currentLoc.z, 0, ctx->getTileBelDimZ(currentLoc.x, currentLoc.y)); +                zRootSearch = +                        IncreasingDiameterSearch(currentLoc.z, 0, ctx->getTileBelDimZ(currentLoc.x, currentLoc.y));              else                  zRootSearch = IncreasingDiameterSearch(cell->constr_z);              while (!xRootSearch.done()) { diff --git a/common/placer1.cc b/common/placer1.cc index 363b4d58..01f822a5 100644 --- a/common/placer1.cc +++ b/common/placer1.cc @@ -81,7 +81,8 @@ class SAPlacer          }      } -    ~SAPlacer() { +    ~SAPlacer() +    {          for (auto &net : ctx->nets)              net.second->udata = old_udata[net.second->udata];      } @@ -351,7 +352,7 @@ class SAPlacer      // Attempt a SA position swap, return true on success or false on failure      bool try_swap_position(CellInfo *cell, BelId newBel)      { -        static std::vector<NetInfo*> updates; +        static std::vector<NetInfo *> updates;          updates.clear();          BelId oldBel = cell->bel;          CellInfo *other_cell = ctx->getBoundBelCell(newBel); @@ -371,7 +372,8 @@ class SAPlacer          for (const auto &port : cell->ports) {              if (port.second.net != nullptr) {                  auto &cost = costs[port.second.net->udata]; -                if (cost.new_cost == 0) continue; +                if (cost.new_cost == 0) +                    continue;                  cost.new_cost = 0;                  updates.emplace_back(port.second.net);              } @@ -381,7 +383,8 @@ class SAPlacer              for (const auto &port : other_cell->ports)                  if (port.second.net != nullptr) {                      auto &cost = costs[port.second.net->udata]; -                    if (cost.new_cost == 0) continue; +                    if (cost.new_cost == 0) +                        continue;                      cost.new_cost = 0;                      updates.emplace_back(port.second.net);                  } @@ -483,7 +486,8 @@ class SAPlacer      const float post_legalise_dia_scale = 1.5;      Placer1Cfg cfg; -    struct CostChange { +    struct CostChange +    {          wirelen_t curr_cost;          wirelen_t new_cost;      }; diff --git a/common/router1.cc b/common/router1.cc index 5cd4414c..c4708de7 100644 --- a/common/router1.cc +++ b/common/router1.cc @@ -532,6 +532,12 @@ void addNetRouteJobs(Context *ctx, const Router1Cfg &cfg, IdString net_name,  {      NetInfo *net_info = ctx->nets.at(net_name).get(); +#ifdef ARCH_ECP5 +    // ECP5 global nets currently appear part-unrouted due to arch database limitations +    // Don't touch them in the router +    if (net_info->is_global) +        return; +#endif      if (net_info->driver.cell == nullptr)          return; diff --git a/common/settings.h b/common/settings.h index e1f1166a..0c4a67db 100644 --- a/common/settings.h +++ b/common/settings.h @@ -38,7 +38,7 @@ class Settings              if (!pair.second) {                  return boost::lexical_cast<T>(pair.first->second);              } -             +          } catch (boost::bad_lexical_cast &) {              log_error("Problem reading setting %s, using default value\n", name);          } @@ -51,7 +51,7 @@ class Settings          auto pair = ctx->settings.emplace(id, std::to_string(value));          if (!pair.second) {              ctx->settings[pair.first->first] = value; -        }         +        }      }    private: diff --git a/common/timing.cc b/common/timing.cc index a0735b55..d1a85779 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -151,6 +151,22 @@ struct Timing          }          // Sanity check to ensure that all ports where fanins were recorded were indeed visited +        if (!port_fanin.empty()) { +            for (auto fanin : port_fanin) { +                NetInfo *net = fanin.first->net; +                if (net != nullptr) { +                    log_info("   remaining fanin includes %s (net %s)\n", fanin.first->name.c_str(ctx), +                             net->name.c_str(ctx)); +                    if (net->driver.cell != nullptr) +                        log_info("        driver = %s.%s\n", net->driver.cell->name.c_str(ctx), +                                 net->driver.port.c_str(ctx)); +                    for (auto net_user : net->users) +                        log_info("        user: %s.%s\n", net_user.cell->name.c_str(ctx), net_user.port.c_str(ctx)); +                } else { +                    log_info("   remaining fanin includes %s (no net)\n", fanin.first->name.c_str(ctx)); +                } +            } +        }          NPNR_ASSERT(port_fanin.empty());          // Go forwards topographically to find the maximum arrival time and max path length for each net diff --git a/ecp5/arch.cc b/ecp5/arch.cc index 82ebfba1..9c059005 100644 --- a/ecp5/arch.cc +++ b/ecp5/arch.cc @@ -22,10 +22,12 @@  #include <cmath>  #include <cstring>  #include "gfx.h" +#include "globals.h"  #include "log.h"  #include "nextpnr.h"  #include "placer1.h"  #include "router1.h" +#include "timing.h"  #include "util.h"  NEXTPNR_NAMESPACE_BEGIN @@ -389,7 +391,12 @@ bool Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay  bool Arch::place() { return placer1(getCtx(), Placer1Cfg(getCtx())); } -bool Arch::route() { return router1(getCtx(), Router1Cfg(getCtx())); } +bool Arch::route() +{ +    route_ecp5_globals(getCtx()); +    assign_budget(getCtx(), true); +    return router1(getCtx(), Router1Cfg(getCtx())); +}  // ----------------------------------------------------------------------- @@ -504,12 +511,12 @@ bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort              delay.delay = 193;              return true;          } - +#if 0 // FIXME          if (fromPort == id_WCK && (toPort == id_F0 || toPort == id_F1)) {              delay.delay = 717;              return true;          } - +#endif          if ((fromPort == id_A0 && toPort == id_WADO3) || (fromPort == id_A1 && toPort == id_WDO1) ||              (fromPort == id_B0 && toPort == id_WADO1) || (fromPort == id_B1 && toPort == id_WDO3) ||              (fromPort == id_C0 && toPort == id_WADO2) || (fromPort == id_C1 && toPort == id_WDO0) || @@ -518,6 +525,12 @@ bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort              return true;          }          return false; +    } else if (cell->type == id_DCCA) { +        if (fromPort == id_CLKI && toPort == id_CLKO) { +            delay.delay = 0; +            return true; +        } +        return false;      } else {          return false;      } @@ -563,6 +576,12 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id          if (port == id_O)              return TMG_STARTPOINT;          return TMG_IGNORE; +    } else if (cell->type == id_DCCA) { +        if (port == id_CLKI) +            return TMG_COMB_INPUT; +        if (port == id_CLKO) +            return TMG_COMB_OUTPUT; +        return TMG_IGNORE;      } else {          NPNR_ASSERT_FALSE_STR("no timing data for cell type '" + cell->type.str(this) + "'");      } @@ -579,4 +598,10 @@ std::vector<std::pair<std::string, std::string>> Arch::getTilesAtLocation(int ro      return ret;  } +GlobalInfoPOD Arch::globalInfoAtLoc(Location loc) +{ +    int locidx = loc.y * chip_info->width + loc.x; +    return chip_info->location_glbinfo[locidx]; +} +  NEXTPNR_NAMESPACE_END diff --git a/ecp5/arch.h b/ecp5/arch.h index da86d4e2..9eac3c9f 100644 --- a/ecp5/arch.h +++ b/ecp5/arch.h @@ -147,6 +147,8 @@ NPNR_PACKED_STRUCT(struct GlobalInfoPOD {      int16_t tap_col;      TapDirection tap_dir;      GlobalQuadrant quad; +    int16_t spine_row; +    int16_t spine_col;  });  NPNR_PACKED_STRUCT(struct ChipInfoPOD { @@ -640,6 +642,21 @@ struct Arch : BaseCtx          return range;      } +    IdString getWireBasename(WireId wire) const { return id(locInfo(wire)->wire_data[wire.index].name.get()); } + +    WireId getWireByLocAndBasename(Location loc, std::string basename) const +    { +        WireId wireId; +        wireId.location = loc; +        for (int i = 0; i < locInfo(wireId)->num_wires; i++) { +            if (locInfo(wireId)->wire_data[i].name.get() == basename) { +                wireId.index = i; +                return wireId; +            } +        } +        return WireId(); +    } +      // -------------------------------------------------      PipId getPipByName(IdString name) const; @@ -891,6 +908,13 @@ struct Arch : BaseCtx          }          NPNR_ASSERT_FALSE_STR("no tile at (" + std::to_string(col) + ", " + std::to_string(row) + ") with type in set");      } + +    GlobalInfoPOD globalInfoAtLoc(Location loc); + +    IdString id_trellis_slice; +    IdString id_clk, id_lsr; +    IdString id_clkmux, id_lsrmux; +    IdString id_srmode, id_mode;  };  NEXTPNR_NAMESPACE_END diff --git a/ecp5/arch_place.cc b/ecp5/arch_place.cc index 55fff73d..6fcd8bde 100644 --- a/ecp5/arch_place.cc +++ b/ecp5/arch_place.cc @@ -39,25 +39,27 @@ bool Arch::slicesCompatible(const std::vector<const CellInfo *> &cells) const      IdString CLKMUX, LSRMUX, SRMODE;      bool first = true;      for (auto cell : cells) { -        if (first) { -            clk_sig = cell->sliceInfo.clk_sig; -            lsr_sig = cell->sliceInfo.lsr_sig; -            CLKMUX = cell->sliceInfo.clkmux; -            LSRMUX = cell->sliceInfo.lsrmux; -            SRMODE = cell->sliceInfo.srmode; -        } else { -            if (cell->sliceInfo.clk_sig != clk_sig) -                return false; -            if (cell->sliceInfo.lsr_sig != lsr_sig) -                return false; -            if (cell->sliceInfo.clkmux != CLKMUX) -                return false; -            if (cell->sliceInfo.lsrmux != LSRMUX) -                return false; -            if (cell->sliceInfo.srmode != SRMODE) -                return false; +        if (cell->sliceInfo.using_dff) { +            if (first) { +                clk_sig = cell->sliceInfo.clk_sig; +                lsr_sig = cell->sliceInfo.lsr_sig; +                CLKMUX = cell->sliceInfo.clkmux; +                LSRMUX = cell->sliceInfo.lsrmux; +                SRMODE = cell->sliceInfo.srmode; +            } else { +                if (cell->sliceInfo.clk_sig != clk_sig) +                    return false; +                if (cell->sliceInfo.lsr_sig != lsr_sig) +                    return false; +                if (cell->sliceInfo.clkmux != CLKMUX) +                    return false; +                if (cell->sliceInfo.lsrmux != LSRMUX) +                    return false; +                if (cell->sliceInfo.srmode != SRMODE) +                    return false; +            } +            first = false;          } -        first = false;      }      return true;  } diff --git a/ecp5/archdefs.h b/ecp5/archdefs.h index c4e1413f..b85852c2 100644 --- a/ecp5/archdefs.h +++ b/ecp5/archdefs.h @@ -136,11 +136,14 @@ struct DecalId  struct ArchNetInfo  { +    bool is_global = false;  }; +  struct ArchCellInfo  {      struct      { +        bool using_dff;          IdString clk_sig, lsr_sig, clkmux, lsrmux, srmode;      } sliceInfo;  }; diff --git a/ecp5/bitstream.cc b/ecp5/bitstream.cc index a1edf9e5..296ea753 100644 --- a/ecp5/bitstream.cc +++ b/ecp5/bitstream.cc @@ -244,19 +244,67 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex              cc.tiles[tname].add_enum(slice + ".REG1.REGSET",                                       str_or_default(ci->params, ctx->id("REG1_REGSET"), "RESET"));              cc.tiles[tname].add_enum(slice + ".CEMUX", str_or_default(ci->params, ctx->id("CEMUX"), "1")); -            NetInfo *lsrnet = nullptr; -            if (ci->ports.find(ctx->id("LSR")) != ci->ports.end() && ci->ports.at(ctx->id("LSR")).net != nullptr) -                lsrnet = ci->ports.at(ctx->id("LSR")).net; -            if (ctx->getBoundWireNet(ctx->getWireByName( -                        ctx->id(fmt_str("X" << bel.location.x << "/Y" << bel.location.y << "/LSR0")))) == lsrnet) { -                cc.tiles[tname].add_enum("LSR0.SRMODE", str_or_default(ci->params, ctx->id("SRMODE"), "LSR_OVER_CE")); -                cc.tiles[tname].add_enum("LSR0.LSRMUX", str_or_default(ci->params, ctx->id("LSRMUX"), "LSR")); -            } else if (ctx->getBoundWireNet(ctx->getWireByName(ctx->id( -                               fmt_str("X" << bel.location.x << "/Y" << bel.location.y << "/LSR1")))) == lsrnet) { -                cc.tiles[tname].add_enum("LSR1.SRMODE", str_or_default(ci->params, ctx->id("SRMODE"), "LSR_OVER_CE")); -                cc.tiles[tname].add_enum("LSR1.LSRMUX", str_or_default(ci->params, ctx->id("LSRMUX"), "LSR")); + +            if (ci->sliceInfo.using_dff) { +                NetInfo *lsrnet = nullptr; +                if (ci->ports.find(ctx->id("LSR")) != ci->ports.end() && ci->ports.at(ctx->id("LSR")).net != nullptr) +                    lsrnet = ci->ports.at(ctx->id("LSR")).net; +                if (ctx->getBoundWireNet(ctx->getWireByName( +                            ctx->id(fmt_str("X" << bel.location.x << "/Y" << bel.location.y << "/LSR0")))) == lsrnet) { +                    cc.tiles[tname].add_enum("LSR0.SRMODE", +                                             str_or_default(ci->params, ctx->id("SRMODE"), "LSR_OVER_CE")); +                    cc.tiles[tname].add_enum("LSR0.LSRMUX", str_or_default(ci->params, ctx->id("LSRMUX"), "LSR")); +                } else if (ctx->getBoundWireNet(ctx->getWireByName(ctx->id( +                                   fmt_str("X" << bel.location.x << "/Y" << bel.location.y << "/LSR1")))) == lsrnet) { +                    cc.tiles[tname].add_enum("LSR1.SRMODE", +                                             str_or_default(ci->params, ctx->id("SRMODE"), "LSR_OVER_CE")); +                    cc.tiles[tname].add_enum("LSR1.LSRMUX", str_or_default(ci->params, ctx->id("LSRMUX"), "LSR")); +                } + +                NetInfo *clknet = nullptr; +                if (ci->ports.find(ctx->id("CLK")) != ci->ports.end() && ci->ports.at(ctx->id("CLK")).net != nullptr) +                    clknet = ci->ports.at(ctx->id("CLK")).net; +                if (ctx->getBoundWireNet(ctx->getWireByName( +                            ctx->id(fmt_str("X" << bel.location.x << "/Y" << bel.location.y << "/CLK0")))) == clknet) { +                    cc.tiles[tname].add_enum("CLK0.CLKMUX", str_or_default(ci->params, ctx->id("CLKMUX"), "CLK")); +                } else if (ctx->getBoundWireNet(ctx->getWireByName(ctx->id( +                                   fmt_str("X" << bel.location.x << "/Y" << bel.location.y << "/CLK1")))) == clknet) { +                    cc.tiles[tname].add_enum("CLK1.CLKMUX", str_or_default(ci->params, ctx->id("CLKMUX"), "CLK")); +                }              } -            // TODO: CLKMUX, CEMUX, carry + +            if (str_or_default(ci->params, ctx->id("MODE"), "LOGIC") == "CCU2") { +                cc.tiles[tname].add_enum(slice + ".CCU2.INJECT1_0", +                                         str_or_default(ci->params, ctx->id("INJECT1_0"), "YES")); +                cc.tiles[tname].add_enum(slice + ".CCU2.INJECT1_1", +                                         str_or_default(ci->params, ctx->id("INJECT1_1"), "YES")); +            } else { +                // Don't interfere with cascade mux wiring +                cc.tiles[tname].add_enum(slice + ".CCU2.INJECT1_0", +                                         str_or_default(ci->params, ctx->id("INJECT1_0"), "_NONE_")); +                cc.tiles[tname].add_enum(slice + ".CCU2.INJECT1_1", +                                         str_or_default(ci->params, ctx->id("INJECT1_1"), "_NONE_")); +            } + +            if (str_or_default(ci->params, ctx->id("MODE"), "LOGIC") == "DPRAM" && slice == "SLICEA") { +                cc.tiles[tname].add_enum(slice + ".WREMUX", str_or_default(ci->params, ctx->id("WREMUX"), "WRE")); + +                NetInfo *wcknet = nullptr; +                std::string wckmux = str_or_default(ci->params, ctx->id("WCKMUX"), "WCK"); +                wckmux = (wckmux == "WCK") ? "CLK" : wckmux; +                if (ci->ports.find(ctx->id("WCK")) != ci->ports.end() && ci->ports.at(ctx->id("WCK")).net != nullptr) +                    wcknet = ci->ports.at(ctx->id("WCK")).net; +                cc.tiles[tname].add_enum("CLK1.CLKMUX", wckmux); +            } + +            // Tie unused inputs high +            for (auto input : {id_A0, id_B0, id_C0, id_D0, id_A1, id_B1, id_C1, id_D1}) { +                if (ci->ports.find(input) == ci->ports.end() || ci->ports.at(input).net == nullptr) { +                    cc.tiles[tname].add_enum(slice + "." + input.str(ctx) + "MUX", "1"); +                } +            } + +            // TODO: CLKMUX          } else if (ci->type == ctx->id("TRELLIS_IO")) {              std::string pio = ctx->locInfo(bel)->bel_data[bel.index].name.get();              std::string iotype = str_or_default(ci->attrs, ctx->id("IO_TYPE"), "LVCMOS33"); @@ -294,6 +342,8 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex              if (dir == "INPUT" && !is_differential(ioType_from_str(iotype))) {                  cc.tiles[pio_tile].add_enum(pio + ".HYSTERESIS", "ON");              } +        } else if (ci->type == ctx->id("DCCA")) { +            // Nothing to do          } else {              NPNR_ASSERT_FALSE("unsupported cell type");          } diff --git a/ecp5/cells.cc b/ecp5/cells.cc index e3532f36..a728104d 100644 --- a/ecp5/cells.cc +++ b/ecp5/cells.cc @@ -124,6 +124,32 @@ std::unique_ptr<CellInfo> create_ecp5_cell(Context *ctx, IdString type, std::str          add_port(ctx, new_cell.get(), "C", PORT_IN);          add_port(ctx, new_cell.get(), "D", PORT_IN);          add_port(ctx, new_cell.get(), "Z", PORT_OUT); +    } else if (type == ctx->id("CCU2C")) { +        new_cell->params[ctx->id("INIT0")] = "0"; +        new_cell->params[ctx->id("INIT1")] = "0"; +        new_cell->params[ctx->id("INJECT1_0")] = "YES"; +        new_cell->params[ctx->id("INJECT1_1")] = "YES"; + +        add_port(ctx, new_cell.get(), "CIN", PORT_IN); + +        add_port(ctx, new_cell.get(), "A0", PORT_IN); +        add_port(ctx, new_cell.get(), "B0", PORT_IN); +        add_port(ctx, new_cell.get(), "C0", PORT_IN); +        add_port(ctx, new_cell.get(), "D0", PORT_IN); + +        add_port(ctx, new_cell.get(), "A1", PORT_IN); +        add_port(ctx, new_cell.get(), "B1", PORT_IN); +        add_port(ctx, new_cell.get(), "C1", PORT_IN); +        add_port(ctx, new_cell.get(), "D1", PORT_IN); + +        add_port(ctx, new_cell.get(), "S0", PORT_OUT); +        add_port(ctx, new_cell.get(), "S1", PORT_OUT); +        add_port(ctx, new_cell.get(), "COUT", PORT_OUT); + +    } else if (type == ctx->id("DCCA")) { +        add_port(ctx, new_cell.get(), "CLKI", PORT_IN); +        add_port(ctx, new_cell.get(), "CLKO", PORT_OUT); +        add_port(ctx, new_cell.get(), "CE", PORT_IN);      } else {          log_error("unable to create ECP5 cell of type %s", type.c_str(ctx));      } @@ -159,6 +185,8 @@ void ff_to_slice(Context *ctx, CellInfo *ff, CellInfo *lc, int index, bool drive      set_param_safe(has_ff, lc, ctx->id("GSR"), str_or_default(ff->params, ctx->id("GSR"), "DISABLED"));      set_param_safe(has_ff, lc, ctx->id("CEMUX"), str_or_default(ff->params, ctx->id("CEMUX"), "1"));      set_param_safe(has_ff, lc, ctx->id("LSRMUX"), str_or_default(ff->params, ctx->id("LSRMUX"), "LSR")); +    set_param_safe(has_ff, lc, ctx->id("CLKMUX"), str_or_default(ff->params, ctx->id("CLKMUX"), "CLK")); +      lc->params[ctx->id(reg + "_SD")] = driven_by_lut ? "1" : "0";      lc->params[ctx->id(reg + "_REGSET")] = str_or_default(ff->params, ctx->id("REGSET"), "RESET");      replace_port_safe(has_ff, ff, ctx->id("CLK"), lc, ctx->id("CLK")); @@ -185,4 +213,134 @@ void lut_to_slice(Context *ctx, CellInfo *lut, CellInfo *lc, int index)      replace_port(lut, ctx->id("Z"), lc, ctx->id("F" + std::to_string(index)));  } +void ccu2c_to_slice(Context *ctx, CellInfo *ccu, CellInfo *lc) +{ +    lc->params[ctx->id("MODE")] = "CCU2"; +    lc->params[ctx->id("LUT0_INITVAL")] = str_or_default(ccu->params, ctx->id("INIT0"), "0"); +    lc->params[ctx->id("LUT1_INITVAL")] = str_or_default(ccu->params, ctx->id("INIT1"), "0"); + +    lc->params[ctx->id("INJECT1_0")] = str_or_default(ccu->params, ctx->id("INJECT1_0"), "YES"); +    lc->params[ctx->id("INJECT1_1")] = str_or_default(ccu->params, ctx->id("INJECT1_1"), "YES"); + +    replace_port(ccu, ctx->id("CIN"), lc, ctx->id("FCI")); + +    replace_port(ccu, ctx->id("A0"), lc, ctx->id("A0")); +    replace_port(ccu, ctx->id("B0"), lc, ctx->id("B0")); +    replace_port(ccu, ctx->id("C0"), lc, ctx->id("C0")); +    replace_port(ccu, ctx->id("D0"), lc, ctx->id("D0")); + +    replace_port(ccu, ctx->id("A1"), lc, ctx->id("A1")); +    replace_port(ccu, ctx->id("B1"), lc, ctx->id("B1")); +    replace_port(ccu, ctx->id("C1"), lc, ctx->id("C1")); +    replace_port(ccu, ctx->id("D1"), lc, ctx->id("D1")); + +    replace_port(ccu, ctx->id("S0"), lc, ctx->id("F0")); +    replace_port(ccu, ctx->id("S1"), lc, ctx->id("F1")); + +    replace_port(ccu, ctx->id("COUT"), lc, ctx->id("FCO")); +} + +void dram_to_ramw(Context *ctx, CellInfo *ram, CellInfo *lc) +{ +    lc->params[ctx->id("MODE")] = "RAMW"; +    replace_port(ram, ctx->id("WAD[0]"), lc, ctx->id("D0")); +    replace_port(ram, ctx->id("WAD[1]"), lc, ctx->id("B0")); +    replace_port(ram, ctx->id("WAD[2]"), lc, ctx->id("C0")); +    replace_port(ram, ctx->id("WAD[3]"), lc, ctx->id("A0")); + +    replace_port(ram, ctx->id("DI[0]"), lc, ctx->id("C1")); +    replace_port(ram, ctx->id("DI[1]"), lc, ctx->id("A1")); +    replace_port(ram, ctx->id("DI[2]"), lc, ctx->id("D1")); +    replace_port(ram, ctx->id("DI[3]"), lc, ctx->id("B1")); +} + +static unsigned get_dram_init(const Context *ctx, const CellInfo *ram, int bit) +{ +    const std::string &idata = str_or_default(ram->params, ctx->id("INITVAL"), +                                              "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); +    NPNR_ASSERT(idata.length() == 64); +    unsigned value = 0; +    for (int i = 0; i < 16; i++) { +        char c = idata.at(63 - (4 * i + bit)); +        if (c == '1') +            value |= (1 << i); +        else +            NPNR_ASSERT(c == '0' || c == 'x'); +    } +    return value; +} + +void dram_to_ram_slice(Context *ctx, CellInfo *ram, CellInfo *lc, CellInfo *ramw, int index) +{ +    lc->params[ctx->id("MODE")] = "DPRAM"; +    lc->params[ctx->id("WREMUX")] = str_or_default(ram->params, ctx->id("WREMUX"), "WRE"); +    lc->params[ctx->id("WCKMUX")] = str_or_default(ram->params, ctx->id("WCKMUX"), "WCK"); + +    unsigned permuted_init0 = 0, permuted_init1 = 0; +    unsigned init0 = get_dram_init(ctx, ram, index * 2), init1 = get_dram_init(ctx, ram, index * 2 + 1); + +    for (int i = 0; i < 16; i++) { +        int permuted_addr = 0; +        if (i & 1) +            permuted_addr |= 8; +        if (i & 2) +            permuted_addr |= 2; +        if (i & 4) +            permuted_addr |= 4; +        if (i & 8) +            permuted_addr |= 1; +        if (init0 & (1 << permuted_addr)) +            permuted_init0 |= (1 << i); +        if (init1 & (1 << permuted_addr)) +            permuted_init1 |= (1 << i); +    } + +    lc->params[ctx->id("LUT0_INITVAL")] = std::to_string(permuted_init0); +    lc->params[ctx->id("LUT1_INITVAL")] = std::to_string(permuted_init1); + +    if (ram->ports.count(ctx->id("RAD[0]"))) { +        connect_port(ctx, ram->ports.at(ctx->id("RAD[0]")).net, lc, ctx->id("D0")); +        connect_port(ctx, ram->ports.at(ctx->id("RAD[0]")).net, lc, ctx->id("D1")); +    } +    if (ram->ports.count(ctx->id("RAD[1]"))) { +        connect_port(ctx, ram->ports.at(ctx->id("RAD[1]")).net, lc, ctx->id("B0")); +        connect_port(ctx, ram->ports.at(ctx->id("RAD[1]")).net, lc, ctx->id("B1")); +    } +    if (ram->ports.count(ctx->id("RAD[2]"))) { +        connect_port(ctx, ram->ports.at(ctx->id("RAD[2]")).net, lc, ctx->id("C0")); +        connect_port(ctx, ram->ports.at(ctx->id("RAD[2]")).net, lc, ctx->id("C1")); +    } +    if (ram->ports.count(ctx->id("RAD[3]"))) { +        connect_port(ctx, ram->ports.at(ctx->id("RAD[3]")).net, lc, ctx->id("A0")); +        connect_port(ctx, ram->ports.at(ctx->id("RAD[3]")).net, lc, ctx->id("A1")); +    } + +    if (ram->ports.count(ctx->id("WRE"))) +        connect_port(ctx, ram->ports.at(ctx->id("WRE")).net, lc, ctx->id("WRE")); +    if (ram->ports.count(ctx->id("WCK"))) +        connect_port(ctx, ram->ports.at(ctx->id("WCK")).net, lc, ctx->id("WCK")); + +    connect_ports(ctx, ramw, id_WADO0, lc, id_WAD0); +    connect_ports(ctx, ramw, id_WADO1, lc, id_WAD1); +    connect_ports(ctx, ramw, id_WADO2, lc, id_WAD2); +    connect_ports(ctx, ramw, id_WADO3, lc, id_WAD3); + +    if (index == 0) { +        connect_ports(ctx, ramw, id_WDO0, lc, id_WD0); +        connect_ports(ctx, ramw, id_WDO1, lc, id_WD1); + +        replace_port(ram, ctx->id("DO[0]"), lc, id_F0); +        replace_port(ram, ctx->id("DO[1]"), lc, id_F1); + +    } else if (index == 1) { +        connect_ports(ctx, ramw, id_WDO2, lc, id_WD0); +        connect_ports(ctx, ramw, id_WDO3, lc, id_WD1); + +        replace_port(ram, ctx->id("DO[2]"), lc, id_F0); +        replace_port(ram, ctx->id("DO[3]"), lc, id_F1); +    } else { +        NPNR_ASSERT_FALSE("bad DPRAM index"); +    } +} +  NEXTPNR_NAMESPACE_END diff --git a/ecp5/cells.h b/ecp5/cells.h index b0c74ca9..a5229fe0 100644 --- a/ecp5/cells.h +++ b/ecp5/cells.h @@ -48,6 +48,9 @@ inline bool is_l6mux(const BaseCtx *ctx, const CellInfo *cell) { return cell->ty  void ff_to_slice(Context *ctx, CellInfo *ff, CellInfo *lc, int index, bool driven_by_lut);  void lut_to_slice(Context *ctx, CellInfo *lut, CellInfo *lc, int index); +void ccu2c_to_slice(Context *ctx, CellInfo *ccu, CellInfo *lc); +void dram_to_ramw(Context *ctx, CellInfo *ram, CellInfo *lc); +void dram_to_ram_slice(Context *ctx, CellInfo *ram, CellInfo *lc, CellInfo *ramw, int index);  NEXTPNR_NAMESPACE_END diff --git a/ecp5/constids.inc b/ecp5/constids.inc index 12eb4a5a..bd55fa90 100644 --- a/ecp5/constids.inc +++ b/ecp5/constids.inc @@ -47,6 +47,10 @@ X(B)  X(TRELLIS_SLICE)  X(TRELLIS_IO) +X(DCCA)  X(CLKMUX)  X(LSRMUX)  X(SRMODE) + +X(CLKI) +X(CLKO) diff --git a/ecp5/globals.cc b/ecp5/globals.cc new file mode 100644 index 00000000..364e4bca --- /dev/null +++ b/ecp5/globals.cc @@ -0,0 +1,359 @@ +/* + *  nextpnr -- Next Generation Place and Route + * + *  Copyright (C) 2018  David Shah <david@symbioticeda.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 "globals.h" +#include <algorithm> +#include <iomanip> +#include <queue> +#include "cells.h" +#include "log.h" +#include "nextpnr.h" + +#define fmt_str(x) (static_cast<const std::ostringstream &>(std::ostringstream() << x).str()) + +NEXTPNR_NAMESPACE_BEGIN + +static std::string get_quad_name(GlobalQuadrant quad) +{ +    switch (quad) { +    case QUAD_UL: +        return "UL"; +    case QUAD_UR: +        return "UR"; +    case QUAD_LL: +        return "LL"; +    case QUAD_LR: +        return "LR"; +    } +    return ""; +} + +class Ecp5GlobalRouter +{ +  public: +    Ecp5GlobalRouter(Context *ctx) : ctx(ctx){}; + +  private: +    bool is_clock_port(const PortRef &user) +    { +        if (user.cell->type == id_TRELLIS_SLICE && (user.port == id_CLK || user.port == id_WCK)) +            return true; +        return false; +    } + +    std::vector<NetInfo *> get_clocks() +    { +        std::unordered_map<IdString, int> clockCount; +        for (auto &net : ctx->nets) { +            NetInfo *ni = net.second.get(); +            clockCount[ni->name] = 0; +            for (const auto &user : ni->users) { +                if (is_clock_port(user)) +                    clockCount[ni->name]++; +            } +            // log_info("clkcount %s: %d\n", ni->name.c_str(ctx),clockCount[ni->name]); +        } +        std::vector<NetInfo *> clocks; +        while (clocks.size() < 16) { +            auto max = std::max_element(clockCount.begin(), clockCount.end(), +                                        [](const decltype(clockCount)::value_type &a, +                                           const decltype(clockCount)::value_type &b) { return a.second < b.second; }); +            if (max == clockCount.end() || max->second < 5) +                break; +            clocks.push_back(ctx->nets.at(max->first).get()); +            clockCount.erase(max->first); +        } +        return clocks; +    } + +    PipId find_tap_pip(WireId tile_glb) +    { +        std::string wireName = ctx->getWireBasename(tile_glb).str(ctx); +        std::string glbName = wireName.substr(2); +        TapDirection td = ctx->globalInfoAtLoc(tile_glb.location).tap_dir; +        WireId tap_wire; +        Location tap_loc; +        tap_loc.x = ctx->globalInfoAtLoc(tile_glb.location).tap_col; +        tap_loc.y = tile_glb.location.y; +        if (td == TAP_DIR_LEFT) { +            tap_wire = ctx->getWireByLocAndBasename(tap_loc, "L_" + glbName); +        } else { +            tap_wire = ctx->getWireByLocAndBasename(tap_loc, "R_" + glbName); +        } +        NPNR_ASSERT(tap_wire != WireId()); +        return *(ctx->getPipsUphill(tap_wire).begin()); +    } + +    PipId find_spine_pip(WireId tap_wire) +    { +        std::string wireName = ctx->getWireBasename(tap_wire).str(ctx); +        Location spine_loc; +        spine_loc.x = ctx->globalInfoAtLoc(tap_wire.location).spine_col; +        spine_loc.y = ctx->globalInfoAtLoc(tap_wire.location).spine_row; +        WireId spine_wire = ctx->getWireByLocAndBasename(spine_loc, wireName); +        return *(ctx->getPipsUphill(spine_wire).begin()); +    } + +    void route_logic_tile_global(NetInfo *net, int global_index, PortRef user) +    { +        WireId userWire = ctx->getBelPinWire(user.cell->bel, user.port); +        WireId globalWire; +        IdString global_name = ctx->id(fmt_str("G_HPBX" << std::setw(2) << std::setfill('0') << global_index << "00")); +        std::queue<WireId> upstream; +        std::unordered_map<WireId, PipId> backtrace; +        upstream.push(userWire); +        bool already_routed = false; +        WireId next; +        // Search back from the pin until we reach the global network +        while (true) { +            next = upstream.front(); +            upstream.pop(); + +            if (ctx->getBoundWireNet(next) == net) { +                already_routed = true; +                globalWire = next; +                break; +            } + +            if (ctx->getWireBasename(next) == global_name) { +                globalWire = next; +                break; +            } +            if (ctx->checkWireAvail(next)) { +                for (auto pip : ctx->getPipsUphill(next)) { +                    WireId src = ctx->getPipSrcWire(pip); +                    backtrace[src] = pip; +                    upstream.push(src); +                } +            } +            if (upstream.size() > 30000) { +                log_error("failed to route HPBX%02d00 to %s.%s\n", global_index, +                          ctx->getBelName(user.cell->bel).c_str(ctx), user.port.c_str(ctx)); +            } +        } +        // Set all the pips we found along the way +        WireId cursor = next; +        while (true) { +            auto fnd = backtrace.find(cursor); +            if (fnd == backtrace.end()) +                break; +            ctx->bindPip(fnd->second, net, STRENGTH_LOCKED); +            cursor = ctx->getPipDstWire(fnd->second); +        } +        // If the global network inside the tile isn't already set up, +        // we also need to bind the buffers along the way +        if (!already_routed) { +            ctx->bindWire(next, net, STRENGTH_LOCKED); +            PipId tap_pip = find_tap_pip(next); +            NetInfo *tap_net = ctx->getBoundPipNet(tap_pip); +            if (tap_net == nullptr) { +                ctx->bindPip(tap_pip, net, STRENGTH_LOCKED); +                PipId spine_pip = find_spine_pip(ctx->getPipSrcWire(tap_pip)); +                NetInfo *spine_net = ctx->getBoundPipNet(spine_pip); +                if (spine_net == nullptr) { +                    ctx->bindPip(spine_pip, net, STRENGTH_LOCKED); +                } else { +                    NPNR_ASSERT(spine_net == net); +                } +            } else { +                NPNR_ASSERT(tap_net == net); +            } +        } +    } + +    bool is_global_io(CellInfo *io, std::string &glb_name) +    { +        std::string func_name = ctx->getPioFunctionName(io->bel); +        if (func_name.substr(0, 5) == "PCLKT") { +            func_name.erase(func_name.find('_'), 1); +            glb_name = "G_" + func_name; +            return true; +        } +        return false; +    } + +    WireId get_global_wire(GlobalQuadrant quad, int network) +    { +        return ctx->getWireByLocAndBasename(Location(0, 0), +                                            "G_" + get_quad_name(quad) + "PCLK" + std::to_string(network)); +    } + +    bool simple_router(NetInfo *net, WireId src, WireId dst, bool allow_fail = false) +    { +        std::queue<WireId> visit; +        std::unordered_map<WireId, PipId> backtrace; +        visit.push(src); +        WireId cursor; +        while (true) { + +            if (visit.empty() || visit.size() > 50000) { +                if (allow_fail) +                    return false; +                log_error("cannot route global from %s to %s.\n", ctx->getWireName(src).c_str(ctx), +                          ctx->getWireName(dst).c_str(ctx)); +            } +            cursor = visit.front(); +            visit.pop(); +            NetInfo *bound = ctx->getBoundWireNet(cursor); +            if (bound == net) { +            } else if (bound != nullptr) { +                continue; +            } +            if (cursor == dst) +                break; +            for (auto dh : ctx->getPipsDownhill(cursor)) { +                WireId pipDst = ctx->getPipDstWire(dh); +                if (backtrace.count(pipDst)) +                    continue; +                backtrace[pipDst] = dh; +                visit.push(pipDst); +            } +        } +        while (true) { +            auto fnd = backtrace.find(cursor); +            if (fnd == backtrace.end()) +                break; +            NetInfo *bound = ctx->getBoundWireNet(cursor); +            if (bound != nullptr) { +                NPNR_ASSERT(bound == net); +                break; +            } +            ctx->bindPip(fnd->second, net, STRENGTH_LOCKED); +            cursor = ctx->getPipSrcWire(fnd->second); +        } +        if (ctx->getBoundWireNet(src) == nullptr) +            ctx->bindWire(src, net, STRENGTH_LOCKED); +        return true; +    } + +    bool route_onto_global(NetInfo *net, int network) +    { +        WireId glb_src; +        NPNR_ASSERT(net->driver.cell->type == id_DCCA); +        glb_src = ctx->getNetinfoSourceWire(net); +        for (int quad = QUAD_UL; quad < QUAD_LR + 1; quad++) { +            WireId glb_dst = get_global_wire(GlobalQuadrant(quad), network); +            NPNR_ASSERT(glb_dst != WireId()); +            bool routed = simple_router(net, glb_src, glb_dst); +            if (!routed) +                return false; +        } +        return true; +    } + +    // Attempt to place a DCC +    void place_dcc(CellInfo *dcc) +    { +        for (auto bel : ctx->getBels()) { +            if (ctx->getBelType(bel) == id_DCCA && ctx->checkBelAvail(bel)) { +                if (ctx->isValidBelForCell(dcc, bel)) { +                    ctx->bindBel(bel, dcc, STRENGTH_LOCKED); +                    return; +                } +            } +        } +        NPNR_ASSERT_FALSE("failed to place dcca"); +    } + +    // Insert a DCC into a net to promote it to a global +    NetInfo *insert_dcc(NetInfo *net) +    { +        auto dcc = create_ecp5_cell(ctx, id_DCCA, "$gbuf$" + net->name.str(ctx)); + +        std::unique_ptr<NetInfo> glbnet = std::unique_ptr<NetInfo>(new NetInfo); +        glbnet->name = ctx->id("$glbnet$" + net->name.str(ctx)); +        glbnet->driver.cell = dcc.get(); +        glbnet->driver.port = id_CLKO; +        glbnet->is_global = true; +        dcc->ports[id_CLKO].net = glbnet.get(); + +        glbnet->users = net->users; +        for (auto user : net->users) { +            user.cell->ports.at(user.port).net = glbnet.get(); +        } +        net->users.clear(); + +        dcc->ports[id_CLKI].net = net; +        PortRef clki_pr; +        clki_pr.port = id_CLKI; +        clki_pr.cell = dcc.get(); +        net->users.push_back(clki_pr); + +        place_dcc(dcc.get()); + +        ctx->cells[dcc->name] = std::move(dcc); +        NetInfo *glbptr = glbnet.get(); +        ctx->nets[glbnet->name] = std::move(glbnet); +        return glbptr; +    } + +    int global_route_priority(const PortRef &load) +    { +        if (load.port == id_WCK || load.port == id_WRE) +            return 90; +        return 99; +    } + +    Context *ctx; + +  public: +    void promote_and_route_globals() +    { +        log_info("Promoting and routing globals...\n"); +        auto clocks = get_clocks(); +        std::set<int> all_globals, fab_globals; +        for (int i = 0; i < 16; i++) { +            all_globals.insert(i); +            if (i < 8) +                fab_globals.insert(i); +        } +        for (auto clock : clocks) { +            bool drives_fabric = std::any_of(clock->users.begin(), clock->users.end(), +                                             [this](const PortRef &port) { return !is_clock_port(port); }); +            int glbid; +            if (drives_fabric) { +                if (fab_globals.empty()) +                    continue; +                glbid = *(fab_globals.begin()); +            } else { +                glbid = *(all_globals.begin()); +            } +            all_globals.erase(glbid); +            fab_globals.erase(glbid); +            log_info("    promoting clock net %s to global %d\n", clock->name.c_str(ctx), glbid); +            auto old_users = clock->users; +            NetInfo *global = insert_dcc(clock); +            bool routed = route_onto_global(global, glbid); +            NPNR_ASSERT(routed); + +            // WCK must have routing priority +            auto sorted_users = global->users; +            std::sort(sorted_users.begin(), sorted_users.end(), [this](const PortRef &a, const PortRef &b) { +                return global_route_priority(a) < global_route_priority(b); +            }); +            for (const auto &user : sorted_users) { +                route_logic_tile_global(global, glbid, user); +            } +        } +    } +}; + +void route_ecp5_globals(Context *ctx) { Ecp5GlobalRouter(ctx).promote_and_route_globals(); } + +NEXTPNR_NAMESPACE_END diff --git a/ecp5/globals.h b/ecp5/globals.h new file mode 100644 index 00000000..23e25c8d --- /dev/null +++ b/ecp5/globals.h @@ -0,0 +1,26 @@ +/* + *  nextpnr -- Next Generation Place and Route + * + *  Copyright (C) 2018  David Shah <david@symbioticeda.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 "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +void route_ecp5_globals(Context *ctx); + +NEXTPNR_NAMESPACE_END
\ No newline at end of file diff --git a/ecp5/pack.cc b/ecp5/pack.cc index a2077204..0045617b 100644 --- a/ecp5/pack.cc +++ b/ecp5/pack.cc @@ -18,13 +18,14 @@   */  #include <algorithm> +#include <boost/optional.hpp>  #include <iterator>  #include <unordered_set>  #include "cells.h" +#include "chain_utils.h"  #include "design_utils.h"  #include "log.h"  #include "util.h" -  NEXTPNR_NAMESPACE_BEGIN  static bool is_nextpnr_iob(Context *ctx, CellInfo *cell) @@ -106,6 +107,41 @@ class Ecp5Packer          return true;      } +    // Return whether or not an FF can be added to a tile (pairing checks must also be done using the fn above) +    bool can_add_ff_to_tile(const std::vector<CellInfo *> &tile_ffs, CellInfo *ff0) +    { +        for (const auto &existing : tile_ffs) { +            if (net_or_nullptr(existing, ctx->id("CLK")) != net_or_nullptr(ff0, ctx->id("CLK"))) +                return false; +            if (net_or_nullptr(existing, ctx->id("LSR")) != net_or_nullptr(ff0, ctx->id("LSR"))) +                return false; +            if (str_or_default(existing->params, ctx->id("CLKMUX"), "CLK") != +                str_or_default(ff0->params, ctx->id("CLKMUX"), "CLK")) +                return false; +            if (str_or_default(existing->params, ctx->id("LSRMUX"), "LSR") != +                str_or_default(ff0->params, ctx->id("LSRMUX"), "LSR")) +                return false; +            if (str_or_default(existing->params, ctx->id("SRMODE"), "LSR_OVER_CE") != +                str_or_default(ff0->params, ctx->id("SRMODE"), "LSR_OVER_CE")) +                return false; +        } +        return true; +    } + +    // Return true if a FF can be added to a DPRAM slice +    bool can_pack_ff_dram(CellInfo *dpram, CellInfo *ff) +    { +        std::string wckmux = str_or_default(dpram->params, ctx->id("WCKMUX"), "WCK"); +        std::string clkmux = str_or_default(ff->params, ctx->id("CLKMUX"), "CLK"); +        if (wckmux != clkmux && !(wckmux == "WCK" && clkmux == "CLK")) +            return false; +        std::string wremux = str_or_default(dpram->params, ctx->id("WREMUX"), "WRE"); +        std::string lsrmux = str_or_default(ff->params, ctx->id("LSRMUX"), "LSR"); +        if (wremux != lsrmux && !(wremux == "WRE" && lsrmux == "LSR")) +            return false; +        return true; +    } +      // Return true if two LUTs can be paired considering FF compatibility      bool can_pack_lutff(IdString lut0, IdString lut1)      { @@ -312,6 +348,323 @@ class Ecp5Packer          flush_cells();      } +    // Create a feed in to the carry chain +    CellInfo *make_carry_feed_in(NetInfo *carry, PortRef chain_in) +    { +        std::unique_ptr<CellInfo> feedin = create_ecp5_cell(ctx, ctx->id("CCU2C")); + +        feedin->params[ctx->id("INIT0")] = "10"; // LUT4 = 0; LUT2 = A +        feedin->params[ctx->id("INIT1")] = "65535"; +        feedin->params[ctx->id("INJECT1_0")] = "NO"; +        feedin->params[ctx->id("INJECT1_1")] = "YES"; + +        carry->users.erase(std::remove_if(carry->users.begin(), carry->users.end(), +                                          [chain_in](const PortRef &user) { +                                              return user.port == chain_in.port && user.cell == chain_in.cell; +                                          }), +                           carry->users.end()); +        connect_port(ctx, carry, feedin.get(), id_A0); + +        std::unique_ptr<NetInfo> new_carry(new NetInfo()); +        new_carry->name = ctx->id(feedin->name.str(ctx) + "$COUT"); +        connect_port(ctx, new_carry.get(), feedin.get(), ctx->id("COUT")); +        chain_in.cell->ports[chain_in.port].net = nullptr; +        connect_port(ctx, new_carry.get(), chain_in.cell, chain_in.port); + +        CellInfo *feedin_ptr = feedin.get(); +        IdString feedin_name = feedin->name; +        ctx->cells[feedin_name] = std::move(feedin); +        IdString new_carry_name = new_carry->name; +        ctx->nets[new_carry_name] = std::move(new_carry); +        return feedin_ptr; +    } + +    // Create a feed out and loop through from the carry chain +    CellInfo *make_carry_feed_out(NetInfo *carry, boost::optional<PortRef> chain_next = boost::optional<PortRef>()) +    { +        std::unique_ptr<CellInfo> feedout = create_ecp5_cell(ctx, ctx->id("CCU2C")); +        feedout->params[ctx->id("INIT0")] = "0"; +        feedout->params[ctx->id("INIT1")] = "10"; // LUT4 = 0; LUT2 = A +        feedout->params[ctx->id("INJECT1_0")] = "NO"; +        feedout->params[ctx->id("INJECT1_1")] = "NO"; + +        PortRef carry_drv = carry->driver; +        carry->driver.cell = nullptr; +        connect_port(ctx, carry, feedout.get(), ctx->id("S0")); + +        std::unique_ptr<NetInfo> new_cin(new NetInfo()); +        new_cin->name = ctx->id(feedout->name.str(ctx) + "$CIN"); +        new_cin->driver = carry_drv; +        carry_drv.cell->ports.at(carry_drv.port).net = new_cin.get(); +        connect_port(ctx, new_cin.get(), feedout.get(), ctx->id("CIN")); + +        if (chain_next) { +            // Loop back into LUT4_1 for feedthrough +            connect_port(ctx, carry, feedout.get(), id_A1); + +            carry->users.erase(std::remove_if(carry->users.begin(), carry->users.end(), +                                              [chain_next](const PortRef &user) { +                                                  return user.port == chain_next->port && user.cell == chain_next->cell; +                                              }), +                               carry->users.end()); + +            std::unique_ptr<NetInfo> new_cout(new NetInfo()); +            new_cout->name = ctx->id(feedout->name.str(ctx) + "$COUT"); +            connect_port(ctx, new_cout.get(), feedout.get(), ctx->id("COUT")); + +            chain_next->cell->ports[chain_next->port].net = nullptr; +            connect_port(ctx, new_cout.get(), chain_next->cell, chain_next->port); + +            IdString new_cout_name = new_cout->name; +            ctx->nets[new_cout_name] = std::move(new_cout); +        } + +        CellInfo *feedout_ptr = feedout.get(); +        IdString feedout_name = feedout->name; +        ctx->cells[feedout_name] = std::move(feedout); + +        IdString new_cin_name = new_cin->name; +        ctx->nets[new_cin_name] = std::move(new_cin); + +        return feedout_ptr; +    } + +    // Split a carry chain into multiple legal chains +    std::vector<CellChain> split_carry_chain(CellChain &carryc) +    { +        bool start_of_chain = true; +        std::vector<CellChain> chains; +        const int max_length = (ctx->chip_info->width - 4) * 4 - 2; +        auto curr_cell = carryc.cells.begin(); +        while (curr_cell != carryc.cells.end()) { +            CellInfo *cell = *curr_cell; +            if (start_of_chain) { +                chains.emplace_back(); +                start_of_chain = false; +                if (cell->ports.at(ctx->id("CIN")).net) { +                    // CIN is not constant and not part of a chain. Must feed in from fabric +                    PortRef inport; +                    inport.cell = cell; +                    inport.port = ctx->id("CIN"); +                    CellInfo *feedin = make_carry_feed_in(cell->ports.at(ctx->id("CIN")).net, inport); +                    chains.back().cells.push_back(feedin); +                } +            } +            chains.back().cells.push_back(cell); +            bool split_chain = int(chains.back().cells.size()) > max_length; +            if (split_chain) { +                CellInfo *passout = make_carry_feed_out(cell->ports.at(ctx->id("COUT")).net); +                chains.back().cells.back() = passout; +                start_of_chain = true; +            } else { +                NetInfo *carry_net = cell->ports.at(ctx->id("COUT")).net; +                bool at_end = (curr_cell == carryc.cells.end() - 1); +                if (carry_net != nullptr && (carry_net->users.size() > 1 || at_end)) { +                    boost::optional<PortRef> nextport; +                    if (!at_end) { +                        auto next_cell = *(curr_cell + 1); +                        PortRef nextpr; +                        nextpr.cell = next_cell; +                        nextpr.port = ctx->id("CIN"); +                        nextport = nextpr; +                    } +                    CellInfo *passout = make_carry_feed_out(cell->ports.at(ctx->id("COUT")).net, nextport); +                    chains.back().cells.push_back(passout); +                } +                ++curr_cell; +            } +        } +        return chains; +    } + +    // Pack carries and set up appropriate relative constraints +    void pack_carries() +    { +        log_info("Packing carries...\n"); +        // Find all chains (including single carry cells) +        auto carry_chains = find_chains( +                ctx, [](const Context *ctx, const CellInfo *cell) { return is_carry(ctx, cell); }, +                [](const Context *ctx, const CellInfo *cell) { +                    return net_driven_by(ctx, cell->ports.at(ctx->id("CIN")).net, is_carry, ctx->id("COUT")); +                }, +                [](const Context *ctx, const CellInfo *cell) { +                    return net_only_drives(ctx, cell->ports.at(ctx->id("COUT")).net, is_carry, ctx->id("CIN"), false); +                }, +                1); +        std::vector<CellChain> all_chains; + +        // Chain splitting +        for (auto &base_chain : carry_chains) { +            if (ctx->verbose) { +                log_info("Found carry chain: \n"); +                for (auto entry : base_chain.cells) +                    log_info("     %s\n", entry->name.c_str(ctx)); +                log_info("\n"); +            } +            std::vector<CellChain> split_chains = split_carry_chain(base_chain); +            for (auto &chain : split_chains) { +                all_chains.push_back(chain); +            } +        } + +        std::vector<std::vector<CellInfo *>> packed_chains; + +        // Chain packing +        std::vector<std::tuple<CellInfo *, CellInfo *, int>> ff_packing; +        for (auto &chain : all_chains) { +            int cell_count = 0; +            std::vector<CellInfo *> tile_ffs; +            std::vector<CellInfo *> packed_chain; +            for (auto &cell : chain.cells) { +                if (cell_count % 4 == 0) +                    tile_ffs.clear(); +                std::unique_ptr<CellInfo> slice = +                        create_ecp5_cell(ctx, ctx->id("TRELLIS_SLICE"), cell->name.str(ctx) + "$CCU2_SLICE"); + +                ccu2c_to_slice(ctx, cell, slice.get()); + +                CellInfo *ff0 = nullptr; +                NetInfo *f0net = slice->ports.at(ctx->id("F0")).net; +                if (f0net != nullptr) { +                    ff0 = net_only_drives(ctx, f0net, is_ff, ctx->id("DI"), false); +                    if (ff0 != nullptr && can_add_ff_to_tile(tile_ffs, ff0)) { +                        ff_packing.push_back(std::make_tuple(ff0, slice.get(), 0)); +                        tile_ffs.push_back(ff0); +                        packed_cells.insert(ff0->name); +                    } +                } + +                CellInfo *ff1 = nullptr; +                NetInfo *f1net = slice->ports.at(ctx->id("F1")).net; +                if (f1net != nullptr) { +                    ff1 = net_only_drives(ctx, f1net, is_ff, ctx->id("DI"), false); +                    if (ff1 != nullptr && (ff0 == nullptr || can_pack_ffs(ff0, ff1)) && +                        can_add_ff_to_tile(tile_ffs, ff1)) { +                        ff_packing.push_back(std::make_tuple(ff1, slice.get(), 1)); +                        tile_ffs.push_back(ff1); +                        packed_cells.insert(ff1->name); +                    } +                } +                packed_chain.push_back(slice.get()); +                new_cells.push_back(std::move(slice)); +                packed_cells.insert(cell->name); +                cell_count++; +            } +            packed_chains.push_back(packed_chain); +        } + +        for (auto ff : ff_packing) +            ff_to_slice(ctx, std::get<0>(ff), std::get<1>(ff), std::get<2>(ff), true); + +        // Relative chain placement +        for (auto &chain : packed_chains) { +            chain.at(0)->constr_abs_z = true; +            chain.at(0)->constr_z = 0; +            for (int i = 1; i < int(chain.size()); i++) { +                chain.at(i)->constr_x = (i / 4); +                chain.at(i)->constr_y = 0; +                chain.at(i)->constr_z = i % 4; +                chain.at(i)->constr_abs_z = true; +                chain.at(i)->constr_parent = chain.at(0); +                chain.at(0)->constr_children.push_back(chain.at(i)); +            } +        } + +        flush_cells(); +    } + +    // Pack distributed RAM +    void pack_dram() +    { +        for (auto cell : sorted(ctx->cells)) { +            CellInfo *ci = cell.second; +            if (is_dpram(ctx, ci)) { + +                // Create RAMW slice +                std::unique_ptr<CellInfo> ramw_slice = +                        create_ecp5_cell(ctx, ctx->id("TRELLIS_SLICE"), ci->name.str(ctx) + "$RAMW_SLICE"); +                dram_to_ramw(ctx, ci, ramw_slice.get()); + +                // Create actual RAM slices +                std::unique_ptr<CellInfo> ram0_slice = +                        create_ecp5_cell(ctx, ctx->id("TRELLIS_SLICE"), ci->name.str(ctx) + "$DPRAM0_SLICE"); +                dram_to_ram_slice(ctx, ci, ram0_slice.get(), ramw_slice.get(), 0); + +                std::unique_ptr<CellInfo> ram1_slice = +                        create_ecp5_cell(ctx, ctx->id("TRELLIS_SLICE"), ci->name.str(ctx) + "$DPRAM1_SLICE"); +                dram_to_ram_slice(ctx, ci, ram1_slice.get(), ramw_slice.get(), 1); + +                // Disconnect ports of original cell after packing +                disconnect_port(ctx, ci, id_WCK); +                disconnect_port(ctx, ci, id_WRE); + +                disconnect_port(ctx, ci, ctx->id("RAD[0]")); +                disconnect_port(ctx, ci, ctx->id("RAD[1]")); +                disconnect_port(ctx, ci, ctx->id("RAD[2]")); +                disconnect_port(ctx, ci, ctx->id("RAD[3]")); + +                // Attempt to pack FFs into RAM slices +                std::vector<std::tuple<CellInfo *, CellInfo *, int>> ff_packing; +                std::vector<CellInfo *> tile_ffs; +                for (auto slice : {ram0_slice.get(), ram1_slice.get()}) { +                    CellInfo *ff0 = nullptr; +                    NetInfo *f0net = slice->ports.at(ctx->id("F0")).net; +                    if (f0net != nullptr) { +                        ff0 = net_only_drives(ctx, f0net, is_ff, ctx->id("DI"), false); +                        if (ff0 != nullptr && can_add_ff_to_tile(tile_ffs, ff0)) { +                            if (can_pack_ff_dram(slice, ff0)) { +                                ff_packing.push_back(std::make_tuple(ff0, slice, 0)); +                                tile_ffs.push_back(ff0); +                                packed_cells.insert(ff0->name); +                            } +                        } +                    } + +                    CellInfo *ff1 = nullptr; +                    NetInfo *f1net = slice->ports.at(ctx->id("F1")).net; +                    if (f1net != nullptr) { +                        ff1 = net_only_drives(ctx, f1net, is_ff, ctx->id("DI"), false); +                        if (ff1 != nullptr && (ff0 == nullptr || can_pack_ffs(ff0, ff1)) && +                            can_add_ff_to_tile(tile_ffs, ff1)) { +                            if (can_pack_ff_dram(slice, ff1)) { +                                ff_packing.push_back(std::make_tuple(ff1, slice, 1)); +                                tile_ffs.push_back(ff1); +                                packed_cells.insert(ff1->name); +                            } +                        } +                    } +                } + +                for (auto ff : ff_packing) +                    ff_to_slice(ctx, std::get<0>(ff), std::get<1>(ff), std::get<2>(ff), true); + +                // Setup placement constraints +                ram0_slice->constr_abs_z = true; +                ram0_slice->constr_z = 0; + +                ram1_slice->constr_parent = ram0_slice.get(); +                ram1_slice->constr_abs_z = true; +                ram1_slice->constr_x = 0; +                ram1_slice->constr_y = 0; +                ram1_slice->constr_z = 1; +                ram0_slice->constr_children.push_back(ram1_slice.get()); + +                ramw_slice->constr_parent = ram0_slice.get(); +                ramw_slice->constr_abs_z = true; +                ramw_slice->constr_x = 0; +                ramw_slice->constr_y = 0; +                ramw_slice->constr_z = 2; +                ram0_slice->constr_children.push_back(ramw_slice.get()); + +                new_cells.push_back(std::move(ram0_slice)); +                new_cells.push_back(std::move(ram1_slice)); +                new_cells.push_back(std::move(ramw_slice)); +                packed_cells.insert(ci->name); +            } +        } +        flush_cells(); +    } +      // Pack LUTs that have been paired together      void pack_lut_pairs()      { @@ -394,14 +747,12 @@ class Ecp5Packer          flush_cells();      } -    void set_lut_input_constant(CellInfo *cell, IdString input, bool value) +    int make_init_with_const_input(int init, int input, bool value)      { -        int index = std::string("ABCD").find(input.str(ctx)); -        int init = int_or_default(cell->params, ctx->id("INIT"));          int new_init = 0;          for (int i = 0; i < 16; i++) { -            if (((i >> index) & 0x1) != value) { -                int other_i = (i & (~(1 << index))) | (value << index); +            if (((i >> input) & 0x1) != value) { +                int other_i = (i & (~(1 << input))) | (value << input);                  if ((init >> other_i) & 0x1)                      new_init |= (1 << i);              } else { @@ -409,10 +760,41 @@ class Ecp5Packer                      new_init |= (1 << i);              }          } +        return new_init; +    } + +    void set_lut_input_constant(CellInfo *cell, IdString input, bool value) +    { +        int index = std::string("ABCD").find(input.str(ctx)); +        int init = int_or_default(cell->params, ctx->id("INIT")); +        int new_init = make_init_with_const_input(init, index, value);          cell->params[ctx->id("INIT")] = std::to_string(new_init);          cell->ports.at(input).net = nullptr;      } +    void set_ccu2c_input_constant(CellInfo *cell, IdString input, bool value) +    { +        std::string input_str = input.str(ctx); +        int lut = std::stoi(input_str.substr(1)); +        int index = std::string("ABCD").find(input_str[0]); +        int init = int_or_default(cell->params, ctx->id("INIT" + std::to_string(lut))); +        int new_init = make_init_with_const_input(init, index, value); +        cell->params[ctx->id("INIT" + std::to_string(lut))] = std::to_string(new_init); +        cell->ports.at(input).net = nullptr; +    } + +    bool is_ccu2c_port_high(CellInfo *cell, IdString input) +    { +        if (!cell->ports.count(input)) +            return true; // disconnected port is high +        if (cell->ports.at(input).net == nullptr || cell->ports.at(input).net->name == ctx->id("$PACKER_VCC_NET")) +            return true; // disconnected or tied-high port +        if (cell->ports.at(input).net->driver.cell != nullptr && +            cell->ports.at(input).net->driver.cell->type == ctx->id("VCC")) +            return true; // pre-pack high +        return false; +    } +      // Merge a net into a constant net      void set_net_constant(const Context *ctx, NetInfo *orig, NetInfo *constnet, bool constval)      { @@ -427,6 +809,37 @@ class Ecp5Packer                  } else if (is_ff(ctx, uc) && user.port == ctx->id("CE")) {                      uc->params[ctx->id("CEMUX")] = constval ? "1" : "0";                      uc->ports[user.port].net = nullptr; +                } else if (is_carry(ctx, uc)) { +                    if (constval && +                        (user.port == id_A0 || user.port == id_A1 || user.port == id_B0 || user.port == id_B1 || +                         user.port == id_C0 || user.port == id_C1 || user.port == id_D0 || user.port == id_D1)) { +                        // Input tied high, nothing special to do (bitstream gen will auto-enable tie-high) +                        uc->ports[user.port].net = nullptr; +                    } else if (!constval) { +                        if (user.port == id_A0 || user.port == id_A1 || user.port == id_B0 || user.port == id_B1) { +                            // These inputs can be switched to tie-high without consequence +                            set_ccu2c_input_constant(uc, user.port, constval); +                        } else if (user.port == id_C0 && is_ccu2c_port_high(uc, id_D0)) { +                            // Partner must be tied high +                            set_ccu2c_input_constant(uc, user.port, constval); +                        } else if (user.port == id_D0 && is_ccu2c_port_high(uc, id_C0)) { +                            // Partner must be tied high +                            set_ccu2c_input_constant(uc, user.port, constval); +                        } else if (user.port == id_C1 && is_ccu2c_port_high(uc, id_D1)) { +                            // Partner must be tied high +                            set_ccu2c_input_constant(uc, user.port, constval); +                        } else if (user.port == id_D1 && is_ccu2c_port_high(uc, id_C1)) { +                            // Partner must be tied high +                            set_ccu2c_input_constant(uc, user.port, constval); +                        } else { +                            // Not allowed to change to a tie-high +                            uc->ports[user.port].net = constnet; +                            constnet->users.push_back(user); +                        } +                    } else { +                        uc->ports[user.port].net = constnet; +                        constnet->users.push_back(user); +                    }                  } else if (is_ff(ctx, uc) && user.port == ctx->id("LSR") &&                             ((!constval && str_or_default(uc->params, ctx->id("LSRMUX"), "LSR") == "LSR") ||                              (constval && str_or_default(uc->params, ctx->id("LSRMUX"), "LSR") == "INV"))) { @@ -501,12 +914,15 @@ class Ecp5Packer      {          pack_io();          pack_constants(); +        pack_dram(); +        pack_carries();          find_lutff_pairs();          pack_lut5s();          pair_luts();          pack_lut_pairs();          pack_remaining_luts();          pack_remaining_ffs(); +        ctx->check();      }    private: @@ -549,6 +965,13 @@ void Arch::assignArchInfo()      for (auto cell : sorted(cells)) {          CellInfo *ci = cell.second;          if (ci->type == id_TRELLIS_SLICE) { + +            ci->sliceInfo.using_dff = false; +            if (ci->ports.count(id_Q0) && ci->ports[id_Q0].net != nullptr) +                ci->sliceInfo.using_dff = true; +            if (ci->ports.count(id_Q1) && ci->ports[id_Q1].net != nullptr) +                ci->sliceInfo.using_dff = true; +              if (ci->ports.count(id_CLK) && ci->ports[id_CLK].net != nullptr)                  ci->sliceInfo.clk_sig = ci->ports[id_CLK].net->name;              else diff --git a/ecp5/trellis_import.py b/ecp5/trellis_import.py index de8e9958..9a26b605 100755 --- a/ecp5/trellis_import.py +++ b/ecp5/trellis_import.py @@ -128,7 +128,12 @@ def process_loc_globals(chip):          for x in range(0, max_col+1):              quad = chip.global_data.get_quadrant(y, x)              tapdrv = chip.global_data.get_tap_driver(y, x) -            global_data[x, y] = (quadrants.index(quad), int(tapdrv.dir), tapdrv.col) +            if tapdrv.col == x: +                spinedrv = chip.global_data.get_spine_driver(quad, x) +                spine = (spinedrv.second, spinedrv.first) +            else: +                spine = (-1, -1) +            global_data[x, y] = (quadrants.index(quad), int(tapdrv.dir), tapdrv.col, spine)  def get_wire_type(name):      if "H00" in name or "V00" in name: @@ -282,6 +287,8 @@ def write_database(dev_name, chip, ddrg, endianness):              bba.u16(global_data[x, y][2], "tap_col")              bba.u8(global_data[x, y][1], "tap_dir")              bba.u8(global_data[x, y][0], "quad") +            bba.u16(global_data[x, y][3][1], "spine_row") +            bba.u16(global_data[x, y][3][0], "spine_col")      for package, pkgdata in sorted(packages.items()):          bba.l("package_data_%s" % package, "PackagePinPOD") diff --git a/generic/arch.cc b/generic/arch.cc index 583c74d8..3e95159a 100644 --- a/generic/arch.cc +++ b/generic/arch.cc @@ -184,20 +184,11 @@ void Arch::setGroupDecal(GroupId group, DecalXY decalxy)      refreshUiGroup(group);  } -void Arch::setWireAttr(IdString wire, IdString key, const std::string &value) -{ -    wires.at(wire).attrs[key] = value; -} +void Arch::setWireAttr(IdString wire, IdString key, const std::string &value) { wires.at(wire).attrs[key] = value; } -void Arch::setPipAttr(IdString pip, IdString key, const std::string &value) -{ -    pips.at(pip).attrs[key] = value; -} +void Arch::setPipAttr(IdString pip, IdString key, const std::string &value) { pips.at(pip).attrs[key] = value; } -void Arch::setBelAttr(IdString bel, IdString key, const std::string &value) -{ -    bels.at(bel).attrs[key] = value; -} +void Arch::setBelAttr(IdString bel, IdString key, const std::string &value) { bels.at(bel).attrs[key] = value; }  // --------------------------------------------------------------- diff --git a/gui/designwidget.cc b/gui/designwidget.cc index c49df085..a45752fc 100644 --- a/gui/designwidget.cc +++ b/gui/designwidget.cc @@ -37,17 +37,14 @@ TreeView::~TreeView() {}  void TreeView::mouseMoveEvent(QMouseEvent *event)
  {
      QModelIndex index = indexAt(event->pos());
 -    if (index!=current) {
 +    if (index != current) {
          current = index;
          Q_EMIT hoverIndexChanged(index);
      }
      QTreeView::mouseMoveEvent(event);
  }
 -void TreeView::leaveEvent(QEvent *event)
 -{
 -    Q_EMIT hoverIndexChanged(QModelIndex());
 -}
 +void TreeView::leaveEvent(QEvent *event) { Q_EMIT hoverIndexChanged(QModelIndex()); }
  DesignWidget::DesignWidget(QWidget *parent) : QWidget(parent), ctx(nullptr), selectionModel(nullptr)
  {
 @@ -827,24 +824,28 @@ void DesignWidget::onHoverIndexChanged(QModelIndex index)      if (index.isValid()) {
          TreeModel::Item *item = treeModel->nodeFromIndex(index);
          if (item->type() != ElementType::NONE) {
 -            Q_EMIT hover(getDecals(item->type(), item->id()).at(0));
 +            std::vector<DecalXY> decals = getDecals(item->type(), item->id());
 +            if (decals.size() > 0)
 +                Q_EMIT hover(decals.at(0));
              return;
          }
      }
 -    Q_EMIT hover(DecalXY());    
 +    Q_EMIT hover(DecalXY());
  }
  void DesignWidget::onHoverPropertyChanged(QtBrowserItem *item)
  {
 -    if (item!=nullptr) {
 +    if (item != nullptr) {
          QtProperty *selectedProperty = item->property();
          ElementType type = getElementTypeByName(selectedProperty->propertyId());
          if (type != ElementType::NONE) {
              IdString value = ctx->id(selectedProperty->valueText().toStdString());
 -            if (value!=IdString()) {
 +            if (value != IdString()) {
                  auto node = treeModel->nodeForIdType(type, value);
                  if (node) {
 -                    Q_EMIT hover(getDecals((*node)->type(), (*node)->id()).at(0));
 +                    std::vector<DecalXY> decals = getDecals((*node)->type(), (*node)->id());
 +                    if (decals.size() > 0)
 +                        Q_EMIT hover(decals.at(0));
                      return;
                  }
              }
 diff --git a/gui/designwidget.h b/gui/designwidget.h index 91da556a..0248d2c7 100644 --- a/gui/designwidget.h +++ b/gui/designwidget.h @@ -20,9 +20,9 @@  #ifndef DESIGNWIDGET_H
  #define DESIGNWIDGET_H
 +#include <QMouseEvent>
  #include <QTreeView>
  #include <QVariant>
 -#include <QMouseEvent>
  #include "nextpnr.h"
  #include "qtgroupboxpropertybrowser.h"
  #include "qtpropertymanager.h"
 @@ -35,7 +35,7 @@ NEXTPNR_NAMESPACE_BEGIN  class TreeView : public QTreeView
  {
      Q_OBJECT
 -  
 +
    public:
      explicit TreeView(QWidget *parent = 0);
      ~TreeView();
 @@ -44,6 +44,7 @@ class TreeView : public QTreeView    Q_SIGNALS:
      void hoverIndexChanged(QModelIndex index);
 +
    private:
      QModelIndex current;
  };
 diff --git a/ice40/arch.cc b/ice40/arch.cc index 28d20adb..eb26ae5a 100644 --- a/ice40/arch.cc +++ b/ice40/arch.cc @@ -414,7 +414,6 @@ std::vector<std::pair<IdString, std::string>> Arch::getPipAttrs(PipId pip) const      return ret;  } -  // -----------------------------------------------------------------------  BelId Arch::getPackagePinBel(const std::string &pin) const @@ -966,7 +965,6 @@ void Arch::assignArchInfo()  void Arch::assignCellInfo(CellInfo *cell)  { -    cell->belType = cell->type;      if (cell->type == id_ICESTORM_LC) {          cell->lcInfo.dffEnable = bool_or_default(cell->params, id_DFF_ENABLE);          cell->lcInfo.carryEnable = bool_or_default(cell->params, id_CARRY_ENABLE); @@ -983,6 +981,8 @@ void Arch::assignCellInfo(CellInfo *cell)              cell->lcInfo.inputCount++;          if (get_net_or_empty(cell, id_I3))              cell->lcInfo.inputCount++; +    } else if (cell->type == id_SB_IO) { +        cell->ioInfo.lvds = str_or_default(cell->params, id_IO_STANDARD, "SB_LVCMOS") == "SB_LVDS_INPUT";      }  } diff --git a/ice40/arch.h b/ice40/arch.h index 37f663d9..27d5db9f 100644 --- a/ice40/arch.h +++ b/ice40/arch.h @@ -817,7 +817,7 @@ struct Arch : BaseCtx      bool isBelLocationValid(BelId bel) const;      // Helper function for above -    bool logicCellsCompatible(const CellInfo** it, const size_t size) const; +    bool logicCellsCompatible(const CellInfo **it, const size_t size) const;      // -------------------------------------------------      // Assign architecure-specific arguments to nets and cells, which must be diff --git a/ice40/arch_place.cc b/ice40/arch_place.cc index c69fd34f..c97b9c26 100644 --- a/ice40/arch_place.cc +++ b/ice40/arch_place.cc @@ -27,14 +27,14 @@  NEXTPNR_NAMESPACE_BEGIN -bool Arch::logicCellsCompatible(const CellInfo** it, const size_t size) const +bool Arch::logicCellsCompatible(const CellInfo **it, const size_t size) const  {      bool dffs_exist = false, dffs_neg = false;      const NetInfo *cen = nullptr, *clk = nullptr, *sr = nullptr;      int locals_count = 0; -    for (auto cell : boost::make_iterator_range(it, it+size)) { -        NPNR_ASSERT(cell->belType == id_ICESTORM_LC); +    for (auto cell : boost::make_iterator_range(it, it + size)) { +        NPNR_ASSERT(cell->type == id_ICESTORM_LC);          if (cell->lcInfo.dffEnable) {              if (!dffs_exist) {                  dffs_exist = true; @@ -139,6 +139,27 @@ bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const                  }              }          } +        Loc ioLoc = getBelLocation(bel); +        Loc compLoc = ioLoc; +        compLoc.z = 1 - compLoc.z; + +        // Check LVDS pairing +        if (cell->ioInfo.lvds) { +            // Check correct z and complement location is free +            if (ioLoc.z != 0) +                return false; +            BelId compBel = getBelByLocation(compLoc); +            CellInfo *compCell = getBoundBelCell(compBel); +            if (compCell) +                return false; +        } else { +            // Check LVDS IO is not placed at complement location +            BelId compBel = getBelByLocation(compLoc); +            CellInfo *compCell = getBoundBelCell(compBel); +            if (compCell && compCell->ioInfo.lvds) +                return false; +        } +          return getBelPackagePin(bel) != "";      } else if (cell->type == id_SB_GB) {          NPNR_ASSERT(cell->ports.at(id_GLOBAL_BUFFER_OUTPUT).net != nullptr); diff --git a/ice40/archdefs.h b/ice40/archdefs.h index 360617fd..c04033e7 100644 --- a/ice40/archdefs.h +++ b/ice40/archdefs.h @@ -134,7 +134,6 @@ struct NetInfo;  struct ArchCellInfo  { -    IdString belType;      union      {          struct @@ -145,6 +144,11 @@ struct ArchCellInfo              int inputCount;              const NetInfo *clk, *cen, *sr;          } lcInfo; +        struct +        { +            bool lvds; +            // TODO: clk packing checks... +        } ioInfo;      };  }; diff --git a/ice40/bitstream.cc b/ice40/bitstream.cc index 4ea91011..e56ed37d 100644 --- a/ice40/bitstream.cc +++ b/ice40/bitstream.cc @@ -447,6 +447,8 @@ void write_asc(const Context *ctx, std::ostream &out)              unsigned pin_type = get_param_or_def(cell.second.get(), ctx->id("PIN_TYPE"));
              bool neg_trigger = get_param_or_def(cell.second.get(), ctx->id("NEG_TRIGGER"));
              bool pullup = get_param_or_def(cell.second.get(), ctx->id("PULLUP"));
 +            bool lvds = get_param_str_or_def(cell.second.get(), ctx->id("IO_STANDARD")) == "SB_LVDS_INPUT";
 +
              for (int i = 0; i < 6; i++) {
                  bool val = (pin_type >> i) & 0x01;
                  set_config(ti, config.at(y).at(x), "IOB_" + std::to_string(z) + ".PINTYPE_" + std::to_string(i), val);
 @@ -457,10 +459,17 @@ void write_asc(const Context *ctx, std::ostream &out)              std::tie(iex, iey, iez) = ieren;
              NPNR_ASSERT(iez != -1);
 -            bool input_en = false;
 -            if ((ctx->wire_to_net[ctx->getBelPinWire(bel, id_D_IN_0).index] != nullptr) ||
 -                (ctx->wire_to_net[ctx->getBelPinWire(bel, id_D_IN_1).index] != nullptr)) {
 -                input_en = true;
 +            bool input_en;
 +            if (lvds) {
 +                input_en = false;
 +                pullup = false;
 +            } else {
 +                if ((ctx->wire_to_net[ctx->getBelPinWire(bel, id_D_IN_0).index] != nullptr) ||
 +                    (ctx->wire_to_net[ctx->getBelPinWire(bel, id_D_IN_1).index] != nullptr)) {
 +                    input_en = true;
 +                } else {
 +                    input_en = false;
 +                }
              }
              if (ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K) {
 @@ -478,6 +487,31 @@ void write_asc(const Context *ctx, std::ostream &out)                      set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_35", !pullup);
                  }
              }
 +
 +            if (lvds) {
 +                NPNR_ASSERT(z == 0);
 +                set_config(ti, config.at(y).at(x), "IoCtrl.LVDS", true);
 +                // Set comp IO config
 +                auto comp_ieren = get_ieren(bi, x, y, 1);
 +                int ciex, ciey, ciez;
 +                std::tie(ciex, ciey, ciez) = comp_ieren;
 +
 +                if (ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K) {
 +                    set_config(ti, config.at(ciey).at(ciex), "IoCtrl.IE_" + std::to_string(ciez), !input_en);
 +                    set_config(ti, config.at(ciey).at(ciex), "IoCtrl.REN_" + std::to_string(ciez), !pullup);
 +                } else {
 +                    set_config(ti, config.at(ciey).at(ciex), "IoCtrl.IE_" + std::to_string(ciez), input_en);
 +                    set_config(ti, config.at(ciey).at(ciex), "IoCtrl.REN_" + std::to_string(ciez), !pullup);
 +                }
 +
 +                if (ctx->args.type == ArchArgs::UP5K) {
 +                    if (ciez == 0) {
 +                        set_config(ti, config.at(ciey).at(ciex), "IoCtrl.cf_bit_39", !pullup);
 +                    } else if (iez == 1) {
 +                        set_config(ti, config.at(ciey).at(ciex), "IoCtrl.cf_bit_35", !pullup);
 +                    }
 +                }
 +            }
          } else if (cell.second->type == ctx->id("SB_GB")) {
              // no cell config bits
          } else if (cell.second->type == ctx->id("ICESTORM_RAM")) {
 @@ -630,6 +664,13 @@ void write_asc(const Context *ctx, std::ostream &out)              int iex, iey, iez;
              std::tie(iex, iey, iez) = ieren;
              if (iez != -1) {
 +                // IO is not actually unused if part of an LVDS pair
 +                if (z == 1) {
 +                    BelId lvds0 = ctx->getBelByLocation(Loc{x, y, 0});
 +                    const CellInfo *lvds0cell = ctx->getBoundBelCell(lvds0);
 +                    if (lvds0cell != nullptr && lvds0cell->ioInfo.lvds)
 +                        continue;
 +                }
                  set_ie_bit_logical(ctx, ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), true);
                  set_ie_bit_logical(ctx, ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), false);
              }
 @@ -870,7 +911,7 @@ bool read_asc(Context *ctx, std::istream &in)              }
              if (isUsed) {
                  NetInfo *net = ctx->wire_to_net[pi.dst];
 -                if (net!=nullptr) {
 +                if (net != nullptr) {
                      WireId wire;
                      wire.index = pi.dst;
                      ctx->unbindWire(wire);
 diff --git a/ice40/cells.cc b/ice40/cells.cc index e79a1fda..886dae2a 100644 --- a/ice40/cells.cc +++ b/ice40/cells.cc @@ -243,6 +243,8 @@ std::unique_ptr<CellInfo> create_ice_cell(Context *ctx, IdString type, std::stri          add_port(ctx, new_cell.get(), "LOCK", PORT_OUT);          add_port(ctx, new_cell.get(), "PLLOUT_A", PORT_OUT);          add_port(ctx, new_cell.get(), "PLLOUT_B", PORT_OUT); +        add_port(ctx, new_cell.get(), "PLLOUTGLOBALA", PORT_OUT); +        add_port(ctx, new_cell.get(), "PLLOUTGLOBALB", PORT_OUT);      } else {          log_error("unable to create iCE40 cell of type %s", type.c_str(ctx));      } @@ -312,7 +314,7 @@ void dff_to_lc(const Context *ctx, CellInfo *dff, CellInfo *lc, bool pass_thru_l      replace_port(dff, ctx->id("Q"), lc, ctx->id("O"));  } -void nxio_to_sb(Context *ctx, CellInfo *nxio, CellInfo *sbio) +void nxio_to_sb(Context *ctx, CellInfo *nxio, CellInfo *sbio, std::unordered_set<IdString> &todelete_cells)  {      if (nxio->type == ctx->id("$nextpnr_ibuf")) {          sbio->params[ctx->id("PIN_TYPE")] = "1"; @@ -339,12 +341,16 @@ void nxio_to_sb(Context *ctx, CellInfo *nxio, CellInfo *sbio)          sbio->params[ctx->id("PIN_TYPE")] = "41";          replace_port(tbuf, ctx->id("A"), sbio, ctx->id("D_OUT_0"));          replace_port(tbuf, ctx->id("E"), sbio, ctx->id("OUTPUT_ENABLE")); -        ctx->nets.erase(donet->name); -        if (!donet->users.empty()) + +        if (donet->users.size() > 1) { +            for (auto user : donet->users) +                log_info("     remaining tristate user: %s.%s\n", user.cell->name.c_str(ctx), user.port.c_str(ctx));              log_error("unsupported tristate IO pattern for IO buffer '%s', "                        "instantiate SB_IO manually to ensure correct behaviour\n",                        nxio->name.c_str(ctx)); -        ctx->cells.erase(tbuf->name); +        } +        ctx->nets.erase(donet->name); +        todelete_cells.insert(tbuf->name);      }  } diff --git a/ice40/cells.h b/ice40/cells.h index 16135448..054388ac 100644 --- a/ice40/cells.h +++ b/ice40/cells.h @@ -98,7 +98,7 @@ void lut_to_lc(const Context *ctx, CellInfo *lut, CellInfo *lc, bool no_dff = tr  void dff_to_lc(const Context *ctx, CellInfo *dff, CellInfo *lc, bool pass_thru_lut = false);  // Convert a nextpnr IO buffer to a SB_IO -void nxio_to_sb(Context *ctx, CellInfo *nxio, CellInfo *sbio); +void nxio_to_sb(Context *ctx, CellInfo *nxio, CellInfo *sbio, std::unordered_set<IdString> &todelete_cells);  // Return true if a port is a clock port  bool is_clock_port(const BaseCtx *ctx, const PortRef &port); diff --git a/ice40/chains.cc b/ice40/chains.cc index bb20b60b..fb361d2d 100644 --- a/ice40/chains.cc +++ b/ice40/chains.cc @@ -21,6 +21,7 @@  #include <algorithm>  #include <vector>  #include "cells.h" +#include "chain_utils.h"  #include "design_utils.h"  #include "log.h"  #include "place_common.h" @@ -28,45 +29,6 @@  NEXTPNR_NAMESPACE_BEGIN -struct CellChain -{ -    std::vector<CellInfo *> cells; -}; - -// Generic chain finder -template <typename F1, typename F2, typename F3> -std::vector<CellChain> find_chains(const Context *ctx, F1 cell_type_predicate, F2 get_previous, F3 get_next, -                                   size_t min_length = 2) -{ -    std::set<IdString> chained; -    std::vector<CellChain> chains; -    for (auto cell : sorted(ctx->cells)) { -        if (chained.find(cell.first) != chained.end()) -            continue; -        CellInfo *ci = cell.second; -        if (cell_type_predicate(ctx, ci)) { -            CellInfo *start = ci; -            CellInfo *prev_start = ci; -            while (prev_start != nullptr) { -                start = prev_start; -                prev_start = get_previous(ctx, start); -            } -            CellChain chain; -            CellInfo *end = start; -            while (end != nullptr) { -                chain.cells.push_back(end); -                end = get_next(ctx, end); -            } -            if (chain.cells.size() >= min_length) { -                chains.push_back(chain); -                for (auto c : chain.cells) -                    chained.insert(c->name); -            } -        } -    } -    return chains; -} -  class ChainConstrainer  {    private: @@ -97,7 +59,8 @@ class ChainConstrainer              }              tile.push_back(cell);              chains.back().cells.push_back(cell); -            bool split_chain = (!ctx->logicCellsCompatible(tile.data(), tile.size())) || (int(chains.back().cells.size()) > max_length); +            bool split_chain = (!ctx->logicCellsCompatible(tile.data(), tile.size())) || +                               (int(chains.back().cells.size()) > max_length);              if (split_chain) {                  CellInfo *passout = make_carry_pass_out(cell->ports.at(ctx->id("COUT")));                  tile.pop_back(); diff --git a/ice40/constids.inc b/ice40/constids.inc index adcea7ad..dad08e59 100644 --- a/ice40/constids.inc +++ b/ice40/constids.inc @@ -435,3 +435,4 @@ X(ICESTORM_SPRAM)  X(DFF_ENABLE)  X(CARRY_ENABLE)  X(NEG_CLK) +X(IO_STANDARD)
\ No newline at end of file diff --git a/ice40/delay.cc b/ice40/delay.cc index d76aaefb..54905551 100644 --- a/ice40/delay.cc +++ b/ice40/delay.cc @@ -121,7 +121,7 @@ struct model_params_t      int delta_sp4;      int delta_sp12; -    static const model_params_t &get(const ArchArgs& args) +    static const model_params_t &get(const ArchArgs &args)      {          static const model_params_t model_hx8k = {588,    129253, 8658, 118333, 23915, -73105, 57696,                                                    -86797, 89,     3706, -316,   -575,  -158,   -296}; diff --git a/ice40/gfx.cc b/ice40/gfx.cc index 74338b8d..320081c5 100644 --- a/ice40/gfx.cc +++ b/ice40/gfx.cc @@ -21,7 +21,8 @@  NEXTPNR_NAMESPACE_BEGIN -void gfxTileWire(std::vector<GraphicElement> &g, int x, int y, int w, int h, GfxTileWireId id, GraphicElement::style_t style) +void gfxTileWire(std::vector<GraphicElement> &g, int x, int y, int w, int h, GfxTileWireId id, +                 GraphicElement::style_t style)  {      GraphicElement el;      el.type = GraphicElement::TYPE_LINE; @@ -462,7 +463,7 @@ void gfxTileWire(std::vector<GraphicElement> &g, int x, int y, int w, int h, Gfx              g.push_back(el);          } -        if (idx <= 15 && (x == 0 || x == w-1) && y == 1) { +        if (idx <= 15 && (x == 0 || x == w - 1) && y == 1) {              float y1 = y - (0.03 + 0.0025 * (60 - idx - 4));              el.x1 = x2; @@ -478,7 +479,7 @@ void gfxTileWire(std::vector<GraphicElement> &g, int x, int y, int w, int h, Gfx              g.push_back(el);          } -        if (idx >= 4 && (x == 0 || x == w-1) && y == h-2) { +        if (idx >= 4 && (x == 0 || x == w - 1) && y == h - 2) {              float y1 = y + 2.0 - (0.03 + 0.0025 * (60 - idx));              el.x1 = x1; diff --git a/ice40/pack.cc b/ice40/pack.cc index 7c853e0e..edd12f92 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -159,7 +159,7 @@ static void pack_carries(Context *ctx)                              exhausted_cells.find(usr.cell->name) == exhausted_cells.end()) {                              // This clause stops us double-packing cells                              i0_matches.insert(usr.cell->name); -                            if (!i1_net) { +                            if (!i1_net && !usr.cell->ports.at(ctx->id("I2")).net) {                                  // I1 is don't care when disconnected, duplicate I0                                  i1_matches.insert(usr.cell->name);                              } @@ -174,7 +174,7 @@ static void pack_carries(Context *ctx)                              exhausted_cells.find(usr.cell->name) == exhausted_cells.end()) {                              // This clause stops us double-packing cells                              i1_matches.insert(usr.cell->name); -                            if (!i0_net) { +                            if (!i0_net && !usr.cell->ports.at(ctx->id("I1")).net) {                                  // I0 is don't care when disconnected, duplicate I1                                  i0_matches.insert(usr.cell->name);                              } @@ -391,6 +391,8 @@ static bool is_nextpnr_iob(Context *ctx, CellInfo *cell)  static void pack_io(Context *ctx)  {      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"); @@ -410,21 +412,27 @@ static void pack_io(Context *ctx)                  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; +                if (((ci->type == ctx->id("$nextpnr_ibuf") || ci->type == ctx->id("$nextpnr_iobuf")) && +                     net->users.size() > 1) || +                    (ci->type == ctx->id("$nextpnr_obuf") && (net->users.size() > 2 || net->driver.cell != nullptr))) +                    log_error("PACKAGE_PIN of SB_IO '%s' connected to more than a single top level IO.\n", +                              sb->name.c_str(ctx)); +                  if (net != nullptr) { -                    ctx->nets.erase(net->name); +                    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) { -                        ctx->nets.erase(net2->name); +                        delete_nets.insert(net2->name);                      }                  }              } else {                  // Create a SB_IO buffer                  std::unique_ptr<CellInfo> ice_cell =                          create_ice_cell(ctx, ctx->id("SB_IO"), ci->name.str(ctx) + "$sb_io"); -                nxio_to_sb(ctx, ci, ice_cell.get()); +                nxio_to_sb(ctx, ci, ice_cell.get(), packed_cells);                  new_cells.push_back(std::move(ice_cell));                  sb = new_cells.back().get();              } @@ -435,6 +443,9 @@ static void pack_io(Context *ctx)      for (auto pcell : packed_cells) {          ctx->cells.erase(pcell);      } +    for (auto dnet : delete_nets) { +        ctx->nets.erase(dnet); +    }      for (auto &ncell : new_cells) {          ctx->cells[ncell->name] = std::move(ncell);      } @@ -451,6 +462,8 @@ static bool is_logic_port(BaseCtx *ctx, const PortRef &port)  static void insert_global(Context *ctx, NetInfo *net, bool is_reset, bool is_cen, bool is_logic)  { +    log_info("promoting %s%s%s%s\n", net->name.c_str(ctx), is_reset ? " [reset]" : "", is_cen ? " [cen]" : "", is_logic ? " [logic]" : ""); +      std::string glb_name = net->name.str(ctx) + std::string("_$glb_") + (is_reset ? "sr" : (is_cen ? "ce" : "clk"));      std::unique_ptr<CellInfo> gb = create_ice_cell(ctx, ctx->id("SB_GB"), "$gbuf_" + glb_name);      gb->ports[ctx->id("USER_SIGNAL_TO_GLOBAL_BUFFER")].net = net; @@ -717,6 +730,32 @@ static void pack_special(Context *ctx)              NetInfo *pad_packagepin_net = nullptr; +            int pllout_a_used = 0; +            int pllout_b_used = 0; +            for (auto port : ci->ports) { +                PortInfo &pi = port.second; +                if (pi.name == ctx->id("PLLOUTCOREA")) +                    pllout_a_used++; +                if (pi.name == ctx->id("PLLOUTCOREB")) +                    pllout_b_used++; +                if (pi.name == ctx->id("PLLOUTCORE")) +                    pllout_a_used++; +                if (pi.name == ctx->id("PLLOUTGLOBALA")) +                    pllout_a_used++; +                if (pi.name == ctx->id("PLLOUTGLOBALB")) +                    pllout_b_used++; +                if (pi.name == ctx->id("PLLOUTGLOBAL")) +                    pllout_a_used++; +            } + +            if (pllout_a_used > 1) +                log_error("PLL '%s' is using multiple ports mapping to PLLOUT_A output of the PLL\n", +                          ci->name.c_str(ctx)); + +            if (pllout_b_used > 1) +                log_error("PLL '%s' is using multiple ports mapping to PLLOUT_B output of the PLL\n", +                          ci->name.c_str(ctx)); +              for (auto port : ci->ports) {                  PortInfo &pi = port.second;                  std::string newname = pi.name.str(ctx); @@ -730,10 +769,22 @@ static void pack_special(Context *ctx)                      newname = "PLLOUT_B";                  if (pi.name == ctx->id("PLLOUTCORE"))                      newname = "PLLOUT_A"; +                if (pi.name == ctx->id("PLLOUTGLOBALA")) +                    newname = "PLLOUT_A"; +                if (pi.name == ctx->id("PLLOUTGLOBALB")) +                    newname = "PLLOUT_B"; +                if (pi.name == ctx->id("PLLOUTGLOBAL")) +                    newname = "PLLOUT_A"; + +                if (pi.name == ctx->id("PLLOUTGLOBALA") || pi.name == ctx->id("PLLOUTGLOBALB") || +                    pi.name == ctx->id("PLLOUTGLOBAL")) +                    log_warning("PLL '%s' is using port %s but implementation does not actually " +                                "use the global clock output of the PLL\n", +                                ci->name.c_str(ctx), pi.name.str(ctx).c_str());                  if (pi.name == ctx->id("PACKAGEPIN")) {                      if (!is_pad) { -                        log_error("PLL '%s' has a PACKAGEPIN but is not a PAD PLL", ci->name.c_str(ctx)); +                        log_error("PLL '%s' has a PACKAGEPIN but is not a PAD PLL\n", ci->name.c_str(ctx));                      } else {                          // We drop this port and instead place the PLL adequately below.                          pad_packagepin_net = port.second.net; @@ -743,18 +794,21 @@ static void pack_special(Context *ctx)                  }                  if (pi.name == ctx->id("REFERENCECLK")) {                      if (!is_core) -                        log_error("PLL '%s' has a REFERENCECLK but is not a CORE PLL", ci->name.c_str(ctx)); +                        log_error("PLL '%s' has a REFERENCECLK but is not a CORE PLL\n", ci->name.c_str(ctx));                  }                  if (packed->ports.count(ctx->id(newname)) == 0) {                      if (ci->ports[pi.name].net == nullptr) { -                        log_warning("PLL '%s' has unknown unconnected port '%s' - ignoring\n", ci->name.c_str(ctx), pi.name.c_str(ctx)); +                        log_warning("PLL '%s' has unknown unconnected port '%s' - ignoring\n", ci->name.c_str(ctx), +                                    pi.name.c_str(ctx));                          continue;                      } else {                          if (ctx->force) { -                            log_error("PLL '%s' has unknown connected port '%s'\n", ci->name.c_str(ctx), pi.name.c_str(ctx)); +                            log_error("PLL '%s' has unknown connected port '%s'\n", ci->name.c_str(ctx), +                                      pi.name.c_str(ctx));                          } else { -                            log_warning("PLL '%s' has unknown connected port '%s' - ignoring\n", ci->name.c_str(ctx), pi.name.c_str(ctx)); +                            log_warning("PLL '%s' has unknown connected port '%s' - ignoring\n", ci->name.c_str(ctx), +                                        pi.name.c_str(ctx));                              continue;                          }                      } @@ -806,13 +860,15 @@ static void pack_special(Context *ctx)                          packagepin_cell->ports.erase(pll_packagepin_driver.port);                      } -                    log_info("  constrained PLL '%s' to %s\n", packed->name.c_str(ctx), ctx->getBelName(bel).c_str(ctx)); +                    log_info("  constrained PLL '%s' to %s\n", packed->name.c_str(ctx), +                             ctx->getBelName(bel).c_str(ctx));                      packed->attrs[ctx->id("BEL")] = ctx->getBelName(bel).str(ctx);                      pll_bel = bel;                      constrained = true;                  }                  if (!constrained) { -                    log_error("Could not constrain PLL '%s' to any PLL Bel (too many PLLs?)\n", packed->name.c_str(ctx)); +                    log_error("Could not constrain PLL '%s' to any PLL Bel (too many PLLs?)\n", +                              packed->name.c_str(ctx));                  }              } @@ -831,8 +887,7 @@ static void pack_special(Context *ctx)              // If we have a net connected to LOCK, make sure it only drives LUTs.              auto port = packed->ports[ctx->id("LOCK")];              if (port.net != nullptr) { -                log_info("  PLL '%s' has LOCK output, need to pass all outputs via LUT\n", -                                ci->name.c_str(ctx)); +                log_info("  PLL '%s' has LOCK output, need to pass all outputs via LUT\n", ci->name.c_str(ctx));                  bool found_lut = false;                  bool all_luts = true;                  unsigned int lut_count = 0;  | 
