xilinx: Support cascaded IOSERDES and TMDS

Signed-off-by: gatecat <gatecat@ds0.me>
This commit is contained in:
gatecat 2026-03-05 13:59:35 +01:00
parent 4f27338b23
commit 4ace8952d3
5 changed files with 118 additions and 19 deletions

View File

@ -171,6 +171,12 @@ X(OLOGICE3_MISR)
X(OSERDESE2)
X(OSERDESE2_OSERDESE2)
X(SHIFTIN1)
X(SHIFTIN2)
X(SHIFTOUT1)
X(SHIFTOUT2)
X(TBYTEIN)
X(ILOGICE3)
X(ILOGICE3_IFF)
X(ILOGICE3_ZHOLD_DELAY)

View File

@ -948,6 +948,17 @@ struct FasmBackend
write_bit("SSTL12_SSTL135_SSTL15.IN");
}
// IN_TERM.NONE and IN_ONLY for TMDS_33 output, e.g. HDMI signals
if (is_output && is_diff) {
if (is_tmds33 && yLoc == 1) {
if (pad->attrs.count(id_IN_TERM))
write_bit("IN_TERM." + pad->attrs.at(id_IN_TERM).as_string());
else
write_bit("IN_TERM.NONE");
write_bit("LVCMOS12_LVCMOS15_LVCMOS18_LVCMOS25_LVCMOS33_LVDS_25_LVTTL_SSTL135_SSTL15_TMDS_33.IN_ONLY");
}
}
write_bit("PULLTYPE." + pulltype);
pop(); // IOB_YN
@ -1064,7 +1075,12 @@ struct FasmBackend
write_bit("ODDR.DDR_CLK_EDGE.SAME_EDGE");
write_bit("ODDR.SRUSED");
write_bit("ODDR_TDDR.IN_USE");
write_bit("OQUSED", ci->getPort(id_OQ) != nullptr);
auto serdes_mode = str_or_default(ci->params, id_SERDES_MODE, "MASTER");
bool is_cascaded = (serdes_mode == "SLAVE");
// For cascaded OSERDESE2, OQUSED must be set even though OQ is not connected
write_bit("OQUSED", is_cascaded || ci->getPort(id_OQ));
write_bit("ZINV_CLK", !bool_or_default(ci->params, id_IS_CLK_INVERTED, false));
for (std::string t : {"T1", "T2", "T3", "T4"})
write_bit("ZINV_" + t, (ci->getPort(ctx->id(t)) != nullptr || t == "T1") &&
@ -1080,7 +1096,7 @@ struct FasmBackend
push("OSERDES");
write_bit("IN_USE");
std::string type = str_or_default(ci->params, id_DATA_RATE_OQ, "BUF");
write_bit(std::string("DATA_RATE_OQ.") + ((ci->getPort(id_OQ) != nullptr) ? type : "BUF"));
write_bit(std::string("DATA_RATE_OQ.") + ((ci->getPort(id_OQ) != nullptr) ? type : "DDR"));
write_bit(std::string("DATA_RATE_TQ.") +
((ci->getPort(id_TQ) != nullptr) ? str_or_default(ci->params, id_DATA_RATE_TQ, "BUF") : "BUF"));
int width = int_or_default(ci->params, id_DATA_WIDTH, 8);
@ -1102,6 +1118,8 @@ struct FasmBackend
#endif
write_bit("SRTYPE.SYNC");
write_bit("TSRTYPE.SYNC");
if (is_cascaded)
write_bit("SERDES_MODE.SLAVE");
pop();
} else if (ci->type == id_ISERDESE2_ISERDESE2) {
std::string data_rate = str_or_default(ci->params, id_DATA_RATE);

View File

@ -774,26 +774,88 @@ void XC7Packer::pack_iologic()
log_info(" binding output DDR cell '%s' to bel '%s'\n", ctx->nameOf(ci), ctx->nameOfBel(oddr_bel));
ctx->bindBel(oddr_bel, ci, STRENGTH_LOCKED);
} else if (ci->type == id_OSERDESE2) {
NetInfo *q = ci->getPort(id_OQ);
NetInfo *ofb = ci->getPort(id_OFB);
bool q_disconnected = !q || q->users.empty();
bool ofb_disconnected = !ofb || ofb->users.empty();
if (q_disconnected && ofb_disconnected) {
log_error("%s '%s' has disconnected OQ/OFB output ports\n", ci->type.c_str(ctx), ctx->nameOf(ci));
NetInfo *shiftin1 = ci->getPort(id_SHIFTIN1), *shiftin2 = ci->getPort(id_SHIFTIN2),
*tbytein = ci->getPort(id_TBYTEIN);
// If connected to ground (i.e. unused) this can't actually be routed so remove it
if (shiftin1 && shiftin1->name == ctx->id("$PACKER_GND_NET")) {
ci->disconnectPort(id_SHIFTIN1);
shiftin1 = nullptr;
}
if (shiftin2 && shiftin2->name == ctx->id("$PACKER_GND_NET")) {
ci->disconnectPort(id_SHIFTIN2);
shiftin2 = nullptr;
}
if (tbytein && tbytein->name == ctx->id("$PACKER_GND_NET")) {
ci->disconnectPort(id_TBYTEIN);
tbytein = nullptr;
}
BelId io_bel;
CellInfo *ob = !q_disconnected ? find_p_outbuf(q) : find_p_outbuf(ofb);
if (ob != nullptr)
io_bel = ob->bel;
else
log_error("%s '%s' has illegal fanout on OQ or OFB output\n", ci->type.c_str(ctx), ctx->nameOf(ci));
SiteIndex ol_site = get_ologic_site(io_bel);
auto serdes_mode = str_or_default(ci->params, id_SERDES_MODE, "MASTER");
BelId oserdes_bel = uarch->get_site_bel(ol_site, id_OSERDESE2);
NPNR_ASSERT(oserdes_bel != BelId());
log_info(" binding output SERDES cell '%s' to bel '%s'\n", ctx->nameOf(ci), ctx->nameOfBel(oserdes_bel));
ctx->bindBel(oserdes_bel, ci, STRENGTH_LOCKED);
if (serdes_mode == "MASTER") {
NetInfo *q = ci->getPort(id_OQ);
NetInfo *ofb = ci->getPort(id_OFB);
bool q_disconnected = !q || q->users.empty();
bool ofb_disconnected = !ofb || ofb->users.empty();
if (q_disconnected && ofb_disconnected) {
log_error("%s '%s' has disconnected OQ/OFB output ports\n", ci->type.c_str(ctx), ctx->nameOf(ci));
}
BelId io_bel;
CellInfo *ob = !q_disconnected ? find_p_outbuf(q) : find_p_outbuf(ofb);
if (ob != nullptr)
io_bel = ob->bel;
else
log_error("%s '%s' has illegal fanout on OQ or OFB output\n", ci->type.c_str(ctx), ctx->nameOf(ci));
SiteIndex ol_site = get_ologic_site(io_bel);
BelId oserdes_bel = uarch->get_site_bel(ol_site, id_OSERDESE2);
NPNR_ASSERT(oserdes_bel != BelId());
log_info(" binding output SERDES cell '%s' to bel '%s'\n", ctx->nameOf(ci),
ctx->nameOfBel(oserdes_bel));
ctx->bindBel(oserdes_bel, ci, STRENGTH_LOCKED);
if (shiftin1 != nullptr || shiftin2 != nullptr) {
CellInfo *cascaded_cell = shiftin1 ? shiftin1->driver.cell : shiftin2->driver.cell;
if (!cascaded_cell || !cascaded_cell->type.in(id_OSERDESE2))
log_error("OSERDESE2 cell '%s' has SHIFTIN driven by illegal cell '%s'\n", ctx->nameOf(ci),
cascaded_cell ? ctx->nameOf(cascaded_cell) : "");
// cascaded location is one below master, so place the cascaded cell there
SiteIndex cascade_site = uarch->rel_site(ol_site, 0, -1);
NPNR_ASSERT(cascade_site != SiteIndex());
BelId cascade_bel = uarch->get_site_bel(cascade_site, id_OSERDESE2);
NPNR_ASSERT(cascade_bel != BelId());
log_info(" binding cascaded output SERDES cell '%s' to bel '%s'\n", ctx->nameOf(cascaded_cell),
ctx->nameOfBel(cascade_bel));
ctx->bindBel(cascade_bel, cascaded_cell, STRENGTH_LOCKED);
}
} else if (serdes_mode == "SLAVE") {
NetInfo *shiftout1 = ci->getPort(id_SHIFTOUT1);
NetInfo *shiftout2 = ci->getPort(id_SHIFTOUT2);
if (!shiftout1 && !shiftout2)
log_error("OSERDESE2 cell '%s' with SERDES_MODE SLAVE must have SHIFTOUT1/2 connected.\n",
ctx->nameOf(ci));
for (auto net : {shiftout1, shiftout2}) {
if (!net)
continue;
if (net->users.entries() != 1)
log_error(
"OSERDESE2 cell '%s' with SERDES_MODE SLAVE has multiple fanout on SHIFTOUT net '%s'\n",
ctx->nameOf(ci), ctx->nameOf(net));
auto usr = *(net->users.begin());
if (usr.cell->type != id_OSERDESE2 || !usr.port.in(id_SHIFTIN1, id_SHIFTIN2))
log_error("OSERDESE2 cell '%s' has SHIFTOUT driving illegal cell port '%s.%s'\n",
ctx->nameOf(ci), ctx->nameOf(usr.cell), ctx->nameOf(usr.port));
}
} else {
log_error("OSERDESE2 cell '%s' has unsupported SERDES_MODE '%s'\n", ctx->nameOf(ci),
serdes_mode.c_str());
}
} else if (ci->type == id_IDDR) {
fold_inverter(ci, "C");

View File

@ -426,6 +426,18 @@ Loc XilinxImpl::rel_site_loc(SiteIndex site) const
return Loc(site_data.rel_x, site_data.rel_y, 0);
}
SiteIndex XilinxImpl::rel_site(SiteIndex site, int dx, int dy) const
{
const auto &base_site_data = tile_extra_data(site.tile)->sites[site.site];
for (size_t i = 0; i < tile_extra_data(site.tile)->sites.size(); i++) {
const auto &site_data = tile_extra_data(site.tile)->sites[i];
if (site_data.name_prefix == base_site_data.name_prefix && site_data.rel_x == (base_site_data.rel_x + dx) &&
site_data.rel_y == (base_site_data.rel_y + dy))
return SiteIndex(site.tile, i);
}
return SiteIndex();
}
int XilinxImpl::hclk_for_iob(BelId pad) const
{
std::string tile_type = bel_tile_type(pad).str(ctx);

View File

@ -150,6 +150,7 @@ struct XilinxImpl : HimbaechelAPI
bool is_bram_tile(BelId bel) const;
SiteIndex get_bel_site(BelId bel) const;
SiteIndex rel_site(SiteIndex site, int dx, int dy) const;
Loc rel_site_loc(SiteIndex site) const;
IdString get_site_name(SiteIndex site) const;
IdString bel_name_in_site(BelId bel) const;