diff options
author | David Shah <davey1576@gmail.com> | 2018-09-30 13:31:27 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-09-30 13:31:27 +0100 |
commit | 2e7aeaef974c8c104e61de03bc57f0ea764bd9c0 (patch) | |
tree | c4ad68c7128a21d1bb4cb5baf16679addddcd074 | |
parent | 07cf349ee46a8c8a3163d2f8a146beab02e6b487 (diff) | |
parent | 0e0ad26f07354938820b6acd1d422fee3208767e (diff) | |
download | nextpnr-2e7aeaef974c8c104e61de03bc57f0ea764bd9c0.tar.gz nextpnr-2e7aeaef974c8c104e61de03bc57f0ea764bd9c0.tar.bz2 nextpnr-2e7aeaef974c8c104e61de03bc57f0ea764bd9c0.zip |
Merge pull request #81 from YosysHQ/ecp5_globals
Adding a simple ECP5 global network router
-rw-r--r-- | common/nextpnr.cc | 7 | ||||
-rw-r--r-- | common/router1.cc | 6 | ||||
-rw-r--r-- | ecp5/arch.cc | 27 | ||||
-rw-r--r-- | ecp5/arch.h | 24 | ||||
-rw-r--r-- | ecp5/archdefs.h | 2 | ||||
-rw-r--r-- | ecp5/bitstream.cc | 2 | ||||
-rw-r--r-- | ecp5/cells.cc | 4 | ||||
-rw-r--r-- | ecp5/constids.inc | 4 | ||||
-rw-r--r-- | ecp5/globals.cc | 345 | ||||
-rw-r--r-- | ecp5/globals.h | 26 | ||||
-rwxr-xr-x | ecp5/trellis_import.py | 9 |
11 files changed, 454 insertions, 2 deletions
diff --git a/common/nextpnr.cc b/common/nextpnr.cc index b04679ad..068bca6f 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(); 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/ecp5/arch.cc b/ecp5/arch.cc index 82ebfba1..830dfc7c 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())); +} // ----------------------------------------------------------------------- @@ -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/archdefs.h b/ecp5/archdefs.h index c4e1413f..b5cdea3c 100644 --- a/ecp5/archdefs.h +++ b/ecp5/archdefs.h @@ -136,7 +136,9 @@ struct DecalId struct ArchNetInfo { + bool is_global = false; }; + struct ArchCellInfo { struct diff --git a/ecp5/bitstream.cc b/ecp5/bitstream.cc index a1edf9e5..f04b1269 100644 --- a/ecp5/bitstream.cc +++ b/ecp5/bitstream.cc @@ -294,6 +294,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..c7afdbc2 100644 --- a/ecp5/cells.cc +++ b/ecp5/cells.cc @@ -124,6 +124,10 @@ 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("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)); } 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..91d224e5 --- /dev/null +++ b/ecp5/globals.cc @@ -0,0 +1,345 @@ +/* + * 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) + 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; + } + 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); + for (const auto &user : global->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/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") |