From e3529d335617cadca0d9d2a5ce5f5b5cdca09a3e Mon Sep 17 00:00:00 2001 From: gatecat Date: Fri, 5 May 2023 10:48:34 +0200 Subject: [PATCH] machxo2: Global placement and clock routing from nexus Signed-off-by: gatecat --- .github/workflows/arch_ci.yml | 2 +- machxo2/arch.cc | 2 + machxo2/arch.h | 3 + machxo2/bitstream.cc | 7 +- machxo2/globals.cc | 178 +++++++++++++++++++ machxo2/pack.cc | 324 +++++++++++++++++++++++++++------- 6 files changed, 450 insertions(+), 66 deletions(-) create mode 100644 machxo2/globals.cc diff --git a/.github/workflows/arch_ci.yml b/.github/workflows/arch_ci.yml index 13abb52f3e..db13c4cb11 100644 --- a/.github/workflows/arch_ci.yml +++ b/.github/workflows/arch_ci.yml @@ -15,7 +15,7 @@ jobs: DEPS_PATH: ${{ github.workspace }}/deps YOSYS_REVISION: bd7ee79486d4e8788f36de8c25a3fb2df451d682 ICESTORM_REVISION: 9f66f9ce16941c6417813cb87653c735a78b53ae - TRELLIS_REVISION: c99b22d35e4109333e7549e56cec139569a17392 + TRELLIS_REVISION: f1e5710099313d2e1862d4ef2582293f6b7ee122 PRJOXIDE_REVISION: c3fb1526cf4a2165e15b74f4a994d153c7695fe4 MISTRAL_REVISION: ebfc0dd2cc7d6d2159b641a397c88554840e93c9 APYCULA_REVISION: 0.5.1a1 diff --git a/machxo2/arch.cc b/machxo2/arch.cc index 6aadd2d17f..3c135e8f87 100644 --- a/machxo2/arch.cc +++ b/machxo2/arch.cc @@ -433,6 +433,8 @@ bool Arch::route() assignArchInfo(); + route_globals(); + bool result; if (router == "router1") { result = router1(getCtx(), Router1Cfg(getCtx())); diff --git a/machxo2/arch.h b/machxo2/arch.h index 222390e8be..855b39df70 100644 --- a/machxo2/arch.h +++ b/machxo2/arch.h @@ -967,6 +967,9 @@ struct Arch : BaseArch // Apply LPF constraints to the context bool apply_lpf(std::string filename, std::istream &in); + // Global clock routing + void route_globals(); + static const std::string defaultPlacer; static const std::vector availablePlacers; static const std::string defaultRouter; diff --git a/machxo2/bitstream.cc b/machxo2/bitstream.cc index bf7d13a761..652152bd4d 100644 --- a/machxo2/bitstream.cc +++ b/machxo2/bitstream.cc @@ -86,9 +86,14 @@ struct MachXO2Bitgen // clang-format on }; - if (prefix2 == "G_" || prefix2 == "L_" || prefix2 == "R_" || prefix7 == "BRANCH_") + if (prefix2 == "G_" || prefix7 == "BRANCH_") return basename; + if (prefix2 == "L_" || prefix2 == "R_") { + if (loc.x == 0 || loc.x == (ctx->getGridDimX() - 1)) + return "G_" + basename.substr(2); + return basename; + } if (prefix2 == "U_" || prefix2 == "D_") { // We needded to keep U_ and D_ prefixes to generate the routing // graph connections properly, but in truth they are not relevant diff --git a/machxo2/globals.cc b/machxo2/globals.cc new file mode 100644 index 0000000000..bef4628d82 --- /dev/null +++ b/machxo2/globals.cc @@ -0,0 +1,178 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2020-23 gatecat + * + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "log.h" +#include "nextpnr.h" +#include "util.h" + +#include + +NEXTPNR_NAMESPACE_BEGIN + +struct MachxoGlobalRouter +{ + Context *ctx; + + MachxoGlobalRouter(Context *ctx) : ctx(ctx){}; + + // When routing globals; we allow global->local for some tricky cases but never local->local + bool global_pip_filter(PipId pip) const + { + WireId src = ctx->getPipSrcWire(pip); + const char *s = ctx->tile_info(src)->wire_data[src.index].name.get(); + if ((s[0] == 'H' || s[0] == 'V') && s[1] == '0') + return false; + return true; + } + + // Dedicated backwards BFS routing for global networks + template + bool backwards_bfs_route(NetInfo *net, store_index user_idx, int iter_limit, bool strict, Tfilt pip_filter) + { + // Queue of wires to visit + std::queue visit; + // Wire -> upstream pip + dict backtrace; + + // Lookup source and destination wires + WireId src = ctx->getNetinfoSourceWire(net); + WireId dst = ctx->getNetinfoSinkWire(net, net->users.at(user_idx), 0); + + if (src == WireId()) + log_error("Net '%s' has an invalid source port %s.%s\n", ctx->nameOf(net), ctx->nameOf(net->driver.cell), + ctx->nameOf(net->driver.port)); + + if (dst == WireId()) + log_error("Net '%s' has an invalid sink port %s.%s\n", ctx->nameOf(net), + ctx->nameOf(net->users.at(user_idx).cell), ctx->nameOf(net->users.at(user_idx).port)); + + if (ctx->getBoundWireNet(src) != net) + ctx->bindWire(src, net, STRENGTH_LOCKED); + + if (src == dst) { + // Nothing more to do + return true; + } + + visit.push(dst); + backtrace[dst] = PipId(); + + int iter = 0; + + while (!visit.empty() && (iter++ < iter_limit)) { + WireId cursor = visit.front(); + visit.pop(); + // Search uphill pips + for (PipId pip : ctx->getPipsUphill(cursor)) { + // Skip pip if unavailable, and not because it's already used for this net + if (!ctx->checkPipAvail(pip) && ctx->getBoundPipNet(pip) != net) + continue; + WireId prev = ctx->getPipSrcWire(pip); + // Ditto for the upstream wire + if (!ctx->checkWireAvail(prev) && ctx->getBoundWireNet(prev) != net) + continue; + // Skip already visited wires + if (backtrace.count(prev)) + continue; + // Apply our custom pip filter + if (!pip_filter(pip)) + continue; + // Add to the queue + visit.push(prev); + backtrace[prev] = pip; + // Check if we are done yet + if (prev == src) + goto done; + } + if (false) { + done: + break; + } + } + + if (backtrace.count(src)) { + WireId cursor = src; + std::vector pips; + // Create a list of pips on the routed path + while (true) { + PipId pip = backtrace.at(cursor); + if (pip == PipId()) + break; + pips.push_back(pip); + cursor = ctx->getPipDstWire(pip); + } + // Reverse that list + std::reverse(pips.begin(), pips.end()); + // Bind pips until we hit already-bound routing + for (PipId pip : pips) { + WireId dst = ctx->getPipDstWire(pip); + if (ctx->getBoundWireNet(dst) == net) + break; + ctx->bindPip(pip, net, STRENGTH_LOCKED); + } + return true; + } else { + if (strict) + log_error("Failed to route net '%s' from %s to %s using dedicated routing.\n", ctx->nameOf(net), + ctx->nameOfWire(src), ctx->nameOfWire(dst)); + return false; + } + } + + bool is_relaxed_sink(const PortRef &sink) const + { + // Cases where global clocks are driving fabric + if ((sink.cell->type == id_TRELLIS_COMB && sink.port != id_WCK) || + (sink.cell->type == id_TRELLIS_FF && sink.port != id_CLK)) + return true; + return false; + } + + void route_clk_net(NetInfo *net) + { + for (auto usr : net->users.enumerate()) + backwards_bfs_route(net, usr.index, 1000000, true, + [&](PipId pip) { return (is_relaxed_sink(usr.value) || global_pip_filter(pip)); }); + log_info(" routed net '%s' using global resources\n", ctx->nameOf(net)); + } + + void operator()() + { + log_info("Routing globals...\n"); + for (auto &net : ctx->nets) { + NetInfo *ni = net.second.get(); + CellInfo *drv = ni->driver.cell; + if (drv == nullptr) + continue; + if (drv->type.in(id_DCCA, id_DCMA)) { + route_clk_net(ni); + continue; + } + } + } +}; + +void Arch::route_globals() +{ + MachxoGlobalRouter glb_router(getCtx()); + glb_router(); +} + +NEXTPNR_NAMESPACE_END diff --git a/machxo2/pack.cc b/machxo2/pack.cc index 3a99b256b6..22ca51c475 100644 --- a/machxo2/pack.cc +++ b/machxo2/pack.cc @@ -941,60 +941,6 @@ class MachXO2Packer } } - // Preplace PLL - void preplace_plls() - { - std::set available_plls; - for (auto bel : ctx->getBels()) { - if (ctx->getBelType(bel) == id_EHXPLLJ && ctx->checkBelAvail(bel)) - available_plls.insert(bel); - } - for (auto &cell : ctx->cells) { - CellInfo *ci = cell.second.get(); - if (ci->type == id_EHXPLLJ && ci->attrs.count(id_BEL)) - available_plls.erase(ctx->getBelByNameStr(ci->attrs.at(id_BEL).as_string())); - } - // Place PLL connected to fixed drivers such as IO close to their source - for (auto &cell : ctx->cells) { - CellInfo *ci = cell.second.get(); - if (ci->type == id_EHXPLLJ && !ci->attrs.count(id_BEL)) { - const NetInfo *drivernet = ci->getPort(id_CLKI); - if (drivernet == nullptr || drivernet->driver.cell == nullptr) - continue; - const CellInfo *drivercell = drivernet->driver.cell; - if (!drivercell->attrs.count(id_BEL)) - continue; - BelId drvbel = ctx->getBelByNameStr(drivercell->attrs.at(id_BEL).as_string()); - Loc drvloc = ctx->getBelLocation(drvbel); - BelId closest_pll; - int closest_distance = std::numeric_limits::max(); - for (auto bel : available_plls) { - Loc pllloc = ctx->getBelLocation(bel); - int distance = std::abs(drvloc.x - pllloc.x) + std::abs(drvloc.y - pllloc.y); - if (distance < closest_distance) { - closest_pll = bel; - closest_distance = distance; - } - } - if (closest_pll == BelId()) - log_error("failed to place PLL '%s'\n", ci->name.c_str(ctx)); - available_plls.erase(closest_pll); - ci->attrs[id_BEL] = ctx->getBelName(closest_pll).str(ctx); - } - } - // Place PLLs driven by logic, etc, randomly - for (auto &cell : ctx->cells) { - CellInfo *ci = cell.second.get(); - if (ci->type == id_EHXPLLJ && !ci->attrs.count(id_BEL)) { - if (available_plls.empty()) - log_error("failed to place PLL '%s'\n", ci->name.c_str(ctx)); - BelId next_pll = *(available_plls.begin()); - available_plls.erase(next_pll); - ci->attrs[id_BEL] = ctx->getBelName(next_pll).str(ctx); - } - } - } - // Check if two nets have identical constant drivers bool equal_constant(NetInfo *a, NetInfo *b) { @@ -1199,18 +1145,17 @@ class MachXO2Packer simple_clk_contraint(vco_period * int_or_default(ci->params, id_CLKOS3_DIV, 1))); } else if (ci->type == id_OSCH) { static std::string const osch_freq[] = { - "2.08", "2.15", "2.22", "2.29", "2.38", "2.46", "2.56", "2.66", - "2.77", "2.89", "3.02", "3.17", "3.33", "3.50", "3.69", "3.91", - "4.16", "4.29", "4.43", "4.59", "4.75", "4.93", "5.12", "5.32", - "5.54", "5.78", "6.05", "6.33", "6.65", "7.00", "7.39", "7.82", - "8.31", "8.58", "8.87", "9.17", "9.50", "9.85", "10.23", "10.64", - "11.08", "11.57", "12.09", "12.67", "13.30", "14.00", "14.78", "15.65", - "15.65", "16.63", "17.73", "19.00", "20.46", "22.17", "24.18", "26.60", - "29.56", "33.25", "38.00", "44.33", "53.20", "66.50", "88.67", "133.00" }; + "2.08", "2.15", "2.22", "2.29", "2.38", "2.46", "2.56", "2.66", "2.77", "2.89", + "3.02", "3.17", "3.33", "3.50", "3.69", "3.91", "4.16", "4.29", "4.43", "4.59", + "4.75", "4.93", "5.12", "5.32", "5.54", "5.78", "6.05", "6.33", "6.65", "7.00", + "7.39", "7.82", "8.31", "8.58", "8.87", "9.17", "9.50", "9.85", "10.23", "10.64", + "11.08", "11.57", "12.09", "12.67", "13.30", "14.00", "14.78", "15.65", "15.65", "16.63", + "17.73", "19.00", "20.46", "22.17", "24.18", "26.60", "29.56", "33.25", "38.00", "44.33", + "53.20", "66.50", "88.67", "133.00"}; std::string freq = str_or_default(ci->params, id_NOM_FREQ, "2.08"); bool found = false; - for (int i=0;i<64;i++) { + for (int i = 0; i < 64; i++) { if (osch_freq[i] == freq) { found = true; set_constraint(ci, id_OSC, simple_clk_contraint(delay_t(1000.0 / std::stof(freq)))); @@ -1235,13 +1180,262 @@ class MachXO2Packer } } + BelId get_bel_attr(const CellInfo *ci) + { + if (!ci->attrs.count(id_BEL)) + return BelId(); + return ctx->getBelByNameStr(ci->attrs.at(id_BEL).as_string()); + } + + // Using a BFS, search for bels of a given type either upstream or downstream of another cell + void 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 = get_bel_attr(cell); + 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); + } + } + } + } + + // Find the nearest bel of a given type; matching a closure predicate + template BelId find_nearest_bel(const CellInfo *cell, IdString dest_type, Tpred predicate) + { + BelId origin = get_bel_attr(cell); + if (origin == BelId()) + return BelId(); + Loc origin_loc = ctx->getBelLocation(origin); + int best_distance = std::numeric_limits::max(); + BelId best_bel = BelId(); + + for (BelId bel : ctx->getBels()) { + if (ctx->getBelType(bel) != dest_type) + continue; + if (!predicate(bel)) + continue; + Loc bel_loc = ctx->getBelLocation(bel); + int dist = std::abs(origin_loc.x - bel_loc.x) + std::abs(origin_loc.y - bel_loc.y); + if (dist < best_distance) { + best_distance = dist; + best_bel = bel; + } + } + return best_bel; + } + + pool used_bels; + // Pre-place a primitive based on routeability first and distance second + bool preplace_prim(CellInfo *cell, IdString pin, bool strict_routing) + { + std::vector routeability_candidates; + + if (cell->attrs.count(id_BEL)) + return false; + + NetInfo *pin_net = cell->getPort(pin); + if (pin_net == nullptr) + return false; + + CellInfo *pin_drv = pin_net->driver.cell; + if (pin_drv == nullptr) + return false; + + // Check based on routeability + find_connected_bels(pin_drv, pin_net->driver.port, cell->type, pin, 25000, routeability_candidates); + + for (BelId cand : routeability_candidates) { + if (used_bels.count(cand)) + continue; + log_info(" constraining %s '%s' to bel '%s' based on dedicated routing\n", ctx->nameOf(cell), + ctx->nameOf(cell->type), ctx->nameOfBel(cand)); + cell->attrs[id_BEL] = ctx->getBelName(cand).str(ctx); + used_bels.insert(cand); + return true; + } + + // Unless in strict mode; check based on simple distance too + BelId nearest = find_nearest_bel(pin_drv, cell->type, [&](BelId bel) { return !used_bels.count(bel); }); + + if (nearest != BelId()) { + log_info(" constraining %s '%s' to bel '%s'\n", ctx->nameOf(cell), ctx->nameOf(cell->type), + ctx->nameOfBel(nearest)); + cell->attrs[id_BEL] = ctx->getBelName(nearest).str(ctx); + used_bels.insert(nearest); + return true; + } + + return false; + } + + // Pre-place a singleton primitive; so decisions can be made on routeability downstream of it + bool preplace_singleton(CellInfo *cell) + { + if (cell->attrs.count(id_BEL)) + return false; + bool did_something = false; + for (BelId bel : ctx->getBels()) { + if (ctx->getBelType(bel) != cell->type) + continue; + // Check that the bel really is a singleton... + NPNR_ASSERT(!cell->attrs.count(id_BEL)); + cell->attrs[id_BEL] = ctx->getBelName(bel).str(ctx); + log_info(" constraining %s '%s' to bel '%s'\n", ctx->nameOf(cell), ctx->nameOf(cell->type), + ctx->nameOfBel(bel)); + did_something = true; + } + return did_something; + } + + // Insert a buffer primitive in a signal; moving all users that match a predicate behind it + template + CellInfo *insert_buffer(NetInfo *net, IdString buffer_type, std::string name_postfix, IdString i, IdString o, + Tpred pred) + { + // Create the buffered net + NetInfo *buffered_net = ctx->createNet(ctx->idf("%s$%s", ctx->nameOf(net), name_postfix.c_str())); + // Create the buffer cell + CellInfo *buffer = ctx->createCell(ctx->idf("%s$drv_%s", ctx->nameOf(buffered_net), ctx->nameOf(buffer_type)), + buffer_type); + buffer->addInput(i); + buffer->addOutput(o); + // Drive the buffered net with the buffer + buffer->connectPort(o, buffered_net); + // Filter users + std::vector remaining_users; + + for (auto &usr : net->users) { + if (pred(usr)) { + usr.cell->ports[usr.port].net = buffered_net; + usr.cell->ports[usr.port].user_idx = buffered_net->users.add(usr); + } else { + remaining_users.push_back(usr); + } + } + + net->users.clear(); + for (auto &usr : remaining_users) + usr.cell->ports.at(usr.port).user_idx = net->users.add(usr); + + // Connect buffer input to original net + buffer->connectPort(i, net); + + return buffer; + } + + // Insert global buffers + void promote_globals() + { + std::vector> clk_fanout; + int available_globals = 8; + for (auto &net : ctx->nets) { + NetInfo *ni = net.second.get(); + // Skip undriven nets; and nets that are already global + if (ni->driver.cell == nullptr) + continue; + if (ni->name.in(ctx->id("$PACKER_GND_NET"), ctx->id("$PACKER_VCC_NET"))) + continue; + if (ni->driver.cell->type == id_DCMA) { + continue; + } + if (ni->driver.cell->type == id_DCCA) { + --available_globals; + continue; + } + // Count the number of clock ports + int clk_count = 0; + for (const auto &usr : ni->users) { + if (usr.cell->type == id_TRELLIS_FF && usr.port == id_CLK) + clk_count++; + if (usr.cell->type == id_DP8KC && usr.port.in(id_CLKA, id_CLKB)) + clk_count++; + } + if (clk_count > 0) + clk_fanout.emplace_back(clk_count, ni->name); + } + if (available_globals <= 0) + return; + // Sort clocks by max fanout + std::sort(clk_fanout.begin(), clk_fanout.end(), std::greater>()); + log_info("Promoting globals...\n"); + // Promote the N highest fanout clocks + for (size_t i = 0; i < std::min(clk_fanout.size(), available_globals); i++) { + NetInfo *net = ctx->nets.at(clk_fanout.at(i).second).get(); + log_info(" promoting clock net '%s'\n", ctx->nameOf(net)); + insert_buffer(net, id_DCCA, "glb_clk", id_CLKI, id_CLKO, + [&](const PortRef &port) { return port.cell->type != id_DCCA; }); + } + } + + // Place certain global cells + void place_globals() + { + // Keep running until we reach a fixed point + log_info("Placing globals...\n"); + bool did_something = true; + while (did_something) { + did_something = false; + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (ci->type == id_OSCH) + did_something |= preplace_singleton(ci); + else if (ci->type == id_DCCA) + did_something |= preplace_prim(ci, id_CLKI, false); + else if (ci->type == id_EHXPLLJ) + did_something |= preplace_prim(ci, id_CLKI, false); + } + } + } + public: void pack() { prepack_checks(); print_logic_usage(); pack_io(); - preplace_plls(); pack_ebr(); pack_misc(); pack_constants(); @@ -1249,6 +1443,8 @@ class MachXO2Packer pack_carries(); pack_luts(); pack_ffs(); + promote_globals(); + place_globals(); generate_constraints(); ctx->fixupHierarchy(); ctx->check();