diff options
Diffstat (limited to 'ice40/pack.cc')
-rw-r--r-- | ice40/pack.cc | 172 |
1 files changed, 172 insertions, 0 deletions
diff --git a/ice40/pack.cc b/ice40/pack.cc index 7e2e389c..b4f711f3 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -3,6 +3,7 @@ * * Copyright (C) 2018 Clifford Wolf <clifford@symbioticeda.com> * Copyright (C) 2018 David Shah <david@symbioticeda.com> + * Copyright (C) 2018 Serge Bazanski <q3k@symbioticeda.com> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -311,6 +312,10 @@ static void set_net_constant(const Context *ctx, NetInfo *orig, NetInfo *constne (user.port != ctx->id("CLK") && ((constval && user.port == ctx->id("CE")) || (!constval && user.port != ctx->id("CE"))))) { uc->ports[user.port].net = nullptr; + } else if (is_ram(ctx, uc) && !constval && user.port != ctx->id("RCLK") && user.port != ctx->id("RCLKN") && + user.port != ctx->id("WCLK") && user.port != ctx->id("WCLKN") && user.port != ctx->id("RCLKE") && + user.port != ctx->id("WCLKE")) { + uc->ports[user.port].net = nullptr; } else { uc->ports[user.port].net = constnet; constnet->users.push_back(user); @@ -536,6 +541,56 @@ static void promote_globals(Context *ctx) } } +// 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<CellInfo> spliceLUT(Context *ctx, CellInfo *ci, IdString portId, bool onlyNonLUTs) +{ + auto port = ci->ports[portId]; + + NPNR_ASSERT(port.net != nullptr); + + // Create pass-through LUT. + std::unique_ptr<CellInfo> pt = + create_ice_cell(ctx, ctx->id("ICESTORM_LC"), ci->name.str(ctx) + "$nextpnr_ice40_pack_pll_lc"); + pt->params[ctx->id("LUT_INIT")] = "255"; // output is always I3 + + // Create LUT output net. + std::unique_ptr<NetInfo> out_net = std::unique_ptr<NetInfo>(new NetInfo); + out_net->name = ctx->id(ci->name.str(ctx) + "$nextnr_ice40_pack_pll_net"); + out_net->driver.cell = pt.get(); + out_net->driver.port = ctx->id("O"); + pt->ports.at(ctx->id("O")).net = out_net.get(); + + // New users of the original cell's port + std::vector<PortRef> new_users; + for (const auto &user : port.net->users) { + if (onlyNonLUTs && user.cell->type == ctx->id("ICESTORM_LC")) { + new_users.push_back(user); + continue; + } + // Rewrite pointer into net in user. + user.cell->ports[user.port].net = out_net.get(); + // Add user to net. + PortRef pr; + pr.cell = user.cell; + pr.port = user.port; + out_net->users.push_back(pr); + } + + // Add LUT to new users. + PortRef pr; + pr.cell = pt.get(); + pr.port = ctx->id("I3"); + new_users.push_back(pr); + pt->ports.at(ctx->id("I3")).net = port.net; + + // Replace users of the original net. + port.net->users = new_users; + + ctx->nets[out_net->name] = std::move(out_net); + return pt; +} + // Pack special functions static void pack_special(Context *ctx) { @@ -591,6 +646,123 @@ static void pack_special(Context *ctx) replace_port(ci, ctx->id(pi.name.c_str(ctx)), packed.get(), ctx->id(newname)); } new_cells.push_back(std::move(packed)); + } else if (is_sb_pll40(ctx, ci)) { + std::unique_ptr<CellInfo> packed = + create_ice_cell(ctx, ctx->id("ICESTORM_PLL"), ci->name.str(ctx) + "_PLL"); + packed_cells.insert(ci->name); + + if (is_sb_pll40_pad(ctx, ci)) { + // TODO(q3k): Implement these after checking their behaviour on + // a board with exposed 'clock pads'. + log_error("SB_PLL40_*_PAD cells are not supported yet.\n"); + } + + for (auto attr : ci->attrs) + packed->attrs[attr.first] = attr.second; + for (auto param : ci->params) + packed->params[param.first] = param.second; + + 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; + packed->params[ctx->id("PLLTYPE")] = std::to_string(sb_pll40_type(ctx, ci)); + + for (auto port : ci->ports) { + PortInfo &pi = port.second; + std::string newname = pi.name.str(ctx); + size_t bpos = newname.find('['); + if (bpos != std::string::npos) { + newname = newname.substr(0, bpos) + "_" + newname.substr(bpos + 1, (newname.size() - bpos) - 2); + } + if (pi.name == ctx->id("PLLOUTCOREA")) + newname = "PLLOUT_A"; + if (pi.name == ctx->id("PLLOUTCOREB")) + newname = "PLLOUT_B"; + if (pi.name == ctx->id("PLLOUTCORE")) + newname = "PLLOUT_A"; + 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) != TYPE_ICESTORM_PLL) + continue; + log_info(" constrained '%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; + } + if (!constrained) { + log_error(" could not constrain '%s' to any PLL Bel\n", packed->name.c_str(ctx)); + } + } + + // The LOCK signal on iCE40 PLLs goes through the neigh_op_bnl_1 wire. + // In practice, this means the LOCK signal can only directly reach LUT + // inputs. + // If we have a net connected to LOCK, make sure it only drives LUTs. + auto port = packed->ports[ctx->id("LOCK")]; + if (port.net != nullptr) { + bool found_lut = false; + bool all_luts = true; + unsigned int lut_count = 0; + for (const auto &user : port.net->users) { + NPNR_ASSERT(user.cell != nullptr); + if (user.cell->type == ctx->id("ICESTORM_LC")) { + found_lut = true; + lut_count++; + } else { + all_luts = false; + } + } + + if (found_lut && all_luts) { + // Every user is a LUT, carry on now. + } else if (found_lut && !all_luts && lut_count < 8) { + // Strategy: create a pass-through LUT, move all non-LUT users behind it. + log_info(" LUT strategy for %s: move non-LUT users to new LUT\n", port.name.c_str(ctx)); + auto pt = spliceLUT(ctx, packed.get(), port.name, true); + new_cells.push_back(std::move(pt)); + } else { + // Strategy: create a pass-through LUT, move every user behind it. + log_info(" LUT strategy for %s: move all users to new LUT\n", port.name.c_str(ctx)); + auto pt = spliceLUT(ctx, packed.get(), port.name, false); + new_cells.push_back(std::move(pt)); + } + + // Find wire that will be driven by this port. + const auto pll_out_wire = ctx->getBelPinWire(pll_bel, ctx->portPinFromId(port.name)); + NPNR_ASSERT(pll_out_wire.index != -1); + + // Now, constrain all LUTs on the output of the signal to be at + // the correct Bel relative to the PLL Bel. + int x = ctx->chip_info->wire_data[pll_out_wire.index].x; + int y = ctx->chip_info->wire_data[pll_out_wire.index].y; + int z = 0; + for (const auto &user : port.net->users) { + NPNR_ASSERT(user.cell != nullptr); + NPNR_ASSERT(user.cell->type == ctx->id("ICESTORM_LC")); + + // TODO(q3k): handle when the Bel might be already the + // target of another constraint. + NPNR_ASSERT(z < 8); + auto target_bel = ctx->getBelByLocation(Loc(x, y, z++)); + auto target_bel_name = ctx->getBelName(target_bel).str(ctx); + user.cell->attrs[ctx->id("BEL")] = target_bel_name; + log_info(" constrained '%s' to %s\n", user.cell->name.c_str(ctx), target_bel_name.c_str()); + } + } + + new_cells.push_back(std::move(packed)); } } |