aboutsummaryrefslogtreecommitdiffstats
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
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
-rw-r--r--.cirrus.yml2
-rw-r--r--.cirrus/Dockerfile.ubuntu16.0412
-rwxr-xr-x.cirrus/archcheck.sh1
-rw-r--r--.gitignore1
-rw-r--r--CMakeLists.txt13
-rw-r--r--README.md16
-rw-r--r--common/arch_pybindings_shared.h5
-rw-r--r--common/design_utils.cc36
-rw-r--r--common/design_utils.h18
-rw-r--r--common/nextpnr.cc4
-rw-r--r--common/router1.cc14
-rw-r--r--common/router2.cc5
-rw-r--r--common/util.h18
-rw-r--r--docs/nexus.md20
-rw-r--r--gui/nexus/family.cmake0
-rw-r--r--gui/nexus/mainwindow.cc87
-rw-r--r--gui/nexus/mainwindow.h49
-rw-r--r--gui/nexus/nextpnr.qrc2
-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
37 files changed, 7434 insertions, 13 deletions
diff --git a/.cirrus.yml b/.cirrus.yml
index a493b04f..e24f3271 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -5,7 +5,7 @@ task:
memory: 20
dockerfile: .cirrus/Dockerfile.ubuntu16.04
- build_script: mkdir build && cd build && cmake .. -DARCH=all -DBUILD_TESTS=on && make -j $(nproc)
+ build_script: mkdir build && cd build && cmake .. -DARCH=all+alpha -DOXIDE_INSTALL_PREFIX=$HOME/.cargo -DBUILD_TESTS=on && make -j3
submodule_script: git submodule sync --recursive && git submodule update --init --recursive
test_generic_script: cd build && ./nextpnr-generic-test
test_ice40_script: cd build && ./nextpnr-ice40-test
diff --git a/.cirrus/Dockerfile.ubuntu16.04 b/.cirrus/Dockerfile.ubuntu16.04
index 92ab2bae..a62cf4c6 100644
--- a/.cirrus/Dockerfile.ubuntu16.04
+++ b/.cirrus/Dockerfile.ubuntu16.04
@@ -9,7 +9,7 @@ RUN set -e -x ;\
build-essential autoconf cmake clang bison wget flex gperf \
libreadline-dev gawk tcl-dev libffi-dev graphviz xdot python3-dev \
libboost-all-dev qt5-default git libftdi-dev pkg-config libeigen3-dev \
- zlib1g-dev
+ zlib1g-dev curl
RUN set -e -x ;\
mkdir -p /usr/local/src ;\
@@ -52,3 +52,13 @@ RUN set -e -x ;\
cmake . ;\
make -j $(nproc) ;\
make install
+
+RUN set -e -x ;\
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y ;\
+ mkdir -p /usr/local/src ;\
+ cd /usr/local/src ;\
+ git clone --recursive https://github.com/daveshah1/prjoxide.git ;\
+ cd prjoxide ;\
+ git reset --hard 72dbb7973f31a30c3b9d18f3bac97caaea9a7f33 ;\
+ cd libprjoxide ;\
+ PATH=$PATH:$HOME/.cargo/bin cargo install --path prjoxide
diff --git a/.cirrus/archcheck.sh b/.cirrus/archcheck.sh
index fde19216..35f115d0 100755
--- a/.cirrus/archcheck.sh
+++ b/.cirrus/archcheck.sh
@@ -4,3 +4,4 @@ echo "Running archcheck!"
${BUILD_DIR}/nextpnr-ice40 --hx8k --package ct256 --test
${BUILD_DIR}/nextpnr-ice40 --up5k --package sg48 --test
${BUILD_DIR}/nextpnr-ecp5 --um5g-25k --package CABGA381 --test
+${BUILD_DIR}/nextpnr-nexus --device LIFCL-40-9BG400CES --test
diff --git a/.gitignore b/.gitignore
index ca7b6e7d..96e6e3cd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@
/nextpnr-generic*
/nextpnr-ice40*
/nextpnr-ecp5*
+/nextpnr-nexus*
cmake-build-*/
Makefile
cmake_install.cmake
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d9c18554..baddfb98 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -66,7 +66,9 @@ endif()
set(PROGRAM_PREFIX "" CACHE STRING "Name prefix for executables")
# List of families to build
-set(FAMILIES generic ice40 ecp5)
+set(FAMILIES generic ice40 ecp5 nexus)
+set(STABLE_FAMILIES generic ice40 ecp5)
+set(EXPERIMENTAL_FAMILIES nexus)
set(ARCH "" CACHE STRING "Architecture family for nextpnr build")
set_property(CACHE ARCH PROPERTY STRINGS ${FAMILIES})
@@ -75,14 +77,20 @@ if (NOT ARCH)
message(STATUS "Architecture needs to be set, set desired one with -DARCH=xxx")
message(STATUS "Supported architectures are :")
message(STATUS " all")
+ message(STATUS " all+alpha")
foreach(item ${FAMILIES})
message(STATUS " ${item}")
endforeach()
message(FATAL_ERROR "Architecture setting is mandatory")
endif ()
+if (ARCH STREQUAL "all+alpha")
+ SET(ARCH ${STABLE_FAMILIES} ${EXPERIMENTAL_FAMILIES})
+endif()
+
+
if (ARCH STREQUAL "all")
- SET(ARCH ${FAMILIES})
+ SET(ARCH ${STABLE_FAMILIES})
endif()
foreach(item ${ARCH})
@@ -286,6 +294,7 @@ endforeach (family)
file(GLOB_RECURSE CLANGFORMAT_FILES *.cc *.h)
string(REGEX REPLACE "[^;]*/ice40/chipdb/chipdb-[^;]*.cc" "" CLANGFORMAT_FILES "${CLANGFORMAT_FILES}")
string(REGEX REPLACE "[^;]*/ecp5/chipdb/chipdb-[^;]*.cc" "" CLANGFORMAT_FILES "${CLANGFORMAT_FILES}")
+string(REGEX REPLACE "[^;]*nexus/chipdb/chipdb-[^;]*.cc" "" CLANGFORMAT_FILES "${CLANGFORMAT_FILES}")
string(REGEX REPLACE "[^;]*/3rdparty[^;]*" "" CLANGFORMAT_FILES "${CLANGFORMAT_FILES}")
string(REGEX REPLACE "[^;]*/generated[^;]*" "" CLANGFORMAT_FILES "${CLANGFORMAT_FILES}")
diff --git a/README.md b/README.md
index 6a70e1e3..699e0c55 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,7 @@ tool.
Currently nextpnr supports:
* Lattice iCE40 devices supported by [Project IceStorm](http://www.clifford.at/icestorm/)
* Lattice ECP5 devices supported by [Project Trellis](https://github.com/YosysHQ/prjtrellis)
+ * Lattice Nexus devices supported by [Project Oxide](https://github.com/daveshah1/prjoxide)
* *(experimental)* a "generic" back-end for user-defined architectures
There is some work in progress towards [support for Xilinx devices](https://github.com/daveshah1/nextpnr-xilinx/) but it is not upstream and not intended for end users at the present time. We hope to see more FPGA families supported in the future. We would love your help in developing this awesome new project!
@@ -103,6 +104,19 @@ sudo make install
- Examples of the ECP5 flow for a range of boards can be found in the [Project Trellis Examples](https://github.com/YosysHQ/prjtrellis/tree/master/examples).
+### nextpnr-nexus
+
+For Nexus support, install [Project Oxide](https://github.com/daveshah1/prjoxide) to `$HOME/.cargo` or another location, which should be passed as `-DOXIDE_INSTALL_PREFIX=$HOME/.cargo` to CMake. Then build and install `nextpnr-nexus` using the following commands:
+
+```
+cmake . -DARCH=nexus -DOXIDE_INSTALL_PREFIX=$HOME/.cargo
+make -j$(nproc)
+sudo make install
+```
+
+ - Examples of the Nexus flow for a range of boards can be found in the [Project Oxide Examples](https://github.com/daveshah1/prjoxide/tree/master/examples).
+
+Nexus support is currently experimental, and has only been tested with engineering sample silicon.
### nextpnr-generic
@@ -126,7 +140,7 @@ make -j$(nproc)
sudo make install
```
-To build every available architecture, use `-DARCH=all`.
+To build every available stable architecture, use `-DARCH=all`. To include experimental arches (currently nexus), use `-DARCH=all+alpha`.
Pre-generating chip databases
-----------------------------
diff --git a/common/arch_pybindings_shared.h b/common/arch_pybindings_shared.h
index 3f7efcc1..8bb93a80 100644
--- a/common/arch_pybindings_shared.h
+++ b/common/arch_pybindings_shared.h
@@ -103,11 +103,6 @@ fn_wrapper_1a<Context, decltype(&Context::getPipDstWire), &Context::getPipDstWir
fn_wrapper_1a<Context, decltype(&Context::getPipDelay), &Context::getPipDelay, pass_through<DelayInfo>,
conv_from_str<PipId>>::def_wrap(ctx_cls, "getPipDelay");
-fn_wrapper_1a<Context, decltype(&Context::getPackagePinBel), &Context::getPackagePinBel, conv_to_str<BelId>,
- pass_through<std::string>>::def_wrap(ctx_cls, "getPackagePinBel");
-fn_wrapper_1a<Context, decltype(&Context::getBelPackagePin), &Context::getBelPackagePin, pass_through<std::string>,
- conv_from_str<BelId>>::def_wrap(ctx_cls, "getBelPackagePin");
-
fn_wrapper_0a<Context, decltype(&Context::getChipName), &Context::getChipName, pass_through<std::string>>::def_wrap(
ctx_cls, "getChipName");
fn_wrapper_0a<Context, decltype(&Context::archId), &Context::archId, conv_to_str<IdString>>::def_wrap(ctx_cls,
diff --git a/common/design_utils.cc b/common/design_utils.cc
index dd866758..16cc2710 100644
--- a/common/design_utils.cc
+++ b/common/design_utils.cc
@@ -30,6 +30,13 @@ void replace_port(CellInfo *old_cell, IdString old_name, CellInfo *rep_cell, IdS
if (!old_cell->ports.count(old_name))
return;
PortInfo &old = old_cell->ports.at(old_name);
+
+ // Create port on the replacement cell if it doesn't already exist
+ if (!rep_cell->ports.count(rep_name)) {
+ rep_cell->ports[rep_name].name = rep_name;
+ rep_cell->ports[rep_name].type = old.type;
+ }
+
PortInfo &rep = rep_cell->ports.at(rep_name);
NPNR_ASSERT(old.type == rep.type);
@@ -157,4 +164,33 @@ void rename_net(Context *ctx, NetInfo *net, IdString new_name)
net->name = new_name;
}
+void replace_bus(Context *ctx, CellInfo *old_cell, IdString old_name, int old_offset, bool old_brackets,
+ CellInfo *new_cell, IdString new_name, int new_offset, bool new_brackets, int width)
+{
+ for (int i = 0; i < width; i++) {
+ IdString old_port = ctx->id(stringf(old_brackets ? "%s[%d]" : "%s%d", old_name.c_str(ctx), i + old_offset));
+ IdString new_port = ctx->id(stringf(new_brackets ? "%s[%d]" : "%s%d", new_name.c_str(ctx), i + new_offset));
+ replace_port(old_cell, old_port, new_cell, new_port);
+ }
+}
+
+void copy_port(Context *ctx, CellInfo *old_cell, IdString old_name, CellInfo *new_cell, IdString new_name)
+{
+ if (!old_cell->ports.count(old_name))
+ return;
+ new_cell->ports[new_name].name = new_name;
+ new_cell->ports[new_name].type = old_cell->ports.at(old_name).type;
+ connect_port(ctx, old_cell->ports.at(old_name).net, new_cell, new_name);
+}
+
+void copy_bus(Context *ctx, CellInfo *old_cell, IdString old_name, int old_offset, bool old_brackets,
+ CellInfo *new_cell, IdString new_name, int new_offset, bool new_brackets, int width)
+{
+ for (int i = 0; i < width; i++) {
+ IdString old_port = ctx->id(stringf(old_brackets ? "%s[%d]" : "%s%d", old_name.c_str(ctx), i + old_offset));
+ IdString new_port = ctx->id(stringf(new_brackets ? "%s[%d]" : "%s%d", new_name.c_str(ctx), i + new_offset));
+ copy_port(ctx, old_cell, old_port, new_cell, new_port);
+ }
+}
+
NEXTPNR_NAMESPACE_END
diff --git a/common/design_utils.h b/common/design_utils.h
index 1ae1d648..6f52eb0c 100644
--- a/common/design_utils.h
+++ b/common/design_utils.h
@@ -82,6 +82,13 @@ template <typename F1> CellInfo *net_driven_by(const Context *ctx, const NetInfo
}
}
+// Check if a port is used
+inline bool port_used(CellInfo *cell, IdString port_name)
+{
+ auto port_fnd = cell->ports.find(port_name);
+ return port_fnd != cell->ports.end() && port_fnd->second.net != nullptr;
+}
+
// Connect a net to a port
void connect_port(const Context *ctx, NetInfo *net, CellInfo *cell, IdString port_name);
@@ -99,6 +106,17 @@ void rename_net(Context *ctx, NetInfo *net, IdString new_name);
void print_utilisation(const Context *ctx);
+// Disconnect a bus of nets (if connected) from old, and connect it to the new ports
+void replace_bus(Context *ctx, CellInfo *old_cell, IdString old_name, int old_offset, bool old_brackets,
+ CellInfo *new_cell, IdString new_name, int new_offset, bool new_brackets, int width);
+
+// Copy a bus of nets (if connected) from old, and connect it to the new ports
+void copy_bus(Context *ctx, CellInfo *old_cell, IdString old_name, int old_offset, bool old_brackets,
+ CellInfo *new_cell, IdString new_name, int new_offset, bool new_brackets, int width);
+
+// Copy a port from one cell to another
+void copy_port(Context *ctx, CellInfo *old_cell, IdString old_name, CellInfo *new_cell, IdString new_name);
+
NEXTPNR_NAMESPACE_END
#endif
diff --git a/common/nextpnr.cc b/common/nextpnr.cc
index 07b88471..9a856b99 100644
--- a/common/nextpnr.cc
+++ b/common/nextpnr.cc
@@ -506,7 +506,7 @@ void Context::check() const
}
}
}
-
+#ifdef CHECK_WIRES
for (auto w : getWires()) {
auto ni = getBoundWireNet(w);
if (ni != nullptr) {
@@ -514,7 +514,7 @@ void Context::check() const
CHECK_FAIL("wire '%s' missing in wires map of bound net '%s'\n", nameOfWire(w), nameOf(ni));
}
}
-
+#endif
for (auto &c : cells) {
auto ci = c.second.get();
if (c.first != ci->name)
diff --git a/common/router1.cc b/common/router1.cc
index 946327d2..d2816c1e 100644
--- a/common/router1.cc
+++ b/common/router1.cc
@@ -469,6 +469,20 @@ struct Router1
}
}
+ // special case
+
+ if (src_wire == dst_wire) {
+ NetInfo *bound = ctx->getBoundWireNet(src_wire);
+ if (bound != nullptr)
+ NPNR_ASSERT(bound == net_info);
+ else {
+ ctx->bindWire(src_wire, net_info, STRENGTH_WEAK);
+ }
+ arc_to_wires[arc].insert(src_wire);
+ wire_to_arcs[src_wire].insert(arc);
+ return true;
+ }
+
// reset wire queue
if (!queue.empty()) {
diff --git a/common/router2.cc b/common/router2.cc
index 4dfd868b..15c97e52 100644
--- a/common/router2.cc
+++ b/common/router2.cc
@@ -774,8 +774,11 @@ struct Router2
if (dst == WireId() || ctx->getBoundWireNet(dst) == net)
return true;
// Skip routes where there is no routing (special cases)
- if (!ad.routed)
+ if (!ad.routed) {
+ if ((src == dst) && ctx->getBoundWireNet(dst) != net)
+ ctx->bindWire(src, net, STRENGTH_WEAK);
return true;
+ }
WireId cursor = dst;
diff --git a/common/util.h b/common/util.h
index 9512bd40..07ebac75 100644
--- a/common/util.h
+++ b/common/util.h
@@ -112,6 +112,24 @@ template <typename K, typename V> std::map<K, V *> sorted(const std::unordered_m
return retVal;
};
+// Wrap an unordered_map, and allow it to be iterated over sorted by key
+template <typename K, typename V> std::map<K, V &> sorted_ref(std::unordered_map<K, V> &orig)
+{
+ std::map<K, V &> retVal;
+ for (auto &item : orig)
+ retVal.emplace(std::make_pair(item.first, std::ref(item.second)));
+ return retVal;
+};
+
+// Wrap an unordered_map, and allow it to be iterated over sorted by key
+template <typename K, typename V> std::map<K, const V &> sorted_cref(const std::unordered_map<K, V> &orig)
+{
+ std::map<K, const V &> retVal;
+ for (auto &item : orig)
+ retVal.emplace(std::make_pair(item.first, std::ref(item.second)));
+ return retVal;
+};
+
// Wrap an unordered_set, and allow it to be iterated over sorted by key
template <typename K> std::set<K> sorted(const std::unordered_set<K> &orig)
{
diff --git a/docs/nexus.md b/docs/nexus.md
new file mode 100644
index 00000000..99ac926f
--- /dev/null
+++ b/docs/nexus.md
@@ -0,0 +1,20 @@
+# nextpnr-nexus notes
+
+### Constraints
+
+Currently the following PDC constraint styles are supported for IO constraints:
+
+```
+ldc_set_location -site {G13} [get_ports gsrn]
+ldc_set_port -iobuf {IO_TYPE=LVCMOS33} [get_ports {led[0]}]
+```
+
+Timing constraints are currently ignored, but should be expected to be supported soon.
+
+### Command Line
+
+A full device name is specified on the command line. It should be postfixed with 'ES' if using an engineering sample device to ensure correct use of the ES IDCODE.
+
+```
+--device LIFCL-40-9BG400CES
+```
diff --git a/gui/nexus/family.cmake b/gui/nexus/family.cmake
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/gui/nexus/family.cmake
diff --git a/gui/nexus/mainwindow.cc b/gui/nexus/mainwindow.cc
new file mode 100644
index 00000000..846284c4
--- /dev/null
+++ b/gui/nexus/mainwindow.cc
@@ -0,0 +1,87 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2018 Miodrag Milanovic <miodrag@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.
+ *
+ */
+
+#include "mainwindow.h"
+#include <QAction>
+#include <QFileDialog>
+#include <QFileInfo>
+#include <QIcon>
+#include <QInputDialog>
+#include <QLineEdit>
+#include <QSet>
+#include <fstream>
+#include "design_utils.h"
+#include "log.h"
+
+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();
+
+ std::string title = "nextpnr-nexus - [EMPTY]";
+ setWindowTitle(title.c_str());
+
+ connect(this, &BaseMainWindow::contextChanged, this, &MainWindow::newContext);
+
+ createMenu();
+}
+
+MainWindow::~MainWindow() {}
+
+void MainWindow::createMenu()
+{
+ // Add arch specific actions
+
+ // Add actions in menus
+}
+
+void MainWindow::new_proj()
+{
+ QStringList arch;
+
+ // TODO: better device picker
+ arch.push_back("LIFCL-40-9BG400CES");
+ arch.push_back("LIFCL-40-8BG72CES");
+
+ bool ok;
+ QString item = QInputDialog::getItem(this, "Select new context", "Chip:", arch, 0, false, &ok);
+ if (ok && !item.isEmpty()) {
+ ArchArgs chipArgs;
+ chipArgs.device = item.toStdString();
+ ctx = std::unique_ptr<Context>(new Context(chipArgs));
+ actionLoadJSON->setEnabled(true);
+ Q_EMIT contextChanged(ctx.get());
+ }
+}
+
+void MainWindow::newContext(Context *ctx)
+{
+ std::string title = "nextpnr-nexus - " + ctx->getChipName();
+ setWindowTitle(title.c_str());
+}
+
+void MainWindow::onDisableActions() {}
+
+void MainWindow::onUpdateActions() {}
+
+NEXTPNR_NAMESPACE_END
diff --git a/gui/nexus/mainwindow.h b/gui/nexus/mainwindow.h
new file mode 100644
index 00000000..13556fa1
--- /dev/null
+++ b/gui/nexus/mainwindow.h
@@ -0,0 +1,49 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2018 Miodrag Milanovic <miodrag@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 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();
+
+ void onDisableActions() override;
+ void onUpdateActions() override;
+
+ protected Q_SLOTS:
+ void new_proj() override;
+
+ void newContext(Context *ctx);
+};
+
+NEXTPNR_NAMESPACE_END
+
+#endif // MAINWINDOW_H
diff --git a/gui/nexus/nextpnr.qrc b/gui/nexus/nextpnr.qrc
new file mode 100644
index 00000000..03585ec0
--- /dev/null
+++ b/gui/nexus/nextpnr.qrc
@@ -0,0 +1,2 @@
+<RCC>
+</RCC>
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