diff --git a/himbaechel/uarch/gowin/constids.inc b/himbaechel/uarch/gowin/constids.inc index 4eb7badf..fcadd941 100644 --- a/himbaechel/uarch/gowin/constids.inc +++ b/himbaechel/uarch/gowin/constids.inc @@ -1239,3 +1239,16 @@ X(DQCE_PIP) X(DCS_USED) X(SELFORCE) +//HCLK Bels +X(CLKDIV) +X(CLKDIV2) + +//HCLK Ports +X(HCLKIN) +X(RESETN) +// X(CALIB) +// X(CLKOUT) + +//HCLK Parameters +X(DIV_MODE) +X(GSREN) diff --git a/himbaechel/uarch/gowin/cst.cc b/himbaechel/uarch/gowin/cst.cc index e4df3f61..310e1406 100644 --- a/himbaechel/uarch/gowin/cst.cc +++ b/himbaechel/uarch/gowin/cst.cc @@ -51,6 +51,27 @@ struct GowinCstReader return Loc(col - 1, row - 1, z); } + BelId getConstrainedHCLKBel(std::smatch match, int maxX, int maxY) + { + int idx = std::stoi(match[3]); + int bel_z = BelZ::CLKDIV_0_Z + 2 * idx; + + std::string side = match[2].str(); + bool lr = (side == "LEFT") || (side == "RIGHT"); + int y_coord = (side == "BOTTOM") ? maxY - 1 : 0; + int x_coord = (side == "RIGHT") ? maxX - 1 : 0; + + for (auto &bel : ctx->getBelsInBucket(ctx->getBelBucketForCellType(id_CLKDIV))) { + auto this_loc = ctx->getBelLocation(bel); + if (lr && this_loc.x == x_coord && this_loc.z == bel_z && this_loc.y != 0 && + this_loc.y != maxY - 1) // left or right side + return bel; + else if (!lr && this_loc.y == y_coord && this_loc.z == bel_z) // top or bottom side + return bel; + } + return BelId(); + } + bool run(void) { pool> constrained_cells; @@ -71,15 +92,19 @@ struct GowinCstReader std::regex iobelre = std::regex("IO([TRBL])([0-9]+)\\[?([A-Z])\\]?"); std::regex inslocre = std::regex("INS_LOC +\"([^\"]+)\" +R([0-9]+)C([0-9]+)\\[([0-9])\\]\\[([AB])\\] *;.*[\\s\\S]*"); + std::regex hclkre = + std::regex("INS_LOC +\"([^\"]+)\" +(TOP|RIGHT|BOTTOM|LEFT)SIDE\\[([0,1])\\] *;*[\\s\\S]*"); std::regex clockre = std::regex("CLOCK_LOC +\"([^\"]+)\" +BUF([GS])(\\[([0-7])\\])?[^;]*;.*[\\s\\S]*"); std::smatch match, match_attr, match_pinloc; std::string line, pinline; + std::vector constrained_clkdivs; enum { ioloc, ioport, insloc, - clock + clock, + hclk } cst_type; while (!in.eof()) { @@ -95,10 +120,14 @@ struct GowinCstReader if (std::regex_match(line, match, inslocre)) { cst_type = insloc; } else { - if ((!line.empty()) && (line.rfind("//", 0) == std::string::npos)) { - log_warning("Invalid constraint: %s\n", line.c_str()); + if (std::regex_match(line, match, hclkre)) { + cst_type = hclk; + } else { + if ((!line.empty()) && (line.rfind("//", 0) == std::string::npos)) { + log_warning("Invalid constraint: %s\n", line.c_str()); + } + continue; } - continue; } } } @@ -166,6 +195,27 @@ struct GowinCstReader } } } break; + case hclk: { + IdString cell_type = it->second->type; + if (cell_type != id_CLKDIV) { + log_error("Unsupported or invalid cell type %s for hclk\n", cell_type.c_str(ctx)); + } + BelId hclk_bel = getConstrainedHCLKBel(match, ctx->getGridDimX(), ctx->getGridDimY()); + if (hclk_bel != BelId()) { + auto hclk_bel_name = ctx->getBelName(hclk_bel); + if (std::find(constrained_clkdivs.begin(), constrained_clkdivs.end(), hclk_bel_name) != + constrained_clkdivs.end()) { + log_error("Only one CLKDIV can be placed at %sSIDE[%s]\n", match[2].str().c_str(), + match[3].str().c_str()); + } + constrained_clkdivs.push_back(hclk_bel_name); + it->second->setAttr(id_BEL, ctx->getBelName(hclk_bel).str(ctx)); + debug_cell(it->second->name, ctx->getBelName(hclk_bel)); + } else { + log_error("No Bel of type CLKDIV found at constrained location %sSIDE[%s]\n", + match[2].str().c_str(), match[3].str().c_str()); + } + } break; default: { // IO_PORT attr=value std::string attr_val = match[2]; while (std::regex_match(attr_val, match_attr, port_attrre)) { diff --git a/himbaechel/uarch/gowin/globals.cc b/himbaechel/uarch/gowin/globals.cc index 48a45d26..e510174b 100644 --- a/himbaechel/uarch/gowin/globals.cc +++ b/himbaechel/uarch/gowin/globals.cc @@ -256,6 +256,16 @@ struct GowinGlobalRouter 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\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; } diff --git a/himbaechel/uarch/gowin/gowin.cc b/himbaechel/uarch/gowin/gowin.cc index 6ce7916c..7f4ae89a 100644 --- a/himbaechel/uarch/gowin/gowin.cc +++ b/himbaechel/uarch/gowin/gowin.cc @@ -71,6 +71,9 @@ struct GowinImpl : HimbaechelAPI std::vector fast_cell_info; void assign_cell_info(); + // Remember HCLK sections that have been reserved to route HCLK signals + std::set routing_reserved_hclk_sections; + // dsp control nets // Each DSP and each macro has a small set of control wires that are // allocated to internal primitives as needed. It is assumed that most @@ -87,9 +90,14 @@ struct GowinImpl : HimbaechelAPI // since this information is already lost during unbinding void adjust_dsp_pin_mapping(void); + // Place explicityl constrained or implicitly constrained (by IOLOGIC) CLKDIV and CLKDIV2 cells + // to avoid routing conflicts and maximize utilization + void place_constrained_hclk_cells(); + // bel placement validation bool slice_valid(int x, int y, int z) const; bool dsp_valid(Loc l, IdString bel_type, bool explain_invalid) const; + bool hclk_valid(BelId bel, IdString bel_type) const; }; struct GowinArch : HimbaechelArch @@ -259,7 +267,250 @@ void GowinImpl::adjust_dsp_pin_mapping(void) } } -void GowinImpl::prePlace() { assign_cell_info(); } +/* + Each HCLK section can serve one of three purposes: + 1. A simple routing path to IOLOGIC FCLK + 2. CLKDIV2 + 3. CLKDIV (only one section at any time) + + Our task is to distribute HCLK signal providers to sections in a way that maximizes utilization while + enforcing user constraints on CLKDIV placement. We achieve this by solving two bipartite matchings: + - The first determines the best HCLK to place a CLKDIV within the established graph. This is then refined + to determine what section to assign the CLKDIV to based on what IOLOGIC it connects to + - The second determines which HCLK sections to use as CLKDIV2 or to reserve for routing. +*/ +void GowinImpl::place_constrained_hclk_cells() +{ + log_info("Running custom HCLK placer...\n"); + std::map constrained_clkdivs; + std::map>> bel_cell_map; + std::vector> alias_cells; + std::map, BelId> final_placement; + + std::set seen_hclk_users; + for (auto &cell : ctx->cells) { + auto ci = cell.second.get(); + + if (is_clkdiv(ci) && ci->attrs.count(id_BEL)) { + BelId constrained_bel = ctx->getBelByName(IdStringList::parse(ctx, ci->attrs.at(id_BEL).as_string())); + NPNR_ASSERT(constrained_bel != BelId() && ctx->getBelType(constrained_bel) == id_CLKDIV); + auto hclk_id_loc = gwu.get_hclk_id(constrained_bel); + constrained_clkdivs[hclk_id_loc] = ci->name; + } + + 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))) { + 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))) + continue; + if (seen_hclk_users.find(user.cell->name) != seen_hclk_users.end()) + 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()); + + 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); + } + } + } + } + + // First matching. We use the upper CLKDIV2 as the ID for an HCLK + std::map> clkdiv_graph; + for (auto bel_cell_candidates : bel_cell_map) { + auto bel = bel_cell_candidates.first; + auto hclk_id_loc = gwu.get_hclk_id(bel); + if (constrained_clkdivs.find(hclk_id_loc) != constrained_clkdivs.end()) { + continue; + } + for (auto candidate : bel_cell_candidates.second) { + auto ci = ctx->cells.at(candidate.first).get(); + if ((ci->type != id_CLKDIV) || ci->attrs.count(id_BEL)) { + continue; + } + clkdiv_graph[hclk_id_loc].insert(candidate.first); + } + } + + if (ctx->debug) { + log_info("<-----CUSTOM HCLK PLACER: Constrained CLKDIVs----->\n"); + for (auto match_pair : constrained_clkdivs) { + log_info("%s cell <-----> CLKDIV at HCLK %s\n", match_pair.second.c_str(ctx), match_pair.second.c_str(ctx)); + } + log("\n"); + } + + auto matching = gwu.find_maximum_bipartite_matching( + clkdiv_graph); // these will serve as constraints + constrained_clkdivs.insert(matching.begin(), matching.end()); + + if (ctx->debug) { + log_info("<-----CUSTOM HCLK PLACER: First Matching(CLKDIV) Results----->\n"); + for (auto match_pair : matching) { + log_info("%s cell <-----> CLKDIV at HCLK %s\n", match_pair.second.c_str(ctx), match_pair.second.c_str(ctx)); + } + log("\n"); + } + + // Refine matching to HCLK section, based on what connections actually exist + std::map> true_clkdivs; + std::set used_bels; + for (auto constr_pair : constrained_clkdivs) { + BelId option0 = ctx->getBelByName(constr_pair.first); + BelId option1 = gwu.get_other_hclk_clkdiv2(option0); + + // On the GW1N-9 devices, only the lower CLKDIV can be fed by a CLKDIV2 + std::vector options = {option1, option0}; + if (chip.str(ctx) == "GW1N-9C") { + auto ci = ctx->cells.at(constr_pair.second).get(); + for (auto cluster_child_cell : ci->constr_children) + if (cluster_child_cell->type == id_CLKDIV2 && options.back() == option0) { + options.pop_back(); + break; + } + } + + bool placed = false; + for (auto option : options) { + if (placed || (used_bels.find(option) != used_bels.end())) + continue; + for (auto option_cell : bel_cell_map[option]) { + if ((option_cell.first != constr_pair.second) || + (true_clkdivs.find(option_cell.first) != true_clkdivs.end())) + continue; + final_placement[option_cell] = option; + true_clkdivs[option_cell.first] = option_cell; + used_bels.insert(option); + placed = true; + break; + } + } + // This must be a constrained CLKDIV that either does not serve IOLOGIC Or + // does not have a direct (HCLK-FCLK) connection IOLOGIC it serves + // We create a new alias to represent this + if (!placed) { + auto new_alias = std::pair(constr_pair.second, -1); + for (auto option : options) + bel_cell_map[option].insert(new_alias); + alias_cells.push_back(new_alias); + true_clkdivs[constr_pair.second] = new_alias; + } + } + + // Second Matching for CLKDIV2 and routing reservation + std::map>> full_hclk_graph; + for (auto bel_cell_candidates : bel_cell_map) { + auto bel = bel_cell_candidates.first; + auto bel_name = ctx->getBelName(bel); + if (!used_bels.count(bel)) { + for (auto candidate : bel_cell_candidates.second) { + if (((candidate.second == -1) || (!true_clkdivs.count(candidate.first)) || + !(true_clkdivs[candidate.first] == candidate))) { + full_hclk_graph[bel_name].insert(candidate); + } + } + } + } + + auto full_matching = gwu.find_maximum_bipartite_matching(full_hclk_graph); + for (auto belname_cellalias : full_matching) { + auto bel = ctx->getBelByName(belname_cellalias.first); + NPNR_ASSERT(!used_bels.count(bel)); + final_placement[belname_cellalias.second] = bel; + } + + if (ctx->debug) { + log_info("<-----CUSTOM HCLK PLACER: Second Matching(CLKDIV2 and Routing) Results------>\n"); + for (auto match_pair : full_matching) { + auto alias = match_pair.second; + auto bel = match_pair.first; + auto cell_type = ctx->cells.at(alias.first).get()->type; + log_info("%s cell %s Alias %d <-----> HCLK Section at %s\n", cell_type.c_str(ctx), alias.first.c_str(ctx), + alias.second, bel.str(ctx).c_str()); + } + log("\n"); + } + + for (auto cell_alias : alias_cells) { + auto ci = ctx->cells.at(cell_alias.first).get(); + + if (final_placement.find(cell_alias) == final_placement.end() && ctx->debug) + if (ci->type == id_CLKDIV2 || ci->type == id_CLKDIV) + log_info("Custom HCLK Placer: Unable to place HCLK cell %s; no BELs available to implement cell type " + "%s\n", + ci->name.c_str(ctx), ci->type.c_str(ctx)); + else + log_info("Custom HCLK Placer: Unable to guarantee route for HCLK signal from %s to IOLOGIC\n", + ci->name.c_str(ctx)); + + else { + auto placement = final_placement[cell_alias]; + if (ctx->debug) + log_info("Custom HCLK Placer: Placing %s Alias %d at %s\n", cell_alias.first.c_str(ctx), + cell_alias.second, ctx->nameOfBel(placement)); + if (ci->type == id_CLKDIV2) + ctx->bindBel(placement, ci, STRENGTH_LOCKED); + + else if ((ci->type == id_CLKDIV) && (true_clkdivs[cell_alias.first] == cell_alias)) { + NetInfo *in = ci->getPort(id_HCLKIN); + if (in && in->driver.cell->type == id_CLKDIV2) { + ctx->bindBel(placement, in->driver.cell, STRENGTH_LOCKED); + } + auto clkdiv_bel = gwu.get_clkdiv_for_clkdiv2(placement); + ctx->bindBel(clkdiv_bel, ci, STRENGTH_LOCKED); + } else { + if (ctx->debug) + log_info("Custom HCLK Placer: Reserving HCLK %s to route clock from %s\n", + ctx->nameOfBel(placement), ci->name.c_str(ctx)); + routing_reserved_hclk_sections.insert(placement); + } + } + if (ci->attrs.count(id_BEL)) + ci->unsetAttr(id_BEL); + } +} + +void GowinImpl::prePlace() +{ + place_constrained_hclk_cells(); + assign_cell_info(); +} + void GowinImpl::postPlace() { gwu.has_SP32(); @@ -349,6 +600,9 @@ bool GowinImpl::isBelLocationValid(BelId bel, bool explain_invalid) const case ID_MULT36X36: /* fall-through */ case ID_ALU54D: return dsp_valid(l, bel_type, explain_invalid); + case ID_CLKDIV2: /* fall-through */ + case ID_CLKDIV: + return hclk_valid(bel, bel_type); } return true; } @@ -608,6 +862,52 @@ bool GowinImpl::slice_valid(int x, int y, int z) const } return true; } +/* + Every HCLK section can be used in one of 3 ways: + 1. As a simple routing path to IOLOGIC FCLK + 2. As a CLKDIV2 + 3. As a CLKDIV (potentially fed by the CLKDIV2 in its section) + + Here we validate that the placement of cells fits within these 3 use cases, while ensuring that + we enforce the constraint that only 1 CLKDIV can be used per HCLK (there is only 1 CLKDIV in each + HCLK but we pretend there are two because doing so makes it easier to enforce the real constraint + that HCLK signals don't crisscross between HCLK sections even after "transformation" by a CLKDIV + or CLKDIV2) +*/ +bool GowinImpl::hclk_valid(BelId bel, IdString bel_type) const +{ + if (bel_type == id_CLKDIV2) { + if (routing_reserved_hclk_sections.count(bel)) + return false; + auto clkdiv_cell = ctx->getBoundBelCell(gwu.get_clkdiv_for_clkdiv2(bel)); + if (clkdiv_cell && ctx->getBoundBelCell(bel)->cluster != clkdiv_cell->name) + return false; + return true; + } else if (bel_type == id_CLKDIV) { + BelId clkdiv2_bel = gwu.get_clkdiv2_for_clkdiv(bel); + if (routing_reserved_hclk_sections.count(clkdiv2_bel)) { + return false; + } + + auto other_clkdiv_cell = ctx->getBoundBelCell(gwu.get_other_hclk_clkdiv(bel)); + if (other_clkdiv_cell) + return false; + + auto clkdiv2_bel_cell = ctx->getBoundBelCell(clkdiv2_bel); + if (clkdiv2_bel_cell && clkdiv2_bel_cell->cluster != ctx->getBoundBelCell(bel)->name) + return false; + + if (clkdiv2_bel_cell && chip.str(ctx) == "GW1N-9C") { + // On the GW1N(R)-9C, it appears that only the 'odd' CLKDIV2 is connected to CLKDIV + Loc loc = ctx->getBelLocation(bel); + if (loc.z == BelZ::CLKDIV_0_Z || loc.z == BelZ::CLKDIV_2_Z) + return false; + } + + return true; + } + return false; +} // Cluster bool GowinImpl::getClusterPlacement(ClusterId cluster, BelId root_bel, diff --git a/himbaechel/uarch/gowin/gowin.h b/himbaechel/uarch/gowin/gowin.h index a9f247d8..c14949d6 100644 --- a/himbaechel/uarch/gowin/gowin.h +++ b/himbaechel/uarch/gowin/gowin.h @@ -62,6 +62,17 @@ inline bool type_is_dsp(IdString cell_type) } inline bool is_dsp(const CellInfo *cell) { return type_is_dsp(cell->type); } +// Return true if a cell is CLKDIV +inline bool type_is_clkdiv(IdString cell_type) { return cell_type == id_CLKDIV; } +inline bool is_clkdiv(const CellInfo *cell) { return type_is_clkdiv(cell->type); } + +// Return true if a cell is CLKDIV2 +inline bool type_is_clkdiv2(IdString cell_type) { return cell_type == id_CLKDIV2; } +inline bool is_clkdiv2(const CellInfo *cell) { return type_is_clkdiv2(cell->type); } + +// Return true for HCLK Cells +inline bool is_hclk(const CellInfo *cell) { return type_is_clkdiv2(cell->type) || type_is_clkdiv(cell->type); } + // ========================================== // extra data in the chip db // ========================================== @@ -191,7 +202,18 @@ enum ALU54D_1_Z = 556 + 3, MULTALU18X18_1_Z = 560, MULTALU36X18_1_Z = 560 + 1, - MULTADDALU18X18_1_Z = 560 + 2 + MULTADDALU18X18_1_Z = 560 + 2, + + // HCLK Bels + CLKDIV2_0_Z = 610, + CLKDIV2_1_Z = 611, + CLKDIV2_2_Z = 612, + CLKDIV2_3_Z = 613, + + CLKDIV_0_Z = 620, + CLKDIV_1_Z = 621, + CLKDIV_2_Z = 622, + CLKDIV_3_Z = 623 }; } diff --git a/himbaechel/uarch/gowin/gowin_arch_gen.py b/himbaechel/uarch/gowin/gowin_arch_gen.py index 75fda033..54d2e979 100644 --- a/himbaechel/uarch/gowin/gowin_arch_gen.py +++ b/himbaechel/uarch/gowin/gowin_arch_gen.py @@ -96,6 +96,17 @@ MULTALU18X18_1_Z = 560 MULTALU36X18_1_Z = 560 + 1 MULTADDALU18X18_1_Z = 560 + 2 +CLKDIV2_0_Z = 610 +CLKDIV2_1_Z = 611 +CLKDIV2_2_Z = 612 +CLKDIV2_3_Z = 613 + + +CLKDIV_0_Z = 620 +CLKDIV_1_Z = 621 +CLKDIV_2_Z = 622 +CLKDIV_3_Z = 623 + # ======================================= # Chipdb additional info # ======================================= @@ -357,6 +368,47 @@ def create_hclk_switch_matrix(tt: TileType, db: chipdb, x: int, y: int): if not tt.has_wire(src): tt.create_wire(src, "HCLK") tt.create_pip(src, dst) + + hclk_bel_zs = { + "CLKDIV2_HCLK0_SECT0": CLKDIV2_0_Z, + "CLKDIV2_HCLK0_SECT1": CLKDIV2_1_Z, + "CLKDIV2_HCLK1_SECT0": CLKDIV2_2_Z, + "CLKDIV2_HCLK1_SECT1": CLKDIV2_3_Z, + "CLKDIV_HCLK0_SECT0": CLKDIV_0_Z, + "CLKDIV_HCLK0_SECT1": CLKDIV_1_Z, + "CLKDIV_HCLK1_SECT0": CLKDIV_2_Z, + "CLKDIV_HCLK1_SECT1": CLKDIV_3_Z + } + + for bel_name, bel_props in db.grid[y][x].bels.items(): + if (bel_name not in hclk_bel_zs): + continue + this_portmap = bel_props.portmap + + if bel_name.startswith("CLKDIV2_"): + bel_type = "CLKDIV2" + elif bel_name.startswith("CLKDIV_"): + bel_type = "CLKDIV" + this_bel = tt.create_bel(bel_name, bel_type, hclk_bel_zs[bel_name]) + + if (bel_name in ["CLKDIV_HCLK0_SECT1", "CLKDIV_HCLK1_SECT1"]): + this_bel.flags |= BEL_FLAG_HIDDEN + if bel_type=="CLKDIV": + this_bel.flags |= BEL_FLAG_GLOBAL + + known_pins = ["HCLKIN", "RESETN", "CLKOUT"] + if bel_type == "CLKDIV": + known_pins.append("CALIB") + + for pin in this_portmap.keys(): + assert pin in known_pins, f"Unknown pin {pin} for bel {this_bel}" + if pin in ["CALIB", "RESETN", "HCLKIN"]: + pin_direction = PinType.INPUT + elif pin in ["CLKOUT"]: + pin_direction = PinType.OUTPUT + wire_type = "HCLK_CTRL" if pin in ("CALIB", "RESETN") else "HCLK" + add_port_wire(tt, this_bel, this_portmap, pin, wire_type, pin_direction) + # map spine -> dqce bel dqce_bels = {} diff --git a/himbaechel/uarch/gowin/gowin_utils.cc b/himbaechel/uarch/gowin/gowin_utils.cc index 5bf398d9..0d6fb8b8 100644 --- a/himbaechel/uarch/gowin/gowin_utils.cc +++ b/himbaechel/uarch/gowin/gowin_utils.cc @@ -6,6 +6,7 @@ #include "himbaechel_constids.h" #include "himbaechel_helpers.h" +#include #include "gowin.h" #include "gowin_utils.h" @@ -290,4 +291,145 @@ CellInfo *GowinUtils::dsp_bus_dst(const CellInfo *ci, const char *bus_prefix, in return connected_to_cell; } +// Use the upper CLKDIV as the id for a hclk section +IdStringList GowinUtils::get_hclk_id(BelId hclk_bel) const +{ + IdString bel_type = ctx->getBelType(hclk_bel); + NPNR_ASSERT(hclk_bel != BelId() && bel_type.in(id_CLKDIV2, id_CLKDIV)); + Loc id_loc = Loc(ctx->getBelLocation(hclk_bel)); + if (bel_type == id_CLKDIV) { + return get_hclk_id(get_clkdiv2_for_clkdiv(hclk_bel)); + } else if (id_loc.z == BelZ::CLKDIV2_0_Z || id_loc.z == BelZ::CLKDIV2_2_Z) + return ctx->getBelName(hclk_bel); + else + return ctx->getBelName(ctx->getBelByLocation(Loc(id_loc.x, id_loc.y, id_loc.z - 1))); + return IdStringList(); +} + +// Get the clkdiv in the same section as a clkdiv2 +BelId GowinUtils::get_clkdiv_for_clkdiv2(BelId clkdiv2_bel) const +{ + NPNR_ASSERT(clkdiv2_bel != BelId() && (ctx->getBelType(clkdiv2_bel) == id_CLKDIV2)); + Loc clkdiv_loc = ctx->getBelLocation(clkdiv2_bel); + clkdiv_loc.z = BelZ::CLKDIV_0_Z + (clkdiv_loc.z - BelZ::CLKDIV2_0_Z); + return ctx->getBelByLocation(clkdiv_loc); +} + +// Get the clkdiv2 in the same section as a clkdiv +BelId GowinUtils::get_clkdiv2_for_clkdiv(BelId clkdiv_bel) const +{ + NPNR_ASSERT(clkdiv_bel != BelId() && (ctx->getBelType(clkdiv_bel) == id_CLKDIV)); + Loc clkdiv_loc = ctx->getBelLocation(clkdiv_bel); + Loc clkdiv2_loc = Loc(clkdiv_loc.x, clkdiv_loc.y, BelZ::CLKDIV2_0_Z + (clkdiv_loc.z - BelZ::CLKDIV_0_Z)); + return ctx->getBelByLocation(clkdiv2_loc); +} + +// Get the clkdiv in the neighbouring section to a clkdiv +BelId GowinUtils::get_other_hclk_clkdiv(BelId clkdiv_bel) const +{ + NPNR_ASSERT(clkdiv_bel != BelId() && (ctx->getBelType(clkdiv_bel) == id_CLKDIV)); + Loc other_loc = Loc(ctx->getBelLocation(clkdiv_bel)); + int dz = BelZ::CLKDIV_1_Z - BelZ::CLKDIV_0_Z; + if (other_loc.z == BelZ::CLKDIV_1_Z || other_loc.z == BelZ::CLKDIV_3_Z) + dz = -dz; + other_loc.z += dz; + return ctx->getBelByLocation(other_loc); +} + +// Get the clkdiv2 in the neighbouring section to a clkdiv2 +BelId GowinUtils::get_other_hclk_clkdiv2(BelId clkdiv2_bel) const +{ + NPNR_ASSERT(clkdiv2_bel != BelId() && (ctx->getBelType(clkdiv2_bel) == id_CLKDIV2)); + Loc other_loc = Loc(ctx->getBelLocation(clkdiv2_bel)); + int dz = BelZ::CLKDIV2_1_Z - BelZ::CLKDIV2_0_Z; + if (other_loc.z == BelZ::CLKDIV2_1_Z || other_loc.z == BelZ::CLKDIV2_3_Z) + dz = -dz; + other_loc.z += dz; + return ctx->getBelByLocation(other_loc); +} + +// Credit: https://cp-algorithms.com/graph/kuhn_maximum_bipartite_matching.html +std::vector GowinUtils::kuhn_find_maximum_bipartite_matching(int n, int k, std::vector> &g) +{ + std::vector mt; + std::vector used(n); + + auto try_kuhn = [&](int v, auto &try_kuhn_ref) -> bool { + if (used[v]) + return false; + used[v] = true; + for (int to : g[v]) { + if (mt[to] == -1 || try_kuhn_ref(mt[to], try_kuhn_ref)) { + mt[to] = v; + return true; + } + } + return false; + }; + + mt.assign(k, -1); + for (int v = 0; v < n; ++v) { + used.assign(n, false); + try_kuhn(v, try_kuhn); + } + + return mt; +} + +// original implementation: nextpnr/machxo2/pack.cc +// Using a BFS, search for bels of a given type either upstream or downstream of another cell +void GowinUtils::find_connected_bels(const CellInfo *cell, IdString port, IdString dest_type, IdString dest_pin, + int iter_limit, std::vector &candidates) +{ + int iter = 0; + std::queue visit; + pool seen_wires; + pool seen_bels; + + BelId bel = cell->bel; + if (bel == BelId()) + return; + WireId start_wire = ctx->getBelPinWire(bel, port); + NPNR_ASSERT(start_wire != WireId()); + PortType dir = ctx->getBelPinType(bel, port); + + visit.push(start_wire); + + while (!visit.empty() && (iter++ < iter_limit)) { + WireId cursor = visit.front(); + visit.pop(); + // Check to see if we have reached a valid bel pin + for (auto bp : ctx->getWireBelPins(cursor)) { + if (ctx->getBelType(bp.bel) != dest_type) + continue; + if (dest_pin != IdString() && bp.pin != dest_pin) + continue; + if (seen_bels.count(bp.bel)) + continue; + seen_bels.insert(bp.bel); + candidates.push_back(bp.bel); + } + // Search in the appropriate direction up/downstream of the cursor + if (dir == PORT_OUT) { + for (PipId p : ctx->getPipsDownhill(cursor)) + if (ctx->checkPipAvail(p)) { + WireId dst = ctx->getPipDstWire(p); + if (seen_wires.count(dst)) + continue; + seen_wires.insert(dst); + visit.push(dst); + } + } else { + for (PipId p : ctx->getPipsUphill(cursor)) + if (ctx->checkPipAvail(p)) { + WireId src = ctx->getPipSrcWire(p); + if (seen_wires.count(src)) + continue; + seen_wires.insert(src); + visit.push(src); + } + } + } +} + NEXTPNR_NAMESPACE_END diff --git a/himbaechel/uarch/gowin/gowin_utils.h b/himbaechel/uarch/gowin/gowin_utils.h index c280b271..77b75082 100644 --- a/himbaechel/uarch/gowin/gowin_utils.h +++ b/himbaechel/uarch/gowin/gowin_utils.h @@ -94,6 +94,59 @@ struct GowinUtils // make cell but do not include it in the list of chip cells. std::unique_ptr create_cell(IdString name, IdString type); + + // HCLK + BelId get_clkdiv_for_clkdiv2(BelId clkdiv2_bel) const; + BelId get_other_hclk_clkdiv2(BelId clkdiv2_bel) const; + BelId get_other_hclk_clkdiv(BelId clkdiv_bel) const; + BelId get_clkdiv2_for_clkdiv(BelId clkdiv_bel) const; + IdStringList get_hclk_id(BelId hclk_bel) const; // use the upper CLKDIV2 (CLKDIV2_0 orCLKDIV2_2) as an id + + // Find Bels connected to a bound cell + void find_connected_bels(const CellInfo *cell, IdString port, IdString dest_type, IdString dest_pin, int iter_limit, + std::vector &candidates); + + // Find a maximum bipartite matching + template std::map find_maximum_bipartite_matching(std::map> &G) + { + std::map U; + std::map V; + std::map V_IDX; + std::vector> int_graph(G.size()); + + int u_idx = 0; + int v_idx = 0; + + // Translate the input graph to an integer graph + for (auto row : G) { + U.insert(std::pair(u_idx, row.first)); + for (auto v : row.second) { + if (V_IDX.find(v) == V_IDX.end()) { + V_IDX[v] = v_idx; + V[v_idx] = v; + v_idx++; + } + int_graph[u_idx].push_back(V_IDX[v]); + } + u_idx++; + } + + std::vector int_matching = kuhn_find_maximum_bipartite_matching(u_idx, v_idx, int_graph); + std::map ret_matching; + + int m_idx = 0; + for (auto val : int_matching) { + if (val >= 0) { // elements that are not matched have a value of -1 + ret_matching[U[val]] = V[m_idx]; + } + m_idx++; + } + + return ret_matching; + } + + // Find a maximum matching in a bipartite graph, g + std::vector kuhn_find_maximum_bipartite_matching(int n, int k, std::vector> &g); }; NEXTPNR_NAMESPACE_END diff --git a/himbaechel/uarch/gowin/pack.cc b/himbaechel/uarch/gowin/pack.cc index c34a88cf..ec1ef8f1 100644 --- a/himbaechel/uarch/gowin/pack.cc +++ b/himbaechel/uarch/gowin/pack.cc @@ -1,7 +1,6 @@ #include "design_utils.h" #include "log.h" #include "nextpnr.h" -#include "util.h" #define HIMBAECHEL_CONSTIDS "uarch/gowin/constids.inc" #include "himbaechel_constids.h" @@ -1325,7 +1324,7 @@ struct GowinPacker { int num = (bit_width == 9 || bit_width == 18 || bit_width == 36) ? 36 : 32; for (int i = 0, j = offset; i < num; ++i, ++j) { - if (((j + 1) % 9) == 0 && (bit_width == 16 || bit_width == 32)) { + if (((i + 1) % 9) == 0 && (bit_width == 16 || bit_width == 32)) { ++j; } ci->renamePort(ctx->idf(from, i), ctx->idf(to, offset ? j % 36 : j)); @@ -1391,15 +1390,6 @@ struct GowinPacker } ++idx; } - switch (idx) { - case 1: - init |= init << 2; /* fallthrough */ - case 2: - init |= init << 4; - break; - default: - break; - } lut->setParam(id_INIT, init); new_cells.push_back(std::move(lut_cell)); @@ -1416,7 +1406,7 @@ struct GowinPacker if (bit_width == 32 || bit_width == 36) { return; } - int read_mode = int_or_default(ci->params, id_READ_MODE, 0); + int read_mode = ci->params.at(id_READ_MODE).as_int64(); if (read_mode == 0) { return; } @@ -1521,7 +1511,7 @@ struct GowinPacker CellInfo *new_ce_net_src = ce_pre_dff; // add delay register in pipeline mode - int read_mode = int_or_default(ci->params, id_READ_MODE, 0); + int read_mode = ci->params.at(id_READ_MODE).as_int64(); if (read_mode) { auto ce_pipe_dff_cell = gwu.create_cell(create_aux_name(ci->name, 0, "_ce_pipe_dff$"), id_DFF); new_cells.push_back(std::move(ce_pipe_dff_cell)); @@ -1597,7 +1587,10 @@ struct GowinPacker ci->connectPort(port, vcc_net); } + ci->addInput(id_WRE); + ci->connectPort(id_WRE, vss_net); ci->addInput(id_WREB); + ci->connectPort(id_WREB, vss_net); if (!ci->params.count(id_BIT_WIDTH)) { ci->setParam(id_BIT_WIDTH, Property(default_bw, 32)); @@ -1609,19 +1602,10 @@ struct GowinPacker ci->copyPortTo(id_CE, ci, id_CEB); ci->copyPortTo(id_OCE, ci, id_OCEB); ci->copyPortTo(id_RESET, ci, id_RESETB); - ci->connectPort(id_WREB, vcc_net); - // disconnect lower address bits for ROM - std::array rom_ignore_bits = {2, 4, 8, 16, 32}; - std::array romx9_ignore_bits = {9, 9, 9, 18, 36}; - for (unsigned int i = 0; i < 14; ++i) { - if (i < size(rom_ignore_bits) && ((ci->type == id_pROM && bit_width >= rom_ignore_bits[i]) || - (ci->type == id_pROMX9 && bit_width >= romx9_ignore_bits[i]))) { - ci->disconnectPort(ctx->idf("AD[%d]", i)); - } else { - ci->renamePort(ctx->idf("AD[%d]", i), ctx->idf("ADA%d", i)); - ci->copyPortTo(ctx->idf("ADA%d", i), ci, ctx->idf("ADB%d", i)); - } + for (int i = 0; i < 14; ++i) { + ci->renamePort(ctx->idf("AD[%d]", i), ctx->idf("ADA%d", i)); + ci->copyPortTo(ctx->idf("ADA%d", i), ci, ctx->idf("ADB%d", i)); } bsram_rename_ports(ci, bit_width, "DO[%d]", "DO%d"); } else { @@ -1630,18 +1614,9 @@ struct GowinPacker ci->renamePort(id_OCE, id_OCEB); ci->renamePort(id_CE, id_CEB); ci->renamePort(id_RESET, id_RESETB); - ci->connectPort(id_WREB, vss_net); - ci->addInput(id_CE); - ci->connectPort(id_CE, vcc_net); - ci->disconnectPort(id_OCEB); - - int read_mode = int_or_default(ci->params, id_READ_MODE, 0); - if (read_mode) { - ci->connectPort(id_OCEB, vcc_net); - } else { - ci->copyPortTo(id_CEB, ci, id_OCEB); - } + ci->addInput(id_CEA); + ci->connectPort(id_CEA, vss_net); for (int i = 0; i < 14; ++i) { ci->renamePort(ctx->idf("AD[%d]", i), ctx->idf("ADB%d", i)); } @@ -1823,21 +1798,6 @@ struct GowinPacker bsram_fix_blksel(ci, new_cells); } - // The statement in the Gowin documentation that in the reading mode - // "READ_MODE=0" the output register is not used and the OCE signal is - // ignored is not confirmed by practice - if the OCE was left - // unconnected or connected to the constant network, then a change in - // output data was observed even with CE=0, as well as the absence of - // such at CE=1. - // Synchronizing CE and OCE helps but it's definitely a hack. - NetInfo *oce_net = ci->getPort(id_OCE); - if (oce_net == nullptr || oce_net->name == ctx->id("$PACKER_VCC") || oce_net->name == ctx->id("$PACKER_GND")) { - if (oce_net != nullptr) { - ci->disconnectPort(id_OCE); - } - ci->copyPortTo(id_CE, ci, id_OCE); - } - // XXX UG285-1.3.6_E Gowin BSRAM & SSRAM User Guide: // For GW1N-9/GW1NR-9/GW1NS-4 series, 32/36-bit SP/SPX9 is divided into two // SP/SPX9s, which occupy two BSRAMs. @@ -2981,6 +2941,39 @@ struct GowinPacker } } + // =================================== + // HCLK -- CLKDIV and CLKDIV2 for now + // =================================== + void pack_hclk(void) + { + log_info("Pack HCLK cells...\n"); + + for (auto &cell : ctx->cells) { + auto ci = cell.second.get(); + if (ci->type != id_CLKDIV) + continue; + NetInfo *hclk_in = ci->getPort(id_HCLKIN); + if (hclk_in) { + CellInfo *this_driver = hclk_in->driver.cell; + if (this_driver && this_driver->type == id_CLKDIV2) { + NetInfo *out = this_driver->getPort(id_CLKOUT); + if (out->users.entries() > 1) { + // We could do as the IDE does sometimes and replicate the CLKDIV2 cell + // as many times as we need. For now, we keep things simple + log_error("CLKDIV2 that drives CLKDIV should drive no other cells\n"); + } + ci->cluster = ci->name; + this_driver->cluster = ci->name; + ci->constr_children.push_back(this_driver); + this_driver->constr_x = 0; + this_driver->constr_y = 0; + this_driver->constr_z = BelZ::CLKDIV2_0_Z - BelZ::CLKDIV_0_Z; + this_driver->constr_abs_z = false; + } + } + } + } + // ========================================= // Create entry points to the clock system // ========================================= @@ -3097,6 +3090,9 @@ struct GowinPacker pack_gsr(); ctx->check(); + pack_hclk(); + ctx->check(); + pack_bandgap(); ctx->check();