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:
YRabbit 2023-07-05 12:49:25 +10:00 committed by myrtle
parent c82654d003
commit c9b23a01db
5 changed files with 616 additions and 286 deletions

View File

@ -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

View File

@ -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

View File

@ -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)

View 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

View 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