mirror of https://github.com/YosysHQ/nextpnr.git
426 lines
15 KiB
C++
426 lines
15 KiB
C++
/*
|
|
* nextpnr -- Next Generation Place and Route
|
|
*
|
|
* Copyright (C) 2024 The Project Peppercorn Authors.
|
|
*
|
|
* 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 "pack.h"
|
|
#include "design_utils.h"
|
|
#include "gatemate_util.h"
|
|
|
|
#define HIMBAECHEL_CONSTIDS "uarch/gatemate/constids.inc"
|
|
#include "himbaechel_constids.h"
|
|
|
|
NEXTPNR_NAMESPACE_BEGIN
|
|
|
|
void GateMatePacker::flush_cells()
|
|
{
|
|
for (auto pcell : packed_cells) {
|
|
for (auto &port : ctx->cells[pcell]->ports) {
|
|
ctx->cells[pcell]->disconnectPort(port.first);
|
|
}
|
|
if (ctx->cells[pcell]->bel != BelId())
|
|
ctx->unbindBel(ctx->cells[pcell]->bel);
|
|
ctx->cells.erase(pcell);
|
|
}
|
|
packed_cells.clear();
|
|
}
|
|
|
|
void GateMatePacker::disconnect_if_gnd(CellInfo *cell, IdString input)
|
|
{
|
|
if (cell->getPort(input) == net_PACKER_GND)
|
|
cell->disconnectPort(input);
|
|
}
|
|
|
|
void GateMatePacker::pack_misc()
|
|
{
|
|
log_info("Packing misc..\n");
|
|
for (auto &cell : ctx->cells) {
|
|
CellInfo &ci = *cell.second;
|
|
if (!ci.type.in(id_CC_USR_RSTN))
|
|
continue;
|
|
ci.type = id_USR_RSTN;
|
|
ci.cluster = ci.name;
|
|
Loc fixed_loc = uarch->locations[std::make_pair(id_USR_RSTN, uarch->preferred_die)];
|
|
ctx->bindBel(ctx->getBelByLocation(fixed_loc), &ci, PlaceStrength::STRENGTH_FIXED);
|
|
|
|
move_ram_i_fixed(&ci, id_USR_RSTN, fixed_loc);
|
|
}
|
|
for (auto &cell : ctx->cells) {
|
|
CellInfo &ci = *cell.second;
|
|
if (!ci.type.in(id_CC_CFG_CTRL))
|
|
continue;
|
|
ci.type = id_CFG_CTRL;
|
|
ci.cluster = ci.name;
|
|
Loc fixed_loc = uarch->locations[std::make_pair(id_CFG_CTRL, uarch->preferred_die)];
|
|
ctx->bindBel(ctx->getBelByLocation(fixed_loc), &ci, PlaceStrength::STRENGTH_FIXED);
|
|
|
|
move_ram_o_fixed(&ci, id_CLK, fixed_loc);
|
|
move_ram_o_fixed(&ci, id_EN, fixed_loc);
|
|
move_ram_o_fixed(&ci, id_VALID, fixed_loc);
|
|
move_ram_o_fixed(&ci, id_RECFG, fixed_loc);
|
|
for (int i = 0; i < 8; i++)
|
|
move_ram_o_fixed(&ci, ctx->idf("DATA[%d]", i), fixed_loc);
|
|
}
|
|
for (auto &cell : ctx->cells) {
|
|
CellInfo &ci = *cell.second;
|
|
if (!ci.type.in(id_CC_ODDR, id_CC_IDDR))
|
|
continue;
|
|
log_error("Cell '%s' of type %s is not connected to GPIO pin.\n", ci.name.c_str(ctx), ci.type.c_str(ctx));
|
|
}
|
|
}
|
|
|
|
void GateMatePacker::disconnect_not_used()
|
|
{
|
|
for (auto &cell : ctx->cells) {
|
|
CellInfo &ci = *cell.second;
|
|
for (auto &p : ci.ports) {
|
|
if (p.second.type == PortType::PORT_OUT) {
|
|
NetInfo *net = ci.getPort(p.first);
|
|
if (net && net->users.entries() == 0) {
|
|
ci.disconnectPort(p.first);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void GateMatePacker::copy_constraint(const NetInfo *in_net, NetInfo *out_net)
|
|
{
|
|
if (!in_net || !out_net)
|
|
return;
|
|
if (ctx->debug)
|
|
log_info("copy clock period constraint on net '%s' from net '%s'\n", out_net->name.c_str(ctx),
|
|
in_net->name.c_str(ctx));
|
|
if (out_net->clkconstr.get() != nullptr)
|
|
log_warning("found multiple clock constraints on net '%s'\n", out_net->name.c_str(ctx));
|
|
if (in_net->clkconstr) {
|
|
out_net->clkconstr = std::unique_ptr<ClockConstraint>(new ClockConstraint());
|
|
out_net->clkconstr->low = in_net->clkconstr->low;
|
|
out_net->clkconstr->high = in_net->clkconstr->high;
|
|
out_net->clkconstr->period = in_net->clkconstr->period;
|
|
}
|
|
}
|
|
|
|
void GateMatePacker::move_connections(NetInfo *from_net, NetInfo *to_net)
|
|
{
|
|
for (const auto &usr : from_net->users) {
|
|
IdString port = usr.port;
|
|
usr.cell->disconnectPort(port);
|
|
usr.cell->connectPort(port, to_net);
|
|
}
|
|
}
|
|
|
|
void GateMatePacker::count_cell(CellInfo &ci)
|
|
{
|
|
packed_cells.insert(ci.name);
|
|
count_per_type[ci.type]++;
|
|
count++;
|
|
}
|
|
|
|
void GateMatePacker::optimize_lut()
|
|
{
|
|
for (auto &cell : ctx->cells) {
|
|
CellInfo &ci = *cell.second;
|
|
if (!ci.type.in(id_CC_LUT1, id_CC_LUT2))
|
|
continue;
|
|
NetInfo *o_net = ci.getPort(id_O);
|
|
if (!o_net) {
|
|
count_cell(ci);
|
|
continue;
|
|
}
|
|
|
|
uint8_t val = int_or_default(ci.params, id_INIT, 0);
|
|
if (ci.type == id_CC_LUT1)
|
|
val = val << 2 | val;
|
|
switch (val) {
|
|
case LUT_ZERO: // constant 0
|
|
move_connections(o_net, net_PACKER_GND);
|
|
count_cell(ci);
|
|
break;
|
|
case LUT_D0: // propagate
|
|
move_connections(o_net, ci.getPort(id_I0));
|
|
count_cell(ci);
|
|
break;
|
|
case LUT_D1: // propagate
|
|
move_connections(o_net, ci.getPort(id_I1));
|
|
count_cell(ci);
|
|
break;
|
|
case LUT_ONE: // constant 1
|
|
move_connections(o_net, net_PACKER_VCC);
|
|
count_cell(ci);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
flush_cells();
|
|
}
|
|
|
|
void GateMatePacker::optimize_mx()
|
|
{
|
|
for (auto &cell : ctx->cells) {
|
|
CellInfo &ci = *cell.second;
|
|
if (!ci.type.in(id_CC_MX2, id_CC_MX4))
|
|
continue;
|
|
NetInfo *y_net = ci.getPort(id_Y);
|
|
if (!y_net) {
|
|
count_cell(ci);
|
|
continue;
|
|
}
|
|
if (ci.type == id_CC_MX2) {
|
|
if (ci.getPort(id_S0) == net_PACKER_GND) {
|
|
move_connections(y_net, ci.getPort(id_D0));
|
|
count_cell(ci);
|
|
continue;
|
|
} else if (ci.getPort(id_S0) == net_PACKER_VCC) {
|
|
move_connections(y_net, ci.getPort(id_D1));
|
|
count_cell(ci);
|
|
continue;
|
|
}
|
|
} else {
|
|
if ((ci.getPort(id_S1) == net_PACKER_GND) && (ci.getPort(id_S0) == net_PACKER_GND)) {
|
|
move_connections(y_net, ci.getPort(id_D0));
|
|
count_cell(ci);
|
|
continue;
|
|
} else if ((ci.getPort(id_S1) == net_PACKER_GND) && (ci.getPort(id_S0) == net_PACKER_VCC)) {
|
|
move_connections(y_net, ci.getPort(id_D1));
|
|
count_cell(ci);
|
|
continue;
|
|
} else if ((ci.getPort(id_S1) == net_PACKER_VCC) && (ci.getPort(id_S0) == net_PACKER_GND)) {
|
|
move_connections(y_net, ci.getPort(id_D2));
|
|
count_cell(ci);
|
|
continue;
|
|
} else if ((ci.getPort(id_S1) == net_PACKER_VCC) && (ci.getPort(id_S0) == net_PACKER_VCC)) {
|
|
move_connections(y_net, ci.getPort(id_D3));
|
|
count_cell(ci);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
flush_cells();
|
|
}
|
|
|
|
void GateMatePacker::optimize_ff()
|
|
{
|
|
for (auto &cell : ctx->cells) {
|
|
CellInfo &ci = *cell.second;
|
|
if (!ci.type.in(id_CC_DFF, id_CC_DLT))
|
|
continue;
|
|
|
|
NetInfo *q_net = ci.getPort(id_Q);
|
|
if (!q_net) {
|
|
count_cell(ci);
|
|
continue;
|
|
}
|
|
|
|
int cpe_clk = int_or_default(ci.params, id_C_CPE_CLK, 0);
|
|
int cpe_en = int_or_default(ci.params, id_C_CPE_EN, 0);
|
|
int cpe_res = int_or_default(ci.params, id_C_CPE_RES, 0);
|
|
int cpe_set = int_or_default(ci.params, id_C_CPE_SET, 0);
|
|
int ff_init = int_or_default(ci.params, id_FF_INIT, 0);
|
|
bool ff_has_init = (ff_init >> 1) & 1;
|
|
bool ff_init_value = ff_init & 1;
|
|
|
|
if (cpe_res == 0) { // RES is always ON
|
|
move_connections(q_net, net_PACKER_GND);
|
|
count_cell(ci);
|
|
continue;
|
|
}
|
|
if (cpe_set == 0) { // SET is always ON
|
|
move_connections(q_net, net_PACKER_VCC);
|
|
count_cell(ci);
|
|
continue;
|
|
}
|
|
|
|
if (ci.type == id_CC_DFF) {
|
|
if ((cpe_en == 0 || cpe_clk == 0) && ci.getPort(id_SR) == nullptr) {
|
|
// Only when there is no SR signal
|
|
// EN always OFF (never loads) or CLK never triggers
|
|
move_connections(q_net,
|
|
ff_has_init ? (ff_init_value ? net_PACKER_VCC : net_PACKER_GND) : net_PACKER_GND);
|
|
count_cell(ci);
|
|
continue;
|
|
}
|
|
} else {
|
|
if (cpe_clk == 3 && ci.getPort(id_SR) == nullptr && cpe_res == 3 && cpe_set == 3) {
|
|
// Clamp G if there is no set or reset
|
|
move_connections(q_net, ci.getPort(id_D));
|
|
count_cell(ci);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
flush_cells();
|
|
}
|
|
|
|
void GateMatePacker::cleanup()
|
|
{
|
|
log_info("Running cleanups..\n");
|
|
dff_update_params();
|
|
int i = 1;
|
|
do {
|
|
count = 0;
|
|
disconnect_not_used();
|
|
optimize_lut();
|
|
optimize_mx();
|
|
optimize_ff();
|
|
for (auto c : count_per_type)
|
|
log_info(" %6d %s cells removed (iteration %d)\n", c.second, c.first.c_str(ctx), i);
|
|
count_per_type.clear();
|
|
i++;
|
|
} while (count != 0);
|
|
}
|
|
|
|
void GateMatePacker::rename_param(CellInfo *cell, IdString name, IdString new_name, int width)
|
|
{
|
|
if (cell->params.count(name)) {
|
|
cell->params[new_name] = Property(int_or_default(cell->params, name, 0), width);
|
|
cell->unsetParam(name);
|
|
}
|
|
}
|
|
|
|
void GateMatePacker::repack_cpe()
|
|
{
|
|
log_info("Repacking CPEs..\n");
|
|
for (auto &cell : ctx->cells) {
|
|
if (cell.second->type.in(id_CPE_L2T4)) {
|
|
Loc l = ctx->getBelLocation(cell.second->bel);
|
|
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);
|
|
}
|
|
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)) {
|
|
BelId bel = cell.second->bel;
|
|
PlaceStrength strength = cell.second->belStrength;
|
|
uint8_t func = int_or_default(cell.second->params, id_C_FUNCTION, 0);
|
|
Loc loc = ctx->getBelLocation(bel);
|
|
loc.z = CPE_LT_FULL_Z;
|
|
ctx->unbindBel(bel);
|
|
ctx->bindBel(ctx->getBelByLocation(loc), cell.second.get(), strength);
|
|
cell.second->renamePort(id_IN1, id_IN5);
|
|
cell.second->renamePort(id_IN2, id_IN6);
|
|
cell.second->renamePort(id_IN3, id_IN7);
|
|
cell.second->renamePort(id_IN4, id_IN8);
|
|
cell.second->renamePort(id_OUT, id_OUT1);
|
|
cell.second->renamePort(id_CPOUT, id_CPOUT1);
|
|
if (!cell.second->params.count(id_INIT_L20))
|
|
cell.second->params[id_INIT_L20] = Property(LUT_D1, 4);
|
|
rename_param(cell.second.get(), id_INIT_L00, id_INIT_L02, 4);
|
|
rename_param(cell.second.get(), id_INIT_L01, id_INIT_L03, 4);
|
|
rename_param(cell.second.get(), id_INIT_L10, id_INIT_L11, 4);
|
|
|
|
switch (func) {
|
|
case C_ADDF:
|
|
cell.second->type = id_CPE_ADDF;
|
|
break;
|
|
case C_ADDF2:
|
|
cell.second->type = id_CPE_ADDF2;
|
|
break;
|
|
case C_MULT:
|
|
cell.second->type = id_CPE_MULT;
|
|
break;
|
|
case C_MX4:
|
|
cell.second->type = id_CPE_MX4;
|
|
break;
|
|
case C_EN_CIN:
|
|
log_error("EN_CIN should be using L2T4.\n");
|
|
break;
|
|
case C_CONCAT:
|
|
cell.second->type = id_CPE_CONCAT;
|
|
break;
|
|
case C_ADDCIN:
|
|
log_error("ADDCIN should be using L2T4.\n");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
loc.z = CPE_LT_U_Z;
|
|
CellInfo *upper = ctx->getBoundBelCell(ctx->getBelByLocation(loc));
|
|
if (upper->params.count(id_INIT_L00))
|
|
cell.second->params[id_INIT_L00] = Property(int_or_default(upper->params, id_INIT_L00, 0), 4);
|
|
if (upper->params.count(id_INIT_L01))
|
|
cell.second->params[id_INIT_L01] = Property(int_or_default(upper->params, id_INIT_L01, 0), 4);
|
|
if (upper->params.count(id_INIT_L10))
|
|
cell.second->params[id_INIT_L10] = Property(int_or_default(upper->params, id_INIT_L10, 0), 4);
|
|
if (upper->params.count(id_C_I1))
|
|
cell.second->params[id_C_I1] = Property(int_or_default(upper->params, id_C_I1, 0), 1);
|
|
if (upper->params.count(id_C_I2))
|
|
cell.second->params[id_C_I2] = Property(int_or_default(upper->params, id_C_I2, 0), 1);
|
|
upper->movePortTo(id_IN1, cell.second.get(), id_IN1);
|
|
upper->movePortTo(id_IN2, cell.second.get(), id_IN2);
|
|
upper->movePortTo(id_IN3, cell.second.get(), id_IN3);
|
|
upper->movePortTo(id_IN4, cell.second.get(), id_IN4);
|
|
upper->movePortTo(id_OUT, cell.second.get(), id_OUT2);
|
|
upper->movePortTo(id_CPOUT, cell.second.get(), id_CPOUT2);
|
|
|
|
}
|
|
// Mark for deletion
|
|
else if (cell.second->type.in(id_CPE_LT_U, id_CPE_DUMMY)) {
|
|
packed_cells.insert(cell.second->name);
|
|
}
|
|
}
|
|
flush_cells();
|
|
}
|
|
|
|
void GateMateImpl::pack()
|
|
{
|
|
const ArchArgs &args = ctx->args;
|
|
if (args.options.count("ccf")) {
|
|
parse_ccf(args.options.at("ccf"));
|
|
}
|
|
|
|
if (forced_die != IdString())
|
|
preferred_die = die_to_index[forced_die];
|
|
|
|
GateMatePacker packer(ctx, this);
|
|
packer.pack_constants();
|
|
packer.cleanup();
|
|
packer.pack_io();
|
|
packer.insert_clocking();
|
|
packer.pack_pll();
|
|
packer.pack_bufg();
|
|
packer.pack_io_sel(); // merge in FF and DDR
|
|
packer.pack_misc();
|
|
packer.pack_ram();
|
|
packer.pack_serdes();
|
|
packer.pack_mult();
|
|
packer.pack_addf();
|
|
packer.pack_cpe();
|
|
packer.copy_clocks();
|
|
packer.remove_constants();
|
|
|
|
if (forced_die != IdString()) {
|
|
for (auto &cell : ctx->cells) {
|
|
if (cell.second->belStrength != PlaceStrength::STRENGTH_FIXED)
|
|
ctx->constrainCellToRegion(cell.second->name, forced_die);
|
|
}
|
|
}
|
|
}
|
|
|
|
void GateMateImpl::repack()
|
|
{
|
|
GateMatePacker packer(ctx, this);
|
|
packer.repack_ram();
|
|
packer.repack_cpe();
|
|
packer.reassign_clocks();
|
|
packer.remove_clocking();
|
|
}
|
|
|
|
NEXTPNR_NAMESPACE_END
|