diff options
Diffstat (limited to 'ecp5')
-rw-r--r-- | ecp5/arch.cc | 31 | ||||
-rw-r--r-- | ecp5/arch.h | 24 | ||||
-rw-r--r-- | ecp5/arch_place.cc | 38 | ||||
-rw-r--r-- | ecp5/archdefs.h | 3 | ||||
-rw-r--r-- | ecp5/bitstream.cc | 74 | ||||
-rw-r--r-- | ecp5/cells.cc | 158 | ||||
-rw-r--r-- | ecp5/cells.h | 3 | ||||
-rw-r--r-- | ecp5/constids.inc | 4 | ||||
-rw-r--r-- | ecp5/globals.cc | 359 | ||||
-rw-r--r-- | ecp5/globals.h | 26 | ||||
-rw-r--r-- | ecp5/pack.cc | 435 | ||||
-rwxr-xr-x | ecp5/trellis_import.py | 9 |
12 files changed, 1124 insertions, 40 deletions
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") |