diff options
| -rw-r--r-- | ice40/arch.cc | 4 | ||||
| -rw-r--r-- | ice40/arch_place.cc | 27 | ||||
| -rw-r--r-- | ice40/bitstream.cc | 108 | ||||
| -rw-r--r-- | ice40/cells.cc | 50 | ||||
| -rw-r--r-- | ice40/cells.h | 15 | ||||
| -rw-r--r-- | ice40/chipdb.py | 9 | ||||
| -rw-r--r-- | ice40/gfx.h | 6 | ||||
| -rw-r--r-- | ice40/pack.cc | 169 | ||||
| -rw-r--r-- | ice40/portpins.inc | 2 | 
9 files changed, 376 insertions, 14 deletions
diff --git a/ice40/arch.cc b/ice40/arch.cc index 2270fdc1..1d7e9546 100644 --- a/ice40/arch.cc +++ b/ice40/arch.cc @@ -2,6 +2,7 @@   *  nextpnr -- Next Generation Place and Route   *   *  Copyright (C) 2018  Clifford Wolf <clifford@symbioticeda.com> + *  Copyright (C) 2018  Serge Bazanski <q3k@symbioticeda.com>   *   *  Permission to use, copy, modify, and/or distribute this software for any   *  purpose with or without fee is hereby granted, provided that the above @@ -314,11 +315,12 @@ WireId Arch::getBelPinWire(BelId bel, PortPin pin) const      int num_bel_wires = chip_info->bel_data[bel.index].num_bel_wires;      const BelWirePOD *bel_wires = chip_info->bel_data[bel.index].bel_wires.get(); -    for (int i = 0; i < num_bel_wires; i++) +    for (int i = 0; i < num_bel_wires; i++) {          if (bel_wires[i].port == pin) {              ret.index = bel_wires[i].wire_index;              break;          } +    }      return ret;  } diff --git a/ice40/arch_place.cc b/ice40/arch_place.cc index d32ebe98..59e1807d 100644 --- a/ice40/arch_place.cc +++ b/ice40/arch_place.cc @@ -3,6 +3,7 @@   *   *  Copyright (C) 2018  Clifford Wolf <clifford@symbioticeda.com>   *  Copyright (C) 2018  David Shah <david@symbioticeda.com> + *  Copyright (C) 2018  Serge Bazanski <q3k@symbioticeda.com>   *   *  Permission to use, copy, modify, and/or distribute this software for any   *  purpose with or without fee is hereby granted, provided that the above @@ -107,6 +108,32 @@ bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const          bel_cells.push_back(cell);          return logicCellsCompatible(bel_cells);      } else if (cell->type == id_sb_io) { +        // Do not allow placement of input SB_IOs on blocks where there a PLL is outputting to. + +        // Find shared PLL by looking for driving bel siblings from D_IN_0 +        // that are a PLL clock output. +        auto wire = getBelPinWire(bel, PIN_D_IN_0); +        PortPin pll_bel_pin; +        BelId pll_bel; +        for (auto pin : getWireBelPins(wire)) { +            if (pin.pin == PIN_PLLOUT_A || pin.pin == PIN_PLLOUT_B) { +                pll_bel = pin.bel; +                pll_bel_pin = pin.pin; +                break; +            } +        } +        // Is there a PLL that shares this IO buffer? +        if (pll_bel.index != -1) { +            // Is a PLL placed in this PLL bel? +            if (!checkBelAvail(pll_bel)) { +                // Is the shared port driving a net? +                auto pll_cell = getBoundBelCell(pll_bel); +                auto pi = cells.at(pll_cell)->ports[portPinToId(pll_bel_pin)]; +                if (pi.net != nullptr) { +                    return false; +                } +            } +        }          return getBelPackagePin(bel) != "";      } else if (cell->type == id_sb_gb) {          NPNR_ASSERT(cell->ports.at(id_glb_buf_out).net != nullptr); diff --git a/ice40/bitstream.cc b/ice40/bitstream.cc index 9ac8e857..e9851a83 100644 --- a/ice40/bitstream.cc +++ b/ice40/bitstream.cc @@ -3,6 +3,7 @@   *
   *  Copyright (C) 2018  Clifford Wolf <clifford@symbioticeda.com>
   *  Copyright (C) 2018  David Shah <david@symbioticeda.com>
 + *  Copyright (C) 2018  Serge Bazanski <q3k@symbioticeda.com>
   *
   *  Permission to use, copy, modify, and/or distribute this software for any
   *  purpose with or without fee is hereby granted, provided that the above
 @@ -74,14 +75,26 @@ void set_config(const TileInfoPOD &ti, std::vector<std::vector<int8_t>> &tile_cf          for (int i = 0; i < cfg.num_bits; i++) {
              int8_t &cbit = tile_cfg.at(cfg.bits[i].row).at(cfg.bits[i].col);
              if (cbit && !value)
 -                log_error("clearing already set config bit %s", name.c_str());
 +                log_error("clearing already set config bit %s\n", name.c_str());
              cbit = value;
          }
      } else {
          int8_t &cbit = tile_cfg.at(cfg.bits[index].row).at(cfg.bits[index].col);
          cbit = value;
          if (cbit && !value)
 -            log_error("clearing already set config bit %s[%d]", name.c_str(), index);
 +            log_error("clearing already set config bit %s[%d]\n", name.c_str(), index);
 +    }
 +}
 +
 +// Set an IE_{EN,REN} logical bit in a tile config. Logical means enabled.
 +// On {HX,LP}1K devices these bits are active low, so we need to invert them.
 +void set_ie_bit_logical(const Context *ctx, const TileInfoPOD &ti, std::vector<std::vector<int8_t>> &tile_cfg,
 +                        const std::string &name, bool value)
 +{
 +    if (ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K) {
 +        set_config(ti, tile_cfg, name, !value);
 +    } else {
 +        set_config(ti, tile_cfg, name, value);
      }
  }
 @@ -117,7 +130,7 @@ static const BelConfigPOD &get_ec_config(const ChipInfoPOD *chip, BelId bel)  typedef std::vector<std::vector<std::vector<std::vector<int8_t>>>> chipconfig_t;
  static void set_ec_cbit(chipconfig_t &config, const Context *ctx, const BelConfigPOD &cell_cbits, std::string name,
 -                        bool value)
 +                        bool value, std::string prefix)
  {
      const ChipInfoPOD *chip = ctx->chip_info;
 @@ -125,7 +138,7 @@ static void set_ec_cbit(chipconfig_t &config, const Context *ctx, const BelConfi          const auto &cbit = cell_cbits.entries[i];
          if (cbit.entry_name.get() == name) {
              const auto &ti = chip->bits_info->tiles_nonrouting[tile_at(ctx, cbit.x, cbit.y)];
 -            set_config(ti, config.at(cbit.y).at(cbit.x), std::string("IpConfig.") + cbit.cbit_name.get(), value);
 +            set_config(ti, config.at(cbit.y).at(cbit.x), prefix + cbit.cbit_name.get(), value);
              return;
          }
      }
 @@ -133,7 +146,7 @@ static void set_ec_cbit(chipconfig_t &config, const Context *ctx, const BelConfi  }
  void configure_extra_cell(chipconfig_t &config, const Context *ctx, CellInfo *cell,
 -                          const std::vector<std::pair<std::string, int>> ¶ms, bool string_style)
 +                          const std::vector<std::pair<std::string, int>> ¶ms, bool string_style, std::string prefix)
  {
      const ChipInfoPOD *chip = ctx->chip_info;
      const auto &bc = get_ec_config(chip, cell->bel);
 @@ -163,10 +176,10 @@ void configure_extra_cell(chipconfig_t &config, const Context *ctx, CellInfo *ce          value.resize(p.second);
          if (p.second == 1) {
 -            set_ec_cbit(config, ctx, bc, p.first, value.at(0));
 +            set_ec_cbit(config, ctx, bc, p.first, value.at(0), prefix);
          } else {
              for (int i = 0; i < p.second; i++) {
 -                set_ec_cbit(config, ctx, bc, p.first + "_" + std::to_string(i), value.at(i));
 +                set_ec_cbit(config, ctx, bc, p.first + "_" + std::to_string(i), value.at(i), prefix);
              }
          }
      }
 @@ -258,8 +271,13 @@ void write_asc(const Context *ctx, std::ostream &out)              }
          }
      }
 +
 +    std::unordered_set<Loc> sb_io_used_by_pll;
 +    std::unordered_set<Loc> sb_io_used_by_io;
 +
      // Set logic cell config
      for (auto &cell : ctx->cells) {
 +
          BelId bel = cell.second.get()->bel;
          if (bel == BelId()) {
              std::cout << "Found unplaced cell " << cell.first.str(ctx) << " while generating bitstream!" << std::endl;
 @@ -304,6 +322,7 @@ void write_asc(const Context *ctx, std::ostream &out)          } else if (cell.second->type == ctx->id("SB_IO")) {
              const BelInfoPOD &beli = ci.bel_data[bel.index];
              int x = beli.x, y = beli.y, z = beli.z;
 +            sb_io_used_by_io.insert(Loc(x, y, z));
              const TileInfoPOD &ti = bi.tiles_nonrouting[TILE_IO];
              unsigned pin_type = get_param_or_def(cell.second.get(), ctx->id("PIN_TYPE"));
              bool neg_trigger = get_param_or_def(cell.second.get(), ctx->id("NEG_TRIGGER"));
 @@ -405,7 +424,70 @@ void write_asc(const Context *ctx, std::ostream &out)                                                                             {"MODE_8x8", 1},
                                                                             {"A_SIGNED", 1},
                                                                             {"B_SIGNED", 1}};
 -            configure_extra_cell(config, ctx, cell.second.get(), mac16_params, false);
 +            configure_extra_cell(config, ctx, cell.second.get(), mac16_params, false, std::string("IpConfig."));
 +        } else if (cell.second->type == ctx->id("ICESTORM_PLL")) {
 +            const std::vector<std::pair<std::string, int>> pll_params = {{"DELAY_ADJMODE_FB", 1},
 +                                                                         {"DELAY_ADJMODE_REL", 1},
 +                                                                         {"DIVF", 7},
 +                                                                         {"DIVQ", 3},
 +                                                                         {"DIVR", 4},
 +                                                                         {"FDA_FEEDBACK", 4},
 +                                                                         {"FDA_RELATIVE", 4},
 +                                                                         {"FEEDBACK_PATH", 3},
 +                                                                         {"FILTER_RANGE", 3},
 +                                                                         {"PLLOUT_SELECT_A", 2},
 +                                                                         {"PLLOUT_SELECT_B", 2},
 +                                                                         {"PLLTYPE", 3},
 +                                                                         {"SHIFTREG_DIV_MODE", 1},
 +                                                                         {"TEST_MODE", 1}};
 +            configure_extra_cell(config, ctx, cell.second.get(), pll_params, false, std::string("PLL."));
 +
 +            // Configure the SB_IOs that the clock outputs are going through.
 +            for (auto &port : cell.second->ports) {
 +                // If this port is not a PLLOUT port, ignore it.
 +                if (port.second.name != ctx->id("PLLOUT_A") && port.second.name != ctx->id("PLLOUT_B"))
 +                    continue;
 +
 +                // If the output is not driving any net, ignore it.
 +                if (port.second.net == nullptr)
 +                    continue;
 +
 +                // Get IO Bel that this PLL port goes through by finding sibling
 +                // Bel driving the same wire via PIN_D_IN_0.
 +                auto wire = ctx->getBelPinWire(cell.second->bel, ctx->portPinFromId(port.second.name));
 +                BelId io_bel;
 +                for (auto pin : ctx->getWireBelPins(wire)) {
 +                    if (pin.pin == PIN_D_IN_0) {
 +                        io_bel = pin.bel;
 +                        break;
 +                    }
 +                }
 +                NPNR_ASSERT(io_bel.index != -1);
 +                auto io_bel_loc = ctx->getBelLocation(io_bel);
 +
 +                // Check that this SB_IO is either unused or just used as an output.
 +                if (sb_io_used_by_io.count(io_bel_loc)) {
 +                    log_error("SB_IO '%s' already in use, cannot route PLL through\n", ctx->getBelName(bel).c_str(ctx));
 +                }
 +                sb_io_used_by_pll.insert(io_bel_loc);
 +
 +                // Get IE/REN config location (cf. http://www.clifford.at/icestorm/io_tile.html)
 +                auto ieren = get_ieren(bi, io_bel_loc.x, io_bel_loc.y, io_bel_loc.z);
 +                int iex, iey, iez;
 +                std::tie(iex, iey, iez) = ieren;
 +                NPNR_ASSERT(iez != -1);
 +
 +                // Write config.
 +                const TileInfoPOD &ti = bi.tiles_nonrouting[TILE_IO];
 +                // Enable input buffer and disable pull-up resistor in block
 +                // (this is used by the PLL).
 +                set_ie_bit_logical(ctx, ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), true);
 +                set_ie_bit_logical(ctx, ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), false);
 +                // PINTYPE[0] passes the PLL through to the fabric.
 +                set_config(ti, config.at(io_bel_loc.y).at(io_bel_loc.x),
 +                           "IOB_" + std::to_string(io_bel_loc.z) + ".PINTYPE_0", true);
 +            }
 +
          } else {
              NPNR_ASSERT(false);
          }
 @@ -416,14 +498,16 @@ void write_asc(const Context *ctx, std::ostream &out)              const TileInfoPOD &ti = bi.tiles_nonrouting[TILE_IO];
              const BelInfoPOD &beli = ci.bel_data[bel.index];
              int x = beli.x, y = beli.y, z = beli.z;
 +            if (sb_io_used_by_pll.count(Loc(x, y, z))) {
 +                continue;
 +            }
 +
              auto ieren = get_ieren(bi, x, y, z);
              int iex, iey, iez;
              std::tie(iex, iey, iez) = ieren;
              if (iez != -1) {
 -                if (ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K) {
 -                    set_config(ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), true);
 -                    set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), false);
 -                }
 +                set_ie_bit_logical(ctx, ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), true);
 +                set_ie_bit_logical(ctx, ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), false);
              }
          } else if (ctx->bel_to_cell[bel.index] == IdString() && ctx->getBelType(bel) == TYPE_ICESTORM_RAM) {
              const BelInfoPOD &beli = ci.bel_data[bel.index];
 diff --git a/ice40/cells.cc b/ice40/cells.cc index 71a65d44..610bf85e 100644 --- a/ice40/cells.cc +++ b/ice40/cells.cc @@ -3,6 +3,7 @@   *   *  Copyright (C) 2018  Clifford Wolf <clifford@symbioticeda.com>   *  Copyright (C) 2018  David Shah <david@symbioticeda.com> + *  Copyright (C) 2018  Serge Bazanski <q3k@symbioticeda.com>   *   *  Permission to use, copy, modify, and/or distribute this software for any   *  purpose with or without fee is hereby granted, provided that the above @@ -207,6 +208,40 @@ std::unique_ptr<CellInfo> create_ice_cell(Context *ctx, IdString type, std::stri          add_port(ctx, new_cell.get(), "ACCUMCO", PORT_OUT);          add_port(ctx, new_cell.get(), "SIGNEXTOUT", PORT_OUT); +    } else if (type == ctx->id("ICESTORM_PLL")) { +        new_cell->params[ctx->id("DELAY_ADJMODE_FB")] = "0"; +        new_cell->params[ctx->id("DELAY_ADJMODE_REL")] = "0"; + +        new_cell->params[ctx->id("DIVF")] = "0"; +        new_cell->params[ctx->id("DIVQ")] = "0"; +        new_cell->params[ctx->id("DIVR")] = "0"; + +        new_cell->params[ctx->id("FDA_FEEDBACK")] = "0"; +        new_cell->params[ctx->id("FDA_RELATIVE")] = "0"; +        new_cell->params[ctx->id("FEEDBACK_PATH")] = "0"; +        new_cell->params[ctx->id("FILTER_RANGE")] = "0"; + +        new_cell->params[ctx->id("PLLOUT_SELECT_A")] = "0"; +        new_cell->params[ctx->id("PLLOUT_SELECT_B")] = "0"; + +        new_cell->params[ctx->id("PLLTYPE")] = "0"; +        new_cell->params[ctx->id("SHIFTREG_DIVMODE")] = "0"; +        new_cell->params[ctx->id("TEST_MODE")] = "0"; + +        add_port(ctx, new_cell.get(), "BYPASS", PORT_IN); +        add_port(ctx, new_cell.get(), "DYNAMICDELAY", PORT_IN); +        add_port(ctx, new_cell.get(), "EXTFEEDBACK", PORT_IN); +        add_port(ctx, new_cell.get(), "LATCHINPUTVALUE", PORT_IN); +        add_port(ctx, new_cell.get(), "REFERENCECLK", PORT_IN); +        add_port(ctx, new_cell.get(), "RESETB", PORT_IN); + +        add_port(ctx, new_cell.get(), "SCLK", PORT_IN); +        add_port(ctx, new_cell.get(), "SDI", PORT_IN); +        add_port(ctx, new_cell.get(), "SDI", PORT_OUT); + +        add_port(ctx, new_cell.get(), "LOCK", PORT_OUT); +        add_port(ctx, new_cell.get(), "PLLOUT_A", PORT_OUT); +        add_port(ctx, new_cell.get(), "PLLOUT_B", PORT_OUT);      } else {          log_error("unable to create iCE40 cell of type %s", type.c_str(ctx));      } @@ -312,6 +347,21 @@ void nxio_to_sb(Context *ctx, CellInfo *nxio, CellInfo *sbio)      }  } +uint8_t sb_pll40_type(const BaseCtx *ctx, const CellInfo *cell) +{ +    if (cell->type == ctx->id("SB_PLL40_PAD")) +        return 2; +    if (cell->type == ctx->id("SB_PLL40_2_PAD")) +        return 4; +    if (cell->type == ctx->id("SB_PLL40_2F_PAD")) +        return 5; +    if (cell->type == ctx->id("SB_PLL40_CORE")) +        return 3; +    if (cell->type == ctx->id("SB_PLL40_2F_CORE")) +        return 7; +    NPNR_ASSERT(0); +} +  bool is_clock_port(const BaseCtx *ctx, const PortRef &port)  {      if (port.cell == nullptr) diff --git a/ice40/cells.h b/ice40/cells.h index 2f9c77e8..16135448 100644 --- a/ice40/cells.h +++ b/ice40/cells.h @@ -71,6 +71,21 @@ inline bool is_sb_spram(const BaseCtx *ctx, const CellInfo *cell) { return cell-  inline bool is_sb_mac16(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_MAC16"); } +inline bool is_sb_pll40(const BaseCtx *ctx, const CellInfo *cell) +{ +    return cell->type == ctx->id("SB_PLL40_PAD") || cell->type == ctx->id("SB_PLL40_2_PAD") || +           cell->type == ctx->id("SB_PLL40_2F_PAD") || cell->type == ctx->id("SB_PLL40_CORE") || +           cell->type == ctx->id("SB_PLL40_2F_CORE"); +} + +inline bool is_sb_pll40_pad(const BaseCtx *ctx, const CellInfo *cell) +{ +    return cell->type == ctx->id("SB_PLL40_PAD") || cell->type == ctx->id("SB_PLL40_2_PAD") || +           cell->type == ctx->id("SB_PLL40_2F_PAD"); +} + +uint8_t sb_pll40_type(const BaseCtx *ctx, const CellInfo *cell); +  // Convert a SB_LUT primitive to (part of) an ICESTORM_LC, swapping ports  // as needed. Set no_dff if a DFF is not being used, so that the output  // can be reconnected diff --git a/ice40/chipdb.py b/ice40/chipdb.py index 1127767d..108197c1 100644 --- a/ice40/chipdb.py +++ b/ice40/chipdb.py @@ -182,6 +182,8 @@ def wire_type(name):          wt = "LOCAL"      elif name in ("WCLK", "WCLKE", "WE", "RCLK", "RCLKE", "RE"):          wt = "LOCAL" +    elif name in ("PLLOUT_A", "PLLOUT_B"): +        wt = "LOCAL"      if wt is None:          print("No type for wire: %s (%s)" % (longname, name), file=sys.stderr) @@ -584,6 +586,9 @@ def is_ec_output(ec_entry):      if "glb_netwk_" in wirename: return True      return False +def is_ec_pll_clock_output(ec, ec_entry): +    return ec[0] == 'PLL' and ec_entry[0] in ('PLLOUT_A', 'PLLOUT_B') +  def add_bel_ec(ec):      ectype, x, y, z = ec      bel = len(bel_name) @@ -598,6 +603,10 @@ def add_bel_ec(ec):                  add_bel_output(bel, wire_names[entry[1]], entry[0])              else:                  add_bel_input(bel, wire_names[entry[1]], entry[0]) +        elif is_ec_pll_clock_output(ec, entry): +            x, y, z = entry[1] +            z = 'io_{}/D_IN_0'.format(z) +            add_bel_output(bel, wire_names[(x, y, z)], entry[0])          else:              extra_cell_config[bel].append(entry) diff --git a/ice40/gfx.h b/ice40/gfx.h index 8a55407d..7eeaccf1 100644 --- a/ice40/gfx.h +++ b/ice40/gfx.h @@ -464,7 +464,11 @@ enum GfxTileWireId      TILE_WIRE_SP12_H_R_23,      TILE_WIRE_SP12_H_L_22, -    TILE_WIRE_SP12_H_L_23 +    TILE_WIRE_SP12_H_L_23, + +    TILE_WIRE_PLLIN, +    TILE_WIRE_PLLOUT_A, +    TILE_WIRE_PLLOUT_B  };  void gfxTileWire(std::vector<GraphicElement> &g, int x, int y, GfxTileWireId id, GraphicElement::style_t style); diff --git a/ice40/pack.cc b/ice40/pack.cc index f054be6b..b1e7380e 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -3,6 +3,7 @@   *   *  Copyright (C) 2018  Clifford Wolf <clifford@symbioticeda.com>   *  Copyright (C) 2018  David Shah <david@symbioticeda.com> + *  Copyright (C) 2018  Serge Bazanski <q3k@symbioticeda.com>   *   *  Permission to use, copy, modify, and/or distribute this software for any   *  purpose with or without fee is hereby granted, provided that the above @@ -540,6 +541,56 @@ static void promote_globals(Context *ctx)      }  } +// spliceLUT adds a pass-through LUT LC between the given cell's output port +// and either all users or only non_LUT users. +static std::unique_ptr<CellInfo> spliceLUT(Context *ctx, CellInfo *ci, IdString portId, bool onlyNonLUTs) +{ +    auto port = ci->ports[portId]; + +    NPNR_ASSERT(port.net != nullptr); + +    // Create pass-through LUT. +    std::unique_ptr<CellInfo> pt = +            create_ice_cell(ctx, ctx->id("ICESTORM_LC"), ci->name.str(ctx) + "$nextpnr_ice40_pack_pll_lc"); +    pt->params[ctx->id("LUT_INIT")] = "255"; // output is always I3 + +    // Create LUT output net. +    std::unique_ptr<NetInfo> out_net = std::unique_ptr<NetInfo>(new NetInfo); +    out_net->name = ctx->id(ci->name.str(ctx) + "$nextnr_ice40_pack_pll_net"); +    out_net->driver.cell = pt.get(); +    out_net->driver.port = ctx->id("O"); +    pt->ports.at(ctx->id("O")).net = out_net.get(); + +    // New users of the original cell's port +    std::vector<PortRef> new_users; +    for (const auto &user : port.net->users) { +        if (onlyNonLUTs && user.cell->type == ctx->id("ICESTORM_LC")) { +            new_users.push_back(user); +            continue; +        } +        // Rewrite pointer into net in user. +        user.cell->ports[user.port].net = out_net.get(); +        // Add user to net. +        PortRef pr; +        pr.cell = user.cell; +        pr.port = user.port; +        out_net->users.push_back(pr); +    } + +    // Add LUT to new users. +    PortRef pr; +    pr.cell = pt.get(); +    pr.port = ctx->id("I3"); +    new_users.push_back(pr); +    pt->ports.at(ctx->id("I3")).net = port.net; + +    // Replace users of the original net. +    port.net->users = new_users; + +    ctx->nets[out_net->name] = std::move(out_net); +    return pt; +} +  // Pack special functions  static void pack_special(Context *ctx)  { @@ -595,6 +646,124 @@ static void pack_special(Context *ctx)                  replace_port(ci, ctx->id(pi.name.c_str(ctx)), packed.get(), ctx->id(newname));              }              new_cells.push_back(std::move(packed)); +        } else if (is_sb_pll40(ctx, ci)) { +            std::unique_ptr<CellInfo> packed = +                    create_ice_cell(ctx, ctx->id("ICESTORM_PLL"), ci->name.str(ctx) + "_PLL"); +            packed_cells.insert(ci->name); + +            if (is_sb_pll40_pad(ctx, ci)) { +                // TODO(q3k): Implement these after checking their behaviour on +                // a board with exposed 'clock pads'. +                log_error("SB_PLL40_*_PAD cells are not supported yet.\n"); +            } + +            for (auto attr : ci->attrs) +                packed->attrs[attr.first] = attr.second; +            for (auto param : ci->params) +                packed->params[param.first] = param.second; + +            auto feedback_path = packed->params[ctx->id("FEEDBACK_PATH")]; +            packed->params[ctx->id("FEEDBACK_PATH")] = +                    feedback_path == "DELAY" +                            ? "0" +                            : feedback_path == "SIMPLE" ? "1" +                                                        : feedback_path == "PHASE_AND_DELAY" +                                                                  ? "2" +                                                                  : feedback_path == "EXTERNAL" ? "6" : feedback_path; +            packed->params[ctx->id("PLLTYPE")] = std::to_string(sb_pll40_type(ctx, ci)); + +            for (auto port : ci->ports) { +                PortInfo &pi = port.second; +                std::string newname = pi.name.str(ctx); +                size_t bpos = newname.find('['); +                if (bpos != std::string::npos) { +                    newname = newname.substr(0, bpos) + "_" + newname.substr(bpos + 1, (newname.size() - bpos) - 2); +                } +                if (pi.name == ctx->id("PLLOUTCOREA")) +                    newname = "PLLOUT_A"; +                if (pi.name == ctx->id("PLLOUTCOREB")) +                    newname = "PLLOUT_B"; +                if (pi.name == ctx->id("PLLOUTCORE")) +                    newname = "PLLOUT_A"; +                replace_port(ci, ctx->id(pi.name.c_str(ctx)), packed.get(), ctx->id(newname)); +            } + +            // If PLL is not constrained already, do that - we need this +            // information to then constrain the LOCK LUT. +            BelId pll_bel; +            bool constrained = false; +            if (packed->attrs.find(ctx->id("BEL")) == packed->attrs.end()) { +                // FIXME replace by getBelsByType when implemented +                for (auto bel : ctx->getBels()) { +                    if (ctx->getBelType(bel) != TYPE_ICESTORM_PLL) +                        continue; +                    log_info("  constrained '%s' to %s\n", packed->name.c_str(ctx), ctx->getBelName(bel).c_str(ctx)); +                    packed->attrs[ctx->id("BEL")] = ctx->getBelName(bel).str(ctx); +                    pll_bel = bel; +                    constrained = true; +                } +                if (!constrained) { +                    log_error("  could not constrain '%s' to any PLL Bel\n", packed->name.c_str(ctx)); +                } +            } + +            // The LOCK signal on iCE40 PLLs goes through the neigh_op_bnl_1 wire. +            // In practice, this means the LOCK signal can only directly reach LUT +            // inputs. +            // If we have a net connected to LOCK, make sure it only drives LUTs. +            auto port = packed->ports[ctx->id("LOCK")]; +            if (port.net != nullptr) { +                bool found_lut = false; +                bool all_luts = true; +                unsigned int lut_count = 0; +                for (const auto &user : port.net->users) { +                    NPNR_ASSERT(user.cell != nullptr); +                    if (user.cell->type == ctx->id("ICESTORM_LC")) { +                        found_lut = true; +                        lut_count++; +                    } else { +                        all_luts = false; +                    } +                } + +                if (found_lut && all_luts) { +                    // Every user is a LUT, carry on now. +                } else if (found_lut && !all_luts && lut_count < 8) { +                    // Strategy: create a pass-through LUT, move all non-LUT users behind it. +                    log_info("    LUT strategy for %s: move non-LUT users to new LUT\n", port.name.c_str(ctx)); +                    auto pt = spliceLUT(ctx, packed.get(), port.name, true); +                    new_cells.push_back(std::move(pt)); +                } else { +                    // Strategy: create a pass-through LUT, move every user behind it. +                    log_info("    LUT strategy for %s: move all users to new LUT\n", port.name.c_str(ctx)); +                    auto pt = spliceLUT(ctx, packed.get(), port.name, false); +                    new_cells.push_back(std::move(pt)); +                } + +                // Find wire that will be driven by this port. +                const auto pll_out_wire = ctx->getBelPinWire(pll_bel, ctx->portPinFromId(port.name)); +                NPNR_ASSERT(pll_out_wire.index != -1); + +                // Now, constrain all LUTs on the output of the signal to be at +                // the correct Bel relative to the PLL Bel. +                int x = ctx->chip_info->wire_data[pll_out_wire.index].x; +                int y = ctx->chip_info->wire_data[pll_out_wire.index].y; +                int z = 0; +                for (const auto &user : port.net->users) { +                    NPNR_ASSERT(user.cell != nullptr); +                    NPNR_ASSERT(user.cell->type == ctx->id("ICESTORM_LC")); + +                    // TODO(q3k): handle when the Bel might be already the +                    // target of another constraint. +                    NPNR_ASSERT(z < 8); +                    auto target_bel = ctx->getBelByLocation(Loc(x, y, z++)); +                    auto target_bel_name = ctx->getBelName(target_bel).str(ctx); +                    user.cell->attrs[ctx->id("BEL")] = target_bel_name; +                    log_info("    constrained '%s' to %s\n", user.cell->name.c_str(ctx), target_bel_name.c_str()); +                } +            } + +            new_cells.push_back(std::move(packed));          }      } diff --git a/ice40/portpins.inc b/ice40/portpins.inc index d78625d1..f9dac887 100644 --- a/ice40/portpins.inc +++ b/ice40/portpins.inc @@ -118,6 +118,8 @@ X(DYNAMICDELAY_5)  X(DYNAMICDELAY_6)  X(DYNAMICDELAY_7)  X(LOCK) +X(PLLOUT_A) +X(PLLOUT_B)  X(BYPASS)  X(RESETB)  X(LATCHINPUTVALUE)  | 
