Merge pull request #47 from YosysHQ/settings_propagate

Use settings for placer1 and router1
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4c222d7..0cf55eb 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -5,6 +5,13 @@
 option(BUILD_GUI "Build GUI" ON)
 option(BUILD_PYTHON "Build Python Integration" ON)
 option(BUILD_TESTS "Build GUI" OFF)
+option(STATIC_BUILD "Create static build" OFF)
+
+set(link_param "")
+if (STATIC_BUILD)
+    set(Boost_USE_STATIC_LIBS   ON)
+    set(link_param "-static")
+endif()
 
 # List of families to build
 set(FAMILIES generic ice40 ecp5)
@@ -211,7 +218,7 @@
         # Include family-specific source files to all family targets and set defines appropriately
         target_include_directories(${target} PRIVATE ${family}/ ${CMAKE_CURRENT_BINARY_DIR}/generated/)
         target_compile_definitions(${target} PRIVATE NEXTPNR_NAMESPACE=nextpnr_${family} ARCH_${ufamily} ARCHNAME=${family})
-        target_link_libraries(${target} LINK_PUBLIC ${Boost_LIBRARIES})
+        target_link_libraries(${target} LINK_PUBLIC ${Boost_LIBRARIES} ${link_param})
         if (NOT MSVC)
             target_link_libraries(${target} LINK_PUBLIC pthread)
         endif()
diff --git a/README.md b/README.md
index e9f197c..a47a06a 100644
--- a/README.md
+++ b/README.md
@@ -127,6 +127,13 @@
 make -j$(nproc)
 ```
 
+To make static build relase for iCE40 architecture use the following:
+
+```
+cmake -DARCH=ice40 -DBUILD_PYTHON=OFF -DBUILD_GUI=OFF -DSTATIC_BUILD=ON .
+make -j$(nproc)
+```
+
 Notes for developers
 --------------------
 
diff --git a/common/log.cc b/common/log.cc
index df5430b..e30449a 100644
--- a/common/log.cc
+++ b/common/log.cc
@@ -46,6 +46,18 @@
 // static bool next_print_log = false;
 static int log_newline_count = 0;
 
+std::string stringf(const char *fmt, ...)
+{
+    std::string string;
+    va_list ap;
+
+    va_start(ap, fmt);
+    string = vstringf(fmt, ap);
+    va_end(ap);
+
+    return string;
+}
+
 std::string vstringf(const char *fmt, va_list ap)
 {
     std::string string;
diff --git a/common/log.h b/common/log.h
index 74b9f0f..b5fddf5 100644
--- a/common/log.h
+++ b/common/log.h
@@ -51,6 +51,9 @@
 extern std::string log_last_error;
 extern void (*log_error_atexit)();
 
+std::string stringf(const char *fmt, ...);
+std::string vstringf(const char *fmt, va_list ap);
+
 extern std::ostream clog;
 void log(const char *format, ...) NPNR_ATTRIBUTE(format(printf, 1, 2));
 void log_always(const char *format, ...) NPNR_ATTRIBUTE(format(printf, 1, 2));
diff --git a/common/placer1.cc b/common/placer1.cc
index 4171e21..363b4d5 100644
--- a/common/placer1.cc
+++ b/common/placer1.cc
@@ -71,6 +71,19 @@
             fast_bels.at(type_idx).at(loc.x).at(loc.y).push_back(bel);
         }
         diameter = std::max(max_x, max_y) + 1;
+
+        costs.resize(ctx->nets.size());
+        old_udata.reserve(ctx->nets.size());
+        decltype(NetInfo::udata) n = 0;
+        for (auto &net : ctx->nets) {
+            old_udata.emplace_back(net.second->udata);
+            net.second->udata = n++;
+        }
+    }
+
+    ~SAPlacer() {
+        for (auto &net : ctx->nets)
+            net.second->udata = old_udata[net.second->udata];
     }
 
     bool place()
@@ -148,7 +161,7 @@
         curr_tns = 0;
         for (auto &net : ctx->nets) {
             wirelen_t wl = get_net_metric(ctx, net.second.get(), MetricType::COST, curr_tns);
-            metrics[net.first] = wl;
+            costs[net.second->udata] = CostChange{wl, -1};
             curr_metric += wl;
         }
 
@@ -249,7 +262,7 @@
             curr_tns = 0;
             for (auto &net : ctx->nets) {
                 wirelen_t wl = get_net_metric(ctx, net.second.get(), MetricType::COST, curr_tns);
-                metrics[net.first] = wl;
+                costs[net.second->udata] = CostChange{wl, -1};
                 curr_metric += wl;
             }
 
@@ -338,10 +351,8 @@
     // Attempt a SA position swap, return true on success or false on failure
     bool try_swap_position(CellInfo *cell, BelId newBel)
     {
-        static std::unordered_set<NetInfo *> update;
-        static std::vector<std::pair<IdString, wirelen_t>> new_lengths;
-        new_lengths.clear();
-        update.clear();
+        static std::vector<NetInfo*> updates;
+        updates.clear();
         BelId oldBel = cell->bel;
         CellInfo *other_cell = ctx->getBoundBelCell(newBel);
         if (other_cell != nullptr && other_cell->belStrength > STRENGTH_WEAK) {
@@ -357,14 +368,23 @@
             ctx->unbindBel(newBel);
         }
 
-        for (const auto &port : cell->ports)
-            if (port.second.net != nullptr)
-                update.insert(port.second.net);
+        for (const auto &port : cell->ports) {
+            if (port.second.net != nullptr) {
+                auto &cost = costs[port.second.net->udata];
+                if (cost.new_cost == 0) continue;
+                cost.new_cost = 0;
+                updates.emplace_back(port.second.net);
+            }
+        }
 
         if (other_cell != nullptr) {
             for (const auto &port : other_cell->ports)
-                if (port.second.net != nullptr)
-                    update.insert(port.second.net);
+                if (port.second.net != nullptr) {
+                    auto &cost = costs[port.second.net->udata];
+                    if (cost.new_cost == 0) continue;
+                    cost.new_cost = 0;
+                    updates.emplace_back(port.second.net);
+                }
         }
 
         ctx->bindBel(newBel, cell, STRENGTH_WEAK);
@@ -382,12 +402,13 @@
         new_metric = curr_metric;
 
         // Recalculate metrics for all nets touched by the peturbation
-        for (auto net : update) {
-            new_metric -= metrics.at(net->name);
+        for (const auto &net : updates) {
+            auto &c = costs[net->udata];
+            new_metric -= c.curr_cost;
             float temp_tns = 0;
             wirelen_t net_new_wl = get_net_metric(ctx, net, MetricType::COST, temp_tns);
             new_metric += net_new_wl;
-            new_lengths.push_back(std::make_pair(net->name, net_new_wl));
+            c.new_cost = net_new_wl;
         }
 
         new_dist = get_constraints_distance(ctx, cell);
@@ -406,8 +427,10 @@
             goto swap_fail;
         }
         curr_metric = new_metric;
-        for (auto new_wl : new_lengths)
-            metrics.at(new_wl.first) = new_wl.second;
+        for (const auto &net : updates) {
+            auto &c = costs[net->udata];
+            c = CostChange{c.new_cost, -1};
+        }
 
         return true;
     swap_fail:
@@ -415,6 +438,8 @@
         if (other_cell != nullptr) {
             ctx->bindBel(newBel, other_cell, STRENGTH_WEAK);
         }
+        for (const auto &net : updates)
+            costs[net->udata].new_cost = -1;
         return false;
     }
 
@@ -443,7 +468,6 @@
     }
 
     Context *ctx;
-    std::unordered_map<IdString, wirelen_t> metrics;
     wirelen_t curr_metric = std::numeric_limits<wirelen_t>::max();
     float curr_tns = 0;
     float temp = 1000;
@@ -458,6 +482,13 @@
     const float post_legalise_temp = 10;
     const float post_legalise_dia_scale = 1.5;
     Placer1Cfg cfg;
+
+    struct CostChange {
+        wirelen_t curr_cost;
+        wirelen_t new_cost;
+    };
+    std::vector<CostChange> costs;
+    std::vector<decltype(NetInfo::udata)> old_udata;
 };
 
 Placer1Cfg::Placer1Cfg(Context *ctx) : Settings(ctx) { constraintWeight = get<float>("placer1/constraintWeight", 10); }
diff --git a/docs/archapi.md b/docs/archapi.md
index 473cdd2..73443c1 100644
--- a/docs/archapi.md
+++ b/docs/archapi.md
@@ -151,6 +151,11 @@
 
 Return the type of a given bel.
 
+### const\_range\<std\:\:pair\<IdString, std::string\>\> getBelAttrs(BelId bel) const
+
+Return the attributes for that bel. Bel attributes are only informal. They are displayed by the GUI but are otherwise
+unused. An implementation may simply return an empty range.
+
 ### WireId getBelPinWire(BelId bel, IdString pin) const
 
 Return the wire connected to the given bel pin.
@@ -180,6 +185,11 @@
 isn't used by any of the core algorithms. Implementations may
 simply return `IdString()`.
 
+### const\_range\<std\:\:pair\<IdString, std::string\>\> getWireAttrs(WireId wire) const
+
+Return the attributes for that wire. Wire attributes are only informal. They are displayed by the GUI but are otherwise
+unused. An implementation may simply return an empty range.
+
 ### uint32\_t getWireChecksum(WireId wire) const
 
 Return a (preferably unique) number that represents this wire. This is used in design state checksum calculations.
@@ -242,6 +252,11 @@
 Get the type of a pip. Pip types are purely informal and
 implementations may simply return `IdString()`.
 
+### const\_range\<std\:\:pair\<IdString, std::string\>\> getPipAttrs(PipId pip) const
+
+Return the attributes for that pip. Pip attributes are only informal. They are displayed by the GUI but are otherwise
+unused. An implementation may simply return an empty range.
+
 ### Loc getPipLocation(PipId pip) const
 
 Get the X/Y/Z location of a given pip. Pip locations do not need to be unique, and in most cases they aren't. So
diff --git a/ecp5/arch.cc b/ecp5/arch.cc
index 047f551..acf1b42 100644
--- a/ecp5/arch.cc
+++ b/ecp5/arch.cc
@@ -367,7 +367,7 @@
 
 delay_t Arch::estimateDelay(WireId src, WireId dst) const
 {
-    return 200 * (abs(src.location.x - dst.location.x) + abs(src.location.y - dst.location.y));
+    return 50 * (abs(src.location.x - dst.location.x) + abs(src.location.y - dst.location.y));
 }
 
 delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const
@@ -376,7 +376,7 @@
     auto driver_loc = getBelLocation(driver.cell->bel);
     auto sink_loc = getBelLocation(sink.cell->bel);
 
-    return 200 * (abs(driver_loc.x - sink_loc.x) + abs(driver_loc.y - sink_loc.y));
+    return 50 * (abs(driver_loc.x - sink_loc.x) + abs(driver_loc.y - sink_loc.y));
 }
 
 bool Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const { return false; }
diff --git a/ecp5/arch.h b/ecp5/arch.h
index 3679262..f5336e5 100644
--- a/ecp5/arch.h
+++ b/ecp5/arch.h
@@ -522,6 +522,12 @@
         return id;
     }
 
+    std::vector<std::pair<IdString, std::string>> getBelAttrs(BelId) const
+    {
+        std::vector<std::pair<IdString, std::string>> ret;
+        return ret;
+    }
+
     WireId getBelPinWire(BelId bel, IdString pin) const;
 
     BelPinRange getWireBelPins(WireId wire) const
@@ -553,6 +559,12 @@
 
     IdString getWireType(WireId wire) const { return IdString(); }
 
+    std::vector<std::pair<IdString, std::string>> getWireAttrs(WireId) const
+    {
+        std::vector<std::pair<IdString, std::string>> ret;
+        return ret;
+    }
+
     uint32_t getWireChecksum(WireId wire) const { return wire.index; }
 
     void bindWire(WireId wire, NetInfo *net, PlaceStrength strength)
@@ -633,6 +645,12 @@
 
     IdString getPipType(PipId pip) const { return IdString(); }
 
+    std::vector<std::pair<IdString, std::string>> getPipAttrs(PipId) const
+    {
+        std::vector<std::pair<IdString, std::string>> ret;
+        return ret;
+    }
+
     uint32_t getPipChecksum(PipId pip) const { return pip.index; }
 
     void bindPip(PipId pip, NetInfo *net, PlaceStrength strength)
diff --git a/generic/arch.cc b/generic/arch.cc
index b37f53b..583c74d 100644
--- a/generic/arch.cc
+++ b/generic/arch.cc
@@ -184,6 +184,21 @@
     refreshUiGroup(group);
 }
 
+void Arch::setWireAttr(IdString wire, IdString key, const std::string &value)
+{
+    wires.at(wire).attrs[key] = value;
+}
+
+void Arch::setPipAttr(IdString pip, IdString key, const std::string &value)
+{
+    pips.at(pip).attrs[key] = value;
+}
+
+void Arch::setBelAttr(IdString bel, IdString key, const std::string &value)
+{
+    bels.at(bel).attrs[key] = value;
+}
+
 // ---------------------------------------------------------------
 
 Arch::Arch(ArchArgs args) : chipName("generic"), args(args) {}
@@ -251,6 +266,8 @@
 
 IdString Arch::getBelType(BelId bel) const { return bels.at(bel).type; }
 
+const std::map<IdString, std::string> &Arch::getBelAttrs(BelId bel) const { return bels.at(bel).attrs; }
+
 WireId Arch::getBelPinWire(BelId bel, IdString pin) const { return bels.at(bel).pins.at(pin).wire; }
 
 PortType Arch::getBelPinType(BelId bel, IdString pin) const { return bels.at(bel).pins.at(pin).type; }
@@ -276,6 +293,8 @@
 
 IdString Arch::getWireType(WireId wire) const { return wires.at(wire).type; }
 
+const std::map<IdString, std::string> &Arch::getWireAttrs(WireId wire) const { return wires.at(wire).attrs; }
+
 uint32_t Arch::getWireChecksum(WireId wire) const
 {
     // FIXME
@@ -328,6 +347,8 @@
 
 IdString Arch::getPipType(PipId pip) const { return pips.at(pip).type; }
 
+const std::map<IdString, std::string> &Arch::getPipAttrs(PipId pip) const { return pips.at(pip).attrs; }
+
 uint32_t Arch::getPipChecksum(PipId wire) const
 {
     // FIXME
diff --git a/generic/arch.h b/generic/arch.h
index 7549a75..22966e2 100644
--- a/generic/arch.h
+++ b/generic/arch.h
@@ -32,6 +32,7 @@
 struct PipInfo
 {
     IdString name, type;
+    std::map<IdString, std::string> attrs;
     NetInfo *bound_net;
     WireId srcWire, dstWire;
     DelayInfo delay;
@@ -42,6 +43,7 @@
 struct WireInfo
 {
     IdString name, type;
+    std::map<IdString, std::string> attrs;
     NetInfo *bound_net;
     std::vector<PipId> downhill, uphill, aliases;
     BelPin uphill_bel_pin;
@@ -61,6 +63,7 @@
 struct BelInfo
 {
     IdString name, type;
+    std::map<IdString, std::string> attrs;
     CellInfo *bound_cell;
     std::unordered_map<IdString, PinInfo> pins;
     DecalXY decalxy;
@@ -120,6 +123,10 @@
     void setBelDecal(BelId bel, DecalXY decalxy);
     void setGroupDecal(GroupId group, DecalXY decalxy);
 
+    void setWireAttr(IdString wire, IdString key, const std::string &value);
+    void setPipAttr(IdString pip, IdString key, const std::string &value);
+    void setBelAttr(IdString bel, IdString key, const std::string &value);
+
     // ---------------------------------------------------------------
     // Common Arch API. Every arch must provide the following methods.
 
@@ -151,6 +158,7 @@
     CellInfo *getConflictingBelCell(BelId bel) const;
     const std::vector<BelId> &getBels() const;
     IdString getBelType(BelId bel) const;
+    const std::map<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;
@@ -158,6 +166,7 @@
     WireId getWireByName(IdString name) const;
     IdString getWireName(WireId wire) const;
     IdString getWireType(WireId wire) const;
+    const std::map<IdString, std::string> &getWireAttrs(WireId wire) const;
     uint32_t getWireChecksum(WireId wire) const;
     void bindWire(WireId wire, NetInfo *net, PlaceStrength strength);
     void unbindWire(WireId wire);
@@ -171,6 +180,7 @@
     PipId getPipByName(IdString name) const;
     IdString getPipName(PipId pip) const;
     IdString getPipType(PipId pip) const;
+    const std::map<IdString, std::string> &getPipAttrs(PipId pip) const;
     uint32_t getPipChecksum(PipId pip) const;
     void bindPip(PipId pip, NetInfo *net, PlaceStrength strength);
     void unbindPip(PipId pip);
diff --git a/gui/designwidget.cc b/gui/designwidget.cc
index 896ef07..1b0e617 100644
--- a/gui/designwidget.cc
+++ b/gui/designwidget.cc
@@ -411,6 +411,11 @@
         addProperty(topItem, QVariant::String, "Conflicting Cell", ctx->nameOf(ctx->getConflictingBelCell(bel)),

                     ElementType::CELL);

 

+        QtProperty *attrsItem = addSubGroup(topItem, "Attributes");

+        for (auto &item : ctx->getBelAttrs(bel)) {

+            addProperty(attrsItem, QVariant::String, item.first.c_str(ctx), item.second.c_str());

+        }

+

         QtProperty *belpinsItem = addSubGroup(topItem, "Ports");

         for (const auto &item : ctx->getBelPins(bel)) {

             QtProperty *portInfoItem = addSubGroup(belpinsItem, item.c_str(ctx));

@@ -433,6 +438,11 @@
         addProperty(topItem, QVariant::String, "Conflicting Net", ctx->nameOf(ctx->getConflictingWireNet(wire)),

                     ElementType::NET);

 

+        QtProperty *attrsItem = addSubGroup(topItem, "Attributes");

+        for (auto &item : ctx->getWireAttrs(wire)) {

+            addProperty(attrsItem, QVariant::String, item.first.c_str(ctx), item.second.c_str());

+        }

+

         DelayInfo delay = ctx->getWireDelay(wire);

 

         QtProperty *delayItem = addSubGroup(topItem, "Delay");

@@ -492,6 +502,11 @@
         addProperty(topItem, QVariant::String, "Dest Wire", ctx->getWireName(ctx->getPipDstWire(pip)).c_str(ctx),

                     ElementType::WIRE);

 

+        QtProperty *attrsItem = addSubGroup(topItem, "Attributes");

+        for (auto &item : ctx->getPipAttrs(pip)) {

+            addProperty(attrsItem, QVariant::String, item.first.c_str(ctx), item.second.c_str());

+        }

+

         DelayInfo delay = ctx->getPipDelay(pip);

 

         QtProperty *delayItem = addSubGroup(topItem, "Delay");

diff --git a/ice40/arch.cc b/ice40/arch.cc
index 357b301..91dc5d6 100644
--- a/ice40/arch.cc
+++ b/ice40/arch.cc
@@ -226,6 +226,12 @@
     return PORT_INOUT;
 }
 
+std::vector<std::pair<IdString, std::string>> Arch::getBelAttrs(BelId) const
+{
+    std::vector<std::pair<IdString, std::string>> ret;
+    return ret;
+}
+
 WireId Arch::getBelPinWire(BelId bel, IdString pin) const
 {
     WireId ret;
@@ -331,6 +337,28 @@
     return IdString();
 }
 
+std::vector<std::pair<IdString, std::string>> Arch::getWireAttrs(WireId wire) const
+{
+    std::vector<std::pair<IdString, std::string>> ret;
+    auto &wi = chip_info->wire_data[wire.index];
+
+    ret.push_back(std::make_pair(id("INDEX"), stringf("%d", wi.netidx)));
+
+    ret.push_back(std::make_pair(id("GRID_X"), stringf("%d", wi.x)));
+    ret.push_back(std::make_pair(id("GRID_Y"), stringf("%d", wi.y)));
+    ret.push_back(std::make_pair(id("GRID_Z"), stringf("%d", wi.z)));
+
+#if 0
+    for (int i = 0; i < wi.num_segments; i++) {
+        auto &si = wi.segments[i];
+        ret.push_back(std::make_pair(id(stringf("segment[%d]", i)),
+                                     stringf("X%d/Y%d/%s", si.x, si.y, chip_info->tile_wire_names[si.index].get())));
+    }
+#endif
+
+    return ret;
+}
+
 // -----------------------------------------------------------------------
 
 PipId Arch::getPipByName(IdString name) const
@@ -372,6 +400,15 @@
 #endif
 }
 
+IdString Arch::getPipType(PipId pip) const { return IdString(); }
+
+std::vector<std::pair<IdString, std::string>> Arch::getPipAttrs(PipId) const
+{
+    std::vector<std::pair<IdString, std::string>> ret;
+    return ret;
+}
+
+
 // -----------------------------------------------------------------------
 
 BelId Arch::getPackagePinBel(const std::string &pin) const
@@ -718,7 +755,7 @@
             GraphicElement el;
             el.type = GraphicElement::TYPE_BOX;
             el.style = decal.active ? GraphicElement::STYLE_ACTIVE : GraphicElement::STYLE_INACTIVE;
-            el.x1 = chip_info->bel_data[bel.index].x + logic_cell_x1;
+            el.x1 = chip_info->bel_data[bel.index].x + lut_swbox_x1;
             el.x2 = chip_info->bel_data[bel.index].x + logic_cell_x2;
             el.y1 = chip_info->bel_data[bel.index].y + logic_cell_y1 +
                     (4 * chip_info->bel_data[bel.index].z) * logic_cell_pitch;
@@ -732,7 +769,7 @@
                 GraphicElement el;
                 el.type = GraphicElement::TYPE_BOX;
                 el.style = decal.active ? GraphicElement::STYLE_ACTIVE : GraphicElement::STYLE_INACTIVE;
-                el.x1 = chip_info->bel_data[bel.index].x + logic_cell_x1;
+                el.x1 = chip_info->bel_data[bel.index].x + lut_swbox_x1;
                 el.x2 = chip_info->bel_data[bel.index].x + logic_cell_x2;
                 el.y1 = chip_info->bel_data[bel.index].y + logic_cell_y1 + i;
                 el.y2 = chip_info->bel_data[bel.index].y + logic_cell_y2 + i + 7 * logic_cell_pitch;
diff --git a/ice40/arch.h b/ice40/arch.h
index 2f2a0f3..871b25f 100644
--- a/ice40/arch.h
+++ b/ice40/arch.h
@@ -108,6 +108,8 @@
     };
 
     RelPtr<char> name;
+    int32_t netidx;
+
     int32_t num_uphill, num_downhill;
     RelPtr<int32_t> pips_uphill, pips_downhill;
 
@@ -225,6 +227,7 @@
     RelPtr<BelConfigPOD> bel_config;
     RelPtr<PackageInfoPOD> packages_data;
     RelPtr<CellTimingPOD> cell_timing;
+    RelPtr<RelPtr<char>> tile_wire_names;
 });
 
 #if defined(_MSC_VER)
@@ -502,6 +505,8 @@
         return IdString(chip_info->bel_data[bel.index].type);
     }
 
+    std::vector<std::pair<IdString, std::string>> getBelAttrs(BelId bel) const;
+
     WireId getBelPinWire(BelId bel, IdString pin) const;
     PortType getBelPinType(BelId bel, IdString pin) const;
     std::vector<IdString> getBelPins(BelId bel) const;
@@ -517,6 +522,7 @@
     }
 
     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; }
 
@@ -692,7 +698,8 @@
 
     IdString getPipName(PipId pip) const;
 
-    IdString getPipType(PipId pip) const { return IdString(); }
+    IdString getPipType(PipId pip) const;
+    std::vector<std::pair<IdString, std::string>> getPipAttrs(PipId pip) const;
 
     uint32_t getPipChecksum(PipId pip) const { return pip.index; }
 
@@ -812,7 +819,7 @@
     bool isBelLocationValid(BelId bel) const;
 
     // Helper function for above
-    bool logicCellsCompatible(const std::vector<const CellInfo *> &cells) const;
+    bool logicCellsCompatible(const CellInfo** it, const size_t size) const;
 
     // -------------------------------------------------
     // Assign architecure-specific arguments to nets and cells, which must be
diff --git a/ice40/arch_place.cc b/ice40/arch_place.cc
index bbddb7e..c69fd34 100644
--- a/ice40/arch_place.cc
+++ b/ice40/arch_place.cc
@@ -23,15 +23,17 @@
 #include "nextpnr.h"
 #include "util.h"
 
+#include <boost/range/iterator_range.hpp>
+
 NEXTPNR_NAMESPACE_BEGIN
 
-bool Arch::logicCellsCompatible(const std::vector<const CellInfo *> &cells) const
+bool Arch::logicCellsCompatible(const CellInfo** it, const size_t size) const
 {
     bool dffs_exist = false, dffs_neg = false;
     const NetInfo *cen = nullptr, *clk = nullptr, *sr = nullptr;
     int locals_count = 0;
 
-    for (auto cell : cells) {
+    for (auto cell : boost::make_iterator_range(it, it+size)) {
         NPNR_ASSERT(cell->belType == id_ICESTORM_LC);
         if (cell->lcInfo.dffEnable) {
             if (!dffs_exist) {
@@ -71,15 +73,15 @@
 bool Arch::isBelLocationValid(BelId bel) const
 {
     if (getBelType(bel) == id_ICESTORM_LC) {
-        std::vector<const CellInfo *> bel_cells;
+        std::array<const CellInfo *, 8> 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.push_back(ci_other);
-            }
+            if (ci_other != nullptr)
+                bel_cells[num_cells++] = ci_other;
         }
-        return logicCellsCompatible(bel_cells);
+        return logicCellsCompatible(bel_cells.data(), num_cells);
     } else {
         CellInfo *ci = getBoundBelCell(bel);
         if (ci == nullptr)
@@ -94,17 +96,18 @@
     if (cell->type == id_ICESTORM_LC) {
         NPNR_ASSERT(getBelType(bel) == id_ICESTORM_LC);
 
-        std::vector<const CellInfo *> bel_cells;
+        std::array<const CellInfo *, 8> 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.push_back(ci_other);
-            }
+            if (ci_other != nullptr && bel_other != bel)
+                bel_cells[num_cells++] = ci_other;
         }
 
-        bel_cells.push_back(cell);
-        return logicCellsCompatible(bel_cells);
+        bel_cells[num_cells++] = cell;
+        return logicCellsCompatible(bel_cells.data(), num_cells);
     } else if (cell->type == id_SB_IO) {
         // Do not allow placement of input SB_IOs on blocks where there a PLL is outputting to.
 
diff --git a/ice40/bitstream.cc b/ice40/bitstream.cc
index ee276e4..4ea9101 100644
--- a/ice40/bitstream.cc
+++ b/ice40/bitstream.cc
@@ -870,10 +870,12 @@
             }

             if (isUsed) {

                 NetInfo *net = ctx->wire_to_net[pi.dst];

-                WireId wire;

-                wire.index = pi.dst;

-                ctx->unbindWire(wire);

-                ctx->bindPip(pip, net, STRENGTH_WEAK);

+                if (net!=nullptr) {

+                    WireId wire;

+                    wire.index = pi.dst;

+                    ctx->unbindWire(wire);

+                    ctx->bindPip(pip, net, STRENGTH_WEAK);

+                }

             }

         }

         for (auto bel : ctx->getBels()) {

diff --git a/ice40/chains.cc b/ice40/chains.cc
index 8638a96..bb20b60 100644
--- a/ice40/chains.cc
+++ b/ice40/chains.cc
@@ -97,7 +97,7 @@
             }
             tile.push_back(cell);
             chains.back().cells.push_back(cell);
-            bool split_chain = (!ctx->logicCellsCompatible(tile)) || (int(chains.back().cells.size()) > max_length);
+            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();
diff --git a/ice40/chipdb.py b/ice40/chipdb.py
index 7bdf82f..a28cba4 100644
--- a/ice40/chipdb.py
+++ b/ice40/chipdb.py
@@ -50,6 +50,7 @@
 wiretypes = dict()
 
 gfx_wire_ids = dict()
+gfx_wire_names = list()
 wire_segments = dict()
 
 fast_timings = None
@@ -93,6 +94,136 @@
             idx = len(gfx_wire_ids)
             name = line.strip().rstrip(",")
             gfx_wire_ids[name] = idx
+            gfx_wire_names.append(name)
+
+def gfx_wire_alias(old, new):
+    assert old in gfx_wire_ids
+    assert new not in gfx_wire_ids
+    gfx_wire_ids[new] = gfx_wire_ids[old]
+
+# GFX aliases for RAM tiles
+
+gfx_wire_alias("TILE_WIRE_LUTFF_0_IN_0", "TILE_WIRE_RAM_RADDR_0")
+gfx_wire_alias("TILE_WIRE_LUTFF_0_IN_1", "TILE_WIRE_RAM_RADDR_1")
+gfx_wire_alias("TILE_WIRE_LUTFF_0_IN_2", "TILE_WIRE_RAM_RADDR_2")
+gfx_wire_alias("TILE_WIRE_LUTFF_0_IN_3", "TILE_WIRE_RAM_RADDR_3")
+
+gfx_wire_alias("TILE_WIRE_LUTFF_1_IN_0", "TILE_WIRE_RAM_RADDR_4")
+gfx_wire_alias("TILE_WIRE_LUTFF_1_IN_1", "TILE_WIRE_RAM_RADDR_5")
+gfx_wire_alias("TILE_WIRE_LUTFF_1_IN_2", "TILE_WIRE_RAM_RADDR_6")
+gfx_wire_alias("TILE_WIRE_LUTFF_1_IN_3", "TILE_WIRE_RAM_RADDR_7")
+
+gfx_wire_alias("TILE_WIRE_LUTFF_2_IN_0", "TILE_WIRE_RAM_RADDR_8")
+gfx_wire_alias("TILE_WIRE_LUTFF_2_IN_1", "TILE_WIRE_RAM_RADDR_9")
+gfx_wire_alias("TILE_WIRE_LUTFF_2_IN_2", "TILE_WIRE_RAM_RADDR_10")
+
+gfx_wire_alias("TILE_WIRE_LUTFF_0_IN_0", "TILE_WIRE_RAM_WADDR_0")
+gfx_wire_alias("TILE_WIRE_LUTFF_0_IN_1", "TILE_WIRE_RAM_WADDR_1")
+gfx_wire_alias("TILE_WIRE_LUTFF_0_IN_2", "TILE_WIRE_RAM_WADDR_2")
+gfx_wire_alias("TILE_WIRE_LUTFF_0_IN_3", "TILE_WIRE_RAM_WADDR_3")
+
+gfx_wire_alias("TILE_WIRE_LUTFF_1_IN_0", "TILE_WIRE_RAM_WADDR_4")
+gfx_wire_alias("TILE_WIRE_LUTFF_1_IN_1", "TILE_WIRE_RAM_WADDR_5")
+gfx_wire_alias("TILE_WIRE_LUTFF_1_IN_2", "TILE_WIRE_RAM_WADDR_6")
+gfx_wire_alias("TILE_WIRE_LUTFF_1_IN_3", "TILE_WIRE_RAM_WADDR_7")
+
+gfx_wire_alias("TILE_WIRE_LUTFF_2_IN_0", "TILE_WIRE_RAM_WADDR_8")
+gfx_wire_alias("TILE_WIRE_LUTFF_2_IN_1", "TILE_WIRE_RAM_WADDR_9")
+gfx_wire_alias("TILE_WIRE_LUTFF_2_IN_2", "TILE_WIRE_RAM_WADDR_10")
+
+gfx_wire_alias("TILE_WIRE_LUTFF_3_IN_0", "TILE_WIRE_RAM_MASK_0")
+gfx_wire_alias("TILE_WIRE_LUTFF_3_IN_1", "TILE_WIRE_RAM_MASK_1")
+gfx_wire_alias("TILE_WIRE_LUTFF_3_IN_2", "TILE_WIRE_RAM_MASK_2")
+gfx_wire_alias("TILE_WIRE_LUTFF_3_IN_3", "TILE_WIRE_RAM_MASK_3")
+
+gfx_wire_alias("TILE_WIRE_LUTFF_4_IN_0", "TILE_WIRE_RAM_MASK_4")
+gfx_wire_alias("TILE_WIRE_LUTFF_4_IN_1", "TILE_WIRE_RAM_MASK_5")
+gfx_wire_alias("TILE_WIRE_LUTFF_4_IN_2", "TILE_WIRE_RAM_MASK_6")
+gfx_wire_alias("TILE_WIRE_LUTFF_4_IN_3", "TILE_WIRE_RAM_MASK_7")
+
+gfx_wire_alias("TILE_WIRE_LUTFF_3_IN_0", "TILE_WIRE_RAM_MASK_8")
+gfx_wire_alias("TILE_WIRE_LUTFF_3_IN_1", "TILE_WIRE_RAM_MASK_9")
+gfx_wire_alias("TILE_WIRE_LUTFF_3_IN_2", "TILE_WIRE_RAM_MASK_10")
+gfx_wire_alias("TILE_WIRE_LUTFF_3_IN_3", "TILE_WIRE_RAM_MASK_11")
+
+gfx_wire_alias("TILE_WIRE_LUTFF_4_IN_0", "TILE_WIRE_RAM_MASK_12")
+gfx_wire_alias("TILE_WIRE_LUTFF_4_IN_1", "TILE_WIRE_RAM_MASK_13")
+gfx_wire_alias("TILE_WIRE_LUTFF_4_IN_2", "TILE_WIRE_RAM_MASK_14")
+gfx_wire_alias("TILE_WIRE_LUTFF_4_IN_3", "TILE_WIRE_RAM_MASK_15")
+
+gfx_wire_alias("TILE_WIRE_LUTFF_5_IN_0", "TILE_WIRE_RAM_WDATA_0")
+gfx_wire_alias("TILE_WIRE_LUTFF_5_IN_1", "TILE_WIRE_RAM_WDATA_1")
+gfx_wire_alias("TILE_WIRE_LUTFF_5_IN_2", "TILE_WIRE_RAM_WDATA_2")
+gfx_wire_alias("TILE_WIRE_LUTFF_5_IN_3", "TILE_WIRE_RAM_WDATA_3")
+
+gfx_wire_alias("TILE_WIRE_LUTFF_6_IN_0", "TILE_WIRE_RAM_WDATA_4")
+gfx_wire_alias("TILE_WIRE_LUTFF_6_IN_1", "TILE_WIRE_RAM_WDATA_5")
+gfx_wire_alias("TILE_WIRE_LUTFF_6_IN_2", "TILE_WIRE_RAM_WDATA_6")
+gfx_wire_alias("TILE_WIRE_LUTFF_6_IN_3", "TILE_WIRE_RAM_WDATA_7")
+
+gfx_wire_alias("TILE_WIRE_LUTFF_5_IN_0", "TILE_WIRE_RAM_WDATA_8")
+gfx_wire_alias("TILE_WIRE_LUTFF_5_IN_1", "TILE_WIRE_RAM_WDATA_9")
+gfx_wire_alias("TILE_WIRE_LUTFF_5_IN_2", "TILE_WIRE_RAM_WDATA_10")
+gfx_wire_alias("TILE_WIRE_LUTFF_5_IN_3", "TILE_WIRE_RAM_WDATA_11")
+
+gfx_wire_alias("TILE_WIRE_LUTFF_6_IN_0", "TILE_WIRE_RAM_WDATA_12")
+gfx_wire_alias("TILE_WIRE_LUTFF_6_IN_1", "TILE_WIRE_RAM_WDATA_13")
+gfx_wire_alias("TILE_WIRE_LUTFF_6_IN_2", "TILE_WIRE_RAM_WDATA_14")
+gfx_wire_alias("TILE_WIRE_LUTFF_6_IN_3", "TILE_WIRE_RAM_WDATA_15")
+
+gfx_wire_alias("TILE_WIRE_LUTFF_0_OUT", "TILE_WIRE_RAM_RDATA_0")
+gfx_wire_alias("TILE_WIRE_LUTFF_1_OUT", "TILE_WIRE_RAM_RDATA_1")
+gfx_wire_alias("TILE_WIRE_LUTFF_2_OUT", "TILE_WIRE_RAM_RDATA_2")
+gfx_wire_alias("TILE_WIRE_LUTFF_3_OUT", "TILE_WIRE_RAM_RDATA_3")
+gfx_wire_alias("TILE_WIRE_LUTFF_4_OUT", "TILE_WIRE_RAM_RDATA_4")
+gfx_wire_alias("TILE_WIRE_LUTFF_5_OUT", "TILE_WIRE_RAM_RDATA_5")
+gfx_wire_alias("TILE_WIRE_LUTFF_6_OUT", "TILE_WIRE_RAM_RDATA_6")
+gfx_wire_alias("TILE_WIRE_LUTFF_7_OUT", "TILE_WIRE_RAM_RDATA_7")
+
+gfx_wire_alias("TILE_WIRE_LUTFF_0_OUT", "TILE_WIRE_RAM_RDATA_8")
+gfx_wire_alias("TILE_WIRE_LUTFF_1_OUT", "TILE_WIRE_RAM_RDATA_9")
+gfx_wire_alias("TILE_WIRE_LUTFF_2_OUT", "TILE_WIRE_RAM_RDATA_10")
+gfx_wire_alias("TILE_WIRE_LUTFF_3_OUT", "TILE_WIRE_RAM_RDATA_11")
+gfx_wire_alias("TILE_WIRE_LUTFF_4_OUT", "TILE_WIRE_RAM_RDATA_12")
+gfx_wire_alias("TILE_WIRE_LUTFF_5_OUT", "TILE_WIRE_RAM_RDATA_13")
+gfx_wire_alias("TILE_WIRE_LUTFF_6_OUT", "TILE_WIRE_RAM_RDATA_14")
+gfx_wire_alias("TILE_WIRE_LUTFF_7_OUT", "TILE_WIRE_RAM_RDATA_15")
+
+gfx_wire_alias("TILE_WIRE_FUNC_GLOBAL_CEN", "TILE_WIRE_RAM_RCLKE")
+gfx_wire_alias("TILE_WIRE_FUNC_GLOBAL_CEN", "TILE_WIRE_RAM_WCLKE")
+gfx_wire_alias("TILE_WIRE_FUNC_GLOBAL_CLK", "TILE_WIRE_RAM_RCLK")
+gfx_wire_alias("TILE_WIRE_FUNC_GLOBAL_CLK", "TILE_WIRE_RAM_WCLK")
+gfx_wire_alias("TILE_WIRE_FUNC_GLOBAL_S_R", "TILE_WIRE_RAM_RE")
+gfx_wire_alias("TILE_WIRE_FUNC_GLOBAL_S_R", "TILE_WIRE_RAM_WE")
+
+# GFX aliases for IO tiles
+
+gfx_wire_alias("TILE_WIRE_LUTFF_0_IN_0", "TILE_WIRE_IO_0_D_OUT_0")
+gfx_wire_alias("TILE_WIRE_LUTFF_0_IN_1", "TILE_WIRE_IO_0_D_OUT_1")
+gfx_wire_alias("TILE_WIRE_LUTFF_0_IN_3", "TILE_WIRE_IO_0_OUT_ENB")
+
+gfx_wire_alias("TILE_WIRE_LUTFF_0_OUT", "TILE_WIRE_IO_0_D_IN_0")
+gfx_wire_alias("TILE_WIRE_LUTFF_1_OUT", "TILE_WIRE_IO_0_D_IN_1")
+
+gfx_wire_alias("TILE_WIRE_LUTFF_4_IN_0", "TILE_WIRE_IO_1_D_OUT_0")
+gfx_wire_alias("TILE_WIRE_LUTFF_4_IN_1", "TILE_WIRE_IO_1_D_OUT_1")
+gfx_wire_alias("TILE_WIRE_LUTFF_4_IN_3", "TILE_WIRE_IO_1_OUT_ENB")
+
+gfx_wire_alias("TILE_WIRE_LUTFF_4_OUT", "TILE_WIRE_IO_1_D_IN_0")
+gfx_wire_alias("TILE_WIRE_LUTFF_5_OUT", "TILE_WIRE_IO_1_D_IN_1")
+
+gfx_wire_alias("TILE_WIRE_FUNC_GLOBAL_CEN", "TILE_WIRE_IO_GLOBAL_CEN")
+gfx_wire_alias("TILE_WIRE_FUNC_GLOBAL_CLK", "TILE_WIRE_IO_GLOBAL_INCLK")
+gfx_wire_alias("TILE_WIRE_FUNC_GLOBAL_S_R", "TILE_WIRE_IO_GLOBAL_OUTCLK")
+
+gfx_wire_alias("TILE_WIRE_FUNC_GLOBAL_G0", "TILE_WIRE_IO_GLOBAL_LATCH")
+
+for neigh in "BNL BNR BOT LFT RGT TNL TNR TOP".split():
+    for i in range(8):
+        gfx_wire_alias("TILE_WIRE_NEIGH_OP_%s_%d" % (neigh, i), "TILE_WIRE_LOGIC_OP_%s_%d" % (neigh, i))
+
+# End of GFX aliases
+
 
 def read_timings(filename):
     db = dict()
@@ -165,6 +296,18 @@
         return name[2] in ("sp12_v_b_0", "sp12_v_b_1")
     return False
 
+def norm_wire_xy(x, y, name):
+    if name.startswith("glb_netwk_"):
+        return None
+    if name.startswith("neigh_op_"):
+        return None
+    if name.startswith("logic_op_"):
+        return None
+    if name.startswith("io_global/latch"):
+        return None
+    return None # FIXME
+    return (x, y)
+
 def cmp_wire_names(newname, oldname):
     if maj_wire_name(newname):
         return True
@@ -508,11 +651,13 @@
                 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])))
+            wire_xy[mode[1]].append(wname)
             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]
+                if (wname[0], wname[1]) not in wire_segments[mode[1]]:
+                    wire_segments[mode[1]][(wname[0], wname[1])] = list()
+                wire_segments[mode[1]][(wname[0], wname[1])].append(wname[2])
             continue
 
         if mode[0] in ("buffer", "routing"):
@@ -561,7 +706,9 @@
     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]
+        if (wname[0], wname[1]) not in wire_segments[wire_idx]:
+            wire_segments[wire_idx][(wname[0], wname[1])] = list()
+        wire_segments[wire_idx][(wname[0], wname[1])].append(wname[2])
     return wire_idx
 
 def add_switch(x, y, bel=-1):
@@ -932,6 +1079,10 @@
 bba.push("chipdb_blob_%s" % dev_name)
 bba.r("chip_info_%s" % dev_name, "chip_info")
 
+bba.l("tile_wire_names")
+for name in gfx_wire_names:
+    bba.s(name, name)
+
 for bel in range(len(bel_name)):
     bba.l("bel_wires_%d" % bel, "BelWirePOD")
     for data in sorted(bel_wires[bel]):
@@ -1028,20 +1179,32 @@
     info["num_bel_pins"] = num_bel_pins
     info["list_bel_pins"] = ("wire%d_bels" % wire) if num_bel_pins > 0 else None
 
+    pos_xy = None
+    first = None
+
     if wire in wire_xy:
-        avg_x, avg_y = 0, 0
+        for x, y, n in wire_xy[wire]:
+            norm_xy = norm_wire_xy(x, y, n)
+            if norm_xy is None:
+                continue
+            if pos_xy is None:
+                pos_xy = norm_xy
+                first = (x, y, n)
+            elif pos_xy != norm_xy:
+                print("Conflicting positions for wire %s: (%d, %d, %s) -> (%d, %d), (%d, %d, %s) -> (%d, %d)" % \
+                        ((info["name"],) + first + pos_xy + (x, y, n) + norm_xy), file=sys.stderr)
+                assert 0
+        if (pos_xy is None) and (len(wire_xy[wire]) > 1):
+                # print("Only 'None' positions for wire %s." % info["name"], file=sys.stderr)
+                # assert 0
+                pass
 
-        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:
+    if pos_xy is None:
         info["x"] = wire_names_r[wire][0]
         info["y"] = wire_names_r[wire][1]
+    else:
+        info["x"] = pos_xy[0]
+        info["y"] = pos_xy[1]
 
     wireinfo.append(info)
 
@@ -1102,14 +1265,21 @@
 bba.l("wire_data_%s" % dev_name, "WireInfoPOD")
 for wire, info in enumerate(wireinfo):
     bba.s(info["name"], "name")
+    bba.u32(wire, "netidx")
+
     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]):
+
+    num_segments = 0
+    for segs in wire_segments[wire].values():
+        num_segments += len(segs)
+    bba.u32(num_segments, "num_segments")
+
+    if num_segments:
         bba.r("wire_segments_%d" % wire, "segments")
     else:
         bba.u32(0, "segments")
@@ -1125,24 +1295,25 @@
 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")
+        for xy, segs in sorted(wire_segments[wire].items()):
+            for seg in segs:
+                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_segname = wire_segments[info["src"]][(info["x"], info["y"])][0]
         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_segname = wire_segments[info["dst"]][(info["x"], info["y"])][0]
         dst_seg = gfx_wire_ids["TILE_WIRE_" + dst_segname.upper().replace("/", "_")]
         dst_segname = dst_segname.replace("/", ".")
 
@@ -1276,5 +1447,6 @@
 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.r("tile_wire_names", "tile_wire_names")
 
 bba.pop()
diff --git a/ice40/family.cmake b/ice40/family.cmake
index f558a14..0e1d36e 100644
--- a/ice40/family.cmake
+++ b/ice40/family.cmake
@@ -36,7 +36,7 @@
         set(DEV_GFXH ${CMAKE_CURRENT_SOURCE_DIR}/ice40/gfx.h)
         add_custom_command(OUTPUT ${DEV_CC_BBA_DB}
                 COMMAND ${PYTHON_EXECUTABLE} ${DB_PY} -p ${DEV_CONSTIDS_INC} -g ${DEV_GFXH} ${OPT_FAST} ${OPT_SLOW} ${DEV_TXT_DB} > ${DEV_CC_BBA_DB}
-                DEPENDS ${DEV_TXT_DB} ${DB_PY}
+                DEPENDS ${DEV_CONSTIDS_INC} ${DEV_GFXH} ${DEV_TXT_DB} ${DB_PY}
                 )
         add_custom_command(OUTPUT ${DEV_CC_DB}
                 COMMAND bbasm ${DEV_CC_BBA_DB} ${DEV_CC_DB}
@@ -69,7 +69,7 @@
         add_custom_command(OUTPUT ${DEV_CC_BBA_DB}
                 COMMAND ${PYTHON_EXECUTABLE} ${DB_PY} -p ${DEV_CONSTIDS_INC} -g ${DEV_GFXH} ${OPT_FAST} ${OPT_SLOW} ${DEV_TXT_DB} > ${DEV_CC_BBA_DB}.new
                 COMMAND mv ${DEV_CC_BBA_DB}.new ${DEV_CC_BBA_DB}
-                DEPENDS ${DEV_TXT_DB} ${DB_PY}
+                DEPENDS ${DEV_CONSTIDS_INC} ${DEV_GFXH} ${DEV_TXT_DB} ${DB_PY}
         )
         add_custom_command(OUTPUT ${DEV_CC_DB}
                 COMMAND bbasm --c ${DEV_CC_BBA_DB} ${DEV_CC_DB}.new
diff --git a/ice40/gfx.cc b/ice40/gfx.cc
index 1ab2fb3..79350ad 100644
--- a/ice40/gfx.cc
+++ b/ice40/gfx.cc
@@ -339,6 +339,78 @@
         g.push_back(el);
     }
 
+    // IO Span-4 Wires connecting to fabric
+
+    if (id >= TILE_WIRE_SPAN4_HORZ_0 && id <= TILE_WIRE_SPAN4_HORZ_47) {
+        int idx = id - TILE_WIRE_SPAN4_HORZ_0;
+        float y1 = y + 1.0 - (0.03 + 0.0025 * (48 - (idx ^ 1)));
+
+        el.x1 = x;
+        el.x2 = x + 1.0;
+        el.y1 = y1;
+        el.y2 = y1;
+        g.push_back(el);
+
+        el.x1 = x + main_swbox_x1 + 0.0025 * ((idx ^ 1) + 35);
+        el.x2 = el.x1;
+        el.y1 = y1;
+        el.y2 = y + main_swbox_y2;
+        g.push_back(el);
+    }
+
+    if (id >= TILE_WIRE_SPAN4_VERT_0 && id <= TILE_WIRE_SPAN4_VERT_47) {
+        int idx = id - TILE_WIRE_SPAN4_VERT_0;
+        float x1 = x + 0.03 + 0.0025 * (48 - (idx ^ 1));
+
+        el.x1 = x1;
+        el.x2 = x1;
+        el.y1 = y;
+        el.y2 = y + 1.0;
+        g.push_back(el);
+
+        el.y1 = y + 1.0 - (0.03 + 0.0025 * (270 - (idx ^ 1)));
+        el.y2 = el.y1;
+        el.x1 = x1;
+        el.x2 = x + main_swbox_x1;
+        g.push_back(el);
+    }
+
+    // IO Span-12 Wires connecting to fabric
+
+    if (id >= TILE_WIRE_SPAN12_HORZ_0 && id <= TILE_WIRE_SPAN12_HORZ_23) {
+        int idx = id - TILE_WIRE_SPAN12_HORZ_0;
+        float y1 = y + 1.0 - (0.03 + 0.0025 * (88 - (idx ^ 1)));
+
+        el.x1 = x;
+        el.x2 = x + 1.0;
+        el.y1 = y1;
+        el.y2 = y1;
+        g.push_back(el);
+
+        el.x1 = x + main_swbox_x1 + 0.0025 * ((idx ^ 1) + 5);
+        el.x2 = el.x1;
+        el.y1 = y1;
+        el.y2 = y + main_swbox_y2;
+        g.push_back(el);
+    }
+
+    if (id >= TILE_WIRE_SPAN12_VERT_0 && id <= TILE_WIRE_SPAN12_VERT_23) {
+        int idx = id - TILE_WIRE_SPAN12_VERT_0;
+        float x1 = x + 0.03 + 0.0025 * (88 - (idx ^ 1));
+
+        el.x1 = x1;
+        el.x2 = x1;
+        el.y1 = y;
+        el.y2 = y + 1.0;
+        g.push_back(el);
+
+        el.y1 = y + 1.0 - (0.03 + 0.0025 * (300 - (idx ^ 1)));
+        el.y2 = el.y1;
+        el.x1 = x1;
+        el.x2 = x + main_swbox_x1;
+        g.push_back(el);
+    }
+
     // Global2Local
 
     if (id >= TILE_WIRE_GLB2LOCAL_0 && id <= TILE_WIRE_GLB2LOCAL_3) {
@@ -457,6 +529,42 @@
         }
     }
 
+    // LC Control for IO and BRAM
+
+    if (id >= TILE_WIRE_FUNC_GLOBAL_CEN && id <= TILE_WIRE_FUNC_GLOBAL_S_R) {
+        int idx = id - TILE_WIRE_FUNC_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);
+    }
+
+    if (id == TILE_WIRE_FABOUT) {
+        el.y1 = y + main_swbox_y1;
+        el.y2 = el.y1 - 0.005 * 4;
+        el.x1 = x + main_swbox_x2 - 0.005 * 9;
+        el.x2 = el.x1;
+        g.push_back(el);
+    }
+
+    if (id == TILE_WIRE_FUNC_GLOBAL_G0) {
+        el.y1 = y + logic_cell_y1;
+        el.y2 = el.y1 - 0.005 * 4;
+        el.x1 = x + logic_cell_x2 - 0.005 * 3;
+        el.x2 = el.x1;
+        g.push_back(el);
+    }
+
     // LC Cascade
 
     if (id >= TILE_WIRE_LUTFF_0_LOUT && id <= TILE_WIRE_LUTFF_6_LOUT) {
@@ -571,6 +679,38 @@
         return true;
     }
 
+    // IO Span-4 Wires connecting to fabric
+
+    if (id >= TILE_WIRE_SPAN4_HORZ_0 && id <= TILE_WIRE_SPAN4_HORZ_47) {
+        int idx = id - TILE_WIRE_SPAN4_HORZ_0;
+        x = main_swbox_x1 + 0.0025 * ((idx ^ 1) + 35);
+        y = main_swbox_y2;
+        return true;
+    }
+
+    if (id >= TILE_WIRE_SPAN4_VERT_0 && id <= TILE_WIRE_SPAN4_VERT_47) {
+        int idx = id - TILE_WIRE_SPAN4_VERT_0;
+        y = 1.0 - (0.03 + 0.0025 * (270 - (idx ^ 1)));
+        x = main_swbox_x1;
+        return true;
+    }
+
+    // IO Span-12 Wires connecting to fabric
+
+    if (id >= TILE_WIRE_SPAN12_HORZ_0 && id <= TILE_WIRE_SPAN12_HORZ_23) {
+        int idx = id - TILE_WIRE_SPAN12_HORZ_0;
+        x = main_swbox_x1 + 0.0025 * ((idx ^ 1) + 5);
+        y = main_swbox_y2;
+        return true;
+    }
+
+    if (id >= TILE_WIRE_SPAN12_VERT_0 && id <= TILE_WIRE_SPAN12_VERT_23) {
+        int idx = id - TILE_WIRE_SPAN12_VERT_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) {
@@ -626,6 +766,19 @@
         return true;
     }
 
+    if (id >= TILE_WIRE_FUNC_GLOBAL_CEN && id <= TILE_WIRE_FUNC_GLOBAL_S_R) {
+        int idx = id - TILE_WIRE_FUNC_GLOBAL_CEN;
+        x = main_swbox_x2 - 0.005 * (idx + 5);
+        y = main_swbox_y1;
+        return true;
+    }
+
+    if (id == TILE_WIRE_FABOUT) {
+        x = main_swbox_x2 - 0.005 * 9;
+        y = main_swbox_y1;
+        return true;
+    }
+
     return false;
 }
 
diff --git a/ice40/gfx.h b/ice40/gfx.h
index 8ee7b0b..5401a41 100644
--- a/ice40/gfx.h
+++ b/ice40/gfx.h
@@ -205,6 +205,13 @@
     TILE_WIRE_LUTFF_GLOBAL_CLK,
     TILE_WIRE_LUTFF_GLOBAL_S_R,
 
+    TILE_WIRE_FUNC_GLOBAL_CEN,
+    TILE_WIRE_FUNC_GLOBAL_CLK,
+    TILE_WIRE_FUNC_GLOBAL_S_R,
+
+    TILE_WIRE_FUNC_GLOBAL_G0,
+    TILE_WIRE_FABOUT,
+
     TILE_WIRE_CARRY_IN,
     TILE_WIRE_CARRY_IN_MUX,
 
@@ -509,6 +516,154 @@
     TILE_WIRE_SP12_H_L_22,
     TILE_WIRE_SP12_H_L_23,
 
+    TILE_WIRE_SPAN4_VERT_0,
+    TILE_WIRE_SPAN4_VERT_1,
+    TILE_WIRE_SPAN4_VERT_2,
+    TILE_WIRE_SPAN4_VERT_3,
+    TILE_WIRE_SPAN4_VERT_4,
+    TILE_WIRE_SPAN4_VERT_5,
+    TILE_WIRE_SPAN4_VERT_6,
+    TILE_WIRE_SPAN4_VERT_7,
+    TILE_WIRE_SPAN4_VERT_8,
+    TILE_WIRE_SPAN4_VERT_9,
+    TILE_WIRE_SPAN4_VERT_10,
+    TILE_WIRE_SPAN4_VERT_11,
+    TILE_WIRE_SPAN4_VERT_12,
+    TILE_WIRE_SPAN4_VERT_13,
+    TILE_WIRE_SPAN4_VERT_14,
+    TILE_WIRE_SPAN4_VERT_15,
+    TILE_WIRE_SPAN4_VERT_16,
+    TILE_WIRE_SPAN4_VERT_17,
+    TILE_WIRE_SPAN4_VERT_18,
+    TILE_WIRE_SPAN4_VERT_19,
+    TILE_WIRE_SPAN4_VERT_20,
+    TILE_WIRE_SPAN4_VERT_21,
+    TILE_WIRE_SPAN4_VERT_22,
+    TILE_WIRE_SPAN4_VERT_23,
+    TILE_WIRE_SPAN4_VERT_24,
+    TILE_WIRE_SPAN4_VERT_25,
+    TILE_WIRE_SPAN4_VERT_26,
+    TILE_WIRE_SPAN4_VERT_27,
+    TILE_WIRE_SPAN4_VERT_28,
+    TILE_WIRE_SPAN4_VERT_29,
+    TILE_WIRE_SPAN4_VERT_30,
+    TILE_WIRE_SPAN4_VERT_31,
+    TILE_WIRE_SPAN4_VERT_32,
+    TILE_WIRE_SPAN4_VERT_33,
+    TILE_WIRE_SPAN4_VERT_34,
+    TILE_WIRE_SPAN4_VERT_35,
+    TILE_WIRE_SPAN4_VERT_36,
+    TILE_WIRE_SPAN4_VERT_37,
+    TILE_WIRE_SPAN4_VERT_38,
+    TILE_WIRE_SPAN4_VERT_39,
+    TILE_WIRE_SPAN4_VERT_40,
+    TILE_WIRE_SPAN4_VERT_41,
+    TILE_WIRE_SPAN4_VERT_42,
+    TILE_WIRE_SPAN4_VERT_43,
+    TILE_WIRE_SPAN4_VERT_44,
+    TILE_WIRE_SPAN4_VERT_45,
+    TILE_WIRE_SPAN4_VERT_46,
+    TILE_WIRE_SPAN4_VERT_47,
+
+    TILE_WIRE_SPAN4_HORZ_0,
+    TILE_WIRE_SPAN4_HORZ_1,
+    TILE_WIRE_SPAN4_HORZ_2,
+    TILE_WIRE_SPAN4_HORZ_3,
+    TILE_WIRE_SPAN4_HORZ_4,
+    TILE_WIRE_SPAN4_HORZ_5,
+    TILE_WIRE_SPAN4_HORZ_6,
+    TILE_WIRE_SPAN4_HORZ_7,
+    TILE_WIRE_SPAN4_HORZ_8,
+    TILE_WIRE_SPAN4_HORZ_9,
+    TILE_WIRE_SPAN4_HORZ_10,
+    TILE_WIRE_SPAN4_HORZ_11,
+    TILE_WIRE_SPAN4_HORZ_12,
+    TILE_WIRE_SPAN4_HORZ_13,
+    TILE_WIRE_SPAN4_HORZ_14,
+    TILE_WIRE_SPAN4_HORZ_15,
+    TILE_WIRE_SPAN4_HORZ_16,
+    TILE_WIRE_SPAN4_HORZ_17,
+    TILE_WIRE_SPAN4_HORZ_18,
+    TILE_WIRE_SPAN4_HORZ_19,
+    TILE_WIRE_SPAN4_HORZ_20,
+    TILE_WIRE_SPAN4_HORZ_21,
+    TILE_WIRE_SPAN4_HORZ_22,
+    TILE_WIRE_SPAN4_HORZ_23,
+    TILE_WIRE_SPAN4_HORZ_24,
+    TILE_WIRE_SPAN4_HORZ_25,
+    TILE_WIRE_SPAN4_HORZ_26,
+    TILE_WIRE_SPAN4_HORZ_27,
+    TILE_WIRE_SPAN4_HORZ_28,
+    TILE_WIRE_SPAN4_HORZ_29,
+    TILE_WIRE_SPAN4_HORZ_30,
+    TILE_WIRE_SPAN4_HORZ_31,
+    TILE_WIRE_SPAN4_HORZ_32,
+    TILE_WIRE_SPAN4_HORZ_33,
+    TILE_WIRE_SPAN4_HORZ_34,
+    TILE_WIRE_SPAN4_HORZ_35,
+    TILE_WIRE_SPAN4_HORZ_36,
+    TILE_WIRE_SPAN4_HORZ_37,
+    TILE_WIRE_SPAN4_HORZ_38,
+    TILE_WIRE_SPAN4_HORZ_39,
+    TILE_WIRE_SPAN4_HORZ_40,
+    TILE_WIRE_SPAN4_HORZ_41,
+    TILE_WIRE_SPAN4_HORZ_42,
+    TILE_WIRE_SPAN4_HORZ_43,
+    TILE_WIRE_SPAN4_HORZ_44,
+    TILE_WIRE_SPAN4_HORZ_45,
+    TILE_WIRE_SPAN4_HORZ_46,
+    TILE_WIRE_SPAN4_HORZ_47,
+
+    TILE_WIRE_SPAN12_VERT_0,
+    TILE_WIRE_SPAN12_VERT_1,
+    TILE_WIRE_SPAN12_VERT_2,
+    TILE_WIRE_SPAN12_VERT_3,
+    TILE_WIRE_SPAN12_VERT_4,
+    TILE_WIRE_SPAN12_VERT_5,
+    TILE_WIRE_SPAN12_VERT_6,
+    TILE_WIRE_SPAN12_VERT_7,
+    TILE_WIRE_SPAN12_VERT_8,
+    TILE_WIRE_SPAN12_VERT_9,
+    TILE_WIRE_SPAN12_VERT_10,
+    TILE_WIRE_SPAN12_VERT_11,
+    TILE_WIRE_SPAN12_VERT_12,
+    TILE_WIRE_SPAN12_VERT_13,
+    TILE_WIRE_SPAN12_VERT_14,
+    TILE_WIRE_SPAN12_VERT_15,
+    TILE_WIRE_SPAN12_VERT_16,
+    TILE_WIRE_SPAN12_VERT_17,
+    TILE_WIRE_SPAN12_VERT_18,
+    TILE_WIRE_SPAN12_VERT_19,
+    TILE_WIRE_SPAN12_VERT_20,
+    TILE_WIRE_SPAN12_VERT_21,
+    TILE_WIRE_SPAN12_VERT_22,
+    TILE_WIRE_SPAN12_VERT_23,
+
+    TILE_WIRE_SPAN12_HORZ_0,
+    TILE_WIRE_SPAN12_HORZ_1,
+    TILE_WIRE_SPAN12_HORZ_2,
+    TILE_WIRE_SPAN12_HORZ_3,
+    TILE_WIRE_SPAN12_HORZ_4,
+    TILE_WIRE_SPAN12_HORZ_5,
+    TILE_WIRE_SPAN12_HORZ_6,
+    TILE_WIRE_SPAN12_HORZ_7,
+    TILE_WIRE_SPAN12_HORZ_8,
+    TILE_WIRE_SPAN12_HORZ_9,
+    TILE_WIRE_SPAN12_HORZ_10,
+    TILE_WIRE_SPAN12_HORZ_11,
+    TILE_WIRE_SPAN12_HORZ_12,
+    TILE_WIRE_SPAN12_HORZ_13,
+    TILE_WIRE_SPAN12_HORZ_14,
+    TILE_WIRE_SPAN12_HORZ_15,
+    TILE_WIRE_SPAN12_HORZ_16,
+    TILE_WIRE_SPAN12_HORZ_17,
+    TILE_WIRE_SPAN12_HORZ_18,
+    TILE_WIRE_SPAN12_HORZ_19,
+    TILE_WIRE_SPAN12_HORZ_20,
+    TILE_WIRE_SPAN12_HORZ_21,
+    TILE_WIRE_SPAN12_HORZ_22,
+    TILE_WIRE_SPAN12_HORZ_23,
+
     TILE_WIRE_PLLIN,
     TILE_WIRE_PLLOUT_A,
     TILE_WIRE_PLLOUT_B
diff --git a/json/jsonparse.cc b/json/jsonparse.cc
index d73525b..a690bf1 100644
--- a/json/jsonparse.cc
+++ b/json/jsonparse.cc
@@ -52,7 +52,7 @@
     std::map<string, JsonNode *> data_dict;
     std::vector<string> data_dict_keys;
 
-    JsonNode(std::istream &f)
+    JsonNode(std::istream &f, int &lineno)
     {
         type = 0;
         data_number = 0;
@@ -63,6 +63,8 @@
             if (ch == EOF)
                 log_error("Unexpected EOF in JSON file.\n");
 
+            if (ch == '\n')
+                lineno++;
             if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n')
                 continue;
 
@@ -91,9 +93,12 @@
                 break;
             }
 
-            if ('0' <= ch && ch <= '9') {
+            if (('0' <= ch && ch <= '9') || ('-' == ch)) {
                 type = 'N';
-                data_number = ch - '0';
+                if (ch == '-')
+                    data_number = 0;
+                else
+                    data_number = ch - '0';
                 data_string += ch;
 
                 while (1) {
@@ -114,6 +119,8 @@
                     data_string += ch;
                 }
 
+                if (data_string[0] == '-')
+                    data_number = -data_number;
                 data_string = "";
                 break;
 
@@ -148,6 +155,8 @@
                     if (ch == EOF)
                         log_error("Unexpected EOF in JSON file.\n");
 
+                    if (ch == '\n')
+                        lineno++;
                     if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == ',')
                         continue;
 
@@ -155,7 +164,7 @@
                         break;
 
                     f.unget();
-                    data_array.push_back(new JsonNode(f));
+                    data_array.push_back(new JsonNode(f, lineno));
                 }
 
                 break;
@@ -170,6 +179,8 @@
                     if (ch == EOF)
                         log_error("Unexpected EOF in JSON file.\n");
 
+                    if (ch == '\n')
+                        lineno++;
                     if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == ',')
                         continue;
 
@@ -177,7 +188,7 @@
                         break;
 
                     f.unget();
-                    JsonNode key(f);
+                    JsonNode key(f, lineno);
 
                     while (1) {
                         ch = f.get();
@@ -185,6 +196,8 @@
                         if (ch == EOF)
                             log_error("Unexpected EOF in JSON file.\n");
 
+                        if (ch == '\n')
+                            lineno++;
                         if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == ':')
                             continue;
 
@@ -192,10 +205,10 @@
                         break;
                     }
 
-                    JsonNode *value = new JsonNode(f);
+                    JsonNode *value = new JsonNode(f, lineno);
 
                     if (key.type != 'S')
-                        log_error("Unexpected non-string key in JSON dict.\n");
+                        log_error("Unexpected non-string key in JSON dict, line %d.\n", lineno);
 
                     data_dict[key.data_string] = value;
                     data_dict_keys.push_back(key.data_string);
@@ -204,7 +217,7 @@
                 break;
             }
 
-            log_error("Unexpected character in JSON file: '%c'\n", ch);
+            log_error("Unexpected character in JSON file, line %d: '%c'\n", lineno, ch);
         }
     }
 
@@ -736,8 +749,9 @@
 {
     try {
         using namespace JsonParser;
+        int lineno = 1;
 
-        JsonNode root(f);
+        JsonNode root(f, lineno);
 
         if (root.type != 'D')
             log_error("JSON root node is not a dictionary.\n");