diff --git a/common/command.cc b/common/command.cc index c5c777e8..5070bf9c 100644 --- a/common/command.cc +++ b/common/command.cc @@ -102,6 +102,7 @@ po::options_description CommandHandler::getGeneralOptions() #endif general.add_options()("json", po::value(), "JSON design file to ingest"); general.add_options()("seed", po::value(), "seed value for random number generator"); + general.add_options()("randomize-seed,r", "randomize seed value for random number generator"); general.add_options()("slack_redist_iter", po::value(), "number of iterations between slack redistribution"); general.add_options()("cstrweight", po::value(), "placer weighting for relative constraint satisfaction"); general.add_options()("pack-only", "pack design only without placement or routing"); @@ -138,6 +139,15 @@ void CommandHandler::setupContext(Context *ctx) ctx->rngseed(vm["seed"].as()); } + if (vm.count("randomize-seed")) { + srand(time(NULL)); + int r; + do { + r = rand(); + } while(r == 0); + ctx->rngseed(r); + } + if (vm.count("slack_redist_iter")) { ctx->slack_redist_iter = vm["slack_redist_iter"].as(); if (vm.count("freq") && vm["freq"].as() == 0) { diff --git a/common/nextpnr.cc b/common/nextpnr.cc index 903ab9e4..be3bfe14 100644 --- a/common/nextpnr.cc +++ b/common/nextpnr.cc @@ -53,6 +53,107 @@ void IdString::initialize_add(const BaseCtx *ctx, const char *s, int idx) ctx->idstring_idx_to_str->push_back(&insert_rc.first->first); } +TimingConstrObjectId BaseCtx::timingWildcardObject() +{ + TimingConstrObjectId id; + id.index = 0; + return id; +} + +TimingConstrObjectId BaseCtx::timingClockDomainObject(NetInfo *clockDomain) +{ + NPNR_ASSERT(clockDomain->clkconstr != nullptr); + if (clockDomain->clkconstr->domain_tmg_id != TimingConstrObjectId()) { + return clockDomain->clkconstr->domain_tmg_id; + } else { + TimingConstraintObject obj; + TimingConstrObjectId id; + id.index = int(constraintObjects.size()); + obj.id = id; + obj.type = TimingConstraintObject::CLOCK_DOMAIN; + obj.entity = clockDomain->name; + clockDomain->clkconstr->domain_tmg_id = id; + constraintObjects.push_back(obj); + return id; + } +} + +TimingConstrObjectId BaseCtx::timingNetObject(NetInfo *net) +{ + if (net->tmg_id != TimingConstrObjectId()) { + return net->tmg_id; + } else { + TimingConstraintObject obj; + TimingConstrObjectId id; + id.index = int(constraintObjects.size()); + obj.id = id; + obj.type = TimingConstraintObject::NET; + obj.entity = net->name; + constraintObjects.push_back(obj); + net->tmg_id = id; + return id; + } +} + +TimingConstrObjectId BaseCtx::timingCellObject(CellInfo *cell) +{ + if (cell->tmg_id != TimingConstrObjectId()) { + return cell->tmg_id; + } else { + TimingConstraintObject obj; + TimingConstrObjectId id; + id.index = int(constraintObjects.size()); + obj.id = id; + obj.type = TimingConstraintObject::CELL; + obj.entity = cell->name; + constraintObjects.push_back(obj); + cell->tmg_id = id; + return id; + } +} + +TimingConstrObjectId BaseCtx::timingPortObject(CellInfo *cell, IdString port) +{ + if (cell->ports.at(port).tmg_id != TimingConstrObjectId()) { + return cell->ports.at(port).tmg_id; + } else { + TimingConstraintObject obj; + TimingConstrObjectId id; + id.index = int(constraintObjects.size()); + obj.id = id; + obj.type = TimingConstraintObject::CELL_PORT; + obj.entity = cell->name; + obj.port = port; + constraintObjects.push_back(obj); + cell->ports.at(port).tmg_id = id; + return id; + } +} + +void BaseCtx::addConstraint(std::unique_ptr constr) +{ + for (auto fromObj : constr->from) + constrsFrom.emplace(fromObj, constr.get()); + for (auto toObj : constr->to) + constrsTo.emplace(toObj, constr.get()); + IdString name = constr->name; + constraints[name] = std::move(constr); +} + +void BaseCtx::removeConstraint(IdString constrName) +{ + TimingConstraint *constr = constraints[constrName].get(); + for (auto fromObj : constr->from) { + auto fromConstrs = constrsFrom.equal_range(fromObj); + constrsFrom.erase(std::find(fromConstrs.first, fromConstrs.second, std::make_pair(fromObj, constr))); + } + for (auto toObj : constr->to) { + auto toConstrs = constrsFrom.equal_range(toObj); + constrsFrom.erase(std::find(toConstrs.first, toConstrs.second, std::make_pair(toObj, constr))); + } + constraints.erase(constrName); +} + const char *BaseCtx::nameOfBel(BelId bel) const { const Context *ctx = getCtx(); @@ -306,4 +407,14 @@ void Context::check() const } } +void BaseCtx::addClock(IdString net, float freq) +{ + log_info("constraining clock net '%s' to %.02f MHz\n", net.c_str(this), freq); + std::unique_ptr cc(new ClockConstraint()); + cc->period = getCtx()->getDelayFromNS(1000 / freq); + cc->high = getCtx()->getDelayFromNS(500 / freq); + cc->low = getCtx()->getDelayFromNS(500 / freq); + nets.at(net)->clkconstr = std::move(cc); +} + NEXTPNR_NAMESPACE_END diff --git a/common/nextpnr.h b/common/nextpnr.h index 4434c438..d58ae529 100644 --- a/common/nextpnr.h +++ b/common/nextpnr.h @@ -194,6 +194,14 @@ struct Loc bool operator!=(const Loc &other) const { return (x != other.x) || (y != other.y) || (z != other.z); } }; +struct TimingConstrObjectId +{ + int32_t index = -1; + + bool operator==(const TimingConstrObjectId &other) const { return index == other.index; } + bool operator!=(const TimingConstrObjectId &other) const { return index != other.index; } +}; + NEXTPNR_NAMESPACE_END namespace std { @@ -208,6 +216,15 @@ template <> struct hash return seed; } }; + +template <> struct hash +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX TimingConstrObjectId &obj) const noexcept + { + return hash()(obj.index); + } +}; + } // namespace std #include "archdefs.h" @@ -266,6 +283,8 @@ struct PipMap PlaceStrength strength = STRENGTH_NONE; }; +struct ClockConstraint; + struct NetInfo : ArchNetInfo { IdString name; @@ -278,6 +297,10 @@ struct NetInfo : ArchNetInfo // wire -> uphill_pip std::unordered_map wires; + std::unique_ptr clkconstr; + + TimingConstrObjectId tmg_id; + Region *region = nullptr; }; @@ -293,6 +316,7 @@ struct PortInfo IdString name; NetInfo *net; PortType type; + TimingConstrObjectId tmg_id; }; struct CellInfo : ArchCellInfo @@ -320,6 +344,7 @@ struct CellInfo : ArchCellInfo // parent.[xyz] := 0 when (constr_parent == nullptr) Region *region = nullptr; + TimingConstrObjectId tmg_id; }; enum TimingPortClass @@ -335,6 +360,68 @@ enum TimingPortClass TMG_IGNORE, // Asynchronous to all clocks, "don't care", and should be ignored (false path) for analysis }; +enum ClockEdge +{ + RISING_EDGE, + FALLING_EDGE +}; + +struct TimingClockingInfo +{ + IdString clock_port; // Port name of clock domain + ClockEdge edge; + DelayInfo setup, hold; // Input timing checks + DelayInfo clockToQ; // Output clock-to-Q time +}; + +struct ClockConstraint +{ + DelayInfo high; + DelayInfo low; + DelayInfo period; + + TimingConstrObjectId domain_tmg_id; +}; + +struct TimingConstraintObject +{ + TimingConstrObjectId id; + enum + { + ANYTHING, + CLOCK_DOMAIN, + NET, + CELL, + CELL_PORT + } type; + IdString entity; // Name of clock net; net or cell + IdString port; // Name of port on a cell +}; + +struct TimingConstraint +{ + IdString name; + + enum + { + FALSE_PATH, + MIN_DELAY, + MAX_DELAY, + MULTICYCLE, + } type; + + delay_t value; + + std::unordered_set from; + std::unordered_set to; +}; + +inline bool operator==(const std::pair &a, + const std::pair &b) +{ + return a.first == b.first && a.second == b.second; +} + struct DeterministicRNG { uint64_t rngstate; @@ -431,6 +518,11 @@ struct BaseCtx idstring_idx_to_str = new std::vector; IdString::initialize_add(this, "", 0); IdString::initialize_arch(this); + + TimingConstraintObject wildcard; + wildcard.id.index = 0; + wildcard.type = TimingConstraintObject::ANYTHING; + constraintObjects.push_back(wildcard); } ~BaseCtx() @@ -487,10 +579,7 @@ struct BaseCtx const Context *getCtx() const { return reinterpret_cast(this); } - const char *nameOf(IdString name) const - { - return name.c_str(this); - } + const char *nameOf(IdString name) const { return name.c_str(this); } template const char *nameOf(const T *obj) const { @@ -524,6 +613,30 @@ struct BaseCtx void refreshUiPip(PipId pip) { pipUiReload.insert(pip); } void refreshUiGroup(GroupId group) { groupUiReload.insert(group); } + + // -------------------------------------------------------------- + + // Timing Constraint API + + // constraint name -> constraint + std::unordered_map> constraints; + // object ID -> object + std::vector constraintObjects; + // object ID -> constraint + std::unordered_multimap constrsFrom; + std::unordered_multimap constrsTo; + + TimingConstrObjectId timingWildcardObject(); + TimingConstrObjectId timingClockDomainObject(NetInfo *clockDomain); + TimingConstrObjectId timingNetObject(NetInfo *net); + TimingConstrObjectId timingCellObject(CellInfo *cell); + TimingConstrObjectId timingPortObject(CellInfo *cell, IdString port); + + void addConstraint(std::unique_ptr constr); + void removeConstraint(IdString constrName); + + // Intended to simplify Python API + void addClock(IdString net, float freq); }; NEXTPNR_NAMESPACE_END diff --git a/common/place_common.cc b/common/place_common.cc index 4cb5ae11..b3eb4267 100644 --- a/common/place_common.cc +++ b/common/place_common.cc @@ -36,8 +36,9 @@ wirelen_t get_net_metric(const Context *ctx, const NetInfo *net, MetricType type bool driver_gb = ctx->getBelGlobalBuf(driver_cell->bel); if (driver_gb) return 0; - IdString clock_port; - bool timing_driven = ctx->timing_driven && type == MetricType::COST && ctx->getPortTimingClass(driver_cell, net->driver.port, clock_port) != TMG_IGNORE; + int clock_count; + bool timing_driven = ctx->timing_driven && type == MetricType::COST && + ctx->getPortTimingClass(driver_cell, net->driver.port, clock_count) != TMG_IGNORE; delay_t negative_slack = 0; delay_t worst_slack = std::numeric_limits::max(); Loc driver_loc = ctx->getBelLocation(driver_cell->bel); diff --git a/common/placer1.cc b/common/placer1.cc index 0fd9a227..0db7ce00 100644 --- a/common/placer1.cc +++ b/common/placer1.cc @@ -50,16 +50,21 @@ class SAPlacer SAPlacer(Context *ctx, Placer1Cfg cfg) : ctx(ctx), cfg(cfg) { int num_bel_types = 0; + for (auto bel : ctx->getBels()) { + IdString type = ctx->getBelType(bel); + if (bel_types.find(type) == bel_types.end()) { + bel_types[type] = std::tuple(num_bel_types++, 1); + } else { + std::get<1>(bel_types.at(type))++; + } + } for (auto bel : ctx->getBels()) { Loc loc = ctx->getBelLocation(bel); IdString type = ctx->getBelType(bel); - int type_idx; - if (bel_types.find(type) == bel_types.end()) { - type_idx = num_bel_types++; - bel_types[type] = type_idx; - } else { - type_idx = bel_types.at(type); - } + int type_idx = std::get<0>(bel_types.at(type)); + int type_cnt = std::get<1>(bel_types.at(type)); + if (type_cnt < cfg.minBelsForGridPick) + loc.x = loc.y = 0; if (int(fast_bels.size()) < type_idx + 1) fast_bels.resize(type_idx + 1); if (int(fast_bels.at(type_idx).size()) < (loc.x + 1)) @@ -463,7 +468,10 @@ class SAPlacer while (true) { int nx = ctx->rng(2 * diameter + 1) + std::max(curr_loc.x - diameter, 0); int ny = ctx->rng(2 * diameter + 1) + std::max(curr_loc.y - diameter, 0); - int beltype_idx = bel_types.at(targetType); + int beltype_idx, beltype_cnt; + std::tie(beltype_idx, beltype_cnt) = bel_types.at(targetType); + if (beltype_cnt < cfg.minBelsForGridPick) + nx = ny = 0; if (nx >= int(fast_bels.at(beltype_idx).size())) continue; if (ny >= int(fast_bels.at(beltype_idx).at(nx).size())) @@ -485,7 +493,7 @@ class SAPlacer bool improved = false; int n_move, n_accept; int diameter = 35, max_x = 1, max_y = 1; - std::unordered_map bel_types; + std::unordered_map> bel_types; std::vector>>> fast_bels; std::unordered_set locked_bels; bool require_legal = true; @@ -503,7 +511,11 @@ class SAPlacer std::vector old_udata; }; -Placer1Cfg::Placer1Cfg(Context *ctx) : Settings(ctx) { constraintWeight = get("placer1/constraintWeight", 10); } +Placer1Cfg::Placer1Cfg(Context *ctx) : Settings(ctx) +{ + constraintWeight = get("placer1/constraintWeight", 10); + minBelsForGridPick = get("placer1/minBelsForGridPick", 64); +} bool placer1(Context *ctx, Placer1Cfg cfg) { diff --git a/common/placer1.h b/common/placer1.h index 55db1fa5..7305f4b1 100644 --- a/common/placer1.h +++ b/common/placer1.h @@ -28,6 +28,7 @@ struct Placer1Cfg : public Settings { Placer1Cfg(Context *ctx); float constraintWeight; + int minBelsForGridPick; }; extern bool placer1(Context *ctx, Placer1Cfg cfg); diff --git a/common/router1.cc b/common/router1.cc index adad37e9..a3388fa8 100644 --- a/common/router1.cc +++ b/common/router1.cc @@ -34,7 +34,10 @@ struct arc_key int user_idx; bool operator==(const arc_key &other) const { return (net_info == other.net_info) && (user_idx == other.user_idx); } - bool operator<(const arc_key &other) const { return net_info == other.net_info ? user_idx < other.user_idx : net_info->name < other.net_info->name; } + bool operator<(const arc_key &other) const + { + return net_info == other.net_info ? user_idx < other.user_idx : net_info->name < other.net_info->name; + } struct Hash { @@ -375,21 +378,20 @@ struct Router1 if (dst_wire == WireId()) log_error("No wire found for port %s on destination cell %s.\n", - ctx->nameOf(net_info->users[user_idx].port), - ctx->nameOf(net_info->users[user_idx].cell)); + ctx->nameOf(net_info->users[user_idx].port), ctx->nameOf(net_info->users[user_idx].cell)); if (dst_to_arc.count(dst_wire)) { if (dst_to_arc.at(dst_wire).net_info == net_info) continue; - log_error("Found two arcs with same sink wire %s: %s (%d) vs %s (%d)\n", - ctx->nameOfWire(dst_wire), ctx->nameOf(net_info), user_idx, - ctx->nameOf(dst_to_arc.at(dst_wire).net_info), dst_to_arc.at(dst_wire).user_idx); + log_error("Found two arcs with same sink wire %s: %s (%d) vs %s (%d)\n", ctx->nameOfWire(dst_wire), + ctx->nameOf(net_info), user_idx, ctx->nameOf(dst_to_arc.at(dst_wire).net_info), + dst_to_arc.at(dst_wire).user_idx); } if (src_to_net.count(dst_wire)) log_error("Wire %s is used as source and sink in different nets: %s vs %s (%d)\n", - ctx->nameOfWire(dst_wire), ctx->nameOf(src_to_net.at(dst_wire)), - ctx->nameOf(net_info), user_idx); + ctx->nameOfWire(dst_wire), ctx->nameOf(src_to_net.at(dst_wire)), ctx->nameOf(net_info), + user_idx); arc_key arc; arc.net_info = net_info; @@ -775,6 +777,7 @@ bool router1(Context *ctx, const Router1Cfg &cfg) router.arcs_without_ripup - last_arcs_without_ripup, int(router.arc_queue.size())); last_arcs_with_ripup = router.arcs_with_ripup; last_arcs_without_ripup = router.arcs_without_ripup; + ctx->yield(); #ifndef NDEBUG router.check(); #endif @@ -800,6 +803,7 @@ bool router1(Context *ctx, const Router1Cfg &cfg) router.arcs_with_ripup - last_arcs_with_ripup, router.arcs_without_ripup - last_arcs_without_ripup, int(router.arc_queue.size())); log_info("Routing complete.\n"); + ctx->yield(); #ifndef NDEBUG router.check(); @@ -808,7 +812,7 @@ bool router1(Context *ctx, const Router1Cfg &cfg) #endif log_info("Checksum: 0x%08x\n", ctx->checksum()); - timing_analysis(ctx, true /* slack_histogram */, true /* print_path */); + timing_analysis(ctx, true /* slack_histogram */, true /* print_fmax */, true /* print_path */); ctx->unlock(); return true; @@ -950,8 +954,7 @@ bool Context::checkRoutedDesign() const for (WireId w : dangling_wires) { if (logged_wires.count(w) == 0) - log(" loop: %s -> %s\n", - ctx->nameOfWire(ctx->getPipSrcWire(net_info->wires.at(w).pip)), + log(" loop: %s -> %s\n", ctx->nameOfWire(ctx->getPipSrcWire(net_info->wires.at(w).pip)), ctx->nameOfWire(w)); } } diff --git a/common/timing.cc b/common/timing.cc index 7e76c647..80be554c 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include "log.h" @@ -29,17 +30,72 @@ NEXTPNR_NAMESPACE_BEGIN +namespace { +struct ClockEvent +{ + IdString clock; + ClockEdge edge; + + bool operator==(const ClockEvent &other) const { return clock == other.clock && edge == other.edge; } +}; + +struct ClockPair +{ + ClockEvent start, end; + + bool operator==(const ClockPair &other) const { return start == other.start && end == other.end; } +}; +} // namespace + +NEXTPNR_NAMESPACE_END +namespace std { + +template <> struct hash +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX ClockEvent &obj) const noexcept + { + std::size_t seed = 0; + boost::hash_combine(seed, hash()(obj.clock)); + boost::hash_combine(seed, hash()(int(obj.edge))); + return seed; + } +}; + +template <> struct hash +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX ClockPair &obj) const noexcept + { + std::size_t seed = 0; + boost::hash_combine(seed, hash()(obj.start)); + boost::hash_combine(seed, hash()(obj.start)); + return seed; + } +}; + +} // namespace std +NEXTPNR_NAMESPACE_BEGIN + typedef std::vector PortRefVector; typedef std::map DelayFrequency; +struct CriticalPath +{ + PortRefVector ports; + delay_t path_delay; + delay_t path_period; +}; + +typedef std::unordered_map CriticalPathMap; + struct Timing { Context *ctx; bool net_delays; bool update; delay_t min_slack; - PortRefVector *crit_path; + CriticalPathMap *crit_path; DelayFrequency *slack_histogram; + IdString async_clock; struct TimingData { @@ -49,23 +105,24 @@ struct Timing unsigned max_path_length = 0; delay_t min_remaining_budget; bool false_startpoint = false; + std::unordered_map arrival_time; }; - Timing(Context *ctx, bool net_delays, bool update, PortRefVector *crit_path = nullptr, + Timing(Context *ctx, bool net_delays, bool update, CriticalPathMap *crit_path = nullptr, DelayFrequency *slack_histogram = nullptr) : ctx(ctx), net_delays(net_delays), update(update), min_slack(1.0e12 / ctx->target_freq), - crit_path(crit_path), slack_histogram(slack_histogram) + crit_path(crit_path), slack_histogram(slack_histogram), async_clock(ctx->id("$async$")) { } delay_t walk_paths() { - const auto clk_period = delay_t(1.0e12 / ctx->target_freq); + const auto clk_period = ctx->getDelayFromNS(1.0e9 / ctx->target_freq).maxDelay(); // First, compute the topographical order of nets to walk through the circuit, assuming it is a _acyclic_ graph // TODO(eddieh): Handle the case where it is cyclic, e.g. combinatorial loops std::vector topographical_order; - std::unordered_map net_data; + std::unordered_map> net_data; // In lieu of deleting edges from the graph, simply count the number of fanins to each output port std::unordered_map port_fanin; @@ -84,22 +141,34 @@ struct Timing } for (auto o : output_ports) { - IdString clockPort; - TimingPortClass portClass = ctx->getPortTimingClass(cell.second.get(), o->name, clockPort); + int clocks = 0; + TimingPortClass portClass = ctx->getPortTimingClass(cell.second.get(), o->name, clocks); // If output port is influenced by a clock (e.g. FF output) then add it to the ordering as a timing // start-point if (portClass == TMG_REGISTER_OUTPUT) { - DelayInfo clkToQ; - ctx->getCellDelay(cell.second.get(), clockPort, o->name, clkToQ); topographical_order.emplace_back(o->net); - net_data.emplace(o->net, TimingData{clkToQ.maxDelay()}); + for (int i = 0; i < clocks; i++) { + TimingClockingInfo clkInfo = ctx->getPortClockingInfo(cell.second.get(), o->name, i); + const NetInfo *clknet = get_net_or_empty(cell.second.get(), clkInfo.clock_port); + IdString clksig = clknet ? clknet->name : async_clock; + net_data[o->net][ClockEvent{clksig, clknet ? clkInfo.edge : RISING_EDGE}] = + TimingData{clkInfo.clockToQ.maxDelay()}; + } + } else { if (portClass == TMG_STARTPOINT || portClass == TMG_GEN_CLOCK || portClass == TMG_IGNORE) { topographical_order.emplace_back(o->net); TimingData td; td.false_startpoint = (portClass == TMG_GEN_CLOCK || portClass == TMG_IGNORE); - net_data.emplace(o->net, std::move(td)); + td.max_arrival = 0; + net_data[o->net][ClockEvent{async_clock, RISING_EDGE}] = td; } + + // Don't analyse paths from a clock input to other pins - they will be considered by the + // special-case handling register input/output class ports + if (portClass == TMG_CLOCK_INPUT) + continue; + // Otherwise, for all driven input ports on this cell, if a timing arc exists between the input and // the current output port, increment fanin counter for (auto i : input_ports) { @@ -120,14 +189,15 @@ struct Timing queue.pop_front(); for (auto &usr : net->users) { - IdString clockPort; - TimingPortClass usrClass = ctx->getPortTimingClass(usr.cell, usr.port, clockPort); + int user_clocks; + TimingPortClass usrClass = ctx->getPortTimingClass(usr.cell, usr.port, user_clocks); if (usrClass == TMG_IGNORE || usrClass == TMG_CLOCK_INPUT) continue; for (auto &port : usr.cell->ports) { if (port.second.type != PORT_OUT || !port.second.net) continue; - TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, port.first, clockPort); + int port_clocks; + TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, port.first, port_clocks); // Skip if this is a clocked output (but allow non-clocked ones) if (portClass == TMG_REGISTER_OUTPUT || portClass == TMG_STARTPOINT || portClass == TMG_IGNORE || @@ -167,97 +237,166 @@ struct Timing } } if (ctx->force) - log_warning("timing analysis failed due to presence of combinatorial loops, incomplete specification of timing ports, etc.\n"); + log_warning("timing analysis failed due to presence of combinatorial loops, incomplete specification " + "of timing ports, etc.\n"); else - log_error("timing analysis failed due to presence of combinatorial loops, incomplete specification of timing ports, etc.\n"); + log_error("timing analysis failed due to presence of combinatorial loops, incomplete specification of " + "timing ports, etc.\n"); } // Go forwards topographically to find the maximum arrival time and max path length for each net for (auto net : topographical_order) { - auto &nd = net_data.at(net); - const auto net_arrival = nd.max_arrival; - const auto net_length_plus_one = nd.max_path_length + 1; - nd.min_remaining_budget = clk_period; - for (auto &usr : net->users) { - IdString clockPort; - TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, clockPort); - if (portClass == TMG_REGISTER_INPUT || portClass == TMG_ENDPOINT || portClass == TMG_IGNORE) { - } else { + if (!net_data.count(net)) + continue; + auto &nd_map = net_data.at(net); + for (auto &startdomain : nd_map) { + ClockEvent start_clk = startdomain.first; + auto &nd = startdomain.second; + if (nd.false_startpoint) + continue; + const auto net_arrival = nd.max_arrival; + const auto net_length_plus_one = nd.max_path_length + 1; + nd.min_remaining_budget = clk_period; + for (auto &usr : net->users) { + int port_clocks; + TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, port_clocks); auto net_delay = net_delays ? ctx->getNetinfoRouteDelay(net, usr) : delay_t(); - auto budget_override = ctx->getBudgetOverride(net, usr, net_delay); auto usr_arrival = net_arrival + net_delay; - // Iterate over all output ports on the same cell as the sink - for (auto port : usr.cell->ports) { - if (port.second.type != PORT_OUT || !port.second.net) - continue; - DelayInfo comb_delay; - // Look up delay through this path - bool is_path = ctx->getCellDelay(usr.cell, usr.port, port.first, comb_delay); - if (!is_path) - continue; - auto &data = net_data[port.second.net]; - auto &arrival = data.max_arrival; - arrival = std::max(arrival, usr_arrival + comb_delay.maxDelay()); - if (!budget_override) { // Do not increment path length if budget overriden since it doesn't - // require a share of the slack - auto &path_length = data.max_path_length; - path_length = std::max(path_length, net_length_plus_one); + + if (portClass == TMG_REGISTER_INPUT || portClass == TMG_ENDPOINT || portClass == TMG_IGNORE || + portClass == TMG_CLOCK_INPUT) { + // Skip + } else { + auto budget_override = ctx->getBudgetOverride(net, usr, net_delay); + // Iterate over all output ports on the same cell as the sink + for (auto port : usr.cell->ports) { + if (port.second.type != PORT_OUT || !port.second.net) + continue; + DelayInfo comb_delay; + // Look up delay through this path + bool is_path = ctx->getCellDelay(usr.cell, usr.port, port.first, comb_delay); + if (!is_path) + continue; + auto &data = net_data[port.second.net][start_clk]; + auto &arrival = data.max_arrival; + arrival = std::max(arrival, usr_arrival + comb_delay.maxDelay()); + if (!budget_override) { // Do not increment path length if budget overriden since it doesn't + // require a share of the slack + auto &path_length = data.max_path_length; + path_length = std::max(path_length, net_length_plus_one); + } } } } } } - const NetInfo *crit_net = nullptr; + std::unordered_map> crit_nets; // Now go backwards topographically to determine the minimum path slack, and to distribute all path slack evenly // between all nets on the path for (auto net : boost::adaptors::reverse(topographical_order)) { - auto &nd = net_data.at(net); - // Ignore false startpoints - if (nd.false_startpoint) + if (!net_data.count(net)) continue; - const delay_t net_length_plus_one = nd.max_path_length + 1; - auto &net_min_remaining_budget = nd.min_remaining_budget; - for (auto &usr : net->users) { - auto net_delay = net_delays ? ctx->getNetinfoRouteDelay(net, usr) : delay_t(); - auto budget_override = ctx->getBudgetOverride(net, usr, net_delay); - IdString associatedClock; - TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, associatedClock); - if (portClass == TMG_REGISTER_INPUT || portClass == TMG_ENDPOINT) { - const auto net_arrival = nd.max_arrival; - auto path_budget = clk_period - (net_arrival + net_delay); - if (update) { - auto budget_share = budget_override ? 0 : path_budget / net_length_plus_one; - usr.budget = std::min(usr.budget, net_delay + budget_share); - net_min_remaining_budget = std::min(net_min_remaining_budget, path_budget - budget_share); - } + auto &nd_map = net_data.at(net); + for (auto &startdomain : nd_map) { + auto &nd = startdomain.second; + // Ignore false startpoints + if (nd.false_startpoint) + continue; + const delay_t net_length_plus_one = nd.max_path_length + 1; + auto &net_min_remaining_budget = nd.min_remaining_budget; + for (auto &usr : net->users) { + auto net_delay = net_delays ? ctx->getNetinfoRouteDelay(net, usr) : delay_t(); + auto budget_override = ctx->getBudgetOverride(net, usr, net_delay); + int port_clocks; + TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, port_clocks); + if (portClass == TMG_REGISTER_INPUT || portClass == TMG_ENDPOINT) { + auto process_endpoint = [&](IdString clksig, ClockEdge edge, delay_t setup) { + const auto net_arrival = nd.max_arrival; + const auto endpoint_arrival = net_arrival + net_delay + setup; + delay_t period; + // Set default period + if (edge == startdomain.first.edge) { + period = clk_period; + } else { + period = clk_period / 2; + } + if (clksig != async_clock) { + if (ctx->nets.at(clksig)->clkconstr) { + if (edge == startdomain.first.edge) { + // same edge + period = ctx->nets.at(clksig)->clkconstr->period.minDelay(); + } else if (edge == RISING_EDGE) { + // falling -> rising + period = ctx->nets.at(clksig)->clkconstr->low.minDelay(); + } else if (edge == FALLING_EDGE) { + // rising -> falling + period = ctx->nets.at(clksig)->clkconstr->high.minDelay(); + } + } + } + auto path_budget = period - endpoint_arrival; - if (path_budget < min_slack) { - min_slack = path_budget; - if (crit_path) { - crit_path->clear(); - crit_path->push_back(&usr); - crit_net = net; + if (update) { + auto budget_share = budget_override ? 0 : path_budget / net_length_plus_one; + usr.budget = std::min(usr.budget, net_delay + budget_share); + net_min_remaining_budget = + std::min(net_min_remaining_budget, path_budget - budget_share); + } + + if (path_budget < min_slack) + min_slack = path_budget; + + if (slack_histogram) { + int slack_ps = ctx->getDelayNS(path_budget) * 1000; + (*slack_histogram)[slack_ps]++; + } + ClockEvent dest_ev{clksig, edge}; + ClockPair clockPair{startdomain.first, dest_ev}; + nd.arrival_time[dest_ev] = std::max(nd.arrival_time[dest_ev], endpoint_arrival); + + if (crit_path) { + if (!crit_nets.count(clockPair) || crit_nets.at(clockPair).first < endpoint_arrival) { + crit_nets[clockPair] = std::make_pair(endpoint_arrival, net); + (*crit_path)[clockPair].path_delay = endpoint_arrival; + (*crit_path)[clockPair].path_period = period; + (*crit_path)[clockPair].ports.clear(); + (*crit_path)[clockPair].ports.push_back(&usr); + } + } + }; + if (portClass == TMG_REGISTER_INPUT) { + for (int i = 0; i < port_clocks; i++) { + TimingClockingInfo clkInfo = ctx->getPortClockingInfo(usr.cell, usr.port, i); + const NetInfo *clknet = get_net_or_empty(usr.cell, clkInfo.clock_port); + IdString clksig = clknet ? clknet->name : async_clock; + process_endpoint(clksig, clknet ? clkInfo.edge : RISING_EDGE, clkInfo.setup.maxDelay()); + } + } else { + process_endpoint(async_clock, RISING_EDGE, 0); + } + + } else if (update) { + + // Iterate over all output ports on the same cell as the sink + for (const auto &port : usr.cell->ports) { + if (port.second.type != PORT_OUT || !port.second.net) + continue; + DelayInfo comb_delay; + bool is_path = ctx->getCellDelay(usr.cell, usr.port, port.first, comb_delay); + if (!is_path) + continue; + if (net_data.count(port.second.net) && + net_data.at(port.second.net).count(startdomain.first)) { + auto path_budget = + net_data.at(port.second.net).at(startdomain.first).min_remaining_budget; + auto budget_share = budget_override ? 0 : path_budget / net_length_plus_one; + usr.budget = std::min(usr.budget, net_delay + budget_share); + net_min_remaining_budget = + std::min(net_min_remaining_budget, path_budget - budget_share); + } } - } - if (slack_histogram) { - int slack_ps = ctx->getDelayNS(path_budget) * 1000; - (*slack_histogram)[slack_ps]++; - } - } else if (update) { - // Iterate over all output ports on the same cell as the sink - for (const auto &port : usr.cell->ports) { - if (port.second.type != PORT_OUT || !port.second.net) - continue; - DelayInfo comb_delay; - bool is_path = ctx->getCellDelay(usr.cell, usr.port, port.first, comb_delay); - if (!is_path) - continue; - auto path_budget = net_data.at(port.second.net).min_remaining_budget; - auto budget_share = budget_override ? 0 : path_budget / net_length_plus_one; - usr.budget = std::min(usr.budget, net_delay + budget_share); - net_min_remaining_budget = std::min(net_min_remaining_budget, path_budget - budget_share); } } } @@ -265,47 +404,55 @@ struct Timing if (crit_path) { // Walk backwards from the most critical net - while (crit_net) { - const PortInfo *crit_ipin = nullptr; - delay_t max_arrival = std::numeric_limits::min(); + for (auto crit_pair : crit_nets) { + NetInfo *crit_net = crit_pair.second.second; + auto &cp_ports = (*crit_path)[crit_pair.first].ports; + while (crit_net) { + const PortInfo *crit_ipin = nullptr; + delay_t max_arrival = std::numeric_limits::min(); - // Look at all input ports on its driving cell - for (const auto &port : crit_net->driver.cell->ports) { - if (port.second.type != PORT_IN || !port.second.net) - continue; - DelayInfo comb_delay; - bool is_path = - ctx->getCellDelay(crit_net->driver.cell, port.first, crit_net->driver.port, comb_delay); - if (!is_path) - continue; - // If input port is influenced by a clock, skip - IdString portClock; - TimingPortClass portClass = ctx->getPortTimingClass(crit_net->driver.cell, port.first, portClock); - if (portClass == TMG_REGISTER_INPUT || portClass == TMG_CLOCK_INPUT || portClass == TMG_ENDPOINT || - portClass == TMG_IGNORE) - continue; + // Look at all input ports on its driving cell + for (const auto &port : crit_net->driver.cell->ports) { + if (port.second.type != PORT_IN || !port.second.net) + continue; + DelayInfo comb_delay; + bool is_path = + ctx->getCellDelay(crit_net->driver.cell, port.first, crit_net->driver.port, comb_delay); + if (!is_path) + continue; + // If input port is influenced by a clock, skip + int port_clocks; + TimingPortClass portClass = + ctx->getPortTimingClass(crit_net->driver.cell, port.first, port_clocks); + if (portClass == TMG_REGISTER_INPUT || portClass == TMG_CLOCK_INPUT || + portClass == TMG_ENDPOINT || portClass == TMG_IGNORE) + continue; - // And find the fanin net with the latest arrival time - const auto net_arrival = net_data.at(port.second.net).max_arrival; - if (net_arrival > max_arrival) { - max_arrival = net_arrival; - crit_ipin = &port.second; + // And find the fanin net with the latest arrival time + if (net_data.count(port.second.net) && + net_data.at(port.second.net).count(crit_pair.first.start)) { + const auto net_arrival = net_data.at(port.second.net).at(crit_pair.first.start).max_arrival; + if (net_arrival > max_arrival) { + max_arrival = net_arrival; + crit_ipin = &port.second; + } + } } - } - if (!crit_ipin) - break; - - // Now convert PortInfo* into a PortRef* - for (auto &usr : crit_ipin->net->users) { - if (usr.cell->name == crit_net->driver.cell->name && usr.port == crit_ipin->name) { - crit_path->push_back(&usr); + if (!crit_ipin) break; + + // Now convert PortInfo* into a PortRef* + for (auto &usr : crit_ipin->net->users) { + if (usr.cell->name == crit_net->driver.cell->name && usr.port == crit_ipin->name) { + cp_ports.push_back(&usr); + break; + } } + crit_net = crit_ipin->net; } - crit_net = crit_ipin->net; + std::reverse(cp_ports.begin(), cp_ports.end()); } - std::reverse(crit_path->begin(), crit_path->end()); } return min_slack; } @@ -366,30 +513,106 @@ void assign_budget(Context *ctx, bool quiet) log_info("Checksum: 0x%08x\n", ctx->checksum()); } -void timing_analysis(Context *ctx, bool print_histogram, bool print_path) +void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool print_path) { - PortRefVector crit_path; + auto format_event = [ctx](const ClockEvent &e, int field_width = 0) { + std::string value; + if (e.clock == ctx->id("$async$")) + value = std::string(""); + else + value = (e.edge == FALLING_EDGE ? std::string("negedge ") : std::string("posedge ")) + e.clock.str(ctx); + if (int(value.length()) < field_width) + value.insert(value.length(), field_width - int(value.length()), ' '); + return value; + }; + + CriticalPathMap crit_paths; DelayFrequency slack_histogram; - Timing timing(ctx, true /* net_delays */, false /* update */, print_path ? &crit_path : nullptr, + Timing timing(ctx, true /* net_delays */, false /* update */, (print_path || print_fmax) ? &crit_paths : nullptr, print_histogram ? &slack_histogram : nullptr); - auto min_slack = timing.walk_paths(); + timing.walk_paths(); + std::map> clock_reports; + std::map clock_fmax; + std::vector xclock_paths; + std::set empty_clocks; // set of clocks with no interior paths + if (print_path || print_fmax) { + for (auto path : crit_paths) { + const ClockEvent &a = path.first.start; + const ClockEvent &b = path.first.end; + empty_clocks.insert(a.clock); + empty_clocks.insert(b.clock); + } + for (auto path : crit_paths) { + const ClockEvent &a = path.first.start; + const ClockEvent &b = path.first.end; + if (a.clock != b.clock || a.clock == ctx->id("$async$")) + continue; + double Fmax; + empty_clocks.erase(a.clock); + if (a.edge == b.edge) + Fmax = 1000 / ctx->getDelayNS(path.second.path_delay); + else + Fmax = 500 / ctx->getDelayNS(path.second.path_delay); + if (!clock_fmax.count(a.clock) || Fmax < clock_fmax.at(a.clock)) { + clock_reports[a.clock] = path; + clock_fmax[a.clock] = Fmax; + } + } + + for (auto &path : crit_paths) { + const ClockEvent &a = path.first.start; + const ClockEvent &b = path.first.end; + if (a.clock == b.clock && a.clock != ctx->id("$async$")) + continue; + xclock_paths.push_back(path.first); + } + + if (clock_reports.empty()) { + log_warning("No clocks found in design"); + } + + std::sort(xclock_paths.begin(), xclock_paths.end(), [ctx](const ClockPair &a, const ClockPair &b) { + if (a.start.clock.str(ctx) < b.start.clock.str(ctx)) + return true; + if (a.start.clock.str(ctx) > b.start.clock.str(ctx)) + return false; + if (a.start.edge < b.start.edge) + return true; + if (a.start.edge > b.start.edge) + return false; + if (a.end.clock.str(ctx) < b.end.clock.str(ctx)) + return true; + if (a.end.clock.str(ctx) > b.end.clock.str(ctx)) + return false; + if (a.end.edge < b.end.edge) + return true; + return false; + }); + } if (print_path) { - if (crit_path.empty()) { - log_info("Design contains no timing paths\n"); - } else { + auto print_path_report = [ctx](ClockPair &clocks, PortRefVector &crit_path) { delay_t total = 0; - log_break(); - log_info("Critical path report:\n"); - log_info("curr total\n"); - auto &front = crit_path.front(); auto &front_port = front->cell->ports.at(front->port); auto &front_driver = front_port.net->driver; - IdString last_port; - ctx->getPortTimingClass(front_driver.cell, front_driver.port, last_port); + int port_clocks; + auto portClass = ctx->getPortTimingClass(front_driver.cell, front_driver.port, port_clocks); + IdString last_port = front_driver.port; + if (portClass == TMG_REGISTER_OUTPUT) { + for (int i = 0; i < port_clocks; i++) { + TimingClockingInfo clockInfo = ctx->getPortClockingInfo(front_driver.cell, front_driver.port, i); + const NetInfo *clknet = get_net_or_empty(front_driver.cell, clockInfo.clock_port); + if (clknet != nullptr && clknet->name == clocks.start.clock && + clockInfo.edge == clocks.start.edge) { + last_port = clockInfo.clock_port; + } + } + } + + log_info("curr total\n"); for (auto sink : crit_path) { auto sink_cell = sink->cell; auto &port = sink_cell->ports.at(sink->port); @@ -397,7 +620,12 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_path) auto &driver = net->driver; auto driver_cell = driver.cell; DelayInfo comb_delay; - ctx->getCellDelay(sink_cell, last_port, driver.port, comb_delay); + if (last_port == driver.port) { + // Case where we start with a STARTPOINT etc + comb_delay = ctx->getDelayFromNS(0); + } else { + ctx->getCellDelay(sink_cell, last_port, driver.port, comb_delay); + } total += comb_delay.maxDelay(); log_info("%4.1f %4.1f Source %s.%s\n", ctx->getDelayNS(comb_delay.maxDelay()), ctx->getDelayNS(total), driver_cell->name.c_str(ctx), driver.port.c_str(ctx)); @@ -413,7 +641,8 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_path) auto driver_wire = ctx->getNetinfoSourceWire(net); auto sink_wire = ctx->getNetinfoSinkWire(net, *sink); log_info(" prediction: %f ns estimate: %f ns\n", - ctx->getDelayNS(ctx->predictDelay(net, *sink)), ctx->getDelayNS(ctx->estimateDelay(driver_wire, sink_wire))); + ctx->getDelayNS(ctx->predictDelay(net, *sink)), + ctx->getDelayNS(ctx->estimateDelay(driver_wire, sink_wire))); auto cursor = sink_wire; delay_t delay; while (driver_wire != cursor) { @@ -422,18 +651,75 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_path) auto pip = it->second.pip; NPNR_ASSERT(pip != PipId()); delay = ctx->getPipDelay(pip).maxDelay(); - log_info(" %1.3f %s\n", ctx->getDelayNS(delay), ctx->getPipName(pip).c_str(ctx)); + log_info(" %1.3f %s\n", ctx->getDelayNS(delay), + ctx->getPipName(pip).c_str(ctx)); cursor = ctx->getPipSrcWire(pip); } } last_port = sink->port; } + }; + + for (auto &clock : clock_reports) { log_break(); + std::string start = + clock.second.first.start.edge == FALLING_EDGE ? std::string("negedge") : std::string("posedge"); + std::string end = + clock.second.first.end.edge == FALLING_EDGE ? std::string("negedge") : std::string("posedge"); + log_info("Critical path report for clock '%s' (%s -> %s):\n", clock.first.c_str(ctx), start.c_str(), + end.c_str()); + auto &crit_path = clock.second.second.ports; + print_path_report(clock.second.first, crit_path); + } + + for (auto &xclock : xclock_paths) { + log_break(); + std::string start = format_event(xclock.start); + std::string end = format_event(xclock.end); + log_info("Critical path report for cross-domain path '%s' -> '%s':\n", start.c_str(), end.c_str()); + auto &crit_path = crit_paths.at(xclock).ports; + print_path_report(xclock, crit_path); } } + if (print_fmax) { + log_break(); + unsigned max_width = 0; + for (auto &clock : clock_reports) + max_width = std::max(max_width, clock.first.str(ctx).size()); + for (auto &clock : clock_reports) { + const auto &clock_name = clock.first.str(ctx); + const int width = max_width - clock_name.size(); + if (ctx->nets.at(clock.first)->clkconstr) { + float target = 1000 / ctx->getDelayNS(ctx->nets.at(clock.first)->clkconstr->period.minDelay()); + log_info("Max frequency for clock %*s'%s': %.02f MHz (%s at %.02f MHz)\n", width, "", + clock_name.c_str(), clock_fmax[clock.first], + (target < clock_fmax[clock.first]) ? "PASS" : "FAIL", target); + } else { + log_info("Max frequency for clock %*s'%s': %.02f MHz\n", width, "", clock_name.c_str(), + clock_fmax[clock.first]); + } + } + for (auto &eclock : empty_clocks) { + if (eclock != ctx->id("$async$")) + log_info("Clock '%s' has no interior paths\n", eclock.c_str(ctx)); + } + log_break(); - delay_t default_slack = delay_t((1.0e9 / ctx->getDelayNS(1)) / ctx->target_freq); - log_info("estimated Fmax = %.2f MHz\n", 1e3 / ctx->getDelayNS(default_slack - min_slack)); + int start_field_width = 0, end_field_width = 0; + for (auto &xclock : xclock_paths) { + start_field_width = std::max((int)format_event(xclock.start).length(), start_field_width); + end_field_width = std::max((int)format_event(xclock.end).length(), end_field_width); + } + + for (auto &xclock : xclock_paths) { + const ClockEvent &a = xclock.start; + const ClockEvent &b = xclock.end; + auto &path = crit_paths.at(xclock); + auto ev_a = format_event(a, start_field_width), ev_b = format_event(b, end_field_width); + log_info("Max delay %s -> %s: %0.02f ns\n", ev_a.c_str(), ev_b.c_str(), ctx->getDelayNS(path.path_delay)); + } + log_break(); + } if (print_histogram && slack_histogram.size() > 0) { unsigned num_bins = 20; diff --git a/common/timing.h b/common/timing.h index cfb71ae0..1fd76310 100644 --- a/common/timing.h +++ b/common/timing.h @@ -29,7 +29,7 @@ void assign_budget(Context *ctx, bool quiet = false); // Perform timing analysis and print out the fmax, and optionally the // critical path -void timing_analysis(Context *ctx, bool slack_histogram = true, bool print_path = false); +void timing_analysis(Context *ctx, bool slack_histogram = true, bool print_fmax = true, bool print_path = false); NEXTPNR_NAMESPACE_END diff --git a/docs/archapi.md b/docs/archapi.md index 40eabd9d..3c938865 100644 --- a/docs/archapi.md +++ b/docs/archapi.md @@ -404,6 +404,10 @@ actual penalty used is a multiple of this value (i.e. a weighted version of this Convert an `delay_t` to an actual real-world delay in nanoseconds. +### DelayInfo getDelayFromNS(float v) const + +Convert a real-world delay in nanoseconds to a DelayInfo with equal min/max rising/falling values. + ### uint32\_t getDelayChecksum(delay\_t v) const Convert a `delay_t` to an integer for checksum calculations. @@ -461,11 +465,17 @@ Cell Delay Methods Returns the delay for the specified path through a cell in the `&delay` argument. The method returns false if there is no timing relationship from `fromPort` to `toPort`. -### TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockPort) const +### TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const Return the _timing port class_ of a port. This can be a register or combinational input or output; clock input or -output; general startpoint or endpoint; or a port ignored for timing purposes. For register ports, clockPort is set -to the associated clock port. +output; general startpoint or endpoint; or a port ignored for timing purposes. For register ports, clockInfoCount is set +to the number of associated _clock edges_ that can be queried by getPortClockingInfo. + +### TimingClockingInfo getPortClockingInfo(const CellInfo *cell, IdString port, int index) const + +Return the _clocking info_ (including port name of clock, clock polarity and setup/hold/clock-to-out times) of a +port. Where ports have more than one clock edge associated with them (such as DDR outputs), `index` can be used to obtain +information for all edges. `index` must be in [0, clockInfoCount), behaviour is undefined otherwise. Placer Methods -------------- diff --git a/docs/constraints.md b/docs/constraints.md new file mode 100644 index 00000000..263df7b6 --- /dev/null +++ b/docs/constraints.md @@ -0,0 +1,37 @@ +# Constraints + +There are three types of constraints available for end users of nextpnr. + +## Architecture-specific IO Cconstraints + +Architectures may provide support for their native (or any other) IO constraint format. +The iCE40 architecture supports PCF constraints thus: + + set_io led[0] 3 + +and the ECP5 architecture supports a subset of LPF constraints: + + LOCATE COMP "led[0]" SITE "E16"; + IOBUF PORT "led[0]" IO_TYPE=LVCMOS25; + + +## Absolute Placement Constraints + +nextpnr provides generic support for placement constraints by setting the Bel attribute on the cell to the name of +the Bel you wish it to be placed at. For example: + + (* BEL="X2/Y5/lc0" *) + +## Clock Constraints + +There are two ways to apply clock constraints in nextpnr. The `--clock {freq}` command line argument is used to +apply a default frequency (in MHz) to all clocks without a more specific constraint. + +The Python API can apply clock constraints to specific named clocks. This is done by passing a Python file +specifying these constraints to the `--pre-pack` command line argument. Inside the file, constraints are applied by +calling the function `ctx.addClock` with the name of the clock and its frequency in MHz, for example: + + ctx.addClock("csi_rx_i.dphy_clk", 96) + ctx.addClock("video_clk", 24) + ctx.addClock("uart_i.sys_clk_i", 12) + diff --git a/ecp5/arch.cc b/ecp5/arch.cc index 9001ce6c..afea8d4a 100644 --- a/ecp5/arch.cc +++ b/ecp5/arch.cc @@ -539,10 +539,10 @@ bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort return true; } #if 0 // FIXME - if (fromPort == id_WCK && (toPort == id_F0 || toPort == id_F1)) { - delay.delay = 717; - return true; - } + if (fromPort == id_WCK && (toPort == id_F0 || toPort == id_F1)) { + delay.delay = 717; + return true; + } #endif if ((fromPort == id_A0 && toPort == id_WADO3) || (fromPort == id_A1 && toPort == id_WDO1) || (fromPort == id_B0 && toPort == id_WADO1) || (fromPort == id_B1 && toPort == id_WDO3) || @@ -576,10 +576,10 @@ bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort } } -TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockPort) const +TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const { auto disconnected = [cell](IdString p) { return !cell->ports.count(p) || cell->ports.at(p).net == nullptr; }; - + clockInfoCount = 0; if (cell->type == id_TRELLIS_SLICE) { int sd0 = int_or_default(cell->params, id("REG0_SD"), 0), sd1 = int_or_default(cell->params, id("REG1_SD"), 0); if (port == id_CLK || port == id_WCK) @@ -598,13 +598,13 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id return TMG_COMB_OUTPUT; if (port == id_DI0 || port == id_DI1 || port == id_CE || port == id_LSR || (sd0 == 1 && port == id_M0) || (sd1 == 1 && port == id_M1)) { - clockPort = id_CLK; + clockInfoCount = 1; return TMG_REGISTER_INPUT; } if (port == id_M0 || port == id_M1) return TMG_COMB_INPUT; if (port == id_Q0 || port == id_Q1) { - clockPort = id_CLK; + clockInfoCount = 1; return TMG_REGISTER_OUTPUT; } @@ -614,7 +614,7 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id if (port == id_WD0 || port == id_WD1 || port == id_WAD0 || port == id_WAD1 || port == id_WAD2 || port == id_WAD3 || port == id_WRE) { - clockPort = id_WCK; + clockInfoCount = 1; return TMG_REGISTER_INPUT; } @@ -638,10 +638,8 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id for (auto c : boost::adaptors::reverse(port_name)) { if (std::isdigit(c)) continue; - if (c == 'A') - clockPort = id_CLKA; - else if (c == 'B') - clockPort = id_CLKB; + if (c == 'A' || c == 'B') + clockInfoCount = 1; else NPNR_ASSERT_FALSE_STR("bad ram port"); return (cell->ports.at(port).type == PORT_OUT) ? TMG_REGISTER_OUTPUT : TMG_REGISTER_INPUT; @@ -653,11 +651,92 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id return TMG_IGNORE; // FIXME } else if (cell->type == id_EHXPLLL) { return TMG_IGNORE; + } else if (cell->type == id_DCUA || cell->type == id_EXTREFB || cell->type == id_PCSCLKDIV) { + if (port == id_CH0_FF_TXI_CLK || port == id_CH0_FF_RXI_CLK || port == id_CH1_FF_TXI_CLK || + port == id_CH1_FF_RXI_CLK) + return TMG_CLOCK_INPUT; + std::string prefix = port.str(this).substr(0, 9); + if (prefix == "CH0_FF_TX" || prefix == "CH0_FF_RX" || prefix == "CH1_FF_TX" || prefix == "CH1_FF_RX") { + clockInfoCount = 1; + return (cell->ports.at(port).type == PORT_OUT) ? TMG_REGISTER_OUTPUT : TMG_REGISTER_INPUT; + } + return TMG_IGNORE; } else { NPNR_ASSERT_FALSE_STR("no timing data for cell type '" + cell->type.str(this) + "'"); } } +TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port, int index) const +{ + TimingClockingInfo info; + info.setup.delay = 0; + info.hold.delay = 0; + info.clockToQ.delay = 0; + if (cell->type == id_TRELLIS_SLICE) { + int sd0 = int_or_default(cell->params, id("REG0_SD"), 0), sd1 = int_or_default(cell->params, id("REG1_SD"), 0); + + if (port == id_WD0 || port == id_WD1 || port == id_WAD0 || port == id_WAD1 || port == id_WAD2 || + port == id_WAD3 || port == id_WRE) { + info.edge = RISING_EDGE; + info.clock_port = id_WCK; + info.setup.delay = 100; + info.hold.delay = 0; + } else if (port == id_DI0 || port == id_DI1 || port == id_CE || port == id_LSR || (sd0 == 1 && port == id_M0) || + (sd1 == 1 && port == id_M1)) { + info.edge = cell->sliceInfo.clkmux == id("INV") ? FALLING_EDGE : RISING_EDGE; + info.clock_port = id_CLK; + info.setup.delay = 100; + info.hold.delay = 0; + } else { + info.edge = cell->sliceInfo.clkmux == id("INV") ? FALLING_EDGE : RISING_EDGE; + info.clock_port = id_CLK; + info.clockToQ.delay = 395; + } + } else if (cell->type == id_DP16KD) { + std::string port_name = port.str(this); + for (auto c : boost::adaptors::reverse(port_name)) { + if (std::isdigit(c)) + continue; + if (c == 'A') { + info.clock_port = id_CLKA; + break; + } else if (c == 'B') { + info.clock_port = id_CLKB; + break; + } else + NPNR_ASSERT_FALSE_STR("bad ram port " + port.str(this)); + } + info.edge = (str_or_default(cell->params, info.clock_port == id_CLKB ? id("CLKBMUX") : id("CLKAMUX"), "CLK") == + "INV") + ? FALLING_EDGE + : RISING_EDGE; + if (cell->ports.at(port).type == PORT_OUT) { + info.clockToQ.delay = 4280; + } else { + info.setup.delay = 100; + info.hold.delay = 0; + } + } else if (cell->type == id_DCUA) { + std::string prefix = port.str(this).substr(0, 9); + info.edge = RISING_EDGE; + if (prefix == "CH0_FF_TX") + info.clock_port = id_CH0_FF_TXI_CLK; + else if (prefix == "CH0_FF_RX") + info.clock_port = id_CH0_FF_RXI_CLK; + else if (prefix == "CH1_FF_TX") + info.clock_port = id_CH1_FF_TXI_CLK; + else if (prefix == "CH1_FF_RX") + info.clock_port = id_CH1_FF_RXI_CLK; + if (cell->ports.at(port).type == PORT_OUT) { + info.clockToQ.delay = 660; + } else { + info.setup.delay = 1000; + info.hold.delay = 0; + } + } + return info; +} + std::vector> Arch::getTilesAtLocation(int row, int col) { std::vector> ret; diff --git a/ecp5/arch.h b/ecp5/arch.h index 9daae11d..aa3c5348 100644 --- a/ecp5/arch.h +++ b/ecp5/arch.h @@ -859,6 +859,12 @@ struct Arch : BaseCtx delay_t getDelayEpsilon() const { return 20; } delay_t getRipupDelayPenalty() const { return 200; } float getDelayNS(delay_t v) const { return v * 0.001; } + DelayInfo getDelayFromNS(float ns) const + { + DelayInfo del; + del.delay = delay_t(ns * 1000); + return del; + } uint32_t getDelayChecksum(delay_t v) const { return v; } bool getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const; @@ -882,8 +888,10 @@ struct Arch : BaseCtx // Get the delay through a cell from one port to another, returning false // if no path exists bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const; - // Get the port class, also setting clockPort if applicable - TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockPort) const; + // Get the port class, also setting clockInfoCount to the number of TimingClockingInfos associated with a port + TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const; + // Get the TimingClockingInfo of a port + TimingClockingInfo getPortClockingInfo(const CellInfo *cell, IdString port, int index) const; // Return true if a port is a net bool isGlobalNet(const NetInfo *net) const; diff --git a/ecp5/arch_place.cc b/ecp5/arch_place.cc index 6fcd8bde..41f87cb8 100644 --- a/ecp5/arch_place.cc +++ b/ecp5/arch_place.cc @@ -101,6 +101,8 @@ bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const bel_cells.push_back(cell); return slicesCompatible(bel_cells); + } else if (cell->type == id_DCUA || cell->type == id_EXTREFB || cell->type == id_PCSCLKDIV) { + return args.type != ArchArgs::LFE5U_25F && args.type != ArchArgs::LFE5U_45F && args.type != ArchArgs::LFE5U_85F; } else { // other checks return true; diff --git a/ecp5/arch_pybindings.cc b/ecp5/arch_pybindings.cc index 9312b4ad..5e73a673 100644 --- a/ecp5/arch_pybindings.cc +++ b/ecp5/arch_pybindings.cc @@ -130,6 +130,10 @@ void arch_wrap_python() "cells"); readonly_wrapper>::def_wrap(ctx_cls, "nets"); + + fn_wrapper_2a_v, + pass_through>::def_wrap(ctx_cls, "addClock"); + WRAP_RANGE(Bel, conv_to_str); WRAP_RANGE(Wire, conv_to_str); WRAP_RANGE(AllPip, conv_to_str); diff --git a/ecp5/archdefs.h b/ecp5/archdefs.h index 01cbad46..b2c23134 100644 --- a/ecp5/archdefs.h +++ b/ecp5/archdefs.h @@ -87,7 +87,10 @@ struct BelId bool operator==(const BelId &other) const { return index == other.index && location == other.location; } bool operator!=(const BelId &other) const { return index != other.index || location != other.location; } - bool operator<(const BelId &other) const { return location == other.location ? index < other.index : location < other.location; } + bool operator<(const BelId &other) const + { + return location == other.location ? index < other.index : location < other.location; + } }; struct WireId @@ -97,7 +100,10 @@ struct WireId bool operator==(const WireId &other) const { return index == other.index && location == other.location; } bool operator!=(const WireId &other) const { return index != other.index || location != other.location; } - bool operator<(const WireId &other) const { return location == other.location ? index < other.index : location < other.location; } + bool operator<(const WireId &other) const + { + return location == other.location ? index < other.index : location < other.location; + } }; struct PipId @@ -107,7 +113,10 @@ struct PipId bool operator==(const PipId &other) const { return index == other.index && location == other.location; } bool operator!=(const PipId &other) const { return index != other.index || location != other.location; } - bool operator<(const PipId &other) const { return location == other.location ? index < other.index : location < other.location; } + bool operator<(const PipId &other) const + { + return location == other.location ? index < other.index : location < other.location; + } }; struct GroupId diff --git a/ecp5/bitstream.cc b/ecp5/bitstream.cc index 6d43b369..4de2a0a6 100644 --- a/ecp5/bitstream.cc +++ b/ecp5/bitstream.cc @@ -19,15 +19,15 @@ #include "bitstream.h" +#include #include #include #include #include #include - #include "config.h" -#include "io.h" #include "log.h" +#include "pio.h" #include "util.h" #define fmt_str(x) (static_cast(std::ostringstream() << x).str()) @@ -379,6 +379,16 @@ std::vector get_dsp_tiles(Context *ctx, BelId bel) return tiles; } +// Get the list of tiles corresponding to a DCU +std::vector get_dcu_tiles(Context *ctx, BelId bel) +{ + std::vector tiles; + Loc loc = ctx->getBelLocation(bel); + for (int i = 0; i < 9; i++) + tiles.push_back(ctx->getTileByTypeAndLocation(loc.y, loc.x + i, "DCU" + std::to_string(i))); + return tiles; +} + // Get the list of tiles corresponding to a PLL std::vector get_pll_tiles(Context *ctx, BelId bel) { @@ -408,26 +418,23 @@ std::vector get_pll_tiles(Context *ctx, BelId bel) void fix_tile_names(Context *ctx, ChipConfig &cc) { // Remove the V prefix/suffix on certain tiles if device is a SERDES variant - if (ctx->args.type == ArchArgs::LFE5UM_25F || ctx->args.type == ArchArgs::LFE5UM_45F || - ctx->args.type == ArchArgs::LFE5UM_85F || ctx->args.type == ArchArgs::LFE5UM5G_25F || - ctx->args.type == ArchArgs::LFE5UM5G_45F || ctx->args.type == ArchArgs::LFE5UM5G_85F) { + if (ctx->args.type == ArchArgs::LFE5U_25F || ctx->args.type == ArchArgs::LFE5U_45F || + ctx->args.type == ArchArgs::LFE5U_85F) { std::map tiletype_xform; for (const auto &tile : cc.tiles) { std::string newname = tile.first; - auto vcib = tile.first.find("VCIB"); - if (vcib != std::string::npos) { - // Remove the V - newname.erase(vcib, 1); + auto cibdcu = tile.first.find("CIB_DCU"); + if (cibdcu != std::string::npos) { + // Add the V + if (newname.at(cibdcu - 1) != 'V') + newname.insert(cibdcu, 1, 'V'); + tiletype_xform[tile.first] = newname; + } else if (boost::ends_with(tile.first, "BMID_0H")) { + newname.back() = 'V'; + tiletype_xform[tile.first] = newname; + } else if (boost::ends_with(tile.first, "BMID_2")) { + newname.push_back('V'); tiletype_xform[tile.first] = newname; - } else if (tile.first.back() == 'V') { - // BMID_0V or BMID_2V - if (tile.first.at(tile.first.size() - 2) == '0') { - newname.at(tile.first.size() - 1) = 'H'; - tiletype_xform[tile.first] = newname; - } else if (tile.first.at(tile.first.size() - 2) == '2') { - newname.pop_back(); - tiletype_xform[tile.first] = newname; - } } } // Apply the name changes @@ -456,6 +463,20 @@ void tieoff_dsp_ports(Context *ctx, ChipConfig &cc, CellInfo *ci) } } +void tieoff_dcu_ports(Context *ctx, ChipConfig &cc, CellInfo *ci) +{ + for (auto port : ci->ports) { + if (port.second.net == nullptr && port.second.type == PORT_IN) { + if (port.first.str(ctx).find("CLK") != std::string::npos || + port.first.str(ctx).find("HDIN") != std::string::npos || + port.first.str(ctx).find("HDOUT") != std::string::npos) + continue; + bool value = bool_or_default(ci->params, ctx->id(port.first.str(ctx) + "MUX"), false); + tie_cib_signal(ctx, cc, ctx->getBelPinWire(ci->bel, port.first), value); + } + } +} + static void set_pip(Context *ctx, ChipConfig &cc, PipId pip) { std::string tile = ctx->getPipTilename(pip); @@ -464,6 +485,46 @@ static void set_pip(Context *ctx, ChipConfig &cc, PipId pip) cc.tiles[tile].add_arc(sink, source); } +static std::vector parse_config_str(std::string str, int length) +{ + // For DCU config which might be bin, hex or dec using prefices accordingly + std::string base = str.substr(0, 2); + std::vector word; + word.resize(length, false); + if (base == "0b") { + for (int i = 0; i < int(str.length()) - 2; i++) { + char c = str.at((str.size() - 1) - i); + NPNR_ASSERT(c == '0' || c == '1'); + word.at(i) = (c == '1'); + } + } else if (base == "0x") { + for (int i = 0; i < int(str.length()) - 2; i++) { + char c = str.at((str.size() - i) - 1); + int nibble = chtohex(c); + word.at(i * 4) = nibble & 0x1; + if (i * 4 + 1 < length) + word.at(i * 4 + 1) = nibble & 0x2; + if (i * 4 + 2 < length) + word.at(i * 4 + 2) = nibble & 0x4; + if (i * 4 + 3 < length) + word.at(i * 4 + 3) = nibble & 0x8; + } + } else if (base == "0d") { + NPNR_ASSERT(length < 64); + unsigned long long value = std::stoull(str.substr(2)); + for (int i = 0; i < length; i++) + if (value & (1 << i)) + word.at(i) = true; + } else { + NPNR_ASSERT(length < 64); + unsigned long long value = std::stoull(str); + for (int i = 0; i < length; i++) + if (value & (1 << i)) + word.at(i) = true; + } + return word; +} + void write_bitstream(Context *ctx, std::string base_config_file, std::string text_config_file) { ChipConfig cc; @@ -481,6 +542,23 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex // TODO: .bit metadata } + // Clear out DCU tieoffs in base config if DCU used + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (ci->type == id_DCUA) { + Loc loc = ctx->getBelLocation(ci->bel); + for (int i = 0; i < 12; i++) { + auto tiles = ctx->getTilesAtLocation(loc.y - 1, loc.x + i); + for (const auto &tile : tiles) { + auto cc_tile = cc.tiles.find(tile.first); + if (cc_tile != cc.tiles.end()) { + cc_tile->second.cenums.clear(); + cc_tile->second.cunknowns.clear(); + } + } + } + } + } // Add all set, configurable pips to the config for (auto pip : ctx->getPips()) { if (ctx->getBoundPipNet(pip) != nullptr) { @@ -1000,6 +1078,28 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex int_to_bitvector(int_or_default(ci->attrs, ctx->id("MFG_ENABLE_FILTEROPAMP"), 0), 1)); cc.tilegroups.push_back(tg); + } else if (ci->type == id_DCUA) { + TileGroup tg; + tg.tiles = get_dcu_tiles(ctx, ci->bel); + tg.config.add_enum("DCU.MODE", "DCUA"); +#include "dcu_bitstream.h" + cc.tilegroups.push_back(tg); + tieoff_dcu_ports(ctx, cc, ci); + } else if (ci->type == id_EXTREFB) { + TileGroup tg; + tg.tiles = get_dcu_tiles(ctx, ci->bel); + tg.config.add_word("EXTREF.REFCK_DCBIAS_EN", + parse_config_str(str_or_default(ci->params, ctx->id("REFCK_DCBIAS_EN"), "0"), 1)); + tg.config.add_word("EXTREF.REFCK_RTERM", + parse_config_str(str_or_default(ci->params, ctx->id("REFCK_RTERM"), "0"), 1)); + tg.config.add_word("EXTREF.REFCK_PWDNB", + parse_config_str(str_or_default(ci->params, ctx->id("REFCK_PWDNB"), "0"), 1)); + cc.tilegroups.push_back(tg); + } else if (ci->type == id_PCSCLKDIV) { + Loc loc = ctx->getBelLocation(ci->bel); + std::string tname = ctx->getTileByTypeAndLocation(loc.y + 1, loc.x, "BMID_0H"); + cc.tiles[tname].add_enum("PCSCLKDIV" + std::to_string(loc.z), + str_or_default(ci->params, ctx->id("GSR"), "ENABLED")); } else { NPNR_ASSERT_FALSE("unsupported cell type"); } diff --git a/ecp5/constids.inc b/ecp5/constids.inc index bdcbc1ea..11ecc240 100644 --- a/ecp5/constids.inc +++ b/ecp5/constids.inc @@ -810,3 +810,310 @@ X(LOCK) X(INTLOCK) X(REFCLK) X(CLKINTFB) + + +X(EXTREFB) +X(REFCLKP) +X(REFCLKN) +X(REFCLKO) + +X(DCUA) +X(CH0_HDINP) +X(CH1_HDINP) +X(CH0_HDINN) +X(CH1_HDINN) +X(D_TXBIT_CLKP_FROM_ND) +X(D_TXBIT_CLKN_FROM_ND) +X(D_SYNC_ND) +X(D_TXPLL_LOL_FROM_ND) +X(CH0_RX_REFCLK) +X(CH1_RX_REFCLK) +X(CH0_FF_RXI_CLK) +X(CH1_FF_RXI_CLK) +X(CH0_FF_TXI_CLK) +X(CH1_FF_TXI_CLK) +X(CH0_FF_EBRD_CLK) +X(CH1_FF_EBRD_CLK) +X(CH0_FF_TX_D_0) +X(CH1_FF_TX_D_0) +X(CH0_FF_TX_D_1) +X(CH1_FF_TX_D_1) +X(CH0_FF_TX_D_2) +X(CH1_FF_TX_D_2) +X(CH0_FF_TX_D_3) +X(CH1_FF_TX_D_3) +X(CH0_FF_TX_D_4) +X(CH1_FF_TX_D_4) +X(CH0_FF_TX_D_5) +X(CH1_FF_TX_D_5) +X(CH0_FF_TX_D_6) +X(CH1_FF_TX_D_6) +X(CH0_FF_TX_D_7) +X(CH1_FF_TX_D_7) +X(CH0_FF_TX_D_8) +X(CH1_FF_TX_D_8) +X(CH0_FF_TX_D_9) +X(CH1_FF_TX_D_9) +X(CH0_FF_TX_D_10) +X(CH1_FF_TX_D_10) +X(CH0_FF_TX_D_11) +X(CH1_FF_TX_D_11) +X(CH0_FF_TX_D_12) +X(CH1_FF_TX_D_12) +X(CH0_FF_TX_D_13) +X(CH1_FF_TX_D_13) +X(CH0_FF_TX_D_14) +X(CH1_FF_TX_D_14) +X(CH0_FF_TX_D_15) +X(CH1_FF_TX_D_15) +X(CH0_FF_TX_D_16) +X(CH1_FF_TX_D_16) +X(CH0_FF_TX_D_17) +X(CH1_FF_TX_D_17) +X(CH0_FF_TX_D_18) +X(CH1_FF_TX_D_18) +X(CH0_FF_TX_D_19) +X(CH1_FF_TX_D_19) +X(CH0_FF_TX_D_20) +X(CH1_FF_TX_D_20) +X(CH0_FF_TX_D_21) +X(CH1_FF_TX_D_21) +X(CH0_FF_TX_D_22) +X(CH1_FF_TX_D_22) +X(CH0_FF_TX_D_23) +X(CH1_FF_TX_D_23) +X(CH0_FFC_EI_EN) +X(CH1_FFC_EI_EN) +X(CH0_FFC_PCIE_DET_EN) +X(CH1_FFC_PCIE_DET_EN) +X(CH0_FFC_PCIE_CT) +X(CH1_FFC_PCIE_CT) +X(CH0_FFC_SB_INV_RX) +X(CH1_FFC_SB_INV_RX) +X(CH0_FFC_ENABLE_CGALIGN) +X(CH1_FFC_ENABLE_CGALIGN) +X(CH0_FFC_SIGNAL_DETECT) +X(CH1_FFC_SIGNAL_DETECT) +X(CH0_FFC_FB_LOOPBACK) +X(CH1_FFC_FB_LOOPBACK) +X(CH0_FFC_SB_PFIFO_LP) +X(CH1_FFC_SB_PFIFO_LP) +X(CH0_FFC_PFIFO_CLR) +X(CH1_FFC_PFIFO_CLR) +X(CH0_FFC_RATE_MODE_RX) +X(CH1_FFC_RATE_MODE_RX) +X(CH0_FFC_RATE_MODE_TX) +X(CH1_FFC_RATE_MODE_TX) +X(CH0_FFC_DIV11_MODE_RX) +X(CH1_FFC_DIV11_MODE_RX) +X(CH0_FFC_RX_GEAR_MODE) +X(CH1_FFC_RX_GEAR_MODE) +X(CH0_FFC_TX_GEAR_MODE) +X(CH1_FFC_TX_GEAR_MODE) +X(CH0_FFC_DIV11_MODE_TX) +X(CH1_FFC_DIV11_MODE_TX) +X(CH0_FFC_LDR_CORE2TX_EN) +X(CH1_FFC_LDR_CORE2TX_EN) +X(CH0_FFC_LANE_TX_RST) +X(CH1_FFC_LANE_TX_RST) +X(CH0_FFC_LANE_RX_RST) +X(CH1_FFC_LANE_RX_RST) +X(CH0_FFC_RRST) +X(CH1_FFC_RRST) +X(CH0_FFC_TXPWDNB) +X(CH1_FFC_TXPWDNB) +X(CH0_FFC_RXPWDNB) +X(CH1_FFC_RXPWDNB) +X(CH0_LDR_CORE2TX) +X(CH1_LDR_CORE2TX) +X(D_SCIWDATA0) +X(D_SCIWDATA1) +X(D_SCIWDATA2) +X(D_SCIWDATA3) +X(D_SCIWDATA4) +X(D_SCIWDATA5) +X(D_SCIWDATA6) +X(D_SCIWDATA7) +X(D_SCIADDR0) +X(D_SCIADDR1) +X(D_SCIADDR2) +X(D_SCIADDR3) +X(D_SCIADDR4) +X(D_SCIADDR5) +X(D_SCIENAUX) +X(D_SCISELAUX) +X(CH0_SCIEN) +X(CH1_SCIEN) +X(CH0_SCISEL) +X(CH1_SCISEL) +X(D_SCIRD) +X(D_SCIWSTN) +X(D_CYAWSTN) +X(D_FFC_SYNC_TOGGLE) +X(D_FFC_DUAL_RST) +X(D_FFC_MACRO_RST) +X(D_FFC_MACROPDB) +X(D_FFC_TRST) +X(CH0_FFC_CDR_EN_BITSLIP) +X(CH1_FFC_CDR_EN_BITSLIP) +X(D_SCAN_ENABLE) +X(D_SCAN_IN_0) +X(D_SCAN_IN_1) +X(D_SCAN_IN_2) +X(D_SCAN_IN_3) +X(D_SCAN_IN_4) +X(D_SCAN_IN_5) +X(D_SCAN_IN_6) +X(D_SCAN_IN_7) +X(D_SCAN_MODE) +X(D_SCAN_RESET) +X(D_CIN0) +X(D_CIN1) +X(D_CIN2) +X(D_CIN3) +X(D_CIN4) +X(D_CIN5) +X(D_CIN6) +X(D_CIN7) +X(D_CIN8) +X(D_CIN9) +X(D_CIN10) +X(D_CIN11) +X(CH0_HDOUTP) +X(CH1_HDOUTP) +X(CH0_HDOUTN) +X(CH1_HDOUTN) +X(D_TXBIT_CLKP_TO_ND) +X(D_TXBIT_CLKN_TO_ND) +X(D_SYNC_PULSE2ND) +X(D_TXPLL_LOL_TO_ND) +X(CH0_FF_RX_F_CLK) +X(CH1_FF_RX_F_CLK) +X(CH0_FF_RX_H_CLK) +X(CH1_FF_RX_H_CLK) +X(CH0_FF_TX_F_CLK) +X(CH1_FF_TX_F_CLK) +X(CH0_FF_TX_H_CLK) +X(CH1_FF_TX_H_CLK) +X(CH0_FF_RX_PCLK) +X(CH1_FF_RX_PCLK) +X(CH0_FF_TX_PCLK) +X(CH1_FF_TX_PCLK) +X(CH0_FF_RX_D_0) +X(CH1_FF_RX_D_0) +X(CH0_FF_RX_D_1) +X(CH1_FF_RX_D_1) +X(CH0_FF_RX_D_2) +X(CH1_FF_RX_D_2) +X(CH0_FF_RX_D_3) +X(CH1_FF_RX_D_3) +X(CH0_FF_RX_D_4) +X(CH1_FF_RX_D_4) +X(CH0_FF_RX_D_5) +X(CH1_FF_RX_D_5) +X(CH0_FF_RX_D_6) +X(CH1_FF_RX_D_6) +X(CH0_FF_RX_D_7) +X(CH1_FF_RX_D_7) +X(CH0_FF_RX_D_8) +X(CH1_FF_RX_D_8) +X(CH0_FF_RX_D_9) +X(CH1_FF_RX_D_9) +X(CH0_FF_RX_D_10) +X(CH1_FF_RX_D_10) +X(CH0_FF_RX_D_11) +X(CH1_FF_RX_D_11) +X(CH0_FF_RX_D_12) +X(CH1_FF_RX_D_12) +X(CH0_FF_RX_D_13) +X(CH1_FF_RX_D_13) +X(CH0_FF_RX_D_14) +X(CH1_FF_RX_D_14) +X(CH0_FF_RX_D_15) +X(CH1_FF_RX_D_15) +X(CH0_FF_RX_D_16) +X(CH1_FF_RX_D_16) +X(CH0_FF_RX_D_17) +X(CH1_FF_RX_D_17) +X(CH0_FF_RX_D_18) +X(CH1_FF_RX_D_18) +X(CH0_FF_RX_D_19) +X(CH1_FF_RX_D_19) +X(CH0_FF_RX_D_20) +X(CH1_FF_RX_D_20) +X(CH0_FF_RX_D_21) +X(CH1_FF_RX_D_21) +X(CH0_FF_RX_D_22) +X(CH1_FF_RX_D_22) +X(CH0_FF_RX_D_23) +X(CH1_FF_RX_D_23) +X(CH0_FFS_PCIE_DONE) +X(CH1_FFS_PCIE_DONE) +X(CH0_FFS_PCIE_CON) +X(CH1_FFS_PCIE_CON) +X(CH0_FFS_RLOS) +X(CH1_FFS_RLOS) +X(CH0_FFS_LS_SYNC_STATUS) +X(CH1_FFS_LS_SYNC_STATUS) +X(CH0_FFS_CC_UNDERRUN) +X(CH1_FFS_CC_UNDERRUN) +X(CH0_FFS_CC_OVERRUN) +X(CH1_FFS_CC_OVERRUN) +X(CH0_FFS_RXFBFIFO_ERROR) +X(CH1_FFS_RXFBFIFO_ERROR) +X(CH0_FFS_TXFBFIFO_ERROR) +X(CH1_FFS_TXFBFIFO_ERROR) +X(CH0_FFS_RLOL) +X(CH1_FFS_RLOL) +X(CH0_FFS_SKP_ADDED) +X(CH1_FFS_SKP_ADDED) +X(CH0_FFS_SKP_DELETED) +X(CH1_FFS_SKP_DELETED) +X(CH0_LDR_RX2CORE) +X(CH1_LDR_RX2CORE) +X(D_SCIRDATA0) +X(D_SCIRDATA1) +X(D_SCIRDATA2) +X(D_SCIRDATA3) +X(D_SCIRDATA4) +X(D_SCIRDATA5) +X(D_SCIRDATA6) +X(D_SCIRDATA7) +X(D_SCIINT) +X(D_SCAN_OUT_0) +X(D_SCAN_OUT_1) +X(D_SCAN_OUT_2) +X(D_SCAN_OUT_3) +X(D_SCAN_OUT_4) +X(D_SCAN_OUT_5) +X(D_SCAN_OUT_6) +X(D_SCAN_OUT_7) +X(D_COUT0) +X(D_COUT1) +X(D_COUT2) +X(D_COUT3) +X(D_COUT4) +X(D_COUT5) +X(D_COUT6) +X(D_COUT7) +X(D_COUT8) +X(D_COUT9) +X(D_COUT10) +X(D_COUT11) +X(D_COUT12) +X(D_COUT13) +X(D_COUT14) +X(D_COUT15) +X(D_COUT16) +X(D_COUT17) +X(D_COUT18) +X(D_COUT19) +X(D_REFCLKI) +X(D_FFS_PLOL) + +X(PCSCLKDIV) +X(SEL2) +X(SEL1) +X(SEL0) +X(CDIV1) +X(CDIVX) \ No newline at end of file diff --git a/ecp5/dcu_bitstream.h b/ecp5/dcu_bitstream.h new file mode 100644 index 00000000..93c1f9f2 --- /dev/null +++ b/ecp5/dcu_bitstream.h @@ -0,0 +1,424 @@ +tg.config.add_word("DCU.CH0_AUTO_CALIB_EN", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_AUTO_CALIB_EN"), "0"), 1)); +tg.config.add_word("DCU.CH0_AUTO_FACQ_EN", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_AUTO_FACQ_EN"), "0"), 1)); +tg.config.add_word("DCU.CH0_BAND_THRESHOLD", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_BAND_THRESHOLD"), "0"), 6)); +tg.config.add_word("DCU.CH0_CALIB_CK_MODE", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_CALIB_CK_MODE"), "0"), 1)); +tg.config.add_word("DCU.CH0_CC_MATCH_1", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_CC_MATCH_1"), "0"), 10)); +tg.config.add_word("DCU.CH0_CC_MATCH_2", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_CC_MATCH_2"), "0"), 10)); +tg.config.add_word("DCU.CH0_CC_MATCH_3", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_CC_MATCH_3"), "0"), 10)); +tg.config.add_word("DCU.CH0_CC_MATCH_4", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_CC_MATCH_4"), "0"), 10)); +tg.config.add_word("DCU.CH0_CDR_CNT4SEL", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_CDR_CNT4SEL"), "0"), 2)); +tg.config.add_word("DCU.CH0_CDR_CNT8SEL", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_CDR_CNT8SEL"), "0"), 2)); +tg.config.add_word("DCU.CH0_CTC_BYPASS", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_CTC_BYPASS"), "0"), 1)); +tg.config.add_word("DCU.CH0_DCOATDCFG", parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOATDCFG"), "0"), 2)); +tg.config.add_word("DCU.CH0_DCOATDDLY", parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOATDDLY"), "0"), 2)); +tg.config.add_word("DCU.CH0_DCOBYPSATD", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOBYPSATD"), "0"), 1)); +tg.config.add_word("DCU.CH0_DCOCALDIV", parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOCALDIV"), "0"), 3)); +tg.config.add_word("DCU.CH0_DCOCTLGI", parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOCTLGI"), "0"), 3)); +tg.config.add_word("DCU.CH0_DCODISBDAVOID", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCODISBDAVOID"), "0"), 1)); +tg.config.add_word("DCU.CH0_DCOFLTDAC", parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOFLTDAC"), "0"), 2)); +tg.config.add_word("DCU.CH0_DCOFTNRG", parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOFTNRG"), "0"), 3)); +tg.config.add_word("DCU.CH0_DCOIOSTUNE", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOIOSTUNE"), "0"), 3)); +tg.config.add_word("DCU.CH0_DCOITUNE", parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOITUNE"), "0"), 2)); +tg.config.add_word("DCU.CH0_DCOITUNE4LSB", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOITUNE4LSB"), "0"), 3)); +tg.config.add_word("DCU.CH0_DCOIUPDNX2", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOIUPDNX2"), "0"), 1)); +tg.config.add_word("DCU.CH0_DCONUOFLSB", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCONUOFLSB"), "0"), 3)); +tg.config.add_word("DCU.CH0_DCOSCALEI", parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOSCALEI"), "0"), 2)); +tg.config.add_word("DCU.CH0_DCOSTARTVAL", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOSTARTVAL"), "0"), 3)); +tg.config.add_word("DCU.CH0_DCOSTEP", parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOSTEP"), "0"), 2)); +tg.config.add_word("DCU.CH0_DEC_BYPASS", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_DEC_BYPASS"), "0"), 1)); +tg.config.add_word("DCU.CH0_ENABLE_CG_ALIGN", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_ENABLE_CG_ALIGN"), "0"), 1)); +tg.config.add_word("DCU.CH0_ENC_BYPASS", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_ENC_BYPASS"), "0"), 1)); +tg.config.add_word("DCU.CH0_FF_RX_F_CLK_DIS", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_FF_RX_F_CLK_DIS"), "0"), 1)); +tg.config.add_word("DCU.CH0_FF_RX_H_CLK_EN", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_FF_RX_H_CLK_EN"), "0"), 1)); +tg.config.add_word("DCU.CH0_FF_TX_F_CLK_DIS", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_FF_TX_F_CLK_DIS"), "0"), 1)); +tg.config.add_word("DCU.CH0_FF_TX_H_CLK_EN", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_FF_TX_H_CLK_EN"), "0"), 1)); +tg.config.add_word("DCU.CH0_GE_AN_ENABLE", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_GE_AN_ENABLE"), "0"), 1)); +tg.config.add_word("DCU.CH0_INVERT_RX", parse_config_str(str_or_default(ci->params, ctx->id("CH0_INVERT_RX"), "0"), 1)); +tg.config.add_word("DCU.CH0_INVERT_TX", parse_config_str(str_or_default(ci->params, ctx->id("CH0_INVERT_TX"), "0"), 1)); +tg.config.add_word("DCU.CH0_LDR_CORE2TX_SEL", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_LDR_CORE2TX_SEL"), "0"), 1)); +tg.config.add_word("DCU.CH0_LDR_RX2CORE_SEL", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_LDR_RX2CORE_SEL"), "0"), 1)); +tg.config.add_word("DCU.CH0_LEQ_OFFSET_SEL", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_LEQ_OFFSET_SEL"), "0"), 1)); +tg.config.add_word("DCU.CH0_LEQ_OFFSET_TRIM", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_LEQ_OFFSET_TRIM"), "0"), 3)); +tg.config.add_word("DCU.CH0_LSM_DISABLE", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_LSM_DISABLE"), "0"), 1)); +tg.config.add_word("DCU.CH0_MATCH_2_ENABLE", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_MATCH_2_ENABLE"), "0"), 1)); +tg.config.add_word("DCU.CH0_MATCH_4_ENABLE", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_MATCH_4_ENABLE"), "0"), 1)); +tg.config.add_word("DCU.CH0_MIN_IPG_CNT", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_MIN_IPG_CNT"), "0"), 2)); +tg.config.add_word("DCU.CH0_PCIE_EI_EN", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_PCIE_EI_EN"), "0"), 1)); +tg.config.add_word("DCU.CH0_PCIE_MODE", parse_config_str(str_or_default(ci->params, ctx->id("CH0_PCIE_MODE"), "0"), 1)); +tg.config.add_word("DCU.CH0_PCS_DET_TIME_SEL", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_PCS_DET_TIME_SEL"), "0"), 2)); +tg.config.add_word("DCU.CH0_PDEN_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH0_PDEN_SEL"), "0"), 1)); +tg.config.add_word("DCU.CH0_PRBS_ENABLE", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_PRBS_ENABLE"), "0"), 1)); +tg.config.add_word("DCU.CH0_PRBS_LOCK", parse_config_str(str_or_default(ci->params, ctx->id("CH0_PRBS_LOCK"), "0"), 1)); +tg.config.add_word("DCU.CH0_PRBS_SELECTION", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_PRBS_SELECTION"), "0"), 1)); +tg.config.add_word("DCU.CH0_RATE_MODE_RX", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_RATE_MODE_RX"), "0"), 1)); +tg.config.add_word("DCU.CH0_RATE_MODE_TX", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_RATE_MODE_TX"), "0"), 1)); +tg.config.add_word("DCU.CH0_RCV_DCC_EN", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_RCV_DCC_EN"), "0"), 1)); +tg.config.add_word("DCU.CH0_REG_BAND_OFFSET", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_REG_BAND_OFFSET"), "0"), 4)); +tg.config.add_word("DCU.CH0_REG_BAND_SEL", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_REG_BAND_SEL"), "0"), 6)); +tg.config.add_word("DCU.CH0_REG_IDAC_EN", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_REG_IDAC_EN"), "0"), 1)); +tg.config.add_word("DCU.CH0_REG_IDAC_SEL", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_REG_IDAC_SEL"), "0"), 10)); +tg.config.add_word("DCU.CH0_REQ_EN", parse_config_str(str_or_default(ci->params, ctx->id("CH0_REQ_EN"), "0"), 1)); +tg.config.add_word("DCU.CH0_REQ_LVL_SET", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_REQ_LVL_SET"), "0"), 2)); +tg.config.add_word("DCU.CH0_RIO_MODE", parse_config_str(str_or_default(ci->params, ctx->id("CH0_RIO_MODE"), "0"), 1)); +tg.config.add_word("DCU.CH0_RLOS_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH0_RLOS_SEL"), "0"), 1)); +tg.config.add_word("DCU.CH0_RPWDNB", parse_config_str(str_or_default(ci->params, ctx->id("CH0_RPWDNB"), "0"), 1)); +tg.config.add_word("DCU.CH0_RTERM_RX", parse_config_str(str_or_default(ci->params, ctx->id("CH0_RTERM_RX"), "0"), 5)); +tg.config.add_word("DCU.CH0_RTERM_TX", parse_config_str(str_or_default(ci->params, ctx->id("CH0_RTERM_TX"), "0"), 5)); +tg.config.add_word("DCU.CH0_RXIN_CM", parse_config_str(str_or_default(ci->params, ctx->id("CH0_RXIN_CM"), "0"), 2)); +tg.config.add_word("DCU.CH0_RXTERM_CM", parse_config_str(str_or_default(ci->params, ctx->id("CH0_RXTERM_CM"), "0"), 2)); +tg.config.add_word("DCU.CH0_RX_DCO_CK_DIV", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_RX_DCO_CK_DIV"), "0"), 3)); +tg.config.add_word("DCU.CH0_RX_DIV11_SEL", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_RX_DIV11_SEL"), "0"), 1)); +tg.config.add_word("DCU.CH0_RX_GEAR_BYPASS", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_RX_GEAR_BYPASS"), "0"), 1)); +tg.config.add_word("DCU.CH0_RX_GEAR_MODE", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_RX_GEAR_MODE"), "0"), 1)); +tg.config.add_word("DCU.CH0_RX_LOS_CEQ", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_RX_LOS_CEQ"), "0"), 2)); +tg.config.add_word("DCU.CH0_RX_LOS_EN", parse_config_str(str_or_default(ci->params, ctx->id("CH0_RX_LOS_EN"), "0"), 1)); +tg.config.add_word("DCU.CH0_RX_LOS_HYST_EN", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_RX_LOS_HYST_EN"), "0"), 1)); +tg.config.add_word("DCU.CH0_RX_LOS_LVL", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_RX_LOS_LVL"), "0"), 3)); +tg.config.add_word("DCU.CH0_RX_RATE_SEL", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_RX_RATE_SEL"), "0"), 4)); +tg.config.add_word("DCU.CH0_RX_SB_BYPASS", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_RX_SB_BYPASS"), "0"), 1)); +tg.config.add_word("DCU.CH0_SB_BYPASS", parse_config_str(str_or_default(ci->params, ctx->id("CH0_SB_BYPASS"), "0"), 1)); +tg.config.add_word("DCU.CH0_SEL_SD_RX_CLK", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_SEL_SD_RX_CLK"), "0"), 1)); +tg.config.add_word("DCU.CH0_TDRV_DAT_SEL", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_DAT_SEL"), "0"), 2)); +tg.config.add_word("DCU.CH0_TDRV_POST_EN", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_POST_EN"), "0"), 1)); +tg.config.add_word("DCU.CH0_TDRV_PRE_EN", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_PRE_EN"), "0"), 1)); +tg.config.add_word("DCU.CH0_TDRV_SLICE0_CUR", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_SLICE0_CUR"), "0"), 3)); +tg.config.add_word("DCU.CH0_TDRV_SLICE0_SEL", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_SLICE0_SEL"), "0"), 2)); +tg.config.add_word("DCU.CH0_TDRV_SLICE1_CUR", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_SLICE1_CUR"), "0"), 3)); +tg.config.add_word("DCU.CH0_TDRV_SLICE1_SEL", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_SLICE1_SEL"), "0"), 2)); +tg.config.add_word("DCU.CH0_TDRV_SLICE2_CUR", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_SLICE2_CUR"), "0"), 2)); +tg.config.add_word("DCU.CH0_TDRV_SLICE2_SEL", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_SLICE2_SEL"), "0"), 2)); +tg.config.add_word("DCU.CH0_TDRV_SLICE3_CUR", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_SLICE3_CUR"), "0"), 2)); +tg.config.add_word("DCU.CH0_TDRV_SLICE3_SEL", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_SLICE3_SEL"), "0"), 2)); +tg.config.add_word("DCU.CH0_TDRV_SLICE4_CUR", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_SLICE4_CUR"), "0"), 2)); +tg.config.add_word("DCU.CH0_TDRV_SLICE4_SEL", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_SLICE4_SEL"), "0"), 2)); +tg.config.add_word("DCU.CH0_TDRV_SLICE5_CUR", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_SLICE5_CUR"), "0"), 2)); +tg.config.add_word("DCU.CH0_TDRV_SLICE5_SEL", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_SLICE5_SEL"), "0"), 2)); +tg.config.add_word("DCU.CH0_TPWDNB", parse_config_str(str_or_default(ci->params, ctx->id("CH0_TPWDNB"), "0"), 1)); +tg.config.add_word("DCU.CH0_TX_CM_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH0_TX_CM_SEL"), "0"), 2)); +tg.config.add_word("DCU.CH0_TX_DIV11_SEL", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_TX_DIV11_SEL"), "0"), 1)); +tg.config.add_word("DCU.CH0_TX_GEAR_BYPASS", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_TX_GEAR_BYPASS"), "0"), 1)); +tg.config.add_word("DCU.CH0_TX_GEAR_MODE", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_TX_GEAR_MODE"), "0"), 1)); +tg.config.add_word("DCU.CH0_TX_POST_SIGN", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_TX_POST_SIGN"), "0"), 1)); +tg.config.add_word("DCU.CH0_TX_PRE_SIGN", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_TX_PRE_SIGN"), "0"), 1)); +tg.config.add_word("DCU.CH0_UC_MODE", parse_config_str(str_or_default(ci->params, ctx->id("CH0_UC_MODE"), "0"), 1)); +tg.config.add_word("DCU.CH0_UDF_COMMA_A", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_UDF_COMMA_A"), "0"), 10)); +tg.config.add_word("DCU.CH0_UDF_COMMA_B", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_UDF_COMMA_B"), "0"), 10)); +tg.config.add_word("DCU.CH0_UDF_COMMA_MASK", + parse_config_str(str_or_default(ci->params, ctx->id("CH0_UDF_COMMA_MASK"), "0"), 10)); +tg.config.add_word("DCU.CH0_WA_BYPASS", parse_config_str(str_or_default(ci->params, ctx->id("CH0_WA_BYPASS"), "0"), 1)); +tg.config.add_word("DCU.CH0_WA_MODE", parse_config_str(str_or_default(ci->params, ctx->id("CH0_WA_MODE"), "0"), 1)); +tg.config.add_word("DCU.CH1_AUTO_CALIB_EN", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_AUTO_CALIB_EN"), "0"), 1)); +tg.config.add_word("DCU.CH1_AUTO_FACQ_EN", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_AUTO_FACQ_EN"), "0"), 1)); +tg.config.add_word("DCU.CH1_BAND_THRESHOLD", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_BAND_THRESHOLD"), "0"), 6)); +tg.config.add_word("DCU.CH1_CALIB_CK_MODE", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_CALIB_CK_MODE"), "0"), 1)); +tg.config.add_word("DCU.CH1_CC_MATCH_1", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_CC_MATCH_1"), "0"), 10)); +tg.config.add_word("DCU.CH1_CC_MATCH_2", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_CC_MATCH_2"), "0"), 10)); +tg.config.add_word("DCU.CH1_CC_MATCH_3", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_CC_MATCH_3"), "0"), 10)); +tg.config.add_word("DCU.CH1_CC_MATCH_4", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_CC_MATCH_4"), "0"), 10)); +tg.config.add_word("DCU.CH1_CDR_CNT4SEL", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_CDR_CNT4SEL"), "0"), 2)); +tg.config.add_word("DCU.CH1_CDR_CNT8SEL", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_CDR_CNT8SEL"), "0"), 2)); +tg.config.add_word("DCU.CH1_CTC_BYPASS", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_CTC_BYPASS"), "0"), 1)); +tg.config.add_word("DCU.CH1_DCOATDCFG", parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOATDCFG"), "0"), 2)); +tg.config.add_word("DCU.CH1_DCOATDDLY", parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOATDDLY"), "0"), 2)); +tg.config.add_word("DCU.CH1_DCOBYPSATD", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOBYPSATD"), "0"), 1)); +tg.config.add_word("DCU.CH1_DCOCALDIV", parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOCALDIV"), "0"), 3)); +tg.config.add_word("DCU.CH1_DCOCTLGI", parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOCTLGI"), "0"), 3)); +tg.config.add_word("DCU.CH1_DCODISBDAVOID", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCODISBDAVOID"), "0"), 1)); +tg.config.add_word("DCU.CH1_DCOFLTDAC", parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOFLTDAC"), "0"), 2)); +tg.config.add_word("DCU.CH1_DCOFTNRG", parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOFTNRG"), "0"), 3)); +tg.config.add_word("DCU.CH1_DCOIOSTUNE", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOIOSTUNE"), "0"), 3)); +tg.config.add_word("DCU.CH1_DCOITUNE", parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOITUNE"), "0"), 2)); +tg.config.add_word("DCU.CH1_DCOITUNE4LSB", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOITUNE4LSB"), "0"), 3)); +tg.config.add_word("DCU.CH1_DCOIUPDNX2", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOIUPDNX2"), "0"), 1)); +tg.config.add_word("DCU.CH1_DCONUOFLSB", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCONUOFLSB"), "0"), 3)); +tg.config.add_word("DCU.CH1_DCOSCALEI", parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOSCALEI"), "0"), 2)); +tg.config.add_word("DCU.CH1_DCOSTARTVAL", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOSTARTVAL"), "0"), 3)); +tg.config.add_word("DCU.CH1_DCOSTEP", parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOSTEP"), "0"), 2)); +tg.config.add_word("DCU.CH1_DEC_BYPASS", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_DEC_BYPASS"), "0"), 1)); +tg.config.add_word("DCU.CH1_ENABLE_CG_ALIGN", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_ENABLE_CG_ALIGN"), "0"), 1)); +tg.config.add_word("DCU.CH1_ENC_BYPASS", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_ENC_BYPASS"), "0"), 1)); +tg.config.add_word("DCU.CH1_FF_RX_F_CLK_DIS", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_FF_RX_F_CLK_DIS"), "0"), 1)); +tg.config.add_word("DCU.CH1_FF_RX_H_CLK_EN", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_FF_RX_H_CLK_EN"), "0"), 1)); +tg.config.add_word("DCU.CH1_FF_TX_F_CLK_DIS", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_FF_TX_F_CLK_DIS"), "0"), 1)); +tg.config.add_word("DCU.CH1_FF_TX_H_CLK_EN", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_FF_TX_H_CLK_EN"), "0"), 1)); +tg.config.add_word("DCU.CH1_GE_AN_ENABLE", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_GE_AN_ENABLE"), "0"), 1)); +tg.config.add_word("DCU.CH1_INVERT_RX", parse_config_str(str_or_default(ci->params, ctx->id("CH1_INVERT_RX"), "0"), 1)); +tg.config.add_word("DCU.CH1_INVERT_TX", parse_config_str(str_or_default(ci->params, ctx->id("CH1_INVERT_TX"), "0"), 1)); +tg.config.add_word("DCU.CH1_LDR_CORE2TX_SEL", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_LDR_CORE2TX_SEL"), "0"), 1)); +tg.config.add_word("DCU.CH1_LDR_RX2CORE_SEL", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_LDR_RX2CORE_SEL"), "0"), 1)); +tg.config.add_word("DCU.CH1_LEQ_OFFSET_SEL", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_LEQ_OFFSET_SEL"), "0"), 1)); +tg.config.add_word("DCU.CH1_LEQ_OFFSET_TRIM", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_LEQ_OFFSET_TRIM"), "0"), 3)); +tg.config.add_word("DCU.CH1_LSM_DISABLE", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_LSM_DISABLE"), "0"), 1)); +tg.config.add_word("DCU.CH1_MATCH_2_ENABLE", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_MATCH_2_ENABLE"), "0"), 1)); +tg.config.add_word("DCU.CH1_MATCH_4_ENABLE", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_MATCH_4_ENABLE"), "0"), 1)); +tg.config.add_word("DCU.CH1_MIN_IPG_CNT", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_MIN_IPG_CNT"), "0"), 2)); +tg.config.add_word("DCU.CH1_PCIE_EI_EN", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_PCIE_EI_EN"), "0"), 1)); +tg.config.add_word("DCU.CH1_PCIE_MODE", parse_config_str(str_or_default(ci->params, ctx->id("CH1_PCIE_MODE"), "0"), 1)); +tg.config.add_word("DCU.CH1_PCS_DET_TIME_SEL", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_PCS_DET_TIME_SEL"), "0"), 2)); +tg.config.add_word("DCU.CH1_PDEN_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH1_PDEN_SEL"), "0"), 1)); +tg.config.add_word("DCU.CH1_PRBS_ENABLE", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_PRBS_ENABLE"), "0"), 1)); +tg.config.add_word("DCU.CH1_PRBS_LOCK", parse_config_str(str_or_default(ci->params, ctx->id("CH1_PRBS_LOCK"), "0"), 1)); +tg.config.add_word("DCU.CH1_PRBS_SELECTION", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_PRBS_SELECTION"), "0"), 1)); +tg.config.add_word("DCU.CH1_RATE_MODE_RX", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_RATE_MODE_RX"), "0"), 1)); +tg.config.add_word("DCU.CH1_RATE_MODE_TX", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_RATE_MODE_TX"), "0"), 1)); +tg.config.add_word("DCU.CH1_RCV_DCC_EN", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_RCV_DCC_EN"), "0"), 1)); +tg.config.add_word("DCU.CH1_REG_BAND_OFFSET", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_REG_BAND_OFFSET"), "0"), 4)); +tg.config.add_word("DCU.CH1_REG_BAND_SEL", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_REG_BAND_SEL"), "0"), 6)); +tg.config.add_word("DCU.CH1_REG_IDAC_EN", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_REG_IDAC_EN"), "0"), 1)); +tg.config.add_word("DCU.CH1_REG_IDAC_SEL", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_REG_IDAC_SEL"), "0"), 10)); +tg.config.add_word("DCU.CH1_REQ_EN", parse_config_str(str_or_default(ci->params, ctx->id("CH1_REQ_EN"), "0"), 1)); +tg.config.add_word("DCU.CH1_REQ_LVL_SET", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_REQ_LVL_SET"), "0"), 2)); +tg.config.add_word("DCU.CH1_RIO_MODE", parse_config_str(str_or_default(ci->params, ctx->id("CH1_RIO_MODE"), "0"), 1)); +tg.config.add_word("DCU.CH1_RLOS_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH1_RLOS_SEL"), "0"), 1)); +tg.config.add_word("DCU.CH1_RPWDNB", parse_config_str(str_or_default(ci->params, ctx->id("CH1_RPWDNB"), "0"), 1)); +tg.config.add_word("DCU.CH1_RTERM_RX", parse_config_str(str_or_default(ci->params, ctx->id("CH1_RTERM_RX"), "0"), 5)); +tg.config.add_word("DCU.CH1_RTERM_TX", parse_config_str(str_or_default(ci->params, ctx->id("CH1_RTERM_TX"), "0"), 5)); +tg.config.add_word("DCU.CH1_RXIN_CM", parse_config_str(str_or_default(ci->params, ctx->id("CH1_RXIN_CM"), "0"), 2)); +tg.config.add_word("DCU.CH1_RXTERM_CM", parse_config_str(str_or_default(ci->params, ctx->id("CH1_RXTERM_CM"), "0"), 2)); +tg.config.add_word("DCU.CH1_RX_DCO_CK_DIV", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_RX_DCO_CK_DIV"), "0"), 3)); +tg.config.add_word("DCU.CH1_RX_DIV11_SEL", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_RX_DIV11_SEL"), "0"), 1)); +tg.config.add_word("DCU.CH1_RX_GEAR_BYPASS", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_RX_GEAR_BYPASS"), "0"), 1)); +tg.config.add_word("DCU.CH1_RX_GEAR_MODE", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_RX_GEAR_MODE"), "0"), 1)); +tg.config.add_word("DCU.CH1_RX_LOS_CEQ", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_RX_LOS_CEQ"), "0"), 2)); +tg.config.add_word("DCU.CH1_RX_LOS_EN", parse_config_str(str_or_default(ci->params, ctx->id("CH1_RX_LOS_EN"), "0"), 1)); +tg.config.add_word("DCU.CH1_RX_LOS_HYST_EN", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_RX_LOS_HYST_EN"), "0"), 1)); +tg.config.add_word("DCU.CH1_RX_LOS_LVL", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_RX_LOS_LVL"), "0"), 3)); +tg.config.add_word("DCU.CH1_RX_RATE_SEL", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_RX_RATE_SEL"), "0"), 4)); +tg.config.add_word("DCU.CH1_RX_SB_BYPASS", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_RX_SB_BYPASS"), "0"), 1)); +tg.config.add_word("DCU.CH1_SB_BYPASS", parse_config_str(str_or_default(ci->params, ctx->id("CH1_SB_BYPASS"), "0"), 1)); +tg.config.add_word("DCU.CH1_SEL_SD_RX_CLK", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_SEL_SD_RX_CLK"), "0"), 1)); +tg.config.add_word("DCU.CH1_TDRV_DAT_SEL", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_DAT_SEL"), "0"), 2)); +tg.config.add_word("DCU.CH1_TDRV_POST_EN", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_POST_EN"), "0"), 1)); +tg.config.add_word("DCU.CH1_TDRV_PRE_EN", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_PRE_EN"), "0"), 1)); +tg.config.add_word("DCU.CH1_TDRV_SLICE0_CUR", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_SLICE0_CUR"), "0"), 3)); +tg.config.add_word("DCU.CH1_TDRV_SLICE0_SEL", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_SLICE0_SEL"), "0"), 2)); +tg.config.add_word("DCU.CH1_TDRV_SLICE1_CUR", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_SLICE1_CUR"), "0"), 3)); +tg.config.add_word("DCU.CH1_TDRV_SLICE1_SEL", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_SLICE1_SEL"), "0"), 2)); +tg.config.add_word("DCU.CH1_TDRV_SLICE2_CUR", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_SLICE2_CUR"), "0"), 2)); +tg.config.add_word("DCU.CH1_TDRV_SLICE2_SEL", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_SLICE2_SEL"), "0"), 2)); +tg.config.add_word("DCU.CH1_TDRV_SLICE3_CUR", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_SLICE3_CUR"), "0"), 2)); +tg.config.add_word("DCU.CH1_TDRV_SLICE3_SEL", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_SLICE3_SEL"), "0"), 2)); +tg.config.add_word("DCU.CH1_TDRV_SLICE4_CUR", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_SLICE4_CUR"), "0"), 2)); +tg.config.add_word("DCU.CH1_TDRV_SLICE4_SEL", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_SLICE4_SEL"), "0"), 2)); +tg.config.add_word("DCU.CH1_TDRV_SLICE5_CUR", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_SLICE5_CUR"), "0"), 2)); +tg.config.add_word("DCU.CH1_TDRV_SLICE5_SEL", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_SLICE5_SEL"), "0"), 2)); +tg.config.add_word("DCU.CH1_TPWDNB", parse_config_str(str_or_default(ci->params, ctx->id("CH1_TPWDNB"), "0"), 1)); +tg.config.add_word("DCU.CH1_TX_CM_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH1_TX_CM_SEL"), "0"), 2)); +tg.config.add_word("DCU.CH1_TX_DIV11_SEL", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_TX_DIV11_SEL"), "0"), 1)); +tg.config.add_word("DCU.CH1_TX_GEAR_BYPASS", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_TX_GEAR_BYPASS"), "0"), 1)); +tg.config.add_word("DCU.CH1_TX_GEAR_MODE", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_TX_GEAR_MODE"), "0"), 1)); +tg.config.add_word("DCU.CH1_TX_POST_SIGN", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_TX_POST_SIGN"), "0"), 1)); +tg.config.add_word("DCU.CH1_TX_PRE_SIGN", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_TX_PRE_SIGN"), "0"), 1)); +tg.config.add_word("DCU.CH1_UC_MODE", parse_config_str(str_or_default(ci->params, ctx->id("CH1_UC_MODE"), "0"), 1)); +tg.config.add_word("DCU.CH1_UDF_COMMA_A", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_UDF_COMMA_A"), "0"), 10)); +tg.config.add_word("DCU.CH1_UDF_COMMA_B", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_UDF_COMMA_B"), "0"), 10)); +tg.config.add_word("DCU.CH1_UDF_COMMA_MASK", + parse_config_str(str_or_default(ci->params, ctx->id("CH1_UDF_COMMA_MASK"), "0"), 10)); +tg.config.add_word("DCU.CH1_WA_BYPASS", parse_config_str(str_or_default(ci->params, ctx->id("CH1_WA_BYPASS"), "0"), 1)); +tg.config.add_word("DCU.CH1_WA_MODE", parse_config_str(str_or_default(ci->params, ctx->id("CH1_WA_MODE"), "0"), 1)); +tg.config.add_word("DCU.D_BITCLK_FROM_ND_EN", + parse_config_str(str_or_default(ci->params, ctx->id("D_BITCLK_FROM_ND_EN"), "0"), 1)); +tg.config.add_word("DCU.D_BITCLK_LOCAL_EN", + parse_config_str(str_or_default(ci->params, ctx->id("D_BITCLK_LOCAL_EN"), "0"), 1)); +tg.config.add_word("DCU.D_BITCLK_ND_EN", + parse_config_str(str_or_default(ci->params, ctx->id("D_BITCLK_ND_EN"), "0"), 1)); +tg.config.add_word("DCU.D_BUS8BIT_SEL", parse_config_str(str_or_default(ci->params, ctx->id("D_BUS8BIT_SEL"), "0"), 1)); +tg.config.add_word("DCU.D_CDR_LOL_SET", parse_config_str(str_or_default(ci->params, ctx->id("D_CDR_LOL_SET"), "0"), 2)); +tg.config.add_word("DCU.D_CMUSETBIASI", parse_config_str(str_or_default(ci->params, ctx->id("D_CMUSETBIASI"), "0"), 2)); +tg.config.add_word("DCU.D_CMUSETI4CPP", parse_config_str(str_or_default(ci->params, ctx->id("D_CMUSETI4CPP"), "0"), 4)); +tg.config.add_word("DCU.D_CMUSETI4CPZ", parse_config_str(str_or_default(ci->params, ctx->id("D_CMUSETI4CPZ"), "0"), 4)); +tg.config.add_word("DCU.D_CMUSETI4VCO", parse_config_str(str_or_default(ci->params, ctx->id("D_CMUSETI4VCO"), "0"), 2)); +tg.config.add_word("DCU.D_CMUSETICP4P", parse_config_str(str_or_default(ci->params, ctx->id("D_CMUSETICP4P"), "0"), 2)); +tg.config.add_word("DCU.D_CMUSETICP4Z", parse_config_str(str_or_default(ci->params, ctx->id("D_CMUSETICP4Z"), "0"), 3)); +tg.config.add_word("DCU.D_CMUSETINITVCT", + parse_config_str(str_or_default(ci->params, ctx->id("D_CMUSETINITVCT"), "0"), 2)); +tg.config.add_word("DCU.D_CMUSETISCL4VCO", + parse_config_str(str_or_default(ci->params, ctx->id("D_CMUSETISCL4VCO"), "0"), 3)); +tg.config.add_word("DCU.D_CMUSETP1GM", parse_config_str(str_or_default(ci->params, ctx->id("D_CMUSETP1GM"), "0"), 3)); +tg.config.add_word("DCU.D_CMUSETP2AGM", parse_config_str(str_or_default(ci->params, ctx->id("D_CMUSETP2AGM"), "0"), 3)); +tg.config.add_word("DCU.D_CMUSETZGM", parse_config_str(str_or_default(ci->params, ctx->id("D_CMUSETZGM"), "0"), 3)); +tg.config.add_word("DCU.D_DCO_CALIB_TIME_SEL", + parse_config_str(str_or_default(ci->params, ctx->id("D_DCO_CALIB_TIME_SEL"), "0"), 2)); +tg.config.add_word("DCU.D_HIGH_MARK", parse_config_str(str_or_default(ci->params, ctx->id("D_HIGH_MARK"), "0"), 4)); +tg.config.add_word("DCU.D_IB_PWDNB", parse_config_str(str_or_default(ci->params, ctx->id("D_IB_PWDNB"), "0"), 1)); +tg.config.add_word("DCU.D_ISETLOS", parse_config_str(str_or_default(ci->params, ctx->id("D_ISETLOS"), "0"), 8)); +tg.config.add_word("DCU.D_LOW_MARK", parse_config_str(str_or_default(ci->params, ctx->id("D_LOW_MARK"), "0"), 4)); +tg.config.add_word("DCU.D_MACROPDB", parse_config_str(str_or_default(ci->params, ctx->id("D_MACROPDB"), "0"), 1)); +tg.config.add_word("DCU.D_PD_ISET", parse_config_str(str_or_default(ci->params, ctx->id("D_PD_ISET"), "0"), 2)); +tg.config.add_word("DCU.D_PLL_LOL_SET", parse_config_str(str_or_default(ci->params, ctx->id("D_PLL_LOL_SET"), "0"), 2)); +tg.config.add_word("DCU.D_REFCK_MODE", parse_config_str(str_or_default(ci->params, ctx->id("D_REFCK_MODE"), "0"), 3)); +tg.config.add_word("DCU.D_REQ_ISET", parse_config_str(str_or_default(ci->params, ctx->id("D_REQ_ISET"), "0"), 3)); +tg.config.add_word("DCU.D_RG_EN", parse_config_str(str_or_default(ci->params, ctx->id("D_RG_EN"), "0"), 1)); +tg.config.add_word("DCU.D_RG_SET", parse_config_str(str_or_default(ci->params, ctx->id("D_RG_SET"), "0"), 2)); +tg.config.add_word("DCU.D_SETICONST_AUX", + parse_config_str(str_or_default(ci->params, ctx->id("D_SETICONST_AUX"), "0"), 2)); +tg.config.add_word("DCU.D_SETICONST_CH", + parse_config_str(str_or_default(ci->params, ctx->id("D_SETICONST_CH"), "0"), 2)); +tg.config.add_word("DCU.D_SETIRPOLY_AUX", + parse_config_str(str_or_default(ci->params, ctx->id("D_SETIRPOLY_AUX"), "0"), 2)); +tg.config.add_word("DCU.D_SETIRPOLY_CH", + parse_config_str(str_or_default(ci->params, ctx->id("D_SETIRPOLY_CH"), "0"), 2)); +tg.config.add_word("DCU.D_SETPLLRC", parse_config_str(str_or_default(ci->params, ctx->id("D_SETPLLRC"), "0"), 6)); +tg.config.add_word("DCU.D_SYNC_LOCAL_EN", + parse_config_str(str_or_default(ci->params, ctx->id("D_SYNC_LOCAL_EN"), "0"), 1)); +tg.config.add_word("DCU.D_SYNC_ND_EN", parse_config_str(str_or_default(ci->params, ctx->id("D_SYNC_ND_EN"), "0"), 1)); +tg.config.add_word("DCU.D_TXPLL_PWDNB", parse_config_str(str_or_default(ci->params, ctx->id("D_TXPLL_PWDNB"), "0"), 1)); +tg.config.add_word("DCU.D_TX_VCO_CK_DIV", + parse_config_str(str_or_default(ci->params, ctx->id("D_TX_VCO_CK_DIV"), "0"), 3)); +tg.config.add_word("DCU.D_XGE_MODE", parse_config_str(str_or_default(ci->params, ctx->id("D_XGE_MODE"), "0"), 1)); diff --git a/ecp5/family.cmake b/ecp5/family.cmake index 6322e24e..ebe654af 100644 --- a/ecp5/family.cmake +++ b/ecp5/family.cmake @@ -18,7 +18,11 @@ file(MAKE_DIRECTORY ecp5/chipdbs/) add_library(ecp5_chipdb OBJECT ecp5/chipdbs/) target_compile_definitions(ecp5_chipdb PRIVATE NEXTPNR_NAMESPACE=nextpnr_${family}) target_include_directories(ecp5_chipdb PRIVATE ${family}/) +if (WIN32) +set(ENV_CMD ${CMAKE_COMMAND} -E env "PYTHONPATH=\"${TRELLIS_ROOT}/libtrellis\;${TRELLIS_ROOT}/util/common\"") +else() set(ENV_CMD ${CMAKE_COMMAND} -E env "PYTHONPATH=${TRELLIS_ROOT}/libtrellis:${TRELLIS_ROOT}/util/common") +endif() if (MSVC) target_sources(ecp5_chipdb PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/ecp5/resource/embed.cc) set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/ecp5/resources/chipdb.rc PROPERTIES LANGUAGE RC) diff --git a/ecp5/globals.cc b/ecp5/globals.cc index 06412fef..66c62024 100644 --- a/ecp5/globals.cc +++ b/ecp5/globals.cc @@ -55,6 +55,9 @@ class Ecp5GlobalRouter { if (user.cell->type == id_TRELLIS_SLICE && (user.port == id_CLK || user.port == id_WCK)) return true; + if (user.cell->type == id_DCUA && (user.port == id_CH0_FF_RXI_CLK || user.port == id_CH1_FF_RXI_CLK || + user.port == id_CH0_FF_TXI_CLK || user.port == id_CH1_FF_TXI_CLK)) + return true; return false; } @@ -65,8 +68,11 @@ class Ecp5GlobalRouter NetInfo *ni = net.second.get(); clockCount[ni->name] = 0; for (const auto &user : ni->users) { - if (is_clock_port(user)) + if (is_clock_port(user)) { clockCount[ni->name]++; + if (user.cell->type == id_DCUA) + clockCount[ni->name] += 100; + } } // log_info("clkcount %s: %d\n", ni->name.c_str(ctx),clockCount[ni->name]); } @@ -290,6 +296,10 @@ class Ecp5GlobalRouter float tns; return get_net_metric(ctx, clki, MetricType::WIRELENGTH, tns); } else { + // Check for dedicated routing + if (has_short_route(ctx->getBelPinWire(drv_bel, drv.port), ctx->getBelPinWire(dcc->bel, id_CLKI))) { + return 0; + } // Driver is locked Loc dcc_loc = ctx->getBelLocation(dcc->bel); Loc drv_loc = ctx->getBelLocation(drv_bel); @@ -297,6 +307,46 @@ class Ecp5GlobalRouter } } + // Return true if a short (<5) route exists between two wires + bool has_short_route(WireId src, WireId dst, int thresh = 5) + { + std::queue visit; + std::unordered_map backtrace; + visit.push(src); + WireId cursor; + while (true) { + + if (visit.empty() || visit.size() > 1000) { + // log_info ("dist %s -> %s = inf\n", ctx->getWireName(src).c_str(ctx), + // ctx->getWireName(dst).c_str(ctx)); + return false; + } + cursor = visit.front(); + visit.pop(); + + if (cursor == dst) + break; + for (auto dh : ctx->getPipsDownhill(cursor)) { + WireId pipDst = ctx->getPipDstWire(dh); + if (backtrace.count(pipDst)) + continue; + backtrace[pipDst] = dh; + visit.push(pipDst); + } + } + int length = 0; + while (true) { + auto fnd = backtrace.find(cursor); + if (fnd == backtrace.end()) + break; + cursor = ctx->getPipSrcWire(fnd->second); + length++; + } + // log_info ("dist %s -> %s = %d\n", ctx->getWireName(src).c_str(ctx), ctx->getWireName(dst).c_str(ctx), + // length); + return length < thresh; + } + // Attempt to place a DCC void place_dcc(CellInfo *dcc) { @@ -335,6 +385,8 @@ class Ecp5GlobalRouter for (auto user : net->users) { if (user.port == id_CLKFB) { keep_users.push_back(user); + } else if (net->driver.cell->type == id_EXTREFB && user.cell->type == id_DCUA) { + keep_users.push_back(user); } else { glbnet->users.push_back(user); user.cell->ports.at(user.port).net = glbnet.get(); @@ -350,6 +402,13 @@ class Ecp5GlobalRouter place_dcc(dcc.get()); + if (net->clkconstr) { + glbnet->clkconstr = std::unique_ptr(new ClockConstraint()); + glbnet->clkconstr->low = net->clkconstr->low; + glbnet->clkconstr->high = net->clkconstr->high; + glbnet->clkconstr->period = net->clkconstr->period; + } + ctx->cells[dcc->name] = std::move(dcc); NetInfo *glbptr = glbnet.get(); ctx->nets[glbnet->name] = std::move(glbnet); diff --git a/ecp5/pack.cc b/ecp5/pack.cc index 73e15609..2d2f7578 100644 --- a/ecp5/pack.cc +++ b/ecp5/pack.cc @@ -235,6 +235,46 @@ class Ecp5Packer } } + // Return true if an port is a top level port that provides its own IOBUF + bool is_top_port(PortRef &port) + { + if (port.cell == nullptr) + return false; + if (port.cell->type == id_DCUA) { + return port.port == id_CH0_HDINP || port.port == id_CH0_HDINN || port.port == id_CH0_HDOUTP || + port.port == id_CH0_HDOUTN || port.port == id_CH1_HDINP || port.port == id_CH1_HDINN || + port.port == id_CH1_HDOUTP || port.port == id_CH1_HDOUTN; + } else if (port.cell->type == id_EXTREFB) { + return port.port == id_REFCLKP || port.port == id_REFCLKN; + } else { + return false; + } + } + + // Return true if a net only drives a top port + bool drives_top_port(NetInfo *net, PortRef &tp) + { + if (net == nullptr) + return false; + for (auto user : net->users) { + if (is_top_port(user)) { + if (net->users.size() > 1) + log_error(" port %s.%s must be connected to (and only to) a top level pin\n", + user.cell->name.c_str(ctx), user.port.c_str(ctx)); + tp = user; + return true; + } + } + if (net->driver.cell != nullptr && is_top_port(net->driver)) { + if (net->users.size() > 1) + log_error(" port %s.%s must be connected to (and only to) a top level pin\n", + net->driver.cell->name.c_str(ctx), net->driver.port.c_str(ctx)); + tp = net->driver; + return true; + } + return false; + } + // Simple "packer" to remove nextpnr IOBUFs, this assumes IOBUFs are manually instantiated void pack_io() { @@ -244,10 +284,14 @@ class Ecp5Packer CellInfo *ci = cell.second; if (is_nextpnr_iob(ctx, ci)) { CellInfo *trio = nullptr; + NetInfo *ionet = nullptr; + PortRef tp; if (ci->type == ctx->id("$nextpnr_ibuf") || ci->type == ctx->id("$nextpnr_iobuf")) { - trio = net_only_drives(ctx, ci->ports.at(ctx->id("O")).net, is_trellis_io, ctx->id("B"), true, ci); + ionet = ci->ports.at(ctx->id("O")).net; + trio = net_only_drives(ctx, ionet, is_trellis_io, ctx->id("B"), true, ci); } else if (ci->type == ctx->id("$nextpnr_obuf")) { + ionet = ci->ports.at(ctx->id("I")).net; trio = net_only_drives(ctx, ci->ports.at(ctx->id("I")).net, is_trellis_io, ctx->id("B"), true, ci); } if (trio != nullptr) { @@ -266,6 +310,19 @@ class Ecp5Packer ctx->nets.erase(net2->name); } } + } else if (drives_top_port(ionet, tp)) { + log_info("%s feeds %s %s.%s, removing %s %s.\n", ci->name.c_str(ctx), tp.cell->type.c_str(ctx), + tp.cell->name.c_str(ctx), tp.port.c_str(ctx), ci->type.c_str(ctx), ci->name.c_str(ctx)); + if (ionet != nullptr) { + ctx->nets.erase(ionet->name); + tp.cell->ports.at(tp.port).net = nullptr; + } + if (ci->type == ctx->id("$nextpnr_iobuf")) { + NetInfo *net2 = ci->ports.at(ctx->id("I")).net; + if (net2 != nullptr) { + ctx->nets.erase(net2->name); + } + } } else { // Create a TRELLIS_IO buffer std::unique_ptr tr_cell = @@ -276,21 +333,23 @@ class Ecp5Packer } packed_cells.insert(ci->name); - for (const auto &attr : ci->attrs) - trio->attrs[attr.first] = attr.second; + if (trio != nullptr) { + for (const auto &attr : ci->attrs) + trio->attrs[attr.first] = attr.second; - auto loc_attr = trio->attrs.find(ctx->id("LOC")); - if (loc_attr != trio->attrs.end()) { - std::string pin = loc_attr->second; - BelId pinBel = ctx->getPackagePinBel(pin); - if (pinBel == BelId()) { - log_error("IO pin '%s' constrained to pin '%s', which does not exist for package '%s'.\n", - trio->name.c_str(ctx), pin.c_str(), ctx->args.package.c_str()); - } else { - log_info("pin '%s' constrained to Bel '%s'.\n", trio->name.c_str(ctx), - ctx->getBelName(pinBel).c_str(ctx)); + auto loc_attr = trio->attrs.find(ctx->id("LOC")); + if (loc_attr != trio->attrs.end()) { + std::string pin = loc_attr->second; + BelId pinBel = ctx->getPackagePinBel(pin); + if (pinBel == BelId()) { + log_error("IO pin '%s' constrained to pin '%s', which does not exist for package '%s'.\n", + trio->name.c_str(ctx), pin.c_str(), ctx->args.package.c_str()); + } else { + log_info("pin '%s' constrained to Bel '%s'.\n", trio->name.c_str(ctx), + ctx->getBelName(pinBel).c_str(ctx)); + } + trio->attrs[ctx->id("BEL")] = ctx->getBelName(pinBel).str(ctx); } - trio->attrs[ctx->id("BEL")] = ctx->getBelName(pinBel).str(ctx); } } } @@ -1033,12 +1092,93 @@ class Ecp5Packer } } + // "Pack" DCUs + void pack_dcus() + { + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (ci->type == id_DCUA) { + if (ci->attrs.count(ctx->id("LOC"))) { + std::string loc = ci->attrs.at(ctx->id("LOC")); + if (loc == "DCU0" && + (ctx->args.type == ArchArgs::LFE5UM_25F || ctx->args.type == ArchArgs::LFE5UM5G_25F)) + ci->attrs[ctx->id("BEL")] = "X42/Y50/DCU"; + else if (loc == "DCU0" && + (ctx->args.type == ArchArgs::LFE5UM_45F || ctx->args.type == ArchArgs::LFE5UM5G_45F)) + ci->attrs[ctx->id("BEL")] = "X42/Y71/DCU"; + else if (loc == "DCU1" && + (ctx->args.type == ArchArgs::LFE5UM_45F || ctx->args.type == ArchArgs::LFE5UM5G_45F)) + ci->attrs[ctx->id("BEL")] = "X69/Y71/DCU"; + else if (loc == "DCU0" && + (ctx->args.type == ArchArgs::LFE5UM_85F || ctx->args.type == ArchArgs::LFE5UM5G_85F)) + ci->attrs[ctx->id("BEL")] = "X46/Y95/DCU"; + else if (loc == "DCU1" && + (ctx->args.type == ArchArgs::LFE5UM_85F || ctx->args.type == ArchArgs::LFE5UM5G_85F)) + ci->attrs[ctx->id("BEL")] = "X71/Y95/DCU"; + else + log_error("no DCU location '%s' in device '%s'\n", loc.c_str(), ctx->getChipName().c_str()); + } + if (!ci->attrs.count(ctx->id("BEL"))) + log_error("DCU must be constrained to a Bel!\n"); + // Empty port auto-creation to generate correct tie-downs + BelId exemplar_bel; + for (auto bel : ctx->getBels()) { + if (ctx->getBelType(bel) == id_DCUA) { + exemplar_bel = bel; + break; + } + } + NPNR_ASSERT(exemplar_bel != BelId()); + for (auto pin : ctx->getBelPins(exemplar_bel)) + if (ctx->getBelPinType(exemplar_bel, pin) == PORT_IN) + autocreate_empty_port(ci, pin); + } + } + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (ci->type == id_EXTREFB) { + const NetInfo *refo = net_or_nullptr(ci, id_REFCLKO); + CellInfo *dcu = nullptr; + if (refo == nullptr) + log_error("EXTREFB REFCLKO must not be unconnected\n"); + for (auto user : refo->users) { + if (user.cell->type != id_DCUA) + continue; + if (dcu != nullptr && dcu != user.cell) + log_error("EXTREFB REFCLKO must only drive a single DCUA\n"); + dcu = user.cell; + } + if (!dcu->attrs.count(ctx->id("BEL"))) + log_error("DCU must be constrained to a Bel!\n"); + std::string bel = dcu->attrs.at(ctx->id("BEL")); + NPNR_ASSERT(bel.substr(bel.length() - 3) == "DCU"); + bel.replace(bel.length() - 3, 3, "EXTREF"); + ci->attrs[ctx->id("BEL")] = bel; + } else if (ci->type == id_PCSCLKDIV) { + const NetInfo *clki = net_or_nullptr(ci, id_CLKI); + if (clki != nullptr && clki->driver.cell != nullptr && clki->driver.cell->type == id_DCUA) { + CellInfo *dcu = clki->driver.cell; + if (!dcu->attrs.count(ctx->id("BEL"))) + log_error("DCU must be constrained to a Bel!\n"); + BelId bel = ctx->getBelByName(ctx->id(dcu->attrs.at(ctx->id("BEL")))); + if (bel == BelId()) + log_error("Invalid DCU bel '%s'\n", dcu->attrs.at(ctx->id("BEL")).c_str()); + Loc loc = ctx->getBelLocation(bel); + // DCU0 -> CLKDIV z=0; DCU1 -> CLKDIV z=1 + ci->constr_abs_z = true; + ci->constr_z = (loc.x >= 69) ? 1 : 0; + } + } + } + } + public: void pack() { pack_io(); pack_ebr(); pack_dsps(); + pack_dcus(); pack_constants(); pack_dram(); pack_carries(); diff --git a/ecp5/io.cc b/ecp5/pio.cc similarity index 99% rename from ecp5/io.cc rename to ecp5/pio.cc index 908caaba..65dcd704 100644 --- a/ecp5/io.cc +++ b/ecp5/pio.cc @@ -17,7 +17,7 @@ * */ -#include "io.h" +#include "pio.h" NEXTPNR_NAMESPACE_BEGIN diff --git a/ecp5/io.h b/ecp5/pio.h similarity index 100% rename from ecp5/io.h rename to ecp5/pio.h diff --git a/ecp5/trellis_import.py b/ecp5/trellis_import.py index 9a26b605..99fe7ba9 100755 --- a/ecp5/trellis_import.py +++ b/ecp5/trellis_import.py @@ -200,9 +200,14 @@ def write_database(dev_name, chip, ddrg, endianness): write_loc(arc.sinkWire.rel, "dst") bba.u32(arc.srcWire.id, "src_idx") bba.u32(arc.sinkWire.id, "dst_idx") - bba.u32(get_pip_delay(get_wire_name(idx, arc.srcWire.rel, arc.srcWire.id), get_wire_name(idx, arc.sinkWire.rel, arc.sinkWire.id)), "delay") # TODO:delay + src_name = get_wire_name(idx, arc.srcWire.rel, arc.srcWire.id) + snk_name = get_wire_name(idx, arc.sinkWire.rel, arc.sinkWire.id) + bba.u32(get_pip_delay(src_name, snk_name), "delay") # TODO:delay bba.u16(get_tiletype_index(ddrg.to_str(arc.tiletype)), "tile_type") - bba.u8(int(arc.cls), "pip_type") + cls = arc.cls + if cls == 1 and "PCS" in snk_name or "DCU" in snk_name or "DCU" in src_name: + cls = 2 + bba.u8(cls, "pip_type") bba.u8(0, "padding") if len(loctype.wires) > 0: for wire_idx in range(len(loctype.wires)): @@ -340,7 +345,7 @@ def write_database(dev_name, chip, ddrg, endianness): bba.pop() return bba -dev_names = {"25k": "LFE5U-25F", "45k": "LFE5U-45F", "85k": "LFE5U-85F"} +dev_names = {"25k": "LFE5UM5G-25F", "45k": "LFE5UM5G-45F", "85k": "LFE5UM5G-85F"} def main(): global max_row, max_col diff --git a/generic/arch.cc b/generic/arch.cc index 4f2e07a2..77417d27 100644 --- a/generic/arch.cc +++ b/generic/arch.cc @@ -463,11 +463,16 @@ bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort } // Get the port class, also setting clockPort if applicable -TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockPort) const +TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const { return TMG_IGNORE; } +TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port, int index) const +{ + NPNR_ASSERT_FALSE("no clocking info for generic"); +} + bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const { return true; } bool Arch::isBelLocationValid(BelId bel) const { return true; } diff --git a/generic/arch.h b/generic/arch.h index 9311464e..dc4258cc 100644 --- a/generic/arch.h +++ b/generic/arch.h @@ -211,6 +211,14 @@ struct Arch : BaseCtx delay_t getDelayEpsilon() const { return 0.01; } delay_t getRipupDelayPenalty() const { return 1.0; } float getDelayNS(delay_t v) const { return v; } + + DelayInfo getDelayFromNS(float ns) const + { + DelayInfo del; + del.delay = ns; + return del; + } + uint32_t getDelayChecksum(delay_t v) const { return 0; } bool getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const; @@ -225,8 +233,10 @@ struct Arch : BaseCtx DecalXY getGroupDecal(GroupId group) const; bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const; - // Get the port class, also setting clockPort if applicable - TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockPort) const; + // Get the port class, also setting clockInfoCount to the number of TimingClockingInfos associated with a port + TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const; + // Get the TimingClockingInfo of a port + TimingClockingInfo getPortClockingInfo(const CellInfo *cell, IdString port, int index) const; bool isValidBelForCell(CellInfo *cell, BelId bel) const; bool isBelLocationValid(BelId bel) const; diff --git a/gui/designwidget.cc b/gui/designwidget.cc index 9895cad1..235dd2cb 100644 --- a/gui/designwidget.cc +++ b/gui/designwidget.cc @@ -291,15 +291,16 @@ void DesignWidget::newContext(Context *ctx) { TreeModel::ElementXYRoot::ElementMap belMap; - for (const auto& bel : ctx->getBels()) { + for (const auto &bel : ctx->getBels()) { auto loc = ctx->getBelLocation(bel); belMap[std::pair(loc.x, loc.y)].push_back(bel); } auto belGetter = [](Context *ctx, BelId id) { return ctx->getBelName(id); }; getTreeByElementType(ElementType::BEL) - ->loadData(ctx, std::unique_ptr>( - new TreeModel::ElementXYRoot(ctx, belMap, belGetter, ElementType::BEL))); + ->loadData(ctx, + std::unique_ptr>( + new TreeModel::ElementXYRoot(ctx, belMap, belGetter, ElementType::BEL))); } { @@ -313,33 +314,37 @@ void DesignWidget::newContext(Context *ctx) } #endif #ifdef ARCH_ECP5 - for (const auto& wire : ctx->getWires()) { + for (const auto &wire : ctx->getWires()) { wireMap[std::pair(wire.location.x, wire.location.y)].push_back(wire); } #endif auto wireGetter = [](Context *ctx, WireId id) { return ctx->getWireName(id); }; getTreeByElementType(ElementType::WIRE) - ->loadData(ctx, std::unique_ptr>( - new TreeModel::ElementXYRoot(ctx, wireMap, wireGetter, ElementType::WIRE))); + ->loadData(ctx, + std::unique_ptr>(new TreeModel::ElementXYRoot( + ctx, wireMap, wireGetter, ElementType::WIRE))); } { TreeModel::ElementXYRoot::ElementMap pipMap; - for (const auto& pip : ctx->getPips()) { + for (const auto &pip : ctx->getPips()) { auto loc = ctx->getPipLocation(pip); pipMap[std::pair(loc.x, loc.y)].push_back(pip); } auto pipGetter = [](Context *ctx, PipId id) { return ctx->getPipName(id); }; getTreeByElementType(ElementType::PIP) - ->loadData(ctx, std::unique_ptr>( - new TreeModel::ElementXYRoot(ctx, pipMap, pipGetter, ElementType::PIP))); + ->loadData(ctx, + std::unique_ptr>( + new TreeModel::ElementXYRoot(ctx, pipMap, pipGetter, ElementType::PIP))); } getTreeByElementType(ElementType::CELL) - ->loadData(ctx, std::unique_ptr(new TreeModel::IdStringList(ElementType::CELL))); + ->loadData(ctx, + std::unique_ptr(new TreeModel::IdStringList(ElementType::CELL))); getTreeByElementType(ElementType::NET) - ->loadData(ctx, std::unique_ptr(new TreeModel::IdStringList(ElementType::NET))); + ->loadData(ctx, + std::unique_ptr(new TreeModel::IdStringList(ElementType::NET))); } updateTree(); } @@ -567,18 +572,18 @@ void DesignWidget::onSelectionChanged(int num, const QItemSelection &, const QIt std::move(d.begin(), d.end(), std::back_inserter(decals)); } } - + // Keep other tree seleciton only if Control is pressed if (num_selected > 1 && QApplication::keyboardModifiers().testFlag(Qt::ControlModifier) == true) { Q_EMIT selected(decals, false); return; } - + // For deselect and multiple select just send all if (selectionModel[num]->selectedIndexes().size() != 1) { Q_EMIT selected(decals, false); return; - } + } QModelIndex index = selectionModel[num]->selectedIndexes().at(0); if (!index.isValid()) @@ -591,7 +596,8 @@ void DesignWidget::onSelectionChanged(int num, const QItemSelection &, const QIt // Clear other tab selections for (int i = 0; i <= getIndexByElementType(ElementType::GROUP); i++) - if (i!=num) selectionModel[i]->clearSelection(); + if (i != num) + selectionModel[i]->clearSelection(); addToHistory(num, index); diff --git a/gui/fpgaviewwidget.cc b/gui/fpgaviewwidget.cc index 4d410aca..3fba6bff 100644 --- a/gui/fpgaviewwidget.cc +++ b/gui/fpgaviewwidget.cc @@ -113,8 +113,8 @@ void FPGAViewWidget::initializeGL() } initializeOpenGLFunctions(); QtImGui::initialize(this); - glClearColor(colors_.background.red() / 255, colors_.background.green() / 255, - colors_.background.blue() / 255, 0.0); + glClearColor(colors_.background.red() / 255, colors_.background.green() / 255, colors_.background.blue() / 255, + 0.0); } float FPGAViewWidget::PickedElement::distance(Context *ctx, float wx, float wy) const @@ -510,8 +510,7 @@ void FPGAViewWidget::renderLines(void) rendererData_ = std::move(data); } } - if (gridChanged) - { + if (gridChanged) { QMutexLocker locker(&rendererDataLock_); rendererData_->gfxGrid.clear(); // Render grid. @@ -520,7 +519,7 @@ void FPGAViewWidget::renderLines(void) } for (float i = 0.0f; i < 1.0f * ctx_->getGridDimY() + 1; i += 1.0f) { PolyLine(0.0f, i, 1.0f * ctx_->getGridDimX(), i).build(rendererData_->gfxGrid); - } + } rendererData_->gfxGrid.last_render++; } if (highlightedOrSelectedChanged) { @@ -556,11 +555,11 @@ void FPGAViewWidget::renderLines(void) } { - QMutexLocker lock(&rendererArgsLock_); + QMutexLocker lock(&rendererArgsLock_); if (rendererArgs_->zoomOutbound) { zoomOutbound(); - rendererArgs_->zoomOutbound = false; + rendererArgs_->zoomOutbound = false; } } } @@ -870,11 +869,8 @@ void FPGAViewWidget::update_vbos() { lineShader_.update_vbos(GraphicElement::STYLE_GRID, rendererData_->gfxGrid); - for (int style = GraphicElement::STYLE_FRAME; style - < GraphicElement::STYLE_HIGHLIGHTED0; - style++) { - lineShader_.update_vbos((enum GraphicElement::style_t)(style), - rendererData_->gfxByStyle[style]); + for (int style = GraphicElement::STYLE_FRAME; style < GraphicElement::STYLE_HIGHLIGHTED0; style++) { + lineShader_.update_vbos((enum GraphicElement::style_t)(style), rendererData_->gfxByStyle[style]); } for (int i = 0; i < 8; i++) { diff --git a/gui/quadtree.h b/gui/quadtree.h index 9fcddf73..a6c38a85 100644 --- a/gui/quadtree.h +++ b/gui/quadtree.h @@ -266,20 +266,20 @@ template class QuadTreeNode splitx_ = (bound_.x1_ - bound_.x0_) / 2 + bound_.x0_; splity_ = (bound_.y1_ - bound_.y0_) / 2 + bound_.y0_; // Create the new children. - children_ = decltype(children_)(new QuadTreeNode[4]{ - // Note: not using [NW] = QuadTreeNode because that seems to - // crash g++ 7.3.0. - /* NW */ QuadTreeNode(BoundingBox(bound_.x0_, bound_.y0_, splitx_, splity_), - depth_ + 1, max_elems_), - /* NE */ - QuadTreeNode(BoundingBox(splitx_, bound_.y0_, bound_.x1_, splity_), - depth_ + 1, max_elems_), - /* SW */ - QuadTreeNode(BoundingBox(bound_.x0_, splity_, splitx_, bound_.y1_), - depth_ + 1, max_elems_), - /* SE */ - QuadTreeNode(BoundingBox(splitx_, splity_, bound_.x1_, bound_.y1_), - depth_ + 1, max_elems_), + children_ = decltype(children_)(new QuadTreeNode[4] { + // Note: not using [NW] = QuadTreeNode because that seems to + // crash g++ 7.3.0. + /* NW */ QuadTreeNode(BoundingBox(bound_.x0_, bound_.y0_, splitx_, splity_), + depth_ + 1, max_elems_), + /* NE */ + QuadTreeNode(BoundingBox(splitx_, bound_.y0_, bound_.x1_, splity_), + depth_ + 1, max_elems_), + /* SW */ + QuadTreeNode(BoundingBox(bound_.x0_, splity_, splitx_, bound_.y1_), + depth_ + 1, max_elems_), + /* SE */ + QuadTreeNode(BoundingBox(splitx_, splity_, bound_.x1_, bound_.y1_), + depth_ + 1, max_elems_), }); // Move all elements to where they belong. auto it = elems_.begin(); diff --git a/ice40/arch.cc b/ice40/arch.cc index 47471bcd..02e5515b 100644 --- a/ice40/arch.cc +++ b/ice40/arch.cc @@ -284,6 +284,25 @@ std::vector Arch::getBelPins(BelId bel) const return ret; } +bool Arch::isBelLocked(BelId bel) const +{ + const BelConfigPOD *bel_config = nullptr; + for (int i = 0; i < chip_info->num_belcfgs; i++) { + if (chip_info->bel_config[i].bel_index == bel.index) { + bel_config = &chip_info->bel_config[i]; + break; + } + } + NPNR_ASSERT(bel_config != nullptr); + for (int i = 0; i < bel_config->num_entries; i++) { + if (strcmp("LOCKED", bel_config->entries[i].cbit_name.get())) + continue; + if ("LOCKED_" + archArgs().package == bel_config->entries[i].entry_name.get()) + return true; + } + return false; +} + // ----------------------------------------------------------------------- WireId Arch::getWireByName(IdString name) const @@ -856,8 +875,9 @@ bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort } // Get the port class, also setting clockPort to associated clock if applicable -TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockPort) const +TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const { + clockInfoCount = 0; if (cell->type == id_ICESTORM_LC) { if (port == id_CLK) return TMG_CLOCK_INPUT; @@ -870,13 +890,13 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id if (cell->lcInfo.inputCount == 0) return TMG_IGNORE; if (cell->lcInfo.dffEnable) { - clockPort = id_CLK; + clockInfoCount = 1; return TMG_REGISTER_OUTPUT; } else return TMG_COMB_OUTPUT; } else { if (cell->lcInfo.dffEnable) { - clockPort = id_CLK; + clockInfoCount = 1; return TMG_REGISTER_INPUT; } else return TMG_COMB_INPUT; @@ -886,23 +906,22 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id if (port == id_RCLK || port == id_WCLK) return TMG_CLOCK_INPUT; - if (port.str(this)[0] == 'R') - clockPort = id_RCLK; - else - clockPort = id_WCLK; + clockInfoCount = 1; if (cell->ports.at(port).type == PORT_OUT) return TMG_REGISTER_OUTPUT; else return TMG_REGISTER_INPUT; } else if (cell->type == id_ICESTORM_DSP || cell->type == id_ICESTORM_SPRAM) { - clockPort = id_CLK; - if (port == id_CLK) + if (port == id_CLK || port == id_CLOCK) return TMG_CLOCK_INPUT; - else if (cell->ports.at(port).type == PORT_OUT) - return TMG_REGISTER_OUTPUT; - else - return TMG_REGISTER_INPUT; + else { + clockInfoCount = 1; + if (cell->ports.at(port).type == PORT_OUT) + return TMG_REGISTER_OUTPUT; + else + return TMG_REGISTER_INPUT; + } } else if (cell->type == id_SB_IO) { if (port == id_D_IN_0 || port == id_D_IN_1) return TMG_STARTPOINT; @@ -927,10 +946,59 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id return TMG_COMB_INPUT; } else if (cell->type == id_SB_WARMBOOT) { return TMG_ENDPOINT; + } else if (cell->type == id_SB_RGBA_DRV) { + if (port == id_RGB0 || port == id_RGB1 || port == id_RGB2) + return TMG_IGNORE; + return TMG_ENDPOINT; } log_error("no timing info for port '%s' of cell type '%s'\n", port.c_str(this), cell->type.c_str(this)); } +TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port, int index) const +{ + TimingClockingInfo info; + if (cell->type == id_ICESTORM_LC) { + info.clock_port = id_CLK; + info.edge = cell->lcInfo.negClk ? FALLING_EDGE : RISING_EDGE; + if (port == id_O) { + bool has_clktoq = getCellDelay(cell, id_CLK, id_O, info.clockToQ); + NPNR_ASSERT(has_clktoq); + } else { + info.setup.delay = 100; + info.hold.delay = 0; + } + } else if (cell->type == id_ICESTORM_RAM) { + if (port.str(this)[0] == 'R') { + info.clock_port = id_RCLK; + info.edge = bool_or_default(cell->params, id("NEG_CLK_R")) ? FALLING_EDGE : RISING_EDGE; + } else { + info.clock_port = id_WCLK; + info.edge = bool_or_default(cell->params, id("NEG_CLK_W")) ? FALLING_EDGE : RISING_EDGE; + } + if (cell->ports.at(port).type == PORT_OUT) { + bool has_clktoq = getCellDelay(cell, info.clock_port, port, info.clockToQ); + NPNR_ASSERT(has_clktoq); + } else { + info.setup.delay = 100; + info.hold.delay = 0; + } + } else if (cell->type == id_ICESTORM_DSP || cell->type == id_ICESTORM_SPRAM) { + info.clock_port = cell->type == id_ICESTORM_SPRAM ? id_CLOCK : id_CLK; + info.edge = RISING_EDGE; + if (cell->ports.at(port).type == PORT_OUT) { + bool has_clktoq = getCellDelay(cell, info.clock_port, port, info.clockToQ); + if (!has_clktoq) + info.clockToQ.delay = 100; + } else { + info.setup.delay = 100; + info.hold.delay = 0; + } + } else { + NPNR_ASSERT_FALSE("unhandled cell type in getPortClockingInfo"); + } + return info; +} + bool Arch::isGlobalNet(const NetInfo *net) const { if (net == nullptr) @@ -980,6 +1048,9 @@ void Arch::assignCellInfo(CellInfo *cell) cell->lcInfo.inputCount++; } else if (cell->type == id_SB_IO) { cell->ioInfo.lvds = str_or_default(cell->params, id_IO_STANDARD, "SB_LVCMOS") == "SB_LVDS_INPUT"; + cell->ioInfo.global = bool_or_default(cell->attrs, this->id("GLOBAL")); + } else if (cell->type == id_SB_GB) { + cell->gbInfo.forPadIn = bool_or_default(cell->attrs, this->id("FOR_PAD_IN")); } } diff --git a/ice40/arch.h b/ice40/arch.h index f7d31b0e..e8c597c9 100644 --- a/ice40/arch.h +++ b/ice40/arch.h @@ -212,11 +212,26 @@ NPNR_PACKED_STRUCT(struct CellTimingPOD { RelPtr path_delays; }); +NPNR_PACKED_STRUCT(struct GlobalNetworkInfoPOD { + uint8_t gb_x; + uint8_t gb_y; + + uint8_t pi_gb_x; + uint8_t pi_gb_y; + uint8_t pi_gb_pio; + + uint8_t pi_eb_bank; + uint16_t pi_eb_x; + uint16_t pi_eb_y; + + uint16_t pad; +}); + NPNR_PACKED_STRUCT(struct ChipInfoPOD { int32_t width, height; int32_t num_bels, num_wires, num_pips; int32_t num_switches, num_belcfgs, num_packages; - int32_t num_timing_cells; + int32_t num_timing_cells, num_global_networks; RelPtr bel_data; RelPtr wire_data; RelPtr pip_data; @@ -225,6 +240,7 @@ NPNR_PACKED_STRUCT(struct ChipInfoPOD { RelPtr bel_config; RelPtr packages_data; RelPtr cell_timing; + RelPtr global_network_info; RelPtr> tile_wire_names; }); @@ -510,6 +526,8 @@ struct Arch : BaseCtx PortType getBelPinType(BelId bel, IdString pin) const; std::vector getBelPins(BelId bel) const; + bool isBelLocked(BelId bel) const; + // ------------------------------------------------- WireId getWireByName(IdString name) const; @@ -796,6 +814,12 @@ struct Arch : BaseCtx delay_t getDelayEpsilon() const { return 20; } delay_t getRipupDelayPenalty() const { return 200; } float getDelayNS(delay_t v) const { return v * 0.001; } + DelayInfo getDelayFromNS(float ns) const + { + DelayInfo del; + del.delay = delay_t(ns * 1000); + return del; + } uint32_t getDelayChecksum(delay_t v) const { return v; } bool getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const; @@ -819,8 +843,10 @@ struct Arch : BaseCtx // Get the delay through a cell from one port to another, returning false // if no path exists bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const; - // Get the port class, also setting clockDomain if applicable - TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockDomain) const; + // Get the port class, also setting clockInfoCount to the number of TimingClockingInfos associated with a port + TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const; + // Get the TimingClockingInfo of a port + TimingClockingInfo getPortClockingInfo(const CellInfo *cell, IdString port, int index) const; // Return true if a port is a net bool isGlobalNet(const NetInfo *net) const; diff --git a/ice40/arch_place.cc b/ice40/arch_place.cc index c97b9c26..41f9b640 100644 --- a/ice40/arch_place.cc +++ b/ice40/arch_place.cc @@ -114,31 +114,30 @@ bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const // Find shared PLL by looking for driving bel siblings from D_IN_0 // that are a PLL clock output. auto wire = getBelPinWire(bel, id_D_IN_0); - IdString pll_bel_pin; - BelId pll_bel; for (auto pin : getWireBelPins(wire)) { if (pin.pin == id_PLLOUT_A || pin.pin == id_PLLOUT_B) { - pll_bel = pin.bel; - pll_bel_pin = pin.pin; - break; - } - } - // Is there a PLL that shares this IO buffer? - if (pll_bel.index != -1) { - auto pll_cell = getBoundBelCell(pll_bel); - // Is a PLL placed in this PLL bel? - if (pll_cell != nullptr) { - // Is the shared port driving a net? - auto pi = pll_cell->ports[pll_bel_pin]; - if (pi.net != nullptr) { - // Are we perhaps a PAD INPUT Bel that can be placed here? - if (pll_cell->attrs[id("BEL_PAD_INPUT")] == getBelName(bel).str(this)) { - return true; - } - return false; - } + // Is there a PLL there ? + auto pll_cell = getBoundBelCell(pin.bel); + if (pll_cell == nullptr) + break; + + // Is that port actually used ? + if ((pin.pin == id_PLLOUT_B) && !is_sb_pll40_dual(this, pll_cell)) + break; + + // Is that SB_IO used at an input ? + if ((cell->ports[id_D_IN_0].net == nullptr) && (cell->ports[id_D_IN_1].net == nullptr)) + break; + + // Are we perhaps a PAD INPUT Bel that can be placed here? + if (pll_cell->attrs[id("BEL_PAD_INPUT")] == getBelName(bel).str(this)) + return true; + + // Conflict + return false; } } + Loc ioLoc = getBelLocation(bel); Loc compLoc = ioLoc; compLoc.z = 1 - compLoc.z; @@ -162,6 +161,8 @@ bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const return getBelPackagePin(bel) != ""; } else if (cell->type == id_SB_GB) { + if (cell->gbInfo.forPadIn) + return true; NPNR_ASSERT(cell->ports.at(id_GLOBAL_BUFFER_OUTPUT).net != nullptr); const NetInfo *net = cell->ports.at(id_GLOBAL_BUFFER_OUTPUT).net; IdString glb_net = getWireName(getBelPinWire(bel, id_GLOBAL_BUFFER_OUTPUT)); diff --git a/ice40/arch_pybindings.cc b/ice40/arch_pybindings.cc index f1639ba6..3fafb1f6 100644 --- a/ice40/arch_pybindings.cc +++ b/ice40/arch_pybindings.cc @@ -140,6 +140,10 @@ void arch_wrap_python() "cells"); readonly_wrapper>::def_wrap(ctx_cls, "nets"); + + fn_wrapper_2a_v, + pass_through>::def_wrap(ctx_cls, "addClock"); + WRAP_RANGE(Bel, conv_to_str); WRAP_RANGE(Wire, conv_to_str); WRAP_RANGE(AllPip, conv_to_str); diff --git a/ice40/archdefs.h b/ice40/archdefs.h index b9614c07..2bffe667 100644 --- a/ice40/archdefs.h +++ b/ice40/archdefs.h @@ -150,8 +150,13 @@ struct ArchCellInfo struct { bool lvds; + bool global; // TODO: clk packing checks... } ioInfo; + struct + { + bool forPadIn; + } gbInfo; }; }; diff --git a/ice40/bitstream.cc b/ice40/bitstream.cc index 4cfed52d..ecb26753 100644 --- a/ice40/bitstream.cc +++ b/ice40/bitstream.cc @@ -1,1044 +1,1076 @@ -/* - * nextpnr -- Next Generation Place and Route - * - * Copyright (C) 2018 Clifford Wolf - * Copyright (C) 2018 David Shah - * Copyright (C) 2018 Serge Bazanski - * - * 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 "bitstream.h" -#include -#include -#include "cells.h" -#include "log.h" - -NEXTPNR_NAMESPACE_BEGIN - -inline TileType tile_at(const Context *ctx, int x, int y) -{ - return ctx->chip_info->tile_grid[y * ctx->chip_info->width + x]; -} - -const ConfigEntryPOD &find_config(const TileInfoPOD &tile, const std::string &name) -{ - for (int i = 0; i < tile.num_config_entries; i++) { - if (std::string(tile.entries[i].name.get()) == name) { - return tile.entries[i]; - } - } - NPNR_ASSERT_FALSE_STR("unable to find config bit " + name); -} - -std::tuple get_ieren(const BitstreamInfoPOD &bi, int8_t x, int8_t y, int8_t z) -{ - for (int i = 0; i < bi.num_ierens; i++) { - auto ie = bi.ierens[i]; - if (ie.iox == x && ie.ioy == y && ie.ioz == z) { - return std::make_tuple(ie.ierx, ie.iery, ie.ierz); - } - } - // No pin at this location - return std::make_tuple(-1, -1, -1); -}; - -bool get_config(const TileInfoPOD &ti, std::vector> &tile_cfg, const std::string &name, - int index = -1) -{ - const ConfigEntryPOD &cfg = find_config(ti, name); - if (index == -1) { - for (int i = 0; i < cfg.num_bits; i++) { - return tile_cfg.at(cfg.bits[i].row).at(cfg.bits[i].col); - } - } else { - return tile_cfg.at(cfg.bits[index].row).at(cfg.bits[index].col); - } - return false; -} - -void set_config(const TileInfoPOD &ti, std::vector> &tile_cfg, const std::string &name, bool value, - int index = -1) -{ - const ConfigEntryPOD &cfg = find_config(ti, name); - if (index == -1) { - for (int i = 0; i < cfg.num_bits; i++) { - int8_t &cbit = tile_cfg.at(cfg.bits[i].row).at(cfg.bits[i].col); - if (cbit && !value) - log_error("clearing already set config bit %s\n", name.c_str()); - cbit = value; - } - } else { - int8_t &cbit = tile_cfg.at(cfg.bits[index].row).at(cfg.bits[index].col); - cbit = value; - if (cbit && !value) - log_error("clearing already set config bit %s[%d]\n", name.c_str(), index); - } -} - -// Set an IE_{EN,REN} logical bit in a tile config. Logical means enabled. -// On {HX,LP}1K devices these bits are active low, so we need to invert them. -void set_ie_bit_logical(const Context *ctx, const TileInfoPOD &ti, std::vector> &tile_cfg, - const std::string &name, bool value) -{ - if (ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K) { - set_config(ti, tile_cfg, name, !value); - } else { - set_config(ti, tile_cfg, name, value); - } -} - -int get_param_or_def(const CellInfo *cell, const IdString param, int defval = 0) -{ - auto found = cell->params.find(param); - if (found != cell->params.end()) - return std::stoi(found->second); - else - return defval; -} - -std::string get_param_str_or_def(const CellInfo *cell, const IdString param, std::string defval = "") -{ - auto found = cell->params.find(param); - if (found != cell->params.end()) - return found->second; - else - return defval; -} - -char get_hexdigit(int i) { return std::string("0123456789ABCDEF").at(i); } - -static const BelConfigPOD &get_ec_config(const ChipInfoPOD *chip, BelId bel) -{ - for (int i = 0; i < chip->num_belcfgs; i++) { - if (chip->bel_config[i].bel_index == bel.index) - return chip->bel_config[i]; - } - NPNR_ASSERT_FALSE("failed to find bel config"); -} - -typedef std::vector>>> chipconfig_t; - -static void set_ec_cbit(chipconfig_t &config, const Context *ctx, const BelConfigPOD &cell_cbits, std::string name, - bool value, std::string prefix) -{ - const ChipInfoPOD *chip = ctx->chip_info; - - for (int i = 0; i < cell_cbits.num_entries; i++) { - const auto &cbit = cell_cbits.entries[i]; - if (cbit.entry_name.get() == name) { - const auto &ti = chip->bits_info->tiles_nonrouting[tile_at(ctx, cbit.x, cbit.y)]; - set_config(ti, config.at(cbit.y).at(cbit.x), prefix + cbit.cbit_name.get(), value); - return; - } - } - NPNR_ASSERT_FALSE_STR("failed to config extra cell config bit " + name); -} - -void configure_extra_cell(chipconfig_t &config, const Context *ctx, CellInfo *cell, - const std::vector> ¶ms, bool string_style, std::string prefix) -{ - const ChipInfoPOD *chip = ctx->chip_info; - const auto &bc = get_ec_config(chip, cell->bel); - for (auto p : params) { - std::vector value; - if (string_style) { - // Lattice's weird string style params, not sure if - // prefixes other than 0b should be supported, only 0b features in docs - std::string raw = get_param_str_or_def(cell, ctx->id(p.first), "0b0"); - assert(raw.substr(0, 2) == "0b"); - raw = raw.substr(2); - value.resize(raw.length()); - for (int i = 0; i < (int)raw.length(); i++) { - if (raw[i] == '1') { - value[(raw.length() - 1) - i] = 1; - } else { - assert(raw[i] == '0'); - value[(raw.length() - 1) - i] = 0; - } - } - } else { - int ival = get_param_or_def(cell, ctx->id(p.first), 0); - - for (int i = 0; i < p.second; i++) - value.push_back((ival >> i) & 0x1); - } - - value.resize(p.second); - if (p.second == 1) { - set_ec_cbit(config, ctx, bc, p.first, value.at(0), prefix); - } else { - for (int i = 0; i < p.second; i++) { - set_ec_cbit(config, ctx, bc, p.first + "_" + std::to_string(i), value.at(i), prefix); - } - } - } -} - -std::string tagTileType(TileType &tile) -{ - if (tile == TILE_NONE) - return ""; - switch (tile) { - case TILE_LOGIC: - return ".logic_tile"; - break; - case TILE_IO: - return ".io_tile"; - break; - case TILE_RAMB: - return ".ramb_tile"; - break; - case TILE_RAMT: - return ".ramt_tile"; - break; - case TILE_DSP0: - return ".dsp0_tile"; - break; - case TILE_DSP1: - return ".dsp1_tile"; - break; - case TILE_DSP2: - return ".dsp2_tile"; - break; - case TILE_DSP3: - return ".dsp3_tile"; - break; - case TILE_IPCON: - return ".ipcon_tile"; - break; - default: - NPNR_ASSERT(false); - } -} - -static BelPin get_one_bel_pin(const Context *ctx, WireId wire) -{ - auto pins = ctx->getWireBelPins(wire); - NPNR_ASSERT(pins.begin() != pins.end()); - return *pins.begin(); -} - -// Permute LUT init value given map (LUT input -> ext input) -unsigned permute_lut(unsigned orig_init, const std::unordered_map &input_permute) -{ - unsigned new_init = 0; - - for (int i = 0; i < 16; i++) { - int permute_address = 0; - for (int j = 0; j < 4; j++) { - if ((i >> j) & 0x1) - permute_address |= (1 << input_permute.at(j)); - } - if ((orig_init >> i) & 0x1) { - new_init |= (1 << permute_address); - } - } - - return new_init; -} - -void write_asc(const Context *ctx, std::ostream &out) -{ - - static const std::vector lut_perm = { - 4, 14, 15, 5, 6, 16, 17, 7, 3, 13, 12, 2, 1, 11, 10, 0, - }; - - // [y][x][row][col] - const ChipInfoPOD &ci = *ctx->chip_info; - const BitstreamInfoPOD &bi = *ci.bits_info; - chipconfig_t config; - config.resize(ci.height); - for (int y = 0; y < ci.height; y++) { - config.at(y).resize(ci.width); - for (int x = 0; x < ci.width; x++) { - TileType tile = tile_at(ctx, x, y); - int rows = bi.tiles_nonrouting[tile].rows; - int cols = bi.tiles_nonrouting[tile].cols; - config.at(y).at(x).resize(rows, std::vector(cols)); - } - } - out << ".comment from next-pnr" << std::endl; - - switch (ctx->args.type) { - case ArchArgs::LP384: - out << ".device 384" << std::endl; - break; - case ArchArgs::HX1K: - case ArchArgs::LP1K: - out << ".device 1k" << std::endl; - break; - case ArchArgs::HX8K: - case ArchArgs::LP8K: - out << ".device 8k" << std::endl; - break; - case ArchArgs::UP5K: - out << ".device 5k" << std::endl; - break; - default: - NPNR_ASSERT_FALSE("unsupported device type\n"); - } - // Set pips - for (auto pip : ctx->getPips()) { - if (ctx->pip_to_net[pip.index] != nullptr) { - const PipInfoPOD &pi = ci.pip_data[pip.index]; - const SwitchInfoPOD &swi = bi.switches[pi.switch_index]; - int sw_bel_idx = swi.bel; - if (sw_bel_idx >= 0) { - const BelInfoPOD &beli = ci.bel_data[sw_bel_idx]; - const TileInfoPOD &ti = bi.tiles_nonrouting[TILE_LOGIC]; - BelId sw_bel; - sw_bel.index = sw_bel_idx; - NPNR_ASSERT(ctx->getBelType(sw_bel) == id_ICESTORM_LC); - - if (ci.wire_data[ctx->getPipDstWire(pip).index].type == WireInfoPOD::WIRE_TYPE_LUTFF_IN_LUT) - continue; // Permutation pips - BelPin output = get_one_bel_pin(ctx, ctx->getPipDstWire(pip)); - NPNR_ASSERT(output.bel == sw_bel && output.pin == id_O); - unsigned lut_init; - - WireId permWire; - for (auto permPip : ctx->getPipsUphill(ctx->getPipSrcWire(pip))) { - if (ctx->getBoundPipNet(permPip) != nullptr) { - permWire = ctx->getPipSrcWire(permPip); - } - } - NPNR_ASSERT(permWire != WireId()); - std::string dName = ci.wire_data[permWire.index].name.get(); - - switch (dName.back()) { - case '0': - lut_init = 2; - break; - case '1': - lut_init = 4; - break; - case '2': - lut_init = 16; - break; - case '3': - lut_init = 256; - break; - default: - NPNR_ASSERT_FALSE("bad feedthru LUT input"); - } - std::vector lc(20, false); - for (int i = 0; i < 16; i++) { - if ((lut_init >> i) & 0x1) - lc.at(lut_perm.at(i)) = true; - } - - for (int i = 0; i < 20; i++) - set_config(ti, config.at(beli.y).at(beli.x), "LC_" + std::to_string(beli.z), lc.at(i), i); - } else { - for (int i = 0; i < swi.num_bits; i++) { - bool val = (pi.switch_mask & (1 << ((swi.num_bits - 1) - i))) != 0; - int8_t &cbit = config.at(swi.y).at(swi.x).at(swi.cbits[i].row).at(swi.cbits[i].col); - if (bool(cbit) != 0) - NPNR_ASSERT(false); - cbit = val; - } - } - } - } - - std::unordered_set sb_io_used_by_pll; - std::unordered_set sb_io_used_by_io; - - // Set logic cell config - for (auto &cell : ctx->cells) { - - BelId bel = cell.second.get()->bel; - if (bel == BelId()) { - std::cout << "Found unplaced cell " << cell.first.str(ctx) << " while generating bitstream!" << std::endl; - continue; - } - if (cell.second->type == ctx->id("ICESTORM_LC")) { - const BelInfoPOD &beli = ci.bel_data[bel.index]; - int x = beli.x, y = beli.y, z = beli.z; - const TileInfoPOD &ti = bi.tiles_nonrouting[TILE_LOGIC]; - unsigned lut_init = get_param_or_def(cell.second.get(), ctx->id("LUT_INIT")); - bool neg_clk = get_param_or_def(cell.second.get(), ctx->id("NEG_CLK")); - bool dff_enable = get_param_or_def(cell.second.get(), ctx->id("DFF_ENABLE")); - bool async_sr = get_param_or_def(cell.second.get(), ctx->id("ASYNC_SR")); - bool set_noreset = get_param_or_def(cell.second.get(), ctx->id("SET_NORESET")); - bool carry_enable = get_param_or_def(cell.second.get(), ctx->id("CARRY_ENABLE")); - std::vector lc(20, false); - - // Discover permutation - std::unordered_map input_perm; - std::set unused; - for (int i = 0; i < 4; i++) - unused.insert(i); - for (int i = 0; i < 4; i++) { - WireId lut_wire = ctx->getBelPinWire(bel, IdString(ID_I0 + i)); - for (auto pip : ctx->getPipsUphill(lut_wire)) { - if (ctx->getBoundPipNet(pip) != nullptr) { - std::string name = ci.wire_data[ctx->getPipSrcWire(pip).index].name.get(); - switch (name.back()) { - case '0': - input_perm[i] = 0; - unused.erase(0); - break; - case '1': - input_perm[i] = 1; - unused.erase(1); - break; - case '2': - input_perm[i] = 2; - unused.erase(2); - break; - case '3': - input_perm[i] = 3; - unused.erase(3); - break; - default: - NPNR_ASSERT_FALSE("failed to determine LUT permutation"); - } - break; - } - } - } - for (int i = 0; i < 4; i++) { - if (!input_perm.count(i)) { - NPNR_ASSERT(!unused.empty()); - input_perm[i] = *(unused.begin()); - unused.erase(input_perm[i]); - } - } - lut_init = permute_lut(lut_init, input_perm); - for (int i = 0; i < 16; i++) { - if ((lut_init >> i) & 0x1) - lc.at(lut_perm.at(i)) = true; - } - lc.at(8) = carry_enable; - lc.at(9) = dff_enable; - lc.at(18) = set_noreset; - lc.at(19) = async_sr; - - for (int i = 0; i < 20; i++) - set_config(ti, config.at(y).at(x), "LC_" + std::to_string(z), lc.at(i), i); - if (dff_enable) - set_config(ti, config.at(y).at(x), "NegClk", neg_clk); - - bool carry_const = get_param_or_def(cell.second.get(), ctx->id("CIN_CONST")); - bool carry_set = get_param_or_def(cell.second.get(), ctx->id("CIN_SET")); - if (carry_const) { - if (!ctx->force) - NPNR_ASSERT(z == 0); - set_config(ti, config.at(y).at(x), "CarryInSet", carry_set); - } - } else if (cell.second->type == ctx->id("SB_IO")) { - const BelInfoPOD &beli = ci.bel_data[bel.index]; - int x = beli.x, y = beli.y, z = beli.z; - sb_io_used_by_io.insert(Loc(x, y, z)); - const TileInfoPOD &ti = bi.tiles_nonrouting[TILE_IO]; - unsigned pin_type = get_param_or_def(cell.second.get(), ctx->id("PIN_TYPE")); - bool neg_trigger = get_param_or_def(cell.second.get(), ctx->id("NEG_TRIGGER")); - bool pullup = get_param_or_def(cell.second.get(), ctx->id("PULLUP")); - bool lvds = get_param_str_or_def(cell.second.get(), ctx->id("IO_STANDARD")) == "SB_LVDS_INPUT"; - - for (int i = 0; i < 6; i++) { - bool val = (pin_type >> i) & 0x01; - set_config(ti, config.at(y).at(x), "IOB_" + std::to_string(z) + ".PINTYPE_" + std::to_string(i), val); - } - set_config(ti, config.at(y).at(x), "NegClk", neg_trigger); - auto ieren = get_ieren(bi, x, y, z); - int iex, iey, iez; - std::tie(iex, iey, iez) = ieren; - NPNR_ASSERT(iez != -1); - - bool input_en; - if (lvds) { - input_en = false; - pullup = false; - } else { - if ((ctx->wire_to_net[ctx->getBelPinWire(bel, id_D_IN_0).index] != nullptr) || - (ctx->wire_to_net[ctx->getBelPinWire(bel, id_D_IN_1).index] != nullptr)) { - input_en = true; - } else { - input_en = false; - } - } - - if (ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K) { - set_config(ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), !input_en); - set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), !pullup); - } else { - set_config(ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), input_en); - set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), !pullup); - } - - if (ctx->args.type == ArchArgs::UP5K) { - if (iez == 0) { - set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_39", !pullup); - } else if (iez == 1) { - set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_35", !pullup); - } - } - - if (lvds) { - NPNR_ASSERT(z == 0); - set_config(ti, config.at(y).at(x), "IoCtrl.LVDS", true); - // Set comp IO config - auto comp_ieren = get_ieren(bi, x, y, 1); - int ciex, ciey, ciez; - std::tie(ciex, ciey, ciez) = comp_ieren; - - if (ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K) { - set_config(ti, config.at(ciey).at(ciex), "IoCtrl.IE_" + std::to_string(ciez), !input_en); - set_config(ti, config.at(ciey).at(ciex), "IoCtrl.REN_" + std::to_string(ciez), !pullup); - } else { - set_config(ti, config.at(ciey).at(ciex), "IoCtrl.IE_" + std::to_string(ciez), input_en); - set_config(ti, config.at(ciey).at(ciex), "IoCtrl.REN_" + std::to_string(ciez), !pullup); - } - - if (ctx->args.type == ArchArgs::UP5K) { - if (ciez == 0) { - set_config(ti, config.at(ciey).at(ciex), "IoCtrl.cf_bit_39", !pullup); - } else if (iez == 1) { - set_config(ti, config.at(ciey).at(ciex), "IoCtrl.cf_bit_35", !pullup); - } - } - } - } else if (cell.second->type == ctx->id("SB_GB")) { - // no cell config bits - } else if (cell.second->type == ctx->id("ICESTORM_RAM")) { - const BelInfoPOD &beli = ci.bel_data[bel.index]; - int x = beli.x, y = beli.y; - const TileInfoPOD &ti_ramt = bi.tiles_nonrouting[TILE_RAMT]; - const TileInfoPOD &ti_ramb = bi.tiles_nonrouting[TILE_RAMB]; - if (!(ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K)) { - set_config(ti_ramb, config.at(y).at(x), "RamConfig.PowerUp", true); - } - bool negclk_r = get_param_or_def(cell.second.get(), ctx->id("NEG_CLK_R")); - bool negclk_w = get_param_or_def(cell.second.get(), ctx->id("NEG_CLK_W")); - int write_mode = get_param_or_def(cell.second.get(), ctx->id("WRITE_MODE")); - int read_mode = get_param_or_def(cell.second.get(), ctx->id("READ_MODE")); - set_config(ti_ramb, config.at(y).at(x), "NegClk", negclk_w); - set_config(ti_ramt, config.at(y + 1).at(x), "NegClk", negclk_r); - - set_config(ti_ramt, config.at(y + 1).at(x), "RamConfig.CBIT_0", write_mode & 0x1); - set_config(ti_ramt, config.at(y + 1).at(x), "RamConfig.CBIT_1", write_mode & 0x2); - set_config(ti_ramt, config.at(y + 1).at(x), "RamConfig.CBIT_2", read_mode & 0x1); - set_config(ti_ramt, config.at(y + 1).at(x), "RamConfig.CBIT_3", read_mode & 0x2); - } else if (cell.second->type == ctx->id("SB_WARMBOOT") || cell.second->type == ctx->id("ICESTORM_LFOSC")) { - // No config needed - } else if (cell.second->type == ctx->id("ICESTORM_SPRAM")) { - const BelInfoPOD &beli = ci.bel_data[bel.index]; - int x = beli.x, y = beli.y, z = beli.z; - NPNR_ASSERT(ctx->args.type == ArchArgs::UP5K); - if (x == 0 && y == 0) { - const TileInfoPOD &ti_ipcon = bi.tiles_nonrouting[TILE_IPCON]; - if (z == 1) { - set_config(ti_ipcon, config.at(1).at(0), "IpConfig.CBIT_0", true); - } else if (z == 2) { - set_config(ti_ipcon, config.at(1).at(0), "IpConfig.CBIT_1", true); - } else { - NPNR_ASSERT(false); - } - } else if (x == 25 && y == 0) { - const TileInfoPOD &ti_ipcon = bi.tiles_nonrouting[TILE_IPCON]; - if (z == 3) { - set_config(ti_ipcon, config.at(1).at(25), "IpConfig.CBIT_0", true); - } else if (z == 4) { - set_config(ti_ipcon, config.at(1).at(25), "IpConfig.CBIT_1", true); - } else { - NPNR_ASSERT(false); - } - } - } else if (cell.second->type == ctx->id("ICESTORM_DSP")) { - const std::vector> mac16_params = {{"C_REG", 1}, - {"A_REG", 1}, - {"B_REG", 1}, - {"D_REG", 1}, - {"TOP_8x8_MULT_REG", 1}, - {"BOT_8x8_MULT_REG", 1}, - {"PIPELINE_16x16_MULT_REG1", 1}, - {"PIPELINE_16x16_MULT_REG2", 1}, - {"TOPOUTPUT_SELECT", 2}, - {"TOPADDSUB_LOWERINPUT", 2}, - {"TOPADDSUB_UPPERINPUT", 1}, - {"TOPADDSUB_CARRYSELECT", 2}, - {"BOTOUTPUT_SELECT", 2}, - {"BOTADDSUB_LOWERINPUT", 2}, - {"BOTADDSUB_UPPERINPUT", 1}, - {"BOTADDSUB_CARRYSELECT", 2}, - {"MODE_8x8", 1}, - {"A_SIGNED", 1}, - {"B_SIGNED", 1}}; - configure_extra_cell(config, ctx, cell.second.get(), mac16_params, false, std::string("IpConfig.")); - } else if (cell.second->type == ctx->id("ICESTORM_HFOSC")) { - const std::vector> hfosc_params = {{"CLKHF_DIV", 2}, {"TRIM_EN", 1}}; - configure_extra_cell(config, ctx, cell.second.get(), hfosc_params, true, std::string("IpConfig.")); - - } else if (cell.second->type == ctx->id("ICESTORM_PLL")) { - const std::vector> pll_params = {{"DELAY_ADJMODE_FB", 1}, - {"DELAY_ADJMODE_REL", 1}, - {"DIVF", 7}, - {"DIVQ", 3}, - {"DIVR", 4}, - {"FDA_FEEDBACK", 4}, - {"FDA_RELATIVE", 4}, - {"FEEDBACK_PATH", 3}, - {"FILTER_RANGE", 3}, - {"PLLOUT_SELECT_A", 2}, - {"PLLOUT_SELECT_B", 2}, - {"PLLTYPE", 3}, - {"SHIFTREG_DIV_MODE", 1}, - {"TEST_MODE", 1}}; - configure_extra_cell(config, ctx, cell.second.get(), pll_params, false, std::string("PLL.")); - - // Configure the SB_IOs that the clock outputs are going through. - for (auto &port : cell.second->ports) { - // If this port is not a PLLOUT port, ignore it. - if (port.second.name != ctx->id("PLLOUT_A") && port.second.name != ctx->id("PLLOUT_B")) - continue; - - // If the output is not driving any net, ignore it. - if (port.second.net == nullptr) - continue; - - // Get IO Bel that this PLL port goes through by finding sibling - // Bel driving the same wire via PIN_D_IN_0. - auto wire = ctx->getBelPinWire(cell.second->bel, port.second.name); - BelId io_bel; - for (auto pin : ctx->getWireBelPins(wire)) { - if (pin.pin == id_D_IN_0) { - io_bel = pin.bel; - break; - } - } - NPNR_ASSERT(io_bel.index != -1); - auto io_bel_loc = ctx->getBelLocation(io_bel); - - // Check that this SB_IO is either unused or just used as an output. - if (sb_io_used_by_io.count(io_bel_loc)) { - log_error("SB_IO '%s' already in use, cannot route PLL through\n", ctx->getBelName(bel).c_str(ctx)); - } - sb_io_used_by_pll.insert(io_bel_loc); - - // Get IE/REN config location (cf. http://www.clifford.at/icestorm/io_tile.html) - auto ieren = get_ieren(bi, io_bel_loc.x, io_bel_loc.y, io_bel_loc.z); - int iex, iey, iez; - std::tie(iex, iey, iez) = ieren; - NPNR_ASSERT(iez != -1); - - // Write config. - const TileInfoPOD &ti = bi.tiles_nonrouting[TILE_IO]; - // Enable input buffer and disable pull-up resistor in block - // (this is used by the PLL). - set_ie_bit_logical(ctx, ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), true); - set_ie_bit_logical(ctx, ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), false); - // PINTYPE[0] passes the PLL through to the fabric. - set_config(ti, config.at(io_bel_loc.y).at(io_bel_loc.x), - "IOB_" + std::to_string(io_bel_loc.z) + ".PINTYPE_0", true); - } - - } else { - NPNR_ASSERT(false); - } - } - // Set config bits in unused IO and RAM - for (auto bel : ctx->getBels()) { - if (ctx->bel_to_cell[bel.index] == nullptr && ctx->getBelType(bel) == id_SB_IO) { - const TileInfoPOD &ti = bi.tiles_nonrouting[TILE_IO]; - const BelInfoPOD &beli = ci.bel_data[bel.index]; - int x = beli.x, y = beli.y, z = beli.z; - if (sb_io_used_by_pll.count(Loc(x, y, z))) { - continue; - } - - auto ieren = get_ieren(bi, x, y, z); - int iex, iey, iez; - std::tie(iex, iey, iez) = ieren; - if (iez != -1) { - // IO is not actually unused if part of an LVDS pair - if (z == 1) { - BelId lvds0 = ctx->getBelByLocation(Loc{x, y, 0}); - const CellInfo *lvds0cell = ctx->getBoundBelCell(lvds0); - if (lvds0cell != nullptr && lvds0cell->ioInfo.lvds) - continue; - } - set_ie_bit_logical(ctx, ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), true); - set_ie_bit_logical(ctx, ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), false); - } - } else if (ctx->bel_to_cell[bel.index] == nullptr && ctx->getBelType(bel) == id_ICESTORM_RAM) { - const BelInfoPOD &beli = ci.bel_data[bel.index]; - int x = beli.x, y = beli.y; - const TileInfoPOD &ti = bi.tiles_nonrouting[TILE_RAMB]; - if ((ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K)) { - set_config(ti, config.at(y).at(x), "RamConfig.PowerUp", true); - } - } - } - - // Set other config bits - for (int y = 0; y < ci.height; y++) { - for (int x = 0; x < ci.width; x++) { - TileType tile = tile_at(ctx, x, y); - const TileInfoPOD &ti = bi.tiles_nonrouting[tile]; - - // set all ColBufCtrl bits (FIXME) - bool setColBufCtrl = true; - if (ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K) { - if (tile == TILE_RAMB || tile == TILE_RAMT) { - setColBufCtrl = (y == 3 || y == 5 || y == 11 || y == 13); - } else { - setColBufCtrl = (y == 4 || y == 5 || y == 12 || y == 13); - } - } else if (ctx->args.type == ArchArgs::LP8K || ctx->args.type == ArchArgs::HX8K) { - setColBufCtrl = (y == 8 || y == 9 || y == 24 || y == 25); - } else if (ctx->args.type == ArchArgs::UP5K) { - setColBufCtrl = (y == 4 || y == 5 || y == 14 || y == 15 || y == 26 || y == 27); - } else if (ctx->args.type == ArchArgs::LP384) { - setColBufCtrl = false; - } - if (setColBufCtrl) { - set_config(ti, config.at(y).at(x), "ColBufCtrl.glb_netwk_0", true); - set_config(ti, config.at(y).at(x), "ColBufCtrl.glb_netwk_1", true); - set_config(ti, config.at(y).at(x), "ColBufCtrl.glb_netwk_2", true); - set_config(ti, config.at(y).at(x), "ColBufCtrl.glb_netwk_3", true); - set_config(ti, config.at(y).at(x), "ColBufCtrl.glb_netwk_4", true); - set_config(ti, config.at(y).at(x), "ColBufCtrl.glb_netwk_5", true); - set_config(ti, config.at(y).at(x), "ColBufCtrl.glb_netwk_6", true); - set_config(ti, config.at(y).at(x), "ColBufCtrl.glb_netwk_7", true); - } - - // Weird UltraPlus bits - if (tile == TILE_DSP0 || tile == TILE_DSP1 || tile == TILE_DSP2 || tile == TILE_DSP3 || - tile == TILE_IPCON) { - if (ctx->args.type == ArchArgs::UP5K && x == 25 && y == 14) { - // Mystery bits not set in this one tile - } else { - for (int lc_idx = 0; lc_idx < 8; lc_idx++) { - static const std::vector ip_dsp_lut_perm = { - 4, 14, 15, 5, 6, 16, 17, 7, 3, 13, 12, 2, 1, 11, 10, 0, - }; - for (int i = 0; i < 16; i++) - set_config(ti, config.at(y).at(x), "LC_" + std::to_string(lc_idx), ((i % 8) >= 4), - ip_dsp_lut_perm.at(i)); - if (tile == TILE_IPCON) - set_config(ti, config.at(y).at(x), - "Cascade.IPCON_LC0" + std::to_string(lc_idx) + "_inmux02_5", true); - else - set_config(ti, config.at(y).at(x), - "Cascade.MULT" + std::to_string(int(tile - TILE_DSP0)) + "_LC0" + - std::to_string(lc_idx) + "_inmux02_5", - true); - } - } - } - } - } - - // Write config out - for (int y = 0; y < ci.height; y++) { - for (int x = 0; x < ci.width; x++) { - TileType tile = tile_at(ctx, x, y); - if (tile == TILE_NONE) - continue; - out << tagTileType(tile); - out << " " << x << " " << y << std::endl; - for (auto row : config.at(y).at(x)) { - for (auto col : row) { - if (col == 1) - out << "1"; - else - out << "0"; - } - out << std::endl; - } - out << std::endl; - } - } - - // Write RAM init data - for (auto &cell : ctx->cells) { - if (cell.second->bel != BelId()) { - if (cell.second->type == ctx->id("ICESTORM_RAM")) { - const BelInfoPOD &beli = ci.bel_data[cell.second->bel.index]; - int x = beli.x, y = beli.y; - out << ".ram_data " << x << " " << y << std::endl; - for (int w = 0; w < 16; w++) { - std::vector bits(256); - std::string init = - get_param_str_or_def(cell.second.get(), ctx->id(std::string("INIT_") + get_hexdigit(w))); - NPNR_ASSERT(init != ""); - for (size_t i = 0; i < init.size(); i++) { - bool val = (init.at((init.size() - 1) - i) == '1'); - bits.at(i) = val; - } - for (int i = bits.size() - 4; i >= 0; i -= 4) { - int c = bits.at(i) + (bits.at(i + 1) << 1) + (bits.at(i + 2) << 2) + (bits.at(i + 3) << 3); - out << char(std::tolower(get_hexdigit(c))); - } - out << std::endl; - } - out << std::endl; - } - } - } - - // Write symbols - // const bool write_symbols = 1; - for (auto wire : ctx->getWires()) { - NetInfo *net = ctx->getBoundWireNet(wire); - if (net != nullptr) - out << ".sym " << wire.index << " " << net->name.str(ctx) << std::endl; - } -} - -void read_config(Context *ctx, std::istream &in, chipconfig_t &config) -{ - constexpr size_t line_buf_size = 65536; - char buffer[line_buf_size]; - int tile_x = -1, tile_y = -1, line_nr = -1; - - while (1) { - in.getline(buffer, line_buf_size); - if (buffer[0] == '.') { - line_nr = -1; - const char *tok = strtok(buffer, " \t\r\n"); - - if (!strcmp(tok, ".device")) { - std::string config_device = strtok(nullptr, " \t\r\n"); - std::string expected; - switch (ctx->args.type) { - case ArchArgs::LP384: - expected = "384"; - break; - case ArchArgs::HX1K: - case ArchArgs::LP1K: - expected = "1k"; - break; - case ArchArgs::HX8K: - case ArchArgs::LP8K: - expected = "8k"; - break; - case ArchArgs::UP5K: - expected = "5k"; - break; - default: - log_error("unsupported device type\n"); - } - if (expected != config_device) - log_error("device type does not match\n"); - } else if (!strcmp(tok, ".io_tile") || !strcmp(tok, ".logic_tile") || !strcmp(tok, ".ramb_tile") || - !strcmp(tok, ".ramt_tile") || !strcmp(tok, ".ipcon_tile") || !strcmp(tok, ".dsp0_tile") || - !strcmp(tok, ".dsp1_tile") || !strcmp(tok, ".dsp2_tile") || !strcmp(tok, ".dsp3_tile")) { - line_nr = 0; - tile_x = atoi(strtok(nullptr, " \t\r\n")); - tile_y = atoi(strtok(nullptr, " \t\r\n")); - - TileType tile = tile_at(ctx, tile_x, tile_y); - if (tok != tagTileType(tile)) - log_error("Wrong tile type for specified position\n"); - - } else if (!strcmp(tok, ".extra_bit")) { - /* - int b = atoi(strtok(nullptr, " \t\r\n")); - int x = atoi(strtok(nullptr, " \t\r\n")); - int y = atoi(strtok(nullptr, " \t\r\n")); - std::tuple key(b, x, y); - extra_bits.insert(key); - */ - } else if (!strcmp(tok, ".sym")) { - int wireIndex = atoi(strtok(nullptr, " \t\r\n")); - const char *name = strtok(nullptr, " \t\r\n"); - - IdString netName = ctx->id(name); - - if (ctx->nets.find(netName) == ctx->nets.end()) { - std::unique_ptr created_net = std::unique_ptr(new NetInfo); - created_net->name = netName; - ctx->nets[netName] = std::move(created_net); - } - - WireId wire; - wire.index = wireIndex; - ctx->bindWire(wire, ctx->nets.at(netName).get(), STRENGTH_WEAK); - } - } else if (line_nr >= 0 && strlen(buffer) > 0) { - if (line_nr > int(config.at(tile_y).at(tile_x).size() - 1)) - log_error("Invalid data in input asc file"); - for (int i = 0; buffer[i] == '0' || buffer[i] == '1'; i++) - config.at(tile_y).at(tile_x).at(line_nr).at(i) = (buffer[i] == '1') ? 1 : 0; - line_nr++; - } - if (in.eof()) - break; - } -} - -bool read_asc(Context *ctx, std::istream &in) -{ - try { - // [y][x][row][col] - const ChipInfoPOD &ci = *ctx->chip_info; - const BitstreamInfoPOD &bi = *ci.bits_info; - chipconfig_t config; - config.resize(ci.height); - for (int y = 0; y < ci.height; y++) { - config.at(y).resize(ci.width); - for (int x = 0; x < ci.width; x++) { - TileType tile = tile_at(ctx, x, y); - int rows = bi.tiles_nonrouting[tile].rows; - int cols = bi.tiles_nonrouting[tile].cols; - config.at(y).at(x).resize(rows, std::vector(cols)); - } - } - read_config(ctx, in, config); - - // Set pips - for (auto pip : ctx->getPips()) { - const PipInfoPOD &pi = ci.pip_data[pip.index]; - const SwitchInfoPOD &swi = bi.switches[pi.switch_index]; - bool isUsed = true; - for (int i = 0; i < swi.num_bits; i++) { - bool val = (pi.switch_mask & (1 << ((swi.num_bits - 1) - i))) != 0; - int8_t cbit = config.at(swi.y).at(swi.x).at(swi.cbits[i].row).at(swi.cbits[i].col); - isUsed &= !(bool(cbit) ^ val); - } - if (isUsed) { - NetInfo *net = ctx->wire_to_net[pi.dst]; - if (net != nullptr) { - WireId wire; - wire.index = pi.dst; - ctx->unbindWire(wire); - ctx->bindPip(pip, net, STRENGTH_WEAK); - } - } - } - for (auto bel : ctx->getBels()) { - if (ctx->getBelType(bel) == id_ICESTORM_LC) { - const TileInfoPOD &ti = bi.tiles_nonrouting[TILE_LOGIC]; - const BelInfoPOD &beli = ci.bel_data[bel.index]; - int x = beli.x, y = beli.y, z = beli.z; - std::vector lc(20, false); - bool isUsed = false; - for (int i = 0; i < 20; i++) { - lc.at(i) = get_config(ti, config.at(y).at(x), "LC_" + std::to_string(z), i); - isUsed |= lc.at(i); - } - bool neg_clk = get_config(ti, config.at(y).at(x), "NegClk"); - isUsed |= neg_clk; - bool carry_set = get_config(ti, config.at(y).at(x), "CarryInSet"); - isUsed |= carry_set; - - if (isUsed) { - std::unique_ptr created = create_ice_cell(ctx, ctx->id("ICESTORM_LC")); - IdString name = created->name; - ctx->cells[name] = std::move(created); - ctx->bindBel(bel, ctx->cells[name].get(), STRENGTH_WEAK); - // TODO: Add port mapping to nets and assign values of properties - } - } - if (ctx->getBelType(bel) == id_SB_IO) { - const TileInfoPOD &ti = bi.tiles_nonrouting[TILE_IO]; - const BelInfoPOD &beli = ci.bel_data[bel.index]; - int x = beli.x, y = beli.y, z = beli.z; - bool isUsed = false; - for (int i = 0; i < 6; i++) { - isUsed |= get_config(ti, config.at(y).at(x), - "IOB_" + std::to_string(z) + ".PINTYPE_" + std::to_string(i)); - } - bool neg_trigger = get_config(ti, config.at(y).at(x), "NegClk"); - isUsed |= neg_trigger; - - if (isUsed) { - std::unique_ptr created = create_ice_cell(ctx, ctx->id("SB_IO")); - IdString name = created->name; - ctx->cells[name] = std::move(created); - ctx->bindBel(bel, ctx->cells[name].get(), STRENGTH_WEAK); - // TODO: Add port mapping to nets and assign values of properties - } - } - } - // Add cells that are without change in initial state of configuration - for (auto &net : ctx->nets) { - for (auto w : net.second->wires) { - if (w.second.pip == PipId()) { - WireId wire = w.first; - for (auto belpin : ctx->getWireBelPins(wire)) { - - if (ctx->checkBelAvail(belpin.bel)) { - if (ctx->getBelType(belpin.bel) == id_ICESTORM_LC) { - std::unique_ptr created = create_ice_cell(ctx, ctx->id("ICESTORM_LC")); - IdString name = created->name; - ctx->cells[name] = std::move(created); - ctx->bindBel(belpin.bel, ctx->cells[name].get(), STRENGTH_WEAK); - // TODO: Add port mapping to nets - } - if (ctx->getBelType(belpin.bel) == id_SB_IO) { - std::unique_ptr created = create_ice_cell(ctx, ctx->id("SB_IO")); - IdString name = created->name; - ctx->cells[name] = std::move(created); - ctx->bindBel(belpin.bel, ctx->cells[name].get(), STRENGTH_WEAK); - // TODO: Add port mapping to nets - } - if (ctx->getBelType(belpin.bel) == id_SB_GB) { - std::unique_ptr created = create_ice_cell(ctx, ctx->id("SB_GB")); - IdString name = created->name; - ctx->cells[name] = std::move(created); - ctx->bindBel(belpin.bel, ctx->cells[name].get(), STRENGTH_WEAK); - // TODO: Add port mapping to nets - } - if (ctx->getBelType(belpin.bel) == id_SB_WARMBOOT) { - std::unique_ptr created = create_ice_cell(ctx, ctx->id("SB_WARMBOOT")); - IdString name = created->name; - ctx->cells[name] = std::move(created); - ctx->bindBel(belpin.bel, ctx->cells[name].get(), STRENGTH_WEAK); - // TODO: Add port mapping to nets - } - if (ctx->getBelType(belpin.bel) == id_ICESTORM_LFOSC) { - std::unique_ptr created = create_ice_cell(ctx, ctx->id("ICESTORM_LFOSC")); - IdString name = created->name; - ctx->cells[name] = std::move(created); - ctx->bindBel(belpin.bel, ctx->cells[name].get(), STRENGTH_WEAK); - // TODO: Add port mapping to nets - } - } - } - } - } - } - for (auto &cell : ctx->cells) { - if (cell.second->bel != BelId()) { - for (auto &port : cell.second->ports) { - IdString pin = port.first; - WireId wire = ctx->getBelPinWire(cell.second->bel, pin); - if (wire != WireId()) { - NetInfo *net = ctx->getBoundWireNet(wire); - if (net != nullptr) { - port.second.net = net; - PortRef ref; - ref.cell = cell.second.get(); - ref.port = port.second.name; - - if (port.second.type == PORT_OUT) - net->driver = ref; - else - net->users.push_back(ref); - } - } - } - } - } - return true; - } catch (log_execution_error_exception) { - return false; - } -} -NEXTPNR_NAMESPACE_END +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Clifford Wolf + * Copyright (C) 2018 David Shah + * Copyright (C) 2018 Serge Bazanski + * + * 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 "bitstream.h" +#include +#include +#include "cells.h" +#include "log.h" + +NEXTPNR_NAMESPACE_BEGIN + +inline TileType tile_at(const Context *ctx, int x, int y) +{ + return ctx->chip_info->tile_grid[y * ctx->chip_info->width + x]; +} + +const ConfigEntryPOD &find_config(const TileInfoPOD &tile, const std::string &name) +{ + for (int i = 0; i < tile.num_config_entries; i++) { + if (std::string(tile.entries[i].name.get()) == name) { + return tile.entries[i]; + } + } + NPNR_ASSERT_FALSE_STR("unable to find config bit " + name); +} + +std::tuple get_ieren(const BitstreamInfoPOD &bi, int8_t x, int8_t y, int8_t z) +{ + for (int i = 0; i < bi.num_ierens; i++) { + auto ie = bi.ierens[i]; + if (ie.iox == x && ie.ioy == y && ie.ioz == z) { + return std::make_tuple(ie.ierx, ie.iery, ie.ierz); + } + } + // No pin at this location + return std::make_tuple(-1, -1, -1); +}; + +bool get_config(const TileInfoPOD &ti, std::vector> &tile_cfg, const std::string &name, + int index = -1) +{ + const ConfigEntryPOD &cfg = find_config(ti, name); + if (index == -1) { + for (int i = 0; i < cfg.num_bits; i++) { + return tile_cfg.at(cfg.bits[i].row).at(cfg.bits[i].col); + } + } else { + return tile_cfg.at(cfg.bits[index].row).at(cfg.bits[index].col); + } + return false; +} + +void set_config(const TileInfoPOD &ti, std::vector> &tile_cfg, const std::string &name, bool value, + int index = -1) +{ + const ConfigEntryPOD &cfg = find_config(ti, name); + if (index == -1) { + for (int i = 0; i < cfg.num_bits; i++) { + int8_t &cbit = tile_cfg.at(cfg.bits[i].row).at(cfg.bits[i].col); + if (cbit && !value) + log_error("clearing already set config bit %s\n", name.c_str()); + cbit = value; + } + } else { + int8_t &cbit = tile_cfg.at(cfg.bits[index].row).at(cfg.bits[index].col); + cbit = value; + if (cbit && !value) + log_error("clearing already set config bit %s[%d]\n", name.c_str(), index); + } +} + +// Set an IE_{EN,REN} logical bit in a tile config. Logical means enabled. +// On {HX,LP}1K devices these bits are active low, so we need to invert them. +void set_ie_bit_logical(const Context *ctx, const TileInfoPOD &ti, std::vector> &tile_cfg, + const std::string &name, bool value) +{ + if (ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K) { + set_config(ti, tile_cfg, name, !value); + } else { + set_config(ti, tile_cfg, name, value); + } +} + +int get_param_or_def(const CellInfo *cell, const IdString param, int defval = 0) +{ + auto found = cell->params.find(param); + if (found != cell->params.end()) + return std::stoi(found->second); + else + return defval; +} + +std::string get_param_str_or_def(const CellInfo *cell, const IdString param, std::string defval = "") +{ + auto found = cell->params.find(param); + if (found != cell->params.end()) + return found->second; + else + return defval; +} + +char get_hexdigit(int i) { return std::string("0123456789ABCDEF").at(i); } + +static const BelConfigPOD &get_ec_config(const ChipInfoPOD *chip, BelId bel) +{ + for (int i = 0; i < chip->num_belcfgs; i++) { + if (chip->bel_config[i].bel_index == bel.index) + return chip->bel_config[i]; + } + NPNR_ASSERT_FALSE("failed to find bel config"); +} + +typedef std::vector>>> chipconfig_t; + +static void set_ec_cbit(chipconfig_t &config, const Context *ctx, const BelConfigPOD &cell_cbits, std::string name, + bool value, std::string prefix) +{ + const ChipInfoPOD *chip = ctx->chip_info; + + for (int i = 0; i < cell_cbits.num_entries; i++) { + const auto &cbit = cell_cbits.entries[i]; + if (cbit.entry_name.get() == name) { + const auto &ti = chip->bits_info->tiles_nonrouting[tile_at(ctx, cbit.x, cbit.y)]; + set_config(ti, config.at(cbit.y).at(cbit.x), prefix + cbit.cbit_name.get(), value); + return; + } + } + NPNR_ASSERT_FALSE_STR("failed to config extra cell config bit " + name); +} + +void configure_extra_cell(chipconfig_t &config, const Context *ctx, CellInfo *cell, + const std::vector> ¶ms, bool string_style, std::string prefix) +{ + const ChipInfoPOD *chip = ctx->chip_info; + const auto &bc = get_ec_config(chip, cell->bel); + for (auto p : params) { + std::vector value; + if (string_style) { + // Lattice's weird string style params, not sure if + // prefixes other than 0b should be supported, only 0b features in docs + std::string raw = get_param_str_or_def(cell, ctx->id(p.first), "0b0"); + assert(raw.substr(0, 2) == "0b"); + raw = raw.substr(2); + value.resize(raw.length()); + for (int i = 0; i < (int)raw.length(); i++) { + if (raw[i] == '1') { + value[(raw.length() - 1) - i] = 1; + } else { + assert(raw[i] == '0'); + value[(raw.length() - 1) - i] = 0; + } + } + } else { + int ival = get_param_or_def(cell, ctx->id(p.first), 0); + + for (int i = 0; i < p.second; i++) + value.push_back((ival >> i) & 0x1); + } + + value.resize(p.second); + if (p.second == 1) { + set_ec_cbit(config, ctx, bc, p.first, value.at(0), prefix); + } else { + for (int i = 0; i < p.second; i++) { + set_ec_cbit(config, ctx, bc, p.first + "_" + std::to_string(i), value.at(i), prefix); + } + } + } +} + +std::string tagTileType(TileType &tile) +{ + if (tile == TILE_NONE) + return ""; + switch (tile) { + case TILE_LOGIC: + return ".logic_tile"; + break; + case TILE_IO: + return ".io_tile"; + break; + case TILE_RAMB: + return ".ramb_tile"; + break; + case TILE_RAMT: + return ".ramt_tile"; + break; + case TILE_DSP0: + return ".dsp0_tile"; + break; + case TILE_DSP1: + return ".dsp1_tile"; + break; + case TILE_DSP2: + return ".dsp2_tile"; + break; + case TILE_DSP3: + return ".dsp3_tile"; + break; + case TILE_IPCON: + return ".ipcon_tile"; + break; + default: + NPNR_ASSERT(false); + } +} + +static BelPin get_one_bel_pin(const Context *ctx, WireId wire) +{ + auto pins = ctx->getWireBelPins(wire); + NPNR_ASSERT(pins.begin() != pins.end()); + return *pins.begin(); +} + +// Permute LUT init value given map (LUT input -> ext input) +unsigned permute_lut(unsigned orig_init, const std::unordered_map &input_permute) +{ + unsigned new_init = 0; + + for (int i = 0; i < 16; i++) { + int permute_address = 0; + for (int j = 0; j < 4; j++) { + if ((i >> j) & 0x1) + permute_address |= (1 << input_permute.at(j)); + } + if ((orig_init >> i) & 0x1) { + new_init |= (1 << permute_address); + } + } + + return new_init; +} + +void write_asc(const Context *ctx, std::ostream &out) +{ + + static const std::vector lut_perm = { + 4, 14, 15, 5, 6, 16, 17, 7, 3, 13, 12, 2, 1, 11, 10, 0, + }; + + // [y][x][row][col] + const ChipInfoPOD &ci = *ctx->chip_info; + const BitstreamInfoPOD &bi = *ci.bits_info; + chipconfig_t config; + config.resize(ci.height); + for (int y = 0; y < ci.height; y++) { + config.at(y).resize(ci.width); + for (int x = 0; x < ci.width; x++) { + TileType tile = tile_at(ctx, x, y); + int rows = bi.tiles_nonrouting[tile].rows; + int cols = bi.tiles_nonrouting[tile].cols; + config.at(y).at(x).resize(rows, std::vector(cols)); + } + } + + std::vector> extra_bits; + + out << ".comment from next-pnr" << std::endl; + + switch (ctx->args.type) { + case ArchArgs::LP384: + out << ".device 384" << std::endl; + break; + case ArchArgs::HX1K: + case ArchArgs::LP1K: + out << ".device 1k" << std::endl; + break; + case ArchArgs::HX8K: + case ArchArgs::LP8K: + out << ".device 8k" << std::endl; + break; + case ArchArgs::UP5K: + out << ".device 5k" << std::endl; + break; + default: + NPNR_ASSERT_FALSE("unsupported device type\n"); + } + // Set pips + for (auto pip : ctx->getPips()) { + if (ctx->pip_to_net[pip.index] != nullptr) { + const PipInfoPOD &pi = ci.pip_data[pip.index]; + const SwitchInfoPOD &swi = bi.switches[pi.switch_index]; + int sw_bel_idx = swi.bel; + if (sw_bel_idx >= 0) { + const BelInfoPOD &beli = ci.bel_data[sw_bel_idx]; + const TileInfoPOD &ti = bi.tiles_nonrouting[TILE_LOGIC]; + BelId sw_bel; + sw_bel.index = sw_bel_idx; + NPNR_ASSERT(ctx->getBelType(sw_bel) == id_ICESTORM_LC); + + if (ci.wire_data[ctx->getPipDstWire(pip).index].type == WireInfoPOD::WIRE_TYPE_LUTFF_IN_LUT) + continue; // Permutation pips + BelPin output = get_one_bel_pin(ctx, ctx->getPipDstWire(pip)); + NPNR_ASSERT(output.bel == sw_bel && output.pin == id_O); + unsigned lut_init; + + WireId permWire; + for (auto permPip : ctx->getPipsUphill(ctx->getPipSrcWire(pip))) { + if (ctx->getBoundPipNet(permPip) != nullptr) { + permWire = ctx->getPipSrcWire(permPip); + } + } + NPNR_ASSERT(permWire != WireId()); + std::string dName = ci.wire_data[permWire.index].name.get(); + + switch (dName.back()) { + case '0': + lut_init = 2; + break; + case '1': + lut_init = 4; + break; + case '2': + lut_init = 16; + break; + case '3': + lut_init = 256; + break; + default: + NPNR_ASSERT_FALSE("bad feedthru LUT input"); + } + std::vector lc(20, false); + for (int i = 0; i < 16; i++) { + if ((lut_init >> i) & 0x1) + lc.at(lut_perm.at(i)) = true; + } + + for (int i = 0; i < 20; i++) + set_config(ti, config.at(beli.y).at(beli.x), "LC_" + std::to_string(beli.z), lc.at(i), i); + } else { + for (int i = 0; i < swi.num_bits; i++) { + bool val = (pi.switch_mask & (1 << ((swi.num_bits - 1) - i))) != 0; + int8_t &cbit = config.at(swi.y).at(swi.x).at(swi.cbits[i].row).at(swi.cbits[i].col); + if (bool(cbit) != 0) + NPNR_ASSERT(false); + cbit = val; + } + } + } + } + + // Scan for PLL and collects the affected SB_IOs + std::unordered_set sb_io_used_by_pll_out; + std::unordered_set sb_io_used_by_pll_pad; + + for (auto &cell : ctx->cells) { + if (cell.second->type != ctx->id("ICESTORM_PLL")) + continue; + + // Collect all locations matching an PLL output port + // note: It doesn't matter if the port is connected or not, or if fabric/global + // is used. As long as it's a PLL type for which the port exists, the SB_IO + // is not available and must be configured for PLL mode + const std::vector ports = {id_PLLOUT_A, id_PLLOUT_B}; + for (auto &port : ports) { + // If the output is not enabled in this mode, ignore it + if (port == id_PLLOUT_B && !is_sb_pll40_dual(ctx, cell.second.get())) + continue; + + // Get IO Bel that this PLL port goes through by finding sibling + // Bel driving the same wire via PIN_D_IN_0. + auto wire = ctx->getBelPinWire(cell.second->bel, port); + BelId io_bel; + for (auto pin : ctx->getWireBelPins(wire)) { + if (pin.pin == id_D_IN_0) { + io_bel = pin.bel; + break; + } + } + NPNR_ASSERT(io_bel.index != -1); + auto io_bel_loc = ctx->getBelLocation(io_bel); + + // Mark this SB_IO as being used by a PLL output path + sb_io_used_by_pll_out.insert(io_bel_loc); + + // If this is a PAD PLL, and this is the 'PLLOUT_A' port, then the same SB_IO is also PAD + if (port == id_PLLOUT_A && is_sb_pll40_pad(ctx, cell.second.get())) + sb_io_used_by_pll_pad.insert(io_bel_loc); + } + } + + // Set logic cell config + for (auto &cell : ctx->cells) { + + BelId bel = cell.second.get()->bel; + if (bel == BelId()) { + std::cout << "Found unplaced cell " << cell.first.str(ctx) << " while generating bitstream!" << std::endl; + continue; + } + if (cell.second->type == ctx->id("ICESTORM_LC")) { + const BelInfoPOD &beli = ci.bel_data[bel.index]; + int x = beli.x, y = beli.y, z = beli.z; + const TileInfoPOD &ti = bi.tiles_nonrouting[TILE_LOGIC]; + unsigned lut_init = get_param_or_def(cell.second.get(), ctx->id("LUT_INIT")); + bool neg_clk = get_param_or_def(cell.second.get(), ctx->id("NEG_CLK")); + bool dff_enable = get_param_or_def(cell.second.get(), ctx->id("DFF_ENABLE")); + bool async_sr = get_param_or_def(cell.second.get(), ctx->id("ASYNC_SR")); + bool set_noreset = get_param_or_def(cell.second.get(), ctx->id("SET_NORESET")); + bool carry_enable = get_param_or_def(cell.second.get(), ctx->id("CARRY_ENABLE")); + std::vector lc(20, false); + + // Discover permutation + std::unordered_map input_perm; + std::set unused; + for (int i = 0; i < 4; i++) + unused.insert(i); + for (int i = 0; i < 4; i++) { + WireId lut_wire = ctx->getBelPinWire(bel, IdString(ID_I0 + i)); + for (auto pip : ctx->getPipsUphill(lut_wire)) { + if (ctx->getBoundPipNet(pip) != nullptr) { + std::string name = ci.wire_data[ctx->getPipSrcWire(pip).index].name.get(); + switch (name.back()) { + case '0': + input_perm[i] = 0; + unused.erase(0); + break; + case '1': + input_perm[i] = 1; + unused.erase(1); + break; + case '2': + input_perm[i] = 2; + unused.erase(2); + break; + case '3': + input_perm[i] = 3; + unused.erase(3); + break; + default: + NPNR_ASSERT_FALSE("failed to determine LUT permutation"); + } + break; + } + } + } + for (int i = 0; i < 4; i++) { + if (!input_perm.count(i)) { + NPNR_ASSERT(!unused.empty()); + input_perm[i] = *(unused.begin()); + unused.erase(input_perm[i]); + } + } + lut_init = permute_lut(lut_init, input_perm); + for (int i = 0; i < 16; i++) { + if ((lut_init >> i) & 0x1) + lc.at(lut_perm.at(i)) = true; + } + lc.at(8) = carry_enable; + lc.at(9) = dff_enable; + lc.at(18) = set_noreset; + lc.at(19) = async_sr; + + for (int i = 0; i < 20; i++) + set_config(ti, config.at(y).at(x), "LC_" + std::to_string(z), lc.at(i), i); + if (dff_enable) + set_config(ti, config.at(y).at(x), "NegClk", neg_clk); + + bool carry_const = get_param_or_def(cell.second.get(), ctx->id("CIN_CONST")); + bool carry_set = get_param_or_def(cell.second.get(), ctx->id("CIN_SET")); + if (carry_const) { + if (!ctx->force) + NPNR_ASSERT(z == 0); + set_config(ti, config.at(y).at(x), "CarryInSet", carry_set); + } + } else if (cell.second->type == ctx->id("SB_IO")) { + const BelInfoPOD &beli = ci.bel_data[bel.index]; + int x = beli.x, y = beli.y, z = beli.z; + const TileInfoPOD &ti = bi.tiles_nonrouting[TILE_IO]; + unsigned pin_type = get_param_or_def(cell.second.get(), ctx->id("PIN_TYPE")); + bool neg_trigger = get_param_or_def(cell.second.get(), ctx->id("NEG_TRIGGER")); + bool pullup = get_param_or_def(cell.second.get(), ctx->id("PULLUP")); + bool lvds = get_param_str_or_def(cell.second.get(), ctx->id("IO_STANDARD")) == "SB_LVDS_INPUT"; + bool used_by_pll_out = sb_io_used_by_pll_out.count(Loc(x, y, z)) > 0; + bool used_by_pll_pad = sb_io_used_by_pll_pad.count(Loc(x, y, z)) > 0; + + for (int i = used_by_pll_out ? 2 : 0; i < 6; i++) { + bool val = (pin_type >> i) & 0x01; + set_config(ti, config.at(y).at(x), "IOB_" + std::to_string(z) + ".PINTYPE_" + std::to_string(i), val); + } + set_config(ti, config.at(y).at(x), "NegClk", neg_trigger); + auto ieren = get_ieren(bi, x, y, z); + int iex, iey, iez; + std::tie(iex, iey, iez) = ieren; + NPNR_ASSERT(iez != -1); + + bool input_en; + if (lvds) { + input_en = false; + pullup = false; + } else { + if ((ctx->wire_to_net[ctx->getBelPinWire(bel, id_D_IN_0).index] != nullptr) || + (ctx->wire_to_net[ctx->getBelPinWire(bel, id_D_IN_1).index] != nullptr)) { + input_en = true; + } else { + input_en = false; + } + } + + input_en = (input_en & !used_by_pll_out) | used_by_pll_pad; + input_en |= cell.second->ioInfo.global; + + if (ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K) { + set_config(ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), !input_en); + set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), !pullup); + } else { + set_config(ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), input_en); + set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), !pullup); + } + + if (ctx->args.type == ArchArgs::UP5K) { + if (iez == 0) { + set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_39", !pullup); + } else if (iez == 1) { + set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_35", !pullup); + } + } + + if (lvds) { + NPNR_ASSERT(z == 0); + set_config(ti, config.at(y).at(x), "IoCtrl.LVDS", true); + // Set comp IO config + auto comp_ieren = get_ieren(bi, x, y, 1); + int ciex, ciey, ciez; + std::tie(ciex, ciey, ciez) = comp_ieren; + + if (ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K) { + set_config(ti, config.at(ciey).at(ciex), "IoCtrl.IE_" + std::to_string(ciez), !input_en); + set_config(ti, config.at(ciey).at(ciex), "IoCtrl.REN_" + std::to_string(ciez), !pullup); + } else { + set_config(ti, config.at(ciey).at(ciex), "IoCtrl.IE_" + std::to_string(ciez), input_en); + set_config(ti, config.at(ciey).at(ciex), "IoCtrl.REN_" + std::to_string(ciez), !pullup); + } + + if (ctx->args.type == ArchArgs::UP5K) { + if (ciez == 0) { + set_config(ti, config.at(ciey).at(ciex), "IoCtrl.cf_bit_39", !pullup); + } else if (iez == 1) { + set_config(ti, config.at(ciey).at(ciex), "IoCtrl.cf_bit_35", !pullup); + } + } + } + } else if (cell.second->type == ctx->id("SB_GB")) { + if (cell.second->gbInfo.forPadIn) { + Loc gb_loc = ctx->getBelLocation(bel); + for (int i = 0; i < ci.num_global_networks; i++) { + if ((gb_loc.x == ci.global_network_info[i].gb_x) && (gb_loc.y == ci.global_network_info[i].gb_y)) { + extra_bits.push_back(std::make_tuple(ci.global_network_info[i].pi_eb_bank, + ci.global_network_info[i].pi_eb_x, + ci.global_network_info[i].pi_eb_y)); + } + } + } + } else if (cell.second->type == ctx->id("ICESTORM_RAM")) { + const BelInfoPOD &beli = ci.bel_data[bel.index]; + int x = beli.x, y = beli.y; + const TileInfoPOD &ti_ramt = bi.tiles_nonrouting[TILE_RAMT]; + const TileInfoPOD &ti_ramb = bi.tiles_nonrouting[TILE_RAMB]; + if (!(ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K)) { + set_config(ti_ramb, config.at(y).at(x), "RamConfig.PowerUp", true); + } + bool negclk_r = get_param_or_def(cell.second.get(), ctx->id("NEG_CLK_R")); + bool negclk_w = get_param_or_def(cell.second.get(), ctx->id("NEG_CLK_W")); + int write_mode = get_param_or_def(cell.second.get(), ctx->id("WRITE_MODE")); + int read_mode = get_param_or_def(cell.second.get(), ctx->id("READ_MODE")); + set_config(ti_ramb, config.at(y).at(x), "NegClk", negclk_w); + set_config(ti_ramt, config.at(y + 1).at(x), "NegClk", negclk_r); + + set_config(ti_ramt, config.at(y + 1).at(x), "RamConfig.CBIT_0", write_mode & 0x1); + set_config(ti_ramt, config.at(y + 1).at(x), "RamConfig.CBIT_1", write_mode & 0x2); + set_config(ti_ramt, config.at(y + 1).at(x), "RamConfig.CBIT_2", read_mode & 0x1); + set_config(ti_ramt, config.at(y + 1).at(x), "RamConfig.CBIT_3", read_mode & 0x2); + } else if (cell.second->type == ctx->id("SB_RGBA_DRV")) { + const std::vector> rgba_params = { + {"CURRENT_MODE", 1}, {"RGB0_CURRENT", 6}, {"RGB1_CURRENT", 6}, {"RGB2_CURRENT", 6}}; + configure_extra_cell(config, ctx, cell.second.get(), rgba_params, true, std::string("IpConfig.")); + set_ec_cbit(config, ctx, get_ec_config(ctx->chip_info, cell.second->bel), "RGBA_DRV_EN", true, "IpConfig."); + } else if (cell.second->type == ctx->id("SB_WARMBOOT") || cell.second->type == ctx->id("ICESTORM_LFOSC")) { + // No config needed + } else if (cell.second->type == ctx->id("ICESTORM_SPRAM")) { + const BelInfoPOD &beli = ci.bel_data[bel.index]; + int x = beli.x, y = beli.y, z = beli.z; + NPNR_ASSERT(ctx->args.type == ArchArgs::UP5K); + if (x == 0 && y == 0) { + const TileInfoPOD &ti_ipcon = bi.tiles_nonrouting[TILE_IPCON]; + if (z == 1) { + set_config(ti_ipcon, config.at(1).at(0), "IpConfig.CBIT_0", true); + } else if (z == 2) { + set_config(ti_ipcon, config.at(1).at(0), "IpConfig.CBIT_1", true); + } else { + NPNR_ASSERT(false); + } + } else if (x == 25 && y == 0) { + const TileInfoPOD &ti_ipcon = bi.tiles_nonrouting[TILE_IPCON]; + if (z == 3) { + set_config(ti_ipcon, config.at(1).at(25), "IpConfig.CBIT_0", true); + } else if (z == 4) { + set_config(ti_ipcon, config.at(1).at(25), "IpConfig.CBIT_1", true); + } else { + NPNR_ASSERT(false); + } + } + } else if (cell.second->type == ctx->id("ICESTORM_DSP")) { + const std::vector> mac16_params = {{"C_REG", 1}, + {"A_REG", 1}, + {"B_REG", 1}, + {"D_REG", 1}, + {"TOP_8x8_MULT_REG", 1}, + {"BOT_8x8_MULT_REG", 1}, + {"PIPELINE_16x16_MULT_REG1", 1}, + {"PIPELINE_16x16_MULT_REG2", 1}, + {"TOPOUTPUT_SELECT", 2}, + {"TOPADDSUB_LOWERINPUT", 2}, + {"TOPADDSUB_UPPERINPUT", 1}, + {"TOPADDSUB_CARRYSELECT", 2}, + {"BOTOUTPUT_SELECT", 2}, + {"BOTADDSUB_LOWERINPUT", 2}, + {"BOTADDSUB_UPPERINPUT", 1}, + {"BOTADDSUB_CARRYSELECT", 2}, + {"MODE_8x8", 1}, + {"A_SIGNED", 1}, + {"B_SIGNED", 1}}; + configure_extra_cell(config, ctx, cell.second.get(), mac16_params, false, std::string("IpConfig.")); + } else if (cell.second->type == ctx->id("ICESTORM_HFOSC")) { + const std::vector> hfosc_params = {{"CLKHF_DIV", 2}, {"TRIM_EN", 1}}; + configure_extra_cell(config, ctx, cell.second.get(), hfosc_params, true, std::string("IpConfig.")); + + } else if (cell.second->type == ctx->id("ICESTORM_PLL")) { + const std::vector> pll_params = {{"DELAY_ADJMODE_FB", 1}, + {"DELAY_ADJMODE_REL", 1}, + {"DIVF", 7}, + {"DIVQ", 3}, + {"DIVR", 4}, + {"FDA_FEEDBACK", 4}, + {"FDA_RELATIVE", 4}, + {"FEEDBACK_PATH", 3}, + {"FILTER_RANGE", 3}, + {"PLLOUT_SELECT_A", 2}, + {"PLLOUT_SELECT_B", 2}, + {"PLLTYPE", 3}, + {"SHIFTREG_DIV_MODE", 1}, + {"TEST_MODE", 1}}; + configure_extra_cell(config, ctx, cell.second.get(), pll_params, false, std::string("PLL.")); + + // Configure the SB_IOs that the clock outputs are going through. + for (auto &io_bel_loc : sb_io_used_by_pll_out) { + // Write config. + const TileInfoPOD &ti = bi.tiles_nonrouting[TILE_IO]; + + // PINTYPE[1:0] == "01" passes the PLL through to the fabric. + set_config(ti, config.at(io_bel_loc.y).at(io_bel_loc.x), + "IOB_" + std::to_string(io_bel_loc.z) + ".PINTYPE_1", false); + set_config(ti, config.at(io_bel_loc.y).at(io_bel_loc.x), + "IOB_" + std::to_string(io_bel_loc.z) + ".PINTYPE_0", true); + } + + } else { + NPNR_ASSERT(false); + } + } + // Set config bits in unused IO and RAM + for (auto bel : ctx->getBels()) { + if (ctx->bel_to_cell[bel.index] == nullptr && ctx->getBelType(bel) == id_SB_IO) { + const TileInfoPOD &ti = bi.tiles_nonrouting[TILE_IO]; + const BelInfoPOD &beli = ci.bel_data[bel.index]; + int x = beli.x, y = beli.y, z = beli.z; + if (sb_io_used_by_pll_out.count(Loc(x, y, z))) { + continue; + } + + auto ieren = get_ieren(bi, x, y, z); + int iex, iey, iez; + std::tie(iex, iey, iez) = ieren; + if (iez != -1) { + // IO is not actually unused if part of an LVDS pair + if (z == 1) { + BelId lvds0 = ctx->getBelByLocation(Loc{x, y, 0}); + const CellInfo *lvds0cell = ctx->getBoundBelCell(lvds0); + if (lvds0cell != nullptr && lvds0cell->ioInfo.lvds) + continue; + } + if (ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K) { + set_config(ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), true); + set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), false); + } else { + set_config(ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), false); + set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), false); + } + } + } else if (ctx->bel_to_cell[bel.index] == nullptr && ctx->getBelType(bel) == id_ICESTORM_RAM) { + const BelInfoPOD &beli = ci.bel_data[bel.index]; + int x = beli.x, y = beli.y; + const TileInfoPOD &ti = bi.tiles_nonrouting[TILE_RAMB]; + if ((ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K)) { + set_config(ti, config.at(y).at(x), "RamConfig.PowerUp", true); + } + } + } + + // Set other config bits + for (int y = 0; y < ci.height; y++) { + for (int x = 0; x < ci.width; x++) { + TileType tile = tile_at(ctx, x, y); + const TileInfoPOD &ti = bi.tiles_nonrouting[tile]; + + // set all ColBufCtrl bits (FIXME) + bool setColBufCtrl = true; + if (ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K) { + if (tile == TILE_RAMB || tile == TILE_RAMT) { + setColBufCtrl = (y == 3 || y == 5 || y == 11 || y == 13); + } else { + setColBufCtrl = (y == 4 || y == 5 || y == 12 || y == 13); + } + } else if (ctx->args.type == ArchArgs::LP8K || ctx->args.type == ArchArgs::HX8K) { + setColBufCtrl = (y == 8 || y == 9 || y == 24 || y == 25); + } else if (ctx->args.type == ArchArgs::UP5K) { + setColBufCtrl = (y == 4 || y == 5 || y == 14 || y == 15 || y == 26 || y == 27); + } else if (ctx->args.type == ArchArgs::LP384) { + setColBufCtrl = false; + } + if (setColBufCtrl) { + set_config(ti, config.at(y).at(x), "ColBufCtrl.glb_netwk_0", true); + set_config(ti, config.at(y).at(x), "ColBufCtrl.glb_netwk_1", true); + set_config(ti, config.at(y).at(x), "ColBufCtrl.glb_netwk_2", true); + set_config(ti, config.at(y).at(x), "ColBufCtrl.glb_netwk_3", true); + set_config(ti, config.at(y).at(x), "ColBufCtrl.glb_netwk_4", true); + set_config(ti, config.at(y).at(x), "ColBufCtrl.glb_netwk_5", true); + set_config(ti, config.at(y).at(x), "ColBufCtrl.glb_netwk_6", true); + set_config(ti, config.at(y).at(x), "ColBufCtrl.glb_netwk_7", true); + } + + // Weird UltraPlus bits + if (tile == TILE_DSP0 || tile == TILE_DSP1 || tile == TILE_DSP2 || tile == TILE_DSP3 || + tile == TILE_IPCON) { + if (ctx->args.type == ArchArgs::UP5K && x == 25 && y == 14) { + // Mystery bits not set in this one tile + } else { + for (int lc_idx = 0; lc_idx < 8; lc_idx++) { + static const std::vector ip_dsp_lut_perm = { + 4, 14, 15, 5, 6, 16, 17, 7, 3, 13, 12, 2, 1, 11, 10, 0, + }; + for (int i = 0; i < 16; i++) + set_config(ti, config.at(y).at(x), "LC_" + std::to_string(lc_idx), ((i % 8) >= 4), + ip_dsp_lut_perm.at(i)); + if (tile == TILE_IPCON) + set_config(ti, config.at(y).at(x), + "Cascade.IPCON_LC0" + std::to_string(lc_idx) + "_inmux02_5", true); + else + set_config(ti, config.at(y).at(x), + "Cascade.MULT" + std::to_string(int(tile - TILE_DSP0)) + "_LC0" + + std::to_string(lc_idx) + "_inmux02_5", + true); + } + } + } + } + } + + // Write config out + for (int y = 0; y < ci.height; y++) { + for (int x = 0; x < ci.width; x++) { + TileType tile = tile_at(ctx, x, y); + if (tile == TILE_NONE) + continue; + out << tagTileType(tile); + out << " " << x << " " << y << std::endl; + for (auto row : config.at(y).at(x)) { + for (auto col : row) { + if (col == 1) + out << "1"; + else + out << "0"; + } + out << std::endl; + } + out << std::endl; + } + } + + // Write RAM init data + for (auto &cell : ctx->cells) { + if (cell.second->bel != BelId()) { + if (cell.second->type == ctx->id("ICESTORM_RAM")) { + const BelInfoPOD &beli = ci.bel_data[cell.second->bel.index]; + int x = beli.x, y = beli.y; + out << ".ram_data " << x << " " << y << std::endl; + for (int w = 0; w < 16; w++) { + std::vector bits(256); + std::string init = + get_param_str_or_def(cell.second.get(), ctx->id(std::string("INIT_") + get_hexdigit(w))); + for (size_t i = 0; i < init.size(); i++) { + bool val = (init.at((init.size() - 1) - i) == '1'); + bits.at(i) = val; + } + for (int i = bits.size() - 4; i >= 0; i -= 4) { + int c = bits.at(i) + (bits.at(i + 1) << 1) + (bits.at(i + 2) << 2) + (bits.at(i + 3) << 3); + out << char(std::tolower(get_hexdigit(c))); + } + out << std::endl; + } + out << std::endl; + } + } + } + + // Write extra-bits + for (auto eb : extra_bits) + out << ".extra_bit " << std::get<0>(eb) << " " << std::get<1>(eb) << " " << std::get<2>(eb) << std::endl; + + // Write symbols + // const bool write_symbols = 1; + for (auto wire : ctx->getWires()) { + NetInfo *net = ctx->getBoundWireNet(wire); + if (net != nullptr) + out << ".sym " << wire.index << " " << net->name.str(ctx) << std::endl; + } +} + +void read_config(Context *ctx, std::istream &in, chipconfig_t &config) +{ + constexpr size_t line_buf_size = 65536; + char buffer[line_buf_size]; + int tile_x = -1, tile_y = -1, line_nr = -1; + + while (1) { + in.getline(buffer, line_buf_size); + if (buffer[0] == '.') { + line_nr = -1; + const char *tok = strtok(buffer, " \t\r\n"); + + if (!strcmp(tok, ".device")) { + std::string config_device = strtok(nullptr, " \t\r\n"); + std::string expected; + switch (ctx->args.type) { + case ArchArgs::LP384: + expected = "384"; + break; + case ArchArgs::HX1K: + case ArchArgs::LP1K: + expected = "1k"; + break; + case ArchArgs::HX8K: + case ArchArgs::LP8K: + expected = "8k"; + break; + case ArchArgs::UP5K: + expected = "5k"; + break; + default: + log_error("unsupported device type\n"); + } + if (expected != config_device) + log_error("device type does not match\n"); + } else if (!strcmp(tok, ".io_tile") || !strcmp(tok, ".logic_tile") || !strcmp(tok, ".ramb_tile") || + !strcmp(tok, ".ramt_tile") || !strcmp(tok, ".ipcon_tile") || !strcmp(tok, ".dsp0_tile") || + !strcmp(tok, ".dsp1_tile") || !strcmp(tok, ".dsp2_tile") || !strcmp(tok, ".dsp3_tile")) { + line_nr = 0; + tile_x = atoi(strtok(nullptr, " \t\r\n")); + tile_y = atoi(strtok(nullptr, " \t\r\n")); + + TileType tile = tile_at(ctx, tile_x, tile_y); + if (tok != tagTileType(tile)) + log_error("Wrong tile type for specified position\n"); + + } else if (!strcmp(tok, ".extra_bit")) { + /* + int b = atoi(strtok(nullptr, " \t\r\n")); + int x = atoi(strtok(nullptr, " \t\r\n")); + int y = atoi(strtok(nullptr, " \t\r\n")); + std::tuple key(b, x, y); + extra_bits.insert(key); + */ + } else if (!strcmp(tok, ".sym")) { + int wireIndex = atoi(strtok(nullptr, " \t\r\n")); + const char *name = strtok(nullptr, " \t\r\n"); + + IdString netName = ctx->id(name); + + if (ctx->nets.find(netName) == ctx->nets.end()) { + std::unique_ptr created_net = std::unique_ptr(new NetInfo); + created_net->name = netName; + ctx->nets[netName] = std::move(created_net); + } + + WireId wire; + wire.index = wireIndex; + ctx->bindWire(wire, ctx->nets.at(netName).get(), STRENGTH_WEAK); + } + } else if (line_nr >= 0 && strlen(buffer) > 0) { + if (line_nr > int(config.at(tile_y).at(tile_x).size() - 1)) + log_error("Invalid data in input asc file"); + for (int i = 0; buffer[i] == '0' || buffer[i] == '1'; i++) + config.at(tile_y).at(tile_x).at(line_nr).at(i) = (buffer[i] == '1') ? 1 : 0; + line_nr++; + } + if (in.eof()) + break; + } +} + +bool read_asc(Context *ctx, std::istream &in) +{ + try { + // [y][x][row][col] + const ChipInfoPOD &ci = *ctx->chip_info; + const BitstreamInfoPOD &bi = *ci.bits_info; + chipconfig_t config; + config.resize(ci.height); + for (int y = 0; y < ci.height; y++) { + config.at(y).resize(ci.width); + for (int x = 0; x < ci.width; x++) { + TileType tile = tile_at(ctx, x, y); + int rows = bi.tiles_nonrouting[tile].rows; + int cols = bi.tiles_nonrouting[tile].cols; + config.at(y).at(x).resize(rows, std::vector(cols)); + } + } + read_config(ctx, in, config); + + // Set pips + for (auto pip : ctx->getPips()) { + const PipInfoPOD &pi = ci.pip_data[pip.index]; + const SwitchInfoPOD &swi = bi.switches[pi.switch_index]; + bool isUsed = true; + for (int i = 0; i < swi.num_bits; i++) { + bool val = (pi.switch_mask & (1 << ((swi.num_bits - 1) - i))) != 0; + int8_t cbit = config.at(swi.y).at(swi.x).at(swi.cbits[i].row).at(swi.cbits[i].col); + isUsed &= !(bool(cbit) ^ val); + } + if (isUsed) { + NetInfo *net = ctx->wire_to_net[pi.dst]; + if (net != nullptr) { + WireId wire; + wire.index = pi.dst; + ctx->unbindWire(wire); + ctx->bindPip(pip, net, STRENGTH_WEAK); + } + } + } + for (auto bel : ctx->getBels()) { + if (ctx->getBelType(bel) == id_ICESTORM_LC) { + const TileInfoPOD &ti = bi.tiles_nonrouting[TILE_LOGIC]; + const BelInfoPOD &beli = ci.bel_data[bel.index]; + int x = beli.x, y = beli.y, z = beli.z; + std::vector lc(20, false); + bool isUsed = false; + for (int i = 0; i < 20; i++) { + lc.at(i) = get_config(ti, config.at(y).at(x), "LC_" + std::to_string(z), i); + isUsed |= lc.at(i); + } + bool neg_clk = get_config(ti, config.at(y).at(x), "NegClk"); + isUsed |= neg_clk; + bool carry_set = get_config(ti, config.at(y).at(x), "CarryInSet"); + isUsed |= carry_set; + + if (isUsed) { + std::unique_ptr created = create_ice_cell(ctx, ctx->id("ICESTORM_LC")); + IdString name = created->name; + ctx->cells[name] = std::move(created); + ctx->bindBel(bel, ctx->cells[name].get(), STRENGTH_WEAK); + // TODO: Add port mapping to nets and assign values of properties + } + } + if (ctx->getBelType(bel) == id_SB_IO) { + const TileInfoPOD &ti = bi.tiles_nonrouting[TILE_IO]; + const BelInfoPOD &beli = ci.bel_data[bel.index]; + int x = beli.x, y = beli.y, z = beli.z; + bool isUsed = false; + for (int i = 0; i < 6; i++) { + isUsed |= get_config(ti, config.at(y).at(x), + "IOB_" + std::to_string(z) + ".PINTYPE_" + std::to_string(i)); + } + bool neg_trigger = get_config(ti, config.at(y).at(x), "NegClk"); + isUsed |= neg_trigger; + + if (isUsed) { + std::unique_ptr created = create_ice_cell(ctx, ctx->id("SB_IO")); + IdString name = created->name; + ctx->cells[name] = std::move(created); + ctx->bindBel(bel, ctx->cells[name].get(), STRENGTH_WEAK); + // TODO: Add port mapping to nets and assign values of properties + } + } + } + // Add cells that are without change in initial state of configuration + for (auto &net : ctx->nets) { + for (auto w : net.second->wires) { + if (w.second.pip == PipId()) { + WireId wire = w.first; + for (auto belpin : ctx->getWireBelPins(wire)) { + + if (ctx->checkBelAvail(belpin.bel)) { + if (ctx->getBelType(belpin.bel) == id_ICESTORM_LC) { + std::unique_ptr created = create_ice_cell(ctx, ctx->id("ICESTORM_LC")); + IdString name = created->name; + ctx->cells[name] = std::move(created); + ctx->bindBel(belpin.bel, ctx->cells[name].get(), STRENGTH_WEAK); + // TODO: Add port mapping to nets + } + if (ctx->getBelType(belpin.bel) == id_SB_IO) { + std::unique_ptr created = create_ice_cell(ctx, ctx->id("SB_IO")); + IdString name = created->name; + ctx->cells[name] = std::move(created); + ctx->bindBel(belpin.bel, ctx->cells[name].get(), STRENGTH_WEAK); + // TODO: Add port mapping to nets + } + if (ctx->getBelType(belpin.bel) == id_SB_GB) { + std::unique_ptr created = create_ice_cell(ctx, ctx->id("SB_GB")); + IdString name = created->name; + ctx->cells[name] = std::move(created); + ctx->bindBel(belpin.bel, ctx->cells[name].get(), STRENGTH_WEAK); + // TODO: Add port mapping to nets + } + if (ctx->getBelType(belpin.bel) == id_SB_WARMBOOT) { + std::unique_ptr created = create_ice_cell(ctx, ctx->id("SB_WARMBOOT")); + IdString name = created->name; + ctx->cells[name] = std::move(created); + ctx->bindBel(belpin.bel, ctx->cells[name].get(), STRENGTH_WEAK); + // TODO: Add port mapping to nets + } + if (ctx->getBelType(belpin.bel) == id_ICESTORM_LFOSC) { + std::unique_ptr created = create_ice_cell(ctx, ctx->id("ICESTORM_LFOSC")); + IdString name = created->name; + ctx->cells[name] = std::move(created); + ctx->bindBel(belpin.bel, ctx->cells[name].get(), STRENGTH_WEAK); + // TODO: Add port mapping to nets + } + } + } + } + } + } + for (auto &cell : ctx->cells) { + if (cell.second->bel != BelId()) { + for (auto &port : cell.second->ports) { + IdString pin = port.first; + WireId wire = ctx->getBelPinWire(cell.second->bel, pin); + if (wire != WireId()) { + NetInfo *net = ctx->getBoundWireNet(wire); + if (net != nullptr) { + port.second.net = net; + PortRef ref; + ref.cell = cell.second.get(); + ref.port = port.second.name; + + if (port.second.type == PORT_OUT) + net->driver = ref; + else + net->users.push_back(ref); + } + } + } + } + } + return true; + } catch (log_execution_error_exception) { + return false; + } +} +NEXTPNR_NAMESPACE_END diff --git a/ice40/cells.cc b/ice40/cells.cc index fbb77b0c..dbb75c2c 100644 --- a/ice40/cells.cc +++ b/ice40/cells.cc @@ -219,7 +219,7 @@ std::unique_ptr create_ice_cell(Context *ctx, IdString type, std::stri new_cell->params[ctx->id("FDA_FEEDBACK")] = "0"; new_cell->params[ctx->id("FDA_RELATIVE")] = "0"; - new_cell->params[ctx->id("FEEDBACK_PATH")] = "0"; + new_cell->params[ctx->id("FEEDBACK_PATH")] = "1"; new_cell->params[ctx->id("FILTER_RANGE")] = "0"; new_cell->params[ctx->id("PLLOUT_SELECT_A")] = "0"; @@ -244,8 +244,22 @@ std::unique_ptr create_ice_cell(Context *ctx, IdString type, std::stri add_port(ctx, new_cell.get(), "LOCK", PORT_OUT); add_port(ctx, new_cell.get(), "PLLOUT_A", PORT_OUT); add_port(ctx, new_cell.get(), "PLLOUT_B", PORT_OUT); - add_port(ctx, new_cell.get(), "PLLOUTGLOBALA", PORT_OUT); - add_port(ctx, new_cell.get(), "PLLOUTGLOBALB", PORT_OUT); + add_port(ctx, new_cell.get(), "PLLOUT_A_GLOBAL", PORT_OUT); + add_port(ctx, new_cell.get(), "PLLOUT_B_GLOBAL", PORT_OUT); + } else if (type == ctx->id("SB_RGBA_DRV")) { + new_cell->params[ctx->id("CURRENT_MODE")] = "0b0"; + new_cell->params[ctx->id("RGB0_CURRENT")] = "0b000000"; + new_cell->params[ctx->id("RGB1_CURRENT")] = "0b000000"; + new_cell->params[ctx->id("RGB2_CURRENT")] = "0b000000"; + + add_port(ctx, new_cell.get(), "CURREN", PORT_IN); + add_port(ctx, new_cell.get(), "RGBLEDEN", PORT_IN); + add_port(ctx, new_cell.get(), "RGB0PWM", PORT_IN); + add_port(ctx, new_cell.get(), "RGB1PWM", PORT_IN); + add_port(ctx, new_cell.get(), "RGB2PWM", PORT_IN); + add_port(ctx, new_cell.get(), "RGB0", PORT_OUT); + add_port(ctx, new_cell.get(), "RGB1", PORT_OUT); + add_port(ctx, new_cell.get(), "RGB2", PORT_OUT); } else { log_error("unable to create iCE40 cell of type %s", type.c_str(ctx)); } @@ -362,7 +376,7 @@ uint8_t sb_pll40_type(const BaseCtx *ctx, const CellInfo *cell) if (cell->type == ctx->id("SB_PLL40_2_PAD")) return 4; if (cell->type == ctx->id("SB_PLL40_2F_PAD")) - return 5; + return 6; if (cell->type == ctx->id("SB_PLL40_CORE")) return 3; if (cell->type == ctx->id("SB_PLL40_2F_CORE")) diff --git a/ice40/cells.h b/ice40/cells.h index 054388ac..1fbd9073 100644 --- a/ice40/cells.h +++ b/ice40/cells.h @@ -53,6 +53,9 @@ inline bool is_lc(const BaseCtx *ctx, const CellInfo *cell) { return cell->type // Return true if a cell is a SB_IO inline bool is_sb_io(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_IO"); } +// Return true if a cell is a SB_GB_IO +inline bool is_sb_gb_io(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_GB_IO"); } + // Return true if a cell is a global buffer inline bool is_gbuf(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_GB"); } @@ -71,6 +74,8 @@ inline bool is_sb_spram(const BaseCtx *ctx, const CellInfo *cell) { return cell- inline bool is_sb_mac16(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_MAC16"); } +inline bool is_sb_rgba_drv(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_RGBA_DRV"); } + inline bool is_sb_pll40(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_PLL40_PAD") || cell->type == ctx->id("SB_PLL40_2_PAD") || @@ -81,7 +86,19 @@ inline bool is_sb_pll40(const BaseCtx *ctx, const CellInfo *cell) inline bool is_sb_pll40_pad(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_PLL40_PAD") || cell->type == ctx->id("SB_PLL40_2_PAD") || - cell->type == ctx->id("SB_PLL40_2F_PAD"); + cell->type == ctx->id("SB_PLL40_2F_PAD") || + (cell->type == ctx->id("ICESTORM_PLL") && + (cell->attrs.at(ctx->id("TYPE")) == "SB_PLL40_PAD" || cell->attrs.at(ctx->id("TYPE")) == "SB_PLL40_2_PAD" || + cell->attrs.at(ctx->id("TYPE")) == "SB_PLL40_2F_PAD")); +} + +inline bool is_sb_pll40_dual(const BaseCtx *ctx, const CellInfo *cell) +{ + return cell->type == ctx->id("SB_PLL40_2_PAD") || cell->type == ctx->id("SB_PLL40_2F_PAD") || + cell->type == ctx->id("SB_PLL40_2F_CORE") || + (cell->type == ctx->id("ICESTORM_PLL") && (cell->attrs.at(ctx->id("TYPE")) == "SB_PLL40_2_PAD" || + cell->attrs.at(ctx->id("TYPE")) == "SB_PLL40_2F_PAD" || + cell->attrs.at(ctx->id("TYPE")) == "SB_PLL40_2F_CORE")); } uint8_t sb_pll40_type(const BaseCtx *ctx, const CellInfo *cell); diff --git a/ice40/chipdb.py b/ice40/chipdb.py index 5b2f3e57..96231b26 100644 --- a/ice40/chipdb.py +++ b/ice40/chipdb.py @@ -36,6 +36,7 @@ ierens = list() extra_cells = dict() extra_cell_config = dict() packages = list() +glbinfo = dict([(i, {}) for i in range(8)]) wire_belports = dict() @@ -640,6 +641,18 @@ with open(args.filename, "r") as f: extra_cells[mode[1]] = [] continue + if line[0] == ".gbufin": + mode = ("gbufin",) + continue + + if line[0] == ".gbufpin": + mode = ("gbufpin",) + continue + + if line[0] == ".extra_bits": + mode = ("extra_bits",) + continue + if (line[0][0] == ".") or (mode is None): mode = None continue @@ -692,11 +705,33 @@ with open(args.filename, "r") as f: if mode[0] == "extra_cell": if line[0] == "LOCKED": - extra_cells[mode[1]].append((("LOCKED_" + line[1]), (0, 0, "LOCKED"))) + for pkg in line[1:]: + extra_cells[mode[1]].append((("LOCKED_" + pkg), (0, 0, "LOCKED"))) else: extra_cells[mode[1]].append((line[0], (int(line[1]), int(line[2]), line[3]))) continue + if mode[0] == "gbufin": + idx = int(line[2]) + glbinfo[idx]['gb_x'] = int(line[0]) + glbinfo[idx]['gb_y'] = int(line[1]) + continue + + if mode[0] == "gbufpin": + idx = int(line[3]) + glbinfo[idx]['pi_gb_x'] = int(line[0]) + glbinfo[idx]['pi_gb_y'] = int(line[1]) + glbinfo[idx]['pi_gb_pio'] = int(line[2]) + continue + + if mode[0] == "extra_bits": + if line[0].startswith('padin_glb_netwk.'): + idx = int(line[0].split('.')[1]) + glbinfo[idx]['pi_eb_bank'] = int(line[1]) + glbinfo[idx]['pi_eb_x'] = int(line[2]) + glbinfo[idx]['pi_eb_y'] = int(line[3]) + continue + def add_wire(x, y, name): global num_wires wire_idx = num_wires @@ -828,6 +863,10 @@ def add_bel_io(x, y, z): add_bel_input(bel, wire_dout_1, "D_OUT_1") add_bel_input(bel, wire_out_en, "OUTPUT_ENABLE") + for gidx, ginfo in glbinfo.items(): + if (ginfo['pi_gb_x'], ginfo['pi_gb_y'], ginfo['pi_gb_pio']) == (x,y,z): + add_bel_output(bel, wire_names[(x, y, "glb_netwk_%d" % gidx)], "GLOBAL_BUFFER_OUTPUT") + def add_bel_ram(x, y): bel = len(bel_name) bel_name.append("X%d/Y%d/ram" % (x, y)) @@ -885,6 +924,18 @@ def is_ec_output(ec_entry): def is_ec_pll_clock_output(ec, ec_entry): return ec[0] == 'PLL' and ec_entry[0] in ('PLLOUT_A', 'PLLOUT_B') +def add_pll_clock_output(bel, ec, entry): + # Fabric output + io_x, io_y, io_z = entry[1] + io_zs = 'io_{}/D_IN_0'.format(io_z) + io_z = int(io_z) + add_bel_output(bel, wire_names[(io_x, io_y, io_zs)], entry[0]) + + # Global output + for gidx, ginfo in glbinfo.items(): + if (ginfo['pi_gb_x'], ginfo['pi_gb_y'], ginfo['pi_gb_pio']) == (io_x, io_y, io_z): + add_bel_output(bel, wire_names[(io_x, io_y, "glb_netwk_%d" % gidx)], entry[0] + '_GLOBAL') + def add_bel_ec(ec): ectype, x, y, z = ec bel = len(bel_name) @@ -894,15 +945,13 @@ def add_bel_ec(ec): bel_pos.append((x, y, z)) bel_wires.append(list()) for entry in extra_cells[ec]: - if is_ec_wire(entry) and "glb_netwk_" not in entry[1][2]: # TODO: osc glb output conflicts with GB + if is_ec_wire(entry): if is_ec_output(entry): add_bel_output(bel, wire_names[entry[1]], entry[0]) else: add_bel_input(bel, wire_names[entry[1]], entry[0]) elif is_ec_pll_clock_output(ec, entry): - x, y, z = entry[1] - z = 'io_{}/D_IN_0'.format(z) - add_bel_output(bel, wire_names[(x, y, z)], entry[0]) + add_pll_clock_output(bel, ec, entry) else: extra_cell_config[bel].append(entry) @@ -973,42 +1022,8 @@ for tile_xy, tile_type in sorted(tiles.items()): for i in range(2): add_bel_io(tile_xy[0], tile_xy[1], i) - if dev_name == "1k": - add_bel_gb(tile_xy, 7, 0, 0) - add_bel_gb(tile_xy, 7, 17, 1) - add_bel_gb(tile_xy, 13, 9, 2) - add_bel_gb(tile_xy, 0, 9, 3) - add_bel_gb(tile_xy, 6, 17, 4) - add_bel_gb(tile_xy, 6, 0, 5) - add_bel_gb(tile_xy, 0, 8, 6) - add_bel_gb(tile_xy, 13, 8, 7) - elif dev_name == "5k": - add_bel_gb(tile_xy, 13, 0, 0) - add_bel_gb(tile_xy, 13, 31, 1) - add_bel_gb(tile_xy, 19, 31, 2) - add_bel_gb(tile_xy, 6, 31, 3) - add_bel_gb(tile_xy, 12, 31, 4) - add_bel_gb(tile_xy, 12, 0, 5) - add_bel_gb(tile_xy, 6, 0, 6) - add_bel_gb(tile_xy, 19, 0, 7) - elif dev_name == "8k": - add_bel_gb(tile_xy, 33, 16, 7) - add_bel_gb(tile_xy, 0, 16, 6) - add_bel_gb(tile_xy, 17, 33, 1) - add_bel_gb(tile_xy, 17, 0, 0) - add_bel_gb(tile_xy, 0, 17, 3) - add_bel_gb(tile_xy, 33, 17, 2) - add_bel_gb(tile_xy, 16, 0, 5) - add_bel_gb(tile_xy, 16, 33, 4) - elif dev_name == "384": - add_bel_gb(tile_xy, 7, 4, 7) - add_bel_gb(tile_xy, 0, 4, 6) - add_bel_gb(tile_xy, 4, 9, 1) - add_bel_gb(tile_xy, 4, 0, 0) - add_bel_gb(tile_xy, 0, 5, 3) - add_bel_gb(tile_xy, 7, 5, 2) - add_bel_gb(tile_xy, 3, 0, 5) - add_bel_gb(tile_xy, 3, 9, 4) + for gidx, ginfo in glbinfo.items(): + add_bel_gb(tile_xy, ginfo['gb_x'], ginfo['gb_y'], gidx) if tile_type == "ramb": add_bel_ram(tile_xy[0], tile_xy[1]) @@ -1423,6 +1438,14 @@ for cell, timings in sorted(cell_timings.items()): bba.u32(len(timings), "num_paths") bba.r("cell_paths_%d" % beltype, "path_delays") +bba.l("global_network_info_%s" % dev_name, "GlobalNetworkInfoPOD") +for i in range(len(glbinfo)): + for k in ['gb_x', 'gb_y', 'pi_gb_x', 'pi_gb_y', 'pi_gb_pio', 'pi_eb_bank']: + bba.u8(glbinfo[i][k], k) + for k in ['pi_eb_x', 'pi_eb_y']: + bba.u16(glbinfo[i][k], k) + bba.u16(0, "padding") + bba.l("chip_info_%s" % dev_name) bba.u32(dev_width, "dev_width") bba.u32(dev_height, "dev_height") @@ -1433,6 +1456,7 @@ bba.u32(len(switchinfo), "num_switches") bba.u32(len(extra_cell_config), "num_belcfgs") bba.u32(len(packageinfo), "num_packages") bba.u32(len(cell_timings), "num_timing_cells") +bba.u32(len(glbinfo), "num_global_networks") bba.r("bel_data_%s" % dev_name, "bel_data") bba.r("wire_data_%s" % dev_name, "wire_data") bba.r("pip_data_%s" % dev_name, "pip_data") @@ -1441,6 +1465,7 @@ bba.r("bits_info_%s" % dev_name, "bits_info") bba.r("bel_config_%s" % dev_name if len(extra_cell_config) > 0 else None, "bel_config") bba.r("package_info_%s" % dev_name, "packages_data") bba.r("cell_timings_%s" % dev_name, "cell_timing") +bba.r("global_network_info_%s" % dev_name, "global_network_info") bba.r("tile_wire_names", "tile_wire_names") bba.pop() diff --git a/ice40/constids.inc b/ice40/constids.inc index dad08e59..e1c4992e 100644 --- a/ice40/constids.inc +++ b/ice40/constids.inc @@ -121,6 +121,8 @@ X(DYNAMICDELAY_7) X(LOCK) X(PLLOUT_A) X(PLLOUT_B) +X(PLLOUT_A_GLOBAL) +X(PLLOUT_B_GLOBAL) X(BYPASS) X(RESETB) X(LATCHINPUTVALUE) diff --git a/ice40/pack.cc b/ice40/pack.cc index b9360b74..dae19b2d 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -381,12 +381,44 @@ static void pack_constants(Context *ctx) } } +static std::unique_ptr create_padin_gbuf(Context *ctx, CellInfo *cell, IdString port_name, + std::string gbuf_name) +{ + // Find the matching SB_GB BEL connected to the same global network + BelId gb_bel; + BelId bel = ctx->getBelByName(ctx->id(cell->attrs[ctx->id("BEL")])); + auto wire = ctx->getBelPinWire(bel, port_name); + for (auto src_bel : ctx->getWireBelPins(wire)) { + if (ctx->getBelType(src_bel.bel) == id_SB_GB && src_bel.pin == id_GLOBAL_BUFFER_OUTPUT) { + gb_bel = src_bel.bel; + break; + } + } + + NPNR_ASSERT(gb_bel != BelId()); + + // Create a SB_GB Cell and lock it there + std::unique_ptr gb = create_ice_cell(ctx, ctx->id("SB_GB"), gbuf_name); + gb->attrs[ctx->id("FOR_PAD_IN")] = "1"; + gb->attrs[ctx->id("BEL")] = ctx->getBelName(gb_bel).str(ctx); + + // Reconnect the net to that port for easier identification it's a global net + replace_port(cell, port_name, gb.get(), id_GLOBAL_BUFFER_OUTPUT); + + return gb; +} + static bool is_nextpnr_iob(Context *ctx, CellInfo *cell) { return cell->type == ctx->id("$nextpnr_ibuf") || cell->type == ctx->id("$nextpnr_obuf") || cell->type == ctx->id("$nextpnr_iobuf"); } +static bool is_ice_iob(const Context *ctx, const CellInfo *cell) +{ + return is_sb_io(ctx, cell) || is_sb_gb_io(ctx, cell); +} + // Pack IO buffers static void pack_io(Context *ctx) { @@ -399,12 +431,15 @@ static void pack_io(Context *ctx) for (auto cell : sorted(ctx->cells)) { CellInfo *ci = cell.second; if (is_nextpnr_iob(ctx, ci)) { - CellInfo *sb = nullptr; + CellInfo *sb = nullptr, *rgb = nullptr; if (ci->type == ctx->id("$nextpnr_ibuf") || ci->type == ctx->id("$nextpnr_iobuf")) { - sb = net_only_drives(ctx, ci->ports.at(ctx->id("O")).net, is_sb_io, ctx->id("PACKAGE_PIN"), true, ci); + sb = net_only_drives(ctx, ci->ports.at(ctx->id("O")).net, is_ice_iob, ctx->id("PACKAGE_PIN"), true, ci); } else if (ci->type == ctx->id("$nextpnr_obuf")) { - sb = net_only_drives(ctx, ci->ports.at(ctx->id("I")).net, is_sb_io, ctx->id("PACKAGE_PIN"), true, ci); + NetInfo *net = ci->ports.at(ctx->id("I")).net; + sb = net_only_drives(ctx, net, is_ice_iob, ctx->id("PACKAGE_PIN"), true, ci); + if (net && net->driver.cell && is_sb_rgba_drv(ctx, net->driver.cell)) + rgb = net->driver.cell; } if (sb != nullptr) { // Trivial case, SB_IO used. Just destroy the net and the @@ -415,8 +450,8 @@ static void pack_io(Context *ctx) if (((ci->type == ctx->id("$nextpnr_ibuf") || ci->type == ctx->id("$nextpnr_iobuf")) && net->users.size() > 1) || (ci->type == ctx->id("$nextpnr_obuf") && (net->users.size() > 2 || net->driver.cell != nullptr))) - log_error("PACKAGE_PIN of SB_IO '%s' connected to more than a single top level IO.\n", - sb->name.c_str(ctx)); + log_error("PACKAGE_PIN of %s '%s' connected to more than a single top level IO.\n", + sb->type.c_str(ctx), sb->name.c_str(ctx)); if (net != nullptr) { delete_nets.insert(net->name); @@ -428,6 +463,11 @@ static void pack_io(Context *ctx) delete_nets.insert(net2->name); } } + } else if (rgb != nullptr) { + log_info("%s use by SB_RGBA_DRV %s, not creating SB_IO\n", ci->name.c_str(ctx), rgb->name.c_str(ctx)); + disconnect_port(ctx, ci, ctx->id("I")); + packed_cells.insert(ci->name); + continue; } else { // Create a SB_IO buffer std::unique_ptr ice_cell = @@ -438,6 +478,24 @@ static void pack_io(Context *ctx) } packed_cells.insert(ci->name); std::copy(ci->attrs.begin(), ci->attrs.end(), std::inserter(sb->attrs, sb->attrs.begin())); + } else if (is_sb_io(ctx, ci) || is_sb_gb_io(ctx, ci)) { + NetInfo *net = ci->ports.at(ctx->id("PACKAGE_PIN")).net; + if ((net != nullptr) && (net->users.size() > 1)) + log_error("PACKAGE_PIN of %s '%s' connected to more than a single top level IO.\n", ci->type.c_str(ctx), + ci->name.c_str(ctx)); + } + } + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (is_sb_gb_io(ctx, ci)) { + // If something is connecto the GLOBAL OUTPUT, create the fake 'matching' SB_GB + std::unique_ptr gb = + create_padin_gbuf(ctx, ci, id_GLOBAL_BUFFER_OUTPUT, "$gbuf_" + ci->name.str(ctx) + "_io"); + new_cells.push_back(std::move(gb)); + + // Make it a normal SB_IO with global marker + ci->type = ctx->id("SB_IO"); + ci->attrs[ctx->id("GLOBAL")] = "1"; } } for (auto pcell : packed_cells) { @@ -456,8 +514,8 @@ static bool is_logic_port(BaseCtx *ctx, const PortRef &port) { if (is_clock_port(ctx, port) || is_reset_port(ctx, port) || is_enable_port(ctx, port)) return false; - return !is_sb_io(ctx, port.cell) && !is_sb_pll40(ctx, port.cell) && !is_sb_pll40_pad(ctx, port.cell) && - port.cell->type != ctx->id("SB_GB"); + return !is_sb_io(ctx, port.cell) && !is_sb_gb_io(ctx, port.cell) && !is_gbuf(ctx, port.cell) && + !is_sb_pll40(ctx, port.cell); } static void insert_global(Context *ctx, NetInfo *net, bool is_reset, bool is_cen, bool is_logic) @@ -490,6 +548,14 @@ static void insert_global(Context *ctx, NetInfo *net, bool is_reset, bool is_cen } } net->users = keep_users; + + if (net->clkconstr) { + glbnet->clkconstr = std::unique_ptr(new ClockConstraint()); + glbnet->clkconstr->low = net->clkconstr->low; + glbnet->clkconstr->high = net->clkconstr->high; + glbnet->clkconstr->period = net->clkconstr->period; + } + ctx->nets[glbnet->name] = std::move(glbnet); ctx->cells[gb->name] = std::move(gb); } @@ -637,6 +703,22 @@ static std::unique_ptr spliceLUT(Context *ctx, CellInfo *ci, IdString return pt; } +// Force placement for cells that are unique anyway +static BelId cell_place_unique(Context *ctx, CellInfo *ci) +{ + for (auto bel : ctx->getBels()) { + if (ctx->getBelType(bel) != ci->type) + continue; + if (ctx->isBelLocked(bel)) + continue; + IdString bel_name = ctx->getBelName(bel); + ci->attrs[ctx->id("BEL")] = bel_name.str(ctx); + log_info(" constrained %s '%s' to %s\n", ci->type.c_str(ctx), ci->name.c_str(ctx), bel_name.c_str(ctx)); + return bel; + } + log_error("Unable to place cell '%s' of type '%s'\n", ci->name.c_str(ctx), ci->type.c_str(ctx)); +} + // Pack special functions static void pack_special(Context *ctx) { @@ -651,25 +733,33 @@ static void pack_special(Context *ctx) std::unique_ptr packed = create_ice_cell(ctx, ctx->id("ICESTORM_LFOSC"), ci->name.str(ctx) + "_OSC"); packed_cells.insert(ci->name); + cell_place_unique(ctx, packed.get()); replace_port(ci, ctx->id("CLKLFEN"), packed.get(), ctx->id("CLKLFEN")); replace_port(ci, ctx->id("CLKLFPU"), packed.get(), ctx->id("CLKLFPU")); - if (/*bool_or_default(ci->attrs, ctx->id("ROUTE_THROUGH_FABRIC"))*/ true) { // FIXME + if (bool_or_default(ci->attrs, ctx->id("ROUTE_THROUGH_FABRIC"))) { replace_port(ci, ctx->id("CLKLF"), packed.get(), ctx->id("CLKLF_FABRIC")); } else { replace_port(ci, ctx->id("CLKLF"), packed.get(), ctx->id("CLKLF")); + std::unique_ptr gb = + create_padin_gbuf(ctx, packed.get(), ctx->id("CLKLF"), "$gbuf_" + ci->name.str(ctx) + "_lfosc"); + new_cells.push_back(std::move(gb)); } new_cells.push_back(std::move(packed)); } else if (is_sb_hfosc(ctx, ci)) { std::unique_ptr packed = create_ice_cell(ctx, ctx->id("ICESTORM_HFOSC"), ci->name.str(ctx) + "_OSC"); packed_cells.insert(ci->name); + cell_place_unique(ctx, packed.get()); packed->params[ctx->id("CLKHF_DIV")] = str_or_default(ci->params, ctx->id("CLKHF_DIV"), "0b00"); replace_port(ci, ctx->id("CLKHFEN"), packed.get(), ctx->id("CLKHFEN")); replace_port(ci, ctx->id("CLKHFPU"), packed.get(), ctx->id("CLKHFPU")); - if (/*bool_or_default(ci->attrs, ctx->id("ROUTE_THROUGH_FABRIC"))*/ true) { // FIXME + if (bool_or_default(ci->attrs, ctx->id("ROUTE_THROUGH_FABRIC"))) { replace_port(ci, ctx->id("CLKHF"), packed.get(), ctx->id("CLKHF_FABRIC")); } else { replace_port(ci, ctx->id("CLKHF"), packed.get(), ctx->id("CLKHF")); + std::unique_ptr gb = + create_padin_gbuf(ctx, packed.get(), ctx->id("CLKHF"), "$gbuf_" + ci->name.str(ctx) + "_hfosc"); + new_cells.push_back(std::move(gb)); } new_cells.push_back(std::move(packed)); } else if (is_sb_spram(ctx, ci)) { @@ -705,6 +795,29 @@ static void pack_special(Context *ctx) replace_port(ci, ctx->id(pi.name.c_str(ctx)), packed.get(), ctx->id(newname)); } new_cells.push_back(std::move(packed)); + } else if (is_sb_rgba_drv(ctx, ci)) { + /* Force placement (no choices anyway) */ + cell_place_unique(ctx, ci); + + /* Disconnect all external ports and check there is no users (they should have been + * dealth with during IO packing */ + for (auto port : ci->ports) { + PortInfo &pi = port.second; + NetInfo *net = pi.net; + + if (net == nullptr) + continue; + if ((pi.name != ctx->id("RGB0")) && (pi.name != ctx->id("RGB1")) && (pi.name != ctx->id("RGB2"))) + continue; + + if (net->users.size() > 0) + log_error("SB_RGBA_DRV port connected to more than just package pin !\n"); + + ctx->nets.erase(net->name); + } + ci->ports.erase(ctx->id("RGB0")); + ci->ports.erase(ctx->id("RGB1")); + ci->ports.erase(ctx->id("RGB2")); } else if (is_sb_pll40(ctx, ci)) { bool is_pad = is_sb_pll40_pad(ctx, ci); bool is_core = !is_pad; @@ -719,6 +832,24 @@ static void pack_special(Context *ctx) for (auto param : ci->params) packed->params[param.first] = param.second; + const std::map pos_map_name = { + {ctx->id("PLLOUT_SELECT"), ctx->id("PLLOUT_SELECT_A")}, + {ctx->id("PLLOUT_SELECT_PORTA"), ctx->id("PLLOUT_SELECT_A")}, + {ctx->id("PLLOUT_SELECT_PORTB"), ctx->id("PLLOUT_SELECT_B")}, + }; + const std::map pos_map_val = { + {"GENCLK", 0}, + {"GENCLK_HALF", 1}, + {"SHIFTREG_90deg", 2}, + {"SHIFTREG_0deg", 3}, + }; + for (auto param : ci->params) + if (pos_map_name.find(param.first) != pos_map_name.end()) { + if (pos_map_val.find(param.second) == pos_map_val.end()) + log_error("Invalid PLL output selection '%s'\n", param.second.c_str()); + packed->params[pos_map_name.at(param.first)] = std::to_string(pos_map_val.at(param.second)); + } + auto feedback_path = packed->params[ctx->id("FEEDBACK_PATH")]; packed->params[ctx->id("FEEDBACK_PATH")] = feedback_path == "DELAY" @@ -731,32 +862,6 @@ static void pack_special(Context *ctx) NetInfo *pad_packagepin_net = nullptr; - int pllout_a_used = 0; - int pllout_b_used = 0; - for (auto port : ci->ports) { - PortInfo &pi = port.second; - if (pi.name == ctx->id("PLLOUTCOREA")) - pllout_a_used++; - if (pi.name == ctx->id("PLLOUTCOREB")) - pllout_b_used++; - if (pi.name == ctx->id("PLLOUTCORE")) - pllout_a_used++; - if (pi.name == ctx->id("PLLOUTGLOBALA")) - pllout_a_used++; - if (pi.name == ctx->id("PLLOUTGLOBALB")) - pllout_b_used++; - if (pi.name == ctx->id("PLLOUTGLOBAL")) - pllout_a_used++; - } - - if (pllout_a_used > 1) - log_error("PLL '%s' is using multiple ports mapping to PLLOUT_A output of the PLL\n", - ci->name.c_str(ctx)); - - if (pllout_b_used > 1) - log_error("PLL '%s' is using multiple ports mapping to PLLOUT_B output of the PLL\n", - ci->name.c_str(ctx)); - for (auto port : ci->ports) { PortInfo &pi = port.second; std::string newname = pi.name.str(ctx); @@ -764,24 +869,15 @@ static void pack_special(Context *ctx) if (bpos != std::string::npos) { newname = newname.substr(0, bpos) + "_" + newname.substr(bpos + 1, (newname.size() - bpos) - 2); } - if (pi.name == ctx->id("PLLOUTCOREA")) + + if (pi.name == ctx->id("PLLOUTCOREA") || pi.name == ctx->id("PLLOUTCORE")) newname = "PLLOUT_A"; if (pi.name == ctx->id("PLLOUTCOREB")) newname = "PLLOUT_B"; - if (pi.name == ctx->id("PLLOUTCORE")) - newname = "PLLOUT_A"; - if (pi.name == ctx->id("PLLOUTGLOBALA")) - newname = "PLLOUT_A"; + if (pi.name == ctx->id("PLLOUTGLOBALA") || pi.name == ctx->id("PLLOUTGLOBALA")) + newname = "PLLOUT_A_GLOBAL"; if (pi.name == ctx->id("PLLOUTGLOBALB")) - newname = "PLLOUT_B"; - if (pi.name == ctx->id("PLLOUTGLOBAL")) - newname = "PLLOUT_A"; - - if (pi.name == ctx->id("PLLOUTGLOBALA") || pi.name == ctx->id("PLLOUTGLOBALB") || - pi.name == ctx->id("PLLOUTGLOBAL")) - log_warning("PLL '%s' is using port %s but implementation does not actually " - "use the global clock output of the PLL\n", - ci->name.c_str(ctx), pi.name.str(ctx).c_str()); + newname = "PLLOUT_B_GLOBAL"; if (pi.name == ctx->id("PACKAGEPIN")) { if (!is_pad) { @@ -825,6 +921,8 @@ static void pack_special(Context *ctx) for (auto bel : ctx->getBels()) { if (ctx->getBelType(bel) != id_ICESTORM_PLL) continue; + if (ctx->isBelLocked(bel)) + continue; // A PAD PLL must have its' PACKAGEPIN on the SB_IO that's shared // with PLLOUT_A. @@ -866,11 +964,22 @@ static void pack_special(Context *ctx) packed->attrs[ctx->id("BEL")] = ctx->getBelName(bel).str(ctx); pll_bel = bel; constrained = true; + break; } if (!constrained) { log_error("Could not constrain PLL '%s' to any PLL Bel (too many PLLs?)\n", packed->name.c_str(ctx)); } + } else { + pll_bel = ctx->getBelByName(ctx->id(packed->attrs[ctx->id("BEL")])); + if (ctx->getBelType(pll_bel) != id_ICESTORM_PLL) + log_error("PLL '%s' is constrained to BEL %s which isn't a ICESTORM_PLL BEL\n", + packed->name.c_str(ctx), ctx->getBelName(pll_bel).c_str(ctx)); + if (ctx->isBelLocked(pll_bel)) + log_error("PLL '%s' is constrained to locked BEL %s\n", packed->name.c_str(ctx), + ctx->getBelName(pll_bel).c_str(ctx)); + log_info(" constrained PLL '%s' to %s\n", packed->name.c_str(ctx), + ctx->getBelName(pll_bel).c_str(ctx)); } // Delete the original PACKAGEPIN net if needed. @@ -939,6 +1048,24 @@ static void pack_special(Context *ctx) } } + // Handle the global buffer connections + for (auto port : packed->ports) { + PortInfo &pi = port.second; + bool is_b_port; + + if (pi.name == ctx->id("PLLOUT_A_GLOBAL")) + is_b_port = false; + else if (pi.name == ctx->id("PLLOUT_B_GLOBAL")) + is_b_port = true; + else + continue; + + std::unique_ptr gb = + create_padin_gbuf(ctx, packed.get(), pi.name, + "$gbuf_" + ci->name.str(ctx) + "_pllout_" + (is_b_port ? "b" : "a")); + new_cells.push_back(std::move(gb)); + } + new_cells.push_back(std::move(packed)); } } @@ -958,13 +1085,13 @@ bool Arch::pack() try { log_break(); pack_constants(ctx); - promote_globals(ctx); pack_io(ctx); pack_lut_lutffs(ctx); pack_nonlut_ffs(ctx); pack_carries(ctx); pack_ram(ctx); pack_special(ctx); + promote_globals(ctx); ctx->assignArchInfo(); constrain_chains(ctx); ctx->assignArchInfo();