From 03223d19505a2db7ebea5599a7bac17206182f20 Mon Sep 17 00:00:00 2001 From: Max Aigner Date: Fri, 10 Jul 2026 16:26:15 +0200 Subject: [PATCH] gatemate: prevent multiplier clusters from overlapping route_mult halos route_mult binds dedicated, locked routing for each CC_MULT cluster with wires computed from the cluster's placed location, reaching a couple of tiles outside the cluster body (the A-passthrough column sits at root.x-1 and its switch-box routes reach further). Nothing reserved those halo wires during placement and there was no spacing rule between multiplier clusters, so once the placer packed two clusters close enough their halos overlapped and route_mult double-bound a shared wire -> abort in bindPip (base_arch.h: w2n_entry == nullptr). It was seed-dependent and only surfaced on denser designs. Add a placement-legality rule in isBelLocationValid: compute a multiplier cluster's halo bounding box (its placed cells' bbox expanded by a margin that covers route_mult's reach) and reject the placement if it overlaps another multiplier cluster's halo. The multiplier cluster roots are cached once in assign_cell_info (via the existing multiplier_* pools) so the check is cheap. Verified on a design that reproducibly triggered the collision: seeds that previously aborted in 'Routing multipliers' now place and route, and a seed that already routed still routes (the rule does not over-constrain). Pairs with the route_mult.cc change that turns the residual assert into an actionable error. Co-Authored-By: Claude --- himbaechel/uarch/gatemate/gatemate.cc | 112 ++++++++++++++++++++++++++ himbaechel/uarch/gatemate/gatemate.h | 9 +++ 2 files changed, 121 insertions(+) diff --git a/himbaechel/uarch/gatemate/gatemate.cc b/himbaechel/uarch/gatemate/gatemate.cc index 4d96ffc2..6cdfaf02 100644 --- a/himbaechel/uarch/gatemate/gatemate.cc +++ b/himbaechel/uarch/gatemate/gatemate.cc @@ -208,6 +208,47 @@ bool GateMateImpl::isBelLocationValid(BelId bel, bool explain_invalid) const return true; } + // Multiplier clusters lay down dedicated, hand-picked LOCKED routing in + // preRoute (route_mult) that reaches into a halo of tiles around the placed + // cluster body. Nothing reserves those wires during placement and there is no + // spacing rule between clusters, so if two multiplier clusters are packed close + // enough their halos overlap and route_mult double-binds a shared wire (the + // base_arch.h bindPip assertion). Forbid any placement where the halo of this + // cell's multiplier cluster would overlap another placed multiplier cluster's + // halo. Placed first so it triggers for every cluster cell type (incl. the + // packed P-register FFs), not just the CPE_LT/L2T4 cells below. + if (!mult_cluster_roots.empty() && cell->cluster != ClusterId()) { + CellInfo *root = get_cluster_root(ctx, cell->cluster); + bool is_mult_cluster = false; + for (auto *r : mult_cluster_roots) { + if (r == root) { + is_mult_cluster = true; + break; + } + } + if (is_mult_cluster) { + int ax0, ay0, ax1, ay1; + if (get_mult_cluster_halo(root, ax0, ay0, ax1, ay1)) { + for (auto *other : mult_cluster_roots) { + if (other == root) + continue; + int bx0, by0, bx1, by1; + // Skip clusters not (fully) placed yet. + if (!get_mult_cluster_halo(other, bx0, by0, bx1, by1)) + continue; + bool disjoint = ax1 < bx0 || bx1 < ax0 || ay1 < by0 || by1 < ay0; + if (!disjoint) { + if (explain_invalid) + log_info("Bel '%s' invalid: multiplier cluster halo overlaps another " + "multiplier cluster.\n", + ctx->nameOfBel(bel)); + return false; + } + } + } + } + } + if (getBelBucketForBel(bel) == id_CPE_FF) { Loc loc = ctx->getBelLocation(bel); const CellInfo *adj_half = ctx->getBoundBelCell( @@ -314,6 +355,51 @@ bool GateMateImpl::getClusterPlacement(ClusterId cluster, BelId root_bel, return getChildPlacement(root_cell, root_loc, placement); } +bool GateMateImpl::get_mult_cluster_halo(CellInfo *root, int &x0, int &y0, int &x1, int &y1) const +{ + // Walk root + constr_children (recursively, like getChildPlacement) and take + // the bounding box of every placed cell. This naturally includes the zero + // driver and A passthrough cells, which are ordinary constr_children. + bool found = false; + std::vector stack; + stack.push_back(root); + while (!stack.empty()) { + CellInfo *c = stack.back(); + stack.pop_back(); + if (c->bel != BelId()) { + Loc loc = ctx->getBelLocation(c->bel); + if (!found) { + x0 = x1 = loc.x; + y0 = y1 = loc.y; + found = true; + } else { + x0 = std::min(x0, loc.x); + x1 = std::max(x1, loc.x); + y0 = std::min(y0, loc.y); + y1 = std::max(y1, loc.y); + } + } + for (auto child : c->constr_children) + stack.push_back(child); + } + if (!found) + return false; + + // route_mult originates its dedicated routing from the A passthrough cells + // (at root.x - 1) and the zero driver, reaching wires at loc.x-1..loc.x+3 and + // loc.y-1..loc.y+1 (plus diagonal hops that terminate on in-cluster mult + // cells), and the SB switchboxes it uses can span a further tile. Relative to + // the all-cells bounding box the excess is ~1-2 tiles per side; a margin of 3 + // covers that with cushion. A slightly generous margin only costs fabric + // spacing; too tight lets the double-bind crash persist. + const int margin = 3; + x0 -= margin; + y0 -= margin; + x1 += margin; + y1 += margin; + return true; +} + void GateMateImpl::prePlace() { assign_cell_info(); } void GateMateImpl::postPlace() @@ -1125,6 +1211,32 @@ void GateMateImpl::assign_cell_info() fc.used = true; } } + + // Cache the root cell of every multiplier cluster so isBelLocationValid can + // check halo overlap without an O(all cells) scan per call. Each multiplier + // cluster has exactly one zero driver, and its members (zero driver, A + // passthroughs) all point at the same cluster root; dedup by root name. + mult_cluster_roots.clear(); + pool seen_roots; + auto record_root = [&](IdString member_name) { + auto it = ctx->cells.find(member_name); + if (it == ctx->cells.end()) + return; + CellInfo *member = it->second.get(); + if (member->cluster == ClusterId()) + return; + CellInfo *root = get_cluster_root(ctx, member->cluster); + if (root == nullptr || seen_roots.count(root->name)) + return; + seen_roots.insert(root->name); + mult_cluster_roots.push_back(root); + }; + for (auto &n : multiplier_zero_drivers) + record_root(n); + for (auto &n : multiplier_a_passthru_lowers) + record_root(n); + for (auto &n : multiplier_a_passthru_uppers) + record_root(n); } // Bel bucket functions diff --git a/himbaechel/uarch/gatemate/gatemate.h b/himbaechel/uarch/gatemate/gatemate.h index 746e5bf8..829d799c 100644 --- a/himbaechel/uarch/gatemate/gatemate.h +++ b/himbaechel/uarch/gatemate/gatemate.h @@ -102,6 +102,10 @@ struct GateMateImpl : HimbaechelAPI pool multiplier_a_passthru_lowers; pool multiplier_a_passthru_uppers; pool multiplier_zero_drivers; + // Cached root cells of every multiplier cluster, populated once at placement + // start (assign_cell_info). Used by isBelLocationValid to keep two multiplier + // clusters' route_mult halos from overlapping. + std::vector mult_cluster_roots; std::vector used_cpes; std::vector pip_data; std::vector pip_mask; @@ -125,6 +129,11 @@ struct GateMateImpl : HimbaechelAPI bool getChildPlacement(const BaseClusterInfo *cluster, Loc root_loc, std::vector> &placement) const; + // Bounding box (in tile coords) of a multiplier cluster's currently-placed + // cells, expanded by a margin covering the route_mult routing halo. Returns + // false if none of the cluster's cells are placed yet. + bool get_mult_cluster_halo(CellInfo *root, int &x0, int &y0, int &x1, int &y1) const; + void write_bitstream(const std::string &device, const std::string &filename); void parse_ccf(const std::string &filename);