gowin: Himbaechel. Add ALU.
- Added support for ALU running in "2" ADDSUB mode, the mode that yosys generates for gowin; - Supports specifying an arbitrary input carry as well as passing the output carry to logic; - A small restructuring of the source files. Signed-off-by: YRabbit <rabbit@yrabbit.cyou>
This commit is contained in:
parent
c82654d003
commit
c9b23a01db
@ -1,12 +1,62 @@
|
||||
#include "himbaechel_api.h"
|
||||
#include "himbaechel_helpers.h"
|
||||
#include "log.h"
|
||||
#include "nextpnr.h"
|
||||
#include "util.h"
|
||||
|
||||
#define GEN_INIT_CONSTIDS
|
||||
#define HIMBAECHEL_CONSTIDS "uarch/gowin/constids.inc"
|
||||
#include "himbaechel_constids.h"
|
||||
|
||||
#include "gowin.h"
|
||||
#include "pack.h"
|
||||
|
||||
NEXTPNR_NAMESPACE_BEGIN
|
||||
|
||||
namespace {
|
||||
struct GowinImpl : HimbaechelAPI
|
||||
{
|
||||
|
||||
~GowinImpl(){};
|
||||
void init_constids(Arch *arch) override { init_uarch_constids(arch); }
|
||||
void init(Context *ctx) override;
|
||||
|
||||
void prePlace() override;
|
||||
void postPlace() override;
|
||||
void pack() override;
|
||||
|
||||
bool isBelLocationValid(BelId bel, bool explain_invalid) const override;
|
||||
|
||||
// Bel bucket functions
|
||||
IdString getBelBucketForCellType(IdString cell_type) const override;
|
||||
|
||||
bool isValidBelForCellType(IdString cell_type, BelId bel) const override;
|
||||
|
||||
private:
|
||||
HimbaechelHelpers h;
|
||||
|
||||
// Validity checking
|
||||
struct GowinCellInfo
|
||||
{
|
||||
const NetInfo *lut_f = nullptr;
|
||||
const NetInfo *ff_d = nullptr, *ff_ce = nullptr, *ff_clk = nullptr, *ff_lsr = nullptr;
|
||||
const NetInfo *alu_sum = nullptr;
|
||||
};
|
||||
std::vector<GowinCellInfo> fast_cell_info;
|
||||
void assign_cell_info();
|
||||
|
||||
// bel placement validation
|
||||
bool slice_valid(int x, int y, int z) const;
|
||||
};
|
||||
|
||||
struct GowinArch : HimbaechelArch
|
||||
{
|
||||
GowinArch() : HimbaechelArch("gowin"){};
|
||||
std::unique_ptr<HimbaechelAPI> create(const dict<std::string, std::string> &args)
|
||||
{
|
||||
return std::make_unique<GowinImpl>();
|
||||
}
|
||||
} gowinrArch;
|
||||
|
||||
void GowinImpl::init(Context *ctx)
|
||||
{
|
||||
h.init(ctx);
|
||||
@ -21,44 +71,23 @@ void GowinImpl::init(Context *ctx)
|
||||
|
||||
void GowinImpl::prePlace() { assign_cell_info(); }
|
||||
|
||||
void GowinImpl::pack()
|
||||
{
|
||||
// Trim nextpnr IOBs - assume IO buffer insertion has been done in synthesis
|
||||
const pool<CellTypePort> top_ports{
|
||||
CellTypePort(id_IBUF, id_I),
|
||||
CellTypePort(id_OBUF, id_O),
|
||||
};
|
||||
h.remove_nextpnr_iobs(top_ports);
|
||||
// Replace constants with LUTs
|
||||
const dict<IdString, Property> vcc_params;
|
||||
const dict<IdString, Property> gnd_params;
|
||||
h.replace_constants(CellTypePort(id_GOWIN_VCC, id_V), CellTypePort(id_GOWIN_GND, id_G), vcc_params, gnd_params);
|
||||
|
||||
// disconnect the constant LUT inputs
|
||||
mod_lut_inputs();
|
||||
|
||||
pack_wideluts();
|
||||
|
||||
// Constrain directly connected LUTs and FFs together to use dedicated resources
|
||||
auto lut_outs = pool<CellTypePort>{{id_LUT1, id_F}, {id_LUT2, id_F}, {id_LUT3, id_F}, {id_LUT4, id_F}};
|
||||
auto dff_ins = pool<CellTypePort>{{id_DFF, id_D}, {id_DFFE, id_D}, {id_DFFN, id_D}, {id_DFFNE, id_D},
|
||||
{id_DFFS, id_D}, {id_DFFSE, id_D}, {id_DFFNS, id_D}, {id_DFFNSE, id_D},
|
||||
{id_DFFR, id_D}, {id_DFFRE, id_D}, {id_DFFNR, id_D}, {id_DFFNRE, id_D},
|
||||
{id_DFFP, id_D}, {id_DFFPE, id_D}, {id_DFFNP, id_D}, {id_DFFNPE, id_D},
|
||||
{id_DFFC, id_D}, {id_DFFCE, id_D}, {id_DFFNC, id_D}, {id_DFFNCE, id_D}};
|
||||
|
||||
int lutffs = h.constrain_cell_pairs(lut_outs, dff_ins, 1);
|
||||
log_info("Constrained %d LUTFF pairs.\n", lutffs);
|
||||
}
|
||||
void GowinImpl::pack() { gowin_pack(ctx); }
|
||||
|
||||
bool GowinImpl::isBelLocationValid(BelId bel, bool explain_invalid) const
|
||||
{
|
||||
Loc l = ctx->getBelLocation(bel);
|
||||
if (ctx->getBelType(bel).in(id_LUT4, id_DFF)) {
|
||||
return slice_valid(l.x, l.y, l.z / 2);
|
||||
} else {
|
||||
if (!ctx->getBoundBelCell(bel)) {
|
||||
return true;
|
||||
}
|
||||
IdString bel_type = ctx->getBelType(bel);
|
||||
if (bel_type.in(id_LUT4, id_DFF)) {
|
||||
return slice_valid(l.x, l.y, l.z / 2);
|
||||
} else {
|
||||
if (bel_type == id_ALU) {
|
||||
return slice_valid(l.x, l.y, l.z - BelZ::ALU0_Z);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Bel bucket functions
|
||||
@ -111,7 +140,9 @@ void GowinImpl::assign_cell_info()
|
||||
auto &fc = fast_cell_info.at(ci->flat_index);
|
||||
if (is_lut(ci)) {
|
||||
fc.lut_f = ci->getPort(id_F);
|
||||
} else if (is_dff(ci)) {
|
||||
continue;
|
||||
}
|
||||
if (is_dff(ci)) {
|
||||
fc.ff_d = ci->getPort(id_D);
|
||||
fc.ff_clk = ci->getPort(id_CLK);
|
||||
fc.ff_ce = ci->getPort(id_CE);
|
||||
@ -121,209 +152,100 @@ void GowinImpl::assign_cell_info()
|
||||
break;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (is_alu(ci)) {
|
||||
fc.alu_sum = ci->getPort(id_SUM);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// placement validation
|
||||
bool GowinImpl::slice_valid(int x, int y, int z) const
|
||||
{
|
||||
const CellInfo *lut = ctx->getBoundBelCell(ctx->getBelByLocation(Loc(x, y, z * 2)));
|
||||
const CellInfo *ff = ctx->getBoundBelCell(ctx->getBelByLocation(Loc(x, y, z * 2 + 1)));
|
||||
if (!ff) {
|
||||
return true; // always valid if only LUT used
|
||||
}
|
||||
const auto &ff_data = fast_cell_info.at(ff->flat_index);
|
||||
if (lut) {
|
||||
const auto &lut_data = fast_cell_info.at(lut->flat_index);
|
||||
if (ff_data.ff_d != lut_data.lut_f)
|
||||
return false;
|
||||
}
|
||||
int adj_z = (1 - (z & 1) * 2 + z) * 2 + 1;
|
||||
const CellInfo *adj_ff = ctx->getBoundBelCell(ctx->getBelByLocation(Loc(x, y, adj_z)));
|
||||
if (adj_ff == nullptr) {
|
||||
return true;
|
||||
}
|
||||
// DFFs must be same type or compatible
|
||||
if (ff->type != adj_ff->type &&
|
||||
((ff->type.in(id_DFFS) && !adj_ff->type.in(id_DFFR)) || (ff->type.in(id_DFFR) && !adj_ff->type.in(id_DFFS)) ||
|
||||
(ff->type.in(id_DFFSE) && !adj_ff->type.in(id_DFFRE)) ||
|
||||
(ff->type.in(id_DFFRE) && !adj_ff->type.in(id_DFFSE)) || (ff->type.in(id_DFFP) && !adj_ff->type.in(id_DFFC)) ||
|
||||
(ff->type.in(id_DFFC) && !adj_ff->type.in(id_DFFP)) || (ff->type.in(id_DFFPE) && !adj_ff->type.in(id_DFFCE)) ||
|
||||
(ff->type.in(id_DFFCE) && !adj_ff->type.in(id_DFFPE)) ||
|
||||
(ff->type.in(id_DFFNS) && !adj_ff->type.in(id_DFFNR)) ||
|
||||
(ff->type.in(id_DFFNR) && !adj_ff->type.in(id_DFFNS)) ||
|
||||
(ff->type.in(id_DFFNSE) && !adj_ff->type.in(id_DFFNRE)) ||
|
||||
(ff->type.in(id_DFFNRE) && !adj_ff->type.in(id_DFFNSE)) ||
|
||||
(ff->type.in(id_DFFNP) && !adj_ff->type.in(id_DFFNC)) ||
|
||||
(ff->type.in(id_DFFNC) && !adj_ff->type.in(id_DFFNP)) ||
|
||||
(ff->type.in(id_DFFNPE) && !adj_ff->type.in(id_DFFNCE)) ||
|
||||
(ff->type.in(id_DFFNCE) && !adj_ff->type.in(id_DFFNPE)))) {
|
||||
const CellInfo *alu = ctx->getBoundBelCell(ctx->getBelByLocation(Loc(x, y, z + BelZ::ALU0_Z)));
|
||||
|
||||
if (alu && lut) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// CE, LSR and CLK must match
|
||||
const auto &adj_ff_data = fast_cell_info.at(adj_ff->flat_index);
|
||||
if (adj_ff_data.ff_lsr == ff_data.ff_lsr) {
|
||||
return true;
|
||||
// check for ALU/LUT in the adjacent cell
|
||||
int adj_lut_z = (1 - (z & 1) * 2 + z) * 2;
|
||||
int adj_alu_z = adj_lut_z / 2 + BelZ::ALU0_Z;
|
||||
const CellInfo *adj_lut = ctx->getBoundBelCell(ctx->getBelByLocation(Loc(x, y, adj_lut_z)));
|
||||
const CellInfo *adj_ff = ctx->getBoundBelCell(ctx->getBelByLocation(Loc(x, y, adj_lut_z + 1)));
|
||||
const CellInfo *adj_alu = ctx->getBoundBelCell(ctx->getBelByLocation(Loc(x, y, adj_alu_z)));
|
||||
|
||||
if ((alu && (adj_lut || (adj_ff && !adj_alu))) || ((lut || (ff && !alu)) && adj_alu)) {
|
||||
return false;
|
||||
}
|
||||
//
|
||||
return false;
|
||||
|
||||
// if there is DFF it must be connected to this LUT or ALU
|
||||
if (ff) {
|
||||
const auto &ff_data = fast_cell_info.at(ff->flat_index);
|
||||
if (lut) {
|
||||
const auto &lut_data = fast_cell_info.at(lut->flat_index);
|
||||
if (ff_data.ff_d != lut_data.lut_f) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (alu) {
|
||||
const auto &alu_data = fast_cell_info.at(alu->flat_index);
|
||||
if (ff_data.ff_d != alu_data.alu_sum) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (adj_ff) {
|
||||
// DFFs must be same type or compatible
|
||||
if (ff->type != adj_ff->type && ((ff->type.in(id_DFFS) && !adj_ff->type.in(id_DFFR)) ||
|
||||
(ff->type.in(id_DFFR) && !adj_ff->type.in(id_DFFS)) ||
|
||||
(ff->type.in(id_DFFSE) && !adj_ff->type.in(id_DFFRE)) ||
|
||||
(ff->type.in(id_DFFRE) && !adj_ff->type.in(id_DFFSE)) ||
|
||||
(ff->type.in(id_DFFP) && !adj_ff->type.in(id_DFFC)) ||
|
||||
(ff->type.in(id_DFFC) && !adj_ff->type.in(id_DFFP)) ||
|
||||
(ff->type.in(id_DFFPE) && !adj_ff->type.in(id_DFFCE)) ||
|
||||
(ff->type.in(id_DFFCE) && !adj_ff->type.in(id_DFFPE)) ||
|
||||
(ff->type.in(id_DFFNS) && !adj_ff->type.in(id_DFFNR)) ||
|
||||
(ff->type.in(id_DFFNR) && !adj_ff->type.in(id_DFFNS)) ||
|
||||
(ff->type.in(id_DFFNSE) && !adj_ff->type.in(id_DFFNRE)) ||
|
||||
(ff->type.in(id_DFFNRE) && !adj_ff->type.in(id_DFFNSE)) ||
|
||||
(ff->type.in(id_DFFNP) && !adj_ff->type.in(id_DFFNC)) ||
|
||||
(ff->type.in(id_DFFNC) && !adj_ff->type.in(id_DFFNP)) ||
|
||||
(ff->type.in(id_DFFNPE) && !adj_ff->type.in(id_DFFNCE)) ||
|
||||
(ff->type.in(id_DFFNCE) && !adj_ff->type.in(id_DFFNPE)))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// CE, LSR and CLK must match
|
||||
const auto &adj_ff_data = fast_cell_info.at(adj_ff->flat_index);
|
||||
if (adj_ff_data.ff_lsr != ff_data.ff_lsr) {
|
||||
return false;
|
||||
}
|
||||
if (adj_ff_data.ff_clk != ff_data.ff_clk) {
|
||||
return false;
|
||||
}
|
||||
if (adj_ff_data.ff_ce != ff_data.ff_ce) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// modify LUTs with constant inputs
|
||||
void GowinImpl::mod_lut_inputs(void)
|
||||
void GowinImpl::postPlace()
|
||||
{
|
||||
for (IdString netname : {ctx->id("$PACKER_GND"), ctx->id("$PACKER_VCC")}) {
|
||||
auto net = ctx->nets.find(netname);
|
||||
if (net == ctx->nets.end()) {
|
||||
continue;
|
||||
}
|
||||
NetInfo *constnet = net->second.get();
|
||||
for (auto user : constnet->users) {
|
||||
CellInfo *uc = user.cell;
|
||||
if (ctx->verbose)
|
||||
log_info("%s user %s\n", ctx->nameOf(constnet), ctx->nameOf(uc));
|
||||
|
||||
if (is_lut(uc) && (user.port.str(ctx).at(0) == 'I')) {
|
||||
auto it_param = uc->params.find(id_INIT);
|
||||
if (it_param == uc->params.end())
|
||||
log_error("No initialization for lut found.\n");
|
||||
|
||||
int64_t uc_init = it_param->second.intval;
|
||||
int64_t mask = 0;
|
||||
uint8_t amt = 0;
|
||||
|
||||
if (user.port == id_I0) {
|
||||
mask = 0x5555;
|
||||
amt = 1;
|
||||
} else if (user.port == id_I1) {
|
||||
mask = 0x3333;
|
||||
amt = 2;
|
||||
} else if (user.port == id_I2) {
|
||||
mask = 0x0F0F;
|
||||
amt = 4;
|
||||
} else if (user.port == id_I3) {
|
||||
mask = 0x00FF;
|
||||
amt = 8;
|
||||
} else {
|
||||
log_error("Port number invalid.\n");
|
||||
}
|
||||
|
||||
if ((constnet->name == ctx->id("$PACKER_GND"))) {
|
||||
uc_init = (uc_init & mask) | ((uc_init & mask) << amt);
|
||||
} else {
|
||||
uc_init = (uc_init & (mask << amt)) | ((uc_init & (mask << amt)) >> amt);
|
||||
}
|
||||
|
||||
size_t uc_init_len = it_param->second.to_string().length();
|
||||
uc_init &= (1LL << uc_init_len) - 1;
|
||||
|
||||
if (ctx->verbose)
|
||||
log_info("%s lut config modified from 0x%lX to 0x%lX\n", ctx->nameOf(uc), it_param->second.intval,
|
||||
uc_init);
|
||||
|
||||
it_param->second = Property(uc_init, uc_init_len);
|
||||
uc->disconnectPort(user.port);
|
||||
}
|
||||
if (ctx->debug) {
|
||||
log_info("================== Final Placement ===================\n");
|
||||
for (auto &cell : ctx->cells) {
|
||||
auto ci = cell.second.get();
|
||||
IdStringList bel = ctx->getBelName(ci->bel);
|
||||
log_info("%s -> %s\n", ctx->nameOf(ci), bel.str(ctx).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// make cluster from LUTs and MUXes
|
||||
void GowinImpl::pack_wideluts(void)
|
||||
{
|
||||
// children's offsets
|
||||
struct _children
|
||||
{
|
||||
IdString port;
|
||||
int dx, dz;
|
||||
} mux_inputs[4][2] = {{{id_I0, 1, -7}, {id_I1, 0, -7}},
|
||||
{{id_I0, 0, 4}, {id_I1, 0, -4}},
|
||||
{{id_I0, 0, 2}, {id_I1, 0, -2}},
|
||||
{{id_I0, 0, -BelZ::MUX20_Z}, {id_I1, 0, 2 - BelZ::MUX20_Z}}};
|
||||
typedef std::function<void(CellInfo &, CellInfo *, int, int)> recurse_func_t;
|
||||
recurse_func_t make_cluster = [&, this](CellInfo &ci_root, CellInfo *ci_cursor, int dx, int dz) {
|
||||
_children *inputs;
|
||||
if (is_lut(ci_cursor)) {
|
||||
return;
|
||||
}
|
||||
switch (ci_cursor->type.hash()) {
|
||||
case ID_MUX2_LUT8:
|
||||
inputs = mux_inputs[0];
|
||||
break;
|
||||
case ID_MUX2_LUT7:
|
||||
inputs = mux_inputs[1];
|
||||
break;
|
||||
case ID_MUX2_LUT6:
|
||||
inputs = mux_inputs[2];
|
||||
break;
|
||||
case ID_MUX2_LUT5:
|
||||
inputs = mux_inputs[3];
|
||||
break;
|
||||
default:
|
||||
log_error("Bad MUX2 node:%s\n", ctx->nameOf(ci_cursor));
|
||||
}
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
// input src
|
||||
NetInfo *in = ci_cursor->getPort(inputs[i].port);
|
||||
NPNR_ASSERT(in && in->driver.cell);
|
||||
int child_dx = dx + inputs[i].dx;
|
||||
int child_dz = dz + inputs[i].dz;
|
||||
ci_root.constr_children.push_back(in->driver.cell);
|
||||
in->driver.cell->cluster = ci_root.name;
|
||||
in->driver.cell->constr_abs_z = false;
|
||||
in->driver.cell->constr_x = child_dx;
|
||||
in->driver.cell->constr_y = 0;
|
||||
in->driver.cell->constr_z = child_dz;
|
||||
make_cluster(ci_root, in->driver.cell, child_dx, child_dz);
|
||||
}
|
||||
};
|
||||
|
||||
// look for MUX2
|
||||
// MUX2_LUT8 create right away, collect others
|
||||
std::vector<IdString> muxes[3];
|
||||
int packed[4] = {0, 0, 0, 0};
|
||||
for (auto &cell : ctx->cells) {
|
||||
auto &ci = *cell.second;
|
||||
if (ci.cluster != ClusterId()) {
|
||||
continue;
|
||||
}
|
||||
if (ci.type == id_MUX2_LUT8) {
|
||||
ci.cluster = ci.name;
|
||||
ci.constr_abs_z = 0;
|
||||
make_cluster(ci, &ci, 0, 0);
|
||||
++packed[0];
|
||||
continue;
|
||||
}
|
||||
if (ci.type.in(id_MUX2_LUT7, id_MUX2_LUT6, id_MUX2_LUT5)) {
|
||||
switch (ci.type.hash()) {
|
||||
case ID_MUX2_LUT7:
|
||||
muxes[0].push_back(cell.first);
|
||||
break;
|
||||
case ID_MUX2_LUT6:
|
||||
muxes[1].push_back(cell.first);
|
||||
break;
|
||||
default: // ID_MUX2_LUT5
|
||||
muxes[2].push_back(cell.first);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// create others
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
for (IdString cell_name : muxes[i]) {
|
||||
auto &ci = *ctx->cells.at(cell_name);
|
||||
if (ci.cluster != ClusterId()) {
|
||||
continue;
|
||||
}
|
||||
ci.cluster = ci.name;
|
||||
ci.constr_abs_z = 0;
|
||||
make_cluster(ci, &ci, 0, 0);
|
||||
++packed[i + 1];
|
||||
}
|
||||
}
|
||||
log_info("Packed MUX2_LUT8:%d, MUX2_LU7:%d, MUX2_LUT6:%d, MUX2_LUT5:%d\n", packed[0], packed[1], packed[2],
|
||||
packed[3]);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
||||
|
@ -1,73 +1,25 @@
|
||||
#ifndef GOWIN_H
|
||||
#define GOWIN_H
|
||||
|
||||
#include "himbaechel_api.h"
|
||||
#include "himbaechel_helpers.h"
|
||||
#include "nextpnr.h"
|
||||
|
||||
#define GEN_INIT_CONSTIDS
|
||||
#define HIMBAECHEL_CONSTIDS "uarch/gowin/constids.inc"
|
||||
#include "himbaechel_constids.h"
|
||||
|
||||
NEXTPNR_NAMESPACE_BEGIN
|
||||
|
||||
namespace {
|
||||
struct GowinImpl : HimbaechelAPI
|
||||
// Return true if a cell is a LUT
|
||||
inline bool type_is_lut(IdString cell_type) { return cell_type.in(id_LUT1, id_LUT2, id_LUT3, id_LUT4); }
|
||||
inline bool is_lut(const CellInfo *cell) { return type_is_lut(cell->type); }
|
||||
// Return true if a cell is a DFF
|
||||
inline bool type_is_dff(IdString cell_type)
|
||||
{
|
||||
|
||||
~GowinImpl(){};
|
||||
void init_constids(Arch *arch) override { init_uarch_constids(arch); }
|
||||
void init(Context *ctx) override;
|
||||
|
||||
void prePlace() override;
|
||||
void pack() override;
|
||||
|
||||
bool isBelLocationValid(BelId bel, bool explain_invalid) const override;
|
||||
|
||||
// Bel bucket functions
|
||||
IdString getBelBucketForCellType(IdString cell_type) const override;
|
||||
|
||||
bool isValidBelForCellType(IdString cell_type, BelId bel) const override;
|
||||
|
||||
private:
|
||||
HimbaechelHelpers h;
|
||||
|
||||
// Validity checking
|
||||
struct GowinCellInfo
|
||||
{
|
||||
const NetInfo *lut_f = nullptr;
|
||||
const NetInfo *ff_d = nullptr, *ff_ce = nullptr, *ff_clk = nullptr, *ff_lsr = nullptr;
|
||||
};
|
||||
std::vector<GowinCellInfo> fast_cell_info;
|
||||
void assign_cell_info();
|
||||
bool slice_valid(int x, int y, int z) const;
|
||||
|
||||
// modify LUTs with constant inputs
|
||||
void mod_lut_inputs(void);
|
||||
|
||||
void pack_wideluts(void);
|
||||
|
||||
// Return true if a cell is a LUT
|
||||
inline bool type_is_lut(IdString cell_type) const { return cell_type.in(id_LUT1, id_LUT2, id_LUT3, id_LUT4); }
|
||||
inline bool is_lut(const CellInfo *cell) const { return type_is_lut(cell->type); }
|
||||
// Return true if a cell is a DFF
|
||||
inline bool type_is_dff(IdString cell_type) const
|
||||
{
|
||||
return cell_type.in(id_DFF, id_DFFE, id_DFFN, id_DFFNE, id_DFFS, id_DFFSE, id_DFFNS, id_DFFNSE, id_DFFR,
|
||||
id_DFFRE, id_DFFNR, id_DFFNRE, id_DFFP, id_DFFPE, id_DFFNP, id_DFFNPE, id_DFFC, id_DFFCE,
|
||||
id_DFFNC, id_DFFNCE);
|
||||
}
|
||||
inline bool is_dff(const CellInfo *cell) const { return type_is_dff(cell->type); }
|
||||
};
|
||||
|
||||
struct GowinArch : HimbaechelArch
|
||||
{
|
||||
GowinArch() : HimbaechelArch("gowin"){};
|
||||
std::unique_ptr<HimbaechelAPI> create(const dict<std::string, std::string> &args)
|
||||
{
|
||||
return std::make_unique<GowinImpl>();
|
||||
}
|
||||
} exampleArch;
|
||||
return cell_type.in(id_DFF, id_DFFE, id_DFFN, id_DFFNE, id_DFFS, id_DFFSE, id_DFFNS, id_DFFNSE, id_DFFR, id_DFFRE,
|
||||
id_DFFNR, id_DFFNRE, id_DFFP, id_DFFPE, id_DFFNP, id_DFFNPE, id_DFFC, id_DFFCE, id_DFFNC,
|
||||
id_DFFNCE);
|
||||
}
|
||||
inline bool is_dff(const CellInfo *cell) { return type_is_dff(cell->type); }
|
||||
// Return true if a cell is a ALU
|
||||
inline bool type_is_alu(IdString cell_type) { return cell_type == id_ALU; }
|
||||
inline bool is_alu(const CellInfo *cell) { return type_is_alu(cell->type); }
|
||||
} // namespace
|
||||
|
||||
// Bels Z ranges. It is desirable that these numbers be synchronized with the chipdb generator
|
||||
@ -80,6 +32,7 @@ enum
|
||||
MUX21_Z = 18,
|
||||
MUX23_Z = 22,
|
||||
MUX27_Z = 29,
|
||||
ALU0_Z = 30, // :35, 6 ALU
|
||||
|
||||
VCC_Z = 277,
|
||||
VSS_Z = 278
|
||||
|
@ -19,6 +19,7 @@ MUX20_Z = 16
|
||||
MUX21_Z = 18
|
||||
MUX23_Z = 22
|
||||
MUX27_Z = 29
|
||||
ALU0_Z = 30 # : 35, 6 ALUs
|
||||
|
||||
VCC_Z = 277
|
||||
GND_Z = 278
|
||||
@ -68,6 +69,7 @@ def create_nodes(chip: Chip, db: chipdb):
|
||||
for y in range(Y):
|
||||
for x in range(X):
|
||||
nodes = []
|
||||
extra_tile_data = chip.tile_type_at(x, y).extra_data
|
||||
# SN and EW
|
||||
for i in [1, 2]:
|
||||
nodes.append([NodeWire(x, y, f'SN{i}0'),
|
||||
@ -92,11 +94,25 @@ def create_nodes(chip: Chip, db: chipdb):
|
||||
NodeWire(*uturn(db, x + offs[0] * 4, y + offs[1] * 4, f'{d}8{i}4')),
|
||||
NodeWire(*uturn(db, x + offs[0] * 8, y + offs[1] * 8, f'{d}8{i}8'))])
|
||||
# I0 for MUX2_LUT8
|
||||
if x < X - 1 and chip.tile_type_at(x, y).extra_data.tile_class == chip.strs.id('LOGIC') and chip.tile_type_at(x, y).extra_data.tile_class == chip.strs.id('LOGIC'):
|
||||
if (x < X - 1 and extra_tile_data.tile_class == chip.strs.id('LOGIC')
|
||||
and chip.tile_type_at(x + 1, y).extra_data.tile_class == chip.strs.id('LOGIC')):
|
||||
nodes.append([NodeWire(x, y, 'OF30'),
|
||||
NodeWire(x + 1, y, 'OF3')])
|
||||
NodeWire(x + 1, y, 'OF3')])
|
||||
|
||||
# ALU
|
||||
if extra_tile_data.tile_class == chip.strs.id('LOGIC'):
|
||||
# local carry chain
|
||||
for i in range(5):
|
||||
nodes.append([NodeWire(x, y, f'COUT{i}'),
|
||||
NodeWire(x, y, f'CIN{i + 1}')]);
|
||||
# gobal carry chain
|
||||
if x > 1 and chip.tile_type_at(x - 1, y).extra_data.tile_class == chip.strs.id('LOGIC'):
|
||||
nodes.append([NodeWire(x, y, f'CIN0'),
|
||||
NodeWire(x - 1, y, f'COUT5')])
|
||||
|
||||
for node in nodes:
|
||||
chip.add_node(node)
|
||||
|
||||
# VCC and VSS sources in the all tiles
|
||||
global_nodes.setdefault('GND', []).append(NodeWire(x, y, 'VSS'))
|
||||
global_nodes.setdefault('VCC', []).append(NodeWire(x, y, 'VCC'))
|
||||
@ -170,16 +186,16 @@ def create_io_tiletype(chip: Chip, db: chipdb, x: int, y: int, ttyp: int):
|
||||
create_switch_matrix(tt, db, x, y)
|
||||
return ttyp
|
||||
|
||||
# XXX lut+dff only for now
|
||||
# logic: luts, dffs, alu etc
|
||||
def create_logic_tiletype(chip: Chip, db: chipdb, x: int, y: int, ttyp: int):
|
||||
lut_inputs = ['A', 'B', 'C', 'D']
|
||||
if ttyp in created_tiletypes:
|
||||
return ttyp
|
||||
typename = "LOGIC"
|
||||
tt = chip.create_tile_type(f"{typename}_{ttyp}")
|
||||
tt.extra_data = TileExtraData(chip.strs.id(typename))
|
||||
|
||||
# setup wires
|
||||
lut_inputs = ['A', 'B', 'C', 'D']
|
||||
# setup LUT wires
|
||||
for i in range(8):
|
||||
for inp_name in lut_inputs:
|
||||
tt.create_wire(f"{inp_name}{i}", "LUT_INPUT")
|
||||
@ -190,14 +206,20 @@ def create_logic_tiletype(chip: Chip, db: chipdb, x: int, y: int, ttyp: int):
|
||||
# just out of curiosity
|
||||
tt.create_wire(f"XD{i}", "FF_INPUT")
|
||||
tt.create_wire(f"Q{i}", "FF_OUT")
|
||||
# setup DFF wires
|
||||
for j in range(3):
|
||||
tt.create_wire(f"CLK{j}", "TILE_CLK")
|
||||
tt.create_wire(f"LSR{j}", "TILE_LSR")
|
||||
tt.create_wire(f"CE{j}", "TILE_CE")
|
||||
# setup MUX2 wires
|
||||
for j in range(8):
|
||||
tt.create_wire(f"OF{j}", "MUX_OUT")
|
||||
tt.create_wire(f"SEL{j}", "MUX_SEL")
|
||||
tt.create_wire("OF30", "MUX_OUT")
|
||||
# setup ALU wires
|
||||
for j in range(6):
|
||||
tt.create_wire(f"CIN{j}", "ALU_CIN")
|
||||
tt.create_wire(f"COUT{j}", "ALU_COUT")
|
||||
|
||||
# create logic cells
|
||||
for i in range(8):
|
||||
@ -222,6 +244,18 @@ def create_logic_tiletype(chip: Chip, db: chipdb, x: int, y: int, ttyp: int):
|
||||
tt.add_bel_pin(ff, "PRESET", f"LSR{i // 2}", PinType.INPUT)
|
||||
tt.add_bel_pin(ff, "CLEAR", f"LSR{i // 2}", PinType.INPUT)
|
||||
tt.add_bel_pin(ff, "CE", f"CE{i // 2}", PinType.INPUT)
|
||||
|
||||
# ALU
|
||||
ff = tt.create_bel(f"ALU{i}", "ALU", z = i + ALU0_Z)
|
||||
tt.add_bel_pin(ff, "SUM", f"F{i}", PinType.OUTPUT)
|
||||
tt.add_bel_pin(ff, "COUT", f"COUT{i}", PinType.OUTPUT)
|
||||
tt.add_bel_pin(ff, "CIN", f"CIN{i}", PinType.INPUT)
|
||||
# pinout for the ADDSUB ALU mode
|
||||
tt.add_bel_pin(ff, "I0", f"A{i}", PinType.INPUT)
|
||||
tt.add_bel_pin(ff, "I1", f"B{i}", PinType.INPUT)
|
||||
tt.add_bel_pin(ff, "I2", f"C{i}", PinType.INPUT)
|
||||
tt.add_bel_pin(ff, "I3", f"D{i}", PinType.INPUT)
|
||||
|
||||
# wide luts
|
||||
for i in range(4):
|
||||
ff = tt.create_bel(f"MUX{i * 2}", "MUX2_LUT5", z = MUX20_Z + i * 4)
|
||||
|
409
himbaechel/uarch/gowin/pack.cc
Normal file
409
himbaechel/uarch/gowin/pack.cc
Normal file
@ -0,0 +1,409 @@
|
||||
#include "log.h"
|
||||
#include "nextpnr.h"
|
||||
|
||||
#define HIMBAECHEL_CONSTIDS "uarch/gowin/constids.inc"
|
||||
#include "himbaechel_constids.h"
|
||||
#include "himbaechel_helpers.h"
|
||||
|
||||
#include "gowin.h"
|
||||
#include "pack.h"
|
||||
|
||||
NEXTPNR_NAMESPACE_BEGIN
|
||||
|
||||
namespace {
|
||||
struct GowinPacker
|
||||
{
|
||||
Context *ctx;
|
||||
HimbaechelHelpers h;
|
||||
|
||||
GowinPacker(Context *ctx) : ctx(ctx) { h.init(ctx); }
|
||||
|
||||
// ===================================
|
||||
// IO
|
||||
// ===================================
|
||||
void pack_iobs(void)
|
||||
{
|
||||
log_info("Pack IOBs...\n");
|
||||
// Trim nextpnr IOBs - assume IO buffer insertion has been done in synthesis
|
||||
const pool<CellTypePort> top_ports{
|
||||
CellTypePort(id_IBUF, id_I),
|
||||
CellTypePort(id_OBUF, id_O),
|
||||
};
|
||||
h.remove_nextpnr_iobs(top_ports);
|
||||
}
|
||||
|
||||
// ===================================
|
||||
// Constant nets
|
||||
// ===================================
|
||||
void handle_constants(void)
|
||||
{
|
||||
log_info("Create constant nets...\n");
|
||||
const dict<IdString, Property> vcc_params;
|
||||
const dict<IdString, Property> gnd_params;
|
||||
h.replace_constants(CellTypePort(id_GOWIN_VCC, id_V), CellTypePort(id_GOWIN_GND, id_G), vcc_params, gnd_params);
|
||||
|
||||
// disconnect the constant LUT inputs
|
||||
log_info("Modify LUTs...\n");
|
||||
for (IdString netname : {ctx->id("$PACKER_GND"), ctx->id("$PACKER_VCC")}) {
|
||||
auto net = ctx->nets.find(netname);
|
||||
if (net == ctx->nets.end()) {
|
||||
continue;
|
||||
}
|
||||
NetInfo *constnet = net->second.get();
|
||||
for (auto user : constnet->users) {
|
||||
CellInfo *uc = user.cell;
|
||||
if (ctx->debug)
|
||||
log_info("%s user %s/%s\n", ctx->nameOf(constnet), ctx->nameOf(uc), user.port.c_str(ctx));
|
||||
|
||||
if (is_lut(uc) && (user.port.str(ctx).at(0) == 'I')) {
|
||||
auto it_param = uc->params.find(id_INIT);
|
||||
if (it_param == uc->params.end())
|
||||
log_error("No initialization for lut found.\n");
|
||||
|
||||
int64_t uc_init = it_param->second.intval;
|
||||
int64_t mask = 0;
|
||||
uint8_t amt = 0;
|
||||
|
||||
if (user.port == id_I0) {
|
||||
mask = 0x5555;
|
||||
amt = 1;
|
||||
} else if (user.port == id_I1) {
|
||||
mask = 0x3333;
|
||||
amt = 2;
|
||||
} else if (user.port == id_I2) {
|
||||
mask = 0x0F0F;
|
||||
amt = 4;
|
||||
} else if (user.port == id_I3) {
|
||||
mask = 0x00FF;
|
||||
amt = 8;
|
||||
} else {
|
||||
log_error("Port number invalid.\n");
|
||||
}
|
||||
|
||||
if ((constnet->name == ctx->id("$PACKER_GND"))) {
|
||||
uc_init = (uc_init & mask) | ((uc_init & mask) << amt);
|
||||
} else {
|
||||
uc_init = (uc_init & (mask << amt)) | ((uc_init & (mask << amt)) >> amt);
|
||||
}
|
||||
|
||||
size_t uc_init_len = it_param->second.to_string().length();
|
||||
uc_init &= (1LL << uc_init_len) - 1;
|
||||
|
||||
if (ctx->verbose && it_param->second.intval != uc_init)
|
||||
log_info("%s lut config modified from 0x%lX to 0x%lX\n", ctx->nameOf(uc),
|
||||
it_param->second.intval, uc_init);
|
||||
|
||||
it_param->second = Property(uc_init, uc_init_len);
|
||||
uc->disconnectPort(user.port);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================
|
||||
// Wideluts
|
||||
// ===================================
|
||||
void pack_wideluts(void)
|
||||
{
|
||||
log_info("Pack wide LUTs...\n");
|
||||
// children's offsets
|
||||
struct _children
|
||||
{
|
||||
IdString port;
|
||||
int dx, dz;
|
||||
} mux_inputs[4][2] = {{{id_I0, 1, -7}, {id_I1, 0, -7}},
|
||||
{{id_I0, 0, 4}, {id_I1, 0, -4}},
|
||||
{{id_I0, 0, 2}, {id_I1, 0, -2}},
|
||||
{{id_I0, 0, -BelZ::MUX20_Z}, {id_I1, 0, 2 - BelZ::MUX20_Z}}};
|
||||
typedef std::function<void(CellInfo &, CellInfo *, int, int)> recurse_func_t;
|
||||
recurse_func_t make_cluster = [&, this](CellInfo &ci_root, CellInfo *ci_cursor, int dx, int dz) {
|
||||
_children *inputs;
|
||||
if (is_lut(ci_cursor)) {
|
||||
return;
|
||||
}
|
||||
switch (ci_cursor->type.hash()) {
|
||||
case ID_MUX2_LUT8:
|
||||
inputs = mux_inputs[0];
|
||||
break;
|
||||
case ID_MUX2_LUT7:
|
||||
inputs = mux_inputs[1];
|
||||
break;
|
||||
case ID_MUX2_LUT6:
|
||||
inputs = mux_inputs[2];
|
||||
break;
|
||||
case ID_MUX2_LUT5:
|
||||
inputs = mux_inputs[3];
|
||||
break;
|
||||
default:
|
||||
log_error("Bad MUX2 node:%s\n", ctx->nameOf(ci_cursor));
|
||||
}
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
// input src
|
||||
NetInfo *in = ci_cursor->getPort(inputs[i].port);
|
||||
NPNR_ASSERT(in && in->driver.cell);
|
||||
int child_dx = dx + inputs[i].dx;
|
||||
int child_dz = dz + inputs[i].dz;
|
||||
ci_root.constr_children.push_back(in->driver.cell);
|
||||
in->driver.cell->cluster = ci_root.name;
|
||||
in->driver.cell->constr_abs_z = false;
|
||||
in->driver.cell->constr_x = child_dx;
|
||||
in->driver.cell->constr_y = 0;
|
||||
in->driver.cell->constr_z = child_dz;
|
||||
make_cluster(ci_root, in->driver.cell, child_dx, child_dz);
|
||||
}
|
||||
};
|
||||
|
||||
// look for MUX2
|
||||
// MUX2_LUT8 create right away, collect others
|
||||
std::vector<IdString> muxes[3];
|
||||
int packed[4] = {0, 0, 0, 0};
|
||||
for (auto &cell : ctx->cells) {
|
||||
auto &ci = *cell.second;
|
||||
if (ci.cluster != ClusterId()) {
|
||||
continue;
|
||||
}
|
||||
if (ci.type == id_MUX2_LUT8) {
|
||||
ci.cluster = ci.name;
|
||||
ci.constr_abs_z = false;
|
||||
make_cluster(ci, &ci, 0, 0);
|
||||
++packed[0];
|
||||
continue;
|
||||
}
|
||||
if (ci.type.in(id_MUX2_LUT7, id_MUX2_LUT6, id_MUX2_LUT5)) {
|
||||
switch (ci.type.hash()) {
|
||||
case ID_MUX2_LUT7:
|
||||
muxes[0].push_back(cell.first);
|
||||
break;
|
||||
case ID_MUX2_LUT6:
|
||||
muxes[1].push_back(cell.first);
|
||||
break;
|
||||
default: // ID_MUX2_LUT5
|
||||
muxes[2].push_back(cell.first);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// create others
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
for (IdString cell_name : muxes[i]) {
|
||||
auto &ci = *ctx->cells.at(cell_name);
|
||||
if (ci.cluster != ClusterId()) {
|
||||
continue;
|
||||
}
|
||||
ci.cluster = ci.name;
|
||||
ci.constr_abs_z = false;
|
||||
make_cluster(ci, &ci, 0, 0);
|
||||
++packed[i + 1];
|
||||
}
|
||||
}
|
||||
log_info("Packed MUX2_LUT8:%d, MUX2_LU7:%d, MUX2_LUT6:%d, MUX2_LUT5:%d\n", packed[0], packed[1], packed[2],
|
||||
packed[3]);
|
||||
}
|
||||
|
||||
// ===================================
|
||||
// ALU
|
||||
// ===================================
|
||||
// create ALU CIN block
|
||||
std::unique_ptr<CellInfo> alu_add_cin_block(Context *ctx, CellInfo *head, NetInfo *cin_net)
|
||||
{
|
||||
std::string name = head->name.str(ctx) + "_HEAD_ALULC";
|
||||
IdString name_id = ctx->id(name);
|
||||
|
||||
NetInfo *cout_net = ctx->createNet(name_id);
|
||||
head->disconnectPort(id_CIN);
|
||||
head->connectPort(id_CIN, cout_net);
|
||||
|
||||
auto cin_ci = std::make_unique<CellInfo>(ctx, name_id, id_ALU);
|
||||
cin_ci->addOutput(id_COUT);
|
||||
cin_ci->connectPort(id_COUT, cout_net);
|
||||
|
||||
if (cin_net->name == ctx->id("$PACKER_GND")) {
|
||||
cin_ci->params[id_ALU_MODE] = std::string("C2L");
|
||||
return cin_ci;
|
||||
}
|
||||
if (cin_net->name == ctx->id("$PACKER_VCC")) {
|
||||
cin_ci->params[id_ALU_MODE] = std::string("ONE2C");
|
||||
return cin_ci;
|
||||
}
|
||||
// CIN from logic
|
||||
cin_ci->addInput(id_I1);
|
||||
cin_ci->addInput(id_I3);
|
||||
cin_ci->addInput(id_I2);
|
||||
cin_ci->connectPort(id_I1, cin_net);
|
||||
cin_ci->connectPort(id_I3, cin_net);
|
||||
cin_ci->connectPort(id_I2, ctx->nets[ctx->id("$PACKER_VCC")].get());
|
||||
cin_ci->params[id_ALU_MODE] = std::string("0"); // ADD
|
||||
return cin_ci;
|
||||
}
|
||||
|
||||
// create ALU COUT block
|
||||
std::unique_ptr<CellInfo> alu_add_cout_block(Context *ctx, CellInfo *tail, NetInfo *cout_net)
|
||||
{
|
||||
std::string name = tail->name.str(ctx) + "_TAIL_ALULC";
|
||||
IdString name_id = ctx->id(name);
|
||||
|
||||
NetInfo *cin_net = ctx->createNet(name_id);
|
||||
tail->disconnectPort(id_COUT);
|
||||
tail->connectPort(id_COUT, cin_net);
|
||||
|
||||
auto cout_ci = std::make_unique<CellInfo>(ctx, name_id, id_ALU);
|
||||
cout_ci->addOutput(id_COUT); // may be needed for the ALU filler
|
||||
cout_ci->addInput(id_CIN);
|
||||
cout_ci->connectPort(id_CIN, cin_net);
|
||||
cout_ci->addOutput(id_SUM);
|
||||
cout_ci->connectPort(id_SUM, cout_net);
|
||||
|
||||
cout_ci->params[id_ALU_MODE] = std::string("C2L");
|
||||
return cout_ci;
|
||||
}
|
||||
|
||||
// create ALU filler block
|
||||
std::unique_ptr<CellInfo> alu_add_dummy_block(Context *ctx, CellInfo *tail)
|
||||
{
|
||||
std::string name = tail->name.str(ctx) + "_DUMMY_ALULC";
|
||||
IdString name_id = ctx->id(name);
|
||||
|
||||
auto dummy_ci = std::make_unique<CellInfo>(ctx, name_id, id_ALU);
|
||||
dummy_ci->params[id_ALU_MODE] = std::string("C2L");
|
||||
return dummy_ci;
|
||||
}
|
||||
|
||||
// create ALU chain
|
||||
void pack_alus(void)
|
||||
{
|
||||
static CellTypePort cell_alu_cout = CellTypePort(id_ALU, id_COUT);
|
||||
static CellTypePort cell_alu_cin = CellTypePort(id_ALU, id_CIN);
|
||||
std::vector<std::unique_ptr<CellInfo>> new_cells;
|
||||
|
||||
log_info("Pack ALUs...\n");
|
||||
for (auto &cell : ctx->cells) {
|
||||
auto ci = cell.second.get();
|
||||
if (ci->cluster != ClusterId()) {
|
||||
continue;
|
||||
}
|
||||
if (is_alu(ci)) {
|
||||
// The ALU head is when the input carry is not a dedicated wire from the previous ALU
|
||||
NetInfo *cin_net = ci->getPort(id_CIN);
|
||||
if (!cin_net || !cin_net->driver.cell) {
|
||||
log_error("CIN disconnected at ALU:%s\n", ctx->nameOf(ci));
|
||||
}
|
||||
if (CellTypePort(cin_net->driver) != cell_alu_cout || cin_net->users.entries() > 1) {
|
||||
if (ctx->debug) {
|
||||
log_info("ALU head found %s. CIN net is %s\n", ctx->nameOf(ci), ctx->nameOf(cin_net));
|
||||
}
|
||||
// always prepend first ALU with carry generator block
|
||||
// three cases: CIN == 0, CIN == 1 and CIN == ?
|
||||
new_cells.push_back(std::move(alu_add_cin_block(ctx, ci, cin_net)));
|
||||
CellInfo *cin_block_ci = new_cells.back().get();
|
||||
// CIN block is the cluster root and is always placed in ALU0
|
||||
// This is a possible place for further optimization
|
||||
cin_block_ci->cluster = cin_block_ci->name;
|
||||
cin_block_ci->constr_z = BelZ::ALU0_Z;
|
||||
cin_block_ci->constr_abs_z = true;
|
||||
|
||||
int alu_chain_len = 1;
|
||||
while (true) {
|
||||
// add to cluster
|
||||
if (ctx->debug) {
|
||||
log_info("Add ALU to the chain (len:%d): %s\n", alu_chain_len, ctx->nameOf(ci));
|
||||
}
|
||||
cin_block_ci->constr_children.push_back(ci);
|
||||
ci->cluster = cin_block_ci->name;
|
||||
ci->constr_abs_z = false;
|
||||
ci->constr_x = alu_chain_len / 6;
|
||||
ci->constr_y = 0;
|
||||
ci->constr_z = alu_chain_len % 6;
|
||||
// XXX I2 is pin C which must be set to 1 for all ALU modes except MUL
|
||||
// we use only mode 2 ADDSUB so create and connect this pin
|
||||
ci->addInput(id_I2);
|
||||
ci->connectPort(id_I2, ctx->nets[ctx->id("$PACKER_VCC")].get());
|
||||
|
||||
++alu_chain_len;
|
||||
|
||||
// check for the chain end
|
||||
NetInfo *cout_net = ci->getPort(id_COUT);
|
||||
if (!cout_net || cout_net->users.empty()) {
|
||||
break;
|
||||
}
|
||||
if (CellTypePort(*cout_net->users.begin()) != cell_alu_cin || cout_net->users.entries() > 1) {
|
||||
new_cells.push_back(std::move(alu_add_cout_block(ctx, ci, cout_net)));
|
||||
CellInfo *cout_block_ci = new_cells.back().get();
|
||||
cin_block_ci->constr_children.push_back(cout_block_ci);
|
||||
cout_block_ci->cluster = cin_block_ci->name;
|
||||
cout_block_ci->constr_abs_z = false;
|
||||
cout_block_ci->constr_x = alu_chain_len / 6;
|
||||
cout_block_ci->constr_y = 0;
|
||||
cout_block_ci->constr_z = alu_chain_len % 6;
|
||||
if (ctx->debug) {
|
||||
log_info("Add ALU carry out to the chain (len:%d): %s\n", alu_chain_len,
|
||||
ctx->nameOf(cout_block_ci));
|
||||
}
|
||||
|
||||
++alu_chain_len;
|
||||
|
||||
break;
|
||||
}
|
||||
ci = (*cout_net->users.begin()).cell;
|
||||
}
|
||||
// ALUs are always paired
|
||||
if (alu_chain_len & 1) {
|
||||
// create dummy cell
|
||||
new_cells.push_back(std::move(alu_add_dummy_block(ctx, ci)));
|
||||
CellInfo *dummy_block_ci = new_cells.back().get();
|
||||
cin_block_ci->constr_children.push_back(dummy_block_ci);
|
||||
dummy_block_ci->cluster = cin_block_ci->name;
|
||||
dummy_block_ci->constr_abs_z = false;
|
||||
dummy_block_ci->constr_x = alu_chain_len / 6;
|
||||
dummy_block_ci->constr_y = 0;
|
||||
dummy_block_ci->constr_z = alu_chain_len % 6;
|
||||
if (ctx->debug) {
|
||||
log_info("Add ALU dummy cell to the chain (len:%d): %s\n", alu_chain_len,
|
||||
ctx->nameOf(dummy_block_ci));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto &ncell : new_cells) {
|
||||
ctx->cells[ncell->name] = std::move(ncell);
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================
|
||||
// glue LUT and FF
|
||||
// ===================================
|
||||
void constrain_lutffs(void)
|
||||
{
|
||||
// Constrain directly connected LUTs and FFs together to use dedicated resources
|
||||
auto lut_outs = pool<CellTypePort>{{id_LUT1, id_F}, {id_LUT2, id_F}, {id_LUT3, id_F}, {id_LUT4, id_F}};
|
||||
auto dff_ins = pool<CellTypePort>{{id_DFF, id_D}, {id_DFFE, id_D}, {id_DFFN, id_D}, {id_DFFNE, id_D},
|
||||
{id_DFFS, id_D}, {id_DFFSE, id_D}, {id_DFFNS, id_D}, {id_DFFNSE, id_D},
|
||||
{id_DFFR, id_D}, {id_DFFRE, id_D}, {id_DFFNR, id_D}, {id_DFFNRE, id_D},
|
||||
{id_DFFP, id_D}, {id_DFFPE, id_D}, {id_DFFNP, id_D}, {id_DFFNPE, id_D},
|
||||
{id_DFFC, id_D}, {id_DFFCE, id_D}, {id_DFFNC, id_D}, {id_DFFNCE, id_D}};
|
||||
|
||||
int lutffs = h.constrain_cell_pairs(lut_outs, dff_ins, 1);
|
||||
log_info("Constrained %d LUTFF pairs.\n", lutffs);
|
||||
|
||||
log_info("Pack ALUs...\n");
|
||||
pack_alus();
|
||||
}
|
||||
|
||||
void run(void)
|
||||
{
|
||||
pack_iobs();
|
||||
handle_constants();
|
||||
pack_wideluts();
|
||||
pack_alus();
|
||||
constrain_lutffs();
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
void gowin_pack(Context *ctx)
|
||||
{
|
||||
GowinPacker packer(ctx);
|
||||
packer.run();
|
||||
}
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
12
himbaechel/uarch/gowin/pack.h
Normal file
12
himbaechel/uarch/gowin/pack.h
Normal file
@ -0,0 +1,12 @@
|
||||
#ifndef GOWIN_PACK_H
|
||||
#define GOWIN_PACK_H
|
||||
|
||||
#include "nextpnr.h"
|
||||
|
||||
NEXTPNR_NAMESPACE_BEGIN
|
||||
|
||||
void gowin_pack(Context *ctx);
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user