aboutsummaryrefslogtreecommitdiffstats
path: root/ecp5
diff options
context:
space:
mode:
authorSerge Bazanski <serge@bazanski.pl>2018-07-17 16:03:48 +0100
committerSerge Bazanski <serge@bazanski.pl>2018-07-17 16:03:48 +0100
commit498bef3f3e82ed214daf44ada83eb22a21159993 (patch)
treeb5d982452b972df13c20cabc0c2b2b73256a285d /ecp5
parentf3c6c76fff90d89dd65af2c02124c098dab63892 (diff)
parent2eb783d626a9a17baf70d2f7750be3c11623d5bc (diff)
downloadnextpnr-498bef3f3e82ed214daf44ada83eb22a21159993.tar.gz
nextpnr-498bef3f3e82ed214daf44ada83eb22a21159993.tar.bz2
nextpnr-498bef3f3e82ed214daf44ada83eb22a21159993.zip
Merge branch 'master' of gitlab.com:SymbioticEDA/nextpnr into q3k/lock-2-electric-boogaloo
Diffstat (limited to 'ecp5')
-rw-r--r--ecp5/arch.cc21
-rw-r--r--ecp5/arch.h8
-rw-r--r--ecp5/arch_place.cc109
-rw-r--r--ecp5/bitstream.cc12
-rw-r--r--ecp5/cells.cc180
-rw-r--r--ecp5/cells.h54
-rw-r--r--ecp5/family.cmake2
-rw-r--r--ecp5/main.cc13
-rw-r--r--ecp5/pack.cc384
-rw-r--r--ecp5/synth/blinky_nopack.ys2
-rwxr-xr-xecp5/trellis_import.py370
11 files changed, 809 insertions, 346 deletions
diff --git a/ecp5/arch.cc b/ecp5/arch.cc
index 3950f4ba..83e32351 100644
--- a/ecp5/arch.cc
+++ b/ecp5/arch.cc
@@ -94,7 +94,7 @@ static const ChipInfoPOD *get_chip_info(const RelPtr<ChipInfoPOD> *ptr) { return
void load_chipdb();
#endif
-#define LFE5U_45F_ONLY
+//#define LFE5U_45F_ONLY
Arch::Arch(ArchArgs args) : args(args)
{
@@ -118,6 +118,14 @@ Arch::Arch(ArchArgs args) : args(args)
log_error("Unsupported ECP5 chip type.\n");
}
#endif
+
+ id_trellis_slice = id("TRELLIS_SLICE");
+ id_clk = id("CLK");
+ id_lsr = id("LSR");
+ id_clkmux = id("CLKMUX");
+ id_lsrmux = id("LSRMUX");
+ id_srmode = id("SRMODE");
+ id_mode = id("MODE");
}
// -----------------------------------------------------------------------
@@ -181,7 +189,10 @@ BelRange Arch::getBelsAtSameTile(BelId bel) const
br.b.cursor_tile = bel.location.y * chip_info->width + bel.location.x;
br.e.cursor_tile = bel.location.y * chip_info->width + bel.location.x;
br.b.cursor_index = 0;
- br.e.cursor_index = locInfo(bel)->num_bels;
+ br.e.cursor_index = locInfo(bel)->num_bels - 1;
+ br.b.chip = chip_info;
+ br.e.chip = chip_info;
+ ++br.e;
return br;
}
@@ -315,12 +326,6 @@ DecalXY Arch::getGroupDecal(GroupId pip) const { return {}; };
// -----------------------------------------------------------------------
-bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const { return true; }
-
-bool Arch::isBelLocationValid(BelId bel) const { return true; }
-
-// -----------------------------------------------------------------------
-
bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, delay_t &delay) const
{
return false;
diff --git a/ecp5/arch.h b/ecp5/arch.h
index 930c488e..4bb71b47 100644
--- a/ecp5/arch.h
+++ b/ecp5/arch.h
@@ -760,6 +760,14 @@ struct Arch : BaseCtx
// Placement validity checks
bool isValidBelForCell(CellInfo *cell, BelId bel) const;
bool isBelLocationValid(BelId bel) const;
+
+ // Helper function for above
+ bool slicesCompatible(const std::vector<const CellInfo *> &cells) const;
+
+ 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
new file mode 100644
index 00000000..22ebab67
--- /dev/null
+++ b/ecp5/arch_place.cc
@@ -0,0 +1,109 @@
+/*
+ * 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 "cells.h"
+#include "log.h"
+#include "nextpnr.h"
+#include "util.h"
+NEXTPNR_NAMESPACE_BEGIN
+
+inline NetInfo *port_or_nullptr(const CellInfo *cell, IdString name)
+{
+ auto found = cell->ports.find(name);
+ if (found == cell->ports.end())
+ return nullptr;
+ return found->second.net;
+}
+
+bool Arch::slicesCompatible(const std::vector<const CellInfo *> &cells) const
+{
+ // TODO: allow different LSR/CLK and MUX/SRMODE settings once
+ // routing details are worked out
+ NetInfo *clk_sig = nullptr, *lsr_sig = nullptr;
+ std::string CLKMUX, LSRMUX, SRMODE;
+ bool first = true;
+ for (auto cell : cells) {
+ if (first) {
+ clk_sig = port_or_nullptr(cell, id_clk);
+ lsr_sig = port_or_nullptr(cell, id_lsr);
+ CLKMUX = str_or_default(cell->params, id_clkmux, "CLK");
+ LSRMUX = str_or_default(cell->params, id_lsrmux, "LSR");
+ SRMODE = str_or_default(cell->params, id_srmode, "CE_OVER_LSR");
+ } else {
+ if (port_or_nullptr(cell, id_clk) != clk_sig)
+ return false;
+ if (port_or_nullptr(cell, id_lsr) != lsr_sig)
+ return false;
+ if (str_or_default(cell->params, id_clkmux, "CLK") != CLKMUX)
+ return false;
+ if (str_or_default(cell->params, id_lsrmux, "LSR") != LSRMUX)
+ return false;
+ if (str_or_default(cell->params, id_srmode, "CE_OVER_LSR") != SRMODE)
+ return false;
+ }
+ first = false;
+ }
+ return true;
+}
+
+bool Arch::isBelLocationValid(BelId bel) const
+{
+ if (getBelType(bel) == TYPE_TRELLIS_SLICE) {
+ std::vector<const CellInfo *> bel_cells;
+ for (auto bel_other : getBelsAtSameTile(bel)) {
+ IdString cell_other = getBoundBelCell(bel_other);
+ if (cell_other != IdString()) {
+ const CellInfo *ci_other = cells.at(cell_other).get();
+ bel_cells.push_back(ci_other);
+ }
+ }
+ return slicesCompatible(bel_cells);
+ } else {
+ IdString cellId = getBoundBelCell(bel);
+ if (cellId == IdString())
+ return true;
+ else
+ return isValidBelForCell(cells.at(cellId).get(), bel);
+ }
+}
+
+bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const
+{
+ if (cell->type == id_trellis_slice) {
+ NPNR_ASSERT(getBelType(bel) == TYPE_TRELLIS_SLICE);
+
+ std::vector<const CellInfo *> bel_cells;
+
+ for (auto bel_other : getBelsAtSameTile(bel)) {
+ IdString cell_other = getBoundBelCell(bel_other);
+ if (cell_other != IdString() && bel_other != bel) {
+ const CellInfo *ci_other = cells.at(cell_other).get();
+ bel_cells.push_back(ci_other);
+ }
+ }
+
+ bel_cells.push_back(cell);
+ return slicesCompatible(bel_cells);
+ } else {
+ // other checks
+ return true;
+ }
+}
+
+NEXTPNR_NAMESPACE_END
diff --git a/ecp5/bitstream.cc b/ecp5/bitstream.cc
index e70d6bb2..19ddb9f9 100644
--- a/ecp5/bitstream.cc
+++ b/ecp5/bitstream.cc
@@ -214,6 +214,18 @@ 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"));
+ IdString lsrnet;
+ 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->name;
+ 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"));
+ }
// TODO: CLKMUX, CEMUX, carry
} else if (ci->type == ctx->id("TRELLIS_IO")) {
std::string pio = ctx->locInfo(bel)->bel_data[bel.index].name.get();
diff --git a/ecp5/cells.cc b/ecp5/cells.cc
new file mode 100644
index 00000000..59504735
--- /dev/null
+++ b/ecp5/cells.cc
@@ -0,0 +1,180 @@
+/*
+ * 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 "cells.h"
+#include <algorithm>
+#include "design_utils.h"
+#include "log.h"
+#include "util.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+void add_port(const Context *ctx, CellInfo *cell, std::string name, PortType dir)
+{
+ IdString id = ctx->id(name);
+ cell->ports[id] = PortInfo{id, nullptr, dir};
+}
+
+std::unique_ptr<CellInfo> create_ecp5_cell(Context *ctx, IdString type, std::string name)
+{
+ static int auto_idx = 0;
+ std::unique_ptr<CellInfo> new_cell = std::unique_ptr<CellInfo>(new CellInfo());
+ if (name.empty()) {
+ new_cell->name = ctx->id("$nextpnr_" + type.str(ctx) + "_" + std::to_string(auto_idx++));
+ } else {
+ new_cell->name = ctx->id(name);
+ }
+ new_cell->type = type;
+ if (type == ctx->id("TRELLIS_SLICE")) {
+ new_cell->params[ctx->id("MODE")] = "LOGIC";
+ new_cell->params[ctx->id("GSR")] = "DISABLED";
+ new_cell->params[ctx->id("SRMODE")] = "LSR_OVER_CE";
+ new_cell->params[ctx->id("CEMUX")] = "1";
+ new_cell->params[ctx->id("CLKMUX")] = "CLK";
+ new_cell->params[ctx->id("LSRMUX")] = "LSR";
+ new_cell->params[ctx->id("LUT0_INITVAL")] = "0";
+ new_cell->params[ctx->id("LUT1_INITVAL")] = "0";
+ new_cell->params[ctx->id("REG0_SD")] = "0";
+ new_cell->params[ctx->id("REG1_SD")] = "0";
+ new_cell->params[ctx->id("REG0_REGSET")] = "RESET";
+ new_cell->params[ctx->id("REG1_REGSET")] = "RESET";
+ new_cell->params[ctx->id("CCU2_INJECT1_0")] = "NO";
+ new_cell->params[ctx->id("CCU2_INJECT1_1")] = "NO";
+ new_cell->params[ctx->id("WREMUX")] = "WRE";
+
+ 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(), "M0", PORT_IN);
+ add_port(ctx, new_cell.get(), "M1", PORT_IN);
+
+ add_port(ctx, new_cell.get(), "FCI", PORT_IN);
+ add_port(ctx, new_cell.get(), "FXA", PORT_IN);
+ add_port(ctx, new_cell.get(), "FXB", PORT_IN);
+
+ add_port(ctx, new_cell.get(), "CLK", PORT_IN);
+ add_port(ctx, new_cell.get(), "LSR", PORT_IN);
+ add_port(ctx, new_cell.get(), "CE", PORT_IN);
+
+ add_port(ctx, new_cell.get(), "DI0", PORT_IN);
+ add_port(ctx, new_cell.get(), "DI1", PORT_IN);
+
+ add_port(ctx, new_cell.get(), "WD0", PORT_IN);
+ add_port(ctx, new_cell.get(), "WD1", PORT_IN);
+ add_port(ctx, new_cell.get(), "WAD0", PORT_IN);
+ add_port(ctx, new_cell.get(), "WAD1", PORT_IN);
+ add_port(ctx, new_cell.get(), "WAD2", PORT_IN);
+ add_port(ctx, new_cell.get(), "WAD3", PORT_IN);
+ add_port(ctx, new_cell.get(), "WRE", PORT_IN);
+ add_port(ctx, new_cell.get(), "WCK", PORT_IN);
+
+ add_port(ctx, new_cell.get(), "F0", PORT_OUT);
+ add_port(ctx, new_cell.get(), "Q0", PORT_OUT);
+ add_port(ctx, new_cell.get(), "F1", PORT_OUT);
+ add_port(ctx, new_cell.get(), "Q1", PORT_OUT);
+
+ add_port(ctx, new_cell.get(), "FCO", PORT_OUT);
+ add_port(ctx, new_cell.get(), "OFX0", PORT_OUT);
+ add_port(ctx, new_cell.get(), "OFX1", PORT_OUT);
+
+ add_port(ctx, new_cell.get(), "WDO0", PORT_OUT);
+ add_port(ctx, new_cell.get(), "WDO1", PORT_OUT);
+ add_port(ctx, new_cell.get(), "WDO2", PORT_OUT);
+ add_port(ctx, new_cell.get(), "WDO3", PORT_OUT);
+ add_port(ctx, new_cell.get(), "WADO0", PORT_OUT);
+ add_port(ctx, new_cell.get(), "WADO1", PORT_OUT);
+ add_port(ctx, new_cell.get(), "WADO2", PORT_OUT);
+ add_port(ctx, new_cell.get(), "WADO3", PORT_OUT);
+ } else if (type == ctx->id("TRELLIS_IO")) {
+ new_cell->params[ctx->id("DIR")] = "INPUT";
+ new_cell->attrs[ctx->id("IO_TYPE")] = "LVCMOS33";
+
+ add_port(ctx, new_cell.get(), "B", PORT_INOUT);
+ add_port(ctx, new_cell.get(), "I", PORT_IN);
+ add_port(ctx, new_cell.get(), "T", PORT_IN);
+ add_port(ctx, new_cell.get(), "O", PORT_OUT);
+ } else {
+ log_error("unable to create ECP5 cell of type %s", type.c_str(ctx));
+ }
+ return new_cell;
+}
+
+static void set_param_safe(bool has_ff, CellInfo *lc, IdString name, const std::string &value)
+{
+ NPNR_ASSERT(!has_ff || lc->params.at(name) == value);
+ lc->params[name] = value;
+}
+
+static void replace_port_safe(bool has_ff, CellInfo *ff, IdString ff_port, CellInfo *lc, IdString lc_port)
+{
+ if (has_ff) {
+ NPNR_ASSERT(lc->ports.at(lc_port).net == ff->ports.at(ff_port).net);
+ NetInfo *ffnet = ff->ports.at(ff_port).net;
+ if (ffnet != nullptr)
+ ffnet->users.erase(
+ std::remove_if(ffnet->users.begin(), ffnet->users.end(),
+ [ff, ff_port](PortRef port) { return port.cell == ff && port.port == ff_port; }),
+ ffnet->users.end());
+ } else {
+ replace_port(ff, ff_port, lc, lc_port);
+ }
+}
+
+void ff_to_slice(Context *ctx, CellInfo *ff, CellInfo *lc, int index, bool driven_by_lut)
+{
+ bool has_ff = lc->ports.at(ctx->id("Q0")).net != nullptr || lc->ports.at(ctx->id("Q1")).net != nullptr;
+ std::string reg = "REG" + std::to_string(index);
+ set_param_safe(has_ff, lc, ctx->id("SRMODE"), str_or_default(ff->params, ctx->id("SRMODE"), "LSR_OVER_CE"));
+ 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"));
+ 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"));
+ if (ff->ports.find(ctx->id("LSR")) != ff->ports.end())
+ replace_port_safe(has_ff, ff, ctx->id("LSR"), lc, ctx->id("LSR"));
+ if (ff->ports.find(ctx->id("CE")) != ff->ports.end())
+ replace_port_safe(has_ff, ff, ctx->id("CE"), lc, ctx->id("CE"));
+
+ replace_port(ff, ctx->id("Q"), lc, ctx->id("Q" + std::to_string(index)));
+ if (driven_by_lut) {
+ replace_port(ff, ctx->id("DI"), lc, ctx->id("DI" + std::to_string(index)));
+ } else {
+ replace_port(ff, ctx->id("DI"), lc, ctx->id("M" + std::to_string(index)));
+ }
+}
+
+void lut_to_slice(Context *ctx, CellInfo *lut, CellInfo *lc, int index)
+{
+ lc->params[ctx->id("LUT" + std::to_string(index) + "_INITVAL")] = str_or_default(lc->params, ctx->id("INIT"), "0");
+ replace_port(lut, ctx->id("A"), lc, ctx->id("A" + std::to_string(index)));
+ replace_port(lut, ctx->id("B"), lc, ctx->id("B" + std::to_string(index)));
+ replace_port(lut, ctx->id("C"), lc, ctx->id("C" + std::to_string(index)));
+ replace_port(lut, ctx->id("D"), lc, ctx->id("D" + std::to_string(index)));
+ replace_port(lut, ctx->id("Z"), lc, ctx->id("F" + std::to_string(index)));
+}
+
+NEXTPNR_NAMESPACE_END
diff --git a/ecp5/cells.h b/ecp5/cells.h
new file mode 100644
index 00000000..b0c74ca9
--- /dev/null
+++ b/ecp5/cells.h
@@ -0,0 +1,54 @@
+/*
+ * 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 ECP5_CELLS_H
+#define ECP5_CELLS_H
+
+#include "nextpnr.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+// Create a standard ECP5 cell and return it
+// Name will be automatically assigned if not specified
+std::unique_ptr<CellInfo> create_ecp5_cell(Context *ctx, IdString type, std::string name = "");
+
+// Return true if a cell is a LUT
+inline bool is_lut(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("LUT4"); }
+
+// Return true if a cell is a flipflop
+inline bool is_ff(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("TRELLIS_FF"); }
+
+inline bool is_carry(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("CCU2C"); }
+
+inline bool is_lc(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("TRELLIS_LC"); }
+
+inline bool is_trellis_io(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("TRELLIS_IO"); }
+
+inline bool is_dpram(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("TRELLIS_DPR16X4"); }
+
+inline bool is_pfumx(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("PFUMX"); }
+
+inline bool is_l6mux(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("L6MUX21"); }
+
+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);
+
+NEXTPNR_NAMESPACE_END
+
+#endif
diff --git a/ecp5/family.cmake b/ecp5/family.cmake
index f4d0bf87..97e5d66b 100644
--- a/ecp5/family.cmake
+++ b/ecp5/family.cmake
@@ -1,5 +1,5 @@
-set(devices 45k)
+set(devices 25k 45k 85k)
if (NOT DEFINED TRELLIS_ROOT)
message(FATAL_ERROR "you must define TRELLIS_ROOT using -DTRELLIS_ROOT=/path/to/prjtrellis for ECP5 support")
diff --git a/ecp5/main.cc b/ecp5/main.cc
index 4cb2f10d..7521b88c 100644
--- a/ecp5/main.cc
+++ b/ecp5/main.cc
@@ -63,6 +63,11 @@ int main(int argc, char *argv[])
#ifndef NO_GUI
options.add_options()("gui", "start gui");
#endif
+
+ options.add_options()("25k", "set device type to LFE5U-25F");
+ options.add_options()("45k", "set device type to LFE5U-45F");
+ options.add_options()("85k", "set device type to LFE5U-85F");
+
options.add_options()("json", po::value<std::string>(), "JSON design file to ingest");
options.add_options()("seed", po::value<int>(), "seed value for random number generator");
@@ -111,6 +116,14 @@ int main(int argc, char *argv[])
ArchArgs args;
args.type = ArchArgs::LFE5U_45F;
+
+ if (vm.count("25k"))
+ args.type = ArchArgs::LFE5U_25F;
+ if (vm.count("45k"))
+ args.type = ArchArgs::LFE5U_45F;
+ if (vm.count("85k"))
+ args.type = ArchArgs::LFE5U_85F;
+
args.package = "CABGA381";
args.speed = 6;
std::unique_ptr<Context> ctx = std::unique_ptr<Context>(new Context(args));
diff --git a/ecp5/pack.cc b/ecp5/pack.cc
index e3ddc07d..1900eded 100644
--- a/ecp5/pack.cc
+++ b/ecp5/pack.cc
@@ -20,6 +20,7 @@
#include <algorithm>
#include <iterator>
#include <unordered_set>
+#include "cells.h"
#include "design_utils.h"
#include "log.h"
#include "util.h"
@@ -32,63 +33,370 @@ static bool is_nextpnr_iob(Context *ctx, CellInfo *cell)
cell->type == ctx->id("$nextpnr_iobuf");
}
-static bool is_trellis_io(const Context *ctx, const CellInfo *cell) { return cell->type == ctx->id("TRELLIS_IO"); }
-
-// Simple "packer" to remove nextpnr IOBUFs, this assumes IOBUFs are manually instantiated
-void pack_io(Context *ctx)
+class Ecp5Packer
{
- std::unordered_set<IdString> packed_cells;
- std::vector<std::unique_ptr<CellInfo>> new_cells;
- log_info("Packing IOs..\n");
+ public:
+ Ecp5Packer(Context *ctx) : ctx(ctx){};
- for (auto cell : sorted(ctx->cells)) {
- CellInfo *ci = cell.second;
- if (is_nextpnr_iob(ctx, ci)) {
- CellInfo *trio = nullptr;
- if (ci->type == ctx->id("$nextpnr_ibuf") || ci->type == ctx->id("$nextpnr_iobuf")) {
- trio = net_only_drives(ctx, ci->ports.at(ctx->id("O")).net, is_trellis_io, ctx->id("B"), true, ci);
+ private:
+ // Process the contents of packed_cells and new_cells
+ void flush_cells()
+ {
+ for (auto pcell : packed_cells) {
+ ctx->cells.erase(pcell);
+ }
+ for (auto &ncell : new_cells) {
+ ctx->cells[ncell->name] = std::move(ncell);
+ }
+ packed_cells.clear();
+ new_cells.clear();
+ }
- } else if (ci->type == ctx->id("$nextpnr_obuf")) {
- trio = net_only_drives(ctx, ci->ports.at(ctx->id("I")).net, is_trellis_io, ctx->id("B"), true, ci);
+ // Find FFs associated with LUTs, or LUT expansion muxes
+ void find_lutff_pairs()
+ {
+ log_info("Finding LUTFF pairs...\n");
+ for (auto cell : sorted(ctx->cells)) {
+ CellInfo *ci = cell.second;
+ if (is_lut(ctx, ci) || is_pfumx(ctx, ci) || is_l6mux(ctx, ci)) {
+ NetInfo *znet = ci->ports.at(ctx->id("Z")).net;
+ if (znet != nullptr) {
+ CellInfo *ff = net_only_drives(ctx, znet, is_ff, ctx->id("DI"), false);
+ if (ff != nullptr) {
+ lutffPairs[ci->name] = ff->name;
+ fflutPairs[ff->name] = ci->name;
+ }
+ }
}
- if (trio != nullptr) {
- // Trivial case, TRELLIS_IO used. Just destroy the net and the
- // iobuf
- log_info("%s feeds TRELLIS_IO %s, removing %s %s.\n", ci->name.c_str(ctx), trio->name.c_str(ctx),
- ci->type.c_str(ctx), ci->name.c_str(ctx));
- NetInfo *net = trio->ports.at(ctx->id("B")).net;
- if (net != nullptr) {
- ctx->nets.erase(net->name);
- trio->ports.at(ctx->id("B")).net = nullptr;
+ }
+ }
+
+ // Return whether two FFs can be packed together in the same slice
+ bool can_pack_ffs(CellInfo *ff0, CellInfo *ff1)
+ {
+ if (str_or_default(ff0->params, ctx->id("GSR"), "DISABLED") !=
+ str_or_default(ff1->params, ctx->id("GSR"), "DISABLED"))
+ return false;
+ if (str_or_default(ff0->params, ctx->id("SRMODE"), "LSR_OVER_CE") !=
+ str_or_default(ff1->params, ctx->id("SRMODE"), "LSR_OVER_CE"))
+ return false;
+ if (str_or_default(ff0->params, ctx->id("CEMUX"), "1") != str_or_default(ff1->params, ctx->id("CEMUX"), "1"))
+ return false;
+ if (str_or_default(ff0->params, ctx->id("LSRMUX"), "LSR") !=
+ str_or_default(ff1->params, ctx->id("LSRMUX"), "LSR"))
+ return false;
+ if (str_or_default(ff0->params, ctx->id("CLKMUX"), "CLK") !=
+ str_or_default(ff1->params, ctx->id("CLKMUX"), "CLK"))
+ return false;
+ if (ff0->ports.at(ctx->id("CLK")).net != ff1->ports.at(ctx->id("CLK")).net)
+ return false;
+ if (ff0->ports.at(ctx->id("CE")).net != ff1->ports.at(ctx->id("CE")).net)
+ return false;
+ if (ff0->ports.at(ctx->id("LSR")).net != ff1->ports.at(ctx->id("LSR")).net)
+ return false;
+ return true;
+ }
+
+ // Return true if two LUTs can be paired considering FF compatibility
+ bool can_pack_lutff(IdString lut0, IdString lut1)
+ {
+ auto ff0 = lutffPairs.find(lut0), ff1 = lutffPairs.find(lut1);
+ if (ff0 != lutffPairs.end() && ff1 != lutffPairs.end()) {
+ return can_pack_ffs(ctx->cells.at(ff0->second).get(), ctx->cells.at(ff1->second).get());
+ } else {
+ return true;
+ }
+ }
+
+ // Find "closely connected" LUTs and pair them together
+ void pair_luts()
+ {
+ log_info("Finding LUT-LUT pairs...\n");
+ std::unordered_set<IdString> procdLuts;
+ for (auto cell : sorted(ctx->cells)) {
+ CellInfo *ci = cell.second;
+ if (is_lut(ctx, ci) && procdLuts.find(cell.first) == procdLuts.end()) {
+ NetInfo *znet = ci->ports.at(ctx->id("Z")).net;
+ if (znet != nullptr) {
+ for (auto user : znet->users) {
+ if (is_lut(ctx, user.cell) && user.cell != ci &&
+ procdLuts.find(user.cell->name) == procdLuts.end()) {
+ if (can_pack_lutff(ci->name, user.cell->name)) {
+ procdLuts.insert(ci->name);
+ procdLuts.insert(user.cell->name);
+ lutPairs[ci->name] = user.cell->name;
+ goto paired;
+ }
+ }
+ }
+ if (false) {
+ paired:
+ continue;
+ }
+ }
+ if (lutffPairs.find(ci->name) != lutffPairs.end()) {
+ NetInfo *qnet = ctx->cells.at(lutffPairs[ci->name])->ports.at(ctx->id("Q")).net;
+ if (qnet != nullptr) {
+ for (auto user : qnet->users) {
+ if (is_lut(ctx, user.cell) && user.cell != ci &&
+ procdLuts.find(user.cell->name) == procdLuts.end()) {
+ if (can_pack_lutff(ci->name, user.cell->name)) {
+ procdLuts.insert(ci->name);
+ procdLuts.insert(user.cell->name);
+ lutPairs[ci->name] = user.cell->name;
+ goto paired_ff;
+ }
+ }
+ }
+ if (false) {
+ paired_ff:
+ continue;
+ }
+ }
+ }
+ for (const char *inp : {"A", "B", "C", "D"}) {
+ NetInfo *innet = ci->ports.at(ctx->id(inp)).net;
+ if (innet != nullptr && innet->driver.cell != nullptr) {
+ CellInfo *drv = innet->driver.cell;
+ if (is_lut(ctx, drv) && drv != ci && innet->driver.port == ctx->id("Z")) {
+ if (procdLuts.find(drv->name) == procdLuts.end()) {
+ if (can_pack_lutff(ci->name, drv->name)) {
+ procdLuts.insert(ci->name);
+ procdLuts.insert(drv->name);
+ lutPairs[ci->name] = drv->name;
+ goto paired_inlut;
+ }
+ }
+ } else if (is_ff(ctx, drv) && innet->driver.port == ctx->id("Q")) {
+ auto fflut = fflutPairs.find(drv->name);
+ if (fflut != fflutPairs.end() && fflut->second != ci->name &&
+ procdLuts.find(fflut->second) == procdLuts.end()) {
+ if (can_pack_lutff(ci->name, fflut->second)) {
+ procdLuts.insert(ci->name);
+ procdLuts.insert(fflut->second);
+ lutPairs[ci->name] = fflut->second;
+ goto paired_inlut;
+ }
+ }
+ }
+ }
+ }
+ if (false) {
+ paired_inlut:
+ continue;
+ }
+ }
+ }
+ }
+
+ // Simple "packer" to remove nextpnr IOBUFs, this assumes IOBUFs are manually instantiated
+ void pack_io()
+ {
+ log_info("Packing IOs..\n");
+
+ for (auto cell : sorted(ctx->cells)) {
+ CellInfo *ci = cell.second;
+ if (is_nextpnr_iob(ctx, ci)) {
+ CellInfo *trio = nullptr;
+ if (ci->type == ctx->id("$nextpnr_ibuf") || ci->type == ctx->id("$nextpnr_iobuf")) {
+ trio = net_only_drives(ctx, ci->ports.at(ctx->id("O")).net, is_trellis_io, ctx->id("B"), true, ci);
+
+ } else if (ci->type == ctx->id("$nextpnr_obuf")) {
+ trio = net_only_drives(ctx, ci->ports.at(ctx->id("I")).net, is_trellis_io, ctx->id("B"), true, ci);
}
- if (ci->type == ctx->id("$nextpnr_iobuf")) {
- NetInfo *net2 = ci->ports.at(ctx->id("I")).net;
- if (net2 != nullptr) {
- ctx->nets.erase(net2->name);
+ if (trio != nullptr) {
+ // Trivial case, TRELLIS_IO used. Just destroy the net and the
+ // iobuf
+ log_info("%s feeds TRELLIS_IO %s, removing %s %s.\n", ci->name.c_str(ctx), trio->name.c_str(ctx),
+ ci->type.c_str(ctx), ci->name.c_str(ctx));
+ NetInfo *net = trio->ports.at(ctx->id("B")).net;
+ if (net != nullptr) {
+ ctx->nets.erase(net->name);
+ trio->ports.at(ctx->id("B")).net = nullptr;
}
+ if (ci->type == ctx->id("$nextpnr_iobuf")) {
+ NetInfo *net2 = ci->ports.at(ctx->id("I")).net;
+ if (net2 != nullptr) {
+ ctx->nets.erase(net2->name);
+ }
+ }
+ } else {
+ log_error("TRELLIS_IO required on all top level IOs...\n");
+ }
+ packed_cells.insert(ci->name);
+ std::copy(ci->attrs.begin(), ci->attrs.end(), std::inserter(trio->attrs, trio->attrs.begin()));
+ }
+ }
+ flush_cells();
+ }
+
+ // Pass to pack LUT5s into a newly created slice
+ void pack_lut5s()
+ {
+ log_info("Packing LUT5s...\n");
+ for (auto cell : sorted(ctx->cells)) {
+ CellInfo *ci = cell.second;
+ if (is_pfumx(ctx, ci)) {
+ std::unique_ptr<CellInfo> packed =
+ create_ecp5_cell(ctx, ctx->id("TRELLIS_SLICE"), ci->name.str(ctx) + "_SLICE");
+ NetInfo *f0 = ci->ports.at(ctx->id("BLUT")).net;
+ if (f0 == nullptr)
+ log_error("PFUMX '%s' has disconnected port 'BLUT'\n", ci->name.c_str(ctx));
+ NetInfo *f1 = ci->ports.at(ctx->id("ALUT")).net;
+ if (f1 == nullptr)
+ log_error("PFUMX '%s' has disconnected port 'ALUT'\n", ci->name.c_str(ctx));
+ CellInfo *lut0 = net_driven_by(ctx, f0, is_lut, ctx->id("Z"));
+ CellInfo *lut1 = net_driven_by(ctx, f1, is_lut, ctx->id("Z"));
+ if (lut0 == nullptr)
+ log_error("PFUMX '%s' has BLUT driven by cell other than a LUT\n", ci->name.c_str(ctx));
+ if (lut1 == nullptr)
+ log_error("PFUMX '%s' has ALUT driven by cell other than a LUT\n", ci->name.c_str(ctx));
+ replace_port(lut0, ctx->id("A"), packed.get(), ctx->id("A0"));
+ replace_port(lut0, ctx->id("B"), packed.get(), ctx->id("B0"));
+ replace_port(lut0, ctx->id("C"), packed.get(), ctx->id("C0"));
+ replace_port(lut0, ctx->id("D"), packed.get(), ctx->id("D0"));
+ replace_port(lut1, ctx->id("A"), packed.get(), ctx->id("A1"));
+ replace_port(lut1, ctx->id("B"), packed.get(), ctx->id("B1"));
+ replace_port(lut1, ctx->id("C"), packed.get(), ctx->id("C1"));
+ replace_port(lut1, ctx->id("D"), packed.get(), ctx->id("D1"));
+ replace_port(ci, ctx->id("C0"), packed.get(), ctx->id("M0"));
+ replace_port(ci, ctx->id("Z"), packed.get(), ctx->id("OFX0"));
+ packed->params[ctx->id("LUT0_INITVAL")] = str_or_default(lut0->params, ctx->id("INIT"), "0");
+ packed->params[ctx->id("LUT1_INITVAL")] = str_or_default(lut1->params, ctx->id("INIT"), "0");
+
+ ctx->nets.erase(f0->name);
+ ctx->nets.erase(f1->name);
+ sliceUsage[packed->name].lut0_used = true;
+ sliceUsage[packed->name].lut1_used = true;
+ sliceUsage[packed->name].mux5_used = true;
+
+ if (lutffPairs.find(ci->name) != lutffPairs.end()) {
+ CellInfo *ff = ctx->cells.at(lutffPairs[ci->name]).get();
+ ff_to_slice(ctx, ff, packed.get(), 0, true);
+ packed_cells.insert(ff->name);
+ sliceUsage[packed->name].ff0_used = true;
}
- } else {
- log_error("TRELLIS_IO required on all top level IOs...\n");
+
+ new_cells.push_back(std::move(packed));
+ packed_cells.insert(lut0->name);
+ packed_cells.insert(lut1->name);
+ packed_cells.insert(ci->name);
}
- packed_cells.insert(ci->name);
- std::copy(ci->attrs.begin(), ci->attrs.end(), std::inserter(trio->attrs, trio->attrs.begin()));
}
+ flush_cells();
}
- for (auto pcell : packed_cells) {
- ctx->cells.erase(pcell);
+
+ // Pack LUTs that have been paired together
+ void pack_lut_pairs()
+ {
+ log_info("Packing paired LUTs into a SLICE...\n");
+ for (auto pair : lutPairs) {
+ CellInfo *lut0 = ctx->cells.at(pair.first).get();
+ CellInfo *lut1 = ctx->cells.at(pair.second).get();
+ std::unique_ptr<CellInfo> slice =
+ create_ecp5_cell(ctx, ctx->id("TRELLIS_SLICE"), lut0->name.str(ctx) + "_SLICE");
+
+ lut_to_slice(ctx, lut0, slice.get(), 0);
+ lut_to_slice(ctx, lut1, slice.get(), 1);
+
+ auto ff0 = lutffPairs.find(lut0->name), ff1 = lutffPairs.find(lut1->name);
+
+ if (ff0 != lutffPairs.end()) {
+ ff_to_slice(ctx, ctx->cells.at(ff0->second).get(), slice.get(), 0, true);
+ packed_cells.insert(ff0->second);
+ }
+ if (ff1 != lutffPairs.end()) {
+ ff_to_slice(ctx, ctx->cells.at(ff1->second).get(), slice.get(), 1, true);
+ packed_cells.insert(ff1->second);
+ }
+
+ new_cells.push_back(std::move(slice));
+ packed_cells.insert(lut0->name);
+ packed_cells.insert(lut1->name);
+ }
+ flush_cells();
}
- for (auto &ncell : new_cells) {
- ctx->cells[ncell->name] = std::move(ncell);
+
+ // Pack single LUTs that weren't paired into their own slice,
+ // with an optional FF also
+ void pack_remaining_luts()
+ {
+ log_info("Packing unpaired LUTs into a SLICE...\n");
+ for (auto cell : sorted(ctx->cells)) {
+ CellInfo *ci = cell.second;
+ if (is_lut(ctx, ci)) {
+ std::unique_ptr<CellInfo> slice =
+ create_ecp5_cell(ctx, ctx->id("TRELLIS_SLICE"), ci->name.str(ctx) + "_SLICE");
+ lut_to_slice(ctx, ci, slice.get(), 0);
+ auto ff = lutffPairs.find(ci->name);
+
+ if (ff != lutffPairs.end()) {
+ ff_to_slice(ctx, ctx->cells.at(ff->second).get(), slice.get(), 0, true);
+ packed_cells.insert(ff->second);
+ }
+
+ new_cells.push_back(std::move(slice));
+ packed_cells.insert(ci->name);
+ }
+ }
+ flush_cells();
}
-}
+ // Pack flipflops that weren't paired with a LUT
+ void pack_remaining_ffs()
+ {
+ log_info("Packing unpaired FFs into a SLICE...\n");
+ for (auto cell : sorted(ctx->cells)) {
+ CellInfo *ci = cell.second;
+ if (is_ff(ctx, ci)) {
+ std::unique_ptr<CellInfo> slice =
+ create_ecp5_cell(ctx, ctx->id("TRELLIS_SLICE"), ci->name.str(ctx) + "_SLICE");
+ ff_to_slice(ctx, ci, slice.get(), 0, false);
+ new_cells.push_back(std::move(slice));
+ packed_cells.insert(ci->name);
+ }
+ }
+ flush_cells();
+ }
+
+ public:
+ void pack()
+ {
+ pack_io();
+ find_lutff_pairs();
+ pack_lut5s();
+ pair_luts();
+ pack_lut_pairs();
+ pack_remaining_luts();
+ pack_remaining_ffs();
+ }
+
+ private:
+ Context *ctx;
+
+ std::unordered_set<IdString> packed_cells;
+ std::vector<std::unique_ptr<CellInfo>> new_cells;
+
+ struct SliceUsage
+ {
+ bool lut0_used = false, lut1_used = false;
+ bool ccu2_used = false, dpram_used = false, ramw_used = false;
+ bool ff0_used = false, ff1_used = false;
+ bool mux5_used = false, muxx_used = false;
+ };
+
+ std::unordered_map<IdString, SliceUsage> sliceUsage;
+ std::unordered_map<IdString, IdString> lutffPairs;
+ std::unordered_map<IdString, IdString> fflutPairs;
+ std::unordered_map<IdString, IdString> lutPairs;
+};
// Main pack function
bool Arch::pack()
{
Context *ctx = getCtx();
try {
log_break();
- pack_io(ctx);
+ Ecp5Packer(ctx).pack();
log_info("Checksum: 0x%08x\n", ctx->checksum());
return true;
} catch (log_execution_error_exception) {
diff --git a/ecp5/synth/blinky_nopack.ys b/ecp5/synth/blinky_nopack.ys
new file mode 100644
index 00000000..fb359380
--- /dev/null
+++ b/ecp5/synth/blinky_nopack.ys
@@ -0,0 +1,2 @@
+read_verilog blinky.v
+synth_ecp5 -noccu2 -nomux -nodram -json blinky.json
diff --git a/ecp5/trellis_import.py b/ecp5/trellis_import.py
index 5eca057a..2bc32169 100755
--- a/ecp5/trellis_import.py
+++ b/ecp5/trellis_import.py
@@ -32,213 +32,6 @@ def get_tiletype_index(name):
portpins = dict()
-loc_wire_indices = dict()
-loc_wires = dict()
-
-loc_bels = dict()
-wire_bel_pins_uphill = dict()
-wire_bel_pins_downhill = dict()
-
-
-# Import all wire names at all locations
-def import_location_wires(rg, x, y):
- loc_wire_indices[x, y] = dict()
- loc_wires[x, y] = list()
- wire_bel_pins_uphill[x, y] = list()
- wire_bel_pins_downhill[x, y] = list()
- rtile = rg.tiles[pytrellis.Location(x, y)]
- for wire in rtile.wires:
- name = rg.to_str(wire.key())
- idx = len(loc_wires[x, y])
- loc_wires[x, y].append(name)
- loc_wire_indices[x, y][name] = idx
- wire_bel_pins_uphill[x, y].append([])
- wire_bel_pins_downhill[x, y].append([])
-
-
-# Take a RoutingId from Trellis and make into a (relx, rely, name) tuple
-def resolve_wirename(rg, rid, cur_x, cur_y):
- if is_global(rid.loc):
- return (cur_x, cur_y, rg.to_str(rid.id))
- else:
- x = rid.loc.x
- y = rid.loc.y
- widx = loc_wire_indices[x, y][rg.to_str(rid.id)]
- return (x - cur_x, y - cur_y, widx)
-
-
-loc_arc_indices = dict() # Map RoutingId index to nextpnr index
-loc_arcs = dict()
-
-
-# Import all arc indices at a location
-def index_location_arcs(rg, x, y):
- loc_arc_indices[x, y] = dict()
- loc_arcs[x, y] = list()
- rtile = rg.tiles[pytrellis.Location(x, y)]
- for arc in rtile.arcs:
- idx = len(loc_arcs[x, y])
- trid = arc.key()
- loc_arcs[x, y].append(trid)
- loc_arc_indices[x, y][trid] = idx
-
-
-def add_bel_input(bel_x, bel_y, bel_idx, bel_pin, wire_x, wire_y, wire_name):
- bel_pin = portpins[bel_pin]
- loc_bels[bel_x, bel_y][bel_idx][2].append((bel_pin, (wire_x, wire_y, loc_wire_indices[wire_x, wire_y][wire_name])))
- wire_bel_pins_downhill[wire_x, wire_y][loc_wire_indices[wire_x, wire_y][wire_name]].append((
- (bel_x, bel_y, bel_idx), bel_pin))
-
-
-def add_bel_output(bel_x, bel_y, bel_idx, bel_pin, wire_x, wire_y, wire_name):
- bel_pin = portpins[bel_pin]
- loc_bels[bel_x, bel_y][bel_idx][2].append((bel_pin, (wire_x, wire_y, loc_wire_indices[wire_x, wire_y][wire_name])))
- wire_bel_pins_uphill[wire_x, wire_y][loc_wire_indices[wire_x, wire_y][wire_name]].append((
- (bel_x, bel_y, bel_idx), bel_pin))
-
-
-def add_slice(x, y, z):
- idx = len(loc_bels[x, y])
- l = ("A", "B", "C", "D")[z]
- name = "SLICE" + l
- loc_bels[x, y].append((name, "SLICE", []))
- lc0 = z * 2
- lc1 = z * 2 + 1
- add_bel_input(x, y, idx, "A0", x, y, "A{}_SLICE".format(lc0))
- add_bel_input(x, y, idx, "B0", x, y, "B{}_SLICE".format(lc0))
- add_bel_input(x, y, idx, "C0", x, y, "C{}_SLICE".format(lc0))
- add_bel_input(x, y, idx, "D0", x, y, "D{}_SLICE".format(lc0))
- add_bel_input(x, y, idx, "M0", x, y, "M{}_SLICE".format(lc0))
-
- add_bel_input(x, y, idx, "A1", x, y, "A{}_SLICE".format(lc1))
- add_bel_input(x, y, idx, "B1", x, y, "B{}_SLICE".format(lc1))
- add_bel_input(x, y, idx, "C1", x, y, "C{}_SLICE".format(lc1))
- add_bel_input(x, y, idx, "D1", x, y, "D{}_SLICE".format(lc1))
- add_bel_input(x, y, idx, "M1", x, y, "M{}_SLICE".format(lc1))
-
- add_bel_input(x, y, idx, "FCI", x, y, "FCI{}_SLICE".format(l if z > 0 else ""))
- add_bel_input(x, y, idx, "FXA", x, y, "FXA{}_SLICE".format(l))
- add_bel_input(x, y, idx, "FXB", x, y, "FXB{}_SLICE".format(l))
-
- add_bel_input(x, y, idx, "CLK", x, y, "CLK{}_SLICE".format(z))
- add_bel_input(x, y, idx, "LSR", x, y, "LSR{}_SLICE".format(z))
- add_bel_input(x, y, idx, "CE", x, y, "CE{}_SLICE".format(z))
-
- add_bel_input(x, y, idx, "DI0", x, y, "DI{}_SLICE".format(lc0))
- add_bel_input(x, y, idx, "DI1", x, y, "DI{}_SLICE".format(lc1))
-
- if z == 0 or z == 1:
- add_bel_input(x, y, idx, "WD0", x, y, "WD0{}_SLICE".format(l))
- add_bel_input(x, y, idx, "WD1", x, y, "WD1{}_SLICE".format(l))
-
- add_bel_input(x, y, idx, "WAD0", x, y, "WAD0{}_SLICE".format(l))
- add_bel_input(x, y, idx, "WAD1", x, y, "WAD1{}_SLICE".format(l))
- add_bel_input(x, y, idx, "WAD2", x, y, "WAD2{}_SLICE".format(l))
- add_bel_input(x, y, idx, "WAD3", x, y, "WAD3{}_SLICE".format(l))
-
- add_bel_input(x, y, idx, "WRE", x, y, "WRE{}_SLICE".format(z))
- add_bel_input(x, y, idx, "WCK", x, y, "WCK{}_SLICE".format(z))
-
- add_bel_output(x, y, idx, "F0", x, y, "F{}_SLICE".format(lc0))
- add_bel_output(x, y, idx, "Q0", x, y, "Q{}_SLICE".format(lc0))
-
- add_bel_output(x, y, idx, "F1", x, y, "F{}_SLICE".format(lc1))
- add_bel_output(x, y, idx, "Q1", x, y, "Q{}_SLICE".format(lc1))
-
- add_bel_output(x, y, idx, "OFX0", x, y, "F5{}_SLICE".format(l))
- add_bel_output(x, y, idx, "OFX1", x, y, "FX{}_SLICE".format(l))
-
- add_bel_output(x, y, idx, "FCO", x, y, "FCO{}_SLICE".format(l if z < 3 else ""))
-
- if z == 2:
- add_bel_output(x, y, idx, "WDO0", x, y, "WDO0C_SLICE")
- add_bel_output(x, y, idx, "WDO1", x, y, "WDO1C_SLICE")
- add_bel_output(x, y, idx, "WDO2", x, y, "WDO2C_SLICE")
- add_bel_output(x, y, idx, "WDO3", x, y, "WDO3C_SLICE")
-
- add_bel_output(x, y, idx, "WADO0", x, y, "WADO0C_SLICE")
- add_bel_output(x, y, idx, "WADO1", x, y, "WADO1C_SLICE")
- add_bel_output(x, y, idx, "WADO2", x, y, "WADO2C_SLICE")
- add_bel_output(x, y, idx, "WADO3", x, y, "WADO3C_SLICE")
-
-
-def add_pio(x, y, z):
- idx = len(loc_bels[x, y])
- l = ("A", "B", "C", "D")[z]
- name = "PIO" + l
- loc_bels[x, y].append((name, "PIO", []))
- add_bel_input(x, y, idx, "I", x, y, "PADDO{}_PIO".format(l))
- add_bel_input(x, y, idx, "T", x, y, "PADDT{}_PIO".format(l))
- add_bel_output(x, y, idx, "O", x, y, "JPADDI{}_PIO".format(l))
-
-
-def add_bels(chip, x, y):
- loc_bels[x, y] = []
- tiles = chip.get_tiles_by_position(y, x)
- num_slices = 0
- num_pios = 0
- for tile in tiles:
- tt = tile.info.type
- if tt == "PLC2":
- num_slices = 4
- elif "PICL0" in tt or "PICR0" in tt:
- num_pios = 4
- elif "PIOT0" in tt or ("PICB0" in tt and "SPICB" not in tt):
- num_pios = 2
- for i in range(num_slices):
- add_slice(x, y, i)
- for i in range(num_pios):
- add_pio(x, y, i)
-
-
-# Import a location, deduplicating if appropriate
-def import_location(rg, x, y):
- rtile = rg.tiles[pytrellis.Location(x, y)]
- arcs = [] # (src, dst, configurable, tiletype)
- wires = [] # (name, uphill, downhill, belpin_uphill, belpins_downhill)
- bels = [] # (name, [(pin, wire)])
- for name in loc_wires[x, y]:
- w = rtile.wires[rg.ident(name)]
- arcs_uphill = []
- arcs_downhill = []
- belpins_uphill = []
- belpins_downhill = []
- for uh in w.uphill:
- arcidx = loc_arc_indices[uh.loc.x, uh.loc.y][uh.id]
- arcs_uphill.append((uh.loc.x - x, uh.loc.y - y, arcidx))
- for dh in w.downhill:
- arcidx = loc_arc_indices[dh.loc.x, dh.loc.y][dh.id]
- arcs_downhill.append((dh.loc.x - x, dh.loc.y - y, arcidx))
- for bp in wire_bel_pins_uphill[x, y][loc_wire_indices[x, y][name]]:
- bel, pin = bp
- bel_x, bel_y, bel_idx = bel
- belpins_uphill.append(((bel_x - x, bel_y - y, bel_idx), pin))
- for bp in wire_bel_pins_downhill[x, y][loc_wire_indices[x, y][name]]:
- bel, pin = bp
- bel_x, bel_y, bel_idx = bel
- belpins_downhill.append(((bel_x - x, bel_y - y, bel_idx), pin))
- assert len(belpins_uphill) <= 1
- wires.append((name, tuple(arcs_downhill), tuple(arcs_uphill), tuple(belpins_uphill), tuple(belpins_downhill)))
-
- for bel in loc_bels[x, y]:
- name, beltype, pins = bel
- xformed_pins = tuple((p[0], (p[1][0] - x, p[1][1] - y, p[1][2])) for p in pins)
- bels.append((name, beltype, xformed_pins))
-
- for arcidx in loc_arcs[x, y]:
- a = rtile.arcs[arcidx]
- source_wire = resolve_wirename(rg, a.source, x, y)
- dest_wire = resolve_wirename(rg, a.sink, x, y)
- arcs.append((source_wire, dest_wire, a.configurable, get_tiletype_index(rg.to_str(a.tiletype))))
-
- tile_data = (tuple(wires), tuple(arcs), tuple(bels))
- if tile_data in location_types:
- type_at_location[x, y] = location_types[tile_data]
- else:
- idx = len(location_types)
- location_types[tile_data] = idx
- type_at_location[x, y] = idx
-
class BinaryBlobAssembler:
def __init__(self, cname, endianness, nodebug=False):
@@ -526,100 +319,96 @@ bel_types = {
"PIO": 2
}
-def write_database(dev_name, endianness):
- def write_loc(x, y, sym_name):
- bba.s16(x, "%s.x" % sym_name)
- bba.s16(y, "%s.y" % sym_name)
+
+def write_database(dev_name, ddrg, endianness):
+ def write_loc(loc, sym_name):
+ bba.s16(loc.x, "%s.x" % sym_name)
+ bba.s16(loc.y, "%s.y" % sym_name)
bba = BinaryBlobAssembler("chipdb_blob_%s" % dev_name, endianness)
bba.r("chip_info", "chip_info")
- for loctype, idx in sorted(location_types.items(), key=lambda x: x[1]):
- wires, arcs, bels = loctype
- if len(arcs) > 0:
+ loctypes = list([_.key() for _ in ddrg.locationTypes])
+
+ for idx in range(len(loctypes)):
+ loctype = ddrg.locationTypes[loctypes[idx]]
+ if len(loctype.arcs) > 0:
bba.l("loc%d_pips" % idx, "PipInfoPOD")
- for arc in arcs:
- src_wire, dst_wire, configurable, tile_type = arc
- write_loc(src_wire[0], src_wire[1], "src")
- write_loc(dst_wire[0], dst_wire[1], "dst")
- bba.u32(src_wire[2], "src_idx")
- bba.u32(dst_wire[2], "dst_idx")
- bba.u32(1, "delay") # TODO:delay
- bba.u16(tile_type, "tile_type")
- bba.u8(1 if not configurable else 0, "pip_type")
+ for arc in loctype.arcs:
+ write_loc(arc.srcWire.rel, "src")
+ write_loc(arc.sinkWire.rel, "dst")
+ bba.u32(arc.srcWire.id, "src_idx")
+ bba.u32(arc.sinkWire.id, "dst_idx")
+ bba.u32(arc.delay, "delay") # TODO:delay
+ bba.u16(get_tiletype_index(ddrg.to_str(arc.tiletype)), "tile_type")
+ bba.u8(int(arc.cls), "pip_type")
bba.u8(0, "padding")
- if len(wires) > 0:
- for wire_idx in range(len(wires)):
- wire = wires[wire_idx]
- name, downpips, uppips, downbels, upbels = wire
- if len(downpips) > 0:
+ if len(loctype.wires) > 0:
+ for wire_idx in range(len(loctype.wires)):
+ wire = loctype.wires[wire_idx]
+ if len(wire.arcsDownhill) > 0:
bba.l("loc%d_wire%d_downpips" % (idx, wire_idx), "PipLocatorPOD")
- for dp in downpips:
- write_loc(dp[0], dp[1], "rel_loc")
- bba.u32(dp[2], "index")
- if len(uppips) > 0:
+ for dp in wire.arcsDownhill:
+ write_loc(dp.rel, "rel_loc")
+ bba.u32(dp.id, "index")
+ if len(wire.arcsUphill) > 0:
bba.l("loc%d_wire%d_uppips" % (idx, wire_idx), "PipLocatorPOD")
- for up in uppips:
- write_loc(up[0], up[1], "rel_loc")
- bba.u32(up[2], "index")
- if len(downbels) > 0:
+ for up in wire.arcsUphill:
+ write_loc(up.rel, "rel_loc")
+ bba.u32(up.id, "index")
+ if len(wire.belsDownhill) > 0:
bba.l("loc%d_wire%d_downbels" % (idx, wire_idx), "BelPortPOD")
- for db in downbels:
- bel, pin = db
- write_loc(bel[0], bel[1], "rel_bel_loc")
- bba.u32(bel[2], "bel_index")
- bba.u32(pin, "port")
+ for db in wire.belsDownhill:
+ write_loc(db.bel.rel, "rel_bel_loc")
+ bba.u32(db.bel.id, "bel_index")
+ bba.u32(portpins[ddrg.to_str(db.pin)], "port")
bba.l("loc%d_wires" % idx, "WireInfoPOD")
- for wire_idx in range(len(wires)):
- wire = wires[wire_idx]
- name, downpips, uppips, downbels, upbels = wire
- bba.s(name, "name")
- bba.u32(len(uppips), "num_uphill")
- bba.u32(len(downpips), "num_downhill")
- bba.r("loc%d_wire%d_uppips" % (idx, wire_idx) if len(uppips) > 0 else None, "pips_uphill")
- bba.r("loc%d_wire%d_downpips" % (idx, wire_idx) if len(downpips) > 0 else None, "pips_downhill")
- bba.u32(len(downbels), "num_bels_downhill")
- if len(upbels) == 1:
- bel, pin = upbels[0]
- write_loc(bel[0], bel[1], "uphill_bel_loc")
- bba.u32(bel[2], "uphill_bel_idx")
- bba.u32(pin, "uphill_bel_pin")
+ for wire_idx in range(len(loctype.wires)):
+ wire = loctype.wires[wire_idx]
+ bba.s(ddrg.to_str(wire.name), "name")
+ bba.u32(len(wire.arcsUphill), "num_uphill")
+ bba.u32(len(wire.arcsDownhill), "num_downhill")
+ bba.r("loc%d_wire%d_uppips" % (idx, wire_idx) if len(wire.arcsUphill) > 0 else None, "pips_uphill")
+ bba.r("loc%d_wire%d_downpips" % (idx, wire_idx) if len(wire.arcsDownhill) > 0 else None, "pips_downhill")
+ bba.u32(len(wire.belsDownhill), "num_bels_downhill")
+ write_loc(wire.belUphill.bel.rel, "uphill_bel_loc")
+ if wire.belUphill.pin != -1:
+ bba.u32(wire.belUphill.bel.id, "uphill_bel_idx")
+ bba.u32(portpins[ddrg.to_str(wire.belUphill.pin)], "uphill_bel_pin")
else:
- write_loc(-1, -1, "bel_uphill.rel_bel_loc")
bba.u32(0xFFFFFFFF, "bel_uphill.bel_index")
bba.u32(0, "bel_uphill.port")
- bba.r("loc%d_wire%d_downbels" % (idx, wire_idx) if len(downbels) > 0 else None, "bels_downhill")
- if len(bels) > 0:
- for bel_idx in range(len(bels)):
- bel, beltype, pins = bels[bel_idx]
+ bba.r("loc%d_wire%d_downbels" % (idx, wire_idx) if len(wire.belsDownhill) > 0 else None, "bels_downhill")
+ if len(loctype.bels) > 0:
+ for bel_idx in range(len(loctype.bels)):
+ bel = loctype.bels[bel_idx]
bba.l("loc%d_bel%d_wires" % (idx, bel_idx), "BelPortPOD")
- for pin in pins:
- port, wire = pin
- write_loc(wire[0], wire[1], "rel_wire_loc")
- bba.u32(wire[2], "wire_index")
- bba.u32(port, "port")
+ for pin in bel.wires:
+ write_loc(pin.wire.rel, "rel_wire_loc")
+ bba.u32(pin.wire.id, "wire_index")
+ bba.u32(portpins[ddrg.to_str(pin.pin)], "port")
bba.l("loc%d_bels" % idx, "BelInfoPOD")
- for bel_idx in range(len(bels)):
- bel, beltype, pins = bels[bel_idx]
- bba.s(bel, "name")
- bba.u32(bel_types[beltype], "type")
- bba.u32(len(pins), "num_bel_wires")
+ for bel_idx in range(len(loctype.bels)):
+ bel = loctype.bels[bel_idx]
+ bba.s(ddrg.to_str(bel.name), "name")
+ bba.u32(bel_types[ddrg.to_str(bel.type)], "type")
+ bba.u32(len(bel.wires), "num_bel_wires")
bba.r("loc%d_bel%d_wires" % (idx, bel_idx), "bel_wires")
bba.l("locations", "LocationTypePOD")
- for loctype, idx in sorted(location_types.items(), key=lambda x: x[1]):
- wires, arcs, bels = loctype
- bba.u32(len(bels), "num_bels")
- bba.u32(len(wires), "num_wires")
- bba.u32(len(arcs), "num_pips")
- bba.r("loc%d_bels" % idx if len(bels) > 0 else None, "bel_data")
- bba.r("loc%d_wires" % idx if len(wires) > 0 else None, "wire_data")
- bba.r("loc%d_pips" % idx if len(arcs) > 0 else None, "pips_data")
+ for idx in range(len(loctypes)):
+ loctype = ddrg.locationTypes[loctypes[idx]]
+ bba.u32(len(loctype.bels), "num_bels")
+ bba.u32(len(loctype.wires), "num_wires")
+ bba.u32(len(loctype.arcs), "num_pips")
+ bba.r("loc%d_bels" % idx if len(loctype.bels) > 0 else None, "bel_data")
+ bba.r("loc%d_wires" % idx if len(loctype.wires) > 0 else None, "wire_data")
+ bba.r("loc%d_pips" % idx if len(loctype.arcs) > 0 else None, "pips_data")
bba.l("location_types", "int32_t")
for y in range(0, max_row+1):
for x in range(0, max_col+1):
- bba.u32(type_at_location[x, y], "loctype")
+ bba.u32(loctypes.index(ddrg.typeAtLocation[pytrellis.Location(x, y)]), "loctype")
bba.l("tiletype_names", "RelPtr<char>")
for tt in tiletype_names:
@@ -659,28 +448,11 @@ def main():
print("Initialising chip...")
chip = pytrellis.Chip(dev_names[args.device])
print("Building routing graph...")
- rg = chip.get_routing_graph()
+ ddrg = pytrellis.make_dedup_chipdb(chip)
max_row = chip.get_max_row()
max_col = chip.get_max_col()
- print("Indexing wires...")
- for y in range(0, max_row + 1):
- for x in range(0, max_col + 1):
- import_location_wires(rg, x, y)
- print("Indexing arcs...")
- for y in range(0, max_row + 1):
- for x in range(0, max_col + 1):
- index_location_arcs(rg, x, y)
- print("Adding bels...")
- for y in range(0, max_row + 1):
- for x in range(0, max_col + 1):
- add_bels(chip, x, y)
- print("Importing tiles...")
- for y in range(0, max_row + 1):
- for x in range(0, max_col + 1):
- print(" At R{}C{}".format(y, x))
- import_location(rg, x, y)
- print("{} unique location types".format(len(location_types)))
- bba = write_database(args.device, "le")
+ print("{} unique location types".format(len(ddrg.locationTypes)))
+ bba = write_database(args.device, ddrg, "le")
if args.c_file: