diff --git a/common/nextpnr.h b/common/nextpnr.h index 021772fe..9ee00a50 100644 --- a/common/nextpnr.h +++ b/common/nextpnr.h @@ -173,6 +173,9 @@ struct Loc bool operator==(const Loc &other) const { return (x == other.x) && (y == other.y) && (z == other.z); } bool operator!=(const Loc &other) const { return (x != other.x) || (y != other.y) || (z == other.z); } + + Loc(int x, int y, int z) : x(x), y(y), z(z) {} + Loc() {} }; NEXTPNR_NAMESPACE_END diff --git a/common/router1.cc b/common/router1.cc index fbf3c467..21c54e4c 100644 --- a/common/router1.cc +++ b/common/router1.cc @@ -210,6 +210,7 @@ struct Router else overtimeRevisitCnt++; } + QueuedWire next_qw; next_qw.wire = next_wire; diff --git a/gui/fpgaviewwidget.cc b/gui/fpgaviewwidget.cc index e21af678..cd042af5 100644 --- a/gui/fpgaviewwidget.cc +++ b/gui/fpgaviewwidget.cc @@ -400,6 +400,7 @@ void FPGAViewWidget::paintGL() // Calculate world thickness to achieve a screen 1px/1.1px line. float thick1Px = mouseToWorldCoordinates(1, 0).x(); float thick11Px = mouseToWorldCoordinates(1.1, 0).x(); + float thick2Px = mouseToWorldCoordinates(2, 0).x(); // Draw grid. auto grid = LineShaderData(); @@ -418,7 +419,7 @@ void FPGAViewWidget::paintGL() for (int i = 0; i < 8; i++) lineShader_.draw(rendererData_->highlighted[i], colors_.highlight[i], thick11Px, matrix); - lineShader_.draw(rendererData_->selected, colors_.selected, thick11Px, matrix); + lineShader_.draw(rendererData_->selected, colors_.selected, thick2Px, matrix); rendererDataLock_.unlock(); } diff --git a/ice40/arch.cc b/ice40/arch.cc index 65b21afd..d6ef800d 100644 --- a/ice40/arch.cc +++ b/ice40/arch.cc @@ -331,11 +331,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/bitstream.cc b/ice40/bitstream.cc index 9ac8e857..77d92a89 100644 --- a/ice40/bitstream.cc +++ b/ice40/bitstream.cc @@ -74,23 +74,33 @@ void set_config(const TileInfoPOD &ti, std::vector> &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 inver them. +void set_ie_bit_logical(const Context *ctx, const TileInfoPOD &ti, std::vector> &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); } } int get_param_or_def(const CellInfo *cell, const IdString param, int defval = 0) { auto found = cell->params.find(param); - if (found != cell->params.end()) + if (found != cell->params.end()) { return std::stoi(found->second); - else + } else return defval; } @@ -117,7 +127,7 @@ static const BelConfigPOD &get_ec_config(const ChipInfoPOD *chip, BelId bel) typedef std::vector>>> 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 +135,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 +143,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> ¶ms, bool string_style) + const std::vector> ¶ms, bool string_style, std::string prefix) { const ChipInfoPOD *chip = ctx->chip_info; const auto &bc = get_ec_config(chip, cell->bel); @@ -163,10 +173,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 +268,13 @@ void write_asc(const Context *ctx, std::ostream &out) } } } + + std::unordered_set sb_io_used_by_pll; + std::unordered_set sb_io_used_by_user; + // 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 +319,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_user.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 +421,79 @@ 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> 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; + + // Find SB_IO Bel in net that's driving a wire. That's the one + // we're routing the signal through. + // TODO(q3k): Is there a nicer way to do this? + bool found = false; + + // For every wire in the PLLOUT net... + for (auto wp : port.second.net->wires) { + // ... get its' uphill bel ... + auto bel = ctx->getBelPinUphill(wp.first).bel; + if (bel == BelId()) { + continue; + } + // ... and check if it's an SB_IO. + if (ctx->getBelType(bel) != TYPE_SB_IO) + continue; + + // Check that this SB_IO is either unused or just used as an output. + const BelInfoPOD &io_beli = ci.bel_data[bel.index]; + auto io_loc = Loc(io_beli.x, io_beli.y, io_beli.z); + + if (sb_io_used_by_user.count(io_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(Loc(io_beli.x, io_beli.y, io_beli.z)); + + // Get IE/REN config location (cf. http://www.clifford.at/icestorm/io_tile.html) + auto ieren = get_ieren(bi, io_beli.x, io_beli.y, io_beli.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_beli.y).at(io_beli.x), "IOB_" + std::to_string(io_beli.z) + ".PINTYPE_0", true); + + found = true; + break; + } + + if (!found) { + log_error("Could not find SB_IO forwarding PLL '%s' %s signal\n", + cell.second->name.c_str(ctx), port.second.name.c_str(ctx)); + } + } + } else { NPNR_ASSERT(false); } @@ -416,14 +504,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..3215a38c 100644 --- a/ice40/cells.cc +++ b/ice40/cells.cc @@ -207,6 +207,40 @@ std::unique_ptr 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 +346,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..404f401c 100644 --- a/ice40/cells.h +++ b/ice40/cells.h @@ -71,6 +71,15 @@ 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"); +} + +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 0e8e3ba7..63f08e36 100644 --- a/ice40/chipdb.py +++ b/ice40/chipdb.py @@ -184,6 +184,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) @@ -591,6 +593,43 @@ 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_pll_clock_output(bel, ec, ec_entry): + #print('add_pll_clock_output', ec, ec_entry) + pll_x, pll_y, pll_z = ec[1], ec[2], ec[3] + port = ec_entry[0] + io_x, io_y, io_z = ec_entry[1] + io_z = int(io_z) + + global num_wires + wire_idx = num_wires + num_wires = num_wires + 1 + + wire_xy[wire_idx] = [(pll_x, pll_y)] + + wire_names_r[wire_idx] = (pll_x, pll_y, port) + wire_names[(pll_x, pll_y, port)] = wire_idx + wire_segments[wire_idx] = { + (pll_x, pll_y): port, + (io_x, io_y): 'PLLIN', + } + + wire_downhill_belports[wire_idx] = {(bel, port),} + bel_wires[bel].append((wire_idx, port)) + + io_wire = wire_names[(io_x, io_y, 'io_{}/D_IN_0'.format(io_z))] + wire_downhill[wire_idx] = {io_wire,} + if io_wire not in wire_uphill: + wire_uphill[io_wire] = set() + wire_uphill[io_wire].add(wire_idx) + + switches.append((io_x, io_y, 0, [])) + switchnum = len(switches) - 1 + pip_xy[(wire_idx, io_wire)] = (io_x, io_y, 0, switchnum) + + def add_bel_ec(ec): ectype, x, y, z = ec bel = len(bel_name) @@ -605,6 +644,8 @@ 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): + add_pll_clock_output(bel, ec, entry) 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 &g, int x, int y, GfxTileWireId id, GraphicElement::style_t style); diff --git a/ice40/pack.cc b/ice40/pack.cc index 7e2e389c..f63e14f6 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -591,6 +591,38 @@ 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 packed = + create_ice_cell(ctx, ctx->id("ICESTORM_PLL"), ci->name.str(ctx) + "_PLL"); + packed_cells.insert(ci->name); + 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)); + } + new_cells.push_back(std::move(packed)); } } diff --git a/ice40/pcf.cc b/ice40/pcf.cc index ac1c8598..3d0d0d84 100644 --- a/ice40/pcf.cc +++ b/ice40/pcf.cc @@ -62,6 +62,31 @@ bool apply_pcf(Context *ctx, std::istream &in) log_info("constrained '%s' to bel '%s'\n", cell.c_str(), fnd_cell->second->attrs[ctx->id("BEL")].c_str()); } + } else if (cmd == "set_loc") { + size_t args_end = 1; + while (args_end < words.size() && words.at(args_end).at(0) == '-') + args_end++; + std::string cell = words.at(args_end); + std::string x = words.at(args_end + 1); + std::string y = words.at(args_end + 2); + std::string z = words.at(args_end + 3); + auto fnd_cell = ctx->cells.find(ctx->id(cell)); + if (fnd_cell == ctx->cells.end()) { + log_error("unmatched pcf constraint %s\n", cell.c_str()); + } + + Loc loc; + loc.x = std::stoi(x); + loc.y = std::stoi(y); + loc.z = std::stoi(z); + auto bel = ctx->getBelByLocation(loc); + if (bel == BelId()) { + log_error("constrain '%s': unknown bel\n", line.c_str()); + } + + fnd_cell->second->attrs[ctx->id("BEL")] = ctx->getBelName(bel).str(ctx); + log_info("constrained '%s' to bel '%s'\n", cell.c_str(), ctx->getBelName(bel).c_str(ctx)); + } else { log_error("unsupported pcf command '%s'\n", cmd.c_str()); } 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)