Merge pull request #199 from eddiehung/xc7

Unofficial xc7 support
diff --git a/.gitignore b/.gitignore
index 9b3ffd0..bf22d03 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@
 /nextpnr-generic*
 /nextpnr-ice40*
 /nextpnr-ecp5*
+/nextpnr-xc7*
 cmake-build-*/
 Makefile
 cmake_install.cmake
@@ -29,3 +30,22 @@
 /ImportExecutables.cmake
 *-coverage/
 *-coverage.info
+
+# nextpnr-xc7
+*.xdl
+*.bit
+
+# ise
+_xmsgs/
+*.bgn
+*.drc
+*.ncd
+*.xwbt
+usage_statistics_webtalk.html
+webtalk.log
+xilinx_device_details.xml
+*_fpga_editor.*
+*.twr
+*.twx
+*.nlf
+*.sdf
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..c0b8228
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "torc"]
+	path = torc
+	url = https://github.com/eddiehung/torc
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 33a703d..c75a32f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -17,7 +17,7 @@
 endif()
 
 # List of families to build
-set(FAMILIES generic ice40 ecp5)
+set(FAMILIES generic ice40 ecp5 xc7)
 
 set(ARCH "" CACHE STRING "Architecture family for nextpnr build")
 set_property(CACHE ARCH PROPERTY STRINGS ${FAMILIES})
@@ -120,9 +120,10 @@
     # Original source: https://github.com/BVLC/caffe/blob/master/cmake/Dependencies.cmake#L148
     set(version ${PYTHONLIBS_VERSION_STRING})
 
-    STRING(REGEX REPLACE "[^0-9]" "" boost_py_version ${version})
+    STRING(REGEX REPLACE "[^0-9]" "" boost_py_version ${version})    
     find_package(Boost QUIET COMPONENTS "python-py${boost_py_version}" ${boost_libs})
     set(Boost_PYTHON_FOUND ${Boost_PYTHON-PY${boost_py_version}_FOUND})
+    set(boost_python_lib "python-py${boost_py_version}")
 
     while (NOT "${version}" STREQUAL "" AND NOT Boost_PYTHON_FOUND)
         STRING(REGEX REPLACE "([0-9.]+).[0-9]+" "\\1" version ${version})
@@ -130,6 +131,7 @@
         STRING(REGEX REPLACE "[^0-9]" "" boost_py_version ${version})
         find_package(Boost QUIET COMPONENTS "python-py${boost_py_version}" ${boost_libs})
         set(Boost_PYTHON_FOUND ${Boost_PYTHON-PY${boost_py_version}_FOUND})
+        set(boost_python_lib "python-py${boost_py_version}")
 
         STRING(REGEX MATCHALL "([0-9.]+).[0-9]+" has_more_version ${version})
         if ("${has_more_version}" STREQUAL "")
@@ -139,6 +141,7 @@
 
     if (NOT Boost_PYTHON_FOUND)
         find_package(Boost QUIET COMPONENTS python3 ${boost_libs})
+        set(boost_python_lib python3)
         if ("${Boost_LIBRARIES}" MATCHES ".*(python|PYTHON).*" )
             set(Boost_PYTHON_FOUND TRUE)
         endif ()
@@ -146,6 +149,7 @@
 
     if (NOT Boost_PYTHON_FOUND)
         find_package(Boost QUIET COMPONENTS python36 ${boost_libs})
+        set(boost_python_lib python36)
         if ("${Boost_LIBRARIES}" MATCHES ".*(python|PYTHON).*" )
             set(Boost_PYTHON_FOUND TRUE)
         endif ()
@@ -153,6 +157,7 @@
 
     if (NOT Boost_PYTHON_FOUND)
         find_package(Boost QUIET COMPONENTS python37 ${boost_libs})
+        set(boost_python_lib python37)
         if ("${Boost_LIBRARIES}" MATCHES ".*(python|PYTHON).*" )
             set(Boost_PYTHON_FOUND TRUE)
         endif ()
@@ -161,6 +166,7 @@
     if (NOT Boost_PYTHON_FOUND)
         STRING(REGEX REPLACE "([0-9]+\\.[0-9]+).*" "\\1" gentoo_version ${PYTHONLIBS_VERSION_STRING})
         find_package(Boost QUIET COMPONENTS python-${gentoo_version} ${boost_libs})
+        set(boost_python_lib python-${gentoo_version})
         if ("${Boost_LIBRARIES}" MATCHES ".*(python|PYTHON).*" )
             set(Boost_PYTHON_FOUND TRUE)
         endif ()
diff --git a/README.md b/README.md
index 5b79d1f..5733a40 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,19 @@
 nextpnr -- a portable FPGA place and route tool
 ===============================================
 
+***
+### NB: This nextpnr-xc7 branch is *unofficial*, *very* proof-of-concept, *very* experimental, *very* unoptimised, and is provided with *no support whatsoever*. Use at your own risk!
+#### It leverages a [torc](https://github.com/torc-isi/torc) fork with minimal changes (those necessary to support building on later versions of gcc) to target XDL-compatible devices.
+### Note that torc is licensed under GPLv3 which differs from nextpnr's ISC license, thus please respect the limitations imposed by both licenses.
+Currently, only LUT1-6, IOB, BUFGCTRL, MMCME2_ADV are supported for xc7z020 and xc7vx680t (but trivial to add others).
+The following example shell scripts are available:
+* blinky.sh -- generates blinky.bit that flashes (with a delay) the 4 LEDs on a ZYBO Z7
+* blinky_sim.sh -- post place-and-route simulation, without any delays (requires [GHDL](https://github.com/ghdl/ghdl))
+* picorv32.sh -- just places-and-routes picorv32.ncd (no testbench)
+* attosoc.sh -- generates attosoc.bit of a self-stimulating picorv32 device that displays (with a delay) prime numbers to the LEDs -- when testing on hardware, consider using a PLL (MMCM) to meet timing
+* attosoc_sim.sh -- post place-and-route simulation of a self-stimulating picorv32 device, without any delays (requires [GHDL](https://github.com/ghdl/ghdl))
+***
+
 nextpnr aims to be a vendor neutral, timing driven, FOSS FPGA place and route
 tool.
 
diff --git a/common/place_common.cc b/common/place_common.cc
index b3eb426..2a18a1a 100644
--- a/common/place_common.cc
+++ b/common/place_common.cc
@@ -37,6 +37,8 @@
     if (driver_gb)
         return 0;
     int clock_count;
+    if (ctx->getPortTimingClass(driver_cell, net->driver.port, clock_count) == TMG_IGNORE)
+	return 0;
     bool timing_driven = ctx->timing_driven && type == MetricType::COST &&
                          ctx->getPortTimingClass(driver_cell, net->driver.port, clock_count) != TMG_IGNORE;
     delay_t negative_slack = 0;
diff --git a/common/router1.cc b/common/router1.cc
index cbc0df9..2c3f668 100644
--- a/common/router1.cc
+++ b/common/router1.cc
@@ -512,6 +512,21 @@
                 WireId next_wire = ctx->getPipDstWire(pip);
                 next_delay += ctx->getWireDelay(next_wire).maxDelay();
 
+#ifdef ARCH_XC7
+                // For BUFG routing, do not exit the global network until the destination tile is reached
+                if (ctx->isGlobalNet(net_info)) {
+                    if (torc_info->wire_is_global[src_wire.index] && !torc_info->wire_is_global[next_wire.index]) {
+                        const auto &arc = torc_info->pip_to_arc[pip.index];
+                        const auto &next_tw = arc.getSinkTilewire();
+                        const auto &next_loc = torc_info->tile_to_xy[next_tw.getTileIndex()];
+                        const auto &dst_tw = torc_info->wire_to_tilewire[dst_wire.index];
+                        const auto &dst_loc = torc_info->tile_to_xy[dst_tw.getTileIndex()];
+                        if (next_loc.second != dst_loc.second || next_loc.first != dst_loc.first)
+                            continue;
+                    }
+                }
+#endif
+
                 WireId conflictWireWire = WireId(), conflictPipWire = WireId();
                 NetInfo *conflictWireNet = nullptr, *conflictPipNet = nullptr;
 
diff --git a/gui/application.cc b/gui/application.cc
index 7751e6f..738b7c6 100644
--- a/gui/application.cc
+++ b/gui/application.cc
@@ -51,9 +51,9 @@
 bool Application::notify(QObject *receiver, QEvent *event)
 {
     bool retVal = true;
-    try {
+    //try {
         retVal = QApplication::notify(receiver, event);
-    } catch (assertion_failure ex) {
+    /*} catch (assertion_failure ex) {
         QString msg;
         QTextStream out(&msg);
         out << ex.filename.c_str() << " at " << ex.line << "\n";
@@ -61,7 +61,7 @@
         QMessageBox::critical(0, "Error", msg);
     } catch (...) {
         QMessageBox::critical(0, "Error", "Fatal error !!!");
-    }
+    }*/
     return retVal;
 }
 
diff --git a/gui/designwidget.cc b/gui/designwidget.cc
index 235dd2c..958d884 100644
--- a/gui/designwidget.cc
+++ b/gui/designwidget.cc
@@ -318,6 +318,12 @@
                 wireMap[std::pair<int, int>(wire.location.x, wire.location.y)].push_back(wire);

             }

 #endif

+#ifdef ARCH_XC7

+            for (const auto &wire : ctx->getWires()) {

+                const auto loc = torc_info->wire_to_loc(wire.index);

+                wireMap[std::pair<int, int>(loc.x, loc.y)].push_back(wire);

+            }

+#endif

             auto wireGetter = [](Context *ctx, WireId id) { return ctx->getWireName(id); };

             getTreeByElementType(ElementType::WIRE)

                     ->loadData(ctx,

@@ -706,8 +712,9 @@
         addProperty(topItem, QVariant::String, "Type", ctx->getPipType(pip).c_str(ctx));

         addProperty(topItem, QVariant::Bool, "Available", ctx->checkPipAvail(pip));

         addProperty(topItem, QVariant::String, "Bound Net", ctx->nameOf(ctx->getBoundPipNet(pip)), ElementType::NET);

+        WireId conflict = ctx->getConflictingPipWire(pip);

         addProperty(topItem, QVariant::String, "Conflicting Wire",

-                    ctx->getWireName(ctx->getConflictingPipWire(pip)).c_str(ctx), ElementType::WIRE);

+                    (conflict!=WireId() ? ctx->getWireName(conflict).c_str(ctx) : ""), ElementType::WIRE);

         addProperty(topItem, QVariant::String, "Conflicting Net", ctx->nameOf(ctx->getConflictingPipNet(pip)),

                     ElementType::NET);

         addProperty(topItem, QVariant::String, "Src Wire", ctx->getWireName(ctx->getPipSrcWire(pip)).c_str(ctx),

diff --git a/gui/xc7/family.cmake b/gui/xc7/family.cmake
new file mode 100644
index 0000000..3b010c7
--- /dev/null
+++ b/gui/xc7/family.cmake
@@ -0,0 +1 @@
+include_directories(/opt/torc/src)
\ No newline at end of file
diff --git a/gui/xc7/mainwindow.cc b/gui/xc7/mainwindow.cc
new file mode 100644
index 0000000..3ee6472
--- /dev/null
+++ b/gui/xc7/mainwindow.cc
@@ -0,0 +1,51 @@
+/*

+ *  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"

+

+static void initMainResource() { Q_INIT_RESOURCE(nextpnr); }

+

+NEXTPNR_NAMESPACE_BEGIN

+

+MainWindow::MainWindow(std::unique_ptr<Context> context, ArchArgs args, QWidget *parent)

+        : BaseMainWindow(std::move(context), args, parent)

+{

+    initMainResource();

+

+    std::string title = "nextpnr-xc7 - [EMPTY]";

+    setWindowTitle(title.c_str());

+

+    connect(this, &BaseMainWindow::contextChanged, this, &MainWindow::newContext);

+

+    createMenu();

+}

+

+MainWindow::~MainWindow() {}

+

+void MainWindow::newContext(Context *ctx)

+{

+    std::string title = "nextpnr-xc7 - " + ctx->getChipName();

+    setWindowTitle(title.c_str());

+}

+

+void MainWindow::createMenu() {}

+

+void MainWindow::new_proj() {}

+

+NEXTPNR_NAMESPACE_END

diff --git a/gui/xc7/mainwindow.h b/gui/xc7/mainwindow.h
new file mode 100644
index 0000000..bb6a4cf
--- /dev/null
+++ b/gui/xc7/mainwindow.h
@@ -0,0 +1,45 @@
+/*

+ *  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, ArchArgs args, QWidget *parent = 0);

+    virtual ~MainWindow();

+

+  public:

+    void createMenu();

+

+  protected Q_SLOTS:

+    void new_proj() override;

+    void newContext(Context *ctx);

+};

+

+NEXTPNR_NAMESPACE_END

+

+#endif // MAINWINDOW_H

diff --git a/gui/xc7/nextpnr.qrc b/gui/xc7/nextpnr.qrc
new file mode 100644
index 0000000..03585ec
--- /dev/null
+++ b/gui/xc7/nextpnr.qrc
@@ -0,0 +1,2 @@
+<RCC>
+</RCC>
diff --git a/torc b/torc
new file mode 160000
index 0000000..a5a4ec0
--- /dev/null
+++ b/torc
@@ -0,0 +1 @@
+Subproject commit a5a4ec057eae3ed07ed2acb9da13404808e37211
diff --git a/xc7/arch.cc b/xc7/arch.cc
new file mode 100644
index 0000000..c4178b0
--- /dev/null
+++ b/xc7/arch.cc
@@ -0,0 +1,904 @@
+/*
+ *  nextpnr -- Next Generation Place and Route
+ *
+ *  Copyright (C) 2018  Clifford Wolf <clifford@symbioticeda.com>
+ *  Copyright (C) 2018  Serge Bazanski <q3k@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 <algorithm>
+#include <cmath>
+#include <regex>
+#include "cells.h"
+#include "gfx.h"
+#include "log.h"
+#include "nextpnr.h"
+#include "placer1.h"
+#include "router1.h"
+#include "util.h"
+
+#include "torc/common/DirectoryTree.hpp"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+std::unique_ptr<const TorcInfo> torc_info;
+TorcInfo::TorcInfo(BaseCtx *ctx, const std::string &inDeviceName, const std::string &inPackageName)
+        : TorcInfo(inDeviceName, inPackageName)
+{
+    static const std::regex re_loc(".+_X(\\d+)Y(\\d+)");
+    std::cmatch what;
+    tile_to_xy.resize(tiles.getTileCount());
+    for (TileIndex tileIndex(0); tileIndex < tiles.getTileCount(); tileIndex++) {
+         const auto &tileInfo = tiles.getTileInfo(tileIndex);
+        if (!std::regex_match(tileInfo.getName(), what, re_loc))
+            throw;
+        const auto x = boost::lexical_cast<int>(what.str(1));
+        const auto y = boost::lexical_cast<int>(what.str(2));
+        tile_to_xy[tileIndex] = std::make_pair(x,y);
+    }
+
+    bel_to_site_index.reserve(sites.getSiteCount() * 4);
+    bel_to_loc.reserve(sites.getSiteCount() * 4);
+    site_index_to_bel.resize(sites.getSiteCount());
+    site_index_to_type.resize(sites.getSiteCount());
+    BelId b;
+    b.index = 0;
+    for (SiteIndex i(0); i < sites.getSiteCount(); ++i) {
+        const auto &site = sites.getSite(i);
+        const auto &pd = site.getPrimitiveDefPtr();
+        const auto &type = pd->getName();
+        int x, y;
+        std::tie(x,y) = tile_to_xy[site.getTileIndex()];
+
+        if (type == "SLICEL" || type == "SLICEM") {
+            bel_to_site_index.push_back(i);
+            bel_to_site_index.push_back(i);
+            bel_to_site_index.push_back(i);
+            bel_to_site_index.push_back(i);
+            site_index_to_type[i] = id_SLICE_LUT6;
+            const auto site_name = site.getName();
+            if (!std::regex_match(site_name.c_str(), what, re_loc))
+                throw;
+            const auto sx = boost::lexical_cast<int>(what.str(1));
+            if ((sx & 1) == 0) {
+                bel_to_loc.emplace_back(x, y, 0);
+                bel_to_loc.emplace_back(x, y, 1);
+                bel_to_loc.emplace_back(x, y, 2);
+                bel_to_loc.emplace_back(x, y, 3);
+            } else {
+                bel_to_loc.emplace_back(x, y, 4);
+                bel_to_loc.emplace_back(x, y, 5);
+                bel_to_loc.emplace_back(x, y, 6);
+                bel_to_loc.emplace_back(x, y, 7);
+            }
+            site_index_to_bel[i] = b;
+            b.index += 4;
+        } else if (type == "IOB33S" || type == "IOB33M") {
+            bel_to_site_index.push_back(i);
+            site_index_to_type[i] = id_IOB33;
+            // TODO: Fix z when two IOBs on same tile
+            bel_to_loc.emplace_back(x, y, 0);
+            site_index_to_bel[i] = b;
+            ++b.index;
+        } else if (type == "IOB18S" || type == "IOB18M") {
+            bel_to_site_index.push_back(i);
+            site_index_to_type[i] = id_IOB18;
+            // TODO: Fix z when two IOBs on same tile
+            bel_to_loc.emplace_back(x, y, 0);
+            site_index_to_bel[i] = b;
+            ++b.index;
+        } else {
+            bel_to_site_index.push_back(i);
+            site_index_to_type[i] = ctx->id(type);
+            bel_to_loc.emplace_back(x, y, 0);
+            site_index_to_bel[i] = b;
+            ++b.index;
+        }
+    }
+    num_bels = bel_to_site_index.size();
+    bel_to_site_index.shrink_to_fit();
+    bel_to_loc.shrink_to_fit();
+
+    const std::regex re_124("(.+_)?[NESW][NESWLR](\\d)((BEG(_[NS])?)|(END(_[NS])?)|[A-E])?\\d(_\\d)?");
+    const std::regex re_L("(.+_)?L(H|V|VB)(_L)?\\d+(_\\d)?");
+    const std::regex re_BYP("BYP(_ALT)?\\d");
+    const std::regex re_BYP_B("BYP_[BL]\\d");
+    const std::regex re_BOUNCE_NS("(BYP|FAN)_BOUNCE_[NS]3_\\d");
+    const std::regex re_FAN("FAN(_ALT)?\\d");
+    const std::regex re_CLB_I1_6("CLBL[LM]_(L|LL|M)_[A-D]([1-6])");
+    const std::regex bufg_i("CLK_BUFG_BUFGCTRL\\d+_I0");
+    const std::regex bufg_o("CLK_BUFG_BUFGCTRL\\d+_O");
+    const std::regex hrow("CLK_HROW_CLK[01]_[34]");
+    std::unordered_map</*TileTypeIndex*/ unsigned, std::vector<delay_t>> delay_lookup;
+    std::unordered_map<Segments::SegmentReference, TileIndex> segment_to_anchor;
+    Tilewire currentTilewire;
+    WireId w;
+    w.index = 0;
+    for (TileIndex tileIndex(0); tileIndex < tiles.getTileCount(); tileIndex++) {
+        // iterate over every wire in the tile
+        const auto &tileInfo = tiles.getTileInfo(tileIndex);
+        auto tileTypeIndex = tileInfo.getTypeIndex();
+        auto wireCount = tiles.getWireCount(tileTypeIndex);
+        currentTilewire.setTileIndex(tileIndex);
+        for (WireIndex wireIndex(0); wireIndex < wireCount; wireIndex++) {
+            currentTilewire.setWireIndex(wireIndex);
+            const auto &currentSegment = segments.getTilewireSegment(currentTilewire);
+
+            if (!currentSegment.isTrivial()) {
+                auto r = segment_to_anchor.emplace(currentSegment, currentSegment.getAnchorTileIndex());
+                if (r.second) {
+                    TilewireVector segment;
+                    const_cast<DDB &>(*ddb).expandSegment(currentTilewire, segment, DDB::eExpandDirectionNone);
+                    // expand all of the arcs
+                    TilewireVector::const_iterator sep = segment.begin();
+                    TilewireVector::const_iterator see = segment.end();
+                    while(sep < see) {
+                        // expand the tilewire sinks
+                        const Tilewire& tilewire = *sep++;
+
+                        const auto &tileInfo = tiles.getTileInfo(tilewire.getTileIndex());
+                        const auto &tileTypeName = tiles.getTileTypeName(tileInfo.getTypeIndex());
+                        if (boost::starts_with(tileTypeName, "INT") || boost::starts_with(tileTypeName, "CLB")) {
+                            r.first->second = tilewire.getTileIndex();
+                            break;
+                        }
+                    }
+                }
+                if (r.first->second != tileIndex)
+                    continue;
+
+                segment_to_wire.emplace(currentSegment, w);
+            } else
+                trivial_to_wire.emplace(currentTilewire, w);
+
+            wire_to_tilewire.push_back(currentTilewire);
+
+            auto it = delay_lookup.find(tileTypeIndex);
+            if (it == delay_lookup.end()) {
+                auto wireCount = tiles.getWireCount(tileTypeIndex);
+                std::vector<delay_t> tile_delays(wireCount);
+                for (WireIndex wireIndex(0); wireIndex < wireCount; wireIndex++) {
+                    const WireInfo &wireInfo = tiles.getWireInfo(tileTypeIndex, wireIndex);
+                    auto wire_name = wireInfo.getName();
+                    if (std::regex_match(wire_name, what, re_124)) {
+                        switch (what.str(2)[0]) {
+                        case '1':
+                            tile_delays[wireIndex] = 150;
+                            break;
+                        case '2':
+                            tile_delays[wireIndex] = 170;
+                            break;
+                        case '4':
+                            tile_delays[wireIndex] = 210;
+                            break;
+                        case '6':
+                            tile_delays[wireIndex] = 210;
+                            break;
+                        default:
+                            throw;
+                        }
+                    } else if (std::regex_match(wire_name, what, re_L)) {
+                        std::string l(what[2]);
+                        if (l == "H")
+                            tile_delays[wireIndex] = 360;
+                        else if (l == "VB")
+                            tile_delays[wireIndex] = 300;
+                        else if (l == "V")
+                            tile_delays[wireIndex] = 350;
+                        else
+                            throw;
+                    } else if (std::regex_match(wire_name, what, re_BYP)) {
+                        tile_delays[wireIndex] = 190;
+                    } else if (std::regex_match(wire_name, what, re_BYP_B)) {
+                    } else if (std::regex_match(wire_name, what, re_FAN)) {
+                        tile_delays[wireIndex] = 190;
+                    } else if (std::regex_match(wire_name, what, re_CLB_I1_6)) {
+                        switch (what.str(2)[0]) {
+                        case '1':
+                            tile_delays[wireIndex] = 280;
+                            break;
+                        case '2':
+                            tile_delays[wireIndex] = 280;
+                            break;
+                        case '3':
+                            tile_delays[wireIndex] = 180;
+                            break;
+                        case '4':
+                            tile_delays[wireIndex] = 180;
+                            break;
+                        case '5':
+                            tile_delays[wireIndex] = 80;
+                            break;
+                        case '6':
+                            tile_delays[wireIndex] = 40;
+                            break;
+                        default:
+                            throw;
+                        }
+                    }
+                }
+                it = delay_lookup.emplace(tileTypeIndex, std::move(tile_delays)).first;
+            }
+            assert(it != delay_lookup.end());
+
+            DelayInfo d;
+            d.delay = it->second[currentTilewire.getWireIndex()];
+            wire_to_delay.emplace_back(std::move(d));
+
+            ++w.index;
+        }
+    }
+    segment_to_anchor.clear();
+    wire_to_tilewire.shrink_to_fit();
+    wire_to_delay.shrink_to_fit();
+    num_wires = wire_to_tilewire.size();
+    wire_is_global.resize(num_wires);
+
+    wire_to_pips_downhill.resize(num_wires);
+    // std::unordered_map<Arc, int> arc_to_pip;
+    ArcVector arcs;
+    ExtendedWireInfo ewi(*ddb);
+    PipId p;
+    p.index = 0;
+    for (w.index = 0; w.index < num_wires; ++w.index) {
+        const auto &currentTilewire = wire_to_tilewire[w.index];
+        if (currentTilewire.isUndefined())
+            continue;
+
+        const auto &tileInfo = tiles.getTileInfo(currentTilewire.getTileIndex());
+        const auto tileTypeName = tiles.getTileTypeName(tileInfo.getTypeIndex());
+        const bool clb = boost::starts_with(
+                tileTypeName, "CLB"); // Disable all CLB route-throughs (i.e. LUT in->out, LUT A->AMUX, for now)
+
+        auto &pips = wire_to_pips_downhill[w.index];
+        const bool clk_tile = boost::starts_with(tileTypeName, "CLK");
+
+        bool global_tile = false;
+
+        arcs.clear();
+        //const_cast<DDB &>(*ddb).expandSegmentSinks(currentTilewire, arcs, DDB::eExpandDirectionNone,
+        //                                           false /* inUseTied */, true /*inUseRegular */,
+        //                                           true /* inUseIrregular */, !clb /* inUseRoutethrough */);
+        {
+            // expand the segment
+            TilewireVector segment;
+            const_cast<DDB &>(*ddb).expandSegment(currentTilewire, segment, DDB::eExpandDirectionNone);
+            // expand all of the arcs
+            TilewireVector::const_iterator sep = segment.begin();
+            TilewireVector::const_iterator see = segment.end();
+            while(sep < see) {
+                // expand the tilewire sinks
+                const Tilewire& tilewire = *sep++;
+
+                const auto &tileInfo = tiles.getTileInfo(tilewire.getTileIndex());
+                const auto &tileTypeName = tiles.getTileTypeName(tileInfo.getTypeIndex());
+                global_tile = global_tile || boost::starts_with(tileTypeName, "CLK") || boost::starts_with(tileTypeName, "HCLK") || boost::starts_with(tileTypeName, "CFG");
+
+                TilewireVector sinks;
+                const_cast<DDB &>(*ddb).expandTilewireSinks(tilewire, sinks, false /*inUseTied*/, true /*inUseRegular*/, true /*inUseIrregular*/,
+                        !clb /* inUseRoutethrough */);
+                // rewrite the sinks as arcs
+                TilewireVector::const_iterator sip = sinks.begin();
+                TilewireVector::const_iterator sie = sinks.end();
+                while(sip < sie) {
+                    Arc a(tilewire, *sip++);
+
+                    // Disable BUFG I0 -> O routethrough
+                    if (clk_tile) {
+                        ewi.set(a.getSourceTilewire());
+                        if (std::regex_match(ewi.mWireName, bufg_i)) {
+                            ewi.set(a.getSinkTilewire());
+                            if (std::regex_match(ewi.mWireName, bufg_o))
+                                continue;
+                        }
+                    }
+
+                    // Disable entering HROW from INT_[LR].CLK[01]
+                    if (boost::starts_with(tileTypeName, "CLK_HROW")) {
+                        ewi.set(a.getSourceTilewire());
+                        if (std::regex_match(ewi.mWireName, hrow))
+                            continue;
+                    }
+
+                    pips.emplace_back(p);
+                    pip_to_arc.emplace_back(a);
+                    // arc_to_pip.emplace(a, p.index);
+                    ++p.index;
+                }
+            }
+        }
+        pips.shrink_to_fit();
+
+        if (global_tile)
+            wire_is_global[w.index] = true;
+    }
+    pip_to_arc.shrink_to_fit();
+    num_pips = pip_to_arc.size();
+
+    height = (int)tiles.getRowCount();
+    width = (int)tiles.getColCount();
+}
+TorcInfo::TorcInfo(const std::string& inDeviceName, const std::string &inPackageName)
+    : ddb(new DDB(inDeviceName, inPackageName)), sites(ddb->getSites()), tiles(ddb->getTiles()),
+          segments(ddb->getSegments())
+{
+}
+
+// -----------------------------------------------------------------------
+
+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)
+{
+    torc::common::DirectoryTree directoryTree("/opt/torc/src/torc");
+    if (args.type == ArchArgs::Z020) {
+        torc_info = std::unique_ptr<TorcInfo>(new TorcInfo(this, "xc7z020", args.package));
+    } else if (args.type == ArchArgs::VX980) {
+        torc_info = std::unique_ptr<TorcInfo>(new TorcInfo(this, "xc7vx980t", args.package));
+    } else {
+        log_error("Unsupported XC7 chip type.\n");
+    }
+
+        width = torc_info->width;
+        height = torc_info->height;
+    /*if (getCtx()->verbose)*/ {
+        log_info("Number of bels:  %d\n", torc_info->num_bels);
+        log_info("Number of wires: %d\n", torc_info->num_wires);
+        log_info("Number of pips:  %d\n", torc_info->num_pips);
+    }
+
+    bel_to_cell.resize(torc_info->num_bels);
+    wire_to_net.resize(torc_info->num_wires);
+    pip_to_net.resize(torc_info->num_pips);
+}
+
+// -----------------------------------------------------------------------
+
+std::string Arch::getChipName() const
+{
+    if (args.type == ArchArgs::Z020) {
+        return "z020";
+    } else if (args.type == ArchArgs::VX980) {
+        return "vx980";
+    } else {
+        log_error("Unsupported XC7 chip type.\n");
+    }
+}
+
+// -----------------------------------------------------------------------
+
+IdString Arch::archArgsToId(ArchArgs args) const
+{
+    if (args.type == ArchArgs::Z020)
+        return id("z020");
+    if (args.type == ArchArgs::VX980)
+        return id("vx980");
+    return IdString();
+}
+
+// -----------------------------------------------------------------------
+
+static bool endsWith(const std::string& str, const std::string& suffix)
+{
+    return str.size() >= suffix.size() && 0 == str.compare(str.size()-suffix.size(), suffix.size(), suffix);
+}
+
+BelId Arch::getBelByName(IdString name) const
+{
+    std::string n = name.str(this);
+    int ndx = 0;
+    if (endsWith(n,"_A") || endsWith(n,"_B") || endsWith(n,"_C") || endsWith(n,"_D"))
+    {
+        ndx = (int)(n.back() - 'A');
+        n = n.substr(0,n.size()-2);
+    }
+    auto it = torc_info->sites.findSiteIndex(n);
+    if (it != SiteIndex(-1)) {
+        BelId id = torc_info->site_index_to_bel.at(it);
+        id.index += ndx;
+        return id;
+    }
+    return BelId();
+}
+
+BelId Arch::getBelByLocation(Loc loc) const
+{
+    BelId bel;
+
+    if (bel_by_loc.empty()) {
+        for (int i = 0; i < torc_info->num_bels; i++) {
+            BelId b;
+            b.index = i;
+            bel_by_loc[getBelLocation(b)] = b;
+        }
+    }
+
+    auto it = bel_by_loc.find(loc);
+    if (it != bel_by_loc.end())
+        bel = it->second;
+
+    return bel;
+}
+
+BelRange Arch::getBelsByTile(int x, int y) const
+{
+    BelRange br;
+
+    br.b.cursor = Arch::getBelByLocation(Loc(x, y, 0)).index;
+    br.e.cursor = br.b.cursor;
+
+    if (br.e.cursor != -1) {
+        while (br.e.cursor < chip_info->num_bels && chip_info->bel_data[br.e.cursor].x == x &&
+               chip_info->bel_data[br.e.cursor].y == y)
+            br.e.cursor++;
+    }
+
+    return br;
+}
+
+PortType Arch::getBelPinType(BelId bel, IdString pin) const
+{
+    NPNR_ASSERT(bel != BelId());
+
+    int num_bel_wires = chip_info->bel_data[bel.index].num_bel_wires;
+    const BelWirePOD *bel_wires = chip_info->bel_data[bel.index].bel_wires.get();
+
+    if (num_bel_wires < 7) {
+        for (int i = 0; i < num_bel_wires; i++) {
+            if (bel_wires[i].port == pin.index)
+                return PortType(bel_wires[i].type);
+        }
+    } else {
+        int b = 0, e = num_bel_wires - 1;
+        while (b <= e) {
+            int i = (b + e) / 2;
+            if (bel_wires[i].port == pin.index)
+                return PortType(bel_wires[i].type);
+            if (bel_wires[i].port > pin.index)
+                e = i - 1;
+            else
+                b = i + 1;
+        }
+    }
+
+    return PORT_INOUT;
+}
+
+std::vector<std::pair<IdString, std::string>> Arch::getBelAttrs(BelId bel) const
+{
+    std::vector<std::pair<IdString, std::string>> ret;
+    return ret;
+}
+
+WireId Arch::getBelPinWire(BelId bel, IdString pin) const
+{
+    auto pin_name = pin.str(this);
+    auto bel_type = getBelType(bel);
+    if (bel_type == id_SLICE_LUT6) {
+        // For all LUT based inputs and outputs (I1-I6,O,OQ,OMUX) then change the I/O into the LUT
+        if (pin_name[0] == 'I' || pin_name[0] == 'O') {
+            switch (torc_info->bel_to_loc[bel.index].z) {
+            case 0:
+            case 4:
+                pin_name[0] = 'A';
+                break;
+            case 1:
+            case 5:
+                pin_name[0] = 'B';
+                break;
+            case 2:
+            case 6:
+                pin_name[0] = 'C';
+                break;
+            case 3:
+            case 7:
+                pin_name[0] = 'D';
+                break;
+            default:
+                throw;
+            }
+        }
+    } else if (bel_type == id_PS7 || bel_type == id_MMCME2_ADV) {
+        // e.g. Convert DDRARB[0] -> DDRARB0
+        pin_name.erase(std::remove_if(pin_name.begin(), pin_name.end(), boost::is_any_of("[]")), pin_name.end());
+    }
+
+    auto site_index = torc_info->bel_to_site_index[bel.index];
+    const auto &site = torc_info->sites.getSite(site_index);
+    auto &tw = site.getPinTilewire(pin_name);
+
+    if (tw.isUndefined())
+        log_error("no wire found for site '%s' pin '%s' \n", torc_info->bel_to_name(bel.index).c_str(),
+                  pin_name.c_str());
+
+    return torc_info->tilewire_to_wire(tw);
+}
+
+std::vector<IdString> Arch::getBelPins(BelId bel) const
+{
+    std::vector<IdString> ret;
+    NPNR_ASSERT("TODO");
+    return ret;
+}
+
+// -----------------------------------------------------------------------
+
+WireId Arch::getWireByName(IdString name) const
+{
+    WireId ret;
+    if (wire_by_name.empty()) {
+        for (int i = 0; i < torc_info->num_wires; i++)
+            wire_by_name[id(torc_info->wire_to_name(i))] = i;
+    }
+
+    auto it = wire_by_name.find(name);
+    if (it != wire_by_name.end())
+        ret.index = it->second;
+
+    return ret;
+}
+
+IdString Arch::getWireType(WireId wire) const
+{
+    NPNR_ASSERT(wire != WireId());
+    NPNR_ASSERT("TODO");
+    return IdString();
+}
+
+// -----------------------------------------------------------------------
+std::vector<std::pair<IdString, std::string>> Arch::getWireAttrs(WireId wire) const
+{
+    std::vector<std::pair<IdString, std::string>> ret;
+    NPNR_ASSERT("TODO");
+    return ret;
+}
+
+// -----------------------------------------------------------------------
+
+PipId Arch::getPipByName(IdString name) const
+{
+    PipId ret;
+
+    if (pip_by_name.empty()) {
+        for (int i = 0; i < torc_info->num_pips; i++) {
+            PipId pip;
+            pip.index = i;
+            pip_by_name[getPipName(pip)] = i;
+        }
+    }
+
+    auto it = pip_by_name.find(name);
+    if (it != pip_by_name.end())
+        ret.index = it->second;
+
+    return ret;
+}
+
+IdString Arch::getPipName(PipId pip) const
+{
+    NPNR_ASSERT(pip != PipId());
+
+    ExtendedWireInfo ewi_src(*torc_info->ddb, torc_info->pip_to_arc[pip.index].getSourceTilewire());
+    ExtendedWireInfo ewi_dst(*torc_info->ddb, torc_info->pip_to_arc[pip.index].getSinkTilewire());
+    std::stringstream pip_name;
+    pip_name << ewi_src.mTileName << "." << ewi_src.mWireName << ".->." << ewi_dst.mWireName;
+    return id(pip_name.str());
+}
+
+std::vector<std::pair<IdString, std::string>> Arch::getPipAttrs(PipId pip) const
+{
+    std::vector<std::pair<IdString, std::string>> ret;
+    NPNR_ASSERT("TODO");
+    return ret;
+}
+
+// -----------------------------------------------------------------------
+
+BelId Arch::getPackagePinBel(const std::string &pin) const { return getBelByName(id(pin)); }
+
+std::string Arch::getBelPackagePin(BelId bel) const
+{
+    NPNR_ASSERT("TODO");
+    return "";
+}
+
+// -----------------------------------------------------------------------
+
+GroupId Arch::getGroupByName(IdString name) const
+{
+    for (auto g : getGroups())
+        if (getGroupName(g) == name)
+            return g;
+    return GroupId();
+}
+
+IdString Arch::getGroupName(GroupId group) const
+{
+    std::string suffix;
+
+    switch (group.type) {
+    NPNR_ASSERT("TODO");
+    default:
+        return IdString();
+    }
+
+    return id("X" + std::to_string(group.x) + "/Y" + std::to_string(group.y) + "/" + suffix);
+}
+
+std::vector<GroupId> Arch::getGroups() const
+{
+    std::vector<GroupId> ret;
+    NPNR_ASSERT("TODO");
+    return ret;
+}
+
+std::vector<BelId> Arch::getGroupBels(GroupId group) const
+{
+    std::vector<BelId> ret;
+    return ret;
+}
+
+std::vector<WireId> Arch::getGroupWires(GroupId group) const
+{
+    std::vector<WireId> ret;
+    return ret;
+}
+
+std::vector<PipId> Arch::getGroupPips(GroupId group) const
+{
+    std::vector<PipId> ret;
+    NPNR_ASSERT("TODO");
+    return ret;
+}
+
+std::vector<GroupId> Arch::getGroupGroups(GroupId group) const
+{
+    std::vector<GroupId> ret;
+    NPNR_ASSERT("TODO");
+    return ret;
+}
+
+// -----------------------------------------------------------------------
+
+bool Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const { return false; }
+
+// -----------------------------------------------------------------------
+
+bool Arch::place() { return placer1(getCtx(), Placer1Cfg(getCtx())); }
+
+bool Arch::route() { return router1(getCtx(), Router1Cfg(getCtx())); }
+
+// -----------------------------------------------------------------------
+
+DecalXY Arch::getBelDecal(BelId bel) const
+{
+    DecalXY decalxy;
+    decalxy.decal.type = DecalId::TYPE_BEL;
+    decalxy.decal.index = bel.index;
+    decalxy.decal.active = bel_to_cell.at(bel.index) != nullptr;
+    return decalxy;
+}
+
+DecalXY Arch::getWireDecal(WireId wire) const
+{
+    DecalXY decalxy;
+    decalxy.decal.type = DecalId::TYPE_WIRE;
+    decalxy.decal.index = wire.index;
+    decalxy.decal.active = wire_to_net.at(wire.index) != nullptr;
+    return decalxy;
+}
+
+DecalXY Arch::getPipDecal(PipId pip) const
+{
+    DecalXY decalxy;
+    decalxy.decal.type = DecalId::TYPE_PIP;
+    decalxy.decal.index = pip.index;
+    decalxy.decal.active = pip_to_net.at(pip.index) != nullptr;
+    return decalxy;
+};
+
+DecalXY Arch::getGroupDecal(GroupId group) const
+{
+    DecalXY decalxy;
+    decalxy.decal.type = DecalId::TYPE_GROUP;
+    decalxy.decal.index = (group.type << 16) | (group.x << 8) | (group.y);
+    decalxy.decal.active = true;
+    return decalxy;
+};
+
+std::vector<GraphicElement> Arch::getDecalGraphics(DecalId decal) const
+{
+    std::vector<GraphicElement> ret;
+
+    if (decal.type == DecalId::TYPE_BEL) {
+        BelId bel;
+        bel.index = decal.index;
+        auto bel_type = getBelType(bel);
+        int x = torc_info->bel_to_loc[bel.index].x;
+        int y = torc_info->bel_to_loc[bel.index].y;
+        int z = torc_info->bel_to_loc[bel.index].z;
+        if (bel_type == id_SLICE_LUT6) {
+            GraphicElement el;
+            /*if (z>3) {
+                z = z - 4;
+                x -= logic_cell_x2- logic_cell_x1;
+            }*/
+            el.type = GraphicElement::TYPE_BOX;
+            el.style = decal.active ? GraphicElement::STYLE_ACTIVE : GraphicElement::STYLE_INACTIVE;
+            el.x1 = x + logic_cell_x1;
+            el.x2 = x + logic_cell_x2;
+            el.y1 = y + logic_cell_y1 + (z)*logic_cell_pitch;
+            el.y2 = y + logic_cell_y2 + (z)*logic_cell_pitch;
+            ret.push_back(el);
+        }
+
+    }
+
+    return ret;
+}
+
+// -----------------------------------------------------------------------
+
+bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const
+{
+    if (cell->type == id_SLICE_LUT6) {
+        if (fromPort.index >= id_I1.index && fromPort.index <= id_I6.index) {
+            if (toPort == id_O) {
+                delay.delay = 124; // Tilo
+                return true;
+            }
+            if (toPort == id_OQ) {
+                delay.delay = 95; // Tas
+                return true;
+            }
+        }
+        if (fromPort == id_CLK) {
+            if (toPort == id_OQ) {
+                delay.delay = 456; // Tcko
+                return true;
+            }
+        }
+    } else if (cell->type == id_BUFGCTRL) {
+        return true;
+    }
+    return false;
+}
+
+// Get the port class, also setting clockPort to associated clock if applicable
+TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const
+{
+    if (cell->type == id_SLICE_LUT6) {
+        if (port == id_CLK)
+            return TMG_CLOCK_INPUT;
+        if (port == id_CIN)
+            return TMG_COMB_INPUT;
+        if (port == id_COUT)
+            return TMG_COMB_OUTPUT;
+        if (port == id_O) {
+            // LCs with no inputs are constant drivers
+            if (cell->lcInfo.inputCount == 0)
+                return TMG_IGNORE;
+            return TMG_COMB_OUTPUT;
+        }
+        if (cell->lcInfo.dffEnable) {
+            clockInfoCount = 1;
+            if (port == id_OQ)
+                return TMG_REGISTER_OUTPUT;
+            return TMG_REGISTER_INPUT;
+        } else {
+            return TMG_COMB_INPUT;
+        }
+        // TODO
+        // if (port == id_OMUX)
+    } else if (cell->type == id_IOB33 || cell->type == id_IOB18) {
+        if (port == id_I)
+            return TMG_STARTPOINT;
+        else if (port == id_O)
+            return TMG_ENDPOINT;
+    } else if (cell->type == id_BUFGCTRL) {
+        if (port == id_O)
+            return TMG_COMB_OUTPUT;
+        return TMG_COMB_INPUT;
+    } else if (cell->type == id_PS7) {
+        // TODO
+        return TMG_IGNORE;
+    } else if (cell->type == id_MMCME2_ADV) {
+        return TMG_IGNORE;
+    }
+    log_error("no timing info for port '%s' of cell type '%s'\n", port.c_str(this), cell->type.c_str(this));
+}
+
+TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port, int index) const
+{
+    TimingClockingInfo info;
+    if (cell->type == id_SLICE_LUT6) {
+        info.clock_port = id_CLK;
+        info.edge = cell->lcInfo.negClk ? FALLING_EDGE : RISING_EDGE;
+        if (port == id_OQ) {
+            bool has_clktoq = getCellDelay(cell, id_CLK, id_OQ, info.clockToQ);
+            NPNR_ASSERT(has_clktoq);
+        } else {
+            info.setup.delay = 124; // Tilo
+            info.hold.delay = 0;
+        }
+    } else {
+        NPNR_ASSERT_FALSE("unhandled cell type in getPortClockingInfo");
+    }
+    return info;
+}
+
+bool Arch::isGlobalNet(const NetInfo *net) const
+{
+    if (net == nullptr)
+        return false;
+    return net->driver.cell != nullptr && net->driver.cell->type == id_BUFGCTRL && net->driver.port == id_O;
+}
+
+// Assign arch arg info
+void Arch::assignArchInfo()
+{
+    for (auto &net : getCtx()->nets) {
+        NetInfo *ni = net.second.get();
+        if (isGlobalNet(ni))
+            ni->is_global = true;
+        ni->is_enable = false;
+        ni->is_reset = false;
+        for (auto usr : ni->users) {
+            if (is_enable_port(this, usr))
+                ni->is_enable = true;
+            if (is_reset_port(this, usr))
+                ni->is_reset = true;
+        }
+    }
+    for (auto &cell : getCtx()->cells) {
+        CellInfo *ci = cell.second.get();
+        assignCellInfo(ci);
+    }
+}
+
+void Arch::assignCellInfo(CellInfo *cell)
+{
+    cell->belType = cell->type;
+    if (cell->type == id_SLICE_LUT6) {
+        cell->lcInfo.dffEnable = bool_or_default(cell->params, id_DFF_ENABLE);
+        cell->lcInfo.carryEnable = bool_or_default(cell->params, id_CARRY_ENABLE);
+        cell->lcInfo.negClk = bool_or_default(cell->params, id_NEG_CLK);
+        cell->lcInfo.clk = get_net_or_empty(cell, id_CLK);
+        cell->lcInfo.cen = get_net_or_empty(cell, id_CEN);
+        cell->lcInfo.sr = get_net_or_empty(cell, id_SR);
+        cell->lcInfo.inputCount = 0;
+        if (get_net_or_empty(cell, id_I1))
+            cell->lcInfo.inputCount++;
+        if (get_net_or_empty(cell, id_I2))
+            cell->lcInfo.inputCount++;
+        if (get_net_or_empty(cell, id_I3))
+            cell->lcInfo.inputCount++;
+        if (get_net_or_empty(cell, id_I4))
+            cell->lcInfo.inputCount++;
+        if (get_net_or_empty(cell, id_I5))
+            cell->lcInfo.inputCount++;
+        if (get_net_or_empty(cell, id_I6))
+            cell->lcInfo.inputCount++;
+    }
+}
+
+NEXTPNR_NAMESPACE_END
diff --git a/xc7/arch.h b/xc7/arch.h
new file mode 100644
index 0000000..699d357
--- /dev/null
+++ b/xc7/arch.h
@@ -0,0 +1,934 @@
+/*
+ *  nextpnr -- Next Generation Place and Route
+ *
+ *  Copyright (C) 2018  Clifford Wolf <clifford@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 NEXTPNR_H
+#error Include "arch.h" via "nextpnr.h" only.
+#endif
+
+#include "torc/Architecture.hpp"
+#include "torc/Common.hpp"
+using namespace torc::architecture;
+using namespace torc::architecture::xilinx;
+
+namespace std {
+template <> struct hash<Segments::SegmentReference>
+{
+    size_t operator()(const Segments::SegmentReference &s) const
+    {
+        size_t seed = 0;
+        boost::hash_combine(seed, hash<unsigned>()(s.getCompactSegmentIndex()));
+        boost::hash_combine(seed, hash<unsigned>()(s.getAnchorTileIndex()));
+        return seed;
+    }
+};
+template <> struct equal_to<Segments::SegmentReference>
+{
+    bool operator()(const Segments::SegmentReference &lhs, const Segments::SegmentReference &rhs) const
+    {
+        return lhs.getAnchorTileIndex() == rhs.getAnchorTileIndex() &&
+               lhs.getCompactSegmentIndex() == rhs.getCompactSegmentIndex();
+    }
+};
+template <> struct hash<Tilewire>
+{
+    size_t operator()(const Tilewire &t) const { return hash_value(t); }
+};
+
+template <> struct hash<Arc>
+{
+    size_t operator()(const Arc &a) const
+    {
+        size_t seed = 0;
+        boost::hash_combine(seed, hash_value(a.getSourceTilewire()));
+        boost::hash_combine(seed, hash_value(a.getSinkTilewire()));
+        return seed;
+    }
+};
+} // namespace std
+
+NEXTPNR_NAMESPACE_BEGIN
+
+/**** Everything in this section must be kept in sync with chipdb.py ****/
+
+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(); }
+};
+
+NPNR_PACKED_STRUCT(struct BelWirePOD {
+    int32_t port;
+    int32_t type;
+    int32_t wire_index;
+});
+
+NPNR_PACKED_STRUCT(struct BelInfoPOD {
+    RelPtr<char> name;
+    int32_t type;
+    int32_t num_bel_wires;
+    RelPtr<BelWirePOD> bel_wires;
+    int8_t x, y, z;
+    int8_t padding_0;
+});
+
+NPNR_PACKED_STRUCT(struct BelPortPOD {
+    int32_t bel_index;
+    int32_t port;
+});
+
+NPNR_PACKED_STRUCT(struct PipInfoPOD {
+    enum PipFlags : uint32_t
+    {
+        FLAG_NONE = 0,
+        FLAG_ROUTETHRU = 1,
+        FLAG_NOCARRY = 2
+    };
+
+    // RelPtr<char> name;
+    int32_t src, dst;
+    int32_t fast_delay;
+    int32_t slow_delay;
+    int8_t x, y;
+    int16_t src_seg, dst_seg;
+    int16_t switch_mask;
+    int32_t switch_index;
+    PipFlags flags;
+});
+
+NPNR_PACKED_STRUCT(struct WireSegmentPOD {
+    int8_t x, y;
+    int16_t index;
+});
+
+NPNR_PACKED_STRUCT(struct WireInfoPOD {
+    enum WireType : int8_t
+    {
+        WIRE_TYPE_NONE = 0,
+        WIRE_TYPE_GLB2LOCAL = 1,
+        WIRE_TYPE_GLB_NETWK = 2,
+        WIRE_TYPE_LOCAL = 3,
+        WIRE_TYPE_LUTFF_IN = 4,
+        WIRE_TYPE_LUTFF_IN_LUT = 5,
+        WIRE_TYPE_LUTFF_LOUT = 6,
+        WIRE_TYPE_LUTFF_OUT = 7,
+        WIRE_TYPE_LUTFF_COUT = 8,
+        WIRE_TYPE_LUTFF_GLOBAL = 9,
+        WIRE_TYPE_CARRY_IN_MUX = 10,
+        WIRE_TYPE_SP4_V = 11,
+        WIRE_TYPE_SP4_H = 12,
+        WIRE_TYPE_SP12_V = 13,
+        WIRE_TYPE_SP12_H = 14
+    };
+
+    RelPtr<char> name;
+    int32_t num_uphill, num_downhill;
+    RelPtr<int32_t> pips_uphill, pips_downhill;
+
+    int32_t num_bel_pins;
+    RelPtr<BelPortPOD> bel_pins;
+
+    int32_t num_segments;
+    RelPtr<WireSegmentPOD> segments;
+
+    int32_t fast_delay;
+    int32_t slow_delay;
+
+    int8_t x, y, z;
+    WireType type;
+});
+
+NPNR_PACKED_STRUCT(struct PackagePinPOD {
+    RelPtr<char> name;
+    int32_t bel_index;
+});
+
+NPNR_PACKED_STRUCT(struct PackageInfoPOD {
+    RelPtr<char> name;
+    int32_t num_pins;
+    RelPtr<PackagePinPOD> pins;
+});
+
+enum TileType : uint32_t
+{
+    TILE_NONE = 0,
+    TILE_LOGIC = 1,
+    TILE_IO = 2,
+    TILE_RAMB = 3,
+    TILE_RAMT = 4,
+    TILE_DSP0 = 5,
+    TILE_DSP1 = 6,
+    TILE_DSP2 = 7,
+    TILE_DSP3 = 8,
+    TILE_IPCON = 9
+};
+
+NPNR_PACKED_STRUCT(struct ConfigBitPOD { int8_t row, col; });
+
+NPNR_PACKED_STRUCT(struct ConfigEntryPOD {
+    RelPtr<char> name;
+    int32_t num_bits;
+    RelPtr<ConfigBitPOD> bits;
+});
+
+NPNR_PACKED_STRUCT(struct TileInfoPOD {
+    int8_t cols, rows;
+    int16_t num_config_entries;
+    RelPtr<ConfigEntryPOD> entries;
+});
+
+static const int max_switch_bits = 5;
+
+NPNR_PACKED_STRUCT(struct SwitchInfoPOD {
+    int32_t num_bits;
+    int32_t bel;
+    int8_t x, y;
+    ConfigBitPOD cbits[max_switch_bits];
+});
+
+NPNR_PACKED_STRUCT(struct IerenInfoPOD {
+    int8_t iox, ioy, ioz;
+    int8_t ierx, iery, ierz;
+});
+
+NPNR_PACKED_STRUCT(struct BitstreamInfoPOD {
+    int32_t num_switches, num_ierens;
+    RelPtr<TileInfoPOD> tiles_nonrouting;
+    RelPtr<SwitchInfoPOD> switches;
+    RelPtr<IerenInfoPOD> ierens;
+});
+
+NPNR_PACKED_STRUCT(struct BelConfigEntryPOD {
+    RelPtr<char> entry_name;
+    RelPtr<char> cbit_name;
+    int8_t x, y;
+    int16_t padding;
+});
+
+// Stores mapping between bel parameters and config bits,
+// for extra cells where this mapping is non-trivial
+NPNR_PACKED_STRUCT(struct BelConfigPOD {
+    int32_t bel_index;
+    int32_t num_entries;
+    RelPtr<BelConfigEntryPOD> entries;
+});
+
+NPNR_PACKED_STRUCT(struct CellPathDelayPOD {
+    int32_t from_port;
+    int32_t to_port;
+    int32_t fast_delay;
+    int32_t slow_delay;
+});
+
+NPNR_PACKED_STRUCT(struct CellTimingPOD {
+    int32_t type;
+    int32_t num_paths;
+    RelPtr<CellPathDelayPOD> path_delays;
+});
+
+NPNR_PACKED_STRUCT(struct ChipInfoPOD {
+    int32_t width, height;
+    int32_t num_bels, num_wires, num_pips;
+    int32_t num_switches, num_belcfgs, num_packages;
+    int32_t num_timing_cells;
+    RelPtr<BelInfoPOD> bel_data;
+    RelPtr<WireInfoPOD> wire_data;
+    RelPtr<PipInfoPOD> pip_data;
+    RelPtr<TileType> tile_grid;
+    RelPtr<BitstreamInfoPOD> bits_info;
+    RelPtr<BelConfigPOD> bel_config;
+    RelPtr<PackageInfoPOD> packages_data;
+    RelPtr<CellTimingPOD> cell_timing;
+});
+
+struct TorcInfo
+{
+    TorcInfo(BaseCtx *ctx, const std::string &inDeviceName, const std::string &inPackageName);
+    TorcInfo() = delete;
+    std::unique_ptr<const DDB> ddb;
+    const Sites &sites;
+    const Tiles &tiles;
+    const Segments &segments;
+
+    const TileInfo &bel_to_tile_info(int32_t index) const
+    {
+        auto si = bel_to_site_index[index];
+        const auto &site = sites.getSite(si);
+        return tiles.getTileInfo(site.getTileIndex());
+    }
+    const std::string &bel_to_name(int32_t index) const
+    {
+        auto si = bel_to_site_index[index];
+        return sites.getSite(si).getName();
+    }
+    std::string wire_to_name(int32_t index) const
+    {
+        const auto &tw = wire_to_tilewire[index];
+        ExtendedWireInfo ewi(*ddb, tw);
+        std::stringstream ss;
+        ss << ewi.mTileName << "/" << ewi.mWireName;
+        ss << "(" << tw.getWireIndex() << "@" << tw.getTileIndex() << ")";
+        return ss.str();
+    }
+
+    Loc wire_to_loc(int32_t index) const
+    {
+        const auto &tw = wire_to_tilewire[index];
+        ExtendedWireInfo ewi(*ddb, tw);
+        Loc l;
+        l.x = (int)ewi.mTileCol;
+        l.y = (int)ewi.mTileRow;
+        return l;
+    }
+    
+    WireId tilewire_to_wire(const Tilewire &tw) const
+    {
+        const auto &segment = segments.getTilewireSegment(tw);
+        if (!segment.isTrivial())
+            return segment_to_wire.at(segment);
+        return trivial_to_wire.at(tw);
+    }
+
+    std::vector<SiteIndex> bel_to_site_index;
+    int num_bels;
+    std::vector<BelId> site_index_to_bel;
+    std::vector<IdString> site_index_to_type;
+    std::vector<Loc> bel_to_loc;
+    std::unordered_map<Segments::SegmentReference, WireId> segment_to_wire;
+    std::unordered_map<Tilewire, WireId> trivial_to_wire;
+    std::vector<Tilewire> wire_to_tilewire;
+    int num_wires;
+    std::vector<DelayInfo> wire_to_delay;
+    //std::vector<std::vector<int>> wire_to_pips_uphill;
+    std::vector<std::vector<PipId>> wire_to_pips_downhill;
+    std::vector<Arc> pip_to_arc;
+    int num_pips;
+    int width;
+    int height;
+    std::vector<bool> wire_is_global;
+    std::vector<std::pair<int,int>> tile_to_xy;
+
+    TorcInfo(const std::string &inDeviceName, const std::string &inPackageName);
+};
+extern std::unique_ptr<const TorcInfo> torc_info;
+
+/************************ End of chipdb section. ************************/
+
+struct BelIterator
+{
+    int cursor;
+
+    BelIterator operator++()
+    {
+        cursor++;
+        return *this;
+    }
+    BelIterator operator++(int)
+    {
+        BelIterator prior(*this);
+        cursor++;
+        return prior;
+    }
+
+    bool operator!=(const BelIterator &other) const { return cursor != other.cursor; }
+
+    bool operator==(const BelIterator &other) const { return cursor == other.cursor; }
+
+    BelId operator*() const
+    {
+        BelId ret;
+        ret.index = cursor;
+        return ret;
+    }
+};
+
+struct BelRange
+{
+    BelIterator b, e;
+    BelIterator begin() const { return b; }
+    BelIterator end() const { return e; }
+};
+
+// -----------------------------------------------------------------------
+
+struct BelPinIterator
+{
+    const BelId bel;
+    Array<const WireIndex>::iterator it;
+
+    void operator++() { it++; }
+    bool operator!=(const BelPinIterator &other) const { return it != other.it && bel != other.bel; }
+
+    BelPin operator*() const
+    {
+        BelPin ret;
+        ret.bel = bel;
+        ret.pin = IdString();
+        return ret;
+    }
+};
+
+struct BelPinRange
+{
+    BelPinIterator b, e;
+    BelPinIterator begin() const { return b; }
+    BelPinIterator end() const { return e; }
+};
+
+// -----------------------------------------------------------------------
+
+struct WireIterator
+{
+    int cursor = -1;
+
+    void operator++() { cursor++; }
+    bool operator!=(const WireIterator &other) const { return cursor != other.cursor; }
+
+    WireId operator*() const
+    {
+        WireId ret;
+        ret.index = cursor;
+        return ret;
+    }
+};
+
+struct WireRange
+{
+    WireIterator b, e;
+    WireIterator begin() const { return b; }
+    WireIterator end() const { return e; }
+};
+
+// -----------------------------------------------------------------------
+
+struct AllPipIterator
+{
+    int cursor = -1;
+
+    void operator++() { cursor++; }
+    bool operator!=(const AllPipIterator &other) const { return cursor != other.cursor; }
+
+    PipId operator*() const
+    {
+        PipId ret;
+        ret.index = cursor;
+        return ret;
+    }
+};
+
+struct AllPipRange
+{
+    AllPipIterator b, e;
+    AllPipIterator begin() const { return b; }
+    AllPipIterator end() const { return e; }
+};
+
+// -----------------------------------------------------------------------
+
+struct PipIterator
+{
+    const PipId *cursor = nullptr;
+
+    void operator++() { cursor++; }
+    bool operator!=(const PipIterator &other) const { return cursor != other.cursor; }
+
+    PipId operator*() const
+    {
+        return *cursor;
+    }
+};
+
+struct PipRange
+{
+    PipIterator b, e;
+    PipIterator begin() const { return b; }
+    PipIterator end() const { return e; }
+};
+
+struct ArchArgs
+{
+    enum ArchArgsTypes
+    {
+        NONE,
+        Z020,
+        VX980
+    } type = NONE;
+    std::string package;
+};
+
+struct Arch : BaseCtx
+{
+    bool fast_part;
+    const ChipInfoPOD *chip_info;
+    const PackageInfoPOD *package_info;
+    int width;
+    int height;
+
+    mutable std::unordered_map<IdString, int> wire_by_name;
+    mutable std::unordered_map<IdString, int> pip_by_name;
+    mutable std::unordered_map<Loc, BelId> bel_by_loc;
+
+    // std::vector<bool> bel_carry;
+    std::vector<CellInfo *> bel_to_cell;
+    std::vector<NetInfo *> wire_to_net;
+    std::vector<NetInfo *> pip_to_net;
+    // std::vector<NetInfo *> switches_locked;
+
+    ArchArgs args;
+    Arch(ArchArgs args);
+
+    std::string getChipName() const;
+
+    IdString archId() const { return id("xc7"); }
+    ArchArgs archArgs() const { return args; }
+    IdString archArgsToId(ArchArgs args) const;
+
+    // -------------------------------------------------
+
+    int getGridDimX() const { return width; }
+    int getGridDimY() const { return height; }
+    int getTileBelDimZ(int, int) const { return 8; }
+    int getTilePipDimZ(int, int) const { return 1; }
+
+    // -------------------------------------------------
+
+    BelId getBelByName(IdString name) const;
+
+    IdString getBelName(BelId bel) const
+    {
+        NPNR_ASSERT(bel != BelId());
+        auto name = torc_info->bel_to_name(bel.index);
+        if (getBelType(bel) == id_SLICE_LUT6) {
+            // Append LUT name to name
+            name.reserve(name.size() + 2);
+            name += "_";
+            switch (torc_info->bel_to_loc[bel.index].z) {
+            case 0:
+            case 4:
+                name += 'A';
+                break;
+            case 1:
+            case 5:
+                name += 'B';
+                break;
+            case 2:
+            case 6:
+                name += 'C';
+                break;
+            case 3:
+            case 7:
+                name += 'D';
+                break;
+            default:
+                throw;
+            }
+        }
+        return id(name);
+    }
+
+    uint32_t getBelChecksum(BelId bel) const { return bel.index; }
+
+    void bindBel(BelId bel, CellInfo *cell, PlaceStrength strength)
+    {
+        NPNR_ASSERT(bel != BelId());
+        NPNR_ASSERT(bel_to_cell[bel.index] == nullptr);
+
+        bel_to_cell[bel.index] = cell;
+        // bel_carry[bel.index] = (cell->type == id_ICESTORM_LC && cell->lcInfo.carryEnable);
+        cell->bel = bel;
+        cell->belStrength = strength;
+        refreshUiBel(bel);
+    }
+
+    void unbindBel(BelId bel)
+    {
+        NPNR_ASSERT(bel != BelId());
+        NPNR_ASSERT(bel_to_cell[bel.index] != nullptr);
+        bel_to_cell[bel.index]->bel = BelId();
+        bel_to_cell[bel.index]->belStrength = STRENGTH_NONE;
+        bel_to_cell[bel.index] = nullptr;
+        // bel_carry[bel.index] = false;
+        refreshUiBel(bel);
+    }
+
+    bool checkBelAvail(BelId bel) const
+    {
+        NPNR_ASSERT(bel != BelId());
+        return bel_to_cell[bel.index] == nullptr;
+    }
+
+    CellInfo *getBoundBelCell(BelId bel) const
+    {
+        NPNR_ASSERT(bel != BelId());
+        return bel_to_cell[bel.index];
+    }
+
+    CellInfo *getConflictingBelCell(BelId bel) const
+    {
+        NPNR_ASSERT(bel != BelId());
+        return bel_to_cell[bel.index];
+    }
+
+    BelRange getBels() const
+    {
+        BelRange range;
+        range.b.cursor = 0;
+        range.e.cursor = torc_info->num_bels;
+        return range;
+    }
+
+    Loc getBelLocation(BelId bel) const { return torc_info->bel_to_loc[bel.index]; }
+
+    BelId getBelByLocation(Loc loc) const;
+    BelRange getBelsByTile(int x, int y) const;
+
+    bool getBelGlobalBuf(BelId bel) const { return getBelType(bel) == id_BUFGCTRL; }
+
+    IdString getBelType(BelId bel) const
+    {
+        NPNR_ASSERT(bel != BelId());
+        auto site_index = torc_info->bel_to_site_index[bel.index];
+        return torc_info->site_index_to_type[site_index];
+    }
+
+    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
+    {
+        NPNR_ASSERT(wire != WireId());
+        return id(torc_info->wire_to_name(wire.index));
+    }
+
+    IdString getWireType(WireId wire) const;
+    std::vector<std::pair<IdString, std::string>> getWireAttrs(WireId wire) const;
+
+    uint32_t getWireChecksum(WireId wire) const { return wire.index; }
+
+    void bindWire(WireId wire, NetInfo *net, PlaceStrength strength)
+    {
+        NPNR_ASSERT(wire != WireId());
+        NPNR_ASSERT(wire_to_net[wire.index] == nullptr);
+        wire_to_net[wire.index] = 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.index] != nullptr);
+
+        auto &net_wires = wire_to_net[wire.index]->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.index] = nullptr;
+        }
+
+        net_wires.erase(it);
+        wire_to_net[wire.index] = nullptr;
+        refreshUiWire(wire);
+    }
+
+    bool checkWireAvail(WireId wire) const
+    {
+        NPNR_ASSERT(wire != WireId());
+        return wire_to_net[wire.index] == nullptr;
+    }
+
+    NetInfo *getBoundWireNet(WireId wire) const
+    {
+        NPNR_ASSERT(wire != WireId());
+        return wire_to_net[wire.index];
+    }
+
+    WireId getConflictingWireWire(WireId wire) const { return wire; }
+
+    NetInfo *getConflictingWireNet(WireId wire) const
+    {
+        NPNR_ASSERT(wire != WireId());
+        return wire_to_net[wire.index];
+    }
+
+    DelayInfo getWireDelay(WireId wire) const { return {}; }
+
+    BelPinRange getWireBelPins(WireId wire) const
+    {
+        BelPinRange range;
+        // TODO
+        return range;
+    }
+
+    WireRange getWires() const
+    {
+        WireRange range;
+        range.b.cursor = 0;
+        range.e.cursor = torc_info->num_wires;
+        return range;
+    }
+
+    // -------------------------------------------------
+
+    PipId getPipByName(IdString name) const;
+
+    void bindPip(PipId pip, NetInfo *net, PlaceStrength strength)
+    {
+        NPNR_ASSERT(pip != PipId());
+        NPNR_ASSERT(pip_to_net[pip.index] == nullptr);
+
+        pip_to_net[pip.index] = net;
+
+        WireId dst = getPipDstWire(pip);
+        NPNR_ASSERT(wire_to_net[dst.index] == nullptr);
+        wire_to_net[dst.index] = 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.index] != nullptr);
+
+        WireId dst = getPipDstWire(pip);
+        NPNR_ASSERT(wire_to_net[dst.index] != nullptr);
+        wire_to_net[dst.index] = nullptr;
+        pip_to_net[pip.index]->wires.erase(dst);
+
+        pip_to_net[pip.index] = nullptr;
+        refreshUiPip(pip);
+        refreshUiWire(dst);
+    }
+
+    bool checkPipAvail(PipId pip) const
+    {
+        NPNR_ASSERT(pip != PipId());
+        return pip_to_net[pip.index] == nullptr;
+    }
+
+    NetInfo *getBoundPipNet(PipId pip) const
+    {
+        NPNR_ASSERT(pip != PipId());
+        return pip_to_net[pip.index];
+    }
+
+    WireId getConflictingPipWire(PipId pip) const { return WireId(); }
+
+    NetInfo *getConflictingPipNet(PipId pip) const
+    {
+        NPNR_ASSERT(pip != PipId());
+        return pip_to_net[pip.index];
+    }
+
+    AllPipRange getPips() const
+    {
+        AllPipRange range;
+        range.b.cursor = 0;
+        range.e.cursor = torc_info->num_pips;
+        return range;
+    }
+
+    Loc getPipLocation(PipId pip) const
+    {
+        Loc loc;
+        NPNR_ASSERT("TODO");
+        return loc;
+    }
+
+    IdString getPipName(PipId pip) const;
+
+    IdString getPipType(PipId pip) const { return IdString(); }
+    std::vector<std::pair<IdString, std::string>> getPipAttrs(PipId pip) const;
+
+    uint32_t getPipChecksum(PipId pip) const { return pip.index; }
+
+    WireId getPipSrcWire(PipId pip) const
+    {
+        NPNR_ASSERT(pip != PipId());
+
+        const auto &arc = torc_info->pip_to_arc[pip.index];
+        const auto &tw = arc.getSourceTilewire();
+        return torc_info->tilewire_to_wire(tw);
+    }
+
+    WireId getPipDstWire(PipId pip) const
+    {
+        NPNR_ASSERT(pip != PipId());
+        const auto &arc = torc_info->pip_to_arc[pip.index];
+        const auto &tw = arc.getSinkTilewire();
+        return torc_info->tilewire_to_wire(tw);
+    }
+
+    DelayInfo getPipDelay(PipId pip) const
+    {
+        NPNR_ASSERT(pip != PipId());
+        auto wire = getPipDstWire(pip);
+        return torc_info->wire_to_delay[wire.index];
+    }
+
+    PipRange getPipsDownhill(WireId wire) const
+    {
+        PipRange range;
+        NPNR_ASSERT(wire != WireId());
+        const auto &pips = torc_info->wire_to_pips_downhill[wire.index];
+        range.b.cursor = pips.data();
+        range.e.cursor = range.b.cursor + pips.size();
+        return range;
+    }
+
+    PipRange getPipsUphill(WireId wire) const
+    {
+        PipRange range;
+        // NPNR_ASSERT(wire != WireId());
+        // const auto &pips = torc_info->wire_to_pips_uphill[wire.index];
+        // range.b.cursor = pips.data();
+        // range.e.cursor = range.b.cursor + pips.size();
+        return range;
+    }
+
+    PipRange getWireAliases(WireId wire) const
+    {
+        PipRange range;
+        NPNR_ASSERT(wire != WireId());
+        range.b.cursor = nullptr;
+        range.e.cursor = nullptr;
+        return range;
+    }
+
+    BelId getPackagePinBel(const std::string &pin) const;
+    std::string getBelPackagePin(BelId bel) const;
+
+    // -------------------------------------------------
+
+    GroupId getGroupByName(IdString name) const;
+    IdString getGroupName(GroupId group) const;
+    std::vector<GroupId> getGroups() const;
+    std::vector<BelId> getGroupBels(GroupId group) const;
+    std::vector<WireId> getGroupWires(GroupId group) const;
+    std::vector<PipId> getGroupPips(GroupId group) const;
+    std::vector<GroupId> getGroupGroups(GroupId group) const;
+
+    // -------------------------------------------------
+
+    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 200; }
+    float getDelayNS(delay_t v) const { return v * 0.001; }
+
+    DelayInfo getDelayFromNS(float ns) const
+    {
+        DelayInfo del;
+        del.delay = delay_t(ns * 1000);
+        return del;
+    }
+
+    uint32_t getDelayChecksum(delay_t v) const { return v; }
+    bool getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const;
+
+    // -------------------------------------------------
+
+    bool pack();
+    bool place();
+    bool route();
+
+    // -------------------------------------------------
+
+    std::vector<GraphicElement> getDecalGraphics(DecalId decal) const;
+
+    DecalXY getBelDecal(BelId bel) const;
+    DecalXY getWireDecal(WireId wire) const;
+    DecalXY getPipDecal(PipId pip) const;
+    DecalXY getGroupDecal(GroupId group) const;
+
+    // -------------------------------------------------
+
+    // Get the delay through a cell from one port to another, returning false
+    // if no path exists
+    bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const;
+    // Get the port class, also setting clockDomain if applicable
+    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;
+    // Return true if a port is a net
+    bool isGlobalNet(const NetInfo *net) 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;
+
+    // Helper function for above
+    bool logicCellsCompatible(const CellInfo **it, const size_t size) const;
+
+    // -------------------------------------------------
+    // 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);
+
+    // -------------------------------------------------
+    BelPin getIOBSharingPLLPin(BelId pll, IdString pll_pin) const
+    {
+        auto wire = getBelPinWire(pll, pll_pin);
+        for (auto src_bel : getWireBelPins(wire)) {
+            if (getBelType(src_bel.bel) == id_SB_IO && src_bel.pin == id_D_IN_0) {
+                return src_bel;
+            }
+        }
+        NPNR_ASSERT_FALSE("Expected PLL pin to share an output with an SB_IO D_IN_{0,1}");
+    }
+
+    float placer_constraintWeight = 10;
+};
+
+NEXTPNR_NAMESPACE_END
diff --git a/xc7/arch_place.cc b/xc7/arch_place.cc
new file mode 100644
index 0000000..7d1e32e
--- /dev/null
+++ b/xc7/arch_place.cc
@@ -0,0 +1,78 @@
+/*
+ *  nextpnr -- Next Generation Place and Route
+ *
+ *  Copyright (C) 2018  Clifford Wolf <clifford@symbioticeda.com>
+ *  Copyright (C) 2018  David Shah <david@symbioticeda.com>
+ *  Copyright (C) 2018  Serge Bazanski <q3k@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 "cells.h"
+#include "nextpnr.h"
+#include "util.h"
+
+#include <boost/range/iterator_range.hpp>
+
+NEXTPNR_NAMESPACE_BEGIN
+
+bool Arch::logicCellsCompatible(const CellInfo **it, const size_t size) const
+{
+    // TODO: Check clock, clock-enable, and set-reset compatiility
+    return true;
+}
+
+bool Arch::isBelLocationValid(BelId bel) const
+{
+    if (getBelType(bel) == id("XC7_LC")) {
+        std::array<const CellInfo *, 4> bel_cells;
+        size_t num_cells = 0;
+        Loc bel_loc = getBelLocation(bel);
+        for (auto bel_other : getBelsByTile(bel_loc.x, bel_loc.y)) {
+            CellInfo *ci_other = getBoundBelCell(bel_other);
+            if (ci_other != nullptr)
+                bel_cells[num_cells++] = ci_other;
+        }
+        return logicCellsCompatible(bel_cells.data(), num_cells);
+    } else {
+        CellInfo *ci = getBoundBelCell(bel);
+        if (ci == nullptr)
+            return true;
+        else
+            return isValidBelForCell(ci, bel);
+    }
+}
+
+bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const
+{
+    if (cell->type == id("XC7_LC")) {
+        std::array<const CellInfo *, 4> bel_cells;
+        size_t num_cells = 0;
+    
+        Loc bel_loc = getBelLocation(bel);
+        for (auto bel_other : getBelsByTile(bel_loc.x, bel_loc.y)) {
+            CellInfo *ci_other = getBoundBelCell(bel_other);
+            if (ci_other != nullptr && bel_other != bel)
+                bel_cells[num_cells++] = ci_other;
+        }
+    
+        bel_cells[num_cells++] = cell;
+        return logicCellsCompatible(bel_cells.data(), num_cells);
+    }
+    else {
+        return true;
+    }
+}
+
+NEXTPNR_NAMESPACE_END
diff --git a/xc7/arch_pybindings.cc b/xc7/arch_pybindings.cc
new file mode 100644
index 0000000..04d9d5d
--- /dev/null
+++ b/xc7/arch_pybindings.cc
@@ -0,0 +1,144 @@
+/*
+ *  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()
+{
+    using namespace PythonConversion;
+    class_<ArchArgs>("ArchArgs").def_readwrite("type", &ArchArgs::type);
+
+    class_<BelId>("BelId").def_readwrite("index", &BelId::index);
+
+    class_<WireId>("WireId").def_readwrite("index", &WireId::index);
+
+    class_<PipId>("PipId").def_readwrite("index", &PipId::index);        
+
+    class_<BelPin>("BelPin").def_readwrite("bel", &BelPin::bel).def_readwrite("pin", &BelPin::pin);
+
+    auto arch_cls = class_<Arch, Arch *, bases<BaseCtx>, boost::noncopyable>("Arch", init<ArchArgs>());
+    auto ctx_cls = class_<Context, Context *, bases<Arch>, boost::noncopyable>("Context", no_init)
+                           .def("checksum", &Context::checksum)
+                           .def("pack", &Context::pack)
+                           .def("place", &Context::place)
+                           .def("route", &Context::route);
+
+    fn_wrapper_1a<Context, decltype(&Context::getBelType), &Context::getBelType, conv_to_str<IdString>,
+                  conv_from_str<BelId>>::def_wrap(ctx_cls, "getBelType");
+    fn_wrapper_1a<Context, decltype(&Context::checkBelAvail), &Context::checkBelAvail, pass_through<bool>,
+                  conv_from_str<BelId>>::def_wrap(ctx_cls, "checkBelAvail");
+    fn_wrapper_1a<Context, decltype(&Context::getBelChecksum), &Context::getBelChecksum, pass_through<uint32_t>,
+                  conv_from_str<BelId>>::def_wrap(ctx_cls, "getBelChecksum");
+    fn_wrapper_3a_v<Context, decltype(&Context::bindBel), &Context::bindBel, conv_from_str<BelId>,
+                    addr_and_unwrap<CellInfo>, pass_through<PlaceStrength>>::def_wrap(ctx_cls, "bindBel");
+    fn_wrapper_1a_v<Context, decltype(&Context::unbindBel), &Context::unbindBel, conv_from_str<BelId>>::def_wrap(
+            ctx_cls, "unbindBel");
+    fn_wrapper_1a<Context, decltype(&Context::getBoundBelCell), &Context::getBoundBelCell, deref_and_wrap<CellInfo>,
+                  conv_from_str<BelId>>::def_wrap(ctx_cls, "getBoundBelCell");
+    fn_wrapper_1a<Context, decltype(&Context::getConflictingBelCell), &Context::getConflictingBelCell,
+                  deref_and_wrap<CellInfo>, conv_from_str<BelId>>::def_wrap(ctx_cls, "getConflictingBelCell");
+    fn_wrapper_0a<Context, decltype(&Context::getBels), &Context::getBels, wrap_context<BelRange>>::def_wrap(ctx_cls,
+                                                                                                             "getBels");
+
+    fn_wrapper_2a<Context, decltype(&Context::getBelPinWire), &Context::getBelPinWire, conv_to_str<WireId>,
+                  conv_from_str<BelId>, conv_from_str<IdString>>::def_wrap(ctx_cls, "getBelPinWire");
+    fn_wrapper_1a<Context, decltype(&Context::getWireBelPins), &Context::getWireBelPins, wrap_context<BelPinRange>,
+                  conv_from_str<WireId>>::def_wrap(ctx_cls, "getWireBelPins");
+
+    fn_wrapper_1a<Context, decltype(&Context::getWireChecksum), &Context::getWireChecksum, pass_through<uint32_t>,
+                  conv_from_str<WireId>>::def_wrap(ctx_cls, "getWireChecksum");
+    fn_wrapper_3a_v<Context, decltype(&Context::bindWire), &Context::bindWire, conv_from_str<WireId>,
+                    addr_and_unwrap<NetInfo>, pass_through<PlaceStrength>>::def_wrap(ctx_cls, "bindWire");
+    fn_wrapper_1a_v<Context, decltype(&Context::unbindWire), &Context::unbindWire, conv_from_str<WireId>>::def_wrap(
+            ctx_cls, "unbindWire");
+    fn_wrapper_1a<Context, decltype(&Context::checkWireAvail), &Context::checkWireAvail, pass_through<bool>,
+                  conv_from_str<WireId>>::def_wrap(ctx_cls, "checkWireAvail");
+    fn_wrapper_1a<Context, decltype(&Context::getBoundWireNet), &Context::getBoundWireNet, deref_and_wrap<NetInfo>,
+                  conv_from_str<WireId>>::def_wrap(ctx_cls, "getBoundWireNet");
+    fn_wrapper_1a<Context, decltype(&Context::getConflictingWireNet), &Context::getConflictingWireNet,
+                  deref_and_wrap<NetInfo>, conv_from_str<WireId>>::def_wrap(ctx_cls, "getConflictingWireNet");
+
+    fn_wrapper_0a<Context, decltype(&Context::getWires), &Context::getWires, wrap_context<WireRange>>::def_wrap(
+            ctx_cls, "getWires");
+
+    fn_wrapper_0a<Context, decltype(&Context::getPips), &Context::getPips, wrap_context<AllPipRange>>::def_wrap(
+            ctx_cls, "getPips");
+    fn_wrapper_1a<Context, decltype(&Context::getPipChecksum), &Context::getPipChecksum, pass_through<uint32_t>,
+                  conv_from_str<PipId>>::def_wrap(ctx_cls, "getPipChecksum");
+    fn_wrapper_3a_v<Context, decltype(&Context::bindPip), &Context::bindPip, conv_from_str<PipId>,
+                    addr_and_unwrap<NetInfo>, pass_through<PlaceStrength>>::def_wrap(ctx_cls, "bindPip");
+    fn_wrapper_1a_v<Context, decltype(&Context::unbindPip), &Context::unbindPip, conv_from_str<PipId>>::def_wrap(
+            ctx_cls, "unbindPip");
+    fn_wrapper_1a<Context, decltype(&Context::checkPipAvail), &Context::checkPipAvail, pass_through<bool>,
+                  conv_from_str<PipId>>::def_wrap(ctx_cls, "checkPipAvail");
+    fn_wrapper_1a<Context, decltype(&Context::getBoundPipNet), &Context::getBoundPipNet, deref_and_wrap<NetInfo>,
+                  conv_from_str<PipId>>::def_wrap(ctx_cls, "getBoundPipNet");
+    fn_wrapper_1a<Context, decltype(&Context::getConflictingPipNet), &Context::getConflictingPipNet,
+                  deref_and_wrap<NetInfo>, conv_from_str<PipId>>::def_wrap(ctx_cls, "getConflictingPipNet");
+
+    fn_wrapper_1a<Context, decltype(&Context::getPipsDownhill), &Context::getPipsDownhill, wrap_context<PipRange>,
+                  conv_from_str<WireId>>::def_wrap(ctx_cls, "getPipsDownhill");
+    fn_wrapper_1a<Context, decltype(&Context::getPipsUphill), &Context::getPipsUphill, wrap_context<PipRange>,
+                  conv_from_str<WireId>>::def_wrap(ctx_cls, "getPipsUphill");
+    fn_wrapper_1a<Context, decltype(&Context::getWireAliases), &Context::getWireAliases, wrap_context<PipRange>,
+                  conv_from_str<WireId>>::def_wrap(ctx_cls, "getWireAliases");
+
+    fn_wrapper_1a<Context, decltype(&Context::getPipSrcWire), &Context::getPipSrcWire, conv_to_str<WireId>,
+                  conv_from_str<PipId>>::def_wrap(ctx_cls, "getPipSrcWire");
+    fn_wrapper_1a<Context, decltype(&Context::getPipDstWire), &Context::getPipDstWire, conv_to_str<WireId>,
+                  conv_from_str<PipId>>::def_wrap(ctx_cls, "getPipDstWire");
+    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,
+                                                                                                          "archId");
+
+    typedef std::unordered_map<IdString, std::unique_ptr<CellInfo>> CellMap;
+    typedef std::unordered_map<IdString, std::unique_ptr<NetInfo>> NetMap;
+
+    readonly_wrapper<Context, decltype(&Context::cells), &Context::cells, wrap_context<CellMap &>>::def_wrap(ctx_cls,
+                                                                                                             "cells");
+    readonly_wrapper<Context, decltype(&Context::nets), &Context::nets, wrap_context<NetMap &>>::def_wrap(ctx_cls,
+                                                                                                          "nets");
+    WRAP_RANGE(Bel, conv_to_str<BelId>);
+    WRAP_RANGE(Wire, conv_to_str<WireId>);
+    WRAP_RANGE(AllPip, conv_to_str<PipId>);
+    WRAP_RANGE(Pip, conv_to_str<PipId>);
+
+    WRAP_MAP_UPTR(CellMap, "IdCellMap");
+    WRAP_MAP_UPTR(NetMap, "IdNetMap");
+}
+
+NEXTPNR_NAMESPACE_END
+
+#endif // NO_PYTHON
diff --git a/xc7/arch_pybindings.h b/xc7/arch_pybindings.h
new file mode 100644
index 0000000..c2c67aa
--- /dev/null
+++ b/xc7/arch_pybindings.h
@@ -0,0 +1,69 @@
+/*
+ *  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 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) { 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) { 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) { return ctx->getPipName(id).str(ctx); }
+};
+
+} // namespace PythonConversion
+
+NEXTPNR_NAMESPACE_END
+#endif
+#endif
diff --git a/xc7/archdefs.h b/xc7/archdefs.h
new file mode 100644
index 0000000..1c2a752
--- /dev/null
+++ b/xc7/archdefs.h
@@ -0,0 +1,201 @@
+/*
+ *  nextpnr -- Next Generation Place and Route
+ *
+ *  Copyright (C) 2018  Clifford Wolf <clifford@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 NEXTPNR_H
+#error Include "archdefs.h" via "nextpnr.h" only.
+#endif
+
+#include "torc/Architecture.hpp"
+using namespace torc::architecture;
+using namespace torc::architecture::xilinx;
+
+NEXTPNR_NAMESPACE_BEGIN
+
+typedef int delay_t;
+
+struct DelayInfo
+{
+    delay_t delay = 0;
+
+    delay_t minRaiseDelay() const { return delay; }
+    delay_t maxRaiseDelay() const { return delay; }
+
+    delay_t minFallDelay() const { return delay; }
+    delay_t maxFallDelay() const { return delay; }
+
+    delay_t minDelay() const { return delay; }
+    delay_t maxDelay() const { return delay; }
+
+    DelayInfo operator+(const DelayInfo &other) const
+    {
+        DelayInfo ret;
+        ret.delay = this->delay + other.delay;
+        return ret;
+    }
+};
+
+// -----------------------------------------------------------------------
+
+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
+
+struct BelId
+{
+    int32_t index = -1;
+
+    bool operator==(const BelId &other) const { return index == other.index; }
+    bool operator!=(const BelId &other) const { return index != other.index; }
+    bool operator<(const BelId &other) const { return index < other.index; }
+};
+
+struct WireId
+{
+    int32_t index = -1;
+
+    bool operator==(const WireId &other) const { return index == other.index; }
+    bool operator!=(const WireId &other) const { return index != other.index; }
+    bool operator<(const WireId &other) const { return index < other.index; }
+};
+
+struct PipId
+{
+    int32_t index = -1;
+
+    bool operator==(const PipId &other) const { return index == other.index; }
+    bool operator!=(const PipId &other) const { return index != other.index; }
+    bool operator<(const PipId &other) const { return index < other.index; }
+};
+
+struct GroupId
+{
+    enum : int8_t
+    {
+        TYPE_NONE,
+        TYPE_FRAME,
+        TYPE_MAIN_SW,
+        TYPE_LOCAL_SW,
+        TYPE_LC0_SW,
+        TYPE_LC1_SW,
+        TYPE_LC2_SW,
+        TYPE_LC3_SW,
+        TYPE_LC4_SW,
+        TYPE_LC5_SW,
+        TYPE_LC6_SW,
+        TYPE_LC7_SW
+    } 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); }
+    bool operator!=(const DecalId &other) const { return (type != other.type) || (index != other.index); }
+};
+
+struct ArchNetInfo
+{
+    bool is_global = false;
+    bool is_reset = false, is_enable = false;
+};
+
+struct NetInfo;
+
+struct ArchCellInfo
+{
+    IdString belType;
+    union
+    {
+        struct
+        {
+            bool dffEnable;
+            bool carryEnable;
+            bool negClk;
+            int inputCount;
+            const NetInfo *clk, *cen, *sr;
+        } lcInfo;
+    };
+};
+
+NEXTPNR_NAMESPACE_END
+
+namespace std {
+template <> struct hash<NEXTPNR_NAMESPACE_PREFIX BelId>
+{
+    std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX BelId &bel) const noexcept { return hash<int>()(bel.index); }
+};
+
+template <> struct hash<NEXTPNR_NAMESPACE_PREFIX WireId>
+{
+    std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX WireId &wire) const noexcept
+    {
+        return hash<int>()(wire.index);
+    }
+};
+
+template <> struct hash<NEXTPNR_NAMESPACE_PREFIX PipId>
+{
+    std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX PipId &pip) const noexcept { return hash<int>()(pip.index); }
+};
+
+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/xc7/attosoc.pcf b/xc7/attosoc.pcf
new file mode 100644
index 0000000..fd14331
--- /dev/null
+++ b/xc7/attosoc.pcf
@@ -0,0 +1,8 @@
+COMP "led[0]" LOCATE = SITE "M14" LEVEL 1;
+COMP "led[1]" LOCATE = SITE "M15" LEVEL 1;
+COMP "led[2]" LOCATE = SITE "G14" LEVEL 1;
+COMP "led[3]" LOCATE = SITE "D18" LEVEL 1;
+COMP "clki" LOCATE = SITE "K17" LEVEL 1;
+NET "pll.clkin1" PERIOD = 8 nS ;
+PIN "clki_pin" = BEL "clki.PAD" PINNAME PAD;
+PIN "clki_pin" CLOCK_DEDICATED_ROUTE = FALSE;
diff --git a/xc7/attosoc.sh b/xc7/attosoc.sh
new file mode 100755
index 0000000..cb04bda
--- /dev/null
+++ b/xc7/attosoc.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+set -ex
+rm -f picorv32.v attosoc.v
+wget https://raw.githubusercontent.com/cliffordwolf/picorv32/master/picorv32.v
+wget https://raw.githubusercontent.com/SymbiFlow/prjtrellis/master/examples/picorv32_versa5g/attosoc.v
+ln -sf firmware_slow.hex firmware.hex
+yosys attosoc.ys
+set +e
+../nextpnr-xc7 --json attosoc.json --xdl attosoc.xdl --pcf attosoc.pcf --freq 125
+set -e
+xdl -xdl2ncd attosoc.xdl
+bitgen -w attosoc.ncd -g UnconstrainedPins:Allow
+trce attosoc.ncd -v 10
diff --git a/xc7/attosoc.ys b/xc7/attosoc.ys
new file mode 100644
index 0000000..7ec2517
--- /dev/null
+++ b/xc7/attosoc.ys
@@ -0,0 +1,55 @@
+read_verilog attosoc_top.v
+read_verilog attosoc.v
+read_verilog picorv32.v
+
+#synth_xilinx -top picorv32
+
+#begin:
+    read_verilog -lib +/xilinx/cells_sim.v
+    read_verilog -lib +/xilinx/cells_xtra.v
+#    read_verilog -lib +/xilinx/brams_bb.v
+#    read_verilog -lib +/xilinx/drams_bb.v
+    hierarchy -check -top top
+
+#flatten:     (only if -flatten)
+    proc
+    flatten
+
+#coarse:
+    synth -run coarse
+
+#bram:
+#    memory_bram -rules +/xilinx/brams.txt
+#    techmap -map +/xilinx/brams_map.v
+#
+#dram:
+#    memory_bram -rules +/xilinx/drams.txt
+#    techmap -map +/xilinx/drams_map.v
+
+fine:
+    opt -fast -full
+    memory_map
+    dffsr2dff
+#    dff2dffe
+    opt -full
+    techmap -map +/techmap.v #-map +/xilinx/arith_map.v
+    opt -fast
+
+map_luts:
+    abc -luts 2:2,3,6:5 #,10,20 [-dff]
+    clean
+
+map_cells:
+    techmap -map +/xilinx/cells_map.v
+    dffinit -ff FDRE Q INIT -ff FDCE Q INIT -ff FDPE Q INIT
+    clean
+
+check:
+    hierarchy -check
+    stat
+    check -noinit
+
+#edif:     (only if -edif)
+#    write_edif <file-name>
+
+write_json attosoc.json
diff --git a/xc7/attosoc_sim.sh b/xc7/attosoc_sim.sh
new file mode 100755
index 0000000..61d668a
--- /dev/null
+++ b/xc7/attosoc_sim.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+set -ex
+rm -f picorv32.v attosoc.v
+wget https://raw.githubusercontent.com/cliffordwolf/picorv32/master/picorv32.v
+wget https://raw.githubusercontent.com/SymbiFlow/prjtrellis/master/examples/picorv32_versa5g/attosoc.v
+ln -sf firmware_fast.hex firmware.hex
+yosys attosoc.ys
+set +e
+../nextpnr-xc7 --json attosoc.json --xdl attosoc.xdl --pcf attosoc.pcf --freq 125
+set -e
+xdl -xdl2ncd attosoc.xdl
+#bitgen -w attosoc.ncd -g UnconstrainedPins:Allow
+trce attosoc.ncd -v 10
+
+netgen -sim -ofmt vhdl attosoc.ncd -w attosoc_pnr.vhd
+ghdl -c -fexplicit --no-vital-checks --ieee=synopsys -Pxilinx-ise attosoc_tb.vhd attosoc_pnr.vhd -r testbench
diff --git a/xc7/attosoc_tb.vhd b/xc7/attosoc_tb.vhd
new file mode 100644
index 0000000..6899618
--- /dev/null
+++ b/xc7/attosoc_tb.vhd
@@ -0,0 +1,25 @@
+library IEEE;
+use IEEE.STD_LOGIC_1164.ALL;
+
+entity testbench is
+end entity;
+architecture rtl of testbench is
+    signal clk : STD_LOGIC;
+    signal led : STD_LOGIC_VECTOR(3 downto 0);
+begin
+    process begin
+        clk <= '0';
+        wait for 4 ns;
+        clk <= '1';
+        wait for 4 ns;
+    end process;
+
+    uut: entity work.name port map(clki_PAD_PAD => clk, led_0_OUTBUF_OUT => led(0), led_1_OUTBUF_OUT => led(1), led_2_OUTBUF_OUT => led(2), led_3_OUTBUF_OUT => led(3));
+
+process
+begin
+report "led = " & std_logic'image(led(3)) & std_logic'image(led(2)) & std_logic'image(led(1)) & std_logic'image(led(0));
+wait on led;
+end process;
+
+end rtl;
diff --git a/xc7/attosoc_top.v b/xc7/attosoc_top.v
new file mode 100644
index 0000000..bbf75a0
--- /dev/null
+++ b/xc7/attosoc_top.v
@@ -0,0 +1,24 @@
+module top (
+	input clki,
+	output [3:0] led
+);
+
+    (* keep *)
+    wire led_unused;
+
+    wire clk;
+    BUFGCTRL clk_gb (
+        .I0(clki),
+        .CE0(1'b1),
+        .CE1(1'b0),
+        .S0(1'b1),
+        .S1(1'b0),
+        .IGNORE0(1'b0),
+        .IGNORE1(1'b0),
+        .O(clk)
+    );
+
+    attosoc soc(.clk(clk), .led({led_unused, led}));
+
+endmodule
+
diff --git a/xc7/blinky.pcf b/xc7/blinky.pcf
new file mode 100644
index 0000000..1b85ac5
--- /dev/null
+++ b/xc7/blinky.pcf
@@ -0,0 +1,9 @@
+COMP "led0" LOCATE = SITE "M14" LEVEL 1;
+COMP "led1" LOCATE = SITE "M15" LEVEL 1;
+COMP "led2" LOCATE = SITE "G14" LEVEL 1;
+COMP "led3" LOCATE = SITE "D18" LEVEL 1;
+COMP "clki" LOCATE = SITE "K17" LEVEL 1;
+COMP "clk_gb" LOCATE = SITE "BUFGCTRL_X0Y31" LEVEL 1;
+NET "clki" PERIOD = 8 nS ;
+PIN "clki_pin" = BEL "clki.PAD" PINNAME PAD;
+PIN "clki_pin" CLOCK_DEDICATED_ROUTE = FALSE;
diff --git a/xc7/blinky.proj b/xc7/blinky.proj
new file mode 100644
index 0000000..f5bb9f8
--- /dev/null
+++ b/xc7/blinky.proj
@@ -0,0 +1,15 @@
+{
+    "project": {
+        "version": "1",
+        "name": "blinky",
+        "arch": {
+            "name": "ice40",
+            "type": "hx1k",
+            "package": "tq144"
+        },
+        "input": {
+            "json": "blinky.json",
+            "pcf": "blinky.pcf"
+        }
+    }
+}
diff --git a/xc7/blinky.sh b/xc7/blinky.sh
new file mode 100755
index 0000000..6bc6857
--- /dev/null
+++ b/xc7/blinky.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+set -ex
+yosys blinky.ys
+../nextpnr-xc7 --json blinky.json --pcf blinky.pcf --xdl blinky.xdl --freq 125
+xdl -xdl2ncd blinky.xdl
+bitgen -w blinky.ncd -g UnconstrainedPins:Allow
diff --git a/xc7/blinky.v b/xc7/blinky.v
new file mode 100644
index 0000000..692c7ab
--- /dev/null
+++ b/xc7/blinky.v
@@ -0,0 +1,32 @@
+module blinky (
+    input  clki,
+    output led0,
+    output led1,
+    output led2,
+    output led3
+);
+    wire clk;
+    BUFGCTRL clk_gb (
+        .I0(clki),
+        .CE0(1'b1),
+        .CE1(1'b0),
+        .S0(1'b1),
+        .S1(1'b0),
+        .IGNORE0(1'b0),
+        .IGNORE1(1'b0),
+        .O(clk)
+    );
+
+    localparam BITS = 4;
+    parameter LOG2DELAY = 23;
+
+    reg [BITS+LOG2DELAY-1:0] counter = 0;
+    reg [BITS-1:0] outcnt;
+
+    always @(posedge clk) begin
+        counter <= counter + 1;
+        outcnt <= counter >> LOG2DELAY;
+    end
+
+    assign {led0, led1, led2, led3} = outcnt ^ (outcnt >> 1);
+endmodule
diff --git a/xc7/blinky.ys b/xc7/blinky.ys
new file mode 100644
index 0000000..090b0ab
--- /dev/null
+++ b/xc7/blinky.ys
@@ -0,0 +1,53 @@
+read_verilog blinky.v
+
+#synth_xilinx -top blinky
+
+#begin:
+    read_verilog -lib +/xilinx/cells_sim.v
+    read_verilog -lib +/xilinx/cells_xtra.v
+#    read_verilog -lib +/xilinx/brams_bb.v
+#    read_verilog -lib +/xilinx/drams_bb.v
+    hierarchy -check -top blinky
+
+#flatten:     (only if -flatten)
+    proc
+    flatten
+
+#coarse:
+    synth -run coarse
+
+#bram:
+#    memory_bram -rules +/xilinx/brams.txt
+#    techmap -map +/xilinx/brams_map.v
+#
+#dram:
+#    memory_bram -rules +/xilinx/drams.txt
+#    techmap -map +/xilinx/drams_map.v
+
+fine:
+    opt -fast -full
+    memory_map
+    dffsr2dff
+#    dff2dffe
+    opt -full
+    techmap -map +/techmap.v #-map +/xilinx/arith_map.v
+    opt -fast
+
+map_luts:
+    abc -luts 2:2,3,6:5 #,10,20 [-dff]
+    clean
+
+map_cells:
+    techmap -map +/xilinx/cells_map.v
+    dffinit -ff FDRE Q INIT -ff FDCE Q INIT -ff FDPE Q INIT
+    clean
+
+check:
+    hierarchy -check
+    stat
+    check -noinit
+
+#edif:     (only if -edif)
+#    write_edif <file-name>
+
+write_json blinky.json
diff --git a/xc7/blinky_sim.sh b/xc7/blinky_sim.sh
new file mode 100755
index 0000000..e353a40
--- /dev/null
+++ b/xc7/blinky_sim.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+set -ex
+yosys blinky_sim.ys
+../nextpnr-xc7 --json blinky.json --pcf blinky.pcf --xdl blinky.xdl --freq 125
+xdl -xdl2ncd blinky.xdl
+trce blinky.ncd -v 10
+netgen -sim -ofmt vhdl blinky.ncd -w blinky_pnr.vhd
+ghdl -c -fexplicit --no-vital-checks --ieee=synopsys -Pxilinx-ise blinky_tb.vhd blinky_pnr.vhd -r testbench
diff --git a/xc7/blinky_sim.ys b/xc7/blinky_sim.ys
new file mode 100644
index 0000000..732e1e3
--- /dev/null
+++ b/xc7/blinky_sim.ys
@@ -0,0 +1,54 @@
+read_verilog blinky.v
+chparam -set LOG2DELAY 0
+
+#synth_xilinx -top blinky
+
+#begin:
+    read_verilog -lib +/xilinx/cells_sim.v
+    read_verilog -lib +/xilinx/cells_xtra.v
+#    read_verilog -lib +/xilinx/brams_bb.v
+#    read_verilog -lib +/xilinx/drams_bb.v
+    hierarchy -check -top blinky
+
+#flatten:     (only if -flatten)
+    proc
+    flatten
+
+#coarse:
+    synth -run coarse
+
+#bram:
+#    memory_bram -rules +/xilinx/brams.txt
+#    techmap -map +/xilinx/brams_map.v
+#
+#dram:
+#    memory_bram -rules +/xilinx/drams.txt
+#    techmap -map +/xilinx/drams_map.v
+
+fine:
+    opt -fast -full
+    memory_map
+    dffsr2dff
+#    dff2dffe
+    opt -full
+    techmap -map +/techmap.v #-map +/xilinx/arith_map.v
+    opt -fast
+
+map_luts:
+    abc -luts 2:2,3,6:5 #,10,20 [-dff]
+    clean
+
+map_cells:
+    techmap -map +/xilinx/cells_map.v
+    dffinit -ff FDRE Q INIT -ff FDCE Q INIT -ff FDPE Q INIT
+    clean
+
+check:
+    hierarchy -check
+    stat
+    check -noinit
+
+#edif:     (only if -edif)
+#    write_edif <file-name>
+
+write_json blinky.json
diff --git a/xc7/blinky_tb.vhd b/xc7/blinky_tb.vhd
new file mode 100644
index 0000000..29b5030
--- /dev/null
+++ b/xc7/blinky_tb.vhd
@@ -0,0 +1,25 @@
+library IEEE;
+use IEEE.STD_LOGIC_1164.ALL;
+
+entity testbench is
+end entity;
+architecture rtl of testbench is
+    signal clk : STD_LOGIC;
+    signal led : STD_LOGIC_VECTOR(3 downto 0);
+begin
+    process begin
+        clk <= '0';
+        wait for 4 ns;
+        clk <= '1';
+        wait for 4 ns;
+    end process;
+
+    uut: entity work.name port map(clki_PAD_PAD => clk, led0_OUTBUF_OUT => led(0), led1_OUTBUF_OUT => led(1), led2_OUTBUF_OUT => led(2), led3_OUTBUF_OUT => led(3));
+
+process
+begin
+report std_logic'image(led(3)) & std_logic'image(led(2)) & std_logic'image(led(1)) & std_logic'image(led(0));
+wait on led;
+end process;
+
+end rtl;
diff --git a/xc7/cells.cc b/xc7/cells.cc
new file mode 100644
index 0000000..601aacc
--- /dev/null
+++ b/xc7/cells.cc
@@ -0,0 +1,242 @@
+/*
+ *  nextpnr -- Next Generation Place and Route
+ *
+ *  Copyright (C) 2018  Clifford Wolf <clifford@symbioticeda.com>
+ *  Copyright (C) 2018  David Shah <david@symbioticeda.com>
+ *  Copyright (C) 2018  Serge Bazanski <q3k@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 "cells.h"
+#include "design_utils.h"
+#include "log.h"
+#include "util.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+void add_port(const Context *ctx, CellInfo *cell, std::string name, PortType dir)
+{
+    IdString id = ctx->id(name);
+    cell->ports[id] = PortInfo{id, nullptr, dir};
+}
+
+std::unique_ptr<CellInfo> create_xc7_cell(Context *ctx, IdString type, std::string name)
+{
+    static int auto_idx = 0;
+    std::unique_ptr<CellInfo> new_cell = std::unique_ptr<CellInfo>(new CellInfo());
+    if (name.empty()) {
+        new_cell->name = ctx->id("$nextpnr_" + type.str(ctx) + "_" + std::to_string(auto_idx++));
+    } else {
+        new_cell->name = ctx->id(name);
+    }
+    new_cell->type = type;
+    if (type == ctx->id("XC7_LC")) {
+        new_cell->type = id_SLICE_LUT6;
+        new_cell->params[ctx->id("INIT")] = "0";
+        new_cell->params[ctx->id("NEG_CLK")] = "0";
+        new_cell->params[ctx->id("CARRY_ENABLE")] = "0";
+        new_cell->params[ctx->id("DFF_ENABLE")] = "0";
+        new_cell->params[ctx->id("CIN_CONST")] = "0";
+        new_cell->params[ctx->id("CIN_SET")] = "0";
+
+        add_port(ctx, new_cell.get(), "I1", PORT_IN);
+        add_port(ctx, new_cell.get(), "I2", PORT_IN);
+        add_port(ctx, new_cell.get(), "I3", PORT_IN);
+        add_port(ctx, new_cell.get(), "I4", PORT_IN);
+        add_port(ctx, new_cell.get(), "I5", PORT_IN);
+        add_port(ctx, new_cell.get(), "I6", PORT_IN);
+        add_port(ctx, new_cell.get(), "CIN", PORT_IN);
+
+        add_port(ctx, new_cell.get(), "CLK", PORT_IN);
+        add_port(ctx, new_cell.get(), "CE", PORT_IN);
+        add_port(ctx, new_cell.get(), "SR", PORT_IN);
+
+        add_port(ctx, new_cell.get(), "O", PORT_OUT);
+        add_port(ctx, new_cell.get(), "OQ", PORT_OUT);
+        add_port(ctx, new_cell.get(), "OMUX", PORT_OUT);
+        add_port(ctx, new_cell.get(), "COUT", PORT_OUT);
+    } else if (type == ctx->id("IOBUF")) {
+	    if (ctx->args.type == ArchArgs::Z020)
+		    new_cell->type = id_IOB33;
+	    else
+		    new_cell->type = id_IOB18;
+        add_port(ctx, new_cell.get(), "I", PORT_OUT);
+        add_port(ctx, new_cell.get(), "O", PORT_IN);
+    } else if (type == id_BUFGCTRL) {
+        add_port(ctx, new_cell.get(), "I0", PORT_IN);
+        add_port(ctx, new_cell.get(), "O", PORT_OUT);
+    } else {
+        log_error("unable to create XC7 cell of type %s\n", type.c_str(ctx));
+    }
+    return new_cell;
+}
+
+void lut_to_lc(const Context *ctx, CellInfo *lut, CellInfo *lc, bool no_dff)
+{
+    lc->params[ctx->id("INIT")] = lut->params[ctx->id("INIT")];
+    int i = 6;
+    if (get_net_or_empty(lut, id_I5))
+        replace_port(lut, id_I5, lc, ctx->id("I" + std::to_string(i--)));
+    if (get_net_or_empty(lut, id_I4))
+        replace_port(lut, id_I4, lc, ctx->id("I" + std::to_string(i--)));
+    if (get_net_or_empty(lut, id_I3))
+        replace_port(lut, id_I3, lc, ctx->id("I" + std::to_string(i--)));
+    if (get_net_or_empty(lut, id_I2))
+        replace_port(lut, id_I2, lc, ctx->id("I" + std::to_string(i--)));
+    if (get_net_or_empty(lut, id_I1))
+        replace_port(lut, id_I1, lc, ctx->id("I" + std::to_string(i--)));
+    replace_port(lut, ctx->id("I0"), lc, ctx->id("I" + std::to_string(i--)));
+    if (no_dff) {
+        replace_port(lut, id_O, lc, id_O);
+        lc->params[ctx->id("DFF_ENABLE")] = "0";
+    }
+    lc->params[ctx->id("LUT_NAME")] = lut->name.str(ctx);
+}
+
+void dff_to_lc(const Context *ctx, CellInfo *dff, CellInfo *lc, bool pass_thru_lut)
+{
+    lc->params[ctx->id("DFF_ENABLE")] = "1";
+    std::string config = dff->type.str(ctx).substr(2);
+    auto citer = config.begin();
+    replace_port(dff, ctx->id("C"), lc, id_CLK);
+
+    if (citer != config.end()) {
+        auto gnd_net = ctx->nets.at(ctx->id("$PACKER_GND_NET")).get();
+
+        if (*citer == 'S') {
+            citer++;
+            if (get_net_or_empty(dff, id_S) != gnd_net) {
+                lc->params[id_SR] = "SRHIGH";
+                replace_port(dff, id_S, lc, id_SR);
+            }
+            else
+                disconnect_port(ctx, dff, id_S);
+            lc->params[ctx->id("SYNC_ATTR")] = "SYNC";
+        } else if (*citer == 'R') {
+            citer++;
+            if (get_net_or_empty(dff, id_R) != gnd_net) {
+                lc->params[id_SR] = "SRLOW";
+                replace_port(dff, id_R, lc, id_SR);
+            }
+            else
+                disconnect_port(ctx, dff, id_R);
+            lc->params[ctx->id("SYNC_ATTR")] = "SYNC";
+        } else if (*citer == 'C') {
+            citer++;
+            if (get_net_or_empty(dff, id_CLR) != gnd_net) {
+                lc->params[id_SR] = "SRLOW";
+                replace_port(dff, id_CLR, lc, id_SR);
+            }
+            else
+                disconnect_port(ctx, dff, id_CLR);
+            lc->params[ctx->id("SYNC_ATTR")] = "ASYNC";
+        } else {
+            NPNR_ASSERT(*citer == 'P');
+            citer++;
+            if (get_net_or_empty(dff, id_PRE) != gnd_net) {
+                lc->params[id_SR] = "SRHIGH";
+                replace_port(dff, id_PRE, lc, id_SR);
+            }
+            else
+                disconnect_port(ctx, dff, id_PRE);
+            lc->params[ctx->id("SYNC_ATTR")] = "ASYNC";
+        }
+    }
+
+    if (citer != config.end() && *citer == 'E') {
+        auto vcc_net = ctx->nets.at(ctx->id("$PACKER_VCC_NET")).get();
+
+        ++citer;
+        if (get_net_or_empty(dff, ctx->id("CE")) != vcc_net)
+            replace_port(dff, ctx->id("CE"), lc, ctx->id("CE"));
+        else
+            disconnect_port(ctx, dff, ctx->id("CE"));
+    }
+
+    NPNR_ASSERT(citer == config.end());
+
+    if (pass_thru_lut) {
+        lc->params[ctx->id("INIT")] = "2";
+        replace_port(dff, ctx->id("D"), lc, id_I1);
+    }
+
+    replace_port(dff, ctx->id("Q"), lc, id_OQ);
+
+    auto it = dff->params.find(ctx->id("INIT"));
+    if (it != dff->params.end())
+        lc->params[ctx->id("FFINIT")] = it->second == "1" ? "INIT1" : "INIT0";
+}
+
+void nxio_to_sb(Context *ctx, CellInfo *nxio, CellInfo *sbio)
+{
+    if (nxio->type == ctx->id("$nextpnr_ibuf")) {
+        sbio->params[ctx->id("PIN_TYPE")] = "1";
+        auto pu_attr = nxio->attrs.find(ctx->id("PULLUP"));
+        if (pu_attr != nxio->attrs.end())
+            sbio->params[ctx->id("PULLUP")] = pu_attr->second;
+        replace_port(nxio, id_O, sbio, id_I);
+    } else if (nxio->type == ctx->id("$nextpnr_obuf")) {
+        sbio->params[ctx->id("PIN_TYPE")] = "25";
+        replace_port(nxio, id_I, sbio, id_O);
+    } else if (nxio->type == ctx->id("$nextpnr_iobuf")) {
+        // N.B. tristate will be dealt with below
+        sbio->params[ctx->id("PIN_TYPE")] = "25";
+        replace_port(nxio, id_I, sbio, id_O);
+        replace_port(nxio, id_O, sbio, id_I);
+    } else {
+        NPNR_ASSERT(false);
+    }
+    NetInfo *donet = sbio->ports.at(id_O).net;
+    CellInfo *tbuf = net_driven_by(
+            ctx, donet, [](const Context *ctx, const CellInfo *cell) { return cell->type == ctx->id("$_TBUF_"); },
+            ctx->id("Y"));
+    if (tbuf) {
+        sbio->params[ctx->id("PIN_TYPE")] = "41";
+        replace_port(tbuf, ctx->id("A"), sbio, id_O);
+        replace_port(tbuf, ctx->id("E"), sbio, ctx->id("OUTPUT_ENABLE"));
+        ctx->nets.erase(donet->name);
+        if (!donet->users.empty())
+            log_error("unsupported tristate IO pattern for IO buffer '%s', "
+                      "instantiate SB_IO manually to ensure correct behaviour\n",
+                      nxio->name.c_str(ctx));
+        ctx->cells.erase(tbuf->name);
+    }
+}
+
+bool is_clock_port(const BaseCtx *ctx, const PortRef &port)
+{
+    if (port.cell == nullptr)
+        return false;
+    NPNR_ASSERT("TODO");
+    return false;
+}
+
+bool is_reset_port(const BaseCtx *ctx, const PortRef &port)
+{
+    if (port.cell == nullptr)
+        return false;
+    NPNR_ASSERT("TODO");
+    return false;
+}
+
+bool is_enable_port(const BaseCtx *ctx, const PortRef &port)
+{
+    if (port.cell == nullptr)
+        return false;
+    NPNR_ASSERT("TODO");
+    return false;
+}
+
+NEXTPNR_NAMESPACE_END
diff --git a/xc7/cells.h b/xc7/cells.h
new file mode 100644
index 0000000..c5956eb
--- /dev/null
+++ b/xc7/cells.h
@@ -0,0 +1,110 @@
+/*
+ *  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.
+ *
+ */
+
+#include "nextpnr.h"
+
+#ifndef ICE40_CELLS_H
+#define ICE40_CELLS_H
+
+NEXTPNR_NAMESPACE_BEGIN
+
+// Create a standard xc7 cell and return it
+// Name will be automatically assigned if not specified
+std::unique_ptr<CellInfo> create_xc7_cell(Context *ctx, IdString type, std::string name = "");
+
+// Return true if a cell is a LUT
+inline bool is_lut(const BaseCtx *ctx, const CellInfo *cell)
+{
+    return cell->type == id_LUT1 || cell->type == id_LUT2 || cell->type == id_LUT3 || cell->type == id_LUT4 ||
+           cell->type == id_LUT5 || cell->type == id_LUT6;
+}
+
+// Return true if a cell is a flipflop
+inline bool is_ff(const BaseCtx *ctx, const CellInfo *cell)
+{
+    return cell->type == id_FDRE || cell->type == id_FDSE || cell->type == id_FDCE || cell->type == id_FDPE;
+}
+
+inline bool is_carry(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_CARRY"); }
+
+inline bool is_lc(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("XC7_LC"); }
+
+// Return true if a cell is a SB_IO
+inline bool is_sb_io(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_IO"); }
+
+// Return true if a cell is a global buffer
+inline bool is_gbuf(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == id_BUFGCTRL; }
+
+// Return true if a cell is a RAM
+inline bool is_ram(const BaseCtx *ctx, const CellInfo *cell)
+{
+    return cell->type == ctx->id("SB_RAM40_4K") || cell->type == ctx->id("SB_RAM40_4KNR") ||
+           cell->type == ctx->id("SB_RAM40_4KNW") || cell->type == ctx->id("SB_RAM40_4KNRNW");
+}
+
+inline bool is_sb_lfosc(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_LFOSC"); }
+
+inline bool is_sb_hfosc(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_HFOSC"); }
+
+inline bool is_sb_spram(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_SPRAM256KA"); }
+
+inline bool is_sb_mac16(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_MAC16"); }
+
+inline bool is_sb_pll40(const BaseCtx *ctx, const CellInfo *cell)
+{
+    return cell->type == ctx->id("SB_PLL40_PAD") || cell->type == ctx->id("SB_PLL40_2_PAD") ||
+           cell->type == ctx->id("SB_PLL40_2F_PAD") || cell->type == ctx->id("SB_PLL40_CORE") ||
+           cell->type == ctx->id("SB_PLL40_2F_CORE");
+}
+
+inline bool is_sb_pll40_pad(const BaseCtx *ctx, const CellInfo *cell)
+{
+    return cell->type == ctx->id("SB_PLL40_PAD") || cell->type == ctx->id("SB_PLL40_2_PAD") ||
+           cell->type == ctx->id("SB_PLL40_2F_PAD");
+}
+
+uint8_t sb_pll40_type(const BaseCtx *ctx, const CellInfo *cell);
+
+// Convert a SB_LUT primitive to (part of) an ICESTORM_LC, swapping ports
+// as needed. Set no_dff if a DFF is not being used, so that the output
+// can be reconnected
+void lut_to_lc(const Context *ctx, CellInfo *lut, CellInfo *lc, bool no_dff = true);
+
+// Convert a SB_DFFx primitive to (part of) an ICESTORM_LC, setting parameters
+// and reconnecting signals as necessary. If pass_thru_lut is True, the LUT will
+// be configured as pass through and D connected to I0, otherwise D will be
+// ignored
+void dff_to_lc(const Context *ctx, CellInfo *dff, CellInfo *lc, bool pass_thru_lut = false);
+
+// Convert a nextpnr IO buffer to a SB_IO
+void nxio_to_sb(Context *ctx, CellInfo *nxio, CellInfo *sbio);
+
+// Return true if a port is a clock port
+bool is_clock_port(const BaseCtx *ctx, const PortRef &port);
+
+// Return true if a port is a reset port
+bool is_reset_port(const BaseCtx *ctx, const PortRef &port);
+
+// Return true if a port is a clock enable port
+bool is_enable_port(const BaseCtx *ctx, const PortRef &port);
+
+NEXTPNR_NAMESPACE_END
+
+#endif
diff --git a/xc7/chains.cc b/xc7/chains.cc
new file mode 100644
index 0000000..be1d676
--- /dev/null
+++ b/xc7/chains.cc
@@ -0,0 +1,288 @@
+/*
+ *  nextpnr -- Next Generation Place and Route
+ *
+ *  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.
+ *
+ */
+
+#include "chains.h"
+#include <algorithm>
+#include <vector>
+#include "cells.h"
+#include "design_utils.h"
+#include "log.h"
+#include "place_common.h"
+#include "util.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+struct CellChain
+{
+    std::vector<CellInfo *> cells;
+};
+
+// Generic chain finder
+template <typename F1, typename F2, typename F3>
+std::vector<CellChain> find_chains(const Context *ctx, F1 cell_type_predicate, F2 get_previous, F3 get_next,
+                                   size_t min_length = 2)
+{
+    std::set<IdString> chained;
+    std::vector<CellChain> chains;
+    for (auto cell : sorted(ctx->cells)) {
+        if (chained.find(cell.first) != chained.end())
+            continue;
+        CellInfo *ci = cell.second;
+        if (cell_type_predicate(ctx, ci)) {
+            CellInfo *start = ci;
+            CellInfo *prev_start = ci;
+            while (prev_start != nullptr) {
+                start = prev_start;
+                prev_start = get_previous(ctx, start);
+            }
+            CellChain chain;
+            CellInfo *end = start;
+            while (end != nullptr) {
+                chain.cells.push_back(end);
+                end = get_next(ctx, end);
+            }
+            if (chain.cells.size() >= min_length) {
+                chains.push_back(chain);
+                for (auto c : chain.cells)
+                    chained.insert(c->name);
+            }
+        }
+    }
+    return chains;
+}
+
+class ChainConstrainer
+{
+  private:
+    Context *ctx;
+    // Split a carry chain into multiple legal chains
+    std::vector<CellChain> split_carry_chain(CellChain &carryc)
+    {
+        bool start_of_chain = true;
+        std::vector<CellChain> chains;
+        std::vector<const CellInfo *> tile;
+        const int max_length = (ctx->chip_info->height - 2) * 8 - 2;
+        auto curr_cell = carryc.cells.begin();
+        while (curr_cell != carryc.cells.end()) {
+            CellInfo *cell = *curr_cell;
+            if (tile.size() >= 8) {
+                tile.clear();
+            }
+            if (start_of_chain) {
+                tile.clear();
+                chains.emplace_back();
+                start_of_chain = false;
+                if (cell->ports.at(ctx->id("CIN")).net) {
+                    // CIN is not constant and not part of a chain. Must feed in from fabric
+                    CellInfo *feedin = make_carry_feed_in(cell, cell->ports.at(ctx->id("CIN")));
+                    chains.back().cells.push_back(feedin);
+                    tile.push_back(feedin);
+                }
+            }
+            tile.push_back(cell);
+            chains.back().cells.push_back(cell);
+            bool split_chain = (!ctx->logicCellsCompatible(tile.data(), tile.size())) ||
+                               (int(chains.back().cells.size()) > max_length);
+            if (split_chain) {
+                CellInfo *passout = make_carry_pass_out(cell->ports.at(ctx->id("COUT")));
+                tile.pop_back();
+                chains.back().cells.back() = passout;
+                start_of_chain = true;
+            } else {
+                NetInfo *carry_net = cell->ports.at(ctx->id("COUT")).net;
+                bool at_end = (curr_cell == carryc.cells.end() - 1);
+                if (carry_net != nullptr && (carry_net->users.size() > 1 || at_end)) {
+                    if (carry_net->users.size() > 2 ||
+                        (net_only_drives(ctx, carry_net, is_lc, ctx->id("I3"), false) !=
+                         net_only_drives(ctx, carry_net, is_lc, ctx->id("CIN"), false)) ||
+                        (at_end && !net_only_drives(ctx, carry_net, is_lc, ctx->id("I3"), true))) {
+                        CellInfo *passout = make_carry_pass_out(cell->ports.at(ctx->id("COUT")));
+                        chains.back().cells.push_back(passout);
+                        tile.push_back(passout);
+                        start_of_chain = true;
+                    }
+                }
+                ++curr_cell;
+            }
+        }
+        return chains;
+    }
+
+    // Insert a logic cell to legalise a COUT->fabric connection
+    CellInfo *make_carry_pass_out(PortInfo &cout_port)
+    {
+        NPNR_ASSERT(cout_port.net != nullptr);
+        std::unique_ptr<CellInfo> lc = create_xc7_cell(ctx, ctx->id("ICESTORM_LC"));
+        lc->params[ctx->id("LUT_INIT")] = "65280"; // 0xff00: O = I3
+        lc->params[ctx->id("CARRY_ENABLE")] = "1";
+        lc->ports.at(ctx->id("O")).net = cout_port.net;
+        std::unique_ptr<NetInfo> co_i3_net(new NetInfo());
+        co_i3_net->name = ctx->id(lc->name.str(ctx) + "$I3");
+        co_i3_net->driver = cout_port.net->driver;
+        PortRef i3_r;
+        i3_r.port = ctx->id("I3");
+        i3_r.cell = lc.get();
+        co_i3_net->users.push_back(i3_r);
+        PortRef o_r;
+        o_r.port = ctx->id("O");
+        o_r.cell = lc.get();
+        cout_port.net->driver = o_r;
+        lc->ports.at(ctx->id("I3")).net = co_i3_net.get();
+        cout_port.net = co_i3_net.get();
+
+        IdString co_i3_name = co_i3_net->name;
+        NPNR_ASSERT(ctx->nets.find(co_i3_name) == ctx->nets.end());
+        ctx->nets[co_i3_name] = std::move(co_i3_net);
+        IdString name = lc->name;
+        ctx->assignCellInfo(lc.get());
+        ctx->cells[lc->name] = std::move(lc);
+        return ctx->cells[name].get();
+    }
+
+    // Insert a logic cell to legalise a CIN->fabric connection
+    CellInfo *make_carry_feed_in(CellInfo *cin_cell, PortInfo &cin_port)
+    {
+        NPNR_ASSERT(cin_port.net != nullptr);
+        std::unique_ptr<CellInfo> lc = create_xc7_cell(ctx, ctx->id("ICESTORM_LC"));
+        lc->params[ctx->id("CARRY_ENABLE")] = "1";
+        lc->params[ctx->id("CIN_CONST")] = "1";
+        lc->params[ctx->id("CIN_SET")] = "1";
+        lc->ports.at(ctx->id("I1")).net = cin_port.net;
+        cin_port.net->users.erase(std::remove_if(cin_port.net->users.begin(), cin_port.net->users.end(),
+                                                 [cin_cell, cin_port](const PortRef &usr) {
+                                                     return usr.cell == cin_cell && usr.port == cin_port.name;
+                                                 }));
+
+        PortRef i1_ref;
+        i1_ref.cell = lc.get();
+        i1_ref.port = ctx->id("I1");
+        lc->ports.at(ctx->id("I1")).net->users.push_back(i1_ref);
+
+        std::unique_ptr<NetInfo> out_net(new NetInfo());
+        out_net->name = ctx->id(lc->name.str(ctx) + "$O");
+
+        PortRef drv_ref;
+        drv_ref.port = ctx->id("COUT");
+        drv_ref.cell = lc.get();
+        out_net->driver = drv_ref;
+        lc->ports.at(ctx->id("COUT")).net = out_net.get();
+
+        PortRef usr_ref;
+        usr_ref.port = cin_port.name;
+        usr_ref.cell = cin_cell;
+        out_net->users.push_back(usr_ref);
+        cin_cell->ports.at(cin_port.name).net = out_net.get();
+
+        IdString out_net_name = out_net->name;
+        NPNR_ASSERT(ctx->nets.find(out_net_name) == ctx->nets.end());
+        ctx->nets[out_net_name] = std::move(out_net);
+
+        IdString name = lc->name;
+        ctx->assignCellInfo(lc.get());
+        ctx->cells[lc->name] = std::move(lc);
+        return ctx->cells[name].get();
+    }
+
+    void process_carries()
+    {
+        std::vector<CellChain> carry_chains =
+                find_chains(ctx, [](const Context *ctx, const CellInfo *cell) { return is_lc(ctx, cell); },
+                            [](const Context *ctx, const
+
+                               CellInfo *cell) {
+                                CellInfo *carry_prev =
+                                        net_driven_by(ctx, cell->ports.at(ctx->id("CIN")).net, is_lc, ctx->id("COUT"));
+                                if (carry_prev != nullptr)
+                                    return carry_prev;
+                                /*CellInfo *i3_prev = net_driven_by(ctx, cell->ports.at(ctx->id("I3")).net, is_lc,
+                                ctx->id("COUT")); if (i3_prev != nullptr) return i3_prev;*/
+                                return (CellInfo *)nullptr;
+                            },
+                            [](const Context *ctx, const CellInfo *cell) {
+                                CellInfo *carry_next = net_only_drives(ctx, cell->ports.at(ctx->id("COUT")).net, is_lc,
+                                                                       ctx->id("CIN"), false);
+                                if (carry_next != nullptr)
+                                    return carry_next;
+                                /*CellInfo *i3_next =
+                                        net_only_drives(ctx, cell->ports.at(ctx->id("COUT")).net, is_lc, ctx->id("I3"),
+                                false); if (i3_next != nullptr) return i3_next;*/
+                                return (CellInfo *)nullptr;
+                            });
+        std::unordered_set<IdString> chained;
+        for (auto &base_chain : carry_chains) {
+            for (auto c : base_chain.cells)
+                chained.insert(c->name);
+        }
+        // Any cells not in chains, but with carry enabled, must also be put in a single-carry chain
+        // for correct processing
+        for (auto cell : sorted(ctx->cells)) {
+            CellInfo *ci = cell.second;
+            if (chained.find(cell.first) == chained.end() && is_lc(ctx, ci) &&
+                bool_or_default(ci->params, ctx->id("CARRY_ENABLE"))) {
+                CellChain sChain;
+                sChain.cells.push_back(ci);
+                chained.insert(cell.first);
+                carry_chains.push_back(sChain);
+            }
+        }
+        std::vector<CellChain> all_chains;
+        // Chain splitting
+        for (auto &base_chain : carry_chains) {
+            if (ctx->verbose) {
+                log_info("Found carry chain: \n");
+                for (auto entry : base_chain.cells)
+                    log_info("     %s\n", entry->name.c_str(ctx));
+                log_info("\n");
+            }
+            std::vector<CellChain> split_chains = split_carry_chain(base_chain);
+            for (auto &chain : split_chains) {
+                all_chains.push_back(chain);
+            }
+        }
+        // Actual chain placement
+        for (auto &chain : all_chains) {
+            if (ctx->verbose)
+                log_info("Placing carry chain starting at '%s'\n", chain.cells.front()->name.c_str(ctx));
+
+            // Place carry chain
+            chain.cells.at(0)->constr_abs_z = true;
+            chain.cells.at(0)->constr_z = 0;
+            for (int i = 1; i < int(chain.cells.size()); i++) {
+                chain.cells.at(i)->constr_x = 0;
+                chain.cells.at(i)->constr_y = (i / 8);
+                chain.cells.at(i)->constr_z = i % 8;
+                chain.cells.at(i)->constr_abs_z = true;
+                chain.cells.at(i)->constr_parent = chain.cells.at(0);
+                chain.cells.at(0)->constr_children.push_back(chain.cells.at(i));
+            }
+        }
+    }
+
+  public:
+    ChainConstrainer(Context *ctx) : ctx(ctx){};
+    void constrain_chains() { process_carries(); }
+};
+
+void constrain_chains(Context *ctx)
+{
+    log_info("Constraining chains...\n");
+    ChainConstrainer(ctx).constrain_chains();
+}
+
+NEXTPNR_NAMESPACE_END
diff --git a/xc7/chains.h b/xc7/chains.h
new file mode 100644
index 0000000..9811230
--- /dev/null
+++ b/xc7/chains.h
@@ -0,0 +1,27 @@
+/*
+ *  nextpnr -- Next Generation Place and Route
+ *
+ *  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.
+ *
+ */
+
+#include "nextpnr.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+// This finds chains, inserts LCs to legalise them as needed, and sets relative constraints as appropriate
+void constrain_chains(Context *ctx);
+
+NEXTPNR_NAMESPACE_END
diff --git a/xc7/chipdb.py b/xc7/chipdb.py
new file mode 100644
index 0000000..7bdf82f
--- /dev/null
+++ b/xc7/chipdb.py
@@ -0,0 +1,1280 @@
+#!/usr/bin/env python3
+
+import sys
+import re
+import textwrap
+import argparse
+
+parser = argparse.ArgumentParser(description="convert ICE40 chip database")
+parser.add_argument("filename", type=str, help="chipdb input filename")
+parser.add_argument("-p", "--constids", type=str, help="path to constids.inc")
+parser.add_argument("-g", "--gfxh", type=str, help="path to gfx.h")
+parser.add_argument("--fast", type=str, help="path to timing data for fast part")
+parser.add_argument("--slow", type=str, help="path to timing data for slow part")
+args = parser.parse_args()
+
+dev_name = None
+dev_width = None
+dev_height = None
+num_wires = None
+
+tiles = dict()
+
+wire_uphill = dict()
+wire_downhill = dict()
+pip_xy = dict()
+
+bel_name = list()
+bel_type = list()
+bel_pos = list()
+bel_wires = list()
+
+switches = list()
+
+ierens = list()
+
+extra_cells = dict()
+extra_cell_config = dict()
+packages = list()
+
+wire_belports = dict()
+
+wire_names = dict()
+wire_names_r = dict()
+wire_xy = dict()
+
+cbit_re = re.compile(r'B(\d+)\[(\d+)\]')
+
+constids = dict()
+tiletypes = dict()
+wiretypes = dict()
+
+gfx_wire_ids = dict()
+wire_segments = dict()
+
+fast_timings = None
+slow_timings = None
+
+with open(args.constids) as f:
+    for line in f:
+        if line.startswith("//"):
+            continue
+        line = line.replace("(", " ")
+        line = line.replace(")", " ")
+        line = line.split()
+        if len(line) == 0:
+            continue
+        assert len(line) == 2
+        assert line[0] == "X"
+        idx = len(constids) + 1
+        constids[line[1]] = idx
+
+constids["PLL"] = constids["ICESTORM_PLL"]
+constids["WARMBOOT"] = constids["SB_WARMBOOT"]
+constids["MAC16"] = constids["ICESTORM_DSP"]
+constids["HFOSC"] = constids["ICESTORM_HFOSC"]
+constids["LFOSC"] = constids["ICESTORM_LFOSC"]
+constids["I2C"] = constids["SB_I2C"]
+constids["SPI"] = constids["SB_SPI"]
+constids["LEDDA_IP"] = constids["SB_LEDDA_IP"]
+constids["RGBA_DRV"] = constids["SB_RGBA_DRV"]
+constids["SPRAM"] = constids["ICESTORM_SPRAM"]
+
+with open(args.gfxh) as f:
+    state = 0
+    for line in f:
+        if state == 0 and line.startswith("enum GfxTileWireId"):
+            state = 1
+        elif state == 1 and line.startswith("};"):
+            state = 0
+        elif state == 1 and (line.startswith("{") or line.strip() == ""):
+            pass
+        elif state == 1:
+            idx = len(gfx_wire_ids)
+            name = line.strip().rstrip(",")
+            gfx_wire_ids[name] = idx
+
+def read_timings(filename):
+    db = dict()
+    with open(filename) as f:
+        cell = None
+        for line in f:
+            line = line.split()
+            if len(line) == 0:
+                continue
+            if line[0] == "CELL":
+                cell = line[1]
+            if line[0] == "IOPATH":
+                key = "%s.%s.%s" % (cell, line[1], line[2])
+                v1 = line[3].split(":")[2]
+                v2 = line[4].split(":")[2]
+                v1 = 0 if v1 == "*" else float(v1)
+                v2 = 0 if v2 == "*" else float(v2)
+                db[key] = max(v1, v2)
+    return db
+
+if args.fast is not None:
+    fast_timings = read_timings(args.fast)
+
+if args.slow is not None:
+    slow_timings = read_timings(args.slow)
+
+tiletypes["NONE"] = 0
+tiletypes["LOGIC"] = 1
+tiletypes["IO"] = 2
+tiletypes["RAMB"] = 3
+tiletypes["RAMT"] = 4
+tiletypes["DSP0"] = 5
+tiletypes["DSP1"] = 6
+tiletypes["DSP2"] = 7
+tiletypes["DSP3"] = 8
+tiletypes["IPCON"] = 9
+
+wiretypes["NONE"]         = 0
+wiretypes["GLB2LOCAL"]    = 1
+wiretypes["GLB_NETWK"]    = 2
+wiretypes["LOCAL"]        = 3
+wiretypes["LUTFF_IN"]     = 4
+wiretypes["LUTFF_IN_LUT"] = 5
+wiretypes["LUTFF_LOUT"]   = 6
+wiretypes["LUTFF_OUT"]    = 7
+wiretypes["LUTFF_COUT"]   = 8
+wiretypes["LUTFF_GLOBAL"] = 9
+wiretypes["CARRY_IN_MUX"] = 10
+wiretypes["SP4_V"]        = 11
+wiretypes["SP4_H"]        = 12
+wiretypes["SP12_V"]       = 13
+wiretypes["SP12_H"]       = 14
+
+def maj_wire_name(name):
+    if name[2].startswith("lutff_"):
+        return True
+    if name[2].startswith("io_"):
+        return True
+    if name[2].startswith("ram/"):
+        return True
+    if name[2].startswith("sp4_h_r_"):
+        return name[2] in ("sp4_h_r_0", "sp4_h_r_1", "sp4_h_r_2", "sp4_h_r_3", "sp4_h_r_4", "sp4_h_r_5",
+                           "sp4_h_r_6", "sp4_h_r_7", "sp4_h_r_8", "sp4_h_r_9", "sp4_h_r_10", "sp4_h_r_11")
+    if name[2].startswith("sp4_v_b_"):
+        return name[2] in ("sp4_v_b_0", "sp4_v_b_1", "sp4_v_b_2", "sp4_v_b_3", "sp4_v_b_4", "sp4_v_b_5",
+                           "sp4_v_b_6", "sp4_v_b_7", "sp4_v_b_8", "sp4_v_b_9", "sp4_v_b_10", "sp4_v_b_11")
+    if name[2].startswith("sp12_h_r_"):
+        return name[2] in ("sp12_h_r_0", "sp12_h_r_1")
+    if name[2].startswith("sp12_v_b_"):
+        return name[2] in ("sp12_v_b_0", "sp12_v_b_1")
+    return False
+
+def cmp_wire_names(newname, oldname):
+    if maj_wire_name(newname):
+        return True
+    if maj_wire_name(oldname):
+        return False
+
+    if newname[2].startswith("sp") and oldname[2].startswith("sp"):
+        m1 = re.match(r".*_(\d+)$", newname[2])
+        m2 = re.match(r".*_(\d+)$", oldname[2])
+        if m1 and m2:
+            idx1 = int(m1.group(1))
+            idx2 = int(m2.group(1))
+            if idx1 != idx2:
+                return idx1 < idx2
+
+    return newname < oldname
+
+def wire_type(name):
+    longname = name
+    name = name.split('/')
+
+    if name[0].startswith("X") and name[1].startswith("Y"):
+        name = name[2:]
+
+    if name[0].startswith("sp4_v_") or name[0].startswith("sp4_r_v_") or name[0].startswith("span4_vert_"):
+        return "SP4_V"
+
+    if name[0].startswith("sp4_h_") or name[0].startswith("span4_horz_"):
+        return "SP4_H"
+
+    if name[0].startswith("sp12_v_") or name[0].startswith("span12_vert_"):
+        return "SP12_V"
+
+    if name[0].startswith("sp12_h_") or name[0].startswith("span12_horz_"):
+        return "SP12_H"
+
+    if name[0].startswith("glb2local"):
+        return "GLB2LOCAL"
+
+    if name[0].startswith("glb_netwk_"):
+        return "GLB_NETWK"
+
+    if name[0].startswith("local_"):
+        return "LOCAL"
+
+    if name[0].startswith("lutff_"):
+        if name[1].startswith("in_"):
+            return "LUTFF_IN_LUT" if name[1].endswith("_lut") else "LUTFF_IN"
+
+        if name[1] == "lout":
+            return "LUTFF_LOUT"
+        if name[1] == "out":
+            return "LUTFF_OUT"
+        if name[1] == "cout":
+            return "LUTFF_COUT"
+
+    if name[0] == "ram":
+        if name[1].startswith("RADDR_"):
+            return "LUTFF_IN"
+        if name[1].startswith("WADDR_"):
+            return "LUTFF_IN"
+        if name[1].startswith("WDATA_"):
+            return "LUTFF_IN"
+        if name[1].startswith("MASK_"):
+            return "LUTFF_IN"
+        if name[1].startswith("RDATA_"):
+            return "LUTFF_OUT"
+        if name[1] in ("WCLK", "WCLKE", "WE", "RCLK", "RCLKE", "RE"):
+            return "LUTFF_GLOBAL"
+
+    if name[0].startswith("io_"):
+        if name[1].startswith("D_IN_") or name[1] == "OUT_ENB":
+            return "LUTFF_IN"
+        if name[1].startswith("D_OUT_"):
+            return "LUTFF_OUT"
+    if name[0] == "fabout":
+        return "LUTFF_IN"
+
+    if name[0] == "lutff_global" or name[0] == "io_global":
+        return "LUTFF_GLOBAL"
+
+    if name[0] == "carry_in_mux":
+        return "CARRY_IN_MUX"
+
+    if name[0] == "carry_in":
+        return "LUTFF_COUT"
+
+    if name[0].startswith("neigh_op_"):
+        return "NONE"
+
+    if name[0].startswith("padin_"):
+        return "NONE"
+
+    # print("No type for wire: %s (%s)" % (longname, name), file=sys.stderr)
+    # assert 0
+
+    return "NONE"
+
+def pipdelay(src_idx, dst_idx, db):
+    if db is None:
+        return 0
+
+    src = wire_names_r[src_idx]
+    dst = wire_names_r[dst_idx]
+    src_type = wire_type(src[2])
+    dst_type = wire_type(dst[2])
+
+    if dst[2].startswith("sp4_") or dst[2].startswith("span4_"):
+        if src[2].startswith("sp12_") or src[2].startswith("span12_"):
+            return db["Sp12to4.I.O"]
+
+        if src[2].startswith("span4_"):
+            return db["IoSpan4Mux.I.O"]
+
+        if dst[2].startswith("sp4_h_"):
+            return db["Span4Mux_h4.I.O"]
+        else:
+            return db["Span4Mux_v4.I.O"]
+
+    if dst[2].startswith("sp12_") or dst[2].startswith("span12_"):
+        if dst[2].startswith("sp12_h_"):
+            return db["Span12Mux_h12.I.O"]
+        else:
+            return db["Span12Mux_v12.I.O"]
+
+    if dst[2] in ("fabout", "clk"):
+        return 0 # FIXME?
+
+    if src[2].startswith("glb_netwk_") and dst[2].startswith("glb2local_"):
+        return 0 # FIXME?
+
+    if dst[2] == "carry_in_mux":
+        return db["ICE_CARRY_IN_MUX.carryinitin.carryinitout"]
+
+    if dst[2] in ("lutff_global/clk", "io_global/inclk", "io_global/outclk", "ram/RCLK", "ram/WCLK"):
+        return db["ClkMux.I.O"]
+
+    if dst[2] in ("lutff_global/s_r", "io_global/latch", "ram/RE", "ram/WE"):
+        return db["SRMux.I.O"]
+
+    if dst[2] in ("lutff_global/cen", "io_global/cen", "ram/RCLKE", "ram/WCLKE"):
+        return db["CEMux.I.O"]
+
+    if dst[2].startswith("local_"):
+        return db["LocalMux.I.O"]
+
+    if src[2].startswith("local_") and dst[2] in ("io_0/D_OUT_0", "io_0/D_OUT_1", "io_0/OUT_ENB", "io_1/D_OUT_0", "io_1/D_OUT_1", "io_1/OUT_ENB"):
+        return db["IoInMux.I.O"]
+
+    if re.match(r"lutff_\d+/in_\d+$", dst[2]):
+        return db["InMux.I.O"]
+
+    if re.match(r"lutff_\d+/in_\d+_lut", dst[2]):
+        return 0
+
+    if re.match(r"ram/(MASK|RADDR|WADDR|WDATA)_", dst[2]):
+        return db["InMux.I.O"]
+
+    if re.match(r"lutff_\d+/out", dst[2]):
+        if re.match(r"lutff_\d+/in_0", src[2]):
+            return db["LogicCell40.in0.lcout"]
+        if re.match(r"lutff_\d+/in_1", src[2]):
+            return db["LogicCell40.in1.lcout"]
+        if re.match(r"lutff_\d+/in_2", src[2]):
+            return db["LogicCell40.in2.lcout"]
+        if re.match(r"lutff_\d+/in_3", src[2]):
+            return db["LogicCell40.in3.lcout"]
+
+    print(src, dst, src_idx, dst_idx, src_type, dst_type, file=sys.stderr)
+    assert 0
+
+def wiredelay(wire_idx, db):
+    if db is None:
+        return 0
+
+    wire = wire_names_r[wire_idx]
+    wtype = wire_type(wire[2])
+
+    # FIXME
+    return 0
+
+def init_tiletypes(device):
+    global num_tile_types, tile_sizes, tile_bits
+    if device == "5k":
+        num_tile_types = 10
+    else:
+        num_tile_types = 5
+    tile_sizes = {i: (0, 0) for i in range(num_tile_types)}
+    tile_bits = [[] for _ in range(num_tile_types)]
+
+with open(args.filename, "r") as f:
+    mode = None
+
+    for line in f:
+        line = line.split()
+
+        if len(line) == 0 or line[0] == "#":
+            continue
+
+        if line[0] == ".device":
+            dev_name = line[1]
+            init_tiletypes(dev_name)
+            dev_width = int(line[2])
+            dev_height = int(line[3])
+            num_wires = int(line[4])
+            continue
+
+        if line[0] == ".net":
+            mode = ("net", int(line[1]))
+            continue
+
+        if line[0] == ".buffer":
+            mode = ("buffer", int(line[3]), int(line[1]), int(line[2]))
+            switches.append((int(line[1]), int(line[2]), line[4:], -1))
+            continue
+
+        if line[0] == ".routing":
+            mode = ("routing", int(line[3]), int(line[1]), int(line[2]))
+            switches.append((int(line[1]), int(line[2]), line[4:], -1))
+            continue
+
+        if line[0] == ".io_tile":
+            tiles[(int(line[1]), int(line[2]))] = "io"
+            mode = None
+            continue
+
+        if line[0] == ".logic_tile":
+            tiles[(int(line[1]), int(line[2]))] = "logic"
+            mode = None
+            continue
+
+        if line[0] == ".ramb_tile":
+            tiles[(int(line[1]), int(line[2]))] = "ramb"
+            mode = None
+            continue
+
+        if line[0] == ".ramt_tile":
+            tiles[(int(line[1]), int(line[2]))] = "ramt"
+            mode = None
+            continue
+
+        if line[0] == ".dsp0_tile":
+            tiles[(int(line[1]), int(line[2]))] = "dsp0"
+            mode = None
+            continue
+
+        if line[0] == ".dsp1_tile":
+            tiles[(int(line[1]), int(line[2]))] = "dsp1"
+            mode = None
+            continue
+
+        if line[0] == ".dsp2_tile":
+            tiles[(int(line[1]), int(line[2]))] = "dsp2"
+            mode = None
+            continue
+
+        if line[0] == ".dsp3_tile":
+            tiles[(int(line[1]), int(line[2]))] = "dsp3"
+            mode = None
+            continue
+
+        if line[0] == ".ipcon_tile":
+            tiles[(int(line[1]), int(line[2]))] = "ipcon"
+            mode = None
+            continue
+
+        if line[0] == ".logic_tile_bits":
+            mode = ("bits", 1)
+            tile_sizes[1] = (int(line[1]), int(line[2]))
+            continue
+
+        if line[0] == ".io_tile_bits":
+            mode = ("bits", 2)
+            tile_sizes[2] = (int(line[1]), int(line[2]))
+            continue
+
+        if line[0] == ".ramb_tile_bits":
+            mode = ("bits", 3)
+            tile_sizes[3] = (int(line[1]), int(line[2]))
+            continue
+
+        if line[0] == ".ramt_tile_bits":
+            mode = ("bits", 4)
+            tile_sizes[4] = (int(line[1]), int(line[2]))
+            continue
+
+        if line[0] == ".dsp0_tile_bits":
+            mode = ("bits", 5)
+            tile_sizes[5] = (int(line[1]), int(line[2]))
+            continue
+
+        if line[0] == ".dsp1_tile_bits":
+            mode = ("bits", 6)
+            tile_sizes[6] = (int(line[1]), int(line[2]))
+            continue
+
+        if line[0] == ".dsp2_tile_bits":
+            mode = ("bits", 7)
+            tile_sizes[7] = (int(line[1]), int(line[2]))
+            continue
+
+        if line[0] == ".dsp3_tile_bits":
+            mode = ("bits", 8)
+            tile_sizes[8] = (int(line[1]), int(line[2]))
+            continue
+
+        if line[0] == ".ipcon_tile_bits":
+            mode = ("bits", 9)
+            tile_sizes[9] = (int(line[1]), int(line[2]))
+            continue
+
+        if line[0] == ".ieren":
+            mode = ("ieren",)
+            continue
+
+        if line[0] == ".pins":
+            mode = ("pins", line[1])
+            packages.append((line[1], []))
+            continue
+
+        if line[0] == ".extra_cell":
+            if len(line) >= 5:
+                mode = ("extra_cell", (line[4], int(line[1]), int(line[2]), int(line[3])))
+            elif line[3] == "WARMBOOT":
+                mode = ("extra_cell", (line[3], int(line[1]), int(line[2]), 0))
+            elif line[3] == "PLL":
+                mode = ("extra_cell", (line[3], int(line[1]), int(line[2]), 3))
+            else:
+                assert 0
+            extra_cells[mode[1]] = []
+            continue
+
+        if (line[0][0] == ".") or (mode is None):
+            mode = None
+            continue
+
+        if mode[0] == "net":
+            wname = (int(line[0]), int(line[1]), line[2])
+            wire_names[wname] = mode[1]
+            if (mode[1] not in wire_names_r) or cmp_wire_names(wname, wire_names_r[mode[1]]):
+                wire_names_r[mode[1]] = wname
+            if mode[1] not in wire_xy:
+                wire_xy[mode[1]] = list()
+            wire_xy[mode[1]].append((int(line[0]), int(line[1])))
+            if mode[1] not in wire_segments:
+                wire_segments[mode[1]] = dict()
+            if ("TILE_WIRE_" + wname[2].upper().replace("/", "_")) in gfx_wire_ids:
+                wire_segments[mode[1]][(wname[0], wname[1])] = wname[2]
+            continue
+
+        if mode[0] in ("buffer", "routing"):
+            wire_a = int(line[1])
+            wire_b = mode[1]
+            if wire_a not in wire_downhill:
+                wire_downhill[wire_a] = set()
+            if wire_b not in wire_uphill:
+                wire_uphill[wire_b] = set()
+            wire_downhill[wire_a].add(wire_b)
+            wire_uphill[wire_b].add(wire_a)
+            pip_xy[(wire_a, wire_b)] = (mode[2], mode[3], int(line[0], 2), len(switches) - 1, 0)
+            continue
+
+        if mode[0] == "bits":
+            name = line[0]
+            bits = []
+            for b in line[1:]:
+                m = cbit_re.match(b)
+                assert m
+                bits.append((int(m.group(1)), int(m.group(2))))
+            tile_bits[mode[1]].append((name, bits))
+            continue
+
+        if mode[0] == "ieren":
+            ierens.append(tuple([int(_) for _ in line]))
+            continue
+
+        if mode[0] == "pins":
+            packages[-1][1].append((line[0], int(line[1]), int(line[2]), int(line[3])))
+            continue
+
+        if mode[0] == "extra_cell":
+            if line[0] == "LOCKED":
+                extra_cells[mode[1]].append((("LOCKED_" + line[1]), (0, 0, "LOCKED")))
+            else:
+                extra_cells[mode[1]].append((line[0], (int(line[1]), int(line[2]), line[3])))
+            continue
+
+def add_wire(x, y, name):
+    global num_wires
+    wire_idx = num_wires
+    num_wires = num_wires + 1
+    wname = (x, y, name)
+    wire_names[wname] = wire_idx
+    wire_names_r[wire_idx] = wname
+    wire_segments[wire_idx] = dict()
+    if ("TILE_WIRE_" + wname[2].upper().replace("/", "_")) in gfx_wire_ids:
+        wire_segments[wire_idx][(wname[0], wname[1])] = wname[2]
+    return wire_idx
+
+def add_switch(x, y, bel=-1):
+    switches.append((x, y, [], bel))
+
+def add_pip(src, dst, flags=0):
+    x, y, _, _ = switches[-1]
+
+    if src not in wire_downhill:
+        wire_downhill[src] = set()
+    wire_downhill[src].add(dst)
+
+    if dst not in wire_uphill:
+        wire_uphill[dst] = set()
+    wire_uphill[dst].add(src)
+
+    pip_xy[(src, dst)] = (x, y, 0, len(switches) - 1, flags)
+
+# Add virtual padin wires
+for i in range(8):
+    add_wire(0, 0, "padin_%d" % i)
+
+def add_bel_input(bel, wire, port):
+    if wire not in wire_belports:
+        wire_belports[wire] = set()
+    wire_belports[wire].add((bel, port))
+    bel_wires[bel].append((constids[port], 0, wire))
+
+def add_bel_output(bel, wire, port):
+    if wire not in wire_belports:
+        wire_belports[wire] = set()
+    wire_belports[wire].add((bel, port))
+    bel_wires[bel].append((constids[port], 1, wire))
+
+def add_bel_lc(x, y, z):
+    bel = len(bel_name)
+    bel_name.append("X%d/Y%d/lc%d" % (x, y, z))
+    bel_type.append("ICESTORM_LC")
+    bel_pos.append((x, y, z))
+    bel_wires.append(list())
+
+    wire_cen = wire_names[(x, y, "lutff_global/cen")]
+    wire_clk = wire_names[(x, y, "lutff_global/clk")]
+    wire_s_r = wire_names[(x, y, "lutff_global/s_r")]
+
+    if z == 0:
+        wire_cin = wire_names[(x, y, "carry_in_mux")]
+    else:
+        wire_cin = wire_names[(x, y, "lutff_%d/cout" % (z-1))]
+
+    wire_in_0 = add_wire(x, y, "lutff_%d/in_0_lut" % z)
+    wire_in_1 = add_wire(x, y, "lutff_%d/in_1_lut" % z)
+    wire_in_2 = add_wire(x, y, "lutff_%d/in_2_lut" % z)
+    wire_in_3 = add_wire(x, y, "lutff_%d/in_3_lut" % z)
+
+    wire_out  = wire_names[(x, y, "lutff_%d/out"  % z)]
+    wire_cout = wire_names[(x, y, "lutff_%d/cout" % z)]
+    wire_lout = wire_names[(x, y, "lutff_%d/lout" % z)] if z < 7 else None
+
+    add_bel_input(bel, wire_cen, "CEN")
+    add_bel_input(bel, wire_clk, "CLK")
+    add_bel_input(bel, wire_s_r, "SR")
+    add_bel_input(bel, wire_cin, "CIN")
+
+    add_bel_input(bel, wire_in_0, "I0")
+    add_bel_input(bel, wire_in_1, "I1")
+    add_bel_input(bel, wire_in_2, "I2")
+    add_bel_input(bel, wire_in_3, "I3")
+
+    add_bel_output(bel, wire_out,  "O")
+    add_bel_output(bel, wire_cout, "COUT")
+
+    if wire_lout is not None:
+        add_bel_output(bel, wire_lout, "LO")
+
+    # route-through LUTs
+    add_switch(x, y, bel)
+    add_pip(wire_in_0, wire_out, 1)
+    add_pip(wire_in_1, wire_out, 1)
+    add_pip(wire_in_2, wire_out, 1)
+    add_pip(wire_in_3, wire_out, 1)
+
+    # LUT permutation pips
+    for i in range(4):
+        add_switch(x, y, bel)
+        for j in range(4):
+            if (i == j) or ((i, j) == (1, 2)) or ((i, j) == (2, 1)):
+                flags = 0
+            else:
+                flags = 2
+            add_pip(wire_names[(x, y, "lutff_%d/in_%d" % (z, i))],
+                    wire_names[(x, y, "lutff_%d/in_%d_lut"  % (z, j))], flags)
+
+def add_bel_io(x, y, z):
+    bel = len(bel_name)
+    bel_name.append("X%d/Y%d/io%d" % (x, y, z))
+    bel_type.append("SB_IO")
+    bel_pos.append((x, y, z))
+    bel_wires.append(list())
+
+    wire_cen   = wire_names[(x, y, "io_global/cen")]
+    wire_iclk  = wire_names[(x, y, "io_global/inclk")]
+    wire_latch = wire_names[(x, y, "io_global/latch")]
+    wire_oclk  = wire_names[(x, y, "io_global/outclk")]
+
+    wire_din_0  = wire_names[(x, y, "io_%d/D_IN_0"  % z)]
+    wire_din_1  = wire_names[(x, y, "io_%d/D_IN_1"  % z)]
+    wire_dout_0 = wire_names[(x, y, "io_%d/D_OUT_0" % z)]
+    wire_dout_1 = wire_names[(x, y, "io_%d/D_OUT_1" % z)]
+    wire_out_en = wire_names[(x, y, "io_%d/OUT_ENB" % z)]
+
+    add_bel_input(bel, wire_cen,    "CLOCK_ENABLE")
+    add_bel_input(bel, wire_iclk,   "INPUT_CLK")
+    add_bel_input(bel, wire_oclk,   "OUTPUT_CLK")
+    add_bel_input(bel, wire_latch,  "LATCH_INPUT_VALUE")
+
+    add_bel_output(bel, wire_din_0, "D_IN_0")
+    add_bel_output(bel, wire_din_1, "D_IN_1")
+
+    add_bel_input(bel, wire_dout_0, "D_OUT_0")
+    add_bel_input(bel, wire_dout_1, "D_OUT_1")
+    add_bel_input(bel, wire_out_en, "OUTPUT_ENABLE")
+
+def add_bel_ram(x, y):
+    bel = len(bel_name)
+    bel_name.append("X%d/Y%d/ram" % (x, y))
+    bel_type.append("ICESTORM_RAM")
+    bel_pos.append((x, y, 0))
+    bel_wires.append(list())
+
+    if (x, y, "ram/WE") in wire_names:
+        # iCE40 1K-style memories
+        y0, y1 = y, y+1
+    else:
+        # iCE40 8K-style memories
+        y1, y0 = y, y+1
+
+    for i in range(16):
+        add_bel_input (bel, wire_names[(x, y0 if i < 8 else y1, "ram/MASK_%d"  % i)], "MASK_%d"  % i)
+        add_bel_input (bel, wire_names[(x, y0 if i < 8 else y1, "ram/WDATA_%d" % i)], "WDATA_%d" % i)
+        add_bel_output(bel, wire_names[(x, y0 if i < 8 else y1, "ram/RDATA_%d" % i)], "RDATA_%d" % i)
+
+    for i in range(11):
+        add_bel_input(bel, wire_names[(x, y0, "ram/WADDR_%d" % i)], "WADDR_%d" % i)
+        add_bel_input(bel, wire_names[(x, y1, "ram/RADDR_%d" % i)], "RADDR_%d" % i)
+
+    add_bel_input(bel, wire_names[(x, y0, "ram/WCLK")], "WCLK")
+    add_bel_input(bel, wire_names[(x, y0, "ram/WCLKE")], "WCLKE")
+    add_bel_input(bel, wire_names[(x, y0, "ram/WE")], "WE")
+
+    add_bel_input(bel, wire_names[(x, y1, "ram/RCLK")], "RCLK")
+    add_bel_input(bel, wire_names[(x, y1, "ram/RCLKE")], "RCLKE")
+    add_bel_input(bel, wire_names[(x, y1, "ram/RE")], "RE")
+
+def add_bel_gb(xy, x, y, g):
+    if xy[0] != x or xy[1] != y:
+        return
+
+    bel = len(bel_name)
+    bel_name.append("X%d/Y%d/gb" % (x, y))
+    bel_type.append("SB_GB")
+    bel_pos.append((x, y, 2))
+    bel_wires.append(list())
+
+    add_bel_input(bel, wire_names[(x, y, "fabout")], "USER_SIGNAL_TO_GLOBAL_BUFFER")
+    add_bel_output(bel, wire_names[(x, y, "glb_netwk_%d" % g)], "GLOBAL_BUFFER_OUTPUT")
+
+def is_ec_wire(ec_entry):
+    return ec_entry[1] in wire_names
+
+def is_ec_output(ec_entry):
+    wirename = ec_entry[1][2]
+    if "O_" in wirename or "slf_op_" in wirename: return True
+    if "neigh_op_" in wirename: return True
+    if "glb_netwk_" in wirename: return True
+    return False
+
+def is_ec_pll_clock_output(ec, ec_entry):
+    return ec[0] == 'PLL' and ec_entry[0] in ('PLLOUT_A', 'PLLOUT_B')
+
+def add_bel_ec(ec):
+    ectype, x, y, z = ec
+    bel = len(bel_name)
+    extra_cell_config[bel] = []
+    bel_name.append("X%d/Y%d/%s_%d" % (x, y, ectype.lower(), z))
+    bel_type.append(ectype)
+    bel_pos.append((x, y, z))
+    bel_wires.append(list())
+    for entry in extra_cells[ec]:
+        if is_ec_wire(entry) and "glb_netwk_" not in entry[1][2]: # TODO: osc glb output conflicts with GB
+            if is_ec_output(entry):
+                add_bel_output(bel, wire_names[entry[1]], entry[0])
+            else:
+                add_bel_input(bel, wire_names[entry[1]], entry[0])
+        elif is_ec_pll_clock_output(ec, entry):
+            x, y, z = entry[1]
+            z = 'io_{}/D_IN_0'.format(z)
+            add_bel_output(bel, wire_names[(x, y, z)], entry[0])
+        else:
+            extra_cell_config[bel].append(entry)
+
+cell_timings = {}
+tmport_to_constids = {
+    "posedge:clk": "CLK",
+    "ce": "CEN",
+    "sr": "SR",
+    "in0": "I0",
+    "in1": "I1",
+    "in2": "I2",
+    "in3": "I3",
+    "carryin": "CIN",
+    "carryout": "COUT",
+    "lcout": "O",
+    "ltout": "LO",
+    "posedge:RCLK": "RCLK",
+    "posedge:WCLK": "WCLK",
+    "RCLKE": "RCLKE",
+    "RE": "RE",
+    "WCLKE": "WCLKE",
+    "WE": "WE",
+    "posedge:CLOCK": "CLOCK",
+    "posedge:SLEEP": "SLEEP",
+    "USERSIGNALTOGLOBALBUFFER": "USER_SIGNAL_TO_GLOBAL_BUFFER",
+    "GLOBALBUFFEROUTPUT": "GLOBAL_BUFFER_OUTPUT"
+}
+
+for i in range(16):
+    tmport_to_constids["RDATA[%d]" % i] = "RDATA_%d" % i
+    tmport_to_constids["WDATA[%d]" % i] = "WDATA_%d" % i
+    tmport_to_constids["MASK[%d]" % i] = "MASK_%d" % i
+    tmport_to_constids["DATAOUT[%d]" % i] = "DATAOUT_%d" % i
+
+for i in range(11):
+    tmport_to_constids["RADDR[%d]" % i] = "RADDR_%d" % i
+    tmport_to_constids["WADDR[%d]" % i] = "WADDR_%d" % i
+
+def add_cell_timingdata(bel_type, timing_cell, fast_db, slow_db):
+    timing_entries = []
+    database = slow_db if slow_db is not None else fast_db
+    for key in database.keys():
+        skey = key.split(".")
+        if skey[0] == timing_cell:
+            if skey[1] in tmport_to_constids and skey[2] in tmport_to_constids:
+                iport = tmport_to_constids[skey[1]]
+                oport = tmport_to_constids[skey[2]]
+                fastdel = fast_db[key] if fast_db is not None else 0
+                slowdel = slow_db[key] if slow_db is not None else 0
+                timing_entries.append((iport, oport, fastdel, slowdel))
+    cell_timings[bel_type] = timing_entries
+
+add_cell_timingdata("ICESTORM_LC", "LogicCell40", fast_timings, slow_timings)
+add_cell_timingdata("SB_GB", "ICE_GB", fast_timings, slow_timings)
+
+if dev_name != "384":
+    add_cell_timingdata("ICESTORM_RAM", "SB_RAM40_4K", fast_timings, slow_timings)
+if dev_name == "5k":
+    add_cell_timingdata("SPRAM", "SB_SPRAM256KA", fast_timings, slow_timings)
+
+
+for tile_xy, tile_type in sorted(tiles.items()):
+    if tile_type == "logic":
+        for i in range(8):
+            add_bel_lc(tile_xy[0], tile_xy[1], i)
+
+    if tile_type == "io":
+        for i in range(2):
+            add_bel_io(tile_xy[0], tile_xy[1], i)
+
+        if dev_name == "1k":
+            add_bel_gb(tile_xy,  7,  0, 0)
+            add_bel_gb(tile_xy,  7, 17, 1)
+            add_bel_gb(tile_xy, 13,  9, 2)
+            add_bel_gb(tile_xy,  0,  9, 3)
+            add_bel_gb(tile_xy,  6, 17, 4)
+            add_bel_gb(tile_xy,  6,  0, 5)
+            add_bel_gb(tile_xy,  0,  8, 6)
+            add_bel_gb(tile_xy, 13,  8, 7)
+        elif dev_name == "5k":
+            add_bel_gb(tile_xy, 13,  0, 0)
+            add_bel_gb(tile_xy, 13, 31, 1)
+            add_bel_gb(tile_xy, 19, 31, 2)
+            add_bel_gb(tile_xy,  6, 31, 3)
+            add_bel_gb(tile_xy, 12, 31, 4)
+            add_bel_gb(tile_xy, 12,  0, 5)
+            add_bel_gb(tile_xy,  6,  0, 6)
+            add_bel_gb(tile_xy, 19,  0, 7)
+        elif dev_name == "8k":
+            add_bel_gb(tile_xy, 33, 16,  7)
+            add_bel_gb(tile_xy,  0, 16,  6)
+            add_bel_gb(tile_xy, 17, 33,  1)
+            add_bel_gb(tile_xy, 17,  0,  0)
+            add_bel_gb(tile_xy,  0, 17,  3)
+            add_bel_gb(tile_xy, 33, 17,  2)
+            add_bel_gb(tile_xy, 16,  0,  5)
+            add_bel_gb(tile_xy, 16, 33,  4)
+        elif dev_name == "384":
+            add_bel_gb(tile_xy,  7,  4,  7)
+            add_bel_gb(tile_xy,  0,  4,  6)
+            add_bel_gb(tile_xy,  4,  9,  1)
+            add_bel_gb(tile_xy,  4,  0,  0)
+            add_bel_gb(tile_xy,  0,  5,  3)
+            add_bel_gb(tile_xy,  7,  5,  2)
+            add_bel_gb(tile_xy,  3,  0,  5)
+            add_bel_gb(tile_xy,  3,  9,  4)
+
+    if tile_type == "ramb":
+        add_bel_ram(tile_xy[0], tile_xy[1])
+
+    for ec in sorted(extra_cells.keys()):
+        if ec[1] == tile_xy[0] and ec[2] == tile_xy[1]:
+            add_bel_ec(ec)
+
+for ec in sorted(extra_cells.keys()):
+    if ec[1] in (0, dev_width - 1) and ec[2] in (0, dev_height - 1):
+        add_bel_ec(ec)
+
+class BinaryBlobAssembler:
+    def l(self, name, ltype = None, export = False):
+        if ltype is None:
+            print("label %s" % (name,))
+        else:
+            print("label %s %s" % (name, ltype))
+
+    def r(self, name, comment):
+        if comment is None:
+            print("ref %s" % (name,))
+        else:
+            print("ref %s %s" % (name, comment))
+
+    def s(self, s, comment):
+        assert "|" not in s
+        print("str |%s| %s" % (s, comment))
+
+    def u8(self, v, comment):
+        if comment is None:
+            print("u8 %d" % (v,))
+        else:
+            print("u8 %d %s" % (v, comment))
+
+    def u16(self, v, comment):
+        if comment is None:
+            print("u16 %d" % (v,))
+        else:
+            print("u16 %d %s" % (v, comment))
+
+    def u32(self, v, comment):
+        if comment is None:
+            print("u32 %d" % (v,))
+        else:
+            print("u32 %d %s" % (v, comment))
+
+    def pre(self, s):
+        print("pre %s" % s)
+
+    def post(self, s):
+        print("post %s" % s)
+
+    def push(self, name):
+        print("push %s" % name)
+
+    def pop(self):
+        print("pop")
+
+bba = BinaryBlobAssembler()
+bba.pre('#include "nextpnr.h"')
+bba.pre('NEXTPNR_NAMESPACE_BEGIN')
+bba.post('NEXTPNR_NAMESPACE_END')
+bba.push("chipdb_blob_%s" % dev_name)
+bba.r("chip_info_%s" % dev_name, "chip_info")
+
+for bel in range(len(bel_name)):
+    bba.l("bel_wires_%d" % bel, "BelWirePOD")
+    for data in sorted(bel_wires[bel]):
+        bba.u32(data[0], "port")
+        bba.u32(data[1], "type")
+        bba.u32(data[2], "wire_index")
+
+bba.l("bel_data_%s" % dev_name, "BelInfoPOD")
+for bel in range(len(bel_name)):
+    bba.s(bel_name[bel], "name")
+    bba.u32(constids[bel_type[bel]], "type")
+    bba.u32(len(bel_wires[bel]), "num_bel_wires")
+    bba.r("bel_wires_%d" % bel, "bel_wires")
+    bba.u8(bel_pos[bel][0], "x")
+    bba.u8(bel_pos[bel][1], "y")
+    bba.u8(bel_pos[bel][2], "z")
+    bba.u8(0, "padding")
+
+wireinfo = list()
+pipinfo = list()
+pipcache = dict()
+
+for wire in range(num_wires):
+    if wire in wire_uphill:
+        pips = list()
+        for src in wire_uphill[wire]:
+            if (src, wire) not in pipcache:
+                pipcache[(src, wire)] = len(pipinfo)
+                pi = dict()
+                pi["src"] = src
+                pi["dst"] = wire
+                pi["fast_delay"] = pipdelay(src, wire, fast_timings)
+                pi["slow_delay"] = pipdelay(src, wire, slow_timings)
+                pi["x"] = pip_xy[(src, wire)][0]
+                pi["y"] = pip_xy[(src, wire)][1]
+                pi["switch_mask"] = pip_xy[(src, wire)][2]
+                pi["switch_index"] = pip_xy[(src, wire)][3]
+                pi["flags"] = pip_xy[(src, wire)][4]
+                pipinfo.append(pi)
+            pips.append(pipcache[(src, wire)])
+        num_uphill = len(pips)
+        list_uphill = "wire%d_uppips" % wire
+        bba.l(list_uphill, "int32_t")
+        for p in pips:
+            bba.u32(p, None)
+    else:
+        num_uphill = 0
+        list_uphill = None
+
+    if wire in wire_downhill:
+        pips = list()
+        for dst in wire_downhill[wire]:
+            if (wire, dst) not in pipcache:
+                pipcache[(wire, dst)] = len(pipinfo)
+                pi = dict()
+                pi["src"] = wire
+                pi["dst"] = dst
+                pi["fast_delay"] = pipdelay(wire, dst, fast_timings)
+                pi["slow_delay"] = pipdelay(wire, dst, slow_timings)
+                pi["x"] = pip_xy[(wire, dst)][0]
+                pi["y"] = pip_xy[(wire, dst)][1]
+                pi["switch_mask"] = pip_xy[(wire, dst)][2]
+                pi["switch_index"] = pip_xy[(wire, dst)][3]
+                pi["flags"] = pip_xy[(wire, dst)][4]
+                pipinfo.append(pi)
+            pips.append(pipcache[(wire, dst)])
+        num_downhill = len(pips)
+        list_downhill = "wire%d_downpips" % wire
+        bba.l(list_downhill, "int32_t")
+        for p in pips:
+            bba.u32(p, None)
+    else:
+        num_downhill = 0
+        list_downhill = None
+
+    if wire in wire_belports:
+        num_bel_pins = len(wire_belports[wire])
+        bba.l("wire%d_bels" % wire, "BelPortPOD")
+        for belport in sorted(wire_belports[wire]):
+            bba.u32(belport[0], "bel_index")
+            bba.u32(constids[belport[1]], "port")
+    else:
+        num_bel_pins = 0
+
+    info = dict()
+    info["name"] = "X%d/Y%d/%s" % wire_names_r[wire]
+
+    info["num_uphill"] = num_uphill
+    info["list_uphill"] = list_uphill
+
+    info["num_downhill"] = num_downhill
+    info["list_downhill"] = list_downhill
+
+    info["num_bel_pins"] = num_bel_pins
+    info["list_bel_pins"] = ("wire%d_bels" % wire) if num_bel_pins > 0 else None
+
+    if wire in wire_xy:
+        avg_x, avg_y = 0, 0
+
+        for x, y in wire_xy[wire]:
+            avg_x += x
+            avg_y += y
+        avg_x /= len(wire_xy[wire])
+        avg_y /= len(wire_xy[wire])
+
+        info["x"] = int(round(avg_x))
+        info["y"] = int(round(avg_y))
+    else:
+        info["x"] = wire_names_r[wire][0]
+        info["y"] = wire_names_r[wire][1]
+
+    wireinfo.append(info)
+
+packageinfo = []
+
+for package in packages:
+    name, pins = package
+    safename = re.sub("[^A-Za-z0-9]", "_", name)
+    pins_info = []
+    for pin in pins:
+        pinname, x, y, z = pin
+        pin_bel = "X%d/Y%d/io%d" % (x, y, z)
+        bel_idx = bel_name.index(pin_bel)
+        pins_info.append((pinname, bel_idx))
+    bba.l("package_%s_pins" % safename, "PackagePinPOD")
+    for pi in pins_info:
+        bba.s(pi[0], "name")
+        bba.u32(pi[1], "bel_index")
+    packageinfo.append((name, len(pins_info), "package_%s_pins" % safename))
+
+tilegrid = []
+for y in range(dev_height):
+    for x in range(dev_width):
+        if (x, y) in tiles:
+            tilegrid.append(tiles[x, y].upper())
+        else:
+            tilegrid.append("NONE")
+
+tileinfo = []
+for t in range(num_tile_types):
+    centries_info = []
+    for cb in tile_bits[t]:
+        name, bits = cb
+        safename = re.sub("[^A-Za-z0-9]", "_", name)
+        bba.l("tile%d_%s_bits" % (t, safename), "ConfigBitPOD")
+        for row, col in bits:
+            bba.u8(row, "row")
+            bba.u8(col, "col")
+        if len(bits) == 0:
+            bba.u32(0, "padding")
+        elif len(bits) % 2 == 1:
+            bba.u16(0, "padding")
+        centries_info.append((name, len(bits), t, safename))
+    bba.l("tile%d_config" % t, "ConfigEntryPOD")
+    for name, num_bits, t, safename in centries_info:
+        bba.s(name, "name")
+        bba.u32(num_bits, "num_bits")
+        bba.r("tile%d_%s_bits" % (t, safename), "num_bits")
+    if len(centries_info) == 0:
+        bba.u32(0, "padding")
+    ti = dict()
+    ti["cols"] = tile_sizes[t][0]
+    ti["rows"] = tile_sizes[t][1]
+    ti["num_entries"] = len(centries_info)
+    ti["entries"] = "tile%d_config" % t
+    tileinfo.append(ti)
+
+bba.l("wire_data_%s" % dev_name, "WireInfoPOD")
+for wire, info in enumerate(wireinfo):
+    bba.s(info["name"], "name")
+    bba.u32(info["num_uphill"], "num_uphill")
+    bba.u32(info["num_downhill"], "num_downhill")
+    bba.r(info["list_uphill"], "pips_uphill")
+    bba.r(info["list_downhill"], "pips_downhill")
+    bba.u32(info["num_bel_pins"], "num_bel_pins")
+    bba.r(info["list_bel_pins"], "bel_pins")
+    bba.u32(len(wire_segments[wire]), "num_segments")
+    if len(wire_segments[wire]):
+        bba.r("wire_segments_%d" % wire, "segments")
+    else:
+        bba.u32(0, "segments")
+
+    bba.u32(wiredelay(wire, fast_timings), "fast_delay")
+    bba.u32(wiredelay(wire, slow_timings), "slow_delay")
+
+    bba.u8(info["x"], "x")
+    bba.u8(info["y"], "y")
+    bba.u8(0, "z") # FIXME
+    bba.u8(wiretypes[wire_type(info["name"])], "type")
+
+for wire in range(num_wires):
+    if len(wire_segments[wire]):
+        bba.l("wire_segments_%d" % wire, "WireSegmentPOD")
+        for xy, seg in sorted(wire_segments[wire].items()):
+            bba.u8(xy[0], "x")
+            bba.u8(xy[1], "y")
+            bba.u16(gfx_wire_ids["TILE_WIRE_" + seg.upper().replace("/", "_")], "index")
+
+bba.l("pip_data_%s" % dev_name, "PipInfoPOD")
+for info in pipinfo:
+    src_seg = -1
+    src_segname = wire_names_r[info["src"]]
+    if (info["x"], info["y"]) in wire_segments[info["src"]]:
+        src_segname = wire_segments[info["src"]][(info["x"], info["y"])]
+        src_seg = gfx_wire_ids["TILE_WIRE_" + src_segname.upper().replace("/", "_")]
+        src_segname = src_segname.replace("/", ".")
+
+    dst_seg = -1
+    dst_segname = wire_names_r[info["dst"]]
+    if (info["x"], info["y"]) in wire_segments[info["dst"]]:
+        dst_segname = wire_segments[info["dst"]][(info["x"], info["y"])]
+        dst_seg = gfx_wire_ids["TILE_WIRE_" + dst_segname.upper().replace("/", "_")]
+        dst_segname = dst_segname.replace("/", ".")
+
+    # bba.s("X%d/Y%d/%s->%s" % (info["x"], info["y"], src_segname, dst_segname), "name")
+    bba.u32(info["src"], "src")
+    bba.u32(info["dst"], "dst")
+    bba.u32(info["fast_delay"], "fast_delay")
+    bba.u32(info["slow_delay"], "slow_delay")
+    bba.u8(info["x"], "x")
+    bba.u8(info["y"], "y")
+    bba.u16(src_seg, "src_seg")
+    bba.u16(dst_seg, "dst_seg")
+    bba.u16(info["switch_mask"], "switch_mask")
+    bba.u32(info["switch_index"], "switch_index")
+    bba.u32(info["flags"], "flags")
+
+switchinfo = []
+for switch in switches:
+    x, y, bits, bel = switch
+    bitlist = []
+    for b in bits:
+        m = cbit_re.match(b)
+        assert m
+        bitlist.append((int(m.group(1)), int(m.group(2))))
+    si = dict()
+    si["x"] = x
+    si["y"] = y
+    si["bits"] = bitlist
+    si["bel"] = bel
+    switchinfo.append(si)
+
+bba.l("switch_data_%s" % dev_name, "SwitchInfoPOD")
+for info in switchinfo:
+    bba.u32(len(info["bits"]), "num_bits")
+    bba.u32(info["bel"], "bel")
+    bba.u8(info["x"], "x")
+    bba.u8(info["y"], "y")
+    for i in range(5):
+        if i < len(info["bits"]):
+            bba.u8(info["bits"][i][0], "row<%d>" % i)
+            bba.u8(info["bits"][i][1], "col<%d>" % i)
+        else:
+            bba.u8(0, "row<%d> (unused)" % i)
+            bba.u8(0, "col<%d> (unused)" % i)
+
+bba.l("tile_data_%s" % dev_name, "TileInfoPOD")
+for info in tileinfo:
+    bba.u8(info["cols"], "cols")
+    bba.u8(info["rows"], "rows")
+    bba.u16(info["num_entries"], "num_entries")
+    bba.r(info["entries"], "entries")
+
+bba.l("ieren_data_%s" % dev_name, "IerenInfoPOD")
+for ieren in ierens:
+    bba.u8(ieren[0], "iox")
+    bba.u8(ieren[1], "ioy")
+    bba.u8(ieren[2], "ioz")
+    bba.u8(ieren[3], "ierx")
+    bba.u8(ieren[4], "iery")
+    bba.u8(ieren[5], "ierz")
+
+if len(ierens) % 2 == 1:
+    bba.u16(0, "padding")
+
+bba.l("bits_info_%s" % dev_name, "BitstreamInfoPOD")
+bba.u32(len(switchinfo), "num_switches")
+bba.u32(len(ierens), "num_ierens")
+bba.r("tile_data_%s" % dev_name, "tiles_nonrouting")
+bba.r("switch_data_%s" % dev_name, "switches")
+bba.r("ieren_data_%s" % dev_name, "ierens")
+
+bba.l("tile_grid_%s" % dev_name, "TileType")
+for t in tilegrid:
+    bba.u32(tiletypes[t], "tiletype")
+
+for bel_idx, entries in sorted(extra_cell_config.items()):
+    if len(entries) > 0:
+        bba.l("bel%d_config_entries" % bel_idx, "BelConfigEntryPOD")
+        for entry in entries:
+            bba.s(entry[0], "entry_name")
+            bba.s(entry[1][2], "cbit_name")
+            bba.u8(entry[1][0], "x")
+            bba.u8(entry[1][1], "y")
+            bba.u16(0, "padding")
+
+if len(extra_cell_config) > 0:
+    bba.l("bel_config_%s" % dev_name, "BelConfigPOD")
+    for bel_idx, entries in sorted(extra_cell_config.items()):
+        bba.u32(bel_idx, "bel_index")
+        bba.u32(len(entries), "num_entries")
+        bba.r("bel%d_config_entries" % bel_idx if len(entries) > 0 else None, "entries")
+
+bba.l("package_info_%s" % dev_name, "PackageInfoPOD")
+for info in packageinfo:
+    bba.s(info[0], "name")
+    bba.u32(info[1], "num_pins")
+    bba.r(info[2], "pins")
+
+for cell, timings in sorted(cell_timings.items()):
+    beltype = constids[cell]
+    bba.l("cell_paths_%d" % beltype, "CellPathDelayPOD")
+    for entry in timings:
+        fromport, toport, fast, slow = entry
+        bba.u32(constids[fromport], "from_port")
+        bba.u32(constids[toport], "to_port")
+        bba.u32(fast, "fast_delay")
+        bba.u32(slow, "slow_delay")
+
+bba.l("cell_timings_%s" % dev_name, "CellTimingPOD")
+for cell, timings in sorted(cell_timings.items()):
+    beltype = constids[cell]
+    bba.u32(beltype, "type")
+    bba.u32(len(timings), "num_paths")
+    bba.r("cell_paths_%d" % beltype, "path_delays")
+
+bba.l("chip_info_%s" % dev_name)
+bba.u32(dev_width, "dev_width")
+bba.u32(dev_height, "dev_height")
+bba.u32(len(bel_name), "num_bels")
+bba.u32(num_wires, "num_wires")
+bba.u32(len(pipinfo), "num_pips")
+bba.u32(len(switchinfo), "num_switches")
+bba.u32(len(extra_cell_config), "num_belcfgs")
+bba.u32(len(packageinfo), "num_packages")
+bba.u32(len(cell_timings), "num_timing_cells")
+bba.r("bel_data_%s" % dev_name, "bel_data")
+bba.r("wire_data_%s" % dev_name, "wire_data")
+bba.r("pip_data_%s" % dev_name, "pip_data")
+bba.r("tile_grid_%s" % dev_name, "tile_grid")
+bba.r("bits_info_%s" % dev_name, "bits_info")
+bba.r("bel_config_%s" % dev_name if len(extra_cell_config) > 0 else None, "bel_config")
+bba.r("package_info_%s" % dev_name, "packages_data")
+bba.r("cell_timings_%s" % dev_name, "cell_timing")
+
+bba.pop()
diff --git a/xc7/constids.inc b/xc7/constids.inc
new file mode 100644
index 0000000..7fae17e
--- /dev/null
+++ b/xc7/constids.inc
@@ -0,0 +1,467 @@
+// pin and port names
+//X(I0)
+X(I1)
+X(I2)
+X(I3)
+X(I4)
+X(I5)
+X(I6)
+X(O)
+X(OQ)
+X(OMUX)
+X(CIN)
+X(COUT)
+X(CEN)
+X(CLK)
+X(SR)
+X(S)
+X(R)
+X(PRE)
+X(CLR)
+
+X(MASK_0)
+X(MASK_1)
+X(MASK_2)
+X(MASK_3)
+X(MASK_4)
+X(MASK_5)
+X(MASK_6)
+X(MASK_7)
+X(MASK_8)
+X(MASK_9)
+X(MASK_10)
+X(MASK_11)
+X(MASK_12)
+X(MASK_13)
+X(MASK_14)
+X(MASK_15)
+
+X(RDATA_0)
+X(RDATA_1)
+X(RDATA_2)
+X(RDATA_3)
+X(RDATA_4)
+X(RDATA_5)
+X(RDATA_6)
+X(RDATA_7)
+X(RDATA_8)
+X(RDATA_9)
+X(RDATA_10)
+X(RDATA_11)
+X(RDATA_12)
+X(RDATA_13)
+X(RDATA_14)
+X(RDATA_15)
+
+X(WDATA_0)
+X(WDATA_1)
+X(WDATA_2)
+X(WDATA_3)
+X(WDATA_4)
+X(WDATA_5)
+X(WDATA_6)
+X(WDATA_7)
+X(WDATA_8)
+X(WDATA_9)
+X(WDATA_10)
+X(WDATA_11)
+X(WDATA_12)
+X(WDATA_13)
+X(WDATA_14)
+X(WDATA_15)
+
+X(WADDR_0)
+X(WADDR_1)
+X(WADDR_2)
+X(WADDR_3)
+X(WADDR_4)
+X(WADDR_5)
+X(WADDR_6)
+X(WADDR_7)
+X(WADDR_8)
+X(WADDR_9)
+X(WADDR_10)
+
+X(RADDR_0)
+X(RADDR_1)
+X(RADDR_2)
+X(RADDR_3)
+X(RADDR_4)
+X(RADDR_5)
+X(RADDR_6)
+X(RADDR_7)
+X(RADDR_8)
+X(RADDR_9)
+X(RADDR_10)
+
+X(WCLK)
+X(WCLKE)
+X(WE)
+
+X(RCLK)
+X(RCLKE)
+X(RE)
+
+X(PACKAGE_PIN)
+X(LATCH_INPUT_VALUE)
+X(CLOCK_ENABLE)
+X(INPUT_CLK)
+X(OUTPUT_CLK)
+X(OUTPUT_ENABLE)
+X(D_OUT_0)
+X(D_OUT_1)
+X(D_IN_0)
+X(D_IN_1)
+
+X(USER_SIGNAL_TO_GLOBAL_BUFFER)
+X(GLOBAL_BUFFER_OUTPUT)
+
+X(REFERENCECLK)
+X(EXTFEEDBACK)
+X(DYNAMICDELAY_0)
+X(DYNAMICDELAY_1)
+X(DYNAMICDELAY_2)
+X(DYNAMICDELAY_3)
+X(DYNAMICDELAY_4)
+X(DYNAMICDELAY_5)
+X(DYNAMICDELAY_6)
+X(DYNAMICDELAY_7)
+X(LOCK)
+X(PLLOUT_A)
+X(PLLOUT_B)
+X(BYPASS)
+X(RESETB)
+X(LATCHINPUTVALUE)
+X(SDO)
+X(SDI)
+X(SCLK)
+
+X(BOOT)
+X(S0)
+X(S1)
+
+X(ADDSUBBOT)
+X(ADDSUBTOP)
+X(AHOLD)
+X(A_0)
+X(A_1)
+X(A_10)
+X(A_11)
+X(A_12)
+X(A_13)
+X(A_14)
+X(A_15)
+X(A_2)
+X(A_3)
+X(A_4)
+X(A_5)
+X(A_6)
+X(A_7)
+X(A_8)
+X(A_9)
+X(BHOLD)
+X(B_0)
+X(B_1)
+X(B_10)
+X(B_11)
+X(B_12)
+X(B_13)
+X(B_14)
+X(B_15)
+X(B_2)
+X(B_3)
+X(B_4)
+X(B_5)
+X(B_6)
+X(B_7)
+X(B_8)
+X(B_9)
+X(CE)
+X(CHOLD)
+X(CI)
+X(CO)
+X(C_0)
+X(C_1)
+X(C_10)
+X(C_11)
+X(C_12)
+X(C_13)
+X(C_14)
+X(C_15)
+X(C_2)
+X(C_3)
+X(C_4)
+X(C_5)
+X(C_6)
+X(C_7)
+X(C_8)
+X(C_9)
+X(DHOLD)
+X(D_0)
+X(D_1)
+X(D_10)
+X(D_11)
+X(D_12)
+X(D_13)
+X(D_14)
+X(D_15)
+X(D_2)
+X(D_3)
+X(D_4)
+X(D_5)
+X(D_6)
+X(D_7)
+X(D_8)
+X(D_9)
+X(IRSTBOT)
+X(IRSTTOP)
+X(OHOLDBOT)
+X(OHOLDTOP)
+X(OLOADBOT)
+X(OLOADTOP)
+X(ORSTBOT)
+X(ORSTTOP)
+X(O_0)
+X(O_1)
+X(O_10)
+X(O_11)
+X(O_12)
+X(O_13)
+X(O_14)
+X(O_15)
+X(O_16)
+X(O_17)
+X(O_18)
+X(O_19)
+X(O_2)
+X(O_20)
+X(O_21)
+X(O_22)
+X(O_23)
+X(O_24)
+X(O_25)
+X(O_26)
+X(O_27)
+X(O_28)
+X(O_29)
+X(O_3)
+X(O_30)
+X(O_31)
+X(O_4)
+X(O_5)
+X(O_6)
+X(O_7)
+X(O_8)
+X(O_9)
+
+X(CLKHF)
+X(CLKHFEN)
+X(CLKHFPU)
+X(CLKHF_FABRIC)
+X(TRIM0)
+X(TRIM1)
+X(TRIM2)
+X(TRIM3)
+X(TRIM4)
+X(TRIM5)
+X(TRIM6)
+X(TRIM7)
+X(TRIM8)
+X(TRIM9)
+
+X(CLKLF)
+X(CLKLFEN)
+X(CLKLFPU)
+X(CLKLF_FABRIC)
+
+X(I2CIRQ)
+X(I2CWKUP)
+X(SBACKO)
+X(SBADRI0)
+X(SBADRI1)
+X(SBADRI2)
+X(SBADRI3)
+X(SBADRI4)
+X(SBADRI5)
+X(SBADRI6)
+X(SBADRI7)
+X(SBCLKI)
+X(SBDATI0)
+X(SBDATI1)
+X(SBDATI2)
+X(SBDATI3)
+X(SBDATI4)
+X(SBDATI5)
+X(SBDATI6)
+X(SBDATI7)
+X(SBDATO0)
+X(SBDATO1)
+X(SBDATO2)
+X(SBDATO3)
+X(SBDATO4)
+X(SBDATO5)
+X(SBDATO6)
+X(SBDATO7)
+X(SBRWI)
+X(SBSTBI)
+X(SCLI)
+X(SCLO)
+X(SCLOE)
+X(SDAI)
+X(SDAO)
+X(SDAOE)
+
+X(MCSNO0)
+X(MCSNO1)
+X(MCSNO2)
+X(MCSNO3)
+X(MCSNOE0)
+X(MCSNOE1)
+X(MCSNOE2)
+X(MCSNOE3)
+X(MI)
+X(MO)
+X(MOE)
+X(SCKI)
+X(SCKO)
+X(SCKOE)
+X(SCSNI)
+X(SI)
+X(SO)
+X(SOE)
+X(SPIIRQ)
+X(SPIWKUP)
+
+X(PU_ENB)
+X(WEAK_PU_ENB)
+
+X(LEDDADDR0)
+X(LEDDADDR1)
+X(LEDDADDR2)
+X(LEDDADDR3)
+X(LEDDCLK)
+X(LEDDCS)
+X(LEDDDAT0)
+X(LEDDDAT1)
+X(LEDDDAT2)
+X(LEDDDAT3)
+X(LEDDDAT4)
+X(LEDDDAT5)
+X(LEDDDAT6)
+X(LEDDDAT7)
+X(LEDDDEN)
+X(LEDDEXE)
+X(LEDDON)
+X(PWMOUT0)
+X(PWMOUT1)
+X(PWMOUT2)
+
+X(CURREN)
+X(RGB0PWM)
+X(RGB1PWM)
+X(RGB2PWM)
+X(RGBLEDEN)
+X(RGB0)
+X(RGB1)
+X(RGB2)
+
+X(ADDRESS_0)
+X(ADDRESS_1)
+X(ADDRESS_10)
+X(ADDRESS_11)
+X(ADDRESS_12)
+X(ADDRESS_13)
+X(ADDRESS_2)
+X(ADDRESS_3)
+X(ADDRESS_4)
+X(ADDRESS_5)
+X(ADDRESS_6)
+X(ADDRESS_7)
+X(ADDRESS_8)
+X(ADDRESS_9)
+X(CHIPSELECT)
+X(CLOCK)
+X(DATAIN_0)
+X(DATAIN_1)
+X(DATAIN_10)
+X(DATAIN_11)
+X(DATAIN_12)
+X(DATAIN_13)
+X(DATAIN_14)
+X(DATAIN_15)
+X(DATAIN_2)
+X(DATAIN_3)
+X(DATAIN_4)
+X(DATAIN_5)
+X(DATAIN_6)
+X(DATAIN_7)
+X(DATAIN_8)
+X(DATAIN_9)
+X(DATAOUT_0)
+X(DATAOUT_1)
+X(DATAOUT_10)
+X(DATAOUT_11)
+X(DATAOUT_12)
+X(DATAOUT_13)
+X(DATAOUT_14)
+X(DATAOUT_15)
+X(DATAOUT_2)
+X(DATAOUT_3)
+X(DATAOUT_4)
+X(DATAOUT_5)
+X(DATAOUT_6)
+X(DATAOUT_7)
+X(DATAOUT_8)
+X(DATAOUT_9)
+X(MASKWREN_0)
+X(MASKWREN_1)
+X(MASKWREN_2)
+X(MASKWREN_3)
+X(POWEROFF)
+X(SLEEP)
+X(STANDBY)
+X(WREN)
+
+// cell and bel types
+X(ICESTORM_LC)
+X(ICESTORM_RAM)
+X(SB_IO)
+X(SB_GB)
+X(ICESTORM_PLL)
+X(SB_WARMBOOT)
+X(ICESTORM_DSP)
+X(ICESTORM_HFOSC)
+X(ICESTORM_LFOSC)
+X(SB_I2C)
+X(SB_SPI)
+X(IO_I3C)
+X(SB_LEDDA_IP)
+X(SB_RGBA_DRV)
+X(ICESTORM_SPRAM)
+
+// cell parameters
+X(DFF_ENABLE)
+X(CARRY_ENABLE)
+X(NEG_CLK)
+
+// XC7
+X(I)
+
+X(LUT1)
+X(LUT2)
+X(LUT3)
+X(LUT4)
+X(LUT5)
+X(LUT6)
+
+X(FDRE)
+X(FDSE)
+X(FDCE)
+X(FDPE)
+
+X(BUFGCTRL)
+X(SLICE_LUT6)
+X(IOB33)
+X(IOB18)
+X(PS7)
+X(MMCME2_ADV)
diff --git a/xc7/delay.cc b/xc7/delay.cc
new file mode 100644
index 0000000..67f96b9
--- /dev/null
+++ b/xc7/delay.cc
@@ -0,0 +1,85 @@
+/*
+ *  nextpnr -- Next Generation Place and Route
+ *
+ *  Copyright (C) 2018  Clifford Wolf <clifford@symbioticeda.com>
+ *  Copyright (C) 2018  Serge Bazanski <q3k@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 "nextpnr.h"
+#include "router1.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+#define NUM_FUZZ_ROUTES 100000
+
+delay_t Arch::estimateDelay(WireId src, WireId dst) const
+{
+    const auto &src_tw = torc_info->wire_to_tilewire[src.index];
+    const auto &src_loc = torc_info->tile_to_xy[src_tw.getTileIndex()];
+    const auto &dst_tw = torc_info->wire_to_tilewire[dst.index];
+    const auto &dst_loc = torc_info->tile_to_xy[dst_tw.getTileIndex()];
+
+    if (!torc_info->wire_is_global[src.index]) {
+        auto abs_delta_x = abs(dst_loc.first - src_loc.first);
+        auto abs_delta_y = abs(dst_loc.second - src_loc.second);
+        auto div_LH = std::div(abs_delta_x, 12);
+        auto div_LV = std::div(abs_delta_y, 18);
+        auto div_LVB = std::div(div_LV.rem, 12);
+        auto div_H6 = std::div(div_LH.rem, 6);
+        auto div_V6 = std::div(div_LVB.rem, 6);
+        auto div_H4 = std::div(div_H6.rem, 4);
+        auto div_V4 = std::div(div_V6.rem, 4);
+        auto div_H2 = std::div(div_H4.rem, 2);
+        auto div_V2 = std::div(div_V4.rem, 2);
+        auto num_H1 = div_H2.rem;
+        auto num_V1 = div_V2.rem;
+        return div_LH.quot * 360 + div_LVB.quot * 300 + div_LV.quot * 350 +
+               (div_H6.quot + div_H4.quot + div_V6.quot + div_V4.quot) * 210 + (div_H2.quot + div_V2.quot) * 170 +
+               (num_H1 + num_V1) * 150;
+    }
+    else {
+        auto src_y = src_loc.second;
+        auto dst_y = dst_loc.second;
+        auto div_src_y = std::div(src_y, 52);
+        auto div_dst_y = std::div(dst_y, 52);
+        return abs(div_dst_y.quot - div_src_y.quot) * 52 + abs(div_dst_y.rem - div_src_y.rem);
+    }
+}
+
+delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const
+{
+    const auto &driver = net_info->driver;
+    auto driver_loc = getBelLocation(driver.cell->bel);
+    auto sink_loc = getBelLocation(sink.cell->bel);
+    auto abs_delta_x = abs(driver_loc.x - sink_loc.x);
+    auto abs_delta_y = abs(driver_loc.y - sink_loc.y);
+    auto div_LH = std::div(abs_delta_x, 12);
+    auto div_LV = std::div(abs_delta_y, 18);
+    auto div_LVB = std::div(div_LV.rem, 12);
+    auto div_H6 = std::div(div_LH.rem, 6);
+    auto div_V6 = std::div(div_LVB.rem, 6);
+    auto div_H4 = std::div(div_H6.rem, 4);
+    auto div_V4 = std::div(div_V6.rem, 4);
+    auto div_H2 = std::div(div_H4.rem, 2);
+    auto div_V2 = std::div(div_V4.rem, 2);
+    auto num_H1 = div_H2.rem;
+    auto num_V1 = div_V2.rem;
+    return div_LH.quot * 360 + div_LVB.quot * 300 + div_LV.quot * 350 +
+           (div_H6.quot + div_H4.quot + div_V6.quot + div_V4.quot) * 210 + (div_H2.quot + div_V2.quot) * 170 +
+           (num_H1 + num_V1) * 150;
+}
+
+NEXTPNR_NAMESPACE_END
diff --git a/xc7/family.cmake b/xc7/family.cmake
new file mode 100644
index 0000000..9de869e
--- /dev/null
+++ b/xc7/family.cmake
@@ -0,0 +1,73 @@
+add_dependencies(nextpnr-${family} torc)
+add_custom_target(torc ALL
+                  COMMAND $(MAKE) > /dev/null 2> /dev/null
+                  COMMENT "Building torc (may take some time...)"
+                  WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/torc/src)
+find_package(Boost REQUIRED COMPONENTS serialization iostreams ${boost_libs} ${boost_python_lib})
+
+include_directories(torc/src)
+target_link_libraries(
+    nextpnr-${family} PRIVATE
+
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/architecture/Arc.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/architecture/ArcUsage.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/architecture/Array.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/architecture/DDB.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/architecture/DDBConsoleStreams.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/architecture/DDBStreamHelper.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/architecture/DigestStream.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/architecture/ExtendedWireInfo.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/architecture/InstancePin.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/architecture/OutputStreamHelpers.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/architecture/Package.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/architecture/Pad.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/architecture/PrimitiveConn.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/architecture/PrimitiveDef.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/architecture/PrimitiveElement.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/architecture/PrimitiveElementPin.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/architecture/PrimitivePin.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/architecture/Segments.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/architecture/Site.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/architecture/Sites.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/architecture/Tiles.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/architecture/TileInfo.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/architecture/Tilewire.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/architecture/Versions.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/architecture/VprExporter.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/architecture/WireInfo.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/architecture/WireUsage.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/architecture/XdlImporter.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/architecture/XilinxDatabaseTypes.o
+
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/common/Annotated.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/common/DeviceDesignator.o
+	${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/common/Devices.o
+	${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/common/DirectoryTree.o
+	${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/common/DottedVersion.o
+	${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/common/NullOutputStream.o
+
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/externals/zlib/zfstream.o
+    z
+
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/physical/Circuit.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/physical/ConfigMap.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/physical/Config.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/physical/Design.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/physical/Factory.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/physical/Instance.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/physical/InstancePin.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/physical/InstanceReference.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/physical/Module.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/physical/ModuleTransformer.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/physical/Named.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/physical/Net.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/physical/OutputStreamHelpers.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/physical/Pip.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/physical/Port.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/physical/Progenitor.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/physical/Progeny.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/physical/Renamable.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/physical/Routethrough.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/physical/TilewirePlaceholder.o
+    ${CMAKE_CURRENT_SOURCE_DIR}/torc/src/torc/physical/XdlExporter.o
+)
diff --git a/xc7/firmware_fast.hex b/xc7/firmware_fast.hex
new file mode 100644
index 0000000..bf6fe43
--- /dev/null
+++ b/xc7/firmware_fast.hex
@@ -0,0 +1,6 @@
+@00000000
+13 04 20 00 B7 04 00 02 13 04 14 00 13 74 F4 0F 
+13 09 20 00 63 5E 89 00 13 05 04 00 93 05 09 00 
+EF 00 80 01 63 08 05 00 13 09 19 00 6F F0 9F FE 
+23 A0 84 00 6F F0 5F FD 93 02 10 00 33 05 B5 40 
+E3 5E 55 FE 67 80 00 00 
diff --git a/xc7/firmware_slow.hex b/xc7/firmware_slow.hex
new file mode 100644
index 0000000..ef9aa3c
--- /dev/null
+++ b/xc7/firmware_slow.hex
@@ -0,0 +1,8 @@
+@00000000

+13 04 20 00 B7 04 00 02 93 09 00 10 13 04 14 00 

+63 44 34 01 13 04 20 00 13 09 20 00 63 5E 89 00 

+13 05 04 00 93 05 09 00 EF 00 C0 01 63 0A 05 00 

+13 09 19 00 6F F0 9F FE 23 A0 84 00 EF 00 80 01 

+6F F0 DF FC 93 02 10 00 33 05 B5 40 E3 5E 55 FE 

+67 80 00 00 B7 82 05 00 93 82 02 E4 93 82 F2 FF 

+E3 9E 02 FE 67 80 00 00 

diff --git a/xc7/gfx.cc b/xc7/gfx.cc
new file mode 100644
index 0000000..1ab2fb3
--- /dev/null
+++ b/xc7/gfx.cc
@@ -0,0 +1,766 @@
+/*
+ *  nextpnr -- Next Generation Place and Route
+ *
+ *  Copyright (C) 2018  Clifford Wolf <clifford@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 "gfx.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+void gfxTileWire(std::vector<GraphicElement> &g, int x, int y, GfxTileWireId id, GraphicElement::style_t style)
+{
+    GraphicElement el;
+    el.type = GraphicElement::TYPE_LINE;
+    el.style = style;
+
+    // Horizontal Span-4 Wires
+
+    if (id >= TILE_WIRE_SP4_H_L_36 && id <= TILE_WIRE_SP4_H_L_47) {
+        int idx = (id - TILE_WIRE_SP4_H_L_36) + 48;
+
+        float y1 = y + 1.0 - (0.03 + 0.0025 * (60 - (idx ^ 1)));
+        float y2 = y + 1.0 - (0.03 + 0.0025 * (60 - idx));
+
+        el.x1 = x;
+        el.x2 = x + 0.01;
+        el.y1 = y1;
+        el.y2 = y1;
+        g.push_back(el);
+
+        el.x1 = x + 0.01;
+        el.x2 = x + 0.02;
+        el.y1 = y1;
+        el.y2 = y2;
+        g.push_back(el);
+
+        el.x1 = x + 0.02;
+        el.x2 = x + 0.9;
+        el.y1 = y2;
+        el.y2 = y2;
+        g.push_back(el);
+
+        el.x1 = x + main_swbox_x1 + 0.0025 * (idx + 35);
+        el.x2 = el.x1;
+        el.y1 = y2;
+        el.y2 = y + main_swbox_y2;
+        g.push_back(el);
+    }
+
+    if (id >= TILE_WIRE_SP4_H_R_0 && id <= TILE_WIRE_SP4_H_R_47) {
+        int idx = id - TILE_WIRE_SP4_H_R_0;
+
+        float y1 = y + 1.0 - (0.03 + 0.0025 * (60 - idx));
+        float y2 = y + 1.0 - (0.03 + 0.0025 * (60 - (idx ^ 1)));
+        float y3 = y + 1.0 - (0.03 + 0.0025 * (60 - (idx ^ 1) - 12));
+
+        if (idx >= 12) {
+            el.x1 = x;
+            el.x2 = x + 0.01;
+            el.y1 = y1;
+            el.y2 = y1;
+            g.push_back(el);
+
+            el.x1 = x + 0.01;
+            el.x2 = x + 0.02;
+            el.y1 = y1;
+            el.y2 = y2;
+            g.push_back(el);
+        }
+
+        el.x1 = x + 0.02;
+        el.x2 = x + 0.9;
+        el.y1 = y2;
+        el.y2 = y2;
+        g.push_back(el);
+
+        el.x1 = x + 0.9;
+        el.x2 = x + 1.0;
+        el.y1 = y2;
+        el.y2 = y3;
+        g.push_back(el);
+
+        el.x1 = x + main_swbox_x1 + 0.0025 * ((idx ^ 1) + 35);
+        el.x2 = el.x1;
+        el.y1 = y2;
+        el.y2 = y + main_swbox_y2;
+        g.push_back(el);
+    }
+
+    // Vertical Span-4 Wires
+
+    if (id >= TILE_WIRE_SP4_V_T_36 && id <= TILE_WIRE_SP4_V_T_47) {
+        int idx = (id - TILE_WIRE_SP4_V_T_36) + 48;
+
+        float x1 = x + 0.03 + 0.0025 * (60 - (idx ^ 1));
+        float x2 = x + 0.03 + 0.0025 * (60 - idx);
+
+        el.y1 = y + 1.00;
+        el.y2 = y + 0.99;
+        el.x1 = x1;
+        el.x2 = x1;
+        g.push_back(el);
+
+        el.y1 = y + 0.99;
+        el.y2 = y + 0.98;
+        el.x1 = x1;
+        el.x2 = x2;
+        g.push_back(el);
+
+        el.y1 = y + 0.98;
+        el.y2 = y + 0.10;
+        el.x1 = x2;
+        el.x2 = x2;
+        g.push_back(el);
+
+        el.y1 = y + 1.0 - (0.03 + 0.0025 * (270 - idx));
+        el.y2 = el.y1;
+        el.x1 = x2;
+        el.x2 = x + main_swbox_x1;
+        g.push_back(el);
+    }
+
+    if (id >= TILE_WIRE_SP4_V_B_0 && id <= TILE_WIRE_SP4_V_B_47) {
+        int idx = id - TILE_WIRE_SP4_V_B_0;
+
+        float x1 = x + 0.03 + 0.0025 * (60 - idx);
+        float x2 = x + 0.03 + 0.0025 * (60 - (idx ^ 1));
+        float x3 = x + 0.03 + 0.0025 * (60 - (idx ^ 1) - 12);
+
+        if (idx >= 12) {
+            el.y1 = y + 1.00;
+            el.y2 = y + 0.99;
+            el.x1 = x1;
+            el.x2 = x1;
+            g.push_back(el);
+
+            el.y1 = y + 0.99;
+            el.y2 = y + 0.98;
+            el.x1 = x1;
+            el.x2 = x2;
+            g.push_back(el);
+        }
+
+        el.y1 = y + 0.98;
+        el.y2 = y + 0.10;
+        el.x1 = x2;
+        el.x2 = x2;
+        g.push_back(el);
+
+        el.y1 = y + 0.10;
+        el.y2 = y;
+        el.x1 = x2;
+        el.x2 = x3;
+        g.push_back(el);
+
+        el.y1 = y + 1.0 - (0.03 + 0.0025 * (145 - (idx ^ 1)));
+        el.y2 = el.y1;
+        el.x1 = x;
+        el.x2 = x2;
+        g.push_back(el);
+
+        el.y1 = y + 1.0 - (0.03 + 0.0025 * (270 - (idx ^ 1)));
+        el.y2 = el.y1;
+        el.x1 = x2;
+        el.x2 = x + main_swbox_x1;
+        g.push_back(el);
+    }
+
+    // Horizontal Span-12 Wires
+
+    if (id >= TILE_WIRE_SP12_H_L_22 && id <= TILE_WIRE_SP12_H_L_23) {
+        int idx = (id - TILE_WIRE_SP12_H_L_22) + 24;
+
+        float y1 = y + 1.0 - (0.03 + 0.0025 * (90 - (idx ^ 1)));
+        float y2 = y + 1.0 - (0.03 + 0.0025 * (90 - idx));
+
+        el.x1 = x;
+        el.x2 = x + 0.01;
+        el.y1 = y1;
+        el.y2 = y1;
+        g.push_back(el);
+
+        el.x1 = x + 0.01;
+        el.x2 = x + 0.02;
+        el.y1 = y1;
+        el.y2 = y2;
+        g.push_back(el);
+
+        el.x1 = x + 0.02;
+        el.x2 = x + 0.98333;
+        el.y1 = y2;
+        el.y2 = y2;
+        g.push_back(el);
+
+        el.x1 = x + main_swbox_x1 + 0.0025 * (idx + 5);
+        el.x2 = el.x1;
+        el.y1 = y2;
+        el.y2 = y + main_swbox_y2;
+        g.push_back(el);
+    }
+
+    if (id >= TILE_WIRE_SP12_H_R_0 && id <= TILE_WIRE_SP12_H_R_23) {
+        int idx = id - TILE_WIRE_SP12_H_R_0;
+
+        float y1 = y + 1.0 - (0.03 + 0.0025 * (90 - idx));
+        float y2 = y + 1.0 - (0.03 + 0.0025 * (90 - (idx ^ 1)));
+        float y3 = y + 1.0 - (0.03 + 0.0025 * (90 - (idx ^ 1) - 2));
+
+        if (idx >= 2) {
+            el.x1 = x;
+            el.x2 = x + 0.01;
+            el.y1 = y1;
+            el.y2 = y1;
+            g.push_back(el);
+
+            el.x1 = x + 0.01;
+            el.x2 = x + 0.02;
+            el.y1 = y1;
+            el.y2 = y2;
+            g.push_back(el);
+        }
+
+        el.x1 = x + 0.02;
+        el.x2 = x + 0.98333;
+        el.y1 = y2;
+        el.y2 = y2;
+        g.push_back(el);
+
+        el.x1 = x + 0.98333;
+        el.x2 = x + 1.0;
+        el.y1 = y2;
+        el.y2 = y3;
+        g.push_back(el);
+
+        el.x1 = x + main_swbox_x1 + 0.0025 * ((idx ^ 1) + 5);
+        el.x2 = el.x1;
+        el.y1 = y2;
+        el.y2 = y + main_swbox_y2;
+        g.push_back(el);
+    }
+
+    // Vertical Right Span-4
+
+    if (id >= TILE_WIRE_SP4_R_V_B_0 && id <= TILE_WIRE_SP4_R_V_B_47) {
+        int idx = id - TILE_WIRE_SP4_R_V_B_0;
+
+        float y1 = y + 1.0 - (0.03 + 0.0025 * (145 - (idx ^ 1)));
+
+        el.y1 = y1;
+        el.y2 = y1;
+        el.x1 = x + main_swbox_x2;
+        el.x2 = x + 1.0;
+        g.push_back(el);
+    }
+
+    // Vertical Span-12 Wires
+
+    if (id >= TILE_WIRE_SP12_V_T_22 && id <= TILE_WIRE_SP12_V_T_23) {
+        int idx = (id - TILE_WIRE_SP12_V_T_22) + 24;
+
+        float x1 = x + 0.03 + 0.0025 * (90 - (idx ^ 1));
+        float x2 = x + 0.03 + 0.0025 * (90 - idx);
+
+        el.y1 = y + 1.00;
+        el.y2 = y + 0.99;
+        el.x1 = x1;
+        el.x2 = x1;
+        g.push_back(el);
+
+        el.y1 = y + 0.99;
+        el.y2 = y + 0.98;
+        el.x1 = x1;
+        el.x2 = x2;
+        g.push_back(el);
+
+        el.y1 = y + 0.98;
+        el.y2 = y + 0.01667;
+        el.x1 = x2;
+        el.x2 = x2;
+        g.push_back(el);
+
+        el.y1 = y + 1.0 - (0.03 + 0.0025 * (300 - idx));
+        el.y2 = el.y1;
+        el.x1 = x2;
+        el.x2 = x + main_swbox_x1;
+        g.push_back(el);
+    }
+
+    if (id >= TILE_WIRE_SP12_V_B_0 && id <= TILE_WIRE_SP12_V_B_23) {
+        int idx = id - TILE_WIRE_SP12_V_B_0;
+
+        float x1 = x + 0.03 + 0.0025 * (90 - idx);
+        float x2 = x + 0.03 + 0.0025 * (90 - (idx ^ 1));
+        float x3 = x + 0.03 + 0.0025 * (90 - (idx ^ 1) - 2);
+
+        if (idx >= 2) {
+            el.y1 = y + 1.00;
+            el.y2 = y + 0.99;
+            el.x1 = x1;
+            el.x2 = x1;
+            g.push_back(el);
+
+            el.y1 = y + 0.99;
+            el.y2 = y + 0.98;
+            el.x1 = x1;
+            el.x2 = x2;
+            g.push_back(el);
+        }
+
+        el.y1 = y + 0.98;
+        el.y2 = y + 0.01667;
+        el.x1 = x2;
+        el.x2 = x2;
+        g.push_back(el);
+
+        el.y1 = y + 0.01667;
+        el.y2 = y;
+        el.x1 = x2;
+        el.x2 = x3;
+        g.push_back(el);
+
+        el.y1 = y + 1.0 - (0.03 + 0.0025 * (300 - (idx ^ 1)));
+        el.y2 = el.y1;
+        el.x1 = x2;
+        el.x2 = x + main_swbox_x1;
+        g.push_back(el);
+    }
+
+    // Global2Local
+
+    if (id >= TILE_WIRE_GLB2LOCAL_0 && id <= TILE_WIRE_GLB2LOCAL_3) {
+        int idx = id - TILE_WIRE_GLB2LOCAL_0;
+        el.x1 = x + main_swbox_x1 + 0.005 * (idx + 5);
+        el.x2 = el.x1;
+        el.y1 = y + main_swbox_y1;
+        el.y2 = el.y1 - 0.02;
+        g.push_back(el);
+    }
+
+    // GlobalNets
+
+    if (id >= TILE_WIRE_GLB_NETWK_0 && id <= TILE_WIRE_GLB_NETWK_7) {
+        int idx = id - TILE_WIRE_GLB_NETWK_0;
+        el.x1 = x + main_swbox_x1 - 0.05;
+        el.x2 = x + main_swbox_x1;
+        el.y1 = y + main_swbox_y1 + 0.005 * (13 - idx);
+        el.y2 = el.y1;
+        g.push_back(el);
+    }
+
+    // Neighbours
+
+    if (id >= TILE_WIRE_NEIGH_OP_BNL_0 && id <= TILE_WIRE_NEIGH_OP_TOP_7) {
+        int idx = id - TILE_WIRE_NEIGH_OP_BNL_0;
+        el.y1 = y + main_swbox_y2 - (0.0025 * (idx + 10) + 0.01 * (idx / 8));
+        el.y2 = el.y1;
+        el.x1 = x + main_swbox_x1 - 0.05;
+        el.x2 = x + main_swbox_x1;
+        g.push_back(el);
+    }
+
+    // Local Tracks
+
+    if (id >= TILE_WIRE_LOCAL_G0_0 && id <= TILE_WIRE_LOCAL_G3_7) {
+        int idx = id - TILE_WIRE_LOCAL_G0_0;
+        el.x1 = x + main_swbox_x2;
+        el.x2 = x + local_swbox_x1;
+        float yoff = y + (local_swbox_y1 + local_swbox_y2) / 2 - 0.005 * 16 - 0.075;
+        el.y1 = yoff + 0.005 * idx + 0.05 * (idx / 8);
+        el.y2 = el.y1;
+        g.push_back(el);
+    }
+
+    // LC Inputs
+
+    if (id >= TILE_WIRE_LUTFF_0_IN_0 && id <= TILE_WIRE_LUTFF_7_IN_3) {
+        int idx = id - TILE_WIRE_LUTFF_0_IN_0;
+        int z = idx / 4;
+        int input = idx % 4;
+        el.x1 = x + local_swbox_x2;
+        el.x2 = x + lut_swbox_x1;
+        el.y1 = y + (logic_cell_y1 + logic_cell_y2) / 2 - 0.0075 + (0.005 * input) + z * logic_cell_pitch;
+        el.y2 = el.y1;
+        g.push_back(el);
+    }
+
+    if (id >= TILE_WIRE_LUTFF_0_IN_0_LUT && id <= TILE_WIRE_LUTFF_7_IN_3_LUT) {
+        int idx = id - TILE_WIRE_LUTFF_0_IN_0_LUT;
+        int z = idx / 4;
+        int input = idx % 4;
+        el.x1 = x + lut_swbox_x2;
+        el.x2 = x + logic_cell_x1;
+        el.y1 = y + (logic_cell_y1 + logic_cell_y2) / 2 - 0.0075 + (0.005 * input) + z * logic_cell_pitch;
+        el.y2 = el.y1;
+        g.push_back(el);
+    }
+
+    // LC Outputs
+
+    if (id >= TILE_WIRE_LUTFF_0_OUT && id <= TILE_WIRE_LUTFF_7_OUT) {
+        int idx = id - TILE_WIRE_LUTFF_0_OUT;
+
+        float y1 = y + 1.0 - (0.03 + 0.0025 * (152 + idx));
+
+        el.y1 = y1;
+        el.y2 = y1;
+        el.x1 = x + main_swbox_x2;
+        el.x2 = x + 0.97 + 0.0025 * (7 - idx);
+        g.push_back(el);
+
+        el.y1 = y1;
+        el.y2 = y + (logic_cell_y1 + logic_cell_y2) / 2 + idx * logic_cell_pitch;
+        el.x1 = el.x2;
+        g.push_back(el);
+
+        el.y1 = el.y2;
+        el.x1 = x + logic_cell_x2;
+        g.push_back(el);
+    }
+
+    // LC Control
+
+    if (id >= TILE_WIRE_LUTFF_GLOBAL_CEN && id <= TILE_WIRE_LUTFF_GLOBAL_S_R) {
+        int idx = id - TILE_WIRE_LUTFF_GLOBAL_CEN;
+
+        el.x1 = x + main_swbox_x2 - 0.005 * (idx + 5);
+        el.x2 = el.x1;
+        el.y1 = y + main_swbox_y1;
+        el.y2 = el.y1 - 0.005 * (idx + 2);
+        g.push_back(el);
+
+        el.y1 = el.y2;
+        el.x2 = x + logic_cell_x2 - 0.005 * (2 - idx + 5);
+        g.push_back(el);
+
+        el.y2 = y + logic_cell_y1;
+        el.x1 = el.x2;
+        g.push_back(el);
+
+        for (int i = 0; i < 7; i++) {
+            el.y1 = y + logic_cell_y2 + i * logic_cell_pitch;
+            el.y2 = y + logic_cell_y1 + (i + 1) * logic_cell_pitch;
+            g.push_back(el);
+        }
+    }
+
+    // LC Cascade
+
+    if (id >= TILE_WIRE_LUTFF_0_LOUT && id <= TILE_WIRE_LUTFF_6_LOUT) {
+        int idx = id - TILE_WIRE_LUTFF_0_LOUT;
+        el.x1 = x + logic_cell_x1 + 0.005 * 5;
+        el.x2 = el.x1;
+        el.y1 = y + logic_cell_y2 + idx * logic_cell_pitch;
+        el.y2 = y + logic_cell_y1 + (idx + 1) * logic_cell_pitch;
+        g.push_back(el);
+    }
+
+    // Carry Chain
+
+    if (id >= TILE_WIRE_LUTFF_0_COUT && id <= TILE_WIRE_LUTFF_7_COUT) {
+        int idx = id - TILE_WIRE_LUTFF_0_COUT;
+        el.x1 = x + logic_cell_x1 + 0.005 * 3;
+        el.x2 = el.x1;
+        el.y1 = y + logic_cell_y2 + idx * logic_cell_pitch;
+        el.y2 = y + (idx < 7 ? logic_cell_y1 + (idx + 1) * logic_cell_pitch : 1.0);
+        g.push_back(el);
+    }
+
+    if (id == TILE_WIRE_CARRY_IN) {
+        el.x1 = x + logic_cell_x1 + 0.005 * 3;
+        el.x2 = el.x1;
+        el.y1 = y;
+        el.y2 = y + 0.01;
+        g.push_back(el);
+    }
+
+    if (id == TILE_WIRE_CARRY_IN_MUX) {
+        el.x1 = x + logic_cell_x1 + 0.005 * 3;
+        el.x2 = el.x1;
+        el.y1 = y + 0.02;
+        el.y2 = y + logic_cell_y1;
+        g.push_back(el);
+    }
+}
+
+static bool getWireXY_main(GfxTileWireId id, float &x, float &y)
+{
+    // Horizontal Span-4 Wires
+
+    if (id >= TILE_WIRE_SP4_H_L_36 && id <= TILE_WIRE_SP4_H_L_47) {
+        int idx = (id - TILE_WIRE_SP4_H_L_36) + 48;
+        x = main_swbox_x1 + 0.0025 * (idx + 35);
+        y = main_swbox_y2;
+        return true;
+    }
+
+    if (id >= TILE_WIRE_SP4_H_R_0 && id <= TILE_WIRE_SP4_H_R_47) {
+        int idx = id - TILE_WIRE_SP4_H_R_0;
+        x = main_swbox_x1 + 0.0025 * ((idx ^ 1) + 35);
+        y = main_swbox_y2;
+        return true;
+    }
+
+    // Vertical Span-4 Wires
+
+    if (id >= TILE_WIRE_SP4_V_T_36 && id <= TILE_WIRE_SP4_V_T_47) {
+        int idx = (id - TILE_WIRE_SP4_V_T_36) + 48;
+        y = 1.0 - (0.03 + 0.0025 * (270 - idx));
+        x = main_swbox_x1;
+        return true;
+    }
+
+    if (id >= TILE_WIRE_SP4_V_B_0 && id <= TILE_WIRE_SP4_V_B_47) {
+        int idx = id - TILE_WIRE_SP4_V_B_0;
+        y = 1.0 - (0.03 + 0.0025 * (270 - (idx ^ 1)));
+        x = main_swbox_x1;
+        return true;
+    }
+
+    // Horizontal Span-12 Wires
+
+    if (id >= TILE_WIRE_SP12_H_L_22 && id <= TILE_WIRE_SP12_H_L_23) {
+        int idx = (id - TILE_WIRE_SP12_H_L_22) + 24;
+        x = main_swbox_x1 + 0.0025 * (idx + 5);
+        y = main_swbox_y2;
+        return true;
+    }
+
+    if (id >= TILE_WIRE_SP12_H_R_0 && id <= TILE_WIRE_SP12_H_R_23) {
+        int idx = id - TILE_WIRE_SP12_H_R_0;
+        x = main_swbox_x1 + 0.0025 * ((idx ^ 1) + 5);
+        y = main_swbox_y2;
+        return true;
+    }
+
+    // Vertical Right Span-4
+
+    if (id >= TILE_WIRE_SP4_R_V_B_0 && id <= TILE_WIRE_SP4_R_V_B_47) {
+        int idx = id - TILE_WIRE_SP4_R_V_B_0;
+        y = 1.0 - (0.03 + 0.0025 * (145 - (idx ^ 1)));
+        x = main_swbox_x2;
+        return true;
+    }
+
+    // Vertical Span-12 Wires
+
+    if (id >= TILE_WIRE_SP12_V_T_22 && id <= TILE_WIRE_SP12_V_T_23) {
+        int idx = (id - TILE_WIRE_SP12_V_T_22) + 24;
+        y = 1.0 - (0.03 + 0.0025 * (300 - idx));
+        x = main_swbox_x1;
+        return true;
+    }
+
+    if (id >= TILE_WIRE_SP12_V_B_0 && id <= TILE_WIRE_SP12_V_B_23) {
+        int idx = id - TILE_WIRE_SP12_V_B_0;
+        y = 1.0 - (0.03 + 0.0025 * (300 - (idx ^ 1)));
+        x = main_swbox_x1;
+        return true;
+    }
+
+    // Global2Local
+
+    if (id >= TILE_WIRE_GLB2LOCAL_0 && id <= TILE_WIRE_GLB2LOCAL_3) {
+        int idx = id - TILE_WIRE_GLB2LOCAL_0;
+        x = main_swbox_x1 + 0.005 * (idx + 5);
+        y = main_swbox_y1;
+        return true;
+    }
+
+    // GlobalNets
+
+    if (id >= TILE_WIRE_GLB_NETWK_0 && id <= TILE_WIRE_GLB_NETWK_7) {
+        int idx = id - TILE_WIRE_GLB_NETWK_0;
+        x = main_swbox_x1;
+        y = main_swbox_y1 + 0.005 * (13 - idx);
+        return true;
+    }
+
+    // Neighbours
+
+    if (id >= TILE_WIRE_NEIGH_OP_BNL_0 && id <= TILE_WIRE_NEIGH_OP_TOP_7) {
+        int idx = id - TILE_WIRE_NEIGH_OP_BNL_0;
+        y = main_swbox_y2 - (0.0025 * (idx + 10) + 0.01 * (idx / 8));
+        x = main_swbox_x1;
+        return true;
+    }
+
+    // Local Tracks
+
+    if (id >= TILE_WIRE_LOCAL_G0_0 && id <= TILE_WIRE_LOCAL_G3_7) {
+        int idx = id - TILE_WIRE_LOCAL_G0_0;
+        float yoff = (local_swbox_y1 + local_swbox_y2) / 2 - 0.005 * 16 - 0.075;
+        x = main_swbox_x2;
+        y = yoff + 0.005 * idx + 0.05 * (idx / 8);
+        return true;
+    }
+
+    // LC Outputs
+
+    if (id >= TILE_WIRE_LUTFF_0_OUT && id <= TILE_WIRE_LUTFF_7_OUT) {
+        int idx = id - TILE_WIRE_LUTFF_0_OUT;
+        y = 1.0 - (0.03 + 0.0025 * (152 + idx));
+        x = main_swbox_x2;
+        return true;
+    }
+
+    // LC Control
+
+    if (id >= TILE_WIRE_LUTFF_GLOBAL_CEN && id <= TILE_WIRE_LUTFF_GLOBAL_S_R) {
+        int idx = id - TILE_WIRE_LUTFF_GLOBAL_CEN;
+        x = main_swbox_x2 - 0.005 * (idx + 5);
+        y = main_swbox_y1;
+        return true;
+    }
+
+    return false;
+}
+
+static bool getWireXY_local(GfxTileWireId id, float &x, float &y)
+{
+    if (id >= TILE_WIRE_LOCAL_G0_0 && id <= TILE_WIRE_LOCAL_G3_7) {
+        int idx = id - TILE_WIRE_LOCAL_G0_0;
+        float yoff = (local_swbox_y1 + local_swbox_y2) / 2 - 0.005 * 16 - 0.075;
+        x = local_swbox_x1;
+        y = yoff + 0.005 * idx + 0.05 * (idx / 8);
+        return true;
+    }
+
+    if (id >= TILE_WIRE_LUTFF_0_IN_0 && id <= TILE_WIRE_LUTFF_7_IN_3) {
+        int idx = id - TILE_WIRE_LUTFF_0_IN_0;
+        int z = idx / 4;
+        int input = idx % 4;
+        x = local_swbox_x2;
+        y = (logic_cell_y1 + logic_cell_y2) / 2 - 0.0075 + (0.005 * input) + z * logic_cell_pitch;
+        return true;
+    }
+
+    return false;
+}
+
+void pipGfx(std::vector<GraphicElement> &g, int x, int y, float x1, float y1, float x2, float y2, float swx1,
+            float swy1, float swx2, float swy2, GraphicElement::style_t style)
+{
+    float tx = 0.5 * (x1 + x2);
+    float ty = 0.5 * (y1 + y2);
+
+    GraphicElement el;
+    el.type = GraphicElement::TYPE_ARROW;
+    el.style = style;
+
+    if (fabsf(x1 - swx1) < 0.001 && fabsf(x2 - swx1) < 0.001) {
+        tx = x1 + 0.25 * fabsf(y1 - y2);
+        goto edge_pip;
+    }
+
+    if (fabsf(x1 - swx2) < 0.001 && fabsf(x2 - swx2) < 0.001) {
+        tx = x1 - 0.25 * fabsf(y1 - y2);
+        goto edge_pip;
+    }
+
+    if (fabsf(y1 - swy1) < 0.001 && fabsf(y2 - swy1) < 0.001) {
+        ty = y1 + 0.25 * fabsf(x1 - x2);
+        goto edge_pip;
+    }
+
+    if (fabsf(y1 - swy1) < 0.001 && fabsf(y2 - swy1) < 0.001) {
+        ty = y1 + 0.25 * fabsf(x1 - x2);
+        goto edge_pip;
+    }
+
+    el.x1 = x + x1;
+    el.y1 = y + y1;
+    el.x2 = x + x2;
+    el.y2 = y + y2;
+    g.push_back(el);
+    return;
+
+edge_pip:
+    el.x1 = x + x1;
+    el.y1 = y + y1;
+    el.x2 = x + tx;
+    el.y2 = y + ty;
+    g.push_back(el);
+
+    el.x1 = x + tx;
+    el.y1 = y + ty;
+    el.x2 = x + x2;
+    el.y2 = y + y2;
+    g.push_back(el);
+}
+
+void gfxTilePip(std::vector<GraphicElement> &g, int x, int y, GfxTileWireId src, GfxTileWireId dst,
+                GraphicElement::style_t style)
+{
+    float x1, y1, x2, y2;
+
+    if (getWireXY_main(src, x1, y1) && getWireXY_main(dst, x2, y2)) {
+        pipGfx(g, x, y, x1, y1, x2, y2, main_swbox_x1, main_swbox_y1, main_swbox_x2, main_swbox_y2, style);
+        return;
+    }
+
+    if (getWireXY_local(src, x1, y1) && getWireXY_local(dst, x2, y2)) {
+        pipGfx(g, x, y, x1, y1, x2, y2, local_swbox_x1, local_swbox_y1, local_swbox_x2, local_swbox_y2, style);
+        return;
+    }
+
+    if (TILE_WIRE_LUTFF_0_IN_0_LUT <= src && src <= TILE_WIRE_LUTFF_7_IN_3_LUT && TILE_WIRE_LUTFF_0_OUT <= dst &&
+        dst <= TILE_WIRE_LUTFF_7_OUT) {
+        int lut_idx = (src - TILE_WIRE_LUTFF_0_IN_0_LUT) / 4;
+        int in_idx = (src - TILE_WIRE_LUTFF_0_IN_0_LUT) % 4;
+
+        GraphicElement el;
+        el.type = GraphicElement::TYPE_ARROW;
+        el.style = style;
+        el.x1 = x + logic_cell_x1;
+        el.x2 = x + logic_cell_x2;
+        el.y1 = y + (logic_cell_y1 + logic_cell_y2) / 2 - 0.0075 + (0.005 * in_idx) + lut_idx * logic_cell_pitch;
+        el.y2 = y + (logic_cell_y1 + logic_cell_y2) / 2 + lut_idx * logic_cell_pitch;
+        g.push_back(el);
+        return;
+    }
+
+    if (TILE_WIRE_LUTFF_0_IN_0 <= src && src <= TILE_WIRE_LUTFF_7_IN_3 && TILE_WIRE_LUTFF_0_IN_0_LUT <= dst &&
+        dst <= TILE_WIRE_LUTFF_7_IN_3_LUT) {
+        int lut_idx = (src - TILE_WIRE_LUTFF_0_IN_0) / 4;
+        int in_idx = (src - TILE_WIRE_LUTFF_0_IN_0) % 4;
+        int out_idx = (dst - TILE_WIRE_LUTFF_0_IN_0_LUT) % 4;
+
+        GraphicElement el;
+        el.type = GraphicElement::TYPE_ARROW;
+        el.style = style;
+        el.x1 = x + lut_swbox_x1;
+        el.x2 = x + lut_swbox_x2;
+        el.y1 = y + (logic_cell_y1 + logic_cell_y2) / 2 - 0.0075 + (0.005 * in_idx) + lut_idx * logic_cell_pitch;
+        el.y2 = y + (logic_cell_y1 + logic_cell_y2) / 2 - 0.0075 + (0.005 * out_idx) + lut_idx * logic_cell_pitch;
+        g.push_back(el);
+        return;
+    }
+
+    if (src == TILE_WIRE_CARRY_IN && dst == TILE_WIRE_CARRY_IN_MUX) {
+        GraphicElement el;
+        el.type = GraphicElement::TYPE_ARROW;
+        el.style = style;
+        el.x1 = x + logic_cell_x1 + 0.005 * 3;
+        el.x2 = el.x1;
+        el.y1 = y + 0.01;
+        el.y2 = y + 0.02;
+        g.push_back(el);
+        return;
+    }
+}
+
+NEXTPNR_NAMESPACE_END
diff --git a/xc7/gfx.h b/xc7/gfx.h
new file mode 100644
index 0000000..8ee7b0b
--- /dev/null
+++ b/xc7/gfx.h
@@ -0,0 +1,523 @@
+/*
+ *  nextpnr -- Next Generation Place and Route
+ *
+ *  Copyright (C) 2018  Clifford Wolf <clifford@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 GFX_H
+#define GFX_H
+
+#include "nextpnr.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+const float main_swbox_x1 = 0.35;
+const float main_swbox_x2 = 0.60;
+const float main_swbox_y1 = 0.05;
+const float main_swbox_y2 = 0.73;
+
+const float local_swbox_x1 = 0.63;
+const float local_swbox_x2 = 0.73;
+const float local_swbox_y1 = 0.05;
+const float local_swbox_y2 = 0.55;
+
+const float lut_swbox_x1 = 0.76;
+const float lut_swbox_x2 = 0.80;
+
+const float logic_cell_x1 = 0.83;
+const float logic_cell_x2 = 0.95;
+const float logic_cell_y1 = 0.05;
+const float logic_cell_y2 = 0.10;
+const float logic_cell_pitch = 0.0625;
+
+enum GfxTileWireId
+{
+    TILE_WIRE_GLB2LOCAL_0,
+    TILE_WIRE_GLB2LOCAL_1,
+    TILE_WIRE_GLB2LOCAL_2,
+    TILE_WIRE_GLB2LOCAL_3,
+
+    TILE_WIRE_GLB_NETWK_0,
+    TILE_WIRE_GLB_NETWK_1,
+    TILE_WIRE_GLB_NETWK_2,
+    TILE_WIRE_GLB_NETWK_3,
+    TILE_WIRE_GLB_NETWK_4,
+    TILE_WIRE_GLB_NETWK_5,
+    TILE_WIRE_GLB_NETWK_6,
+    TILE_WIRE_GLB_NETWK_7,
+
+    TILE_WIRE_LOCAL_G0_0,
+    TILE_WIRE_LOCAL_G0_1,
+    TILE_WIRE_LOCAL_G0_2,
+    TILE_WIRE_LOCAL_G0_3,
+    TILE_WIRE_LOCAL_G0_4,
+    TILE_WIRE_LOCAL_G0_5,
+    TILE_WIRE_LOCAL_G0_6,
+    TILE_WIRE_LOCAL_G0_7,
+
+    TILE_WIRE_LOCAL_G1_0,
+    TILE_WIRE_LOCAL_G1_1,
+    TILE_WIRE_LOCAL_G1_2,
+    TILE_WIRE_LOCAL_G1_3,
+    TILE_WIRE_LOCAL_G1_4,
+    TILE_WIRE_LOCAL_G1_5,
+    TILE_WIRE_LOCAL_G1_6,
+    TILE_WIRE_LOCAL_G1_7,
+
+    TILE_WIRE_LOCAL_G2_0,
+    TILE_WIRE_LOCAL_G2_1,
+    TILE_WIRE_LOCAL_G2_2,
+    TILE_WIRE_LOCAL_G2_3,
+    TILE_WIRE_LOCAL_G2_4,
+    TILE_WIRE_LOCAL_G2_5,
+    TILE_WIRE_LOCAL_G2_6,
+    TILE_WIRE_LOCAL_G2_7,
+
+    TILE_WIRE_LOCAL_G3_0,
+    TILE_WIRE_LOCAL_G3_1,
+    TILE_WIRE_LOCAL_G3_2,
+    TILE_WIRE_LOCAL_G3_3,
+    TILE_WIRE_LOCAL_G3_4,
+    TILE_WIRE_LOCAL_G3_5,
+    TILE_WIRE_LOCAL_G3_6,
+    TILE_WIRE_LOCAL_G3_7,
+
+    TILE_WIRE_LUTFF_0_IN_0,
+    TILE_WIRE_LUTFF_0_IN_1,
+    TILE_WIRE_LUTFF_0_IN_2,
+    TILE_WIRE_LUTFF_0_IN_3,
+
+    TILE_WIRE_LUTFF_1_IN_0,
+    TILE_WIRE_LUTFF_1_IN_1,
+    TILE_WIRE_LUTFF_1_IN_2,
+    TILE_WIRE_LUTFF_1_IN_3,
+
+    TILE_WIRE_LUTFF_2_IN_0,
+    TILE_WIRE_LUTFF_2_IN_1,
+    TILE_WIRE_LUTFF_2_IN_2,
+    TILE_WIRE_LUTFF_2_IN_3,
+
+    TILE_WIRE_LUTFF_3_IN_0,
+    TILE_WIRE_LUTFF_3_IN_1,
+    TILE_WIRE_LUTFF_3_IN_2,
+    TILE_WIRE_LUTFF_3_IN_3,
+
+    TILE_WIRE_LUTFF_4_IN_0,
+    TILE_WIRE_LUTFF_4_IN_1,
+    TILE_WIRE_LUTFF_4_IN_2,
+    TILE_WIRE_LUTFF_4_IN_3,
+
+    TILE_WIRE_LUTFF_5_IN_0,
+    TILE_WIRE_LUTFF_5_IN_1,
+    TILE_WIRE_LUTFF_5_IN_2,
+    TILE_WIRE_LUTFF_5_IN_3,
+
+    TILE_WIRE_LUTFF_6_IN_0,
+    TILE_WIRE_LUTFF_6_IN_1,
+    TILE_WIRE_LUTFF_6_IN_2,
+    TILE_WIRE_LUTFF_6_IN_3,
+
+    TILE_WIRE_LUTFF_7_IN_0,
+    TILE_WIRE_LUTFF_7_IN_1,
+    TILE_WIRE_LUTFF_7_IN_2,
+    TILE_WIRE_LUTFF_7_IN_3,
+
+    TILE_WIRE_LUTFF_0_IN_0_LUT,
+    TILE_WIRE_LUTFF_0_IN_1_LUT,
+    TILE_WIRE_LUTFF_0_IN_2_LUT,
+    TILE_WIRE_LUTFF_0_IN_3_LUT,
+
+    TILE_WIRE_LUTFF_1_IN_0_LUT,
+    TILE_WIRE_LUTFF_1_IN_1_LUT,
+    TILE_WIRE_LUTFF_1_IN_2_LUT,
+    TILE_WIRE_LUTFF_1_IN_3_LUT,
+
+    TILE_WIRE_LUTFF_2_IN_0_LUT,
+    TILE_WIRE_LUTFF_2_IN_1_LUT,
+    TILE_WIRE_LUTFF_2_IN_2_LUT,
+    TILE_WIRE_LUTFF_2_IN_3_LUT,
+
+    TILE_WIRE_LUTFF_3_IN_0_LUT,
+    TILE_WIRE_LUTFF_3_IN_1_LUT,
+    TILE_WIRE_LUTFF_3_IN_2_LUT,
+    TILE_WIRE_LUTFF_3_IN_3_LUT,
+
+    TILE_WIRE_LUTFF_4_IN_0_LUT,
+    TILE_WIRE_LUTFF_4_IN_1_LUT,
+    TILE_WIRE_LUTFF_4_IN_2_LUT,
+    TILE_WIRE_LUTFF_4_IN_3_LUT,
+
+    TILE_WIRE_LUTFF_5_IN_0_LUT,
+    TILE_WIRE_LUTFF_5_IN_1_LUT,
+    TILE_WIRE_LUTFF_5_IN_2_LUT,
+    TILE_WIRE_LUTFF_5_IN_3_LUT,
+
+    TILE_WIRE_LUTFF_6_IN_0_LUT,
+    TILE_WIRE_LUTFF_6_IN_1_LUT,
+    TILE_WIRE_LUTFF_6_IN_2_LUT,
+    TILE_WIRE_LUTFF_6_IN_3_LUT,
+
+    TILE_WIRE_LUTFF_7_IN_0_LUT,
+    TILE_WIRE_LUTFF_7_IN_1_LUT,
+    TILE_WIRE_LUTFF_7_IN_2_LUT,
+    TILE_WIRE_LUTFF_7_IN_3_LUT,
+
+    TILE_WIRE_LUTFF_0_LOUT,
+    TILE_WIRE_LUTFF_1_LOUT,
+    TILE_WIRE_LUTFF_2_LOUT,
+    TILE_WIRE_LUTFF_3_LOUT,
+    TILE_WIRE_LUTFF_4_LOUT,
+    TILE_WIRE_LUTFF_5_LOUT,
+    TILE_WIRE_LUTFF_6_LOUT,
+
+    TILE_WIRE_LUTFF_0_OUT,
+    TILE_WIRE_LUTFF_1_OUT,
+    TILE_WIRE_LUTFF_2_OUT,
+    TILE_WIRE_LUTFF_3_OUT,
+    TILE_WIRE_LUTFF_4_OUT,
+    TILE_WIRE_LUTFF_5_OUT,
+    TILE_WIRE_LUTFF_6_OUT,
+    TILE_WIRE_LUTFF_7_OUT,
+
+    TILE_WIRE_LUTFF_0_COUT,
+    TILE_WIRE_LUTFF_1_COUT,
+    TILE_WIRE_LUTFF_2_COUT,
+    TILE_WIRE_LUTFF_3_COUT,
+    TILE_WIRE_LUTFF_4_COUT,
+    TILE_WIRE_LUTFF_5_COUT,
+    TILE_WIRE_LUTFF_6_COUT,
+    TILE_WIRE_LUTFF_7_COUT,
+
+    TILE_WIRE_LUTFF_GLOBAL_CEN,
+    TILE_WIRE_LUTFF_GLOBAL_CLK,
+    TILE_WIRE_LUTFF_GLOBAL_S_R,
+
+    TILE_WIRE_CARRY_IN,
+    TILE_WIRE_CARRY_IN_MUX,
+
+    TILE_WIRE_NEIGH_OP_BNL_0,
+    TILE_WIRE_NEIGH_OP_BNL_1,
+    TILE_WIRE_NEIGH_OP_BNL_2,
+    TILE_WIRE_NEIGH_OP_BNL_3,
+    TILE_WIRE_NEIGH_OP_BNL_4,
+    TILE_WIRE_NEIGH_OP_BNL_5,
+    TILE_WIRE_NEIGH_OP_BNL_6,
+    TILE_WIRE_NEIGH_OP_BNL_7,
+
+    TILE_WIRE_NEIGH_OP_BNR_0,
+    TILE_WIRE_NEIGH_OP_BNR_1,
+    TILE_WIRE_NEIGH_OP_BNR_2,
+    TILE_WIRE_NEIGH_OP_BNR_3,
+    TILE_WIRE_NEIGH_OP_BNR_4,
+    TILE_WIRE_NEIGH_OP_BNR_5,
+    TILE_WIRE_NEIGH_OP_BNR_6,
+    TILE_WIRE_NEIGH_OP_BNR_7,
+
+    TILE_WIRE_NEIGH_OP_BOT_0,
+    TILE_WIRE_NEIGH_OP_BOT_1,
+    TILE_WIRE_NEIGH_OP_BOT_2,
+    TILE_WIRE_NEIGH_OP_BOT_3,
+    TILE_WIRE_NEIGH_OP_BOT_4,
+    TILE_WIRE_NEIGH_OP_BOT_5,
+    TILE_WIRE_NEIGH_OP_BOT_6,
+    TILE_WIRE_NEIGH_OP_BOT_7,
+
+    TILE_WIRE_NEIGH_OP_LFT_0,
+    TILE_WIRE_NEIGH_OP_LFT_1,
+    TILE_WIRE_NEIGH_OP_LFT_2,
+    TILE_WIRE_NEIGH_OP_LFT_3,
+    TILE_WIRE_NEIGH_OP_LFT_4,
+    TILE_WIRE_NEIGH_OP_LFT_5,
+    TILE_WIRE_NEIGH_OP_LFT_6,
+    TILE_WIRE_NEIGH_OP_LFT_7,
+
+    TILE_WIRE_NEIGH_OP_RGT_0,
+    TILE_WIRE_NEIGH_OP_RGT_1,
+    TILE_WIRE_NEIGH_OP_RGT_2,
+    TILE_WIRE_NEIGH_OP_RGT_3,
+    TILE_WIRE_NEIGH_OP_RGT_4,
+    TILE_WIRE_NEIGH_OP_RGT_5,
+    TILE_WIRE_NEIGH_OP_RGT_6,
+    TILE_WIRE_NEIGH_OP_RGT_7,
+
+    TILE_WIRE_NEIGH_OP_TNL_0,
+    TILE_WIRE_NEIGH_OP_TNL_1,
+    TILE_WIRE_NEIGH_OP_TNL_2,
+    TILE_WIRE_NEIGH_OP_TNL_3,
+    TILE_WIRE_NEIGH_OP_TNL_4,
+    TILE_WIRE_NEIGH_OP_TNL_5,
+    TILE_WIRE_NEIGH_OP_TNL_6,
+    TILE_WIRE_NEIGH_OP_TNL_7,
+
+    TILE_WIRE_NEIGH_OP_TNR_0,
+    TILE_WIRE_NEIGH_OP_TNR_1,
+    TILE_WIRE_NEIGH_OP_TNR_2,
+    TILE_WIRE_NEIGH_OP_TNR_3,
+    TILE_WIRE_NEIGH_OP_TNR_4,
+    TILE_WIRE_NEIGH_OP_TNR_5,
+    TILE_WIRE_NEIGH_OP_TNR_6,
+    TILE_WIRE_NEIGH_OP_TNR_7,
+
+    TILE_WIRE_NEIGH_OP_TOP_0,
+    TILE_WIRE_NEIGH_OP_TOP_1,
+    TILE_WIRE_NEIGH_OP_TOP_2,
+    TILE_WIRE_NEIGH_OP_TOP_3,
+    TILE_WIRE_NEIGH_OP_TOP_4,
+    TILE_WIRE_NEIGH_OP_TOP_5,
+    TILE_WIRE_NEIGH_OP_TOP_6,
+    TILE_WIRE_NEIGH_OP_TOP_7,
+
+    TILE_WIRE_SP4_V_B_0,
+    TILE_WIRE_SP4_V_B_1,
+    TILE_WIRE_SP4_V_B_2,
+    TILE_WIRE_SP4_V_B_3,
+    TILE_WIRE_SP4_V_B_4,
+    TILE_WIRE_SP4_V_B_5,
+    TILE_WIRE_SP4_V_B_6,
+    TILE_WIRE_SP4_V_B_7,
+    TILE_WIRE_SP4_V_B_8,
+    TILE_WIRE_SP4_V_B_9,
+    TILE_WIRE_SP4_V_B_10,
+    TILE_WIRE_SP4_V_B_11,
+    TILE_WIRE_SP4_V_B_12,
+    TILE_WIRE_SP4_V_B_13,
+    TILE_WIRE_SP4_V_B_14,
+    TILE_WIRE_SP4_V_B_15,
+    TILE_WIRE_SP4_V_B_16,
+    TILE_WIRE_SP4_V_B_17,
+    TILE_WIRE_SP4_V_B_18,
+    TILE_WIRE_SP4_V_B_19,
+    TILE_WIRE_SP4_V_B_20,
+    TILE_WIRE_SP4_V_B_21,
+    TILE_WIRE_SP4_V_B_22,
+    TILE_WIRE_SP4_V_B_23,
+    TILE_WIRE_SP4_V_B_24,
+    TILE_WIRE_SP4_V_B_25,
+    TILE_WIRE_SP4_V_B_26,
+    TILE_WIRE_SP4_V_B_27,
+    TILE_WIRE_SP4_V_B_28,
+    TILE_WIRE_SP4_V_B_29,
+    TILE_WIRE_SP4_V_B_30,
+    TILE_WIRE_SP4_V_B_31,
+    TILE_WIRE_SP4_V_B_32,
+    TILE_WIRE_SP4_V_B_33,
+    TILE_WIRE_SP4_V_B_34,
+    TILE_WIRE_SP4_V_B_35,
+    TILE_WIRE_SP4_V_B_36,
+    TILE_WIRE_SP4_V_B_37,
+    TILE_WIRE_SP4_V_B_38,
+    TILE_WIRE_SP4_V_B_39,
+    TILE_WIRE_SP4_V_B_40,
+    TILE_WIRE_SP4_V_B_41,
+    TILE_WIRE_SP4_V_B_42,
+    TILE_WIRE_SP4_V_B_43,
+    TILE_WIRE_SP4_V_B_44,
+    TILE_WIRE_SP4_V_B_45,
+    TILE_WIRE_SP4_V_B_46,
+    TILE_WIRE_SP4_V_B_47,
+
+    TILE_WIRE_SP4_V_T_36,
+    TILE_WIRE_SP4_V_T_37,
+    TILE_WIRE_SP4_V_T_38,
+    TILE_WIRE_SP4_V_T_39,
+    TILE_WIRE_SP4_V_T_40,
+    TILE_WIRE_SP4_V_T_41,
+    TILE_WIRE_SP4_V_T_42,
+    TILE_WIRE_SP4_V_T_43,
+    TILE_WIRE_SP4_V_T_44,
+    TILE_WIRE_SP4_V_T_45,
+    TILE_WIRE_SP4_V_T_46,
+    TILE_WIRE_SP4_V_T_47,
+
+    TILE_WIRE_SP4_R_V_B_0,
+    TILE_WIRE_SP4_R_V_B_1,
+    TILE_WIRE_SP4_R_V_B_2,
+    TILE_WIRE_SP4_R_V_B_3,
+    TILE_WIRE_SP4_R_V_B_4,
+    TILE_WIRE_SP4_R_V_B_5,
+    TILE_WIRE_SP4_R_V_B_6,
+    TILE_WIRE_SP4_R_V_B_7,
+    TILE_WIRE_SP4_R_V_B_8,
+    TILE_WIRE_SP4_R_V_B_9,
+    TILE_WIRE_SP4_R_V_B_10,
+    TILE_WIRE_SP4_R_V_B_11,
+    TILE_WIRE_SP4_R_V_B_12,
+    TILE_WIRE_SP4_R_V_B_13,
+    TILE_WIRE_SP4_R_V_B_14,
+    TILE_WIRE_SP4_R_V_B_15,
+    TILE_WIRE_SP4_R_V_B_16,
+    TILE_WIRE_SP4_R_V_B_17,
+    TILE_WIRE_SP4_R_V_B_18,
+    TILE_WIRE_SP4_R_V_B_19,
+    TILE_WIRE_SP4_R_V_B_20,
+    TILE_WIRE_SP4_R_V_B_21,
+    TILE_WIRE_SP4_R_V_B_22,
+    TILE_WIRE_SP4_R_V_B_23,
+    TILE_WIRE_SP4_R_V_B_24,
+    TILE_WIRE_SP4_R_V_B_25,
+    TILE_WIRE_SP4_R_V_B_26,
+    TILE_WIRE_SP4_R_V_B_27,
+    TILE_WIRE_SP4_R_V_B_28,
+    TILE_WIRE_SP4_R_V_B_29,
+    TILE_WIRE_SP4_R_V_B_30,
+    TILE_WIRE_SP4_R_V_B_31,
+    TILE_WIRE_SP4_R_V_B_32,
+    TILE_WIRE_SP4_R_V_B_33,
+    TILE_WIRE_SP4_R_V_B_34,
+    TILE_WIRE_SP4_R_V_B_35,
+    TILE_WIRE_SP4_R_V_B_36,
+    TILE_WIRE_SP4_R_V_B_37,
+    TILE_WIRE_SP4_R_V_B_38,
+    TILE_WIRE_SP4_R_V_B_39,
+    TILE_WIRE_SP4_R_V_B_40,
+    TILE_WIRE_SP4_R_V_B_41,
+    TILE_WIRE_SP4_R_V_B_42,
+    TILE_WIRE_SP4_R_V_B_43,
+    TILE_WIRE_SP4_R_V_B_44,
+    TILE_WIRE_SP4_R_V_B_45,
+    TILE_WIRE_SP4_R_V_B_46,
+    TILE_WIRE_SP4_R_V_B_47,
+
+    TILE_WIRE_SP4_H_L_36,
+    TILE_WIRE_SP4_H_L_37,
+    TILE_WIRE_SP4_H_L_38,
+    TILE_WIRE_SP4_H_L_39,
+    TILE_WIRE_SP4_H_L_40,
+    TILE_WIRE_SP4_H_L_41,
+    TILE_WIRE_SP4_H_L_42,
+    TILE_WIRE_SP4_H_L_43,
+    TILE_WIRE_SP4_H_L_44,
+    TILE_WIRE_SP4_H_L_45,
+    TILE_WIRE_SP4_H_L_46,
+    TILE_WIRE_SP4_H_L_47,
+
+    TILE_WIRE_SP4_H_R_0,
+    TILE_WIRE_SP4_H_R_1,
+    TILE_WIRE_SP4_H_R_2,
+    TILE_WIRE_SP4_H_R_3,
+    TILE_WIRE_SP4_H_R_4,
+    TILE_WIRE_SP4_H_R_5,
+    TILE_WIRE_SP4_H_R_6,
+    TILE_WIRE_SP4_H_R_7,
+    TILE_WIRE_SP4_H_R_8,
+    TILE_WIRE_SP4_H_R_9,
+    TILE_WIRE_SP4_H_R_10,
+    TILE_WIRE_SP4_H_R_11,
+    TILE_WIRE_SP4_H_R_12,
+    TILE_WIRE_SP4_H_R_13,
+    TILE_WIRE_SP4_H_R_14,
+    TILE_WIRE_SP4_H_R_15,
+    TILE_WIRE_SP4_H_R_16,
+    TILE_WIRE_SP4_H_R_17,
+    TILE_WIRE_SP4_H_R_18,
+    TILE_WIRE_SP4_H_R_19,
+    TILE_WIRE_SP4_H_R_20,
+    TILE_WIRE_SP4_H_R_21,
+    TILE_WIRE_SP4_H_R_22,
+    TILE_WIRE_SP4_H_R_23,
+    TILE_WIRE_SP4_H_R_24,
+    TILE_WIRE_SP4_H_R_25,
+    TILE_WIRE_SP4_H_R_26,
+    TILE_WIRE_SP4_H_R_27,
+    TILE_WIRE_SP4_H_R_28,
+    TILE_WIRE_SP4_H_R_29,
+    TILE_WIRE_SP4_H_R_30,
+    TILE_WIRE_SP4_H_R_31,
+    TILE_WIRE_SP4_H_R_32,
+    TILE_WIRE_SP4_H_R_33,
+    TILE_WIRE_SP4_H_R_34,
+    TILE_WIRE_SP4_H_R_35,
+    TILE_WIRE_SP4_H_R_36,
+    TILE_WIRE_SP4_H_R_37,
+    TILE_WIRE_SP4_H_R_38,
+    TILE_WIRE_SP4_H_R_39,
+    TILE_WIRE_SP4_H_R_40,
+    TILE_WIRE_SP4_H_R_41,
+    TILE_WIRE_SP4_H_R_42,
+    TILE_WIRE_SP4_H_R_43,
+    TILE_WIRE_SP4_H_R_44,
+    TILE_WIRE_SP4_H_R_45,
+    TILE_WIRE_SP4_H_R_46,
+    TILE_WIRE_SP4_H_R_47,
+
+    TILE_WIRE_SP12_V_B_0,
+    TILE_WIRE_SP12_V_B_1,
+    TILE_WIRE_SP12_V_B_2,
+    TILE_WIRE_SP12_V_B_3,
+    TILE_WIRE_SP12_V_B_4,
+    TILE_WIRE_SP12_V_B_5,
+    TILE_WIRE_SP12_V_B_6,
+    TILE_WIRE_SP12_V_B_7,
+    TILE_WIRE_SP12_V_B_8,
+    TILE_WIRE_SP12_V_B_9,
+    TILE_WIRE_SP12_V_B_10,
+    TILE_WIRE_SP12_V_B_11,
+    TILE_WIRE_SP12_V_B_12,
+    TILE_WIRE_SP12_V_B_13,
+    TILE_WIRE_SP12_V_B_14,
+    TILE_WIRE_SP12_V_B_15,
+    TILE_WIRE_SP12_V_B_16,
+    TILE_WIRE_SP12_V_B_17,
+    TILE_WIRE_SP12_V_B_18,
+    TILE_WIRE_SP12_V_B_19,
+    TILE_WIRE_SP12_V_B_20,
+    TILE_WIRE_SP12_V_B_21,
+    TILE_WIRE_SP12_V_B_22,
+    TILE_WIRE_SP12_V_B_23,
+
+    TILE_WIRE_SP12_V_T_22,
+    TILE_WIRE_SP12_V_T_23,
+
+    TILE_WIRE_SP12_H_R_0,
+    TILE_WIRE_SP12_H_R_1,
+    TILE_WIRE_SP12_H_R_2,
+    TILE_WIRE_SP12_H_R_3,
+    TILE_WIRE_SP12_H_R_4,
+    TILE_WIRE_SP12_H_R_5,
+    TILE_WIRE_SP12_H_R_6,
+    TILE_WIRE_SP12_H_R_7,
+    TILE_WIRE_SP12_H_R_8,
+    TILE_WIRE_SP12_H_R_9,
+    TILE_WIRE_SP12_H_R_10,
+    TILE_WIRE_SP12_H_R_11,
+    TILE_WIRE_SP12_H_R_12,
+    TILE_WIRE_SP12_H_R_13,
+    TILE_WIRE_SP12_H_R_14,
+    TILE_WIRE_SP12_H_R_15,
+    TILE_WIRE_SP12_H_R_16,
+    TILE_WIRE_SP12_H_R_17,
+    TILE_WIRE_SP12_H_R_18,
+    TILE_WIRE_SP12_H_R_19,
+    TILE_WIRE_SP12_H_R_20,
+    TILE_WIRE_SP12_H_R_21,
+    TILE_WIRE_SP12_H_R_22,
+    TILE_WIRE_SP12_H_R_23,
+
+    TILE_WIRE_SP12_H_L_22,
+    TILE_WIRE_SP12_H_L_23,
+
+    TILE_WIRE_PLLIN,
+    TILE_WIRE_PLLOUT_A,
+    TILE_WIRE_PLLOUT_B
+};
+
+void gfxTileWire(std::vector<GraphicElement> &g, int x, int y, GfxTileWireId id, GraphicElement::style_t style);
+void gfxTilePip(std::vector<GraphicElement> &g, int x, int y, GfxTileWireId src, GfxTileWireId dst,
+                GraphicElement::style_t style);
+
+NEXTPNR_NAMESPACE_END
+
+#endif // GFX_H
diff --git a/xc7/main.cc b/xc7/main.cc
new file mode 100644
index 0000000..cac9a39
--- /dev/null
+++ b/xc7/main.cc
@@ -0,0 +1,124 @@
+/*
+ *  nextpnr -- Next Generation Place and Route
+ *
+ *  Copyright (C) 2018  Clifford Wolf <clifford@symbioticeda.com>
+ *  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.
+ *
+ */
+
+#ifdef MAIN_EXECUTABLE
+
+#include <fstream>
+#include "command.h"
+#include "design_utils.h"
+#include "jsonparse.h"
+#include "log.h"
+#include "pcf.h"
+#include "timing.h"
+#include "xdl.h"
+
+USING_NEXTPNR_NAMESPACE
+
+class Xc7CommandHandler : public CommandHandler
+{
+  public:
+    Xc7CommandHandler(int argc, char **argv);
+    virtual ~Xc7CommandHandler(){};
+    std::unique_ptr<Context> createContext() override;
+    void setupArchContext(Context *ctx) override;
+    void validate() override;
+    void customAfterLoad(Context *ctx) override;
+    void customBitstream(Context *ctx) override;
+
+  protected:
+    po::options_description getArchOptions();
+};
+
+Xc7CommandHandler::Xc7CommandHandler(int argc, char **argv) : CommandHandler(argc, argv) {}
+
+po::options_description Xc7CommandHandler::getArchOptions()
+{
+    po::options_description specific("Architecture specific options");
+    specific.add_options()("z020", "set device type to xc7z020");
+    specific.add_options()("vx980", "set device type to xc7v980");
+    specific.add_options()("package", po::value<std::string>(), "set device package");
+    specific.add_options()("pcf", po::value<std::string>(), "PCF constraints file to ingest");
+    specific.add_options()("xdl", po::value<std::string>(), "XDL file to write");
+    //    specific.add_options()("tmfuzz", "run path delay estimate fuzzer");
+    return specific;
+}
+void Xc7CommandHandler::validate()
+{
+    conflicting_options(vm, "read", "json");
+    //    if ((vm.count("lp384") + vm.count("lp1k") + vm.count("lp8k") + vm.count("hx1k") + vm.count("hx8k") +
+    //         vm.count("up5k")) > 1)
+    //        log_error("Only one device type can be set\n");
+}
+
+void Xc7CommandHandler::customAfterLoad(Context *ctx)
+{
+    if (vm.count("pcf")) {
+        std::string filename = vm["pcf"].as<std::string>();
+        std::ifstream pcf(filename);
+        if (!apply_pcf(ctx, filename, pcf))
+            log_error("Loading PCF failed.\n");
+    }
+}
+void Xc7CommandHandler::customBitstream(Context *ctx)
+{
+    if (vm.count("xdl")) {
+        std::string filename = vm["xdl"].as<std::string>();
+        std::ofstream f(filename);
+        write_xdl(ctx, f);
+    }
+}
+
+void Xc7CommandHandler::setupArchContext(Context *ctx)
+{
+    //    if (vm.count("tmfuzz"))
+    //        ice40DelayFuzzerMain(ctx);
+}
+
+std::unique_ptr<Context> Xc7CommandHandler::createContext()
+{
+    if (vm.count("z020")) {
+        chipArgs.type = ArchArgs::Z020;
+        chipArgs.package = "clg400";
+    }
+
+    if (vm.count("vx980")) {
+        chipArgs.type = ArchArgs::VX980;
+        chipArgs.package = "ffg1926";
+    }
+
+
+    if (chipArgs.type == ArchArgs::NONE) {
+        chipArgs.type = ArchArgs::Z020;
+        chipArgs.package = "clg400";
+    }
+
+    if (vm.count("package"))
+        chipArgs.package = vm["package"].as<std::string>();
+
+    return std::unique_ptr<Context>(new Context(chipArgs));
+}
+
+int main(int argc, char *argv[])
+{
+    Xc7CommandHandler handler(argc, argv);
+    return handler.exec();
+}
+
+#endif
diff --git a/xc7/pack.cc b/xc7/pack.cc
new file mode 100644
index 0000000..a4b57f2
--- /dev/null
+++ b/xc7/pack.cc
@@ -0,0 +1,728 @@
+/*
+ *  nextpnr -- Next Generation Place and Route
+ *
+ *  Copyright (C) 2018  Clifford Wolf <clifford@symbioticeda.com>
+ *  Copyright (C) 2018  David Shah <david@symbioticeda.com>
+ *  Copyright (C) 2018  Serge Bazanski <q3k@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 <algorithm>
+#include <iterator>
+#include <unordered_set>
+#include "cells.h"
+#include "chains.h"
+#include "design_utils.h"
+#include "log.h"
+#include "util.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+// Pack LUTs and LUT-FF pairs
+static void pack_lut_lutffs(Context *ctx)
+{
+    log_info("Packing LUT-FFs..\n");
+
+    std::unordered_set<IdString> packed_cells;
+    std::vector<std::unique_ptr<CellInfo>> new_cells;
+    for (auto cell : sorted(ctx->cells)) {
+        CellInfo *ci = cell.second;
+        if (ctx->verbose)
+            log_info("cell '%s' is of type '%s'\n", ci->name.c_str(ctx), ci->type.c_str(ctx));
+        if (is_lut(ctx, ci)) {
+            std::unique_ptr<CellInfo> packed = create_xc7_cell(ctx, ctx->id("XC7_LC"), ci->name.str(ctx) + "_LC");
+            std::copy(ci->attrs.begin(), ci->attrs.end(), std::inserter(packed->attrs, packed->attrs.begin()));
+            packed_cells.insert(ci->name);
+            if (ctx->verbose)
+                log_info("packed cell %s into %s\n", ci->name.c_str(ctx), packed->name.c_str(ctx));
+            // See if we can pack into a DFF
+            // TODO: LUT cascade
+            NetInfo *o = ci->ports.at(ctx->id("O")).net;
+            CellInfo *dff = net_only_drives(ctx, o, is_ff, ctx->id("D"), true);
+            auto lut_bel = ci->attrs.find(ctx->id("BEL"));
+            bool packed_dff = false;
+            if (dff) {
+                if (ctx->verbose)
+                    log_info("found attached dff %s\n", dff->name.c_str(ctx));
+                auto dff_bel = dff->attrs.find(ctx->id("BEL"));
+                if (lut_bel != ci->attrs.end() && dff_bel != dff->attrs.end() && lut_bel->second != dff_bel->second) {
+                    // Locations don't match, can't pack
+                } else {
+                    lut_to_lc(ctx, ci, packed.get(), false);
+                    dff_to_lc(ctx, dff, packed.get(), false);
+                    ctx->nets.erase(o->name);
+                    if (dff_bel != dff->attrs.end())
+                        packed->attrs[ctx->id("BEL")] = dff_bel->second;
+                    packed_cells.insert(dff->name);
+                    if (ctx->verbose)
+                        log_info("packed cell %s into %s\n", dff->name.c_str(ctx), packed->name.c_str(ctx));
+                    packed_dff = true;
+                }
+            }
+            if (!packed_dff) {
+                lut_to_lc(ctx, ci, packed.get(), true);
+            }
+            new_cells.push_back(std::move(packed));
+        }
+    }
+    for (auto pcell : packed_cells) {
+        ctx->cells.erase(pcell);
+    }
+    for (auto &ncell : new_cells) {
+        ctx->cells[ncell->name] = std::move(ncell);
+    }
+}
+
+// Pack FFs not packed as LUTFFs
+static void pack_nonlut_ffs(Context *ctx)
+{
+    log_info("Packing non-LUT FFs..\n");
+
+    std::unordered_set<IdString> packed_cells;
+    std::vector<std::unique_ptr<CellInfo>> new_cells;
+
+    for (auto cell : sorted(ctx->cells)) {
+        CellInfo *ci = cell.second;
+        if (is_ff(ctx, ci)) {
+            std::unique_ptr<CellInfo> packed = create_xc7_cell(ctx, ctx->id("XC7_LC"), ci->name.str(ctx) + "_DFFLC");
+            std::copy(ci->attrs.begin(), ci->attrs.end(), std::inserter(packed->attrs, packed->attrs.begin()));
+            if (ctx->verbose)
+                log_info("packed cell %s into %s\n", ci->name.c_str(ctx), packed->name.c_str(ctx));
+            packed_cells.insert(ci->name);
+            dff_to_lc(ctx, ci, packed.get(), true);
+            new_cells.push_back(std::move(packed));
+        }
+    }
+    for (auto pcell : packed_cells) {
+        ctx->cells.erase(pcell);
+    }
+    for (auto &ncell : new_cells) {
+        ctx->cells[ncell->name] = std::move(ncell);
+    }
+}
+
+static bool net_is_constant(const Context *ctx, NetInfo *net, bool &value)
+{
+    if (net == nullptr)
+        return false;
+    if (net->name == ctx->id("$PACKER_GND_NET") || net->name == ctx->id("$PACKER_VCC_NET")) {
+        value = (net->name == ctx->id("$PACKER_VCC_NET"));
+        return true;
+    } else {
+        return false;
+    }
+}
+
+// Pack carry logic
+static void pack_carries(Context *ctx)
+{
+    //log_info("Packing carries..\n");
+    // TODO
+}
+
+// "Pack" RAMs
+static void pack_ram(Context *ctx)
+{
+    //log_info("Packing RAMs..\n");
+    // TODO
+}
+
+// Merge a net into a constant net
+static void set_net_constant(const Context *ctx, NetInfo *orig, NetInfo *constnet, bool constval)
+{
+    orig->driver.cell = nullptr;
+    for (auto user : orig->users) {
+        if (user.cell != nullptr) {
+            CellInfo *uc = user.cell;
+            if (ctx->verbose)
+                log_info("%s user %s\n", orig->name.c_str(ctx), uc->name.c_str(ctx));
+            if ((is_lut(ctx, uc) || is_lc(ctx, uc) || is_carry(ctx, uc)) && (user.port.str(ctx).at(0) == 'I') &&
+                !constval) {
+                uc->ports[user.port].net = nullptr;
+            } else {
+                uc->ports[user.port].net = constnet;
+                constnet->users.push_back(user);
+            }
+        }
+    }
+    orig->users.clear();
+}
+
+// Pack constants (simple implementation)
+static void pack_constants(Context *ctx)
+{
+    log_info("Packing constants..\n");
+
+    std::unique_ptr<CellInfo> gnd_cell = create_xc7_cell(ctx, ctx->id("XC7_LC"), "$PACKER_GND");
+    gnd_cell->params[ctx->id("INIT")] = "0";
+    std::unique_ptr<NetInfo> gnd_net = std::unique_ptr<NetInfo>(new NetInfo);
+    gnd_net->name = ctx->id("$PACKER_GND_NET");
+    gnd_net->driver.cell = gnd_cell.get();
+    gnd_net->driver.port = id_O;
+    gnd_cell->ports.at(id_O).net = gnd_net.get();
+
+    std::unique_ptr<CellInfo> vcc_cell = create_xc7_cell(ctx, ctx->id("XC7_LC"), "$PACKER_VCC");
+    vcc_cell->params[ctx->id("INIT")] = "1";
+    std::unique_ptr<NetInfo> vcc_net = std::unique_ptr<NetInfo>(new NetInfo);
+    vcc_net->name = ctx->id("$PACKER_VCC_NET");
+    vcc_net->driver.cell = vcc_cell.get();
+    vcc_net->driver.port = id_O;
+    vcc_cell->ports.at(id_O).net = vcc_net.get();
+
+    std::vector<IdString> dead_nets;
+
+    bool gnd_used = false;
+
+    for (auto net : sorted(ctx->nets)) {
+        NetInfo *ni = net.second;
+        if (ni->driver.cell != nullptr && ni->driver.cell->type == ctx->id("GND")) {
+            IdString drv_cell = ni->driver.cell->name;
+            set_net_constant(ctx, ni, gnd_net.get(), false);
+            gnd_used = true;
+            dead_nets.push_back(net.first);
+            ctx->cells.erase(drv_cell);
+        } else if (ni->driver.cell != nullptr && ni->driver.cell->type == ctx->id("VCC")) {
+            IdString drv_cell = ni->driver.cell->name;
+            set_net_constant(ctx, ni, vcc_net.get(), true);
+            dead_nets.push_back(net.first);
+            ctx->cells.erase(drv_cell);
+        }
+    }
+
+    if (gnd_used) {
+        ctx->cells[gnd_cell->name] = std::move(gnd_cell);
+        ctx->nets[gnd_net->name] = std::move(gnd_net);
+    }
+    // Vcc cell always inserted for now, as it may be needed during carry legalisation (TODO: trim later if actually
+    // never used?)
+    ctx->cells[vcc_cell->name] = std::move(vcc_cell);
+    ctx->nets[vcc_net->name] = std::move(vcc_net);
+
+    for (auto dn : dead_nets) {
+        ctx->nets.erase(dn);
+    }
+}
+
+static bool is_nextpnr_iob(Context *ctx, CellInfo *cell)
+{
+    return cell->type == ctx->id("$nextpnr_ibuf") || cell->type == ctx->id("$nextpnr_obuf") ||
+           cell->type == ctx->id("$nextpnr_iobuf");
+}
+
+// Pack IO buffers
+static void pack_io(Context *ctx)
+{
+    std::unordered_set<IdString> packed_cells;
+    std::vector<std::unique_ptr<CellInfo>> new_cells;
+    log_info("Packing IOs..\n");
+
+    for (auto cell : sorted(ctx->cells)) {
+        CellInfo *ci = cell.second;
+        if (is_nextpnr_iob(ctx, ci)) {
+            CellInfo *sb = nullptr;
+            if (ci->type == ctx->id("$nextpnr_ibuf") || ci->type == ctx->id("$nextpnr_iobuf")) {
+                sb = net_only_drives(ctx, ci->ports.at(ctx->id("O")).net, is_sb_io, ctx->id("PACKAGE_PIN"), true, ci);
+
+            } else if (ci->type == ctx->id("$nextpnr_obuf")) {
+                sb = net_only_drives(ctx, ci->ports.at(ctx->id("I")).net, is_sb_io, ctx->id("PACKAGE_PIN"), true, ci);
+            }
+            if (sb != nullptr) {
+                // Trivial case, IOBUF used. Just destroy the net and the
+                // iobuf
+                log_info("%s feeds IOBUF %s, removing %s %s.\n", ci->name.c_str(ctx), sb->name.c_str(ctx),
+                         ci->type.c_str(ctx), ci->name.c_str(ctx));
+                NetInfo *net = sb->ports.at(ctx->id("PACKAGE_PIN")).net;
+                if (net != nullptr) {
+                    ctx->nets.erase(net->name);
+                    sb->ports.at(ctx->id("PACKAGE_PIN")).net = nullptr;
+                }
+                if (ci->type == ctx->id("$nextpnr_iobuf")) {
+                    NetInfo *net2 = ci->ports.at(ctx->id("I")).net;
+                    if (net2 != nullptr) {
+                        ctx->nets.erase(net2->name);
+                    }
+                }
+            } else {
+                // Create a IOBUF buffer
+                std::unique_ptr<CellInfo> xc7_cell = create_xc7_cell(ctx, ctx->id("IOBUF"), ci->name.str(ctx));
+                nxio_to_sb(ctx, ci, xc7_cell.get());
+                new_cells.push_back(std::move(xc7_cell));
+                sb = new_cells.back().get();
+            }
+            packed_cells.insert(ci->name);
+            std::copy(ci->attrs.begin(), ci->attrs.end(), std::inserter(sb->attrs, sb->attrs.begin()));
+        }
+    }
+    for (auto pcell : packed_cells) {
+        ctx->cells.erase(pcell);
+    }
+    for (auto &ncell : new_cells) {
+        ctx->cells[ncell->name] = std::move(ncell);
+    }
+}
+
+// Return true if a port counts as "logic" for global promotion
+static bool is_logic_port(BaseCtx *ctx, const PortRef &port)
+{
+    if (is_clock_port(ctx, port) || is_reset_port(ctx, port) || is_enable_port(ctx, port))
+        return false;
+    return !is_sb_io(ctx, port.cell) && port.cell->type != id_BUFGCTRL;
+}
+
+static void insert_global(Context *ctx, NetInfo *net, bool is_reset, bool is_cen, bool is_logic)
+{
+    std::string glb_name = net->name.str(ctx) + std::string("_$glb_") + (is_reset ? "sr" : (is_cen ? "ce" : "clk"));
+    std::unique_ptr<CellInfo> gb = create_xc7_cell(ctx, id_BUFGCTRL, "$bufg_" + glb_name);
+    gb->ports[ctx->id("I0")].net = net;
+    PortRef pr;
+    pr.cell = gb.get();
+    pr.port = ctx->id("I0");
+    net->users.push_back(pr);
+
+    pr.cell = gb.get();
+    pr.port = ctx->id("O");
+    std::unique_ptr<NetInfo> glbnet = std::unique_ptr<NetInfo>(new NetInfo());
+    glbnet->name = ctx->id(glb_name);
+    glbnet->driver = pr;
+    gb->ports[ctx->id("O")].net = glbnet.get();
+    std::vector<PortRef> keep_users;
+    for (auto user : net->users) {
+        if (is_clock_port(ctx, user) || (is_reset && is_reset_port(ctx, user)) ||
+            (is_cen && is_enable_port(ctx, user)) || (is_logic && is_logic_port(ctx, user))) {
+            user.cell->ports[user.port].net = glbnet.get();
+            glbnet->users.push_back(user);
+        } else {
+            keep_users.push_back(user);
+        }
+    }
+    net->users = keep_users;
+    ctx->nets[glbnet->name] = std::move(glbnet);
+    ctx->cells[gb->name] = std::move(gb);
+}
+
+// Simple global promoter (clock only)
+static void promote_globals(Context *ctx)
+{
+    log_info("Promoting globals..\n");
+    const int logic_fanout_thresh = 15;
+    const int enable_fanout_thresh = 5;
+    std::map<IdString, int> clock_count, reset_count, cen_count, logic_count;
+    for (auto net : sorted(ctx->nets)) {
+        NetInfo *ni = net.second;
+        if (ni->driver.cell != nullptr && !ctx->isGlobalNet(ni)) {
+            clock_count[net.first] = 0;
+            reset_count[net.first] = 0;
+            cen_count[net.first] = 0;
+
+            for (auto user : ni->users) {
+                if (is_clock_port(ctx, user))
+                    clock_count[net.first]++;
+                if (is_reset_port(ctx, user))
+                    reset_count[net.first]++;
+                if (is_enable_port(ctx, user))
+                    cen_count[net.first]++;
+                if (is_logic_port(ctx, user))
+                    logic_count[net.first]++;
+            }
+        }
+    }
+    int prom_globals = 0, prom_resets = 0, prom_cens = 0, prom_logics = 0;
+    int gbs_available = 8;
+    for (auto &cell : ctx->cells)
+        if (is_gbuf(ctx, cell.second.get()))
+            --gbs_available;
+    while (prom_globals < gbs_available) {
+        auto global_clock = std::max_element(clock_count.begin(), clock_count.end(),
+                                             [](const std::pair<IdString, int> &a, const std::pair<IdString, int> &b) {
+                                                 return a.second < b.second;
+                                             });
+
+        auto global_reset = std::max_element(reset_count.begin(), reset_count.end(),
+                                             [](const std::pair<IdString, int> &a, const std::pair<IdString, int> &b) {
+                                                 return a.second < b.second;
+                                             });
+        auto global_cen = std::max_element(cen_count.begin(), cen_count.end(),
+                                           [](const std::pair<IdString, int> &a, const std::pair<IdString, int> &b) {
+                                               return a.second < b.second;
+                                           });
+        auto global_logic = std::max_element(logic_count.begin(), logic_count.end(),
+                                             [](const std::pair<IdString, int> &a, const std::pair<IdString, int> &b) {
+                                                 return a.second < b.second;
+                                             });
+        if (global_clock->second == 0 && prom_logics < 4 && global_logic->second > logic_fanout_thresh &&
+            (global_logic->second > global_cen->second || prom_cens >= 4) &&
+            (global_logic->second > global_reset->second || prom_resets >= 4)) {
+            NetInfo *logicnet = ctx->nets[global_logic->first].get();
+            insert_global(ctx, logicnet, false, false, true);
+            ++prom_globals;
+            ++prom_logics;
+            clock_count.erase(logicnet->name);
+            reset_count.erase(logicnet->name);
+            cen_count.erase(logicnet->name);
+            logic_count.erase(logicnet->name);
+        } else if (global_reset->second > global_clock->second && prom_resets < 4) {
+            NetInfo *rstnet = ctx->nets[global_reset->first].get();
+            insert_global(ctx, rstnet, true, false, false);
+            ++prom_globals;
+            ++prom_resets;
+            clock_count.erase(rstnet->name);
+            reset_count.erase(rstnet->name);
+            cen_count.erase(rstnet->name);
+            logic_count.erase(rstnet->name);
+        } else if (global_cen->second > global_clock->second && prom_cens < 4 &&
+                   global_cen->second > enable_fanout_thresh) {
+            NetInfo *cennet = ctx->nets[global_cen->first].get();
+            insert_global(ctx, cennet, false, true, false);
+            ++prom_globals;
+            ++prom_cens;
+            clock_count.erase(cennet->name);
+            reset_count.erase(cennet->name);
+            cen_count.erase(cennet->name);
+            logic_count.erase(cennet->name);
+        } else if (global_clock->second != 0) {
+            NetInfo *clknet = ctx->nets[global_clock->first].get();
+            insert_global(ctx, clknet, false, false, false);
+            ++prom_globals;
+            clock_count.erase(clknet->name);
+            reset_count.erase(clknet->name);
+            cen_count.erase(clknet->name);
+            logic_count.erase(clknet->name);
+        } else {
+            break;
+        }
+    }
+}
+
+// spliceLUT adds a pass-through LUT LC between the given cell's output port
+// and either all users or only non_LUT users.
+static std::unique_ptr<CellInfo> spliceLUT(Context *ctx, CellInfo *ci, IdString portId, bool onlyNonLUTs)
+{
+    auto port = ci->ports[portId];
+
+    NPNR_ASSERT(port.net != nullptr);
+
+    // Create pass-through LUT.
+    std::unique_ptr<CellInfo> pt =
+            create_xc7_cell(ctx, ctx->id("XC7_LC"), ci->name.str(ctx) + "$nextpnr_" + portId.str(ctx) + "_lut_through");
+    pt->params[ctx->id("INIT")] = "65280"; // output is always I3
+
+    // Create LUT output net.
+    std::unique_ptr<NetInfo> out_net = std::unique_ptr<NetInfo>(new NetInfo);
+    out_net->name = ctx->id(ci->name.str(ctx) + "$nextnr_" + portId.str(ctx) + "_lut_through_net");
+    out_net->driver.cell = pt.get();
+    out_net->driver.port = ctx->id("O");
+    pt->ports.at(ctx->id("O")).net = out_net.get();
+
+    // New users of the original cell's port
+    std::vector<PortRef> new_users;
+    for (const auto &user : port.net->users) {
+        if (onlyNonLUTs && user.cell->type == ctx->id("XC7_LC")) {
+            new_users.push_back(user);
+            continue;
+        }
+        // Rewrite pointer into net in user.
+        user.cell->ports[user.port].net = out_net.get();
+        // Add user to net.
+        PortRef pr;
+        pr.cell = user.cell;
+        pr.port = user.port;
+        out_net->users.push_back(pr);
+    }
+
+    // Add LUT to new users.
+    PortRef pr;
+    pr.cell = pt.get();
+    pr.port = ctx->id("I3");
+    new_users.push_back(pr);
+    pt->ports.at(ctx->id("I3")).net = port.net;
+
+    // Replace users of the original net.
+    port.net->users = new_users;
+
+    ctx->nets[out_net->name] = std::move(out_net);
+    return pt;
+}
+
+// Pack special functions
+static void pack_special(Context *ctx)
+{
+    log_info("Packing special functions..\n");
+
+    std::unordered_set<IdString> packed_cells;
+    std::vector<std::unique_ptr<CellInfo>> new_cells;
+
+    for (auto cell : sorted(ctx->cells)) {
+        CellInfo *ci = cell.second;
+        if (ci->type == id_BUFGCTRL) {
+            ci->params.emplace(ctx->id("PRESELECT_I0"), "FALSE");
+            ci->params.emplace(ctx->id("CE0INV"), "CE0");
+            ci->params.emplace(ctx->id("S0INV"), "S0");
+            ci->params.emplace(ctx->id("IGNORE0INV"), "IGNORE0");
+            ci->params.emplace(ctx->id("CE1INV"), "CE1");
+            ci->params.emplace(ctx->id("S1INV"), "S1");
+            ci->params.emplace(ctx->id("IGNORE1INV"), "IGNORE1");
+        } else if (ci->type == id_MMCME2_ADV) {
+            ci->params.emplace(ctx->id("BANDWIDTH"), "OPTIMIZED");
+            ci->params.emplace(ctx->id("CLKBURST_ENABLE"), "FALSE");
+            ci->params.emplace(ctx->id("CLKBURST_REPEAT"), "FALSE");
+            ci->params.emplace(ctx->id("CLKFBIN_EDGE"), "FALSE");
+            ci->params.emplace(ctx->id("CLKFBIN_NOCOUNT"), "TRUE");
+            ci->params.emplace(ctx->id("CLKFBOUT_EDGE"), "FALSE");
+            ci->params.emplace(ctx->id("CLKFBOUT_EN"), "TRUE");
+            ci->params.emplace(ctx->id("CLKFBOUT_FRAC_EN"), "FALSE");
+            ci->params.emplace(ctx->id("CLKFBOUT_FRAC_WF_FALL"), "FALSE");
+            ci->params.emplace(ctx->id("CLKFBOUT_FRAC_WF_RISE"), "FALSE");
+            ci->params.emplace(ctx->id("CLKFBOUT_NOCOUNT"), "TRUE");
+            ci->params.emplace(ctx->id("CLKFBOUT_USE_FINE_PS"), "FALSE");
+            ci->params.emplace(ctx->id("CLKINSELINV"), "CLKINSEL");
+            ci->params.emplace(ctx->id("CLKOUT0_EDGE"), "FALSE");
+            ci->params.emplace(ctx->id("CLKOUT0_EN"), "FALSE");
+            ci->params.emplace(ctx->id("CLKOUT0_FRAC_EN"), "FALSE");
+            ci->params.emplace(ctx->id("CLKOUT0_FRAC_WF_FALL"), "FALSE");
+            ci->params.emplace(ctx->id("CLKOUT0_FRAC_WF_RISE"), "FALSE");
+            ci->params.emplace(ctx->id("CLKOUT0_NOCOUNT"), "TRUE");
+            ci->params.emplace(ctx->id("CLKOUT0_USE_FINE_PS"), "FALSE");
+            ci->params.emplace(ctx->id("CLKOUT1_EDGE"), "FALSE");
+            ci->params.emplace(ctx->id("CLKOUT1_EN"), "FALSE");
+            ci->params.emplace(ctx->id("CLKOUT1_NOCOUNT"), "TRUE");
+            ci->params.emplace(ctx->id("CLKOUT1_USE_FINE_PS"), "FALSE");
+            ci->params.emplace(ctx->id("CLKOUT2_EDGE"), "FALSE");
+            ci->params.emplace(ctx->id("CLKOUT2_EN"), "FALSE");
+            ci->params.emplace(ctx->id("CLKOUT2_NOCOUNT"), "TRUE");
+            ci->params.emplace(ctx->id("CLKOUT2_USE_FINE_PS"), "FALSE");
+            ci->params.emplace(ctx->id("CLKOUT3_EDGE"), "FALSE");
+            ci->params.emplace(ctx->id("CLKOUT3_EN"), "FALSE");
+            ci->params.emplace(ctx->id("CLKOUT3_NOCOUNT"), "TRUE");
+            ci->params.emplace(ctx->id("CLKOUT3_USE_FINE_PS"), "FALSE");
+            ci->params.emplace(ctx->id("CLKOUT4_CASCADE"), "FALSE");
+            ci->params.emplace(ctx->id("CLKOUT4_EDGE"), "FALSE");
+            ci->params.emplace(ctx->id("CLKOUT4_EN"), "FALSE");
+            ci->params.emplace(ctx->id("CLKOUT4_NOCOUNT"), "TRUE");
+            ci->params.emplace(ctx->id("CLKOUT4_USE_FINE_PS"), "FALSE");
+            ci->params.emplace(ctx->id("CLKOUT5_EDGE"), "FALSE");
+            ci->params.emplace(ctx->id("CLKOUT5_EN"), "FALSE");
+            ci->params.emplace(ctx->id("CLKOUT5_NOCOUNT"), "TRUE");
+            ci->params.emplace(ctx->id("CLKOUT5_USE_FINE_PS"), "FALSE");
+            ci->params.emplace(ctx->id("CLKOUT6_EDGE"), "FALSE");
+            ci->params.emplace(ctx->id("CLKOUT6_EN"), "FALSE");
+            ci->params.emplace(ctx->id("CLKOUT6_NOCOUNT"), "TRUE");
+            ci->params.emplace(ctx->id("CLKOUT6_USE_FINE_PS"), "FALSE");
+            ci->params.emplace(ctx->id("COMPENSATION"), "INTERNAL");
+            ci->params.emplace(ctx->id("DIRECT_PATH_CNTRL"), "FALSE");
+            ci->params.emplace(ctx->id("DIVCLK_EDGE"), "FALSE");
+            ci->params.emplace(ctx->id("DIVCLK_NOCOUNT"), "TRUE");
+            ci->params.emplace(ctx->id("EN_VCO_DIV1"), "FALSE");
+            ci->params.emplace(ctx->id("EN_VCO_DIV6"), "FALSE");
+            ci->params.emplace(ctx->id("GTS_WAIT"), "FALSE");
+            ci->params.emplace(ctx->id("HVLF_CNT_TEST_EN"), "FALSE");
+            ci->params.emplace(ctx->id("INTERP_TEST"), "FALSE");
+            ci->params.emplace(ctx->id("IN_DLY_EN"), "TRUE");
+            ci->params.emplace(ctx->id("LF_LOW_SEL"), "FALSE");
+            ci->params.emplace(ctx->id("MMCM_EN"), "TRUE");
+            ci->params.emplace(ctx->id("PERF0_USE_CLK"), "FALSE");
+            ci->params.emplace(ctx->id("PERF1_USE_CLK"), "FALSE");
+            ci->params.emplace(ctx->id("PERF2_USE_CLK"), "FALSE");
+            ci->params.emplace(ctx->id("PERF3_USE_CLK"), "FALSE");
+            ci->params.emplace(ctx->id("PSENINV"), "PSEN");
+            ci->params.emplace(ctx->id("PSINCDECINV"), "PSINCDEC");
+            ci->params.emplace(ctx->id("PWRDWNINV"), "PWRDWN");
+            ci->params.emplace(ctx->id("RSTINV"), "RST");
+            ci->params.emplace(ctx->id("SEL_HV_NMOS"), "FALSE");
+            ci->params.emplace(ctx->id("SEL_LV_NMOS"), "FALSE");
+            ci->params.emplace(ctx->id("SEL_SLIPD"), "FALSE");
+            ci->params.emplace(ctx->id("SS_EN"), "FALSE");
+            ci->params.emplace(ctx->id("SS_MODE"), "CENTER_HIGH");
+            ci->params.emplace(ctx->id("STARTUP_WAIT"), "FALSE");
+            ci->params.emplace(ctx->id("SUP_SEL_AREG"), "FALSE");
+            ci->params.emplace(ctx->id("SUP_SEL_DREG"), "FALSE");
+            ci->params.emplace(ctx->id("TMUX_MUX_SEL"), "00");
+            ci->params.emplace(ctx->id("VLF_HIGH_DIS_B"), "TRUE");
+            ci->params.emplace(ctx->id("VLF_HIGH_PWDN_B"), "TRUE");
+            //ci->params.emplace(ctx->id("MMCME2_ADV:mmcm_adv_inst:");
+            ci->params.emplace(ctx->id("ANALOG_MISC"), "0000");
+            ci->params.emplace(ctx->id("AVDD_COMP_SET"), "011");
+            ci->params.emplace(ctx->id("AVDD_VBG_PD"), "110");
+            ci->params.emplace(ctx->id("AVDD_VBG_SEL"), "1001");
+            ci->params.emplace(ctx->id("CLKBURST_CNT"), "1");
+            ci->params.emplace(ctx->id("CLKFBIN_HT"), "1");
+            ci->params.emplace(ctx->id("CLKFBIN_LT"), "1");
+            ci->params.emplace(ctx->id("CLKFBIN_MULT"), "1");
+            ci->params.emplace(ctx->id("CLKFBOUT_DT"), "0");
+            ci->params.emplace(ctx->id("CLKFBOUT_FRAC"), "0");
+            ci->params.emplace(ctx->id("CLKFBOUT_HT"), "1");
+            ci->params.emplace(ctx->id("CLKFBOUT_LT"), "1");
+            ci->params.emplace(ctx->id("CLKFBOUT_MULT_F"), "40.5");
+            ci->params.emplace(ctx->id("CLKFBOUT_MX"), "00");
+            ci->params.emplace(ctx->id("CLKFBOUT_PHASE"), "0.0");
+            ci->params.emplace(ctx->id("CLKFBOUT_PM_FALL"), "000");
+            ci->params.emplace(ctx->id("CLKFBOUT_PM_RISE"), "000");
+            ci->params.emplace(ctx->id("CLKFB_MUX_SEL"), "000");
+            ci->params.emplace(ctx->id("CLKIN1_MUX_SEL"), "000");
+            ci->params.emplace(ctx->id("CLKIN1_PERIOD"), "8");
+            ci->params.emplace(ctx->id("CLKIN2_MUX_SEL"), "000");
+            ci->params.emplace(ctx->id("CLKIN2_PERIOD"), "0");
+            ci->params.emplace(ctx->id("CLKOUT0_DIVIDE_F"), "16.875");
+            ci->params.emplace(ctx->id("CLKOUT0_DT"), "0");
+            ci->params.emplace(ctx->id("CLKOUT0_DUTY_CYCLE"), "0.5");
+            ci->params.emplace(ctx->id("CLKOUT0_FRAC"), "0");
+            ci->params.emplace(ctx->id("CLKOUT0_HT"), "1");
+            ci->params.emplace(ctx->id("CLKOUT0_LT"), "1");
+            ci->params.emplace(ctx->id("CLKOUT0_MX"), "00");
+            ci->params.emplace(ctx->id("CLKOUT0_PHASE"), "0.0");
+            ci->params.emplace(ctx->id("CLKOUT0_PM_FALL"), "000");
+            ci->params.emplace(ctx->id("CLKOUT0_PM_RISE"), "000");
+            ci->params.emplace(ctx->id("CLKOUT1_DIVIDE"), "1");
+            ci->params.emplace(ctx->id("CLKOUT1_DT"), "0");
+            ci->params.emplace(ctx->id("CLKOUT1_DUTY_CYCLE"), "0.5");
+            ci->params.emplace(ctx->id("CLKOUT1_HT"), "1");
+            ci->params.emplace(ctx->id("CLKOUT1_LT"), "1");
+            ci->params.emplace(ctx->id("CLKOUT1_MX"), "00");
+            ci->params.emplace(ctx->id("CLKOUT1_PHASE"), "0.0");
+            ci->params.emplace(ctx->id("CLKOUT1_PM"), "000");
+            ci->params.emplace(ctx->id("CLKOUT2_DIVIDE"), "1");
+            ci->params.emplace(ctx->id("CLKOUT2_DT"), "0");
+            ci->params.emplace(ctx->id("CLKOUT2_DUTY_CYCLE"), "0.5");
+            ci->params.emplace(ctx->id("CLKOUT2_HT"), "1");
+            ci->params.emplace(ctx->id("CLKOUT2_LT"), "1");
+            ci->params.emplace(ctx->id("CLKOUT2_MX"), "00");
+            ci->params.emplace(ctx->id("CLKOUT2_PHASE"), "0.0");
+            ci->params.emplace(ctx->id("CLKOUT2_PM"), "000");
+            ci->params.emplace(ctx->id("CLKOUT3_DIVIDE"), "1");
+            ci->params.emplace(ctx->id("CLKOUT3_DT"), "0");
+            ci->params.emplace(ctx->id("CLKOUT3_DUTY_CYCLE"), "0.5");
+            ci->params.emplace(ctx->id("CLKOUT3_HT"), "1");
+            ci->params.emplace(ctx->id("CLKOUT3_LT"), "1");
+            ci->params.emplace(ctx->id("CLKOUT3_MX"), "00");
+            ci->params.emplace(ctx->id("CLKOUT3_PHASE"), "0.0");
+            ci->params.emplace(ctx->id("CLKOUT3_PM"), "000");
+            ci->params.emplace(ctx->id("CLKOUT4_DIVIDE"), "1");
+            ci->params.emplace(ctx->id("CLKOUT4_DT"), "0");
+            ci->params.emplace(ctx->id("CLKOUT4_DUTY_CYCLE"), "0.5");
+            ci->params.emplace(ctx->id("CLKOUT4_HT"), "1");
+            ci->params.emplace(ctx->id("CLKOUT4_LT"), "1");
+            ci->params.emplace(ctx->id("CLKOUT4_MX"), "00");
+            ci->params.emplace(ctx->id("CLKOUT4_PHASE"), "0.0");
+            ci->params.emplace(ctx->id("CLKOUT4_PM"), "000");
+            ci->params.emplace(ctx->id("CLKOUT5_DIVIDE"), "1");
+            ci->params.emplace(ctx->id("CLKOUT5_DT"), "0");
+            ci->params.emplace(ctx->id("CLKOUT5_DUTY_CYCLE"), "0.5");
+            ci->params.emplace(ctx->id("CLKOUT5_HT"), "1");
+            ci->params.emplace(ctx->id("CLKOUT5_LT"), "1");
+            ci->params.emplace(ctx->id("CLKOUT5_MX"), "00");
+            ci->params.emplace(ctx->id("CLKOUT5_PHASE"), "0.0");
+            ci->params.emplace(ctx->id("CLKOUT5_PM"), "000");
+            ci->params.emplace(ctx->id("CLKOUT6_DIVIDE"), "1");
+            ci->params.emplace(ctx->id("CLKOUT6_DT"), "0");
+            ci->params.emplace(ctx->id("CLKOUT6_DUTY_CYCLE"), "0.5");
+            ci->params.emplace(ctx->id("CLKOUT6_HT"), "1");
+            ci->params.emplace(ctx->id("CLKOUT6_LT"), "1");
+            ci->params.emplace(ctx->id("CLKOUT6_MX"), "00");
+            ci->params.emplace(ctx->id("CLKOUT6_PHASE"), "0.0");
+            ci->params.emplace(ctx->id("CLKOUT6_PM"), "000");
+            ci->params.emplace(ctx->id("CONTROL_0"), "1111001101111100");
+            ci->params.emplace(ctx->id("CONTROL_1"), "0111110101001101");
+            ci->params.emplace(ctx->id("CONTROL_2"), "0101000001000010");
+            ci->params.emplace(ctx->id("CONTROL_3"), "1110101111001000");
+            ci->params.emplace(ctx->id("CONTROL_4"), "1101010011011111");
+            ci->params.emplace(ctx->id("CONTROL_5"), "1010110111111011");
+            ci->params.emplace(ctx->id("CONTROL_6"), "1011001011000011");
+            ci->params.emplace(ctx->id("CONTROL_7"), "0100110000101110");
+            ci->params.emplace(ctx->id("CP"), "0000");
+            ci->params.emplace(ctx->id("CP_BIAS_TRIP_SET"), "0");
+            ci->params.emplace(ctx->id("CP_RES"), "01");
+            ci->params.emplace(ctx->id("DIVCLK_DIVIDE"), "5");
+            ci->params.emplace(ctx->id("DIVCLK_HT"), "1");
+            ci->params.emplace(ctx->id("DIVCLK_LT"), "1");
+            ci->params.emplace(ctx->id("DVDD_COMP_SET"), "011");
+            ci->params.emplace(ctx->id("DVDD_VBG_PD"), "110");
+            ci->params.emplace(ctx->id("DVDD_VBG_SEL"), "1001");
+            ci->params.emplace(ctx->id("EN_CURR_SINK"), "11");
+            ci->params.emplace(ctx->id("FINE_PS_FRAC"), "0");
+            ci->params.emplace(ctx->id("FREQ_BB_USE_CLK0"), "0");
+            ci->params.emplace(ctx->id("FREQ_BB_USE_CLK1"), "0");
+            ci->params.emplace(ctx->id("FREQ_BB_USE_CLK2"), "0");
+            ci->params.emplace(ctx->id("FREQ_BB_USE_CLK3"), "0");
+            ci->params.emplace(ctx->id("FREQ_COMP"), "01");
+            ci->params.emplace(ctx->id("HROW_DLY_SET"), "0");
+            ci->params.emplace(ctx->id("HVLF_CNT_TEST"), "0");
+            ci->params.emplace(ctx->id("INTERP_EN"), "00010000");
+            ci->params.emplace(ctx->id("IN_DLY_MX_CVDD"), "011000");
+            ci->params.emplace(ctx->id("IN_DLY_MX_DVDD"), "000001");
+            ci->params.emplace(ctx->id("IN_DLY_SET"), "38");
+            ci->params.emplace(ctx->id("LFHF"), "11");
+            ci->params.emplace(ctx->id("LF_NEN"), "10");
+            ci->params.emplace(ctx->id("LF_PEN"), "00");
+            ci->params.emplace(ctx->id("LOCK_CNT"), "128");
+            ci->params.emplace(ctx->id("LOCK_FB_DLY"), "3");
+            ci->params.emplace(ctx->id("LOCK_REF_DLY"), "3");
+            ci->params.emplace(ctx->id("LOCK_SAT_HIGH"), "160");
+            ci->params.emplace(ctx->id("MAN_LF"), "000");
+            ci->params.emplace(ctx->id("MVDD_SEL"), "11");
+            ci->params.emplace(ctx->id("PERF0_MUX_SEL"), "000");
+            ci->params.emplace(ctx->id("PERF1_MUX_SEL"), "000");
+            ci->params.emplace(ctx->id("PERF2_MUX_SEL"), "000");
+            ci->params.emplace(ctx->id("PERF3_MUX_SEL"), "000");
+            ci->params.emplace(ctx->id("PFD"), "0100001");
+            ci->params.emplace(ctx->id("REF_JITTER1"), "0.01");
+            ci->params.emplace(ctx->id("REF_JITTER2"), "0.01");
+            ci->params.emplace(ctx->id("RES"), "0000");
+            ci->params.emplace(ctx->id("SKEW_FLOP_INV"), "0000");
+            ci->params.emplace(ctx->id("SPARE_ANALOG"), "00000");
+            ci->params.emplace(ctx->id("SPARE_DIGITAL"), "00000");
+            ci->params.emplace(ctx->id("SS_MOD_PERIOD"), "10000");
+            ci->params.emplace(ctx->id("SS_STEPS"), "011");
+            ci->params.emplace(ctx->id("SS_STEPS_INIT"), "010");
+            ci->params.emplace(ctx->id("SYNTH_CLK_DIV"), "11");
+            ci->params.emplace(ctx->id("UNLOCK_CNT"), "64");
+            ci->params.emplace(ctx->id("VREF_START"), "01");
+
+            ci->params[ctx->id("COMPENSATION")] = "INTERNAL";
+        }
+    }
+
+    for (auto pcell : packed_cells) {
+        ctx->cells.erase(pcell);
+    }
+    for (auto &ncell : new_cells) {
+        ctx->cells[ncell->name] = std::move(ncell);
+    }
+}
+
+// Main pack function
+bool Arch::pack()
+{
+    Context *ctx = getCtx();
+    try {
+        log_break();
+        pack_constants(ctx);
+        // TODO
+        // promote_globals(ctx);
+        pack_io(ctx);
+        pack_lut_lutffs(ctx);
+        pack_nonlut_ffs(ctx);
+        pack_carries(ctx);
+        pack_ram(ctx);
+        pack_special(ctx);
+        ctx->assignArchInfo();
+        constrain_chains(ctx);
+        ctx->assignArchInfo();
+        log_info("Checksum: 0x%08x\n", ctx->checksum());
+        return true;
+    } catch (log_execution_error_exception) {
+        return false;
+    }
+}
+
+NEXTPNR_NAMESPACE_END
diff --git a/xc7/pcf.cc b/xc7/pcf.cc
new file mode 100644
index 0000000..f56a177
--- /dev/null
+++ b/xc7/pcf.cc
@@ -0,0 +1,84 @@
+/*
+ *  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.
+ *
+ */
+
+#include "pcf.h"
+#include <sstream>
+#include "log.h"
+
+#include <boost/algorithm/string.hpp>
+
+NEXTPNR_NAMESPACE_BEGIN
+
+// Read a w
+
+// Apply PCF constraints to a pre-packing design
+bool apply_pcf(Context *ctx, std::string filename, std::istream &in)
+{
+    try {
+        if (!in)
+            log_error("failed to open PCF file\n");
+        std::string line;
+        while (std::getline(in, line)) {
+            size_t cstart = line.find("#");
+            if (cstart != std::string::npos)
+                line = line.substr(0, cstart);
+            std::stringstream ss(line);
+            std::vector<std::string> words;
+            std::string tmp;
+            while (ss >> tmp)
+                words.push_back(tmp);
+            if (words.size() == 0)
+                continue;
+            std::string cmd = words.at(0);
+            if (cmd == "COMP") {
+                size_t args_end = 1;
+                while (args_end < words.size() && words.at(args_end).at(0) == '-')
+                    args_end++;
+                std::string cell = words.at(args_end);
+                boost::trim_if(cell, boost::is_any_of("\""));
+                std::string pin = words.at(args_end + 4);
+                boost::trim_if(pin, boost::is_any_of("\""));
+                auto fnd_cell = ctx->cells.find(ctx->id(cell));
+                if (fnd_cell == ctx->cells.end()) {
+                    log_warning("unmatched pcf constraint %s\n", cell.c_str());
+                } else {
+                    BelId pin_bel = ctx->getPackagePinBel(pin);
+                    if (pin_bel == BelId())
+                        log_error("package does not have a pin named %s\n", pin.c_str());
+                    fnd_cell->second->attrs[ctx->id("BEL")] = ctx->getBelName(pin_bel).str(ctx);
+                    log_info("constrained '%s' to bel '%s'\n", cell.c_str(),
+                             fnd_cell->second->attrs[ctx->id("BEL")].c_str());
+                }
+            } else if (cmd == "NET") {
+                // TODO
+            } else if (cmd == "PIN") {
+                // TODO
+            } else {
+                log_error("unsupported pcf command '%s'\n", cmd.c_str());
+            }
+        }
+        ctx->settings.emplace(ctx->id("project/input/pcf"), filename);
+        return true;
+    } catch (log_execution_error_exception) {
+        return false;
+    }
+}
+
+NEXTPNR_NAMESPACE_END
diff --git a/xc7/pcf.h b/xc7/pcf.h
new file mode 100644
index 0000000..ecc81e5
--- /dev/null
+++ b/xc7/pcf.h
@@ -0,0 +1,34 @@
+/*
+ *  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 PCF_H
+#define PCF_H
+
+#include <iostream>
+#include "nextpnr.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+// Apply PCF constraints to a pre-packing design
+bool apply_pcf(Context *ctx, std::string filename, std::istream &in);
+
+NEXTPNR_NAMESPACE_END
+
+#endif // ROUTE_H
diff --git a/xc7/picorv32.pcf b/xc7/picorv32.pcf
new file mode 100644
index 0000000..bc8e084
--- /dev/null
+++ b/xc7/picorv32.pcf
@@ -0,0 +1,3 @@
+NET "clki" PERIOD = 8 nS ;
+PIN "clki_pin" = BEL "clki.PAD" PINNAME PAD;
+PIN "clki_pin" CLOCK_DEDICATED_ROUTE = FALSE;
diff --git a/xc7/picorv32.proj b/xc7/picorv32.proj
new file mode 100644
index 0000000..a8c83bd
--- /dev/null
+++ b/xc7/picorv32.proj
@@ -0,0 +1,15 @@
+{
+    "project": {
+        "version": "1",
+        "name": "picorv32",
+        "arch": {
+            "name": "ice40",
+            "type": "hx8k",
+            "package": "ct256"
+        },
+        "input": {
+            "json": "picorv32.json",
+            "pcf": "icebreaker.pcf"
+        }
+    }
+}
diff --git a/xc7/picorv32.sh b/xc7/picorv32.sh
new file mode 100755
index 0000000..ec2aee8
--- /dev/null
+++ b/xc7/picorv32.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+set -ex
+rm -f picorv32.v
+wget https://raw.githubusercontent.com/cliffordwolf/picorv32/master/picorv32.v
+yosys picorv32.ys
+set +e
+../nextpnr-xc7 --json picorv32.json --xdl picorv32.xdl --pcf picorv32.pcf --freq 125
+set -e
+xdl -xdl2ncd picorv32.xdl
+#bitgen -w blinky.ncd -g UnconstrainedPins:Allow
+trce picorv32.ncd -v 10
diff --git a/xc7/picorv32.ys b/xc7/picorv32.ys
new file mode 100644
index 0000000..e6eec6c
--- /dev/null
+++ b/xc7/picorv32.ys
@@ -0,0 +1,55 @@
+read_verilog picorv32.v
+read_verilog picorv32_top.v
+read_verilog 125MHz_to_60MHz.v
+
+#synth_xilinx -top picorv32
+
+#begin:
+    read_verilog -lib +/xilinx/cells_sim.v
+    read_verilog -lib +/xilinx/cells_xtra.v
+#    read_verilog -lib +/xilinx/brams_bb.v
+#    read_verilog -lib +/xilinx/drams_bb.v
+    hierarchy -check -top top
+
+#flatten:     (only if -flatten)
+    proc
+    flatten
+
+#coarse:
+    synth -run coarse
+
+#bram:
+#    memory_bram -rules +/xilinx/brams.txt
+#    techmap -map +/xilinx/brams_map.v
+#
+#dram:
+#    memory_bram -rules +/xilinx/drams.txt
+#    techmap -map +/xilinx/drams_map.v
+
+fine:
+    opt -fast -full
+    memory_map
+    dffsr2dff
+#    dff2dffe
+    opt -full
+    techmap -map +/techmap.v #-map +/xilinx/arith_map.v
+    opt -fast
+
+map_luts:
+    abc -luts 2:2,3,6:5 #,10,20 [-dff]
+    clean
+
+map_cells:
+    techmap -map +/xilinx/cells_map.v
+    dffinit -ff FDRE Q INIT -ff FDCE Q INIT -ff FDPE Q INIT
+    clean
+
+check:
+    hierarchy -check
+    stat
+    check -noinit
+
+#edif:     (only if -edif)
+#    write_edif <file-name>
+
+write_json picorv32.json
diff --git a/xc7/picorv32_benchmark.py b/xc7/picorv32_benchmark.py
new file mode 100755
index 0000000..a4ec581
--- /dev/null
+++ b/xc7/picorv32_benchmark.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python3
+import os, sys, threading
+from os import path
+import subprocess
+import re
+
+num_runs = 8
+
+if not path.exists("picorv32.json"):
+    subprocess.run(["wget", "https://raw.githubusercontent.com/cliffordwolf/picorv32/master/picorv32.v"], check=True)
+    subprocess.run(["yosys", "-q", "-p", "synth_ice40 -json picorv32.json -top top", "picorv32.v", "picorv32_top.v"], check=True)
+
+fmax = {}
+
+if not path.exists("picorv32_work"):
+    os.mkdir("picorv32_work")
+
+threads = []
+
+for i in range(num_runs):
+    def runner(run):
+        ascfile = "picorv32_work/picorv32_s{}.asc".format(run)
+        if path.exists(ascfile):
+            os.remove(ascfile)
+        result = subprocess.run(["../nextpnr-ice40", "--hx8k", "--seed", str(run), "--json", "picorv32.json", "--asc", ascfile, "--freq", "70"], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
+        if result.returncode != 0:
+            print("Run {} failed!".format(run))
+        else:
+            icetime_res = subprocess.check_output(["icetime", "-d", "hx8k", ascfile])
+            fmax_m = re.search(r'\(([0-9.]+) MHz\)', icetime_res.decode('utf-8'))
+            fmax[run] = float(fmax_m.group(1))
+    threads.append(threading.Thread(target=runner, args=[i+1]))
+
+for t in threads: t.start()
+for t in threads: t.join()
+
+fmax_min = min(fmax.values())
+fmax_max = max(fmax.values())
+fmax_avg = sum(fmax.values()) / len(fmax)
+
+print("{}/{} runs passed".format(len(fmax), num_runs))
+print("icetime: min = {} MHz, avg = {} MHz, max = {} MHz".format(fmax_min, fmax_avg, fmax_max))
diff --git a/xc7/picorv32_top.v b/xc7/picorv32_top.v
new file mode 100644
index 0000000..3735aa1
--- /dev/null
+++ b/xc7/picorv32_top.v
@@ -0,0 +1,44 @@
+module top (
+	input clki, resetn,
+	output trap,
+
+	output        mem_valid,
+	output        mem_instr,
+	input         mem_ready,
+
+	output [31:0] mem_addr,
+	output [31:0] mem_wdata,
+	output [ 3:0] mem_wstrb,
+	input  [31:0] mem_rdata
+);
+
+    wire clk;
+    BUFGCTRL clk_gb (
+        .I0(clki),
+        .CE0(1'b1),
+        .CE1(1'b0),
+        .S0(1'b1),
+        .S1(1'b0),
+        .IGNORE0(1'b0),
+        .IGNORE1(1'b0),
+        .O(clk)
+    );
+
+    picorv32 #(
+        .ENABLE_COUNTERS(0),
+        .TWO_STAGE_SHIFT(0),
+        .CATCH_MISALIGN(0),
+        .CATCH_ILLINSN(0)
+    ) cpu (
+        .clk      (clk     ),
+        .resetn   (resetn   ),
+        .trap     (trap     ),
+        .mem_valid(mem_valid),
+        .mem_instr(mem_instr),
+        .mem_ready(mem_ready),
+        .mem_addr (mem_addr ),
+        .mem_wdata(mem_wdata),
+        .mem_wstrb(mem_wstrb),
+        .mem_rdata(mem_rdata)
+    );
+endmodule
diff --git a/xc7/project.cc b/xc7/project.cc
new file mode 100644
index 0000000..5c8def1
--- /dev/null
+++ b/xc7/project.cc
@@ -0,0 +1,58 @@
+/*
+ *  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 "project.h"
+#include <boost/filesystem/convenience.hpp>
+#include <fstream>
+#include "log.h"
+#include "pcf.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+void ProjectHandler::saveArch(Context *ctx, pt::ptree &root, std::string path)
+{
+    root.put("project.arch.package", ctx->archArgs().package);
+    if (ctx->settings.find(ctx->id("project/input/pcf")) != ctx->settings.end()) {
+        std::string fn = ctx->settings[ctx->id("project/input/pcf")];
+        root.put("project.input.pcf", make_relative(fn, path).string());
+    }
+}
+
+std::unique_ptr<Context> ProjectHandler::createContext(pt::ptree &root)
+{
+    ArchArgs chipArgs;
+    std::string arch_type = root.get<std::string>("project.arch.type");
+    if (arch_type == "z020") {
+        chipArgs.type = ArchArgs::Z020;
+    }
+    chipArgs.package = root.get<std::string>("project.arch.package");
+
+    return std::unique_ptr<Context>(new Context(chipArgs));
+}
+
+void ProjectHandler::loadArch(Context *ctx, pt::ptree &root, std::string path)
+{
+    auto input = root.get_child("project").get_child("input");
+    boost::filesystem::path pcf = boost::filesystem::path(path) / input.get<std::string>("pcf");
+    std::ifstream f(pcf.string());
+    if (!apply_pcf(ctx, input.get<std::string>("pcf"), f))
+        log_error("Loading PCF failed.\n");
+}
+
+NEXTPNR_NAMESPACE_END
diff --git a/xc7/tmfuzz.py b/xc7/tmfuzz.py
new file mode 100644
index 0000000..4ec2a54
--- /dev/null
+++ b/xc7/tmfuzz.py
@@ -0,0 +1,357 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# ../nextpnr-ice40 --hx8k --tmfuzz > tmfuzz_hx8k.txt
+# ../nextpnr-ice40 --lp8k --tmfuzz > tmfuzz_lp8k.txt
+# ../nextpnr-ice40 --up5k --tmfuzz > tmfuzz_up5k.txt
+
+import numpy as np
+import matplotlib.pyplot as plt
+from collections import defaultdict
+
+device = "hx8k"
+# device = "lp8k"
+# device = "up5k"
+
+sel_src_type = "LUTFF_OUT"
+sel_dst_type = "LUTFF_IN_LUT"
+
+#%% Read fuzz data
+
+src_dst_pairs = defaultdict(lambda: 0)
+
+delay_data = list()
+all_delay_data = list()
+
+delay_map_sum = np.zeros((41, 41))
+delay_map_sum2 = np.zeros((41, 41))
+delay_map_count = np.zeros((41, 41))
+
+same_tile_delays = list()
+neighbour_tile_delays = list()
+
+type_delta_data = dict()
+
+with open("tmfuzz_%s.txt" % device, "r") as f:
+    for line in f:
+        line = line.split()
+
+        if line[0] == "dst":
+            dst_xy = (int(line[1]), int(line[2]))
+            dst_type = line[3]
+            dst_wire = line[4]
+
+        src_xy = (int(line[1]), int(line[2]))
+        src_type = line[3]
+        src_wire = line[4]
+
+        delay = int(line[5])
+        estdelay = int(line[6])
+
+        all_delay_data.append((delay, estdelay))
+
+        src_dst_pairs[src_type, dst_type] += 1
+
+        dx = dst_xy[0] - src_xy[0]
+        dy = dst_xy[1] - src_xy[1]
+
+        if src_type == sel_src_type and dst_type == sel_dst_type:
+            if dx == 0 and dy == 0:
+                same_tile_delays.append(delay)
+
+            elif abs(dx) <= 1 and abs(dy) <= 1:
+                neighbour_tile_delays.append(delay)
+
+            else:
+                delay_data.append((delay, estdelay, dx, dy, 0, 0, 0))
+
+                relx = 20 + dst_xy[0] - src_xy[0]
+                rely = 20 + dst_xy[1] - src_xy[1]
+
+                if (0 <= relx <= 40) and (0 <= rely <= 40):
+                    delay_map_sum[relx, rely] += delay
+                    delay_map_sum2[relx, rely] += delay*delay
+                    delay_map_count[relx, rely] += 1
+
+        if dst_type == sel_dst_type:
+            if src_type not in type_delta_data:
+                type_delta_data[src_type] = list()
+
+            type_delta_data[src_type].append((dx, dy, delay))
+
+delay_data = np.array(delay_data)
+all_delay_data = np.array(all_delay_data)
+max_delay = np.max(delay_data[:, 0:2])
+
+mean_same_tile_delays = np.mean(neighbour_tile_delays)
+mean_neighbour_tile_delays = np.mean(neighbour_tile_delays)
+
+print("Avg same tile delay: %.2f (%.2f std, N=%d)" % \
+        (mean_same_tile_delays, np.std(same_tile_delays), len(same_tile_delays)))
+print("Avg neighbour tile delay: %.2f (%.2f std, N=%d)" % \
+        (mean_neighbour_tile_delays, np.std(neighbour_tile_delays), len(neighbour_tile_delays)))
+
+#%% Apply simple low-weight bluring to fill gaps
+
+for i in range(0):
+    neigh_sum = np.zeros((41, 41))
+    neigh_sum2 = np.zeros((41, 41))
+    neigh_count = np.zeros((41, 41))
+
+    for x in range(41):
+        for y in range(41):
+            for p in range(-1, 2):
+                for q in range(-1, 2):
+                    if p == 0 and q == 0:
+                        continue
+                    if 0 <= (x+p) <= 40:
+                        if 0 <= (y+q) <= 40:
+                            neigh_sum[x, y] += delay_map_sum[x+p, y+q]
+                            neigh_sum2[x, y] += delay_map_sum2[x+p, y+q]
+                            neigh_count[x, y] += delay_map_count[x+p, y+q]
+
+    delay_map_sum += 0.1 * neigh_sum
+    delay_map_sum2 += 0.1 * neigh_sum2
+    delay_map_count += 0.1 * neigh_count
+
+delay_map = delay_map_sum / delay_map_count
+delay_map_std = np.sqrt(delay_map_count*delay_map_sum2 - delay_map_sum**2) / delay_map_count
+
+#%% Print src-dst-pair summary
+
+print("Src-Dst-Type pair summary:")
+for cnt, src, dst in sorted([(v, k[0], k[1]) for k, v in src_dst_pairs.items()]):
+    print("%20s %20s %5d%s" % (src, dst, cnt, " *" if src == sel_src_type and dst == sel_dst_type else ""))
+print()
+
+#%% Plot estimate vs actual delay
+
+plt.figure(figsize=(8, 3))
+plt.title("Estimate vs Actual Delay")
+plt.plot(all_delay_data[:, 0], all_delay_data[:, 1], ".")
+plt.plot(delay_data[:, 0], delay_data[:, 1], ".")
+plt.plot([0, max_delay], [0, max_delay], "k")
+plt.ylabel("Estimated Delay")
+plt.xlabel("Actual Delay")
+plt.grid()
+plt.show()
+
+#%% Plot delay heatmap and std dev heatmap
+
+plt.figure(figsize=(9, 3))
+plt.subplot(121)
+plt.title("Actual Delay Map")
+plt.imshow(delay_map)
+plt.colorbar()
+plt.subplot(122)
+plt.title("Standard Deviation")
+plt.imshow(delay_map_std)
+plt.colorbar()
+plt.show()
+
+#%% Generate Model #0
+
+def nonlinearPreprocessor0(dx, dy):
+    dx, dy = abs(dx), abs(dy)
+    values = [1.0]
+    values.append(dx + dy)
+    return np.array(values)
+
+A = np.zeros((41*41, len(nonlinearPreprocessor0(0, 0))))
+b = np.zeros(41*41)
+
+index = 0
+for x in range(41):
+    for y in range(41):
+        if delay_map_count[x, y] > 0:
+            A[index, :] = nonlinearPreprocessor0(x-20, y-20)
+            b[index] = delay_map[x, y]
+        index += 1
+
+model0_params, _, _, _ = np.linalg.lstsq(A, b)
+print("Model #0 parameters:", model0_params)
+
+model0_map = np.zeros((41, 41))
+for x in range(41):
+    for y in range(41):
+        v = np.dot(model0_params, nonlinearPreprocessor0(x-20, y-20))
+        model0_map[x, y] = v
+
+plt.figure(figsize=(9, 3))
+plt.subplot(121)
+plt.title("Model #0 Delay Map")
+plt.imshow(model0_map)
+plt.colorbar()
+plt.subplot(122)
+plt.title("Model #0 Error Map")
+plt.imshow(model0_map - delay_map)
+plt.colorbar()
+plt.show()
+
+for i in range(delay_data.shape[0]):
+    dx = delay_data[i, 2]
+    dy = delay_data[i, 3]
+    delay_data[i, 4] =  np.dot(model0_params, nonlinearPreprocessor0(dx, dy))
+
+plt.figure(figsize=(8, 3))
+plt.title("Model #0 vs Actual Delay")
+plt.plot(delay_data[:, 0], delay_data[:, 4], ".")
+plt.plot(delay_map.flat, model0_map.flat, ".")
+plt.plot([0, max_delay], [0, max_delay], "k")
+plt.ylabel("Model #0 Delay")
+plt.xlabel("Actual Delay")
+plt.grid()
+plt.show()
+
+print("In-sample RMS error: %f" % np.sqrt(np.nanmean((delay_map - model0_map)**2)))
+print("Out-of-sample RMS error: %f" % np.sqrt(np.nanmean((delay_data[:, 0] - delay_data[:, 4])**2)))
+print()
+
+#%% Generate Model #1
+
+def nonlinearPreprocessor1(dx, dy):
+    dx, dy = abs(dx), abs(dy)
+    values = [1.0]
+    values.append(dx + dy)                    # 1-norm
+    values.append((dx**2 + dy**2)**(1/2))     # 2-norm
+    values.append((dx**3 + dy**3)**(1/3))     # 3-norm
+    return np.array(values)
+
+A = np.zeros((41*41, len(nonlinearPreprocessor1(0, 0))))
+b = np.zeros(41*41)
+
+index = 0
+for x in range(41):
+    for y in range(41):
+        if delay_map_count[x, y] > 0:
+            A[index, :] = nonlinearPreprocessor1(x-20, y-20)
+            b[index] = delay_map[x, y]
+        index += 1
+
+model1_params, _, _, _ = np.linalg.lstsq(A, b)
+print("Model #1 parameters:", model1_params)
+
+model1_map = np.zeros((41, 41))
+for x in range(41):
+    for y in range(41):
+        v = np.dot(model1_params, nonlinearPreprocessor1(x-20, y-20))
+        model1_map[x, y] = v
+
+plt.figure(figsize=(9, 3))
+plt.subplot(121)
+plt.title("Model #1 Delay Map")
+plt.imshow(model1_map)
+plt.colorbar()
+plt.subplot(122)
+plt.title("Model #1 Error Map")
+plt.imshow(model1_map - delay_map)
+plt.colorbar()
+plt.show()
+
+for i in range(delay_data.shape[0]):
+    dx = delay_data[i, 2]
+    dy = delay_data[i, 3]
+    delay_data[i, 5] = np.dot(model1_params, nonlinearPreprocessor1(dx, dy))
+
+plt.figure(figsize=(8, 3))
+plt.title("Model #1 vs Actual Delay")
+plt.plot(delay_data[:, 0], delay_data[:, 5], ".")
+plt.plot(delay_map.flat, model1_map.flat, ".")
+plt.plot([0, max_delay], [0, max_delay], "k")
+plt.ylabel("Model #1 Delay")
+plt.xlabel("Actual Delay")
+plt.grid()
+plt.show()
+
+print("In-sample RMS error: %f" % np.sqrt(np.nanmean((delay_map - model1_map)**2)))
+print("Out-of-sample RMS error: %f" % np.sqrt(np.nanmean((delay_data[:, 0] - delay_data[:, 5])**2)))
+print()
+
+#%% Generate Model #2
+
+def nonlinearPreprocessor2(v):
+    return np.array([1, v, np.sqrt(v)])
+
+A = np.zeros((41*41, len(nonlinearPreprocessor2(0))))
+b = np.zeros(41*41)
+
+index = 0
+for x in range(41):
+    for y in range(41):
+        if delay_map_count[x, y] > 0:
+            A[index, :] = nonlinearPreprocessor2(model1_map[x, y])
+            b[index] = delay_map[x, y]
+        index += 1
+
+model2_params, _, _, _ = np.linalg.lstsq(A, b)
+print("Model #2 parameters:", model2_params)
+
+model2_map = np.zeros((41, 41))
+for x in range(41):
+    for y in range(41):
+        v = np.dot(model1_params, nonlinearPreprocessor1(x-20, y-20))
+        v = np.dot(model2_params, nonlinearPreprocessor2(v))
+        model2_map[x, y] = v
+
+plt.figure(figsize=(9, 3))
+plt.subplot(121)
+plt.title("Model #2 Delay Map")
+plt.imshow(model2_map)
+plt.colorbar()
+plt.subplot(122)
+plt.title("Model #2 Error Map")
+plt.imshow(model2_map - delay_map)
+plt.colorbar()
+plt.show()
+
+for i in range(delay_data.shape[0]):
+    dx = delay_data[i, 2]
+    dy = delay_data[i, 3]
+    delay_data[i, 6] = np.dot(model2_params, nonlinearPreprocessor2(delay_data[i, 5]))
+
+plt.figure(figsize=(8, 3))
+plt.title("Model #2 vs Actual Delay")
+plt.plot(delay_data[:, 0], delay_data[:, 6], ".")
+plt.plot(delay_map.flat, model2_map.flat, ".")
+plt.plot([0, max_delay], [0, max_delay], "k")
+plt.ylabel("Model #2 Delay")
+plt.xlabel("Actual Delay")
+plt.grid()
+plt.show()
+
+print("In-sample RMS error: %f" % np.sqrt(np.nanmean((delay_map - model2_map)**2)))
+print("Out-of-sample RMS error: %f" % np.sqrt(np.nanmean((delay_data[:, 0] - delay_data[:, 6])**2)))
+print()
+
+#%% Generate deltas for different source net types
+
+type_deltas = dict()
+
+print("Delay deltas for different src types:")
+for src_type in sorted(type_delta_data.keys()):
+    deltas = list()
+
+    for dx, dy, delay in type_delta_data[src_type]:
+        dx = abs(dx)
+        dy = abs(dy)
+
+        if dx > 1 or dy > 1:
+            est = model0_params[0] + model0_params[1] * (dx + dy)
+        else:
+            est = mean_neighbour_tile_delays
+        deltas.append(delay - est)
+
+    print("%15s: %8.2f (std %6.2f)" % (\
+            src_type, np.mean(deltas), np.std(deltas)))
+
+    type_deltas[src_type] = np.mean(deltas)
+
+#%% Print C defs of model parameters
+
+print("--snip--")
+print("%d, %d, %d," % (mean_neighbour_tile_delays, 128 * model0_params[0], 128 * model0_params[1]))
+print("%d, %d, %d, %d," % (128 * model1_params[0], 128 * model1_params[1], 128 * model1_params[2], 128 * model1_params[3]))
+print("%d, %d, %d," % (128 * model2_params[0], 128 * model2_params[1], 128 * model2_params[2]))
+print("%d, %d, %d, %d" % (type_deltas["LOCAL"], type_deltas["LUTFF_IN"], \
+                          (type_deltas["SP4_H"] + type_deltas["SP4_V"]) / 2,
+                          (type_deltas["SP12_H"] + type_deltas["SP12_V"]) / 2))
+print("--snap--")
diff --git a/xc7/xdl.cc b/xc7/xdl.cc
new file mode 100644
index 0000000..2687d73
--- /dev/null
+++ b/xc7/xdl.cc
@@ -0,0 +1,285 @@
+/*

+ *  nextpnr -- Next Generation Place and Route

+ *

+ *  Copyright (C) 2018  Clifford Wolf <clifford@symbioticeda.com>

+ *  Copyright (C) 2018  David Shah <david@symbioticeda.com>

+ *  Copyright (C) 2018  Serge Bazanski <q3k@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 "xdl.h"

+#include <cctype>

+#include <vector>

+#include "cells.h"

+#include "log.h"

+#include "nextpnr.h"

+#include "util.h"

+#include <boost/range/adaptor/reversed.hpp>

+

+#include "torc/Physical.hpp"

+using namespace torc::architecture::xilinx;

+using namespace torc::physical;

+

+NEXTPNR_NAMESPACE_BEGIN

+

+DesignSharedPtr create_torc_design(const Context *ctx)

+{

+    auto designPtr = Factory::newDesignPtr("name", torc_info->ddb->getDeviceName(), ctx->args.package, "-1", "");

+

+    std::unordered_map<int32_t, InstanceSharedPtr> site_to_instance;

+    std::vector<std::pair<std::string, std::string>> lut_inputs;

+    lut_inputs.reserve(6);

+

+    auto bel_to_lut = [](const BelId bel) {

+        switch (torc_info->bel_to_loc[bel.index].z) {

+        case 0:

+        case 4:

+            return "A";

+            break;

+        case 1:

+        case 5:

+            return "B";

+            break;

+        case 2:

+        case 6:

+            return "C";

+            break;

+        case 3:

+        case 7:

+            return "D";

+            break;

+        default:

+            throw;

+        }

+    };

+

+    for (const auto &cell : ctx->cells) {

+        const char *type;

+        if (cell.second->type == id_SLICE_LUT6)

+            type = "SLICEL";

+        else if (cell.second->type == id_IOB33 || cell.second->type == id_IOB18 || cell.second->type == id_BUFGCTRL || cell.second->type == id_PS7 || cell.second->type == id_MMCME2_ADV)

+            type = cell.second->type.c_str(ctx);

+        else

+            log_error("Unsupported cell type '%s'.\n", cell.second->type.c_str(ctx));

+

+        auto site_index = torc_info->bel_to_site_index[cell.second->bel.index];

+        auto ret = site_to_instance.emplace(site_index, nullptr);

+        InstanceSharedPtr instPtr;

+        if (ret.second) {

+            instPtr = Factory::newInstancePtr(cell.second->name.str(ctx), type, "", "");

+            auto b = designPtr->addInstance(instPtr);

+            assert(b);

+            ret.first->second = instPtr;

+

+            const auto &tile_info = torc_info->bel_to_tile_info(cell.second->bel.index);

+            instPtr->setTile(tile_info.getName());

+            instPtr->setSite(torc_info->bel_to_name(cell.second->bel.index));

+        } else

+            instPtr = ret.first->second;

+

+        if (cell.second->type == id_SLICE_LUT6) {

+            std::string setting, name, value;

+            const std::string lut = bel_to_lut(cell.second->bel);

+

+            setting = lut + "6LUT";

+            value = "#LUT:O6=";

+            lut_inputs.clear();

+            if (get_net_or_empty(cell.second.get(), id_I1))

+                lut_inputs.emplace_back("A1", "~A1");

+            if (get_net_or_empty(cell.second.get(), id_I2))

+                lut_inputs.emplace_back("A2", "~A2");

+            if (get_net_or_empty(cell.second.get(), id_I3))

+                lut_inputs.emplace_back("A3", "~A3");

+            if (get_net_or_empty(cell.second.get(), id_I4))

+                lut_inputs.emplace_back("A4", "~A4");

+            if (get_net_or_empty(cell.second.get(), id_I5))

+                lut_inputs.emplace_back("A5", "~A5");

+            if (get_net_or_empty(cell.second.get(), id_I6))

+                lut_inputs.emplace_back("A6", "~A6");

+            const auto &init = cell.second->params[ctx->id("INIT")];

+            // Assume from Yosys that INIT masks of less than 32 bits are output as uint32_t

+            if (lut_inputs.size() < 6) {

+                auto init_as_uint = boost::lexical_cast<uint32_t>(init);

+                NPNR_ASSERT(init_as_uint <= ((1ull << (1u << lut_inputs.size())) - 1));

+                if (lut_inputs.empty())

+                    value += init;

+                else {

+                    unsigned n = 0;

+                    for (unsigned o = 0; o < (1u << lut_inputs.size()); ++o) {

+                        if (!((init_as_uint >> o) & 1))

+                            continue;

+                        if (n++ > 0)

+                            value += "+";

+                        value += "(";

+                        value += (o & 1) ? lut_inputs[0].first : lut_inputs[0].second;

+                        for (unsigned i = 1; i < lut_inputs.size(); ++i) {

+                            value += "*";

+                            value += o & (1 << i) ? lut_inputs[i].first : lut_inputs[i].second;

+                        }

+                        value += ")";

+                    }

+                }

+            }

+            // Otherwise as a bit string

+            else {

+                NPNR_ASSERT(init.size() == (1u << lut_inputs.size()));

+                unsigned n = 0;

+                for (unsigned i = 0; i < init.size(); ++i) {

+                    if (init[init.size() - 1 - i] == '0')

+                        continue;

+                    if (n++ > 0)

+                        value += "+";

+                    value += "(";

+                    value += (i & 1) ? lut_inputs[0].first : lut_inputs[0].second;

+                    for (unsigned j = 1; j < lut_inputs.size(); ++j) {

+                        value += "*";

+                        value += i & (1 << j) ? lut_inputs[j].first : lut_inputs[j].second;

+                    }

+                    value += ")";

+                }

+            }

+

+            auto it = cell.second->params.find(ctx->id("LUT_NAME"));

+            if (it != cell.second->params.end())

+                name = it->second;

+            else

+                name = cell.second->name.str(ctx);

+            boost::replace_all(name, ":", "\\:");

+            instPtr->setConfig(setting, name, value);

+

+            auto O = get_net_or_empty(cell.second.get(), id_O);

+            if (O) {

+                setting = lut;

+                setting += "USED";

+                instPtr->setConfig(setting, "", "0");

+            }

+

+            auto OQ = get_net_or_empty(cell.second.get(), id_OQ);

+            if (OQ) {

+                setting = lut;

+                setting += "FF";

+                name = OQ->name.str(ctx);

+                boost::replace_all(name, ":", "\\:");

+                instPtr->setConfig(setting, name, "#FF");

+                instPtr->setConfig(setting + "MUX", "", "O6");

+                instPtr->setConfig(setting + "INIT", "", cell.second->params.at(ctx->id("FFINIT")));

+

+                if (cell.second->lcInfo.negClk)

+                    instPtr->setConfig("CLKINV", "", "CLK_B");

+                else

+                    instPtr->setConfig("CLKINV", "", "CLK");

+

+                if (get_net_or_empty(cell.second.get(), id_SR)) {

+                    instPtr->setConfig(setting + "SR", "", cell.second->params.at(id_SR));

+                    instPtr->setConfig("SRUSEDMUX", "", "IN");

+                }

+                instPtr->setConfig("SYNC_ATTR", "", cell.second->params.at(ctx->id("SYNC_ATTR")));

+                if (get_net_or_empty(cell.second.get(), ctx->id("CE")))

+                    instPtr->setConfig("CEUSEDMUX", "", "IN");

+

+            }

+        } else if (cell.second->type == id_IOB33) {

+            if (get_net_or_empty(cell.second.get(), id_I)) {

+                instPtr->setConfig("IUSED", "", "0");

+                instPtr->setConfig("IBUF_LOW_PWR", "", "TRUE");

+                instPtr->setConfig("ISTANDARD", "", "LVCMOS33");

+            } else {

+                instPtr->setConfig("OUSED", "", "0");

+                instPtr->setConfig("OSTANDARD", "", "LVCMOS33");

+                instPtr->setConfig("DRIVE", "", "12");

+                instPtr->setConfig("SLEW", "", "SLOW");

+            }

+        } else if (cell.second->type == id_IOB18) {

+            if (get_net_or_empty(cell.second.get(), id_I)) {

+                instPtr->setConfig("IUSED", "", "0");

+                instPtr->setConfig("IBUF_LOW_PWR", "", "TRUE");

+                instPtr->setConfig("ISTANDARD", "", "LVCMOS18");

+            } else {

+                instPtr->setConfig("OUSED", "", "0");

+                instPtr->setConfig("OSTANDARD", "", "LVCMOS18");

+                instPtr->setConfig("DRIVE", "", "12");

+                instPtr->setConfig("SLEW", "", "SLOW");

+            }

+        } else if (cell.second->type == id_BUFGCTRL || cell.second->type == id_PS7 || cell.second->type == id_MMCME2_ADV) {

+            for (const auto& i : cell.second->params)

+                instPtr->setConfig(i.first.str(ctx), "", i.second);

+        } else

+            log_error("Unsupported cell type '%s'.\n", cell.second->type.c_str(ctx));

+    }

+

+    for (const auto &net : ctx->nets) {

+        const auto &driver = net.second->driver;

+

+        auto site_index = torc_info->bel_to_site_index[driver.cell->bel.index];

+        auto instPtr = site_to_instance.at(site_index);

+

+        auto netPtr = Factory::newNetPtr(net.second->name.str(ctx));

+

+        auto pin_name = driver.port.str(ctx);

+        // For all LUT based inputs and outputs (I1-I6,O,OQ,OMUX) then change the I/O into the LUT

+        if (driver.cell->type == id_SLICE_LUT6 && (pin_name[0] == 'I' || pin_name[0] == 'O')) {

+            const auto lut = bel_to_lut(driver.cell->bel);

+            pin_name[0] = lut[0];

+        }

+        // e.g. Convert DDRARB[0] -> DDRARB0

+        pin_name.erase(std::remove_if(pin_name.begin(), pin_name.end(), boost::is_any_of("[]")), pin_name.end());

+        auto pinPtr = Factory::newInstancePinPtr(instPtr, pin_name);

+        netPtr->addSource(pinPtr);

+

+        if (!net.second->users.empty()) {

+            for (const auto &user : net.second->users) {

+                site_index = torc_info->bel_to_site_index[user.cell->bel.index];

+                instPtr = site_to_instance.at(site_index);

+

+                pin_name = user.port.str(ctx);

+                // For all LUT based inputs and outputs (I1-I6,O,OQ,OMUX) then change the I/O into the LUT

+                if (user.cell->type == id_SLICE_LUT6 && (pin_name[0] == 'I' || pin_name[0] == 'O')) {

+                    const auto lut = bel_to_lut(user.cell->bel);

+                    pin_name[0] = lut[0];

+                }

+                else {

+                    // e.g. Convert DDRARB[0] -> DDRARB0

+                    pin_name.erase(std::remove_if(pin_name.begin(), pin_name.end(), boost::is_any_of("[]")), pin_name.end());

+                }

+                pinPtr = Factory::newInstancePinPtr(instPtr, pin_name);

+                netPtr->addSink(pinPtr);

+            }

+

+            auto b = designPtr->addNet(netPtr);

+            assert(b);

+

+            for (const auto &i : net.second->wires) {

+                const auto &pip_map = i.second;

+                if (pip_map.pip == PipId())

+                    continue;

+                ExtendedWireInfo ewi_src(*torc_info->ddb, torc_info->pip_to_arc[pip_map.pip.index].getSourceTilewire());

+                ExtendedWireInfo ewi_dst(*torc_info->ddb, torc_info->pip_to_arc[pip_map.pip.index].getSinkTilewire());

+                auto p = Factory::newPip(ewi_src.mTileName, ewi_src.mWireName, ewi_dst.mWireName,

+                                         ePipUnidirectionalBuffered);

+                netPtr->addPip(p);

+            }

+        }

+    }

+

+    return designPtr;

+}

+

+void write_xdl(const Context *ctx, std::ostream &out)

+{

+    XdlExporter exporter(out);

+    auto designPtr = create_torc_design(ctx);

+    exporter(designPtr);

+}

+

+NEXTPNR_NAMESPACE_END

diff --git a/xc7/xdl.h b/xc7/xdl.h
new file mode 100644
index 0000000..19bc547
--- /dev/null
+++ b/xc7/xdl.h
@@ -0,0 +1,33 @@
+/*
+ *  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 XC7_BITSTREAM_H
+#define XC7_BITSTREAM_H
+
+#include <iostream>
+#include "nextpnr.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+void write_xdl(const Context *ctx, std::ostream &out);
+
+NEXTPNR_NAMESPACE_END
+
+#endif