aboutsummaryrefslogtreecommitdiffstats
path: root/nexus
diff options
context:
space:
mode:
authorMiodrag Milanović <mmicko@gmail.com>2020-11-30 10:56:59 +0100
committerGitHub <noreply@github.com>2020-11-30 10:56:59 +0100
commit8b5c0dc1e49b692b0bb598a90034c27db653622d (patch)
treeafd13c654df1faa0b5df9f11be74eede087fa564 /nexus
parent1afa494e69e3c8af3dd5d1685b9cd2b1d3bea4d0 (diff)
parent2fe8bebc6ce464afadef2403a8331031e16c5a5d (diff)
downloadnextpnr-8b5c0dc1e49b692b0bb598a90034c27db653622d.tar.gz
nextpnr-8b5c0dc1e49b692b0bb598a90034c27db653622d.tar.bz2
nextpnr-8b5c0dc1e49b692b0bb598a90034c27db653622d.zip
Merge pull request #524 from daveshah1/nextpnr-nexus
Upstreaming basic support for Nexus devices
Diffstat (limited to 'nexus')
-rw-r--r--nexus/.gitignore1
-rw-r--r--nexus/CMakeLists.txt57
-rw-r--r--nexus/arch.cc963
-rw-r--r--nexus/arch.h1586
-rw-r--r--nexus/arch_place.cc118
-rw-r--r--nexus/arch_pybindings.cc72
-rw-r--r--nexus/arch_pybindings.h98
-rw-r--r--nexus/archdefs.h253
-rw-r--r--nexus/bba_version.inc1
-rw-r--r--nexus/constids.inc377
-rw-r--r--nexus/family.cmake53
-rw-r--r--nexus/fasm.cc669
-rw-r--r--nexus/global.cc168
-rw-r--r--nexus/io.cc70
-rw-r--r--nexus/main.cc99
-rw-r--r--nexus/pack.cc1866
-rw-r--r--nexus/pdc.cc352
-rw-r--r--nexus/pins.cc180
-rw-r--r--nexus/post_place.cc161
19 files changed, 7144 insertions, 0 deletions
diff --git a/nexus/.gitignore b/nexus/.gitignore
new file mode 100644
index 00000000..0cb37421
--- /dev/null
+++ b/nexus/.gitignore
@@ -0,0 +1 @@
+/chipdb/
diff --git a/nexus/CMakeLists.txt b/nexus/CMakeLists.txt
new file mode 100644
index 00000000..d58bd689
--- /dev/null
+++ b/nexus/CMakeLists.txt
@@ -0,0 +1,57 @@
+cmake_minimum_required(VERSION 3.5)
+project(chipdb-nexus NONE)
+
+set(ALL_NEXUS_FAMILIES LIFCL)
+
+# NOTE: Unlike iCE40 and ECP5; one database can cover all densities of a given family
+
+set(NEXUS_FAMILIES ${ALL_NEXUS_FAMILIES} CACHE STRING
+ "Include support for these Nexus families (available: ${ALL_NEXUS_FAMILIES})")
+message(STATUS "Enabled Nexus families: ${NEXUS_FAMILIES}")
+
+if(DEFINED NEXUS_CHIPDB)
+ add_custom_target(chipdb-nexus-bbas ALL)
+else()
+ # shared among all families
+ set(OXIDE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}" CACHE STRING
+ "prjoxide install prefix")
+ message(STATUS "prjoxide install prefix: ${OXIDE_INSTALL_PREFIX}")
+
+ set(all_device_bbas)
+ file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/chipdb)
+ foreach(subfamily ${NEXUS_FAMILIES})
+ if(NOT subfamily IN_LIST ALL_NEXUS_FAMILIES)
+ message(FATAL_ERROR "${subfamily} is not a supported Nexus family")
+ endif()
+
+ set(family_bba chipdb/chipdb-${subfamily}.bba)
+ set(PRJOXIDE_TOOL ${OXIDE_INSTALL_PREFIX}/bin/prjoxide)
+ add_custom_command(
+ OUTPUT ${family_bba}
+ COMMAND
+ ${PRJOXIDE_TOOL} bba-export ${subfamily} ${CMAKE_CURRENT_SOURCE_DIR}/constids.inc ${family_bba}.new
+ # atomically update
+ COMMAND ${CMAKE_COMMAND} -E rename ${family_bba}.new ${family_bba}
+ DEPENDS
+ ${PRJOXIDE_TOOL}
+ ${CMAKE_CURRENT_SOURCE_DIR}/constids.inc
+ ${CMAKE_CURRENT_SOURCE_DIR}/bba_version.inc
+ ${PREVIOUS_CHIPDB_TARGET}
+ VERBATIM)
+ list(APPEND all_device_bbas ${family_bba})
+ if(SERIALIZE_CHIPDBS)
+ set(PREVIOUS_CHIPDB_TARGET ${CMAKE_CURRENT_BINARY_DIR}/${family_bba})
+ endif()
+ endforeach()
+
+ add_custom_target(chipdb-nexus-bbas ALL DEPENDS ${all_device_bbas})
+
+ get_directory_property(has_parent PARENT_DIRECTORY)
+ if(has_parent)
+ set(NEXUS_CHIPDB ${CMAKE_CURRENT_BINARY_DIR}/chipdb PARENT_SCOPE)
+ # serialize chipdb build across multiple architectures
+ set(PREVIOUS_CHIPDB_TARGET chipdb-nexus-bbas PARENT_SCOPE)
+ else()
+ message(STATUS "Build nextpnr with -DNEXUS_CHIPDB=${CMAKE_CURRENT_BINARY_DIR}/chipdb")
+ endif()
+endif()
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
diff --git a/nexus/arch.h b/nexus/arch.h
new file mode 100644
index 00000000..d4d4799e
--- /dev/null
+++ b/nexus/arch.h
@@ -0,0 +1,1586 @@
+/*
+ * 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.
+ *
+ */
+
+#ifndef NEXTPNR_H
+#error Include "arch.h" via "nextpnr.h" only.
+#endif
+
+#include <boost/iostreams/device/mapped_file.hpp>
+
+#include <iostream>
+
+NEXTPNR_NAMESPACE_BEGIN
+
+template <typename T> struct RelPtr
+{
+ int32_t offset;
+
+ // void set(const T *ptr) {
+ // offset = reinterpret_cast<const char*>(ptr) -
+ // reinterpret_cast<const char*>(this);
+ // }
+
+ const T *get() const { return reinterpret_cast<const T *>(reinterpret_cast<const char *>(this) + offset); }
+
+ const T &operator[](size_t index) const { return get()[index]; }
+
+ const T &operator*() const { return *(get()); }
+
+ const T *operator->() const { return get(); }
+};
+
+/*
+ Fully deduplicated database
+
+ There are two key data structures in the database:
+
+ Locations (aka tile but not called this to avoid confusion
+ with Lattice terminology), are a (x, y) location.
+
+ Local wires; pips and bels are all stored once per variety of location
+ (called a location type) with a separate grid containing the location type
+ at a (x, y) coordinate.
+
+ Each location also has _neighbours_, other locations with interconnected
+ wires. The set of neighbours for a location are called a _neighbourhood_.
+
+ Each variety of _neighbourhood_ for a location type is also stored once,
+ using relative coordinates.
+
+*/
+
+NPNR_PACKED_STRUCT(struct BelWirePOD {
+ uint32_t port;
+ uint16_t type;
+ uint16_t wire_index; // wire index in tile
+});
+
+NPNR_PACKED_STRUCT(struct BelInfoPOD {
+ int32_t name; // bel name in tile IdString
+ int32_t type; // bel type IdString
+ int16_t rel_x, rel_y; // bel location relative to parent
+ int32_t z; // bel location absolute Z
+ RelPtr<BelWirePOD> ports; // ports, sorted by name IdString
+ int32_t num_ports; // number of ports
+});
+
+NPNR_PACKED_STRUCT(struct BelPinPOD {
+ uint32_t bel; // bel index in tile
+ int32_t pin; // bel pin name IdString
+});
+
+enum TileWireFlags : uint32_t
+{
+ WIRE_PRIMARY = 0x80000000,
+};
+
+NPNR_PACKED_STRUCT(struct LocWireInfoPOD {
+ int32_t name; // wire name in tile IdString
+ uint32_t flags;
+ int32_t num_uphill, num_downhill, num_bpins;
+ // Note this pip lists exclude neighbourhood pips
+ RelPtr<int32_t> pips_uh, pips_dh; // list of uphill/downhill pip indices in tile
+ RelPtr<BelPinPOD> bel_pins;
+});
+
+enum PipFlags
+{
+ PIP_FIXED_CONN = 0x8000,
+};
+
+NPNR_PACKED_STRUCT(struct PipInfoPOD {
+ uint16_t from_wire, to_wire;
+ uint16_t flags;
+ uint16_t timing_class;
+ int32_t tile_type;
+});
+
+enum RelLocType : uint8_t
+{
+ REL_LOC_XY = 0,
+ REL_LOC_GLOBAL = 1,
+ REL_LOC_BRANCH = 2,
+ REL_LOC_BRANCH_L = 3,
+ REL_LOC_BRANCH_R = 4,
+ REL_LOC_SPINE = 5,
+ REL_LOC_HROW = 6,
+ REL_LOC_VCC = 7,
+};
+
+enum ArcFlags
+{
+ LOGICAL_TO_PRIMARY = 0x80,
+ PHYSICAL_DOWNHILL = 0x08,
+};
+
+NPNR_PACKED_STRUCT(struct RelWireInfoPOD {
+ int16_t rel_x, rel_y;
+ uint16_t wire_index;
+ uint8_t loc_type;
+ uint8_t arc_flags;
+});
+
+NPNR_PACKED_STRUCT(struct WireNeighboursInfoPOD {
+ uint32_t num_nwires;
+ RelPtr<RelWireInfoPOD> neigh_wires;
+});
+
+NPNR_PACKED_STRUCT(struct LocNeighourhoodPOD { RelPtr<WireNeighboursInfoPOD> wire_neighbours; });
+
+NPNR_PACKED_STRUCT(struct LocTypePOD {
+ uint32_t num_bels, num_wires, num_pips, num_nhtypes;
+ RelPtr<BelInfoPOD> bels;
+ RelPtr<LocWireInfoPOD> wires;
+ RelPtr<PipInfoPOD> pips;
+ RelPtr<LocNeighourhoodPOD> neighbourhoods;
+});
+
+// A physical (bitstream) tile; of which there may be more than
+// one in a logical tile (XY grid location).
+// Tile name is reconstructed {prefix}R{row}C{col}:{tiletype}
+NPNR_PACKED_STRUCT(struct PhysicalTileInfoPOD {
+ int32_t prefix; // tile name prefix IdString
+ int32_t tiletype; // tile type IdString
+});
+
+enum LocFlags : uint32_t
+{
+ LOC_LOGIC = 0x000001,
+ LOC_IO18 = 0x000002,
+ LOC_IO33 = 0x000004,
+ LOC_BRAM = 0x000008,
+ LOC_DSP = 0x000010,
+ LOC_IP = 0x000020,
+ LOC_CIB = 0x000040,
+ LOC_TAP = 0x001000,
+ LOC_SPINE = 0x002000,
+ LOC_TRUNK = 0x004000,
+ LOC_MIDMUX = 0x008000,
+ LOC_CMUX = 0x010000,
+};
+
+NPNR_PACKED_STRUCT(struct GridLocationPOD {
+ uint32_t loc_type;
+ uint32_t loc_flags;
+ uint16_t neighbourhood_type;
+ uint16_t num_phys_tiles;
+ RelPtr<PhysicalTileInfoPOD> phys_tiles;
+});
+
+enum PioSide : uint8_t
+{
+ PIO_LEFT = 0,
+ PIO_RIGHT = 1,
+ PIO_TOP = 2,
+ PIO_BOTTOM = 3
+};
+
+enum PioDqsFunction : uint8_t
+{
+ PIO_DQS_DQ = 0,
+ PIO_DQS_DQS = 1,
+ PIO_DQS_DQSN = 2
+};
+
+NPNR_PACKED_STRUCT(struct PackageInfoPOD {
+ RelPtr<char> full_name; // full package name, e.g. CABGA400
+ RelPtr<char> short_name; // name used in part number, e.g. BG400
+});
+
+NPNR_PACKED_STRUCT(struct PadInfoPOD {
+ int16_t offset; // position offset of tile along side (-1 if not a regular PIO)
+ int8_t side; // PIO side (see PioSide enum)
+ int8_t pio_index; // index within IO tile
+
+ int16_t bank; // IO bank
+
+ int16_t dqs_group; // DQS group offset
+ int8_t dqs_func; // DQS function
+
+ int8_t vref_index; // VREF index in bank, or -1 if N/A
+
+ uint16_t num_funcs; // length of special function list
+ uint16_t padding; // padding for alignment
+
+ RelPtr<uint32_t> func_strs; // list of special function IdStrings
+
+ RelPtr<RelPtr<char>> pins; // package index --> package pin name
+});
+
+NPNR_PACKED_STRUCT(struct GlobalBranchInfoPOD {
+ uint16_t branch_col;
+ uint16_t from_col;
+ uint16_t tap_driver_col;
+ uint16_t tap_side;
+ uint16_t to_col;
+ uint16_t padding;
+});
+
+NPNR_PACKED_STRUCT(struct GlobalSpineInfoPOD {
+ uint16_t from_row;
+ uint16_t to_row;
+ uint16_t spine_row;
+ uint16_t padding;
+});
+
+NPNR_PACKED_STRUCT(struct GlobalHrowInfoPOD {
+ uint16_t hrow_col;
+ uint16_t padding;
+ uint32_t num_spine_cols;
+ RelPtr<uint32_t> spine_cols;
+});
+
+NPNR_PACKED_STRUCT(struct GlobalInfoPOD {
+ uint32_t num_branches, num_spines, num_hrows;
+ RelPtr<GlobalBranchInfoPOD> branches;
+ RelPtr<GlobalSpineInfoPOD> spines;
+ RelPtr<GlobalHrowInfoPOD> hrows;
+});
+
+NPNR_PACKED_STRUCT(struct ChipInfoPOD {
+ RelPtr<char> device_name;
+ uint16_t width;
+ uint16_t height;
+ uint32_t num_tiles;
+ uint32_t num_pads;
+ uint32_t num_packages;
+ RelPtr<GridLocationPOD> grid;
+ RelPtr<GlobalInfoPOD> globals;
+ RelPtr<PadInfoPOD> pads;
+ RelPtr<PackageInfoPOD> packages;
+});
+
+NPNR_PACKED_STRUCT(struct IdStringDBPOD {
+ uint32_t num_file_ids; // number of IDs loaded from constids.inc
+ uint32_t num_bba_ids; // number of IDs in BBA file
+ RelPtr<RelPtr<char>> bba_id_strs;
+});
+
+// Timing structures are generally sorted using IdString indices as keys for fast binary searches
+// All delays are integer picoseconds
+
+// Sort key: (to_port, from_port) for binary search by IdString
+NPNR_PACKED_STRUCT(struct CellPropDelayPOD {
+ int32_t from_port;
+ int32_t to_port;
+ int32_t min_delay;
+ int32_t max_delay;
+});
+
+// Sort key: (sig_port, clock_port) for binary search by IdString
+NPNR_PACKED_STRUCT(struct CellSetupHoldPOD {
+ int32_t sig_port;
+ int32_t clock_port;
+ int32_t min_setup;
+ int32_t max_setup;
+ int32_t min_hold;
+ int32_t max_hold;
+});
+
+// Sort key: (cell_type, cell_variant) for binary search by IdString
+NPNR_PACKED_STRUCT(struct CellTimingPOD {
+ int32_t cell_type;
+ int32_t cell_variant;
+ int32_t num_prop_delays;
+ int32_t num_setup_holds;
+ RelPtr<CellPropDelayPOD> prop_delays;
+ RelPtr<CellSetupHoldPOD> setup_holds;
+});
+
+NPNR_PACKED_STRUCT(struct PipTimingPOD {
+ int32_t min_delay;
+ int32_t max_delay;
+ // fanout adder seemingly unused by nexus, reserved for future ECP5 etc support
+ int32_t min_fanout_adder;
+ int32_t max_fanout_adder;
+});
+
+NPNR_PACKED_STRUCT(struct SpeedGradePOD {
+ RelPtr<char> name;
+ int32_t num_cell_types;
+ int32_t num_pip_classes;
+ RelPtr<CellTimingPOD> cell_types;
+ RelPtr<PipTimingPOD> pip_classes;
+});
+
+NPNR_PACKED_STRUCT(struct DatabasePOD {
+ uint32_t version;
+ uint32_t num_chips;
+ uint32_t num_loctypes;
+ uint32_t num_speed_grades;
+ RelPtr<char> family;
+ RelPtr<ChipInfoPOD> chips;
+ RelPtr<LocTypePOD> loctypes;
+ RelPtr<SpeedGradePOD> speed_grades;
+ RelPtr<IdStringDBPOD> ids;
+});
+
+// -----------------------------------------------------------------------
+
+// Helper functions for database access
+namespace {
+template <typename Id> const LocTypePOD &chip_loc_data(const DatabasePOD *db, const ChipInfoPOD *chip, const Id &id)
+{
+ return db->loctypes[chip->grid[id.tile].loc_type];
+}
+
+template <typename Id>
+const LocNeighourhoodPOD &chip_nh_data(const DatabasePOD *db, const ChipInfoPOD *chip, const Id &id)
+{
+ auto &t = chip->grid[id.tile];
+ return db->loctypes[t.loc_type].neighbourhoods[t.neighbourhood_type];
+}
+
+inline const BelInfoPOD &chip_bel_data(const DatabasePOD *db, const ChipInfoPOD *chip, BelId id)
+{
+ return chip_loc_data(db, chip, id).bels[id.index];
+}
+inline const LocWireInfoPOD &chip_wire_data(const DatabasePOD *db, const ChipInfoPOD *chip, WireId id)
+{
+ return chip_loc_data(db, chip, id).wires[id.index];
+}
+inline const PipInfoPOD &chip_pip_data(const DatabasePOD *db, const ChipInfoPOD *chip, PipId id)
+{
+ return chip_loc_data(db, chip, id).pips[id.index];
+}
+inline bool chip_rel_tile(const ChipInfoPOD *chip, int32_t base, int16_t rel_x, int16_t rel_y, int32_t &next)
+{
+ int32_t curr_x = base % chip->width;
+ int32_t curr_y = base / chip->width;
+ int32_t new_x = curr_x + rel_x;
+ int32_t new_y = curr_y + rel_y;
+ if (new_x < 0 || new_x >= chip->width)
+ return false;
+ if (new_y < 0 || new_y >= chip->height)
+ return false;
+ next = new_y * chip->width + new_x;
+ return true;
+}
+inline int32_t chip_tile_from_xy(const ChipInfoPOD *chip, int32_t x, int32_t y) { return y * chip->width + x; }
+inline bool chip_get_branch_loc(const ChipInfoPOD *chip, int32_t x, int32_t &branch_x)
+{
+ for (int i = 0; i < int(chip->globals->num_branches); i++) {
+ auto &b = chip->globals->branches[i];
+ if (x >= b.from_col && x <= b.to_col) {
+ branch_x = b.branch_col;
+ return true;
+ }
+ }
+ return false;
+}
+inline bool chip_get_spine_loc(const ChipInfoPOD *chip, int32_t x, int32_t y, int32_t &spine_x, int32_t &spine_y)
+{
+ bool y_found = false;
+ for (int i = 0; i < int(chip->globals->num_spines); i++) {
+ auto &s = chip->globals->spines[i];
+ if (y >= s.from_row && y <= s.to_row) {
+ spine_y = s.spine_row;
+ y_found = true;
+ break;
+ }
+ }
+ if (!y_found)
+ return false;
+ for (int i = 0; i < int(chip->globals->num_hrows); i++) {
+ auto &hr = chip->globals->hrows[i];
+ for (int j = 0; j < int(hr.num_spine_cols); j++) {
+ int32_t sc = hr.spine_cols[j];
+ if (std::abs(sc - x) < 3) {
+ spine_x = sc;
+ return true;
+ }
+ }
+ }
+ return false;
+}
+inline bool chip_get_hrow_loc(const ChipInfoPOD *chip, int32_t x, int32_t y, int32_t &hrow_x, int32_t &hrow_y)
+{
+ bool y_found = false;
+ for (int i = 0; i < int(chip->globals->num_spines); i++) {
+ auto &s = chip->globals->spines[i];
+ if (std::abs(y - s.spine_row) < 3) {
+ hrow_y = s.spine_row;
+ y_found = true;
+ break;
+ }
+ }
+ if (!y_found)
+ return false;
+ for (int i = 0; i < int(chip->globals->num_hrows); i++) {
+ auto &hr = chip->globals->hrows[i];
+ for (int j = 0; j < int(hr.num_spine_cols); j++) {
+ int32_t sc = hr.spine_cols[j];
+ if (std::abs(sc - x) < 3) {
+ hrow_x = hr.hrow_col;
+ return true;
+ }
+ }
+ }
+ return false;
+}
+inline bool chip_branch_tile(const ChipInfoPOD *chip, int32_t x, int32_t y, int32_t &next)
+{
+ int32_t branch_x;
+ if (!chip_get_branch_loc(chip, x, branch_x))
+ return false;
+ next = chip_tile_from_xy(chip, branch_x, y);
+ return true;
+}
+inline bool chip_rel_loc_tile(const ChipInfoPOD *chip, int32_t base, const RelWireInfoPOD &rel, int32_t &next)
+{
+ int32_t curr_x = base % chip->width;
+ int32_t curr_y = base / chip->width;
+ switch (rel.loc_type) {
+ case REL_LOC_XY:
+ return chip_rel_tile(chip, base, rel.rel_x, rel.rel_y, next);
+ case REL_LOC_BRANCH:
+ return chip_branch_tile(chip, curr_x, curr_y, next);
+ case REL_LOC_BRANCH_L:
+ return chip_branch_tile(chip, curr_x - 2, curr_y, next);
+ case REL_LOC_BRANCH_R:
+ return chip_branch_tile(chip, curr_x + 2, curr_y, next);
+ case REL_LOC_SPINE: {
+ int32_t spine_x, spine_y;
+ if (!chip_get_spine_loc(chip, curr_x, curr_y, spine_x, spine_y))
+ return false;
+ next = chip_tile_from_xy(chip, spine_x, spine_y);
+ return true;
+ }
+ case REL_LOC_HROW: {
+ int32_t hrow_x, hrow_y;
+ if (!chip_get_hrow_loc(chip, curr_x, curr_y, hrow_x, hrow_y))
+ return false;
+ next = chip_tile_from_xy(chip, hrow_x, hrow_y);
+ return true;
+ }
+ case REL_LOC_GLOBAL:
+ case REL_LOC_VCC:
+ next = 0;
+ return true;
+ default:
+ return false;
+ }
+}
+inline WireId chip_canonical_wire(const DatabasePOD *db, const ChipInfoPOD *chip, int32_t tile, uint16_t index)
+{
+ WireId wire{tile, index};
+ // `tile` is the primary location for the wire, so ID is already canonical
+ if (chip_wire_data(db, chip, wire).flags & WIRE_PRIMARY)
+ return wire;
+ // Not primary; find the primary location which forms the canonical ID
+ auto &nd = chip_nh_data(db, chip, wire);
+ auto &wn = nd.wire_neighbours[index];
+ for (size_t i = 0; i < wn.num_nwires; i++) {
+ auto &nw = wn.neigh_wires[i];
+ if (nw.arc_flags & LOGICAL_TO_PRIMARY) {
+ if (chip_rel_loc_tile(chip, tile, nw, wire.tile)) {
+ wire.index = nw.wire_index;
+ break;
+ }
+ }
+ }
+ return wire;
+}
+inline bool chip_wire_is_primary(const DatabasePOD *db, const ChipInfoPOD *chip, int32_t tile, uint16_t index)
+{
+ WireId wire{tile, index};
+ // `tile` is the primary location for the wire, so ID is already canonical
+ if (chip_wire_data(db, chip, wire).flags & WIRE_PRIMARY)
+ return true;
+ // Not primary; find the primary location which forms the canonical ID
+ auto &nd = chip_nh_data(db, chip, wire);
+ auto &wn = nd.wire_neighbours[index];
+ for (size_t i = 0; i < wn.num_nwires; i++) {
+ auto &nw = wn.neigh_wires[i];
+ if (nw.arc_flags & LOGICAL_TO_PRIMARY) {
+ if (chip_rel_loc_tile(chip, tile, nw, wire.tile)) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+} // namespace
+
+// -----------------------------------------------------------------------
+
+struct BelIterator
+{
+ const DatabasePOD *db;
+ const ChipInfoPOD *chip;
+ int cursor_index;
+ int cursor_tile;
+
+ BelIterator operator++()
+ {
+ cursor_index++;
+ while (cursor_tile < int(chip->num_tiles) &&
+ cursor_index >= int(db->loctypes[chip->grid[cursor_tile].loc_type].num_bels)) {
+ cursor_index = 0;
+ cursor_tile++;
+ }
+ return *this;
+ }
+ BelIterator operator++(int)
+ {
+ BelIterator prior(*this);
+ ++(*this);
+ return prior;
+ }
+
+ bool operator!=(const BelIterator &other) const
+ {
+ return cursor_index != other.cursor_index || cursor_tile != other.cursor_tile;
+ }
+
+ bool operator==(const BelIterator &other) const
+ {
+ return cursor_index == other.cursor_index && cursor_tile == other.cursor_tile;
+ }
+
+ BelId operator*() const
+ {
+ BelId ret;
+ ret.tile = cursor_tile;
+ ret.index = cursor_index;
+ return ret;
+ }
+};
+
+struct BelRange
+{
+ BelIterator b, e;
+ BelIterator begin() const { return b; }
+ BelIterator end() const { return e; }
+};
+
+// -----------------------------------------------------------------------
+
+struct WireIterator
+{
+ const DatabasePOD *db;
+ const ChipInfoPOD *chip;
+ int cursor_index;
+ int cursor_tile = 0;
+
+ WireIterator operator++()
+ {
+ // Iterate over nodes first, then tile wires that aren't nodes
+ do {
+ cursor_index++;
+ while (cursor_tile < int(chip->num_tiles) &&
+ cursor_index >= int(db->loctypes[chip->grid[cursor_tile].loc_type].num_wires)) {
+ cursor_index = 0;
+ cursor_tile++;
+ }
+ } while (cursor_tile < int(chip->num_tiles) && !chip_wire_is_primary(db, chip, cursor_tile, cursor_index));
+
+ return *this;
+ }
+ WireIterator operator++(int)
+ {
+ WireIterator prior(*this);
+ ++(*this);
+ return prior;
+ }
+
+ bool operator!=(const WireIterator &other) const
+ {
+ return cursor_index != other.cursor_index || cursor_tile != other.cursor_tile;
+ }
+
+ bool operator==(const WireIterator &other) const
+ {
+ return cursor_index == other.cursor_index && cursor_tile == other.cursor_tile;
+ }
+
+ WireId operator*() const
+ {
+ WireId ret;
+ ret.tile = cursor_tile;
+ ret.index = cursor_index;
+ return ret;
+ }
+};
+
+struct WireRange
+{
+ WireIterator b, e;
+ WireIterator begin() const { return b; }
+ WireIterator end() const { return e; }
+};
+
+// Iterate over all neighour wires for a wire
+struct NeighWireIterator
+{
+ const DatabasePOD *db;
+ const ChipInfoPOD *chip;
+ WireId baseWire;
+ int cursor = -1;
+
+ void operator++()
+ {
+ auto &wn = chip_nh_data(db, chip, baseWire).wire_neighbours[baseWire.index];
+ int32_t tile;
+ do
+ cursor++;
+ while (cursor < int(wn.num_nwires) &&
+ ((wn.neigh_wires[cursor].arc_flags & LOGICAL_TO_PRIMARY) ||
+ !chip_rel_tile(chip, baseWire.tile, wn.neigh_wires[cursor].rel_x, wn.neigh_wires[cursor].rel_y, tile)));
+ }
+ bool operator!=(const NeighWireIterator &other) const { return cursor != other.cursor; }
+
+ // Returns a *denormalised* identifier that may be a non-primary wire (and thus should _not_ be used
+ // as a WireId in general as it will break invariants)
+ WireId operator*() const
+ {
+ if (cursor == -1) {
+ return baseWire;
+ } else {
+ auto &nw = chip_nh_data(db, chip, baseWire).wire_neighbours[baseWire.index].neigh_wires[cursor];
+ WireId result;
+ result.index = nw.wire_index;
+ if (!chip_rel_tile(chip, baseWire.tile, nw.rel_x, nw.rel_y, result.tile))
+ return WireId();
+ return result;
+ }
+ }
+};
+
+struct NeighWireRange
+{
+ NeighWireIterator b, e;
+ NeighWireIterator begin() const { return b; }
+ NeighWireIterator end() const { return e; }
+};
+
+// -----------------------------------------------------------------------
+
+struct AllPipIterator
+{
+ const DatabasePOD *db;
+ const ChipInfoPOD *chip;
+ int cursor_index;
+ int cursor_tile;
+
+ AllPipIterator operator++()
+ {
+ cursor_index++;
+ while (cursor_tile < int(chip->num_tiles) &&
+ cursor_index >= int(db->loctypes[chip->grid[cursor_tile].loc_type].num_pips)) {
+ cursor_index = 0;
+ cursor_tile++;
+ }
+ return *this;
+ }
+ AllPipIterator operator++(int)
+ {
+ AllPipIterator prior(*this);
+ ++(*this);
+ return prior;
+ }
+
+ bool operator!=(const AllPipIterator &other) const
+ {
+ return cursor_index != other.cursor_index || cursor_tile != other.cursor_tile;
+ }
+
+ bool operator==(const AllPipIterator &other) const
+ {
+ return cursor_index == other.cursor_index && cursor_tile == other.cursor_tile;
+ }
+
+ PipId operator*() const
+ {
+ PipId ret;
+ ret.tile = cursor_tile;
+ ret.index = cursor_index;
+ return ret;
+ }
+};
+
+struct AllPipRange
+{
+ AllPipIterator b, e;
+ AllPipIterator begin() const { return b; }
+ AllPipIterator end() const { return e; }
+};
+
+// -----------------------------------------------------------------------
+
+struct UpDownhillPipIterator
+{
+ const DatabasePOD *db;
+ const ChipInfoPOD *chip;
+ NeighWireIterator twi, twi_end;
+ int cursor = -1;
+ bool uphill = false;
+
+ void operator++()
+ {
+ cursor++;
+ while (true) {
+ if (!(twi != twi_end))
+ break;
+ WireId w = *twi;
+ auto &tile = db->loctypes[chip->grid[w.tile].loc_type];
+ if (cursor < int(uphill ? tile.wires[w.index].num_uphill : tile.wires[w.index].num_downhill))
+ break;
+ ++twi;
+ cursor = 0;
+ }
+ }
+ bool operator!=(const UpDownhillPipIterator &other) const { return twi != other.twi || cursor != other.cursor; }
+
+ PipId operator*() const
+ {
+ PipId ret;
+ WireId w = *twi;
+ ret.tile = w.tile;
+ auto &tile = db->loctypes[chip->grid[w.tile].loc_type];
+ ret.index = uphill ? tile.wires[w.index].pips_uh[cursor] : tile.wires[w.index].pips_dh[cursor];
+ return ret;
+ }
+};
+
+struct UpDownhillPipRange
+{
+ UpDownhillPipIterator b, e;
+ UpDownhillPipIterator begin() const { return b; }
+ UpDownhillPipIterator end() const { return e; }
+};
+
+struct WireBelPinIterator
+{
+ const DatabasePOD *db;
+ const ChipInfoPOD *chip;
+ NeighWireIterator twi, twi_end;
+ int cursor = -1;
+
+ void operator++()
+ {
+ cursor++;
+ while (true) {
+ if (!(twi != twi_end))
+ break;
+ if (cursor < chip_wire_data(db, chip, *twi).num_bpins)
+ break;
+ ++twi;
+ cursor = 0;
+ }
+ }
+ bool operator!=(const WireBelPinIterator &other) const { return twi != other.twi || cursor != other.cursor; }
+
+ BelPin operator*() const
+ {
+ BelPin ret;
+ WireId w = *twi;
+ auto &bp = chip_wire_data(db, chip, w).bel_pins[cursor];
+ ret.bel.tile = w.tile;
+ ret.bel.index = bp.bel;
+ ret.pin = IdString(bp.pin);
+ return ret;
+ }
+};
+
+struct WireBelPinRange
+{
+ WireBelPinIterator b, e;
+ WireBelPinIterator begin() const { return b; }
+ WireBelPinIterator end() const { return e; }
+};
+
+// -----------------------------------------------------------------------
+
+// This enum captures different 'styles' of cell pins
+// This is a combination of the modes available for a pin (tied high, low or inverted)
+// and the default value to set it to not connected
+enum CellPinStyle
+{
+ PINOPT_NONE = 0x0, // no options, just signal as-is
+ PINOPT_LO = 0x1, // can be tied low
+ PINOPT_HI = 0x2, // can be tied high
+ PINOPT_INV = 0x4, // can be inverted
+
+ PINOPT_LOHI = 0x3, // can be tied low or high
+ PINOPT_LOHIINV = 0x7, // can be tied low or high; or inverted
+
+ PINOPT_MASK = 0x7,
+
+ PINDEF_NONE = 0x00, // leave disconnected
+ PINDEF_0 = 0x10, // connect to 0 if not used
+ PINDEF_1 = 0x20, // connect to 1 if not used
+
+ PINDEF_MASK = 0x30,
+
+ PINGLB_CLK = 0x100, // pin is a 'clock' for global purposes
+
+ PINGLB_MASK = 0x100,
+
+ PINBIT_GATED = 0x1000, // pin must be enabled in bitstream if used
+ PINBIT_1 = 0x2000, // pin has an explicit bit that must be set if tied to 1
+ PINBIT_CIBMUX = 0x4000, // pin's CIBMUX must be floating for pin to be 1
+
+ PINSTYLE_NONE = 0x0000, // default
+ PINSTYLE_CIB = 0x4012, // 'CIB' signal, floats high but explicitly zeroed if not used
+ PINSTYLE_CLK = 0x0107, // CLK type signal, invertible and defaults to disconnected
+ PINSTYLE_CE = 0x0027, // CE type signal, invertible and defaults to enabled
+ PINSTYLE_LSR = 0x0017, // LSR type signal, invertible and defaults to not reset
+ PINSTYLE_DEDI = 0x0000, // dedicated signals, leave alone
+ PINSTYLE_PU = 0x4022, // signals that float high and default high
+ PINSTYLE_T = 0x4027, // PIO 'T' signal
+
+ PINSTYLE_ADLSB = 0x4017, // special case of the EBR address MSBs
+ PINSTYLE_INV_PD = 0x0017, // invertible, pull down by default
+ PINSTYLE_INV_PU = 0x4027, // invertible, pull up by default
+
+ PINSTYLE_IOL_CE = 0x2027, // CE type signal, with explicit 'const-1' config bit
+ PINSTYLE_GATE = 0x1011, // gated signal that defaults to 0
+};
+
+// This represents the mux options for a pin
+enum CellPinMux
+{
+ PINMUX_SIG = 0,
+ PINMUX_0 = 1,
+ PINMUX_1 = 2,
+ PINMUX_INV = 3,
+};
+
+// This represents the various kinds of IO pins
+enum IOStyle
+{
+ IOBANK_WR = 0x1, // needs wide range IO bank
+ IOBANK_HP = 0x2, // needs high perf IO bank
+
+ IOMODE_REF = 0x10, // IO is referenced
+ IOMODE_DIFF = 0x20, // IO is true differential
+ IOMODE_PSEUDO_DIFF = 0x40, // IO is pseduo differential
+
+ IOSTYLE_SE_WR = 0x01, // single ended, wide range
+ IOSTYLE_SE_HP = 0x02, // single ended, high perf
+ IOSTYLE_PD_WR = 0x41, // pseudo diff, wide range
+
+ IOSTYLE_REF_HP = 0x12, // referenced high perf
+ IOSTYLE_DIFF_HP = 0x22, // differential high perf
+};
+
+struct IOTypeData
+{
+ IOStyle style;
+ int vcco; // required Vcco in 10mV
+};
+
+// -----------------------------------------------------------------------
+
+const int bba_version =
+#include "bba_version.inc"
+ ;
+
+struct ArchArgs
+{
+ std::string device;
+};
+
+struct Arch : BaseCtx
+{
+ ArchArgs args;
+ std::string family, device, package, speed, rating, variant;
+ Arch(ArchArgs args);
+
+ // -------------------------------------------------
+
+ // Database references
+ boost::iostreams::mapped_file_source blob_file;
+ const DatabasePOD *db;
+ const ChipInfoPOD *chip_info;
+ const SpeedGradePOD *speed_grade;
+
+ int package_idx;
+
+ // Binding states
+ struct LogicTileStatus
+ {
+ struct SliceStatus
+ {
+ bool valid = true, dirty = true;
+ } slices[4];
+ struct HalfTileStatus
+ {
+ bool valid = true, dirty = true;
+ } halfs[2];
+ CellInfo *cells[32];
+ };
+
+ struct TileStatus
+ {
+ std::vector<CellInfo *> boundcells;
+ std::vector<BelId> bels_by_z;
+ LogicTileStatus *lts = nullptr;
+ ~TileStatus() { delete lts; }
+ };
+
+ std::vector<TileStatus> tileStatus;
+ std::unordered_map<WireId, NetInfo *> wire_to_net;
+ std::unordered_map<PipId, NetInfo *> pip_to_net;
+
+ // -------------------------------------------------
+
+ std::string getChipName() const;
+
+ IdString archId() const { return id("nexus"); }
+ ArchArgs archArgs() const { return args; }
+ IdString archArgsToId(ArchArgs args) const;
+
+ int getGridDimX() const { return chip_info->width; }
+ int getGridDimY() const { return chip_info->height; }
+ int getTileBelDimZ(int, int) const { return 256; }
+ int getTilePipDimZ(int, int) const { return 1; }
+
+ // -------------------------------------------------
+
+ BelId getBelByName(IdString name) const;
+
+ IdString getBelName(BelId bel) const
+ {
+ std::string name = "X";
+ name += std::to_string(bel.tile % chip_info->width);
+ name += "/Y";
+ name += std::to_string(bel.tile / chip_info->width);
+ name += "/";
+ name += nameOf(IdString(bel_data(bel).name));
+ return id(name);
+ }
+
+ uint32_t getBelChecksum(BelId bel) const { return (bel.tile << 16) ^ bel.index; }
+
+ void bindBel(BelId bel, CellInfo *cell, PlaceStrength strength)
+ {
+ NPNR_ASSERT(bel != BelId());
+ NPNR_ASSERT(tileStatus[bel.tile].boundcells[bel.index] == nullptr);
+ tileStatus[bel.tile].boundcells[bel.index] = cell;
+ cell->bel = bel;
+ cell->belStrength = strength;
+ refreshUiBel(bel);
+
+ if (bel_tile_is(bel, LOC_LOGIC))
+ update_logic_bel(bel, cell);
+ }
+
+ void unbindBel(BelId bel)
+ {
+ NPNR_ASSERT(bel != BelId());
+ NPNR_ASSERT(tileStatus[bel.tile].boundcells[bel.index] != nullptr);
+
+ if (bel_tile_is(bel, LOC_LOGIC))
+ update_logic_bel(bel, nullptr);
+
+ tileStatus[bel.tile].boundcells[bel.index]->bel = BelId();
+ tileStatus[bel.tile].boundcells[bel.index]->belStrength = STRENGTH_NONE;
+ tileStatus[bel.tile].boundcells[bel.index] = nullptr;
+ refreshUiBel(bel);
+ }
+
+ bool checkBelAvail(BelId bel) const
+ {
+ NPNR_ASSERT(bel != BelId());
+ return tileStatus[bel.tile].boundcells[bel.index] == nullptr;
+ }
+
+ CellInfo *getBoundBelCell(BelId bel) const
+ {
+ NPNR_ASSERT(bel != BelId());
+ return tileStatus[bel.tile].boundcells[bel.index];
+ }
+
+ CellInfo *getConflictingBelCell(BelId bel) const
+ {
+ NPNR_ASSERT(bel != BelId());
+ return tileStatus[bel.tile].boundcells[bel.index];
+ }
+
+ BelRange getBels() const
+ {
+ BelRange range;
+ range.b.cursor_tile = 0;
+ range.b.cursor_index = -1;
+ range.b.chip = chip_info;
+ range.b.db = db;
+ ++range.b; //-1 and then ++ deals with the case of no bels in the first tile
+ range.e.cursor_tile = chip_info->width * chip_info->height;
+ range.e.cursor_index = 0;
+ range.e.chip = chip_info;
+ range.e.db = db;
+ return range;
+ }
+
+ Loc getBelLocation(BelId bel) const
+ {
+ NPNR_ASSERT(bel != BelId());
+ Loc loc;
+ loc.x = bel.tile % chip_info->width + bel_data(bel).rel_x;
+ loc.y = bel.tile / chip_info->width + bel_data(bel).rel_y;
+ loc.z = bel_data(bel).z;
+ return loc;
+ }
+
+ BelId getBelByLocation(Loc loc) const
+ {
+ BelId ret;
+ auto &t = tileStatus.at(loc.y * chip_info->width + loc.x);
+ if (loc.z >= int(t.bels_by_z.size()))
+ return BelId();
+ return t.bels_by_z.at(loc.z);
+ }
+
+ std::vector<BelId> getBelsByTile(int x, int y) const;
+
+ bool getBelGlobalBuf(BelId bel) const { return false; }
+
+ IdString getBelType(BelId bel) const
+ {
+ NPNR_ASSERT(bel != BelId());
+ return IdString(bel_data(bel).type);
+ }
+
+ std::vector<std::pair<IdString, std::string>> getBelAttrs(BelId bel) const;
+
+ WireId getBelPinWire(BelId bel, IdString pin) const;
+ PortType getBelPinType(BelId bel, IdString pin) const;
+ std::vector<IdString> getBelPins(BelId bel) const;
+
+ // -------------------------------------------------
+
+ WireId getWireByName(IdString name) const;
+ IdString getWireName(WireId wire) const
+ {
+ std::string name = "X";
+ name += std::to_string(wire.tile % chip_info->width);
+ name += "/Y";
+ name += std::to_string(wire.tile / chip_info->width);
+ name += "/";
+ name += nameOf(IdString(wire_data(wire).name));
+ return id(name);
+ }
+
+ IdString getWireType(WireId wire) const;
+ std::vector<std::pair<IdString, std::string>> getWireAttrs(WireId wire) const;
+
+ uint32_t getWireChecksum(WireId wire) const { return (wire.tile << 16) ^ wire.index; }
+
+ void bindWire(WireId wire, NetInfo *net, PlaceStrength strength)
+ {
+ NPNR_ASSERT(wire != WireId());
+ NPNR_ASSERT(wire_to_net[wire] == nullptr);
+ wire_to_net[wire] = net;
+ net->wires[wire].pip = PipId();
+ net->wires[wire].strength = strength;
+ refreshUiWire(wire);
+ }
+
+ void unbindWire(WireId wire)
+ {
+ NPNR_ASSERT(wire != WireId());
+ NPNR_ASSERT(wire_to_net[wire] != nullptr);
+
+ auto &net_wires = wire_to_net[wire]->wires;
+ auto it = net_wires.find(wire);
+ NPNR_ASSERT(it != net_wires.end());
+
+ auto pip = it->second.pip;
+ if (pip != PipId()) {
+ pip_to_net[pip] = nullptr;
+ }
+
+ net_wires.erase(it);
+ wire_to_net[wire] = nullptr;
+ refreshUiWire(wire);
+ }
+
+ bool checkWireAvail(WireId wire) const
+ {
+ NPNR_ASSERT(wire != WireId());
+ auto w2n = wire_to_net.find(wire);
+ return w2n == wire_to_net.end() || w2n->second == nullptr;
+ }
+
+ NetInfo *getBoundWireNet(WireId wire) const
+ {
+ NPNR_ASSERT(wire != WireId());
+ auto w2n = wire_to_net.find(wire);
+ return w2n == wire_to_net.end() ? nullptr : w2n->second;
+ }
+
+ NetInfo *getConflictingWireNet(WireId wire) const
+ {
+ NPNR_ASSERT(wire != WireId());
+ auto w2n = wire_to_net.find(wire);
+ return w2n == wire_to_net.end() ? nullptr : w2n->second;
+ }
+
+ WireId getConflictingWireWire(WireId wire) const { return wire; }
+
+ DelayInfo getWireDelay(WireId wire) const
+ {
+ DelayInfo delay;
+ delay.min_delay = 0;
+ delay.max_delay = 0;
+ return delay;
+ }
+
+ WireBelPinRange getWireBelPins(WireId wire) const
+ {
+ WireBelPinRange range;
+ NPNR_ASSERT(wire != WireId());
+ NeighWireRange nwr = neigh_wire_range(wire);
+ range.b.chip = chip_info;
+ range.b.db = db;
+ range.b.twi = nwr.b;
+ range.b.twi_end = nwr.e;
+ range.b.cursor = -1;
+ ++range.b;
+ range.e.chip = chip_info;
+ range.e.db = db;
+ range.e.twi = nwr.e;
+ range.e.twi_end = nwr.e;
+ range.e.cursor = 0;
+ return range;
+ }
+
+ WireRange getWires() const
+ {
+ WireRange range;
+ range.b.chip = chip_info;
+ range.b.db = db;
+ range.b.cursor_tile = 0;
+ range.b.cursor_index = -1;
+ ++range.b; //-1 and then ++ deals with the case of no wires in the first tile
+ range.e.chip = chip_info;
+ range.e.db = db;
+ range.e.cursor_tile = chip_info->num_tiles;
+ range.e.cursor_index = 0;
+ return range;
+ }
+
+ // -------------------------------------------------
+
+ PipId getPipByName(IdString name) const;
+ IdString getPipName(PipId pip) const;
+
+ void bindPip(PipId pip, NetInfo *net, PlaceStrength strength)
+ {
+ NPNR_ASSERT(pip != PipId());
+ NPNR_ASSERT(pip_to_net[pip] == nullptr);
+
+ WireId dst = canonical_wire(pip.tile, pip_data(pip).to_wire);
+ NPNR_ASSERT(wire_to_net[dst] == nullptr || wire_to_net[dst] == net);
+
+ pip_to_net[pip] = net;
+
+ wire_to_net[dst] = net;
+ net->wires[dst].pip = pip;
+ net->wires[dst].strength = strength;
+ refreshUiPip(pip);
+ refreshUiWire(dst);
+ }
+
+ void unbindPip(PipId pip)
+ {
+ NPNR_ASSERT(pip != PipId());
+ NPNR_ASSERT(pip_to_net[pip] != nullptr);
+
+ WireId dst = canonical_wire(pip.tile, pip_data(pip).to_wire);
+ NPNR_ASSERT(wire_to_net[dst] != nullptr);
+ wire_to_net[dst] = nullptr;
+ pip_to_net[pip]->wires.erase(dst);
+
+ pip_to_net[pip] = nullptr;
+ refreshUiPip(pip);
+ refreshUiWire(dst);
+ }
+
+ bool checkPipAvail(PipId pip) const
+ {
+ NPNR_ASSERT(pip != PipId());
+ return pip_to_net.find(pip) == pip_to_net.end() || pip_to_net.at(pip) == nullptr;
+ }
+
+ NetInfo *getBoundPipNet(PipId pip) const
+ {
+ NPNR_ASSERT(pip != PipId());
+ auto p2n = pip_to_net.find(pip);
+ return p2n == pip_to_net.end() ? nullptr : p2n->second;
+ }
+
+ WireId getConflictingPipWire(PipId pip) const { return getPipDstWire(pip); }
+
+ NetInfo *getConflictingPipNet(PipId pip) const
+ {
+ NPNR_ASSERT(pip != PipId());
+ auto p2n = pip_to_net.find(pip);
+ return p2n == pip_to_net.end() ? nullptr : p2n->second;
+ }
+
+ AllPipRange getPips() const
+ {
+ AllPipRange range;
+ range.b.cursor_tile = 0;
+ range.b.cursor_index = -1;
+ range.b.chip = chip_info;
+ range.b.db = db;
+ ++range.b; //-1 and then ++ deals with the case of no pips in the first tile
+ range.e.cursor_tile = chip_info->width * chip_info->height;
+ range.e.cursor_index = 0;
+ range.e.chip = chip_info;
+ range.e.db = db;
+ return range;
+ }
+
+ Loc getPipLocation(PipId pip) const
+ {
+ Loc loc;
+ loc.x = pip.tile % chip_info->width;
+ loc.y = pip.tile / chip_info->width;
+ loc.z = 0;
+ return loc;
+ }
+
+ IdString getPipType(PipId pip) const;
+ std::vector<std::pair<IdString, std::string>> getPipAttrs(PipId pip) const;
+
+ uint32_t getPipChecksum(PipId pip) const { return pip.tile << 16 | pip.index; }
+
+ WireId getPipSrcWire(PipId pip) const { return canonical_wire(pip.tile, pip_data(pip).from_wire); }
+
+ WireId getPipDstWire(PipId pip) const { return canonical_wire(pip.tile, pip_data(pip).to_wire); }
+
+ DelayInfo getPipDelay(PipId pip) const
+ {
+ DelayInfo delay;
+ auto &cls = speed_grade->pip_classes[pip_data(pip).timing_class];
+ delay.min_delay = std::max(0, cls.min_delay);
+ delay.max_delay = std::max(0, cls.max_delay);
+ return delay;
+ }
+
+ UpDownhillPipRange getPipsDownhill(WireId wire) const
+ {
+ UpDownhillPipRange range;
+ NPNR_ASSERT(wire != WireId());
+ NeighWireRange nwr = neigh_wire_range(wire);
+ range.b.chip = chip_info;
+ range.b.db = db;
+ range.b.twi = nwr.b;
+ range.b.twi_end = nwr.e;
+ range.b.cursor = -1;
+ range.b.uphill = false;
+ ++range.b;
+ range.e.chip = chip_info;
+ range.e.db = db;
+ range.e.twi = nwr.e;
+ range.e.twi_end = nwr.e;
+ range.e.cursor = 0;
+ range.e.uphill = false;
+ return range;
+ }
+
+ UpDownhillPipRange getPipsUphill(WireId wire) const
+ {
+ UpDownhillPipRange range;
+ NPNR_ASSERT(wire != WireId());
+ NeighWireRange nwr = neigh_wire_range(wire);
+ range.b.chip = chip_info;
+ range.b.db = db;
+ range.b.twi = nwr.b;
+ range.b.twi_end = nwr.e;
+ range.b.cursor = -1;
+ range.b.uphill = true;
+ ++range.b;
+ range.e.chip = chip_info;
+ range.e.db = db;
+ range.e.twi = nwr.e;
+ range.e.twi_end = nwr.e;
+ range.e.cursor = 0;
+ range.e.uphill = true;
+ return range;
+ }
+
+ UpDownhillPipRange getWireAliases(WireId wire) const
+ {
+ UpDownhillPipRange range;
+ range.b.cursor = 0;
+ range.b.twi.cursor = 0;
+ range.e.cursor = 0;
+ range.e.twi.cursor = 0;
+ return range;
+ }
+
+ // -------------------------------------------------
+
+ GroupId getGroupByName(IdString name) const { return GroupId(); }
+ IdString getGroupName(GroupId group) const { return IdString(); }
+ std::vector<GroupId> getGroups() const { return {}; }
+ std::vector<BelId> getGroupBels(GroupId group) const { return {}; }
+ std::vector<WireId> getGroupWires(GroupId group) const { return {}; }
+ std::vector<PipId> getGroupPips(GroupId group) const { return {}; }
+ std::vector<GroupId> getGroupGroups(GroupId group) const { return {}; }
+
+ // -------------------------------------------------
+
+ delay_t estimateDelay(WireId src, WireId dst) const;
+ delay_t predictDelay(const NetInfo *net_info, const PortRef &sink) const;
+ delay_t getDelayEpsilon() const { return 20; }
+ delay_t getRipupDelayPenalty() const { return 120; }
+ delay_t getWireRipupDelayPenalty(WireId wire) const;
+ float getDelayNS(delay_t v) const { return v * 0.001; }
+ DelayInfo getDelayFromNS(float ns) const
+ {
+ DelayInfo del;
+ del.min_delay = delay_t(ns * 1000);
+ del.max_delay = delay_t(ns * 1000);
+ return del;
+ }
+ uint32_t getDelayChecksum(delay_t v) const { return v; }
+ bool getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const;
+ ArcBounds getRouteBoundingBox(WireId src, WireId dst) const;
+
+ // for better DSP bounding boxes
+ void pre_routing();
+ std::unordered_set<WireId> dsp_wires;
+
+ // -------------------------------------------------
+
+ // Get the delay through a cell from one port to another, returning false
+ // if no path exists. This only considers combinational delays, as required by the Arch API
+ bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const;
+ // Get the port class, also setting clockInfoCount to the number of TimingClockingInfos associated with a port
+ TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const;
+ // Get the TimingClockingInfo of a port
+ TimingClockingInfo getPortClockingInfo(const CellInfo *cell, IdString port, int index) const;
+
+ // -------------------------------------------------
+
+ // Perform placement validity checks, returning false on failure (all
+ // implemented in arch_place.cc)
+
+ // Whether or not a given cell can be placed at a given Bel
+ // This is not intended for Bel type checks, but finer-grained constraints
+ // such as conflicting set/reset signals, etc
+ bool isValidBelForCell(CellInfo *cell, BelId bel) const;
+
+ // Return true whether all Bels at a given location are valid
+ bool isBelLocationValid(BelId bel) const;
+
+ // -------------------------------------------------
+
+ bool pack();
+ bool place();
+ bool route();
+
+ // arch-specific post-placement optimisations
+ void post_place_opt();
+
+ // -------------------------------------------------
+ // Assign architecure-specific arguments to nets and cells, which must be
+ // called between packing or further
+ // netlist modifications, and validity checks
+ void assignArchInfo();
+ void assignCellInfo(CellInfo *cell);
+
+ // -------------------------------------------------
+ // Arch-specific global routing
+ void route_globals();
+
+ // -------------------------------------------------
+
+ std::vector<GraphicElement> getDecalGraphics(DecalId decal) const;
+
+ DecalXY getBelDecal(BelId bel) const;
+ DecalXY getWireDecal(WireId wire) const;
+ DecalXY getPipDecal(PipId pip) const;
+ DecalXY getGroupDecal(GroupId group) const;
+
+ // -------------------------------------------------
+
+ static const std::string defaultPlacer;
+ static const std::vector<std::string> availablePlacers;
+ static const std::string defaultRouter;
+ static const std::vector<std::string> availableRouters;
+
+ // -------------------------------------------------
+
+ template <typename Id> const LocTypePOD &loc_data(const Id &id) const { return chip_loc_data(db, chip_info, id); }
+
+ template <typename Id> const LocNeighourhoodPOD &nh_data(const Id &id) const
+ {
+ return chip_nh_data(db, chip_info, id);
+ }
+
+ inline const BelInfoPOD &bel_data(BelId id) const { return chip_bel_data(db, chip_info, id); }
+ inline const LocWireInfoPOD &wire_data(WireId id) const { return chip_wire_data(db, chip_info, id); }
+ inline const PipInfoPOD &pip_data(PipId id) const { return chip_pip_data(db, chip_info, id); }
+ inline bool rel_tile(int32_t base, int16_t rel_x, int16_t rel_y, int32_t &next) const
+ {
+ return chip_rel_tile(chip_info, base, rel_x, rel_y, next);
+ }
+ inline WireId canonical_wire(int32_t tile, uint16_t index) const
+ {
+ WireId c = chip_canonical_wire(db, chip_info, tile, index);
+ return c;
+ }
+ IdString pip_src_wire_name(PipId pip) const
+ {
+ int wire = pip_data(pip).from_wire;
+ return db->loctypes[chip_info->grid[pip.tile].loc_type].wires[wire].name;
+ }
+ IdString pip_dst_wire_name(PipId pip) const
+ {
+ int wire = pip_data(pip).to_wire;
+ return db->loctypes[chip_info->grid[pip.tile].loc_type].wires[wire].name;
+ }
+
+ // -------------------------------------------------
+
+ typedef std::unordered_map<IdString, CellPinStyle> CellPinsData;
+
+ std::unordered_map<IdString, CellPinsData> cell_pins_db;
+ CellPinStyle get_cell_pin_style(const CellInfo *cell, IdString port) const;
+
+ void init_cell_pin_data();
+
+ // -------------------------------------------------
+
+ // Parse a possibly-Lattice-style (C literal in Verilog string) style parameter
+ Property parse_lattice_param(const CellInfo *ci, IdString prop, int width, int64_t defval) const;
+
+ // -------------------------------------------------
+
+ NeighWireRange neigh_wire_range(WireId wire) const
+ {
+ NeighWireRange range;
+ range.b.chip = chip_info;
+ range.b.db = db;
+ range.b.baseWire = wire;
+ range.b.cursor = -1;
+
+ range.e.chip = chip_info;
+ range.e.db = db;
+ range.e.baseWire = wire;
+ range.e.cursor = nh_data(wire).wire_neighbours[wire.index].num_nwires;
+ return range;
+ }
+
+ // -------------------------------------------------
+
+ template <typename TId> uint32_t tile_loc_flags(TId id) const { return chip_info->grid[id.tile].loc_flags; }
+
+ template <typename TId> bool tile_is(TId id, LocFlags lf) const { return tile_loc_flags(id) & lf; }
+
+ bool bel_tile_is(BelId bel, LocFlags lf) const
+ {
+ int32_t tile;
+ NPNR_ASSERT(rel_tile(bel.tile, bel_data(bel).rel_x, bel_data(bel).rel_y, tile));
+ return chip_info->grid[tile].loc_flags & lf;
+ }
+
+ // -------------------------------------------------
+
+ enum LogicBelZ
+ {
+ BEL_LUT0 = 0,
+ BEL_LUT1 = 1,
+ BEL_FF0 = 2,
+ BEL_FF1 = 3,
+ BEL_RAMW = 4,
+ };
+
+ void update_logic_bel(BelId bel, CellInfo *cell)
+ {
+ int z = bel_data(bel).z;
+ NPNR_ASSERT(z < 32);
+ auto &tts = tileStatus[bel.tile];
+ if (tts.lts == nullptr)
+ tts.lts = new LogicTileStatus();
+ auto &ts = *(tts.lts);
+ ts.cells[z] = cell;
+ switch (z & 0x7) {
+ case BEL_FF0:
+ case BEL_FF1:
+ case BEL_RAMW:
+ ts.halfs[(z >> 3) / 2].dirty = true;
+ /* fall-through */
+ case BEL_LUT0:
+ case BEL_LUT1:
+ ts.slices[(z >> 3)].dirty = true;
+ break;
+ }
+ }
+
+ bool nexus_logic_tile_valid(LogicTileStatus &lts) const;
+
+ CellPinMux get_cell_pinmux(const CellInfo *cell, IdString pin) const;
+ void set_cell_pinmux(CellInfo *cell, IdString pin, CellPinMux state);
+
+ // -------------------------------------------------
+
+ const PadInfoPOD *get_pkg_pin_data(const std::string &pin) const;
+ Loc get_pad_loc(const PadInfoPOD *pad) const;
+ BelId get_pad_pio_bel(const PadInfoPOD *pad) const;
+ const PadInfoPOD *get_bel_pad(BelId bel) const;
+ std::string get_pad_functions(const PadInfoPOD *pad) const;
+
+ // -------------------------------------------------
+ // Data about different IO standard, mostly used by bitgen
+ static const std::unordered_map<std::string, IOTypeData> io_types;
+ int get_io_type_vcc(const std::string &io_type) const;
+ bool is_io_type_diff(const std::string &io_type) const;
+ bool is_io_type_ref(const std::string &io_type) const;
+
+ // -------------------------------------------------
+ // Cell timing lookup helpers
+
+ bool is_dsp_cell(const CellInfo *cell) const;
+
+ // Given cell type and variant, get the index inside the speed grade timing data
+ int get_cell_timing_idx(IdString cell_type, IdString cell_variant = IdString()) const;
+ // Return true and set delay if a comb path exists in a given cell timing index
+ bool lookup_cell_delay(int type_idx, IdString from_port, IdString to_port, DelayInfo &delay) const;
+ // Get setup and hold time for a given cell timing index and signal/clock pair
+ void lookup_cell_setuphold(int type_idx, IdString from_port, IdString clock, DelayInfo &setup,
+ DelayInfo &hold) const;
+ // Get setup and hold time and associated clock for a given cell timing index and signal
+ void lookup_cell_setuphold_clock(int type_idx, IdString from_port, IdString &clock, DelayInfo &setup,
+ DelayInfo &hold) const;
+ // Similar to lookup_cell_delay but only needs the 'to' signal, intended for clk->out delays
+ void lookup_cell_clock_out(int type_idx, IdString to_port, IdString &clock, DelayInfo &delay) const;
+ // Attempt to look up port type based on database
+ TimingPortClass lookup_port_type(int type_idx, IdString port, PortType dir, IdString clock) const;
+ // -------------------------------------------------
+
+ // List of IO constraints, used by PDC parser
+ std::unordered_map<IdString, std::unordered_map<IdString, Property>> io_attr;
+
+ void read_pdc(std::istream &in);
+
+ // -------------------------------------------------
+ void write_fasm(std::ostream &out) const;
+};
+
+NEXTPNR_NAMESPACE_END
diff --git a/nexus/arch_place.cc b/nexus/arch_place.cc
new file mode 100644
index 00000000..0d141013
--- /dev/null
+++ b/nexus/arch_place.cc
@@ -0,0 +1,118 @@
+/*
+ * 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 "log.h"
+#include "nextpnr.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+bool Arch::nexus_logic_tile_valid(LogicTileStatus &lts) const
+{
+ for (int s = 0; s < 4; s++) {
+ if (lts.slices[s].dirty) {
+ lts.slices[s].valid = false;
+ lts.slices[s].dirty = false;
+ CellInfo *lut0 = lts.cells[(s << 3) | BEL_LUT0];
+ CellInfo *lut1 = lts.cells[(s << 3) | BEL_LUT1];
+ CellInfo *ff0 = lts.cells[(s << 3) | BEL_FF0];
+ CellInfo *ff1 = lts.cells[(s << 3) | BEL_FF1];
+
+ if (s == 2) {
+ CellInfo *ramw = lts.cells[(s << 3) | BEL_RAMW];
+ // Nothing else in SLICEC can be used if the RAMW is used
+ if (ramw != nullptr) {
+ if (lut0 != nullptr || lut1 != nullptr || ff0 != nullptr || ff1 != nullptr)
+ return false;
+ }
+ }
+
+ if (lut0 != nullptr) {
+ // Check for overuse of M signal
+ if (lut0->lutInfo.mux2_used && ff0 != nullptr && ff0->ffInfo.m != nullptr)
+ return false;
+ }
+ // Check for correct use of FF0 DI
+ if (ff0 != nullptr && ff0->ffInfo.di != nullptr &&
+ (lut0 == nullptr || (ff0->ffInfo.di != lut0->lutInfo.f && ff0->ffInfo.di != lut0->lutInfo.ofx)))
+ return false;
+ if (lut1 != nullptr) {
+ // LUT1 cannot contain a MUX2
+ if (lut1->lutInfo.mux2_used)
+ return false;
+ // If LUT1 is carry then LUT0 must be carry too
+ if (lut1->lutInfo.is_carry && (lut0 == nullptr || !lut0->lutInfo.is_carry))
+ return false;
+ if (!lut1->lutInfo.is_carry && lut0 != nullptr && lut0->lutInfo.is_carry)
+ return false;
+ }
+ // Check for correct use of FF1 DI
+ if (ff1 != nullptr && ff1->ffInfo.di != nullptr && (lut1 == nullptr || ff1->ffInfo.di != lut1->lutInfo.f))
+ return false;
+ lts.slices[s].valid = true;
+ } else if (!lts.slices[s].valid) {
+ return false;
+ }
+ }
+ for (int h = 0; h < 2; h++) {
+ if (lts.halfs[h].dirty) {
+ bool found_ff = false;
+ FFControlSet ctrlset;
+ for (int i = 0; i < 2; i++) {
+ for (auto bel : {BEL_FF0, BEL_FF1, BEL_RAMW}) {
+ if (bel == BEL_RAMW && (h != 1 || i != 0))
+ continue;
+ CellInfo *ci = lts.cells[(h * 2 + i) << 3 | bel];
+ if (ci == nullptr)
+ continue;
+ if (!found_ff) {
+ ctrlset = ci->ffInfo.ctrlset;
+ found_ff = true;
+ } else if (ci->ffInfo.ctrlset != ctrlset) {
+ return false;
+ }
+ }
+ }
+ } else if (!lts.halfs[h].valid) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const
+{
+ // FIXME
+ return true;
+}
+
+bool Arch::isBelLocationValid(BelId bel) const
+{
+ if (bel_tile_is(bel, LOC_LOGIC)) {
+ LogicTileStatus *lts = tileStatus[bel.tile].lts;
+ if (lts == nullptr)
+ return true;
+ else
+ return nexus_logic_tile_valid(*lts);
+ } else {
+ return true;
+ }
+}
+
+NEXTPNR_NAMESPACE_END
diff --git a/nexus/arch_pybindings.cc b/nexus/arch_pybindings.cc
new file mode 100644
index 00000000..caab8312
--- /dev/null
+++ b/nexus/arch_pybindings.cc
@@ -0,0 +1,72 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2018 Clifford Wolf <clifford@symbioticeda.com>
+ * Copyright (C) 2018 David Shah <david@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
+ * 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.
+ *
+ */
+
+#ifndef NO_PYTHON
+
+#include "arch_pybindings.h"
+#include "nextpnr.h"
+#include "pybindings.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+void arch_wrap_python(py::module &m)
+{
+ using namespace PythonConversion;
+ py::class_<ArchArgs>(m, "ArchArgs").def_readwrite("device", &ArchArgs::device);
+
+ py::class_<BelId>(m, "BelId").def_readwrite("index", &BelId::index).def_readwrite("tile", &BelId::tile);
+
+ py::class_<WireId>(m, "WireId").def_readwrite("index", &WireId::index).def_readwrite("tile", &WireId::tile);
+
+ py::class_<PipId>(m, "PipId").def_readwrite("index", &PipId::index).def_readwrite("tile", &PipId::tile);
+
+ py::class_<BelPin>(m, "BelPin").def_readwrite("bel", &BelPin::bel).def_readwrite("pin", &BelPin::pin);
+
+ auto arch_cls = py::class_<Arch, BaseCtx>(m, "Arch").def(py::init<ArchArgs>());
+ auto ctx_cls = py::class_<Context, Arch>(m, "Context")
+ .def("checksum", &Context::checksum)
+ .def("pack", &Context::pack)
+ .def("place", &Context::place)
+ .def("route", &Context::route);
+
+ typedef std::unordered_map<IdString, std::unique_ptr<CellInfo>> CellMap;
+ typedef std::unordered_map<IdString, std::unique_ptr<NetInfo>> NetMap;
+ typedef std::unordered_map<IdString, HierarchicalCell> HierarchyMap;
+ typedef std::unordered_map<IdString, IdString> AliasMap;
+
+ typedef UpDownhillPipRange PipRange;
+ typedef WireBelPinRange BelPinRange;
+
+#include "arch_pybindings_shared.h"
+
+ WRAP_RANGE(m, Bel, conv_to_str<BelId>);
+ WRAP_RANGE(m, Wire, conv_to_str<WireId>);
+ WRAP_RANGE(m, AllPip, conv_to_str<PipId>);
+ WRAP_RANGE(m, UpDownhillPip, conv_to_str<PipId>);
+ WRAP_RANGE(m, WireBelPin, wrap_context<BelPin>);
+
+ WRAP_MAP_UPTR(m, CellMap, "IdCellMap");
+ WRAP_MAP_UPTR(m, NetMap, "IdNetMap");
+ WRAP_MAP(m, HierarchyMap, wrap_context<HierarchicalCell &>, "HierarchyMap");
+}
+
+NEXTPNR_NAMESPACE_END
+
+#endif // NO_PYTHON
diff --git a/nexus/arch_pybindings.h b/nexus/arch_pybindings.h
new file mode 100644
index 00000000..326af306
--- /dev/null
+++ b/nexus/arch_pybindings.h
@@ -0,0 +1,98 @@
+/*
+ * 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.
+ *
+ */
+#ifndef ARCH_PYBINDINGS_H
+#define ARCH_PYBINDINGS_H
+#ifndef NO_PYTHON
+
+#include "nextpnr.h"
+#include "pybindings.h"
+#include "pywrappers.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+namespace PythonConversion {
+
+template <> struct string_converter<BelId>
+{
+ BelId from_str(Context *ctx, std::string name) { return ctx->getBelByName(ctx->id(name)); }
+
+ std::string to_str(Context *ctx, BelId id)
+ {
+ if (id == BelId())
+ throw bad_wrap();
+ return ctx->getBelName(id).str(ctx);
+ }
+};
+
+template <> struct string_converter<WireId>
+{
+ WireId from_str(Context *ctx, std::string name) { return ctx->getWireByName(ctx->id(name)); }
+
+ std::string to_str(Context *ctx, WireId id)
+ {
+ if (id == WireId())
+ throw bad_wrap();
+ return ctx->getWireName(id).str(ctx);
+ }
+};
+
+template <> struct string_converter<const WireId>
+{
+ WireId from_str(Context *ctx, std::string name) { return ctx->getWireByName(ctx->id(name)); }
+
+ std::string to_str(Context *ctx, WireId id)
+ {
+ if (id == WireId())
+ throw bad_wrap();
+ return ctx->getWireName(id).str(ctx);
+ }
+};
+
+template <> struct string_converter<PipId>
+{
+ PipId from_str(Context *ctx, std::string name) { return ctx->getPipByName(ctx->id(name)); }
+
+ std::string to_str(Context *ctx, PipId id)
+ {
+ if (id == PipId())
+ throw bad_wrap();
+ return ctx->getPipName(id).str(ctx);
+ }
+};
+
+template <> struct string_converter<BelPin>
+{
+ BelPin from_str(Context *ctx, std::string name)
+ {
+ NPNR_ASSERT_FALSE("string_converter<BelPin>::from_str not implemented");
+ }
+
+ std::string to_str(Context *ctx, BelPin pin)
+ {
+ if (pin.bel == BelId())
+ throw bad_wrap();
+ return ctx->getBelName(pin.bel).str(ctx) + "/" + pin.pin.str(ctx);
+ }
+};
+
+} // namespace PythonConversion
+
+NEXTPNR_NAMESPACE_END
+#endif
+#endif
diff --git a/nexus/archdefs.h b/nexus/archdefs.h
new file mode 100644
index 00000000..adc1342c
--- /dev/null
+++ b/nexus/archdefs.h
@@ -0,0 +1,253 @@
+/*
+ * 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.
+ *
+ */
+
+#ifndef NEXTPNR_H
+#error Include "archdefs.h" via "nextpnr.h" only.
+#endif
+
+NEXTPNR_NAMESPACE_BEGIN
+
+typedef int delay_t;
+
+struct DelayInfo
+{
+ delay_t min_delay = 0, max_delay = 0;
+
+ delay_t minRaiseDelay() const { return min_delay; }
+ delay_t maxRaiseDelay() const { return max_delay; }
+
+ delay_t minFallDelay() const { return min_delay; }
+ delay_t maxFallDelay() const { return max_delay; }
+
+ delay_t minDelay() const { return min_delay; }
+ delay_t maxDelay() const { return max_delay; }
+
+ DelayInfo operator+(const DelayInfo &other) const
+ {
+ DelayInfo ret;
+ ret.min_delay = this->min_delay + other.min_delay;
+ ret.max_delay = this->max_delay + other.max_delay;
+ return ret;
+ }
+};
+// https://bugreports.qt.io/browse/QTBUG-80789
+
+#ifndef Q_MOC_RUN
+enum ConstIds
+{
+ ID_NONE
+#define X(t) , ID_##t
+#include "constids.inc"
+#undef X
+};
+
+#define X(t) static constexpr auto id_##t = IdString(ID_##t);
+#include "constids.inc"
+#undef X
+#endif
+
+struct BelId
+{
+ int32_t tile = -1;
+ // PIP index in tile
+ int32_t index = -1;
+
+ BelId() = default;
+ inline BelId(int32_t tile, int32_t index) : tile(tile), index(index){};
+
+ bool operator==(const BelId &other) const { return tile == other.tile && index == other.index; }
+ bool operator!=(const BelId &other) const { return tile != other.tile || index != other.index; }
+ bool operator<(const BelId &other) const
+ {
+ return tile < other.tile || (tile == other.tile && index < other.index);
+ }
+};
+
+struct WireId
+{
+ int32_t tile = -1;
+ // Node wires: tile == -1; index = node index in chipdb
+ // Tile wires: tile != -1; index = wire index in tile
+ int32_t index = -1;
+
+ WireId() = default;
+ inline WireId(int32_t tile, int32_t index) : tile(tile), index(index){};
+
+ bool operator==(const WireId &other) const { return tile == other.tile && index == other.index; }
+ bool operator!=(const WireId &other) const { return tile != other.tile || index != other.index; }
+ bool operator<(const WireId &other) const
+ {
+ return tile < other.tile || (tile == other.tile && index < other.index);
+ }
+};
+
+struct PipId
+{
+ int32_t tile = -1;
+ // PIP index in tile
+ int32_t index = -1;
+
+ PipId() = default;
+ inline PipId(int32_t tile, int32_t index) : tile(tile), index(index){};
+
+ bool operator==(const PipId &other) const { return tile == other.tile && index == other.index; }
+ bool operator!=(const PipId &other) const { return tile != other.tile || index != other.index; }
+ bool operator<(const PipId &other) const
+ {
+ return tile < other.tile || (tile == other.tile && index < other.index);
+ }
+};
+
+struct GroupId
+{
+ enum : int8_t
+ {
+ TYPE_NONE,
+ } type = TYPE_NONE;
+ int8_t x = 0, y = 0;
+
+ bool operator==(const GroupId &other) const { return (type == other.type) && (x == other.x) && (y == other.y); }
+ bool operator!=(const GroupId &other) const { return (type != other.type) || (x != other.x) || (y == other.y); }
+};
+
+struct DecalId
+{
+ enum : int8_t
+ {
+ TYPE_NONE,
+ TYPE_BEL,
+ TYPE_WIRE,
+ TYPE_PIP,
+ TYPE_GROUP
+ } type = TYPE_NONE;
+ int32_t index = -1;
+ bool active = false;
+
+ bool operator==(const DecalId &other) const
+ {
+ return (type == other.type) && (index == other.index) && (active == other.active);
+ }
+ bool operator!=(const DecalId &other) const
+ {
+ return (type != other.type) || (index != other.index) || (active != other.active);
+ }
+};
+
+struct ArchNetInfo
+{
+ bool is_global;
+ bool is_clock, is_reset;
+};
+
+struct NetInfo;
+
+struct FFControlSet
+{
+ int clkmux, cemux, lsrmux;
+ bool async, regddr_en, gsr_en;
+ NetInfo *clk, *lsr, *ce;
+};
+
+inline bool operator!=(const FFControlSet &a, const FFControlSet &b)
+{
+ return (a.clkmux != b.clkmux) || (a.cemux != b.cemux) || (a.lsrmux != b.lsrmux) || (a.async != b.async) ||
+ (a.regddr_en != b.regddr_en) || (a.gsr_en != b.gsr_en) || (a.clk != b.clk) || (a.lsr != b.lsr) ||
+ (a.ce != b.ce);
+}
+
+struct ArchCellInfo
+{
+ union
+ {
+ struct
+ {
+ bool is_memory, is_carry, mux2_used;
+ NetInfo *f, *ofx;
+ } lutInfo;
+ struct
+ {
+ FFControlSet ctrlset;
+ NetInfo *di, *m;
+ } ffInfo;
+ };
+
+ int tmg_index = -1;
+ // Map from cell/bel ports to logical timing ports
+ std::unordered_map<IdString, IdString> tmg_portmap;
+};
+
+NEXTPNR_NAMESPACE_END
+
+namespace std {
+template <> struct hash<NEXTPNR_NAMESPACE_PREFIX BelId>
+{
+ std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX BelId &bel) const noexcept
+ {
+ std::size_t seed = 0;
+ boost::hash_combine(seed, hash<int>()(bel.tile));
+ boost::hash_combine(seed, hash<int>()(bel.index));
+ return seed;
+ }
+};
+
+template <> struct hash<NEXTPNR_NAMESPACE_PREFIX WireId>
+{
+ std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX WireId &wire) const noexcept
+ {
+ std::size_t seed = 0;
+ boost::hash_combine(seed, hash<int>()(wire.tile));
+ boost::hash_combine(seed, hash<int>()(wire.index));
+ return seed;
+ }
+};
+
+template <> struct hash<NEXTPNR_NAMESPACE_PREFIX PipId>
+{
+ std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX PipId &pip) const noexcept
+ {
+ std::size_t seed = 0;
+ boost::hash_combine(seed, hash<int>()(pip.tile));
+ boost::hash_combine(seed, hash<int>()(pip.index));
+ return seed;
+ }
+};
+
+template <> struct hash<NEXTPNR_NAMESPACE_PREFIX GroupId>
+{
+ std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX GroupId &group) const noexcept
+ {
+ std::size_t seed = 0;
+ boost::hash_combine(seed, hash<int>()(group.type));
+ boost::hash_combine(seed, hash<int>()(group.x));
+ boost::hash_combine(seed, hash<int>()(group.y));
+ return seed;
+ }
+};
+
+template <> struct hash<NEXTPNR_NAMESPACE_PREFIX DecalId>
+{
+ std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX DecalId &decal) const noexcept
+ {
+ std::size_t seed = 0;
+ boost::hash_combine(seed, hash<int>()(decal.type));
+ boost::hash_combine(seed, hash<int>()(decal.index));
+ return seed;
+ }
+};
+} // namespace std
diff --git a/nexus/bba_version.inc b/nexus/bba_version.inc
new file mode 100644
index 00000000..ec635144
--- /dev/null
+++ b/nexus/bba_version.inc
@@ -0,0 +1 @@
+9
diff --git a/nexus/constids.inc b/nexus/constids.inc
new file mode 100644
index 00000000..9b12d197
--- /dev/null
+++ b/nexus/constids.inc
@@ -0,0 +1,377 @@
+X(BEL)
+
+X(OXIDE_FF)
+X(CLK)
+X(CE)
+X(LSR)
+X(DI)
+X(M)
+X(Q)
+
+X(OXIDE_COMB)
+X(A)
+X(B)
+X(C)
+X(D)
+X(F)
+X(FCI)
+X(FCO)
+X(SEL)
+X(F1)
+X(OFX)
+X(WAD0)
+X(WAD1)
+X(WAD2)
+X(WAD3)
+X(WDI)
+X(WCK)
+X(WRE)
+
+X(RAMW)
+X(A0)
+X(A1)
+X(B0)
+X(B1)
+X(C0)
+X(C1)
+X(D0)
+X(D1)
+X(WADO0)
+X(WADO1)
+X(WADO2)
+X(WADO3)
+X(WCKO)
+X(WREO)
+X(WDO0)
+X(WDO1)
+X(WDO2)
+X(WDO3)
+
+X(SEIO33_CORE)
+X(T)
+X(I)
+X(O)
+X(I3CRESEN)
+X(I3CWKPU)
+
+X(SEIO18_CORE)
+X(DOLP)
+X(INLP)
+X(INADC)
+
+X(DIFFIO18_CORE)
+X(HSRXEN)
+X(HSTXEN)
+
+X(LUT4)
+X(INIT)
+X(Z)
+
+X(WIDEFN9)
+X(INIT0)
+X(INIT1)
+
+X(INV)
+X(VHI)
+X(VLO)
+
+X(FD1P3BX)
+X(FD1P3DX)
+X(FD1P3IX)
+X(FD1P3JX)
+X(CK)
+X(SP)
+X(PD)
+X(CD)
+X(GSR)
+
+X(CCU2)
+X(CIN)
+X(COUT)
+X(S0)
+X(S1)
+X(F0)
+
+X(CLKMUX)
+X(CEMUX)
+X(LSRMUX)
+X(REGDDR)
+X(SRMODE)
+X(REGSET)
+X(LSRMODE)
+
+X(MODE)
+X(INJECT)
+
+X(PLC)
+X(CIB)
+X(CIB_T)
+X(CIB_LR)
+
+X(IO_TYPE)
+
+
+X(OSCA)
+X(OSC)
+X(OSC_CORE)
+X(HFCLKOUT)
+X(LFCLKOUT)
+X(HF_CLK_DIV)
+X(HFOUTEN)
+
+X(OXIDE_EBR)
+X(CLKA)
+X(CLKB)
+X(CEA)
+X(CEB)
+X(CSA0)
+X(CSA1)
+X(CSA2)
+X(CSB0)
+X(CSB1)
+X(CSB2)
+X(ADA0)
+X(ADA1)
+X(ADA2)
+X(ADA3)
+X(ADB0)
+X(ADB1)
+X(WEA)
+X(WEB)
+X(RSTA)
+X(RSTB)
+
+X(LOC)
+
+X(IB)
+X(OB)
+X(OBZ)
+X(BB)
+X(BB_I3C_A)
+
+X(SEIO33)
+X(SEIO18)
+X(DIFFIO18)
+X(IOPAD)
+X(PADDO)
+X(PADDI)
+X(PADDT)
+
+X(PREADD9_CORE)
+X(MULT9_CORE)
+X(MULT18_CORE)
+X(REG18_CORE)
+X(MULT18X36_CORE)
+X(MULT36_CORE)
+X(ACC54_CORE)
+
+X(PREADD9)
+X(MULT9)
+X(MULT18)
+X(REG18)
+X(M18X36)
+X(MULT36)
+X(ACC54)
+
+X(MULT9X9)
+
+X(DCC)
+X(CLKI)
+X(CLKO)
+
+X(DPR16X4)
+X(INITVAL)
+X(DPRAM)
+
+X(DP16K)
+X(PDP16K)
+X(PDPSC16K)
+X(SP16K)
+X(FIFO16K)
+
+X(DP16K_MODE)
+X(PDP16K_MODE)
+X(PDPSC16K_MODE)
+X(SP16K_MODE)
+X(FIFO16K_MODE)
+
+X(DPSC512K)
+X(PDPSC512K)
+X(SP512K)
+
+X(DPSC512K_MODE)
+X(PDPSC512K_MODE)
+X(SP512K_MODE)
+
+X(WID)
+X(CSDECODE_A)
+X(CSDECODE_B)
+X(CSDECODE_R)
+X(CSDECODE_W)
+X(CSDECODE)
+
+X(CLKW)
+X(CLKR)
+X(CEW)
+X(CER)
+X(RST)
+
+X(DWS0)
+X(DWS1)
+X(DWS2)
+X(DWS3)
+X(DWS4)
+
+X(WEAMUX)
+
+X(VCC_DRV)
+
+X(RSTCL)
+X(CECL)
+X(B2)
+X(B3)
+X(B4)
+X(B5)
+X(B6)
+X(B7)
+X(B8)
+X(BSIGNED)
+X(C2)
+X(C3)
+X(C4)
+X(C5)
+X(C6)
+X(C7)
+X(C8)
+X(C9)
+
+X(RSTP)
+X(CEP)
+X(A2)
+X(A3)
+X(A4)
+X(A5)
+X(A6)
+X(A7)
+X(A8)
+X(ASIGNED)
+
+X(SFTCTRL0)
+X(SFTCTRL1)
+X(SFTCTRL2)
+X(SFTCTRL3)
+X(ROUNDEN)
+
+X(LOAD)
+X(M9ADDSUB1)
+X(M9ADDSUB0)
+X(ADDSUB1)
+X(ADDSUB0)
+
+X(CEO)
+X(RSTO)
+X(CEC)
+X(RSTC)
+X(SIGNEDI)
+X(CECIN)
+X(CECTRL)
+X(RSTCIN)
+X(RSTCTRL)
+
+X(SIGNEDSTATIC_EN)
+X(SUBSTRACT_EN)
+X(CSIGNED)
+X(BSIGNED_OPERAND_EN)
+X(BYPASS_PREADD9)
+X(REGBYPSBR0)
+X(REGBYPSBR1)
+X(REGBYPSBL)
+X(SHIFTBR)
+X(SHIFTBL)
+X(PREADDCAS_EN)
+X(SR_18BITSHIFT_EN)
+X(OPC)
+X(RESET)
+X(RESETMODE)
+
+X(ASIGNED_OPERAND_EN)
+X(BYPASS_MULT9)
+X(REGBYPSA1)
+X(REGBYPSA2)
+X(REGBYPSB)
+X(SHIFTA)
+
+X(REGBYPS)
+
+X(PP)
+
+X(SIGNEDA)
+X(SIGNEDB)
+X(RSTOUT)
+X(CEOUT)
+X(REGINPUTA)
+X(REGINPUTB)
+X(REGOUTPUT)
+
+X(MULT18X18)
+X(ROUNDBIT)
+X(ROUNDHALFUP)
+X(ROUNDRTZI)
+X(SFTEN)
+
+X(MULT18X36)
+X(MULT36X36H)
+X(MULT36X36)
+
+X(SIGNEDC)
+X(REGINPUTC)
+
+X(MULTPREADD9X9)
+X(MULTPREADD18X18)
+
+X(REGPIPELINE)
+X(REGADDSUB)
+X(REGLOADC)
+X(REGLOADC2)
+X(REGCIN)
+
+X(ACC108CASCADE)
+X(ACCUBYPS)
+X(ACCUMODE)
+X(ADDSUBSIGNREGBYPS1)
+X(ADDSUBSIGNREGBYPS2)
+X(ADDSUBSIGNREGBYPS3)
+X(ADDSUB_CTRL)
+X(CASCOUTREGBYPS)
+X(CINREGBYPS1)
+X(CINREGBYPS2)
+X(CINREGBYPS3)
+X(CONSTSEL)
+X(CREGBYPS1)
+X(CREGBYPS2)
+X(CREGBYPS3)
+X(DSPCASCADE)
+X(LOADREGBYPS1)
+X(LOADREGBYPS2)
+X(LOADREGBYPS3)
+X(M9ADDSUBREGBYPS1)
+X(M9ADDSUBREGBYPS2)
+X(M9ADDSUBREGBYPS3)
+X(M9ADDSUB_CTRL)
+X(OUTREGBYPS)
+X(SIGN)
+X(STATICOPCODE_EN)
+X(PROGCONST)
+
+X(MULTADDSUB18X18)
+X(MULTADDSUB36X36)
+
+X(CEPIPE)
+X(RSTPIPE)
+
+X(LOADC)
+X(ADDSUB)
+X(SIGNED)
+X(SUM0)
+X(SUM1)
+X(CINPUT)
diff --git a/nexus/family.cmake b/nexus/family.cmake
new file mode 100644
index 00000000..4ee65dbc
--- /dev/null
+++ b/nexus/family.cmake
@@ -0,0 +1,53 @@
+add_subdirectory(${family})
+message(STATUS "Using Nexus chipdb: ${NEXUS_CHIPDB}")
+
+set(chipdb_sources)
+set(chipdb_binaries)
+file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${family}/chipdb)
+foreach(subfamily ${NEXUS_FAMILIES})
+ set(chipdb_bba ${NEXUS_CHIPDB}/chipdb-${subfamily}.bba)
+ set(chipdb_bin ${family}/chipdb/chipdb-${subfamily}.bin)
+ set(chipdb_cc ${family}/chipdb/chipdb-${subfamily}.cc)
+ if(BBASM_MODE STREQUAL "binary")
+ add_custom_command(
+ OUTPUT ${chipdb_bin}
+ COMMAND bbasm ${BBASM_ENDIAN_FLAG} ${chipdb_bba} ${chipdb_bin}
+ DEPENDS bbasm chipdb-${family}-bbas ${chipdb_bba})
+ list(APPEND chipdb_binaries ${chipdb_bin})
+ elseif(BBASM_MODE STREQUAL "embed")
+ add_custom_command(
+ OUTPUT ${chipdb_cc} ${chipdb_bin}
+ COMMAND bbasm ${BBASM_ENDIAN_FLAG} --e ${chipdb_bba} ${chipdb_cc} ${chipdb_bin}
+ DEPENDS bbasm chipdb-${family}-bbas ${chipdb_bba})
+ list(APPEND chipdb_sources ${chipdb_cc})
+ list(APPEND chipdb_binaries ${chipdb_bin})
+ elseif(BBASM_MODE STREQUAL "string")
+ add_custom_command(
+ OUTPUT ${chipdb_cc}
+ COMMAND bbasm ${BBASM_ENDIAN_FLAG} --c ${chipdb_bba} ${chipdb_cc}
+ DEPENDS bbasm chipdb-${family}-bbas ${chipdb_bba})
+ list(APPEND chipdb_sources ${chipdb_cc})
+ endif()
+endforeach()
+if(WIN32)
+ set(chipdb_rc ${CMAKE_CURRENT_BINARY_DIR}/${family}/resource/chipdb.rc)
+ list(APPEND chipdb_sources ${chipdb_rc})
+
+ file(WRITE ${chipdb_rc})
+ foreach(subfamily ${NEXUS_FAMILIES})
+ file(APPEND ${chipdb_rc}
+ "${family}/chipdb-${subfamily}.bin RCDATA \"${CMAKE_CURRENT_BINARY_DIR}/${family}/chipdb/chipdb-${subfamily}.bin\"")
+ endforeach()
+endif()
+
+add_custom_target(chipdb-${family}-bins DEPENDS ${chipdb_sources} ${chipdb_binaries})
+
+add_library(chipdb-${family} OBJECT ${NEXUS_CHIPDB} ${chipdb_sources})
+add_dependencies(chipdb-${family} chipdb-${family}-bins)
+target_compile_options(chipdb-${family} PRIVATE -g0 -O0 -w)
+target_compile_definitions(chipdb-${family} PRIVATE NEXTPNR_NAMESPACE=nextpnr_${family})
+target_include_directories(chipdb-${family} PRIVATE ${family})
+
+foreach(family_target ${family_targets})
+ target_sources(${family_target} PRIVATE $<TARGET_OBJECTS:chipdb-${family}>)
+endforeach()
diff --git a/nexus/fasm.cc b/nexus/fasm.cc
new file mode 100644
index 00000000..5da809c6
--- /dev/null
+++ b/nexus/fasm.cc
@@ -0,0 +1,669 @@
+/*
+ * 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 "log.h"
+#include "nextpnr.h"
+#include "util.h"
+
+#include <queue>
+
+NEXTPNR_NAMESPACE_BEGIN
+namespace {
+struct NexusFasmWriter
+{
+ const Context *ctx;
+ std::ostream &out;
+ std::vector<std::string> fasm_ctx;
+
+ NexusFasmWriter(const Context *ctx, std::ostream &out) : ctx(ctx), out(out) {}
+
+ // Add a 'dot' prefix to the FASM context stack
+ void push(const std::string &x) { fasm_ctx.push_back(x); }
+
+ // Remove a prefix from the FASM context stack
+ void pop() { fasm_ctx.pop_back(); }
+
+ // Remove N prefices from the FASM context stack
+ void pop(int N)
+ {
+ for (int i = 0; i < N; i++)
+ fasm_ctx.pop_back();
+ }
+ bool last_was_blank = true;
+ // Insert a blank line if the last wasn't blank
+ void blank()
+ {
+ if (!last_was_blank)
+ out << std::endl;
+ last_was_blank = true;
+ }
+ // Write out all prefices from the stack, interspersed with .
+ void write_prefix()
+ {
+ for (auto &x : fasm_ctx)
+ out << x << ".";
+ last_was_blank = false;
+ }
+ // Write a single config bit; if value is true
+ void write_bit(const std::string &name, bool value = true)
+ {
+ if (value) {
+ write_prefix();
+ out << name << std::endl;
+ }
+ }
+ // Write a FASM attribute
+ void write_attribute(const std::string &key, const std::string &value, bool str = true)
+ {
+ std::string qu = str ? "\"" : "";
+ out << "{ " << key << "=" << qu << value << qu << " }" << std::endl;
+ last_was_blank = false;
+ }
+ // Write a FASM comment
+ void write_comment(const std::string &cmt) { out << "# " << cmt << std::endl; }
+ // Write a FASM bitvector; optionally inverting the values in the process
+ void write_vector(const std::string &name, const std::vector<bool> &value, bool invert = false)
+ {
+ write_prefix();
+ out << name << " = " << int(value.size()) << "'b";
+ for (auto bit : boost::adaptors::reverse(value))
+ out << ((bit ^ invert) ? '1' : '0');
+ out << std::endl;
+ }
+ // Write a FASM bitvector given an integer value
+ void write_int_vector(const std::string &name, uint64_t value, int width, bool invert = false)
+ {
+ std::vector<bool> bits(width, false);
+ for (int i = 0; i < width; i++)
+ bits[i] = (value & (1ULL << i)) != 0;
+ write_vector(name, bits, invert);
+ }
+ // Write an int vector param
+ void write_int_vector_param(const CellInfo *cell, const std::string &name, uint64_t defval, int width,
+ bool invert = false)
+ {
+ uint64_t value = int_or_default(cell->params, ctx->id(name), defval);
+ std::vector<bool> bits(width, false);
+ for (int i = 0; i < width; i++)
+ bits[i] = (value & (1ULL << i)) != 0;
+ write_vector(stringf("%s[%d:0]", name.c_str(), width - 1), bits, invert);
+ }
+ // Look up an enum value in a cell's parameters and write it to the FASM in name.value format
+ void write_enum(const CellInfo *cell, const std::string &name, const std::string &defval = "")
+ {
+ auto fnd = cell->params.find(ctx->id(name));
+ if (fnd == cell->params.end()) {
+ if (!defval.empty())
+ write_bit(stringf("%s.%s", name.c_str(), defval.c_str()));
+ } else {
+ write_bit(stringf("%s.%s", name.c_str(), fnd->second.c_str()));
+ }
+ }
+ // Look up an IO attribute in the cell's attributes and write it to the FASM in name.value format
+ void write_ioattr(const CellInfo *cell, const std::string &name, const std::string &defval = "")
+ {
+ auto fnd = cell->attrs.find(ctx->id(name));
+ if (fnd == cell->attrs.end()) {
+ if (!defval.empty())
+ write_bit(stringf("%s.%s", name.c_str(), defval.c_str()));
+ } else {
+ write_bit(stringf("%s.%s", name.c_str(), fnd->second.c_str()));
+ }
+ }
+ void write_ioattr_postfix(const CellInfo *cell, const std::string &name, const std::string &postfix,
+ const std::string &defval = "")
+ {
+ auto fnd = cell->attrs.find(ctx->id(name));
+ if (fnd == cell->attrs.end()) {
+ if (!defval.empty())
+ write_bit(stringf("%s_%s.%s", name.c_str(), postfix.c_str(), defval.c_str()));
+ } else {
+ write_bit(stringf("%s_%s.%s", name.c_str(), postfix.c_str(), fnd->second.c_str()));
+ }
+ }
+
+ // Gets the full name of a tile
+ std::string tile_name(int loc, const PhysicalTileInfoPOD &tile)
+ {
+ int r = loc / ctx->chip_info->width;
+ int c = loc % ctx->chip_info->width;
+ return stringf("%sR%dC%d__%s", ctx->nameOf(tile.prefix), r, c, ctx->nameOf(tile.tiletype));
+ }
+ // Look up a tile by location index and tile type
+ const PhysicalTileInfoPOD &tile_by_type_and_loc(int loc, IdString type)
+ {
+ auto &ploc = ctx->chip_info->grid[loc];
+ for (int i = 0; i < ploc.num_phys_tiles; i++) {
+ if (ploc.phys_tiles[i].tiletype == type.index)
+ return ploc.phys_tiles[i];
+ }
+ log_error("No tile of type %s found at location R%dC%d", ctx->nameOf(type), loc / ctx->chip_info->width,
+ loc % ctx->chip_info->width);
+ }
+ // Gets the single tile at a location
+ const PhysicalTileInfoPOD &tile_at_loc(int loc)
+ {
+ auto &ploc = ctx->chip_info->grid[loc];
+ NPNR_ASSERT(ploc.num_phys_tiles == 1);
+ return ploc.phys_tiles[0];
+ }
+ // Escape an internal prjoxide name for FASM by replacing : with __
+ std::string escape_name(const std::string &name)
+ {
+ std::string escaped;
+ for (char c : name) {
+ if (c == ':')
+ escaped += "__";
+ else
+ escaped += c;
+ }
+ return escaped;
+ }
+ // Push a tile name onto the prefix stack
+ void push_tile(int loc, IdString tile_type) { push(tile_name(loc, tile_by_type_and_loc(loc, tile_type))); }
+ void push_tile(int loc) { push(tile_name(loc, tile_at_loc(loc))); }
+ // Push a bel name onto the prefix stack
+ void push_belname(BelId bel) { push(ctx->nameOf(ctx->bel_data(bel).name)); }
+ // Push the tile group name corresponding to a bel onto the prefix stack
+ void push_belgroup(BelId bel)
+ {
+ int r = bel.tile / ctx->chip_info->width;
+ int c = bel.tile % ctx->chip_info->width;
+ auto bel_data = ctx->bel_data(bel);
+ r += bel_data.rel_y;
+ c += bel_data.rel_x;
+ std::string s = stringf("R%dC%d_%s", r, c, ctx->nameOf(ctx->bel_data(bel).name));
+ push(s);
+ }
+ // Push a bel's group and name
+ void push_bel(BelId bel)
+ {
+ push_belgroup(bel);
+ fasm_ctx.back() += stringf(".%s", ctx->nameOf(ctx->bel_data(bel).name));
+ }
+ // Write out a pip in tile.dst.src format
+ void write_pip(PipId pip)
+ {
+ auto &pd = ctx->pip_data(pip);
+ if (pd.flags & PIP_FIXED_CONN)
+ return;
+ std::string tile = tile_name(pip.tile, tile_by_type_and_loc(pip.tile, pd.tile_type));
+ std::string source_wire = escape_name(ctx->pip_src_wire_name(pip).str(ctx));
+ std::string dest_wire = escape_name(ctx->pip_dst_wire_name(pip).str(ctx));
+ out << stringf("%s.PIP.%s.%s", tile.c_str(), dest_wire.c_str(), source_wire.c_str()) << std::endl;
+ }
+ // Write out all the pips corresponding to a net
+ void write_net(const NetInfo *net)
+ {
+ write_comment(stringf("Net %s", ctx->nameOf(net)));
+ std::set<PipId> sorted_pips;
+ for (auto &w : net->wires)
+ if (w.second.pip != PipId())
+ sorted_pips.insert(w.second.pip);
+ for (auto p : sorted_pips)
+ write_pip(p);
+ blank();
+ }
+ // Find the CIBMUX output for a signal
+ WireId find_cibmux(const CellInfo *cell, IdString pin)
+ {
+ WireId cursor = ctx->getBelPinWire(cell->bel, pin);
+ if (cursor == WireId())
+ return WireId();
+ for (int i = 0; i < 10; i++) {
+ std::string cursor_name = IdString(ctx->wire_data(cursor).name).str(ctx);
+ if (cursor_name.find("JCIBMUXOUT") == 0) {
+ return cursor;
+ }
+ for (PipId pip : ctx->getPipsUphill(cursor))
+ if (ctx->checkPipAvail(pip)) {
+ cursor = ctx->getPipSrcWire(pip);
+ break;
+ }
+ }
+ return WireId();
+ }
+ // Write out the mux config for a cell
+ void write_cell_muxes(const CellInfo *cell)
+ {
+ for (auto port : sorted_cref(cell->ports)) {
+ // Only relevant to inputs
+ if (port.second.type != PORT_IN)
+ continue;
+ auto pin_style = ctx->get_cell_pin_style(cell, port.first);
+ auto pin_mux = ctx->get_cell_pinmux(cell, port.first);
+ // Invertible pins
+ if (pin_style & PINOPT_INV) {
+ if (pin_mux == PINMUX_INV || pin_mux == PINMUX_0)
+ write_bit(stringf("%sMUX.INV", ctx->nameOf(port.first)));
+ else if (pin_mux == PINMUX_SIG)
+ write_bit(stringf("%sMUX.%s", ctx->nameOf(port.first), ctx->nameOf(port.first)));
+ }
+ // Pins that must be explictly enabled
+ if ((pin_style & PINBIT_GATED) && (pin_mux == PINMUX_SIG))
+ write_bit(stringf("%sMUX.%s", ctx->nameOf(port.first), ctx->nameOf(port.first)));
+ // Pins that must be explictly set to 1 rather than just left floating
+ if ((pin_style & PINBIT_1) && (pin_mux == PINMUX_1))
+ write_bit(stringf("%sMUX.1", ctx->nameOf(port.first)));
+ // Handle CIB muxes - these must be set such that floating pins really are floating to VCC and not connected
+ // to another CIB signal
+ if ((pin_style & PINBIT_CIBMUX) && port.second.net == nullptr) {
+ WireId cibmuxout = find_cibmux(cell, port.first);
+ if (cibmuxout != WireId()) {
+ write_comment(stringf("CIBMUX for unused pin %s", ctx->nameOf(port.first)));
+ bool found = false;
+ for (PipId pip : ctx->getPipsUphill(cibmuxout)) {
+ if (ctx->checkPipAvail(pip) && ctx->checkWireAvail(ctx->getPipSrcWire(pip))) {
+ write_pip(pip);
+ found = true;
+ break;
+ }
+ }
+ NPNR_ASSERT(found);
+ }
+ }
+ }
+ }
+
+ // Write config for an OXIDE_COMB cell
+ void write_comb(const CellInfo *cell)
+ {
+ BelId bel = cell->bel;
+ int z = ctx->bel_data(bel).z;
+ int k = z & 0x1;
+ char slice = 'A' + (z >> 3);
+ push_tile(bel.tile, id_PLC);
+ push(stringf("SLICE%c", slice));
+ if (cell->params.count(id_INIT))
+ write_int_vector(stringf("K%d.INIT[15:0]", k), int_or_default(cell->params, id_INIT, 0), 16);
+ if (cell->lutInfo.is_carry) {
+ write_bit("MODE.CCU2");
+ write_enum(cell, "CCU2.INJECT", "NO");
+ }
+ pop(2);
+ }
+ // Write config for an OXIDE_FF cell
+ void write_ff(const CellInfo *cell)
+ {
+ BelId bel = cell->bel;
+ int z = ctx->bel_data(bel).z;
+ int k = z & 0x1;
+ char slice = 'A' + (z >> 3);
+ push_tile(bel.tile, id_PLC);
+ push(stringf("SLICE%c", slice));
+ push(stringf("REG%d", k));
+ write_bit("USED.YES");
+ write_enum(cell, "REGSET", "RESET");
+ write_enum(cell, "LSRMODE", "LSR");
+ write_enum(cell, "SEL", "DF");
+ pop();
+ write_enum(cell, "REGDDR");
+ write_enum(cell, "SRMODE");
+ write_cell_muxes(cell);
+ pop(2);
+ }
+
+ // Write out config for an OXIDE_RAMW cell
+ void write_ramw(const CellInfo *cell)
+ {
+ BelId bel = cell->bel;
+ push_tile(bel.tile, id_PLC);
+ push("SLICEC");
+ write_bit("MODE.RAMW");
+ write_cell_muxes(cell);
+ pop(2);
+ }
+
+ std::unordered_set<BelId> used_io;
+
+ struct BankConfig
+ {
+ bool diff_used = false;
+ bool lvds_used = false;
+ bool slvs_used = false;
+ bool dphy_used = false;
+ };
+
+ std::map<int, BankConfig> bank_cfg;
+
+ // Write config for an SEIO33_CORE cell
+ void write_io33(const CellInfo *cell)
+ {
+ BelId bel = cell->bel;
+ used_io.insert(bel);
+ push_bel(bel);
+ const NetInfo *t = get_net_or_empty(cell, id_T);
+ auto tmux = ctx->get_cell_pinmux(cell, id_T);
+ bool is_input = false, is_output = false;
+ if (tmux == PINMUX_0) {
+ is_output = true;
+ } else if (tmux == PINMUX_1 || t == nullptr) {
+ is_input = true;
+ }
+ const char *iodir = is_input ? "INPUT" : (is_output ? "OUTPUT" : "BIDIR");
+ write_bit(stringf("BASE_TYPE.%s_%s", iodir, str_or_default(cell->attrs, id_IO_TYPE, "LVCMOS33").c_str()));
+ write_ioattr(cell, "PULLMODE", "NONE");
+ write_cell_muxes(cell);
+ pop();
+ }
+ // Write config for an SEIO18_CORE cell
+ void write_io18(const CellInfo *cell)
+ {
+ BelId bel = cell->bel;
+ used_io.insert(bel);
+ push_bel(bel);
+ push("SEIO18");
+ const NetInfo *t = get_net_or_empty(cell, id_T);
+ auto tmux = ctx->get_cell_pinmux(cell, id_T);
+ bool is_input = false, is_output = false;
+ if (tmux == PINMUX_0) {
+ is_output = true;
+ } else if (tmux == PINMUX_1 || t == nullptr) {
+ is_input = true;
+ }
+ const char *iodir = is_input ? "INPUT" : (is_output ? "OUTPUT" : "BIDIR");
+ write_bit(stringf("BASE_TYPE.%s_%s", iodir, str_or_default(cell->attrs, id_IO_TYPE, "LVCMOS18H").c_str()));
+ write_ioattr(cell, "PULLMODE", "NONE");
+ pop();
+ write_cell_muxes(cell);
+ pop();
+ }
+ // Write config for an SEIO18_CORE cell
+ void write_diffio18(const CellInfo *cell)
+ {
+ BelId bel = cell->bel;
+
+ Loc bel_loc = ctx->getBelLocation(bel);
+ for (int i = 0; i < 2; i++) {
+ // Mark both A and B pins as used
+ used_io.insert(ctx->getBelByLocation(Loc(bel_loc.x, bel_loc.y, i)));
+ }
+ push_belgroup(bel);
+ push("PIOA");
+ push("DIFFIO18");
+
+ auto &bank = bank_cfg[ctx->get_bel_pad(ctx->getBelByLocation(Loc(bel_loc.x, bel_loc.y, 0)))->bank];
+
+ bank.diff_used = true;
+
+ const NetInfo *t = get_net_or_empty(cell, id_T);
+ auto tmux = ctx->get_cell_pinmux(cell, id_T);
+ bool is_input = false, is_output = false;
+ if (tmux == PINMUX_0) {
+ is_output = true;
+ } else if (tmux == PINMUX_1 || t == nullptr) {
+ is_input = true;
+ }
+
+ const char *iodir = is_input ? "INPUT" : (is_output ? "OUTPUT" : "BIDIR");
+ std::string type = str_or_default(cell->attrs, id_IO_TYPE, "LVDS");
+ write_bit(stringf("BASE_TYPE.%s_%s", iodir, type.c_str()));
+ if (type == "LVDS") {
+ write_ioattr_postfix(cell, "DIFFDRIVE", "LVDS", "3P5");
+ bank.lvds_used = true;
+ } else if (type == "SLVS") {
+ write_ioattr_postfix(cell, "DIFFDRIVE", "SLVS", "2P0");
+ bank.slvs_used = true;
+ } else if (type == "MIPI_DPHY") {
+ write_ioattr_postfix(cell, "DIFFDRIVE", "MIPI_DPHY", "2P0");
+ bank.dphy_used = true;
+ }
+
+ write_ioattr(cell, "PULLMODE", "FAILSAFE");
+ write_ioattr(cell, "DIFFRESISTOR");
+ pop();
+ write_cell_muxes(cell);
+ pop(2);
+ }
+ // Write config for an OSC_CORE cell
+ void write_osc(const CellInfo *cell)
+ {
+ BelId bel = cell->bel;
+ push_tile(bel.tile);
+ push_belname(bel);
+ write_enum(cell, "HF_OSC_EN");
+ write_enum(cell, "HF_FABRIC_EN");
+ write_enum(cell, "HFDIV_FABRIC_EN", "ENABLED");
+ write_enum(cell, "LF_FABRIC_EN");
+ write_enum(cell, "LF_OUTPUT_EN");
+ write_enum(cell, "DEBUG_N", "DISABLED");
+ write_int_vector(stringf("HF_CLK_DIV[7:0]"), ctx->parse_lattice_param(cell, id_HF_CLK_DIV, 8, 0).intval, 8);
+ write_cell_muxes(cell);
+ pop(2);
+ }
+ // Write config for an OXIDE_EBR cell
+ void write_bram(const CellInfo *cell)
+ {
+ // EBR configuration
+ BelId bel = cell->bel;
+ push_bel(bel);
+ int wid = int_or_default(cell->params, id_WID, 0);
+ std::string mode = str_or_default(cell->params, id_MODE, "");
+
+ write_bit(stringf("MODE.%s_MODE", mode.c_str()));
+ write_enum(cell, "INIT_DATA", "STATIC");
+ write_enum(cell, "GSR", "DISABLED");
+
+ write_int_vector("WID[10:0]", wid, 11);
+
+ push(stringf("%s_MODE", mode.c_str()));
+
+ if (mode == "DP16K") {
+ write_int_vector_param(cell, "CSDECODE_A", 7, 3, true);
+ write_int_vector_param(cell, "CSDECODE_B", 7, 3, true);
+ write_enum(cell, "ASYNC_RST_RELEASE_A");
+ write_enum(cell, "ASYNC_RST_RELEASE_B");
+ write_enum(cell, "DATA_WIDTH_A");
+ write_enum(cell, "DATA_WIDTH_B");
+ write_enum(cell, "OUTREG_A");
+ write_enum(cell, "OUTREG_B");
+ write_enum(cell, "RESETMODE_A");
+ write_enum(cell, "RESETMODE_B");
+ } else if (mode == "PDP16K" || mode == "PDPSC16K") {
+ write_int_vector_param(cell, "CSDECODE_W", 7, 3, true);
+ write_int_vector_param(cell, "CSDECODE_R", 7, 3, true);
+ write_enum(cell, "ASYNC_RST_RELEASE");
+ write_enum(cell, "DATA_WIDTH_W");
+ write_enum(cell, "DATA_WIDTH_R");
+ write_enum(cell, "OUTREG");
+ write_enum(cell, "RESETMODE");
+ }
+
+ pop();
+ push("DP16K_MODE"); // muxes always use the DP16K perspective
+ write_cell_muxes(cell);
+ pop(2);
+ blank();
+
+ // EBR initialisation
+ if (wid > 0) {
+ push(stringf("IP_EBR_WID%d", wid));
+ for (int i = 0; i < 64; i++) {
+ IdString param = ctx->id(stringf("INITVAL_%02X", i));
+ if (!cell->params.count(param))
+ continue;
+ auto &prop = cell->params.at(param);
+ std::string value;
+ if (prop.is_string) {
+ NPNR_ASSERT(prop.str.substr(0, 2) == "0x");
+ // Lattice-style hex string
+ value = prop.str.substr(2);
+ value = stringf("320'h%s", value.c_str());
+ } else {
+ // True Verilog bitvector
+ value = stringf("320'b%s", prop.str.c_str());
+ }
+ write_bit(stringf("INITVAL_%02X[319:0] = %s", i, value.c_str()));
+ }
+ pop();
+ }
+ }
+
+ bool is_mux_param(const std::string &key)
+ {
+ return (key.size() >= 3 && (key.compare(key.size() - 3, 3, "MUX") == 0));
+ }
+
+ // Write config for some kind of DSP cell
+ void write_dsp(const CellInfo *cell)
+ {
+ BelId bel = cell->bel;
+ push_bel(bel);
+ if (cell->type != id_MULT18_CORE && cell->type != id_MULT18X36_CORE && cell->type != id_MULT36_CORE)
+ write_bit(stringf("MODE.%s", ctx->nameOf(cell->type)));
+ for (auto param : sorted_cref(cell->params)) {
+ const std::string &param_name = param.first.str(ctx);
+ if (is_mux_param(param_name))
+ continue;
+ if (param.first == id_ROUNDBIT) {
+ // currently unsupported in oxide, but appears rarely used
+ NPNR_ASSERT(param.second.as_string() == "ROUND_TO_BIT0");
+ continue;
+ }
+ write_enum(cell, param_name);
+ }
+ write_cell_muxes(cell);
+ pop();
+ }
+ // Write out FASM for unused bels where needed
+ void write_unused()
+ {
+ write_comment("# Unused bels");
+
+ // DSP primitives are configured to a default mode; even if unused
+ static const std::unordered_map<IdString, std::vector<std::string>> dsp_defconf = {
+ {id_MULT9_CORE,
+ {
+ "GSR.ENABLED",
+ "MODE.NONE",
+ "RSTAMUX.RSTA",
+ "RSTPMUX.RSTP",
+ }},
+ {id_PREADD9_CORE,
+ {
+ "GSR.ENABLED",
+ "MODE.NONE",
+ "RSTBMUX.RSTB",
+ "RSTCLMUX.RSTCL",
+ }},
+ {id_REG18_CORE,
+ {
+ "GSR.ENABLED",
+ "MODE.NONE",
+ "RSTPMUX.RSTP",
+ }},
+ {id_ACC54_CORE,
+ {
+ "ACCUBYPS.BYPASS",
+ "MODE.NONE",
+ }},
+ };
+
+ for (BelId bel : ctx->getBels()) {
+ IdString type = ctx->getBelType(bel);
+ if (type == id_SEIO33_CORE && !used_io.count(bel)) {
+ push_bel(bel);
+ write_bit("BASE_TYPE.NONE");
+ pop();
+ blank();
+ } else if (type == id_SEIO18_CORE && !used_io.count(bel)) {
+ push_bel(bel);
+ push("SEIO18");
+ write_bit("BASE_TYPE.NONE");
+ pop(2);
+ blank();
+ } else if (dsp_defconf.count(type) && ctx->getBoundBelCell(bel) == nullptr) {
+ push_bel(bel);
+ for (const auto &cbit : dsp_defconf.at(type))
+ write_bit(cbit);
+ pop();
+ blank();
+ }
+ }
+ }
+ // Write out placeholder bankref config
+ void write_bankcfg()
+ {
+ for (int i = 0; i < 8; i++) {
+ if (i >= 3 && i <= 5) {
+ // 1.8V banks
+ push(stringf("GLOBAL.BANK%d", i));
+ auto &bank = bank_cfg[i];
+ write_bit("DIFF_IO.ON", bank.diff_used);
+ write_bit("LVDS_IO.ON", bank.lvds_used);
+ write_bit("SLVS_IO.ON", bank.slvs_used);
+ write_bit("MIPI_DPHY_IO.ON", bank.dphy_used);
+
+ pop();
+ } else {
+ // 3.3V banks, this should eventually be set based on the bank config
+ write_bit(stringf("GLOBAL.BANK%d.VCC.3V3", i));
+ }
+ }
+ blank();
+ }
+ // Write out FASM for the whole design
+ void operator()()
+ {
+ // Write device config
+ write_attribute("oxide.device", ctx->device);
+ write_attribute("oxide.device_variant", ctx->variant);
+ blank();
+ // Write routing
+ for (auto n : sorted(ctx->nets)) {
+ write_net(n.second);
+ }
+ // Write cell config
+ for (auto c : sorted(ctx->cells)) {
+ const CellInfo *ci = c.second;
+ write_comment(stringf("# Cell %s", ctx->nameOf(ci)));
+ if (ci->type == id_OXIDE_COMB)
+ write_comb(ci);
+ else if (ci->type == id_OXIDE_FF)
+ write_ff(ci);
+ else if (ci->type == id_RAMW)
+ write_ramw(ci);
+ else if (ci->type == id_SEIO33_CORE)
+ write_io33(ci);
+ else if (ci->type == id_SEIO18_CORE)
+ write_io18(ci);
+ else if (ci->type == id_DIFFIO18_CORE)
+ write_diffio18(ci);
+ else if (ci->type == id_OSC_CORE)
+ write_osc(ci);
+ else if (ci->type == id_OXIDE_EBR)
+ write_bram(ci);
+ else 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)
+ write_dsp(ci);
+ blank();
+ }
+ // Write config for unused bels
+ write_unused();
+ // Write bank config
+ write_bankcfg();
+ }
+};
+} // namespace
+
+void Arch::write_fasm(std::ostream &out) const { NexusFasmWriter(getCtx(), out)(); }
+
+NEXTPNR_NAMESPACE_END
diff --git a/nexus/global.cc b/nexus/global.cc
new file mode 100644
index 00000000..f7abb399
--- /dev/null
+++ b/nexus/global.cc
@@ -0,0 +1,168 @@
+/*
+ * 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 "log.h"
+#include "nextpnr.h"
+#include "util.h"
+
+#include <queue>
+
+NEXTPNR_NAMESPACE_BEGIN
+
+struct NexusGlobalRouter
+{
+ Context *ctx;
+
+ NexusGlobalRouter(Context *ctx) : ctx(ctx){};
+
+ // When routing globals; we allow global->local for some tricky cases but never local->local
+ bool global_pip_filter(PipId pip) const
+ {
+ IdString dest_basename(ctx->wire_data(ctx->getPipDstWire(pip)).name);
+ const std::string &s = dest_basename.str(ctx);
+ if (s.size() > 2 && (s[0] == 'H' || s[0] == 'V') && s[1] == '0')
+ return false;
+ return true;
+ }
+
+ // Dedicated backwards BFS routing for global networks
+ template <typename Tfilt>
+ bool backwards_bfs_route(NetInfo *net, size_t user_idx, int iter_limit, bool strict, Tfilt pip_filter)
+ {
+ // Queue of wires to visit
+ std::queue<WireId> visit;
+ // Wire -> upstream pip
+ std::unordered_map<WireId, PipId> backtrace;
+
+ // Lookup source and destination wires
+ WireId src = ctx->getNetinfoSourceWire(net);
+ WireId dst = ctx->getNetinfoSinkWire(net, net->users.at(user_idx));
+
+ if (src == WireId())
+ log_error("Net '%s' has an invalid source port %s.%s\n", ctx->nameOf(net), ctx->nameOf(net->driver.cell),
+ ctx->nameOf(net->driver.port));
+
+ if (dst == WireId())
+ log_error("Net '%s' has an invalid sink port %s.%s\n", ctx->nameOf(net),
+ ctx->nameOf(net->users.at(user_idx).cell), ctx->nameOf(net->users.at(user_idx).port));
+
+ if (ctx->getBoundWireNet(src) != net)
+ ctx->bindWire(src, net, STRENGTH_LOCKED);
+
+ if (src == dst) {
+ // Nothing more to do
+ return true;
+ }
+
+ visit.push(dst);
+ backtrace[dst] = PipId();
+
+ int iter = 0;
+
+ while (!visit.empty() && (iter++ < iter_limit)) {
+ WireId cursor = visit.front();
+ visit.pop();
+ // Search uphill pips
+ for (PipId pip : ctx->getPipsUphill(cursor)) {
+ // Skip pip if unavailable, and not because it's already used for this net
+ if (!ctx->checkPipAvail(pip) && ctx->getBoundPipNet(pip) != net)
+ continue;
+ WireId prev = ctx->getPipSrcWire(pip);
+ // Ditto for the upstream wire
+ if (!ctx->checkWireAvail(prev) && ctx->getBoundWireNet(prev) != net)
+ continue;
+ // Skip already visited wires
+ if (backtrace.count(prev))
+ continue;
+ // Apply our custom pip filter
+ if (!pip_filter(pip))
+ continue;
+ // Add to the queue
+ visit.push(prev);
+ backtrace[prev] = pip;
+ // Check if we are done yet
+ if (prev == src)
+ goto done;
+ }
+ if (false) {
+ done:
+ break;
+ }
+ }
+
+ if (backtrace.count(src)) {
+ WireId cursor = src;
+ std::vector<PipId> pips;
+ // Create a list of pips on the routed path
+ while (true) {
+ PipId pip = backtrace.at(cursor);
+ if (pip == PipId())
+ break;
+ pips.push_back(pip);
+ cursor = ctx->getPipDstWire(pip);
+ }
+ // Reverse that list
+ std::reverse(pips.begin(), pips.end());
+ // Bind pips until we hit already-bound routing
+ for (PipId pip : pips) {
+ WireId dst = ctx->getPipDstWire(pip);
+ if (ctx->getBoundWireNet(dst) == net)
+ break;
+ ctx->bindPip(pip, net, STRENGTH_LOCKED);
+ }
+ return true;
+ } else {
+ if (strict)
+ log_error("Failed to route net '%s' from %s to %s using dedicated routing.\n", ctx->nameOf(net),
+ ctx->nameOfWire(src), ctx->nameOfWire(dst));
+ return false;
+ }
+ }
+
+ void route_clk_net(NetInfo *net)
+ {
+ for (size_t i = 0; i < net->users.size(); i++)
+ backwards_bfs_route(net, i, 1000000, true, [&](PipId pip) { return global_pip_filter(pip); });
+ log_info(" routed net '%s' using global resources\n", ctx->nameOf(net));
+ }
+
+ void operator()()
+ {
+ log_info("Routing globals...\n");
+ for (auto net : sorted(ctx->nets)) {
+ NetInfo *ni = net.second;
+ CellInfo *drv = ni->driver.cell;
+ if (drv == nullptr)
+ continue;
+ if (drv->type == id_DCC) {
+ route_clk_net(ni);
+ continue;
+ }
+ }
+ }
+};
+
+void Arch::route_globals()
+{
+ NexusGlobalRouter glb_router(getCtx());
+ glb_router();
+}
+
+NEXTPNR_NAMESPACE_END
diff --git a/nexus/io.cc b/nexus/io.cc
new file mode 100644
index 00000000..fd5e584f
--- /dev/null
+++ b/nexus/io.cc
@@ -0,0 +1,70 @@
+/*
+ * 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 "log.h"
+#include "nextpnr.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+const std::unordered_map<std::string, IOTypeData> Arch::io_types = {
+ {"LVCMOS33", {IOSTYLE_SE_WR, 330}}, {"LVCMOS25", {IOSTYLE_SE_WR, 250}},
+ {"LVCMOS18", {IOSTYLE_SE_WR, 180}}, {"LVCMOS15", {IOSTYLE_SE_WR, 150}},
+ {"LVCMOS12", {IOSTYLE_SE_WR, 120}}, {"LVCMOS10", {IOSTYLE_SE_WR, 120}},
+
+ {"LVCMOS33D", {IOSTYLE_PD_WR, 330}}, {"LVCMOS25D", {IOSTYLE_PD_WR, 250}},
+
+ {"LVCMOS18H", {IOSTYLE_SE_HP, 180}}, {"LVCMOS15H", {IOSTYLE_SE_HP, 150}},
+ {"LVCMOS12H", {IOSTYLE_SE_HP, 120}}, {"LVCMOS10R", {IOSTYLE_SE_HP, 120}},
+ {"LVCMOS10H", {IOSTYLE_SE_HP, 100}},
+
+ {"HSTL15_I", {IOSTYLE_REF_HP, 150}}, {"SSTL15_I", {IOSTYLE_REF_HP, 150}},
+ {"SSTL15_II", {IOSTYLE_REF_HP, 150}}, {"SSTL135_I", {IOSTYLE_REF_HP, 135}},
+ {"SSTL135_II", {IOSTYLE_REF_HP, 135}}, {"HSUL12", {IOSTYLE_REF_HP, 120}},
+
+ {"LVDS", {IOSTYLE_DIFF_HP, 180}}, {"SLVS", {IOSTYLE_DIFF_HP, 120}},
+ {"MIPI_DPHY", {IOSTYLE_DIFF_HP, 120}}, {"HSUL12D", {IOSTYLE_DIFF_HP, 120}},
+
+ {"HSTL15D_I", {IOSTYLE_DIFF_HP, 150}}, {"SSTL15D_I", {IOSTYLE_DIFF_HP, 150}},
+ {"SSTL15D_II", {IOSTYLE_DIFF_HP, 150}}, {"SSTL135D_I", {IOSTYLE_DIFF_HP, 135}},
+ {"SSTL135D_II", {IOSTYLE_DIFF_HP, 135}}, {"HSUL12D", {IOSTYLE_DIFF_HP, 120}},
+};
+
+int Arch::get_io_type_vcc(const std::string &io_type) const
+{
+ if (!io_types.count(io_type))
+ log_error("IO type '%s' not supported.\n", io_type.c_str());
+ return io_types.at(io_type).vcco;
+}
+
+bool Arch::is_io_type_diff(const std::string &io_type) const
+{
+ if (!io_types.count(io_type))
+ log_error("IO type '%s' not supported.\n", io_type.c_str());
+ return io_types.at(io_type).style & IOMODE_DIFF;
+}
+
+bool Arch::is_io_type_ref(const std::string &io_type) const
+{
+ if (!io_types.count(io_type))
+ log_error("IO type '%s' not supported.\n", io_type.c_str());
+ return io_types.at(io_type).style & IOMODE_REF;
+}
+
+NEXTPNR_NAMESPACE_END
diff --git a/nexus/main.cc b/nexus/main.cc
new file mode 100644
index 00000000..cced1b95
--- /dev/null
+++ b/nexus/main.cc
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ *
+ */
+
+#ifdef MAIN_EXECUTABLE
+
+#include <fstream>
+#include "command.h"
+#include "design_utils.h"
+#include "jsonwrite.h"
+#include "log.h"
+#include "timing.h"
+
+USING_NEXTPNR_NAMESPACE
+
+class NexusCommandHandler : public CommandHandler
+{
+ public:
+ NexusCommandHandler(int argc, char **argv);
+ virtual ~NexusCommandHandler(){};
+ std::unique_ptr<Context> createContext(std::unordered_map<std::string, Property> &values) override;
+ void setupArchContext(Context *ctx) override{};
+ void customBitstream(Context *ctx) override;
+ void customAfterLoad(Context *ctx) override;
+
+ protected:
+ po::options_description getArchOptions() override;
+};
+
+NexusCommandHandler::NexusCommandHandler(int argc, char **argv) : CommandHandler(argc, argv) {}
+
+po::options_description NexusCommandHandler::getArchOptions()
+{
+ po::options_description specific("Architecture specific options");
+ specific.add_options()("device", po::value<std::string>(), "device name");
+ specific.add_options()("fasm", po::value<std::string>(), "fasm file to write");
+ specific.add_options()("pdc", po::value<std::string>(), "physical constraints file");
+ specific.add_options()("no-post-place-opt", "disable post-place repacking (debugging use only)");
+
+ return specific;
+}
+
+void NexusCommandHandler::customBitstream(Context *ctx)
+{
+ if (vm.count("fasm")) {
+ std::string filename = vm["fasm"].as<std::string>();
+ std::ofstream out(filename);
+ if (!out)
+ log_error("Failed to open output FASM file %s.\n", filename.c_str());
+ ctx->write_fasm(out);
+ }
+}
+
+std::unique_ptr<Context> NexusCommandHandler::createContext(std::unordered_map<std::string, Property> &values)
+{
+ ArchArgs chipArgs;
+ if (!vm.count("device")) {
+ log_error("device must be specified on the command line (e.g. --device LIFCL-40-9BG400CES)\n");
+ }
+ chipArgs.device = vm["device"].as<std::string>();
+ auto ctx = std::unique_ptr<Context>(new Context(chipArgs));
+ if (vm.count("no-post-place-opt"))
+ ctx->settings[ctx->id("no_post_place_opt")] = Property::State::S1;
+ return ctx;
+}
+
+void NexusCommandHandler::customAfterLoad(Context *ctx)
+{
+ if (vm.count("pdc")) {
+ std::string filename = vm["pdc"].as<std::string>();
+ std::ifstream in(filename);
+ if (!in)
+ log_error("Failed to open input PDC file %s.\n", filename.c_str());
+ ctx->read_pdc(in);
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ NexusCommandHandler handler(argc, argv);
+ return handler.exec();
+}
+
+#endif
diff --git a/nexus/pack.cc b/nexus/pack.cc
new file mode 100644
index 00000000..ff5d1047
--- /dev/null
+++ b/nexus/pack.cc
@@ -0,0 +1,1866 @@
+/*
+ * 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 "design_utils.h"
+#include "log.h"
+#include "nextpnr.h"
+#include "util.h"
+
+#include <boost/algorithm/string.hpp>
+#include <queue>
+
+NEXTPNR_NAMESPACE_BEGIN
+
+namespace {
+bool is_enabled(CellInfo *ci, IdString prop) { return str_or_default(ci->params, prop, "") == "ENABLED"; }
+} // namespace
+
+// Parse a possibly-Lattice-style (C literal in Verilog string) style parameter
+Property Arch::parse_lattice_param(const CellInfo *ci, IdString prop, int width, int64_t defval) const
+{
+ auto fnd = ci->params.find(prop);
+ if (fnd == ci->params.end())
+ return Property(defval, width);
+ const auto &val = fnd->second;
+ if (val.is_string) {
+ const std::string &s = val.str;
+ Property temp;
+
+ if (boost::starts_with(s, "0b")) {
+ for (int i = int(s.length()) - 1; i >= 2; i--) {
+ char c = s.at(i);
+ if (c != '0' && c != '1' && c != 'x')
+ log_error("Invalid binary digit '%c' in property %s.%s\n", c, nameOf(ci), nameOf(prop));
+ temp.str.push_back(c);
+ }
+ } else if (boost::starts_with(s, "0x")) {
+ for (int i = int(s.length()) - 1; i >= 2; i--) {
+ char c = s.at(i);
+ int nibble;
+ if (c >= '0' && c <= '9')
+ nibble = (c - '0');
+ else if (c >= 'a' && c <= 'f')
+ nibble = (c - 'a') + 10;
+ else if (c >= 'A' && c <= 'F')
+ nibble = (c - 'A') + 10;
+ else
+ log_error("Invalid hex digit '%c' in property %s.%s\n", c, nameOf(ci), nameOf(prop));
+ for (int j = 0; j < 4; j++)
+ temp.str.push_back(((nibble >> j) & 0x1) ? Property::S1 : Property::S0);
+ }
+ } else {
+ int64_t ival = 0;
+ try {
+ if (boost::starts_with(s, "0d"))
+ ival = std::stoll(s.substr(2));
+ else
+ ival = std::stoll(s);
+ } catch (std::runtime_error &e) {
+ log_error("Invalid decimal value for property %s.%s", nameOf(ci), nameOf(prop));
+ }
+ temp = Property(ival);
+ }
+
+ for (auto b : temp.str.substr(width)) {
+ if (b == Property::S1)
+ log_error("Found value for property %s.%s with width greater than %d\n", nameOf(ci), nameOf(prop),
+ width);
+ }
+ temp.update_intval();
+ return temp.extract(0, width);
+ } else {
+ for (auto b : val.str.substr(width)) {
+ if (b == Property::S1)
+ log_error("Found bitvector value for property %s.%s with width greater than %d - perhaps a string was "
+ "converted to bits?\n",
+ nameOf(ci), nameOf(prop), width);
+ }
+ return val.extract(0, width);
+ }
+}
+
+struct NexusPacker
+{
+ Context *ctx;
+
+ // Generic cell transformation
+ // Given cell name map and port map
+ // If port name is not found in port map; it will be copied as-is but stripping []
+ struct XFormRule
+ {
+ IdString new_type;
+ std::unordered_map<IdString, IdString> port_xform;
+ std::unordered_map<IdString, std::vector<IdString>> port_multixform;
+ std::unordered_map<IdString, IdString> param_xform;
+ std::vector<std::pair<IdString, std::string>> set_attrs;
+ std::vector<std::pair<IdString, Property>> set_params;
+ std::vector<std::pair<IdString, Property>> default_params;
+ std::vector<std::tuple<IdString, IdString, int, int64_t>> parse_params;
+ };
+
+ void xform_cell(const std::unordered_map<IdString, XFormRule> &rules, CellInfo *ci)
+ {
+ auto &rule = rules.at(ci->type);
+ ci->type = rule.new_type;
+ std::vector<IdString> orig_port_names;
+ for (auto &port : ci->ports)
+ orig_port_names.push_back(port.first);
+
+ for (auto pname : orig_port_names) {
+ if (rule.port_multixform.count(pname)) {
+ auto old_port = ci->ports.at(pname);
+ disconnect_port(ctx, ci, pname);
+ ci->ports.erase(pname);
+ for (auto new_name : rule.port_multixform.at(pname)) {
+ ci->ports[new_name].name = new_name;
+ ci->ports[new_name].type = old_port.type;
+ connect_port(ctx, old_port.net, ci, new_name);
+ }
+ } else {
+ IdString new_name;
+ if (rule.port_xform.count(pname)) {
+ new_name = rule.port_xform.at(pname);
+ } else {
+ std::string stripped_name;
+ for (auto c : pname.str(ctx))
+ if (c != '[' && c != ']')
+ stripped_name += c;
+ new_name = ctx->id(stripped_name);
+ }
+ if (new_name != pname) {
+ rename_port(ctx, ci, pname, new_name);
+ }
+ }
+ }
+
+ std::vector<IdString> xform_params;
+ for (auto &param : ci->params)
+ if (rule.param_xform.count(param.first))
+ xform_params.push_back(param.first);
+ for (auto param : xform_params)
+ ci->params[rule.param_xform.at(param)] = ci->params[param];
+
+ for (auto &attr : rule.set_attrs)
+ ci->attrs[attr.first] = attr.second;
+
+ for (auto &param : rule.default_params)
+ if (!ci->params.count(param.first))
+ ci->params[param.first] = param.second;
+
+ {
+ IdString old_param, new_param;
+ int width;
+ int64_t def;
+ for (const auto &p : rule.parse_params) {
+ std::tie(old_param, new_param, width, def) = p;
+ ci->params[new_param] = ctx->parse_lattice_param(ci, old_param, width, def);
+ }
+ }
+
+ for (auto &param : rule.set_params)
+ ci->params[param.first] = param.second;
+ }
+
+ void generic_xform(const std::unordered_map<IdString, XFormRule> &rules, bool print_summary = false)
+ {
+ std::map<std::string, int> cell_count;
+ std::map<std::string, int> new_types;
+ for (auto cell : sorted(ctx->cells)) {
+ CellInfo *ci = cell.second;
+ if (rules.count(ci->type)) {
+ cell_count[ci->type.str(ctx)]++;
+ xform_cell(rules, ci);
+ new_types[ci->type.str(ctx)]++;
+ }
+ }
+ if (print_summary) {
+ for (auto &nt : new_types) {
+ log_info(" Created %d %s cells from:\n", nt.second, nt.first.c_str());
+ for (auto &cc : cell_count) {
+ if (rules.at(ctx->id(cc.first)).new_type != ctx->id(nt.first))
+ continue;
+ log_info(" %6dx %s\n", cc.second, cc.first.c_str());
+ }
+ }
+ }
+ }
+
+ void pack_luts()
+ {
+ log_info("Packing LUTs...\n");
+ std::unordered_map<IdString, XFormRule> lut_rules;
+ lut_rules[id_LUT4].new_type = id_OXIDE_COMB;
+ lut_rules[id_LUT4].port_xform[id_Z] = id_F;
+ lut_rules[id_LUT4].parse_params.emplace_back(id_INIT, id_INIT, 16, 0);
+
+ lut_rules[id_INV].new_type = id_OXIDE_COMB;
+ lut_rules[id_INV].port_xform[id_Z] = id_F;
+ lut_rules[id_INV].port_xform[id_A] = id_A;
+ lut_rules[id_INV].set_params.emplace_back(id_INIT, 0x5555);
+
+ lut_rules[id_VHI].new_type = id_OXIDE_COMB;
+ lut_rules[id_VHI].port_xform[id_Z] = id_F;
+ lut_rules[id_VHI].set_params.emplace_back(id_INIT, 0xFFFF);
+
+ lut_rules[id_VLO].new_type = id_OXIDE_COMB;
+ lut_rules[id_VLO].port_xform[id_Z] = id_F;
+ lut_rules[id_VLO].set_params.emplace_back(id_INIT, 0x0000);
+
+ generic_xform(lut_rules);
+ }
+
+ void pack_ffs()
+ {
+ log_info("Packing FFs...\n");
+ std::unordered_map<IdString, XFormRule> ff_rules;
+ for (auto type : {id_FD1P3BX, id_FD1P3DX, id_FD1P3IX, id_FD1P3JX}) {
+ ff_rules[type].new_type = id_OXIDE_FF;
+ ff_rules[type].port_xform[id_CK] = id_CLK;
+ ff_rules[type].port_xform[id_D] = id_M; // will be rerouted to DI later if applicable
+ ff_rules[type].port_xform[id_SP] = id_CE;
+ ff_rules[type].port_xform[id_Q] = id_Q;
+
+ ff_rules[type].default_params.emplace_back(id_CLKMUX, std::string("CLK"));
+ ff_rules[type].default_params.emplace_back(id_CEMUX, std::string("CE"));
+ ff_rules[type].default_params.emplace_back(id_LSRMUX, std::string("LSR"));
+ ff_rules[type].set_params.emplace_back(id_LSRMODE, std::string("LSR"));
+ }
+ // Async preload
+ ff_rules[id_FD1P3BX].set_params.emplace_back(id_SRMODE, std::string("ASYNC"));
+ ff_rules[id_FD1P3BX].set_params.emplace_back(id_REGSET, std::string("SET"));
+ ff_rules[id_FD1P3BX].port_xform[id_PD] = id_LSR;
+ // Async clear
+ ff_rules[id_FD1P3DX].set_params.emplace_back(id_SRMODE, std::string("ASYNC"));
+ ff_rules[id_FD1P3DX].set_params.emplace_back(id_REGSET, std::string("RESET"));
+ ff_rules[id_FD1P3DX].port_xform[id_CD] = id_LSR;
+ // Sync preload
+ ff_rules[id_FD1P3JX].set_params.emplace_back(id_SRMODE, std::string("LSR_OVER_CE"));
+ ff_rules[id_FD1P3JX].set_params.emplace_back(id_REGSET, std::string("SET"));
+ ff_rules[id_FD1P3JX].port_xform[id_PD] = id_LSR;
+ // Sync clear
+ ff_rules[id_FD1P3IX].set_params.emplace_back(id_SRMODE, std::string("LSR_OVER_CE"));
+ ff_rules[id_FD1P3IX].set_params.emplace_back(id_REGSET, std::string("RESET"));
+ ff_rules[id_FD1P3IX].port_xform[id_CD] = id_LSR;
+
+ generic_xform(ff_rules, true);
+ }
+
+ std::unordered_map<IdString, BelId> reference_bels;
+
+ void autocreate_ports(CellInfo *cell)
+ {
+ // Automatically create ports for all inputs, and maybe outputs, of a cell; even if they were left off the
+ // instantiation so we can tie them to constants as appropriate This also checks for any cells that don't have
+ // corresponding bels
+
+ if (!reference_bels.count(cell->type)) {
+ // We need to look up a corresponding bel to get the list of input ports
+ BelId ref_bel;
+ for (BelId bel : ctx->getBels()) {
+ if (ctx->getBelType(bel) != cell->type)
+ continue;
+ ref_bel = bel;
+ break;
+ }
+ if (ref_bel == BelId())
+ log_error("Cell type '%s' instantiated as '%s' is not supported by this device.\n",
+ ctx->nameOf(cell->type), ctx->nameOf(cell));
+ reference_bels[cell->type] = ref_bel;
+ }
+
+ BelId bel = reference_bels.at(cell->type);
+ for (IdString pin : ctx->getBelPins(bel)) {
+ PortType dir = ctx->getBelPinType(bel, pin);
+ if (dir != PORT_IN)
+ continue;
+ if (cell->ports.count(pin))
+ continue;
+ if (cell->type == id_OXIDE_COMB && pin == id_SEL)
+ continue; // doesn't always exist and not needed
+ cell->ports[pin].name = pin;
+ cell->ports[pin].type = dir;
+ }
+ }
+
+ NetInfo *get_const_net(IdString type)
+ {
+ // Gets a constant net, given the driver type (VHI or VLO)
+ // If one doesn't exist already; then create it
+ for (auto cell : sorted(ctx->cells)) {
+ CellInfo *ci = cell.second;
+ if (ci->type != type)
+ continue;
+ NetInfo *z = get_net_or_empty(ci, id_Z);
+ if (z == nullptr)
+ continue;
+ return z;
+ }
+
+ NetInfo *new_net = ctx->createNet(ctx->id(stringf("$CONST_%s_NET_", type.c_str(ctx))));
+ CellInfo *new_cell = ctx->createCell(ctx->id(stringf("$CONST_%s_DRV_", type.c_str(ctx))), type);
+ new_cell->addOutput(id_Z);
+ connect_port(ctx, new_net, new_cell, id_Z);
+ return new_net;
+ }
+
+ CellPinMux get_pin_needed_muxval(CellInfo *cell, IdString port)
+ {
+ NetInfo *net = get_net_or_empty(cell, port);
+ if (net == nullptr || net->driver.cell == nullptr) {
+ // Pin is disconnected
+ // If a mux value exists already, honour it
+ CellPinMux exist_mux = ctx->get_cell_pinmux(cell, port);
+ if (exist_mux != PINMUX_SIG)
+ return exist_mux;
+ // Otherwise, look up the default value and use that
+ CellPinStyle pin_style = ctx->get_cell_pin_style(cell, port);
+ if ((pin_style & PINDEF_MASK) == PINDEF_0)
+ return PINMUX_0;
+ else if ((pin_style & PINDEF_MASK) == PINDEF_1)
+ return PINMUX_1;
+ else
+ return PINMUX_SIG;
+ }
+ // Look to see if the driver is an inverter or constant
+ IdString drv_type = net->driver.cell->type;
+ if (drv_type == id_INV)
+ return PINMUX_INV;
+ else if (drv_type == id_VLO)
+ return PINMUX_0;
+ else if (drv_type == id_VHI)
+ return PINMUX_1;
+ else
+ return PINMUX_SIG;
+ }
+
+ void uninvert_port(CellInfo *cell, IdString port)
+ {
+ // Rewire a port so it is driven by the input to an inverter
+ NetInfo *net = get_net_or_empty(cell, port);
+ NPNR_ASSERT(net != nullptr && net->driver.cell != nullptr && net->driver.cell->type == id_INV);
+ CellInfo *inv = net->driver.cell;
+ disconnect_port(ctx, cell, port);
+
+ NetInfo *inv_a = get_net_or_empty(inv, id_A);
+ if (inv_a != nullptr) {
+ connect_port(ctx, inv_a, cell, port);
+ }
+ }
+
+ void trim_design()
+ {
+ // Remove unused inverters and high/low drivers
+ std::vector<IdString> trim_cells;
+ std::vector<IdString> trim_nets;
+ for (auto cell : sorted(ctx->cells)) {
+ CellInfo *ci = cell.second;
+ if (ci->type != id_INV && ci->type != id_VLO && ci->type != id_VHI && ci->type != id_VCC_DRV)
+ continue;
+ NetInfo *z = get_net_or_empty(ci, id_Z);
+ if (z == nullptr) {
+ trim_cells.push_back(ci->name);
+ continue;
+ }
+ if (!z->users.empty())
+ continue;
+
+ disconnect_port(ctx, ci, id_A);
+
+ trim_cells.push_back(ci->name);
+ trim_nets.push_back(z->name);
+ }
+
+ for (IdString rem_net : trim_nets)
+ ctx->nets.erase(rem_net);
+ for (IdString rem_cell : trim_cells)
+ ctx->cells.erase(rem_cell);
+ }
+
+ std::string remove_brackets(const std::string &name)
+ {
+ std::string new_name;
+ new_name.reserve(name.size());
+ for (char c : name)
+ if (c != '[' && c != ']')
+ new_name.push_back(c);
+ return new_name;
+ }
+
+ void prim_to_core(CellInfo *cell, IdString new_type = {})
+ {
+ // Convert a primitive to a '_CORE' variant
+ if (new_type == IdString())
+ new_type = ctx->id(cell->type.str(ctx) + "_CORE");
+ cell->type = new_type;
+ std::set<IdString> port_names;
+ for (auto port : cell->ports)
+ port_names.insert(port.first);
+ for (IdString port : port_names) {
+ IdString new_name = ctx->id(remove_brackets(port.str(ctx)));
+ if (new_name != port)
+ rename_port(ctx, cell, port, new_name);
+ }
+ }
+
+ NetInfo *gnd_net = nullptr, *vcc_net = nullptr, *dedi_vcc_net = nullptr;
+
+ void process_inv_constants(CellInfo *cell)
+ {
+ // Automatically create any extra inputs needed; so we can set them accordingly
+ autocreate_ports(cell);
+
+ for (auto &port : cell->ports) {
+ // Iterate over all inputs
+ if (port.second.type != PORT_IN)
+ continue;
+ IdString port_name = port.first;
+
+ CellPinMux req_mux = get_pin_needed_muxval(cell, port_name);
+ if (req_mux == PINMUX_SIG) {
+ // No special setting required, ignore
+ continue;
+ }
+
+ CellPinStyle pin_style = ctx->get_cell_pin_style(cell, port_name);
+
+ if (req_mux == PINMUX_INV) {
+ // Pin is inverted. If there is a hard inverter; then use it
+ if (pin_style & PINOPT_INV) {
+ uninvert_port(cell, port_name);
+ ctx->set_cell_pinmux(cell, port_name, PINMUX_INV);
+ }
+ } else if (req_mux == PINMUX_0 || req_mux == PINMUX_1) {
+ // Pin is tied to a constant
+ // If there is a hard constant option; use it
+ if ((pin_style & int(req_mux)) == req_mux) {
+
+ if ((cell->type == id_OXIDE_COMB) && (req_mux == PINMUX_1)) {
+ // We need to add a connection to the dedicated Vcc resource that can feed these cell ports
+ disconnect_port(ctx, cell, port_name);
+ connect_port(ctx, dedi_vcc_net, cell, port_name);
+ continue;
+ }
+
+ disconnect_port(ctx, cell, port_name);
+ ctx->set_cell_pinmux(cell, port_name, req_mux);
+ } else if (port.second.net == nullptr) {
+ // If the port is disconnected; and there is no hard constant
+ // then we need to connect it to the relevant soft-constant net
+ connect_port(ctx, (req_mux == PINMUX_1) ? vcc_net : gnd_net, cell, port_name);
+ }
+ }
+ }
+ }
+
+ void prepare_io()
+ {
+ // Find the actual IO buffer corresponding to a port; and copy attributes across to it
+ // Note that this relies on Yosys to do IO buffer inference, to match vendor tooling behaviour
+ // In all cases the nextpnr-inserted IO buffers are removed as redundant.
+ for (auto &port : sorted_ref(ctx->ports)) {
+ if (!ctx->cells.count(port.first))
+ log_error("Port '%s' doesn't seem to have a corresponding top level IO\n", ctx->nameOf(port.first));
+ CellInfo *ci = ctx->cells.at(port.first).get();
+
+ PortRef top_port;
+ top_port.cell = nullptr;
+ bool is_npnr_iob = false;
+
+ if (ci->type == ctx->id("$nextpnr_ibuf") || ci->type == ctx->id("$nextpnr_iobuf")) {
+ // Might have an input buffer (IB etc) connected to it
+ is_npnr_iob = true;
+ NetInfo *o = get_net_or_empty(ci, id_O);
+ if (o == nullptr)
+ ;
+ else if (o->users.size() > 1)
+ log_error("Top level pin '%s' has multiple input buffers\n", ctx->nameOf(port.first));
+ else if (o->users.size() == 1)
+ top_port = o->users.at(0);
+ }
+ if (ci->type == ctx->id("$nextpnr_obuf") || ci->type == ctx->id("$nextpnr_iobuf")) {
+ // Might have an output buffer (OB etc) connected to it
+ is_npnr_iob = true;
+ NetInfo *i = get_net_or_empty(ci, id_I);
+ if (i != nullptr && i->driver.cell != nullptr) {
+ if (top_port.cell != nullptr)
+ log_error("Top level pin '%s' has multiple input/output buffers\n", ctx->nameOf(port.first));
+ top_port = i->driver;
+ }
+ // Edge case of a bidirectional buffer driving an output pin
+ if (i->users.size() > 2) {
+ log_error("Top level pin '%s' has illegal buffer configuration\n", ctx->nameOf(port.first));
+ } else if (i->users.size() == 2) {
+ if (top_port.cell != nullptr)
+ log_error("Top level pin '%s' has illegal buffer configuration\n", ctx->nameOf(port.first));
+ for (auto &usr : i->users) {
+ if (usr.cell->type == ctx->id("$nextpnr_obuf") || usr.cell->type == ctx->id("$nextpnr_iobuf"))
+ continue;
+ top_port = usr;
+ break;
+ }
+ }
+ }
+ if (!is_npnr_iob)
+ log_error("Port '%s' doesn't seem to have a corresponding top level IO (internal cell type mismatch)\n",
+ ctx->nameOf(port.first));
+
+ if (top_port.cell == nullptr) {
+ log_info("Trimming port '%s' as it is unused.\n", ctx->nameOf(port.first));
+ } else {
+ // Copy attributes to real IO buffer
+ if (ctx->io_attr.count(port.first)) {
+ for (auto &kv : ctx->io_attr.at(port.first)) {
+ top_port.cell->attrs[kv.first] = kv.second;
+ }
+ }
+ // Make sure that top level net is set correctly
+ port.second.net = top_port.cell->ports.at(top_port.port).net;
+ }
+ // Now remove the nextpnr-inserted buffer
+ disconnect_port(ctx, ci, id_I);
+ disconnect_port(ctx, ci, id_O);
+ ctx->cells.erase(port.first);
+ }
+ }
+
+ BelId get_bel_attr(const CellInfo *ci)
+ {
+ if (!ci->attrs.count(id_BEL))
+ return BelId();
+ return ctx->getBelByName(ctx->id(ci->attrs.at(id_BEL).as_string()));
+ }
+
+ void pack_io()
+ {
+ std::unordered_set<IdString> iob_types = {id_IB, id_OB, id_OBZ, id_BB,
+ id_BB_I3C_A, id_SEIO33, id_SEIO18, id_DIFFIO18,
+ id_SEIO33_CORE, id_SEIO18_CORE, id_DIFFIO18_CORE};
+
+ std::unordered_map<IdString, XFormRule> io_rules;
+
+ // For the low level primitives, make sure we always preserve their type
+ io_rules[id_SEIO33_CORE].new_type = id_SEIO33_CORE;
+ io_rules[id_SEIO18_CORE].new_type = id_SEIO18_CORE;
+ io_rules[id_DIFFIO18_CORE].new_type = id_DIFFIO18_CORE;
+
+ // Some IO buffer types need a bit of pin renaming, too
+ io_rules[id_SEIO33].new_type = id_SEIO33_CORE;
+ io_rules[id_SEIO33].port_xform[id_PADDI] = id_O;
+ io_rules[id_SEIO33].port_xform[id_PADDO] = id_I;
+ io_rules[id_SEIO33].port_xform[id_PADDT] = id_T;
+ io_rules[id_SEIO33].port_xform[id_IOPAD] = id_B;
+
+ io_rules[id_BB_I3C_A] = io_rules[id_SEIO33];
+
+ io_rules[id_SEIO18] = io_rules[id_SEIO33];
+ io_rules[id_SEIO18].new_type = id_SEIO18_CORE;
+
+ io_rules[id_DIFFIO18] = io_rules[id_SEIO33];
+ io_rules[id_DIFFIO18].new_type = id_DIFFIO18_CORE;
+
+ // Stage 0: deal with top level inserted IO buffers
+ prepare_io();
+
+ // Stage 1: setup constraints
+ for (auto cell : sorted(ctx->cells)) {
+ CellInfo *ci = cell.second;
+ // Iterate through all IO buffer primitives
+ if (!iob_types.count(ci->type))
+ continue;
+ // We need all IO constrained so we can pick the right IO bel type
+ // An improvement would be to allocate unconstrained IO here
+ if (!ci->attrs.count(id_LOC))
+ log_error("Found unconstrained IO '%s', these are currently unsupported\n", ctx->nameOf(ci));
+ // Convert package pin constraint to bel constraint
+ std::string loc = ci->attrs.at(id_LOC).as_string();
+ auto pad_info = ctx->get_pkg_pin_data(loc);
+ if (pad_info == nullptr)
+ log_error("IO '%s' is constrained to invalid pin '%s'\n", ctx->nameOf(ci), loc.c_str());
+ auto func = ctx->get_pad_functions(pad_info);
+ BelId bel = ctx->get_pad_pio_bel(pad_info);
+
+ if (bel == BelId()) {
+ log_error("IO '%s' is constrained to pin %s (%s) which is not a general purpose IO pin.\n",
+ ctx->nameOf(ci), loc.c_str(), func.c_str());
+ } else {
+
+ // Get IO type for reporting purposes
+ std::string io_type = str_or_default(ci->attrs, id_IO_TYPE, "LVCMOS33");
+
+ if (ctx->is_io_type_diff(io_type)) {
+ // Convert from SEIO18 to DIFFIO18
+ if (ctx->getBelType(bel) != id_SEIO18_CORE)
+ log_error("IO '%s' uses differential type '%s' but is placed on wide range pin '%s'\n",
+ ctx->nameOf(ci), io_type.c_str(), loc.c_str());
+ Loc bel_loc = ctx->getBelLocation(bel);
+ if (bel_loc.z != 0)
+ log_error("IO '%s' uses differential type '%s' but is placed on 'B' side pin '%s'\n",
+ ctx->nameOf(ci), io_type.c_str(), loc.c_str());
+ bel_loc.z = 2;
+ bel = ctx->getBelByLocation(bel_loc);
+ }
+
+ log_info("Constraining %s IO '%s' to pin %s (%s%sbel %s)\n", io_type.c_str(), ctx->nameOf(ci),
+ loc.c_str(), func.c_str(), func.empty() ? "" : "; ", ctx->nameOfBel(bel));
+ ci->attrs[id_BEL] = ctx->getBelName(bel).str(ctx);
+ }
+ }
+ // Stage 2: apply rules for primitives that need them
+ generic_xform(io_rules, false);
+ // Stage 3: all other IO primitives become their bel type
+ for (auto cell : sorted(ctx->cells)) {
+ CellInfo *ci = cell.second;
+ // Iterate through all IO buffer primitives
+ if (!iob_types.count(ci->type))
+ continue;
+ // Skip those dealt with in stage 2
+ if (io_rules.count(ci->type))
+ continue;
+ // For non-bidirectional IO, we also need to configure tristate and rename B
+ if (ci->type == id_IB) {
+ ctx->set_cell_pinmux(ci, id_T, PINMUX_1);
+ rename_port(ctx, ci, id_I, id_B);
+ } else if (ci->type == id_OB) {
+ ctx->set_cell_pinmux(ci, id_T, PINMUX_0);
+ rename_port(ctx, ci, id_O, id_B);
+ } else if (ci->type == id_OBZ) {
+ ctx->set_cell_pinmux(ci, id_T, PINMUX_SIG);
+ rename_port(ctx, ci, id_O, id_B);
+ }
+ // Get the IO bel
+ BelId bel = get_bel_attr(ci);
+ // Set the cell type to the bel type
+ IdString type = ctx->getBelType(bel);
+ NPNR_ASSERT(type != IdString());
+ ci->type = type;
+ }
+ }
+
+ void pack_constants()
+ {
+ // Make sure we have high and low nets available
+ vcc_net = get_const_net(id_VHI);
+ gnd_net = get_const_net(id_VLO);
+ dedi_vcc_net = get_const_net(id_VCC_DRV);
+ // Iterate through cells
+ for (auto cell : sorted(ctx->cells)) {
+ CellInfo *ci = cell.second;
+ // Skip certain cells at this point
+ if (ci->type != id_LUT4 && ci->type != id_INV && ci->type != id_VHI && ci->type != id_VLO &&
+ ci->type != id_VCC_DRV)
+ process_inv_constants(cell.second);
+ }
+ // Remove superfluous inverters and constant drivers
+ trim_design();
+ }
+
+ // Using a BFS, search for bels of a given type either upstream or downstream of another cell
+ void find_connected_bels(const CellInfo *cell, IdString port, IdString dest_type, IdString dest_pin, int iter_limit,
+ std::vector<BelId> &candidates)
+ {
+ int iter = 0;
+ std::queue<WireId> visit;
+ std::unordered_set<WireId> seen_wires;
+ std::unordered_set<BelId> seen_bels;
+
+ BelId bel = get_bel_attr(cell);
+ NPNR_ASSERT(bel != BelId());
+ WireId start_wire = ctx->getBelPinWire(bel, port);
+ NPNR_ASSERT(start_wire != WireId());
+ PortType dir = ctx->getBelPinType(bel, port);
+
+ visit.push(start_wire);
+
+ while (!visit.empty() && (iter++ < iter_limit)) {
+ WireId cursor = visit.front();
+ visit.pop();
+ // Check to see if we have reached a valid bel pin
+ for (auto bp : ctx->getWireBelPins(cursor)) {
+ if (ctx->getBelType(bp.bel) != dest_type)
+ continue;
+ if (dest_pin != IdString() && bp.pin != dest_pin)
+ continue;
+ if (seen_bels.count(bp.bel))
+ continue;
+ seen_bels.insert(bp.bel);
+ candidates.push_back(bp.bel);
+ }
+ // Search in the appropriate direction up/downstream of the cursor
+ if (dir == PORT_OUT) {
+ for (PipId p : ctx->getPipsDownhill(cursor))
+ if (ctx->checkPipAvail(p)) {
+ WireId dst = ctx->getPipDstWire(p);
+ if (seen_wires.count(dst))
+ continue;
+ seen_wires.insert(dst);
+ visit.push(dst);
+ }
+ } else {
+ for (PipId p : ctx->getPipsUphill(cursor))
+ if (ctx->checkPipAvail(p)) {
+ WireId src = ctx->getPipSrcWire(p);
+ if (seen_wires.count(src))
+ continue;
+ seen_wires.insert(src);
+ visit.push(src);
+ }
+ }
+ }
+ }
+
+ // Find the nearest bel of a given type; matching a closure predicate
+ template <typename Tpred> BelId find_nearest_bel(const CellInfo *cell, IdString dest_type, Tpred predicate)
+ {
+ BelId origin = get_bel_attr(cell);
+ if (origin == BelId())
+ return BelId();
+ Loc origin_loc = ctx->getBelLocation(origin);
+ int best_distance = std::numeric_limits<int>::max();
+ BelId best_bel = BelId();
+
+ for (BelId bel : ctx->getBels()) {
+ if (ctx->getBelType(bel) != dest_type)
+ continue;
+ if (!predicate(bel))
+ continue;
+ Loc bel_loc = ctx->getBelLocation(bel);
+ int dist = std::abs(origin_loc.x - bel_loc.x) + std::abs(origin_loc.y - bel_loc.y);
+ if (dist < best_distance) {
+ best_distance = dist;
+ best_bel = bel;
+ }
+ }
+ return best_bel;
+ }
+
+ std::unordered_set<BelId> used_bels;
+
+ // Pre-place a primitive based on routeability first and distance second
+ bool preplace_prim(CellInfo *cell, IdString pin, bool strict_routing)
+ {
+ std::vector<BelId> routeability_candidates;
+
+ if (cell->attrs.count(id_BEL))
+ return false;
+
+ NetInfo *pin_net = get_net_or_empty(cell, pin);
+ if (pin_net == nullptr)
+ return false;
+
+ CellInfo *pin_drv = pin_net->driver.cell;
+ if (pin_drv == nullptr)
+ return false;
+
+ // Check based on routeability
+ find_connected_bels(pin_drv, pin_net->driver.port, cell->type, pin, 25000, routeability_candidates);
+
+ for (BelId cand : routeability_candidates) {
+ if (used_bels.count(cand))
+ continue;
+ log_info(" constraining %s '%s' to bel '%s' based on dedicated routing\n", ctx->nameOf(cell),
+ ctx->nameOf(cell->type), ctx->nameOfBel(cand));
+ cell->attrs[id_BEL] = ctx->getBelName(cand).str(ctx);
+ used_bels.insert(cand);
+ return true;
+ }
+
+ // Unless in strict mode; check based on simple distance too
+ BelId nearest = find_nearest_bel(pin_drv, cell->type, [&](BelId bel) { return !used_bels.count(bel); });
+
+ if (nearest != BelId()) {
+ log_info(" constraining %s '%s' to bel '%s'\n", ctx->nameOf(cell), ctx->nameOf(cell->type),
+ ctx->nameOfBel(nearest));
+ cell->attrs[id_BEL] = ctx->getBelName(nearest).str(ctx);
+ used_bels.insert(nearest);
+ return true;
+ }
+
+ return false;
+ }
+
+ // Pre-place a singleton primitive; so decisions can be made on routeability downstream of it
+ bool preplace_singleton(CellInfo *cell)
+ {
+ if (cell->attrs.count(id_BEL))
+ return false;
+ bool did_something = false;
+ for (BelId bel : ctx->getBels()) {
+ if (ctx->getBelType(bel) != cell->type)
+ continue;
+ // Check that the bel really is a singleton...
+ NPNR_ASSERT(!cell->attrs.count(id_BEL));
+ cell->attrs[id_BEL] = ctx->getBelName(bel).str(ctx);
+ log_info(" constraining %s '%s' to bel '%s'\n", ctx->nameOf(cell), ctx->nameOf(cell->type),
+ ctx->nameOfBel(bel));
+ did_something = true;
+ }
+ return did_something;
+ }
+
+ // Insert a buffer primitive in a signal; moving all users that match a predicate behind it
+ template <typename Tpred>
+ CellInfo *insert_buffer(NetInfo *net, IdString buffer_type, std::string name_postfix, IdString i, IdString o,
+ Tpred pred)
+ {
+ // Create the buffered net
+ NetInfo *buffered_net = ctx->createNet(ctx->id(stringf("%s$%s", ctx->nameOf(net), name_postfix.c_str())));
+ // Create the buffer cell
+ CellInfo *buffer = ctx->createCell(
+ ctx->id(stringf("%s$drv_%s", ctx->nameOf(buffered_net), ctx->nameOf(buffer_type))), buffer_type);
+ buffer->addInput(i);
+ buffer->addOutput(o);
+ // Drive the buffered net with the buffer
+ connect_port(ctx, buffered_net, buffer, o);
+ // Filter users
+ std::vector<PortRef> remaining_users;
+
+ for (auto &usr : net->users) {
+ if (pred(usr)) {
+ usr.cell->ports[usr.port].net = buffered_net;
+ buffered_net->users.push_back(usr);
+ } else {
+ remaining_users.push_back(usr);
+ }
+ }
+
+ std::swap(net->users, remaining_users);
+
+ // Connect buffer input to original net
+ connect_port(ctx, net, buffer, i);
+
+ return buffer;
+ }
+
+ // Insert global buffers
+ void promote_globals()
+ {
+ std::vector<std::pair<int, IdString>> clk_fanout;
+ int available_globals = 16;
+ for (auto net : sorted(ctx->nets)) {
+ NetInfo *ni = net.second;
+ // Skip undriven nets; and nets that are already global
+ if (ni->driver.cell == nullptr)
+ continue;
+ if (ni->driver.cell->type == id_DCC) {
+ --available_globals;
+ continue;
+ }
+ // Count the number of clock ports
+ int clk_count = 0;
+ for (const auto &usr : ni->users) {
+ auto port_style = ctx->get_cell_pin_style(usr.cell, usr.port);
+ if (port_style & PINGLB_CLK)
+ ++clk_count;
+ }
+ if (clk_count > 0)
+ clk_fanout.emplace_back(clk_count, ni->name);
+ }
+ if (available_globals <= 0)
+ return;
+ // Sort clocks by max fanout
+ std::sort(clk_fanout.begin(), clk_fanout.end(), std::greater<std::pair<int, IdString>>());
+ log_info("Promoting globals...\n");
+ // Promote the N highest fanout clocks
+ for (size_t i = 0; i < std::min<size_t>(clk_fanout.size(), available_globals); i++) {
+ NetInfo *net = ctx->nets.at(clk_fanout.at(i).second).get();
+ log_info(" promoting clock net '%s'\n", ctx->nameOf(net));
+ insert_buffer(net, id_DCC, "glb_clk", id_CLKI, id_CLKO, [](const PortRef &port) { return true; });
+ }
+ }
+
+ // Place certain global cells
+ void place_globals()
+ {
+ // Keep running until we reach a fixed point
+ log_info("Placing globals...\n");
+ bool did_something = true;
+ while (did_something) {
+ did_something = false;
+ for (auto cell : sorted(ctx->cells)) {
+ CellInfo *ci = cell.second;
+ if (ci->type == id_OSC_CORE)
+ did_something |= preplace_singleton(ci);
+ else if (ci->type == id_DCC)
+ did_something |= preplace_prim(ci, id_CLKI, false);
+ }
+ }
+ }
+
+ // Get a bus port name
+ IdString bus(const std::string &base, int i) { return ctx->id(stringf("%s[%d]", base.c_str(), i)); }
+
+ IdString bus_flat(const std::string &base, int i) { return ctx->id(stringf("%s%d", base.c_str(), i)); }
+
+ // Pack a LUTRAM into COMB and RAMW cells
+ void pack_lutram()
+ {
+ // Do this so we don't have an iterate-and-modfiy situation
+ std::vector<CellInfo *> lutrams;
+ for (auto cell : sorted(ctx->cells)) {
+ CellInfo *ci = cell.second;
+ if (ci->type != id_DPR16X4)
+ continue;
+ lutrams.push_back(ci);
+ }
+
+ // Port permutation vectors
+ IdString ramw_wdo[4] = {id_D1, id_C1, id_A1, id_B1};
+ IdString ramw_wado[4] = {id_D0, id_B0, id_C0, id_A0};
+ IdString comb0_rad[4] = {id_D, id_B, id_C, id_A};
+ IdString comb1_rad[4] = {id_C, id_B, id_D, id_A};
+
+ for (CellInfo *ci : lutrams) {
+ // Create constituent cells
+ CellInfo *ramw = ctx->createCell(ctx->id(stringf("%s$lutram_ramw$", ctx->nameOf(ci))), id_RAMW);
+ std::vector<CellInfo *> combs;
+ for (int i = 0; i < 4; i++)
+ combs.push_back(
+ ctx->createCell(ctx->id(stringf("%s$lutram_comb[%d]$", ctx->nameOf(ci), i)), id_OXIDE_COMB));
+ // Rewiring - external WCK and WRE
+ replace_port(ci, id_WCK, ramw, id_CLK);
+ replace_port(ci, id_WRE, ramw, id_LSR);
+
+ // Internal WCK and WRE signals
+ ramw->addOutput(id_WCKO);
+ ramw->addOutput(id_WREO);
+ NetInfo *int_wck = ctx->createNet(ctx->id(stringf("%s$lutram_wck$", ctx->nameOf(ci))));
+ NetInfo *int_wre = ctx->createNet(ctx->id(stringf("%s$lutram_wre$", ctx->nameOf(ci))));
+ connect_port(ctx, int_wck, ramw, id_WCKO);
+ connect_port(ctx, int_wre, ramw, id_WREO);
+
+ uint64_t initval = ctx->parse_lattice_param(ci, id_INITVAL, 64, 0).as_int64();
+
+ // Rewiring - buses
+ for (int i = 0; i < 4; i++) {
+ // Write address - external
+ replace_port(ci, bus("WAD", i), ramw, ramw_wado[i]);
+ // Write data - external
+ replace_port(ci, bus("DI", i), ramw, ramw_wdo[i]);
+ // Read data
+ replace_port(ci, bus("DO", i), combs[i], id_F);
+ // Read address
+ NetInfo *rad = get_net_or_empty(ci, bus("RAD", i));
+ if (rad != nullptr) {
+ for (int j = 0; j < 4; j++) {
+ IdString port = (j % 2) ? comb1_rad[i] : comb0_rad[i];
+ combs[j]->addInput(port);
+ connect_port(ctx, rad, combs[j], port);
+ }
+ disconnect_port(ctx, ci, bus("RAD", i));
+ }
+ // Write address - internal
+ NetInfo *int_wad = ctx->createNet(ctx->id(stringf("%s$lutram_wad[%d]$", ctx->nameOf(ci), i)));
+ ramw->addOutput(bus_flat("WADO", i));
+ connect_port(ctx, int_wad, ramw, bus_flat("WADO", i));
+ for (int j = 0; j < 4; j++) {
+ combs[j]->addInput(bus_flat("WAD", i));
+ connect_port(ctx, int_wad, combs[j], bus_flat("WAD", i));
+ }
+ // Write data - internal
+ NetInfo *int_wd = ctx->createNet(ctx->id(stringf("%s$lutram_wd[%d]$", ctx->nameOf(ci), i)));
+ ramw->addOutput(bus_flat("WDO", i));
+ connect_port(ctx, int_wd, ramw, bus_flat("WDO", i));
+ combs[i]->addInput(id_WDI);
+ connect_port(ctx, int_wd, combs[i], id_WDI);
+ // Write clock and enable - internal
+ combs[i]->addInput(id_WCK);
+ combs[i]->addInput(id_WRE);
+ connect_port(ctx, int_wck, combs[i], id_WCK);
+ connect_port(ctx, int_wre, combs[i], id_WRE);
+ // Remap init val
+ uint64_t split_init = 0;
+ for (int j = 0; j < 16; j++)
+ if (initval & (1ULL << (4 * j + i)))
+ split_init |= (1 << j);
+ combs[i]->params[id_INIT] = Property(split_init, 16);
+
+ combs[i]->params[id_MODE] = std::string("DPRAM");
+ }
+
+ // Setup relative constraints
+ combs[0]->constr_z = 0;
+ combs[0]->constr_abs_z = true;
+ for (int i = 1; i < 4; i++) {
+ combs[i]->constr_x = 0;
+ combs[i]->constr_y = 0;
+ combs[i]->constr_z = ((i / 2) << 3) | (i % 2);
+ combs[i]->constr_abs_z = true;
+ combs[i]->constr_parent = combs[0];
+ combs[0]->constr_children.push_back(combs[i]);
+ }
+
+ ramw->constr_x = 0;
+ ramw->constr_y = 0;
+ ramw->constr_z = (2 << 3) | Arch::BEL_RAMW;
+ ramw->constr_abs_z = true;
+ ramw->constr_parent = combs[0];
+ combs[0]->constr_children.push_back(ramw);
+ // Remove now-packed cell
+ ctx->cells.erase(ci->name);
+ }
+ }
+
+ void convert_prims()
+ {
+ // Convert primitives from their non-CORE variant to their CORE variant
+ static const std::unordered_map<IdString, IdString> prim_map = {
+ {id_OSCA, id_OSC_CORE}, {id_DP16K, id_DP16K_MODE}, {id_PDP16K, id_PDP16K_MODE},
+ {id_PDPSC16K, id_PDPSC16K_MODE}, {id_SP16K, id_SP16K_MODE}, {id_FIFO16K, id_FIFO16K_MODE},
+ };
+
+ for (auto cell : sorted(ctx->cells)) {
+ CellInfo *ci = cell.second;
+ if (!prim_map.count(ci->type))
+ continue;
+ prim_to_core(ci, prim_map.at(ci->type));
+ }
+ }
+
+ void add_bus_xform(XFormRule &rule, const std::string &o, const std::string &n, int width, int old_offset = 0,
+ int new_offset = 0)
+ {
+ for (int i = 0; i < width; i++)
+ rule.port_xform[bus_flat(o, i + old_offset)] = bus_flat(n, i + new_offset);
+ }
+
+ void pack_bram()
+ {
+ std::unordered_map<IdString, XFormRule> bram_rules;
+ bram_rules[id_DP16K_MODE].new_type = id_OXIDE_EBR;
+ bram_rules[id_DP16K_MODE].set_params.emplace_back(id_MODE, std::string("DP16K"));
+ bram_rules[id_DP16K_MODE].parse_params.emplace_back(id_CSDECODE_A, id_CSDECODE_A, 3, 7);
+ bram_rules[id_DP16K_MODE].parse_params.emplace_back(id_CSDECODE_B, id_CSDECODE_B, 3, 7);
+ // Pseudo dual port
+ bram_rules[id_PDP16K_MODE].new_type = id_OXIDE_EBR;
+ bram_rules[id_PDP16K_MODE].set_params.emplace_back(id_MODE, std::string("PDP16K"));
+ bram_rules[id_PDP16K_MODE].set_params.emplace_back(id_WEAMUX, std::string("1"));
+ bram_rules[id_PDP16K_MODE].parse_params.emplace_back(id_CSDECODE_R, id_CSDECODE_R, 3, 7);
+ bram_rules[id_PDP16K_MODE].parse_params.emplace_back(id_CSDECODE_W, id_CSDECODE_W, 3, 7);
+ bram_rules[id_PDP16K_MODE].port_xform[id_CLKW] = id_CLKA;
+ bram_rules[id_PDP16K_MODE].port_xform[id_CLKR] = id_CLKB;
+ bram_rules[id_PDP16K_MODE].port_xform[id_CEW] = id_CEA;
+ bram_rules[id_PDP16K_MODE].port_xform[id_CER] = id_CEB;
+ bram_rules[id_PDP16K_MODE].port_multixform[id_RST] = {id_RSTA, id_RSTB};
+ add_bus_xform(bram_rules[id_PDP16K_MODE], "ADW", "ADA", 14);
+ add_bus_xform(bram_rules[id_PDP16K_MODE], "ADR", "ADB", 14);
+ add_bus_xform(bram_rules[id_PDP16K_MODE], "CSW", "CSA", 3);
+ add_bus_xform(bram_rules[id_PDP16K_MODE], "CSR", "CSB", 3);
+ add_bus_xform(bram_rules[id_PDP16K_MODE], "DI", "DIA", 18, 0, 0);
+ add_bus_xform(bram_rules[id_PDP16K_MODE], "DI", "DIB", 18, 18, 0);
+ add_bus_xform(bram_rules[id_PDP16K_MODE], "DO", "DOB", 18, 0, 0);
+ add_bus_xform(bram_rules[id_PDP16K_MODE], "DO", "DOA", 18, 18, 0);
+
+ // Pseudo dual port; single clock
+ bram_rules[id_PDPSC16K_MODE] = bram_rules[id_PDP16K_MODE];
+ bram_rules[id_PDPSC16K_MODE].set_params.clear();
+ bram_rules[id_PDPSC16K_MODE].set_params.emplace_back(id_MODE, std::string("PDPSC16K"));
+ bram_rules[id_PDPSC16K_MODE].set_params.emplace_back(id_WEAMUX, std::string("1"));
+ bram_rules[id_PDPSC16K_MODE].port_multixform[id_CLK] = {id_CLKA, id_CLKB};
+
+ log_info("Packing BRAM...\n");
+ generic_xform(bram_rules, true);
+
+ int wid = 2;
+ for (auto cell : sorted(ctx->cells)) {
+ CellInfo *ci = cell.second;
+ if (ci->type != id_OXIDE_EBR)
+ continue;
+ if (ci->params.count(id_WID))
+ continue;
+ ci->params[id_WID] = wid++;
+ }
+ }
+
+ void pack_widefn()
+ {
+ std::vector<CellInfo *> widefns;
+ for (auto cell : sorted(ctx->cells)) {
+ CellInfo *ci = cell.second;
+ if (ci->type != id_WIDEFN9)
+ continue;
+ widefns.push_back(ci);
+ }
+
+ for (CellInfo *ci : widefns) {
+ std::vector<CellInfo *> combs;
+ for (int i = 0; i < 2; i++)
+ combs.push_back(
+ ctx->createCell(ctx->id(stringf("%s$widefn_comb[%d]$", ctx->nameOf(ci), i)), id_OXIDE_COMB));
+
+ for (int i = 0; i < 2; i++) {
+ replace_port(ci, bus_flat("A", i), combs[i], id_A);
+ replace_port(ci, bus_flat("B", i), combs[i], id_B);
+ replace_port(ci, bus_flat("C", i), combs[i], id_C);
+ replace_port(ci, bus_flat("D", i), combs[i], id_D);
+ }
+
+ replace_port(ci, id_SEL, combs[0], id_SEL);
+ replace_port(ci, id_Z, combs[0], id_OFX);
+
+ NetInfo *f1 = ctx->createNet(ctx->id(stringf("%s$widefn_f1$", ctx->nameOf(ci))));
+ combs[0]->addInput(id_F1);
+ combs[1]->addOutput(id_F);
+ connect_port(ctx, f1, combs[1], id_F);
+ connect_port(ctx, f1, combs[0], id_F1);
+
+ combs[0]->params[id_INIT] = ctx->parse_lattice_param(ci, id_INIT0, 16, 0);
+ combs[1]->params[id_INIT] = ctx->parse_lattice_param(ci, id_INIT1, 16, 0);
+
+ combs[1]->constr_parent = combs[0];
+ combs[1]->constr_x = 0;
+ combs[1]->constr_y = 0;
+ combs[1]->constr_z = 1;
+ combs[1]->constr_abs_z = false;
+ combs[0]->constr_children.push_back(combs[1]);
+
+ ctx->cells.erase(ci->name);
+ }
+ }
+
+ void pack_carries()
+ {
+ // Find root carry cells
+ log_info("Packing carries...\n");
+ std::vector<CellInfo *> roots;
+ for (auto cell : sorted(ctx->cells)) {
+ CellInfo *ci = cell.second;
+ if (ci->type != id_CCU2)
+ continue;
+ if (get_net_or_empty(ci, id_CIN) != nullptr)
+ continue;
+ roots.push_back(ci);
+ }
+ for (CellInfo *root : roots) {
+ CellInfo *ci = root;
+ CellInfo *constr_base = nullptr;
+ int idx = 0;
+ do {
+ if (ci->type != id_CCU2)
+ log_error("Found non-carry cell '%s' in carry chain!\n", ctx->nameOf(ci));
+ // Split the carry into two COMB cells
+ std::vector<CellInfo *> combs;
+ for (int i = 0; i < 2; i++)
+ combs.push_back(
+ ctx->createCell(ctx->id(stringf("%s$ccu2_comb[%d]$", ctx->nameOf(ci), i)), id_OXIDE_COMB));
+ // Rewire LUT ports
+ for (int i = 0; i < 2; i++) {
+ combs[i]->params[id_MODE] = std::string("CCU2");
+ replace_port(ci, bus_flat("A", i), combs[i], id_A);
+ replace_port(ci, bus_flat("B", i), combs[i], id_B);
+ replace_port(ci, bus_flat("C", i), combs[i], id_C);
+ replace_port(ci, bus_flat("D", i), combs[i], id_D);
+ replace_port(ci, bus_flat("S", i), combs[i], id_F);
+ }
+
+ // External carry chain
+ replace_port(ci, id_CIN, combs[0], id_FCI);
+ replace_port(ci, id_COUT, combs[1], id_FCO);
+
+ // Copy parameters
+ if (ci->params.count(id_INJECT))
+ combs[0]->params[id_INJECT] = ci->params[id_INJECT];
+ combs[0]->params[id_INIT] = ctx->parse_lattice_param(ci, id_INIT0, 16, 0);
+ combs[1]->params[id_INIT] = ctx->parse_lattice_param(ci, id_INIT1, 16, 0);
+
+ // Internal carry net between the two split COMB cells
+ NetInfo *int_cy = ctx->createNet(ctx->id(stringf("%s$widefn_int_cy$", ctx->nameOf(ci))));
+ combs[0]->addOutput(id_FCO);
+ combs[1]->addInput(id_FCI);
+ connect_port(ctx, int_cy, combs[0], id_FCO);
+ connect_port(ctx, int_cy, combs[1], id_FCI);
+
+ // Relative constraints
+ for (int i = 0; i < 2; i++) {
+ int z = (idx % 8);
+ combs[i]->constr_z = ((z / 2) << 3) | (z % 2);
+ combs[i]->constr_abs_z = true;
+ if (constr_base == nullptr) {
+ // This is the very first cell in the chain
+ constr_base = combs[i];
+ } else {
+ combs[i]->constr_x = (idx / 8);
+ combs[i]->constr_y = 0;
+ combs[i]->constr_parent = constr_base;
+ constr_base->constr_children.push_back(combs[i]);
+ }
+
+ ++idx;
+ }
+
+ ctx->cells.erase(ci->name);
+
+ // Find next cell in chain, if it exists
+ NetInfo *fco = get_net_or_empty(combs[1], id_FCO);
+ ci = nullptr;
+ if (fco != nullptr) {
+ if (fco->users.size() > 1)
+ log_error("Carry cell '%s' has multiple fanout on FCO\n", ctx->nameOf(combs[1]));
+ else if (fco->users.size() == 1) {
+ NPNR_ASSERT(fco->users.at(0).port == id_CIN);
+ ci = fco->users.at(0).cell;
+ }
+ }
+ } while (ci != nullptr);
+ }
+ }
+
+ // Function to check if a wire is general routing; and therefore skipped for cascade purposes
+ bool is_general_routing(WireId wire)
+ {
+ std::string name = ctx->nameOf(ctx->wire_data(wire).name);
+ if (name.size() == 3 && (name.substr(0, 2) == "JF" || name.substr(0, 2) == "JQ"))
+ return true;
+ if (name.size() == 12 && (name.substr(0, 10) == "JCIBMUXOUT"))
+ return true;
+ return false;
+ }
+
+ // Automatically generate cascade connections downstream of a cell
+ // using the temporary placement that we use solely to access the routing graph
+ void auto_cascade_cell(CellInfo *cell, BelId bel, const std::unordered_map<BelId, CellInfo *> &bel2cell)
+ {
+ // Create outputs based on the actual bel
+ for (auto bp : ctx->getBelPins(bel)) {
+ if (ctx->getBelPinType(bel, bp) != PORT_OUT)
+ continue;
+ if (cell->ports.count(bp))
+ continue;
+ cell->addOutput(bp);
+ }
+ for (auto port : sorted_ref(cell->ports)) {
+ // Skip if not an output, or being used already for something else
+ if (port.second.type != PORT_OUT || port.second.net != nullptr)
+ continue;
+ // Get the corresponding start wire
+ WireId start_wire = ctx->getBelPinWire(bel, port.first);
+
+ // Skip if the start wire doesn't actually exist
+ if (start_wire == WireId())
+ continue;
+
+ if (ctx->debug)
+ log_info(" searching cascade routing for wire %s:\n", ctx->nameOfWire(start_wire));
+
+ // Standard BFS-type exploration
+ std::queue<WireId> visit;
+ std::unordered_set<WireId> in_queue;
+ visit.push(start_wire);
+ in_queue.insert(start_wire);
+ int iter = 0;
+ const int iter_limit = 1000;
+
+ while (!visit.empty() && (iter++ < iter_limit)) {
+ WireId cursor = visit.front();
+ visit.pop();
+
+ if (ctx->debug)
+ log_info(" visit '%s'\n", ctx->nameOfWire(cursor));
+
+ // Check for downstream bel pins
+ bool found_active_pins = false;
+ for (auto bp : ctx->getWireBelPins(cursor)) {
+ auto fnd_cell = bel2cell.find(bp.bel);
+ // Always skip unused bels, and don't set found_active_pins
+ // so we can route through these if needed
+ if (fnd_cell == bel2cell.end())
+ continue;
+ // Skip outputs
+ if (ctx->getBelPinType(bp.bel, bp.pin) != PORT_IN)
+ continue;
+
+ if (ctx->debug)
+ log_info(" bel %s pin %s\n", ctx->nameOfBel(bp.bel), ctx->nameOf(bp.pin));
+
+ found_active_pins = true;
+ CellInfo *other_cell = fnd_cell->second;
+
+ if (other_cell == cell)
+ continue;
+
+ // Skip pins that are already in use
+ if (get_net_or_empty(other_cell, bp.pin) != nullptr)
+ continue;
+ // Create the input if it doesn't exist
+ if (!other_cell->ports.count(bp.pin))
+ other_cell->addInput(bp.pin);
+ // Make the connection
+ connect_ports(ctx, cell, port.first, other_cell, bp.pin);
+
+ if (ctx->debug)
+ log_info(" found %s.%s\n", ctx->nameOf(other_cell), ctx->nameOf(bp.pin));
+ }
+
+ // By doing this we never attempt to route-through bels
+ // that are actually in use
+ if (found_active_pins)
+ continue;
+
+ // Search downstream pips for wires to add to the queue
+ for (auto pip : ctx->getPipsDownhill(cursor)) {
+ WireId dst = ctx->getPipDstWire(pip);
+ // Ignore general routing, as that isn't a useful cascade path
+ if (is_general_routing(dst))
+ continue;
+ if (in_queue.count(dst))
+ continue;
+ in_queue.insert(dst);
+ visit.push(dst);
+ }
+ }
+ }
+ }
+
+ // Insert all the cascade connections for a group of cells given the root
+ void auto_cascade_group(CellInfo *root)
+ {
+
+ auto get_child_loc = [&](Loc base, const CellInfo *sub) {
+ Loc l = base;
+ l.x += sub->constr_x;
+ l.y += sub->constr_y;
+ l.z = sub->constr_abs_z ? sub->constr_z : (sub->constr_z + base.z);
+ return l;
+ };
+
+ // We first create a temporary placement so we can access the routing graph
+ bool found = false;
+ std::unordered_map<BelId, CellInfo *> bel2cell;
+ std::unordered_map<IdString, BelId> cell2bel;
+
+ for (BelId root_bel : ctx->getBels()) {
+ if (ctx->getBelType(root_bel) != root->type)
+ continue;
+ Loc root_loc = ctx->getBelLocation(root_bel);
+ found = true;
+ bel2cell.clear();
+ cell2bel.clear();
+ bel2cell[root_bel] = root;
+ cell2bel[root->name] = root_bel;
+
+ for (auto child : root->constr_children) {
+ // Check that a valid placement exists for all children in the macro at this location
+ Loc c_loc = get_child_loc(root_loc, child);
+ BelId c_bel = ctx->getBelByLocation(c_loc);
+ if (c_bel == BelId()) {
+ found = false;
+ break;
+ }
+ if (ctx->getBelType(c_bel) != child->type) {
+ found = false;
+ break;
+ }
+ bel2cell[c_bel] = child;
+ cell2bel[child->name] = c_bel;
+ }
+
+ if (found)
+ break;
+ }
+
+ if (!found)
+ log_error("Failed to create temporary placement for cell '%s' of type '%s'\n", ctx->nameOf(root),
+ ctx->nameOf(root->type));
+
+ // Create the necessary new ports
+ autocreate_ports(root);
+ for (auto child : root->constr_children)
+ autocreate_ports(child);
+
+ // Insert cascade connections from all cells in the macro
+ auto_cascade_cell(root, cell2bel.at(root->name), bel2cell);
+ for (auto child : root->constr_children)
+ auto_cascade_cell(child, cell2bel.at(child->name), bel2cell);
+ }
+
+ // Create a DSP cell
+ CellInfo *create_dsp_cell(IdString base_name, IdString type, CellInfo *constr_base, int dx, int dz)
+ {
+ IdString name = ctx->id(stringf("%s/%s_x%d_z%d", ctx->nameOf(base_name), ctx->nameOf(type), dx, dz));
+ CellInfo *cell = ctx->createCell(name, type);
+ if (constr_base != nullptr) {
+ // We might be constraining against an already-constrained cell
+ if (constr_base->constr_parent != nullptr) {
+ cell->constr_x = dx + constr_base->constr_x;
+ cell->constr_y = constr_base->constr_y;
+ cell->constr_z = dz + constr_base->constr_z;
+ cell->constr_abs_z = false;
+ cell->constr_parent = constr_base->constr_parent;
+ constr_base->constr_parent->constr_children.push_back(cell);
+ } else {
+ cell->constr_x = dx;
+ cell->constr_y = 0;
+ cell->constr_z = dz;
+ cell->constr_abs_z = false;
+ cell->constr_parent = constr_base;
+ constr_base->constr_children.push_back(cell);
+ }
+ }
+ // Setup some default parameters
+ if (type == id_PREADD9_CORE) {
+ cell->params[id_SIGNEDSTATIC_EN] = std::string("DISABLED");
+ cell->params[id_BYPASS_PREADD9] = std::string("BYPASS");
+ cell->params[id_CSIGNED] = std::string("DISABLED");
+ cell->params[id_GSR] = std::string("DISABLED");
+ cell->params[id_OPC] = std::string("INPUT_B_AS_PREADDER_OPERAND");
+ cell->params[id_PREADDCAS_EN] = std::string("DISABLED");
+ cell->params[id_REGBYPSBL] = std::string("REGISTER");
+ cell->params[id_REGBYPSBR0] = std::string("BYPASS");
+ cell->params[id_REGBYPSBR1] = std::string("BYPASS");
+ cell->params[id_RESET] = std::string("SYNC");
+ cell->params[id_SHIFTBL] = std::string("BYPASS");
+ cell->params[id_SHIFTBR] = std::string("REGISTER");
+ cell->params[id_SIGNEDSTATIC_EN] = std::string("DISABLED");
+ cell->params[id_SR_18BITSHIFT_EN] = std::string("DISABLED");
+ cell->params[id_SUBSTRACT_EN] = std::string("SUBTRACTION");
+ } else if (type == id_MULT9_CORE) {
+ cell->params[id_ASIGNED_OPERAND_EN] = std::string("DISABLED");
+ cell->params[id_BYPASS_MULT9] = std::string("USED");
+ cell->params[id_GSR] = std::string("DISABLED");
+ cell->params[id_REGBYPSA1] = std::string("BYPASS");
+ cell->params[id_REGBYPSA2] = std::string("BYPASS");
+ cell->params[id_REGBYPSB] = std::string("BYPASS");
+ cell->params[id_RESET] = std::string("SYNC");
+ cell->params[id_GSR] = std::string("DISABLED");
+ cell->params[id_SHIFTA] = std::string("DISABLED");
+ cell->params[id_SIGNEDSTATIC_EN] = std::string("DISABLED");
+ cell->params[id_SR_18BITSHIFT_EN] = std::string("DISABLED");
+ } else if (type == id_MULT18_CORE) {
+ cell->params[id_MULT18X18] = std::string("ENABLED");
+ cell->params[id_ROUNDBIT] = std::string("ROUND_TO_BIT0");
+ cell->params[id_ROUNDHALFUP] = std::string("DISABLED");
+ cell->params[id_ROUNDRTZI] = std::string("ROUND_TO_ZERO");
+ cell->params[id_SFTEN] = std::string("DISABLED");
+ } else if (type == id_MULT18X36_CORE) {
+ cell->params[id_SFTEN] = std::string("DISABLED");
+ cell->params[id_MULT18X36] = std::string("ENABLED");
+ cell->params[id_MULT36] = std::string("DISABLED");
+ cell->params[id_MULT36X36H] = std::string("USED_AS_LOWER_BIT_GENERATION");
+ cell->params[id_ROUNDHALFUP] = std::string("DISABLED");
+ cell->params[id_ROUNDRTZI] = std::string("ROUND_TO_ZERO");
+ cell->params[id_ROUNDBIT] = std::string("ROUND_TO_BIT0");
+ } else if (type == id_MULT36_CORE) {
+ cell->params[id_MULT36X36] = std::string("ENABLED");
+ } else if (type == id_REG18_CORE) {
+ cell->params[id_GSR] = std::string("DISABLED");
+ cell->params[id_REGBYPS] = std::string("BYPASS");
+ cell->params[id_RESET] = std::string("SYNC");
+ } else if (type == id_ACC54_CORE) {
+ cell->params[id_ACC108CASCADE] = std::string("BYPASSCASCADE");
+ cell->params[id_ACCUBYPS] = std::string("USED");
+ cell->params[id_ACCUMODE] = std::string("MODE7");
+ cell->params[id_ADDSUBSIGNREGBYPS1] = std::string("BYPASS");
+ cell->params[id_ADDSUBSIGNREGBYPS2] = std::string("BYPASS");
+ cell->params[id_ADDSUBSIGNREGBYPS3] = std::string("BYPASS");
+ cell->params[id_ADDSUB_CTRL] = std::string("ADD_ADD_CTRL_54_BIT_ADDER");
+ cell->params[id_CASCOUTREGBYPS] = std::string("BYPASS");
+ cell->params[id_CINREGBYPS1] = std::string("BYPASS");
+ cell->params[id_CINREGBYPS2] = std::string("BYPASS");
+ cell->params[id_CINREGBYPS3] = std::string("BYPASS");
+ cell->params[id_CONSTSEL] = std::string("BYPASS");
+ cell->params[id_CREGBYPS1] = std::string("BYPASS");
+ cell->params[id_CREGBYPS2] = std::string("BYPASS");
+ cell->params[id_CREGBYPS3] = std::string("BYPASS");
+ cell->params[id_DSPCASCADE] = std::string("DISABLED");
+ cell->params[id_GSR] = std::string("DISABLED");
+ cell->params[id_LOADREGBYPS1] = std::string("BYPASS");
+ cell->params[id_LOADREGBYPS2] = std::string("BYPASS");
+ cell->params[id_LOADREGBYPS3] = std::string("BYPASS");
+ cell->params[id_M9ADDSUBREGBYPS1] = std::string("BYPASS");
+ cell->params[id_M9ADDSUBREGBYPS2] = std::string("BYPASS");
+ cell->params[id_M9ADDSUBREGBYPS3] = std::string("BYPASS");
+ cell->params[id_M9ADDSUB_CTRL] = std::string("ADDITION");
+ cell->params[id_OUTREGBYPS] = std::string("BYPASS");
+ cell->params[id_RESET] = std::string("SYNC");
+ cell->params[id_ROUNDHALFUP] = std::string("DISABLED");
+ cell->params[id_ROUNDRTZI] = std::string("ROUND_TO_ZERO");
+ cell->params[id_ROUNDBIT] = std::string("ROUND_TO_BIT0");
+ cell->params[id_SFTEN] = std::string("DISABLED");
+ cell->params[id_SIGN] = std::string("DISABLED");
+ cell->params[id_STATICOPCODE_EN] = std::string("DISABLED");
+ }
+ return cell;
+ }
+
+ void copy_global_dsp_params(CellInfo *orig, CellInfo *root)
+ {
+ if (root->params.count(id_GSR) && orig->params.count(id_GSR))
+ root->params[id_GSR] = orig->params.at(id_GSR);
+ if (root->params.count(id_RESET) && orig->params.count(id_RESETMODE))
+ root->params[id_RESET] = orig->params.at(id_RESETMODE);
+ for (auto child : root->constr_children)
+ copy_global_dsp_params(orig, child);
+ }
+
+ void copy_param(CellInfo *orig, IdString orig_name, CellInfo *dst, IdString dst_name)
+ {
+ if (!orig->params.count(orig_name))
+ return;
+ dst->params[dst_name] = orig->params[orig_name];
+ }
+
+ struct DSPMacroType
+ {
+ int a_width; // width of 'A' input
+ int b_width; // width of 'B' input
+ int c_width; // width of 'C' input
+ int z_width; // width of 'Z' output
+ int N9x9; // number of 9x9 mult+preadds
+ int N18x18; // number of 18x18 mult
+ int N18x36; // number of 18x36 mult
+ bool has_preadd; // preadder is used
+ bool has_addsub; // post-multiply ALU addsub is used
+ };
+
+ const std::unordered_map<IdString, DSPMacroType> dsp_types = {
+ {id_MULT9X9, {9, 9, 0, 18, 1, 0, 0, false, false}},
+ {id_MULT18X18, {18, 18, 0, 36, 2, 1, 0, false, false}},
+ {id_MULT18X36, {18, 36, 0, 54, 4, 2, 1, false, false}},
+ {id_MULT36X36, {36, 36, 0, 72, 8, 4, 2, false, false}},
+ {id_MULTPREADD9X9, {9, 9, 9, 18, 1, 0, 0, true, false}},
+ {id_MULTPREADD18X18, {18, 18, 18, 36, 2, 1, 0, true, false}},
+ {id_MULTADDSUB18X18, {18, 18, 54, 54, 2, 1, 0, false, true}},
+ {id_MULTADDSUB36X36, {36, 36, 108, 108, 8, 4, 2, false, true}},
+ };
+
+ void pack_dsps()
+ {
+ log_info("Packing DSPs...\n");
+ std::vector<CellInfo *> to_remove;
+
+ for (auto cell : sorted(ctx->cells)) {
+ CellInfo *ci = cell.second;
+ if (!dsp_types.count(ci->type))
+ continue;
+ auto &mt = dsp_types.at(ci->type);
+ int Nreg18 = mt.z_width / 18;
+
+ // Create consituent cells
+ std::vector<CellInfo *> preadd9(mt.N9x9), mult9(mt.N9x9), mult18(mt.N18x18), mult18x36(mt.N18x36),
+ reg18(Nreg18);
+ for (int i = 0; i < mt.N9x9; i++) {
+ preadd9[i] = create_dsp_cell(ci->name, id_PREADD9_CORE, preadd9[0], (i / 4) * 4 + (i / 2) % 2, (i % 2));
+ mult9[i] = create_dsp_cell(ci->name, id_MULT9_CORE, preadd9[0], (i / 4) * 4 + (i / 2) % 2, (i % 2) + 2);
+ }
+ for (int i = 0; i < mt.N18x18; i++)
+ mult18[i] = create_dsp_cell(ci->name, id_MULT18_CORE, preadd9[0], (i / 2) * 4 + i % 2, 4);
+ for (int i = 0; i < mt.N18x36; i++)
+ mult18x36[i] = create_dsp_cell(ci->name, id_MULT18X36_CORE, preadd9[0], (i * 4) + 2, 4);
+ for (int i = 0; i < Nreg18; i++) {
+ int idx = i;
+ if (mt.has_addsub && (i >= 4))
+ idx += 2;
+ reg18[i] = create_dsp_cell(ci->name, id_REG18_CORE, preadd9[0], (idx / 4) * 4 + 2, idx % 4);
+ }
+
+ // Configure the 9x9 preadd+multiply blocks
+ for (int i = 0; i < mt.N9x9; i++) {
+ // B input split across pre-adders
+ int b_start = (9 * i) % mt.b_width;
+ copy_bus(ctx, ci, id_B, b_start, true, preadd9[i], id_B, 0, false, 9);
+ // A input split across MULT9s
+ int a_start = 9 * (i % 2) + 18 * (i / 4);
+ copy_bus(ctx, ci, id_A, a_start, true, mult9[i], id_A, 0, false, 9);
+ // Connect control set signals
+ copy_port(ctx, ci, id_CLK, mult9[i], id_CLK);
+ copy_port(ctx, ci, id_CEA, mult9[i], id_CEA);
+ copy_port(ctx, ci, id_RSTA, mult9[i], id_RSTA);
+ copy_port(ctx, ci, id_CLK, preadd9[i], id_CLK);
+ copy_port(ctx, ci, id_CEB, preadd9[i], id_CEB);
+ copy_port(ctx, ci, id_RSTB, preadd9[i], id_RSTB);
+ // Copy register configuration
+ copy_param(ci, id_REGINPUTA, mult9[i], id_REGBYPSA1);
+ copy_param(ci, id_REGINPUTB, preadd9[i], id_REGBYPSBR0);
+
+ // Connect and configure pre-adder if it isn't bypassed
+ if (mt.has_preadd) {
+ copy_bus(ctx, ci, id_C, 9 * i, true, preadd9[i], id_C, 0, false, 9);
+ if (i == (mt.N9x9 - 1))
+ copy_port(ctx, ci, id_SIGNEDC, preadd9[i], id_C9);
+ copy_param(ci, id_REGINPUTC, preadd9[i], id_REGBYPSBL);
+ copy_port(ctx, ci, id_CEC, preadd9[i], id_CECL);
+ copy_port(ctx, ci, id_RSTC, preadd9[i], id_RSTCL);
+ // Enable preadder
+ preadd9[i]->params[id_BYPASS_PREADD9] = std::string("USED");
+ preadd9[i]->params[id_OPC] = std::string("INPUT_C_AS_PREADDER_OPERAND");
+ if (i > 0)
+ preadd9[i]->params[id_PREADDCAS_EN] = std::string("ENABLED");
+ } else if (mt.has_addsub) {
+ // Connect only for routeability reasons
+ copy_bus(ctx, ci, id_C, 10 * i + ((i >= 4) ? 14 : 0), true, preadd9[i], id_C, 0, false, 10);
+ }
+
+ // Connect up signedness for the most significant nonet
+ if ((b_start + 9) == mt.b_width)
+ copy_port(ctx, ci, mt.has_addsub ? id_SIGNED : id_SIGNEDB, preadd9[i], id_BSIGNED);
+ if ((a_start + 9) == mt.a_width)
+ copy_port(ctx, ci, mt.has_addsub ? id_SIGNED : id_SIGNEDA, mult9[i], id_ASIGNED);
+ }
+
+ bool mult36_used = (mt.a_width >= 36) && (mt.b_width >= 36);
+ // Configure mult18x36s
+ for (int i = 0; i < mt.N18x36; i++) {
+ mult18x36[i]->params[id_MULT36] = mult36_used ? std::string("ENABLED") : std::string("DISABLED");
+ mult18x36[i]->params[id_MULT36X36H] = (i == 1) ? std::string("USED_AS_HIGHER_BIT_GENERATION")
+ : std::string("USED_AS_LOWER_BIT_GENERATION");
+ }
+ // Create final mult36 if needed
+ CellInfo *mult36 = nullptr;
+ if (mult36_used) {
+ mult36 = create_dsp_cell(ci->name, id_MULT36_CORE, preadd9[0], 6, 6);
+ }
+
+ // Configure output registers
+ for (int i = 0; i < Nreg18; i++) {
+ // Output split across reg18s
+ if (!mt.has_addsub)
+ replace_bus(ctx, ci, id_Z, i * 18, true, reg18[i], id_PP, 0, false, 18);
+ // Connect control set signals
+ copy_port(ctx, ci, id_CLK, reg18[i], id_CLK);
+ copy_port(ctx, ci, mt.has_addsub ? id_CEPIPE : id_CEOUT, reg18[i], id_CEP);
+ copy_port(ctx, ci, mt.has_addsub ? id_RSTPIPE : id_RSTOUT, reg18[i], id_RSTP);
+ // Copy register configuration
+ copy_param(ci, mt.has_addsub ? id_REGPIPELINE : id_REGOUTPUT, reg18[i], id_REGBYPS);
+ }
+
+ if (mt.has_addsub) {
+ // Create and configure ACC54s
+ int Nacc54 = mt.c_width / 54;
+ std::vector<CellInfo *> acc54(Nacc54);
+ for (int i = 0; i < Nacc54; i++)
+ acc54[i] = create_dsp_cell(ci->name, id_ACC54_CORE, preadd9[0], (i * 4) + 2, 5);
+ for (int i = 0; i < Nacc54; i++) {
+ // C addsub input
+ copy_bus(ctx, ci, id_C, 54 * i, true, acc54[i], id_CINPUT, 0, false, 54);
+ // Output
+ replace_bus(ctx, ci, id_Z, i * 54, true, acc54[i], id_SUM0, 0, false, 36);
+ replace_bus(ctx, ci, id_Z, i * 54 + 36, true, acc54[i], id_SUM1, 0, false, 18);
+ // Control set
+ copy_port(ctx, ci, id_CLK, acc54[i], id_CLK);
+ copy_port(ctx, ci, id_RSTCTRL, acc54[i], id_RSTCTRL);
+ copy_port(ctx, ci, id_CECTRL, acc54[i], id_CECTRL);
+ copy_port(ctx, ci, id_RSTCIN, acc54[i], id_RSTCIN);
+ copy_port(ctx, ci, id_CECIN, acc54[i], id_CECIN);
+ copy_port(ctx, ci, id_RSTOUT, acc54[i], id_RSTO);
+ copy_port(ctx, ci, id_CEOUT, acc54[i], id_CEO);
+ copy_port(ctx, ci, id_RSTC, acc54[i], id_RSTC);
+ copy_port(ctx, ci, id_CEC, acc54[i], id_CEC);
+ // Add/acc control
+ if (i == 0)
+ copy_port(ctx, ci, id_CIN, acc54[i], id_CIN);
+ else
+ ctx->set_cell_pinmux(acc54[i], id_CIN, PINMUX_1);
+ if (i == (Nacc54 - 1))
+ copy_port(ctx, ci, id_SIGNED, acc54[i], id_SIGNEDI);
+ copy_port(ctx, ci, id_ADDSUB, acc54[i], id_ADDSUB0);
+ copy_port(ctx, ci, id_ADDSUB, acc54[i], id_ADDSUB1);
+ copy_port(ctx, ci, id_LOADC, acc54[i], id_LOAD);
+ // Configuration
+ copy_param(ci, id_REGINPUTC, acc54[i], id_CREGBYPS1);
+ copy_param(ci, id_REGADDSUB, acc54[i], id_ADDSUBSIGNREGBYPS1);
+ copy_param(ci, id_REGADDSUB, acc54[i], id_M9ADDSUBREGBYPS1);
+ copy_param(ci, id_REGLOADC, acc54[i], id_LOADREGBYPS1);
+ copy_param(ci, id_REGLOADC2, acc54[i], id_LOADREGBYPS2);
+ copy_param(ci, id_REGCIN, acc54[i], id_CINREGBYPS1);
+
+ copy_param(ci, id_REGPIPELINE, acc54[i], id_CREGBYPS2);
+ copy_param(ci, id_REGPIPELINE, acc54[i], id_ADDSUBSIGNREGBYPS2);
+ copy_param(ci, id_REGPIPELINE, acc54[i], id_CINREGBYPS2);
+ copy_param(ci, id_REGPIPELINE, acc54[i], id_M9ADDSUBREGBYPS2);
+ copy_param(ci, id_REGOUTPUT, acc54[i], id_OUTREGBYPS);
+
+ if (i == 1) {
+ // Top ACC54 in a 108-bit config
+ acc54[i]->params[id_ACCUMODE] = std::string("MODE6");
+ acc54[i]->params[id_ACC108CASCADE] = std::string("CASCADE2ACCU54TOFORMACCU108");
+ } else if ((i == 0) && (Nacc54 == 2)) {
+ // Bottom ACC54 in a 108-bit config
+ acc54[i]->params[id_ACCUMODE] = std::string("MODE2");
+ }
+ }
+ }
+
+ // Misc finalisation
+ copy_global_dsp_params(ci, preadd9[0]);
+ auto_cascade_group(preadd9[0]);
+ to_remove.push_back(ci);
+ }
+
+ for (auto cell : to_remove) {
+ for (auto port : sorted_ref(cell->ports))
+ disconnect_port(ctx, cell, port.first);
+ ctx->cells.erase(cell->name);
+ }
+ }
+
+ explicit NexusPacker(Context *ctx) : ctx(ctx) {}
+
+ void operator()()
+ {
+ pack_io();
+ pack_dsps();
+ convert_prims();
+ pack_bram();
+ pack_lutram();
+ pack_carries();
+ pack_widefn();
+ pack_ffs();
+ pack_constants();
+ pack_luts();
+ promote_globals();
+ place_globals();
+ }
+};
+
+bool Arch::pack()
+{
+ (NexusPacker(getCtx()))();
+ attrs[id("step")] = std::string("pack");
+ archInfoToAttributes();
+ assignArchInfo();
+ return true;
+}
+
+// -----------------------------------------------------------------------
+
+void Arch::assignArchInfo()
+{
+ for (auto cell : sorted(cells)) {
+ assignCellInfo(cell.second);
+ }
+}
+
+const std::vector<std::string> dsp_bus_prefices = {
+ "M9ADDSUB", "ADDSUB", "SFTCTRL", "DSPIN", "CINPUT", "DSPOUT", "CASCOUT", "CASCIN", "PML72", "PMH72", "SUM1",
+ "SUM0", "BRS1", "BRS2", "BLS1", "BLS2", "BLSO", "BRSO", "PL18", "PH18", "PL36", "PH36",
+ "PL72", "PH72", "P72", "P36", "P18", "AS1", "AS2", "ARL", "ARH", "BRL", "BRH",
+ "AO", "BO", "AB", "AR", "BR", "PM", "PP", "A", "B", "C"};
+
+void Arch::assignCellInfo(CellInfo *cell)
+{
+ cell->tmg_index = -1;
+ if (cell->type == id_OXIDE_COMB) {
+ cell->lutInfo.is_memory = str_or_default(cell->params, id_MODE, "LOGIC") == "DPRAM";
+ cell->lutInfo.is_carry = str_or_default(cell->params, id_MODE, "LOGIC") == "CCU2";
+ cell->lutInfo.mux2_used = port_used(cell, id_OFX);
+ cell->lutInfo.f = get_net_or_empty(cell, id_F);
+ cell->lutInfo.ofx = get_net_or_empty(cell, id_OFX);
+ if (cell->lutInfo.is_carry) {
+ cell->tmg_portmap[id_A] = id_A0;
+ cell->tmg_portmap[id_B] = id_B0;
+ cell->tmg_portmap[id_C] = id_C0;
+ cell->tmg_portmap[id_D] = id_D0;
+ cell->tmg_portmap[id_F] = id_F0;
+ cell->tmg_index = get_cell_timing_idx(id_OXIDE_COMB, id_CCU2);
+ } else if (cell->lutInfo.ofx != nullptr) {
+ cell->tmg_index = get_cell_timing_idx(id_OXIDE_COMB, id_WIDEFN9);
+ } else if (cell->lutInfo.is_memory) {
+ cell->tmg_index = get_cell_timing_idx(id_OXIDE_COMB, id_DPRAM);
+ } else {
+ cell->tmg_index = get_cell_timing_idx(id_OXIDE_COMB, id_LUT4);
+ }
+ } else if (cell->type == id_OXIDE_FF) {
+ cell->ffInfo.ctrlset.async = str_or_default(cell->params, id_SRMODE, "LSR_OVER_CE") == "ASYNC";
+ cell->ffInfo.ctrlset.regddr_en = is_enabled(cell, id_REGDDR);
+ cell->ffInfo.ctrlset.gsr_en = is_enabled(cell, id_GSR);
+ cell->ffInfo.ctrlset.clkmux = id(str_or_default(cell->params, id_CLKMUX, "CLK")).index;
+ cell->ffInfo.ctrlset.cemux = id(str_or_default(cell->params, id_CEMUX, "CE")).index;
+ cell->ffInfo.ctrlset.lsrmux = id(str_or_default(cell->params, id_LSRMUX, "LSR")).index;
+ cell->ffInfo.ctrlset.clk = get_net_or_empty(cell, id_CLK);
+ cell->ffInfo.ctrlset.ce = get_net_or_empty(cell, id_CE);
+ cell->ffInfo.ctrlset.lsr = get_net_or_empty(cell, id_LSR);
+ cell->ffInfo.di = get_net_or_empty(cell, id_DI);
+ cell->ffInfo.m = get_net_or_empty(cell, id_M);
+ cell->tmg_index = get_cell_timing_idx(id_OXIDE_FF, id("PPP:SYNC"));
+ } else if (cell->type == id_RAMW) {
+ cell->ffInfo.ctrlset.async = true;
+ cell->ffInfo.ctrlset.regddr_en = false;
+ cell->ffInfo.ctrlset.gsr_en = false;
+ cell->ffInfo.ctrlset.clkmux = id(str_or_default(cell->params, id_CLKMUX, "CLK")).index;
+ cell->ffInfo.ctrlset.cemux = ID_CE;
+ cell->ffInfo.ctrlset.lsrmux = ID_INV;
+ cell->ffInfo.ctrlset.clk = get_net_or_empty(cell, id_CLK);
+ cell->ffInfo.ctrlset.ce = nullptr;
+ cell->ffInfo.ctrlset.lsr = get_net_or_empty(cell, id_LSR);
+ cell->ffInfo.di = nullptr;
+ cell->ffInfo.m = nullptr;
+ cell->tmg_index = get_cell_timing_idx(id_RAMW);
+ } else if (cell->type == id_OXIDE_EBR) {
+ // Strip off bus indices to get the timing ports
+ // as timing is generally word-wide
+ for (const auto &port : cell->ports) {
+ const std::string &name = port.first.str(this);
+ size_t idx_end = name.find_last_not_of("0123456789");
+ std::string base = name.substr(0, idx_end + 1);
+ if (base == "ADA" || base == "ADB") {
+ // [4:0] and [13:5] have different timing
+ int idx = std::stoi(name.substr(idx_end + 1));
+ cell->tmg_portmap[port.first] = id(base + ((idx >= 5) ? "_13_5" : "_4_0"));
+ } else {
+ // Just strip off bus index
+ cell->tmg_portmap[port.first] = id(base);
+ }
+ }
+
+ cell->tmg_index = get_cell_timing_idx(id(str_or_default(cell->params, id_MODE, "DP16K") + "_MODE"));
+ NPNR_ASSERT(cell->tmg_index != -1);
+ } else if (is_dsp_cell(cell)) {
+ // Strip off bus indices to get the timing ports
+ // as timing is generally word-wide
+ for (const auto &port : cell->ports) {
+ const std::string &name = port.first.str(this);
+ size_t idx_end = name.find_last_not_of("0123456789");
+ if (idx_end == std::string::npos)
+ continue;
+ for (const auto &p : dsp_bus_prefices) {
+ if (name.size() > p.size() && name.substr(0, p.size()) == p && idx_end <= p.size()) {
+ cell->tmg_portmap[port.first] = id(p);
+ break;
+ }
+ }
+ }
+ // Build up the configuration string
+ std::set<std::string> config;
+ for (const auto &param : cell->params) {
+ const std::string &name = param.first.str(this);
+ size_t byp_pos = name.find("REGBYPS");
+ if (byp_pos != std::string::npos && param.second.str == "REGISTER") {
+ // Register enabled
+ config.insert(name.substr(0, byp_pos + 3) + name.substr(byp_pos + 7));
+ } else if (param.first == id_BYPASS_PREADD9 && param.second.str == "BYPASS") {
+ // PREADD9 bypass
+ config.insert("BYPASS");
+ }
+ }
+ std::string config_str;
+ for (const auto &cfg : config) {
+ if (!config_str.empty())
+ config_str += ',';
+ config_str += cfg;
+ }
+ cell->tmg_index = get_cell_timing_idx(cell->type, id(config_str));
+ if (cell->tmg_index == -1) {
+ log_warning("Unsupported timing config '%s' on %s cell '%s', falling back to default.\n",
+ config_str.c_str(), nameOf(cell->type), nameOf(cell));
+ cell->tmg_index = get_cell_timing_idx(cell->type);
+ }
+ }
+}
+
+NEXTPNR_NAMESPACE_END
diff --git a/nexus/pdc.cc b/nexus/pdc.cc
new file mode 100644
index 00000000..3efab69a
--- /dev/null
+++ b/nexus/pdc.cc
@@ -0,0 +1,352 @@
+/*
+ * 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 "log.h"
+#include "nextpnr.h"
+
+#include <iterator>
+
+NEXTPNR_NAMESPACE_BEGIN
+
+struct TCLEntity
+{
+ enum EntityType
+ {
+ ENTITY_CELL,
+ ENTITY_PORT,
+ ENTITY_NET,
+ } type;
+ IdString name;
+
+ TCLEntity(EntityType type, IdString name) : type(type), name(name) {}
+
+ const std::string &to_string(Context *ctx) { return name.str(ctx); }
+
+ CellInfo *get_cell(Context *ctx)
+ {
+ if (type != ENTITY_CELL)
+ return nullptr;
+ return ctx->cells.at(name).get();
+ }
+
+ PortInfo *get_port(Context *ctx)
+ {
+ if (type != ENTITY_PORT)
+ return nullptr;
+ return &ctx->ports.at(name);
+ }
+
+ NetInfo *get_net(Context *ctx)
+ {
+ if (type != ENTITY_NET)
+ return nullptr;
+ return ctx->nets.at(name).get();
+ }
+};
+
+struct TCLValue
+{
+ TCLValue(const std::string &s) : is_string(true), str(s){};
+ TCLValue(const std::vector<TCLEntity> &l) : is_string(false), list(l){};
+
+ bool is_string;
+ std::string str; // simple string value
+ std::vector<TCLEntity> list; // list of entities
+};
+
+struct PDCParser
+{
+ std::string buf;
+ int pos = 0;
+ int lineno = 1;
+ Context *ctx;
+
+ PDCParser(const std::string &buf, Context *ctx) : buf(buf), ctx(ctx){};
+
+ inline bool eof() const { return pos == int(buf.size()); }
+
+ inline char peek() const { return buf.at(pos); }
+
+ inline char get()
+ {
+ char c = buf.at(pos++);
+ if (c == '\n')
+ ++lineno;
+ return c;
+ }
+
+ std::string get(int n)
+ {
+ std::string s = buf.substr(pos, n);
+ pos += n;
+ return s;
+ }
+
+ // If next char matches c, take it from the stream and return true
+ bool check_get(char c)
+ {
+ if (peek() == c) {
+ get();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ // If next char matches any in chars, take it from the stream and return true
+ bool check_get_any(const std::string &chrs)
+ {
+ char c = peek();
+ if (chrs.find(c) != std::string::npos) {
+ get();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ inline void skip_blank(bool nl = false)
+ {
+ while (!eof() && check_get_any(nl ? " \t\n\r" : " \t"))
+ ;
+ }
+
+ // Return true if end of line (or file)
+ inline bool skip_check_eol()
+ {
+ skip_blank(false);
+ if (eof())
+ return true;
+ char c = peek();
+ // Comments count as end of line
+ if (c == '#') {
+ get();
+ while (!eof() && peek() != '\n' && peek() != '\r')
+ get();
+ return true;
+ }
+ if (c == ';') {
+ // Forced end of line
+ get();
+ return true;
+ }
+ return (c == '\n' || c == '\r');
+ }
+
+ inline std::string get_str()
+ {
+ std::string s;
+ skip_blank(false);
+ if (eof())
+ return "";
+
+ bool in_quotes = false, in_braces = false, escaped = false;
+
+ char c = get();
+
+ if (c == '"')
+ in_quotes = true;
+ else if (c == '{')
+ in_braces = true;
+ else
+ s += c;
+
+ while (true) {
+ char c = peek();
+ if (!in_quotes && !in_braces && !escaped && (std::isblank(c) || c == ']')) {
+ break;
+ }
+ get();
+ if (escaped) {
+ s += c;
+ escaped = false;
+ } else if ((in_quotes && c == '"') || (in_braces && c == '}')) {
+ break;
+ } else if (c == '\\') {
+ escaped = true;
+ } else {
+ s += c;
+ }
+ }
+
+ return s;
+ }
+
+ TCLValue evaluate(const std::vector<TCLValue> &arguments)
+ {
+ NPNR_ASSERT(!arguments.empty());
+ auto &arg0 = arguments.at(0);
+ NPNR_ASSERT(arg0.is_string);
+ const std::string &cmd = arg0.str;
+ if (cmd == "get_ports")
+ return cmd_get_ports(arguments);
+ else if (cmd == "get_cells")
+ return cmd_get_cells(arguments);
+ else if (cmd == "ldc_set_location")
+ return cmd_ldc_set_location(arguments);
+ else if (cmd == "ldc_set_port")
+ return cmd_ldc_set_port(arguments);
+ else if (cmd == "ldc_set_sysconfig" || cmd == "get_nets" || cmd == "create_clock") {
+ log_warning("%s is not yet supported!\n", cmd.c_str());
+ return TCLValue("");
+ } else
+ log_error("Unsupported PDC command '%s'\n", cmd.c_str());
+ }
+
+ std::vector<TCLValue> get_arguments()
+ {
+ std::vector<TCLValue> args;
+ while (!skip_check_eol()) {
+ if (check_get('[')) {
+ // Start of a sub-expression
+ auto result = evaluate(get_arguments());
+ NPNR_ASSERT(check_get(']'));
+ args.push_back(result);
+ } else if (peek() == ']') {
+ break;
+ } else {
+ args.push_back(get_str());
+ }
+ }
+ skip_blank(true);
+ return args;
+ }
+
+ TCLValue cmd_get_ports(const std::vector<TCLValue> &arguments)
+ {
+ std::vector<TCLEntity> ports;
+ for (int i = 1; i < int(arguments.size()); i++) {
+ auto &arg = arguments.at(i);
+ if (!arg.is_string)
+ log_error("get_ports expected string arguments (line %d)\n", lineno);
+ std::string s = arg.str;
+ if (s.at(0) == '-')
+ log_error("unsupported argument '%s' to get_ports (line %d)\n", s.c_str(), lineno);
+ IdString id = ctx->id(s);
+ if (ctx->ports.count(id))
+ ports.emplace_back(TCLEntity::ENTITY_PORT, id);
+ }
+ return ports;
+ }
+
+ TCLValue cmd_get_cells(const std::vector<TCLValue> &arguments)
+ {
+ std::vector<TCLEntity> cells;
+ for (int i = 1; i < int(arguments.size()); i++) {
+ auto &arg = arguments.at(i);
+ if (!arg.is_string)
+ log_error("get_cells expected string arguments (line %d)\n", lineno);
+ std::string s = arg.str;
+ if (s.at(0) == '-')
+ log_error("unsupported argument '%s' to get_cells (line %d)\n", s.c_str(), lineno);
+ IdString id = ctx->id(s);
+ if (ctx->cells.count(id))
+ cells.emplace_back(TCLEntity::ENTITY_CELL, id);
+ }
+ return cells;
+ }
+
+ TCLValue cmd_ldc_set_location(const std::vector<TCLValue> &arguments)
+ {
+ std::string site;
+
+ for (int i = 1; i < int(arguments.size()); i++) {
+ auto &arg = arguments.at(i);
+ if (arg.is_string) {
+ std::string s = arg.str;
+ if (s == "-site") {
+ i++;
+ auto &val = arguments.at(i);
+ if (!val.is_string)
+ log_error("expecting string argument to -site (line %d)\n", lineno);
+ site = val.str;
+ }
+ } else {
+ if (site.empty())
+ log_error("expecting -site before list of objects (line %d)\n", lineno);
+ for (const auto &ety : arg.list) {
+ if (ety.type == TCLEntity::ENTITY_PORT)
+ ctx->io_attr[ety.name][id_LOC] = site;
+ else if (ety.type == TCLEntity::ENTITY_CELL)
+ ctx->cells[ety.name]->attrs[id_LOC] = site;
+ else
+ log_error("ldc_set_location applies only to cells or IO ports (line %d)\n", lineno);
+ }
+ }
+ }
+ return std::string{};
+ }
+
+ TCLValue cmd_ldc_set_port(const std::vector<TCLValue> &arguments)
+ {
+ std::unordered_map<IdString, Property> args;
+ for (int i = 1; i < int(arguments.size()); i++) {
+ auto &arg = arguments.at(i);
+ if (arg.is_string) {
+ std::string s = arg.str;
+ if (s == "-iobuf") {
+ i++;
+ auto &val = arguments.at(i);
+ if (!val.is_string)
+ log_error("expecting string argument to -iobuf (line %d)\n", lineno);
+ std::stringstream ss(val.str);
+ std::string kv;
+ while (ss >> kv) {
+ auto eqp = kv.find('=');
+ if (eqp == std::string::npos)
+ log_error("expected key-value pair separated by '=' (line %d)", lineno);
+ std::string k = kv.substr(0, eqp), v = kv.substr(eqp + 1);
+ args[ctx->id(k)] = v;
+ }
+ } else {
+ log_error("unexpected argument '%s' to ldc_set_port (line %d)\n", s.c_str(), lineno);
+ }
+ } else {
+ for (const auto &ety : arg.list) {
+ if (ety.type == TCLEntity::ENTITY_PORT)
+ for (const auto &kv : args)
+ ctx->io_attr[ety.name][kv.first] = kv.second;
+ else
+ log_error("ldc_set_port applies only to IO ports (line %d)\n", lineno);
+ }
+ }
+ }
+ return std::string{};
+ }
+
+ void operator()()
+ {
+ while (!eof()) {
+ skip_blank(true);
+ auto args = get_arguments();
+ if (args.empty())
+ continue;
+ evaluate(args);
+ }
+ }
+};
+
+void Arch::read_pdc(std::istream &in)
+{
+ std::string buf(std::istreambuf_iterator<char>(in), {});
+ PDCParser(buf, getCtx())();
+}
+
+NEXTPNR_NAMESPACE_END
diff --git a/nexus/pins.cc b/nexus/pins.cc
new file mode 100644
index 00000000..0587c032
--- /dev/null
+++ b/nexus/pins.cc
@@ -0,0 +1,180 @@
+/*
+ * 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 "nextpnr.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+namespace {
+
+static const std::unordered_map<IdString, Arch::CellPinsData> base_cell_pin_data = {
+ {id_OXIDE_COMB,
+ {
+ {id_WCK, PINSTYLE_DEDI},
+ {id_WRE, PINSTYLE_DEDI},
+
+ {id_FCI, PINSTYLE_DEDI},
+ {id_F1, PINSTYLE_DEDI},
+ {id_WAD0, PINSTYLE_DEDI},
+ {id_WAD1, PINSTYLE_DEDI},
+ {id_WAD2, PINSTYLE_DEDI},
+ {id_WAD3, PINSTYLE_DEDI},
+ {id_WDI, PINSTYLE_DEDI},
+
+ {{}, PINSTYLE_PU},
+ }},
+ {id_OXIDE_FF,
+ {
+ {id_CLK, PINSTYLE_CLK},
+ {id_LSR, PINSTYLE_LSR},
+ {id_CE, PINSTYLE_CE},
+ {{}, PINSTYLE_DEDI},
+ }},
+ {id_RAMW,
+ {
+ {id_CLK, PINSTYLE_CLK},
+ {{}, PINSTYLE_DEDI},
+ }},
+ {id_SEIO18_CORE,
+ {
+ {id_T, PINSTYLE_T},
+ {id_B, PINSTYLE_DEDI},
+ {{}, PINSTYLE_PU},
+ }},
+ {id_DIFFIO18_CORE,
+ {
+ {id_T, PINSTYLE_T},
+ {id_B, PINSTYLE_DEDI},
+ {{}, PINSTYLE_PU},
+ }},
+ {id_SEIO33_CORE,
+ {
+ {id_T, PINSTYLE_T},
+ {id_B, PINSTYLE_DEDI},
+ {{}, PINSTYLE_PU},
+ }},
+ {id_OXIDE_EBR,
+ {{id_CLKA, PINSTYLE_CLK}, {id_CLKB, PINSTYLE_CLK}, {id_CEA, PINSTYLE_CE}, {id_CEB, PINSTYLE_CE},
+ {id_CSA0, PINSTYLE_PU}, {id_CSA1, PINSTYLE_PU}, {id_CSA2, PINSTYLE_PU}, {id_CSB0, PINSTYLE_PU},
+ {id_CSB1, PINSTYLE_PU}, {id_CSB2, PINSTYLE_PU}, {id_ADA0, PINSTYLE_ADLSB}, {id_ADA1, PINSTYLE_ADLSB},
+ {id_ADA2, PINSTYLE_ADLSB}, {id_ADA2, PINSTYLE_ADLSB}, {id_ADA3, PINSTYLE_ADLSB}, {id_ADB0, PINSTYLE_ADLSB},
+ {id_ADB1, PINSTYLE_ADLSB}, {id_WEA, PINSTYLE_INV_PD}, {id_WEB, PINSTYLE_INV_PD}, {id_RSTA, PINSTYLE_INV_PD},
+ {id_RSTB, PINSTYLE_INV_PD}, {{id_DWS0}, PINSTYLE_PU}, {{id_DWS1}, PINSTYLE_PU}, {{id_DWS2}, PINSTYLE_PU},
+ {{id_DWS3}, PINSTYLE_PU}, {{id_DWS4}, PINSTYLE_PU}, {{}, PINSTYLE_CIB}}},
+ {id_OSC_CORE,
+ {
+ {id_HFOUTEN, PINSTYLE_PU},
+ {{}, PINSTYLE_CIB},
+ }},
+ {id_PREADD9_CORE,
+ {
+ {id_CLK, PINSTYLE_CLK}, {id_RSTCL, PINSTYLE_LSR}, {id_RSTB, PINSTYLE_LSR}, {id_CECL, PINSTYLE_CE},
+ {id_CEB, PINSTYLE_CE},
+
+ {id_B0, PINSTYLE_CIB}, {id_B1, PINSTYLE_CIB}, {id_B2, PINSTYLE_CIB}, {id_B3, PINSTYLE_CIB},
+ {id_B4, PINSTYLE_CIB}, {id_B5, PINSTYLE_CIB}, {id_B6, PINSTYLE_CIB}, {id_B7, PINSTYLE_CIB},
+ {id_B8, PINSTYLE_CIB}, {id_BSIGNED, PINSTYLE_CIB},
+
+ {id_C0, PINSTYLE_CIB}, {id_C1, PINSTYLE_CIB}, {id_C2, PINSTYLE_CIB}, {id_C3, PINSTYLE_CIB},
+ {id_C4, PINSTYLE_CIB}, {id_C5, PINSTYLE_CIB}, {id_C6, PINSTYLE_CIB}, {id_C7, PINSTYLE_CIB},
+ {id_C8, PINSTYLE_CIB}, {id_C9, PINSTYLE_CIB},
+
+ {{}, PINSTYLE_DEDI},
+ }},
+ {id_MULT9_CORE,
+ {
+ {id_CLK, PINSTYLE_CLK},
+ {id_RSTA, PINSTYLE_LSR},
+ {id_RSTP, PINSTYLE_LSR},
+ {id_CEA, PINSTYLE_CE},
+ {id_CEP, PINSTYLE_CE},
+
+ {id_A0, PINSTYLE_CIB},
+ {id_A1, PINSTYLE_CIB},
+ {id_A2, PINSTYLE_CIB},
+ {id_A3, PINSTYLE_CIB},
+ {id_A4, PINSTYLE_CIB},
+ {id_A5, PINSTYLE_CIB},
+ {id_A6, PINSTYLE_CIB},
+ {id_A7, PINSTYLE_CIB},
+ {id_A8, PINSTYLE_CIB},
+ {id_ASIGNED, PINSTYLE_CIB},
+
+ {{}, PINSTYLE_DEDI},
+ }},
+ {id_REG18_CORE,
+ {
+ {id_CLK, PINSTYLE_CLK},
+ {id_RSTP, PINSTYLE_LSR},
+ {id_CEP, PINSTYLE_CE},
+ {{}, PINSTYLE_DEDI},
+ }},
+ {id_MULT18_CORE,
+ {
+ {id_SFTCTRL0, PINSTYLE_PU},
+ {id_SFTCTRL1, PINSTYLE_PU},
+ {id_SFTCTRL2, PINSTYLE_PU},
+ {id_SFTCTRL3, PINSTYLE_PU},
+ {id_ROUNDEN, PINSTYLE_CIB},
+ {{}, PINSTYLE_DEDI},
+ }},
+ {id_MULT18X36_CORE,
+ {
+ {id_SFTCTRL0, PINSTYLE_PU},
+ {id_SFTCTRL1, PINSTYLE_PU},
+ {id_SFTCTRL2, PINSTYLE_PU},
+ {id_SFTCTRL3, PINSTYLE_PU},
+ {id_ROUNDEN, PINSTYLE_CIB},
+ {{}, PINSTYLE_DEDI},
+ }},
+ {id_ACC54_CORE,
+ {
+ {id_CLK, PINSTYLE_CLK}, {id_RSTC, PINSTYLE_LSR}, {id_CEC, PINSTYLE_CE},
+ {id_SIGNEDI, PINSTYLE_CIB}, {id_RSTCTRL, PINSTYLE_LSR}, {id_CECTRL, PINSTYLE_CE},
+ {id_RSTCIN, PINSTYLE_LSR}, {id_CECIN, PINSTYLE_CE}, {id_LOAD, PINSTYLE_CIB},
+ {id_ADDSUB0, PINSTYLE_CIB}, {id_ADDSUB1, PINSTYLE_CIB}, {id_M9ADDSUB0, PINSTYLE_PU},
+ {id_M9ADDSUB1, PINSTYLE_PU}, {id_ROUNDEN, PINSTYLE_CIB}, {id_RSTO, PINSTYLE_LSR},
+ {id_CEO, PINSTYLE_CE}, {id_CIN, PINSTYLE_CIB}, {id_SFTCTRL0, PINSTYLE_PU},
+ {id_SFTCTRL1, PINSTYLE_PU}, {id_SFTCTRL2, PINSTYLE_PU}, {id_SFTCTRL3, PINSTYLE_PU},
+ {{}, PINSTYLE_DEDI},
+ }}};
+} // namespace
+
+void Arch::init_cell_pin_data() { cell_pins_db = base_cell_pin_data; }
+
+CellPinStyle Arch::get_cell_pin_style(const CellInfo *cell, IdString port) const
+{
+ // Look up the pin style in the cell database
+ auto fnd_cell = cell_pins_db.find(cell->type);
+ if (fnd_cell == cell_pins_db.end())
+ return PINSTYLE_NONE;
+ auto fnd_port = fnd_cell->second.find(port);
+ if (fnd_port != fnd_cell->second.end())
+ return fnd_port->second;
+ // If there isn't an exact port match, then the empty IdString
+ // represents a wildcard default match
+ auto fnd_default = fnd_cell->second.find({});
+ if (fnd_default != fnd_cell->second.end())
+ return fnd_default->second;
+
+ return PINSTYLE_NONE;
+}
+
+NEXTPNR_NAMESPACE_END
diff --git a/nexus/post_place.cc b/nexus/post_place.cc
new file mode 100644
index 00000000..65676188
--- /dev/null
+++ b/nexus/post_place.cc
@@ -0,0 +1,161 @@
+/*
+ * 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 "design_utils.h"
+#include "log.h"
+#include "nextpnr.h"
+#include "timing.h"
+#include "util.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+struct NexusPostPlaceOpt
+{
+ Context *ctx;
+ NetCriticalityMap net_crit;
+
+ NexusPostPlaceOpt(Context *ctx) : ctx(ctx){};
+
+ inline bool is_constrained(CellInfo *cell)
+ {
+ return cell->constr_parent != nullptr || !cell->constr_children.empty();
+ }
+
+ bool swap_cell_placement(CellInfo *cell, BelId new_bel)
+ {
+ if (is_constrained(cell))
+ return false;
+ BelId oldBel = cell->bel;
+ CellInfo *other_cell = ctx->getBoundBelCell(new_bel);
+ if (other_cell != nullptr && (is_constrained(other_cell) || other_cell->belStrength > STRENGTH_WEAK)) {
+ return false;
+ }
+
+ ctx->unbindBel(oldBel);
+ if (other_cell != nullptr) {
+ ctx->unbindBel(new_bel);
+ }
+
+ ctx->bindBel(new_bel, cell, STRENGTH_WEAK);
+
+ if (other_cell != nullptr) {
+ ctx->bindBel(oldBel, other_cell, STRENGTH_WEAK);
+ }
+
+ if (!ctx->isBelLocationValid(new_bel) || ((other_cell != nullptr && !ctx->isBelLocationValid(oldBel)))) {
+ // New placement is not legal.
+ ctx->unbindBel(new_bel);
+ if (other_cell != nullptr)
+ ctx->unbindBel(oldBel);
+
+ // Revert.
+ ctx->bindBel(oldBel, cell, STRENGTH_WEAK);
+ if (other_cell != nullptr)
+ ctx->bindBel(new_bel, other_cell, STRENGTH_WEAK);
+ return false;
+ }
+
+ return true;
+ }
+
+ int get_distance(BelId a, BelId b)
+ {
+ Loc la = ctx->getBelLocation(a);
+ Loc lb = ctx->getBelLocation(b);
+ return std::abs(la.x - lb.x) + std::abs(la.y - lb.y);
+ }
+
+ BelId lut_to_ff(BelId lut)
+ {
+ Loc ff_loc = ctx->getBelLocation(lut);
+ ff_loc.z += (Arch::BEL_FF0 - Arch::BEL_LUT0);
+ return ctx->getBelByLocation(ff_loc);
+ }
+
+ void opt_lutffs()
+ {
+ int moves_made = 0;
+ for (auto cell : sorted(ctx->cells)) {
+ // Search for FF cells
+ CellInfo *ff = cell.second;
+ if (ff->type != id_OXIDE_FF)
+ continue;
+ // Check M ('fabric') input net
+ NetInfo *m = get_net_or_empty(ff, id_M);
+ if (m == nullptr)
+ continue;
+
+ // Ignore FFs that need both DI and M (PRLD mode)
+ if (get_net_or_empty(ff, id_DI) != nullptr)
+ continue;
+
+ const auto &drv = m->driver;
+ // Skip if driver isn't a LUT/MUX2
+ if (drv.cell == nullptr || drv.cell->type != id_OXIDE_COMB || (drv.port != id_F && drv.port != id_OFX))
+ continue;
+ CellInfo *lut = drv.cell;
+ // Check distance to move isn't too far
+ if (get_distance(ff->bel, lut->bel) > lut_ff_radius)
+ continue;
+ // Find the bel we plan to move into
+ BelId dest_ff = lut_to_ff(lut->bel);
+ NPNR_ASSERT(dest_ff != BelId());
+ NPNR_ASSERT(ctx->getBelType(dest_ff) == id_OXIDE_FF);
+ // Ended up in the ideal location by chance
+ if (dest_ff != ff->bel) {
+ // If dest_ff is already placed *and* using direct 'DI' input, don't touch it
+ CellInfo *dest_ff_cell = ctx->getBoundBelCell(dest_ff);
+ if (dest_ff_cell != nullptr && get_net_or_empty(dest_ff_cell, id_DI) != nullptr)
+ continue;
+ // Attempt the swap
+ bool swap_result = swap_cell_placement(ff, dest_ff);
+ if (!swap_result)
+ continue;
+ }
+ // Use direct interconnect
+ rename_port(ctx, ff, id_M, id_DI);
+ ff->params[id_SEL] = std::string("DL");
+ ++moves_made;
+ continue;
+ }
+ log_info(" created %d direct LUT-FF pairs\n", moves_made);
+ }
+
+ void operator()()
+ {
+ get_criticalities(ctx, &net_crit);
+ opt_lutffs();
+ }
+
+ // Configuration
+ const int lut_ff_radius = 2;
+ const int lut_lut_radius = 1;
+ const float lut_lut_crit = 0.85;
+};
+
+void Arch::post_place_opt()
+{
+ if (bool_or_default(settings, id("no_post_place_opt")))
+ return;
+ log_info("Running post-place optimisations...\n");
+ NexusPostPlaceOpt opt(getCtx());
+ opt();
+}
+
+NEXTPNR_NAMESPACE_END