mirror of https://github.com/YosysHQ/nextpnr.git
gatemate: add CP lines as clock and general routing [sc-184] (#1638)
* gatemate: add alternate clock routes * use additional pins * Fix clock router and timings * Fix DDR nets * Test passtrough concept * remove not used variable * wip * handle pip masks * Cleanup * create CPE_CPLINES cells and set properties on them * Fix pip masking * rough code to break cplines into subnets * add ports to cell * mux bridges need cell bel pins too * fix multiplier output register packing * remove empty if * Fix ODDR * Add options to disable some pips * Use resources info * mask field to resource field * produce valid netlist with propagation netlist at least * adapt reassign_cplines for internal resource pips * Handle block and resources * fix formatting * It is required to set all mandatory properties now * arch API for resources * current progress * Add option to skip bridges * perform per-wire resource congestion costing * Added no-cpe-cp option * resource bugfix * comment out spammy debug message * Fix routing conflicts issues * allow only some pass trough for clock router * handle inversion bits for pass signals * verify inversion before/after assigning bridges * we care only if there is net * Revert "we care only if there is net" This reverts commit3da2769e31. * Revert "verify inversion before/after assigning bridges" This reverts commit8613ee17c8. * chipdb version bump * clangformat * cleanup * cleanup * Initial conversion to GroupId * Keep group info in pip extra * Cleanup headers * Initialize resource efficiently * Addressing review comments * improve resource docs * Make CP lines not use as clocks as default --------- Co-authored-by: Lofty <dan.ravensloft@gmail.com>
This commit is contained in:
parent
501b36e646
commit
b8a6559a3f
|
|
@ -150,6 +150,10 @@ template <typename R> struct ArchAPI : BaseCtx
|
|||
std::vector<std::pair<CellInfo *, BelId>> &placement) const = 0;
|
||||
// Routing methods
|
||||
virtual void expandBoundingBox(BoundingBox &bb) const = 0;
|
||||
// Resource methods
|
||||
virtual GroupId getResourceKeyForPip(PipId pip) const = 0;
|
||||
virtual int getResourceValueForPip(PipId pip) const = 0;
|
||||
virtual bool isGroupResource(GroupId group) const = 0;
|
||||
|
||||
// Flow methods
|
||||
virtual bool pack() = 0;
|
||||
|
|
|
|||
|
|
@ -452,6 +452,11 @@ template <typename R> struct BaseArch : ArchAPI<R>
|
|||
bb.y1 = std::min(bb.y1 + 1, this->getGridDimY());
|
||||
}
|
||||
|
||||
// Resource methods
|
||||
virtual GroupId getResourceKeyForPip(PipId /*pip*/) const override { return GroupId(); }
|
||||
virtual int getResourceValueForPip(PipId /*pip*/) const override { return 0; }
|
||||
virtual bool isGroupResource(GroupId /*group*/) const override { return false; }
|
||||
|
||||
// Flow methods
|
||||
virtual void assignArchInfo() override {};
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@
|
|||
|
||||
#include "log.h"
|
||||
#include "nextpnr.h"
|
||||
#include "nextpnr_assertions.h"
|
||||
#include "router1.h"
|
||||
#include "scope_lock.h"
|
||||
#include "timing.h"
|
||||
|
|
@ -49,6 +50,12 @@ NEXTPNR_NAMESPACE_BEGIN
|
|||
namespace {
|
||||
struct Router2
|
||||
{
|
||||
// std::pair<int, int> is a bit too confusing, so:
|
||||
struct NetResourceData
|
||||
{
|
||||
int value = 0;
|
||||
int count = 0;
|
||||
};
|
||||
|
||||
struct PerArcData
|
||||
{
|
||||
|
|
@ -64,6 +71,7 @@ struct Router2
|
|||
WireId src_wire;
|
||||
dict<WireId, std::pair<PipId, int>> wires;
|
||||
std::vector<std::vector<PerArcData>> arcs;
|
||||
dict<GroupId, NetResourceData> resources;
|
||||
BoundingBox bb;
|
||||
// Coordinates of the center of the net, used for the weight-to-average
|
||||
int cx, cy, hpwl;
|
||||
|
|
@ -99,6 +107,14 @@ struct Router2
|
|||
float cost_fwd = 0.0, cost_bwd = 0.0;
|
||||
};
|
||||
|
||||
struct PerResourceData
|
||||
{
|
||||
GroupId key;
|
||||
// Historical congestion cost
|
||||
dict<int, int> value_count;
|
||||
float hist_cong_cost = 1.0;
|
||||
};
|
||||
|
||||
Context *ctx;
|
||||
Router2Cfg cfg;
|
||||
|
||||
|
|
@ -237,6 +253,36 @@ struct Router2
|
|||
}
|
||||
}
|
||||
|
||||
dict<GroupId, int> resource_to_idx;
|
||||
dict<WireId, int> wire_to_resource;
|
||||
std::vector<PerResourceData> flat_resources;
|
||||
|
||||
PerResourceData &resource_data(GroupId r) { return flat_resources[resource_to_idx.at(r)]; }
|
||||
|
||||
void setup_resources()
|
||||
{
|
||||
for (auto resource_key : ctx->getGroups()) {
|
||||
if (!ctx->isGroupResource(resource_key))
|
||||
continue;
|
||||
|
||||
auto entry = resource_to_idx.find(resource_key);
|
||||
|
||||
if (entry == resource_to_idx.end()) {
|
||||
auto data = PerResourceData{};
|
||||
data.key = resource_key;
|
||||
resource_to_idx.insert({resource_key, flat_resources.size()});
|
||||
flat_resources.push_back(data);
|
||||
|
||||
entry = resource_to_idx.find(resource_key);
|
||||
}
|
||||
for (auto pip : ctx->getGroupPips(resource_key)) {
|
||||
auto dest_wire = ctx->getPipDstWire(pip);
|
||||
if (wire_to_resource.count(dest_wire) == 0)
|
||||
wire_to_resource.insert({dest_wire, entry->second});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct QueuedWire
|
||||
{
|
||||
|
||||
|
|
@ -316,8 +362,8 @@ struct Router2
|
|||
void bind_pip_internal(PerNetData &net, store_index<PortRef> user, int wire, PipId pip)
|
||||
{
|
||||
auto &wd = flat_wires.at(wire);
|
||||
auto found = net.wires.find(wd.w);
|
||||
if (found == net.wires.end()) {
|
||||
auto wire_found = net.wires.find(wd.w);
|
||||
if (wire_found == net.wires.end()) {
|
||||
// Not yet used for any arcs of this net, add to list
|
||||
net.wires.emplace(wd.w, std::make_pair(pip, 1));
|
||||
// Increase bound count of wire by 1
|
||||
|
|
@ -325,22 +371,72 @@ struct Router2
|
|||
} else {
|
||||
// Already used for at least one other arc of this net
|
||||
// Don't allow two uphill PIPs for the same net and wire
|
||||
NPNR_ASSERT(found->second.first == pip);
|
||||
NPNR_ASSERT(wire_found->second.first == pip);
|
||||
// Increase the count of bound arcs
|
||||
++found->second.second;
|
||||
++wire_found->second.second;
|
||||
}
|
||||
|
||||
if (pip == PipId())
|
||||
return;
|
||||
|
||||
auto resource_key = ctx->getResourceKeyForPip(pip);
|
||||
if (resource_key == GroupId())
|
||||
return;
|
||||
|
||||
auto &rd = resource_data(resource_key);
|
||||
auto resource_value = ctx->getResourceValueForPip(pip);
|
||||
|
||||
auto resource_found = net.resources.find(resource_key);
|
||||
|
||||
if (resource_found == net.resources.end()) {
|
||||
net.resources.emplace(resource_key, NetResourceData{resource_value, 1});
|
||||
} else {
|
||||
// net resource value must agree with arc resource value
|
||||
NPNR_ASSERT(resource_found->second.value == resource_value);
|
||||
|
||||
++resource_found->second.count;
|
||||
}
|
||||
|
||||
auto resource_value_count = rd.value_count.find(resource_value);
|
||||
if (resource_value_count == rd.value_count.end()) {
|
||||
rd.value_count.insert({resource_value, 1});
|
||||
} else {
|
||||
++resource_value_count->second;
|
||||
}
|
||||
}
|
||||
|
||||
void unbind_pip_internal(PerNetData &net, store_index<PortRef> user, WireId wire)
|
||||
{
|
||||
auto &wd = wire_data(wire);
|
||||
auto &b = net.wires.at(wd.w);
|
||||
--b.second;
|
||||
if (b.second == 0) {
|
||||
auto &wire_found = net.wires.at(wd.w);
|
||||
auto pip = wire_found.first;
|
||||
|
||||
--wire_found.second;
|
||||
if (wire_found.second == 0) {
|
||||
// No remaining arcs of this net bound to this wire
|
||||
--wd.curr_cong;
|
||||
net.wires.erase(wd.w);
|
||||
}
|
||||
|
||||
if (pip == PipId())
|
||||
return;
|
||||
|
||||
auto resource_key = ctx->getResourceKeyForPip(pip);
|
||||
if (resource_key == GroupId())
|
||||
return;
|
||||
|
||||
auto &rd = resource_data(resource_key);
|
||||
auto resource_value = ctx->getResourceValueForPip(pip);
|
||||
auto resource_found = net.resources.at(resource_key);
|
||||
|
||||
--resource_found.count;
|
||||
--rd.value_count.at(resource_value);
|
||||
if (resource_found.count == 0) {
|
||||
net.resources.erase(resource_key);
|
||||
}
|
||||
if (rd.value_count.at(resource_value) == 0) {
|
||||
rd.value_count.erase(resource_value);
|
||||
}
|
||||
}
|
||||
|
||||
void ripup_arc(NetInfo *net, store_index<PortRef> user, size_t phys_pin)
|
||||
|
|
@ -372,6 +468,8 @@ struct Router2
|
|||
int overuse = wd.curr_cong;
|
||||
float hist_cost = 1.0f + crit_weight * (wd.hist_cong_cost - 1.0f);
|
||||
float bias_cost = 0;
|
||||
float resource_hist_cost = 0.0f;
|
||||
float resource_present_cost = 0.0f;
|
||||
int source_uses = 0;
|
||||
if (nd.wires.count(wire)) {
|
||||
overuse -= 1;
|
||||
|
|
@ -383,7 +481,16 @@ struct Router2
|
|||
bias_cost = cfg.bias_cost_factor * (base_cost / int(net->users.entries())) *
|
||||
((std::abs(pl.x - nd.cx) + std::abs(pl.y - nd.cy)) / float(nd.hpwl));
|
||||
}
|
||||
return base_cost * hist_cost * present_cost / (1 + (source_uses * crit_weight)) + bias_cost;
|
||||
|
||||
auto resource_key = wire_to_resource.find(wire);
|
||||
if (resource_key != wire_to_resource.end()) {
|
||||
auto &rd = flat_resources.at(resource_key->second);
|
||||
resource_hist_cost = 1.0f + crit_weight * (rd.hist_cong_cost - 1.0f);
|
||||
resource_present_cost = 1.0f + rd.value_count.size() * curr_cong_weight * crit_weight;
|
||||
}
|
||||
|
||||
return base_cost * hist_cost * present_cost / (1 + (source_uses * crit_weight)) + bias_cost +
|
||||
base_cost * resource_hist_cost * resource_present_cost / (1 + crit_weight);
|
||||
}
|
||||
|
||||
float get_togo_cost(NetInfo *net, store_index<PortRef> user, int wire, WireId src_sink, bool bwd, float crit_weight)
|
||||
|
|
@ -412,6 +519,12 @@ struct Router2
|
|||
auto &uh = nd.wires.at(cursor).first;
|
||||
if (uh == PipId())
|
||||
break;
|
||||
auto resource_key = ctx->getResourceKeyForPip(uh);
|
||||
if (resource_key != GroupId()) {
|
||||
auto &rd = resource_data(resource_key);
|
||||
if (rd.value_count.size() > 1)
|
||||
break;
|
||||
}
|
||||
cursor = ctx->getPipSrcWire(uh);
|
||||
}
|
||||
return (cursor == src_wire);
|
||||
|
|
@ -817,6 +930,14 @@ struct Router2
|
|||
auto fnd_wire = nd.wires.find(next);
|
||||
if (fnd_wire != nd.wires.end() && fnd_wire->second.first != dh)
|
||||
continue;
|
||||
// Don't allow the same resource to be bound to the same net with a different value
|
||||
auto resource_key = ctx->getResourceKeyForPip(dh);
|
||||
if (resource_key != GroupId()) {
|
||||
auto fnd_resource = nd.resources.find(resource_key);
|
||||
if (fnd_resource != nd.resources.end() &&
|
||||
fnd_resource->second.value != ctx->getResourceValueForPip(dh))
|
||||
continue;
|
||||
}
|
||||
if (!thread_test_wire(t, nwd))
|
||||
continue; // thread safety issue
|
||||
set_visited_fwd(t, next_idx, dh, next_score.delay);
|
||||
|
|
@ -882,6 +1003,14 @@ struct Router2
|
|||
// Reserved for another net
|
||||
if (nwd.reserved_net != -1 && nwd.reserved_net != net->udata)
|
||||
continue;
|
||||
// Don't allow the same resource to be bound to the same net with a different value
|
||||
auto resource_key = ctx->getResourceKeyForPip(uh);
|
||||
if (resource_key != GroupId()) {
|
||||
auto fnd_resource = nd.resources.find(resource_key);
|
||||
if (fnd_resource != nd.resources.end() &&
|
||||
fnd_resource->second.value != ctx->getResourceValueForPip(uh))
|
||||
continue;
|
||||
}
|
||||
if (!thread_test_wire(t, nwd))
|
||||
continue; // thread safety issue
|
||||
set_visited_bwd(t, next_idx, uh, next_score.delay);
|
||||
|
|
@ -912,8 +1041,15 @@ struct Router2
|
|||
if (pip == PipId()) {
|
||||
break;
|
||||
}
|
||||
ROUTE_LOG_DBG(" fwd pip: %s (%d, %d)\n", ctx->nameOfPip(pip), ctx->getPipLocation(pip).x,
|
||||
ctx->getPipLocation(pip).y);
|
||||
auto resource_key = ctx->getResourceKeyForPip(pip);
|
||||
if (resource_key != GroupId()) {
|
||||
ROUTE_LOG_DBG(" fwd pip: %s (%d, %d) %s = %d\n", ctx->nameOfPip(pip),
|
||||
ctx->getPipLocation(pip).x, ctx->getPipLocation(pip).y,
|
||||
ctx->nameOfGroup(resource_key), ctx->getResourceValueForPip(pip));
|
||||
} else {
|
||||
ROUTE_LOG_DBG(" fwd pip: %s (%d, %d)\n", ctx->nameOfPip(pip),
|
||||
ctx->getPipLocation(pip).x, ctx->getPipLocation(pip).y);
|
||||
}
|
||||
cursor_bwd = wire_to_idx.at(ctx->getPipSrcWire(pip));
|
||||
}
|
||||
|
||||
|
|
@ -944,8 +1080,16 @@ struct Router2
|
|||
if (pip == PipId()) {
|
||||
break;
|
||||
}
|
||||
ROUTE_LOG_DBG(" bwd pip: %s (%d, %d)\n", ctx->nameOfPip(pip), ctx->getPipLocation(pip).x,
|
||||
ctx->getPipLocation(pip).y);
|
||||
auto resource_key = ctx->getResourceKeyForPip(pip);
|
||||
if (resource_key != GroupId()) {
|
||||
ROUTE_LOG_DBG(" bwd pip: %s (%d, %d) %s = %d\n", ctx->nameOfPip(pip),
|
||||
ctx->getPipLocation(pip).x, ctx->getPipLocation(pip).y,
|
||||
ctx->nameOfGroup(resource_key), ctx->getResourceValueForPip(pip));
|
||||
} else {
|
||||
ROUTE_LOG_DBG(" bwd pip: %s (%d, %d)\n", ctx->nameOfPip(pip), ctx->getPipLocation(pip).x,
|
||||
ctx->getPipLocation(pip).y);
|
||||
}
|
||||
|
||||
cursor_fwd = wire_to_idx.at(ctx->getPipDstWire(pip));
|
||||
bind_pip_internal(nd, i, cursor_fwd, pip);
|
||||
if (ctx->debug && !is_mt) {
|
||||
|
|
@ -1060,35 +1204,58 @@ struct Router2
|
|||
|
||||
int total_wire_use = 0;
|
||||
int overused_wires = 0;
|
||||
int total_overuse = 0;
|
||||
int total_wire_overuse = 0;
|
||||
int total_resource_use = 0;
|
||||
int overused_resources = 0;
|
||||
int total_resource_overuse = 0;
|
||||
std::vector<int> route_queue;
|
||||
std::set<int> failed_nets;
|
||||
|
||||
void update_congestion()
|
||||
{
|
||||
total_overuse = 0;
|
||||
total_wire_overuse = 0;
|
||||
overused_wires = 0;
|
||||
total_wire_use = 0;
|
||||
total_resource_overuse = 0;
|
||||
overused_resources = 0;
|
||||
total_resource_use = 0;
|
||||
failed_nets.clear();
|
||||
pool<WireId> already_updated;
|
||||
pool<WireId> already_updated_wires;
|
||||
pool<GroupId> already_updated_resources;
|
||||
for (size_t i = 0; i < nets.size(); i++) {
|
||||
auto &nd = nets.at(i);
|
||||
for (const auto &w : nd.wires) {
|
||||
++total_wire_use;
|
||||
auto &wd = wire_data(w.first);
|
||||
if (wd.curr_cong > 1) {
|
||||
if (already_updated.count(w.first)) {
|
||||
++total_overuse;
|
||||
if (already_updated_wires.count(w.first)) {
|
||||
++total_wire_overuse;
|
||||
} else {
|
||||
if (curr_cong_weight > 0)
|
||||
wd.hist_cong_cost =
|
||||
std::min(1e9, wd.hist_cong_cost + (wd.curr_cong - 1) * hist_cong_weight);
|
||||
already_updated.insert(w.first);
|
||||
already_updated_wires.insert(w.first);
|
||||
++overused_wires;
|
||||
}
|
||||
failed_nets.insert(i);
|
||||
}
|
||||
}
|
||||
for (const auto &r : nd.resources) {
|
||||
++total_resource_use;
|
||||
auto &rd = resource_data(r.first);
|
||||
if (rd.value_count.size() > 1) {
|
||||
if (already_updated_resources.count(r.first)) {
|
||||
++total_resource_overuse;
|
||||
} else {
|
||||
if (curr_cong_weight > 0)
|
||||
rd.hist_cong_cost =
|
||||
std::min(1e9, rd.hist_cong_cost + (rd.value_count.size() - 1) * hist_cong_weight);
|
||||
already_updated_resources.insert(r.first);
|
||||
++overused_resources;
|
||||
}
|
||||
failed_nets.insert(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int n : failed_nets) {
|
||||
auto &net_data = nets.at(n);
|
||||
|
|
@ -1502,6 +1669,7 @@ struct Router2
|
|||
log_info("Running router2...\n");
|
||||
log_info("Setting up routing resources...\n");
|
||||
auto rstart = std::chrono::high_resolution_clock::now();
|
||||
setup_resources();
|
||||
setup_nets();
|
||||
setup_wires();
|
||||
find_all_reserved_wires();
|
||||
|
|
@ -1587,19 +1755,24 @@ struct Router2
|
|||
}
|
||||
}
|
||||
}
|
||||
if (overused_wires == 0 && tmgfail == 0) {
|
||||
if (overused_wires == 0 && overused_resources == 0 && tmgfail == 0) {
|
||||
// Try and actually bind nextpnr Arch API wires
|
||||
bind_and_check_all();
|
||||
}
|
||||
for (auto cn : failed_nets)
|
||||
route_queue.push_back(cn);
|
||||
std::string resource_str = total_resource_use == 0
|
||||
? ""
|
||||
: stringf("resources=%d overused=%d overuse=%d ", total_resource_use,
|
||||
overused_resources, total_resource_overuse);
|
||||
if (timing_driven_ripup)
|
||||
log_info(" iter=%d wires=%d overused=%d overuse=%d tmgfail=%d archfail=%s\n", iter, total_wire_use,
|
||||
overused_wires, total_overuse, tmgfail,
|
||||
log_info(" iter=%d wires=%d overused=%d overuse=%d %stmgfail=%d "
|
||||
"archfail=%s\n",
|
||||
iter, total_wire_use, overused_wires, total_wire_overuse, resource_str.c_str(), tmgfail,
|
||||
(overused_wires > 0 || tmgfail > 0) ? "NA" : std::to_string(arch_fail).c_str());
|
||||
else
|
||||
log_info(" iter=%d wires=%d overused=%d overuse=%d archfail=%s\n", iter, total_wire_use,
|
||||
overused_wires, total_overuse,
|
||||
log_info(" iter=%d wires=%d overused=%d overuse=%d %sarchfail=%s\n", iter, total_wire_use,
|
||||
overused_wires, total_wire_overuse, resource_str.c_str(),
|
||||
(overused_wires > 0 || tmgfail > 0) ? "NA" : std::to_string(arch_fail).c_str());
|
||||
++iter;
|
||||
if (curr_cong_weight < 1e9)
|
||||
|
|
|
|||
|
|
@ -780,3 +780,26 @@ Router Methods
|
|||
As part of `router2` implementation, during congestion update, every third time a net fails to route, this method is executed to expand the bounding box to increase the search space.
|
||||
|
||||
Default implementation expands by one tile in each direction.
|
||||
|
||||
Resource Methods
|
||||
---------------
|
||||
|
||||
Some architectures may have shared resources across pips, e.g. a single bitstream bit controls multiple multiplexers. To produce legal output, these resource APIs can be used to ensure resource "keys" have "values" that agree across pips. Note that a pip has zero or one resource keys, so if a shared resource requires multiple keys, it must have multiple pips in series.
|
||||
|
||||
### GroupId getResourceKeyForPip(PipId pip) const
|
||||
|
||||
Returns the resource key for given pip, or `GroupId()` if a pip has no resource key.
|
||||
|
||||
*BaseArch default: returns `GroupId()`*
|
||||
|
||||
### int getResourceValueForPip(PipId pip) const
|
||||
|
||||
Returns the resource value for given pip, or `0` if a pip has no resource key.
|
||||
|
||||
*BaseArch default: returns `0`*
|
||||
|
||||
### bool isGroupResource(GroupId group) const
|
||||
|
||||
Returns `true` if `group` represents a resource.
|
||||
|
||||
*BaseArch default: returns `false`*
|
||||
|
|
|
|||
|
|
@ -788,6 +788,12 @@ struct Arch : BaseArch<ArchRanges>
|
|||
// Routing methods
|
||||
void expandBoundingBox(BoundingBox &bb) const override { uarch->expandBoundingBox(bb); };
|
||||
|
||||
// ------------------------------------------------
|
||||
// Resource methods
|
||||
GroupId getResourceKeyForPip(PipId pip) const override { return uarch->getResourceKeyForPip(pip); };
|
||||
int getResourceValueForPip(PipId pip) const override { return uarch->getResourceValueForPip(pip); };
|
||||
bool isGroupResource(GroupId group) const override { return uarch->isGroupResource(group); };
|
||||
|
||||
// ------------------------------------------------
|
||||
|
||||
bool pack() override;
|
||||
|
|
|
|||
|
|
@ -130,6 +130,12 @@ struct HimbaechelAPI
|
|||
|
||||
// Routing methods
|
||||
virtual void expandBoundingBox(BoundingBox &bb) const;
|
||||
|
||||
// Resource methods
|
||||
virtual GroupId getResourceKeyForPip(PipId pip) const { return GroupId(); };
|
||||
virtual int getResourceValueForPip(PipId pip) const { return 0; }
|
||||
virtual bool isGroupResource(GroupId /*group*/) const { return false; }
|
||||
|
||||
// --- Flow hooks ---
|
||||
virtual void pack() {}; // replaces the pack function
|
||||
// Called before and after main placement and routing
|
||||
|
|
|
|||
|
|
@ -65,10 +65,12 @@ struct BitstreamBackend
|
|||
|
||||
WireId cursor = dst_wire;
|
||||
bool invert = false;
|
||||
if (net_info->driver.cell && net_info->driver.cell->type == id_CPE_BRIDGE &&
|
||||
net_info->driver.port == id_MUXOUT) {
|
||||
int val = int_or_default(net_info->driver.cell->params, id_C_SN, 0) + 1;
|
||||
invert ^= need_inversion(net_info->driver.cell, ctx->idf("IN%d", val));
|
||||
if (net_info->driver.cell && uarch->pass_backtrace.count(net_info->driver.cell->name)) {
|
||||
auto &bt = uarch->pass_backtrace[net_info->driver.cell->name];
|
||||
if (bt.count(net_info->driver.port)) {
|
||||
IdString src_port = bt[net_info->driver.port];
|
||||
invert ^= need_inversion(net_info->driver.cell, src_port);
|
||||
}
|
||||
}
|
||||
while (cursor != WireId() && cursor != src_wire) {
|
||||
auto it = net_info->wires.find(cursor);
|
||||
|
|
|
|||
|
|
@ -925,6 +925,10 @@ X(CINX)
|
|||
X(CPE_FF_U)
|
||||
// CPE_FF_U pins
|
||||
X(DIN)
|
||||
X(CLK_INT)
|
||||
X(EN_INT)
|
||||
X(CINY2)
|
||||
X(PINY2)
|
||||
//X(CLK)
|
||||
//X(EN)
|
||||
//X(SR)
|
||||
|
|
@ -958,8 +962,8 @@ X(COMBIN)
|
|||
X(PINX)
|
||||
X(CINY1)
|
||||
//X(PINY1)
|
||||
X(CINY2)
|
||||
X(PINY2)
|
||||
//X(CINY2)
|
||||
//X(PINY2)
|
||||
X(COUTX)
|
||||
X(POUTX)
|
||||
X(COUTY1)
|
||||
|
|
@ -971,6 +975,10 @@ X(POUTY2)
|
|||
X(CPE_FF_L)
|
||||
// CPE_FF_L pins
|
||||
//X(DIN)
|
||||
//X(CLK_INT)
|
||||
//X(EN_INT)
|
||||
//X(CINY2)
|
||||
//X(PINY2)
|
||||
//X(CLK)
|
||||
//X(EN)
|
||||
//X(SR)
|
||||
|
|
@ -3220,3 +3228,5 @@ X(timing_pll_clock_core0_i_clk_core180_o)
|
|||
X(timing_pll_clock_core0_i_clk_core270_o)
|
||||
X(timing_pll_clock_core0_i_clk_core90_o)
|
||||
X(timing_pll_locked_steady_reset_i_pll_locked_steady_o)
|
||||
|
||||
X(RESOURCE)
|
||||
|
|
|
|||
|
|
@ -243,7 +243,7 @@ TimingPortClass GateMateImpl::getPortTimingClass(const CellInfo *cell, IdString
|
|||
return TMG_COMB_OUTPUT;
|
||||
return TMG_COMB_INPUT;
|
||||
} else if (cell->type.in(id_CPE_FF, id_CPE_FF_L, id_CPE_FF_U, id_CPE_LATCH)) {
|
||||
if (port == id_CLK)
|
||||
if (port.in(id_CLK_INT))
|
||||
return TMG_CLOCK_INPUT;
|
||||
clockInfoCount = 1;
|
||||
if (port == id_DOUT)
|
||||
|
|
@ -364,7 +364,7 @@ TimingClockingInfo GateMateImpl::getPortClockingInfo(const CellInfo *cell, IdStr
|
|||
if (cell->type.in(id_CPE_FF, id_CPE_FF_L, id_CPE_FF_U, id_CPE_LATCH)) {
|
||||
bool inverted = int_or_default(cell->params, id_C_CPE_CLK, 0) == 0b01;
|
||||
info.edge = inverted ? FALLING_EDGE : RISING_EDGE;
|
||||
info.clock_port = id_CLK;
|
||||
info.clock_port = id_CLK_INT;
|
||||
if (port.in(id_DIN, id_EN, id_SR))
|
||||
get_setuphold_from_tmg_db(id_timing_del_Setup_D_L, id_timing_del_Hold_D_L, info.setup, info.hold);
|
||||
if (port.in(id_DOUT)) {
|
||||
|
|
|
|||
|
|
@ -43,6 +43,9 @@ NPNR_PACKED_STRUCT(struct GateMatePipExtraDataPOD {
|
|||
uint8_t plane;
|
||||
uint8_t dummy1;
|
||||
uint16_t dummy2;
|
||||
uint32_t block;
|
||||
uint32_t resource;
|
||||
uint32_t group_index;
|
||||
});
|
||||
|
||||
NPNR_PACKED_STRUCT(struct GateMateBelPinConstraintPOD {
|
||||
|
|
@ -124,6 +127,26 @@ enum ClusterPlacement
|
|||
PLACE_DB_CONSTR = 32,
|
||||
};
|
||||
|
||||
enum PipMask
|
||||
{
|
||||
C_SELX = 1 << 0,
|
||||
C_SELY1 = 1 << 1,
|
||||
C_SELY2 = 1 << 2,
|
||||
C_SEL_C = 1 << 3,
|
||||
C_SEL_P = 1 << 4,
|
||||
C_Y12 = 1 << 5,
|
||||
C_CX_I = 1 << 6,
|
||||
C_CY1_I = 1 << 7,
|
||||
C_CY2_I = 1 << 8,
|
||||
C_PX_I = 1 << 9,
|
||||
C_PY1_I = 1 << 10,
|
||||
C_PY2_I = 1 << 11,
|
||||
|
||||
IS_MULT = 1 << 29,
|
||||
IS_ADDF = 1 << 30,
|
||||
IS_COMP = 1 << 31,
|
||||
};
|
||||
|
||||
struct PllCfgRecord
|
||||
{
|
||||
double weight;
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
#include "gatemate.h"
|
||||
#include "log.h"
|
||||
#include "nextpnr_assertions.h"
|
||||
#include "placer_heap.h"
|
||||
|
||||
#define GEN_INIT_CONSTIDS
|
||||
|
|
@ -43,6 +44,9 @@ po::options_description GateMateImpl::getUArchOptions()
|
|||
specific.add_options()("strategy", po::value<std::string>(),
|
||||
"multi-die clock placement strategy (mirror, full or clk1)");
|
||||
specific.add_options()("force_die", po::value<std::string>(), "force specific die (example 1A,1B...)");
|
||||
specific.add_options()("clk-cp", "use CP lines for CLK and EN");
|
||||
specific.add_options()("no-cpe-cp", "do not use CP lines pass through CPE");
|
||||
specific.add_options()("no-bridges", "do not use CPE in bridge mode");
|
||||
return specific;
|
||||
}
|
||||
|
||||
|
|
@ -112,6 +116,9 @@ void GateMateImpl::init_database(Arch *arch)
|
|||
: fpga_mode == 3 ? "SPEED"
|
||||
: "");
|
||||
arch->set_speed_grade(speed_grade);
|
||||
use_cp_for_clk = args.options.count("clk-cp") == 1;
|
||||
use_cp_for_cpe = args.options.count("no-cpe-cp") == 0;
|
||||
use_bridges = args.options.count("no-bridges") == 0;
|
||||
}
|
||||
|
||||
void GateMateImpl::init(Context *ctx)
|
||||
|
|
@ -312,6 +319,16 @@ void GateMateImpl::postPlace()
|
|||
repack();
|
||||
ctx->assignArchInfo();
|
||||
used_cpes.resize(ctx->getGridDimX() * ctx->getGridDimY());
|
||||
pip_data.resize(ctx->getGridDimX() * ctx->getGridDimY());
|
||||
pip_mask.resize(ctx->getGridDimX() * ctx->getGridDimY());
|
||||
|
||||
auto set_param_mask_data = [&](CellInfo *cell, IdString param, uint32_t pip_mask, uint32_t &mask, uint32_t &data) {
|
||||
if (cell->params.count(param)) {
|
||||
mask |= pip_mask;
|
||||
if (int_or_default(cell->params, param, 0))
|
||||
data |= pip_mask;
|
||||
}
|
||||
};
|
||||
for (auto &cell : ctx->cells) {
|
||||
// We need to skip CPE_MULT since using CP outputs is mandatory
|
||||
// even if output is actually not connected
|
||||
|
|
@ -321,11 +338,64 @@ void GateMateImpl::postPlace()
|
|||
marked_used = true;
|
||||
if (marked_used)
|
||||
used_cpes[cell.second.get()->bel.tile] = true;
|
||||
|
||||
uint32_t mask = pip_mask[cell.second.get()->bel.tile];
|
||||
uint32_t data = pip_data[cell.second.get()->bel.tile];
|
||||
if (cell.second.get()->type == id_CPE_MULT) {
|
||||
mask |= PipMask::IS_MULT;
|
||||
data |= PipMask::IS_MULT;
|
||||
}
|
||||
if (cell.second.get()->type.in(id_CPE_ADDF, id_CPE_ADDF2)) {
|
||||
data |= PipMask::IS_ADDF;
|
||||
mask |= PipMask::IS_ADDF;
|
||||
}
|
||||
if (cell.second.get()->type == id_CPE_COMP) {
|
||||
data |= PipMask::IS_COMP;
|
||||
mask |= PipMask::IS_COMP;
|
||||
}
|
||||
set_param_mask_data(cell.second.get(), id_C_SELX, PipMask::C_SELX, mask, data);
|
||||
set_param_mask_data(cell.second.get(), id_C_SELY1, PipMask::C_SELY1, mask, data);
|
||||
set_param_mask_data(cell.second.get(), id_C_SELY2, PipMask::C_SELY2, mask, data);
|
||||
set_param_mask_data(cell.second.get(), id_C_SEL_C, PipMask::C_SEL_C, mask, data);
|
||||
set_param_mask_data(cell.second.get(), id_C_SEL_P, PipMask::C_SEL_P, mask, data);
|
||||
set_param_mask_data(cell.second.get(), id_C_Y12, PipMask::C_Y12, mask, data);
|
||||
set_param_mask_data(cell.second.get(), id_C_CX_I, PipMask::C_CX_I, mask, data);
|
||||
set_param_mask_data(cell.second.get(), id_C_CY1_I, PipMask::C_CY1_I, mask, data);
|
||||
set_param_mask_data(cell.second.get(), id_C_CY2_I, PipMask::C_CY2_I, mask, data);
|
||||
set_param_mask_data(cell.second.get(), id_C_PX_I, PipMask::C_PX_I, mask, data);
|
||||
set_param_mask_data(cell.second.get(), id_C_PY1_I, PipMask::C_PY1_I, mask, data);
|
||||
set_param_mask_data(cell.second.get(), id_C_PY2_I, PipMask::C_PY2_I, mask, data);
|
||||
pip_mask[cell.second.get()->bel.tile] = mask;
|
||||
pip_data[cell.second.get()->bel.tile] = data;
|
||||
}
|
||||
}
|
||||
bool GateMateImpl::checkPipAvail(PipId pip) const
|
||||
{
|
||||
const auto &extra_data = *pip_extra_data(pip);
|
||||
if (!use_cp_for_clk && extra_data.type == PipExtra::PIP_EXTRA_MUX) {
|
||||
if (extra_data.value == 1 && IdString(extra_data.name).in(id_C_CLKSEL, id_C_ENSEL))
|
||||
return false;
|
||||
}
|
||||
if (!use_cp_for_cpe && extra_data.type == PipExtra::PIP_EXTRA_MUX && extra_data.resource != 0 &&
|
||||
extra_data.resource <= PipMask::C_PY2_I) {
|
||||
return false;
|
||||
}
|
||||
if (!use_bridges && extra_data.type == PipExtra::PIP_EXTRA_MUX &&
|
||||
IdString(extra_data.name) == ctx->id("CPE.C_SN")) {
|
||||
return false;
|
||||
}
|
||||
if (extra_data.type == PipExtra::PIP_EXTRA_MUX && (extra_data.block != 0)) {
|
||||
if (pip_mask[pip.tile] & extra_data.block) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (extra_data.type == PipExtra::PIP_EXTRA_MUX && (extra_data.resource != 0)) {
|
||||
if (pip_mask[pip.tile] & extra_data.resource) {
|
||||
if ((pip_data[pip.tile] & extra_data.resource) != (extra_data.value ? extra_data.resource : 0)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (extra_data.type != PipExtra::PIP_EXTRA_MUX || (extra_data.flags & MUX_ROUTING) == 0)
|
||||
return true;
|
||||
if (used_cpes[pip.tile])
|
||||
|
|
@ -338,6 +408,21 @@ void GateMateImpl::preRoute()
|
|||
route_mult();
|
||||
route_clock();
|
||||
ctx->assignArchInfo();
|
||||
|
||||
for (auto &net : ctx->nets) {
|
||||
NetInfo *ni = net.second.get();
|
||||
if (ni->wires.empty())
|
||||
continue;
|
||||
for (auto &w : ni->wires) {
|
||||
if (w.second.pip != PipId()) {
|
||||
const auto &extra_data = *pip_extra_data(w.second.pip);
|
||||
if (extra_data.type == PipExtra::PIP_EXTRA_MUX && extra_data.resource != 0) {
|
||||
pip_mask[w.second.pip.tile] |= extra_data.resource;
|
||||
pip_data[w.second.pip.tile] |= extra_data.value ? extra_data.resource : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GateMateImpl::reassign_bridges(NetInfo *ni, const dict<WireId, PipMap> &net_wires, WireId wire,
|
||||
|
|
@ -380,11 +465,18 @@ void GateMateImpl::reassign_bridges(NetInfo *ni, const dict<WireId, PipMap> &net
|
|||
NetInfo *new_net = ctx->createNet(ctx->idf("%s$muxout", name.c_str(ctx)));
|
||||
IdString in_port = ctx->idf("IN%d", extra_data.value + 1);
|
||||
|
||||
cell->addInput(in_port);
|
||||
cell->connectPort(in_port, ni);
|
||||
auto add_port = [&](const IdString id, PortType dir) {
|
||||
cell->ports[id].name = id;
|
||||
cell->ports[id].type = dir;
|
||||
cell->cell_bel_pins[id] = std::vector{id};
|
||||
};
|
||||
|
||||
cell->addOutput(id_MUXOUT);
|
||||
add_port(in_port, PORT_IN);
|
||||
add_port(id_MUXOUT, PORT_OUT);
|
||||
|
||||
cell->connectPort(in_port, ni);
|
||||
cell->connectPort(id_MUXOUT, new_net);
|
||||
pass_backtrace[cell->name][id_MUXOUT] = in_port;
|
||||
|
||||
num++;
|
||||
|
||||
|
|
@ -392,11 +484,122 @@ void GateMateImpl::reassign_bridges(NetInfo *ni, const dict<WireId, PipMap> &net
|
|||
}
|
||||
}
|
||||
|
||||
void GateMateImpl::reassign_cplines(NetInfo *ni, const dict<WireId, PipMap> &net_wires, WireId wire,
|
||||
dict<WireId, IdString> &wire_to_net, int &num, IdString in_port)
|
||||
{
|
||||
wire_to_net.insert({wire, ni->name});
|
||||
|
||||
for (auto pip : ctx->getPipsDownhill(wire)) {
|
||||
auto dst = ctx->getPipDstWire(pip);
|
||||
|
||||
// Ignore wires not part of the net
|
||||
auto it = net_wires.find(dst);
|
||||
if (it == net_wires.end())
|
||||
continue;
|
||||
// Ignore pips if the wire is driven by another pip.
|
||||
if (pip != it->second.pip)
|
||||
continue;
|
||||
// Ignore wires already visited.
|
||||
if (wire_to_net.count(dst))
|
||||
continue;
|
||||
|
||||
const auto &extra_data = *pip_extra_data(pip);
|
||||
// If not a CP line pip, just recurse.
|
||||
if (extra_data.type != PipExtra::PIP_EXTRA_MUX || extra_data.resource == 0) {
|
||||
reassign_cplines(ni, net_wires, dst, wire_to_net, num, in_port);
|
||||
continue;
|
||||
}
|
||||
|
||||
// We have a bridge that needs to be translated to a bel.
|
||||
IdStringList id = ctx->getPipName(pip);
|
||||
Loc loc = ctx->getPipLocation(pip);
|
||||
BelId bel = ctx->getBelByLocation({loc.x, loc.y, CPE_CPLINES_Z});
|
||||
CellInfo *cell = ctx->getBoundBelCell(bel);
|
||||
if (!cell) {
|
||||
IdString name = ctx->idf("cplines$%s", id[0].c_str(ctx));
|
||||
cell = ctx->createCell(name, id_CPE_CPLINES);
|
||||
auto add_port = [&](const IdString id, PortType dir) {
|
||||
cell->ports[id].name = id;
|
||||
cell->ports[id].type = dir;
|
||||
cell->cell_bel_pins[id] = std::vector{id};
|
||||
};
|
||||
|
||||
add_port(id_OUT1, PORT_IN);
|
||||
add_port(id_OUT2, PORT_IN);
|
||||
add_port(id_COMPOUT, PORT_IN);
|
||||
|
||||
add_port(id_CINX, PORT_IN);
|
||||
add_port(id_PINX, PORT_IN);
|
||||
add_port(id_CINY1, PORT_IN);
|
||||
add_port(id_PINY1, PORT_IN);
|
||||
add_port(id_CINY2, PORT_IN);
|
||||
add_port(id_PINY2, PORT_IN);
|
||||
|
||||
add_port(id_COUTX, PORT_OUT);
|
||||
add_port(id_POUTX, PORT_OUT);
|
||||
add_port(id_COUTY1, PORT_OUT);
|
||||
add_port(id_POUTY1, PORT_OUT);
|
||||
add_port(id_COUTY2, PORT_OUT);
|
||||
add_port(id_POUTY2, PORT_OUT);
|
||||
|
||||
ctx->bindBel(bel, cell, PlaceStrength::STRENGTH_FIXED);
|
||||
}
|
||||
|
||||
cell->setParam(ctx->getGroupName(ctx->getResourceKeyForPip(pip))[1],
|
||||
Property(extra_data.value, extra_data.bits));
|
||||
|
||||
// We have to discover the ports needed by this config.
|
||||
auto input_port_map = dict<IdString, IdString>{
|
||||
{ctx->id("CPE.CINX"), id_CINX},
|
||||
{ctx->id("CPE.CINY1"), id_CINY1},
|
||||
{ctx->id("CPE.CINY2"), id_CINY2},
|
||||
{ctx->id("CPE.PINX"), id_PINX},
|
||||
{ctx->id("CPE.PINY1"), id_PINY1},
|
||||
{ctx->id("CPE.PINY2"), id_PINY2},
|
||||
{ctx->id("CPE.OUT1_IN_int"), id_OUT1},
|
||||
{ctx->id("CPE.OUT2_IN_int"), id_OUT2},
|
||||
{ctx->id("CPE.COMPOUT_IN_int"), id_COMPOUT},
|
||||
};
|
||||
|
||||
auto input_port_name = input_port_map.find(ctx->getWireName(ctx->getPipSrcWire(pip))[1]);
|
||||
if (input_port_name != input_port_map.end()) {
|
||||
if (cell->getPort(input_port_name->second) == nullptr) {
|
||||
cell->connectPort(input_port_name->second, ni);
|
||||
in_port = input_port_name->second;
|
||||
} else
|
||||
NPNR_ASSERT(cell->getPort(input_port_name->second) == ni);
|
||||
}
|
||||
|
||||
auto output_port_map =
|
||||
dict<IdString, IdString>{{ctx->id("CPE.COUTX"), id_COUTX}, {ctx->id("CPE.COUTY1"), id_COUTY1},
|
||||
{ctx->id("CPE.COUTY2"), id_COUTY2}, {ctx->id("CPE.POUTX"), id_POUTX},
|
||||
{ctx->id("CPE.POUTY1"), id_POUTY1}, {ctx->id("CPE.POUTY2"), id_POUTY2}};
|
||||
|
||||
auto output_port_name = output_port_map.find(ctx->getWireName(ctx->getPipDstWire(pip))[1]);
|
||||
if (output_port_name != output_port_map.end()) {
|
||||
NetInfo *new_net =
|
||||
ctx->createNet(ctx->idf("%s$%s", cell->name.c_str(ctx), output_port_name->second.c_str(ctx)));
|
||||
|
||||
cell->addOutput(output_port_name->second);
|
||||
cell->connectPort(output_port_name->second, new_net);
|
||||
pass_backtrace[cell->name][output_port_name->second] = in_port;
|
||||
|
||||
num++;
|
||||
|
||||
reassign_cplines(new_net, net_wires, dst, wire_to_net, num, in_port);
|
||||
} else {
|
||||
// this is an internal resource pip; recurse anyway.
|
||||
reassign_cplines(ni, net_wires, dst, wire_to_net, num, in_port);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GateMateImpl::postRoute()
|
||||
{
|
||||
int num = 0;
|
||||
|
||||
pool<IdString> nets_with_bridges;
|
||||
pool<IdString> nets_with_cplines;
|
||||
|
||||
for (auto &net : ctx->nets) {
|
||||
NetInfo *ni = net.second.get();
|
||||
|
|
@ -451,19 +654,76 @@ void GateMateImpl::postRoute()
|
|||
}
|
||||
}
|
||||
|
||||
num = 0;
|
||||
|
||||
for (auto &net : ctx->nets) {
|
||||
NetInfo *ni = net.second.get();
|
||||
for (auto &w : ni->wires) {
|
||||
if (w.second.pip != PipId()) {
|
||||
const auto &extra_data = *pip_extra_data(w.second.pip);
|
||||
if (extra_data.type == PipExtra::PIP_EXTRA_MUX && extra_data.resource != 0) {
|
||||
nets_with_cplines.insert(ni->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto net_name : nets_with_cplines) {
|
||||
auto *ni = ctx->nets.at(net_name).get();
|
||||
auto net_wires = ni->wires; // copy wires to preserve across unbind/rebind.
|
||||
auto wire_to_net = dict<WireId, IdString>{};
|
||||
auto wire_to_port = dict<WireId, std::vector<PortRef>>{};
|
||||
|
||||
for (auto &usr : ni->users)
|
||||
for (auto sink_wire : ctx->getNetinfoSinkWires(ni, usr)) {
|
||||
auto result = wire_to_port.find(sink_wire);
|
||||
if (result == wire_to_port.end())
|
||||
wire_to_port.insert({sink_wire, std::vector<PortRef>{usr}});
|
||||
else
|
||||
result->second.push_back(usr);
|
||||
}
|
||||
|
||||
// traverse the routing tree to assign bridge nets to wires.
|
||||
reassign_cplines(ni, net_wires, ctx->getNetinfoSourceWire(ni), wire_to_net, num, IdString());
|
||||
|
||||
for (auto &pair : net_wires)
|
||||
ctx->unbindWire(pair.first);
|
||||
|
||||
for (auto &pair : net_wires) {
|
||||
auto wire = pair.first;
|
||||
auto pip = pair.second.pip;
|
||||
auto strength = pair.second.strength;
|
||||
auto *net = ctx->nets.at(wire_to_net.at(wire)).get();
|
||||
if (pip == PipId())
|
||||
ctx->bindWire(wire, net, strength);
|
||||
else
|
||||
ctx->bindPip(pip, net, strength);
|
||||
|
||||
if (wire_to_port.count(wire)) {
|
||||
for (auto sink : wire_to_port.at(wire)) {
|
||||
NPNR_ASSERT(sink.cell != nullptr && sink.port != IdString());
|
||||
sink.cell->disconnectPort(sink.port);
|
||||
sink.cell->connectPort(sink.port, net);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dict<IdString, int> cfg;
|
||||
dict<IdString, IdString> port_mapping;
|
||||
auto add_input = [&](IdString orig_port, IdString port, bool merged) -> bool {
|
||||
static dict<IdString, IdString> convert_port = {
|
||||
{ctx->id("CPE.IN1"), id_IN1}, {ctx->id("CPE.IN2"), id_IN2}, {ctx->id("CPE.IN3"), id_IN3},
|
||||
{ctx->id("CPE.IN4"), id_IN4}, {ctx->id("CPE.IN5"), id_IN1}, {ctx->id("CPE.IN6"), id_IN2},
|
||||
{ctx->id("CPE.IN7"), id_IN3}, {ctx->id("CPE.IN8"), id_IN4}, {ctx->id("CPE.PINY1"), id_PINY1},
|
||||
{ctx->id("CPE.CINX"), id_CINX}, {ctx->id("CPE.PINX"), id_PINX}};
|
||||
{ctx->id("CPE.IN1"), id_IN1}, {ctx->id("CPE.IN2"), id_IN2}, {ctx->id("CPE.IN3"), id_IN3},
|
||||
{ctx->id("CPE.IN4"), id_IN4}, {ctx->id("CPE.IN5"), id_IN1}, {ctx->id("CPE.IN6"), id_IN2},
|
||||
{ctx->id("CPE.IN7"), id_IN3}, {ctx->id("CPE.IN8"), id_IN4}, {ctx->id("CPE.PINY1"), id_PINY1},
|
||||
{ctx->id("CPE.PINY2"), id_PINY2}, {ctx->id("CPE.CINY2"), id_CINY2}, {ctx->id("CPE.CLK"), id_CLK},
|
||||
{ctx->id("CPE.EN"), id_EN}, {ctx->id("CPE.CINX"), id_CINX}, {ctx->id("CPE.PINX"), id_PINX}};
|
||||
static dict<IdString, IdString> convert_port_merged = {
|
||||
{ctx->id("CPE.IN1"), id_IN1}, {ctx->id("CPE.IN2"), id_IN2}, {ctx->id("CPE.IN3"), id_IN3},
|
||||
{ctx->id("CPE.IN4"), id_IN4}, {ctx->id("CPE.IN5"), id_IN5}, {ctx->id("CPE.IN6"), id_IN6},
|
||||
{ctx->id("CPE.IN7"), id_IN7}, {ctx->id("CPE.IN8"), id_IN8}, {ctx->id("CPE.PINY1"), id_PINY1},
|
||||
{ctx->id("CPE.CINX"), id_CINX}, {ctx->id("CPE.PINX"), id_PINX}};
|
||||
{ctx->id("CPE.IN1"), id_IN1}, {ctx->id("CPE.IN2"), id_IN2}, {ctx->id("CPE.IN3"), id_IN3},
|
||||
{ctx->id("CPE.IN4"), id_IN4}, {ctx->id("CPE.IN5"), id_IN5}, {ctx->id("CPE.IN6"), id_IN6},
|
||||
{ctx->id("CPE.IN7"), id_IN7}, {ctx->id("CPE.IN8"), id_IN8}, {ctx->id("CPE.PINY1"), id_PINY1},
|
||||
{ctx->id("CPE.PINY2"), id_PINY2}, {ctx->id("CPE.CINY2"), id_CINY2}, {ctx->id("CPE.CLK"), id_CLK},
|
||||
{ctx->id("CPE.EN"), id_EN}, {ctx->id("CPE.CINX"), id_CINX}, {ctx->id("CPE.PINX"), id_PINX}};
|
||||
if (convert_port.count(port)) {
|
||||
port_mapping.emplace(orig_port, merged ? convert_port_merged[port] : convert_port[port]);
|
||||
return true;
|
||||
|
|
@ -645,6 +905,24 @@ void GateMateImpl::postRoute()
|
|||
if (cfg.count(id_C_I4) && cfg.at(id_C_I4) == 1)
|
||||
cell.second->params[id_C_I4] = Property(1, 1);
|
||||
}
|
||||
if (cell.second->type.in(id_CPE_FF, id_CPE_FF_L, id_CPE_FF_U, id_CPE_LATCH)) {
|
||||
cfg.clear();
|
||||
port_mapping.clear();
|
||||
check_input(cell.second.get(), id_CLK_INT, false);
|
||||
check_input(cell.second.get(), id_EN_INT, false);
|
||||
if (cfg.count(id_C_CLKSEL) && cfg.at(id_C_CLKSEL) == 1) {
|
||||
uint8_t val = int_or_default(cell.second->params, id_C_CPE_CLK, 0) & 1;
|
||||
cell.second->params[id_C_CPE_CLK] = Property(val ? 3 : 0, 2);
|
||||
cell.second->params[id_C_CLKSEL] = Property(1, 1);
|
||||
}
|
||||
if (cfg.count(id_C_ENSEL) && cfg.at(id_C_ENSEL) == 1) {
|
||||
uint8_t val = int_or_default(cell.second->params, id_C_CPE_EN, 0) & 1;
|
||||
cell.second->params[id_C_CPE_EN] = Property(val ? 3 : 0, 2);
|
||||
cell.second->params[id_C_ENSEL] = Property(1, 1);
|
||||
}
|
||||
cell.second->renamePort(id_CLK_INT, port_mapping[id_CLK_INT]);
|
||||
cell.second->renamePort(id_EN_INT, port_mapping[id_EN_INT]);
|
||||
}
|
||||
}
|
||||
ctx->assignArchInfo();
|
||||
|
||||
|
|
@ -681,6 +959,25 @@ void GateMateImpl::expandBoundingBox(BoundingBox &bb) const
|
|||
bb.y1 = std::min((bb.y1 & 0xfffe) + 5, ctx->getGridDimY());
|
||||
}
|
||||
|
||||
GroupId GateMateImpl::getResourceKeyForPip(PipId pip) const
|
||||
{
|
||||
const auto &extra_data = *pip_extra_data(pip);
|
||||
if (extra_data.type != PipExtra::PIP_EXTRA_MUX || extra_data.group_index == 0)
|
||||
return GroupId();
|
||||
|
||||
return GroupId(pip.tile, extra_data.group_index);
|
||||
}
|
||||
|
||||
int GateMateImpl::getResourceValueForPip(PipId pip) const
|
||||
{
|
||||
const auto &extra_data = *pip_extra_data(pip);
|
||||
if (extra_data.type != PipExtra::PIP_EXTRA_MUX || extra_data.resource == 0)
|
||||
return 0;
|
||||
return extra_data.value;
|
||||
}
|
||||
|
||||
bool GateMateImpl::isGroupResource(GroupId group) const { return ctx->getGroupType(group) == id_RESOURCE; }
|
||||
|
||||
void GateMateImpl::configurePlacerHeap(PlacerHeapCfg &cfg)
|
||||
{
|
||||
cfg.chainRipup = true;
|
||||
|
|
@ -722,8 +1019,8 @@ void GateMateImpl::assign_cell_info()
|
|||
CellInfo *ci = cell.second.get();
|
||||
auto &fc = fast_cell_info.at(ci->flat_index);
|
||||
if (getBelBucketForCellType(ci->type) == id_CPE_FF) {
|
||||
fc.ff_en = ci->getPort(id_EN);
|
||||
fc.ff_clk = ci->getPort(id_CLK);
|
||||
fc.ff_en = ci->getPort(id_EN_INT);
|
||||
fc.ff_clk = ci->getPort(id_CLK_INT);
|
||||
fc.ff_sr = ci->getPort(id_SR);
|
||||
fc.config = get_dff_config(ci);
|
||||
fc.used = true;
|
||||
|
|
|
|||
|
|
@ -79,6 +79,10 @@ struct GateMateImpl : HimbaechelAPI
|
|||
|
||||
bool isPipInverting(PipId pip) const override;
|
||||
|
||||
GroupId getResourceKeyForPip(PipId pip) const override;
|
||||
int getResourceValueForPip(PipId pip) const override;
|
||||
bool isGroupResource(GroupId group) const override;
|
||||
|
||||
const GateMateTileExtraDataPOD *tile_extra_data(int tile) const;
|
||||
const GateMateBelExtraDataPOD *bel_extra_data(BelId bel) const;
|
||||
const GateMatePipExtraDataPOD *pip_extra_data(PipId pip) const;
|
||||
|
|
@ -95,8 +99,9 @@ struct GateMateImpl : HimbaechelAPI
|
|||
pool<IdString> multiplier_a_passthru_lowers;
|
||||
pool<IdString> multiplier_a_passthru_uppers;
|
||||
pool<IdString> multiplier_zero_drivers;
|
||||
std::vector<CellInfo *> multipliers;
|
||||
std::vector<bool> used_cpes;
|
||||
std::vector<uint32_t> pip_data;
|
||||
std::vector<uint32_t> pip_mask;
|
||||
int fpga_mode;
|
||||
int timing_mode;
|
||||
std::map<const NetInfo *, int> global_signals;
|
||||
|
|
@ -109,6 +114,7 @@ struct GateMateImpl : HimbaechelAPI
|
|||
MultiDieStrategy strategy;
|
||||
dict<int, IdString> index_to_die;
|
||||
dict<IdString, int> die_to_index;
|
||||
dict<IdString, dict<IdString, IdString>> pass_backtrace;
|
||||
|
||||
private:
|
||||
bool getChildPlacement(const BaseClusterInfo *cluster, Loc root_loc,
|
||||
|
|
@ -123,6 +129,8 @@ struct GateMateImpl : HimbaechelAPI
|
|||
void route_mult();
|
||||
void reassign_bridges(NetInfo *net, const dict<WireId, PipMap> &net_wires, WireId wire,
|
||||
dict<WireId, IdString> &wire_to_net, int &num);
|
||||
void reassign_cplines(NetInfo *net, const dict<WireId, PipMap> &net_wires, WireId wire,
|
||||
dict<WireId, IdString> &wire_to_net, int &num, IdString in_port);
|
||||
void repack();
|
||||
|
||||
bool get_delay_from_tmg_db(IdString id, DelayQuad &delay) const;
|
||||
|
|
@ -142,6 +150,9 @@ struct GateMateImpl : HimbaechelAPI
|
|||
std::map<IdString, const GateMateTimingExtraDataPOD *> timing;
|
||||
dict<IdString, int> ram_signal_clk;
|
||||
IdString forced_die;
|
||||
bool use_cp_for_clk;
|
||||
bool use_cp_for_cpe;
|
||||
bool use_bridges;
|
||||
};
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
||||
|
|
|
|||
|
|
@ -73,6 +73,9 @@ class PipExtraData(BBAStruct):
|
|||
value: int = 0
|
||||
invert: int = 0
|
||||
plane: int = 0
|
||||
block: int = 0
|
||||
resource: int = 0
|
||||
group_index: int = 0
|
||||
|
||||
def serialise_lists(self, context: str, bba: BBAWriter):
|
||||
pass
|
||||
|
|
@ -85,6 +88,9 @@ class PipExtraData(BBAStruct):
|
|||
bba.u8(self.plane)
|
||||
bba.u8(0)
|
||||
bba.u16(0)
|
||||
bba.u32(self.block)
|
||||
bba.u32(self.resource)
|
||||
bba.u32(self.group_index)
|
||||
|
||||
@dataclass
|
||||
class BelPinConstraint(BBAStruct):
|
||||
|
|
@ -224,7 +230,22 @@ def set_timings(ch):
|
|||
assert k in timing, f"pip class {k} not found in timing data"
|
||||
tmg.set_pip_class(grade=speed, name=k, delay=convert_timing(timing[k]))
|
||||
|
||||
EXPECTED_VERSION = 1.11
|
||||
EXPECTED_VERSION = 1.12
|
||||
|
||||
RESOURCE_NAMES = {
|
||||
1 << 0: "C_SELX",
|
||||
1 << 1: "C_SELY1",
|
||||
1 << 2: "C_SELY2",
|
||||
1 << 3: "C_SEL_C",
|
||||
1 << 4: "C_SEL_P",
|
||||
1 << 5: "C_Y12",
|
||||
1 << 6: "C_CX_I",
|
||||
1 << 7: "C_CY1_I",
|
||||
1 << 8: "C_CY2_I",
|
||||
1 << 9: "C_PX_I",
|
||||
1 << 10: "C_PY1_I",
|
||||
1 << 11: "C_PY2_I",
|
||||
}
|
||||
|
||||
def main():
|
||||
# Range needs to be +1, but we are adding +2 more to coordinates, since
|
||||
|
|
@ -278,6 +299,9 @@ def main():
|
|||
tt = ch.create_tile_type(type_name)
|
||||
for group in sorted(die.get_groups_for_type(type_name)):
|
||||
tt.create_group(group.name, group.type)
|
||||
if ("CPE" in type_name):
|
||||
for name in RESOURCE_NAMES.values():
|
||||
tt.create_group(name, "RESOURCE")
|
||||
for wire in sorted(die.get_endpoints_for_type(type_name)):
|
||||
tt.create_wire(wire.name, wire.type)
|
||||
if type_name in new_wires:
|
||||
|
|
@ -310,7 +334,12 @@ def main():
|
|||
plane = int(mux.name[10:12])
|
||||
if mux.name == "CPE.C_SN":
|
||||
mux_flags |= MUX_ROUTING
|
||||
pp.extra_data = PipExtraData(PIP_EXTRA_MUX, ch.strs.id(mux.name), mux.bits, mux.value, mux_flags, plane)
|
||||
group_index = 0
|
||||
if mux.resource > 0:
|
||||
group = RESOURCE_NAMES.get(mux.resource, "UNKNOWN")
|
||||
group_index = tt._group2idx[tt.strs.id(group)]
|
||||
tt.add_pip_to_group(pp, group)
|
||||
pp.extra_data = PipExtraData(PIP_EXTRA_MUX, ch.strs.id(mux.name), mux.bits, mux.value, mux_flags, plane, mux.block, mux.resource, group_index)
|
||||
if type_name in new_wires:
|
||||
for wire in sorted(new_wires[type_name]):
|
||||
delay = wire_delay[wire]
|
||||
|
|
|
|||
|
|
@ -41,6 +41,13 @@ void GateMateImpl::drawBel(std::vector<GraphicElement> &g, GraphicElement::style
|
|||
el.y2 = el.y1 + 0.50;
|
||||
g.push_back(el);
|
||||
break;
|
||||
case id_CPE_CPLINES.index:
|
||||
el.x1 = loc.x + 0.72;
|
||||
el.x2 = el.x1 + 0.06;
|
||||
el.y1 = loc.y + 0.25;
|
||||
el.y2 = el.y1 + 0.50;
|
||||
g.push_back(el);
|
||||
break;
|
||||
case id_CPE_LT_L.index:
|
||||
el.x1 = loc.x + 0.20;
|
||||
el.x2 = el.x1 + 0.20;
|
||||
|
|
|
|||
|
|
@ -381,8 +381,6 @@ void GateMatePacker::repack_cpe()
|
|||
if (l.z == CPE_LT_L_Z) {
|
||||
if (!cell.second->params.count(id_INIT_L20))
|
||||
cell.second->params[id_INIT_L20] = Property(LUT_D1, 4);
|
||||
if (cell.second->getPort(id_D0_10)) {
|
||||
}
|
||||
}
|
||||
cell.second->params[id_L2T4_UPPER] = Property((l.z == CPE_LT_U_Z) ? 1 : 0, 1);
|
||||
} else if (cell.second->type.in(id_CPE_LT_L)) {
|
||||
|
|
|
|||
|
|
@ -33,9 +33,9 @@ bool GateMatePacker::are_ffs_compatible(CellInfo *dff, CellInfo *other)
|
|||
{
|
||||
if (!other)
|
||||
return true;
|
||||
if (dff->getPort(id_CLK) != other->getPort(id_CLK))
|
||||
if (dff->getPort(id_CLK_INT) != other->getPort(id_CLK))
|
||||
return false;
|
||||
if (dff->getPort(id_EN) != other->getPort(id_EN))
|
||||
if (dff->getPort(id_EN_INT) != other->getPort(id_EN))
|
||||
return false;
|
||||
if (dff->getPort(id_SR) != other->getPort(id_SR))
|
||||
return false;
|
||||
|
|
@ -192,6 +192,8 @@ void GateMatePacker::pack_cpe()
|
|||
ci.constr_children.push_back(dff);
|
||||
dff->renamePort(id_D, id_DIN);
|
||||
dff->renamePort(id_Q, id_DOUT);
|
||||
dff->renamePort(id_CLK, id_CLK_INT);
|
||||
dff->renamePort(id_EN, id_EN_INT);
|
||||
dff->type = (dff->type == id_CC_DLT) ? id_CPE_LATCH : id_CPE_FF;
|
||||
};
|
||||
|
||||
|
|
@ -387,6 +389,8 @@ void GateMatePacker::pack_cpe()
|
|||
ci.cluster = ci.name;
|
||||
ci.constr_children.push_back(lt);
|
||||
ci.renamePort(id_Q, id_DOUT);
|
||||
ci.renamePort(id_CLK, id_CLK_INT);
|
||||
ci.renamePort(id_EN, id_EN_INT);
|
||||
NetInfo *d_net = ci.getPort(id_D);
|
||||
if (d_net == net_PACKER_GND) {
|
||||
lt->params[id_INIT_L10] = Property(LUT_ZERO, 4);
|
||||
|
|
@ -537,6 +541,8 @@ void GateMatePacker::pack_addf()
|
|||
cell->constr_children.push_back(dff);
|
||||
dff->renamePort(id_D, id_DIN);
|
||||
dff->renamePort(id_Q, id_DOUT);
|
||||
dff->renamePort(id_CLK, id_CLK_INT);
|
||||
dff->renamePort(id_EN, id_EN_INT);
|
||||
dff->type = (dff->type == id_CC_DLT) ? id_CPE_LATCH : id_CPE_FF;
|
||||
return dff;
|
||||
}
|
||||
|
|
@ -567,6 +573,7 @@ void GateMatePacker::pack_addf()
|
|||
|
||||
CellInfo *ci_cplines = create_cell_ptr(id_CPE_CPLINES, ctx->idf("%s$ci_cplines", root->name.c_str(ctx)));
|
||||
ci_cplines->params[id_C_SELY1] = Property(1, 1);
|
||||
ci_cplines->params[id_C_SEL_C] = Property(0, 1);
|
||||
ci_cplines->params[id_C_CY1_I] = Property(1, 1);
|
||||
root->constr_children.push_back(ci_cplines);
|
||||
ci_cplines->cluster = root->name;
|
||||
|
|
@ -763,6 +770,8 @@ std::pair<CellInfo *, CellInfo *> GateMatePacker::move_ram_io(CellInfo *cell, Id
|
|||
/* if (ci.type.in(id_CC_DFF, id_CC_DLT)) {
|
||||
cpe_half = create_cell_ptr(id_CPE_L2T4, ctx->idf("%s$%s_cpe", cell->name.c_str(ctx), oPort.c_str(ctx)));
|
||||
ci.renamePort(id_Q, id_DOUT);
|
||||
ci.renamePort(id_CLK, id_CLK_INT);
|
||||
ci.renamePort(id_EN, id_EN_INT);
|
||||
NetInfo *d_net = ci.getPort(id_D);
|
||||
if (d_net == net_PACKER_GND) {
|
||||
cpe_half->params[id_INIT_L10] = Property(LUT_ZERO, 4);
|
||||
|
|
|
|||
|
|
@ -540,11 +540,11 @@ void GateMatePacker::pack_io_sel()
|
|||
if (do_net->driver.cell && do_net->driver.cell->type == id_CC_LUT1 && do_net->users.entries() == 1) {
|
||||
NetInfo *net = do_net->driver.cell->getPort(id_I0);
|
||||
if (net->driver.cell && net->driver.cell->type == id_CC_ODDR && net->users.entries() == 1) {
|
||||
do_net = net;
|
||||
packed_cells.insert(net->driver.cell->name);
|
||||
packed_cells.insert(do_net->driver.cell->name);
|
||||
// Inverting both input is equal to inverter at output
|
||||
is_inverted[0] = true;
|
||||
is_inverted[1] = true;
|
||||
do_net = net;
|
||||
}
|
||||
}
|
||||
if (do_net->driver.cell && do_net->driver.cell->type == id_CC_ODDR && do_net->users.entries() == 1) {
|
||||
|
|
|
|||
|
|
@ -936,9 +936,44 @@ void GateMatePacker::pack_mult()
|
|||
if (p_net && p_net->users.entries() == 1) {
|
||||
auto *p_net_sink = (*p_net->users.begin()).cell;
|
||||
NPNR_ASSERT(p_net_sink != nullptr);
|
||||
if (p_net_sink->type == id_CC_DFF && !are_ffs_compatible(p_zero_sink, p_net_sink)) {
|
||||
log_info(" Inconsistent control set; not packing output register.\n");
|
||||
return false;
|
||||
if (p_net_sink->type == id_CC_DFF) {
|
||||
bool incompatible = false;
|
||||
if (p_zero_sink->getPort(id_CLK) != p_net_sink->getPort(id_CLK)) {
|
||||
const char *p_zero_clk = "(none)";
|
||||
const char *p_net_clk = "(none)";
|
||||
if (p_zero_sink->getPort(id_CLK) != nullptr)
|
||||
p_zero_clk = p_zero_sink->getPort(id_CLK)->name.c_str(ctx);
|
||||
if (p_net_sink->getPort(id_CLK) != nullptr)
|
||||
p_net_clk = p_net_sink->getPort(id_CLK)->name.c_str(ctx);
|
||||
log_info(" registers have inconsistent clocks: %s vs %s\n", p_zero_clk, p_net_clk);
|
||||
incompatible = true;
|
||||
}
|
||||
if (p_zero_sink->getPort(id_EN) != p_net_sink->getPort(id_EN)) {
|
||||
const char *p_zero_en = "(none)";
|
||||
const char *p_net_en = "(none)";
|
||||
if (p_zero_sink->getPort(id_EN) != nullptr)
|
||||
p_zero_en = p_zero_sink->getPort(id_EN)->name.c_str(ctx);
|
||||
if (p_net_sink->getPort(id_EN) != nullptr)
|
||||
p_net_en = p_net_sink->getPort(id_EN)->name.c_str(ctx);
|
||||
log_info(" registers have inconsistent enables: %s vs %s\n", p_zero_en, p_net_en);
|
||||
incompatible = true;
|
||||
}
|
||||
if (p_zero_sink->getPort(id_SR) != p_net_sink->getPort(id_SR)) {
|
||||
const char *p_zero_sr = "(none)";
|
||||
const char *p_net_sr = "(none)";
|
||||
if (p_zero_sink->getPort(id_SR) != nullptr)
|
||||
p_zero_sr = p_zero_sink->getPort(id_SR)->name.c_str(ctx);
|
||||
if (p_net_sink->getPort(id_SR) != nullptr)
|
||||
p_net_sr = p_net_sink->getPort(id_EN)->name.c_str(ctx);
|
||||
log_info(" registers have inconsistent resets: %s vs %s\n", p_zero_sr, p_net_sr);
|
||||
incompatible = true;
|
||||
}
|
||||
if (uarch->get_dff_config(p_zero_sink) != uarch->get_dff_config(p_net_sink))
|
||||
log_info(" registers have different configurations\n");
|
||||
if (incompatible) {
|
||||
log_info(" ...not packing output register\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,11 +52,11 @@ void GateMateImpl::route_clock()
|
|||
auto reserved_wires = dict<WireId, IdString>{};
|
||||
|
||||
auto feeds_clk_port = [&](PortRef &port) {
|
||||
return (ctx->getBelBucketForCellType(port.cell->type) == id_CPE_FF) && port.port.in(id_CLK);
|
||||
return (ctx->getBelBucketForCellType(port.cell->type) == id_CPE_FF) && port.port.in(id_CLK_INT);
|
||||
};
|
||||
|
||||
auto feeds_ddr_port = [&](NetInfo *net, PortRef &port) {
|
||||
return this->ddr_nets.find(net->name) != this->ddr_nets.end() && port.port == id_IN1;
|
||||
return this->ddr_nets.find(net->name) != this->ddr_nets.end() && port.port == id_D0_10;
|
||||
};
|
||||
|
||||
auto pip_plane = [&](PipId pip) {
|
||||
|
|
@ -151,6 +151,12 @@ void GateMateImpl::route_clock()
|
|||
}
|
||||
|
||||
for (auto dh : ctx->getPipsDownhill(curr.wire)) {
|
||||
const auto &extra_data = *pip_extra_data(dh);
|
||||
// Allow only CINY2->COUTY2 pass through for clock router
|
||||
if (extra_data.type == PipExtra::PIP_EXTRA_MUX && extra_data.resource != 0) {
|
||||
if (!(extra_data.resource == PipMask::C_CY2_I && extra_data.value == 0))
|
||||
continue;
|
||||
}
|
||||
if (!ctx->checkPipAvailForNet(dh, clk_net))
|
||||
continue;
|
||||
WireId dst = ctx->getPipDstWire(dh);
|
||||
|
|
|
|||
Loading…
Reference in New Issue