nextpnr/nexus/arch.cc
Keith Rothman c99fbde0eb Mark IdString and IdStringList single argument constructors explicit.
Single argument constructors will silently convert to that type.  This
is typically not the right thing to do.  For example, the nexus and
ice40 arch_pybindings.h files were incorrectly parsing bel name strings,
etc.

Signed-off-by: Keith Rothman <537074+litghost@users.noreply.github.com>
2021-02-04 16:38:07 -08:00

980 lines
34 KiB
C++

/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2020 David Shah <dave@ds0.me>
*
*
* 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 <boost/algorithm/string.hpp>
#include "embed.h"
#include "log.h"
#include "nextpnr.h"
#include "placer1.h"
#include "placer_heap.h"
#include "router1.h"
#include "router2.h"
#include "timing.h"
#include "util.h"
NEXTPNR_NAMESPACE_BEGIN
// -----------------------------------------------------------------------
void IdString::initialize_arch(const BaseCtx *ctx)
{
#define X(t) initialize_add(ctx, #t, ID_##t);
#include "constids.inc"
#undef X
}
// -----------------------------------------------------------------------
Arch::Arch(ArchArgs args) : args(args)
{
// Parse device string
if (boost::starts_with(args.device, "LIFCL")) {
family = "LIFCL";
} else {
log_error("Unknown device string '%s' (expected device name like 'LIFCL-40-8SG72C')\n", args.device.c_str());
}
auto last_sep = args.device.rfind('-');
if (last_sep == std::string::npos)
log_error("Unknown device string '%s' (expected device name like 'LIFCL-40-8SG72C')\n", args.device.c_str());
device = args.device.substr(0, last_sep);
speed = args.device.substr(last_sep + 1, 1);
auto package_end = args.device.find_last_of("0123456789");
if (package_end == std::string::npos || package_end < last_sep)
log_error("Unknown device string '%s' (expected device name like 'LIFCL-40-8SG72C')\n", args.device.c_str());
package = args.device.substr(last_sep + 2, (package_end - (last_sep + 2)) + 1);
rating = args.device.substr(package_end + 1);
// Check for 'ES' part
if (rating.size() > 1 && rating.substr(1) == "ES") {
variant = "ES";
} else {
variant = "";
}
// Load database
std::string chipdb = stringf("nexus/chipdb-%s.bin", family.c_str());
auto db_ptr = reinterpret_cast<const RelPtr<DatabasePOD> *>(get_chipdb(chipdb));
if (db_ptr == nullptr)
log_error("Failed to load chipdb '%s'\n", chipdb.c_str());
db = db_ptr->get();
// Check database version and family
if (db->version != bba_version) {
log_error("Provided database version %d is %s than nextpnr version %d, please rebuild database/nextpnr.\n",
int(db->version), (db->version > bba_version) ? "newer" : "older", int(bba_version));
}
if (db->family.get() != family) {
log_error("Database is for family '%s' but provided device is family '%s'.\n", db->family.get(),
family.c_str());
}
// Set up chip_info
chip_info = nullptr;
for (auto &chip : db->chips) {
if (chip.device_name.get() == device) {
chip_info = &chip;
break;
}
}
if (!chip_info)
log_error("Unknown device '%s'.\n", device.c_str());
// Set up bba IdStrings
for (size_t i = 0; i < db->ids->bba_id_strs.size(); i++) {
IdString::initialize_add(this, db->ids->bba_id_strs[i].get(), uint32_t(i) + db->ids->num_file_ids);
}
// Set up validity structures
tileStatus.resize(chip_info->grid.size());
for (size_t i = 0; i < chip_info->grid.size(); i++) {
tileStatus[i].boundcells.resize(db->loctypes[chip_info->grid[i].loc_type].bels.size());
}
// This structure is needed for a fast getBelByLocation because bels can have an offset
for (size_t i = 0; i < chip_info->grid.size(); i++) {
auto &loc = db->loctypes[chip_info->grid[i].loc_type];
for (unsigned j = 0; j < loc.bels.size(); j++) {
auto &bel = loc.bels[j];
int rel_bel_tile;
if (!rel_tile(i, bel.rel_x, bel.rel_y, rel_bel_tile))
continue;
auto &ts = tileStatus.at(rel_bel_tile);
if (int(ts.bels_by_z.size()) <= bel.z)
ts.bels_by_z.resize(bel.z + 1);
ts.bels_by_z[bel.z].tile = i;
ts.bels_by_z[bel.z].index = j;
}
}
for (int i = 0; i < chip_info->width; i++) {
IdString x_id = id(stringf("X%d", i));
x_ids.push_back(x_id);
id_to_x[x_id] = i;
}
for (int i = 0; i < chip_info->height; i++) {
IdString y_id = id(stringf("Y%d", i));
y_ids.push_back(y_id);
id_to_y[y_id] = i;
}
init_cell_pin_data();
// Validate and set up package
package_idx = -1;
for (size_t i = 0; i < chip_info->packages.size(); i++) {
if (package == chip_info->packages[i].short_name.get()) {
package_idx = i;
break;
}
}
if (package_idx == -1) {
std::string all_packages = "";
for (auto &pkg : chip_info->packages) {
all_packages += " ";
all_packages += pkg.short_name.get();
}
log_error("Unknown package '%s'. Available package options:%s\n", package.c_str(), all_packages.c_str());
}
// Validate and set up speed grade
// Convert speed to speed grade (TODO: low power back bias mode too)
if (speed == "7")
speed = "10";
else if (speed == "8")
speed = "11";
else if (speed == "9")
speed = "12";
speed_grade = nullptr;
for (auto &sg : db->speed_grades) {
if (sg.name.get() == speed) {
speed_grade = &sg;
break;
}
}
if (!speed_grade)
log_error("Unknown speed grade '%s'.\n", speed.c_str());
std::unordered_set<IdString> bel_types;
for (BelId bel : getBels()) {
bel_types.insert(getBelType(bel));
}
for (IdString bel_type : bel_types) {
cell_types.push_back(bel_type);
BelBucketId bucket;
bucket.name = bel_type;
buckets.push_back(bucket);
}
}
// -----------------------------------------------------------------------
std::string Arch::getChipName() const { return args.device; }
IdString Arch::archArgsToId(ArchArgs args) const { return id(args.device); }
// -----------------------------------------------------------------------
BelId Arch::getBelByName(IdStringList name) const
{
if (name.size() != 3)
return BelId();
int x = id_to_x.at(name[0]);
int y = id_to_y.at(name[1]);
NPNR_ASSERT(x >= 0 && x < chip_info->width);
NPNR_ASSERT(y >= 0 && y < chip_info->height);
auto &tile = db->loctypes[chip_info->grid[y * chip_info->width + x].loc_type];
for (size_t i = 0; i < tile.bels.size(); i++) {
if (tile.bels[i].name == name[2].index) {
BelId ret;
ret.tile = y * chip_info->width + x;
ret.index = i;
return ret;
}
}
return BelId();
}
std::vector<BelId> Arch::getBelsByTile(int x, int y) const
{
std::vector<BelId> bels;
for (auto bel : tileStatus.at(y * chip_info->width + x).bels_by_z)
if (bel != BelId())
bels.push_back(bel);
return bels;
}
WireId Arch::getBelPinWire(BelId bel, IdString pin) const
{
// Binary search on wire IdString, by ID
int num_bel_wires = bel_data(bel).ports.size();
const BelWirePOD *bel_ports = bel_data(bel).ports.get();
if (num_bel_wires < 7) {
for (int i = 0; i < num_bel_wires; i++) {
if (int(bel_ports[i].port) == pin.index) {
return canonical_wire(bel.tile, bel_ports[i].wire_index);
}
}
} else {
int b = 0, e = num_bel_wires - 1;
while (b <= e) {
int i = (b + e) / 2;
if (int(bel_ports[i].port) == pin.index) {
return canonical_wire(bel.tile, bel_ports[i].wire_index);
}
if (int(bel_ports[i].port) > pin.index)
e = i - 1;
else
b = i + 1;
}
}
return WireId();
}
PortType Arch::getBelPinType(BelId bel, IdString pin) const
{
// Binary search on wire IdString, by ID
int num_bel_wires = bel_data(bel).ports.size();
const BelWirePOD *bel_ports = bel_data(bel).ports.get();
if (num_bel_wires < 7) {
for (int i = 0; i < num_bel_wires; i++) {
if (int(bel_ports[i].port) == pin.index) {
return PortType(bel_ports[i].type);
}
}
} else {
int b = 0, e = num_bel_wires - 1;
while (b <= e) {
int i = (b + e) / 2;
if (int(bel_ports[i].port) == pin.index) {
return PortType(bel_ports[i].type);
}
if (int(bel_ports[i].port) > pin.index)
e = i - 1;
else
b = i + 1;
}
}
NPNR_ASSERT_FALSE("unknown bel pin");
}
std::vector<IdString> Arch::getBelPins(BelId bel) const
{
std::vector<IdString> ret;
for (auto &p : bel_data(bel).ports)
ret.push_back(IdString(p.port));
return ret;
}
std::vector<std::pair<IdString, std::string>> Arch::getBelAttrs(BelId bel) const
{
std::vector<std::pair<IdString, std::string>> ret;
ret.emplace_back(id("INDEX"), stringf("%d", bel.index));
ret.emplace_back(id("GRID_X"), stringf("%d", bel.tile % chip_info->width));
ret.emplace_back(id("GRID_Y"), stringf("%d", bel.tile / chip_info->width));
ret.emplace_back(id("BEL_Z"), stringf("%d", bel_data(bel).z));
ret.emplace_back(id("BEL_TYPE"), nameOf(getBelType(bel)));
return ret;
}
// -----------------------------------------------------------------------
WireId Arch::getWireByName(IdStringList name) const
{
if (name.size() != 3)
return WireId();
int x = id_to_x.at(name[0]);
int y = id_to_y.at(name[1]);
NPNR_ASSERT(x >= 0 && x < chip_info->width);
NPNR_ASSERT(y >= 0 && y < chip_info->height);
auto &tile = db->loctypes[chip_info->grid[y * chip_info->width + x].loc_type];
for (size_t i = 0; i < tile.wires.size(); i++) {
if (tile.wires[i].name == name[2].index) {
WireId ret;
ret.tile = y * chip_info->width + x;
ret.index = i;
return ret;
}
}
return WireId();
}
IdString Arch::getWireType(WireId wire) const { return id("WIRE"); }
std::vector<std::pair<IdString, std::string>> Arch::getWireAttrs(WireId wire) const
{
std::vector<std::pair<IdString, std::string>> ret;
ret.emplace_back(id("INDEX"), stringf("%d", wire.index));
ret.emplace_back(id("GRID_X"), stringf("%d", wire.tile % chip_info->width));
ret.emplace_back(id("GRID_Y"), stringf("%d", wire.tile / chip_info->width));
ret.emplace_back(id("FLAGS"), stringf("%u", wire_data(wire).flags));
return ret;
}
// -----------------------------------------------------------------------
PipId Arch::getPipByName(IdStringList name) const
{
if (name.size() != 5)
return PipId();
int x = id_to_x.at(name[0]);
int y = id_to_y.at(name[1]);
NPNR_ASSERT(x >= 0 && x < chip_info->width);
NPNR_ASSERT(y >= 0 && y < chip_info->height);
PipId ret;
ret.tile = y * chip_info->width + x;
ret.index = std::stoi(name[2].str(this));
return ret;
}
IdStringList Arch::getPipName(PipId pip) const
{
NPNR_ASSERT(pip != PipId());
std::array<IdString, 5> ids{x_ids.at(pip.tile % chip_info->width), y_ids.at(pip.tile / chip_info->width),
id(stringf("%d", pip.index)), IdString(loc_data(pip).wires[pip_data(pip).to_wire].name),
IdString(loc_data(pip).wires[pip_data(pip).from_wire].name)};
return IdStringList(ids);
}
IdString Arch::getPipType(PipId pip) const { return IdString(); }
std::vector<std::pair<IdString, std::string>> Arch::getPipAttrs(PipId pip) const
{
std::vector<std::pair<IdString, std::string>> ret;
ret.emplace_back(id("INDEX"), stringf("%d", pip.index));
ret.emplace_back(id("GRID_X"), stringf("%d", pip.tile % chip_info->width));
ret.emplace_back(id("GRID_Y"), stringf("%d", pip.tile / chip_info->width));
ret.emplace_back(id("FROM_TILE_WIRE"), nameOf(IdString(loc_data(pip).wires[pip_data(pip).from_wire].name)));
ret.emplace_back(id("TO_TILE_WIRE"), nameOf(IdString(loc_data(pip).wires[pip_data(pip).to_wire].name)));
return ret;
}
// -----------------------------------------------------------------------
namespace {
const float bel_ofs_x = 0.7, bel_ofs_y = 0.0375;
const float bel_sp_x = 0.1, bel_sp_y = 0.1;
const float bel_width = 0.075, bel_height = 0.075;
} // namespace
std::vector<GraphicElement> Arch::getDecalGraphics(DecalId decal) const
{
std::vector<GraphicElement> ret;
switch (decal.type) {
case DecalId::TYPE_BEL: {
auto style = decal.active ? GraphicElement::STYLE_ACTIVE : GraphicElement::STYLE_INACTIVE;
if (decal.index != -1) {
int slice = (decal.index >> 3) & 0x3;
int bel = decal.index & 0x7;
float x1, x2, y1, y2;
if (bel == BEL_RAMW) {
x1 = bel_ofs_x;
y1 = bel_ofs_y + 2 * bel_sp_y * slice;
x2 = x1 + bel_sp_x + bel_width;
y2 = y1 + bel_height;
} else {
x1 = bel_ofs_x + bel_sp_x * (bel >> 1);
y1 = bel_ofs_y + 2 * bel_sp_y * slice + bel_sp_y * (bel & 0x1);
if (slice >= 2)
y1 += bel_sp_y * 1.5;
x2 = x1 + bel_width;
y2 = y1 + bel_height;
}
ret.emplace_back(GraphicElement::TYPE_BOX, style, x1, y1, x2, y2, 1);
}
break;
};
default:
break;
}
return ret;
}
DecalXY Arch::getBelDecal(BelId bel) const
{
DecalXY decalxy;
decalxy.decal.type = DecalId::TYPE_BEL;
if (tile_is(bel, LOC_LOGIC))
decalxy.decal.index = bel_data(bel).z;
else
decalxy.decal.index = -1;
decalxy.decal.active = (getBoundBelCell(bel) != nullptr);
decalxy.x = bel.tile % chip_info->width;
decalxy.y = bel.tile / chip_info->width;
return decalxy;
}
DecalXY Arch::getWireDecal(WireId wire) const { return {}; }
DecalXY Arch::getPipDecal(PipId pip) const { return {}; };
DecalXY Arch::getGroupDecal(GroupId pip) const { return {}; };
// -----------------------------------------------------------------------
bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const
{
auto lookup_port = [&](IdString p) {
auto fnd = cell->tmg_portmap.find(p);
return fnd == cell->tmg_portmap.end() ? p : fnd->second;
};
if (cell->type == id_OXIDE_COMB) {
if (cell->lutInfo.is_carry) {
bool result = lookup_cell_delay(cell->tmg_index, lookup_port(fromPort), lookup_port(toPort), delay);
// Because CCU2 = 2x OXIDE_COMB
if (result && fromPort == id_FCI && toPort == id_FCO) {
delay.min_delay /= 2;
delay.max_delay /= 2;
}
return result;
} else {
if (toPort == id_F || toPort == id_OFX)
return lookup_cell_delay(cell->tmg_index, fromPort, toPort, delay);
}
} else if (is_dsp_cell(cell)) {
if (fromPort == id_CLK)
return false; // don't include delays that are actually clock-to-out here
return lookup_cell_delay(cell->tmg_index, lookup_port(fromPort), lookup_port(toPort), delay);
}
return false;
}
TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const
{
auto disconnected = [cell](IdString p) { return !cell->ports.count(p) || cell->ports.at(p).net == nullptr; };
auto lookup_port = [&](IdString p) {
auto fnd = cell->tmg_portmap.find(p);
return fnd == cell->tmg_portmap.end() ? p : fnd->second;
};
clockInfoCount = 0;
if (cell->type == id_OXIDE_COMB) {
if (port == id_A || port == id_B || port == id_C || port == id_D || port == id_SEL || port == id_F1 ||
port == id_FCI || port == id_WDI)
return TMG_COMB_INPUT;
if (port == id_F || port == id_OFX || port == id_FCO) {
if (disconnected(id_A) && disconnected(id_B) && disconnected(id_C) && disconnected(id_D) &&
disconnected(id_FCI) && disconnected(id_SEL) && disconnected(id_WDI))
return TMG_IGNORE;
else
return TMG_COMB_OUTPUT;
}
} else if (cell->type == id_OXIDE_FF) {
if (port == id_CLK)
return TMG_CLOCK_INPUT;
else if (port == id_Q) {
clockInfoCount = 1;
return TMG_REGISTER_OUTPUT;
} else {
clockInfoCount = 1;
return TMG_REGISTER_INPUT;
}
} else if (cell->type == id_RAMW) {
if (port == id_CLK)
return TMG_CLOCK_INPUT;
else if (port == id_WDO0 || port == id_WDO1 || port == id_WDO2 || port == id_WDO3) {
clockInfoCount = 1;
return TMG_REGISTER_OUTPUT;
} else if (port == id_A0 || port == id_A1 || port == id_B0 || port == id_B1 || port == id_C0 || port == id_C1 ||
port == id_D0 || port == id_D1) {
clockInfoCount = 1;
return TMG_REGISTER_INPUT;
}
} else if (cell->type == id_OXIDE_EBR) {
if (port == id_DWS0 || port == id_DWS1 || port == id_DWS2 || port == id_DWS3 || port == id_DWS4)
return TMG_IGNORE;
if (port == id_CLKA || port == id_CLKB)
return TMG_CLOCK_INPUT;
clockInfoCount = 1;
return (cell->ports.at(port).type == PORT_IN) ? TMG_REGISTER_INPUT : TMG_REGISTER_OUTPUT;
} else if (cell->type == id_MULT18_CORE || cell->type == id_MULT18X36_CORE || cell->type == id_MULT36_CORE) {
return (cell->ports.at(port).type == PORT_IN) ? TMG_COMB_INPUT : TMG_COMB_OUTPUT;
} else if (cell->type == id_PREADD9_CORE || cell->type == id_REG18_CORE || cell->type == id_MULT9_CORE) {
if (port == id_CLK)
return TMG_CLOCK_INPUT;
auto type = lookup_port_type(cell->tmg_index, lookup_port(port), cell->ports.at(port).type, id_CLK);
if (type == TMG_REGISTER_INPUT || type == TMG_REGISTER_OUTPUT)
clockInfoCount = 1;
return type;
}
return TMG_IGNORE;
}
TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port, int index) const
{
auto lookup_port = [&](IdString p) {
auto fnd = cell->tmg_portmap.find(p);
return fnd == cell->tmg_portmap.end() ? p : fnd->second;
};
TimingClockingInfo info;
if (cell->type == id_OXIDE_FF) {
info.edge = (cell->ffInfo.ctrlset.clkmux == ID_INV) ? FALLING_EDGE : RISING_EDGE;
info.clock_port = id_CLK;
if (port == id_Q)
NPNR_ASSERT(lookup_cell_delay(cell->tmg_index, id_CLK, port, info.clockToQ));
else
lookup_cell_setuphold(cell->tmg_index, port, id_CLK, info.setup, info.hold);
} else if (cell->type == id_RAMW) {
info.edge = (cell->ffInfo.ctrlset.clkmux == ID_INV) ? FALLING_EDGE : RISING_EDGE;
info.clock_port = id_CLK;
if (port == id_WDO0 || port == id_WDO1 || port == id_WDO2 || port == id_WDO3)
NPNR_ASSERT(lookup_cell_delay(cell->tmg_index, id_CLK, port, info.clockToQ));
else
lookup_cell_setuphold(cell->tmg_index, port, id_CLK, info.setup, info.hold);
} else if (cell->type == id_OXIDE_EBR) {
if (cell->ports.at(port).type == PORT_IN) {
lookup_cell_setuphold_clock(cell->tmg_index, lookup_port(port), info.clock_port, info.setup, info.hold);
} else {
lookup_cell_clock_out(cell->tmg_index, lookup_port(port), info.clock_port, info.clockToQ);
}
// Lookup edge based on inversion
info.edge = (get_cell_pinmux(cell, info.clock_port) == PINMUX_INV) ? FALLING_EDGE : RISING_EDGE;
} else if (cell->type == id_PREADD9_CORE || cell->type == id_REG18_CORE || cell->type == id_MULT9_CORE) {
info.clock_port = id_CLK;
if (cell->ports.at(port).type == PORT_IN) {
lookup_cell_setuphold(cell->tmg_index, lookup_port(port), id_CLK, info.setup, info.hold);
} else {
NPNR_ASSERT(lookup_cell_delay(cell->tmg_index, id_CLK, lookup_port(port), info.clockToQ));
}
info.edge = (get_cell_pinmux(cell, info.clock_port) == PINMUX_INV) ? FALLING_EDGE : RISING_EDGE;
} else {
NPNR_ASSERT_FALSE("missing clocking info");
}
return info;
}
// -----------------------------------------------------------------------
delay_t Arch::estimateDelay(WireId src, WireId dst) const
{
int src_x = src.tile % chip_info->width, src_y = src.tile / chip_info->width;
int dst_x = dst.tile % chip_info->width, dst_y = dst.tile / chip_info->width;
int dist_x = std::abs(src_x - dst_x);
int dist_y = std::abs(src_y - dst_y);
return 75 * dist_x + 75 * dist_y + 200;
}
delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const
{
if (net_info->driver.cell == nullptr || net_info->driver.cell->bel == BelId() || sink.cell->bel == BelId())
return 0;
if (sink.port == id_FCI)
return 0;
int src_x = net_info->driver.cell->bel.tile % chip_info->width,
src_y = net_info->driver.cell->bel.tile / chip_info->width;
int dst_x = sink.cell->bel.tile % chip_info->width, dst_y = sink.cell->bel.tile / chip_info->width;
int dist_x = std::abs(src_x - dst_x);
int dist_y = std::abs(src_y - dst_y);
return 100 * dist_x + 100 * dist_y + 250;
}
bool Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const { return false; }
ArcBounds Arch::getRouteBoundingBox(WireId src, WireId dst) const
{
ArcBounds bb;
int src_x = src.tile % chip_info->width, src_y = src.tile / chip_info->width;
int dst_x = dst.tile % chip_info->width, dst_y = dst.tile / chip_info->width;
bb.x0 = src_x;
bb.y0 = src_y;
bb.x1 = src_x;
bb.y1 = src_y;
auto extend = [&](int x, int y) {
bb.x0 = std::min(bb.x0, x);
bb.x1 = std::max(bb.x1, x);
bb.y0 = std::min(bb.y0, y);
bb.y1 = std::max(bb.y1, y);
};
extend(dst_x, dst_y);
if (dsp_wires.count(src) || dsp_wires.count(dst)) {
bb.x0 = std::max<int>(0, bb.x0 - 6);
bb.x1 = std::min<int>(chip_info->width, bb.x1 + 6);
}
if (lram_wires.count(src) || lram_wires.count(dst)) {
bb.y0 = std::max<int>(0, bb.y0 - 7);
bb.y1 = std::min<int>(chip_info->width, bb.y1 + 7);
}
return bb;
}
// -----------------------------------------------------------------------
bool Arch::place()
{
std::string placer = str_or_default(settings, id("placer"), defaultPlacer);
if (placer == "heap") {
PlacerHeapCfg cfg(getCtx());
cfg.ioBufTypes.insert(id_SEIO33_CORE);
cfg.ioBufTypes.insert(id_SEIO18_CORE);
cfg.ioBufTypes.insert(id_OSC_CORE);
cfg.cellGroups.emplace_back();
cfg.cellGroups.back().insert({id_OXIDE_COMB});
cfg.cellGroups.back().insert({id_OXIDE_FF});
cfg.beta = 0.5;
cfg.criticalityExponent = 7;
if (!placer_heap(getCtx(), cfg))
return false;
} else if (placer == "sa") {
if (!placer1(getCtx(), Placer1Cfg(getCtx())))
return false;
} else {
log_error("Nexus architecture does not support placer '%s'\n", placer.c_str());
}
post_place_opt();
getCtx()->attrs[getCtx()->id("step")] = std::string("place");
archInfoToAttributes();
return true;
}
void Arch::pre_routing()
{
for (auto cell : sorted(cells)) {
CellInfo *ci = cell.second;
if (ci->type == id_MULT9_CORE || ci->type == id_PREADD9_CORE || ci->type == id_MULT18_CORE ||
ci->type == id_MULT18X36_CORE || ci->type == id_MULT36_CORE || ci->type == id_REG18_CORE ||
ci->type == id_ACC54_CORE) {
for (auto port : sorted_ref(ci->ports)) {
WireId wire = getBelPinWire(ci->bel, port.first);
if (wire != WireId())
dsp_wires.insert(wire);
}
}
if (ci->type == id_LRAM_CORE) {
for (auto port : sorted_ref(ci->ports)) {
WireId wire = getBelPinWire(ci->bel, port.first);
if (wire != WireId())
lram_wires.insert(wire);
}
}
}
}
bool Arch::route()
{
assign_budget(getCtx(), true);
pre_routing();
route_globals();
std::string router = str_or_default(settings, id("router"), defaultRouter);
bool result;
if (router == "router1") {
result = router1(getCtx(), Router1Cfg(getCtx()));
} else if (router == "router2") {
router2(getCtx(), Router2Cfg(getCtx()));
result = true;
} else {
log_error("iCE40 architecture does not support router '%s'\n", router.c_str());
}
getCtx()->attrs[getCtx()->id("step")] = std::string("route");
archInfoToAttributes();
return result;
}
// -----------------------------------------------------------------------
CellPinMux Arch::get_cell_pinmux(const CellInfo *cell, IdString pin) const
{
IdString param = id(stringf("%sMUX", pin.c_str(this)));
auto fnd_param = cell->params.find(param);
if (fnd_param == cell->params.end())
return PINMUX_SIG;
const std::string &pm = fnd_param->second.as_string();
if (pm == "0")
return PINMUX_0;
else if (pm == "1")
return PINMUX_1;
else if (pm == "INV")
return PINMUX_INV;
else if (pm == pin.c_str(this))
return PINMUX_SIG;
else {
log_error("Invalid %s setting '%s' for cell '%s'\n", nameOf(param), pm.c_str(), nameOf(cell));
NPNR_ASSERT_FALSE("unreachable");
}
}
void Arch::set_cell_pinmux(CellInfo *cell, IdString pin, CellPinMux state)
{
IdString param = id(stringf("%sMUX", pin.c_str(this)));
switch (state) {
case PINMUX_SIG:
cell->params.erase(param);
break;
case PINMUX_0:
cell->params[param] = std::string("0");
break;
case PINMUX_1:
cell->params[param] = std::string("1");
break;
case PINMUX_INV:
cell->params[param] = std::string("INV");
break;
default:
NPNR_ASSERT_FALSE("unreachable");
}
}
// -----------------------------------------------------------------------
const PadInfoPOD *Arch::get_pkg_pin_data(const std::string &pin) const
{
for (auto &pad : chip_info->pads) {
if (pin == pad.pins[package_idx].get())
return &pad;
}
return nullptr;
}
Loc Arch::get_pad_loc(const PadInfoPOD *pad) const
{
Loc loc;
switch (pad->side) {
case PIO_LEFT:
loc.x = 0;
loc.y = pad->offset;
break;
case PIO_RIGHT:
loc.x = chip_info->width - 1;
loc.y = pad->offset;
break;
case PIO_TOP:
loc.x = pad->offset;
loc.y = 0;
break;
case PIO_BOTTOM:
loc.x = pad->offset;
loc.y = chip_info->height - 1;
}
loc.z = pad->pio_index;
return loc;
}
BelId Arch::get_pad_pio_bel(const PadInfoPOD *pad) const
{
if (pad == nullptr)
return BelId();
return getBelByLocation(get_pad_loc(pad));
}
const PadInfoPOD *Arch::get_bel_pad(BelId bel) const
{
Loc loc = getBelLocation(bel);
int side = -1, offset = -1;
// Convert (x, y) to (side, offset)
if (loc.x == 0) {
side = PIO_LEFT;
offset = loc.y;
} else if (loc.x == (chip_info->width - 1)) {
side = PIO_RIGHT;
offset = loc.y;
} else if (loc.y == 0) {
side = PIO_TOP;
offset = loc.x;
} else if (loc.y == (chip_info->height - 1)) {
side = PIO_BOTTOM;
offset = loc.x;
} else {
return nullptr;
}
// Lookup in the list of pads
for (auto &pad : chip_info->pads) {
if (pad.side == side && pad.offset == offset && pad.pio_index == loc.z)
return &pad;
}
return nullptr;
}
std::string Arch::get_pad_functions(const PadInfoPOD *pad) const
{
std::string s;
for (auto f : pad->func_strs) {
if (!s.empty())
s += '/';
s += IdString(f).str(this);
}
return s;
}
// -----------------------------------------------------------------------
// Helper for cell timing lookups
namespace {
template <typename Tres, typename Tgetter, typename Tkey>
int db_binary_search(const Tres *list, int count, Tgetter key_getter, Tkey key)
{
if (count < 7) {
for (int i = 0; i < count; i++) {
if (key_getter(list[i]) == key) {
return i;
}
}
} else {
int b = 0, e = count - 1;
while (b <= e) {
int i = (b + e) / 2;
if (key_getter(list[i]) == key) {
return i;
}
if (key_getter(list[i]) > key)
e = i - 1;
else
b = i + 1;
}
}
return -1;
}
} // namespace
bool Arch::is_dsp_cell(const CellInfo *cell) const
{
return cell->type == id_MULT18_CORE || cell->type == id_MULT18X36_CORE || cell->type == id_MULT36_CORE ||
cell->type == id_PREADD9_CORE || cell->type == id_REG18_CORE || cell->type == id_MULT9_CORE;
}
int Arch::get_cell_timing_idx(IdString cell_type, IdString cell_variant) const
{
return db_binary_search(
speed_grade->cell_types.get(), speed_grade->cell_types.size(),
[](const CellTimingPOD &ct) { return std::make_pair(ct.cell_type, ct.cell_variant); },
std::make_pair(cell_type.index, cell_variant.index));
}
bool Arch::lookup_cell_delay(int type_idx, IdString from_port, IdString to_port, DelayInfo &delay) const
{
NPNR_ASSERT(type_idx != -1);
const auto &ct = speed_grade->cell_types[type_idx];
int dly_idx = db_binary_search(
ct.prop_delays.get(), ct.prop_delays.size(),
[](const CellPropDelayPOD &pd) { return std::make_pair(pd.to_port, pd.from_port); },
std::make_pair(to_port.index, from_port.index));
if (dly_idx == -1)
return false;
delay.min_delay = ct.prop_delays[dly_idx].min_delay;
delay.max_delay = ct.prop_delays[dly_idx].max_delay;
return true;
}
void Arch::lookup_cell_setuphold(int type_idx, IdString from_port, IdString clock, DelayInfo &setup,
DelayInfo &hold) const
{
NPNR_ASSERT(type_idx != -1);
const auto &ct = speed_grade->cell_types[type_idx];
int dly_idx = db_binary_search(
ct.setup_holds.get(), ct.setup_holds.size(),
[](const CellSetupHoldPOD &sh) { return std::make_pair(sh.sig_port, sh.clock_port); },
std::make_pair(from_port.index, clock.index));
NPNR_ASSERT(dly_idx != -1);
setup.min_delay = ct.setup_holds[dly_idx].min_setup;
setup.max_delay = ct.setup_holds[dly_idx].max_setup;
hold.min_delay = ct.setup_holds[dly_idx].min_hold;
hold.max_delay = ct.setup_holds[dly_idx].max_hold;
}
void Arch::lookup_cell_setuphold_clock(int type_idx, IdString from_port, IdString &clock, DelayInfo &setup,
DelayInfo &hold) const
{
NPNR_ASSERT(type_idx != -1);
const auto &ct = speed_grade->cell_types[type_idx];
int dly_idx = db_binary_search(
ct.setup_holds.get(), ct.setup_holds.size(), [](const CellSetupHoldPOD &sh) { return sh.sig_port; },
from_port.index);
NPNR_ASSERT(dly_idx != -1);
clock = IdString(ct.setup_holds[dly_idx].clock_port);
setup.min_delay = ct.setup_holds[dly_idx].min_setup;
setup.max_delay = ct.setup_holds[dly_idx].max_setup;
hold.min_delay = ct.setup_holds[dly_idx].min_hold;
hold.max_delay = ct.setup_holds[dly_idx].max_hold;
}
void Arch::lookup_cell_clock_out(int type_idx, IdString to_port, IdString &clock, DelayInfo &delay) const
{
NPNR_ASSERT(type_idx != -1);
const auto &ct = speed_grade->cell_types[type_idx];
int dly_idx = db_binary_search(
ct.prop_delays.get(), ct.prop_delays.size(), [](const CellPropDelayPOD &pd) { return pd.to_port; },
to_port.index);
NPNR_ASSERT(dly_idx != -1);
clock = IdString(ct.prop_delays[dly_idx].from_port);
delay.min_delay = ct.prop_delays[dly_idx].min_delay;
delay.max_delay = ct.prop_delays[dly_idx].max_delay;
}
TimingPortClass Arch::lookup_port_type(int type_idx, IdString port, PortType dir, IdString clock) const
{
if (dir == PORT_IN) {
NPNR_ASSERT(type_idx != -1);
const auto &ct = speed_grade->cell_types[type_idx];
// If a setup-hold entry exists, then this is a register input
int sh_idx = db_binary_search(
ct.setup_holds.get(), ct.setup_holds.size(),
[](const CellSetupHoldPOD &sh) { return std::make_pair(sh.sig_port, sh.clock_port); },
std::make_pair(port.index, clock.index));
return (sh_idx != -1) ? TMG_REGISTER_INPUT : TMG_COMB_INPUT;
} else {
DelayInfo dly;
// If a clock-to-out entry exists, then this is a register output
return lookup_cell_delay(type_idx, clock, port, dly) ? TMG_REGISTER_OUTPUT : TMG_COMB_OUTPUT;
}
}
// -----------------------------------------------------------------------
#ifdef WITH_HEAP
const std::string Arch::defaultPlacer = "heap";
#else
const std::string Arch::defaultPlacer = "sa";
#endif
const std::vector<std::string> Arch::availablePlacers = {"sa",
#ifdef WITH_HEAP
"heap"
#endif
};
const std::string Arch::defaultRouter = "router2";
const std::vector<std::string> Arch::availableRouters = {"router1", "router2"};
NEXTPNR_NAMESPACE_END