/* * nextpnr -- Next Generation Place and Route * * Copyright (C) 2021 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 void Arch::create_clkbuf(int x, int y) { for (int z = 0; z < 4; z++) { if (z != 2) continue; // TODO: why do other Zs not work? // For now we only consider the input path from general routing, other inputs like dedicated clock pins are // still a TODO BelId bel = add_bel(x, y, idf("CLKBUF[%d]", z), id_MISTRAL_CLKENA); add_bel_pin(bel, id_A, PORT_IN, get_port(CycloneV::CMUXHG, x, y, -1, CycloneV::CLKIN, z)); add_bel_pin(bel, id_Q, PORT_OUT, get_port(CycloneV::CMUXHG, x, y, z, CycloneV::CLKOUT)); // TODO: enable pin bel_data(bel).block_index = z; } } bool Arch::is_clkbuf_cell(IdString cell_type) const { return cell_type.in(id_MISTRAL_CLKENA, id_MISTRAL_CLKBUF); } void Arch::create_hps_mpu_general_purpose(int x, int y) { BelId gp_bel = add_bel(x, y, id_cyclonev_hps_interface_mpu_general_purpose, id_cyclonev_hps_interface_mpu_general_purpose); for (int i = 0; i < 32; i++) { add_bel_pin(gp_bel, idf("gp_in[%d]", i), PORT_IN, get_port(CycloneV::HPS_MPU_GENERAL_PURPOSE, x, y, -1, CycloneV::GP_IN, i)); add_bel_pin(gp_bel, idf("gp_out[%d]", i), PORT_OUT, get_port(CycloneV::HPS_MPU_GENERAL_PURPOSE, x, y, -1, CycloneV::GP_OUT, i)); } } void Arch::create_control(int x, int y) { BelId oscillator_bel = add_bel(x, y, id_cyclonev_oscillator, id_cyclonev_oscillator); add_bel_pin(oscillator_bel, id_oscena, PORT_IN, get_port(CycloneV::CTRL, x, y, -1, CycloneV::OSC_ENA, -1)); add_bel_pin(oscillator_bel, id_clkout, PORT_OUT, get_port(CycloneV::CTRL, x, y, -1, CycloneV::CLK_OUT, -1)); 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