diff --git a/.gitignore b/.gitignore index 96e6e3cd..40c27b4c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /nextpnr-ice40* /nextpnr-ecp5* /nextpnr-nexus* +/nextpnr-fpga_interchange* cmake-build-*/ Makefile cmake_install.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index b6fddcf1..1bfef987 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,9 +66,9 @@ endif() set(PROGRAM_PREFIX "" CACHE STRING "Name prefix for executables") # List of families to build -set(FAMILIES generic ice40 ecp5 nexus gowin) +set(FAMILIES generic ice40 ecp5 nexus gowin fpga_interchange) set(STABLE_FAMILIES generic ice40 ecp5) -set(EXPERIMENTAL_FAMILIES nexus gowin) +set(EXPERIMENTAL_FAMILIES nexus gowin fpga_interchange) set(ARCH "" CACHE STRING "Architecture family for nextpnr build") set_property(CACHE ARCH PROPERTY STRINGS ${FAMILIES}) diff --git a/common/relptr.h b/common/relptr.h index 035d61fb..d45912ab 100644 --- a/common/relptr.h +++ b/common/relptr.h @@ -32,6 +32,7 @@ NPNR_PACKED_STRUCT(template struct RelSlice { const T *end() const { return get() + length; } const size_t size() const { return length; } + const ptrdiff_t ssize() const { return length; } const T &operator*() const { return *(get()); } diff --git a/fpga_interchange/README.md b/fpga_interchange/README.md new file mode 100644 index 00000000..d0c4f2bf --- /dev/null +++ b/fpga_interchange/README.md @@ -0,0 +1,162 @@ +## FPGA interchange nextpnr architecture + +This nextpnr architecture is a meta architecture that in theory will implement +any architecture that emits a complete FPGA interchange device database. + +### FPGA interchange + +The FPGA interchange is a set of file formats intended to describe any modern +island based FPGA. It consists of three primary file formats: + + - Device database + - This is a description of a particular FPGA fabric. This description + includes placement locations, placement constraints and a complete + description of the routing fabric. + - This file will also include timing information once added. + + - Logical netlist + - This is the output of a synthesis tool. This is equivalent to the + Yosys JSON format, EDIF, or eblif. + - As part of future nextpnr development, a frontend will be added that + takes this format as input. + + - Physical netlist + - This is the output of a place and route tool. It can describe a clustered + design, a partially or fully placed design, and a partially or fully + routed design. + +### Current status + +This architecture implementation can be compiled in conjunction with a FPGA +interchange device database, and the outputs from +`fpga_interchange.nextpnr_emit`, which is part of the +[python-fpga-interchange](https://github.com/SymbiFlow/python-fpga-interchange/) +library. + +The current implementation is missing essential features for place and route. +As these features are added, this implementation will become more useful. + + - [ ] Placement constraints are unimplemented, meaning invalid or unroutable + designs can be generated from the placer. + - [ ] Logical netlist macro expansion is not implemented, meaning that any + macro primitives are unplaceable. Common macro primitives examples are + differential IO buffers (IBUFDS) and some LUT RAM (e.g. RAM64X1D). + - [ ] Cell -> BEL pin mapping is not in place, meaning any primitives that + have different BEL pins with respect to their cell pins will not be + routable. + - [ ] Nextpnr only allows for cell -> BEL pin maps that are 1 to 1. The + FPGA interchange accommodates cell -> BEL pin maps that include 1 to + many relationships for sinks. A common primitives that uses 1 to many + maps are the RAMB18E1. + - [ ] The router lookahead is missing, meaning that router runtime + performance will be terrible. + - [ ] Physical netlist backend is missing, so even if + `nextpnr-fpga_interchange` completes successfully, there is no way to + generate output that can be consumed by downstream tools. + - [ ] XDC parsing and port constraints are unimplemented, so IO pins cannot + be fixed. The chipdb BBA output is also missing package pin data, so + only site constraints are currently possible. Eventually the chipdb BBA + should also include package pin data to allow for ports to be bound to + package pins. + - [ ] The routing graph that is currently emitted does not have ground and + VCC networks, so all signals must currently be tied to an IO signal. + Site pins being tied to constants also needs handling so that site + local inverters are used rather than routing signals suboptimally. + - [ ] Pseudo pips (e.g. pips that consume BELs and or site resources) should + block their respective resources. This effects designs that have some + routing in place before placement. + - [ ] Pseudo site pips (e.g. site pips that route through BELs) should block + their respective resources. Without this, using some pseudo site pips + could result in invalid placements. + - [ ] Timing information is missing from the FPGA interchange device + database, so it is also currently missing from the FPGA interchange + architecture. Once timing information is added to the device database + schema, it needs to be added to the architecture. + +#### FPGA interchange fabrics + +Currently only Xilinx 7-series, UltraScale and UltraScale+ fabrics have a +device database generator, via [RapidWright](https://github.com/Xilinx/RapidWright). + +##### Artix 35T example + +Download RapidWright and generate the device database. +``` +# FIXME: Use main branch once interchange branch is merged. +git clone -b interchange https://github.com/Xilinx/RapidWright.git +cd RapidWright +make update_jars + +# FIXME: Current RapidWright jars generate database with duplicate PIPs +# https://github.com/Xilinx/RapidWright/issues/127 +# Remove this wget once the latest RapidWright JAR is published. +wget https://github.com/Xilinx/RapidWright/releases/download/v2020.2.1-beta/rapidwright-api-lib-2020.2.1_update1.jar +mv rapidwright-api-lib-2020.2.1_update1.jar jars/rapidwright-api-lib-2020.2.0.jar + +./scripts/invoke_rapidwright.sh com.xilinx.rapidwright.interchange.DeviceResourcesExample xc7a35tcpg236-1 +export RAPIDWRIGHT_PATH=$(pwd) +export INTERCHANGE_DIR=$(pwd)/interchange +``` + +Install python FPGA interchange library. +``` +git clone https://github.com/SymbiFlow/python-fpga-interchange.git +cd python-fpga-interchange +pip install -r requirements.txt +``` + +Patch device database with cell constraints and LUT annotations: +``` +python3 -mfpga_interchange.patch \ + --schema_dir ${INTERCHANGE_DIR} \ + --schema device \ + --patch_path constraints \ + --patch_format yaml \ + ${RAPIDWRIGHT_PATH}/xc7a35tcpg236-1.device \ + test_data/series7_constraints.yaml \ + xc7a35tcpg236-1_constraints.device +python3 -mfpga_interchange.patch \ + --schema_dir ${INTERCHANGE_DIR} \ + --schema device \ + --patch_path lutDefinitions \ + --patch_format yaml \ + xc7a35tcpg236-1_constraints.device \ + test_data/series7_luts.yaml \ + xc7a35tcpg236-1_constraints_luts.device +``` + +Generate nextpnr BBA and constids.inc from device database: +``` +python3 -mfpga_interchange.nextpnr_emit \ + --schema_dir ${INTERCHANGE_DIR} \ + --output_dir ${NEXTPNR_DIR}/fpga_interchange/ \ + --device xc7a35tcpg236-1_constraints_luts.device +``` + +Build nextpnr: + +``` +cd ${NEXTPNR_DIR} +cmake -DARCH=fpga_interchange . +make -j +``` + +Compile generated BBA: +``` +bba/bbasm -l fpga_interchange/chipdb.bba fpga_interchange/chipdb.bin +``` + +Run nextpnr archcheck: +``` +./nextpnr-fpga_interchange --chipdb fpga_interchange/chipdb.bin --test +``` + +Once nextpnr can complete the place and route task and output the physical +netlist, RapidWright can be used to generate a DCP suitable for bitstream +output and DRC checks. + +``` +${RAPIDWRIGHT_PATH}/scripts/invoke_rapidwright.sh \ + com.xilinx.rapidwright.interchange.PhysicalNetlistToDcp \ + +``` diff --git a/fpga_interchange/arch.cc b/fpga_interchange/arch.cc new file mode 100644 index 00000000..63cf290b --- /dev/null +++ b/fpga_interchange/arch.cc @@ -0,0 +1,578 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Wolf + * Copyright (C) 2018-19 David Shah + * Copyright (C) 2021 Symbiflow Authors + * + * 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 +#include +#include +#include +#include +#include +#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 + +static std::pair split_identifier_name_dot(const std::string &name) +{ + size_t first_dot = name.find('.'); + NPNR_ASSERT(first_dot != std::string::npos); + return std::make_pair(name.substr(0, first_dot), name.substr(first_dot + 1)); +}; + +// ----------------------------------------------------------------------- + +void IdString::initialize_arch(const BaseCtx *ctx) {} + +// ----------------------------------------------------------------------- + +static const ChipInfoPOD *get_chip_info(const RelPtr *ptr) { return ptr->get(); } + +Arch::Arch(ArchArgs args) : args(args) +{ + try { + blob_file.open(args.chipdb); + if (args.chipdb.empty() || !blob_file.is_open()) + log_error("Unable to read chipdb %s\n", args.chipdb.c_str()); + const char *blob = reinterpret_cast(blob_file.data()); + chip_info = get_chip_info(reinterpret_cast *>(blob)); + } catch (...) { + log_error("Unable to read chipdb %s\n", args.chipdb.c_str()); + } + + // Read strings from constids into IdString database, checking that list + // is unique and matches expected constid value. + int id = 1; + for (const auto &constid : *chip_info->constids) { + IdString::initialize_add(this, constid.get(), id++); + } + + tileStatus.resize(chip_info->tiles.size()); + for (int i = 0; i < chip_info->tiles.ssize(); i++) { + tileStatus[i].boundcells.resize(chip_info->tile_types[chip_info->tiles[i].type].bel_data.size()); + } + + // Sanity check cell name ids. + const CellMapPOD &cell_map = *chip_info->cell_map; + int32_t first_cell_id = cell_map.cell_names[0]; + for (int32_t i = 0; i < cell_map.cell_names.ssize(); ++i) { + log_assert(cell_map.cell_names[i] == i + first_cell_id); + } +} + +// ----------------------------------------------------------------------- + +std::string Arch::getChipName() const { return chip_info->name.get(); } + +// ----------------------------------------------------------------------- + +IdString Arch::archArgsToId(ArchArgs args) const { return IdString(); } + +// ----------------------------------------------------------------------- + +void Arch::setup_byname() const +{ + if (tile_by_name.empty()) { + for (int i = 0; i < chip_info->tiles.ssize(); i++) { + tile_by_name[id(chip_info->tiles[i].name.get())] = i; + } + } + + if (site_by_name.empty()) { + for (int i = 0; i < chip_info->tiles.ssize(); i++) { + auto &tile = chip_info->tiles[i]; + auto &tile_type = chip_info->tile_types[tile.type]; + for (int j = 0; j < tile_type.number_sites; j++) { + auto &site = chip_info->sites[tile.sites[j]]; + site_by_name[id(site.name.get())] = std::make_pair(i, j); + } + } + } +} + +BelId Arch::getBelByName(IdStringList name) const +{ + BelId ret; + if (name.ids.size() != 2) { + return BelId(); + } + + setup_byname(); + + int tile, site; + std::tie(tile, site) = site_by_name.at(name.ids[0]); + auto &tile_info = chip_info->tile_types[chip_info->tiles[tile].type]; + IdString belname = name.ids[1]; + for (int i = 0; i < tile_info.bel_data.ssize(); i++) { + if (tile_info.bel_data[i].site == site && tile_info.bel_data[i].name == belname.index) { + ret.tile = tile; + ret.index = i; + break; + } + } + + return ret; +} + +BelRange Arch::getBelsByTile(int x, int y) const +{ + BelRange br; + + br.b.cursor_tile = get_tile_index(x, y); + br.e.cursor_tile = br.b.cursor_tile; + br.b.cursor_index = 0; + br.e.cursor_index = chip_info->tile_types[chip_info->tiles[br.b.cursor_tile].type].bel_data.size(); + br.b.chip = chip_info; + br.e.chip = chip_info; + + if (br.b != br.e) { + ++br.e; + } + return br; +} + +WireId Arch::getBelPinWire(BelId bel, IdString pin) const +{ + NPNR_ASSERT(bel != BelId()); + + int pin_index = get_bel_pin_index(bel, pin); + + auto &bel_data = bel_info(chip_info, bel); + NPNR_ASSERT(pin_index >= 0 && pin_index < bel_data.num_bel_wires); + + const int32_t *wires = bel_data.wires.get(); + int32_t wire_index = wires[pin_index]; + if (wire_index < 0) { + // This BEL pin is not connected. + return WireId(); + } else { + return canonical_wire(chip_info, bel.tile, wire_index); + } +} + +PortType Arch::getBelPinType(BelId bel, IdString pin) const +{ + NPNR_ASSERT(bel != BelId()); + + int pin_index = get_bel_pin_index(bel, pin); + auto &bel_data = bel_info(chip_info, bel); + NPNR_ASSERT(pin_index >= 0 && pin_index < bel_data.num_bel_wires); + const int32_t *types = bel_data.types.get(); + return PortType(types[pin_index]); +} + +// ----------------------------------------------------------------------- + +WireId Arch::getWireByName(IdStringList name) const +{ + WireId ret; + if (name.ids.size() != 2) { + return WireId(); + } + + setup_byname(); + + auto iter = site_by_name.find(name.ids[0]); + if (iter != site_by_name.end()) { + int tile; + int site; + std::tie(tile, site) = iter->second; + auto &tile_info = chip_info->tile_types[chip_info->tiles[tile].type]; + IdString wirename = name.ids[1]; + for (int i = 0; i < tile_info.wire_data.ssize(); i++) { + if (tile_info.wire_data[i].site == site && tile_info.wire_data[i].name == wirename.index) { + ret.tile = tile; + ret.index = i; + break; + } + } + } else { + int tile = tile_by_name.at(name.ids[0]); + auto &tile_info = chip_info->tile_types[chip_info->tiles[tile].type]; + IdString wirename = name.ids[1]; + for (int i = 0; i < tile_info.wire_data.ssize(); i++) { + if (tile_info.wire_data[i].site == -1 && tile_info.wire_data[i].name == wirename.index) { + int32_t node = chip_info->tiles[tile].tile_wire_to_node[i]; + if (node == -1) { + // Not a nodal wire + ret.tile = tile; + ret.index = i; + } else { + // Is a nodal wire, set tile to -1 + ret.tile = -1; + ret.index = node; + } + break; + } + } + } + + return ret; +} + +IdString Arch::getWireType(WireId wire) const { return id(""); } +std::vector> Arch::getWireAttrs(WireId wire) const { return {}; } + +// ----------------------------------------------------------------------- + +PipId Arch::getPipByName(IdStringList name) const +{ + // PIP name structure: + // Tile PIP: /. + // Site PIP: // + // Site pin: / + // Psuedo site PIP: /. + + setup_byname(); + + if (name.ids.size() == 3) { + // This is a Site PIP. + IdString site_name = name.ids[0]; + IdString belname = name.ids[1]; + IdString pinname = name.ids[2]; + + int tile; + int site; + std::tie(tile, site) = site_by_name.at(site_name); + auto &tile_info = chip_info->tile_types[chip_info->tiles[tile].type]; + + std::array ids{name.ids[0], belname}; + BelId bel = getBelByName(IdStringList(ids)); + NPNR_ASSERT(bel != BelId()); + + int pin_index = get_bel_pin_index(bel, pinname); + NPNR_ASSERT(pin_index >= 0); + + for (int i = 0; i < tile_info.pip_data.ssize(); i++) { + if (tile_info.pip_data[i].site == site && tile_info.pip_data[i].bel == bel.index && + tile_info.pip_data[i].extra_data == pin_index) { + + PipId ret; + ret.tile = tile; + ret.index = i; + return ret; + } + } + } else { + auto iter = site_by_name.find(name.ids[0]); + if (iter != site_by_name.end()) { + // This is either a site pin or a psuedo site pip. + // psuedo site pips are /. + // site pins are / + int tile; + int site; + std::tie(tile, site) = iter->second; + auto &tile_info = chip_info->tile_types[chip_info->tiles[tile].type]; + + std::string pip_second = name.ids[1].str(this); + auto split = pip_second.find('.'); + if (split == std::string::npos) { + // This is a site pin! + BelId bel = getBelByName(name); + NPNR_ASSERT(bel != BelId()); + + for (int i = 0; i < tile_info.pip_data.ssize(); i++) { + if (tile_info.pip_data[i].site == site && tile_info.pip_data[i].bel == bel.index) { + + PipId ret; + ret.tile = tile; + ret.index = i; + return ret; + } + } + } else { + // This is a psuedo site pip! + IdString src_site_wire = id(pip_second.substr(0, split)); + IdString dst_site_wire = id(pip_second.substr(split + 1)); + int32_t src_index = -1; + int32_t dst_index = -1; + + for (int i = 0; i < tile_info.wire_data.ssize(); i++) { + if (tile_info.wire_data[i].site == site && tile_info.wire_data[i].name == src_site_wire.index) { + src_index = i; + if (dst_index != -1) { + break; + } + } + if (tile_info.wire_data[i].site == site && tile_info.wire_data[i].name == dst_site_wire.index) { + dst_index = i; + if (src_index != -1) { + break; + } + } + } + + NPNR_ASSERT(src_index != -1); + NPNR_ASSERT(dst_index != -1); + + for (int i = 0; i < tile_info.pip_data.ssize(); i++) { + if (tile_info.pip_data[i].site == site && tile_info.pip_data[i].src_index == src_index && + tile_info.pip_data[i].dst_index == dst_index) { + + PipId ret; + ret.tile = tile; + ret.index = i; + return ret; + } + } + } + } else { + int tile = tile_by_name.at(name.ids[0]); + auto &tile_info = chip_info->tile_types[chip_info->tiles[tile].type]; + + std::string pip_second = name.ids[1].str(this); + auto spn = split_identifier_name_dot(pip_second); + auto src_wire_name = id(spn.first); + auto dst_wire_name = id(spn.second); + + int32_t src_index = -1; + int32_t dst_index = -1; + for (int i = 0; i < tile_info.wire_data.ssize(); i++) { + if (tile_info.wire_data[i].site == -1 && tile_info.wire_data[i].name == src_wire_name.index) { + src_index = i; + if (dst_index != -1) { + break; + } + } + if (tile_info.wire_data[i].site == -1 && tile_info.wire_data[i].name == dst_wire_name.index) { + dst_index = i; + if (src_index != -1) { + break; + } + } + } + + NPNR_ASSERT(src_index != -1); + NPNR_ASSERT(dst_index != -1); + + for (int i = 0; i < tile_info.pip_data.ssize(); i++) { + if (tile_info.pip_data[i].src_index == src_index && tile_info.pip_data[i].dst_index == dst_index) { + + PipId ret; + ret.tile = tile; + ret.index = i; + return ret; + } + } + } + } + + return PipId(); +} + +IdStringList Arch::getPipName(PipId pip) const +{ + // PIP name structure: + // Tile PIP: /. + // Psuedo site PIP: /. + // Site PIP: // + // Site pin: / + NPNR_ASSERT(pip != PipId()); + auto &tile = chip_info->tiles[pip.tile]; + auto &tile_type = loc_info(chip_info, pip); + auto &pip_info = tile_type.pip_data[pip.index]; + if (pip_info.site != -1) { + // This is either a site pin or a site pip. + auto &site = chip_info->sites[tile.sites[pip_info.site]]; + auto &bel = tile_type.bel_data[pip_info.bel]; + IdString bel_name(bel.name); + if (bel.category == BEL_CATEGORY_LOGIC) { + // This is a psuedo pip + IdString src_wire_name = IdString(tile_type.wire_data[pip_info.src_index].name); + IdString dst_wire_name = IdString(tile_type.wire_data[pip_info.dst_index].name); + IdString pip = id(src_wire_name.str(this) + "." + dst_wire_name.str(this)); + std::array ids{id(site.name.get()), pip}; + return IdStringList(ids); + + } else if (bel.category == BEL_CATEGORY_ROUTING) { + // This is a site pip. + IdString pin_name(bel.ports[pip_info.extra_data]); + std::array ids{id(site.name.get()), bel_name, pin_name}; + return IdStringList(ids); + } else { + NPNR_ASSERT(bel.category == BEL_CATEGORY_SITE_PORT); + // This is a site pin, just the name of the BEL is a unique identifier. + std::array ids{id(site.name.get()), bel_name}; + return IdStringList(ids); + } + } else { + // This is a tile pip. + IdString src_wire_name = IdString(tile_type.wire_data[pip_info.src_index].name); + IdString dst_wire_name = IdString(tile_type.wire_data[pip_info.dst_index].name); + IdString pip = id(src_wire_name.str(this) + "." + dst_wire_name.str(this)); + std::array ids{id(std::string(tile.name.get())), pip}; + return IdStringList(ids); + } +} + +IdString Arch::getPipType(PipId pip) const { return id("PIP"); } + +std::vector> Arch::getPipAttrs(PipId pip) const { return {}; } + +// ----------------------------------------------------------------------- + +BelId Arch::getBelByLocation(Loc loc) const +{ + BelId bi; + if (loc.x >= chip_info->width || loc.y >= chip_info->height) + return BelId(); + bi.tile = get_tile_index(loc); + auto &li = loc_info(chip_info, bi); + + if (loc.z >= li.bel_data.ssize()) { + return BelId(); + } else { + bi.index = loc.z; + return bi; + } +} + +std::vector> Arch::getBelAttrs(BelId bel) const { return {}; } + +// ----------------------------------------------------------------------- + +ArcBounds Arch::getRouteBoundingBox(WireId src, WireId dst) const +{ + int dst_tile = dst.tile == -1 ? chip_info->nodes[dst.index].tile_wires[0].tile : dst.tile; + int src_tile = src.tile == -1 ? chip_info->nodes[src.index].tile_wires[0].tile : src.tile; + + int x0, x1, y0, y1; + x0 = src_tile % chip_info->width; + x1 = x0; + y0 = src_tile / chip_info->width; + y1 = y0; + auto expand = [&](int x, int y) { + x0 = std::min(x0, x); + x1 = std::max(x1, x); + y0 = std::min(y0, y); + y1 = std::max(y1, y); + }; + + expand(dst_tile % chip_info->width, dst_tile / chip_info->width); + + if (source_locs.count(src)) + expand(source_locs.at(src).x, source_locs.at(src).y); + + if (sink_locs.count(dst)) { + expand(sink_locs.at(dst).x, sink_locs.at(dst).y); + } + + return {x0, y0, x1, y1}; +} + +delay_t Arch::getWireRipupDelayPenalty(WireId wire) const { return getRipupDelayPenalty(); } + +bool Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const { return false; } + +// ----------------------------------------------------------------------- + +bool Arch::pack() +{ + // FIXME: Implement this + return false; +} + +bool Arch::place() +{ + // FIXME: Implement this + return false; +} + +bool Arch::route() +{ + // FIXME: Implement this + return false; +} + +// ----------------------------------------------------------------------- + +std::vector Arch::getDecalGraphics(DecalId decal) const { return {}; } + +DecalXY Arch::getBelDecal(BelId bel) const +{ + DecalXY decalxy; + return decalxy; +} + +DecalXY Arch::getWireDecal(WireId wire) const +{ + DecalXY decalxy; + return decalxy; +} + +DecalXY Arch::getPipDecal(PipId pip) const { return {}; }; + +DecalXY Arch::getGroupDecal(GroupId pip) const { return {}; }; + +// ----------------------------------------------------------------------- + +delay_t Arch::estimateDelay(WireId src, WireId dst, bool debug) const +{ + // FIXME: Implement something to push the A* router in the right direction. + return 0; +} + +delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const +{ + // FIXME: Implement when adding timing-driven place and route. + return 0; +} + +bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const +{ + // FIXME: Implement when adding timing-driven place and route. + return false; +} + +TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const +{ + // FIXME: Implement when adding timing-driven place and route. + return TMG_IGNORE; +} + +TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port, int index) const +{ + // FIXME: Implement when adding timing-driven place and route. + TimingClockingInfo info; + return info; +} + +#ifdef WITH_HEAP +const std::string Arch::defaultPlacer = "heap"; +#else +const std::string Arch::defaultPlacer = "sa"; +#endif + +const std::vector Arch::availablePlacers = {"sa", +#ifdef WITH_HEAP + "heap" +#endif +}; + +const std::string Arch::defaultRouter = "router2"; +const std::vector Arch::availableRouters = {"router1", "router2"}; + +NEXTPNR_NAMESPACE_END diff --git a/fpga_interchange/arch.h b/fpga_interchange/arch.h new file mode 100644 index 00000000..12c30c3d --- /dev/null +++ b/fpga_interchange/arch.h @@ -0,0 +1,1277 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Wolf + * Copyright (C) 2018-19 David Shah + * Copyright (C) 2021 Symbiflow Authors + * + * + * 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 + +#include + +NEXTPNR_NAMESPACE_BEGIN + +/**** Everything in this section must be kept in sync with chipdb.py ****/ + +#include "relptr.h" + +// Flattened site indexing. +// +// To enable flat BelId.z spaces, every tile and sites within that tile are +// flattened. +// +// This has implications on BelId's, WireId's and PipId's. +// The flattened site space works as follows: +// - Objects that belong to the tile are first. BELs are always part of Sites, +// so no BEL objects are in this category. +// - All site alternative modes are exposed as a "full" site. +// - Each site appends it's BEL's, wires (site wires) and PIP's. +// - Sites add two types of pips. Sites will add pip data first for site +// pips, and then for site pin edges. +// 1. The first type is site pips, which connect site wires to other site +// wires. +// 2. The second type is site pin edges, which connect site wires to tile +// wires (or vise-versa). + +NPNR_PACKED_STRUCT(struct BelInfoPOD { + int32_t name; // bel name (in site) constid + int32_t type; // Type name constid + int32_t bel_bucket; // BEL bucket constid. + + int32_t num_bel_wires; + RelPtr ports; // port name constid + RelPtr types; // port type (IN/OUT/BIDIR) + RelPtr wires; // connected wire index in tile, or -1 if NA + + int16_t site; + int16_t site_variant; // some sites have alternative types + int16_t category; + int16_t padding; + + RelPtr valid_cells; // Bool array, length of number_cells. +}); + +enum BELCategory +{ + // BEL is a logic element + BEL_CATEGORY_LOGIC = 0, + // BEL is a site routing mux + BEL_CATEGORY_ROUTING = 1, + // BEL is a site port, e.g. boundry between site and routing graph. + BEL_CATEGORY_SITE_PORT = 2 +}; + +NPNR_PACKED_STRUCT(struct BelPortPOD { + int32_t bel_index; + int32_t port; +}); + +NPNR_PACKED_STRUCT(struct TileWireInfoPOD { + int32_t name; // wire name constid + + // Pip index inside tile + RelSlice pips_uphill; + + // Pip index inside tile + RelSlice pips_downhill; + + // Bel index inside tile + RelSlice bel_pins; + + int16_t site; // site index in tile + int16_t site_variant; // site variant index in tile +}); + +NPNR_PACKED_STRUCT(struct PipInfoPOD { + int32_t src_index, dst_index; + int16_t site; // site index in tile + int16_t site_variant; // site variant index in tile + int16_t bel; // BEL this pip belongs to if site pip. + int16_t extra_data; +}); + +NPNR_PACKED_STRUCT(struct TileTypeInfoPOD { + int32_t name; // Tile type constid + + int32_t number_sites; + + RelSlice bel_data; + + RelSlice wire_data; + + RelSlice pip_data; +}); + +NPNR_PACKED_STRUCT(struct SiteInstInfoPOD { + RelPtr name; + + // Which site type is this site instance? + // constid + int32_t site_type; +}); + +NPNR_PACKED_STRUCT(struct TileInstInfoPOD { + // Name of this tile. + RelPtr name; + + // Index into root.tile_types. + int32_t type; + + // This array is root.tile_types[type].number_sites long. + // Index into root.sites + RelPtr sites; + + // Number of tile wires; excluding any site-internal wires + // which come after general wires and are not stored here + // as they will never be nodal + // -1 if a tile-local wire; node index if nodal wire + RelSlice tile_wire_to_node; +}); + +NPNR_PACKED_STRUCT(struct TileWireRefPOD { + int32_t tile; + int32_t index; +}); + +NPNR_PACKED_STRUCT(struct NodeInfoPOD { RelSlice tile_wires; }); + +NPNR_PACKED_STRUCT(struct CellMapPOD { + // Cell names supported in this arch. + RelSlice cell_names; // constids + RelSlice cell_bel_buckets; // constids +}); + +NPNR_PACKED_STRUCT(struct ChipInfoPOD { + RelPtr name; + RelPtr generator; + + int32_t version; + int32_t width, height; + + RelSlice tile_types; + RelSlice sites; + RelSlice tiles; + RelSlice nodes; + + // BEL bucket constids. + RelSlice bel_buckets; + + RelPtr cell_map; + + // Constid string data. + RelPtr>> constids; +}); + +/************************ End of chipdb section. ************************/ + +inline const TileTypeInfoPOD &tile_info(const ChipInfoPOD *chip_info, int32_t tile) +{ + return chip_info->tile_types[chip_info->tiles[tile].type]; +} + +template const TileTypeInfoPOD &loc_info(const ChipInfoPOD *chip_info, Id &id) +{ + return chip_info->tile_types[chip_info->tiles[id.tile].type]; +} + +inline const BelInfoPOD &bel_info(const ChipInfoPOD *chip_info, BelId bel) +{ + NPNR_ASSERT(bel != BelId()); + return loc_info(chip_info, bel).bel_data[bel.index]; +} + +struct BelIterator +{ + const ChipInfoPOD *chip; + int cursor_index; + int cursor_tile; + + BelIterator operator++() + { + cursor_index++; + while (cursor_tile < chip->tiles.ssize() && cursor_index >= tile_info(chip, cursor_tile).bel_data.ssize()) { + 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 FilteredBelIterator +{ + std::function filter; + BelIterator b, e; + + FilteredBelIterator operator++() + { + ++b; + while (b != e) { + if (filter(*b)) { + break; + } + + ++b; + } + return *this; + } + + bool operator!=(const FilteredBelIterator &other) const + { + NPNR_ASSERT(e == other.e); + return b != other.b; + } + + bool operator==(const FilteredBelIterator &other) const + { + NPNR_ASSERT(e == other.e); + return b == other.b; + } + + BelId operator*() const + { + BelId bel = *b; + NPNR_ASSERT(filter(bel)); + return bel; + } +}; + +struct FilteredBelRange +{ + FilteredBelRange(BelIterator bel_b, BelIterator bel_e, std::function filter) + { + b.filter = filter; + b.b = bel_b; + b.e = bel_e; + + if (b.b != b.e && !filter(*b.b)) { + ++b; + } + + e.b = bel_e; + e.e = bel_e; + + if (b != e) { + NPNR_ASSERT(filter(*b.b)); + } + } + + FilteredBelIterator b, e; + FilteredBelIterator begin() const { return b; } + FilteredBelIterator end() const { return e; } +}; + +// ----------------------------------------------------------------------- + +// Iterate over TileWires for a wire (will be more than one if nodal) +struct TileWireIterator +{ + const ChipInfoPOD *chip; + WireId baseWire; + int cursor = -1; + + void operator++() { cursor++; } + + bool operator==(const TileWireIterator &other) const { return cursor == other.cursor; } + bool operator!=(const TileWireIterator &other) const { return cursor != other.cursor; } + + // Returns a *denormalised* identifier always pointing to a tile wire rather than a node + WireId operator*() const + { + if (baseWire.tile == -1) { + WireId tw; + const auto &node_wire = chip->nodes[baseWire.index].tile_wires[cursor]; + tw.tile = node_wire.tile; + tw.index = node_wire.index; + return tw; + } else { + return baseWire; + } + } +}; + +struct TileWireRange +{ + TileWireIterator b, e; + TileWireIterator begin() const { return b; } + TileWireIterator end() const { return e; } +}; + +inline WireId canonical_wire(const ChipInfoPOD *chip_info, int32_t tile, int32_t wire) +{ + WireId id; + + if (wire >= chip_info->tiles[tile].tile_wire_to_node.ssize()) { + // Cannot be a nodal wire + id.tile = tile; + id.index = wire; + } else { + int32_t node = chip_info->tiles[tile].tile_wire_to_node[wire]; + if (node == -1) { + // Not a nodal wire + id.tile = tile; + id.index = wire; + } else { + // Is a nodal wire, set tile to -1 + id.tile = -1; + id.index = node; + } + } + + return id; +} + +// ----------------------------------------------------------------------- + +struct WireIterator +{ + const ChipInfoPOD *chip; + int cursor_index = 0; + int cursor_tile = -1; + + WireIterator operator++() + { + // Iterate over nodes first, then tile wires that aren't nodes + do { + cursor_index++; + if (cursor_tile == -1 && cursor_index >= chip->nodes.ssize()) { + cursor_tile = 0; + cursor_index = 0; + } + while (cursor_tile != -1 && cursor_tile < chip->tiles.ssize() && + cursor_index >= chip->tile_types[chip->tiles[cursor_tile].type].wire_data.ssize()) { + cursor_index = 0; + cursor_tile++; + } + + } while ((cursor_tile != -1 && cursor_tile < chip->tiles.ssize() && + cursor_index < chip->tiles[cursor_tile].tile_wire_to_node.ssize() && + chip->tiles[cursor_tile].tile_wire_to_node[cursor_index] != -1)); + + 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; } +}; + +// ----------------------------------------------------------------------- +struct AllPipIterator +{ + const ChipInfoPOD *chip; + int cursor_index; + int cursor_tile; + + AllPipIterator operator++() + { + cursor_index++; + while (cursor_tile < chip->tiles.ssize() && + cursor_index >= chip->tile_types[chip->tiles[cursor_tile].type].pip_data.ssize()) { + 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 UphillPipIterator +{ + const ChipInfoPOD *chip; + TileWireIterator twi, twi_end; + int cursor = -1; + + void operator++() + { + cursor++; + while (true) { + if (!(twi != twi_end)) + break; + WireId w = *twi; + auto &tile = chip->tile_types[chip->tiles[w.tile].type]; + if (cursor < tile.wire_data[w.index].pips_uphill.ssize()) + break; + ++twi; + cursor = 0; + } + } + bool operator!=(const UphillPipIterator &other) const { return twi != other.twi || cursor != other.cursor; } + + PipId operator*() const + { + PipId ret; + WireId w = *twi; + ret.tile = w.tile; + ret.index = chip->tile_types[chip->tiles[w.tile].type].wire_data[w.index].pips_uphill[cursor]; + return ret; + } +}; + +struct UphillPipRange +{ + UphillPipIterator b, e; + UphillPipIterator begin() const { return b; } + UphillPipIterator end() const { return e; } +}; + +struct DownhillPipIterator +{ + const ChipInfoPOD *chip; + TileWireIterator twi, twi_end; + int cursor = -1; + + void operator++() + { + cursor++; + while (true) { + if (!(twi != twi_end)) + break; + WireId w = *twi; + auto &tile = chip->tile_types[chip->tiles[w.tile].type]; + if (cursor < tile.wire_data[w.index].pips_downhill.ssize()) + break; + ++twi; + cursor = 0; + } + } + bool operator!=(const DownhillPipIterator &other) const { return twi != other.twi || cursor != other.cursor; } + + PipId operator*() const + { + PipId ret; + WireId w = *twi; + ret.tile = w.tile; + ret.index = chip->tile_types[chip->tiles[w.tile].type].wire_data[w.index].pips_downhill[cursor]; + return ret; + } +}; + +struct DownhillPipRange +{ + DownhillPipIterator b, e; + DownhillPipIterator begin() const { return b; } + DownhillPipIterator end() const { return e; } +}; + +struct BelPinIterator +{ + const ChipInfoPOD *chip; + TileWireIterator twi, twi_end; + int cursor = -1; + + void operator++() + { + cursor++; + + while (twi != twi_end) { + WireId w = *twi; + auto &tile = tile_info(chip, w.tile); + if (cursor < tile.wire_data[w.index].bel_pins.ssize()) + break; + + ++twi; + cursor = 0; + } + } + bool operator!=(const BelPinIterator &other) const { return twi != other.twi || cursor != other.cursor; } + + BelPin operator*() const + { + BelPin ret; + WireId w = *twi; + ret.bel.tile = w.tile; + ret.bel.index = tile_info(chip, w.tile).wire_data[w.index].bel_pins[cursor].bel_index; + ret.pin.index = tile_info(chip, w.tile).wire_data[w.index].bel_pins[cursor].port; + return ret; + } +}; + +struct BelPinRange +{ + BelPinIterator b, e; + BelPinIterator begin() const { return b; } + BelPinIterator end() const { return e; } +}; + +struct IdStringIterator +{ + const int32_t *cursor; + + void operator++() { cursor += 1; } + + bool operator!=(const IdStringIterator &other) const { return cursor != other.cursor; } + + bool operator==(const IdStringIterator &other) const { return cursor == other.cursor; } + + IdString operator*() const { return IdString(*cursor); } +}; + +struct IdStringRange +{ + IdStringIterator b, e; + IdStringIterator begin() const { return b; } + IdStringIterator end() const { return e; } +}; + +struct BelBucketIterator +{ + IdStringIterator cursor; + + void operator++() { ++cursor; } + + bool operator!=(const BelBucketIterator &other) const { return cursor != other.cursor; } + + bool operator==(const BelBucketIterator &other) const { return cursor == other.cursor; } + + BelBucketId operator*() const + { + BelBucketId bucket; + bucket.name = IdString(*cursor); + return bucket; + } +}; + +struct BelBucketRange +{ + BelBucketIterator b, e; + BelBucketIterator begin() const { return b; } + BelBucketIterator end() const { return e; } +}; + +struct ArchArgs +{ + std::string chipdb; +}; + +struct Arch : BaseCtx +{ + boost::iostreams::mapped_file_source blob_file; + const ChipInfoPOD *chip_info; + + mutable std::unordered_map tile_by_name; + mutable std::unordered_map> site_by_name; + + std::unordered_map wire_to_net; + std::unordered_map pip_to_net; + std::unordered_map> driving_pip_loc; + std::unordered_map reserved_wires; + + struct TileStatus + { + std::vector boundcells; + }; + + std::vector tileStatus; + + ArchArgs args; + Arch(ArchArgs args); + + std::string getChipName() const; + + IdString archId() const { return id(chip_info->name.get()); } + ArchArgs archArgs() const { return args; } + IdString archArgsToId(ArchArgs args) const; + + // ------------------------------------------------- + + uint32_t get_tile_index(int x, int y) const { return (y * chip_info->width + x); } + uint32_t get_tile_index(Loc loc) const { return get_tile_index(loc.x, loc.y); } + template + void get_tile_x_y(TileIndex tile_index, CoordIndex *x, CoordIndex *y) const + { + *x = tile_index % chip_info->width; + *y = tile_index / chip_info->width; + } + + template void get_tile_loc(TileIndex tile_index, Loc *loc) const + { + get_tile_x_y(tile_index, &loc->x, &loc->y); + } + + int getGridDimX() const { return chip_info->width; } + int getGridDimY() const { return chip_info->height; } + int getTileBelDimZ(int x, int y) const + { + return chip_info->tile_types[chip_info->tiles[get_tile_index(x, y)].type].bel_data.size(); + } + int getTilePipDimZ(int x, int y) const + { + return chip_info->tile_types[chip_info->tiles[get_tile_index(x, y)].type].number_sites; + } + char getNameDelimiter() const { return '/'; } + + // ------------------------------------------------- + + void setup_byname() const; + + BelId getBelByName(IdStringList name) const; + + IdStringList getBelName(BelId bel) const + { + NPNR_ASSERT(bel != BelId()); + int site_index = bel_info(chip_info, bel).site; + NPNR_ASSERT(site_index >= 0); + const SiteInstInfoPOD &site = chip_info->sites[chip_info->tiles[bel.tile].sites[site_index]]; + std::array ids{id(site.name.get()), IdString(bel_info(chip_info, bel).name)}; + return IdStringList(ids); + } + + uint32_t getBelChecksum(BelId bel) const { return 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); + } + + void unbindBel(BelId bel) + { + NPNR_ASSERT(bel != BelId()); + NPNR_ASSERT(tileStatus[bel.tile].boundcells[bel.index] != 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 { 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; //-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; + return range; + } + + Loc getBelLocation(BelId bel) const + { + NPNR_ASSERT(bel != BelId()); + Loc loc; + get_tile_x_y(bel.tile, &loc.x, &loc.y); + loc.z = bel.index; + return loc; + } + + BelId getBelByLocation(Loc loc) const; + BelRange getBelsByTile(int x, int y) const; + + bool getBelGlobalBuf(BelId bel) const + { + // FIXME: This probably needs to be fixed! + return false; + } + + bool getBelHidden(BelId bel) const { return bel_info(chip_info, bel).category != BEL_CATEGORY_LOGIC; } + + IdString getBelType(BelId bel) const + { + NPNR_ASSERT(bel != BelId()); + return IdString(bel_info(chip_info, bel).type); + } + + std::vector> getBelAttrs(BelId bel) const; + + int get_bel_pin_index(BelId bel, IdString pin) const + { + NPNR_ASSERT(bel != BelId()); + int num_bel_wires = bel_info(chip_info, bel).num_bel_wires; + const int32_t *ports = bel_info(chip_info, bel).ports.get(); + for (int i = 0; i < num_bel_wires; i++) { + if (ports[i] == pin.index) { + return i; + } + } + + return -1; + } + + WireId getBelPinWire(BelId bel, IdString pin) const; + PortType getBelPinType(BelId bel, IdString pin) const; + + IdStringRange getBelPins(BelId bel) const + { + NPNR_ASSERT(bel != BelId()); + + int num_bel_wires = bel_info(chip_info, bel).num_bel_wires; + const int32_t *ports = bel_info(chip_info, bel).ports.get(); + + IdStringRange str_range; + str_range.b.cursor = &ports[0]; + str_range.e.cursor = &ports[num_bel_wires - 1]; + + return str_range; + } + + // ------------------------------------------------- + + WireId getWireByName(IdStringList name) const; + + const TileWireInfoPOD &wire_info(WireId wire) const + { + if (wire.tile == -1) { + const TileWireRefPOD &wr = chip_info->nodes[wire.index].tile_wires[0]; + return chip_info->tile_types[chip_info->tiles[wr.tile].type].wire_data[wr.index]; + } else { + return loc_info(chip_info, wire).wire_data[wire.index]; + } + } + + IdStringList getWireName(WireId wire) const + { + NPNR_ASSERT(wire != WireId()); + if (wire.tile != -1) { + const auto &tile_type = loc_info(chip_info, wire); + if (tile_type.wire_data[wire.index].site != -1) { + int site_index = tile_type.wire_data[wire.index].site; + const SiteInstInfoPOD &site = chip_info->sites[chip_info->tiles[wire.tile].sites[site_index]]; + std::array ids{id(site.name.get()), IdString(tile_type.wire_data[wire.index].name)}; + return IdStringList(ids); + } + } + + int32_t tile = wire.tile == -1 ? chip_info->nodes[wire.index].tile_wires[0].tile : wire.tile; + IdString tile_name = id(chip_info->tiles[tile].name.get()); + std::array ids{tile_name, IdString(wire_info(wire).name)}; + return IdStringList(ids); + } + + IdString getWireType(WireId wire) const; + std::vector> getWireAttrs(WireId wire) const; + + uint32_t getWireChecksum(WireId wire) const { return 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; + } + + WireId getConflictingWireWire(WireId wire) const { return wire; } + + 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; + } + + DelayInfo getWireDelay(WireId wire) const + { + DelayInfo delay; + delay.delay = 0; + return delay; + } + + TileWireRange get_tile_wire_range(WireId wire) const + { + TileWireRange range; + range.b.chip = chip_info; + range.b.baseWire = wire; + range.b.cursor = -1; + ++range.b; + + range.e.chip = chip_info; + range.e.baseWire = wire; + if (wire.tile == -1) { + range.e.cursor = chip_info->nodes[wire.index].tile_wires.size(); + } else { + range.e.cursor = 1; + } + return range; + } + + BelPinRange getWireBelPins(WireId wire) const + { + BelPinRange range; + NPNR_ASSERT(wire != WireId()); + + TileWireRange twr = get_tile_wire_range(wire); + range.b.chip = chip_info; + range.b.twi = twr.b; + range.b.twi_end = twr.e; + range.b.cursor = -1; + ++range.b; + + range.e.chip = chip_info; + range.e.twi = twr.e; + range.e.twi_end = twr.e; + range.e.cursor = 0; + return range; + } + + WireRange getWires() const + { + WireRange range; + range.b.chip = chip_info; + range.b.cursor_tile = -1; + range.b.cursor_index = 0; + range.e.chip = chip_info; + range.e.cursor_tile = chip_info->tiles.size(); + range.e.cursor_index = 0; + return range; + } + + // ------------------------------------------------- + + PipId getPipByName(IdStringList name) const; + IdStringList getPipName(PipId pip) const; + IdString getPipType(PipId pip) const; + std::vector> getPipAttrs(PipId pip) const; + + void bindPip(PipId pip, NetInfo *net, PlaceStrength strength) + { + NPNR_ASSERT(pip != PipId()); + NPNR_ASSERT(pip_to_net[pip] == nullptr); + + WireId dst = getPipDstWire(pip); + NPNR_ASSERT(wire_to_net[dst] == nullptr || wire_to_net[dst] == net); + + pip_to_net[pip] = net; + std::pair loc; + get_tile_x_y(pip.tile, &loc.first, &loc.second); + driving_pip_loc[dst] = loc; + + 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 = getPipDstWire(pip); + 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 + { + 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; //-1 and then ++ deals with the case of no wries in the first tile + range.e.cursor_tile = chip_info->width * chip_info->height; + range.e.cursor_index = 0; + range.e.chip = chip_info; + return range; + } + + Loc getPipLocation(PipId pip) const + { + Loc loc; + get_tile_loc(pip.tile, &loc); + loc.z = 0; + return loc; + } + + uint32_t getPipChecksum(PipId pip) const { return pip.index; } + + WireId getPipSrcWire(PipId pip) const + { + return canonical_wire(chip_info, pip.tile, loc_info(chip_info, pip).pip_data[pip.index].src_index); + } + + WireId getPipDstWire(PipId pip) const + { + return canonical_wire(chip_info, pip.tile, loc_info(chip_info, pip).pip_data[pip.index].dst_index); + } + + DelayInfo getPipDelay(PipId pip) const { return DelayInfo(); } + + DownhillPipRange getPipsDownhill(WireId wire) const + { + DownhillPipRange range; + NPNR_ASSERT(wire != WireId()); + TileWireRange twr = get_tile_wire_range(wire); + range.b.chip = chip_info; + range.b.twi = twr.b; + range.b.twi_end = twr.e; + range.b.cursor = -1; + ++range.b; + range.e.chip = chip_info; + range.e.twi = twr.e; + range.e.twi_end = twr.e; + range.e.cursor = 0; + return range; + } + + UphillPipRange getPipsUphill(WireId wire) const + { + UphillPipRange range; + NPNR_ASSERT(wire != WireId()); + TileWireRange twr = get_tile_wire_range(wire); + range.b.chip = chip_info; + range.b.twi = twr.b; + range.b.twi_end = twr.e; + range.b.cursor = -1; + ++range.b; + range.e.chip = chip_info; + range.e.twi = twr.e; + range.e.twi_end = twr.e; + range.e.cursor = 0; + return range; + } + + // ------------------------------------------------- + + // FIXME: Use groups to get access to sites. + GroupId getGroupByName(IdStringList name) const { return GroupId(); } + IdStringList getGroupName(GroupId group) const { return IdStringList(); } + std::vector getGroups() const { return {}; } + std::vector getGroupBels(GroupId group) const { return {}; } + std::vector getGroupWires(GroupId group) const { return {}; } + std::vector getGroupPips(GroupId group) const { return {}; } + std::vector getGroupGroups(GroupId group) const { return {}; } + + // ------------------------------------------------- + delay_t estimateDelay(WireId src, WireId dst, bool debug = false) const; + delay_t predictDelay(const NetInfo *net_info, const PortRef &sink) const; + ArcBounds getRouteBoundingBox(WireId src, WireId dst) 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.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; + + // ------------------------------------------------- + + bool pack(); + bool place(); + bool route(); + // ------------------------------------------------- + + std::vector getDecalGraphics(DecalId decal) const; + + DecalXY getBelDecal(BelId bel) const; + DecalXY getWireDecal(WireId wire) const; + DecalXY getPipDecal(PipId pip) const; + DecalXY getGroupDecal(GroupId group) const; + + // ------------------------------------------------- + + // 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; + + // ------------------------------------------------- + + const BelBucketRange getBelBuckets() const + { + BelBucketRange bel_bucket_range; + bel_bucket_range.b.cursor.cursor = chip_info->bel_buckets.begin(); + bel_bucket_range.e.cursor.cursor = chip_info->bel_buckets.end(); + return bel_bucket_range; + } + + BelBucketId getBelBucketForBel(BelId bel) const + { + BelBucketId bel_bucket; + bel_bucket.name = IdString(bel_info(chip_info, bel).bel_bucket); + return bel_bucket; + } + + const IdStringRange getCellTypes() const + { + const CellMapPOD &cell_map = *chip_info->cell_map; + + IdStringRange id_range; + id_range.b.cursor = cell_map.cell_names.begin(); + id_range.e.cursor = cell_map.cell_names.end(); + + return id_range; + } + + IdString getBelBucketName(BelBucketId bucket) const { return bucket.name; } + + BelBucketId getBelBucketByName(IdString name) const + { + for (BelBucketId bel_bucket : getBelBuckets()) { + if (bel_bucket.name == name) { + return bel_bucket; + } + } + + NPNR_ASSERT_FALSE("Failed to find BEL bucket for name."); + return BelBucketId(); + } + + size_t getCellTypeIndex(IdString cell_type) const + { + const CellMapPOD &cell_map = *chip_info->cell_map; + int cell_offset = cell_type.index - cell_map.cell_names[0]; + NPNR_ASSERT(cell_offset >= 0 && cell_offset < cell_map.cell_names.ssize()); + NPNR_ASSERT(cell_map.cell_names[cell_offset] == cell_type.index); + + return cell_offset; + } + + BelBucketId getBelBucketForCellType(IdString cell_type) const + { + BelBucketId bucket; + const CellMapPOD &cell_map = *chip_info->cell_map; + bucket.name = IdString(cell_map.cell_bel_buckets[getCellTypeIndex(cell_type)]); + return bucket; + } + + FilteredBelRange getBelsInBucket(BelBucketId bucket) const + { + BelRange range = getBels(); + FilteredBelRange filtered_range(range.begin(), range.end(), + [this, bucket](BelId bel) { return getBelBucketForBel(bel) == bucket; }); + + return filtered_range; + } + + bool isValidBelForCellType(IdString cell_type, BelId bel) const + { + return bel_info(chip_info, bel).valid_cells[getCellTypeIndex(cell_type)]; + } + + // 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 + { + NPNR_ASSERT(isValidBelForCellType(cell->type, bel)); + + // FIXME: Implement this + return true; + } + + // Return true whether all Bels at a given location are valid + bool isBelLocationValid(BelId bel) const + { + // FIXME: Implement this + return true; + } + + IdString getBelTileType(BelId bel) const { return IdString(loc_info(chip_info, bel).name); } + + std::unordered_map sink_locs, source_locs; + // ------------------------------------------------- + void assignArchInfo() {} + + // ------------------------------------------------- + + static const std::string defaultPlacer; + static const std::vector availablePlacers; + + static const std::string defaultRouter; + static const std::vector availableRouters; + + // ------------------------------------------------- + void write_physical_netlist(const std::string &filename) const {} +}; + +NEXTPNR_NAMESPACE_END diff --git a/fpga_interchange/arch_pybindings.cc b/fpga_interchange/arch_pybindings.cc new file mode 100644 index 00000000..416a015a --- /dev/null +++ b/fpga_interchange/arch_pybindings.cc @@ -0,0 +1,77 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2020 David Shah + * Copyright (C) 2021 Symbiflow Authors + * + * 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_(m, "ArchArgs").def_readwrite("chipdb", &ArchArgs::chipdb); + + py::class_(m, "BelId").def_readwrite("index", &BelId::index); + + py::class_(m, "WireId").def_readwrite("index", &WireId::index); + + py::class_(m, "PipId").def_readwrite("index", &PipId::index); + + auto arch_cls = py::class_(m, "Arch").def(py::init()); + auto ctx_cls = py::class_(m, "Context") + .def("checksum", &Context::checksum) + .def("pack", &Context::pack) + .def("place", &Context::place) + .def("route", &Context::route); + + fn_wrapper_2a, + addr_and_unwrap, conv_from_str>::def_wrap(ctx_cls, "isValidBelForCell"); + + typedef std::unordered_map> CellMap; + typedef std::unordered_map> NetMap; + typedef std::unordered_map AliasMap; + typedef std::unordered_map HierarchyMap; + + auto belpin_cls = py::class_>(m, "BelPin"); + readonly_wrapper>::def_wrap(belpin_cls, "bel"); + readonly_wrapper>::def_wrap(belpin_cls, "pin"); + + typedef FilteredBelRange BelRangeForBelBucket; +#include "arch_pybindings_shared.h" + + WRAP_RANGE(m, BelBucket, conv_to_str); + WRAP_RANGE(m, Bel, conv_to_str); + WRAP_RANGE(m, Wire, conv_to_str); + WRAP_RANGE(m, AllPip, conv_to_str); + WRAP_RANGE(m, UphillPip, conv_to_str); + WRAP_RANGE(m, DownhillPip, conv_to_str); + WRAP_RANGE(m, BelPin, wrap_context); + + WRAP_MAP_UPTR(m, CellMap, "IdCellMap"); + WRAP_MAP_UPTR(m, NetMap, "IdNetMap"); + WRAP_MAP(m, HierarchyMap, wrap_context, "HierarchyMap"); +} + +NEXTPNR_NAMESPACE_END + +#endif // NO_PYTHON diff --git a/fpga_interchange/arch_pybindings.h b/fpga_interchange/arch_pybindings.h new file mode 100644 index 00000000..1cccdf55 --- /dev/null +++ b/fpga_interchange/arch_pybindings.h @@ -0,0 +1,110 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2020 David Shah + * Copyright (C) 2021 Symbiflow Authors + * + * 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" + +NEXTPNR_NAMESPACE_BEGIN + +namespace PythonConversion { + +template <> struct string_converter +{ + BelId from_str(Context *ctx, std::string name) { return ctx->getBelByName(IdStringList::parse(ctx, 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 from_str(Context *ctx, std::string name) { return ctx->getWireByName(IdStringList::parse(ctx, 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 +{ + WireId from_str(Context *ctx, std::string name) { return ctx->getWireByName(IdStringList::parse(ctx, 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 from_str(Context *ctx, std::string name) { return ctx->getPipByName(IdStringList::parse(ctx, 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 +{ + BelBucketId from_str(Context *ctx, std::string name) { return ctx->getBelBucketByName(ctx->id(name)); } + + std::string to_str(Context *ctx, BelBucketId id) + { + if (id == BelBucketId()) + throw bad_wrap(); + return ctx->getBelBucketName(id).str(ctx); + } +}; + +template <> struct string_converter +{ + BelPin from_str(Context *ctx, std::string name) + { + NPNR_ASSERT_FALSE("string_converter::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/fpga_interchange/archdefs.h b/fpga_interchange/archdefs.h new file mode 100644 index 00000000..d6d0a3c7 --- /dev/null +++ b/fpga_interchange/archdefs.h @@ -0,0 +1,192 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Wolf + * Copyright (C) 2021 Symbiflow Authors + * + * 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 + +#include + +typedef int delay_t; + +struct DelayInfo +{ + delay_t delay = 0; + + delay_t minRaiseDelay() const { return delay; } + delay_t maxRaiseDelay() const { return delay; } + + delay_t minFallDelay() const { return delay; } + delay_t maxFallDelay() const { return delay; } + + delay_t minDelay() const { return delay; } + delay_t maxDelay() const { return delay; } + + DelayInfo operator+(const DelayInfo &other) const + { + DelayInfo ret; + ret.delay = this->delay + other.delay; + return ret; + } +}; + +// ----------------------------------------------------------------------- + +struct BelId +{ + // Tile that contains this BEL. + int32_t tile = -1; + // Index into tile type BEL array. + // BEL indicies are the same for all tiles of the same type. + int32_t index = -1; + + 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 +{ + // Tile that contains this wire. + int32_t tile = -1; + int32_t index = -1; + + 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; + int32_t index = -1; + + 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 +{ + bool operator==(const GroupId &other) const { return true; } + bool operator!=(const GroupId &other) const { return false; } +}; + +struct DecalId +{ + bool operator==(const DecalId &other) const { return true; } + bool operator!=(const DecalId &other) const { return false; } +}; + +struct BelBucketId +{ + IdString name; + + bool operator==(const BelBucketId &other) const { return (name == other.name); } + bool operator!=(const BelBucketId &other) const { return (name != other.name); } + bool operator<(const BelBucketId &other) const { return name < other.name; } +}; + +struct ArchNetInfo +{ +}; + +struct NetInfo; + +struct ArchCellInfo +{ +}; + +NEXTPNR_NAMESPACE_END + +namespace std { +template <> struct hash +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX BelId &bel) const noexcept + { + std::size_t seed = 0; + boost::hash_combine(seed, hash()(bel.tile)); + boost::hash_combine(seed, hash()(bel.index)); + return seed; + } +}; + +template <> struct hash +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX WireId &wire) const noexcept + { + std::size_t seed = 0; + boost::hash_combine(seed, hash()(wire.tile)); + boost::hash_combine(seed, hash()(wire.index)); + return seed; + } +}; + +template <> struct hash +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX PipId &pip) const noexcept + { + std::size_t seed = 0; + boost::hash_combine(seed, hash()(pip.tile)); + boost::hash_combine(seed, hash()(pip.index)); + return seed; + } +}; + +template <> struct hash +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX GroupId &group) const noexcept + { + std::size_t seed = 0; + return seed; + } +}; + +template <> struct hash +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX DecalId &decal) const noexcept + { + std::size_t seed = 0; + return seed; + } +}; + +template <> struct hash +{ + std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX BelBucketId &bucket) const noexcept + { + std::size_t seed = 0; + boost::hash_combine(seed, hash()(bucket.name)); + return seed; + } +}; + +} // namespace std diff --git a/fpga_interchange/family.cmake b/fpga_interchange/family.cmake new file mode 100644 index 00000000..e69de29b diff --git a/fpga_interchange/main.cc b/fpga_interchange/main.cc new file mode 100644 index 00000000..1f98b186 --- /dev/null +++ b/fpga_interchange/main.cc @@ -0,0 +1,84 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Claire Wolf + * Copyright (C) 2021 Symbiflow Authors + * + * 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 +#include "command.h" +#include "design_utils.h" +#include "jsonwrite.h" +#include "log.h" +#include "timing.h" + +USING_NEXTPNR_NAMESPACE + +class FpgaInterchangeCommandHandler : public CommandHandler +{ + public: + FpgaInterchangeCommandHandler(int argc, char **argv); + virtual ~FpgaInterchangeCommandHandler(){}; + std::unique_ptr createContext(std::unordered_map &values) override; + void setupArchContext(Context *ctx) override{}; + void customBitstream(Context *ctx) override; + void customAfterLoad(Context *ctx) override; + + protected: + po::options_description getArchOptions() override; +}; + +FpgaInterchangeCommandHandler::FpgaInterchangeCommandHandler(int argc, char **argv) : CommandHandler(argc, argv) {} + +po::options_description FpgaInterchangeCommandHandler::getArchOptions() +{ + po::options_description specific("Architecture specific options"); + specific.add_options()("chipdb", po::value(), "name of chip database binary"); + specific.add_options()("xdc", po::value>(), "XDC-style constraints file"); + specific.add_options()("phys", po::value(), "FPGA interchange Physical netlist to write"); + + return specific; +} + +void FpgaInterchangeCommandHandler::customBitstream(Context *ctx) +{ + if (vm.count("phys")) { + std::string filename = vm["phys"].as(); + ctx->write_physical_netlist(filename); + } +} + +std::unique_ptr FpgaInterchangeCommandHandler::createContext(std::unordered_map &values) +{ + ArchArgs chipArgs; + if (!vm.count("chipdb")) { + log_error("chip database binary must be provided\n"); + } + chipArgs.chipdb = vm["chipdb"].as(); + return std::unique_ptr(new Context(chipArgs)); +} + +void FpgaInterchangeCommandHandler::customAfterLoad(Context *ctx) {} + +int main(int argc, char *argv[]) +{ + FpgaInterchangeCommandHandler handler(argc, argv); + return handler.exec(); +} + +#endif diff --git a/gui/fpga_interchange/family.cmake b/gui/fpga_interchange/family.cmake new file mode 100644 index 00000000..e69de29b diff --git a/gui/fpga_interchange/mainwindow.cc b/gui/fpga_interchange/mainwindow.cc new file mode 100644 index 00000000..b8a89ca9 --- /dev/null +++ b/gui/fpga_interchange/mainwindow.cc @@ -0,0 +1,50 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Miodrag Milanovic + * Copyright (C) 2021 Symbiflow Authors + * + * 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 "mainwindow.h" + +#include +#include + +static void initMainResource() { Q_INIT_RESOURCE(nextpnr); } + +NEXTPNR_NAMESPACE_BEGIN + +MainWindow::MainWindow(std::unique_ptr context, CommandHandler *handler, QWidget *parent) + : BaseMainWindow(std::move(context), handler, parent) +{ + initMainResource(); + QMessageBox::critical(0, "Error - FIXME", "No GUI support for nextpnr-generic"); + std::exit(1); +} + +MainWindow::~MainWindow() {} + +void MainWindow::newContext(Context *ctx) +{ + std::string title = "nextpnr-generic - " + ctx->getChipName(); + setWindowTitle(title.c_str()); +} + +void MainWindow::createMenu() {} + +void MainWindow::new_proj() {} + +NEXTPNR_NAMESPACE_END diff --git a/gui/fpga_interchange/mainwindow.h b/gui/fpga_interchange/mainwindow.h new file mode 100644 index 00000000..0983845b --- /dev/null +++ b/gui/fpga_interchange/mainwindow.h @@ -0,0 +1,46 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 Miodrag Milanovic + * Copyright (C) 2021 Symbiflow Authors + * + * 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 MAINWINDOW_H +#define MAINWINDOW_H + +#include "../basewindow.h" + +NEXTPNR_NAMESPACE_BEGIN + +class MainWindow : public BaseMainWindow +{ + Q_OBJECT + + public: + explicit MainWindow(std::unique_ptr context, CommandHandler *handler, QWidget *parent = 0); + virtual ~MainWindow(); + + public: + void createMenu(); + + protected Q_SLOTS: + void new_proj() override; + void newContext(Context *ctx); +}; + +NEXTPNR_NAMESPACE_END + +#endif // MAINWINDOW_H diff --git a/gui/fpga_interchange/nextpnr.qrc b/gui/fpga_interchange/nextpnr.qrc new file mode 100644 index 00000000..03585ec0 --- /dev/null +++ b/gui/fpga_interchange/nextpnr.qrc @@ -0,0 +1,2 @@ + +