From 4ace8952d34b9f723d2ea61daba3af4dbc7855a2 Mon Sep 17 00:00:00 2001 From: gatecat Date: Thu, 5 Mar 2026 13:59:35 +0100 Subject: [PATCH] xilinx: Support cascaded IOSERDES and TMDS Signed-off-by: gatecat --- himbaechel/uarch/xilinx/constids.inc | 6 ++ himbaechel/uarch/xilinx/fasm.cc | 22 ++++++- himbaechel/uarch/xilinx/pack_io.cc | 96 +++++++++++++++++++++++----- himbaechel/uarch/xilinx/xilinx.cc | 12 ++++ himbaechel/uarch/xilinx/xilinx.h | 1 + 5 files changed, 118 insertions(+), 19 deletions(-) diff --git a/himbaechel/uarch/xilinx/constids.inc b/himbaechel/uarch/xilinx/constids.inc index 9bc7b9c7..7157e716 100644 --- a/himbaechel/uarch/xilinx/constids.inc +++ b/himbaechel/uarch/xilinx/constids.inc @@ -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) diff --git a/himbaechel/uarch/xilinx/fasm.cc b/himbaechel/uarch/xilinx/fasm.cc index d9170243..ca85b2f3 100644 --- a/himbaechel/uarch/xilinx/fasm.cc +++ b/himbaechel/uarch/xilinx/fasm.cc @@ -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); diff --git a/himbaechel/uarch/xilinx/pack_io.cc b/himbaechel/uarch/xilinx/pack_io.cc index 0c2af402..b485ba49 100644 --- a/himbaechel/uarch/xilinx/pack_io.cc +++ b/himbaechel/uarch/xilinx/pack_io.cc @@ -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"); diff --git a/himbaechel/uarch/xilinx/xilinx.cc b/himbaechel/uarch/xilinx/xilinx.cc index 7dd75220..ad41cdbd 100644 --- a/himbaechel/uarch/xilinx/xilinx.cc +++ b/himbaechel/uarch/xilinx/xilinx.cc @@ -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); diff --git a/himbaechel/uarch/xilinx/xilinx.h b/himbaechel/uarch/xilinx/xilinx.h index ce165623..ceaa2c87 100644 --- a/himbaechel/uarch/xilinx/xilinx.h +++ b/himbaechel/uarch/xilinx/xilinx.h @@ -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;