From bc7b8b63ed6c8e855ca0256a2fde2b8c5885fd79 Mon Sep 17 00:00:00 2001 From: gatecat Date: Tue, 16 May 2023 16:38:16 +0200 Subject: [PATCH] mistral: Add a basic global clock router Signed-off-by: gatecat --- mistral/arch.cc | 2 + mistral/arch.h | 1 + mistral/globals.cc | 149 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 152 insertions(+) diff --git a/mistral/arch.cc b/mistral/arch.cc index 05d780f3..da75ad92 100644 --- a/mistral/arch.cc +++ b/mistral/arch.cc @@ -484,6 +484,8 @@ bool Arch::route() lab_pre_route(); + route_globals(); + std::string router = str_or_default(settings, id_router, defaultRouter); bool result; if (router == "router1") { diff --git a/mistral/arch.h b/mistral/arch.h index aabe10c6..7cdc9f6b 100644 --- a/mistral/arch.h +++ b/mistral/arch.h @@ -503,6 +503,7 @@ struct Arch : BaseArch // ------------------------------------------------- bool is_clkbuf_cell(IdString cell_type) const; // globals.cc + void route_globals(); // globals.cc // ------------------------------------------------- diff --git a/mistral/globals.cc b/mistral/globals.cc index bdd835a7..cb00b635 100644 --- a/mistral/globals.cc +++ b/mistral/globals.cc @@ -21,6 +21,8 @@ #include "nextpnr.h" #include "util.h" +#include + NEXTPNR_NAMESPACE_BEGIN void Arch::create_clkbuf(int x, int y) @@ -60,4 +62,151 @@ void Arch::create_control(int x, int y) add_bel_pin(oscillator_bel, id_clkout1, PORT_OUT, get_port(CycloneV::CTRL, x, y, -1, CycloneV::CLK_OUT1, -1)); } +struct MistralGlobalRouter +{ + Context *ctx; + + MistralGlobalRouter(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 + { + auto src_type = CycloneV::rn2t(pip.src); + return src_type != CycloneV::H14 && src_type != CycloneV::H6 && src_type != CycloneV::H3 && + src_type != CycloneV::V12 && src_type != CycloneV::V2 && src_type != CycloneV::V4 && + src_type != CycloneV::WM; + } + + // 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_MISTRAL_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_MISTRAL_CLKENA, id_MISTRAL_CLKBUF)) { + route_clk_net(ni); + continue; + } + } + } +}; + +void Arch::route_globals() +{ + MistralGlobalRouter router(getCtx()); + router(); +} + NEXTPNR_NAMESPACE_END