diff options
author | gatecat <gatecat@ds0.me> | 2022-03-31 11:17:57 +0100 |
---|---|---|
committer | gatecat <gatecat@ds0.me> | 2022-04-07 18:02:36 +0100 |
commit | efb58711b0dfcdb8080f63bd64d3f9d9fafd2637 (patch) | |
tree | a2b876f5cacc69125bdb2fbdc171517c6fb969c9 | |
parent | c4e47ba1a85d840c31d4be5c3f2c032664abd814 (diff) | |
download | nextpnr-efb58711b0dfcdb8080f63bd64d3f9d9fafd2637.tar.gz nextpnr-efb58711b0dfcdb8080f63bd64d3f9d9fafd2637.tar.bz2 nextpnr-efb58711b0dfcdb8080f63bd64d3f9d9fafd2637.zip |
ecp5: Split the SLICE bel into separate LUT/FF/RAMW bels
-rw-r--r-- | .cirrus/Dockerfile.ubuntu20.04 | 2 | ||||
-rw-r--r-- | ecp5/arch.cc | 147 | ||||
-rw-r--r-- | ecp5/arch.h | 107 | ||||
-rw-r--r-- | ecp5/arch_place.cc | 237 | ||||
-rw-r--r-- | ecp5/archdefs.h | 41 | ||||
-rw-r--r-- | ecp5/bitstream.cc | 115 | ||||
-rw-r--r-- | ecp5/cells.cc | 318 | ||||
-rw-r--r-- | ecp5/cells.h | 11 | ||||
-rw-r--r-- | ecp5/constids.inc | 10 | ||||
-rw-r--r-- | ecp5/gfx.cc | 26 | ||||
-rw-r--r-- | ecp5/globals.cc | 8 | ||||
-rw-r--r-- | ecp5/pack.cc | 1401 | ||||
-rwxr-xr-x | ecp5/trellis_import.py | 63 |
13 files changed, 1137 insertions, 1349 deletions
diff --git a/.cirrus/Dockerfile.ubuntu20.04 b/.cirrus/Dockerfile.ubuntu20.04 index 6561ed59..35ea18bd 100644 --- a/.cirrus/Dockerfile.ubuntu20.04 +++ b/.cirrus/Dockerfile.ubuntu20.04 @@ -48,7 +48,7 @@ RUN set -e -x ;\ cd /usr/local/src ;\ git clone --recursive https://github.com/YosysHQ/prjtrellis.git ;\ cd prjtrellis ;\ - git reset --hard 7239331d5463321d4864164f320beef67310f1e5 ;\ + git reset --hard 26f917d6052e084df30211ae3a78c8a165121e09 ;\ cd libtrellis ;\ cmake . ;\ make -j $(nproc) ;\ diff --git a/ecp5/arch.cc b/ecp5/arch.cc index 3d20badb..50993e2b 100644 --- a/ecp5/arch.cc +++ b/ecp5/arch.cc @@ -103,7 +103,19 @@ Arch::Arch(ArchArgs args) : args(args) if (!package_info) log_error("Unsupported package '%s' for '%s'.\n", args.package.c_str(), getChipName().c_str()); - bel_to_cell.resize(chip_info->height * chip_info->width * max_loc_bels, nullptr); + 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->locations[chip_info->location_type[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(); @@ -545,20 +557,24 @@ ArcBounds Arch::getRouteBoundingBox(WireId src, WireId dst) const delay_t Arch::predictDelay(BelId src_bel, IdString src_pin, BelId dst_bel, IdString dst_pin) const { - if ((src_pin == id_FCO && dst_pin == id_FCI) || dst_pin == id_FXA || dst_pin == id_FXB) + if ((src_pin == id_FCO && dst_pin == id_FCI) || dst_pin == id_FXA || dst_pin == id_FXB || + (src_pin == id_F && dst_pin == id_DI)) return 0; auto driver_loc = getBelLocation(src_bel); auto sink_loc = getBelLocation(dst_bel); // Encourage use of direct interconnect + // exact LUT input doesn't matter as they can be permuted by the router... if (driver_loc.x == sink_loc.x && driver_loc.y == sink_loc.y) { - if ((dst_pin == id_A0 || dst_pin == id_A1) && (src_pin == id_F1) && (driver_loc.z == 2 || driver_loc.z == 3)) - return 0; - if ((dst_pin == id_B0 || dst_pin == id_B1) && (src_pin == id_F1) && (driver_loc.z == 0 || driver_loc.z == 1)) - return 0; - if ((dst_pin == id_C0 || dst_pin == id_C1) && (src_pin == id_F0) && (driver_loc.z == 2 || driver_loc.z == 3)) - return 0; - if ((dst_pin == id_D0 || dst_pin == id_D1) && (src_pin == id_F0) && (driver_loc.z == 0 || driver_loc.z == 1)) - return 0; + if (dst_pin.in(id_A, id_B, id_C, id_D) && src_pin == id_Q) { + int lut = (sink_loc.z >> lc_idx_shift), ff = (driver_loc.z >> lc_idx_shift); + if (lut == ff) + return 0; + } + if (dst_pin.in(id_A, id_B, id_C, id_D) && src_pin == id_F) { + int l0 = (driver_loc.z >> lc_idx_shift); + if (l0 != 1 && l0 != 6) + return 0; + } } int dx = abs(driver_loc.x - sink_loc.x), dy = abs(driver_loc.y - sink_loc.y); @@ -597,6 +613,14 @@ bool Arch::place() cfg.cellGroups.back().insert(id_MULT18X18D); cfg.cellGroups.back().insert(id_ALU54B); + cfg.cellGroups.emplace_back(); + cfg.cellGroups.back().insert(id_TRELLIS_COMB); + cfg.cellGroups.back().insert(id_TRELLIS_FF); + cfg.cellGroups.back().insert(id_TRELLIS_RAMW); + cfg.placeAllAtOnce = true; + + cfg.beta = 0.75; + if (!placer_heap(getCtx(), cfg)) return false; } else if (placer == "sa") { @@ -605,7 +629,6 @@ bool Arch::place() } else { log_error("ECP5 architecture does not support placer '%s'\n", placer.c_str()); } - permute_luts(); // In out-of-context mode, create a locked macro if (bool_or_default(settings, id("arch.ooc"))) @@ -710,7 +733,7 @@ DecalXY Arch::getBelDecal(BelId bel) const decalxy.decal.type = DecalId::TYPE_BEL; decalxy.decal.location = bel.location; decalxy.decal.z = bel.index; - decalxy.decal.active = (bel_to_cell.at(get_bel_flat_index(bel)) != nullptr); + decalxy.decal.active = getBoundBelCell(bel) != nullptr; return decalxy; } @@ -791,14 +814,19 @@ void Arch::get_setuphold_from_tmg_db(IdString tctype, IdString clock, IdString p bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayQuad &delay) const { // Data for -8 grade - if (cell->type == id_TRELLIS_SLICE) { - bool has_carry = cell->sliceInfo.is_carry; - if (fromPort == id_A0 || fromPort == id_B0 || fromPort == id_C0 || fromPort == id_D0 || fromPort == id_A1 || - fromPort == id_B1 || fromPort == id_C1 || fromPort == id_D1 || fromPort == id_M0 || fromPort == id_M1 || - fromPort == id_FXA || fromPort == id_FXB || fromPort == id_FCI) { - return get_delay_from_tmg_db(has_carry ? id_SCCU2C : id_SLOGICB, fromPort, toPort, delay); - } - + if (cell->type == id_TRELLIS_COMB) { + bool has_carry = cell->combInfo.flags & ArchCellInfo::COMB_CARRY; + IdString tmg_type = has_carry ? (((cell->constr_z >> Arch::lc_idx_shift) % 2) ? id_TRELLIS_COMB_CARRY1 + : id_TRELLIS_COMB_CARRY0) + : id_TRELLIS_COMB; + if (fromPort == id_A || fromPort == id_B || fromPort == id_C || fromPort == id_D || fromPort == id_M || + fromPort == id_F1 || fromPort == id_FXA || fromPort == id_FXB || fromPort == id_FCI) + return get_delay_from_tmg_db(tmg_type, fromPort, toPort, delay); + else + return false; + } else if (cell->type == id_TRELLIS_FF) { + return false; + } else if (cell->type == id_TRELLIS_RAMW) { if ((fromPort == id_A0 && toPort == id_WADO3) || (fromPort == id_A1 && toPort == id_WDO1) || (fromPort == id_B0 && toPort == id_WADO1) || (fromPort == id_B1 && toPort == id_WDO3) || (fromPort == id_C0 && toPort == id_WADO2) || (fromPort == id_C1 && toPort == id_WDO0) || @@ -841,45 +869,46 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, in { auto disconnected = [cell](IdString p) { return !cell->ports.count(p) || cell->ports.at(p).net == nullptr; }; clockInfoCount = 0; - if (cell->type == id_TRELLIS_SLICE) { - int sd0 = cell->sliceInfo.sd0, sd1 = cell->sliceInfo.sd1; - if (port == id_CLK || port == id_WCK) + if (cell->type == id_TRELLIS_COMB) { + if (port == id_WCK) return TMG_CLOCK_INPUT; - if (port == id_A0 || port == id_A1 || port == id_B0 || port == id_B1 || port == id_C0 || port == id_C1 || - port == id_D0 || port == id_D1 || port == id_FCI || port == id_FXA || port == id_FXB) + if (port == id_A || port == id_B || port == id_C || port == id_D || port == id_FCI || port == id_FXA || + port == id_FXB || port == id_F1) return TMG_COMB_INPUT; - if (port == id_F0 && disconnected(id_A0) && disconnected(id_B0) && disconnected(id_C0) && disconnected(id_D0) && + if (port == id_F && disconnected(id_A) && disconnected(id_B) && disconnected(id_C) && disconnected(id_D) && disconnected(id_FCI)) return TMG_IGNORE; // LUT with no inputs is a constant - if (port == id_F1 && disconnected(id_A1) && disconnected(id_B1) && disconnected(id_C1) && disconnected(id_D1) && - disconnected(id_FCI)) - return TMG_IGNORE; // LUT with no inputs is a constant - - if (port == id_F0 || port == id_F1 || port == id_FCO || port == id_OFX0 || port == id_OFX1) + if (port == id_F || port == id_FCO || port == id_OFX) return TMG_COMB_OUTPUT; - if (port == id_DI0 || port == id_DI1 || port == id_CE || port == id_LSR || (sd0 == 0 && port == id_M0) || - (sd1 == 0 && port == id_M1)) { + if (port == id_M) + return TMG_COMB_INPUT; + if (port == id_WD || port == id_WAD0 || port == id_WAD1 || port == id_WAD2 || port == id_WAD3 || + port == id_WRE) { clockInfoCount = 1; return TMG_REGISTER_INPUT; } - if (port == id_M0 || port == id_M1) - return TMG_COMB_INPUT; - if (port == id_Q0 || port == id_Q1) { + return TMG_IGNORE; + } else if (cell->type == id_TRELLIS_FF) { + bool using_m = (cell->ffInfo.flags & ArchCellInfo::FF_M_USED); + if (port == id_CLK) + return TMG_CLOCK_INPUT; + if (port == id_DI || (using_m && (port == id_M)) || port == id_CE || port == id_LSR) { + clockInfoCount = 1; + return TMG_REGISTER_INPUT; + } + if (port == id_Q) { clockInfoCount = 1; return TMG_REGISTER_OUTPUT; } - + return TMG_IGNORE; + } else if (cell->type == id_TRELLIS_RAMW) { + if (port == id_A0 || port == id_A1 || port == id_B0 || port == id_B1 || port == id_C0 || port == id_C1 || + port == id_D0 || port == id_D1) + return TMG_COMB_INPUT; if (port == id_WDO0 || port == id_WDO1 || port == id_WDO2 || port == id_WDO3 || port == id_WADO0 || port == id_WADO1 || port == id_WADO2 || port == id_WADO3) return TMG_COMB_OUTPUT; - - if (port == id_WD0 || port == id_WD1 || port == id_WAD0 || port == id_WAD1 || port == id_WAD2 || - port == id_WAD3 || port == id_WRE) { - clockInfoCount = 1; - return TMG_REGISTER_INPUT; - } - - NPNR_ASSERT_FALSE_STR("no timing type for slice port '" + port.str(this) + "'"); + return TMG_IGNORE; } else if (cell->type == id_TRELLIS_IO) { if (port == id_T || port == id_I) return TMG_ENDPOINT; @@ -1024,21 +1053,29 @@ TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port info.setup = DelayPair(0); info.hold = DelayPair(0); info.clockToQ = DelayQuad(0); - if (cell->type == id_TRELLIS_SLICE) { - int sd0 = cell->sliceInfo.sd0, sd1 = cell->sliceInfo.sd1; - if (port == id_WD0 || port == id_WD1 || port == id_WAD0 || port == id_WAD1 || port == id_WAD2 || - port == id_WAD3 || port == id_WRE) { - info.edge = RISING_EDGE; + if (cell->type == id_TRELLIS_COMB) { + if (port == id_WD || port == id_WAD0 || port == id_WAD1 || port == id_WAD2 || port == id_WAD3 || + port == id_WRE) { + if (port == id_WD) + port = id_WD0; + info.edge = (cell->combInfo.flags & ArchCellInfo::COMB_RAM_WCKINV) ? FALLING_EDGE : RISING_EDGE; info.clock_port = id_WCK; get_setuphold_from_tmg_db(id_SDPRAME, id_WCK, port, info.setup, info.hold); - } else if (port == id_DI0 || port == id_DI1 || port == id_CE || port == id_LSR || (sd0 == 0 && port == id_M0) || - (sd1 == 0 && port == id_M1)) { - info.edge = cell->sliceInfo.clkmux == id_INV ? FALLING_EDGE : RISING_EDGE; + } + } else if (cell->type == id_TRELLIS_FF) { + bool using_m = (cell->ffInfo.flags & ArchCellInfo::FF_M_USED); + if (port == id_DI || port == id_CE || port == id_LSR || (using_m && port == id_M)) { + if (port == id_DI) + port = id_DI0; + if (port == id_M) + port = id_M0; + info.edge = (cell->ffInfo.flags & ArchCellInfo::FF_CLKINV) ? FALLING_EDGE : RISING_EDGE; info.clock_port = id_CLK; get_setuphold_from_tmg_db(id_SLOGICB, id_CLK, port, info.setup, info.hold); - } else { - info.edge = cell->sliceInfo.clkmux == id_INV ? FALLING_EDGE : RISING_EDGE; + NPNR_ASSERT(port == id_Q); + port = id_Q0; + info.edge = (cell->ffInfo.flags & ArchCellInfo::FF_CLKINV) ? FALLING_EDGE : RISING_EDGE; info.clock_port = id_CLK; bool is_path = get_delay_from_tmg_db(id_SLOGICB, id_CLK, port, info.clockToQ); NPNR_ASSERT(is_path); diff --git a/ecp5/arch.h b/ecp5/arch.h index c1bed2b3..3d95fc9b 100644 --- a/ecp5/arch.h +++ b/ecp5/arch.h @@ -452,7 +452,6 @@ struct Arch : BaseArch<ArchRanges> mutable dict<IdStringList, PipId> pip_by_name; - std::vector<CellInfo *> bel_to_cell; enum class LutPermRule { NONE, @@ -462,6 +461,39 @@ struct Arch : BaseArch<ArchRanges> std::vector<LutPermRule> lutperm_allowed; bool disable_router_lutperm = false; + // For fast, incremental validity checking of split SLICE + + // BEL z-position lookup, x-ored with (index in tile) << 2 + 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<CellInfo *, 8 * (1 << lc_idx_shift)> cells; + }; + + struct TileStatus + { + std::vector<CellInfo *> boundcells; + LogicTileStatus *lts = nullptr; + // TODO: use similar mechanism for DSP legality checking + ~TileStatus() { delete lts; } + }; + // faster replacements for base_pip2net, base_wire2net // indexed by get_pip_vecidx() std::vector<NetInfo *> pip2net; @@ -492,7 +524,7 @@ struct Arch : BaseArch<ArchRanges> // ------------------------------------------------- - static const int max_loc_bels = 20; + static const int max_loc_bels = 32; int getGridDimX() const override { return chip_info->width; }; int getGridDimY() const override { return chip_info->height; }; @@ -509,6 +541,11 @@ struct Arch : BaseArch<ArchRanges> return &(chip_info->locations[chip_info->location_type[id.location.y * chip_info->width + id.location.x]]); } + template <typename Id> inline int tile_index(Id id) const + { + return id.location.y * chip_info->width + id.location.x; + } + IdStringList getBelName(BelId bel) const override { NPNR_ASSERT(bel != BelId()); @@ -519,41 +556,57 @@ struct Arch : BaseArch<ArchRanges> uint32_t getBelChecksum(BelId bel) const override { return bel.index; } - int get_bel_flat_index(BelId bel) const - { - return (bel.location.y * chip_info->width + bel.location.x) * max_loc_bels + bel.index; - } - int get_slice_index(int x, int y, int slice) const { NPNR_ASSERT(slice >= 0 && slice < 4); return (y * chip_info->width + x) * 4 + slice; } + void update_bel(BelId bel, CellInfo *old_cell, CellInfo *new_cell) + { + CellInfo *act_cell = (old_cell == nullptr) ? new_cell : old_cell; + if (act_cell->type == id_TRELLIS_FF || act_cell->type == id_TRELLIS_COMB || act_cell->type == id_TRELLIS_RAMW) { + LogicTileStatus *lts = tile_status.at(tile_index(bel)).lts; + NPNR_ASSERT(lts != nullptr); + int z = loc_info(bel)->bel_data[bel.index].z; + lts->slices[(z >> lc_idx_shift) / 2].dirty = true; + if (act_cell->type == id_TRELLIS_FF) + lts->tile_dirty = true; // because FF CLK/LSR signals are tile-wide + if (act_cell->type == id_TRELLIS_COMB && (act_cell->combInfo.flags & ArchCellInfo::COMB_LUTRAM)) + lts->tile_dirty = true; // because RAM shares CLK/LSR signals with FFs + lts->cells[z] = new_cell; + } + } + void bindBel(BelId bel, CellInfo *cell, PlaceStrength strength) override { NPNR_ASSERT(bel != BelId()); - int idx = get_bel_flat_index(bel); - NPNR_ASSERT(bel_to_cell.at(idx) == nullptr); - bel_to_cell[idx] = cell; + auto &slot = tile_status.at(tile_index(bel)).boundcells.at(bel.index); + NPNR_ASSERT(slot == nullptr); + slot = cell; cell->bel = bel; cell->belStrength = strength; - if (getBelType(bel) == id_TRELLIS_SLICE) { - lutperm_allowed.at(get_slice_index(bel.location.x, bel.location.y, getBelLocation(bel).z)) = - (cell->sliceInfo.is_memory ? LutPermRule::NONE - : (cell->sliceInfo.is_carry ? LutPermRule::CARRY : LutPermRule::ALL)); + if (getBelType(bel) == id_TRELLIS_COMB) { + int flags = cell->combInfo.flags; + lutperm_allowed.at( + get_slice_index(bel.location.x, bel.location.y, (getBelLocation(bel).z >> lc_idx_shift) / 2)) = + (((flags & ArchCellInfo::COMB_LUTRAM) || (flags & ArchCellInfo::COMB_RAMW_BLOCK)) + ? LutPermRule::NONE + : ((flags & ArchCellInfo::COMB_CARRY) ? LutPermRule::CARRY : LutPermRule::ALL)); } + update_bel(bel, nullptr, cell); refreshUiBel(bel); } void unbindBel(BelId bel) override { NPNR_ASSERT(bel != BelId()); - int idx = get_bel_flat_index(bel); - NPNR_ASSERT(bel_to_cell.at(idx) != nullptr); - bel_to_cell[idx]->bel = BelId(); - bel_to_cell[idx]->belStrength = STRENGTH_NONE; - bel_to_cell[idx] = nullptr; + auto &slot = tile_status.at(tile_index(bel)).boundcells.at(bel.index); + NPNR_ASSERT(slot != nullptr); + update_bel(bel, slot, nullptr); + slot->bel = BelId(); + slot->belStrength = STRENGTH_NONE; + slot = nullptr; refreshUiBel(bel); } @@ -574,19 +627,22 @@ struct Arch : BaseArch<ArchRanges> bool checkBelAvail(BelId bel) const override { NPNR_ASSERT(bel != BelId()); - return bel_to_cell[get_bel_flat_index(bel)] == nullptr; + const CellInfo *slot = tile_status.at(tile_index(bel)).boundcells.at(bel.index); + return slot == nullptr; } CellInfo *getBoundBelCell(BelId bel) const override { NPNR_ASSERT(bel != BelId()); - return bel_to_cell[get_bel_flat_index(bel)]; + CellInfo *slot = tile_status.at(tile_index(bel)).boundcells.at(bel.index); + return slot; } CellInfo *getConflictingBelCell(BelId bel) const override { NPNR_ASSERT(bel != BelId()); - return bel_to_cell[get_bel_flat_index(bel)]; + CellInfo *slot = tile_status.at(tile_index(bel)).boundcells.at(bel.index); + return slot; } BelRange getBels() const override @@ -957,12 +1013,11 @@ struct Arch : BaseArch<ArchRanges> bool isBelLocationValid(BelId bel) const override; // Helper function for above - bool slices_compatible(const std::vector<const CellInfo *> &cells) const; + bool slices_compatible(LogicTileStatus *lts) const; + void assign_arch_info_for_cell(CellInfo *ci); void assignArchInfo() override; - void permute_luts(); - std::vector<std::pair<std::string, std::string>> get_tiles_at_loc(int row, int col); std::string get_tile_by_type_loc(int row, int col, std::string type) const { @@ -1028,6 +1083,8 @@ struct Arch : BaseArch<ArchRanges> std::vector<IdString> cell_types; std::vector<BelBucketId> buckets; + + mutable std::vector<TileStatus> tile_status; }; NEXTPNR_NAMESPACE_END diff --git a/ecp5/arch_place.cc b/ecp5/arch_place.cc index b1849ee6..afe9aca0 100644 --- a/ecp5/arch_place.cc +++ b/ecp5/arch_place.cc @@ -33,53 +33,156 @@ inline NetInfo *port_or_nullptr(const CellInfo *cell, IdString name) return found->second.net; } -bool Arch::slices_compatible(const std::vector<const CellInfo *> &cells) const +bool Arch::slices_compatible(LogicTileStatus *lts) const { - // TODO: allow different LSR/CLK and MUX/SRMODE settings once - // routing details are worked out - IdString clk_sig, lsr_sig; - IdString CLKMUX, LSRMUX, SRMODE; - bool first = true; - for (auto cell : cells) { - if (cell->sliceInfo.using_dff) { - if (first) { - clk_sig = cell->sliceInfo.clk_sig; - lsr_sig = cell->sliceInfo.lsr_sig; - CLKMUX = cell->sliceInfo.clkmux; - LSRMUX = cell->sliceInfo.lsrmux; - SRMODE = cell->sliceInfo.srmode; - } else { - if (cell->sliceInfo.clk_sig != clk_sig) - return false; - if (cell->sliceInfo.lsr_sig != lsr_sig) - return false; - if (cell->sliceInfo.clkmux != CLKMUX) + 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 (cell->sliceInfo.lsrmux != LSRMUX) + 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 (cell->sliceInfo.srmode != SRMODE) + 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; + } } - first = false; } +#undef CHECK_EQUAL + lts->tile_valid = true; + } else { + if (!lts->tile_valid) + return false; } + return true; } bool Arch::isBelLocationValid(BelId bel) const { - if (getBelType(bel) == id_TRELLIS_SLICE) { - std::vector<const CellInfo *> bel_cells; - Loc bel_loc = getBelLocation(bel); - for (auto bel_other : getBelsByTile(bel_loc.x, bel_loc.y)) { - CellInfo *cell_other = getBoundBelCell(bel_other); - if (cell_other != nullptr) { - bel_cells.push_back(cell_other); - } - } - if (getBoundBelCell(bel) != nullptr && getBoundBelCell(bel)->sliceInfo.has_l6mux && ((bel_loc.z % 2) == 1)) - return false; - return slices_compatible(bel_cells); + 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); } else { CellInfo *cell = getBoundBelCell(bel); if (cell == nullptr) { @@ -93,70 +196,6 @@ bool Arch::isBelLocationValid(BelId bel) const } } -void Arch::permute_luts() -{ - TimingAnalyser tmg(getCtx()); - tmg.setup(); - - auto proc_lut = [&](CellInfo *ci, int lut) { - std::vector<IdString> port_names; - for (int i = 0; i < 4; i++) - port_names.push_back(id(std::string("ABCD").substr(i, 1) + std::to_string(lut))); - - std::vector<std::pair<float, int>> inputs; - std::vector<NetInfo *> orig_nets; - - for (int i = 0; i < 4; i++) { - if (!ci->ports.count(port_names.at(i))) { - ci->ports[port_names.at(i)].name = port_names.at(i); - ci->ports[port_names.at(i)].type = PORT_IN; - } - auto &port = ci->ports.at(port_names.at(i)); - float crit = (port.net == nullptr) ? 0 : tmg.get_criticality(CellPortKey(ci->name, port_names.at(i))); - orig_nets.push_back(port.net); - inputs.emplace_back(crit, i); - } - // Least critical first (A input is slowest) - - // Avoid permuting locked LUTs (e.g. from an OOC submodule) - if (ci->belStrength <= STRENGTH_STRONG) - std::sort(inputs.begin(), inputs.end()); - for (int i = 0; i < 4; i++) { - IdString p = port_names.at(i); - // log_info("%s %s %f\n", p.c_str(ctx), port_names.at(inputs.at(i).second).c_str(ctx), inputs.at(i).first); - ci->disconnectPort(p); - ci->ports.at(p).net = nullptr; - if (orig_nets.at(inputs.at(i).second) != nullptr) { - ci->connectPort(p, orig_nets.at(inputs.at(i).second)); - ci->params[id(p.str(this) + "MUX")] = p.str(this); - } else { - ci->params[id(p.str(this) + "MUX")] = std::string("1"); - } - } - // Rewrite function - int old_init = int_or_default(ci->params, id("LUT" + std::to_string(lut) + "_INITVAL"), 0); - int new_init = 0; - for (int i = 0; i < 16; i++) { - int old_index = 0; - for (int k = 0; k < 4; k++) { - if (i & (1 << k)) - old_index |= (1 << inputs.at(k).second); - } - if (old_init & (1 << old_index)) - new_init |= (1 << i); - } - ci->params[id("LUT" + std::to_string(lut) + "_INITVAL")] = Property(new_init, 16); - }; - - for (auto &cell : cells) { - CellInfo *ci = cell.second.get(); - if (ci->type == id_TRELLIS_SLICE && str_or_default(ci->params, id_MODE, "LOGIC") == "LOGIC") { - proc_lut(ci, 0); - proc_lut(ci, 1); - } - } -} - void Arch::setup_wire_locations() { wire_loc_overrides.clear(); diff --git a/ecp5/archdefs.h b/ecp5/archdefs.h index dd260a3e..b7d892c5 100644 --- a/ecp5/archdefs.h +++ b/ecp5/archdefs.h @@ -156,19 +156,46 @@ struct ArchNetInfo typedef IdString ClusterId; +struct CellInfo; 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 { - bool using_dff; - bool has_l6mux; - bool is_carry; - bool is_memory; - IdString clk_sig, lsr_sig, clkmux, lsrmux, srmode; - int sd0, sd1; - } sliceInfo; + uint8_t flags; + IdString clk_sig, lsr_sig, ce_sig, di_sig; + } ffInfo; struct { bool is_pdp; diff --git a/ecp5/bitstream.cc b/ecp5/bitstream.cc index 40d843b9..3c01fe71 100644 --- a/ecp5/bitstream.cc +++ b/ecp5/bitstream.cc @@ -524,10 +524,10 @@ static void set_pip(Context *ctx, ChipConfig &cc, PipId pip) cc.tiles[tile].add_arc(sink, source); } -static unsigned permute_lut(Context *ctx, CellInfo *cell, pool<IdString> &used_phys_pins, int k, unsigned orig_init) +static unsigned permute_lut(Context *ctx, CellInfo *cell, pool<IdString> &used_phys_pins, unsigned orig_init) { std::array<std::vector<unsigned>, 4> phys_to_log; - const std::array<IdString, 4> ports{k ? id_A1 : id_A0, k ? id_B1 : id_B0, k ? id_C1 : id_C0, k ? id_D1 : id_D0}; + const std::array<IdString, 4> ports{id_A, id_B, id_C, id_D}; for (unsigned i = 0; i < 4; i++) { WireId pin_wire = ctx->getBelPinWire(cell->bel, ports[i]); for (PipId pip : ctx->getPipsUphill(pin_wire)) { @@ -547,7 +547,7 @@ static unsigned permute_lut(Context *ctx, CellInfo *cell, pool<IdString> &used_p for (unsigned i = 0; i < 4; i++) if (!phys_to_log.at(i).empty()) used_phys_pins.insert(ports.at(i)); - if (cell->sliceInfo.is_carry) { + if (cell->combInfo.flags & ArchCellInfo::COMB_CARRY) { // Insert dummy entries to ensure we keep the split between the two halves of a CCU2 for (unsigned i = 0; i < 4; i++) { if (!phys_to_log.at(i).empty()) @@ -840,77 +840,72 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex log_warning("found unplaced cell '%s' during bitstream gen\n", ci->name.c_str(ctx)); } BelId bel = ci->bel; - if (ci->type == id_TRELLIS_SLICE) { + if (ci->type == id_TRELLIS_COMB) { pool<IdString> used_phys_pins; std::string tname = ctx->get_tile_by_type_loc(bel.location.y, bel.location.x, "PLC2"); - std::string slice = ctx->loc_info(bel)->bel_data[bel.index].name.get(); - int lut0_init = int_or_default(ci->params, id_LUT0_INITVAL); - int lut1_init = int_or_default(ci->params, id_LUT1_INITVAL); - cc.tiles[tname].add_word(slice + ".K0.INIT", - int_to_bitvector(permute_lut(ctx, ci, used_phys_pins, 0, lut0_init), 16)); - cc.tiles[tname].add_word(slice + ".K1.INIT", - int_to_bitvector(permute_lut(ctx, ci, used_phys_pins, 1, lut1_init), 16)); - cc.tiles[tname].add_enum(slice + ".MODE", str_or_default(ci->params, id_MODE, "LOGIC")); - cc.tiles[tname].add_enum(slice + ".GSR", str_or_default(ci->params, id_GSR, "ENABLED")); - cc.tiles[tname].add_enum(slice + ".REG0.SD", intstr_or_default(ci->params, id_REG0_SD, "0")); - cc.tiles[tname].add_enum(slice + ".REG1.SD", intstr_or_default(ci->params, id_REG1_SD, "0")); - cc.tiles[tname].add_enum(slice + ".REG0.REGSET", str_or_default(ci->params, id_REG0_REGSET, "RESET")); - cc.tiles[tname].add_enum(slice + ".REG1.REGSET", str_or_default(ci->params, id_REG1_REGSET, "RESET")); - cc.tiles[tname].add_enum(slice + ".REG0.LSRMODE", str_or_default(ci->params, id_REG0_LSRMODE, "LSR")); - cc.tiles[tname].add_enum(slice + ".REG1.LSRMODE", str_or_default(ci->params, id_REG1_LSRMODE, "LSR")); - cc.tiles[tname].add_enum(slice + ".CEMUX", str_or_default(ci->params, id_CEMUX, "1")); - - if (ci->sliceInfo.using_dff) { - NetInfo *lsrnet = nullptr; - if (ci->ports.find(id_LSR) != ci->ports.end() && ci->ports.at(id_LSR).net != nullptr) - lsrnet = ci->ports.at(id_LSR).net; - if (ctx->getBoundWireNet(ctx->get_wire_by_loc_basename(bel.location, "LSR0")) == lsrnet) { - cc.tiles[tname].add_enum("LSR0.SRMODE", str_or_default(ci->params, id_SRMODE, "LSR_OVER_CE")); - cc.tiles[tname].add_enum("LSR0.LSRMUX", str_or_default(ci->params, id_LSRMUX, "LSR")); - } - if (ctx->getBoundWireNet(ctx->get_wire_by_loc_basename(bel.location, "LSR1")) == lsrnet) { - cc.tiles[tname].add_enum("LSR1.SRMODE", str_or_default(ci->params, id_SRMODE, "LSR_OVER_CE")); - cc.tiles[tname].add_enum("LSR1.LSRMUX", str_or_default(ci->params, id_LSRMUX, "LSR")); - } - - NetInfo *clknet = nullptr; - if (ci->ports.find(id_CLK) != ci->ports.end() && ci->ports.at(id_CLK).net != nullptr) - clknet = ci->ports.at(id_CLK).net; - if (ctx->getBoundWireNet(ctx->get_wire_by_loc_basename(bel.location, "CLK0")) == clknet) { - cc.tiles[tname].add_enum("CLK0.CLKMUX", str_or_default(ci->params, id_CLKMUX, "CLK")); - } - if (ctx->getBoundWireNet(ctx->get_wire_by_loc_basename(bel.location, "CLK1")) == clknet) { - cc.tiles[tname].add_enum("CLK1.CLKMUX", str_or_default(ci->params, id_CLKMUX, "CLK")); - } - } - - if (str_or_default(ci->params, id_MODE, "LOGIC") == "CCU2") { - cc.tiles[tname].add_enum(slice + ".CCU2.INJECT1_0", - str_or_default(ci->params, id_CCU2_INJECT1_0, "YES")); - cc.tiles[tname].add_enum(slice + ".CCU2.INJECT1_1", - str_or_default(ci->params, id_CCU2_INJECT1_1, "YES")); + int z = ctx->loc_info(bel)->bel_data[bel.index].z >> Arch::lc_idx_shift; + std::string slice = std::string("SLICE") + "ABCD"[z / 2]; + std::string lc = std::to_string(z % 2); + std::string mode = str_or_default(ci->params, id_MODE, "LOGIC"); + if (mode == "RAMW_BLOCK") + continue; + int lut_init = int_or_default(ci->params, id_INITVAL); + cc.tiles[tname].add_enum(slice + ".MODE", mode); + cc.tiles[tname].add_word(slice + ".K" + lc + ".INIT", + int_to_bitvector(permute_lut(ctx, ci, used_phys_pins, lut_init), 16)); + if (mode == "CCU2") { + cc.tiles[tname].add_enum(slice + ".CCU2.INJECT1_" + lc, + str_or_default(ci->params, id_CCU2_INJECT1, "YES")); } else { // Don't interfere with cascade mux wiring - cc.tiles[tname].add_enum(slice + ".CCU2.INJECT1_0", "_NONE_"); - cc.tiles[tname].add_enum(slice + ".CCU2.INJECT1_1", "_NONE_"); + cc.tiles[tname].add_enum(slice + ".CCU2.INJECT1_" + lc, "_NONE_"); } - - if (str_or_default(ci->params, id_MODE, "LOGIC") == "DPRAM" && slice == "SLICEA") { + if (mode == "DPRAM" && slice == "SLICEA" && lc == "0") { cc.tiles[tname].add_enum(slice + ".WREMUX", str_or_default(ci->params, id_WREMUX, "WRE")); - std::string wckmux = str_or_default(ci->params, id_WCKMUX, "WCK"); wckmux = (wckmux == "WCK") ? "CLK" : wckmux; cc.tiles[tname].add_enum("CLK1.CLKMUX", wckmux); } - - // Tie unused inputs high - for (auto input : {id_A0, id_B0, id_C0, id_D0, id_A1, id_B1, id_C1, id_D1}) { + for (auto input : {id_A, id_B, id_C, id_D}) { if (!used_phys_pins.count(input)) { - cc.tiles[tname].add_enum(slice + "." + input.str(ctx) + "MUX", "1"); + cc.tiles[tname].add_enum(slice + "." + input.str(ctx) + lc + "MUX", "1"); } } + } else if (ci->type == id_TRELLIS_FF) { + std::string tname = ctx->get_tile_by_type_loc(bel.location.y, bel.location.x, "PLC2"); + int z = ctx->loc_info(bel)->bel_data[bel.index].z >> Arch::lc_idx_shift; + std::string slice = std::string("SLICE") + "ABCD"[z / 2]; + std::string lc = std::to_string(z % 2); + + cc.tiles[tname].add_enum(slice + ".GSR", str_or_default(ci->params, id_GSR, "ENABLED")); + cc.tiles[tname].add_enum(slice + ".REG" + lc + ".SD", intstr_or_default(ci->params, id_SD, "0")); + cc.tiles[tname].add_enum(slice + ".REG" + lc + ".REGSET", str_or_default(ci->params, id_REGSET, "RESET")); + cc.tiles[tname].add_enum(slice + ".REG" + lc + ".LSRMODE", str_or_default(ci->params, id_LSRMODE, "LSR")); + + cc.tiles[tname].add_enum(slice + ".CEMUX", str_or_default(ci->params, id_CEMUX, "1")); + + NetInfo *lsrnet = ci->getPort(id_LSR); + if (ctx->getBoundWireNet(ctx->get_wire_by_loc_basename(bel.location, "LSR0")) == lsrnet) { + cc.tiles[tname].add_enum("LSR0.SRMODE", str_or_default(ci->params, id_SRMODE, "LSR_OVER_CE")); + cc.tiles[tname].add_enum("LSR0.LSRMUX", str_or_default(ci->params, id_LSRMUX, "LSR")); + } + if (ctx->getBoundWireNet(ctx->get_wire_by_loc_basename(bel.location, "LSR1")) == lsrnet) { + cc.tiles[tname].add_enum("LSR1.SRMODE", str_or_default(ci->params, id_SRMODE, "LSR_OVER_CE")); + cc.tiles[tname].add_enum("LSR1.LSRMUX", str_or_default(ci->params, id_LSRMUX, "LSR")); + } - // TODO: CLKMUX + NetInfo *clknet = ci->getPort(id_CLK); + if (ctx->getBoundWireNet(ctx->get_wire_by_loc_basename(bel.location, "CLK0")) == clknet) { + cc.tiles[tname].add_enum("CLK0.CLKMUX", str_or_default(ci->params, id_CLKMUX, "CLK")); + } + if (ctx->getBoundWireNet(ctx->get_wire_by_loc_basename(bel.location, "CLK1")) == clknet) { + cc.tiles[tname].add_enum("CLK1.CLKMUX", str_or_default(ci->params, id_CLKMUX, "CLK")); + } + } else if (ci->type == id_TRELLIS_RAMW) { + std::string tname = ctx->get_tile_by_type_loc(bel.location.y, bel.location.x, "PLC2"); + cc.tiles[tname].add_enum("SLICEC.MODE", "RAMW"); + cc.tiles[tname].add_word("SLICEC.K0.INIT", std::vector<bool>(16, false)); + cc.tiles[tname].add_word("SLICEC.K1.INIT", std::vector<bool>(16, false)); } else if (ci->type == id_TRELLIS_IO) { std::string pio = ctx->loc_info(bel)->bel_data[bel.index].name.get(); std::string iotype = str_or_default(ci->attrs, id_IO_TYPE, "LVCMOS33"); diff --git a/ecp5/cells.cc b/ecp5/cells.cc index 2c5f96d3..99f672a6 100644 --- a/ecp5/cells.cc +++ b/ecp5/cells.cc @@ -47,49 +47,28 @@ std::unique_ptr<CellInfo> create_ecp5_cell(Context *ctx, IdString type, std::str } }; - 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("DISABLED"); - 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("CLK"); - new_cell->params[id_LSRMUX] = std::string("LSR"); - new_cell->params[id_LUT0_INITVAL] = Property(0, 16); - new_cell->params[id_LUT1_INITVAL] = Property(0, 16); - new_cell->params[id_REG0_SD] = std::string("0"); - new_cell->params[id_REG1_SD] = std::string("0"); - new_cell->params[id_REG0_REGSET] = std::string("RESET"); - new_cell->params[id_REG1_REGSET] = std::string("RESET"); - new_cell->params[id_CCU2_INJECT1_0] = std::string("NO"); - new_cell->params[id_CCU2_INJECT1_1] = std::string("NO"); + 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_A0); - new_cell->addInput(id_B0); - new_cell->addInput(id_C0); - new_cell->addInput(id_D0); - - new_cell->addInput(id_A1); - new_cell->addInput(id_B1); - new_cell->addInput(id_C1); - new_cell->addInput(id_D1); + new_cell->addInput(id_A); + new_cell->addInput(id_B); + new_cell->addInput(id_C); + new_cell->addInput(id_D); - new_cell->addInput(id_M0); - new_cell->addInput(id_M1); + 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_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_WD); new_cell->addInput(id_WAD0); new_cell->addInput(id_WAD1); new_cell->addInput(id_WAD2); @@ -97,23 +76,15 @@ std::unique_ptr<CellInfo> create_ecp5_cell(Context *ctx, IdString type, std::str 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_F); 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); + 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"); @@ -200,122 +171,6 @@ std::unique_ptr<CellInfo> create_ecp5_cell(Context *ctx, IdString type, std::str return new_cell; } -static void set_param_safe(bool has_ff, CellInfo *lc, IdString name, const std::string &value) -{ - NPNR_ASSERT(!has_ff || lc->params.at(name) == value); - lc->params[name] = value; -} - -static void replace_port_safe(bool has_ff, CellInfo *ff, IdString ff_port, CellInfo *lc, IdString lc_port) -{ - if (has_ff) { - NPNR_ASSERT(lc->ports.at(lc_port).net == ff->ports.at(ff_port).net); - NetInfo *ffnet = ff->ports.at(ff_port).net; - if (ffnet != nullptr) - ffnet->users.remove(ff->ports.at(ff_port).user_idx); - } else { - ff->movePortTo(ff_port, lc, lc_port); - } -} - -void ff_to_slice(Context *ctx, CellInfo *ff, CellInfo *lc, int index, bool driven_by_lut) -{ - if (lc->hierpath == IdString()) - lc->hierpath = ff->hierpath; - bool has_ff = lc->ports.at(id_Q0).net != nullptr || lc->ports.at(id_Q1).net != nullptr; - std::string reg = "REG" + std::to_string(index); - set_param_safe(has_ff, lc, id_SRMODE, str_or_default(ff->params, id_SRMODE, "LSR_OVER_CE")); - set_param_safe(has_ff, lc, id_GSR, str_or_default(ff->params, id_GSR, "DISABLED")); - set_param_safe(has_ff, lc, id_CEMUX, str_or_default(ff->params, id_CEMUX, "1")); - set_param_safe(has_ff, lc, id_LSRMUX, str_or_default(ff->params, id_LSRMUX, "LSR")); - set_param_safe(has_ff, lc, id_CLKMUX, str_or_default(ff->params, id_CLKMUX, "CLK")); - - lc->params[ctx->id(reg + "_SD")] = std::string(driven_by_lut ? "1" : "0"); - lc->params[ctx->id(reg + "_REGSET")] = str_or_default(ff->params, id_REGSET, "RESET"); - lc->params[ctx->id(reg + "_LSRMODE")] = str_or_default(ff->params, id_LSRMODE, "LSR"); - replace_port_safe(has_ff, ff, id_CLK, lc, id_CLK); - if (ff->ports.find(id_LSR) != ff->ports.end()) - replace_port_safe(has_ff, ff, id_LSR, lc, id_LSR); - if (ff->ports.find(id_CE) != ff->ports.end()) - replace_port_safe(has_ff, ff, id_CE, lc, id_CE); - - ff->movePortTo(id_Q, lc, ctx->id("Q" + std::to_string(index))); - if (ff->getPort(id_M) != nullptr) { - // PRLD FFs that use both M and DI - NPNR_ASSERT(!driven_by_lut); - // As M is used; must route DI through a new LUT - lc->params[ctx->id(reg + "_SD")] = std::string("1"); - lc->params[ctx->id("LUT" + std::to_string(index) + "_INITVAL")] = Property(0xFF00, 16); - ff->movePortTo(id_DI, lc, ctx->id("D" + std::to_string(index))); - ff->movePortTo(id_M, lc, ctx->id("M" + std::to_string(index))); - lc->connectPorts(ctx->id("F" + std::to_string(index)), lc, ctx->id("DI" + std::to_string(index))); - } else { - if (driven_by_lut) { - ff->movePortTo(id_DI, lc, ctx->id("DI" + std::to_string(index))); - } else { - ff->movePortTo(id_DI, lc, ctx->id("M" + std::to_string(index))); - } - } -} - -void lut_to_slice(Context *ctx, CellInfo *lut, CellInfo *lc, int index) -{ - if (lc->hierpath == IdString()) - lc->hierpath = lut->hierpath; - lc->params[ctx->id("LUT" + std::to_string(index) + "_INITVAL")] = - get_or_default(lut->params, id_INIT, Property(0, 16)); - lut->movePortTo(id_A, lc, ctx->id("A" + std::to_string(index))); - lut->movePortTo(id_B, lc, ctx->id("B" + std::to_string(index))); - lut->movePortTo(id_C, lc, ctx->id("C" + std::to_string(index))); - lut->movePortTo(id_D, lc, ctx->id("D" + std::to_string(index))); - lut->movePortTo(id_Z, lc, ctx->id("F" + std::to_string(index))); -} - -void ccu2c_to_slice(Context *ctx, CellInfo *ccu, CellInfo *lc) -{ - if (lc->hierpath == IdString()) - lc->hierpath = ccu->hierpath; - lc->params[id_MODE] = std::string("CCU2"); - lc->params[id_LUT0_INITVAL] = get_or_default(ccu->params, id_INIT0, Property(0, 16)); - lc->params[id_LUT1_INITVAL] = get_or_default(ccu->params, id_INIT1, Property(0, 16)); - - lc->params[id_CCU2_INJECT1_0] = str_or_default(ccu->params, id_INJECT1_0, "YES"); - lc->params[id_CCU2_INJECT1_1] = str_or_default(ccu->params, id_INJECT1_1, "YES"); - - ccu->movePortTo(id_CIN, lc, id_FCI); - - ccu->movePortTo(id_A0, lc, id_A0); - ccu->movePortTo(id_B0, lc, id_B0); - ccu->movePortTo(id_C0, lc, id_C0); - ccu->movePortTo(id_D0, lc, id_D0); - - ccu->movePortTo(id_A1, lc, id_A1); - ccu->movePortTo(id_B1, lc, id_B1); - ccu->movePortTo(id_C1, lc, id_C1); - ccu->movePortTo(id_D1, lc, id_D1); - - ccu->movePortTo(id_S0, lc, id_F0); - ccu->movePortTo(id_S1, lc, id_F1); - - ccu->movePortTo(id_COUT, lc, id_FCO); -} - -void dram_to_ramw(Context *ctx, CellInfo *ram, CellInfo *lc) -{ - if (lc->hierpath == IdString()) - lc->hierpath = ram->hierpath; - lc->params[id_MODE] = std::string("RAMW"); - ram->movePortTo(ctx->id("WAD[0]"), lc, id_D0); - ram->movePortTo(ctx->id("WAD[1]"), lc, id_B0); - ram->movePortTo(ctx->id("WAD[2]"), lc, id_C0); - ram->movePortTo(ctx->id("WAD[3]"), lc, id_A0); - - ram->movePortTo(ctx->id("DI[0]"), lc, id_C1); - ram->movePortTo(ctx->id("DI[1]"), lc, id_A1); - ram->movePortTo(ctx->id("DI[2]"), lc, id_D1); - ram->movePortTo(ctx->id("DI[3]"), lc, id_B1); -} - 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)); @@ -333,16 +188,70 @@ static unsigned get_dram_init(const Context *ctx, const CellInfo *ram, int bit) return value; } -void dram_to_ram_slice(Context *ctx, CellInfo *ram, CellInfo *lc, CellInfo *ramw, int index) +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) { - if (lc->hierpath == IdString()) - lc->hierpath = ram->hierpath; - lc->params[id_MODE] = std::string("DPRAM"); - lc->params[id_WREMUX] = str_or_default(ram->params, id_WREMUX, "WRE"); - lc->params[id_WCKMUX] = str_or_default(ram->params, id_WCKMUX, "WCK"); + 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!"); + } - unsigned permuted_init0 = 0, permuted_init1 = 0; - unsigned init0 = get_dram_init(ctx, ram, index * 2), init1 = get_dram_init(ctx, ram, index * 2 + 1); + 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; @@ -354,58 +263,41 @@ void dram_to_ram_slice(Context *ctx, CellInfo *ram, CellInfo *lc, CellInfo *ramw permuted_addr |= 4; if (i & 8) permuted_addr |= 1; - if (init0 & (1 << permuted_addr)) - permuted_init0 |= (1 << i); - if (init1 & (1 << permuted_addr)) - permuted_init1 |= (1 << i); + if (init & (1 << permuted_addr)) + permuted_init |= (1 << i); } - lc->params[id_LUT0_INITVAL] = Property(permuted_init0, 16); - lc->params[id_LUT1_INITVAL] = Property(permuted_init1, 16); + comb->params[ctx->id("INITVAL")] = Property(permuted_init, 16); - if (ram->ports.count(ctx->id("RAD[0]"))) { - lc->connectPort(id_D0, ram->ports.at(ctx->id("RAD[0]")).net); - lc->connectPort(id_D1, ram->ports.at(ctx->id("RAD[0]")).net); - } - if (ram->ports.count(ctx->id("RAD[1]"))) { - lc->connectPort(id_B0, ram->ports.at(ctx->id("RAD[1]")).net); - lc->connectPort(id_B1, ram->ports.at(ctx->id("RAD[1]")).net); - } - if (ram->ports.count(ctx->id("RAD[2]"))) { - lc->connectPort(id_C0, ram->ports.at(ctx->id("RAD[2]")).net); - lc->connectPort(id_C1, ram->ports.at(ctx->id("RAD[2]")).net); - } - if (ram->ports.count(ctx->id("RAD[3]"))) { - lc->connectPort(id_A0, ram->ports.at(ctx->id("RAD[3]")).net); - lc->connectPort(id_A1, ram->ports.at(ctx->id("RAD[3]")).net); - } + if (ram->ports.count(ctx->id("RAD[0]"))) + comb->connectPort(id_D, ram->ports.at(ctx->id("RAD[0]")).net); - if (ram->ports.count(id_WRE)) - lc->connectPort(id_WRE, ram->ports.at(id_WRE).net); - if (ram->ports.count(id_WCK)) - lc->connectPort(id_WCK, ram->ports.at(id_WCK).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); - ramw->connectPorts(id_WADO0, lc, id_WAD0); - ramw->connectPorts(id_WADO1, lc, id_WAD1); - ramw->connectPorts(id_WADO2, lc, id_WAD2); - ramw->connectPorts(id_WADO3, lc, id_WAD3); + if (ram->ports.count(ctx->id("RAD[3]"))) + comb->connectPort(id_A, ram->ports.at(ctx->id("RAD[3]")).net); - if (index == 0) { - ramw->connectPorts(id_WDO0, lc, id_WD0); - ramw->connectPorts(id_WDO1, lc, id_WD1); + 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); - ram->movePortTo(ctx->id("DO[0]"), lc, id_F0); - ram->movePortTo(ctx->id("DO[1]"), lc, id_F1); + 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); - } else if (index == 1) { - ramw->connectPorts(id_WDO2, lc, id_WD0); - ramw->connectPorts(id_WDO3, lc, id_WD1); + 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); - ram->movePortTo(ctx->id("DO[2]"), lc, id_F0); - ram->movePortTo(ctx->id("DO[3]"), lc, id_F1); - } else { - NPNR_ASSERT_FALSE("bad DPRAM index"); - } + for (auto &attr : ram->attrs) + comb->attrs[attr.first] = attr.second; } void nxio_to_tr(Context *ctx, CellInfo *nxio, CellInfo *trio, std::vector<std::unique_ptr<CellInfo>> &created_cells, diff --git a/ecp5/cells.h b/ecp5/cells.h index 8f0a8cbf..185b19ce 100644 --- a/ecp5/cells.h +++ b/ecp5/cells.h @@ -37,8 +37,6 @@ inline bool is_ff(const BaseCtx *ctx, const CellInfo *cell) { return cell->type inline bool is_carry(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == id_CCU2C; } -inline bool is_lc(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == id_TRELLIS_SLICE; } - inline bool is_trellis_io(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == id_TRELLIS_IO; } inline bool is_dpram(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == id_TRELLIS_DPR16X4; } @@ -62,11 +60,10 @@ inline bool is_iologic_output_cell(const BaseCtx *ctx, const CellInfo *cell) (str_or_default(cell->attrs, id_ioff_dir, "") != "input")); } -void ff_to_slice(Context *ctx, CellInfo *ff, CellInfo *lc, int index, bool driven_by_lut); -void lut_to_slice(Context *ctx, CellInfo *lut, CellInfo *lc, int index); -void ccu2c_to_slice(Context *ctx, CellInfo *ccu, CellInfo *lc); -void dram_to_ramw(Context *ctx, CellInfo *ram, CellInfo *lc); -void dram_to_ram_slice(Context *ctx, CellInfo *ram, CellInfo *lc, CellInfo *ramw, int index); +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); // Convert a nextpnr IO buffer to a TRELLIS_IO void nxio_to_tr(Context *ctx, CellInfo *nxio, CellInfo *trio, std::vector<std::unique_ptr<CellInfo>> &created_cells, diff --git a/ecp5/constids.inc b/ecp5/constids.inc index 760e1623..c161f94f 100644 --- a/ecp5/constids.inc +++ b/ecp5/constids.inc @@ -1851,3 +1851,13 @@ X(placer) X(route) X(router) X(syn_useioff) + +X(TRELLIS_COMB) +X(TRELLIS_RAMW) +X(TRELLIS_COMB_CARRY0) +X(TRELLIS_COMB_CARRY1) + +X(WD) +X(OFX) +X(F) +X(CCU2_INJECT1) diff --git a/ecp5/gfx.cc b/ecp5/gfx.cc index 8b5015f8..fe206de0 100644 --- a/ecp5/gfx.cc +++ b/ecp5/gfx.cc @@ -23,11 +23,13 @@ NEXTPNR_NAMESPACE_BEGIN const float slice_x1 = 0.92; +const float slice_x2_comb = 0.927; +const float slice_x1_ff = 0.933; const float slice_x2 = 0.94; const float slice_x2_wide = 0.97; const float slice_y1 = 0.71; -const float slice_y2 = 0.745 + 0.0068; -const float slice_pitch = 0.0374 + 0.0068; +const float slice_y2 = 0.7275 + 0.0068 / 2; +const float slice_pitch = (0.0374 + 0.0068) / 2; const float io_cell_v_x1 = 0.76; const float io_cell_v_x2 = 0.95; @@ -54,11 +56,25 @@ void gfxTileBel(std::vector<GraphicElement> &g, int x, int y, int z, int w, int GraphicElement el; el.type = GraphicElement::TYPE_BOX; el.style = style; - if (bel_type == id_TRELLIS_SLICE) { + if (bel_type == id_TRELLIS_COMB) { el.x1 = x + slice_x1; + el.x2 = x + slice_x2_comb; + el.y1 = y + slice_y1 + (z >> Arch::lc_idx_shift) * slice_pitch; + el.y2 = y + slice_y2 + (z >> Arch::lc_idx_shift) * slice_pitch; + g.push_back(el); + + el.style = GraphicElement::STYLE_FRAME; + el.x1 = x + slice_x2_comb + 15 * wire_distance; + el.x2 = el.x1 + wire_distance; + el.y1 = y + slice_y2 - wire_distance * (TILE_WIRE_CLK3_SLICE - TILE_WIRE_DUMMY_D2 + 5 + (3 - z) * 26) + + 3 * slice_pitch - 0.0007f; + el.y2 = el.y1 + wire_distance * 5; + g.push_back(el); + } else if (bel_type == id_TRELLIS_FF) { + el.x1 = x + slice_x1_ff; el.x2 = x + slice_x2; - el.y1 = y + slice_y1 + (z)*slice_pitch; - el.y2 = y + slice_y2 + (z)*slice_pitch; + el.y1 = y + slice_y1 + (z >> Arch::lc_idx_shift) * slice_pitch; + el.y2 = y + slice_y2 + (z >> Arch::lc_idx_shift) * slice_pitch; g.push_back(el); el.style = GraphicElement::STYLE_FRAME; diff --git a/ecp5/globals.cc b/ecp5/globals.cc index 71188aa0..7123705a 100644 --- a/ecp5/globals.cc +++ b/ecp5/globals.cc @@ -53,7 +53,9 @@ class Ecp5GlobalRouter private: bool is_clock_port(const PortRef &user) { - if (user.cell->type == id_TRELLIS_SLICE && (user.port == id_CLK || user.port == id_WCK)) + if (user.cell->type == id_TRELLIS_FF && user.port == id_CLK) + return true; + if (user.cell->type == id_TRELLIS_COMB && user.port == id_WCK) return true; if (user.cell->type == id_DCUA && (user.port == id_CH0_FF_RXI_CLK || user.port == id_CH1_FF_RXI_CLK || user.port == id_CH0_FF_TXI_CLK || user.port == id_CH1_FF_TXI_CLK)) @@ -65,7 +67,9 @@ class Ecp5GlobalRouter bool is_logic_port(const PortRef &user) { - if (user.cell->type == id_TRELLIS_SLICE && user.port != id_CLK && user.port != id_WCK) + if (user.cell->type == id_TRELLIS_FF && user.port != id_CLK) + return true; + if (user.cell->type == id_TRELLIS_COMB && user.port != id_WCK) return true; return false; } diff --git a/ecp5/pack.cc b/ecp5/pack.cc index f65e992e..7aa9b4c4 100644 --- a/ecp5/pack.cc +++ b/ecp5/pack.cc @@ -56,22 +56,21 @@ class Ecp5Packer } // Print logic usage - int available_slices = 0; 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_SLICE) { - available_slices += 1; - total_luts += 2; - total_ffs += 2; + if (ctx->getBelType(bel) == id_TRELLIS_COMB) { + total_luts += 1; Loc l = ctx->getBelLocation(bel); - if (l.z == 0 || l.z == 1) - total_ramluts += 2; - if (l.z == 2) - total_ramwluts += 2; + 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) { @@ -101,292 +100,164 @@ class Ecp5Packer log_break(); } - // Find FFs associated with LUTs, or LUT expansion muxes - void find_lutff_pairs() + // Pack LUTs + void pack_luts() { - log_info("Finding LUTFF pairs...\n"); + log_info("Packing LUTs...\n"); for (auto &cell : ctx->cells) { CellInfo *ci = cell.second.get(); - if (is_lut(ctx, ci) || is_pfumx(ctx, ci) || is_l6mux(ctx, ci)) { - NetInfo *znet = ci->ports.at(id_Z).net; - if (znet != nullptr) { - CellInfo *ff = net_only_drives(ctx, znet, is_ff, id_DI, false); - // Can't combine preload FF with LUT due to conflict on M - if (ff != nullptr && ff->getPort(id_M) == nullptr) { - lutffPairs[ci->name] = ff->name; - fflutPairs[ff->name] = ci->name; - } - } - } - } - } - - // Check if a flipflop is available in a slice - bool is_ff_available(CellInfo *slice, int ff) - { - if (slice->getPort((ff == 1) ? id_Q1 : id_Q0) != nullptr) - return false; - if (slice->getPort((ff == 1) ? id_M1 : id_M0) != nullptr) - return false; - return true; - } - - // Check if a flipflop can be added to a slice - bool can_add_ff_to_slice(CellInfo *slice, CellInfo *ff) - { - std::string clkmux = str_or_default(ff->params, id_CLKMUX, "CLK"); - std::string lsrmux = str_or_default(ff->params, id_LSRMUX, "LSR"); - - bool has_dpram = str_or_default(slice->params, id_MODE, "LOGIC") == "DPRAM"; - if (has_dpram) { - std::string wckmux = str_or_default(slice->params, id_WCKMUX, "WCK"); - std::string wremux = str_or_default(slice->params, id_WREMUX, "WRE"); - if (wckmux != clkmux && !(wckmux == "WCK" && clkmux == "CLK")) - return false; - if (wremux != lsrmux && !(wremux == "WRE" && lsrmux == "LSR")) - return false; + if (is_lut(ctx, ci)) + lut_to_comb(ctx, ci); } - bool has_ff0 = slice->getPort(id_Q0) != nullptr; - bool has_ff1 = slice->getPort(id_Q1) != nullptr; - if (!has_ff0 && !has_ff1) - return true; - if (str_or_default(ff->params, id_GSR, "DISABLED") != str_or_default(slice->params, id_GSR, "DISABLED")) - return false; - if (str_or_default(ff->params, id_SRMODE, "LSR_OVER_CE") != - str_or_default(slice->params, id_SRMODE, "LSR_OVER_CE")) - return false; - if (str_or_default(ff->params, id_CEMUX, "1") != str_or_default(slice->params, id_CEMUX, "1")) - return false; - if (str_or_default(ff->params, id_LSRMUX, "LSR") != str_or_default(slice->params, id_LSRMUX, "LSR")) - return false; - if (str_or_default(ff->params, id_CLKMUX, "CLK") != str_or_default(slice->params, id_CLKMUX, "CLK")) - return false; - if (net_or_nullptr(ff, id_CLK) != net_or_nullptr(slice, id_CLK)) - return false; - if (net_or_nullptr(ff, id_CE) != net_or_nullptr(slice, id_CE)) - return false; - if (net_or_nullptr(ff, id_LSR) != net_or_nullptr(slice, id_LSR)) - return false; - return true; } - const NetInfo *net_or_nullptr(CellInfo *cell, IdString port) + // Gets the z-position of a cell in a macro + int get_macro_cell_z(const CellInfo *ci) { - auto fnd = cell->ports.find(port); - if (fnd == cell->ports.end()) - return nullptr; + 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 fnd->second.net; + return 0; } - // Return whether two FFs can be packed together in the same slice - bool can_pack_ffs(CellInfo *ff0, CellInfo *ff1) + // Gets the relative xy-position of a cell in a macro + std::pair<int, int> get_macro_cell_xy(const CellInfo *ci) { - if (str_or_default(ff0->params, id_GSR, "DISABLED") != str_or_default(ff1->params, id_GSR, "DISABLED")) - return false; - if (str_or_default(ff0->params, id_SRMODE, "LSR_OVER_CE") != - str_or_default(ff1->params, id_SRMODE, "LSR_OVER_CE")) - return false; - if (str_or_default(ff0->params, id_CEMUX, "1") != str_or_default(ff1->params, id_CEMUX, "1")) - return false; - if (str_or_default(ff0->params, id_LSRMUX, "LSR") != str_or_default(ff1->params, id_LSRMUX, "LSR")) - return false; - if (str_or_default(ff0->params, id_CLKMUX, "CLK") != str_or_default(ff1->params, id_CLKMUX, "CLK")) - return false; - if (net_or_nullptr(ff0, id_CLK) != net_or_nullptr(ff1, id_CLK)) - return false; - if (net_or_nullptr(ff0, id_CE) != net_or_nullptr(ff1, id_CE)) - return false; - if (net_or_nullptr(ff0, id_LSR) != net_or_nullptr(ff1, id_LSR)) - return false; - return true; + if (ci->cluster != ClusterId()) + return {ci->constr_x, ci->constr_y}; + else + return {0, 0}; } - // Return whether or not an FF can be added to a tile (pairing checks must also be done using the fn above) - bool can_add_ff_to_tile(const std::vector<CellInfo *> &tile_ffs, CellInfo *ff0) + // Relatively constrain one cell to another + void rel_constr_cells(CellInfo *a, CellInfo *b, int dz) { - for (const auto &existing : tile_ffs) { - if (net_or_nullptr(existing, id_CLK) != net_or_nullptr(ff0, id_CLK)) - return false; - if (net_or_nullptr(existing, id_LSR) != net_or_nullptr(ff0, id_LSR)) - return false; - if (str_or_default(existing->params, id_CLKMUX, "CLK") != str_or_default(ff0->params, id_CLKMUX, "CLK")) - return false; - if (str_or_default(existing->params, id_LSRMUX, "LSR") != str_or_default(ff0->params, id_LSRMUX, "LSR")) - return false; - if (str_or_default(existing->params, id_SRMODE, "LSR_OVER_CE") != - str_or_default(ff0->params, id_SRMODE, "LSR_OVER_CE")) - return false; + 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; } - return true; } - // Return true if a FF can be added to a DPRAM slice - bool can_pack_ff_dram(CellInfo *dpram, CellInfo *ff) + // 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) { - if (ff->getPort(id_M) != nullptr) - return false; // skip PRLD FFs due to M/DI conflict - std::string wckmux = str_or_default(dpram->params, id_WCKMUX, "WCK"); - std::string clkmux = str_or_default(ff->params, id_CLKMUX, "CLK"); - if (wckmux != clkmux && !(wckmux == "WCK" && clkmux == "CLK")) - return false; - std::string wremux = str_or_default(dpram->params, id_WREMUX, "WRE"); - std::string lsrmux = str_or_default(ff->params, id_LSRMUX, "LSR"); - if (wremux != lsrmux && !(wremux == "WRE" && lsrmux == "LSR")) - return false; - return true; - } + 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); + }; - // Return true if two LUTs can be paired considering FF compatibility - bool can_pack_lutff(IdString lut0, IdString lut1) - { - auto ff0 = lutffPairs.find(lut0), ff1 = lutffPairs.find(lut1); - if (ff0 != lutffPairs.end() && ff1 != lutffPairs.end()) { - return can_pack_ffs(ctx->cells.at(ff0->second).get(), ctx->cells.at(ff1->second).get()); + if (comb->cluster != ClusterId()) { + CellInfo *root = ctx->getClusterRootCell(comb->cluster); + process_cell(root); + for (auto &ch : root->constr_children) + process_cell(ch); } else { - return true; + 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); } - // Find "closely connected" LUTs and pair them together - void pair_luts() + void pack_ffs() { - log_info("Finding LUT-LUT pairs...\n"); - pool<IdString> procdLuts; + log_info("Packing FFs...\n"); + int pairs = 0; for (auto &cell : ctx->cells) { CellInfo *ci = cell.second.get(); - if (is_lut(ctx, ci) && procdLuts.find(cell.first) == procdLuts.end()) { - NetInfo *znet = ci->ports.at(id_Z).net; - std::vector<NetInfo *> inpnets; - if (znet != nullptr) { - for (auto user : znet->users) { - if (is_lut(ctx, user.cell) && user.cell != ci && - procdLuts.find(user.cell->name) == procdLuts.end()) { - if (can_pack_lutff(ci->name, user.cell->name)) { - procdLuts.insert(ci->name); - procdLuts.insert(user.cell->name); - lutPairs[ci->name] = user.cell->name; - goto paired; - } - } - } - if (false) { - paired: - continue; - } - } - if (lutffPairs.find(ci->name) != lutffPairs.end()) { - NetInfo *qnet = ctx->cells.at(lutffPairs[ci->name])->ports.at(id_Q).net; - if (qnet != nullptr) { - for (auto user : qnet->users) { - if (is_lut(ctx, user.cell) && user.cell != ci && - procdLuts.find(user.cell->name) == procdLuts.end()) { - if (can_pack_lutff(ci->name, user.cell->name)) { - procdLuts.insert(ci->name); - procdLuts.insert(user.cell->name); - lutPairs[ci->name] = user.cell->name; - goto paired_ff; - } - } - } - if (false) { - paired_ff: + if (is_ff(ctx, ci)) { + NetInfo *di = get_net_or_empty(ci, 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; } - } - } - for (const char *inp : {"A", "B", "C", "D"}) { - if (!ci->ports.count(ctx->id(inp))) - continue; - NetInfo *innet = ci->ports.at(ctx->id(inp)).net; - if (innet != nullptr && innet->driver.cell != nullptr) { - CellInfo *drv = innet->driver.cell; - if (is_lut(ctx, drv) && drv != ci && innet->driver.port == id_Z) { - if (procdLuts.find(drv->name) == procdLuts.end()) { - if (can_pack_lutff(ci->name, drv->name)) { - procdLuts.insert(ci->name); - procdLuts.insert(drv->name); - lutPairs[ci->name] = drv->name; - goto paired_inlut; - } - } - } else if (is_ff(ctx, drv) && innet->driver.port == id_Q) { - auto fflut = fflutPairs.find(drv->name); - if (fflut != fflutPairs.end() && fflut->second != ci->name && - procdLuts.find(fflut->second) == procdLuts.end()) { - if (can_pack_lutff(ci->name, fflut->second)) { - procdLuts.insert(ci->name); - procdLuts.insert(fflut->second); - lutPairs[ci->name] = fflut->second; - goto paired_inlut; - } - } - } - } - } - - // Pack LUTs feeding the same CCU2, RAM or DFF into a SLICE - if (znet != nullptr && znet->users.entries() < 10) { - for (auto user : znet->users) { - if (is_lc(ctx, user.cell) || user.cell->type == id_DP16KD || is_ff(ctx, user.cell)) { - for (auto port : user.cell->ports) { - if (port.second.type != PORT_IN || port.second.net == nullptr || - port.second.net == znet) - continue; - if (port.second.net->users.entries() > 10) - continue; - CellInfo *drv = port.second.net->driver.cell; - if (drv == nullptr) - continue; - if (is_lut(ctx, drv) && !procdLuts.count(drv->name) && - can_pack_lutff(ci->name, drv->name)) { - procdLuts.insert(ci->name); - procdLuts.insert(drv->name); - lutPairs[ci->name] = drv->name; - goto paired_inlut; - } - } - } - } - } - - // Pack LUTs sharing an input with a simple fanout-based heuristic - for (const char *inp : {"A", "B", "C", "D"}) { - if (!ci->ports.count(ctx->id(inp))) + } 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; - NetInfo *innet = ci->ports.at(ctx->id(inp)).net; - if (innet != nullptr && innet->users.entries() < 5 && innet->users.entries() > 1) - inpnets.push_back(innet); - } - std::sort(inpnets.begin(), inpnets.end(), - [&](const NetInfo *a, const NetInfo *b) { return a->users.entries() < b->users.entries(); }); - for (auto inet : inpnets) { - for (auto &user : inet->users) { - if (user.cell == nullptr || user.cell == ci || !is_lut(ctx, user.cell)) - continue; - if (procdLuts.count(user.cell->name)) - continue; - if (can_pack_lutff(ci->name, user.cell->name)) { - procdLuts.insert(ci->name); - procdLuts.insert(user.cell->name); - lutPairs[ci->name] = user.cell->name; - goto paired_inlut; - } } } - - if (false) { - paired_inlut: - 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); } } } - if (ctx->debug) { - log_info("Singleton LUTs (packer QoR debug): \n"); - for (auto &cell : ctx->cells) - if (is_lut(ctx, cell.second.get()) && !procdLuts.count(cell.first)) - log_info(" %s\n", cell.first.c_str(ctx)); - } + log_info(" %d FFs paired with LUTs.\n", pairs); } // Return true if an port is a top level port that provides its own IOBUF @@ -429,6 +300,192 @@ class Ecp5Packer return false; } + // Pass to pack LUT5s into a newly created slice + void pack_lut5xs() + { + log_info("Packing LUT5-7s...\n"); + + // Gets the "COMB1" side of a LUT5, where we pack a LUT[67] into + auto get_comb1_from_lut5 = [&](CellInfo *lut5) { + NetInfo *f1 = get_net_or_empty(lut5, id_F1); + NPNR_ASSERT(f1 != nullptr); + NPNR_ASSERT(f1->driver.cell != nullptr); + return f1->driver.cell; + }; + + dict<IdString, std::pair<CellInfo *, CellInfo *>> lut5_roots, lut6_roots, lut7_roots; + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (is_pfumx(ctx, ci)) { + NetInfo *f0 = ci->ports.at(id_BLUT).net; + + if (f0 == nullptr) + log_error("PFUMX '%s' has disconnected port 'BLUT'\n", ci->name.c_str(ctx)); + NetInfo *f1 = ci->ports.at(id_ALUT).net; + if (f1 == nullptr) + log_error("PFUMX '%s' has disconnected port 'ALUT'\n", ci->name.c_str(ctx)); + + CellInfo *lut0 = + (f0->driver.cell && f0->driver.cell->type == id_TRELLIS_COMB && f0->driver.port == id_F) + ? f0->driver.cell + : nullptr; + CellInfo *lut1 = + (f1->driver.cell && f1->driver.cell->type == id_TRELLIS_COMB && f1->driver.port == id_F) + ? f1->driver.cell + : nullptr; + if (lut0 == nullptr || lut0->cluster != ClusterId()) + log_error("PFUMX '%s' has BLUT driven by cell other than a LUT\n", ci->name.c_str(ctx)); + if (lut1 == nullptr || lut1->cluster != ClusterId()) + log_error("PFUMX '%s' has ALUT driven by cell other than a LUT\n", ci->name.c_str(ctx)); + lut0->addInput(id_F1); + lut0->addInput(id_M); + lut0->addOutput(id_OFX); + + ci->movePortTo(id_Z, lut0, id_OFX); + ci->movePortTo(id_ALUT, lut0, id_F1); + ci->movePortTo(id_C0, lut0, id_M); + ci->disconnectPort(id_BLUT); + + lut5_roots[lut0->name] = {lut0, lut1}; + packed_cells.insert(ci->name); + } + } + flush_cells(); + // Pack LUT6s + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (is_l6mux(ctx, ci)) { + NetInfo *ofx0_0 = ci->ports.at(id_D0).net; + if (ofx0_0 == nullptr) + log_error("L6MUX21 '%s' has disconnected port 'D0'\n", ci->name.c_str(ctx)); + NetInfo *ofx0_1 = ci->ports.at(id_D1).net; + if (ofx0_1 == nullptr) + log_error("L6MUX21 '%s' has disconnected port 'D1'\n", ci->name.c_str(ctx)); + CellInfo *comb0 = (ofx0_0->driver.cell && ofx0_0->driver.cell->type == id_TRELLIS_COMB && + ofx0_0->driver.port == id_OFX) + ? ofx0_0->driver.cell + : nullptr; + CellInfo *comb1 = (ofx0_1->driver.cell && ofx0_1->driver.cell->type == id_TRELLIS_COMB && + ofx0_1->driver.port == id_OFX) + ? ofx0_1->driver.cell + : nullptr; + if (comb0 == nullptr) { + if (!net_driven_by(ctx, ofx0_0, is_l6mux, id_Z)) + log_error("L6MUX21 '%s' has D0 driven by cell other than a SLICE OFX0 but not a LUT7 mux " + "('%s.%s')\n", + ci->name.c_str(ctx), ofx0_0->driver.cell->name.c_str(ctx), + ofx0_0->driver.port.c_str(ctx)); + continue; + } + if (lut6_roots.count(comb0->name)) + continue; + + if (comb1 == nullptr) { + if (!net_driven_by(ctx, ofx0_1, is_l6mux, id_Z)) + log_error("L6MUX21 '%s' has D1 driven by cell other than a SLICE OFX0 but not a LUT7 mux " + "('%s.%s')\n", + ci->name.c_str(ctx), ofx0_0->driver.cell->name.c_str(ctx), + ofx0_0->driver.port.c_str(ctx)); + continue; + } + if (lut6_roots.count(comb1->name)) + continue; + if (ctx->verbose) + log_info(" mux '%s' forms part of a LUT6\n", cell.first.c_str(ctx)); + comb0 = get_comb1_from_lut5(comb0); + comb1 = get_comb1_from_lut5(comb1); + + comb1->addInput(id_FXA); + comb1->addInput(id_FXB); + comb1->addInput(id_M); + comb1->addOutput(id_OFX); + ci->movePortTo(id_D0, comb1, id_FXA); + ci->movePortTo(id_D1, comb1, id_FXB); + ci->movePortTo(id_SD, comb1, id_M); + ci->movePortTo(id_Z, comb1, id_OFX); + lut6_roots[comb1->name] = {comb0, comb1}; + packed_cells.insert(ci->name); + } + } + flush_cells(); + // Pack LUT7s + for (auto &cell : ctx->cells) { + CellInfo *ci = cell.second.get(); + if (is_l6mux(ctx, ci)) { + NetInfo *ofx1_0 = ci->ports.at(id_D0).net; + if (ofx1_0 == nullptr) + log_error("L6MUX21 '%s' has disconnected port 'D0'\n", ci->name.c_str(ctx)); + NetInfo *ofx1_1 = ci->ports.at(id_D1).net; + if (ofx1_1 == nullptr) + log_error("L6MUX21 '%s' has disconnected port 'D1'\n", ci->name.c_str(ctx)); + CellInfo *comb1 = (ofx1_0->driver.cell && ofx1_0->driver.cell->type == id_TRELLIS_COMB && + ofx1_0->driver.port == id_OFX) + ? ofx1_0->driver.cell + : nullptr; + CellInfo *comb3 = (ofx1_1->driver.cell && ofx1_1->driver.cell->type == id_TRELLIS_COMB && + ofx1_1->driver.port == id_OFX) + ? ofx1_1->driver.cell + : nullptr; + if (comb1 == nullptr) + log_error("L6MUX21 '%s' has D0 driven by cell other than a SLICE OFX ('%s.%s')\n", + ci->name.c_str(ctx), ofx1_0->driver.cell->name.c_str(ctx), + ofx1_0->driver.port.c_str(ctx)); + if (comb3 == nullptr) + log_error("L6MUX21 '%s' has D1 driven by cell other than a SLICE OFX ('%s.%s')\n", + ci->name.c_str(ctx), ofx1_1->driver.cell->name.c_str(ctx), + ofx1_1->driver.port.c_str(ctx)); + + NetInfo *fxa_0 = comb1->ports.at(id_FXA).net; + if (fxa_0 == nullptr) + log_error("SLICE '%s' has disconnected port 'FXA'\n", comb1->name.c_str(ctx)); + NetInfo *fxa_1 = comb3->ports.at(id_FXA).net; + if (fxa_1 == nullptr) + log_error("SLICE '%s' has disconnected port 'FXA'\n", comb3->name.c_str(ctx)); + + CellInfo *comb2 = net_driven_by( + ctx, fxa_1, + [](const Context *ctx, const CellInfo *ci) { + (void)ctx; + return ci->type == id_TRELLIS_COMB; + }, + id_OFX); + if (comb2 == nullptr) + log_error("SLICE '%s' has FXA driven by cell other than a SLICE OFX0 ('%s.%s')\n", + comb3->name.c_str(ctx), fxa_1->driver.cell->name.c_str(ctx), + fxa_1->driver.port.c_str(ctx)); + comb2 = get_comb1_from_lut5(comb2); + comb2->addInput(id_FXA); + comb2->addInput(id_FXB); + comb2->addInput(id_M); + comb2->addOutput(id_OFX); + ci->movePortTo(id_D0, comb2, id_FXA); + ci->movePortTo(id_D1, comb2, id_FXB); + ci->movePortTo(id_SD, comb2, id_M); + ci->movePortTo(id_Z, comb2, id_OFX); + + lut7_roots[comb2->name] = {comb1, comb3}; + packed_cells.insert(ci->name); + } + } + + for (auto &root : lut7_roots) { + auto &cells = root.second; + cells.second->cluster = cells.second->name; + cells.second->constr_abs_z = true; + cells.second->constr_z = (1 << Arch::lc_idx_shift) | Arch::BEL_COMB; + rel_constr_cells(cells.second, cells.first, (4 << Arch::lc_idx_shift)); + } + for (auto &root : lut6_roots) { + auto &cells = root.second; + rel_constr_cells(cells.second, cells.first, (2 << Arch::lc_idx_shift)); + } + for (auto &root : lut5_roots) { + auto &cells = root.second; + rel_constr_cells(cells.first, cells.second, (1 << Arch::lc_idx_shift)); + } + flush_cells(); + } + // Simple "packer" to remove nextpnr IOBUFs, this assumes IOBUFs are manually instantiated void pack_io() { @@ -523,215 +580,6 @@ class Ecp5Packer flush_cells(); } - // Pass to pack LUT5s into a newly created slice - void pack_lut5xs() - { - log_info("Packing LUT5-7s...\n"); - for (auto &cell : ctx->cells) { - CellInfo *ci = cell.second.get(); - if (is_pfumx(ctx, ci)) { - std::unique_ptr<CellInfo> packed = - create_ecp5_cell(ctx, id_TRELLIS_SLICE, ci->name.str(ctx) + "_SLICE"); - NetInfo *f0 = ci->ports.at(id_BLUT).net; - if (f0 == nullptr) - log_error("PFUMX '%s' has disconnected port 'BLUT'\n", ci->name.c_str(ctx)); - NetInfo *f1 = ci->ports.at(id_ALUT).net; - if (f1 == nullptr) - log_error("PFUMX '%s' has disconnected port 'ALUT'\n", ci->name.c_str(ctx)); - CellInfo *lut0 = net_driven_by(ctx, f0, is_lut, id_Z); - CellInfo *lut1 = net_driven_by(ctx, f1, is_lut, id_Z); - if (lut0 == nullptr) - log_error("PFUMX '%s' has BLUT driven by cell other than a LUT\n", ci->name.c_str(ctx)); - if (lut1 == nullptr) - log_error("PFUMX '%s' has ALUT driven by cell other than a LUT\n", ci->name.c_str(ctx)); - if (ctx->verbose) - log_info(" mux '%s' forms part of a LUT5\n", cell.first.c_str(ctx)); - lut0->movePortTo(id_A, packed.get(), id_A0); - lut0->movePortTo(id_B, packed.get(), id_B0); - lut0->movePortTo(id_C, packed.get(), id_C0); - lut0->movePortTo(id_D, packed.get(), id_D0); - lut1->movePortTo(id_A, packed.get(), id_A1); - lut1->movePortTo(id_B, packed.get(), id_B1); - lut1->movePortTo(id_C, packed.get(), id_C1); - lut1->movePortTo(id_D, packed.get(), id_D1); - ci->movePortTo(id_C0, packed.get(), id_M0); - ci->movePortTo(id_Z, packed.get(), id_OFX0); - packed->params[id_LUT0_INITVAL] = get_or_default(lut0->params, id_INIT, Property(0, 16)); - packed->params[id_LUT1_INITVAL] = get_or_default(lut1->params, id_INIT, Property(0, 16)); - - ctx->nets.erase(f0->name); - ctx->nets.erase(f1->name); - sliceUsage[packed->name].lut0_used = true; - sliceUsage[packed->name].lut1_used = true; - sliceUsage[packed->name].mux5_used = true; - - if (lutffPairs.find(ci->name) != lutffPairs.end()) { - CellInfo *ff = ctx->cells.at(lutffPairs[ci->name]).get(); - ff_to_slice(ctx, ff, packed.get(), 0, true); - packed_cells.insert(ff->name); - sliceUsage[packed->name].ff0_used = true; - lutffPairs.erase(ci->name); - fflutPairs.erase(ff->name); - } - - new_cells.push_back(std::move(packed)); - packed_cells.insert(lut0->name); - packed_cells.insert(lut1->name); - packed_cells.insert(ci->name); - } - } - flush_cells(); - // Pack LUT6s - for (auto &cell : ctx->cells) { - CellInfo *ci = cell.second.get(); - if (is_l6mux(ctx, ci)) { - NetInfo *ofx0_0 = ci->ports.at(id_D0).net; - if (ofx0_0 == nullptr) - log_error("L6MUX21 '%s' has disconnected port 'D0'\n", ci->name.c_str(ctx)); - NetInfo *ofx0_1 = ci->ports.at(id_D1).net; - if (ofx0_1 == nullptr) - log_error("L6MUX21 '%s' has disconnected port 'D1'\n", ci->name.c_str(ctx)); - CellInfo *slice0 = net_driven_by(ctx, ofx0_0, is_lc, id_OFX0); - CellInfo *slice1 = net_driven_by(ctx, ofx0_1, is_lc, id_OFX0); - if (slice0 == nullptr) { - if (!net_driven_by(ctx, ofx0_0, is_l6mux, id_Z) && !net_driven_by(ctx, ofx0_0, is_lc, id_OFX1)) - log_error("L6MUX21 '%s' has D0 driven by cell other than a SLICE OFX0 but not a LUT7 mux " - "('%s.%s')\n", - ci->name.c_str(ctx), ofx0_0->driver.cell->name.c_str(ctx), - ofx0_0->driver.port.c_str(ctx)); - continue; - } - if (slice1 == nullptr) { - if (!net_driven_by(ctx, ofx0_1, is_l6mux, id_Z) && !net_driven_by(ctx, ofx0_1, is_lc, id_OFX1)) - log_error("L6MUX21 '%s' has D1 driven by cell other than a SLICE OFX0 but not a LUT7 mux " - "('%s.%s')\n", - ci->name.c_str(ctx), ofx0_0->driver.cell->name.c_str(ctx), - ofx0_0->driver.port.c_str(ctx)); - continue; - } - if (ctx->verbose) - log_info(" mux '%s' forms part of a LUT6\n", cell.first.c_str(ctx)); - ci->movePortTo(id_D0, slice1, id_FXA); - ci->movePortTo(id_D1, slice1, id_FXB); - ci->movePortTo(id_SD, slice1, id_M1); - ci->movePortTo(id_Z, slice1, id_OFX1); - slice0->constr_z = 1; - slice0->constr_x = 0; - slice0->constr_y = 0; - slice0->cluster = slice1->name; - slice1->constr_z = 0; - slice1->constr_abs_z = true; - slice1->constr_children.push_back(slice0); - slice1->cluster = slice1->name; - - if (lutffPairs.find(ci->name) != lutffPairs.end()) { - CellInfo *ff = ctx->cells.at(lutffPairs[ci->name]).get(); - ff_to_slice(ctx, ff, slice1, 1, true); - packed_cells.insert(ff->name); - sliceUsage[slice1->name].ff1_used = true; - lutffPairs.erase(ci->name); - fflutPairs.erase(ff->name); - } - - packed_cells.insert(ci->name); - } - } - flush_cells(); - // Pack LUT7s - for (auto &cell : ctx->cells) { - CellInfo *ci = cell.second.get(); - if (is_l6mux(ctx, ci)) { - NetInfo *ofx1_0 = ci->ports.at(id_D0).net; - if (ofx1_0 == nullptr) - log_error("L6MUX21 '%s' has disconnected port 'D0'\n", ci->name.c_str(ctx)); - NetInfo *ofx1_1 = ci->ports.at(id_D1).net; - if (ofx1_1 == nullptr) - log_error("L6MUX21 '%s' has disconnected port 'D1'\n", ci->name.c_str(ctx)); - CellInfo *slice1 = net_driven_by(ctx, ofx1_0, is_lc, id_OFX1); - CellInfo *slice3 = net_driven_by(ctx, ofx1_1, is_lc, id_OFX1); - if (slice1 == nullptr) - log_error("L6MUX21 '%s' has D0 driven by cell other than a SLICE OFX ('%s.%s')\n", - ci->name.c_str(ctx), ofx1_0->driver.cell->name.c_str(ctx), - ofx1_0->driver.port.c_str(ctx)); - if (slice3 == nullptr) - log_error("L6MUX21 '%s' has D1 driven by cell other than a SLICE OFX ('%s.%s')\n", - ci->name.c_str(ctx), ofx1_1->driver.cell->name.c_str(ctx), - ofx1_1->driver.port.c_str(ctx)); - - NetInfo *fxa_0 = slice1->ports.at(id_FXA).net; - if (fxa_0 == nullptr) - log_error("SLICE '%s' has disconnected port 'FXA'\n", slice1->name.c_str(ctx)); - NetInfo *fxa_1 = slice3->ports.at(id_FXA).net; - if (fxa_1 == nullptr) - log_error("SLICE '%s' has disconnected port 'FXA'\n", slice3->name.c_str(ctx)); - - CellInfo *slice0 = net_driven_by(ctx, fxa_0, is_lc, id_OFX0); - CellInfo *slice2 = net_driven_by(ctx, fxa_1, is_lc, id_OFX0); - if (slice0 == nullptr) - log_error("SLICE '%s' has FXA driven by cell other than a SLICE OFX0 ('%s.%s')\n", - slice1->name.c_str(ctx), fxa_0->driver.cell->name.c_str(ctx), - fxa_0->driver.port.c_str(ctx)); - if (slice2 == nullptr) - log_error("SLICE '%s' has FXA driven by cell other than a SLICE OFX0 ('%s.%s')\n", - slice3->name.c_str(ctx), fxa_1->driver.cell->name.c_str(ctx), - fxa_1->driver.port.c_str(ctx)); - - ci->movePortTo(id_D0, slice2, id_FXA); - ci->movePortTo(id_D1, slice2, id_FXB); - ci->movePortTo(id_SD, slice2, id_M1); - ci->movePortTo(id_Z, slice2, id_OFX1); - - for (auto slice : {slice0, slice1, slice2, slice3}) { - slice->constr_children.clear(); - slice->constr_abs_z = false; - slice->constr_x = 0; - slice->constr_y = 0; - slice->constr_z = 0; - slice->cluster = ClusterId(); - } - slice3->constr_children.clear(); - slice3->constr_abs_z = true; - slice3->constr_z = 0; - slice3->cluster = slice3->name; - - slice2->constr_children.clear(); - slice2->constr_abs_z = true; - slice2->constr_z = 1; - slice2->constr_x = 0; - slice2->constr_y = 0; - slice2->cluster = slice3->name; - slice3->constr_children.push_back(slice2); - - slice1->constr_children.clear(); - slice1->constr_abs_z = true; - slice1->constr_z = 2; - slice1->constr_x = 0; - slice1->constr_y = 0; - slice1->cluster = slice3->name; - slice3->constr_children.push_back(slice1); - - slice0->constr_children.clear(); - slice0->constr_abs_z = true; - slice0->constr_z = 3; - slice0->constr_x = 0; - slice0->constr_y = 0; - slice0->cluster = slice3->name; - slice3->constr_children.push_back(slice0); - - if (lutffPairs.find(ci->name) != lutffPairs.end()) { - CellInfo *ff = ctx->cells.at(lutffPairs[ci->name]).get(); - ff_to_slice(ctx, ff, slice2, 1, true); - packed_cells.insert(ff->name); - sliceUsage[slice2->name].ff1_used = true; - lutffPairs.erase(ci->name); - fflutPairs.erase(ff->name); - } - - packed_cells.insert(ci->name); - } - } - flush_cells(); - } // Create a feed in to the carry chain CellInfo *make_carry_feed_in(NetInfo *carry, PortRef chain_in) { @@ -881,58 +729,37 @@ class Ecp5Packer std::vector<std::tuple<CellInfo *, CellInfo *, int>> ff_packing; for (auto &chain : all_chains) { int cell_count = 0; - std::vector<CellInfo *> tile_ffs; std::vector<CellInfo *> packed_chain; for (auto &cell : chain.cells) { - if (cell_count % 4 == 0) - tile_ffs.clear(); - std::unique_ptr<CellInfo> slice = - create_ecp5_cell(ctx, id_TRELLIS_SLICE, cell->name.str(ctx) + "$CCU2_SLICE"); - - ccu2c_to_slice(ctx, cell, slice.get()); - - CellInfo *ff0 = nullptr; - NetInfo *f0net = slice->ports.at(id_F0).net; - if (f0net != nullptr) { - ff0 = net_only_drives(ctx, f0net, is_ff, id_DI, false); - if (ff0 != nullptr && can_add_ff_to_tile(tile_ffs, ff0)) { - ff_packing.push_back(std::make_tuple(ff0, slice.get(), 0)); - tile_ffs.push_back(ff0); - packed_cells.insert(ff0->name); - } - } + std::unique_ptr<CellInfo> comb0 = + create_ecp5_cell(ctx, id_TRELLIS_COMB, cell->name.str(ctx) + "$CCU2_COMB0"); + std::unique_ptr<CellInfo> comb1 = + create_ecp5_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")); - CellInfo *ff1 = nullptr; - NetInfo *f1net = slice->ports.at(id_F1).net; - if (f1net != nullptr) { - ff1 = net_only_drives(ctx, f1net, is_ff, id_DI, false); - if (ff1 != nullptr && (ff0 == nullptr || can_pack_ffs(ff0, ff1)) && - can_add_ff_to_tile(tile_ffs, ff1)) { - ff_packing.push_back(std::make_tuple(ff1, slice.get(), 1)); - tile_ffs.push_back(ff1); - packed_cells.insert(ff1->name); - } - } - packed_chain.push_back(slice.get()); - new_cells.push_back(std::move(slice)); + 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); } - for (auto ff : ff_packing) - ff_to_slice(ctx, std::get<0>(ff), std::get<1>(ff), std::get<2>(ff), true); - // 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 / 4); + chain.at(i)->constr_x = (i / 8); chain.at(i)->constr_y = 0; - chain.at(i)->constr_z = i % 4; + 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)); @@ -951,83 +778,64 @@ class Ecp5Packer // Create RAMW slice std::unique_ptr<CellInfo> ramw_slice = - create_ecp5_cell(ctx, id_TRELLIS_SLICE, ci->name.str(ctx) + "$RAMW_SLICE"); - dram_to_ramw(ctx, ci, ramw_slice.get()); + create_ecp5_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<CellInfo> ram0_slice = - create_ecp5_cell(ctx, id_TRELLIS_SLICE, ci->name.str(ctx) + "$DPRAM0_SLICE"); - dram_to_ram_slice(ctx, ci, ram0_slice.get(), ramw_slice.get(), 0); - - std::unique_ptr<CellInfo> ram1_slice = - create_ecp5_cell(ctx, id_TRELLIS_SLICE, ci->name.str(ctx) + "$DPRAM1_SLICE"); - dram_to_ram_slice(ctx, ci, ram1_slice.get(), ramw_slice.get(), 1); + std::unique_ptr<CellInfo> ram_comb[4]; + for (int i = 0; i < 4; i++) { + ram_comb[i] = create_ecp5_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<CellInfo> ramw_block[2]; + for (int i = 0; i < 2; i++) { + ramw_block[i] = create_ecp5_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); - ci->disconnectPort(ctx->id("RAD[0]")); - ci->disconnectPort(ctx->id("RAD[1]")); - ci->disconnectPort(ctx->id("RAD[2]")); - ci->disconnectPort(ctx->id("RAD[3]")); - - // Attempt to pack FFs into RAM slices - std::vector<std::tuple<CellInfo *, CellInfo *, int>> ff_packing; - std::vector<CellInfo *> tile_ffs; - for (auto slice : {ram0_slice.get(), ram1_slice.get()}) { - CellInfo *ff0 = nullptr; - NetInfo *f0net = slice->ports.at(id_F0).net; - if (f0net != nullptr) { - ff0 = net_only_drives(ctx, f0net, is_ff, id_DI, false); - if (ff0 != nullptr && can_add_ff_to_tile(tile_ffs, ff0)) { - if (can_pack_ff_dram(slice, ff0)) { - ff_packing.push_back(std::make_tuple(ff0, slice, 0)); - tile_ffs.push_back(ff0); - packed_cells.insert(ff0->name); - } - } - } - - CellInfo *ff1 = nullptr; - NetInfo *f1net = slice->ports.at(id_F1).net; - if (f1net != nullptr) { - ff1 = net_only_drives(ctx, f1net, is_ff, id_DI, false); - if (ff1 != nullptr && (ff0 == nullptr || can_pack_ffs(ff0, ff1)) && - can_add_ff_to_tile(tile_ffs, ff1)) { - if (can_pack_ff_dram(slice, ff1)) { - ff_packing.push_back(std::make_tuple(ff1, slice, 1)); - tile_ffs.push_back(ff1); - packed_cells.insert(ff1->name); - } - } - } - } - - for (auto ff : ff_packing) - ff_to_slice(ctx, std::get<0>(ff), std::get<1>(ff), std::get<2>(ff), true); + for (int i = 0; i < 4; i++) + ci->disconnectPort(ctx->id(stringf("RAD[%d]", i))); // Setup placement constraints - ram0_slice->constr_abs_z = true; - ram0_slice->constr_z = 0; - ram0_slice->cluster = ram0_slice->name; - - ram1_slice->cluster = ram0_slice->name; - ram1_slice->constr_abs_z = true; - ram1_slice->constr_x = 0; - ram1_slice->constr_y = 0; - ram1_slice->constr_z = 1; - ram0_slice->constr_children.push_back(ram1_slice.get()); - - ramw_slice->cluster = ram0_slice->name; + // 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 = 2; - ram0_slice->constr_children.push_back(ramw_slice.get()); + ramw_slice->constr_z = (4 << ctx->lc_idx_shift) | Arch::BEL_RAMW; + ram_comb[0]->constr_children.push_back(ramw_slice.get()); - new_cells.push_back(std::move(ram0_slice)); - new_cells.push_back(std::move(ram1_slice)); + 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); } @@ -1035,183 +843,6 @@ class Ecp5Packer flush_cells(); } - // Pack LUTs that have been paired together - void pack_lut_pairs() - { - log_info("Packing paired LUTs into a SLICE...\n"); - for (auto pair : lutPairs) { - CellInfo *lut0 = ctx->cells.at(pair.first).get(); - CellInfo *lut1 = ctx->cells.at(pair.second).get(); - std::unique_ptr<CellInfo> slice = create_ecp5_cell(ctx, id_TRELLIS_SLICE, lut0->name.str(ctx) + "_SLICE"); - - lut_to_slice(ctx, lut0, slice.get(), 0); - lut_to_slice(ctx, lut1, slice.get(), 1); - - auto ff0 = lutffPairs.find(lut0->name); - - if (ff0 != lutffPairs.end()) { - ff_to_slice(ctx, ctx->cells.at(ff0->second).get(), slice.get(), 0, true); - packed_cells.insert(ff0->second); - fflutPairs.erase(ff0->second); - lutffPairs.erase(lut0->name); - } - - auto ff1 = lutffPairs.find(lut1->name); - - if (ff1 != lutffPairs.end()) { - ff_to_slice(ctx, ctx->cells.at(ff1->second).get(), slice.get(), 1, true); - packed_cells.insert(ff1->second); - fflutPairs.erase(ff1->second); - lutffPairs.erase(lut1->name); - } - - new_cells.push_back(std::move(slice)); - packed_cells.insert(lut0->name); - packed_cells.insert(lut1->name); - } - flush_cells(); - } - - // Pack single LUTs that weren't paired into their own slice, - // with an optional FF also - void pack_remaining_luts() - { - log_info("Packing unpaired LUTs into a SLICE...\n"); - for (auto &cell : ctx->cells) { - CellInfo *ci = cell.second.get(); - if (is_lut(ctx, ci)) { - std::unique_ptr<CellInfo> slice = create_ecp5_cell(ctx, id_TRELLIS_SLICE, ci->name.str(ctx) + "_SLICE"); - lut_to_slice(ctx, ci, slice.get(), 1); - auto ff = lutffPairs.find(ci->name); - - if (ff != lutffPairs.end()) { - ff_to_slice(ctx, ctx->cells.at(ff->second).get(), slice.get(), 1, true); - packed_cells.insert(ff->second); - fflutPairs.erase(ff->second); - lutffPairs.erase(ci->name); - } - - new_cells.push_back(std::move(slice)); - packed_cells.insert(ci->name); - } - } - flush_cells(); - } - - // Find a cell that meets some criteria near an origin cell - // Used for packing an FF into a nearby SLICE - template <typename TFunc> CellInfo *find_nearby_cell(CellInfo *origin, TFunc Func) - { - pool<CellInfo *, hash_ptr_ops> visited_cells; - std::queue<CellInfo *> to_visit; - visited_cells.insert(origin); - to_visit.push(origin); - int iter = 0; - while (!to_visit.empty() && iter < 10000) { - CellInfo *cursor = to_visit.front(); - to_visit.pop(); - if (Func(cursor)) - return cursor; - for (const auto &port : cursor->ports) { - NetInfo *pn = port.second.net; - if (pn == nullptr) - continue; - // Skip high-fanout nets that are unlikely to be relevant - if (pn->users.entries() > 25) - continue; - // Add other ports on this net if not already visited - auto visit_port = [&](const PortRef &port) { - if (port.cell == nullptr) - return; - if (visited_cells.count(port.cell)) - return; - // If not already visited; add the cell of this port to the queue - to_visit.push(port.cell); - visited_cells.insert(port.cell); - }; - visit_port(pn->driver); - for (const auto &usr : pn->users) - visit_port(usr); - } - ++iter; - } - return nullptr; - } - - // Pack flipflops that weren't paired with a LUT - float dense_pack_mode_thresh = 0.95f; - void pack_remaining_ffs() - { - // Enter dense flipflop packing mode once utilisation exceeds a threshold (default: 95%) - int used_slices = 0; - for (auto &cell : ctx->cells) - if (cell.second->type == id_TRELLIS_SLICE) - ++used_slices; - - log_info("Packing unpaired FFs into a SLICE...\n"); - for (auto &cell : ctx->cells) { - CellInfo *ci = cell.second.get(); - if (is_ff(ctx, ci)) { - bool pack_dense = used_slices > (dense_pack_mode_thresh * available_slices); - bool requires_m = ci->getPort(id_M) != nullptr; - if (pack_dense && !requires_m) { - // If dense packing threshold exceeded; always try and pack the FF into an existing slice - // Find a SLICE with space "near" the flipflop in the netlist - std::vector<CellInfo *> ltile; - CellInfo *target = find_nearby_cell(ci, [&](CellInfo *cursor) { - if (cursor->type != id_TRELLIS_SLICE) - return false; - if (cursor->cluster != ClusterId()) { - auto &constr_children = ctx->cells.at(cursor->cluster)->constr_children; - // Skip big chains for performance - if (constr_children.size() > 8) - return false; - // Have to check the whole of the tile for legality when dealing with chains, not just slice - ltile.clear(); - if (cursor->cluster != cursor->name) - ltile.push_back(ctx->cells.at(cursor->cluster).get()); - else - ltile.push_back(cursor); - for (auto c : constr_children) - ltile.push_back(c); - if (!can_add_ff_to_tile(ltile, cursor)) - return false; - } - if (!can_add_ff_to_slice(cursor, ci)) - return false; - for (int i = 0; i < 2; i++) - if (is_ff_available(cursor, i)) - return true; - return false; - }); - - // If found, add the FF to this slice instead of creating a new one - if (target != nullptr) { - for (int i = 0; i < 2; i++) { - if (is_ff_available(target, i)) { - ff_to_slice(ctx, ci, target, i, false); - goto ff_packed; - } - } - } - - if (false) { - ff_packed: - packed_cells.insert(ci->name); - continue; - } - } - - std::unique_ptr<CellInfo> slice = create_ecp5_cell(ctx, id_TRELLIS_SLICE, ci->name.str(ctx) + "_SLICE"); - ff_to_slice(ctx, ci, slice.get(), 0, false); - new_cells.push_back(std::move(slice)); - ++used_slices; - packed_cells.insert(ci->name); - } - } - flush_cells(); - } - int make_init_with_const_input(int init, int input, bool value) { int new_init = 0; @@ -1729,7 +1360,7 @@ class Ecp5Packer for (auto &cell : ctx->cells) { CellInfo *ci = cell.second.get(); if (ci->type == id_EXTREFB) { - const NetInfo *refo = net_or_nullptr(ci, id_REFCLKO); + const NetInfo *refo = ci->getPort(id_REFCLKO); CellInfo *dcu = nullptr; std::string loc_bel = std::string("NONE"); std::string dcu_bel = std::string("NONE"); @@ -1785,7 +1416,7 @@ class Ecp5Packer ci->attrs[id_BEL] = dcu_bel; } } else if (ci->type == id_PCSCLKDIV) { - const NetInfo *clki = net_or_nullptr(ci, id_CLKI); + const NetInfo *clki = ci->getPort(id_CLKI); if (clki != nullptr && clki->driver.cell != nullptr && clki->driver.cell->type == id_DCUA) { CellInfo *dcu = clki->driver.cell; if (!dcu->attrs.count(id_BEL)) @@ -1842,7 +1473,7 @@ class Ecp5Packer for (auto &cell : ctx->cells) { CellInfo *ci = cell.second.get(); if (ci->type == id_EHXPLLL && !ci->attrs.count(id_BEL)) { - const NetInfo *drivernet = net_or_nullptr(ci, id_CLKI); + const NetInfo *drivernet = ci->getPort(id_CLKI); if (drivernet == nullptr || drivernet->driver.cell == nullptr) continue; const CellInfo *drivercell = drivernet->driver.cell; @@ -2079,7 +1710,7 @@ class Ecp5Packer {id_RDMOVE, id_RDDIRECTION, id_WRMOVE, id_WRDIRECTION, id_READ0, id_READ1, id_READCLKSEL0, id_READCLKSEL1, id_READCLKSEL2, id_DYNDELAY0, id_DYNDELAY1, id_DYNDELAY2, id_DYNDELAY3, id_DYNDELAY4, id_DYNDELAY5, id_DYNDELAY6, id_DYNDELAY7}) { - if (net_or_nullptr(ci, zport) == nullptr) + if (ci->getPort(zport) == nullptr) tie_zero(ci, zport); } } @@ -2778,7 +2409,7 @@ class Ecp5Packer for (auto &cell : ctx->cells) { CellInfo *ci = cell.second.get(); if (ci->type == id_CLKDIVF) { - const NetInfo *clki = net_or_nullptr(ci, id_CLKI); + const NetInfo *clki = ci->getPort(id_CLKI); for (auto &eclk : eclks) { if (eclk.second.unbuf == clki) { for (auto bel : ctx->getBels()) { @@ -2800,8 +2431,8 @@ class Ecp5Packer clkdiv_done: continue; } else if (ci->type == id_ECLKSYNCB) { - const NetInfo *eclki = net_or_nullptr(ci, id_ECLKI); - const NetInfo *eclko = net_or_nullptr(ci, id_ECLKO); + const NetInfo *eclki = ci->getPort(id_ECLKI); + const NetInfo *eclko = ci->getPort(id_ECLKO); if (eclki != nullptr && eclki->driver.cell != nullptr) { if (eclki->driver.cell->type == id_ECLKBRIDGECS) { BelId bel = ctx->getBelByNameStr(eclki->driver.cell->attrs.at(id_BEL).as_string()); @@ -2833,13 +2464,13 @@ class Ecp5Packer continue; } else if (ci->type == id_DDRDLLA) { ci->type = id_DDRDLL; // transform from Verilog to Bel name - const NetInfo *clk = net_or_nullptr(ci, id_CLK); + const NetInfo *clk = ci->getPort(id_CLK); if (clk == nullptr) log_error("DDRDLLA '%s' has disconnected port CLK\n", ci->name.c_str(ctx)); bool left_bank_users = false, right_bank_users = false; // Check which side the delay codes (DDRDEL) are used on - const NetInfo *ddrdel = net_or_nullptr(ci, id_DDRDEL); + const NetInfo *ddrdel = ci->getPort(id_DDRDEL); if (ddrdel != nullptr) { for (auto &usr : ddrdel->users) { const CellInfo *uc = usr.cell; @@ -2922,7 +2553,7 @@ class Ecp5Packer continue; // Case of a CLKDIVF driven by an ECLKSYNC constrained above; without the input being used elsewhere as // an edge clock - const NetInfo *clki = net_or_nullptr(ci, id_CLKI); + const NetInfo *clki = ci->getPort(id_CLKI); if (clki == nullptr || clki->driver.cell == nullptr) continue; CellInfo *drv = clki->driver.cell; @@ -3131,12 +2762,9 @@ class Ecp5Packer pack_constants(); pack_dram(); pack_carries(); - find_lutff_pairs(); + pack_luts(); pack_lut5xs(); - pair_luts(); - pack_lut_pairs(); - pack_remaining_luts(); - pack_remaining_ffs(); + pack_ffs(); generate_constraints(); promote_ecp5_globals(ctx); ctx->fixupHierarchy(); @@ -3180,122 +2808,147 @@ bool Arch::pack() } } -void Arch::assignArchInfo() +void Arch::assign_arch_info_for_cell(CellInfo *ci) { - for (auto &cell : cells) { - CellInfo *ci = cell.second.get(); - if (ci->type == id_TRELLIS_SLICE) { - - ci->sliceInfo.using_dff = false; - if (ci->ports.count(id_Q0) && ci->ports[id_Q0].net != nullptr) - ci->sliceInfo.using_dff = true; - if (ci->ports.count(id_Q1) && ci->ports[id_Q1].net != nullptr) - ci->sliceInfo.using_dff = true; - - if (ci->ports.count(id_CLK) && ci->ports[id_CLK].net != nullptr) - ci->sliceInfo.clk_sig = ci->ports[id_CLK].net->name; - else - ci->sliceInfo.clk_sig = IdString(); + auto get_port_net = [&](CellInfo *ci, IdString p) { + NetInfo *n = get_net_or_empty(ci, 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); + } else if (ci->type == id_DP16KD) { + ci->ramInfo.is_pdp = (int_or_default(ci->params, id_DATA_WIDTH_A, 0) == 36); + + // Output register mode (REGMODE_{A,B}). Valid options are 'NOREG' and 'OUTREG'. + std::string regmode_a = str_or_default(ci->params, id_REGMODE_A, "NOREG"); + if (regmode_a != "NOREG" && regmode_a != "OUTREG") + log_error("DP16KD %s has invalid REGMODE_A configuration '%s'\n", ci->name.c_str(this), regmode_a.c_str()); + std::string regmode_b = str_or_default(ci->params, id_REGMODE_B, "NOREG"); + if (regmode_b != "NOREG" && regmode_b != "OUTREG") + log_error("DP16KD %s has invalid REGMODE_B configuration '%s'\n", ci->name.c_str(this), regmode_b.c_str()); + ci->ramInfo.is_output_a_registered = regmode_a == "OUTREG"; + ci->ramInfo.is_output_b_registered = regmode_b == "OUTREG"; + + // Based on the REGMODE, we have different timing lookup tables. + if (!ci->ramInfo.is_output_a_registered && !ci->ramInfo.is_output_b_registered) { + ci->ramInfo.regmode_timing_id = id_DP16KD_REGMODE_A_NOREG_REGMODE_B_NOREG; + } else if (!ci->ramInfo.is_output_a_registered && ci->ramInfo.is_output_b_registered) { + ci->ramInfo.regmode_timing_id = id_DP16KD_REGMODE_A_NOREG_REGMODE_B_OUTREG; + } else if (ci->ramInfo.is_output_a_registered && !ci->ramInfo.is_output_b_registered) { + ci->ramInfo.regmode_timing_id = id_DP16KD_REGMODE_A_OUTREG_REGMODE_B_NOREG; + } else if (ci->ramInfo.is_output_a_registered && ci->ramInfo.is_output_b_registered) { + ci->ramInfo.regmode_timing_id = id_DP16KD_REGMODE_A_OUTREG_REGMODE_B_OUTREG; + } + } else if (ci->type == id_MULT18X18D) { + // For the multiplier block, our timing db is dictated by whether any of the input/output registers are + // enabled. To that end, we need to work out what the parameters are for the INPUTA_CLK, INPUTB_CLK and + // OUTPUT_CLK are. + // The clock check is the same IN_A/B and OUT, so hoist it to a function + auto get_clock_parameter = [&](std::string param_name) { + std::string clk = str_or_default(ci->params, id(param_name), "NONE"); + if (clk != "NONE" && clk != "CLK0" && clk != "CLK1" && clk != "CLK2" && clk != "CLK3") + log_error("MULT18X18D %s has invalid %s configuration '%s'\n", ci->name.c_str(this), param_name.c_str(), + clk.c_str()); + return clk; + }; - if (ci->ports.count(id_LSR) && ci->ports[id_LSR].net != nullptr) - ci->sliceInfo.lsr_sig = ci->ports[id_LSR].net->name; - else - ci->sliceInfo.lsr_sig = IdString(); - - ci->sliceInfo.clkmux = id(str_or_default(ci->params, id_CLKMUX, "CLK")); - ci->sliceInfo.lsrmux = id(str_or_default(ci->params, id_LSRMUX, "LSR")); - ci->sliceInfo.srmode = id(str_or_default(ci->params, id_SRMODE, "LSR_OVER_CE")); - std::string mode = str_or_default(ci->params, id_MODE, "LOGIC"); - ci->sliceInfo.is_carry = (mode == "CCU2"); - ci->sliceInfo.is_memory = (mode == "DPRAM" || mode == "RAMW"); - ci->sliceInfo.sd0 = std::stoi(str_or_default(ci->params, id_REG0_SD, "0")); - ci->sliceInfo.sd1 = std::stoi(str_or_default(ci->params, id_REG1_SD, "0")); - ci->sliceInfo.has_l6mux = false; - if (ci->ports.count(id_FXA) && ci->ports[id_FXA].net != nullptr && - ci->ports[id_FXA].net->driver.port == id_OFX0) - ci->sliceInfo.has_l6mux = true; - } else if (ci->type == id_DP16KD) { - ci->ramInfo.is_pdp = (int_or_default(ci->params, id_DATA_WIDTH_A, 0) == 36); - - // Output register mode (REGMODE_{A,B}). Valid options are 'NOREG' and 'OUTREG'. - std::string regmode_a = str_or_default(ci->params, id_REGMODE_A, "NOREG"); - if (regmode_a != "NOREG" && regmode_a != "OUTREG") - log_error("DP16KD %s has invalid REGMODE_A configuration '%s'\n", ci->name.c_str(this), - regmode_a.c_str()); - std::string regmode_b = str_or_default(ci->params, id_REGMODE_B, "NOREG"); - if (regmode_b != "NOREG" && regmode_b != "OUTREG") - log_error("DP16KD %s has invalid REGMODE_B configuration '%s'\n", ci->name.c_str(this), - regmode_b.c_str()); - ci->ramInfo.is_output_a_registered = regmode_a == "OUTREG"; - ci->ramInfo.is_output_b_registered = regmode_b == "OUTREG"; - - // Based on the REGMODE, we have different timing lookup tables. - if (!ci->ramInfo.is_output_a_registered && !ci->ramInfo.is_output_b_registered) { - ci->ramInfo.regmode_timing_id = id_DP16KD_REGMODE_A_NOREG_REGMODE_B_NOREG; - } else if (!ci->ramInfo.is_output_a_registered && ci->ramInfo.is_output_b_registered) { - ci->ramInfo.regmode_timing_id = id_DP16KD_REGMODE_A_NOREG_REGMODE_B_OUTREG; - } else if (ci->ramInfo.is_output_a_registered && !ci->ramInfo.is_output_b_registered) { - ci->ramInfo.regmode_timing_id = id_DP16KD_REGMODE_A_OUTREG_REGMODE_B_NOREG; - } else if (ci->ramInfo.is_output_a_registered && ci->ramInfo.is_output_b_registered) { - ci->ramInfo.regmode_timing_id = id_DP16KD_REGMODE_A_OUTREG_REGMODE_B_OUTREG; - } - } else if (ci->type == id_MULT18X18D) { - // For the multiplier block, our timing db is dictated by whether any of the input/output registers are - // enabled. To that end, we need to work out what the parameters are for the INPUTA_CLK, INPUTB_CLK and - // OUTPUT_CLK are. - // The clock check is the same IN_A/B and OUT, so hoist it to a function - auto get_clock_parameter = [&](std::string param_name) { - std::string clk = str_or_default(ci->params, id(param_name), "NONE"); - if (clk != "NONE" && clk != "CLK0" && clk != "CLK1" && clk != "CLK2" && clk != "CLK3") - log_error("MULT18X18D %s has invalid %s configuration '%s'\n", ci->name.c_str(this), - param_name.c_str(), clk.c_str()); - return clk; - }; - - // Get the input clock setting from the cell - std::string reg_inputa_clk = get_clock_parameter("REG_INPUTA_CLK"); - std::string reg_inputb_clk = get_clock_parameter("REG_INPUTB_CLK"); - - // Inputs are registered IFF the REG_INPUT value is not NONE - const bool is_in_a_registered = reg_inputa_clk != "NONE"; - const bool is_in_b_registered = reg_inputb_clk != "NONE"; - - // Similarly, get the output register clock - std::string reg_output_clk = get_clock_parameter("REG_OUTPUT_CLK"); - const bool is_output_registered = reg_output_clk != "NONE"; - - // If only one of the inputs is registered, we are going to treat that as - // neither input registered so that we don't have to deal with mixed timing. - // Emit a warning to that effect. - const bool any_input_registered = is_in_a_registered || is_in_b_registered; - const bool both_inputs_registered = is_in_a_registered && is_in_b_registered; - const bool input_registers_mismatched = any_input_registered && !both_inputs_registered; - if (input_registers_mismatched) { - log_warning("MULT18X18D %s has unsupported mixed input register modes (reg_inputa_clk=%s, " - "reg_inputb_clk=%s)\n", - ci->name.c_str(this), reg_inputa_clk.c_str(), reg_inputb_clk.c_str()); - log_warning("Timings for MULT18X18D %s will be calculated as though neither input were registered\n", - ci->name.c_str(this)); - - // Act as though the inputs are unregistered, so select timing DB based only on the - // output register mode - ci->multInfo.timing_id = is_output_registered ? id_MULT18X18D_REGS_OUTPUT : id_MULT18X18D_REGS_NONE; - } else { - // Based on our register settings, pick the timing data to use for this cell - if (!both_inputs_registered && !is_output_registered) { - ci->multInfo.timing_id = id_MULT18X18D_REGS_NONE; - } else if (both_inputs_registered && !is_output_registered) { - ci->multInfo.timing_id = id_MULT18X18D_REGS_INPUT; - } else if (!both_inputs_registered && is_output_registered) { - ci->multInfo.timing_id = id_MULT18X18D_REGS_OUTPUT; - } else if (both_inputs_registered && is_output_registered) { - ci->multInfo.timing_id = id_MULT18X18D_REGS_ALL; - } + // Get the input clock setting from the cell + std::string reg_inputa_clk = get_clock_parameter("REG_INPUTA_CLK"); + std::string reg_inputb_clk = get_clock_parameter("REG_INPUTB_CLK"); + + // Inputs are registered IFF the REG_INPUT value is not NONE + const bool is_in_a_registered = reg_inputa_clk != "NONE"; + const bool is_in_b_registered = reg_inputb_clk != "NONE"; + + // Similarly, get the output register clock + std::string reg_output_clk = get_clock_parameter("REG_OUTPUT_CLK"); + const bool is_output_registered = reg_output_clk != "NONE"; + + // If only one of the inputs is registered, we are going to treat that as + // neither input registered so that we don't have to deal with mixed timing. + // Emit a warning to that effect. + const bool any_input_registered = is_in_a_registered || is_in_b_registered; + const bool both_inputs_registered = is_in_a_registered && is_in_b_registered; + const bool input_registers_mismatched = any_input_registered && !both_inputs_registered; + if (input_registers_mismatched) { + log_warning("MULT18X18D %s has unsupported mixed input register modes (reg_inputa_clk=%s, " + "reg_inputb_clk=%s)\n", + ci->name.c_str(this), reg_inputa_clk.c_str(), reg_inputb_clk.c_str()); + log_warning("Timings for MULT18X18D %s will be calculated as though neither input were registered\n", + ci->name.c_str(this)); + + // Act as though the inputs are unregistered, so select timing DB based only on the + // output register mode + ci->multInfo.timing_id = is_output_registered ? id_MULT18X18D_REGS_OUTPUT : id_MULT18X18D_REGS_NONE; + } else { + // Based on our register settings, pick the timing data to use for this cell + if (!both_inputs_registered && !is_output_registered) { + ci->multInfo.timing_id = id_MULT18X18D_REGS_NONE; + } else if (both_inputs_registered && !is_output_registered) { + ci->multInfo.timing_id = id_MULT18X18D_REGS_INPUT; + } else if (!both_inputs_registered && is_output_registered) { + ci->multInfo.timing_id = id_MULT18X18D_REGS_OUTPUT; + } else if (both_inputs_registered && is_output_registered) { + ci->multInfo.timing_id = id_MULT18X18D_REGS_ALL; } - // If we aren't a pure combinatorial multiplier, then our timings are - // calculated with respect to CLK0 - ci->multInfo.is_clocked = ci->multInfo.timing_id != id_MULT18X18D_REGS_NONE; } + // If we aren't a pure combinatorial multiplier, then our timings are + // calculated with respect to CLK0 + ci->multInfo.is_clocked = ci->multInfo.timing_id != id_MULT18X18D_REGS_NONE; + } +} + +void Arch::assignArchInfo() +{ + for (auto &cell : cells) { + CellInfo *ci = cell.second.get(); + assign_arch_info_for_cell(ci); } for (auto &net : nets) { net.second->is_global = bool_or_default(net.second->attrs, id_ECP5_IS_GLOBAL); diff --git a/ecp5/trellis_import.py b/ecp5/trellis_import.py index a586db7b..1554ea1a 100755 --- a/ecp5/trellis_import.py +++ b/ecp5/trellis_import.py @@ -311,6 +311,64 @@ timing_port_xform = { } +delay_db = {} +# Convert from Lattice-style grouped SLICE to new nextpnr split style SLICE +def postprocess_timing_data(cells): + def delay_diff(x, y): + return (x[0] - y[0], x[1] - y[1]) + + split_cells = {} + comb_delays = {} + comb_delays[("A", "F")] = delay_db["SLOGICB"][("A0", "F0")] + comb_delays[("B", "F")] = delay_db["SLOGICB"][("B0", "F0")] + comb_delays[("C", "F")] = delay_db["SLOGICB"][("C0", "F0")] + comb_delays[("D", "F")] = delay_db["SLOGICB"][("D0", "F0")] + comb_delays[("A", "OFX")] = delay_db["SLOGICB"][("A0", "OFX0")] + comb_delays[("B", "OFX")] = delay_db["SLOGICB"][("B0", "OFX0")] + comb_delays[("C", "OFX")] = delay_db["SLOGICB"][("C0", "OFX0")] + comb_delays[("D", "OFX")] = delay_db["SLOGICB"][("D0", "OFX0")] + comb_delays[("M", "OFX")] = delay_db["SLOGICB"][("M0", "OFX0")] # worst case + comb_delays[("F1", "OFX")] = delay_diff(delay_db["SLOGICB"][("A1", "OFX0")], + delay_db["SLOGICB"][("A1", "F1")]) + comb_delays[("FXA", "OFX")] = delay_db["SLOGICB"][("FXA", "OFX1")] + comb_delays[("FXB", "OFX")] = delay_db["SLOGICB"][("FXB", "OFX1")] + split_cells["TRELLIS_COMB"] = comb_delays + + carry0_delays = {} + carry0_delays[("A", "F")] = delay_db["SCCU2C"][("A0", "F0")] + carry0_delays[("B", "F")] = delay_db["SCCU2C"][("B0", "F0")] + carry0_delays[("C", "F")] = delay_db["SCCU2C"][("C0", "F0")] + carry0_delays[("D", "F")] = delay_db["SCCU2C"][("D0", "F0")] + carry0_delays[("A", "FCO")] = delay_db["SCCU2C"][("A0", "FCO")] + carry0_delays[("B", "FCO")] = delay_db["SCCU2C"][("B0", "FCO")] + carry0_delays[("C", "FCO")] = delay_db["SCCU2C"][("C0", "FCO")] + carry0_delays[("D", "FCO")] = delay_db["SCCU2C"][("D0", "FCO")] + carry0_delays[("FCI", "F")] = delay_db["SCCU2C"][("FCI", "F0")] + carry0_delays[("FCI", "FCO")] = delay_db["SCCU2C"][("FCI", "FCO")] + + split_cells["TRELLIS_COMB_CARRY0"] = carry0_delays + + carry1_delays = {} + carry1_delays[("A", "F")] = delay_db["SCCU2C"][("A1", "F1")] + carry1_delays[("B", "F")] = delay_db["SCCU2C"][("B1", "F1")] + carry1_delays[("C", "F")] = delay_db["SCCU2C"][("C1", "F1")] + carry1_delays[("D", "F")] = delay_db["SCCU2C"][("D1", "F1")] + carry1_delays[("A", "FCO")] = delay_db["SCCU2C"][("A1", "FCO")] + carry1_delays[("B", "FCO")] = delay_db["SCCU2C"][("B1", "FCO")] + carry1_delays[("C", "FCO")] = delay_db["SCCU2C"][("C1", "FCO")] + carry1_delays[("D", "FCO")] = delay_db["SCCU2C"][("D1", "FCO")] + carry1_delays[("FCI", "F")] = delay_diff(delay_db["SCCU2C"][("FCI", "F1")], delay_db["SCCU2C"][("FCI", "FCO")]) + carry1_delays[("FCI", "FCO")] = (0, 0) + + split_cells["TRELLIS_COMB_CARRY1"] = carry1_delays + + for celltype, celldelays in sorted(split_cells.items()): + delays = [] + setupholds = [] + for (from_pin, to_pin), (min_delay, max_delay) in sorted(celldelays.items()): + delays.append((constids[from_pin], constids[to_pin], min_delay, max_delay)) + cells.append((constids[celltype], delays, setupholds)) + def process_timing_data(): for grade in speed_grade_names: with open(timing_dbs.cells_db_path("ECP5", grade)) as f: @@ -320,6 +378,7 @@ def process_timing_data(): celltype = constids[cell.replace(":", "_").replace("=", "_").replace(",", "_")] delays = [] setupholds = [] + delay_db[cell] = {} for entry in cdata: if entry["type"] == "Width": continue @@ -332,6 +391,7 @@ def process_timing_data(): to_pin = timing_port_xform[to_pin] min_delay = min(entry["rising"][0], entry["falling"][0]) max_delay = min(entry["rising"][2], entry["falling"][2]) + delay_db[cell][(from_pin, to_pin)] = (min_delay, max_delay) delays.append((constids[from_pin], constids[to_pin], min_delay, max_delay)) elif entry["type"] == "SetupHold": if type(entry["pin"]) is list: @@ -346,6 +406,7 @@ def process_timing_data(): else: assert False, entry["type"] cells.append((celltype, delays, setupholds)) + postprocess_timing_data(cells) pip_class_delays = [] for i in range(len(pip_class_to_idx)): pip_class_delays.append((50, 50, 0, 0)) @@ -625,7 +686,7 @@ def main(): # print("Initialising chip...") chip = pytrellis.Chip(dev_names[args.device]) # print("Building routing graph...") - ddrg = pytrellis.make_dedup_chipdb(chip, include_lutperm_pips=True) + ddrg = pytrellis.make_dedup_chipdb(chip, include_lutperm_pips=True, split_slice_mode=True) max_row = chip.get_max_row() max_col = chip.get_max_col() process_timing_data() |