diff --git a/machxo2/arch.cc b/machxo2/arch.cc index 022fb8ff..32b188e6 100644 --- a/machxo2/arch.cc +++ b/machxo2/arch.cc @@ -95,6 +95,20 @@ Arch::Arch(ArchArgs args) : args(args) if (!package_info) log_error("Unsupported package '%s' for '%s'.\n", package_name, getChipName().c_str()); + tile_status.resize(chip_info->num_tiles); + for (int i = 0; i < chip_info->num_tiles; i++) { + auto &ts = tile_status.at(i); + auto &tile_data = chip_info->tile_info[i]; + ts.boundcells.resize(chip_info->tiles[i].bel_data.size(), nullptr); + for (auto &name : tile_data.tile_names) { + if (strcmp(chip_info->tiletype_names[name.type_idx].get(), "PLC2") == 0) { + // Is a logic tile + ts.lts = new LogicTileStatus(); + break; + } + } + } + BaseArch::init_cell_types(); BaseArch::init_bel_buckets(); @@ -470,12 +484,6 @@ DecalXY Arch::getPipDecal(PipId pip) const // --------------------------------------------------------------- -bool Arch::isBelLocationValid(BelId bel, bool explain_invalid) const -{ - // FIXME: Same deal as isValidBelForCell. - return true; -} - const std::string Arch::defaultPlacer = "heap"; const std::vector Arch::availablePlacers = {"sa", "heap"}; diff --git a/machxo2/arch.h b/machxo2/arch.h index 8cb8accc..1eab9d1f 100644 --- a/machxo2/arch.h +++ b/machxo2/arch.h @@ -381,6 +381,38 @@ struct Arch : BaseArch // inverse of the above for name->object mapping dict id_to_x, id_to_y; + enum LogicBELType + { + BEL_COMB = 0, + BEL_FF = 1, + BEL_RAMW = 2 + }; + static const int lc_idx_shift = 2; + + struct LogicTileStatus + { + // Per-SLICE valid and dirty bits + struct SliceStatus + { + bool valid = true, dirty = true; + } slices[4]; + // Per-tile legality check for control set legality + bool tile_valid = true; + bool tile_dirty = true; + // Fast index from z-pos to cell + std::array cells; + }; + + struct TileStatus + { + std::vector boundcells; + LogicTileStatus *lts = nullptr; + // TODO: use similar mechanism for DSP legality checking + ~TileStatus() { delete lts; } + }; + + mutable std::vector tile_status; + // Helpers template const TileTypePOD *tile_info(Id &id) const { @@ -392,6 +424,11 @@ struct Arch : BaseArch return (bel.location.y * chip_info->width + bel.location.x) * max_loc_bels + bel.index; } + template inline int tile_index(Id id) const + { + return id.location.y * chip_info->width + id.location.x; + } + // --------------------------------------------------------------- // Common Arch API. Every arch must provide the following methods. @@ -624,6 +661,12 @@ struct Arch : BaseArch // Placer bool isBelLocationValid(BelId bel, bool explain_invalid = false) const override; + // Helper function for above + bool slices_compatible(LogicTileStatus *lts) const; + + void assign_arch_info_for_cell(CellInfo *ci); + void assignArchInfo() override; + static const std::string defaultPlacer; static const std::vector availablePlacers; static const std::string defaultRouter; diff --git a/machxo2/archdefs.h b/machxo2/archdefs.h index 1520f0c3..4809324a 100644 --- a/machxo2/archdefs.h +++ b/machxo2/archdefs.h @@ -143,6 +143,57 @@ struct NetInfo; struct ArchCellInfo : BaseClusterInfo { + enum CombFlags : uint8_t + { + COMB_NONE = 0x00, + COMB_CARRY = 0x01, + COMB_LUTRAM = 0x02, + COMB_MUX5 = 0x04, + COMB_MUX6 = 0x08, + COMB_RAM_WCKINV = 0x10, + COMB_RAM_WREINV = 0x20, + COMB_RAMW_BLOCK = 0x40, + }; + + enum FFFlags : uint8_t + { + FF_NONE = 0x00, + FF_CLKINV = 0x01, + FF_CEINV = 0x02, + FF_CECONST = 0x04, + FF_LSRINV = 0x08, + FF_GSREN = 0x10, + FF_ASYNC = 0x20, + FF_M_USED = 0x40, + }; + + struct + { + uint8_t flags; + IdString ram_wck, ram_wre; + CellInfo *mux_fxad; + } combInfo; + struct + { + uint8_t flags; + IdString clk_sig, lsr_sig, ce_sig, di_sig; + } ffInfo; + struct + { + bool is_pdp; + // Are the outputs from a DP16KD registered (OUTREG) + // or non-registered (NOREG) + bool is_output_a_registered; + bool is_output_b_registered; + // Which timing information to use for a DP16KD. Depends on registering + // configuration. + IdString regmode_timing_id; + } ramInfo; + struct + { + bool is_clocked; + IdString timing_id; + } multInfo; }; NEXTPNR_NAMESPACE_END diff --git a/machxo2/cells.cc b/machxo2/cells.cc index 5f277d17..edd9f816 100644 --- a/machxo2/cells.cc +++ b/machxo2/cells.cc @@ -32,24 +32,73 @@ std::unique_ptr create_machxo2_cell(Context *ctx, IdString type, std:: name.empty() ? ctx->id("$nextpnr_" + type.str(ctx) + "_" + std::to_string(auto_idx++)) : ctx->id(name); auto new_cell = std::make_unique(ctx, name_id, type); - if (type == id_TRELLIS_SLICE) { + if (type == id_TRELLIS_COMB) { new_cell->params[id_MODE] = std::string("LOGIC"); - new_cell->params[id_GSR] = std::string("ENABLED"); - new_cell->params[id_SRMODE] = std::string("LSR_OVER_CE"); - new_cell->params[id_CEMUX] = std::string("1"); - new_cell->params[id_CLKMUX] = std::string("0"); - new_cell->params[id_LSRMUX] = std::string("LSR"); - new_cell->params[id_LSRONMUX] = std::string("LSRMUX"); - new_cell->params[id_LUT0_INITVAL] = Property(0xFFFF, 16); - new_cell->params[id_LUT1_INITVAL] = Property(0xFFFF, 16); - new_cell->params[id_REGMODE] = std::string("FF"); - new_cell->params[id_REG0_SD] = std::string("1"); - new_cell->params[id_REG1_SD] = std::string("1"); - new_cell->params[id_REG0_REGSET] = std::string("SET"); - new_cell->params[id_REG1_REGSET] = std::string("SET"); - new_cell->params[id_CCU2_INJECT1_0] = std::string("YES"); - new_cell->params[id_CCU2_INJECT1_1] = std::string("YES"); - new_cell->params[id_WREMUX] = std::string("INV"); + new_cell->params[id_INITVAL] = Property(0, 16); + new_cell->params[id_CCU2_INJECT1] = std::string("NO"); + new_cell->params[id_WREMUX] = std::string("WRE"); + + new_cell->addInput(id_A); + new_cell->addInput(id_B); + new_cell->addInput(id_C); + new_cell->addInput(id_D); + + new_cell->addInput(id_M); + + new_cell->addInput(id_F1); + new_cell->addInput(id_FCI); + new_cell->addInput(id_FXA); + new_cell->addInput(id_FXB); + + new_cell->addInput(id_DI0); + new_cell->addInput(id_DI1); + + new_cell->addInput(id_WD); + new_cell->addInput(id_WAD0); + new_cell->addInput(id_WAD1); + new_cell->addInput(id_WAD2); + new_cell->addInput(id_WAD3); + new_cell->addInput(id_WRE); + new_cell->addInput(id_WCK); + + new_cell->addOutput(id_F); + + new_cell->addOutput(id_FCO); + new_cell->addOutput(id_OFX); + } else if (type == id_TRELLIS_RAMW) { + for (auto i : {id_A0, id_B0, id_C0, id_D0, id_A1, id_B1, id_C1, id_D1}) + new_cell->addInput(i); + for (auto o : {id_WDO0, id_WDO1, id_WDO2, id_WDO3, id_WADO0, id_WADO1, id_WADO2, id_WADO3}) + new_cell->addOutput(o); + } else if (type == id_TRELLIS_IO) { + new_cell->params[id_DIR] = std::string("INPUT"); + new_cell->attrs[id_IO_TYPE] = std::string("LVCMOS33"); + new_cell->params[id_DATAMUX_ODDR] = std::string("PADDO"); + new_cell->params[id_DATAMUX_MDDR] = std::string("PADDO"); + + new_cell->addInout(id_B); + new_cell->addInput(id_I); + new_cell->addInput(id_T); + new_cell->addOutput(id_O); + + new_cell->addInput(id_IOLDO); + new_cell->addInput(id_IOLTO); + + } else if (type == id_LUT4) { + new_cell->params[id_INIT] = Property(0, 16); + + new_cell->addInput(id_A); + new_cell->addInput(id_B); + new_cell->addInput(id_C); + new_cell->addInput(id_D); + new_cell->addOutput(id_Z); + } else if (type == id_CCU2C) { + new_cell->params[id_INIT0] = Property(0, 16); + new_cell->params[id_INIT1] = Property(0, 16); + new_cell->params[id_INJECT1_0] = std::string("YES"); + new_cell->params[id_INJECT1_1] = std::string("YES"); + + new_cell->addInput(id_CIN); new_cell->addInput(id_A0); new_cell->addInput(id_B0); @@ -61,62 +110,9 @@ std::unique_ptr create_machxo2_cell(Context *ctx, IdString type, std:: new_cell->addInput(id_C1); new_cell->addInput(id_D1); - new_cell->addInput(id_M0); - new_cell->addInput(id_M1); - - new_cell->addInput(id_FCI); - new_cell->addInput(id_FXA); - new_cell->addInput(id_FXB); - - new_cell->addInput(id_CLK); - new_cell->addInput(id_LSR); - new_cell->addInput(id_CE); - - new_cell->addInput(id_DI0); - new_cell->addInput(id_DI1); - - new_cell->addInput(id_WD0); - new_cell->addInput(id_WD1); - new_cell->addInput(id_WAD0); - new_cell->addInput(id_WAD1); - new_cell->addInput(id_WAD2); - new_cell->addInput(id_WAD3); - new_cell->addInput(id_WRE); - new_cell->addInput(id_WCK); - - new_cell->addOutput(id_F0); - new_cell->addOutput(id_Q0); - new_cell->addOutput(id_F1); - new_cell->addOutput(id_Q1); - - new_cell->addOutput(id_FCO); - new_cell->addOutput(id_OFX0); - new_cell->addOutput(id_OFX1); - - new_cell->addOutput(id_WDO0); - new_cell->addOutput(id_WDO1); - new_cell->addOutput(id_WDO2); - new_cell->addOutput(id_WDO3); - new_cell->addOutput(id_WADO0); - new_cell->addOutput(id_WADO1); - new_cell->addOutput(id_WADO2); - new_cell->addOutput(id_WADO3); - } else if (type == id_TRELLIS_IO) { - new_cell->params[id_DIR] = std::string("INPUT"); - new_cell->attrs[id_IO_TYPE] = std::string("LVCMOS33"); - - new_cell->addInout(id_B); - new_cell->addInput(id_I); - new_cell->addInput(id_EN); - new_cell->addOutput(id_O); - } else if (type == id_LUT4) { - new_cell->params[id_INIT] = Property(0, 16); - - new_cell->addInput(id_A); - new_cell->addInput(id_B); - new_cell->addInput(id_C); - new_cell->addInput(id_D); - new_cell->addOutput(id_Z); + new_cell->addOutput(id_S0); + new_cell->addOutput(id_S1); + new_cell->addOutput(id_COUT); } else { log_error("unable to create MachXO2 cell of type %s", type.c_str(ctx)); } @@ -124,6 +120,135 @@ std::unique_ptr create_machxo2_cell(Context *ctx, IdString type, std:: return new_cell; } +static unsigned get_dram_init(const Context *ctx, const CellInfo *ram, int bit) +{ + auto init_prop = get_or_default(ram->params, id_INITVAL, Property(0, 64)); + NPNR_ASSERT(!init_prop.is_string); + const std::string &idata = init_prop.str; + NPNR_ASSERT(idata.length() == 64); + unsigned value = 0; + for (int i = 0; i < 16; i++) { + char c = idata.at(4 * i + bit); + if (c == '1') + value |= (1 << i); + else + NPNR_ASSERT(c == '0' || c == 'x'); + } + return value; +} + +void lut_to_comb(Context *ctx, CellInfo *lut) +{ + lut->type = id_TRELLIS_COMB; + lut->params[id_INITVAL] = get_or_default(lut->params, id_INIT, Property(0, 16)); + lut->params.erase(id_INIT); + lut->renamePort(id_Z, id_F); +} + +void dram_to_ramw_split(Context *ctx, CellInfo *ram, CellInfo *ramw) +{ + if (ramw->hierpath == IdString()) + ramw->hierpath = ramw->hierpath; + ram->movePortTo(ctx->id("WAD[0]"), ramw, id_D0); + ram->movePortTo(ctx->id("WAD[1]"), ramw, id_B0); + ram->movePortTo(ctx->id("WAD[2]"), ramw, id_C0); + ram->movePortTo(ctx->id("WAD[3]"), ramw, id_A0); + + ram->movePortTo(ctx->id("DI[0]"), ramw, id_C1); + ram->movePortTo(ctx->id("DI[1]"), ramw, id_A1); + ram->movePortTo(ctx->id("DI[2]"), ramw, id_D1); + ram->movePortTo(ctx->id("DI[3]"), ramw, id_B1); +} + +void ccu2_to_comb(Context *ctx, CellInfo *ccu, CellInfo *comb, NetInfo *internal_carry, int i) +{ + std::string ii = std::to_string(i); + if (comb->hierpath == IdString()) + comb->hierpath = ccu->hierpath; + + comb->params[id_MODE] = std::string("CCU2"); + comb->params[id_INITVAL] = get_or_default(ccu->params, ctx->id("INIT" + ii), Property(0, 16)); + comb->params[id_CCU2_INJECT1] = str_or_default(ccu->params, ctx->id("INJECT1_" + ii), "YES"); + + ccu->movePortTo(ctx->id("A" + ii), comb, id_A); + ccu->movePortTo(ctx->id("B" + ii), comb, id_B); + ccu->movePortTo(ctx->id("C" + ii), comb, id_C); + ccu->movePortTo(ctx->id("D" + ii), comb, id_D); + + ccu->movePortTo(ctx->id("S" + ii), comb, id_F); + + if (i == 0) { + ccu->movePortTo(id_CIN, comb, id_FCI); + comb->connectPort(id_FCO, internal_carry); + } else if (i == 1) { + comb->connectPort(id_FCI, internal_carry); + ccu->movePortTo(id_COUT, comb, id_FCO); + } else { + NPNR_ASSERT_FALSE("bad carry index!"); + } + + for (auto &attr : ccu->attrs) + comb->attrs[attr.first] = attr.second; +} + +void dram_to_comb(Context *ctx, CellInfo *ram, CellInfo *comb, CellInfo *ramw, int index) +{ + if (comb->hierpath == IdString()) + comb->hierpath = ram->hierpath; + comb->params[id_MODE] = std::string("DPRAM"); + comb->params[id_WREMUX] = str_or_default(ram->params, id_WREMUX, "WRE"); + comb->params[id_WCKMUX] = str_or_default(ram->params, id_WCKMUX, "WCK"); + + unsigned permuted_init = 0; + unsigned init = get_dram_init(ctx, ram, index); + + for (int i = 0; i < 16; i++) { + int permuted_addr = 0; + if (i & 1) + permuted_addr |= 8; + if (i & 2) + permuted_addr |= 2; + if (i & 4) + permuted_addr |= 4; + if (i & 8) + permuted_addr |= 1; + if (init & (1 << permuted_addr)) + permuted_init |= (1 << i); + } + + comb->params[ctx->id("INITVAL")] = Property(permuted_init, 16); + + if (ram->ports.count(ctx->id("RAD[0]"))) + comb->connectPort(id_D, ram->ports.at(ctx->id("RAD[0]")).net); + + if (ram->ports.count(ctx->id("RAD[1]"))) + comb->connectPort(id_B, ram->ports.at(ctx->id("RAD[1]")).net); + + if (ram->ports.count(ctx->id("RAD[2]"))) + comb->connectPort(id_C, ram->ports.at(ctx->id("RAD[2]")).net); + + if (ram->ports.count(ctx->id("RAD[3]"))) + comb->connectPort(id_A, ram->ports.at(ctx->id("RAD[3]")).net); + + if (ram->ports.count(id_WRE)) + comb->connectPort(id_WRE, ram->ports.at(id_WRE).net); + if (ram->ports.count(id_WCK)) + comb->connectPort(id_WCK, ram->ports.at(id_WCK).net); + + ramw->connectPorts(id_WADO0, comb, id_WAD0); + ramw->connectPorts(id_WADO1, comb, id_WAD1); + ramw->connectPorts(id_WADO2, comb, id_WAD2); + ramw->connectPorts(id_WADO3, comb, id_WAD3); + + NPNR_ASSERT(index < 4); + std::string ii = std::to_string(index); + ramw->connectPorts(ctx->id("WDO" + ii), comb, id_WD); + ram->movePortTo(ctx->id("DO[" + ii + "]"), comb, id_F); + + for (auto &attr : ram->attrs) + comb->attrs[attr.first] = attr.second; +} + void lut_to_lc(const Context *ctx, CellInfo *lut, CellInfo *lc, bool no_dff) { lc->params[id_LUT0_INITVAL] = lut->params[id_INIT]; @@ -165,6 +290,82 @@ void dff_to_lc(Context *ctx, CellInfo *dff, CellInfo *lc, LutType lut_type) } } -void nxio_to_iob(Context *ctx, CellInfo *nxio, CellInfo *iob, pool &todelete_cells) {} +void nxio_to_tr(Context *ctx, CellInfo *nxio, CellInfo *trio, std::vector> &created_cells, + pool &todelete_cells) +{ + if (nxio->type == ctx->id("$nextpnr_ibuf")) { + trio->params[id_DIR] = std::string("INPUT"); + nxio->movePortTo(id_O, trio, id_O); + } else if (nxio->type == ctx->id("$nextpnr_obuf")) { + trio->params[id_DIR] = std::string("OUTPUT"); + nxio->movePortTo(id_I, trio, id_I); + } else if (nxio->type == ctx->id("$nextpnr_iobuf")) { + // N.B. tristate will be dealt with below + NetInfo *i = nxio->getPort(id_I); + if (i == nullptr || i->driver.cell == nullptr) + trio->params[id_DIR] = std::string("INPUT"); + else { + log_info("%s: %s.%s\n", ctx->nameOf(i), ctx->nameOf(i->driver.cell), ctx->nameOf(i->driver.port)); + trio->params[id_DIR] = std::string("BIDIR"); + } + nxio->movePortTo(id_I, trio, id_I); + nxio->movePortTo(id_O, trio, id_O); + } else { + NPNR_ASSERT(false); + } + NetInfo *donet = trio->ports.at(id_I).net, *dinet = trio->ports.at(id_O).net; + + // Rename I/O nets to avoid conflicts + if (donet != nullptr && donet->name == nxio->name) + if (donet) + ctx->renameNet(donet->name, ctx->id(donet->name.str(ctx) + "$TRELLIS_IO_OUT")); + if (dinet != nullptr && dinet->name == nxio->name) + if (dinet) + ctx->renameNet(dinet->name, ctx->id(dinet->name.str(ctx) + "$TRELLIS_IO_IN")); + + if (ctx->nets.count(nxio->name)) { + int i = 0; + IdString new_name; + do { + new_name = ctx->id(nxio->name.str(ctx) + "$rename$" + std::to_string(i++)); + } while (ctx->nets.count(new_name)); + if (ctx->nets.at(nxio->name).get()) + ctx->renameNet(ctx->nets.at(nxio->name).get()->name, new_name); + } + + // Create a new top port net for accurate IO timing analysis and simulation netlists + if (ctx->ports.count(nxio->name)) { + IdString tn_netname = nxio->name; + NPNR_ASSERT(!ctx->nets.count(tn_netname)); + ctx->net_aliases.erase(tn_netname); + NetInfo *toplevel_net = ctx->createNet(tn_netname); + toplevel_net->name = tn_netname; + trio->connectPort(id_B, toplevel_net); + ctx->ports[nxio->name].net = toplevel_net; + } + + CellInfo *tbuf = net_driven_by( + ctx, donet, [](const Context *ctx, const CellInfo *cell) { return cell->type == ctx->id("$_TBUF_"); }, + id_Y); + if (tbuf) { + tbuf->movePortTo(id_A, trio, id_I); + // Need to invert E to form T + std::unique_ptr inv_lut = create_machxo2_cell(ctx, id_LUT4, trio->name.str(ctx) + "$invert_T"); + tbuf->movePortTo(id_E, inv_lut.get(), id_A); + inv_lut->params[id_INIT] = Property(21845, 16); + inv_lut->connectPorts(id_Z, trio, id_T); + created_cells.push_back(std::move(inv_lut)); + + if (donet->users.entries() > 1) { + for (auto user : donet->users) + log_info(" remaining tristate user: %s.%s\n", user.cell->name.c_str(ctx), user.port.c_str(ctx)); + log_error("unsupported tristate IO pattern for IO buffer '%s', " + "instantiate SB_IO manually to ensure correct behaviour\n", + nxio->name.c_str(ctx)); + } + ctx->nets.erase(donet->name); + todelete_cells.insert(tbuf->name); + } +} NEXTPNR_NAMESPACE_END diff --git a/machxo2/cells.h b/machxo2/cells.h index 753d7f11..cc64b52e 100644 --- a/machxo2/cells.h +++ b/machxo2/cells.h @@ -41,12 +41,13 @@ std::unique_ptr create_machxo2_cell(Context *ctx, IdString type, std:: // Return true if a cell is a LUT inline bool is_lut(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == id_LUT4; } +inline bool is_carry(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == id_CCU2C; } +inline bool is_dpram(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == id_TRELLIS_DPR16X4; } +inline bool is_trellis_io(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == id_TRELLIS_IO; } // Return true if a cell is a flipflop inline bool is_ff(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == id_TRELLIS_FF; } -inline bool is_lc(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == id_TRELLIS_SLICE; } - // Convert a LUT primitive to (part of) an GENERIC_SLICE, swapping ports // as needed. Set no_dff if a DFF is not being used, so that the output // can be reconnected @@ -58,8 +59,15 @@ void lut_to_lc(const Context *ctx, CellInfo *lut, CellInfo *lc, bool no_dff = tr // ignored void dff_to_lc(Context *ctx, CellInfo *dff, CellInfo *lc, LutType lut_type = LutType::Normal); -// Convert a nextpnr IO buffer to a GENERIC_IOB -void nxio_to_iob(Context *ctx, CellInfo *nxio, CellInfo *sbio, pool &todelete_cells); +// Convert a nextpnr IO buffer to a TRELLIS_IO +void nxio_to_tr(Context *ctx, CellInfo *nxio, CellInfo *trio, std::vector> &created_cells, + pool &todelete_cells); + +void lut_to_comb(Context *ctx, CellInfo *lut); +void dram_to_ramw_split(Context *ctx, CellInfo *ram, CellInfo *ramw); +void ccu2_to_comb(Context *ctx, CellInfo *ccu, CellInfo *comb, NetInfo *internal_carry, int i); +void dram_to_comb(Context *ctx, CellInfo *ram, CellInfo *comb, CellInfo *ramw, int index); + NEXTPNR_NAMESPACE_END diff --git a/machxo2/constids.inc b/machxo2/constids.inc index d527c28f..0f5c2e10 100644 --- a/machxo2/constids.inc +++ b/machxo2/constids.inc @@ -15,6 +15,8 @@ X(FXB) X(CLK) X(LSR) X(CE) +X(CEA) +X(CEW) X(DI0) X(DI1) X(WD0) @@ -131,3 +133,32 @@ X(place) X(placer) X(route) X(router) + + +X(TRELLIS_COMB) +X(TRELLIS_RAMW) +X(WD) +X(OFX) +X(F) +X(M) + +X(CCU2C) +X(CCU2_INJECT1) +X(INJECT1_0) +X(INJECT1_1) +X(TRELLIS_DPR16X4) +X(INITVAL) +X(INIT0) +X(INIT1) + +X(DATAMUX_ODDR) +X(DATAMUX_MDDR) +X(CIN) +X(S0) +X(S1) +X(COUT) + +X(E) +X(Y) + +X(WCKMUX) \ No newline at end of file diff --git a/machxo2/facade_import.py b/machxo2/facade_import.py index 9d0b5ed9..e4822f23 100644 --- a/machxo2/facade_import.py +++ b/machxo2/facade_import.py @@ -481,7 +481,7 @@ def main(): constids["PIO"] = constids["TRELLIS_IO"] chip = pytrellis.Chip(dev_names[args.device]) - rg = pytrellis.make_optimized_chipdb(chip, split_slice_mode=False) + rg = pytrellis.make_optimized_chipdb(chip, split_slice_mode=True) max_row = chip.get_max_row() max_col = chip.get_max_col() process_pio_db(rg, args.device) diff --git a/machxo2/pack.cc b/machxo2/pack.cc index 5f895cf9..b6aa8be6 100644 --- a/machxo2/pack.cc +++ b/machxo2/pack.cc @@ -1,8 +1,7 @@ /* * nextpnr -- Next Generation Place and Route * - * Copyright (C) 2018-19 gatecat - * Copyright (C) 2021 William D. Jones + * Copyright (C) 2018 gatecat * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -19,326 +18,1169 @@ */ #include +#include #include +#include #include "cells.h" +#include "chain_utils.h" #include "design_utils.h" #include "log.h" +#include "timing.h" #include "util.h" - NEXTPNR_NAMESPACE_BEGIN -// Pack LUTs and LUT-FF pairs -static void pack_lut_lutffs(Context *ctx) -{ - log_info("Packing LUT-FFs..\n"); - - pool packed_cells; - std::vector> new_cells; - for (auto &cell : ctx->cells) { - CellInfo *ci = cell.second.get(); - if (ctx->verbose) - log_info("cell '%s' is of type '%s'\n", ci->name.c_str(ctx), ci->type.c_str(ctx)); - if (is_lut(ctx, ci)) { - std::unique_ptr packed = create_machxo2_cell(ctx, id_TRELLIS_SLICE, ci->name.str(ctx) + "_LC"); - for (auto &attr : ci->attrs) - packed->attrs[attr.first] = attr.second; - - packed_cells.insert(ci->name); - if (ctx->verbose) - log_info("packed cell %s into %s\n", ci->name.c_str(ctx), packed->name.c_str(ctx)); - // See if we can pack into a DFF. Both LUT4 and FF outputs are - // available for a given slice, so we can pack a FF even if the - // LUT4 drives more than one FF. - NetInfo *o = ci->ports.at(id_Z).net; - CellInfo *dff = net_only_drives(ctx, o, is_ff, id_DI, false); - auto lut_bel = ci->attrs.find(id_BEL); - bool packed_dff = false; - - if (dff) { - if (ctx->verbose) - log_info("found attached dff %s\n", dff->name.c_str(ctx)); - auto dff_bel = dff->attrs.find(id_BEL); - if (lut_bel != ci->attrs.end() && dff_bel != dff->attrs.end() && lut_bel->second != dff_bel->second) { - // Locations don't match, can't pack - } else { - lut_to_lc(ctx, ci, packed.get(), false); - dff_to_lc(ctx, dff, packed.get(), LutType::Normal); - if (dff_bel != dff->attrs.end()) - packed->attrs[id_BEL] = dff_bel->second; - packed_cells.insert(dff->name); - if (ctx->verbose) - log_info("packed cell %s into %s\n", dff->name.c_str(ctx), packed->name.c_str(ctx)); - packed_dff = true; - } - } - if (!packed_dff) { - lut_to_lc(ctx, ci, packed.get(), true); - } - new_cells.push_back(std::move(packed)); - } - } - - for (auto pcell : packed_cells) { - ctx->cells.erase(pcell); - } - for (auto &ncell : new_cells) { - ctx->cells[ncell->name] = std::move(ncell); - } -} - -static void pack_remaining_ffs(Context *ctx) -{ - log_info("Packing remaining FFs..\n"); - - pool packed_cells; - std::vector> new_cells; - - for (auto &cell : ctx->cells) { - CellInfo *ci = cell.second.get(); - - if (is_ff(ctx, ci)) { - if (ctx->verbose) - log_info("cell '%s' of type '%s remains unpacked'\n", ci->name.c_str(ctx), ci->type.c_str(ctx)); - - std::unique_ptr packed = create_machxo2_cell(ctx, id_TRELLIS_SLICE, ci->name.str(ctx) + "_LC"); - for (auto &attr : ci->attrs) - packed->attrs[attr.first] = attr.second; - - auto dff_bel = ci->attrs.find(id_BEL); - - dff_to_lc(ctx, ci, packed.get(), LutType::None); - - if (dff_bel != ci->attrs.end()) - packed->attrs[id_BEL] = dff_bel->second; - packed_cells.insert(ci->name); - if (ctx->verbose) - log_info("packed cell %s into %s\n", ci->name.c_str(ctx), packed->name.c_str(ctx)); - - new_cells.push_back(std::move(packed)); - } - } - - for (auto pcell : packed_cells) { - ctx->cells.erase(pcell); - } - for (auto &ncell : new_cells) { - ctx->cells[ncell->name] = std::move(ncell); - } -} - -// Merge a net into a constant net -static void set_net_constant(Context *ctx, NetInfo *orig, NetInfo *constnet, bool constval) -{ - (void)constval; - - pool packed_cells; - std::vector> new_cells; - - orig->driver.cell = nullptr; - for (auto user : orig->users) { - if (user.cell != nullptr) { - CellInfo *uc = user.cell; - if (ctx->verbose) - log_info("%s user %s\n", orig->name.c_str(ctx), uc->name.c_str(ctx)); - - if (uc->type == id_TRELLIS_FF && user.port == id_DI) { - log_info("TRELLIS_FF %s is driven by a constant\n", uc->name.c_str(ctx)); - - std::unique_ptr lc = create_machxo2_cell(ctx, id_TRELLIS_SLICE, uc->name.str(ctx) + "_CONST"); - for (auto &attr : uc->attrs) - lc->attrs[attr.first] = attr.second; - - dff_to_lc(ctx, uc, lc.get(), LutType::PassThru); - packed_cells.insert(uc->name); - - lc->ports[id_A0].net = constnet; - user.cell = lc.get(); - user.port = id_A0; - - new_cells.push_back(std::move(lc)); - } else { - uc->ports[user.port].net = constnet; - } - - user.cell->ports.at(user.port).user_idx = constnet->users.add(user); - } - } - orig->users.clear(); - - for (auto pcell : packed_cells) { - ctx->cells.erase(pcell); - } - for (auto &ncell : new_cells) { - ctx->cells[ncell->name] = std::move(ncell); - } -} - -// Pack constants (based on simple implementation in generic). -// VCC/GND cells provided by nextpnr automatically. -static void pack_constants(Context *ctx) -{ - log_info("Packing constants..\n"); - - std::unique_ptr const_cell = create_machxo2_cell(ctx, id_TRELLIS_SLICE, "$PACKER_CONST"); - const_cell->params[id_LUT0_INITVAL] = Property(0, 16); - const_cell->params[id_LUT1_INITVAL] = Property(0xFFFF, 16); - - NetInfo *gnd_net = ctx->createNet(ctx->id("$PACKER_GND_NET")); - gnd_net->driver.cell = const_cell.get(); - gnd_net->driver.port = id_F0; - const_cell->ports.at(id_F0).net = gnd_net; - - NetInfo *vcc_net = ctx->createNet(ctx->id("$PACKER_VCC_NET")); - vcc_net->name = ctx->id("$PACKER_VCC_NET"); - vcc_net->driver.cell = const_cell.get(); - vcc_net->driver.port = id_F1; - const_cell->ports.at(id_F1).net = vcc_net; - - std::vector dead_nets; - - for (auto &net : ctx->nets) { - NetInfo *ni = net.second.get(); - if (ni->driver.cell != nullptr && ni->driver.cell->type == id_GND) { - IdString drv_cell = ni->driver.cell->name; - set_net_constant(ctx, ni, gnd_net, false); - dead_nets.push_back(net.first); - ctx->cells.erase(drv_cell); - } else if (ni->driver.cell != nullptr && ni->driver.cell->type == id_VCC) { - IdString drv_cell = ni->driver.cell->name; - set_net_constant(ctx, ni, vcc_net, true); - dead_nets.push_back(net.first); - ctx->cells.erase(drv_cell); - } - } - - ctx->cells[const_cell->name] = std::move(const_cell); - - for (auto dn : dead_nets) { - ctx->nets.erase(dn); - } -} - static bool is_nextpnr_iob(Context *ctx, CellInfo *cell) { return cell->type == ctx->id("$nextpnr_ibuf") || cell->type == ctx->id("$nextpnr_obuf") || cell->type == ctx->id("$nextpnr_iobuf"); } -static bool is_trellis_iob(const Context *ctx, const CellInfo *cell) { return cell->type == id_TRELLIS_IO; } - -static bool nextpnr_iob_connects_only_trellis_iob(Context *ctx, CellInfo *iob, NetInfo *&top) +static bool net_is_constant(const Context *ctx, NetInfo *net, bool &value) { - NPNR_ASSERT(is_nextpnr_iob(ctx, iob)); - - if (iob->type == ctx->id("$nextpnr_ibuf")) { - NetInfo *o = iob->ports.at(id_O).net; - top = o; - - CellInfo *fio = net_only_drives(ctx, o, is_trellis_iob, id_B, true); - return fio != nullptr; - } else if (iob->type == ctx->id("$nextpnr_obuf")) { - NetInfo *i = iob->ports.at(id_I).net; - top = i; - - // If connected to a TRELLIS_IO PAD, the net attached to an I port of an - // $nextpnr_obuf will not have a driver, only users; an inout port - // like PAD cannot be a driver in nextpnr. So net_driven_by won't - // return anything. We exclude the IOB as one of the two users because - // we already know that the net drives the $nextpnr_obuf. - CellInfo *fio = net_only_drives(ctx, i, is_trellis_iob, id_B, true, iob); - return fio != nullptr; - } else if (iob->type == ctx->id("$nextpnr_iobuf")) { - NetInfo *o = iob->ports.at(id_O).net; - top = o; - - // When split_io is enabled in a frontend (it is for JSON), the I and O - // ports of a $nextpnr_iobuf are split; the I port connects to the - // driver of the original net before IOB insertion, and the O port - // connects everything else. Because TRELLIS_IO PADs cannot be a driver - // in nextpnr, the we can safely ignore the I port of an $nextpnr_iobuf - // for any JSON input we're interested in accepting. - CellInfo *fio_o = net_only_drives(ctx, o, is_trellis_iob, id_B, true); - return fio_o != nullptr; + auto gnd = ctx->id("$PACKER_GND_NET"); + auto vcc = ctx->id("$PACKER_VCC_NET"); + if (net == nullptr) + return false; + if (net->name.in(gnd, vcc)) { + value = (net->name == vcc); + return true; + } else { + return false; } - - // Unreachable! - NPNR_ASSERT(false); } -// Pack IO buffers- Right now, all this does is remove $nextpnr_[io]buf cells. -// User is expected to manually instantiate TRELLIS_IO with BEL/IO_TYPE -// attributes. -static void pack_io(Context *ctx) +class Ecp5Packer { - pool packed_cells; + public: + Ecp5Packer(Context *ctx) : ctx(ctx){}; - log_info("Packing IOs..\n"); + private: + // Process the contents of packed_cells and new_cells + void flush_cells() + { + for (auto pcell : packed_cells) { + ctx->cells.erase(pcell); + } + for (auto &ncell : new_cells) { + ctx->cells[ncell->name] = std::move(ncell); + } + packed_cells.clear(); + new_cells.clear(); + } - for (auto &cell : ctx->cells) { - CellInfo *ci = cell.second.get(); - if (is_nextpnr_iob(ctx, ci)) { - NetInfo *top; - - if (!nextpnr_iob_connects_only_trellis_iob(ctx, ci, top)) - log_error("Top level net '%s' is not connected to a TRELLIS_IO PAD port.\n", top->name.c_str(ctx)); - - if (ctx->verbose) - log_info("Removing top-level IOBUF '%s' of type '%s'\n", ci->name.c_str(ctx), ci->type.c_str(ctx)); - - for (auto &p : ci->ports) - ci->disconnectPort(p.first); - packed_cells.insert(ci->name); - } else if (is_trellis_iob(ctx, ci)) { - // If TRELLIS_IO has LOC attribute, convert the LOC (pin) to a BEL - // attribute and place TRELLIS_IO at resulting BEL location. A BEL - // attribute already on a TRELLIS_IO is an error. Attributes on - // the pin attached to the PAD of TRELLIS_IO are ignored by this - // packing phase. - auto loc_attr_cell = ci->attrs.find(id_LOC); - auto bel_attr_cell = ci->attrs.find(id_BEL); - - if (loc_attr_cell != ci->attrs.end()) { - if (bel_attr_cell != ci->attrs.end()) { - log_error("IO buffer %s has both a BEL attribute and LOC attribute.\n", ci->name.c_str(ctx)); - } - - log_info("found LOC attribute on IO buffer %s.\n", ci->name.c_str(ctx)); - std::string pin = loc_attr_cell->second.as_string(); - - BelId pinBel = ctx->getPackagePinBel(pin); - if (pinBel == BelId()) { - log_error("IO buffer '%s' constrained to pin '%s', which does not exist for package '%s'.\n", - ci->name.c_str(ctx), pin.c_str(), ctx->package_name); - } else { - log_info("pin '%s' constrained to Bel '%s'.\n", ci->name.c_str(ctx), ctx->nameOfBel(pinBel)); - } - ci->attrs[id_BEL] = ctx->getBelName(pinBel).str(ctx); + // Print logic usage + void print_logic_usage() + { + int total_luts = 0, total_ffs = 0; + int total_ramluts = 0, total_ramwluts = 0; + for (auto bel : ctx->getBels()) { + if (ctx->getBelType(bel) == id_TRELLIS_COMB) { + total_luts += 1; + Loc l = ctx->getBelLocation(bel); + if (l.z <= 3) + total_ramluts += 1; } + if (ctx->getBelType(bel) == id_TRELLIS_FF) + total_ffs += 1; + if (ctx->getBelType(bel) == id_TRELLIS_RAMW) + total_ramwluts += 2; + } + int used_lgluts = 0, used_cyluts = 0, used_ramluts = 0, used_ramwluts = 0, used_ffs = 0; + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (is_lut(ctx, ci)) + ++used_lgluts; + if (is_carry(ctx, ci)) + used_cyluts += 2; + if (is_dpram(ctx, ci)) { + used_ramluts += 4; + used_ramwluts += 2; + } + if (is_ff(ctx, ci)) + ++used_ffs; + } + log_info("Logic utilisation before packing:\n"); + auto pc = [](int used, int total) { return 100 * used / total; }; + int used_luts = used_lgluts + used_cyluts + used_ramluts + used_ramwluts; + log_info(" Total LUT4s: %5d/%5d %5d%%\n", used_luts, total_luts, pc(used_luts, total_luts)); + log_info(" logic LUTs: %5d/%5d %5d%%\n", used_lgluts, total_luts, pc(used_lgluts, total_luts)); + log_info(" carry LUTs: %5d/%5d %5d%%\n", used_cyluts, total_luts, pc(used_cyluts, total_luts)); + log_info(" RAM LUTs: %5d/%5d %5d%%\n", used_ramluts, total_ramluts, pc(used_ramluts, total_ramluts)); + log_info(" RAMW LUTs: %5d/%5d %5d%%\n", used_ramwluts, total_ramwluts, + pc(used_ramwluts, total_ramwluts)); + log_break(); + log_info(" Total DFFs: %5d/%5d %5d%%\n", used_ffs, total_ffs, pc(used_ffs, total_ffs)); + log_break(); + } + + // Pack LUTs + void pack_luts() + { + log_info("Packing LUTs...\n"); + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (is_lut(ctx, ci)) + lut_to_comb(ctx, ci); } } - for (auto pcell : packed_cells) { - ctx->cells.erase(pcell); + // Gets the z-position of a cell in a macro + int get_macro_cell_z(const CellInfo *ci) + { + if (ci->constr_abs_z) + return ci->constr_z; + else if (ci->cluster != ClusterId() && ctx->getClusterRootCell(ci->cluster) != ci) + return ci->constr_z + get_macro_cell_z(ctx->getClusterRootCell(ci->cluster)); + else + return 0; } -} + // Gets the relative xy-position of a cell in a macro + std::pair get_macro_cell_xy(const CellInfo *ci) + { + if (ci->cluster != ClusterId()) + return {ci->constr_x, ci->constr_y}; + else + return {0, 0}; + } + + // Relatively constrain one cell to another + void rel_constr_cells(CellInfo *a, CellInfo *b, int dz) + { + if (a->cluster != ClusterId() && ctx->getClusterRootCell(a->cluster) != a) { + NPNR_ASSERT(b->cluster == ClusterId()); + NPNR_ASSERT(b->constr_children.empty()); + CellInfo *root = ctx->getClusterRootCell(a->cluster); + root->constr_children.push_back(b); + b->cluster = root->cluster; + b->constr_x = a->constr_x; + b->constr_y = a->constr_y; + b->constr_z = get_macro_cell_z(a) + dz; + b->constr_abs_z = a->constr_abs_z; + } else if (b->cluster != ClusterId() && ctx->getClusterRootCell(b->cluster) != b) { + NPNR_ASSERT(a->constr_children.empty()); + CellInfo *root = ctx->getClusterRootCell(b->cluster); + root->constr_children.push_back(a); + a->cluster = root->cluster; + a->constr_x = b->constr_x; + a->constr_y = b->constr_y; + a->constr_z = get_macro_cell_z(b) - dz; + a->constr_abs_z = b->constr_abs_z; + } else if (!b->constr_children.empty()) { + NPNR_ASSERT(a->constr_children.empty()); + b->constr_children.push_back(a); + a->cluster = b->cluster; + a->constr_x = 0; + a->constr_y = 0; + a->constr_z = get_macro_cell_z(b) - dz; + a->constr_abs_z = b->constr_abs_z; + } else { + NPNR_ASSERT(a->cluster == ClusterId() || ctx->getClusterRootCell(a->cluster) == a); + a->constr_children.push_back(b); + a->cluster = a->name; + b->cluster = a->name; + b->constr_x = 0; + b->constr_y = 0; + b->constr_z = get_macro_cell_z(a) + dz; + b->constr_abs_z = a->constr_abs_z; + } + } + + // Check if it is legal to add a FF to a macro + // This reuses the tile validity code + bool can_add_flipflop_to_macro(CellInfo *comb, CellInfo *ff) + { + Arch::LogicTileStatus lts; + std::fill(lts.cells.begin(), lts.cells.end(), nullptr); + lts.tile_dirty = true; + for (auto &sl : lts.slices) + sl.dirty = true; + + auto process_cell = [&](CellInfo *ci) { + if (get_macro_cell_xy(ci) != get_macro_cell_xy(comb)) + return; + int z = get_macro_cell_z(ci); + auto &slot = lts.cells.at(z); + NPNR_ASSERT(slot == nullptr); + slot = ci; + // Make sure fields needed for validity checking are set correctly + ctx->assign_arch_info_for_cell(ci); + }; + + if (comb->cluster != ClusterId()) { + CellInfo *root = ctx->getClusterRootCell(comb->cluster); + process_cell(root); + for (auto &ch : root->constr_children) + process_cell(ch); + } else { + process_cell(comb); + for (auto &ch : comb->constr_children) + process_cell(ch); + } + int ff_z = get_macro_cell_z(comb) + (Arch::BEL_FF - Arch::BEL_COMB); + if (lts.cells.at(ff_z) != nullptr) + return false; + ctx->assign_arch_info_for_cell(ff); + lts.cells.at(ff_z) = ff; + return ctx->slices_compatible(<s); + } + + void pack_ffs() + { + log_info("Packing FFs...\n"); + int pairs = 0; + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (is_ff(ctx, ci)) { + NetInfo *di = ci->getPort(id_DI); + if (di->driver.cell != nullptr && di->driver.cell->type == id_TRELLIS_COMB && di->driver.port == id_F) { + CellInfo *comb = di->driver.cell; + if (comb->cluster != ClusterId()) { + // Special procedure where the comb cell is part of an existing macro + // Need to make sure that CLK, CE, SR, etc are shared correctly, or + // the design will not be routeable + if (can_add_flipflop_to_macro(comb, ci)) { + ci->params[id_SD] = std::string("1"); + rel_constr_cells(comb, ci, (Arch::BEL_FF - Arch::BEL_COMB)); + // Packed successfully + ++pairs; + continue; + } + } else { + // LUT/COMB is not part of a macro, this is the easy case + // Constrain FF and LUT together, no need to rewire + ci->params[id_SD] = std::string("1"); + comb->constr_children.push_back(ci); + ci->cluster = comb->name; + comb->cluster = comb->name; + ci->constr_x = 0; + ci->constr_y = 0; + ci->constr_z = (Arch::BEL_FF - Arch::BEL_COMB); + ci->constr_abs_z = false; + // Packed successfully + ++pairs; + continue; + } + } + { + // Didn't manage to pack it with a driving combinational cell + // Rewire to use general routing + ci->params[id_SD] = std::string("0"); + ci->renamePort(id_DI, id_M); + } + } + } + log_info(" %d FFs paired with LUTs.\n", pairs); + } + + // Return true if an port is a top level port that provides its own IOBUF + bool is_top_port(PortRef &port) + { + return false; + } + + // Return true if a net only drives a top port + bool drives_top_port(NetInfo *net, PortRef &tp) + { + if (net == nullptr) + return false; + for (auto user : net->users) { + if (is_top_port(user)) { + if (net->users.entries() > 1) + log_error(" port %s.%s must be connected to (and only to) a top level pin\n", + user.cell->name.c_str(ctx), user.port.c_str(ctx)); + tp = user; + return true; + } + } + if (net->driver.cell != nullptr && is_top_port(net->driver)) { + if (net->users.entries() > 1) + log_error(" port %s.%s must be connected to (and only to) a top level pin\n", + net->driver.cell->name.c_str(ctx), net->driver.port.c_str(ctx)); + tp = net->driver; + return true; + } + return false; + } + + // Simple "packer" to remove nextpnr IOBUFs, this assumes IOBUFs are manually instantiated + void pack_io() + { + log_info("Packing IOs..\n"); + + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (is_nextpnr_iob(ctx, ci)) { + CellInfo *trio = nullptr; + NetInfo *ionet = nullptr; + PortRef tp; + if (ci->type == ctx->id("$nextpnr_ibuf") || ci->type == ctx->id("$nextpnr_iobuf")) { + ionet = ci->ports.at(id_O).net; + trio = net_only_drives(ctx, ionet, is_trellis_io, id_B, true, ci); + + } else if (ci->type == ctx->id("$nextpnr_obuf")) { + ionet = ci->ports.at(id_I).net; + trio = net_only_drives(ctx, ci->ports.at(id_I).net, is_trellis_io, id_B, true, ci); + } + if (bool_or_default(ctx->settings, ctx->id("arch.ooc"))) { + // No IO buffer insertion in out-of-context mode, just remove the nextpnr buffer + // and leave the top level port + for (auto &port : ci->ports) + ci->disconnectPort(port.first); + } else if (trio != nullptr) { + // Trivial case, TRELLIS_IO used. Just remove the IOBUF + log_info("%s feeds TRELLIS_IO %s, removing %s %s.\n", ci->name.c_str(ctx), trio->name.c_str(ctx), + ci->type.c_str(ctx), ci->name.c_str(ctx)); + + NetInfo *net = trio->ports.at(id_B).net; + if (((ci->type == ctx->id("$nextpnr_ibuf") || ci->type == ctx->id("$nextpnr_iobuf")) && + net->users.entries() > 1) || + (ci->type == ctx->id("$nextpnr_obuf") && + (net->users.entries() > 2 || net->driver.cell != nullptr)) || + (ci->type == ctx->id("$nextpnr_iobuf") && ci->ports.at(id_I).net != nullptr && + ci->ports.at(id_I).net->driver.cell != nullptr)) + log_error("Pin B of %s '%s' connected to more than a single top level IO.\n", + trio->type.c_str(ctx), trio->name.c_str(ctx)); + if (net != nullptr) { + if (net->clkconstr != nullptr && trio->ports.count(id_O)) { + NetInfo *onet = trio->ports.at(id_O).net; + if (onet != nullptr && !onet->clkconstr) { + // Move clock constraint from IO pad to input buffer output + std::swap(net->clkconstr, onet->clkconstr); + } + } + } + } else if (drives_top_port(ionet, tp)) { + log_info("%s feeds %s %s.%s, removing %s %s.\n", ci->name.c_str(ctx), tp.cell->type.c_str(ctx), + tp.cell->name.c_str(ctx), tp.port.c_str(ctx), ci->type.c_str(ctx), ci->name.c_str(ctx)); + if (ionet != nullptr) { + ctx->nets.erase(ionet->name); + tp.cell->ports.at(tp.port).net = nullptr; + } + if (ci->type == ctx->id("$nextpnr_iobuf")) { + NetInfo *net2 = ci->ports.at(id_I).net; + if (net2 != nullptr) { + ctx->nets.erase(net2->name); + } + } + } else { + // Create a TRELLIS_IO buffer + std::unique_ptr tr_cell = + create_machxo2_cell(ctx, id_TRELLIS_IO, ci->name.str(ctx) + "$tr_io"); + nxio_to_tr(ctx, ci, tr_cell.get(), new_cells, packed_cells); + new_cells.push_back(std::move(tr_cell)); + trio = new_cells.back().get(); + } + for (auto port : ci->ports) + ci->disconnectPort(port.first); + packed_cells.insert(ci->name); + if (trio != nullptr) { + for (const auto &attr : ci->attrs) + trio->attrs[attr.first] = attr.second; + + auto loc_attr = trio->attrs.find(id_LOC); + if (loc_attr != trio->attrs.end()) { + std::string pin = loc_attr->second.as_string(); + BelId pinBel = ctx->getPackagePinBel(pin); + if (pinBel == BelId()) { + log_error("IO pin '%s' constrained to pin '%s', which does not exist for package '%s'.\n", + trio->name.c_str(ctx), pin.c_str(), ctx->package_name); + } else { + log_info("pin '%s' constrained to Bel '%s'.\n", trio->name.c_str(ctx), + ctx->nameOfBel(pinBel)); + } + trio->attrs[id_BEL] = ctx->getBelName(pinBel).str(ctx); + } + } + } + } + flush_cells(); + } + + // Create a feed in to the carry chain + CellInfo *make_carry_feed_in(NetInfo *carry, PortRef chain_in) + { + std::unique_ptr feedin = create_machxo2_cell(ctx, id_CCU2C); + + feedin->params[id_INIT0] = Property(10, 16); // LUT4 = 0; LUT2 = A + feedin->params[id_INIT1] = Property(65535, 16); + feedin->params[id_INJECT1_0] = std::string("NO"); + feedin->params[id_INJECT1_1] = std::string("YES"); + + carry->users.remove(chain_in.cell->ports.at(chain_in.port).user_idx); + feedin->connectPort(id_A0, carry); + + NetInfo *new_carry = ctx->createNet(ctx->id(feedin->name.str(ctx) + "$COUT")); + feedin->connectPort(id_COUT, new_carry); + chain_in.cell->ports[chain_in.port].net = nullptr; + chain_in.cell->ports[chain_in.port].user_idx = {}; + + chain_in.cell->connectPort(chain_in.port, new_carry); + + CellInfo *feedin_ptr = feedin.get(); + IdString feedin_name = feedin->name; + ctx->cells[feedin_name] = std::move(feedin); + return feedin_ptr; + } + + // Create a feed out and loop through from the carry chain + CellInfo *make_carry_feed_out(NetInfo *carry, boost::optional chain_next = boost::optional()) + { + std::unique_ptr feedout = create_machxo2_cell(ctx, id_CCU2C); + + feedout->params[id_INIT0] = Property(0, 16); + feedout->params[id_INIT1] = Property(10, 16); // LUT4 = 0; LUT2 = A + feedout->params[id_INJECT1_0] = std::string("NO"); + feedout->params[id_INJECT1_1] = std::string("NO"); + + PortRef carry_drv = carry->driver; + carry->driver.cell = nullptr; + feedout->connectPort(id_S0, carry); + + NetInfo *new_cin = ctx->createNet(ctx->id(feedout->name.str(ctx) + "$CIN")); + new_cin->driver = carry_drv; + carry_drv.cell->ports.at(carry_drv.port).net = new_cin; + feedout->connectPort(id_CIN, new_cin); + + if (chain_next) { + // Loop back into LUT4_1 for feedthrough + feedout->connectPort(id_A1, carry); + if (chain_next->cell && chain_next->cell->ports.at(chain_next->port).user_idx) + carry->users.remove(chain_next->cell->ports.at(chain_next->port).user_idx); + + NetInfo *new_cout = ctx->createNet(ctx->id(feedout->name.str(ctx) + "$COUT")); + feedout->connectPort(id_COUT, new_cout); + + chain_next->cell->ports[chain_next->port].net = nullptr; + chain_next->cell->connectPort(chain_next->port, new_cout); + } + + CellInfo *feedout_ptr = feedout.get(); + IdString feedout_name = feedout->name; + ctx->cells[feedout_name] = std::move(feedout); + + return feedout_ptr; + } + + // Split a carry chain into multiple legal chains + std::vector split_carry_chain(CellChain &carryc) + { + bool start_of_chain = true; + std::vector chains; + const int max_length = (ctx->chip_info->width - 4) * 4 - 2; + auto curr_cell = carryc.cells.begin(); + while (curr_cell != carryc.cells.end()) { + CellInfo *cell = *curr_cell; + if (start_of_chain) { + chains.emplace_back(); + start_of_chain = false; + if (cell->ports.at(id_CIN).net) { + // CIN is not constant and not part of a chain. Must feed in from fabric + PortRef inport; + inport.cell = cell; + inport.port = id_CIN; + CellInfo *feedin = make_carry_feed_in(cell->ports.at(id_CIN).net, inport); + chains.back().cells.push_back(feedin); + } + } + chains.back().cells.push_back(cell); + bool split_chain = int(chains.back().cells.size()) > max_length; + if (split_chain) { + CellInfo *passout = make_carry_feed_out(cell->ports.at(id_COUT).net); + chains.back().cells.back() = passout; + start_of_chain = true; + } else { + NetInfo *carry_net = cell->ports.at(id_COUT).net; + bool at_end = (curr_cell == carryc.cells.end() - 1); + if (carry_net != nullptr && (carry_net->users.entries() > 1 || at_end)) { + boost::optional nextport; + if (!at_end) { + auto next_cell = *(curr_cell + 1); + PortRef nextpr; + nextpr.cell = next_cell; + nextpr.port = id_CIN; + nextport = nextpr; + } + CellInfo *passout = make_carry_feed_out(cell->ports.at(id_COUT).net, nextport); + chains.back().cells.push_back(passout); + } + ++curr_cell; + } + } + return chains; + } + + // Pack carries and set up appropriate relative constraints + void pack_carries() + { + log_info("Packing carries...\n"); + // Find all chains (including single carry cells) + auto carry_chains = find_chains( + ctx, [](const Context *ctx, const CellInfo *cell) { return is_carry(ctx, cell); }, + [](const Context *ctx, const CellInfo *cell) { + return net_driven_by(ctx, cell->ports.at(id_CIN).net, is_carry, id_COUT); + }, + [](const Context *ctx, const CellInfo *cell) { + return net_only_drives(ctx, cell->ports.at(id_COUT).net, is_carry, id_CIN, false); + }, + 1); + std::vector all_chains; + + // Chain splitting + for (auto &base_chain : carry_chains) { + if (ctx->verbose) { + log_info("Found carry chain: \n"); + for (auto entry : base_chain.cells) + log_info(" %s\n", entry->name.c_str(ctx)); + log_info("\n"); + } + std::vector split_chains = split_carry_chain(base_chain); + for (auto &chain : split_chains) { + all_chains.push_back(chain); + } + } + + std::vector> packed_chains; + + // Chain packing + std::vector> ff_packing; + for (auto &chain : all_chains) { + int cell_count = 0; + std::vector packed_chain; + for (auto &cell : chain.cells) { + std::unique_ptr comb0 = + create_machxo2_cell(ctx, id_TRELLIS_COMB, cell->name.str(ctx) + "$CCU2_COMB0"); + std::unique_ptr comb1 = + create_machxo2_cell(ctx, id_TRELLIS_COMB, cell->name.str(ctx) + "$CCU2_COMB1"); + NetInfo *carry_net = ctx->createNet(ctx->id(cell->name.str(ctx) + "$CCU2_FCI_INT")); + + ccu2_to_comb(ctx, cell, comb0.get(), carry_net, 0); + ccu2_to_comb(ctx, cell, comb1.get(), carry_net, 1); + + packed_chain.push_back(comb0.get()); + packed_chain.push_back(comb1.get()); + + new_cells.push_back(std::move(comb0)); + new_cells.push_back(std::move(comb1)); + packed_cells.insert(cell->name); + cell_count++; + } + packed_chains.push_back(packed_chain); + } + + // Relative chain placement + for (auto &chain : packed_chains) { + chain.at(0)->constr_abs_z = true; + chain.at(0)->constr_z = 0; + chain.at(0)->cluster = chain.at(0)->name; + for (int i = 1; i < int(chain.size()); i++) { + chain.at(i)->constr_x = (i / 8); + chain.at(i)->constr_y = 0; + chain.at(i)->constr_z = (i % 8) << ctx->lc_idx_shift | Arch::BEL_COMB; + chain.at(i)->constr_abs_z = true; + chain.at(i)->cluster = chain.at(0)->name; + chain.at(0)->constr_children.push_back(chain.at(i)); + } + } + + flush_cells(); + } + + // Pack distributed RAM + void pack_dram() + { + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (is_dpram(ctx, ci)) { + + // Create RAMW slice + std::unique_ptr ramw_slice = + create_machxo2_cell(ctx, id_TRELLIS_RAMW, ci->name.str(ctx) + "$RAMW_SLICE"); + dram_to_ramw_split(ctx, ci, ramw_slice.get()); + + // Create actual RAM slices + std::unique_ptr ram_comb[4]; + for (int i = 0; i < 4; i++) { + ram_comb[i] = create_machxo2_cell(ctx, id_TRELLIS_COMB, + ci->name.str(ctx) + "$DPRAM_COMB" + std::to_string(i)); + dram_to_comb(ctx, ci, ram_comb[i].get(), ramw_slice.get(), i); + } + // Create 'block' SLICEs as a placement hint that these cells are mutually exclusive with the RAMW + std::unique_ptr ramw_block[2]; + for (int i = 0; i < 2; i++) { + ramw_block[i] = create_machxo2_cell(ctx, id_TRELLIS_COMB, + ci->name.str(ctx) + "$RAMW_BLOCK" + std::to_string(i)); + ramw_block[i]->params[id_MODE] = std::string("RAMW_BLOCK"); + } + + // Disconnect ports of original cell after packing + ci->disconnectPort(id_WCK); + ci->disconnectPort(id_WRE); + + for (int i = 0; i < 4; i++) + ci->disconnectPort(ctx->idf("RAD[%d]", i)); + + // Setup placement constraints + // Use the 0th bit as an anchor + ram_comb[0]->constr_abs_z = true; + ram_comb[0]->constr_z = Arch::BEL_COMB; + ram_comb[0]->cluster = ram_comb[0]->name; + for (int i = 1; i < 4; i++) { + ram_comb[i]->cluster = ram_comb[0]->name; + ram_comb[i]->constr_abs_z = true; + ram_comb[i]->constr_x = 0; + ram_comb[i]->constr_y = 0; + ram_comb[i]->constr_z = (i << ctx->lc_idx_shift) | Arch::BEL_COMB; + ram_comb[0]->constr_children.push_back(ram_comb[i].get()); + } + for (int i = 0; i < 2; i++) { + ramw_block[i]->cluster = ram_comb[0]->name; + ramw_block[i]->constr_abs_z = true; + ramw_block[i]->constr_x = 0; + ramw_block[i]->constr_y = 0; + ramw_block[i]->constr_z = ((i + 4) << ctx->lc_idx_shift) | Arch::BEL_COMB; + ram_comb[0]->constr_children.push_back(ramw_block[i].get()); + } + + ramw_slice->cluster = ram_comb[0]->name; + ramw_slice->constr_abs_z = true; + ramw_slice->constr_x = 0; + ramw_slice->constr_y = 0; + ramw_slice->constr_z = (4 << ctx->lc_idx_shift) | Arch::BEL_RAMW; + ram_comb[0]->constr_children.push_back(ramw_slice.get()); + + for (int i = 0; i < 4; i++) + new_cells.push_back(std::move(ram_comb[i])); + for (int i = 0; i < 2; i++) + new_cells.push_back(std::move(ramw_block[i])); + new_cells.push_back(std::move(ramw_slice)); + packed_cells.insert(ci->name); + } + } + flush_cells(); + } + + int make_init_with_const_input(int init, int input, bool value) + { + int new_init = 0; + for (int i = 0; i < 16; i++) { + if (((i >> input) & 0x1) != value) { + int other_i = (i & (~(1 << input))) | (value << input); + if ((init >> other_i) & 0x1) + new_init |= (1 << i); + } else { + if ((init >> i) & 0x1) + new_init |= (1 << i); + } + } + return new_init; + } + + void set_lut_input_constant(CellInfo *cell, IdString input, bool value) + { + int index = std::string("ABCD").find(input.str(ctx)); + int init = int_or_default(cell->params, id_INIT); + int new_init = make_init_with_const_input(init, index, value); + cell->params[id_INIT] = Property(new_init, 16); + cell->ports.at(input).net = nullptr; + } + + void set_ccu2c_input_constant(CellInfo *cell, IdString input, bool value) + { + std::string input_str = input.str(ctx); + int lut = std::stoi(input_str.substr(1)); + int index = std::string("ABCD").find(input_str[0]); + int init = int_or_default(cell->params, ctx->id("INIT" + std::to_string(lut))); + int new_init = make_init_with_const_input(init, index, value); + cell->params[ctx->id("INIT" + std::to_string(lut))] = Property(new_init, 16); + cell->ports.at(input).net = nullptr; + } + + bool is_ccu2c_port_high(CellInfo *cell, IdString input) + { + if (!cell->ports.count(input)) + return true; // disconnected port is high + if (cell->ports.at(input).net == nullptr || cell->ports.at(input).net->name == ctx->id("$PACKER_VCC_NET")) + return true; // disconnected or tied-high port + if (cell->ports.at(input).net->driver.cell != nullptr && cell->ports.at(input).net->driver.cell->type == id_VCC) + return true; // pre-pack high + return false; + } + + // Merge a net into a constant net + void set_net_constant(const Context *ctx, NetInfo *orig, NetInfo *constnet, bool constval) + { + orig->driver.cell = nullptr; + for (auto user : orig->users) { + if (user.cell != nullptr) { + CellInfo *uc = user.cell; + if (ctx->verbose) + log_info("%s user %s\n", orig->name.c_str(ctx), uc->name.c_str(ctx)); + if (is_lut(ctx, uc)) { + set_lut_input_constant(uc, user.port, constval); + } else if (is_ff(ctx, uc) && user.port == id_CE) { + uc->params[id_CEMUX] = std::string(constval ? "1" : "0"); + uc->ports[user.port].net = nullptr; + } else if (is_carry(ctx, uc)) { + if (constval && (user.port.in(id_A0, id_A1, id_B0, id_B1, id_C0, id_C1, id_D0, id_D1))) { + // Input tied high, nothing special to do (bitstream gen will auto-enable tie-high) + uc->ports[user.port].net = nullptr; + } else if (!constval) { + if (user.port.in(id_A0, id_A1, id_B0, id_B1)) { + // These inputs can be switched to tie-high without consequence + set_ccu2c_input_constant(uc, user.port, constval); + } else if (user.port == id_C0 && is_ccu2c_port_high(uc, id_D0)) { + // Partner must be tied high + set_ccu2c_input_constant(uc, user.port, constval); + } else if (user.port == id_D0 && is_ccu2c_port_high(uc, id_C0)) { + // Partner must be tied high + set_ccu2c_input_constant(uc, user.port, constval); + } else if (user.port == id_C1 && is_ccu2c_port_high(uc, id_D1)) { + // Partner must be tied high + set_ccu2c_input_constant(uc, user.port, constval); + } else if (user.port == id_D1 && is_ccu2c_port_high(uc, id_C1)) { + // Partner must be tied high + set_ccu2c_input_constant(uc, user.port, constval); + } else { + // Not allowed to change to a tie-high + uc->ports[user.port].net = constnet; + uc->ports[user.port].user_idx = constnet->users.add(user); + } + } else { + uc->ports[user.port].net = constnet; + uc->ports[user.port].user_idx = constnet->users.add(user); + } + } else if (is_ff(ctx, uc) && user.port == id_LSR && + ((!constval && str_or_default(uc->params, id_LSRMUX, "LSR") == "LSR") || + (constval && str_or_default(uc->params, id_LSRMUX, "LSR") == "INV"))) { + uc->ports[user.port].net = nullptr; + } else { + uc->ports[user.port].net = constnet; + uc->ports[user.port].user_idx = constnet->users.add(user); + } + } + } + orig->users.clear(); + } + + // Pack constants (simple implementation) + void pack_constants() + { + log_info("Packing constants..\n"); + + std::unique_ptr gnd_cell = create_machxo2_cell(ctx, id_LUT4, "$PACKER_GND"); + gnd_cell->params[id_INIT] = Property(0, 16); + auto gnd_net = std::make_unique(ctx->id("$PACKER_GND_NET")); + gnd_net->driver.cell = gnd_cell.get(); + gnd_net->driver.port = id_Z; + gnd_cell->ports.at(id_Z).net = gnd_net.get(); + + std::unique_ptr vcc_cell = create_machxo2_cell(ctx, id_LUT4, "$PACKER_VCC"); + vcc_cell->params[id_INIT] = Property(65535, 16); + auto vcc_net = std::make_unique(ctx->id("$PACKER_VCC_NET")); + vcc_net->driver.cell = vcc_cell.get(); + vcc_net->driver.port = id_Z; + vcc_cell->ports.at(id_Z).net = vcc_net.get(); + + std::vector dead_nets; + + bool gnd_used = false, vcc_used = false; + + for (auto &net : ctx->nets) { + NetInfo *ni = net.second.get(); + if (ni->driver.cell != nullptr && ni->driver.cell->type == id_GND) { + IdString drv_cell = ni->driver.cell->name; + set_net_constant(ctx, ni, gnd_net.get(), false); + gnd_used = true; + dead_nets.push_back(net.first); + ctx->cells.erase(drv_cell); + } else if (ni->driver.cell != nullptr && ni->driver.cell->type == id_VCC) { + IdString drv_cell = ni->driver.cell->name; + set_net_constant(ctx, ni, vcc_net.get(), true); + vcc_used = true; + dead_nets.push_back(net.first); + ctx->cells.erase(drv_cell); + } + } + + if (gnd_used) { + ctx->cells[gnd_cell->name] = std::move(gnd_cell); + ctx->nets[gnd_net->name] = std::move(gnd_net); + } + if (vcc_used) { + ctx->cells[vcc_cell->name] = std::move(vcc_cell); + ctx->nets[vcc_net->name] = std::move(vcc_net); + } + + for (auto dn : dead_nets) { + ctx->nets.erase(dn); + } + } + + void autocreate_empty_port(CellInfo *cell, IdString port) + { + if (!cell->ports.count(port)) { + cell->ports[port].name = port; + cell->ports[port].net = nullptr; + cell->ports[port].type = PORT_IN; + } + } + + // Check if two nets have identical constant drivers + bool equal_constant(NetInfo *a, NetInfo *b) + { + if (a == nullptr && b == nullptr) + return true; + if ((a == nullptr) != (b == nullptr)) + return false; + if (a->driver.cell == nullptr || b->driver.cell == nullptr) + return (a->driver.cell == nullptr && b->driver.cell == nullptr); + if (a->driver.cell->type != id_GND && a->driver.cell->type != id_VCC) + return false; + return a->driver.cell->type == b->driver.cell->type; + } + + struct EdgeClockInfo + { + CellInfo *buffer = nullptr; + NetInfo *unbuf = nullptr; + NetInfo *buf = nullptr; + }; + + std::map, EdgeClockInfo> eclks; + std::map bridge_side_hint; + + void tie_zero(CellInfo *ci, IdString port) + { + + if (!ci->ports.count(port)) { + ci->ports[port].name = port; + ci->ports[port].type = PORT_IN; + } + IdString name = ctx->id(ci->name.str(ctx) + "$zero$" + port.str(ctx)); + + auto zero_cell = std::make_unique(ctx, name, id_GND); + NetInfo *zero_net = ctx->createNet(name); + zero_cell->addOutput(id_GND); + zero_cell->connectPort(id_GND, zero_net); + ci->connectPort(port, zero_net); + new_cells.push_back(std::move(zero_cell)); + } + + int lookup_delay(const std::string &del_mode) + { + if (del_mode == "USER_DEFINED") + return 0; + else if (del_mode == "DQS_ALIGNED_X2") + return 6; + else if (del_mode == "DQS_CMD_CLK") + return 9; + else if (del_mode == "ECLK_ALIGNED") + return 21; + else if (del_mode == "ECLK_CENTERED") + return 11; + else if (del_mode == "ECLKBRIDGE_ALIGNED") + return 39; + else if (del_mode == "ECLKBRIDGE_CENTERED") + return 29; + else if (del_mode == "SCLK_ALIGNED") + return 50; + else if (del_mode == "SCLK_CENTERED") + return 39; + else if (del_mode == "SCLK_ZEROHOLD") + return 59; + else + log_error("Unsupported DEL_MODE '%s'\n", del_mode.c_str()); + } + + void prepack_checks() + { + // Check for legacy-style JSON (use CEMUX as a clue) and error out, avoiding a confusing assertion failure + // later + for (auto &cell : ctx->cells) { + if (is_ff(ctx, cell.second.get()) && cell.second->params.count(id_CEMUX) && + !cell.second->params[id_CEMUX].is_string) + log_error("Found netlist using legacy-style JSON parameter values, please update your Yosys.\n"); + } + } + + public: + void pack() + { + prepack_checks(); + print_logic_usage(); + pack_io(); + pack_constants(); + pack_dram(); + pack_carries(); + pack_luts(); + pack_ffs(); + ctx->fixupHierarchy(); + ctx->check(); + } + + private: + Context *ctx; + + pool packed_cells; + std::vector> new_cells; + + struct SliceUsage + { + bool lut0_used = false, lut1_used = false; + bool ccu2_used = false, dpram_used = false, ramw_used = false; + bool ff0_used = false, ff1_used = false; + bool mux5_used = false, muxx_used = false; + }; + + dict sliceUsage; + dict lutffPairs; + dict fflutPairs; + dict lutPairs; +}; // Main pack function bool Arch::pack() { Context *ctx = getCtx(); try { log_break(); - pack_constants(ctx); - pack_io(ctx); - pack_lut_lutffs(ctx); - pack_remaining_ffs(ctx); - ctx->settings[id_pack] = 1; - ctx->assignArchInfo(); + Ecp5Packer(ctx).pack(); log_info("Checksum: 0x%08x\n", ctx->checksum()); + assignArchInfo(); + ctx->settings[id_pack] = 1; + archInfoToAttributes(); return true; } catch (log_execution_error_exception) { + assignArchInfo(); return false; } } +void Arch::assign_arch_info_for_cell(CellInfo *ci) +{ + auto get_port_net = [&](CellInfo *ci, IdString p) { + NetInfo *n = ci->getPort(p); + return n ? n->name : IdString(); + }; + if (ci->type == id_TRELLIS_COMB) { + std::string mode = str_or_default(ci->params, id_MODE, "LOGIC"); + ci->combInfo.flags = ArchCellInfo::COMB_NONE; + if (mode == "CCU2") + ci->combInfo.flags |= ArchCellInfo::COMB_CARRY; + if (mode == "DPRAM") { + ci->combInfo.flags |= ArchCellInfo::COMB_LUTRAM; + std::string wckmux = str_or_default(ci->params, id_WCKMUX, "WCK"); + if (wckmux == "INV") + ci->combInfo.flags |= ArchCellInfo::COMB_RAM_WCKINV; + std::string wremux = str_or_default(ci->params, id_WREMUX, "WRE"); + if (wremux == "INV" || wremux == "0") + ci->combInfo.flags |= ArchCellInfo::COMB_RAM_WREINV; + ci->combInfo.ram_wck = get_port_net(ci, id_WCK); + ci->combInfo.ram_wre = get_port_net(ci, id_WRE); + } + if (mode == "RAMW_BLOCK") + ci->combInfo.flags |= ArchCellInfo::COMB_RAMW_BLOCK; + if (ci->getPort(id_F1) != nullptr) + ci->combInfo.flags |= ArchCellInfo::COMB_MUX5; + if (ci->getPort(id_FXA) != nullptr || ci->getPort(id_FXB) != nullptr) { + ci->combInfo.flags |= ArchCellInfo::COMB_MUX6; + NetInfo *fxa = ci->getPort(id_FXA); + if (fxa != nullptr) + ci->combInfo.mux_fxad = fxa->driver.cell; + } + } else if (ci->type == id_TRELLIS_FF) { + ci->ffInfo.flags = ArchCellInfo::FF_NONE; + if (str_or_default(ci->params, id_GSR, "ENABLED") == "ENABLED") + ci->ffInfo.flags |= ArchCellInfo::FF_GSREN; + if (str_or_default(ci->params, id_SRMODE, "LSR_OVER_CE") == "ASYNC") + ci->ffInfo.flags |= ArchCellInfo::FF_ASYNC; + if (ci->getPort(id_M) != nullptr) + ci->ffInfo.flags |= ArchCellInfo::FF_M_USED; + std::string clkmux = str_or_default(ci->params, id_CLKMUX, "CLK"); + std::string cemux = str_or_default(ci->params, id_CEMUX, "CE"); + std::string lsrmux = str_or_default(ci->params, id_LSRMUX, "LSR"); + if (clkmux == "INV" || clkmux == "0") + ci->ffInfo.flags |= ArchCellInfo::FF_CLKINV; + if (cemux == "INV" || cemux == "0") + ci->ffInfo.flags |= ArchCellInfo::FF_CEINV; + if (cemux == "1" || cemux == "0") + ci->ffInfo.flags |= ArchCellInfo::FF_CECONST; + if (lsrmux == "INV") + ci->ffInfo.flags |= ArchCellInfo::FF_LSRINV; + ci->ffInfo.clk_sig = get_port_net(ci, id_CLK); + ci->ffInfo.ce_sig = get_port_net(ci, id_CE); + ci->ffInfo.lsr_sig = get_port_net(ci, id_LSR); + } +} + +void Arch::assignArchInfo() +{ + for (auto &cell : cells) { + CellInfo *ci = cell.second.get(); + assign_arch_info_for_cell(ci); + } +} + +inline NetInfo *port_or_nullptr(const CellInfo *cell, IdString name) +{ + auto found = cell->ports.find(name); + if (found == cell->ports.end()) + return nullptr; + return found->second.net; +} + +bool Arch::slices_compatible(LogicTileStatus *lts) const +{ + if (lts == nullptr) + return true; + for (int sl = 0; sl < 4; sl++) { + if (!lts->slices[sl].dirty) { + if (!lts->slices[sl].valid) + return false; + continue; + } + lts->slices[sl].dirty = false; + lts->slices[sl].valid = false; + bool found_ff = false; + uint8_t last_ff_flags = 0; + IdString last_ce_sig; + bool ramw_used = false; + if (sl == 2 && lts->cells[((sl * 2) << lc_idx_shift) | BEL_RAMW] != nullptr) + ramw_used = true; + for (int l = 0; l < 2; l++) { + bool comb_m_used = false; + CellInfo *comb = lts->cells[((sl * 2 + l) << lc_idx_shift) | BEL_COMB]; + if (comb != nullptr) { + uint8_t flags = comb->combInfo.flags; + if (ramw_used && !(flags & ArchCellInfo::COMB_RAMW_BLOCK)) + return false; + if (flags & ArchCellInfo::COMB_MUX5) { + // MUX5 uses M signal and must be in LC 0 + comb_m_used = true; + if (l != 0) + return false; + } + if (flags & ArchCellInfo::COMB_MUX6) { + // MUX6+ uses M signal and must be in LC 1 + comb_m_used = true; + if (l != 1) + return false; + if (comb->combInfo.mux_fxad != nullptr && + (comb->combInfo.mux_fxad->combInfo.flags & ArchCellInfo::COMB_MUX5)) { + // LUT6 structure must be rooted at SLICE 0 or 2 + if (sl != 0 && sl != 2) + return false; + } + } + // LUTRAM must be in bottom two SLICEs only + if ((flags & ArchCellInfo::COMB_LUTRAM) && (sl > 1)) + return false; + if (l == 1) { + // Carry usage must be the same for LCs 0 and 1 in a SLICE + CellInfo *comb0 = lts->cells[((sl * 2 + 0) << lc_idx_shift) | BEL_COMB]; + if (comb0 && + ((comb0->combInfo.flags & ArchCellInfo::COMB_CARRY) != (flags & ArchCellInfo::COMB_CARRY))) + return false; + } + } + + CellInfo *ff = lts->cells[((sl * 2 + l) << lc_idx_shift) | BEL_FF]; + if (ff != nullptr) { + uint8_t flags = ff->ffInfo.flags; + if (comb_m_used && (flags & ArchCellInfo::FF_M_USED)) + return false; + if (found_ff) { + if ((flags & ArchCellInfo::FF_GSREN) != (last_ff_flags & ArchCellInfo::FF_GSREN)) + return false; + if ((flags & ArchCellInfo::FF_CECONST) != (last_ff_flags & ArchCellInfo::FF_CECONST)) + return false; + if ((flags & ArchCellInfo::FF_CEINV) != (last_ff_flags & ArchCellInfo::FF_CEINV)) + return false; + if (ff->ffInfo.ce_sig != last_ce_sig) + return false; + } else { + found_ff = true; + last_ff_flags = flags; + last_ce_sig = ff->ffInfo.ce_sig; + } + } + } + + lts->slices[sl].valid = true; + } + if (lts->tile_dirty) { + bool found_global_ff = false; + bool found_global_dpram = false; + bool global_lsrinv = false; + bool global_clkinv = false; + bool global_async = false; + + IdString clk_sig, lsr_sig; + + lts->tile_dirty = false; + lts->tile_valid = false; + +#define CHECK_EQUAL(x, y) \ + do { \ + if ((x) != (y)) \ + return false; \ + } while (0) + for (int i = 0; i < 8; i++) { + if (i < 4) { + // DPRAM + CellInfo *comb = lts->cells[(i << lc_idx_shift) | BEL_COMB]; + if (comb != nullptr && (comb->combInfo.flags & ArchCellInfo::COMB_LUTRAM)) { + if (found_global_dpram) { + CHECK_EQUAL(bool(comb->combInfo.flags & ArchCellInfo::COMB_RAM_WCKINV), global_clkinv); + CHECK_EQUAL(bool(comb->combInfo.flags & ArchCellInfo::COMB_RAM_WREINV), global_lsrinv); + } else { + global_clkinv = bool(comb->combInfo.flags & ArchCellInfo::COMB_RAM_WCKINV); + global_lsrinv = bool(comb->combInfo.flags & ArchCellInfo::COMB_RAM_WREINV); + found_global_dpram = true; + } + } + } + // FF + CellInfo *ff = lts->cells[(i << lc_idx_shift) | BEL_FF]; + if (ff != nullptr) { + if (found_global_dpram) { + CHECK_EQUAL(bool(ff->ffInfo.flags & ArchCellInfo::FF_CLKINV), global_clkinv); + CHECK_EQUAL(bool(ff->ffInfo.flags & ArchCellInfo::FF_LSRINV), global_lsrinv); + } + if (found_global_ff) { + CHECK_EQUAL(ff->ffInfo.clk_sig, clk_sig); + CHECK_EQUAL(ff->ffInfo.lsr_sig, lsr_sig); + CHECK_EQUAL(bool(ff->ffInfo.flags & ArchCellInfo::FF_CLKINV), global_clkinv); + CHECK_EQUAL(bool(ff->ffInfo.flags & ArchCellInfo::FF_LSRINV), global_lsrinv); + CHECK_EQUAL(bool(ff->ffInfo.flags & ArchCellInfo::FF_ASYNC), global_async); + + } else { + clk_sig = ff->ffInfo.clk_sig; + lsr_sig = ff->ffInfo.lsr_sig; + global_clkinv = bool(ff->ffInfo.flags & ArchCellInfo::FF_CLKINV); + global_lsrinv = bool(ff->ffInfo.flags & ArchCellInfo::FF_LSRINV); + global_async = bool(ff->ffInfo.flags & ArchCellInfo::FF_ASYNC); + found_global_ff = true; + } + } + } +#undef CHECK_EQUAL + lts->tile_valid = true; + } else { + if (!lts->tile_valid) + return false; + } + + return true; +} + +bool Arch::isBelLocationValid(BelId bel, bool explain_invalid) const +{ + IdString bel_type = getBelType(bel); + if (bel_type.in(id_TRELLIS_COMB, id_TRELLIS_FF, id_TRELLIS_RAMW)) { + return slices_compatible(tile_status.at(tile_index(bel)).lts); + } + return true; +} + NEXTPNR_NAMESPACE_END