Merge pull request #524 from daveshah1/nextpnr-nexus

Upstreaming basic support for Nexus devices
This commit is contained in:
Miodrag Milanović 2020-11-30 10:56:59 +01:00 committed by GitHub
commit 8b5c0dc1e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 7434 additions and 13 deletions

View File

@ -5,7 +5,7 @@ task:
memory: 20
dockerfile: .cirrus/Dockerfile.ubuntu16.04
build_script: mkdir build && cd build && cmake .. -DARCH=all -DBUILD_TESTS=on && make -j $(nproc)
build_script: mkdir build && cd build && cmake .. -DARCH=all+alpha -DOXIDE_INSTALL_PREFIX=$HOME/.cargo -DBUILD_TESTS=on && make -j3
submodule_script: git submodule sync --recursive && git submodule update --init --recursive
test_generic_script: cd build && ./nextpnr-generic-test
test_ice40_script: cd build && ./nextpnr-ice40-test

View File

@ -9,7 +9,7 @@ RUN set -e -x ;\
build-essential autoconf cmake clang bison wget flex gperf \
libreadline-dev gawk tcl-dev libffi-dev graphviz xdot python3-dev \
libboost-all-dev qt5-default git libftdi-dev pkg-config libeigen3-dev \
zlib1g-dev
zlib1g-dev curl
RUN set -e -x ;\
mkdir -p /usr/local/src ;\
@ -52,3 +52,13 @@ RUN set -e -x ;\
cmake . ;\
make -j $(nproc) ;\
make install
RUN set -e -x ;\
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y ;\
mkdir -p /usr/local/src ;\
cd /usr/local/src ;\
git clone --recursive https://github.com/daveshah1/prjoxide.git ;\
cd prjoxide ;\
git reset --hard 72dbb7973f31a30c3b9d18f3bac97caaea9a7f33 ;\
cd libprjoxide ;\
PATH=$PATH:$HOME/.cargo/bin cargo install --path prjoxide

View File

@ -4,3 +4,4 @@ echo "Running archcheck!"
${BUILD_DIR}/nextpnr-ice40 --hx8k --package ct256 --test
${BUILD_DIR}/nextpnr-ice40 --up5k --package sg48 --test
${BUILD_DIR}/nextpnr-ecp5 --um5g-25k --package CABGA381 --test
${BUILD_DIR}/nextpnr-nexus --device LIFCL-40-9BG400CES --test

1
.gitignore vendored
View File

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

View File

@ -66,7 +66,9 @@ endif()
set(PROGRAM_PREFIX "" CACHE STRING "Name prefix for executables")
# List of families to build
set(FAMILIES generic ice40 ecp5)
set(FAMILIES generic ice40 ecp5 nexus)
set(STABLE_FAMILIES generic ice40 ecp5)
set(EXPERIMENTAL_FAMILIES nexus)
set(ARCH "" CACHE STRING "Architecture family for nextpnr build")
set_property(CACHE ARCH PROPERTY STRINGS ${FAMILIES})
@ -75,14 +77,20 @@ if (NOT ARCH)
message(STATUS "Architecture needs to be set, set desired one with -DARCH=xxx")
message(STATUS "Supported architectures are :")
message(STATUS " all")
message(STATUS " all+alpha")
foreach(item ${FAMILIES})
message(STATUS " ${item}")
endforeach()
message(FATAL_ERROR "Architecture setting is mandatory")
endif ()
if (ARCH STREQUAL "all+alpha")
SET(ARCH ${STABLE_FAMILIES} ${EXPERIMENTAL_FAMILIES})
endif()
if (ARCH STREQUAL "all")
SET(ARCH ${FAMILIES})
SET(ARCH ${STABLE_FAMILIES})
endif()
foreach(item ${ARCH})
@ -286,6 +294,7 @@ endforeach (family)
file(GLOB_RECURSE CLANGFORMAT_FILES *.cc *.h)
string(REGEX REPLACE "[^;]*/ice40/chipdb/chipdb-[^;]*.cc" "" CLANGFORMAT_FILES "${CLANGFORMAT_FILES}")
string(REGEX REPLACE "[^;]*/ecp5/chipdb/chipdb-[^;]*.cc" "" CLANGFORMAT_FILES "${CLANGFORMAT_FILES}")
string(REGEX REPLACE "[^;]*nexus/chipdb/chipdb-[^;]*.cc" "" CLANGFORMAT_FILES "${CLANGFORMAT_FILES}")
string(REGEX REPLACE "[^;]*/3rdparty[^;]*" "" CLANGFORMAT_FILES "${CLANGFORMAT_FILES}")
string(REGEX REPLACE "[^;]*/generated[^;]*" "" CLANGFORMAT_FILES "${CLANGFORMAT_FILES}")

View File

@ -7,6 +7,7 @@ tool.
Currently nextpnr supports:
* Lattice iCE40 devices supported by [Project IceStorm](http://www.clifford.at/icestorm/)
* Lattice ECP5 devices supported by [Project Trellis](https://github.com/YosysHQ/prjtrellis)
* Lattice Nexus devices supported by [Project Oxide](https://github.com/daveshah1/prjoxide)
* *(experimental)* a "generic" back-end for user-defined architectures
There is some work in progress towards [support for Xilinx devices](https://github.com/daveshah1/nextpnr-xilinx/) but it is not upstream and not intended for end users at the present time. We hope to see more FPGA families supported in the future. We would love your help in developing this awesome new project!
@ -103,6 +104,19 @@ sudo make install
- Examples of the ECP5 flow for a range of boards can be found in the [Project Trellis Examples](https://github.com/YosysHQ/prjtrellis/tree/master/examples).
### nextpnr-nexus
For Nexus support, install [Project Oxide](https://github.com/daveshah1/prjoxide) to `$HOME/.cargo` or another location, which should be passed as `-DOXIDE_INSTALL_PREFIX=$HOME/.cargo` to CMake. Then build and install `nextpnr-nexus` using the following commands:
```
cmake . -DARCH=nexus -DOXIDE_INSTALL_PREFIX=$HOME/.cargo
make -j$(nproc)
sudo make install
```
- Examples of the Nexus flow for a range of boards can be found in the [Project Oxide Examples](https://github.com/daveshah1/prjoxide/tree/master/examples).
Nexus support is currently experimental, and has only been tested with engineering sample silicon.
### nextpnr-generic
@ -126,7 +140,7 @@ make -j$(nproc)
sudo make install
```
To build every available architecture, use `-DARCH=all`.
To build every available stable architecture, use `-DARCH=all`. To include experimental arches (currently nexus), use `-DARCH=all+alpha`.
Pre-generating chip databases
-----------------------------

View File

@ -103,11 +103,6 @@ fn_wrapper_1a<Context, decltype(&Context::getPipDstWire), &Context::getPipDstWir
fn_wrapper_1a<Context, decltype(&Context::getPipDelay), &Context::getPipDelay, pass_through<DelayInfo>,
conv_from_str<PipId>>::def_wrap(ctx_cls, "getPipDelay");
fn_wrapper_1a<Context, decltype(&Context::getPackagePinBel), &Context::getPackagePinBel, conv_to_str<BelId>,
pass_through<std::string>>::def_wrap(ctx_cls, "getPackagePinBel");
fn_wrapper_1a<Context, decltype(&Context::getBelPackagePin), &Context::getBelPackagePin, pass_through<std::string>,
conv_from_str<BelId>>::def_wrap(ctx_cls, "getBelPackagePin");
fn_wrapper_0a<Context, decltype(&Context::getChipName), &Context::getChipName, pass_through<std::string>>::def_wrap(
ctx_cls, "getChipName");
fn_wrapper_0a<Context, decltype(&Context::archId), &Context::archId, conv_to_str<IdString>>::def_wrap(ctx_cls,

View File

@ -30,6 +30,13 @@ void replace_port(CellInfo *old_cell, IdString old_name, CellInfo *rep_cell, IdS
if (!old_cell->ports.count(old_name))
return;
PortInfo &old = old_cell->ports.at(old_name);
// Create port on the replacement cell if it doesn't already exist
if (!rep_cell->ports.count(rep_name)) {
rep_cell->ports[rep_name].name = rep_name;
rep_cell->ports[rep_name].type = old.type;
}
PortInfo &rep = rep_cell->ports.at(rep_name);
NPNR_ASSERT(old.type == rep.type);
@ -157,4 +164,33 @@ void rename_net(Context *ctx, NetInfo *net, IdString new_name)
net->name = new_name;
}
void replace_bus(Context *ctx, CellInfo *old_cell, IdString old_name, int old_offset, bool old_brackets,
CellInfo *new_cell, IdString new_name, int new_offset, bool new_brackets, int width)
{
for (int i = 0; i < width; i++) {
IdString old_port = ctx->id(stringf(old_brackets ? "%s[%d]" : "%s%d", old_name.c_str(ctx), i + old_offset));
IdString new_port = ctx->id(stringf(new_brackets ? "%s[%d]" : "%s%d", new_name.c_str(ctx), i + new_offset));
replace_port(old_cell, old_port, new_cell, new_port);
}
}
void copy_port(Context *ctx, CellInfo *old_cell, IdString old_name, CellInfo *new_cell, IdString new_name)
{
if (!old_cell->ports.count(old_name))
return;
new_cell->ports[new_name].name = new_name;
new_cell->ports[new_name].type = old_cell->ports.at(old_name).type;
connect_port(ctx, old_cell->ports.at(old_name).net, new_cell, new_name);
}
void copy_bus(Context *ctx, CellInfo *old_cell, IdString old_name, int old_offset, bool old_brackets,
CellInfo *new_cell, IdString new_name, int new_offset, bool new_brackets, int width)
{
for (int i = 0; i < width; i++) {
IdString old_port = ctx->id(stringf(old_brackets ? "%s[%d]" : "%s%d", old_name.c_str(ctx), i + old_offset));
IdString new_port = ctx->id(stringf(new_brackets ? "%s[%d]" : "%s%d", new_name.c_str(ctx), i + new_offset));
copy_port(ctx, old_cell, old_port, new_cell, new_port);
}
}
NEXTPNR_NAMESPACE_END

View File

@ -82,6 +82,13 @@ template <typename F1> CellInfo *net_driven_by(const Context *ctx, const NetInfo
}
}
// Check if a port is used
inline bool port_used(CellInfo *cell, IdString port_name)
{
auto port_fnd = cell->ports.find(port_name);
return port_fnd != cell->ports.end() && port_fnd->second.net != nullptr;
}
// Connect a net to a port
void connect_port(const Context *ctx, NetInfo *net, CellInfo *cell, IdString port_name);
@ -99,6 +106,17 @@ void rename_net(Context *ctx, NetInfo *net, IdString new_name);
void print_utilisation(const Context *ctx);
// Disconnect a bus of nets (if connected) from old, and connect it to the new ports
void replace_bus(Context *ctx, CellInfo *old_cell, IdString old_name, int old_offset, bool old_brackets,
CellInfo *new_cell, IdString new_name, int new_offset, bool new_brackets, int width);
// Copy a bus of nets (if connected) from old, and connect it to the new ports
void copy_bus(Context *ctx, CellInfo *old_cell, IdString old_name, int old_offset, bool old_brackets,
CellInfo *new_cell, IdString new_name, int new_offset, bool new_brackets, int width);
// Copy a port from one cell to another
void copy_port(Context *ctx, CellInfo *old_cell, IdString old_name, CellInfo *new_cell, IdString new_name);
NEXTPNR_NAMESPACE_END
#endif

View File

@ -506,7 +506,7 @@ void Context::check() const
}
}
}
#ifdef CHECK_WIRES
for (auto w : getWires()) {
auto ni = getBoundWireNet(w);
if (ni != nullptr) {
@ -514,7 +514,7 @@ void Context::check() const
CHECK_FAIL("wire '%s' missing in wires map of bound net '%s'\n", nameOfWire(w), nameOf(ni));
}
}
#endif
for (auto &c : cells) {
auto ci = c.second.get();
if (c.first != ci->name)

View File

@ -469,6 +469,20 @@ struct Router1
}
}
// special case
if (src_wire == dst_wire) {
NetInfo *bound = ctx->getBoundWireNet(src_wire);
if (bound != nullptr)
NPNR_ASSERT(bound == net_info);
else {
ctx->bindWire(src_wire, net_info, STRENGTH_WEAK);
}
arc_to_wires[arc].insert(src_wire);
wire_to_arcs[src_wire].insert(arc);
return true;
}
// reset wire queue
if (!queue.empty()) {

View File

@ -774,8 +774,11 @@ struct Router2
if (dst == WireId() || ctx->getBoundWireNet(dst) == net)
return true;
// Skip routes where there is no routing (special cases)
if (!ad.routed)
if (!ad.routed) {
if ((src == dst) && ctx->getBoundWireNet(dst) != net)
ctx->bindWire(src, net, STRENGTH_WEAK);
return true;
}
WireId cursor = dst;

View File

@ -112,6 +112,24 @@ template <typename K, typename V> std::map<K, V *> sorted(const std::unordered_m
return retVal;
};
// Wrap an unordered_map, and allow it to be iterated over sorted by key
template <typename K, typename V> std::map<K, V &> sorted_ref(std::unordered_map<K, V> &orig)
{
std::map<K, V &> retVal;
for (auto &item : orig)
retVal.emplace(std::make_pair(item.first, std::ref(item.second)));
return retVal;
};
// Wrap an unordered_map, and allow it to be iterated over sorted by key
template <typename K, typename V> std::map<K, const V &> sorted_cref(const std::unordered_map<K, V> &orig)
{
std::map<K, const V &> retVal;
for (auto &item : orig)
retVal.emplace(std::make_pair(item.first, std::ref(item.second)));
return retVal;
};
// Wrap an unordered_set, and allow it to be iterated over sorted by key
template <typename K> std::set<K> sorted(const std::unordered_set<K> &orig)
{

20
docs/nexus.md Normal file
View File

@ -0,0 +1,20 @@
# nextpnr-nexus notes
### Constraints
Currently the following PDC constraint styles are supported for IO constraints:
```
ldc_set_location -site {G13} [get_ports gsrn]
ldc_set_port -iobuf {IO_TYPE=LVCMOS33} [get_ports {led[0]}]
```
Timing constraints are currently ignored, but should be expected to be supported soon.
### Command Line
A full device name is specified on the command line. It should be postfixed with 'ES' if using an engineering sample device to ensure correct use of the ES IDCODE.
```
--device LIFCL-40-9BG400CES
```

0
gui/nexus/family.cmake Normal file
View File

87
gui/nexus/mainwindow.cc Normal file
View File

@ -0,0 +1,87 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 Miodrag Milanovic <miodrag@symbioticeda.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include "mainwindow.h"
#include <QAction>
#include <QFileDialog>
#include <QFileInfo>
#include <QIcon>
#include <QInputDialog>
#include <QLineEdit>
#include <QSet>
#include <fstream>
#include "design_utils.h"
#include "log.h"
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();
std::string title = "nextpnr-nexus - [EMPTY]";
setWindowTitle(title.c_str());
connect(this, &BaseMainWindow::contextChanged, this, &MainWindow::newContext);
createMenu();
}
MainWindow::~MainWindow() {}
void MainWindow::createMenu()
{
// Add arch specific actions
// Add actions in menus
}
void MainWindow::new_proj()
{
QStringList arch;
// TODO: better device picker
arch.push_back("LIFCL-40-9BG400CES");
arch.push_back("LIFCL-40-8BG72CES");
bool ok;
QString item = QInputDialog::getItem(this, "Select new context", "Chip:", arch, 0, false, &ok);
if (ok && !item.isEmpty()) {
ArchArgs chipArgs;
chipArgs.device = item.toStdString();
ctx = std::unique_ptr<Context>(new Context(chipArgs));
actionLoadJSON->setEnabled(true);
Q_EMIT contextChanged(ctx.get());
}
}
void MainWindow::newContext(Context *ctx)
{
std::string title = "nextpnr-nexus - " + ctx->getChipName();
setWindowTitle(title.c_str());
}
void MainWindow::onDisableActions() {}
void MainWindow::onUpdateActions() {}
NEXTPNR_NAMESPACE_END

49
gui/nexus/mainwindow.h Normal file
View File

@ -0,0 +1,49 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 Miodrag Milanovic <miodrag@symbioticeda.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#ifndef 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();
void onDisableActions() override;
void onUpdateActions() override;
protected Q_SLOTS:
void new_proj() override;
void newContext(Context *ctx);
};
NEXTPNR_NAMESPACE_END
#endif // MAINWINDOW_H

2
gui/nexus/nextpnr.qrc Normal file
View File

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

1
nexus/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/chipdb/

57
nexus/CMakeLists.txt Normal file
View File

@ -0,0 +1,57 @@
cmake_minimum_required(VERSION 3.5)
project(chipdb-nexus NONE)
set(ALL_NEXUS_FAMILIES LIFCL)
# NOTE: Unlike iCE40 and ECP5; one database can cover all densities of a given family
set(NEXUS_FAMILIES ${ALL_NEXUS_FAMILIES} CACHE STRING
"Include support for these Nexus families (available: ${ALL_NEXUS_FAMILIES})")
message(STATUS "Enabled Nexus families: ${NEXUS_FAMILIES}")
if(DEFINED NEXUS_CHIPDB)
add_custom_target(chipdb-nexus-bbas ALL)
else()
# shared among all families
set(OXIDE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}" CACHE STRING
"prjoxide install prefix")
message(STATUS "prjoxide install prefix: ${OXIDE_INSTALL_PREFIX}")
set(all_device_bbas)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/chipdb)
foreach(subfamily ${NEXUS_FAMILIES})
if(NOT subfamily IN_LIST ALL_NEXUS_FAMILIES)
message(FATAL_ERROR "${subfamily} is not a supported Nexus family")
endif()
set(family_bba chipdb/chipdb-${subfamily}.bba)
set(PRJOXIDE_TOOL ${OXIDE_INSTALL_PREFIX}/bin/prjoxide)
add_custom_command(
OUTPUT ${family_bba}
COMMAND
${PRJOXIDE_TOOL} bba-export ${subfamily} ${CMAKE_CURRENT_SOURCE_DIR}/constids.inc ${family_bba}.new
# atomically update
COMMAND ${CMAKE_COMMAND} -E rename ${family_bba}.new ${family_bba}
DEPENDS
${PRJOXIDE_TOOL}
${CMAKE_CURRENT_SOURCE_DIR}/constids.inc
${CMAKE_CURRENT_SOURCE_DIR}/bba_version.inc
${PREVIOUS_CHIPDB_TARGET}
VERBATIM)
list(APPEND all_device_bbas ${family_bba})
if(SERIALIZE_CHIPDBS)
set(PREVIOUS_CHIPDB_TARGET ${CMAKE_CURRENT_BINARY_DIR}/${family_bba})
endif()
endforeach()
add_custom_target(chipdb-nexus-bbas ALL DEPENDS ${all_device_bbas})
get_directory_property(has_parent PARENT_DIRECTORY)
if(has_parent)
set(NEXUS_CHIPDB ${CMAKE_CURRENT_BINARY_DIR}/chipdb PARENT_SCOPE)
# serialize chipdb build across multiple architectures
set(PREVIOUS_CHIPDB_TARGET chipdb-nexus-bbas PARENT_SCOPE)
else()
message(STATUS "Build nextpnr with -DNEXUS_CHIPDB=${CMAKE_CURRENT_BINARY_DIR}/chipdb")
endif()
endif()

963
nexus/arch.cc Normal file
View File

@ -0,0 +1,963 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2020 David Shah <dave@ds0.me>
*
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include <boost/algorithm/string.hpp>
#include "embed.h"
#include "log.h"
#include "nextpnr.h"
#include "placer1.h"
#include "placer_heap.h"
#include "router1.h"
#include "router2.h"
#include "timing.h"
#include "util.h"
NEXTPNR_NAMESPACE_BEGIN
namespace {
static std::tuple<int, int, std::string> split_identifier_name(const std::string &name)
{
size_t first_slash = name.find('/');
NPNR_ASSERT(first_slash != std::string::npos);
size_t second_slash = name.find('/', first_slash + 1);
NPNR_ASSERT(second_slash != std::string::npos);
return std::make_tuple(std::stoi(name.substr(1, first_slash)),
std::stoi(name.substr(first_slash + 2, second_slash - first_slash)),
name.substr(second_slash + 1));
};
} // namespace
// -----------------------------------------------------------------------
void IdString::initialize_arch(const BaseCtx *ctx)
{
#define X(t) initialize_add(ctx, #t, ID_##t);
#include "constids.inc"
#undef X
}
// -----------------------------------------------------------------------
Arch::Arch(ArchArgs args) : args(args)
{
// Parse device string
if (boost::starts_with(args.device, "LIFCL")) {
family = "LIFCL";
} else {
log_error("Unknown device string '%s' (expected device name like 'LIFCL-40-8SG72C')\n", args.device.c_str());
}
auto last_sep = args.device.rfind('-');
if (last_sep == std::string::npos)
log_error("Unknown device string '%s' (expected device name like 'LIFCL-40-8SG72C')\n", args.device.c_str());
device = args.device.substr(0, last_sep);
speed = args.device.substr(last_sep + 1, 1);
auto package_end = args.device.find_last_of("0123456789");
if (package_end == std::string::npos || package_end < last_sep)
log_error("Unknown device string '%s' (expected device name like 'LIFCL-40-8SG72C')\n", args.device.c_str());
package = args.device.substr(last_sep + 2, (package_end - (last_sep + 2)) + 1);
rating = args.device.substr(package_end + 1);
// Check for 'ES' part
if (rating.size() > 1 && rating.substr(1) == "ES") {
variant = "ES";
} else {
variant = "";
}
// Load database
std::string chipdb = stringf("nexus/chipdb-%s.bin", family.c_str());
auto db_ptr = reinterpret_cast<const RelPtr<DatabasePOD> *>(get_chipdb(chipdb));
if (db_ptr == nullptr)
log_error("Failed to load chipdb '%s'\n", chipdb.c_str());
db = db_ptr->get();
// Check database version and family
if (db->version != bba_version) {
log_error("Provided database version %d is %s than nextpnr version %d, please rebuild database/nextpnr.\n",
int(db->version), (db->version > bba_version) ? "newer" : "older", int(bba_version));
}
if (db->family.get() != family) {
log_error("Database is for family '%s' but provided device is family '%s'.\n", db->family.get(),
family.c_str());
}
// Set up chip_info
chip_info = nullptr;
for (size_t i = 0; i < db->num_chips; i++) {
auto &chip = db->chips[i];
if (chip.device_name.get() == device) {
chip_info = &chip;
break;
}
}
if (!chip_info)
log_error("Unknown device '%s'.\n", device.c_str());
// Set up bba IdStrings
for (size_t i = 0; i < db->ids->num_bba_ids; i++) {
IdString::initialize_add(this, db->ids->bba_id_strs[i].get(), uint32_t(i) + db->ids->num_file_ids);
}
// Set up validity structures
tileStatus.resize(chip_info->num_tiles);
for (size_t i = 0; i < chip_info->num_tiles; i++) {
tileStatus[i].boundcells.resize(db->loctypes[chip_info->grid[i].loc_type].num_bels);
}
// This structure is needed for a fast getBelByLocation because bels can have an offset
for (size_t i = 0; i < chip_info->num_tiles; i++) {
auto &loc = db->loctypes[chip_info->grid[i].loc_type];
for (unsigned j = 0; j < loc.num_bels; j++) {
auto &bel = loc.bels[j];
int rel_bel_tile;
if (!rel_tile(i, bel.rel_x, bel.rel_y, rel_bel_tile))
continue;
auto &ts = tileStatus.at(rel_bel_tile);
if (int(ts.bels_by_z.size()) <= bel.z)
ts.bels_by_z.resize(bel.z + 1);
ts.bels_by_z[bel.z].tile = i;
ts.bels_by_z[bel.z].index = j;
}
}
init_cell_pin_data();
// Validate and set up package
package_idx = -1;
for (size_t i = 0; i < chip_info->num_packages; i++) {
if (package == chip_info->packages[i].short_name.get()) {
package_idx = i;
break;
}
}
if (package_idx == -1) {
std::string all_packages = "";
for (size_t i = 0; i < chip_info->num_packages; i++) {
all_packages += " ";
all_packages += chip_info->packages[i].short_name.get();
}
log_error("Unknown package '%s'. Available package options:%s\n", package.c_str(), all_packages.c_str());
}
// Validate and set up speed grade
// Convert speed to speed grade (TODO: low power back bias mode too)
if (speed == "7")
speed = "10";
else if (speed == "8")
speed = "11";
else if (speed == "9")
speed = "12";
speed_grade = nullptr;
for (size_t i = 0; i < db->num_speed_grades; i++) {
auto &sg = db->speed_grades[i];
if (sg.name.get() == speed) {
speed_grade = &sg;
break;
}
}
if (!speed_grade)
log_error("Unknown speed grade '%s'.\n", speed.c_str());
}
// -----------------------------------------------------------------------
std::string Arch::getChipName() const { return args.device; }
IdString Arch::archArgsToId(ArchArgs args) const { return id(args.device); }
// -----------------------------------------------------------------------
BelId Arch::getBelByName(IdString name) const
{
int x, y;
std::string belname;
std::tie(x, y, belname) = split_identifier_name(name.str(this));
NPNR_ASSERT(x >= 0 && x < chip_info->width);
NPNR_ASSERT(y >= 0 && y < chip_info->height);
auto &tile = db->loctypes[chip_info->grid[y * chip_info->width + x].loc_type];
IdString bn = id(belname);
for (size_t i = 0; i < tile.num_bels; i++) {
if (tile.bels[i].name == bn.index) {
BelId ret;
ret.tile = y * chip_info->width + x;
ret.index = i;
return ret;
}
}
return BelId();
}
std::vector<BelId> Arch::getBelsByTile(int x, int y) const
{
std::vector<BelId> bels;
for (auto bel : tileStatus.at(y * chip_info->width + x).bels_by_z)
if (bel != BelId())
bels.push_back(bel);
return bels;
}
WireId Arch::getBelPinWire(BelId bel, IdString pin) const
{
// Binary search on wire IdString, by ID
int num_bel_wires = bel_data(bel).num_ports;
const BelWirePOD *bel_ports = bel_data(bel).ports.get();
if (num_bel_wires < 7) {
for (int i = 0; i < num_bel_wires; i++) {
if (int(bel_ports[i].port) == pin.index) {
return canonical_wire(bel.tile, bel_ports[i].wire_index);
}
}
} else {
int b = 0, e = num_bel_wires - 1;
while (b <= e) {
int i = (b + e) / 2;
if (int(bel_ports[i].port) == pin.index) {
return canonical_wire(bel.tile, bel_ports[i].wire_index);
}
if (int(bel_ports[i].port) > pin.index)
e = i - 1;
else
b = i + 1;
}
}
return WireId();
}
PortType Arch::getBelPinType(BelId bel, IdString pin) const
{
// Binary search on wire IdString, by ID
int num_bel_wires = bel_data(bel).num_ports;
const BelWirePOD *bel_ports = bel_data(bel).ports.get();
if (num_bel_wires < 7) {
for (int i = 0; i < num_bel_wires; i++) {
if (int(bel_ports[i].port) == pin.index) {
return PortType(bel_ports[i].type);
}
}
} else {
int b = 0, e = num_bel_wires - 1;
while (b <= e) {
int i = (b + e) / 2;
if (int(bel_ports[i].port) == pin.index) {
return PortType(bel_ports[i].type);
}
if (int(bel_ports[i].port) > pin.index)
e = i - 1;
else
b = i + 1;
}
}
NPNR_ASSERT_FALSE("unknown bel pin");
}
std::vector<IdString> Arch::getBelPins(BelId bel) const
{
std::vector<IdString> ret;
int num_bel_wires = bel_data(bel).num_ports;
const BelWirePOD *bel_ports = bel_data(bel).ports.get();
for (int i = 0; i < num_bel_wires; i++)
ret.push_back(IdString(bel_ports[i].port));
return ret;
}
std::vector<std::pair<IdString, std::string>> Arch::getBelAttrs(BelId bel) const
{
std::vector<std::pair<IdString, std::string>> ret;
ret.emplace_back(id("INDEX"), stringf("%d", bel.index));
ret.emplace_back(id("GRID_X"), stringf("%d", bel.tile % chip_info->width));
ret.emplace_back(id("GRID_Y"), stringf("%d", bel.tile / chip_info->width));
ret.emplace_back(id("BEL_Z"), stringf("%d", bel_data(bel).z));
ret.emplace_back(id("BEL_TYPE"), nameOf(getBelType(bel)));
return ret;
}
// -----------------------------------------------------------------------
WireId Arch::getWireByName(IdString name) const
{
int x, y;
std::string wirename;
std::tie(x, y, wirename) = split_identifier_name(name.str(this));
NPNR_ASSERT(x >= 0 && x < chip_info->width);
NPNR_ASSERT(y >= 0 && y < chip_info->height);
auto &tile = db->loctypes[chip_info->grid[y * chip_info->width + x].loc_type];
IdString wn = id(wirename);
for (size_t i = 0; i < tile.num_wires; i++) {
if (tile.wires[i].name == wn.index) {
WireId ret;
ret.tile = y * chip_info->width + x;
ret.index = i;
return ret;
}
}
return WireId();
}
IdString Arch::getWireType(WireId wire) const { return id("WIRE"); }
std::vector<std::pair<IdString, std::string>> Arch::getWireAttrs(WireId wire) const
{
std::vector<std::pair<IdString, std::string>> ret;
ret.emplace_back(id("INDEX"), stringf("%d", wire.index));
ret.emplace_back(id("GRID_X"), stringf("%d", wire.tile % chip_info->width));
ret.emplace_back(id("GRID_Y"), stringf("%d", wire.tile / chip_info->width));
ret.emplace_back(id("FLAGS"), stringf("%u", wire_data(wire).flags));
return ret;
}
// -----------------------------------------------------------------------
PipId Arch::getPipByName(IdString name) const
{
int x, y;
std::string pipname;
std::tie(x, y, pipname) = split_identifier_name(name.str(this));
NPNR_ASSERT(x >= 0 && x < chip_info->width);
NPNR_ASSERT(y >= 0 && y < chip_info->height);
PipId ret;
ret.tile = y * chip_info->width + x;
auto sep_pos = pipname.find(':');
ret.index = std::stoi(pipname.substr(0, sep_pos));
return ret;
}
IdString Arch::getPipName(PipId pip) const
{
NPNR_ASSERT(pip != PipId());
return id(stringf("X%d/Y%d/%d:%s->%s", pip.tile % chip_info->width, pip.tile / chip_info->width, pip.index,
nameOf(loc_data(pip).wires[pip_data(pip).from_wire].name),
nameOf(loc_data(pip).wires[pip_data(pip).to_wire].name)));
}
IdString Arch::getPipType(PipId pip) const { return IdString(); }
std::vector<std::pair<IdString, std::string>> Arch::getPipAttrs(PipId pip) const
{
std::vector<std::pair<IdString, std::string>> ret;
ret.emplace_back(id("INDEX"), stringf("%d", pip.index));
ret.emplace_back(id("GRID_X"), stringf("%d", pip.tile % chip_info->width));
ret.emplace_back(id("GRID_Y"), stringf("%d", pip.tile / chip_info->width));
ret.emplace_back(id("FROM_TILE_WIRE"), nameOf(loc_data(pip).wires[pip_data(pip).from_wire].name));
ret.emplace_back(id("TO_TILE_WIRE"), nameOf(loc_data(pip).wires[pip_data(pip).to_wire].name));
return ret;
}
// -----------------------------------------------------------------------
namespace {
const float bel_ofs_x = 0.7, bel_ofs_y = 0.0375;
const float bel_sp_x = 0.1, bel_sp_y = 0.1;
const float bel_width = 0.075, bel_height = 0.075;
} // namespace
std::vector<GraphicElement> Arch::getDecalGraphics(DecalId decal) const
{
std::vector<GraphicElement> ret;
switch (decal.type) {
case DecalId::TYPE_BEL: {
auto style = decal.active ? GraphicElement::STYLE_ACTIVE : GraphicElement::STYLE_INACTIVE;
if (decal.index != -1) {
int slice = (decal.index >> 3) & 0x3;
int bel = decal.index & 0x7;
float x1, x2, y1, y2;
if (bel == BEL_RAMW) {
x1 = bel_ofs_x;
y1 = bel_ofs_y + 2 * bel_sp_y * slice;
x2 = x1 + bel_sp_x + bel_width;
y2 = y1 + bel_height;
} else {
x1 = bel_ofs_x + bel_sp_x * (bel >> 1);
y1 = bel_ofs_y + 2 * bel_sp_y * slice + bel_sp_y * (bel & 0x1);
if (slice >= 2)
y1 += bel_sp_y * 1.5;
x2 = x1 + bel_width;
y2 = y1 + bel_height;
}
ret.emplace_back(GraphicElement::TYPE_BOX, style, x1, y1, x2, y2, 1);
}
break;
};
default:
break;
}
return ret;
}
DecalXY Arch::getBelDecal(BelId bel) const
{
DecalXY decalxy;
decalxy.decal.type = DecalId::TYPE_BEL;
if (tile_is(bel, LOC_LOGIC))
decalxy.decal.index = bel_data(bel).z;
else
decalxy.decal.index = -1;
decalxy.decal.active = (getBoundBelCell(bel) != nullptr);
decalxy.x = bel.tile % chip_info->width;
decalxy.y = bel.tile / chip_info->width;
return decalxy;
}
DecalXY Arch::getWireDecal(WireId wire) const { return {}; }
DecalXY Arch::getPipDecal(PipId pip) const { return {}; };
DecalXY Arch::getGroupDecal(GroupId pip) const { return {}; };
// -----------------------------------------------------------------------
bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const
{
auto lookup_port = [&](IdString p) {
auto fnd = cell->tmg_portmap.find(p);
return fnd == cell->tmg_portmap.end() ? p : fnd->second;
};
if (cell->type == id_OXIDE_COMB) {
if (cell->lutInfo.is_carry) {
bool result = lookup_cell_delay(cell->tmg_index, lookup_port(fromPort), lookup_port(toPort), delay);
// Because CCU2 = 2x OXIDE_COMB
if (result && fromPort == id_FCI && toPort == id_FCO) {
delay.min_delay /= 2;
delay.max_delay /= 2;
}
return result;
} else {
if (toPort == id_F || toPort == id_OFX)
return lookup_cell_delay(cell->tmg_index, fromPort, toPort, delay);
}
} else if (is_dsp_cell(cell)) {
if (fromPort == id_CLK)
return false; // don't include delays that are actually clock-to-out here
return lookup_cell_delay(cell->tmg_index, lookup_port(fromPort), lookup_port(toPort), delay);
}
return false;
}
TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const
{
auto disconnected = [cell](IdString p) { return !cell->ports.count(p) || cell->ports.at(p).net == nullptr; };
auto lookup_port = [&](IdString p) {
auto fnd = cell->tmg_portmap.find(p);
return fnd == cell->tmg_portmap.end() ? p : fnd->second;
};
clockInfoCount = 0;
if (cell->type == id_OXIDE_COMB) {
if (port == id_A || port == id_B || port == id_C || port == id_D || port == id_SEL || port == id_F1 ||
port == id_FCI || port == id_WDI)
return TMG_COMB_INPUT;
if (port == id_F || port == id_OFX || port == id_FCO) {
if (disconnected(id_A) && disconnected(id_B) && disconnected(id_C) && disconnected(id_D) &&
disconnected(id_FCI) && disconnected(id_SEL) && disconnected(id_WDI))
return TMG_IGNORE;
else
return TMG_COMB_OUTPUT;
}
} else if (cell->type == id_OXIDE_FF) {
if (port == id_CLK)
return TMG_CLOCK_INPUT;
else if (port == id_Q) {
clockInfoCount = 1;
return TMG_REGISTER_OUTPUT;
} else {
clockInfoCount = 1;
return TMG_REGISTER_INPUT;
}
} else if (cell->type == id_RAMW) {
if (port == id_CLK)
return TMG_CLOCK_INPUT;
else if (port == id_WDO0 || port == id_WDO1 || port == id_WDO2 || port == id_WDO3) {
clockInfoCount = 1;
return TMG_REGISTER_OUTPUT;
} else if (port == id_A0 || port == id_A1 || port == id_B0 || port == id_B1 || port == id_C0 || port == id_C1 ||
port == id_D0 || port == id_D1) {
clockInfoCount = 1;
return TMG_REGISTER_INPUT;
}
} else if (cell->type == id_OXIDE_EBR) {
if (port == id_DWS0 || port == id_DWS1 || port == id_DWS2 || port == id_DWS3 || port == id_DWS4)
return TMG_IGNORE;
if (port == id_CLKA || port == id_CLKB)
return TMG_CLOCK_INPUT;
clockInfoCount = 1;
return (cell->ports.at(port).type == PORT_IN) ? TMG_REGISTER_INPUT : TMG_REGISTER_OUTPUT;
} else if (cell->type == id_MULT18_CORE || cell->type == id_MULT18X36_CORE || cell->type == id_MULT36_CORE) {
return (cell->ports.at(port).type == PORT_IN) ? TMG_COMB_INPUT : TMG_COMB_OUTPUT;
} else if (cell->type == id_PREADD9_CORE || cell->type == id_REG18_CORE || cell->type == id_MULT9_CORE) {
if (port == id_CLK)
return TMG_CLOCK_INPUT;
auto type = lookup_port_type(cell->tmg_index, lookup_port(port), cell->ports.at(port).type, id_CLK);
if (type == TMG_REGISTER_INPUT || type == TMG_REGISTER_OUTPUT)
clockInfoCount = 1;
return type;
}
return TMG_IGNORE;
}
TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port, int index) const
{
auto lookup_port = [&](IdString p) {
auto fnd = cell->tmg_portmap.find(p);
return fnd == cell->tmg_portmap.end() ? p : fnd->second;
};
TimingClockingInfo info;
if (cell->type == id_OXIDE_FF) {
info.edge = (cell->ffInfo.ctrlset.clkmux == ID_INV) ? FALLING_EDGE : RISING_EDGE;
info.clock_port = id_CLK;
if (port == id_Q)
NPNR_ASSERT(lookup_cell_delay(cell->tmg_index, id_CLK, port, info.clockToQ));
else
lookup_cell_setuphold(cell->tmg_index, port, id_CLK, info.setup, info.hold);
} else if (cell->type == id_RAMW) {
info.edge = (cell->ffInfo.ctrlset.clkmux == ID_INV) ? FALLING_EDGE : RISING_EDGE;
info.clock_port = id_CLK;
if (port == id_WDO0 || port == id_WDO1 || port == id_WDO2 || port == id_WDO3)
NPNR_ASSERT(lookup_cell_delay(cell->tmg_index, id_CLK, port, info.clockToQ));
else
lookup_cell_setuphold(cell->tmg_index, port, id_CLK, info.setup, info.hold);
} else if (cell->type == id_OXIDE_EBR) {
if (cell->ports.at(port).type == PORT_IN) {
lookup_cell_setuphold_clock(cell->tmg_index, lookup_port(port), info.clock_port, info.setup, info.hold);
} else {
lookup_cell_clock_out(cell->tmg_index, lookup_port(port), info.clock_port, info.clockToQ);
}
// Lookup edge based on inversion
info.edge = (get_cell_pinmux(cell, info.clock_port) == PINMUX_INV) ? FALLING_EDGE : RISING_EDGE;
} else if (cell->type == id_PREADD9_CORE || cell->type == id_REG18_CORE || cell->type == id_MULT9_CORE) {
info.clock_port = id_CLK;
if (cell->ports.at(port).type == PORT_IN) {
lookup_cell_setuphold(cell->tmg_index, lookup_port(port), id_CLK, info.setup, info.hold);
} else {
NPNR_ASSERT(lookup_cell_delay(cell->tmg_index, id_CLK, lookup_port(port), info.clockToQ));
}
info.edge = (get_cell_pinmux(cell, info.clock_port) == PINMUX_INV) ? FALLING_EDGE : RISING_EDGE;
} else {
NPNR_ASSERT_FALSE("missing clocking info");
}
return info;
}
// -----------------------------------------------------------------------
delay_t Arch::estimateDelay(WireId src, WireId dst) const
{
int src_x = src.tile % chip_info->width, src_y = src.tile / chip_info->width;
int dst_x = dst.tile % chip_info->width, dst_y = dst.tile / chip_info->width;
int dist_x = std::abs(src_x - dst_x);
int dist_y = std::abs(src_y - dst_y);
return 75 * dist_x + 75 * dist_y + 200;
}
delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const
{
if (net_info->driver.cell == nullptr || net_info->driver.cell->bel == BelId() || sink.cell->bel == BelId())
return 0;
if (sink.port == id_FCI)
return 0;
int src_x = net_info->driver.cell->bel.tile % chip_info->width,
src_y = net_info->driver.cell->bel.tile / chip_info->width;
int dst_x = sink.cell->bel.tile % chip_info->width, dst_y = sink.cell->bel.tile / chip_info->width;
int dist_x = std::abs(src_x - dst_x);
int dist_y = std::abs(src_y - dst_y);
return 100 * dist_x + 100 * dist_y + 250;
}
bool Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const { return false; }
ArcBounds Arch::getRouteBoundingBox(WireId src, WireId dst) const
{
ArcBounds bb;
int src_x = src.tile % chip_info->width, src_y = src.tile / chip_info->width;
int dst_x = dst.tile % chip_info->width, dst_y = dst.tile / chip_info->width;
bb.x0 = src_x;
bb.y0 = src_y;
bb.x1 = src_x;
bb.y1 = src_y;
auto extend = [&](int x, int y) {
bb.x0 = std::min(bb.x0, x);
bb.x1 = std::max(bb.x1, x);
bb.y0 = std::min(bb.y0, y);
bb.y1 = std::max(bb.y1, y);
};
extend(dst_x, dst_y);
if (dsp_wires.count(src) || dsp_wires.count(dst)) {
bb.x0 = std::max<int>(0, bb.x0 - 6);
bb.x1 = std::min<int>(chip_info->width, bb.x1 + 6);
}
return bb;
}
// -----------------------------------------------------------------------
bool Arch::place()
{
std::string placer = str_or_default(settings, id("placer"), defaultPlacer);
if (placer == "heap") {
PlacerHeapCfg cfg(getCtx());
cfg.ioBufTypes.insert(id_SEIO33_CORE);
cfg.ioBufTypes.insert(id_SEIO18_CORE);
cfg.ioBufTypes.insert(id_OSC_CORE);
cfg.cellGroups.emplace_back();
cfg.cellGroups.back().insert(id_OXIDE_COMB);
cfg.cellGroups.back().insert(id_OXIDE_FF);
cfg.beta = 0.5;
cfg.criticalityExponent = 7;
if (!placer_heap(getCtx(), cfg))
return false;
} else if (placer == "sa") {
if (!placer1(getCtx(), Placer1Cfg(getCtx())))
return false;
} else {
log_error("Nexus architecture does not support placer '%s'\n", placer.c_str());
}
post_place_opt();
getCtx()->attrs[getCtx()->id("step")] = std::string("place");
archInfoToAttributes();
return true;
}
void Arch::pre_routing()
{
for (auto cell : sorted(cells)) {
CellInfo *ci = cell.second;
if (ci->type == id_MULT9_CORE || ci->type == id_PREADD9_CORE || ci->type == id_MULT18_CORE ||
ci->type == id_MULT18X36_CORE || ci->type == id_MULT36_CORE || ci->type == id_REG18_CORE ||
ci->type == id_ACC54_CORE) {
for (auto port : sorted_ref(ci->ports)) {
WireId wire = getBelPinWire(ci->bel, port.first);
if (wire != WireId())
dsp_wires.insert(wire);
}
}
}
}
bool Arch::route()
{
assign_budget(getCtx(), true);
pre_routing();
route_globals();
std::string router = str_or_default(settings, id("router"), defaultRouter);
bool result;
if (router == "router1") {
result = router1(getCtx(), Router1Cfg(getCtx()));
} else if (router == "router2") {
router2(getCtx(), Router2Cfg(getCtx()));
result = true;
} else {
log_error("iCE40 architecture does not support router '%s'\n", router.c_str());
}
getCtx()->attrs[getCtx()->id("step")] = std::string("route");
archInfoToAttributes();
return result;
}
// -----------------------------------------------------------------------
CellPinMux Arch::get_cell_pinmux(const CellInfo *cell, IdString pin) const
{
IdString param = id(stringf("%sMUX", pin.c_str(this)));
auto fnd_param = cell->params.find(param);
if (fnd_param == cell->params.end())
return PINMUX_SIG;
const std::string &pm = fnd_param->second.as_string();
if (pm == "0")
return PINMUX_0;
else if (pm == "1")
return PINMUX_1;
else if (pm == "INV")
return PINMUX_INV;
else if (pm == pin.c_str(this))
return PINMUX_SIG;
else {
log_error("Invalid %s setting '%s' for cell '%s'\n", nameOf(param), pm.c_str(), nameOf(cell));
NPNR_ASSERT_FALSE("unreachable");
}
}
void Arch::set_cell_pinmux(CellInfo *cell, IdString pin, CellPinMux state)
{
IdString param = id(stringf("%sMUX", pin.c_str(this)));
switch (state) {
case PINMUX_SIG:
cell->params.erase(param);
break;
case PINMUX_0:
cell->params[param] = std::string("0");
break;
case PINMUX_1:
cell->params[param] = std::string("1");
break;
case PINMUX_INV:
cell->params[param] = std::string("INV");
break;
default:
NPNR_ASSERT_FALSE("unreachable");
}
}
// -----------------------------------------------------------------------
const PadInfoPOD *Arch::get_pkg_pin_data(const std::string &pin) const
{
for (size_t i = 0; i < chip_info->num_pads; i++) {
const PadInfoPOD *pad = &(chip_info->pads[i]);
if (pin == pad->pins[package_idx].get())
return pad;
}
return nullptr;
}
Loc Arch::get_pad_loc(const PadInfoPOD *pad) const
{
Loc loc;
switch (pad->side) {
case PIO_LEFT:
loc.x = 0;
loc.y = pad->offset;
break;
case PIO_RIGHT:
loc.x = chip_info->width - 1;
loc.y = pad->offset;
break;
case PIO_TOP:
loc.x = pad->offset;
loc.y = 0;
break;
case PIO_BOTTOM:
loc.x = pad->offset;
loc.y = chip_info->height - 1;
}
loc.z = pad->pio_index;
return loc;
}
BelId Arch::get_pad_pio_bel(const PadInfoPOD *pad) const
{
if (pad == nullptr)
return BelId();
return getBelByLocation(get_pad_loc(pad));
}
const PadInfoPOD *Arch::get_bel_pad(BelId bel) const
{
Loc loc = getBelLocation(bel);
int side = -1, offset = -1;
// Convert (x, y) to (side, offset)
if (loc.x == 0) {
side = PIO_LEFT;
offset = loc.y;
} else if (loc.x == (chip_info->width - 1)) {
side = PIO_RIGHT;
offset = loc.y;
} else if (loc.y == 0) {
side = PIO_TOP;
offset = loc.x;
} else if (loc.y == (chip_info->height - 1)) {
side = PIO_BOTTOM;
offset = loc.x;
} else {
return nullptr;
}
// Lookup in the list of pads
for (size_t i = 0; i < chip_info->num_pads; i++) {
const PadInfoPOD *pad = &(chip_info->pads[i]);
if (pad->side == side && pad->offset == offset && pad->pio_index == loc.z)
return pad;
}
return nullptr;
}
std::string Arch::get_pad_functions(const PadInfoPOD *pad) const
{
std::string s;
for (size_t i = 0; i < pad->num_funcs; i++) {
if (!s.empty())
s += '/';
s += IdString(pad->func_strs[i]).str(this);
}
return s;
}
// -----------------------------------------------------------------------
// Helper for cell timing lookups
namespace {
template <typename Tres, typename Tgetter, typename Tkey>
int db_binary_search(const Tres *list, int count, Tgetter key_getter, Tkey key)
{
if (count < 7) {
for (int i = 0; i < count; i++) {
if (key_getter(list[i]) == key) {
return i;
}
}
} else {
int b = 0, e = count - 1;
while (b <= e) {
int i = (b + e) / 2;
if (key_getter(list[i]) == key) {
return i;
}
if (key_getter(list[i]) > key)
e = i - 1;
else
b = i + 1;
}
}
return -1;
}
} // namespace
bool Arch::is_dsp_cell(const CellInfo *cell) const
{
return cell->type == id_MULT18_CORE || cell->type == id_MULT18X36_CORE || cell->type == id_MULT36_CORE ||
cell->type == id_PREADD9_CORE || cell->type == id_REG18_CORE || cell->type == id_MULT9_CORE;
}
int Arch::get_cell_timing_idx(IdString cell_type, IdString cell_variant) const
{
return db_binary_search(
speed_grade->cell_types.get(), speed_grade->num_cell_types,
[](const CellTimingPOD &ct) { return std::make_pair(ct.cell_type, ct.cell_variant); },
std::make_pair(cell_type.index, cell_variant.index));
}
bool Arch::lookup_cell_delay(int type_idx, IdString from_port, IdString to_port, DelayInfo &delay) const
{
NPNR_ASSERT(type_idx != -1);
const auto &ct = speed_grade->cell_types[type_idx];
int dly_idx = db_binary_search(
ct.prop_delays.get(), ct.num_prop_delays,
[](const CellPropDelayPOD &pd) { return std::make_pair(pd.to_port, pd.from_port); },
std::make_pair(to_port.index, from_port.index));
if (dly_idx == -1)
return false;
delay.min_delay = ct.prop_delays[dly_idx].min_delay;
delay.max_delay = ct.prop_delays[dly_idx].max_delay;
return true;
}
void Arch::lookup_cell_setuphold(int type_idx, IdString from_port, IdString clock, DelayInfo &setup,
DelayInfo &hold) const
{
NPNR_ASSERT(type_idx != -1);
const auto &ct = speed_grade->cell_types[type_idx];
int dly_idx = db_binary_search(
ct.setup_holds.get(), ct.num_setup_holds,
[](const CellSetupHoldPOD &sh) { return std::make_pair(sh.sig_port, sh.clock_port); },
std::make_pair(from_port.index, clock.index));
NPNR_ASSERT(dly_idx != -1);
setup.min_delay = ct.setup_holds[dly_idx].min_setup;
setup.max_delay = ct.setup_holds[dly_idx].max_setup;
hold.min_delay = ct.setup_holds[dly_idx].min_hold;
hold.max_delay = ct.setup_holds[dly_idx].max_hold;
}
void Arch::lookup_cell_setuphold_clock(int type_idx, IdString from_port, IdString &clock, DelayInfo &setup,
DelayInfo &hold) const
{
NPNR_ASSERT(type_idx != -1);
const auto &ct = speed_grade->cell_types[type_idx];
int dly_idx = db_binary_search(
ct.setup_holds.get(), ct.num_setup_holds, [](const CellSetupHoldPOD &sh) { return sh.sig_port; },
from_port.index);
NPNR_ASSERT(dly_idx != -1);
clock = IdString(ct.setup_holds[dly_idx].clock_port);
setup.min_delay = ct.setup_holds[dly_idx].min_setup;
setup.max_delay = ct.setup_holds[dly_idx].max_setup;
hold.min_delay = ct.setup_holds[dly_idx].min_hold;
hold.max_delay = ct.setup_holds[dly_idx].max_hold;
}
void Arch::lookup_cell_clock_out(int type_idx, IdString to_port, IdString &clock, DelayInfo &delay) const
{
NPNR_ASSERT(type_idx != -1);
const auto &ct = speed_grade->cell_types[type_idx];
int dly_idx = db_binary_search(
ct.prop_delays.get(), ct.num_prop_delays, [](const CellPropDelayPOD &pd) { return pd.to_port; },
to_port.index);
NPNR_ASSERT(dly_idx != -1);
clock = ct.prop_delays[dly_idx].from_port;
delay.min_delay = ct.prop_delays[dly_idx].min_delay;
delay.max_delay = ct.prop_delays[dly_idx].max_delay;
}
TimingPortClass Arch::lookup_port_type(int type_idx, IdString port, PortType dir, IdString clock) const
{
if (dir == PORT_IN) {
NPNR_ASSERT(type_idx != -1);
const auto &ct = speed_grade->cell_types[type_idx];
// If a setup-hold entry exists, then this is a register input
int sh_idx = db_binary_search(
ct.setup_holds.get(), ct.num_setup_holds,
[](const CellSetupHoldPOD &sh) { return std::make_pair(sh.sig_port, sh.clock_port); },
std::make_pair(port.index, clock.index));
return (sh_idx != -1) ? TMG_REGISTER_INPUT : TMG_COMB_INPUT;
} else {
DelayInfo dly;
// If a clock-to-out entry exists, then this is a register output
return lookup_cell_delay(type_idx, clock, port, dly) ? TMG_REGISTER_OUTPUT : TMG_COMB_OUTPUT;
}
}
// -----------------------------------------------------------------------
#ifdef WITH_HEAP
const std::string Arch::defaultPlacer = "heap";
#else
const std::string Arch::defaultPlacer = "sa";
#endif
const std::vector<std::string> Arch::availablePlacers = {"sa",
#ifdef WITH_HEAP
"heap"
#endif
};
const std::string Arch::defaultRouter = "router2";
const std::vector<std::string> Arch::availableRouters = {"router1", "router2"};
NEXTPNR_NAMESPACE_END

1586
nexus/arch.h Normal file

File diff suppressed because it is too large Load Diff

118
nexus/arch_place.cc Normal file
View File

@ -0,0 +1,118 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2020 David Shah <dave@ds0.me>
*
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include "log.h"
#include "nextpnr.h"
NEXTPNR_NAMESPACE_BEGIN
bool Arch::nexus_logic_tile_valid(LogicTileStatus &lts) const
{
for (int s = 0; s < 4; s++) {
if (lts.slices[s].dirty) {
lts.slices[s].valid = false;
lts.slices[s].dirty = false;
CellInfo *lut0 = lts.cells[(s << 3) | BEL_LUT0];
CellInfo *lut1 = lts.cells[(s << 3) | BEL_LUT1];
CellInfo *ff0 = lts.cells[(s << 3) | BEL_FF0];
CellInfo *ff1 = lts.cells[(s << 3) | BEL_FF1];
if (s == 2) {
CellInfo *ramw = lts.cells[(s << 3) | BEL_RAMW];
// Nothing else in SLICEC can be used if the RAMW is used
if (ramw != nullptr) {
if (lut0 != nullptr || lut1 != nullptr || ff0 != nullptr || ff1 != nullptr)
return false;
}
}
if (lut0 != nullptr) {
// Check for overuse of M signal
if (lut0->lutInfo.mux2_used && ff0 != nullptr && ff0->ffInfo.m != nullptr)
return false;
}
// Check for correct use of FF0 DI
if (ff0 != nullptr && ff0->ffInfo.di != nullptr &&
(lut0 == nullptr || (ff0->ffInfo.di != lut0->lutInfo.f && ff0->ffInfo.di != lut0->lutInfo.ofx)))
return false;
if (lut1 != nullptr) {
// LUT1 cannot contain a MUX2
if (lut1->lutInfo.mux2_used)
return false;
// If LUT1 is carry then LUT0 must be carry too
if (lut1->lutInfo.is_carry && (lut0 == nullptr || !lut0->lutInfo.is_carry))
return false;
if (!lut1->lutInfo.is_carry && lut0 != nullptr && lut0->lutInfo.is_carry)
return false;
}
// Check for correct use of FF1 DI
if (ff1 != nullptr && ff1->ffInfo.di != nullptr && (lut1 == nullptr || ff1->ffInfo.di != lut1->lutInfo.f))
return false;
lts.slices[s].valid = true;
} else if (!lts.slices[s].valid) {
return false;
}
}
for (int h = 0; h < 2; h++) {
if (lts.halfs[h].dirty) {
bool found_ff = false;
FFControlSet ctrlset;
for (int i = 0; i < 2; i++) {
for (auto bel : {BEL_FF0, BEL_FF1, BEL_RAMW}) {
if (bel == BEL_RAMW && (h != 1 || i != 0))
continue;
CellInfo *ci = lts.cells[(h * 2 + i) << 3 | bel];
if (ci == nullptr)
continue;
if (!found_ff) {
ctrlset = ci->ffInfo.ctrlset;
found_ff = true;
} else if (ci->ffInfo.ctrlset != ctrlset) {
return false;
}
}
}
} else if (!lts.halfs[h].valid) {
return false;
}
}
return true;
}
bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const
{
// FIXME
return true;
}
bool Arch::isBelLocationValid(BelId bel) const
{
if (bel_tile_is(bel, LOC_LOGIC)) {
LogicTileStatus *lts = tileStatus[bel.tile].lts;
if (lts == nullptr)
return true;
else
return nexus_logic_tile_valid(*lts);
} else {
return true;
}
}
NEXTPNR_NAMESPACE_END

72
nexus/arch_pybindings.cc Normal file
View File

@ -0,0 +1,72 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 Clifford Wolf <clifford@symbioticeda.com>
* Copyright (C) 2018 David Shah <david@symbioticeda.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#ifndef NO_PYTHON
#include "arch_pybindings.h"
#include "nextpnr.h"
#include "pybindings.h"
NEXTPNR_NAMESPACE_BEGIN
void arch_wrap_python(py::module &m)
{
using namespace PythonConversion;
py::class_<ArchArgs>(m, "ArchArgs").def_readwrite("device", &ArchArgs::device);
py::class_<BelId>(m, "BelId").def_readwrite("index", &BelId::index).def_readwrite("tile", &BelId::tile);
py::class_<WireId>(m, "WireId").def_readwrite("index", &WireId::index).def_readwrite("tile", &WireId::tile);
py::class_<PipId>(m, "PipId").def_readwrite("index", &PipId::index).def_readwrite("tile", &PipId::tile);
py::class_<BelPin>(m, "BelPin").def_readwrite("bel", &BelPin::bel).def_readwrite("pin", &BelPin::pin);
auto arch_cls = py::class_<Arch, BaseCtx>(m, "Arch").def(py::init<ArchArgs>());
auto ctx_cls = py::class_<Context, Arch>(m, "Context")
.def("checksum", &Context::checksum)
.def("pack", &Context::pack)
.def("place", &Context::place)
.def("route", &Context::route);
typedef std::unordered_map<IdString, std::unique_ptr<CellInfo>> CellMap;
typedef std::unordered_map<IdString, std::unique_ptr<NetInfo>> NetMap;
typedef std::unordered_map<IdString, HierarchicalCell> HierarchyMap;
typedef std::unordered_map<IdString, IdString> AliasMap;
typedef UpDownhillPipRange PipRange;
typedef WireBelPinRange BelPinRange;
#include "arch_pybindings_shared.h"
WRAP_RANGE(m, Bel, conv_to_str<BelId>);
WRAP_RANGE(m, Wire, conv_to_str<WireId>);
WRAP_RANGE(m, AllPip, conv_to_str<PipId>);
WRAP_RANGE(m, UpDownhillPip, conv_to_str<PipId>);
WRAP_RANGE(m, WireBelPin, wrap_context<BelPin>);
WRAP_MAP_UPTR(m, CellMap, "IdCellMap");
WRAP_MAP_UPTR(m, NetMap, "IdNetMap");
WRAP_MAP(m, HierarchyMap, wrap_context<HierarchicalCell &>, "HierarchyMap");
}
NEXTPNR_NAMESPACE_END
#endif // NO_PYTHON

98
nexus/arch_pybindings.h Normal file
View File

@ -0,0 +1,98 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2020 David Shah <dave@ds0.me>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#ifndef ARCH_PYBINDINGS_H
#define ARCH_PYBINDINGS_H
#ifndef NO_PYTHON
#include "nextpnr.h"
#include "pybindings.h"
#include "pywrappers.h"
NEXTPNR_NAMESPACE_BEGIN
namespace PythonConversion {
template <> struct string_converter<BelId>
{
BelId from_str(Context *ctx, std::string name) { return ctx->getBelByName(ctx->id(name)); }
std::string to_str(Context *ctx, BelId id)
{
if (id == BelId())
throw bad_wrap();
return ctx->getBelName(id).str(ctx);
}
};
template <> struct string_converter<WireId>
{
WireId from_str(Context *ctx, std::string name) { return ctx->getWireByName(ctx->id(name)); }
std::string to_str(Context *ctx, WireId id)
{
if (id == WireId())
throw bad_wrap();
return ctx->getWireName(id).str(ctx);
}
};
template <> struct string_converter<const WireId>
{
WireId from_str(Context *ctx, std::string name) { return ctx->getWireByName(ctx->id(name)); }
std::string to_str(Context *ctx, WireId id)
{
if (id == WireId())
throw bad_wrap();
return ctx->getWireName(id).str(ctx);
}
};
template <> struct string_converter<PipId>
{
PipId from_str(Context *ctx, std::string name) { return ctx->getPipByName(ctx->id(name)); }
std::string to_str(Context *ctx, PipId id)
{
if (id == PipId())
throw bad_wrap();
return ctx->getPipName(id).str(ctx);
}
};
template <> struct string_converter<BelPin>
{
BelPin from_str(Context *ctx, std::string name)
{
NPNR_ASSERT_FALSE("string_converter<BelPin>::from_str not implemented");
}
std::string to_str(Context *ctx, BelPin pin)
{
if (pin.bel == BelId())
throw bad_wrap();
return ctx->getBelName(pin.bel).str(ctx) + "/" + pin.pin.str(ctx);
}
};
} // namespace PythonConversion
NEXTPNR_NAMESPACE_END
#endif
#endif

253
nexus/archdefs.h Normal file
View File

@ -0,0 +1,253 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2020 David Shah <dave@ds0.me>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#ifndef NEXTPNR_H
#error Include "archdefs.h" via "nextpnr.h" only.
#endif
NEXTPNR_NAMESPACE_BEGIN
typedef int delay_t;
struct DelayInfo
{
delay_t min_delay = 0, max_delay = 0;
delay_t minRaiseDelay() const { return min_delay; }
delay_t maxRaiseDelay() const { return max_delay; }
delay_t minFallDelay() const { return min_delay; }
delay_t maxFallDelay() const { return max_delay; }
delay_t minDelay() const { return min_delay; }
delay_t maxDelay() const { return max_delay; }
DelayInfo operator+(const DelayInfo &other) const
{
DelayInfo ret;
ret.min_delay = this->min_delay + other.min_delay;
ret.max_delay = this->max_delay + other.max_delay;
return ret;
}
};
// https://bugreports.qt.io/browse/QTBUG-80789
#ifndef Q_MOC_RUN
enum ConstIds
{
ID_NONE
#define X(t) , ID_##t
#include "constids.inc"
#undef X
};
#define X(t) static constexpr auto id_##t = IdString(ID_##t);
#include "constids.inc"
#undef X
#endif
struct BelId
{
int32_t tile = -1;
// PIP index in tile
int32_t index = -1;
BelId() = default;
inline BelId(int32_t tile, int32_t index) : tile(tile), index(index){};
bool operator==(const BelId &other) const { return tile == other.tile && index == other.index; }
bool operator!=(const BelId &other) const { return tile != other.tile || index != other.index; }
bool operator<(const BelId &other) const
{
return tile < other.tile || (tile == other.tile && index < other.index);
}
};
struct WireId
{
int32_t tile = -1;
// Node wires: tile == -1; index = node index in chipdb
// Tile wires: tile != -1; index = wire index in tile
int32_t index = -1;
WireId() = default;
inline WireId(int32_t tile, int32_t index) : tile(tile), index(index){};
bool operator==(const WireId &other) const { return tile == other.tile && index == other.index; }
bool operator!=(const WireId &other) const { return tile != other.tile || index != other.index; }
bool operator<(const WireId &other) const
{
return tile < other.tile || (tile == other.tile && index < other.index);
}
};
struct PipId
{
int32_t tile = -1;
// PIP index in tile
int32_t index = -1;
PipId() = default;
inline PipId(int32_t tile, int32_t index) : tile(tile), index(index){};
bool operator==(const PipId &other) const { return tile == other.tile && index == other.index; }
bool operator!=(const PipId &other) const { return tile != other.tile || index != other.index; }
bool operator<(const PipId &other) const
{
return tile < other.tile || (tile == other.tile && index < other.index);
}
};
struct GroupId
{
enum : int8_t
{
TYPE_NONE,
} type = TYPE_NONE;
int8_t x = 0, y = 0;
bool operator==(const GroupId &other) const { return (type == other.type) && (x == other.x) && (y == other.y); }
bool operator!=(const GroupId &other) const { return (type != other.type) || (x != other.x) || (y == other.y); }
};
struct DecalId
{
enum : int8_t
{
TYPE_NONE,
TYPE_BEL,
TYPE_WIRE,
TYPE_PIP,
TYPE_GROUP
} type = TYPE_NONE;
int32_t index = -1;
bool active = false;
bool operator==(const DecalId &other) const
{
return (type == other.type) && (index == other.index) && (active == other.active);
}
bool operator!=(const DecalId &other) const
{
return (type != other.type) || (index != other.index) || (active != other.active);
}
};
struct ArchNetInfo
{
bool is_global;
bool is_clock, is_reset;
};
struct NetInfo;
struct FFControlSet
{
int clkmux, cemux, lsrmux;
bool async, regddr_en, gsr_en;
NetInfo *clk, *lsr, *ce;
};
inline bool operator!=(const FFControlSet &a, const FFControlSet &b)
{
return (a.clkmux != b.clkmux) || (a.cemux != b.cemux) || (a.lsrmux != b.lsrmux) || (a.async != b.async) ||
(a.regddr_en != b.regddr_en) || (a.gsr_en != b.gsr_en) || (a.clk != b.clk) || (a.lsr != b.lsr) ||
(a.ce != b.ce);
}
struct ArchCellInfo
{
union
{
struct
{
bool is_memory, is_carry, mux2_used;
NetInfo *f, *ofx;
} lutInfo;
struct
{
FFControlSet ctrlset;
NetInfo *di, *m;
} ffInfo;
};
int tmg_index = -1;
// Map from cell/bel ports to logical timing ports
std::unordered_map<IdString, IdString> tmg_portmap;
};
NEXTPNR_NAMESPACE_END
namespace std {
template <> struct hash<NEXTPNR_NAMESPACE_PREFIX BelId>
{
std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX BelId &bel) const noexcept
{
std::size_t seed = 0;
boost::hash_combine(seed, hash<int>()(bel.tile));
boost::hash_combine(seed, hash<int>()(bel.index));
return seed;
}
};
template <> struct hash<NEXTPNR_NAMESPACE_PREFIX WireId>
{
std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX WireId &wire) const noexcept
{
std::size_t seed = 0;
boost::hash_combine(seed, hash<int>()(wire.tile));
boost::hash_combine(seed, hash<int>()(wire.index));
return seed;
}
};
template <> struct hash<NEXTPNR_NAMESPACE_PREFIX PipId>
{
std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX PipId &pip) const noexcept
{
std::size_t seed = 0;
boost::hash_combine(seed, hash<int>()(pip.tile));
boost::hash_combine(seed, hash<int>()(pip.index));
return seed;
}
};
template <> struct hash<NEXTPNR_NAMESPACE_PREFIX GroupId>
{
std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX GroupId &group) const noexcept
{
std::size_t seed = 0;
boost::hash_combine(seed, hash<int>()(group.type));
boost::hash_combine(seed, hash<int>()(group.x));
boost::hash_combine(seed, hash<int>()(group.y));
return seed;
}
};
template <> struct hash<NEXTPNR_NAMESPACE_PREFIX DecalId>
{
std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX DecalId &decal) const noexcept
{
std::size_t seed = 0;
boost::hash_combine(seed, hash<int>()(decal.type));
boost::hash_combine(seed, hash<int>()(decal.index));
return seed;
}
};
} // namespace std

1
nexus/bba_version.inc Normal file
View File

@ -0,0 +1 @@
9

377
nexus/constids.inc Normal file
View File

@ -0,0 +1,377 @@
X(BEL)
X(OXIDE_FF)
X(CLK)
X(CE)
X(LSR)
X(DI)
X(M)
X(Q)
X(OXIDE_COMB)
X(A)
X(B)
X(C)
X(D)
X(F)
X(FCI)
X(FCO)
X(SEL)
X(F1)
X(OFX)
X(WAD0)
X(WAD1)
X(WAD2)
X(WAD3)
X(WDI)
X(WCK)
X(WRE)
X(RAMW)
X(A0)
X(A1)
X(B0)
X(B1)
X(C0)
X(C1)
X(D0)
X(D1)
X(WADO0)
X(WADO1)
X(WADO2)
X(WADO3)
X(WCKO)
X(WREO)
X(WDO0)
X(WDO1)
X(WDO2)
X(WDO3)
X(SEIO33_CORE)
X(T)
X(I)
X(O)
X(I3CRESEN)
X(I3CWKPU)
X(SEIO18_CORE)
X(DOLP)
X(INLP)
X(INADC)
X(DIFFIO18_CORE)
X(HSRXEN)
X(HSTXEN)
X(LUT4)
X(INIT)
X(Z)
X(WIDEFN9)
X(INIT0)
X(INIT1)
X(INV)
X(VHI)
X(VLO)
X(FD1P3BX)
X(FD1P3DX)
X(FD1P3IX)
X(FD1P3JX)
X(CK)
X(SP)
X(PD)
X(CD)
X(GSR)
X(CCU2)
X(CIN)
X(COUT)
X(S0)
X(S1)
X(F0)
X(CLKMUX)
X(CEMUX)
X(LSRMUX)
X(REGDDR)
X(SRMODE)
X(REGSET)
X(LSRMODE)
X(MODE)
X(INJECT)
X(PLC)
X(CIB)
X(CIB_T)
X(CIB_LR)
X(IO_TYPE)
X(OSCA)
X(OSC)
X(OSC_CORE)
X(HFCLKOUT)
X(LFCLKOUT)
X(HF_CLK_DIV)
X(HFOUTEN)
X(OXIDE_EBR)
X(CLKA)
X(CLKB)
X(CEA)
X(CEB)
X(CSA0)
X(CSA1)
X(CSA2)
X(CSB0)
X(CSB1)
X(CSB2)
X(ADA0)
X(ADA1)
X(ADA2)
X(ADA3)
X(ADB0)
X(ADB1)
X(WEA)
X(WEB)
X(RSTA)
X(RSTB)
X(LOC)
X(IB)
X(OB)
X(OBZ)
X(BB)
X(BB_I3C_A)
X(SEIO33)
X(SEIO18)
X(DIFFIO18)
X(IOPAD)
X(PADDO)
X(PADDI)
X(PADDT)
X(PREADD9_CORE)
X(MULT9_CORE)
X(MULT18_CORE)
X(REG18_CORE)
X(MULT18X36_CORE)
X(MULT36_CORE)
X(ACC54_CORE)
X(PREADD9)
X(MULT9)
X(MULT18)
X(REG18)
X(M18X36)
X(MULT36)
X(ACC54)
X(MULT9X9)
X(DCC)
X(CLKI)
X(CLKO)
X(DPR16X4)
X(INITVAL)
X(DPRAM)
X(DP16K)
X(PDP16K)
X(PDPSC16K)
X(SP16K)
X(FIFO16K)
X(DP16K_MODE)
X(PDP16K_MODE)
X(PDPSC16K_MODE)
X(SP16K_MODE)
X(FIFO16K_MODE)
X(DPSC512K)
X(PDPSC512K)
X(SP512K)
X(DPSC512K_MODE)
X(PDPSC512K_MODE)
X(SP512K_MODE)
X(WID)
X(CSDECODE_A)
X(CSDECODE_B)
X(CSDECODE_R)
X(CSDECODE_W)
X(CSDECODE)
X(CLKW)
X(CLKR)
X(CEW)
X(CER)
X(RST)
X(DWS0)
X(DWS1)
X(DWS2)
X(DWS3)
X(DWS4)
X(WEAMUX)
X(VCC_DRV)
X(RSTCL)
X(CECL)
X(B2)
X(B3)
X(B4)
X(B5)
X(B6)
X(B7)
X(B8)
X(BSIGNED)
X(C2)
X(C3)
X(C4)
X(C5)
X(C6)
X(C7)
X(C8)
X(C9)
X(RSTP)
X(CEP)
X(A2)
X(A3)
X(A4)
X(A5)
X(A6)
X(A7)
X(A8)
X(ASIGNED)
X(SFTCTRL0)
X(SFTCTRL1)
X(SFTCTRL2)
X(SFTCTRL3)
X(ROUNDEN)
X(LOAD)
X(M9ADDSUB1)
X(M9ADDSUB0)
X(ADDSUB1)
X(ADDSUB0)
X(CEO)
X(RSTO)
X(CEC)
X(RSTC)
X(SIGNEDI)
X(CECIN)
X(CECTRL)
X(RSTCIN)
X(RSTCTRL)
X(SIGNEDSTATIC_EN)
X(SUBSTRACT_EN)
X(CSIGNED)
X(BSIGNED_OPERAND_EN)
X(BYPASS_PREADD9)
X(REGBYPSBR0)
X(REGBYPSBR1)
X(REGBYPSBL)
X(SHIFTBR)
X(SHIFTBL)
X(PREADDCAS_EN)
X(SR_18BITSHIFT_EN)
X(OPC)
X(RESET)
X(RESETMODE)
X(ASIGNED_OPERAND_EN)
X(BYPASS_MULT9)
X(REGBYPSA1)
X(REGBYPSA2)
X(REGBYPSB)
X(SHIFTA)
X(REGBYPS)
X(PP)
X(SIGNEDA)
X(SIGNEDB)
X(RSTOUT)
X(CEOUT)
X(REGINPUTA)
X(REGINPUTB)
X(REGOUTPUT)
X(MULT18X18)
X(ROUNDBIT)
X(ROUNDHALFUP)
X(ROUNDRTZI)
X(SFTEN)
X(MULT18X36)
X(MULT36X36H)
X(MULT36X36)
X(SIGNEDC)
X(REGINPUTC)
X(MULTPREADD9X9)
X(MULTPREADD18X18)
X(REGPIPELINE)
X(REGADDSUB)
X(REGLOADC)
X(REGLOADC2)
X(REGCIN)
X(ACC108CASCADE)
X(ACCUBYPS)
X(ACCUMODE)
X(ADDSUBSIGNREGBYPS1)
X(ADDSUBSIGNREGBYPS2)
X(ADDSUBSIGNREGBYPS3)
X(ADDSUB_CTRL)
X(CASCOUTREGBYPS)
X(CINREGBYPS1)
X(CINREGBYPS2)
X(CINREGBYPS3)
X(CONSTSEL)
X(CREGBYPS1)
X(CREGBYPS2)
X(CREGBYPS3)
X(DSPCASCADE)
X(LOADREGBYPS1)
X(LOADREGBYPS2)
X(LOADREGBYPS3)
X(M9ADDSUBREGBYPS1)
X(M9ADDSUBREGBYPS2)
X(M9ADDSUBREGBYPS3)
X(M9ADDSUB_CTRL)
X(OUTREGBYPS)
X(SIGN)
X(STATICOPCODE_EN)
X(PROGCONST)
X(MULTADDSUB18X18)
X(MULTADDSUB36X36)
X(CEPIPE)
X(RSTPIPE)
X(LOADC)
X(ADDSUB)
X(SIGNED)
X(SUM0)
X(SUM1)
X(CINPUT)

53
nexus/family.cmake Normal file
View File

@ -0,0 +1,53 @@
add_subdirectory(${family})
message(STATUS "Using Nexus chipdb: ${NEXUS_CHIPDB}")
set(chipdb_sources)
set(chipdb_binaries)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${family}/chipdb)
foreach(subfamily ${NEXUS_FAMILIES})
set(chipdb_bba ${NEXUS_CHIPDB}/chipdb-${subfamily}.bba)
set(chipdb_bin ${family}/chipdb/chipdb-${subfamily}.bin)
set(chipdb_cc ${family}/chipdb/chipdb-${subfamily}.cc)
if(BBASM_MODE STREQUAL "binary")
add_custom_command(
OUTPUT ${chipdb_bin}
COMMAND bbasm ${BBASM_ENDIAN_FLAG} ${chipdb_bba} ${chipdb_bin}
DEPENDS bbasm chipdb-${family}-bbas ${chipdb_bba})
list(APPEND chipdb_binaries ${chipdb_bin})
elseif(BBASM_MODE STREQUAL "embed")
add_custom_command(
OUTPUT ${chipdb_cc} ${chipdb_bin}
COMMAND bbasm ${BBASM_ENDIAN_FLAG} --e ${chipdb_bba} ${chipdb_cc} ${chipdb_bin}
DEPENDS bbasm chipdb-${family}-bbas ${chipdb_bba})
list(APPEND chipdb_sources ${chipdb_cc})
list(APPEND chipdb_binaries ${chipdb_bin})
elseif(BBASM_MODE STREQUAL "string")
add_custom_command(
OUTPUT ${chipdb_cc}
COMMAND bbasm ${BBASM_ENDIAN_FLAG} --c ${chipdb_bba} ${chipdb_cc}
DEPENDS bbasm chipdb-${family}-bbas ${chipdb_bba})
list(APPEND chipdb_sources ${chipdb_cc})
endif()
endforeach()
if(WIN32)
set(chipdb_rc ${CMAKE_CURRENT_BINARY_DIR}/${family}/resource/chipdb.rc)
list(APPEND chipdb_sources ${chipdb_rc})
file(WRITE ${chipdb_rc})
foreach(subfamily ${NEXUS_FAMILIES})
file(APPEND ${chipdb_rc}
"${family}/chipdb-${subfamily}.bin RCDATA \"${CMAKE_CURRENT_BINARY_DIR}/${family}/chipdb/chipdb-${subfamily}.bin\"")
endforeach()
endif()
add_custom_target(chipdb-${family}-bins DEPENDS ${chipdb_sources} ${chipdb_binaries})
add_library(chipdb-${family} OBJECT ${NEXUS_CHIPDB} ${chipdb_sources})
add_dependencies(chipdb-${family} chipdb-${family}-bins)
target_compile_options(chipdb-${family} PRIVATE -g0 -O0 -w)
target_compile_definitions(chipdb-${family} PRIVATE NEXTPNR_NAMESPACE=nextpnr_${family})
target_include_directories(chipdb-${family} PRIVATE ${family})
foreach(family_target ${family_targets})
target_sources(${family_target} PRIVATE $<TARGET_OBJECTS:chipdb-${family}>)
endforeach()

669
nexus/fasm.cc Normal file
View File

@ -0,0 +1,669 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2020 David Shah <dave@ds0.me>
*
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include "log.h"
#include "nextpnr.h"
#include "util.h"
#include <queue>
NEXTPNR_NAMESPACE_BEGIN
namespace {
struct NexusFasmWriter
{
const Context *ctx;
std::ostream &out;
std::vector<std::string> fasm_ctx;
NexusFasmWriter(const Context *ctx, std::ostream &out) : ctx(ctx), out(out) {}
// Add a 'dot' prefix to the FASM context stack
void push(const std::string &x) { fasm_ctx.push_back(x); }
// Remove a prefix from the FASM context stack
void pop() { fasm_ctx.pop_back(); }
// Remove N prefices from the FASM context stack
void pop(int N)
{
for (int i = 0; i < N; i++)
fasm_ctx.pop_back();
}
bool last_was_blank = true;
// Insert a blank line if the last wasn't blank
void blank()
{
if (!last_was_blank)
out << std::endl;
last_was_blank = true;
}
// Write out all prefices from the stack, interspersed with .
void write_prefix()
{
for (auto &x : fasm_ctx)
out << x << ".";
last_was_blank = false;
}
// Write a single config bit; if value is true
void write_bit(const std::string &name, bool value = true)
{
if (value) {
write_prefix();
out << name << std::endl;
}
}
// Write a FASM attribute
void write_attribute(const std::string &key, const std::string &value, bool str = true)
{
std::string qu = str ? "\"" : "";
out << "{ " << key << "=" << qu << value << qu << " }" << std::endl;
last_was_blank = false;
}
// Write a FASM comment
void write_comment(const std::string &cmt) { out << "# " << cmt << std::endl; }
// Write a FASM bitvector; optionally inverting the values in the process
void write_vector(const std::string &name, const std::vector<bool> &value, bool invert = false)
{
write_prefix();
out << name << " = " << int(value.size()) << "'b";
for (auto bit : boost::adaptors::reverse(value))
out << ((bit ^ invert) ? '1' : '0');
out << std::endl;
}
// Write a FASM bitvector given an integer value
void write_int_vector(const std::string &name, uint64_t value, int width, bool invert = false)
{
std::vector<bool> bits(width, false);
for (int i = 0; i < width; i++)
bits[i] = (value & (1ULL << i)) != 0;
write_vector(name, bits, invert);
}
// Write an int vector param
void write_int_vector_param(const CellInfo *cell, const std::string &name, uint64_t defval, int width,
bool invert = false)
{
uint64_t value = int_or_default(cell->params, ctx->id(name), defval);
std::vector<bool> bits(width, false);
for (int i = 0; i < width; i++)
bits[i] = (value & (1ULL << i)) != 0;
write_vector(stringf("%s[%d:0]", name.c_str(), width - 1), bits, invert);
}
// Look up an enum value in a cell's parameters and write it to the FASM in name.value format
void write_enum(const CellInfo *cell, const std::string &name, const std::string &defval = "")
{
auto fnd = cell->params.find(ctx->id(name));
if (fnd == cell->params.end()) {
if (!defval.empty())
write_bit(stringf("%s.%s", name.c_str(), defval.c_str()));
} else {
write_bit(stringf("%s.%s", name.c_str(), fnd->second.c_str()));
}
}
// Look up an IO attribute in the cell's attributes and write it to the FASM in name.value format
void write_ioattr(const CellInfo *cell, const std::string &name, const std::string &defval = "")
{
auto fnd = cell->attrs.find(ctx->id(name));
if (fnd == cell->attrs.end()) {
if (!defval.empty())
write_bit(stringf("%s.%s", name.c_str(), defval.c_str()));
} else {
write_bit(stringf("%s.%s", name.c_str(), fnd->second.c_str()));
}
}
void write_ioattr_postfix(const CellInfo *cell, const std::string &name, const std::string &postfix,
const std::string &defval = "")
{
auto fnd = cell->attrs.find(ctx->id(name));
if (fnd == cell->attrs.end()) {
if (!defval.empty())
write_bit(stringf("%s_%s.%s", name.c_str(), postfix.c_str(), defval.c_str()));
} else {
write_bit(stringf("%s_%s.%s", name.c_str(), postfix.c_str(), fnd->second.c_str()));
}
}
// Gets the full name of a tile
std::string tile_name(int loc, const PhysicalTileInfoPOD &tile)
{
int r = loc / ctx->chip_info->width;
int c = loc % ctx->chip_info->width;
return stringf("%sR%dC%d__%s", ctx->nameOf(tile.prefix), r, c, ctx->nameOf(tile.tiletype));
}
// Look up a tile by location index and tile type
const PhysicalTileInfoPOD &tile_by_type_and_loc(int loc, IdString type)
{
auto &ploc = ctx->chip_info->grid[loc];
for (int i = 0; i < ploc.num_phys_tiles; i++) {
if (ploc.phys_tiles[i].tiletype == type.index)
return ploc.phys_tiles[i];
}
log_error("No tile of type %s found at location R%dC%d", ctx->nameOf(type), loc / ctx->chip_info->width,
loc % ctx->chip_info->width);
}
// Gets the single tile at a location
const PhysicalTileInfoPOD &tile_at_loc(int loc)
{
auto &ploc = ctx->chip_info->grid[loc];
NPNR_ASSERT(ploc.num_phys_tiles == 1);
return ploc.phys_tiles[0];
}
// Escape an internal prjoxide name for FASM by replacing : with __
std::string escape_name(const std::string &name)
{
std::string escaped;
for (char c : name) {
if (c == ':')
escaped += "__";
else
escaped += c;
}
return escaped;
}
// Push a tile name onto the prefix stack
void push_tile(int loc, IdString tile_type) { push(tile_name(loc, tile_by_type_and_loc(loc, tile_type))); }
void push_tile(int loc) { push(tile_name(loc, tile_at_loc(loc))); }
// Push a bel name onto the prefix stack
void push_belname(BelId bel) { push(ctx->nameOf(ctx->bel_data(bel).name)); }
// Push the tile group name corresponding to a bel onto the prefix stack
void push_belgroup(BelId bel)
{
int r = bel.tile / ctx->chip_info->width;
int c = bel.tile % ctx->chip_info->width;
auto bel_data = ctx->bel_data(bel);
r += bel_data.rel_y;
c += bel_data.rel_x;
std::string s = stringf("R%dC%d_%s", r, c, ctx->nameOf(ctx->bel_data(bel).name));
push(s);
}
// Push a bel's group and name
void push_bel(BelId bel)
{
push_belgroup(bel);
fasm_ctx.back() += stringf(".%s", ctx->nameOf(ctx->bel_data(bel).name));
}
// Write out a pip in tile.dst.src format
void write_pip(PipId pip)
{
auto &pd = ctx->pip_data(pip);
if (pd.flags & PIP_FIXED_CONN)
return;
std::string tile = tile_name(pip.tile, tile_by_type_and_loc(pip.tile, pd.tile_type));
std::string source_wire = escape_name(ctx->pip_src_wire_name(pip).str(ctx));
std::string dest_wire = escape_name(ctx->pip_dst_wire_name(pip).str(ctx));
out << stringf("%s.PIP.%s.%s", tile.c_str(), dest_wire.c_str(), source_wire.c_str()) << std::endl;
}
// Write out all the pips corresponding to a net
void write_net(const NetInfo *net)
{
write_comment(stringf("Net %s", ctx->nameOf(net)));
std::set<PipId> sorted_pips;
for (auto &w : net->wires)
if (w.second.pip != PipId())
sorted_pips.insert(w.second.pip);
for (auto p : sorted_pips)
write_pip(p);
blank();
}
// Find the CIBMUX output for a signal
WireId find_cibmux(const CellInfo *cell, IdString pin)
{
WireId cursor = ctx->getBelPinWire(cell->bel, pin);
if (cursor == WireId())
return WireId();
for (int i = 0; i < 10; i++) {
std::string cursor_name = IdString(ctx->wire_data(cursor).name).str(ctx);
if (cursor_name.find("JCIBMUXOUT") == 0) {
return cursor;
}
for (PipId pip : ctx->getPipsUphill(cursor))
if (ctx->checkPipAvail(pip)) {
cursor = ctx->getPipSrcWire(pip);
break;
}
}
return WireId();
}
// Write out the mux config for a cell
void write_cell_muxes(const CellInfo *cell)
{
for (auto port : sorted_cref(cell->ports)) {
// Only relevant to inputs
if (port.second.type != PORT_IN)
continue;
auto pin_style = ctx->get_cell_pin_style(cell, port.first);
auto pin_mux = ctx->get_cell_pinmux(cell, port.first);
// Invertible pins
if (pin_style & PINOPT_INV) {
if (pin_mux == PINMUX_INV || pin_mux == PINMUX_0)
write_bit(stringf("%sMUX.INV", ctx->nameOf(port.first)));
else if (pin_mux == PINMUX_SIG)
write_bit(stringf("%sMUX.%s", ctx->nameOf(port.first), ctx->nameOf(port.first)));
}
// Pins that must be explictly enabled
if ((pin_style & PINBIT_GATED) && (pin_mux == PINMUX_SIG))
write_bit(stringf("%sMUX.%s", ctx->nameOf(port.first), ctx->nameOf(port.first)));
// Pins that must be explictly set to 1 rather than just left floating
if ((pin_style & PINBIT_1) && (pin_mux == PINMUX_1))
write_bit(stringf("%sMUX.1", ctx->nameOf(port.first)));
// Handle CIB muxes - these must be set such that floating pins really are floating to VCC and not connected
// to another CIB signal
if ((pin_style & PINBIT_CIBMUX) && port.second.net == nullptr) {
WireId cibmuxout = find_cibmux(cell, port.first);
if (cibmuxout != WireId()) {
write_comment(stringf("CIBMUX for unused pin %s", ctx->nameOf(port.first)));
bool found = false;
for (PipId pip : ctx->getPipsUphill(cibmuxout)) {
if (ctx->checkPipAvail(pip) && ctx->checkWireAvail(ctx->getPipSrcWire(pip))) {
write_pip(pip);
found = true;
break;
}
}
NPNR_ASSERT(found);
}
}
}
}
// Write config for an OXIDE_COMB cell
void write_comb(const CellInfo *cell)
{
BelId bel = cell->bel;
int z = ctx->bel_data(bel).z;
int k = z & 0x1;
char slice = 'A' + (z >> 3);
push_tile(bel.tile, id_PLC);
push(stringf("SLICE%c", slice));
if (cell->params.count(id_INIT))
write_int_vector(stringf("K%d.INIT[15:0]", k), int_or_default(cell->params, id_INIT, 0), 16);
if (cell->lutInfo.is_carry) {
write_bit("MODE.CCU2");
write_enum(cell, "CCU2.INJECT", "NO");
}
pop(2);
}
// Write config for an OXIDE_FF cell
void write_ff(const CellInfo *cell)
{
BelId bel = cell->bel;
int z = ctx->bel_data(bel).z;
int k = z & 0x1;
char slice = 'A' + (z >> 3);
push_tile(bel.tile, id_PLC);
push(stringf("SLICE%c", slice));
push(stringf("REG%d", k));
write_bit("USED.YES");
write_enum(cell, "REGSET", "RESET");
write_enum(cell, "LSRMODE", "LSR");
write_enum(cell, "SEL", "DF");
pop();
write_enum(cell, "REGDDR");
write_enum(cell, "SRMODE");
write_cell_muxes(cell);
pop(2);
}
// Write out config for an OXIDE_RAMW cell
void write_ramw(const CellInfo *cell)
{
BelId bel = cell->bel;
push_tile(bel.tile, id_PLC);
push("SLICEC");
write_bit("MODE.RAMW");
write_cell_muxes(cell);
pop(2);
}
std::unordered_set<BelId> used_io;
struct BankConfig
{
bool diff_used = false;
bool lvds_used = false;
bool slvs_used = false;
bool dphy_used = false;
};
std::map<int, BankConfig> bank_cfg;
// Write config for an SEIO33_CORE cell
void write_io33(const CellInfo *cell)
{
BelId bel = cell->bel;
used_io.insert(bel);
push_bel(bel);
const NetInfo *t = get_net_or_empty(cell, id_T);
auto tmux = ctx->get_cell_pinmux(cell, id_T);
bool is_input = false, is_output = false;
if (tmux == PINMUX_0) {
is_output = true;
} else if (tmux == PINMUX_1 || t == nullptr) {
is_input = true;
}
const char *iodir = is_input ? "INPUT" : (is_output ? "OUTPUT" : "BIDIR");
write_bit(stringf("BASE_TYPE.%s_%s", iodir, str_or_default(cell->attrs, id_IO_TYPE, "LVCMOS33").c_str()));
write_ioattr(cell, "PULLMODE", "NONE");
write_cell_muxes(cell);
pop();
}
// Write config for an SEIO18_CORE cell
void write_io18(const CellInfo *cell)
{
BelId bel = cell->bel;
used_io.insert(bel);
push_bel(bel);
push("SEIO18");
const NetInfo *t = get_net_or_empty(cell, id_T);
auto tmux = ctx->get_cell_pinmux(cell, id_T);
bool is_input = false, is_output = false;
if (tmux == PINMUX_0) {
is_output = true;
} else if (tmux == PINMUX_1 || t == nullptr) {
is_input = true;
}
const char *iodir = is_input ? "INPUT" : (is_output ? "OUTPUT" : "BIDIR");
write_bit(stringf("BASE_TYPE.%s_%s", iodir, str_or_default(cell->attrs, id_IO_TYPE, "LVCMOS18H").c_str()));
write_ioattr(cell, "PULLMODE", "NONE");
pop();
write_cell_muxes(cell);
pop();
}
// Write config for an SEIO18_CORE cell
void write_diffio18(const CellInfo *cell)
{
BelId bel = cell->bel;
Loc bel_loc = ctx->getBelLocation(bel);
for (int i = 0; i < 2; i++) {
// Mark both A and B pins as used
used_io.insert(ctx->getBelByLocation(Loc(bel_loc.x, bel_loc.y, i)));
}
push_belgroup(bel);
push("PIOA");
push("DIFFIO18");
auto &bank = bank_cfg[ctx->get_bel_pad(ctx->getBelByLocation(Loc(bel_loc.x, bel_loc.y, 0)))->bank];
bank.diff_used = true;
const NetInfo *t = get_net_or_empty(cell, id_T);
auto tmux = ctx->get_cell_pinmux(cell, id_T);
bool is_input = false, is_output = false;
if (tmux == PINMUX_0) {
is_output = true;
} else if (tmux == PINMUX_1 || t == nullptr) {
is_input = true;
}
const char *iodir = is_input ? "INPUT" : (is_output ? "OUTPUT" : "BIDIR");
std::string type = str_or_default(cell->attrs, id_IO_TYPE, "LVDS");
write_bit(stringf("BASE_TYPE.%s_%s", iodir, type.c_str()));
if (type == "LVDS") {
write_ioattr_postfix(cell, "DIFFDRIVE", "LVDS", "3P5");
bank.lvds_used = true;
} else if (type == "SLVS") {
write_ioattr_postfix(cell, "DIFFDRIVE", "SLVS", "2P0");
bank.slvs_used = true;
} else if (type == "MIPI_DPHY") {
write_ioattr_postfix(cell, "DIFFDRIVE", "MIPI_DPHY", "2P0");
bank.dphy_used = true;
}
write_ioattr(cell, "PULLMODE", "FAILSAFE");
write_ioattr(cell, "DIFFRESISTOR");
pop();
write_cell_muxes(cell);
pop(2);
}
// Write config for an OSC_CORE cell
void write_osc(const CellInfo *cell)
{
BelId bel = cell->bel;
push_tile(bel.tile);
push_belname(bel);
write_enum(cell, "HF_OSC_EN");
write_enum(cell, "HF_FABRIC_EN");
write_enum(cell, "HFDIV_FABRIC_EN", "ENABLED");
write_enum(cell, "LF_FABRIC_EN");
write_enum(cell, "LF_OUTPUT_EN");
write_enum(cell, "DEBUG_N", "DISABLED");
write_int_vector(stringf("HF_CLK_DIV[7:0]"), ctx->parse_lattice_param(cell, id_HF_CLK_DIV, 8, 0).intval, 8);
write_cell_muxes(cell);
pop(2);
}
// Write config for an OXIDE_EBR cell
void write_bram(const CellInfo *cell)
{
// EBR configuration
BelId bel = cell->bel;
push_bel(bel);
int wid = int_or_default(cell->params, id_WID, 0);
std::string mode = str_or_default(cell->params, id_MODE, "");
write_bit(stringf("MODE.%s_MODE", mode.c_str()));
write_enum(cell, "INIT_DATA", "STATIC");
write_enum(cell, "GSR", "DISABLED");
write_int_vector("WID[10:0]", wid, 11);
push(stringf("%s_MODE", mode.c_str()));
if (mode == "DP16K") {
write_int_vector_param(cell, "CSDECODE_A", 7, 3, true);
write_int_vector_param(cell, "CSDECODE_B", 7, 3, true);
write_enum(cell, "ASYNC_RST_RELEASE_A");
write_enum(cell, "ASYNC_RST_RELEASE_B");
write_enum(cell, "DATA_WIDTH_A");
write_enum(cell, "DATA_WIDTH_B");
write_enum(cell, "OUTREG_A");
write_enum(cell, "OUTREG_B");
write_enum(cell, "RESETMODE_A");
write_enum(cell, "RESETMODE_B");
} else if (mode == "PDP16K" || mode == "PDPSC16K") {
write_int_vector_param(cell, "CSDECODE_W", 7, 3, true);
write_int_vector_param(cell, "CSDECODE_R", 7, 3, true);
write_enum(cell, "ASYNC_RST_RELEASE");
write_enum(cell, "DATA_WIDTH_W");
write_enum(cell, "DATA_WIDTH_R");
write_enum(cell, "OUTREG");
write_enum(cell, "RESETMODE");
}
pop();
push("DP16K_MODE"); // muxes always use the DP16K perspective
write_cell_muxes(cell);
pop(2);
blank();
// EBR initialisation
if (wid > 0) {
push(stringf("IP_EBR_WID%d", wid));
for (int i = 0; i < 64; i++) {
IdString param = ctx->id(stringf("INITVAL_%02X", i));
if (!cell->params.count(param))
continue;
auto &prop = cell->params.at(param);
std::string value;
if (prop.is_string) {
NPNR_ASSERT(prop.str.substr(0, 2) == "0x");
// Lattice-style hex string
value = prop.str.substr(2);
value = stringf("320'h%s", value.c_str());
} else {
// True Verilog bitvector
value = stringf("320'b%s", prop.str.c_str());
}
write_bit(stringf("INITVAL_%02X[319:0] = %s", i, value.c_str()));
}
pop();
}
}
bool is_mux_param(const std::string &key)
{
return (key.size() >= 3 && (key.compare(key.size() - 3, 3, "MUX") == 0));
}
// Write config for some kind of DSP cell
void write_dsp(const CellInfo *cell)
{
BelId bel = cell->bel;
push_bel(bel);
if (cell->type != id_MULT18_CORE && cell->type != id_MULT18X36_CORE && cell->type != id_MULT36_CORE)
write_bit(stringf("MODE.%s", ctx->nameOf(cell->type)));
for (auto param : sorted_cref(cell->params)) {
const std::string &param_name = param.first.str(ctx);
if (is_mux_param(param_name))
continue;
if (param.first == id_ROUNDBIT) {
// currently unsupported in oxide, but appears rarely used
NPNR_ASSERT(param.second.as_string() == "ROUND_TO_BIT0");
continue;
}
write_enum(cell, param_name);
}
write_cell_muxes(cell);
pop();
}
// Write out FASM for unused bels where needed
void write_unused()
{
write_comment("# Unused bels");
// DSP primitives are configured to a default mode; even if unused
static const std::unordered_map<IdString, std::vector<std::string>> dsp_defconf = {
{id_MULT9_CORE,
{
"GSR.ENABLED",
"MODE.NONE",
"RSTAMUX.RSTA",
"RSTPMUX.RSTP",
}},
{id_PREADD9_CORE,
{
"GSR.ENABLED",
"MODE.NONE",
"RSTBMUX.RSTB",
"RSTCLMUX.RSTCL",
}},
{id_REG18_CORE,
{
"GSR.ENABLED",
"MODE.NONE",
"RSTPMUX.RSTP",
}},
{id_ACC54_CORE,
{
"ACCUBYPS.BYPASS",
"MODE.NONE",
}},
};
for (BelId bel : ctx->getBels()) {
IdString type = ctx->getBelType(bel);
if (type == id_SEIO33_CORE && !used_io.count(bel)) {
push_bel(bel);
write_bit("BASE_TYPE.NONE");
pop();
blank();
} else if (type == id_SEIO18_CORE && !used_io.count(bel)) {
push_bel(bel);
push("SEIO18");
write_bit("BASE_TYPE.NONE");
pop(2);
blank();
} else if (dsp_defconf.count(type) && ctx->getBoundBelCell(bel) == nullptr) {
push_bel(bel);
for (const auto &cbit : dsp_defconf.at(type))
write_bit(cbit);
pop();
blank();
}
}
}
// Write out placeholder bankref config
void write_bankcfg()
{
for (int i = 0; i < 8; i++) {
if (i >= 3 && i <= 5) {
// 1.8V banks
push(stringf("GLOBAL.BANK%d", i));
auto &bank = bank_cfg[i];
write_bit("DIFF_IO.ON", bank.diff_used);
write_bit("LVDS_IO.ON", bank.lvds_used);
write_bit("SLVS_IO.ON", bank.slvs_used);
write_bit("MIPI_DPHY_IO.ON", bank.dphy_used);
pop();
} else {
// 3.3V banks, this should eventually be set based on the bank config
write_bit(stringf("GLOBAL.BANK%d.VCC.3V3", i));
}
}
blank();
}
// Write out FASM for the whole design
void operator()()
{
// Write device config
write_attribute("oxide.device", ctx->device);
write_attribute("oxide.device_variant", ctx->variant);
blank();
// Write routing
for (auto n : sorted(ctx->nets)) {
write_net(n.second);
}
// Write cell config
for (auto c : sorted(ctx->cells)) {
const CellInfo *ci = c.second;
write_comment(stringf("# Cell %s", ctx->nameOf(ci)));
if (ci->type == id_OXIDE_COMB)
write_comb(ci);
else if (ci->type == id_OXIDE_FF)
write_ff(ci);
else if (ci->type == id_RAMW)
write_ramw(ci);
else if (ci->type == id_SEIO33_CORE)
write_io33(ci);
else if (ci->type == id_SEIO18_CORE)
write_io18(ci);
else if (ci->type == id_DIFFIO18_CORE)
write_diffio18(ci);
else if (ci->type == id_OSC_CORE)
write_osc(ci);
else if (ci->type == id_OXIDE_EBR)
write_bram(ci);
else if (ci->type == id_MULT9_CORE || ci->type == id_PREADD9_CORE || ci->type == id_MULT18_CORE ||
ci->type == id_MULT18X36_CORE || ci->type == id_MULT36_CORE || ci->type == id_REG18_CORE ||
ci->type == id_ACC54_CORE)
write_dsp(ci);
blank();
}
// Write config for unused bels
write_unused();
// Write bank config
write_bankcfg();
}
};
} // namespace
void Arch::write_fasm(std::ostream &out) const { NexusFasmWriter(getCtx(), out)(); }
NEXTPNR_NAMESPACE_END

168
nexus/global.cc Normal file
View File

@ -0,0 +1,168 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2020 David Shah <dave@ds0.me>
*
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include "log.h"
#include "nextpnr.h"
#include "util.h"
#include <queue>
NEXTPNR_NAMESPACE_BEGIN
struct NexusGlobalRouter
{
Context *ctx;
NexusGlobalRouter(Context *ctx) : ctx(ctx){};
// When routing globals; we allow global->local for some tricky cases but never local->local
bool global_pip_filter(PipId pip) const
{
IdString dest_basename(ctx->wire_data(ctx->getPipDstWire(pip)).name);
const std::string &s = dest_basename.str(ctx);
if (s.size() > 2 && (s[0] == 'H' || s[0] == 'V') && s[1] == '0')
return false;
return true;
}
// Dedicated backwards BFS routing for global networks
template <typename Tfilt>
bool backwards_bfs_route(NetInfo *net, size_t user_idx, int iter_limit, bool strict, Tfilt pip_filter)
{
// Queue of wires to visit
std::queue<WireId> visit;
// Wire -> upstream pip
std::unordered_map<WireId, PipId> backtrace;
// Lookup source and destination wires
WireId src = ctx->getNetinfoSourceWire(net);
WireId dst = ctx->getNetinfoSinkWire(net, net->users.at(user_idx));
if (src == WireId())
log_error("Net '%s' has an invalid source port %s.%s\n", ctx->nameOf(net), ctx->nameOf(net->driver.cell),
ctx->nameOf(net->driver.port));
if (dst == WireId())
log_error("Net '%s' has an invalid sink port %s.%s\n", ctx->nameOf(net),
ctx->nameOf(net->users.at(user_idx).cell), ctx->nameOf(net->users.at(user_idx).port));
if (ctx->getBoundWireNet(src) != net)
ctx->bindWire(src, net, STRENGTH_LOCKED);
if (src == dst) {
// Nothing more to do
return true;
}
visit.push(dst);
backtrace[dst] = PipId();
int iter = 0;
while (!visit.empty() && (iter++ < iter_limit)) {
WireId cursor = visit.front();
visit.pop();
// Search uphill pips
for (PipId pip : ctx->getPipsUphill(cursor)) {
// Skip pip if unavailable, and not because it's already used for this net
if (!ctx->checkPipAvail(pip) && ctx->getBoundPipNet(pip) != net)
continue;
WireId prev = ctx->getPipSrcWire(pip);
// Ditto for the upstream wire
if (!ctx->checkWireAvail(prev) && ctx->getBoundWireNet(prev) != net)
continue;
// Skip already visited wires
if (backtrace.count(prev))
continue;
// Apply our custom pip filter
if (!pip_filter(pip))
continue;
// Add to the queue
visit.push(prev);
backtrace[prev] = pip;
// Check if we are done yet
if (prev == src)
goto done;
}
if (false) {
done:
break;
}
}
if (backtrace.count(src)) {
WireId cursor = src;
std::vector<PipId> pips;
// Create a list of pips on the routed path
while (true) {
PipId pip = backtrace.at(cursor);
if (pip == PipId())
break;
pips.push_back(pip);
cursor = ctx->getPipDstWire(pip);
}
// Reverse that list
std::reverse(pips.begin(), pips.end());
// Bind pips until we hit already-bound routing
for (PipId pip : pips) {
WireId dst = ctx->getPipDstWire(pip);
if (ctx->getBoundWireNet(dst) == net)
break;
ctx->bindPip(pip, net, STRENGTH_LOCKED);
}
return true;
} else {
if (strict)
log_error("Failed to route net '%s' from %s to %s using dedicated routing.\n", ctx->nameOf(net),
ctx->nameOfWire(src), ctx->nameOfWire(dst));
return false;
}
}
void route_clk_net(NetInfo *net)
{
for (size_t i = 0; i < net->users.size(); i++)
backwards_bfs_route(net, i, 1000000, true, [&](PipId pip) { return global_pip_filter(pip); });
log_info(" routed net '%s' using global resources\n", ctx->nameOf(net));
}
void operator()()
{
log_info("Routing globals...\n");
for (auto net : sorted(ctx->nets)) {
NetInfo *ni = net.second;
CellInfo *drv = ni->driver.cell;
if (drv == nullptr)
continue;
if (drv->type == id_DCC) {
route_clk_net(ni);
continue;
}
}
}
};
void Arch::route_globals()
{
NexusGlobalRouter glb_router(getCtx());
glb_router();
}
NEXTPNR_NAMESPACE_END

70
nexus/io.cc Normal file
View File

@ -0,0 +1,70 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2020 David Shah <dave@ds0.me>
*
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include "log.h"
#include "nextpnr.h"
NEXTPNR_NAMESPACE_BEGIN
const std::unordered_map<std::string, IOTypeData> Arch::io_types = {
{"LVCMOS33", {IOSTYLE_SE_WR, 330}}, {"LVCMOS25", {IOSTYLE_SE_WR, 250}},
{"LVCMOS18", {IOSTYLE_SE_WR, 180}}, {"LVCMOS15", {IOSTYLE_SE_WR, 150}},
{"LVCMOS12", {IOSTYLE_SE_WR, 120}}, {"LVCMOS10", {IOSTYLE_SE_WR, 120}},
{"LVCMOS33D", {IOSTYLE_PD_WR, 330}}, {"LVCMOS25D", {IOSTYLE_PD_WR, 250}},
{"LVCMOS18H", {IOSTYLE_SE_HP, 180}}, {"LVCMOS15H", {IOSTYLE_SE_HP, 150}},
{"LVCMOS12H", {IOSTYLE_SE_HP, 120}}, {"LVCMOS10R", {IOSTYLE_SE_HP, 120}},
{"LVCMOS10H", {IOSTYLE_SE_HP, 100}},
{"HSTL15_I", {IOSTYLE_REF_HP, 150}}, {"SSTL15_I", {IOSTYLE_REF_HP, 150}},
{"SSTL15_II", {IOSTYLE_REF_HP, 150}}, {"SSTL135_I", {IOSTYLE_REF_HP, 135}},
{"SSTL135_II", {IOSTYLE_REF_HP, 135}}, {"HSUL12", {IOSTYLE_REF_HP, 120}},
{"LVDS", {IOSTYLE_DIFF_HP, 180}}, {"SLVS", {IOSTYLE_DIFF_HP, 120}},
{"MIPI_DPHY", {IOSTYLE_DIFF_HP, 120}}, {"HSUL12D", {IOSTYLE_DIFF_HP, 120}},
{"HSTL15D_I", {IOSTYLE_DIFF_HP, 150}}, {"SSTL15D_I", {IOSTYLE_DIFF_HP, 150}},
{"SSTL15D_II", {IOSTYLE_DIFF_HP, 150}}, {"SSTL135D_I", {IOSTYLE_DIFF_HP, 135}},
{"SSTL135D_II", {IOSTYLE_DIFF_HP, 135}}, {"HSUL12D", {IOSTYLE_DIFF_HP, 120}},
};
int Arch::get_io_type_vcc(const std::string &io_type) const
{
if (!io_types.count(io_type))
log_error("IO type '%s' not supported.\n", io_type.c_str());
return io_types.at(io_type).vcco;
}
bool Arch::is_io_type_diff(const std::string &io_type) const
{
if (!io_types.count(io_type))
log_error("IO type '%s' not supported.\n", io_type.c_str());
return io_types.at(io_type).style & IOMODE_DIFF;
}
bool Arch::is_io_type_ref(const std::string &io_type) const
{
if (!io_types.count(io_type))
log_error("IO type '%s' not supported.\n", io_type.c_str());
return io_types.at(io_type).style & IOMODE_REF;
}
NEXTPNR_NAMESPACE_END

99
nexus/main.cc Normal file
View File

@ -0,0 +1,99 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2020 David Shah <dave@ds0.me>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#ifdef MAIN_EXECUTABLE
#include <fstream>
#include "command.h"
#include "design_utils.h"
#include "jsonwrite.h"
#include "log.h"
#include "timing.h"
USING_NEXTPNR_NAMESPACE
class NexusCommandHandler : public CommandHandler
{
public:
NexusCommandHandler(int argc, char **argv);
virtual ~NexusCommandHandler(){};
std::unique_ptr<Context> createContext(std::unordered_map<std::string, Property> &values) override;
void setupArchContext(Context *ctx) override{};
void customBitstream(Context *ctx) override;
void customAfterLoad(Context *ctx) override;
protected:
po::options_description getArchOptions() override;
};
NexusCommandHandler::NexusCommandHandler(int argc, char **argv) : CommandHandler(argc, argv) {}
po::options_description NexusCommandHandler::getArchOptions()
{
po::options_description specific("Architecture specific options");
specific.add_options()("device", po::value<std::string>(), "device name");
specific.add_options()("fasm", po::value<std::string>(), "fasm file to write");
specific.add_options()("pdc", po::value<std::string>(), "physical constraints file");
specific.add_options()("no-post-place-opt", "disable post-place repacking (debugging use only)");
return specific;
}
void NexusCommandHandler::customBitstream(Context *ctx)
{
if (vm.count("fasm")) {
std::string filename = vm["fasm"].as<std::string>();
std::ofstream out(filename);
if (!out)
log_error("Failed to open output FASM file %s.\n", filename.c_str());
ctx->write_fasm(out);
}
}
std::unique_ptr<Context> NexusCommandHandler::createContext(std::unordered_map<std::string, Property> &values)
{
ArchArgs chipArgs;
if (!vm.count("device")) {
log_error("device must be specified on the command line (e.g. --device LIFCL-40-9BG400CES)\n");
}
chipArgs.device = vm["device"].as<std::string>();
auto ctx = std::unique_ptr<Context>(new Context(chipArgs));
if (vm.count("no-post-place-opt"))
ctx->settings[ctx->id("no_post_place_opt")] = Property::State::S1;
return ctx;
}
void NexusCommandHandler::customAfterLoad(Context *ctx)
{
if (vm.count("pdc")) {
std::string filename = vm["pdc"].as<std::string>();
std::ifstream in(filename);
if (!in)
log_error("Failed to open input PDC file %s.\n", filename.c_str());
ctx->read_pdc(in);
}
}
int main(int argc, char *argv[])
{
NexusCommandHandler handler(argc, argv);
return handler.exec();
}
#endif

1866
nexus/pack.cc Normal file

File diff suppressed because it is too large Load Diff

352
nexus/pdc.cc Normal file
View File

@ -0,0 +1,352 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2020 David Shah <dave@ds0.me>
*
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include "log.h"
#include "nextpnr.h"
#include <iterator>
NEXTPNR_NAMESPACE_BEGIN
struct TCLEntity
{
enum EntityType
{
ENTITY_CELL,
ENTITY_PORT,
ENTITY_NET,
} type;
IdString name;
TCLEntity(EntityType type, IdString name) : type(type), name(name) {}
const std::string &to_string(Context *ctx) { return name.str(ctx); }
CellInfo *get_cell(Context *ctx)
{
if (type != ENTITY_CELL)
return nullptr;
return ctx->cells.at(name).get();
}
PortInfo *get_port(Context *ctx)
{
if (type != ENTITY_PORT)
return nullptr;
return &ctx->ports.at(name);
}
NetInfo *get_net(Context *ctx)
{
if (type != ENTITY_NET)
return nullptr;
return ctx->nets.at(name).get();
}
};
struct TCLValue
{
TCLValue(const std::string &s) : is_string(true), str(s){};
TCLValue(const std::vector<TCLEntity> &l) : is_string(false), list(l){};
bool is_string;
std::string str; // simple string value
std::vector<TCLEntity> list; // list of entities
};
struct PDCParser
{
std::string buf;
int pos = 0;
int lineno = 1;
Context *ctx;
PDCParser(const std::string &buf, Context *ctx) : buf(buf), ctx(ctx){};
inline bool eof() const { return pos == int(buf.size()); }
inline char peek() const { return buf.at(pos); }
inline char get()
{
char c = buf.at(pos++);
if (c == '\n')
++lineno;
return c;
}
std::string get(int n)
{
std::string s = buf.substr(pos, n);
pos += n;
return s;
}
// If next char matches c, take it from the stream and return true
bool check_get(char c)
{
if (peek() == c) {
get();
return true;
} else {
return false;
}
}
// If next char matches any in chars, take it from the stream and return true
bool check_get_any(const std::string &chrs)
{
char c = peek();
if (chrs.find(c) != std::string::npos) {
get();
return true;
} else {
return false;
}
}
inline void skip_blank(bool nl = false)
{
while (!eof() && check_get_any(nl ? " \t\n\r" : " \t"))
;
}
// Return true if end of line (or file)
inline bool skip_check_eol()
{
skip_blank(false);
if (eof())
return true;
char c = peek();
// Comments count as end of line
if (c == '#') {
get();
while (!eof() && peek() != '\n' && peek() != '\r')
get();
return true;
}
if (c == ';') {
// Forced end of line
get();
return true;
}
return (c == '\n' || c == '\r');
}
inline std::string get_str()
{
std::string s;
skip_blank(false);
if (eof())
return "";
bool in_quotes = false, in_braces = false, escaped = false;
char c = get();
if (c == '"')
in_quotes = true;
else if (c == '{')
in_braces = true;
else
s += c;
while (true) {
char c = peek();
if (!in_quotes && !in_braces && !escaped && (std::isblank(c) || c == ']')) {
break;
}
get();
if (escaped) {
s += c;
escaped = false;
} else if ((in_quotes && c == '"') || (in_braces && c == '}')) {
break;
} else if (c == '\\') {
escaped = true;
} else {
s += c;
}
}
return s;
}
TCLValue evaluate(const std::vector<TCLValue> &arguments)
{
NPNR_ASSERT(!arguments.empty());
auto &arg0 = arguments.at(0);
NPNR_ASSERT(arg0.is_string);
const std::string &cmd = arg0.str;
if (cmd == "get_ports")
return cmd_get_ports(arguments);
else if (cmd == "get_cells")
return cmd_get_cells(arguments);
else if (cmd == "ldc_set_location")
return cmd_ldc_set_location(arguments);
else if (cmd == "ldc_set_port")
return cmd_ldc_set_port(arguments);
else if (cmd == "ldc_set_sysconfig" || cmd == "get_nets" || cmd == "create_clock") {
log_warning("%s is not yet supported!\n", cmd.c_str());
return TCLValue("");
} else
log_error("Unsupported PDC command '%s'\n", cmd.c_str());
}
std::vector<TCLValue> get_arguments()
{
std::vector<TCLValue> args;
while (!skip_check_eol()) {
if (check_get('[')) {
// Start of a sub-expression
auto result = evaluate(get_arguments());
NPNR_ASSERT(check_get(']'));
args.push_back(result);
} else if (peek() == ']') {
break;
} else {
args.push_back(get_str());
}
}
skip_blank(true);
return args;
}
TCLValue cmd_get_ports(const std::vector<TCLValue> &arguments)
{
std::vector<TCLEntity> ports;
for (int i = 1; i < int(arguments.size()); i++) {
auto &arg = arguments.at(i);
if (!arg.is_string)
log_error("get_ports expected string arguments (line %d)\n", lineno);
std::string s = arg.str;
if (s.at(0) == '-')
log_error("unsupported argument '%s' to get_ports (line %d)\n", s.c_str(), lineno);
IdString id = ctx->id(s);
if (ctx->ports.count(id))
ports.emplace_back(TCLEntity::ENTITY_PORT, id);
}
return ports;
}
TCLValue cmd_get_cells(const std::vector<TCLValue> &arguments)
{
std::vector<TCLEntity> cells;
for (int i = 1; i < int(arguments.size()); i++) {
auto &arg = arguments.at(i);
if (!arg.is_string)
log_error("get_cells expected string arguments (line %d)\n", lineno);
std::string s = arg.str;
if (s.at(0) == '-')
log_error("unsupported argument '%s' to get_cells (line %d)\n", s.c_str(), lineno);
IdString id = ctx->id(s);
if (ctx->cells.count(id))
cells.emplace_back(TCLEntity::ENTITY_CELL, id);
}
return cells;
}
TCLValue cmd_ldc_set_location(const std::vector<TCLValue> &arguments)
{
std::string site;
for (int i = 1; i < int(arguments.size()); i++) {
auto &arg = arguments.at(i);
if (arg.is_string) {
std::string s = arg.str;
if (s == "-site") {
i++;
auto &val = arguments.at(i);
if (!val.is_string)
log_error("expecting string argument to -site (line %d)\n", lineno);
site = val.str;
}
} else {
if (site.empty())
log_error("expecting -site before list of objects (line %d)\n", lineno);
for (const auto &ety : arg.list) {
if (ety.type == TCLEntity::ENTITY_PORT)
ctx->io_attr[ety.name][id_LOC] = site;
else if (ety.type == TCLEntity::ENTITY_CELL)
ctx->cells[ety.name]->attrs[id_LOC] = site;
else
log_error("ldc_set_location applies only to cells or IO ports (line %d)\n", lineno);
}
}
}
return std::string{};
}
TCLValue cmd_ldc_set_port(const std::vector<TCLValue> &arguments)
{
std::unordered_map<IdString, Property> args;
for (int i = 1; i < int(arguments.size()); i++) {
auto &arg = arguments.at(i);
if (arg.is_string) {
std::string s = arg.str;
if (s == "-iobuf") {
i++;
auto &val = arguments.at(i);
if (!val.is_string)
log_error("expecting string argument to -iobuf (line %d)\n", lineno);
std::stringstream ss(val.str);
std::string kv;
while (ss >> kv) {
auto eqp = kv.find('=');
if (eqp == std::string::npos)
log_error("expected key-value pair separated by '=' (line %d)", lineno);
std::string k = kv.substr(0, eqp), v = kv.substr(eqp + 1);
args[ctx->id(k)] = v;
}
} else {
log_error("unexpected argument '%s' to ldc_set_port (line %d)\n", s.c_str(), lineno);
}
} else {
for (const auto &ety : arg.list) {
if (ety.type == TCLEntity::ENTITY_PORT)
for (const auto &kv : args)
ctx->io_attr[ety.name][kv.first] = kv.second;
else
log_error("ldc_set_port applies only to IO ports (line %d)\n", lineno);
}
}
}
return std::string{};
}
void operator()()
{
while (!eof()) {
skip_blank(true);
auto args = get_arguments();
if (args.empty())
continue;
evaluate(args);
}
}
};
void Arch::read_pdc(std::istream &in)
{
std::string buf(std::istreambuf_iterator<char>(in), {});
PDCParser(buf, getCtx())();
}
NEXTPNR_NAMESPACE_END

180
nexus/pins.cc Normal file
View File

@ -0,0 +1,180 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2020 David Shah <dave@ds0.me>
*
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include "nextpnr.h"
NEXTPNR_NAMESPACE_BEGIN
namespace {
static const std::unordered_map<IdString, Arch::CellPinsData> base_cell_pin_data = {
{id_OXIDE_COMB,
{
{id_WCK, PINSTYLE_DEDI},
{id_WRE, PINSTYLE_DEDI},
{id_FCI, PINSTYLE_DEDI},
{id_F1, PINSTYLE_DEDI},
{id_WAD0, PINSTYLE_DEDI},
{id_WAD1, PINSTYLE_DEDI},
{id_WAD2, PINSTYLE_DEDI},
{id_WAD3, PINSTYLE_DEDI},
{id_WDI, PINSTYLE_DEDI},
{{}, PINSTYLE_PU},
}},
{id_OXIDE_FF,
{
{id_CLK, PINSTYLE_CLK},
{id_LSR, PINSTYLE_LSR},
{id_CE, PINSTYLE_CE},
{{}, PINSTYLE_DEDI},
}},
{id_RAMW,
{
{id_CLK, PINSTYLE_CLK},
{{}, PINSTYLE_DEDI},
}},
{id_SEIO18_CORE,
{
{id_T, PINSTYLE_T},
{id_B, PINSTYLE_DEDI},
{{}, PINSTYLE_PU},
}},
{id_DIFFIO18_CORE,
{
{id_T, PINSTYLE_T},
{id_B, PINSTYLE_DEDI},
{{}, PINSTYLE_PU},
}},
{id_SEIO33_CORE,
{
{id_T, PINSTYLE_T},
{id_B, PINSTYLE_DEDI},
{{}, PINSTYLE_PU},
}},
{id_OXIDE_EBR,
{{id_CLKA, PINSTYLE_CLK}, {id_CLKB, PINSTYLE_CLK}, {id_CEA, PINSTYLE_CE}, {id_CEB, PINSTYLE_CE},
{id_CSA0, PINSTYLE_PU}, {id_CSA1, PINSTYLE_PU}, {id_CSA2, PINSTYLE_PU}, {id_CSB0, PINSTYLE_PU},
{id_CSB1, PINSTYLE_PU}, {id_CSB2, PINSTYLE_PU}, {id_ADA0, PINSTYLE_ADLSB}, {id_ADA1, PINSTYLE_ADLSB},
{id_ADA2, PINSTYLE_ADLSB}, {id_ADA2, PINSTYLE_ADLSB}, {id_ADA3, PINSTYLE_ADLSB}, {id_ADB0, PINSTYLE_ADLSB},
{id_ADB1, PINSTYLE_ADLSB}, {id_WEA, PINSTYLE_INV_PD}, {id_WEB, PINSTYLE_INV_PD}, {id_RSTA, PINSTYLE_INV_PD},
{id_RSTB, PINSTYLE_INV_PD}, {{id_DWS0}, PINSTYLE_PU}, {{id_DWS1}, PINSTYLE_PU}, {{id_DWS2}, PINSTYLE_PU},
{{id_DWS3}, PINSTYLE_PU}, {{id_DWS4}, PINSTYLE_PU}, {{}, PINSTYLE_CIB}}},
{id_OSC_CORE,
{
{id_HFOUTEN, PINSTYLE_PU},
{{}, PINSTYLE_CIB},
}},
{id_PREADD9_CORE,
{
{id_CLK, PINSTYLE_CLK}, {id_RSTCL, PINSTYLE_LSR}, {id_RSTB, PINSTYLE_LSR}, {id_CECL, PINSTYLE_CE},
{id_CEB, PINSTYLE_CE},
{id_B0, PINSTYLE_CIB}, {id_B1, PINSTYLE_CIB}, {id_B2, PINSTYLE_CIB}, {id_B3, PINSTYLE_CIB},
{id_B4, PINSTYLE_CIB}, {id_B5, PINSTYLE_CIB}, {id_B6, PINSTYLE_CIB}, {id_B7, PINSTYLE_CIB},
{id_B8, PINSTYLE_CIB}, {id_BSIGNED, PINSTYLE_CIB},
{id_C0, PINSTYLE_CIB}, {id_C1, PINSTYLE_CIB}, {id_C2, PINSTYLE_CIB}, {id_C3, PINSTYLE_CIB},
{id_C4, PINSTYLE_CIB}, {id_C5, PINSTYLE_CIB}, {id_C6, PINSTYLE_CIB}, {id_C7, PINSTYLE_CIB},
{id_C8, PINSTYLE_CIB}, {id_C9, PINSTYLE_CIB},
{{}, PINSTYLE_DEDI},
}},
{id_MULT9_CORE,
{
{id_CLK, PINSTYLE_CLK},
{id_RSTA, PINSTYLE_LSR},
{id_RSTP, PINSTYLE_LSR},
{id_CEA, PINSTYLE_CE},
{id_CEP, PINSTYLE_CE},
{id_A0, PINSTYLE_CIB},
{id_A1, PINSTYLE_CIB},
{id_A2, PINSTYLE_CIB},
{id_A3, PINSTYLE_CIB},
{id_A4, PINSTYLE_CIB},
{id_A5, PINSTYLE_CIB},
{id_A6, PINSTYLE_CIB},
{id_A7, PINSTYLE_CIB},
{id_A8, PINSTYLE_CIB},
{id_ASIGNED, PINSTYLE_CIB},
{{}, PINSTYLE_DEDI},
}},
{id_REG18_CORE,
{
{id_CLK, PINSTYLE_CLK},
{id_RSTP, PINSTYLE_LSR},
{id_CEP, PINSTYLE_CE},
{{}, PINSTYLE_DEDI},
}},
{id_MULT18_CORE,
{
{id_SFTCTRL0, PINSTYLE_PU},
{id_SFTCTRL1, PINSTYLE_PU},
{id_SFTCTRL2, PINSTYLE_PU},
{id_SFTCTRL3, PINSTYLE_PU},
{id_ROUNDEN, PINSTYLE_CIB},
{{}, PINSTYLE_DEDI},
}},
{id_MULT18X36_CORE,
{
{id_SFTCTRL0, PINSTYLE_PU},
{id_SFTCTRL1, PINSTYLE_PU},
{id_SFTCTRL2, PINSTYLE_PU},
{id_SFTCTRL3, PINSTYLE_PU},
{id_ROUNDEN, PINSTYLE_CIB},
{{}, PINSTYLE_DEDI},
}},
{id_ACC54_CORE,
{
{id_CLK, PINSTYLE_CLK}, {id_RSTC, PINSTYLE_LSR}, {id_CEC, PINSTYLE_CE},
{id_SIGNEDI, PINSTYLE_CIB}, {id_RSTCTRL, PINSTYLE_LSR}, {id_CECTRL, PINSTYLE_CE},
{id_RSTCIN, PINSTYLE_LSR}, {id_CECIN, PINSTYLE_CE}, {id_LOAD, PINSTYLE_CIB},
{id_ADDSUB0, PINSTYLE_CIB}, {id_ADDSUB1, PINSTYLE_CIB}, {id_M9ADDSUB0, PINSTYLE_PU},
{id_M9ADDSUB1, PINSTYLE_PU}, {id_ROUNDEN, PINSTYLE_CIB}, {id_RSTO, PINSTYLE_LSR},
{id_CEO, PINSTYLE_CE}, {id_CIN, PINSTYLE_CIB}, {id_SFTCTRL0, PINSTYLE_PU},
{id_SFTCTRL1, PINSTYLE_PU}, {id_SFTCTRL2, PINSTYLE_PU}, {id_SFTCTRL3, PINSTYLE_PU},
{{}, PINSTYLE_DEDI},
}}};
} // namespace
void Arch::init_cell_pin_data() { cell_pins_db = base_cell_pin_data; }
CellPinStyle Arch::get_cell_pin_style(const CellInfo *cell, IdString port) const
{
// Look up the pin style in the cell database
auto fnd_cell = cell_pins_db.find(cell->type);
if (fnd_cell == cell_pins_db.end())
return PINSTYLE_NONE;
auto fnd_port = fnd_cell->second.find(port);
if (fnd_port != fnd_cell->second.end())
return fnd_port->second;
// If there isn't an exact port match, then the empty IdString
// represents a wildcard default match
auto fnd_default = fnd_cell->second.find({});
if (fnd_default != fnd_cell->second.end())
return fnd_default->second;
return PINSTYLE_NONE;
}
NEXTPNR_NAMESPACE_END

161
nexus/post_place.cc Normal file
View File

@ -0,0 +1,161 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2020 David Shah <dave@ds0.me>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include "design_utils.h"
#include "log.h"
#include "nextpnr.h"
#include "timing.h"
#include "util.h"
NEXTPNR_NAMESPACE_BEGIN
struct NexusPostPlaceOpt
{
Context *ctx;
NetCriticalityMap net_crit;
NexusPostPlaceOpt(Context *ctx) : ctx(ctx){};
inline bool is_constrained(CellInfo *cell)
{
return cell->constr_parent != nullptr || !cell->constr_children.empty();
}
bool swap_cell_placement(CellInfo *cell, BelId new_bel)
{
if (is_constrained(cell))
return false;
BelId oldBel = cell->bel;
CellInfo *other_cell = ctx->getBoundBelCell(new_bel);
if (other_cell != nullptr && (is_constrained(other_cell) || other_cell->belStrength > STRENGTH_WEAK)) {
return false;
}
ctx->unbindBel(oldBel);
if (other_cell != nullptr) {
ctx->unbindBel(new_bel);
}
ctx->bindBel(new_bel, cell, STRENGTH_WEAK);
if (other_cell != nullptr) {
ctx->bindBel(oldBel, other_cell, STRENGTH_WEAK);
}
if (!ctx->isBelLocationValid(new_bel) || ((other_cell != nullptr && !ctx->isBelLocationValid(oldBel)))) {
// New placement is not legal.
ctx->unbindBel(new_bel);
if (other_cell != nullptr)
ctx->unbindBel(oldBel);
// Revert.
ctx->bindBel(oldBel, cell, STRENGTH_WEAK);
if (other_cell != nullptr)
ctx->bindBel(new_bel, other_cell, STRENGTH_WEAK);
return false;
}
return true;
}
int get_distance(BelId a, BelId b)
{
Loc la = ctx->getBelLocation(a);
Loc lb = ctx->getBelLocation(b);
return std::abs(la.x - lb.x) + std::abs(la.y - lb.y);
}
BelId lut_to_ff(BelId lut)
{
Loc ff_loc = ctx->getBelLocation(lut);
ff_loc.z += (Arch::BEL_FF0 - Arch::BEL_LUT0);
return ctx->getBelByLocation(ff_loc);
}
void opt_lutffs()
{
int moves_made = 0;
for (auto cell : sorted(ctx->cells)) {
// Search for FF cells
CellInfo *ff = cell.second;
if (ff->type != id_OXIDE_FF)
continue;
// Check M ('fabric') input net
NetInfo *m = get_net_or_empty(ff, id_M);
if (m == nullptr)
continue;
// Ignore FFs that need both DI and M (PRLD mode)
if (get_net_or_empty(ff, id_DI) != nullptr)
continue;
const auto &drv = m->driver;
// Skip if driver isn't a LUT/MUX2
if (drv.cell == nullptr || drv.cell->type != id_OXIDE_COMB || (drv.port != id_F && drv.port != id_OFX))
continue;
CellInfo *lut = drv.cell;
// Check distance to move isn't too far
if (get_distance(ff->bel, lut->bel) > lut_ff_radius)
continue;
// Find the bel we plan to move into
BelId dest_ff = lut_to_ff(lut->bel);
NPNR_ASSERT(dest_ff != BelId());
NPNR_ASSERT(ctx->getBelType(dest_ff) == id_OXIDE_FF);
// Ended up in the ideal location by chance
if (dest_ff != ff->bel) {
// If dest_ff is already placed *and* using direct 'DI' input, don't touch it
CellInfo *dest_ff_cell = ctx->getBoundBelCell(dest_ff);
if (dest_ff_cell != nullptr && get_net_or_empty(dest_ff_cell, id_DI) != nullptr)
continue;
// Attempt the swap
bool swap_result = swap_cell_placement(ff, dest_ff);
if (!swap_result)
continue;
}
// Use direct interconnect
rename_port(ctx, ff, id_M, id_DI);
ff->params[id_SEL] = std::string("DL");
++moves_made;
continue;
}
log_info(" created %d direct LUT-FF pairs\n", moves_made);
}
void operator()()
{
get_criticalities(ctx, &net_crit);
opt_lutffs();
}
// Configuration
const int lut_ff_radius = 2;
const int lut_lut_radius = 1;
const float lut_lut_crit = 0.85;
};
void Arch::post_place_opt()
{
if (bool_or_default(settings, id("no_post_place_opt")))
return;
log_info("Running post-place optimisations...\n");
NexusPostPlaceOpt opt(getCtx());
opt();
}
NEXTPNR_NAMESPACE_END