aboutsummaryrefslogtreecommitdiffstats
path: root/nexus/arch.cc
diff options
context:
space:
mode:
Diffstat (limited to 'nexus/arch.cc')
-rw-r--r--nexus/arch.cc963
1 files changed, 963 insertions, 0 deletions
diff --git a/nexus/arch.cc b/nexus/arch.cc
new file mode 100644
index 00000000..f222a5ad
--- /dev/null
+++ b/nexus/arch.cc
@@ -0,0 +1,963 @@
+/*
+ * 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
+
+namespace {
+static std::tuple<int, int, std::string> split_identifier_name(const std::string &name)
+{
+ size_t first_slash = name.find('/');
+ NPNR_ASSERT(first_slash != std::string::npos);
+ size_t second_slash = name.find('/', first_slash + 1);
+ NPNR_ASSERT(second_slash != std::string::npos);
+ return std::make_tuple(std::stoi(name.substr(1, first_slash)),
+ std::stoi(name.substr(first_slash + 2, second_slash - first_slash)),
+ name.substr(second_slash + 1));
+};
+
+} // namespace
+
+// -----------------------------------------------------------------------
+
+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 (size_t i = 0; i < db->num_chips; i++) {
+ auto &chip = db->chips[i];
+ 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->num_bba_ids; 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->num_tiles);
+ for (size_t i = 0; i < chip_info->num_tiles; i++) {
+ tileStatus[i].boundcells.resize(db->loctypes[chip_info->grid[i].loc_type].num_bels);
+ }
+ // This structure is needed for a fast getBelByLocation because bels can have an offset
+ for (size_t i = 0; i < chip_info->num_tiles; i++) {
+ auto &loc = db->loctypes[chip_info->grid[i].loc_type];
+ for (unsigned j = 0; j < loc.num_bels; 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;
+ }
+ }
+ init_cell_pin_data();
+ // Validate and set up package
+ package_idx = -1;
+ for (size_t i = 0; i < chip_info->num_packages; i++) {
+ if (package == chip_info->packages[i].short_name.get()) {
+ package_idx = i;
+ break;
+ }
+ }
+ if (package_idx == -1) {
+ std::string all_packages = "";
+ for (size_t i = 0; i < chip_info->num_packages; i++) {
+ all_packages += " ";
+ all_packages += chip_info->packages[i].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 (size_t i = 0; i < db->num_speed_grades; i++) {
+ auto &sg = db->speed_grades[i];
+ if (sg.name.get() == speed) {
+ speed_grade = &sg;
+ break;
+ }
+ }
+ if (!speed_grade)
+ log_error("Unknown speed grade '%s'.\n", speed.c_str());
+}
+
+// -----------------------------------------------------------------------
+
+std::string Arch::getChipName() const { return args.device; }
+IdString Arch::archArgsToId(ArchArgs args) const { return id(args.device); }
+
+// -----------------------------------------------------------------------
+
+BelId Arch::getBelByName(IdString name) const
+{
+ int x, y;
+ std::string belname;
+ std::tie(x, y, belname) = split_identifier_name(name.str(this));
+ 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];
+ IdString bn = id(belname);
+ for (size_t i = 0; i < tile.num_bels; i++) {
+ if (tile.bels[i].name == bn.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).num_ports;
+ 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).num_ports;
+ 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;
+ int num_bel_wires = bel_data(bel).num_ports;
+ const BelWirePOD *bel_ports = bel_data(bel).ports.get();
+ for (int i = 0; i < num_bel_wires; i++)
+ ret.push_back(IdString(bel_ports[i].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(IdString name) const
+{
+ int x, y;
+ std::string wirename;
+ std::tie(x, y, wirename) = split_identifier_name(name.str(this));
+ 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];
+ IdString wn = id(wirename);
+ for (size_t i = 0; i < tile.num_wires; i++) {
+ if (tile.wires[i].name == wn.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(IdString name) const
+{
+ int x, y;
+ std::string pipname;
+ std::tie(x, y, pipname) = split_identifier_name(name.str(this));
+ 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;
+ auto sep_pos = pipname.find(':');
+ ret.index = std::stoi(pipname.substr(0, sep_pos));
+ return ret;
+}
+
+IdString Arch::getPipName(PipId pip) const
+{
+ NPNR_ASSERT(pip != PipId());
+ return id(stringf("X%d/Y%d/%d:%s->%s", pip.tile % chip_info->width, pip.tile / chip_info->width, pip.index,
+ nameOf(loc_data(pip).wires[pip_data(pip).from_wire].name),
+ nameOf(loc_data(pip).wires[pip_data(pip).to_wire].name)));
+}
+
+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(loc_data(pip).wires[pip_data(pip).from_wire].name));
+ ret.emplace_back(id("TO_TILE_WIRE"), nameOf(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);
+ }
+
+ 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);
+ }
+ }
+ }
+}
+
+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 (size_t i = 0; i < chip_info->num_pads; i++) {
+ const PadInfoPOD *pad = &(chip_info->pads[i]);
+ 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 (size_t i = 0; i < chip_info->num_pads; i++) {
+ const PadInfoPOD *pad = &(chip_info->pads[i]);
+ 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 (size_t i = 0; i < pad->num_funcs; i++) {
+ if (!s.empty())
+ s += '/';
+ s += IdString(pad->func_strs[i]).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->num_cell_types,
+ [](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.num_prop_delays,
+ [](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.num_setup_holds,
+ [](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.num_setup_holds, [](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.num_prop_delays, [](const CellPropDelayPOD &pd) { return pd.to_port; },
+ to_port.index);
+ NPNR_ASSERT(dly_idx != -1);
+ clock = 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.num_setup_holds,
+ [](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