From 0db35f0776afb5a8e9deccd8249a650b6ba87391 Mon Sep 17 00:00:00 2001 From: YRabbit Date: Sat, 12 Jul 2025 15:39:25 +1000 Subject: [PATCH] Gowin. Allow clock network routing from GP pins. Adds automatic connection of a general-purpose pin to the global clock network. The old behaviour, where such networks have to be explicitly specified, can be activated with the command line key "--vopt disable_gp_clock_routing". Signed-off-by: YRabbit --- himbaechel/uarch/gowin/constids.inc | 6 ++ himbaechel/uarch/gowin/globals.cc | 95 +++++---------------------- himbaechel/uarch/gowin/gowin.cc | 5 ++ himbaechel/uarch/gowin/gowin_utils.cc | 69 +++++++++++++++++++ himbaechel/uarch/gowin/gowin_utils.h | 18 +++++ himbaechel/uarch/gowin/pack.cc | 32 +++++++-- 6 files changed, 143 insertions(+), 82 deletions(-) diff --git a/himbaechel/uarch/gowin/constids.inc b/himbaechel/uarch/gowin/constids.inc index 00c020ed..35d03ccf 100644 --- a/himbaechel/uarch/gowin/constids.inc +++ b/himbaechel/uarch/gowin/constids.inc @@ -126,6 +126,7 @@ X(MCLK) X(CLK0) X(CLK1) X(CLK2) +X(CLK3) X(LSR0) X(LSR1) X(LSR2) @@ -1358,3 +1359,8 @@ X(LW_TAP) X(LW_TAP_0) X(LW_BRANCH) X(SEG_WIRES_TO_ISOLATE) + +// routing params +X(NO_GP_CLOCK_ROUTING) + + diff --git a/himbaechel/uarch/gowin/globals.cc b/himbaechel/uarch/gowin/globals.cc index ad42ecb9..27ac0a95 100644 --- a/himbaechel/uarch/gowin/globals.cc +++ b/himbaechel/uarch/gowin/globals.cc @@ -248,69 +248,6 @@ struct GowinGlobalRouter } } - bool driver_is_buf(const PortRef &driver) { return CellTypePort(driver) == CellTypePort(id_BUFG, id_O); } - bool driver_is_dqce(const PortRef &driver) { return CellTypePort(driver) == CellTypePort(id_DQCE, id_CLKOUT); } - bool driver_is_dcs(const PortRef &driver) { return CellTypePort(driver) == CellTypePort(id_DCS, id_CLKOUT); } - bool driver_is_dhcen(const PortRef &driver) { return CellTypePort(driver) == CellTypePort(id_DHCEN, id_CLKOUT); } - bool driver_is_mipi(const PortRef &driver) - { - return CellTypePort(driver) == CellTypePort(id_IOBUF, id_O) && driver.cell->params.count(id_MIPI_IBUF); - } - bool driver_is_clksrc(const PortRef &driver) - { - // dedicated pins - if (CellTypePort(driver) == CellTypePort(id_IBUF, id_O)) { - - NPNR_ASSERT(driver.cell->bel != BelId()); - IdStringList pin_func = gwu.get_pin_funcs(driver.cell->bel); - for (size_t i = 0; i < pin_func.size(); ++i) { - if (ctx->debug) { - log_info("bel:%s, pin func: %zu:%s\n", ctx->nameOfBel(driver.cell->bel), i, - pin_func[i].str(ctx).c_str()); - } - if (pin_func[i].str(ctx).rfind("GCLKT", 0) == 0) { - if (ctx->debug) { - log_info("Clock pin:%s:%s\n", ctx->getBelName(driver.cell->bel).str(ctx).c_str(), - pin_func[i].c_str(ctx)); - } - return true; - } - } - } - // PLL outputs - if (driver.cell->type.in(id_rPLL, id_PLLVR)) { - if (driver.port.in(id_CLKOUT, id_CLKOUTD, id_CLKOUTD3, id_CLKOUTP)) { - if (ctx->debug) { - log_info("PLL out:%s:%s\n", ctx->getBelName(driver.cell->bel).str(ctx).c_str(), - driver.port.c_str(ctx)); - } - return true; - } - } - // HCLK outputs - if (driver.cell->type.in(id_CLKDIV, id_CLKDIV2)) { - if (driver.port.in(id_CLKOUT)) { - if (ctx->debug) { - log_info("%s out:%s:%s:%s\n", driver.cell->type.c_str(ctx), - ctx->getBelName(driver.cell->bel).str(ctx).c_str(), driver.port.c_str(ctx), - ctx->nameOfWire(ctx->getBelPinWire(driver.cell->bel, driver.port))); - } - return true; - } - } - // DLLDLY outputs - if (driver.cell->type == id_DLLDLY) { - if (driver.port.in(id_CLKOUT)) { - if (ctx->debug) { - log_info("%s out:%s:%s\n", driver.cell->type.c_str(ctx), - ctx->getBelName(driver.cell->bel).str(ctx).c_str(), driver.port.c_str(ctx)); - } - return true; - } - } - return false; - } - enum RouteResult { NOT_ROUTED = 0, @@ -367,11 +304,11 @@ struct GowinGlobalRouter NPNR_ASSERT(net_before_dqce != nullptr); PortRef driver = net_before_dqce->driver; - NPNR_ASSERT_MSG(driver_is_buf(driver) || driver_is_clksrc(driver), + NPNR_ASSERT_MSG(gwu.driver_is_buf(driver) || gwu.driver_is_clksrc(driver), stringf("The input source for %s is not a clock.", ctx->nameOf(dqce_ci)).c_str()); WireId src; // use BUF input if there is one - if (driver_is_buf(driver)) { + if (gwu.driver_is_buf(driver)) { src = ctx->getBelPinWire(driver.cell->bel, id_I); } else { src = ctx->getBelPinWire(driver.cell->bel, driver.port); @@ -463,7 +400,7 @@ struct GowinGlobalRouter continue; } driver = net_before_dcs->driver; - if (driver_is_buf(driver) || driver_is_clksrc(driver)) { + if (gwu.driver_is_buf(driver) || gwu.driver_is_clksrc(driver)) { break; } net_before_dcs = nullptr; @@ -472,7 +409,7 @@ struct GowinGlobalRouter WireId src; // use BUF input if there is one - if (driver_is_buf(driver)) { + if (gwu.driver_is_buf(driver)) { src = ctx->getBelPinWire(driver.cell->bel, id_I); } else { src = ctx->getBelPinWire(driver.cell->bel, driver.port); @@ -574,12 +511,14 @@ struct GowinGlobalRouter NPNR_ASSERT(net_before_dhcen != nullptr); PortRef driver = net_before_dhcen->driver; - NPNR_ASSERT_MSG(driver_is_buf(driver) || driver_is_clksrc(driver) || driver_is_mipi(driver), - stringf("The input source for %s is not a clock.", ctx->nameOf(dhcen_ci)).c_str()); + NPNR_ASSERT_MSG(gwu.driver_is_buf(driver) || gwu.driver_is_clksrc(driver) || gwu.driver_is_mipi(driver), + stringf("The input source (%s:%s) for %s is not a clock.", ctx->nameOf(driver.cell), + driver.port.c_str(ctx), ctx->nameOf(dhcen_ci)) + .c_str()); IdString port; // use BUF input if there is one - if (driver_is_buf(driver)) { + if (gwu.driver_is_buf(driver)) { port = id_I; } else { port = driver.port; @@ -588,7 +527,7 @@ struct GowinGlobalRouter std::vector path; RouteResult route_result; - if (driver_is_mipi(driver)) { + if (gwu.driver_is_mipi(driver)) { route_result = route_direct_net( net, [&](PipId pip, WireId src_wire) { return segment_wire_filter(pip) && dcs_input_filter(pip); }, src, &path); @@ -637,7 +576,7 @@ struct GowinGlobalRouter hw_dhcen->setAttr(id_DHCEN_USED, 1); dhcen_ci->copyPortTo(id_CE, hw_dhcen, id_CE); } - if (driver_is_mipi(driver)) { + if (gwu.driver_is_mipi(driver)) { ctx->bindWire(src, net_before_dhcen, STRENGTH_LOCKED); } @@ -673,7 +612,7 @@ struct GowinGlobalRouter return global_pip_filter(pip, src_wire) && segment_wire_filter(pip) && dcs_input_filter(pip); }, src); - if (route_result == NOT_ROUTED || route_result == ROUTED_PARTIALLY) { + if (route_result == NOT_ROUTED) { log_error("Can't route the %s net. It might be worth removing the BUFG buffer flag.\n", ctx->nameOf(net)); } @@ -1249,19 +1188,19 @@ struct GowinGlobalRouter } continue; } - if (driver_is_buf(ni->driver)) { + if (gwu.driver_is_buf(ni->driver)) { buf_nets.push_back(net.first); } else { - if (driver_is_clksrc(ni->driver)) { + if (gwu.driver_is_clksrc(ni->driver)) { clk_nets.push_back(net.first); } else { - if (driver_is_dqce(ni->driver)) { + if (gwu.driver_is_dqce(ni->driver)) { dqce_nets.push_back(net.first); } else { - if (driver_is_dcs(ni->driver)) { + if (gwu.driver_is_dcs(ni->driver)) { dcs_nets.push_back(net.first); } else { - if (driver_is_dhcen(ni->driver)) { + if (gwu.driver_is_dhcen(ni->driver)) { dhcen_nets.push_back(net.first); } else { seg_nets.push_back(net.first); diff --git a/himbaechel/uarch/gowin/gowin.cc b/himbaechel/uarch/gowin/gowin.cc index 70a30b9f..d5e421d6 100644 --- a/himbaechel/uarch/gowin/gowin.cc +++ b/himbaechel/uarch/gowin/gowin.cc @@ -209,6 +209,11 @@ void GowinImpl::init(Context *ctx) if (args.options.count("ioreg_in_iob")) { ctx->settings[id_IOREG_IN_IOB] = Property(1); } + + // smart clock routing + if (args.options.count("disable_gp_clock_routing")) { + ctx->settings[id_NO_GP_CLOCK_ROUTING] = Property(1); + } } // We do not allow the use of global wires that bypass a special router. diff --git a/himbaechel/uarch/gowin/gowin_utils.cc b/himbaechel/uarch/gowin/gowin_utils.cc index 628181fc..d6ba94d5 100644 --- a/himbaechel/uarch/gowin/gowin_utils.cc +++ b/himbaechel/uarch/gowin/gowin_utils.cc @@ -12,6 +12,75 @@ NEXTPNR_NAMESPACE_BEGIN +// clock sources +bool GowinUtils::driver_is_clksrc(const PortRef &driver) +{ + // dedicated pins + if (CellTypePort(driver) == CellTypePort(id_IBUF, id_O)) { + + NPNR_ASSERT(driver.cell->bel != BelId()); + IdStringList pin_func = get_pin_funcs(driver.cell->bel); + for (size_t i = 0; i < pin_func.size(); ++i) { + if (ctx->debug) { + log_info("bel:%s, pin func: %zu:%s\n", ctx->nameOfBel(driver.cell->bel), i, + pin_func[i].str(ctx).c_str()); + } + if (pin_func[i].str(ctx).rfind("GCLKT", 0) == 0) { + if (ctx->debug) { + log_info("Clock pin:%s:%s\n", ctx->getBelName(driver.cell->bel).str(ctx).c_str(), + pin_func[i].c_str(ctx)); + } + return true; + } + } + } + // PLL outputs + if (driver.cell->type.in(id_rPLL, id_PLLVR)) { + if (driver.port.in(id_CLKOUT, id_CLKOUTD, id_CLKOUTD3, id_CLKOUTP)) { + if (ctx->debug) { + if (driver.cell->bel != BelId()) { + log_info("PLL out bel:%s:%s\n", ctx->nameOfBel(driver.cell->bel), driver.port.c_str(ctx)); + } else { + log_info("PLL out:%s:%s\n", ctx->nameOf(driver.cell), driver.port.c_str(ctx)); + } + } + return true; + } + } + // HCLK outputs + if (driver.cell->type.in(id_CLKDIV, id_CLKDIV2)) { + if (driver.port.in(id_CLKOUT)) { + if (ctx->debug) { + if (driver.cell->bel != BelId()) { + log_info("%s out bel:%s:%s:%s\n", driver.cell->type.c_str(ctx), + ctx->getBelName(driver.cell->bel).str(ctx).c_str(), driver.port.c_str(ctx), + ctx->nameOfWire(ctx->getBelPinWire(driver.cell->bel, driver.port))); + } else { + log_info("%s out:%s:%s\n", driver.cell->type.c_str(ctx), ctx->nameOf(driver.cell), + driver.port.c_str(ctx)); + } + } + return true; + } + } + // DLLDLY outputs + if (driver.cell->type == id_DLLDLY) { + if (driver.port.in(id_CLKOUT)) { + if (ctx->debug) { + if (driver.cell->bel != BelId()) { + log_info("%s out bel:%s:%s\n", driver.cell->type.c_str(ctx), + ctx->getBelName(driver.cell->bel).str(ctx).c_str(), driver.port.c_str(ctx)); + } else { + log_info("%s out:%s:%s\n", driver.cell->type.c_str(ctx), ctx->nameOf(driver.cell), + driver.port.c_str(ctx)); + } + } + return true; + } + } + return false; +} + // Segments int GowinUtils::get_segments_count(void) const { diff --git a/himbaechel/uarch/gowin/gowin_utils.h b/himbaechel/uarch/gowin/gowin_utils.h index 3766ba6f..1fe269e5 100644 --- a/himbaechel/uarch/gowin/gowin_utils.h +++ b/himbaechel/uarch/gowin/gowin_utils.h @@ -67,6 +67,24 @@ struct GowinUtils return ni->users.entries() != 0; } + // net sources + inline bool driver_is_io(const PortRef &driver) { return CellTypePort(driver) == CellTypePort(id_IBUF, id_O); } + inline bool driver_is_buf(const PortRef &driver) { return CellTypePort(driver) == CellTypePort(id_BUFG, id_O); } + inline bool driver_is_dqce(const PortRef &driver) + { + return CellTypePort(driver) == CellTypePort(id_DQCE, id_CLKOUT); + } + inline bool driver_is_dcs(const PortRef &driver) { return CellTypePort(driver) == CellTypePort(id_DCS, id_CLKOUT); } + inline bool driver_is_dhcen(const PortRef &driver) + { + return CellTypePort(driver) == CellTypePort(id_DHCEN, id_CLKOUT); + } + inline bool driver_is_mipi(const PortRef &driver) + { + return CellTypePort(driver) == CellTypePort(id_IOBUF, id_O) && driver.cell->params.count(id_MIPI_IBUF); + } + bool driver_is_clksrc(const PortRef &driver); + // BSRAM bool has_SP32(void); bool need_SP_fix(void); diff --git a/himbaechel/uarch/gowin/pack.cc b/himbaechel/uarch/gowin/pack.cc index 53efc44c..7bbc750c 100644 --- a/himbaechel/uarch/gowin/pack.cc +++ b/himbaechel/uarch/gowin/pack.cc @@ -3860,10 +3860,34 @@ struct GowinPacker log_info("Pack buffered nets...\n"); for (auto &net : ctx->nets) { - auto &ni = *net.second; - if (ni.driver.cell == nullptr || ni.attrs.count(id_CLOCK) == 0 || ni.users.empty()) { + NetInfo *ni = net.second.get(); + if (ni->driver.cell == nullptr || ni->users.empty() || net.first == ctx->id("$PACKER_GND") || + net.first == ctx->id("$PACKER_VCC")) { continue; } + if (ni->attrs.count(id_CLOCK) == 0) { + if (ctx->settings.count(id_NO_GP_CLOCK_ROUTING)) { + continue; + } + if (gwu.driver_is_clksrc(ni->driver) || (!gwu.driver_is_io(ni->driver))) { + // no need for buffering + continue; + } + // check users for the clock inputs + bool has_clock_users = false; + for (auto usr : ni->users) { + if (usr.port.in(id_CLKIN, id_CLK, id_CLK0, id_CLK1, id_CLK2, id_CLK3, id_CLKFB)) { + has_clock_users = true; + break; + } + } + if (!has_clock_users) { + continue; + } + if (ctx->verbose) { + log_info("Add buffering to a potentially clock network '%s'\n", ctx->nameOf(ni)); + } + } // make new BUF cell single user for the net driver IdString buf_name = ctx->idf("%s_BUFG", net.first.c_str(ctx)); @@ -3871,8 +3895,8 @@ struct GowinPacker CellInfo *buf_ci = ctx->cells.at(buf_name).get(); buf_ci->addInput(id_I); // move driver - CellInfo *driver_cell = ni.driver.cell; - IdString driver_port = ni.driver.port; + CellInfo *driver_cell = ni->driver.cell; + IdString driver_port = ni->driver.port; driver_cell->movePortTo(driver_port, buf_ci, id_O); buf_ci->connectPorts(id_I, driver_cell, driver_port);