diff options
-rw-r--r-- | README.md | 4 | ||||
-rw-r--r-- | common/command.cc | 4 | ||||
-rw-r--r-- | common/placer1.cc | 272 | ||||
-rw-r--r-- | common/timing_opt.cc | 2 | ||||
-rw-r--r-- | ice40/arch.cc | 4 | ||||
-rw-r--r-- | ice40/arch_place.cc | 36 | ||||
-rw-r--r-- | ice40/pack.cc | 112 |
7 files changed, 377 insertions, 57 deletions
@@ -69,11 +69,11 @@ cmake -DARCH=ice40 -DICEBOX_ROOT=C:/ProgramData/icestorm/share/icebox -DCMAKE_TO cmake --build . --config Release ``` -A simple example that runs on the iCEstick dev board can be found in `ice40/blinky.*`. +A simple example that runs on the iCEstick dev board can be found in `ice40/examples/blinky/blinky.*`. Usage example: ``` -cd ice40 +cd ice40/examples/blinky yosys -p 'synth_ice40 -top blinky -json blinky.json' blinky.v # synthesize into blinky.json nextpnr-ice40 --hx1k --json blinky.json --pcf blinky.pcf --asc blinky.asc # run place and route icepack blinky.asc blinky.bin # generate binary bitstream file diff --git a/common/command.cc b/common/command.cc index 6f4137fe..49081e72 100644 --- a/common/command.cc +++ b/common/command.cc @@ -87,8 +87,8 @@ bool CommandHandler::executeBeforeContext() if (vm.count("log")) { std::string logfilename = vm["log"].as<std::string>(); - logfile = std::ofstream(logfilename); - if (!logfile) + logfile.open(logfilename); + if (!logfile.is_open()) log_error("Failed to open log file '%s' for writing.\n", logfilename.c_str()); log_streams.push_back(std::make_pair(&logfile, LogLevel::LOG_MSG)); } diff --git a/common/placer1.cc b/common/placer1.cc index 98251627..a8ddd8a6 100644 --- a/common/placer1.cc +++ b/common/placer1.cc @@ -64,9 +64,10 @@ class SAPlacer private: struct BoundingBox { + // Actual bounding box int x0 = 0, x1 = 0, y0 = 0, y1 = 0; - bool is_inside_inc(int x, int y) const { return x >= x0 && x <= x1 && y >= y0 && y <= y1; } - bool touches_bounds(int x, int y) const { return x == x0 || x == x1 || y == y0 || y == y1; } + // Number of cells at each extremity + int nx0 = 0, nx1 = 0, ny0 = 0, ny1 = 0; wirelen_t hpwl() const { return wirelen_t((x1 - x0) + (y1 - y0)); } }; @@ -103,15 +104,12 @@ class SAPlacer net_bounds.resize(ctx->nets.size()); net_arc_tcost.resize(ctx->nets.size()); - moveChange.already_bounds_changed.resize(ctx->nets.size()); - moveChange.already_changed_arcs.resize(ctx->nets.size()); old_udata.reserve(ctx->nets.size()); net_by_udata.reserve(ctx->nets.size()); decltype(NetInfo::udata) n = 0; for (auto &net : ctx->nets) { old_udata.emplace_back(net.second->udata); net_arc_tcost.at(n).resize(net.second->users.size()); - moveChange.already_changed_arcs.at(n).resize(net.second->users.size()); net.second->udata = n++; net_by_udata.push_back(net.second.get()); } @@ -252,6 +250,7 @@ class SAPlacer // Calculate costs after initial placement setup_costs(); + moveChange.init(this); curr_wirelen_cost = total_wirelen_cost(); curr_timing_cost = total_timing_cost(); last_wirelen_cost = curr_wirelen_cost; @@ -292,6 +291,24 @@ class SAPlacer } } + if (ctx->debug) { + // Verify correctness of incremental wirelen updates + for (size_t i = 0; i < net_bounds.size(); i++) { + auto net = net_by_udata[i]; + if (ignore_net(net)) + continue; + auto &incr = net_bounds.at(i), gold = get_net_bounds(net); + NPNR_ASSERT(incr.x0 == gold.x0); + NPNR_ASSERT(incr.x1 == gold.x1); + NPNR_ASSERT(incr.y0 == gold.y0); + NPNR_ASSERT(incr.y1 == gold.y1); + NPNR_ASSERT(incr.nx0 == gold.nx0); + NPNR_ASSERT(incr.nx1 == gold.nx1); + NPNR_ASSERT(incr.ny0 == gold.ny0); + NPNR_ASSERT(incr.ny1 == gold.ny1); + } + } + if (curr_wirelen_cost < min_wirelen) { min_wirelen = curr_wirelen_cost; improved = true; @@ -366,6 +383,10 @@ class SAPlacer get_criticalities(ctx, &net_crit); // Need to rebuild costs after criticalities change setup_costs(); + // Reset incremental bounds + moveChange.reset(this); + moveChange.new_net_bounds = net_bounds; + // Recalculate total metric entirely to avoid rounding errors // accumulating over time curr_wirelen_cost = total_wirelen_cost(); @@ -475,7 +496,7 @@ class SAPlacer bool try_swap_position(CellInfo *cell, BelId newBel) { static const double epsilon = 1e-20; - moveChange.reset(); + moveChange.reset(this); if (!require_legal && is_constrained(cell)) return false; BelId oldBel = cell->bel; @@ -587,7 +608,7 @@ class SAPlacer std::vector<std::pair<CellInfo *, BelId>> moves_made; std::vector<std::pair<CellInfo *, BelId>> dest_bels; double delta = 0; - moveChange.reset(); + moveChange.reset(this); if (ctx->debug) log_info("finding cells for chain swap %s\n", cell->name.c_str(ctx)); @@ -619,6 +640,10 @@ class SAPlacer for (const auto &db : dest_bels) { BelId oldBel = swap_cell_bels(db.first, db.second); moves_made.emplace_back(std::make_pair(db.first, oldBel)); + CellInfo *bound = ctx->getBoundBelCell(oldBel); + add_move_cell(moveChange, db.first, oldBel); + if (bound != nullptr) + add_move_cell(moveChange, bound, db.second); } for (const auto &mm : moves_made) { if (!ctx->isBelLocationValid(mm.first->bel) || !check_cell_bel_region(mm.first, mm.first->bel)) @@ -628,9 +653,6 @@ class SAPlacer CellInfo *bound = ctx->getBoundBelCell(mm.second); if (bound && !check_cell_bel_region(bound, bound->bel)) goto swap_fail; - add_move_cell(moveChange, mm.first, mm.second); - if (bound != nullptr) - add_move_cell(moveChange, bound, mm.first->bel); } compute_cost_changes(moveChange); delta = lambda * (moveChange.timing_delta / last_timing_cost) + @@ -717,15 +739,38 @@ class SAPlacer bb.x1 = dloc.x; bb.y0 = dloc.y; bb.y1 = dloc.y; - + bb.nx0 = 1; + bb.nx1 = 1; + bb.ny0 = 1; + bb.ny1 = 1; for (auto user : net->users) { if (user.cell->bel == BelId()) continue; Loc uloc = ctx->getBelLocation(user.cell->bel); - bb.x0 = std::min(bb.x0, uloc.x); - bb.x1 = std::max(bb.x1, uloc.x); - bb.y0 = std::min(bb.y0, uloc.y); - bb.y1 = std::max(bb.y1, uloc.y); + if (bb.x0 == uloc.x) + ++bb.nx0; + else if (uloc.x < bb.x0) { + bb.x0 = uloc.x; + bb.nx0 = 1; + } + if (bb.x1 == uloc.x) + ++bb.nx1; + else if (uloc.x > bb.x1) { + bb.x1 = uloc.x; + bb.nx1 = 1; + } + if (bb.y0 == uloc.y) + ++bb.ny0; + else if (uloc.y < bb.y0) { + bb.y0 = uloc.y; + bb.ny0 = 1; + } + if (bb.y1 == uloc.y) + ++bb.ny1; + else if (uloc.y > bb.y1) { + bb.y1 = uloc.y; + bb.ny1 = 1; + } } return bb; @@ -789,27 +834,53 @@ class SAPlacer // Cost-change-related data for a move struct MoveChangeData { - std::vector<decltype(NetInfo::udata)> bounds_changed_nets; + + enum BoundChangeType + { + NO_CHANGE, + CELL_MOVED_INWARDS, + CELL_MOVED_OUTWARDS, + FULL_RECOMPUTE + }; + + std::vector<decltype(NetInfo::udata)> bounds_changed_nets_x, bounds_changed_nets_y; std::vector<std::pair<decltype(NetInfo::udata), size_t>> changed_arcs; - std::vector<bool> already_bounds_changed; + std::vector<BoundChangeType> already_bounds_changed_x, already_bounds_changed_y; std::vector<std::vector<bool>> already_changed_arcs; - std::vector<std::pair<decltype(NetInfo::udata), BoundingBox>> new_net_bounds; + std::vector<BoundingBox> new_net_bounds; std::vector<std::pair<std::pair<decltype(NetInfo::udata), size_t>, double>> new_arc_costs; wirelen_t wirelen_delta = 0; double timing_delta = 0; - void reset() + void init(SAPlacer *p) + { + already_bounds_changed_x.resize(p->ctx->nets.size()); + already_bounds_changed_y.resize(p->ctx->nets.size()); + already_changed_arcs.resize(p->ctx->nets.size()); + for (auto &net : p->ctx->nets) { + already_changed_arcs.at(net.second->udata).resize(net.second->users.size()); + } + new_net_bounds = p->net_bounds; + } + + void reset(SAPlacer *p) { - for (auto bc : bounds_changed_nets) - already_bounds_changed[bc] = false; + for (auto bc : bounds_changed_nets_x) { + new_net_bounds[bc] = p->net_bounds[bc]; + already_bounds_changed_x[bc] = NO_CHANGE; + } + for (auto bc : bounds_changed_nets_y) { + new_net_bounds[bc] = p->net_bounds[bc]; + already_bounds_changed_y[bc] = NO_CHANGE; + } for (const auto &tc : changed_arcs) already_changed_arcs[tc.first][tc.second] = false; - bounds_changed_nets.clear(); + bounds_changed_nets_x.clear(); + bounds_changed_nets_y.clear(); changed_arcs.clear(); - new_net_bounds.clear(); new_arc_costs.clear(); wirelen_delta = 0; timing_delta = 0; @@ -828,14 +899,128 @@ class SAPlacer continue; if (ignore_net(pn)) continue; - const BoundingBox &curr_bounds = net_bounds[pn->udata]; - // If the old location was at the edge of the bounds, or the new location exceeds the bounds, - // an update is needed - if (curr_bounds.touches_bounds(old_loc.x, old_loc.y) || !curr_bounds.is_inside_inc(curr_loc.x, curr_loc.y)) - if (!mc.already_bounds_changed[pn->udata]) { - mc.bounds_changed_nets.push_back(pn->udata); - mc.already_bounds_changed[pn->udata] = true; + BoundingBox &curr_bounds = mc.new_net_bounds[pn->udata]; + // Incremental bounding box updates + // Note that everything other than full updates are applied immediately rather than being queued, + // so further updates to the same net in the same move are dealt with correctly. + // If a full update is already queued, this can be considered a no-op + if (mc.already_bounds_changed_x[pn->udata] != MoveChangeData::FULL_RECOMPUTE) { + // Bounds x0 + if (curr_loc.x < curr_bounds.x0) { + // Further out than current bounds x0 + curr_bounds.x0 = curr_loc.x; + curr_bounds.nx0 = 1; + if (mc.already_bounds_changed_x[pn->udata] == MoveChangeData::NO_CHANGE) { + // Checking already_bounds_changed_x ensures that each net is only added once + // to bounds_changed_nets, lest we add its HPWL change multiple times skewing the + // overall cost change + mc.already_bounds_changed_x[pn->udata] = MoveChangeData::CELL_MOVED_OUTWARDS; + mc.bounds_changed_nets_x.push_back(pn->udata); + } + } else if (curr_loc.x == curr_bounds.x0 && old_loc.x > curr_bounds.x0) { + curr_bounds.nx0++; + if (mc.already_bounds_changed_x[pn->udata] == MoveChangeData::NO_CHANGE) { + mc.already_bounds_changed_x[pn->udata] = MoveChangeData::CELL_MOVED_OUTWARDS; + mc.bounds_changed_nets_x.push_back(pn->udata); + } + } else if (old_loc.x == curr_bounds.x0 && curr_loc.x > curr_bounds.x0) { + if (mc.already_bounds_changed_x[pn->udata] == MoveChangeData::NO_CHANGE) + mc.bounds_changed_nets_x.push_back(pn->udata); + if (curr_bounds.nx0 == 1) { + mc.already_bounds_changed_x[pn->udata] = MoveChangeData::FULL_RECOMPUTE; + } else { + curr_bounds.nx0--; + if (mc.already_bounds_changed_x[pn->udata] == MoveChangeData::NO_CHANGE) + mc.already_bounds_changed_x[pn->udata] = MoveChangeData::CELL_MOVED_INWARDS; + } } + + // Bounds x1 + if (curr_loc.x > curr_bounds.x1) { + // Further out than current bounds x1 + curr_bounds.x1 = curr_loc.x; + curr_bounds.nx1 = 1; + if (mc.already_bounds_changed_x[pn->udata] == MoveChangeData::NO_CHANGE) { + // Checking already_bounds_changed_x ensures that each net is only added once + // to bounds_changed_nets, lest we add its HPWL change multiple times skewing the + // overall cost change + mc.already_bounds_changed_x[pn->udata] = MoveChangeData::CELL_MOVED_OUTWARDS; + mc.bounds_changed_nets_x.push_back(pn->udata); + } + } else if (curr_loc.x == curr_bounds.x1 && old_loc.x < curr_bounds.x1) { + curr_bounds.nx1++; + if (mc.already_bounds_changed_x[pn->udata] == MoveChangeData::NO_CHANGE) { + mc.already_bounds_changed_x[pn->udata] = MoveChangeData::CELL_MOVED_OUTWARDS; + mc.bounds_changed_nets_x.push_back(pn->udata); + } + } else if (old_loc.x == curr_bounds.x1 && curr_loc.x < curr_bounds.x1) { + if (mc.already_bounds_changed_x[pn->udata] == MoveChangeData::NO_CHANGE) + mc.bounds_changed_nets_x.push_back(pn->udata); + if (curr_bounds.nx1 == 1) { + mc.already_bounds_changed_x[pn->udata] = MoveChangeData::FULL_RECOMPUTE; + } else { + curr_bounds.nx1--; + if (mc.already_bounds_changed_x[pn->udata] == MoveChangeData::NO_CHANGE) + mc.already_bounds_changed_x[pn->udata] = MoveChangeData::CELL_MOVED_INWARDS; + } + } + } + if (mc.already_bounds_changed_y[pn->udata] != MoveChangeData::FULL_RECOMPUTE) { + // Bounds y0 + if (curr_loc.y < curr_bounds.y0) { + // Further out than current bounds y0 + curr_bounds.y0 = curr_loc.y; + curr_bounds.ny0 = 1; + if (mc.already_bounds_changed_y[pn->udata] == MoveChangeData::NO_CHANGE) { + mc.already_bounds_changed_y[pn->udata] = MoveChangeData::CELL_MOVED_OUTWARDS; + mc.bounds_changed_nets_y.push_back(pn->udata); + } + } else if (curr_loc.y == curr_bounds.y0 && old_loc.y > curr_bounds.y0) { + curr_bounds.ny0++; + if (mc.already_bounds_changed_y[pn->udata] == MoveChangeData::NO_CHANGE) { + mc.already_bounds_changed_y[pn->udata] = MoveChangeData::CELL_MOVED_OUTWARDS; + mc.bounds_changed_nets_y.push_back(pn->udata); + } + } else if (old_loc.y == curr_bounds.y0 && curr_loc.y > curr_bounds.y0) { + if (mc.already_bounds_changed_y[pn->udata] == MoveChangeData::NO_CHANGE) + mc.bounds_changed_nets_y.push_back(pn->udata); + if (curr_bounds.ny0 == 1) { + mc.already_bounds_changed_y[pn->udata] = MoveChangeData::FULL_RECOMPUTE; + } else { + curr_bounds.ny0--; + if (mc.already_bounds_changed_y[pn->udata] == MoveChangeData::NO_CHANGE) + mc.already_bounds_changed_y[pn->udata] = MoveChangeData::CELL_MOVED_INWARDS; + } + } + + // Bounds y1 + if (curr_loc.y > curr_bounds.y1) { + // Further out than current bounds y1 + curr_bounds.y1 = curr_loc.y; + curr_bounds.ny1 = 1; + if (mc.already_bounds_changed_y[pn->udata] == MoveChangeData::NO_CHANGE) { + mc.already_bounds_changed_y[pn->udata] = MoveChangeData::CELL_MOVED_OUTWARDS; + mc.bounds_changed_nets_y.push_back(pn->udata); + } + } else if (curr_loc.y == curr_bounds.y1 && old_loc.y < curr_bounds.y1) { + curr_bounds.ny1++; + if (mc.already_bounds_changed_y[pn->udata] == MoveChangeData::NO_CHANGE) { + mc.already_bounds_changed_y[pn->udata] = MoveChangeData::CELL_MOVED_OUTWARDS; + mc.bounds_changed_nets_y.push_back(pn->udata); + } + } else if (old_loc.y == curr_bounds.y1 && curr_loc.y < curr_bounds.y1) { + if (mc.already_bounds_changed_y[pn->udata] == MoveChangeData::NO_CHANGE) + mc.bounds_changed_nets_y.push_back(pn->udata); + if (curr_bounds.ny1 == 1) { + mc.already_bounds_changed_y[pn->udata] = MoveChangeData::FULL_RECOMPUTE; + } else { + curr_bounds.ny1--; + if (mc.already_bounds_changed_y[pn->udata] == MoveChangeData::NO_CHANGE) + mc.already_bounds_changed_y[pn->udata] = MoveChangeData::CELL_MOVED_INWARDS; + } + } + } + if (ctx->timing_driven && int(pn->users.size()) < cfg.timingFanoutThresh) { // Output ports - all arcs change timing if (port.second.type == PORT_OUT) { @@ -860,13 +1045,22 @@ class SAPlacer void compute_cost_changes(MoveChangeData &md) { - for (const auto &bc : md.bounds_changed_nets) { - wirelen_t old_hpwl = net_bounds.at(bc).hpwl(); - auto bounds = get_net_bounds(net_by_udata.at(bc)); - md.new_net_bounds.emplace_back(std::make_pair(bc, bounds)); - md.wirelen_delta += (bounds.hpwl() - old_hpwl); - md.already_bounds_changed[bc] = false; + for (const auto &bc : md.bounds_changed_nets_x) { + if (md.already_bounds_changed_x[bc] == MoveChangeData::FULL_RECOMPUTE) + md.new_net_bounds[bc] = get_net_bounds(net_by_udata[bc]); + } + for (const auto &bc : md.bounds_changed_nets_y) { + if (md.already_bounds_changed_x[bc] != MoveChangeData::FULL_RECOMPUTE && + md.already_bounds_changed_y[bc] == MoveChangeData::FULL_RECOMPUTE) + md.new_net_bounds[bc] = get_net_bounds(net_by_udata[bc]); } + + for (const auto &bc : md.bounds_changed_nets_x) + md.wirelen_delta += md.new_net_bounds[bc].hpwl() - net_bounds[bc].hpwl(); + for (const auto &bc : md.bounds_changed_nets_y) + if (md.already_bounds_changed_x[bc] == MoveChangeData::NO_CHANGE) + md.wirelen_delta += md.new_net_bounds[bc].hpwl() - net_bounds[bc].hpwl(); + if (ctx->timing_driven) { for (const auto &tc : md.changed_arcs) { double old_cost = net_arc_tcost.at(tc.first).at(tc.second); @@ -880,8 +1074,10 @@ class SAPlacer void commit_cost_changes(MoveChangeData &md) { - for (const auto &bc : md.new_net_bounds) - net_bounds[bc.first] = bc.second; + for (const auto &bc : md.bounds_changed_nets_x) + net_bounds[bc] = md.new_net_bounds[bc]; + for (const auto &bc : md.bounds_changed_nets_y) + net_bounds[bc] = md.new_net_bounds[bc]; for (const auto &tc : md.new_arc_costs) net_arc_tcost[tc.first.first].at(tc.first.second) = tc.second; curr_wirelen_cost += md.wirelen_delta; diff --git a/common/timing_opt.cc b/common/timing_opt.cc index c0968db7..898222ab 100644 --- a/common/timing_opt.cc +++ b/common/timing_opt.cc @@ -84,6 +84,7 @@ class TimingOptimiser bool optimise() { log_info("Running timing-driven placement optimisation...\n"); + ctx->lock(); if (ctx->verbose) timing_analysis(ctx, false, true, false, false); for (int i = 0; i < 30; i++) { @@ -96,6 +97,7 @@ class TimingOptimiser if (ctx->verbose) timing_analysis(ctx, false, true, false, false); } + ctx->unlock(); return true; } diff --git a/ice40/arch.cc b/ice40/arch.cc index bfcadc0b..d536ad35 100644 --- a/ice40/arch.cc +++ b/ice40/arch.cc @@ -1221,8 +1221,8 @@ void Arch::assignCellInfo(CellInfo *cell) } else if (cell->type == id_SB_IO) { cell->ioInfo.lvds = str_or_default(cell->params, id_IO_STANDARD, "SB_LVCMOS") == "SB_LVDS_INPUT"; cell->ioInfo.global = bool_or_default(cell->attrs, this->id("GLOBAL")); - cell->ioInfo.pintype = int_or_default(cell->attrs, this->id("PIN_TYPE")); - cell->ioInfo.negtrig = bool_or_default(cell->attrs, this->id("NEG_TRIGGER")); + cell->ioInfo.pintype = int_or_default(cell->params, this->id("PIN_TYPE")); + cell->ioInfo.negtrig = bool_or_default(cell->params, this->id("NEG_TRIGGER")); } else if (cell->type == id_SB_GB) { cell->gbInfo.forPadIn = bool_or_default(cell->attrs, this->id("FOR_PAD_IN")); diff --git a/ice40/arch_place.cc b/ice40/arch_place.cc index 90bb62b9..ede8d47f 100644 --- a/ice40/arch_place.cc +++ b/ice40/arch_place.cc @@ -91,6 +91,18 @@ bool Arch::isBelLocationValid(BelId bel) const } } +static inline bool _io_pintype_need_clk_in(unsigned pin_type) { return (pin_type & 0x01) == 0x00; } + +static inline bool _io_pintype_need_clk_out(unsigned pin_type) +{ + return ((pin_type & 0x30) == 0x30) || ((pin_type & 0x3c) && ((pin_type & 0x0c) != 0x08)); +} + +static inline bool _io_pintype_need_clk_en(unsigned pin_type) +{ + return _io_pintype_need_clk_in(pin_type) || _io_pintype_need_clk_out(pin_type); +} + bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const { if (cell->type == id_ICESTORM_LC) { @@ -157,6 +169,30 @@ bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const CellInfo *compCell = getBoundBelCell(compBel); if (compCell && compCell->ioInfo.lvds) return false; + + // Check for conflicts on shared nets + // - CLOCK_ENABLE + // - OUTPUT_CLK + // - INPUT_CLK + if (compCell) { + bool use[6] = { + _io_pintype_need_clk_in(cell->ioInfo.pintype), + _io_pintype_need_clk_in(compCell->ioInfo.pintype), + _io_pintype_need_clk_out(cell->ioInfo.pintype), + _io_pintype_need_clk_out(compCell->ioInfo.pintype), + _io_pintype_need_clk_en(cell->ioInfo.pintype), + _io_pintype_need_clk_en(compCell->ioInfo.pintype), + }; + NetInfo *nets[] = { + cell->ports[id_INPUT_CLK].net, compCell->ports[id_INPUT_CLK].net, + cell->ports[id_OUTPUT_CLK].net, compCell->ports[id_OUTPUT_CLK].net, + cell->ports[id_CLOCK_ENABLE].net, compCell->ports[id_CLOCK_ENABLE].net, + }; + + for (int i = 0; i < 6; i++) + if (use[i] && (nets[i] != nets[i ^ 1]) && (use[i ^ 1] || (nets[i ^ 1] != nullptr))) + return false; + } } return getBelPackagePin(bel) != ""; diff --git a/ice40/pack.cc b/ice40/pack.cc index b1efe5b2..2ba0bb5a 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -383,12 +383,9 @@ static void pack_constants(Context *ctx) } } -static std::unique_ptr<CellInfo> create_padin_gbuf(Context *ctx, CellInfo *cell, IdString port_name, - std::string gbuf_name) +static BelId find_padin_gbuf(Context *ctx, BelId bel, IdString port_name) { - // Find the matching SB_GB BEL connected to the same global network BelId gb_bel; - BelId bel = ctx->getBelByName(ctx->id(cell->attrs[ctx->id("BEL")])); auto wire = ctx->getBelPinWire(bel, port_name); if (wire == WireId()) @@ -401,6 +398,15 @@ static std::unique_ptr<CellInfo> create_padin_gbuf(Context *ctx, CellInfo *cell, } } + return gb_bel; +} + +static std::unique_ptr<CellInfo> create_padin_gbuf(Context *ctx, CellInfo *cell, IdString port_name, + std::string gbuf_name) +{ + // Find the matching SB_GB BEL connected to the same global network + BelId bel = ctx->getBelByName(ctx->id(cell->attrs[ctx->id("BEL")])); + BelId gb_bel = find_padin_gbuf(ctx, bel, port_name); NPNR_ASSERT(gb_bel != BelId()); // Create a SB_GB Cell and lock it there @@ -502,6 +508,19 @@ static void pack_io(Context *ctx) // Make it a normal SB_IO with global marker ci->type = ctx->id("SB_IO"); ci->attrs[ctx->id("GLOBAL")] = "1"; + } else if (is_sb_io(ctx, ci)) { + // Disconnect unused inputs + NetInfo *net_in0 = ci->ports.count(id_D_IN_0) ? ci->ports[id_D_IN_0].net : nullptr; + NetInfo *net_in1 = ci->ports.count(id_D_IN_1) ? ci->ports[id_D_IN_1].net : nullptr; + + if (net_in0 != nullptr && net_in0->users.size() == 0) { + delete_nets.insert(net_in0->name); + ci->ports[id_D_IN_0].net = nullptr; + } + if (net_in1 != nullptr && net_in1->users.size() == 0) { + delete_nets.insert(net_in1->name); + ci->ports[id_D_IN_1].net = nullptr; + } } } for (auto pcell : packed_cells) { @@ -691,14 +710,15 @@ static void promote_globals(Context *ctx) // Figure out where to place PLLs static void place_plls(Context *ctx) { - std::map<BelId, std::pair<BelPin, BelPin>> pll_all_bels; + std::map<BelId, std::tuple<BelPin, BelId, BelPin, BelId>> pll_all_bels; std::map<BelId, CellInfo *> pll_used_bels; std::vector<CellInfo *> pll_cells; std::map<BelId, CellInfo *> bel2io; + std::map<BelId, CellInfo *> bel2gb; log_info("Placing PLLs..\n"); - // Find all the PLLs BELs and matching IO sites + // Find all the PLLs BELs and matching IO sites and global networks for (auto bel : ctx->getBels()) { if (ctx->getBelType(bel) != id_ICESTORM_PLL) continue; @@ -707,8 +727,10 @@ static void place_plls(Context *ctx) auto io_a_pin = ctx->getIOBSharingPLLPin(bel, id_PLLOUT_A); auto io_b_pin = ctx->getIOBSharingPLLPin(bel, id_PLLOUT_B); + auto gb_a = find_padin_gbuf(ctx, bel, id_PLLOUT_A_GLOBAL); + auto gb_b = find_padin_gbuf(ctx, bel, id_PLLOUT_B_GLOBAL); - pll_all_bels[bel] = std::make_pair(io_a_pin, io_b_pin); + pll_all_bels[bel] = std::make_tuple(io_a_pin, gb_a, io_b_pin, gb_b); } // Find all the PLLs cells we need to place and do pre-checks @@ -813,7 +835,8 @@ static void place_plls(Context *ctx) for (auto placed_pll : pll_used_bels) { BelPin pll_io_a, pll_io_b; - std::tie(pll_io_a, pll_io_b) = pll_all_bels[placed_pll.first]; + BelId gb_a, gb_b; + std::tie(pll_io_a, gb_a, pll_io_b, gb_b) = pll_all_bels[placed_pll.first]; if (io_bel == pll_io_a.bel) { // All the PAD type PLL stuff already checked above,so only // check for conflict with a user placed CORE PLL @@ -831,6 +854,47 @@ static void place_plls(Context *ctx) bel2io[io_bel] = io_ci; } + // Scan all SB_GBs to check for conflicts with PLL BELs + for (auto gb_cell : sorted(ctx->cells)) { + CellInfo *gb_ci = gb_cell.second; + if (!is_gbuf(ctx, gb_ci)) + continue; + + // Only consider the bound ones + if (!gb_ci->attrs.count(ctx->id("BEL"))) + continue; + + // Check all placed PLL (either forced by user, or forced by PACKAGEPIN) + BelId gb_bel = ctx->getBelByName(ctx->id(gb_ci->attrs[ctx->id("BEL")])); + + for (auto placed_pll : pll_used_bels) { + CellInfo *ci = placed_pll.second; + + // Used global connections + bool gb_a_used = ci->ports.count(id_PLLOUT_A_GLOBAL) && (ci->ports[id_PLLOUT_A_GLOBAL].net != nullptr) && + (ci->ports[id_PLLOUT_A_GLOBAL].net->users.size() > 0); + bool gb_b_used = is_sb_pll40_dual(ctx, ci) && ci->ports.count(id_PLLOUT_B_GLOBAL) && + (ci->ports[id_PLLOUT_B_GLOBAL].net != nullptr) && + (ci->ports[id_PLLOUT_B_GLOBAL].net->users.size() > 0); + + // Check for conflict + BelPin pll_io_a, pll_io_b; + BelId gb_a, gb_b; + std::tie(pll_io_a, gb_a, pll_io_b, gb_b) = pll_all_bels[placed_pll.first]; + if (gb_a_used && (gb_bel == gb_a)) { + log_error("PLL '%s' A output conflict with SB_GB '%s'\n", placed_pll.second->name.c_str(ctx), + gb_cell.second->name.c_str(ctx)); + } + if (gb_b_used && (gb_bel == gb_b)) { + log_error("PLL '%s' B output conflicts with SB_GB '%s'\n", placed_pll.second->name.c_str(ctx), + gb_cell.second->name.c_str(ctx)); + } + } + + // Save for later checks + bel2gb[gb_bel] = gb_ci; + } + // Scan all the CORE PLLs and place them in remaining available PLL BELs // (in two pass ... first do the dual ones, harder to place, then single port) for (int i = 0; i < 2; i++) { @@ -849,6 +913,13 @@ static void place_plls(Context *ctx) log_error("PLL '%s' is of CORE type but doesn't have a valid REFERENCECLK connection\n", ci->name.c_str(ctx)); + // Used global connections + bool gb_a_used = ci->ports.count(id_PLLOUT_A_GLOBAL) && (ci->ports[id_PLLOUT_A_GLOBAL].net != nullptr) && + (ci->ports[id_PLLOUT_A_GLOBAL].net->users.size() > 0); + bool gb_b_used = is_sb_pll40_dual(ctx, ci) && ci->ports.count(id_PLLOUT_B_GLOBAL) && + (ci->ports[id_PLLOUT_B_GLOBAL].net != nullptr) && + (ci->ports[id_PLLOUT_B_GLOBAL].net->users.size() > 0); + // Could this be a PAD PLL ? bool could_be_pad = false; BelId pad_bel; @@ -861,7 +932,8 @@ static void place_plls(Context *ctx) if (pll_used_bels.count(bel_pll.first)) continue; BelPin pll_io_a, pll_io_b; - std::tie(pll_io_a, pll_io_b) = bel_pll.second; + BelId gb_a, gb_b; + std::tie(pll_io_a, gb_a, pll_io_b, gb_b) = bel_pll.second; if (bel2io.count(pll_io_a.bel)) { if (pll_io_a.bel == pad_bel) could_be_pad = !bel2io.count(pll_io_b.bel) || !is_sb_pll40_dual(ctx, ci); @@ -869,6 +941,10 @@ static void place_plls(Context *ctx) } if (bel2io.count(pll_io_b.bel) && is_sb_pll40_dual(ctx, ci)) continue; + if (gb_a_used && bel2gb.count(gb_a)) + continue; + if (gb_b_used && bel2gb.count(gb_b)) + continue; found_bel = bel_pll.first; break; } @@ -1278,10 +1354,20 @@ static void pack_special(Context *ctx) else continue; - std::unique_ptr<CellInfo> gb = - create_padin_gbuf(ctx, packed.get(), pi.name, - "$gbuf_" + ci->name.str(ctx) + "_pllout_" + (is_b_port ? "b" : "a")); - new_cells.push_back(std::move(gb)); + // Only if there is actually a net ... + if (pi.net != nullptr) { + // ... and it's used + if (pi.net->users.size() > 0) { + std::unique_ptr<CellInfo> gb = + create_padin_gbuf(ctx, packed.get(), pi.name, + "$gbuf_" + ci->name.str(ctx) + "_pllout_" + (is_b_port ? "b" : "a")); + new_cells.push_back(std::move(gb)); + } else { + // If not, remove it to avoid routing issues + ctx->nets.erase(pi.net->name); + packed->ports[pi.name].net = nullptr; + } + } } new_cells.push_back(std::move(packed)); |