Merge https://github.com/YosysHQ/nextpnr into xc7
This commit is contained in:
commit
16e001b679
11
.cirrus.yml
Normal file
11
.cirrus.yml
Normal file
@ -0,0 +1,11 @@
|
||||
task:
|
||||
name: build-test-ubuntu1604
|
||||
container:
|
||||
cpu: 4
|
||||
memory: 16
|
||||
dockerfile: .cirrus/Dockerfile.ubuntu16.04
|
||||
|
||||
build_script: mkdir build && cd build && cmake .. -DARCH=all -DTRELLIS_ROOT=/usr/local/src/prjtrellis -DBUILD_TESTS=on && make -j $(nproc)
|
||||
test_generic_script: cd build && ./nextpnr-generic-test
|
||||
test_ice40_script: cd build && ./nextpnr-ice40-test
|
||||
test_ecp5_script: cd build && ./nextpnr-ecp5-test
|
33
.cirrus/Dockerfile.ubuntu16.04
Normal file
33
.cirrus/Dockerfile.ubuntu16.04
Normal file
@ -0,0 +1,33 @@
|
||||
FROM ubuntu:xenial-20181113
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN set -e -x ;\
|
||||
apt-get -y update ;\
|
||||
apt-get -y upgrade ;\
|
||||
apt-get -y install \
|
||||
build-essential cmake clang python3-dev libboost-all-dev qt5-default git
|
||||
|
||||
RUN set -e -x ;\
|
||||
apt-get -y install \
|
||||
libftdi-dev pkg-config
|
||||
|
||||
RUN set -e -x ;\
|
||||
mkdir -p /usr/local/src ;\
|
||||
cd /usr/local/src ;\
|
||||
git clone --recursive https://github.com/cliffordwolf/icestorm.git ;\
|
||||
cd icestorm ;\
|
||||
git reset --hard 9671b760f84ca4006f0ef101a3e3b201df4eabb5 ;\
|
||||
make -j $(nproc) ;\
|
||||
make install
|
||||
|
||||
RUN set -e -x ;\
|
||||
mkdir -p /usr/local/src ;\
|
||||
cd /usr/local/src ;\
|
||||
git clone --recursive https://github.com/SymbiFlow/prjtrellis.git ;\
|
||||
cd prjtrellis ;\
|
||||
git reset --hard de035a6e5e5818804a66b9408f0ad381b10957db ;\
|
||||
cd libtrellis ;\
|
||||
cmake -DCMAKE_INSTALL_PREFIX=/usr . ;\
|
||||
make -j $(nproc) ;\
|
||||
make install
|
@ -48,8 +48,8 @@ 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_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /W4 /wd4100 /wd4244 /wd4125 /wd4800 /wd4456 /wd4458 /wd4305 /wd4459 /wd4121 /wd4996 /wd4127")
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "-Wall -fPIC -ggdb")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "-Wall -fPIC -O3 -g")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "-Wall -fPIC -ggdb -pipe")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "-Wall -fPIC -O3 -g -pipe")
|
||||
endif()
|
||||
set(CMAKE_DEFIN)
|
||||
|
||||
|
@ -131,9 +131,9 @@ void CommandHandler::setupContext(Context *ctx)
|
||||
}
|
||||
|
||||
if (vm.count("quiet")) {
|
||||
log_streams.push_back(std::make_pair(&std::cerr, LogLevel::WARNING));
|
||||
log_streams.push_back(std::make_pair(&std::cerr, LogLevel::WARNING_MSG));
|
||||
} else {
|
||||
log_streams.push_back(std::make_pair(&std::cerr, LogLevel::LOG));
|
||||
log_streams.push_back(std::make_pair(&std::cerr, LogLevel::LOG_MSG));
|
||||
}
|
||||
|
||||
if (vm.count("log")) {
|
||||
@ -141,7 +141,7 @@ void CommandHandler::setupContext(Context *ctx)
|
||||
logfile = std::ofstream(logfilename);
|
||||
if (!logfile)
|
||||
log_error("Failed to open log file '%s' for writing.\n", logfilename.c_str());
|
||||
log_streams.push_back(std::make_pair(&logfile, LogLevel::LOG));
|
||||
log_streams.push_back(std::make_pair(&logfile, LogLevel::LOG_MSG));
|
||||
}
|
||||
|
||||
if (vm.count("force")) {
|
||||
@ -285,8 +285,8 @@ void CommandHandler::conflicting_options(const boost::program_options::variables
|
||||
|
||||
void CommandHandler::printFooter()
|
||||
{
|
||||
int warning_count = get_or_default(message_count_by_level, LogLevel::WARNING, 0),
|
||||
error_count = get_or_default(message_count_by_level, LogLevel::ERROR, 0);
|
||||
int warning_count = get_or_default(message_count_by_level, LogLevel::WARNING_MSG, 0),
|
||||
error_count = get_or_default(message_count_by_level, LogLevel::ERROR_MSG, 0);
|
||||
if (warning_count > 0 || error_count > 0)
|
||||
log_always("%d warning%s, %d error%s\n", warning_count, warning_count == 1 ? "" : "s", error_count,
|
||||
error_count == 1 ? "" : "s");
|
||||
|
@ -84,7 +84,7 @@ std::string vstringf(const char *fmt, va_list ap)
|
||||
return string;
|
||||
}
|
||||
|
||||
void logv(const char *format, va_list ap, LogLevel level = LogLevel::LOG)
|
||||
void logv(const char *format, va_list ap, LogLevel level = LogLevel::LOG_MSG)
|
||||
{
|
||||
//
|
||||
// Trim newlines from the beginning
|
||||
@ -132,7 +132,7 @@ void log_always(const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
logv(format, ap, LogLevel::ALWAYS);
|
||||
logv(format, ap, LogLevel::ALWAYS_MSG);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
@ -140,7 +140,7 @@ void log(const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
logv(format, ap, LogLevel::LOG);
|
||||
logv(format, ap, LogLevel::LOG_MSG);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
@ -148,7 +148,7 @@ void log_info(const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
logv_prefixed("Info: ", format, ap, LogLevel::INFO);
|
||||
logv_prefixed("Info: ", format, ap, LogLevel::INFO_MSG);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
@ -156,7 +156,7 @@ void log_warning(const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
logv_prefixed("Warning: ", format, ap, LogLevel::WARNING);
|
||||
logv_prefixed("Warning: ", format, ap, LogLevel::WARNING_MSG);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
@ -164,7 +164,7 @@ void log_error(const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
logv_prefixed("ERROR: ", format, ap, LogLevel::ERROR);
|
||||
logv_prefixed("ERROR: ", format, ap, LogLevel::ERROR_MSG);
|
||||
|
||||
if (log_error_atexit)
|
||||
log_error_atexit();
|
||||
@ -184,7 +184,7 @@ void log_nonfatal_error(const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
logv_prefixed("ERROR: ", format, ap, LogLevel::ERROR);
|
||||
logv_prefixed("ERROR: ", format, ap, LogLevel::ERROR_MSG);
|
||||
va_end(ap);
|
||||
had_nonfatal_error = true;
|
||||
}
|
||||
|
10
common/log.h
10
common/log.h
@ -44,11 +44,11 @@ struct log_execution_error_exception
|
||||
|
||||
enum class LogLevel
|
||||
{
|
||||
LOG,
|
||||
INFO,
|
||||
WARNING,
|
||||
ERROR,
|
||||
ALWAYS
|
||||
LOG_MSG,
|
||||
INFO_MSG,
|
||||
WARNING_MSG,
|
||||
ERROR_MSG,
|
||||
ALWAYS_MSG
|
||||
};
|
||||
|
||||
extern std::vector<std::pair<std::ostream *, LogLevel>> log_streams;
|
||||
|
@ -9,7 +9,7 @@ bool check_all_nets_driven(Context *ctx)
|
||||
{
|
||||
const bool debug = false;
|
||||
|
||||
log_info("Rule checker, Verifying pre-placed design\n");
|
||||
log_info("Rule checker, verifying imported design\n");
|
||||
|
||||
for (auto &cell_entry : ctx->cells) {
|
||||
CellInfo *cell = cell_entry.second.get();
|
||||
|
190
common/timing.cc
190
common/timing.cc
@ -86,6 +86,7 @@ struct CriticalPath
|
||||
};
|
||||
|
||||
typedef std::unordered_map<ClockPair, CriticalPath> CriticalPathMap;
|
||||
typedef std::unordered_map<IdString, NetCriticalityInfo> NetCriticalityMap;
|
||||
|
||||
struct Timing
|
||||
{
|
||||
@ -95,6 +96,7 @@ struct Timing
|
||||
delay_t min_slack;
|
||||
CriticalPathMap *crit_path;
|
||||
DelayFrequency *slack_histogram;
|
||||
NetCriticalityMap *net_crit;
|
||||
IdString async_clock;
|
||||
|
||||
struct TimingData
|
||||
@ -105,13 +107,15 @@ struct Timing
|
||||
unsigned max_path_length = 0;
|
||||
delay_t min_remaining_budget;
|
||||
bool false_startpoint = false;
|
||||
std::vector<delay_t> min_required;
|
||||
std::unordered_map<ClockEvent, delay_t> arrival_time;
|
||||
};
|
||||
|
||||
Timing(Context *ctx, bool net_delays, bool update, CriticalPathMap *crit_path = nullptr,
|
||||
DelayFrequency *slack_histogram = nullptr)
|
||||
DelayFrequency *slack_histogram = nullptr, NetCriticalityMap *net_crit = nullptr)
|
||||
: ctx(ctx), net_delays(net_delays), update(update), min_slack(1.0e12 / ctx->target_freq),
|
||||
crit_path(crit_path), slack_histogram(slack_histogram), async_clock(ctx->id("$async$"))
|
||||
crit_path(crit_path), slack_histogram(slack_histogram), net_crit(net_crit),
|
||||
async_clock(ctx->id("$async$"))
|
||||
{
|
||||
}
|
||||
|
||||
@ -454,6 +458,180 @@ struct Timing
|
||||
std::reverse(cp_ports.begin(), cp_ports.end());
|
||||
}
|
||||
}
|
||||
|
||||
if (net_crit) {
|
||||
NPNR_ASSERT(crit_path);
|
||||
// Go through in reverse topographical order to set required times
|
||||
for (auto net : boost::adaptors::reverse(topographical_order)) {
|
||||
if (!net_data.count(net))
|
||||
continue;
|
||||
auto &nd_map = net_data.at(net);
|
||||
for (auto &startdomain : nd_map) {
|
||||
auto &nd = startdomain.second;
|
||||
if (nd.false_startpoint)
|
||||
continue;
|
||||
if (startdomain.first.clock == async_clock)
|
||||
continue;
|
||||
if (nd.min_required.empty())
|
||||
nd.min_required.resize(net->users.size(), std::numeric_limits<delay_t>::max());
|
||||
delay_t net_min_required = std::numeric_limits<delay_t>::max();
|
||||
for (size_t i = 0; i < net->users.size(); i++) {
|
||||
auto &usr = net->users.at(i);
|
||||
auto net_delay = ctx->getNetinfoRouteDelay(net, usr);
|
||||
int port_clocks;
|
||||
TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, port_clocks);
|
||||
if (portClass == TMG_REGISTER_INPUT || portClass == TMG_ENDPOINT) {
|
||||
auto process_endpoint = [&](IdString clksig, ClockEdge edge, delay_t setup) {
|
||||
delay_t period;
|
||||
// Set default period
|
||||
if (edge == startdomain.first.edge) {
|
||||
period = clk_period;
|
||||
} else {
|
||||
period = clk_period / 2;
|
||||
}
|
||||
if (clksig != async_clock) {
|
||||
if (ctx->nets.at(clksig)->clkconstr) {
|
||||
if (edge == startdomain.first.edge) {
|
||||
// same edge
|
||||
period = ctx->nets.at(clksig)->clkconstr->period.minDelay();
|
||||
} else if (edge == RISING_EDGE) {
|
||||
// falling -> rising
|
||||
period = ctx->nets.at(clksig)->clkconstr->low.minDelay();
|
||||
} else if (edge == FALLING_EDGE) {
|
||||
// rising -> falling
|
||||
period = ctx->nets.at(clksig)->clkconstr->high.minDelay();
|
||||
}
|
||||
}
|
||||
}
|
||||
nd.min_required.at(i) = std::min(period - setup, nd.min_required.at(i));
|
||||
};
|
||||
if (portClass == TMG_REGISTER_INPUT) {
|
||||
for (int j = 0; j < port_clocks; j++) {
|
||||
TimingClockingInfo clkInfo = ctx->getPortClockingInfo(usr.cell, usr.port, j);
|
||||
const NetInfo *clknet = get_net_or_empty(usr.cell, clkInfo.clock_port);
|
||||
IdString clksig = clknet ? clknet->name : async_clock;
|
||||
process_endpoint(clksig, clknet ? clkInfo.edge : RISING_EDGE,
|
||||
clkInfo.setup.maxDelay());
|
||||
}
|
||||
} else {
|
||||
process_endpoint(async_clock, RISING_EDGE, 0);
|
||||
}
|
||||
}
|
||||
net_min_required = std::min(net_min_required, nd.min_required.at(i) - net_delay);
|
||||
}
|
||||
PortRef &drv = net->driver;
|
||||
if (drv.cell == nullptr)
|
||||
continue;
|
||||
for (const auto &port : drv.cell->ports) {
|
||||
if (port.second.type != PORT_IN || !port.second.net)
|
||||
continue;
|
||||
DelayInfo comb_delay;
|
||||
bool is_path = ctx->getCellDelay(drv.cell, port.first, drv.port, comb_delay);
|
||||
if (!is_path)
|
||||
continue;
|
||||
int cc;
|
||||
auto pclass = ctx->getPortTimingClass(drv.cell, port.first, cc);
|
||||
if (pclass != TMG_COMB_INPUT)
|
||||
continue;
|
||||
NetInfo *sink_net = port.second.net;
|
||||
if (net_data.count(sink_net) && net_data.at(sink_net).count(startdomain.first)) {
|
||||
auto &sink_nd = net_data.at(sink_net).at(startdomain.first);
|
||||
if (sink_nd.min_required.empty())
|
||||
sink_nd.min_required.resize(sink_net->users.size(),
|
||||
std::numeric_limits<delay_t>::max());
|
||||
for (size_t i = 0; i < sink_net->users.size(); i++) {
|
||||
auto &user = sink_net->users.at(i);
|
||||
if (user.cell == drv.cell && user.port == port.first) {
|
||||
sink_nd.min_required.at(i) = net_min_required - comb_delay.maxDelay();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
std::unordered_map<ClockEvent, delay_t> worst_slack;
|
||||
|
||||
// Assign slack values
|
||||
for (auto &net_entry : net_data) {
|
||||
const NetInfo *net = net_entry.first;
|
||||
for (auto &startdomain : net_entry.second) {
|
||||
auto &nd = startdomain.second;
|
||||
if (startdomain.first.clock == async_clock)
|
||||
continue;
|
||||
if (nd.min_required.empty())
|
||||
continue;
|
||||
auto &nc = (*net_crit)[net->name];
|
||||
if (nc.slack.empty())
|
||||
nc.slack.resize(net->users.size(), std::numeric_limits<delay_t>::max());
|
||||
#if 0
|
||||
if (ctx->debug)
|
||||
log_info("Net %s cd %s\n", net->name.c_str(ctx), startdomain.first.clock.c_str(ctx));
|
||||
#endif
|
||||
for (size_t i = 0; i < net->users.size(); i++) {
|
||||
delay_t slack = nd.min_required.at(i) -
|
||||
(nd.max_arrival + ctx->getNetinfoRouteDelay(net, net->users.at(i)));
|
||||
#if 0
|
||||
if (ctx->debug)
|
||||
log_info(" user %s.%s required %.02fns arrival %.02f route %.02f slack %.02f\n",
|
||||
net->users.at(i).cell->name.c_str(ctx), net->users.at(i).port.c_str(ctx),
|
||||
ctx->getDelayNS(nd.min_required.at(i)), ctx->getDelayNS(nd.max_arrival),
|
||||
ctx->getDelayNS(ctx->getNetinfoRouteDelay(net, net->users.at(i))), ctx->getDelayNS(slack));
|
||||
#endif
|
||||
if (worst_slack.count(startdomain.first))
|
||||
worst_slack.at(startdomain.first) = std::min(worst_slack.at(startdomain.first), slack);
|
||||
else
|
||||
worst_slack[startdomain.first] = slack;
|
||||
nc.slack.at(i) = slack;
|
||||
}
|
||||
if (ctx->debug)
|
||||
log_break();
|
||||
}
|
||||
}
|
||||
// Assign criticality values
|
||||
for (auto &net_entry : net_data) {
|
||||
const NetInfo *net = net_entry.first;
|
||||
for (auto &startdomain : net_entry.second) {
|
||||
if (startdomain.first.clock == async_clock)
|
||||
continue;
|
||||
auto &nd = startdomain.second;
|
||||
if (nd.min_required.empty())
|
||||
continue;
|
||||
auto &nc = (*net_crit)[net->name];
|
||||
if (nc.slack.empty())
|
||||
continue;
|
||||
if (nc.criticality.empty())
|
||||
nc.criticality.resize(net->users.size(), 0);
|
||||
// Only consider intra-clock paths for criticality
|
||||
if (!crit_path->count(ClockPair{startdomain.first, startdomain.first}))
|
||||
continue;
|
||||
delay_t dmax = crit_path->at(ClockPair{startdomain.first, startdomain.first}).path_delay;
|
||||
for (size_t i = 0; i < net->users.size(); i++) {
|
||||
float criticality = 1.0f - (float(nc.slack.at(i) - worst_slack.at(startdomain.first)) / dmax);
|
||||
nc.criticality.at(i) = criticality;
|
||||
}
|
||||
nc.max_path_length = nd.max_path_length;
|
||||
nc.cd_worst_slack = worst_slack.at(startdomain.first);
|
||||
}
|
||||
}
|
||||
#if 0
|
||||
if (ctx->debug) {
|
||||
for (auto &nc : *net_crit) {
|
||||
NetInfo *net = ctx->nets.at(nc.first).get();
|
||||
log_info("Net %s maxlen %d worst_slack %.02fns: \n", nc.first.c_str(ctx), nc.second.max_path_length,
|
||||
ctx->getDelayNS(nc.second.cd_worst_slack));
|
||||
if (!nc.second.criticality.empty() && !nc.second.slack.empty()) {
|
||||
for (size_t i = 0; i < net->users.size(); i++) {
|
||||
log_info(" user %s.%s slack %.02fns crit %.03f\n", net->users.at(i).cell->name.c_str(ctx),
|
||||
net->users.at(i).port.c_str(ctx), ctx->getDelayNS(nc.second.slack.at(i)),
|
||||
nc.second.criticality.at(i));
|
||||
}
|
||||
}
|
||||
log_break();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return min_slack;
|
||||
}
|
||||
|
||||
@ -766,4 +944,12 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p
|
||||
}
|
||||
}
|
||||
|
||||
void get_criticalities(Context *ctx, NetCriticalityMap *net_crit)
|
||||
{
|
||||
CriticalPathMap crit_paths;
|
||||
net_crit->clear();
|
||||
Timing timing(ctx, true, true, &crit_paths, nullptr, net_crit);
|
||||
timing.walk_paths();
|
||||
}
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
||||
|
@ -32,6 +32,19 @@ void assign_budget(Context *ctx, bool quiet = false);
|
||||
void timing_analysis(Context *ctx, bool slack_histogram = true, bool print_fmax = true, bool print_path = false,
|
||||
bool warn_on_failure = false);
|
||||
|
||||
// Data for the timing optimisation algorithm
|
||||
struct NetCriticalityInfo
|
||||
{
|
||||
// One each per user
|
||||
std::vector<delay_t> slack;
|
||||
std::vector<float> criticality;
|
||||
unsigned max_path_length = 0;
|
||||
delay_t cd_worst_slack = std::numeric_limits<delay_t>::max();
|
||||
};
|
||||
|
||||
typedef std::unordered_map<IdString, NetCriticalityInfo> NetCriticalityMap;
|
||||
void get_criticalities(Context *ctx, NetCriticalityMap *net_crit);
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
||||
|
||||
#endif
|
||||
|
623
common/timing_opt.cc
Normal file
623
common/timing_opt.cc
Normal file
@ -0,0 +1,623 @@
|
||||
/*
|
||||
* nextpnr -- Next Generation Place and Route
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* Timing-optimised detailed placement algorithm using BFS of the neighbour graph created from cells
|
||||
* on a critical path
|
||||
*
|
||||
* Based on "An Effective Timing-Driven Detailed Placement Algorithm for FPGAs"
|
||||
* https://www.cerc.utexas.edu/utda/publications/C205.pdf
|
||||
*
|
||||
* Modifications made to deal with the smaller Bels that nextpnr uses instead of swapping whole tiles,
|
||||
* and deal with the fact that not every cell on the crit path may be swappable.
|
||||
*/
|
||||
|
||||
#include "timing_opt.h"
|
||||
#include <boost/range/adaptor/reversed.hpp>
|
||||
#include <queue>
|
||||
#include "nextpnr.h"
|
||||
#include "timing.h"
|
||||
#include "util.h"
|
||||
|
||||
namespace std {
|
||||
|
||||
template <> struct hash<std::pair<NEXTPNR_NAMESPACE_PREFIX IdString, NEXTPNR_NAMESPACE_PREFIX IdString>>
|
||||
{
|
||||
std::size_t
|
||||
operator()(const std::pair<NEXTPNR_NAMESPACE_PREFIX IdString, NEXTPNR_NAMESPACE_PREFIX IdString> &idp) const
|
||||
noexcept
|
||||
{
|
||||
std::size_t seed = 0;
|
||||
boost::hash_combine(seed, hash<NEXTPNR_NAMESPACE_PREFIX IdString>()(idp.first));
|
||||
boost::hash_combine(seed, hash<NEXTPNR_NAMESPACE_PREFIX IdString>()(idp.second));
|
||||
return seed;
|
||||
}
|
||||
};
|
||||
|
||||
template <> struct hash<std::pair<int, NEXTPNR_NAMESPACE_PREFIX BelId>>
|
||||
{
|
||||
std::size_t operator()(const std::pair<int, NEXTPNR_NAMESPACE_PREFIX BelId> &idp) const noexcept
|
||||
{
|
||||
std::size_t seed = 0;
|
||||
boost::hash_combine(seed, hash<int>()(idp.first));
|
||||
boost::hash_combine(seed, hash<NEXTPNR_NAMESPACE_PREFIX BelId>()(idp.second));
|
||||
return seed;
|
||||
}
|
||||
};
|
||||
|
||||
template <> struct hash<std::pair<NEXTPNR_NAMESPACE_PREFIX IdString, NEXTPNR_NAMESPACE_PREFIX BelId>>
|
||||
{
|
||||
std::size_t
|
||||
operator()(const std::pair<NEXTPNR_NAMESPACE_PREFIX IdString, NEXTPNR_NAMESPACE_PREFIX BelId> &idp) const noexcept
|
||||
{
|
||||
std::size_t seed = 0;
|
||||
boost::hash_combine(seed, hash<NEXTPNR_NAMESPACE_PREFIX IdString>()(idp.first));
|
||||
boost::hash_combine(seed, hash<NEXTPNR_NAMESPACE_PREFIX BelId>()(idp.second));
|
||||
return seed;
|
||||
}
|
||||
};
|
||||
} // namespace std
|
||||
|
||||
NEXTPNR_NAMESPACE_BEGIN
|
||||
|
||||
class TimingOptimiser
|
||||
{
|
||||
public:
|
||||
TimingOptimiser(Context *ctx, TimingOptCfg cfg) : ctx(ctx), cfg(cfg){};
|
||||
bool optimise()
|
||||
{
|
||||
log_info("Running timing-driven placement optimisation...\n");
|
||||
if (ctx->verbose)
|
||||
timing_analysis(ctx, false, true, false, false);
|
||||
for (int i = 0; i < 30; i++) {
|
||||
log_info(" Iteration %d...\n", i);
|
||||
get_criticalities(ctx, &net_crit);
|
||||
setup_delay_limits();
|
||||
auto crit_paths = find_crit_paths(0.98, 50000);
|
||||
for (auto &path : crit_paths)
|
||||
optimise_path(path);
|
||||
if (ctx->verbose)
|
||||
timing_analysis(ctx, false, true, false, false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
void setup_delay_limits()
|
||||
{
|
||||
max_net_delay.clear();
|
||||
for (auto net : sorted(ctx->nets)) {
|
||||
NetInfo *ni = net.second;
|
||||
for (auto usr : ni->users) {
|
||||
max_net_delay[std::make_pair(usr.cell->name, usr.port)] = std::numeric_limits<delay_t>::max();
|
||||
}
|
||||
if (!net_crit.count(net.first))
|
||||
continue;
|
||||
auto &nc = net_crit.at(net.first);
|
||||
if (nc.slack.empty())
|
||||
continue;
|
||||
for (size_t i = 0; i < ni->users.size(); i++) {
|
||||
auto &usr = ni->users.at(i);
|
||||
delay_t net_delay = ctx->getNetinfoRouteDelay(ni, usr);
|
||||
if (nc.max_path_length != 0) {
|
||||
max_net_delay[std::make_pair(usr.cell->name, usr.port)] =
|
||||
net_delay + ((nc.slack.at(i) - nc.cd_worst_slack) / 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool check_cell_delay_limits(CellInfo *cell)
|
||||
{
|
||||
for (const auto &port : cell->ports) {
|
||||
int nc;
|
||||
if (ctx->getPortTimingClass(cell, port.first, nc) == TMG_IGNORE)
|
||||
continue;
|
||||
NetInfo *net = port.second.net;
|
||||
if (net == nullptr)
|
||||
continue;
|
||||
if (port.second.type == PORT_IN) {
|
||||
if (net->driver.cell == nullptr || net->driver.cell->bel == BelId())
|
||||
continue;
|
||||
for (auto user : net->users) {
|
||||
if (user.cell == cell && user.port == port.first) {
|
||||
if (ctx->predictDelay(net, user) >
|
||||
1.1 * max_net_delay.at(std::make_pair(cell->name, port.first)))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (port.second.type == PORT_OUT) {
|
||||
for (auto user : net->users) {
|
||||
// This could get expensive for high-fanout nets??
|
||||
BelId dstBel = user.cell->bel;
|
||||
if (dstBel == BelId())
|
||||
continue;
|
||||
if (ctx->predictDelay(net, user) >
|
||||
1.1 * max_net_delay.at(std::make_pair(user.cell->name, user.port))) {
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
BelId cell_swap_bel(CellInfo *cell, BelId newBel)
|
||||
{
|
||||
BelId oldBel = cell->bel;
|
||||
if (oldBel == newBel)
|
||||
return oldBel;
|
||||
CellInfo *other_cell = ctx->getBoundBelCell(newBel);
|
||||
NPNR_ASSERT(other_cell == nullptr || other_cell->belStrength <= STRENGTH_WEAK);
|
||||
ctx->unbindBel(oldBel);
|
||||
if (other_cell != nullptr) {
|
||||
ctx->unbindBel(newBel);
|
||||
ctx->bindBel(oldBel, other_cell, STRENGTH_WEAK);
|
||||
}
|
||||
ctx->bindBel(newBel, cell, STRENGTH_WEAK);
|
||||
return oldBel;
|
||||
}
|
||||
|
||||
// Check that a series of moves are both legal and remain within maximum delay bounds
|
||||
// Moves are specified as a vector of pairs <cell, oldBel>
|
||||
bool acceptable_move(std::vector<std::pair<CellInfo *, BelId>> &move, bool check_delays = true)
|
||||
{
|
||||
for (auto &entry : move) {
|
||||
if (!ctx->isBelLocationValid(entry.first->bel))
|
||||
return false;
|
||||
if (!ctx->isBelLocationValid(entry.second))
|
||||
return false;
|
||||
if (!check_delays)
|
||||
continue;
|
||||
if (!check_cell_delay_limits(entry.first))
|
||||
return false;
|
||||
// We might have swapped another cell onto the original bel. Check this for max delay violations
|
||||
// too
|
||||
CellInfo *swapped = ctx->getBoundBelCell(entry.second);
|
||||
if (swapped != nullptr && !check_cell_delay_limits(swapped))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int find_neighbours(CellInfo *cell, IdString prev_cell, int d, bool allow_swap)
|
||||
{
|
||||
BelId curr = cell->bel;
|
||||
Loc curr_loc = ctx->getBelLocation(curr);
|
||||
int found_count = 0;
|
||||
cell_neighbour_bels[cell->name] = std::unordered_set<BelId>{};
|
||||
for (int dy = -d; dy <= d; dy++) {
|
||||
for (int dx = -d; dx <= d; dx++) {
|
||||
// Go through all the Bels at this location
|
||||
// First, find all bels of the correct type that are either unbound or bound normally
|
||||
// Strongly bound bels are ignored
|
||||
// FIXME: This means that we cannot touch carry chains or similar relatively constrained macros
|
||||
std::vector<BelId> free_bels_at_loc;
|
||||
std::vector<BelId> bound_bels_at_loc;
|
||||
for (auto bel : ctx->getBelsByTile(curr_loc.x + dx, curr_loc.y + dy)) {
|
||||
if (ctx->getBelType(bel) != cell->type)
|
||||
continue;
|
||||
CellInfo *bound = ctx->getBoundBelCell(bel);
|
||||
if (bound == nullptr) {
|
||||
free_bels_at_loc.push_back(bel);
|
||||
} else if (bound->belStrength <= STRENGTH_WEAK && bound->constr_parent == nullptr &&
|
||||
bound->constr_children.empty()) {
|
||||
bound_bels_at_loc.push_back(bel);
|
||||
}
|
||||
}
|
||||
BelId candidate;
|
||||
|
||||
while (!free_bels_at_loc.empty() || !bound_bels_at_loc.empty()) {
|
||||
BelId try_bel;
|
||||
if (!free_bels_at_loc.empty()) {
|
||||
int try_idx = ctx->rng(int(free_bels_at_loc.size()));
|
||||
try_bel = free_bels_at_loc.at(try_idx);
|
||||
free_bels_at_loc.erase(free_bels_at_loc.begin() + try_idx);
|
||||
} else {
|
||||
int try_idx = ctx->rng(int(bound_bels_at_loc.size()));
|
||||
try_bel = bound_bels_at_loc.at(try_idx);
|
||||
bound_bels_at_loc.erase(bound_bels_at_loc.begin() + try_idx);
|
||||
}
|
||||
if (bel_candidate_cells.count(try_bel) && !allow_swap) {
|
||||
// Overlap is only allowed if it is with the previous cell (this is handled by removing those
|
||||
// edges in the graph), or if allow_swap is true to deal with cases where overlap means few
|
||||
// neighbours are identified
|
||||
if (bel_candidate_cells.at(try_bel).size() > 1 ||
|
||||
(bel_candidate_cells.at(try_bel).size() == 1 &&
|
||||
*(bel_candidate_cells.at(try_bel).begin()) != prev_cell))
|
||||
continue;
|
||||
}
|
||||
// TODO: what else to check here?
|
||||
candidate = try_bel;
|
||||
break;
|
||||
}
|
||||
|
||||
if (candidate != BelId()) {
|
||||
cell_neighbour_bels[cell->name].insert(candidate);
|
||||
bel_candidate_cells[candidate].insert(cell->name);
|
||||
// Work out if we need to delete any overlap
|
||||
std::vector<IdString> overlap;
|
||||
for (auto other : bel_candidate_cells[candidate])
|
||||
if (other != cell->name && other != prev_cell)
|
||||
overlap.push_back(other);
|
||||
if (overlap.size() > 0)
|
||||
NPNR_ASSERT(allow_swap);
|
||||
for (auto ov : overlap) {
|
||||
bel_candidate_cells[candidate].erase(ov);
|
||||
cell_neighbour_bels[ov].erase(candidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return found_count;
|
||||
}
|
||||
|
||||
std::vector<std::vector<PortRef *>> find_crit_paths(float crit_thresh, size_t max_count)
|
||||
{
|
||||
std::vector<std::vector<PortRef *>> crit_paths;
|
||||
std::vector<std::pair<NetInfo *, int>> crit_nets;
|
||||
std::vector<IdString> netnames;
|
||||
std::transform(ctx->nets.begin(), ctx->nets.end(), std::back_inserter(netnames),
|
||||
[](const std::pair<const IdString, std::unique_ptr<NetInfo>> &kv) { return kv.first; });
|
||||
ctx->sorted_shuffle(netnames);
|
||||
for (auto net : netnames) {
|
||||
if (crit_nets.size() >= max_count)
|
||||
break;
|
||||
if (!net_crit.count(net))
|
||||
continue;
|
||||
auto crit_user = std::max_element(net_crit[net].criticality.begin(), net_crit[net].criticality.end());
|
||||
if (*crit_user > crit_thresh)
|
||||
crit_nets.push_back(
|
||||
std::make_pair(ctx->nets[net].get(), crit_user - net_crit[net].criticality.begin()));
|
||||
}
|
||||
|
||||
auto port_user_index = [](CellInfo *cell, PortInfo &port) -> size_t {
|
||||
NPNR_ASSERT(port.net != nullptr);
|
||||
for (size_t i = 0; i < port.net->users.size(); i++) {
|
||||
auto &usr = port.net->users.at(i);
|
||||
if (usr.cell == cell && usr.port == port.name)
|
||||
return i;
|
||||
}
|
||||
NPNR_ASSERT_FALSE("port user not found on net");
|
||||
};
|
||||
std::unordered_set<PortRef *> used_ports;
|
||||
|
||||
for (auto crit_net : crit_nets) {
|
||||
|
||||
if (used_ports.count(&(crit_net.first->users.at(crit_net.second))))
|
||||
continue;
|
||||
|
||||
std::deque<PortRef *> crit_path;
|
||||
|
||||
// FIXME: This will fail badly on combinational loops
|
||||
|
||||
// Iterate backwards following greatest criticality
|
||||
NetInfo *back_cursor = crit_net.first;
|
||||
while (back_cursor != nullptr) {
|
||||
float max_crit = 0;
|
||||
std::pair<NetInfo *, size_t> crit_sink{nullptr, 0};
|
||||
CellInfo *cell = back_cursor->driver.cell;
|
||||
if (cell == nullptr)
|
||||
break;
|
||||
for (auto port : cell->ports) {
|
||||
if (port.second.type != PORT_IN)
|
||||
continue;
|
||||
NetInfo *pn = port.second.net;
|
||||
if (pn == nullptr)
|
||||
continue;
|
||||
if (!net_crit.count(pn->name) || net_crit.at(pn->name).criticality.empty())
|
||||
continue;
|
||||
int ccount;
|
||||
DelayInfo combDelay;
|
||||
TimingPortClass tpclass = ctx->getPortTimingClass(cell, port.first, ccount);
|
||||
if (tpclass != TMG_COMB_INPUT)
|
||||
continue;
|
||||
bool is_path = ctx->getCellDelay(cell, port.first, back_cursor->driver.port, combDelay);
|
||||
if (!is_path)
|
||||
continue;
|
||||
size_t user_idx = port_user_index(cell, port.second);
|
||||
float usr_crit = net_crit.at(pn->name).criticality.at(user_idx);
|
||||
if (used_ports.count(&(pn->users.at(user_idx))))
|
||||
continue;
|
||||
if (usr_crit >= max_crit) {
|
||||
max_crit = usr_crit;
|
||||
crit_sink = std::make_pair(pn, user_idx);
|
||||
}
|
||||
}
|
||||
|
||||
if (crit_sink.first != nullptr) {
|
||||
crit_path.push_front(&(crit_sink.first->users.at(crit_sink.second)));
|
||||
used_ports.insert(&(crit_sink.first->users.at(crit_sink.second)));
|
||||
}
|
||||
back_cursor = crit_sink.first;
|
||||
}
|
||||
// Iterate forwards following greatest criticiality
|
||||
PortRef *fwd_cursor = &(crit_net.first->users.at(crit_net.second));
|
||||
while (fwd_cursor != nullptr) {
|
||||
crit_path.push_back(fwd_cursor);
|
||||
float max_crit = 0;
|
||||
std::pair<NetInfo *, size_t> crit_sink{nullptr, 0};
|
||||
CellInfo *cell = fwd_cursor->cell;
|
||||
for (auto port : cell->ports) {
|
||||
if (port.second.type != PORT_OUT)
|
||||
continue;
|
||||
NetInfo *pn = port.second.net;
|
||||
if (pn == nullptr)
|
||||
continue;
|
||||
if (!net_crit.count(pn->name) || net_crit.at(pn->name).criticality.empty())
|
||||
continue;
|
||||
int ccount;
|
||||
DelayInfo combDelay;
|
||||
TimingPortClass tpclass = ctx->getPortTimingClass(cell, port.first, ccount);
|
||||
if (tpclass != TMG_COMB_OUTPUT && tpclass != TMG_REGISTER_OUTPUT)
|
||||
continue;
|
||||
bool is_path = ctx->getCellDelay(cell, fwd_cursor->port, port.first, combDelay);
|
||||
if (!is_path)
|
||||
continue;
|
||||
auto &crits = net_crit.at(pn->name).criticality;
|
||||
for (size_t i = 0; i < crits.size(); i++) {
|
||||
if (used_ports.count(&(pn->users.at(i))))
|
||||
continue;
|
||||
if (crits.at(i) >= max_crit) {
|
||||
max_crit = crits.at(i);
|
||||
crit_sink = std::make_pair(pn, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (crit_sink.first != nullptr) {
|
||||
fwd_cursor = &(crit_sink.first->users.at(crit_sink.second));
|
||||
used_ports.insert(&(crit_sink.first->users.at(crit_sink.second)));
|
||||
} else {
|
||||
fwd_cursor = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<PortRef *> crit_path_vec;
|
||||
std::copy(crit_path.begin(), crit_path.end(), std::back_inserter(crit_path_vec));
|
||||
crit_paths.push_back(crit_path_vec);
|
||||
}
|
||||
|
||||
return crit_paths;
|
||||
}
|
||||
|
||||
void optimise_path(std::vector<PortRef *> &path)
|
||||
{
|
||||
path_cells.clear();
|
||||
cell_neighbour_bels.clear();
|
||||
bel_candidate_cells.clear();
|
||||
if (ctx->debug)
|
||||
log_info("Optimising the following path: \n");
|
||||
|
||||
auto front_port = path.front();
|
||||
NetInfo *front_net = front_port->cell->ports.at(front_port->port).net;
|
||||
if (front_net != nullptr && front_net->driver.cell != nullptr) {
|
||||
auto front_cell = front_net->driver.cell;
|
||||
if (front_cell->belStrength <= STRENGTH_WEAK && cfg.cellTypes.count(front_cell->type) &&
|
||||
front_cell->constr_parent == nullptr && front_cell->constr_children.empty()) {
|
||||
path_cells.push_back(front_cell->name);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto port : path) {
|
||||
if (ctx->debug) {
|
||||
float crit = 0;
|
||||
NetInfo *pn = port->cell->ports.at(port->port).net;
|
||||
if (net_crit.count(pn->name) && !net_crit.at(pn->name).criticality.empty())
|
||||
for (size_t i = 0; i < pn->users.size(); i++)
|
||||
if (pn->users.at(i).cell == port->cell && pn->users.at(i).port == port->port)
|
||||
crit = net_crit.at(pn->name).criticality.at(i);
|
||||
log_info(" %s.%s at %s crit %0.02f\n", port->cell->name.c_str(ctx), port->port.c_str(ctx),
|
||||
ctx->getBelName(port->cell->bel).c_str(ctx), crit);
|
||||
}
|
||||
if (std::find(path_cells.begin(), path_cells.end(), port->cell->name) != path_cells.end())
|
||||
continue;
|
||||
if (port->cell->belStrength > STRENGTH_WEAK || !cfg.cellTypes.count(port->cell->type) ||
|
||||
port->cell->constr_parent != nullptr || !port->cell->constr_children.empty())
|
||||
continue;
|
||||
if (ctx->debug)
|
||||
log_info(" can move\n");
|
||||
path_cells.push_back(port->cell->name);
|
||||
}
|
||||
|
||||
if (path_cells.size() < 2) {
|
||||
if (ctx->debug) {
|
||||
log_info("Too few moveable cells; skipping path\n");
|
||||
log_break();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate original delay before touching anything
|
||||
delay_t original_delay = 0;
|
||||
|
||||
for (size_t i = 0; i < path.size(); i++) {
|
||||
NetInfo *pn = path.at(i)->cell->ports.at(path.at(i)->port).net;
|
||||
for (size_t j = 0; j < pn->users.size(); j++) {
|
||||
auto &usr = pn->users.at(j);
|
||||
if (usr.cell == path.at(i)->cell && usr.port == path.at(i)->port) {
|
||||
original_delay += ctx->predictDelay(pn, usr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IdString last_cell;
|
||||
const int d = 2; // FIXME: how to best determine d
|
||||
for (auto cell : path_cells) {
|
||||
// FIXME: when should we allow swapping due to a lack of candidates
|
||||
find_neighbours(ctx->cells[cell].get(), last_cell, d, false);
|
||||
last_cell = cell;
|
||||
}
|
||||
|
||||
if (ctx->debug) {
|
||||
for (auto cell : path_cells) {
|
||||
log_info("Candidate neighbours for %s (%s):\n", cell.c_str(ctx),
|
||||
ctx->getBelName(ctx->cells[cell]->bel).c_str(ctx));
|
||||
for (auto neigh : cell_neighbour_bels.at(cell)) {
|
||||
log_info(" %s\n", ctx->getBelName(neigh).c_str(ctx));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Actual BFS path optimisation algorithm
|
||||
std::unordered_map<IdString, std::unordered_map<BelId, delay_t>> cumul_costs;
|
||||
std::unordered_map<std::pair<IdString, BelId>, std::pair<IdString, BelId>> backtrace;
|
||||
std::queue<std::pair<int, BelId>> visit;
|
||||
std::unordered_set<std::pair<int, BelId>> to_visit;
|
||||
|
||||
for (auto startbel : cell_neighbour_bels[path_cells.front()]) {
|
||||
// Swap for legality check
|
||||
CellInfo *cell = ctx->cells.at(path_cells.front()).get();
|
||||
BelId origBel = cell_swap_bel(cell, startbel);
|
||||
std::vector<std::pair<CellInfo *, BelId>> move{std::make_pair(cell, origBel)};
|
||||
if (acceptable_move(move)) {
|
||||
auto entry = std::make_pair(0, startbel);
|
||||
visit.push(entry);
|
||||
cumul_costs[path_cells.front()][startbel] = 0;
|
||||
}
|
||||
// Swap back
|
||||
cell_swap_bel(cell, origBel);
|
||||
}
|
||||
|
||||
while (!visit.empty()) {
|
||||
auto entry = visit.front();
|
||||
visit.pop();
|
||||
auto cellname = path_cells.at(entry.first);
|
||||
if (entry.first == int(path_cells.size()) - 1)
|
||||
continue;
|
||||
std::vector<std::pair<CellInfo *, BelId>> move;
|
||||
// Apply the entire backtrace for accurate legality and delay checks
|
||||
// This is probably pretty expensive (but also probably pales in comparison to the number of swaps
|
||||
// SA will make...)
|
||||
std::vector<std::pair<IdString, BelId>> route_to_entry;
|
||||
auto cursor = std::make_pair(cellname, entry.second);
|
||||
route_to_entry.push_back(cursor);
|
||||
while (backtrace.count(cursor)) {
|
||||
cursor = backtrace.at(cursor);
|
||||
route_to_entry.push_back(cursor);
|
||||
}
|
||||
for (auto rt_entry : boost::adaptors::reverse(route_to_entry)) {
|
||||
CellInfo *cell = ctx->cells.at(rt_entry.first).get();
|
||||
BelId origBel = cell_swap_bel(cell, rt_entry.second);
|
||||
move.push_back(std::make_pair(cell, origBel));
|
||||
}
|
||||
|
||||
// Have a look at where we can travel from here
|
||||
for (auto neighbour : cell_neighbour_bels.at(path_cells.at(entry.first + 1))) {
|
||||
// Edges between overlapping bels are deleted
|
||||
if (neighbour == entry.second)
|
||||
continue;
|
||||
// Experimentally swap the next path cell onto the neighbour bel we are trying
|
||||
IdString ncname = path_cells.at(entry.first + 1);
|
||||
CellInfo *next_cell = ctx->cells.at(ncname).get();
|
||||
BelId origBel = cell_swap_bel(next_cell, neighbour);
|
||||
move.push_back(std::make_pair(next_cell, origBel));
|
||||
|
||||
delay_t total_delay = 0;
|
||||
|
||||
for (size_t i = 0; i < path.size(); i++) {
|
||||
NetInfo *pn = path.at(i)->cell->ports.at(path.at(i)->port).net;
|
||||
for (size_t j = 0; j < pn->users.size(); j++) {
|
||||
auto &usr = pn->users.at(j);
|
||||
if (usr.cell == path.at(i)->cell && usr.port == path.at(i)->port) {
|
||||
total_delay += ctx->predictDelay(pn, usr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (path.at(i)->cell == next_cell)
|
||||
break;
|
||||
}
|
||||
|
||||
// First, check if the move is actually worthwhile from a delay point of view before the expensive
|
||||
// legality check
|
||||
if (!cumul_costs.count(ncname) || !cumul_costs.at(ncname).count(neighbour) ||
|
||||
cumul_costs.at(ncname).at(neighbour) > total_delay) {
|
||||
// Now check that the swaps we have made to get here are legal and meet max delay requirements
|
||||
if (acceptable_move(move)) {
|
||||
cumul_costs[ncname][neighbour] = total_delay;
|
||||
backtrace[std::make_pair(ncname, neighbour)] = std::make_pair(cellname, entry.second);
|
||||
if (!to_visit.count(std::make_pair(entry.first + 1, neighbour)))
|
||||
visit.push(std::make_pair(entry.first + 1, neighbour));
|
||||
}
|
||||
}
|
||||
// Revert the experimental swap
|
||||
cell_swap_bel(move.back().first, move.back().second);
|
||||
move.pop_back();
|
||||
}
|
||||
|
||||
// Revert move by swapping cells back to their original order
|
||||
// Execute swaps in reverse order to how we made them originally
|
||||
for (auto move_entry : boost::adaptors::reverse(move)) {
|
||||
cell_swap_bel(move_entry.first, move_entry.second);
|
||||
}
|
||||
}
|
||||
|
||||
// Did we find a solution??
|
||||
if (cumul_costs.count(path_cells.back())) {
|
||||
// Find the end position with the lowest total delay
|
||||
auto &end_options = cumul_costs.at(path_cells.back());
|
||||
auto lowest = std::min_element(end_options.begin(), end_options.end(),
|
||||
[](const std::pair<BelId, delay_t> &a, const std::pair<BelId, delay_t> &b) {
|
||||
return a.second < b.second;
|
||||
});
|
||||
NPNR_ASSERT(lowest != end_options.end());
|
||||
|
||||
std::vector<std::pair<IdString, BelId>> route_to_solution;
|
||||
auto cursor = std::make_pair(path_cells.back(), lowest->first);
|
||||
route_to_solution.push_back(cursor);
|
||||
while (backtrace.count(cursor)) {
|
||||
cursor = backtrace.at(cursor);
|
||||
route_to_solution.push_back(cursor);
|
||||
}
|
||||
if (ctx->debug)
|
||||
log_info("Found a solution with cost %.02f ns (existing path %.02f ns)\n",
|
||||
ctx->getDelayNS(lowest->second), ctx->getDelayNS(original_delay));
|
||||
for (auto rt_entry : boost::adaptors::reverse(route_to_solution)) {
|
||||
CellInfo *cell = ctx->cells.at(rt_entry.first).get();
|
||||
cell_swap_bel(cell, rt_entry.second);
|
||||
if (ctx->debug)
|
||||
log_info(" %s at %s\n", rt_entry.first.c_str(ctx), ctx->getBelName(rt_entry.second).c_str(ctx));
|
||||
}
|
||||
|
||||
} else {
|
||||
if (ctx->debug)
|
||||
log_info("Solution was not found\n");
|
||||
}
|
||||
if (ctx->debug)
|
||||
log_break();
|
||||
}
|
||||
|
||||
// Current candidate Bels for cells (linked in both direction>
|
||||
std::vector<IdString> path_cells;
|
||||
std::unordered_map<IdString, std::unordered_set<BelId>> cell_neighbour_bels;
|
||||
std::unordered_map<BelId, std::unordered_set<IdString>> bel_candidate_cells;
|
||||
// Map cell ports to net delay limit
|
||||
std::unordered_map<std::pair<IdString, IdString>, delay_t> max_net_delay;
|
||||
// Criticality data from timing analysis
|
||||
NetCriticalityMap net_crit;
|
||||
Context *ctx;
|
||||
TimingOptCfg cfg;
|
||||
};
|
||||
|
||||
bool timing_opt(Context *ctx, TimingOptCfg cfg) { return TimingOptimiser(ctx, cfg).optimise(); }
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
37
common/timing_opt.h
Normal file
37
common/timing_opt.h
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* nextpnr -- Next Generation Place and Route
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "nextpnr.h"
|
||||
#include "settings.h"
|
||||
|
||||
NEXTPNR_NAMESPACE_BEGIN
|
||||
|
||||
struct TimingOptCfg : public Settings
|
||||
{
|
||||
TimingOptCfg(Context *ctx) : Settings(ctx) {}
|
||||
|
||||
// The timing optimiser will *only* optimise cells of these types
|
||||
// Normally these would only be logic cells (or tiles if applicable), the algorithm makes little sense
|
||||
// for other cell types
|
||||
std::unordered_set<IdString> cellTypes;
|
||||
};
|
||||
|
||||
extern bool timing_opt(Context *ctx, TimingOptCfg cfg);
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
@ -670,7 +670,8 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, in
|
||||
}
|
||||
return TMG_IGNORE;
|
||||
} else {
|
||||
NPNR_ASSERT_FALSE_STR("no timing data for cell type '" + cell->type.str(this) + "'");
|
||||
log_error("cell type '%s' is unsupported (instantiated as '%s')\n", cell->type.c_str(this),
|
||||
cell->name.c_str(this));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -298,6 +298,8 @@ class Ecp5GlobalRouter
|
||||
} else {
|
||||
// Check for dedicated routing
|
||||
if (has_short_route(ctx->getBelPinWire(drv_bel, drv.port), ctx->getBelPinWire(dcc->bel, id_CLKI))) {
|
||||
// log_info("dedicated route %s -> %s\n", ctx->getWireName(ctx->getBelPinWire(drv_bel,
|
||||
// drv.port)).c_str(ctx), ctx->getBelName(dcc->bel).c_str(ctx));
|
||||
return 0;
|
||||
}
|
||||
// Driver is locked
|
||||
@ -308,7 +310,7 @@ class Ecp5GlobalRouter
|
||||
}
|
||||
|
||||
// Return true if a short (<5) route exists between two wires
|
||||
bool has_short_route(WireId src, WireId dst, int thresh = 5)
|
||||
bool has_short_route(WireId src, WireId dst, int thresh = 7)
|
||||
{
|
||||
std::queue<WireId> visit;
|
||||
std::unordered_map<WireId, PipId> backtrace;
|
||||
@ -316,7 +318,7 @@ class Ecp5GlobalRouter
|
||||
WireId cursor;
|
||||
while (true) {
|
||||
|
||||
if (visit.empty() || visit.size() > 1000) {
|
||||
if (visit.empty() || visit.size() > 10000) {
|
||||
// log_info ("dist %s -> %s = inf\n", ctx->getWireName(src).c_str(ctx),
|
||||
// ctx->getWireName(dst).c_str(ctx));
|
||||
return false;
|
||||
|
55
ecp5/pack.cc
55
ecp5/pack.cc
@ -1323,6 +1323,60 @@ class Ecp5Packer
|
||||
}
|
||||
}
|
||||
|
||||
// Preplace PLL
|
||||
void preplace_plls()
|
||||
{
|
||||
std::set<BelId> available_plls;
|
||||
for (auto bel : ctx->getBels()) {
|
||||
if (ctx->getBelType(bel) == id_EHXPLLL && ctx->checkBelAvail(bel))
|
||||
available_plls.insert(bel);
|
||||
}
|
||||
for (auto cell : sorted(ctx->cells)) {
|
||||
CellInfo *ci = cell.second;
|
||||
if (ci->type == id_EHXPLLL && ci->attrs.count(ctx->id("BEL")))
|
||||
available_plls.erase(ctx->getBelByName(ctx->id(ci->attrs.at(ctx->id("BEL")))));
|
||||
}
|
||||
// Place PLL connected to fixed drivers such as IO close to their source
|
||||
for (auto cell : sorted(ctx->cells)) {
|
||||
CellInfo *ci = cell.second;
|
||||
if (ci->type == id_EHXPLLL && !ci->attrs.count(ctx->id("BEL"))) {
|
||||
const NetInfo *drivernet = net_or_nullptr(ci, id_CLKI);
|
||||
if (drivernet == nullptr || drivernet->driver.cell == nullptr)
|
||||
continue;
|
||||
const CellInfo *drivercell = drivernet->driver.cell;
|
||||
if (!drivercell->attrs.count(ctx->id("BEL")))
|
||||
continue;
|
||||
BelId drvbel = ctx->getBelByName(ctx->id(drivercell->attrs.at(ctx->id("BEL"))));
|
||||
Loc drvloc = ctx->getBelLocation(drvbel);
|
||||
BelId closest_pll;
|
||||
int closest_distance = std::numeric_limits<int>::max();
|
||||
for (auto bel : available_plls) {
|
||||
Loc pllloc = ctx->getBelLocation(bel);
|
||||
int distance = std::abs(drvloc.x - pllloc.x) + std::abs(drvloc.y - pllloc.y);
|
||||
if (distance < closest_distance) {
|
||||
closest_pll = bel;
|
||||
closest_distance = distance;
|
||||
}
|
||||
}
|
||||
if (closest_pll == BelId())
|
||||
log_error("failed to place PLL '%s'\n", ci->name.c_str(ctx));
|
||||
available_plls.erase(closest_pll);
|
||||
ci->attrs[ctx->id("BEL")] = ctx->getBelName(closest_pll).str(ctx);
|
||||
}
|
||||
}
|
||||
// Place PLLs driven by logic, etc, randomly
|
||||
for (auto cell : sorted(ctx->cells)) {
|
||||
CellInfo *ci = cell.second;
|
||||
if (ci->type == id_EHXPLLL && !ci->attrs.count(ctx->id("BEL"))) {
|
||||
if (available_plls.empty())
|
||||
log_error("failed to place PLL '%s'\n", ci->name.c_str(ctx));
|
||||
BelId next_pll = *(available_plls.begin());
|
||||
available_plls.erase(next_pll);
|
||||
ci->attrs[ctx->id("BEL")] = ctx->getBelName(next_pll).str(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void pack()
|
||||
{
|
||||
@ -1330,6 +1384,7 @@ class Ecp5Packer
|
||||
pack_ebr();
|
||||
pack_dsps();
|
||||
pack_dcus();
|
||||
preplace_plls();
|
||||
pack_constants();
|
||||
pack_dram();
|
||||
pack_carries();
|
||||
|
@ -41,6 +41,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
||||
{
|
||||
QSurfaceFormat fmt;
|
||||
fmt.setSamples(10);
|
||||
fmt.setProfile(QSurfaceFormat::CoreProfile);
|
||||
QSurfaceFormat::setDefaultFormat(fmt);
|
||||
#ifdef _WIN32
|
||||
SetConsoleCtrlHandler((PHANDLER_ROUTINE)WinHandler, TRUE);
|
||||
|
@ -126,6 +126,7 @@ TaskManager::TaskManager() : toTerminate(false), toPause(false)
|
||||
|
||||
TaskManager::~TaskManager()
|
||||
{
|
||||
log_write_function = nullptr;
|
||||
if (workerThread.isRunning())
|
||||
terminate_thread();
|
||||
workerThread.quit();
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include "nextpnr.h"
|
||||
#include "placer1.h"
|
||||
#include "router1.h"
|
||||
#include "timing_opt.h"
|
||||
#include "util.h"
|
||||
|
||||
NEXTPNR_NAMESPACE_BEGIN
|
||||
@ -626,7 +627,18 @@ bool Arch::getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
bool Arch::place() { return placer1(getCtx(), Placer1Cfg(getCtx())); }
|
||||
bool Arch::place()
|
||||
{
|
||||
if (!placer1(getCtx(), Placer1Cfg(getCtx())))
|
||||
return false;
|
||||
if (bool_or_default(settings, id("opt_timing"), false)) {
|
||||
TimingOptCfg tocfg(getCtx());
|
||||
tocfg.cellTypes.insert(id_ICESTORM_LC);
|
||||
return timing_opt(getCtx(), tocfg);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool Arch::route() { return router1(getCtx(), Router1Cfg(getCtx())); }
|
||||
|
||||
@ -950,8 +962,12 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, in
|
||||
if (port == id_RGB0 || port == id_RGB1 || port == id_RGB2)
|
||||
return TMG_IGNORE;
|
||||
return TMG_ENDPOINT;
|
||||
} else if (cell->type == id_SB_LEDDA_IP) {
|
||||
if (port == id_CLK || port == id_CLOCK)
|
||||
return TMG_CLOCK_INPUT;
|
||||
return TMG_IGNORE;
|
||||
}
|
||||
log_error("no timing info for port '%s' of cell type '%s'\n", port.c_str(this), cell->type.c_str(this));
|
||||
log_error("cell type '%s' is unsupported (instantiated as '%s')\n", cell->type.c_str(this), cell->name.c_str(this));
|
||||
}
|
||||
|
||||
TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port, int index) const
|
||||
|
@ -156,7 +156,9 @@ void configure_extra_cell(chipconfig_t &config, const Context *ctx, CellInfo *ce
|
||||
// Lattice's weird string style params, not sure if
|
||||
// prefixes other than 0b should be supported, only 0b features in docs
|
||||
std::string raw = get_param_str_or_def(cell, ctx->id(p.first), "0b0");
|
||||
assert(raw.substr(0, 2) == "0b");
|
||||
if (raw.substr(0, 2) != "0b")
|
||||
log_error("expected configuration string starting with '0b' for parameter '%s' on cell '%s'\n",
|
||||
p.first.c_str(), cell->name.c_str(ctx));
|
||||
raw = raw.substr(2);
|
||||
value.resize(raw.length());
|
||||
for (int i = 0; i < (int)raw.length(); i++) {
|
||||
@ -168,7 +170,13 @@ void configure_extra_cell(chipconfig_t &config, const Context *ctx, CellInfo *ce
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int ival = get_param_or_def(cell, ctx->id(p.first), 0);
|
||||
int ival;
|
||||
try {
|
||||
ival = get_param_or_def(cell, ctx->id(p.first), 0);
|
||||
} catch (std::invalid_argument &e) {
|
||||
log_error("expected numeric value for parameter '%s' on cell '%s'\n", p.first.c_str(),
|
||||
cell->name.c_str(ctx));
|
||||
}
|
||||
|
||||
for (int i = 0; i < p.second; i++)
|
||||
value.push_back((ival >> i) & 0x1);
|
||||
@ -486,7 +494,7 @@ void write_asc(const Context *ctx, std::ostream &out)
|
||||
unsigned pin_type = get_param_or_def(cell.second.get(), ctx->id("PIN_TYPE"));
|
||||
bool neg_trigger = get_param_or_def(cell.second.get(), ctx->id("NEG_TRIGGER"));
|
||||
bool pullup = get_param_or_def(cell.second.get(), ctx->id("PULLUP"));
|
||||
bool lvds = get_param_str_or_def(cell.second.get(), ctx->id("IO_STANDARD")) == "SB_LVDS_INPUT";
|
||||
bool lvds = cell.second->ioInfo.lvds;
|
||||
bool used_by_pll_out = sb_io_used_by_pll_out.count(Loc(x, y, z)) > 0;
|
||||
bool used_by_pll_pad = sb_io_used_by_pll_pad.count(Loc(x, y, z)) > 0;
|
||||
|
||||
@ -495,64 +503,64 @@ void write_asc(const Context *ctx, std::ostream &out)
|
||||
set_config(ti, config.at(y).at(x), "IOB_" + std::to_string(z) + ".PINTYPE_" + std::to_string(i), val);
|
||||
}
|
||||
set_config(ti, config.at(y).at(x), "NegClk", neg_trigger);
|
||||
auto ieren = get_ieren(bi, x, y, z);
|
||||
int iex, iey, iez;
|
||||
std::tie(iex, iey, iez) = ieren;
|
||||
NPNR_ASSERT(iez != -1);
|
||||
|
||||
bool input_en;
|
||||
if (lvds) {
|
||||
input_en = false;
|
||||
pullup = false;
|
||||
} else {
|
||||
if ((ctx->wire_to_net[ctx->getBelPinWire(bel, id_D_IN_0).index] != nullptr) ||
|
||||
(ctx->wire_to_net[ctx->getBelPinWire(bel, id_D_IN_1).index] != nullptr)) {
|
||||
input_en = true;
|
||||
} else {
|
||||
input_en = false;
|
||||
}
|
||||
bool input_en = false;
|
||||
if ((ctx->wire_to_net[ctx->getBelPinWire(bel, id_D_IN_0).index] != nullptr) ||
|
||||
(ctx->wire_to_net[ctx->getBelPinWire(bel, id_D_IN_1).index] != nullptr)) {
|
||||
input_en = true;
|
||||
}
|
||||
|
||||
input_en = (input_en & !used_by_pll_out) | used_by_pll_pad;
|
||||
input_en |= cell.second->ioInfo.global;
|
||||
|
||||
if (ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K) {
|
||||
set_config(ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), !input_en);
|
||||
set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), !pullup);
|
||||
} else {
|
||||
set_config(ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), input_en);
|
||||
set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), !pullup);
|
||||
}
|
||||
|
||||
if (ctx->args.type == ArchArgs::UP5K) {
|
||||
if (iez == 0) {
|
||||
set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_39", !pullup);
|
||||
} else if (iez == 1) {
|
||||
set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_35", !pullup);
|
||||
}
|
||||
}
|
||||
|
||||
if (lvds) {
|
||||
NPNR_ASSERT(z == 0);
|
||||
set_config(ti, config.at(y).at(x), "IoCtrl.LVDS", true);
|
||||
// Set comp IO config
|
||||
auto comp_ieren = get_ieren(bi, x, y, 1);
|
||||
int ciex, ciey, ciez;
|
||||
std::tie(ciex, ciey, ciez) = comp_ieren;
|
||||
if (!lvds) {
|
||||
auto ieren = get_ieren(bi, x, y, z);
|
||||
int iex, iey, iez;
|
||||
std::tie(iex, iey, iez) = ieren;
|
||||
NPNR_ASSERT(iez != -1);
|
||||
|
||||
if (ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K) {
|
||||
set_config(ti, config.at(ciey).at(ciex), "IoCtrl.IE_" + std::to_string(ciez), !input_en);
|
||||
set_config(ti, config.at(ciey).at(ciex), "IoCtrl.REN_" + std::to_string(ciez), !pullup);
|
||||
set_config(ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), !input_en);
|
||||
set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), !pullup);
|
||||
} else {
|
||||
set_config(ti, config.at(ciey).at(ciex), "IoCtrl.IE_" + std::to_string(ciez), input_en);
|
||||
set_config(ti, config.at(ciey).at(ciex), "IoCtrl.REN_" + std::to_string(ciez), !pullup);
|
||||
set_config(ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), input_en);
|
||||
set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), !pullup);
|
||||
}
|
||||
|
||||
if (ctx->args.type == ArchArgs::UP5K) {
|
||||
if (ciez == 0) {
|
||||
set_config(ti, config.at(ciey).at(ciex), "IoCtrl.cf_bit_39", !pullup);
|
||||
if (iez == 0) {
|
||||
set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_39", !pullup);
|
||||
} else if (iez == 1) {
|
||||
set_config(ti, config.at(ciey).at(ciex), "IoCtrl.cf_bit_35", !pullup);
|
||||
set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_35", !pullup);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
NPNR_ASSERT(z == 0);
|
||||
// Only enable the actual LVDS buffer if input is used for something
|
||||
set_config(ti, config.at(y).at(x), "IoCtrl.LVDS", input_en);
|
||||
|
||||
// Set both IO config
|
||||
for (int cz = 0; cz < 2; cz++) {
|
||||
auto ieren = get_ieren(bi, x, y, cz);
|
||||
int iex, iey, iez;
|
||||
std::tie(iex, iey, iez) = ieren;
|
||||
NPNR_ASSERT(iez != -1);
|
||||
|
||||
pullup &= !input_en; /* If input is used, force disable pullups */
|
||||
|
||||
if (ctx->args.type == ArchArgs::LP1K || ctx->args.type == ArchArgs::HX1K) {
|
||||
set_config(ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), true);
|
||||
set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), !pullup);
|
||||
} else {
|
||||
set_config(ti, config.at(iey).at(iex), "IoCtrl.IE_" + std::to_string(iez), false);
|
||||
set_config(ti, config.at(iey).at(iex), "IoCtrl.REN_" + std::to_string(iez), !pullup);
|
||||
}
|
||||
|
||||
if (ctx->args.type == ArchArgs::UP5K) {
|
||||
if (iez == 0) {
|
||||
set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_39", !pullup);
|
||||
} else if (iez == 1) {
|
||||
set_config(ti, config.at(iey).at(iex), "IoCtrl.cf_bit_35", !pullup);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -591,7 +599,8 @@ void write_asc(const Context *ctx, std::ostream &out)
|
||||
{"CURRENT_MODE", 1}, {"RGB0_CURRENT", 6}, {"RGB1_CURRENT", 6}, {"RGB2_CURRENT", 6}};
|
||||
configure_extra_cell(config, ctx, cell.second.get(), rgba_params, true, std::string("IpConfig."));
|
||||
set_ec_cbit(config, ctx, get_ec_config(ctx->chip_info, cell.second->bel), "RGBA_DRV_EN", true, "IpConfig.");
|
||||
} else if (cell.second->type == ctx->id("SB_WARMBOOT") || cell.second->type == ctx->id("ICESTORM_LFOSC")) {
|
||||
} else if (cell.second->type == ctx->id("SB_WARMBOOT") || cell.second->type == ctx->id("ICESTORM_LFOSC") ||
|
||||
cell.second->type == ctx->id("SB_LEDDA_IP")) {
|
||||
// No config needed
|
||||
} else if (cell.second->type == ctx->id("ICESTORM_SPRAM")) {
|
||||
const BelInfoPOD &beli = ci.bel_data[bel.index];
|
||||
@ -688,7 +697,7 @@ void write_asc(const Context *ctx, std::ostream &out)
|
||||
int iex, iey, iez;
|
||||
std::tie(iex, iey, iez) = ieren;
|
||||
if (iez != -1) {
|
||||
// IO is not actually unused if part of an LVDS pair
|
||||
// If IO is in LVDS pair, it will be configured by the other pair
|
||||
if (z == 1) {
|
||||
BelId lvds0 = ctx->getBelByLocation(Loc{x, y, 0});
|
||||
const CellInfo *lvds0cell = ctx->getBoundBelCell(lvds0);
|
||||
|
@ -260,6 +260,21 @@ std::unique_ptr<CellInfo> create_ice_cell(Context *ctx, IdString type, std::stri
|
||||
add_port(ctx, new_cell.get(), "RGB0", PORT_OUT);
|
||||
add_port(ctx, new_cell.get(), "RGB1", PORT_OUT);
|
||||
add_port(ctx, new_cell.get(), "RGB2", PORT_OUT);
|
||||
} else if (type == ctx->id("SB_LEDDA_IP")) {
|
||||
add_port(ctx, new_cell.get(), "LEDDCS", PORT_IN);
|
||||
add_port(ctx, new_cell.get(), "LEDDCLK", PORT_IN);
|
||||
for (int i = 0; i < 8; i++)
|
||||
add_port(ctx, new_cell.get(), "LEDDDAT" + std::to_string(i), PORT_IN);
|
||||
for (int i = 0; i < 3; i++)
|
||||
add_port(ctx, new_cell.get(), "LEDDADDR" + std::to_string(i), PORT_IN);
|
||||
add_port(ctx, new_cell.get(), "LEDDDEN", PORT_IN);
|
||||
add_port(ctx, new_cell.get(), "LEDDEXE", PORT_IN);
|
||||
add_port(ctx, new_cell.get(), "LEDDRST", PORT_IN); // doesn't actually exist, for icecube code compatibility
|
||||
// only
|
||||
add_port(ctx, new_cell.get(), "PWMOUT0", PORT_OUT);
|
||||
add_port(ctx, new_cell.get(), "PWMOUT1", PORT_OUT);
|
||||
add_port(ctx, new_cell.get(), "PWMOUT2", PORT_OUT);
|
||||
add_port(ctx, new_cell.get(), "LEDDON", PORT_OUT);
|
||||
} else {
|
||||
log_error("unable to create iCE40 cell of type %s", type.c_str(ctx));
|
||||
}
|
||||
|
@ -76,6 +76,8 @@ inline bool is_sb_mac16(const BaseCtx *ctx, const CellInfo *cell) { return cell-
|
||||
|
||||
inline bool is_sb_rgba_drv(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_RGBA_DRV"); }
|
||||
|
||||
inline bool is_sb_ledda_ip(const BaseCtx *ctx, const CellInfo *cell) { return cell->type == ctx->id("SB_LEDDA_IP"); }
|
||||
|
||||
inline bool is_sb_pll40(const BaseCtx *ctx, const CellInfo *cell)
|
||||
{
|
||||
return cell->type == ctx->id("SB_PLL40_PAD") || cell->type == ctx->id("SB_PLL40_2_PAD") ||
|
||||
|
107
ice40/chains.cc
107
ice40/chains.cc
@ -62,7 +62,7 @@ class ChainConstrainer
|
||||
bool split_chain = (!ctx->logicCellsCompatible(tile.data(), tile.size())) ||
|
||||
(int(chains.back().cells.size()) > max_length);
|
||||
if (split_chain) {
|
||||
CellInfo *passout = make_carry_pass_out(cell->ports.at(ctx->id("COUT")));
|
||||
CellInfo *passout = make_carry_pass_out((*(curr_cell - 1))->ports.at(ctx->id("COUT")));
|
||||
tile.pop_back();
|
||||
chains.back().cells.back() = passout;
|
||||
start_of_chain = true;
|
||||
@ -74,10 +74,10 @@ class ChainConstrainer
|
||||
(net_only_drives(ctx, carry_net, is_lc, ctx->id("I3"), false) !=
|
||||
net_only_drives(ctx, carry_net, is_lc, ctx->id("CIN"), false)) ||
|
||||
(at_end && !net_only_drives(ctx, carry_net, is_lc, ctx->id("I3"), true))) {
|
||||
CellInfo *passout = make_carry_pass_out(cell->ports.at(ctx->id("COUT")));
|
||||
CellInfo *passout = make_carry_pass_out(cell->ports.at(ctx->id("COUT")),
|
||||
at_end ? nullptr : *(curr_cell + 1));
|
||||
chains.back().cells.push_back(passout);
|
||||
tile.push_back(passout);
|
||||
start_of_chain = true;
|
||||
}
|
||||
}
|
||||
++curr_cell;
|
||||
@ -87,30 +87,75 @@ class ChainConstrainer
|
||||
}
|
||||
|
||||
// Insert a logic cell to legalise a COUT->fabric connection
|
||||
CellInfo *make_carry_pass_out(PortInfo &cout_port)
|
||||
CellInfo *make_carry_pass_out(PortInfo &cout_port, CellInfo *cin_cell = nullptr)
|
||||
{
|
||||
NPNR_ASSERT(cout_port.net != nullptr);
|
||||
std::unique_ptr<CellInfo> lc = create_ice_cell(ctx, ctx->id("ICESTORM_LC"));
|
||||
lc->params[ctx->id("LUT_INIT")] = "65280"; // 0xff00: O = I3
|
||||
lc->params[ctx->id("CARRY_ENABLE")] = "1";
|
||||
lc->ports.at(ctx->id("O")).net = cout_port.net;
|
||||
lc->ports.at(id_O).net = cout_port.net;
|
||||
std::unique_ptr<NetInfo> co_i3_net(new NetInfo());
|
||||
co_i3_net->name = ctx->id(lc->name.str(ctx) + "$I3");
|
||||
co_i3_net->driver = cout_port.net->driver;
|
||||
PortRef i3_r;
|
||||
i3_r.port = ctx->id("I3");
|
||||
i3_r.port = id_I3;
|
||||
i3_r.cell = lc.get();
|
||||
co_i3_net->users.push_back(i3_r);
|
||||
PortRef o_r;
|
||||
o_r.port = ctx->id("O");
|
||||
o_r.port = id_O;
|
||||
o_r.cell = lc.get();
|
||||
cout_port.net->driver = o_r;
|
||||
lc->ports.at(ctx->id("I3")).net = co_i3_net.get();
|
||||
lc->ports.at(id_I3).net = co_i3_net.get();
|
||||
cout_port.net = co_i3_net.get();
|
||||
|
||||
IdString co_i3_name = co_i3_net->name;
|
||||
NPNR_ASSERT(ctx->nets.find(co_i3_name) == ctx->nets.end());
|
||||
ctx->nets[co_i3_name] = std::move(co_i3_net);
|
||||
|
||||
// If COUT also connects to a CIN; preserve the carry chain
|
||||
if (cin_cell) {
|
||||
std::unique_ptr<NetInfo> co_cin_net(new NetInfo());
|
||||
co_cin_net->name = ctx->id(lc->name.str(ctx) + "$COUT");
|
||||
|
||||
// Connect I1 to 1 to preserve carry chain
|
||||
NetInfo *vcc = ctx->nets.at(ctx->id("$PACKER_VCC_NET")).get();
|
||||
lc->ports.at(id_I1).net = vcc;
|
||||
PortRef i1_r;
|
||||
i1_r.port = id_I1;
|
||||
i1_r.cell = lc.get();
|
||||
vcc->users.push_back(i1_r);
|
||||
|
||||
// Connect co_cin_net to the COUT of the LC
|
||||
PortRef co_r;
|
||||
co_r.port = id_COUT;
|
||||
co_r.cell = lc.get();
|
||||
co_cin_net->driver = co_r;
|
||||
lc->ports.at(id_COUT).net = co_cin_net.get();
|
||||
|
||||
// Find the user corresponding to the next CIN
|
||||
int replaced_ports = 0;
|
||||
if (ctx->debug)
|
||||
log_info("cell: %s\n", cin_cell->name.c_str(ctx));
|
||||
for (auto port : {id_CIN, id_I3}) {
|
||||
auto &usr = lc->ports.at(id_O).net->users;
|
||||
if (ctx->debug)
|
||||
for (auto user : usr)
|
||||
log_info("%s.%s\n", user.cell->name.c_str(ctx), user.port.c_str(ctx));
|
||||
auto fnd_user = std::find_if(usr.begin(), usr.end(),
|
||||
[&](const PortRef &pr) { return pr.cell == cin_cell && pr.port == port; });
|
||||
if (fnd_user != usr.end()) {
|
||||
co_cin_net->users.push_back(*fnd_user);
|
||||
usr.erase(fnd_user);
|
||||
cin_cell->ports.at(port).net = co_cin_net.get();
|
||||
++replaced_ports;
|
||||
}
|
||||
}
|
||||
NPNR_ASSERT(replaced_ports > 0);
|
||||
IdString co_cin_name = co_cin_net->name;
|
||||
NPNR_ASSERT(ctx->nets.find(co_cin_name) == ctx->nets.end());
|
||||
ctx->nets[co_cin_name] = std::move(co_cin_net);
|
||||
}
|
||||
|
||||
IdString name = lc->name;
|
||||
ctx->assignCellInfo(lc.get());
|
||||
ctx->cells[lc->name] = std::move(lc);
|
||||
@ -163,29 +208,31 @@ class ChainConstrainer
|
||||
|
||||
void process_carries()
|
||||
{
|
||||
std::vector<CellChain> carry_chains =
|
||||
find_chains(ctx, [](const Context *ctx, const CellInfo *cell) { return is_lc(ctx, cell); },
|
||||
[](const Context *ctx, const
|
||||
std::vector<CellChain> carry_chains = find_chains(
|
||||
ctx, [](const Context *ctx, const CellInfo *cell) { return is_lc(ctx, cell); },
|
||||
[](const Context *ctx, const
|
||||
|
||||
CellInfo *cell) {
|
||||
CellInfo *carry_prev =
|
||||
net_driven_by(ctx, cell->ports.at(ctx->id("CIN")).net, is_lc, ctx->id("COUT"));
|
||||
if (carry_prev != nullptr)
|
||||
return carry_prev;
|
||||
/*CellInfo *i3_prev = net_driven_by(ctx, cell->ports.at(ctx->id("I3")).net, is_lc,
|
||||
ctx->id("COUT")); if (i3_prev != nullptr) return i3_prev;*/
|
||||
return (CellInfo *)nullptr;
|
||||
},
|
||||
[](const Context *ctx, const CellInfo *cell) {
|
||||
CellInfo *carry_next = net_only_drives(ctx, cell->ports.at(ctx->id("COUT")).net, is_lc,
|
||||
ctx->id("CIN"), false);
|
||||
if (carry_next != nullptr)
|
||||
return carry_next;
|
||||
/*CellInfo *i3_next =
|
||||
net_only_drives(ctx, cell->ports.at(ctx->id("COUT")).net, is_lc, ctx->id("I3"),
|
||||
false); if (i3_next != nullptr) return i3_next;*/
|
||||
return (CellInfo *)nullptr;
|
||||
});
|
||||
CellInfo *cell) {
|
||||
CellInfo *carry_prev =
|
||||
net_driven_by(ctx, cell->ports.at(ctx->id("CIN")).net, is_lc, ctx->id("COUT"));
|
||||
if (carry_prev != nullptr)
|
||||
return carry_prev;
|
||||
CellInfo *i3_prev = net_driven_by(ctx, cell->ports.at(ctx->id("I3")).net, is_lc, ctx->id("COUT"));
|
||||
if (i3_prev != nullptr)
|
||||
return i3_prev;
|
||||
return (CellInfo *)nullptr;
|
||||
},
|
||||
[](const Context *ctx, const CellInfo *cell) {
|
||||
CellInfo *carry_next =
|
||||
net_only_drives(ctx, cell->ports.at(ctx->id("COUT")).net, is_lc, ctx->id("CIN"), false);
|
||||
if (carry_next != nullptr)
|
||||
return carry_next;
|
||||
CellInfo *i3_next =
|
||||
net_only_drives(ctx, cell->ports.at(ctx->id("COUT")).net, is_lc, ctx->id("I3"), false);
|
||||
if (i3_next != nullptr)
|
||||
return i3_next;
|
||||
return (CellInfo *)nullptr;
|
||||
});
|
||||
std::unordered_set<IdString> chained;
|
||||
for (auto &base_chain : carry_chains) {
|
||||
for (auto c : base_chain.cells)
|
||||
|
@ -65,6 +65,10 @@ po::options_description Ice40CommandHandler::getArchOptions()
|
||||
specific.add_options()("pcf", po::value<std::string>(), "PCF constraints file to ingest");
|
||||
specific.add_options()("asc", po::value<std::string>(), "asc bitstream file to write");
|
||||
specific.add_options()("read", po::value<std::string>(), "asc bitstream file to read");
|
||||
specific.add_options()("promote-logic",
|
||||
"enable promotion of 'logic' globals (in addition to clk/ce/sr by default)");
|
||||
specific.add_options()("no-promote-globals", "disable all global promotion");
|
||||
specific.add_options()("opt-timing", "run post-placement timing optimisation pass (experimental)");
|
||||
specific.add_options()("tmfuzz", "run path delay estimate fuzzer");
|
||||
return specific;
|
||||
}
|
||||
@ -152,7 +156,15 @@ std::unique_ptr<Context> Ice40CommandHandler::createContext()
|
||||
if (vm.count("package"))
|
||||
chipArgs.package = vm["package"].as<std::string>();
|
||||
|
||||
return std::unique_ptr<Context>(new Context(chipArgs));
|
||||
auto ctx = std::unique_ptr<Context>(new Context(chipArgs));
|
||||
|
||||
if (vm.count("promote-logic"))
|
||||
ctx->settings[ctx->id("promote_logic")] = "1";
|
||||
if (vm.count("no-promote-globals"))
|
||||
ctx->settings[ctx->id("no_promote_globals")] = "1";
|
||||
if (vm.count("opt-timing"))
|
||||
ctx->settings[ctx->id("opt_timing")] = "1";
|
||||
return ctx;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
|
335
ice40/pack.cc
335
ice40/pack.cc
@ -478,6 +478,9 @@ static void pack_io(Context *ctx)
|
||||
}
|
||||
packed_cells.insert(ci->name);
|
||||
std::copy(ci->attrs.begin(), ci->attrs.end(), std::inserter(sb->attrs, sb->attrs.begin()));
|
||||
if (!sb->attrs.count(ctx->id("BEL")))
|
||||
log_warning("IO '%s' is not constrained to a pin and will be automatically placed\n",
|
||||
ci->name.c_str(ctx));
|
||||
} else if (is_sb_io(ctx, ci) || is_sb_gb_io(ctx, ci)) {
|
||||
NetInfo *net = ci->ports.at(ctx->id("PACKAGE_PIN")).net;
|
||||
if ((net != nullptr) && (net->users.size() > 1))
|
||||
@ -518,10 +521,10 @@ static bool is_logic_port(BaseCtx *ctx, const PortRef &port)
|
||||
!is_sb_pll40(ctx, port.cell);
|
||||
}
|
||||
|
||||
static void insert_global(Context *ctx, NetInfo *net, bool is_reset, bool is_cen, bool is_logic)
|
||||
static void insert_global(Context *ctx, NetInfo *net, bool is_reset, bool is_cen, bool is_logic, int fanout)
|
||||
{
|
||||
log_info("promoting %s%s%s%s\n", net->name.c_str(ctx), is_reset ? " [reset]" : "", is_cen ? " [cen]" : "",
|
||||
is_logic ? " [logic]" : "");
|
||||
log_info("promoting %s%s%s%s (fanout %d)\n", net->name.c_str(ctx), is_reset ? " [reset]" : "",
|
||||
is_cen ? " [cen]" : "", is_logic ? " [logic]" : "", fanout);
|
||||
|
||||
std::string glb_name = net->name.str(ctx) + std::string("_$glb_") + (is_reset ? "sr" : (is_cen ? "ce" : "clk"));
|
||||
std::unique_ptr<CellInfo> gb = create_ice_cell(ctx, ctx->id("SB_GB"), "$gbuf_" + glb_name);
|
||||
@ -565,7 +568,8 @@ static void promote_globals(Context *ctx)
|
||||
{
|
||||
log_info("Promoting globals..\n");
|
||||
const int logic_fanout_thresh = 15;
|
||||
const int enable_fanout_thresh = 5;
|
||||
const int enable_fanout_thresh = 15;
|
||||
const int reset_fanout_thresh = 15;
|
||||
std::map<IdString, int> clock_count, reset_count, cen_count, logic_count;
|
||||
for (auto net : sorted(ctx->nets)) {
|
||||
NetInfo *ni = net.second;
|
||||
@ -637,18 +641,20 @@ static void promote_globals(Context *ctx)
|
||||
});
|
||||
if (global_clock->second == 0 && prom_logics < 4 && global_logic->second > logic_fanout_thresh &&
|
||||
(global_logic->second > global_cen->second || prom_cens >= cens_available) &&
|
||||
(global_logic->second > global_reset->second || prom_resets >= resets_available)) {
|
||||
(global_logic->second > global_reset->second || prom_resets >= resets_available) &&
|
||||
bool_or_default(ctx->settings, ctx->id("promote_logic"), false)) {
|
||||
NetInfo *logicnet = ctx->nets[global_logic->first].get();
|
||||
insert_global(ctx, logicnet, false, false, true);
|
||||
insert_global(ctx, logicnet, false, false, true, global_logic->second);
|
||||
++prom_globals;
|
||||
++prom_logics;
|
||||
clock_count.erase(logicnet->name);
|
||||
reset_count.erase(logicnet->name);
|
||||
cen_count.erase(logicnet->name);
|
||||
logic_count.erase(logicnet->name);
|
||||
} else if (global_reset->second > global_clock->second && prom_resets < resets_available) {
|
||||
} else if (global_reset->second > global_clock->second && prom_resets < resets_available &&
|
||||
global_reset->second > reset_fanout_thresh) {
|
||||
NetInfo *rstnet = ctx->nets[global_reset->first].get();
|
||||
insert_global(ctx, rstnet, true, false, false);
|
||||
insert_global(ctx, rstnet, true, false, false, global_reset->second);
|
||||
++prom_globals;
|
||||
++prom_resets;
|
||||
clock_count.erase(rstnet->name);
|
||||
@ -658,7 +664,7 @@ static void promote_globals(Context *ctx)
|
||||
} else if (global_cen->second > global_clock->second && prom_cens < cens_available &&
|
||||
global_cen->second > enable_fanout_thresh) {
|
||||
NetInfo *cennet = ctx->nets[global_cen->first].get();
|
||||
insert_global(ctx, cennet, false, true, false);
|
||||
insert_global(ctx, cennet, false, true, false, global_cen->second);
|
||||
++prom_globals;
|
||||
++prom_cens;
|
||||
clock_count.erase(cennet->name);
|
||||
@ -667,7 +673,7 @@ static void promote_globals(Context *ctx)
|
||||
logic_count.erase(cennet->name);
|
||||
} else if (global_clock->second != 0) {
|
||||
NetInfo *clknet = ctx->nets[global_clock->first].get();
|
||||
insert_global(ctx, clknet, false, false, false);
|
||||
insert_global(ctx, clknet, false, false, false, global_clock->second);
|
||||
++prom_globals;
|
||||
clock_count.erase(clknet->name);
|
||||
reset_count.erase(clknet->name);
|
||||
@ -679,6 +685,202 @@ static void promote_globals(Context *ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// Figure out where to place PLLs
|
||||
static void place_plls(Context *ctx)
|
||||
{
|
||||
std::map<BelId, std::pair<BelPin, BelPin>> pll_all_bels;
|
||||
std::map<BelId, CellInfo *> pll_used_bels;
|
||||
std::vector<CellInfo *> pll_cells;
|
||||
std::map<BelId, CellInfo *> bel2io;
|
||||
|
||||
log_info("Placing PLLs..\n");
|
||||
|
||||
// Find all the PLLs BELs and matching IO sites
|
||||
for (auto bel : ctx->getBels()) {
|
||||
if (ctx->getBelType(bel) != id_ICESTORM_PLL)
|
||||
continue;
|
||||
if (ctx->isBelLocked(bel))
|
||||
continue;
|
||||
|
||||
auto io_a_pin = ctx->getIOBSharingPLLPin(bel, id_PLLOUT_A);
|
||||
auto io_b_pin = ctx->getIOBSharingPLLPin(bel, id_PLLOUT_B);
|
||||
|
||||
pll_all_bels[bel] = std::make_pair(io_a_pin, io_b_pin);
|
||||
}
|
||||
|
||||
// Find all the PLLs cells we need to place and do pre-checks
|
||||
for (auto cell : sorted(ctx->cells)) {
|
||||
CellInfo *ci = cell.second;
|
||||
if (!is_sb_pll40(ctx, ci))
|
||||
continue;
|
||||
|
||||
// If it's constrained already, add to already used list
|
||||
if (ci->attrs.count(ctx->id("BEL"))) {
|
||||
BelId bel_constrain = ctx->getBelByName(ctx->id(ci->attrs[ctx->id("BEL")]));
|
||||
if (pll_all_bels.count(bel_constrain) == 0)
|
||||
log_error("PLL '%s' is constrained to invalid BEL '%s'\n", ci->name.c_str(ctx),
|
||||
ci->attrs[ctx->id("BEL")].c_str());
|
||||
pll_used_bels[bel_constrain] = ci;
|
||||
}
|
||||
|
||||
// Add it to our list of PLLs to process
|
||||
pll_cells.push_back(ci);
|
||||
}
|
||||
|
||||
// Scan all the PAD PLLs
|
||||
for (auto ci : pll_cells) {
|
||||
if (!is_sb_pll40_pad(ctx, ci))
|
||||
continue;
|
||||
|
||||
// Check PACKAGEPIN connection
|
||||
if (!ci->ports.count(ctx->id("PACKAGEPIN")))
|
||||
log_error("PLL '%s' is of PAD type but doesn't have a PACKAGEPIN port\n", ci->name.c_str(ctx));
|
||||
|
||||
NetInfo *ni = ci->ports.at(ctx->id("PACKAGEPIN")).net;
|
||||
if (ni == nullptr || ni->driver.cell == nullptr)
|
||||
log_error("PLL '%s' is of PAD type but doesn't have a valid PACKAGEPIN connection\n", ci->name.c_str(ctx));
|
||||
|
||||
CellInfo *io_cell = ni->driver.cell;
|
||||
if (io_cell->type != id_SB_IO || ni->driver.port != id_D_IN_0)
|
||||
log_error("PLL '%s' has a PACKAGEPIN driven by an %s, should be directly connected to an input "
|
||||
"SB_IO.D_IN_0 port\n",
|
||||
ci->name.c_str(ctx), io_cell->type.c_str(ctx));
|
||||
if (ni->users.size() != 1)
|
||||
log_error("PLL '%s' clock input '%s' can only drive PLL\n", ci->name.c_str(ctx), ni->name.c_str(ctx));
|
||||
if (!io_cell->attrs.count(ctx->id("BEL")))
|
||||
log_error("PLL '%s' PACKAGEPIN SB_IO '%s' is unconstrained\n", ci->name.c_str(ctx),
|
||||
io_cell->name.c_str(ctx));
|
||||
|
||||
BelId io_bel = ctx->getBelByName(ctx->id(io_cell->attrs.at(ctx->id("BEL"))));
|
||||
BelId found_bel;
|
||||
|
||||
// Find the PLL BEL that would suit that connection
|
||||
for (auto pll_bel : pll_all_bels) {
|
||||
if (std::get<0>(pll_bel.second).bel == io_bel) {
|
||||
found_bel = pll_bel.first;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found_bel == BelId())
|
||||
log_error("PLL '%s' PACKAGEPIN SB_IO '%s' is not connected to any PLL BEL\n", ci->name.c_str(ctx),
|
||||
io_cell->name.c_str(ctx));
|
||||
if (pll_used_bels.count(found_bel)) {
|
||||
CellInfo *conflict_cell = pll_used_bels.at(found_bel);
|
||||
log_error("PLL '%s' PACKAGEPIN forces it to BEL %s but BEL is already assigned to PLL '%s'\n",
|
||||
ci->name.c_str(ctx), ctx->getBelName(found_bel).c_str(ctx), conflict_cell->name.c_str(ctx));
|
||||
}
|
||||
|
||||
// Is it user constrained ?
|
||||
if (ci->attrs.count(ctx->id("BEL"))) {
|
||||
// Yes. Check it actually matches !
|
||||
BelId bel_constrain = ctx->getBelByName(ctx->id(ci->attrs[ctx->id("BEL")]));
|
||||
if (bel_constrain != found_bel)
|
||||
log_error("PLL '%s' is user constrained to %s but can only be placed in %s based on its PACKAGEPIN "
|
||||
"connection\n",
|
||||
ci->name.c_str(ctx), ctx->getBelName(bel_constrain).c_str(ctx),
|
||||
ctx->getBelName(found_bel).c_str(ctx));
|
||||
} else {
|
||||
// No, we can constrain it ourselves
|
||||
ci->attrs[ctx->id("BEL")] = ctx->getBelName(found_bel).str(ctx);
|
||||
pll_used_bels[found_bel] = ci;
|
||||
}
|
||||
|
||||
// Inform user
|
||||
log_info(" constrained PLL '%s' to %s\n", ci->name.c_str(ctx), ctx->getBelName(found_bel).c_str(ctx));
|
||||
}
|
||||
|
||||
// Scan all SB_IOs to check for conflict with PLL BELs
|
||||
for (auto io_cell : sorted(ctx->cells)) {
|
||||
CellInfo *io_ci = io_cell.second;
|
||||
if (!is_sb_io(ctx, io_ci))
|
||||
continue;
|
||||
|
||||
// Only consider bound IO that are used as inputs
|
||||
if (!io_ci->attrs.count(ctx->id("BEL")))
|
||||
continue;
|
||||
if ((!io_ci->ports.count(id_D_IN_0) || (io_ci->ports[id_D_IN_0].net == nullptr)) &&
|
||||
(!io_ci->ports.count(id_D_IN_1) || (io_ci->ports[id_D_IN_1].net == nullptr)))
|
||||
continue;
|
||||
|
||||
// Check all placed PLL (either forced by user, or forced by PACKAGEPIN)
|
||||
BelId io_bel = ctx->getBelByName(ctx->id(io_ci->attrs[ctx->id("BEL")]));
|
||||
|
||||
for (auto placed_pll : pll_used_bels) {
|
||||
BelPin pll_io_a, pll_io_b;
|
||||
std::tie(pll_io_a, pll_io_b) = pll_all_bels[placed_pll.first];
|
||||
if (io_bel == pll_io_a.bel) {
|
||||
// All the PAD type PLL stuff already checked above,so only
|
||||
// check for conflict with a user placed CORE PLL
|
||||
if (!is_sb_pll40_pad(ctx, placed_pll.second))
|
||||
log_error("PLL '%s' A output conflict with SB_IO '%s' that's used as input\n",
|
||||
placed_pll.second->name.c_str(ctx), io_cell.second->name.c_str(ctx));
|
||||
} else if (io_bel == pll_io_b.bel) {
|
||||
if (is_sb_pll40_dual(ctx, placed_pll.second))
|
||||
log_error("PLL '%s' B output conflicts with SB_IO '%s' that's used as input\n",
|
||||
placed_pll.second->name.c_str(ctx), io_cell.second->name.c_str(ctx));
|
||||
}
|
||||
}
|
||||
|
||||
// Save for later checks
|
||||
bel2io[io_bel] = io_ci;
|
||||
}
|
||||
|
||||
// Scan all the CORE PLLs and place them in remaining available PLL BELs
|
||||
// (in two pass ... first do the dual ones, harder to place, then single port)
|
||||
for (int i = 0; i < 2; i++) {
|
||||
for (auto ci : pll_cells) {
|
||||
if (is_sb_pll40_pad(ctx, ci))
|
||||
continue;
|
||||
if (is_sb_pll40_dual(ctx, ci) ^ i)
|
||||
continue;
|
||||
|
||||
// Check REFERENCECLK connection
|
||||
if (!ci->ports.count(id_REFERENCECLK))
|
||||
log_error("PLL '%s' is of CORE type but doesn't have a REFERENCECLK port\n", ci->name.c_str(ctx));
|
||||
|
||||
NetInfo *ni = ci->ports.at(id_REFERENCECLK).net;
|
||||
if (ni == nullptr || ni->driver.cell == nullptr)
|
||||
log_error("PLL '%s' is of CORE type but doesn't have a valid REFERENCECLK connection\n",
|
||||
ci->name.c_str(ctx));
|
||||
|
||||
// Could this be a PAD PLL ?
|
||||
bool could_be_pad = false;
|
||||
BelId pad_bel;
|
||||
if (ni->users.size() == 1 && is_sb_io(ctx, ni->driver.cell) && ni->driver.cell->attrs.count(ctx->id("BEL")))
|
||||
pad_bel = ctx->getBelByName(ctx->id(ni->driver.cell->attrs[ctx->id("BEL")]));
|
||||
|
||||
// Find a BEL for it
|
||||
BelId found_bel;
|
||||
for (auto bel_pll : pll_all_bels) {
|
||||
BelPin pll_io_a, pll_io_b;
|
||||
std::tie(pll_io_a, pll_io_b) = bel_pll.second;
|
||||
if (bel2io.count(pll_io_a.bel)) {
|
||||
if (pll_io_a.bel == pad_bel)
|
||||
could_be_pad = !bel2io.count(pll_io_b.bel) || !is_sb_pll40_dual(ctx, ci);
|
||||
continue;
|
||||
}
|
||||
if (bel2io.count(pll_io_b.bel) && is_sb_pll40_dual(ctx, ci))
|
||||
continue;
|
||||
found_bel = bel_pll.first;
|
||||
break;
|
||||
}
|
||||
|
||||
// Apply constrain & Inform user of result
|
||||
if (found_bel == BelId())
|
||||
log_error("PLL '%s' couldn't be placed anywhere, no suitable BEL found.%s\n", ci->name.c_str(ctx),
|
||||
could_be_pad ? " Did you mean to use a PAD PLL ?" : "");
|
||||
|
||||
log_info(" constrained PLL '%s' to %s\n", ci->name.c_str(ctx), ctx->getBelName(found_bel).c_str(ctx));
|
||||
if (could_be_pad)
|
||||
log_info(" (given its connections, this PLL could have been a PAD PLL)\n");
|
||||
|
||||
ci->attrs[ctx->id("BEL")] = ctx->getBelName(found_bel).str(ctx);
|
||||
pll_used_bels[found_bel] = ci;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// spliceLUT adds a pass-through LUT LC between the given cell's output port
|
||||
// and either all users or only non_LUT users.
|
||||
static std::unique_ptr<CellInfo> spliceLUT(Context *ctx, CellInfo *ci, IdString portId, bool onlyNonLUTs)
|
||||
@ -844,6 +1046,10 @@ static void pack_special(Context *ctx)
|
||||
ci->ports.erase(ctx->id("RGB0"));
|
||||
ci->ports.erase(ctx->id("RGB1"));
|
||||
ci->ports.erase(ctx->id("RGB2"));
|
||||
} else if (is_sb_ledda_ip(ctx, ci)) {
|
||||
/* Force placement (no choices anyway) */
|
||||
cell_place_unique(ctx, ci);
|
||||
|
||||
} else if (is_sb_pll40(ctx, ci)) {
|
||||
bool is_pad = is_sb_pll40_pad(ctx, ci);
|
||||
bool is_core = !is_pad;
|
||||
@ -877,13 +1083,17 @@ static void pack_special(Context *ctx)
|
||||
}
|
||||
|
||||
auto feedback_path = packed->params[ctx->id("FEEDBACK_PATH")];
|
||||
packed->params[ctx->id("FEEDBACK_PATH")] =
|
||||
feedback_path == "DELAY"
|
||||
? "0"
|
||||
: feedback_path == "SIMPLE" ? "1"
|
||||
: feedback_path == "PHASE_AND_DELAY"
|
||||
? "2"
|
||||
: feedback_path == "EXTERNAL" ? "6" : feedback_path;
|
||||
std::string fbp_value = feedback_path == "DELAY"
|
||||
? "0"
|
||||
: feedback_path == "SIMPLE"
|
||||
? "1"
|
||||
: feedback_path == "PHASE_AND_DELAY"
|
||||
? "2"
|
||||
: feedback_path == "EXTERNAL" ? "6" : feedback_path;
|
||||
if (!std::all_of(fbp_value.begin(), fbp_value.end(), isdigit))
|
||||
log_error("PLL '%s' has unsupported FEEDBACK_PATH value '%s'\n", ci->name.c_str(ctx),
|
||||
feedback_path.c_str());
|
||||
packed->params[ctx->id("FEEDBACK_PATH")] = fbp_value;
|
||||
packed->params[ctx->id("PLLTYPE")] = std::to_string(sb_pll40_type(ctx, ci));
|
||||
|
||||
NetInfo *pad_packagepin_net = nullptr;
|
||||
@ -939,82 +1149,25 @@ static void pack_special(Context *ctx)
|
||||
replace_port(ci, ctx->id(pi.name.c_str(ctx)), packed.get(), ctx->id(newname));
|
||||
}
|
||||
|
||||
// If PLL is not constrained already, do that - we need this
|
||||
// information to then constrain the LOCK LUT.
|
||||
BelId pll_bel;
|
||||
bool constrained = false;
|
||||
if (packed->attrs.find(ctx->id("BEL")) == packed->attrs.end()) {
|
||||
for (auto bel : ctx->getBels()) {
|
||||
if (ctx->getBelType(bel) != id_ICESTORM_PLL)
|
||||
continue;
|
||||
if (ctx->isBelLocked(bel))
|
||||
continue;
|
||||
// PLL must have been placed already in place_plls()
|
||||
BelId pll_bel = ctx->getBelByName(ctx->id(packed->attrs[ctx->id("BEL")]));
|
||||
NPNR_ASSERT(pll_bel != BelId());
|
||||
|
||||
// A PAD PLL must have its' PACKAGEPIN on the SB_IO that's shared
|
||||
// with PLLOUT_A.
|
||||
if (is_pad) {
|
||||
auto pll_sb_io_belpin = ctx->getIOBSharingPLLPin(bel, id_PLLOUT_A);
|
||||
NPNR_ASSERT(pad_packagepin_net != nullptr);
|
||||
auto pll_packagepin_driver = pad_packagepin_net->driver;
|
||||
NPNR_ASSERT(pll_packagepin_driver.cell != nullptr);
|
||||
if (pll_packagepin_driver.cell->type != ctx->id("SB_IO")) {
|
||||
log_error("PLL '%s' has a PACKAGEPIN driven by "
|
||||
"an %s, should be directly connected to an input SB_IO\n",
|
||||
ci->name.c_str(ctx), pll_packagepin_driver.cell->type.c_str(ctx));
|
||||
}
|
||||
// Deal with PAD PLL peculiarities
|
||||
if (is_pad) {
|
||||
NPNR_ASSERT(pad_packagepin_net != nullptr);
|
||||
auto pll_packagepin_driver = pad_packagepin_net->driver;
|
||||
NPNR_ASSERT(pll_packagepin_driver.cell != nullptr);
|
||||
auto packagepin_cell = pll_packagepin_driver.cell;
|
||||
auto packagepin_bel_name = packagepin_cell->attrs.find(ctx->id("BEL"));
|
||||
|
||||
auto packagepin_cell = pll_packagepin_driver.cell;
|
||||
auto packagepin_bel_name = packagepin_cell->attrs.find(ctx->id("BEL"));
|
||||
if (packagepin_bel_name == packagepin_cell->attrs.end()) {
|
||||
log_error("PLL '%s' PACKAGEPIN SB_IO '%s' is unconstrained\n", ci->name.c_str(ctx),
|
||||
packagepin_cell->name.c_str(ctx));
|
||||
}
|
||||
auto packagepin_bel = ctx->getBelByName(ctx->id(packagepin_bel_name->second));
|
||||
if (pll_sb_io_belpin.bel != packagepin_bel) {
|
||||
log_error("PLL '%s' PACKAGEPIN is connected to pin %s, can only be pin %s\n",
|
||||
ci->name.c_str(ctx), ctx->getBelPackagePin(packagepin_bel).c_str(),
|
||||
ctx->getBelPackagePin(pll_sb_io_belpin.bel).c_str());
|
||||
}
|
||||
if (pad_packagepin_net->users.size() != 1) {
|
||||
log_error("PLL '%s' clock input '%s' can only drive PLL\n", ci->name.c_str(ctx),
|
||||
pad_packagepin_net->name.c_str(ctx));
|
||||
}
|
||||
// Set an attribute about this PLL's PAD SB_IO.
|
||||
packed->attrs[ctx->id("BEL_PAD_INPUT")] = packagepin_bel_name->second;
|
||||
// Remove the connection from the SB_IO to the PLL.
|
||||
packagepin_cell->ports.erase(pll_packagepin_driver.port);
|
||||
}
|
||||
// Set an attribute about this PLL's PAD SB_IO.
|
||||
packed->attrs[ctx->id("BEL_PAD_INPUT")] = packagepin_bel_name->second;
|
||||
|
||||
log_info(" constrained PLL '%s' to %s\n", packed->name.c_str(ctx),
|
||||
ctx->getBelName(bel).c_str(ctx));
|
||||
packed->attrs[ctx->id("BEL")] = ctx->getBelName(bel).str(ctx);
|
||||
pll_bel = bel;
|
||||
constrained = true;
|
||||
break;
|
||||
}
|
||||
if (!constrained) {
|
||||
log_error("Could not constrain PLL '%s' to any PLL Bel (too many PLLs?)\n",
|
||||
packed->name.c_str(ctx));
|
||||
}
|
||||
} else {
|
||||
pll_bel = ctx->getBelByName(ctx->id(packed->attrs[ctx->id("BEL")]));
|
||||
if (ctx->getBelType(pll_bel) != id_ICESTORM_PLL)
|
||||
log_error("PLL '%s' is constrained to BEL %s which isn't a ICESTORM_PLL BEL\n",
|
||||
packed->name.c_str(ctx), ctx->getBelName(pll_bel).c_str(ctx));
|
||||
if (ctx->isBelLocked(pll_bel))
|
||||
log_error("PLL '%s' is constrained to locked BEL %s\n", packed->name.c_str(ctx),
|
||||
ctx->getBelName(pll_bel).c_str(ctx));
|
||||
log_info(" constrained PLL '%s' to %s\n", packed->name.c_str(ctx),
|
||||
ctx->getBelName(pll_bel).c_str(ctx));
|
||||
}
|
||||
|
||||
// Delete the original PACKAGEPIN net if needed.
|
||||
if (pad_packagepin_net != nullptr) {
|
||||
for (auto user : pad_packagepin_net->users) {
|
||||
// Disconnect PACKAGEPIN (it's a physical HW link)
|
||||
for (auto user : pad_packagepin_net->users)
|
||||
user.cell->ports.erase(user.port);
|
||||
}
|
||||
if (pad_packagepin_net->driver.cell != nullptr)
|
||||
pad_packagepin_net->driver.cell->ports.erase(pad_packagepin_net->driver.port);
|
||||
packagepin_cell->ports.erase(pll_packagepin_driver.port);
|
||||
ctx->nets.erase(pad_packagepin_net->name);
|
||||
pad_packagepin_net = nullptr;
|
||||
}
|
||||
@ -1118,8 +1271,10 @@ bool Arch::pack()
|
||||
pack_nonlut_ffs(ctx);
|
||||
pack_carries(ctx);
|
||||
pack_ram(ctx);
|
||||
place_plls(ctx);
|
||||
pack_special(ctx);
|
||||
promote_globals(ctx);
|
||||
if (!bool_or_default(ctx->settings, ctx->id("no_promote_globals"), false))
|
||||
promote_globals(ctx);
|
||||
ctx->assignArchInfo();
|
||||
constrain_chains(ctx);
|
||||
ctx->assignArchInfo();
|
||||
|
@ -22,7 +22,7 @@ for i in range(num_runs):
|
||||
ascfile = "picorv32_work/picorv32_s{}.asc".format(run)
|
||||
if path.exists(ascfile):
|
||||
os.remove(ascfile)
|
||||
result = subprocess.run(["../nextpnr-ice40", "--hx8k", "--seed", str(run), "--json", "picorv32.json", "--asc", ascfile, "--freq", "70"], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
|
||||
result = subprocess.run(["../nextpnr-ice40", "--hx8k", "--seed", str(run), "--json", "picorv32.json", "--asc", ascfile, "--freq", "40", "--opt-timing"], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
|
||||
if result.returncode != 0:
|
||||
print("Run {} failed!".format(run))
|
||||
else:
|
||||
|
@ -594,7 +594,11 @@ void json_import_cell(Context *ctx, string modname, const std::vector<IdString>
|
||||
if (type == PORT_IN || type == PORT_INOUT) {
|
||||
net->users.push_back(pr);
|
||||
} else if (type == PORT_OUT) {
|
||||
assert(net->driver.cell == nullptr);
|
||||
if (net->driver.cell != nullptr)
|
||||
log_error("multiple drivers on net '%s' (%s.%s and %s.%s)\n",
|
||||
net->name.c_str(ctx), net->driver.cell->name.c_str(ctx),
|
||||
net->driver.port.c_str(ctx), pr.cell->name.c_str(ctx),
|
||||
pr.port.c_str(ctx));
|
||||
net->driver = pr;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user