Merge pull request #567 from litghost/initial_fpga_interchange

Initial FPGA interchange arch
This commit is contained in:
David Shah 2021-02-05 19:15:29 +00:00 committed by GitHub
commit 8b4163b77c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 2582 additions and 2 deletions

1
.gitignore vendored
View File

@ -4,6 +4,7 @@
/nextpnr-ice40*
/nextpnr-ecp5*
/nextpnr-nexus*
/nextpnr-fpga_interchange*
cmake-build-*/
Makefile
cmake_install.cmake

View File

@ -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})

View File

@ -32,6 +32,7 @@ NPNR_PACKED_STRUCT(template <typename T> 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()); }

162
fpga_interchange/README.md Normal file
View File

@ -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 \
<logical netlist file> <physical netlist file> <XDC file> <output DCP>
```

578
fpga_interchange/arch.cc Normal file
View File

@ -0,0 +1,578 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 Claire Wolf <claire@symbioticeda.com>
* Copyright (C) 2018-19 David Shah <david@symbioticeda.com>
* 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 <algorithm>
#include <boost/algorithm/string.hpp>
#include <boost/range/adaptor/reversed.hpp>
#include <cmath>
#include <cstring>
#include <queue>
#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<std::string, std::string> 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<ChipInfoPOD> *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<const char *>(blob_file.data());
chip_info = get_chip_info(reinterpret_cast<const RelPtr<ChipInfoPOD> *>(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<std::pair<IdString, std::string>> Arch::getWireAttrs(WireId wire) const { return {}; }
// -----------------------------------------------------------------------
PipId Arch::getPipByName(IdStringList name) const
{
// PIP name structure:
// Tile PIP: <tile name>/<source wire name>.<destination wire name>
// Site PIP: <site name>/<bel name>/<input bel pin name>
// Site pin: <site name>/<bel name>
// Psuedo site PIP: <site name>/<source wire name>.<destination wire name>
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<IdString, 2> 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>/<src site wire>.<dst site wire>
// site pins are <site>/<bel>
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: <tile name>/<source wire name>.<destination wire name>
// Psuedo site PIP: <site name>/<input site wire>.<output site wire>
// Site PIP: <site name>/<bel name>/<input bel pin name>
// Site pin: <site name>/<bel name>
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<IdString, 2> 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<IdString, 3> 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<IdString, 2> 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<IdString, 2> ids{id(std::string(tile.name.get())), pip};
return IdStringList(ids);
}
}
IdString Arch::getPipType(PipId pip) const { return id("PIP"); }
std::vector<std::pair<IdString, std::string>> 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<std::pair<IdString, std::string>> 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<GraphicElement> 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<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

1277
fpga_interchange/arch.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,77 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2020 David Shah <dave@ds0.me>
* 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_<ArchArgs>(m, "ArchArgs").def_readwrite("chipdb", &ArchArgs::chipdb);
py::class_<BelId>(m, "BelId").def_readwrite("index", &BelId::index);
py::class_<WireId>(m, "WireId").def_readwrite("index", &WireId::index);
py::class_<PipId>(m, "PipId").def_readwrite("index", &PipId::index);
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);
fn_wrapper_2a<Context, decltype(&Context::isValidBelForCell), &Context::isValidBelForCell, pass_through<bool>,
addr_and_unwrap<CellInfo>, conv_from_str<BelId>>::def_wrap(ctx_cls, "isValidBelForCell");
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, IdString> AliasMap;
typedef std::unordered_map<IdString, HierarchicalCell> HierarchyMap;
auto belpin_cls = py::class_<ContextualWrapper<BelPin>>(m, "BelPin");
readonly_wrapper<BelPin, decltype(&BelPin::bel), &BelPin::bel, conv_to_str<BelId>>::def_wrap(belpin_cls, "bel");
readonly_wrapper<BelPin, decltype(&BelPin::pin), &BelPin::pin, conv_to_str<IdString>>::def_wrap(belpin_cls, "pin");
typedef FilteredBelRange BelRangeForBelBucket;
#include "arch_pybindings_shared.h"
WRAP_RANGE(m, BelBucket, conv_to_str<BelBucketId>);
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, UphillPip, conv_to_str<PipId>);
WRAP_RANGE(m, DownhillPip, conv_to_str<PipId>);
WRAP_RANGE(m, BelPin, 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

View File

@ -0,0 +1,110 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2020 David Shah <dave@ds0.me>
* 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>
{
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>
{
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<const WireId>
{
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>
{
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>
{
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>
{
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

192
fpga_interchange/archdefs.h Normal file
View File

@ -0,0 +1,192 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 Claire Wolf <claire@symbioticeda.com>
* 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 <cstdint>
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<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;
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;
return seed;
}
};
template <> struct hash<NEXTPNR_NAMESPACE_PREFIX BelBucketId>
{
std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX BelBucketId &bucket) const noexcept
{
std::size_t seed = 0;
boost::hash_combine(seed, hash<NEXTPNR_NAMESPACE_PREFIX IdString>()(bucket.name));
return seed;
}
};
} // namespace std

View File

84
fpga_interchange/main.cc Normal file
View File

@ -0,0 +1,84 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 Claire Wolf <claire@symbioticeda.com>
* 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 <fstream>
#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<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;
};
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<std::string>(), "name of chip database binary");
specific.add_options()("xdc", po::value<std::vector<std::string>>(), "XDC-style constraints file");
specific.add_options()("phys", po::value<std::string>(), "FPGA interchange Physical netlist to write");
return specific;
}
void FpgaInterchangeCommandHandler::customBitstream(Context *ctx)
{
if (vm.count("phys")) {
std::string filename = vm["phys"].as<std::string>();
ctx->write_physical_netlist(filename);
}
}
std::unique_ptr<Context> FpgaInterchangeCommandHandler::createContext(std::unordered_map<std::string, Property> &values)
{
ArchArgs chipArgs;
if (!vm.count("chipdb")) {
log_error("chip database binary must be provided\n");
}
chipArgs.chipdb = vm["chipdb"].as<std::string>();
return std::unique_ptr<Context>(new Context(chipArgs));
}
void FpgaInterchangeCommandHandler::customAfterLoad(Context *ctx) {}
int main(int argc, char *argv[])
{
FpgaInterchangeCommandHandler handler(argc, argv);
return handler.exec();
}
#endif

View File

View File

@ -0,0 +1,50 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 Miodrag Milanovic <miodrag@symbioticeda.com>
* 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 <QMessageBox>
#include <cstdlib>
static void initMainResource() { Q_INIT_RESOURCE(nextpnr); }
NEXTPNR_NAMESPACE_BEGIN
MainWindow::MainWindow(std::unique_ptr<Context> 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

View File

@ -0,0 +1,46 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 Miodrag Milanovic <miodrag@symbioticeda.com>
* 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> 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

View File

@ -0,0 +1,2 @@
<RCC>
</RCC>