From c9b23a01dbde9b287c73e1969e4d2b0643d2a4ce Mon Sep 17 00:00:00 2001 From: YRabbit Date: Wed, 5 Jul 2023 12:49:25 +1000 Subject: [PATCH] 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 --- himbaechel/uarch/gowin/gowin.cc | 362 ++++++++------------ himbaechel/uarch/gowin/gowin.h | 75 +---- himbaechel/uarch/gowin/gowin_arch_gen.py | 44 ++- himbaechel/uarch/gowin/pack.cc | 409 +++++++++++++++++++++++ himbaechel/uarch/gowin/pack.h | 12 + 5 files changed, 616 insertions(+), 286 deletions(-) create mode 100644 himbaechel/uarch/gowin/pack.cc create mode 100644 himbaechel/uarch/gowin/pack.h diff --git a/himbaechel/uarch/gowin/gowin.cc b/himbaechel/uarch/gowin/gowin.cc index 0489e0c4..9c691a8e 100644 --- a/himbaechel/uarch/gowin/gowin.cc +++ b/himbaechel/uarch/gowin/gowin.cc @@ -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 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 create(const dict &args) + { + return std::make_unique(); + } +} 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 top_ports{ - CellTypePort(id_IBUF, id_I), - CellTypePort(id_OBUF, id_O), - }; - h.remove_nextpnr_iobs(top_ports); - // Replace constants with LUTs - const dict vcc_params; - const dict 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{{id_LUT1, id_F}, {id_LUT2, id_F}, {id_LUT3, id_F}, {id_LUT4, id_F}}; - auto dff_ins = pool{{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 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 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 diff --git a/himbaechel/uarch/gowin/gowin.h b/himbaechel/uarch/gowin/gowin.h index ed18cdab..9d2f736d 100644 --- a/himbaechel/uarch/gowin/gowin.h +++ b/himbaechel/uarch/gowin/gowin.h @@ -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 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 create(const dict &args) - { - return std::make_unique(); - } -} 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 diff --git a/himbaechel/uarch/gowin/gowin_arch_gen.py b/himbaechel/uarch/gowin/gowin_arch_gen.py index 08a1e5d8..c3ec84aa 100644 --- a/himbaechel/uarch/gowin/gowin_arch_gen.py +++ b/himbaechel/uarch/gowin/gowin_arch_gen.py @@ -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) diff --git a/himbaechel/uarch/gowin/pack.cc b/himbaechel/uarch/gowin/pack.cc new file mode 100644 index 00000000..9989a430 --- /dev/null +++ b/himbaechel/uarch/gowin/pack.cc @@ -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 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 vcc_params; + const dict 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 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 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 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(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 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(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 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(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> 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{{id_LUT1, id_F}, {id_LUT2, id_F}, {id_LUT3, id_F}, {id_LUT4, id_F}}; + auto dff_ins = pool{{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 diff --git a/himbaechel/uarch/gowin/pack.h b/himbaechel/uarch/gowin/pack.h new file mode 100644 index 00000000..7d86c024 --- /dev/null +++ b/himbaechel/uarch/gowin/pack.h @@ -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