Merge pull request #219 from daveshah1/placer_heap
HeAP-based analytical placer
This commit is contained in:
commit
c67b8259bb
@ -8,7 +8,7 @@ RUN set -e -x ;\
|
|||||||
apt-get -y install \
|
apt-get -y install \
|
||||||
build-essential autoconf cmake clang bison wget flex gperf \
|
build-essential autoconf cmake clang bison wget flex gperf \
|
||||||
libreadline-dev gawk tcl-dev libffi-dev graphviz xdot python3-dev \
|
libreadline-dev gawk tcl-dev libffi-dev graphviz xdot python3-dev \
|
||||||
libboost-all-dev qt5-default git libftdi-dev pkg-config
|
libboost-all-dev qt5-default git libftdi-dev pkg-config libeigen3-dev
|
||||||
|
|
||||||
RUN set -e -x ;\
|
RUN set -e -x ;\
|
||||||
mkdir -p /usr/local/src ;\
|
mkdir -p /usr/local/src ;\
|
||||||
|
@ -5,6 +5,8 @@ project(nextpnr)
|
|||||||
option(BUILD_GUI "Build GUI" ON)
|
option(BUILD_GUI "Build GUI" ON)
|
||||||
option(BUILD_PYTHON "Build Python Integration" ON)
|
option(BUILD_PYTHON "Build Python Integration" ON)
|
||||||
option(BUILD_TESTS "Build GUI" OFF)
|
option(BUILD_TESTS "Build GUI" OFF)
|
||||||
|
option(BUILD_HEAP "Build HeAP analytic placer" ON)
|
||||||
|
option(USE_OPENMP "Use OpenMP to accelerate analytic placer" OFF)
|
||||||
option(COVERAGE "Add code coverage info" OFF)
|
option(COVERAGE "Add code coverage info" OFF)
|
||||||
option(STATIC_BUILD "Create static build" OFF)
|
option(STATIC_BUILD "Create static build" OFF)
|
||||||
option(EXTERNAL_CHIPDB "Create build with pre-built chipdb binaries" OFF)
|
option(EXTERNAL_CHIPDB "Create build with pre-built chipdb binaries" OFF)
|
||||||
@ -53,12 +55,16 @@ endforeach()
|
|||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 11)
|
set(CMAKE_CXX_STANDARD 11)
|
||||||
if (MSVC)
|
if (MSVC)
|
||||||
set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "" FORCE)
|
set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "" FORCE)
|
||||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /D_DEBUG /W4 /wd4100 /wd4244 /wd4125 /wd4800 /wd4456 /wd4458 /wd4305 /wd4459 /wd4121 /wd4996")
|
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /D_DEBUG /W4 /wd4100 /wd4244 /wd4125 /wd4800 /wd4456 /wd4458 /wd4305 /wd4459 /wd4121 /wd4996")
|
||||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /W4 /wd4100 /wd4244 /wd4125 /wd4800 /wd4456 /wd4458 /wd4305 /wd4459 /wd4121 /wd4996 /wd4127")
|
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /W4 /wd4100 /wd4244 /wd4125 /wd4800 /wd4456 /wd4458 /wd4305 /wd4459 /wd4121 /wd4996 /wd4127")
|
||||||
else()
|
else()
|
||||||
set(CMAKE_CXX_FLAGS_DEBUG "-Wall -fPIC -ggdb -pipe")
|
set(CMAKE_CXX_FLAGS_DEBUG "-Wall -fPIC -ggdb -pipe")
|
||||||
set(CMAKE_CXX_FLAGS_RELEASE "-Wall -fPIC -O3 -g -pipe")
|
if (USE_OPENMP)
|
||||||
|
set(CMAKE_CXX_FLAGS_RELEASE "-Wall -fPIC -O3 -g -pipe -fopenmp")
|
||||||
|
else()
|
||||||
|
set(CMAKE_CXX_FLAGS_RELEASE "-Wall -fPIC -O3 -g -pipe")
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
set(CMAKE_DEFIN)
|
set(CMAKE_DEFIN)
|
||||||
|
|
||||||
@ -181,6 +187,14 @@ if (BUILD_PYTHON)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
include_directories(common/ json/ ${Boost_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS})
|
include_directories(common/ json/ ${Boost_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS})
|
||||||
|
|
||||||
|
if(BUILD_HEAP)
|
||||||
|
find_package (Eigen3 REQUIRED NO_MODULE)
|
||||||
|
include_directories(${EIGEN3_INCLUDE_DIRS})
|
||||||
|
add_definitions(${EIGEN3_DEFINITIONS})
|
||||||
|
add_definitions(-DWITH_HEAP)
|
||||||
|
endif()
|
||||||
|
|
||||||
aux_source_directory(common/ COMMON_SRC_FILES)
|
aux_source_directory(common/ COMMON_SRC_FILES)
|
||||||
aux_source_directory(json/ JSON_PARSER_FILES)
|
aux_source_directory(json/ JSON_PARSER_FILES)
|
||||||
set(COMMON_FILES ${COMMON_SRC_FILES} ${JSON_PARSER_FILES})
|
set(COMMON_FILES ${COMMON_SRC_FILES} ${JSON_PARSER_FILES})
|
||||||
|
10
README.md
10
README.md
@ -36,6 +36,7 @@ of the selected architecture:
|
|||||||
- Python 3.5 or later, including development libraries (`python3-dev` for Ubuntu)
|
- Python 3.5 or later, including development libraries (`python3-dev` for Ubuntu)
|
||||||
- on Windows make sure to install same version as supported by [vcpkg](https://github.com/Microsoft/vcpkg/blob/master/ports/python3/CONTROL)
|
- on Windows make sure to install same version as supported by [vcpkg](https://github.com/Microsoft/vcpkg/blob/master/ports/python3/CONTROL)
|
||||||
- Boost libraries (`libboost-dev libboost-filesystem-dev libboost-thread-dev libboost-program-options-dev libboost-python-dev libboost-dev` or `libboost-all-dev` for Ubuntu)
|
- Boost libraries (`libboost-dev libboost-filesystem-dev libboost-thread-dev libboost-program-options-dev libboost-python-dev libboost-dev` or `libboost-all-dev` for Ubuntu)
|
||||||
|
- Eigen3 (`libeigen3-dev` for Ubuntu) is required to build the analytic placer
|
||||||
- Latest git Yosys is required to synthesise the demo design
|
- Latest git Yosys is required to synthesise the demo design
|
||||||
- For building on Windows with MSVC, usage of vcpkg is advised for dependency installation.
|
- For building on Windows with MSVC, usage of vcpkg is advised for dependency installation.
|
||||||
- For 32 bit builds: `vcpkg install boost-filesystem boost-program-options boost-thread boost-python qt5-base`
|
- For 32 bit builds: `vcpkg install boost-filesystem boost-program-options boost-thread boost-python qt5-base`
|
||||||
@ -119,11 +120,11 @@ Use cmake `-D` options to specify which version of nextpnr you want to build.
|
|||||||
Use `-DARCH=...` to set the architecture. It is a semicolon separated list.
|
Use `-DARCH=...` to set the architecture. It is a semicolon separated list.
|
||||||
Use `cmake . -DARCH=all` to build all supported architectures.
|
Use `cmake . -DARCH=all` to build all supported architectures.
|
||||||
|
|
||||||
The following runs a debug build of the iCE40 architecture without GUI
|
The following runs a debug build of the iCE40 architecture without GUI,
|
||||||
and without Python support and only HX1K support:
|
without Python support, without the HeAP analytic placer and only HX1K support:
|
||||||
|
|
||||||
```
|
```
|
||||||
cmake -DARCH=ice40 -DCMAKE_BUILD_TYPE=Debug -DBUILD_PYTHON=OFF -DBUILD_GUI=OFF -DICE40_HX1K_ONLY=1 .
|
cmake -DARCH=ice40 -DCMAKE_BUILD_TYPE=Debug -DBUILD_PYTHON=OFF -DBUILD_GUI=OFF -DBUILD_HEAP=OFF -DICE40_HX1K_ONLY=1 .
|
||||||
make -j$(nproc)
|
make -j$(nproc)
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -134,6 +135,9 @@ cmake -DARCH=ice40 -DBUILD_PYTHON=OFF -DBUILD_GUI=OFF -DSTATIC_BUILD=ON .
|
|||||||
make -j$(nproc)
|
make -j$(nproc)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The HeAP placer's solver can optionally use OpenMP for a speedup on very large designs. Enable this by passing
|
||||||
|
`-DUSE_OPENMP=yes` to cmake (compiler support may vary).
|
||||||
|
|
||||||
You can change the location where nextpnr will be installed (this will usually default to `/usr/local`) by using
|
You can change the location where nextpnr will be installed (this will usually default to `/usr/local`) by using
|
||||||
`-DCMAKE_INSTALL_PREFIX=/install/prefix`.
|
`-DCMAKE_INSTALL_PREFIX=/install/prefix`.
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
#include "pybindings.h"
|
#include "pybindings.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <boost/algorithm/string/join.hpp>
|
||||||
#include <boost/filesystem/convenience.hpp>
|
#include <boost/filesystem/convenience.hpp>
|
||||||
#include <boost/program_options.hpp>
|
#include <boost/program_options.hpp>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
@ -120,8 +121,18 @@ po::options_description CommandHandler::getGeneralOptions()
|
|||||||
general.add_options()("json", po::value<std::string>(), "JSON design file to ingest");
|
general.add_options()("json", po::value<std::string>(), "JSON design file to ingest");
|
||||||
general.add_options()("seed", po::value<int>(), "seed value for random number generator");
|
general.add_options()("seed", po::value<int>(), "seed value for random number generator");
|
||||||
general.add_options()("randomize-seed,r", "randomize seed value for random number generator");
|
general.add_options()("randomize-seed,r", "randomize seed value for random number generator");
|
||||||
|
|
||||||
|
general.add_options()(
|
||||||
|
"placer", po::value<std::string>(),
|
||||||
|
std::string("placer algorithm to use; available: " + boost::algorithm::join(Arch::availablePlacers, ", ") +
|
||||||
|
"; default: " + Arch::defaultPlacer)
|
||||||
|
.c_str());
|
||||||
|
|
||||||
general.add_options()("slack_redist_iter", po::value<int>(), "number of iterations between slack redistribution");
|
general.add_options()("slack_redist_iter", po::value<int>(), "number of iterations between slack redistribution");
|
||||||
general.add_options()("cstrweight", po::value<float>(), "placer weighting for relative constraint satisfaction");
|
general.add_options()("cstrweight", po::value<float>(), "placer weighting for relative constraint satisfaction");
|
||||||
|
general.add_options()("starttemp", po::value<float>(), "placer SA start temperature");
|
||||||
|
general.add_options()("placer-budgets", "use budget rather than criticality in placer timing weights");
|
||||||
|
|
||||||
general.add_options()("pack-only", "pack design only without placement or routing");
|
general.add_options()("pack-only", "pack design only without placement or routing");
|
||||||
|
|
||||||
general.add_options()("ignore-loops", "ignore combinational loops in timing analysis");
|
general.add_options()("ignore-loops", "ignore combinational loops in timing analysis");
|
||||||
@ -183,10 +194,27 @@ void CommandHandler::setupContext(Context *ctx)
|
|||||||
settings->set("timing/allowFail", true);
|
settings->set("timing/allowFail", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (vm.count("placer")) {
|
||||||
|
std::string placer = vm["placer"].as<std::string>();
|
||||||
|
if (std::find(Arch::availablePlacers.begin(), Arch::availablePlacers.end(), placer) ==
|
||||||
|
Arch::availablePlacers.end())
|
||||||
|
log_error("Placer algorithm '%s' is not supported (available options: %s)\n", placer.c_str(),
|
||||||
|
boost::algorithm::join(Arch::availablePlacers, ", ").c_str());
|
||||||
|
settings->set("placer", placer);
|
||||||
|
} else {
|
||||||
|
settings->set("placer", Arch::defaultPlacer);
|
||||||
|
}
|
||||||
|
|
||||||
if (vm.count("cstrweight")) {
|
if (vm.count("cstrweight")) {
|
||||||
settings->set("placer1/constraintWeight", vm["cstrweight"].as<float>());
|
settings->set("placer1/constraintWeight", vm["cstrweight"].as<float>());
|
||||||
}
|
}
|
||||||
|
if (vm.count("starttemp")) {
|
||||||
|
settings->set("placer1/startTemp", vm["starttemp"].as<float>());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vm.count("placer-budgets")) {
|
||||||
|
settings->set("placer1/budgetBased", true);
|
||||||
|
}
|
||||||
if (vm.count("freq")) {
|
if (vm.count("freq")) {
|
||||||
auto freq = vm["freq"].as<double>();
|
auto freq = vm["freq"].as<double>();
|
||||||
if (freq > 0)
|
if (freq > 0)
|
||||||
|
@ -221,6 +221,9 @@ delay_t Context::getNetinfoRouteDelay(const NetInfo *net_info, const PortRef &us
|
|||||||
return 0;
|
return 0;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (net_info->wires.empty())
|
||||||
|
return predictDelay(net_info, user_info);
|
||||||
|
|
||||||
WireId src_wire = getNetinfoSourceWire(net_info);
|
WireId src_wire = getNetinfoSourceWire(net_info);
|
||||||
if (src_wire == WireId())
|
if (src_wire == WireId())
|
||||||
return 0;
|
return 0;
|
||||||
@ -421,4 +424,25 @@ void BaseCtx::addClock(IdString net, float freq)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BaseCtx::createRectangularRegion(IdString name, int x0, int y0, int x1, int y1)
|
||||||
|
{
|
||||||
|
std::unique_ptr<Region> new_region(new Region());
|
||||||
|
new_region->name = name;
|
||||||
|
new_region->constr_bels = true;
|
||||||
|
new_region->constr_pips = false;
|
||||||
|
new_region->constr_wires = false;
|
||||||
|
for (int x = x0; x <= x1; x++) {
|
||||||
|
for (int y = y0; y <= y1; y++) {
|
||||||
|
for (auto bel : getCtx()->getBelsByTile(x, y))
|
||||||
|
new_region->bels.insert(bel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
region[name] = std::move(new_region);
|
||||||
|
}
|
||||||
|
void BaseCtx::addBelToRegion(IdString name, BelId bel) { region[name]->bels.insert(bel); }
|
||||||
|
void BaseCtx::constrainCellToRegion(IdString cell, IdString region_name)
|
||||||
|
{
|
||||||
|
cells[cell]->region = region[region_name].get();
|
||||||
|
}
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_END
|
NEXTPNR_NAMESPACE_END
|
||||||
|
@ -637,6 +637,9 @@ struct BaseCtx
|
|||||||
|
|
||||||
// Intended to simplify Python API
|
// Intended to simplify Python API
|
||||||
void addClock(IdString net, float freq);
|
void addClock(IdString net, float freq);
|
||||||
|
void createRectangularRegion(IdString name, int x0, int y0, int x1, int y1);
|
||||||
|
void addBelToRegion(IdString name, BelId bel);
|
||||||
|
void constrainCellToRegion(IdString cell, IdString region_name);
|
||||||
};
|
};
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_END
|
NEXTPNR_NAMESPACE_END
|
||||||
|
@ -304,7 +304,7 @@ class ConstraintLegaliseWorker
|
|||||||
// Set the strength to locked on all cells in chain
|
// Set the strength to locked on all cells in chain
|
||||||
void lockdown_chain(CellInfo *root)
|
void lockdown_chain(CellInfo *root)
|
||||||
{
|
{
|
||||||
root->belStrength = STRENGTH_LOCKED;
|
root->belStrength = STRENGTH_STRONG;
|
||||||
for (auto child : root->constr_children)
|
for (auto child : root->constr_children)
|
||||||
lockdown_chain(child);
|
lockdown_chain(child);
|
||||||
}
|
}
|
||||||
@ -380,7 +380,7 @@ class ConstraintLegaliseWorker
|
|||||||
rippedCells.insert(confl_cell->name);
|
rippedCells.insert(confl_cell->name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx->bindBel(target, ctx->cells.at(cp.first).get(), STRENGTH_LOCKED);
|
ctx->bindBel(target, ctx->cells.at(cp.first).get(), STRENGTH_STRONG);
|
||||||
rippedCells.erase(cp.first);
|
rippedCells.erase(cp.first);
|
||||||
}
|
}
|
||||||
for (auto cp : solution) {
|
for (auto cp : solution) {
|
||||||
@ -529,4 +529,12 @@ int get_constraints_distance(const Context *ctx, const CellInfo *cell)
|
|||||||
return dist;
|
return dist;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool check_cell_bel_region(const CellInfo *cell, BelId bel)
|
||||||
|
{
|
||||||
|
if (cell->region != nullptr && cell->region->constr_bels && !cell->region->bels.count(bel))
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_END
|
NEXTPNR_NAMESPACE_END
|
||||||
|
@ -49,6 +49,10 @@ bool legalise_relative_constraints(Context *ctx);
|
|||||||
|
|
||||||
// Get the total distance from satisfied constraints for a cell
|
// Get the total distance from satisfied constraints for a cell
|
||||||
int get_constraints_distance(const Context *ctx, const CellInfo *cell);
|
int get_constraints_distance(const Context *ctx, const CellInfo *cell);
|
||||||
|
|
||||||
|
// Check that a Bel is within the region for a cell
|
||||||
|
bool check_cell_bel_region(const CellInfo *cell, BelId bel);
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_END
|
NEXTPNR_NAMESPACE_END
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
#include "placer1.h"
|
#include "placer1.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <boost/lexical_cast.hpp>
|
#include <boost/lexical_cast.hpp>
|
||||||
|
#include <boost/range/adaptor/reversed.hpp>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@ -43,10 +44,32 @@
|
|||||||
#include "timing.h"
|
#include "timing.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
|
namespace std {
|
||||||
|
template <> struct hash<std::pair<NEXTPNR_NAMESPACE_PREFIX IdString, std::size_t>>
|
||||||
|
{
|
||||||
|
std::size_t operator()(const std::pair<NEXTPNR_NAMESPACE_PREFIX IdString, std::size_t> &idp) const noexcept
|
||||||
|
{
|
||||||
|
std::size_t seed = 0;
|
||||||
|
boost::hash_combine(seed, hash<NEXTPNR_NAMESPACE_PREFIX IdString>()(idp.first));
|
||||||
|
boost::hash_combine(seed, hash<std::size_t>()(idp.second));
|
||||||
|
return seed;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace std
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_BEGIN
|
NEXTPNR_NAMESPACE_BEGIN
|
||||||
|
|
||||||
class SAPlacer
|
class SAPlacer
|
||||||
{
|
{
|
||||||
|
private:
|
||||||
|
struct BoundingBox
|
||||||
|
{
|
||||||
|
int x0 = 0, x1 = 0, y0 = 0, y1 = 0;
|
||||||
|
bool is_inside_inc(int x, int y) const { return x >= x0 && x <= x1 && y >= y0 && y <= y1; }
|
||||||
|
bool touches_bounds(int x, int y) const { return x == x0 || x == x1 || y == y0 || y == y1; }
|
||||||
|
wirelen_t hpwl() const { return wirelen_t((x1 - x0) + (y1 - y0)); }
|
||||||
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SAPlacer(Context *ctx, Placer1Cfg cfg) : ctx(ctx), cfg(cfg)
|
SAPlacer(Context *ctx, Placer1Cfg cfg) : ctx(ctx), cfg(cfg)
|
||||||
{
|
{
|
||||||
@ -78,13 +101,44 @@ class SAPlacer
|
|||||||
}
|
}
|
||||||
diameter = std::max(max_x, max_y) + 1;
|
diameter = std::max(max_x, max_y) + 1;
|
||||||
|
|
||||||
costs.resize(ctx->nets.size());
|
net_bounds.resize(ctx->nets.size());
|
||||||
|
net_arc_tcost.resize(ctx->nets.size());
|
||||||
|
moveChange.already_bounds_changed.resize(ctx->nets.size());
|
||||||
|
moveChange.already_changed_arcs.resize(ctx->nets.size());
|
||||||
old_udata.reserve(ctx->nets.size());
|
old_udata.reserve(ctx->nets.size());
|
||||||
|
net_by_udata.reserve(ctx->nets.size());
|
||||||
decltype(NetInfo::udata) n = 0;
|
decltype(NetInfo::udata) n = 0;
|
||||||
for (auto &net : ctx->nets) {
|
for (auto &net : ctx->nets) {
|
||||||
old_udata.emplace_back(net.second->udata);
|
old_udata.emplace_back(net.second->udata);
|
||||||
|
net_arc_tcost.at(n).resize(net.second->users.size());
|
||||||
|
moveChange.already_changed_arcs.at(n).resize(net.second->users.size());
|
||||||
net.second->udata = n++;
|
net.second->udata = n++;
|
||||||
|
net_by_udata.push_back(net.second.get());
|
||||||
}
|
}
|
||||||
|
for (auto ®ion : sorted(ctx->region)) {
|
||||||
|
Region *r = region.second;
|
||||||
|
BoundingBox bb;
|
||||||
|
if (r->constr_bels) {
|
||||||
|
bb.x0 = std::numeric_limits<int>::max();
|
||||||
|
bb.x1 = std::numeric_limits<int>::min();
|
||||||
|
bb.y0 = std::numeric_limits<int>::max();
|
||||||
|
bb.y1 = std::numeric_limits<int>::min();
|
||||||
|
for (auto bel : r->bels) {
|
||||||
|
Loc loc = ctx->getBelLocation(bel);
|
||||||
|
bb.x0 = std::min(bb.x0, loc.x);
|
||||||
|
bb.x1 = std::max(bb.x1, loc.x);
|
||||||
|
bb.y0 = std::min(bb.y0, loc.y);
|
||||||
|
bb.y1 = std::max(bb.y1, loc.y);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bb.x0 = 0;
|
||||||
|
bb.y0 = 0;
|
||||||
|
bb.x1 = max_x;
|
||||||
|
bb.y1 = max_y;
|
||||||
|
}
|
||||||
|
region_bounds[r->name] = bb;
|
||||||
|
}
|
||||||
|
build_port_index();
|
||||||
}
|
}
|
||||||
|
|
||||||
~SAPlacer()
|
~SAPlacer()
|
||||||
@ -93,12 +147,15 @@ class SAPlacer
|
|||||||
net.second->udata = old_udata[net.second->udata];
|
net.second->udata = old_udata[net.second->udata];
|
||||||
}
|
}
|
||||||
|
|
||||||
bool place()
|
bool place(bool refine = false)
|
||||||
{
|
{
|
||||||
log_break();
|
log_break();
|
||||||
ctx->lock();
|
ctx->lock();
|
||||||
|
|
||||||
size_t placed_cells = 0;
|
size_t placed_cells = 0;
|
||||||
|
std::vector<CellInfo *> autoplaced;
|
||||||
|
std::vector<CellInfo *> chain_basis;
|
||||||
|
if (!refine) {
|
||||||
// Initial constraints placer
|
// Initial constraints placer
|
||||||
for (auto &cell_entry : ctx->cells) {
|
for (auto &cell_entry : ctx->cells) {
|
||||||
CellInfo *cell = cell_entry.second.get();
|
CellInfo *cell = cell_entry.second.get();
|
||||||
@ -126,7 +183,8 @@ class SAPlacer
|
|||||||
|
|
||||||
auto bound_cell = ctx->getBoundBelCell(bel);
|
auto bound_cell = ctx->getBoundBelCell(bel);
|
||||||
if (bound_cell) {
|
if (bound_cell) {
|
||||||
log_error("Cell \'%s\' cannot be bound to bel \'%s\' since it is already bound to cell \'%s\'\n",
|
log_error(
|
||||||
|
"Cell \'%s\' cannot be bound to bel \'%s\' since it is already bound to cell \'%s\'\n",
|
||||||
cell->name.c_str(ctx), loc_name.c_str(), bound_cell->name.c_str(ctx));
|
cell->name.c_str(ctx), loc_name.c_str(), bound_cell->name.c_str(ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,7 +198,7 @@ class SAPlacer
|
|||||||
ctx->yield();
|
ctx->yield();
|
||||||
|
|
||||||
// Sort to-place cells for deterministic initial placement
|
// Sort to-place cells for deterministic initial placement
|
||||||
std::vector<CellInfo *> autoplaced;
|
|
||||||
for (auto &cell : ctx->cells) {
|
for (auto &cell : ctx->cells) {
|
||||||
CellInfo *ci = cell.second.get();
|
CellInfo *ci = cell.second.get();
|
||||||
if (ci->bel == BelId()) {
|
if (ci->bel == BelId()) {
|
||||||
@ -163,27 +221,47 @@ class SAPlacer
|
|||||||
if ((placed_cells - constr_placed_cells) % 500 != 0)
|
if ((placed_cells - constr_placed_cells) % 500 != 0)
|
||||||
log_info(" initial placement placed %d/%d cells\n", int(placed_cells - constr_placed_cells),
|
log_info(" initial placement placed %d/%d cells\n", int(placed_cells - constr_placed_cells),
|
||||||
int(autoplaced.size()));
|
int(autoplaced.size()));
|
||||||
if (ctx->slack_redist_iter > 0)
|
if (cfg.budgetBased && ctx->slack_redist_iter > 0)
|
||||||
assign_budget(ctx);
|
assign_budget(ctx);
|
||||||
ctx->yield();
|
ctx->yield();
|
||||||
auto iplace_end = std::chrono::high_resolution_clock::now();
|
auto iplace_end = std::chrono::high_resolution_clock::now();
|
||||||
log_info("Initial placement time %.02fs\n", std::chrono::duration<float>(iplace_end - iplace_start).count());
|
log_info("Initial placement time %.02fs\n",
|
||||||
auto saplace_start = std::chrono::high_resolution_clock::now();
|
std::chrono::duration<float>(iplace_end - iplace_start).count());
|
||||||
log_info("Running simulated annealing placer.\n");
|
log_info("Running simulated annealing placer.\n");
|
||||||
|
} else {
|
||||||
// Calculate metric after initial placement
|
for (auto &cell : ctx->cells) {
|
||||||
curr_metric = 0;
|
CellInfo *ci = cell.second.get();
|
||||||
curr_tns = 0;
|
if (ci->belStrength > STRENGTH_STRONG)
|
||||||
for (auto &net : ctx->nets) {
|
continue;
|
||||||
wirelen_t wl = get_net_metric(ctx, net.second.get(), MetricType::COST, curr_tns);
|
else if (ci->constr_parent != nullptr)
|
||||||
costs[net.second->udata] = CostChange{wl, -1};
|
continue;
|
||||||
curr_metric += wl;
|
else if (!ci->constr_children.empty() || ci->constr_z != ci->UNCONSTR)
|
||||||
|
chain_basis.push_back(ci);
|
||||||
|
else
|
||||||
|
autoplaced.push_back(ci);
|
||||||
}
|
}
|
||||||
|
require_legal = false;
|
||||||
|
diameter = 3;
|
||||||
|
log_info("Running simulated annealing placer for refinement.\n");
|
||||||
|
}
|
||||||
|
auto saplace_start = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
// Invoke timing analysis to obtain criticalities
|
||||||
|
if (!cfg.budgetBased)
|
||||||
|
get_criticalities(ctx, &net_crit);
|
||||||
|
|
||||||
|
// Calculate costs after initial placement
|
||||||
|
setup_costs();
|
||||||
|
curr_wirelen_cost = total_wirelen_cost();
|
||||||
|
curr_timing_cost = total_timing_cost();
|
||||||
|
last_wirelen_cost = curr_wirelen_cost;
|
||||||
|
last_timing_cost = curr_timing_cost;
|
||||||
|
|
||||||
|
wirelen_t avg_wirelen = curr_wirelen_cost;
|
||||||
|
wirelen_t min_wirelen = curr_wirelen_cost;
|
||||||
|
|
||||||
int n_no_progress = 0;
|
int n_no_progress = 0;
|
||||||
wirelen_t min_metric = curr_metric;
|
temp = refine ? 1e-7 : cfg.startTemp;
|
||||||
double avg_metric = curr_metric;
|
|
||||||
temp = 10000;
|
|
||||||
|
|
||||||
// Main simulated annealing loop
|
// Main simulated annealing loop
|
||||||
for (int iter = 1;; iter++) {
|
for (int iter = 1;; iter++) {
|
||||||
@ -191,9 +269,9 @@ class SAPlacer
|
|||||||
improved = false;
|
improved = false;
|
||||||
|
|
||||||
if (iter % 5 == 0 || iter == 1)
|
if (iter % 5 == 0 || iter == 1)
|
||||||
log_info(" at iteration #%d: temp = %f, cost = "
|
log_info(" at iteration #%d: temp = %f, timing cost = "
|
||||||
"%.0f, est tns = %.02fns\n",
|
"%.0f, wirelen = %.0f\n",
|
||||||
iter, temp, double(curr_metric), curr_tns);
|
iter, temp, double(curr_timing_cost), double(curr_wirelen_cost));
|
||||||
|
|
||||||
for (int m = 0; m < 15; ++m) {
|
for (int m = 0; m < 15; ++m) {
|
||||||
// Loop through all automatically placed cells
|
// Loop through all automatically placed cells
|
||||||
@ -205,10 +283,17 @@ class SAPlacer
|
|||||||
if (try_bel != BelId() && try_bel != cell->bel)
|
if (try_bel != BelId() && try_bel != cell->bel)
|
||||||
try_swap_position(cell, try_bel);
|
try_swap_position(cell, try_bel);
|
||||||
}
|
}
|
||||||
|
// Also try swapping chains, if applicable
|
||||||
|
for (auto cb : chain_basis) {
|
||||||
|
Loc chain_base_loc = ctx->getBelLocation(cb->bel);
|
||||||
|
BelId try_base = random_bel_for_cell(cb, chain_base_loc.z);
|
||||||
|
if (try_base != BelId() && try_base != cb->bel)
|
||||||
|
try_swap_chain(cb, try_base);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (curr_metric < min_metric) {
|
if (curr_wirelen_cost < min_wirelen) {
|
||||||
min_metric = curr_metric;
|
min_wirelen = curr_wirelen_cost;
|
||||||
improved = true;
|
improved = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,9 +303,10 @@ class SAPlacer
|
|||||||
else
|
else
|
||||||
n_no_progress++;
|
n_no_progress++;
|
||||||
|
|
||||||
if (temp <= 1e-3 && n_no_progress >= 5) {
|
if (temp <= 1e-7 && n_no_progress >= (refine ? 1 : 5)) {
|
||||||
if (iter % 5 != 0)
|
log_info(" at iteration #%d: temp = %f, timing cost = "
|
||||||
log_info(" at iteration #%d: temp = %f, cost = %f\n", iter, temp, double(curr_metric));
|
"%.0f, wirelen = %.0f \n",
|
||||||
|
iter, temp, double(curr_timing_cost), double(curr_wirelen_cost));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,61 +314,64 @@ class SAPlacer
|
|||||||
|
|
||||||
int M = std::max(max_x, max_y) + 1;
|
int M = std::max(max_x, max_y) + 1;
|
||||||
|
|
||||||
double upper = 0.6, lower = 0.4;
|
if (ctx->verbose)
|
||||||
|
log("iter #%d: temp = %f, timing cost = "
|
||||||
|
"%.0f, wirelen = %.0f, dia = %d, Ra = %.02f \n",
|
||||||
|
iter, temp, double(curr_timing_cost), double(curr_wirelen_cost), diameter, Raccept);
|
||||||
|
|
||||||
if (curr_metric < 0.95 * avg_metric && curr_metric > 0) {
|
if (curr_wirelen_cost < 0.95 * avg_wirelen && curr_wirelen_cost > 0) {
|
||||||
avg_metric = 0.8 * avg_metric + 0.2 * curr_metric;
|
avg_wirelen = 0.8 * avg_wirelen + 0.2 * curr_wirelen_cost;
|
||||||
} else {
|
} else {
|
||||||
if (Raccept >= 0.8) {
|
double diam_next = diameter * (1.0 - 0.44 + Raccept);
|
||||||
temp *= 0.7;
|
diameter = std::max<int>(1, std::min<int>(M, int(diam_next + 0.5)));
|
||||||
} else if (Raccept > upper) {
|
if (Raccept > 0.96) {
|
||||||
if (diameter < M)
|
temp *= 0.5;
|
||||||
diameter++;
|
} else if (Raccept > 0.8) {
|
||||||
else
|
|
||||||
temp *= 0.9;
|
temp *= 0.9;
|
||||||
} else if (Raccept > lower) {
|
} else if (Raccept > 0.15 && diameter > 1) {
|
||||||
temp *= 0.95;
|
temp *= 0.95;
|
||||||
} else {
|
} else {
|
||||||
// Raccept < 0.3
|
|
||||||
if (diameter > 1)
|
|
||||||
diameter--;
|
|
||||||
else
|
|
||||||
temp *= 0.8;
|
temp *= 0.8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Once cooled below legalise threshold, run legalisation and start requiring
|
// Once cooled below legalise threshold, run legalisation and start requiring
|
||||||
// legal moves only
|
// legal moves only
|
||||||
if (temp < legalise_temp && require_legal) {
|
if (diameter < legalise_dia && require_legal) {
|
||||||
if (legalise_relative_constraints(ctx)) {
|
if (legalise_relative_constraints(ctx)) {
|
||||||
// Only increase temperature if something was moved
|
// Only increase temperature if something was moved
|
||||||
autoplaced.clear();
|
autoplaced.clear();
|
||||||
|
chain_basis.clear();
|
||||||
for (auto cell : sorted(ctx->cells)) {
|
for (auto cell : sorted(ctx->cells)) {
|
||||||
if (cell.second->belStrength < STRENGTH_STRONG)
|
if (cell.second->belStrength <= STRENGTH_STRONG && cell.second->constr_parent == nullptr &&
|
||||||
|
!cell.second->constr_children.empty())
|
||||||
|
chain_basis.push_back(cell.second);
|
||||||
|
else if (cell.second->belStrength < STRENGTH_STRONG)
|
||||||
autoplaced.push_back(cell.second);
|
autoplaced.push_back(cell.second);
|
||||||
}
|
}
|
||||||
temp = post_legalise_temp;
|
// temp = post_legalise_temp;
|
||||||
diameter *= post_legalise_dia_scale;
|
// diameter = std::min<int>(M, diameter * post_legalise_dia_scale);
|
||||||
ctx->shuffle(autoplaced);
|
ctx->shuffle(autoplaced);
|
||||||
|
|
||||||
// Legalisation is a big change so force a slack redistribution here
|
// Legalisation is a big change so force a slack redistribution here
|
||||||
if (ctx->slack_redist_iter > 0)
|
if (ctx->slack_redist_iter > 0 && cfg.budgetBased)
|
||||||
assign_budget(ctx, true /* quiet */);
|
assign_budget(ctx, true /* quiet */);
|
||||||
}
|
}
|
||||||
require_legal = false;
|
require_legal = false;
|
||||||
} else if (ctx->slack_redist_iter > 0 && iter % ctx->slack_redist_iter == 0) {
|
} else if (cfg.budgetBased && ctx->slack_redist_iter > 0 && iter % ctx->slack_redist_iter == 0) {
|
||||||
assign_budget(ctx, true /* quiet */);
|
assign_budget(ctx, true /* quiet */);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Invoke timing analysis to obtain criticalities
|
||||||
|
if (!cfg.budgetBased && ctx->timing_driven)
|
||||||
|
get_criticalities(ctx, &net_crit);
|
||||||
|
// Need to rebuild costs after criticalities change
|
||||||
|
setup_costs();
|
||||||
// Recalculate total metric entirely to avoid rounding errors
|
// Recalculate total metric entirely to avoid rounding errors
|
||||||
// accumulating over time
|
// accumulating over time
|
||||||
curr_metric = 0;
|
curr_wirelen_cost = total_wirelen_cost();
|
||||||
curr_tns = 0;
|
curr_timing_cost = total_timing_cost();
|
||||||
for (auto &net : ctx->nets) {
|
last_wirelen_cost = curr_wirelen_cost;
|
||||||
wirelen_t wl = get_net_metric(ctx, net.second.get(), MetricType::COST, curr_tns);
|
last_timing_cost = curr_timing_cost;
|
||||||
costs[net.second->udata] = CostChange{wl, -1};
|
|
||||||
curr_metric += wl;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Let the UI show visualization updates.
|
// Let the UI show visualization updates.
|
||||||
ctx->yield();
|
ctx->yield();
|
||||||
}
|
}
|
||||||
@ -334,7 +423,8 @@ class SAPlacer
|
|||||||
ctx->unbindBel(cell->bel);
|
ctx->unbindBel(cell->bel);
|
||||||
}
|
}
|
||||||
IdString targetType = cell->type;
|
IdString targetType = cell->type;
|
||||||
for (auto bel : ctx->getBels()) {
|
|
||||||
|
auto proc_bel = [&](BelId bel) {
|
||||||
if (ctx->getBelType(bel) == targetType && ctx->isValidBelForCell(cell, bel)) {
|
if (ctx->getBelType(bel) == targetType && ctx->isValidBelForCell(cell, bel)) {
|
||||||
if (ctx->checkBelAvail(bel)) {
|
if (ctx->checkBelAvail(bel)) {
|
||||||
uint64_t score = ctx->rng64();
|
uint64_t score = ctx->rng64();
|
||||||
@ -352,7 +442,18 @@ class SAPlacer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (cell->region != nullptr && cell->region->constr_bels) {
|
||||||
|
for (auto bel : cell->region->bels) {
|
||||||
|
proc_bel(bel);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
for (auto bel : ctx->getBels()) {
|
||||||
|
proc_bel(bel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (best_bel == BelId()) {
|
if (best_bel == BelId()) {
|
||||||
if (iters == 0 || ripup_bel == BelId())
|
if (iters == 0 || ripup_bel == BelId())
|
||||||
log_error("failed to place cell '%s' of type '%s'\n", cell->name.c_str(ctx), cell->type.c_str(ctx));
|
log_error("failed to place cell '%s' of type '%s'\n", cell->name.c_str(ctx), cell->type.c_str(ctx));
|
||||||
@ -373,49 +474,38 @@ class SAPlacer
|
|||||||
// Attempt a SA position swap, return true on success or false on failure
|
// Attempt a SA position swap, return true on success or false on failure
|
||||||
bool try_swap_position(CellInfo *cell, BelId newBel)
|
bool try_swap_position(CellInfo *cell, BelId newBel)
|
||||||
{
|
{
|
||||||
static std::vector<NetInfo *> updates;
|
static const double epsilon = 1e-20;
|
||||||
updates.clear();
|
moveChange.reset();
|
||||||
|
if (!require_legal && is_constrained(cell))
|
||||||
|
return false;
|
||||||
BelId oldBel = cell->bel;
|
BelId oldBel = cell->bel;
|
||||||
CellInfo *other_cell = ctx->getBoundBelCell(newBel);
|
CellInfo *other_cell = ctx->getBoundBelCell(newBel);
|
||||||
if (other_cell != nullptr && other_cell->belStrength > STRENGTH_WEAK) {
|
if (!require_legal && other_cell != nullptr &&
|
||||||
|
(is_constrained(other_cell) || other_cell->belStrength > STRENGTH_WEAK)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
int old_dist = get_constraints_distance(ctx, cell);
|
int old_dist = get_constraints_distance(ctx, cell);
|
||||||
int new_dist;
|
int new_dist;
|
||||||
if (other_cell != nullptr)
|
if (other_cell != nullptr)
|
||||||
old_dist += get_constraints_distance(ctx, other_cell);
|
old_dist += get_constraints_distance(ctx, other_cell);
|
||||||
wirelen_t new_metric = 0, delta;
|
double delta = 0;
|
||||||
ctx->unbindBel(oldBel);
|
ctx->unbindBel(oldBel);
|
||||||
if (other_cell != nullptr) {
|
if (other_cell != nullptr) {
|
||||||
ctx->unbindBel(newBel);
|
ctx->unbindBel(newBel);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto &port : cell->ports) {
|
|
||||||
if (port.second.net != nullptr) {
|
|
||||||
auto &cost = costs[port.second.net->udata];
|
|
||||||
if (cost.new_cost == 0)
|
|
||||||
continue;
|
|
||||||
cost.new_cost = 0;
|
|
||||||
updates.emplace_back(port.second.net);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (other_cell != nullptr) {
|
|
||||||
for (const auto &port : other_cell->ports)
|
|
||||||
if (port.second.net != nullptr) {
|
|
||||||
auto &cost = costs[port.second.net->udata];
|
|
||||||
if (cost.new_cost == 0)
|
|
||||||
continue;
|
|
||||||
cost.new_cost = 0;
|
|
||||||
updates.emplace_back(port.second.net);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx->bindBel(newBel, cell, STRENGTH_WEAK);
|
ctx->bindBel(newBel, cell, STRENGTH_WEAK);
|
||||||
|
|
||||||
if (other_cell != nullptr) {
|
if (other_cell != nullptr) {
|
||||||
ctx->bindBel(oldBel, other_cell, STRENGTH_WEAK);
|
ctx->bindBel(oldBel, other_cell, STRENGTH_WEAK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_move_cell(moveChange, cell, oldBel);
|
||||||
|
|
||||||
|
if (other_cell != nullptr) {
|
||||||
|
add_move_cell(moveChange, other_cell, newBel);
|
||||||
|
}
|
||||||
|
|
||||||
if (!ctx->isBelLocationValid(newBel) || ((other_cell != nullptr && !ctx->isBelLocationValid(oldBel)))) {
|
if (!ctx->isBelLocationValid(newBel) || ((other_cell != nullptr && !ctx->isBelLocationValid(oldBel)))) {
|
||||||
ctx->unbindBel(newBel);
|
ctx->unbindBel(newBel);
|
||||||
if (other_cell != nullptr)
|
if (other_cell != nullptr)
|
||||||
@ -423,26 +513,18 @@ class SAPlacer
|
|||||||
goto swap_fail;
|
goto swap_fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
new_metric = curr_metric;
|
|
||||||
|
|
||||||
// Recalculate metrics for all nets touched by the peturbation
|
// Recalculate metrics for all nets touched by the peturbation
|
||||||
for (const auto &net : updates) {
|
compute_cost_changes(moveChange);
|
||||||
auto &c = costs[net->udata];
|
|
||||||
new_metric -= c.curr_cost;
|
|
||||||
float temp_tns = 0;
|
|
||||||
wirelen_t net_new_wl = get_net_metric(ctx, net, MetricType::COST, temp_tns);
|
|
||||||
new_metric += net_new_wl;
|
|
||||||
c.new_cost = net_new_wl;
|
|
||||||
}
|
|
||||||
|
|
||||||
new_dist = get_constraints_distance(ctx, cell);
|
new_dist = get_constraints_distance(ctx, cell);
|
||||||
if (other_cell != nullptr)
|
if (other_cell != nullptr)
|
||||||
new_dist += get_constraints_distance(ctx, other_cell);
|
new_dist += get_constraints_distance(ctx, other_cell);
|
||||||
delta = new_metric - curr_metric;
|
delta = lambda * (moveChange.timing_delta / std::max<double>(last_timing_cost, epsilon)) +
|
||||||
delta += (cfg.constraintWeight / temp) * (new_dist - old_dist);
|
(1 - lambda) * (double(moveChange.wirelen_delta) / std::max<double>(last_wirelen_cost, epsilon));
|
||||||
|
delta += (cfg.constraintWeight / temp) * (new_dist - old_dist) / last_wirelen_cost;
|
||||||
n_move++;
|
n_move++;
|
||||||
// SA acceptance criterea
|
// SA acceptance criterea
|
||||||
if (delta < 0 || (temp > 1e-6 && (ctx->rng() / float(0x3fffffff)) <= std::exp(-delta / temp))) {
|
if (delta < 0 || (temp > 1e-8 && (ctx->rng() / float(0x3fffffff)) <= std::exp(-delta / temp))) {
|
||||||
n_accept++;
|
n_accept++;
|
||||||
} else {
|
} else {
|
||||||
if (other_cell != nullptr)
|
if (other_cell != nullptr)
|
||||||
@ -450,32 +532,148 @@ class SAPlacer
|
|||||||
ctx->unbindBel(newBel);
|
ctx->unbindBel(newBel);
|
||||||
goto swap_fail;
|
goto swap_fail;
|
||||||
}
|
}
|
||||||
curr_metric = new_metric;
|
commit_cost_changes(moveChange);
|
||||||
for (const auto &net : updates) {
|
#if 0
|
||||||
auto &c = costs[net->udata];
|
log_info("swap %s -> %s\n", cell->name.c_str(ctx), ctx->getBelName(newBel).c_str(ctx));
|
||||||
c = CostChange{c.new_cost, -1};
|
if (other_cell != nullptr)
|
||||||
}
|
log_info("swap %s -> %s\n", other_cell->name.c_str(ctx), ctx->getBelName(oldBel).c_str(ctx));
|
||||||
|
#endif
|
||||||
return true;
|
return true;
|
||||||
swap_fail:
|
swap_fail:
|
||||||
ctx->bindBel(oldBel, cell, STRENGTH_WEAK);
|
ctx->bindBel(oldBel, cell, STRENGTH_WEAK);
|
||||||
if (other_cell != nullptr) {
|
if (other_cell != nullptr) {
|
||||||
ctx->bindBel(newBel, other_cell, STRENGTH_WEAK);
|
ctx->bindBel(newBel, other_cell, STRENGTH_WEAK);
|
||||||
}
|
}
|
||||||
for (const auto &net : updates)
|
return false;
|
||||||
costs[net->udata].new_cost = -1;
|
}
|
||||||
|
|
||||||
|
inline bool is_constrained(CellInfo *cell)
|
||||||
|
{
|
||||||
|
return cell->constr_parent != nullptr || !cell->constr_children.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap the Bel of a cell with another, return the original location
|
||||||
|
BelId swap_cell_bels(CellInfo *cell, BelId newBel)
|
||||||
|
{
|
||||||
|
BelId oldBel = cell->bel;
|
||||||
|
#if 0
|
||||||
|
log_info("%s old: %s new: %s\n", cell->name.c_str(ctx), ctx->getBelName(cell->bel).c_str(ctx), ctx->getBelName(newBel).c_str(ctx));
|
||||||
|
#endif
|
||||||
|
CellInfo *bound = ctx->getBoundBelCell(newBel);
|
||||||
|
if (bound != nullptr)
|
||||||
|
ctx->unbindBel(newBel);
|
||||||
|
ctx->unbindBel(oldBel);
|
||||||
|
ctx->bindBel(newBel, cell, is_constrained(cell) ? STRENGTH_STRONG : STRENGTH_WEAK);
|
||||||
|
if (bound != nullptr)
|
||||||
|
ctx->bindBel(oldBel, bound, is_constrained(bound) ? STRENGTH_STRONG : STRENGTH_WEAK);
|
||||||
|
return oldBel;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discover the relative positions of all cells in a chain
|
||||||
|
void discover_chain(Loc baseLoc, CellInfo *cell, std::vector<std::pair<CellInfo *, Loc>> &cell_rel)
|
||||||
|
{
|
||||||
|
Loc cellLoc = ctx->getBelLocation(cell->bel);
|
||||||
|
Loc rel{cellLoc.x - baseLoc.x, cellLoc.y - baseLoc.y, cellLoc.z};
|
||||||
|
cell_rel.emplace_back(std::make_pair(cell, rel));
|
||||||
|
for (auto child : cell->constr_children)
|
||||||
|
discover_chain(baseLoc, child, cell_rel);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to swap a chain with a non-chain
|
||||||
|
bool try_swap_chain(CellInfo *cell, BelId newBase)
|
||||||
|
{
|
||||||
|
std::vector<std::pair<CellInfo *, Loc>> cell_rel;
|
||||||
|
std::unordered_set<IdString> cells;
|
||||||
|
std::vector<std::pair<CellInfo *, BelId>> moves_made;
|
||||||
|
std::vector<std::pair<CellInfo *, BelId>> dest_bels;
|
||||||
|
double delta = 0;
|
||||||
|
moveChange.reset();
|
||||||
|
if (ctx->debug)
|
||||||
|
log_info("finding cells for chain swap %s\n", cell->name.c_str(ctx));
|
||||||
|
|
||||||
|
Loc baseLoc = ctx->getBelLocation(cell->bel);
|
||||||
|
discover_chain(baseLoc, cell, cell_rel);
|
||||||
|
Loc newBaseLoc = ctx->getBelLocation(newBase);
|
||||||
|
NPNR_ASSERT(newBaseLoc.z == baseLoc.z);
|
||||||
|
for (const auto &cr : cell_rel)
|
||||||
|
cells.insert(cr.first->name);
|
||||||
|
|
||||||
|
for (const auto &cr : cell_rel) {
|
||||||
|
Loc targetLoc = {newBaseLoc.x + cr.second.x, newBaseLoc.y + cr.second.y, cr.second.z};
|
||||||
|
BelId targetBel = ctx->getBelByLocation(targetLoc);
|
||||||
|
if (targetBel == BelId())
|
||||||
|
return false;
|
||||||
|
if (ctx->getBelType(targetBel) != cell->type)
|
||||||
|
return false;
|
||||||
|
CellInfo *bound = ctx->getBoundBelCell(targetBel);
|
||||||
|
// We don't consider swapping chains with other chains, at least for the time being - unless it is
|
||||||
|
// part of this chain
|
||||||
|
if (bound != nullptr && !cells.count(bound->name) &&
|
||||||
|
(bound->belStrength >= STRENGTH_STRONG || is_constrained(bound)))
|
||||||
|
return false;
|
||||||
|
dest_bels.emplace_back(std::make_pair(cr.first, targetBel));
|
||||||
|
}
|
||||||
|
if (ctx->debug)
|
||||||
|
log_info("trying chain swap %s\n", cell->name.c_str(ctx));
|
||||||
|
// <cell, oldBel>
|
||||||
|
for (const auto &db : dest_bels) {
|
||||||
|
BelId oldBel = swap_cell_bels(db.first, db.second);
|
||||||
|
moves_made.emplace_back(std::make_pair(db.first, oldBel));
|
||||||
|
}
|
||||||
|
for (const auto &mm : moves_made) {
|
||||||
|
if (!ctx->isBelLocationValid(mm.first->bel) || !check_cell_bel_region(mm.first, mm.first->bel))
|
||||||
|
goto swap_fail;
|
||||||
|
if (!ctx->isBelLocationValid(mm.second))
|
||||||
|
goto swap_fail;
|
||||||
|
CellInfo *bound = ctx->getBoundBelCell(mm.second);
|
||||||
|
if (bound && !check_cell_bel_region(bound, bound->bel))
|
||||||
|
goto swap_fail;
|
||||||
|
add_move_cell(moveChange, mm.first, mm.second);
|
||||||
|
if (bound != nullptr)
|
||||||
|
add_move_cell(moveChange, bound, mm.first->bel);
|
||||||
|
}
|
||||||
|
compute_cost_changes(moveChange);
|
||||||
|
delta = lambda * (moveChange.timing_delta / last_timing_cost) +
|
||||||
|
(1 - lambda) * (double(moveChange.wirelen_delta) / last_wirelen_cost);
|
||||||
|
n_move++;
|
||||||
|
// SA acceptance criterea
|
||||||
|
if (delta < 0 || (temp > 1e-9 && (ctx->rng() / float(0x3fffffff)) <= std::exp(-delta / temp))) {
|
||||||
|
n_accept++;
|
||||||
|
if (ctx->debug)
|
||||||
|
log_info("accepted chain swap %s\n", cell->name.c_str(ctx));
|
||||||
|
} else {
|
||||||
|
goto swap_fail;
|
||||||
|
}
|
||||||
|
commit_cost_changes(moveChange);
|
||||||
|
return true;
|
||||||
|
swap_fail:
|
||||||
|
for (const auto &entry : boost::adaptors::reverse(moves_made))
|
||||||
|
swap_cell_bels(entry.first, entry.second);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find a random Bel of the correct type for a cell, within the specified
|
// Find a random Bel of the correct type for a cell, within the specified
|
||||||
// diameter
|
// diameter
|
||||||
BelId random_bel_for_cell(CellInfo *cell)
|
BelId random_bel_for_cell(CellInfo *cell, int force_z = -1)
|
||||||
{
|
{
|
||||||
IdString targetType = cell->type;
|
IdString targetType = cell->type;
|
||||||
Loc curr_loc = ctx->getBelLocation(cell->bel);
|
Loc curr_loc = ctx->getBelLocation(cell->bel);
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
int dx = diameter, dy = diameter;
|
||||||
|
if (cell->region != nullptr && cell->region->constr_bels) {
|
||||||
|
dx = std::min(diameter, (region_bounds[cell->region->name].x1 - region_bounds[cell->region->name].x0) + 1);
|
||||||
|
dy = std::min(diameter, (region_bounds[cell->region->name].y1 - region_bounds[cell->region->name].y0) + 1);
|
||||||
|
// Clamp location to within bounds
|
||||||
|
curr_loc.x = std::max(region_bounds[cell->region->name].x0, curr_loc.x);
|
||||||
|
curr_loc.x = std::min(region_bounds[cell->region->name].x1, curr_loc.x);
|
||||||
|
curr_loc.y = std::max(region_bounds[cell->region->name].y0, curr_loc.y);
|
||||||
|
curr_loc.y = std::min(region_bounds[cell->region->name].y1, curr_loc.y);
|
||||||
|
}
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
int nx = ctx->rng(2 * diameter + 1) + std::max(curr_loc.x - diameter, 0);
|
int nx = ctx->rng(2 * dx + 1) + std::max(curr_loc.x - dx, 0);
|
||||||
int ny = ctx->rng(2 * diameter + 1) + std::max(curr_loc.y - diameter, 0);
|
int ny = ctx->rng(2 * dy + 1) + std::max(curr_loc.y - dy, 0);
|
||||||
int beltype_idx, beltype_cnt;
|
int beltype_idx, beltype_cnt;
|
||||||
std::tie(beltype_idx, beltype_cnt) = bel_types.at(targetType);
|
std::tie(beltype_idx, beltype_cnt) = bel_types.at(targetType);
|
||||||
if (beltype_cnt < cfg.minBelsForGridPick)
|
if (beltype_cnt < cfg.minBelsForGridPick)
|
||||||
@ -488,41 +686,262 @@ class SAPlacer
|
|||||||
if (fb.size() == 0)
|
if (fb.size() == 0)
|
||||||
continue;
|
continue;
|
||||||
BelId bel = fb.at(ctx->rng(int(fb.size())));
|
BelId bel = fb.at(ctx->rng(int(fb.size())));
|
||||||
|
if (force_z != -1) {
|
||||||
|
Loc loc = ctx->getBelLocation(bel);
|
||||||
|
if (loc.z != force_z)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!check_cell_bel_region(cell, bel))
|
||||||
|
continue;
|
||||||
if (locked_bels.find(bel) != locked_bels.end())
|
if (locked_bels.find(bel) != locked_bels.end())
|
||||||
continue;
|
continue;
|
||||||
|
count++;
|
||||||
return bel;
|
return bel;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return true if a net is to be entirely ignored
|
||||||
|
inline bool ignore_net(NetInfo *net)
|
||||||
|
{
|
||||||
|
return net->driver.cell == nullptr || net->driver.cell->bel == BelId() ||
|
||||||
|
ctx->getBelGlobalBuf(net->driver.cell->bel);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the bounding box for a net
|
||||||
|
inline BoundingBox get_net_bounds(NetInfo *net)
|
||||||
|
{
|
||||||
|
BoundingBox bb;
|
||||||
|
NPNR_ASSERT(net->driver.cell != nullptr);
|
||||||
|
Loc dloc = ctx->getBelLocation(net->driver.cell->bel);
|
||||||
|
bb.x0 = dloc.x;
|
||||||
|
bb.x1 = dloc.x;
|
||||||
|
bb.y0 = dloc.y;
|
||||||
|
bb.y1 = dloc.y;
|
||||||
|
|
||||||
|
for (auto user : net->users) {
|
||||||
|
if (user.cell->bel == BelId())
|
||||||
|
continue;
|
||||||
|
Loc uloc = ctx->getBelLocation(user.cell->bel);
|
||||||
|
bb.x0 = std::min(bb.x0, uloc.x);
|
||||||
|
bb.x1 = std::max(bb.x1, uloc.x);
|
||||||
|
bb.y0 = std::min(bb.y0, uloc.y);
|
||||||
|
bb.y1 = std::max(bb.y1, uloc.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bb;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the timing cost for an arc of a net
|
||||||
|
inline double get_timing_cost(NetInfo *net, size_t user)
|
||||||
|
{
|
||||||
|
int cc;
|
||||||
|
if (net->driver.cell == nullptr)
|
||||||
|
return 0;
|
||||||
|
if (ctx->getPortTimingClass(net->driver.cell, net->driver.port, cc) == TMG_IGNORE)
|
||||||
|
return 0;
|
||||||
|
if (cfg.budgetBased) {
|
||||||
|
double delay = ctx->getDelayNS(ctx->predictDelay(net, net->users.at(user)));
|
||||||
|
return std::min(10.0, std::exp(delay - ctx->getDelayNS(net->users.at(user).budget) / 10));
|
||||||
|
} else {
|
||||||
|
auto crit = net_crit.find(net->name);
|
||||||
|
if (crit == net_crit.end() || crit->second.criticality.empty())
|
||||||
|
return 0;
|
||||||
|
double delay = ctx->getDelayNS(ctx->predictDelay(net, net->users.at(user)));
|
||||||
|
return delay * std::pow(crit->second.criticality.at(user), crit_exp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up the cost maps
|
||||||
|
void setup_costs()
|
||||||
|
{
|
||||||
|
for (auto net : sorted(ctx->nets)) {
|
||||||
|
NetInfo *ni = net.second;
|
||||||
|
if (ignore_net(ni))
|
||||||
|
continue;
|
||||||
|
net_bounds[ni->udata] = get_net_bounds(ni);
|
||||||
|
if (ctx->timing_driven && int(ni->users.size()) < cfg.timingFanoutThresh)
|
||||||
|
for (size_t i = 0; i < ni->users.size(); i++)
|
||||||
|
net_arc_tcost[ni->udata][i] = get_timing_cost(ni, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the total wiring cost for the design
|
||||||
|
wirelen_t total_wirelen_cost()
|
||||||
|
{
|
||||||
|
wirelen_t cost = 0;
|
||||||
|
for (const auto &net : net_bounds)
|
||||||
|
cost += net.hpwl();
|
||||||
|
return cost;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the total timing cost for the design
|
||||||
|
double total_timing_cost()
|
||||||
|
{
|
||||||
|
double cost = 0;
|
||||||
|
for (const auto &net : net_arc_tcost) {
|
||||||
|
for (auto arc_cost : net) {
|
||||||
|
cost += arc_cost;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cost;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cost-change-related data for a move
|
||||||
|
struct MoveChangeData
|
||||||
|
{
|
||||||
|
std::vector<decltype(NetInfo::udata)> bounds_changed_nets;
|
||||||
|
std::vector<std::pair<decltype(NetInfo::udata), size_t>> changed_arcs;
|
||||||
|
|
||||||
|
std::vector<bool> already_bounds_changed;
|
||||||
|
std::vector<std::vector<bool>> already_changed_arcs;
|
||||||
|
|
||||||
|
std::vector<std::pair<decltype(NetInfo::udata), BoundingBox>> new_net_bounds;
|
||||||
|
std::vector<std::pair<std::pair<decltype(NetInfo::udata), size_t>, double>> new_arc_costs;
|
||||||
|
|
||||||
|
wirelen_t wirelen_delta = 0;
|
||||||
|
double timing_delta = 0;
|
||||||
|
|
||||||
|
void reset()
|
||||||
|
{
|
||||||
|
for (auto bc : bounds_changed_nets)
|
||||||
|
already_bounds_changed[bc] = false;
|
||||||
|
for (const auto &tc : changed_arcs)
|
||||||
|
already_changed_arcs[tc.first][tc.second] = false;
|
||||||
|
bounds_changed_nets.clear();
|
||||||
|
changed_arcs.clear();
|
||||||
|
new_net_bounds.clear();
|
||||||
|
new_arc_costs.clear();
|
||||||
|
wirelen_delta = 0;
|
||||||
|
timing_delta = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} moveChange;
|
||||||
|
|
||||||
|
void add_move_cell(MoveChangeData &mc, CellInfo *cell, BelId old_bel)
|
||||||
|
{
|
||||||
|
Loc curr_loc = ctx->getBelLocation(cell->bel);
|
||||||
|
Loc old_loc = ctx->getBelLocation(old_bel);
|
||||||
|
// Check net bounds
|
||||||
|
for (const auto &port : cell->ports) {
|
||||||
|
NetInfo *pn = port.second.net;
|
||||||
|
if (pn == nullptr)
|
||||||
|
continue;
|
||||||
|
if (ignore_net(pn))
|
||||||
|
continue;
|
||||||
|
const BoundingBox &curr_bounds = net_bounds[pn->udata];
|
||||||
|
// If the old location was at the edge of the bounds, or the new location exceeds the bounds,
|
||||||
|
// an update is needed
|
||||||
|
if (curr_bounds.touches_bounds(old_loc.x, old_loc.y) || !curr_bounds.is_inside_inc(curr_loc.x, curr_loc.y))
|
||||||
|
if (!mc.already_bounds_changed[pn->udata]) {
|
||||||
|
mc.bounds_changed_nets.push_back(pn->udata);
|
||||||
|
mc.already_bounds_changed[pn->udata] = true;
|
||||||
|
}
|
||||||
|
if (ctx->timing_driven && int(pn->users.size()) < cfg.timingFanoutThresh) {
|
||||||
|
// Output ports - all arcs change timing
|
||||||
|
if (port.second.type == PORT_OUT) {
|
||||||
|
int cc;
|
||||||
|
TimingPortClass cls = ctx->getPortTimingClass(cell, port.first, cc);
|
||||||
|
if (cls != TMG_IGNORE)
|
||||||
|
for (size_t i = 0; i < pn->users.size(); i++)
|
||||||
|
if (!mc.already_changed_arcs[pn->udata][i]) {
|
||||||
|
mc.changed_arcs.emplace_back(std::make_pair(pn->udata, i));
|
||||||
|
mc.already_changed_arcs[pn->udata][i] = true;
|
||||||
|
}
|
||||||
|
} else if (port.second.type == PORT_IN) {
|
||||||
|
auto usr = fast_port_to_user.at(&port.second);
|
||||||
|
if (!mc.already_changed_arcs[pn->udata][usr]) {
|
||||||
|
mc.changed_arcs.emplace_back(std::make_pair(pn->udata, usr));
|
||||||
|
mc.already_changed_arcs[pn->udata][usr] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void compute_cost_changes(MoveChangeData &md)
|
||||||
|
{
|
||||||
|
for (const auto &bc : md.bounds_changed_nets) {
|
||||||
|
wirelen_t old_hpwl = net_bounds.at(bc).hpwl();
|
||||||
|
auto bounds = get_net_bounds(net_by_udata.at(bc));
|
||||||
|
md.new_net_bounds.emplace_back(std::make_pair(bc, bounds));
|
||||||
|
md.wirelen_delta += (bounds.hpwl() - old_hpwl);
|
||||||
|
md.already_bounds_changed[bc] = false;
|
||||||
|
}
|
||||||
|
if (ctx->timing_driven) {
|
||||||
|
for (const auto &tc : md.changed_arcs) {
|
||||||
|
double old_cost = net_arc_tcost.at(tc.first).at(tc.second);
|
||||||
|
double new_cost = get_timing_cost(net_by_udata.at(tc.first), tc.second);
|
||||||
|
md.new_arc_costs.emplace_back(std::make_pair(tc, new_cost));
|
||||||
|
md.timing_delta += (new_cost - old_cost);
|
||||||
|
md.already_changed_arcs[tc.first][tc.second] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void commit_cost_changes(MoveChangeData &md)
|
||||||
|
{
|
||||||
|
for (const auto &bc : md.new_net_bounds)
|
||||||
|
net_bounds[bc.first] = bc.second;
|
||||||
|
for (const auto &tc : md.new_arc_costs)
|
||||||
|
net_arc_tcost[tc.first.first].at(tc.first.second) = tc.second;
|
||||||
|
curr_wirelen_cost += md.wirelen_delta;
|
||||||
|
curr_timing_cost += md.timing_delta;
|
||||||
|
}
|
||||||
|
// Build the cell port -> user index
|
||||||
|
void build_port_index()
|
||||||
|
{
|
||||||
|
for (auto net : sorted(ctx->nets)) {
|
||||||
|
NetInfo *ni = net.second;
|
||||||
|
for (size_t i = 0; i < ni->users.size(); i++) {
|
||||||
|
auto &usr = ni->users.at(i);
|
||||||
|
fast_port_to_user[&(usr.cell->ports.at(usr.port))] = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the combined wirelen/timing metric
|
||||||
|
inline double curr_metric() { return lambda * curr_timing_cost + (1 - lambda) * curr_wirelen_cost; }
|
||||||
|
|
||||||
|
// Map nets to their bounding box (so we can skip recompute for moves that do not exceed the bounds
|
||||||
|
std::vector<BoundingBox> net_bounds;
|
||||||
|
// Map net arcs to their timing cost (criticality * delay ns)
|
||||||
|
std::vector<std::vector<double>> net_arc_tcost;
|
||||||
|
|
||||||
|
// Fast lookup for cell port to net user index
|
||||||
|
std::unordered_map<const PortInfo *, size_t> fast_port_to_user;
|
||||||
|
|
||||||
|
// Wirelength and timing cost at last and current iteration
|
||||||
|
wirelen_t last_wirelen_cost, curr_wirelen_cost;
|
||||||
|
double last_timing_cost, curr_timing_cost;
|
||||||
|
|
||||||
|
// Criticality data from timing analysis
|
||||||
|
NetCriticalityMap net_crit;
|
||||||
|
|
||||||
Context *ctx;
|
Context *ctx;
|
||||||
wirelen_t curr_metric = std::numeric_limits<wirelen_t>::max();
|
float temp = 10;
|
||||||
float curr_tns = 0;
|
float crit_exp = 8;
|
||||||
float temp = 1000;
|
float lambda = 0.5;
|
||||||
bool improved = false;
|
bool improved = false;
|
||||||
int n_move, n_accept;
|
int n_move, n_accept;
|
||||||
int diameter = 35, max_x = 1, max_y = 1;
|
int diameter = 35, max_x = 1, max_y = 1;
|
||||||
std::unordered_map<IdString, std::tuple<int, int>> bel_types;
|
std::unordered_map<IdString, std::tuple<int, int>> bel_types;
|
||||||
|
std::unordered_map<IdString, BoundingBox> region_bounds;
|
||||||
std::vector<std::vector<std::vector<std::vector<BelId>>>> fast_bels;
|
std::vector<std::vector<std::vector<std::vector<BelId>>>> fast_bels;
|
||||||
std::unordered_set<BelId> locked_bels;
|
std::unordered_set<BelId> locked_bels;
|
||||||
bool require_legal = true;
|
std::vector<NetInfo *> net_by_udata;
|
||||||
const float legalise_temp = 1;
|
|
||||||
const float post_legalise_temp = 10;
|
|
||||||
const float post_legalise_dia_scale = 1.5;
|
|
||||||
Placer1Cfg cfg;
|
|
||||||
|
|
||||||
struct CostChange
|
|
||||||
{
|
|
||||||
wirelen_t curr_cost;
|
|
||||||
wirelen_t new_cost;
|
|
||||||
};
|
|
||||||
std::vector<CostChange> costs;
|
|
||||||
std::vector<decltype(NetInfo::udata)> old_udata;
|
std::vector<decltype(NetInfo::udata)> old_udata;
|
||||||
|
bool require_legal = true;
|
||||||
|
const int legalise_dia = 4;
|
||||||
|
Placer1Cfg cfg;
|
||||||
};
|
};
|
||||||
|
|
||||||
Placer1Cfg::Placer1Cfg(Context *ctx) : Settings(ctx)
|
Placer1Cfg::Placer1Cfg(Context *ctx) : Settings(ctx)
|
||||||
{
|
{
|
||||||
constraintWeight = get<float>("placer1/constraintWeight", 10);
|
constraintWeight = get<float>("placer1/constraintWeight", 10);
|
||||||
minBelsForGridPick = get<int>("placer1/minBelsForGridPick", 64);
|
minBelsForGridPick = get<int>("placer1/minBelsForGridPick", 64);
|
||||||
|
budgetBased = get<bool>("placer1/budgetBased", false);
|
||||||
|
startTemp = get<float>("placer1/startTemp", 1);
|
||||||
|
timingFanoutThresh = std::numeric_limits<int>::max();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool placer1(Context *ctx, Placer1Cfg cfg)
|
bool placer1(Context *ctx, Placer1Cfg cfg)
|
||||||
@ -545,4 +964,24 @@ bool placer1(Context *ctx, Placer1Cfg cfg)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool placer1_refine(Context *ctx, Placer1Cfg cfg)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
SAPlacer placer(ctx, cfg);
|
||||||
|
placer.place(true);
|
||||||
|
log_info("Checksum: 0x%08x\n", ctx->checksum());
|
||||||
|
#ifndef NDEBUG
|
||||||
|
ctx->lock();
|
||||||
|
ctx->check();
|
||||||
|
ctx->unlock();
|
||||||
|
#endif
|
||||||
|
return true;
|
||||||
|
} catch (log_execution_error_exception) {
|
||||||
|
#ifndef NDEBUG
|
||||||
|
ctx->check();
|
||||||
|
#endif
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_END
|
NEXTPNR_NAMESPACE_END
|
||||||
|
@ -29,9 +29,13 @@ struct Placer1Cfg : public Settings
|
|||||||
Placer1Cfg(Context *ctx);
|
Placer1Cfg(Context *ctx);
|
||||||
float constraintWeight;
|
float constraintWeight;
|
||||||
int minBelsForGridPick;
|
int minBelsForGridPick;
|
||||||
|
bool budgetBased;
|
||||||
|
float startTemp;
|
||||||
|
int timingFanoutThresh;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern bool placer1(Context *ctx, Placer1Cfg cfg);
|
extern bool placer1(Context *ctx, Placer1Cfg cfg);
|
||||||
|
extern bool placer1_refine(Context *ctx, Placer1Cfg cfg);
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_END
|
NEXTPNR_NAMESPACE_END
|
||||||
|
|
||||||
|
1544
common/placer_heap.cc
Normal file
1544
common/placer_heap.cc
Normal file
File diff suppressed because it is too large
Load Diff
47
common/placer_heap.h
Normal file
47
common/placer_heap.h
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* nextpnr -- Next Generation Place and Route
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019 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.
|
||||||
|
*
|
||||||
|
* [[cite]] HeAP
|
||||||
|
* Analytical Placement for Heterogeneous FPGAs, Marcel Gort and Jason H. Anderson
|
||||||
|
* https://janders.eecg.utoronto.ca/pdfs/marcelfpl12.pdf
|
||||||
|
*
|
||||||
|
* [[cite]] SimPL
|
||||||
|
* SimPL: An Effective Placement Algorithm, Myung-Chul Kim, Dong-Jin Lee and Igor L. Markov
|
||||||
|
* http://www.ece.umich.edu/cse/awards/pdfs/iccad10-simpl.pdf
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef PLACER_HEAP_H
|
||||||
|
#define PLACER_HEAP
|
||||||
|
#include "nextpnr.h"
|
||||||
|
#include "settings.h"
|
||||||
|
|
||||||
|
NEXTPNR_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
struct PlacerHeapCfg : public Settings
|
||||||
|
{
|
||||||
|
PlacerHeapCfg(Context *ctx);
|
||||||
|
|
||||||
|
float alpha;
|
||||||
|
float criticalityExponent;
|
||||||
|
float timingWeight;
|
||||||
|
|
||||||
|
std::unordered_set<IdString> ioBufTypes;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern bool placer_heap(Context *ctx, PlacerHeapCfg cfg);
|
||||||
|
NEXTPNR_NAMESPACE_END
|
||||||
|
#endif
|
@ -104,6 +104,7 @@ BOOST_PYTHON_MODULE(MODULE_NAME)
|
|||||||
typedef std::unordered_map<IdString, std::string> AttrMap;
|
typedef std::unordered_map<IdString, std::string> AttrMap;
|
||||||
typedef std::unordered_map<IdString, PortInfo> PortMap;
|
typedef std::unordered_map<IdString, PortInfo> PortMap;
|
||||||
typedef std::unordered_map<IdString, IdString> PinMap;
|
typedef std::unordered_map<IdString, IdString> PinMap;
|
||||||
|
typedef std::unordered_map<IdString, std::unique_ptr<Region>> RegionMap;
|
||||||
|
|
||||||
class_<BaseCtx, BaseCtx *, boost::noncopyable>("BaseCtx", no_init);
|
class_<BaseCtx, BaseCtx *, boost::noncopyable>("BaseCtx", no_init);
|
||||||
|
|
||||||
@ -135,6 +136,8 @@ BOOST_PYTHON_MODULE(MODULE_NAME)
|
|||||||
|
|
||||||
typedef std::vector<PortRef> PortRefVector;
|
typedef std::vector<PortRef> PortRefVector;
|
||||||
typedef std::unordered_map<WireId, PipMap> WireMap;
|
typedef std::unordered_map<WireId, PipMap> WireMap;
|
||||||
|
typedef std::unordered_set<BelId> BelSet;
|
||||||
|
typedef std::unordered_set<WireId> WireSet;
|
||||||
|
|
||||||
auto ni_cls = class_<ContextualWrapper<NetInfo &>>("NetInfo", no_init);
|
auto ni_cls = class_<ContextualWrapper<NetInfo &>>("NetInfo", no_init);
|
||||||
readwrite_wrapper<NetInfo &, decltype(&NetInfo::name), &NetInfo::name, conv_to_str<IdString>,
|
readwrite_wrapper<NetInfo &, decltype(&NetInfo::name), &NetInfo::name, conv_to_str<IdString>,
|
||||||
@ -163,10 +166,25 @@ BOOST_PYTHON_MODULE(MODULE_NAME)
|
|||||||
def("parse_json", parse_json_shim);
|
def("parse_json", parse_json_shim);
|
||||||
def("load_design", load_design_shim, return_value_policy<manage_new_object>());
|
def("load_design", load_design_shim, return_value_policy<manage_new_object>());
|
||||||
|
|
||||||
|
auto region_cls = class_<ContextualWrapper<Region &>>("Region", no_init);
|
||||||
|
readwrite_wrapper<Region &, decltype(&Region::name), &Region::name, conv_to_str<IdString>,
|
||||||
|
conv_from_str<IdString>>::def_wrap(region_cls, "name");
|
||||||
|
readwrite_wrapper<Region &, decltype(&Region::constr_bels), &Region::constr_bels, pass_through<bool>,
|
||||||
|
pass_through<bool>>::def_wrap(region_cls, "constr_bels");
|
||||||
|
readwrite_wrapper<Region &, decltype(&Region::constr_wires), &Region::constr_wires, pass_through<bool>,
|
||||||
|
pass_through<bool>>::def_wrap(region_cls, "constr_bels");
|
||||||
|
readwrite_wrapper<Region &, decltype(&Region::constr_pips), &Region::constr_pips, pass_through<bool>,
|
||||||
|
pass_through<bool>>::def_wrap(region_cls, "constr_pips");
|
||||||
|
readonly_wrapper<Region &, decltype(&Region::bels), &Region::bels, wrap_context<BelSet &>>::def_wrap(region_cls,
|
||||||
|
"bels");
|
||||||
|
readonly_wrapper<Region &, decltype(&Region::wires), &Region::wires, wrap_context<WireSet &>>::def_wrap(region_cls,
|
||||||
|
"wires");
|
||||||
|
|
||||||
WRAP_MAP(AttrMap, pass_through<std::string>, "AttrMap");
|
WRAP_MAP(AttrMap, pass_through<std::string>, "AttrMap");
|
||||||
WRAP_MAP(PortMap, wrap_context<PortInfo &>, "PortMap");
|
WRAP_MAP(PortMap, wrap_context<PortInfo &>, "PortMap");
|
||||||
WRAP_MAP(PinMap, conv_to_str<IdString>, "PinMap");
|
WRAP_MAP(PinMap, conv_to_str<IdString>, "PinMap");
|
||||||
WRAP_MAP(WireMap, wrap_context<PipMap &>, "WireMap");
|
WRAP_MAP(WireMap, wrap_context<PipMap &>, "WireMap");
|
||||||
|
WRAP_MAP_UPTR(RegionMap, "RegionMap");
|
||||||
|
|
||||||
WRAP_VECTOR(PortRefVector, wrap_context<PortRef &>);
|
WRAP_VECTOR(PortRefVector, wrap_context<PortRef &>);
|
||||||
|
|
||||||
|
@ -345,6 +345,12 @@ template <typename T, typename value_conv> struct map_wrapper
|
|||||||
std::terminate();
|
std::terminate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool contains(wrapped_map &x, std::string const &i)
|
||||||
|
{
|
||||||
|
K k = PythonConversion::string_converter<K>().from_str(x.ctx, i);
|
||||||
|
return x.base.count(k);
|
||||||
|
}
|
||||||
|
|
||||||
static void wrap(const char *map_name, const char *kv_name, const char *kv_iter_name, const char *iter_name)
|
static void wrap(const char *map_name, const char *kv_name, const char *kv_iter_name, const char *iter_name)
|
||||||
{
|
{
|
||||||
map_pair_wrapper<typename KV::first_type, typename KV::second_type, value_conv>::wrap(kv_name, kv_iter_name);
|
map_pair_wrapper<typename KV::first_type, typename KV::second_type, value_conv>::wrap(kv_name, kv_iter_name);
|
||||||
@ -353,6 +359,7 @@ template <typename T, typename value_conv> struct map_wrapper
|
|||||||
class_<wrapped_map>(map_name, no_init)
|
class_<wrapped_map>(map_name, no_init)
|
||||||
.def("__iter__", rw::iter)
|
.def("__iter__", rw::iter)
|
||||||
.def("__len__", len)
|
.def("__len__", len)
|
||||||
|
.def("__contains__", contains)
|
||||||
.def("__getitem__", get)
|
.def("__getitem__", get)
|
||||||
.def("__setitem__", set, with_custodian_and_ward<1, 2>());
|
.def("__setitem__", set, with_custodian_and_ward<1, 2>());
|
||||||
}
|
}
|
||||||
@ -465,6 +472,12 @@ template <typename T> struct map_wrapper_uptr
|
|||||||
std::terminate();
|
std::terminate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool contains(wrapped_map &x, std::string const &i)
|
||||||
|
{
|
||||||
|
K k = PythonConversion::string_converter<K>().from_str(x.ctx, i);
|
||||||
|
return x.base.count(k);
|
||||||
|
}
|
||||||
|
|
||||||
static void wrap(const char *map_name, const char *kv_name, const char *kv_iter_name, const char *iter_name)
|
static void wrap(const char *map_name, const char *kv_name, const char *kv_iter_name, const char *iter_name)
|
||||||
{
|
{
|
||||||
map_pair_wrapper_uptr<typename KV::first_type, typename KV::second_type>::wrap(kv_name, kv_iter_name);
|
map_pair_wrapper_uptr<typename KV::first_type, typename KV::second_type>::wrap(kv_name, kv_iter_name);
|
||||||
@ -473,6 +486,7 @@ template <typename T> struct map_wrapper_uptr
|
|||||||
class_<wrapped_map>(map_name, no_init)
|
class_<wrapped_map>(map_name, no_init)
|
||||||
.def("__iter__", rw::iter)
|
.def("__iter__", rw::iter)
|
||||||
.def("__len__", len)
|
.def("__len__", len)
|
||||||
|
.def("__contains__", contains)
|
||||||
.def("__getitem__", get)
|
.def("__getitem__", get)
|
||||||
.def("__setitem__", set, with_custodian_and_ward<1, 2>());
|
.def("__setitem__", set, with_custodian_and_ward<1, 2>());
|
||||||
}
|
}
|
||||||
|
@ -269,7 +269,7 @@ template <typename Class, typename FuncT, FuncT fn, typename arg1_conv, typename
|
|||||||
template <typename WrapCls> static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); }
|
template <typename WrapCls> static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); }
|
||||||
};
|
};
|
||||||
|
|
||||||
// Three parameters, one return
|
// Three parameters, no return
|
||||||
template <typename Class, typename FuncT, FuncT fn, typename arg1_conv, typename arg2_conv, typename arg3_conv>
|
template <typename Class, typename FuncT, FuncT fn, typename arg1_conv, typename arg2_conv, typename arg3_conv>
|
||||||
struct fn_wrapper_3a_v
|
struct fn_wrapper_3a_v
|
||||||
{
|
{
|
||||||
@ -288,6 +288,30 @@ struct fn_wrapper_3a_v
|
|||||||
template <typename WrapCls> static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); }
|
template <typename WrapCls> static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Five parameters, no return
|
||||||
|
template <typename Class, typename FuncT, FuncT fn, typename arg1_conv, typename arg2_conv, typename arg3_conv,
|
||||||
|
typename arg4_conv, typename arg5_conv>
|
||||||
|
struct fn_wrapper_5a_v
|
||||||
|
{
|
||||||
|
using class_type = typename WrapIfNotContext<Class>::maybe_wrapped_t;
|
||||||
|
using conv_arg1_type = typename arg1_conv::arg_type;
|
||||||
|
using conv_arg2_type = typename arg2_conv::arg_type;
|
||||||
|
using conv_arg3_type = typename arg3_conv::arg_type;
|
||||||
|
using conv_arg4_type = typename arg4_conv::arg_type;
|
||||||
|
using conv_arg5_type = typename arg5_conv::arg_type;
|
||||||
|
|
||||||
|
static void wrapped_fn(class_type &cls, conv_arg1_type arg1, conv_arg2_type arg2, conv_arg3_type arg3,
|
||||||
|
conv_arg4_type arg4, conv_arg5_type arg5)
|
||||||
|
{
|
||||||
|
Context *ctx = get_ctx<Class>(cls);
|
||||||
|
Class &base = get_base<Class>(cls);
|
||||||
|
return (base.*fn)(arg1_conv()(ctx, arg1), arg2_conv()(ctx, arg2), arg3_conv()(ctx, arg3),
|
||||||
|
arg4_conv()(ctx, arg4), arg5_conv()(ctx, arg5));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename WrapCls> static void def_wrap(WrapCls cls_, const char *name) { cls_.def(name, wrapped_fn); }
|
||||||
|
};
|
||||||
|
|
||||||
// Wrapped getter
|
// Wrapped getter
|
||||||
template <typename Class, typename MemT, MemT mem, typename v_conv> struct readonly_wrapper
|
template <typename Class, typename MemT, MemT mem, typename v_conv> struct readonly_wrapper
|
||||||
{
|
{
|
||||||
|
@ -45,19 +45,30 @@ class Settings
|
|||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T> void set(const char *name, T value)
|
template <typename T> void set(const char *name, T value);
|
||||||
{
|
|
||||||
IdString id = ctx->id(name);
|
|
||||||
auto pair = ctx->settings.emplace(id, std::to_string(value));
|
|
||||||
if (!pair.second) {
|
|
||||||
ctx->settings[pair.first->first] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Context *ctx;
|
Context *ctx;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename T> inline void Settings::set(const char *name, T value)
|
||||||
|
{
|
||||||
|
IdString id = ctx->id(name);
|
||||||
|
auto pair = ctx->settings.emplace(id, std::to_string(value));
|
||||||
|
if (!pair.second) {
|
||||||
|
ctx->settings[pair.first->first] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <> inline void Settings::set<std::string>(const char *name, std::string value)
|
||||||
|
{
|
||||||
|
IdString id = ctx->id(name);
|
||||||
|
auto pair = ctx->settings.emplace(id, value);
|
||||||
|
if (!pair.second) {
|
||||||
|
ctx->settings[pair.first->first] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_END
|
NEXTPNR_NAMESPACE_END
|
||||||
|
|
||||||
#endif // SETTINGS_H
|
#endif // SETTINGS_H
|
||||||
|
@ -904,8 +904,7 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p
|
|||||||
if (!warn_on_failure || passed)
|
if (!warn_on_failure || passed)
|
||||||
log_info("Max frequency for clock %*s'%s': %.02f MHz (%s at %.02f MHz)\n", width, "",
|
log_info("Max frequency for clock %*s'%s': %.02f MHz (%s at %.02f MHz)\n", width, "",
|
||||||
clock_name.c_str(), clock_fmax[clock.first], passed ? "PASS" : "FAIL", target);
|
clock_name.c_str(), clock_fmax[clock.first], passed ? "PASS" : "FAIL", target);
|
||||||
else
|
else if (bool_or_default(ctx->settings, ctx->id("timing/allowFail"), false))
|
||||||
if (bool_or_default(ctx->settings, ctx->id("timing/allowFail"), false))
|
|
||||||
log_warning("Max frequency for clock %*s'%s': %.02f MHz (%s at %.02f MHz)\n", width, "",
|
log_warning("Max frequency for clock %*s'%s': %.02f MHz (%s at %.02f MHz)\n", width, "",
|
||||||
clock_name.c_str(), clock_fmax[clock.first], passed ? "PASS" : "FAIL", target);
|
clock_name.c_str(), clock_fmax[clock.first], passed ? "PASS" : "FAIL", target);
|
||||||
else
|
else
|
||||||
|
@ -490,3 +490,14 @@ a certain number of different clock signals allowed for a group of bels.
|
|||||||
|
|
||||||
Returns true if a bell in the current configuration is valid, i.e. if
|
Returns true if a bell in the current configuration is valid, i.e. if
|
||||||
`isValidBelForCell()` would return true for the current mapping.
|
`isValidBelForCell()` would return true for the current mapping.
|
||||||
|
|
||||||
|
|
||||||
|
### static const std::string defaultPlacer
|
||||||
|
|
||||||
|
Name of the default placement algorithm for the architecture, if
|
||||||
|
`--placer` isn't specified on the command line.
|
||||||
|
|
||||||
|
### static const std::vector\<std::string\> availablePlacers
|
||||||
|
|
||||||
|
Name of available placer algorithms for the architecture, used
|
||||||
|
to provide help for and validate `--placer`.
|
43
ecp5/arch.cc
43
ecp5/arch.cc
@ -28,6 +28,7 @@
|
|||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "nextpnr.h"
|
#include "nextpnr.h"
|
||||||
#include "placer1.h"
|
#include "placer1.h"
|
||||||
|
#include "placer_heap.h"
|
||||||
#include "router1.h"
|
#include "router1.h"
|
||||||
#include "timing.h"
|
#include "timing.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
@ -456,6 +457,7 @@ delay_t Arch::estimateDelay(WireId src, WireId dst) const
|
|||||||
auto src_loc = est_location(src), dst_loc = est_location(dst);
|
auto src_loc = est_location(src), dst_loc = est_location(dst);
|
||||||
|
|
||||||
int dx = abs(src_loc.first - dst_loc.first), dy = abs(src_loc.second - dst_loc.second);
|
int dx = abs(src_loc.first - dst_loc.first), dy = abs(src_loc.second - dst_loc.second);
|
||||||
|
|
||||||
return (130 - 25 * args.speed) *
|
return (130 - 25 * args.speed) *
|
||||||
(6 + std::max(dx - 5, 0) + std::max(dy - 5, 0) + 2 * (std::min(dx, 5) + std::min(dy, 5)));
|
(6 + std::max(dx - 5, 0) + std::max(dy - 5, 0) + 2 * (std::min(dx, 5) + std::min(dy, 5)));
|
||||||
}
|
}
|
||||||
@ -467,7 +469,6 @@ delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const
|
|||||||
return 0;
|
return 0;
|
||||||
auto driver_loc = getBelLocation(driver.cell->bel);
|
auto driver_loc = getBelLocation(driver.cell->bel);
|
||||||
auto sink_loc = getBelLocation(sink.cell->bel);
|
auto sink_loc = getBelLocation(sink.cell->bel);
|
||||||
|
|
||||||
// Encourage use of direct interconnect
|
// Encourage use of direct interconnect
|
||||||
if (driver_loc.x == sink_loc.x && driver_loc.y == sink_loc.y) {
|
if (driver_loc.x == sink_loc.x && driver_loc.y == sink_loc.y) {
|
||||||
if ((sink.port == id_A0 || sink.port == id_A1) && (driver.port == id_F1) &&
|
if ((sink.port == id_A0 || sink.port == id_A1) && (driver.port == id_F1) &&
|
||||||
@ -485,6 +486,7 @@ delay_t Arch::predictDelay(const NetInfo *net_info, const PortRef &sink) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
int dx = abs(driver_loc.x - sink_loc.x), dy = abs(driver_loc.y - sink_loc.y);
|
int dx = abs(driver_loc.x - sink_loc.x), dy = abs(driver_loc.y - sink_loc.y);
|
||||||
|
|
||||||
return (130 - 25 * args.speed) *
|
return (130 - 25 * args.speed) *
|
||||||
(6 + std::max(dx - 5, 0) + std::max(dy - 5, 0) + 2 * (std::min(dx, 5) + std::min(dy, 5)));
|
(6 + std::max(dx - 5, 0) + std::max(dy - 5, 0) + 2 * (std::min(dx, 5) + std::min(dy, 5)));
|
||||||
}
|
}
|
||||||
@ -506,10 +508,23 @@ bool Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay
|
|||||||
|
|
||||||
bool Arch::place()
|
bool Arch::place()
|
||||||
{
|
{
|
||||||
bool result = placer1(getCtx(), Placer1Cfg(getCtx()));
|
std::string placer = str_or_default(settings, id("placer"), defaultPlacer);
|
||||||
if (result)
|
|
||||||
|
if (placer == "heap") {
|
||||||
|
PlacerHeapCfg cfg(getCtx());
|
||||||
|
cfg.criticalityExponent = 7;
|
||||||
|
cfg.ioBufTypes.insert(id_TRELLIS_IO);
|
||||||
|
if (!placer_heap(getCtx(), cfg))
|
||||||
|
return false;
|
||||||
|
} else if (placer == "sa") {
|
||||||
|
if (!placer1(getCtx(), Placer1Cfg(getCtx())))
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
log_error("ECP5 architecture does not support placer '%s'\n", placer.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
permute_luts();
|
permute_luts();
|
||||||
return result;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Arch::route()
|
bool Arch::route()
|
||||||
@ -605,6 +620,11 @@ DecalXY Arch::getGroupDecal(GroupId pip) const { return {}; };
|
|||||||
|
|
||||||
bool Arch::getDelayFromTimingDatabase(IdString tctype, IdString from, IdString to, DelayInfo &delay) const
|
bool Arch::getDelayFromTimingDatabase(IdString tctype, IdString from, IdString to, DelayInfo &delay) const
|
||||||
{
|
{
|
||||||
|
auto fnd_dk = celldelay_cache.find({tctype, from, to});
|
||||||
|
if (fnd_dk != celldelay_cache.end()) {
|
||||||
|
delay = fnd_dk->second.second;
|
||||||
|
return fnd_dk->second.first;
|
||||||
|
}
|
||||||
for (int i = 0; i < speed_grade->num_cell_timings; i++) {
|
for (int i = 0; i < speed_grade->num_cell_timings; i++) {
|
||||||
const auto &tc = speed_grade->cell_timings[i];
|
const auto &tc = speed_grade->cell_timings[i];
|
||||||
if (tc.cell_type == tctype.index) {
|
if (tc.cell_type == tctype.index) {
|
||||||
@ -613,9 +633,11 @@ bool Arch::getDelayFromTimingDatabase(IdString tctype, IdString from, IdString t
|
|||||||
if (dly.from_port == from.index && dly.to_port == to.index) {
|
if (dly.from_port == from.index && dly.to_port == to.index) {
|
||||||
delay.max_delay = dly.max_delay;
|
delay.max_delay = dly.max_delay;
|
||||||
delay.min_delay = dly.min_delay;
|
delay.min_delay = dly.min_delay;
|
||||||
|
celldelay_cache[{tctype, from, to}] = std::make_pair(true, delay);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
celldelay_cache[{tctype, from, to}] = std::make_pair(false, DelayInfo());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -645,7 +667,6 @@ void Arch::getSetupHoldFromTimingDatabase(IdString tctype, IdString clock, IdStr
|
|||||||
|
|
||||||
bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const
|
bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const
|
||||||
{
|
{
|
||||||
|
|
||||||
// Data for -8 grade
|
// Data for -8 grade
|
||||||
if (cell->type == id_TRELLIS_SLICE) {
|
if (cell->type == id_TRELLIS_SLICE) {
|
||||||
bool has_carry = cell->sliceInfo.is_carry;
|
bool has_carry = cell->sliceInfo.is_carry;
|
||||||
@ -965,4 +986,16 @@ WireId Arch::getBankECLK(int bank, int eclk)
|
|||||||
return getWireByLocAndBasename(Location(0, 0), "G_BANK" + std::to_string(bank) + "ECLK" + std::to_string(eclk));
|
return getWireByLocAndBasename(Location(0, 0), "G_BANK" + std::to_string(bank) + "ECLK" + std::to_string(eclk));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#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
|
||||||
|
};
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_END
|
NEXTPNR_NAMESPACE_END
|
||||||
|
29
ecp5/arch.h
29
ecp5/arch.h
@ -448,6 +448,30 @@ struct ArchArgs
|
|||||||
} speed = SPEED_6;
|
} speed = SPEED_6;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct DelayKey
|
||||||
|
{
|
||||||
|
IdString celltype, from, to;
|
||||||
|
inline bool operator==(const DelayKey &other) const
|
||||||
|
{
|
||||||
|
return celltype == other.celltype && from == other.from && to == other.to;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
NEXTPNR_NAMESPACE_END
|
||||||
|
namespace std {
|
||||||
|
template <> struct hash<NEXTPNR_NAMESPACE_PREFIX DelayKey>
|
||||||
|
{
|
||||||
|
std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX DelayKey &dk) const noexcept
|
||||||
|
{
|
||||||
|
std::size_t seed = std::hash<NEXTPNR_NAMESPACE_PREFIX IdString>()(dk.celltype);
|
||||||
|
seed ^= std::hash<NEXTPNR_NAMESPACE_PREFIX IdString>()(dk.from) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
|
||||||
|
seed ^= std::hash<NEXTPNR_NAMESPACE_PREFIX IdString>()(dk.to) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
|
||||||
|
return seed;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace std
|
||||||
|
NEXTPNR_NAMESPACE_BEGIN
|
||||||
|
|
||||||
struct Arch : BaseCtx
|
struct Arch : BaseCtx
|
||||||
{
|
{
|
||||||
const ChipInfoPOD *chip_info;
|
const ChipInfoPOD *chip_info;
|
||||||
@ -1019,6 +1043,11 @@ struct Arch : BaseCtx
|
|||||||
IdString id_clk, id_lsr;
|
IdString id_clk, id_lsr;
|
||||||
IdString id_clkmux, id_lsrmux;
|
IdString id_clkmux, id_lsrmux;
|
||||||
IdString id_srmode, id_mode;
|
IdString id_srmode, id_mode;
|
||||||
|
|
||||||
|
mutable std::unordered_map<DelayKey, std::pair<bool, DelayInfo>> celldelay_cache;
|
||||||
|
|
||||||
|
static const std::string defaultPlacer;
|
||||||
|
static const std::vector<std::string> availablePlacers;
|
||||||
};
|
};
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_END
|
NEXTPNR_NAMESPACE_END
|
||||||
|
@ -133,6 +133,13 @@ void arch_wrap_python()
|
|||||||
|
|
||||||
fn_wrapper_2a_v<Context, decltype(&Context::addClock), &Context::addClock, conv_from_str<IdString>,
|
fn_wrapper_2a_v<Context, decltype(&Context::addClock), &Context::addClock, conv_from_str<IdString>,
|
||||||
pass_through<float>>::def_wrap(ctx_cls, "addClock");
|
pass_through<float>>::def_wrap(ctx_cls, "addClock");
|
||||||
|
fn_wrapper_5a_v<Context, decltype(&Context::createRectangularRegion), &Context::createRectangularRegion,
|
||||||
|
conv_from_str<IdString>, pass_through<int>, pass_through<int>, pass_through<int>,
|
||||||
|
pass_through<int>>::def_wrap(ctx_cls, "createRectangularRegion");
|
||||||
|
fn_wrapper_2a_v<Context, decltype(&Context::addBelToRegion), &Context::addBelToRegion, conv_from_str<IdString>,
|
||||||
|
conv_from_str<BelId>>::def_wrap(ctx_cls, "addBelToRegion");
|
||||||
|
fn_wrapper_2a_v<Context, decltype(&Context::constrainCellToRegion), &Context::constrainCellToRegion,
|
||||||
|
conv_from_str<IdString>, conv_from_str<IdString>>::def_wrap(ctx_cls, "constrainCellToRegion");
|
||||||
|
|
||||||
WRAP_RANGE(Bel, conv_to_str<BelId>);
|
WRAP_RANGE(Bel, conv_to_str<BelId>);
|
||||||
WRAP_RANGE(Wire, conv_to_str<WireId>);
|
WRAP_RANGE(Wire, conv_to_str<WireId>);
|
||||||
|
@ -149,8 +149,8 @@ std::unique_ptr<Context> ECP5CommandHandler::createContext()
|
|||||||
chipArgs.speed = ArchArgs::SPEED_6;
|
chipArgs.speed = ArchArgs::SPEED_6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
auto ctx = std::unique_ptr<Context>(new Context(chipArgs));
|
||||||
return std::unique_ptr<Context>(new Context(chipArgs));
|
return ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ECP5CommandHandler::customAfterLoad(Context *ctx)
|
void ECP5CommandHandler::customAfterLoad(Context *ctx)
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
#include "nextpnr.h"
|
#include "nextpnr.h"
|
||||||
#include "placer1.h"
|
#include "placer1.h"
|
||||||
#include "router1.h"
|
#include "router1.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_BEGIN
|
NEXTPNR_NAMESPACE_BEGIN
|
||||||
|
|
||||||
@ -439,7 +440,16 @@ bool Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay
|
|||||||
|
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
|
|
||||||
bool Arch::place() { return placer1(getCtx(), Placer1Cfg(getCtx())); }
|
bool Arch::place()
|
||||||
|
{
|
||||||
|
std::string placer = str_or_default(settings, id("placer"), defaultPlacer);
|
||||||
|
// FIXME: No HeAP because it needs a list of IO buffers
|
||||||
|
if (placer == "sa") {
|
||||||
|
return placer1(getCtx(), Placer1Cfg(getCtx()));
|
||||||
|
} else {
|
||||||
|
log_error("Generic architecture does not support placer '%s'\n", placer.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool Arch::route() { return router1(getCtx(), Router1Cfg(getCtx())); }
|
bool Arch::route() { return router1(getCtx(), Router1Cfg(getCtx())); }
|
||||||
|
|
||||||
@ -476,4 +486,7 @@ TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port
|
|||||||
bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const { return true; }
|
bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const { return true; }
|
||||||
bool Arch::isBelLocationValid(BelId bel) const { return true; }
|
bool Arch::isBelLocationValid(BelId bel) const { return true; }
|
||||||
|
|
||||||
|
const std::string Arch::defaultPlacer = "sa";
|
||||||
|
const std::vector<std::string> Arch::availablePlacers = {"sa"};
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_END
|
NEXTPNR_NAMESPACE_END
|
||||||
|
@ -240,6 +240,9 @@ struct Arch : BaseCtx
|
|||||||
|
|
||||||
bool isValidBelForCell(CellInfo *cell, BelId bel) const;
|
bool isValidBelForCell(CellInfo *cell, BelId bel) const;
|
||||||
bool isBelLocationValid(BelId bel) const;
|
bool isBelLocationValid(BelId bel) const;
|
||||||
|
|
||||||
|
static const std::string defaultPlacer;
|
||||||
|
static const std::vector<std::string> availablePlacers;
|
||||||
};
|
};
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_END
|
NEXTPNR_NAMESPACE_END
|
||||||
|
@ -26,10 +26,10 @@
|
|||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "nextpnr.h"
|
#include "nextpnr.h"
|
||||||
#include "placer1.h"
|
#include "placer1.h"
|
||||||
|
#include "placer_heap.h"
|
||||||
#include "router1.h"
|
#include "router1.h"
|
||||||
#include "timing_opt.h"
|
#include "timing_opt.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_BEGIN
|
NEXTPNR_NAMESPACE_BEGIN
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
@ -671,8 +671,18 @@ bool Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay
|
|||||||
|
|
||||||
bool Arch::place()
|
bool Arch::place()
|
||||||
{
|
{
|
||||||
|
std::string placer = str_or_default(settings, id("placer"), defaultPlacer);
|
||||||
|
if (placer == "heap") {
|
||||||
|
PlacerHeapCfg cfg(getCtx());
|
||||||
|
cfg.ioBufTypes.insert(id_SB_IO);
|
||||||
|
if (!placer_heap(getCtx(), cfg))
|
||||||
|
return false;
|
||||||
|
} else if (placer == "sa") {
|
||||||
if (!placer1(getCtx(), Placer1Cfg(getCtx())))
|
if (!placer1(getCtx(), Placer1Cfg(getCtx())))
|
||||||
return false;
|
return false;
|
||||||
|
} else {
|
||||||
|
log_error("iCE40 architecture does not support placer '%s'\n", placer.c_str());
|
||||||
|
}
|
||||||
if (bool_or_default(settings, id("opt_timing"), false)) {
|
if (bool_or_default(settings, id("opt_timing"), false)) {
|
||||||
TimingOptCfg tocfg(getCtx());
|
TimingOptCfg tocfg(getCtx());
|
||||||
tocfg.cellTypes.insert(id_ICESTORM_LC);
|
tocfg.cellTypes.insert(id_ICESTORM_LC);
|
||||||
@ -1198,4 +1208,12 @@ void Arch::assignCellInfo(CellInfo *cell)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::string Arch::defaultPlacer = "sa";
|
||||||
|
|
||||||
|
const std::vector<std::string> Arch::availablePlacers = {"sa",
|
||||||
|
#ifdef WITH_HEAP
|
||||||
|
"heap"
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_END
|
NEXTPNR_NAMESPACE_END
|
||||||
|
@ -897,6 +897,9 @@ struct Arch : BaseCtx
|
|||||||
IdString glb_net = getWireName(getBelPinWire(bel, id_GLOBAL_BUFFER_OUTPUT));
|
IdString glb_net = getWireName(getBelPinWire(bel, id_GLOBAL_BUFFER_OUTPUT));
|
||||||
return std::stoi(std::string("") + glb_net.str(this).back());
|
return std::stoi(std::string("") + glb_net.str(this).back());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const std::string defaultPlacer;
|
||||||
|
static const std::vector<std::string> availablePlacers;
|
||||||
};
|
};
|
||||||
|
|
||||||
void ice40DelayFuzzerMain(Context *ctx);
|
void ice40DelayFuzzerMain(Context *ctx);
|
||||||
|
@ -144,6 +144,13 @@ void arch_wrap_python()
|
|||||||
|
|
||||||
fn_wrapper_2a_v<Context, decltype(&Context::addClock), &Context::addClock, conv_from_str<IdString>,
|
fn_wrapper_2a_v<Context, decltype(&Context::addClock), &Context::addClock, conv_from_str<IdString>,
|
||||||
pass_through<float>>::def_wrap(ctx_cls, "addClock");
|
pass_through<float>>::def_wrap(ctx_cls, "addClock");
|
||||||
|
fn_wrapper_5a_v<Context, decltype(&Context::createRectangularRegion), &Context::createRectangularRegion,
|
||||||
|
conv_from_str<IdString>, pass_through<int>, pass_through<int>, pass_through<int>,
|
||||||
|
pass_through<int>>::def_wrap(ctx_cls, "createRectangularRegion");
|
||||||
|
fn_wrapper_2a_v<Context, decltype(&Context::addBelToRegion), &Context::addBelToRegion, conv_from_str<IdString>,
|
||||||
|
conv_from_str<BelId>>::def_wrap(ctx_cls, "addBelToRegion");
|
||||||
|
fn_wrapper_2a_v<Context, decltype(&Context::constrainCellToRegion), &Context::constrainCellToRegion,
|
||||||
|
conv_from_str<IdString>, conv_from_str<IdString>>::def_wrap(ctx_cls, "constrainCellToRegion");
|
||||||
|
|
||||||
WRAP_RANGE(Bel, conv_to_str<BelId>);
|
WRAP_RANGE(Bel, conv_to_str<BelId>);
|
||||||
WRAP_RANGE(Wire, conv_to_str<WireId>);
|
WRAP_RANGE(Wire, conv_to_str<WireId>);
|
||||||
|
4
ice40/examples/floorplan/.gitignore
vendored
Normal file
4
ice40/examples/floorplan/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
*.json
|
||||||
|
*.asc
|
||||||
|
*.bin
|
||||||
|
__pycache__
|
5
ice40/examples/floorplan/floorplan.py
Normal file
5
ice40/examples/floorplan/floorplan.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
ctx.createRectangularRegion("osc", 1, 1, 1, 4)
|
||||||
|
for cell, cellinfo in ctx.cells:
|
||||||
|
if "ringosc" in cellinfo.attrs:
|
||||||
|
print("Floorplanned cell %s" % cell)
|
||||||
|
ctx.constrainCellToRegion(cell, "osc")
|
6
ice40/examples/floorplan/floorplan.sh
Executable file
6
ice40/examples/floorplan/floorplan.sh
Executable file
@ -0,0 +1,6 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -ex
|
||||||
|
yosys -p "synth_ice40 -top top -json floorplan.json" floorplan.v
|
||||||
|
../../../nextpnr-ice40 --up5k --json floorplan.json --pcf icebreaker.pcf --asc floorplan.asc --ignore-loops --pre-place floorplan.py
|
||||||
|
icepack floorplan.asc floorplan.bin
|
||||||
|
iceprog floorplan.bin
|
22
ice40/examples/floorplan/floorplan.v
Normal file
22
ice40/examples/floorplan/floorplan.v
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
module top(output LED1, LED2, LED3, LED4, LED5);
|
||||||
|
localparam N = 31;
|
||||||
|
wire [N:0] x;
|
||||||
|
assign x[0] = x[N];
|
||||||
|
|
||||||
|
genvar ii;
|
||||||
|
generate
|
||||||
|
|
||||||
|
for (ii = 0; ii < N; ii = ii + 1) begin
|
||||||
|
(* ringosc *)
|
||||||
|
SB_LUT4 #(.LUT_INIT(1)) lut_i(.I0(x[ii]), .I1(), .I2(), .I3(), .O(x[ii+1]));
|
||||||
|
end
|
||||||
|
endgenerate
|
||||||
|
|
||||||
|
assign clk = x[N];
|
||||||
|
|
||||||
|
|
||||||
|
reg [19:0] ctr;
|
||||||
|
always @(posedge clk)
|
||||||
|
ctr <= ctr + 1'b1;
|
||||||
|
assign {LED5, LED4, LED3, LED2, LED1} = ctr[19:15];
|
||||||
|
endmodule
|
5
ice40/examples/floorplan/icebreaker.pcf
Normal file
5
ice40/examples/floorplan/icebreaker.pcf
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
set_io -nowarn LED1 26
|
||||||
|
set_io -nowarn LED2 27
|
||||||
|
set_io -nowarn LED3 25
|
||||||
|
set_io -nowarn LED4 23
|
||||||
|
set_io -nowarn LED5 21
|
@ -176,7 +176,6 @@ std::unique_ptr<Context> Ice40CommandHandler::createContext()
|
|||||||
ctx->settings[ctx->id("opt_timing")] = "1";
|
ctx->settings[ctx->id("opt_timing")] = "1";
|
||||||
if (vm.count("pcf-allow-unconstrained"))
|
if (vm.count("pcf-allow-unconstrained"))
|
||||||
ctx->settings[ctx->id("pcf_allow_unconstrained")] = "1";
|
ctx->settings[ctx->id("pcf_allow_unconstrained")] = "1";
|
||||||
|
|
||||||
return ctx;
|
return ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
2
tests
2
tests
@ -1 +1 @@
|
|||||||
Subproject commit f29dcbe187b517d01964b1074eb7ff0b90849eed
|
Subproject commit 32a683071758ee59d47e2c5cb29c87882993facd
|
Loading…
Reference in New Issue
Block a user