From 553c61193605e0ff0fde5aa478e32e5b9c5f3518 Mon Sep 17 00:00:00 2001 From: "William D. Jones" Date: Sat, 3 Nov 2018 13:00:39 -0400 Subject: [PATCH 01/67] Rename io.{h,cc} to pio.{h,cc} to avoid naming conflict with Windows-provided io.h. Signed-off-by: William D. Jones --- ecp5/bitstream.cc | 2 +- ecp5/{io.cc => pio.cc} | 2 +- ecp5/{io.h => pio.h} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename ecp5/{io.cc => pio.cc} (99%) rename ecp5/{io.h => pio.h} (100%) diff --git a/ecp5/bitstream.cc b/ecp5/bitstream.cc index 95256732..1c150ae2 100644 --- a/ecp5/bitstream.cc +++ b/ecp5/bitstream.cc @@ -26,7 +26,7 @@ #include #include "config.h" -#include "io.h" +#include "pio.h" #include "log.h" #include "util.h" 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 From 14ad19e0641f27e413ee9684889df755463e238d Mon Sep 17 00:00:00 2001 From: "William D. Jones" Date: Sat, 3 Nov 2018 13:12:37 -0400 Subject: [PATCH 02/67] Use native PATH environment-variable separator on Windows for PYTHONPATH. Fixes 'Bad address' error in cmake. Signed-off-by: William D. Jones --- ecp5/family.cmake | 4 ++++ 1 file changed, 4 insertions(+) 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) From 83b1c436303ad152cf78a517979d8ab7e24eadf6 Mon Sep 17 00:00:00 2001 From: David Shah Date: Wed, 24 Oct 2018 18:10:19 +0100 Subject: [PATCH 03/67] timing: Working on a timing constraint API Signed-off-by: David Shah --- common/nextpnr.h | 103 +++++++++++++++++++++++++++++++++++++++++++++++ common/timing.cc | 3 +- 2 files changed, 105 insertions(+), 1 deletion(-) diff --git a/common/nextpnr.h b/common/nextpnr.h index 59ae0323..909d49a1 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,8 @@ struct NetInfo : ArchNetInfo // wire -> uphill_pip std::unordered_map wires; + ClockConstraint *clkconstr = nullptr; + Region *region = nullptr; }; @@ -293,6 +314,7 @@ struct PortInfo IdString name; NetInfo *net; PortType type; + TimingConstrObjectId tmg_id; }; struct CellInfo : ArchCellInfo @@ -320,6 +342,7 @@ struct CellInfo : ArchCellInfo // parent.[xyz] := 0 when (constr_parent == nullptr) Region *region = nullptr; + TimingConstrObjectId tmg_id; }; enum TimingPortClass @@ -335,6 +358,60 @@ enum TimingPortClass TMG_IGNORE, // Asynchronous to all clocks, "don't care", and should be ignored (false path) for analysis }; +struct TimingClockingInfo +{ + IdString clock_port; // Port name of clock domain + enum + { + RISING, + FALLING + } 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; +}; + struct DeterministicRNG { uint64_t rngstate; @@ -431,6 +508,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() @@ -514,6 +596,27 @@ 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); }; NEXTPNR_NAMESPACE_END diff --git a/common/timing.cc b/common/timing.cc index 2769cd65..196168ab 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -215,7 +215,8 @@ struct Timing for (auto net : boost::adaptors::reverse(topographical_order)) { auto &nd = net_data.at(net); // Ignore false startpoints - if (nd.false_startpoint) continue; + 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) { From b6312abc5dd7a357ad75269ae2e190607c90d671 Mon Sep 17 00:00:00 2001 From: David Shah Date: Sun, 28 Oct 2018 11:52:21 +0000 Subject: [PATCH 04/67] timing: Implementing parts of new timing API Signed-off-by: David Shah --- common/nextpnr.cc | 94 +++++++++++++++++++++++++++++++++++++++++++++++ common/nextpnr.h | 2 + 2 files changed, 96 insertions(+) diff --git a/common/nextpnr.cc b/common/nextpnr.cc index 4e6407b2..5bcf913a 100644 --- a/common/nextpnr.cc +++ b/common/nextpnr.cc @@ -51,6 +51,100 @@ 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); +} + WireId Context::getNetinfoSourceWire(const NetInfo *net_info) const { if (net_info->driver.cell == nullptr) diff --git a/common/nextpnr.h b/common/nextpnr.h index 909d49a1..96ff266c 100644 --- a/common/nextpnr.h +++ b/common/nextpnr.h @@ -299,6 +299,8 @@ struct NetInfo : ArchNetInfo ClockConstraint *clkconstr = nullptr; + TimingConstrObjectId tmg_id; + Region *region = nullptr; }; From 3ca02cc55c543829ab608b82af79f3747bc2c808 Mon Sep 17 00:00:00 2001 From: David Shah Date: Tue, 30 Oct 2018 10:07:37 +0000 Subject: [PATCH 05/67] Working on adding multiple domains to timing analysis Signed-off-by: David Shah --- common/timing.cc | 120 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 87 insertions(+), 33 deletions(-) diff --git a/common/timing.cc b/common/timing.cc index 196168ab..488b4ddf 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -29,6 +29,46 @@ NEXTPNR_NAMESPACE_BEGIN +namespace { + struct ClockEvent { + IdString clock; + enum { + POSEDGE, + NEGEDGE + } edge; + }; + + struct ClockPair { + ClockEvent start, end; + }; +} + +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; + } + }; + +} +NEXTPNR_NAMESPACE_BEGIN + typedef std::vector PortRefVector; typedef std::map DelayFrequency; @@ -41,11 +81,20 @@ struct Timing PortRefVector *crit_path; DelayFrequency *slack_histogram; + struct ClockDomain + { + IdString net; + enum { + RISING, + FALLING + } edge; + }; + struct TimingData { TimingData() : max_arrival(), max_path_length(), min_remaining_budget() {} - TimingData(delay_t max_arrival) : max_arrival(max_arrival), max_path_length(), min_remaining_budget() {} - delay_t max_arrival; + TimingData(ClockPair dest, delay_t max_arrival) : max_path_length(), min_remaining_budget() {} + std::unordedelay_t max_arrival; unsigned max_path_length = 0; delay_t min_remaining_budget; bool false_startpoint = false; @@ -65,7 +114,7 @@ struct Timing // 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; @@ -92,13 +141,13 @@ struct Timing 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()}); + net_data[o->net][ClockEvent{IdString(), ClockEvent::POSEDGE}] = TimingData{clkToQ.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)); + net_data[o->net][ClockEvent{IdString(), ClockEvent::POSEDGE}] = td; } // 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 @@ -174,38 +223,43 @@ struct Timing // 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 { - 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); + auto &nd_map = net_data.at(net); + for (auto &startdomain : nd_map) { + ClockEvent start_clk = startdomain.first; + auto &nd = startdomain.second; + 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 { + 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][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; From 122771cac312ddff2735e9c1ecd694c8599027b6 Mon Sep 17 00:00:00 2001 From: David Shah Date: Fri, 2 Nov 2018 13:35:59 +0000 Subject: [PATCH 06/67] timing: iCE40 Arch API changes for clocking info Signed-off-by: David Shah --- docs/archapi.md | 12 ++++++-- ice40/arch.cc | 80 ++++++++++++++++++++++++++++++++++++++----------- ice40/arch.h | 6 ++-- ice40/pack.cc | 3 +- 4 files changed, 77 insertions(+), 24 deletions(-) diff --git a/docs/archapi.md b/docs/archapi.md index 73443c15..6b22c6df 100644 --- a/docs/archapi.md +++ b/docs/archapi.md @@ -455,11 +455,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/ice40/arch.cc b/ice40/arch.cc index eb26ae5a..021be872 100644 --- a/ice40/arch.cc +++ b/ice40/arch.cc @@ -856,8 +856,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,18 +871,15 @@ 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 + } else return TMG_COMB_OUTPUT; - } - else { + } else { if (cell->lcInfo.dffEnable) { - clockPort = id_CLK; + clockInfoCount = 1; return TMG_REGISTER_INPUT; - } - else + } else return TMG_COMB_INPUT; } } else if (cell->type == id_ICESTORM_RAM) { @@ -889,23 +887,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) 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; @@ -934,6 +931,53 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id 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 ? TimingClockingInfo::FALLING : TimingClockingInfo::RISING; + 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")) ? TimingClockingInfo::FALLING + : TimingClockingInfo::RISING; + } else { + info.clock_port = id_WCLK; + info.edge = bool_or_default(cell->params, id("NEG_CLK_W")) ? TimingClockingInfo::FALLING + : TimingClockingInfo::RISING; + } + 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 = id_CLK; + info.edge = TimingClockingInfo::RISING; + 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) diff --git a/ice40/arch.h b/ice40/arch.h index bdcee3b8..ff2f7e4c 100644 --- a/ice40/arch.h +++ b/ice40/arch.h @@ -798,8 +798,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/pack.cc b/ice40/pack.cc index edd12f92..b9360b74 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -462,7 +462,8 @@ static bool is_logic_port(BaseCtx *ctx, const PortRef &port) static void insert_global(Context *ctx, NetInfo *net, bool is_reset, bool is_cen, bool is_logic) { - log_info("promoting %s%s%s%s\n", net->name.c_str(ctx), is_reset ? " [reset]" : "", is_cen ? " [cen]" : "", is_logic ? " [logic]" : ""); + log_info("promoting %s%s%s%s\n", net->name.c_str(ctx), is_reset ? " [reset]" : "", is_cen ? " [cen]" : "", + is_logic ? " [logic]" : ""); std::string glb_name = net->name.str(ctx) + std::string("_$glb_") + (is_reset ? "sr" : (is_cen ? "ce" : "clk")); std::unique_ptr gb = create_ice_cell(ctx, ctx->id("SB_GB"), "$gbuf_" + glb_name); From 9687f7d1da805103cd66260fac15f5d8b6617cbb Mon Sep 17 00:00:00 2001 From: David Shah Date: Fri, 2 Nov 2018 16:56:53 +0000 Subject: [PATCH 07/67] Working on multi-clock analysis Signed-off-by: David Shah --- common/nextpnr.cc | 21 ++- common/nextpnr.h | 12 +- common/timing.cc | 418 +++++++++++++++++++++++++++------------------- ice40/arch.cc | 10 +- 4 files changed, 273 insertions(+), 188 deletions(-) diff --git a/common/nextpnr.cc b/common/nextpnr.cc index 5bcf913a..3621217b 100644 --- a/common/nextpnr.cc +++ b/common/nextpnr.cc @@ -51,13 +51,15 @@ 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 BaseCtx::timingWildcardObject() +{ TimingConstrObjectId id; id.index = 0; return id; } -TimingConstrObjectId BaseCtx::timingClockDomainObject(NetInfo *clockDomain) { +TimingConstrObjectId BaseCtx::timingClockDomainObject(NetInfo *clockDomain) +{ NPNR_ASSERT(clockDomain->clkconstr != nullptr); if (clockDomain->clkconstr->domain_tmg_id != TimingConstrObjectId()) { return clockDomain->clkconstr->domain_tmg_id; @@ -74,7 +76,8 @@ TimingConstrObjectId BaseCtx::timingClockDomainObject(NetInfo *clockDomain) { } } -TimingConstrObjectId BaseCtx::timingNetObject(NetInfo *net) { +TimingConstrObjectId BaseCtx::timingNetObject(NetInfo *net) +{ if (net->tmg_id != TimingConstrObjectId()) { return net->tmg_id; } else { @@ -90,7 +93,8 @@ TimingConstrObjectId BaseCtx::timingNetObject(NetInfo *net) { } } -TimingConstrObjectId BaseCtx::timingCellObject(CellInfo *cell) { +TimingConstrObjectId BaseCtx::timingCellObject(CellInfo *cell) +{ if (cell->tmg_id != TimingConstrObjectId()) { return cell->tmg_id; } else { @@ -106,7 +110,8 @@ TimingConstrObjectId BaseCtx::timingCellObject(CellInfo *cell) { } } -TimingConstrObjectId BaseCtx::timingPortObject(CellInfo *cell, IdString port) { +TimingConstrObjectId BaseCtx::timingPortObject(CellInfo *cell, IdString port) +{ if (cell->ports.at(port).tmg_id != TimingConstrObjectId()) { return cell->ports.at(port).tmg_id; } else { @@ -123,7 +128,8 @@ TimingConstrObjectId BaseCtx::timingPortObject(CellInfo *cell, IdString port) { } } -void BaseCtx::addConstraint(std::unique_ptr constr) { +void BaseCtx::addConstraint(std::unique_ptr constr) +{ for (auto fromObj : constr->from) constrsFrom.emplace(fromObj, constr.get()); for (auto toObj : constr->to) @@ -132,7 +138,8 @@ void BaseCtx::addConstraint(std::unique_ptr constr) { constraints[name] = std::move(constr); } -void BaseCtx::removeConstraint(IdString constrName) { +void BaseCtx::removeConstraint(IdString constrName) +{ TimingConstraint *constr = constraints[constrName].get(); for (auto fromObj : constr->from) { auto fromConstrs = constrsFrom.equal_range(fromObj); diff --git a/common/nextpnr.h b/common/nextpnr.h index 96ff266c..fae9770e 100644 --- a/common/nextpnr.h +++ b/common/nextpnr.h @@ -360,14 +360,16 @@ 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 - enum - { - RISING, - FALLING - } edge; + ClockEdge edge; DelayInfo setup, hold; // Input timing checks DelayInfo clockToQ; // Output clock-to-Q time }; diff --git a/common/timing.cc b/common/timing.cc index 488b4ddf..b794f116 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include "log.h" @@ -30,80 +31,83 @@ NEXTPNR_NAMESPACE_BEGIN namespace { - struct ClockEvent { - IdString clock; - enum { - POSEDGE, - NEGEDGE - } edge; - }; +struct ClockEvent +{ + IdString clock; + ClockEdge edge; +}; - struct ClockPair { - ClockEvent start, end; - }; -} +struct ClockPair +{ + ClockEvent start, 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 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; - } - }; +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; - - struct ClockDomain - { - IdString net; - enum { - RISING, - FALLING - } edge; - }; + IdString async_clock; struct TimingData { TimingData() : max_arrival(), max_path_length(), min_remaining_budget() {} - TimingData(ClockPair dest, delay_t max_arrival) : max_path_length(), min_remaining_budget() {} - std::unordedelay_t max_arrival; + TimingData(delay_t max_arrival) : max_arrival(max_arrival), max_path_length(), min_remaining_budget() {} + delay_t max_arrival; 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$")) { } @@ -133,21 +137,26 @@ 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[o->net][ClockEvent{IdString(), ClockEvent::POSEDGE}] = 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[o->net][ClockEvent{IdString(), ClockEvent::POSEDGE}] = td; + net_data[o->net][ClockEvent{async_clock, RISING_EDGE}] = td; } // 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 @@ -169,14 +178,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 || @@ -231,13 +241,15 @@ struct Timing 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); + 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 usr_arrival = net_arrival + net_delay; + if (portClass == TMG_REGISTER_INPUT || portClass == TMG_ENDPOINT || portClass == TMG_IGNORE) { + // Skip } else { - 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) @@ -259,59 +271,93 @@ struct Timing } } } - } - 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) - 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; + auto path_budget = clk_period - endpoint_arrival; + delay_t period; - if (path_budget < min_slack) { - min_slack = path_budget; - if (crit_path) { - crit_path->clear(); - crit_path->push_back(&usr); - crit_net = net; + if (edge == startdomain.first.edge) { + period = clk_period; + } else { + period = clk_period / 2; + } + + 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 = clk_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 ? RISING_EDGE : clkInfo.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; + 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); } } } @@ -319,47 +365,52 @@ 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 + 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; } @@ -422,56 +473,83 @@ void assign_budget(Context *ctx, bool quiet) void timing_analysis(Context *ctx, bool print_histogram, bool print_path) { - PortRefVector crit_path; + 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 ? &crit_paths : nullptr, print_histogram ? &slack_histogram : nullptr); auto min_slack = timing.walk_paths(); if (print_path) { - if (crit_path.empty()) { - log_info("Design contains no timing paths\n"); + std::map> clock_reports; + 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; + delay_t slack = path.second.path_period - path.second.path_delay; + if (!clock_reports.count(a.clock) || + slack < (clock_reports.at(a.clock).second.path_period - clock_reports.at(a.clock).second.path_delay)) { + clock_reports[a.clock] = path; + } + } + if (clock_reports.empty()) { + log_warning("No clocks found in design"); } else { delay_t total = 0; log_break(); - log_info("Critical path report:\n"); - log_info("curr total\n"); + for (auto &clock : clock_reports) { + log_info("Critical path report for clock '%s':\n", clock.first.c_str(ctx)); + log_info("curr total\n"); + auto &crit_path = clock.second.second.ports; + auto &front = crit_path.front(); + auto &front_port = front->cell->ports.at(front->port); + auto &front_driver = front_port.net->driver; - auto &front = crit_path.front(); - auto &front_port = front->cell->ports.at(front->port); - auto &front_driver = front_port.net->driver; + int port_clocks; + ctx->getPortTimingClass(front_driver.cell, front_driver.port, port_clocks); + 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 == clock.first && + clockInfo.edge == clock.second.first.start.edge) { + IdString last_port = clockInfo.clock_port; - IdString last_port; - ctx->getPortTimingClass(front_driver.cell, front_driver.port, last_port); - for (auto sink : crit_path) { - auto sink_cell = sink->cell; - auto &port = sink_cell->ports.at(sink->port); - auto net = port.net; - auto &driver = net->driver; - auto driver_cell = driver.cell; - DelayInfo comb_delay; - 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)); - auto net_delay = ctx->getNetinfoRouteDelay(net, *sink); - total += net_delay; - auto driver_loc = ctx->getBelLocation(driver_cell->bel); - auto sink_loc = ctx->getBelLocation(sink_cell->bel); - log_info("%4.1f %4.1f Net %s budget %f ns (%d,%d) -> (%d,%d)\n", ctx->getDelayNS(net_delay), - ctx->getDelayNS(total), net->name.c_str(ctx), ctx->getDelayNS(sink->budget), driver_loc.x, - driver_loc.y, sink_loc.x, sink_loc.y); - log_info(" Sink %s.%s\n", sink_cell->name.c_str(ctx), sink->port.c_str(ctx)); - last_port = sink->port; + for (auto sink : crit_path) { + auto sink_cell = sink->cell; + auto &port = sink_cell->ports.at(sink->port); + auto net = port.net; + auto &driver = net->driver; + auto driver_cell = driver.cell; + DelayInfo comb_delay; + 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)); + auto net_delay = ctx->getNetinfoRouteDelay(net, *sink); + total += net_delay; + auto driver_loc = ctx->getBelLocation(driver_cell->bel); + auto sink_loc = ctx->getBelLocation(sink_cell->bel); + log_info("%4.1f %4.1f Net %s budget %f ns (%d,%d) -> (%d,%d)\n", + ctx->getDelayNS(net_delay), ctx->getDelayNS(total), net->name.c_str(ctx), + ctx->getDelayNS(sink->budget), driver_loc.x, driver_loc.y, sink_loc.x, sink_loc.y); + log_info(" Sink %s.%s\n", sink_cell->name.c_str(ctx), sink->port.c_str(ctx)); + last_port = sink->port; + } + } + } + log_break(); + double Fmax; + if (clock.second.first.start.edge == clock.second.first.end.edge) + Fmax = 1000 / ctx->getDelayNS(clock.second.second.path_delay); + else + Fmax = 500 / ctx->getDelayNS(clock.second.second.path_delay); + log_info("Max frequency for clock '%s': %.02f MHz\n", clock.first.c_str(ctx), Fmax); + log_break(); } - 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)); - if (print_histogram && slack_histogram.size() > 0) { unsigned num_bins = 20; unsigned bar_width = 60; diff --git a/ice40/arch.cc b/ice40/arch.cc index 021be872..c14fecc4 100644 --- a/ice40/arch.cc +++ b/ice40/arch.cc @@ -936,7 +936,7 @@ TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port TimingClockingInfo info; if (cell->type == id_ICESTORM_LC) { info.clock_port = id_CLK; - info.edge = cell->lcInfo.negClk ? TimingClockingInfo::FALLING : TimingClockingInfo::RISING; + 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); @@ -947,12 +947,10 @@ TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port } 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")) ? TimingClockingInfo::FALLING - : TimingClockingInfo::RISING; + 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")) ? TimingClockingInfo::FALLING - : TimingClockingInfo::RISING; + 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); @@ -963,7 +961,7 @@ TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port } } else if (cell->type == id_ICESTORM_DSP || cell->type == id_ICESTORM_SPRAM) { info.clock_port = id_CLK; - info.edge = TimingClockingInfo::RISING; + 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) From 143abc603482b2429d481d445333ebfab698498a Mon Sep 17 00:00:00 2001 From: David Shah Date: Fri, 2 Nov 2018 17:26:14 +0000 Subject: [PATCH 08/67] timing: Multiple clock analysis Signed-off-by: David Shah --- common/nextpnr.h | 6 ++++++ common/timing.cc | 34 +++++++++++++++++++++++++--------- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/common/nextpnr.h b/common/nextpnr.h index fae9770e..216e1532 100644 --- a/common/nextpnr.h +++ b/common/nextpnr.h @@ -416,6 +416,12 @@ struct TimingConstraint 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; diff --git a/common/timing.cc b/common/timing.cc index b794f116..3969a2ac 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -35,11 +35,15 @@ 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 @@ -353,10 +357,15 @@ struct Timing 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).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 (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); + } } } } @@ -390,11 +399,14 @@ struct Timing continue; // And find the fanin net with the latest arrival time - 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 (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) @@ -539,14 +551,18 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_path) } } log_break(); + } + log_break(); + for (auto &clock : clock_reports) { + double Fmax; if (clock.second.first.start.edge == clock.second.first.end.edge) Fmax = 1000 / ctx->getDelayNS(clock.second.second.path_delay); else Fmax = 500 / ctx->getDelayNS(clock.second.second.path_delay); log_info("Max frequency for clock '%s': %.02f MHz\n", clock.first.c_str(ctx), Fmax); - log_break(); } + log_break(); } } From cba9b528e8427e84bf1f6c6b8c34dc2bbe2d6bdf Mon Sep 17 00:00:00 2001 From: David Shah Date: Fri, 2 Nov 2018 18:59:04 +0000 Subject: [PATCH 09/67] timing: Improve Fmax output and print cross-clock paths Signed-off-by: David Shah --- common/router1.cc | 2 +- common/timing.cc | 182 +++++++++++++++++++++++++++++++++------------- common/timing.h | 2 +- 3 files changed, 132 insertions(+), 54 deletions(-) diff --git a/common/router1.cc b/common/router1.cc index c4708de7..08c9d701 100644 --- a/common/router1.cc +++ b/common/router1.cc @@ -951,7 +951,7 @@ bool router1(Context *ctx, const Router1Cfg &cfg) #ifndef NDEBUG ctx->check(); #endif - 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; } catch (log_execution_error_exception) { diff --git a/common/timing.cc b/common/timing.cc index 3969a2ac..73e48871 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -406,7 +406,6 @@ struct Timing crit_ipin = &port.second; } } - } if (!crit_ipin) @@ -483,17 +482,28 @@ 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) { + 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_paths : 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(); - - if (print_path) { - std::map> clock_reports; + std::map> clock_reports; + std::vector xclock_paths; + if (print_path || print_fmax) { for (auto path : crit_paths) { const ClockEvent &a = path.first.start; const ClockEvent &b = path.first.end; @@ -505,65 +515,133 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_path) clock_reports[a.clock] = path; } } + + 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"); - } else { - delay_t total = 0; - log_break(); - for (auto &clock : clock_reports) { - log_info("Critical path report for clock '%s':\n", clock.first.c_str(ctx)); - log_info("curr total\n"); - auto &crit_path = clock.second.second.ports; - auto &front = crit_path.front(); - auto &front_port = front->cell->ports.at(front->port); - auto &front_driver = front_port.net->driver; + } - int port_clocks; - ctx->getPortTimingClass(front_driver.cell, front_driver.port, port_clocks); + 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) { + auto print_path_report = [ctx] (ClockPair &clocks, PortRefVector &crit_path) { + delay_t total = 0; + auto &front = crit_path.front(); + auto &front_port = front->cell->ports.at(front->port); + auto &front_driver = front_port.net->driver; + + 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 == clock.first && - clockInfo.edge == clock.second.first.start.edge) { - IdString last_port = clockInfo.clock_port; - - for (auto sink : crit_path) { - auto sink_cell = sink->cell; - auto &port = sink_cell->ports.at(sink->port); - auto net = port.net; - auto &driver = net->driver; - auto driver_cell = driver.cell; - DelayInfo comb_delay; - 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)); - auto net_delay = ctx->getNetinfoRouteDelay(net, *sink); - total += net_delay; - auto driver_loc = ctx->getBelLocation(driver_cell->bel); - auto sink_loc = ctx->getBelLocation(sink_cell->bel); - log_info("%4.1f %4.1f Net %s budget %f ns (%d,%d) -> (%d,%d)\n", - ctx->getDelayNS(net_delay), ctx->getDelayNS(total), net->name.c_str(ctx), - ctx->getDelayNS(sink->budget), driver_loc.x, driver_loc.y, sink_loc.x, sink_loc.y); - log_info(" Sink %s.%s\n", sink_cell->name.c_str(ctx), sink->port.c_str(ctx)); - last_port = sink->port; - } + if (clknet != nullptr && clknet->name == clocks.start.clock && + clockInfo.edge == clocks.start.edge) { } } - log_break(); } - log_break(); - for (auto &clock : clock_reports) { - double Fmax; - if (clock.second.first.start.edge == clock.second.first.end.edge) - Fmax = 1000 / ctx->getDelayNS(clock.second.second.path_delay); - else - Fmax = 500 / ctx->getDelayNS(clock.second.second.path_delay); - log_info("Max frequency for clock '%s': %.02f MHz\n", clock.first.c_str(ctx), Fmax); + for (auto sink : crit_path) { + auto sink_cell = sink->cell; + auto &port = sink_cell->ports.at(sink->port); + auto net = port.net; + auto &driver = net->driver; + auto driver_cell = driver.cell; + DelayInfo comb_delay; + if (last_port == driver.port) { + // Case where we start with a STARTPOINT etc + comb_delay.delay = 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)); + auto net_delay = ctx->getNetinfoRouteDelay(net, *sink); + total += net_delay; + auto driver_loc = ctx->getBelLocation(driver_cell->bel); + auto sink_loc = ctx->getBelLocation(sink_cell->bel); + log_info("%4.1f %4.1f Net %s budget %f ns (%d,%d) -> (%d,%d)\n", ctx->getDelayNS(net_delay), + ctx->getDelayNS(total), net->name.c_str(ctx), ctx->getDelayNS(sink->budget), + driver_loc.x, driver_loc.y, sink_loc.x, sink_loc.y); + log_info(" Sink %s.%s\n", sink_cell->name.c_str(ctx), sink->port.c_str(ctx)); + last_port = sink->port; } + + }; + + for (auto &clock : clock_reports) { log_break(); + log_info("Critical path report for clock '%s':\n", clock.first.c_str(ctx)); + log_info("curr total\n"); + 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()); + log_info("curr total\n"); + auto &crit_path = crit_paths.at(xclock).ports; + print_path_report(xclock, crit_path); + } + } + if (print_fmax) { + log_break(); + for (auto &clock : clock_reports) { + + double Fmax; + if (clock.second.first.start.edge == clock.second.first.end.edge) + Fmax = 1000 / ctx->getDelayNS(clock.second.second.path_delay); + else + Fmax = 500 / ctx->getDelayNS(clock.second.second.path_delay); + log_info("Max frequency for clock '%s': %.02f MHz\n", clock.first.c_str(ctx), Fmax); + } + log_break(); + + 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) { 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 From fad69d49309ec979f0251a3213f212968629a8ed Mon Sep 17 00:00:00 2001 From: David Shah Date: Fri, 2 Nov 2018 19:13:50 +0000 Subject: [PATCH 10/67] timing: Don't include false startpoints in async paths Signed-off-by: David Shah --- common/timing.cc | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/common/timing.cc b/common/timing.cc index 73e48871..8ecea38e 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -237,10 +237,14 @@ struct Timing // Go forwards topographically to find the maximum arrival time and max path length for each net for (auto net : topographical_order) { + 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; @@ -282,6 +286,8 @@ struct Timing // 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)) { + if (!net_data.count(net)) + continue; auto &nd_map = net_data.at(net); for (auto &startdomain : nd_map) { auto &nd = startdomain.second; @@ -399,7 +405,7 @@ struct Timing continue; // And find the fanin net with the latest arrival time - if (net_data.at(port.second.net).count(crit_pair.first.start)) { + 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; @@ -593,7 +599,7 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p log_info(" Sink %s.%s\n", sink_cell->name.c_str(ctx), sink->port.c_str(ctx)); last_port = sink->port; } - + }; for (auto &clock : clock_reports) { From e633aa09ccd89040d450b7cb4b7864c4fd8c0468 Mon Sep 17 00:00:00 2001 From: David Shah Date: Sat, 3 Nov 2018 14:09:27 +0000 Subject: [PATCH 11/67] timing: Fix handling of clock inputs Signed-off-by: David Shah --- common/timing.cc | 27 +++++++++++++++++---------- ice40/arch.cc | 4 ++-- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/common/timing.cc b/common/timing.cc index 8ecea38e..10b321f7 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -160,8 +160,15 @@ struct Timing topographical_order.emplace_back(o->net); TimingData td; td.false_startpoint = (portClass == TMG_GEN_CLOCK || portClass == TMG_IGNORE); + 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) { @@ -254,7 +261,8 @@ struct Timing auto net_delay = net_delays ? ctx->getNetinfoRouteDelay(net, usr) : delay_t(); auto usr_arrival = net_arrival + net_delay; - if (portClass == TMG_REGISTER_INPUT || portClass == TMG_ENDPOINT || portClass == TMG_IGNORE) { + 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); @@ -347,7 +355,7 @@ struct Timing 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 ? RISING_EDGE : clkInfo.edge, clkInfo.setup.maxDelay()); + process_endpoint(clksig, clknet ? clkInfo.edge : RISING_EDGE, clkInfo.setup.maxDelay()); } } else { process_endpoint(async_clock, RISING_EDGE, 0); @@ -405,7 +413,8 @@ struct Timing continue; // 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)) { + 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; @@ -554,7 +563,7 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p } if (print_path) { - auto print_path_report = [ctx] (ClockPair &clocks, PortRefVector &crit_path) { + auto print_path_report = [ctx](ClockPair &clocks, PortRefVector &crit_path) { delay_t total = 0; auto &front = crit_path.front(); auto &front_port = front->cell->ports.at(front->port); @@ -587,19 +596,18 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p 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)); + 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)); auto net_delay = ctx->getNetinfoRouteDelay(net, *sink); total += net_delay; auto driver_loc = ctx->getBelLocation(driver_cell->bel); auto sink_loc = ctx->getBelLocation(sink_cell->bel); log_info("%4.1f %4.1f Net %s budget %f ns (%d,%d) -> (%d,%d)\n", ctx->getDelayNS(net_delay), - ctx->getDelayNS(total), net->name.c_str(ctx), ctx->getDelayNS(sink->budget), - driver_loc.x, driver_loc.y, sink_loc.x, sink_loc.y); + ctx->getDelayNS(total), net->name.c_str(ctx), ctx->getDelayNS(sink->budget), driver_loc.x, + driver_loc.y, sink_loc.x, sink_loc.y); log_info(" Sink %s.%s\n", sink_cell->name.c_str(ctx), sink->port.c_str(ctx)); last_port = sink->port; } - }; for (auto &clock : clock_reports) { @@ -610,7 +618,6 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p print_path_report(clock.second.first, crit_path); } - for (auto &xclock : xclock_paths) { log_break(); std::string start = format_event(xclock.start); diff --git a/ice40/arch.cc b/ice40/arch.cc index c14fecc4..2d910d6f 100644 --- a/ice40/arch.cc +++ b/ice40/arch.cc @@ -894,7 +894,7 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, in else return TMG_REGISTER_INPUT; } else if (cell->type == id_ICESTORM_DSP || cell->type == id_ICESTORM_SPRAM) { - if (port == id_CLK) + if (port == id_CLK || port == id_CLOCK) return TMG_CLOCK_INPUT; else { clockInfoCount = 1; @@ -960,7 +960,7 @@ TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port info.hold.delay = 0; } } else if (cell->type == id_ICESTORM_DSP || cell->type == id_ICESTORM_SPRAM) { - info.clock_port = id_CLK; + 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); From 07e265868b49447b600c6c3da9f042c593f467f3 Mon Sep 17 00:00:00 2001 From: David Shah Date: Sun, 4 Nov 2018 14:03:33 +0000 Subject: [PATCH 12/67] archapi: Add getDelayFromNS to improve timing algorithm portability Signed-off-by: David Shah --- common/timing.cc | 6 +++--- docs/archapi.md | 4 ++++ ice40/arch.h | 6 ++++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/common/timing.cc b/common/timing.cc index 10b321f7..60b97655 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -117,7 +117,7 @@ struct Timing 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 @@ -344,7 +344,7 @@ struct Timing 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 = clk_period; + (*crit_path)[clockPair].path_period = period; (*crit_path)[clockPair].ports.clear(); (*crit_path)[clockPair].ports.push_back(&usr); } @@ -591,7 +591,7 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p DelayInfo comb_delay; if (last_port == driver.port) { // Case where we start with a STARTPOINT etc - comb_delay.delay = 0; + comb_delay = ctx->getDelayFromNS(0); } else { ctx->getCellDelay(sink_cell, last_port, driver.port, comb_delay); } diff --git a/docs/archapi.md b/docs/archapi.md index 6b22c6df..fd3bfb3a 100644 --- a/docs/archapi.md +++ b/docs/archapi.md @@ -398,6 +398,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. diff --git a/ice40/arch.h b/ice40/arch.h index ff2f7e4c..80fcf761 100644 --- a/ice40/arch.h +++ b/ice40/arch.h @@ -775,6 +775,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; From bd2b3e5e029e9f84f8e0f52e33ad9a5c12f7c9ff Mon Sep 17 00:00:00 2001 From: David Shah Date: Sun, 4 Nov 2018 14:26:16 +0000 Subject: [PATCH 13/67] timing: Fix Fmax for clocks with mixed edge usage Signed-off-by: David Shah --- common/timing.cc | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/common/timing.cc b/common/timing.cc index 60b97655..a2d48ed9 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -517,6 +517,7 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p print_histogram ? &slack_histogram : nullptr); auto min_slack = timing.walk_paths(); std::map> clock_reports; + std::map clock_fmax; std::vector xclock_paths; if (print_path || print_fmax) { for (auto path : crit_paths) { @@ -524,10 +525,15 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p const ClockEvent &b = path.first.end; if (a.clock != b.clock || a.clock == ctx->id("$async$")) continue; - delay_t slack = path.second.path_period - path.second.path_delay; - if (!clock_reports.count(a.clock) || - slack < (clock_reports.at(a.clock).second.path_period - clock_reports.at(a.clock).second.path_delay)) { + double Fmax; + 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; } } @@ -578,6 +584,7 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p 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; } } } @@ -631,13 +638,7 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p if (print_fmax) { log_break(); for (auto &clock : clock_reports) { - - double Fmax; - if (clock.second.first.start.edge == clock.second.first.end.edge) - Fmax = 1000 / ctx->getDelayNS(clock.second.second.path_delay); - else - Fmax = 500 / ctx->getDelayNS(clock.second.second.path_delay); - log_info("Max frequency for clock '%s': %.02f MHz\n", clock.first.c_str(ctx), Fmax); + log_info("Max frequency for clock '%s': %.02f MHz\n", clock.first.c_str(ctx), clock_fmax[clock.first]); } log_break(); From 8af86ff37d3f370f2bf9add46261f3b5a6b3f5a4 Mon Sep 17 00:00:00 2001 From: David Shah Date: Sun, 4 Nov 2018 14:51:48 +0000 Subject: [PATCH 14/67] ecp5: Update arch to new timing API Signed-off-by: David Shah --- common/timing.cc | 3 +- ecp5/arch.cc | 75 +++++++++++++++++++++++++++++++++++++++--------- ecp5/arch.h | 12 ++++++-- 3 files changed, 73 insertions(+), 17 deletions(-) diff --git a/common/timing.cc b/common/timing.cc index a2d48ed9..8351b1f3 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -530,8 +530,7 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p 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)) { + if (!clock_fmax.count(a.clock) || Fmax < clock_fmax.at(a.clock)) { clock_reports[a.clock] = path; clock_fmax[a.clock] = Fmax; } diff --git a/ecp5/arch.cc b/ecp5/arch.cc index 91331f7e..a7bebd61 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; @@ -658,6 +656,57 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id } } +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) { + for (auto c : boost::adaptors::reverse(port.str(this))) { + if (std::isdigit(c)) + continue; + if (c == 'A') + info.clock_port = id_CLKA; + else if (c == 'B') + info.clock_port = id_CLKB; + else + NPNR_ASSERT_FALSE_STR("bad ram port"); + } + 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; + } + } + return info; +} + std::vector> Arch::getTilesAtLocation(int row, int col) { std::vector> ret; diff --git a/ecp5/arch.h b/ecp5/arch.h index 583d539f..07b1e65d 100644 --- a/ecp5/arch.h +++ b/ecp5/arch.h @@ -855,6 +855,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; @@ -878,8 +884,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; From dab70466cdce724611904e066197feef564f3eea Mon Sep 17 00:00:00 2001 From: David Shah Date: Sun, 4 Nov 2018 14:58:25 +0000 Subject: [PATCH 15/67] generic: Update arch to new timing API Signed-off-by: David Shah --- generic/arch.cc | 7 ++++++- generic/arch.h | 14 ++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/generic/arch.cc b/generic/arch.cc index 3e95159a..4439f517 100644 --- a/generic/arch.cc +++ b/generic/arch.cc @@ -461,11 +461,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 22966e2a..1eca9701 100644 --- a/generic/arch.h +++ b/generic/arch.h @@ -209,6 +209,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; @@ -223,8 +231,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; From 11579a1046640a21b79aa6a1f579d3464267d0a1 Mon Sep 17 00:00:00 2001 From: David Shah Date: Sun, 4 Nov 2018 15:11:01 +0000 Subject: [PATCH 16/67] ecp5: EBR clocking fix Signed-off-by: David Shah --- ecp5/arch.cc | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/ecp5/arch.cc b/ecp5/arch.cc index a7bebd61..e035c0f4 100644 --- a/ecp5/arch.cc +++ b/ecp5/arch.cc @@ -683,15 +683,18 @@ TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port info.clockToQ.delay = 395; } } else if (cell->type == id_DP16KD) { - for (auto c : boost::adaptors::reverse(port.str(this))) { + std::string port_name = port.str(this); + for (auto c : boost::adaptors::reverse(port_name)) { if (std::isdigit(c)) continue; - if (c == 'A') + if (c == 'A') { info.clock_port = id_CLKA; - else if (c == 'B') + break; + } else if (c == 'B') { info.clock_port = id_CLKB; - else - NPNR_ASSERT_FALSE_STR("bad ram port"); + 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") From fc5e6bec9ab8bf2c25b2b943de4013daf727dfb8 Mon Sep 17 00:00:00 2001 From: David Shah Date: Mon, 12 Nov 2018 13:42:25 +0000 Subject: [PATCH 17/67] timing: Add support for clock constraints Signed-off-by: David Shah --- common/nextpnr.cc | 9 +++++++++ common/nextpnr.h | 5 ++++- common/timing.cc | 25 ++++++++++++++++++++++--- ecp5/arch_pybindings.cc | 4 ++++ ice40/arch_pybindings.cc | 4 ++++ ice40/pack.cc | 8 ++++++++ 6 files changed, 51 insertions(+), 4 deletions(-) diff --git a/common/nextpnr.cc b/common/nextpnr.cc index 3621217b..8e8a8d19 100644 --- a/common/nextpnr.cc +++ b/common/nextpnr.cc @@ -381,4 +381,13 @@ void Context::check() const } } +void BaseCtx::addClock(IdString net, float 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 216e1532..70af6c71 100644 --- a/common/nextpnr.h +++ b/common/nextpnr.h @@ -297,7 +297,7 @@ struct NetInfo : ArchNetInfo // wire -> uphill_pip std::unordered_map wires; - ClockConstraint *clkconstr = nullptr; + std::unique_ptr clkconstr; TimingConstrObjectId tmg_id; @@ -627,6 +627,9 @@ struct BaseCtx 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/timing.cc b/common/timing.cc index 8351b1f3..fec74312 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -315,13 +315,26 @@ struct Timing const auto endpoint_arrival = net_arrival + net_delay + setup; auto path_budget = clk_period - endpoint_arrival; 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(); + } + } + } if (update) { auto budget_share = budget_override ? 0 : path_budget / net_length_plus_one; usr.budget = std::min(usr.budget, net_delay + budget_share); @@ -637,7 +650,13 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p if (print_fmax) { log_break(); for (auto &clock : clock_reports) { - log_info("Max frequency for clock '%s': %.02f MHz\n", clock.first.c_str(ctx), clock_fmax[clock.first]); + 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': %.02f MHz (%s at %.02f MHz)\n", clock.first.c_str(ctx), + clock_fmax[clock.first], (target < clock_fmax[clock.first]) ? "PASS" : "FAIL", target); + } else { + log_info("Max frequency for clock '%s': %.02f MHz\n", clock.first.c_str(ctx), clock_fmax[clock.first]); + } } log_break(); 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/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/pack.cc b/ice40/pack.cc index b9360b74..7a27d505 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -490,6 +490,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); } From d3ad522bfe4d6c0f63a455ad5deabc568a50b5f3 Mon Sep 17 00:00:00 2001 From: David Shah Date: Mon, 12 Nov 2018 13:59:09 +0000 Subject: [PATCH 18/67] ecp5: Copy clock constraints during global promotion Signed-off-by: David Shah --- ecp5/globals.cc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ecp5/globals.cc b/ecp5/globals.cc index 06412fef..9b0928a4 100644 --- a/ecp5/globals.cc +++ b/ecp5/globals.cc @@ -350,6 +350,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); From ba7a7a3733c493fc950d5bedbc49b4c78b451b3d Mon Sep 17 00:00:00 2001 From: David Shah Date: Mon, 12 Nov 2018 14:00:08 +0000 Subject: [PATCH 19/67] timing: Fix compile warning Signed-off-by: David Shah --- common/timing.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/timing.cc b/common/timing.cc index fec74312..4c6ed9f5 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -528,7 +528,7 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p 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; From 4134bfa78e9d56593ee306e482f778e5fc04f3d0 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Tue, 13 Nov 2018 12:12:26 -0800 Subject: [PATCH 20/67] [timing] Resolve another merge conflict --- common/place_common.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/place_common.cc b/common/place_common.cc index 1c262c6f..04e9b7d0 100644 --- a/common/place_common.cc +++ b/common/place_common.cc @@ -36,8 +36,8 @@ 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); From 9f13bc7eb0f5045f82360c7b6d686cfe0b6c8e01 Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Tue, 13 Nov 2018 14:14:51 -0800 Subject: [PATCH 21/67] [timing] Crit path report to print out edges --- common/timing.cc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/common/timing.cc b/common/timing.cc index 4c6ed9f5..b5ebe488 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -601,6 +601,7 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p } } + log_info("curr total\n"); for (auto sink : crit_path) { auto sink_cell = sink->cell; auto &port = sink_cell->ports.at(sink->port); @@ -631,8 +632,9 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p for (auto &clock : clock_reports) { log_break(); - log_info("Critical path report for clock '%s':\n", clock.first.c_str(ctx)); - log_info("curr total\n"); + 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); } @@ -642,7 +644,6 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p 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()); - log_info("curr total\n"); auto &crit_path = crit_paths.at(xclock).ports; print_path_report(xclock, crit_path); } From adc50a207fb442a7b44ac76b8d04dc5477c23c8f Mon Sep 17 00:00:00 2001 From: David Shah Date: Wed, 14 Nov 2018 08:46:10 +0000 Subject: [PATCH 22/67] Timing fixes Signed-off-by: David Shah --- common/nextpnr.cc | 1 + common/timing.cc | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/common/nextpnr.cc b/common/nextpnr.cc index 514017c6..b304f4f4 100644 --- a/common/nextpnr.cc +++ b/common/nextpnr.cc @@ -409,6 +409,7 @@ 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); diff --git a/common/timing.cc b/common/timing.cc index b5ebe488..1d3fec56 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -313,7 +313,6 @@ struct Timing 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; - auto path_budget = clk_period - endpoint_arrival; delay_t period; // Set default period if (edge == startdomain.first.edge) { @@ -335,6 +334,8 @@ struct Timing } } } + auto path_budget = period - endpoint_arrival; + if (update) { auto budget_share = budget_override ? 0 : path_budget / net_length_plus_one; usr.budget = std::min(usr.budget, net_delay + budget_share); @@ -532,13 +533,21 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p 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 @@ -659,6 +668,10 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p log_info("Max frequency for clock '%s': %.02f MHz\n", clock.first.c_str(ctx), 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(); int start_field_width = 0, end_field_width = 0; From e1d2c595a18814d81528e49ba48dbd05fe6466ac Mon Sep 17 00:00:00 2001 From: Eddie Hung Date: Wed, 14 Nov 2018 18:27:43 -0800 Subject: [PATCH 23/67] Improve message spacing --- common/nextpnr.cc | 2 +- common/timing.cc | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/common/nextpnr.cc b/common/nextpnr.cc index b304f4f4..be3bfe14 100644 --- a/common/nextpnr.cc +++ b/common/nextpnr.cc @@ -409,7 +409,7 @@ 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); + 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); diff --git a/common/timing.cc b/common/timing.cc index b414c6f7..40e4d344 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -676,13 +676,18 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p } 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': %.02f MHz (%s at %.02f MHz)\n", clock.first.c_str(ctx), + 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': %.02f MHz\n", clock.first.c_str(ctx), clock_fmax[clock.first]); + 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) { From 9f9b242cf0a3b587df8f5b0eb542ca7256ca0eb9 Mon Sep 17 00:00:00 2001 From: David Shah Date: Thu, 15 Nov 2018 11:25:26 +0000 Subject: [PATCH 24/67] docs: Add documentation on constraints support Signed-off-by: David Shah --- docs/constraints.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 docs/constraints.md 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) + From cc9fb1497d070eafff678a092efc649c508b3b90 Mon Sep 17 00:00:00 2001 From: David Shah Date: Wed, 7 Nov 2018 11:00:57 +0000 Subject: [PATCH 25/67] ecp5: Groundwork for DCU support Signed-off-by: David Shah --- ecp5/bitstream.cc | 23 ++-- ecp5/constids.inc | 300 +++++++++++++++++++++++++++++++++++++++++ ecp5/trellis_import.py | 11 +- 3 files changed, 318 insertions(+), 16 deletions(-) diff --git a/ecp5/bitstream.cc b/ecp5/bitstream.cc index 6d43b369..a48b7793 100644 --- a/ecp5/bitstream.cc +++ b/ecp5/bitstream.cc @@ -414,20 +414,17 @@ void fix_tile_names(Context *ctx, ChipConfig &cc) 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 + newname.insert(cibdcu, 1, 'V'); + tiletype_xform[tile.first] = newname; + } else if (tile.first.substr(tile.first.size() - 7) == "BMID_0H") { + newname.back() = 'V'; + tiletype_xform[tile.first] = newname; + } else if (tile.first.substr(tile.first.size() - 6) == "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 diff --git a/ecp5/constids.inc b/ecp5/constids.inc index bdcbc1ea..4f5c3ef3 100644 --- a/ecp5/constids.inc +++ b/ecp5/constids.inc @@ -810,3 +810,303 @@ 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) 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 From 983903887d1f6b3be2060fdfdd680b67006b8e08 Mon Sep 17 00:00:00 2001 From: David Shah Date: Wed, 7 Nov 2018 11:12:21 +0000 Subject: [PATCH 26/67] ecp5: DCU bitstream gen handling Signed-off-by: David Shah --- ecp5/bitstream.cc | 45 ++++++++ ecp5/dcu_bitstream.h | 254 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 299 insertions(+) create mode 100644 ecp5/dcu_bitstream.h diff --git a/ecp5/bitstream.cc b/ecp5/bitstream.cc index a48b7793..53d4eb4f 100644 --- a/ecp5/bitstream.cc +++ b/ecp5/bitstream.cc @@ -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) { @@ -453,6 +463,19 @@ 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); + 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); @@ -478,6 +501,21 @@ 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(); + } + } + } + } // Add all set, configurable pips to the config for (auto pip : ctx->getPips()) { if (ctx->getBoundPipNet(pip) != nullptr) { @@ -997,6 +1035,13 @@ 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 { NPNR_ASSERT_FALSE("unsupported cell type"); } diff --git a/ecp5/dcu_bitstream.h b/ecp5/dcu_bitstream.h new file mode 100644 index 00000000..0a5028d2 --- /dev/null +++ b/ecp5/dcu_bitstream.h @@ -0,0 +1,254 @@ +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)); From c5a3571a061b1340355bb758be71a86922ee863f Mon Sep 17 00:00:00 2001 From: David Shah Date: Wed, 7 Nov 2018 14:24:58 +0000 Subject: [PATCH 27/67] ecp5: Working on DCU Signed-off-by: David Shah --- ecp5/arch.cc | 2 ++ ecp5/bitstream.cc | 44 +++++++++++++++++++++++++++++++++++++++----- ecp5/pack.cc | 22 ++++++++++++++++++++++ 3 files changed, 63 insertions(+), 5 deletions(-) diff --git a/ecp5/arch.cc b/ecp5/arch.cc index fe6a9545..93ed5788 100644 --- a/ecp5/arch.cc +++ b/ecp5/arch.cc @@ -651,6 +651,8 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, in return TMG_IGNORE; // FIXME } else if (cell->type == id_EHXPLLL) { return TMG_IGNORE; + } else if (cell->type == id_DCUA) { + return TMG_IGNORE; // FIXME } else { NPNR_ASSERT_FALSE_STR("no timing data for cell type '" + cell->type.str(this) + "'"); } diff --git a/ecp5/bitstream.cc b/ecp5/bitstream.cc index 53d4eb4f..4fee6802 100644 --- a/ecp5/bitstream.cc +++ b/ecp5/bitstream.cc @@ -91,6 +91,7 @@ static void tie_cib_signal(Context *ctx, ChipConfig &cc, WireId wire, bool value NPNR_ASSERT(signals.size() < 100); cibsig = signals.front(); basename = ctx->getWireBasename(cibsig).str(ctx); + log_info("%s\n", basename.c_str()); signals.pop(); if (std::regex_match(basename, cib_re)) break; @@ -418,9 +419,8 @@ 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; @@ -467,7 +467,7 @@ 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); + 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); @@ -484,6 +484,38 @@ 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'); + } + } 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; + } + return word; +} + void write_bitstream(Context *ctx, std::string base_config_file, std::string text_config_file) { ChipConfig cc; @@ -510,8 +542,10 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex 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()) + if (cc_tile != cc.tiles.end()) { cc_tile->second.cenums.clear(); + cc_tile->second.cunknowns.clear(); + } } } } diff --git a/ecp5/pack.cc b/ecp5/pack.cc index 73e15609..4f1c7f79 100644 --- a/ecp5/pack.cc +++ b/ecp5/pack.cc @@ -1033,12 +1033,34 @@ class Ecp5Packer } } + // "Pack" DCUs + void pack_dcus() { + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (ci->type == id_DCUA) { + // 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); + } + } + } + public: void pack() { pack_io(); pack_ebr(); pack_dsps(); + pack_dcus(); pack_constants(); pack_dram(); pack_carries(); From 4f8dfd8e1b5276337f46a1e21bc9b0075450d10b Mon Sep 17 00:00:00 2001 From: David Shah Date: Wed, 7 Nov 2018 14:36:42 +0000 Subject: [PATCH 28/67] ecp5: Prefer DCCs with dedicated routing when placing DCCs Signed-off-by: David Shah --- ecp5/globals.cc | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/ecp5/globals.cc b/ecp5/globals.cc index 9b0928a4..f2a556fa 100644 --- a/ecp5/globals.cc +++ b/ecp5/globals.cc @@ -290,6 +290,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 +301,43 @@ 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) { @@ -319,6 +360,8 @@ class Ecp5GlobalRouter ctx->bindBel(best_bel, dcc, STRENGTH_LOCKED); } + + // Insert a DCC into a net to promote it to a global NetInfo *insert_dcc(NetInfo *net) { From c9d83ec08b753cc8e110bb36f2f529bfdafa293f Mon Sep 17 00:00:00 2001 From: David Shah Date: Wed, 7 Nov 2018 14:55:35 +0000 Subject: [PATCH 29/67] dcu: Fix bitstream param handling Signed-off-by: David Shah --- ecp5/bitstream.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/ecp5/bitstream.cc b/ecp5/bitstream.cc index 4fee6802..c5eca14f 100644 --- a/ecp5/bitstream.cc +++ b/ecp5/bitstream.cc @@ -493,6 +493,7 @@ static std::vector parse_config_str(std::string str, int length) { 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++) { From 37cbabecfbd22119ad5ba0adfc4d7011831a9af4 Mon Sep 17 00:00:00 2001 From: David Shah Date: Wed, 7 Nov 2018 15:08:47 +0000 Subject: [PATCH 30/67] ecp5: remove debug and clangformat Signed-off-by: David Shah --- ecp5/bitstream.cc | 9 +++++---- ecp5/globals.cc | 11 ++++++----- ecp5/pack.cc | 3 ++- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/ecp5/bitstream.cc b/ecp5/bitstream.cc index c5eca14f..00486e39 100644 --- a/ecp5/bitstream.cc +++ b/ecp5/bitstream.cc @@ -91,7 +91,6 @@ static void tie_cib_signal(Context *ctx, ChipConfig &cc, WireId wire, bool value NPNR_ASSERT(signals.size() < 100); cibsig = signals.front(); basename = ctx->getWireBasename(cibsig).str(ctx); - log_info("%s\n", basename.c_str()); signals.pop(); if (std::regex_match(basename, cib_re)) break; @@ -467,7 +466,9 @@ 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) + 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); @@ -475,7 +476,6 @@ void tieoff_dcu_ports(Context *ctx, ChipConfig &cc, CellInfo *ci) } } - static void set_pip(Context *ctx, ChipConfig &cc, PipId pip) { std::string tile = ctx->getPipTilename(pip); @@ -484,7 +484,8 @@ 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) { +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; diff --git a/ecp5/globals.cc b/ecp5/globals.cc index f2a556fa..75535dfe 100644 --- a/ecp5/globals.cc +++ b/ecp5/globals.cc @@ -302,7 +302,8 @@ class Ecp5GlobalRouter } // Return true if a short (<5) route exists between two wires - bool has_short_route(WireId src, WireId dst, int thresh = 5) { + bool has_short_route(WireId src, WireId dst, int thresh = 5) + { std::queue visit; std::unordered_map backtrace; visit.push(src); @@ -310,7 +311,8 @@ class Ecp5GlobalRouter 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)); + // log_info ("dist %s -> %s = inf\n", ctx->getWireName(src).c_str(ctx), + // ctx->getWireName(dst).c_str(ctx)); return false; } cursor = visit.front(); @@ -334,7 +336,8 @@ class Ecp5GlobalRouter 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); + // log_info ("dist %s -> %s = %d\n", ctx->getWireName(src).c_str(ctx), ctx->getWireName(dst).c_str(ctx), + // length); return length < thresh; } @@ -360,8 +363,6 @@ class Ecp5GlobalRouter ctx->bindBel(best_bel, dcc, STRENGTH_LOCKED); } - - // Insert a DCC into a net to promote it to a global NetInfo *insert_dcc(NetInfo *net) { diff --git a/ecp5/pack.cc b/ecp5/pack.cc index 4f1c7f79..ae416a7b 100644 --- a/ecp5/pack.cc +++ b/ecp5/pack.cc @@ -1034,7 +1034,8 @@ class Ecp5Packer } // "Pack" DCUs - void pack_dcus() { + void pack_dcus() + { for (auto cell : sorted(ctx->cells)) { CellInfo *ci = cell.second; if (ci->type == id_DCUA) { From e9fe444dc7dd5bf09f50ef6c742a637f6bc24f41 Mon Sep 17 00:00:00 2001 From: David Shah Date: Wed, 7 Nov 2018 20:44:43 +0000 Subject: [PATCH 31/67] ecp5: Adding ancillary DCU bels Signed-off-by: David Shah --- ecp5/arch.cc | 2 +- ecp5/bitstream.cc | 17 +++++++++++++++++ ecp5/constids.inc | 7 +++++++ ecp5/pack.cc | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 1 deletion(-) diff --git a/ecp5/arch.cc b/ecp5/arch.cc index 93ed5788..a0d8e8ae 100644 --- a/ecp5/arch.cc +++ b/ecp5/arch.cc @@ -651,7 +651,7 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, in return TMG_IGNORE; // FIXME } else if (cell->type == id_EHXPLLL) { return TMG_IGNORE; - } else if (cell->type == id_DCUA) { + } else if (cell->type == id_DCUA || cell->type == id_EXTREFB || cell->type == id_PCSCLKDIV) { return TMG_IGNORE; // FIXME } else { NPNR_ASSERT_FALSE_STR("no timing data for cell type '" + cell->type.str(this) + "'"); diff --git a/ecp5/bitstream.cc b/ecp5/bitstream.cc index 00486e39..2df0ed0b 100644 --- a/ecp5/bitstream.cc +++ b/ecp5/bitstream.cc @@ -514,6 +514,12 @@ static std::vector parse_config_str(std::string str, int length) 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; } @@ -1078,6 +1084,17 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex #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 4f5c3ef3..11ecc240 100644 --- a/ecp5/constids.inc +++ b/ecp5/constids.inc @@ -1110,3 +1110,10 @@ 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/pack.cc b/ecp5/pack.cc index ae416a7b..66428e95 100644 --- a/ecp5/pack.cc +++ b/ecp5/pack.cc @@ -1039,6 +1039,8 @@ class Ecp5Packer for (auto cell : sorted(ctx->cells)) { CellInfo *ci = cell.second; if (ci->type == id_DCUA) { + 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()) { @@ -1051,6 +1053,36 @@ class Ecp5Packer for (auto pin : ctx->getBelPins(exemplar_bel)) if (ctx->getBelPinType(exemplar_bel, pin) == PORT_IN) autocreate_empty_port(ci, pin); + } else 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 || (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; + } } } } From 36178a571354302cfb3166f7bf83f91cf754ef70 Mon Sep 17 00:00:00 2001 From: David Shah Date: Fri, 9 Nov 2018 15:17:26 +0000 Subject: [PATCH 32/67] ecp5: Trim IO connected to top level ports Signed-off-by: David Shah --- ecp5/pack.cc | 86 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 72 insertions(+), 14 deletions(-) diff --git a/ecp5/pack.cc b/ecp5/pack.cc index 66428e95..ab31a8f3 100644 --- a/ecp5/pack.cc +++ b/ecp5/pack.cc @@ -235,6 +235,43 @@ 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 +281,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 +307,20 @@ 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,22 +331,25 @@ 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); } + } } flush_cells(); From bc022173f01794ea67809f2d7cdc54c3c3d6696f Mon Sep 17 00:00:00 2001 From: David Shah Date: Fri, 9 Nov 2018 15:17:37 +0000 Subject: [PATCH 33/67] ecp5: clangformat Signed-off-by: David Shah --- ecp5/bitstream.cc | 14 +++++++++----- ecp5/pack.cc | 27 ++++++++++++++------------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/ecp5/bitstream.cc b/ecp5/bitstream.cc index 2df0ed0b..5af051c7 100644 --- a/ecp5/bitstream.cc +++ b/ecp5/bitstream.cc @@ -1087,14 +1087,18 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex } 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)); + 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")); + 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/pack.cc b/ecp5/pack.cc index ab31a8f3..fdf04d4e 100644 --- a/ecp5/pack.cc +++ b/ecp5/pack.cc @@ -236,14 +236,14 @@ class Ecp5Packer } // Return true if an port is a top level port that provides its own IOBUF - bool is_top_port(PortRef &port) { + 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; + 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 { @@ -252,20 +252,23 @@ class Ecp5Packer } // Return true if a net only drives a top port - bool drives_top_port(NetInfo *net, PortRef &tp) { + 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)); + 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)); + 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; } @@ -308,9 +311,8 @@ class Ecp5Packer } } } 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)); + 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; @@ -349,7 +351,6 @@ class Ecp5Packer trio->attrs[ctx->id("BEL")] = ctx->getBelName(pinBel).str(ctx); } } - } } flush_cells(); @@ -1123,7 +1124,7 @@ class Ecp5Packer } 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")); + 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; @@ -1139,7 +1140,7 @@ class Ecp5Packer 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; + ci->constr_z = (loc.x >= 69) ? 1 : 0; } } } From 0eba7d9789b0e9fa10c33349928608f59dbf9488 Mon Sep 17 00:00:00 2001 From: David Shah Date: Fri, 9 Nov 2018 18:18:50 +0000 Subject: [PATCH 34/67] ecp5: EXTREFB fixes Signed-off-by: David Shah --- ecp5/globals.cc | 2 ++ ecp5/pack.cc | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ecp5/globals.cc b/ecp5/globals.cc index 75535dfe..c6cc7e57 100644 --- a/ecp5/globals.cc +++ b/ecp5/globals.cc @@ -379,6 +379,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_DCCA) { + keep_users.push_back(user); } else { glbnet->users.push_back(user); user.cell->ports.at(user.port).net = glbnet.get(); diff --git a/ecp5/pack.cc b/ecp5/pack.cc index fdf04d4e..82e2888a 100644 --- a/ecp5/pack.cc +++ b/ecp5/pack.cc @@ -1118,7 +1118,9 @@ class Ecp5Packer if (refo == nullptr) log_error("EXTREFB REFCLKO must not be unconnected\n"); for (auto user : refo->users) { - if (user.cell->type != id_DCUA || (dcu != nullptr && dcu != user.cell)) + 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; } From 084f9cf63f148a8af991c5827a25d239709ff0e6 Mon Sep 17 00:00:00 2001 From: David Shah Date: Sun, 11 Nov 2018 10:22:57 +0000 Subject: [PATCH 35/67] ecp5: DCU clocking fixes Signed-off-by: David Shah --- ecp5/globals.cc | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ecp5/globals.cc b/ecp5/globals.cc index c6cc7e57..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]); } @@ -379,7 +385,7 @@ 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_DCCA) { + } else if (net->driver.cell->type == id_EXTREFB && user.cell->type == id_DCUA) { keep_users.push_back(user); } else { glbnet->users.push_back(user); From 02736d06800dbbab6eef9d2660e9d481874dd7ae Mon Sep 17 00:00:00 2001 From: David Shah Date: Sun, 11 Nov 2018 10:46:07 +0000 Subject: [PATCH 36/67] ecp5: Add timing info for SERDES Signed-off-by: David Shah --- ecp5/arch.cc | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/ecp5/arch.cc b/ecp5/arch.cc index a0d8e8ae..afea8d4a 100644 --- a/ecp5/arch.cc +++ b/ecp5/arch.cc @@ -652,7 +652,15 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, in } else if (cell->type == id_EHXPLLL) { return TMG_IGNORE; } else if (cell->type == id_DCUA || cell->type == id_EXTREFB || cell->type == id_PCSCLKDIV) { - return TMG_IGNORE; // FIXME + 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) + "'"); } @@ -708,6 +716,23 @@ TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port 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; } From 01e0da16f0cb8fffb78b476b912edbd676106b04 Mon Sep 17 00:00:00 2001 From: David Shah Date: Sun, 11 Nov 2018 15:36:00 +0000 Subject: [PATCH 37/67] ecp5: Add DCU availability check Signed-off-by: David Shah --- ecp5/arch_place.cc | 2 ++ 1 file changed, 2 insertions(+) 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; From 91a09271965cf9652fd823d6df59051f35e0213d Mon Sep 17 00:00:00 2001 From: David Shah Date: Sun, 11 Nov 2018 15:50:56 +0000 Subject: [PATCH 38/67] ecp5: Support LOC attribute on DCUs Signed-off-by: David Shah --- ecp5/pack.cc | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/ecp5/pack.cc b/ecp5/pack.cc index 82e2888a..2d2f7578 100644 --- a/ecp5/pack.cc +++ b/ecp5/pack.cc @@ -1098,6 +1098,26 @@ class Ecp5Packer 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 @@ -1112,7 +1132,11 @@ class Ecp5Packer for (auto pin : ctx->getBelPins(exemplar_bel)) if (ctx->getBelPinType(exemplar_bel, pin) == PORT_IN) autocreate_empty_port(ci, pin); - } else if (ci->type == id_EXTREFB) { + } + } + 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) From 7e1df8246241148acbe8a5bd74d588d9b493807a Mon Sep 17 00:00:00 2001 From: David Shah Date: Thu, 15 Nov 2018 11:54:28 +0000 Subject: [PATCH 39/67] ecp5: Regression fix & format Signed-off-by: David Shah --- ecp5/archdefs.h | 15 ++++++++++++--- ecp5/bitstream.cc | 3 ++- 2 files changed, 14 insertions(+), 4 deletions(-) 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 5af051c7..b19bd1c6 100644 --- a/ecp5/bitstream.cc +++ b/ecp5/bitstream.cc @@ -426,7 +426,8 @@ void fix_tile_names(Context *ctx, ChipConfig &cc) auto cibdcu = tile.first.find("CIB_DCU"); if (cibdcu != std::string::npos) { // Add the V - newname.insert(cibdcu, 1, 'V'); + if (newname.at(cibdcu - 1) != 'V') + newname.insert(cibdcu, 1, 'V'); tiletype_xform[tile.first] = newname; } else if (tile.first.substr(tile.first.size() - 7) == "BMID_0H") { newname.back() = 'V'; From f07bd98d59765f2c565815d1a6483db04c57a9d1 Mon Sep 17 00:00:00 2001 From: David Shah Date: Fri, 16 Nov 2018 09:58:18 +0000 Subject: [PATCH 40/67] ecp5: Better use of Boost Signed-off-by: David Shah --- ecp5/bitstream.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ecp5/bitstream.cc b/ecp5/bitstream.cc index b19bd1c6..5e3ff694 100644 --- a/ecp5/bitstream.cc +++ b/ecp5/bitstream.cc @@ -24,7 +24,7 @@ #include #include #include - +#include #include "config.h" #include "io.h" #include "log.h" @@ -429,10 +429,10 @@ void fix_tile_names(Context *ctx, ChipConfig &cc) if (newname.at(cibdcu - 1) != 'V') newname.insert(cibdcu, 1, 'V'); tiletype_xform[tile.first] = newname; - } else if (tile.first.substr(tile.first.size() - 7) == "BMID_0H") { + } else if (boost::ends_with(tile.first, "BMID_0H")) { newname.back() = 'V'; tiletype_xform[tile.first] = newname; - } else if (tile.first.substr(tile.first.size() - 6) == "BMID_2") { + } else if (boost::ends_with(tile.first, "BMID_2")) { newname.push_back('V'); tiletype_xform[tile.first] = newname; } From 20aa0a0eedbce02f50e134772dcd2b4a5be830e4 Mon Sep 17 00:00:00 2001 From: David Shah Date: Fri, 16 Nov 2018 13:18:53 +0000 Subject: [PATCH 41/67] ice40: Remove unnecessary RAM assertion Fixes #121 Signed-off-by: David Shah --- ice40/bitstream.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/ice40/bitstream.cc b/ice40/bitstream.cc index 4cfed52d..4efb1091 100644 --- a/ice40/bitstream.cc +++ b/ice40/bitstream.cc @@ -775,7 +775,6 @@ void write_asc(const Context *ctx, std::ostream &out) 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; From 9c52afcf5fabd888ec7d89e506ebe00c5a1a3640 Mon Sep 17 00:00:00 2001 From: David Shah Date: Fri, 16 Nov 2018 13:25:51 +0000 Subject: [PATCH 42/67] clangformat Signed-off-by: David Shah --- common/nextpnr.h | 5 +- common/place_common.cc | 3 +- common/router1.cc | 21 +- common/timing.cc | 29 ++- ecp5/bitstream.cc | 4 +- ecp5/dcu_bitstream.h | 510 +++++++++++++++++++++++++++-------------- gui/designwidget.cc | 36 +-- gui/ecp5/mainwindow.cc | 6 +- gui/fpgaviewwidget.cc | 94 ++++---- gui/lineshader.cc | 7 +- gui/lineshader.h | 12 +- gui/quadtree.h | 28 +-- ice40/gfx.cc | 4 +- 13 files changed, 463 insertions(+), 296 deletions(-) diff --git a/common/nextpnr.h b/common/nextpnr.h index a6617ae4..d58ae529 100644 --- a/common/nextpnr.h +++ b/common/nextpnr.h @@ -579,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 { diff --git a/common/place_common.cc b/common/place_common.cc index a13a963c..b3eb4267 100644 --- a/common/place_common.cc +++ b/common/place_common.cc @@ -37,7 +37,8 @@ wirelen_t get_net_metric(const Context *ctx, const NetInfo *net, MetricType type if (driver_gb) return 0; int clock_count; - bool timing_driven = ctx->timing_driven && type == MetricType::COST && ctx->getPortTimingClass(driver_cell, net->driver.port, clock_count) != TMG_IGNORE; + 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/router1.cc b/common/router1.cc index 958c24d4..198461bc 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; @@ -950,8 +952,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 40e4d344..80be554c 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -237,9 +237,11 @@ 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 @@ -639,7 +641,8 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p 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) { @@ -648,7 +651,8 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p 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); } } @@ -658,9 +662,12 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p 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()); + 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); } @@ -684,10 +691,12 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p 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); + 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]); + 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) { diff --git a/ecp5/bitstream.cc b/ecp5/bitstream.cc index 961a3956..4de2a0a6 100644 --- a/ecp5/bitstream.cc +++ b/ecp5/bitstream.cc @@ -19,15 +19,15 @@ #include "bitstream.h" +#include #include #include #include #include #include -#include #include "config.h" -#include "pio.h" #include "log.h" +#include "pio.h" #include "util.h" #define fmt_str(x) (static_cast(std::ostringstream() << x).str()) diff --git a/ecp5/dcu_bitstream.h b/ecp5/dcu_bitstream.h index 0a5028d2..93c1f9f2 100644 --- a/ecp5/dcu_bitstream.h +++ b/ecp5/dcu_bitstream.h @@ -1,64 +1,110 @@ -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_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_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_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_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_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_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_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_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_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_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_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_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)); @@ -66,107 +112,186 @@ tg.config.add_word("DCU.CH0_RTERM_RX", parse_config_str(str_or_default(ci->param 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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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)); @@ -174,49 +299,85 @@ tg.config.add_word("DCU.CH1_RTERM_RX", parse_config_str(str_or_default(ci->param 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_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_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_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_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_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_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)); @@ -225,12 +386,15 @@ tg.config.add_word("DCU.D_CMUSETI4CPZ", parse_config_str(str_or_default(ci->para 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_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_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)); @@ -242,13 +406,19 @@ tg.config.add_word("DCU.D_REFCK_MODE", parse_config_str(str_or_default(ci->param 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_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_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_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/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/ecp5/mainwindow.cc b/gui/ecp5/mainwindow.cc index fe2f9e57..c6c7bc97 100644 --- a/gui/ecp5/mainwindow.cc +++ b/gui/ecp5/mainwindow.cc @@ -18,9 +18,9 @@ */ #include "mainwindow.h" +#include #include "bitstream.h" #include "log.h" -#include #include #include @@ -150,7 +150,7 @@ void MainWindow::open_lpf() { QString fileName = QFileDialog::getOpenFileName(this, QString("Open LPF"), QString(), QString("*.lpf")); if (!fileName.isEmpty()) { - std::ifstream in(fileName.toStdString()); + std::ifstream in(fileName.toStdString()); if (ctx->applyLPF(fileName.toStdString(), in)) { log("Loading LPF successful.\n"); actionPack->setEnabled(true); @@ -158,7 +158,7 @@ void MainWindow::open_lpf() } else { actionLoadLPF->setEnabled(true); log("Loading LPF failed.\n"); - } + } } } diff --git a/gui/fpgaviewwidget.cc b/gui/fpgaviewwidget.cc index b771d6a4..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 @@ -314,36 +314,27 @@ void FPGAViewWidget::paintGL() } // Render the grid. - lineShader_.draw(GraphicElement::STYLE_GRID, colors_.grid, thick1Px, - matrix); + lineShader_.draw(GraphicElement::STYLE_GRID, colors_.grid, thick1Px, matrix); // Render Arch graphics. - lineShader_.draw(GraphicElement::STYLE_FRAME, colors_.frame, thick11Px, - matrix); - lineShader_.draw(GraphicElement::STYLE_HIDDEN, colors_.hidden, thick11Px, - matrix); - lineShader_.draw(GraphicElement::STYLE_INACTIVE, colors_.inactive, - thick11Px, matrix); - lineShader_.draw(GraphicElement::STYLE_ACTIVE, colors_.active, thick11Px, - matrix); + lineShader_.draw(GraphicElement::STYLE_FRAME, colors_.frame, thick11Px, matrix); + lineShader_.draw(GraphicElement::STYLE_HIDDEN, colors_.hidden, thick11Px, matrix); + lineShader_.draw(GraphicElement::STYLE_INACTIVE, colors_.inactive, thick11Px, matrix); + lineShader_.draw(GraphicElement::STYLE_ACTIVE, colors_.active, thick11Px, matrix); // Draw highlighted items. for (int i = 0; i < 8; i++) { - GraphicElement::style_t style = (GraphicElement::style_t)( - GraphicElement::STYLE_HIGHLIGHTED0 + i); + GraphicElement::style_t style = (GraphicElement::style_t)(GraphicElement::STYLE_HIGHLIGHTED0 + i); lineShader_.draw(style, colors_.highlight[i], thick11Px, matrix); } - lineShader_.draw(GraphicElement::STYLE_SELECTED, colors_.selected, - thick11Px, matrix); - lineShader_.draw(GraphicElement::STYLE_HOVER, colors_.hovered, - thick2Px, matrix); + lineShader_.draw(GraphicElement::STYLE_SELECTED, colors_.selected, thick11Px, matrix); + lineShader_.draw(GraphicElement::STYLE_HOVER, colors_.hovered, thick2Px, matrix); // Render ImGui QtImGui::newFrame(); QMutexLocker lock(&rendererArgsLock_); - if (!(rendererArgs_->hoveredDecal == DecalXY()) && rendererArgs_->hintText.size() > 0) - { + if (!(rendererArgs_->hoveredDecal == DecalXY()) && rendererArgs_->hintText.size() > 0) { ImGui::SetNextWindowPos(ImVec2(rendererArgs_->x, rendererArgs_->y)); ImGui::BeginTooltip(); ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); @@ -351,7 +342,7 @@ void FPGAViewWidget::paintGL() ImGui::PopTextWrapPos(); ImGui::EndTooltip(); } - ImGui::Render(); + ImGui::Render(); } void FPGAViewWidget::pokeRenderer(void) { renderRunner_->poke(); } @@ -444,10 +435,10 @@ void FPGAViewWidget::renderLines(void) int last_render[GraphicElement::STYLE_HIGHLIGHTED0]; { QMutexLocker locker(&rendererDataLock_); - for(int i =0; igfxByStyle[(enum GraphicElement::style_t)i].last_render; } - + auto data = std::unique_ptr(new FPGAViewWidget::RendererData); // Reset bounding box. data->bbGlobal.clear(); @@ -514,13 +505,12 @@ void FPGAViewWidget::renderLines(void) for (int i = 0; i < 8; i++) data->gfxHighlighted[i] = rendererData_->gfxHighlighted[i]; } - for(int i =0; igfxByStyle[(enum GraphicElement::style_t)i].last_render = ++last_render[i]; rendererData_ = std::move(data); } } - if (gridChanged) - { + if (gridChanged) { QMutexLocker locker(&rendererDataLock_); rendererData_->gfxGrid.clear(); // Render grid. @@ -529,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) { @@ -537,7 +527,7 @@ void FPGAViewWidget::renderLines(void) // Whether the currently being hovered decal is also selected. bool hoveringSelected = false; - // Render selected. + // Render selected. rendererData_->bbSelected.clear(); rendererData_->gfxSelected.clear(); for (auto &decal : selectedDecals) { @@ -565,11 +555,11 @@ void FPGAViewWidget::renderLines(void) } { - QMutexLocker lock(&rendererArgsLock_); + QMutexLocker lock(&rendererArgsLock_); if (rendererArgs_->zoomOutbound) { zoomOutbound(); - rendererArgs_->zoomOutbound = false; + rendererArgs_->zoomOutbound = false; } } } @@ -651,7 +641,8 @@ boost::optional FPGAViewWidget::pickElement(float void FPGAViewWidget::mousePressEvent(QMouseEvent *event) { ImGuiIO &io = ImGui::GetIO(); - if (io.WantCaptureMouse) return; + if (io.WantCaptureMouse) + return; if (event->buttons() & Qt::RightButton || event->buttons() & Qt::MidButton) { lastDragPos_ = event->pos(); @@ -687,7 +678,8 @@ void FPGAViewWidget::mousePressEvent(QMouseEvent *event) void FPGAViewWidget::mouseMoveEvent(QMouseEvent *event) { ImGuiIO &io = ImGui::GetIO(); - if (io.WantCaptureMouse) return; + if (io.WantCaptureMouse) + return; if (event->buttons() & Qt::RightButton || event->buttons() & Qt::MidButton) { const int dx = event->x() - lastDragPos_.x(); @@ -724,22 +716,23 @@ void FPGAViewWidget::mouseMoveEvent(QMouseEvent *event) if (closest.type == ElementType::BEL) { rendererArgs_->hintText = std::string("BEL\n") + ctx_->getBelName(closest.bel).c_str(ctx_); CellInfo *cell = ctx_->getBoundBelCell(closest.bel); - if (cell!=nullptr) - rendererArgs_->hintText += std::string("\nCELL\n") +ctx_->nameOf(cell); + if (cell != nullptr) + rendererArgs_->hintText += std::string("\nCELL\n") + ctx_->nameOf(cell); } else if (closest.type == ElementType::WIRE) { rendererArgs_->hintText = std::string("WIRE\n") + ctx_->getWireName(closest.wire).c_str(ctx_); NetInfo *net = ctx_->getBoundWireNet(closest.wire); - if (net!=nullptr) - rendererArgs_->hintText += std::string("\nNET\n") +ctx_->nameOf(net); + if (net != nullptr) + rendererArgs_->hintText += std::string("\nNET\n") + ctx_->nameOf(net); } else if (closest.type == ElementType::PIP) { rendererArgs_->hintText = std::string("PIP\n") + ctx_->getPipName(closest.pip).c_str(ctx_); NetInfo *net = ctx_->getBoundPipNet(closest.pip); - if (net!=nullptr) - rendererArgs_->hintText += std::string("\nNET\n") +ctx_->nameOf(net); + if (net != nullptr) + rendererArgs_->hintText += std::string("\nNET\n") + ctx_->nameOf(net); } else if (closest.type == ElementType::GROUP) { rendererArgs_->hintText = std::string("GROUP\n") + ctx_->getGroupName(closest.group).c_str(ctx_); - } else rendererArgs_->hintText = ""; - + } else + rendererArgs_->hintText = ""; + pokeRenderer(); } update(); @@ -789,8 +782,9 @@ QVector4D FPGAViewWidget::mouseToWorldDimensions(float x, float y) void FPGAViewWidget::wheelEvent(QWheelEvent *event) { ImGuiIO &io = ImGui::GetIO(); - if (io.WantCaptureMouse) return; - + if (io.WantCaptureMouse) + return; + QPoint degree = event->angleDelta() / 8; if (!degree.isNull()) @@ -875,23 +869,17 @@ 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++) { - GraphicElement::style_t style = (GraphicElement::style_t)( - GraphicElement::STYLE_HIGHLIGHTED0 + i); + GraphicElement::style_t style = (GraphicElement::style_t)(GraphicElement::STYLE_HIGHLIGHTED0 + i); lineShader_.update_vbos(style, rendererData_->gfxHighlighted[i]); } - lineShader_.update_vbos(GraphicElement::STYLE_SELECTED, - rendererData_->gfxSelected); - lineShader_.update_vbos(GraphicElement::STYLE_HOVER, - rendererData_->gfxHovered); + lineShader_.update_vbos(GraphicElement::STYLE_SELECTED, rendererData_->gfxSelected); + lineShader_.update_vbos(GraphicElement::STYLE_HOVER, rendererData_->gfxHovered); } NEXTPNR_NAMESPACE_END diff --git a/gui/lineshader.cc b/gui/lineshader.cc index fc726df5..eded1689 100644 --- a/gui/lineshader.cc +++ b/gui/lineshader.cc @@ -206,8 +206,7 @@ bool LineShader::compile(void) return true; } -void LineShader::update_vbos(enum GraphicElement::style_t style, - const LineShaderData &line) +void LineShader::update_vbos(enum GraphicElement::style_t style, const LineShaderData &line) { if (buffers_[style].last_vbo_update == line.last_render) return; @@ -230,8 +229,8 @@ void LineShader::update_vbos(enum GraphicElement::style_t style, buffers_[style].index.allocate(&line.indices[0], sizeof(GLuint) * line.indices.size()); } -void LineShader::draw(enum GraphicElement::style_t style, const QColor &color, - float thickness, const QMatrix4x4 &projection) +void LineShader::draw(enum GraphicElement::style_t style, const QColor &color, float thickness, + const QMatrix4x4 &projection) { auto gl = QOpenGLContext::currentContext()->functions(); if (buffers_[style].indices == 0) diff --git a/gui/lineshader.h b/gui/lineshader.h index eb0f9e09..98042051 100644 --- a/gui/lineshader.h +++ b/gui/lineshader.h @@ -20,12 +20,12 @@ #ifndef LINESHADER_H #define LINESHADER_H -#include #include #include #include #include #include +#include #include "log.h" #include "nextpnr.h" @@ -169,9 +169,7 @@ class LineShader } uniforms_; public: - LineShader(QObject *parent) : parent_(parent), program_(nullptr) - { - } + LineShader(QObject *parent) : parent_(parent), program_(nullptr) {} static constexpr const char *vertexShaderSource_ = "#version 110\n" @@ -194,12 +192,10 @@ class LineShader // Must be called on initialization. bool compile(void); - void update_vbos(enum GraphicElement::style_t style, - const LineShaderData &line); + void update_vbos(enum GraphicElement::style_t style, const LineShaderData &line); // Render a LineShaderData with a given M/V/P transformation. - void draw(enum GraphicElement::style_t style, const QColor &color, - float thickness, const QMatrix4x4 &projection); + void draw(enum GraphicElement::style_t style, const QColor &color, float thickness, const QMatrix4x4 &projection); }; NEXTPNR_NAMESPACE_END 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/gfx.cc b/ice40/gfx.cc index c41c424b..7ef43663 100644 --- a/ice40/gfx.cc +++ b/ice40/gfx.cc @@ -972,11 +972,11 @@ void gfxTilePip(std::vector &g, int x, int y, GfxTileWireId src, return; } - if (getWireXY_local(src, x1, y1) && getWireXY_local(dst, x2, y2)) { + if (getWireXY_local(src, x1, y1) && getWireXY_local(dst, x2, y2)) { pipGfx(g, x, y, x1, y1, x2, y2, local_swbox_x1, local_swbox_y1, local_swbox_x2, local_swbox_y2, style); return; } - + if (TILE_WIRE_LUTFF_0_IN_0_LUT <= src && src <= TILE_WIRE_LUTFF_7_IN_3_LUT && TILE_WIRE_LUTFF_0_OUT <= dst && dst <= TILE_WIRE_LUTFF_7_OUT) { int lut_idx = (src - TILE_WIRE_LUTFF_0_IN_0_LUT) / 4; From 01950a2349d9119c6d77d412a07bd836fada365f Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Fri, 16 Nov 2018 16:24:56 +0100 Subject: [PATCH 43/67] ice40/bitstream: Convert to UNIX line endings Signed-off-by: Sylvain Munaut --- ice40/bitstream.cc | 2086 ++++++++++++++++++++++---------------------- 1 file changed, 1043 insertions(+), 1043 deletions(-) diff --git a/ice40/bitstream.cc b/ice40/bitstream.cc index 4efb1091..4e54df1d 100644 --- a/ice40/bitstream.cc +++ b/ice40/bitstream.cc @@ -1,1043 +1,1043 @@ -/* - * 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))); - 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)); + } + } + 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))); + 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 From e1e8d8cd14020b3b3ccf1995587438340b65bb5c Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Fri, 16 Nov 2018 14:44:24 +0100 Subject: [PATCH 44/67] ice40: Add warning if an instanciated SB_IO has its PACKAGE_PIN used elsewhere Signed-off-by: Sylvain Munaut --- ice40/pack.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ice40/pack.cc b/ice40/pack.cc index 7a27d505..e0a9f6ad 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -438,6 +438,11 @@ 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)) { + NetInfo *net = ci->ports.at(ctx->id("PACKAGE_PIN")).net; + if ((net != nullptr) && (net->users.size() > 1)) + log_error("PACKAGE_PIN of SB_IO '%s' connected to more than a single top level IO.\n", + ci->name.c_str(ctx)); } } for (auto pcell : packed_cells) { From 42fbb110fcea1ae05ba48433df86d6381715daeb Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Fri, 16 Nov 2018 22:53:53 +0100 Subject: [PATCH 45/67] ice40/bitstream: Handle IoCtrl.IE_ polarity when configuring unused SB_IO Signed-off-by: Sylvain Munaut --- ice40/bitstream.cc | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ice40/bitstream.cc b/ice40/bitstream.cc index 4e54df1d..5d138798 100644 --- a/ice40/bitstream.cc +++ b/ice40/bitstream.cc @@ -671,8 +671,13 @@ void write_asc(const Context *ctx, std::ostream &out) 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); + 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]; From 70e1fe423f663a3a02233e4f1c1f4f917d14c23c Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Sat, 17 Nov 2018 10:18:45 +0100 Subject: [PATCH 46/67] ice40/chipdb: Fix LOCKED keyword support to include all packages Signed-off-by: Sylvain Munaut --- ice40/chipdb.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ice40/chipdb.py b/ice40/chipdb.py index 5b2f3e57..2009483f 100644 --- a/ice40/chipdb.py +++ b/ice40/chipdb.py @@ -692,7 +692,8 @@ 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 From b29165eeba41317f160af7e386739ff5a31a8bb9 Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Sat, 17 Nov 2018 10:18:17 +0100 Subject: [PATCH 47/67] ice40/arch: Add helper to check if a BEL is LOCKED or not Signed-off-by: Sylvain Munaut --- ice40/arch.cc | 19 +++++++++++++++++++ ice40/arch.h | 2 ++ 2 files changed, 21 insertions(+) diff --git a/ice40/arch.cc b/ice40/arch.cc index 2a9e167b..995150ac 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 diff --git a/ice40/arch.h b/ice40/arch.h index 836dc46e..b6f10c03 100644 --- a/ice40/arch.h +++ b/ice40/arch.h @@ -510,6 +510,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; From 8c69a3bba3d72dd445f1003d470c3ed5ed06c5d4 Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Sat, 17 Nov 2018 10:19:08 +0100 Subject: [PATCH 48/67] ice40/pack: Make sure we don't use a LOCKED bel when placing PLL Signed-off-by: Sylvain Munaut --- ice40/pack.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ice40/pack.cc b/ice40/pack.cc index e0a9f6ad..d0142fa3 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -838,6 +838,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. From 5fb3353557853178c2787ea8a80afddf5ace8a4d Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Fri, 16 Nov 2018 22:55:57 +0100 Subject: [PATCH 49/67] ice40/pack: Allow PLL to be constrained via 'BEL' attributes Signed-off-by: Sylvain Munaut --- ice40/pack.cc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ice40/pack.cc b/ice40/pack.cc index d0142fa3..c7614830 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -886,6 +886,16 @@ static void pack_special(Context *ctx) 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. From ac5d767d4fc96a02cfcf5f06930c1aa5f41c97b4 Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Fri, 16 Nov 2018 22:55:31 +0100 Subject: [PATCH 50/67] ice40/pack: Stop looking for BEL when we have one during PLL placement Ideally we should first process all the PLL that are constrained somehow (either explicitely or because they are PAD) and then free place the rest. Signed-off-by: Sylvain Munaut --- ice40/pack.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/ice40/pack.cc b/ice40/pack.cc index c7614830..c91c97be 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -881,6 +881,7 @@ 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", From 35e9ec773776354c7a5bf2d5d2692c78111b603c Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Sun, 18 Nov 2018 19:16:08 +0100 Subject: [PATCH 51/67] ice40: Minor fix in predicate checking for logic port - is_sb_pll40 covers all the PLL types - Use helper to test for gbuf Signed-off-by: Sylvain Munaut --- ice40/pack.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ice40/pack.cc b/ice40/pack.cc index c91c97be..ece352a7 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -461,8 +461,9 @@ 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_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) From 78f3c2c37dbb007830704c5038c524991d171381 Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Fri, 16 Nov 2018 23:35:13 +0100 Subject: [PATCH 52/67] ice40: Make PLL default FEEDBACK_MODE to SIMPLE Signed-off-by: Sylvain Munaut --- ice40/cells.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ice40/cells.cc b/ice40/cells.cc index fbb77b0c..35255580 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"; From ad23caef33ed768f1e11b29e3c4c10edabd1c836 Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Sun, 18 Nov 2018 16:11:40 +0100 Subject: [PATCH 53/67] ice40/pll: Add proper support for PLLOUT_SELECT_xxx attributes Signed-off-by: Sylvain Munaut --- ice40/pack.cc | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/ice40/pack.cc b/ice40/pack.cc index ece352a7..d689fa69 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -733,6 +733,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" From f6d60229844d95ab94666629d2f0284b2f524227 Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Sun, 18 Nov 2018 16:11:14 +0100 Subject: [PATCH 54/67] ice40: Fix PLLTYPE for SB_PLL40_2F_PAD Signed-off-by: Sylvain Munaut --- ice40/cells.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ice40/cells.cc b/ice40/cells.cc index 35255580..3906334f 100644 --- a/ice40/cells.cc +++ b/ice40/cells.cc @@ -362,7 +362,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")) From 9483a95a4adfe9f9715a0066770e12f8b581185e Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Sun, 18 Nov 2018 19:19:07 +0100 Subject: [PATCH 55/67] ice40: Improve the is_sb_pll40_XXX predicates collection - Add a test for dual output PLL variant - Make them handle the packet version of the cell This will become useful for various tests during PLL rework Signed-off-by: Sylvain Munaut --- ice40/cells.h | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/ice40/cells.h b/ice40/cells.h index 054388ac..7f349020 100644 --- a/ice40/cells.h +++ b/ice40/cells.h @@ -81,7 +81,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); From c219d8fe4d3b8b6c79a715ca5808156287e7f642 Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Sun, 18 Nov 2018 19:21:41 +0100 Subject: [PATCH 56/67] ice40: Fix BEL validity check for PLL vs SB_IO Signed-off-by: Sylvain Munaut --- ice40/arch_place.cc | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/ice40/arch_place.cc b/ice40/arch_place.cc index c97b9c26..a09a5092 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; From 3f4dc7c80e19b9ff404055029c3239d29d0af25f Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Fri, 16 Nov 2018 00:04:55 +0100 Subject: [PATCH 57/67] ice40: Add GlobalNetowkrInfo in the chip database Signed-off-by: Sylvain Munaut --- ice40/arch.h | 18 ++++++++++- ice40/chipdb.py | 82 +++++++++++++++++++++++++++---------------------- 2 files changed, 63 insertions(+), 37 deletions(-) diff --git a/ice40/arch.h b/ice40/arch.h index b6f10c03..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; }); diff --git a/ice40/chipdb.py b/ice40/chipdb.py index 2009483f..6db6c26b 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 @@ -698,6 +711,27 @@ with open(args.filename, "r") as f: 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 @@ -974,42 +1008,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]) @@ -1424,6 +1424,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") @@ -1434,6 +1442,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") @@ -1442,6 +1451,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() From 325d46e284fd7944b99929c1482e641a1db53931 Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Mon, 19 Nov 2018 01:49:52 +0100 Subject: [PATCH 58/67] ice40/chipdb: Add wires to global network for all cells that can drive it The icebox DB is a bit inconsistent in how global network connections are represented. Here we make it appear consistent by creating ports on the cells that can drive it. Signed-off-by: Sylvain Munaut --- ice40/cells.cc | 4 ++-- ice40/chipdb.py | 22 ++++++++++++++++++---- ice40/constids.inc | 2 ++ 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/ice40/cells.cc b/ice40/cells.cc index 3906334f..53f2e10c 100644 --- a/ice40/cells.cc +++ b/ice40/cells.cc @@ -244,8 +244,8 @@ 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 { log_error("unable to create iCE40 cell of type %s", type.c_str(ctx)); } diff --git a/ice40/chipdb.py b/ice40/chipdb.py index 6db6c26b..96231b26 100644 --- a/ice40/chipdb.py +++ b/ice40/chipdb.py @@ -863,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)) @@ -920,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) @@ -929,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) 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) From bc9f2da470b96c4e2f65324a0da1ffe099b5e586 Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Mon, 19 Nov 2018 01:57:47 +0100 Subject: [PATCH 59/67] ice40: Introduce the concept of forPadIn SB_GB Those are cells that are created mainly to handle the various sources a global network can be driven from other than a user net. When the flag is set, this means the global network usually driven by this BEL is in fact driven by something else and so that SB_GB BEL and matching global network can't be used. This is also what gets used to set the extra bits during bitstream generation. Signed-off-by: Sylvain Munaut --- ice40/arch.cc | 2 ++ ice40/arch_place.cc | 2 ++ ice40/archdefs.h | 4 ++++ ice40/bitstream.cc | 18 +++++++++++++++++- ice40/pack.cc | 29 ++++++++++++++++++++++++++++- 5 files changed, 53 insertions(+), 2 deletions(-) diff --git a/ice40/arch.cc b/ice40/arch.cc index 995150ac..3138b813 100644 --- a/ice40/arch.cc +++ b/ice40/arch.cc @@ -1044,6 +1044,8 @@ 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"; + } 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_place.cc b/ice40/arch_place.cc index a09a5092..41f9b640 100644 --- a/ice40/arch_place.cc +++ b/ice40/arch_place.cc @@ -161,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/archdefs.h b/ice40/archdefs.h index b9614c07..8dcf0365 100644 --- a/ice40/archdefs.h +++ b/ice40/archdefs.h @@ -152,6 +152,10 @@ struct ArchCellInfo bool lvds; // TODO: clk packing checks... } ioInfo; + struct + { + bool forPadIn; + } gbInfo; }; }; diff --git a/ice40/bitstream.cc b/ice40/bitstream.cc index 5d138798..c32680ee 100644 --- a/ice40/bitstream.cc +++ b/ice40/bitstream.cc @@ -269,6 +269,9 @@ void write_asc(const Context *ctx, std::ostream &out) 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) { @@ -513,7 +516,16 @@ void write_asc(const Context *ctx, std::ostream &out) } } } else if (cell.second->type == ctx->id("SB_GB")) { - // no cell config bits + 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; @@ -795,6 +807,10 @@ void write_asc(const Context *ctx, std::ostream &out) } } + // 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()) { diff --git a/ice40/pack.cc b/ice40/pack.cc index d689fa69..cae6ab8c 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -381,6 +381,33 @@ 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") || @@ -1003,13 +1030,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(); From d8e4c21d96bfca7da60d0c445a1fdba48f46e9e1 Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Fri, 16 Nov 2018 02:03:25 +0100 Subject: [PATCH 60/67] ice40: Add support for PLL global outputs via PADIN Signed-off-by: Sylvain Munaut --- ice40/bitstream.cc | 94 ++++++++++++++++++++++++---------------------- ice40/pack.cc | 63 ++++++++++++------------------- 2 files changed, 73 insertions(+), 84 deletions(-) diff --git a/ice40/bitstream.cc b/ice40/bitstream.cc index c32680ee..d2c2ac16 100644 --- a/ice40/bitstream.cc +++ b/ice40/bitstream.cc @@ -356,8 +356,45 @@ void write_asc(const Context *ctx, std::ostream &out) } } - std::unordered_set sb_io_used_by_pll; - std::unordered_set sb_io_used_by_io; + // 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) { @@ -445,14 +482,15 @@ void write_asc(const Context *ctx, std::ostream &out) } 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"; + 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 = 0; i < 6; i++) { + 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); } @@ -475,6 +513,8 @@ void write_asc(const Context *ctx, std::ostream &out) } } + input_en = (input_en & !used_by_pll_out) | used_by_pll_pad; + 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); @@ -613,47 +653,13 @@ void write_asc(const Context *ctx, std::ostream &out) 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); - + for (auto &io_bel_loc : sb_io_used_by_pll_out) { // 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. + + // 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); } @@ -668,7 +674,7 @@ void write_asc(const Context *ctx, std::ostream &out) 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))) { + if (sb_io_used_by_pll_out.count(Loc(x, y, z))) { continue; } diff --git a/ice40/pack.cc b/ice40/pack.cc index cae6ab8c..ca67baab 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -790,32 +790,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); @@ -823,24 +797,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) { @@ -1011,6 +976,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)); } } From 519d4e2af8d8e7d8e2d1d873b9cd9681ef83cc62 Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Sat, 17 Nov 2018 13:04:14 +0100 Subject: [PATCH 61/67] ice40: Add support for SB_GB_IO During packing we replace them by standard SB_IO cells and create the 'fake' SB_GB that matches that IO site global buffer connection. It's done in a separate pass because we need to make sure the nextpnr iob have been dealt first so we have our final Bel location on the SB_IO. Signed-off-by: Sylvain Munaut --- ice40/arch.cc | 1 + ice40/archdefs.h | 1 + ice40/bitstream.cc | 1 + ice40/cells.h | 3 +++ ice40/pack.cc | 33 +++++++++++++++++++++++++-------- 5 files changed, 31 insertions(+), 8 deletions(-) diff --git a/ice40/arch.cc b/ice40/arch.cc index 3138b813..259eec67 100644 --- a/ice40/arch.cc +++ b/ice40/arch.cc @@ -1044,6 +1044,7 @@ 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/archdefs.h b/ice40/archdefs.h index 8dcf0365..2bffe667 100644 --- a/ice40/archdefs.h +++ b/ice40/archdefs.h @@ -150,6 +150,7 @@ struct ArchCellInfo struct { bool lvds; + bool global; // TODO: clk packing checks... } ioInfo; struct diff --git a/ice40/bitstream.cc b/ice40/bitstream.cc index d2c2ac16..44d385e4 100644 --- a/ice40/bitstream.cc +++ b/ice40/bitstream.cc @@ -514,6 +514,7 @@ void write_asc(const Context *ctx, std::ostream &out) } 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); diff --git a/ice40/cells.h b/ice40/cells.h index 7f349020..270292ed 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"); } diff --git a/ice40/pack.cc b/ice40/pack.cc index ca67baab..a40f0878 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -414,6 +414,11 @@ static bool is_nextpnr_iob(Context *ctx, CellInfo *cell) 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) { @@ -428,10 +433,10 @@ static void pack_io(Context *ctx) if (is_nextpnr_iob(ctx, ci)) { CellInfo *sb = 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); + sb = net_only_drives(ctx, ci->ports.at(ctx->id("I")).net, is_ice_iob, ctx->id("PACKAGE_PIN"), true, ci); } if (sb != nullptr) { // Trivial case, SB_IO used. Just destroy the net and the @@ -442,8 +447,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); @@ -465,13 +470,26 @@ 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)) { + } 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 SB_IO '%s' connected to more than a single top level IO.\n", + 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) { ctx->cells.erase(pcell); } @@ -488,8 +506,7 @@ 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_gbuf(ctx, port.cell) && + 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); } From 271cc7be115fc28ffe82a1bade235e41a3b43896 Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Mon, 19 Nov 2018 01:59:53 +0100 Subject: [PATCH 62/67] ice40/pack: Add helper to constain cells that are unique in the FPGA Signed-off-by: Sylvain Munaut --- ice40/pack.cc | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ice40/pack.cc b/ice40/pack.cc index a40f0878..255dc75f 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -695,6 +695,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) { From de8de6304f6905525fd5774d30851c0cc9fe4e37 Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Mon, 19 Nov 2018 02:00:59 +0100 Subject: [PATCH 63/67] ice40: Add global network output support for LFOSC/HFOSC Signed-off-by: Sylvain Munaut --- ice40/pack.cc | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/ice40/pack.cc b/ice40/pack.cc index 255dc75f..c0970735 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -725,25 +725,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)) { From e8556aff372c77c1e14a4378b43b47f8ba1e75ec Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Sat, 17 Nov 2018 18:02:31 +0100 Subject: [PATCH 64/67] ice40: Add support for SB_RGBA_DRV Signed-off-by: Sylvain Munaut --- ice40/arch.cc | 4 ++++ ice40/bitstream.cc | 5 +++++ ice40/cells.cc | 14 ++++++++++++++ ice40/cells.h | 2 ++ ice40/pack.cc | 35 +++++++++++++++++++++++++++++++++-- 5 files changed, 58 insertions(+), 2 deletions(-) diff --git a/ice40/arch.cc b/ice40/arch.cc index 259eec67..02e5515b 100644 --- a/ice40/arch.cc +++ b/ice40/arch.cc @@ -946,6 +946,10 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, in 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)); } diff --git a/ice40/bitstream.cc b/ice40/bitstream.cc index 44d385e4..ecb26753 100644 --- a/ice40/bitstream.cc +++ b/ice40/bitstream.cc @@ -586,6 +586,11 @@ void write_asc(const Context *ctx, std::ostream &out) 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")) { diff --git a/ice40/cells.cc b/ice40/cells.cc index 53f2e10c..dbb75c2c 100644 --- a/ice40/cells.cc +++ b/ice40/cells.cc @@ -246,6 +246,20 @@ std::unique_ptr create_ice_cell(Context *ctx, IdString type, std::stri add_port(ctx, new_cell.get(), "PLLOUT_B", 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)); } diff --git a/ice40/cells.h b/ice40/cells.h index 270292ed..1fbd9073 100644 --- a/ice40/cells.h +++ b/ice40/cells.h @@ -74,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") || diff --git a/ice40/pack.cc b/ice40/pack.cc index c0970735..dae19b2d 100644 --- a/ice40/pack.cc +++ b/ice40/pack.cc @@ -431,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_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_ice_iob, 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 @@ -460,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 = @@ -787,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; From d6fd0e7e5b3d0d7eef4013c1b0547a3066b0a485 Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Sat, 17 Nov 2018 19:19:32 +0100 Subject: [PATCH 65/67] common/placer1: In random pick, only use grid if there is more than 64 BELs If you have a large grid and very few BELs of a given type, picking a random grid location yields very little odds of finding a BEL of that type. So for those, just put all of them at (0,0) and do a true random pick. Signed-off-by: Sylvain Munaut --- common/placer1.cc | 32 ++++++++++++++++++++++---------- common/placer1.h | 1 + 2 files changed, 23 insertions(+), 10 deletions(-) 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); From e167043e73ca8ce0e347be6cac4ee3f3c6024b57 Mon Sep 17 00:00:00 2001 From: Maik Merten Date: Mon, 19 Nov 2018 19:45:12 +0100 Subject: [PATCH 66/67] add "randomize-seed" command-line option --- common/command.cc | 10 ++++++++++ 1 file changed, 10 insertions(+) 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) { From b5d518583ec0b3799ce213935acca3ac0654c42f Mon Sep 17 00:00:00 2001 From: Clifford Wolf Date: Tue, 20 Nov 2018 18:58:15 +0100 Subject: [PATCH 67/67] Add missing router1 ctx->yield() calls Signed-off-by: Clifford Wolf --- common/router1.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/router1.cc b/common/router1.cc index 198461bc..a3388fa8 100644 --- a/common/router1.cc +++ b/common/router1.cc @@ -777,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 @@ -802,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();