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; |