Merge branch 'q3k/pll' into 'master'

ice40: support CORE PLLs

See merge request SymbioticEDA/nextpnr!17
This commit is contained in:
Serge Bazanski 2018-07-24 15:01:12 +00:00
commit 62bcda87bd
9 changed files with 375 additions and 14 deletions

View File

@ -2,6 +2,7 @@
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 Clifford Wolf <clifford@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
@ -314,11 +315,12 @@ WireId Arch::getBelPinWire(BelId bel, PortPin pin) const
int num_bel_wires = chip_info->bel_data[bel.index].num_bel_wires;
const BelWirePOD *bel_wires = chip_info->bel_data[bel.index].bel_wires.get();
for (int i = 0; i < num_bel_wires; i++)
for (int i = 0; i < num_bel_wires; i++) {
if (bel_wires[i].port == pin) {
ret.index = bel_wires[i].wire_index;
break;
}
}
return ret;
}

View File

@ -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
@ -107,6 +108,32 @@ bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const
bel_cells.push_back(cell);
return logicCellsCompatible(bel_cells);
} else if (cell->type == id_sb_io) {
// Do not allow placement of input SB_IOs on blocks where there a PLL is outputting to.
// Find shared PLL by looking for driving bel siblings from D_IN_0
// that are a PLL clock output.
auto wire = getBelPinWire(bel, PIN_D_IN_0);
PortPin pll_bel_pin;
BelId pll_bel;
for (auto pin : getWireBelPins(wire)) {
if (pin.pin == PIN_PLLOUT_A || pin.pin == PIN_PLLOUT_B) {
pll_bel = pin.bel;
pll_bel_pin = pin.pin;
break;
}
}
// Is there a PLL that shares this IO buffer?
if (pll_bel.index != -1) {
// Is a PLL placed in this PLL bel?
if (!checkBelAvail(pll_bel)) {
// Is the shared port driving a net?
auto pll_cell = getBoundBelCell(pll_bel);
auto pi = cells.at(pll_cell)->ports[portPinToId(pll_bel_pin)];
if (pi.net != nullptr) {
return false;
}
}
}
return getBelPackagePin(bel) != "";
} else if (cell->type == id_sb_gb) {
NPNR_ASSERT(cell->ports.at(id_glb_buf_out).net != nullptr);

View File

@ -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
@ -74,14 +75,26 @@ void set_config(const TileInfoPOD &ti, std::vector<std::vector<int8_t>> &tile_cf
for (int i = 0; i < cfg.num_bits; i++) {
int8_t &cbit = tile_cfg.at(cfg.bits[i].row).at(cfg.bits[i].col);
if (cbit && !value)
log_error("clearing already set config bit %s", name.c_str());
log_error("clearing already set config bit %s\n", name.c_str());
cbit = value;
}
} else {
int8_t &cbit = tile_cfg.at(cfg.bits[index].row).at(cfg.bits[index].col);
cbit = value;
if (cbit && !value)
log_error("clearing already set config bit %s[%d]", name.c_str(), index);
log_error("clearing already set config bit %s[%d]\n", name.c_str(), index);
}
}
// Set an IE_{EN,REN} logical bit in a tile config. Logical means enabled.
// On {HX,LP}1K devices these bits are active low, so we need to invert them.
void set_ie_bit_logical(const Context *ctx, const TileInfoPOD &ti, std::vector<std::vector<int8_t>> &tile_cfg,
const std::string &name, bool value)
{
if (ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K) {
set_config(ti, tile_cfg, name, !value);
} else {
set_config(ti, tile_cfg, name, value);
}
}
@ -117,7 +130,7 @@ static const BelConfigPOD &get_ec_config(const ChipInfoPOD *chip, BelId bel)
typedef std::vector<std::vector<std::vector<std::vector<int8_t>>>> chipconfig_t;
static void set_ec_cbit(chipconfig_t &config, const Context *ctx, const BelConfigPOD &cell_cbits, std::string name,
bool value)
bool value, std::string prefix)
{
const ChipInfoPOD *chip = ctx->chip_info;
@ -125,7 +138,7 @@ static void set_ec_cbit(chipconfig_t &config, const Context *ctx, const BelConfi
const auto &cbit = cell_cbits.entries[i];
if (cbit.entry_name.get() == name) {
const auto &ti = chip->bits_info->tiles_nonrouting[tile_at(ctx, cbit.x, cbit.y)];
set_config(ti, config.at(cbit.y).at(cbit.x), std::string("IpConfig.") + cbit.cbit_name.get(), value);
set_config(ti, config.at(cbit.y).at(cbit.x), prefix + cbit.cbit_name.get(), value);
return;
}
}
@ -133,7 +146,7 @@ static void set_ec_cbit(chipconfig_t &config, const Context *ctx, const BelConfi
}
void configure_extra_cell(chipconfig_t &config, const Context *ctx, CellInfo *cell,
const std::vector<std::pair<std::string, int>> &params, bool string_style)
const std::vector<std::pair<std::string, int>> &params, bool string_style, std::string prefix)
{
const ChipInfoPOD *chip = ctx->chip_info;
const auto &bc = get_ec_config(chip, cell->bel);
@ -163,10 +176,10 @@ void configure_extra_cell(chipconfig_t &config, const Context *ctx, CellInfo *ce
value.resize(p.second);
if (p.second == 1) {
set_ec_cbit(config, ctx, bc, p.first, value.at(0));
set_ec_cbit(config, ctx, bc, p.first, value.at(0), prefix);
} else {
for (int i = 0; i < p.second; i++) {
set_ec_cbit(config, ctx, bc, p.first + "_" + std::to_string(i), value.at(i));
set_ec_cbit(config, ctx, bc, p.first + "_" + std::to_string(i), value.at(i), prefix);
}
}
}
@ -258,8 +271,13 @@ void write_asc(const Context *ctx, std::ostream &out)
}
}
}
std::unordered_set<Loc> sb_io_used_by_pll;
std::unordered_set<Loc> sb_io_used_by_io;
// Set logic cell config
for (auto &cell : ctx->cells) {
BelId bel = cell.second.get()->bel;
if (bel == BelId()) {
std::cout << "Found unplaced cell " << cell.first.str(ctx) << " while generating bitstream!" << std::endl;
@ -304,6 +322,7 @@ void write_asc(const Context *ctx, std::ostream &out)
} else if (cell.second->type == ctx->id("SB_IO")) {
const BelInfoPOD &beli = ci.bel_data[bel.index];
int x = beli.x, y = beli.y, z = beli.z;
sb_io_used_by_io.insert(Loc(x, y, z));
const TileInfoPOD &ti = bi.tiles_nonrouting[TILE_IO];
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"));
@ -405,7 +424,70 @@ void write_asc(const Context *ctx, std::ostream &out)
{"MODE_8x8", 1},
{"A_SIGNED", 1},
{"B_SIGNED", 1}};
configure_extra_cell(config, ctx, cell.second.get(), mac16_params, false);
configure_extra_cell(config, ctx, cell.second.get(), mac16_params, false, std::string("IpConfig."));
} else if (cell.second->type == ctx->id("ICESTORM_PLL")) {
const std::vector<std::pair<std::string, int>> pll_params = {{"DELAY_ADJMODE_FB", 1},
{"DELAY_ADJMODE_REL", 1},
{"DIVF", 7},
{"DIVQ", 3},
{"DIVR", 4},
{"FDA_FEEDBACK", 4},
{"FDA_RELATIVE", 4},
{"FEEDBACK_PATH", 3},
{"FILTER_RANGE", 3},
{"PLLOUT_SELECT_A", 2},
{"PLLOUT_SELECT_B", 2},
{"PLLTYPE", 3},
{"SHIFTREG_DIV_MODE", 1},
{"TEST_MODE", 1}};
configure_extra_cell(config, ctx, cell.second.get(), pll_params, false, std::string("PLL."));
// Configure the SB_IOs that the clock outputs are going through.
for (auto &port : cell.second->ports) {
// If this port is not a PLLOUT port, ignore it.
if (port.second.name != ctx->id("PLLOUT_A") && port.second.name != ctx->id("PLLOUT_B"))
continue;
// If the output is not driving any net, ignore it.
if (port.second.net == nullptr)
continue;
// Get IO Bel that this PLL port goes through by finding sibling
// Bel driving the same wire via PIN_D_IN_0.
auto wire = ctx->getBelPinWire(cell.second->bel, ctx->portPinFromId(port.second.name));
BelId io_bel;
for (auto pin : ctx->getWireBelPins(wire)) {
if (pin.pin == PIN_D_IN_0) {
io_bel = pin.bel;
break;
}
}
NPNR_ASSERT(io_bel.index != -1);
auto io_bel_loc = ctx->getBelLocation(io_bel);
// Check that this SB_IO is either unused or just used as an output.
if (sb_io_used_by_io.count(io_bel_loc)) {
log_error("SB_IO '%s' already in use, cannot route PLL through\n", ctx->getBelName(bel).c_str(ctx));
}
sb_io_used_by_pll.insert(io_bel_loc);
// Get IE/REN config location (cf. http://www.clifford.at/icestorm/io_tile.html)
auto ieren = get_ieren(bi, io_bel_loc.x, io_bel_loc.y, io_bel_loc.z);
int iex, iey, iez;
std::tie(iex, iey, iez) = ieren;
NPNR_ASSERT(iez != -1);
// Write config.
const TileInfoPOD &ti = bi.tiles_nonrouting[TILE_IO];
// Enable input buffer and disable pull-up resistor in block
// (this is used by the PLL).
set_ie_bit_logical(ctx, ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), true);
set_ie_bit_logical(ctx, ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), false);
// PINTYPE[0] passes the PLL through to the fabric.
set_config(ti, config.at(io_bel_loc.y).at(io_bel_loc.x),
"IOB_" + std::to_string(io_bel_loc.z) + ".PINTYPE_0", true);
}
} else {
NPNR_ASSERT(false);
}
@ -416,14 +498,16 @@ void write_asc(const Context *ctx, std::ostream &out)
const TileInfoPOD &ti = bi.tiles_nonrouting[TILE_IO];
const BelInfoPOD &beli = ci.bel_data[bel.index];
int x = beli.x, y = beli.y, z = beli.z;
if (sb_io_used_by_pll.count(Loc(x, y, z))) {
continue;
}
auto ieren = get_ieren(bi, x, y, z);
int iex, iey, iez;
std::tie(iex, iey, iez) = ieren;
if (iez != -1) {
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), false);
}
set_ie_bit_logical(ctx, ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), true);
set_ie_bit_logical(ctx, ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), false);
}
} else if (ctx->bel_to_cell[bel.index] == IdString() && ctx->getBelType(bel) == TYPE_ICESTORM_RAM) {
const BelInfoPOD &beli = ci.bel_data[bel.index];

View File

@ -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
@ -207,6 +208,40 @@ std::unique_ptr<CellInfo> create_ice_cell(Context *ctx, IdString type, std::stri
add_port(ctx, new_cell.get(), "ACCUMCO", PORT_OUT);
add_port(ctx, new_cell.get(), "SIGNEXTOUT", PORT_OUT);
} else if (type == ctx->id("ICESTORM_PLL")) {
new_cell->params[ctx->id("DELAY_ADJMODE_FB")] = "0";
new_cell->params[ctx->id("DELAY_ADJMODE_REL")] = "0";
new_cell->params[ctx->id("DIVF")] = "0";
new_cell->params[ctx->id("DIVQ")] = "0";
new_cell->params[ctx->id("DIVR")] = "0";
new_cell->params[ctx->id("FDA_FEEDBACK")] = "0";
new_cell->params[ctx->id("FDA_RELATIVE")] = "0";
new_cell->params[ctx->id("FEEDBACK_PATH")] = "0";
new_cell->params[ctx->id("FILTER_RANGE")] = "0";
new_cell->params[ctx->id("PLLOUT_SELECT_A")] = "0";
new_cell->params[ctx->id("PLLOUT_SELECT_B")] = "0";
new_cell->params[ctx->id("PLLTYPE")] = "0";
new_cell->params[ctx->id("SHIFTREG_DIVMODE")] = "0";
new_cell->params[ctx->id("TEST_MODE")] = "0";
add_port(ctx, new_cell.get(), "BYPASS", PORT_IN);
add_port(ctx, new_cell.get(), "DYNAMICDELAY", PORT_IN);
add_port(ctx, new_cell.get(), "EXTFEEDBACK", PORT_IN);
add_port(ctx, new_cell.get(), "LATCHINPUTVALUE", PORT_IN);
add_port(ctx, new_cell.get(), "REFERENCECLK", PORT_IN);
add_port(ctx, new_cell.get(), "RESETB", PORT_IN);
add_port(ctx, new_cell.get(), "SCLK", PORT_IN);
add_port(ctx, new_cell.get(), "SDI", PORT_IN);
add_port(ctx, new_cell.get(), "SDI", PORT_OUT);
add_port(ctx, new_cell.get(), "LOCK", PORT_OUT);
add_port(ctx, new_cell.get(), "PLLOUT_A", PORT_OUT);
add_port(ctx, new_cell.get(), "PLLOUT_B", PORT_OUT);
} else {
log_error("unable to create iCE40 cell of type %s", type.c_str(ctx));
}
@ -312,6 +347,21 @@ void nxio_to_sb(Context *ctx, CellInfo *nxio, CellInfo *sbio)
}
}
uint8_t sb_pll40_type(const BaseCtx *ctx, const CellInfo *cell)
{
if (cell->type == ctx->id("SB_PLL40_PAD"))
return 2;
if (cell->type == ctx->id("SB_PLL40_2_PAD"))
return 4;
if (cell->type == ctx->id("SB_PLL40_2F_PAD"))
return 5;
if (cell->type == ctx->id("SB_PLL40_CORE"))
return 3;
if (cell->type == ctx->id("SB_PLL40_2F_CORE"))
return 7;
NPNR_ASSERT(0);
}
bool is_clock_port(const BaseCtx *ctx, const PortRef &port)
{
if (port.cell == nullptr)

View File

@ -71,6 +71,21 @@ inline bool is_sb_spram(const BaseCtx *ctx, const CellInfo *cell) { return cell-
inline bool is_sb_mac16(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_MAC16"); }
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") ||
cell->type == ctx->id("SB_PLL40_2F_PAD") || cell->type == ctx->id("SB_PLL40_CORE") ||
cell->type == ctx->id("SB_PLL40_2F_CORE");
}
inline bool is_sb_pll40_pad(const BaseCtx *ctx, const CellInfo *cell)
{
return cell->type == ctx->id("SB_PLL40_PAD") || cell->type == ctx->id("SB_PLL40_2_PAD") ||
cell->type == ctx->id("SB_PLL40_2F_PAD");
}
uint8_t sb_pll40_type(const BaseCtx *ctx, const CellInfo *cell);
// Convert a SB_LUT primitive to (part of) an ICESTORM_LC, swapping ports
// as needed. Set no_dff if a DFF is not being used, so that the output
// can be reconnected

View File

@ -182,6 +182,8 @@ def wire_type(name):
wt = "LOCAL"
elif name in ("WCLK", "WCLKE", "WE", "RCLK", "RCLKE", "RE"):
wt = "LOCAL"
elif name in ("PLLOUT_A", "PLLOUT_B"):
wt = "LOCAL"
if wt is None:
print("No type for wire: %s (%s)" % (longname, name), file=sys.stderr)
@ -584,6 +586,9 @@ def is_ec_output(ec_entry):
if "glb_netwk_" in wirename: return True
return False
def is_ec_pll_clock_output(ec, ec_entry):
return ec[0] == 'PLL' and ec_entry[0] in ('PLLOUT_A', 'PLLOUT_B')
def add_bel_ec(ec):
ectype, x, y, z = ec
bel = len(bel_name)
@ -598,6 +603,10 @@ def add_bel_ec(ec):
add_bel_output(bel, wire_names[entry[1]], entry[0])
else:
add_bel_input(bel, wire_names[entry[1]], entry[0])
elif is_ec_pll_clock_output(ec, entry):
x, y, z = entry[1]
z = 'io_{}/D_IN_0'.format(z)
add_bel_output(bel, wire_names[(x, y, z)], entry[0])
else:
extra_cell_config[bel].append(entry)

View File

@ -464,7 +464,11 @@ enum GfxTileWireId
TILE_WIRE_SP12_H_R_23,
TILE_WIRE_SP12_H_L_22,
TILE_WIRE_SP12_H_L_23
TILE_WIRE_SP12_H_L_23,
TILE_WIRE_PLLIN,
TILE_WIRE_PLLOUT_A,
TILE_WIRE_PLLOUT_B
};
void gfxTileWire(std::vector<GraphicElement> &g, int x, int y, GfxTileWireId id, GraphicElement::style_t style);

View File

@ -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
@ -540,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)
{
@ -594,6 +645,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));
}
}

View File

@ -118,6 +118,8 @@ X(DYNAMICDELAY_5)
X(DYNAMICDELAY_6)
X(DYNAMICDELAY_7)
X(LOCK)
X(PLLOUT_A)
X(PLLOUT_B)
X(BYPASS)
X(RESETB)
X(LATCHINPUTVALUE)