From d770eb672fcaf303d391d4fb22e57b13dd130ca5 Mon Sep 17 00:00:00 2001 From: David Shah Date: Mon, 1 Oct 2018 15:23:12 +0100 Subject: [PATCH 01/11] ecp5: Helper functions for distributed RAM support Signed-off-by: David Shah --- common/design_utils.cc | 2 ++ ecp5/cells.cc | 45 ++++++++++++++++++++++++++++++++++++++++++ ecp5/cells.h | 2 ++ ecp5/pack.cc | 17 ++++++++++++++++ 4 files changed, 66 insertions(+) diff --git a/common/design_utils.cc b/common/design_utils.cc index 7ad7f749..e3051d20 100644 --- a/common/design_utils.cc +++ b/common/design_utils.cc @@ -76,6 +76,8 @@ void print_utilisation(const Context *ctx) // Connect a net to a port void connect_port(const Context *ctx, NetInfo *net, CellInfo *cell, IdString port_name) { + if (net == nullptr) + return; PortInfo &port = cell->ports.at(port_name); NPNR_ASSERT(port.net == nullptr); port.net = net; diff --git a/ecp5/cells.cc b/ecp5/cells.cc index 7e101ab2..048db1d7 100644 --- a/ecp5/cells.cc +++ b/ecp5/cells.cc @@ -238,4 +238,49 @@ void ccu2c_to_slice(Context *ctx, CellInfo *ccu, CellInfo *lc) 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")); +} + +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"); + + // TODO: INIT + + 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")); +} + NEXTPNR_NAMESPACE_END diff --git a/ecp5/cells.h b/ecp5/cells.h index d2ea5490..c68d1dd3 100644 --- a/ecp5/cells.h +++ b/ecp5/cells.h @@ -49,6 +49,8 @@ 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, int index); NEXTPNR_NAMESPACE_END diff --git a/ecp5/pack.cc b/ecp5/pack.cc index 14b387d5..2c7ce020 100644 --- a/ecp5/pack.cc +++ b/ecp5/pack.cc @@ -555,6 +555,23 @@ class Ecp5Packer flush_cells(); } + // Pack distributed RAM + void pack_dram() + { + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (is_dpram(ctx, ci)) { + std::unique_ptr ramw_slice = + create_ecp5_cell(ctx, ctx->id("TRELLIS_SLICE"), ci->name.str(ctx) + "$RAMW_SLICE"); + dram_to_ramw(ctx, ci, ramw_slice.get()); + + 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() { From a4ac174ccbc26eeef3cb49dab89658e2b5ecd90f Mon Sep 17 00:00:00 2001 From: David Shah Date: Mon, 1 Oct 2018 15:43:02 +0100 Subject: [PATCH 02/11] design_utils: Adding some design helper functions Signed-off-by: David Shah --- common/design_utils.cc | 30 ++++++++++++++++++++++++++++++ common/design_utils.h | 6 ++++++ 2 files changed, 36 insertions(+) diff --git a/common/design_utils.cc b/common/design_utils.cc index e3051d20..a0b87764 100644 --- a/common/design_utils.cc +++ b/common/design_utils.cc @@ -19,6 +19,7 @@ */ #include "design_utils.h" +#include #include #include "log.h" #include "util.h" @@ -95,4 +96,33 @@ void connect_port(const Context *ctx, NetInfo *net, CellInfo *cell, IdString por } } +void disconnect_port(const Context *ctx, CellInfo *cell, IdString port_name) +{ + if (!cell->ports.count(port_name)) + return; + PortInfo &port = cell->ports.at(port_name); + if (port.net != nullptr) { + port.net->users.erase(std::remove_if(port.net->users.begin(), port.net->users.end(), + [cell, port_name](const PortRef &user) { + return user.cell == cell && user.port == port_name; + }), + port.net->users.end()); + } +} + +void connect_ports(Context *ctx, CellInfo *cell1, IdString port1_name, CellInfo *cell2, IdString port2_name) +{ + PortInfo &port1 = cell1->ports.at(port1_name); + if (port1.net == nullptr) { + // No net on port1; need to create one + std::unique_ptr p1net(new NetInfo()); + p1net->name = ctx->id(cell1->name.str(ctx) + "$conn$" + port1_name.str(ctx)); + connect_port(ctx, p1net.get(), cell1, port1_name); + IdString p1name = p1net->name; + NPNR_ASSERT(!ctx->cells.count(p1name)); + ctx->nets[p1name] = std::move(p1net); + } + connect_port(ctx, port1.net, cell2, port2_name); +} + NEXTPNR_NAMESPACE_END diff --git a/common/design_utils.h b/common/design_utils.h index ccf2463b..8a42d21f 100644 --- a/common/design_utils.h +++ b/common/design_utils.h @@ -85,6 +85,12 @@ template CellInfo *net_driven_by(const Context *ctx, const NetInfo // Connect a net to a port void connect_port(const Context *ctx, NetInfo *net, CellInfo *cell, IdString port_name); +// Disconnect a net from a port +void disconnect_port(const Context *ctx, CellInfo *cell, IdString port_name); + +// Connect two ports together +void connect_ports(Context *ctx, CellInfo *cell1, IdString port1_name, CellInfo *cell2, IdString port2_name); + void print_utilisation(const Context *ctx); NEXTPNR_NAMESPACE_END From 885fae8236c0bdb73c99b0605e1d7bdf14000dd4 Mon Sep 17 00:00:00 2001 From: David Shah Date: Mon, 1 Oct 2018 16:43:22 +0100 Subject: [PATCH 03/11] ecp5: Handling of DRAM initialisation and wiring Signed-off-by: David Shah --- ecp5/cells.cc | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/ecp5/cells.cc b/ecp5/cells.cc index 048db1d7..ba2d511f 100644 --- a/ecp5/cells.cc +++ b/ecp5/cells.cc @@ -252,13 +252,49 @@ void dram_to_ramw(Context *ctx, CellInfo *ram, CellInfo *lc) 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 |= (i << 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"); - // TODO: INIT + unsigned permuted_init0 = 0, permuted_init1 = 0; + unsigned init0 = get_dram_init(ctx, ramw, index * 2), init1 = get_dram_init(ctx, ramw, 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")); @@ -281,6 +317,28 @@ void dram_to_ram_slice(Context *ctx, CellInfo *ram, CellInfo *lc, CellInfo *ramw 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 From 9518c5d7629bfac996f9a3585a8d7bd86789357d Mon Sep 17 00:00:00 2001 From: David Shah Date: Mon, 1 Oct 2018 17:05:02 +0100 Subject: [PATCH 04/11] ecp5: Working on DRAM packing Signed-off-by: David Shah --- ecp5/cells.h | 2 +- ecp5/pack.cc | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/ecp5/cells.h b/ecp5/cells.h index c68d1dd3..a5229fe0 100644 --- a/ecp5/cells.h +++ b/ecp5/cells.h @@ -50,7 +50,7 @@ void ff_to_slice(Context *ctx, CellInfo *ff, CellInfo *lc, int index, bool drive 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, int index); +void dram_to_ram_slice(Context *ctx, CellInfo *ram, CellInfo *lc, CellInfo *ramw, int index); NEXTPNR_NAMESPACE_END diff --git a/ecp5/pack.cc b/ecp5/pack.cc index 2c7ce020..fbe3879a 100644 --- a/ecp5/pack.cc +++ b/ecp5/pack.cc @@ -561,10 +561,77 @@ class Ecp5Packer for (auto cell : sorted(ctx->cells)) { CellInfo *ci = cell.second; if (is_dpram(ctx, ci)) { + + // Create RAMW slice std::unique_ptr 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 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 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 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_file(tile_ffs, ff0)) { + ff_to_slice(ctx, ff0, slice, 0, true); + 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_file(tile_ffs, ff1)) { + ff_to_slice(ctx, ff1, slice, 1, true); + tile_ffs.push_back(ff1); + packed_cells.insert(ff1->name); + } + } + } + + // 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); } From c8a9bb807c21db935985a6be0d7f7deb1afd16d0 Mon Sep 17 00:00:00 2001 From: David Shah Date: Mon, 1 Oct 2018 17:45:35 +0100 Subject: [PATCH 05/11] ecp5: Debugging DRAM packing Signed-off-by: David Shah --- common/nextpnr.cc | 5 +++++ common/timing.cc | 18 ++++++++++++++++++ ecp5/bitstream.cc | 9 +++++++++ ecp5/globals.cc | 2 +- ecp5/pack.cc | 12 +++++++----- 5 files changed, 40 insertions(+), 6 deletions(-) diff --git a/common/nextpnr.cc b/common/nextpnr.cc index 068bca6f..4e6407b2 100644 --- a/common/nextpnr.cc +++ b/common/nextpnr.cc @@ -245,6 +245,11 @@ void Context::check() const NPNR_ASSERT(ni == getBoundPipNet(w.second.pip)); } } + if (ni->driver.cell != nullptr) + NPNR_ASSERT(ni->driver.cell->ports.at(ni->driver.port).net == ni); + for (auto user : ni->users) { + NPNR_ASSERT(user.cell->ports.at(user.port).net == ni); + } } for (auto w : getWires()) { diff --git a/common/timing.cc b/common/timing.cc index 62697353..e5e90445 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -164,6 +164,24 @@ struct Timing } // Sanity check to ensure that all ports where fanins were recorded were indeed visited + if (!port_fanin.empty()) { + for (auto fanin : port_fanin) { + NetInfo *net = fanin.first->net; + if (net != nullptr) { + log_info(" remaining fanin includes %s (net %s)\n", fanin.first->name.c_str(ctx), + net->name.c_str(ctx)); + if (net->driver.cell != nullptr) + log_info(" driver = %s.%s\n", net->driver.cell->name.c_str(ctx), + net->driver.port.c_str(ctx)); + for (auto net_user : net->users) + log_info(" user: %s.%s\n", net_user.cell->name.c_str(ctx), + net_user.port.c_str(ctx)); + } else { + log_info(" remaining fanin includes %s (no net)\n", fanin.first->name.c_str(ctx)); + + } + } + } NPNR_ASSERT(port_fanin.empty()); // Go forwards topographically to find the maximum arrival time and max path length for each net diff --git a/ecp5/bitstream.cc b/ecp5/bitstream.cc index bfd51666..d8914f1a 100644 --- a/ecp5/bitstream.cc +++ b/ecp5/bitstream.cc @@ -269,6 +269,15 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex str_or_default(ci->params, ctx->id("INJECT1_1"), "YES")); } + 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")); + + // FIXME: WCKMUX + NPNR_ASSERT(str_or_default(ci->params, ctx->id("WCKMUX"), "WCK") == "WCK"); + } + + // 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) { diff --git a/ecp5/globals.cc b/ecp5/globals.cc index 91d224e5..e5627b66 100644 --- a/ecp5/globals.cc +++ b/ecp5/globals.cc @@ -52,7 +52,7 @@ class Ecp5GlobalRouter private: bool is_clock_port(const PortRef &user) { - if (user.cell->type == id_TRELLIS_SLICE && user.port == id_CLK) + if (user.cell->type == id_TRELLIS_SLICE && (user.port == id_CLK || user.port == id_WCK)) return true; return false; } diff --git a/ecp5/pack.cc b/ecp5/pack.cc index fbe3879a..53203e33 100644 --- a/ecp5/pack.cc +++ b/ecp5/pack.cc @@ -108,7 +108,7 @@ class Ecp5Packer } // 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_file(const std::vector &tile_ffs, CellInfo *ff0) + bool can_add_ff_to_tile(const std::vector &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"))) @@ -512,7 +512,7 @@ class Ecp5Packer 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_file(tile_ffs, ff0)) { + if (ff0 != nullptr && can_add_ff_to_tile(tile_ffs, ff0)) { ff_to_slice(ctx, ff0, slice.get(), 0, true); tile_ffs.push_back(ff0); packed_cells.insert(ff0->name); @@ -524,7 +524,7 @@ class Ecp5Packer 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_file(tile_ffs, ff1)) { + can_add_ff_to_tile(tile_ffs, ff1)) { ff_to_slice(ctx, ff1, slice.get(), 1, true); tile_ffs.push_back(ff1); packed_cells.insert(ff1->name); @@ -592,7 +592,7 @@ class Ecp5Packer 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_file(tile_ffs, ff0)) { + if (ff0 != nullptr && can_add_ff_to_tile(tile_ffs, ff0)) { ff_to_slice(ctx, ff0, slice, 0, true); tile_ffs.push_back(ff0); packed_cells.insert(ff0->name); @@ -604,7 +604,7 @@ class Ecp5Packer 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_file(tile_ffs, ff1)) { + can_add_ff_to_tile(tile_ffs, ff1)) { ff_to_slice(ctx, ff1, slice, 1, true); tile_ffs.push_back(ff1); packed_cells.insert(ff1->name); @@ -888,6 +888,7 @@ class Ecp5Packer { pack_io(); pack_constants(); + pack_dram(); pack_carries(); find_lutff_pairs(); pack_lut5s(); @@ -895,6 +896,7 @@ class Ecp5Packer pack_lut_pairs(); pack_remaining_luts(); pack_remaining_ffs(); + ctx->check(); } private: From 3dfc5b864a66ba1bfc286de884bd46a859e4306d Mon Sep 17 00:00:00 2001 From: David Shah Date: Mon, 1 Oct 2018 17:51:36 +0100 Subject: [PATCH 06/11] ecp5: Remove broken DRAM timing arc Signed-off-by: David Shah --- ecp5/arch.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ecp5/arch.cc b/ecp5/arch.cc index 830dfc7c..6cd83cce 100644 --- a/ecp5/arch.cc +++ b/ecp5/arch.cc @@ -511,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) || From 2c96d4770df064eb02050b0f94de5b45d8724b4c Mon Sep 17 00:00:00 2001 From: David Shah Date: Mon, 1 Oct 2018 18:15:11 +0100 Subject: [PATCH 07/11] ecp5: Fix DRAM initialisation Signed-off-by: David Shah --- ecp5/cells.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ecp5/cells.cc b/ecp5/cells.cc index ba2d511f..815ae228 100644 --- a/ecp5/cells.cc +++ b/ecp5/cells.cc @@ -261,7 +261,7 @@ static unsigned get_dram_init(const Context *ctx, const CellInfo *ram, int bit) for (int i = 0; i < 16; i++) { char c = idata.at(63 - (4 * i + bit)); if (c == '1') - value |= (i << i); + value |= (1 << i); else NPNR_ASSERT(c == '0' || c == 'x'); } @@ -275,7 +275,7 @@ void dram_to_ram_slice(Context *ctx, CellInfo *ram, CellInfo *lc, CellInfo *ramw 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, ramw, index * 2), init1 = get_dram_init(ctx, ramw, index * 2 + 1); + 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; From fd4498736ed4ddd0810913b03dd7f772072b4180 Mon Sep 17 00:00:00 2001 From: David Shah Date: Mon, 1 Oct 2018 18:19:56 +0100 Subject: [PATCH 08/11] ecp5: Fix packing of FFs into carry/DRAM slices Signed-off-by: David Shah --- ecp5/pack.cc | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/ecp5/pack.cc b/ecp5/pack.cc index 53203e33..3ba6e238 100644 --- a/ecp5/pack.cc +++ b/ecp5/pack.cc @@ -496,6 +496,7 @@ class Ecp5Packer std::vector> packed_chains; // Chain packing + std::vector> ff_packing; for (auto &chain : all_chains) { int cell_count = 0; std::vector tile_ffs; @@ -513,7 +514,7 @@ class Ecp5Packer 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_to_slice(ctx, ff0, slice.get(), 0, true); + ff_packing.push_back(std::make_tuple(ff0, slice.get(), 0)); tile_ffs.push_back(ff0); packed_cells.insert(ff0->name); } @@ -525,7 +526,7 @@ class Ecp5Packer 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_to_slice(ctx, ff1, slice.get(), 1, true); + ff_packing.push_back(std::make_tuple(ff1, slice.get(), 1)); tile_ffs.push_back(ff1); packed_cells.insert(ff1->name); } @@ -538,6 +539,9 @@ class Ecp5Packer 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; @@ -586,6 +590,7 @@ class Ecp5Packer disconnect_port(ctx, ci, ctx->id("RAD[3]")); // Attempt to pack FFs into RAM slices + std::vector> ff_packing; std::vector tile_ffs; for (auto slice : {ram0_slice.get(), ram1_slice.get()}) { CellInfo *ff0 = nullptr; @@ -593,7 +598,7 @@ class Ecp5Packer 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_to_slice(ctx, ff0, slice, 0, true); + ff_packing.push_back(std::make_tuple(ff0, slice, 0)); tile_ffs.push_back(ff0); packed_cells.insert(ff0->name); } @@ -605,13 +610,16 @@ class Ecp5Packer 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_to_slice(ctx, ff1, slice, 1, true); + 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; From 9ebec3b87f4b4a6a24fc391d1acd944490630499 Mon Sep 17 00:00:00 2001 From: David Shah Date: Mon, 1 Oct 2018 18:20:14 +0100 Subject: [PATCH 09/11] clangformat Signed-off-by: David Shah --- common/timing.cc | 4 +--- ecp5/arch.cc | 2 +- ecp5/bitstream.cc | 4 +--- ecp5/pack.cc | 4 ++-- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/common/timing.cc b/common/timing.cc index e5e90445..e3a7635f 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -174,11 +174,9 @@ struct Timing log_info(" driver = %s.%s\n", net->driver.cell->name.c_str(ctx), net->driver.port.c_str(ctx)); for (auto net_user : net->users) - log_info(" user: %s.%s\n", net_user.cell->name.c_str(ctx), - net_user.port.c_str(ctx)); + log_info(" user: %s.%s\n", net_user.cell->name.c_str(ctx), net_user.port.c_str(ctx)); } else { log_info(" remaining fanin includes %s (no net)\n", fanin.first->name.c_str(ctx)); - } } } diff --git a/ecp5/arch.cc b/ecp5/arch.cc index 6cd83cce..9c059005 100644 --- a/ecp5/arch.cc +++ b/ecp5/arch.cc @@ -511,7 +511,7 @@ bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort delay.delay = 193; return true; } -#if 0 //FIXME +#if 0 // FIXME if (fromPort == id_WCK && (toPort == id_F0 || toPort == id_F1)) { delay.delay = 717; return true; diff --git a/ecp5/bitstream.cc b/ecp5/bitstream.cc index d8914f1a..0c2d511d 100644 --- a/ecp5/bitstream.cc +++ b/ecp5/bitstream.cc @@ -270,14 +270,12 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex } 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")); + cc.tiles[tname].add_enum(slice + ".WREMUX", str_or_default(ci->params, ctx->id("WREMUX"), "WRE")); // FIXME: WCKMUX NPNR_ASSERT(str_or_default(ci->params, ctx->id("WCKMUX"), "WCK") == "WCK"); } - // 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) { diff --git a/ecp5/pack.cc b/ecp5/pack.cc index 3ba6e238..8e33356e 100644 --- a/ecp5/pack.cc +++ b/ecp5/pack.cc @@ -525,7 +525,7 @@ class Ecp5Packer 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)) { + 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); @@ -609,7 +609,7 @@ class Ecp5Packer 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)) { + can_add_ff_to_tile(tile_ffs, ff1)) { ff_packing.push_back(std::make_tuple(ff1, slice, 1)); tile_ffs.push_back(ff1); packed_cells.insert(ff1->name); From 8cbc92b7f3cd106363ee115c4ec6a9f2bbaba8c8 Mon Sep 17 00:00:00 2001 From: David Shah Date: Mon, 1 Oct 2018 18:45:14 +0100 Subject: [PATCH 10/11] ecp5: Small DRAM routing fixes Signed-off-by: David Shah --- ecp5/globals.cc | 16 +++++++++++++++- ecp5/pack.cc | 16 ++++++++++------ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/ecp5/globals.cc b/ecp5/globals.cc index e5627b66..364e4bca 100644 --- a/ecp5/globals.cc +++ b/ecp5/globals.cc @@ -302,6 +302,14 @@ class Ecp5GlobalRouter 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: @@ -333,7 +341,13 @@ class Ecp5GlobalRouter NetInfo *global = insert_dcc(clock); bool routed = route_onto_global(global, glbid); NPNR_ASSERT(routed); - for (const auto &user : global->users) { + + // 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); } } diff --git a/ecp5/pack.cc b/ecp5/pack.cc index 8e33356e..60ac55f6 100644 --- a/ecp5/pack.cc +++ b/ecp5/pack.cc @@ -598,9 +598,11 @@ class Ecp5Packer 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, 0)); - tile_ffs.push_back(ff0); - packed_cells.insert(ff0->name); + if (net_or_nullptr(ff0, ctx->id("CLK")) == net_or_nullptr(slice, ctx->id("WCK"))) { + ff_packing.push_back(std::make_tuple(ff0, slice, 0)); + tile_ffs.push_back(ff0); + packed_cells.insert(ff0->name); + } } } @@ -610,9 +612,11 @@ class Ecp5Packer 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, 1)); - tile_ffs.push_back(ff1); - packed_cells.insert(ff1->name); + if (net_or_nullptr(ff1, ctx->id("CLK")) == net_or_nullptr(slice, ctx->id("WCK"))) { + ff_packing.push_back(std::make_tuple(ff1, slice, 1)); + tile_ffs.push_back(ff1); + packed_cells.insert(ff1->name); + } } } } From bf7161d2b49ff5660626a0ac4af5a7eeb3fb77c1 Mon Sep 17 00:00:00 2001 From: David Shah Date: Tue, 2 Oct 2018 15:50:45 +0100 Subject: [PATCH 11/11] ecp5: Negative clock support, general slice improvements Signed-off-by: David Shah --- ecp5/bitstream.cc | 25 +++++++++++++++++++++++-- ecp5/cells.cc | 2 ++ ecp5/pack.cc | 18 ++++++++++++++++-- 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/ecp5/bitstream.cc b/ecp5/bitstream.cc index 0c2d511d..296ea753 100644 --- a/ecp5/bitstream.cc +++ b/ecp5/bitstream.cc @@ -260,6 +260,17 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex 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")); + } } if (str_or_default(ci->params, ctx->id("MODE"), "LOGIC") == "CCU2") { @@ -267,13 +278,23 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex 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")); - // FIXME: WCKMUX - NPNR_ASSERT(str_or_default(ci->params, ctx->id("WCKMUX"), "WCK") == "WCK"); + 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 diff --git a/ecp5/cells.cc b/ecp5/cells.cc index 815ae228..a728104d 100644 --- a/ecp5/cells.cc +++ b/ecp5/cells.cc @@ -185,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")); diff --git a/ecp5/pack.cc b/ecp5/pack.cc index 60ac55f6..0045617b 100644 --- a/ecp5/pack.cc +++ b/ecp5/pack.cc @@ -128,6 +128,20 @@ class Ecp5Packer 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) { @@ -598,7 +612,7 @@ class Ecp5Packer 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 (net_or_nullptr(ff0, ctx->id("CLK")) == net_or_nullptr(slice, ctx->id("WCK"))) { + 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); @@ -612,7 +626,7 @@ class Ecp5Packer 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 (net_or_nullptr(ff1, ctx->id("CLK")) == net_or_nullptr(slice, ctx->id("WCK"))) { + 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);