diff options
author | Miodrag Milanović <mmicko@gmail.com> | 2020-11-30 10:56:59 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-11-30 10:56:59 +0100 |
commit | 8b5c0dc1e49b692b0bb598a90034c27db653622d (patch) | |
tree | afd13c654df1faa0b5df9f11be74eede087fa564 /nexus | |
parent | 1afa494e69e3c8af3dd5d1685b9cd2b1d3bea4d0 (diff) | |
parent | 2fe8bebc6ce464afadef2403a8331031e16c5a5d (diff) | |
download | nextpnr-8b5c0dc1e49b692b0bb598a90034c27db653622d.tar.gz nextpnr-8b5c0dc1e49b692b0bb598a90034c27db653622d.tar.bz2 nextpnr-8b5c0dc1e49b692b0bb598a90034c27db653622d.zip |
Merge pull request #524 from daveshah1/nextpnr-nexus
Upstreaming basic support for Nexus devices
Diffstat (limited to 'nexus')
-rw-r--r-- | nexus/.gitignore | 1 | ||||
-rw-r--r-- | nexus/CMakeLists.txt | 57 | ||||
-rw-r--r-- | nexus/arch.cc | 963 | ||||
-rw-r--r-- | nexus/arch.h | 1586 | ||||
-rw-r--r-- | nexus/arch_place.cc | 118 | ||||
-rw-r--r-- | nexus/arch_pybindings.cc | 72 | ||||
-rw-r--r-- | nexus/arch_pybindings.h | 98 | ||||
-rw-r--r-- | nexus/archdefs.h | 253 | ||||
-rw-r--r-- | nexus/bba_version.inc | 1 | ||||
-rw-r--r-- | nexus/constids.inc | 377 | ||||
-rw-r--r-- | nexus/family.cmake | 53 | ||||
-rw-r--r-- | nexus/fasm.cc | 669 | ||||
-rw-r--r-- | nexus/global.cc | 168 | ||||
-rw-r--r-- | nexus/io.cc | 70 | ||||
-rw-r--r-- | nexus/main.cc | 99 | ||||
-rw-r--r-- | nexus/pack.cc | 1866 | ||||
-rw-r--r-- | nexus/pdc.cc | 352 | ||||
-rw-r--r-- | nexus/pins.cc | 180 | ||||
-rw-r--r-- | nexus/post_place.cc | 161 |
19 files changed, 7144 insertions, 0 deletions
diff --git a/nexus/.gitignore b/nexus/.gitignore new file mode 100644 index 00000000..0cb37421 --- /dev/null +++ b/nexus/.gitignore @@ -0,0 +1 @@ +/chipdb/ diff --git a/nexus/CMakeLists.txt b/nexus/CMakeLists.txt new file mode 100644 index 00000000..d58bd689 --- /dev/null +++ b/nexus/CMakeLists.txt @@ -0,0 +1,57 @@ +cmake_minimum_required(VERSION 3.5) +project(chipdb-nexus NONE) + +set(ALL_NEXUS_FAMILIES LIFCL) + +# NOTE: Unlike iCE40 and ECP5; one database can cover all densities of a given family + +set(NEXUS_FAMILIES ${ALL_NEXUS_FAMILIES} CACHE STRING + "Include support for these Nexus families (available: ${ALL_NEXUS_FAMILIES})") +message(STATUS "Enabled Nexus families: ${NEXUS_FAMILIES}") + +if(DEFINED NEXUS_CHIPDB) + add_custom_target(chipdb-nexus-bbas ALL) +else() + # shared among all families + set(OXIDE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}" CACHE STRING + "prjoxide install prefix") + message(STATUS "prjoxide install prefix: ${OXIDE_INSTALL_PREFIX}") + + set(all_device_bbas) + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/chipdb) + foreach(subfamily ${NEXUS_FAMILIES}) + if(NOT subfamily IN_LIST ALL_NEXUS_FAMILIES) + message(FATAL_ERROR "${subfamily} is not a supported Nexus family") + endif() + + set(family_bba chipdb/chipdb-${subfamily}.bba) + set(PRJOXIDE_TOOL ${OXIDE_INSTALL_PREFIX}/bin/prjoxide) + add_custom_command( + OUTPUT ${family_bba} + COMMAND + ${PRJOXIDE_TOOL} bba-export ${subfamily} ${CMAKE_CURRENT_SOURCE_DIR}/constids.inc ${family_bba}.new + # atomically update + COMMAND ${CMAKE_COMMAND} -E rename ${family_bba}.new ${family_bba} + DEPENDS + ${PRJOXIDE_TOOL} + ${CMAKE_CURRENT_SOURCE_DIR}/constids.inc + ${CMAKE_CURRENT_SOURCE_DIR}/bba_version.inc + ${PREVIOUS_CHIPDB_TARGET} + VERBATIM) + list(APPEND all_device_bbas ${family_bba}) + if(SERIALIZE_CHIPDBS) + set(PREVIOUS_CHIPDB_TARGET ${CMAKE_CURRENT_BINARY_DIR}/${family_bba}) + endif() + endforeach() + + add_custom_target(chipdb-nexus-bbas ALL DEPENDS ${all_device_bbas}) + + get_directory_property(has_parent PARENT_DIRECTORY) + if(has_parent) + set(NEXUS_CHIPDB ${CMAKE_CURRENT_BINARY_DIR}/chipdb PARENT_SCOPE) + # serialize chipdb build across multiple architectures + set(PREVIOUS_CHIPDB_TARGET chipdb-nexus-bbas PARENT_SCOPE) + else() + message(STATUS "Build nextpnr with -DNEXUS_CHIPDB=${CMAKE_CURRENT_BINARY_DIR}/chipdb") + endif() +endif() diff --git a/nexus/arch.cc b/nexus/arch.cc new file mode 100644 index 00000000..f222a5ad --- /dev/null +++ b/nexus/arch.cc @@ -0,0 +1,963 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2020 David Shah <dave@ds0.me> + * + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include <boost/algorithm/string.hpp> + +#include "embed.h" +#include "log.h" +#include "nextpnr.h" +#include "placer1.h" +#include "placer_heap.h" +#include "router1.h" +#include "router2.h" +#include "timing.h" +#include "util.h" + +NEXTPNR_NAMESPACE_BEGIN + +namespace { +static std::tuple<int, int, std::string> split_identifier_name(const std::string &name) +{ + size_t first_slash = name.find('/'); + NPNR_ASSERT(first_slash != std::string::npos); + size_t second_slash = name.find('/', first_slash + 1); + NPNR_ASSERT(second_slash != std::string::npos); + return std::make_tuple(std::stoi(name.substr(1, first_slash)), + std::stoi(name.substr(first_slash + 2, second_slash - first_slash)), + name.substr(second_slash + 1)); +}; + +} // namespace + +// ----------------------------------------------------------------------- + +void IdString::initialize_arch(const BaseCtx *ctx) +{ +#define X(t) initialize_add(ctx, #t, ID_##t); + +#include "constids.inc" + +#undef X +} + +// ----------------------------------------------------------------------- + +Arch::Arch(ArchArgs args) : args(args) +{ + // Parse device string + if (boost::starts_with(args.device, "LIFCL")) { + family = "LIFCL"; + } else { + log_error("Unknown device string '%s' (expected device name like 'LIFCL-40-8SG72C')\n", args.device.c_str()); + } + auto last_sep = args.device.rfind('-'); + if (last_sep == std::string::npos) + log_error("Unknown device string '%s' (expected device name like 'LIFCL-40-8SG72C')\n", args.device.c_str()); + device = args.device.substr(0, last_sep); + speed = args.device.substr(last_sep + 1, 1); + auto package_end = args.device.find_last_of("0123456789"); + if (package_end == std::string::npos || package_end < last_sep) + log_error("Unknown device string '%s' (expected device name like 'LIFCL-40-8SG72C')\n", args.device.c_str()); + package = args.device.substr(last_sep + 2, (package_end - (last_sep + 2)) + 1); + rating = args.device.substr(package_end + 1); + + // Check for 'ES' part + if (rating.size() > 1 && rating.substr(1) == "ES") { + variant = "ES"; + } else { + variant = ""; + } + + // Load database + std::string chipdb = stringf("nexus/chipdb-%s.bin", family.c_str()); + auto db_ptr = reinterpret_cast<const RelPtr<DatabasePOD> *>(get_chipdb(chipdb)); + if (db_ptr == nullptr) + log_error("Failed to load chipdb '%s'\n", chipdb.c_str()); + db = db_ptr->get(); + // Check database version and family + if (db->version != bba_version) { + log_error("Provided database version %d is %s than nextpnr version %d, please rebuild database/nextpnr.\n", + int(db->version), (db->version > bba_version) ? "newer" : "older", int(bba_version)); + } + if (db->family.get() != family) { + log_error("Database is for family '%s' but provided device is family '%s'.\n", db->family.get(), + family.c_str()); + } + // Set up chip_info + chip_info = nullptr; + for (size_t i = 0; i < db->num_chips; i++) { + auto &chip = db->chips[i]; + if (chip.device_name.get() == device) { + chip_info = &chip; + break; + } + } + if (!chip_info) + log_error("Unknown device '%s'.\n", device.c_str()); + // Set up bba IdStrings + for (size_t i = 0; i < db->ids->num_bba_ids; i++) { + IdString::initialize_add(this, db->ids->bba_id_strs[i].get(), uint32_t(i) + db->ids->num_file_ids); + } + // Set up validity structures + tileStatus.resize(chip_info->num_tiles); + for (size_t i = 0; i < chip_info->num_tiles; i++) { + tileStatus[i].boundcells.resize(db->loctypes[chip_info->grid[i].loc_type].num_bels); + } + // This structure is needed for a fast getBelByLocation because bels can have an offset + for (size_t i = 0; i < chip_info->num_tiles; i++) { + auto &loc = db->loctypes[chip_info->grid[i].loc_type]; + for (unsigned j = 0; j < loc.num_bels; j++) { + auto &bel = loc.bels[j]; + int rel_bel_tile; + if (!rel_tile(i, bel.rel_x, bel.rel_y, rel_bel_tile)) + continue; + auto &ts = tileStatus.at(rel_bel_tile); + if (int(ts.bels_by_z.size()) <= bel.z) + ts.bels_by_z.resize(bel.z + 1); + ts.bels_by_z[bel.z].tile = i; + ts.bels_by_z[bel.z].index = j; + } + } + init_cell_pin_data(); + // Validate and set up package + package_idx = -1; + for (size_t i = 0; i < chip_info->num_packages; i++) { + if (package == chip_info->packages[i].short_name.get()) { + package_idx = i; + break; + } + } + if (package_idx == -1) { + std::string all_packages = ""; + for (size_t i = 0; i < chip_info->num_packages; i++) { + all_packages += " "; + all_packages += chip_info->packages[i].short_name.get(); + } + log_error("Unknown package '%s'. Available package options:%s\n", package.c_str(), all_packages.c_str()); + } + + // Validate and set up speed grade + + // Convert speed to speed grade (TODO: low power back bias mode too) + if (speed == "7") + speed = "10"; + else if (speed == "8") + speed = "11"; + else if (speed == "9") + speed = "12"; + + speed_grade = nullptr; + for (size_t i = 0; i < db->num_speed_grades; i++) { + auto &sg = db->speed_grades[i]; + if (sg.name.get() == speed) { + speed_grade = &sg; + break; + } + } + if (!speed_grade) + log_error("Unknown speed grade '%s'.\n", speed.c_str()); +} + +// ----------------------------------------------------------------------- + +std::string Arch::getChipName() const { return args.device; } +IdString Arch::archArgsToId(ArchArgs args) const { return id(args.device); } + +// ----------------------------------------------------------------------- + +BelId Arch::getBelByName(IdString name) const +{ + int x, y; + std::string belname; + std::tie(x, y, belname) = split_identifier_name(name.str(this)); + NPNR_ASSERT(x >= 0 && x < chip_info->width); + NPNR_ASSERT(y >= 0 && y < chip_info->height); + auto &tile = db->loctypes[chip_info->grid[y * chip_info->width + x].loc_type]; + IdString bn = id(belname); + for (size_t i = 0; i < tile.num_bels; i++) { + if (tile.bels[i].name == bn.index) { + BelId ret; + ret.tile = y * chip_info->width + x; + ret.index = i; + return ret; + } + } + return BelId(); +} + +std::vector<BelId> Arch::getBelsByTile(int x, int y) const +{ + std::vector<BelId> bels; + for (auto bel : tileStatus.at(y * chip_info->width + x).bels_by_z) + if (bel != BelId()) + bels.push_back(bel); + return bels; +} + +WireId Arch::getBelPinWire(BelId bel, IdString pin) const +{ + // Binary search on wire IdString, by ID + int num_bel_wires = bel_data(bel).num_ports; + const BelWirePOD *bel_ports = bel_data(bel).ports.get(); + + if (num_bel_wires < 7) { + for (int i = 0; i < num_bel_wires; i++) { + if (int(bel_ports[i].port) == pin.index) { + return canonical_wire(bel.tile, bel_ports[i].wire_index); + } + } + } else { + int b = 0, e = num_bel_wires - 1; + while (b <= e) { + int i = (b + e) / 2; + if (int(bel_ports[i].port) == pin.index) { + return canonical_wire(bel.tile, bel_ports[i].wire_index); + } + if (int(bel_ports[i].port) > pin.index) + e = i - 1; + else + b = i + 1; + } + } + + return WireId(); +} + +PortType Arch::getBelPinType(BelId bel, IdString pin) const +{ + // Binary search on wire IdString, by ID + int num_bel_wires = bel_data(bel).num_ports; + const BelWirePOD *bel_ports = bel_data(bel).ports.get(); + + if (num_bel_wires < 7) { + for (int i = 0; i < num_bel_wires; i++) { + if (int(bel_ports[i].port) == pin.index) { + return PortType(bel_ports[i].type); + } + } + } else { + int b = 0, e = num_bel_wires - 1; + while (b <= e) { + int i = (b + e) / 2; + if (int(bel_ports[i].port) == pin.index) { + return PortType(bel_ports[i].type); + } + if (int(bel_ports[i].port) > pin.index) + e = i - 1; + else + b = i + 1; + } + } + + NPNR_ASSERT_FALSE("unknown bel pin"); +} + +std::vector<IdString> Arch::getBelPins(BelId bel) const +{ + std::vector<IdString> ret; + int num_bel_wires = bel_data(bel).num_ports; + const BelWirePOD *bel_ports = bel_data(bel).ports.get(); + for (int i = 0; i < num_bel_wires; i++) + ret.push_back(IdString(bel_ports[i].port)); + return ret; +} + +std::vector<std::pair<IdString, std::string>> Arch::getBelAttrs(BelId bel) const +{ + std::vector<std::pair<IdString, std::string>> ret; + + ret.emplace_back(id("INDEX"), stringf("%d", bel.index)); + + ret.emplace_back(id("GRID_X"), stringf("%d", bel.tile % chip_info->width)); + ret.emplace_back(id("GRID_Y"), stringf("%d", bel.tile / chip_info->width)); + ret.emplace_back(id("BEL_Z"), stringf("%d", bel_data(bel).z)); + + ret.emplace_back(id("BEL_TYPE"), nameOf(getBelType(bel))); + + return ret; +} + +// ----------------------------------------------------------------------- + +WireId Arch::getWireByName(IdString name) const +{ + int x, y; + std::string wirename; + std::tie(x, y, wirename) = split_identifier_name(name.str(this)); + NPNR_ASSERT(x >= 0 && x < chip_info->width); + NPNR_ASSERT(y >= 0 && y < chip_info->height); + auto &tile = db->loctypes[chip_info->grid[y * chip_info->width + x].loc_type]; + IdString wn = id(wirename); + for (size_t i = 0; i < tile.num_wires; i++) { + if (tile.wires[i].name == wn.index) { + WireId ret; + ret.tile = y * chip_info->width + x; + ret.index = i; + return ret; + } + } + return WireId(); +} + +IdString Arch::getWireType(WireId wire) const { return id("WIRE"); } + +std::vector<std::pair<IdString, std::string>> Arch::getWireAttrs(WireId wire) const +{ + std::vector<std::pair<IdString, std::string>> ret; + + ret.emplace_back(id("INDEX"), stringf("%d", wire.index)); + + ret.emplace_back(id("GRID_X"), stringf("%d", wire.tile % chip_info->width)); + ret.emplace_back(id("GRID_Y"), stringf("%d", wire.tile / chip_info->width)); + ret.emplace_back(id("FLAGS"), stringf("%u", wire_data(wire).flags)); + + return ret; +} + +// ----------------------------------------------------------------------- + +PipId Arch::getPipByName(IdString name) const +{ + int x, y; + std::string pipname; + std::tie(x, y, pipname) = split_identifier_name(name.str(this)); + NPNR_ASSERT(x >= 0 && x < chip_info->width); + NPNR_ASSERT(y >= 0 && y < chip_info->height); + PipId ret; + ret.tile = y * chip_info->width + x; + auto sep_pos = pipname.find(':'); + ret.index = std::stoi(pipname.substr(0, sep_pos)); + return ret; +} + +IdString Arch::getPipName(PipId pip) const +{ + NPNR_ASSERT(pip != PipId()); + return id(stringf("X%d/Y%d/%d:%s->%s", pip.tile % chip_info->width, pip.tile / chip_info->width, pip.index, + nameOf(loc_data(pip).wires[pip_data(pip).from_wire].name), + nameOf(loc_data(pip).wires[pip_data(pip).to_wire].name))); +} + +IdString Arch::getPipType(PipId pip) const { return IdString(); } + +std::vector<std::pair<IdString, std::string>> Arch::getPipAttrs(PipId pip) const +{ + std::vector<std::pair<IdString, std::string>> ret; + + ret.emplace_back(id("INDEX"), stringf("%d", pip.index)); + + ret.emplace_back(id("GRID_X"), stringf("%d", pip.tile % chip_info->width)); + ret.emplace_back(id("GRID_Y"), stringf("%d", pip.tile / chip_info->width)); + + ret.emplace_back(id("FROM_TILE_WIRE"), nameOf(loc_data(pip).wires[pip_data(pip).from_wire].name)); + ret.emplace_back(id("TO_TILE_WIRE"), nameOf(loc_data(pip).wires[pip_data(pip).to_wire].name)); + + return ret; +} + +// ----------------------------------------------------------------------- + +namespace { +const float bel_ofs_x = 0.7, bel_ofs_y = 0.0375; +const float bel_sp_x = 0.1, bel_sp_y = 0.1; +const float bel_width = 0.075, bel_height = 0.075; +} // namespace + +std::vector<GraphicElement> Arch::getDecalGraphics(DecalId decal) const +{ + std::vector<GraphicElement> ret; + + switch (decal.type) { + case DecalId::TYPE_BEL: { + auto style = decal.active ? GraphicElement::STYLE_ACTIVE : GraphicElement::STYLE_INACTIVE; + if (decal.index != -1) { + int slice = (decal.index >> 3) & 0x3; + int bel = decal.index & 0x7; + float x1, x2, y1, y2; + if (bel == BEL_RAMW) { + x1 = bel_ofs_x; + y1 = bel_ofs_y + 2 * bel_sp_y * slice; + x2 = x1 + bel_sp_x + bel_width; + y2 = y1 + bel_height; + } else { + x1 = bel_ofs_x + bel_sp_x * (bel >> 1); + y1 = bel_ofs_y + 2 * bel_sp_y * slice + bel_sp_y * (bel & 0x1); + if (slice >= 2) + y1 += bel_sp_y * 1.5; + x2 = x1 + bel_width; + y2 = y1 + bel_height; + } + ret.emplace_back(GraphicElement::TYPE_BOX, style, x1, y1, x2, y2, 1); + } + break; + }; + default: + break; + } + + return ret; +} + +DecalXY Arch::getBelDecal(BelId bel) const +{ + DecalXY decalxy; + decalxy.decal.type = DecalId::TYPE_BEL; + if (tile_is(bel, LOC_LOGIC)) + decalxy.decal.index = bel_data(bel).z; + else + decalxy.decal.index = -1; + decalxy.decal.active = (getBoundBelCell(bel) != nullptr); + decalxy.x = bel.tile % chip_info->width; + decalxy.y = bel.tile / chip_info->width; + return decalxy; +} + +DecalXY Arch::getWireDecal(WireId wire) const { return {}; } + +DecalXY Arch::getPipDecal(PipId pip) const { return {}; }; + +DecalXY Arch::getGroupDecal(GroupId pip) const { return {}; }; + +// ----------------------------------------------------------------------- + +bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const +{ + auto lookup_port = [&](IdString p) { + auto fnd = cell->tmg_portmap.find(p); + return fnd == cell->tmg_portmap.end() ? p : fnd->second; + }; + if (cell->type == id_OXIDE_COMB) { + if (cell->lutInfo.is_carry) { + bool result = lookup_cell_delay(cell->tmg_index, lookup_port(fromPort), lookup_port(toPort), delay); + // Because CCU2 = 2x OXIDE_COMB + if (result && fromPort == id_FCI && toPort == id_FCO) { + delay.min_delay /= 2; + delay.max_delay /= 2; + } + return result; + } else { + if (toPort == id_F || toPort == id_OFX) + return lookup_cell_delay(cell->tmg_index, fromPort, toPort, delay); + } + } else if (is_dsp_cell(cell)) { + if (fromPort == id_CLK) + return false; // don't include delays that are actually clock-to-out here + return lookup_cell_delay(cell->tmg_index, lookup_port(fromPort), lookup_port(toPort), delay); + } + return false; +} + +TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const +{ + auto disconnected = [cell](IdString p) { return !cell->ports.count(p) || cell->ports.at(p).net == nullptr; }; + auto lookup_port = [&](IdString p) { + auto fnd = cell->tmg_portmap.find(p); + return fnd == cell->tmg_portmap.end() ? p : fnd->second; + }; + clockInfoCount = 0; + if (cell->type == id_OXIDE_COMB) { + if (port == id_A || port == id_B || port == id_C || port == id_D || port == id_SEL || port == id_F1 || + port == id_FCI || port == id_WDI) + return TMG_COMB_INPUT; + if (port == id_F || port == id_OFX || port == id_FCO) { + if (disconnected(id_A) && disconnected(id_B) && disconnected(id_C) && disconnected(id_D) && + disconnected(id_FCI) && disconnected(id_SEL) && disconnected(id_WDI)) + return TMG_IGNORE; + else + return TMG_COMB_OUTPUT; + } + } else if (cell->type == id_OXIDE_FF) { + if (port == id_CLK) + return TMG_CLOCK_INPUT; + else if (port == id_Q) { + clockInfoCount = 1; + return TMG_REGISTER_OUTPUT; + } else { + clockInfoCount = 1; + return TMG_REGISTER_INPUT; + } + } else if (cell->type == id_RAMW) { + if (port == id_CLK) + return TMG_CLOCK_INPUT; + else if (port == id_WDO0 || port == id_WDO1 || port == id_WDO2 || port == id_WDO3) { + clockInfoCount = 1; + return TMG_REGISTER_OUTPUT; + } else 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) { + clockInfoCount = 1; + return TMG_REGISTER_INPUT; + } + } else if (cell->type == id_OXIDE_EBR) { + if (port == id_DWS0 || port == id_DWS1 || port == id_DWS2 || port == id_DWS3 || port == id_DWS4) + return TMG_IGNORE; + if (port == id_CLKA || port == id_CLKB) + return TMG_CLOCK_INPUT; + clockInfoCount = 1; + return (cell->ports.at(port).type == PORT_IN) ? TMG_REGISTER_INPUT : TMG_REGISTER_OUTPUT; + } else if (cell->type == id_MULT18_CORE || cell->type == id_MULT18X36_CORE || cell->type == id_MULT36_CORE) { + return (cell->ports.at(port).type == PORT_IN) ? TMG_COMB_INPUT : TMG_COMB_OUTPUT; + } else if (cell->type == id_PREADD9_CORE || cell->type == id_REG18_CORE || cell->type == id_MULT9_CORE) { + if (port == id_CLK) + return TMG_CLOCK_INPUT; + auto type = lookup_port_type(cell->tmg_index, lookup_port(port), cell->ports.at(port).type, id_CLK); + if (type == TMG_REGISTER_INPUT || type == TMG_REGISTER_OUTPUT) + clockInfoCount = 1; + return type; + } + return TMG_IGNORE; +} + +TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port, int index) const +{ + auto lookup_port = [&](IdString p) { + auto fnd = cell->tmg_portmap.find(p); + return fnd == cell->tmg_portmap.end() ? p : fnd->second; + }; + TimingClockingInfo info; + if (cell->type == id_OXIDE_FF) { + info.edge = (cell->ffInfo.ctrlset.clkmux == ID_INV) ? FALLING_EDGE : RISING_EDGE; + info.clock_port = id_CLK; + if (port == id_Q) + NPNR_ASSERT(lookup_cell_delay(cell->tmg_index, id_CLK, port, info.clockToQ)); + else + lookup_cell_setuphold(cell->tmg_index, port, id_CLK, info.setup, info.hold); + } else if (cell->type == id_RAMW) { + info.edge = (cell->ffInfo.ctrlset.clkmux == ID_INV) ? FALLING_EDGE : RISING_EDGE; + info.clock_port = id_CLK; + if (port == id_WDO0 || port == id_WDO1 || port == id_WDO2 || port == id_WDO3) + NPNR_ASSERT(lookup_cell_delay(cell->tmg_index, id_CLK, port, info.clockToQ)); + else + lookup_cell_setuphold(cell->tmg_index, port, id_CLK, info.setup, info.hold); + } else if (cell->type == id_OXIDE_EBR) { + if (cell->ports.at(port).type == PORT_IN) { + lookup_cell_setuphold_clock(cell->tmg_index, lookup_port(port), info.clock_port, info.setup, info.hold); + } else { + lookup_cell_clock_out(cell->tmg_index, lookup_port(port), info.clock_port, info.clockToQ); + } + // Lookup edge based on inversion + info.edge = (get_cell_pinmux(cell, info.clock_port) == PINMUX_INV) ? FALLING_EDGE : RISING_EDGE; + } else if (cell->type == id_PREADD9_CORE || cell->type == id_REG18_CORE || cell->type == id_MULT9_CORE) { + info.clock_port = id_CLK; + if (cell->ports.at(port).type == PORT_IN) { + lookup_cell_setuphold(cell->tmg_index, lookup_port(port), id_CLK, info.setup, info.hold); + } else { + NPNR_ASSERT(lookup_cell_delay(cell->tmg_index, id_CLK, lookup_port(port), info.clockToQ)); + } + info.edge = (get_cell_pinmux(cell, info.clock_port) == PINMUX_INV) ? FALLING_EDGE : RISING_EDGE; + } else { + NPNR_ASSERT_FALSE("missing clocking info"); + } + return info; +} + +// ----------------------------------------------------------------------- + +delay_t Arch::estimateDelay(WireId src, WireId dst) const +{ + int src_x = src.tile % chip_info->width, src_y = src.tile / chip_info->width; + int dst_x = dst.tile % chip_info->width, dst_y = dst.tile / chip_info->width; + int dist_x = std::abs(src_x - dst_x); + int dist_y = std::abs(src_y - dst_y); + return 75 * dist_x + 75 * dist_y + 200; +} +delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const +{ + if (net_info->driver.cell == nullptr || net_info->driver.cell->bel == BelId() || sink.cell->bel == BelId()) + return 0; + if (sink.port == id_FCI) + return 0; + int src_x = net_info->driver.cell->bel.tile % chip_info->width, + src_y = net_info->driver.cell->bel.tile / chip_info->width; + + int dst_x = sink.cell->bel.tile % chip_info->width, dst_y = sink.cell->bel.tile / chip_info->width; + int dist_x = std::abs(src_x - dst_x); + int dist_y = std::abs(src_y - dst_y); + return 100 * dist_x + 100 * dist_y + 250; +} + +bool Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const { return false; } + +ArcBounds Arch::getRouteBoundingBox(WireId src, WireId dst) const +{ + ArcBounds bb; + + int src_x = src.tile % chip_info->width, src_y = src.tile / chip_info->width; + int dst_x = dst.tile % chip_info->width, dst_y = dst.tile / chip_info->width; + + bb.x0 = src_x; + bb.y0 = src_y; + bb.x1 = src_x; + bb.y1 = src_y; + + auto extend = [&](int x, int y) { + bb.x0 = std::min(bb.x0, x); + bb.x1 = std::max(bb.x1, x); + bb.y0 = std::min(bb.y0, y); + bb.y1 = std::max(bb.y1, y); + }; + + extend(dst_x, dst_y); + + if (dsp_wires.count(src) || dsp_wires.count(dst)) { + bb.x0 = std::max<int>(0, bb.x0 - 6); + bb.x1 = std::min<int>(chip_info->width, bb.x1 + 6); + } + + return bb; +} + +// ----------------------------------------------------------------------- + +bool Arch::place() +{ + std::string placer = str_or_default(settings, id("placer"), defaultPlacer); + + if (placer == "heap") { + PlacerHeapCfg cfg(getCtx()); + cfg.ioBufTypes.insert(id_SEIO33_CORE); + cfg.ioBufTypes.insert(id_SEIO18_CORE); + cfg.ioBufTypes.insert(id_OSC_CORE); + cfg.cellGroups.emplace_back(); + cfg.cellGroups.back().insert(id_OXIDE_COMB); + cfg.cellGroups.back().insert(id_OXIDE_FF); + + cfg.beta = 0.5; + cfg.criticalityExponent = 7; + if (!placer_heap(getCtx(), cfg)) + return false; + } else if (placer == "sa") { + if (!placer1(getCtx(), Placer1Cfg(getCtx()))) + return false; + } else { + log_error("Nexus architecture does not support placer '%s'\n", placer.c_str()); + } + + post_place_opt(); + + getCtx()->attrs[getCtx()->id("step")] = std::string("place"); + archInfoToAttributes(); + return true; +} + +void Arch::pre_routing() +{ + for (auto cell : sorted(cells)) { + CellInfo *ci = cell.second; + if (ci->type == id_MULT9_CORE || ci->type == id_PREADD9_CORE || ci->type == id_MULT18_CORE || + ci->type == id_MULT18X36_CORE || ci->type == id_MULT36_CORE || ci->type == id_REG18_CORE || + ci->type == id_ACC54_CORE) { + for (auto port : sorted_ref(ci->ports)) { + WireId wire = getBelPinWire(ci->bel, port.first); + if (wire != WireId()) + dsp_wires.insert(wire); + } + } + } +} + +bool Arch::route() +{ + assign_budget(getCtx(), true); + + pre_routing(); + + route_globals(); + + std::string router = str_or_default(settings, id("router"), defaultRouter); + bool result; + if (router == "router1") { + result = router1(getCtx(), Router1Cfg(getCtx())); + } else if (router == "router2") { + router2(getCtx(), Router2Cfg(getCtx())); + result = true; + } else { + log_error("iCE40 architecture does not support router '%s'\n", router.c_str()); + } + getCtx()->attrs[getCtx()->id("step")] = std::string("route"); + archInfoToAttributes(); + return result; +} + +// ----------------------------------------------------------------------- + +CellPinMux Arch::get_cell_pinmux(const CellInfo *cell, IdString pin) const +{ + IdString param = id(stringf("%sMUX", pin.c_str(this))); + auto fnd_param = cell->params.find(param); + if (fnd_param == cell->params.end()) + return PINMUX_SIG; + const std::string &pm = fnd_param->second.as_string(); + if (pm == "0") + return PINMUX_0; + else if (pm == "1") + return PINMUX_1; + else if (pm == "INV") + return PINMUX_INV; + else if (pm == pin.c_str(this)) + return PINMUX_SIG; + else { + log_error("Invalid %s setting '%s' for cell '%s'\n", nameOf(param), pm.c_str(), nameOf(cell)); + NPNR_ASSERT_FALSE("unreachable"); + } +} + +void Arch::set_cell_pinmux(CellInfo *cell, IdString pin, CellPinMux state) +{ + IdString param = id(stringf("%sMUX", pin.c_str(this))); + switch (state) { + case PINMUX_SIG: + cell->params.erase(param); + break; + case PINMUX_0: + cell->params[param] = std::string("0"); + break; + case PINMUX_1: + cell->params[param] = std::string("1"); + break; + case PINMUX_INV: + cell->params[param] = std::string("INV"); + break; + default: + NPNR_ASSERT_FALSE("unreachable"); + } +} + +// ----------------------------------------------------------------------- + +const PadInfoPOD *Arch::get_pkg_pin_data(const std::string &pin) const +{ + for (size_t i = 0; i < chip_info->num_pads; i++) { + const PadInfoPOD *pad = &(chip_info->pads[i]); + if (pin == pad->pins[package_idx].get()) + return pad; + } + return nullptr; +} + +Loc Arch::get_pad_loc(const PadInfoPOD *pad) const +{ + Loc loc; + switch (pad->side) { + case PIO_LEFT: + loc.x = 0; + loc.y = pad->offset; + break; + case PIO_RIGHT: + loc.x = chip_info->width - 1; + loc.y = pad->offset; + break; + case PIO_TOP: + loc.x = pad->offset; + loc.y = 0; + break; + case PIO_BOTTOM: + loc.x = pad->offset; + loc.y = chip_info->height - 1; + } + loc.z = pad->pio_index; + return loc; +} + +BelId Arch::get_pad_pio_bel(const PadInfoPOD *pad) const +{ + if (pad == nullptr) + return BelId(); + return getBelByLocation(get_pad_loc(pad)); +} + +const PadInfoPOD *Arch::get_bel_pad(BelId bel) const +{ + Loc loc = getBelLocation(bel); + int side = -1, offset = -1; + // Convert (x, y) to (side, offset) + if (loc.x == 0) { + side = PIO_LEFT; + offset = loc.y; + } else if (loc.x == (chip_info->width - 1)) { + side = PIO_RIGHT; + offset = loc.y; + } else if (loc.y == 0) { + side = PIO_TOP; + offset = loc.x; + } else if (loc.y == (chip_info->height - 1)) { + side = PIO_BOTTOM; + offset = loc.x; + } else { + return nullptr; + } + // Lookup in the list of pads + for (size_t i = 0; i < chip_info->num_pads; i++) { + const PadInfoPOD *pad = &(chip_info->pads[i]); + if (pad->side == side && pad->offset == offset && pad->pio_index == loc.z) + return pad; + } + return nullptr; +} + +std::string Arch::get_pad_functions(const PadInfoPOD *pad) const +{ + std::string s; + for (size_t i = 0; i < pad->num_funcs; i++) { + if (!s.empty()) + s += '/'; + s += IdString(pad->func_strs[i]).str(this); + } + return s; +} + +// ----------------------------------------------------------------------- + +// Helper for cell timing lookups +namespace { +template <typename Tres, typename Tgetter, typename Tkey> +int db_binary_search(const Tres *list, int count, Tgetter key_getter, Tkey key) +{ + if (count < 7) { + for (int i = 0; i < count; i++) { + if (key_getter(list[i]) == key) { + return i; + } + } + } else { + int b = 0, e = count - 1; + while (b <= e) { + int i = (b + e) / 2; + if (key_getter(list[i]) == key) { + return i; + } + if (key_getter(list[i]) > key) + e = i - 1; + else + b = i + 1; + } + } + return -1; +} +} // namespace + +bool Arch::is_dsp_cell(const CellInfo *cell) const +{ + return cell->type == id_MULT18_CORE || cell->type == id_MULT18X36_CORE || cell->type == id_MULT36_CORE || + cell->type == id_PREADD9_CORE || cell->type == id_REG18_CORE || cell->type == id_MULT9_CORE; +} + +int Arch::get_cell_timing_idx(IdString cell_type, IdString cell_variant) const +{ + return db_binary_search( + speed_grade->cell_types.get(), speed_grade->num_cell_types, + [](const CellTimingPOD &ct) { return std::make_pair(ct.cell_type, ct.cell_variant); }, + std::make_pair(cell_type.index, cell_variant.index)); +} + +bool Arch::lookup_cell_delay(int type_idx, IdString from_port, IdString to_port, DelayInfo &delay) const +{ + NPNR_ASSERT(type_idx != -1); + const auto &ct = speed_grade->cell_types[type_idx]; + int dly_idx = db_binary_search( + ct.prop_delays.get(), ct.num_prop_delays, + [](const CellPropDelayPOD &pd) { return std::make_pair(pd.to_port, pd.from_port); }, + std::make_pair(to_port.index, from_port.index)); + if (dly_idx == -1) + return false; + delay.min_delay = ct.prop_delays[dly_idx].min_delay; + delay.max_delay = ct.prop_delays[dly_idx].max_delay; + return true; +} + +void Arch::lookup_cell_setuphold(int type_idx, IdString from_port, IdString clock, DelayInfo &setup, + DelayInfo &hold) const +{ + NPNR_ASSERT(type_idx != -1); + const auto &ct = speed_grade->cell_types[type_idx]; + int dly_idx = db_binary_search( + ct.setup_holds.get(), ct.num_setup_holds, + [](const CellSetupHoldPOD &sh) { return std::make_pair(sh.sig_port, sh.clock_port); }, + std::make_pair(from_port.index, clock.index)); + NPNR_ASSERT(dly_idx != -1); + setup.min_delay = ct.setup_holds[dly_idx].min_setup; + setup.max_delay = ct.setup_holds[dly_idx].max_setup; + hold.min_delay = ct.setup_holds[dly_idx].min_hold; + hold.max_delay = ct.setup_holds[dly_idx].max_hold; +} + +void Arch::lookup_cell_setuphold_clock(int type_idx, IdString from_port, IdString &clock, DelayInfo &setup, + DelayInfo &hold) const +{ + NPNR_ASSERT(type_idx != -1); + const auto &ct = speed_grade->cell_types[type_idx]; + int dly_idx = db_binary_search( + ct.setup_holds.get(), ct.num_setup_holds, [](const CellSetupHoldPOD &sh) { return sh.sig_port; }, + from_port.index); + NPNR_ASSERT(dly_idx != -1); + clock = IdString(ct.setup_holds[dly_idx].clock_port); + setup.min_delay = ct.setup_holds[dly_idx].min_setup; + setup.max_delay = ct.setup_holds[dly_idx].max_setup; + hold.min_delay = ct.setup_holds[dly_idx].min_hold; + hold.max_delay = ct.setup_holds[dly_idx].max_hold; +} +void Arch::lookup_cell_clock_out(int type_idx, IdString to_port, IdString &clock, DelayInfo &delay) const +{ + NPNR_ASSERT(type_idx != -1); + const auto &ct = speed_grade->cell_types[type_idx]; + int dly_idx = db_binary_search( + ct.prop_delays.get(), ct.num_prop_delays, [](const CellPropDelayPOD &pd) { return pd.to_port; }, + to_port.index); + NPNR_ASSERT(dly_idx != -1); + clock = ct.prop_delays[dly_idx].from_port; + delay.min_delay = ct.prop_delays[dly_idx].min_delay; + delay.max_delay = ct.prop_delays[dly_idx].max_delay; +} +TimingPortClass Arch::lookup_port_type(int type_idx, IdString port, PortType dir, IdString clock) const +{ + if (dir == PORT_IN) { + NPNR_ASSERT(type_idx != -1); + const auto &ct = speed_grade->cell_types[type_idx]; + // If a setup-hold entry exists, then this is a register input + int sh_idx = db_binary_search( + ct.setup_holds.get(), ct.num_setup_holds, + [](const CellSetupHoldPOD &sh) { return std::make_pair(sh.sig_port, sh.clock_port); }, + std::make_pair(port.index, clock.index)); + return (sh_idx != -1) ? TMG_REGISTER_INPUT : TMG_COMB_INPUT; + } else { + DelayInfo dly; + // If a clock-to-out entry exists, then this is a register output + return lookup_cell_delay(type_idx, clock, port, dly) ? TMG_REGISTER_OUTPUT : TMG_COMB_OUTPUT; + } +} + +// ----------------------------------------------------------------------- + +#ifdef WITH_HEAP +const std::string Arch::defaultPlacer = "heap"; +#else +const std::string Arch::defaultPlacer = "sa"; +#endif + +const std::vector<std::string> Arch::availablePlacers = {"sa", +#ifdef WITH_HEAP + "heap" +#endif + +}; + +const std::string Arch::defaultRouter = "router2"; +const std::vector<std::string> Arch::availableRouters = {"router1", "router2"}; + +NEXTPNR_NAMESPACE_END diff --git a/nexus/arch.h b/nexus/arch.h new file mode 100644 index 00000000..d4d4799e --- /dev/null +++ b/nexus/arch.h @@ -0,0 +1,1586 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2020 David Shah <dave@ds0.me> + * + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef NEXTPNR_H +#error Include "arch.h" via "nextpnr.h" only. +#endif + +#include <boost/iostreams/device/mapped_file.hpp> + +#include <iostream> + +NEXTPNR_NAMESPACE_BEGIN + +template <typename T> struct RelPtr +{ + int32_t offset; + + // void set(const T *ptr) { + // offset = reinterpret_cast<const char*>(ptr) - + // reinterpret_cast<const char*>(this); + // } + + const T *get() const { return reinterpret_cast<const T *>(reinterpret_cast<const char *>(this) + offset); } + + const T &operator[](size_t index) const { return get()[index]; } + + const T &operator*() const { return *(get()); } + + const T *operator->() const { return get(); } +}; + +/* + Fully deduplicated database + + There are two key data structures in the database: + + Locations (aka tile but not called this to avoid confusion + with Lattice terminology), are a (x, y) location. + + Local wires; pips and bels are all stored once per variety of location + (called a location type) with a separate grid containing the location type + at a (x, y) coordinate. + + Each location also has _neighbours_, other locations with interconnected + wires. The set of neighbours for a location are called a _neighbourhood_. + + Each variety of _neighbourhood_ for a location type is also stored once, + using relative coordinates. + +*/ + +NPNR_PACKED_STRUCT(struct BelWirePOD { + uint32_t port; + uint16_t type; + uint16_t wire_index; // wire index in tile +}); + +NPNR_PACKED_STRUCT(struct BelInfoPOD { + int32_t name; // bel name in tile IdString + int32_t type; // bel type IdString + int16_t rel_x, rel_y; // bel location relative to parent + int32_t z; // bel location absolute Z + RelPtr<BelWirePOD> ports; // ports, sorted by name IdString + int32_t num_ports; // number of ports +}); + +NPNR_PACKED_STRUCT(struct BelPinPOD { + uint32_t bel; // bel index in tile + int32_t pin; // bel pin name IdString +}); + +enum TileWireFlags : uint32_t +{ + WIRE_PRIMARY = 0x80000000, +}; + +NPNR_PACKED_STRUCT(struct LocWireInfoPOD { + int32_t name; // wire name in tile IdString + uint32_t flags; + int32_t num_uphill, num_downhill, num_bpins; + // Note this pip lists exclude neighbourhood pips + RelPtr<int32_t> pips_uh, pips_dh; // list of uphill/downhill pip indices in tile + RelPtr<BelPinPOD> bel_pins; +}); + +enum PipFlags +{ + PIP_FIXED_CONN = 0x8000, +}; + +NPNR_PACKED_STRUCT(struct PipInfoPOD { + uint16_t from_wire, to_wire; + uint16_t flags; + uint16_t timing_class; + int32_t tile_type; +}); + +enum RelLocType : uint8_t +{ + REL_LOC_XY = 0, + REL_LOC_GLOBAL = 1, + REL_LOC_BRANCH = 2, + REL_LOC_BRANCH_L = 3, + REL_LOC_BRANCH_R = 4, + REL_LOC_SPINE = 5, + REL_LOC_HROW = 6, + REL_LOC_VCC = 7, +}; + +enum ArcFlags +{ + LOGICAL_TO_PRIMARY = 0x80, + PHYSICAL_DOWNHILL = 0x08, +}; + +NPNR_PACKED_STRUCT(struct RelWireInfoPOD { + int16_t rel_x, rel_y; + uint16_t wire_index; + uint8_t loc_type; + uint8_t arc_flags; +}); + +NPNR_PACKED_STRUCT(struct WireNeighboursInfoPOD { + uint32_t num_nwires; + RelPtr<RelWireInfoPOD> neigh_wires; +}); + +NPNR_PACKED_STRUCT(struct LocNeighourhoodPOD { RelPtr<WireNeighboursInfoPOD> wire_neighbours; }); + +NPNR_PACKED_STRUCT(struct LocTypePOD { + uint32_t num_bels, num_wires, num_pips, num_nhtypes; + RelPtr<BelInfoPOD> bels; + RelPtr<LocWireInfoPOD> wires; + RelPtr<PipInfoPOD> pips; + RelPtr<LocNeighourhoodPOD> neighbourhoods; +}); + +// A physical (bitstream) tile; of which there may be more than +// one in a logical tile (XY grid location). +// Tile name is reconstructed {prefix}R{row}C{col}:{tiletype} +NPNR_PACKED_STRUCT(struct PhysicalTileInfoPOD { + int32_t prefix; // tile name prefix IdString + int32_t tiletype; // tile type IdString +}); + +enum LocFlags : uint32_t +{ + LOC_LOGIC = 0x000001, + LOC_IO18 = 0x000002, + LOC_IO33 = 0x000004, + LOC_BRAM = 0x000008, + LOC_DSP = 0x000010, + LOC_IP = 0x000020, + LOC_CIB = 0x000040, + LOC_TAP = 0x001000, + LOC_SPINE = 0x002000, + LOC_TRUNK = 0x004000, + LOC_MIDMUX = 0x008000, + LOC_CMUX = 0x010000, +}; + +NPNR_PACKED_STRUCT(struct GridLocationPOD { + uint32_t loc_type; + uint32_t loc_flags; + uint16_t neighbourhood_type; + uint16_t num_phys_tiles; + RelPtr<PhysicalTileInfoPOD> phys_tiles; +}); + +enum PioSide : uint8_t +{ + PIO_LEFT = 0, + PIO_RIGHT = 1, + PIO_TOP = 2, + PIO_BOTTOM = 3 +}; + +enum PioDqsFunction : uint8_t +{ + PIO_DQS_DQ = 0, + PIO_DQS_DQS = 1, + PIO_DQS_DQSN = 2 +}; + +NPNR_PACKED_STRUCT(struct PackageInfoPOD { + RelPtr<char> full_name; // full package name, e.g. CABGA400 + RelPtr<char> short_name; // name used in part number, e.g. BG400 +}); + +NPNR_PACKED_STRUCT(struct PadInfoPOD { + int16_t offset; // position offset of tile along side (-1 if not a regular PIO) + int8_t side; // PIO side (see PioSide enum) + int8_t pio_index; // index within IO tile + + int16_t bank; // IO bank + + int16_t dqs_group; // DQS group offset + int8_t dqs_func; // DQS function + + int8_t vref_index; // VREF index in bank, or -1 if N/A + + uint16_t num_funcs; // length of special function list + uint16_t padding; // padding for alignment + + RelPtr<uint32_t> func_strs; // list of special function IdStrings + + RelPtr<RelPtr<char>> pins; // package index --> package pin name +}); + +NPNR_PACKED_STRUCT(struct GlobalBranchInfoPOD { + uint16_t branch_col; + uint16_t from_col; + uint16_t tap_driver_col; + uint16_t tap_side; + uint16_t to_col; + uint16_t padding; +}); + +NPNR_PACKED_STRUCT(struct GlobalSpineInfoPOD { + uint16_t from_row; + uint16_t to_row; + uint16_t spine_row; + uint16_t padding; +}); + +NPNR_PACKED_STRUCT(struct GlobalHrowInfoPOD { + uint16_t hrow_col; + uint16_t padding; + uint32_t num_spine_cols; + RelPtr<uint32_t> spine_cols; +}); + +NPNR_PACKED_STRUCT(struct GlobalInfoPOD { + uint32_t num_branches, num_spines, num_hrows; + RelPtr<GlobalBranchInfoPOD> branches; + RelPtr<GlobalSpineInfoPOD> spines; + RelPtr<GlobalHrowInfoPOD> hrows; +}); + +NPNR_PACKED_STRUCT(struct ChipInfoPOD { + RelPtr<char> device_name; + uint16_t width; + uint16_t height; + uint32_t num_tiles; + uint32_t num_pads; + uint32_t num_packages; + RelPtr<GridLocationPOD> grid; + RelPtr<GlobalInfoPOD> globals; + RelPtr<PadInfoPOD> pads; + RelPtr<PackageInfoPOD> packages; +}); + +NPNR_PACKED_STRUCT(struct IdStringDBPOD { + uint32_t num_file_ids; // number of IDs loaded from constids.inc + uint32_t num_bba_ids; // number of IDs in BBA file + RelPtr<RelPtr<char>> bba_id_strs; +}); + +// Timing structures are generally sorted using IdString indices as keys for fast binary searches +// All delays are integer picoseconds + +// Sort key: (to_port, from_port) for binary search by IdString +NPNR_PACKED_STRUCT(struct CellPropDelayPOD { + int32_t from_port; + int32_t to_port; + int32_t min_delay; + int32_t max_delay; +}); + +// Sort key: (sig_port, clock_port) for binary search by IdString +NPNR_PACKED_STRUCT(struct CellSetupHoldPOD { + int32_t sig_port; + int32_t clock_port; + int32_t min_setup; + int32_t max_setup; + int32_t min_hold; + int32_t max_hold; +}); + +// Sort key: (cell_type, cell_variant) for binary search by IdString +NPNR_PACKED_STRUCT(struct CellTimingPOD { + int32_t cell_type; + int32_t cell_variant; + int32_t num_prop_delays; + int32_t num_setup_holds; + RelPtr<CellPropDelayPOD> prop_delays; + RelPtr<CellSetupHoldPOD> setup_holds; +}); + +NPNR_PACKED_STRUCT(struct PipTimingPOD { + int32_t min_delay; + int32_t max_delay; + // fanout adder seemingly unused by nexus, reserved for future ECP5 etc support + int32_t min_fanout_adder; + int32_t max_fanout_adder; +}); + +NPNR_PACKED_STRUCT(struct SpeedGradePOD { + RelPtr<char> name; + int32_t num_cell_types; + int32_t num_pip_classes; + RelPtr<CellTimingPOD> cell_types; + RelPtr<PipTimingPOD> pip_classes; +}); + +NPNR_PACKED_STRUCT(struct DatabasePOD { + uint32_t version; + uint32_t num_chips; + uint32_t num_loctypes; + uint32_t num_speed_grades; + RelPtr<char> family; + RelPtr<ChipInfoPOD> chips; + RelPtr<LocTypePOD> loctypes; + RelPtr<SpeedGradePOD> speed_grades; + RelPtr<IdStringDBPOD> ids; +}); + +// ----------------------------------------------------------------------- + +// Helper functions for database access +namespace { +template <typename Id> const LocTypePOD &chip_loc_data(const DatabasePOD *db, const ChipInfoPOD *chip, const Id &id) +{ + return db->loctypes[chip->grid[id.tile].loc_type]; +} + +template <typename Id> +const LocNeighourhoodPOD &chip_nh_data(const DatabasePOD *db, const ChipInfoPOD *chip, const Id &id) +{ + auto &t = chip->grid[id.tile]; + return db->loctypes[t.loc_type].neighbourhoods[t.neighbourhood_type]; +} + +inline const BelInfoPOD &chip_bel_data(const DatabasePOD *db, const ChipInfoPOD *chip, BelId id) +{ + return chip_loc_data(db, chip, id).bels[id.index]; +} +inline const LocWireInfoPOD &chip_wire_data(const DatabasePOD *db, const ChipInfoPOD *chip, WireId id) +{ + return chip_loc_data(db, chip, id).wires[id.index]; +} +inline const PipInfoPOD &chip_pip_data(const DatabasePOD *db, const ChipInfoPOD *chip, PipId id) +{ + return chip_loc_data(db, chip, id).pips[id.index]; +} +inline bool chip_rel_tile(const ChipInfoPOD *chip, int32_t base, int16_t rel_x, int16_t rel_y, int32_t &next) +{ + int32_t curr_x = base % chip->width; + int32_t curr_y = base / chip->width; + int32_t new_x = curr_x + rel_x; + int32_t new_y = curr_y + rel_y; + if (new_x < 0 || new_x >= chip->width) + return false; + if (new_y < 0 || new_y >= chip->height) + return false; + next = new_y * chip->width + new_x; + return true; +} +inline int32_t chip_tile_from_xy(const ChipInfoPOD *chip, int32_t x, int32_t y) { return y * chip->width + x; } +inline bool chip_get_branch_loc(const ChipInfoPOD *chip, int32_t x, int32_t &branch_x) +{ + for (int i = 0; i < int(chip->globals->num_branches); i++) { + auto &b = chip->globals->branches[i]; + if (x >= b.from_col && x <= b.to_col) { + branch_x = b.branch_col; + return true; + } + } + return false; +} +inline bool chip_get_spine_loc(const ChipInfoPOD *chip, int32_t x, int32_t y, int32_t &spine_x, int32_t &spine_y) +{ + bool y_found = false; + for (int i = 0; i < int(chip->globals->num_spines); i++) { + auto &s = chip->globals->spines[i]; + if (y >= s.from_row && y <= s.to_row) { + spine_y = s.spine_row; + y_found = true; + break; + } + } + if (!y_found) + return false; + for (int i = 0; i < int(chip->globals->num_hrows); i++) { + auto &hr = chip->globals->hrows[i]; + for (int j = 0; j < int(hr.num_spine_cols); j++) { + int32_t sc = hr.spine_cols[j]; + if (std::abs(sc - x) < 3) { + spine_x = sc; + return true; + } + } + } + return false; +} +inline bool chip_get_hrow_loc(const ChipInfoPOD *chip, int32_t x, int32_t y, int32_t &hrow_x, int32_t &hrow_y) +{ + bool y_found = false; + for (int i = 0; i < int(chip->globals->num_spines); i++) { + auto &s = chip->globals->spines[i]; + if (std::abs(y - s.spine_row) < 3) { + hrow_y = s.spine_row; + y_found = true; + break; + } + } + if (!y_found) + return false; + for (int i = 0; i < int(chip->globals->num_hrows); i++) { + auto &hr = chip->globals->hrows[i]; + for (int j = 0; j < int(hr.num_spine_cols); j++) { + int32_t sc = hr.spine_cols[j]; + if (std::abs(sc - x) < 3) { + hrow_x = hr.hrow_col; + return true; + } + } + } + return false; +} +inline bool chip_branch_tile(const ChipInfoPOD *chip, int32_t x, int32_t y, int32_t &next) +{ + int32_t branch_x; + if (!chip_get_branch_loc(chip, x, branch_x)) + return false; + next = chip_tile_from_xy(chip, branch_x, y); + return true; +} +inline bool chip_rel_loc_tile(const ChipInfoPOD *chip, int32_t base, const RelWireInfoPOD &rel, int32_t &next) +{ + int32_t curr_x = base % chip->width; + int32_t curr_y = base / chip->width; + switch (rel.loc_type) { + case REL_LOC_XY: + return chip_rel_tile(chip, base, rel.rel_x, rel.rel_y, next); + case REL_LOC_BRANCH: + return chip_branch_tile(chip, curr_x, curr_y, next); + case REL_LOC_BRANCH_L: + return chip_branch_tile(chip, curr_x - 2, curr_y, next); + case REL_LOC_BRANCH_R: + return chip_branch_tile(chip, curr_x + 2, curr_y, next); + case REL_LOC_SPINE: { + int32_t spine_x, spine_y; + if (!chip_get_spine_loc(chip, curr_x, curr_y, spine_x, spine_y)) + return false; + next = chip_tile_from_xy(chip, spine_x, spine_y); + return true; + } + case REL_LOC_HROW: { + int32_t hrow_x, hrow_y; + if (!chip_get_hrow_loc(chip, curr_x, curr_y, hrow_x, hrow_y)) + return false; + next = chip_tile_from_xy(chip, hrow_x, hrow_y); + return true; + } + case REL_LOC_GLOBAL: + case REL_LOC_VCC: + next = 0; + return true; + default: + return false; + } +} +inline WireId chip_canonical_wire(const DatabasePOD *db, const ChipInfoPOD *chip, int32_t tile, uint16_t index) +{ + WireId wire{tile, index}; + // `tile` is the primary location for the wire, so ID is already canonical + if (chip_wire_data(db, chip, wire).flags & WIRE_PRIMARY) + return wire; + // Not primary; find the primary location which forms the canonical ID + auto &nd = chip_nh_data(db, chip, wire); + auto &wn = nd.wire_neighbours[index]; + for (size_t i = 0; i < wn.num_nwires; i++) { + auto &nw = wn.neigh_wires[i]; + if (nw.arc_flags & LOGICAL_TO_PRIMARY) { + if (chip_rel_loc_tile(chip, tile, nw, wire.tile)) { + wire.index = nw.wire_index; + break; + } + } + } + return wire; +} +inline bool chip_wire_is_primary(const DatabasePOD *db, const ChipInfoPOD *chip, int32_t tile, uint16_t index) +{ + WireId wire{tile, index}; + // `tile` is the primary location for the wire, so ID is already canonical + if (chip_wire_data(db, chip, wire).flags & WIRE_PRIMARY) + return true; + // Not primary; find the primary location which forms the canonical ID + auto &nd = chip_nh_data(db, chip, wire); + auto &wn = nd.wire_neighbours[index]; + for (size_t i = 0; i < wn.num_nwires; i++) { + auto &nw = wn.neigh_wires[i]; + if (nw.arc_flags & LOGICAL_TO_PRIMARY) { + if (chip_rel_loc_tile(chip, tile, nw, wire.tile)) { + return false; + } + } + } + return true; +} +} // namespace + +// ----------------------------------------------------------------------- + +struct BelIterator +{ + const DatabasePOD *db; + const ChipInfoPOD *chip; + int cursor_index; + int cursor_tile; + + BelIterator operator++() + { + cursor_index++; + while (cursor_tile < int(chip->num_tiles) && + cursor_index >= int(db->loctypes[chip->grid[cursor_tile].loc_type].num_bels)) { + cursor_index = 0; + cursor_tile++; + } + return *this; + } + BelIterator operator++(int) + { + BelIterator prior(*this); + ++(*this); + return prior; + } + + bool operator!=(const BelIterator &other) const + { + return cursor_index != other.cursor_index || cursor_tile != other.cursor_tile; + } + + bool operator==(const BelIterator &other) const + { + return cursor_index == other.cursor_index && cursor_tile == other.cursor_tile; + } + + BelId operator*() const + { + BelId ret; + ret.tile = cursor_tile; + ret.index = cursor_index; + return ret; + } +}; + +struct BelRange +{ + BelIterator b, e; + BelIterator begin() const { return b; } + BelIterator end() const { return e; } +}; + +// ----------------------------------------------------------------------- + +struct WireIterator +{ + const DatabasePOD *db; + const ChipInfoPOD *chip; + int cursor_index; + int cursor_tile = 0; + + WireIterator operator++() + { + // Iterate over nodes first, then tile wires that aren't nodes + do { + cursor_index++; + while (cursor_tile < int(chip->num_tiles) && + cursor_index >= int(db->loctypes[chip->grid[cursor_tile].loc_type].num_wires)) { + cursor_index = 0; + cursor_tile++; + } + } while (cursor_tile < int(chip->num_tiles) && !chip_wire_is_primary(db, chip, cursor_tile, cursor_index)); + + return *this; + } + WireIterator operator++(int) + { + WireIterator prior(*this); + ++(*this); + return prior; + } + + bool operator!=(const WireIterator &other) const + { + return cursor_index != other.cursor_index || cursor_tile != other.cursor_tile; + } + + bool operator==(const WireIterator &other) const + { + return cursor_index == other.cursor_index && cursor_tile == other.cursor_tile; + } + + WireId operator*() const + { + WireId ret; + ret.tile = cursor_tile; + ret.index = cursor_index; + return ret; + } +}; + +struct WireRange +{ + WireIterator b, e; + WireIterator begin() const { return b; } + WireIterator end() const { return e; } +}; + +// Iterate over all neighour wires for a wire +struct NeighWireIterator +{ + const DatabasePOD *db; + const ChipInfoPOD *chip; + WireId baseWire; + int cursor = -1; + + void operator++() + { + auto &wn = chip_nh_data(db, chip, baseWire).wire_neighbours[baseWire.index]; + int32_t tile; + do + cursor++; + while (cursor < int(wn.num_nwires) && + ((wn.neigh_wires[cursor].arc_flags & LOGICAL_TO_PRIMARY) || + !chip_rel_tile(chip, baseWire.tile, wn.neigh_wires[cursor].rel_x, wn.neigh_wires[cursor].rel_y, tile))); + } + bool operator!=(const NeighWireIterator &other) const { return cursor != other.cursor; } + + // Returns a *denormalised* identifier that may be a non-primary wire (and thus should _not_ be used + // as a WireId in general as it will break invariants) + WireId operator*() const + { + if (cursor == -1) { + return baseWire; + } else { + auto &nw = chip_nh_data(db, chip, baseWire).wire_neighbours[baseWire.index].neigh_wires[cursor]; + WireId result; + result.index = nw.wire_index; + if (!chip_rel_tile(chip, baseWire.tile, nw.rel_x, nw.rel_y, result.tile)) + return WireId(); + return result; + } + } +}; + +struct NeighWireRange +{ + NeighWireIterator b, e; + NeighWireIterator begin() const { return b; } + NeighWireIterator end() const { return e; } +}; + +// ----------------------------------------------------------------------- + +struct AllPipIterator +{ + const DatabasePOD *db; + const ChipInfoPOD *chip; + int cursor_index; + int cursor_tile; + + AllPipIterator operator++() + { + cursor_index++; + while (cursor_tile < int(chip->num_tiles) && + cursor_index >= int(db->loctypes[chip->grid[cursor_tile].loc_type].num_pips)) { + cursor_index = 0; + cursor_tile++; + } + return *this; + } + AllPipIterator operator++(int) + { + AllPipIterator prior(*this); + ++(*this); + return prior; + } + + bool operator!=(const AllPipIterator &other) const + { + return cursor_index != other.cursor_index || cursor_tile != other.cursor_tile; + } + + bool operator==(const AllPipIterator &other) const + { + return cursor_index == other.cursor_index && cursor_tile == other.cursor_tile; + } + + PipId operator*() const + { + PipId ret; + ret.tile = cursor_tile; + ret.index = cursor_index; + return ret; + } +}; + +struct AllPipRange +{ + AllPipIterator b, e; + AllPipIterator begin() const { return b; } + AllPipIterator end() const { return e; } +}; + +// ----------------------------------------------------------------------- + +struct UpDownhillPipIterator +{ + const DatabasePOD *db; + const ChipInfoPOD *chip; + NeighWireIterator twi, twi_end; + int cursor = -1; + bool uphill = false; + + void operator++() + { + cursor++; + while (true) { + if (!(twi != twi_end)) + break; + WireId w = *twi; + auto &tile = db->loctypes[chip->grid[w.tile].loc_type]; + if (cursor < int(uphill ? tile.wires[w.index].num_uphill : tile.wires[w.index].num_downhill)) + break; + ++twi; + cursor = 0; + } + } + bool operator!=(const UpDownhillPipIterator &other) const { return twi != other.twi || cursor != other.cursor; } + + PipId operator*() const + { + PipId ret; + WireId w = *twi; + ret.tile = w.tile; + auto &tile = db->loctypes[chip->grid[w.tile].loc_type]; + ret.index = uphill ? tile.wires[w.index].pips_uh[cursor] : tile.wires[w.index].pips_dh[cursor]; + return ret; + } +}; + +struct UpDownhillPipRange +{ + UpDownhillPipIterator b, e; + UpDownhillPipIterator begin() const { return b; } + UpDownhillPipIterator end() const { return e; } +}; + +struct WireBelPinIterator +{ + const DatabasePOD *db; + const ChipInfoPOD *chip; + NeighWireIterator twi, twi_end; + int cursor = -1; + + void operator++() + { + cursor++; + while (true) { + if (!(twi != twi_end)) + break; + if (cursor < chip_wire_data(db, chip, *twi).num_bpins) + break; + ++twi; + cursor = 0; + } + } + bool operator!=(const WireBelPinIterator &other) const { return twi != other.twi || cursor != other.cursor; } + + BelPin operator*() const + { + BelPin ret; + WireId w = *twi; + auto &bp = chip_wire_data(db, chip, w).bel_pins[cursor]; + ret.bel.tile = w.tile; + ret.bel.index = bp.bel; + ret.pin = IdString(bp.pin); + return ret; + } +}; + +struct WireBelPinRange +{ + WireBelPinIterator b, e; + WireBelPinIterator begin() const { return b; } + WireBelPinIterator end() const { return e; } +}; + +// ----------------------------------------------------------------------- + +// This enum captures different 'styles' of cell pins +// This is a combination of the modes available for a pin (tied high, low or inverted) +// and the default value to set it to not connected +enum CellPinStyle +{ + PINOPT_NONE = 0x0, // no options, just signal as-is + PINOPT_LO = 0x1, // can be tied low + PINOPT_HI = 0x2, // can be tied high + PINOPT_INV = 0x4, // can be inverted + + PINOPT_LOHI = 0x3, // can be tied low or high + PINOPT_LOHIINV = 0x7, // can be tied low or high; or inverted + + PINOPT_MASK = 0x7, + + PINDEF_NONE = 0x00, // leave disconnected + PINDEF_0 = 0x10, // connect to 0 if not used + PINDEF_1 = 0x20, // connect to 1 if not used + + PINDEF_MASK = 0x30, + + PINGLB_CLK = 0x100, // pin is a 'clock' for global purposes + + PINGLB_MASK = 0x100, + + PINBIT_GATED = 0x1000, // pin must be enabled in bitstream if used + PINBIT_1 = 0x2000, // pin has an explicit bit that must be set if tied to 1 + PINBIT_CIBMUX = 0x4000, // pin's CIBMUX must be floating for pin to be 1 + + PINSTYLE_NONE = 0x0000, // default + PINSTYLE_CIB = 0x4012, // 'CIB' signal, floats high but explicitly zeroed if not used + PINSTYLE_CLK = 0x0107, // CLK type signal, invertible and defaults to disconnected + PINSTYLE_CE = 0x0027, // CE type signal, invertible and defaults to enabled + PINSTYLE_LSR = 0x0017, // LSR type signal, invertible and defaults to not reset + PINSTYLE_DEDI = 0x0000, // dedicated signals, leave alone + PINSTYLE_PU = 0x4022, // signals that float high and default high + PINSTYLE_T = 0x4027, // PIO 'T' signal + + PINSTYLE_ADLSB = 0x4017, // special case of the EBR address MSBs + PINSTYLE_INV_PD = 0x0017, // invertible, pull down by default + PINSTYLE_INV_PU = 0x4027, // invertible, pull up by default + + PINSTYLE_IOL_CE = 0x2027, // CE type signal, with explicit 'const-1' config bit + PINSTYLE_GATE = 0x1011, // gated signal that defaults to 0 +}; + +// This represents the mux options for a pin +enum CellPinMux +{ + PINMUX_SIG = 0, + PINMUX_0 = 1, + PINMUX_1 = 2, + PINMUX_INV = 3, +}; + +// This represents the various kinds of IO pins +enum IOStyle +{ + IOBANK_WR = 0x1, // needs wide range IO bank + IOBANK_HP = 0x2, // needs high perf IO bank + + IOMODE_REF = 0x10, // IO is referenced + IOMODE_DIFF = 0x20, // IO is true differential + IOMODE_PSEUDO_DIFF = 0x40, // IO is pseduo differential + + IOSTYLE_SE_WR = 0x01, // single ended, wide range + IOSTYLE_SE_HP = 0x02, // single ended, high perf + IOSTYLE_PD_WR = 0x41, // pseudo diff, wide range + + IOSTYLE_REF_HP = 0x12, // referenced high perf + IOSTYLE_DIFF_HP = 0x22, // differential high perf +}; + +struct IOTypeData +{ + IOStyle style; + int vcco; // required Vcco in 10mV +}; + +// ----------------------------------------------------------------------- + +const int bba_version = +#include "bba_version.inc" + ; + +struct ArchArgs +{ + std::string device; +}; + +struct Arch : BaseCtx +{ + ArchArgs args; + std::string family, device, package, speed, rating, variant; + Arch(ArchArgs args); + + // ------------------------------------------------- + + // Database references + boost::iostreams::mapped_file_source blob_file; + const DatabasePOD *db; + const ChipInfoPOD *chip_info; + const SpeedGradePOD *speed_grade; + + int package_idx; + + // Binding states + struct LogicTileStatus + { + struct SliceStatus + { + bool valid = true, dirty = true; + } slices[4]; + struct HalfTileStatus + { + bool valid = true, dirty = true; + } halfs[2]; + CellInfo *cells[32]; + }; + + struct TileStatus + { + std::vector<CellInfo *> boundcells; + std::vector<BelId> bels_by_z; + LogicTileStatus *lts = nullptr; + ~TileStatus() { delete lts; } + }; + + std::vector<TileStatus> tileStatus; + std::unordered_map<WireId, NetInfo *> wire_to_net; + std::unordered_map<PipId, NetInfo *> pip_to_net; + + // ------------------------------------------------- + + std::string getChipName() const; + + IdString archId() const { return id("nexus"); } + ArchArgs archArgs() const { return args; } + IdString archArgsToId(ArchArgs args) const; + + int getGridDimX() const { return chip_info->width; } + int getGridDimY() const { return chip_info->height; } + int getTileBelDimZ(int, int) const { return 256; } + int getTilePipDimZ(int, int) const { return 1; } + + // ------------------------------------------------- + + BelId getBelByName(IdString name) const; + + IdString getBelName(BelId bel) const + { + std::string name = "X"; + name += std::to_string(bel.tile % chip_info->width); + name += "/Y"; + name += std::to_string(bel.tile / chip_info->width); + name += "/"; + name += nameOf(IdString(bel_data(bel).name)); + return id(name); + } + + uint32_t getBelChecksum(BelId bel) const { return (bel.tile << 16) ^ bel.index; } + + void bindBel(BelId bel, CellInfo *cell, PlaceStrength strength) + { + NPNR_ASSERT(bel != BelId()); + NPNR_ASSERT(tileStatus[bel.tile].boundcells[bel.index] == nullptr); + tileStatus[bel.tile].boundcells[bel.index] = cell; + cell->bel = bel; + cell->belStrength = strength; + refreshUiBel(bel); + + if (bel_tile_is(bel, LOC_LOGIC)) + update_logic_bel(bel, cell); + } + + void unbindBel(BelId bel) + { + NPNR_ASSERT(bel != BelId()); + NPNR_ASSERT(tileStatus[bel.tile].boundcells[bel.index] != nullptr); + + if (bel_tile_is(bel, LOC_LOGIC)) + update_logic_bel(bel, nullptr); + + tileStatus[bel.tile].boundcells[bel.index]->bel = BelId(); + tileStatus[bel.tile].boundcells[bel.index]->belStrength = STRENGTH_NONE; + tileStatus[bel.tile].boundcells[bel.index] = nullptr; + refreshUiBel(bel); + } + + bool checkBelAvail(BelId bel) const + { + NPNR_ASSERT(bel != BelId()); + return tileStatus[bel.tile].boundcells[bel.index] == nullptr; + } + + CellInfo *getBoundBelCell(BelId bel) const + { + NPNR_ASSERT(bel != BelId()); + return tileStatus[bel.tile].boundcells[bel.index]; + } + + CellInfo *getConflictingBelCell(BelId bel) const + { + NPNR_ASSERT(bel != BelId()); + return tileStatus[bel.tile].boundcells[bel.index]; + } + + BelRange getBels() const + { + BelRange range; + range.b.cursor_tile = 0; + range.b.cursor_index = -1; + range.b.chip = chip_info; + range.b.db = db; + ++range.b; //-1 and then ++ deals with the case of no bels in the first tile + range.e.cursor_tile = chip_info->width * chip_info->height; + range.e.cursor_index = 0; + range.e.chip = chip_info; + range.e.db = db; + return range; + } + + Loc getBelLocation(BelId bel) const + { + NPNR_ASSERT(bel != BelId()); + Loc loc; + loc.x = bel.tile % chip_info->width + bel_data(bel).rel_x; + loc.y = bel.tile / chip_info->width + bel_data(bel).rel_y; + loc.z = bel_data(bel).z; + return loc; + } + + BelId getBelByLocation(Loc loc) const + { + BelId ret; + auto &t = tileStatus.at(loc.y * chip_info->width + loc.x); + if (loc.z >= int(t.bels_by_z.size())) + return BelId(); + return t.bels_by_z.at(loc.z); + } + + std::vector<BelId> getBelsByTile(int x, int y) const; + + bool getBelGlobalBuf(BelId bel) const { return false; } + + IdString getBelType(BelId bel) const + { + NPNR_ASSERT(bel != BelId()); + return IdString(bel_data(bel).type); + } + + std::vector<std::pair<IdString, std::string>> getBelAttrs(BelId bel) const; + + WireId getBelPinWire(BelId bel, IdString pin) const; + PortType getBelPinType(BelId bel, IdString pin) const; + std::vector<IdString> getBelPins(BelId bel) const; + + // ------------------------------------------------- + + WireId getWireByName(IdString name) const; + IdString getWireName(WireId wire) const + { + std::string name = "X"; + name += std::to_string(wire.tile % chip_info->width); + name += "/Y"; + name += std::to_string(wire.tile / chip_info->width); + name += "/"; + name += nameOf(IdString(wire_data(wire).name)); + return id(name); + } + + IdString getWireType(WireId wire) const; + std::vector<std::pair<IdString, std::string>> getWireAttrs(WireId wire) const; + + uint32_t getWireChecksum(WireId wire) const { return (wire.tile << 16) ^ wire.index; } + + void bindWire(WireId wire, NetInfo *net, PlaceStrength strength) + { + NPNR_ASSERT(wire != WireId()); + NPNR_ASSERT(wire_to_net[wire] == nullptr); + wire_to_net[wire] = net; + net->wires[wire].pip = PipId(); + net->wires[wire].strength = strength; + refreshUiWire(wire); + } + + void unbindWire(WireId wire) + { + NPNR_ASSERT(wire != WireId()); + NPNR_ASSERT(wire_to_net[wire] != nullptr); + + auto &net_wires = wire_to_net[wire]->wires; + auto it = net_wires.find(wire); + NPNR_ASSERT(it != net_wires.end()); + + auto pip = it->second.pip; + if (pip != PipId()) { + pip_to_net[pip] = nullptr; + } + + net_wires.erase(it); + wire_to_net[wire] = nullptr; + refreshUiWire(wire); + } + + bool checkWireAvail(WireId wire) const + { + NPNR_ASSERT(wire != WireId()); + auto w2n = wire_to_net.find(wire); + return w2n == wire_to_net.end() || w2n->second == nullptr; + } + + NetInfo *getBoundWireNet(WireId wire) const + { + NPNR_ASSERT(wire != WireId()); + auto w2n = wire_to_net.find(wire); + return w2n == wire_to_net.end() ? nullptr : w2n->second; + } + + NetInfo *getConflictingWireNet(WireId wire) const + { + NPNR_ASSERT(wire != WireId()); + auto w2n = wire_to_net.find(wire); + return w2n == wire_to_net.end() ? nullptr : w2n->second; + } + + WireId getConflictingWireWire(WireId wire) const { return wire; } + + DelayInfo getWireDelay(WireId wire) const + { + DelayInfo delay; + delay.min_delay = 0; + delay.max_delay = 0; + return delay; + } + + WireBelPinRange getWireBelPins(WireId wire) const + { + WireBelPinRange range; + NPNR_ASSERT(wire != WireId()); + NeighWireRange nwr = neigh_wire_range(wire); + range.b.chip = chip_info; + range.b.db = db; + range.b.twi = nwr.b; + range.b.twi_end = nwr.e; + range.b.cursor = -1; + ++range.b; + range.e.chip = chip_info; + range.e.db = db; + range.e.twi = nwr.e; + range.e.twi_end = nwr.e; + range.e.cursor = 0; + return range; + } + + WireRange getWires() const + { + WireRange range; + range.b.chip = chip_info; + range.b.db = db; + range.b.cursor_tile = 0; + range.b.cursor_index = -1; + ++range.b; //-1 and then ++ deals with the case of no wires in the first tile + range.e.chip = chip_info; + range.e.db = db; + range.e.cursor_tile = chip_info->num_tiles; + range.e.cursor_index = 0; + return range; + } + + // ------------------------------------------------- + + PipId getPipByName(IdString name) const; + IdString getPipName(PipId pip) const; + + void bindPip(PipId pip, NetInfo *net, PlaceStrength strength) + { + NPNR_ASSERT(pip != PipId()); + NPNR_ASSERT(pip_to_net[pip] == nullptr); + + WireId dst = canonical_wire(pip.tile, pip_data(pip).to_wire); + NPNR_ASSERT(wire_to_net[dst] == nullptr || wire_to_net[dst] == net); + + pip_to_net[pip] = net; + + wire_to_net[dst] = net; + net->wires[dst].pip = pip; + net->wires[dst].strength = strength; + refreshUiPip(pip); + refreshUiWire(dst); + } + + void unbindPip(PipId pip) + { + NPNR_ASSERT(pip != PipId()); + NPNR_ASSERT(pip_to_net[pip] != nullptr); + + WireId dst = canonical_wire(pip.tile, pip_data(pip).to_wire); + NPNR_ASSERT(wire_to_net[dst] != nullptr); + wire_to_net[dst] = nullptr; + pip_to_net[pip]->wires.erase(dst); + + pip_to_net[pip] = nullptr; + refreshUiPip(pip); + refreshUiWire(dst); + } + + bool checkPipAvail(PipId pip) const + { + NPNR_ASSERT(pip != PipId()); + return pip_to_net.find(pip) == pip_to_net.end() || pip_to_net.at(pip) == nullptr; + } + + NetInfo *getBoundPipNet(PipId pip) const + { + NPNR_ASSERT(pip != PipId()); + auto p2n = pip_to_net.find(pip); + return p2n == pip_to_net.end() ? nullptr : p2n->second; + } + + WireId getConflictingPipWire(PipId pip) const { return getPipDstWire(pip); } + + NetInfo *getConflictingPipNet(PipId pip) const + { + NPNR_ASSERT(pip != PipId()); + auto p2n = pip_to_net.find(pip); + return p2n == pip_to_net.end() ? nullptr : p2n->second; + } + + AllPipRange getPips() const + { + AllPipRange range; + range.b.cursor_tile = 0; + range.b.cursor_index = -1; + range.b.chip = chip_info; + range.b.db = db; + ++range.b; //-1 and then ++ deals with the case of no pips in the first tile + range.e.cursor_tile = chip_info->width * chip_info->height; + range.e.cursor_index = 0; + range.e.chip = chip_info; + range.e.db = db; + return range; + } + + Loc getPipLocation(PipId pip) const + { + Loc loc; + loc.x = pip.tile % chip_info->width; + loc.y = pip.tile / chip_info->width; + loc.z = 0; + return loc; + } + + IdString getPipType(PipId pip) const; + std::vector<std::pair<IdString, std::string>> getPipAttrs(PipId pip) const; + + uint32_t getPipChecksum(PipId pip) const { return pip.tile << 16 | pip.index; } + + WireId getPipSrcWire(PipId pip) const { return canonical_wire(pip.tile, pip_data(pip).from_wire); } + + WireId getPipDstWire(PipId pip) const { return canonical_wire(pip.tile, pip_data(pip).to_wire); } + + DelayInfo getPipDelay(PipId pip) const + { + DelayInfo delay; + auto &cls = speed_grade->pip_classes[pip_data(pip).timing_class]; + delay.min_delay = std::max(0, cls.min_delay); + delay.max_delay = std::max(0, cls.max_delay); + return delay; + } + + UpDownhillPipRange getPipsDownhill(WireId wire) const + { + UpDownhillPipRange range; + NPNR_ASSERT(wire != WireId()); + NeighWireRange nwr = neigh_wire_range(wire); + range.b.chip = chip_info; + range.b.db = db; + range.b.twi = nwr.b; + range.b.twi_end = nwr.e; + range.b.cursor = -1; + range.b.uphill = false; + ++range.b; + range.e.chip = chip_info; + range.e.db = db; + range.e.twi = nwr.e; + range.e.twi_end = nwr.e; + range.e.cursor = 0; + range.e.uphill = false; + return range; + } + + UpDownhillPipRange getPipsUphill(WireId wire) const + { + UpDownhillPipRange range; + NPNR_ASSERT(wire != WireId()); + NeighWireRange nwr = neigh_wire_range(wire); + range.b.chip = chip_info; + range.b.db = db; + range.b.twi = nwr.b; + range.b.twi_end = nwr.e; + range.b.cursor = -1; + range.b.uphill = true; + ++range.b; + range.e.chip = chip_info; + range.e.db = db; + range.e.twi = nwr.e; + range.e.twi_end = nwr.e; + range.e.cursor = 0; + range.e.uphill = true; + return range; + } + + UpDownhillPipRange getWireAliases(WireId wire) const + { + UpDownhillPipRange range; + range.b.cursor = 0; + range.b.twi.cursor = 0; + range.e.cursor = 0; + range.e.twi.cursor = 0; + return range; + } + + // ------------------------------------------------- + + GroupId getGroupByName(IdString name) const { return GroupId(); } + IdString getGroupName(GroupId group) const { return IdString(); } + std::vector<GroupId> getGroups() const { return {}; } + std::vector<BelId> getGroupBels(GroupId group) const { return {}; } + std::vector<WireId> getGroupWires(GroupId group) const { return {}; } + std::vector<PipId> getGroupPips(GroupId group) const { return {}; } + std::vector<GroupId> getGroupGroups(GroupId group) const { return {}; } + + // ------------------------------------------------- + + delay_t estimateDelay(WireId src, WireId dst) const; + delay_t predictDelay(const NetInfo *net_info, const PortRef &sink) const; + delay_t getDelayEpsilon() const { return 20; } + delay_t getRipupDelayPenalty() const { return 120; } + delay_t getWireRipupDelayPenalty(WireId wire) const; + float getDelayNS(delay_t v) const { return v * 0.001; } + DelayInfo getDelayFromNS(float ns) const + { + DelayInfo del; + del.min_delay = delay_t(ns * 1000); + del.max_delay = delay_t(ns * 1000); + return del; + } + uint32_t getDelayChecksum(delay_t v) const { return v; } + bool getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const; + ArcBounds getRouteBoundingBox(WireId src, WireId dst) const; + + // for better DSP bounding boxes + void pre_routing(); + std::unordered_set<WireId> dsp_wires; + + // ------------------------------------------------- + + // Get the delay through a cell from one port to another, returning false + // if no path exists. This only considers combinational delays, as required by the Arch API + bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const; + // Get the port class, also setting clockInfoCount to the number of TimingClockingInfos associated with a port + TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const; + // Get the TimingClockingInfo of a port + TimingClockingInfo getPortClockingInfo(const CellInfo *cell, IdString port, int index) const; + + // ------------------------------------------------- + + // Perform placement validity checks, returning false on failure (all + // implemented in arch_place.cc) + + // Whether or not a given cell can be placed at a given Bel + // This is not intended for Bel type checks, but finer-grained constraints + // such as conflicting set/reset signals, etc + bool isValidBelForCell(CellInfo *cell, BelId bel) const; + + // Return true whether all Bels at a given location are valid + bool isBelLocationValid(BelId bel) const; + + // ------------------------------------------------- + + bool pack(); + bool place(); + bool route(); + + // arch-specific post-placement optimisations + void post_place_opt(); + + // ------------------------------------------------- + // Assign architecure-specific arguments to nets and cells, which must be + // called between packing or further + // netlist modifications, and validity checks + void assignArchInfo(); + void assignCellInfo(CellInfo *cell); + + // ------------------------------------------------- + // Arch-specific global routing + void route_globals(); + + // ------------------------------------------------- + + std::vector<GraphicElement> getDecalGraphics(DecalId decal) const; + + DecalXY getBelDecal(BelId bel) const; + DecalXY getWireDecal(WireId wire) const; + DecalXY getPipDecal(PipId pip) const; + DecalXY getGroupDecal(GroupId group) const; + + // ------------------------------------------------- + + static const std::string defaultPlacer; + static const std::vector<std::string> availablePlacers; + static const std::string defaultRouter; + static const std::vector<std::string> availableRouters; + + // ------------------------------------------------- + + template <typename Id> const LocTypePOD &loc_data(const Id &id) const { return chip_loc_data(db, chip_info, id); } + + template <typename Id> const LocNeighourhoodPOD &nh_data(const Id &id) const + { + return chip_nh_data(db, chip_info, id); + } + + inline const BelInfoPOD &bel_data(BelId id) const { return chip_bel_data(db, chip_info, id); } + inline const LocWireInfoPOD &wire_data(WireId id) const { return chip_wire_data(db, chip_info, id); } + inline const PipInfoPOD &pip_data(PipId id) const { return chip_pip_data(db, chip_info, id); } + inline bool rel_tile(int32_t base, int16_t rel_x, int16_t rel_y, int32_t &next) const + { + return chip_rel_tile(chip_info, base, rel_x, rel_y, next); + } + inline WireId canonical_wire(int32_t tile, uint16_t index) const + { + WireId c = chip_canonical_wire(db, chip_info, tile, index); + return c; + } + IdString pip_src_wire_name(PipId pip) const + { + int wire = pip_data(pip).from_wire; + return db->loctypes[chip_info->grid[pip.tile].loc_type].wires[wire].name; + } + IdString pip_dst_wire_name(PipId pip) const + { + int wire = pip_data(pip).to_wire; + return db->loctypes[chip_info->grid[pip.tile].loc_type].wires[wire].name; + } + + // ------------------------------------------------- + + typedef std::unordered_map<IdString, CellPinStyle> CellPinsData; + + std::unordered_map<IdString, CellPinsData> cell_pins_db; + CellPinStyle get_cell_pin_style(const CellInfo *cell, IdString port) const; + + void init_cell_pin_data(); + + // ------------------------------------------------- + + // Parse a possibly-Lattice-style (C literal in Verilog string) style parameter + Property parse_lattice_param(const CellInfo *ci, IdString prop, int width, int64_t defval) const; + + // ------------------------------------------------- + + NeighWireRange neigh_wire_range(WireId wire) const + { + NeighWireRange range; + range.b.chip = chip_info; + range.b.db = db; + range.b.baseWire = wire; + range.b.cursor = -1; + + range.e.chip = chip_info; + range.e.db = db; + range.e.baseWire = wire; + range.e.cursor = nh_data(wire).wire_neighbours[wire.index].num_nwires; + return range; + } + + // ------------------------------------------------- + + template <typename TId> uint32_t tile_loc_flags(TId id) const { return chip_info->grid[id.tile].loc_flags; } + + template <typename TId> bool tile_is(TId id, LocFlags lf) const { return tile_loc_flags(id) & lf; } + + bool bel_tile_is(BelId bel, LocFlags lf) const + { + int32_t tile; + NPNR_ASSERT(rel_tile(bel.tile, bel_data(bel).rel_x, bel_data(bel).rel_y, tile)); + return chip_info->grid[tile].loc_flags & lf; + } + + // ------------------------------------------------- + + enum LogicBelZ + { + BEL_LUT0 = 0, + BEL_LUT1 = 1, + BEL_FF0 = 2, + BEL_FF1 = 3, + BEL_RAMW = 4, + }; + + void update_logic_bel(BelId bel, CellInfo *cell) + { + int z = bel_data(bel).z; + NPNR_ASSERT(z < 32); + auto &tts = tileStatus[bel.tile]; + if (tts.lts == nullptr) + tts.lts = new LogicTileStatus(); + auto &ts = *(tts.lts); + ts.cells[z] = cell; + switch (z & 0x7) { + case BEL_FF0: + case BEL_FF1: + case BEL_RAMW: + ts.halfs[(z >> 3) / 2].dirty = true; + /* fall-through */ + case BEL_LUT0: + case BEL_LUT1: + ts.slices[(z >> 3)].dirty = true; + break; + } + } + + bool nexus_logic_tile_valid(LogicTileStatus <s) const; + + CellPinMux get_cell_pinmux(const CellInfo *cell, IdString pin) const; + void set_cell_pinmux(CellInfo *cell, IdString pin, CellPinMux state); + + // ------------------------------------------------- + + const PadInfoPOD *get_pkg_pin_data(const std::string &pin) const; + Loc get_pad_loc(const PadInfoPOD *pad) const; + BelId get_pad_pio_bel(const PadInfoPOD *pad) const; + const PadInfoPOD *get_bel_pad(BelId bel) const; + std::string get_pad_functions(const PadInfoPOD *pad) const; + + // ------------------------------------------------- + // Data about different IO standard, mostly used by bitgen + static const std::unordered_map<std::string, IOTypeData> io_types; + int get_io_type_vcc(const std::string &io_type) const; + bool is_io_type_diff(const std::string &io_type) const; + bool is_io_type_ref(const std::string &io_type) const; + + // ------------------------------------------------- + // Cell timing lookup helpers + + bool is_dsp_cell(const CellInfo *cell) const; + + // Given cell type and variant, get the index inside the speed grade timing data + int get_cell_timing_idx(IdString cell_type, IdString cell_variant = IdString()) const; + // Return true and set delay if a comb path exists in a given cell timing index + bool lookup_cell_delay(int type_idx, IdString from_port, IdString to_port, DelayInfo &delay) const; + // Get setup and hold time for a given cell timing index and signal/clock pair + void lookup_cell_setuphold(int type_idx, IdString from_port, IdString clock, DelayInfo &setup, + DelayInfo &hold) const; + // Get setup and hold time and associated clock for a given cell timing index and signal + void lookup_cell_setuphold_clock(int type_idx, IdString from_port, IdString &clock, DelayInfo &setup, + DelayInfo &hold) const; + // Similar to lookup_cell_delay but only needs the 'to' signal, intended for clk->out delays + void lookup_cell_clock_out(int type_idx, IdString to_port, IdString &clock, DelayInfo &delay) const; + // Attempt to look up port type based on database + TimingPortClass lookup_port_type(int type_idx, IdString port, PortType dir, IdString clock) const; + // ------------------------------------------------- + + // List of IO constraints, used by PDC parser + std::unordered_map<IdString, std::unordered_map<IdString, Property>> io_attr; + + void read_pdc(std::istream &in); + + // ------------------------------------------------- + void write_fasm(std::ostream &out) const; +}; + +NEXTPNR_NAMESPACE_END diff --git a/nexus/arch_place.cc b/nexus/arch_place.cc new file mode 100644 index 00000000..0d141013 --- /dev/null +++ b/nexus/arch_place.cc @@ -0,0 +1,118 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2020 David Shah <dave@ds0.me> + * + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "log.h" +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +bool Arch::nexus_logic_tile_valid(LogicTileStatus <s) const +{ + for (int s = 0; s < 4; s++) { + if (lts.slices[s].dirty) { + lts.slices[s].valid = false; + lts.slices[s].dirty = false; + CellInfo *lut0 = lts.cells[(s << 3) | BEL_LUT0]; + CellInfo *lut1 = lts.cells[(s << 3) | BEL_LUT1]; + CellInfo *ff0 = lts.cells[(s << 3) | BEL_FF0]; + CellInfo *ff1 = lts.cells[(s << 3) | BEL_FF1]; + + if (s == 2) { + CellInfo *ramw = lts.cells[(s << 3) | BEL_RAMW]; + // Nothing else in SLICEC can be used if the RAMW is used + if (ramw != nullptr) { + if (lut0 != nullptr || lut1 != nullptr || ff0 != nullptr || ff1 != nullptr) + return false; + } + } + + if (lut0 != nullptr) { + // Check for overuse of M signal + if (lut0->lutInfo.mux2_used && ff0 != nullptr && ff0->ffInfo.m != nullptr) + return false; + } + // Check for correct use of FF0 DI + if (ff0 != nullptr && ff0->ffInfo.di != nullptr && + (lut0 == nullptr || (ff0->ffInfo.di != lut0->lutInfo.f && ff0->ffInfo.di != lut0->lutInfo.ofx))) + return false; + if (lut1 != nullptr) { + // LUT1 cannot contain a MUX2 + if (lut1->lutInfo.mux2_used) + return false; + // If LUT1 is carry then LUT0 must be carry too + if (lut1->lutInfo.is_carry && (lut0 == nullptr || !lut0->lutInfo.is_carry)) + return false; + if (!lut1->lutInfo.is_carry && lut0 != nullptr && lut0->lutInfo.is_carry) + return false; + } + // Check for correct use of FF1 DI + if (ff1 != nullptr && ff1->ffInfo.di != nullptr && (lut1 == nullptr || ff1->ffInfo.di != lut1->lutInfo.f)) + return false; + lts.slices[s].valid = true; + } else if (!lts.slices[s].valid) { + return false; + } + } + for (int h = 0; h < 2; h++) { + if (lts.halfs[h].dirty) { + bool found_ff = false; + FFControlSet ctrlset; + for (int i = 0; i < 2; i++) { + for (auto bel : {BEL_FF0, BEL_FF1, BEL_RAMW}) { + if (bel == BEL_RAMW && (h != 1 || i != 0)) + continue; + CellInfo *ci = lts.cells[(h * 2 + i) << 3 | bel]; + if (ci == nullptr) + continue; + if (!found_ff) { + ctrlset = ci->ffInfo.ctrlset; + found_ff = true; + } else if (ci->ffInfo.ctrlset != ctrlset) { + return false; + } + } + } + } else if (!lts.halfs[h].valid) { + return false; + } + } + return true; +} + +bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const +{ + // FIXME + return true; +} + +bool Arch::isBelLocationValid(BelId bel) const +{ + if (bel_tile_is(bel, LOC_LOGIC)) { + LogicTileStatus *lts = tileStatus[bel.tile].lts; + if (lts == nullptr) + return true; + else + return nexus_logic_tile_valid(*lts); + } else { + return true; + } +} + +NEXTPNR_NAMESPACE_END diff --git a/nexus/arch_pybindings.cc b/nexus/arch_pybindings.cc new file mode 100644 index 00000000..caab8312 --- /dev/null +++ b/nexus/arch_pybindings.cc @@ -0,0 +1,72 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Clifford Wolf <clifford@symbioticeda.com> + * Copyright (C) 2018 David Shah <david@symbioticeda.com> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef NO_PYTHON + +#include "arch_pybindings.h" +#include "nextpnr.h" +#include "pybindings.h" + +NEXTPNR_NAMESPACE_BEGIN + +void arch_wrap_python(py::module &m) +{ + using namespace PythonConversion; + py::class_<ArchArgs>(m, "ArchArgs").def_readwrite("device", &ArchArgs::device); + + py::class_<BelId>(m, "BelId").def_readwrite("index", &BelId::index).def_readwrite("tile", &BelId::tile); + + py::class_<WireId>(m, "WireId").def_readwrite("index", &WireId::index).def_readwrite("tile", &WireId::tile); + + py::class_<PipId>(m, "PipId").def_readwrite("index", &PipId::index).def_readwrite("tile", &PipId::tile); + + py::class_<BelPin>(m, "BelPin").def_readwrite("bel", &BelPin::bel).def_readwrite("pin", &BelPin::pin); + + auto arch_cls = py::class_<Arch, BaseCtx>(m, "Arch").def(py::init<ArchArgs>()); + auto ctx_cls = py::class_<Context, Arch>(m, "Context") + .def("checksum", &Context::checksum) + .def("pack", &Context::pack) + .def("place", &Context::place) + .def("route", &Context::route); + + typedef std::unordered_map<IdString, std::unique_ptr<CellInfo>> CellMap; + typedef std::unordered_map<IdString, std::unique_ptr<NetInfo>> NetMap; + typedef std::unordered_map<IdString, HierarchicalCell> HierarchyMap; + typedef std::unordered_map<IdString, IdString> AliasMap; + + typedef UpDownhillPipRange PipRange; + typedef WireBelPinRange BelPinRange; + +#include "arch_pybindings_shared.h" + + WRAP_RANGE(m, Bel, conv_to_str<BelId>); + WRAP_RANGE(m, Wire, conv_to_str<WireId>); + WRAP_RANGE(m, AllPip, conv_to_str<PipId>); + WRAP_RANGE(m, UpDownhillPip, conv_to_str<PipId>); + WRAP_RANGE(m, WireBelPin, wrap_context<BelPin>); + + WRAP_MAP_UPTR(m, CellMap, "IdCellMap"); + WRAP_MAP_UPTR(m, NetMap, "IdNetMap"); + WRAP_MAP(m, HierarchyMap, wrap_context<HierarchicalCell &>, "HierarchyMap"); +} + +NEXTPNR_NAMESPACE_END + +#endif // NO_PYTHON diff --git a/nexus/arch_pybindings.h b/nexus/arch_pybindings.h new file mode 100644 index 00000000..326af306 --- /dev/null +++ b/nexus/arch_pybindings.h @@ -0,0 +1,98 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2020 David Shah <dave@ds0.me> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ +#ifndef ARCH_PYBINDINGS_H +#define ARCH_PYBINDINGS_H +#ifndef NO_PYTHON + +#include "nextpnr.h" +#include "pybindings.h" +#include "pywrappers.h" + +NEXTPNR_NAMESPACE_BEGIN + +namespace PythonConversion { + +template <> struct string_converter<BelId> +{ + BelId from_str(Context *ctx, std::string name) { return ctx->getBelByName(ctx->id(name)); } + + std::string to_str(Context *ctx, BelId id) + { + if (id == BelId()) + throw bad_wrap(); + return ctx->getBelName(id).str(ctx); + } +}; + +template <> struct string_converter<WireId> +{ + WireId from_str(Context *ctx, std::string name) { return ctx->getWireByName(ctx->id(name)); } + + std::string to_str(Context *ctx, WireId id) + { + if (id == WireId()) + throw bad_wrap(); + return ctx->getWireName(id).str(ctx); + } +}; + +template <> struct string_converter<const WireId> +{ + WireId from_str(Context *ctx, std::string name) { return ctx->getWireByName(ctx->id(name)); } + + std::string to_str(Context *ctx, WireId id) + { + if (id == WireId()) + throw bad_wrap(); + return ctx->getWireName(id).str(ctx); + } +}; + +template <> struct string_converter<PipId> +{ + PipId from_str(Context *ctx, std::string name) { return ctx->getPipByName(ctx->id(name)); } + + std::string to_str(Context *ctx, PipId id) + { + if (id == PipId()) + throw bad_wrap(); + return ctx->getPipName(id).str(ctx); + } +}; + +template <> struct string_converter<BelPin> +{ + BelPin from_str(Context *ctx, std::string name) + { + NPNR_ASSERT_FALSE("string_converter<BelPin>::from_str not implemented"); + } + + std::string to_str(Context *ctx, BelPin pin) + { + if (pin.bel == BelId()) + throw bad_wrap(); + return ctx->getBelName(pin.bel).str(ctx) + "/" + pin.pin.str(ctx); + } +}; + +} // namespace PythonConversion + +NEXTPNR_NAMESPACE_END +#endif +#endif diff --git a/nexus/archdefs.h b/nexus/archdefs.h new file mode 100644 index 00000000..adc1342c --- /dev/null +++ b/nexus/archdefs.h @@ -0,0 +1,253 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2020 David Shah <dave@ds0.me> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef NEXTPNR_H +#error Include "archdefs.h" via "nextpnr.h" only. +#endif + +NEXTPNR_NAMESPACE_BEGIN + +typedef int delay_t; + +struct DelayInfo +{ + delay_t min_delay = 0, max_delay = 0; + + delay_t minRaiseDelay() const { return min_delay; } + delay_t maxRaiseDelay() const { return max_delay; } + + delay_t minFallDelay() const { return min_delay; } + delay_t maxFallDelay() const { return max_delay; } + + delay_t minDelay() const { return min_delay; } + delay_t maxDelay() const { return max_delay; } + + DelayInfo operator+(const DelayInfo &other) const + { + DelayInfo ret; + ret.min_delay = this->min_delay + other.min_delay; + ret.max_delay = this->max_delay + other.max_delay; + return ret; + } +}; +// https://bugreports.qt.io/browse/QTBUG-80789 + +#ifndef Q_MOC_RUN +enum ConstIds +{ + ID_NONE +#define X(t) , ID_##t +#include "constids.inc" +#undef X +}; + +#define X(t) static constexpr auto id_##t = IdString(ID_##t); +#include "constids.inc" +#undef X +#endif + +struct BelId +{ + int32_t tile = -1; + // PIP index in tile + int32_t index = -1; + + BelId() = default; + inline BelId(int32_t tile, int32_t index) : tile(tile), index(index){}; + + bool operator==(const BelId &other) const { return tile == other.tile && index == other.index; } + bool operator!=(const BelId &other) const { return tile != other.tile || index != other.index; } + bool operator<(const BelId &other) const + { + return tile < other.tile || (tile == other.tile && index < other.index); + } +}; + +struct WireId +{ + int32_t tile = -1; + // Node wires: tile == -1; index = node index in chipdb + // Tile wires: tile != -1; index = wire index in tile + int32_t index = -1; + + WireId() = default; + inline WireId(int32_t tile, int32_t index) : tile(tile), index(index){}; + + bool operator==(const WireId &other) const { return tile == other.tile && index == other.index; } + bool operator!=(const WireId &other) const { return tile != other.tile || index != other.index; } + bool operator<(const WireId &other) const + { + return tile < other.tile || (tile == other.tile && index < other.index); + } +}; + +struct PipId +{ + int32_t tile = -1; + // PIP index in tile + int32_t index = -1; + + PipId() = default; + inline PipId(int32_t tile, int32_t index) : tile(tile), index(index){}; + + bool operator==(const PipId &other) const { return tile == other.tile && index == other.index; } + bool operator!=(const PipId &other) const { return tile != other.tile || index != other.index; } + bool operator<(const PipId &other) const + { + return tile < other.tile || (tile == other.tile && index < other.index); + } +}; + +struct GroupId +{ + enum : int8_t + { + TYPE_NONE, + } type = TYPE_NONE; + int8_t x = 0, y = 0; + + bool operator==(const GroupId &other) const { return (type == other.type) && (x == other.x) && (y == other.y); } + bool operator!=(const GroupId &other) const { return (type != other.type) || (x != other.x) || (y == other.y); } +}; + +struct DecalId +{ + enum : int8_t + { + TYPE_NONE, + TYPE_BEL, + TYPE_WIRE, + TYPE_PIP, + TYPE_GROUP + } type = TYPE_NONE; + int32_t index = -1; + bool active = false; + + bool operator==(const DecalId &other) const + { + return (type == other.type) && (index == other.index) && (active == other.active); + } + bool operator!=(const DecalId &other) const + { + return (type != other.type) || (index != other.index) || (active != other.active); + } +}; + +struct ArchNetInfo +{ + bool is_global; + bool is_clock, is_reset; +}; + +struct NetInfo; + +struct FFControlSet +{ + int clkmux, cemux, lsrmux; + bool async, regddr_en, gsr_en; + NetInfo *clk, *lsr, *ce; +}; + +inline bool operator!=(const FFControlSet &a, const FFControlSet &b) +{ + return (a.clkmux != b.clkmux) || (a.cemux != b.cemux) || (a.lsrmux != b.lsrmux) || (a.async != b.async) || + (a.regddr_en != b.regddr_en) || (a.gsr_en != b.gsr_en) || (a.clk != b.clk) || (a.lsr != b.lsr) || + (a.ce != b.ce); +} + +struct ArchCellInfo +{ + union + { + struct + { + bool is_memory, is_carry, mux2_used; + NetInfo *f, *ofx; + } lutInfo; + struct + { + FFControlSet ctrlset; + NetInfo *di, *m; + } ffInfo; + }; + + int tmg_index = -1; + // Map from cell/bel ports to logical timing ports + std::unordered_map<IdString, IdString> tmg_portmap; +}; + +NEXTPNR_NAMESPACE_END + +namespace std { +template <> struct hash<NEXTPNR_NAMESPACE_PREFIX BelId> +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX BelId &bel) const noexcept + { + std::size_t seed = 0; + boost::hash_combine(seed, hash<int>()(bel.tile)); + boost::hash_combine(seed, hash<int>()(bel.index)); + return seed; + } +}; + +template <> struct hash<NEXTPNR_NAMESPACE_PREFIX WireId> +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX WireId &wire) const noexcept + { + std::size_t seed = 0; + boost::hash_combine(seed, hash<int>()(wire.tile)); + boost::hash_combine(seed, hash<int>()(wire.index)); + return seed; + } +}; + +template <> struct hash<NEXTPNR_NAMESPACE_PREFIX PipId> +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX PipId &pip) const noexcept + { + std::size_t seed = 0; + boost::hash_combine(seed, hash<int>()(pip.tile)); + boost::hash_combine(seed, hash<int>()(pip.index)); + return seed; + } +}; + +template <> struct hash<NEXTPNR_NAMESPACE_PREFIX GroupId> +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX GroupId &group) const noexcept + { + std::size_t seed = 0; + boost::hash_combine(seed, hash<int>()(group.type)); + boost::hash_combine(seed, hash<int>()(group.x)); + boost::hash_combine(seed, hash<int>()(group.y)); + return seed; + } +}; + +template <> struct hash<NEXTPNR_NAMESPACE_PREFIX DecalId> +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX DecalId &decal) const noexcept + { + std::size_t seed = 0; + boost::hash_combine(seed, hash<int>()(decal.type)); + boost::hash_combine(seed, hash<int>()(decal.index)); + return seed; + } +}; +} // namespace std diff --git a/nexus/bba_version.inc b/nexus/bba_version.inc new file mode 100644 index 00000000..ec635144 --- /dev/null +++ b/nexus/bba_version.inc @@ -0,0 +1 @@ +9 diff --git a/nexus/constids.inc b/nexus/constids.inc new file mode 100644 index 00000000..9b12d197 --- /dev/null +++ b/nexus/constids.inc @@ -0,0 +1,377 @@ +X(BEL) + +X(OXIDE_FF) +X(CLK) +X(CE) +X(LSR) +X(DI) +X(M) +X(Q) + +X(OXIDE_COMB) +X(A) +X(B) +X(C) +X(D) +X(F) +X(FCI) +X(FCO) +X(SEL) +X(F1) +X(OFX) +X(WAD0) +X(WAD1) +X(WAD2) +X(WAD3) +X(WDI) +X(WCK) +X(WRE) + +X(RAMW) +X(A0) +X(A1) +X(B0) +X(B1) +X(C0) +X(C1) +X(D0) +X(D1) +X(WADO0) +X(WADO1) +X(WADO2) +X(WADO3) +X(WCKO) +X(WREO) +X(WDO0) +X(WDO1) +X(WDO2) +X(WDO3) + +X(SEIO33_CORE) +X(T) +X(I) +X(O) +X(I3CRESEN) +X(I3CWKPU) + +X(SEIO18_CORE) +X(DOLP) +X(INLP) +X(INADC) + +X(DIFFIO18_CORE) +X(HSRXEN) +X(HSTXEN) + +X(LUT4) +X(INIT) +X(Z) + +X(WIDEFN9) +X(INIT0) +X(INIT1) + +X(INV) +X(VHI) +X(VLO) + +X(FD1P3BX) +X(FD1P3DX) +X(FD1P3IX) +X(FD1P3JX) +X(CK) +X(SP) +X(PD) +X(CD) +X(GSR) + +X(CCU2) +X(CIN) +X(COUT) +X(S0) +X(S1) +X(F0) + +X(CLKMUX) +X(CEMUX) +X(LSRMUX) +X(REGDDR) +X(SRMODE) +X(REGSET) +X(LSRMODE) + +X(MODE) +X(INJECT) + +X(PLC) +X(CIB) +X(CIB_T) +X(CIB_LR) + +X(IO_TYPE) + + +X(OSCA) +X(OSC) +X(OSC_CORE) +X(HFCLKOUT) +X(LFCLKOUT) +X(HF_CLK_DIV) +X(HFOUTEN) + +X(OXIDE_EBR) +X(CLKA) +X(CLKB) +X(CEA) +X(CEB) +X(CSA0) +X(CSA1) +X(CSA2) +X(CSB0) +X(CSB1) +X(CSB2) +X(ADA0) +X(ADA1) +X(ADA2) +X(ADA3) +X(ADB0) +X(ADB1) +X(WEA) +X(WEB) +X(RSTA) +X(RSTB) + +X(LOC) + +X(IB) +X(OB) +X(OBZ) +X(BB) +X(BB_I3C_A) + +X(SEIO33) +X(SEIO18) +X(DIFFIO18) +X(IOPAD) +X(PADDO) +X(PADDI) +X(PADDT) + +X(PREADD9_CORE) +X(MULT9_CORE) +X(MULT18_CORE) +X(REG18_CORE) +X(MULT18X36_CORE) +X(MULT36_CORE) +X(ACC54_CORE) + +X(PREADD9) +X(MULT9) +X(MULT18) +X(REG18) +X(M18X36) +X(MULT36) +X(ACC54) + +X(MULT9X9) + +X(DCC) +X(CLKI) +X(CLKO) + +X(DPR16X4) +X(INITVAL) +X(DPRAM) + +X(DP16K) +X(PDP16K) +X(PDPSC16K) +X(SP16K) +X(FIFO16K) + +X(DP16K_MODE) +X(PDP16K_MODE) +X(PDPSC16K_MODE) +X(SP16K_MODE) +X(FIFO16K_MODE) + +X(DPSC512K) +X(PDPSC512K) +X(SP512K) + +X(DPSC512K_MODE) +X(PDPSC512K_MODE) +X(SP512K_MODE) + +X(WID) +X(CSDECODE_A) +X(CSDECODE_B) +X(CSDECODE_R) +X(CSDECODE_W) +X(CSDECODE) + +X(CLKW) +X(CLKR) +X(CEW) +X(CER) +X(RST) + +X(DWS0) +X(DWS1) +X(DWS2) +X(DWS3) +X(DWS4) + +X(WEAMUX) + +X(VCC_DRV) + +X(RSTCL) +X(CECL) +X(B2) +X(B3) +X(B4) +X(B5) +X(B6) +X(B7) +X(B8) +X(BSIGNED) +X(C2) +X(C3) +X(C4) +X(C5) +X(C6) +X(C7) +X(C8) +X(C9) + +X(RSTP) +X(CEP) +X(A2) +X(A3) +X(A4) +X(A5) +X(A6) +X(A7) +X(A8) +X(ASIGNED) + +X(SFTCTRL0) +X(SFTCTRL1) +X(SFTCTRL2) +X(SFTCTRL3) +X(ROUNDEN) + +X(LOAD) +X(M9ADDSUB1) +X(M9ADDSUB0) +X(ADDSUB1) +X(ADDSUB0) + +X(CEO) +X(RSTO) +X(CEC) +X(RSTC) +X(SIGNEDI) +X(CECIN) +X(CECTRL) +X(RSTCIN) +X(RSTCTRL) + +X(SIGNEDSTATIC_EN) +X(SUBSTRACT_EN) +X(CSIGNED) +X(BSIGNED_OPERAND_EN) +X(BYPASS_PREADD9) +X(REGBYPSBR0) +X(REGBYPSBR1) +X(REGBYPSBL) +X(SHIFTBR) +X(SHIFTBL) +X(PREADDCAS_EN) +X(SR_18BITSHIFT_EN) +X(OPC) +X(RESET) +X(RESETMODE) + +X(ASIGNED_OPERAND_EN) +X(BYPASS_MULT9) +X(REGBYPSA1) +X(REGBYPSA2) +X(REGBYPSB) +X(SHIFTA) + +X(REGBYPS) + +X(PP) + +X(SIGNEDA) +X(SIGNEDB) +X(RSTOUT) +X(CEOUT) +X(REGINPUTA) +X(REGINPUTB) +X(REGOUTPUT) + +X(MULT18X18) +X(ROUNDBIT) +X(ROUNDHALFUP) +X(ROUNDRTZI) +X(SFTEN) + +X(MULT18X36) +X(MULT36X36H) +X(MULT36X36) + +X(SIGNEDC) +X(REGINPUTC) + +X(MULTPREADD9X9) +X(MULTPREADD18X18) + +X(REGPIPELINE) +X(REGADDSUB) +X(REGLOADC) +X(REGLOADC2) +X(REGCIN) + +X(ACC108CASCADE) +X(ACCUBYPS) +X(ACCUMODE) +X(ADDSUBSIGNREGBYPS1) +X(ADDSUBSIGNREGBYPS2) +X(ADDSUBSIGNREGBYPS3) +X(ADDSUB_CTRL) +X(CASCOUTREGBYPS) +X(CINREGBYPS1) +X(CINREGBYPS2) +X(CINREGBYPS3) +X(CONSTSEL) +X(CREGBYPS1) +X(CREGBYPS2) +X(CREGBYPS3) +X(DSPCASCADE) +X(LOADREGBYPS1) +X(LOADREGBYPS2) +X(LOADREGBYPS3) +X(M9ADDSUBREGBYPS1) +X(M9ADDSUBREGBYPS2) +X(M9ADDSUBREGBYPS3) +X(M9ADDSUB_CTRL) +X(OUTREGBYPS) +X(SIGN) +X(STATICOPCODE_EN) +X(PROGCONST) + +X(MULTADDSUB18X18) +X(MULTADDSUB36X36) + +X(CEPIPE) +X(RSTPIPE) + +X(LOADC) +X(ADDSUB) +X(SIGNED) +X(SUM0) +X(SUM1) +X(CINPUT) diff --git a/nexus/family.cmake b/nexus/family.cmake new file mode 100644 index 00000000..4ee65dbc --- /dev/null +++ b/nexus/family.cmake @@ -0,0 +1,53 @@ +add_subdirectory(${family}) +message(STATUS "Using Nexus chipdb: ${NEXUS_CHIPDB}") + +set(chipdb_sources) +set(chipdb_binaries) +file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${family}/chipdb) +foreach(subfamily ${NEXUS_FAMILIES}) + set(chipdb_bba ${NEXUS_CHIPDB}/chipdb-${subfamily}.bba) + set(chipdb_bin ${family}/chipdb/chipdb-${subfamily}.bin) + set(chipdb_cc ${family}/chipdb/chipdb-${subfamily}.cc) + if(BBASM_MODE STREQUAL "binary") + add_custom_command( + OUTPUT ${chipdb_bin} + COMMAND bbasm ${BBASM_ENDIAN_FLAG} ${chipdb_bba} ${chipdb_bin} + DEPENDS bbasm chipdb-${family}-bbas ${chipdb_bba}) + list(APPEND chipdb_binaries ${chipdb_bin}) + elseif(BBASM_MODE STREQUAL "embed") + add_custom_command( + OUTPUT ${chipdb_cc} ${chipdb_bin} + COMMAND bbasm ${BBASM_ENDIAN_FLAG} --e ${chipdb_bba} ${chipdb_cc} ${chipdb_bin} + DEPENDS bbasm chipdb-${family}-bbas ${chipdb_bba}) + list(APPEND chipdb_sources ${chipdb_cc}) + list(APPEND chipdb_binaries ${chipdb_bin}) + elseif(BBASM_MODE STREQUAL "string") + add_custom_command( + OUTPUT ${chipdb_cc} + COMMAND bbasm ${BBASM_ENDIAN_FLAG} --c ${chipdb_bba} ${chipdb_cc} + DEPENDS bbasm chipdb-${family}-bbas ${chipdb_bba}) + list(APPEND chipdb_sources ${chipdb_cc}) + endif() +endforeach() +if(WIN32) + set(chipdb_rc ${CMAKE_CURRENT_BINARY_DIR}/${family}/resource/chipdb.rc) + list(APPEND chipdb_sources ${chipdb_rc}) + + file(WRITE ${chipdb_rc}) + foreach(subfamily ${NEXUS_FAMILIES}) + file(APPEND ${chipdb_rc} + "${family}/chipdb-${subfamily}.bin RCDATA \"${CMAKE_CURRENT_BINARY_DIR}/${family}/chipdb/chipdb-${subfamily}.bin\"") + endforeach() +endif() + +add_custom_target(chipdb-${family}-bins DEPENDS ${chipdb_sources} ${chipdb_binaries}) + +add_library(chipdb-${family} OBJECT ${NEXUS_CHIPDB} ${chipdb_sources}) +add_dependencies(chipdb-${family} chipdb-${family}-bins) +target_compile_options(chipdb-${family} PRIVATE -g0 -O0 -w) +target_compile_definitions(chipdb-${family} PRIVATE NEXTPNR_NAMESPACE=nextpnr_${family}) +target_include_directories(chipdb-${family} PRIVATE ${family}) + +foreach(family_target ${family_targets}) + target_sources(${family_target} PRIVATE $<TARGET_OBJECTS:chipdb-${family}>) +endforeach() diff --git a/nexus/fasm.cc b/nexus/fasm.cc new file mode 100644 index 00000000..5da809c6 --- /dev/null +++ b/nexus/fasm.cc @@ -0,0 +1,669 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2020 David Shah <dave@ds0.me> + * + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "log.h" +#include "nextpnr.h" +#include "util.h" + +#include <queue> + +NEXTPNR_NAMESPACE_BEGIN +namespace { +struct NexusFasmWriter +{ + const Context *ctx; + std::ostream &out; + std::vector<std::string> fasm_ctx; + + NexusFasmWriter(const Context *ctx, std::ostream &out) : ctx(ctx), out(out) {} + + // Add a 'dot' prefix to the FASM context stack + void push(const std::string &x) { fasm_ctx.push_back(x); } + + // Remove a prefix from the FASM context stack + void pop() { fasm_ctx.pop_back(); } + + // Remove N prefices from the FASM context stack + void pop(int N) + { + for (int i = 0; i < N; i++) + fasm_ctx.pop_back(); + } + bool last_was_blank = true; + // Insert a blank line if the last wasn't blank + void blank() + { + if (!last_was_blank) + out << std::endl; + last_was_blank = true; + } + // Write out all prefices from the stack, interspersed with . + void write_prefix() + { + for (auto &x : fasm_ctx) + out << x << "."; + last_was_blank = false; + } + // Write a single config bit; if value is true + void write_bit(const std::string &name, bool value = true) + { + if (value) { + write_prefix(); + out << name << std::endl; + } + } + // Write a FASM attribute + void write_attribute(const std::string &key, const std::string &value, bool str = true) + { + std::string qu = str ? "\"" : ""; + out << "{ " << key << "=" << qu << value << qu << " }" << std::endl; + last_was_blank = false; + } + // Write a FASM comment + void write_comment(const std::string &cmt) { out << "# " << cmt << std::endl; } + // Write a FASM bitvector; optionally inverting the values in the process + void write_vector(const std::string &name, const std::vector<bool> &value, bool invert = false) + { + write_prefix(); + out << name << " = " << int(value.size()) << "'b"; + for (auto bit : boost::adaptors::reverse(value)) + out << ((bit ^ invert) ? '1' : '0'); + out << std::endl; + } + // Write a FASM bitvector given an integer value + void write_int_vector(const std::string &name, uint64_t value, int width, bool invert = false) + { + std::vector<bool> bits(width, false); + for (int i = 0; i < width; i++) + bits[i] = (value & (1ULL << i)) != 0; + write_vector(name, bits, invert); + } + // Write an int vector param + void write_int_vector_param(const CellInfo *cell, const std::string &name, uint64_t defval, int width, + bool invert = false) + { + uint64_t value = int_or_default(cell->params, ctx->id(name), defval); + std::vector<bool> bits(width, false); + for (int i = 0; i < width; i++) + bits[i] = (value & (1ULL << i)) != 0; + write_vector(stringf("%s[%d:0]", name.c_str(), width - 1), bits, invert); + } + // Look up an enum value in a cell's parameters and write it to the FASM in name.value format + void write_enum(const CellInfo *cell, const std::string &name, const std::string &defval = "") + { + auto fnd = cell->params.find(ctx->id(name)); + if (fnd == cell->params.end()) { + if (!defval.empty()) + write_bit(stringf("%s.%s", name.c_str(), defval.c_str())); + } else { + write_bit(stringf("%s.%s", name.c_str(), fnd->second.c_str())); + } + } + // Look up an IO attribute in the cell's attributes and write it to the FASM in name.value format + void write_ioattr(const CellInfo *cell, const std::string &name, const std::string &defval = "") + { + auto fnd = cell->attrs.find(ctx->id(name)); + if (fnd == cell->attrs.end()) { + if (!defval.empty()) + write_bit(stringf("%s.%s", name.c_str(), defval.c_str())); + } else { + write_bit(stringf("%s.%s", name.c_str(), fnd->second.c_str())); + } + } + void write_ioattr_postfix(const CellInfo *cell, const std::string &name, const std::string &postfix, + const std::string &defval = "") + { + auto fnd = cell->attrs.find(ctx->id(name)); + if (fnd == cell->attrs.end()) { + if (!defval.empty()) + write_bit(stringf("%s_%s.%s", name.c_str(), postfix.c_str(), defval.c_str())); + } else { + write_bit(stringf("%s_%s.%s", name.c_str(), postfix.c_str(), fnd->second.c_str())); + } + } + + // Gets the full name of a tile + std::string tile_name(int loc, const PhysicalTileInfoPOD &tile) + { + int r = loc / ctx->chip_info->width; + int c = loc % ctx->chip_info->width; + return stringf("%sR%dC%d__%s", ctx->nameOf(tile.prefix), r, c, ctx->nameOf(tile.tiletype)); + } + // Look up a tile by location index and tile type + const PhysicalTileInfoPOD &tile_by_type_and_loc(int loc, IdString type) + { + auto &ploc = ctx->chip_info->grid[loc]; + for (int i = 0; i < ploc.num_phys_tiles; i++) { + if (ploc.phys_tiles[i].tiletype == type.index) + return ploc.phys_tiles[i]; + } + log_error("No tile of type %s found at location R%dC%d", ctx->nameOf(type), loc / ctx->chip_info->width, + loc % ctx->chip_info->width); + } + // Gets the single tile at a location + const PhysicalTileInfoPOD &tile_at_loc(int loc) + { + auto &ploc = ctx->chip_info->grid[loc]; + NPNR_ASSERT(ploc.num_phys_tiles == 1); + return ploc.phys_tiles[0]; + } + // Escape an internal prjoxide name for FASM by replacing : with __ + std::string escape_name(const std::string &name) + { + std::string escaped; + for (char c : name) { + if (c == ':') + escaped += "__"; + else + escaped += c; + } + return escaped; + } + // Push a tile name onto the prefix stack + void push_tile(int loc, IdString tile_type) { push(tile_name(loc, tile_by_type_and_loc(loc, tile_type))); } + void push_tile(int loc) { push(tile_name(loc, tile_at_loc(loc))); } + // Push a bel name onto the prefix stack + void push_belname(BelId bel) { push(ctx->nameOf(ctx->bel_data(bel).name)); } + // Push the tile group name corresponding to a bel onto the prefix stack + void push_belgroup(BelId bel) + { + int r = bel.tile / ctx->chip_info->width; + int c = bel.tile % ctx->chip_info->width; + auto bel_data = ctx->bel_data(bel); + r += bel_data.rel_y; + c += bel_data.rel_x; + std::string s = stringf("R%dC%d_%s", r, c, ctx->nameOf(ctx->bel_data(bel).name)); + push(s); + } + // Push a bel's group and name + void push_bel(BelId bel) + { + push_belgroup(bel); + fasm_ctx.back() += stringf(".%s", ctx->nameOf(ctx->bel_data(bel).name)); + } + // Write out a pip in tile.dst.src format + void write_pip(PipId pip) + { + auto &pd = ctx->pip_data(pip); + if (pd.flags & PIP_FIXED_CONN) + return; + std::string tile = tile_name(pip.tile, tile_by_type_and_loc(pip.tile, pd.tile_type)); + std::string source_wire = escape_name(ctx->pip_src_wire_name(pip).str(ctx)); + std::string dest_wire = escape_name(ctx->pip_dst_wire_name(pip).str(ctx)); + out << stringf("%s.PIP.%s.%s", tile.c_str(), dest_wire.c_str(), source_wire.c_str()) << std::endl; + } + // Write out all the pips corresponding to a net + void write_net(const NetInfo *net) + { + write_comment(stringf("Net %s", ctx->nameOf(net))); + std::set<PipId> sorted_pips; + for (auto &w : net->wires) + if (w.second.pip != PipId()) + sorted_pips.insert(w.second.pip); + for (auto p : sorted_pips) + write_pip(p); + blank(); + } + // Find the CIBMUX output for a signal + WireId find_cibmux(const CellInfo *cell, IdString pin) + { + WireId cursor = ctx->getBelPinWire(cell->bel, pin); + if (cursor == WireId()) + return WireId(); + for (int i = 0; i < 10; i++) { + std::string cursor_name = IdString(ctx->wire_data(cursor).name).str(ctx); + if (cursor_name.find("JCIBMUXOUT") == 0) { + return cursor; + } + for (PipId pip : ctx->getPipsUphill(cursor)) + if (ctx->checkPipAvail(pip)) { + cursor = ctx->getPipSrcWire(pip); + break; + } + } + return WireId(); + } + // Write out the mux config for a cell + void write_cell_muxes(const CellInfo *cell) + { + for (auto port : sorted_cref(cell->ports)) { + // Only relevant to inputs + if (port.second.type != PORT_IN) + continue; + auto pin_style = ctx->get_cell_pin_style(cell, port.first); + auto pin_mux = ctx->get_cell_pinmux(cell, port.first); + // Invertible pins + if (pin_style & PINOPT_INV) { + if (pin_mux == PINMUX_INV || pin_mux == PINMUX_0) + write_bit(stringf("%sMUX.INV", ctx->nameOf(port.first))); + else if (pin_mux == PINMUX_SIG) + write_bit(stringf("%sMUX.%s", ctx->nameOf(port.first), ctx->nameOf(port.first))); + } + // Pins that must be explictly enabled + if ((pin_style & PINBIT_GATED) && (pin_mux == PINMUX_SIG)) + write_bit(stringf("%sMUX.%s", ctx->nameOf(port.first), ctx->nameOf(port.first))); + // Pins that must be explictly set to 1 rather than just left floating + if ((pin_style & PINBIT_1) && (pin_mux == PINMUX_1)) + write_bit(stringf("%sMUX.1", ctx->nameOf(port.first))); + // Handle CIB muxes - these must be set such that floating pins really are floating to VCC and not connected + // to another CIB signal + if ((pin_style & PINBIT_CIBMUX) && port.second.net == nullptr) { + WireId cibmuxout = find_cibmux(cell, port.first); + if (cibmuxout != WireId()) { + write_comment(stringf("CIBMUX for unused pin %s", ctx->nameOf(port.first))); + bool found = false; + for (PipId pip : ctx->getPipsUphill(cibmuxout)) { + if (ctx->checkPipAvail(pip) && ctx->checkWireAvail(ctx->getPipSrcWire(pip))) { + write_pip(pip); + found = true; + break; + } + } + NPNR_ASSERT(found); + } + } + } + } + + // Write config for an OXIDE_COMB cell + void write_comb(const CellInfo *cell) + { + BelId bel = cell->bel; + int z = ctx->bel_data(bel).z; + int k = z & 0x1; + char slice = 'A' + (z >> 3); + push_tile(bel.tile, id_PLC); + push(stringf("SLICE%c", slice)); + if (cell->params.count(id_INIT)) + write_int_vector(stringf("K%d.INIT[15:0]", k), int_or_default(cell->params, id_INIT, 0), 16); + if (cell->lutInfo.is_carry) { + write_bit("MODE.CCU2"); + write_enum(cell, "CCU2.INJECT", "NO"); + } + pop(2); + } + // Write config for an OXIDE_FF cell + void write_ff(const CellInfo *cell) + { + BelId bel = cell->bel; + int z = ctx->bel_data(bel).z; + int k = z & 0x1; + char slice = 'A' + (z >> 3); + push_tile(bel.tile, id_PLC); + push(stringf("SLICE%c", slice)); + push(stringf("REG%d", k)); + write_bit("USED.YES"); + write_enum(cell, "REGSET", "RESET"); + write_enum(cell, "LSRMODE", "LSR"); + write_enum(cell, "SEL", "DF"); + pop(); + write_enum(cell, "REGDDR"); + write_enum(cell, "SRMODE"); + write_cell_muxes(cell); + pop(2); + } + + // Write out config for an OXIDE_RAMW cell + void write_ramw(const CellInfo *cell) + { + BelId bel = cell->bel; + push_tile(bel.tile, id_PLC); + push("SLICEC"); + write_bit("MODE.RAMW"); + write_cell_muxes(cell); + pop(2); + } + + std::unordered_set<BelId> used_io; + + struct BankConfig + { + bool diff_used = false; + bool lvds_used = false; + bool slvs_used = false; + bool dphy_used = false; + }; + + std::map<int, BankConfig> bank_cfg; + + // Write config for an SEIO33_CORE cell + void write_io33(const CellInfo *cell) + { + BelId bel = cell->bel; + used_io.insert(bel); + push_bel(bel); + const NetInfo *t = get_net_or_empty(cell, id_T); + auto tmux = ctx->get_cell_pinmux(cell, id_T); + bool is_input = false, is_output = false; + if (tmux == PINMUX_0) { + is_output = true; + } else if (tmux == PINMUX_1 || t == nullptr) { + is_input = true; + } + const char *iodir = is_input ? "INPUT" : (is_output ? "OUTPUT" : "BIDIR"); + write_bit(stringf("BASE_TYPE.%s_%s", iodir, str_or_default(cell->attrs, id_IO_TYPE, "LVCMOS33").c_str())); + write_ioattr(cell, "PULLMODE", "NONE"); + write_cell_muxes(cell); + pop(); + } + // Write config for an SEIO18_CORE cell + void write_io18(const CellInfo *cell) + { + BelId bel = cell->bel; + used_io.insert(bel); + push_bel(bel); + push("SEIO18"); + const NetInfo *t = get_net_or_empty(cell, id_T); + auto tmux = ctx->get_cell_pinmux(cell, id_T); + bool is_input = false, is_output = false; + if (tmux == PINMUX_0) { + is_output = true; + } else if (tmux == PINMUX_1 || t == nullptr) { + is_input = true; + } + const char *iodir = is_input ? "INPUT" : (is_output ? "OUTPUT" : "BIDIR"); + write_bit(stringf("BASE_TYPE.%s_%s", iodir, str_or_default(cell->attrs, id_IO_TYPE, "LVCMOS18H").c_str())); + write_ioattr(cell, "PULLMODE", "NONE"); + pop(); + write_cell_muxes(cell); + pop(); + } + // Write config for an SEIO18_CORE cell + void write_diffio18(const CellInfo *cell) + { + BelId bel = cell->bel; + + Loc bel_loc = ctx->getBelLocation(bel); + for (int i = 0; i < 2; i++) { + // Mark both A and B pins as used + used_io.insert(ctx->getBelByLocation(Loc(bel_loc.x, bel_loc.y, i))); + } + push_belgroup(bel); + push("PIOA"); + push("DIFFIO18"); + + auto &bank = bank_cfg[ctx->get_bel_pad(ctx->getBelByLocation(Loc(bel_loc.x, bel_loc.y, 0)))->bank]; + + bank.diff_used = true; + + const NetInfo *t = get_net_or_empty(cell, id_T); + auto tmux = ctx->get_cell_pinmux(cell, id_T); + bool is_input = false, is_output = false; + if (tmux == PINMUX_0) { + is_output = true; + } else if (tmux == PINMUX_1 || t == nullptr) { + is_input = true; + } + + const char *iodir = is_input ? "INPUT" : (is_output ? "OUTPUT" : "BIDIR"); + std::string type = str_or_default(cell->attrs, id_IO_TYPE, "LVDS"); + write_bit(stringf("BASE_TYPE.%s_%s", iodir, type.c_str())); + if (type == "LVDS") { + write_ioattr_postfix(cell, "DIFFDRIVE", "LVDS", "3P5"); + bank.lvds_used = true; + } else if (type == "SLVS") { + write_ioattr_postfix(cell, "DIFFDRIVE", "SLVS", "2P0"); + bank.slvs_used = true; + } else if (type == "MIPI_DPHY") { + write_ioattr_postfix(cell, "DIFFDRIVE", "MIPI_DPHY", "2P0"); + bank.dphy_used = true; + } + + write_ioattr(cell, "PULLMODE", "FAILSAFE"); + write_ioattr(cell, "DIFFRESISTOR"); + pop(); + write_cell_muxes(cell); + pop(2); + } + // Write config for an OSC_CORE cell + void write_osc(const CellInfo *cell) + { + BelId bel = cell->bel; + push_tile(bel.tile); + push_belname(bel); + write_enum(cell, "HF_OSC_EN"); + write_enum(cell, "HF_FABRIC_EN"); + write_enum(cell, "HFDIV_FABRIC_EN", "ENABLED"); + write_enum(cell, "LF_FABRIC_EN"); + write_enum(cell, "LF_OUTPUT_EN"); + write_enum(cell, "DEBUG_N", "DISABLED"); + write_int_vector(stringf("HF_CLK_DIV[7:0]"), ctx->parse_lattice_param(cell, id_HF_CLK_DIV, 8, 0).intval, 8); + write_cell_muxes(cell); + pop(2); + } + // Write config for an OXIDE_EBR cell + void write_bram(const CellInfo *cell) + { + // EBR configuration + BelId bel = cell->bel; + push_bel(bel); + int wid = int_or_default(cell->params, id_WID, 0); + std::string mode = str_or_default(cell->params, id_MODE, ""); + + write_bit(stringf("MODE.%s_MODE", mode.c_str())); + write_enum(cell, "INIT_DATA", "STATIC"); + write_enum(cell, "GSR", "DISABLED"); + + write_int_vector("WID[10:0]", wid, 11); + + push(stringf("%s_MODE", mode.c_str())); + + if (mode == "DP16K") { + write_int_vector_param(cell, "CSDECODE_A", 7, 3, true); + write_int_vector_param(cell, "CSDECODE_B", 7, 3, true); + write_enum(cell, "ASYNC_RST_RELEASE_A"); + write_enum(cell, "ASYNC_RST_RELEASE_B"); + write_enum(cell, "DATA_WIDTH_A"); + write_enum(cell, "DATA_WIDTH_B"); + write_enum(cell, "OUTREG_A"); + write_enum(cell, "OUTREG_B"); + write_enum(cell, "RESETMODE_A"); + write_enum(cell, "RESETMODE_B"); + } else if (mode == "PDP16K" || mode == "PDPSC16K") { + write_int_vector_param(cell, "CSDECODE_W", 7, 3, true); + write_int_vector_param(cell, "CSDECODE_R", 7, 3, true); + write_enum(cell, "ASYNC_RST_RELEASE"); + write_enum(cell, "DATA_WIDTH_W"); + write_enum(cell, "DATA_WIDTH_R"); + write_enum(cell, "OUTREG"); + write_enum(cell, "RESETMODE"); + } + + pop(); + push("DP16K_MODE"); // muxes always use the DP16K perspective + write_cell_muxes(cell); + pop(2); + blank(); + + // EBR initialisation + if (wid > 0) { + push(stringf("IP_EBR_WID%d", wid)); + for (int i = 0; i < 64; i++) { + IdString param = ctx->id(stringf("INITVAL_%02X", i)); + if (!cell->params.count(param)) + continue; + auto &prop = cell->params.at(param); + std::string value; + if (prop.is_string) { + NPNR_ASSERT(prop.str.substr(0, 2) == "0x"); + // Lattice-style hex string + value = prop.str.substr(2); + value = stringf("320'h%s", value.c_str()); + } else { + // True Verilog bitvector + value = stringf("320'b%s", prop.str.c_str()); + } + write_bit(stringf("INITVAL_%02X[319:0] = %s", i, value.c_str())); + } + pop(); + } + } + + bool is_mux_param(const std::string &key) + { + return (key.size() >= 3 && (key.compare(key.size() - 3, 3, "MUX") == 0)); + } + + // Write config for some kind of DSP cell + void write_dsp(const CellInfo *cell) + { + BelId bel = cell->bel; + push_bel(bel); + if (cell->type != id_MULT18_CORE && cell->type != id_MULT18X36_CORE && cell->type != id_MULT36_CORE) + write_bit(stringf("MODE.%s", ctx->nameOf(cell->type))); + for (auto param : sorted_cref(cell->params)) { + const std::string ¶m_name = param.first.str(ctx); + if (is_mux_param(param_name)) + continue; + if (param.first == id_ROUNDBIT) { + // currently unsupported in oxide, but appears rarely used + NPNR_ASSERT(param.second.as_string() == "ROUND_TO_BIT0"); + continue; + } + write_enum(cell, param_name); + } + write_cell_muxes(cell); + pop(); + } + // Write out FASM for unused bels where needed + void write_unused() + { + write_comment("# Unused bels"); + + // DSP primitives are configured to a default mode; even if unused + static const std::unordered_map<IdString, std::vector<std::string>> dsp_defconf = { + {id_MULT9_CORE, + { + "GSR.ENABLED", + "MODE.NONE", + "RSTAMUX.RSTA", + "RSTPMUX.RSTP", + }}, + {id_PREADD9_CORE, + { + "GSR.ENABLED", + "MODE.NONE", + "RSTBMUX.RSTB", + "RSTCLMUX.RSTCL", + }}, + {id_REG18_CORE, + { + "GSR.ENABLED", + "MODE.NONE", + "RSTPMUX.RSTP", + }}, + {id_ACC54_CORE, + { + "ACCUBYPS.BYPASS", + "MODE.NONE", + }}, + }; + + for (BelId bel : ctx->getBels()) { + IdString type = ctx->getBelType(bel); + if (type == id_SEIO33_CORE && !used_io.count(bel)) { + push_bel(bel); + write_bit("BASE_TYPE.NONE"); + pop(); + blank(); + } else if (type == id_SEIO18_CORE && !used_io.count(bel)) { + push_bel(bel); + push("SEIO18"); + write_bit("BASE_TYPE.NONE"); + pop(2); + blank(); + } else if (dsp_defconf.count(type) && ctx->getBoundBelCell(bel) == nullptr) { + push_bel(bel); + for (const auto &cbit : dsp_defconf.at(type)) + write_bit(cbit); + pop(); + blank(); + } + } + } + // Write out placeholder bankref config + void write_bankcfg() + { + for (int i = 0; i < 8; i++) { + if (i >= 3 && i <= 5) { + // 1.8V banks + push(stringf("GLOBAL.BANK%d", i)); + auto &bank = bank_cfg[i]; + write_bit("DIFF_IO.ON", bank.diff_used); + write_bit("LVDS_IO.ON", bank.lvds_used); + write_bit("SLVS_IO.ON", bank.slvs_used); + write_bit("MIPI_DPHY_IO.ON", bank.dphy_used); + + pop(); + } else { + // 3.3V banks, this should eventually be set based on the bank config + write_bit(stringf("GLOBAL.BANK%d.VCC.3V3", i)); + } + } + blank(); + } + // Write out FASM for the whole design + void operator()() + { + // Write device config + write_attribute("oxide.device", ctx->device); + write_attribute("oxide.device_variant", ctx->variant); + blank(); + // Write routing + for (auto n : sorted(ctx->nets)) { + write_net(n.second); + } + // Write cell config + for (auto c : sorted(ctx->cells)) { + const CellInfo *ci = c.second; + write_comment(stringf("# Cell %s", ctx->nameOf(ci))); + if (ci->type == id_OXIDE_COMB) + write_comb(ci); + else if (ci->type == id_OXIDE_FF) + write_ff(ci); + else if (ci->type == id_RAMW) + write_ramw(ci); + else if (ci->type == id_SEIO33_CORE) + write_io33(ci); + else if (ci->type == id_SEIO18_CORE) + write_io18(ci); + else if (ci->type == id_DIFFIO18_CORE) + write_diffio18(ci); + else if (ci->type == id_OSC_CORE) + write_osc(ci); + else if (ci->type == id_OXIDE_EBR) + write_bram(ci); + else if (ci->type == id_MULT9_CORE || ci->type == id_PREADD9_CORE || ci->type == id_MULT18_CORE || + ci->type == id_MULT18X36_CORE || ci->type == id_MULT36_CORE || ci->type == id_REG18_CORE || + ci->type == id_ACC54_CORE) + write_dsp(ci); + blank(); + } + // Write config for unused bels + write_unused(); + // Write bank config + write_bankcfg(); + } +}; +} // namespace + +void Arch::write_fasm(std::ostream &out) const { NexusFasmWriter(getCtx(), out)(); } + +NEXTPNR_NAMESPACE_END diff --git a/nexus/global.cc b/nexus/global.cc new file mode 100644 index 00000000..f7abb399 --- /dev/null +++ b/nexus/global.cc @@ -0,0 +1,168 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2020 David Shah <dave@ds0.me> + * + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "log.h" +#include "nextpnr.h" +#include "util.h" + +#include <queue> + +NEXTPNR_NAMESPACE_BEGIN + +struct NexusGlobalRouter +{ + Context *ctx; + + NexusGlobalRouter(Context *ctx) : ctx(ctx){}; + + // When routing globals; we allow global->local for some tricky cases but never local->local + bool global_pip_filter(PipId pip) const + { + IdString dest_basename(ctx->wire_data(ctx->getPipDstWire(pip)).name); + const std::string &s = dest_basename.str(ctx); + if (s.size() > 2 && (s[0] == 'H' || s[0] == 'V') && s[1] == '0') + return false; + return true; + } + + // Dedicated backwards BFS routing for global networks + template <typename Tfilt> + bool backwards_bfs_route(NetInfo *net, size_t user_idx, int iter_limit, bool strict, Tfilt pip_filter) + { + // Queue of wires to visit + std::queue<WireId> visit; + // Wire -> upstream pip + std::unordered_map<WireId, PipId> backtrace; + + // Lookup source and destination wires + WireId src = ctx->getNetinfoSourceWire(net); + WireId dst = ctx->getNetinfoSinkWire(net, net->users.at(user_idx)); + + if (src == WireId()) + log_error("Net '%s' has an invalid source port %s.%s\n", ctx->nameOf(net), ctx->nameOf(net->driver.cell), + ctx->nameOf(net->driver.port)); + + if (dst == WireId()) + log_error("Net '%s' has an invalid sink port %s.%s\n", ctx->nameOf(net), + ctx->nameOf(net->users.at(user_idx).cell), ctx->nameOf(net->users.at(user_idx).port)); + + if (ctx->getBoundWireNet(src) != net) + ctx->bindWire(src, net, STRENGTH_LOCKED); + + if (src == dst) { + // Nothing more to do + return true; + } + + visit.push(dst); + backtrace[dst] = PipId(); + + int iter = 0; + + while (!visit.empty() && (iter++ < iter_limit)) { + WireId cursor = visit.front(); + visit.pop(); + // Search uphill pips + for (PipId pip : ctx->getPipsUphill(cursor)) { + // Skip pip if unavailable, and not because it's already used for this net + if (!ctx->checkPipAvail(pip) && ctx->getBoundPipNet(pip) != net) + continue; + WireId prev = ctx->getPipSrcWire(pip); + // Ditto for the upstream wire + if (!ctx->checkWireAvail(prev) && ctx->getBoundWireNet(prev) != net) + continue; + // Skip already visited wires + if (backtrace.count(prev)) + continue; + // Apply our custom pip filter + if (!pip_filter(pip)) + continue; + // Add to the queue + visit.push(prev); + backtrace[prev] = pip; + // Check if we are done yet + if (prev == src) + goto done; + } + if (false) { + done: + break; + } + } + + if (backtrace.count(src)) { + WireId cursor = src; + std::vector<PipId> pips; + // Create a list of pips on the routed path + while (true) { + PipId pip = backtrace.at(cursor); + if (pip == PipId()) + break; + pips.push_back(pip); + cursor = ctx->getPipDstWire(pip); + } + // Reverse that list + std::reverse(pips.begin(), pips.end()); + // Bind pips until we hit already-bound routing + for (PipId pip : pips) { + WireId dst = ctx->getPipDstWire(pip); + if (ctx->getBoundWireNet(dst) == net) + break; + ctx->bindPip(pip, net, STRENGTH_LOCKED); + } + return true; + } else { + if (strict) + log_error("Failed to route net '%s' from %s to %s using dedicated routing.\n", ctx->nameOf(net), + ctx->nameOfWire(src), ctx->nameOfWire(dst)); + return false; + } + } + + void route_clk_net(NetInfo *net) + { + for (size_t i = 0; i < net->users.size(); i++) + backwards_bfs_route(net, i, 1000000, true, [&](PipId pip) { return global_pip_filter(pip); }); + log_info(" routed net '%s' using global resources\n", ctx->nameOf(net)); + } + + void operator()() + { + log_info("Routing globals...\n"); + for (auto net : sorted(ctx->nets)) { + NetInfo *ni = net.second; + CellInfo *drv = ni->driver.cell; + if (drv == nullptr) + continue; + if (drv->type == id_DCC) { + route_clk_net(ni); + continue; + } + } + } +}; + +void Arch::route_globals() +{ + NexusGlobalRouter glb_router(getCtx()); + glb_router(); +} + +NEXTPNR_NAMESPACE_END diff --git a/nexus/io.cc b/nexus/io.cc new file mode 100644 index 00000000..fd5e584f --- /dev/null +++ b/nexus/io.cc @@ -0,0 +1,70 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2020 David Shah <dave@ds0.me> + * + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "log.h" +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +const std::unordered_map<std::string, IOTypeData> Arch::io_types = { + {"LVCMOS33", {IOSTYLE_SE_WR, 330}}, {"LVCMOS25", {IOSTYLE_SE_WR, 250}}, + {"LVCMOS18", {IOSTYLE_SE_WR, 180}}, {"LVCMOS15", {IOSTYLE_SE_WR, 150}}, + {"LVCMOS12", {IOSTYLE_SE_WR, 120}}, {"LVCMOS10", {IOSTYLE_SE_WR, 120}}, + + {"LVCMOS33D", {IOSTYLE_PD_WR, 330}}, {"LVCMOS25D", {IOSTYLE_PD_WR, 250}}, + + {"LVCMOS18H", {IOSTYLE_SE_HP, 180}}, {"LVCMOS15H", {IOSTYLE_SE_HP, 150}}, + {"LVCMOS12H", {IOSTYLE_SE_HP, 120}}, {"LVCMOS10R", {IOSTYLE_SE_HP, 120}}, + {"LVCMOS10H", {IOSTYLE_SE_HP, 100}}, + + {"HSTL15_I", {IOSTYLE_REF_HP, 150}}, {"SSTL15_I", {IOSTYLE_REF_HP, 150}}, + {"SSTL15_II", {IOSTYLE_REF_HP, 150}}, {"SSTL135_I", {IOSTYLE_REF_HP, 135}}, + {"SSTL135_II", {IOSTYLE_REF_HP, 135}}, {"HSUL12", {IOSTYLE_REF_HP, 120}}, + + {"LVDS", {IOSTYLE_DIFF_HP, 180}}, {"SLVS", {IOSTYLE_DIFF_HP, 120}}, + {"MIPI_DPHY", {IOSTYLE_DIFF_HP, 120}}, {"HSUL12D", {IOSTYLE_DIFF_HP, 120}}, + + {"HSTL15D_I", {IOSTYLE_DIFF_HP, 150}}, {"SSTL15D_I", {IOSTYLE_DIFF_HP, 150}}, + {"SSTL15D_II", {IOSTYLE_DIFF_HP, 150}}, {"SSTL135D_I", {IOSTYLE_DIFF_HP, 135}}, + {"SSTL135D_II", {IOSTYLE_DIFF_HP, 135}}, {"HSUL12D", {IOSTYLE_DIFF_HP, 120}}, +}; + +int Arch::get_io_type_vcc(const std::string &io_type) const +{ + if (!io_types.count(io_type)) + log_error("IO type '%s' not supported.\n", io_type.c_str()); + return io_types.at(io_type).vcco; +} + +bool Arch::is_io_type_diff(const std::string &io_type) const +{ + if (!io_types.count(io_type)) + log_error("IO type '%s' not supported.\n", io_type.c_str()); + return io_types.at(io_type).style & IOMODE_DIFF; +} + +bool Arch::is_io_type_ref(const std::string &io_type) const +{ + if (!io_types.count(io_type)) + log_error("IO type '%s' not supported.\n", io_type.c_str()); + return io_types.at(io_type).style & IOMODE_REF; +} + +NEXTPNR_NAMESPACE_END diff --git a/nexus/main.cc b/nexus/main.cc new file mode 100644 index 00000000..cced1b95 --- /dev/null +++ b/nexus/main.cc @@ -0,0 +1,99 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2020 David Shah <dave@ds0.me> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifdef MAIN_EXECUTABLE + +#include <fstream> +#include "command.h" +#include "design_utils.h" +#include "jsonwrite.h" +#include "log.h" +#include "timing.h" + +USING_NEXTPNR_NAMESPACE + +class NexusCommandHandler : public CommandHandler +{ + public: + NexusCommandHandler(int argc, char **argv); + virtual ~NexusCommandHandler(){}; + std::unique_ptr<Context> createContext(std::unordered_map<std::string, Property> &values) override; + void setupArchContext(Context *ctx) override{}; + void customBitstream(Context *ctx) override; + void customAfterLoad(Context *ctx) override; + + protected: + po::options_description getArchOptions() override; +}; + +NexusCommandHandler::NexusCommandHandler(int argc, char **argv) : CommandHandler(argc, argv) {} + +po::options_description NexusCommandHandler::getArchOptions() +{ + po::options_description specific("Architecture specific options"); + specific.add_options()("device", po::value<std::string>(), "device name"); + specific.add_options()("fasm", po::value<std::string>(), "fasm file to write"); + specific.add_options()("pdc", po::value<std::string>(), "physical constraints file"); + specific.add_options()("no-post-place-opt", "disable post-place repacking (debugging use only)"); + + return specific; +} + +void NexusCommandHandler::customBitstream(Context *ctx) +{ + if (vm.count("fasm")) { + std::string filename = vm["fasm"].as<std::string>(); + std::ofstream out(filename); + if (!out) + log_error("Failed to open output FASM file %s.\n", filename.c_str()); + ctx->write_fasm(out); + } +} + +std::unique_ptr<Context> NexusCommandHandler::createContext(std::unordered_map<std::string, Property> &values) +{ + ArchArgs chipArgs; + if (!vm.count("device")) { + log_error("device must be specified on the command line (e.g. --device LIFCL-40-9BG400CES)\n"); + } + chipArgs.device = vm["device"].as<std::string>(); + auto ctx = std::unique_ptr<Context>(new Context(chipArgs)); + if (vm.count("no-post-place-opt")) + ctx->settings[ctx->id("no_post_place_opt")] = Property::State::S1; + return ctx; +} + +void NexusCommandHandler::customAfterLoad(Context *ctx) +{ + if (vm.count("pdc")) { + std::string filename = vm["pdc"].as<std::string>(); + std::ifstream in(filename); + if (!in) + log_error("Failed to open input PDC file %s.\n", filename.c_str()); + ctx->read_pdc(in); + } +} + +int main(int argc, char *argv[]) +{ + NexusCommandHandler handler(argc, argv); + return handler.exec(); +} + +#endif diff --git a/nexus/pack.cc b/nexus/pack.cc new file mode 100644 index 00000000..ff5d1047 --- /dev/null +++ b/nexus/pack.cc @@ -0,0 +1,1866 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2020 David Shah <dave@ds0.me> + * + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "design_utils.h" +#include "log.h" +#include "nextpnr.h" +#include "util.h" + +#include <boost/algorithm/string.hpp> +#include <queue> + +NEXTPNR_NAMESPACE_BEGIN + +namespace { +bool is_enabled(CellInfo *ci, IdString prop) { return str_or_default(ci->params, prop, "") == "ENABLED"; } +} // namespace + +// Parse a possibly-Lattice-style (C literal in Verilog string) style parameter +Property Arch::parse_lattice_param(const CellInfo *ci, IdString prop, int width, int64_t defval) const +{ + auto fnd = ci->params.find(prop); + if (fnd == ci->params.end()) + return Property(defval, width); + const auto &val = fnd->second; + if (val.is_string) { + const std::string &s = val.str; + Property temp; + + if (boost::starts_with(s, "0b")) { + for (int i = int(s.length()) - 1; i >= 2; i--) { + char c = s.at(i); + if (c != '0' && c != '1' && c != 'x') + log_error("Invalid binary digit '%c' in property %s.%s\n", c, nameOf(ci), nameOf(prop)); + temp.str.push_back(c); + } + } else if (boost::starts_with(s, "0x")) { + for (int i = int(s.length()) - 1; i >= 2; i--) { + char c = s.at(i); + int nibble; + if (c >= '0' && c <= '9') + nibble = (c - '0'); + else if (c >= 'a' && c <= 'f') + nibble = (c - 'a') + 10; + else if (c >= 'A' && c <= 'F') + nibble = (c - 'A') + 10; + else + log_error("Invalid hex digit '%c' in property %s.%s\n", c, nameOf(ci), nameOf(prop)); + for (int j = 0; j < 4; j++) + temp.str.push_back(((nibble >> j) & 0x1) ? Property::S1 : Property::S0); + } + } else { + int64_t ival = 0; + try { + if (boost::starts_with(s, "0d")) + ival = std::stoll(s.substr(2)); + else + ival = std::stoll(s); + } catch (std::runtime_error &e) { + log_error("Invalid decimal value for property %s.%s", nameOf(ci), nameOf(prop)); + } + temp = Property(ival); + } + + for (auto b : temp.str.substr(width)) { + if (b == Property::S1) + log_error("Found value for property %s.%s with width greater than %d\n", nameOf(ci), nameOf(prop), + width); + } + temp.update_intval(); + return temp.extract(0, width); + } else { + for (auto b : val.str.substr(width)) { + if (b == Property::S1) + log_error("Found bitvector value for property %s.%s with width greater than %d - perhaps a string was " + "converted to bits?\n", + nameOf(ci), nameOf(prop), width); + } + return val.extract(0, width); + } +} + +struct NexusPacker +{ + Context *ctx; + + // Generic cell transformation + // Given cell name map and port map + // If port name is not found in port map; it will be copied as-is but stripping [] + struct XFormRule + { + IdString new_type; + std::unordered_map<IdString, IdString> port_xform; + std::unordered_map<IdString, std::vector<IdString>> port_multixform; + std::unordered_map<IdString, IdString> param_xform; + std::vector<std::pair<IdString, std::string>> set_attrs; + std::vector<std::pair<IdString, Property>> set_params; + std::vector<std::pair<IdString, Property>> default_params; + std::vector<std::tuple<IdString, IdString, int, int64_t>> parse_params; + }; + + void xform_cell(const std::unordered_map<IdString, XFormRule> &rules, CellInfo *ci) + { + auto &rule = rules.at(ci->type); + ci->type = rule.new_type; + std::vector<IdString> orig_port_names; + for (auto &port : ci->ports) + orig_port_names.push_back(port.first); + + for (auto pname : orig_port_names) { + if (rule.port_multixform.count(pname)) { + auto old_port = ci->ports.at(pname); + disconnect_port(ctx, ci, pname); + ci->ports.erase(pname); + for (auto new_name : rule.port_multixform.at(pname)) { + ci->ports[new_name].name = new_name; + ci->ports[new_name].type = old_port.type; + connect_port(ctx, old_port.net, ci, new_name); + } + } else { + IdString new_name; + if (rule.port_xform.count(pname)) { + new_name = rule.port_xform.at(pname); + } else { + std::string stripped_name; + for (auto c : pname.str(ctx)) + if (c != '[' && c != ']') + stripped_name += c; + new_name = ctx->id(stripped_name); + } + if (new_name != pname) { + rename_port(ctx, ci, pname, new_name); + } + } + } + + std::vector<IdString> xform_params; + for (auto ¶m : ci->params) + if (rule.param_xform.count(param.first)) + xform_params.push_back(param.first); + for (auto param : xform_params) + ci->params[rule.param_xform.at(param)] = ci->params[param]; + + for (auto &attr : rule.set_attrs) + ci->attrs[attr.first] = attr.second; + + for (auto ¶m : rule.default_params) + if (!ci->params.count(param.first)) + ci->params[param.first] = param.second; + + { + IdString old_param, new_param; + int width; + int64_t def; + for (const auto &p : rule.parse_params) { + std::tie(old_param, new_param, width, def) = p; + ci->params[new_param] = ctx->parse_lattice_param(ci, old_param, width, def); + } + } + + for (auto ¶m : rule.set_params) + ci->params[param.first] = param.second; + } + + void generic_xform(const std::unordered_map<IdString, XFormRule> &rules, bool print_summary = false) + { + std::map<std::string, int> cell_count; + std::map<std::string, int> new_types; + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (rules.count(ci->type)) { + cell_count[ci->type.str(ctx)]++; + xform_cell(rules, ci); + new_types[ci->type.str(ctx)]++; + } + } + if (print_summary) { + for (auto &nt : new_types) { + log_info(" Created %d %s cells from:\n", nt.second, nt.first.c_str()); + for (auto &cc : cell_count) { + if (rules.at(ctx->id(cc.first)).new_type != ctx->id(nt.first)) + continue; + log_info(" %6dx %s\n", cc.second, cc.first.c_str()); + } + } + } + } + + void pack_luts() + { + log_info("Packing LUTs...\n"); + std::unordered_map<IdString, XFormRule> lut_rules; + lut_rules[id_LUT4].new_type = id_OXIDE_COMB; + lut_rules[id_LUT4].port_xform[id_Z] = id_F; + lut_rules[id_LUT4].parse_params.emplace_back(id_INIT, id_INIT, 16, 0); + + lut_rules[id_INV].new_type = id_OXIDE_COMB; + lut_rules[id_INV].port_xform[id_Z] = id_F; + lut_rules[id_INV].port_xform[id_A] = id_A; + lut_rules[id_INV].set_params.emplace_back(id_INIT, 0x5555); + + lut_rules[id_VHI].new_type = id_OXIDE_COMB; + lut_rules[id_VHI].port_xform[id_Z] = id_F; + lut_rules[id_VHI].set_params.emplace_back(id_INIT, 0xFFFF); + + lut_rules[id_VLO].new_type = id_OXIDE_COMB; + lut_rules[id_VLO].port_xform[id_Z] = id_F; + lut_rules[id_VLO].set_params.emplace_back(id_INIT, 0x0000); + + generic_xform(lut_rules); + } + + void pack_ffs() + { + log_info("Packing FFs...\n"); + std::unordered_map<IdString, XFormRule> ff_rules; + for (auto type : {id_FD1P3BX, id_FD1P3DX, id_FD1P3IX, id_FD1P3JX}) { + ff_rules[type].new_type = id_OXIDE_FF; + ff_rules[type].port_xform[id_CK] = id_CLK; + ff_rules[type].port_xform[id_D] = id_M; // will be rerouted to DI later if applicable + ff_rules[type].port_xform[id_SP] = id_CE; + ff_rules[type].port_xform[id_Q] = id_Q; + + ff_rules[type].default_params.emplace_back(id_CLKMUX, std::string("CLK")); + ff_rules[type].default_params.emplace_back(id_CEMUX, std::string("CE")); + ff_rules[type].default_params.emplace_back(id_LSRMUX, std::string("LSR")); + ff_rules[type].set_params.emplace_back(id_LSRMODE, std::string("LSR")); + } + // Async preload + ff_rules[id_FD1P3BX].set_params.emplace_back(id_SRMODE, std::string("ASYNC")); + ff_rules[id_FD1P3BX].set_params.emplace_back(id_REGSET, std::string("SET")); + ff_rules[id_FD1P3BX].port_xform[id_PD] = id_LSR; + // Async clear + ff_rules[id_FD1P3DX].set_params.emplace_back(id_SRMODE, std::string("ASYNC")); + ff_rules[id_FD1P3DX].set_params.emplace_back(id_REGSET, std::string("RESET")); + ff_rules[id_FD1P3DX].port_xform[id_CD] = id_LSR; + // Sync preload + ff_rules[id_FD1P3JX].set_params.emplace_back(id_SRMODE, std::string("LSR_OVER_CE")); + ff_rules[id_FD1P3JX].set_params.emplace_back(id_REGSET, std::string("SET")); + ff_rules[id_FD1P3JX].port_xform[id_PD] = id_LSR; + // Sync clear + ff_rules[id_FD1P3IX].set_params.emplace_back(id_SRMODE, std::string("LSR_OVER_CE")); + ff_rules[id_FD1P3IX].set_params.emplace_back(id_REGSET, std::string("RESET")); + ff_rules[id_FD1P3IX].port_xform[id_CD] = id_LSR; + + generic_xform(ff_rules, true); + } + + std::unordered_map<IdString, BelId> reference_bels; + + void autocreate_ports(CellInfo *cell) + { + // Automatically create ports for all inputs, and maybe outputs, of a cell; even if they were left off the + // instantiation so we can tie them to constants as appropriate This also checks for any cells that don't have + // corresponding bels + + if (!reference_bels.count(cell->type)) { + // We need to look up a corresponding bel to get the list of input ports + BelId ref_bel; + for (BelId bel : ctx->getBels()) { + if (ctx->getBelType(bel) != cell->type) + continue; + ref_bel = bel; + break; + } + if (ref_bel == BelId()) + log_error("Cell type '%s' instantiated as '%s' is not supported by this device.\n", + ctx->nameOf(cell->type), ctx->nameOf(cell)); + reference_bels[cell->type] = ref_bel; + } + + BelId bel = reference_bels.at(cell->type); + for (IdString pin : ctx->getBelPins(bel)) { + PortType dir = ctx->getBelPinType(bel, pin); + if (dir != PORT_IN) + continue; + if (cell->ports.count(pin)) + continue; + if (cell->type == id_OXIDE_COMB && pin == id_SEL) + continue; // doesn't always exist and not needed + cell->ports[pin].name = pin; + cell->ports[pin].type = dir; + } + } + + NetInfo *get_const_net(IdString type) + { + // Gets a constant net, given the driver type (VHI or VLO) + // If one doesn't exist already; then create it + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (ci->type != type) + continue; + NetInfo *z = get_net_or_empty(ci, id_Z); + if (z == nullptr) + continue; + return z; + } + + NetInfo *new_net = ctx->createNet(ctx->id(stringf("$CONST_%s_NET_", type.c_str(ctx)))); + CellInfo *new_cell = ctx->createCell(ctx->id(stringf("$CONST_%s_DRV_", type.c_str(ctx))), type); + new_cell->addOutput(id_Z); + connect_port(ctx, new_net, new_cell, id_Z); + return new_net; + } + + CellPinMux get_pin_needed_muxval(CellInfo *cell, IdString port) + { + NetInfo *net = get_net_or_empty(cell, port); + if (net == nullptr || net->driver.cell == nullptr) { + // Pin is disconnected + // If a mux value exists already, honour it + CellPinMux exist_mux = ctx->get_cell_pinmux(cell, port); + if (exist_mux != PINMUX_SIG) + return exist_mux; + // Otherwise, look up the default value and use that + CellPinStyle pin_style = ctx->get_cell_pin_style(cell, port); + if ((pin_style & PINDEF_MASK) == PINDEF_0) + return PINMUX_0; + else if ((pin_style & PINDEF_MASK) == PINDEF_1) + return PINMUX_1; + else + return PINMUX_SIG; + } + // Look to see if the driver is an inverter or constant + IdString drv_type = net->driver.cell->type; + if (drv_type == id_INV) + return PINMUX_INV; + else if (drv_type == id_VLO) + return PINMUX_0; + else if (drv_type == id_VHI) + return PINMUX_1; + else + return PINMUX_SIG; + } + + void uninvert_port(CellInfo *cell, IdString port) + { + // Rewire a port so it is driven by the input to an inverter + NetInfo *net = get_net_or_empty(cell, port); + NPNR_ASSERT(net != nullptr && net->driver.cell != nullptr && net->driver.cell->type == id_INV); + CellInfo *inv = net->driver.cell; + disconnect_port(ctx, cell, port); + + NetInfo *inv_a = get_net_or_empty(inv, id_A); + if (inv_a != nullptr) { + connect_port(ctx, inv_a, cell, port); + } + } + + void trim_design() + { + // Remove unused inverters and high/low drivers + std::vector<IdString> trim_cells; + std::vector<IdString> trim_nets; + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (ci->type != id_INV && ci->type != id_VLO && ci->type != id_VHI && ci->type != id_VCC_DRV) + continue; + NetInfo *z = get_net_or_empty(ci, id_Z); + if (z == nullptr) { + trim_cells.push_back(ci->name); + continue; + } + if (!z->users.empty()) + continue; + + disconnect_port(ctx, ci, id_A); + + trim_cells.push_back(ci->name); + trim_nets.push_back(z->name); + } + + for (IdString rem_net : trim_nets) + ctx->nets.erase(rem_net); + for (IdString rem_cell : trim_cells) + ctx->cells.erase(rem_cell); + } + + std::string remove_brackets(const std::string &name) + { + std::string new_name; + new_name.reserve(name.size()); + for (char c : name) + if (c != '[' && c != ']') + new_name.push_back(c); + return new_name; + } + + void prim_to_core(CellInfo *cell, IdString new_type = {}) + { + // Convert a primitive to a '_CORE' variant + if (new_type == IdString()) + new_type = ctx->id(cell->type.str(ctx) + "_CORE"); + cell->type = new_type; + std::set<IdString> port_names; + for (auto port : cell->ports) + port_names.insert(port.first); + for (IdString port : port_names) { + IdString new_name = ctx->id(remove_brackets(port.str(ctx))); + if (new_name != port) + rename_port(ctx, cell, port, new_name); + } + } + + NetInfo *gnd_net = nullptr, *vcc_net = nullptr, *dedi_vcc_net = nullptr; + + void process_inv_constants(CellInfo *cell) + { + // Automatically create any extra inputs needed; so we can set them accordingly + autocreate_ports(cell); + + for (auto &port : cell->ports) { + // Iterate over all inputs + if (port.second.type != PORT_IN) + continue; + IdString port_name = port.first; + + CellPinMux req_mux = get_pin_needed_muxval(cell, port_name); + if (req_mux == PINMUX_SIG) { + // No special setting required, ignore + continue; + } + + CellPinStyle pin_style = ctx->get_cell_pin_style(cell, port_name); + + if (req_mux == PINMUX_INV) { + // Pin is inverted. If there is a hard inverter; then use it + if (pin_style & PINOPT_INV) { + uninvert_port(cell, port_name); + ctx->set_cell_pinmux(cell, port_name, PINMUX_INV); + } + } else if (req_mux == PINMUX_0 || req_mux == PINMUX_1) { + // Pin is tied to a constant + // If there is a hard constant option; use it + if ((pin_style & int(req_mux)) == req_mux) { + + if ((cell->type == id_OXIDE_COMB) && (req_mux == PINMUX_1)) { + // We need to add a connection to the dedicated Vcc resource that can feed these cell ports + disconnect_port(ctx, cell, port_name); + connect_port(ctx, dedi_vcc_net, cell, port_name); + continue; + } + + disconnect_port(ctx, cell, port_name); + ctx->set_cell_pinmux(cell, port_name, req_mux); + } else if (port.second.net == nullptr) { + // If the port is disconnected; and there is no hard constant + // then we need to connect it to the relevant soft-constant net + connect_port(ctx, (req_mux == PINMUX_1) ? vcc_net : gnd_net, cell, port_name); + } + } + } + } + + void prepare_io() + { + // Find the actual IO buffer corresponding to a port; and copy attributes across to it + // Note that this relies on Yosys to do IO buffer inference, to match vendor tooling behaviour + // In all cases the nextpnr-inserted IO buffers are removed as redundant. + for (auto &port : sorted_ref(ctx->ports)) { + if (!ctx->cells.count(port.first)) + log_error("Port '%s' doesn't seem to have a corresponding top level IO\n", ctx->nameOf(port.first)); + CellInfo *ci = ctx->cells.at(port.first).get(); + + PortRef top_port; + top_port.cell = nullptr; + bool is_npnr_iob = false; + + if (ci->type == ctx->id("$nextpnr_ibuf") || ci->type == ctx->id("$nextpnr_iobuf")) { + // Might have an input buffer (IB etc) connected to it + is_npnr_iob = true; + NetInfo *o = get_net_or_empty(ci, id_O); + if (o == nullptr) + ; + else if (o->users.size() > 1) + log_error("Top level pin '%s' has multiple input buffers\n", ctx->nameOf(port.first)); + else if (o->users.size() == 1) + top_port = o->users.at(0); + } + if (ci->type == ctx->id("$nextpnr_obuf") || ci->type == ctx->id("$nextpnr_iobuf")) { + // Might have an output buffer (OB etc) connected to it + is_npnr_iob = true; + NetInfo *i = get_net_or_empty(ci, id_I); + if (i != nullptr && i->driver.cell != nullptr) { + if (top_port.cell != nullptr) + log_error("Top level pin '%s' has multiple input/output buffers\n", ctx->nameOf(port.first)); + top_port = i->driver; + } + // Edge case of a bidirectional buffer driving an output pin + if (i->users.size() > 2) { + log_error("Top level pin '%s' has illegal buffer configuration\n", ctx->nameOf(port.first)); + } else if (i->users.size() == 2) { + if (top_port.cell != nullptr) + log_error("Top level pin '%s' has illegal buffer configuration\n", ctx->nameOf(port.first)); + for (auto &usr : i->users) { + if (usr.cell->type == ctx->id("$nextpnr_obuf") || usr.cell->type == ctx->id("$nextpnr_iobuf")) + continue; + top_port = usr; + break; + } + } + } + if (!is_npnr_iob) + log_error("Port '%s' doesn't seem to have a corresponding top level IO (internal cell type mismatch)\n", + ctx->nameOf(port.first)); + + if (top_port.cell == nullptr) { + log_info("Trimming port '%s' as it is unused.\n", ctx->nameOf(port.first)); + } else { + // Copy attributes to real IO buffer + if (ctx->io_attr.count(port.first)) { + for (auto &kv : ctx->io_attr.at(port.first)) { + top_port.cell->attrs[kv.first] = kv.second; + } + } + // Make sure that top level net is set correctly + port.second.net = top_port.cell->ports.at(top_port.port).net; + } + // Now remove the nextpnr-inserted buffer + disconnect_port(ctx, ci, id_I); + disconnect_port(ctx, ci, id_O); + ctx->cells.erase(port.first); + } + } + + BelId get_bel_attr(const CellInfo *ci) + { + if (!ci->attrs.count(id_BEL)) + return BelId(); + return ctx->getBelByName(ctx->id(ci->attrs.at(id_BEL).as_string())); + } + + void pack_io() + { + std::unordered_set<IdString> iob_types = {id_IB, id_OB, id_OBZ, id_BB, + id_BB_I3C_A, id_SEIO33, id_SEIO18, id_DIFFIO18, + id_SEIO33_CORE, id_SEIO18_CORE, id_DIFFIO18_CORE}; + + std::unordered_map<IdString, XFormRule> io_rules; + + // For the low level primitives, make sure we always preserve their type + io_rules[id_SEIO33_CORE].new_type = id_SEIO33_CORE; + io_rules[id_SEIO18_CORE].new_type = id_SEIO18_CORE; + io_rules[id_DIFFIO18_CORE].new_type = id_DIFFIO18_CORE; + + // Some IO buffer types need a bit of pin renaming, too + io_rules[id_SEIO33].new_type = id_SEIO33_CORE; + io_rules[id_SEIO33].port_xform[id_PADDI] = id_O; + io_rules[id_SEIO33].port_xform[id_PADDO] = id_I; + io_rules[id_SEIO33].port_xform[id_PADDT] = id_T; + io_rules[id_SEIO33].port_xform[id_IOPAD] = id_B; + + io_rules[id_BB_I3C_A] = io_rules[id_SEIO33]; + + io_rules[id_SEIO18] = io_rules[id_SEIO33]; + io_rules[id_SEIO18].new_type = id_SEIO18_CORE; + + io_rules[id_DIFFIO18] = io_rules[id_SEIO33]; + io_rules[id_DIFFIO18].new_type = id_DIFFIO18_CORE; + + // Stage 0: deal with top level inserted IO buffers + prepare_io(); + + // Stage 1: setup constraints + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + // Iterate through all IO buffer primitives + if (!iob_types.count(ci->type)) + continue; + // We need all IO constrained so we can pick the right IO bel type + // An improvement would be to allocate unconstrained IO here + if (!ci->attrs.count(id_LOC)) + log_error("Found unconstrained IO '%s', these are currently unsupported\n", ctx->nameOf(ci)); + // Convert package pin constraint to bel constraint + std::string loc = ci->attrs.at(id_LOC).as_string(); + auto pad_info = ctx->get_pkg_pin_data(loc); + if (pad_info == nullptr) + log_error("IO '%s' is constrained to invalid pin '%s'\n", ctx->nameOf(ci), loc.c_str()); + auto func = ctx->get_pad_functions(pad_info); + BelId bel = ctx->get_pad_pio_bel(pad_info); + + if (bel == BelId()) { + log_error("IO '%s' is constrained to pin %s (%s) which is not a general purpose IO pin.\n", + ctx->nameOf(ci), loc.c_str(), func.c_str()); + } else { + + // Get IO type for reporting purposes + std::string io_type = str_or_default(ci->attrs, id_IO_TYPE, "LVCMOS33"); + + if (ctx->is_io_type_diff(io_type)) { + // Convert from SEIO18 to DIFFIO18 + if (ctx->getBelType(bel) != id_SEIO18_CORE) + log_error("IO '%s' uses differential type '%s' but is placed on wide range pin '%s'\n", + ctx->nameOf(ci), io_type.c_str(), loc.c_str()); + Loc bel_loc = ctx->getBelLocation(bel); + if (bel_loc.z != 0) + log_error("IO '%s' uses differential type '%s' but is placed on 'B' side pin '%s'\n", + ctx->nameOf(ci), io_type.c_str(), loc.c_str()); + bel_loc.z = 2; + bel = ctx->getBelByLocation(bel_loc); + } + + log_info("Constraining %s IO '%s' to pin %s (%s%sbel %s)\n", io_type.c_str(), ctx->nameOf(ci), + loc.c_str(), func.c_str(), func.empty() ? "" : "; ", ctx->nameOfBel(bel)); + ci->attrs[id_BEL] = ctx->getBelName(bel).str(ctx); + } + } + // Stage 2: apply rules for primitives that need them + generic_xform(io_rules, false); + // Stage 3: all other IO primitives become their bel type + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + // Iterate through all IO buffer primitives + if (!iob_types.count(ci->type)) + continue; + // Skip those dealt with in stage 2 + if (io_rules.count(ci->type)) + continue; + // For non-bidirectional IO, we also need to configure tristate and rename B + if (ci->type == id_IB) { + ctx->set_cell_pinmux(ci, id_T, PINMUX_1); + rename_port(ctx, ci, id_I, id_B); + } else if (ci->type == id_OB) { + ctx->set_cell_pinmux(ci, id_T, PINMUX_0); + rename_port(ctx, ci, id_O, id_B); + } else if (ci->type == id_OBZ) { + ctx->set_cell_pinmux(ci, id_T, PINMUX_SIG); + rename_port(ctx, ci, id_O, id_B); + } + // Get the IO bel + BelId bel = get_bel_attr(ci); + // Set the cell type to the bel type + IdString type = ctx->getBelType(bel); + NPNR_ASSERT(type != IdString()); + ci->type = type; + } + } + + void pack_constants() + { + // Make sure we have high and low nets available + vcc_net = get_const_net(id_VHI); + gnd_net = get_const_net(id_VLO); + dedi_vcc_net = get_const_net(id_VCC_DRV); + // Iterate through cells + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + // Skip certain cells at this point + if (ci->type != id_LUT4 && ci->type != id_INV && ci->type != id_VHI && ci->type != id_VLO && + ci->type != id_VCC_DRV) + process_inv_constants(cell.second); + } + // Remove superfluous inverters and constant drivers + trim_design(); + } + + // Using a BFS, search for bels of a given type either upstream or downstream of another cell + void find_connected_bels(const CellInfo *cell, IdString port, IdString dest_type, IdString dest_pin, int iter_limit, + std::vector<BelId> &candidates) + { + int iter = 0; + std::queue<WireId> visit; + std::unordered_set<WireId> seen_wires; + std::unordered_set<BelId> seen_bels; + + BelId bel = get_bel_attr(cell); + NPNR_ASSERT(bel != BelId()); + WireId start_wire = ctx->getBelPinWire(bel, port); + NPNR_ASSERT(start_wire != WireId()); + PortType dir = ctx->getBelPinType(bel, port); + + visit.push(start_wire); + + while (!visit.empty() && (iter++ < iter_limit)) { + WireId cursor = visit.front(); + visit.pop(); + // Check to see if we have reached a valid bel pin + for (auto bp : ctx->getWireBelPins(cursor)) { + if (ctx->getBelType(bp.bel) != dest_type) + continue; + if (dest_pin != IdString() && bp.pin != dest_pin) + continue; + if (seen_bels.count(bp.bel)) + continue; + seen_bels.insert(bp.bel); + candidates.push_back(bp.bel); + } + // Search in the appropriate direction up/downstream of the cursor + if (dir == PORT_OUT) { + for (PipId p : ctx->getPipsDownhill(cursor)) + if (ctx->checkPipAvail(p)) { + WireId dst = ctx->getPipDstWire(p); + if (seen_wires.count(dst)) + continue; + seen_wires.insert(dst); + visit.push(dst); + } + } else { + for (PipId p : ctx->getPipsUphill(cursor)) + if (ctx->checkPipAvail(p)) { + WireId src = ctx->getPipSrcWire(p); + if (seen_wires.count(src)) + continue; + seen_wires.insert(src); + visit.push(src); + } + } + } + } + + // Find the nearest bel of a given type; matching a closure predicate + template <typename Tpred> BelId find_nearest_bel(const CellInfo *cell, IdString dest_type, Tpred predicate) + { + BelId origin = get_bel_attr(cell); + if (origin == BelId()) + return BelId(); + Loc origin_loc = ctx->getBelLocation(origin); + int best_distance = std::numeric_limits<int>::max(); + BelId best_bel = BelId(); + + for (BelId bel : ctx->getBels()) { + if (ctx->getBelType(bel) != dest_type) + continue; + if (!predicate(bel)) + continue; + Loc bel_loc = ctx->getBelLocation(bel); + int dist = std::abs(origin_loc.x - bel_loc.x) + std::abs(origin_loc.y - bel_loc.y); + if (dist < best_distance) { + best_distance = dist; + best_bel = bel; + } + } + return best_bel; + } + + std::unordered_set<BelId> used_bels; + + // Pre-place a primitive based on routeability first and distance second + bool preplace_prim(CellInfo *cell, IdString pin, bool strict_routing) + { + std::vector<BelId> routeability_candidates; + + if (cell->attrs.count(id_BEL)) + return false; + + NetInfo *pin_net = get_net_or_empty(cell, pin); + if (pin_net == nullptr) + return false; + + CellInfo *pin_drv = pin_net->driver.cell; + if (pin_drv == nullptr) + return false; + + // Check based on routeability + find_connected_bels(pin_drv, pin_net->driver.port, cell->type, pin, 25000, routeability_candidates); + + for (BelId cand : routeability_candidates) { + if (used_bels.count(cand)) + continue; + log_info(" constraining %s '%s' to bel '%s' based on dedicated routing\n", ctx->nameOf(cell), + ctx->nameOf(cell->type), ctx->nameOfBel(cand)); + cell->attrs[id_BEL] = ctx->getBelName(cand).str(ctx); + used_bels.insert(cand); + return true; + } + + // Unless in strict mode; check based on simple distance too + BelId nearest = find_nearest_bel(pin_drv, cell->type, [&](BelId bel) { return !used_bels.count(bel); }); + + if (nearest != BelId()) { + log_info(" constraining %s '%s' to bel '%s'\n", ctx->nameOf(cell), ctx->nameOf(cell->type), + ctx->nameOfBel(nearest)); + cell->attrs[id_BEL] = ctx->getBelName(nearest).str(ctx); + used_bels.insert(nearest); + return true; + } + + return false; + } + + // Pre-place a singleton primitive; so decisions can be made on routeability downstream of it + bool preplace_singleton(CellInfo *cell) + { + if (cell->attrs.count(id_BEL)) + return false; + bool did_something = false; + for (BelId bel : ctx->getBels()) { + if (ctx->getBelType(bel) != cell->type) + continue; + // Check that the bel really is a singleton... + NPNR_ASSERT(!cell->attrs.count(id_BEL)); + cell->attrs[id_BEL] = ctx->getBelName(bel).str(ctx); + log_info(" constraining %s '%s' to bel '%s'\n", ctx->nameOf(cell), ctx->nameOf(cell->type), + ctx->nameOfBel(bel)); + did_something = true; + } + return did_something; + } + + // Insert a buffer primitive in a signal; moving all users that match a predicate behind it + template <typename Tpred> + CellInfo *insert_buffer(NetInfo *net, IdString buffer_type, std::string name_postfix, IdString i, IdString o, + Tpred pred) + { + // Create the buffered net + NetInfo *buffered_net = ctx->createNet(ctx->id(stringf("%s$%s", ctx->nameOf(net), name_postfix.c_str()))); + // Create the buffer cell + CellInfo *buffer = ctx->createCell( + ctx->id(stringf("%s$drv_%s", ctx->nameOf(buffered_net), ctx->nameOf(buffer_type))), buffer_type); + buffer->addInput(i); + buffer->addOutput(o); + // Drive the buffered net with the buffer + connect_port(ctx, buffered_net, buffer, o); + // Filter users + std::vector<PortRef> remaining_users; + + for (auto &usr : net->users) { + if (pred(usr)) { + usr.cell->ports[usr.port].net = buffered_net; + buffered_net->users.push_back(usr); + } else { + remaining_users.push_back(usr); + } + } + + std::swap(net->users, remaining_users); + + // Connect buffer input to original net + connect_port(ctx, net, buffer, i); + + return buffer; + } + + // Insert global buffers + void promote_globals() + { + std::vector<std::pair<int, IdString>> clk_fanout; + int available_globals = 16; + for (auto net : sorted(ctx->nets)) { + NetInfo *ni = net.second; + // Skip undriven nets; and nets that are already global + if (ni->driver.cell == nullptr) + continue; + if (ni->driver.cell->type == id_DCC) { + --available_globals; + continue; + } + // Count the number of clock ports + int clk_count = 0; + for (const auto &usr : ni->users) { + auto port_style = ctx->get_cell_pin_style(usr.cell, usr.port); + if (port_style & PINGLB_CLK) + ++clk_count; + } + if (clk_count > 0) + clk_fanout.emplace_back(clk_count, ni->name); + } + if (available_globals <= 0) + return; + // Sort clocks by max fanout + std::sort(clk_fanout.begin(), clk_fanout.end(), std::greater<std::pair<int, IdString>>()); + log_info("Promoting globals...\n"); + // Promote the N highest fanout clocks + for (size_t i = 0; i < std::min<size_t>(clk_fanout.size(), available_globals); i++) { + NetInfo *net = ctx->nets.at(clk_fanout.at(i).second).get(); + log_info(" promoting clock net '%s'\n", ctx->nameOf(net)); + insert_buffer(net, id_DCC, "glb_clk", id_CLKI, id_CLKO, [](const PortRef &port) { return true; }); + } + } + + // Place certain global cells + void place_globals() + { + // Keep running until we reach a fixed point + log_info("Placing globals...\n"); + bool did_something = true; + while (did_something) { + did_something = false; + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (ci->type == id_OSC_CORE) + did_something |= preplace_singleton(ci); + else if (ci->type == id_DCC) + did_something |= preplace_prim(ci, id_CLKI, false); + } + } + } + + // Get a bus port name + IdString bus(const std::string &base, int i) { return ctx->id(stringf("%s[%d]", base.c_str(), i)); } + + IdString bus_flat(const std::string &base, int i) { return ctx->id(stringf("%s%d", base.c_str(), i)); } + + // Pack a LUTRAM into COMB and RAMW cells + void pack_lutram() + { + // Do this so we don't have an iterate-and-modfiy situation + std::vector<CellInfo *> lutrams; + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (ci->type != id_DPR16X4) + continue; + lutrams.push_back(ci); + } + + // Port permutation vectors + IdString ramw_wdo[4] = {id_D1, id_C1, id_A1, id_B1}; + IdString ramw_wado[4] = {id_D0, id_B0, id_C0, id_A0}; + IdString comb0_rad[4] = {id_D, id_B, id_C, id_A}; + IdString comb1_rad[4] = {id_C, id_B, id_D, id_A}; + + for (CellInfo *ci : lutrams) { + // Create constituent cells + CellInfo *ramw = ctx->createCell(ctx->id(stringf("%s$lutram_ramw$", ctx->nameOf(ci))), id_RAMW); + std::vector<CellInfo *> combs; + for (int i = 0; i < 4; i++) + combs.push_back( + ctx->createCell(ctx->id(stringf("%s$lutram_comb[%d]$", ctx->nameOf(ci), i)), id_OXIDE_COMB)); + // Rewiring - external WCK and WRE + replace_port(ci, id_WCK, ramw, id_CLK); + replace_port(ci, id_WRE, ramw, id_LSR); + + // Internal WCK and WRE signals + ramw->addOutput(id_WCKO); + ramw->addOutput(id_WREO); + NetInfo *int_wck = ctx->createNet(ctx->id(stringf("%s$lutram_wck$", ctx->nameOf(ci)))); + NetInfo *int_wre = ctx->createNet(ctx->id(stringf("%s$lutram_wre$", ctx->nameOf(ci)))); + connect_port(ctx, int_wck, ramw, id_WCKO); + connect_port(ctx, int_wre, ramw, id_WREO); + + uint64_t initval = ctx->parse_lattice_param(ci, id_INITVAL, 64, 0).as_int64(); + + // Rewiring - buses + for (int i = 0; i < 4; i++) { + // Write address - external + replace_port(ci, bus("WAD", i), ramw, ramw_wado[i]); + // Write data - external + replace_port(ci, bus("DI", i), ramw, ramw_wdo[i]); + // Read data + replace_port(ci, bus("DO", i), combs[i], id_F); + // Read address + NetInfo *rad = get_net_or_empty(ci, bus("RAD", i)); + if (rad != nullptr) { + for (int j = 0; j < 4; j++) { + IdString port = (j % 2) ? comb1_rad[i] : comb0_rad[i]; + combs[j]->addInput(port); + connect_port(ctx, rad, combs[j], port); + } + disconnect_port(ctx, ci, bus("RAD", i)); + } + // Write address - internal + NetInfo *int_wad = ctx->createNet(ctx->id(stringf("%s$lutram_wad[%d]$", ctx->nameOf(ci), i))); + ramw->addOutput(bus_flat("WADO", i)); + connect_port(ctx, int_wad, ramw, bus_flat("WADO", i)); + for (int j = 0; j < 4; j++) { + combs[j]->addInput(bus_flat("WAD", i)); + connect_port(ctx, int_wad, combs[j], bus_flat("WAD", i)); + } + // Write data - internal + NetInfo *int_wd = ctx->createNet(ctx->id(stringf("%s$lutram_wd[%d]$", ctx->nameOf(ci), i))); + ramw->addOutput(bus_flat("WDO", i)); + connect_port(ctx, int_wd, ramw, bus_flat("WDO", i)); + combs[i]->addInput(id_WDI); + connect_port(ctx, int_wd, combs[i], id_WDI); + // Write clock and enable - internal + combs[i]->addInput(id_WCK); + combs[i]->addInput(id_WRE); + connect_port(ctx, int_wck, combs[i], id_WCK); + connect_port(ctx, int_wre, combs[i], id_WRE); + // Remap init val + uint64_t split_init = 0; + for (int j = 0; j < 16; j++) + if (initval & (1ULL << (4 * j + i))) + split_init |= (1 << j); + combs[i]->params[id_INIT] = Property(split_init, 16); + + combs[i]->params[id_MODE] = std::string("DPRAM"); + } + + // Setup relative constraints + combs[0]->constr_z = 0; + combs[0]->constr_abs_z = true; + for (int i = 1; i < 4; i++) { + combs[i]->constr_x = 0; + combs[i]->constr_y = 0; + combs[i]->constr_z = ((i / 2) << 3) | (i % 2); + combs[i]->constr_abs_z = true; + combs[i]->constr_parent = combs[0]; + combs[0]->constr_children.push_back(combs[i]); + } + + ramw->constr_x = 0; + ramw->constr_y = 0; + ramw->constr_z = (2 << 3) | Arch::BEL_RAMW; + ramw->constr_abs_z = true; + ramw->constr_parent = combs[0]; + combs[0]->constr_children.push_back(ramw); + // Remove now-packed cell + ctx->cells.erase(ci->name); + } + } + + void convert_prims() + { + // Convert primitives from their non-CORE variant to their CORE variant + static const std::unordered_map<IdString, IdString> prim_map = { + {id_OSCA, id_OSC_CORE}, {id_DP16K, id_DP16K_MODE}, {id_PDP16K, id_PDP16K_MODE}, + {id_PDPSC16K, id_PDPSC16K_MODE}, {id_SP16K, id_SP16K_MODE}, {id_FIFO16K, id_FIFO16K_MODE}, + }; + + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (!prim_map.count(ci->type)) + continue; + prim_to_core(ci, prim_map.at(ci->type)); + } + } + + void add_bus_xform(XFormRule &rule, const std::string &o, const std::string &n, int width, int old_offset = 0, + int new_offset = 0) + { + for (int i = 0; i < width; i++) + rule.port_xform[bus_flat(o, i + old_offset)] = bus_flat(n, i + new_offset); + } + + void pack_bram() + { + std::unordered_map<IdString, XFormRule> bram_rules; + bram_rules[id_DP16K_MODE].new_type = id_OXIDE_EBR; + bram_rules[id_DP16K_MODE].set_params.emplace_back(id_MODE, std::string("DP16K")); + bram_rules[id_DP16K_MODE].parse_params.emplace_back(id_CSDECODE_A, id_CSDECODE_A, 3, 7); + bram_rules[id_DP16K_MODE].parse_params.emplace_back(id_CSDECODE_B, id_CSDECODE_B, 3, 7); + // Pseudo dual port + bram_rules[id_PDP16K_MODE].new_type = id_OXIDE_EBR; + bram_rules[id_PDP16K_MODE].set_params.emplace_back(id_MODE, std::string("PDP16K")); + bram_rules[id_PDP16K_MODE].set_params.emplace_back(id_WEAMUX, std::string("1")); + bram_rules[id_PDP16K_MODE].parse_params.emplace_back(id_CSDECODE_R, id_CSDECODE_R, 3, 7); + bram_rules[id_PDP16K_MODE].parse_params.emplace_back(id_CSDECODE_W, id_CSDECODE_W, 3, 7); + bram_rules[id_PDP16K_MODE].port_xform[id_CLKW] = id_CLKA; + bram_rules[id_PDP16K_MODE].port_xform[id_CLKR] = id_CLKB; + bram_rules[id_PDP16K_MODE].port_xform[id_CEW] = id_CEA; + bram_rules[id_PDP16K_MODE].port_xform[id_CER] = id_CEB; + bram_rules[id_PDP16K_MODE].port_multixform[id_RST] = {id_RSTA, id_RSTB}; + add_bus_xform(bram_rules[id_PDP16K_MODE], "ADW", "ADA", 14); + add_bus_xform(bram_rules[id_PDP16K_MODE], "ADR", "ADB", 14); + add_bus_xform(bram_rules[id_PDP16K_MODE], "CSW", "CSA", 3); + add_bus_xform(bram_rules[id_PDP16K_MODE], "CSR", "CSB", 3); + add_bus_xform(bram_rules[id_PDP16K_MODE], "DI", "DIA", 18, 0, 0); + add_bus_xform(bram_rules[id_PDP16K_MODE], "DI", "DIB", 18, 18, 0); + add_bus_xform(bram_rules[id_PDP16K_MODE], "DO", "DOB", 18, 0, 0); + add_bus_xform(bram_rules[id_PDP16K_MODE], "DO", "DOA", 18, 18, 0); + + // Pseudo dual port; single clock + bram_rules[id_PDPSC16K_MODE] = bram_rules[id_PDP16K_MODE]; + bram_rules[id_PDPSC16K_MODE].set_params.clear(); + bram_rules[id_PDPSC16K_MODE].set_params.emplace_back(id_MODE, std::string("PDPSC16K")); + bram_rules[id_PDPSC16K_MODE].set_params.emplace_back(id_WEAMUX, std::string("1")); + bram_rules[id_PDPSC16K_MODE].port_multixform[id_CLK] = {id_CLKA, id_CLKB}; + + log_info("Packing BRAM...\n"); + generic_xform(bram_rules, true); + + int wid = 2; + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (ci->type != id_OXIDE_EBR) + continue; + if (ci->params.count(id_WID)) + continue; + ci->params[id_WID] = wid++; + } + } + + void pack_widefn() + { + std::vector<CellInfo *> widefns; + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (ci->type != id_WIDEFN9) + continue; + widefns.push_back(ci); + } + + for (CellInfo *ci : widefns) { + std::vector<CellInfo *> combs; + for (int i = 0; i < 2; i++) + combs.push_back( + ctx->createCell(ctx->id(stringf("%s$widefn_comb[%d]$", ctx->nameOf(ci), i)), id_OXIDE_COMB)); + + for (int i = 0; i < 2; i++) { + replace_port(ci, bus_flat("A", i), combs[i], id_A); + replace_port(ci, bus_flat("B", i), combs[i], id_B); + replace_port(ci, bus_flat("C", i), combs[i], id_C); + replace_port(ci, bus_flat("D", i), combs[i], id_D); + } + + replace_port(ci, id_SEL, combs[0], id_SEL); + replace_port(ci, id_Z, combs[0], id_OFX); + + NetInfo *f1 = ctx->createNet(ctx->id(stringf("%s$widefn_f1$", ctx->nameOf(ci)))); + combs[0]->addInput(id_F1); + combs[1]->addOutput(id_F); + connect_port(ctx, f1, combs[1], id_F); + connect_port(ctx, f1, combs[0], id_F1); + + combs[0]->params[id_INIT] = ctx->parse_lattice_param(ci, id_INIT0, 16, 0); + combs[1]->params[id_INIT] = ctx->parse_lattice_param(ci, id_INIT1, 16, 0); + + combs[1]->constr_parent = combs[0]; + combs[1]->constr_x = 0; + combs[1]->constr_y = 0; + combs[1]->constr_z = 1; + combs[1]->constr_abs_z = false; + combs[0]->constr_children.push_back(combs[1]); + + ctx->cells.erase(ci->name); + } + } + + void pack_carries() + { + // Find root carry cells + log_info("Packing carries...\n"); + std::vector<CellInfo *> roots; + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (ci->type != id_CCU2) + continue; + if (get_net_or_empty(ci, id_CIN) != nullptr) + continue; + roots.push_back(ci); + } + for (CellInfo *root : roots) { + CellInfo *ci = root; + CellInfo *constr_base = nullptr; + int idx = 0; + do { + if (ci->type != id_CCU2) + log_error("Found non-carry cell '%s' in carry chain!\n", ctx->nameOf(ci)); + // Split the carry into two COMB cells + std::vector<CellInfo *> combs; + for (int i = 0; i < 2; i++) + combs.push_back( + ctx->createCell(ctx->id(stringf("%s$ccu2_comb[%d]$", ctx->nameOf(ci), i)), id_OXIDE_COMB)); + // Rewire LUT ports + for (int i = 0; i < 2; i++) { + combs[i]->params[id_MODE] = std::string("CCU2"); + replace_port(ci, bus_flat("A", i), combs[i], id_A); + replace_port(ci, bus_flat("B", i), combs[i], id_B); + replace_port(ci, bus_flat("C", i), combs[i], id_C); + replace_port(ci, bus_flat("D", i), combs[i], id_D); + replace_port(ci, bus_flat("S", i), combs[i], id_F); + } + + // External carry chain + replace_port(ci, id_CIN, combs[0], id_FCI); + replace_port(ci, id_COUT, combs[1], id_FCO); + + // Copy parameters + if (ci->params.count(id_INJECT)) + combs[0]->params[id_INJECT] = ci->params[id_INJECT]; + combs[0]->params[id_INIT] = ctx->parse_lattice_param(ci, id_INIT0, 16, 0); + combs[1]->params[id_INIT] = ctx->parse_lattice_param(ci, id_INIT1, 16, 0); + + // Internal carry net between the two split COMB cells + NetInfo *int_cy = ctx->createNet(ctx->id(stringf("%s$widefn_int_cy$", ctx->nameOf(ci)))); + combs[0]->addOutput(id_FCO); + combs[1]->addInput(id_FCI); + connect_port(ctx, int_cy, combs[0], id_FCO); + connect_port(ctx, int_cy, combs[1], id_FCI); + + // Relative constraints + for (int i = 0; i < 2; i++) { + int z = (idx % 8); + combs[i]->constr_z = ((z / 2) << 3) | (z % 2); + combs[i]->constr_abs_z = true; + if (constr_base == nullptr) { + // This is the very first cell in the chain + constr_base = combs[i]; + } else { + combs[i]->constr_x = (idx / 8); + combs[i]->constr_y = 0; + combs[i]->constr_parent = constr_base; + constr_base->constr_children.push_back(combs[i]); + } + + ++idx; + } + + ctx->cells.erase(ci->name); + + // Find next cell in chain, if it exists + NetInfo *fco = get_net_or_empty(combs[1], id_FCO); + ci = nullptr; + if (fco != nullptr) { + if (fco->users.size() > 1) + log_error("Carry cell '%s' has multiple fanout on FCO\n", ctx->nameOf(combs[1])); + else if (fco->users.size() == 1) { + NPNR_ASSERT(fco->users.at(0).port == id_CIN); + ci = fco->users.at(0).cell; + } + } + } while (ci != nullptr); + } + } + + // Function to check if a wire is general routing; and therefore skipped for cascade purposes + bool is_general_routing(WireId wire) + { + std::string name = ctx->nameOf(ctx->wire_data(wire).name); + if (name.size() == 3 && (name.substr(0, 2) == "JF" || name.substr(0, 2) == "JQ")) + return true; + if (name.size() == 12 && (name.substr(0, 10) == "JCIBMUXOUT")) + return true; + return false; + } + + // Automatically generate cascade connections downstream of a cell + // using the temporary placement that we use solely to access the routing graph + void auto_cascade_cell(CellInfo *cell, BelId bel, const std::unordered_map<BelId, CellInfo *> &bel2cell) + { + // Create outputs based on the actual bel + for (auto bp : ctx->getBelPins(bel)) { + if (ctx->getBelPinType(bel, bp) != PORT_OUT) + continue; + if (cell->ports.count(bp)) + continue; + cell->addOutput(bp); + } + for (auto port : sorted_ref(cell->ports)) { + // Skip if not an output, or being used already for something else + if (port.second.type != PORT_OUT || port.second.net != nullptr) + continue; + // Get the corresponding start wire + WireId start_wire = ctx->getBelPinWire(bel, port.first); + + // Skip if the start wire doesn't actually exist + if (start_wire == WireId()) + continue; + + if (ctx->debug) + log_info(" searching cascade routing for wire %s:\n", ctx->nameOfWire(start_wire)); + + // Standard BFS-type exploration + std::queue<WireId> visit; + std::unordered_set<WireId> in_queue; + visit.push(start_wire); + in_queue.insert(start_wire); + int iter = 0; + const int iter_limit = 1000; + + while (!visit.empty() && (iter++ < iter_limit)) { + WireId cursor = visit.front(); + visit.pop(); + + if (ctx->debug) + log_info(" visit '%s'\n", ctx->nameOfWire(cursor)); + + // Check for downstream bel pins + bool found_active_pins = false; + for (auto bp : ctx->getWireBelPins(cursor)) { + auto fnd_cell = bel2cell.find(bp.bel); + // Always skip unused bels, and don't set found_active_pins + // so we can route through these if needed + if (fnd_cell == bel2cell.end()) + continue; + // Skip outputs + if (ctx->getBelPinType(bp.bel, bp.pin) != PORT_IN) + continue; + + if (ctx->debug) + log_info(" bel %s pin %s\n", ctx->nameOfBel(bp.bel), ctx->nameOf(bp.pin)); + + found_active_pins = true; + CellInfo *other_cell = fnd_cell->second; + + if (other_cell == cell) + continue; + + // Skip pins that are already in use + if (get_net_or_empty(other_cell, bp.pin) != nullptr) + continue; + // Create the input if it doesn't exist + if (!other_cell->ports.count(bp.pin)) + other_cell->addInput(bp.pin); + // Make the connection + connect_ports(ctx, cell, port.first, other_cell, bp.pin); + + if (ctx->debug) + log_info(" found %s.%s\n", ctx->nameOf(other_cell), ctx->nameOf(bp.pin)); + } + + // By doing this we never attempt to route-through bels + // that are actually in use + if (found_active_pins) + continue; + + // Search downstream pips for wires to add to the queue + for (auto pip : ctx->getPipsDownhill(cursor)) { + WireId dst = ctx->getPipDstWire(pip); + // Ignore general routing, as that isn't a useful cascade path + if (is_general_routing(dst)) + continue; + if (in_queue.count(dst)) + continue; + in_queue.insert(dst); + visit.push(dst); + } + } + } + } + + // Insert all the cascade connections for a group of cells given the root + void auto_cascade_group(CellInfo *root) + { + + auto get_child_loc = [&](Loc base, const CellInfo *sub) { + Loc l = base; + l.x += sub->constr_x; + l.y += sub->constr_y; + l.z = sub->constr_abs_z ? sub->constr_z : (sub->constr_z + base.z); + return l; + }; + + // We first create a temporary placement so we can access the routing graph + bool found = false; + std::unordered_map<BelId, CellInfo *> bel2cell; + std::unordered_map<IdString, BelId> cell2bel; + + for (BelId root_bel : ctx->getBels()) { + if (ctx->getBelType(root_bel) != root->type) + continue; + Loc root_loc = ctx->getBelLocation(root_bel); + found = true; + bel2cell.clear(); + cell2bel.clear(); + bel2cell[root_bel] = root; + cell2bel[root->name] = root_bel; + + for (auto child : root->constr_children) { + // Check that a valid placement exists for all children in the macro at this location + Loc c_loc = get_child_loc(root_loc, child); + BelId c_bel = ctx->getBelByLocation(c_loc); + if (c_bel == BelId()) { + found = false; + break; + } + if (ctx->getBelType(c_bel) != child->type) { + found = false; + break; + } + bel2cell[c_bel] = child; + cell2bel[child->name] = c_bel; + } + + if (found) + break; + } + + if (!found) + log_error("Failed to create temporary placement for cell '%s' of type '%s'\n", ctx->nameOf(root), + ctx->nameOf(root->type)); + + // Create the necessary new ports + autocreate_ports(root); + for (auto child : root->constr_children) + autocreate_ports(child); + + // Insert cascade connections from all cells in the macro + auto_cascade_cell(root, cell2bel.at(root->name), bel2cell); + for (auto child : root->constr_children) + auto_cascade_cell(child, cell2bel.at(child->name), bel2cell); + } + + // Create a DSP cell + CellInfo *create_dsp_cell(IdString base_name, IdString type, CellInfo *constr_base, int dx, int dz) + { + IdString name = ctx->id(stringf("%s/%s_x%d_z%d", ctx->nameOf(base_name), ctx->nameOf(type), dx, dz)); + CellInfo *cell = ctx->createCell(name, type); + if (constr_base != nullptr) { + // We might be constraining against an already-constrained cell + if (constr_base->constr_parent != nullptr) { + cell->constr_x = dx + constr_base->constr_x; + cell->constr_y = constr_base->constr_y; + cell->constr_z = dz + constr_base->constr_z; + cell->constr_abs_z = false; + cell->constr_parent = constr_base->constr_parent; + constr_base->constr_parent->constr_children.push_back(cell); + } else { + cell->constr_x = dx; + cell->constr_y = 0; + cell->constr_z = dz; + cell->constr_abs_z = false; + cell->constr_parent = constr_base; + constr_base->constr_children.push_back(cell); + } + } + // Setup some default parameters + if (type == id_PREADD9_CORE) { + cell->params[id_SIGNEDSTATIC_EN] = std::string("DISABLED"); + cell->params[id_BYPASS_PREADD9] = std::string("BYPASS"); + cell->params[id_CSIGNED] = std::string("DISABLED"); + cell->params[id_GSR] = std::string("DISABLED"); + cell->params[id_OPC] = std::string("INPUT_B_AS_PREADDER_OPERAND"); + cell->params[id_PREADDCAS_EN] = std::string("DISABLED"); + cell->params[id_REGBYPSBL] = std::string("REGISTER"); + cell->params[id_REGBYPSBR0] = std::string("BYPASS"); + cell->params[id_REGBYPSBR1] = std::string("BYPASS"); + cell->params[id_RESET] = std::string("SYNC"); + cell->params[id_SHIFTBL] = std::string("BYPASS"); + cell->params[id_SHIFTBR] = std::string("REGISTER"); + cell->params[id_SIGNEDSTATIC_EN] = std::string("DISABLED"); + cell->params[id_SR_18BITSHIFT_EN] = std::string("DISABLED"); + cell->params[id_SUBSTRACT_EN] = std::string("SUBTRACTION"); + } else if (type == id_MULT9_CORE) { + cell->params[id_ASIGNED_OPERAND_EN] = std::string("DISABLED"); + cell->params[id_BYPASS_MULT9] = std::string("USED"); + cell->params[id_GSR] = std::string("DISABLED"); + cell->params[id_REGBYPSA1] = std::string("BYPASS"); + cell->params[id_REGBYPSA2] = std::string("BYPASS"); + cell->params[id_REGBYPSB] = std::string("BYPASS"); + cell->params[id_RESET] = std::string("SYNC"); + cell->params[id_GSR] = std::string("DISABLED"); + cell->params[id_SHIFTA] = std::string("DISABLED"); + cell->params[id_SIGNEDSTATIC_EN] = std::string("DISABLED"); + cell->params[id_SR_18BITSHIFT_EN] = std::string("DISABLED"); + } else if (type == id_MULT18_CORE) { + cell->params[id_MULT18X18] = std::string("ENABLED"); + cell->params[id_ROUNDBIT] = std::string("ROUND_TO_BIT0"); + cell->params[id_ROUNDHALFUP] = std::string("DISABLED"); + cell->params[id_ROUNDRTZI] = std::string("ROUND_TO_ZERO"); + cell->params[id_SFTEN] = std::string("DISABLED"); + } else if (type == id_MULT18X36_CORE) { + cell->params[id_SFTEN] = std::string("DISABLED"); + cell->params[id_MULT18X36] = std::string("ENABLED"); + cell->params[id_MULT36] = std::string("DISABLED"); + cell->params[id_MULT36X36H] = std::string("USED_AS_LOWER_BIT_GENERATION"); + cell->params[id_ROUNDHALFUP] = std::string("DISABLED"); + cell->params[id_ROUNDRTZI] = std::string("ROUND_TO_ZERO"); + cell->params[id_ROUNDBIT] = std::string("ROUND_TO_BIT0"); + } else if (type == id_MULT36_CORE) { + cell->params[id_MULT36X36] = std::string("ENABLED"); + } else if (type == id_REG18_CORE) { + cell->params[id_GSR] = std::string("DISABLED"); + cell->params[id_REGBYPS] = std::string("BYPASS"); + cell->params[id_RESET] = std::string("SYNC"); + } else if (type == id_ACC54_CORE) { + cell->params[id_ACC108CASCADE] = std::string("BYPASSCASCADE"); + cell->params[id_ACCUBYPS] = std::string("USED"); + cell->params[id_ACCUMODE] = std::string("MODE7"); + cell->params[id_ADDSUBSIGNREGBYPS1] = std::string("BYPASS"); + cell->params[id_ADDSUBSIGNREGBYPS2] = std::string("BYPASS"); + cell->params[id_ADDSUBSIGNREGBYPS3] = std::string("BYPASS"); + cell->params[id_ADDSUB_CTRL] = std::string("ADD_ADD_CTRL_54_BIT_ADDER"); + cell->params[id_CASCOUTREGBYPS] = std::string("BYPASS"); + cell->params[id_CINREGBYPS1] = std::string("BYPASS"); + cell->params[id_CINREGBYPS2] = std::string("BYPASS"); + cell->params[id_CINREGBYPS3] = std::string("BYPASS"); + cell->params[id_CONSTSEL] = std::string("BYPASS"); + cell->params[id_CREGBYPS1] = std::string("BYPASS"); + cell->params[id_CREGBYPS2] = std::string("BYPASS"); + cell->params[id_CREGBYPS3] = std::string("BYPASS"); + cell->params[id_DSPCASCADE] = std::string("DISABLED"); + cell->params[id_GSR] = std::string("DISABLED"); + cell->params[id_LOADREGBYPS1] = std::string("BYPASS"); + cell->params[id_LOADREGBYPS2] = std::string("BYPASS"); + cell->params[id_LOADREGBYPS3] = std::string("BYPASS"); + cell->params[id_M9ADDSUBREGBYPS1] = std::string("BYPASS"); + cell->params[id_M9ADDSUBREGBYPS2] = std::string("BYPASS"); + cell->params[id_M9ADDSUBREGBYPS3] = std::string("BYPASS"); + cell->params[id_M9ADDSUB_CTRL] = std::string("ADDITION"); + cell->params[id_OUTREGBYPS] = std::string("BYPASS"); + cell->params[id_RESET] = std::string("SYNC"); + cell->params[id_ROUNDHALFUP] = std::string("DISABLED"); + cell->params[id_ROUNDRTZI] = std::string("ROUND_TO_ZERO"); + cell->params[id_ROUNDBIT] = std::string("ROUND_TO_BIT0"); + cell->params[id_SFTEN] = std::string("DISABLED"); + cell->params[id_SIGN] = std::string("DISABLED"); + cell->params[id_STATICOPCODE_EN] = std::string("DISABLED"); + } + return cell; + } + + void copy_global_dsp_params(CellInfo *orig, CellInfo *root) + { + if (root->params.count(id_GSR) && orig->params.count(id_GSR)) + root->params[id_GSR] = orig->params.at(id_GSR); + if (root->params.count(id_RESET) && orig->params.count(id_RESETMODE)) + root->params[id_RESET] = orig->params.at(id_RESETMODE); + for (auto child : root->constr_children) + copy_global_dsp_params(orig, child); + } + + void copy_param(CellInfo *orig, IdString orig_name, CellInfo *dst, IdString dst_name) + { + if (!orig->params.count(orig_name)) + return; + dst->params[dst_name] = orig->params[orig_name]; + } + + struct DSPMacroType + { + int a_width; // width of 'A' input + int b_width; // width of 'B' input + int c_width; // width of 'C' input + int z_width; // width of 'Z' output + int N9x9; // number of 9x9 mult+preadds + int N18x18; // number of 18x18 mult + int N18x36; // number of 18x36 mult + bool has_preadd; // preadder is used + bool has_addsub; // post-multiply ALU addsub is used + }; + + const std::unordered_map<IdString, DSPMacroType> dsp_types = { + {id_MULT9X9, {9, 9, 0, 18, 1, 0, 0, false, false}}, + {id_MULT18X18, {18, 18, 0, 36, 2, 1, 0, false, false}}, + {id_MULT18X36, {18, 36, 0, 54, 4, 2, 1, false, false}}, + {id_MULT36X36, {36, 36, 0, 72, 8, 4, 2, false, false}}, + {id_MULTPREADD9X9, {9, 9, 9, 18, 1, 0, 0, true, false}}, + {id_MULTPREADD18X18, {18, 18, 18, 36, 2, 1, 0, true, false}}, + {id_MULTADDSUB18X18, {18, 18, 54, 54, 2, 1, 0, false, true}}, + {id_MULTADDSUB36X36, {36, 36, 108, 108, 8, 4, 2, false, true}}, + }; + + void pack_dsps() + { + log_info("Packing DSPs...\n"); + std::vector<CellInfo *> to_remove; + + for (auto cell : sorted(ctx->cells)) { + CellInfo *ci = cell.second; + if (!dsp_types.count(ci->type)) + continue; + auto &mt = dsp_types.at(ci->type); + int Nreg18 = mt.z_width / 18; + + // Create consituent cells + std::vector<CellInfo *> preadd9(mt.N9x9), mult9(mt.N9x9), mult18(mt.N18x18), mult18x36(mt.N18x36), + reg18(Nreg18); + for (int i = 0; i < mt.N9x9; i++) { + preadd9[i] = create_dsp_cell(ci->name, id_PREADD9_CORE, preadd9[0], (i / 4) * 4 + (i / 2) % 2, (i % 2)); + mult9[i] = create_dsp_cell(ci->name, id_MULT9_CORE, preadd9[0], (i / 4) * 4 + (i / 2) % 2, (i % 2) + 2); + } + for (int i = 0; i < mt.N18x18; i++) + mult18[i] = create_dsp_cell(ci->name, id_MULT18_CORE, preadd9[0], (i / 2) * 4 + i % 2, 4); + for (int i = 0; i < mt.N18x36; i++) + mult18x36[i] = create_dsp_cell(ci->name, id_MULT18X36_CORE, preadd9[0], (i * 4) + 2, 4); + for (int i = 0; i < Nreg18; i++) { + int idx = i; + if (mt.has_addsub && (i >= 4)) + idx += 2; + reg18[i] = create_dsp_cell(ci->name, id_REG18_CORE, preadd9[0], (idx / 4) * 4 + 2, idx % 4); + } + + // Configure the 9x9 preadd+multiply blocks + for (int i = 0; i < mt.N9x9; i++) { + // B input split across pre-adders + int b_start = (9 * i) % mt.b_width; + copy_bus(ctx, ci, id_B, b_start, true, preadd9[i], id_B, 0, false, 9); + // A input split across MULT9s + int a_start = 9 * (i % 2) + 18 * (i / 4); + copy_bus(ctx, ci, id_A, a_start, true, mult9[i], id_A, 0, false, 9); + // Connect control set signals + copy_port(ctx, ci, id_CLK, mult9[i], id_CLK); + copy_port(ctx, ci, id_CEA, mult9[i], id_CEA); + copy_port(ctx, ci, id_RSTA, mult9[i], id_RSTA); + copy_port(ctx, ci, id_CLK, preadd9[i], id_CLK); + copy_port(ctx, ci, id_CEB, preadd9[i], id_CEB); + copy_port(ctx, ci, id_RSTB, preadd9[i], id_RSTB); + // Copy register configuration + copy_param(ci, id_REGINPUTA, mult9[i], id_REGBYPSA1); + copy_param(ci, id_REGINPUTB, preadd9[i], id_REGBYPSBR0); + + // Connect and configure pre-adder if it isn't bypassed + if (mt.has_preadd) { + copy_bus(ctx, ci, id_C, 9 * i, true, preadd9[i], id_C, 0, false, 9); + if (i == (mt.N9x9 - 1)) + copy_port(ctx, ci, id_SIGNEDC, preadd9[i], id_C9); + copy_param(ci, id_REGINPUTC, preadd9[i], id_REGBYPSBL); + copy_port(ctx, ci, id_CEC, preadd9[i], id_CECL); + copy_port(ctx, ci, id_RSTC, preadd9[i], id_RSTCL); + // Enable preadder + preadd9[i]->params[id_BYPASS_PREADD9] = std::string("USED"); + preadd9[i]->params[id_OPC] = std::string("INPUT_C_AS_PREADDER_OPERAND"); + if (i > 0) + preadd9[i]->params[id_PREADDCAS_EN] = std::string("ENABLED"); + } else if (mt.has_addsub) { + // Connect only for routeability reasons + copy_bus(ctx, ci, id_C, 10 * i + ((i >= 4) ? 14 : 0), true, preadd9[i], id_C, 0, false, 10); + } + + // Connect up signedness for the most significant nonet + if ((b_start + 9) == mt.b_width) + copy_port(ctx, ci, mt.has_addsub ? id_SIGNED : id_SIGNEDB, preadd9[i], id_BSIGNED); + if ((a_start + 9) == mt.a_width) + copy_port(ctx, ci, mt.has_addsub ? id_SIGNED : id_SIGNEDA, mult9[i], id_ASIGNED); + } + + bool mult36_used = (mt.a_width >= 36) && (mt.b_width >= 36); + // Configure mult18x36s + for (int i = 0; i < mt.N18x36; i++) { + mult18x36[i]->params[id_MULT36] = mult36_used ? std::string("ENABLED") : std::string("DISABLED"); + mult18x36[i]->params[id_MULT36X36H] = (i == 1) ? std::string("USED_AS_HIGHER_BIT_GENERATION") + : std::string("USED_AS_LOWER_BIT_GENERATION"); + } + // Create final mult36 if needed + CellInfo *mult36 = nullptr; + if (mult36_used) { + mult36 = create_dsp_cell(ci->name, id_MULT36_CORE, preadd9[0], 6, 6); + } + + // Configure output registers + for (int i = 0; i < Nreg18; i++) { + // Output split across reg18s + if (!mt.has_addsub) + replace_bus(ctx, ci, id_Z, i * 18, true, reg18[i], id_PP, 0, false, 18); + // Connect control set signals + copy_port(ctx, ci, id_CLK, reg18[i], id_CLK); + copy_port(ctx, ci, mt.has_addsub ? id_CEPIPE : id_CEOUT, reg18[i], id_CEP); + copy_port(ctx, ci, mt.has_addsub ? id_RSTPIPE : id_RSTOUT, reg18[i], id_RSTP); + // Copy register configuration + copy_param(ci, mt.has_addsub ? id_REGPIPELINE : id_REGOUTPUT, reg18[i], id_REGBYPS); + } + + if (mt.has_addsub) { + // Create and configure ACC54s + int Nacc54 = mt.c_width / 54; + std::vector<CellInfo *> acc54(Nacc54); + for (int i = 0; i < Nacc54; i++) + acc54[i] = create_dsp_cell(ci->name, id_ACC54_CORE, preadd9[0], (i * 4) + 2, 5); + for (int i = 0; i < Nacc54; i++) { + // C addsub input + copy_bus(ctx, ci, id_C, 54 * i, true, acc54[i], id_CINPUT, 0, false, 54); + // Output + replace_bus(ctx, ci, id_Z, i * 54, true, acc54[i], id_SUM0, 0, false, 36); + replace_bus(ctx, ci, id_Z, i * 54 + 36, true, acc54[i], id_SUM1, 0, false, 18); + // Control set + copy_port(ctx, ci, id_CLK, acc54[i], id_CLK); + copy_port(ctx, ci, id_RSTCTRL, acc54[i], id_RSTCTRL); + copy_port(ctx, ci, id_CECTRL, acc54[i], id_CECTRL); + copy_port(ctx, ci, id_RSTCIN, acc54[i], id_RSTCIN); + copy_port(ctx, ci, id_CECIN, acc54[i], id_CECIN); + copy_port(ctx, ci, id_RSTOUT, acc54[i], id_RSTO); + copy_port(ctx, ci, id_CEOUT, acc54[i], id_CEO); + copy_port(ctx, ci, id_RSTC, acc54[i], id_RSTC); + copy_port(ctx, ci, id_CEC, acc54[i], id_CEC); + // Add/acc control + if (i == 0) + copy_port(ctx, ci, id_CIN, acc54[i], id_CIN); + else + ctx->set_cell_pinmux(acc54[i], id_CIN, PINMUX_1); + if (i == (Nacc54 - 1)) + copy_port(ctx, ci, id_SIGNED, acc54[i], id_SIGNEDI); + copy_port(ctx, ci, id_ADDSUB, acc54[i], id_ADDSUB0); + copy_port(ctx, ci, id_ADDSUB, acc54[i], id_ADDSUB1); + copy_port(ctx, ci, id_LOADC, acc54[i], id_LOAD); + // Configuration + copy_param(ci, id_REGINPUTC, acc54[i], id_CREGBYPS1); + copy_param(ci, id_REGADDSUB, acc54[i], id_ADDSUBSIGNREGBYPS1); + copy_param(ci, id_REGADDSUB, acc54[i], id_M9ADDSUBREGBYPS1); + copy_param(ci, id_REGLOADC, acc54[i], id_LOADREGBYPS1); + copy_param(ci, id_REGLOADC2, acc54[i], id_LOADREGBYPS2); + copy_param(ci, id_REGCIN, acc54[i], id_CINREGBYPS1); + + copy_param(ci, id_REGPIPELINE, acc54[i], id_CREGBYPS2); + copy_param(ci, id_REGPIPELINE, acc54[i], id_ADDSUBSIGNREGBYPS2); + copy_param(ci, id_REGPIPELINE, acc54[i], id_CINREGBYPS2); + copy_param(ci, id_REGPIPELINE, acc54[i], id_M9ADDSUBREGBYPS2); + copy_param(ci, id_REGOUTPUT, acc54[i], id_OUTREGBYPS); + + if (i == 1) { + // Top ACC54 in a 108-bit config + acc54[i]->params[id_ACCUMODE] = std::string("MODE6"); + acc54[i]->params[id_ACC108CASCADE] = std::string("CASCADE2ACCU54TOFORMACCU108"); + } else if ((i == 0) && (Nacc54 == 2)) { + // Bottom ACC54 in a 108-bit config + acc54[i]->params[id_ACCUMODE] = std::string("MODE2"); + } + } + } + + // Misc finalisation + copy_global_dsp_params(ci, preadd9[0]); + auto_cascade_group(preadd9[0]); + to_remove.push_back(ci); + } + + for (auto cell : to_remove) { + for (auto port : sorted_ref(cell->ports)) + disconnect_port(ctx, cell, port.first); + ctx->cells.erase(cell->name); + } + } + + explicit NexusPacker(Context *ctx) : ctx(ctx) {} + + void operator()() + { + pack_io(); + pack_dsps(); + convert_prims(); + pack_bram(); + pack_lutram(); + pack_carries(); + pack_widefn(); + pack_ffs(); + pack_constants(); + pack_luts(); + promote_globals(); + place_globals(); + } +}; + +bool Arch::pack() +{ + (NexusPacker(getCtx()))(); + attrs[id("step")] = std::string("pack"); + archInfoToAttributes(); + assignArchInfo(); + return true; +} + +// ----------------------------------------------------------------------- + +void Arch::assignArchInfo() +{ + for (auto cell : sorted(cells)) { + assignCellInfo(cell.second); + } +} + +const std::vector<std::string> dsp_bus_prefices = { + "M9ADDSUB", "ADDSUB", "SFTCTRL", "DSPIN", "CINPUT", "DSPOUT", "CASCOUT", "CASCIN", "PML72", "PMH72", "SUM1", + "SUM0", "BRS1", "BRS2", "BLS1", "BLS2", "BLSO", "BRSO", "PL18", "PH18", "PL36", "PH36", + "PL72", "PH72", "P72", "P36", "P18", "AS1", "AS2", "ARL", "ARH", "BRL", "BRH", + "AO", "BO", "AB", "AR", "BR", "PM", "PP", "A", "B", "C"}; + +void Arch::assignCellInfo(CellInfo *cell) +{ + cell->tmg_index = -1; + if (cell->type == id_OXIDE_COMB) { + cell->lutInfo.is_memory = str_or_default(cell->params, id_MODE, "LOGIC") == "DPRAM"; + cell->lutInfo.is_carry = str_or_default(cell->params, id_MODE, "LOGIC") == "CCU2"; + cell->lutInfo.mux2_used = port_used(cell, id_OFX); + cell->lutInfo.f = get_net_or_empty(cell, id_F); + cell->lutInfo.ofx = get_net_or_empty(cell, id_OFX); + if (cell->lutInfo.is_carry) { + cell->tmg_portmap[id_A] = id_A0; + cell->tmg_portmap[id_B] = id_B0; + cell->tmg_portmap[id_C] = id_C0; + cell->tmg_portmap[id_D] = id_D0; + cell->tmg_portmap[id_F] = id_F0; + cell->tmg_index = get_cell_timing_idx(id_OXIDE_COMB, id_CCU2); + } else if (cell->lutInfo.ofx != nullptr) { + cell->tmg_index = get_cell_timing_idx(id_OXIDE_COMB, id_WIDEFN9); + } else if (cell->lutInfo.is_memory) { + cell->tmg_index = get_cell_timing_idx(id_OXIDE_COMB, id_DPRAM); + } else { + cell->tmg_index = get_cell_timing_idx(id_OXIDE_COMB, id_LUT4); + } + } else if (cell->type == id_OXIDE_FF) { + cell->ffInfo.ctrlset.async = str_or_default(cell->params, id_SRMODE, "LSR_OVER_CE") == "ASYNC"; + cell->ffInfo.ctrlset.regddr_en = is_enabled(cell, id_REGDDR); + cell->ffInfo.ctrlset.gsr_en = is_enabled(cell, id_GSR); + cell->ffInfo.ctrlset.clkmux = id(str_or_default(cell->params, id_CLKMUX, "CLK")).index; + cell->ffInfo.ctrlset.cemux = id(str_or_default(cell->params, id_CEMUX, "CE")).index; + cell->ffInfo.ctrlset.lsrmux = id(str_or_default(cell->params, id_LSRMUX, "LSR")).index; + cell->ffInfo.ctrlset.clk = get_net_or_empty(cell, id_CLK); + cell->ffInfo.ctrlset.ce = get_net_or_empty(cell, id_CE); + cell->ffInfo.ctrlset.lsr = get_net_or_empty(cell, id_LSR); + cell->ffInfo.di = get_net_or_empty(cell, id_DI); + cell->ffInfo.m = get_net_or_empty(cell, id_M); + cell->tmg_index = get_cell_timing_idx(id_OXIDE_FF, id("PPP:SYNC")); + } else if (cell->type == id_RAMW) { + cell->ffInfo.ctrlset.async = true; + cell->ffInfo.ctrlset.regddr_en = false; + cell->ffInfo.ctrlset.gsr_en = false; + cell->ffInfo.ctrlset.clkmux = id(str_or_default(cell->params, id_CLKMUX, "CLK")).index; + cell->ffInfo.ctrlset.cemux = ID_CE; + cell->ffInfo.ctrlset.lsrmux = ID_INV; + cell->ffInfo.ctrlset.clk = get_net_or_empty(cell, id_CLK); + cell->ffInfo.ctrlset.ce = nullptr; + cell->ffInfo.ctrlset.lsr = get_net_or_empty(cell, id_LSR); + cell->ffInfo.di = nullptr; + cell->ffInfo.m = nullptr; + cell->tmg_index = get_cell_timing_idx(id_RAMW); + } else if (cell->type == id_OXIDE_EBR) { + // Strip off bus indices to get the timing ports + // as timing is generally word-wide + for (const auto &port : cell->ports) { + const std::string &name = port.first.str(this); + size_t idx_end = name.find_last_not_of("0123456789"); + std::string base = name.substr(0, idx_end + 1); + if (base == "ADA" || base == "ADB") { + // [4:0] and [13:5] have different timing + int idx = std::stoi(name.substr(idx_end + 1)); + cell->tmg_portmap[port.first] = id(base + ((idx >= 5) ? "_13_5" : "_4_0")); + } else { + // Just strip off bus index + cell->tmg_portmap[port.first] = id(base); + } + } + + cell->tmg_index = get_cell_timing_idx(id(str_or_default(cell->params, id_MODE, "DP16K") + "_MODE")); + NPNR_ASSERT(cell->tmg_index != -1); + } else if (is_dsp_cell(cell)) { + // Strip off bus indices to get the timing ports + // as timing is generally word-wide + for (const auto &port : cell->ports) { + const std::string &name = port.first.str(this); + size_t idx_end = name.find_last_not_of("0123456789"); + if (idx_end == std::string::npos) + continue; + for (const auto &p : dsp_bus_prefices) { + if (name.size() > p.size() && name.substr(0, p.size()) == p && idx_end <= p.size()) { + cell->tmg_portmap[port.first] = id(p); + break; + } + } + } + // Build up the configuration string + std::set<std::string> config; + for (const auto ¶m : cell->params) { + const std::string &name = param.first.str(this); + size_t byp_pos = name.find("REGBYPS"); + if (byp_pos != std::string::npos && param.second.str == "REGISTER") { + // Register enabled + config.insert(name.substr(0, byp_pos + 3) + name.substr(byp_pos + 7)); + } else if (param.first == id_BYPASS_PREADD9 && param.second.str == "BYPASS") { + // PREADD9 bypass + config.insert("BYPASS"); + } + } + std::string config_str; + for (const auto &cfg : config) { + if (!config_str.empty()) + config_str += ','; + config_str += cfg; + } + cell->tmg_index = get_cell_timing_idx(cell->type, id(config_str)); + if (cell->tmg_index == -1) { + log_warning("Unsupported timing config '%s' on %s cell '%s', falling back to default.\n", + config_str.c_str(), nameOf(cell->type), nameOf(cell)); + cell->tmg_index = get_cell_timing_idx(cell->type); + } + } +} + +NEXTPNR_NAMESPACE_END diff --git a/nexus/pdc.cc b/nexus/pdc.cc new file mode 100644 index 00000000..3efab69a --- /dev/null +++ b/nexus/pdc.cc @@ -0,0 +1,352 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2020 David Shah <dave@ds0.me> + * + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "log.h" +#include "nextpnr.h" + +#include <iterator> + +NEXTPNR_NAMESPACE_BEGIN + +struct TCLEntity +{ + enum EntityType + { + ENTITY_CELL, + ENTITY_PORT, + ENTITY_NET, + } type; + IdString name; + + TCLEntity(EntityType type, IdString name) : type(type), name(name) {} + + const std::string &to_string(Context *ctx) { return name.str(ctx); } + + CellInfo *get_cell(Context *ctx) + { + if (type != ENTITY_CELL) + return nullptr; + return ctx->cells.at(name).get(); + } + + PortInfo *get_port(Context *ctx) + { + if (type != ENTITY_PORT) + return nullptr; + return &ctx->ports.at(name); + } + + NetInfo *get_net(Context *ctx) + { + if (type != ENTITY_NET) + return nullptr; + return ctx->nets.at(name).get(); + } +}; + +struct TCLValue +{ + TCLValue(const std::string &s) : is_string(true), str(s){}; + TCLValue(const std::vector<TCLEntity> &l) : is_string(false), list(l){}; + + bool is_string; + std::string str; // simple string value + std::vector<TCLEntity> list; // list of entities +}; + +struct PDCParser +{ + std::string buf; + int pos = 0; + int lineno = 1; + Context *ctx; + + PDCParser(const std::string &buf, Context *ctx) : buf(buf), ctx(ctx){}; + + inline bool eof() const { return pos == int(buf.size()); } + + inline char peek() const { return buf.at(pos); } + + inline char get() + { + char c = buf.at(pos++); + if (c == '\n') + ++lineno; + return c; + } + + std::string get(int n) + { + std::string s = buf.substr(pos, n); + pos += n; + return s; + } + + // If next char matches c, take it from the stream and return true + bool check_get(char c) + { + if (peek() == c) { + get(); + return true; + } else { + return false; + } + } + + // If next char matches any in chars, take it from the stream and return true + bool check_get_any(const std::string &chrs) + { + char c = peek(); + if (chrs.find(c) != std::string::npos) { + get(); + return true; + } else { + return false; + } + } + + inline void skip_blank(bool nl = false) + { + while (!eof() && check_get_any(nl ? " \t\n\r" : " \t")) + ; + } + + // Return true if end of line (or file) + inline bool skip_check_eol() + { + skip_blank(false); + if (eof()) + return true; + char c = peek(); + // Comments count as end of line + if (c == '#') { + get(); + while (!eof() && peek() != '\n' && peek() != '\r') + get(); + return true; + } + if (c == ';') { + // Forced end of line + get(); + return true; + } + return (c == '\n' || c == '\r'); + } + + inline std::string get_str() + { + std::string s; + skip_blank(false); + if (eof()) + return ""; + + bool in_quotes = false, in_braces = false, escaped = false; + + char c = get(); + + if (c == '"') + in_quotes = true; + else if (c == '{') + in_braces = true; + else + s += c; + + while (true) { + char c = peek(); + if (!in_quotes && !in_braces && !escaped && (std::isblank(c) || c == ']')) { + break; + } + get(); + if (escaped) { + s += c; + escaped = false; + } else if ((in_quotes && c == '"') || (in_braces && c == '}')) { + break; + } else if (c == '\\') { + escaped = true; + } else { + s += c; + } + } + + return s; + } + + TCLValue evaluate(const std::vector<TCLValue> &arguments) + { + NPNR_ASSERT(!arguments.empty()); + auto &arg0 = arguments.at(0); + NPNR_ASSERT(arg0.is_string); + const std::string &cmd = arg0.str; + if (cmd == "get_ports") + return cmd_get_ports(arguments); + else if (cmd == "get_cells") + return cmd_get_cells(arguments); + else if (cmd == "ldc_set_location") + return cmd_ldc_set_location(arguments); + else if (cmd == "ldc_set_port") + return cmd_ldc_set_port(arguments); + else if (cmd == "ldc_set_sysconfig" || cmd == "get_nets" || cmd == "create_clock") { + log_warning("%s is not yet supported!\n", cmd.c_str()); + return TCLValue(""); + } else + log_error("Unsupported PDC command '%s'\n", cmd.c_str()); + } + + std::vector<TCLValue> get_arguments() + { + std::vector<TCLValue> args; + while (!skip_check_eol()) { + if (check_get('[')) { + // Start of a sub-expression + auto result = evaluate(get_arguments()); + NPNR_ASSERT(check_get(']')); + args.push_back(result); + } else if (peek() == ']') { + break; + } else { + args.push_back(get_str()); + } + } + skip_blank(true); + return args; + } + + TCLValue cmd_get_ports(const std::vector<TCLValue> &arguments) + { + std::vector<TCLEntity> ports; + for (int i = 1; i < int(arguments.size()); i++) { + auto &arg = arguments.at(i); + if (!arg.is_string) + log_error("get_ports expected string arguments (line %d)\n", lineno); + std::string s = arg.str; + if (s.at(0) == '-') + log_error("unsupported argument '%s' to get_ports (line %d)\n", s.c_str(), lineno); + IdString id = ctx->id(s); + if (ctx->ports.count(id)) + ports.emplace_back(TCLEntity::ENTITY_PORT, id); + } + return ports; + } + + TCLValue cmd_get_cells(const std::vector<TCLValue> &arguments) + { + std::vector<TCLEntity> cells; + for (int i = 1; i < int(arguments.size()); i++) { + auto &arg = arguments.at(i); + if (!arg.is_string) + log_error("get_cells expected string arguments (line %d)\n", lineno); + std::string s = arg.str; + if (s.at(0) == '-') + log_error("unsupported argument '%s' to get_cells (line %d)\n", s.c_str(), lineno); + IdString id = ctx->id(s); + if (ctx->cells.count(id)) + cells.emplace_back(TCLEntity::ENTITY_CELL, id); + } + return cells; + } + + TCLValue cmd_ldc_set_location(const std::vector<TCLValue> &arguments) + { + std::string site; + + for (int i = 1; i < int(arguments.size()); i++) { + auto &arg = arguments.at(i); + if (arg.is_string) { + std::string s = arg.str; + if (s == "-site") { + i++; + auto &val = arguments.at(i); + if (!val.is_string) + log_error("expecting string argument to -site (line %d)\n", lineno); + site = val.str; + } + } else { + if (site.empty()) + log_error("expecting -site before list of objects (line %d)\n", lineno); + for (const auto &ety : arg.list) { + if (ety.type == TCLEntity::ENTITY_PORT) + ctx->io_attr[ety.name][id_LOC] = site; + else if (ety.type == TCLEntity::ENTITY_CELL) + ctx->cells[ety.name]->attrs[id_LOC] = site; + else + log_error("ldc_set_location applies only to cells or IO ports (line %d)\n", lineno); + } + } + } + return std::string{}; + } + + TCLValue cmd_ldc_set_port(const std::vector<TCLValue> &arguments) + { + std::unordered_map<IdString, Property> args; + for (int i = 1; i < int(arguments.size()); i++) { + auto &arg = arguments.at(i); + if (arg.is_string) { + std::string s = arg.str; + if (s == "-iobuf") { + i++; + auto &val = arguments.at(i); + if (!val.is_string) + log_error("expecting string argument to -iobuf (line %d)\n", lineno); + std::stringstream ss(val.str); + std::string kv; + while (ss >> kv) { + auto eqp = kv.find('='); + if (eqp == std::string::npos) + log_error("expected key-value pair separated by '=' (line %d)", lineno); + std::string k = kv.substr(0, eqp), v = kv.substr(eqp + 1); + args[ctx->id(k)] = v; + } + } else { + log_error("unexpected argument '%s' to ldc_set_port (line %d)\n", s.c_str(), lineno); + } + } else { + for (const auto &ety : arg.list) { + if (ety.type == TCLEntity::ENTITY_PORT) + for (const auto &kv : args) + ctx->io_attr[ety.name][kv.first] = kv.second; + else + log_error("ldc_set_port applies only to IO ports (line %d)\n", lineno); + } + } + } + return std::string{}; + } + + void operator()() + { + while (!eof()) { + skip_blank(true); + auto args = get_arguments(); + if (args.empty()) + continue; + evaluate(args); + } + } +}; + +void Arch::read_pdc(std::istream &in) +{ + std::string buf(std::istreambuf_iterator<char>(in), {}); + PDCParser(buf, getCtx())(); +} + +NEXTPNR_NAMESPACE_END diff --git a/nexus/pins.cc b/nexus/pins.cc new file mode 100644 index 00000000..0587c032 --- /dev/null +++ b/nexus/pins.cc @@ -0,0 +1,180 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2020 David Shah <dave@ds0.me> + * + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +namespace { + +static const std::unordered_map<IdString, Arch::CellPinsData> base_cell_pin_data = { + {id_OXIDE_COMB, + { + {id_WCK, PINSTYLE_DEDI}, + {id_WRE, PINSTYLE_DEDI}, + + {id_FCI, PINSTYLE_DEDI}, + {id_F1, PINSTYLE_DEDI}, + {id_WAD0, PINSTYLE_DEDI}, + {id_WAD1, PINSTYLE_DEDI}, + {id_WAD2, PINSTYLE_DEDI}, + {id_WAD3, PINSTYLE_DEDI}, + {id_WDI, PINSTYLE_DEDI}, + + {{}, PINSTYLE_PU}, + }}, + {id_OXIDE_FF, + { + {id_CLK, PINSTYLE_CLK}, + {id_LSR, PINSTYLE_LSR}, + {id_CE, PINSTYLE_CE}, + {{}, PINSTYLE_DEDI}, + }}, + {id_RAMW, + { + {id_CLK, PINSTYLE_CLK}, + {{}, PINSTYLE_DEDI}, + }}, + {id_SEIO18_CORE, + { + {id_T, PINSTYLE_T}, + {id_B, PINSTYLE_DEDI}, + {{}, PINSTYLE_PU}, + }}, + {id_DIFFIO18_CORE, + { + {id_T, PINSTYLE_T}, + {id_B, PINSTYLE_DEDI}, + {{}, PINSTYLE_PU}, + }}, + {id_SEIO33_CORE, + { + {id_T, PINSTYLE_T}, + {id_B, PINSTYLE_DEDI}, + {{}, PINSTYLE_PU}, + }}, + {id_OXIDE_EBR, + {{id_CLKA, PINSTYLE_CLK}, {id_CLKB, PINSTYLE_CLK}, {id_CEA, PINSTYLE_CE}, {id_CEB, PINSTYLE_CE}, + {id_CSA0, PINSTYLE_PU}, {id_CSA1, PINSTYLE_PU}, {id_CSA2, PINSTYLE_PU}, {id_CSB0, PINSTYLE_PU}, + {id_CSB1, PINSTYLE_PU}, {id_CSB2, PINSTYLE_PU}, {id_ADA0, PINSTYLE_ADLSB}, {id_ADA1, PINSTYLE_ADLSB}, + {id_ADA2, PINSTYLE_ADLSB}, {id_ADA2, PINSTYLE_ADLSB}, {id_ADA3, PINSTYLE_ADLSB}, {id_ADB0, PINSTYLE_ADLSB}, + {id_ADB1, PINSTYLE_ADLSB}, {id_WEA, PINSTYLE_INV_PD}, {id_WEB, PINSTYLE_INV_PD}, {id_RSTA, PINSTYLE_INV_PD}, + {id_RSTB, PINSTYLE_INV_PD}, {{id_DWS0}, PINSTYLE_PU}, {{id_DWS1}, PINSTYLE_PU}, {{id_DWS2}, PINSTYLE_PU}, + {{id_DWS3}, PINSTYLE_PU}, {{id_DWS4}, PINSTYLE_PU}, {{}, PINSTYLE_CIB}}}, + {id_OSC_CORE, + { + {id_HFOUTEN, PINSTYLE_PU}, + {{}, PINSTYLE_CIB}, + }}, + {id_PREADD9_CORE, + { + {id_CLK, PINSTYLE_CLK}, {id_RSTCL, PINSTYLE_LSR}, {id_RSTB, PINSTYLE_LSR}, {id_CECL, PINSTYLE_CE}, + {id_CEB, PINSTYLE_CE}, + + {id_B0, PINSTYLE_CIB}, {id_B1, PINSTYLE_CIB}, {id_B2, PINSTYLE_CIB}, {id_B3, PINSTYLE_CIB}, + {id_B4, PINSTYLE_CIB}, {id_B5, PINSTYLE_CIB}, {id_B6, PINSTYLE_CIB}, {id_B7, PINSTYLE_CIB}, + {id_B8, PINSTYLE_CIB}, {id_BSIGNED, PINSTYLE_CIB}, + + {id_C0, PINSTYLE_CIB}, {id_C1, PINSTYLE_CIB}, {id_C2, PINSTYLE_CIB}, {id_C3, PINSTYLE_CIB}, + {id_C4, PINSTYLE_CIB}, {id_C5, PINSTYLE_CIB}, {id_C6, PINSTYLE_CIB}, {id_C7, PINSTYLE_CIB}, + {id_C8, PINSTYLE_CIB}, {id_C9, PINSTYLE_CIB}, + + {{}, PINSTYLE_DEDI}, + }}, + {id_MULT9_CORE, + { + {id_CLK, PINSTYLE_CLK}, + {id_RSTA, PINSTYLE_LSR}, + {id_RSTP, PINSTYLE_LSR}, + {id_CEA, PINSTYLE_CE}, + {id_CEP, PINSTYLE_CE}, + + {id_A0, PINSTYLE_CIB}, + {id_A1, PINSTYLE_CIB}, + {id_A2, PINSTYLE_CIB}, + {id_A3, PINSTYLE_CIB}, + {id_A4, PINSTYLE_CIB}, + {id_A5, PINSTYLE_CIB}, + {id_A6, PINSTYLE_CIB}, + {id_A7, PINSTYLE_CIB}, + {id_A8, PINSTYLE_CIB}, + {id_ASIGNED, PINSTYLE_CIB}, + + {{}, PINSTYLE_DEDI}, + }}, + {id_REG18_CORE, + { + {id_CLK, PINSTYLE_CLK}, + {id_RSTP, PINSTYLE_LSR}, + {id_CEP, PINSTYLE_CE}, + {{}, PINSTYLE_DEDI}, + }}, + {id_MULT18_CORE, + { + {id_SFTCTRL0, PINSTYLE_PU}, + {id_SFTCTRL1, PINSTYLE_PU}, + {id_SFTCTRL2, PINSTYLE_PU}, + {id_SFTCTRL3, PINSTYLE_PU}, + {id_ROUNDEN, PINSTYLE_CIB}, + {{}, PINSTYLE_DEDI}, + }}, + {id_MULT18X36_CORE, + { + {id_SFTCTRL0, PINSTYLE_PU}, + {id_SFTCTRL1, PINSTYLE_PU}, + {id_SFTCTRL2, PINSTYLE_PU}, + {id_SFTCTRL3, PINSTYLE_PU}, + {id_ROUNDEN, PINSTYLE_CIB}, + {{}, PINSTYLE_DEDI}, + }}, + {id_ACC54_CORE, + { + {id_CLK, PINSTYLE_CLK}, {id_RSTC, PINSTYLE_LSR}, {id_CEC, PINSTYLE_CE}, + {id_SIGNEDI, PINSTYLE_CIB}, {id_RSTCTRL, PINSTYLE_LSR}, {id_CECTRL, PINSTYLE_CE}, + {id_RSTCIN, PINSTYLE_LSR}, {id_CECIN, PINSTYLE_CE}, {id_LOAD, PINSTYLE_CIB}, + {id_ADDSUB0, PINSTYLE_CIB}, {id_ADDSUB1, PINSTYLE_CIB}, {id_M9ADDSUB0, PINSTYLE_PU}, + {id_M9ADDSUB1, PINSTYLE_PU}, {id_ROUNDEN, PINSTYLE_CIB}, {id_RSTO, PINSTYLE_LSR}, + {id_CEO, PINSTYLE_CE}, {id_CIN, PINSTYLE_CIB}, {id_SFTCTRL0, PINSTYLE_PU}, + {id_SFTCTRL1, PINSTYLE_PU}, {id_SFTCTRL2, PINSTYLE_PU}, {id_SFTCTRL3, PINSTYLE_PU}, + {{}, PINSTYLE_DEDI}, + }}}; +} // namespace + +void Arch::init_cell_pin_data() { cell_pins_db = base_cell_pin_data; } + +CellPinStyle Arch::get_cell_pin_style(const CellInfo *cell, IdString port) const +{ + // Look up the pin style in the cell database + auto fnd_cell = cell_pins_db.find(cell->type); + if (fnd_cell == cell_pins_db.end()) + return PINSTYLE_NONE; + auto fnd_port = fnd_cell->second.find(port); + if (fnd_port != fnd_cell->second.end()) + return fnd_port->second; + // If there isn't an exact port match, then the empty IdString + // represents a wildcard default match + auto fnd_default = fnd_cell->second.find({}); + if (fnd_default != fnd_cell->second.end()) + return fnd_default->second; + + return PINSTYLE_NONE; +} + +NEXTPNR_NAMESPACE_END diff --git a/nexus/post_place.cc b/nexus/post_place.cc new file mode 100644 index 00000000..65676188 --- /dev/null +++ b/nexus/post_place.cc @@ -0,0 +1,161 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2020 David Shah <dave@ds0.me> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "design_utils.h" +#include "log.h" +#include "nextpnr.h" +#include "timing.h" +#include "util.h" + +NEXTPNR_NAMESPACE_BEGIN + +struct NexusPostPlaceOpt +{ + Context *ctx; + NetCriticalityMap net_crit; + + NexusPostPlaceOpt(Context *ctx) : ctx(ctx){}; + + inline bool is_constrained(CellInfo *cell) + { + return cell->constr_parent != nullptr || !cell->constr_children.empty(); + } + + bool swap_cell_placement(CellInfo *cell, BelId new_bel) + { + if (is_constrained(cell)) + return false; + BelId oldBel = cell->bel; + CellInfo *other_cell = ctx->getBoundBelCell(new_bel); + if (other_cell != nullptr && (is_constrained(other_cell) || other_cell->belStrength > STRENGTH_WEAK)) { + return false; + } + + ctx->unbindBel(oldBel); + if (other_cell != nullptr) { + ctx->unbindBel(new_bel); + } + + ctx->bindBel(new_bel, cell, STRENGTH_WEAK); + + if (other_cell != nullptr) { + ctx->bindBel(oldBel, other_cell, STRENGTH_WEAK); + } + + if (!ctx->isBelLocationValid(new_bel) || ((other_cell != nullptr && !ctx->isBelLocationValid(oldBel)))) { + // New placement is not legal. + ctx->unbindBel(new_bel); + if (other_cell != nullptr) + ctx->unbindBel(oldBel); + + // Revert. + ctx->bindBel(oldBel, cell, STRENGTH_WEAK); + if (other_cell != nullptr) + ctx->bindBel(new_bel, other_cell, STRENGTH_WEAK); + return false; + } + + return true; + } + + int get_distance(BelId a, BelId b) + { + Loc la = ctx->getBelLocation(a); + Loc lb = ctx->getBelLocation(b); + return std::abs(la.x - lb.x) + std::abs(la.y - lb.y); + } + + BelId lut_to_ff(BelId lut) + { + Loc ff_loc = ctx->getBelLocation(lut); + ff_loc.z += (Arch::BEL_FF0 - Arch::BEL_LUT0); + return ctx->getBelByLocation(ff_loc); + } + + void opt_lutffs() + { + int moves_made = 0; + for (auto cell : sorted(ctx->cells)) { + // Search for FF cells + CellInfo *ff = cell.second; + if (ff->type != id_OXIDE_FF) + continue; + // Check M ('fabric') input net + NetInfo *m = get_net_or_empty(ff, id_M); + if (m == nullptr) + continue; + + // Ignore FFs that need both DI and M (PRLD mode) + if (get_net_or_empty(ff, id_DI) != nullptr) + continue; + + const auto &drv = m->driver; + // Skip if driver isn't a LUT/MUX2 + if (drv.cell == nullptr || drv.cell->type != id_OXIDE_COMB || (drv.port != id_F && drv.port != id_OFX)) + continue; + CellInfo *lut = drv.cell; + // Check distance to move isn't too far + if (get_distance(ff->bel, lut->bel) > lut_ff_radius) + continue; + // Find the bel we plan to move into + BelId dest_ff = lut_to_ff(lut->bel); + NPNR_ASSERT(dest_ff != BelId()); + NPNR_ASSERT(ctx->getBelType(dest_ff) == id_OXIDE_FF); + // Ended up in the ideal location by chance + if (dest_ff != ff->bel) { + // If dest_ff is already placed *and* using direct 'DI' input, don't touch it + CellInfo *dest_ff_cell = ctx->getBoundBelCell(dest_ff); + if (dest_ff_cell != nullptr && get_net_or_empty(dest_ff_cell, id_DI) != nullptr) + continue; + // Attempt the swap + bool swap_result = swap_cell_placement(ff, dest_ff); + if (!swap_result) + continue; + } + // Use direct interconnect + rename_port(ctx, ff, id_M, id_DI); + ff->params[id_SEL] = std::string("DL"); + ++moves_made; + continue; + } + log_info(" created %d direct LUT-FF pairs\n", moves_made); + } + + void operator()() + { + get_criticalities(ctx, &net_crit); + opt_lutffs(); + } + + // Configuration + const int lut_ff_radius = 2; + const int lut_lut_radius = 1; + const float lut_lut_crit = 0.85; +}; + +void Arch::post_place_opt() +{ + if (bool_or_default(settings, id("no_post_place_opt"))) + return; + log_info("Running post-place optimisations...\n"); + NexusPostPlaceOpt opt(getCtx()); + opt(); +} + +NEXTPNR_NAMESPACE_END |