
A simple router that takes advantage of the fact that in each cell with DFFs their CLK inputs can directly connect to the global clock network. Networks with a large number of such sinks are sought and then each network is assigned to the available independent global clock networks. There are limited possibilities for routing mixed networks, that is, when the sinks are not only CLKs: in this case an attempt is made to use wires such as SN10/20 and EW10/20, that is, one short transition can be added between the global clock network and the sink. * At this time, networks with a source other than the I/O pin are not supported. This is typical for Tangnano4k and runber boards. * Router is disabled by default, you need to specify option --enable-globals to activate * No new chip bases are required. This may change in the distant future. Signed-off-by: YRabbit <rabbit@yrabbit.cyou>
336 lines
13 KiB
C++
336 lines
13 KiB
C++
/*
|
|
* nextpnr -- Next Generation Place and Route
|
|
*
|
|
* Copyright (C) 2018 gatecat <gatecat@ds0.me>
|
|
*
|
|
* Permission to use, copy, modify, and/or distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*
|
|
*/
|
|
|
|
#include "globals.h"
|
|
#include <algorithm>
|
|
#include <iomanip>
|
|
#include <iostream>
|
|
#include <queue>
|
|
#include "cells.h"
|
|
#include "log.h"
|
|
#include "nextpnr.h"
|
|
#include "place_common.h"
|
|
#include "util.h"
|
|
|
|
NEXTPNR_NAMESPACE_BEGIN
|
|
|
|
class GowinGlobalRouter
|
|
{
|
|
public:
|
|
GowinGlobalRouter(Context *ctx) : ctx(ctx){};
|
|
|
|
private:
|
|
// wire -> clock#
|
|
dict<WireId, int> used_wires;
|
|
|
|
// ordered nets
|
|
struct onet_t
|
|
{
|
|
IdString name;
|
|
int clock_ports;
|
|
WireId clock_io_wire; // IO wire if there is one
|
|
|
|
onet_t() : name(IdString()), clock_ports(0), clock_io_wire(WireId()) {}
|
|
onet_t(IdString _name) : name(_name), clock_ports(0), clock_io_wire(WireId()) {}
|
|
|
|
// sort
|
|
bool operator<(const onet_t &other) const
|
|
{
|
|
if ((clock_io_wire != WireId()) ^ (other.clock_io_wire != WireId())) {
|
|
return !(clock_io_wire != WireId());
|
|
}
|
|
return clock_ports < other.clock_ports;
|
|
}
|
|
// search
|
|
bool operator==(const onet_t &other) const { return name == other.name; }
|
|
};
|
|
|
|
bool is_clock_port(PortRef const &user)
|
|
{
|
|
if (user.cell->type == id_SLICE && user.port == id_CLK) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
WireId clock_io(PortRef const &driver)
|
|
{
|
|
// XXX normally all alternative functions of the pins should be passed
|
|
// in the chip database, but at the moment we find them from aliases/pips
|
|
// XXX check diff inputs too
|
|
if (driver.cell == nullptr || driver.cell->bel == BelId()) {
|
|
return WireId();
|
|
}
|
|
// clock IOs have pips output->SPINExx
|
|
BelInfo &bel = ctx->bels.at(driver.cell->bel);
|
|
if (bel.type != id_IOB) {
|
|
return WireId();
|
|
}
|
|
WireId wire = bel.pins[id_O].wire;
|
|
for (auto const &pip : ctx->getPipsDownhill(wire)) {
|
|
if (ctx->wire_info(ctx->getPipDstWire(pip)).type.str(ctx).rfind("SPINE", 0) == 0) {
|
|
return wire;
|
|
}
|
|
}
|
|
return WireId();
|
|
}
|
|
|
|
// gather the clock nets
|
|
void gather_clock_nets(std::vector<onet_t> &clock_nets)
|
|
{
|
|
for (auto const &net : ctx->nets) {
|
|
NetInfo const *ni = net.second.get();
|
|
auto new_clock = clock_nets.end();
|
|
WireId clock_wire = clock_io(ni->driver);
|
|
if (clock_wire != WireId()) {
|
|
clock_nets.emplace_back(net.first);
|
|
new_clock = --clock_nets.end();
|
|
new_clock->clock_io_wire = clock_wire;
|
|
}
|
|
for (auto const &user : ni->users) {
|
|
if (is_clock_port(user)) {
|
|
if (new_clock == clock_nets.end()) {
|
|
clock_nets.emplace_back(net.first);
|
|
new_clock = --clock_nets.end();
|
|
}
|
|
++(new_clock->clock_ports);
|
|
}
|
|
}
|
|
}
|
|
// need to prioritize the nets
|
|
std::sort(clock_nets.begin(), clock_nets.end());
|
|
|
|
if (ctx->verbose) {
|
|
for (auto const &net : clock_nets) {
|
|
log_info(" Net:%s, ports:%d, io:%s\n", net.name.c_str(ctx), net.clock_ports,
|
|
net.clock_io_wire == WireId() ? "No" : net.clock_io_wire.c_str(ctx));
|
|
}
|
|
}
|
|
}
|
|
|
|
// non clock port
|
|
// returns GB pip
|
|
IdString route_to_non_clock_port(WireId const dstWire, int clock, pool<IdString> &used_pips,
|
|
pool<IdString> &undo_wires)
|
|
{
|
|
static std::vector<IdString> one_hop = {id_S111, id_S121, id_N111, id_N121, id_W111, id_W121, id_E111, id_E121};
|
|
char buf[40];
|
|
// uphill pips
|
|
for (auto const &uphill : ctx->getPipsUphill(dstWire)) {
|
|
WireId srcWire = ctx->getPipSrcWire(uphill);
|
|
if (find(one_hop.begin(), one_hop.end(), ctx->wire_info(ctx->getPipSrcWire(uphill)).type) !=
|
|
one_hop.end()) {
|
|
// found one hop pip
|
|
if (used_wires.count(srcWire)) {
|
|
if (used_wires[srcWire] != clock) {
|
|
continue;
|
|
}
|
|
}
|
|
WireInfo wi = ctx->wire_info(srcWire);
|
|
std::string wire_alias = srcWire.str(ctx).substr(srcWire.str(ctx).rfind("_") + 1);
|
|
snprintf(buf, sizeof(buf), "R%dC%d_GB%d0_%s", wi.y + 1, wi.x + 1, clock, wire_alias.c_str());
|
|
IdString gb = ctx->id(buf);
|
|
auto up_pips = ctx->getPipsUphill(srcWire);
|
|
if (find(up_pips.begin(), up_pips.end(), gb) != up_pips.end()) {
|
|
if (!used_wires.count(srcWire)) {
|
|
used_wires.insert(std::make_pair(srcWire, clock));
|
|
undo_wires.insert(srcWire);
|
|
}
|
|
used_pips.insert(uphill);
|
|
if (ctx->verbose) {
|
|
log_info(" 1-hop Pip:%s\n", uphill.c_str(ctx));
|
|
}
|
|
return gb;
|
|
}
|
|
}
|
|
}
|
|
return IdString();
|
|
}
|
|
|
|
// route one net
|
|
void route_net(onet_t const &net, int clock)
|
|
{
|
|
// For failed routing undo
|
|
pool<IdString> used_pips;
|
|
pool<IdString> undo_wires;
|
|
|
|
log_info(" Route net %s, use clock #%d.\n", net.name.c_str(ctx), clock);
|
|
for (auto const &user : ctx->net_info(net.name).users) {
|
|
// >>> port <- GB<clock>0
|
|
WireId dstWire = ctx->getNetinfoSinkWire(&ctx->net_info(net.name), user, 0);
|
|
if (ctx->verbose) {
|
|
log_info(" Cell:%s, port:%s, wire:%s\n", user.cell->name.c_str(ctx), user.port.c_str(ctx),
|
|
dstWire.c_str(ctx));
|
|
}
|
|
|
|
char buf[30];
|
|
PipId gb_pip_id;
|
|
if (user.port == id_CLK) {
|
|
WireInfo const wi = ctx->wire_info(dstWire);
|
|
snprintf(buf, sizeof(buf), "R%dC%d_GB%d0_%s", wi.y + 1, wi.x + 1, clock,
|
|
ctx->wire_info(dstWire).type.c_str(ctx));
|
|
gb_pip_id = ctx->id(buf);
|
|
// sanity
|
|
NPNR_ASSERT(find(ctx->getPipsUphill(dstWire).begin(), ctx->getPipsUphill(dstWire).end(), gb_pip_id) !=
|
|
ctx->getPipsUphill(dstWire).end());
|
|
} else {
|
|
// Non clock port
|
|
gb_pip_id = route_to_non_clock_port(dstWire, clock, used_pips, undo_wires);
|
|
if (gb_pip_id == IdString()) {
|
|
if (ctx->verbose) {
|
|
log_info(" Can't find route to %s, net %s will be routed in a standard way.\n",
|
|
dstWire.c_str(ctx), net.name.c_str(ctx));
|
|
}
|
|
for (IdString const &undo : undo_wires) {
|
|
used_wires.erase(undo);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
if (ctx->verbose) {
|
|
log_info(" GB Pip:%s\n", gb_pip_id.c_str(ctx));
|
|
}
|
|
|
|
if (used_pips.count(gb_pip_id)) {
|
|
if (ctx->verbose) {
|
|
log_info(" ^routed already^\n");
|
|
}
|
|
continue;
|
|
}
|
|
used_pips.insert(gb_pip_id);
|
|
|
|
// >>> GBOx <- GTx0
|
|
dstWire = ctx->getPipSrcWire(gb_pip_id);
|
|
WireInfo dstWireInfo = ctx->wire_info(dstWire);
|
|
int branch_tap_idx = clock > 3 ? 1 : 0;
|
|
snprintf(buf, sizeof(buf), "R%dC%d_GT%d0_GBO%d", dstWireInfo.y + 1, dstWireInfo.x + 1, branch_tap_idx,
|
|
branch_tap_idx);
|
|
PipId gt_pip_id = ctx->id(buf);
|
|
if (ctx->verbose) {
|
|
log_info(" GT Pip:%s\n", buf);
|
|
}
|
|
// sanity
|
|
NPNR_ASSERT(find(ctx->getPipsUphill(dstWire).begin(), ctx->getPipsUphill(dstWire).end(), gt_pip_id) !=
|
|
ctx->getPipsUphill(dstWire).end());
|
|
// if already routed
|
|
if (used_pips.count(gt_pip_id)) {
|
|
if (ctx->verbose) {
|
|
log_info(" ^routed already^\n");
|
|
}
|
|
continue;
|
|
}
|
|
used_pips.insert(gt_pip_id);
|
|
|
|
// >>> GTx0 <- SPINExx
|
|
// XXX no optimization here, we need to store
|
|
// the SPINE <-> clock# correspondence in the database. In the
|
|
// meantime, we define in run-time in a completely suboptimal way.
|
|
std::vector<std::string> clock_spine;
|
|
dstWire = ctx->getPipSrcWire(gt_pip_id);
|
|
for (auto const &uphill_pip : ctx->getPipsUphill(dstWire)) {
|
|
std::string name = ctx->wire_info(ctx->getPipSrcWire(uphill_pip)).type.str(ctx);
|
|
if (name.rfind("SPINE", 0) == 0) {
|
|
clock_spine.push_back(name);
|
|
}
|
|
}
|
|
sort(clock_spine.begin(), clock_spine.end(), [](const std::string &a, const std::string &b) -> bool {
|
|
return (a.size() < b.size()) || (a.size() == b.size() && a < b);
|
|
});
|
|
dstWireInfo = ctx->wire_info(dstWire);
|
|
snprintf(buf, sizeof(buf), "R%dC%d_%s_GT%d0", dstWireInfo.y + 1, dstWireInfo.x + 1,
|
|
clock_spine[clock - branch_tap_idx * 4].c_str(), branch_tap_idx);
|
|
PipId spine_pip_id = ctx->id(buf);
|
|
if (ctx->verbose) {
|
|
log_info(" Spine Pip:%s\n", buf);
|
|
}
|
|
// sanity
|
|
NPNR_ASSERT(find(ctx->getPipsUphill(dstWire).begin(), ctx->getPipsUphill(dstWire).end(), spine_pip_id) !=
|
|
ctx->getPipsUphill(dstWire).end());
|
|
// if already routed
|
|
if (used_pips.count(spine_pip_id)) {
|
|
if (ctx->verbose) {
|
|
log_info(" ^routed already^\n");
|
|
}
|
|
continue;
|
|
}
|
|
used_pips.insert(spine_pip_id);
|
|
|
|
// >>> SPINExx <- IO
|
|
dstWire = ctx->getPipSrcWire(spine_pip_id);
|
|
dstWireInfo = ctx->wire_info(dstWire);
|
|
PipId io_pip_id = PipId();
|
|
for (auto const &uphill_pip : ctx->getPipsUphill(dstWire)) {
|
|
if (ctx->getPipSrcWire(uphill_pip) == net.clock_io_wire) {
|
|
io_pip_id = uphill_pip;
|
|
}
|
|
}
|
|
NPNR_ASSERT(io_pip_id != PipId());
|
|
if (ctx->verbose) {
|
|
log_info(" IO Pip:%s\n", io_pip_id.c_str(ctx));
|
|
}
|
|
// if already routed
|
|
if (used_pips.count(io_pip_id)) {
|
|
if (ctx->verbose) {
|
|
log_info(" ^routed already^\n");
|
|
}
|
|
continue;
|
|
}
|
|
used_pips.insert(io_pip_id);
|
|
}
|
|
log_info(" Net %s is routed.\n", net.name.c_str(ctx));
|
|
for (auto const pip : used_pips) {
|
|
ctx->bindPip(pip, &ctx->net_info(net.name), STRENGTH_LOCKED);
|
|
}
|
|
ctx->bindWire(net.clock_io_wire, &ctx->net_info(net.name), STRENGTH_LOCKED);
|
|
}
|
|
|
|
public:
|
|
Context *ctx;
|
|
void route_globals()
|
|
{
|
|
log_info("Routing globals...\n");
|
|
|
|
std::vector<onet_t> clock_nets;
|
|
gather_clock_nets(clock_nets);
|
|
// XXX we need to use the list of indexes of clocks from the database
|
|
// use 6 clocks (XXX 3 for GW1NZ-1)
|
|
int max_clock = 3, cur_clock = -1;
|
|
for (auto const &net : clock_nets) {
|
|
// XXX only IO clock for now
|
|
if (net.clock_io_wire == WireId()) {
|
|
log_info(" Non IO clock, skip %s.\n", net.name.c_str(ctx));
|
|
continue;
|
|
}
|
|
if (++cur_clock >= max_clock) {
|
|
log_info(" No more clock wires left, skip the remaining nets.\n");
|
|
break;
|
|
}
|
|
route_net(net, cur_clock);
|
|
}
|
|
}
|
|
};
|
|
|
|
void route_gowin_globals(Context *ctx)
|
|
{
|
|
GowinGlobalRouter router(ctx);
|
|
router.route_globals();
|
|
}
|
|
|
|
NEXTPNR_NAMESPACE_END
|