From d8988e16827a4830a052f0711f9c4828a62d77bf Mon Sep 17 00:00:00 2001 From: YRabbit Date: Wed, 12 Mar 2025 18:32:38 +1000 Subject: [PATCH] Gowin. Add HCLK wires to PLL. (#1462) Adds the ability to use high-speed clock lines (together with CLKDIV2 type frequency dividers operating on them) as sieve signals for the CLKIN and CLKFB inputs of the rPLL and PLLVR primitives (these cover the full range of supported Gowin chips). Signed-off-by: YRabbit --- himbaechel/uarch/gowin/globals.cc | 5 +- himbaechel/uarch/gowin/gowin.cc | 158 +++++++++++++++++------ himbaechel/uarch/gowin/gowin.h | 6 + himbaechel/uarch/gowin/gowin_arch_gen.py | 6 + himbaechel/uarch/gowin/gowin_utils.cc | 12 ++ himbaechel/uarch/gowin/gowin_utils.h | 2 + 6 files changed, 146 insertions(+), 43 deletions(-) diff --git a/himbaechel/uarch/gowin/globals.cc b/himbaechel/uarch/gowin/globals.cc index f71af38c..055964bd 100644 --- a/himbaechel/uarch/gowin/globals.cc +++ b/himbaechel/uarch/gowin/globals.cc @@ -282,8 +282,9 @@ struct GowinGlobalRouter if (driver.cell->type.in(id_CLKDIV, id_CLKDIV2)) { 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)); + 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; } diff --git a/himbaechel/uarch/gowin/gowin.cc b/himbaechel/uarch/gowin/gowin.cc index aa70669e..bce9e124 100644 --- a/himbaechel/uarch/gowin/gowin.cc +++ b/himbaechel/uarch/gowin/gowin.cc @@ -287,7 +287,7 @@ void GowinImpl::adjust_dsp_pin_mapping(void) /* Each HCLK section can serve one of three purposes: - 1. A simple routing path to IOLOGIC FCLK + 1. A simple routing path to IOLOGIC FCLK or PLL inputs 2. CLKDIV2 3. CLKDIV (only one section at any time) @@ -305,6 +305,45 @@ void GowinImpl::place_constrained_hclk_cells() std::vector> alias_cells; std::map, BelId> final_placement; + const bool chip_has_clkdiv_hclk_connection = gwu.has_CLKDIV_HCLK(); + const bool chip_has_pll_hclk = gwu.has_PLL_HCLK(); + pool free_pll_bels; + if (chip_has_pll_hclk) { + // gather free PLL bels + for (auto bel : ctx->getBels()) { + if (ctx->getBelType(bel).in(id_rPLL, id_PLLVR) && !ctx->getBoundBelCell(bel)) { + free_pll_bels.insert(bel); + } + } + } + + const auto is_hclk_user = [&](const CellInfo *ci) -> bool { + if ((is_iologici(ci) || is_iologico(ci)) && + !ci->type.in(id_ODDR, id_ODDRC, id_IDDR, id_IDDRC, id_IOLOGICI_EMPTY, id_IOLOGICO_EMPTY)) { + return true; + } + if (chip_has_pll_hclk && is_pll(ci)) { + return true; + } + return false; + }; + + // returns the list of networks connected to the cell + std::vector net_list; + auto get_nets = [&](const CellInfo *ci) -> void { + net_list.clear(); + if (is_iologici(ci) || is_iologico(ci)) { + net_list.push_back(ci->getPort(id_FCLK)); + return; + } + if (chip_has_pll_hclk && is_pll(ci)) { + net_list.push_back(ci->getPort(id_CLKIN)); + net_list.push_back(ci->getPort(id_CLKFB)); + return; + } + }; + + // cell, port std::set seen_hclk_users; for (auto &cell : ctx->cells) { auto ci = cell.second.get(); @@ -319,51 +358,57 @@ void GowinImpl::place_constrained_hclk_cells() if ((seen_hclk_users.find(ci->name) != seen_hclk_users.end())) continue; - if (((is_iologici(ci) || is_iologico(ci)) && - !ci->type.in(id_ODDR, id_ODDRC, id_IDDR, id_IDDRC, id_IOLOGICI_EMPTY, id_IOLOGICO_EMPTY))) { - NetInfo *hclk_net = ci->getPort(id_FCLK); - if (hclk_net) - continue; - CellInfo *hclk_driver = hclk_net->driver.cell; - if (!hclk_driver) - continue; - if (chip.str(ctx) == "GW1N-9C" && hclk_driver->type != id_CLKDIV2) { - // CLKDIV doesn't seem to connect directly to FCLK on this device, and routing is guaranteed to succeed. - continue; - } - - int alias_count = 0; - std::set> seen_options; - for (auto user : hclk_net->users) { - std::vector bel_candidates; - std::set these_options; - - if (!(user.port == id_FCLK && (is_iologici(user.cell) || is_iologico(user.cell)) && - !user.cell->type.in(id_ODDR, id_ODDRC, id_IDDR, id_IDDRC))) + if (is_hclk_user(ci)) { + get_nets(ci); + for (auto &hclk_net : net_list) { + if (!hclk_net) { continue; - if (seen_hclk_users.find(user.cell->name) != seen_hclk_users.end()) + } + CellInfo *hclk_driver = hclk_net->driver.cell; + if (!hclk_driver) + continue; + if (!(chip_has_clkdiv_hclk_connection || hclk_driver->type == id_CLKDIV2)) { continue; - seen_hclk_users.insert(user.cell->name); - - if (ctx->debug) { - log_info("Custom HCLK Placer: Found HCLK user: %s\n", user.cell->name.c_str(ctx)); } - gwu.find_connected_bels(user.cell, id_FCLK, id_CLKDIV2, id_CLKOUT, 16, bel_candidates); - these_options.insert(bel_candidates.begin(), bel_candidates.end()); + int alias_count = 0; + std::set> seen_options; + for (auto user : hclk_net->users) { + std::vector bel_candidates; + std::set these_options; - if (seen_options.find(these_options) != seen_options.end()) - continue; - seen_options.insert(these_options); + if (!is_hclk_user(user.cell)) { + continue; + } + seen_hclk_users.insert(user.cell->name); - // When an HCLK signal is routed to different (and disconnected) FCLKs, we treat each new - // HCLK-FCLK connection as a pseudo-HCLK cell since it must also be assigned an HCLK section - auto alias_index = std::pair(hclk_driver->name, alias_count); - alias_cells.push_back(alias_index); - alias_count++; + if (ctx->debug) { + log_info("Custom HCLK Placer: Found HCLK user: %s\n", user.cell->name.c_str(ctx)); + } - for (auto option : these_options) { - bel_cell_map[option].insert(alias_index); + if (is_pll(user.cell) && user.cell->bel == BelId()) { + if (free_pll_bels.empty()) { + log_error("No BELs for %s\n", ctx->nameOf(user.cell)); + } + ctx->bindBel(free_pll_bels.pop(), user.cell, PlaceStrength::STRENGTH_LOCKED); + } + + gwu.find_connected_bels(user.cell, user.port, id_CLKDIV2, id_CLKOUT, 1000000, bel_candidates); + these_options.insert(bel_candidates.begin(), bel_candidates.end()); + + if (seen_options.find(these_options) != seen_options.end()) + continue; + seen_options.insert(these_options); + + // When an HCLK signal is routed to different (and disconnected) FCLKs, we treat each new + // HCLK-FCLK connection as a pseudo-HCLK cell since it must also be assigned an HCLK section + auto alias_index = std::pair(hclk_driver->name, alias_count); + alias_cells.push_back(alias_index); + alias_count++; + + for (auto option : these_options) { + bel_cell_map[option].insert(alias_index); + } } } } @@ -571,8 +616,6 @@ void GowinImpl::postRoute() } user.cell->setAttr(id_IOLOGIC_FCLK, Property("UNKNOWN")); visited_hclk_users.insert(user.cell->name); - // XXX Based on the implementation, perhaps a function - // is needed to get Pip from a Wire PipId up_pip = h_net->wires.at(ctx->getNetinfoSinkWire(h_net, user, 0)).pip; IdString up_wire_name = ctx->getWireName(ctx->getPipSrcWire(up_pip))[1]; if (up_wire_name.in(id_HCLK_OUT0, id_HCLK_OUT1, id_HCLK_OUT2, id_HCLK_OUT3)) { @@ -590,6 +633,39 @@ void GowinImpl::postRoute() } } } + } else { + if (is_pll(ci)) { + // CLKIN is connected to HLCK? + NetInfo *h_net = ci->getPort(id_CLKIN); + if (h_net == nullptr || h_net->wires.empty()) { + continue; + } + PortRef pr = {ci, id_CLKIN}; + PipId up_pip = h_net->wires.at(ctx->getNetinfoSinkWire(h_net, pr, 0)).pip; + IdString up_wire_name = ctx->getWireName(ctx->getPipSrcWire(up_pip))[1]; + if (up_wire_name.in(id_HCLK_OUT0, id_HCLK_OUT1)) { + ci->setParam(id_INSEL, Property("CLKIN3")); + } else { + if (up_wire_name.in(id_HCLK_OUT2, id_HCLK_OUT3)) { + ci->setParam(id_INSEL, Property("CLKIN4")); + } + } + // CLKFB is connected to HLCK? + h_net = ci->getPort(id_CLKFB); + if (h_net == nullptr || h_net->wires.empty()) { + continue; + } + pr.port = id_CLKFB; + up_pip = h_net->wires.at(ctx->getNetinfoSinkWire(h_net, pr, 0)).pip; + up_wire_name = ctx->getWireName(ctx->getPipSrcWire(up_pip))[1]; + if (up_wire_name.in(id_HCLK_OUT0, id_HCLK_OUT1)) { + ci->setParam(id_FBSEL, Property("CLKFB1")); + } else { + if (up_wire_name.in(id_HCLK_OUT2, id_HCLK_OUT3)) { + ci->setParam(id_FBSEL, Property("CLKFB4")); + } + } + } } } } diff --git a/himbaechel/uarch/gowin/gowin.h b/himbaechel/uarch/gowin/gowin.h index 0c7a24d9..c9a43b34 100644 --- a/himbaechel/uarch/gowin/gowin.h +++ b/himbaechel/uarch/gowin/gowin.h @@ -93,6 +93,10 @@ inline bool type_is_userflash(IdString cell_type) } inline bool is_userflash(const CellInfo *cell) { return type_is_userflash(cell->type); } +// Return true if a cell is a PLL +inline bool type_is_pll(IdString cell_type) { return cell_type.in(id_rPLL, id_PLLVR); } +inline bool is_pll(const CellInfo *cell) { return type_is_pll(cell->type); } + // Return true if a cell is a EMCU inline bool type_is_emcu(IdString cell_type) { return cell_type == id_EMCU; } inline bool is_emcu(const CellInfo *cell) { return type_is_emcu(cell->type); } @@ -168,6 +172,8 @@ NPNR_PACKED_STRUCT(struct Extra_chip_data_POD { static constexpr int32_t NEED_BSRAM_OUTREG_FIX = 4; static constexpr int32_t NEED_BLKSEL_FIX = 8; static constexpr int32_t HAS_BANDGAP = 16; + static constexpr int32_t HAS_PLL_HCLK = 32; + static constexpr int32_t HAS_CLKDIV_HCLK = 64; }); } // namespace diff --git a/himbaechel/uarch/gowin/gowin_arch_gen.py b/himbaechel/uarch/gowin/gowin_arch_gen.py index 7c2169ca..140fae11 100644 --- a/himbaechel/uarch/gowin/gowin_arch_gen.py +++ b/himbaechel/uarch/gowin/gowin_arch_gen.py @@ -20,6 +20,8 @@ CHIP_NEED_SP_FIX = 0x2 CHIP_NEED_BSRAM_OUTREG_FIX = 0x4 CHIP_NEED_BLKSEL_FIX = 0x8 CHIP_HAS_BANDGAP = 0x10 +CHIP_HAS_PLL_HCLK = 0x20 +CHIP_HAS_CLKDIV_HCLK = 0x40 # Tile flags TILE_I3C_CAPABLE_IO = 0x1 @@ -1442,6 +1444,10 @@ def main(): chip_flags |= CHIP_NEED_BLKSEL_FIX; if "HAS_BANDGAP" in db.chip_flags: chip_flags |= CHIP_HAS_BANDGAP; + if "HAS_PLL_HCLK" in db.chip_flags: + chip_flags |= CHIP_HAS_PLL_HCLK; + if "HAS_CLKDIV_HCLK" in db.chip_flags: + chip_flags |= CHIP_HAS_CLKDIV_HCLK; X = db.cols; Y = db.rows; diff --git a/himbaechel/uarch/gowin/gowin_utils.cc b/himbaechel/uarch/gowin/gowin_utils.cc index f1d1c7a8..92410949 100644 --- a/himbaechel/uarch/gowin/gowin_utils.cc +++ b/himbaechel/uarch/gowin/gowin_utils.cc @@ -190,6 +190,18 @@ bool GowinUtils::need_BLKSEL_fix(void) return extra->chip_flags & Extra_chip_data_POD::NEED_BLKSEL_FIX; } +bool GowinUtils::has_PLL_HCLK(void) +{ + const Extra_chip_data_POD *extra = reinterpret_cast(ctx->chip_info->extra_data.get()); + return extra->chip_flags & Extra_chip_data_POD::HAS_PLL_HCLK; +} + +bool GowinUtils::has_CLKDIV_HCLK(void) +{ + const Extra_chip_data_POD *extra = reinterpret_cast(ctx->chip_info->extra_data.get()); + return extra->chip_flags & Extra_chip_data_POD::HAS_CLKDIV_HCLK; +} + IdString GowinUtils::create_aux_name(IdString main_name, int idx, const char *str_suffix) { return idx ? ctx->idf("%s%s%d", main_name.c_str(ctx), str_suffix, idx) diff --git a/himbaechel/uarch/gowin/gowin_utils.h b/himbaechel/uarch/gowin/gowin_utils.h index 40894319..2a46a843 100644 --- a/himbaechel/uarch/gowin/gowin_utils.h +++ b/himbaechel/uarch/gowin/gowin_utils.h @@ -57,6 +57,8 @@ struct GowinUtils bool need_SP_fix(void); bool need_BSRAM_OUTREG_fix(void); bool need_BLKSEL_fix(void); + bool has_PLL_HCLK(void); + bool has_CLKDIV_HCLK(void); // Power saving bool has_BANDGAP(void);