text: /*
   *  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 "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
      snprintf(buf, sizeof(buf), "$PACKER_BUFS_%c", longwire + 'A');
      auto net = std::make_unique<NetInfo>(id(buf));
      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::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;
  }
  
  void Arch::addWire(IdString name, IdString type, int x, int y)
  {
      NPNR_ASSERT(wires.count(name) == 0);
      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)
  {
      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();
  }
  
  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::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;
  }
  
  // ---------------------------------------------------------------
  
  // TODO represent wires more intelligently.
  IdString Arch::wireToGlobal(int &row, int &col, const DatabasePOD *db, IdString &wire)
  {
      const std::string &wirename = wire.str(this);
      char buf[32];
      if (wirename == "VCC" || wirename == "VSS") {
          row = 0;
          col = 0;
          return wire;
      }
      if (!isdigit(wirename[1]) || !isdigit(wirename[2]) || !isdigit(wirename[3])) {
          snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, wirename.c_str());
          return id(buf);
      }
      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:
          snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, wirename.c_str());
          return id(buf);
          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';
      }
      snprintf(buf, 32, "%c%d0", direction, num);
      wire = id(buf);
      // local aliases
      const TilePOD *tile = db->grid[row * db->cols + col].get();
      auto local_alias = pairLookup(tile->aliases.get(), tile->num_aliases, wire.index);
      if (local_alias != nullptr) {
          wire = IdString(local_alias->src_id);
          snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, wire.c_str(this));
      } else {
          snprintf(buf, 32, "R%dC%d_%c%d", row + 1, col + 1, direction, num);
      }
      return id(buf);
  }
  
  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;
  }
  
  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;
      }
  }
  
  DelayQuad delayLookup(const TimingPOD *first, int len, IdString name)
  {
      TimingPOD needle;
      needle.name_id = name.index;
      const TimingPOD *timing = genericLookup(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 +\"([^\"]+)\" +([^ ,;]+)(, *[^ ;]+)? *;.*");
      std::regex portre = std::regex("IO_PORT +\"([^\"]+)\" +([^;]+;).*");
      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])\\] *;.*");
      std::regex clockre = std::regex("CLOCK_LOC +\"([^\"]+)\" +BUF([GS])(\\[([0-7])\\])?[^;]*;.*");
      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 PairPOD *belname = pairLookup(package->pins.get(), package->num_pins, pinname.index);
              if (belname != nullptr) {
                  std::string bel = IdString(belname->src_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, "GW_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));
      }
  }
  
  Arch::Arch(ArchArgs args) : args(args)
  {
      family = args.family;
  
      // 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) {
          // decals
          gfxCreateBelDecals(this);
      }
  
      // 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));
  
      // 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) {
              // CRU decal
              snprintf(buf, 32, "R%dC%d_CRU", row + 1, col + 1);
              grpname = id(buf);
              addGroup(grpname);
              setGroupDecal(grpname, gfxGetCruGroupDecalXY(col, row));
          }
          // 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) == 0)
                      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) == 0)
                      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;
              bool oddrc = false;
              switch (static_cast<ConstIds>(bel->type_id)) {
              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:
                  snprintf(buf, 32, "R%dC%d_OSC", row + 1, col + 1);
                  belname = id(buf);
                  addBel(belname, id_OSC, Loc(col, row, BelZ::osc_z), false);
                  portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_OSCOUT)->src_id);
                  snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this));
                  addBelOutput(belname, id_OSCOUT, id(buf));
                  break;
              case ID_OSCH:
                  snprintf(buf, 32, "R%dC%d_OSCH", row + 1, col + 1);
                  belname = id(buf);
                  addBel(belname, id_OSCH, Loc(col, row, BelZ::osc_z), false);
                  portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_OSCOUT)->src_id);
                  snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this));
                  addBelOutput(belname, id_OSCOUT, id(buf));
                  break;
              case ID_OSCF:
                  snprintf(buf, 32, "R%dC%d_OSCF", row + 1, col + 1);
                  belname = id(buf);
                  addBel(belname, id_OSCF, Loc(col, row, BelZ::osc_z), false);
                  portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_OSCOUT)->src_id);
                  snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this));
                  addBelOutput(belname, id_OSCOUT, id(buf));
                  portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_OSCEN)->src_id);
                  snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this));
                  addBelInput(belname, id_OSCEN, id(buf));
                  break;
              case ID_OSCZ:
                  snprintf(buf, 32, "R%dC%d_OSCZ", row + 1, col + 1);
                  belname = id(buf);
                  addBel(belname, id_OSCZ, Loc(col, row, BelZ::osc_z), false);
                  portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_OSCOUT)->src_id);
                  snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this));
                  addBelOutput(belname, id_OSCOUT, id(buf));
                  portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_OSCEN)->src_id);
                  snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this));
                  addBelInput(belname, id_OSCEN, 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) {
                          addGroup(grpname);
                          setGroupDecal(grpname, gfxGetLutGroupDecalXY(col, row, z >> 1));
                      }
                  }
                  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 *xxx_port = pairLookup(bel->ports.get(), bel->num_ports, ID_XXX_VSS0);
                  if (xxx_port != nullptr) {
                      gw1n9_quirk = true;
                      portname = IdString(xxx_port->src_id);
                      snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this));
                      addBelInput(belname, id_XXX_VSS0, id(buf));
                  }
                  xxx_port = pairLookup(bel->ports.get(), bel->num_ports, ID_XXX_VSS1);
                  if (xxx_port != nullptr) {
                      gw1n9_quirk = true;
                      portname = IdString(xxx_port->src_id);
                      snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this));
                      addBelInput(belname, id_XXX_VSS1, id(buf));
                  }
              } 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_ODDRCB:
                  z++; /* fall-through*/
              case ID_ODDRCA:
                  oddrc = true;
                  z++; /* fall-through*/
              case ID_ODDRB:
                  z++; /* fall-through*/
              case ID_ODDRA: {
                  snprintf(buf, 32, "R%dC%d_ODDR%s%c", row + 1, col + 1, oddrc ? "C" : "", 'A' + z - (oddrc ? 2 : 0));
                  belname = id(buf);
                  addBel(belname, id_ODDR, Loc(col, row, BelZ::iologic_0_z + z), false);
  
                  portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_D0)->src_id);
                  snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this));
                  addBelInput(belname, id_D0, id(buf));
  
                  portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_D1)->src_id);
                  snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this));
                  addBelInput(belname, id_D1, id(buf));
  
                  portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_TX)->src_id);
                  snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this));
                  addBelInput(belname, id_TX, id(buf));
  
                  portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_CLK)->src_id);
                  snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this));
                  addBelInput(belname, id_CLK, id(buf));
  
                  const PairPOD *xxx_port = pairLookup(bel->ports.get(), bel->num_ports, ID_XXX_VSS);
                  if (xxx_port != nullptr) {
                      ddr_has_extra_inputs = true;
                      portname = IdString(xxx_port->src_id);
                      snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this));
                      addBelInput(belname, id_XXX_VSS, id(buf));
                  }
                  xxx_port = pairLookup(bel->ports.get(), bel->num_ports, ID_XXX_VCC);
                  if (xxx_port != nullptr) {
                      ddr_has_extra_inputs = true;
                      portname = IdString(xxx_port->src_id);
                      snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this));
                      addBelInput(belname, id_XXX_VCC, id(buf));
                  }
  
                  if (oddrc) {
                      portname = IdString(pairLookup(bel->ports.get(), bel->num_ports, ID_CE)->src_id);
                      snprintf(buf, 32, "R%dC%d_%s", row + 1, col + 1, portname.c_str(this));
                      addBelInput(belname, id_CE, id(buf));
                  }
  
                  // dummy wires
                  snprintf(buf, 32, "ODDR%s%c_Q0", oddrc ? "C" : "", 'A' + z - (oddrc ? 2 : 0));
                  IdString id_q0 = id(buf);
                  IdString q0_name = wireToGlobal(row, col, db, id_q0);
                  if (wires.count(q0_name) == 0)
                      addWire(q0_name, id_q0, row, col);
                  addBelOutput(belname, id_Q0, q0_name);
  
                  snprintf(buf, 32, "ODDR%s%c_Q1", oddrc ? "C" : "", 'A' + z - (oddrc ? 2 : 0));
                  IdString id_q1 = id(buf);
                  IdString q1_name = wireToGlobal(row, col, db, id_q1);
                  if (wires.count(q1_name) == 0)
                      addWire(q1_name, id_q1, row, col);
                  addBelOutput(belname, id_Q1, q1_name);
              } break;
              default:
                  break;
              }
          }
      }
      // 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; }
  
  ArcBounds Arch::getRouteBoundingBox(WireId src, WireId dst) const
  {
      ArcBounds 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;
  }
  
  bool Arch::route()
  {
      std::string router = str_or_default(settings, id_router, defaultRouter);
      bool result;
      if (router == "router1") {
          result = router1(getCtx(), Router1Cfg(getCtx()));
      } else if (router == "router2") {
          router2(getCtx(), Router2Cfg(getCtx()));
          result = true;
      } else {
          log_error("Gowin architecture does not support router '%s'\n", router.c_str());
      }
      getCtx()->settings[id_route] = 1;
      archInfoToAttributes();
      fix_longwire_bels();
      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) 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()));
  }
  
  #ifdef WITH_HEAP
  const std::string Arch::defaultPlacer = "heap";
  #else
  const std::string Arch::defaultPlacer = "sa";
  #endif
  
  const std::vector<std::string> Arch::availablePlacers = {"sa",
  #ifdef WITH_HEAP
                                                           "heap"
  #endif
  };
  
  const std::string Arch::defaultRouter = "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);
              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_GW_MUX2_LUT8:
              delay = delay + delayLookup(speed->lut.timings.get(), speed->lut.num_timings, id_fx_ofx1);
              /* FALLTHRU */
          case ID_GW_MUX2_LUT7:
              delay = delay + delayLookup(speed->lut.timings.get(), speed->lut.num_timings, id_fx_ofx1);
              /* FALLTHRU */
          case ID_GW_MUX2_LUT6:
              delay = delay + delayLookup(speed->lut.timings.get(), speed->lut.num_timings, id_fx_ofx1);
              /* FALLTHRU */
          case ID_GW_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);
          }
          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;
  }
  
  NEXTPNR_NAMESPACE_END
  