/* * nextpnr -- Next Generation Place and Route * * Copyright (C) 2019-2023 Myrtle Shah * * 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 #include #include #include #include #include "chain_utils.h" #include "design_utils.h" #include "extra_data.h" #include "log.h" #include "nextpnr.h" #include "pack.h" #include "pins.h" #define HIMBAECHEL_CONSTIDS "uarch/xilinx/constids.inc" #include "himbaechel_constids.h" NEXTPNR_NAMESPACE_BEGIN BelId XilinxPacker::find_bel_with_short_route(WireId source, IdString beltype, IdString belpin) { if (source == WireId()) return BelId(); const size_t max_visit = 1000000; // effort/runtime tradeoff pool visited; std::queue visit; visit.push(source); while (!visit.empty() && visited.size() < max_visit) { WireId cursor = visit.front(); visit.pop(); for (auto bp : ctx->getWireBelPins(cursor)) if (bp.pin == belpin && ctx->getBelType(bp.bel) == beltype && ctx->checkBelAvail(bp.bel)) return bp.bel; for (auto pip : ctx->getPipsDownhill(cursor)) { WireId dst = ctx->getPipDstWire(pip); IdString intent = ctx->getWireType(dst); // log_info("%s %s\n", ctx->nameOfWire(dst), ctx->nameOf(intent)); if (intent.in(id_NODE_DOUBLE, id_NODE_HLONG, id_NODE_HQUAD, id_NODE_VLONG, id_NODE_VQUAD, id_NODE_SINGLE, id_NODE_CLE_OUTPUT, id_NODE_OPTDELAY, id_BENTQUAD, id_DOUBLE, id_HLONG, id_HQUAD, id_OPTDELAY, id_SINGLE, id_VLONG, id_VLONG12, id_VQUAD)) continue; if (visited.count(dst)) continue; visit.push(dst); visited.insert(dst); } } return BelId(); } bool XilinxPacker::try_preplace(CellInfo *cell, IdString port) { if (cell->attrs.count(id_BEL) || cell->bel != BelId()) return false; NetInfo *n = cell->getPort(port); if (n == nullptr || n->driver.cell == nullptr) return false; CellInfo *drv = n->driver.cell; BelId drv_bel = drv->bel; if (drv_bel == BelId()) return false; WireId drv_wire = ctx->getBelPinWire(drv_bel, n->driver.port); if (drv_wire == WireId()) return false; BelId tgt = find_bel_with_short_route(drv_wire, cell->type, port); if (tgt != BelId()) { ctx->bindBel(tgt, cell, STRENGTH_LOCKED); log_info(" Constrained %s '%s' to bel '%s' based on dedicated routing\n", cell->type.c_str(ctx), ctx->nameOf(cell), ctx->nameOfBel(tgt)); return true; } else { return false; } } void XilinxPacker::preplace_unique(CellInfo *cell) { if (cell->attrs.count(id_BEL) || cell->bel != BelId()) return; for (auto bel : ctx->getBels()) { if (ctx->checkBelAvail(bel) && ctx->getBelType(bel) == cell->type) { ctx->bindBel(bel, cell, STRENGTH_LOCKED); return; } } } void XC7Packer::prepare_clocking() { log_info("Preparing clocking...\n"); dict upgrade; upgrade[id_MMCME2_BASE] = id_MMCME2_ADV; upgrade[id_PLLE2_BASE] = id_PLLE2_ADV; for (auto &cell : ctx->cells) { CellInfo *ci = cell.second.get(); if (upgrade.count(ci->type)) { IdString new_type = upgrade.at(ci->type); ci->type = new_type; } else if (ci->type == id_BUFG) { ci->type = id_BUFGCTRL; ci->renamePort(id_I, id_I0); tie_port(ci, "CE0", true, true); tie_port(ci, "S0", true, true); tie_port(ci, "S1", false, true); tie_port(ci, "IGNORE0", true, true); } else if (ci->type == id_BUFGCE) { ci->type = id_BUFGCTRL; ci->renamePort(id_I, id_I0); ci->renamePort(id_CE, id_CE0); tie_port(ci, "S0", true, true); tie_port(ci, "S1", false, true); tie_port(ci, "IGNORE0", true, true); } } } void XC7Packer::pack_plls() { log_info("Packing PLLs...\n"); auto set_default = [](CellInfo *ci, IdString param, const Property &value) { if (!ci->params.count(param)) ci->params[param] = value; }; dict pll_rules; pll_rules[id_MMCME2_ADV].new_type = id_MMCME2_ADV_MMCME2_ADV; pll_rules[id_PLLE2_ADV].new_type = id_PLLE2_ADV_PLLE2_ADV; generic_xform(pll_rules); for (auto &cell : ctx->cells) { CellInfo *ci = cell.second.get(); if (ci->type == id_MMCM_MMCM_TOP) { // Fixup parameters for (int i = 1; i <= 2; i++) set_default(ci, ctx->idf("CLKIN%d_PERIOD", i), Property("0.0")); for (int i = 0; i <= 6; i++) { set_default(ci, ctx->idf("CLKOUT%d_CASCADE", i), Property("FALSE")); set_default(ci, ctx->idf("CLKOUT%d_DIVIDE", i), Property(1)); set_default(ci, ctx->idf("CLKOUT%d_DUTY_CYCLE", i), Property("0.5")); set_default(ci, ctx->idf("CLKOUT%d_PHASE", i), Property(0)); set_default(ci, ctx->idf("CLKOUT%d_USE_FINE_PS", i), Property("FALSE")); } set_default(ci, id_COMPENSATION, Property("INTERNAL")); // Fixup routing if (str_or_default(ci->params, id_COMPENSATION, "INTERNAL") == "INTERNAL") { ci->disconnectPort(id_CLKFBIN); ci->connectPort(id_CLKFBIN, ctx->nets.at(ctx->id("$PACKER_VCC_NET")).get()); } } } } void XC7Packer::pack_gbs() { log_info("Packing global buffers...\n"); dict gb_rules; gb_rules[id_BUFGCTRL].new_type = id_BUFGCTRL; gb_rules[id_BUFGCTRL].new_type = id_BUFGCTRL; generic_xform(gb_rules); // Make sure prerequisites are set up first for (auto &cell : ctx->cells) { CellInfo *ci = cell.second.get(); if (ci->type == id_PS7_PS7) preplace_unique(ci); if (ci->type.in(id_PSEUDO_GND, id_PSEUDO_VCC)) preplace_unique(ci); } } void XC7Packer::preplace_clocking() { bool did_something = false; do { did_something = false; for (auto &cell : ctx->cells) { CellInfo *ci = cell.second.get(); if (ci->bel != BelId()) continue; if (ci->type == id_BUFGCTRL) did_something |= try_preplace(ci, id_I0); else if (ci->type == id_BUFG_BUFG) did_something |= try_preplace(ci, id_I); else if (ci->type.in(id_MMCM_MMCM_TOP, id_PLL_PLL_TOP, id_PLLE2_ADV_PLLE2_ADV, id_MMCME2_ADV_MMCME2_ADV)) did_something |= try_preplace(ci, id_CLKIN1); } } while (did_something); } void XC7Packer::pack_clocking() { pack_plls(); pack_gbs(); preplace_clocking(); } void XilinxImpl::route_clocks() { log_info("Routing global clocks...\n"); // Special pass for faster routing of global clock psuedo-net for (auto &net : ctx->nets) { NetInfo *clk_net = net.second.get(); if (!clk_net->driver.cell) continue; // check if we have a global clock net, skip otherwise bool is_global = false; if ((clk_net->driver.cell->type.in(id_BUFGCTRL, id_BUFCE_BUFG_PS, id_BUFCE_BUFCE, id_BUFGCE_DIV_BUFGCE_DIV)) && clk_net->driver.port == id_O) is_global = true; else if (clk_net->driver.cell->type.in(id_PLLE2_ADV_PLLE2_ADV, id_MMCME2_ADV_MMCME2_ADV) && clk_net->users.entries() == 1 && ((*clk_net->users.begin()).cell->type.in(id_BUFGCTRL, id_BUFCE_BUFCE, id_BUFGCE_DIV_BUFGCE_DIV))) is_global = true; else if (clk_net->users.entries() == 1 && (*clk_net->users.begin()).cell->type.in(id_PLLE2_ADV_PLLE2_ADV, id_MMCME2_ADV_MMCME2_ADV) && (*clk_net->users.begin()).port == id_CLKIN1) is_global = true; if (!is_global) continue; log_info(" routing clock '%s'\n", clk_net->name.c_str(ctx)); ctx->bindWire(ctx->getNetinfoSourceWire(clk_net), clk_net, STRENGTH_LOCKED); for (auto &usr : clk_net->users) { std::queue visit; dict backtrace; WireId dest = WireId(); auto sink_wire = ctx->getNetinfoSinkWire(clk_net, usr, 0); if (ctx->debug) { auto sink_wire_name = "(uninitialized)"; if (sink_wire != WireId()) sink_wire_name = ctx->nameOfWire(sink_wire); log_info(" routing arc to %s.%s (wire %s):\n", usr.cell->name.c_str(ctx), usr.port.c_str(ctx), sink_wire_name); } visit.push(sink_wire); while (!visit.empty()) { WireId curr = visit.front(); visit.pop(); if (ctx->getBoundWireNet(curr) == clk_net) { dest = curr; break; } for (auto uh : ctx->getPipsUphill(curr)) { if (!ctx->checkPipAvail(uh)) continue; WireId src = ctx->getPipSrcWire(uh); if (backtrace.count(src)) continue; IdString intent = ctx->getWireType(src); if (intent.in(id_NODE_DOUBLE, id_NODE_HLONG, id_NODE_HQUAD, id_NODE_VLONG, id_NODE_VQUAD, id_NODE_SINGLE, id_NODE_CLE_OUTPUT, id_NODE_OPTDELAY, id_BENTQUAD, id_DOUBLE, id_HLONG, id_HQUAD, id_OPTDELAY, id_SINGLE, id_VLONG, id_VLONG12, id_VQUAD, id_PINBOUNCE)) continue; if (!ctx->checkWireAvail(src) && ctx->getBoundWireNet(src) != clk_net) continue; backtrace[src] = uh; visit.push(src); } } if (dest == WireId()) { log_info(" failed to find a route using dedicated resources.\n"); if (clk_net->users.entries() == 1 && (*clk_net->users.begin()).cell->type == id_PLLE2_ADV_PLLE2_ADV && (*clk_net->users.begin()).port == id_CLKIN1) { // Due to some missing pips, currently special case more lenient solution std::queue empty; std::swap(visit, empty); backtrace.clear(); visit.push(sink_wire); while (!visit.empty()) { WireId curr = visit.front(); visit.pop(); if (ctx->getBoundWireNet(curr) == clk_net) { dest = curr; break; } for (auto uh : ctx->getPipsUphill(curr)) { if (!ctx->checkPipAvail(uh)) continue; WireId src = ctx->getPipSrcWire(uh); if (backtrace.count(src)) continue; if (!ctx->checkWireAvail(src) && ctx->getBoundWireNet(src) != clk_net) continue; backtrace[src] = uh; visit.push(src); } } if (dest == WireId()) continue; } else { continue; } } while (backtrace.count(dest)) { auto uh = backtrace[dest]; dest = ctx->getPipDstWire(uh); if (ctx->getBoundWireNet(dest) == clk_net) { NPNR_ASSERT(clk_net->wires.at(dest).pip == uh); break; } if (ctx->debug) log_info(" bind pip %s --> %s\n", ctx->nameOfPip(uh), ctx->nameOfWire(dest)); ctx->bindPip(uh, clk_net, STRENGTH_LOCKED); } } } #if 0 for (auto& net : nets) { NetInfo *ni = net.second.get(); for (auto &usr : ni->users) { if (usr.cell->type != id_BUFGCTRL || usr.port != id_I0) continue; WireId dst = getCtx()->getNetinfoSinkWire(ni, usr, 0); std::queue visit; visit.push(dst); int i = 0; while(!visit.empty() && i < 5000) { WireId curr = visit.front(); visit.pop(); log(" %s\n", nameOfWire(curr)); for (auto pip : getPipsUphill(curr)) { auto &pd = locInfo(pip).pip_data[pip.index]; log_info(" p %s sr %s (t %d s %d sv %d)\n", nameOfPip(pip), nameOfWire(getPipSrcWire(pip)), pd.flags, pd.site, pd.site_variant); if (!checkPipAvail(pip)) { log(" p unavail\n"); continue; } WireId src = getPipSrcWire(pip); if (!checkWireAvail(src)) { log(" w unavail (%s)\n", nameOf(getBoundWireNet(src))); continue; } log_info(" p %s s %s\n", nameOfPip(pip), nameOfWire(src)); visit.push(src); } ++i; } } } #endif } namespace { double float_or_default(CellInfo *ci, IdString p, double def) { if (!ci->params.count(p)) return def; auto &prop = ci->params.at(p); if (prop.is_string) return std::stod(prop.as_string()); else return prop.as_int64(); } } // namespace void XilinxPacker::generate_constraints() { log_info("Generating derived timing constraints...\n"); auto MHz = [&](delay_t a) { return 1000.0 / ctx->getDelayNS(a); }; auto equals_epsilon = [](delay_t a, delay_t b) { return (std::abs(a - b) / std::max(double(b), 1.0)) < 1e-3; }; auto equals_epsilon_pair = [&](DelayPair &a, DelayPair &b) { return equals_epsilon(a.min_delay, b.min_delay) && equals_epsilon(a.max_delay, b.max_delay); }; auto equals_epsilon_constr = [&](ClockConstraint &a, ClockConstraint &b) { return equals_epsilon_pair(a.high, b.high) && equals_epsilon_pair(a.low, b.low) && equals_epsilon_pair(a.period, b.period); }; pool user_constrained, changed_nets; for (auto &net : ctx->nets) { if (net.second->clkconstr != nullptr) user_constrained.insert(net.first); changed_nets.insert(net.first); } auto get_period = [&](CellInfo *ci, IdString port, delay_t &period) { if (!ci->ports.count(port)) return false; NetInfo *from = ci->ports.at(port).net; if (from == nullptr || from->clkconstr == nullptr) return false; period = from->clkconstr->period.minDelay(); return true; }; auto simple_clk_contraint = [&](delay_t period) { auto constr = std::unique_ptr(new ClockConstraint()); constr->low = DelayPair(period / 2); constr->high = DelayPair(period / 2); constr->period = DelayPair(period); return constr; }; auto set_constraint = [&](CellInfo *ci, IdString port, std::unique_ptr constr) { if (!ci->ports.count(port)) return; NetInfo *to = ci->ports.at(port).net; if (to == nullptr) return; if (to->clkconstr != nullptr) { if (!equals_epsilon_constr(*to->clkconstr, *constr) && user_constrained.count(to->name)) log_warning(" Overriding derived constraint of %.1f MHz on net %s with user-specified constraint of " "%.1f MHz.\n", MHz(to->clkconstr->period.min_delay), to->name.c_str(ctx), MHz(constr->period.min_delay)); return; } to->clkconstr = std::move(constr); log_info(" Derived frequency constraint of %.1f MHz for net %s\n", MHz(to->clkconstr->period.minDelay()), to->name.c_str(ctx)); changed_nets.insert(to->name); }; auto copy_constraint = [&](CellInfo *ci, IdString fromPort, IdString toPort, double ratio = 1.0) { if (!ci->ports.count(fromPort) || !ci->ports.count(toPort)) return; NetInfo *from = ci->ports.at(fromPort).net, *to = ci->ports.at(toPort).net; if (from == nullptr || from->clkconstr == nullptr || to == nullptr) return; if (to->clkconstr != nullptr) { if (!equals_epsilon(to->clkconstr->period.minDelay(), delay_t(from->clkconstr->period.minDelay() / ratio)) && user_constrained.count(to->name)) log_warning(" Overriding derived constraint of %.1f MHz on net %s with user-specified constraint of " "%.1f MHz.\n", MHz(to->clkconstr->period.minDelay()), to->name.c_str(ctx), MHz(delay_t(from->clkconstr->period.minDelay() / ratio))); return; } to->clkconstr = std::unique_ptr(new ClockConstraint()); to->clkconstr->low = DelayPair(ctx->getDelayFromNS(ctx->getDelayNS(from->clkconstr->low.min_delay) / ratio)); to->clkconstr->high = DelayPair(ctx->getDelayFromNS(ctx->getDelayNS(from->clkconstr->high.min_delay) / ratio)); to->clkconstr->period = DelayPair(ctx->getDelayFromNS(ctx->getDelayNS(from->clkconstr->period.min_delay) / ratio)); log_info(" Derived frequency constraint of %.1f MHz for net %s\n", MHz(to->clkconstr->period.minDelay()), to->name.c_str(ctx)); changed_nets.insert(to->name); }; // Run in a loop while constraints are changing to deal with dependencies // Iteration limit avoids hanging in crazy loopback situation (self-fed PLLs or dividers, etc) int iter = 0; const int itermax = 5000; while (!changed_nets.empty() && iter < itermax) { ++iter; pool changed_cells; for (auto net : changed_nets) { for (auto &user : ctx->nets.at(net)->users) if (user.port.in(id_CLKIN1, id_I0, id_PAD)) changed_cells.insert(user.cell->name); } changed_nets.clear(); for (auto cell : changed_cells) { CellInfo *ci = ctx->cells.at(cell).get(); if (ci->type == id_BUFGCTRL) { copy_constraint(ci, id_I0, id_O, 1); } else if (ci->type.in(id_IOB33M_INBUF_EN, id_IOB33S_INBUF_EN, id_IOB33_INBUF_EN, id_IOB18_INBUF_DCIEN, id_IOB18M_INBUF_DCIEN)) { copy_constraint(ci, id_PAD, id_OUT, 1); } else if (ci->type.in(id_MMCME2_ADV_MMCME2_ADV, id_PLLE2_ADV_PLLE2_ADV)) { delay_t period_in; if (!get_period(ci, id_CLKIN1, period_in)) continue; log_info(" Input frequency of PLL '%s' is constrained to %.1f MHz\n", ci->name.c_str(ctx), MHz(period_in)); double period_in_div = period_in * int_or_default(ci->params, id_DIVCLK_DIVIDE, 1); const NetInfo *clkfb = ci->getPort(id_CLKFBIN); if (!clkfb || clkfb->driver.cell != ci) continue; const std::string &clkfb_port = clkfb->driver.port.str(ctx); double feedback_div = 0; if (clkfb_port == "CLKFBOUT") { feedback_div = float_or_default( ci, ci->type == id_MMCME2_ADV_MMCME2_ADV ? id_CLKFBOUT_MULT_F : id_CLKFBOUT_MULT, 1); } else { if (clkfb_port.substr(0, 6) != "CLKOUT") continue; feedback_div = float_or_default( ci, ctx->idf("CLKOUT%s_DIVIDE%s", clkfb_port.substr(6).c_str(), (ci->type == id_MMCME2_ADV_MMCME2_ADV && clkfb_port.substr(6) == "0") ? "_F" : ""), 1); } double vco_period = period_in_div / feedback_div; double vco_freq = MHz(vco_period); log_info(" Derived VCO frequency %.1f MHz for PLL '%s'\n", vco_freq, ci->name.c_str(ctx)); for (int i = 0; i <= 6; i++) { auto port = ctx->idf("CLKOUT%d", i); if (!ci->getPort(port)) continue; set_constraint( ci, port, simple_clk_contraint( vco_period * float_or_default( ci, ctx->idf("CLKOUT%d_DIVIDE%s", i, (ci->type == id_MMCME2_ADV_MMCME2_ADV && i == 0) ? "_F" : ""), 1))); } } } } } NEXTPNR_NAMESPACE_END