2022-06-23 09:42:58 +08:00
|
|
|
/*
|
|
|
|
* nextpnr -- Next Generation Place and Route
|
|
|
|
*
|
|
|
|
* Copyright (C) 2018 gatecat <gatecat@ds0.me>
|
2022-06-24 06:20:06 +08:00
|
|
|
* Copyright (C) 2022 YRabbit <rabbit@yrabbit.cyou>
|
2022-06-23 09:42:58 +08:00
|
|
|
*
|
|
|
|
* 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 <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
|
|
|
|
|
2022-07-04 08:32:39 +08:00
|
|
|
bool GowinGlobalRouter::is_clock_port(PortRef const &user)
|
2022-06-23 09:42:58 +08:00
|
|
|
{
|
2022-08-11 01:58:22 +08:00
|
|
|
if ((user.cell->type.in(id_SLICE, id_ODDR, id_ODDRC)) && user.port == id_CLK) {
|
2022-07-04 08:32:39 +08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2022-06-23 09:42:58 +08:00
|
|
|
|
2022-12-30 09:55:39 +08:00
|
|
|
std::pair<WireId, BelId> GowinGlobalRouter::clock_src(Context *ctx, PortRef const &driver)
|
2022-07-04 08:32:39 +08:00
|
|
|
{
|
2022-12-30 09:55:39 +08:00
|
|
|
if (driver.cell == nullptr) {
|
2022-07-04 08:32:39 +08:00
|
|
|
return std::make_pair(WireId(), BelId());
|
2022-06-23 09:42:58 +08:00
|
|
|
}
|
|
|
|
|
2022-12-30 09:55:39 +08:00
|
|
|
BelInfo &bel = ctx->bel_info(driver.cell->bel);
|
|
|
|
WireId wire;
|
|
|
|
if (driver.cell->type == id_IOB) {
|
|
|
|
if (ctx->is_GCLKT_iob(driver.cell)) {
|
|
|
|
wire = bel.pins[id_O].wire;
|
2022-07-04 08:32:39 +08:00
|
|
|
return std::make_pair(wire, bel.name);
|
2022-06-23 09:42:58 +08:00
|
|
|
}
|
2022-12-30 09:55:39 +08:00
|
|
|
return std::make_pair(WireId(), BelId());
|
|
|
|
}
|
2023-01-26 18:26:05 +08:00
|
|
|
if (driver.cell->type == id_rPLL || driver.cell->type == id_PLLVR) {
|
2022-12-30 09:55:39 +08:00
|
|
|
if (driver.port == id_CLKOUT || driver.port == id_CLKOUTP || driver.port == id_CLKOUTD ||
|
|
|
|
driver.port == id_CLKOUTD3) {
|
|
|
|
wire = bel.pins[driver.port].wire;
|
|
|
|
return std::make_pair(wire, bel.name);
|
|
|
|
}
|
|
|
|
return std::make_pair(WireId(), BelId());
|
2022-06-23 09:42:58 +08:00
|
|
|
}
|
2022-07-04 08:32:39 +08:00
|
|
|
return std::make_pair(WireId(), BelId());
|
|
|
|
}
|
2022-06-23 09:42:58 +08:00
|
|
|
|
2022-07-04 08:32:39 +08:00
|
|
|
// gather the clock nets
|
|
|
|
void GowinGlobalRouter::gather_clock_nets(Context *ctx, std::vector<globalnet_t> &clock_nets)
|
|
|
|
{
|
|
|
|
for (auto const &net : ctx->nets) {
|
|
|
|
NetInfo const *ni = net.second.get();
|
|
|
|
auto new_clock = clock_nets.end();
|
2022-12-30 09:55:39 +08:00
|
|
|
auto clock_wire_bel = clock_src(ctx, ni->driver);
|
2022-07-04 08:32:39 +08:00
|
|
|
if (clock_wire_bel.first != WireId()) {
|
|
|
|
clock_nets.emplace_back(net.first);
|
|
|
|
new_clock = --clock_nets.end();
|
2022-12-30 09:55:39 +08:00
|
|
|
new_clock->clock_wire = clock_wire_bel.first;
|
|
|
|
new_clock->clock_bel = clock_wire_bel.second;
|
2022-07-04 08:32:39 +08:00
|
|
|
}
|
|
|
|
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();
|
2022-06-23 09:42:58 +08:00
|
|
|
}
|
2022-07-04 08:32:39 +08:00
|
|
|
++(new_clock->clock_ports);
|
2022-06-23 09:42:58 +08:00
|
|
|
}
|
|
|
|
}
|
2022-07-04 08:32:39 +08:00
|
|
|
}
|
|
|
|
// need to prioritize the nets
|
|
|
|
std::sort(clock_nets.begin(), clock_nets.end());
|
2022-06-23 09:42:58 +08:00
|
|
|
|
2022-07-04 08:32:39 +08:00
|
|
|
if (ctx->verbose) {
|
|
|
|
for (auto const &net : clock_nets) {
|
2022-12-30 09:55:39 +08:00
|
|
|
log_info(" Net:%s, ports:%d, clock source:%s\n", net.name.c_str(ctx), net.clock_ports,
|
|
|
|
net.clock_wire == WireId() ? "No" : net.clock_wire.c_str(ctx));
|
2022-06-23 09:42:58 +08:00
|
|
|
}
|
|
|
|
}
|
2022-07-04 08:32:39 +08:00
|
|
|
}
|
2022-06-23 09:42:58 +08:00
|
|
|
|
2022-07-04 08:32:39 +08:00
|
|
|
// non clock port
|
|
|
|
// returns GB pip
|
|
|
|
IdString GowinGlobalRouter::route_to_non_clock_port(Context *ctx, WireId const dstWire, int clock,
|
|
|
|
pool<IdString> &used_pips, pool<IdString> &undo_wires)
|
|
|
|
{
|
2023-01-26 18:26:05 +08:00
|
|
|
static std::vector<IdString> one_hop_0 = {id_W111, id_W121, id_E111, id_E121};
|
|
|
|
static std::vector<IdString> one_hop_4 = {id_S111, id_S121, id_N111, id_N121};
|
2022-07-04 08:32:39 +08:00
|
|
|
// uphill pips
|
|
|
|
for (auto const uphill : ctx->getPipsUphill(dstWire)) {
|
|
|
|
WireId srcWire = ctx->getPipSrcWire(uphill);
|
2023-01-26 18:26:05 +08:00
|
|
|
bool found;
|
|
|
|
if (clock < 4) {
|
|
|
|
found = find(one_hop_0.begin(), one_hop_0.end(), ctx->wire_info(ctx->getPipSrcWire(uphill)).type) !=
|
|
|
|
one_hop_0.end();
|
|
|
|
} else {
|
|
|
|
found = find(one_hop_4.begin(), one_hop_4.end(), ctx->wire_info(ctx->getPipSrcWire(uphill)).type) !=
|
|
|
|
one_hop_4.end();
|
|
|
|
}
|
|
|
|
if (found) {
|
2022-07-04 08:32:39 +08:00
|
|
|
// 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);
|
2023-01-26 18:26:05 +08:00
|
|
|
IdString gb = ctx->idf("R%dC%d_GB%d0_%s", wi.y + 1, wi.x + 1, clock, wire_alias.c_str());
|
|
|
|
if (ctx->verbose) {
|
|
|
|
log_info(" 1-hop gb:%s\n", gb.c_str(ctx));
|
|
|
|
}
|
|
|
|
// sanity
|
|
|
|
NPNR_ASSERT(find(ctx->getPipsUphill(srcWire).begin(), ctx->getPipsUphill(srcWire).end(), gb) !=
|
|
|
|
ctx->getPipsUphill(srcWire).end());
|
2022-07-04 08:32:39 +08:00
|
|
|
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);
|
2022-06-23 09:42:58 +08:00
|
|
|
}
|
2022-07-04 08:32:39 +08:00
|
|
|
used_pips.insert(uphill);
|
|
|
|
if (ctx->verbose) {
|
|
|
|
log_info(" 1-hop Pip:%s\n", uphill.c_str(ctx));
|
2022-06-23 09:42:58 +08:00
|
|
|
}
|
2022-07-04 08:32:39 +08:00
|
|
|
return gb;
|
2022-06-23 09:42:58 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-07-04 08:32:39 +08:00
|
|
|
return IdString();
|
|
|
|
}
|
2022-06-23 09:42:58 +08:00
|
|
|
|
2022-07-04 08:32:39 +08:00
|
|
|
// route one net
|
|
|
|
void GowinGlobalRouter::route_net(Context *ctx, globalnet_t const &net)
|
|
|
|
{
|
|
|
|
// For failed routing undo
|
|
|
|
pool<IdString> used_pips;
|
|
|
|
pool<IdString> undo_wires;
|
2022-06-23 09:42:58 +08:00
|
|
|
|
2022-07-04 08:32:39 +08:00
|
|
|
log_info(" Route net %s, use clock #%d.\n", net.name.c_str(ctx), net.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));
|
|
|
|
}
|
2022-06-23 09:42:58 +08:00
|
|
|
|
2022-07-04 08:32:39 +08:00
|
|
|
char buf[30];
|
|
|
|
PipId gb_pip_id;
|
2023-01-26 18:26:05 +08:00
|
|
|
if (user.port == id_CLK || user.port == id_CLKIN) {
|
2022-07-04 08:32:39 +08:00
|
|
|
WireInfo const wi = ctx->wire_info(dstWire);
|
2023-01-26 18:26:05 +08:00
|
|
|
gb_pip_id =
|
|
|
|
ctx->idf("R%dC%d_GB%d0_%s", wi.y + 1, wi.x + 1, net.clock, ctx->wire_info(dstWire).type.c_str(ctx));
|
2022-06-23 09:42:58 +08:00
|
|
|
// sanity
|
2022-07-04 08:32:39 +08:00
|
|
|
NPNR_ASSERT(find(ctx->getPipsUphill(dstWire).begin(), ctx->getPipsUphill(dstWire).end(), gb_pip_id) !=
|
2022-06-23 09:42:58 +08:00
|
|
|
ctx->getPipsUphill(dstWire).end());
|
2022-07-04 08:32:39 +08:00
|
|
|
} else {
|
|
|
|
// Non clock port
|
|
|
|
gb_pip_id = route_to_non_clock_port(ctx, dstWire, net.clock, used_pips, undo_wires);
|
|
|
|
if (gb_pip_id == IdString()) {
|
2022-06-23 09:42:58 +08:00
|
|
|
if (ctx->verbose) {
|
2022-07-04 08:32:39 +08:00
|
|
|
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));
|
2022-06-23 09:42:58 +08:00
|
|
|
}
|
2022-07-04 08:32:39 +08:00
|
|
|
for (IdString const undo : undo_wires) {
|
|
|
|
used_wires.erase(undo);
|
2022-06-23 09:42:58 +08:00
|
|
|
}
|
2022-07-04 08:32:39 +08:00
|
|
|
return;
|
2022-06-23 09:42:58 +08:00
|
|
|
}
|
2022-07-04 08:32:39 +08:00
|
|
|
}
|
|
|
|
if (ctx->verbose) {
|
|
|
|
log_info(" GB Pip:%s\n", gb_pip_id.c_str(ctx));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (used_pips.count(gb_pip_id)) {
|
2022-06-23 09:42:58 +08:00
|
|
|
if (ctx->verbose) {
|
2022-07-04 08:32:39 +08:00
|
|
|
log_info(" ^routed already^\n");
|
2022-06-23 09:42:58 +08:00
|
|
|
}
|
2022-07-04 08:32:39 +08:00
|
|
|
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 = net.clock > 3 ? 1 : 0;
|
2023-01-26 18:26:05 +08:00
|
|
|
PipId gt_pip_id =
|
|
|
|
ctx->idf("R%dC%d_GT%d0_GBO%d", dstWireInfo.y + 1, dstWireInfo.x + 1, branch_tap_idx, branch_tap_idx);
|
2022-07-04 08:32:39 +08:00
|
|
|
if (ctx->verbose) {
|
2023-01-26 18:26:05 +08:00
|
|
|
log_info(" GT Pip:%s\n", gt_pip_id.c_str(ctx));
|
2022-07-04 08:32:39 +08:00
|
|
|
}
|
|
|
|
// 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");
|
2022-06-23 09:42:58 +08:00
|
|
|
}
|
2022-07-04 08:32:39 +08:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
used_pips.insert(gt_pip_id);
|
2022-06-23 09:42:58 +08:00
|
|
|
|
2022-07-04 08:32:39 +08:00
|
|
|
// >>> 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);
|
2022-06-23 09:42:58 +08:00
|
|
|
}
|
2022-07-04 08:32:39 +08:00
|
|
|
}
|
|
|
|
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[net.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)) {
|
2022-06-23 09:42:58 +08:00
|
|
|
if (ctx->verbose) {
|
2022-07-04 08:32:39 +08:00
|
|
|
log_info(" ^routed already^\n");
|
2022-06-23 09:42:58 +08:00
|
|
|
}
|
2022-07-04 08:32:39 +08:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
used_pips.insert(spine_pip_id);
|
|
|
|
|
2022-12-30 09:55:39 +08:00
|
|
|
// >>> SPINExx <- Src
|
2022-07-04 08:32:39 +08:00
|
|
|
dstWire = ctx->getPipSrcWire(spine_pip_id);
|
|
|
|
dstWireInfo = ctx->wire_info(dstWire);
|
2022-12-30 09:55:39 +08:00
|
|
|
PipId src_pip_id = PipId();
|
2022-07-04 08:32:39 +08:00
|
|
|
for (auto const uphill_pip : ctx->getPipsUphill(dstWire)) {
|
2022-12-30 09:55:39 +08:00
|
|
|
if (ctx->getPipSrcWire(uphill_pip) == net.clock_wire) {
|
|
|
|
src_pip_id = uphill_pip;
|
2023-01-26 18:26:05 +08:00
|
|
|
break;
|
2022-06-23 09:42:58 +08:00
|
|
|
}
|
|
|
|
}
|
2022-07-04 08:32:39 +08:00
|
|
|
if (ctx->verbose) {
|
2022-12-30 09:55:39 +08:00
|
|
|
log_info(" Src Pip:%s\n", src_pip_id.c_str(ctx));
|
2022-07-04 08:32:39 +08:00
|
|
|
}
|
2023-01-26 18:26:05 +08:00
|
|
|
NPNR_ASSERT(src_pip_id != PipId());
|
2022-07-04 08:32:39 +08:00
|
|
|
// if already routed
|
2022-12-30 09:55:39 +08:00
|
|
|
if (used_pips.count(src_pip_id)) {
|
2022-07-04 08:32:39 +08:00
|
|
|
if (ctx->verbose) {
|
|
|
|
log_info(" ^routed already^\n");
|
|
|
|
}
|
|
|
|
continue;
|
2022-06-23 09:42:58 +08:00
|
|
|
}
|
2022-12-30 09:55:39 +08:00
|
|
|
used_pips.insert(src_pip_id);
|
2022-06-23 09:42:58 +08:00
|
|
|
}
|
2022-07-04 08:32:39 +08:00
|
|
|
log_info(" Net %s is routed.\n", net.name.c_str(ctx));
|
2022-12-30 09:55:39 +08:00
|
|
|
if (!ctx->net_info(net.name).users.empty()) {
|
|
|
|
for (auto const pip : used_pips) {
|
|
|
|
ctx->bindPip(pip, &ctx->net_info(net.name), STRENGTH_LOCKED);
|
|
|
|
}
|
|
|
|
ctx->bindWire(net.clock_wire, &ctx->net_info(net.name), STRENGTH_LOCKED);
|
2022-07-04 08:32:39 +08:00
|
|
|
}
|
|
|
|
}
|
2022-06-23 09:42:58 +08:00
|
|
|
|
2022-07-04 08:32:39 +08:00
|
|
|
void GowinGlobalRouter::route_globals(Context *ctx)
|
|
|
|
{
|
|
|
|
log_info("Routing globals...\n");
|
2022-06-23 09:42:58 +08:00
|
|
|
|
2022-07-04 08:32:39 +08:00
|
|
|
for (auto const &net : nets) {
|
|
|
|
route_net(ctx, net);
|
2022-06-23 09:42:58 +08:00
|
|
|
}
|
2022-07-04 08:32:39 +08:00
|
|
|
}
|
2022-06-23 09:42:58 +08:00
|
|
|
|
2022-07-04 08:32:39 +08:00
|
|
|
// Allocate networks that will be routed through the global system.
|
|
|
|
// Mark their driver cells as global buffers to exclude them from the analysis.
|
|
|
|
void GowinGlobalRouter::mark_globals(Context *ctx)
|
2022-06-23 09:42:58 +08:00
|
|
|
{
|
2022-07-04 08:32:39 +08:00
|
|
|
log_info("Find global nets...\n");
|
|
|
|
|
|
|
|
std::vector<globalnet_t> clock_nets;
|
|
|
|
gather_clock_nets(ctx, clock_nets);
|
|
|
|
// XXX we need to use the list of indexes of clocks from the database
|
|
|
|
// use 6 clocks (XXX 3 for GW1NZ-1)
|
2023-01-18 17:18:02 +08:00
|
|
|
int max_clock = ctx->max_clock, cur_clock = -1;
|
2022-07-04 08:32:39 +08:00
|
|
|
for (auto &net : clock_nets) {
|
|
|
|
// XXX only IO clock for now
|
2022-12-30 09:55:39 +08:00
|
|
|
if (net.clock_wire == WireId()) {
|
|
|
|
log_info(" Non clock source, skip %s.\n", net.name.c_str(ctx));
|
2022-07-04 08:32:39 +08:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (++cur_clock >= max_clock) {
|
|
|
|
log_info(" No more clock wires left, skip the remaining nets.\n");
|
|
|
|
break;
|
|
|
|
}
|
2022-12-30 09:55:39 +08:00
|
|
|
if (ctx->net_info(net.name).users.empty()) {
|
|
|
|
--cur_clock;
|
|
|
|
net.clock = -1;
|
|
|
|
} else {
|
|
|
|
net.clock = cur_clock;
|
|
|
|
}
|
|
|
|
BelInfo &bi = ctx->bel_info(net.clock_bel);
|
2022-07-04 08:32:39 +08:00
|
|
|
bi.gb = true;
|
|
|
|
nets.emplace_back(net);
|
|
|
|
}
|
2022-06-23 09:42:58 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
NEXTPNR_NAMESPACE_END
|