From 0b5748a7afeb914efa80b7856d0d6c53e5b4f8ec Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Tue, 27 Nov 2018 19:20:15 +0100 Subject: [PATCH 01/46] Fix compile on GCC 5.5 or older --- common/log.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/common/log.h b/common/log.h index 77adbb2f..52158f18 100644 --- a/common/log.h +++ b/common/log.h @@ -84,4 +84,14 @@ static inline void log_assert_worker(bool cond, const char *expr, const char *fi NEXTPNR_NAMESPACE_END +namespace std { +template <> struct hash +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX LogLevel &loglevel) const noexcept + { + return std::hash()((int)loglevel); + } +}; +} // namespace std + #endif From 80f7ef4b4b5a632e73c077da5810b218969132bc Mon Sep 17 00:00:00 2001 From: David Shah Date: Tue, 27 Nov 2018 19:06:55 +0000 Subject: [PATCH 02/46] ice40: Finer-grained control of global promotion Signed-off-by: David Shah --- ice40/main.cc | 11 ++++++++++- ice40/pack.cc | 6 ++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/ice40/main.cc b/ice40/main.cc index fcc56d04..4b6a9e42 100644 --- a/ice40/main.cc +++ b/ice40/main.cc @@ -65,6 +65,9 @@ po::options_description Ice40CommandHandler::getArchOptions() specific.add_options()("pcf", po::value(), "PCF constraints file to ingest"); specific.add_options()("asc", po::value(), "asc bitstream file to write"); specific.add_options()("read", po::value(), "asc bitstream file to read"); + specific.add_options()("promote-logic", + "enable promotion of 'logic' globals (in addition to clk/ce/sr by default)"); + specific.add_options()("no-promote-globals", "disable all global promotion"); specific.add_options()("tmfuzz", "run path delay estimate fuzzer"); return specific; } @@ -152,7 +155,13 @@ std::unique_ptr Ice40CommandHandler::createContext() if (vm.count("package")) chipArgs.package = vm["package"].as(); - return std::unique_ptr(new Context(chipArgs)); + auto ctx = std::unique_ptr(new Context(chipArgs)); + + if (vm.count("promote-logic")) + ctx->settings[ctx->id("promote_logic")] = "1"; + if (vm.count("no-promote-globals")) + ctx->settings[ctx->id("no_promote_globals")] = "1"; + return ctx; } int main(int argc, char *argv[]) diff --git a/ice40/pack.cc b/ice40/pack.cc index fc28121e..60464230 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -637,7 +637,8 @@ static void promote_globals(Context *ctx) }); if (global_clock->second == 0 && prom_logics < 4 && global_logic->second > logic_fanout_thresh && (global_logic->second > global_cen->second || prom_cens >= cens_available) && - (global_logic->second > global_reset->second || prom_resets >= resets_available)) { + (global_logic->second > global_reset->second || prom_resets >= resets_available) && + bool_or_default(ctx->settings, ctx->id("promote_logic"), false)) { NetInfo *logicnet = ctx->nets[global_logic->first].get(); insert_global(ctx, logicnet, false, false, true); ++prom_globals; @@ -1119,7 +1120,8 @@ bool Arch::pack() pack_carries(ctx); pack_ram(ctx); pack_special(ctx); - promote_globals(ctx); + if (!bool_or_default(ctx->settings, ctx->id("no_promote_globals"), false)) + promote_globals(ctx); ctx->assignArchInfo(); constrain_chains(ctx); ctx->assignArchInfo(); From 5f0f2b060b1f8dee94e4cbcfe1b190c9f0b53bb0 Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Tue, 27 Nov 2018 03:16:05 +0100 Subject: [PATCH 03/46] ice40: Update the way LVDS inputs are handled during bitstream generation * Instead of "patching" input_en, we completely separate config for normal and LVDS pair. - For normal pair, nothing changes - For LVDS pairs, the IE/REN bits are always set as if the input buffer are disabled. Then if input_en was set to 1 (i.e. the input is actually for something), then we set the IoCtrl.LVDS bit. - Also for LVDS, if input is used, pullups are forcibly disabled. * When scanning for unused IOs, never process those part of a LVDS pair. They will have been configured by the complement Signed-off-by: Sylvain Munaut --- ice40/bitstream.cc | 96 +++++++++++++++++++++++----------------------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/ice40/bitstream.cc b/ice40/bitstream.cc index ecb26753..e20d372a 100644 --- a/ice40/bitstream.cc +++ b/ice40/bitstream.cc @@ -486,7 +486,7 @@ void write_asc(const Context *ctx, std::ostream &out) unsigned pin_type = get_param_or_def(cell.second.get(), ctx->id("PIN_TYPE")); bool neg_trigger = get_param_or_def(cell.second.get(), ctx->id("NEG_TRIGGER")); bool pullup = get_param_or_def(cell.second.get(), ctx->id("PULLUP")); - bool lvds = get_param_str_or_def(cell.second.get(), ctx->id("IO_STANDARD")) == "SB_LVDS_INPUT"; + bool lvds = cell.second->ioInfo.lvds; bool used_by_pll_out = sb_io_used_by_pll_out.count(Loc(x, y, z)) > 0; bool used_by_pll_pad = sb_io_used_by_pll_pad.count(Loc(x, y, z)) > 0; @@ -495,64 +495,64 @@ void write_asc(const Context *ctx, std::ostream &out) set_config(ti, config.at(y).at(x), "IOB_" + std::to_string(z) + ".PINTYPE_" + std::to_string(i), val); } set_config(ti, config.at(y).at(x), "NegClk", neg_trigger); - auto ieren = get_ieren(bi, x, y, z); - int iex, iey, iez; - std::tie(iex, iey, iez) = ieren; - NPNR_ASSERT(iez != -1); - bool input_en; - if (lvds) { - input_en = false; - pullup = false; - } else { - if ((ctx->wire_to_net[ctx->getBelPinWire(bel, id_D_IN_0).index] != nullptr) || - (ctx->wire_to_net[ctx->getBelPinWire(bel, id_D_IN_1).index] != nullptr)) { - input_en = true; - } else { - input_en = false; - } + bool input_en = false; + if ((ctx->wire_to_net[ctx->getBelPinWire(bel, id_D_IN_0).index] != nullptr) || + (ctx->wire_to_net[ctx->getBelPinWire(bel, id_D_IN_1).index] != nullptr)) { + input_en = true; } - input_en = (input_en & !used_by_pll_out) | used_by_pll_pad; input_en |= cell.second->ioInfo.global; - if (ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K) { - set_config(ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), !input_en); - set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), !pullup); - } else { - set_config(ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), input_en); - set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), !pullup); - } - - if (ctx->args.type == ArchArgs::UP5K) { - if (iez == 0) { - set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_39", !pullup); - } else if (iez == 1) { - set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_35", !pullup); - } - } - - if (lvds) { - NPNR_ASSERT(z == 0); - set_config(ti, config.at(y).at(x), "IoCtrl.LVDS", true); - // Set comp IO config - auto comp_ieren = get_ieren(bi, x, y, 1); - int ciex, ciey, ciez; - std::tie(ciex, ciey, ciez) = comp_ieren; + if (!lvds) { + auto ieren = get_ieren(bi, x, y, z); + int iex, iey, iez; + std::tie(iex, iey, iez) = ieren; + NPNR_ASSERT(iez != -1); if (ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K) { - set_config(ti, config.at(ciey).at(ciex), "IoCtrl.IE_" + std::to_string(ciez), !input_en); - set_config(ti, config.at(ciey).at(ciex), "IoCtrl.REN_" + std::to_string(ciez), !pullup); + set_config(ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), !input_en); + set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), !pullup); } else { - set_config(ti, config.at(ciey).at(ciex), "IoCtrl.IE_" + std::to_string(ciez), input_en); - set_config(ti, config.at(ciey).at(ciex), "IoCtrl.REN_" + std::to_string(ciez), !pullup); + set_config(ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), input_en); + set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), !pullup); } if (ctx->args.type == ArchArgs::UP5K) { - if (ciez == 0) { - set_config(ti, config.at(ciey).at(ciex), "IoCtrl.cf_bit_39", !pullup); + if (iez == 0) { + set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_39", !pullup); } else if (iez == 1) { - set_config(ti, config.at(ciey).at(ciex), "IoCtrl.cf_bit_35", !pullup); + set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_35", !pullup); + } + } + } else { + NPNR_ASSERT(z == 0); + // Only enable the actual LVDS buffer if input is used for something + set_config(ti, config.at(y).at(x), "IoCtrl.LVDS", input_en); + + // Set both IO config + for (int cz = 0; cz < 2; cz++) { + auto ieren = get_ieren(bi, x, y, cz); + int iex, iey, iez; + std::tie(iex, iey, iez) = ieren; + NPNR_ASSERT(iez != -1); + + pullup &= !input_en; /* If input is used, force disable pullups */ + + if (ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K) { + set_config(ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), true); + set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), !pullup); + } else { + set_config(ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), false); + set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), !pullup); + } + + if (ctx->args.type == ArchArgs::UP5K) { + if (iez == 0) { + set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_39", !pullup); + } else if (iez == 1) { + set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_35", !pullup); + } } } } @@ -688,7 +688,7 @@ void write_asc(const Context *ctx, std::ostream &out) int iex, iey, iez; std::tie(iex, iey, iez) = ieren; if (iez != -1) { - // IO is not actually unused if part of an LVDS pair + // If IO is in LVDS pair, it will be configured by the other pair if (z == 1) { BelId lvds0 = ctx->getBelByLocation(Loc{x, y, 0}); const CellInfo *lvds0cell = ctx->getBoundBelCell(lvds0); From a65b12e8d6d291c04a3982448250014c7de3cbd3 Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Wed, 28 Nov 2018 10:04:06 +0100 Subject: [PATCH 04/46] ice40: Revamp the whole PLL placement/validity check logic We do a pre-pass on all the PLLs to place them before packing. To place them: - First pass with all the PADs PLLs since those can only fit at one specific BEL depending on the input connection - Second pass with all the dual outputs CORE PLLs. Those can go anywhere where there is no conflicts with their A & B outputs and used IO pins - Third pass with the single output CORE PLLs. Those have the least constrains. During theses passes, we also check the validity of all their connections. Signed-off-by: Sylvain Munaut --- ice40/pack.cc | 272 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 200 insertions(+), 72 deletions(-) diff --git a/ice40/pack.cc b/ice40/pack.cc index 60464230..65b5a222 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -680,6 +680,190 @@ static void promote_globals(Context *ctx) } } +// Figure out where to place PLLs +static void place_plls(Context *ctx) +{ + std::map> pll_all_bels; + std::map pll_used_bels; + std::vector pll_cells; + std::map bel2io; + + log_info("Placing PLLs..\n"); + + // Find all the PLLs BELs and matching IO sites + for (auto bel : ctx->getBels()) { + if (ctx->getBelType(bel) != id_ICESTORM_PLL) + continue; + if (ctx->isBelLocked(bel)) + continue; + + auto io_a_pin = ctx->getIOBSharingPLLPin(bel, id_PLLOUT_A); + auto io_b_pin = ctx->getIOBSharingPLLPin(bel, id_PLLOUT_B); + + pll_all_bels[bel] = std::make_pair(io_a_pin, io_b_pin); + } + + // Find all the PLLs cells we need to place and do pre-checks + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (!is_sb_pll40(ctx, ci)) + continue; + + // If it's constrained already, add to already used list + if (ci->attrs.count(ctx->id("BEL"))) { + BelId bel_constrain = ctx->getBelByName(ctx->id(ci->attrs[ctx->id("BEL")])); + if (pll_all_bels.count(bel_constrain) == 0) + log_error("PLL '%s' is constrained to invalid BEL '%s'\n", ci->name.c_str(ctx), + ci->attrs[ctx->id("BEL")].c_str()); + pll_used_bels[bel_constrain] = ci; + } + + // Add it to our list of PLLs to process + pll_cells.push_back(ci); + } + + // Scan all the PAD PLLs + for (auto ci : pll_cells) { + if (!is_sb_pll40_pad(ctx, ci)) + continue; + + // Check PACKAGEPIN connection + if (!ci->ports.count(ctx->id("PACKAGEPIN"))) + log_error("PLL '%s' is of PAD type but doesn't have a PACKAGEPIN port\n", ci->name.c_str(ctx)); + + NetInfo *ni = ci->ports.at(ctx->id("PACKAGEPIN")).net; + if (ni == nullptr || ni->driver.cell == nullptr) + log_error("PLL '%s' is of PAD type but doesn't have a valid PACKAGEPIN connection\n", ci->name.c_str(ctx)); + + CellInfo *io_cell = ni->driver.cell; + if (io_cell->type != id_SB_IO || ni->driver.port != id_D_IN_0) + log_error("PLL '%s' has a PACKAGEPIN driven by an %s, should be directly connected to an input " + "SB_IO.D_IN_0 port\n", + ci->name.c_str(ctx), io_cell->type.c_str(ctx)); + if (ni->users.size() != 1) + log_error("PLL '%s' clock input '%s' can only drive PLL\n", ci->name.c_str(ctx), ni->name.c_str(ctx)); + if (!io_cell->attrs.count(ctx->id("BEL"))) + log_error("PLL '%s' PACKAGEPIN SB_IO '%s' is unconstrained\n", ci->name.c_str(ctx), + io_cell->name.c_str(ctx)); + + BelId io_bel = ctx->getBelByName(ctx->id(io_cell->attrs.at(ctx->id("BEL")))); + BelId found_bel; + + // Find the PLL BEL that would suit that connection + for (auto pll_bel : pll_all_bels) { + if (std::get<0>(pll_bel.second).bel == io_bel) { + found_bel = pll_bel.first; + break; + } + } + + if (found_bel == BelId()) + log_error("PLL '%s' PACKAGEPIN SB_IO '%s' is not connected to any PLL BEL\n", ci->name.c_str(ctx), + io_cell->name.c_str(ctx)); + if (pll_used_bels.count(found_bel)) { + CellInfo *conflict_cell = pll_used_bels.at(found_bel); + log_error("PLL '%s' PACKAGEPIN forces it to BEL %s but BEL is already assigned to PLL '%s'\n", + ci->name.c_str(ctx), ctx->getBelName(found_bel).c_str(ctx), conflict_cell->name.c_str(ctx)); + } + + // Is it user constrained ? + if (ci->attrs.count(ctx->id("BEL"))) { + // Yes. Check it actually matches ! + BelId bel_constrain = ctx->getBelByName(ctx->id(ci->attrs[ctx->id("BEL")])); + if (bel_constrain != found_bel) + log_error("PLL '%s' is user constrained to %s but can only be placed in %s based on its PACKAGEPIN " + "connection\n", + ci->name.c_str(ctx), ctx->getBelName(bel_constrain).c_str(ctx), + ctx->getBelName(found_bel).c_str(ctx)); + } else { + // No, we can constrain it ourselves + ci->attrs[ctx->id("BEL")] = ctx->getBelName(found_bel).str(ctx); + pll_used_bels[found_bel] = ci; + } + + // Inform user + log_info(" constrained PLL '%s' to %s\n", ci->name.c_str(ctx), ctx->getBelName(found_bel).c_str(ctx)); + } + + // Scan all SB_IOs to check for conflict with PLL BELs + for (auto io_cell : sorted(ctx->cells)) { + CellInfo *io_ci = io_cell.second; + if (!is_sb_io(ctx, io_ci)) + continue; + + // Only consider bound IO that are used as inputs + if (!io_ci->attrs.count(ctx->id("BEL"))) + continue; + if ((!io_ci->ports.count(id_D_IN_0) || (io_ci->ports[id_D_IN_0].net == nullptr)) && + (!io_ci->ports.count(id_D_IN_1) || (io_ci->ports[id_D_IN_1].net == nullptr))) + continue; + + // Check all placed PLL (either forced by user, or forced by PACKAGEPIN) + BelId io_bel = ctx->getBelByName(ctx->id(io_ci->attrs[ctx->id("BEL")])); + + for (auto placed_pll : pll_used_bels) { + BelPin pll_io_a, pll_io_b; + std::tie(pll_io_a, pll_io_b) = pll_all_bels[placed_pll.first]; + if (io_bel == pll_io_a.bel) { + // All the PAD type PLL stuff already checked above,so only + // check for conflict with a user placed CORE PLL + if (!is_sb_pll40_pad(ctx, placed_pll.second)) + log_error("PLL '%s' A output conflict with SB_IO '%s' that's used as input\n", + placed_pll.second->name.c_str(ctx), io_cell.second->name.c_str(ctx)); + } else if (io_bel == pll_io_b.bel) { + if (is_sb_pll40_dual(ctx, placed_pll.second)) + log_error("PLL '%s' B output conflicts with SB_IO '%s' that's used as input\n", + placed_pll.second->name.c_str(ctx), io_cell.second->name.c_str(ctx)); + } + } + + // Save for later checks + bel2io[io_bel] = io_ci; + } + + // Scan all the CORE PLLs and place them in remaining available PLL BELs + // (in two pass ... first do the dual ones, harder to place, then single port) + for (int i = 0; i < 2; i++) { + for (auto ci : pll_cells) { + if (is_sb_pll40_pad(ctx, ci)) + continue; + if (is_sb_pll40_dual(ctx, ci) ^ i) + continue; + + // Check REFERENCECLK connection + if (!ci->ports.count(id_REFERENCECLK)) + log_error("PLL '%s' is of CORE type but doesn't have a REFERENCECLK port\n", ci->name.c_str(ctx)); + + NetInfo *ni = ci->ports.at(id_REFERENCECLK).net; + if (ni == nullptr || ni->driver.cell == nullptr) + log_error("PLL '%s' is of CORE type but doesn't have a valid REFERENCECLK connection\n", + ci->name.c_str(ctx)); + + // Find a BEL for it + BelId found_bel; + for (auto bel_pll : pll_all_bels) { + BelPin pll_io_a, pll_io_b; + std::tie(pll_io_a, pll_io_b) = bel_pll.second; + if (bel2io.count(pll_io_a.bel)) + continue; + if (bel2io.count(pll_io_b.bel) && is_sb_pll40_dual(ctx, ci)) + continue; + found_bel = bel_pll.first; + break; + } + + // Apply constrain & Inform user of result + if (found_bel == BelId()) + log_error("PLL '%s' couldn't be placed anywhere, no suitable BEL found\n", ci->name.c_str(ctx)); + + log_info(" constrained PLL '%s' to %s\n", ci->name.c_str(ctx), ctx->getBelName(found_bel).c_str(ctx)); + + ci->attrs[ctx->id("BEL")] = ctx->getBelName(found_bel).str(ctx); + pll_used_bels[found_bel] = ci; + } + } +} + // spliceLUT adds a pass-through LUT LC between the given cell's output port // and either all users or only non_LUT users. static std::unique_ptr spliceLUT(Context *ctx, CellInfo *ci, IdString portId, bool onlyNonLUTs) @@ -940,82 +1124,25 @@ static void pack_special(Context *ctx) replace_port(ci, ctx->id(pi.name.c_str(ctx)), packed.get(), ctx->id(newname)); } - // If PLL is not constrained already, do that - we need this - // information to then constrain the LOCK LUT. - BelId pll_bel; - bool constrained = false; - if (packed->attrs.find(ctx->id("BEL")) == packed->attrs.end()) { - for (auto bel : ctx->getBels()) { - if (ctx->getBelType(bel) != id_ICESTORM_PLL) - continue; - if (ctx->isBelLocked(bel)) - continue; + // PLL must have been placed already in place_plls() + BelId pll_bel = ctx->getBelByName(ctx->id(packed->attrs[ctx->id("BEL")])); + NPNR_ASSERT(pll_bel != BelId()); - // A PAD PLL must have its' PACKAGEPIN on the SB_IO that's shared - // with PLLOUT_A. - if (is_pad) { - auto pll_sb_io_belpin = ctx->getIOBSharingPLLPin(bel, id_PLLOUT_A); - NPNR_ASSERT(pad_packagepin_net != nullptr); - auto pll_packagepin_driver = pad_packagepin_net->driver; - NPNR_ASSERT(pll_packagepin_driver.cell != nullptr); - if (pll_packagepin_driver.cell->type != ctx->id("SB_IO")) { - log_error("PLL '%s' has a PACKAGEPIN driven by " - "an %s, should be directly connected to an input SB_IO\n", - ci->name.c_str(ctx), pll_packagepin_driver.cell->type.c_str(ctx)); - } + // Deal with PAD PLL peculiarities + if (is_pad) { + NPNR_ASSERT(pad_packagepin_net != nullptr); + auto pll_packagepin_driver = pad_packagepin_net->driver; + NPNR_ASSERT(pll_packagepin_driver.cell != nullptr); + auto packagepin_cell = pll_packagepin_driver.cell; + auto packagepin_bel_name = packagepin_cell->attrs.find(ctx->id("BEL")); - auto packagepin_cell = pll_packagepin_driver.cell; - auto packagepin_bel_name = packagepin_cell->attrs.find(ctx->id("BEL")); - if (packagepin_bel_name == packagepin_cell->attrs.end()) { - log_error("PLL '%s' PACKAGEPIN SB_IO '%s' is unconstrained\n", ci->name.c_str(ctx), - packagepin_cell->name.c_str(ctx)); - } - auto packagepin_bel = ctx->getBelByName(ctx->id(packagepin_bel_name->second)); - if (pll_sb_io_belpin.bel != packagepin_bel) { - log_error("PLL '%s' PACKAGEPIN is connected to pin %s, can only be pin %s\n", - ci->name.c_str(ctx), ctx->getBelPackagePin(packagepin_bel).c_str(), - ctx->getBelPackagePin(pll_sb_io_belpin.bel).c_str()); - } - if (pad_packagepin_net->users.size() != 1) { - log_error("PLL '%s' clock input '%s' can only drive PLL\n", ci->name.c_str(ctx), - pad_packagepin_net->name.c_str(ctx)); - } - // Set an attribute about this PLL's PAD SB_IO. - packed->attrs[ctx->id("BEL_PAD_INPUT")] = packagepin_bel_name->second; - // Remove the connection from the SB_IO to the PLL. - packagepin_cell->ports.erase(pll_packagepin_driver.port); - } + // Set an attribute about this PLL's PAD SB_IO. + packed->attrs[ctx->id("BEL_PAD_INPUT")] = packagepin_bel_name->second; - log_info(" constrained PLL '%s' to %s\n", packed->name.c_str(ctx), - ctx->getBelName(bel).c_str(ctx)); - packed->attrs[ctx->id("BEL")] = ctx->getBelName(bel).str(ctx); - pll_bel = bel; - constrained = true; - break; - } - if (!constrained) { - log_error("Could not constrain PLL '%s' to any PLL Bel (too many PLLs?)\n", - packed->name.c_str(ctx)); - } - } else { - pll_bel = ctx->getBelByName(ctx->id(packed->attrs[ctx->id("BEL")])); - if (ctx->getBelType(pll_bel) != id_ICESTORM_PLL) - log_error("PLL '%s' is constrained to BEL %s which isn't a ICESTORM_PLL BEL\n", - packed->name.c_str(ctx), ctx->getBelName(pll_bel).c_str(ctx)); - if (ctx->isBelLocked(pll_bel)) - log_error("PLL '%s' is constrained to locked BEL %s\n", packed->name.c_str(ctx), - ctx->getBelName(pll_bel).c_str(ctx)); - log_info(" constrained PLL '%s' to %s\n", packed->name.c_str(ctx), - ctx->getBelName(pll_bel).c_str(ctx)); - } - - // Delete the original PACKAGEPIN net if needed. - if (pad_packagepin_net != nullptr) { - for (auto user : pad_packagepin_net->users) { + // Disconnect PACKAGEPIN (it's a physical HW link) + for (auto user : pad_packagepin_net->users) user.cell->ports.erase(user.port); - } - if (pad_packagepin_net->driver.cell != nullptr) - pad_packagepin_net->driver.cell->ports.erase(pad_packagepin_net->driver.port); + packagepin_cell->ports.erase(pll_packagepin_driver.port); ctx->nets.erase(pad_packagepin_net->name); pad_packagepin_net = nullptr; } @@ -1119,6 +1246,7 @@ bool Arch::pack() pack_nonlut_ffs(ctx); pack_carries(ctx); pack_ram(ctx); + place_plls(ctx); pack_special(ctx); if (!bool_or_default(ctx->settings, ctx->id("no_promote_globals"), false)) promote_globals(ctx); From ba958d17921beac714d66246f70ae7ea073197b5 Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Wed, 28 Nov 2018 11:19:49 +0100 Subject: [PATCH 05/46] ice40: Try to be helpful and suggest using PAD PLL instead of CORE Signed-off-by: Sylvain Munaut --- ice40/pack.cc | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/ice40/pack.cc b/ice40/pack.cc index 65b5a222..170b25e7 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -839,13 +839,22 @@ static void place_plls(Context *ctx) log_error("PLL '%s' is of CORE type but doesn't have a valid REFERENCECLK connection\n", ci->name.c_str(ctx)); + // Could this be a PAD PLL ? + bool could_be_pad = false; + BelId pad_bel; + if (ni->users.size() == 1 && is_sb_io(ctx, ni->driver.cell) && ni->driver.cell->attrs.count(ctx->id("BEL"))) + pad_bel = ctx->getBelByName(ctx->id(ni->driver.cell->attrs[ctx->id("BEL")])); + // Find a BEL for it BelId found_bel; for (auto bel_pll : pll_all_bels) { BelPin pll_io_a, pll_io_b; std::tie(pll_io_a, pll_io_b) = bel_pll.second; - if (bel2io.count(pll_io_a.bel)) + if (bel2io.count(pll_io_a.bel)) { + if (pll_io_a.bel == pad_bel) + could_be_pad = !bel2io.count(pll_io_b.bel) || !is_sb_pll40_dual(ctx, ci); continue; + } if (bel2io.count(pll_io_b.bel) && is_sb_pll40_dual(ctx, ci)) continue; found_bel = bel_pll.first; @@ -854,9 +863,12 @@ static void place_plls(Context *ctx) // Apply constrain & Inform user of result if (found_bel == BelId()) - log_error("PLL '%s' couldn't be placed anywhere, no suitable BEL found\n", ci->name.c_str(ctx)); + log_error("PLL '%s' couldn't be placed anywhere, no suitable BEL found.%s\n", ci->name.c_str(ctx), + could_be_pad ? " Did you mean to use a PAD PLL ?" : ""); log_info(" constrained PLL '%s' to %s\n", ci->name.c_str(ctx), ctx->getBelName(found_bel).c_str(ctx)); + if (could_be_pad) + log_info(" (given its connections, this PLL could have been a PAD PLL)\n"); ci->attrs[ctx->id("BEL")] = ctx->getBelName(found_bel).str(ctx); pll_used_bels[found_bel] = ci; From a974124a7a984cad831065f337953074596daefb Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 28 Nov 2018 23:52:48 +0000 Subject: [PATCH 06/46] ice40: print fanout of nets promoted to globals. --- ice40/pack.cc | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/ice40/pack.cc b/ice40/pack.cc index 170b25e7..e2344295 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -518,10 +518,14 @@ static bool is_logic_port(BaseCtx *ctx, const PortRef &port) !is_sb_pll40(ctx, port.cell); } -static void insert_global(Context *ctx, NetInfo *net, bool is_reset, bool is_cen, bool is_logic) +static void insert_global(Context *ctx, NetInfo *net, bool is_reset, bool is_cen, bool is_logic, int fanout) { - log_info("promoting %s%s%s%s\n", net->name.c_str(ctx), is_reset ? " [reset]" : "", is_cen ? " [cen]" : "", - is_logic ? " [logic]" : ""); + log_info("promoting %s%s%s%s (fanout %d)\n", + net->name.c_str(ctx), + is_reset ? " [reset]" : "", + is_cen ? " [cen]" : "", + is_logic ? " [logic]" : "", + fanout); std::string glb_name = net->name.str(ctx) + std::string("_$glb_") + (is_reset ? "sr" : (is_cen ? "ce" : "clk")); std::unique_ptr gb = create_ice_cell(ctx, ctx->id("SB_GB"), "$gbuf_" + glb_name); @@ -640,7 +644,7 @@ static void promote_globals(Context *ctx) (global_logic->second > global_reset->second || prom_resets >= resets_available) && bool_or_default(ctx->settings, ctx->id("promote_logic"), false)) { NetInfo *logicnet = ctx->nets[global_logic->first].get(); - insert_global(ctx, logicnet, false, false, true); + insert_global(ctx, logicnet, false, false, true, global_logic->second); ++prom_globals; ++prom_logics; clock_count.erase(logicnet->name); @@ -649,7 +653,7 @@ static void promote_globals(Context *ctx) logic_count.erase(logicnet->name); } else if (global_reset->second > global_clock->second && prom_resets < resets_available) { NetInfo *rstnet = ctx->nets[global_reset->first].get(); - insert_global(ctx, rstnet, true, false, false); + insert_global(ctx, rstnet, true, false, false, global_reset->second); ++prom_globals; ++prom_resets; clock_count.erase(rstnet->name); @@ -659,7 +663,7 @@ static void promote_globals(Context *ctx) } else if (global_cen->second > global_clock->second && prom_cens < cens_available && global_cen->second > enable_fanout_thresh) { NetInfo *cennet = ctx->nets[global_cen->first].get(); - insert_global(ctx, cennet, false, true, false); + insert_global(ctx, cennet, false, true, false, global_cen->second); ++prom_globals; ++prom_cens; clock_count.erase(cennet->name); @@ -668,7 +672,7 @@ static void promote_globals(Context *ctx) logic_count.erase(cennet->name); } else if (global_clock->second != 0) { NetInfo *clknet = ctx->nets[global_clock->first].get(); - insert_global(ctx, clknet, false, false, false); + insert_global(ctx, clknet, false, false, false, global_clock->second); ++prom_globals; clock_count.erase(clknet->name); reset_count.erase(clknet->name); From db96b88d790e3ca541ea33898681ede2b2c01e7e Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 29 Nov 2018 00:12:35 +0000 Subject: [PATCH 07/46] ice40: raise CE global promotion threshold. --- ice40/pack.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ice40/pack.cc b/ice40/pack.cc index 170b25e7..2e151d3f 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -565,7 +565,7 @@ static void promote_globals(Context *ctx) { log_info("Promoting globals..\n"); const int logic_fanout_thresh = 15; - const int enable_fanout_thresh = 5; + const int enable_fanout_thresh = 15; std::map clock_count, reset_count, cen_count, logic_count; for (auto net : sorted(ctx->nets)) { NetInfo *ni = net.second; From dbc14ea76d1af5b1d00a665dc0877969c1e76dc0 Mon Sep 17 00:00:00 2001 From: David Shah Date: Thu, 29 Nov 2018 19:20:51 +0000 Subject: [PATCH 08/46] json: Improve reporting of multiple drivers Signed-off-by: David Shah --- json/jsonparse.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/json/jsonparse.cc b/json/jsonparse.cc index bddbee0b..b953fc7c 100644 --- a/json/jsonparse.cc +++ b/json/jsonparse.cc @@ -594,7 +594,11 @@ void json_import_cell(Context *ctx, string modname, const std::vector if (type == PORT_IN || type == PORT_INOUT) { net->users.push_back(pr); } else if (type == PORT_OUT) { - assert(net->driver.cell == nullptr); + if (net->driver.cell != nullptr) + log_error("multiple drivers on net '%s' (%s.%s and %s.%s)\n", + net->name.c_str(ctx), net->driver.cell->name.c_str(ctx), + net->driver.port.c_str(ctx), pr.cell->name.c_str(ctx), + pr.port.c_str(ctx)); net->driver = pr; } } From 4e05d093977043f5cb959406cd0a8583cf2a9bbe Mon Sep 17 00:00:00 2001 From: David Shah Date: Thu, 29 Nov 2018 19:26:23 +0000 Subject: [PATCH 09/46] Improve reporting of unknown cell types Signed-off-by: David Shah --- ecp5/arch.cc | 3 ++- ice40/arch.cc | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ecp5/arch.cc b/ecp5/arch.cc index 22b350c7..719426ab 100644 --- a/ecp5/arch.cc +++ b/ecp5/arch.cc @@ -670,7 +670,8 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, in } return TMG_IGNORE; } else { - NPNR_ASSERT_FALSE_STR("no timing data for cell type '" + cell->type.str(this) + "'"); + log_error("cell type '%s' is unsupported (instantiated as '%s')\n", cell->type.c_str(this), + cell->name.c_str(this)); } } diff --git a/ice40/arch.cc b/ice40/arch.cc index 02e5515b..ddf25270 100644 --- a/ice40/arch.cc +++ b/ice40/arch.cc @@ -951,7 +951,7 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, in return TMG_IGNORE; return TMG_ENDPOINT; } - log_error("no timing info for port '%s' of cell type '%s'\n", port.c_str(this), cell->type.c_str(this)); + log_error("cell type '%s' is unsupported (instantiated as '%s')\n", cell->type.c_str(this), cell->name.c_str(this)); } TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port, int index) const From 90138fc1201067844ca68f486399619eeabf0589 Mon Sep 17 00:00:00 2001 From: David Shah Date: Thu, 29 Nov 2018 19:28:15 +0000 Subject: [PATCH 10/46] rulecheck: Improve message printed at start Signed-off-by: David Shah --- common/rulecheck.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/rulecheck.cc b/common/rulecheck.cc index c696e548..1db9ae00 100644 --- a/common/rulecheck.cc +++ b/common/rulecheck.cc @@ -9,7 +9,7 @@ bool check_all_nets_driven(Context *ctx) { const bool debug = false; - log_info("Rule checker, Verifying pre-placed design\n"); + log_info("Rule checker, verifying imported design\n"); for (auto &cell_entry : ctx->cells) { CellInfo *cell = cell_entry.second.get(); From 8af367ad0ad7a2ea0bc11f4f20326dacfeb26d65 Mon Sep 17 00:00:00 2001 From: David Shah Date: Thu, 29 Nov 2018 19:35:19 +0000 Subject: [PATCH 11/46] ice40: Add a warning for unconstrained IO Signed-off-by: David Shah --- ice40/pack.cc | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/ice40/pack.cc b/ice40/pack.cc index 88112d59..523a2642 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -478,6 +478,9 @@ static void pack_io(Context *ctx) } packed_cells.insert(ci->name); std::copy(ci->attrs.begin(), ci->attrs.end(), std::inserter(sb->attrs, sb->attrs.begin())); + if (!sb->attrs.count(ctx->id("BEL"))) + log_warning("IO '%s' is not constrained to a pin and will be automatically placed\n", + ci->name.c_str(ctx)); } else if (is_sb_io(ctx, ci) || is_sb_gb_io(ctx, ci)) { NetInfo *net = ci->ports.at(ctx->id("PACKAGE_PIN")).net; if ((net != nullptr) && (net->users.size() > 1)) @@ -520,12 +523,8 @@ static bool is_logic_port(BaseCtx *ctx, const PortRef &port) static void insert_global(Context *ctx, NetInfo *net, bool is_reset, bool is_cen, bool is_logic, int fanout) { - log_info("promoting %s%s%s%s (fanout %d)\n", - net->name.c_str(ctx), - is_reset ? " [reset]" : "", - is_cen ? " [cen]" : "", - is_logic ? " [logic]" : "", - fanout); + log_info("promoting %s%s%s%s (fanout %d)\n", net->name.c_str(ctx), is_reset ? " [reset]" : "", + is_cen ? " [cen]" : "", is_logic ? " [logic]" : "", fanout); std::string glb_name = net->name.str(ctx) + std::string("_$glb_") + (is_reset ? "sr" : (is_cen ? "ce" : "clk")); std::unique_ptr gb = create_ice_cell(ctx, ctx->id("SB_GB"), "$gbuf_" + glb_name); From 5ddf99cf5d3e6d012bd0938163dc256ef9e34770 Mon Sep 17 00:00:00 2001 From: David Shah Date: Fri, 30 Nov 2018 16:09:56 +0000 Subject: [PATCH 12/46] ecp5: Pre-place PLLs and use dedicated routes into globals Signed-off-by: David Shah --- ecp5/globals.cc | 6 ++++-- ecp5/pack.cc | 55 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/ecp5/globals.cc b/ecp5/globals.cc index 66c62024..ddaae5e5 100644 --- a/ecp5/globals.cc +++ b/ecp5/globals.cc @@ -298,6 +298,8 @@ class Ecp5GlobalRouter } else { // Check for dedicated routing if (has_short_route(ctx->getBelPinWire(drv_bel, drv.port), ctx->getBelPinWire(dcc->bel, id_CLKI))) { + // log_info("dedicated route %s -> %s\n", ctx->getWireName(ctx->getBelPinWire(drv_bel, + // drv.port)).c_str(ctx), ctx->getBelName(dcc->bel).c_str(ctx)); return 0; } // Driver is locked @@ -308,7 +310,7 @@ class Ecp5GlobalRouter } // Return true if a short (<5) route exists between two wires - bool has_short_route(WireId src, WireId dst, int thresh = 5) + bool has_short_route(WireId src, WireId dst, int thresh = 7) { std::queue visit; std::unordered_map backtrace; @@ -316,7 +318,7 @@ class Ecp5GlobalRouter WireId cursor; while (true) { - if (visit.empty() || visit.size() > 1000) { + if (visit.empty() || visit.size() > 10000) { // log_info ("dist %s -> %s = inf\n", ctx->getWireName(src).c_str(ctx), // ctx->getWireName(dst).c_str(ctx)); return false; diff --git a/ecp5/pack.cc b/ecp5/pack.cc index 78bf7a87..ca329530 100644 --- a/ecp5/pack.cc +++ b/ecp5/pack.cc @@ -1323,6 +1323,60 @@ class Ecp5Packer } } + // Preplace PLL + void preplace_plls() + { + std::set available_plls; + for (auto bel : ctx->getBels()) { + if (ctx->getBelType(bel) == id_EHXPLLL && ctx->checkBelAvail(bel)) + available_plls.insert(bel); + } + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (ci->type == id_EHXPLLL && ci->attrs.count(ctx->id("BEL"))) + available_plls.erase(ctx->getBelByName(ctx->id(ci->attrs.at(ctx->id("BEL"))))); + } + // Place PLL connected to fixed drivers such as IO close to their source + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (ci->type == id_EHXPLLL && !ci->attrs.count(ctx->id("BEL"))) { + const NetInfo *drivernet = net_or_nullptr(ci, id_CLKI); + if (drivernet == nullptr || drivernet->driver.cell == nullptr) + continue; + const CellInfo *drivercell = drivernet->driver.cell; + if (!drivercell->attrs.count(ctx->id("BEL"))) + continue; + BelId drvbel = ctx->getBelByName(ctx->id(drivercell->attrs.at(ctx->id("BEL")))); + Loc drvloc = ctx->getBelLocation(drvbel); + BelId closest_pll; + int closest_distance = std::numeric_limits::max(); + for (auto bel : available_plls) { + Loc pllloc = ctx->getBelLocation(bel); + int distance = std::abs(drvloc.x - pllloc.x) + std::abs(drvloc.y - pllloc.y); + if (distance < closest_distance) { + closest_pll = bel; + closest_distance = distance; + } + } + if (closest_pll == BelId()) + log_error("failed to place PLL '%s'\n", ci->name.c_str(ctx)); + available_plls.erase(closest_pll); + ci->attrs[ctx->id("BEL")] = ctx->getBelName(closest_pll).str(ctx); + } + } + // Place PLLs driven by logic, etc, randomly + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (ci->type == id_EHXPLLL && !ci->attrs.count(ctx->id("BEL"))) { + if (available_plls.empty()) + log_error("failed to place PLL '%s'\n", ci->name.c_str(ctx)); + BelId next_pll = *(available_plls.begin()); + available_plls.erase(next_pll); + ci->attrs[ctx->id("BEL")] = ctx->getBelName(next_pll).str(ctx); + } + } + } + public: void pack() { @@ -1330,6 +1384,7 @@ class Ecp5Packer pack_ebr(); pack_dsps(); pack_dcus(); + preplace_plls(); pack_constants(); pack_dram(); pack_carries(); From d4b3c1d819703667b604ad144a36415c2f3bcdf5 Mon Sep 17 00:00:00 2001 From: Daniel Serpell Date: Sat, 1 Dec 2018 22:27:04 -0300 Subject: [PATCH 13/46] ice40: Add support for placing SB_LEDDA_IP block. Signed-off-by: Daniel Serpell --- ice40/arch.cc | 4 ++++ ice40/bitstream.cc | 3 ++- ice40/cells.cc | 14 ++++++++++++++ ice40/cells.h | 2 ++ ice40/pack.cc | 4 ++++ 5 files changed, 26 insertions(+), 1 deletion(-) diff --git a/ice40/arch.cc b/ice40/arch.cc index ddf25270..ada78020 100644 --- a/ice40/arch.cc +++ b/ice40/arch.cc @@ -950,6 +950,10 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, in if (port == id_RGB0 || port == id_RGB1 || port == id_RGB2) return TMG_IGNORE; return TMG_ENDPOINT; + } else if (cell->type == id_SB_LEDDA_IP) { + if (port == id_CLK || port == id_CLOCK) + return TMG_CLOCK_INPUT; + return TMG_IGNORE; } log_error("cell type '%s' is unsupported (instantiated as '%s')\n", cell->type.c_str(this), cell->name.c_str(this)); } diff --git a/ice40/bitstream.cc b/ice40/bitstream.cc index e20d372a..87d77b9d 100644 --- a/ice40/bitstream.cc +++ b/ice40/bitstream.cc @@ -591,7 +591,8 @@ void write_asc(const Context *ctx, std::ostream &out) {"CURRENT_MODE", 1}, {"RGB0_CURRENT", 6}, {"RGB1_CURRENT", 6}, {"RGB2_CURRENT", 6}}; configure_extra_cell(config, ctx, cell.second.get(), rgba_params, true, std::string("IpConfig.")); set_ec_cbit(config, ctx, get_ec_config(ctx->chip_info, cell.second->bel), "RGBA_DRV_EN", true, "IpConfig."); - } else if (cell.second->type == ctx->id("SB_WARMBOOT") || cell.second->type == ctx->id("ICESTORM_LFOSC")) { + } else if (cell.second->type == ctx->id("SB_WARMBOOT") || cell.second->type == ctx->id("ICESTORM_LFOSC") || + cell.second->type == ctx->id("SB_LEDDA_IP") ) { // No config needed } else if (cell.second->type == ctx->id("ICESTORM_SPRAM")) { const BelInfoPOD &beli = ci.bel_data[bel.index]; diff --git a/ice40/cells.cc b/ice40/cells.cc index dbb75c2c..aad719b1 100644 --- a/ice40/cells.cc +++ b/ice40/cells.cc @@ -260,6 +260,20 @@ std::unique_ptr create_ice_cell(Context *ctx, IdString type, std::stri add_port(ctx, new_cell.get(), "RGB0", PORT_OUT); add_port(ctx, new_cell.get(), "RGB1", PORT_OUT); add_port(ctx, new_cell.get(), "RGB2", PORT_OUT); + } else if (type == ctx->id("SB_LEDDA_IP")) { + add_port(ctx, new_cell.get(), "LEDDCS", PORT_IN); + add_port(ctx, new_cell.get(), "LEDDCLK", PORT_IN); + for (int i = 0; i < 8; i++) + add_port(ctx, new_cell.get(), "LEDDDAT" + std::to_string(i), PORT_IN); + for (int i = 0; i < 3; i++) + add_port(ctx, new_cell.get(), "LEDDADDR" + std::to_string(i), PORT_IN); + add_port(ctx, new_cell.get(), "LEDDDEN", PORT_IN); + add_port(ctx, new_cell.get(), "LEDDEXE", PORT_IN); + add_port(ctx, new_cell.get(), "LEDDRST", PORT_IN); //doesn't actually exist, for icecube code compatibility only + add_port(ctx, new_cell.get(), "PWMOUT0", PORT_OUT); + add_port(ctx, new_cell.get(), "PWMOUT1", PORT_OUT); + add_port(ctx, new_cell.get(), "PWMOUT2", PORT_OUT); + add_port(ctx, new_cell.get(), "LEDDON", PORT_OUT); } else { log_error("unable to create iCE40 cell of type %s", type.c_str(ctx)); } diff --git a/ice40/cells.h b/ice40/cells.h index 1fbd9073..93ef3db4 100644 --- a/ice40/cells.h +++ b/ice40/cells.h @@ -76,6 +76,8 @@ inline bool is_sb_mac16(const BaseCtx *ctx, const CellInfo *cell) { return cell- inline bool is_sb_rgba_drv(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_RGBA_DRV"); } +inline bool is_sb_ledda_ip(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_LEDDA_IP"); } + inline bool is_sb_pll40(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_PLL40_PAD") || cell->type == ctx->id("SB_PLL40_2_PAD") || diff --git a/ice40/pack.cc b/ice40/pack.cc index 523a2642..17d2df8b 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -1044,6 +1044,10 @@ static void pack_special(Context *ctx) ci->ports.erase(ctx->id("RGB0")); ci->ports.erase(ctx->id("RGB1")); ci->ports.erase(ctx->id("RGB2")); + } else if (is_sb_ledda_ip(ctx, ci)) { + /* Force placement (no choices anyway) */ + cell_place_unique(ctx, ci); + } else if (is_sb_pll40(ctx, ci)) { bool is_pad = is_sb_pll40_pad(ctx, ci); bool is_core = !is_pad; From 7fad6058bda171668719ffd6a6f917ec4699d275 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 4 Dec 2018 07:40:55 +0000 Subject: [PATCH 14/46] ice40: add reset global promotion threshold. --- ice40/pack.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ice40/pack.cc b/ice40/pack.cc index 17d2df8b..e71db46f 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -569,6 +569,7 @@ static void promote_globals(Context *ctx) log_info("Promoting globals..\n"); const int logic_fanout_thresh = 15; const int enable_fanout_thresh = 15; + const int reset_fanout_thresh = 15; std::map clock_count, reset_count, cen_count, logic_count; for (auto net : sorted(ctx->nets)) { NetInfo *ni = net.second; @@ -650,7 +651,8 @@ static void promote_globals(Context *ctx) reset_count.erase(logicnet->name); cen_count.erase(logicnet->name); logic_count.erase(logicnet->name); - } else if (global_reset->second > global_clock->second && prom_resets < resets_available) { + } else if (global_reset->second > global_clock->second && prom_resets < resets_available && + global_reset->second > reset_fanout_thresh) { NetInfo *rstnet = ctx->nets[global_reset->first].get(); insert_global(ctx, rstnet, true, false, false, global_reset->second); ++prom_globals; From 0c93b55650a8a8919f2697cd6d6dbd373bf5ff19 Mon Sep 17 00:00:00 2001 From: David Shah Date: Tue, 4 Dec 2018 12:02:26 +0000 Subject: [PATCH 15/46] ice40: Include I3 connectivity in chain Thanks @smunaut Signed-off-by: David Shah --- ice40/chains.cc | 46 ++++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/ice40/chains.cc b/ice40/chains.cc index fb361d2d..07eb100b 100644 --- a/ice40/chains.cc +++ b/ice40/chains.cc @@ -163,29 +163,31 @@ class ChainConstrainer void process_carries() { - std::vector carry_chains = - find_chains(ctx, [](const Context *ctx, const CellInfo *cell) { return is_lc(ctx, cell); }, - [](const Context *ctx, const + std::vector carry_chains = find_chains( + ctx, [](const Context *ctx, const CellInfo *cell) { return is_lc(ctx, cell); }, + [](const Context *ctx, const - CellInfo *cell) { - CellInfo *carry_prev = - net_driven_by(ctx, cell->ports.at(ctx->id("CIN")).net, is_lc, ctx->id("COUT")); - if (carry_prev != nullptr) - return carry_prev; - /*CellInfo *i3_prev = net_driven_by(ctx, cell->ports.at(ctx->id("I3")).net, is_lc, - ctx->id("COUT")); if (i3_prev != nullptr) return i3_prev;*/ - return (CellInfo *)nullptr; - }, - [](const Context *ctx, const CellInfo *cell) { - CellInfo *carry_next = net_only_drives(ctx, cell->ports.at(ctx->id("COUT")).net, is_lc, - ctx->id("CIN"), false); - if (carry_next != nullptr) - return carry_next; - /*CellInfo *i3_next = - net_only_drives(ctx, cell->ports.at(ctx->id("COUT")).net, is_lc, ctx->id("I3"), - false); if (i3_next != nullptr) return i3_next;*/ - return (CellInfo *)nullptr; - }); + CellInfo *cell) { + CellInfo *carry_prev = + net_driven_by(ctx, cell->ports.at(ctx->id("CIN")).net, is_lc, ctx->id("COUT")); + if (carry_prev != nullptr) + return carry_prev; + CellInfo *i3_prev = net_driven_by(ctx, cell->ports.at(ctx->id("I3")).net, is_lc, ctx->id("COUT")); + if (i3_prev != nullptr) + return i3_prev; + return (CellInfo *)nullptr; + }, + [](const Context *ctx, const CellInfo *cell) { + CellInfo *carry_next = + net_only_drives(ctx, cell->ports.at(ctx->id("COUT")).net, is_lc, ctx->id("CIN"), false); + if (carry_next != nullptr) + return carry_next; + CellInfo *i3_next = + net_only_drives(ctx, cell->ports.at(ctx->id("COUT")).net, is_lc, ctx->id("I3"), false); + if (i3_next != nullptr) + return i3_next; + return (CellInfo *)nullptr; + }); std::unordered_set chained; for (auto &base_chain : carry_chains) { for (auto c : base_chain.cells) From 51cda136b185730afa9e058e5b8e998cdb8c1d08 Mon Sep 17 00:00:00 2001 From: David Shah Date: Tue, 4 Dec 2018 12:31:32 +0000 Subject: [PATCH 16/46] ice40: Don't split carry chain in simple feed-out cases Signed-off-by: David Shah --- ice40/chains.cc | 57 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 7 deletions(-) diff --git a/ice40/chains.cc b/ice40/chains.cc index 07eb100b..b8fbee0f 100644 --- a/ice40/chains.cc +++ b/ice40/chains.cc @@ -74,10 +74,10 @@ class ChainConstrainer (net_only_drives(ctx, carry_net, is_lc, ctx->id("I3"), false) != net_only_drives(ctx, carry_net, is_lc, ctx->id("CIN"), false)) || (at_end && !net_only_drives(ctx, carry_net, is_lc, ctx->id("I3"), true))) { - CellInfo *passout = make_carry_pass_out(cell->ports.at(ctx->id("COUT"))); + CellInfo *passout = make_carry_pass_out(cell->ports.at(ctx->id("COUT")), + at_end ? nullptr : *(curr_cell + 1)); chains.back().cells.push_back(passout); tile.push_back(passout); - start_of_chain = true; } } ++curr_cell; @@ -87,30 +87,73 @@ class ChainConstrainer } // Insert a logic cell to legalise a COUT->fabric connection - CellInfo *make_carry_pass_out(PortInfo &cout_port) + CellInfo *make_carry_pass_out(PortInfo &cout_port, CellInfo *cin_cell = nullptr) { NPNR_ASSERT(cout_port.net != nullptr); std::unique_ptr lc = create_ice_cell(ctx, ctx->id("ICESTORM_LC")); lc->params[ctx->id("LUT_INIT")] = "65280"; // 0xff00: O = I3 lc->params[ctx->id("CARRY_ENABLE")] = "1"; - lc->ports.at(ctx->id("O")).net = cout_port.net; + lc->ports.at(id_O).net = cout_port.net; std::unique_ptr co_i3_net(new NetInfo()); co_i3_net->name = ctx->id(lc->name.str(ctx) + "$I3"); co_i3_net->driver = cout_port.net->driver; PortRef i3_r; - i3_r.port = ctx->id("I3"); + i3_r.port = id_I3; i3_r.cell = lc.get(); co_i3_net->users.push_back(i3_r); PortRef o_r; - o_r.port = ctx->id("O"); + o_r.port = id_O; o_r.cell = lc.get(); cout_port.net->driver = o_r; - lc->ports.at(ctx->id("I3")).net = co_i3_net.get(); + lc->ports.at(id_I3).net = co_i3_net.get(); cout_port.net = co_i3_net.get(); IdString co_i3_name = co_i3_net->name; NPNR_ASSERT(ctx->nets.find(co_i3_name) == ctx->nets.end()); ctx->nets[co_i3_name] = std::move(co_i3_net); + + // If COUT also connects to a CIN; preserve the carry chain + if (cin_cell) { + std::unique_ptr co_cin_net(new NetInfo()); + co_cin_net->name = ctx->id(lc->name.str(ctx) + "$COUT"); + + // Connect I1 to 1 to preserve carry chain + NetInfo *vcc = ctx->nets.at(ctx->id("$PACKER_VCC_NET")).get(); + lc->ports.at(id_I1).net = vcc; + PortRef i1_r; + i1_r.port = id_I1; + i1_r.cell = lc.get(); + vcc->users.push_back(i1_r); + + // Connect co_cin_net to the COUT of the LC + PortRef co_r; + co_r.port = id_COUT; + co_r.cell = lc.get(); + co_cin_net->driver = co_r; + lc->ports.at(id_COUT).net = co_cin_net.get(); + + // Find the user corresponding to the next CIN + int replaced_ports = 0; + log_info("cell: %s\n", cin_cell->name.c_str(ctx)); + for (auto port : {id_CIN, id_I3}) { + auto &usr = lc->ports.at(id_O).net->users; + for (auto user : usr) + log_info("%s.%s\n", user.cell->name.c_str(ctx), user.port.c_str(ctx)); + auto fnd_user = std::find_if(usr.begin(), usr.end(), + [&](const PortRef &pr) { return pr.cell == cin_cell && pr.port == port; }); + if (fnd_user != usr.end()) { + co_cin_net->users.push_back(*fnd_user); + usr.erase(fnd_user); + cin_cell->ports.at(port).net = co_cin_net.get(); + ++replaced_ports; + } + } + NPNR_ASSERT(replaced_ports > 0); + IdString co_cin_name = co_cin_net->name; + NPNR_ASSERT(ctx->nets.find(co_cin_name) == ctx->nets.end()); + ctx->nets[co_cin_name] = std::move(co_cin_net); + } + IdString name = lc->name; ctx->assignCellInfo(lc.get()); ctx->cells[lc->name] = std::move(lc); From 99e1b6db4785d4a34f2a350551681ea45362840a Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Tue, 4 Dec 2018 20:20:50 +0100 Subject: [PATCH 17/46] build: Make use of the pipe option to avoid temporary files This is really useful when building the ice40 with the gigantic .cc files that generate multi gigabyte .s temporary files ... this way the assembler just processed it in streaming way. Signed-off-by: Sylvain Munaut --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 24ec76b2..33a703d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,8 +48,8 @@ set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "" FORCE) set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /D_DEBUG /W4 /wd4100 /wd4244 /wd4125 /wd4800 /wd4456 /wd4458 /wd4305 /wd4459 /wd4121 /wd4996") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /W4 /wd4100 /wd4244 /wd4125 /wd4800 /wd4456 /wd4458 /wd4305 /wd4459 /wd4121 /wd4996 /wd4127") else() -set(CMAKE_CXX_FLAGS_DEBUG "-Wall -fPIC -ggdb") -set(CMAKE_CXX_FLAGS_RELEASE "-Wall -fPIC -O3 -g") +set(CMAKE_CXX_FLAGS_DEBUG "-Wall -fPIC -ggdb -pipe") +set(CMAKE_CXX_FLAGS_RELEASE "-Wall -fPIC -O3 -g -pipe") endif() set(CMAKE_DEFIN) From 92ddef9fc3bace1008138ec0d3b99bffd6c58416 Mon Sep 17 00:00:00 2001 From: Adrian Jeakins Date: Tue, 4 Dec 2018 23:05:06 +0000 Subject: [PATCH 18/46] Fix crash starting the GUI on macOS where we must request a core profile. See http://doc.qt.io/qt-5/qabstractopenglfunctions.html --- gui/application.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/gui/application.cc b/gui/application.cc index aece5d2a..7751e6f1 100644 --- a/gui/application.cc +++ b/gui/application.cc @@ -41,6 +41,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) { QSurfaceFormat fmt; fmt.setSamples(10); + fmt.setProfile(QSurfaceFormat::CoreProfile); QSurfaceFormat::setDefaultFormat(fmt); #ifdef _WIN32 SetConsoleCtrlHandler((PHANDLER_ROUTINE)WinHandler, TRUE); From d298687dc2472e3afd2b19979719658b5eba3132 Mon Sep 17 00:00:00 2001 From: David Shah Date: Wed, 5 Dec 2018 10:12:23 +0000 Subject: [PATCH 19/46] ice40: Fix carry chain splitting Signed-off-by: David Shah --- ice40/chains.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ice40/chains.cc b/ice40/chains.cc index b8fbee0f..3ef367f1 100644 --- a/ice40/chains.cc +++ b/ice40/chains.cc @@ -62,7 +62,7 @@ class ChainConstrainer bool split_chain = (!ctx->logicCellsCompatible(tile.data(), tile.size())) || (int(chains.back().cells.size()) > max_length); if (split_chain) { - CellInfo *passout = make_carry_pass_out(cell->ports.at(ctx->id("COUT"))); + CellInfo *passout = make_carry_pass_out((*(curr_cell - 1))->ports.at(ctx->id("COUT"))); tile.pop_back(); chains.back().cells.back() = passout; start_of_chain = true; From a6315833d3ca7eac414b8496d8ff12a7f30a4145 Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Wed, 5 Dec 2018 19:58:38 +0100 Subject: [PATCH 20/46] Renamed LogLevel members, to prevent issue with system defines on Windows --- common/command.cc | 10 +++++----- common/log.cc | 14 +++++++------- common/log.h | 10 +++++----- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/common/command.cc b/common/command.cc index 206a4d30..d332375a 100644 --- a/common/command.cc +++ b/common/command.cc @@ -131,9 +131,9 @@ void CommandHandler::setupContext(Context *ctx) } if (vm.count("quiet")) { - log_streams.push_back(std::make_pair(&std::cerr, LogLevel::WARNING)); + log_streams.push_back(std::make_pair(&std::cerr, LogLevel::WARNING_MSG)); } else { - log_streams.push_back(std::make_pair(&std::cerr, LogLevel::LOG)); + log_streams.push_back(std::make_pair(&std::cerr, LogLevel::LOG_MSG)); } if (vm.count("log")) { @@ -141,7 +141,7 @@ void CommandHandler::setupContext(Context *ctx) logfile = std::ofstream(logfilename); if (!logfile) log_error("Failed to open log file '%s' for writing.\n", logfilename.c_str()); - log_streams.push_back(std::make_pair(&logfile, LogLevel::LOG)); + log_streams.push_back(std::make_pair(&logfile, LogLevel::LOG_MSG)); } if (vm.count("force")) { @@ -285,8 +285,8 @@ void CommandHandler::conflicting_options(const boost::program_options::variables void CommandHandler::printFooter() { - int warning_count = get_or_default(message_count_by_level, LogLevel::WARNING, 0), - error_count = get_or_default(message_count_by_level, LogLevel::ERROR, 0); + int warning_count = get_or_default(message_count_by_level, LogLevel::WARNING_MSG, 0), + error_count = get_or_default(message_count_by_level, LogLevel::ERROR_MSG, 0); if (warning_count > 0 || error_count > 0) log_always("%d warning%s, %d error%s\n", warning_count, warning_count == 1 ? "" : "s", error_count, error_count == 1 ? "" : "s"); diff --git a/common/log.cc b/common/log.cc index 0a75b020..01aec79a 100644 --- a/common/log.cc +++ b/common/log.cc @@ -84,7 +84,7 @@ std::string vstringf(const char *fmt, va_list ap) return string; } -void logv(const char *format, va_list ap, LogLevel level = LogLevel::LOG) +void logv(const char *format, va_list ap, LogLevel level = LogLevel::LOG_MSG) { // // Trim newlines from the beginning @@ -132,7 +132,7 @@ void log_always(const char *format, ...) { va_list ap; va_start(ap, format); - logv(format, ap, LogLevel::ALWAYS); + logv(format, ap, LogLevel::ALWAYS_MSG); va_end(ap); } @@ -140,7 +140,7 @@ void log(const char *format, ...) { va_list ap; va_start(ap, format); - logv(format, ap, LogLevel::LOG); + logv(format, ap, LogLevel::LOG_MSG); va_end(ap); } @@ -148,7 +148,7 @@ void log_info(const char *format, ...) { va_list ap; va_start(ap, format); - logv_prefixed("Info: ", format, ap, LogLevel::INFO); + logv_prefixed("Info: ", format, ap, LogLevel::INFO_MSG); va_end(ap); } @@ -156,7 +156,7 @@ void log_warning(const char *format, ...) { va_list ap; va_start(ap, format); - logv_prefixed("Warning: ", format, ap, LogLevel::WARNING); + logv_prefixed("Warning: ", format, ap, LogLevel::WARNING_MSG); va_end(ap); } @@ -164,7 +164,7 @@ void log_error(const char *format, ...) { va_list ap; va_start(ap, format); - logv_prefixed("ERROR: ", format, ap, LogLevel::ERROR); + logv_prefixed("ERROR: ", format, ap, LogLevel::ERROR_MSG); if (log_error_atexit) log_error_atexit(); @@ -184,7 +184,7 @@ void log_nonfatal_error(const char *format, ...) { va_list ap; va_start(ap, format); - logv_prefixed("ERROR: ", format, ap, LogLevel::ERROR); + logv_prefixed("ERROR: ", format, ap, LogLevel::ERROR_MSG); va_end(ap); had_nonfatal_error = true; } diff --git a/common/log.h b/common/log.h index 52158f18..5745df0d 100644 --- a/common/log.h +++ b/common/log.h @@ -44,11 +44,11 @@ struct log_execution_error_exception enum class LogLevel { - LOG, - INFO, - WARNING, - ERROR, - ALWAYS + LOG_MSG, + INFO_MSG, + WARNING_MSG, + ERROR_MSG, + ALWAYS_MSG }; extern std::vector> log_streams; From 331134cff2d3b66fa2f42394ea68b64f2cfe40a1 Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Wed, 5 Dec 2018 23:03:06 +0100 Subject: [PATCH 21/46] ci: implement using CirrusCI --- .cirrus.yml | 8 ++++++++ .cirrus/Dockerfile.ubuntu16.04 | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 .cirrus.yml create mode 100644 .cirrus/Dockerfile.ubuntu16.04 diff --git a/.cirrus.yml b/.cirrus.yml new file mode 100644 index 00000000..78bba57b --- /dev/null +++ b/.cirrus.yml @@ -0,0 +1,8 @@ +task: + name: build-ubuntu1604 + container: + cpu: 4 + memory: 8 + dockerfile: .cirrus/Dockerfile.ubuntu16.04 + + build_script: mkdir build && cd build && cmake .. -DARCH=all -DTRELLIS_ROOT=/usr/local/src/prjtrellis && make -j $(nproc) diff --git a/.cirrus/Dockerfile.ubuntu16.04 b/.cirrus/Dockerfile.ubuntu16.04 new file mode 100644 index 00000000..7335292f --- /dev/null +++ b/.cirrus/Dockerfile.ubuntu16.04 @@ -0,0 +1,33 @@ +FROM ubuntu:xenial-20181113 + +ENV DEBIAN_FRONTEND=noninteractive + +RUN set -e -x ;\ + apt-get -y update ;\ + apt-get -y upgrade ;\ + apt-get -y install \ + build-essential cmake clang python3-dev libboost-all-dev qt5-default git + +RUN set -e -x ;\ + apt-get -y install \ + libftdi-dev pkg-config + +RUN set -e -x ;\ + mkdir -p /usr/local/src ;\ + cd /usr/local/src ;\ + git clone --recursive https://github.com/cliffordwolf/icestorm.git ;\ + cd icestorm ;\ + git reset --hard 9671b760f84ca4006f0ef101a3e3b201df4eabb5 ;\ + make -j $(nproc) ;\ + make install + +RUN set -e -x ;\ + mkdir -p /usr/local/src ;\ + cd /usr/local/src ;\ + git clone --recursive https://github.com/SymbiFlow/prjtrellis.git ;\ + cd prjtrellis ;\ + git reset --hard de035a6e5e5818804a66b9408f0ad381b10957db ;\ + cd libtrellis ;\ + cmake -DCMAKE_INSTALL_PREFIX=/usr . ;\ + make -j $(nproc) ;\ + make install From f4793a671eaffe630225c66dd810d02d9e516844 Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Wed, 5 Dec 2018 23:44:20 +0100 Subject: [PATCH 22/46] ci: downloadmoreram.com --- .cirrus.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.cirrus.yml b/.cirrus.yml index 78bba57b..067c3559 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -2,7 +2,7 @@ task: name: build-ubuntu1604 container: cpu: 4 - memory: 8 + memory: 12 dockerfile: .cirrus/Dockerfile.ubuntu16.04 build_script: mkdir build && cd build && cmake .. -DARCH=all -DTRELLIS_ROOT=/usr/local/src/prjtrellis && make -j $(nproc) From 659b859360ae7975fcd5e43d13968c94504f6f6a Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Thu, 6 Dec 2018 00:18:51 +0100 Subject: [PATCH 23/46] ci: run test binaries --- .cirrus.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.cirrus.yml b/.cirrus.yml index 067c3559..a64b5b76 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -1,8 +1,11 @@ task: - name: build-ubuntu1604 + name: build-test-ubuntu1604 container: cpu: 4 memory: 12 dockerfile: .cirrus/Dockerfile.ubuntu16.04 - build_script: mkdir build && cd build && cmake .. -DARCH=all -DTRELLIS_ROOT=/usr/local/src/prjtrellis && make -j $(nproc) + build_script: mkdir build && cd build && cmake .. -DARCH=all -DTRELLIS_ROOT=/usr/local/src/prjtrellis -DBUILD_TESTS=on && make -j $(nproc) + test_generic_script: ./nextpnr-generic-test + test_ice40_script: ./nextpnr-ice40-test + test_ecp5_script: ./nextpnr-ecp5-test From 70c30c9c9659db84c37fb8a0d2f3cab8630360e5 Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Thu, 6 Dec 2018 00:34:39 +0100 Subject: [PATCH 24/46] ci: more RAM --- .cirrus.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.cirrus.yml b/.cirrus.yml index a64b5b76..10865c7b 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -2,10 +2,10 @@ task: name: build-test-ubuntu1604 container: cpu: 4 - memory: 12 + memory: 16 dockerfile: .cirrus/Dockerfile.ubuntu16.04 build_script: mkdir build && cd build && cmake .. -DARCH=all -DTRELLIS_ROOT=/usr/local/src/prjtrellis -DBUILD_TESTS=on && make -j $(nproc) - test_generic_script: ./nextpnr-generic-test - test_ice40_script: ./nextpnr-ice40-test - test_ecp5_script: ./nextpnr-ecp5-test + test_generic_script: cd build && ./nextpnr-generic-test + test_ice40_script: cd build && ./nextpnr-ice40-test + test_ecp5_script: cd build && ./nextpnr-ecp5-test From fdb632f24ca6fe949b51ca37648ef72732a116c8 Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Thu, 6 Dec 2018 08:31:40 +0100 Subject: [PATCH 25/46] Fix crash exiting nextpnr gui --- gui/worker.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/gui/worker.cc b/gui/worker.cc index b009ecd3..900883d4 100644 --- a/gui/worker.cc +++ b/gui/worker.cc @@ -126,6 +126,7 @@ TaskManager::TaskManager() : toTerminate(false), toPause(false) TaskManager::~TaskManager() { + log_write_function = nullptr; if (workerThread.isRunning()) terminate_thread(); workerThread.quit(); From dbaabae23594dc3ac058abeb9f3de2db756a4636 Mon Sep 17 00:00:00 2001 From: David Shah Date: Thu, 6 Dec 2018 10:51:17 +0000 Subject: [PATCH 26/46] ice40: Put debug logging behind ctx->debug Signed-off-by: David Shah --- ice40/chains.cc | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ice40/chains.cc b/ice40/chains.cc index 3ef367f1..b3b54d6b 100644 --- a/ice40/chains.cc +++ b/ice40/chains.cc @@ -134,11 +134,13 @@ class ChainConstrainer // Find the user corresponding to the next CIN int replaced_ports = 0; - log_info("cell: %s\n", cin_cell->name.c_str(ctx)); + if (ctx->debug) + log_info("cell: %s\n", cin_cell->name.c_str(ctx)); for (auto port : {id_CIN, id_I3}) { auto &usr = lc->ports.at(id_O).net->users; - for (auto user : usr) - log_info("%s.%s\n", user.cell->name.c_str(ctx), user.port.c_str(ctx)); + if (ctx->debug) + for (auto user : usr) + log_info("%s.%s\n", user.cell->name.c_str(ctx), user.port.c_str(ctx)); auto fnd_user = std::find_if(usr.begin(), usr.end(), [&](const PortRef &pr) { return pr.cell == cin_cell && pr.port == port; }); if (fnd_user != usr.end()) { From 88e1e6bdf4d01d31389fce92cdc88e16c9a5ebc1 Mon Sep 17 00:00:00 2001 From: David Shah Date: Thu, 6 Dec 2018 10:52:46 +0000 Subject: [PATCH 27/46] clangformat Signed-off-by: David Shah --- ice40/bitstream.cc | 2 +- ice40/cells.cc | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ice40/bitstream.cc b/ice40/bitstream.cc index 87d77b9d..83664169 100644 --- a/ice40/bitstream.cc +++ b/ice40/bitstream.cc @@ -592,7 +592,7 @@ void write_asc(const Context *ctx, std::ostream &out) configure_extra_cell(config, ctx, cell.second.get(), rgba_params, true, std::string("IpConfig.")); set_ec_cbit(config, ctx, get_ec_config(ctx->chip_info, cell.second->bel), "RGBA_DRV_EN", true, "IpConfig."); } else if (cell.second->type == ctx->id("SB_WARMBOOT") || cell.second->type == ctx->id("ICESTORM_LFOSC") || - cell.second->type == ctx->id("SB_LEDDA_IP") ) { + cell.second->type == ctx->id("SB_LEDDA_IP")) { // No config needed } else if (cell.second->type == ctx->id("ICESTORM_SPRAM")) { const BelInfoPOD &beli = ci.bel_data[bel.index]; diff --git a/ice40/cells.cc b/ice40/cells.cc index aad719b1..35a5346f 100644 --- a/ice40/cells.cc +++ b/ice40/cells.cc @@ -269,7 +269,8 @@ std::unique_ptr create_ice_cell(Context *ctx, IdString type, std::stri add_port(ctx, new_cell.get(), "LEDDADDR" + std::to_string(i), PORT_IN); add_port(ctx, new_cell.get(), "LEDDDEN", PORT_IN); add_port(ctx, new_cell.get(), "LEDDEXE", PORT_IN); - add_port(ctx, new_cell.get(), "LEDDRST", PORT_IN); //doesn't actually exist, for icecube code compatibility only + add_port(ctx, new_cell.get(), "LEDDRST", PORT_IN); // doesn't actually exist, for icecube code compatibility + // only add_port(ctx, new_cell.get(), "PWMOUT0", PORT_OUT); add_port(ctx, new_cell.get(), "PWMOUT1", PORT_OUT); add_port(ctx, new_cell.get(), "PWMOUT2", PORT_OUT); From 9a42b64a6853a3802a6d934a1ca251e84ddb7e07 Mon Sep 17 00:00:00 2001 From: David Shah Date: Sat, 1 Dec 2018 11:54:26 +0000 Subject: [PATCH 28/46] timing: Add criticality calculation to timing analysis Signed-off-by: David Shah --- common/timing.cc | 150 ++++++++++++++++++++++++++++++++++++++++++- common/timing_opt.cc | 42 ++++++++++++ common/timing_opt.h | 30 +++++++++ 3 files changed, 220 insertions(+), 2 deletions(-) create mode 100644 common/timing_opt.cc create mode 100644 common/timing_opt.h diff --git a/common/timing.cc b/common/timing.cc index 88ab14c2..55d3a46f 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -85,7 +85,16 @@ struct CriticalPath delay_t path_period; }; +// Data for the timing optimisation algorithm +struct NetCriticalityInfo +{ + // One each per user + std::vector slack; + std::vector criticality; +}; + typedef std::unordered_map CriticalPathMap; +typedef std::unordered_map NetCriticalityMap; struct Timing { @@ -96,6 +105,7 @@ struct Timing CriticalPathMap *crit_path; DelayFrequency *slack_histogram; IdString async_clock; + NetCriticalityMap *net_crit; struct TimingData { @@ -105,13 +115,15 @@ struct Timing unsigned max_path_length = 0; delay_t min_remaining_budget; bool false_startpoint = false; + std::vector min_required; std::unordered_map arrival_time; }; Timing(Context *ctx, bool net_delays, bool update, CriticalPathMap *crit_path = nullptr, - DelayFrequency *slack_histogram = nullptr) + DelayFrequency *slack_histogram = nullptr, NetCriticalityMap *net_crit = nullptr) : ctx(ctx), net_delays(net_delays), update(update), min_slack(1.0e12 / ctx->target_freq), - crit_path(crit_path), slack_histogram(slack_histogram), async_clock(ctx->id("$async$")) + crit_path(crit_path), slack_histogram(slack_histogram), net_crit(net_crit), + async_clock(ctx->id("$async$")) { } @@ -454,6 +466,140 @@ struct Timing std::reverse(cp_ports.begin(), cp_ports.end()); } } + + if (net_crit) { + NPNR_ASSERT(crit_path); + // Go through in reverse topographical order to set required times + for (auto net : boost::adaptors::reverse(topographical_order)) { + if (!net_data.count(net)) + continue; + auto &nd_map = net_data.at(net); + for (auto &startdomain : nd_map) { + auto &nd = startdomain.second; + if (nd.false_startpoint) + continue; + const delay_t net_length_plus_one = nd.max_path_length + 1; + auto &net_min_remaining_budget = nd.min_remaining_budget; + if (nd.min_required.empty()) + nd.min_required.resize(net->users.size(), std::numeric_limits::max()); + delay_t net_min_required = std::numeric_limits::max(); + for (size_t i = 0; i < net->users.size(); i++) { + auto &usr = net->users.at(i); + auto net_delay = ctx->getNetinfoRouteDelay(net, usr); + int port_clocks; + TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, port_clocks); + if (portClass == TMG_REGISTER_INPUT || portClass == TMG_ENDPOINT) { + auto process_endpoint = [&](IdString clksig, ClockEdge edge, delay_t setup) { + delay_t period; + // Set default period + if (edge == startdomain.first.edge) { + period = clk_period; + } else { + period = clk_period / 2; + } + if (clksig != async_clock) { + if (ctx->nets.at(clksig)->clkconstr) { + if (edge == startdomain.first.edge) { + // same edge + period = ctx->nets.at(clksig)->clkconstr->period.minDelay(); + } else if (edge == RISING_EDGE) { + // falling -> rising + period = ctx->nets.at(clksig)->clkconstr->low.minDelay(); + } else if (edge == FALLING_EDGE) { + // rising -> falling + period = ctx->nets.at(clksig)->clkconstr->high.minDelay(); + } + } + } + nd.min_required.at(i) = std::min(period - setup, nd.min_required.at(i)); + }; + if (portClass == TMG_REGISTER_INPUT) { + for (int j = 0; j < port_clocks; j++) { + TimingClockingInfo clkInfo = ctx->getPortClockingInfo(usr.cell, usr.port, j); + const NetInfo *clknet = get_net_or_empty(usr.cell, clkInfo.clock_port); + IdString clksig = clknet ? clknet->name : async_clock; + process_endpoint(clksig, clknet ? clkInfo.edge : RISING_EDGE, + clkInfo.setup.maxDelay()); + } + } else { + process_endpoint(async_clock, RISING_EDGE, 0); + } + } + net_min_required = std::min(net_min_required, nd.min_required.at(i) - net_delay); + } + PortRef &drv = net->driver; + if (drv.cell == nullptr) + continue; + for (const auto &port : drv.cell->ports) { + if (port.second.type != PORT_IN || !port.second.net) + continue; + DelayInfo comb_delay; + bool is_path = ctx->getCellDelay(drv.cell, port.first, drv.port, comb_delay); + if (!is_path) + continue; + NetInfo *sink_net = port.second.net; + if (net_data.count(sink_net) && net_data.at(sink_net).count(startdomain.first)) { + auto &sink_nd = net_data.at(sink_net).at(startdomain.first); + if (sink_nd.min_required.empty()) + sink_nd.min_required.resize(sink_net->users.size(), + std::numeric_limits::max()); + for (size_t i = 0; i < sink_net->users.size(); i++) { + auto &user = sink_net->users.at(i); + if (user.cell == drv.cell && user.port == port.first) { + sink_nd.min_required.at(i) = net_min_required - comb_delay.maxDelay(); + break; + } + } + } + } + } + } + std::unordered_map worst_slack; + + // Assign slack values + for (auto &net_entry : net_data) { + const NetInfo *net = net_entry.first; + for (auto &startdomain : net_entry.second) { + auto &nd = startdomain.second; + if (nd.min_required.empty()) + continue; + auto &nc = (*net_crit)[net->name]; + if (nc.slack.empty()) + nc.slack.resize(net->users.size(), std::numeric_limits::max()); + for (size_t i = 0; i < net->users.size(); i++) { + delay_t slack = nd.min_required.at(i) - + (nd.max_arrival + ctx->getNetinfoRouteDelay(net, net->users.at(i))); + if (worst_slack.count(startdomain.first)) + worst_slack.at(startdomain.first) = std::min(worst_slack.at(startdomain.first), slack); + else + worst_slack[startdomain.first] = slack; + nc.slack.at(i) = std::min(nc.slack.at(i), slack); + } + } + } + // Assign criticality values + for (auto &net_entry : net_data) { + const NetInfo *net = net_entry.first; + for (auto &startdomain : net_entry.second) { + auto &nd = startdomain.second; + if (nd.min_required.empty()) + continue; + auto &nc = (*net_crit)[net->name]; + if (nc.slack.empty()) + continue; + if (nc.criticality.empty()) + nc.criticality.resize(net->users.size(), 0); + // Only consider intra-clock paths for criticality + if (!crit_path->count(ClockPair{startdomain.first, startdomain.first})) + continue; + delay_t dmax = crit_path->at(ClockPair{startdomain.first, startdomain.first}).path_delay; + for (size_t i = 0; i < net->users.size(); i++) { + float criticality = 1.0 - ((nc.slack.at(i) - worst_slack.at(startdomain.first)) / dmax); + nc.criticality.at(i) = std::max(nc.criticality.at(i), criticality); + } + } + } + } return min_slack; } diff --git a/common/timing_opt.cc b/common/timing_opt.cc new file mode 100644 index 00000000..b33c2db0 --- /dev/null +++ b/common/timing_opt.cc @@ -0,0 +1,42 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 David Shah + * + * 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. + * + */ + +/* + * Timing-optimised detailed placement algorithm + * Based on "An Effective Timing-Driven Detailed Placement Algorithm for FPGAs" + * https://www.cerc.utexas.edu/utda/publications/C205.pdf + */ + +#include "timing_opt.h" +#include "nextpnr.h" +NEXTPNR_NAMESPACE_BEGIN + +class TimingOptimiser +{ + public: + TimingOptimiser(Context *ctx) : ctx(ctx){}; + bool optimise() {} + + private: + Context *ctx; +}; + +bool timing_opt(Context *ctx, TimingOptCfg cfg) { return TimingOptimiser(ctx).optimise(); } + +NEXTPNR_NAMESPACE_END diff --git a/common/timing_opt.h b/common/timing_opt.h new file mode 100644 index 00000000..60df7df9 --- /dev/null +++ b/common/timing_opt.h @@ -0,0 +1,30 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 David Shah + * + * 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 + +struct TimingOptCfg : public Settings +{ +}; + +extern bool timing_opt(Context *ctx, TimingOptCfg cfg); + +NEXTPNR_NAMESPACE_END From 83e32775775cc06d0f70a18e2a18089c38ff3c35 Mon Sep 17 00:00:00 2001 From: David Shah Date: Sat, 1 Dec 2018 13:22:57 +0000 Subject: [PATCH 29/46] timing_opt: Implement neighbour Bel finder Signed-off-by: David Shah --- common/timing.cc | 2 ++ common/timing_opt.cc | 77 ++++++++++++++++++++++++++++++++++++++++++++ common/timing_opt.h | 4 +++ 3 files changed, 83 insertions(+) diff --git a/common/timing.cc b/common/timing.cc index 55d3a46f..ebe3a177 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -91,6 +91,7 @@ struct NetCriticalityInfo // One each per user std::vector slack; std::vector criticality; + unsigned max_path_length = 0; }; typedef std::unordered_map CriticalPathMap; @@ -597,6 +598,7 @@ struct Timing float criticality = 1.0 - ((nc.slack.at(i) - worst_slack.at(startdomain.first)) / dmax); nc.criticality.at(i) = std::max(nc.criticality.at(i), criticality); } + nc.max_path_length = std::max(nc.max_path_length, nd.max_path_length); } } } diff --git a/common/timing_opt.cc b/common/timing_opt.cc index b33c2db0..de8e00a5 100644 --- a/common/timing_opt.cc +++ b/common/timing_opt.cc @@ -34,6 +34,83 @@ class TimingOptimiser bool optimise() {} private: + // Ratio of available to already-candidates to begin borrowing + const float borrow_thresh = 0.2; + + bool check_cell_delay_limits(CellInfo *cell) { + + } + + bool acceptable_bel_candidate(CellInfo *cell, BelId newBel) { + bool result = true; + // At the moment we have to actually do the swap to get an accurate legality result + // Switching to macro swaps might help with this + BelId oldBel = cell->bel; + CellInfo *other_cell = ctx->getBoundBelCell(newBel); + if (other_cell != nullptr && other_cell->belStrength > STRENGTH_WEAK) { + return false; + } + + ctx->bindBel(newBel, cell, STRENGTH_WEAK); + if (other_cell != nullptr) { + ctx->bindBel(oldBel, other_cell, STRENGTH_WEAK); + } + if (!ctx->isBelLocationValid(newBel) || ((other_cell != nullptr && !ctx->isBelLocationValid(oldBel)))) { + result = false; + goto unbind; + } + + + +unbind: + ctx->unbindBel(newBel); + if (other_cell != nullptr) + ctx->unbindBel(oldBel); + // Undo the swap + ctx->bindBel(oldBel, cell, STRENGTH_WEAK); + if (other_cell != nullptr) { + ctx->bindBel(newBel, other_cell, STRENGTH_WEAK); + } + return result; + } + + void find_neighbours(CellInfo *cell, int d) { + BelId curr = cell->bel; + Loc curr_loc = ctx->getBelLocation(curr); + for (int dy = -d; dy <= d; dy++) { + for (int dx = -d; dx <= d; dx++) { + if (dx == 0 && dy == 0) + continue; + // Go through all the Bels at this location + // First, find all bels of the correct type that are either unbound or bound normally + // Strongly bound bels are ignored + // FIXME: This means that we cannot touch carry chains or similar relatively constrained macros + std::vector free_bels_at_loc; + std::vector bound_bels_at_loc; + for (auto bel : ctx->getBelsByTile(curr_loc.x + dx, curr_loc.y + dy)) { + if (ctx->getBelType(bel) != cell->type) + continue; + CellInfo *bound = ctx->getBoundBelCell(bel); + if (bound == nullptr) { + free_bels_at_loc.push_back(bel); + } else if (bound->belStrength <= STRENGTH_WEAK) { + bound_bels_at_loc.push_back(bel); + } + } + bool found = false; + + if (found) + continue; + } + } + } + + // Current candidate Bels for cells (linked in both direction> + std::vector path_cells; + std::unordered_map> cell_neighbour_bels; + std::unordered_map> bel_candidate_cells; + // Map net users to net delay limit + std::unordered_map> max_net_delay; Context *ctx; }; diff --git a/common/timing_opt.h b/common/timing_opt.h index 60df7df9..746294bb 100644 --- a/common/timing_opt.h +++ b/common/timing_opt.h @@ -23,6 +23,10 @@ NEXTPNR_NAMESPACE_BEGIN struct TimingOptCfg : public Settings { + // The timing optimiser will *only* optimise cells of these types + // Normally these would only be logic cells (or tiles if applicable), the algorithm makes little sense + // for other cell types + std::unordered_set cellTypes; }; extern bool timing_opt(Context *ctx, TimingOptCfg cfg); From 2de506c071b090c18977a594efbd6effd0315bf5 Mon Sep 17 00:00:00 2001 From: David Shah Date: Sat, 1 Dec 2018 13:43:12 +0000 Subject: [PATCH 30/46] timing_opt: Functions to calculate arc delay limits Signed-off-by: David Shah --- common/timing.cc | 17 +++++++------- common/timing.h | 13 +++++++++++ common/timing_opt.cc | 55 +++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 74 insertions(+), 11 deletions(-) diff --git a/common/timing.cc b/common/timing.cc index ebe3a177..1f48261d 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -85,14 +85,7 @@ struct CriticalPath delay_t path_period; }; -// Data for the timing optimisation algorithm -struct NetCriticalityInfo -{ - // One each per user - std::vector slack; - std::vector criticality; - unsigned max_path_length = 0; -}; + typedef std::unordered_map CriticalPathMap; typedef std::unordered_map NetCriticalityMap; @@ -599,6 +592,7 @@ struct Timing nc.criticality.at(i) = std::max(nc.criticality.at(i), criticality); } nc.max_path_length = std::max(nc.max_path_length, nd.max_path_length); + nc.cd_worst_slack = std::min(nc.cd_worst_slack, worst_slack.at(startdomain.first)); } } } @@ -914,4 +908,11 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p } } +void get_criticalities(Context *ctx, NetCriticalityMap *net_crit) { + CriticalPathMap crit_paths; + net_crit->clear(); + Timing timing(ctx, true, true, &crit_paths, nullptr, net_crit); + timing.walk_paths(); +} + NEXTPNR_NAMESPACE_END diff --git a/common/timing.h b/common/timing.h index 42f928dc..f1d18e8a 100644 --- a/common/timing.h +++ b/common/timing.h @@ -32,6 +32,19 @@ void assign_budget(Context *ctx, bool quiet = false); void timing_analysis(Context *ctx, bool slack_histogram = true, bool print_fmax = true, bool print_path = false, bool warn_on_failure = false); +// Data for the timing optimisation algorithm +struct NetCriticalityInfo +{ + // One each per user + std::vector slack; + std::vector criticality; + unsigned max_path_length = 0; + delay_t cd_worst_slack = std::numeric_limits::max(); +}; + +typedef std::unordered_map NetCriticalityMap; +void get_criticalities(Context *ctx, NetCriticalityMap *net_crit); + NEXTPNR_NAMESPACE_END #endif diff --git a/common/timing_opt.cc b/common/timing_opt.cc index de8e00a5..97860a23 100644 --- a/common/timing_opt.cc +++ b/common/timing_opt.cc @@ -23,8 +23,10 @@ * https://www.cerc.utexas.edu/utda/publications/C205.pdf */ +#include "timing.h" #include "timing_opt.h" #include "nextpnr.h" +#include "util.h" NEXTPNR_NAMESPACE_BEGIN class TimingOptimiser @@ -37,8 +39,52 @@ class TimingOptimiser // Ratio of available to already-candidates to begin borrowing const float borrow_thresh = 0.2; + void setup_delay_limits() { + for (auto net : sorted(ctx->nets)) { + NetInfo *ni = net.second; + max_net_delay[ni].clear(); + max_net_delay[ni].resize(ni->users.size(), std::numeric_limits::max()); + if (!net_crit.count(net.first)) + continue; + auto &nc = net_crit.at(net.first); + if (nc.slack.empty()) + continue; + for (size_t i = 0; i < ni->users.size(); i++) { + delay_t net_delay = ctx->getNetinfoRouteDelay(ni, ni->users.at(i)); + max_net_delay[ni].at(i) = net_delay + ((nc.slack.at(i) - nc.cd_worst_slack) / nc.max_path_length); + } + } + } + bool check_cell_delay_limits(CellInfo *cell) { - + for (const auto &port : cell->ports) { + int nc; + if (ctx->getPortTimingClass(cell, port.first, nc) == TMG_IGNORE) + continue; + NetInfo *net = port.second.net; + if (net == nullptr) + continue; + if (port.second.type == PORT_IN) { + if (net->driver.cell == nullptr || net->driver.cell->bel == BelId()) + continue; + BelId srcBel = net->driver.cell->bel; + if (ctx->estimateDelay(ctx->getBelPinWire(srcBel, net->driver.port), + ctx->getBelPinWire(cell->bel, port.first)) > max_net_delay.at(std::make_pair(cell->name, port.first))) + return false; + } else if (port.second.type == PORT_OUT) { + for (auto user : net->users) { + // This could get expensive for high-fanout nets?? + BelId dstBel = user.cell->bel; + if (dstBel == BelId()) + continue; + if (ctx->estimateDelay(ctx->getBelPinWire(cell->bel, port.first), + ctx->getBelPinWire(dstBel, user.port)) > max_net_delay.at(std::make_pair(user.cell->name, user.port))) + return false; + } + } + + } + return true; } bool acceptable_bel_candidate(CellInfo *cell, BelId newBel) { @@ -109,8 +155,11 @@ unbind: std::vector path_cells; std::unordered_map> cell_neighbour_bels; std::unordered_map> bel_candidate_cells; - // Map net users to net delay limit - std::unordered_map> max_net_delay; + // Map cell ports to net delay limit + std::unordered_map, delay_t> max_net_delay; + // Criticality data from timing analysis + NetCriticalityMap net_crit; + Context *ctx; }; From cd9a65a84c34bfeb6d759e3c147272e09880cb0f Mon Sep 17 00:00:00 2001 From: David Shah Date: Sat, 1 Dec 2018 14:06:51 +0000 Subject: [PATCH 31/46] timing_opt: Neigbour bel validity checking Signed-off-by: David Shah --- common/timing_opt.cc | 53 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/common/timing_opt.cc b/common/timing_opt.cc index 97860a23..abfe5cf1 100644 --- a/common/timing_opt.cc +++ b/common/timing_opt.cc @@ -106,7 +106,10 @@ class TimingOptimiser goto unbind; } - + if (!check_cell_delay_limits(cell) || (other_cell != nullptr && !check_cell_delay_limits(other_cell))) { + result = false; + goto unbind; + } unbind: ctx->unbindBel(newBel); @@ -120,9 +123,10 @@ unbind: return result; } - void find_neighbours(CellInfo *cell, int d) { + int find_neighbours(CellInfo *cell, IdString prev_cell, int d, bool allow_swap) { BelId curr = cell->bel; Loc curr_loc = ctx->getBelLocation(curr); + int found_count = 0; for (int dy = -d; dy <= d; dy++) { for (int dx = -d; dx <= d; dx++) { if (dx == 0 && dy == 0) @@ -143,12 +147,51 @@ unbind: bound_bels_at_loc.push_back(bel); } } - bool found = false; + BelId candidate; - if (found) - continue; + while (!free_bels_at_loc.empty() && !bound_bels_at_loc.empty()) { + BelId try_bel; + if (!free_bels_at_loc.empty()) { + int try_idx = ctx->rng(int(free_bels_at_loc.size())); + try_bel = free_bels_at_loc.at(try_idx); + free_bels_at_loc.erase(free_bels_at_loc.begin() + try_idx); + } else { + int try_idx = ctx->rng(int(bound_bels_at_loc.size())); + try_bel = bound_bels_at_loc.at(try_idx); + bound_bels_at_loc.erase(bound_bels_at_loc.begin() + try_idx); + } + if (bel_candidate_cells.count(try_bel) && !allow_swap) { + // Overlap is only allowed if it is with the previous cell (this is handled by removing those + // edges in the graph), or if allow_swap is true to deal with cases where overlap means few neighbours + // are identified + if (bel_candidate_cells.at(try_bel).size() > 1 || (bel_candidate_cells.at(try_bel).size() == 0 || + *(bel_candidate_cells.at(try_bel).begin()) != prev_cell)) + continue; + } + if (acceptable_bel_candidate(cell, try_bel)) { + candidate = try_bel; + break; + } + } + + if (candidate != BelId()) { + cell_neighbour_bels[cell->name].insert(candidate); + bel_candidate_cells[candidate].insert(cell->name); + // Work out if we need to delete any overlap + std::vector overlap; + for (auto other : bel_candidate_cells[candidate]) + if (other != cell->name && other != prev_cell) + overlap.push_back(other); + if (overlap.size() > 0) + NPNR_ASSERT(allow_swap); + for (auto ov : overlap) { + bel_candidate_cells[candidate].erase(ov); + cell_neighbour_bels[ov].erase(candidate); + } + } } } + return found_count; } // Current candidate Bels for cells (linked in both direction> From 51a662d37e4361fc2a39258fd1dc1b56ff6c15b0 Mon Sep 17 00:00:00 2001 From: David Shah Date: Sat, 1 Dec 2018 15:22:32 +0000 Subject: [PATCH 32/46] timing_opt: Implement critical path finder Signed-off-by: David Shah --- common/timing_opt.cc | 114 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/common/timing_opt.cc b/common/timing_opt.cc index abfe5cf1..c7ecd814 100644 --- a/common/timing_opt.cc +++ b/common/timing_opt.cc @@ -194,6 +194,120 @@ unbind: return found_count; } + std::vector> find_crit_paths(float crit_thresh, int max_count) { + std::vector> crit_paths; + std::vector> crit_nets; + std::vector netnames; + std::transform(ctx->nets.begin(), ctx->nets.end(), std::back_inserter(netnames), + [](const std::pair> &kv){ + return kv.first; + }); + ctx->sorted_shuffle(netnames); + for (auto net : netnames) { + if (crit_nets.size() >= max_count) + break; + if (!net_crit.count(net)) + continue; + auto crit_user = std::max_element(net_crit[net].criticality.begin(), + net_crit[net].criticality.end()); + if (*crit_user > crit_thresh) + crit_nets.push_back(std::make_pair(ctx->nets[net].get(), crit_user - net_crit[net].criticality.begin())); + } + + auto port_user_index = [](CellInfo *cell, PortInfo &port) -> size_t { + NPNR_ASSERT(port.net != nullptr); + for (size_t i = 0; i < port.net->users.size(); i++) { + auto &usr = port.net->users.at(i); + if (usr.cell == cell && usr.port == port.name) + return i; + } + NPNR_ASSERT_FALSE("port user not found on net"); + }; + + for (auto crit_net : crit_nets) { + std::deque crit_path; + + // FIXME: This will fail badly on combinational loops + + // Iterate backwards following greatest criticality + NetInfo* back_cursor = crit_net.first; + while (back_cursor != nullptr) { + float max_crit = 0; + std::pair crit_sink{nullptr, 0}; + CellInfo *cell = back_cursor->driver.cell; + if (cell == nullptr) + break; + for (auto port : cell->ports) { + if (port.second.type != PORT_IN) + continue; + NetInfo *pn = port.second.net; + if (pn == nullptr) + continue; + if (!net_crit.count(pn->name) || net_crit.at(pn->name).criticality.empty()) + continue; + int ccount; + DelayInfo combDelay; + TimingPortClass tpclass = ctx->getPortTimingClass(cell, port.first, ccount); + if (tpclass != TMG_COMB_INPUT && tpclass != TMG_REGISTER_INPUT) + continue; + bool is_path = ctx->getCellDelay(cell, port.first, back_cursor->driver.port, combDelay); + if (!is_path) + continue; + size_t user_idx = port_user_index(cell, port.second); + float usr_crit = net_crit.at(pn->name).criticality.at(user_idx); + if (usr_crit >= max_crit) { + max_crit = usr_crit; + crit_sink = std::make_pair(pn, user_idx); + } + } + + if (crit_sink.first != nullptr) { + crit_path.push_front(&(crit_sink.first->users.at(crit_sink.second))); + } + back_cursor = crit_sink.first; + } + // Iterate forwards following greatest criticiality + PortRef *fwd_cursor = &(crit_net.first->users.at(crit_net.second)); + while (fwd_cursor != nullptr) { + crit_path.push_back(fwd_cursor); + float max_crit = 0; + std::pair crit_sink{nullptr, 0}; + CellInfo *cell = fwd_cursor->cell; + for (auto port : cell->ports) { + if (port.second.type != PORT_OUT) + continue; + NetInfo *pn = port.second.net; + if (pn == nullptr) + continue; + if (!net_crit.count(pn->name) || net_crit.at(pn->name).criticality.empty()) + continue; + int ccount; + DelayInfo combDelay; + TimingPortClass tpclass = ctx->getPortTimingClass(cell, port.first, ccount); + if (tpclass != TMG_COMB_OUTPUT && tpclass != TMG_REGISTER_OUTPUT) + continue; + auto &crits = net_crit.at(pn->name).criticality; + auto most_crit_usr = std::max_element(crits.begin(), crits.end()); + if (*most_crit_usr >= max_crit) { + max_crit = *most_crit_usr; + crit_sink = std::make_pair(pn, std::distance(crits.begin(), most_crit_usr)); + } + } + if (crit_sink.first != nullptr) { + fwd_cursor = &(crit_sink.first->users.at(crit_sink.second)); + } else { + fwd_cursor = nullptr; + } + } + + std::vector crit_path_vec; + std::copy(crit_path.begin(), crit_path.end(), std::back_inserter(crit_path_vec)); + crit_paths.push_back(crit_path_vec); + } + + return crit_paths; + } + // Current candidate Bels for cells (linked in both direction> std::vector path_cells; std::unordered_map> cell_neighbour_bels; From 1b7214a18ae4cf6fb62827b06e4b5f158292da4b Mon Sep 17 00:00:00 2001 From: David Shah Date: Sat, 1 Dec 2018 16:50:47 +0000 Subject: [PATCH 33/46] timing_opt: Implement the BFS-based path optimisation Signed-off-by: David Shah --- common/timing_opt.cc | 186 +++++++++++++++++++++++++++++++++++-------- common/timing_opt.h | 3 +- 2 files changed, 154 insertions(+), 35 deletions(-) diff --git a/common/timing_opt.cc b/common/timing_opt.cc index c7ecd814..42c2242a 100644 --- a/common/timing_opt.cc +++ b/common/timing_opt.cc @@ -18,15 +18,22 @@ */ /* - * Timing-optimised detailed placement algorithm + * Timing-optimised detailed placement algorithm using BFS of the neighbour graph created from cells + * on a critical path + * * Based on "An Effective Timing-Driven Detailed Placement Algorithm for FPGAs" * https://www.cerc.utexas.edu/utda/publications/C205.pdf + * + * Modifications made to deal with the smaller Bels that nextpnr uses instead of swapping whole tiles, + * and deal with the fact that not every cell on the crit path may be swappable. */ #include "timing.h" #include "timing_opt.h" #include "nextpnr.h" #include "util.h" +#include +#include NEXTPNR_NAMESPACE_BEGIN class TimingOptimiser @@ -87,40 +94,38 @@ class TimingOptimiser return true; } - bool acceptable_bel_candidate(CellInfo *cell, BelId newBel) { - bool result = true; - // At the moment we have to actually do the swap to get an accurate legality result - // Switching to macro swaps might help with this + BelId cell_swap_bel(CellInfo *cell, BelId newBel) { BelId oldBel = cell->bel; CellInfo *other_cell = ctx->getBoundBelCell(newBel); - if (other_cell != nullptr && other_cell->belStrength > STRENGTH_WEAK) { - return false; - } - - ctx->bindBel(newBel, cell, STRENGTH_WEAK); + NPNR_ASSERT(other_cell == nullptr || other_cell->belStrength <= STRENGTH_WEAK); + ctx->unbindBel(oldBel); if (other_cell != nullptr) { + ctx->unbindBel(newBel); ctx->bindBel(oldBel, other_cell, STRENGTH_WEAK); } - if (!ctx->isBelLocationValid(newBel) || ((other_cell != nullptr && !ctx->isBelLocationValid(oldBel)))) { - result = false; - goto unbind; - } + ctx->bindBel(newBel, cell, STRENGTH_WEAK); + return oldBel; + } - if (!check_cell_delay_limits(cell) || (other_cell != nullptr && !check_cell_delay_limits(other_cell))) { - result = false; - goto unbind; + // Check that a series of moves are both legal and remain within maximum delay bounds + // Moves are specified as a vector of pairs + bool acceptable_move(std::vector> &move, bool check_delays = true) { + for (auto &entry : move) { + if (!ctx->isBelLocationValid(entry.first->bel)) + return false; + if (!ctx->isBelLocationValid(entry.second)) + return false; + if (!check_delays) + continue; + if (!check_cell_delay_limits(entry.first)) + return false; + // We might have swapped another cell onto the original bel. Check this for max delay violations + // too + CellInfo *swapped = ctx->getBoundBelCell(entry.second); + if (swapped != nullptr && !check_cell_delay_limits(swapped)) + return false; } - -unbind: - ctx->unbindBel(newBel); - if (other_cell != nullptr) - ctx->unbindBel(oldBel); - // Undo the swap - ctx->bindBel(oldBel, cell, STRENGTH_WEAK); - if (other_cell != nullptr) { - ctx->bindBel(newBel, other_cell, STRENGTH_WEAK); - } - return result; + return true; } int find_neighbours(CellInfo *cell, IdString prev_cell, int d, bool allow_swap) { @@ -129,8 +134,6 @@ unbind: int found_count = 0; for (int dy = -d; dy <= d; dy++) { for (int dx = -d; dx <= d; dx++) { - if (dx == 0 && dy == 0) - continue; // Go through all the Bels at this location // First, find all bels of the correct type that are either unbound or bound normally // Strongly bound bels are ignored @@ -168,10 +171,9 @@ unbind: *(bel_candidate_cells.at(try_bel).begin()) != prev_cell)) continue; } - if (acceptable_bel_candidate(cell, try_bel)) { - candidate = try_bel; - break; - } + // TODO: what else to check here? + candidate = try_bel; + break; } if (candidate != BelId()) { @@ -308,6 +310,120 @@ unbind: return crit_paths; } + void optimise_path(std::vector &path) { + path_cells.clear(); + cell_neighbour_bels.clear(); + bel_candidate_cells.clear(); + for (auto port : path) { + if (std::find(path_cells.begin(), path_cells.end(), port->cell->name) != path_cells.end()) + continue; + if (port->cell->belStrength > STRENGTH_WEAK || !cfg.cellTypes.count(port->cell->type)) + continue; + path_cells.push_back(port->cell->name); + } + + if (path_cells.empty()) + return; + + IdString last_cell; + const int d = 3; // FIXME: how to best determine d + for (auto cell : path_cells) { + // FIXME: when should we allow swapping due to a lack of candidates + find_neighbours(ctx->cells[cell].get(), last_cell, d, false); + last_cell = cell; + } + // Map cells that we will actually modify to the arc we will use for cost + // calculation + // for delay calc purposes + std::unordered_map> cost_ports; + PortRef *last_port = nullptr; + auto pcell = path_cells.begin(); + for (auto port : path) { + if (port->cell->name == *pcell) { + cost_ports[*pcell] = std::make_pair(last_port, port); + pcell++; + } + last_port = port; + } + + // Actual BFS path optimisation algorithm + std::unordered_map> cumul_costs; + std::unordered_map, std::pair> backtrace; + std::queue> visit; + std::unordered_set> to_visit; + + for (auto startbel : cell_neighbour_bels[path_cells.front()]) { + auto entry = std::make_pair(0, startbel); + visit.push(entry); + cumul_costs[path_cells.front()][startbel] = 0; + } + + while(!visit.empty()) { + auto entry = visit.front(); + visit.pop(); + auto cellname = path_cells.at(entry.first); + if (entry.first == path_cells.size() - 1) + continue; + std::vector> move; + // Apply the entire backtrace for accurate legality and delay checks + // This is probably pretty expensive (but also probably pales in comparison to the number of swaps + // SA will make...) + std::vector> route_to_entry; + auto cursor = std::make_pair(cellname, entry.second); + route_to_entry.push_back(cursor); + while (backtrace.count(cursor)) { + cursor = backtrace.at(cursor); + route_to_entry.push_back(cursor); + } + for (auto rt_entry : boost::adaptors::reverse(route_to_entry)) { + CellInfo *cell = ctx->cells.at(rt_entry.first).get(); + BelId origBel = cell_swap_bel(cell, rt_entry.second); + move.push_back(std::make_pair(cell, origBel)); + } + + delay_t cdelay = cumul_costs[cellname][entry.second]; + + // Have a look at where we can travel from here + for (auto neighbour : cell_neighbour_bels.at(path_cells.at(entry.first + 1))) { + // Edges between overlapping bels are deleted + if (neighbour == entry.second) + continue; + // Experimentally swap the next path cell onto the neighbour bel we are trying + IdString ncname = path_cells.at(entry.first + 1); + CellInfo *next_cell = ctx->cells.at(ncname).get(); + BelId origBel = cell_swap_bel(next_cell, neighbour); + move.push_back(std::make_pair(next_cell, origBel)); + + // Check the new cumulative delay + auto port_pair = cost_ports.at(ncname); + delay_t edge_delay = ctx->estimateDelay(ctx->getBelPinWire(port_pair.first->cell->bel, port_pair.first->port), + ctx->getBelPinWire(port_pair.second->cell->bel, port_pair.second->port)); + delay_t total_delay = cdelay + edge_delay; + // First, check if the move is actually worthwhile from a delay point of view before the expensive + // legality check + if (!cumul_costs.count(ncname) || !cumul_costs.at(ncname).count(neighbour) + || cumul_costs.at(ncname).at(neighbour) > total_delay) { + // Now check that the swaps we have made to get here are legal and meet max delay requirements + if (acceptable_move(move)) { + cumul_costs[ncname][neighbour] = total_delay; + backtrace[std::make_pair(ncname, neighbour)] = std::make_pair(cellname, entry.second); + if (!to_visit.count(std::make_pair(entry.first + 1, neighbour))) + visit.push(std::make_pair(entry.first + 1, neighbour)); + } + } + // Revert the experimental swap + cell_swap_bel(move.back().first, move.back().second); + move.pop_back(); + } + + // Revert move by swapping cells back to their original order + // Execute swaps in reverse order to how we made them originally + for (auto move_entry : boost::adaptors::reverse(move)) { + cell_swap_bel(move_entry.first, move_entry.second); + } + } + } + // Current candidate Bels for cells (linked in both direction> std::vector path_cells; std::unordered_map> cell_neighbour_bels; @@ -317,6 +433,8 @@ unbind: // Criticality data from timing analysis NetCriticalityMap net_crit; + TimingOptCfg cfg; + Context *ctx; }; diff --git a/common/timing_opt.h b/common/timing_opt.h index 746294bb..fda29d30 100644 --- a/common/timing_opt.h +++ b/common/timing_opt.h @@ -18,6 +18,7 @@ */ #include "nextpnr.h" +#include "settings.h" NEXTPNR_NAMESPACE_BEGIN @@ -26,7 +27,7 @@ struct TimingOptCfg : public Settings // The timing optimiser will *only* optimise cells of these types // Normally these would only be logic cells (or tiles if applicable), the algorithm makes little sense // for other cell types - std::unordered_set cellTypes; + std::unordered_set cellTypes; }; extern bool timing_opt(Context *ctx, TimingOptCfg cfg); From b51308708bf7202c097deb7f70ff83e710e0970c Mon Sep 17 00:00:00 2001 From: David Shah Date: Sun, 2 Dec 2018 12:01:43 +0000 Subject: [PATCH 34/46] timing_opt: Debugging and integration Signed-off-by: David Shah --- common/timing_opt.cc | 127 ++++++++++++++++++++++++++++++++++++++----- common/timing_opt.h | 2 + ice40/arch.cc | 9 ++- 3 files changed, 123 insertions(+), 15 deletions(-) diff --git a/common/timing_opt.cc b/common/timing_opt.cc index 42c2242a..3a289812 100644 --- a/common/timing_opt.cc +++ b/common/timing_opt.cc @@ -34,31 +34,92 @@ #include "util.h" #include #include + +namespace std { + + template <> struct hash> + { + std::size_t operator()(const std::pair &idp) const noexcept + { + std::size_t seed = 0; + boost::hash_combine(seed, hash()(idp.first)); + boost::hash_combine(seed, hash()(idp.second)); + return seed; + } + }; + + template <> struct hash> + { + std::size_t operator()(const std::pair &idp) const noexcept + { + std::size_t seed = 0; + boost::hash_combine(seed, hash()(idp.first)); + boost::hash_combine(seed, hash()(idp.second)); + return seed; + } + }; + + template <> struct hash> + { + std::size_t operator()(const std::pair &idp) const noexcept + { + std::size_t seed = 0; + boost::hash_combine(seed, hash()(idp.first)); + boost::hash_combine(seed, hash()(idp.second)); + return seed; + } + }; +} + NEXTPNR_NAMESPACE_BEGIN class TimingOptimiser { public: - TimingOptimiser(Context *ctx) : ctx(ctx){}; - bool optimise() {} + TimingOptimiser(Context *ctx, TimingOptCfg cfg) : ctx(ctx), cfg(cfg) {}; + bool optimise() { + log_info("Running timing-driven placement optimisation...\n"); +#if 1 + timing_analysis(ctx, false, true, ctx->debug, false); +#endif + for (int i = 0; i < 20; i++) { + log_info(" Iteration %d...\n", i); + get_criticalities(ctx, &net_crit); + setup_delay_limits(); + auto crit_paths = find_crit_paths(0.98, 1000); + for (auto &path : crit_paths) + optimise_path(path); +#if 1 + timing_analysis(ctx, false, true, ctx->debug, false); +#endif + } + return true; + } private: // Ratio of available to already-candidates to begin borrowing const float borrow_thresh = 0.2; void setup_delay_limits() { + max_net_delay.clear(); for (auto net : sorted(ctx->nets)) { NetInfo *ni = net.second; - max_net_delay[ni].clear(); - max_net_delay[ni].resize(ni->users.size(), std::numeric_limits::max()); + for (auto usr : ni->users) { + max_net_delay[std::make_pair(usr.cell->name, usr.port)] + = std::numeric_limits::max(); + } if (!net_crit.count(net.first)) continue; auto &nc = net_crit.at(net.first); if (nc.slack.empty()) continue; for (size_t i = 0; i < ni->users.size(); i++) { - delay_t net_delay = ctx->getNetinfoRouteDelay(ni, ni->users.at(i)); - max_net_delay[ni].at(i) = net_delay + ((nc.slack.at(i) - nc.cd_worst_slack) / nc.max_path_length); + auto &usr = ni->users.at(i); + delay_t net_delay = ctx->getNetinfoRouteDelay(ni, usr); + if (nc.max_path_length != 0) { + max_net_delay[std::make_pair(usr.cell->name, usr.port)] + = net_delay + ((nc.slack.at(i) - nc.cd_worst_slack) / nc.max_path_length); + } } } } @@ -196,12 +257,12 @@ class TimingOptimiser return found_count; } - std::vector> find_crit_paths(float crit_thresh, int max_count) { + std::vector> find_crit_paths(float crit_thresh, size_t max_count) { std::vector> crit_paths; std::vector> crit_nets; std::vector netnames; std::transform(ctx->nets.begin(), ctx->nets.end(), std::back_inserter(netnames), - [](const std::pair> &kv){ + [](const std::pair> &kv){ return kv.first; }); ctx->sorted_shuffle(netnames); @@ -250,7 +311,7 @@ class TimingOptimiser int ccount; DelayInfo combDelay; TimingPortClass tpclass = ctx->getPortTimingClass(cell, port.first, ccount); - if (tpclass != TMG_COMB_INPUT && tpclass != TMG_REGISTER_INPUT) + if (tpclass != TMG_COMB_INPUT) continue; bool is_path = ctx->getCellDelay(cell, port.first, back_cursor->driver.port, combDelay); if (!is_path) @@ -286,7 +347,7 @@ class TimingOptimiser int ccount; DelayInfo combDelay; TimingPortClass tpclass = ctx->getPortTimingClass(cell, port.first, ccount); - if (tpclass != TMG_COMB_OUTPUT && tpclass != TMG_REGISTER_OUTPUT) + if (tpclass != TMG_COMB_OUTPUT) continue; auto &crits = net_crit.at(pn->name).criticality; auto most_crit_usr = std::max_element(crits.begin(), crits.end()); @@ -314,11 +375,17 @@ class TimingOptimiser path_cells.clear(); cell_neighbour_bels.clear(); bel_candidate_cells.clear(); + if (ctx->debug) + log_info("Optimising the following path: \n"); for (auto port : path) { + if (ctx->debug) + log_info(" %s.%s at %s\n", port->cell->name.c_str(ctx), port->port.c_str(ctx), ctx->getBelName(port->cell->bel).c_str(ctx)); if (std::find(path_cells.begin(), path_cells.end(), port->cell->name) != path_cells.end()) continue; if (port->cell->belStrength > STRENGTH_WEAK || !cfg.cellTypes.count(port->cell->type)) continue; + if (ctx->debug) + log_info(" can move\n"); path_cells.push_back(port->cell->name); } @@ -362,7 +429,7 @@ class TimingOptimiser auto entry = visit.front(); visit.pop(); auto cellname = path_cells.at(entry.first); - if (entry.first == path_cells.size() - 1) + if (entry.first == int(path_cells.size()) - 1) continue; std::vector> move; // Apply the entire backtrace for accurate legality and delay checks @@ -422,6 +489,39 @@ class TimingOptimiser cell_swap_bel(move_entry.first, move_entry.second); } } + + // Did we find a solution?? + if (cumul_costs.count(path_cells.back())) { + // Find the end position with the lowest total delay + auto &end_options = cumul_costs.at(path_cells.back()); + auto lowest = std::min_element(end_options.begin(), end_options.end(), [](const std::pair &a, + const std::pair &b) { + return a.second < b.second; + }); + NPNR_ASSERT(lowest != end_options.end()); + + std::vector> route_to_solution; + auto cursor = std::make_pair(path_cells.back(), lowest->first); + route_to_solution.push_back(cursor); + while (backtrace.count(cursor)) { + cursor = backtrace.at(cursor); + route_to_solution.push_back(cursor); + } + if (ctx->debug) + log_info("Found a solution with cost %.02f ns\n", ctx->getDelayNS(lowest->second)); + for (auto rt_entry : boost::adaptors::reverse(route_to_solution)) { + CellInfo *cell = ctx->cells.at(rt_entry.first).get(); + cell_swap_bel(cell, rt_entry.second); + if(ctx->debug) + log_info(" %s at %s\n", rt_entry.first.c_str(ctx), ctx->getBelName(rt_entry.second).c_str(ctx)); + } + + } else { + if (ctx->debug) + log_info("Solution was not found\n"); + } + if (ctx->debug) + log_break(); } // Current candidate Bels for cells (linked in both direction> @@ -432,12 +532,11 @@ class TimingOptimiser std::unordered_map, delay_t> max_net_delay; // Criticality data from timing analysis NetCriticalityMap net_crit; - + Context *ctx; TimingOptCfg cfg; - Context *ctx; }; -bool timing_opt(Context *ctx, TimingOptCfg cfg) { return TimingOptimiser(ctx).optimise(); } +bool timing_opt(Context *ctx, TimingOptCfg cfg) { return TimingOptimiser(ctx, cfg).optimise(); } NEXTPNR_NAMESPACE_END diff --git a/common/timing_opt.h b/common/timing_opt.h index fda29d30..ceb35c71 100644 --- a/common/timing_opt.h +++ b/common/timing_opt.h @@ -24,6 +24,8 @@ NEXTPNR_NAMESPACE_BEGIN struct TimingOptCfg : public Settings { + TimingOptCfg(Context *ctx) : Settings(ctx) {} + // The timing optimiser will *only* optimise cells of these types // Normally these would only be logic cells (or tiles if applicable), the algorithm makes little sense // for other cell types diff --git a/ice40/arch.cc b/ice40/arch.cc index ada78020..5cd55774 100644 --- a/ice40/arch.cc +++ b/ice40/arch.cc @@ -27,6 +27,7 @@ #include "placer1.h" #include "router1.h" #include "util.h" +#include "timing_opt.h" NEXTPNR_NAMESPACE_BEGIN @@ -626,7 +627,13 @@ bool Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay // ----------------------------------------------------------------------- -bool Arch::place() { return placer1(getCtx(), Placer1Cfg(getCtx())); } +bool Arch::place() { + if(!placer1(getCtx(), Placer1Cfg(getCtx()))) + return false; + TimingOptCfg tocfg(getCtx()); + tocfg.cellTypes.insert(id_ICESTORM_LC); + return timing_opt(getCtx(), tocfg); +} bool Arch::route() { return router1(getCtx(), Router1Cfg(getCtx())); } From e1c74ad3db06c7279b018a93416dc3be178002d5 Mon Sep 17 00:00:00 2001 From: David Shah Date: Sun, 2 Dec 2018 12:23:18 +0000 Subject: [PATCH 35/46] timing_opt: Fixes including single-move legality Signed-off-by: David Shah --- common/timing.cc | 8 ++++- common/timing_opt.cc | 69 ++++++++++++++++++++++++++++++++++---------- 2 files changed, 60 insertions(+), 17 deletions(-) diff --git a/common/timing.cc b/common/timing.cc index 1f48261d..69ccc78f 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -472,6 +472,8 @@ struct Timing auto &nd = startdomain.second; if (nd.false_startpoint) continue; + if (startdomain.first.clock == async_clock) + continue; const delay_t net_length_plus_one = nd.max_path_length + 1; auto &net_min_remaining_budget = nd.min_remaining_budget; if (nd.min_required.empty()) @@ -555,6 +557,8 @@ struct Timing const NetInfo *net = net_entry.first; for (auto &startdomain : net_entry.second) { auto &nd = startdomain.second; + if (startdomain.first.clock == async_clock) + continue; if (nd.min_required.empty()) continue; auto &nc = (*net_crit)[net->name]; @@ -575,6 +579,8 @@ struct Timing for (auto &net_entry : net_data) { const NetInfo *net = net_entry.first; for (auto &startdomain : net_entry.second) { + if (startdomain.first.clock == async_clock) + continue; auto &nd = startdomain.second; if (nd.min_required.empty()) continue; @@ -588,7 +594,7 @@ struct Timing continue; delay_t dmax = crit_path->at(ClockPair{startdomain.first, startdomain.first}).path_delay; for (size_t i = 0; i < net->users.size(); i++) { - float criticality = 1.0 - ((nc.slack.at(i) - worst_slack.at(startdomain.first)) / dmax); + float criticality = 1.0f - (float(nc.slack.at(i) - worst_slack.at(startdomain.first)) / dmax); nc.criticality.at(i) = std::max(nc.criticality.at(i), criticality); } nc.max_path_length = std::max(nc.max_path_length, nd.max_path_length); diff --git a/common/timing_opt.cc b/common/timing_opt.cc index 3a289812..d1194876 100644 --- a/common/timing_opt.cc +++ b/common/timing_opt.cc @@ -80,7 +80,7 @@ class TimingOptimiser bool optimise() { log_info("Running timing-driven placement optimisation...\n"); #if 1 - timing_analysis(ctx, false, true, ctx->debug, false); + timing_analysis(ctx, false, true, false, false); #endif for (int i = 0; i < 20; i++) { log_info(" Iteration %d...\n", i); @@ -90,7 +90,7 @@ class TimingOptimiser for (auto &path : crit_paths) optimise_path(path); #if 1 - timing_analysis(ctx, false, true, ctx->debug, false); + timing_analysis(ctx, false, true, false, false); #endif } return true; @@ -146,8 +146,17 @@ class TimingOptimiser if (dstBel == BelId()) continue; if (ctx->estimateDelay(ctx->getBelPinWire(cell->bel, port.first), - ctx->getBelPinWire(dstBel, user.port)) > max_net_delay.at(std::make_pair(user.cell->name, user.port))) + ctx->getBelPinWire(dstBel, user.port)) > max_net_delay.at(std::make_pair(user.cell->name, user.port))) { +#if 0 + if (ctx->debug) { + log_info(" est delay %.02fns exceeded maximum %.02fns\n", ctx->getDelayNS(ctx->estimateDelay(ctx->getBelPinWire(cell->bel, port.first), + ctx->getBelPinWire(dstBel, user.port))), + ctx->getDelayNS(max_net_delay.at(std::make_pair(user.cell->name, user.port)))); + } +#endif return false; + + } } } @@ -193,6 +202,7 @@ class TimingOptimiser BelId curr = cell->bel; Loc curr_loc = ctx->getBelLocation(curr); int found_count = 0; + cell_neighbour_bels[cell->name] = std::unordered_set{}; for (int dy = -d; dy <= d; dy++) { for (int dx = -d; dx <= d; dx++) { // Go through all the Bels at this location @@ -207,7 +217,7 @@ class TimingOptimiser CellInfo *bound = ctx->getBoundBelCell(bel); if (bound == nullptr) { free_bels_at_loc.push_back(bel); - } else if (bound->belStrength <= STRENGTH_WEAK) { + } else if (bound->belStrength <= STRENGTH_WEAK || bound->constr_parent != nullptr || !bound->constr_children.empty()) { bound_bels_at_loc.push_back(bel); } } @@ -286,6 +296,7 @@ class TimingOptimiser } NPNR_ASSERT_FALSE("port user not found on net"); }; + std::unordered_set used_ports; for (auto crit_net : crit_nets) { std::deque crit_path; @@ -318,6 +329,8 @@ class TimingOptimiser continue; size_t user_idx = port_user_index(cell, port.second); float usr_crit = net_crit.at(pn->name).criticality.at(user_idx); + if (used_ports.count(&(pn->users.at(user_idx)))) + continue; if (usr_crit >= max_crit) { max_crit = usr_crit; crit_sink = std::make_pair(pn, user_idx); @@ -326,6 +339,7 @@ class TimingOptimiser if (crit_sink.first != nullptr) { crit_path.push_front(&(crit_sink.first->users.at(crit_sink.second))); + used_ports.insert(&(crit_sink.first->users.at(crit_sink.second))); } back_cursor = crit_sink.first; } @@ -350,14 +364,19 @@ class TimingOptimiser if (tpclass != TMG_COMB_OUTPUT) continue; auto &crits = net_crit.at(pn->name).criticality; - auto most_crit_usr = std::max_element(crits.begin(), crits.end()); - if (*most_crit_usr >= max_crit) { - max_crit = *most_crit_usr; - crit_sink = std::make_pair(pn, std::distance(crits.begin(), most_crit_usr)); + for (size_t i = 0; i < crits.size(); i++) { + if (used_ports.count(&(pn->users.at(i)))) + continue; + if (crits.at(i) >= max_crit) { + max_crit = crits.at(i); + crit_sink = std::make_pair(pn, i); + } } + } if (crit_sink.first != nullptr) { fwd_cursor = &(crit_sink.first->users.at(crit_sink.second)); + used_ports.insert(&(crit_sink.first->users.at(crit_sink.second))); } else { fwd_cursor = nullptr; } @@ -378,20 +397,30 @@ class TimingOptimiser if (ctx->debug) log_info("Optimising the following path: \n"); for (auto port : path) { - if (ctx->debug) - log_info(" %s.%s at %s\n", port->cell->name.c_str(ctx), port->port.c_str(ctx), ctx->getBelName(port->cell->bel).c_str(ctx)); + if (ctx->debug) { + float crit = 0; + NetInfo *pn = port->cell->ports.at(port->port).net; + if (net_crit.count(pn->name) && !net_crit.at(pn->name).criticality.empty()) + for (size_t i = 0; i < pn->users.size(); i++) + if (pn->users.at(i).cell == port->cell && pn->users.at(i).port == port->port) + crit = net_crit.at(pn->name).criticality.at(i); + log_info(" %s.%s at %s crit %0.02f\n", port->cell->name.c_str(ctx), port->port.c_str(ctx), ctx->getBelName(port->cell->bel).c_str(ctx), crit); + + } if (std::find(path_cells.begin(), path_cells.end(), port->cell->name) != path_cells.end()) continue; - if (port->cell->belStrength > STRENGTH_WEAK || !cfg.cellTypes.count(port->cell->type)) + if (port->cell->belStrength > STRENGTH_WEAK || !cfg.cellTypes.count(port->cell->type) || port->cell->constr_parent != nullptr || !port->cell->constr_children.empty()) continue; if (ctx->debug) log_info(" can move\n"); path_cells.push_back(port->cell->name); } - if (path_cells.empty()) + if (path_cells.size() < 3) { + log_info("Too few moveable cells; skipping path\n"); + log_break(); return; - + } IdString last_cell; const int d = 3; // FIXME: how to best determine d for (auto cell : path_cells) { @@ -420,9 +449,17 @@ class TimingOptimiser std::unordered_set> to_visit; for (auto startbel : cell_neighbour_bels[path_cells.front()]) { - auto entry = std::make_pair(0, startbel); - visit.push(entry); - cumul_costs[path_cells.front()][startbel] = 0; + // Swap for legality check + CellInfo *cell = ctx->cells.at(path_cells.front()).get(); + BelId origBel = cell_swap_bel(cell, startbel); + std::vector> move{std::make_pair(cell, origBel)}; + if (acceptable_move(move)) { + auto entry = std::make_pair(0, startbel); + visit.push(entry); + cumul_costs[path_cells.front()][startbel] = 0; + } + // Swap back + cell_swap_bel(cell, origBel); } while(!visit.empty()) { From 254c5ea3599bb78051642030c410bcb79c17699a Mon Sep 17 00:00:00 2001 From: David Shah Date: Sun, 2 Dec 2018 13:15:39 +0000 Subject: [PATCH 36/46] clangformat Signed-off-by: David Shah --- common/timing.cc | 5 +- common/timing_opt.cc | 170 +++++++++++++++++++++++-------------------- ice40/arch.cc | 7 +- 3 files changed, 96 insertions(+), 86 deletions(-) diff --git a/common/timing.cc b/common/timing.cc index 69ccc78f..e90718d8 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -85,8 +85,6 @@ struct CriticalPath delay_t path_period; }; - - typedef std::unordered_map CriticalPathMap; typedef std::unordered_map NetCriticalityMap; @@ -914,7 +912,8 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p } } -void get_criticalities(Context *ctx, NetCriticalityMap *net_crit) { +void get_criticalities(Context *ctx, NetCriticalityMap *net_crit) +{ CriticalPathMap crit_paths; net_crit->clear(); Timing timing(ctx, true, true, &crit_paths, nullptr, net_crit); diff --git a/common/timing_opt.cc b/common/timing_opt.cc index d1194876..950cbbbd 100644 --- a/common/timing_opt.cc +++ b/common/timing_opt.cc @@ -28,56 +28,60 @@ * and deal with the fact that not every cell on the crit path may be swappable. */ -#include "timing.h" #include "timing_opt.h" -#include "nextpnr.h" -#include "util.h" #include #include +#include "nextpnr.h" +#include "timing.h" +#include "util.h" namespace std { - template <> struct hash> +template <> struct hash> +{ + std::size_t + operator()(const std::pair &idp) const + noexcept { - std::size_t operator()(const std::pair &idp) const noexcept - { - std::size_t seed = 0; - boost::hash_combine(seed, hash()(idp.first)); - boost::hash_combine(seed, hash()(idp.second)); - return seed; - } - }; + std::size_t seed = 0; + boost::hash_combine(seed, hash()(idp.first)); + boost::hash_combine(seed, hash()(idp.second)); + return seed; + } +}; - template <> struct hash> +template <> struct hash> +{ + std::size_t operator()(const std::pair &idp) const noexcept { - std::size_t operator()(const std::pair &idp) const noexcept - { - std::size_t seed = 0; - boost::hash_combine(seed, hash()(idp.first)); - boost::hash_combine(seed, hash()(idp.second)); - return seed; - } - }; + std::size_t seed = 0; + boost::hash_combine(seed, hash()(idp.first)); + boost::hash_combine(seed, hash()(idp.second)); + return seed; + } +}; - template <> struct hash> +template <> struct hash> +{ + std::size_t + operator()(const std::pair &idp) const noexcept { - std::size_t operator()(const std::pair &idp) const noexcept - { - std::size_t seed = 0; - boost::hash_combine(seed, hash()(idp.first)); - boost::hash_combine(seed, hash()(idp.second)); - return seed; - } - }; -} + std::size_t seed = 0; + boost::hash_combine(seed, hash()(idp.first)); + boost::hash_combine(seed, hash()(idp.second)); + return seed; + } +}; +} // namespace std NEXTPNR_NAMESPACE_BEGIN class TimingOptimiser { public: - TimingOptimiser(Context *ctx, TimingOptCfg cfg) : ctx(ctx), cfg(cfg) {}; - bool optimise() { + TimingOptimiser(Context *ctx, TimingOptCfg cfg) : ctx(ctx), cfg(cfg){}; + bool optimise() + { log_info("Running timing-driven placement optimisation...\n"); #if 1 timing_analysis(ctx, false, true, false, false); @@ -100,13 +104,13 @@ class TimingOptimiser // Ratio of available to already-candidates to begin borrowing const float borrow_thresh = 0.2; - void setup_delay_limits() { + void setup_delay_limits() + { max_net_delay.clear(); for (auto net : sorted(ctx->nets)) { NetInfo *ni = net.second; for (auto usr : ni->users) { - max_net_delay[std::make_pair(usr.cell->name, usr.port)] - = std::numeric_limits::max(); + max_net_delay[std::make_pair(usr.cell->name, usr.port)] = std::numeric_limits::max(); } if (!net_crit.count(net.first)) continue; @@ -117,14 +121,15 @@ class TimingOptimiser auto &usr = ni->users.at(i); delay_t net_delay = ctx->getNetinfoRouteDelay(ni, usr); if (nc.max_path_length != 0) { - max_net_delay[std::make_pair(usr.cell->name, usr.port)] - = net_delay + ((nc.slack.at(i) - nc.cd_worst_slack) / nc.max_path_length); + max_net_delay[std::make_pair(usr.cell->name, usr.port)] = + net_delay + ((nc.slack.at(i) - nc.cd_worst_slack) / nc.max_path_length); } } } } - bool check_cell_delay_limits(CellInfo *cell) { + bool check_cell_delay_limits(CellInfo *cell) + { for (const auto &port : cell->ports) { int nc; if (ctx->getPortTimingClass(cell, port.first, nc) == TMG_IGNORE) @@ -137,7 +142,8 @@ class TimingOptimiser continue; BelId srcBel = net->driver.cell->bel; if (ctx->estimateDelay(ctx->getBelPinWire(srcBel, net->driver.port), - ctx->getBelPinWire(cell->bel, port.first)) > max_net_delay.at(std::make_pair(cell->name, port.first))) + ctx->getBelPinWire(cell->bel, port.first)) > + max_net_delay.at(std::make_pair(cell->name, port.first))) return false; } else if (port.second.type == PORT_OUT) { for (auto user : net->users) { @@ -146,7 +152,8 @@ class TimingOptimiser if (dstBel == BelId()) continue; if (ctx->estimateDelay(ctx->getBelPinWire(cell->bel, port.first), - ctx->getBelPinWire(dstBel, user.port)) > max_net_delay.at(std::make_pair(user.cell->name, user.port))) { + ctx->getBelPinWire(dstBel, user.port)) > + max_net_delay.at(std::make_pair(user.cell->name, user.port))) { #if 0 if (ctx->debug) { log_info(" est delay %.02fns exceeded maximum %.02fns\n", ctx->getDelayNS(ctx->estimateDelay(ctx->getBelPinWire(cell->bel, port.first), @@ -155,16 +162,15 @@ class TimingOptimiser } #endif return false; - } } } - } return true; } - BelId cell_swap_bel(CellInfo *cell, BelId newBel) { + BelId cell_swap_bel(CellInfo *cell, BelId newBel) + { BelId oldBel = cell->bel; CellInfo *other_cell = ctx->getBoundBelCell(newBel); NPNR_ASSERT(other_cell == nullptr || other_cell->belStrength <= STRENGTH_WEAK); @@ -179,7 +185,8 @@ class TimingOptimiser // Check that a series of moves are both legal and remain within maximum delay bounds // Moves are specified as a vector of pairs - bool acceptable_move(std::vector> &move, bool check_delays = true) { + bool acceptable_move(std::vector> &move, bool check_delays = true) + { for (auto &entry : move) { if (!ctx->isBelLocationValid(entry.first->bel)) return false; @@ -198,7 +205,8 @@ class TimingOptimiser return true; } - int find_neighbours(CellInfo *cell, IdString prev_cell, int d, bool allow_swap) { + int find_neighbours(CellInfo *cell, IdString prev_cell, int d, bool allow_swap) + { BelId curr = cell->bel; Loc curr_loc = ctx->getBelLocation(curr); int found_count = 0; @@ -217,7 +225,8 @@ class TimingOptimiser CellInfo *bound = ctx->getBoundBelCell(bel); if (bound == nullptr) { free_bels_at_loc.push_back(bel); - } else if (bound->belStrength <= STRENGTH_WEAK || bound->constr_parent != nullptr || !bound->constr_children.empty()) { + } else if (bound->belStrength <= STRENGTH_WEAK || bound->constr_parent != nullptr || + !bound->constr_children.empty()) { bound_bels_at_loc.push_back(bel); } } @@ -236,10 +245,11 @@ class TimingOptimiser } if (bel_candidate_cells.count(try_bel) && !allow_swap) { // Overlap is only allowed if it is with the previous cell (this is handled by removing those - // edges in the graph), or if allow_swap is true to deal with cases where overlap means few neighbours - // are identified - if (bel_candidate_cells.at(try_bel).size() > 1 || (bel_candidate_cells.at(try_bel).size() == 0 || - *(bel_candidate_cells.at(try_bel).begin()) != prev_cell)) + // edges in the graph), or if allow_swap is true to deal with cases where overlap means few + // neighbours are identified + if (bel_candidate_cells.at(try_bel).size() > 1 || + (bel_candidate_cells.at(try_bel).size() == 0 || + *(bel_candidate_cells.at(try_bel).begin()) != prev_cell)) continue; } // TODO: what else to check here? @@ -267,24 +277,23 @@ class TimingOptimiser return found_count; } - std::vector> find_crit_paths(float crit_thresh, size_t max_count) { - std::vector> crit_paths; + std::vector> find_crit_paths(float crit_thresh, size_t max_count) + { + std::vector> crit_paths; std::vector> crit_nets; std::vector netnames; std::transform(ctx->nets.begin(), ctx->nets.end(), std::back_inserter(netnames), - [](const std::pair> &kv){ - return kv.first; - }); + [](const std::pair> &kv) { return kv.first; }); ctx->sorted_shuffle(netnames); for (auto net : netnames) { if (crit_nets.size() >= max_count) break; if (!net_crit.count(net)) continue; - auto crit_user = std::max_element(net_crit[net].criticality.begin(), - net_crit[net].criticality.end()); + auto crit_user = std::max_element(net_crit[net].criticality.begin(), net_crit[net].criticality.end()); if (*crit_user > crit_thresh) - crit_nets.push_back(std::make_pair(ctx->nets[net].get(), crit_user - net_crit[net].criticality.begin())); + crit_nets.push_back( + std::make_pair(ctx->nets[net].get(), crit_user - net_crit[net].criticality.begin())); } auto port_user_index = [](CellInfo *cell, PortInfo &port) -> size_t { @@ -296,15 +305,15 @@ class TimingOptimiser } NPNR_ASSERT_FALSE("port user not found on net"); }; - std::unordered_set used_ports; + std::unordered_set used_ports; for (auto crit_net : crit_nets) { - std::deque crit_path; + std::deque crit_path; // FIXME: This will fail badly on combinational loops // Iterate backwards following greatest criticality - NetInfo* back_cursor = crit_net.first; + NetInfo *back_cursor = crit_net.first; while (back_cursor != nullptr) { float max_crit = 0; std::pair crit_sink{nullptr, 0}; @@ -372,7 +381,6 @@ class TimingOptimiser crit_sink = std::make_pair(pn, i); } } - } if (crit_sink.first != nullptr) { fwd_cursor = &(crit_sink.first->users.at(crit_sink.second)); @@ -382,7 +390,7 @@ class TimingOptimiser } } - std::vector crit_path_vec; + std::vector crit_path_vec; std::copy(crit_path.begin(), crit_path.end(), std::back_inserter(crit_path_vec)); crit_paths.push_back(crit_path_vec); } @@ -390,7 +398,8 @@ class TimingOptimiser return crit_paths; } - void optimise_path(std::vector &path) { + void optimise_path(std::vector &path) + { path_cells.clear(); cell_neighbour_bels.clear(); bel_candidate_cells.clear(); @@ -404,12 +413,13 @@ class TimingOptimiser for (size_t i = 0; i < pn->users.size(); i++) if (pn->users.at(i).cell == port->cell && pn->users.at(i).port == port->port) crit = net_crit.at(pn->name).criticality.at(i); - log_info(" %s.%s at %s crit %0.02f\n", port->cell->name.c_str(ctx), port->port.c_str(ctx), ctx->getBelName(port->cell->bel).c_str(ctx), crit); - + log_info(" %s.%s at %s crit %0.02f\n", port->cell->name.c_str(ctx), port->port.c_str(ctx), + ctx->getBelName(port->cell->bel).c_str(ctx), crit); } if (std::find(path_cells.begin(), path_cells.end(), port->cell->name) != path_cells.end()) continue; - if (port->cell->belStrength > STRENGTH_WEAK || !cfg.cellTypes.count(port->cell->type) || port->cell->constr_parent != nullptr || !port->cell->constr_children.empty()) + if (port->cell->belStrength > STRENGTH_WEAK || !cfg.cellTypes.count(port->cell->type) || + port->cell->constr_parent != nullptr || !port->cell->constr_children.empty()) continue; if (ctx->debug) log_info(" can move\n"); @@ -452,7 +462,7 @@ class TimingOptimiser // Swap for legality check CellInfo *cell = ctx->cells.at(path_cells.front()).get(); BelId origBel = cell_swap_bel(cell, startbel); - std::vector> move{std::make_pair(cell, origBel)}; + std::vector> move{std::make_pair(cell, origBel)}; if (acceptable_move(move)) { auto entry = std::make_pair(0, startbel); visit.push(entry); @@ -462,7 +472,7 @@ class TimingOptimiser cell_swap_bel(cell, origBel); } - while(!visit.empty()) { + while (!visit.empty()) { auto entry = visit.front(); visit.pop(); auto cellname = path_cells.at(entry.first); @@ -500,13 +510,14 @@ class TimingOptimiser // Check the new cumulative delay auto port_pair = cost_ports.at(ncname); - delay_t edge_delay = ctx->estimateDelay(ctx->getBelPinWire(port_pair.first->cell->bel, port_pair.first->port), - ctx->getBelPinWire(port_pair.second->cell->bel, port_pair.second->port)); + delay_t edge_delay = + ctx->estimateDelay(ctx->getBelPinWire(port_pair.first->cell->bel, port_pair.first->port), + ctx->getBelPinWire(port_pair.second->cell->bel, port_pair.second->port)); delay_t total_delay = cdelay + edge_delay; // First, check if the move is actually worthwhile from a delay point of view before the expensive // legality check - if (!cumul_costs.count(ncname) || !cumul_costs.at(ncname).count(neighbour) - || cumul_costs.at(ncname).at(neighbour) > total_delay) { + if (!cumul_costs.count(ncname) || !cumul_costs.at(ncname).count(neighbour) || + cumul_costs.at(ncname).at(neighbour) > total_delay) { // Now check that the swaps we have made to get here are legal and meet max delay requirements if (acceptable_move(move)) { cumul_costs[ncname][neighbour] = total_delay; @@ -531,10 +542,10 @@ class TimingOptimiser if (cumul_costs.count(path_cells.back())) { // Find the end position with the lowest total delay auto &end_options = cumul_costs.at(path_cells.back()); - auto lowest = std::min_element(end_options.begin(), end_options.end(), [](const std::pair &a, - const std::pair &b) { - return a.second < b.second; - }); + auto lowest = std::min_element(end_options.begin(), end_options.end(), + [](const std::pair &a, const std::pair &b) { + return a.second < b.second; + }); NPNR_ASSERT(lowest != end_options.end()); std::vector> route_to_solution; @@ -549,7 +560,7 @@ class TimingOptimiser for (auto rt_entry : boost::adaptors::reverse(route_to_solution)) { CellInfo *cell = ctx->cells.at(rt_entry.first).get(); cell_swap_bel(cell, rt_entry.second); - if(ctx->debug) + if (ctx->debug) log_info(" %s at %s\n", rt_entry.first.c_str(ctx), ctx->getBelName(rt_entry.second).c_str(ctx)); } @@ -571,7 +582,6 @@ class TimingOptimiser NetCriticalityMap net_crit; Context *ctx; TimingOptCfg cfg; - }; bool timing_opt(Context *ctx, TimingOptCfg cfg) { return TimingOptimiser(ctx, cfg).optimise(); } diff --git a/ice40/arch.cc b/ice40/arch.cc index 5cd55774..98e6d4c7 100644 --- a/ice40/arch.cc +++ b/ice40/arch.cc @@ -26,8 +26,8 @@ #include "nextpnr.h" #include "placer1.h" #include "router1.h" -#include "util.h" #include "timing_opt.h" +#include "util.h" NEXTPNR_NAMESPACE_BEGIN @@ -627,8 +627,9 @@ bool Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay // ----------------------------------------------------------------------- -bool Arch::place() { - if(!placer1(getCtx(), Placer1Cfg(getCtx()))) +bool Arch::place() +{ + if (!placer1(getCtx(), Placer1Cfg(getCtx()))) return false; TimingOptCfg tocfg(getCtx()); tocfg.cellTypes.insert(id_ICESTORM_LC); From 0f40e5fe8ce29bf55a943f7f0ff288a5e78dde6b Mon Sep 17 00:00:00 2001 From: David Shah Date: Sun, 2 Dec 2018 13:47:56 +0000 Subject: [PATCH 37/46] timing: Fixes to criticality calculation Signed-off-by: David Shah --- common/timing.cc | 16 ++++++++++++++++ common/timing_opt.cc | 12 +++++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/common/timing.cc b/common/timing.cc index e90718d8..000a36b7 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -599,6 +599,22 @@ struct Timing nc.cd_worst_slack = std::min(nc.cd_worst_slack, worst_slack.at(startdomain.first)); } } + + if (ctx->debug) { + for (auto &nc : *net_crit) { + NetInfo *net = ctx->nets.at(nc.first).get(); + log_info("Net %s maxlen %d worst_slack %.02fns: \n", nc.first.c_str(ctx), nc.second.max_path_length, + ctx->getDelayNS(nc.second.cd_worst_slack)); + if (!nc.second.criticality.empty() && !nc.second.slack.empty()) { + for (size_t i = 0; i < net->users.size(); i++) { + log_info(" user %s.%s slack %.02fns crit %.03f\n", net->users.at(i).cell->name.c_str(ctx), + net->users.at(i).port.c_str(ctx), ctx->getDelayNS(nc.second.slack.at(i)), + nc.second.criticality.at(i)); + } + } + log_break(); + } + } } return min_slack; } diff --git a/common/timing_opt.cc b/common/timing_opt.cc index 950cbbbd..300ca06f 100644 --- a/common/timing_opt.cc +++ b/common/timing_opt.cc @@ -90,7 +90,7 @@ class TimingOptimiser log_info(" Iteration %d...\n", i); get_criticalities(ctx, &net_crit); setup_delay_limits(); - auto crit_paths = find_crit_paths(0.98, 1000); + auto crit_paths = find_crit_paths(0.92, 1000); for (auto &path : crit_paths) optimise_path(path); #if 1 @@ -372,6 +372,9 @@ class TimingOptimiser TimingPortClass tpclass = ctx->getPortTimingClass(cell, port.first, ccount); if (tpclass != TMG_COMB_OUTPUT) continue; + bool is_path = ctx->getCellDelay(cell, fwd_cursor->port, port.first, combDelay); + if (!is_path) + continue; auto &crits = net_crit.at(pn->name).criticality; for (size_t i = 0; i < crits.size(); i++) { if (used_ports.count(&(pn->users.at(i)))) @@ -427,8 +430,11 @@ class TimingOptimiser } if (path_cells.size() < 3) { - log_info("Too few moveable cells; skipping path\n"); - log_break(); + if (ctx->debug) { + log_info("Too few moveable cells; skipping path\n"); + log_break(); + } + return; } IdString last_cell; From f3adf5a576a881e39cf78e599cbcd7ed3d3b8ec1 Mon Sep 17 00:00:00 2001 From: David Shah Date: Sun, 2 Dec 2018 14:08:11 +0000 Subject: [PATCH 38/46] timing_opt: Make an optional pass controlled by command line Signed-off-by: David Shah --- ice40/arch.cc | 11 ++++++++--- ice40/main.cc | 3 +++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/ice40/arch.cc b/ice40/arch.cc index 98e6d4c7..9dbc78bb 100644 --- a/ice40/arch.cc +++ b/ice40/arch.cc @@ -631,9 +631,14 @@ bool Arch::place() { if (!placer1(getCtx(), Placer1Cfg(getCtx()))) return false; - TimingOptCfg tocfg(getCtx()); - tocfg.cellTypes.insert(id_ICESTORM_LC); - return timing_opt(getCtx(), tocfg); + if(bool_or_default(settings, id("opt_timing"), false)) { + TimingOptCfg tocfg(getCtx()); + tocfg.cellTypes.insert(id_ICESTORM_LC); + return timing_opt(getCtx(), tocfg); + } else { + return true; + } + } bool Arch::route() { return router1(getCtx(), Router1Cfg(getCtx())); } diff --git a/ice40/main.cc b/ice40/main.cc index 4b6a9e42..543bd229 100644 --- a/ice40/main.cc +++ b/ice40/main.cc @@ -68,6 +68,7 @@ po::options_description Ice40CommandHandler::getArchOptions() specific.add_options()("promote-logic", "enable promotion of 'logic' globals (in addition to clk/ce/sr by default)"); specific.add_options()("no-promote-globals", "disable all global promotion"); + specific.add_options()("opt-timing", "run post-placement timing optimisation pass (experimental)"); specific.add_options()("tmfuzz", "run path delay estimate fuzzer"); return specific; } @@ -161,6 +162,8 @@ std::unique_ptr Ice40CommandHandler::createContext() ctx->settings[ctx->id("promote_logic")] = "1"; if (vm.count("no-promote-globals")) ctx->settings[ctx->id("no_promote_globals")] = "1"; + if (vm.count("opt-timing")) + ctx->settings[ctx->id("opt_timing")] = "1"; return ctx; } From a990a1576cc3b932ec784a0d9863f0ba9c337b0f Mon Sep 17 00:00:00 2001 From: David Shah Date: Sun, 2 Dec 2018 14:14:44 +0000 Subject: [PATCH 39/46] timing_opt: Fix criticality and cost calculations Signed-off-by: David Shah --- common/timing.cc | 13 +++++++++++++ common/timing_opt.cc | 40 +++++++++++++++++----------------------- 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/common/timing.cc b/common/timing.cc index 000a36b7..18caa989 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -531,6 +531,10 @@ struct Timing bool is_path = ctx->getCellDelay(drv.cell, port.first, drv.port, comb_delay); if (!is_path) continue; + int cc; + auto pclass = ctx->getPortTimingClass(drv.cell, port.first, cc); + if (pclass != TMG_COMB_INPUT) + continue; NetInfo *sink_net = port.second.net; if (net_data.count(sink_net) && net_data.at(sink_net).count(startdomain.first)) { auto &sink_nd = net_data.at(sink_net).at(startdomain.first); @@ -562,15 +566,24 @@ struct Timing auto &nc = (*net_crit)[net->name]; if (nc.slack.empty()) nc.slack.resize(net->users.size(), std::numeric_limits::max()); + if (ctx->debug) + log_info("Net %s cd %s\n", net->name.c_str(ctx), startdomain.first.clock.c_str(ctx)); for (size_t i = 0; i < net->users.size(); i++) { delay_t slack = nd.min_required.at(i) - (nd.max_arrival + ctx->getNetinfoRouteDelay(net, net->users.at(i))); + if (ctx->debug) + log_info(" user %s.%s required %.02fns arrival %.02f route %.02f slack %.02f\n", + net->users.at(i).cell->name.c_str(ctx), net->users.at(i).port.c_str(ctx), + ctx->getDelayNS(nd.min_required.at(i)), ctx->getDelayNS(nd.max_arrival), + ctx->getDelayNS(ctx->getNetinfoRouteDelay(net, net->users.at(i))), ctx->getDelayNS(slack)); if (worst_slack.count(startdomain.first)) worst_slack.at(startdomain.first) = std::min(worst_slack.at(startdomain.first), slack); else worst_slack[startdomain.first] = slack; nc.slack.at(i) = std::min(nc.slack.at(i), slack); } + if (ctx->debug) + log_break(); } } // Assign criticality values diff --git a/common/timing_opt.cc b/common/timing_opt.cc index 300ca06f..ed1618da 100644 --- a/common/timing_opt.cc +++ b/common/timing_opt.cc @@ -90,7 +90,7 @@ class TimingOptimiser log_info(" Iteration %d...\n", i); get_criticalities(ctx, &net_crit); setup_delay_limits(); - auto crit_paths = find_crit_paths(0.92, 1000); + auto crit_paths = find_crit_paths(0.95, 1000); for (auto &path : crit_paths) optimise_path(path); #if 1 @@ -438,25 +438,12 @@ class TimingOptimiser return; } IdString last_cell; - const int d = 3; // FIXME: how to best determine d + const int d = 4; // FIXME: how to best determine d for (auto cell : path_cells) { // FIXME: when should we allow swapping due to a lack of candidates find_neighbours(ctx->cells[cell].get(), last_cell, d, false); last_cell = cell; } - // Map cells that we will actually modify to the arc we will use for cost - // calculation - // for delay calc purposes - std::unordered_map> cost_ports; - PortRef *last_port = nullptr; - auto pcell = path_cells.begin(); - for (auto port : path) { - if (port->cell->name == *pcell) { - cost_ports[*pcell] = std::make_pair(last_port, port); - pcell++; - } - last_port = port; - } // Actual BFS path optimisation algorithm std::unordered_map> cumul_costs; @@ -501,8 +488,6 @@ class TimingOptimiser move.push_back(std::make_pair(cell, origBel)); } - delay_t cdelay = cumul_costs[cellname][entry.second]; - // Have a look at where we can travel from here for (auto neighbour : cell_neighbour_bels.at(path_cells.at(entry.first + 1))) { // Edges between overlapping bels are deleted @@ -514,12 +499,21 @@ class TimingOptimiser BelId origBel = cell_swap_bel(next_cell, neighbour); move.push_back(std::make_pair(next_cell, origBel)); - // Check the new cumulative delay - auto port_pair = cost_ports.at(ncname); - delay_t edge_delay = - ctx->estimateDelay(ctx->getBelPinWire(port_pair.first->cell->bel, port_pair.first->port), - ctx->getBelPinWire(port_pair.second->cell->bel, port_pair.second->port)); - delay_t total_delay = cdelay + edge_delay; + delay_t total_delay = 0; + + for (size_t i = 0; i < path.size(); i++) { + NetInfo *pn = path.at(i)->cell->ports.at(path.at(i)->port).net; + for (size_t j = 0; j < pn->users.size(); j++) { + auto & usr = pn->users.at(j); + if (usr.cell == path.at(i)->cell && usr.port == path.at(i)->port) { + total_delay += ctx->predictDelay(pn, usr); + break; + } + } + if (path.at(i)->cell == next_cell) + break; + } + // First, check if the move is actually worthwhile from a delay point of view before the expensive // legality check if (!cumul_costs.count(ncname) || !cumul_costs.at(ncname).count(neighbour) || From f53dc8d3c9735b4d9c50db1848de9dd3fefbe7ef Mon Sep 17 00:00:00 2001 From: David Shah Date: Sun, 2 Dec 2018 15:49:24 +0000 Subject: [PATCH 40/46] timing_opt: Improve heuristics Signed-off-by: David Shah --- common/timing.cc | 7 ++++- common/timing_opt.cc | 63 +++++++++++++++++++++++++------------ ice40/picorv32_benchmark.py | 2 +- 3 files changed, 50 insertions(+), 22 deletions(-) diff --git a/common/timing.cc b/common/timing.cc index 18caa989..6965307d 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -566,16 +566,20 @@ struct Timing auto &nc = (*net_crit)[net->name]; if (nc.slack.empty()) nc.slack.resize(net->users.size(), std::numeric_limits::max()); +#if 0 if (ctx->debug) log_info("Net %s cd %s\n", net->name.c_str(ctx), startdomain.first.clock.c_str(ctx)); +#endif for (size_t i = 0; i < net->users.size(); i++) { delay_t slack = nd.min_required.at(i) - (nd.max_arrival + ctx->getNetinfoRouteDelay(net, net->users.at(i))); +#if 0 if (ctx->debug) log_info(" user %s.%s required %.02fns arrival %.02f route %.02f slack %.02f\n", net->users.at(i).cell->name.c_str(ctx), net->users.at(i).port.c_str(ctx), ctx->getDelayNS(nd.min_required.at(i)), ctx->getDelayNS(nd.max_arrival), ctx->getDelayNS(ctx->getNetinfoRouteDelay(net, net->users.at(i))), ctx->getDelayNS(slack)); +#endif if (worst_slack.count(startdomain.first)) worst_slack.at(startdomain.first) = std::min(worst_slack.at(startdomain.first), slack); else @@ -612,7 +616,7 @@ struct Timing nc.cd_worst_slack = std::min(nc.cd_worst_slack, worst_slack.at(startdomain.first)); } } - +#if 0 if (ctx->debug) { for (auto &nc : *net_crit) { NetInfo *net = ctx->nets.at(nc.first).get(); @@ -628,6 +632,7 @@ struct Timing log_break(); } } +#endif } return min_slack; } diff --git a/common/timing_opt.cc b/common/timing_opt.cc index ed1618da..e8bb7d4f 100644 --- a/common/timing_opt.cc +++ b/common/timing_opt.cc @@ -90,7 +90,7 @@ class TimingOptimiser log_info(" Iteration %d...\n", i); get_criticalities(ctx, &net_crit); setup_delay_limits(); - auto crit_paths = find_crit_paths(0.95, 1000); + auto crit_paths = find_crit_paths(0.98, 1000); for (auto &path : crit_paths) optimise_path(path); #if 1 @@ -140,27 +140,23 @@ class TimingOptimiser if (port.second.type == PORT_IN) { if (net->driver.cell == nullptr || net->driver.cell->bel == BelId()) continue; - BelId srcBel = net->driver.cell->bel; - if (ctx->estimateDelay(ctx->getBelPinWire(srcBel, net->driver.port), - ctx->getBelPinWire(cell->bel, port.first)) > - max_net_delay.at(std::make_pair(cell->name, port.first))) - return false; + for (auto user : net->users) { + if (user.cell == cell && user.port == port.first) { + if (ctx->predictDelay(net, user) > + 1.1 * max_net_delay.at(std::make_pair(cell->name, port.first))) + return false; + } + } + } else if (port.second.type == PORT_OUT) { for (auto user : net->users) { // This could get expensive for high-fanout nets?? BelId dstBel = user.cell->bel; if (dstBel == BelId()) continue; - if (ctx->estimateDelay(ctx->getBelPinWire(cell->bel, port.first), - ctx->getBelPinWire(dstBel, user.port)) > - max_net_delay.at(std::make_pair(user.cell->name, user.port))) { -#if 0 - if (ctx->debug) { - log_info(" est delay %.02fns exceeded maximum %.02fns\n", ctx->getDelayNS(ctx->estimateDelay(ctx->getBelPinWire(cell->bel, port.first), - ctx->getBelPinWire(dstBel, user.port))), - ctx->getDelayNS(max_net_delay.at(std::make_pair(user.cell->name, user.port)))); - } -#endif + if (ctx->predictDelay(net, user) > + 1.1 * max_net_delay.at(std::make_pair(user.cell->name, user.port))) { + return false; } } @@ -370,7 +366,7 @@ class TimingOptimiser int ccount; DelayInfo combDelay; TimingPortClass tpclass = ctx->getPortTimingClass(cell, port.first, ccount); - if (tpclass != TMG_COMB_OUTPUT) + if (tpclass != TMG_COMB_OUTPUT && tpclass != TMG_REGISTER_OUTPUT) continue; bool is_path = ctx->getCellDelay(cell, fwd_cursor->port, port.first, combDelay); if (!is_path) @@ -408,6 +404,17 @@ class TimingOptimiser bel_candidate_cells.clear(); if (ctx->debug) log_info("Optimising the following path: \n"); + + auto front_port = path.front(); + NetInfo *front_net = front_port->cell->ports.at(front_port->port).net; + if (front_net != nullptr && front_net->driver.cell != nullptr) { + auto front_cell = front_net->driver.cell; + if (front_cell->belStrength <= STRENGTH_WEAK && cfg.cellTypes.count(front_cell->type) && + front_cell->constr_parent == nullptr && front_cell->constr_children.empty()) { + path_cells.push_back(front_cell->name); + } + } + for (auto port : path) { if (ctx->debug) { float crit = 0; @@ -429,7 +436,7 @@ class TimingOptimiser path_cells.push_back(port->cell->name); } - if (path_cells.size() < 3) { + if (path_cells.size() < 2) { if (ctx->debug) { log_info("Too few moveable cells; skipping path\n"); log_break(); @@ -437,8 +444,23 @@ class TimingOptimiser return; } + + // Calculate original delay before touching anything + delay_t original_delay = 0; + + for (size_t i = 0; i < path.size(); i++) { + NetInfo *pn = path.at(i)->cell->ports.at(path.at(i)->port).net; + for (size_t j = 0; j < pn->users.size(); j++) { + auto & usr = pn->users.at(j); + if (usr.cell == path.at(i)->cell && usr.port == path.at(i)->port) { + original_delay += ctx->predictDelay(pn, usr); + break; + } + } + } + IdString last_cell; - const int d = 4; // FIXME: how to best determine d + const int d = 3; // FIXME: how to best determine d for (auto cell : path_cells) { // FIXME: when should we allow swapping due to a lack of candidates find_neighbours(ctx->cells[cell].get(), last_cell, d, false); @@ -556,7 +578,8 @@ class TimingOptimiser route_to_solution.push_back(cursor); } if (ctx->debug) - log_info("Found a solution with cost %.02f ns\n", ctx->getDelayNS(lowest->second)); + log_info("Found a solution with cost %.02f ns (existing path %.02f ns)\n", ctx->getDelayNS(lowest->second), + ctx->getDelayNS(original_delay)); for (auto rt_entry : boost::adaptors::reverse(route_to_solution)) { CellInfo *cell = ctx->cells.at(rt_entry.first).get(); cell_swap_bel(cell, rt_entry.second); diff --git a/ice40/picorv32_benchmark.py b/ice40/picorv32_benchmark.py index a4ec581e..5e4fc2e1 100755 --- a/ice40/picorv32_benchmark.py +++ b/ice40/picorv32_benchmark.py @@ -22,7 +22,7 @@ for i in range(num_runs): ascfile = "picorv32_work/picorv32_s{}.asc".format(run) if path.exists(ascfile): os.remove(ascfile) - result = subprocess.run(["../nextpnr-ice40", "--hx8k", "--seed", str(run), "--json", "picorv32.json", "--asc", ascfile, "--freq", "70"], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL) + result = subprocess.run(["../nextpnr-ice40", "--hx8k", "--seed", str(run), "--json", "picorv32.json", "--asc", ascfile, "--freq", "40", "--opt-timing"], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL) if result.returncode != 0: print("Run {} failed!".format(run)) else: From 745960fa858b91dc27371971770a0abd8ca244dc Mon Sep 17 00:00:00 2001 From: David Shah Date: Sun, 2 Dec 2018 16:34:58 +0000 Subject: [PATCH 41/46] timing_opt: Neighbour related fixes Signed-off-by: David Shah --- common/timing_opt.cc | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/common/timing_opt.cc b/common/timing_opt.cc index e8bb7d4f..851a20d7 100644 --- a/common/timing_opt.cc +++ b/common/timing_opt.cc @@ -86,11 +86,11 @@ class TimingOptimiser #if 1 timing_analysis(ctx, false, true, false, false); #endif - for (int i = 0; i < 20; i++) { + for (int i = 0; i < 100; i++) { log_info(" Iteration %d...\n", i); get_criticalities(ctx, &net_crit); setup_delay_limits(); - auto crit_paths = find_crit_paths(0.98, 1000); + auto crit_paths = find_crit_paths(0.98, 50000); for (auto &path : crit_paths) optimise_path(path); #if 1 @@ -122,7 +122,7 @@ class TimingOptimiser delay_t net_delay = ctx->getNetinfoRouteDelay(ni, usr); if (nc.max_path_length != 0) { max_net_delay[std::make_pair(usr.cell->name, usr.port)] = - net_delay + ((nc.slack.at(i) - nc.cd_worst_slack) / nc.max_path_length); + net_delay + ((nc.slack.at(i) - nc.cd_worst_slack) / 10); } } } @@ -168,6 +168,8 @@ class TimingOptimiser BelId cell_swap_bel(CellInfo *cell, BelId newBel) { BelId oldBel = cell->bel; + if (oldBel == newBel) + return oldBel; CellInfo *other_cell = ctx->getBoundBelCell(newBel); NPNR_ASSERT(other_cell == nullptr || other_cell->belStrength <= STRENGTH_WEAK); ctx->unbindBel(oldBel); @@ -221,14 +223,14 @@ class TimingOptimiser CellInfo *bound = ctx->getBoundBelCell(bel); if (bound == nullptr) { free_bels_at_loc.push_back(bel); - } else if (bound->belStrength <= STRENGTH_WEAK || bound->constr_parent != nullptr || - !bound->constr_children.empty()) { + } else if (bound->belStrength <= STRENGTH_WEAK && bound->constr_parent == nullptr && + bound->constr_children.empty()) { bound_bels_at_loc.push_back(bel); } } BelId candidate; - while (!free_bels_at_loc.empty() && !bound_bels_at_loc.empty()) { + while (!free_bels_at_loc.empty() || !bound_bels_at_loc.empty()) { BelId try_bel; if (!free_bels_at_loc.empty()) { int try_idx = ctx->rng(int(free_bels_at_loc.size())); @@ -244,7 +246,7 @@ class TimingOptimiser // edges in the graph), or if allow_swap is true to deal with cases where overlap means few // neighbours are identified if (bel_candidate_cells.at(try_bel).size() > 1 || - (bel_candidate_cells.at(try_bel).size() == 0 || + (bel_candidate_cells.at(try_bel).size() == 1 && *(bel_candidate_cells.at(try_bel).begin()) != prev_cell)) continue; } @@ -304,6 +306,10 @@ class TimingOptimiser std::unordered_set used_ports; for (auto crit_net : crit_nets) { + + if (used_ports.count(&(crit_net.first->users.at(crit_net.second)))) + continue; + std::deque crit_path; // FIXME: This will fail badly on combinational loops @@ -460,13 +466,22 @@ class TimingOptimiser } IdString last_cell; - const int d = 3; // FIXME: how to best determine d + const int d = 5; // FIXME: how to best determine d for (auto cell : path_cells) { // FIXME: when should we allow swapping due to a lack of candidates find_neighbours(ctx->cells[cell].get(), last_cell, d, false); last_cell = cell; } + if (ctx->debug) { + for (auto cell : path_cells) { + log_info("Candidate neighbours for %s (%s):\n", cell.c_str(ctx), ctx->getBelName(ctx->cells[cell]->bel).c_str(ctx)); + for (auto neigh : cell_neighbour_bels.at(cell)) { + log_info(" %s\n", ctx->getBelName(neigh).c_str(ctx)); + } + } + } + // Actual BFS path optimisation algorithm std::unordered_map> cumul_costs; std::unordered_map, std::pair> backtrace; From 2b84b33cd697a1dfa46ed4bc231644177add2b83 Mon Sep 17 00:00:00 2001 From: David Shah Date: Sun, 2 Dec 2018 16:43:11 +0000 Subject: [PATCH 42/46] timing_opt: Reduce search diameter to 2 Signed-off-by: David Shah --- common/timing_opt.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/timing_opt.cc b/common/timing_opt.cc index 851a20d7..6aa120ae 100644 --- a/common/timing_opt.cc +++ b/common/timing_opt.cc @@ -466,7 +466,7 @@ class TimingOptimiser } IdString last_cell; - const int d = 5; // FIXME: how to best determine d + const int d = 2; // FIXME: how to best determine d for (auto cell : path_cells) { // FIXME: when should we allow swapping due to a lack of candidates find_neighbours(ctx->cells[cell].get(), last_cell, d, false); From 56dfd5564a2581bcb04a927cfc3161acae662064 Mon Sep 17 00:00:00 2001 From: David Shah Date: Wed, 5 Dec 2018 12:31:35 +0000 Subject: [PATCH 43/46] timing: Fix xclock crit calc and compiler warnings Signed-off-by: David Shah --- common/timing.cc | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/common/timing.cc b/common/timing.cc index 6965307d..b15327fb 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -96,8 +96,8 @@ struct Timing delay_t min_slack; CriticalPathMap *crit_path; DelayFrequency *slack_histogram; - IdString async_clock; NetCriticalityMap *net_crit; + IdString async_clock; struct TimingData { @@ -472,8 +472,6 @@ struct Timing continue; if (startdomain.first.clock == async_clock) continue; - const delay_t net_length_plus_one = nd.max_path_length + 1; - auto &net_min_remaining_budget = nd.min_remaining_budget; if (nd.min_required.empty()) nd.min_required.resize(net->users.size(), std::numeric_limits::max()); delay_t net_min_required = std::numeric_limits::max(); @@ -584,7 +582,7 @@ struct Timing worst_slack.at(startdomain.first) = std::min(worst_slack.at(startdomain.first), slack); else worst_slack[startdomain.first] = slack; - nc.slack.at(i) = std::min(nc.slack.at(i), slack); + nc.slack.at(i) = slack; } if (ctx->debug) log_break(); @@ -610,10 +608,10 @@ struct Timing delay_t dmax = crit_path->at(ClockPair{startdomain.first, startdomain.first}).path_delay; for (size_t i = 0; i < net->users.size(); i++) { float criticality = 1.0f - (float(nc.slack.at(i) - worst_slack.at(startdomain.first)) / dmax); - nc.criticality.at(i) = std::max(nc.criticality.at(i), criticality); + nc.criticality.at(i) = criticality; } - nc.max_path_length = std::max(nc.max_path_length, nd.max_path_length); - nc.cd_worst_slack = std::min(nc.cd_worst_slack, worst_slack.at(startdomain.first)); + nc.max_path_length = nd.max_path_length; + nc.cd_worst_slack = worst_slack.at(startdomain.first); } } #if 0 From b732e42fa312b83bee6c122d69e0a171afca779c Mon Sep 17 00:00:00 2001 From: David Shah Date: Thu, 6 Dec 2018 11:00:16 +0000 Subject: [PATCH 44/46] timing_opt: Reduce iterations to 30, tidy up logging Signed-off-by: David Shah --- common/timing_opt.cc | 28 ++++++++++++---------------- ice40/arch.cc | 3 +-- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/common/timing_opt.cc b/common/timing_opt.cc index 6aa120ae..a451bfa0 100644 --- a/common/timing_opt.cc +++ b/common/timing_opt.cc @@ -83,27 +83,22 @@ class TimingOptimiser bool optimise() { log_info("Running timing-driven placement optimisation...\n"); -#if 1 - timing_analysis(ctx, false, true, false, false); -#endif - for (int i = 0; i < 100; i++) { + if (ctx->verbose) + timing_analysis(ctx, false, true, false, false); + for (int i = 0; i < 30; i++) { log_info(" Iteration %d...\n", i); get_criticalities(ctx, &net_crit); setup_delay_limits(); auto crit_paths = find_crit_paths(0.98, 50000); for (auto &path : crit_paths) optimise_path(path); -#if 1 - timing_analysis(ctx, false, true, false, false); -#endif + if (ctx->verbose) + timing_analysis(ctx, false, true, false, false); } return true; } private: - // Ratio of available to already-candidates to begin borrowing - const float borrow_thresh = 0.2; - void setup_delay_limits() { max_net_delay.clear(); @@ -416,7 +411,7 @@ class TimingOptimiser if (front_net != nullptr && front_net->driver.cell != nullptr) { auto front_cell = front_net->driver.cell; if (front_cell->belStrength <= STRENGTH_WEAK && cfg.cellTypes.count(front_cell->type) && - front_cell->constr_parent == nullptr && front_cell->constr_children.empty()) { + front_cell->constr_parent == nullptr && front_cell->constr_children.empty()) { path_cells.push_back(front_cell->name); } } @@ -457,7 +452,7 @@ class TimingOptimiser for (size_t i = 0; i < path.size(); i++) { NetInfo *pn = path.at(i)->cell->ports.at(path.at(i)->port).net; for (size_t j = 0; j < pn->users.size(); j++) { - auto & usr = pn->users.at(j); + auto &usr = pn->users.at(j); if (usr.cell == path.at(i)->cell && usr.port == path.at(i)->port) { original_delay += ctx->predictDelay(pn, usr); break; @@ -475,7 +470,8 @@ class TimingOptimiser if (ctx->debug) { for (auto cell : path_cells) { - log_info("Candidate neighbours for %s (%s):\n", cell.c_str(ctx), ctx->getBelName(ctx->cells[cell]->bel).c_str(ctx)); + log_info("Candidate neighbours for %s (%s):\n", cell.c_str(ctx), + ctx->getBelName(ctx->cells[cell]->bel).c_str(ctx)); for (auto neigh : cell_neighbour_bels.at(cell)) { log_info(" %s\n", ctx->getBelName(neigh).c_str(ctx)); } @@ -541,7 +537,7 @@ class TimingOptimiser for (size_t i = 0; i < path.size(); i++) { NetInfo *pn = path.at(i)->cell->ports.at(path.at(i)->port).net; for (size_t j = 0; j < pn->users.size(); j++) { - auto & usr = pn->users.at(j); + auto &usr = pn->users.at(j); if (usr.cell == path.at(i)->cell && usr.port == path.at(i)->port) { total_delay += ctx->predictDelay(pn, usr); break; @@ -593,8 +589,8 @@ class TimingOptimiser route_to_solution.push_back(cursor); } if (ctx->debug) - log_info("Found a solution with cost %.02f ns (existing path %.02f ns)\n", ctx->getDelayNS(lowest->second), - ctx->getDelayNS(original_delay)); + log_info("Found a solution with cost %.02f ns (existing path %.02f ns)\n", + ctx->getDelayNS(lowest->second), ctx->getDelayNS(original_delay)); for (auto rt_entry : boost::adaptors::reverse(route_to_solution)) { CellInfo *cell = ctx->cells.at(rt_entry.first).get(); cell_swap_bel(cell, rt_entry.second); diff --git a/ice40/arch.cc b/ice40/arch.cc index 9dbc78bb..8f52987c 100644 --- a/ice40/arch.cc +++ b/ice40/arch.cc @@ -631,14 +631,13 @@ bool Arch::place() { if (!placer1(getCtx(), Placer1Cfg(getCtx()))) return false; - if(bool_or_default(settings, id("opt_timing"), false)) { + if (bool_or_default(settings, id("opt_timing"), false)) { TimingOptCfg tocfg(getCtx()); tocfg.cellTypes.insert(id_ICESTORM_LC); return timing_opt(getCtx(), tocfg); } else { return true; } - } bool Arch::route() { return router1(getCtx(), Router1Cfg(getCtx())); } From e7fc42ac84d70b2ac09f4e0f8666504f75de89e2 Mon Sep 17 00:00:00 2001 From: David Shah Date: Thu, 6 Dec 2018 11:19:48 +0000 Subject: [PATCH 45/46] ice40: Improve bitstream error handling Fixes #161 and provides a clearer error for #170 Signed-off-by: David Shah --- ice40/bitstream.cc | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/ice40/bitstream.cc b/ice40/bitstream.cc index 83664169..d1f51676 100644 --- a/ice40/bitstream.cc +++ b/ice40/bitstream.cc @@ -156,7 +156,9 @@ void configure_extra_cell(chipconfig_t &config, const Context *ctx, CellInfo *ce // Lattice's weird string style params, not sure if // prefixes other than 0b should be supported, only 0b features in docs std::string raw = get_param_str_or_def(cell, ctx->id(p.first), "0b0"); - assert(raw.substr(0, 2) == "0b"); + if (raw.substr(0, 2) != "0b") + log_error("expected configuration string starting with '0b' for parameter '%s' on cell '%s'\n", + p.first.c_str(), cell->name.c_str(ctx)); raw = raw.substr(2); value.resize(raw.length()); for (int i = 0; i < (int)raw.length(); i++) { @@ -168,7 +170,13 @@ void configure_extra_cell(chipconfig_t &config, const Context *ctx, CellInfo *ce } } } else { - int ival = get_param_or_def(cell, ctx->id(p.first), 0); + int ival; + try { + ival = get_param_or_def(cell, ctx->id(p.first), 0); + } catch (std::invalid_argument &e) { + log_error("expected numeric value for parameter '%s' on cell '%s'\n", p.first.c_str(), + cell->name.c_str(ctx)); + } for (int i = 0; i < p.second; i++) value.push_back((ival >> i) & 0x1); From 144363693db2649d6c9bcd3a73e6e4f381c67b22 Mon Sep 17 00:00:00 2001 From: David Shah Date: Thu, 6 Dec 2018 11:29:33 +0000 Subject: [PATCH 46/46] ice40: Report error for unsupported PLL FEEDBACK_PATH values Signed-off-by: David Shah --- ice40/pack.cc | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/ice40/pack.cc b/ice40/pack.cc index e71db46f..242f8ceb 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -1083,13 +1083,17 @@ static void pack_special(Context *ctx) } auto feedback_path = packed->params[ctx->id("FEEDBACK_PATH")]; - packed->params[ctx->id("FEEDBACK_PATH")] = - feedback_path == "DELAY" - ? "0" - : feedback_path == "SIMPLE" ? "1" - : feedback_path == "PHASE_AND_DELAY" - ? "2" - : feedback_path == "EXTERNAL" ? "6" : feedback_path; + std::string fbp_value = feedback_path == "DELAY" + ? "0" + : feedback_path == "SIMPLE" + ? "1" + : feedback_path == "PHASE_AND_DELAY" + ? "2" + : feedback_path == "EXTERNAL" ? "6" : feedback_path; + if (!std::all_of(fbp_value.begin(), fbp_value.end(), isdigit)) + log_error("PLL '%s' has unsupported FEEDBACK_PATH value '%s'\n", ci->name.c_str(ctx), + feedback_path.c_str()); + packed->params[ctx->id("FEEDBACK_PATH")] = fbp_value; packed->params[ctx->id("PLLTYPE")] = std::to_string(sb_pll40_type(ctx, ci)); NetInfo *pad_packagepin_net = nullptr;