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 commit 3da2769e31.

* Revert "verify inversion before/after assigning bridges"

This reverts commit 8613ee17c8.

* 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:
Miodrag Milanović 2026-02-25 08:22:16 +01:00 committed by GitHub
parent 501b36e646
commit b8a6559a3f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 702 additions and 58 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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