himbaechel/xilinx: place cascaded RAMB36 pairs as clusters

CASCADEOUTA/B -> CASCADEINA/B are dedicated wires that only reach the
vertically adjacent RAMB36 site in the same column; the router has no
general path for them. Without a placement constraint the two cells
of a depth-cascaded pair land on arbitrary sites and routing fails.

Cluster each cascade pair in pack_bram (analogous to carry chains).
Plain relative (x,y,z) constraints cannot express "next RAMB36 up the
column": bel z indices are not uniform across BRAM tiles. Instead,
precompute a next-RAMB36-in-column map at init and resolve child bels
through a getClusterPlacement override.

Hardware-verified on xc7a100t: cascaded 64K x 1 memories place, route
and function (128K ROM content fingerprint passes).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Joris van Zwieten 2026-07-30 15:49:25 +02:00
parent a652a752de
commit 1c85dee263
3 changed files with 87 additions and 0 deletions

View File

@ -808,6 +808,42 @@ void XC7Packer::pack_bram()
}
}
}
// Cluster cascaded RAMB36 pairs: CASCADEOUT->CASCADEIN are dedicated
// wires that only reach the vertically adjacent RAMB36 site, so the
// pair must be placed together (cf. carry chains). BRAM tiles are 5
// grid rows apart within a column.
int cascade_pairs = 0;
for (auto &cell : ctx->cells) {
CellInfo *lower = cell.second.get();
if (lower->type != id_RAMB36E1_RAMB36E1)
continue;
for (auto pn : {ctx->id("CASCADEOUTA"), ctx->id("CASCADEOUTB")}) {
NetInfo *cn = lower->getPort(pn);
if (cn == nullptr)
continue;
for (auto &usr : cn->users) {
CellInfo *upper = usr.cell;
if (upper == lower || upper->type != id_RAMB36E1_RAMB36E1)
continue;
if (upper->cluster != ClusterId())
continue; // already clustered (e.g. via the A cascade)
if (lower->cluster == ClusterId())
lower->cluster = lower->name;
else if (lower->cluster != lower->name)
continue; // lower is a child of another cluster; unsupported
upper->cluster = lower->name;
lower->constr_children.push_back(upper);
upper->constr_x = 0;
upper->constr_y = 5;
upper->constr_z = 0;
upper->constr_abs_z = false;
++cascade_pairs;
}
}
}
if (cascade_pairs > 0)
log_info(" Clustered %d cascaded BRAM pairs\n", cascade_pairs);
}
void XilinxPacker::pack_inverters()

View File

@ -101,6 +101,49 @@ void XilinxImpl::init(Context *ctx)
auto extra_data = tile_extra_data(i);
tile_status.at(i).site_variant.resize(extra_data->sites.ssize());
}
// Build the next-RAMB36-up-the-column map for cascade placement
std::map<int, std::map<int, BelId>> bram36_by_col; // x -> y -> bel
for (BelId bel : ctx->getBels()) {
if (ctx->getBelType(bel) != id_RAMB36E1_RAMB36E1)
continue;
Loc l = ctx->getBelLocation(bel);
bram36_by_col[l.x][l.y] = bel;
}
for (auto &col : bram36_by_col) {
BelId prev;
int prev_y = 0;
for (auto &entry : col.second) {
if (prev != BelId() && (entry.first - prev_y) <= 6)
next_bram36_up[entry.second] = prev; // grid Y increases downwards: 'up' = smaller y
prev = entry.second;
prev_y = entry.first;
}
}
}
bool XilinxImpl::getClusterPlacement(ClusterId cluster, BelId root_bel,
std::vector<std::pair<CellInfo *, BelId>> &placement) const
{
CellInfo *root_cell = ctx->getClusterRootCell(cluster);
if (root_cell->type == id_RAMB36E1_RAMB36E1) {
if (ctx->getBelType(root_bel) != id_RAMB36E1_RAMB36E1)
return false;
placement.clear();
placement.emplace_back(root_cell, root_bel);
BelId cursor = root_bel;
for (auto child : root_cell->constr_children) {
auto fnd = next_bram36_up.find(cursor);
if (fnd == next_bram36_up.end())
return false;
cursor = fnd->second;
if (ctx->getBelType(cursor) != id_RAMB36E1_RAMB36E1)
return false;
placement.emplace_back(child, cursor);
}
return true;
}
return HimbaechelAPI::getClusterPlacement(cluster, root_bel, placement);
}
SiteIndex XilinxImpl::get_bel_site(BelId bel) const

View File

@ -123,6 +123,14 @@ struct XilinxImpl : HimbaechelAPI
bool isBelLocationValid(BelId bel, bool explain_invalid = false) const override;
bool xc7_logic_tile_valid(IdString tileType, const LogicTileStatus &lts) const;
// BRAM cascade support: CASCADEOUT->CASCADEIN only reaches the next
// RAMB36 site up the column; bel z indices are not uniform across BRAM
// tiles, so relative (x,y,z) constraints cannot express this. Precompute
// the next-RAMB36-in-column map and use it in getClusterPlacement.
dict<BelId, BelId> next_bram36_up;
bool getClusterPlacement(ClusterId cluster, BelId root_bel,
std::vector<std::pair<CellInfo *, BelId>> &placement) const override;
// Pips
bool is_pip_unavail(PipId pip) const;
bool checkPipAvail(PipId pip) const override { return !is_pip_unavail(pip); }