Merge branch 'master' into xc7
This commit is contained in:
commit
75b48dfe1e
@ -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) {
|
||||
|
@ -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
|
||||
|
121
common/nextpnr.h
121
common/nextpnr.h
@ -194,6 +194,14 @@ struct Loc
|
||||
bool operator!=(const Loc &other) const { return (x != other.x) || (y != other.y) || (z != other.z); }
|
||||
};
|
||||
|
||||
struct TimingConstrObjectId
|
||||
{
|
||||
int32_t index = -1;
|
||||
|
||||
bool operator==(const TimingConstrObjectId &other) const { return index == other.index; }
|
||||
bool operator!=(const TimingConstrObjectId &other) const { return index != other.index; }
|
||||
};
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
||||
|
||||
namespace std {
|
||||
@ -208,6 +216,15 @@ template <> struct hash<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
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -28,6 +28,7 @@ struct Placer1Cfg : public Settings
|
||||
{
|
||||
Placer1Cfg(Context *ctx);
|
||||
float constraintWeight;
|
||||
int minBelsForGridPick;
|
||||
};
|
||||
|
||||
extern bool placer1(Context *ctx, Placer1Cfg cfg);
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
558
common/timing.cc
558
common/timing.cc
@ -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;
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
37
docs/constraints.md
Normal 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)
|
||||
|
105
ecp5/arch.cc
105
ecp5/arch.cc
@ -539,10 +539,10 @@ bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort
|
||||
return true;
|
||||
}
|
||||
#if 0 // FIXME
|
||||
if (fromPort == id_WCK && (toPort == id_F0 || toPort == id_F1)) {
|
||||
delay.delay = 717;
|
||||
return true;
|
||||
}
|
||||
if (fromPort == id_WCK && (toPort == id_F0 || toPort == id_F1)) {
|
||||
delay.delay = 717;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
if ((fromPort == id_A0 && toPort == id_WADO3) || (fromPort == id_A1 && toPort == id_WDO1) ||
|
||||
(fromPort == id_B0 && toPort == id_WADO1) || (fromPort == id_B1 && toPort == id_WDO3) ||
|
||||
@ -576,10 +576,10 @@ bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort
|
||||
}
|
||||
}
|
||||
|
||||
TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockPort) const
|
||||
TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const
|
||||
{
|
||||
auto disconnected = [cell](IdString p) { return !cell->ports.count(p) || cell->ports.at(p).net == nullptr; };
|
||||
|
||||
clockInfoCount = 0;
|
||||
if (cell->type == id_TRELLIS_SLICE) {
|
||||
int sd0 = int_or_default(cell->params, id("REG0_SD"), 0), sd1 = int_or_default(cell->params, id("REG1_SD"), 0);
|
||||
if (port == id_CLK || port == id_WCK)
|
||||
@ -598,13 +598,13 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id
|
||||
return TMG_COMB_OUTPUT;
|
||||
if (port == id_DI0 || port == id_DI1 || port == id_CE || port == id_LSR || (sd0 == 1 && port == id_M0) ||
|
||||
(sd1 == 1 && port == id_M1)) {
|
||||
clockPort = id_CLK;
|
||||
clockInfoCount = 1;
|
||||
return TMG_REGISTER_INPUT;
|
||||
}
|
||||
if (port == id_M0 || port == id_M1)
|
||||
return TMG_COMB_INPUT;
|
||||
if (port == id_Q0 || port == id_Q1) {
|
||||
clockPort = id_CLK;
|
||||
clockInfoCount = 1;
|
||||
return TMG_REGISTER_OUTPUT;
|
||||
}
|
||||
|
||||
@ -614,7 +614,7 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id
|
||||
|
||||
if (port == id_WD0 || port == id_WD1 || port == id_WAD0 || port == id_WAD1 || port == id_WAD2 ||
|
||||
port == id_WAD3 || port == id_WRE) {
|
||||
clockPort = id_WCK;
|
||||
clockInfoCount = 1;
|
||||
return TMG_REGISTER_INPUT;
|
||||
}
|
||||
|
||||
@ -638,10 +638,8 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id
|
||||
for (auto c : boost::adaptors::reverse(port_name)) {
|
||||
if (std::isdigit(c))
|
||||
continue;
|
||||
if (c == 'A')
|
||||
clockPort = id_CLKA;
|
||||
else if (c == 'B')
|
||||
clockPort = id_CLKB;
|
||||
if (c == 'A' || c == 'B')
|
||||
clockInfoCount = 1;
|
||||
else
|
||||
NPNR_ASSERT_FALSE_STR("bad ram port");
|
||||
return (cell->ports.at(port).type == PORT_OUT) ? TMG_REGISTER_OUTPUT : TMG_REGISTER_INPUT;
|
||||
@ -653,11 +651,92 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id
|
||||
return TMG_IGNORE; // FIXME
|
||||
} else if (cell->type == id_EHXPLLL) {
|
||||
return TMG_IGNORE;
|
||||
} else if (cell->type == id_DCUA || cell->type == id_EXTREFB || cell->type == id_PCSCLKDIV) {
|
||||
if (port == id_CH0_FF_TXI_CLK || port == id_CH0_FF_RXI_CLK || port == id_CH1_FF_TXI_CLK ||
|
||||
port == id_CH1_FF_RXI_CLK)
|
||||
return TMG_CLOCK_INPUT;
|
||||
std::string prefix = port.str(this).substr(0, 9);
|
||||
if (prefix == "CH0_FF_TX" || prefix == "CH0_FF_RX" || prefix == "CH1_FF_TX" || prefix == "CH1_FF_RX") {
|
||||
clockInfoCount = 1;
|
||||
return (cell->ports.at(port).type == PORT_OUT) ? TMG_REGISTER_OUTPUT : TMG_REGISTER_INPUT;
|
||||
}
|
||||
return TMG_IGNORE;
|
||||
} else {
|
||||
NPNR_ASSERT_FALSE_STR("no timing data for cell type '" + cell->type.str(this) + "'");
|
||||
}
|
||||
}
|
||||
|
||||
TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port, int index) const
|
||||
{
|
||||
TimingClockingInfo info;
|
||||
info.setup.delay = 0;
|
||||
info.hold.delay = 0;
|
||||
info.clockToQ.delay = 0;
|
||||
if (cell->type == id_TRELLIS_SLICE) {
|
||||
int sd0 = int_or_default(cell->params, id("REG0_SD"), 0), sd1 = int_or_default(cell->params, id("REG1_SD"), 0);
|
||||
|
||||
if (port == id_WD0 || port == id_WD1 || port == id_WAD0 || port == id_WAD1 || port == id_WAD2 ||
|
||||
port == id_WAD3 || port == id_WRE) {
|
||||
info.edge = RISING_EDGE;
|
||||
info.clock_port = id_WCK;
|
||||
info.setup.delay = 100;
|
||||
info.hold.delay = 0;
|
||||
} else if (port == id_DI0 || port == id_DI1 || port == id_CE || port == id_LSR || (sd0 == 1 && port == id_M0) ||
|
||||
(sd1 == 1 && port == id_M1)) {
|
||||
info.edge = cell->sliceInfo.clkmux == id("INV") ? FALLING_EDGE : RISING_EDGE;
|
||||
info.clock_port = id_CLK;
|
||||
info.setup.delay = 100;
|
||||
info.hold.delay = 0;
|
||||
} else {
|
||||
info.edge = cell->sliceInfo.clkmux == id("INV") ? FALLING_EDGE : RISING_EDGE;
|
||||
info.clock_port = id_CLK;
|
||||
info.clockToQ.delay = 395;
|
||||
}
|
||||
} else if (cell->type == id_DP16KD) {
|
||||
std::string port_name = port.str(this);
|
||||
for (auto c : boost::adaptors::reverse(port_name)) {
|
||||
if (std::isdigit(c))
|
||||
continue;
|
||||
if (c == 'A') {
|
||||
info.clock_port = id_CLKA;
|
||||
break;
|
||||
} else if (c == 'B') {
|
||||
info.clock_port = id_CLKB;
|
||||
break;
|
||||
} else
|
||||
NPNR_ASSERT_FALSE_STR("bad ram port " + port.str(this));
|
||||
}
|
||||
info.edge = (str_or_default(cell->params, info.clock_port == id_CLKB ? id("CLKBMUX") : id("CLKAMUX"), "CLK") ==
|
||||
"INV")
|
||||
? FALLING_EDGE
|
||||
: RISING_EDGE;
|
||||
if (cell->ports.at(port).type == PORT_OUT) {
|
||||
info.clockToQ.delay = 4280;
|
||||
} else {
|
||||
info.setup.delay = 100;
|
||||
info.hold.delay = 0;
|
||||
}
|
||||
} else if (cell->type == id_DCUA) {
|
||||
std::string prefix = port.str(this).substr(0, 9);
|
||||
info.edge = RISING_EDGE;
|
||||
if (prefix == "CH0_FF_TX")
|
||||
info.clock_port = id_CH0_FF_TXI_CLK;
|
||||
else if (prefix == "CH0_FF_RX")
|
||||
info.clock_port = id_CH0_FF_RXI_CLK;
|
||||
else if (prefix == "CH1_FF_TX")
|
||||
info.clock_port = id_CH1_FF_TXI_CLK;
|
||||
else if (prefix == "CH1_FF_RX")
|
||||
info.clock_port = id_CH1_FF_RXI_CLK;
|
||||
if (cell->ports.at(port).type == PORT_OUT) {
|
||||
info.clockToQ.delay = 660;
|
||||
} else {
|
||||
info.setup.delay = 1000;
|
||||
info.hold.delay = 0;
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> Arch::getTilesAtLocation(int row, int col)
|
||||
{
|
||||
std::vector<std::pair<std::string, std::string>> ret;
|
||||
|
12
ecp5/arch.h
12
ecp5/arch.h
@ -859,6 +859,12 @@ struct Arch : BaseCtx
|
||||
delay_t getDelayEpsilon() const { return 20; }
|
||||
delay_t getRipupDelayPenalty() const { return 200; }
|
||||
float getDelayNS(delay_t v) const { return v * 0.001; }
|
||||
DelayInfo getDelayFromNS(float ns) const
|
||||
{
|
||||
DelayInfo del;
|
||||
del.delay = delay_t(ns * 1000);
|
||||
return del;
|
||||
}
|
||||
uint32_t getDelayChecksum(delay_t v) const { return v; }
|
||||
bool getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const;
|
||||
|
||||
@ -882,8 +888,10 @@ struct Arch : BaseCtx
|
||||
// Get the delay through a cell from one port to another, returning false
|
||||
// if no path exists
|
||||
bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const;
|
||||
// Get the port class, also setting clockPort if applicable
|
||||
TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockPort) const;
|
||||
// Get the port class, also setting clockInfoCount to the number of TimingClockingInfos associated with a port
|
||||
TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const;
|
||||
// Get the TimingClockingInfo of a port
|
||||
TimingClockingInfo getPortClockingInfo(const CellInfo *cell, IdString port, int index) const;
|
||||
// Return true if a port is a net
|
||||
bool isGlobalNet(const NetInfo *net) const;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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>);
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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
424
ecp5/dcu_bitstream.h
Normal 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));
|
@ -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)
|
||||
|
@ -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);
|
||||
|
168
ecp5/pack.cc
168
ecp5/pack.cc
@ -235,6 +235,46 @@ class Ecp5Packer
|
||||
}
|
||||
}
|
||||
|
||||
// Return true if an port is a top level port that provides its own IOBUF
|
||||
bool is_top_port(PortRef &port)
|
||||
{
|
||||
if (port.cell == nullptr)
|
||||
return false;
|
||||
if (port.cell->type == id_DCUA) {
|
||||
return port.port == id_CH0_HDINP || port.port == id_CH0_HDINN || port.port == id_CH0_HDOUTP ||
|
||||
port.port == id_CH0_HDOUTN || port.port == id_CH1_HDINP || port.port == id_CH1_HDINN ||
|
||||
port.port == id_CH1_HDOUTP || port.port == id_CH1_HDOUTN;
|
||||
} else if (port.cell->type == id_EXTREFB) {
|
||||
return port.port == id_REFCLKP || port.port == id_REFCLKN;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Return true if a net only drives a top port
|
||||
bool drives_top_port(NetInfo *net, PortRef &tp)
|
||||
{
|
||||
if (net == nullptr)
|
||||
return false;
|
||||
for (auto user : net->users) {
|
||||
if (is_top_port(user)) {
|
||||
if (net->users.size() > 1)
|
||||
log_error(" port %s.%s must be connected to (and only to) a top level pin\n",
|
||||
user.cell->name.c_str(ctx), user.port.c_str(ctx));
|
||||
tp = user;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (net->driver.cell != nullptr && is_top_port(net->driver)) {
|
||||
if (net->users.size() > 1)
|
||||
log_error(" port %s.%s must be connected to (and only to) a top level pin\n",
|
||||
net->driver.cell->name.c_str(ctx), net->driver.port.c_str(ctx));
|
||||
tp = net->driver;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Simple "packer" to remove nextpnr IOBUFs, this assumes IOBUFs are manually instantiated
|
||||
void pack_io()
|
||||
{
|
||||
@ -244,10 +284,14 @@ class Ecp5Packer
|
||||
CellInfo *ci = cell.second;
|
||||
if (is_nextpnr_iob(ctx, ci)) {
|
||||
CellInfo *trio = nullptr;
|
||||
NetInfo *ionet = nullptr;
|
||||
PortRef tp;
|
||||
if (ci->type == ctx->id("$nextpnr_ibuf") || ci->type == ctx->id("$nextpnr_iobuf")) {
|
||||
trio = net_only_drives(ctx, ci->ports.at(ctx->id("O")).net, is_trellis_io, ctx->id("B"), true, ci);
|
||||
ionet = ci->ports.at(ctx->id("O")).net;
|
||||
trio = net_only_drives(ctx, ionet, is_trellis_io, ctx->id("B"), true, ci);
|
||||
|
||||
} else if (ci->type == ctx->id("$nextpnr_obuf")) {
|
||||
ionet = ci->ports.at(ctx->id("I")).net;
|
||||
trio = net_only_drives(ctx, ci->ports.at(ctx->id("I")).net, is_trellis_io, ctx->id("B"), true, ci);
|
||||
}
|
||||
if (trio != nullptr) {
|
||||
@ -266,6 +310,19 @@ class Ecp5Packer
|
||||
ctx->nets.erase(net2->name);
|
||||
}
|
||||
}
|
||||
} else if (drives_top_port(ionet, tp)) {
|
||||
log_info("%s feeds %s %s.%s, removing %s %s.\n", ci->name.c_str(ctx), tp.cell->type.c_str(ctx),
|
||||
tp.cell->name.c_str(ctx), tp.port.c_str(ctx), ci->type.c_str(ctx), ci->name.c_str(ctx));
|
||||
if (ionet != nullptr) {
|
||||
ctx->nets.erase(ionet->name);
|
||||
tp.cell->ports.at(tp.port).net = nullptr;
|
||||
}
|
||||
if (ci->type == ctx->id("$nextpnr_iobuf")) {
|
||||
NetInfo *net2 = ci->ports.at(ctx->id("I")).net;
|
||||
if (net2 != nullptr) {
|
||||
ctx->nets.erase(net2->name);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Create a TRELLIS_IO buffer
|
||||
std::unique_ptr<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();
|
||||
|
@ -17,7 +17,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include "io.h"
|
||||
#include "pio.h"
|
||||
|
||||
NEXTPNR_NAMESPACE_BEGIN
|
||||
|
@ -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
|
||||
|
@ -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; }
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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++) {
|
||||
|
@ -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();
|
||||
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
|
32
ice40/arch.h
32
ice40/arch.h
@ -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;
|
||||
|
||||
|
@ -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));
|
||||
|
@ -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>);
|
||||
|
@ -150,8 +150,13 @@ struct ArchCellInfo
|
||||
struct
|
||||
{
|
||||
bool lvds;
|
||||
bool global;
|
||||
// TODO: clk packing checks...
|
||||
} ioInfo;
|
||||
struct
|
||||
{
|
||||
bool forPadIn;
|
||||
} gbInfo;
|
||||
};
|
||||
};
|
||||
|
||||
|
2120
ice40/bitstream.cc
2120
ice40/bitstream.cc
File diff suppressed because it is too large
Load Diff
@ -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"))
|
||||
|
@ -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);
|
||||
|
107
ice40/chipdb.py
107
ice40/chipdb.py
@ -36,6 +36,7 @@ ierens = list()
|
||||
extra_cells = dict()
|
||||
extra_cell_config = dict()
|
||||
packages = list()
|
||||
glbinfo = dict([(i, {}) for i in range(8)])
|
||||
|
||||
wire_belports = dict()
|
||||
|
||||
@ -640,6 +641,18 @@ with open(args.filename, "r") as f:
|
||||
extra_cells[mode[1]] = []
|
||||
continue
|
||||
|
||||
if line[0] == ".gbufin":
|
||||
mode = ("gbufin",)
|
||||
continue
|
||||
|
||||
if line[0] == ".gbufpin":
|
||||
mode = ("gbufpin",)
|
||||
continue
|
||||
|
||||
if line[0] == ".extra_bits":
|
||||
mode = ("extra_bits",)
|
||||
continue
|
||||
|
||||
if (line[0][0] == ".") or (mode is None):
|
||||
mode = None
|
||||
continue
|
||||
@ -692,11 +705,33 @@ with open(args.filename, "r") as f:
|
||||
|
||||
if mode[0] == "extra_cell":
|
||||
if line[0] == "LOCKED":
|
||||
extra_cells[mode[1]].append((("LOCKED_" + line[1]), (0, 0, "LOCKED")))
|
||||
for pkg in line[1:]:
|
||||
extra_cells[mode[1]].append((("LOCKED_" + pkg), (0, 0, "LOCKED")))
|
||||
else:
|
||||
extra_cells[mode[1]].append((line[0], (int(line[1]), int(line[2]), line[3])))
|
||||
continue
|
||||
|
||||
if mode[0] == "gbufin":
|
||||
idx = int(line[2])
|
||||
glbinfo[idx]['gb_x'] = int(line[0])
|
||||
glbinfo[idx]['gb_y'] = int(line[1])
|
||||
continue
|
||||
|
||||
if mode[0] == "gbufpin":
|
||||
idx = int(line[3])
|
||||
glbinfo[idx]['pi_gb_x'] = int(line[0])
|
||||
glbinfo[idx]['pi_gb_y'] = int(line[1])
|
||||
glbinfo[idx]['pi_gb_pio'] = int(line[2])
|
||||
continue
|
||||
|
||||
if mode[0] == "extra_bits":
|
||||
if line[0].startswith('padin_glb_netwk.'):
|
||||
idx = int(line[0].split('.')[1])
|
||||
glbinfo[idx]['pi_eb_bank'] = int(line[1])
|
||||
glbinfo[idx]['pi_eb_x'] = int(line[2])
|
||||
glbinfo[idx]['pi_eb_y'] = int(line[3])
|
||||
continue
|
||||
|
||||
def add_wire(x, y, name):
|
||||
global num_wires
|
||||
wire_idx = num_wires
|
||||
@ -828,6 +863,10 @@ def add_bel_io(x, y, z):
|
||||
add_bel_input(bel, wire_dout_1, "D_OUT_1")
|
||||
add_bel_input(bel, wire_out_en, "OUTPUT_ENABLE")
|
||||
|
||||
for gidx, ginfo in glbinfo.items():
|
||||
if (ginfo['pi_gb_x'], ginfo['pi_gb_y'], ginfo['pi_gb_pio']) == (x,y,z):
|
||||
add_bel_output(bel, wire_names[(x, y, "glb_netwk_%d" % gidx)], "GLOBAL_BUFFER_OUTPUT")
|
||||
|
||||
def add_bel_ram(x, y):
|
||||
bel = len(bel_name)
|
||||
bel_name.append("X%d/Y%d/ram" % (x, y))
|
||||
@ -885,6 +924,18 @@ def is_ec_output(ec_entry):
|
||||
def is_ec_pll_clock_output(ec, ec_entry):
|
||||
return ec[0] == 'PLL' and ec_entry[0] in ('PLLOUT_A', 'PLLOUT_B')
|
||||
|
||||
def add_pll_clock_output(bel, ec, entry):
|
||||
# Fabric output
|
||||
io_x, io_y, io_z = entry[1]
|
||||
io_zs = 'io_{}/D_IN_0'.format(io_z)
|
||||
io_z = int(io_z)
|
||||
add_bel_output(bel, wire_names[(io_x, io_y, io_zs)], entry[0])
|
||||
|
||||
# Global output
|
||||
for gidx, ginfo in glbinfo.items():
|
||||
if (ginfo['pi_gb_x'], ginfo['pi_gb_y'], ginfo['pi_gb_pio']) == (io_x, io_y, io_z):
|
||||
add_bel_output(bel, wire_names[(io_x, io_y, "glb_netwk_%d" % gidx)], entry[0] + '_GLOBAL')
|
||||
|
||||
def add_bel_ec(ec):
|
||||
ectype, x, y, z = ec
|
||||
bel = len(bel_name)
|
||||
@ -894,15 +945,13 @@ def add_bel_ec(ec):
|
||||
bel_pos.append((x, y, z))
|
||||
bel_wires.append(list())
|
||||
for entry in extra_cells[ec]:
|
||||
if is_ec_wire(entry) and "glb_netwk_" not in entry[1][2]: # TODO: osc glb output conflicts with GB
|
||||
if is_ec_wire(entry):
|
||||
if is_ec_output(entry):
|
||||
add_bel_output(bel, wire_names[entry[1]], entry[0])
|
||||
else:
|
||||
add_bel_input(bel, wire_names[entry[1]], entry[0])
|
||||
elif is_ec_pll_clock_output(ec, entry):
|
||||
x, y, z = entry[1]
|
||||
z = 'io_{}/D_IN_0'.format(z)
|
||||
add_bel_output(bel, wire_names[(x, y, z)], entry[0])
|
||||
add_pll_clock_output(bel, ec, entry)
|
||||
else:
|
||||
extra_cell_config[bel].append(entry)
|
||||
|
||||
@ -973,42 +1022,8 @@ for tile_xy, tile_type in sorted(tiles.items()):
|
||||
for i in range(2):
|
||||
add_bel_io(tile_xy[0], tile_xy[1], i)
|
||||
|
||||
if dev_name == "1k":
|
||||
add_bel_gb(tile_xy, 7, 0, 0)
|
||||
add_bel_gb(tile_xy, 7, 17, 1)
|
||||
add_bel_gb(tile_xy, 13, 9, 2)
|
||||
add_bel_gb(tile_xy, 0, 9, 3)
|
||||
add_bel_gb(tile_xy, 6, 17, 4)
|
||||
add_bel_gb(tile_xy, 6, 0, 5)
|
||||
add_bel_gb(tile_xy, 0, 8, 6)
|
||||
add_bel_gb(tile_xy, 13, 8, 7)
|
||||
elif dev_name == "5k":
|
||||
add_bel_gb(tile_xy, 13, 0, 0)
|
||||
add_bel_gb(tile_xy, 13, 31, 1)
|
||||
add_bel_gb(tile_xy, 19, 31, 2)
|
||||
add_bel_gb(tile_xy, 6, 31, 3)
|
||||
add_bel_gb(tile_xy, 12, 31, 4)
|
||||
add_bel_gb(tile_xy, 12, 0, 5)
|
||||
add_bel_gb(tile_xy, 6, 0, 6)
|
||||
add_bel_gb(tile_xy, 19, 0, 7)
|
||||
elif dev_name == "8k":
|
||||
add_bel_gb(tile_xy, 33, 16, 7)
|
||||
add_bel_gb(tile_xy, 0, 16, 6)
|
||||
add_bel_gb(tile_xy, 17, 33, 1)
|
||||
add_bel_gb(tile_xy, 17, 0, 0)
|
||||
add_bel_gb(tile_xy, 0, 17, 3)
|
||||
add_bel_gb(tile_xy, 33, 17, 2)
|
||||
add_bel_gb(tile_xy, 16, 0, 5)
|
||||
add_bel_gb(tile_xy, 16, 33, 4)
|
||||
elif dev_name == "384":
|
||||
add_bel_gb(tile_xy, 7, 4, 7)
|
||||
add_bel_gb(tile_xy, 0, 4, 6)
|
||||
add_bel_gb(tile_xy, 4, 9, 1)
|
||||
add_bel_gb(tile_xy, 4, 0, 0)
|
||||
add_bel_gb(tile_xy, 0, 5, 3)
|
||||
add_bel_gb(tile_xy, 7, 5, 2)
|
||||
add_bel_gb(tile_xy, 3, 0, 5)
|
||||
add_bel_gb(tile_xy, 3, 9, 4)
|
||||
for gidx, ginfo in glbinfo.items():
|
||||
add_bel_gb(tile_xy, ginfo['gb_x'], ginfo['gb_y'], gidx)
|
||||
|
||||
if tile_type == "ramb":
|
||||
add_bel_ram(tile_xy[0], tile_xy[1])
|
||||
@ -1423,6 +1438,14 @@ for cell, timings in sorted(cell_timings.items()):
|
||||
bba.u32(len(timings), "num_paths")
|
||||
bba.r("cell_paths_%d" % beltype, "path_delays")
|
||||
|
||||
bba.l("global_network_info_%s" % dev_name, "GlobalNetworkInfoPOD")
|
||||
for i in range(len(glbinfo)):
|
||||
for k in ['gb_x', 'gb_y', 'pi_gb_x', 'pi_gb_y', 'pi_gb_pio', 'pi_eb_bank']:
|
||||
bba.u8(glbinfo[i][k], k)
|
||||
for k in ['pi_eb_x', 'pi_eb_y']:
|
||||
bba.u16(glbinfo[i][k], k)
|
||||
bba.u16(0, "padding")
|
||||
|
||||
bba.l("chip_info_%s" % dev_name)
|
||||
bba.u32(dev_width, "dev_width")
|
||||
bba.u32(dev_height, "dev_height")
|
||||
@ -1433,6 +1456,7 @@ bba.u32(len(switchinfo), "num_switches")
|
||||
bba.u32(len(extra_cell_config), "num_belcfgs")
|
||||
bba.u32(len(packageinfo), "num_packages")
|
||||
bba.u32(len(cell_timings), "num_timing_cells")
|
||||
bba.u32(len(glbinfo), "num_global_networks")
|
||||
bba.r("bel_data_%s" % dev_name, "bel_data")
|
||||
bba.r("wire_data_%s" % dev_name, "wire_data")
|
||||
bba.r("pip_data_%s" % dev_name, "pip_data")
|
||||
@ -1441,6 +1465,7 @@ bba.r("bits_info_%s" % dev_name, "bits_info")
|
||||
bba.r("bel_config_%s" % dev_name if len(extra_cell_config) > 0 else None, "bel_config")
|
||||
bba.r("package_info_%s" % dev_name, "packages_data")
|
||||
bba.r("cell_timings_%s" % dev_name, "cell_timing")
|
||||
bba.r("global_network_info_%s" % dev_name, "global_network_info")
|
||||
bba.r("tile_wire_names", "tile_wire_names")
|
||||
|
||||
bba.pop()
|
||||
|
@ -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)
|
||||
|
227
ice40/pack.cc
227
ice40/pack.cc
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user