/*
 *  nextpnr -- Next Generation Place and Route
 *
 *  Copyright (C) 2018  Claire Xenia Wolf <claire@yosyshq.com>
 *  Copyright (C) 2020  Pepijn de Vos <pepijn@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 <boost/algorithm/string.hpp>
#include <cells.h>
#include <iostream>
#include <math.h>
#include <regex>
#include "design_utils.h"
#include "embed.h"
#include "gfx.h"
#include "nextpnr.h"
#include "placer1.h"
#include "placer_heap.h"
#include "router1.h"
#include "router2.h"
#include "util.h"

NEXTPNR_NAMESPACE_BEGIN

const PairPOD *pairLookup(const PairPOD *list, const size_t len, const int dest);

// GUI
void Arch::fixClockSpineDecals(void)
{
    for (auto sp : clockSpinesCache) {
        // row: (#of spine cells, wire_id)
        dict<int, std::pair<int, IdString>> rows;
        IdString min_x, max_x;
        min_x = max_x = *sp.second.begin();
        for (auto wire : sp.second) {
            WireInfo &wi = wire_info(wire);
            std::pair<int, IdString> &row = rows[wi.y];
            ++row.first;
            row.second = wire;
            if (wi.x < wire_info(min_x).x) {
                min_x = wire;
            } else {
                if (wi.x > wire_info(max_x).x) {
                    max_x = wire;
                }
            }
        }
        // central mux row owns the global decal
        int mux_row = -1;
        for (auto row : rows) {
            if (row.second.first == 1) {
                mux_row = row.first;
                break;
            }
        }
        // if there is no separate central mux than all decals are the same
        if (mux_row == -1) {
            mux_row = rows.begin()->first;
            WireInfo &wi = wire_info(rows.at(mux_row).second);
            GraphicElement &el_active = decal_graphics.at(wi.decalxy_active.decal).at(0);
            GraphicElement &el_inactive = decal_graphics.at(wi.decalxy_inactive.decal).at(0);
            el_active.y1 -= wi.y;
            el_active.y2 -= wi.y;
            el_inactive.y1 -= wi.y;
            el_inactive.y2 -= wi.y;
            el_active.x1 += wire_info(min_x).x;
            el_active.x2 += wire_info(max_x).x;
            el_inactive.x1 += wire_info(min_x).x;
            el_inactive.x2 += wire_info(max_x).x;
        } else {
            // change the global decal
            WireInfo &wi = wire_info(rows.at(mux_row).second);
            // clear spine decals
            float y = 0.;
            for (auto wire : sp.second) {
                if (wire == wi.name) {
                    continue;
                }
                wire_info(wire).decalxy_active = DecalXY();
                wire_info(wire).decalxy_inactive = DecalXY();
                y = wire_info(wire).y;
            }
            GraphicElement &el_active = decal_graphics.at(wi.decalxy_active.decal).at(0);
            GraphicElement &el_inactive = decal_graphics.at(wi.decalxy_inactive.decal).at(0);
            el_active.y1 -= y;
            el_active.y2 -= y;
            el_inactive.y1 -= y;
            el_inactive.y2 -= y;
            el_active.x1 += wire_info(min_x).x;
            el_active.x2 += wire_info(max_x).x;
            el_inactive.x1 += wire_info(min_x).x;
            el_inactive.x2 += wire_info(max_x).x;
        }
        refreshUi();
    }
}

void Arch::updateClockSpinesCache(IdString spine_id, IdString wire_id)
{
    std::vector<IdString> &sp = clockSpinesCache[spine_id];
    if (std::find(sp.begin(), sp.end(), wire_id) == sp.end()) {
        sp.push_back(wire_id);
    }
}

DecalXY Arch::getBelDecal(BelId bel) const
{
    CellInfo *ci = getBoundBelCell(bel);
    if (ci == nullptr) {
        return bels.at(bel).decalxy_inactive;
    } else {
        // LUT + used/unused DFF
        if (bels.at(bel).type == id_SLICE) {
            DecalXY decalxy = bels.at(bel).decalxy_active;
            if (!ci->params.at(id_FF_USED).as_bool()) {
                decalxy.decal = id_DECAL_LUT_UNUSED_DFF_ACTIVE;
                if (ci->params.count(id_ALU_MODE) != 0) {
                    decalxy.decal = id_DECAL_ALU_ACTIVE;
                }
            }
            return decalxy;
        }
    }
    return bels.at(bel).decalxy_active;
}

DecalXY Arch::getGroupDecal(GroupId grp) const { return groups.at(grp).decalxy; }

DecalXY Arch::getPipDecal(PipId pip) const
{
    if (getBoundPipNet(pip) == nullptr) {
        return pips.at(pip).decalxy_inactive;
    }
    return pips.at(pip).decalxy_active;
}

DecalXY Arch::getWireDecal(WireId wire) const
{
    static std::vector<IdString> clk_wires = {id_GB00, id_GB10, id_GB20, id_GB30, id_GB40, id_GB50, id_GB60, id_GB70};
    static std::vector<IdString> pip_dst = {id_CLK0, id_CLK1, id_CLK2, id_EW10, id_EW20, id_SN10, id_SN20};
    if (getBoundWireNet(wire) == nullptr) {
        if (std::find(clk_wires.begin(), clk_wires.end(), wires.at(wire).type) != clk_wires.end()) {
            for (auto dst : pip_dst) {
                // check if pip is used
                char pip_name[20];
                snprintf(pip_name, sizeof(pip_name), "%s_%s", wire.c_str(this), dst.c_str(this));
                if (pips.count(id(pip_name)) != 0) {
                    if (getBoundPipNet(id(pip_name)) != nullptr) {
                        return wires.at(wire).decalxy_active;
                    }
                }
            }
        } else {
            // spines
            if (clockSpinesCache.count(wires.at(wire).type) != 0) {
                std::vector<IdString> const &sp = clockSpinesCache.at(wires.at(wire).type);
                for (auto w : sp) {
                    if (getBoundWireNet(w) != nullptr) {
                        return wires.at(wire).decalxy_active;
                    }
                }
            }
        }
        return wires.at(wire).decalxy_inactive;
    }
    return wires.at(wire).decalxy_active;
}

bool Arch::allocate_longwire(NetInfo *ni, int lw_idx)
{
    NPNR_ASSERT(ni != nullptr);
    if (ni->driver.cell == nullptr) {
        return false;
    }
    if (ni->name == id("$PACKER_VCC_NET") || ni->name == id("$PACKER_GND_NET")) {
        return false;
    }
    // So far only for OBUF
    switch (ni->driver.cell->type.index) {
    case ID_ODDR:  /* fall-through*/
    case ID_ODDRC: /* fall-through*/
    case ID_IOBUF: /* fall-through*/
    case ID_TBUF:
        return false;
    case ID_OBUF:
        if (getCtx()->debug) {
            log_info("Long wire for IO %s\n", nameOf(ni));
        }
        ni = ni->driver.cell->ports.at(id_I).net;
        return allocate_longwire(ni, lw_idx);
        break;
    default:
        break;
    }

    if (getCtx()->debug) {
        log_info("Requested index:%d\n", lw_idx);
    }
    if (avail_longwires == 0 || (lw_idx != -1 && (avail_longwires & (1 << lw_idx)) == 0)) {
        return false;
    }
    int longwire = lw_idx;
    if (lw_idx == -1) {
        for (longwire = 7; longwire >= 0; --longwire) {
            if (avail_longwires & (1 << longwire)) {
                break;
            }
        }
    }
    avail_longwires &= ~(1 << longwire);

    // BUFS cell
    CellInfo *bufs;
    char buf[40];
    snprintf(buf, sizeof(buf), "$PACKER_BUFS%d", longwire);
    std::unique_ptr<CellInfo> new_cell = create_generic_cell(getCtx(), id_BUFS, buf);
    bufs = new_cell.get();
    cells[bufs->name] = std::move(new_cell);
    if (lw_idx != -1) {
        bufs->cluster = bufs->name;
        bufs->constr_z = lw_idx + BelZ::bufs_0_z;
        bufs->constr_abs_z = true;
        bufs->constr_children.clear();
    }

    // old driver -> bufs LW input net
    auto net = std::make_unique<NetInfo>(idf("$PACKER_BUFS_%c", longwire + 'A'));
    NetInfo *bufs_net = net.get();
    nets[net->name] = std::move(net);

    // split the net
    CellInfo *driver_cell = ni->driver.cell;
    IdString driver_port = ni->driver.port;
    driver_cell->disconnectPort(driver_port);

    bufs->connectPort(id_O, ni);
    bufs->connectPort(id_I, bufs_net);
    driver_cell->connectPort(driver_port, bufs_net);

    if (getCtx()->debug) {
        log_info("Long wire %d was allocated\n", longwire);
    }
    return true;
}

void Arch::auto_longwires() {}

void Arch::fix_longwire_bels()
{
    // After routing, it is clear which wires and in which bus SS00 and SS40 are used and
    // in which quadrant they are routed. Here we write it in the attributes.
    for (auto &cell : cells) {
        CellInfo *ci = cell.second.get();
        if (ci->type != id_BUFS) {
            continue;
        }
        const NetInfo *ni = ci->getPort(id_O);
        if (ni == nullptr) {
            continue;
        }
        // bus wire is one of the wires
        // value does not matter, but the L/R parameter itself
        for (auto &wire : ni->wires) {
            WireId w = wires[wire.first].type;
            switch (w.hash()) {
            case ID_LWSPINETL0:
            case ID_LWSPINETL1:
            case ID_LWSPINETL2:
            case ID_LWSPINETL3:
            case ID_LWSPINETL4:
            case ID_LWSPINETL5:
            case ID_LWSPINETL6:
            case ID_LWSPINETL7:
            case ID_LWSPINEBL0:
            case ID_LWSPINEBL1:
            case ID_LWSPINEBL2:
            case ID_LWSPINEBL3:
            case ID_LWSPINEBL4:
            case ID_LWSPINEBL5:
            case ID_LWSPINEBL6:
            case ID_LWSPINEBL7:
                ci->setParam(id("L"), Property(w.str(this)));
                break;
            case ID_LWSPINETR0:
            case ID_LWSPINETR1:
            case ID_LWSPINETR2:
            case ID_LWSPINETR3:
            case ID_LWSPINETR4:
            case ID_LWSPINETR5:
            case ID_LWSPINETR6:
            case ID_LWSPINETR7:
            case ID_LWSPINEBR0:
            case ID_LWSPINEBR1:
            case ID_LWSPINEBR2:
            case ID_LWSPINEBR3:
            case ID_LWSPINEBR4:
            case ID_LWSPINEBR5:
            case ID_LWSPINEBR6:
            case ID_LWSPINEBR7:
                ci->setParam(id("R"), Property(w.str(this)));
                break;
            default:
                break;
            }
        }
    }
}

WireInfo &Arch::wire_info(IdString wire)
{
    auto w = wires.find(wire);
    if (w == wires.end())
        NPNR_ASSERT_FALSE_STR("no wire named " + wire.str(this));
    return w->second;
}

PipInfo &Arch::pip_info(IdString pip)
{
    auto p = pips.find(pip);
    if (p == pips.end())
        NPNR_ASSERT_FALSE_STR("no pip named " + pip.str(this));
    return p->second;
}

BelInfo &Arch::bel_info(IdString bel)
{
    auto b = bels.find(bel);
    if (b == bels.end())
        NPNR_ASSERT_FALSE_STR("no bel named " + bel.str(this));
    return b->second;
}

NetInfo &Arch::net_info(IdString net)
{
    auto b = nets.find(net);
    if (b == nets.end())
        NPNR_ASSERT_FALSE_STR("no net named " + net.str(this));
    return *b->second;
}

void Arch::addWire(IdString name, IdString type, int x, int y)
{
    NPNR_ASSERT(!wires.count(name));
    WireInfo &wi = wires[name];
    wi.name = name;
    wi.type = type;
    wi.x = x;
    wi.y = y;

    wire_ids.push_back(name);

    // Needed to ensure empty tile bel locations
    if (int(bels_by_tile.size()) <= x)
        bels_by_tile.resize(x + 1);
    if (int(bels_by_tile[x].size()) <= y)
        bels_by_tile[x].resize(y + 1);
    if (int(tileBelDimZ.size()) <= x)
        tileBelDimZ.resize(x + 1);
    if (int(tileBelDimZ[x].size()) <= y)
        tileBelDimZ[x].resize(y + 1);
}

void Arch::addPip(IdString name, IdString type, IdString srcWire, IdString dstWire, DelayQuad delay, Loc loc)
{
    NPNR_ASSERT(pips.count(name) == 0);
    PipInfo &pi = pips[name];
    pi.name = name;
    pi.type = type;
    pi.srcWire = srcWire;
    pi.dstWire = dstWire;
    pi.delay = delay;
    pi.loc = loc;

    wire_info(srcWire).downhill.push_back(name);
    wire_info(dstWire).uphill.push_back(name);
    pip_ids.push_back(name);

    if (int(tilePipDimZ.size()) <= loc.x)
        tilePipDimZ.resize(loc.x + 1);

    if (int(tilePipDimZ[loc.x].size()) <= loc.y)
        tilePipDimZ[loc.x].resize(loc.y + 1);

    // Needed to ensure empty tile bel locations
    if (int(bels_by_tile.size()) <= loc.x)
        bels_by_tile.resize(loc.x + 1);
    if (int(bels_by_tile[loc.x].size()) <= loc.y)
        bels_by_tile[loc.x].resize(loc.y + 1);
    if (int(tileBelDimZ.size()) <= loc.x)
        tileBelDimZ.resize(loc.x + 1);
    if (int(tileBelDimZ[loc.x].size()) <= loc.y)
        tileBelDimZ[loc.x].resize(loc.y + 1);

    gridDimX = std::max(gridDimX, loc.x + 1);
    gridDimY = std::max(gridDimY, loc.y + 1);
    tilePipDimZ[loc.x][loc.y] = std::max(tilePipDimZ[loc.x][loc.y], loc.z + 1);
}

void Arch::addGroup(IdString name)
{
    NPNR_ASSERT(groups.count(name) == 0);
    GroupInfo &gi = groups[name];
    gi.name = name;
}

void Arch::addBel(IdString name, IdString type, Loc loc, bool gb)
{
    NPNR_ASSERT(bels.count(name) == 0);
    NPNR_ASSERT(bel_by_loc.count(loc) == 0);
    BelInfo &bi = bels[name];
    bi.name = name;
    bi.type = type;
    bi.x = loc.x;
    bi.y = loc.y;
    bi.z = loc.z;
    bi.gb = gb;

    bel_ids.push_back(name);
    bel_by_loc[loc] = name;

    if (int(bels_by_tile.size()) <= loc.x)
        bels_by_tile.resize(loc.x + 1);

    if (int(bels_by_tile[loc.x].size()) <= loc.y)
        bels_by_tile[loc.x].resize(loc.y + 1);

    bels_by_tile[loc.x][loc.y].push_back(name);

    if (int(tileBelDimZ.size()) <= loc.x)
        tileBelDimZ.resize(loc.x + 1);

    if (int(tileBelDimZ[loc.x].size()) <= loc.y)
        tileBelDimZ[loc.x].resize(loc.y + 1);

    gridDimX = std::max(gridDimX, loc.x + 1);
    gridDimY = std::max(gridDimY, loc.y + 1);
    tileBelDimZ[loc.x][loc.y] = std::max(tileBelDimZ[loc.x][loc.y], loc.z + 1);
}

void Arch::addBelInput(IdString bel, IdString name, IdString wire)
{
    NPNR_ASSERT(bel_info(bel).pins.count(name) == 0);
    PinInfo &pi = bel_info(bel).pins[name];
    pi.name = name;
    pi.wire = wire;
    pi.type = PORT_IN;

    wire_info(wire).downhill_bel_pins.push_back(BelPin{bel, name});
    wire_info(wire).bel_pins.push_back(BelPin{bel, name});
}

void Arch::addBelOutput(IdString bel, IdString name, IdString wire)
{
    NPNR_ASSERT(bel_info(bel).pins.count(name) == 0);
    PinInfo &pi = bel_info(bel).pins[name];
    pi.name = name;
    pi.wire = wire;
    pi.type = PORT_OUT;

    wire_info(wire).uphill_bel_pin = BelPin{bel, name};
    wire_info(wire).bel_pins.push_back(BelPin{bel, name});
}

void Arch::addBelInout(IdString bel, IdString name, IdString wire)
{
    NPNR_ASSERT(bel_info(bel).pins.count(name) == 0);
    PinInfo &pi = bel_info(bel).pins[name];
    pi.name = name;
    pi.wire = wire;
    pi.type = PORT_INOUT;

    wire_info(wire).downhill_bel_pins.push_back(BelPin{bel, name});
    wire_info(wire).bel_pins.push_back(BelPin{bel, name});
}

void Arch::addGroupBel(IdString group, IdString bel) { groups[group].bels.push_back(bel); }

void Arch::addGroupWire(IdString group, IdString wire) { groups[group].wires.push_back(wire); }

void Arch::addGroupPip(IdString group, IdString pip) { groups[group].pips.push_back(pip); }

void Arch::addGroupGroup(IdString group, IdString grp) { groups[group].groups.push_back(grp); }

void Arch::addDecalGraphic(DecalId decal, const GraphicElement &graphic)
{
    decal_graphics[decal].push_back(graphic);
    refreshUi();
}

void Arch::setWireDecal(WireId wire, DecalXY active, DecalXY inactive)
{
    wire_info(wire).decalxy_active = active;
    wire_info(wire).decalxy_inactive = inactive;
    refreshUiWire(wire);
}

void Arch::setPipDecal(PipId pip, DecalXY active, DecalXY inactive)
{
    pip_info(pip).decalxy_active = active;
    pip_info(pip).decalxy_inactive = inactive;
    refreshUiPip(pip);
}

void Arch::setBelDecal(BelId bel, DecalXY active, DecalXY inactive)
{
    bel_info(bel).decalxy_active = active;
    bel_info(bel).decalxy_inactive = inactive;
    refreshUiBel(bel);
}

void Arch::setDefaultDecals(void)
{
#ifndef NO_GUI
    for (BelId bel : getBels()) {
        gfxSetBelDefaultDecal(this, bel_info(bel));
    }
    for (PipId pip : getPips()) {
        gfxSetPipDefaultDecal(this, pip_info(pip));
    }
    for (WireId wire : getWires()) {
        gfxSetWireDefaultDecal(this, wire_info(wire));
    }
    fixClockSpineDecals();
#endif
}

void Arch::setGroupDecal(GroupId group, DecalXY decalxy)
{
    groups[group].decalxy = decalxy;
    refreshUiGroup(group);
}

void Arch::setWireAttr(IdString wire, IdString key, const std::string &value) { wire_info(wire).attrs[key] = value; }

void Arch::setPipAttr(IdString pip, IdString key, const std::string &value) { pip_info(pip).attrs[key] = value; }

void Arch::setBelAttr(IdString bel, IdString key, const std::string &value) { bel_info(bel).attrs[key] = value; }

void Arch::setDelayScaling(double scale, double offset)
{
    args.delayScale = scale;
    args.delayOffset = offset;
}

void Arch::addCellTimingClass(IdString cell, IdString port, TimingPortClass cls)
{
    cellTiming[cell].portClasses[port] = cls;
}

void Arch::addCellTimingClock(IdString cell, IdString port) { cellTiming[cell].portClasses[port] = TMG_CLOCK_INPUT; }

void Arch::addCellTimingDelay(IdString cell, IdString fromPort, IdString toPort, DelayQuad delay)
{
    if (get_or_default(cellTiming[cell].portClasses, fromPort, TMG_IGNORE) == TMG_IGNORE)
        cellTiming[cell].portClasses[fromPort] = TMG_COMB_INPUT;
    if (get_or_default(cellTiming[cell].portClasses, toPort, TMG_IGNORE) == TMG_IGNORE)
        cellTiming[cell].portClasses[toPort] = TMG_COMB_OUTPUT;
    cellTiming[cell].combDelays[CellDelayKey{fromPort, toPort}] = delay;
}

void Arch::addCellTimingSetupHold(IdString cell, IdString port, IdString clock, DelayPair setup, DelayPair hold)
{
    TimingClockingInfo ci;
    ci.clock_port = clock;
    ci.edge = RISING_EDGE;
    ci.setup = setup;
    ci.hold = hold;
    cellTiming[cell].clockingInfo[port].push_back(ci);
    cellTiming[cell].portClasses[port] = TMG_REGISTER_INPUT;
}

void Arch::addCellTimingClockToOut(IdString cell, IdString port, IdString clock, DelayQuad clktoq)
{
    TimingClockingInfo ci;
    ci.clock_port = clock;
    ci.edge = RISING_EDGE;
    ci.clockToQ = clktoq;
    cellTiming[cell].clockingInfo[port].push_back(ci);
    cellTiming[cell].portClasses[port] = TMG_REGISTER_OUTPUT;
}

// ---------------------------------------------------------------

IdString Arch::apply_local_aliases(int row, int col, const DatabasePOD *db, IdString &wire)
{
    const TilePOD *tile = db->grid[row * db->cols + col].get();
    auto local_alias = pairLookup(tile->aliases.get(), tile->num_aliases, wire.index);
    IdString res_wire = IdString();
    if (local_alias != nullptr) {
        wire = IdString(local_alias->src_id);
        res_wire = idf("R%dC%d_%s", row + 1, col + 1, wire.c_str(this));
    }
    return res_wire;
}

// TODO represent wires more intelligently.
IdString Arch::wireToGlobal(int &row, int &col, const DatabasePOD *db, IdString &wire)
{
    const std::string &wirename = wire.str(this);
    if (wirename == "VCC" || wirename == "VSS") {
        row = 0;
        col = 0;
        return wire;
    }
    if (!isdigit(wirename[1]) || !isdigit(wirename[2]) || !isdigit(wirename[3])) {
        IdString res_wire = apply_local_aliases(row, col, db, wire);
        if (res_wire == IdString()) {
            return idf("R%dC%d_%s", row + 1, col + 1, wirename.c_str());
        }
        return res_wire;
    }
    char direction = wirename[0];
    int num = std::stoi(wirename.substr(1, 2));
    int segment = std::stoi(wirename.substr(3, 1));
    switch (direction) {
    case 'N':
        row += segment;
        break;
    case 'S':
        row -= segment;
        break;
    case 'E':
        col -= segment;
        break;
    case 'W':
        col += segment;
        break;
    default:
        return idf("R%dC%d_%s", row + 1, col + 1, wirename.c_str());
        break;
    }
    // wires wrap around the edges
    // assumes 0-based indexes
    if (row < 0) {
        row = -1 - row;
        direction = 'N';
    } else if (col < 0) {
        col = -1 - col;
        direction = 'W';
    } else if (row >= db->rows) {
        row = 2 * db->rows - 1 - row;
        direction = 'S';
    } else if (col >= db->cols) {
        col = 2 * db->cols - 1 - col;
        direction = 'E';
    }
    wire = idf("%c%d0", direction, num);
    // local aliases
    IdString res_wire = apply_local_aliases(row, col, db, wire);
    if (res_wire == IdString()) {
        res_wire = idf("R%dC%d_%c%d", row + 1, col + 1, direction, num);
    }
    return res_wire;
}

const PairPOD *pairLookup(const PairPOD *list, const size_t len, const int dest)
{
    for (size_t i = 0; i < len; i++) {
        const PairPOD *pair = &list[i];
        if (pair->dest_id == dest) {
            return pair;
        }
    }
    return nullptr;
}

const PinPOD *pinLookup(const PinPOD *list, const size_t len, const int idx)
{
    for (size_t i = 0; i < len; i++) {
        const PinPOD *pin = &list[i];
        if (pin->index_id == idx) {
            return pin;
        }
    }
    return nullptr;
}

bool aliasCompare(GlobalAliasPOD i, GlobalAliasPOD j)
{
    return (i.dest_row < j.dest_row) || (i.dest_row == j.dest_row && i.dest_col < j.dest_col) ||
           (i.dest_row == j.dest_row && i.dest_col == j.dest_col && i.dest_id < j.dest_id);
}

bool timingCompare(TimingPOD i, TimingPOD j) { return i.name_id < j.name_id; }

template <class T, class C> const T *genericLookup(const T *first, int len, const T val, C compare)
{
    auto res = std::lower_bound(first, first + len, val, compare);
    if (res - first != len && !compare(val, *res)) {
        return res;
    } else {
        return nullptr;
    }
}

template <class T, class C> const T *timingLookup(const T *first, int len, const T val, C compare)
{
    for (int i = 0; i < len; ++i) {
        auto res = &first[i];
        if (!(compare(*res, val) || compare(val, *res))) {
            return res;
        }
    }
    return nullptr;
}

DelayQuad delayLookup(const TimingPOD *first, int len, IdString name)
{
    TimingPOD needle;
    needle.name_id = name.index;
    const TimingPOD *timing = timingLookup(first, len, needle, timingCompare);
    DelayQuad delay;
    if (timing != nullptr) {
        delay.fall.max_delay = std::max(timing->ff, timing->rf) / 1000.;
        delay.fall.min_delay = std::min(timing->ff, timing->rf) / 1000.;
        delay.rise.max_delay = std::max(timing->rr, timing->fr) / 1000.;
        delay.rise.min_delay = std::min(timing->rr, timing->fr) / 1000.;
    } else {
        delay = DelayQuad(0);
    }
    return delay;
}

DelayQuad Arch::getWireTypeDelay(IdString wire)
{
    IdString len;
    IdString glbsrc;
    switch (wire.index) {
    case ID_X01:
    case ID_X02:
    case ID_X03:
    case ID_X04:
    case ID_X05:
    case ID_X06:
    case ID_X07:
    case ID_X08:
    case ID_I0:
    case ID_I1:
        len = id_X0;
        break;
    case ID_N100:
    case ID_N130:
    case ID_S100:
    case ID_S130:
    case ID_E100:
    case ID_E130:
    case ID_W100:
    case ID_W130:
    case ID_E110:
    case ID_W110:
    case ID_E120:
    case ID_W120:
    case ID_S110:
    case ID_N110:
    case ID_S120:
    case ID_N120:
    case ID_SN10:
    case ID_SN20:
    case ID_EW10:
    case ID_EW20:
    case ID_I01:
        len = id_FX1;
        break;
    case ID_N200:
    case ID_N210:
    case ID_N220:
    case ID_N230:
    case ID_N240:
    case ID_N250:
    case ID_N260:
    case ID_N270:
    case ID_S200:
    case ID_S210:
    case ID_S220:
    case ID_S230:
    case ID_S240:
    case ID_S250:
    case ID_S260:
    case ID_S270:
    case ID_E200:
    case ID_E210:
    case ID_E220:
    case ID_E230:
    case ID_E240:
    case ID_E250:
    case ID_E260:
    case ID_E270:
    case ID_W200:
    case ID_W210:
    case ID_W220:
    case ID_W230:
    case ID_W240:
    case ID_W250:
    case ID_W260:
    case ID_W270:
        len = id_X2;
        break;
    case ID_N800:
    case ID_N810:
    case ID_N820:
    case ID_N830:
    case ID_S800:
    case ID_S810:
    case ID_S820:
    case ID_S830:
    case ID_E800:
    case ID_E810:
    case ID_E820:
    case ID_E830:
    case ID_W800:
    case ID_W810:
    case ID_W820:
    case ID_W830:
        len = id_X8;
        break;
    case ID_LT02:
    case ID_LT13:
        glbsrc = id_SPINE_TAP_SCLK_0;
        break;
    case ID_LT01:
    case ID_LT04:
        glbsrc = id_SPINE_TAP_SCLK_1;
        break;
    case ID_LBO0:
    case ID_LBO1:
        glbsrc = id_TAP_BRANCH_SCLK;
        break;
    case ID_LB01:
    case ID_LB11:
    case ID_LB21:
    case ID_LB31:
    case ID_LB41:
    case ID_LB51:
    case ID_LB61:
    case ID_LB71:
        glbsrc = id_BRANCH_SCLK;
        break;
    case ID_GT00:
    case ID_GT10:
        glbsrc = id_SPINE_TAP_PCLK;
        break;
    case ID_GBO0:
    case ID_GBO1:
        glbsrc = id_TAP_BRANCH_PCLK;
        break;
    case ID_GB00:
    case ID_GB10:
    case ID_GB20:
    case ID_GB30:
    case ID_GB40:
    case ID_GB50:
    case ID_GB60:
    case ID_GB70:
        glbsrc = id_BRANCH_PCLK;
        break;
    default:
        if (wire.str(this).rfind("LWSPINE", 0) == 0) {
            glbsrc = IdString(ID_CENT_SPINE_SCLK);
        } else if (wire.str(this).rfind("SPINE", 0) == 0) {
            glbsrc = IdString(ID_CENT_SPINE_PCLK);
        } else if (wire.str(this).rfind("UNK", 0) == 0) {
            glbsrc = IdString(ID_PIO_CENT_PCLK);
        }
        break;
    }
    if (len != IdString()) {
        return delayLookup(speed->wire.timings.get(), speed->wire.num_timings, len);
    } else if (glbsrc != IdString()) {
        return delayLookup(speed->glbsrc.timings.get(), speed->glbsrc.num_timings, glbsrc);
    } else {
        return DelayQuad(0);
    }
}

static Loc getLoc(std::smatch match, int maxX, int maxY)
{
    int col = std::stoi(match[2]);
    int row = 1; // Top
    std::string side = match[1].str();
    if (side == "R") {
        row = col;
        col = maxX;
    } else if (side == "B") {
        row = maxY;
    } else if (side == "L") {
        row = col;
        col = 1;
    }
    int z = match[3].str()[0] - 'A';
    return Loc(col - 1, row - 1, z);
}

void Arch::read_cst(std::istream &in)
{
    // If two locations are specified separated by commas (for differential I/O buffers),
    // only the first location is actually recognized and used.
    // And pin A will be Positive and pin B will be Negative in any case.
    std::regex iobre = std::regex("IO_LOC +\"([^\"]+)\" +([^ ,;]+)(, *[^ ;]+)? *;.*[\\s\\S]*");
    std::regex portre = std::regex("IO_PORT +\"([^\"]+)\" +([^;]+;).*[\\s\\S]*");
    std::regex port_attrre = std::regex("([^ =;]+=[^ =;]+) *([^;]*;)");
    std::regex iobelre = std::regex("IO([TRBL])([0-9]+)\\[?([A-Z])\\]?");
    std::regex inslocre =
            std::regex("INS_LOC +\"([^\"]+)\" +R([0-9]+)C([0-9]+)\\[([0-9])\\]\\[([AB])\\] *;.*[\\s\\S]*");
    std::regex clockre = std::regex("CLOCK_LOC +\"([^\"]+)\" +BUF([GS])(\\[([0-7])\\])?[^;]*;.*[\\s\\S]*");
    std::smatch match, match_attr, match_pinloc;
    std::string line, pinline;
    enum
    {
        ioloc,
        ioport,
        insloc,
        clock
    } cst_type;

    settings.erase(id_cst);
    while (!in.eof()) {
        std::getline(in, line);
        cst_type = ioloc;
        if (!std::regex_match(line, match, iobre)) {
            if (std::regex_match(line, match, portre)) {
                cst_type = ioport;
            } else {
                if (std::regex_match(line, match, clockre)) {
                    cst_type = clock;
                } else {
                    if (std::regex_match(line, match, inslocre)) {
                        cst_type = insloc;
                    } else {
                        if ((!line.empty()) && (line.rfind("//", 0) == std::string::npos)) {
                            log_warning("Invalid constraint: %s\n", line.c_str());
                        }
                        continue;
                    }
                }
            }
        }

        IdString net = id(match[1]);
        auto it = cells.find(net);
        if (cst_type != clock && it == cells.end()) {
            log_info("Cell %s not found\n", net.c_str(this));
            continue;
        }
        switch (cst_type) {
        case clock: { // CLOCK name BUFG|S=#
            std::string which_clock = match[2];
            std::string lw = match[4];
            int lw_idx = -1;
            if (lw.length() > 0) {
                lw_idx = atoi(lw.c_str());
                log_info("lw_idx:%d\n", lw_idx);
            }
            if (which_clock.at(0) == 'S') {
                auto ni = nets.find(net);
                if (ni == nets.end()) {
                    log_info("Net %s not found\n", net.c_str(this));
                    continue;
                }
                if (!allocate_longwire(ni->second.get(), lw_idx)) {
                    log_info("Can't use the long wires. The %s network will use normal routing.\n", net.c_str(this));
                }
            } else {
                log_info("BUFG isn't supported\n");
                continue;
            }
        } break;
        case ioloc: { // IO_LOC name pin
            IdString pinname = id(match[2]);
            pinline = match[2];
            const PinPOD *belname = pinLookup(package->pins.get(), package->num_pins, pinname.index);
            if (belname != nullptr) {
                std::string bel = IdString(belname->loc_id).str(this);
                it->second->setAttr(IdString(ID_BEL), bel);
            } else {
                if (std::regex_match(pinline, match_pinloc, iobelre)) {
                    // may be it's IOx#[AB] style?
                    Loc loc = getLoc(match_pinloc, getGridDimX(), getGridDimY());
                    BelId bel = getBelByLocation(loc);
                    if (bel == BelId()) {
                        log_error("Pin %s not found (TRBL style). \n", pinline.c_str());
                    }
                    std::string belname = getCtx()->nameOfBel(bel);
                    it->second->setAttr(IdString(ID_BEL), belname);
                } else {
                    log_error("Pin %s not found (pin# style)\n", pinname.c_str(this));
                }
            }
        } break;
        case insloc: { // INS_LOC
            int slice = std::stoi(match[4].str()) * 2;
            if (match[5].str() == "B") {
                ++slice;
            }
            std::string belname = std::string("R") + match[2].str() + "C" + match[3].str() + stringf("_SLICE%d", slice);
            it->second->setAttr(IdString(ID_BEL), belname);
        } break;
        default: { // IO_PORT attr=value
            std::string attr_val = match[2];
            while (std::regex_match(attr_val, match_attr, port_attrre)) {
                std::string attr = "&";
                attr += match_attr[1];
                boost::algorithm::to_upper(attr);
                it->second->setAttr(id(attr), 1);
                attr_val = match_attr[2];
            }
        }
        }
    }
    settings[id_cst] = 1;
}

// Add all MUXes for the cell
void Arch::addMuxBels(const DatabasePOD *db, int row, int col)
{
    IdString belname, bel_id;
    char buf[40];
    int z;

    // make all wide luts with these parameters
    struct
    {
        char type;         // MUX type 5,6,7,8
        char bel_idx;      // just bel name suffix
        char in_prefix[2]; // input from F or OF
        char in_idx[2];    // input from bel with idx
    } const mux_names[] = {{'5', '0', "", {'0', '1'}},  {'6', '0', "O", {'2', '0'}}, {'5', '1', "", {'2', '3'}},
                           {'7', '0', "O", {'5', '1'}}, {'5', '2', "", {'4', '5'}},  {'6', '1', "O", {'6', '4'}},
                           {'5', '3', "", {'6', '7'}},  {'8', '0', "O", {'3', '3'}}};

    // 4 MUX2_LUT5, 2 MUX2_LUT6, 1 MUX2_LUT7, 1 MUX2_LUT8
    for (int j = 0; j < 8; ++j) {
        z = j + BelZ::mux_0_z;

        int grow = row + 1;
        int gcol = col + 1;

        // no MUX2_LUT8 in the last column
        if (j == 7 && col == getGridDimX() - 2) {
            continue;
        }

        // bel
        snprintf(buf, 40, "R%dC%d_MUX2_LUT%c%c", grow, gcol, mux_names[j].type, mux_names[j].bel_idx);
        belname = id(buf);
        snprintf(buf, 40, "MUX2_LUT%c", mux_names[j].type);
        bel_id = id(buf);
        addBel(belname, bel_id, Loc(col, row, z), false);

        // dummy wires
        snprintf(buf, 40, "I0MUX%d", j);
        IdString id_wire_i0 = id(buf);
        IdString wire_i0_name = wireToGlobal(row, col, db, id_wire_i0);
        addWire(wire_i0_name, id_wire_i0, col, row);

        snprintf(buf, 40, "I1MUX%d", j);
        IdString id_wire_i1 = id(buf);
        IdString wire_i1_name = wireToGlobal(row, col, db, id_wire_i1);
        addWire(wire_i1_name, id_wire_i1, col, row);

        // dummy right pip
        DelayQuad delay = getWireTypeDelay(id_I0);
        snprintf(buf, 40, "%sF%c", mux_names[j].in_prefix, mux_names[j].in_idx[1]);
        IdString id_src_F = id(buf);
        IdString src_F = wireToGlobal(row, col, db, id_src_F);
        snprintf(buf, 40, "R%dC%d_%s_DUMMY_%s", grow, gcol, id_src_F.c_str(this), id_wire_i1.c_str(this));
        addPip(id(buf), id_wire_i1, src_F, wire_i1_name, delay, Loc(col, row, 0));

        // dummy left pip
        snprintf(buf, 40, "%sF%c", mux_names[j].in_prefix, mux_names[j].in_idx[0]);
        id_src_F = id(buf);
        // LUT8's I0 is wired to the right cell
        int src_col = col;
        if (j == 7) {
            ++src_col;
            delay = getWireTypeDelay(id_I01);
        }
        src_F = wireToGlobal(row, src_col, db, id_src_F);
        snprintf(buf, 40, "R%dC%d_%s_DUMMY_%s", grow, gcol, id_src_F.c_str(this), id_wire_i0.c_str(this));
        addPip(id(buf), id_wire_i0, src_F, wire_i0_name, delay, Loc(col, row, 0));

        // the MUX ports
        snprintf(buf, 40, "R%dC%d_OF%d", grow, gcol, j);
        addBelOutput(belname, id_OF, id(buf));
        snprintf(buf, 40, "R%dC%d_SEL%d", grow, gcol, j);
        addBelInput(belname, id_SEL, id(buf));
        snprintf(buf, 40, "R%dC%d_I0MUX%d", grow, gcol, j);
        addBelInput(belname, id_I0, id(buf));
        snprintf(buf, 40, "R%dC%d_I1MUX%d", grow, gcol, j);
        addBelInput(belname, id_I1, id(buf));
    }
}

void Arch::add_pllvr_ports(DatabasePOD const *db, BelsPOD const *bel, IdString belname, int row, int col)
{
    IdString portname;

    for (int pid :
         {ID_CLKIN,   ID_CLKFB,   ID_FBDSEL0, ID_FBDSEL1, ID_FBDSEL2, ID_FBDSEL3, ID_FBDSEL4, ID_FBDSEL5, ID_IDSEL0,
          ID_IDSEL1,  ID_IDSEL2,  ID_IDSEL3,  ID_IDSEL4,  ID_IDSEL5,  ID_ODSEL0,  ID_ODSEL1,  ID_ODSEL2,  ID_ODSEL3,
          ID_ODSEL4,  ID_ODSEL5,  ID_VREN,    ID_PSDA0,   ID_PSDA1,   ID_PSDA2,   ID_PSDA3,   ID_DUTYDA0, ID_DUTYDA1,
          ID_DUTYDA2, ID_DUTYDA3, ID_FDLY0,   ID_FDLY1,   ID_FDLY2,   ID_FDLY3,   ID_RESET,   ID_RESET_P}) {
        portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, pid)->src_id);
        IdString wire = idf("R%dC%d_%s", row + 1, col + 1, portname.c_str(this));
        if (!wires.count(wire)) {
            GlobalAliasPOD alias;
            alias.dest_col = col;
            alias.dest_row = row;
            alias.dest_id = portname.hash();
            auto alias_src = genericLookup(db->aliases.get(), db->num_aliases, alias, aliasCompare);
            NPNR_ASSERT(alias_src != nullptr);
            int srcrow = alias_src->src_row;
            int srccol = alias_src->src_col;
            IdString srcid = IdString(alias_src->src_id);
            wire = wireToGlobal(srcrow, srccol, db, srcid);
            if (!wires.count(wire)) {
                addWire(wire, srcid, srccol, srcrow);
            }
        }
        addBelInput(belname, IdString(pid), wire);
    }
    for (int pid : {ID_LOCK, ID_CLKOUT, ID_CLKOUTP, ID_CLKOUTD, ID_CLKOUTD3}) {
        portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, pid)->src_id);
        addBelOutput(belname, IdString(pid), idf("R%dC%d_%s", row + 1, col + 1, portname.c_str(this)));
    }
}

void Arch::add_rpll_ports(DatabasePOD const *db, BelsPOD const *bel, IdString belname, int row, int col)
{
    IdString portname;

    for (int pid :
         {ID_CLKIN,   ID_CLKFB,  ID_FBDSEL0, ID_FBDSEL1, ID_FBDSEL2, ID_FBDSEL3, ID_FBDSEL4, ID_FBDSEL5, ID_IDSEL0,
          ID_IDSEL1,  ID_IDSEL2, ID_IDSEL3,  ID_IDSEL4,  ID_IDSEL5,  ID_ODSEL0,  ID_ODSEL1,  ID_ODSEL2,  ID_ODSEL3,
          ID_ODSEL4,  ID_ODSEL5, ID_PSDA0,   ID_PSDA1,   ID_PSDA2,   ID_PSDA3,   ID_DUTYDA0, ID_DUTYDA1, ID_DUTYDA2,
          ID_DUTYDA3, ID_FDLY0,  ID_FDLY1,   ID_FDLY2,   ID_FDLY3,   ID_RESET,   ID_RESET_P}) {
        const PairPOD *port = pairLookup(bel->ports.get(), bel->num_ports, pid);
        // old base
        if (port == nullptr) {
            log_warning("When building nextpnr, obsolete old apicula bases were used. Probably not working properly "
                        "with PLL.\n");
            return;
        }
        portname = IdString(port->src_id);
        IdString wire = idf("R%dC%d_%s", row + 1, col + 1, portname.c_str(this));
        if (!wires.count(wire)) {
            GlobalAliasPOD alias;
            alias.dest_col = col;
            alias.dest_row = row;
            alias.dest_id = portname.hash();
            auto alias_src = genericLookup(db->aliases.get(), db->num_aliases, alias, aliasCompare);
            NPNR_ASSERT(alias_src != nullptr);
            int srcrow = alias_src->src_row;
            int srccol = alias_src->src_col;
            IdString srcid = IdString(alias_src->src_id);
            wire = wireToGlobal(srcrow, srccol, db, srcid);
            if (!wires.count(wire)) {
                addWire(wire, srcid, srccol, srcrow);
            }
        }
        addBelInput(belname, IdString(pid), wire);
    }
    for (int pid : {ID_LOCK, ID_CLKOUT, ID_CLKOUTP, ID_CLKOUTD, ID_CLKOUTD3}) {
        portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, pid)->src_id);
        IdString wire = idf("R%dC%d_%s", row + 1, col + 1, portname.c_str(this));
        if (!wires.count(wire)) {
            GlobalAliasPOD alias;
            alias.dest_col = col;
            alias.dest_row = row;
            alias.dest_id = portname.hash();
            auto alias_src = genericLookup(db->aliases.get(), db->num_aliases, alias, aliasCompare);
            NPNR_ASSERT(alias_src != nullptr);
            int srcrow = alias_src->src_row;
            int srccol = alias_src->src_col;
            IdString srcid = IdString(alias_src->src_id);
            wire = wireToGlobal(srcrow, srccol, db, srcid);
            if (!wires.count(wire)) {
                addWire(wire, srcid, srccol, srcrow);
            }
        }
        addBelOutput(belname, IdString(pid), wire);
    }
}

static bool skip_aux_oser16(std::string device, int row, int col)
{
    if (device == "GW1NSR-4C") {
        switch (col) {
        case 2:  /* fall-through*/
        case 4:  /* fall-through*/
        case 6:  /* fall-through*/
        case 8:  /* fall-through*/
        case 9:  /* fall-through*/
        case 11: /* fall-through*/
        case 13: /* fall-through*/
        case 15: /* fall-through*/
        case 17: /* fall-through*/
        case 18: /* fall-through*/
        case 20: /* fall-through*/
        case 22: /* fall-through*/
        case 24: /* fall-through*/
        case 26: /* fall-through*/
        case 27: /* fall-through*/
        case 29: /* fall-through*/
        case 31: /* fall-through*/
        case 33: /* fall-through*/
        case 35:
            return true;
        default:
            break;
        }
    }
    if (device == "GW1NR-9" || device == "GW1NR-9C") {
        switch (col) {
        case 2:  /* fall-through*/
        case 4:  /* fall-through*/
        case 6:  /* fall-through*/
        case 8:  /* fall-through*/
        case 9:  /* fall-through*/
        case 11: /* fall-through*/
        case 13: /* fall-through*/
        case 15: /* fall-through*/
        case 17: /* fall-through*/
        case 18: /* fall-through*/
        case 19: /* fall-through*/
        case 21: /* fall-through*/
        case 23: /* fall-through*/
        case 25: /* fall-through*/
        case 27: /* fall-through*/
        case 28: /* fall-through*/
        case 29: /* fall-through*/
        case 31: /* fall-through*/
        case 33: /* fall-through*/
        case 35: /* fall-through*/
        case 36: /* fall-through*/
        case 37: /* fall-through*/
        case 39: /* fall-through*/
        case 41: /* fall-through*/
        case 43: /* fall-through*/
        case 45:
            return true;
        default:
            break;
        }
    }
    return false;
}

WireId Arch::get_make_port_wire(const DatabasePOD *db, const BelsPOD *bel, int row, int col, IdString port)
{
    IdString wirename = IdString(pairLookup(bel->ports.get(), bel->num_ports, port.hash())->src_id);
    IdString wire = idf("R%dC%d_%s", row + 1, col + 1, wirename.c_str(this));
    if (!wires.count(wire)) {
        GlobalAliasPOD alias;
        alias.dest_col = col;
        alias.dest_row = row;
        alias.dest_id = port.hash();
        auto alias_src = genericLookup(db->aliases.get(), db->num_aliases, alias, aliasCompare);
        NPNR_ASSERT(alias_src != nullptr);
        int srcrow = alias_src->src_row;
        int srccol = alias_src->src_col;
        IdString srcid = IdString(alias_src->src_id);
        wire = wireToGlobal(srcrow, srccol, db, srcid);
        if (!wires.count(wire)) {
            addWire(wire, srcid, srccol, srcrow);
        }
    }
    return wire;
}

Arch::Arch(ArchArgs args) : args(args)
{
    family = args.family;

    max_clock = 6;
    if (family == "GW1NZ-1") {
        max_clock = 3;
    }

    // Load database
    std::string chipdb = stringf("gowin/chipdb-%s.bin", family.c_str());
    auto db = reinterpret_cast<const DatabasePOD *>(get_chipdb(chipdb));
    if (db == nullptr) {
        log_error("Failed to load chipdb '%s'\n", chipdb.c_str());
    }
    if (db->version != chipdb_version) {
        log_error("Incorrect chipdb version %u is used. Version %u is required\n", db->version, chipdb_version);
    }
    if (db->family.get() != family) {
        log_error("Database is for family '%s' but provided device is family '%s'.\n", db->family.get(),
                  family.c_str());
    }
    // setup id strings
    for (size_t i = 0; i < db->num_ids; i++) {
        IdString::initialize_add(this, db->id_strs[i].get(), uint32_t(i) + db->num_constids);
    }

    // Empty decal
    addDecalGraphic(IdString(), GraphicElement());

    if (args.gui) {
#ifndef NO_GUI
        // decals
        gfxCreateBelDecals(this);
#endif
    }

    // setup package
    IdString package_name;
    IdString device_id;
    IdString speed_id;
    for (unsigned int i = 0; i < db->num_partnumbers; i++) {
        auto partnumber = &db->partnumber_packages[i];
        // std::cout << IdString(partnumber->name_id).str(this) << IdString(partnumber->package_id).str(this) <<
        // std::endl;
        if (IdString(partnumber->name_id) == id(args.partnumber)) {
            package_name = IdString(partnumber->package_id);
            device_id = IdString(partnumber->device_id);
            speed_id = IdString(partnumber->speed_id);
            break;
        }
    }
    if (package_name == IdString()) {
        log_error("Unsupported partnumber '%s'.\n", args.partnumber.c_str());
    }

    // setup timing info
    speed = nullptr;
    for (unsigned int i = 0; i < db->num_speeds; i++) {
        const TimingClassPOD *tc = &db->speeds[i];
        // std::cout << IdString(tc->name_id).str(this) << std::endl;
        if (IdString(tc->name_id) == speed_id) {
            speed = tc->groups.get();
            break;
        }
    }
    if (speed == nullptr) {
        log_error("Unsupported speed grade '%s'.\n", speed_id.c_str(this));
    }

    const VariantPOD *variant = nullptr;
    for (unsigned int i = 0; i < db->num_variants; i++) {
        auto var = &db->variants[i];
        // std::cout << IdString(var->name_id).str(this) << std::endl;
        if (IdString(var->name_id) == device_id) {
            variant = var;
            break;
        }
    }
    if (variant == nullptr) {
        log_error("Unsupported device grade '%s'.\n", device_id.c_str(this));
    }

    package = nullptr;
    for (unsigned int i = 0; i < variant->num_packages; i++) {
        auto pkg = &variant->packages[i];
        // std::cout << IdString(pkg->name_id).str(this) << std::endl;
        if (IdString(pkg->name_id) == package_name) {
            package = pkg;
            break;
        }
        // for (int j=0; j < pkg->num_pins; j++) {
        //     auto pin = pkg->pins[j];
        //     std::cout << IdString(pin.src_id).str(this) << " " << IdString(pin.dest_id).str(this) << std::endl;
        // }
    }

    if (package == nullptr) {
        log_error("Unsupported package '%s'.\n", package_name.c_str(this));
    }

    //
    log_info("Series:%s Device:%s Package:%s Speed:%s\n", family.c_str(), device_id.c_str(this),
             package_name.c_str(this), speed_id.c_str(this));

    device = device_id.str(this);

    // setup db
    // add global VCC and GND bels
    addBel(id_GND, id_GND, Loc(0, 0, BelZ::gnd_0_z), true);
    addWire(id_VSS, id_VSS, 0, 0);
    addBelOutput(id_GND, id_G, id_VSS);
    addBel(id_VCC, id_VCC, Loc(0, 0, BelZ::vcc_0_z), true);
    addWire(id_VCC, id_VCC, 0, 0);
    addBelOutput(id_VCC, id_V, id_VCC);
    char buf[32];
    // The reverse order of the enumeration simplifies the creation
    // of MUX2_LUT8s: they need the existence of the wire on the right.
    for (int i = db->rows * db->cols - 1; i >= 0; --i) {
        IdString grpname;
        int row = i / db->cols;
        int col = i % db->cols;
        const TilePOD *tile = db->grid[i].get();
        if (args.gui) {
#ifndef NO_GUI
            // CRU decal
            snprintf(buf, 32, "R%dC%d_CRU", row + 1, col + 1);
            grpname = id(buf);
            addGroup(grpname);
            setGroupDecal(grpname, gfxGetCruGroupDecalXY(col, row));
#endif
        }
        // setup wires
        const PairPOD *pips[2] = {tile->pips.get(), tile->clock_pips.get()};
        unsigned int num_pips[2] = {tile->num_pips, tile->num_clock_pips};
        for (int p = 0; p < 2; p++) {
            for (unsigned int j = 0; j < num_pips[p]; j++) {
                const PairPOD pip = pips[p][j];
                int destrow = row;
                int destcol = col;
                IdString destid(pip.dest_id), gdestid(pip.dest_id);
                IdString gdestname = wireToGlobal(destrow, destcol, db, gdestid);
                if (!wires.count(gdestname))
                    addWire(gdestname, destid, destcol, destrow);
                int srcrow = row;
                int srccol = col;
                IdString srcid(pip.src_id), gsrcid(pip.src_id);
                IdString gsrcname = wireToGlobal(srcrow, srccol, db, gsrcid);
                if (!wires.count(gsrcname))
                    addWire(gsrcname, srcid, srccol, srcrow);
            }
        }
        for (unsigned int j = 0; j < tile->num_bels; j++) {
            const BelsPOD *bel = &tile->bels[j];
            IdString belname;
            IdString portname;
            int z = 0;
            bool dff = true;
            switch (static_cast<ConstIds>(bel->type_id)) {
            case ID_PLLVR:
                belname = idf("R%dC%d_PLLVR", row + 1, col + 1);
                addBel(belname, id_PLLVR, Loc(col, row, BelZ::pllvr_z), false);
                add_pllvr_ports(db, bel, belname, row, col);
                break;
            case ID_RPLLA:
                belname = idf("R%dC%d_rPLL", row + 1, col + 1);
                addBel(belname, id_rPLL, Loc(col, row, BelZ::pll_z), false);
                add_rpll_ports(db, bel, belname, row, col);
                break;
            case ID_BUFS7:
                z++; /* fall-through*/
            case ID_BUFS6:
                z++; /* fall-through*/
            case ID_BUFS5:
                z++; /* fall-through*/
            case ID_BUFS4:
                z++; /* fall-through*/
            case ID_BUFS3:
                z++; /* fall-through*/
            case ID_BUFS2:
                z++; /* fall-through*/
            case ID_BUFS1:
                z++; /* fall-through*/
            case ID_BUFS0:
                snprintf(buf, 32, "R%dC%d_BUFS%d", row + 1, col + 1, z);
                belname = id(buf);
                addBel(belname, id_BUFS, Loc(col, row, BelZ::bufs_0_z + z), false);
                portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_I)->src_id);
                snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this));
                addBelInput(belname, id_I, id(buf));
                portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_O)->src_id);
                snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this));
                addBelOutput(belname, id_O, id(buf));
                break;
            case ID_GSR0:
                snprintf(buf, 32, "R%dC%d_GSR0", row + 1, col + 1);
                belname = id(buf);
                addBel(belname, id_GSR, Loc(col, row, 0), false);
                portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_GSRI)->src_id);
                snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this));
                addBelInput(belname, id_GSRI, id(buf));
                break;
            case ID_OSC:  /* fall-through*/
            case ID_OSCH: /* fall-through*/
            case ID_OSCW:
                belname = idf("R%dC%d_%s", row + 1, col + 1, IdString(bel->type_id).c_str(this));
                addBel(belname, IdString(bel->type_id), Loc(col, row, BelZ::osc_z), false);
                addBelOutput(belname, id_OSCOUT, get_make_port_wire(db, bel, row, col, id_OSCOUT));
                break;
            case ID_OSCF: /* fall-through*/
            case ID_OSCZ: /* fall-through*/
            case ID_OSCO:
                belname = idf("R%dC%d_%s", row + 1, col + 1, IdString(bel->type_id).c_str(this));
                addBel(belname, IdString(bel->type_id), Loc(col, row, BelZ::osc_z), false);
                addBelOutput(belname, id_OSCOUT, get_make_port_wire(db, bel, row, col, id_OSCOUT));
                addBelInput(belname, id_OSCEN, get_make_port_wire(db, bel, row, col, id_OSCEN));
                break;
            case ID_RAM16:
                snprintf(buf, 32, "R%dC%d_RAMW", row + 1, col + 1);
                belname = id(buf);
                addBel(belname, id_RAMW, Loc(col, row, BelZ::lutram_0_z), false);

                snprintf(buf, 32, "R%dC%d_A%d", row + 1, col + 1, 4);
                addBelInput(belname, id_A4, id(buf));
                snprintf(buf, 32, "R%dC%d_B%d", row + 1, col + 1, 4);
                addBelInput(belname, id_B4, id(buf));
                snprintf(buf, 32, "R%dC%d_C%d", row + 1, col + 1, 4);
                addBelInput(belname, id_C4, id(buf));
                snprintf(buf, 32, "R%dC%d_D%d", row + 1, col + 1, 4);
                addBelInput(belname, id_D4, id(buf));

                snprintf(buf, 32, "R%dC%d_A%d", row + 1, col + 1, 5);
                addBelInput(belname, id_A5, id(buf));
                snprintf(buf, 32, "R%dC%d_B%d", row + 1, col + 1, 5);
                addBelInput(belname, id_B5, id(buf));
                snprintf(buf, 32, "R%dC%d_C%d", row + 1, col + 1, 5);
                addBelInput(belname, id_C5, id(buf));
                snprintf(buf, 32, "R%dC%d_D%d", row + 1, col + 1, 5);
                addBelInput(belname, id_D5, id(buf));

                snprintf(buf, 32, "R%dC%d_CLK%d", row + 1, col + 1, 2);
                addBelInput(belname, id_CLK, id(buf));
                snprintf(buf, 32, "R%dC%d_LSR%d", row + 1, col + 1, 2);
                addBelInput(belname, id_LSR, id(buf));
                snprintf(buf, 32, "R%dC%d_CE%d", row + 1, col + 1, 2);
                addBelInput(belname, id_CE, id(buf));
                break;
            // fall through the ++
            case ID_LUT7:
                z++;
                dff = false; /* fall-through*/
            case ID_LUT6:
                z++;
                dff = false; /* fall-through*/
            case ID_LUT5:
                z++; /* fall-through*/
            case ID_LUT4:
                z++; /* fall-through*/
            case ID_LUT3:
                z++; /* fall-through*/
            case ID_LUT2:
                z++; /* fall-through*/
            case ID_LUT1:
                z++; /* fall-through*/
            case ID_LUT0:
                // common LUT+DFF code
                snprintf(buf, 32, "R%dC%d_SLICE%d", row + 1, col + 1, z);
                belname = id(buf);
                addBel(belname, id_SLICE, Loc(col, row, z), false);
                snprintf(buf, 32, "R%dC%d_F%d", row + 1, col + 1, z);
                addBelOutput(belname, id_F, id(buf));
                snprintf(buf, 32, "R%dC%d_A%d", row + 1, col + 1, z);
                addBelInput(belname, id_A, id(buf));
                snprintf(buf, 32, "R%dC%d_B%d", row + 1, col + 1, z);
                addBelInput(belname, id_B, id(buf));
                snprintf(buf, 32, "R%dC%d_C%d", row + 1, col + 1, z);
                addBelInput(belname, id_C, id(buf));
                snprintf(buf, 32, "R%dC%d_D%d", row + 1, col + 1, z);
                addBelInput(belname, id_D, id(buf));
                if (dff) {
                    snprintf(buf, 32, "R%dC%d_CLK%d", row + 1, col + 1, z / 2);
                    addBelInput(belname, id_CLK, id(buf));
                    snprintf(buf, 32, "R%dC%d_LSR%d", row + 1, col + 1, z / 2);
                    addBelInput(belname, id_LSR, id(buf));
                    snprintf(buf, 32, "R%dC%d_CE%d", row + 1, col + 1, z / 2);
                    addBelInput(belname, id_CE, id(buf));
                    snprintf(buf, 32, "R%dC%d_Q%d", row + 1, col + 1, z);
                    addBelOutput(belname, id_Q, id(buf));
                }
                if (z == 0) {
                    addMuxBels(db, row, col);
                }
                if (z % 2 == 0) {
                    snprintf(buf, 32, "R%dC%d_LUT_GRP%d", row + 1, col + 1, z);
                    grpname = id(buf);
                    if (args.gui) {
#ifndef NO_GUI
                        addGroup(grpname);
                        setGroupDecal(grpname, gfxGetLutGroupDecalXY(col, row, z >> 1));
#endif
                    }
                }
                break;
            case ID_IOBJ:
                z++; /* fall-through*/
            case ID_IOBI:
                z++; /* fall-through*/
            case ID_IOBH:
                z++; /* fall-through*/
            case ID_IOBG:
                z++; /* fall-through*/
            case ID_IOBF:
                z++; /* fall-through*/
            case ID_IOBE:
                z++; /* fall-through*/
            case ID_IOBD:
                z++; /* fall-through*/
            case ID_IOBC:
                z++; /* fall-through*/
            case ID_IOBB:
                z++; /* fall-through*/
            case ID_IOBA: {
                snprintf(buf, 32, "R%dC%d_IOB%c", row + 1, col + 1, 'A' + z);
                belname = id(buf);
                addBel(belname, id_IOB, Loc(col, row, z), false);
                portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_O)->src_id);
                snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this));
                addBelOutput(belname, id_O, id(buf));
                portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_I)->src_id);
                snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this));
                addBelInput(belname, id_I, id(buf));
                portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_OE)->src_id);
                snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this));
                addBelInput(belname, id_OEN, id(buf));
                // GW1NR-9 quirk
                const PairPOD *quirk_port = pairLookup(bel->ports.get(), bel->num_ports, ID_GW9_ALWAYS_LOW0);
                if (quirk_port != nullptr) {
                    gw1n9_quirk = true;
                    portname = IdString(quirk_port->src_id);
                    snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this));
                    addBelInput(belname, id_GW9_ALWAYS_LOW0, id(buf));
                }
                quirk_port = pairLookup(bel->ports.get(), bel->num_ports, ID_GW9_ALWAYS_LOW1);
                if (quirk_port != nullptr) {
                    gw1n9_quirk = true;
                    portname = IdString(quirk_port->src_id);
                    snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this));
                    addBelInput(belname, id_GW9_ALWAYS_LOW1, id(buf));
                }
                if (!z && device_id == id("GW1NR-9C")) {
                    addBelInput(belname, id_GW9C_ALWAYS_LOW0, idf("R%dC%d_C6", row + 1, col + 1));
                    addBelInput(belname, id_GW9C_ALWAYS_LOW1, idf("R%dC%d_D6", row + 1, col + 1));
                }
            } break;
                // Simplified IO
            case ID_IOBJS:
                z++; /* fall-through*/
            case ID_IOBIS:
                z++; /* fall-through*/
            case ID_IOBHS:
                z++; /* fall-through*/
            case ID_IOBGS:
                z++; /* fall-through*/
            case ID_IOBFS:
                z++; /* fall-through*/
            case ID_IOBES:
                z++; /* fall-through*/
            case ID_IOBDS:
                z++; /* fall-through*/
            case ID_IOBCS:
                z++; /* fall-through*/
            case ID_IOBBS:
                z++; /* fall-through*/
            case ID_IOBAS:
                snprintf(buf, 32, "R%dC%d_IOB%c", row + 1, col + 1, 'A' + z);
                belname = id(buf);
                addBel(belname, id_IOBS, Loc(col, row, z), false);
                portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_O)->src_id);
                snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this));
                addBelOutput(belname, id_O, id(buf));
                portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_I)->src_id);
                snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this));
                addBelInput(belname, id_I, id(buf));
                portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_OE)->src_id);
                snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this));
                addBelInput(belname, id_OEN, id(buf));
                break;

                // IO logic
            case ID_IOLOGICB:
                z++; /* fall-through*/
            case ID_IOLOGICA: {
                belname = idf("R%dC%d_IOLOGIC%c", row + 1, col + 1, 'A' + z);
                addBel(belname, id_IOLOGIC, Loc(col, row, BelZ::iologic_z + z), false);

                IdString const iologic_in_ports[] = {id_TX,    id_TX0,  id_TX1,   id_TX2,    id_TX3,   id_RESET,
                                                     id_CALIB, id_PCLK, id_D,     id_D0,     id_D1,    id_D2,
                                                     id_D3,    id_D4,   id_D5,    id_D6,     id_D7,    id_D8,
                                                     id_D9,    id_CLK,  id_CLEAR, id_DAADJ0, id_DAADJ1};
                for (IdString port : iologic_in_ports) {
                    const PairPOD *portid = pairLookup(bel->ports.get(), bel->num_ports, port.hash());
                    if (portid != nullptr) {
                        portname = IdString(portid->src_id);
                        addBelInput(belname, port, idf("R%dC%d_%s", row + 1, col + 1, portname.c_str(this)));
                    }
                }
                IdString const iologic_out_ports[] = {id_Q,  id_Q0, id_Q1, id_Q2, id_Q3, id_Q4,
                                                      id_Q5, id_Q6, id_Q7, id_Q8, id_Q9};
                for (IdString port : iologic_out_ports) {
                    portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, port.hash())->src_id);
                    addBelOutput(belname, port, idf("R%dC%d_%s", row + 1, col + 1, portname.c_str(this)));
                }
                auto fclk = pairLookup(bel->ports.get(), bel->num_ports, ID_FCLK);
                // XXX as long as there is no special processing of the pins
                if (fclk != nullptr) {
                    portname = IdString(fclk->src_id);
                    IdString wire = idf("R%dC%d_%s", row + 1, col + 1, portname.c_str(this));
                    if (!wires.count(wire)) {
                        GlobalAliasPOD alias;
                        alias.dest_col = col;
                        alias.dest_row = row;
                        alias.dest_id = portname.hash();
                        auto alias_src = genericLookup(db->aliases.get(), db->num_aliases, alias, aliasCompare);
                        if (alias_src != nullptr) {
                            int srcrow = alias_src->src_row;
                            int srccol = alias_src->src_col;
                            IdString srcid = IdString(alias_src->src_id);
                            wire = wireToGlobal(srcrow, srccol, db, srcid);
                            if (!wires.count(wire)) {
                                addWire(wire, srcid, srccol, srcrow);
                            }
                            addBelInput(belname, id_FCLK, wire);
                        }
                        // XXX here we are creating an
                        // IOLOGIC with a missing FCLK input. This is so
                        // because bels with the same type can be placed in
                        // on the chip where there is no pin, so no
                        // IOLOGIC makes sense. But since each type is
                        // described only once in the database we can't really
                        // mark these special bel somehow.
                        // By creating an IOLOGIC without an FCLK input we
                        // create a routing error later, so that "bad"
                        // locations are handled.
                    } else {
                        addBelInput(belname, id_FCLK, idf("R%dC%d_%s", row + 1, col + 1, portname.c_str(this)));
                    }
                }
            } break;
            case ID_OSER16: {
                if (skip_aux_oser16(device, row, col)) {
                    break;
                }
                belname = idf("R%dC%d_OSER16", row + 1, col + 1);
                addBel(belname, id_OSER16, Loc(col, row, BelZ::oser16_z), false);

                const IdString oser16_in_ports[] = {id_RESET, id_PCLK, id_D0,  id_D1,  id_D2,  id_D3,
                                                    id_D4,    id_D5,   id_D6,  id_D7,  id_D8,  id_D9,
                                                    id_D10,   id_D11,  id_D12, id_D13, id_D14, id_D15};
                for (IdString port : oser16_in_ports) {
                    portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, port.hash())->src_id);
                    addBelInput(belname, port, idf("R%dC%d_%s", row + 1, col + 1, portname.c_str(this)));
                }
                portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, id_Q0.hash())->src_id);
                addBelOutput(belname, id_Q, idf("R%dC%d_%s", row + 1, col + 1, portname.c_str(this)));
                auto fclk = pairLookup(bel->ports.get(), bel->num_ports, ID_FCLK);
                // XXX as long as there is no special processing of the pins
                if (fclk != nullptr) {
                    portname = IdString(fclk->src_id);
                    IdString wire = idf("R%dC%d_%s", row + 1, col + 1, portname.c_str(this));
                    if (!wires.count(wire)) {
                        GlobalAliasPOD alias;
                        alias.dest_col = col;
                        alias.dest_row = row;
                        alias.dest_id = portname.hash();
                        auto alias_src = genericLookup(db->aliases.get(), db->num_aliases, alias, aliasCompare);
                        if (alias_src != nullptr) {
                            int srcrow = alias_src->src_row;
                            int srccol = alias_src->src_col;
                            IdString srcid = IdString(alias_src->src_id);
                            wire = wireToGlobal(srcrow, srccol, db, srcid);
                            if (!wires.count(wire)) {
                                addWire(wire, srcid, srccol, srcrow);
                            }
                            addBelInput(belname, id_FCLK, wire);
                        }
                    } else {
                        addBelInput(belname, id_FCLK, idf("R%dC%d_%s", row + 1, col + 1, portname.c_str(this)));
                    }
                }
            } break;
            case ID_IDES16: {
                if (skip_aux_oser16(device, row, col)) {
                    break;
                }
                belname = idf("R%dC%d_IDES16", row + 1, col + 1);
                addBel(belname, id_IDES16, Loc(col, row, BelZ::ides16_z), false);

                IdString const ides16_in_ports[] = {id_RESET, id_PCLK, id_CALIB, id_D};
                for (IdString port : ides16_in_ports) {
                    portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, port.hash())->src_id);
                    addBelInput(belname, port, idf("R%dC%d_%s", row + 1, col + 1, portname.c_str(this)));
                }
                IdString const ides16_out_ports[] = {id_Q0, id_Q1, id_Q2,  id_Q3,  id_Q4,  id_Q5,  id_Q6,  id_Q7,
                                                     id_Q8, id_Q9, id_Q10, id_Q11, id_Q12, id_Q13, id_Q14, id_Q15};
                for (IdString port : ides16_out_ports) {
                    portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, port.hash())->src_id);
                    addBelOutput(belname, port, idf("R%dC%d_%s", row + 1, col + 1, portname.c_str(this)));
                }
                auto fclk = pairLookup(bel->ports.get(), bel->num_ports, ID_FCLK);
                // XXX as long as there is no special processing of the pins
                if (fclk != nullptr) {
                    portname = IdString(fclk->src_id);
                    IdString wire = idf("R%dC%d_%s", row + 1, col + 1, portname.c_str(this));
                    if (!wires.count(wire)) {
                        GlobalAliasPOD alias;
                        alias.dest_col = col;
                        alias.dest_row = row;
                        alias.dest_id = portname.hash();
                        auto alias_src = genericLookup(db->aliases.get(), db->num_aliases, alias, aliasCompare);
                        if (alias_src != nullptr) {
                            int srcrow = alias_src->src_row;
                            int srccol = alias_src->src_col;
                            IdString srcid = IdString(alias_src->src_id);
                            wire = wireToGlobal(srcrow, srccol, db, srcid);
                            if (!wires.count(wire)) {
                                addWire(wire, srcid, srccol, srcrow);
                            }
                            addBelInput(belname, id_FCLK, wire);
                        }
                    } else {
                        addBelInput(belname, id_FCLK, idf("R%dC%d_%s", row + 1, col + 1, portname.c_str(this)));
                    }
                }
            } break;
            default:
                break;
            }
        }
    }

    // IO pin configs
    for (unsigned int i = 0; i < package->num_pins; i++) {
        const PinPOD *pin = &package->pins[i];
        if (pin->num_cfgs == 0) {
            continue;
        }
        auto b = bels.find(IdString(pin->loc_id));
        if (b == bels.end()) {
            // Not all pins are transmitted, e.g. MODE, DONE etc.
            continue;
        }
        std::vector<IdString> &cfgs = b->second.pin_cfgs;
        for (unsigned int j = 0; j < pin->num_cfgs; ++j) {
            cfgs.push_back(IdString(pin->cfgs[j]));
        }
    }

    // setup pips
    for (int i = 0; i < db->rows * db->cols; i++) {
        int row = i / db->cols;
        int col = i % db->cols;
        const TilePOD *tile = db->grid[i].get();
        const PairPOD *pips[2] = {tile->pips.get(), tile->clock_pips.get()};
        unsigned int num_pips[2] = {tile->num_pips, tile->num_clock_pips};
        for (int p = 0; p < 2; p++) {
            for (unsigned int j = 0; j < num_pips[p]; j++) {
                const PairPOD pip = pips[p][j];
                int destrow = row;
                int destcol = col;
                IdString destid(pip.dest_id), gdestid(pip.dest_id);
                IdString gdestname = wireToGlobal(destrow, destcol, db, gdestid);
                int srcrow = row;
                int srccol = col;
                IdString srcid(pip.src_id), gsrcid(pip.src_id);
                IdString gsrcname = wireToGlobal(srcrow, srccol, db, gsrcid);

                snprintf(buf, 32, "R%dC%d_%s_%s", row + 1, col + 1, srcid.c_str(this), destid.c_str(this));
                IdString pipname = id(buf);
                DelayQuad delay = getWireTypeDelay(destid);
                // global alias
                srcid = IdString(pip.src_id);
                GlobalAliasPOD alias;
                alias.dest_col = srccol;
                alias.dest_row = srcrow;
                alias.dest_id = srcid.index;
                auto alias_src = genericLookup(db->aliases.get(), db->num_aliases, alias, aliasCompare);
                if (alias_src != nullptr) {
                    srccol = alias_src->src_col;
                    srcrow = alias_src->src_row;
                    srcid = IdString(alias_src->src_id);
                    gsrcname = wireToGlobal(srcrow, srccol, db, srcid);
                }
                addPip(pipname, destid, gsrcname, gdestname, delay, Loc(col, row, j));
            }
        }
    }
    if (args.gui) {
        setDefaultDecals();
    }

    // Permissible combinations of modes in a single slice
    dff_comp_mode[id_DFF] = id_DFF;
    dff_comp_mode[id_DFFE] = id_DFFE;
    dff_comp_mode[id_DFFS] = id_DFFR;
    dff_comp_mode[id_DFFR] = id_DFFS;
    dff_comp_mode[id_DFFSE] = id_DFFRE;
    dff_comp_mode[id_DFFRE] = id_DFFSE;
    dff_comp_mode[id_DFFP] = id_DFFC;
    dff_comp_mode[id_DFFC] = id_DFFP;
    dff_comp_mode[id_DFFPE] = id_DFFCE;
    dff_comp_mode[id_DFFCE] = id_DFFPE;
    dff_comp_mode[id_DFFNS] = id_DFFNR;
    dff_comp_mode[id_DFFNR] = id_DFFNS;
    dff_comp_mode[id_DFFNSE] = id_DFFNRE;
    dff_comp_mode[id_DFFNRE] = id_DFFNSE;
    dff_comp_mode[id_DFFNP] = id_DFFNC;
    dff_comp_mode[id_DFFNC] = id_DFFNP;
    dff_comp_mode[id_DFFNPE] = id_DFFNCE;
    dff_comp_mode[id_DFFNCE] = id_DFFNPE;

    BaseArch::init_cell_types();
    BaseArch::init_bel_buckets();
}

void IdString::initialize_arch(const BaseCtx *ctx)
{
#define X(t) initialize_add(ctx, #t, ID_##t);
#include "constids.inc"
#undef X
}

// ---------------------------------------------------------------

BelId Arch::getBelByName(IdStringList name) const
{
    if (bels.count(name[0]))
        return name[0];
    return BelId();
}

IdStringList Arch::getBelName(BelId bel) const { return IdStringList(bel); }

Loc Arch::getBelLocation(BelId bel) const
{
    auto &info = bels.at(bel);
    return Loc(info.x, info.y, info.z);
}

BelId Arch::getBelByLocation(Loc loc) const
{
    auto it = bel_by_loc.find(loc);
    if (it != bel_by_loc.end())
        return it->second;
    return BelId();
}

const std::vector<BelId> &Arch::getBelsByTile(int x, int y) const { return bels_by_tile.at(x).at(y); }

bool Arch::haveBelType(int x, int y, IdString bel_type)
{
    for (auto bel : getBelsByTile(x, y)) {
        BelInfo bi = bel_info(bel);
        if (bi.type == bel_type) {
            return true;
        }
    }
    return false;
}

bool Arch::getBelGlobalBuf(BelId bel) const { return bels.at(bel).gb; }

void Arch::bindBel(BelId bel, CellInfo *cell, PlaceStrength strength)
{
    bels.at(bel).bound_cell = cell;
    cell->bel = bel;
    cell->belStrength = strength;
    refreshUiBel(bel);
}

void Arch::unbindBel(BelId bel)
{
    bels.at(bel).bound_cell->bel = BelId();
    bels.at(bel).bound_cell->belStrength = STRENGTH_NONE;
    bels.at(bel).bound_cell = nullptr;
    refreshUiBel(bel);
}

bool Arch::checkBelAvail(BelId bel) const { return bels.at(bel).bound_cell == nullptr; }

CellInfo *Arch::getBoundBelCell(BelId bel) const { return bels.at(bel).bound_cell; }

CellInfo *Arch::getConflictingBelCell(BelId bel) const { return bels.at(bel).bound_cell; }

const std::vector<BelId> &Arch::getBels() const { return bel_ids; }

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
{
    const auto &bdata = bels.at(bel);
    if (!bdata.pins.count(pin))
        log_error("bel '%s' has no pin '%s'\n", bel.c_str(this), pin.c_str(this));
    return bdata.pins.at(pin).wire;
}

PortType Arch::getBelPinType(BelId bel, IdString pin) const { return bels.at(bel).pins.at(pin).type; }

std::vector<IdString> Arch::getBelPins(BelId bel) const
{
    std::vector<IdString> ret;
    for (auto &it : bels.at(bel).pins)
        ret.push_back(it.first);
    return ret;
}

std::array<IdString, 1> Arch::getBelPinsForCellPin(const CellInfo *cell_info, IdString pin) const { return {pin}; }

// ---------------------------------------------------------------

WireId Arch::getWireByName(IdStringList name) const
{
    if (wires.count(name[0]))
        return name[0];
    return WireId();
}

IdStringList Arch::getWireName(WireId wire) const { return IdStringList(wire); }

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; }

void Arch::bindWire(WireId wire, NetInfo *net, PlaceStrength strength)
{
    wires.at(wire).bound_net = net;
    net->wires[wire].pip = PipId();
    net->wires[wire].strength = strength;
    refreshUiWire(wire);
}

void Arch::unbindWire(WireId wire)
{
    auto &net_wires = wires.at(wire).bound_net->wires;

    auto pip = net_wires.at(wire).pip;
    if (pip != PipId()) {
        pips.at(pip).bound_net = nullptr;
        refreshUiPip(pip);
    }

    net_wires.erase(wire);
    wires.at(wire).bound_net = nullptr;
    refreshUiWire(wire);
}

bool Arch::checkWireAvail(WireId wire) const { return wires.at(wire).bound_net == nullptr; }

NetInfo *Arch::getBoundWireNet(WireId wire) const { return wires.at(wire).bound_net; }

NetInfo *Arch::getConflictingWireNet(WireId wire) const { return wires.at(wire).bound_net; }

const std::vector<BelPin> &Arch::getWireBelPins(WireId wire) const { return wires.at(wire).bel_pins; }

const std::vector<WireId> &Arch::getWires() const { return wire_ids; }

// ---------------------------------------------------------------

PipId Arch::getPipByName(IdStringList name) const
{
    if (pips.count(name[0]))
        return name[0];
    return PipId();
}

IdStringList Arch::getPipName(PipId pip) const { return IdStringList(pip); }

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; }

void Arch::bindPip(PipId pip, NetInfo *net, PlaceStrength strength)
{
    WireId wire = pips.at(pip).dstWire;
    pips.at(pip).bound_net = net;
    wires.at(wire).bound_net = net;
    net->wires[wire].pip = pip;
    net->wires[wire].strength = strength;
    refreshUiPip(pip);
    refreshUiWire(wire);
}

void Arch::unbindPip(PipId pip)
{
    WireId wire = pips.at(pip).dstWire;
    wires.at(wire).bound_net->wires.erase(wire);
    pips.at(pip).bound_net = nullptr;
    wires.at(wire).bound_net = nullptr;
    refreshUiPip(pip);
    refreshUiWire(wire);
}

bool Arch::checkPipAvail(PipId pip) const { return pips.at(pip).bound_net == nullptr; }

NetInfo *Arch::getBoundPipNet(PipId pip) const { return pips.at(pip).bound_net; }

NetInfo *Arch::getConflictingPipNet(PipId pip) const { return pips.at(pip).bound_net; }

WireId Arch::getConflictingPipWire(PipId pip) const { return pips.at(pip).bound_net ? pips.at(pip).dstWire : WireId(); }

const std::vector<PipId> &Arch::getPips() const { return pip_ids; }

Loc Arch::getPipLocation(PipId pip) const { return pips.at(pip).loc; }

WireId Arch::getPipSrcWire(PipId pip) const { return pips.at(pip).srcWire; }

WireId Arch::getPipDstWire(PipId pip) const { return pips.at(pip).dstWire; }

DelayQuad Arch::getPipDelay(PipId pip) const { return pips.at(pip).delay; }

const std::vector<PipId> &Arch::getPipsDownhill(WireId wire) const { return wires.at(wire).downhill; }

const std::vector<PipId> &Arch::getPipsUphill(WireId wire) const { return wires.at(wire).uphill; }

// ---------------------------------------------------------------

GroupId Arch::getGroupByName(IdStringList name) const { return name[0]; }

IdStringList Arch::getGroupName(GroupId group) const { return IdStringList(group); }

std::vector<GroupId> Arch::getGroups() const
{
    std::vector<GroupId> ret;
    for (auto &it : groups)
        ret.push_back(it.first);
    return ret;
}

const std::vector<BelId> &Arch::getGroupBels(GroupId group) const { return groups.at(group).bels; }

const std::vector<WireId> &Arch::getGroupWires(GroupId group) const { return groups.at(group).wires; }

const std::vector<PipId> &Arch::getGroupPips(GroupId group) const { return groups.at(group).pips; }

const std::vector<GroupId> &Arch::getGroupGroups(GroupId group) const { return groups.at(group).groups; }

// ---------------------------------------------------------------

delay_t Arch::estimateDelay(WireId src, WireId dst) const
{
    const WireInfo &s = wires.at(src);
    const WireInfo &d = wires.at(dst);
    int dx = abs(s.x - d.x);
    int dy = abs(s.y - d.y);
    return (dx + dy) * args.delayScale + args.delayOffset;
}

delay_t Arch::predictDelay(BelId src_bel, IdString src_pin, BelId dst_bel, IdString dst_pin) const
{
    NPNR_UNUSED(src_pin);
    NPNR_UNUSED(dst_pin);
    auto driver_loc = getBelLocation(src_bel);
    auto sink_loc = getBelLocation(dst_bel);

    int dx = abs(sink_loc.x - driver_loc.x);
    int dy = abs(sink_loc.y - driver_loc.y);
    return (dx + dy) * args.delayScale + args.delayOffset;
}

bool Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const { return false; }

BoundingBox Arch::getRouteBoundingBox(WireId src, WireId dst) const
{
    BoundingBox bb;

    int src_x = wires.at(src).x;
    int src_y = wires.at(src).y;
    int dst_x = wires.at(dst).x;
    int dst_y = wires.at(dst).y;

    bb.x0 = src_x;
    bb.y0 = src_y;
    bb.x1 = src_x;
    bb.y1 = src_y;

    auto extend = [&](int x, int y) {
        bb.x0 = std::min(bb.x0, x);
        bb.x1 = std::max(bb.x1, x);
        bb.y0 = std::min(bb.y0, y);
        bb.y1 = std::max(bb.y1, y);
    };
    extend(dst_x, dst_y);
    return bb;
}

// ---------------------------------------------------------------

bool Arch::place()
{
    std::string placer = str_or_default(settings, id_placer, defaultPlacer);
    bool retVal;
    if (placer == "heap") {
        bool have_iobuf_or_constr = false;
        for (auto &cell : cells) {
            CellInfo *ci = cell.second.get();
            if (ci->type == id_IOB || ci->bel != BelId() || ci->attrs.count(id_BEL)) {
                have_iobuf_or_constr = true;
                break;
            }
        }
        if (!have_iobuf_or_constr) {
            log_warning("Unable to use HeAP due to a lack of IO buffers or constrained cells as anchors; reverting to "
                        "SA.\n");
            retVal = placer1(getCtx(), Placer1Cfg(getCtx()));
        } else {
            PlacerHeapCfg cfg(getCtx());
            cfg.ioBufTypes.insert(id_IOB);
            cfg.beta = 0.5;
            retVal = placer_heap(getCtx(), cfg);
        }
        getCtx()->settings[id_place] = 1;
        archInfoToAttributes();
    } else if (placer == "sa") {
        retVal = placer1(getCtx(), Placer1Cfg(getCtx()));
        getCtx()->settings[id_place] = 1;
        archInfoToAttributes();
        return retVal;
    } else {
        log_error("Gowin architecture does not support placer '%s'\n", placer.c_str());
    }
    // debug placement
    if (getCtx()->debug) {
        for (auto &cell : getCtx()->cells) {
            log_info("Placed: %s -> %s\n", cell.first.c_str(getCtx()), getCtx()->nameOfBel(cell.second->bel));
        }
    }
    return retVal;
}

static bool is_spec_iob(const Context *ctx, const CellInfo *cell, IdString pin_name)
{
    if (!is_iob(ctx, cell)) {
        return false;
    }
    std::vector<IdString> const &cfgs = ctx->bels.at(cell->bel).pin_cfgs;
    bool have_pin = std::find(cfgs.begin(), cfgs.end(), pin_name) != cfgs.end();
    return have_pin;
}

static bool is_RPLL_T_IN_iob(const Context *ctx, const CellInfo *cell)
{
    return is_spec_iob(ctx, cell, ctx->id("RPLL_T_IN"));
}

static bool is_LPLL_T_IN_iob(const Context *ctx, const CellInfo *cell)
{
    return is_spec_iob(ctx, cell, ctx->id("LPLL_T_IN"));
}

static bool is_RPLL_T_FB_iob(const Context *ctx, const CellInfo *cell)
{
    return is_spec_iob(ctx, cell, ctx->id("RPLL_T_FB"));
}

static bool is_LPLL_T_FB_iob(const Context *ctx, const CellInfo *cell)
{
    return is_spec_iob(ctx, cell, ctx->id("LPLL_T_FB"));
}

bool Arch::is_GCLKT_iob(const CellInfo *cell)
{
    for (int i = 0; i < 6; ++i) {
        if (is_spec_iob(getCtx(), cell, idf("GCLKT_%d", i))) {
            return true;
        }
    }
    return false;
}

void Arch::bind_pll_to_bel(CellInfo *ci, PLL loc)
{
    BelId bel;
    switch (ci->type.hash()) {
    case ID_PLLVR:
        bel = loc == PLL::left ? id("R1C28_PLLVR") : id("R1C37_PLLVR");
        break;
    case ID_rPLL:
        if (family == "GW1N-1" || family == "GW1NZ-1") {
            if (loc == PLL::left) {
                return;
            }
            bel = id("R1C18_rPLL");
            break;
        }
        if (family == "GW1NS-2") {
            if (loc == PLL::left) {
                return;
            }
            bel = id("R10C20_rPLL");
            break;
        }
        if (family == "GW1N-4") {
            bel = loc == PLL::left ? id("R1C10_rPLL") : id("R1C28_rPLL");
            break;
        }
        if (family == "GW1NR-9C" || family == "GW1NR-9") {
            bel = loc == PLL::left ? id("R10C1_rPLL") : id("R10C47_rPLL");
            break;
        }
        return;
    default:
        return;
    }
    if (checkBelAvail(bel) || ci->belStrength != STRENGTH_LOCKED) {
        if (ci->bel == bel) {
            unbindBel(bel);
        } else {
            if (!checkBelAvail(bel) && ci->belStrength != STRENGTH_LOCKED) {
                CellInfo *other_ci = getBoundBelCell(bel);
                unbindBel(bel);
                BelId our_bel = ci->bel;
                unbindBel(our_bel);
                bindBel(our_bel, other_ci, STRENGTH_LOCKED);
            }
        }
        ci->disconnectPort(id_CLKIN);
        ci->setParam(id_INSEL, Property("CLKIN0"));
        bindBel(bel, ci, STRENGTH_LOCKED);
    }
}

// If the PLL input can be connected using a direct wire, then do so,
// bypassing conventional routing.
void Arch::fix_pll_nets(Context *ctx)
{
    for (auto &cell : ctx->cells) {
        CellInfo *ci = cell.second.get();
        if (ci->type != id_rPLL && ci->type != id_PLLVR) {
            continue;
        }
        // *** CLKIN
        do {
            if (!port_used(ci, id_CLKIN)) {
                ci->setParam(id_INSEL, Property("UNKNOWN"));
                break;
            }
            NetInfo *net = ci->getPort(id_CLKIN);
            if (net->name == id("$PACKER_VCC_NET") || net->name == id("$PACKER_GND_NET")) {
                ci->setParam(id_INSEL, Property("UNKNOWN"));
                break;
            }
            if (net_driven_by(ctx, net, is_RPLL_T_IN_iob, id_O) != nullptr) {
                bind_pll_to_bel(ci, PLL::right);
                break;
            }
            if (net_driven_by(ctx, net, is_LPLL_T_IN_iob, id_O) != nullptr) {
                bind_pll_to_bel(ci, PLL::left);
                break;
            }
            // XXX do special bels (HCLK etc)
            // This is general routing through CLK0 pip
            ci->setParam(id_INSEL, Property("CLKIN1"));
        } while (0);

        do {
            // *** CLKFB
            if (str_or_default(ci->params, id_CLKFB_SEL, "internal") == "internal") {
                ci->setParam(id_FBSEL, Property("CLKFB3"));
                continue;
            }
            if (!port_used(ci, id_CLKFB)) {
                ci->setParam(id_FBSEL, Property("UNKNOWN"));
                continue;
            }
            NetInfo *net = ci->getPort(id_CLKFB);
            if (net->name == id("$PACKER_VCC_NET") || net->name == id("$PACKER_GND_NET")) {
                ci->setParam(id_FBSEL, Property("UNKNOWN"));
                continue;
            }
            // XXX Redesign for chips other than N-1 and NS-4
            if (net_driven_by(ctx, net, is_RPLL_T_FB_iob, id_O) != nullptr) {
                ci->disconnectPort(id_CLKFB);
                ci->setParam(id_FBSEL, Property("CLKFB2"));
                break;
            }
            if (net_driven_by(ctx, net, is_LPLL_T_FB_iob, id_O) != nullptr) {
                ci->disconnectPort(id_CLKFB);
                ci->setParam(id_FBSEL, Property("CLKFB2"));
                break;
            }
            // XXX do special bels (HCLK etc)
            // This is general routing through CLK2 pip
            ci->setParam(id_FBSEL, Property("CLKFB0"));
        } while (0);
    }
}

// mark with hclk is used
void Arch::mark_used_hclk(Context *ctx)
{
    pool<IdString> aux_cells;
    for (auto &cell : ctx->cells) {
        CellInfo *ci = cell.second.get();
        if (ci->type != id_IOLOGIC) {
            continue;
        }
        if (ci->attrs.count(id_IOLOGIC_FCLK)) {
            continue;
        }
        // if it's an aux cell
        if (ci->attrs.count(id_IOLOGIC_MASTER_CELL)) {
            aux_cells.insert(ci->name);
            continue;
        }
        ci->setAttr(id_IOLOGIC_FCLK, Property("UNKNOWN"));

        // *** FCLK
        if (port_used(ci, id_FCLK)) {
            NetInfo const *net = ci->getPort(id_FCLK);
            for (auto const &user : net->users) {
                if (user.cell != ci) {
                    continue;
                }
                if (user.port != id_FCLK) {
                    continue;
                }
                WireId dstWire = ctx->getNetinfoSinkWire(net, user, 0);
                if (ctx->verbose) {
                    log_info("   Cell:%s, port:%s, wire:%s\n", user.cell->name.c_str(this), user.port.c_str(this),
                             dstWire.c_str(this));
                }
                for (PipId pip : getPipsUphill(dstWire)) {
                    if (!checkPipAvail(pip)) {
                        WireId src_wire = getPipSrcWire(pip);
                        ci->setAttr(id_IOLOGIC_FCLK, Property(wire_info(src_wire).type.str(this)));
                    }
                }
            }
        }
    }
    for (auto acell : aux_cells) {
        IdString main_cell = ctx->id(ctx->cells.at(acell)->attrs.at(id_IOLOGIC_MASTER_CELL).as_string());
        Property &fclk = ctx->cells.at(main_cell)->attrs.at(id_IOLOGIC_FCLK);
        ctx->cells.at(acell)->setAttr(id_IOLOGIC_FCLK, fclk);
    }
}

void Arch::pre_route(Context *ctx)
{
    fix_pll_nets(ctx);
    if (bool_or_default(settings, id("arch.enable-globals"))) {
        mark_gowin_globals(ctx);
    }
}

void Arch::post_route(Context *ctx)
{
    fix_longwire_bels();
    mark_used_hclk(ctx);
}

bool Arch::route()
{
    std::string router = str_or_default(settings, id_router, defaultRouter);
    Context *ctx = getCtx();

    pre_route(ctx);
    if (bool_or_default(settings, id("arch.enable-globals"))) {
        route_gowin_globals(ctx);
    }

    bool result;
    if (router == "router1") {
        result = router1(ctx, Router1Cfg(ctx));
    } else if (router == "router2") {
        router2(ctx, Router2Cfg(ctx));
        result = true;
    } else {
        log_error("Gowin architecture does not support router '%s'\n", router.c_str());
    }
    getCtx()->settings[id_route] = 1;
    archInfoToAttributes();
    post_route(ctx);
    return result;
}

// ---------------------------------------------------------------
std::vector<GraphicElement> Arch::getDecalGraphics(DecalId decal) const
{
    if (!decal_graphics.count(decal)) {
        // XXX
        return std::vector<GraphicElement>();
    }
    return decal_graphics.at(decal);
}

// ---------------------------------------------------------------

bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayQuad &delay) const
{
    if (!cellTiming.count(cell->name))
        return false;
    const auto &tmg = cellTiming.at(cell->name);
    auto fnd = tmg.combDelays.find(CellDelayKey{fromPort, toPort});
    if (fnd != tmg.combDelays.end()) {
        delay = fnd->second;
        return true;
    } else {
        return false;
    }
}

// Get the port class, also setting clockPort if applicable
TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const
{
    if (!cellTiming.count(cell->name))
        return TMG_IGNORE;
    const auto &tmg = cellTiming.at(cell->name);
    if (tmg.clockingInfo.count(port))
        clockInfoCount = int(tmg.clockingInfo.at(port).size());
    else
        clockInfoCount = 0;
    return get_or_default(tmg.portClasses, port, TMG_IGNORE);
}

TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port, int index) const
{
    NPNR_ASSERT(cellTiming.count(cell->name));
    const auto &tmg = cellTiming.at(cell->name);
    NPNR_ASSERT(tmg.clockingInfo.count(port));
    return tmg.clockingInfo.at(port).at(index);
}

bool Arch::isBelLocationValid(BelId bel, bool explain_invalid) const
{
    Loc loc = getBelLocation(bel);

    std::vector<const CellInfo *> cells;
    for (auto tbel : getBelsByTile(loc.x, loc.y)) {
        CellInfo *bound = getBoundBelCell(tbel);
        if (bound != nullptr)
            cells.push_back(bound);
    }

    return cellsCompatible(cells.data(), int(cells.size()));
}

const std::string Arch::defaultPlacer = "heap";

const std::vector<std::string> Arch::availablePlacers = {"sa", "heap"};

const std::string Arch::defaultRouter = "router1";
const std::vector<std::string> Arch::availableRouters = {"router1", "router2"};

void Arch::assignArchInfo()
{
    for (auto &cell : getCtx()->cells) {
        IdString cname = cell.first;
        CellInfo *ci = cell.second.get();
        DelayQuad delay = DelayQuad(0);
        ci->is_slice = false;
        switch (ci->type.index) {
        case ID_SLICE: {
            ci->is_slice = true;
            ci->ff_used = ci->params.at(id_FF_USED).as_bool();
            ci->ff_type = id(ci->params.at(id_FF_TYPE).as_string());
            ci->slice_clk = ci->getPort(id_CLK);
            ci->slice_ce = ci->getPort(id_CE);
            ci->slice_lsr = ci->getPort(id_LSR);

            // add timing paths
            addCellTimingClock(cname, id_CLK);
            addCellTimingClass(cname, id_CE, TMG_REGISTER_INPUT);
            addCellTimingClass(cname, id_LSR, TMG_REGISTER_INPUT);
            IdString ports[4] = {id_A, id_B, id_C, id_D};
            for (int i = 0; i < 4; i++) {
                DelayPair setup =
                        delayLookup(speed->dff.timings.get(), speed->dff.num_timings, id_clksetpos).delayPair();
                DelayPair hold =
                        delayLookup(speed->dff.timings.get(), speed->dff.num_timings, id_clkholdpos).delayPair();
                addCellTimingSetupHold(cname, ports[i], id_CLK, setup, hold);
            }
            DelayQuad clkout = delayLookup(speed->dff.timings.get(), speed->dff.num_timings, id_clk_qpos);
            addCellTimingClockToOut(cname, id_Q, id_CLK, clkout);
            IdString port_delay[4] = {id_a_f, id_b_f, id_c_f, id_d_f};
            for (int i = 0; i < 4; i++) {
                DelayQuad delay = delayLookup(speed->lut.timings.get(), speed->lut.num_timings, port_delay[i]);
                addCellTimingDelay(cname, ports[i], id_F, delay);
            }
            break;
        }
        case ID_MUX2_LUT8:
            delay = delay + delayLookup(speed->lut.timings.get(), speed->lut.num_timings, id_fx_ofx1);
            /* FALLTHRU */
        case ID_MUX2_LUT7:
            delay = delay + delayLookup(speed->lut.timings.get(), speed->lut.num_timings, id_fx_ofx1);
            /* FALLTHRU */
        case ID_MUX2_LUT6:
            delay = delay + delayLookup(speed->lut.timings.get(), speed->lut.num_timings, id_fx_ofx1);
            /* FALLTHRU */
        case ID_MUX2_LUT5: {
            delay = delay + delayLookup(speed->lut.timings.get(), speed->lut.num_timings, id_fx_ofx1);
            addCellTimingDelay(cname, id_I0, id_OF, delay);
            addCellTimingDelay(cname, id_I1, id_OF, delay);
            addCellTimingClass(cname, id_SEL, TMG_COMB_INPUT);
            break;
        }
        case ID_IOB:
            /* FALLTHRU */
        case ID_IOBS:
            addCellTimingClass(cname, id_I, TMG_ENDPOINT);
            addCellTimingClass(cname, id_O, TMG_STARTPOINT);
            break;
        case ID_BUFS:
            addCellTimingClass(cname, id_I, TMG_ENDPOINT);
            addCellTimingClass(cname, id_O, TMG_STARTPOINT);
            break;
        default:
            break;
        }
    }
}

bool Arch::cellsCompatible(const CellInfo **cells, int count) const
{
    const NetInfo *clk[4] = {nullptr, nullptr, nullptr, nullptr};
    const NetInfo *ce[4] = {nullptr, nullptr, nullptr, nullptr};
    const NetInfo *lsr[4] = {nullptr, nullptr, nullptr, nullptr};
    IdString mode[4] = {IdString(), IdString(), IdString(), IdString()};
    for (int i = 0; i < count; i++) {
        const CellInfo *ci = cells[i];
        if (ci->is_slice) {
            Loc loc = getBelLocation(ci->bel);
            int cls = loc.z / 2;
            if (loc.z >= 6 && ci->ff_used) // top slice have no ff
                return false;
            if (clk[cls] == nullptr)
                clk[cls] = ci->slice_clk;
            else if (clk[cls] != ci->slice_clk)
                return false;
            if (ce[cls] == nullptr)
                ce[cls] = ci->slice_ce;
            else if (ce[cls] != ci->slice_ce)
                return false;
            if (lsr[cls] == nullptr)
                lsr[cls] = ci->slice_lsr;
            else if (lsr[cls] != ci->slice_lsr)
                return false;
            if (mode[cls] == IdString())
                mode[cls] = ci->ff_type;
            else if (mode[cls] != ci->ff_type) {
                auto res = dff_comp_mode.find(mode[cls]);
                if (res == dff_comp_mode.end() || res->second != ci->ff_type)
                    return false;
            }
        }
    }
    return true;
}

void Arch::route_gowin_globals(Context *ctx) { globals_router.route_globals(ctx); }

void Arch::mark_gowin_globals(Context *ctx) { globals_router.mark_globals(ctx); }
// ---------------------------------------------------------------
void Arch::pre_pack(Context *ctx)
{
    if (bool_or_default(settings, id("arch.enable-auto-longwires"))) {
        auto_longwires();
    }
}

void Arch::post_pack(Context *ctx) {}

NEXTPNR_NAMESPACE_END
