nextpnr/gowin/pack.cc
YRabbit 2e68962a02 gowin: add PLL pins processing
Uses the information of the special input pins for the PLL in the
current chip. If such pins are involved, no routing is performed and
information about the use of implicit wires is passed to the packer.

The RESET and RESET_P inputs are now also disabled if they are connected
to VSS/VCC.

Signed-off-by: YRabbit <rabbit@yrabbit.cyou>
2022-12-04 15:06:44 +10:00

1201 lines
45 KiB
C++

/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018-19 gatecat <gatecat@ds0.me>
* Copyright (C) 2020 Pepijn de Vos <pepijn@symbioticeda.com>
*
* 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 <iostream>
#include <iterator>
#include "cells.h"
#include "design_utils.h"
#include "log.h"
#include "util.h"
#include "globals.h"
NEXTPNR_NAMESPACE_BEGIN
static void make_dummy_alu(Context *ctx, int alu_idx, CellInfo *ci, CellInfo *packed_head,
std::vector<std::unique_ptr<CellInfo>> &new_cells)
{
if ((alu_idx % 2) == 0) {
return;
}
std::unique_ptr<CellInfo> dummy = create_generic_cell(ctx, id_SLICE, ci->name.str(ctx) + "_DUMMY_ALULC");
if (ctx->verbose) {
log_info("packed dummy ALU %s.\n", ctx->nameOf(dummy.get()));
}
dummy->params[id_ALU_MODE] = std::string("C2L");
// add to cluster
dummy->cluster = packed_head->name;
dummy->constr_z = alu_idx % 6;
dummy->constr_x = alu_idx / 6;
dummy->constr_y = 0;
packed_head->constr_children.push_back(dummy.get());
new_cells.push_back(std::move(dummy));
}
// replace ALU with LUT
static void pack_alus(Context *ctx)
{
log_info("Packing ALUs..\n");
// cell name, CIN net name
pool<std::pair<IdString, IdString>> alu_heads;
// collect heads
for (auto &cell : ctx->cells) {
CellInfo *ci = cell.second.get();
if (is_alu(ctx, ci)) {
NetInfo *cin = ci->ports.at(id_CIN).net;
CellInfo *cin_ci = cin->driver.cell;
if (cin == nullptr || cin_ci == nullptr) {
log_error("CIN disconnected at ALU:%s\n", ctx->nameOf(ci));
continue;
}
if (!is_alu(ctx, cin_ci) || cin->users.entries() > 1) {
if (ctx->verbose) {
log_info("ALU head found %s. CIN net is %s\n", ctx->nameOf(ci), ctx->nameOf(cin));
}
alu_heads.insert(std::make_pair(ci->name, cin->name));
}
}
}
pool<IdString> packed_cells;
pool<IdString> delete_nets;
std::vector<std::unique_ptr<CellInfo>> new_cells;
for (auto &head : alu_heads) {
CellInfo *ci = ctx->cells[head.first].get();
IdString cin_netId = head.second;
if (ctx->verbose) {
log_info("cell '%s' is of type '%s'\n", ctx->nameOf(ci), ci->type.c_str(ctx));
}
std::unique_ptr<CellInfo> packed_head = create_generic_cell(ctx, id_SLICE, ci->name.str(ctx) + "_HEAD_ALULC");
// Head is always SLICE0
packed_head->constr_z = 0;
packed_head->constr_abs_z = true;
if (ctx->verbose) {
log_info("packed ALU head into %s. CIN net is %s\n", ctx->nameOf(packed_head.get()),
ctx->nameOf(cin_netId));
}
packed_head->connectPort(id_C, ctx->nets[ctx->id("$PACKER_VCC_NET")].get());
if (cin_netId == ctx->id("$PACKER_GND_NET")) {
// CIN = 0
packed_head->params[id_ALU_MODE] = std::string("C2L");
} else {
if (cin_netId == ctx->id("$PACKER_VCC_NET")) {
// CIN = 1
packed_head->params[id_ALU_MODE] = std::string("ONE2C");
} else {
// CIN from logic
packed_head->connectPort(id_B, ctx->nets[cin_netId].get());
packed_head->connectPort(id_D, ctx->nets[cin_netId].get());
packed_head->params[id_ALU_MODE] = std::string("0"); // ADD
}
}
int alu_idx = 1;
do { // go through the ALU chain
auto alu_bel = ci->attrs.find(id_BEL);
if (alu_bel != ci->attrs.end()) {
log_error("ALU %s placement restrictions are not supported.\n", ctx->nameOf(ci));
return;
}
// remove cell
packed_cells.insert(ci->name);
// CIN/COUT are hardwired, delete
ci->disconnectPort(id_CIN);
NetInfo *cout = ci->ports.at(id_COUT).net;
ci->disconnectPort(id_COUT);
std::unique_ptr<CellInfo> packed = create_generic_cell(ctx, id_SLICE, ci->name.str(ctx) + "_ALULC");
if (ctx->verbose) {
log_info("packed ALU into %s. COUT net is %s\n", ctx->nameOf(packed.get()), ctx->nameOf(cout));
}
int mode = int_or_default(ci->params, id_ALU_MODE);
packed->params[id_ALU_MODE] = mode;
if (mode == 9) { // MULT
packed->connectPort(id_C, ctx->nets[ctx->id("$PACKER_GND_NET")].get());
} else {
packed->connectPort(id_C, ctx->nets[ctx->id("$PACKER_VCC_NET")].get());
}
// add to cluster
packed->cluster = packed_head->name;
packed->constr_z = alu_idx % 6;
packed->constr_x = alu_idx / 6;
packed->constr_y = 0;
packed_head->constr_children.push_back(packed.get());
++alu_idx;
// connect all remainig ports
ci->movePortTo(id_SUM, packed.get(), id_F);
switch (mode) {
case 0: // ADD
ci->movePortTo(id_I0, packed.get(), id_B);
ci->movePortTo(id_I1, packed.get(), id_D);
break;
case 1: // SUB
ci->movePortTo(id_I0, packed.get(), id_A);
ci->movePortTo(id_I1, packed.get(), id_D);
break;
case 5: // LE
ci->movePortTo(id_I0, packed.get(), id_A);
ci->movePortTo(id_I1, packed.get(), id_B);
break;
case 9: // MULT
ci->movePortTo(id_I0, packed.get(), id_A);
ci->movePortTo(id_I1, packed.get(), id_B);
packed->disconnectPort(id_D);
packed->connectPort(id_D, ctx->nets[ctx->id("$PACKER_VCC_NET")].get());
break;
default:
ci->movePortTo(id_I0, packed.get(), id_A);
ci->movePortTo(id_I1, packed.get(), id_B);
ci->movePortTo(id_I3, packed.get(), id_D);
}
new_cells.push_back(std::move(packed));
if (cout != nullptr && cout->users.entries() > 0) {
// if COUT used by logic
if ((cout->users.entries() > 1) || (!is_alu(ctx, (*cout->users.begin()).cell))) {
if (ctx->verbose) {
log_info("COUT is used by logic\n");
}
// make gate C->logic
std::unique_ptr<CellInfo> packed_tail =
create_generic_cell(ctx, id_SLICE, ci->name.str(ctx) + "_TAIL_ALULC");
if (ctx->verbose) {
log_info("packed ALU tail into %s. COUT net is %s\n", ctx->nameOf(packed_tail.get()),
ctx->nameOf(cout));
}
packed_tail->params[id_ALU_MODE] = std::string("C2L");
packed_tail->connectPort(id_F, cout);
// add to cluster
packed_tail->cluster = packed_head->name;
packed_tail->constr_z = alu_idx % 6;
packed_tail->constr_x = alu_idx / 6;
packed_tail->constr_y = 0;
++alu_idx;
packed_head->constr_children.push_back(packed_tail.get());
new_cells.push_back(std::move(packed_tail));
make_dummy_alu(ctx, alu_idx, ci, packed_head.get(), new_cells);
break;
}
// next ALU
ci = (*cout->users.begin()).cell;
// if ALU is too big
if (alu_idx == (ctx->gridDimX - 2) * 6 - 1) {
log_error("ALU %s is the %dth in the chain. Such long chains are not supported.\n", ctx->nameOf(ci),
alu_idx);
break;
}
} else {
// COUT is unused
if (ctx->verbose) {
log_info("cell is the ALU tail. Index is %d\n", alu_idx);
}
make_dummy_alu(ctx, alu_idx, ci, packed_head.get(), new_cells);
break;
}
} while (1);
// add head to the cluster
packed_head->cluster = packed_head->name;
new_cells.push_back(std::move(packed_head));
}
// actual delete, erase and move cells/nets
for (auto pcell : packed_cells) {
ctx->cells.erase(pcell);
}
for (auto dnet : delete_nets) {
ctx->nets.erase(dnet);
}
for (auto &ncell : new_cells) {
ctx->cells[ncell->name] = std::move(ncell);
}
}
// pack MUX2_LUT5
static void pack_mux2_lut5(Context *ctx, CellInfo *ci, pool<IdString> &packed_cells, pool<IdString> &delete_nets,
std::vector<std::unique_ptr<CellInfo>> &new_cells)
{
if (bool_or_default(ci->attrs, id_SINGLE_INPUT_MUX)) {
// find the muxed LUT
NetInfo *i1 = ci->ports.at(id_I1).net;
CellInfo *lut1 = net_driven_by(ctx, i1, is_lut, id_F);
if (lut1 == nullptr) {
log_error("MUX2_LUT5 '%s' port I1 isn't connected to the LUT\n", ctx->nameOf(ci));
return;
}
if (ctx->verbose) {
log_info("found attached lut1 %s\n", ctx->nameOf(lut1));
}
// XXX enable the placement constraints
auto mux_bel = ci->attrs.find(id_BEL);
auto lut1_bel = lut1->attrs.find(id_BEL);
if (lut1_bel != lut1->attrs.end() || mux_bel != ci->attrs.end()) {
log_error("MUX2_LUT5 '%s' placement restrictions are not supported yet\n", ctx->nameOf(ci));
return;
}
std::unique_ptr<CellInfo> packed = create_generic_cell(ctx, id_MUX2_LUT5, ci->name.str(ctx) + "_LC");
if (ctx->verbose) {
log_info("packed cell %s into %s\n", ctx->nameOf(ci), ctx->nameOf(packed.get()));
}
// mux is the cluster root
packed->cluster = packed->name;
lut1->cluster = packed->name;
lut1->constr_z = -BelZ::mux_0_z + 1;
packed->constr_children.clear();
// reconnect MUX ports
ci->movePortTo(id_O, packed.get(), id_OF);
ci->movePortTo(id_I1, packed.get(), id_I1);
// remove cells
packed_cells.insert(ci->name);
// new MUX cell
new_cells.push_back(std::move(packed));
} else {
// find the muxed LUTs
NetInfo *i0 = ci->ports.at(id_I0).net;
NetInfo *i1 = ci->ports.at(id_I1).net;
CellInfo *lut0 = net_driven_by(ctx, i0, is_lut, id_F);
CellInfo *lut1 = net_driven_by(ctx, i1, is_lut, id_F);
if (lut0 == nullptr || lut1 == nullptr) {
log_error("MUX2_LUT5 '%s' port I0 or I1 isn't connected to the LUT\n", ctx->nameOf(ci));
return;
}
if (ctx->verbose) {
log_info("found attached lut0 %s\n", ctx->nameOf(lut0));
log_info("found attached lut1 %s\n", ctx->nameOf(lut1));
}
// XXX enable the placement constraints
auto mux_bel = ci->attrs.find(id_BEL);
auto lut0_bel = lut0->attrs.find(id_BEL);
auto lut1_bel = lut1->attrs.find(id_BEL);
if (lut0_bel != lut0->attrs.end() || lut1_bel != lut1->attrs.end() || mux_bel != ci->attrs.end()) {
log_error("MUX2_LUT5 '%s' placement restrictions are not supported yet\n", ctx->nameOf(ci));
return;
}
std::unique_ptr<CellInfo> packed = create_generic_cell(ctx, id_MUX2_LUT5, ci->name.str(ctx) + "_LC");
if (ctx->verbose) {
log_info("packed cell %s into %s\n", ctx->nameOf(ci), ctx->nameOf(packed.get()));
}
// mux is the cluster root
packed->cluster = packed->name;
lut0->cluster = packed->name;
lut0->constr_z = -BelZ::mux_0_z;
lut1->cluster = packed->name;
lut1->constr_z = -BelZ::mux_0_z + 1;
packed->constr_children.clear();
// reconnect MUX ports
ci->movePortTo(id_O, packed.get(), id_OF);
ci->movePortTo(id_S0, packed.get(), id_SEL);
ci->movePortTo(id_I0, packed.get(), id_I0);
ci->movePortTo(id_I1, packed.get(), id_I1);
// remove cells
packed_cells.insert(ci->name);
// new MUX cell
new_cells.push_back(std::move(packed));
}
}
// Common MUX2 packing routine
static void pack_mux2_lut(Context *ctx, CellInfo *ci, bool (*pred)(const BaseCtx *, const CellInfo *),
char const type_suffix, IdString const type_id, int const x[2], int const z[2],
pool<IdString> &packed_cells, pool<IdString> &delete_nets,
std::vector<std::unique_ptr<CellInfo>> &new_cells)
{
// find the muxed LUTs
NetInfo *i0 = ci->ports.at(id_I0).net;
NetInfo *i1 = ci->ports.at(id_I1).net;
CellInfo *mux0 = net_driven_by(ctx, i0, pred, id_OF);
CellInfo *mux1 = net_driven_by(ctx, i1, pred, id_OF);
if (mux0 == nullptr || mux1 == nullptr) {
log_error("MUX2_LUT%c '%s' port I0 or I1 isn't connected to the MUX\n", type_suffix, ctx->nameOf(ci));
return;
}
if (ctx->verbose) {
log_info("found attached mux0 %s\n", ctx->nameOf(mux0));
log_info("found attached mux1 %s\n", ctx->nameOf(mux1));
}
// XXX enable the placement constraints
auto mux_bel = ci->attrs.find(id_BEL);
auto mux0_bel = mux0->attrs.find(id_BEL);
auto mux1_bel = mux1->attrs.find(id_BEL);
if (mux0_bel != mux0->attrs.end() || mux1_bel != mux1->attrs.end() || mux_bel != ci->attrs.end()) {
log_error("MUX2_LUT%c '%s' placement restrictions are not supported yet\n", type_suffix, ctx->nameOf(ci));
return;
}
std::unique_ptr<CellInfo> packed = create_generic_cell(ctx, type_id, ci->name.str(ctx) + "_LC");
if (ctx->verbose) {
log_info("packed cell %s into %s\n", ctx->nameOf(ci), ctx->nameOf(packed.get()));
}
// mux is the cluster root
packed->cluster = packed->name;
mux0->cluster = packed->name;
mux0->constr_x = x[0];
mux0->constr_y = 0;
mux0->constr_z = z[0];
for (auto &child : mux0->constr_children) {
child->cluster = packed->name;
child->constr_x += mux0->constr_x;
child->constr_z += mux0->constr_z;
packed->constr_children.push_back(child);
}
mux0->constr_children.clear();
mux1->cluster = packed->name;
mux1->constr_x = x[1];
mux0->constr_y = 0;
mux1->constr_z = z[1];
for (auto &child : mux1->constr_children) {
child->cluster = packed->name;
child->constr_x += mux1->constr_x;
child->constr_z += mux1->constr_z;
packed->constr_children.push_back(child);
}
mux1->constr_children.clear();
packed->constr_children.push_back(mux0);
packed->constr_children.push_back(mux1);
// reconnect MUX ports
ci->movePortTo(id_O, packed.get(), id_OF);
ci->movePortTo(id_S0, packed.get(), id_SEL);
ci->movePortTo(id_I0, packed.get(), id_I0);
ci->movePortTo(id_I1, packed.get(), id_I1);
// remove cells
packed_cells.insert(ci->name);
// new MUX cell
new_cells.push_back(std::move(packed));
}
// pack MUX2_LUT6
static void pack_mux2_lut6(Context *ctx, CellInfo *ci, pool<IdString> &packed_cells, pool<IdString> &delete_nets,
std::vector<std::unique_ptr<CellInfo>> &new_cells)
{
static int x[] = {0, 0};
static int z[] = {+1, -1};
pack_mux2_lut(ctx, ci, is_mux2_lut5, '6', id_MUX2_LUT6, x, z, packed_cells, delete_nets, new_cells);
}
// pack MUX2_LUT7
static void pack_mux2_lut7(Context *ctx, CellInfo *ci, pool<IdString> &packed_cells, pool<IdString> &delete_nets,
std::vector<std::unique_ptr<CellInfo>> &new_cells)
{
static int x[] = {0, 0};
static int z[] = {+2, -2};
pack_mux2_lut(ctx, ci, is_mux2_lut6, '7', id_MUX2_LUT7, x, z, packed_cells, delete_nets, new_cells);
}
// pack MUX2_LUT8
static void pack_mux2_lut8(Context *ctx, CellInfo *ci, pool<IdString> &packed_cells, pool<IdString> &delete_nets,
std::vector<std::unique_ptr<CellInfo>> &new_cells)
{
static int x[] = {1, 0};
static int z[] = {-4, -4};
pack_mux2_lut(ctx, ci, is_mux2_lut7, '8', id_MUX2_LUT8, x, z, packed_cells, delete_nets, new_cells);
}
// Pack wide LUTs
static void pack_wideluts(Context *ctx)
{
log_info("Packing wide LUTs..\n");
pool<IdString> packed_cells;
pool<IdString> delete_nets;
std::vector<std::unique_ptr<CellInfo>> new_cells;
pool<IdString> mux2lut6;
pool<IdString> mux2lut7;
pool<IdString> mux2lut8;
// do MUX2_LUT5 and collect LUT6/7/8
log_info("Packing LUT5s..\n");
for (auto &cell : ctx->cells) {
CellInfo *ci = cell.second.get();
if (ctx->verbose) {
log_info("cell '%s' is of type '%s'\n", ctx->nameOf(ci), ci->type.c_str(ctx));
}
if (is_widelut(ctx, ci)) {
if (is_mux2_lut5(ctx, ci)) {
pack_mux2_lut5(ctx, ci, packed_cells, delete_nets, new_cells);
} else {
if (is_mux2_lut6(ctx, ci)) {
mux2lut6.insert(ci->name);
} else {
if (is_mux2_lut7(ctx, ci)) {
mux2lut7.insert(ci->name);
} else {
if (is_mux2_lut8(ctx, ci)) {
mux2lut8.insert(ci->name);
}
}
}
}
}
}
// do MUX_LUT6
log_info("Packing LUT6s..\n");
for (auto &cell_name : mux2lut6) {
pack_mux2_lut6(ctx, ctx->cells[cell_name].get(), packed_cells, delete_nets, new_cells);
}
// do MUX_LUT7
log_info("Packing LUT7s..\n");
for (auto &cell_name : mux2lut7) {
pack_mux2_lut7(ctx, ctx->cells[cell_name].get(), packed_cells, delete_nets, new_cells);
}
// do MUX_LUT8
log_info("Packing LUT8s..\n");
for (auto &cell_name : mux2lut8) {
pack_mux2_lut8(ctx, ctx->cells[cell_name].get(), packed_cells, delete_nets, new_cells);
}
// actual delete, erase and move cells/nets
for (auto pcell : packed_cells) {
ctx->cells.erase(pcell);
}
for (auto dnet : delete_nets) {
ctx->nets.erase(dnet);
}
for (auto &ncell : new_cells) {
ctx->cells[ncell->name] = std::move(ncell);
}
}
// Pack LUTs and LUT-FF pairs
static void pack_lut_lutffs(Context *ctx)
{
log_info("Packing LUT-FFs..\n");
pool<IdString> packed_cells;
std::vector<std::unique_ptr<CellInfo>> new_cells;
for (auto &cell : ctx->cells) {
CellInfo *ci = cell.second.get();
if (ctx->verbose)
log_info("cell '%s' is of type '%s'\n", ctx->nameOf(ci), ci->type.c_str(ctx));
if (is_lut(ctx, ci)) {
std::unique_ptr<CellInfo> packed = create_generic_cell(ctx, id_SLICE, ci->name.str(ctx) + "_LC");
for (auto &attr : ci->attrs)
packed->attrs[attr.first] = attr.second;
packed_cells.insert(ci->name);
if (ctx->verbose)
log_info("packed cell %s into %s\n", ctx->nameOf(ci), ctx->nameOf(packed.get()));
// See if we can pack into a DFF
// TODO: LUT cascade
NetInfo *o = ci->ports.at(id_F).net;
CellInfo *dff = net_only_drives(ctx, o, is_ff, id_D, true);
auto lut_bel = ci->attrs.find(id_BEL);
bool packed_dff = false;
if (dff) {
if (ctx->verbose)
log_info("found attached dff %s\n", ctx->nameOf(dff));
auto dff_bel = dff->attrs.find(id_BEL);
if (lut_bel != ci->attrs.end() && dff_bel != dff->attrs.end() && lut_bel->second != dff_bel->second) {
// Locations don't match, can't pack
} else {
lut_to_lc(ctx, ci, packed.get(), false);
dff_to_lc(ctx, dff, packed.get(), false);
ctx->nets.erase(o->name);
if (dff_bel != dff->attrs.end())
packed->attrs[id_BEL] = dff_bel->second;
packed_cells.insert(dff->name);
if (ctx->verbose)
log_info("packed cell %s into %s\n", ctx->nameOf(dff), ctx->nameOf(packed.get()));
packed_dff = true;
}
}
if (!packed_dff) {
lut_to_lc(ctx, ci, packed.get(), true);
}
new_cells.push_back(std::move(packed));
}
}
for (auto pcell : packed_cells) {
ctx->cells.erase(pcell);
}
for (auto &ncell : new_cells) {
ctx->cells[ncell->name] = std::move(ncell);
}
}
// Pack FFs not packed as LUTFFs
static void pack_nonlut_ffs(Context *ctx)
{
log_info("Packing non-LUT FFs..\n");
pool<IdString> packed_cells;
std::vector<std::unique_ptr<CellInfo>> new_cells;
for (auto &cell : ctx->cells) {
CellInfo *ci = cell.second.get();
if (is_ff(ctx, ci)) {
std::unique_ptr<CellInfo> packed = create_generic_cell(ctx, id_SLICE, ci->name.str(ctx) + "_DFFLC");
for (auto &attr : ci->attrs)
packed->attrs[attr.first] = attr.second;
if (ctx->verbose)
log_info("packed cell %s into %s\n", ctx->nameOf(ci), ctx->nameOf(packed.get()));
packed_cells.insert(ci->name);
dff_to_lc(ctx, ci, packed.get(), true);
new_cells.push_back(std::move(packed));
}
}
for (auto pcell : packed_cells) {
ctx->cells.erase(pcell);
}
for (auto &ncell : new_cells) {
ctx->cells[ncell->name] = std::move(ncell);
}
}
// Merge a net into a constant net
static void set_net_constant(const Context *ctx, NetInfo *orig, NetInfo *constnet, bool constval)
{
orig->driver.cell = nullptr;
for (auto user : orig->users) {
if (user.cell != nullptr) {
CellInfo *uc = user.cell;
if (ctx->verbose)
log_info("%s user %s\n", ctx->nameOf(orig), ctx->nameOf(uc));
if ((is_lut(ctx, uc) || is_lc(ctx, uc)) && (user.port.str(ctx).at(0) == 'I') && !constval) {
uc->ports[user.port].net = nullptr;
uc->ports[user.port].user_idx = {};
} else {
uc->ports[user.port].net = constnet;
uc->ports[user.port].user_idx = constnet->users.add(user);
}
}
}
orig->users.clear();
}
// Pack constants (simple implementation)
static void pack_constants(Context *ctx)
{
log_info("Packing constants..\n");
std::unique_ptr<CellInfo> gnd_cell = create_generic_cell(ctx, id_GND, "$PACKER_GND");
auto gnd_net = std::make_unique<NetInfo>(ctx->id("$PACKER_GND_NET"));
gnd_net->driver.cell = gnd_cell.get();
gnd_net->driver.port = id_G;
gnd_cell->ports.at(id_G).net = gnd_net.get();
std::unique_ptr<CellInfo> vcc_cell = create_generic_cell(ctx, id_VCC, "$PACKER_VCC");
auto vcc_net = std::make_unique<NetInfo>(ctx->id("$PACKER_VCC_NET"));
vcc_net->driver.cell = vcc_cell.get();
vcc_net->driver.port = id_V;
vcc_cell->ports.at(id_V).net = vcc_net.get();
std::vector<IdString> dead_nets;
bool gnd_used = true; // XXX May be needed for simplified IO
for (auto &net : ctx->nets) {
NetInfo *ni = net.second.get();
if (ni->driver.cell != nullptr && ni->driver.cell->type == id_GND) {
IdString drv_cell = ni->driver.cell->name;
set_net_constant(ctx, ni, gnd_net.get(), false);
gnd_used = true;
dead_nets.push_back(net.first);
ctx->cells.erase(drv_cell);
} else if (ni->driver.cell != nullptr && ni->driver.cell->type == id_VCC) {
IdString drv_cell = ni->driver.cell->name;
set_net_constant(ctx, ni, vcc_net.get(), true);
dead_nets.push_back(net.first);
ctx->cells.erase(drv_cell);
}
}
if (gnd_used) {
ctx->cells[gnd_cell->name] = std::move(gnd_cell);
ctx->nets[gnd_net->name] = std::move(gnd_net);
}
// Vcc cell always inserted for now, as it may be needed during carry legalisation (TODO: trim later if actually
// never used?)
ctx->cells[vcc_cell->name] = std::move(vcc_cell);
ctx->nets[vcc_net->name] = std::move(vcc_net);
for (auto dn : dead_nets) {
ctx->nets.erase(dn);
}
}
// Pack global set-reset
static void pack_gsr(Context *ctx)
{
log_info("Packing GSR..\n");
bool user_gsr = false;
for (auto &cell : ctx->cells) {
CellInfo *ci = cell.second.get();
if (ctx->verbose)
log_info("cell '%s' is of type '%s'\n", ctx->nameOf(ci), ci->type.c_str(ctx));
if (ci->type == id_GSR) {
user_gsr = true;
break;
}
}
if (!user_gsr) {
// XXX
bool have_gsr_bel = false;
for (auto bi : ctx->bels) {
if (bi.second.type == id_GSR) {
have_gsr_bel = true;
break;
}
}
if (have_gsr_bel) {
// make default GSR
std::unique_ptr<CellInfo> gsr_cell = create_generic_cell(ctx, id_GSR, "GSR");
gsr_cell->connectPort(id_GSRI, ctx->nets[ctx->id("$PACKER_VCC_NET")].get());
ctx->cells[gsr_cell->name] = std::move(gsr_cell);
} else {
log_info("No GSR in the chip base\n");
}
}
}
// Pack shadow RAM
void pack_sram(Context *ctx)
{
log_info("Packing Shadow RAM..\n");
pool<IdString> packed_cells;
std::vector<std::unique_ptr<CellInfo>> new_cells;
for (auto &cell : ctx->cells) {
CellInfo *ci = cell.second.get();
if (is_sram(ctx, ci)) {
// Create RAMW slice
std::unique_ptr<CellInfo> ramw_slice = create_generic_cell(ctx, id_RAMW, ci->name.str(ctx) + "$RAMW_SLICE");
sram_to_ramw_split(ctx, ci, ramw_slice.get());
ramw_slice->connectPort(id_CE, ctx->nets[ctx->id("$PACKER_VCC_NET")].get());
// Create actual RAM slices
std::unique_ptr<CellInfo> ram_comb[4];
for (int i = 0; i < 4; i++) {
ram_comb[i] = create_generic_cell(ctx, id_SLICE, ci->name.str(ctx) + "$SRAM_SLICE" + std::to_string(i));
ram_comb[i]->params[id_FF_USED] = 1;
ram_comb[i]->params[id_FF_TYPE] = std::string("RAM");
sram_to_slice(ctx, ci, ram_comb[i].get(), i);
}
// Create 'block' SLICEs as a placement hint that these cells are mutually exclusive with the RAMW
std::unique_ptr<CellInfo> ramw_block[2];
for (int i = 0; i < 2; i++) {
ramw_block[i] =
create_generic_cell(ctx, id_SLICE, ci->name.str(ctx) + "$RAMW_BLOCK" + std::to_string(i));
ram_comb[i]->params[id_FF_USED] = 1;
ramw_block[i]->params[id_FF_TYPE] = std::string("RAM");
}
// Disconnect ports of original cell after packing
// ci->disconnectPort(id_WCK);
// ci->disconnectPort(id_WRE);
for (int i = 0; i < 4; i++)
ci->disconnectPort(ctx->idf("RAD[%d]", i));
// Setup placement constraints
// Use the 0th bit as an anchor
ram_comb[0]->constr_abs_z = true;
ram_comb[0]->constr_z = 0;
ram_comb[0]->cluster = ram_comb[0]->name;
for (int i = 1; i < 4; i++) {
ram_comb[i]->cluster = ram_comb[0]->name;
ram_comb[i]->constr_abs_z = true;
ram_comb[i]->constr_x = 0;
ram_comb[i]->constr_y = 0;
ram_comb[i]->constr_z = i;
ram_comb[0]->constr_children.push_back(ram_comb[i].get());
}
for (int i = 0; i < 2; i++) {
ramw_block[i]->cluster = ram_comb[0]->name;
ramw_block[i]->constr_abs_z = true;
ramw_block[i]->constr_x = 0;
ramw_block[i]->constr_y = 0;
ramw_block[i]->constr_z = i + 4;
ram_comb[0]->constr_children.push_back(ramw_block[i].get());
}
ramw_slice->cluster = ram_comb[0]->name;
ramw_slice->constr_abs_z = true;
ramw_slice->constr_x = 0;
ramw_slice->constr_y = 0;
ramw_slice->constr_z = BelZ::lutram_0_z;
ram_comb[0]->constr_children.push_back(ramw_slice.get());
for (int i = 0; i < 4; i++)
new_cells.push_back(std::move(ram_comb[i]));
for (int i = 0; i < 2; i++)
new_cells.push_back(std::move(ramw_block[i]));
new_cells.push_back(std::move(ramw_slice));
packed_cells.insert(ci->name);
}
}
for (auto pcell : packed_cells) {
ctx->cells.erase(pcell);
}
for (auto &ncell : new_cells) {
ctx->cells[ncell->name] = std::move(ncell);
}
}
static bool is_nextpnr_iob(const Context *ctx, CellInfo *cell)
{
return cell->type == ctx->id("$nextpnr_ibuf") || cell->type == ctx->id("$nextpnr_obuf") ||
cell->type == ctx->id("$nextpnr_iobuf");
}
static bool is_gowin_iob(const Context *ctx, const CellInfo *cell)
{
switch (cell->type.index) {
case ID_IBUF:
case ID_OBUF:
case ID_IOBUF:
case ID_TBUF:
return true;
default:
return false;
}
}
static bool is_gowin_diff_iob(const Context *ctx, const CellInfo *cell)
{
switch (cell->type.index) {
case ID_TLVDS_OBUF:
return true;
default:
return false;
}
}
static bool is_gowin_iologic(const Context *ctx, const CellInfo *cell)
{
switch (cell->type.index) {
case ID_ODDR: /* fall-through*/
case ID_ODDRC:
return true;
default:
return false;
}
}
// Pack IO logic
static void pack_iologic(Context *ctx)
{
pool<IdString> packed_cells;
pool<IdString> delete_nets;
std::vector<std::unique_ptr<CellInfo>> new_cells;
log_info("Packing IO logic..\n");
for (auto &cell : ctx->cells) {
CellInfo *ci = cell.second.get();
if (ctx->verbose)
log_info("cell '%s' is of type '%s'\n", ctx->nameOf(ci), ci->type.c_str(ctx));
if (is_gowin_iologic(ctx, ci)) {
CellInfo *q0_dst = nullptr;
CellInfo *q1_dst = nullptr;
switch (ci->type.index) {
case ID_ODDRC: /* fall-through*/
case ID_ODDR: {
q0_dst = net_only_drives(ctx, ci->ports.at(id_Q0).net, is_iob, id_I);
NPNR_ASSERT(q0_dst != nullptr);
auto iob_bel = q0_dst->attrs.find(id_BEL);
if (q0_dst->attrs.count(id_DIFF_TYPE)) {
ci->attrs[id_OBUF_TYPE] = std::string("DBUF");
} else {
ci->attrs[id_OBUF_TYPE] = std::string("SBUF");
}
if (iob_bel != q0_dst->attrs.end()) {
// already know there to place, no need of any cluster stuff
Loc loc = ctx->getBelLocation(ctx->getBelByNameStr(iob_bel->second.as_string()));
loc.z += BelZ::iologic_0_z;
ci->attrs[id_BEL] = ctx->getBelName(ctx->getBelByLocation(loc)).str(ctx);
} else {
// make cluster from ODDR and OBUF
ci->cluster = ci->name;
ci->constr_x = 0;
ci->constr_y = 0;
ci->constr_z = 0;
ci->constr_abs_z = false;
ci->constr_children.push_back(q0_dst);
q0_dst->cluster = ci->name;
q0_dst->constr_x = 0;
q0_dst->constr_y = 0;
q0_dst->constr_z = -BelZ::iologic_0_z;
q0_dst->constr_abs_z = false;
}
// disconnect Q0 output: it is wired internally
delete_nets.insert(ci->ports.at(id_Q0).net->name);
q0_dst->disconnectPort(id_I);
ci->disconnectPort(id_Q0);
ci->attrs[id_IOBUF] = 0;
// if Q1 is conected then disconnet it too
if (port_used(ci, id_Q1)) {
q1_dst = net_only_drives(ctx, ci->ports.at(id_Q1).net, is_iob, id_OEN);
if (q1_dst != nullptr) {
delete_nets.insert(ci->ports.at(id_Q1).net->name);
q0_dst->disconnectPort(id_OEN);
ci->disconnectPort(id_Q1);
ci->attrs[id_IOBUF] = 1;
}
}
// if have XXX_ inputs connect them
if (ctx->ddr_has_extra_inputs) {
ci->addInput(id_XXX_VSS);
ci->connectPort(id_XXX_VSS, ctx->nets[ctx->id("$PACKER_GND_NET")].get());
ci->addInput(id_XXX_VCC);
ci->connectPort(id_XXX_VCC, ctx->nets[ctx->id("$PACKER_VCC_NET")].get());
}
if (ctx->gw1n9_quirk && iob_bel != q0_dst->attrs.end()) {
bool have_XXX_VSS0 =
ctx->bels[ctx->getBelByNameStr(iob_bel->second.as_string())].pins.count(id_XXX_VSS0);
if (have_XXX_VSS0) {
q0_dst->disconnectPort(id_XXX_VSS0);
q0_dst->connectPort(id_XXX_VSS0, ctx->nets[ctx->id("$PACKER_VCC_NET")].get());
}
}
} break;
default:
break;
}
}
}
for (auto pcell : packed_cells) {
ctx->cells.erase(pcell);
}
for (auto dnet : delete_nets) {
ctx->nets.erase(dnet);
}
for (auto &ncell : new_cells) {
ctx->cells[ncell->name] = std::move(ncell);
}
}
// Pack differential IO buffers
static void pack_diff_io(Context *ctx)
{
pool<IdString> packed_cells;
pool<IdString> delete_nets;
std::vector<std::unique_ptr<CellInfo>> new_cells;
log_info("Packing diff IOs..\n");
for (auto &cell : ctx->cells) {
CellInfo *ci = cell.second.get();
if (ctx->verbose)
log_info("cell '%s' is of type '%s'\n", ctx->nameOf(ci), ci->type.c_str(ctx));
if (is_gowin_diff_iob(ctx, ci)) {
CellInfo *iob_p = nullptr;
CellInfo *iob_n = nullptr;
switch (ci->type.index) {
case ID_TLVDS_OBUF: {
iob_p = net_only_drives(ctx, ci->ports.at(id_O).net, is_iob, id_I);
iob_n = net_only_drives(ctx, ci->ports.at(id_OB).net, is_iob, id_I);
NPNR_ASSERT(iob_p != nullptr);
NPNR_ASSERT(iob_n != nullptr);
auto iob_p_bel_a = iob_p->attrs.find(id_BEL);
if (iob_p_bel_a == iob_p->attrs.end()) {
log_error("LVDS '%s' must be restricted.\n", ctx->nameOf(ci));
continue;
}
BelId iob_p_bel = ctx->getBelByNameStr(iob_p_bel_a->second.as_string());
Loc loc_p = ctx->getBelLocation(iob_p_bel);
if (loc_p.z != 0) {
log_error("LVDS '%s' positive pin is not A.\n", ctx->nameOf(ci));
continue;
}
// restrict the N buffer
loc_p.z = 1;
iob_n->attrs[id_BEL] = ctx->getBelName(ctx->getBelByLocation(loc_p)).str(ctx);
// mark IOBs as part of DS pair
iob_n->attrs[id_DIFF] = std::string("N");
iob_n->attrs[id_DIFF_TYPE] = std::string("TLVDS_OBUF");
iob_p->attrs[id_DIFF] = std::string("P");
iob_p->attrs[id_DIFF_TYPE] = std::string("TLVDS_OBUF");
// disconnect N input: it is wired internally
delete_nets.insert(iob_n->ports.at(id_I).net->name);
iob_n->disconnectPort(id_I);
ci->disconnectPort(id_OB);
// disconnect P output
delete_nets.insert(ci->ports.at(id_O).net->name);
ci->disconnectPort(id_O);
// connect TLVDS input to P input
ci->movePortTo(id_I, iob_p, id_I);
packed_cells.insert(ci->name);
} break;
default:
break;
}
}
}
for (auto pcell : packed_cells) {
ctx->cells.erase(pcell);
}
for (auto dnet : delete_nets) {
ctx->nets.erase(dnet);
}
for (auto &ncell : new_cells) {
ctx->cells[ncell->name] = std::move(ncell);
}
}
static bool is_pll(const Context *ctx, const CellInfo *cell)
{
switch (cell->type.hash()) {
case ID_rPLL:
return true;
default:
return false;
}
}
// Pack PLLs
static void pack_plls(Context *ctx)
{
pool<IdString> packed_cells;
pool<IdString> delete_nets;
std::vector<std::unique_ptr<CellInfo>> new_cells;
log_info("Packing PLLs..\n");
for (auto &cell : ctx->cells) {
CellInfo *ci = cell.second.get();
if (ctx->verbose)
log_info("cell '%s' is of type '%s'\n", ctx->nameOf(ci), ci->type.c_str(ctx));
if (is_pll(ctx, ci)) {
std::string parm_device = str_or_default(ci->params, id_DEVICE, "GW1N-1");
if (parm_device != ctx->device) {
log_error("Wrong PLL device:%s vs %s\n", parm_device.c_str(), ctx->device.c_str());
continue;
}
switch (ci->type.hash()) {
case ID_rPLL: {
if (parm_device == "GW1N-1" || parm_device == "GW1NZ-1") {
// Unused ports will be disabled during image generation. Here we add flags for such ports.
Property pr_enable("ENABLE"), pr_disable("DISABLE");
IdString ports[][2] = {{id_CLKOUTP, id_CLKOUTPS},
{id_CLKOUTD, id_CLKOUTDIV},
{id_CLKOUTD3, id_CLKOUTDIV3},
{id_LOCK, id_FLOCK}};
for (int i = 0; i < 4; ++i) {
ci->setParam(ports[i][1], port_used(ci, ports[i][0]) ? pr_enable : pr_disable);
}
// B half
std::unique_ptr<CellInfo> cell = create_generic_cell(ctx, id_RPLLB, ci->name.str(ctx) + "$rpllb");
reconnect_rpllb(ctx, ci, cell.get());
new_cells.push_back(std::move(cell));
auto pllb_cell = new_cells.back().get();
// A half
cell = create_generic_cell(ctx, id_RPLLA, ci->name.str(ctx) + "$rplla");
reconnect_rplla(ctx, ci, cell.get());
new_cells.push_back(std::move(cell));
auto plla_cell = new_cells.back().get();
// need params for gowin_pack
for (auto &parm : ci->params) {
plla_cell->setParam(parm.first, parm.second);
pllb_cell->setParam(parm.first, parm.second);
}
packed_cells.insert(ci->name);
} else {
log_error("PLL isn't supported for %s\n", ctx->device.c_str());
}
} break;
default:
break;
}
}
}
for (auto pcell : packed_cells) {
ctx->cells.erase(pcell);
}
for (auto dnet : delete_nets) {
ctx->nets.erase(dnet);
}
for (auto &ncell : new_cells) {
ctx->cells[ncell->name] = std::move(ncell);
}
}
// Pack IO buffers
static void pack_io(Context *ctx)
{
pool<IdString> packed_cells;
pool<IdString> delete_nets;
std::vector<std::unique_ptr<CellInfo>> new_cells;
log_info("Packing IOs..\n");
for (auto &cell : ctx->cells) {
CellInfo *ci = cell.second.get();
if (ctx->verbose)
log_info("cell '%s' is of type '%s'\n", ctx->nameOf(ci), ci->type.c_str(ctx));
if (is_gowin_iob(ctx, ci)) {
CellInfo *iob = nullptr;
switch (ci->type.index) {
case ID_IBUF:
iob = net_driven_by(ctx, ci->ports.at(id_I).net, is_nextpnr_iob, id_O);
break;
case ID_OBUF:
iob = net_only_drives(ctx, ci->ports.at(id_O).net, is_nextpnr_iob, id_I);
break;
case ID_IOBUF:
iob = net_driven_by(ctx, ci->ports.at(id_IO).net, is_nextpnr_iob, id_O);
break;
case ID_TBUF:
iob = net_only_drives(ctx, ci->ports.at(id_O).net, is_nextpnr_iob, id_I);
break;
default:
break;
}
if (iob != nullptr) {
// delete the $nexpnr_[io]buf
for (auto &p : iob->ports) {
IdString netname = p.second.net->name;
iob->disconnectPort(p.first);
delete_nets.insert(netname);
}
packed_cells.insert(iob->name);
}
// what type to create
IdString new_cell_type = id_IOB;
std::string constr_bel_name = std::string("");
bool have_xxx_port = false;
// check whether the given IO is limited to simplified IO cells
auto constr_bel = ci->attrs.find(id_BEL);
if (constr_bel != ci->attrs.end()) {
constr_bel_name = constr_bel->second.as_string();
}
if (iob != nullptr) {
constr_bel = iob->attrs.find(id_BEL);
if (constr_bel != iob->attrs.end()) {
constr_bel_name = constr_bel->second.as_string();
}
}
if (!constr_bel_name.empty()) {
BelId constr_bel = ctx->getBelByNameStr(constr_bel_name);
if (constr_bel != BelId()) {
new_cell_type = ctx->bels[constr_bel].type;
if (ctx->gw1n9_quirk) {
have_xxx_port = ctx->bels[constr_bel].pins.count(id_XXX_VSS0) != 0;
}
}
}
// Create a IOB buffer
std::unique_ptr<CellInfo> ice_cell = create_generic_cell(ctx, new_cell_type, ci->name.str(ctx) + "$iob");
gwio_to_iob(ctx, ci, ice_cell.get(), packed_cells);
new_cells.push_back(std::move(ice_cell));
auto gwiob = new_cells.back().get();
// XXX GW1NR-9 quirks
if (have_xxx_port && ci->type != id_IBUF) {
gwiob->addInput(id_XXX_VSS0);
gwiob->connectPort(id_XXX_VSS0, ctx->nets[ctx->id("$PACKER_GND_NET")].get());
gwiob->addInput(id_XXX_VSS1);
gwiob->connectPort(id_XXX_VSS1, ctx->nets[ctx->id("$PACKER_GND_NET")].get());
}
packed_cells.insert(ci->name);
if (iob != nullptr) {
// in Gowin .CST port attributes take precedence over cell attributes.
// first copy cell attrs related to IO
for (auto &attr : ci->attrs) {
if (attr.first == IdString(ID_BEL) || attr.first.str(ctx)[0] == '&') {
gwiob->setAttr(attr.first, attr.second);
}
}
// rewrite attributes from the port
for (auto &attr : iob->attrs) {
gwiob->setAttr(attr.first, attr.second);
}
}
}
}
for (auto pcell : packed_cells) {
ctx->cells.erase(pcell);
}
for (auto dnet : delete_nets) {
ctx->nets.erase(dnet);
}
for (auto &ncell : new_cells) {
ctx->cells[ncell->name] = std::move(ncell);
}
}
// Main pack function
bool Arch::pack()
{
Context *ctx = getCtx();
try {
log_break();
pre_pack(ctx);
pack_constants(ctx);
pack_sram(ctx);
pack_gsr(ctx);
pack_io(ctx);
pack_diff_io(ctx);
pack_iologic(ctx);
pack_wideluts(ctx);
pack_alus(ctx);
pack_lut_lutffs(ctx);
pack_nonlut_ffs(ctx);
pack_plls(ctx);
post_pack(ctx);
ctx->settings[id_pack] = 1;
ctx->assignArchInfo();
log_info("Checksum: 0x%08x\n", ctx->checksum());
return true;
} catch (log_execution_error_exception) {
return false;
}
}
NEXTPNR_NAMESPACE_END