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 <noreply@anthropic.com>
This commit is contained in:
Max Aigner 2026-07-10 16:26:15 +02:00
parent ca97eb6ec2
commit 03223d1950
2 changed files with 121 additions and 0 deletions

View File

@ -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<CellInfo *> 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<IdString> 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

View File

@ -102,6 +102,10 @@ struct GateMateImpl : HimbaechelAPI
pool<IdString> multiplier_a_passthru_lowers;
pool<IdString> multiplier_a_passthru_uppers;
pool<IdString> 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<CellInfo *> mult_cluster_roots;
std::vector<bool> used_cpes;
std::vector<uint32_t> pip_data;
std::vector<uint32_t> pip_mask;
@ -125,6 +129,11 @@ struct GateMateImpl : HimbaechelAPI
bool getChildPlacement(const BaseClusterInfo *cluster, Loc root_loc,
std::vector<std::pair<CellInfo *, BelId>> &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);