Merge branch 'master' into xc7

This commit is contained in:
Eddie Hung 2018-11-20 14:26:26 -08:00
commit 75b48dfe1e
41 changed files with 3509 additions and 1443 deletions

View File

@ -102,6 +102,7 @@ po::options_description CommandHandler::getGeneralOptions()
#endif
general.add_options()("json", po::value<std::string>(), "JSON design file to ingest");
general.add_options()("seed", po::value<int>(), "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<int>(), "number of iterations between slack redistribution");
general.add_options()("cstrweight", po::value<float>(), "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<int>());
}
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<int>();
if (vm.count("freq") && vm["freq"].as<double>() == 0) {

View File

@ -53,6 +53,107 @@ void IdString::initialize_add(const BaseCtx *ctx, const char *s, int idx)
ctx->idstring_idx_to_str->push_back(&insert_rc.first->first);
}
TimingConstrObjectId BaseCtx::timingWildcardObject()
{
TimingConstrObjectId id;
id.index = 0;
return id;
}
TimingConstrObjectId BaseCtx::timingClockDomainObject(NetInfo *clockDomain)
{
NPNR_ASSERT(clockDomain->clkconstr != nullptr);
if (clockDomain->clkconstr->domain_tmg_id != TimingConstrObjectId()) {
return clockDomain->clkconstr->domain_tmg_id;
} else {
TimingConstraintObject obj;
TimingConstrObjectId id;
id.index = int(constraintObjects.size());
obj.id = id;
obj.type = TimingConstraintObject::CLOCK_DOMAIN;
obj.entity = clockDomain->name;
clockDomain->clkconstr->domain_tmg_id = id;
constraintObjects.push_back(obj);
return id;
}
}
TimingConstrObjectId BaseCtx::timingNetObject(NetInfo *net)
{
if (net->tmg_id != TimingConstrObjectId()) {
return net->tmg_id;
} else {
TimingConstraintObject obj;
TimingConstrObjectId id;
id.index = int(constraintObjects.size());
obj.id = id;
obj.type = TimingConstraintObject::NET;
obj.entity = net->name;
constraintObjects.push_back(obj);
net->tmg_id = id;
return id;
}
}
TimingConstrObjectId BaseCtx::timingCellObject(CellInfo *cell)
{
if (cell->tmg_id != TimingConstrObjectId()) {
return cell->tmg_id;
} else {
TimingConstraintObject obj;
TimingConstrObjectId id;
id.index = int(constraintObjects.size());
obj.id = id;
obj.type = TimingConstraintObject::CELL;
obj.entity = cell->name;
constraintObjects.push_back(obj);
cell->tmg_id = id;
return id;
}
}
TimingConstrObjectId BaseCtx::timingPortObject(CellInfo *cell, IdString port)
{
if (cell->ports.at(port).tmg_id != TimingConstrObjectId()) {
return cell->ports.at(port).tmg_id;
} else {
TimingConstraintObject obj;
TimingConstrObjectId id;
id.index = int(constraintObjects.size());
obj.id = id;
obj.type = TimingConstraintObject::CELL_PORT;
obj.entity = cell->name;
obj.port = port;
constraintObjects.push_back(obj);
cell->ports.at(port).tmg_id = id;
return id;
}
}
void BaseCtx::addConstraint(std::unique_ptr<TimingConstraint> constr)
{
for (auto fromObj : constr->from)
constrsFrom.emplace(fromObj, constr.get());
for (auto toObj : constr->to)
constrsTo.emplace(toObj, constr.get());
IdString name = constr->name;
constraints[name] = std::move(constr);
}
void BaseCtx::removeConstraint(IdString constrName)
{
TimingConstraint *constr = constraints[constrName].get();
for (auto fromObj : constr->from) {
auto fromConstrs = constrsFrom.equal_range(fromObj);
constrsFrom.erase(std::find(fromConstrs.first, fromConstrs.second, std::make_pair(fromObj, constr)));
}
for (auto toObj : constr->to) {
auto toConstrs = constrsFrom.equal_range(toObj);
constrsFrom.erase(std::find(toConstrs.first, toConstrs.second, std::make_pair(toObj, constr)));
}
constraints.erase(constrName);
}
const char *BaseCtx::nameOfBel(BelId bel) const
{
const Context *ctx = getCtx();
@ -306,4 +407,14 @@ void Context::check() const
}
}
void BaseCtx::addClock(IdString net, float freq)
{
log_info("constraining clock net '%s' to %.02f MHz\n", net.c_str(this), freq);
std::unique_ptr<ClockConstraint> 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

View File

@ -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<NEXTPNR_NAMESPACE_PREFIX Loc>
return seed;
}
};
template <> struct hash<NEXTPNR_NAMESPACE_PREFIX TimingConstrObjectId>
{
std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX TimingConstrObjectId &obj) const noexcept
{
return hash<int>()(obj.index);
}
};
} // namespace std
#include "archdefs.h"
@ -266,6 +283,8 @@ struct PipMap
PlaceStrength strength = STRENGTH_NONE;
};
struct ClockConstraint;
struct NetInfo : ArchNetInfo
{
IdString name;
@ -278,6 +297,10 @@ struct NetInfo : ArchNetInfo
// wire -> uphill_pip
std::unordered_map<WireId, PipMap> wires;
std::unique_ptr<ClockConstraint> clkconstr;
TimingConstrObjectId tmg_id;
Region *region = nullptr;
};
@ -293,6 +316,7 @@ struct PortInfo
IdString name;
NetInfo *net;
PortType type;
TimingConstrObjectId tmg_id;
};
struct CellInfo : ArchCellInfo
@ -320,6 +344,7 @@ struct CellInfo : ArchCellInfo
// parent.[xyz] := 0 when (constr_parent == nullptr)
Region *region = nullptr;
TimingConstrObjectId tmg_id;
};
enum TimingPortClass
@ -335,6 +360,68 @@ enum TimingPortClass
TMG_IGNORE, // Asynchronous to all clocks, "don't care", and should be ignored (false path) for analysis
};
enum ClockEdge
{
RISING_EDGE,
FALLING_EDGE
};
struct TimingClockingInfo
{
IdString clock_port; // Port name of clock domain
ClockEdge edge;
DelayInfo setup, hold; // Input timing checks
DelayInfo clockToQ; // Output clock-to-Q time
};
struct ClockConstraint
{
DelayInfo high;
DelayInfo low;
DelayInfo period;
TimingConstrObjectId domain_tmg_id;
};
struct TimingConstraintObject
{
TimingConstrObjectId id;
enum
{
ANYTHING,
CLOCK_DOMAIN,
NET,
CELL,
CELL_PORT
} type;
IdString entity; // Name of clock net; net or cell
IdString port; // Name of port on a cell
};
struct TimingConstraint
{
IdString name;
enum
{
FALSE_PATH,
MIN_DELAY,
MAX_DELAY,
MULTICYCLE,
} type;
delay_t value;
std::unordered_set<TimingConstrObjectId> from;
std::unordered_set<TimingConstrObjectId> to;
};
inline bool operator==(const std::pair<const TimingConstrObjectId, TimingConstraint *> &a,
const std::pair<TimingConstrObjectId, TimingConstraint *> &b)
{
return a.first == b.first && a.second == b.second;
}
struct DeterministicRNG
{
uint64_t rngstate;
@ -431,6 +518,11 @@ struct BaseCtx
idstring_idx_to_str = new std::vector<const std::string *>;
IdString::initialize_add(this, "", 0);
IdString::initialize_arch(this);
TimingConstraintObject wildcard;
wildcard.id.index = 0;
wildcard.type = TimingConstraintObject::ANYTHING;
constraintObjects.push_back(wildcard);
}
~BaseCtx()
@ -487,10 +579,7 @@ struct BaseCtx
const Context *getCtx() const { return reinterpret_cast<const Context *>(this); }
const char *nameOf(IdString name) const
{
return name.c_str(this);
}
const char *nameOf(IdString name) const { return name.c_str(this); }
template <typename T> const char *nameOf(const T *obj) const
{
@ -524,6 +613,30 @@ struct BaseCtx
void refreshUiPip(PipId pip) { pipUiReload.insert(pip); }
void refreshUiGroup(GroupId group) { groupUiReload.insert(group); }
// --------------------------------------------------------------
// Timing Constraint API
// constraint name -> constraint
std::unordered_map<IdString, std::unique_ptr<TimingConstraint>> constraints;
// object ID -> object
std::vector<TimingConstraintObject> constraintObjects;
// object ID -> constraint
std::unordered_multimap<TimingConstrObjectId, TimingConstraint *> constrsFrom;
std::unordered_multimap<TimingConstrObjectId, TimingConstraint *> 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<TimingConstraint> constr);
void removeConstraint(IdString constrName);
// Intended to simplify Python API
void addClock(IdString net, float freq);
};
NEXTPNR_NAMESPACE_END

View File

@ -36,8 +36,9 @@ wirelen_t get_net_metric(const Context *ctx, const NetInfo *net, MetricType type
bool driver_gb = ctx->getBelGlobalBuf(driver_cell->bel);
if (driver_gb)
return 0;
IdString clock_port;
bool timing_driven = ctx->timing_driven && type == MetricType::COST && ctx->getPortTimingClass(driver_cell, net->driver.port, clock_port) != TMG_IGNORE;
int clock_count;
bool timing_driven = ctx->timing_driven && type == MetricType::COST &&
ctx->getPortTimingClass(driver_cell, net->driver.port, clock_count) != TMG_IGNORE;
delay_t negative_slack = 0;
delay_t worst_slack = std::numeric_limits<delay_t>::max();
Loc driver_loc = ctx->getBelLocation(driver_cell->bel);

View File

@ -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<int, int>(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<IdString, int> bel_types;
std::unordered_map<IdString, std::tuple<int, int>> bel_types;
std::vector<std::vector<std::vector<std::vector<BelId>>>> fast_bels;
std::unordered_set<BelId> locked_bels;
bool require_legal = true;
@ -503,7 +511,11 @@ class SAPlacer
std::vector<decltype(NetInfo::udata)> old_udata;
};
Placer1Cfg::Placer1Cfg(Context *ctx) : Settings(ctx) { constraintWeight = get<float>("placer1/constraintWeight", 10); }
Placer1Cfg::Placer1Cfg(Context *ctx) : Settings(ctx)
{
constraintWeight = get<float>("placer1/constraintWeight", 10);
minBelsForGridPick = get<int>("placer1/minBelsForGridPick", 64);
}
bool placer1(Context *ctx, Placer1Cfg cfg)
{

View File

@ -28,6 +28,7 @@ struct Placer1Cfg : public Settings
{
Placer1Cfg(Context *ctx);
float constraintWeight;
int minBelsForGridPick;
};
extern bool placer1(Context *ctx, Placer1Cfg cfg);

View File

@ -34,7 +34,10 @@ struct arc_key
int user_idx;
bool operator==(const arc_key &other) const { return (net_info == other.net_info) && (user_idx == other.user_idx); }
bool operator<(const arc_key &other) const { return net_info == other.net_info ? user_idx < other.user_idx : net_info->name < other.net_info->name; }
bool operator<(const arc_key &other) const
{
return net_info == other.net_info ? user_idx < other.user_idx : net_info->name < other.net_info->name;
}
struct Hash
{
@ -375,21 +378,20 @@ struct Router1
if (dst_wire == WireId())
log_error("No wire found for port %s on destination cell %s.\n",
ctx->nameOf(net_info->users[user_idx].port),
ctx->nameOf(net_info->users[user_idx].cell));
ctx->nameOf(net_info->users[user_idx].port), ctx->nameOf(net_info->users[user_idx].cell));
if (dst_to_arc.count(dst_wire)) {
if (dst_to_arc.at(dst_wire).net_info == net_info)
continue;
log_error("Found two arcs with same sink wire %s: %s (%d) vs %s (%d)\n",
ctx->nameOfWire(dst_wire), ctx->nameOf(net_info), user_idx,
ctx->nameOf(dst_to_arc.at(dst_wire).net_info), dst_to_arc.at(dst_wire).user_idx);
log_error("Found two arcs with same sink wire %s: %s (%d) vs %s (%d)\n", ctx->nameOfWire(dst_wire),
ctx->nameOf(net_info), user_idx, ctx->nameOf(dst_to_arc.at(dst_wire).net_info),
dst_to_arc.at(dst_wire).user_idx);
}
if (src_to_net.count(dst_wire))
log_error("Wire %s is used as source and sink in different nets: %s vs %s (%d)\n",
ctx->nameOfWire(dst_wire), ctx->nameOf(src_to_net.at(dst_wire)),
ctx->nameOf(net_info), user_idx);
ctx->nameOfWire(dst_wire), ctx->nameOf(src_to_net.at(dst_wire)), ctx->nameOf(net_info),
user_idx);
arc_key arc;
arc.net_info = net_info;
@ -775,6 +777,7 @@ bool router1(Context *ctx, const Router1Cfg &cfg)
router.arcs_without_ripup - last_arcs_without_ripup, int(router.arc_queue.size()));
last_arcs_with_ripup = router.arcs_with_ripup;
last_arcs_without_ripup = router.arcs_without_ripup;
ctx->yield();
#ifndef NDEBUG
router.check();
#endif
@ -800,6 +803,7 @@ bool router1(Context *ctx, const Router1Cfg &cfg)
router.arcs_with_ripup - last_arcs_with_ripup, router.arcs_without_ripup - last_arcs_without_ripup,
int(router.arc_queue.size()));
log_info("Routing complete.\n");
ctx->yield();
#ifndef NDEBUG
router.check();
@ -808,7 +812,7 @@ bool router1(Context *ctx, const Router1Cfg &cfg)
#endif
log_info("Checksum: 0x%08x\n", ctx->checksum());
timing_analysis(ctx, true /* slack_histogram */, true /* print_path */);
timing_analysis(ctx, true /* slack_histogram */, true /* print_fmax */, true /* print_path */);
ctx->unlock();
return true;
@ -950,8 +954,7 @@ bool Context::checkRoutedDesign() const
for (WireId w : dangling_wires) {
if (logged_wires.count(w) == 0)
log(" loop: %s -> %s\n",
ctx->nameOfWire(ctx->getPipSrcWire(net_info->wires.at(w).pip)),
log(" loop: %s -> %s\n", ctx->nameOfWire(ctx->getPipSrcWire(net_info->wires.at(w).pip)),
ctx->nameOfWire(w));
}
}

View File

@ -22,6 +22,7 @@
#include <algorithm>
#include <boost/range/adaptor/reversed.hpp>
#include <deque>
#include <map>
#include <unordered_map>
#include <utility>
#include "log.h"
@ -29,17 +30,72 @@
NEXTPNR_NAMESPACE_BEGIN
namespace {
struct ClockEvent
{
IdString clock;
ClockEdge edge;
bool operator==(const ClockEvent &other) const { return clock == other.clock && edge == other.edge; }
};
struct ClockPair
{
ClockEvent start, end;
bool operator==(const ClockPair &other) const { return start == other.start && end == other.end; }
};
} // namespace
NEXTPNR_NAMESPACE_END
namespace std {
template <> struct hash<NEXTPNR_NAMESPACE_PREFIX ClockEvent>
{
std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX ClockEvent &obj) const noexcept
{
std::size_t seed = 0;
boost::hash_combine(seed, hash<NEXTPNR_NAMESPACE_PREFIX IdString>()(obj.clock));
boost::hash_combine(seed, hash<int>()(int(obj.edge)));
return seed;
}
};
template <> struct hash<NEXTPNR_NAMESPACE_PREFIX ClockPair>
{
std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX ClockPair &obj) const noexcept
{
std::size_t seed = 0;
boost::hash_combine(seed, hash<NEXTPNR_NAMESPACE_PREFIX ClockEvent>()(obj.start));
boost::hash_combine(seed, hash<NEXTPNR_NAMESPACE_PREFIX ClockEvent>()(obj.start));
return seed;
}
};
} // namespace std
NEXTPNR_NAMESPACE_BEGIN
typedef std::vector<const PortRef *> PortRefVector;
typedef std::map<int, unsigned> DelayFrequency;
struct CriticalPath
{
PortRefVector ports;
delay_t path_delay;
delay_t path_period;
};
typedef std::unordered_map<ClockPair, CriticalPath> CriticalPathMap;
struct Timing
{
Context *ctx;
bool net_delays;
bool update;
delay_t min_slack;
PortRefVector *crit_path;
CriticalPathMap *crit_path;
DelayFrequency *slack_histogram;
IdString async_clock;
struct TimingData
{
@ -49,23 +105,24 @@ struct Timing
unsigned max_path_length = 0;
delay_t min_remaining_budget;
bool false_startpoint = false;
std::unordered_map<ClockEvent, delay_t> arrival_time;
};
Timing(Context *ctx, bool net_delays, bool update, PortRefVector *crit_path = nullptr,
Timing(Context *ctx, bool net_delays, bool update, CriticalPathMap *crit_path = nullptr,
DelayFrequency *slack_histogram = nullptr)
: ctx(ctx), net_delays(net_delays), update(update), min_slack(1.0e12 / ctx->target_freq),
crit_path(crit_path), slack_histogram(slack_histogram)
crit_path(crit_path), slack_histogram(slack_histogram), async_clock(ctx->id("$async$"))
{
}
delay_t walk_paths()
{
const auto clk_period = delay_t(1.0e12 / ctx->target_freq);
const auto clk_period = ctx->getDelayFromNS(1.0e9 / ctx->target_freq).maxDelay();
// First, compute the topographical order of nets to walk through the circuit, assuming it is a _acyclic_ graph
// TODO(eddieh): Handle the case where it is cyclic, e.g. combinatorial loops
std::vector<NetInfo *> topographical_order;
std::unordered_map<const NetInfo *, TimingData> net_data;
std::unordered_map<const NetInfo *, std::unordered_map<ClockEvent, TimingData>> net_data;
// In lieu of deleting edges from the graph, simply count the number of fanins to each output port
std::unordered_map<const PortInfo *, unsigned> port_fanin;
@ -84,22 +141,34 @@ struct Timing
}
for (auto o : output_ports) {
IdString clockPort;
TimingPortClass portClass = ctx->getPortTimingClass(cell.second.get(), o->name, clockPort);
int clocks = 0;
TimingPortClass portClass = ctx->getPortTimingClass(cell.second.get(), o->name, clocks);
// If output port is influenced by a clock (e.g. FF output) then add it to the ordering as a timing
// start-point
if (portClass == TMG_REGISTER_OUTPUT) {
DelayInfo clkToQ;
ctx->getCellDelay(cell.second.get(), clockPort, o->name, clkToQ);
topographical_order.emplace_back(o->net);
net_data.emplace(o->net, TimingData{clkToQ.maxDelay()});
for (int i = 0; i < clocks; i++) {
TimingClockingInfo clkInfo = ctx->getPortClockingInfo(cell.second.get(), o->name, i);
const NetInfo *clknet = get_net_or_empty(cell.second.get(), clkInfo.clock_port);
IdString clksig = clknet ? clknet->name : async_clock;
net_data[o->net][ClockEvent{clksig, clknet ? clkInfo.edge : RISING_EDGE}] =
TimingData{clkInfo.clockToQ.maxDelay()};
}
} else {
if (portClass == TMG_STARTPOINT || portClass == TMG_GEN_CLOCK || portClass == TMG_IGNORE) {
topographical_order.emplace_back(o->net);
TimingData td;
td.false_startpoint = (portClass == TMG_GEN_CLOCK || portClass == TMG_IGNORE);
net_data.emplace(o->net, std::move(td));
td.max_arrival = 0;
net_data[o->net][ClockEvent{async_clock, RISING_EDGE}] = td;
}
// Don't analyse paths from a clock input to other pins - they will be considered by the
// special-case handling register input/output class ports
if (portClass == TMG_CLOCK_INPUT)
continue;
// Otherwise, for all driven input ports on this cell, if a timing arc exists between the input and
// the current output port, increment fanin counter
for (auto i : input_ports) {
@ -120,14 +189,15 @@ struct Timing
queue.pop_front();
for (auto &usr : net->users) {
IdString clockPort;
TimingPortClass usrClass = ctx->getPortTimingClass(usr.cell, usr.port, clockPort);
int user_clocks;
TimingPortClass usrClass = ctx->getPortTimingClass(usr.cell, usr.port, user_clocks);
if (usrClass == TMG_IGNORE || usrClass == TMG_CLOCK_INPUT)
continue;
for (auto &port : usr.cell->ports) {
if (port.second.type != PORT_OUT || !port.second.net)
continue;
TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, port.first, clockPort);
int port_clocks;
TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, port.first, port_clocks);
// Skip if this is a clocked output (but allow non-clocked ones)
if (portClass == TMG_REGISTER_OUTPUT || portClass == TMG_STARTPOINT || portClass == TMG_IGNORE ||
@ -167,97 +237,166 @@ struct Timing
}
}
if (ctx->force)
log_warning("timing analysis failed due to presence of combinatorial loops, incomplete specification of timing ports, etc.\n");
log_warning("timing analysis failed due to presence of combinatorial loops, incomplete specification "
"of timing ports, etc.\n");
else
log_error("timing analysis failed due to presence of combinatorial loops, incomplete specification of timing ports, etc.\n");
log_error("timing analysis failed due to presence of combinatorial loops, incomplete specification of "
"timing ports, etc.\n");
}
// Go forwards topographically to find the maximum arrival time and max path length for each net
for (auto net : topographical_order) {
auto &nd = net_data.at(net);
const auto net_arrival = nd.max_arrival;
const auto net_length_plus_one = nd.max_path_length + 1;
nd.min_remaining_budget = clk_period;
for (auto &usr : net->users) {
IdString clockPort;
TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, clockPort);
if (portClass == TMG_REGISTER_INPUT || portClass == TMG_ENDPOINT || portClass == TMG_IGNORE) {
} else {
if (!net_data.count(net))
continue;
auto &nd_map = net_data.at(net);
for (auto &startdomain : nd_map) {
ClockEvent start_clk = startdomain.first;
auto &nd = startdomain.second;
if (nd.false_startpoint)
continue;
const auto net_arrival = nd.max_arrival;
const auto net_length_plus_one = nd.max_path_length + 1;
nd.min_remaining_budget = clk_period;
for (auto &usr : net->users) {
int port_clocks;
TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, port_clocks);
auto net_delay = net_delays ? ctx->getNetinfoRouteDelay(net, usr) : delay_t();
auto budget_override = ctx->getBudgetOverride(net, usr, net_delay);
auto usr_arrival = net_arrival + net_delay;
// Iterate over all output ports on the same cell as the sink
for (auto port : usr.cell->ports) {
if (port.second.type != PORT_OUT || !port.second.net)
continue;
DelayInfo comb_delay;
// Look up delay through this path
bool is_path = ctx->getCellDelay(usr.cell, usr.port, port.first, comb_delay);
if (!is_path)
continue;
auto &data = net_data[port.second.net];
auto &arrival = data.max_arrival;
arrival = std::max(arrival, usr_arrival + comb_delay.maxDelay());
if (!budget_override) { // Do not increment path length if budget overriden since it doesn't
// require a share of the slack
auto &path_length = data.max_path_length;
path_length = std::max(path_length, net_length_plus_one);
if (portClass == TMG_REGISTER_INPUT || portClass == TMG_ENDPOINT || portClass == TMG_IGNORE ||
portClass == TMG_CLOCK_INPUT) {
// Skip
} else {
auto budget_override = ctx->getBudgetOverride(net, usr, net_delay);
// Iterate over all output ports on the same cell as the sink
for (auto port : usr.cell->ports) {
if (port.second.type != PORT_OUT || !port.second.net)
continue;
DelayInfo comb_delay;
// Look up delay through this path
bool is_path = ctx->getCellDelay(usr.cell, usr.port, port.first, comb_delay);
if (!is_path)
continue;
auto &data = net_data[port.second.net][start_clk];
auto &arrival = data.max_arrival;
arrival = std::max(arrival, usr_arrival + comb_delay.maxDelay());
if (!budget_override) { // Do not increment path length if budget overriden since it doesn't
// require a share of the slack
auto &path_length = data.max_path_length;
path_length = std::max(path_length, net_length_plus_one);
}
}
}
}
}
}
const NetInfo *crit_net = nullptr;
std::unordered_map<ClockPair, std::pair<delay_t, NetInfo *>> crit_nets;
// Now go backwards topographically to determine the minimum path slack, and to distribute all path slack evenly
// between all nets on the path
for (auto net : boost::adaptors::reverse(topographical_order)) {
auto &nd = net_data.at(net);
// Ignore false startpoints
if (nd.false_startpoint)
if (!net_data.count(net))
continue;
const delay_t net_length_plus_one = nd.max_path_length + 1;
auto &net_min_remaining_budget = nd.min_remaining_budget;
for (auto &usr : net->users) {
auto net_delay = net_delays ? ctx->getNetinfoRouteDelay(net, usr) : delay_t();
auto budget_override = ctx->getBudgetOverride(net, usr, net_delay);
IdString associatedClock;
TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, associatedClock);
if (portClass == TMG_REGISTER_INPUT || portClass == TMG_ENDPOINT) {
const auto net_arrival = nd.max_arrival;
auto path_budget = clk_period - (net_arrival + net_delay);
if (update) {
auto budget_share = budget_override ? 0 : path_budget / net_length_plus_one;
usr.budget = std::min(usr.budget, net_delay + budget_share);
net_min_remaining_budget = std::min(net_min_remaining_budget, path_budget - budget_share);
}
auto &nd_map = net_data.at(net);
for (auto &startdomain : nd_map) {
auto &nd = startdomain.second;
// Ignore false startpoints
if (nd.false_startpoint)
continue;
const delay_t net_length_plus_one = nd.max_path_length + 1;
auto &net_min_remaining_budget = nd.min_remaining_budget;
for (auto &usr : net->users) {
auto net_delay = net_delays ? ctx->getNetinfoRouteDelay(net, usr) : delay_t();
auto budget_override = ctx->getBudgetOverride(net, usr, net_delay);
int port_clocks;
TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, port_clocks);
if (portClass == TMG_REGISTER_INPUT || portClass == TMG_ENDPOINT) {
auto process_endpoint = [&](IdString clksig, ClockEdge edge, delay_t setup) {
const auto net_arrival = nd.max_arrival;
const auto endpoint_arrival = net_arrival + net_delay + setup;
delay_t period;
// Set default period
if (edge == startdomain.first.edge) {
period = clk_period;
} else {
period = clk_period / 2;
}
if (clksig != async_clock) {
if (ctx->nets.at(clksig)->clkconstr) {
if (edge == startdomain.first.edge) {
// same edge
period = ctx->nets.at(clksig)->clkconstr->period.minDelay();
} else if (edge == RISING_EDGE) {
// falling -> rising
period = ctx->nets.at(clksig)->clkconstr->low.minDelay();
} else if (edge == FALLING_EDGE) {
// rising -> falling
period = ctx->nets.at(clksig)->clkconstr->high.minDelay();
}
}
}
auto path_budget = period - endpoint_arrival;
if (path_budget < min_slack) {
min_slack = path_budget;
if (crit_path) {
crit_path->clear();
crit_path->push_back(&usr);
crit_net = net;
if (update) {
auto budget_share = budget_override ? 0 : path_budget / net_length_plus_one;
usr.budget = std::min(usr.budget, net_delay + budget_share);
net_min_remaining_budget =
std::min(net_min_remaining_budget, path_budget - budget_share);
}
if (path_budget < min_slack)
min_slack = path_budget;
if (slack_histogram) {
int slack_ps = ctx->getDelayNS(path_budget) * 1000;
(*slack_histogram)[slack_ps]++;
}
ClockEvent dest_ev{clksig, edge};
ClockPair clockPair{startdomain.first, dest_ev};
nd.arrival_time[dest_ev] = std::max(nd.arrival_time[dest_ev], endpoint_arrival);
if (crit_path) {
if (!crit_nets.count(clockPair) || crit_nets.at(clockPair).first < endpoint_arrival) {
crit_nets[clockPair] = std::make_pair(endpoint_arrival, net);
(*crit_path)[clockPair].path_delay = endpoint_arrival;
(*crit_path)[clockPair].path_period = period;
(*crit_path)[clockPair].ports.clear();
(*crit_path)[clockPair].ports.push_back(&usr);
}
}
};
if (portClass == TMG_REGISTER_INPUT) {
for (int i = 0; i < port_clocks; i++) {
TimingClockingInfo clkInfo = ctx->getPortClockingInfo(usr.cell, usr.port, i);
const NetInfo *clknet = get_net_or_empty(usr.cell, clkInfo.clock_port);
IdString clksig = clknet ? clknet->name : async_clock;
process_endpoint(clksig, clknet ? clkInfo.edge : RISING_EDGE, clkInfo.setup.maxDelay());
}
} else {
process_endpoint(async_clock, RISING_EDGE, 0);
}
} else if (update) {
// Iterate over all output ports on the same cell as the sink
for (const auto &port : usr.cell->ports) {
if (port.second.type != PORT_OUT || !port.second.net)
continue;
DelayInfo comb_delay;
bool is_path = ctx->getCellDelay(usr.cell, usr.port, port.first, comb_delay);
if (!is_path)
continue;
if (net_data.count(port.second.net) &&
net_data.at(port.second.net).count(startdomain.first)) {
auto path_budget =
net_data.at(port.second.net).at(startdomain.first).min_remaining_budget;
auto budget_share = budget_override ? 0 : path_budget / net_length_plus_one;
usr.budget = std::min(usr.budget, net_delay + budget_share);
net_min_remaining_budget =
std::min(net_min_remaining_budget, path_budget - budget_share);
}
}
}
if (slack_histogram) {
int slack_ps = ctx->getDelayNS(path_budget) * 1000;
(*slack_histogram)[slack_ps]++;
}
} else if (update) {
// Iterate over all output ports on the same cell as the sink
for (const auto &port : usr.cell->ports) {
if (port.second.type != PORT_OUT || !port.second.net)
continue;
DelayInfo comb_delay;
bool is_path = ctx->getCellDelay(usr.cell, usr.port, port.first, comb_delay);
if (!is_path)
continue;
auto path_budget = net_data.at(port.second.net).min_remaining_budget;
auto budget_share = budget_override ? 0 : path_budget / net_length_plus_one;
usr.budget = std::min(usr.budget, net_delay + budget_share);
net_min_remaining_budget = std::min(net_min_remaining_budget, path_budget - budget_share);
}
}
}
@ -265,47 +404,55 @@ struct Timing
if (crit_path) {
// Walk backwards from the most critical net
while (crit_net) {
const PortInfo *crit_ipin = nullptr;
delay_t max_arrival = std::numeric_limits<delay_t>::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<delay_t>::min();
// Look at all input ports on its driving cell
for (const auto &port : crit_net->driver.cell->ports) {
if (port.second.type != PORT_IN || !port.second.net)
continue;
DelayInfo comb_delay;
bool is_path =
ctx->getCellDelay(crit_net->driver.cell, port.first, crit_net->driver.port, comb_delay);
if (!is_path)
continue;
// If input port is influenced by a clock, skip
IdString portClock;
TimingPortClass portClass = ctx->getPortTimingClass(crit_net->driver.cell, port.first, portClock);
if (portClass == TMG_REGISTER_INPUT || portClass == TMG_CLOCK_INPUT || portClass == TMG_ENDPOINT ||
portClass == TMG_IGNORE)
continue;
// Look at all input ports on its driving cell
for (const auto &port : crit_net->driver.cell->ports) {
if (port.second.type != PORT_IN || !port.second.net)
continue;
DelayInfo comb_delay;
bool is_path =
ctx->getCellDelay(crit_net->driver.cell, port.first, crit_net->driver.port, comb_delay);
if (!is_path)
continue;
// If input port is influenced by a clock, skip
int port_clocks;
TimingPortClass portClass =
ctx->getPortTimingClass(crit_net->driver.cell, port.first, port_clocks);
if (portClass == TMG_REGISTER_INPUT || portClass == TMG_CLOCK_INPUT ||
portClass == TMG_ENDPOINT || portClass == TMG_IGNORE)
continue;
// And find the fanin net with the latest arrival time
const auto net_arrival = net_data.at(port.second.net).max_arrival;
if (net_arrival > max_arrival) {
max_arrival = net_arrival;
crit_ipin = &port.second;
// And find the fanin net with the latest arrival time
if (net_data.count(port.second.net) &&
net_data.at(port.second.net).count(crit_pair.first.start)) {
const auto net_arrival = net_data.at(port.second.net).at(crit_pair.first.start).max_arrival;
if (net_arrival > max_arrival) {
max_arrival = net_arrival;
crit_ipin = &port.second;
}
}
}
}
if (!crit_ipin)
break;
// Now convert PortInfo* into a PortRef*
for (auto &usr : crit_ipin->net->users) {
if (usr.cell->name == crit_net->driver.cell->name && usr.port == crit_ipin->name) {
crit_path->push_back(&usr);
if (!crit_ipin)
break;
// Now convert PortInfo* into a PortRef*
for (auto &usr : crit_ipin->net->users) {
if (usr.cell->name == crit_net->driver.cell->name && usr.port == crit_ipin->name) {
cp_ports.push_back(&usr);
break;
}
}
crit_net = crit_ipin->net;
}
crit_net = crit_ipin->net;
std::reverse(cp_ports.begin(), cp_ports.end());
}
std::reverse(crit_path->begin(), crit_path->end());
}
return min_slack;
}
@ -366,30 +513,106 @@ void assign_budget(Context *ctx, bool quiet)
log_info("Checksum: 0x%08x\n", ctx->checksum());
}
void timing_analysis(Context *ctx, bool print_histogram, bool print_path)
void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool print_path)
{
PortRefVector crit_path;
auto format_event = [ctx](const ClockEvent &e, int field_width = 0) {
std::string value;
if (e.clock == ctx->id("$async$"))
value = std::string("<async>");
else
value = (e.edge == FALLING_EDGE ? std::string("negedge ") : std::string("posedge ")) + e.clock.str(ctx);
if (int(value.length()) < field_width)
value.insert(value.length(), field_width - int(value.length()), ' ');
return value;
};
CriticalPathMap crit_paths;
DelayFrequency slack_histogram;
Timing timing(ctx, true /* net_delays */, false /* update */, print_path ? &crit_path : nullptr,
Timing timing(ctx, true /* net_delays */, false /* update */, (print_path || print_fmax) ? &crit_paths : nullptr,
print_histogram ? &slack_histogram : nullptr);
auto min_slack = timing.walk_paths();
timing.walk_paths();
std::map<IdString, std::pair<ClockPair, CriticalPath>> clock_reports;
std::map<IdString, double> clock_fmax;
std::vector<ClockPair> xclock_paths;
std::set<IdString> empty_clocks; // set of clocks with no interior paths
if (print_path || print_fmax) {
for (auto path : crit_paths) {
const ClockEvent &a = path.first.start;
const ClockEvent &b = path.first.end;
empty_clocks.insert(a.clock);
empty_clocks.insert(b.clock);
}
for (auto path : crit_paths) {
const ClockEvent &a = path.first.start;
const ClockEvent &b = path.first.end;
if (a.clock != b.clock || a.clock == ctx->id("$async$"))
continue;
double Fmax;
empty_clocks.erase(a.clock);
if (a.edge == b.edge)
Fmax = 1000 / ctx->getDelayNS(path.second.path_delay);
else
Fmax = 500 / ctx->getDelayNS(path.second.path_delay);
if (!clock_fmax.count(a.clock) || Fmax < clock_fmax.at(a.clock)) {
clock_reports[a.clock] = path;
clock_fmax[a.clock] = Fmax;
}
}
for (auto &path : crit_paths) {
const ClockEvent &a = path.first.start;
const ClockEvent &b = path.first.end;
if (a.clock == b.clock && a.clock != ctx->id("$async$"))
continue;
xclock_paths.push_back(path.first);
}
if (clock_reports.empty()) {
log_warning("No clocks found in design");
}
std::sort(xclock_paths.begin(), xclock_paths.end(), [ctx](const ClockPair &a, const ClockPair &b) {
if (a.start.clock.str(ctx) < b.start.clock.str(ctx))
return true;
if (a.start.clock.str(ctx) > b.start.clock.str(ctx))
return false;
if (a.start.edge < b.start.edge)
return true;
if (a.start.edge > b.start.edge)
return false;
if (a.end.clock.str(ctx) < b.end.clock.str(ctx))
return true;
if (a.end.clock.str(ctx) > b.end.clock.str(ctx))
return false;
if (a.end.edge < b.end.edge)
return true;
return false;
});
}
if (print_path) {
if (crit_path.empty()) {
log_info("Design contains no timing paths\n");
} else {
auto print_path_report = [ctx](ClockPair &clocks, PortRefVector &crit_path) {
delay_t total = 0;
log_break();
log_info("Critical path report:\n");
log_info("curr total\n");
auto &front = crit_path.front();
auto &front_port = front->cell->ports.at(front->port);
auto &front_driver = front_port.net->driver;
IdString last_port;
ctx->getPortTimingClass(front_driver.cell, front_driver.port, last_port);
int port_clocks;
auto portClass = ctx->getPortTimingClass(front_driver.cell, front_driver.port, port_clocks);
IdString last_port = front_driver.port;
if (portClass == TMG_REGISTER_OUTPUT) {
for (int i = 0; i < port_clocks; i++) {
TimingClockingInfo clockInfo = ctx->getPortClockingInfo(front_driver.cell, front_driver.port, i);
const NetInfo *clknet = get_net_or_empty(front_driver.cell, clockInfo.clock_port);
if (clknet != nullptr && clknet->name == clocks.start.clock &&
clockInfo.edge == clocks.start.edge) {
last_port = clockInfo.clock_port;
}
}
}
log_info("curr total\n");
for (auto sink : crit_path) {
auto sink_cell = sink->cell;
auto &port = sink_cell->ports.at(sink->port);
@ -397,7 +620,12 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_path)
auto &driver = net->driver;
auto driver_cell = driver.cell;
DelayInfo comb_delay;
ctx->getCellDelay(sink_cell, last_port, driver.port, comb_delay);
if (last_port == driver.port) {
// Case where we start with a STARTPOINT etc
comb_delay = ctx->getDelayFromNS(0);
} else {
ctx->getCellDelay(sink_cell, last_port, driver.port, comb_delay);
}
total += comb_delay.maxDelay();
log_info("%4.1f %4.1f Source %s.%s\n", ctx->getDelayNS(comb_delay.maxDelay()), ctx->getDelayNS(total),
driver_cell->name.c_str(ctx), driver.port.c_str(ctx));
@ -413,7 +641,8 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_path)
auto driver_wire = ctx->getNetinfoSourceWire(net);
auto sink_wire = ctx->getNetinfoSinkWire(net, *sink);
log_info(" prediction: %f ns estimate: %f ns\n",
ctx->getDelayNS(ctx->predictDelay(net, *sink)), ctx->getDelayNS(ctx->estimateDelay(driver_wire, sink_wire)));
ctx->getDelayNS(ctx->predictDelay(net, *sink)),
ctx->getDelayNS(ctx->estimateDelay(driver_wire, sink_wire)));
auto cursor = sink_wire;
delay_t delay;
while (driver_wire != cursor) {
@ -422,18 +651,75 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_path)
auto pip = it->second.pip;
NPNR_ASSERT(pip != PipId());
delay = ctx->getPipDelay(pip).maxDelay();
log_info(" %1.3f %s\n", ctx->getDelayNS(delay), ctx->getPipName(pip).c_str(ctx));
log_info(" %1.3f %s\n", ctx->getDelayNS(delay),
ctx->getPipName(pip).c_str(ctx));
cursor = ctx->getPipSrcWire(pip);
}
}
last_port = sink->port;
}
};
for (auto &clock : clock_reports) {
log_break();
std::string start =
clock.second.first.start.edge == FALLING_EDGE ? std::string("negedge") : std::string("posedge");
std::string end =
clock.second.first.end.edge == FALLING_EDGE ? std::string("negedge") : std::string("posedge");
log_info("Critical path report for clock '%s' (%s -> %s):\n", clock.first.c_str(ctx), start.c_str(),
end.c_str());
auto &crit_path = clock.second.second.ports;
print_path_report(clock.second.first, crit_path);
}
for (auto &xclock : xclock_paths) {
log_break();
std::string start = format_event(xclock.start);
std::string end = format_event(xclock.end);
log_info("Critical path report for cross-domain path '%s' -> '%s':\n", start.c_str(), end.c_str());
auto &crit_path = crit_paths.at(xclock).ports;
print_path_report(xclock, crit_path);
}
}
if (print_fmax) {
log_break();
unsigned max_width = 0;
for (auto &clock : clock_reports)
max_width = std::max<unsigned>(max_width, clock.first.str(ctx).size());
for (auto &clock : clock_reports) {
const auto &clock_name = clock.first.str(ctx);
const int width = max_width - clock_name.size();
if (ctx->nets.at(clock.first)->clkconstr) {
float target = 1000 / ctx->getDelayNS(ctx->nets.at(clock.first)->clkconstr->period.minDelay());
log_info("Max frequency for clock %*s'%s': %.02f MHz (%s at %.02f MHz)\n", width, "",
clock_name.c_str(), clock_fmax[clock.first],
(target < clock_fmax[clock.first]) ? "PASS" : "FAIL", target);
} else {
log_info("Max frequency for clock %*s'%s': %.02f MHz\n", width, "", clock_name.c_str(),
clock_fmax[clock.first]);
}
}
for (auto &eclock : empty_clocks) {
if (eclock != ctx->id("$async$"))
log_info("Clock '%s' has no interior paths\n", eclock.c_str(ctx));
}
log_break();
delay_t default_slack = delay_t((1.0e9 / ctx->getDelayNS(1)) / ctx->target_freq);
log_info("estimated Fmax = %.2f MHz\n", 1e3 / ctx->getDelayNS(default_slack - min_slack));
int start_field_width = 0, end_field_width = 0;
for (auto &xclock : xclock_paths) {
start_field_width = std::max((int)format_event(xclock.start).length(), start_field_width);
end_field_width = std::max((int)format_event(xclock.end).length(), end_field_width);
}
for (auto &xclock : xclock_paths) {
const ClockEvent &a = xclock.start;
const ClockEvent &b = xclock.end;
auto &path = crit_paths.at(xclock);
auto ev_a = format_event(a, start_field_width), ev_b = format_event(b, end_field_width);
log_info("Max delay %s -> %s: %0.02f ns\n", ev_a.c_str(), ev_b.c_str(), ctx->getDelayNS(path.path_delay));
}
log_break();
}
if (print_histogram && slack_histogram.size() > 0) {
unsigned num_bins = 20;

View File

@ -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

View File

@ -404,6 +404,10 @@ actual penalty used is a multiple of this value (i.e. a weighted version of this
Convert an `delay_t` to an actual real-world delay in nanoseconds.
### DelayInfo getDelayFromNS(float v) const
Convert a real-world delay in nanoseconds to a DelayInfo with equal min/max rising/falling values.
### uint32\_t getDelayChecksum(delay\_t v) const
Convert a `delay_t` to an integer for checksum calculations.
@ -461,11 +465,17 @@ Cell Delay Methods
Returns the delay for the specified path through a cell in the `&delay` argument. The method returns
false if there is no timing relationship from `fromPort` to `toPort`.
### TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockPort) const
### TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const
Return the _timing port class_ of a port. This can be a register or combinational input or output; clock input or
output; general startpoint or endpoint; or a port ignored for timing purposes. For register ports, clockPort is set
to the associated clock port.
output; general startpoint or endpoint; or a port ignored for timing purposes. For register ports, clockInfoCount is set
to the number of associated _clock edges_ that can be queried by getPortClockingInfo.
### TimingClockingInfo getPortClockingInfo(const CellInfo *cell, IdString port, int index) const
Return the _clocking info_ (including port name of clock, clock polarity and setup/hold/clock-to-out times) of a
port. Where ports have more than one clock edge associated with them (such as DDR outputs), `index` can be used to obtain
information for all edges. `index` must be in [0, clockInfoCount), behaviour is undefined otherwise.
Placer Methods
--------------

37
docs/constraints.md Normal file
View File

@ -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)

View File

@ -539,10 +539,10 @@ bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort
return true;
}
#if 0 // FIXME
if (fromPort == id_WCK && (toPort == id_F0 || toPort == id_F1)) {
delay.delay = 717;
return true;
}
if (fromPort == id_WCK && (toPort == id_F0 || toPort == id_F1)) {
delay.delay = 717;
return true;
}
#endif
if ((fromPort == id_A0 && toPort == id_WADO3) || (fromPort == id_A1 && toPort == id_WDO1) ||
(fromPort == id_B0 && toPort == id_WADO1) || (fromPort == id_B1 && toPort == id_WDO3) ||
@ -576,10 +576,10 @@ bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort
}
}
TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockPort) const
TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const
{
auto disconnected = [cell](IdString p) { return !cell->ports.count(p) || cell->ports.at(p).net == nullptr; };
clockInfoCount = 0;
if (cell->type == id_TRELLIS_SLICE) {
int sd0 = int_or_default(cell->params, id("REG0_SD"), 0), sd1 = int_or_default(cell->params, id("REG1_SD"), 0);
if (port == id_CLK || port == id_WCK)
@ -598,13 +598,13 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id
return TMG_COMB_OUTPUT;
if (port == id_DI0 || port == id_DI1 || port == id_CE || port == id_LSR || (sd0 == 1 && port == id_M0) ||
(sd1 == 1 && port == id_M1)) {
clockPort = id_CLK;
clockInfoCount = 1;
return TMG_REGISTER_INPUT;
}
if (port == id_M0 || port == id_M1)
return TMG_COMB_INPUT;
if (port == id_Q0 || port == id_Q1) {
clockPort = id_CLK;
clockInfoCount = 1;
return TMG_REGISTER_OUTPUT;
}
@ -614,7 +614,7 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id
if (port == id_WD0 || port == id_WD1 || port == id_WAD0 || port == id_WAD1 || port == id_WAD2 ||
port == id_WAD3 || port == id_WRE) {
clockPort = id_WCK;
clockInfoCount = 1;
return TMG_REGISTER_INPUT;
}
@ -638,10 +638,8 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id
for (auto c : boost::adaptors::reverse(port_name)) {
if (std::isdigit(c))
continue;
if (c == 'A')
clockPort = id_CLKA;
else if (c == 'B')
clockPort = id_CLKB;
if (c == 'A' || c == 'B')
clockInfoCount = 1;
else
NPNR_ASSERT_FALSE_STR("bad ram port");
return (cell->ports.at(port).type == PORT_OUT) ? TMG_REGISTER_OUTPUT : TMG_REGISTER_INPUT;
@ -653,11 +651,92 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id
return TMG_IGNORE; // FIXME
} else if (cell->type == id_EHXPLLL) {
return TMG_IGNORE;
} else if (cell->type == id_DCUA || cell->type == id_EXTREFB || cell->type == id_PCSCLKDIV) {
if (port == id_CH0_FF_TXI_CLK || port == id_CH0_FF_RXI_CLK || port == id_CH1_FF_TXI_CLK ||
port == id_CH1_FF_RXI_CLK)
return TMG_CLOCK_INPUT;
std::string prefix = port.str(this).substr(0, 9);
if (prefix == "CH0_FF_TX" || prefix == "CH0_FF_RX" || prefix == "CH1_FF_TX" || prefix == "CH1_FF_RX") {
clockInfoCount = 1;
return (cell->ports.at(port).type == PORT_OUT) ? TMG_REGISTER_OUTPUT : TMG_REGISTER_INPUT;
}
return TMG_IGNORE;
} else {
NPNR_ASSERT_FALSE_STR("no timing data for cell type '" + cell->type.str(this) + "'");
}
}
TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port, int index) const
{
TimingClockingInfo info;
info.setup.delay = 0;
info.hold.delay = 0;
info.clockToQ.delay = 0;
if (cell->type == id_TRELLIS_SLICE) {
int sd0 = int_or_default(cell->params, id("REG0_SD"), 0), sd1 = int_or_default(cell->params, id("REG1_SD"), 0);
if (port == id_WD0 || port == id_WD1 || port == id_WAD0 || port == id_WAD1 || port == id_WAD2 ||
port == id_WAD3 || port == id_WRE) {
info.edge = RISING_EDGE;
info.clock_port = id_WCK;
info.setup.delay = 100;
info.hold.delay = 0;
} else if (port == id_DI0 || port == id_DI1 || port == id_CE || port == id_LSR || (sd0 == 1 && port == id_M0) ||
(sd1 == 1 && port == id_M1)) {
info.edge = cell->sliceInfo.clkmux == id("INV") ? FALLING_EDGE : RISING_EDGE;
info.clock_port = id_CLK;
info.setup.delay = 100;
info.hold.delay = 0;
} else {
info.edge = cell->sliceInfo.clkmux == id("INV") ? FALLING_EDGE : RISING_EDGE;
info.clock_port = id_CLK;
info.clockToQ.delay = 395;
}
} else if (cell->type == id_DP16KD) {
std::string port_name = port.str(this);
for (auto c : boost::adaptors::reverse(port_name)) {
if (std::isdigit(c))
continue;
if (c == 'A') {
info.clock_port = id_CLKA;
break;
} else if (c == 'B') {
info.clock_port = id_CLKB;
break;
} else
NPNR_ASSERT_FALSE_STR("bad ram port " + port.str(this));
}
info.edge = (str_or_default(cell->params, info.clock_port == id_CLKB ? id("CLKBMUX") : id("CLKAMUX"), "CLK") ==
"INV")
? FALLING_EDGE
: RISING_EDGE;
if (cell->ports.at(port).type == PORT_OUT) {
info.clockToQ.delay = 4280;
} else {
info.setup.delay = 100;
info.hold.delay = 0;
}
} else if (cell->type == id_DCUA) {
std::string prefix = port.str(this).substr(0, 9);
info.edge = RISING_EDGE;
if (prefix == "CH0_FF_TX")
info.clock_port = id_CH0_FF_TXI_CLK;
else if (prefix == "CH0_FF_RX")
info.clock_port = id_CH0_FF_RXI_CLK;
else if (prefix == "CH1_FF_TX")
info.clock_port = id_CH1_FF_TXI_CLK;
else if (prefix == "CH1_FF_RX")
info.clock_port = id_CH1_FF_RXI_CLK;
if (cell->ports.at(port).type == PORT_OUT) {
info.clockToQ.delay = 660;
} else {
info.setup.delay = 1000;
info.hold.delay = 0;
}
}
return info;
}
std::vector<std::pair<std::string, std::string>> Arch::getTilesAtLocation(int row, int col)
{
std::vector<std::pair<std::string, std::string>> ret;

View File

@ -859,6 +859,12 @@ struct Arch : BaseCtx
delay_t getDelayEpsilon() const { return 20; }
delay_t getRipupDelayPenalty() const { return 200; }
float getDelayNS(delay_t v) const { return v * 0.001; }
DelayInfo getDelayFromNS(float ns) const
{
DelayInfo del;
del.delay = delay_t(ns * 1000);
return del;
}
uint32_t getDelayChecksum(delay_t v) const { return v; }
bool getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const;
@ -882,8 +888,10 @@ struct Arch : BaseCtx
// Get the delay through a cell from one port to another, returning false
// if no path exists
bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const;
// Get the port class, also setting clockPort if applicable
TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockPort) const;
// Get the port class, also setting clockInfoCount to the number of TimingClockingInfos associated with a port
TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const;
// Get the TimingClockingInfo of a port
TimingClockingInfo getPortClockingInfo(const CellInfo *cell, IdString port, int index) const;
// Return true if a port is a net
bool isGlobalNet(const NetInfo *net) const;

View File

@ -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;

View File

@ -130,6 +130,10 @@ void arch_wrap_python()
"cells");
readonly_wrapper<Context, decltype(&Context::nets), &Context::nets, wrap_context<NetMap &>>::def_wrap(ctx_cls,
"nets");
fn_wrapper_2a_v<Context, decltype(&Context::addClock), &Context::addClock, conv_from_str<IdString>,
pass_through<float>>::def_wrap(ctx_cls, "addClock");
WRAP_RANGE(Bel, conv_to_str<BelId>);
WRAP_RANGE(Wire, conv_to_str<WireId>);
WRAP_RANGE(AllPip, conv_to_str<PipId>);

View File

@ -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

View File

@ -19,15 +19,15 @@
#include "bitstream.h"
#include <boost/algorithm/string/predicate.hpp>
#include <fstream>
#include <iomanip>
#include <queue>
#include <regex>
#include <streambuf>
#include "config.h"
#include "io.h"
#include "log.h"
#include "pio.h"
#include "util.h"
#define fmt_str(x) (static_cast<const std::ostringstream &>(std::ostringstream() << x).str())
@ -379,6 +379,16 @@ std::vector<std::string> get_dsp_tiles(Context *ctx, BelId bel)
return tiles;
}
// Get the list of tiles corresponding to a DCU
std::vector<std::string> get_dcu_tiles(Context *ctx, BelId bel)
{
std::vector<std::string> 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<std::string> get_pll_tiles(Context *ctx, BelId bel)
{
@ -408,26 +418,23 @@ std::vector<std::string> 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<std::string, std::string> tiletype_xform;
for (const auto &tile : cc.tiles) {
std::string newname = tile.first;
auto vcib = tile.first.find("VCIB");
if (vcib != std::string::npos) {
// Remove the V
newname.erase(vcib, 1);
auto cibdcu = tile.first.find("CIB_DCU");
if (cibdcu != std::string::npos) {
// Add the V
if (newname.at(cibdcu - 1) != 'V')
newname.insert(cibdcu, 1, 'V');
tiletype_xform[tile.first] = newname;
} else if (boost::ends_with(tile.first, "BMID_0H")) {
newname.back() = 'V';
tiletype_xform[tile.first] = newname;
} else if (boost::ends_with(tile.first, "BMID_2")) {
newname.push_back('V');
tiletype_xform[tile.first] = newname;
} else if (tile.first.back() == 'V') {
// BMID_0V or BMID_2V
if (tile.first.at(tile.first.size() - 2) == '0') {
newname.at(tile.first.size() - 1) = 'H';
tiletype_xform[tile.first] = newname;
} else if (tile.first.at(tile.first.size() - 2) == '2') {
newname.pop_back();
tiletype_xform[tile.first] = newname;
}
}
}
// Apply the name changes
@ -456,6 +463,20 @@ void tieoff_dsp_ports(Context *ctx, ChipConfig &cc, CellInfo *ci)
}
}
void tieoff_dcu_ports(Context *ctx, ChipConfig &cc, CellInfo *ci)
{
for (auto port : ci->ports) {
if (port.second.net == nullptr && port.second.type == PORT_IN) {
if (port.first.str(ctx).find("CLK") != std::string::npos ||
port.first.str(ctx).find("HDIN") != std::string::npos ||
port.first.str(ctx).find("HDOUT") != std::string::npos)
continue;
bool value = bool_or_default(ci->params, ctx->id(port.first.str(ctx) + "MUX"), false);
tie_cib_signal(ctx, cc, ctx->getBelPinWire(ci->bel, port.first), value);
}
}
}
static void set_pip(Context *ctx, ChipConfig &cc, PipId pip)
{
std::string tile = ctx->getPipTilename(pip);
@ -464,6 +485,46 @@ static void set_pip(Context *ctx, ChipConfig &cc, PipId pip)
cc.tiles[tile].add_arc(sink, source);
}
static std::vector<bool> 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<bool> word;
word.resize(length, false);
if (base == "0b") {
for (int i = 0; i < int(str.length()) - 2; i++) {
char c = str.at((str.size() - 1) - i);
NPNR_ASSERT(c == '0' || c == '1');
word.at(i) = (c == '1');
}
} else if (base == "0x") {
for (int i = 0; i < int(str.length()) - 2; i++) {
char c = str.at((str.size() - i) - 1);
int nibble = chtohex(c);
word.at(i * 4) = nibble & 0x1;
if (i * 4 + 1 < length)
word.at(i * 4 + 1) = nibble & 0x2;
if (i * 4 + 2 < length)
word.at(i * 4 + 2) = nibble & 0x4;
if (i * 4 + 3 < length)
word.at(i * 4 + 3) = nibble & 0x8;
}
} else if (base == "0d") {
NPNR_ASSERT(length < 64);
unsigned long long value = std::stoull(str.substr(2));
for (int i = 0; i < length; i++)
if (value & (1 << i))
word.at(i) = true;
} else {
NPNR_ASSERT(length < 64);
unsigned long long value = std::stoull(str);
for (int i = 0; i < length; i++)
if (value & (1 << i))
word.at(i) = true;
}
return word;
}
void write_bitstream(Context *ctx, std::string base_config_file, std::string text_config_file)
{
ChipConfig cc;
@ -481,6 +542,23 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex
// TODO: .bit metadata
}
// Clear out DCU tieoffs in base config if DCU used
for (auto &cell : ctx->cells) {
CellInfo *ci = cell.second.get();
if (ci->type == id_DCUA) {
Loc loc = ctx->getBelLocation(ci->bel);
for (int i = 0; i < 12; i++) {
auto tiles = ctx->getTilesAtLocation(loc.y - 1, loc.x + i);
for (const auto &tile : tiles) {
auto cc_tile = cc.tiles.find(tile.first);
if (cc_tile != cc.tiles.end()) {
cc_tile->second.cenums.clear();
cc_tile->second.cunknowns.clear();
}
}
}
}
}
// Add all set, configurable pips to the config
for (auto pip : ctx->getPips()) {
if (ctx->getBoundPipNet(pip) != nullptr) {
@ -1000,6 +1078,28 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex
int_to_bitvector(int_or_default(ci->attrs, ctx->id("MFG_ENABLE_FILTEROPAMP"), 0), 1));
cc.tilegroups.push_back(tg);
} else if (ci->type == id_DCUA) {
TileGroup tg;
tg.tiles = get_dcu_tiles(ctx, ci->bel);
tg.config.add_enum("DCU.MODE", "DCUA");
#include "dcu_bitstream.h"
cc.tilegroups.push_back(tg);
tieoff_dcu_ports(ctx, cc, ci);
} else if (ci->type == id_EXTREFB) {
TileGroup tg;
tg.tiles = get_dcu_tiles(ctx, ci->bel);
tg.config.add_word("EXTREF.REFCK_DCBIAS_EN",
parse_config_str(str_or_default(ci->params, ctx->id("REFCK_DCBIAS_EN"), "0"), 1));
tg.config.add_word("EXTREF.REFCK_RTERM",
parse_config_str(str_or_default(ci->params, ctx->id("REFCK_RTERM"), "0"), 1));
tg.config.add_word("EXTREF.REFCK_PWDNB",
parse_config_str(str_or_default(ci->params, ctx->id("REFCK_PWDNB"), "0"), 1));
cc.tilegroups.push_back(tg);
} else if (ci->type == id_PCSCLKDIV) {
Loc loc = ctx->getBelLocation(ci->bel);
std::string tname = ctx->getTileByTypeAndLocation(loc.y + 1, loc.x, "BMID_0H");
cc.tiles[tname].add_enum("PCSCLKDIV" + std::to_string(loc.z),
str_or_default(ci->params, ctx->id("GSR"), "ENABLED"));
} else {
NPNR_ASSERT_FALSE("unsupported cell type");
}

View File

@ -810,3 +810,310 @@ X(LOCK)
X(INTLOCK)
X(REFCLK)
X(CLKINTFB)
X(EXTREFB)
X(REFCLKP)
X(REFCLKN)
X(REFCLKO)
X(DCUA)
X(CH0_HDINP)
X(CH1_HDINP)
X(CH0_HDINN)
X(CH1_HDINN)
X(D_TXBIT_CLKP_FROM_ND)
X(D_TXBIT_CLKN_FROM_ND)
X(D_SYNC_ND)
X(D_TXPLL_LOL_FROM_ND)
X(CH0_RX_REFCLK)
X(CH1_RX_REFCLK)
X(CH0_FF_RXI_CLK)
X(CH1_FF_RXI_CLK)
X(CH0_FF_TXI_CLK)
X(CH1_FF_TXI_CLK)
X(CH0_FF_EBRD_CLK)
X(CH1_FF_EBRD_CLK)
X(CH0_FF_TX_D_0)
X(CH1_FF_TX_D_0)
X(CH0_FF_TX_D_1)
X(CH1_FF_TX_D_1)
X(CH0_FF_TX_D_2)
X(CH1_FF_TX_D_2)
X(CH0_FF_TX_D_3)
X(CH1_FF_TX_D_3)
X(CH0_FF_TX_D_4)
X(CH1_FF_TX_D_4)
X(CH0_FF_TX_D_5)
X(CH1_FF_TX_D_5)
X(CH0_FF_TX_D_6)
X(CH1_FF_TX_D_6)
X(CH0_FF_TX_D_7)
X(CH1_FF_TX_D_7)
X(CH0_FF_TX_D_8)
X(CH1_FF_TX_D_8)
X(CH0_FF_TX_D_9)
X(CH1_FF_TX_D_9)
X(CH0_FF_TX_D_10)
X(CH1_FF_TX_D_10)
X(CH0_FF_TX_D_11)
X(CH1_FF_TX_D_11)
X(CH0_FF_TX_D_12)
X(CH1_FF_TX_D_12)
X(CH0_FF_TX_D_13)
X(CH1_FF_TX_D_13)
X(CH0_FF_TX_D_14)
X(CH1_FF_TX_D_14)
X(CH0_FF_TX_D_15)
X(CH1_FF_TX_D_15)
X(CH0_FF_TX_D_16)
X(CH1_FF_TX_D_16)
X(CH0_FF_TX_D_17)
X(CH1_FF_TX_D_17)
X(CH0_FF_TX_D_18)
X(CH1_FF_TX_D_18)
X(CH0_FF_TX_D_19)
X(CH1_FF_TX_D_19)
X(CH0_FF_TX_D_20)
X(CH1_FF_TX_D_20)
X(CH0_FF_TX_D_21)
X(CH1_FF_TX_D_21)
X(CH0_FF_TX_D_22)
X(CH1_FF_TX_D_22)
X(CH0_FF_TX_D_23)
X(CH1_FF_TX_D_23)
X(CH0_FFC_EI_EN)
X(CH1_FFC_EI_EN)
X(CH0_FFC_PCIE_DET_EN)
X(CH1_FFC_PCIE_DET_EN)
X(CH0_FFC_PCIE_CT)
X(CH1_FFC_PCIE_CT)
X(CH0_FFC_SB_INV_RX)
X(CH1_FFC_SB_INV_RX)
X(CH0_FFC_ENABLE_CGALIGN)
X(CH1_FFC_ENABLE_CGALIGN)
X(CH0_FFC_SIGNAL_DETECT)
X(CH1_FFC_SIGNAL_DETECT)
X(CH0_FFC_FB_LOOPBACK)
X(CH1_FFC_FB_LOOPBACK)
X(CH0_FFC_SB_PFIFO_LP)
X(CH1_FFC_SB_PFIFO_LP)
X(CH0_FFC_PFIFO_CLR)
X(CH1_FFC_PFIFO_CLR)
X(CH0_FFC_RATE_MODE_RX)
X(CH1_FFC_RATE_MODE_RX)
X(CH0_FFC_RATE_MODE_TX)
X(CH1_FFC_RATE_MODE_TX)
X(CH0_FFC_DIV11_MODE_RX)
X(CH1_FFC_DIV11_MODE_RX)
X(CH0_FFC_RX_GEAR_MODE)
X(CH1_FFC_RX_GEAR_MODE)
X(CH0_FFC_TX_GEAR_MODE)
X(CH1_FFC_TX_GEAR_MODE)
X(CH0_FFC_DIV11_MODE_TX)
X(CH1_FFC_DIV11_MODE_TX)
X(CH0_FFC_LDR_CORE2TX_EN)
X(CH1_FFC_LDR_CORE2TX_EN)
X(CH0_FFC_LANE_TX_RST)
X(CH1_FFC_LANE_TX_RST)
X(CH0_FFC_LANE_RX_RST)
X(CH1_FFC_LANE_RX_RST)
X(CH0_FFC_RRST)
X(CH1_FFC_RRST)
X(CH0_FFC_TXPWDNB)
X(CH1_FFC_TXPWDNB)
X(CH0_FFC_RXPWDNB)
X(CH1_FFC_RXPWDNB)
X(CH0_LDR_CORE2TX)
X(CH1_LDR_CORE2TX)
X(D_SCIWDATA0)
X(D_SCIWDATA1)
X(D_SCIWDATA2)
X(D_SCIWDATA3)
X(D_SCIWDATA4)
X(D_SCIWDATA5)
X(D_SCIWDATA6)
X(D_SCIWDATA7)
X(D_SCIADDR0)
X(D_SCIADDR1)
X(D_SCIADDR2)
X(D_SCIADDR3)
X(D_SCIADDR4)
X(D_SCIADDR5)
X(D_SCIENAUX)
X(D_SCISELAUX)
X(CH0_SCIEN)
X(CH1_SCIEN)
X(CH0_SCISEL)
X(CH1_SCISEL)
X(D_SCIRD)
X(D_SCIWSTN)
X(D_CYAWSTN)
X(D_FFC_SYNC_TOGGLE)
X(D_FFC_DUAL_RST)
X(D_FFC_MACRO_RST)
X(D_FFC_MACROPDB)
X(D_FFC_TRST)
X(CH0_FFC_CDR_EN_BITSLIP)
X(CH1_FFC_CDR_EN_BITSLIP)
X(D_SCAN_ENABLE)
X(D_SCAN_IN_0)
X(D_SCAN_IN_1)
X(D_SCAN_IN_2)
X(D_SCAN_IN_3)
X(D_SCAN_IN_4)
X(D_SCAN_IN_5)
X(D_SCAN_IN_6)
X(D_SCAN_IN_7)
X(D_SCAN_MODE)
X(D_SCAN_RESET)
X(D_CIN0)
X(D_CIN1)
X(D_CIN2)
X(D_CIN3)
X(D_CIN4)
X(D_CIN5)
X(D_CIN6)
X(D_CIN7)
X(D_CIN8)
X(D_CIN9)
X(D_CIN10)
X(D_CIN11)
X(CH0_HDOUTP)
X(CH1_HDOUTP)
X(CH0_HDOUTN)
X(CH1_HDOUTN)
X(D_TXBIT_CLKP_TO_ND)
X(D_TXBIT_CLKN_TO_ND)
X(D_SYNC_PULSE2ND)
X(D_TXPLL_LOL_TO_ND)
X(CH0_FF_RX_F_CLK)
X(CH1_FF_RX_F_CLK)
X(CH0_FF_RX_H_CLK)
X(CH1_FF_RX_H_CLK)
X(CH0_FF_TX_F_CLK)
X(CH1_FF_TX_F_CLK)
X(CH0_FF_TX_H_CLK)
X(CH1_FF_TX_H_CLK)
X(CH0_FF_RX_PCLK)
X(CH1_FF_RX_PCLK)
X(CH0_FF_TX_PCLK)
X(CH1_FF_TX_PCLK)
X(CH0_FF_RX_D_0)
X(CH1_FF_RX_D_0)
X(CH0_FF_RX_D_1)
X(CH1_FF_RX_D_1)
X(CH0_FF_RX_D_2)
X(CH1_FF_RX_D_2)
X(CH0_FF_RX_D_3)
X(CH1_FF_RX_D_3)
X(CH0_FF_RX_D_4)
X(CH1_FF_RX_D_4)
X(CH0_FF_RX_D_5)
X(CH1_FF_RX_D_5)
X(CH0_FF_RX_D_6)
X(CH1_FF_RX_D_6)
X(CH0_FF_RX_D_7)
X(CH1_FF_RX_D_7)
X(CH0_FF_RX_D_8)
X(CH1_FF_RX_D_8)
X(CH0_FF_RX_D_9)
X(CH1_FF_RX_D_9)
X(CH0_FF_RX_D_10)
X(CH1_FF_RX_D_10)
X(CH0_FF_RX_D_11)
X(CH1_FF_RX_D_11)
X(CH0_FF_RX_D_12)
X(CH1_FF_RX_D_12)
X(CH0_FF_RX_D_13)
X(CH1_FF_RX_D_13)
X(CH0_FF_RX_D_14)
X(CH1_FF_RX_D_14)
X(CH0_FF_RX_D_15)
X(CH1_FF_RX_D_15)
X(CH0_FF_RX_D_16)
X(CH1_FF_RX_D_16)
X(CH0_FF_RX_D_17)
X(CH1_FF_RX_D_17)
X(CH0_FF_RX_D_18)
X(CH1_FF_RX_D_18)
X(CH0_FF_RX_D_19)
X(CH1_FF_RX_D_19)
X(CH0_FF_RX_D_20)
X(CH1_FF_RX_D_20)
X(CH0_FF_RX_D_21)
X(CH1_FF_RX_D_21)
X(CH0_FF_RX_D_22)
X(CH1_FF_RX_D_22)
X(CH0_FF_RX_D_23)
X(CH1_FF_RX_D_23)
X(CH0_FFS_PCIE_DONE)
X(CH1_FFS_PCIE_DONE)
X(CH0_FFS_PCIE_CON)
X(CH1_FFS_PCIE_CON)
X(CH0_FFS_RLOS)
X(CH1_FFS_RLOS)
X(CH0_FFS_LS_SYNC_STATUS)
X(CH1_FFS_LS_SYNC_STATUS)
X(CH0_FFS_CC_UNDERRUN)
X(CH1_FFS_CC_UNDERRUN)
X(CH0_FFS_CC_OVERRUN)
X(CH1_FFS_CC_OVERRUN)
X(CH0_FFS_RXFBFIFO_ERROR)
X(CH1_FFS_RXFBFIFO_ERROR)
X(CH0_FFS_TXFBFIFO_ERROR)
X(CH1_FFS_TXFBFIFO_ERROR)
X(CH0_FFS_RLOL)
X(CH1_FFS_RLOL)
X(CH0_FFS_SKP_ADDED)
X(CH1_FFS_SKP_ADDED)
X(CH0_FFS_SKP_DELETED)
X(CH1_FFS_SKP_DELETED)
X(CH0_LDR_RX2CORE)
X(CH1_LDR_RX2CORE)
X(D_SCIRDATA0)
X(D_SCIRDATA1)
X(D_SCIRDATA2)
X(D_SCIRDATA3)
X(D_SCIRDATA4)
X(D_SCIRDATA5)
X(D_SCIRDATA6)
X(D_SCIRDATA7)
X(D_SCIINT)
X(D_SCAN_OUT_0)
X(D_SCAN_OUT_1)
X(D_SCAN_OUT_2)
X(D_SCAN_OUT_3)
X(D_SCAN_OUT_4)
X(D_SCAN_OUT_5)
X(D_SCAN_OUT_6)
X(D_SCAN_OUT_7)
X(D_COUT0)
X(D_COUT1)
X(D_COUT2)
X(D_COUT3)
X(D_COUT4)
X(D_COUT5)
X(D_COUT6)
X(D_COUT7)
X(D_COUT8)
X(D_COUT9)
X(D_COUT10)
X(D_COUT11)
X(D_COUT12)
X(D_COUT13)
X(D_COUT14)
X(D_COUT15)
X(D_COUT16)
X(D_COUT17)
X(D_COUT18)
X(D_COUT19)
X(D_REFCLKI)
X(D_FFS_PLOL)
X(PCSCLKDIV)
X(SEL2)
X(SEL1)
X(SEL0)
X(CDIV1)
X(CDIVX)

424
ecp5/dcu_bitstream.h Normal file
View File

@ -0,0 +1,424 @@
tg.config.add_word("DCU.CH0_AUTO_CALIB_EN",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_AUTO_CALIB_EN"), "0"), 1));
tg.config.add_word("DCU.CH0_AUTO_FACQ_EN",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_AUTO_FACQ_EN"), "0"), 1));
tg.config.add_word("DCU.CH0_BAND_THRESHOLD",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_BAND_THRESHOLD"), "0"), 6));
tg.config.add_word("DCU.CH0_CALIB_CK_MODE",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_CALIB_CK_MODE"), "0"), 1));
tg.config.add_word("DCU.CH0_CC_MATCH_1",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_CC_MATCH_1"), "0"), 10));
tg.config.add_word("DCU.CH0_CC_MATCH_2",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_CC_MATCH_2"), "0"), 10));
tg.config.add_word("DCU.CH0_CC_MATCH_3",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_CC_MATCH_3"), "0"), 10));
tg.config.add_word("DCU.CH0_CC_MATCH_4",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_CC_MATCH_4"), "0"), 10));
tg.config.add_word("DCU.CH0_CDR_CNT4SEL",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_CDR_CNT4SEL"), "0"), 2));
tg.config.add_word("DCU.CH0_CDR_CNT8SEL",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_CDR_CNT8SEL"), "0"), 2));
tg.config.add_word("DCU.CH0_CTC_BYPASS",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_CTC_BYPASS"), "0"), 1));
tg.config.add_word("DCU.CH0_DCOATDCFG", parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOATDCFG"), "0"), 2));
tg.config.add_word("DCU.CH0_DCOATDDLY", parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOATDDLY"), "0"), 2));
tg.config.add_word("DCU.CH0_DCOBYPSATD",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOBYPSATD"), "0"), 1));
tg.config.add_word("DCU.CH0_DCOCALDIV", parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOCALDIV"), "0"), 3));
tg.config.add_word("DCU.CH0_DCOCTLGI", parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOCTLGI"), "0"), 3));
tg.config.add_word("DCU.CH0_DCODISBDAVOID",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCODISBDAVOID"), "0"), 1));
tg.config.add_word("DCU.CH0_DCOFLTDAC", parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOFLTDAC"), "0"), 2));
tg.config.add_word("DCU.CH0_DCOFTNRG", parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOFTNRG"), "0"), 3));
tg.config.add_word("DCU.CH0_DCOIOSTUNE",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOIOSTUNE"), "0"), 3));
tg.config.add_word("DCU.CH0_DCOITUNE", parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOITUNE"), "0"), 2));
tg.config.add_word("DCU.CH0_DCOITUNE4LSB",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOITUNE4LSB"), "0"), 3));
tg.config.add_word("DCU.CH0_DCOIUPDNX2",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOIUPDNX2"), "0"), 1));
tg.config.add_word("DCU.CH0_DCONUOFLSB",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCONUOFLSB"), "0"), 3));
tg.config.add_word("DCU.CH0_DCOSCALEI", parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOSCALEI"), "0"), 2));
tg.config.add_word("DCU.CH0_DCOSTARTVAL",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOSTARTVAL"), "0"), 3));
tg.config.add_word("DCU.CH0_DCOSTEP", parse_config_str(str_or_default(ci->params, ctx->id("CH0_DCOSTEP"), "0"), 2));
tg.config.add_word("DCU.CH0_DEC_BYPASS",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_DEC_BYPASS"), "0"), 1));
tg.config.add_word("DCU.CH0_ENABLE_CG_ALIGN",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_ENABLE_CG_ALIGN"), "0"), 1));
tg.config.add_word("DCU.CH0_ENC_BYPASS",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_ENC_BYPASS"), "0"), 1));
tg.config.add_word("DCU.CH0_FF_RX_F_CLK_DIS",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_FF_RX_F_CLK_DIS"), "0"), 1));
tg.config.add_word("DCU.CH0_FF_RX_H_CLK_EN",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_FF_RX_H_CLK_EN"), "0"), 1));
tg.config.add_word("DCU.CH0_FF_TX_F_CLK_DIS",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_FF_TX_F_CLK_DIS"), "0"), 1));
tg.config.add_word("DCU.CH0_FF_TX_H_CLK_EN",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_FF_TX_H_CLK_EN"), "0"), 1));
tg.config.add_word("DCU.CH0_GE_AN_ENABLE",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_GE_AN_ENABLE"), "0"), 1));
tg.config.add_word("DCU.CH0_INVERT_RX", parse_config_str(str_or_default(ci->params, ctx->id("CH0_INVERT_RX"), "0"), 1));
tg.config.add_word("DCU.CH0_INVERT_TX", parse_config_str(str_or_default(ci->params, ctx->id("CH0_INVERT_TX"), "0"), 1));
tg.config.add_word("DCU.CH0_LDR_CORE2TX_SEL",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_LDR_CORE2TX_SEL"), "0"), 1));
tg.config.add_word("DCU.CH0_LDR_RX2CORE_SEL",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_LDR_RX2CORE_SEL"), "0"), 1));
tg.config.add_word("DCU.CH0_LEQ_OFFSET_SEL",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_LEQ_OFFSET_SEL"), "0"), 1));
tg.config.add_word("DCU.CH0_LEQ_OFFSET_TRIM",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_LEQ_OFFSET_TRIM"), "0"), 3));
tg.config.add_word("DCU.CH0_LSM_DISABLE",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_LSM_DISABLE"), "0"), 1));
tg.config.add_word("DCU.CH0_MATCH_2_ENABLE",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_MATCH_2_ENABLE"), "0"), 1));
tg.config.add_word("DCU.CH0_MATCH_4_ENABLE",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_MATCH_4_ENABLE"), "0"), 1));
tg.config.add_word("DCU.CH0_MIN_IPG_CNT",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_MIN_IPG_CNT"), "0"), 2));
tg.config.add_word("DCU.CH0_PCIE_EI_EN",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_PCIE_EI_EN"), "0"), 1));
tg.config.add_word("DCU.CH0_PCIE_MODE", parse_config_str(str_or_default(ci->params, ctx->id("CH0_PCIE_MODE"), "0"), 1));
tg.config.add_word("DCU.CH0_PCS_DET_TIME_SEL",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_PCS_DET_TIME_SEL"), "0"), 2));
tg.config.add_word("DCU.CH0_PDEN_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH0_PDEN_SEL"), "0"), 1));
tg.config.add_word("DCU.CH0_PRBS_ENABLE",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_PRBS_ENABLE"), "0"), 1));
tg.config.add_word("DCU.CH0_PRBS_LOCK", parse_config_str(str_or_default(ci->params, ctx->id("CH0_PRBS_LOCK"), "0"), 1));
tg.config.add_word("DCU.CH0_PRBS_SELECTION",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_PRBS_SELECTION"), "0"), 1));
tg.config.add_word("DCU.CH0_RATE_MODE_RX",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_RATE_MODE_RX"), "0"), 1));
tg.config.add_word("DCU.CH0_RATE_MODE_TX",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_RATE_MODE_TX"), "0"), 1));
tg.config.add_word("DCU.CH0_RCV_DCC_EN",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_RCV_DCC_EN"), "0"), 1));
tg.config.add_word("DCU.CH0_REG_BAND_OFFSET",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_REG_BAND_OFFSET"), "0"), 4));
tg.config.add_word("DCU.CH0_REG_BAND_SEL",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_REG_BAND_SEL"), "0"), 6));
tg.config.add_word("DCU.CH0_REG_IDAC_EN",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_REG_IDAC_EN"), "0"), 1));
tg.config.add_word("DCU.CH0_REG_IDAC_SEL",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_REG_IDAC_SEL"), "0"), 10));
tg.config.add_word("DCU.CH0_REQ_EN", parse_config_str(str_or_default(ci->params, ctx->id("CH0_REQ_EN"), "0"), 1));
tg.config.add_word("DCU.CH0_REQ_LVL_SET",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_REQ_LVL_SET"), "0"), 2));
tg.config.add_word("DCU.CH0_RIO_MODE", parse_config_str(str_or_default(ci->params, ctx->id("CH0_RIO_MODE"), "0"), 1));
tg.config.add_word("DCU.CH0_RLOS_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH0_RLOS_SEL"), "0"), 1));
tg.config.add_word("DCU.CH0_RPWDNB", parse_config_str(str_or_default(ci->params, ctx->id("CH0_RPWDNB"), "0"), 1));
tg.config.add_word("DCU.CH0_RTERM_RX", parse_config_str(str_or_default(ci->params, ctx->id("CH0_RTERM_RX"), "0"), 5));
tg.config.add_word("DCU.CH0_RTERM_TX", parse_config_str(str_or_default(ci->params, ctx->id("CH0_RTERM_TX"), "0"), 5));
tg.config.add_word("DCU.CH0_RXIN_CM", parse_config_str(str_or_default(ci->params, ctx->id("CH0_RXIN_CM"), "0"), 2));
tg.config.add_word("DCU.CH0_RXTERM_CM", parse_config_str(str_or_default(ci->params, ctx->id("CH0_RXTERM_CM"), "0"), 2));
tg.config.add_word("DCU.CH0_RX_DCO_CK_DIV",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_RX_DCO_CK_DIV"), "0"), 3));
tg.config.add_word("DCU.CH0_RX_DIV11_SEL",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_RX_DIV11_SEL"), "0"), 1));
tg.config.add_word("DCU.CH0_RX_GEAR_BYPASS",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_RX_GEAR_BYPASS"), "0"), 1));
tg.config.add_word("DCU.CH0_RX_GEAR_MODE",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_RX_GEAR_MODE"), "0"), 1));
tg.config.add_word("DCU.CH0_RX_LOS_CEQ",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_RX_LOS_CEQ"), "0"), 2));
tg.config.add_word("DCU.CH0_RX_LOS_EN", parse_config_str(str_or_default(ci->params, ctx->id("CH0_RX_LOS_EN"), "0"), 1));
tg.config.add_word("DCU.CH0_RX_LOS_HYST_EN",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_RX_LOS_HYST_EN"), "0"), 1));
tg.config.add_word("DCU.CH0_RX_LOS_LVL",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_RX_LOS_LVL"), "0"), 3));
tg.config.add_word("DCU.CH0_RX_RATE_SEL",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_RX_RATE_SEL"), "0"), 4));
tg.config.add_word("DCU.CH0_RX_SB_BYPASS",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_RX_SB_BYPASS"), "0"), 1));
tg.config.add_word("DCU.CH0_SB_BYPASS", parse_config_str(str_or_default(ci->params, ctx->id("CH0_SB_BYPASS"), "0"), 1));
tg.config.add_word("DCU.CH0_SEL_SD_RX_CLK",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_SEL_SD_RX_CLK"), "0"), 1));
tg.config.add_word("DCU.CH0_TDRV_DAT_SEL",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_DAT_SEL"), "0"), 2));
tg.config.add_word("DCU.CH0_TDRV_POST_EN",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_POST_EN"), "0"), 1));
tg.config.add_word("DCU.CH0_TDRV_PRE_EN",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_PRE_EN"), "0"), 1));
tg.config.add_word("DCU.CH0_TDRV_SLICE0_CUR",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_SLICE0_CUR"), "0"), 3));
tg.config.add_word("DCU.CH0_TDRV_SLICE0_SEL",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_SLICE0_SEL"), "0"), 2));
tg.config.add_word("DCU.CH0_TDRV_SLICE1_CUR",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_SLICE1_CUR"), "0"), 3));
tg.config.add_word("DCU.CH0_TDRV_SLICE1_SEL",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_SLICE1_SEL"), "0"), 2));
tg.config.add_word("DCU.CH0_TDRV_SLICE2_CUR",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_SLICE2_CUR"), "0"), 2));
tg.config.add_word("DCU.CH0_TDRV_SLICE2_SEL",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_SLICE2_SEL"), "0"), 2));
tg.config.add_word("DCU.CH0_TDRV_SLICE3_CUR",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_SLICE3_CUR"), "0"), 2));
tg.config.add_word("DCU.CH0_TDRV_SLICE3_SEL",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_SLICE3_SEL"), "0"), 2));
tg.config.add_word("DCU.CH0_TDRV_SLICE4_CUR",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_SLICE4_CUR"), "0"), 2));
tg.config.add_word("DCU.CH0_TDRV_SLICE4_SEL",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_SLICE4_SEL"), "0"), 2));
tg.config.add_word("DCU.CH0_TDRV_SLICE5_CUR",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_SLICE5_CUR"), "0"), 2));
tg.config.add_word("DCU.CH0_TDRV_SLICE5_SEL",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_TDRV_SLICE5_SEL"), "0"), 2));
tg.config.add_word("DCU.CH0_TPWDNB", parse_config_str(str_or_default(ci->params, ctx->id("CH0_TPWDNB"), "0"), 1));
tg.config.add_word("DCU.CH0_TX_CM_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH0_TX_CM_SEL"), "0"), 2));
tg.config.add_word("DCU.CH0_TX_DIV11_SEL",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_TX_DIV11_SEL"), "0"), 1));
tg.config.add_word("DCU.CH0_TX_GEAR_BYPASS",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_TX_GEAR_BYPASS"), "0"), 1));
tg.config.add_word("DCU.CH0_TX_GEAR_MODE",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_TX_GEAR_MODE"), "0"), 1));
tg.config.add_word("DCU.CH0_TX_POST_SIGN",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_TX_POST_SIGN"), "0"), 1));
tg.config.add_word("DCU.CH0_TX_PRE_SIGN",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_TX_PRE_SIGN"), "0"), 1));
tg.config.add_word("DCU.CH0_UC_MODE", parse_config_str(str_or_default(ci->params, ctx->id("CH0_UC_MODE"), "0"), 1));
tg.config.add_word("DCU.CH0_UDF_COMMA_A",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_UDF_COMMA_A"), "0"), 10));
tg.config.add_word("DCU.CH0_UDF_COMMA_B",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_UDF_COMMA_B"), "0"), 10));
tg.config.add_word("DCU.CH0_UDF_COMMA_MASK",
parse_config_str(str_or_default(ci->params, ctx->id("CH0_UDF_COMMA_MASK"), "0"), 10));
tg.config.add_word("DCU.CH0_WA_BYPASS", parse_config_str(str_or_default(ci->params, ctx->id("CH0_WA_BYPASS"), "0"), 1));
tg.config.add_word("DCU.CH0_WA_MODE", parse_config_str(str_or_default(ci->params, ctx->id("CH0_WA_MODE"), "0"), 1));
tg.config.add_word("DCU.CH1_AUTO_CALIB_EN",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_AUTO_CALIB_EN"), "0"), 1));
tg.config.add_word("DCU.CH1_AUTO_FACQ_EN",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_AUTO_FACQ_EN"), "0"), 1));
tg.config.add_word("DCU.CH1_BAND_THRESHOLD",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_BAND_THRESHOLD"), "0"), 6));
tg.config.add_word("DCU.CH1_CALIB_CK_MODE",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_CALIB_CK_MODE"), "0"), 1));
tg.config.add_word("DCU.CH1_CC_MATCH_1",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_CC_MATCH_1"), "0"), 10));
tg.config.add_word("DCU.CH1_CC_MATCH_2",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_CC_MATCH_2"), "0"), 10));
tg.config.add_word("DCU.CH1_CC_MATCH_3",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_CC_MATCH_3"), "0"), 10));
tg.config.add_word("DCU.CH1_CC_MATCH_4",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_CC_MATCH_4"), "0"), 10));
tg.config.add_word("DCU.CH1_CDR_CNT4SEL",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_CDR_CNT4SEL"), "0"), 2));
tg.config.add_word("DCU.CH1_CDR_CNT8SEL",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_CDR_CNT8SEL"), "0"), 2));
tg.config.add_word("DCU.CH1_CTC_BYPASS",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_CTC_BYPASS"), "0"), 1));
tg.config.add_word("DCU.CH1_DCOATDCFG", parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOATDCFG"), "0"), 2));
tg.config.add_word("DCU.CH1_DCOATDDLY", parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOATDDLY"), "0"), 2));
tg.config.add_word("DCU.CH1_DCOBYPSATD",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOBYPSATD"), "0"), 1));
tg.config.add_word("DCU.CH1_DCOCALDIV", parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOCALDIV"), "0"), 3));
tg.config.add_word("DCU.CH1_DCOCTLGI", parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOCTLGI"), "0"), 3));
tg.config.add_word("DCU.CH1_DCODISBDAVOID",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCODISBDAVOID"), "0"), 1));
tg.config.add_word("DCU.CH1_DCOFLTDAC", parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOFLTDAC"), "0"), 2));
tg.config.add_word("DCU.CH1_DCOFTNRG", parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOFTNRG"), "0"), 3));
tg.config.add_word("DCU.CH1_DCOIOSTUNE",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOIOSTUNE"), "0"), 3));
tg.config.add_word("DCU.CH1_DCOITUNE", parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOITUNE"), "0"), 2));
tg.config.add_word("DCU.CH1_DCOITUNE4LSB",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOITUNE4LSB"), "0"), 3));
tg.config.add_word("DCU.CH1_DCOIUPDNX2",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOIUPDNX2"), "0"), 1));
tg.config.add_word("DCU.CH1_DCONUOFLSB",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCONUOFLSB"), "0"), 3));
tg.config.add_word("DCU.CH1_DCOSCALEI", parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOSCALEI"), "0"), 2));
tg.config.add_word("DCU.CH1_DCOSTARTVAL",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOSTARTVAL"), "0"), 3));
tg.config.add_word("DCU.CH1_DCOSTEP", parse_config_str(str_or_default(ci->params, ctx->id("CH1_DCOSTEP"), "0"), 2));
tg.config.add_word("DCU.CH1_DEC_BYPASS",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_DEC_BYPASS"), "0"), 1));
tg.config.add_word("DCU.CH1_ENABLE_CG_ALIGN",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_ENABLE_CG_ALIGN"), "0"), 1));
tg.config.add_word("DCU.CH1_ENC_BYPASS",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_ENC_BYPASS"), "0"), 1));
tg.config.add_word("DCU.CH1_FF_RX_F_CLK_DIS",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_FF_RX_F_CLK_DIS"), "0"), 1));
tg.config.add_word("DCU.CH1_FF_RX_H_CLK_EN",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_FF_RX_H_CLK_EN"), "0"), 1));
tg.config.add_word("DCU.CH1_FF_TX_F_CLK_DIS",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_FF_TX_F_CLK_DIS"), "0"), 1));
tg.config.add_word("DCU.CH1_FF_TX_H_CLK_EN",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_FF_TX_H_CLK_EN"), "0"), 1));
tg.config.add_word("DCU.CH1_GE_AN_ENABLE",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_GE_AN_ENABLE"), "0"), 1));
tg.config.add_word("DCU.CH1_INVERT_RX", parse_config_str(str_or_default(ci->params, ctx->id("CH1_INVERT_RX"), "0"), 1));
tg.config.add_word("DCU.CH1_INVERT_TX", parse_config_str(str_or_default(ci->params, ctx->id("CH1_INVERT_TX"), "0"), 1));
tg.config.add_word("DCU.CH1_LDR_CORE2TX_SEL",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_LDR_CORE2TX_SEL"), "0"), 1));
tg.config.add_word("DCU.CH1_LDR_RX2CORE_SEL",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_LDR_RX2CORE_SEL"), "0"), 1));
tg.config.add_word("DCU.CH1_LEQ_OFFSET_SEL",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_LEQ_OFFSET_SEL"), "0"), 1));
tg.config.add_word("DCU.CH1_LEQ_OFFSET_TRIM",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_LEQ_OFFSET_TRIM"), "0"), 3));
tg.config.add_word("DCU.CH1_LSM_DISABLE",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_LSM_DISABLE"), "0"), 1));
tg.config.add_word("DCU.CH1_MATCH_2_ENABLE",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_MATCH_2_ENABLE"), "0"), 1));
tg.config.add_word("DCU.CH1_MATCH_4_ENABLE",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_MATCH_4_ENABLE"), "0"), 1));
tg.config.add_word("DCU.CH1_MIN_IPG_CNT",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_MIN_IPG_CNT"), "0"), 2));
tg.config.add_word("DCU.CH1_PCIE_EI_EN",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_PCIE_EI_EN"), "0"), 1));
tg.config.add_word("DCU.CH1_PCIE_MODE", parse_config_str(str_or_default(ci->params, ctx->id("CH1_PCIE_MODE"), "0"), 1));
tg.config.add_word("DCU.CH1_PCS_DET_TIME_SEL",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_PCS_DET_TIME_SEL"), "0"), 2));
tg.config.add_word("DCU.CH1_PDEN_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH1_PDEN_SEL"), "0"), 1));
tg.config.add_word("DCU.CH1_PRBS_ENABLE",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_PRBS_ENABLE"), "0"), 1));
tg.config.add_word("DCU.CH1_PRBS_LOCK", parse_config_str(str_or_default(ci->params, ctx->id("CH1_PRBS_LOCK"), "0"), 1));
tg.config.add_word("DCU.CH1_PRBS_SELECTION",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_PRBS_SELECTION"), "0"), 1));
tg.config.add_word("DCU.CH1_RATE_MODE_RX",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_RATE_MODE_RX"), "0"), 1));
tg.config.add_word("DCU.CH1_RATE_MODE_TX",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_RATE_MODE_TX"), "0"), 1));
tg.config.add_word("DCU.CH1_RCV_DCC_EN",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_RCV_DCC_EN"), "0"), 1));
tg.config.add_word("DCU.CH1_REG_BAND_OFFSET",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_REG_BAND_OFFSET"), "0"), 4));
tg.config.add_word("DCU.CH1_REG_BAND_SEL",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_REG_BAND_SEL"), "0"), 6));
tg.config.add_word("DCU.CH1_REG_IDAC_EN",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_REG_IDAC_EN"), "0"), 1));
tg.config.add_word("DCU.CH1_REG_IDAC_SEL",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_REG_IDAC_SEL"), "0"), 10));
tg.config.add_word("DCU.CH1_REQ_EN", parse_config_str(str_or_default(ci->params, ctx->id("CH1_REQ_EN"), "0"), 1));
tg.config.add_word("DCU.CH1_REQ_LVL_SET",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_REQ_LVL_SET"), "0"), 2));
tg.config.add_word("DCU.CH1_RIO_MODE", parse_config_str(str_or_default(ci->params, ctx->id("CH1_RIO_MODE"), "0"), 1));
tg.config.add_word("DCU.CH1_RLOS_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH1_RLOS_SEL"), "0"), 1));
tg.config.add_word("DCU.CH1_RPWDNB", parse_config_str(str_or_default(ci->params, ctx->id("CH1_RPWDNB"), "0"), 1));
tg.config.add_word("DCU.CH1_RTERM_RX", parse_config_str(str_or_default(ci->params, ctx->id("CH1_RTERM_RX"), "0"), 5));
tg.config.add_word("DCU.CH1_RTERM_TX", parse_config_str(str_or_default(ci->params, ctx->id("CH1_RTERM_TX"), "0"), 5));
tg.config.add_word("DCU.CH1_RXIN_CM", parse_config_str(str_or_default(ci->params, ctx->id("CH1_RXIN_CM"), "0"), 2));
tg.config.add_word("DCU.CH1_RXTERM_CM", parse_config_str(str_or_default(ci->params, ctx->id("CH1_RXTERM_CM"), "0"), 2));
tg.config.add_word("DCU.CH1_RX_DCO_CK_DIV",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_RX_DCO_CK_DIV"), "0"), 3));
tg.config.add_word("DCU.CH1_RX_DIV11_SEL",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_RX_DIV11_SEL"), "0"), 1));
tg.config.add_word("DCU.CH1_RX_GEAR_BYPASS",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_RX_GEAR_BYPASS"), "0"), 1));
tg.config.add_word("DCU.CH1_RX_GEAR_MODE",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_RX_GEAR_MODE"), "0"), 1));
tg.config.add_word("DCU.CH1_RX_LOS_CEQ",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_RX_LOS_CEQ"), "0"), 2));
tg.config.add_word("DCU.CH1_RX_LOS_EN", parse_config_str(str_or_default(ci->params, ctx->id("CH1_RX_LOS_EN"), "0"), 1));
tg.config.add_word("DCU.CH1_RX_LOS_HYST_EN",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_RX_LOS_HYST_EN"), "0"), 1));
tg.config.add_word("DCU.CH1_RX_LOS_LVL",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_RX_LOS_LVL"), "0"), 3));
tg.config.add_word("DCU.CH1_RX_RATE_SEL",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_RX_RATE_SEL"), "0"), 4));
tg.config.add_word("DCU.CH1_RX_SB_BYPASS",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_RX_SB_BYPASS"), "0"), 1));
tg.config.add_word("DCU.CH1_SB_BYPASS", parse_config_str(str_or_default(ci->params, ctx->id("CH1_SB_BYPASS"), "0"), 1));
tg.config.add_word("DCU.CH1_SEL_SD_RX_CLK",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_SEL_SD_RX_CLK"), "0"), 1));
tg.config.add_word("DCU.CH1_TDRV_DAT_SEL",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_DAT_SEL"), "0"), 2));
tg.config.add_word("DCU.CH1_TDRV_POST_EN",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_POST_EN"), "0"), 1));
tg.config.add_word("DCU.CH1_TDRV_PRE_EN",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_PRE_EN"), "0"), 1));
tg.config.add_word("DCU.CH1_TDRV_SLICE0_CUR",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_SLICE0_CUR"), "0"), 3));
tg.config.add_word("DCU.CH1_TDRV_SLICE0_SEL",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_SLICE0_SEL"), "0"), 2));
tg.config.add_word("DCU.CH1_TDRV_SLICE1_CUR",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_SLICE1_CUR"), "0"), 3));
tg.config.add_word("DCU.CH1_TDRV_SLICE1_SEL",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_SLICE1_SEL"), "0"), 2));
tg.config.add_word("DCU.CH1_TDRV_SLICE2_CUR",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_SLICE2_CUR"), "0"), 2));
tg.config.add_word("DCU.CH1_TDRV_SLICE2_SEL",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_SLICE2_SEL"), "0"), 2));
tg.config.add_word("DCU.CH1_TDRV_SLICE3_CUR",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_SLICE3_CUR"), "0"), 2));
tg.config.add_word("DCU.CH1_TDRV_SLICE3_SEL",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_SLICE3_SEL"), "0"), 2));
tg.config.add_word("DCU.CH1_TDRV_SLICE4_CUR",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_SLICE4_CUR"), "0"), 2));
tg.config.add_word("DCU.CH1_TDRV_SLICE4_SEL",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_SLICE4_SEL"), "0"), 2));
tg.config.add_word("DCU.CH1_TDRV_SLICE5_CUR",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_SLICE5_CUR"), "0"), 2));
tg.config.add_word("DCU.CH1_TDRV_SLICE5_SEL",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_TDRV_SLICE5_SEL"), "0"), 2));
tg.config.add_word("DCU.CH1_TPWDNB", parse_config_str(str_or_default(ci->params, ctx->id("CH1_TPWDNB"), "0"), 1));
tg.config.add_word("DCU.CH1_TX_CM_SEL", parse_config_str(str_or_default(ci->params, ctx->id("CH1_TX_CM_SEL"), "0"), 2));
tg.config.add_word("DCU.CH1_TX_DIV11_SEL",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_TX_DIV11_SEL"), "0"), 1));
tg.config.add_word("DCU.CH1_TX_GEAR_BYPASS",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_TX_GEAR_BYPASS"), "0"), 1));
tg.config.add_word("DCU.CH1_TX_GEAR_MODE",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_TX_GEAR_MODE"), "0"), 1));
tg.config.add_word("DCU.CH1_TX_POST_SIGN",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_TX_POST_SIGN"), "0"), 1));
tg.config.add_word("DCU.CH1_TX_PRE_SIGN",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_TX_PRE_SIGN"), "0"), 1));
tg.config.add_word("DCU.CH1_UC_MODE", parse_config_str(str_or_default(ci->params, ctx->id("CH1_UC_MODE"), "0"), 1));
tg.config.add_word("DCU.CH1_UDF_COMMA_A",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_UDF_COMMA_A"), "0"), 10));
tg.config.add_word("DCU.CH1_UDF_COMMA_B",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_UDF_COMMA_B"), "0"), 10));
tg.config.add_word("DCU.CH1_UDF_COMMA_MASK",
parse_config_str(str_or_default(ci->params, ctx->id("CH1_UDF_COMMA_MASK"), "0"), 10));
tg.config.add_word("DCU.CH1_WA_BYPASS", parse_config_str(str_or_default(ci->params, ctx->id("CH1_WA_BYPASS"), "0"), 1));
tg.config.add_word("DCU.CH1_WA_MODE", parse_config_str(str_or_default(ci->params, ctx->id("CH1_WA_MODE"), "0"), 1));
tg.config.add_word("DCU.D_BITCLK_FROM_ND_EN",
parse_config_str(str_or_default(ci->params, ctx->id("D_BITCLK_FROM_ND_EN"), "0"), 1));
tg.config.add_word("DCU.D_BITCLK_LOCAL_EN",
parse_config_str(str_or_default(ci->params, ctx->id("D_BITCLK_LOCAL_EN"), "0"), 1));
tg.config.add_word("DCU.D_BITCLK_ND_EN",
parse_config_str(str_or_default(ci->params, ctx->id("D_BITCLK_ND_EN"), "0"), 1));
tg.config.add_word("DCU.D_BUS8BIT_SEL", parse_config_str(str_or_default(ci->params, ctx->id("D_BUS8BIT_SEL"), "0"), 1));
tg.config.add_word("DCU.D_CDR_LOL_SET", parse_config_str(str_or_default(ci->params, ctx->id("D_CDR_LOL_SET"), "0"), 2));
tg.config.add_word("DCU.D_CMUSETBIASI", parse_config_str(str_or_default(ci->params, ctx->id("D_CMUSETBIASI"), "0"), 2));
tg.config.add_word("DCU.D_CMUSETI4CPP", parse_config_str(str_or_default(ci->params, ctx->id("D_CMUSETI4CPP"), "0"), 4));
tg.config.add_word("DCU.D_CMUSETI4CPZ", parse_config_str(str_or_default(ci->params, ctx->id("D_CMUSETI4CPZ"), "0"), 4));
tg.config.add_word("DCU.D_CMUSETI4VCO", parse_config_str(str_or_default(ci->params, ctx->id("D_CMUSETI4VCO"), "0"), 2));
tg.config.add_word("DCU.D_CMUSETICP4P", parse_config_str(str_or_default(ci->params, ctx->id("D_CMUSETICP4P"), "0"), 2));
tg.config.add_word("DCU.D_CMUSETICP4Z", parse_config_str(str_or_default(ci->params, ctx->id("D_CMUSETICP4Z"), "0"), 3));
tg.config.add_word("DCU.D_CMUSETINITVCT",
parse_config_str(str_or_default(ci->params, ctx->id("D_CMUSETINITVCT"), "0"), 2));
tg.config.add_word("DCU.D_CMUSETISCL4VCO",
parse_config_str(str_or_default(ci->params, ctx->id("D_CMUSETISCL4VCO"), "0"), 3));
tg.config.add_word("DCU.D_CMUSETP1GM", parse_config_str(str_or_default(ci->params, ctx->id("D_CMUSETP1GM"), "0"), 3));
tg.config.add_word("DCU.D_CMUSETP2AGM", parse_config_str(str_or_default(ci->params, ctx->id("D_CMUSETP2AGM"), "0"), 3));
tg.config.add_word("DCU.D_CMUSETZGM", parse_config_str(str_or_default(ci->params, ctx->id("D_CMUSETZGM"), "0"), 3));
tg.config.add_word("DCU.D_DCO_CALIB_TIME_SEL",
parse_config_str(str_or_default(ci->params, ctx->id("D_DCO_CALIB_TIME_SEL"), "0"), 2));
tg.config.add_word("DCU.D_HIGH_MARK", parse_config_str(str_or_default(ci->params, ctx->id("D_HIGH_MARK"), "0"), 4));
tg.config.add_word("DCU.D_IB_PWDNB", parse_config_str(str_or_default(ci->params, ctx->id("D_IB_PWDNB"), "0"), 1));
tg.config.add_word("DCU.D_ISETLOS", parse_config_str(str_or_default(ci->params, ctx->id("D_ISETLOS"), "0"), 8));
tg.config.add_word("DCU.D_LOW_MARK", parse_config_str(str_or_default(ci->params, ctx->id("D_LOW_MARK"), "0"), 4));
tg.config.add_word("DCU.D_MACROPDB", parse_config_str(str_or_default(ci->params, ctx->id("D_MACROPDB"), "0"), 1));
tg.config.add_word("DCU.D_PD_ISET", parse_config_str(str_or_default(ci->params, ctx->id("D_PD_ISET"), "0"), 2));
tg.config.add_word("DCU.D_PLL_LOL_SET", parse_config_str(str_or_default(ci->params, ctx->id("D_PLL_LOL_SET"), "0"), 2));
tg.config.add_word("DCU.D_REFCK_MODE", parse_config_str(str_or_default(ci->params, ctx->id("D_REFCK_MODE"), "0"), 3));
tg.config.add_word("DCU.D_REQ_ISET", parse_config_str(str_or_default(ci->params, ctx->id("D_REQ_ISET"), "0"), 3));
tg.config.add_word("DCU.D_RG_EN", parse_config_str(str_or_default(ci->params, ctx->id("D_RG_EN"), "0"), 1));
tg.config.add_word("DCU.D_RG_SET", parse_config_str(str_or_default(ci->params, ctx->id("D_RG_SET"), "0"), 2));
tg.config.add_word("DCU.D_SETICONST_AUX",
parse_config_str(str_or_default(ci->params, ctx->id("D_SETICONST_AUX"), "0"), 2));
tg.config.add_word("DCU.D_SETICONST_CH",
parse_config_str(str_or_default(ci->params, ctx->id("D_SETICONST_CH"), "0"), 2));
tg.config.add_word("DCU.D_SETIRPOLY_AUX",
parse_config_str(str_or_default(ci->params, ctx->id("D_SETIRPOLY_AUX"), "0"), 2));
tg.config.add_word("DCU.D_SETIRPOLY_CH",
parse_config_str(str_or_default(ci->params, ctx->id("D_SETIRPOLY_CH"), "0"), 2));
tg.config.add_word("DCU.D_SETPLLRC", parse_config_str(str_or_default(ci->params, ctx->id("D_SETPLLRC"), "0"), 6));
tg.config.add_word("DCU.D_SYNC_LOCAL_EN",
parse_config_str(str_or_default(ci->params, ctx->id("D_SYNC_LOCAL_EN"), "0"), 1));
tg.config.add_word("DCU.D_SYNC_ND_EN", parse_config_str(str_or_default(ci->params, ctx->id("D_SYNC_ND_EN"), "0"), 1));
tg.config.add_word("DCU.D_TXPLL_PWDNB", parse_config_str(str_or_default(ci->params, ctx->id("D_TXPLL_PWDNB"), "0"), 1));
tg.config.add_word("DCU.D_TX_VCO_CK_DIV",
parse_config_str(str_or_default(ci->params, ctx->id("D_TX_VCO_CK_DIV"), "0"), 3));
tg.config.add_word("DCU.D_XGE_MODE", parse_config_str(str_or_default(ci->params, ctx->id("D_XGE_MODE"), "0"), 1));

View File

@ -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)

View File

@ -55,6 +55,9 @@ class Ecp5GlobalRouter
{
if (user.cell->type == id_TRELLIS_SLICE && (user.port == id_CLK || user.port == id_WCK))
return true;
if (user.cell->type == id_DCUA && (user.port == id_CH0_FF_RXI_CLK || user.port == id_CH1_FF_RXI_CLK ||
user.port == id_CH0_FF_TXI_CLK || user.port == id_CH1_FF_TXI_CLK))
return true;
return false;
}
@ -65,8 +68,11 @@ class Ecp5GlobalRouter
NetInfo *ni = net.second.get();
clockCount[ni->name] = 0;
for (const auto &user : ni->users) {
if (is_clock_port(user))
if (is_clock_port(user)) {
clockCount[ni->name]++;
if (user.cell->type == id_DCUA)
clockCount[ni->name] += 100;
}
}
// log_info("clkcount %s: %d\n", ni->name.c_str(ctx),clockCount[ni->name]);
}
@ -290,6 +296,10 @@ class Ecp5GlobalRouter
float tns;
return get_net_metric(ctx, clki, MetricType::WIRELENGTH, tns);
} else {
// Check for dedicated routing
if (has_short_route(ctx->getBelPinWire(drv_bel, drv.port), ctx->getBelPinWire(dcc->bel, id_CLKI))) {
return 0;
}
// Driver is locked
Loc dcc_loc = ctx->getBelLocation(dcc->bel);
Loc drv_loc = ctx->getBelLocation(drv_bel);
@ -297,6 +307,46 @@ class Ecp5GlobalRouter
}
}
// Return true if a short (<5) route exists between two wires
bool has_short_route(WireId src, WireId dst, int thresh = 5)
{
std::queue<WireId> visit;
std::unordered_map<WireId, PipId> backtrace;
visit.push(src);
WireId cursor;
while (true) {
if (visit.empty() || visit.size() > 1000) {
// log_info ("dist %s -> %s = inf\n", ctx->getWireName(src).c_str(ctx),
// ctx->getWireName(dst).c_str(ctx));
return false;
}
cursor = visit.front();
visit.pop();
if (cursor == dst)
break;
for (auto dh : ctx->getPipsDownhill(cursor)) {
WireId pipDst = ctx->getPipDstWire(dh);
if (backtrace.count(pipDst))
continue;
backtrace[pipDst] = dh;
visit.push(pipDst);
}
}
int length = 0;
while (true) {
auto fnd = backtrace.find(cursor);
if (fnd == backtrace.end())
break;
cursor = ctx->getPipSrcWire(fnd->second);
length++;
}
// log_info ("dist %s -> %s = %d\n", ctx->getWireName(src).c_str(ctx), ctx->getWireName(dst).c_str(ctx),
// length);
return length < thresh;
}
// Attempt to place a DCC
void place_dcc(CellInfo *dcc)
{
@ -335,6 +385,8 @@ class Ecp5GlobalRouter
for (auto user : net->users) {
if (user.port == id_CLKFB) {
keep_users.push_back(user);
} else if (net->driver.cell->type == id_EXTREFB && user.cell->type == id_DCUA) {
keep_users.push_back(user);
} else {
glbnet->users.push_back(user);
user.cell->ports.at(user.port).net = glbnet.get();
@ -350,6 +402,13 @@ class Ecp5GlobalRouter
place_dcc(dcc.get());
if (net->clkconstr) {
glbnet->clkconstr = std::unique_ptr<ClockConstraint>(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);

View File

@ -235,6 +235,46 @@ class Ecp5Packer
}
}
// Return true if an port is a top level port that provides its own IOBUF
bool is_top_port(PortRef &port)
{
if (port.cell == nullptr)
return false;
if (port.cell->type == id_DCUA) {
return port.port == id_CH0_HDINP || port.port == id_CH0_HDINN || port.port == id_CH0_HDOUTP ||
port.port == id_CH0_HDOUTN || port.port == id_CH1_HDINP || port.port == id_CH1_HDINN ||
port.port == id_CH1_HDOUTP || port.port == id_CH1_HDOUTN;
} else if (port.cell->type == id_EXTREFB) {
return port.port == id_REFCLKP || port.port == id_REFCLKN;
} else {
return false;
}
}
// Return true if a net only drives a top port
bool drives_top_port(NetInfo *net, PortRef &tp)
{
if (net == nullptr)
return false;
for (auto user : net->users) {
if (is_top_port(user)) {
if (net->users.size() > 1)
log_error(" port %s.%s must be connected to (and only to) a top level pin\n",
user.cell->name.c_str(ctx), user.port.c_str(ctx));
tp = user;
return true;
}
}
if (net->driver.cell != nullptr && is_top_port(net->driver)) {
if (net->users.size() > 1)
log_error(" port %s.%s must be connected to (and only to) a top level pin\n",
net->driver.cell->name.c_str(ctx), net->driver.port.c_str(ctx));
tp = net->driver;
return true;
}
return false;
}
// Simple "packer" to remove nextpnr IOBUFs, this assumes IOBUFs are manually instantiated
void pack_io()
{
@ -244,10 +284,14 @@ class Ecp5Packer
CellInfo *ci = cell.second;
if (is_nextpnr_iob(ctx, ci)) {
CellInfo *trio = nullptr;
NetInfo *ionet = nullptr;
PortRef tp;
if (ci->type == ctx->id("$nextpnr_ibuf") || ci->type == ctx->id("$nextpnr_iobuf")) {
trio = net_only_drives(ctx, ci->ports.at(ctx->id("O")).net, is_trellis_io, ctx->id("B"), true, ci);
ionet = ci->ports.at(ctx->id("O")).net;
trio = net_only_drives(ctx, ionet, is_trellis_io, ctx->id("B"), true, ci);
} else if (ci->type == ctx->id("$nextpnr_obuf")) {
ionet = ci->ports.at(ctx->id("I")).net;
trio = net_only_drives(ctx, ci->ports.at(ctx->id("I")).net, is_trellis_io, ctx->id("B"), true, ci);
}
if (trio != nullptr) {
@ -266,6 +310,19 @@ class Ecp5Packer
ctx->nets.erase(net2->name);
}
}
} else if (drives_top_port(ionet, tp)) {
log_info("%s feeds %s %s.%s, removing %s %s.\n", ci->name.c_str(ctx), tp.cell->type.c_str(ctx),
tp.cell->name.c_str(ctx), tp.port.c_str(ctx), ci->type.c_str(ctx), ci->name.c_str(ctx));
if (ionet != nullptr) {
ctx->nets.erase(ionet->name);
tp.cell->ports.at(tp.port).net = nullptr;
}
if (ci->type == ctx->id("$nextpnr_iobuf")) {
NetInfo *net2 = ci->ports.at(ctx->id("I")).net;
if (net2 != nullptr) {
ctx->nets.erase(net2->name);
}
}
} else {
// Create a TRELLIS_IO buffer
std::unique_ptr<CellInfo> tr_cell =
@ -276,21 +333,23 @@ class Ecp5Packer
}
packed_cells.insert(ci->name);
for (const auto &attr : ci->attrs)
trio->attrs[attr.first] = attr.second;
if (trio != nullptr) {
for (const auto &attr : ci->attrs)
trio->attrs[attr.first] = attr.second;
auto loc_attr = trio->attrs.find(ctx->id("LOC"));
if (loc_attr != trio->attrs.end()) {
std::string pin = loc_attr->second;
BelId pinBel = ctx->getPackagePinBel(pin);
if (pinBel == BelId()) {
log_error("IO pin '%s' constrained to pin '%s', which does not exist for package '%s'.\n",
trio->name.c_str(ctx), pin.c_str(), ctx->args.package.c_str());
} else {
log_info("pin '%s' constrained to Bel '%s'.\n", trio->name.c_str(ctx),
ctx->getBelName(pinBel).c_str(ctx));
auto loc_attr = trio->attrs.find(ctx->id("LOC"));
if (loc_attr != trio->attrs.end()) {
std::string pin = loc_attr->second;
BelId pinBel = ctx->getPackagePinBel(pin);
if (pinBel == BelId()) {
log_error("IO pin '%s' constrained to pin '%s', which does not exist for package '%s'.\n",
trio->name.c_str(ctx), pin.c_str(), ctx->args.package.c_str());
} else {
log_info("pin '%s' constrained to Bel '%s'.\n", trio->name.c_str(ctx),
ctx->getBelName(pinBel).c_str(ctx));
}
trio->attrs[ctx->id("BEL")] = ctx->getBelName(pinBel).str(ctx);
}
trio->attrs[ctx->id("BEL")] = ctx->getBelName(pinBel).str(ctx);
}
}
}
@ -1033,12 +1092,93 @@ class Ecp5Packer
}
}
// "Pack" DCUs
void pack_dcus()
{
for (auto cell : sorted(ctx->cells)) {
CellInfo *ci = cell.second;
if (ci->type == id_DCUA) {
if (ci->attrs.count(ctx->id("LOC"))) {
std::string loc = ci->attrs.at(ctx->id("LOC"));
if (loc == "DCU0" &&
(ctx->args.type == ArchArgs::LFE5UM_25F || ctx->args.type == ArchArgs::LFE5UM5G_25F))
ci->attrs[ctx->id("BEL")] = "X42/Y50/DCU";
else if (loc == "DCU0" &&
(ctx->args.type == ArchArgs::LFE5UM_45F || ctx->args.type == ArchArgs::LFE5UM5G_45F))
ci->attrs[ctx->id("BEL")] = "X42/Y71/DCU";
else if (loc == "DCU1" &&
(ctx->args.type == ArchArgs::LFE5UM_45F || ctx->args.type == ArchArgs::LFE5UM5G_45F))
ci->attrs[ctx->id("BEL")] = "X69/Y71/DCU";
else if (loc == "DCU0" &&
(ctx->args.type == ArchArgs::LFE5UM_85F || ctx->args.type == ArchArgs::LFE5UM5G_85F))
ci->attrs[ctx->id("BEL")] = "X46/Y95/DCU";
else if (loc == "DCU1" &&
(ctx->args.type == ArchArgs::LFE5UM_85F || ctx->args.type == ArchArgs::LFE5UM5G_85F))
ci->attrs[ctx->id("BEL")] = "X71/Y95/DCU";
else
log_error("no DCU location '%s' in device '%s'\n", loc.c_str(), ctx->getChipName().c_str());
}
if (!ci->attrs.count(ctx->id("BEL")))
log_error("DCU must be constrained to a Bel!\n");
// Empty port auto-creation to generate correct tie-downs
BelId exemplar_bel;
for (auto bel : ctx->getBels()) {
if (ctx->getBelType(bel) == id_DCUA) {
exemplar_bel = bel;
break;
}
}
NPNR_ASSERT(exemplar_bel != BelId());
for (auto pin : ctx->getBelPins(exemplar_bel))
if (ctx->getBelPinType(exemplar_bel, pin) == PORT_IN)
autocreate_empty_port(ci, pin);
}
}
for (auto cell : sorted(ctx->cells)) {
CellInfo *ci = cell.second;
if (ci->type == id_EXTREFB) {
const NetInfo *refo = net_or_nullptr(ci, id_REFCLKO);
CellInfo *dcu = nullptr;
if (refo == nullptr)
log_error("EXTREFB REFCLKO must not be unconnected\n");
for (auto user : refo->users) {
if (user.cell->type != id_DCUA)
continue;
if (dcu != nullptr && dcu != user.cell)
log_error("EXTREFB REFCLKO must only drive a single DCUA\n");
dcu = user.cell;
}
if (!dcu->attrs.count(ctx->id("BEL")))
log_error("DCU must be constrained to a Bel!\n");
std::string bel = dcu->attrs.at(ctx->id("BEL"));
NPNR_ASSERT(bel.substr(bel.length() - 3) == "DCU");
bel.replace(bel.length() - 3, 3, "EXTREF");
ci->attrs[ctx->id("BEL")] = bel;
} else if (ci->type == id_PCSCLKDIV) {
const NetInfo *clki = net_or_nullptr(ci, id_CLKI);
if (clki != nullptr && clki->driver.cell != nullptr && clki->driver.cell->type == id_DCUA) {
CellInfo *dcu = clki->driver.cell;
if (!dcu->attrs.count(ctx->id("BEL")))
log_error("DCU must be constrained to a Bel!\n");
BelId bel = ctx->getBelByName(ctx->id(dcu->attrs.at(ctx->id("BEL"))));
if (bel == BelId())
log_error("Invalid DCU bel '%s'\n", dcu->attrs.at(ctx->id("BEL")).c_str());
Loc loc = ctx->getBelLocation(bel);
// DCU0 -> CLKDIV z=0; DCU1 -> CLKDIV z=1
ci->constr_abs_z = true;
ci->constr_z = (loc.x >= 69) ? 1 : 0;
}
}
}
}
public:
void pack()
{
pack_io();
pack_ebr();
pack_dsps();
pack_dcus();
pack_constants();
pack_dram();
pack_carries();

View File

@ -17,7 +17,7 @@
*
*/
#include "io.h"
#include "pio.h"
NEXTPNR_NAMESPACE_BEGIN

View File

@ -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

View File

@ -463,11 +463,16 @@ bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort
}
// Get the port class, also setting clockPort if applicable
TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockPort) const
TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const
{
return TMG_IGNORE;
}
TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port, int index) const
{
NPNR_ASSERT_FALSE("no clocking info for generic");
}
bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const { return true; }
bool Arch::isBelLocationValid(BelId bel) const { return true; }

View File

@ -211,6 +211,14 @@ struct Arch : BaseCtx
delay_t getDelayEpsilon() const { return 0.01; }
delay_t getRipupDelayPenalty() const { return 1.0; }
float getDelayNS(delay_t v) const { return v; }
DelayInfo getDelayFromNS(float ns) const
{
DelayInfo del;
del.delay = ns;
return del;
}
uint32_t getDelayChecksum(delay_t v) const { return 0; }
bool getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const;
@ -225,8 +233,10 @@ struct Arch : BaseCtx
DecalXY getGroupDecal(GroupId group) const;
bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const;
// Get the port class, also setting clockPort if applicable
TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockPort) const;
// Get the port class, also setting clockInfoCount to the number of TimingClockingInfos associated with a port
TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const;
// Get the TimingClockingInfo of a port
TimingClockingInfo getPortClockingInfo(const CellInfo *cell, IdString port, int index) const;
bool isValidBelForCell(CellInfo *cell, BelId bel) const;
bool isBelLocationValid(BelId bel) const;

View File

@ -291,15 +291,16 @@ void DesignWidget::newContext(Context *ctx)
{
TreeModel::ElementXYRoot<BelId>::ElementMap belMap;
for (const auto& bel : ctx->getBels()) {
for (const auto &bel : ctx->getBels()) {
auto loc = ctx->getBelLocation(bel);
belMap[std::pair<int, int>(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<TreeModel::ElementXYRoot<BelId>>(
new TreeModel::ElementXYRoot<BelId>(ctx, belMap, belGetter, ElementType::BEL)));
->loadData(ctx,
std::unique_ptr<TreeModel::ElementXYRoot<BelId>>(
new TreeModel::ElementXYRoot<BelId>(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<int, int>(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<TreeModel::ElementXYRoot<WireId>>(
new TreeModel::ElementXYRoot<WireId>(ctx, wireMap, wireGetter, ElementType::WIRE)));
->loadData(ctx,
std::unique_ptr<TreeModel::ElementXYRoot<WireId>>(new TreeModel::ElementXYRoot<WireId>(
ctx, wireMap, wireGetter, ElementType::WIRE)));
}
{
TreeModel::ElementXYRoot<PipId>::ElementMap pipMap;
for (const auto& pip : ctx->getPips()) {
for (const auto &pip : ctx->getPips()) {
auto loc = ctx->getPipLocation(pip);
pipMap[std::pair<int, int>(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<TreeModel::ElementXYRoot<PipId>>(
new TreeModel::ElementXYRoot<PipId>(ctx, pipMap, pipGetter, ElementType::PIP)));
->loadData(ctx,
std::unique_ptr<TreeModel::ElementXYRoot<PipId>>(
new TreeModel::ElementXYRoot<PipId>(ctx, pipMap, pipGetter, ElementType::PIP)));
}
getTreeByElementType(ElementType::CELL)
->loadData(ctx, std::unique_ptr<TreeModel::IdStringList>(new TreeModel::IdStringList(ElementType::CELL)));
->loadData(ctx,
std::unique_ptr<TreeModel::IdStringList>(new TreeModel::IdStringList(ElementType::CELL)));
getTreeByElementType(ElementType::NET)
->loadData(ctx, std::unique_ptr<TreeModel::IdStringList>(new TreeModel::IdStringList(ElementType::NET)));
->loadData(ctx,
std::unique_ptr<TreeModel::IdStringList>(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);

View File

@ -113,8 +113,8 @@ void FPGAViewWidget::initializeGL()
}
initializeOpenGLFunctions();
QtImGui::initialize(this);
glClearColor(colors_.background.red() / 255, colors_.background.green() / 255,
colors_.background.blue() / 255, 0.0);
glClearColor(colors_.background.red() / 255, colors_.background.green() / 255, colors_.background.blue() / 255,
0.0);
}
float FPGAViewWidget::PickedElement::distance(Context *ctx, float wx, float wy) const
@ -510,8 +510,7 @@ void FPGAViewWidget::renderLines(void)
rendererData_ = std::move(data);
}
}
if (gridChanged)
{
if (gridChanged) {
QMutexLocker locker(&rendererDataLock_);
rendererData_->gfxGrid.clear();
// Render grid.
@ -520,7 +519,7 @@ void FPGAViewWidget::renderLines(void)
}
for (float i = 0.0f; i < 1.0f * ctx_->getGridDimY() + 1; i += 1.0f) {
PolyLine(0.0f, i, 1.0f * ctx_->getGridDimX(), i).build(rendererData_->gfxGrid);
}
}
rendererData_->gfxGrid.last_render++;
}
if (highlightedOrSelectedChanged) {
@ -556,11 +555,11 @@ void FPGAViewWidget::renderLines(void)
}
{
QMutexLocker lock(&rendererArgsLock_);
QMutexLocker lock(&rendererArgsLock_);
if (rendererArgs_->zoomOutbound) {
zoomOutbound();
rendererArgs_->zoomOutbound = false;
rendererArgs_->zoomOutbound = false;
}
}
}
@ -870,11 +869,8 @@ void FPGAViewWidget::update_vbos()
{
lineShader_.update_vbos(GraphicElement::STYLE_GRID, rendererData_->gfxGrid);
for (int style = GraphicElement::STYLE_FRAME; style
< GraphicElement::STYLE_HIGHLIGHTED0;
style++) {
lineShader_.update_vbos((enum GraphicElement::style_t)(style),
rendererData_->gfxByStyle[style]);
for (int style = GraphicElement::STYLE_FRAME; style < GraphicElement::STYLE_HIGHLIGHTED0; style++) {
lineShader_.update_vbos((enum GraphicElement::style_t)(style), rendererData_->gfxByStyle[style]);
}
for (int i = 0; i < 8; i++) {

View File

@ -266,20 +266,20 @@ template <typename CoordinateT, typename ElementT> 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<CoordinateT, ElementT>[4]{
// Note: not using [NW] = QuadTreeNode because that seems to
// crash g++ 7.3.0.
/* NW */ QuadTreeNode<CoordinateT, ElementT>(BoundingBox(bound_.x0_, bound_.y0_, splitx_, splity_),
depth_ + 1, max_elems_),
/* NE */
QuadTreeNode<CoordinateT, ElementT>(BoundingBox(splitx_, bound_.y0_, bound_.x1_, splity_),
depth_ + 1, max_elems_),
/* SW */
QuadTreeNode<CoordinateT, ElementT>(BoundingBox(bound_.x0_, splity_, splitx_, bound_.y1_),
depth_ + 1, max_elems_),
/* SE */
QuadTreeNode<CoordinateT, ElementT>(BoundingBox(splitx_, splity_, bound_.x1_, bound_.y1_),
depth_ + 1, max_elems_),
children_ = decltype(children_)(new QuadTreeNode<CoordinateT, ElementT>[4] {
// Note: not using [NW] = QuadTreeNode because that seems to
// crash g++ 7.3.0.
/* NW */ QuadTreeNode<CoordinateT, ElementT>(BoundingBox(bound_.x0_, bound_.y0_, splitx_, splity_),
depth_ + 1, max_elems_),
/* NE */
QuadTreeNode<CoordinateT, ElementT>(BoundingBox(splitx_, bound_.y0_, bound_.x1_, splity_),
depth_ + 1, max_elems_),
/* SW */
QuadTreeNode<CoordinateT, ElementT>(BoundingBox(bound_.x0_, splity_, splitx_, bound_.y1_),
depth_ + 1, max_elems_),
/* SE */
QuadTreeNode<CoordinateT, ElementT>(BoundingBox(splitx_, splity_, bound_.x1_, bound_.y1_),
depth_ + 1, max_elems_),
});
// Move all elements to where they belong.
auto it = elems_.begin();

View File

@ -284,6 +284,25 @@ std::vector<IdString> Arch::getBelPins(BelId bel) const
return ret;
}
bool Arch::isBelLocked(BelId bel) const
{
const BelConfigPOD *bel_config = nullptr;
for (int i = 0; i < chip_info->num_belcfgs; i++) {
if (chip_info->bel_config[i].bel_index == bel.index) {
bel_config = &chip_info->bel_config[i];
break;
}
}
NPNR_ASSERT(bel_config != nullptr);
for (int i = 0; i < bel_config->num_entries; i++) {
if (strcmp("LOCKED", bel_config->entries[i].cbit_name.get()))
continue;
if ("LOCKED_" + archArgs().package == bel_config->entries[i].entry_name.get())
return true;
}
return false;
}
// -----------------------------------------------------------------------
WireId Arch::getWireByName(IdString name) const
@ -856,8 +875,9 @@ bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort
}
// Get the port class, also setting clockPort to associated clock if applicable
TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockPort) const
TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const
{
clockInfoCount = 0;
if (cell->type == id_ICESTORM_LC) {
if (port == id_CLK)
return TMG_CLOCK_INPUT;
@ -870,13 +890,13 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id
if (cell->lcInfo.inputCount == 0)
return TMG_IGNORE;
if (cell->lcInfo.dffEnable) {
clockPort = id_CLK;
clockInfoCount = 1;
return TMG_REGISTER_OUTPUT;
} else
return TMG_COMB_OUTPUT;
} else {
if (cell->lcInfo.dffEnable) {
clockPort = id_CLK;
clockInfoCount = 1;
return TMG_REGISTER_INPUT;
} else
return TMG_COMB_INPUT;
@ -886,23 +906,22 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id
if (port == id_RCLK || port == id_WCLK)
return TMG_CLOCK_INPUT;
if (port.str(this)[0] == 'R')
clockPort = id_RCLK;
else
clockPort = id_WCLK;
clockInfoCount = 1;
if (cell->ports.at(port).type == PORT_OUT)
return TMG_REGISTER_OUTPUT;
else
return TMG_REGISTER_INPUT;
} else if (cell->type == id_ICESTORM_DSP || cell->type == id_ICESTORM_SPRAM) {
clockPort = id_CLK;
if (port == id_CLK)
if (port == id_CLK || port == id_CLOCK)
return TMG_CLOCK_INPUT;
else if (cell->ports.at(port).type == PORT_OUT)
return TMG_REGISTER_OUTPUT;
else
return TMG_REGISTER_INPUT;
else {
clockInfoCount = 1;
if (cell->ports.at(port).type == PORT_OUT)
return TMG_REGISTER_OUTPUT;
else
return TMG_REGISTER_INPUT;
}
} else if (cell->type == id_SB_IO) {
if (port == id_D_IN_0 || port == id_D_IN_1)
return TMG_STARTPOINT;
@ -927,10 +946,59 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id
return TMG_COMB_INPUT;
} else if (cell->type == id_SB_WARMBOOT) {
return TMG_ENDPOINT;
} else if (cell->type == id_SB_RGBA_DRV) {
if (port == id_RGB0 || port == id_RGB1 || port == id_RGB2)
return TMG_IGNORE;
return TMG_ENDPOINT;
}
log_error("no timing info for port '%s' of cell type '%s'\n", port.c_str(this), cell->type.c_str(this));
}
TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port, int index) const
{
TimingClockingInfo info;
if (cell->type == id_ICESTORM_LC) {
info.clock_port = id_CLK;
info.edge = cell->lcInfo.negClk ? FALLING_EDGE : RISING_EDGE;
if (port == id_O) {
bool has_clktoq = getCellDelay(cell, id_CLK, id_O, info.clockToQ);
NPNR_ASSERT(has_clktoq);
} else {
info.setup.delay = 100;
info.hold.delay = 0;
}
} else if (cell->type == id_ICESTORM_RAM) {
if (port.str(this)[0] == 'R') {
info.clock_port = id_RCLK;
info.edge = bool_or_default(cell->params, id("NEG_CLK_R")) ? FALLING_EDGE : RISING_EDGE;
} else {
info.clock_port = id_WCLK;
info.edge = bool_or_default(cell->params, id("NEG_CLK_W")) ? FALLING_EDGE : RISING_EDGE;
}
if (cell->ports.at(port).type == PORT_OUT) {
bool has_clktoq = getCellDelay(cell, info.clock_port, port, info.clockToQ);
NPNR_ASSERT(has_clktoq);
} else {
info.setup.delay = 100;
info.hold.delay = 0;
}
} else if (cell->type == id_ICESTORM_DSP || cell->type == id_ICESTORM_SPRAM) {
info.clock_port = cell->type == id_ICESTORM_SPRAM ? id_CLOCK : id_CLK;
info.edge = RISING_EDGE;
if (cell->ports.at(port).type == PORT_OUT) {
bool has_clktoq = getCellDelay(cell, info.clock_port, port, info.clockToQ);
if (!has_clktoq)
info.clockToQ.delay = 100;
} else {
info.setup.delay = 100;
info.hold.delay = 0;
}
} else {
NPNR_ASSERT_FALSE("unhandled cell type in getPortClockingInfo");
}
return info;
}
bool Arch::isGlobalNet(const NetInfo *net) const
{
if (net == nullptr)
@ -980,6 +1048,9 @@ void Arch::assignCellInfo(CellInfo *cell)
cell->lcInfo.inputCount++;
} else if (cell->type == id_SB_IO) {
cell->ioInfo.lvds = str_or_default(cell->params, id_IO_STANDARD, "SB_LVCMOS") == "SB_LVDS_INPUT";
cell->ioInfo.global = bool_or_default(cell->attrs, this->id("GLOBAL"));
} else if (cell->type == id_SB_GB) {
cell->gbInfo.forPadIn = bool_or_default(cell->attrs, this->id("FOR_PAD_IN"));
}
}

View File

@ -212,11 +212,26 @@ NPNR_PACKED_STRUCT(struct CellTimingPOD {
RelPtr<CellPathDelayPOD> 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<BelInfoPOD> bel_data;
RelPtr<WireInfoPOD> wire_data;
RelPtr<PipInfoPOD> pip_data;
@ -225,6 +240,7 @@ NPNR_PACKED_STRUCT(struct ChipInfoPOD {
RelPtr<BelConfigPOD> bel_config;
RelPtr<PackageInfoPOD> packages_data;
RelPtr<CellTimingPOD> cell_timing;
RelPtr<GlobalNetworkInfoPOD> global_network_info;
RelPtr<RelPtr<char>> tile_wire_names;
});
@ -510,6 +526,8 @@ struct Arch : BaseCtx
PortType getBelPinType(BelId bel, IdString pin) const;
std::vector<IdString> getBelPins(BelId bel) const;
bool isBelLocked(BelId bel) const;
// -------------------------------------------------
WireId getWireByName(IdString name) const;
@ -796,6 +814,12 @@ struct Arch : BaseCtx
delay_t getDelayEpsilon() const { return 20; }
delay_t getRipupDelayPenalty() const { return 200; }
float getDelayNS(delay_t v) const { return v * 0.001; }
DelayInfo getDelayFromNS(float ns) const
{
DelayInfo del;
del.delay = delay_t(ns * 1000);
return del;
}
uint32_t getDelayChecksum(delay_t v) const { return v; }
bool getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const;
@ -819,8 +843,10 @@ struct Arch : BaseCtx
// Get the delay through a cell from one port to another, returning false
// if no path exists
bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const;
// Get the port class, also setting clockDomain if applicable
TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockDomain) const;
// Get the port class, also setting clockInfoCount to the number of TimingClockingInfos associated with a port
TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const;
// Get the TimingClockingInfo of a port
TimingClockingInfo getPortClockingInfo(const CellInfo *cell, IdString port, int index) const;
// Return true if a port is a net
bool isGlobalNet(const NetInfo *net) const;

View File

@ -114,31 +114,30 @@ bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const
// Find shared PLL by looking for driving bel siblings from D_IN_0
// that are a PLL clock output.
auto wire = getBelPinWire(bel, id_D_IN_0);
IdString pll_bel_pin;
BelId pll_bel;
for (auto pin : getWireBelPins(wire)) {
if (pin.pin == id_PLLOUT_A || pin.pin == id_PLLOUT_B) {
pll_bel = pin.bel;
pll_bel_pin = pin.pin;
break;
}
}
// Is there a PLL that shares this IO buffer?
if (pll_bel.index != -1) {
auto pll_cell = getBoundBelCell(pll_bel);
// Is a PLL placed in this PLL bel?
if (pll_cell != nullptr) {
// Is the shared port driving a net?
auto pi = pll_cell->ports[pll_bel_pin];
if (pi.net != nullptr) {
// Are we perhaps a PAD INPUT Bel that can be placed here?
if (pll_cell->attrs[id("BEL_PAD_INPUT")] == getBelName(bel).str(this)) {
return true;
}
return false;
}
// Is there a PLL there ?
auto pll_cell = getBoundBelCell(pin.bel);
if (pll_cell == nullptr)
break;
// Is that port actually used ?
if ((pin.pin == id_PLLOUT_B) && !is_sb_pll40_dual(this, pll_cell))
break;
// Is that SB_IO used at an input ?
if ((cell->ports[id_D_IN_0].net == nullptr) && (cell->ports[id_D_IN_1].net == nullptr))
break;
// Are we perhaps a PAD INPUT Bel that can be placed here?
if (pll_cell->attrs[id("BEL_PAD_INPUT")] == getBelName(bel).str(this))
return true;
// Conflict
return false;
}
}
Loc ioLoc = getBelLocation(bel);
Loc compLoc = ioLoc;
compLoc.z = 1 - compLoc.z;
@ -162,6 +161,8 @@ bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const
return getBelPackagePin(bel) != "";
} else if (cell->type == id_SB_GB) {
if (cell->gbInfo.forPadIn)
return true;
NPNR_ASSERT(cell->ports.at(id_GLOBAL_BUFFER_OUTPUT).net != nullptr);
const NetInfo *net = cell->ports.at(id_GLOBAL_BUFFER_OUTPUT).net;
IdString glb_net = getWireName(getBelPinWire(bel, id_GLOBAL_BUFFER_OUTPUT));

View File

@ -140,6 +140,10 @@ void arch_wrap_python()
"cells");
readonly_wrapper<Context, decltype(&Context::nets), &Context::nets, wrap_context<NetMap &>>::def_wrap(ctx_cls,
"nets");
fn_wrapper_2a_v<Context, decltype(&Context::addClock), &Context::addClock, conv_from_str<IdString>,
pass_through<float>>::def_wrap(ctx_cls, "addClock");
WRAP_RANGE(Bel, conv_to_str<BelId>);
WRAP_RANGE(Wire, conv_to_str<WireId>);
WRAP_RANGE(AllPip, conv_to_str<PipId>);

View File

@ -150,8 +150,13 @@ struct ArchCellInfo
struct
{
bool lvds;
bool global;
// TODO: clk packing checks...
} ioInfo;
struct
{
bool forPadIn;
} gbInfo;
};
};

File diff suppressed because it is too large Load Diff

View File

@ -219,7 +219,7 @@ std::unique_ptr<CellInfo> create_ice_cell(Context *ctx, IdString type, std::stri
new_cell->params[ctx->id("FDA_FEEDBACK")] = "0";
new_cell->params[ctx->id("FDA_RELATIVE")] = "0";
new_cell->params[ctx->id("FEEDBACK_PATH")] = "0";
new_cell->params[ctx->id("FEEDBACK_PATH")] = "1";
new_cell->params[ctx->id("FILTER_RANGE")] = "0";
new_cell->params[ctx->id("PLLOUT_SELECT_A")] = "0";
@ -244,8 +244,22 @@ std::unique_ptr<CellInfo> create_ice_cell(Context *ctx, IdString type, std::stri
add_port(ctx, new_cell.get(), "LOCK", PORT_OUT);
add_port(ctx, new_cell.get(), "PLLOUT_A", PORT_OUT);
add_port(ctx, new_cell.get(), "PLLOUT_B", PORT_OUT);
add_port(ctx, new_cell.get(), "PLLOUTGLOBALA", PORT_OUT);
add_port(ctx, new_cell.get(), "PLLOUTGLOBALB", PORT_OUT);
add_port(ctx, new_cell.get(), "PLLOUT_A_GLOBAL", PORT_OUT);
add_port(ctx, new_cell.get(), "PLLOUT_B_GLOBAL", PORT_OUT);
} else if (type == ctx->id("SB_RGBA_DRV")) {
new_cell->params[ctx->id("CURRENT_MODE")] = "0b0";
new_cell->params[ctx->id("RGB0_CURRENT")] = "0b000000";
new_cell->params[ctx->id("RGB1_CURRENT")] = "0b000000";
new_cell->params[ctx->id("RGB2_CURRENT")] = "0b000000";
add_port(ctx, new_cell.get(), "CURREN", PORT_IN);
add_port(ctx, new_cell.get(), "RGBLEDEN", PORT_IN);
add_port(ctx, new_cell.get(), "RGB0PWM", PORT_IN);
add_port(ctx, new_cell.get(), "RGB1PWM", PORT_IN);
add_port(ctx, new_cell.get(), "RGB2PWM", PORT_IN);
add_port(ctx, new_cell.get(), "RGB0", PORT_OUT);
add_port(ctx, new_cell.get(), "RGB1", PORT_OUT);
add_port(ctx, new_cell.get(), "RGB2", PORT_OUT);
} else {
log_error("unable to create iCE40 cell of type %s", type.c_str(ctx));
}
@ -362,7 +376,7 @@ uint8_t sb_pll40_type(const BaseCtx *ctx, const CellInfo *cell)
if (cell->type == ctx->id("SB_PLL40_2_PAD"))
return 4;
if (cell->type == ctx->id("SB_PLL40_2F_PAD"))
return 5;
return 6;
if (cell->type == ctx->id("SB_PLL40_CORE"))
return 3;
if (cell->type == ctx->id("SB_PLL40_2F_CORE"))

View File

@ -53,6 +53,9 @@ inline bool is_lc(const BaseCtx *ctx, const CellInfo *cell) { return cell->type
// Return true if a cell is a SB_IO
inline bool is_sb_io(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_IO"); }
// Return true if a cell is a SB_GB_IO
inline bool is_sb_gb_io(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_GB_IO"); }
// Return true if a cell is a global buffer
inline bool is_gbuf(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_GB"); }
@ -71,6 +74,8 @@ inline bool is_sb_spram(const BaseCtx *ctx, const CellInfo *cell) { return cell-
inline bool is_sb_mac16(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_MAC16"); }
inline bool is_sb_rgba_drv(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_RGBA_DRV"); }
inline bool is_sb_pll40(const BaseCtx *ctx, const CellInfo *cell)
{
return cell->type == ctx->id("SB_PLL40_PAD") || cell->type == ctx->id("SB_PLL40_2_PAD") ||
@ -81,7 +86,19 @@ inline bool is_sb_pll40(const BaseCtx *ctx, const CellInfo *cell)
inline bool is_sb_pll40_pad(const BaseCtx *ctx, const CellInfo *cell)
{
return cell->type == ctx->id("SB_PLL40_PAD") || cell->type == ctx->id("SB_PLL40_2_PAD") ||
cell->type == ctx->id("SB_PLL40_2F_PAD");
cell->type == ctx->id("SB_PLL40_2F_PAD") ||
(cell->type == ctx->id("ICESTORM_PLL") &&
(cell->attrs.at(ctx->id("TYPE")) == "SB_PLL40_PAD" || cell->attrs.at(ctx->id("TYPE")) == "SB_PLL40_2_PAD" ||
cell->attrs.at(ctx->id("TYPE")) == "SB_PLL40_2F_PAD"));
}
inline bool is_sb_pll40_dual(const BaseCtx *ctx, const CellInfo *cell)
{
return cell->type == ctx->id("SB_PLL40_2_PAD") || cell->type == ctx->id("SB_PLL40_2F_PAD") ||
cell->type == ctx->id("SB_PLL40_2F_CORE") ||
(cell->type == ctx->id("ICESTORM_PLL") && (cell->attrs.at(ctx->id("TYPE")) == "SB_PLL40_2_PAD" ||
cell->attrs.at(ctx->id("TYPE")) == "SB_PLL40_2F_PAD" ||
cell->attrs.at(ctx->id("TYPE")) == "SB_PLL40_2F_CORE"));
}
uint8_t sb_pll40_type(const BaseCtx *ctx, const CellInfo *cell);

View File

@ -36,6 +36,7 @@ ierens = list()
extra_cells = dict()
extra_cell_config = dict()
packages = list()
glbinfo = dict([(i, {}) for i in range(8)])
wire_belports = dict()
@ -640,6 +641,18 @@ with open(args.filename, "r") as f:
extra_cells[mode[1]] = []
continue
if line[0] == ".gbufin":
mode = ("gbufin",)
continue
if line[0] == ".gbufpin":
mode = ("gbufpin",)
continue
if line[0] == ".extra_bits":
mode = ("extra_bits",)
continue
if (line[0][0] == ".") or (mode is None):
mode = None
continue
@ -692,11 +705,33 @@ with open(args.filename, "r") as f:
if mode[0] == "extra_cell":
if line[0] == "LOCKED":
extra_cells[mode[1]].append((("LOCKED_" + line[1]), (0, 0, "LOCKED")))
for pkg in line[1:]:
extra_cells[mode[1]].append((("LOCKED_" + pkg), (0, 0, "LOCKED")))
else:
extra_cells[mode[1]].append((line[0], (int(line[1]), int(line[2]), line[3])))
continue
if mode[0] == "gbufin":
idx = int(line[2])
glbinfo[idx]['gb_x'] = int(line[0])
glbinfo[idx]['gb_y'] = int(line[1])
continue
if mode[0] == "gbufpin":
idx = int(line[3])
glbinfo[idx]['pi_gb_x'] = int(line[0])
glbinfo[idx]['pi_gb_y'] = int(line[1])
glbinfo[idx]['pi_gb_pio'] = int(line[2])
continue
if mode[0] == "extra_bits":
if line[0].startswith('padin_glb_netwk.'):
idx = int(line[0].split('.')[1])
glbinfo[idx]['pi_eb_bank'] = int(line[1])
glbinfo[idx]['pi_eb_x'] = int(line[2])
glbinfo[idx]['pi_eb_y'] = int(line[3])
continue
def add_wire(x, y, name):
global num_wires
wire_idx = num_wires
@ -828,6 +863,10 @@ def add_bel_io(x, y, z):
add_bel_input(bel, wire_dout_1, "D_OUT_1")
add_bel_input(bel, wire_out_en, "OUTPUT_ENABLE")
for gidx, ginfo in glbinfo.items():
if (ginfo['pi_gb_x'], ginfo['pi_gb_y'], ginfo['pi_gb_pio']) == (x,y,z):
add_bel_output(bel, wire_names[(x, y, "glb_netwk_%d" % gidx)], "GLOBAL_BUFFER_OUTPUT")
def add_bel_ram(x, y):
bel = len(bel_name)
bel_name.append("X%d/Y%d/ram" % (x, y))
@ -885,6 +924,18 @@ def is_ec_output(ec_entry):
def is_ec_pll_clock_output(ec, ec_entry):
return ec[0] == 'PLL' and ec_entry[0] in ('PLLOUT_A', 'PLLOUT_B')
def add_pll_clock_output(bel, ec, entry):
# Fabric output
io_x, io_y, io_z = entry[1]
io_zs = 'io_{}/D_IN_0'.format(io_z)
io_z = int(io_z)
add_bel_output(bel, wire_names[(io_x, io_y, io_zs)], entry[0])
# Global output
for gidx, ginfo in glbinfo.items():
if (ginfo['pi_gb_x'], ginfo['pi_gb_y'], ginfo['pi_gb_pio']) == (io_x, io_y, io_z):
add_bel_output(bel, wire_names[(io_x, io_y, "glb_netwk_%d" % gidx)], entry[0] + '_GLOBAL')
def add_bel_ec(ec):
ectype, x, y, z = ec
bel = len(bel_name)
@ -894,15 +945,13 @@ def add_bel_ec(ec):
bel_pos.append((x, y, z))
bel_wires.append(list())
for entry in extra_cells[ec]:
if is_ec_wire(entry) and "glb_netwk_" not in entry[1][2]: # TODO: osc glb output conflicts with GB
if is_ec_wire(entry):
if is_ec_output(entry):
add_bel_output(bel, wire_names[entry[1]], entry[0])
else:
add_bel_input(bel, wire_names[entry[1]], entry[0])
elif is_ec_pll_clock_output(ec, entry):
x, y, z = entry[1]
z = 'io_{}/D_IN_0'.format(z)
add_bel_output(bel, wire_names[(x, y, z)], entry[0])
add_pll_clock_output(bel, ec, entry)
else:
extra_cell_config[bel].append(entry)
@ -973,42 +1022,8 @@ for tile_xy, tile_type in sorted(tiles.items()):
for i in range(2):
add_bel_io(tile_xy[0], tile_xy[1], i)
if dev_name == "1k":
add_bel_gb(tile_xy, 7, 0, 0)
add_bel_gb(tile_xy, 7, 17, 1)
add_bel_gb(tile_xy, 13, 9, 2)
add_bel_gb(tile_xy, 0, 9, 3)
add_bel_gb(tile_xy, 6, 17, 4)
add_bel_gb(tile_xy, 6, 0, 5)
add_bel_gb(tile_xy, 0, 8, 6)
add_bel_gb(tile_xy, 13, 8, 7)
elif dev_name == "5k":
add_bel_gb(tile_xy, 13, 0, 0)
add_bel_gb(tile_xy, 13, 31, 1)
add_bel_gb(tile_xy, 19, 31, 2)
add_bel_gb(tile_xy, 6, 31, 3)
add_bel_gb(tile_xy, 12, 31, 4)
add_bel_gb(tile_xy, 12, 0, 5)
add_bel_gb(tile_xy, 6, 0, 6)
add_bel_gb(tile_xy, 19, 0, 7)
elif dev_name == "8k":
add_bel_gb(tile_xy, 33, 16, 7)
add_bel_gb(tile_xy, 0, 16, 6)
add_bel_gb(tile_xy, 17, 33, 1)
add_bel_gb(tile_xy, 17, 0, 0)
add_bel_gb(tile_xy, 0, 17, 3)
add_bel_gb(tile_xy, 33, 17, 2)
add_bel_gb(tile_xy, 16, 0, 5)
add_bel_gb(tile_xy, 16, 33, 4)
elif dev_name == "384":
add_bel_gb(tile_xy, 7, 4, 7)
add_bel_gb(tile_xy, 0, 4, 6)
add_bel_gb(tile_xy, 4, 9, 1)
add_bel_gb(tile_xy, 4, 0, 0)
add_bel_gb(tile_xy, 0, 5, 3)
add_bel_gb(tile_xy, 7, 5, 2)
add_bel_gb(tile_xy, 3, 0, 5)
add_bel_gb(tile_xy, 3, 9, 4)
for gidx, ginfo in glbinfo.items():
add_bel_gb(tile_xy, ginfo['gb_x'], ginfo['gb_y'], gidx)
if tile_type == "ramb":
add_bel_ram(tile_xy[0], tile_xy[1])
@ -1423,6 +1438,14 @@ for cell, timings in sorted(cell_timings.items()):
bba.u32(len(timings), "num_paths")
bba.r("cell_paths_%d" % beltype, "path_delays")
bba.l("global_network_info_%s" % dev_name, "GlobalNetworkInfoPOD")
for i in range(len(glbinfo)):
for k in ['gb_x', 'gb_y', 'pi_gb_x', 'pi_gb_y', 'pi_gb_pio', 'pi_eb_bank']:
bba.u8(glbinfo[i][k], k)
for k in ['pi_eb_x', 'pi_eb_y']:
bba.u16(glbinfo[i][k], k)
bba.u16(0, "padding")
bba.l("chip_info_%s" % dev_name)
bba.u32(dev_width, "dev_width")
bba.u32(dev_height, "dev_height")
@ -1433,6 +1456,7 @@ bba.u32(len(switchinfo), "num_switches")
bba.u32(len(extra_cell_config), "num_belcfgs")
bba.u32(len(packageinfo), "num_packages")
bba.u32(len(cell_timings), "num_timing_cells")
bba.u32(len(glbinfo), "num_global_networks")
bba.r("bel_data_%s" % dev_name, "bel_data")
bba.r("wire_data_%s" % dev_name, "wire_data")
bba.r("pip_data_%s" % dev_name, "pip_data")
@ -1441,6 +1465,7 @@ bba.r("bits_info_%s" % dev_name, "bits_info")
bba.r("bel_config_%s" % dev_name if len(extra_cell_config) > 0 else None, "bel_config")
bba.r("package_info_%s" % dev_name, "packages_data")
bba.r("cell_timings_%s" % dev_name, "cell_timing")
bba.r("global_network_info_%s" % dev_name, "global_network_info")
bba.r("tile_wire_names", "tile_wire_names")
bba.pop()

View File

@ -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)

View File

@ -381,12 +381,44 @@ static void pack_constants(Context *ctx)
}
}
static std::unique_ptr<CellInfo> 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<CellInfo> gb = create_ice_cell(ctx, ctx->id("SB_GB"), gbuf_name);
gb->attrs[ctx->id("FOR_PAD_IN")] = "1";
gb->attrs[ctx->id("BEL")] = ctx->getBelName(gb_bel).str(ctx);
// Reconnect the net to that port for easier identification it's a global net
replace_port(cell, port_name, gb.get(), id_GLOBAL_BUFFER_OUTPUT);
return gb;
}
static bool is_nextpnr_iob(Context *ctx, CellInfo *cell)
{
return cell->type == ctx->id("$nextpnr_ibuf") || cell->type == ctx->id("$nextpnr_obuf") ||
cell->type == ctx->id("$nextpnr_iobuf");
}
static bool is_ice_iob(const Context *ctx, const CellInfo *cell)
{
return is_sb_io(ctx, cell) || is_sb_gb_io(ctx, cell);
}
// Pack IO buffers
static void pack_io(Context *ctx)
{
@ -399,12 +431,15 @@ static void pack_io(Context *ctx)
for (auto cell : sorted(ctx->cells)) {
CellInfo *ci = cell.second;
if (is_nextpnr_iob(ctx, ci)) {
CellInfo *sb = nullptr;
CellInfo *sb = nullptr, *rgb = nullptr;
if (ci->type == ctx->id("$nextpnr_ibuf") || ci->type == ctx->id("$nextpnr_iobuf")) {
sb = net_only_drives(ctx, ci->ports.at(ctx->id("O")).net, is_sb_io, ctx->id("PACKAGE_PIN"), true, ci);
sb = net_only_drives(ctx, ci->ports.at(ctx->id("O")).net, is_ice_iob, ctx->id("PACKAGE_PIN"), true, ci);
} else if (ci->type == ctx->id("$nextpnr_obuf")) {
sb = net_only_drives(ctx, ci->ports.at(ctx->id("I")).net, is_sb_io, ctx->id("PACKAGE_PIN"), true, ci);
NetInfo *net = ci->ports.at(ctx->id("I")).net;
sb = net_only_drives(ctx, net, is_ice_iob, ctx->id("PACKAGE_PIN"), true, ci);
if (net && net->driver.cell && is_sb_rgba_drv(ctx, net->driver.cell))
rgb = net->driver.cell;
}
if (sb != nullptr) {
// Trivial case, SB_IO used. Just destroy the net and the
@ -415,8 +450,8 @@ static void pack_io(Context *ctx)
if (((ci->type == ctx->id("$nextpnr_ibuf") || ci->type == ctx->id("$nextpnr_iobuf")) &&
net->users.size() > 1) ||
(ci->type == ctx->id("$nextpnr_obuf") && (net->users.size() > 2 || net->driver.cell != nullptr)))
log_error("PACKAGE_PIN of SB_IO '%s' connected to more than a single top level IO.\n",
sb->name.c_str(ctx));
log_error("PACKAGE_PIN of %s '%s' connected to more than a single top level IO.\n",
sb->type.c_str(ctx), sb->name.c_str(ctx));
if (net != nullptr) {
delete_nets.insert(net->name);
@ -428,6 +463,11 @@ static void pack_io(Context *ctx)
delete_nets.insert(net2->name);
}
}
} else if (rgb != nullptr) {
log_info("%s use by SB_RGBA_DRV %s, not creating SB_IO\n", ci->name.c_str(ctx), rgb->name.c_str(ctx));
disconnect_port(ctx, ci, ctx->id("I"));
packed_cells.insert(ci->name);
continue;
} else {
// Create a SB_IO buffer
std::unique_ptr<CellInfo> ice_cell =
@ -438,6 +478,24 @@ static void pack_io(Context *ctx)
}
packed_cells.insert(ci->name);
std::copy(ci->attrs.begin(), ci->attrs.end(), std::inserter(sb->attrs, sb->attrs.begin()));
} else if (is_sb_io(ctx, ci) || is_sb_gb_io(ctx, ci)) {
NetInfo *net = ci->ports.at(ctx->id("PACKAGE_PIN")).net;
if ((net != nullptr) && (net->users.size() > 1))
log_error("PACKAGE_PIN of %s '%s' connected to more than a single top level IO.\n", ci->type.c_str(ctx),
ci->name.c_str(ctx));
}
}
for (auto cell : sorted(ctx->cells)) {
CellInfo *ci = cell.second;
if (is_sb_gb_io(ctx, ci)) {
// If something is connecto the GLOBAL OUTPUT, create the fake 'matching' SB_GB
std::unique_ptr<CellInfo> gb =
create_padin_gbuf(ctx, ci, id_GLOBAL_BUFFER_OUTPUT, "$gbuf_" + ci->name.str(ctx) + "_io");
new_cells.push_back(std::move(gb));
// Make it a normal SB_IO with global marker
ci->type = ctx->id("SB_IO");
ci->attrs[ctx->id("GLOBAL")] = "1";
}
}
for (auto pcell : packed_cells) {
@ -456,8 +514,8 @@ static bool is_logic_port(BaseCtx *ctx, const PortRef &port)
{
if (is_clock_port(ctx, port) || is_reset_port(ctx, port) || is_enable_port(ctx, port))
return false;
return !is_sb_io(ctx, port.cell) && !is_sb_pll40(ctx, port.cell) && !is_sb_pll40_pad(ctx, port.cell) &&
port.cell->type != ctx->id("SB_GB");
return !is_sb_io(ctx, port.cell) && !is_sb_gb_io(ctx, port.cell) && !is_gbuf(ctx, port.cell) &&
!is_sb_pll40(ctx, port.cell);
}
static void insert_global(Context *ctx, NetInfo *net, bool is_reset, bool is_cen, bool is_logic)
@ -490,6 +548,14 @@ static void insert_global(Context *ctx, NetInfo *net, bool is_reset, bool is_cen
}
}
net->users = keep_users;
if (net->clkconstr) {
glbnet->clkconstr = std::unique_ptr<ClockConstraint>(new ClockConstraint());
glbnet->clkconstr->low = net->clkconstr->low;
glbnet->clkconstr->high = net->clkconstr->high;
glbnet->clkconstr->period = net->clkconstr->period;
}
ctx->nets[glbnet->name] = std::move(glbnet);
ctx->cells[gb->name] = std::move(gb);
}
@ -637,6 +703,22 @@ static std::unique_ptr<CellInfo> spliceLUT(Context *ctx, CellInfo *ci, IdString
return pt;
}
// Force placement for cells that are unique anyway
static BelId cell_place_unique(Context *ctx, CellInfo *ci)
{
for (auto bel : ctx->getBels()) {
if (ctx->getBelType(bel) != ci->type)
continue;
if (ctx->isBelLocked(bel))
continue;
IdString bel_name = ctx->getBelName(bel);
ci->attrs[ctx->id("BEL")] = bel_name.str(ctx);
log_info(" constrained %s '%s' to %s\n", ci->type.c_str(ctx), ci->name.c_str(ctx), bel_name.c_str(ctx));
return bel;
}
log_error("Unable to place cell '%s' of type '%s'\n", ci->name.c_str(ctx), ci->type.c_str(ctx));
}
// Pack special functions
static void pack_special(Context *ctx)
{
@ -651,25 +733,33 @@ static void pack_special(Context *ctx)
std::unique_ptr<CellInfo> 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<CellInfo> 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<CellInfo> 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<CellInfo> gb =
create_padin_gbuf(ctx, packed.get(), ctx->id("CLKHF"), "$gbuf_" + ci->name.str(ctx) + "_hfosc");
new_cells.push_back(std::move(gb));
}
new_cells.push_back(std::move(packed));
} else if (is_sb_spram(ctx, ci)) {
@ -705,6 +795,29 @@ static void pack_special(Context *ctx)
replace_port(ci, ctx->id(pi.name.c_str(ctx)), packed.get(), ctx->id(newname));
}
new_cells.push_back(std::move(packed));
} else if (is_sb_rgba_drv(ctx, ci)) {
/* Force placement (no choices anyway) */
cell_place_unique(ctx, ci);
/* Disconnect all external ports and check there is no users (they should have been
* dealth with during IO packing */
for (auto port : ci->ports) {
PortInfo &pi = port.second;
NetInfo *net = pi.net;
if (net == nullptr)
continue;
if ((pi.name != ctx->id("RGB0")) && (pi.name != ctx->id("RGB1")) && (pi.name != ctx->id("RGB2")))
continue;
if (net->users.size() > 0)
log_error("SB_RGBA_DRV port connected to more than just package pin !\n");
ctx->nets.erase(net->name);
}
ci->ports.erase(ctx->id("RGB0"));
ci->ports.erase(ctx->id("RGB1"));
ci->ports.erase(ctx->id("RGB2"));
} else if (is_sb_pll40(ctx, ci)) {
bool is_pad = is_sb_pll40_pad(ctx, ci);
bool is_core = !is_pad;
@ -719,6 +832,24 @@ static void pack_special(Context *ctx)
for (auto param : ci->params)
packed->params[param.first] = param.second;
const std::map<IdString, IdString> 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<std::string, int> pos_map_val = {
{"GENCLK", 0},
{"GENCLK_HALF", 1},
{"SHIFTREG_90deg", 2},
{"SHIFTREG_0deg", 3},
};
for (auto param : ci->params)
if (pos_map_name.find(param.first) != pos_map_name.end()) {
if (pos_map_val.find(param.second) == pos_map_val.end())
log_error("Invalid PLL output selection '%s'\n", param.second.c_str());
packed->params[pos_map_name.at(param.first)] = std::to_string(pos_map_val.at(param.second));
}
auto feedback_path = packed->params[ctx->id("FEEDBACK_PATH")];
packed->params[ctx->id("FEEDBACK_PATH")] =
feedback_path == "DELAY"
@ -731,32 +862,6 @@ static void pack_special(Context *ctx)
NetInfo *pad_packagepin_net = nullptr;
int pllout_a_used = 0;
int pllout_b_used = 0;
for (auto port : ci->ports) {
PortInfo &pi = port.second;
if (pi.name == ctx->id("PLLOUTCOREA"))
pllout_a_used++;
if (pi.name == ctx->id("PLLOUTCOREB"))
pllout_b_used++;
if (pi.name == ctx->id("PLLOUTCORE"))
pllout_a_used++;
if (pi.name == ctx->id("PLLOUTGLOBALA"))
pllout_a_used++;
if (pi.name == ctx->id("PLLOUTGLOBALB"))
pllout_b_used++;
if (pi.name == ctx->id("PLLOUTGLOBAL"))
pllout_a_used++;
}
if (pllout_a_used > 1)
log_error("PLL '%s' is using multiple ports mapping to PLLOUT_A output of the PLL\n",
ci->name.c_str(ctx));
if (pllout_b_used > 1)
log_error("PLL '%s' is using multiple ports mapping to PLLOUT_B output of the PLL\n",
ci->name.c_str(ctx));
for (auto port : ci->ports) {
PortInfo &pi = port.second;
std::string newname = pi.name.str(ctx);
@ -764,24 +869,15 @@ static void pack_special(Context *ctx)
if (bpos != std::string::npos) {
newname = newname.substr(0, bpos) + "_" + newname.substr(bpos + 1, (newname.size() - bpos) - 2);
}
if (pi.name == ctx->id("PLLOUTCOREA"))
if (pi.name == ctx->id("PLLOUTCOREA") || pi.name == ctx->id("PLLOUTCORE"))
newname = "PLLOUT_A";
if (pi.name == ctx->id("PLLOUTCOREB"))
newname = "PLLOUT_B";
if (pi.name == ctx->id("PLLOUTCORE"))
newname = "PLLOUT_A";
if (pi.name == ctx->id("PLLOUTGLOBALA"))
newname = "PLLOUT_A";
if (pi.name == ctx->id("PLLOUTGLOBALA") || pi.name == ctx->id("PLLOUTGLOBALA"))
newname = "PLLOUT_A_GLOBAL";
if (pi.name == ctx->id("PLLOUTGLOBALB"))
newname = "PLLOUT_B";
if (pi.name == ctx->id("PLLOUTGLOBAL"))
newname = "PLLOUT_A";
if (pi.name == ctx->id("PLLOUTGLOBALA") || pi.name == ctx->id("PLLOUTGLOBALB") ||
pi.name == ctx->id("PLLOUTGLOBAL"))
log_warning("PLL '%s' is using port %s but implementation does not actually "
"use the global clock output of the PLL\n",
ci->name.c_str(ctx), pi.name.str(ctx).c_str());
newname = "PLLOUT_B_GLOBAL";
if (pi.name == ctx->id("PACKAGEPIN")) {
if (!is_pad) {
@ -825,6 +921,8 @@ static void pack_special(Context *ctx)
for (auto bel : ctx->getBels()) {
if (ctx->getBelType(bel) != id_ICESTORM_PLL)
continue;
if (ctx->isBelLocked(bel))
continue;
// A PAD PLL must have its' PACKAGEPIN on the SB_IO that's shared
// with PLLOUT_A.
@ -866,11 +964,22 @@ static void pack_special(Context *ctx)
packed->attrs[ctx->id("BEL")] = ctx->getBelName(bel).str(ctx);
pll_bel = bel;
constrained = true;
break;
}
if (!constrained) {
log_error("Could not constrain PLL '%s' to any PLL Bel (too many PLLs?)\n",
packed->name.c_str(ctx));
}
} else {
pll_bel = ctx->getBelByName(ctx->id(packed->attrs[ctx->id("BEL")]));
if (ctx->getBelType(pll_bel) != id_ICESTORM_PLL)
log_error("PLL '%s' is constrained to BEL %s which isn't a ICESTORM_PLL BEL\n",
packed->name.c_str(ctx), ctx->getBelName(pll_bel).c_str(ctx));
if (ctx->isBelLocked(pll_bel))
log_error("PLL '%s' is constrained to locked BEL %s\n", packed->name.c_str(ctx),
ctx->getBelName(pll_bel).c_str(ctx));
log_info(" constrained PLL '%s' to %s\n", packed->name.c_str(ctx),
ctx->getBelName(pll_bel).c_str(ctx));
}
// Delete the original PACKAGEPIN net if needed.
@ -939,6 +1048,24 @@ static void pack_special(Context *ctx)
}
}
// Handle the global buffer connections
for (auto port : packed->ports) {
PortInfo &pi = port.second;
bool is_b_port;
if (pi.name == ctx->id("PLLOUT_A_GLOBAL"))
is_b_port = false;
else if (pi.name == ctx->id("PLLOUT_B_GLOBAL"))
is_b_port = true;
else
continue;
std::unique_ptr<CellInfo> gb =
create_padin_gbuf(ctx, packed.get(), pi.name,
"$gbuf_" + ci->name.str(ctx) + "_pllout_" + (is_b_port ? "b" : "a"));
new_cells.push_back(std::move(gb));
}
new_cells.push_back(std::move(packed));
}
}
@ -958,13 +1085,13 @@ bool Arch::pack()
try {
log_break();
pack_constants(ctx);
promote_globals(ctx);
pack_io(ctx);
pack_lut_lutffs(ctx);
pack_nonlut_ffs(ctx);
pack_carries(ctx);
pack_ram(ctx);
pack_special(ctx);
promote_globals(ctx);
ctx->assignArchInfo();
constrain_chains(ctx);
ctx->assignArchInfo();