aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Shah <dave@ds0.me>2021-02-05 19:15:29 +0000
committerGitHub <noreply@github.com>2021-02-05 19:15:29 +0000
commit8b4163b77c667c4f2a29e48adab96abc2a83b03d (patch)
treef0ebf104fd3b9a5f1752698b3df4e5c2f2af3304
parentb0f9b7834e4cb035d1fd60f0fa1948c0fdfa233c (diff)
parenta0ee42833b774483f9b2fc35109f7ec948dbdc9b (diff)
downloadnextpnr-8b4163b77c667c4f2a29e48adab96abc2a83b03d.tar.gz
nextpnr-8b4163b77c667c4f2a29e48adab96abc2a83b03d.tar.bz2
nextpnr-8b4163b77c667c4f2a29e48adab96abc2a83b03d.zip
Merge pull request #567 from litghost/initial_fpga_interchange
Initial FPGA interchange arch
-rw-r--r--.gitignore1
-rw-r--r--CMakeLists.txt4
-rw-r--r--common/relptr.h1
-rw-r--r--fpga_interchange/README.md162
-rw-r--r--fpga_interchange/arch.cc578
-rw-r--r--fpga_interchange/arch.h1277
-rw-r--r--fpga_interchange/arch_pybindings.cc77
-rw-r--r--fpga_interchange/arch_pybindings.h110
-rw-r--r--fpga_interchange/archdefs.h192
-rw-r--r--fpga_interchange/family.cmake0
-rw-r--r--fpga_interchange/main.cc84
-rw-r--r--gui/fpga_interchange/family.cmake0
-rw-r--r--gui/fpga_interchange/mainwindow.cc50
-rw-r--r--gui/fpga_interchange/mainwindow.h46
-rw-r--r--gui/fpga_interchange/nextpnr.qrc2
15 files changed, 2582 insertions, 2 deletions
diff --git a/.gitignore b/.gitignore
index 96e6e3cd..40c27b4c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,7 @@
/nextpnr-ice40*
/nextpnr-ecp5*
/nextpnr-nexus*
+/nextpnr-fpga_interchange*
cmake-build-*/
Makefile
cmake_install.cmake
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b6fddcf1..1bfef987 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -66,9 +66,9 @@ endif()
set(PROGRAM_PREFIX "" CACHE STRING "Name prefix for executables")
# List of families to build
-set(FAMILIES generic ice40 ecp5 nexus gowin)
+set(FAMILIES generic ice40 ecp5 nexus gowin fpga_interchange)
set(STABLE_FAMILIES generic ice40 ecp5)
-set(EXPERIMENTAL_FAMILIES nexus gowin)
+set(EXPERIMENTAL_FAMILIES nexus gowin fpga_interchange)
set(ARCH "" CACHE STRING "Architecture family for nextpnr build")
set_property(CACHE ARCH PROPERTY STRINGS ${FAMILIES})
diff --git a/common/relptr.h b/common/relptr.h
index 035d61fb..d45912ab 100644
--- a/common/relptr.h
+++ b/common/relptr.h
@@ -32,6 +32,7 @@ NPNR_PACKED_STRUCT(template <typename T> struct RelSlice {
const T *end() const { return get() + length; }
const size_t size() const { return length; }
+ const ptrdiff_t ssize() const { return length; }
const T &operator*() const { return *(get()); }
diff --git a/fpga_interchange/README.md b/fpga_interchange/README.md
new file mode 100644
index 00000000..d0c4f2bf
--- /dev/null
+++ b/fpga_interchange/README.md
@@ -0,0 +1,162 @@
+## FPGA interchange nextpnr architecture
+
+This nextpnr architecture is a meta architecture that in theory will implement
+any architecture that emits a complete FPGA interchange device database.
+
+### FPGA interchange
+
+The FPGA interchange is a set of file formats intended to describe any modern
+island based FPGA. It consists of three primary file formats:
+
+ - Device database
+ - This is a description of a particular FPGA fabric. This description
+ includes placement locations, placement constraints and a complete
+ description of the routing fabric.
+ - This file will also include timing information once added.
+
+ - Logical netlist
+ - This is the output of a synthesis tool. This is equivalent to the
+ Yosys JSON format, EDIF, or eblif.
+ - As part of future nextpnr development, a frontend will be added that
+ takes this format as input.
+
+ - Physical netlist
+ - This is the output of a place and route tool. It can describe a clustered
+ design, a partially or fully placed design, and a partially or fully
+ routed design.
+
+### Current status
+
+This architecture implementation can be compiled in conjunction with a FPGA
+interchange device database, and the outputs from
+`fpga_interchange.nextpnr_emit`, which is part of the
+[python-fpga-interchange](https://github.com/SymbiFlow/python-fpga-interchange/)
+library.
+
+The current implementation is missing essential features for place and route.
+As these features are added, this implementation will become more useful.
+
+ - [ ] Placement constraints are unimplemented, meaning invalid or unroutable
+ designs can be generated from the placer.
+ - [ ] Logical netlist macro expansion is not implemented, meaning that any
+ macro primitives are unplaceable. Common macro primitives examples are
+ differential IO buffers (IBUFDS) and some LUT RAM (e.g. RAM64X1D).
+ - [ ] Cell -> BEL pin mapping is not in place, meaning any primitives that
+ have different BEL pins with respect to their cell pins will not be
+ routable.
+ - [ ] Nextpnr only allows for cell -> BEL pin maps that are 1 to 1. The
+ FPGA interchange accommodates cell -> BEL pin maps that include 1 to
+ many relationships for sinks. A common primitives that uses 1 to many
+ maps are the RAMB18E1.
+ - [ ] The router lookahead is missing, meaning that router runtime
+ performance will be terrible.
+ - [ ] Physical netlist backend is missing, so even if
+ `nextpnr-fpga_interchange` completes successfully, there is no way to
+ generate output that can be consumed by downstream tools.
+ - [ ] XDC parsing and port constraints are unimplemented, so IO pins cannot
+ be fixed. The chipdb BBA output is also missing package pin data, so
+ only site constraints are currently possible. Eventually the chipdb BBA
+ should also include package pin data to allow for ports to be bound to
+ package pins.
+ - [ ] The routing graph that is currently emitted does not have ground and
+ VCC networks, so all signals must currently be tied to an IO signal.
+ Site pins being tied to constants also needs handling so that site
+ local inverters are used rather than routing signals suboptimally.
+ - [ ] Pseudo pips (e.g. pips that consume BELs and or site resources) should
+ block their respective resources. This effects designs that have some
+ routing in place before placement.
+ - [ ] Pseudo site pips (e.g. site pips that route through BELs) should block
+ their respective resources. Without this, using some pseudo site pips
+ could result in invalid placements.
+ - [ ] Timing information is missing from the FPGA interchange device
+ database, so it is also currently missing from the FPGA interchange
+ architecture. Once timing information is added to the device database
+ schema, it needs to be added to the architecture.
+
+#### FPGA interchange fabrics
+
+Currently only Xilinx 7-series, UltraScale and UltraScale+ fabrics have a
+device database generator, via [RapidWright](https://github.com/Xilinx/RapidWright).
+
+##### Artix 35T example
+
+Download RapidWright and generate the device database.
+```
+# FIXME: Use main branch once interchange branch is merged.
+git clone -b interchange https://github.com/Xilinx/RapidWright.git
+cd RapidWright
+make update_jars
+
+# FIXME: Current RapidWright jars generate database with duplicate PIPs
+# https://github.com/Xilinx/RapidWright/issues/127
+# Remove this wget once the latest RapidWright JAR is published.
+wget https://github.com/Xilinx/RapidWright/releases/download/v2020.2.1-beta/rapidwright-api-lib-2020.2.1_update1.jar
+mv rapidwright-api-lib-2020.2.1_update1.jar jars/rapidwright-api-lib-2020.2.0.jar
+
+./scripts/invoke_rapidwright.sh com.xilinx.rapidwright.interchange.DeviceResourcesExample xc7a35tcpg236-1
+export RAPIDWRIGHT_PATH=$(pwd)
+export INTERCHANGE_DIR=$(pwd)/interchange
+```
+
+Install python FPGA interchange library.
+```
+git clone https://github.com/SymbiFlow/python-fpga-interchange.git
+cd python-fpga-interchange
+pip install -r requirements.txt
+```
+
+Patch device database with cell constraints and LUT annotations:
+```
+python3 -mfpga_interchange.patch \
+ --schema_dir ${INTERCHANGE_DIR} \
+ --schema device \
+ --patch_path constraints \
+ --patch_format yaml \
+ ${RAPIDWRIGHT_PATH}/xc7a35tcpg236-1.device \
+ test_data/series7_constraints.yaml \
+ xc7a35tcpg236-1_constraints.device
+python3 -mfpga_interchange.patch \
+ --schema_dir ${INTERCHANGE_DIR} \
+ --schema device \
+ --patch_path lutDefinitions \
+ --patch_format yaml \
+ xc7a35tcpg236-1_constraints.device \
+ test_data/series7_luts.yaml \
+ xc7a35tcpg236-1_constraints_luts.device
+```
+
+Generate nextpnr BBA and constids.inc from device database:
+```
+python3 -mfpga_interchange.nextpnr_emit \
+ --schema_dir ${INTERCHANGE_DIR} \
+ --output_dir ${NEXTPNR_DIR}/fpga_interchange/ \
+ --device xc7a35tcpg236-1_constraints_luts.device
+```
+
+Build nextpnr:
+
+```
+cd ${NEXTPNR_DIR}
+cmake -DARCH=fpga_interchange .
+make -j
+```
+
+Compile generated BBA:
+```
+bba/bbasm -l fpga_interchange/chipdb.bba fpga_interchange/chipdb.bin
+```
+
+Run nextpnr archcheck:
+```
+./nextpnr-fpga_interchange --chipdb fpga_interchange/chipdb.bin --test
+```
+
+Once nextpnr can complete the place and route task and output the physical
+netlist, RapidWright can be used to generate a DCP suitable for bitstream
+output and DRC checks.
+
+```
+${RAPIDWRIGHT_PATH}/scripts/invoke_rapidwright.sh \
+ com.xilinx.rapidwright.interchange.PhysicalNetlistToDcp \
+ <logical netlist file> <physical netlist file> <XDC file> <output DCP>
+```
diff --git a/fpga_interchange/arch.cc b/fpga_interchange/arch.cc
new file mode 100644
index 00000000..63cf290b
--- /dev/null
+++ b/fpga_interchange/arch.cc
@@ -0,0 +1,578 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2018 Claire Wolf <claire@symbioticeda.com>
+ * Copyright (C) 2018-19 David Shah <david@symbioticeda.com>
+ * Copyright (C) 2021 Symbiflow Authors
+ *
+ * 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 <algorithm>
+#include <boost/algorithm/string.hpp>
+#include <boost/range/adaptor/reversed.hpp>
+#include <cmath>
+#include <cstring>
+#include <queue>
+#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
+
+static std::pair<std::string, std::string> split_identifier_name_dot(const std::string &name)
+{
+ size_t first_dot = name.find('.');
+ NPNR_ASSERT(first_dot != std::string::npos);
+ return std::make_pair(name.substr(0, first_dot), name.substr(first_dot + 1));
+};
+
+// -----------------------------------------------------------------------
+
+void IdString::initialize_arch(const BaseCtx *ctx) {}
+
+// -----------------------------------------------------------------------
+
+static const ChipInfoPOD *get_chip_info(const RelPtr<ChipInfoPOD> *ptr) { return ptr->get(); }
+
+Arch::Arch(ArchArgs args) : args(args)
+{
+ try {
+ blob_file.open(args.chipdb);
+ if (args.chipdb.empty() || !blob_file.is_open())
+ log_error("Unable to read chipdb %s\n", args.chipdb.c_str());
+ const char *blob = reinterpret_cast<const char *>(blob_file.data());
+ chip_info = get_chip_info(reinterpret_cast<const RelPtr<ChipInfoPOD> *>(blob));
+ } catch (...) {
+ log_error("Unable to read chipdb %s\n", args.chipdb.c_str());
+ }
+
+ // Read strings from constids into IdString database, checking that list
+ // is unique and matches expected constid value.
+ int id = 1;
+ for (const auto &constid : *chip_info->constids) {
+ IdString::initialize_add(this, constid.get(), id++);
+ }
+
+ tileStatus.resize(chip_info->tiles.size());
+ for (int i = 0; i < chip_info->tiles.ssize(); i++) {
+ tileStatus[i].boundcells.resize(chip_info->tile_types[chip_info->tiles[i].type].bel_data.size());
+ }
+
+ // Sanity check cell name ids.
+ const CellMapPOD &cell_map = *chip_info->cell_map;
+ int32_t first_cell_id = cell_map.cell_names[0];
+ for (int32_t i = 0; i < cell_map.cell_names.ssize(); ++i) {
+ log_assert(cell_map.cell_names[i] == i + first_cell_id);
+ }
+}
+
+// -----------------------------------------------------------------------
+
+std::string Arch::getChipName() const { return chip_info->name.get(); }
+
+// -----------------------------------------------------------------------
+
+IdString Arch::archArgsToId(ArchArgs args) const { return IdString(); }
+
+// -----------------------------------------------------------------------
+
+void Arch::setup_byname() const
+{
+ if (tile_by_name.empty()) {
+ for (int i = 0; i < chip_info->tiles.ssize(); i++) {
+ tile_by_name[id(chip_info->tiles[i].name.get())] = i;
+ }
+ }
+
+ if (site_by_name.empty()) {
+ for (int i = 0; i < chip_info->tiles.ssize(); i++) {
+ auto &tile = chip_info->tiles[i];
+ auto &tile_type = chip_info->tile_types[tile.type];
+ for (int j = 0; j < tile_type.number_sites; j++) {
+ auto &site = chip_info->sites[tile.sites[j]];
+ site_by_name[id(site.name.get())] = std::make_pair(i, j);
+ }
+ }
+ }
+}
+
+BelId Arch::getBelByName(IdStringList name) const
+{
+ BelId ret;
+ if (name.ids.size() != 2) {
+ return BelId();
+ }
+
+ setup_byname();
+
+ int tile, site;
+ std::tie(tile, site) = site_by_name.at(name.ids[0]);
+ auto &tile_info = chip_info->tile_types[chip_info->tiles[tile].type];
+ IdString belname = name.ids[1];
+ for (int i = 0; i < tile_info.bel_data.ssize(); i++) {
+ if (tile_info.bel_data[i].site == site && tile_info.bel_data[i].name == belname.index) {
+ ret.tile = tile;
+ ret.index = i;
+ break;
+ }
+ }
+
+ return ret;
+}
+
+BelRange Arch::getBelsByTile(int x, int y) const
+{
+ BelRange br;
+
+ br.b.cursor_tile = get_tile_index(x, y);
+ br.e.cursor_tile = br.b.cursor_tile;
+ br.b.cursor_index = 0;
+ br.e.cursor_index = chip_info->tile_types[chip_info->tiles[br.b.cursor_tile].type].bel_data.size();
+ br.b.chip = chip_info;
+ br.e.chip = chip_info;
+
+ if (br.b != br.e) {
+ ++br.e;
+ }
+ return br;
+}
+
+WireId Arch::getBelPinWire(BelId bel, IdString pin) const
+{
+ NPNR_ASSERT(bel != BelId());
+
+ int pin_index = get_bel_pin_index(bel, pin);
+
+ auto &bel_data = bel_info(chip_info, bel);
+ NPNR_ASSERT(pin_index >= 0 && pin_index < bel_data.num_bel_wires);
+
+ const int32_t *wires = bel_data.wires.get();
+ int32_t wire_index = wires[pin_index];
+ if (wire_index < 0) {
+ // This BEL pin is not connected.
+ return WireId();
+ } else {
+ return canonical_wire(chip_info, bel.tile, wire_index);
+ }
+}
+
+PortType Arch::getBelPinType(BelId bel, IdString pin) const
+{
+ NPNR_ASSERT(bel != BelId());
+
+ int pin_index = get_bel_pin_index(bel, pin);
+ auto &bel_data = bel_info(chip_info, bel);
+ NPNR_ASSERT(pin_index >= 0 && pin_index < bel_data.num_bel_wires);
+ const int32_t *types = bel_data.types.get();
+ return PortType(types[pin_index]);
+}
+
+// -----------------------------------------------------------------------
+
+WireId Arch::getWireByName(IdStringList name) const
+{
+ WireId ret;
+ if (name.ids.size() != 2) {
+ return WireId();
+ }
+
+ setup_byname();
+
+ auto iter = site_by_name.find(name.ids[0]);
+ if (iter != site_by_name.end()) {
+ int tile;
+ int site;
+ std::tie(tile, site) = iter->second;
+ auto &tile_info = chip_info->tile_types[chip_info->tiles[tile].type];
+ IdString wirename = name.ids[1];
+ for (int i = 0; i < tile_info.wire_data.ssize(); i++) {
+ if (tile_info.wire_data[i].site == site && tile_info.wire_data[i].name == wirename.index) {
+ ret.tile = tile;
+ ret.index = i;
+ break;
+ }
+ }
+ } else {
+ int tile = tile_by_name.at(name.ids[0]);
+ auto &tile_info = chip_info->tile_types[chip_info->tiles[tile].type];
+ IdString wirename = name.ids[1];
+ for (int i = 0; i < tile_info.wire_data.ssize(); i++) {
+ if (tile_info.wire_data[i].site == -1 && tile_info.wire_data[i].name == wirename.index) {
+ int32_t node = chip_info->tiles[tile].tile_wire_to_node[i];
+ if (node == -1) {
+ // Not a nodal wire
+ ret.tile = tile;
+ ret.index = i;
+ } else {
+ // Is a nodal wire, set tile to -1
+ ret.tile = -1;
+ ret.index = node;
+ }
+ break;
+ }
+ }
+ }
+
+ return ret;
+}
+
+IdString Arch::getWireType(WireId wire) const { return id(""); }
+std::vector<std::pair<IdString, std::string>> Arch::getWireAttrs(WireId wire) const { return {}; }
+
+// -----------------------------------------------------------------------
+
+PipId Arch::getPipByName(IdStringList name) const
+{
+ // PIP name structure:
+ // Tile PIP: <tile name>/<source wire name>.<destination wire name>
+ // Site PIP: <site name>/<bel name>/<input bel pin name>
+ // Site pin: <site name>/<bel name>
+ // Psuedo site PIP: <site name>/<source wire name>.<destination wire name>
+
+ setup_byname();
+
+ if (name.ids.size() == 3) {
+ // This is a Site PIP.
+ IdString site_name = name.ids[0];
+ IdString belname = name.ids[1];
+ IdString pinname = name.ids[2];
+
+ int tile;
+ int site;
+ std::tie(tile, site) = site_by_name.at(site_name);
+ auto &tile_info = chip_info->tile_types[chip_info->tiles[tile].type];
+
+ std::array<IdString, 2> ids{name.ids[0], belname};
+ BelId bel = getBelByName(IdStringList(ids));
+ NPNR_ASSERT(bel != BelId());
+
+ int pin_index = get_bel_pin_index(bel, pinname);
+ NPNR_ASSERT(pin_index >= 0);
+
+ for (int i = 0; i < tile_info.pip_data.ssize(); i++) {
+ if (tile_info.pip_data[i].site == site && tile_info.pip_data[i].bel == bel.index &&
+ tile_info.pip_data[i].extra_data == pin_index) {
+
+ PipId ret;
+ ret.tile = tile;
+ ret.index = i;
+ return ret;
+ }
+ }
+ } else {
+ auto iter = site_by_name.find(name.ids[0]);
+ if (iter != site_by_name.end()) {
+ // This is either a site pin or a psuedo site pip.
+ // psuedo site pips are <site>/<src site wire>.<dst site wire>
+ // site pins are <site>/<bel>
+ int tile;
+ int site;
+ std::tie(tile, site) = iter->second;
+ auto &tile_info = chip_info->tile_types[chip_info->tiles[tile].type];
+
+ std::string pip_second = name.ids[1].str(this);
+ auto split = pip_second.find('.');
+ if (split == std::string::npos) {
+ // This is a site pin!
+ BelId bel = getBelByName(name);
+ NPNR_ASSERT(bel != BelId());
+
+ for (int i = 0; i < tile_info.pip_data.ssize(); i++) {
+ if (tile_info.pip_data[i].site == site && tile_info.pip_data[i].bel == bel.index) {
+
+ PipId ret;
+ ret.tile = tile;
+ ret.index = i;
+ return ret;
+ }
+ }
+ } else {
+ // This is a psuedo site pip!
+ IdString src_site_wire = id(pip_second.substr(0, split));
+ IdString dst_site_wire = id(pip_second.substr(split + 1));
+ int32_t src_index = -1;
+ int32_t dst_index = -1;
+
+ for (int i = 0; i < tile_info.wire_data.ssize(); i++) {
+ if (tile_info.wire_data[i].site == site && tile_info.wire_data[i].name == src_site_wire.index) {
+ src_index = i;
+ if (dst_index != -1) {
+ break;
+ }
+ }
+ if (tile_info.wire_data[i].site == site && tile_info.wire_data[i].name == dst_site_wire.index) {
+ dst_index = i;
+ if (src_index != -1) {
+ break;
+ }
+ }
+ }
+
+ NPNR_ASSERT(src_index != -1);
+ NPNR_ASSERT(dst_index != -1);
+
+ for (int i = 0; i < tile_info.pip_data.ssize(); i++) {
+ if (tile_info.pip_data[i].site == site && tile_info.pip_data[i].src_index == src_index &&
+ tile_info.pip_data[i].dst_index == dst_index) {
+
+ PipId ret;
+ ret.tile = tile;
+ ret.index = i;
+ return ret;
+ }
+ }
+ }
+ } else {
+ int tile = tile_by_name.at(name.ids[0]);
+ auto &tile_info = chip_info->tile_types[chip_info->tiles[tile].type];
+
+ std::string pip_second = name.ids[1].str(this);
+ auto spn = split_identifier_name_dot(pip_second);
+ auto src_wire_name = id(spn.first);
+ auto dst_wire_name = id(spn.second);
+
+ int32_t src_index = -1;
+ int32_t dst_index = -1;
+ for (int i = 0; i < tile_info.wire_data.ssize(); i++) {
+ if (tile_info.wire_data[i].site == -1 && tile_info.wire_data[i].name == src_wire_name.index) {
+ src_index = i;
+ if (dst_index != -1) {
+ break;
+ }
+ }
+ if (tile_info.wire_data[i].site == -1 && tile_info.wire_data[i].name == dst_wire_name.index) {
+ dst_index = i;
+ if (src_index != -1) {
+ break;
+ }
+ }
+ }
+
+ NPNR_ASSERT(src_index != -1);
+ NPNR_ASSERT(dst_index != -1);
+
+ for (int i = 0; i < tile_info.pip_data.ssize(); i++) {
+ if (tile_info.pip_data[i].src_index == src_index && tile_info.pip_data[i].dst_index == dst_index) {
+
+ PipId ret;
+ ret.tile = tile;
+ ret.index = i;
+ return ret;
+ }
+ }
+ }
+ }
+
+ return PipId();
+}
+
+IdStringList Arch::getPipName(PipId pip) const
+{
+ // PIP name structure:
+ // Tile PIP: <tile name>/<source wire name>.<destination wire name>
+ // Psuedo site PIP: <site name>/<input site wire>.<output site wire>
+ // Site PIP: <site name>/<bel name>/<input bel pin name>
+ // Site pin: <site name>/<bel name>
+ NPNR_ASSERT(pip != PipId());
+ auto &tile = chip_info->tiles[pip.tile];
+ auto &tile_type = loc_info(chip_info, pip);
+ auto &pip_info = tile_type.pip_data[pip.index];
+ if (pip_info.site != -1) {
+ // This is either a site pin or a site pip.
+ auto &site = chip_info->sites[tile.sites[pip_info.site]];
+ auto &bel = tile_type.bel_data[pip_info.bel];
+ IdString bel_name(bel.name);
+ if (bel.category == BEL_CATEGORY_LOGIC) {
+ // This is a psuedo pip
+ IdString src_wire_name = IdString(tile_type.wire_data[pip_info.src_index].name);
+ IdString dst_wire_name = IdString(tile_type.wire_data[pip_info.dst_index].name);
+ IdString pip = id(src_wire_name.str(this) + "." + dst_wire_name.str(this));
+ std::array<IdString, 2> ids{id(site.name.get()), pip};
+ return IdStringList(ids);
+
+ } else if (bel.category == BEL_CATEGORY_ROUTING) {
+ // This is a site pip.
+ IdString pin_name(bel.ports[pip_info.extra_data]);
+ std::array<IdString, 3> ids{id(site.name.get()), bel_name, pin_name};
+ return IdStringList(ids);
+ } else {
+ NPNR_ASSERT(bel.category == BEL_CATEGORY_SITE_PORT);
+ // This is a site pin, just the name of the BEL is a unique identifier.
+ std::array<IdString, 2> ids{id(site.name.get()), bel_name};
+ return IdStringList(ids);
+ }
+ } else {
+ // This is a tile pip.
+ IdString src_wire_name = IdString(tile_type.wire_data[pip_info.src_index].name);
+ IdString dst_wire_name = IdString(tile_type.wire_data[pip_info.dst_index].name);
+ IdString pip = id(src_wire_name.str(this) + "." + dst_wire_name.str(this));
+ std::array<IdString, 2> ids{id(std::string(tile.name.get())), pip};
+ return IdStringList(ids);
+ }
+}
+
+IdString Arch::getPipType(PipId pip) const { return id("PIP"); }
+
+std::vector<std::pair<IdString, std::string>> Arch::getPipAttrs(PipId pip) const { return {}; }
+
+// -----------------------------------------------------------------------
+
+BelId Arch::getBelByLocation(Loc loc) const
+{
+ BelId bi;
+ if (loc.x >= chip_info->width || loc.y >= chip_info->height)
+ return BelId();
+ bi.tile = get_tile_index(loc);
+ auto &li = loc_info(chip_info, bi);
+
+ if (loc.z >= li.bel_data.ssize()) {
+ return BelId();
+ } else {
+ bi.index = loc.z;
+ return bi;
+ }
+}
+
+std::vector<std::pair<IdString, std::string>> Arch::getBelAttrs(BelId bel) const { return {}; }
+
+// -----------------------------------------------------------------------
+
+ArcBounds Arch::getRouteBoundingBox(WireId src, WireId dst) const
+{
+ int dst_tile = dst.tile == -1 ? chip_info->nodes[dst.index].tile_wires[0].tile : dst.tile;
+ int src_tile = src.tile == -1 ? chip_info->nodes[src.index].tile_wires[0].tile : src.tile;
+
+ int x0, x1, y0, y1;
+ x0 = src_tile % chip_info->width;
+ x1 = x0;
+ y0 = src_tile / chip_info->width;
+ y1 = y0;
+ auto expand = [&](int x, int y) {
+ x0 = std::min(x0, x);
+ x1 = std::max(x1, x);
+ y0 = std::min(y0, y);
+ y1 = std::max(y1, y);
+ };
+
+ expand(dst_tile % chip_info->width, dst_tile / chip_info->width);
+
+ if (source_locs.count(src))
+ expand(source_locs.at(src).x, source_locs.at(src).y);
+
+ if (sink_locs.count(dst)) {
+ expand(sink_locs.at(dst).x, sink_locs.at(dst).y);
+ }
+
+ return {x0, y0, x1, y1};
+}
+
+delay_t Arch::getWireRipupDelayPenalty(WireId wire) const { return getRipupDelayPenalty(); }
+
+bool Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const { return false; }
+
+// -----------------------------------------------------------------------
+
+bool Arch::pack()
+{
+ // FIXME: Implement this
+ return false;
+}
+
+bool Arch::place()
+{
+ // FIXME: Implement this
+ return false;
+}
+
+bool Arch::route()
+{
+ // FIXME: Implement this
+ return false;
+}
+
+// -----------------------------------------------------------------------
+
+std::vector<GraphicElement> Arch::getDecalGraphics(DecalId decal) const { return {}; }
+
+DecalXY Arch::getBelDecal(BelId bel) const
+{
+ DecalXY decalxy;
+ return decalxy;
+}
+
+DecalXY Arch::getWireDecal(WireId wire) const
+{
+ DecalXY decalxy;
+ return decalxy;
+}
+
+DecalXY Arch::getPipDecal(PipId pip) const { return {}; };
+
+DecalXY Arch::getGroupDecal(GroupId pip) const { return {}; };
+
+// -----------------------------------------------------------------------
+
+delay_t Arch::estimateDelay(WireId src, WireId dst, bool debug) const
+{
+ // FIXME: Implement something to push the A* router in the right direction.
+ return 0;
+}
+
+delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const
+{
+ // FIXME: Implement when adding timing-driven place and route.
+ return 0;
+}
+
+bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const
+{
+ // FIXME: Implement when adding timing-driven place and route.
+ return false;
+}
+
+TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const
+{
+ // FIXME: Implement when adding timing-driven place and route.
+ return TMG_IGNORE;
+}
+
+TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port, int index) const
+{
+ // FIXME: Implement when adding timing-driven place and route.
+ TimingClockingInfo info;
+ return info;
+}
+
+#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/fpga_interchange/arch.h b/fpga_interchange/arch.h
new file mode 100644
index 00000000..12c30c3d
--- /dev/null
+++ b/fpga_interchange/arch.h
@@ -0,0 +1,1277 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2018 Claire Wolf <claire@symbioticeda.com>
+ * Copyright (C) 2018-19 David Shah <david@symbioticeda.com>
+ * Copyright (C) 2021 Symbiflow Authors
+ *
+ *
+ * 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
+
+/**** Everything in this section must be kept in sync with chipdb.py ****/
+
+#include "relptr.h"
+
+// Flattened site indexing.
+//
+// To enable flat BelId.z spaces, every tile and sites within that tile are
+// flattened.
+//
+// This has implications on BelId's, WireId's and PipId's.
+// The flattened site space works as follows:
+// - Objects that belong to the tile are first. BELs are always part of Sites,
+// so no BEL objects are in this category.
+// - All site alternative modes are exposed as a "full" site.
+// - Each site appends it's BEL's, wires (site wires) and PIP's.
+// - Sites add two types of pips. Sites will add pip data first for site
+// pips, and then for site pin edges.
+// 1. The first type is site pips, which connect site wires to other site
+// wires.
+// 2. The second type is site pin edges, which connect site wires to tile
+// wires (or vise-versa).
+
+NPNR_PACKED_STRUCT(struct BelInfoPOD {
+ int32_t name; // bel name (in site) constid
+ int32_t type; // Type name constid
+ int32_t bel_bucket; // BEL bucket constid.
+
+ int32_t num_bel_wires;
+ RelPtr<int32_t> ports; // port name constid
+ RelPtr<int32_t> types; // port type (IN/OUT/BIDIR)
+ RelPtr<int32_t> wires; // connected wire index in tile, or -1 if NA
+
+ int16_t site;
+ int16_t site_variant; // some sites have alternative types
+ int16_t category;
+ int16_t padding;
+
+ RelPtr<int8_t> valid_cells; // Bool array, length of number_cells.
+});
+
+enum BELCategory
+{
+ // BEL is a logic element
+ BEL_CATEGORY_LOGIC = 0,
+ // BEL is a site routing mux
+ BEL_CATEGORY_ROUTING = 1,
+ // BEL is a site port, e.g. boundry between site and routing graph.
+ BEL_CATEGORY_SITE_PORT = 2
+};
+
+NPNR_PACKED_STRUCT(struct BelPortPOD {
+ int32_t bel_index;
+ int32_t port;
+});
+
+NPNR_PACKED_STRUCT(struct TileWireInfoPOD {
+ int32_t name; // wire name constid
+
+ // Pip index inside tile
+ RelSlice<int32_t> pips_uphill;
+
+ // Pip index inside tile
+ RelSlice<int32_t> pips_downhill;
+
+ // Bel index inside tile
+ RelSlice<BelPortPOD> bel_pins;
+
+ int16_t site; // site index in tile
+ int16_t site_variant; // site variant index in tile
+});
+
+NPNR_PACKED_STRUCT(struct PipInfoPOD {
+ int32_t src_index, dst_index;
+ int16_t site; // site index in tile
+ int16_t site_variant; // site variant index in tile
+ int16_t bel; // BEL this pip belongs to if site pip.
+ int16_t extra_data;
+});
+
+NPNR_PACKED_STRUCT(struct TileTypeInfoPOD {
+ int32_t name; // Tile type constid
+
+ int32_t number_sites;
+
+ RelSlice<BelInfoPOD> bel_data;
+
+ RelSlice<TileWireInfoPOD> wire_data;
+
+ RelSlice<PipInfoPOD> pip_data;
+});
+
+NPNR_PACKED_STRUCT(struct SiteInstInfoPOD {
+ RelPtr<char> name;
+
+ // Which site type is this site instance?
+ // constid
+ int32_t site_type;
+});
+
+NPNR_PACKED_STRUCT(struct TileInstInfoPOD {
+ // Name of this tile.
+ RelPtr<char> name;
+
+ // Index into root.tile_types.
+ int32_t type;
+
+ // This array is root.tile_types[type].number_sites long.
+ // Index into root.sites
+ RelPtr<int32_t> sites;
+
+ // Number of tile wires; excluding any site-internal wires
+ // which come after general wires and are not stored here
+ // as they will never be nodal
+ // -1 if a tile-local wire; node index if nodal wire
+ RelSlice<int32_t> tile_wire_to_node;
+});
+
+NPNR_PACKED_STRUCT(struct TileWireRefPOD {
+ int32_t tile;
+ int32_t index;
+});
+
+NPNR_PACKED_STRUCT(struct NodeInfoPOD { RelSlice<TileWireRefPOD> tile_wires; });
+
+NPNR_PACKED_STRUCT(struct CellMapPOD {
+ // Cell names supported in this arch.
+ RelSlice<int32_t> cell_names; // constids
+ RelSlice<int32_t> cell_bel_buckets; // constids
+});
+
+NPNR_PACKED_STRUCT(struct ChipInfoPOD {
+ RelPtr<char> name;
+ RelPtr<char> generator;
+
+ int32_t version;
+ int32_t width, height;
+
+ RelSlice<TileTypeInfoPOD> tile_types;
+ RelSlice<SiteInstInfoPOD> sites;
+ RelSlice<TileInstInfoPOD> tiles;
+ RelSlice<NodeInfoPOD> nodes;
+
+ // BEL bucket constids.
+ RelSlice<int32_t> bel_buckets;
+
+ RelPtr<CellMapPOD> cell_map;
+
+ // Constid string data.
+ RelPtr<RelSlice<RelPtr<char>>> constids;
+});
+
+/************************ End of chipdb section. ************************/
+
+inline const TileTypeInfoPOD &tile_info(const ChipInfoPOD *chip_info, int32_t tile)
+{
+ return chip_info->tile_types[chip_info->tiles[tile].type];
+}
+
+template <typename Id> const TileTypeInfoPOD &loc_info(const ChipInfoPOD *chip_info, Id &id)
+{
+ return chip_info->tile_types[chip_info->tiles[id.tile].type];
+}
+
+inline const BelInfoPOD &bel_info(const ChipInfoPOD *chip_info, BelId bel)
+{
+ NPNR_ASSERT(bel != BelId());
+ return loc_info(chip_info, bel).bel_data[bel.index];
+}
+
+struct BelIterator
+{
+ const ChipInfoPOD *chip;
+ int cursor_index;
+ int cursor_tile;
+
+ BelIterator operator++()
+ {
+ cursor_index++;
+ while (cursor_tile < chip->tiles.ssize() && cursor_index >= tile_info(chip, cursor_tile).bel_data.ssize()) {
+ 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 FilteredBelIterator
+{
+ std::function<bool(BelId)> filter;
+ BelIterator b, e;
+
+ FilteredBelIterator operator++()
+ {
+ ++b;
+ while (b != e) {
+ if (filter(*b)) {
+ break;
+ }
+
+ ++b;
+ }
+ return *this;
+ }
+
+ bool operator!=(const FilteredBelIterator &other) const
+ {
+ NPNR_ASSERT(e == other.e);
+ return b != other.b;
+ }
+
+ bool operator==(const FilteredBelIterator &other) const
+ {
+ NPNR_ASSERT(e == other.e);
+ return b == other.b;
+ }
+
+ BelId operator*() const
+ {
+ BelId bel = *b;
+ NPNR_ASSERT(filter(bel));
+ return bel;
+ }
+};
+
+struct FilteredBelRange
+{
+ FilteredBelRange(BelIterator bel_b, BelIterator bel_e, std::function<bool(BelId)> filter)
+ {
+ b.filter = filter;
+ b.b = bel_b;
+ b.e = bel_e;
+
+ if (b.b != b.e && !filter(*b.b)) {
+ ++b;
+ }
+
+ e.b = bel_e;
+ e.e = bel_e;
+
+ if (b != e) {
+ NPNR_ASSERT(filter(*b.b));
+ }
+ }
+
+ FilteredBelIterator b, e;
+ FilteredBelIterator begin() const { return b; }
+ FilteredBelIterator end() const { return e; }
+};
+
+// -----------------------------------------------------------------------
+
+// Iterate over TileWires for a wire (will be more than one if nodal)
+struct TileWireIterator
+{
+ const ChipInfoPOD *chip;
+ WireId baseWire;
+ int cursor = -1;
+
+ void operator++() { cursor++; }
+
+ bool operator==(const TileWireIterator &other) const { return cursor == other.cursor; }
+ bool operator!=(const TileWireIterator &other) const { return cursor != other.cursor; }
+
+ // Returns a *denormalised* identifier always pointing to a tile wire rather than a node
+ WireId operator*() const
+ {
+ if (baseWire.tile == -1) {
+ WireId tw;
+ const auto &node_wire = chip->nodes[baseWire.index].tile_wires[cursor];
+ tw.tile = node_wire.tile;
+ tw.index = node_wire.index;
+ return tw;
+ } else {
+ return baseWire;
+ }
+ }
+};
+
+struct TileWireRange
+{
+ TileWireIterator b, e;
+ TileWireIterator begin() const { return b; }
+ TileWireIterator end() const { return e; }
+};
+
+inline WireId canonical_wire(const ChipInfoPOD *chip_info, int32_t tile, int32_t wire)
+{
+ WireId id;
+
+ if (wire >= chip_info->tiles[tile].tile_wire_to_node.ssize()) {
+ // Cannot be a nodal wire
+ id.tile = tile;
+ id.index = wire;
+ } else {
+ int32_t node = chip_info->tiles[tile].tile_wire_to_node[wire];
+ if (node == -1) {
+ // Not a nodal wire
+ id.tile = tile;
+ id.index = wire;
+ } else {
+ // Is a nodal wire, set tile to -1
+ id.tile = -1;
+ id.index = node;
+ }
+ }
+
+ return id;
+}
+
+// -----------------------------------------------------------------------
+
+struct WireIterator
+{
+ const ChipInfoPOD *chip;
+ int cursor_index = 0;
+ int cursor_tile = -1;
+
+ WireIterator operator++()
+ {
+ // Iterate over nodes first, then tile wires that aren't nodes
+ do {
+ cursor_index++;
+ if (cursor_tile == -1 && cursor_index >= chip->nodes.ssize()) {
+ cursor_tile = 0;
+ cursor_index = 0;
+ }
+ while (cursor_tile != -1 && cursor_tile < chip->tiles.ssize() &&
+ cursor_index >= chip->tile_types[chip->tiles[cursor_tile].type].wire_data.ssize()) {
+ cursor_index = 0;
+ cursor_tile++;
+ }
+
+ } while ((cursor_tile != -1 && cursor_tile < chip->tiles.ssize() &&
+ cursor_index < chip->tiles[cursor_tile].tile_wire_to_node.ssize() &&
+ chip->tiles[cursor_tile].tile_wire_to_node[cursor_index] != -1));
+
+ 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; }
+};
+
+// -----------------------------------------------------------------------
+struct AllPipIterator
+{
+ const ChipInfoPOD *chip;
+ int cursor_index;
+ int cursor_tile;
+
+ AllPipIterator operator++()
+ {
+ cursor_index++;
+ while (cursor_tile < chip->tiles.ssize() &&
+ cursor_index >= chip->tile_types[chip->tiles[cursor_tile].type].pip_data.ssize()) {
+ 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 UphillPipIterator
+{
+ const ChipInfoPOD *chip;
+ TileWireIterator twi, twi_end;
+ int cursor = -1;
+
+ void operator++()
+ {
+ cursor++;
+ while (true) {
+ if (!(twi != twi_end))
+ break;
+ WireId w = *twi;
+ auto &tile = chip->tile_types[chip->tiles[w.tile].type];
+ if (cursor < tile.wire_data[w.index].pips_uphill.ssize())
+ break;
+ ++twi;
+ cursor = 0;
+ }
+ }
+ bool operator!=(const UphillPipIterator &other) const { return twi != other.twi || cursor != other.cursor; }
+
+ PipId operator*() const
+ {
+ PipId ret;
+ WireId w = *twi;
+ ret.tile = w.tile;
+ ret.index = chip->tile_types[chip->tiles[w.tile].type].wire_data[w.index].pips_uphill[cursor];
+ return ret;
+ }
+};
+
+struct UphillPipRange
+{
+ UphillPipIterator b, e;
+ UphillPipIterator begin() const { return b; }
+ UphillPipIterator end() const { return e; }
+};
+
+struct DownhillPipIterator
+{
+ const ChipInfoPOD *chip;
+ TileWireIterator twi, twi_end;
+ int cursor = -1;
+
+ void operator++()
+ {
+ cursor++;
+ while (true) {
+ if (!(twi != twi_end))
+ break;
+ WireId w = *twi;
+ auto &tile = chip->tile_types[chip->tiles[w.tile].type];
+ if (cursor < tile.wire_data[w.index].pips_downhill.ssize())
+ break;
+ ++twi;
+ cursor = 0;
+ }
+ }
+ bool operator!=(const DownhillPipIterator &other) const { return twi != other.twi || cursor != other.cursor; }
+
+ PipId operator*() const
+ {
+ PipId ret;
+ WireId w = *twi;
+ ret.tile = w.tile;
+ ret.index = chip->tile_types[chip->tiles[w.tile].type].wire_data[w.index].pips_downhill[cursor];
+ return ret;
+ }
+};
+
+struct DownhillPipRange
+{
+ DownhillPipIterator b, e;
+ DownhillPipIterator begin() const { return b; }
+ DownhillPipIterator end() const { return e; }
+};
+
+struct BelPinIterator
+{
+ const ChipInfoPOD *chip;
+ TileWireIterator twi, twi_end;
+ int cursor = -1;
+
+ void operator++()
+ {
+ cursor++;
+
+ while (twi != twi_end) {
+ WireId w = *twi;
+ auto &tile = tile_info(chip, w.tile);
+ if (cursor < tile.wire_data[w.index].bel_pins.ssize())
+ break;
+
+ ++twi;
+ cursor = 0;
+ }
+ }
+ bool operator!=(const BelPinIterator &other) const { return twi != other.twi || cursor != other.cursor; }
+
+ BelPin operator*() const
+ {
+ BelPin ret;
+ WireId w = *twi;
+ ret.bel.tile = w.tile;
+ ret.bel.index = tile_info(chip, w.tile).wire_data[w.index].bel_pins[cursor].bel_index;
+ ret.pin.index = tile_info(chip, w.tile).wire_data[w.index].bel_pins[cursor].port;
+ return ret;
+ }
+};
+
+struct BelPinRange
+{
+ BelPinIterator b, e;
+ BelPinIterator begin() const { return b; }
+ BelPinIterator end() const { return e; }
+};
+
+struct IdStringIterator
+{
+ const int32_t *cursor;
+
+ void operator++() { cursor += 1; }
+
+ bool operator!=(const IdStringIterator &other) const { return cursor != other.cursor; }
+
+ bool operator==(const IdStringIterator &other) const { return cursor == other.cursor; }
+
+ IdString operator*() const { return IdString(*cursor); }
+};
+
+struct IdStringRange
+{
+ IdStringIterator b, e;
+ IdStringIterator begin() const { return b; }
+ IdStringIterator end() const { return e; }
+};
+
+struct BelBucketIterator
+{
+ IdStringIterator cursor;
+
+ void operator++() { ++cursor; }
+
+ bool operator!=(const BelBucketIterator &other) const { return cursor != other.cursor; }
+
+ bool operator==(const BelBucketIterator &other) const { return cursor == other.cursor; }
+
+ BelBucketId operator*() const
+ {
+ BelBucketId bucket;
+ bucket.name = IdString(*cursor);
+ return bucket;
+ }
+};
+
+struct BelBucketRange
+{
+ BelBucketIterator b, e;
+ BelBucketIterator begin() const { return b; }
+ BelBucketIterator end() const { return e; }
+};
+
+struct ArchArgs
+{
+ std::string chipdb;
+};
+
+struct Arch : BaseCtx
+{
+ boost::iostreams::mapped_file_source blob_file;
+ const ChipInfoPOD *chip_info;
+
+ mutable std::unordered_map<IdString, int> tile_by_name;
+ mutable std::unordered_map<IdString, std::pair<int, int>> site_by_name;
+
+ std::unordered_map<WireId, NetInfo *> wire_to_net;
+ std::unordered_map<PipId, NetInfo *> pip_to_net;
+ std::unordered_map<WireId, std::pair<int, int>> driving_pip_loc;
+ std::unordered_map<WireId, NetInfo *> reserved_wires;
+
+ struct TileStatus
+ {
+ std::vector<CellInfo *> boundcells;
+ };
+
+ std::vector<TileStatus> tileStatus;
+
+ ArchArgs args;
+ Arch(ArchArgs args);
+
+ std::string getChipName() const;
+
+ IdString archId() const { return id(chip_info->name.get()); }
+ ArchArgs archArgs() const { return args; }
+ IdString archArgsToId(ArchArgs args) const;
+
+ // -------------------------------------------------
+
+ uint32_t get_tile_index(int x, int y) const { return (y * chip_info->width + x); }
+ uint32_t get_tile_index(Loc loc) const { return get_tile_index(loc.x, loc.y); }
+ template <typename TileIndex, typename CoordIndex>
+ void get_tile_x_y(TileIndex tile_index, CoordIndex *x, CoordIndex *y) const
+ {
+ *x = tile_index % chip_info->width;
+ *y = tile_index / chip_info->width;
+ }
+
+ template <typename TileIndex> void get_tile_loc(TileIndex tile_index, Loc *loc) const
+ {
+ get_tile_x_y(tile_index, &loc->x, &loc->y);
+ }
+
+ int getGridDimX() const { return chip_info->width; }
+ int getGridDimY() const { return chip_info->height; }
+ int getTileBelDimZ(int x, int y) const
+ {
+ return chip_info->tile_types[chip_info->tiles[get_tile_index(x, y)].type].bel_data.size();
+ }
+ int getTilePipDimZ(int x, int y) const
+ {
+ return chip_info->tile_types[chip_info->tiles[get_tile_index(x, y)].type].number_sites;
+ }
+ char getNameDelimiter() const { return '/'; }
+
+ // -------------------------------------------------
+
+ void setup_byname() const;
+
+ BelId getBelByName(IdStringList name) const;
+
+ IdStringList getBelName(BelId bel) const
+ {
+ NPNR_ASSERT(bel != BelId());
+ int site_index = bel_info(chip_info, bel).site;
+ NPNR_ASSERT(site_index >= 0);
+ const SiteInstInfoPOD &site = chip_info->sites[chip_info->tiles[bel.tile].sites[site_index]];
+ std::array<IdString, 2> ids{id(site.name.get()), IdString(bel_info(chip_info, bel).name)};
+ return IdStringList(ids);
+ }
+
+ uint32_t getBelChecksum(BelId bel) const { return 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);
+ }
+
+ void unbindBel(BelId bel)
+ {
+ NPNR_ASSERT(bel != BelId());
+ NPNR_ASSERT(tileStatus[bel.tile].boundcells[bel.index] != 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 { 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; //-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;
+ return range;
+ }
+
+ Loc getBelLocation(BelId bel) const
+ {
+ NPNR_ASSERT(bel != BelId());
+ Loc loc;
+ get_tile_x_y(bel.tile, &loc.x, &loc.y);
+ loc.z = bel.index;
+ return loc;
+ }
+
+ BelId getBelByLocation(Loc loc) const;
+ BelRange getBelsByTile(int x, int y) const;
+
+ bool getBelGlobalBuf(BelId bel) const
+ {
+ // FIXME: This probably needs to be fixed!
+ return false;
+ }
+
+ bool getBelHidden(BelId bel) const { return bel_info(chip_info, bel).category != BEL_CATEGORY_LOGIC; }
+
+ IdString getBelType(BelId bel) const
+ {
+ NPNR_ASSERT(bel != BelId());
+ return IdString(bel_info(chip_info, bel).type);
+ }
+
+ std::vector<std::pair<IdString, std::string>> getBelAttrs(BelId bel) const;
+
+ int get_bel_pin_index(BelId bel, IdString pin) const
+ {
+ NPNR_ASSERT(bel != BelId());
+ int num_bel_wires = bel_info(chip_info, bel).num_bel_wires;
+ const int32_t *ports = bel_info(chip_info, bel).ports.get();
+ for (int i = 0; i < num_bel_wires; i++) {
+ if (ports[i] == pin.index) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ WireId getBelPinWire(BelId bel, IdString pin) const;
+ PortType getBelPinType(BelId bel, IdString pin) const;
+
+ IdStringRange getBelPins(BelId bel) const
+ {
+ NPNR_ASSERT(bel != BelId());
+
+ int num_bel_wires = bel_info(chip_info, bel).num_bel_wires;
+ const int32_t *ports = bel_info(chip_info, bel).ports.get();
+
+ IdStringRange str_range;
+ str_range.b.cursor = &ports[0];
+ str_range.e.cursor = &ports[num_bel_wires - 1];
+
+ return str_range;
+ }
+
+ // -------------------------------------------------
+
+ WireId getWireByName(IdStringList name) const;
+
+ const TileWireInfoPOD &wire_info(WireId wire) const
+ {
+ if (wire.tile == -1) {
+ const TileWireRefPOD &wr = chip_info->nodes[wire.index].tile_wires[0];
+ return chip_info->tile_types[chip_info->tiles[wr.tile].type].wire_data[wr.index];
+ } else {
+ return loc_info(chip_info, wire).wire_data[wire.index];
+ }
+ }
+
+ IdStringList getWireName(WireId wire) const
+ {
+ NPNR_ASSERT(wire != WireId());
+ if (wire.tile != -1) {
+ const auto &tile_type = loc_info(chip_info, wire);
+ if (tile_type.wire_data[wire.index].site != -1) {
+ int site_index = tile_type.wire_data[wire.index].site;
+ const SiteInstInfoPOD &site = chip_info->sites[chip_info->tiles[wire.tile].sites[site_index]];
+ std::array<IdString, 2> ids{id(site.name.get()), IdString(tile_type.wire_data[wire.index].name)};
+ return IdStringList(ids);
+ }
+ }
+
+ int32_t tile = wire.tile == -1 ? chip_info->nodes[wire.index].tile_wires[0].tile : wire.tile;
+ IdString tile_name = id(chip_info->tiles[tile].name.get());
+ std::array<IdString, 2> ids{tile_name, IdString(wire_info(wire).name)};
+ return IdStringList(ids);
+ }
+
+ IdString getWireType(WireId wire) const;
+ std::vector<std::pair<IdString, std::string>> getWireAttrs(WireId wire) const;
+
+ uint32_t getWireChecksum(WireId wire) const { return 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;
+ }
+
+ WireId getConflictingWireWire(WireId wire) const { return wire; }
+
+ 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;
+ }
+
+ DelayInfo getWireDelay(WireId wire) const
+ {
+ DelayInfo delay;
+ delay.delay = 0;
+ return delay;
+ }
+
+ TileWireRange get_tile_wire_range(WireId wire) const
+ {
+ TileWireRange range;
+ range.b.chip = chip_info;
+ range.b.baseWire = wire;
+ range.b.cursor = -1;
+ ++range.b;
+
+ range.e.chip = chip_info;
+ range.e.baseWire = wire;
+ if (wire.tile == -1) {
+ range.e.cursor = chip_info->nodes[wire.index].tile_wires.size();
+ } else {
+ range.e.cursor = 1;
+ }
+ return range;
+ }
+
+ BelPinRange getWireBelPins(WireId wire) const
+ {
+ BelPinRange range;
+ NPNR_ASSERT(wire != WireId());
+
+ TileWireRange twr = get_tile_wire_range(wire);
+ range.b.chip = chip_info;
+ range.b.twi = twr.b;
+ range.b.twi_end = twr.e;
+ range.b.cursor = -1;
+ ++range.b;
+
+ range.e.chip = chip_info;
+ range.e.twi = twr.e;
+ range.e.twi_end = twr.e;
+ range.e.cursor = 0;
+ return range;
+ }
+
+ WireRange getWires() const
+ {
+ WireRange range;
+ range.b.chip = chip_info;
+ range.b.cursor_tile = -1;
+ range.b.cursor_index = 0;
+ range.e.chip = chip_info;
+ range.e.cursor_tile = chip_info->tiles.size();
+ range.e.cursor_index = 0;
+ return range;
+ }
+
+ // -------------------------------------------------
+
+ PipId getPipByName(IdStringList name) const;
+ IdStringList getPipName(PipId pip) const;
+ IdString getPipType(PipId pip) const;
+ std::vector<std::pair<IdString, std::string>> getPipAttrs(PipId pip) const;
+
+ void bindPip(PipId pip, NetInfo *net, PlaceStrength strength)
+ {
+ NPNR_ASSERT(pip != PipId());
+ NPNR_ASSERT(pip_to_net[pip] == nullptr);
+
+ WireId dst = getPipDstWire(pip);
+ NPNR_ASSERT(wire_to_net[dst] == nullptr || wire_to_net[dst] == net);
+
+ pip_to_net[pip] = net;
+ std::pair<int, int> loc;
+ get_tile_x_y(pip.tile, &loc.first, &loc.second);
+ driving_pip_loc[dst] = loc;
+
+ 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 = getPipDstWire(pip);
+ 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
+ {
+ 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; //-1 and then ++ deals with the case of no wries in the first tile
+ range.e.cursor_tile = chip_info->width * chip_info->height;
+ range.e.cursor_index = 0;
+ range.e.chip = chip_info;
+ return range;
+ }
+
+ Loc getPipLocation(PipId pip) const
+ {
+ Loc loc;
+ get_tile_loc(pip.tile, &loc);
+ loc.z = 0;
+ return loc;
+ }
+
+ uint32_t getPipChecksum(PipId pip) const { return pip.index; }
+
+ WireId getPipSrcWire(PipId pip) const
+ {
+ return canonical_wire(chip_info, pip.tile, loc_info(chip_info, pip).pip_data[pip.index].src_index);
+ }
+
+ WireId getPipDstWire(PipId pip) const
+ {
+ return canonical_wire(chip_info, pip.tile, loc_info(chip_info, pip).pip_data[pip.index].dst_index);
+ }
+
+ DelayInfo getPipDelay(PipId pip) const { return DelayInfo(); }
+
+ DownhillPipRange getPipsDownhill(WireId wire) const
+ {
+ DownhillPipRange range;
+ NPNR_ASSERT(wire != WireId());
+ TileWireRange twr = get_tile_wire_range(wire);
+ range.b.chip = chip_info;
+ range.b.twi = twr.b;
+ range.b.twi_end = twr.e;
+ range.b.cursor = -1;
+ ++range.b;
+ range.e.chip = chip_info;
+ range.e.twi = twr.e;
+ range.e.twi_end = twr.e;
+ range.e.cursor = 0;
+ return range;
+ }
+
+ UphillPipRange getPipsUphill(WireId wire) const
+ {
+ UphillPipRange range;
+ NPNR_ASSERT(wire != WireId());
+ TileWireRange twr = get_tile_wire_range(wire);
+ range.b.chip = chip_info;
+ range.b.twi = twr.b;
+ range.b.twi_end = twr.e;
+ range.b.cursor = -1;
+ ++range.b;
+ range.e.chip = chip_info;
+ range.e.twi = twr.e;
+ range.e.twi_end = twr.e;
+ range.e.cursor = 0;
+ return range;
+ }
+
+ // -------------------------------------------------
+
+ // FIXME: Use groups to get access to sites.
+ GroupId getGroupByName(IdStringList name) const { return GroupId(); }
+ IdStringList getGroupName(GroupId group) const { return IdStringList(); }
+ 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, bool debug = false) const;
+ delay_t predictDelay(const NetInfo *net_info, const PortRef &sink) const;
+ ArcBounds getRouteBoundingBox(WireId src, WireId dst) 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.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;
+
+ // -------------------------------------------------
+
+ bool pack();
+ bool place();
+ bool route();
+ // -------------------------------------------------
+
+ 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;
+
+ // -------------------------------------------------
+
+ // 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;
+
+ // -------------------------------------------------
+
+ const BelBucketRange getBelBuckets() const
+ {
+ BelBucketRange bel_bucket_range;
+ bel_bucket_range.b.cursor.cursor = chip_info->bel_buckets.begin();
+ bel_bucket_range.e.cursor.cursor = chip_info->bel_buckets.end();
+ return bel_bucket_range;
+ }
+
+ BelBucketId getBelBucketForBel(BelId bel) const
+ {
+ BelBucketId bel_bucket;
+ bel_bucket.name = IdString(bel_info(chip_info, bel).bel_bucket);
+ return bel_bucket;
+ }
+
+ const IdStringRange getCellTypes() const
+ {
+ const CellMapPOD &cell_map = *chip_info->cell_map;
+
+ IdStringRange id_range;
+ id_range.b.cursor = cell_map.cell_names.begin();
+ id_range.e.cursor = cell_map.cell_names.end();
+
+ return id_range;
+ }
+
+ IdString getBelBucketName(BelBucketId bucket) const { return bucket.name; }
+
+ BelBucketId getBelBucketByName(IdString name) const
+ {
+ for (BelBucketId bel_bucket : getBelBuckets()) {
+ if (bel_bucket.name == name) {
+ return bel_bucket;
+ }
+ }
+
+ NPNR_ASSERT_FALSE("Failed to find BEL bucket for name.");
+ return BelBucketId();
+ }
+
+ size_t getCellTypeIndex(IdString cell_type) const
+ {
+ const CellMapPOD &cell_map = *chip_info->cell_map;
+ int cell_offset = cell_type.index - cell_map.cell_names[0];
+ NPNR_ASSERT(cell_offset >= 0 && cell_offset < cell_map.cell_names.ssize());
+ NPNR_ASSERT(cell_map.cell_names[cell_offset] == cell_type.index);
+
+ return cell_offset;
+ }
+
+ BelBucketId getBelBucketForCellType(IdString cell_type) const
+ {
+ BelBucketId bucket;
+ const CellMapPOD &cell_map = *chip_info->cell_map;
+ bucket.name = IdString(cell_map.cell_bel_buckets[getCellTypeIndex(cell_type)]);
+ return bucket;
+ }
+
+ FilteredBelRange getBelsInBucket(BelBucketId bucket) const
+ {
+ BelRange range = getBels();
+ FilteredBelRange filtered_range(range.begin(), range.end(),
+ [this, bucket](BelId bel) { return getBelBucketForBel(bel) == bucket; });
+
+ return filtered_range;
+ }
+
+ bool isValidBelForCellType(IdString cell_type, BelId bel) const
+ {
+ return bel_info(chip_info, bel).valid_cells[getCellTypeIndex(cell_type)];
+ }
+
+ // 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
+ {
+ NPNR_ASSERT(isValidBelForCellType(cell->type, bel));
+
+ // FIXME: Implement this
+ return true;
+ }
+
+ // Return true whether all Bels at a given location are valid
+ bool isBelLocationValid(BelId bel) const
+ {
+ // FIXME: Implement this
+ return true;
+ }
+
+ IdString getBelTileType(BelId bel) const { return IdString(loc_info(chip_info, bel).name); }
+
+ std::unordered_map<WireId, Loc> sink_locs, source_locs;
+ // -------------------------------------------------
+ void assignArchInfo() {}
+
+ // -------------------------------------------------
+
+ static const std::string defaultPlacer;
+ static const std::vector<std::string> availablePlacers;
+
+ static const std::string defaultRouter;
+ static const std::vector<std::string> availableRouters;
+
+ // -------------------------------------------------
+ void write_physical_netlist(const std::string &filename) const {}
+};
+
+NEXTPNR_NAMESPACE_END
diff --git a/fpga_interchange/arch_pybindings.cc b/fpga_interchange/arch_pybindings.cc
new file mode 100644
index 00000000..416a015a
--- /dev/null
+++ b/fpga_interchange/arch_pybindings.cc
@@ -0,0 +1,77 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2020 David Shah <dave@ds0.me>
+ * Copyright (C) 2021 Symbiflow Authors
+ *
+ * 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("chipdb", &ArchArgs::chipdb);
+
+ py::class_<BelId>(m, "BelId").def_readwrite("index", &BelId::index);
+
+ py::class_<WireId>(m, "WireId").def_readwrite("index", &WireId::index);
+
+ py::class_<PipId>(m, "PipId").def_readwrite("index", &PipId::index);
+
+ 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);
+
+ fn_wrapper_2a<Context, decltype(&Context::isValidBelForCell), &Context::isValidBelForCell, pass_through<bool>,
+ addr_and_unwrap<CellInfo>, conv_from_str<BelId>>::def_wrap(ctx_cls, "isValidBelForCell");
+
+ 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, IdString> AliasMap;
+ typedef std::unordered_map<IdString, HierarchicalCell> HierarchyMap;
+
+ auto belpin_cls = py::class_<ContextualWrapper<BelPin>>(m, "BelPin");
+ readonly_wrapper<BelPin, decltype(&BelPin::bel), &BelPin::bel, conv_to_str<BelId>>::def_wrap(belpin_cls, "bel");
+ readonly_wrapper<BelPin, decltype(&BelPin::pin), &BelPin::pin, conv_to_str<IdString>>::def_wrap(belpin_cls, "pin");
+
+ typedef FilteredBelRange BelRangeForBelBucket;
+#include "arch_pybindings_shared.h"
+
+ WRAP_RANGE(m, BelBucket, conv_to_str<BelBucketId>);
+ 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, UphillPip, conv_to_str<PipId>);
+ WRAP_RANGE(m, DownhillPip, conv_to_str<PipId>);
+ WRAP_RANGE(m, BelPin, 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/fpga_interchange/arch_pybindings.h b/fpga_interchange/arch_pybindings.h
new file mode 100644
index 00000000..1cccdf55
--- /dev/null
+++ b/fpga_interchange/arch_pybindings.h
@@ -0,0 +1,110 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2020 David Shah <dave@ds0.me>
+ * Copyright (C) 2021 Symbiflow Authors
+ *
+ * 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"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+namespace PythonConversion {
+
+template <> struct string_converter<BelId>
+{
+ BelId from_str(Context *ctx, std::string name) { return ctx->getBelByName(IdStringList::parse(ctx, 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(IdStringList::parse(ctx, 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(IdStringList::parse(ctx, 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(IdStringList::parse(ctx, 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<BelBucketId>
+{
+ BelBucketId from_str(Context *ctx, std::string name) { return ctx->getBelBucketByName(ctx->id(name)); }
+
+ std::string to_str(Context *ctx, BelBucketId id)
+ {
+ if (id == BelBucketId())
+ throw bad_wrap();
+ return ctx->getBelBucketName(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/fpga_interchange/archdefs.h b/fpga_interchange/archdefs.h
new file mode 100644
index 00000000..d6d0a3c7
--- /dev/null
+++ b/fpga_interchange/archdefs.h
@@ -0,0 +1,192 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2018 Claire Wolf <claire@symbioticeda.com>
+ * Copyright (C) 2021 Symbiflow Authors
+ *
+ * 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
+
+#include <cstdint>
+
+typedef int delay_t;
+
+struct DelayInfo
+{
+ delay_t delay = 0;
+
+ delay_t minRaiseDelay() const { return delay; }
+ delay_t maxRaiseDelay() const { return delay; }
+
+ delay_t minFallDelay() const { return delay; }
+ delay_t maxFallDelay() const { return delay; }
+
+ delay_t minDelay() const { return delay; }
+ delay_t maxDelay() const { return delay; }
+
+ DelayInfo operator+(const DelayInfo &other) const
+ {
+ DelayInfo ret;
+ ret.delay = this->delay + other.delay;
+ return ret;
+ }
+};
+
+// -----------------------------------------------------------------------
+
+struct BelId
+{
+ // Tile that contains this BEL.
+ int32_t tile = -1;
+ // Index into tile type BEL array.
+ // BEL indicies are the same for all tiles of the same type.
+ int32_t index = -1;
+
+ 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
+{
+ // Tile that contains this wire.
+ int32_t tile = -1;
+ int32_t index = -1;
+
+ 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;
+ int32_t index = -1;
+
+ 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
+{
+ bool operator==(const GroupId &other) const { return true; }
+ bool operator!=(const GroupId &other) const { return false; }
+};
+
+struct DecalId
+{
+ bool operator==(const DecalId &other) const { return true; }
+ bool operator!=(const DecalId &other) const { return false; }
+};
+
+struct BelBucketId
+{
+ IdString name;
+
+ bool operator==(const BelBucketId &other) const { return (name == other.name); }
+ bool operator!=(const BelBucketId &other) const { return (name != other.name); }
+ bool operator<(const BelBucketId &other) const { return name < other.name; }
+};
+
+struct ArchNetInfo
+{
+};
+
+struct NetInfo;
+
+struct ArchCellInfo
+{
+};
+
+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;
+ 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;
+ return seed;
+ }
+};
+
+template <> struct hash<NEXTPNR_NAMESPACE_PREFIX BelBucketId>
+{
+ std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX BelBucketId &bucket) const noexcept
+ {
+ std::size_t seed = 0;
+ boost::hash_combine(seed, hash<NEXTPNR_NAMESPACE_PREFIX IdString>()(bucket.name));
+ return seed;
+ }
+};
+
+} // namespace std
diff --git a/fpga_interchange/family.cmake b/fpga_interchange/family.cmake
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/fpga_interchange/family.cmake
diff --git a/fpga_interchange/main.cc b/fpga_interchange/main.cc
new file mode 100644
index 00000000..1f98b186
--- /dev/null
+++ b/fpga_interchange/main.cc
@@ -0,0 +1,84 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2018 Claire Wolf <claire@symbioticeda.com>
+ * Copyright (C) 2021 Symbiflow Authors
+ *
+ * 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 FpgaInterchangeCommandHandler : public CommandHandler
+{
+ public:
+ FpgaInterchangeCommandHandler(int argc, char **argv);
+ virtual ~FpgaInterchangeCommandHandler(){};
+ 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;
+};
+
+FpgaInterchangeCommandHandler::FpgaInterchangeCommandHandler(int argc, char **argv) : CommandHandler(argc, argv) {}
+
+po::options_description FpgaInterchangeCommandHandler::getArchOptions()
+{
+ po::options_description specific("Architecture specific options");
+ specific.add_options()("chipdb", po::value<std::string>(), "name of chip database binary");
+ specific.add_options()("xdc", po::value<std::vector<std::string>>(), "XDC-style constraints file");
+ specific.add_options()("phys", po::value<std::string>(), "FPGA interchange Physical netlist to write");
+
+ return specific;
+}
+
+void FpgaInterchangeCommandHandler::customBitstream(Context *ctx)
+{
+ if (vm.count("phys")) {
+ std::string filename = vm["phys"].as<std::string>();
+ ctx->write_physical_netlist(filename);
+ }
+}
+
+std::unique_ptr<Context> FpgaInterchangeCommandHandler::createContext(std::unordered_map<std::string, Property> &values)
+{
+ ArchArgs chipArgs;
+ if (!vm.count("chipdb")) {
+ log_error("chip database binary must be provided\n");
+ }
+ chipArgs.chipdb = vm["chipdb"].as<std::string>();
+ return std::unique_ptr<Context>(new Context(chipArgs));
+}
+
+void FpgaInterchangeCommandHandler::customAfterLoad(Context *ctx) {}
+
+int main(int argc, char *argv[])
+{
+ FpgaInterchangeCommandHandler handler(argc, argv);
+ return handler.exec();
+}
+
+#endif
diff --git a/gui/fpga_interchange/family.cmake b/gui/fpga_interchange/family.cmake
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/gui/fpga_interchange/family.cmake
diff --git a/gui/fpga_interchange/mainwindow.cc b/gui/fpga_interchange/mainwindow.cc
new file mode 100644
index 00000000..b8a89ca9
--- /dev/null
+++ b/gui/fpga_interchange/mainwindow.cc
@@ -0,0 +1,50 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2018 Miodrag Milanovic <miodrag@symbioticeda.com>
+ * Copyright (C) 2021 Symbiflow Authors
+ *
+ * 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 "mainwindow.h"
+
+#include <QMessageBox>
+#include <cstdlib>
+
+static void initMainResource() { Q_INIT_RESOURCE(nextpnr); }
+
+NEXTPNR_NAMESPACE_BEGIN
+
+MainWindow::MainWindow(std::unique_ptr<Context> context, CommandHandler *handler, QWidget *parent)
+ : BaseMainWindow(std::move(context), handler, parent)
+{
+ initMainResource();
+ QMessageBox::critical(0, "Error - FIXME", "No GUI support for nextpnr-generic");
+ std::exit(1);
+}
+
+MainWindow::~MainWindow() {}
+
+void MainWindow::newContext(Context *ctx)
+{
+ std::string title = "nextpnr-generic - " + ctx->getChipName();
+ setWindowTitle(title.c_str());
+}
+
+void MainWindow::createMenu() {}
+
+void MainWindow::new_proj() {}
+
+NEXTPNR_NAMESPACE_END
diff --git a/gui/fpga_interchange/mainwindow.h b/gui/fpga_interchange/mainwindow.h
new file mode 100644
index 00000000..0983845b
--- /dev/null
+++ b/gui/fpga_interchange/mainwindow.h
@@ -0,0 +1,46 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2018 Miodrag Milanovic <miodrag@symbioticeda.com>
+ * Copyright (C) 2021 Symbiflow Authors
+ *
+ * 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 MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include "../basewindow.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+class MainWindow : public BaseMainWindow
+{
+ Q_OBJECT
+
+ public:
+ explicit MainWindow(std::unique_ptr<Context> context, CommandHandler *handler, QWidget *parent = 0);
+ virtual ~MainWindow();
+
+ public:
+ void createMenu();
+
+ protected Q_SLOTS:
+ void new_proj() override;
+ void newContext(Context *ctx);
+};
+
+NEXTPNR_NAMESPACE_END
+
+#endif // MAINWINDOW_H
diff --git a/gui/fpga_interchange/nextpnr.qrc b/gui/fpga_interchange/nextpnr.qrc
new file mode 100644
index 00000000..03585ec0
--- /dev/null
+++ b/gui/fpga_interchange/nextpnr.qrc
@@ -0,0 +1,2 @@
+<RCC>
+</RCC>